metame-cli 1.5.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -78,6 +78,27 @@ function findProcessesByPattern(pattern) {
78
78
  return pids;
79
79
  }
80
80
 
81
+ /**
82
+ * Kill a process and its entire child tree.
83
+ * POSIX: process.kill(-pid, signal) to kill process group.
84
+ * Windows: taskkill /PID /T /F (tree kill).
85
+ * Returns true if kill was attempted, false if process not found.
86
+ */
87
+ function killProcessTree(pid, signal = 'SIGTERM') {
88
+ if (!pid || pid <= 0) return false;
89
+ try {
90
+ if (IS_WIN) {
91
+ execSync(`taskkill /PID ${pid} /T /F`, { stdio: 'ignore', windowsHide: true, timeout: 5000 });
92
+ } else {
93
+ // Try process group first, fallback to single process
94
+ try { process.kill(-pid, signal); } catch { process.kill(pid, signal); }
95
+ }
96
+ return true;
97
+ } catch {
98
+ return false;
99
+ }
100
+ }
101
+
81
102
  /**
82
103
  * Whether the socket path needs fs.unlinkSync before server.listen().
83
104
  * Named Pipes on Windows are kernel-managed — no file to unlink.
@@ -169,6 +190,7 @@ module.exports = {
169
190
  socketPath,
170
191
  sleepSync,
171
192
  findProcessesByPattern,
193
+ killProcessTree,
172
194
  needsSocketCleanup,
173
195
  icon,
174
196
  };
@@ -331,10 +331,22 @@ function callDistillModel(input, extraEnv, timeout, options = {}) {
331
331
  const args = engine === 'codex'
332
332
  ? ['exec', '--json', '-m', model, '--full-auto', '-']
333
333
  : ['-p', '--model', model, '--no-session-persistence'];
334
+ // On Windows, bare binary names need shell:true to resolve .cmd wrappers.
335
+ // For codex, also sanitize CODEX_HOME if it points to a non-existent path.
336
+ const isWin = process.platform === 'win32';
337
+ if (isWin && engine === 'codex' && env.CODEX_HOME && !fs.existsSync(env.CODEX_HOME)) {
338
+ delete env.CODEX_HOME;
339
+ }
340
+ const spawnOpts = {
341
+ env,
342
+ timeout,
343
+ maxBuffer: 10 * 1024 * 1024,
344
+ ...(isWin ? { shell: process.env.COMSPEC || true, windowsHide: true } : {}),
345
+ };
334
346
  return new Promise((resolve, reject) => {
335
347
  const proc = execFile(
336
348
  bin, args,
337
- { env, timeout, maxBuffer: 10 * 1024 * 1024 },
349
+ spawnOpts,
338
350
  (err, stdout, stderr) => {
339
351
  if (err) {
340
352
  const detail = (stderr || stdout || '').trim().split('\n')[0];
@@ -370,7 +382,7 @@ function callDistillModel(input, extraEnv, timeout, options = {}) {
370
382
  // ---------------------------------------------------------
371
383
  // ENGINE AWARENESS (set by daemon.js setDefaultEngine)
372
384
  // ---------------------------------------------------------
373
- let _currentEngine = 'claude';
385
+ let _currentEngine = process.env.METAME_ENGINE === 'codex' ? 'codex' : 'claude';
374
386
  function setEngine(name) { _currentEngine = (name === 'codex') ? 'codex' : 'claude'; }
375
387
  function getEngine() { return _currentEngine; }
376
388
 
@@ -12,6 +12,8 @@ const path = require('path');
12
12
 
13
13
  const API_BASE = 'https://api.telegram.org';
14
14
 
15
+ const { StringDecoder } = require('string_decoder');
16
+
15
17
  /**
16
18
  * Make an HTTPS request to Telegram Bot API
17
19
  */
@@ -34,8 +36,10 @@ function apiRequest(token, method, params = {}, timeout = 10000, signal = null)
34
36
 
35
37
  const req = https.request(options, (res) => {
36
38
  let data = '';
37
- res.on('data', (chunk) => { data += chunk; });
39
+ const decoder = new StringDecoder('utf8');
40
+ res.on('data', (chunk) => { data += decoder.write(chunk); });
38
41
  res.on('end', () => {
42
+ data += decoder.end();
39
43
  try {
40
44
  const parsed = JSON.parse(data);
41
45
  if (parsed.ok) {
@@ -252,7 +256,7 @@ function createBot(token) {
252
256
  resolve(destPath);
253
257
  });
254
258
  fileStream.on('error', (err) => {
255
- fs.unlink(destPath, () => {});
259
+ fs.unlink(destPath, () => { });
256
260
  reject(err);
257
261
  });
258
262
  }).on('error', reject);
@@ -324,7 +328,7 @@ function createBot(token) {
324
328
  });
325
329
  },
326
330
 
327
- };
331
+ };
328
332
  }
329
333
 
330
334
  /**
@@ -369,11 +373,11 @@ function toTelegramMarkdownV2(md) {
369
373
  while ((m = pattern.exec(md)) !== null) {
370
374
  if (m.index > last) out += escapePlain(md.slice(last, m.index));
371
375
 
372
- if (m[1] !== undefined) out += '```' + m[1].replace(/[`\\]/g, '\\$&') + '```';
373
- else if (m[2] !== undefined) out += '`' + m[2].replace(/[`\\]/g, '\\$&') + '`';
374
- else if (m[3] !== undefined) out += '*' + toTelegramMarkdownV2(m[3]) + '*';
375
- else if (m[4] !== undefined) out += '_' + toTelegramMarkdownV2(m[4]) + '_';
376
- else if (m[5] !== undefined) out += '_' + toTelegramMarkdownV2(m[5]) + '_';
376
+ if (m[1] !== undefined) out += '```' + m[1].replace(/[`\\]/g, '\\$&') + '```';
377
+ else if (m[2] !== undefined) out += '`' + m[2].replace(/[`\\]/g, '\\$&') + '`';
378
+ else if (m[3] !== undefined) out += '*' + toTelegramMarkdownV2(m[3]) + '*';
379
+ else if (m[4] !== undefined) out += '_' + toTelegramMarkdownV2(m[4]) + '_';
380
+ else if (m[5] !== undefined) out += '_' + toTelegramMarkdownV2(m[5]) + '_';
377
381
  else if (m[6] !== undefined) out += '[' + toTelegramMarkdownV2(m[6]) + '](' + m[7].replace(/[()\\]/g, '\\$&') + ')';
378
382
  else if (m[9] !== undefined) out += '*' + escapePlain(m[9]) + '*';
379
383
  else if (m[10] !== undefined) {