@mmmbuto/nexuscli 0.9.0 → 0.9.2

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.
@@ -59,8 +59,8 @@
59
59
 
60
60
  <!-- Prevent Scaling on iOS -->
61
61
  <meta name="format-detection" content="telephone=no" />
62
- <script type="module" crossorigin src="/assets/index-DFYYfeuX.js"></script>
63
- <link rel="stylesheet" crossorigin href="/assets/index-Dl9FJBOB.css">
62
+ <script type="module" crossorigin src="/assets/index-x6Jl2qtq.js"></script>
63
+ <link rel="stylesheet" crossorigin href="/assets/index-Ci39i_2l.css">
64
64
  </head>
65
65
  <body>
66
66
  <div id="root"></div>
@@ -1,5 +1,5 @@
1
1
  // NexusCLI Service Worker
2
- const CACHE_VERSION = 'nexuscli-v1';
2
+ const CACHE_VERSION = 'nexuscli-v1766155108128';
3
3
  const STATIC_CACHE = `${CACHE_VERSION}-static`;
4
4
  const DYNAMIC_CACHE = `${CACHE_VERSION}-dynamic`;
5
5
 
@@ -5,7 +5,7 @@
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "dev": "vite",
8
- "build": "vite build",
8
+ "build": "vite build && node scripts/inject-sw-version.js",
9
9
  "preview": "vite preview"
10
10
  },
11
11
  "dependencies": {
package/lib/cli/api.js CHANGED
@@ -32,6 +32,12 @@ const SUPPORTED_PROVIDERS = {
32
32
  description: 'Multi-provider gateway',
33
33
  keyFormat: 'sk-or-*',
34
34
  url: 'https://openrouter.ai/keys'
35
+ },
36
+ zai: {
37
+ name: 'Z.ai',
38
+ description: 'GLM-4.6 (Chinese/English Multilingual)',
39
+ keyFormat: 'starts with alphanumeric + dot',
40
+ url: 'https://z.ai'
35
41
  }
36
42
  };
37
43
 
@@ -59,6 +59,14 @@ function getCliTools() {
59
59
  label: 'DeepSeek Chat',
60
60
  description: '💬 Fast Chat',
61
61
  category: 'claude'
62
+ },
63
+ // === GLM-4.6 (Z.ai) ===
64
+ {
65
+ id: 'glm-4-6',
66
+ name: 'glm-4-6',
67
+ label: 'GLM 4.6',
68
+ description: '🌍 Advanced Chinese/English Multilingual',
69
+ category: 'claude'
62
70
  }
63
71
  ]
64
72
  },
@@ -29,11 +29,13 @@ function spawn(command, args, options = {}) {
29
29
 
30
30
  proc.stdout.on('data', (buf) => {
31
31
  const data = buf.toString();
32
+ console.log('[PTY-Adapter] stdout:', data.substring(0, 200));
32
33
  dataHandlers.forEach((fn) => fn(data));
33
34
  });
34
35
 
35
36
  proc.stderr.on('data', (buf) => {
36
37
  const data = buf.toString();
38
+ console.log('[PTY-Adapter] stderr:', data.substring(0, 200));
37
39
  dataHandlers.forEach((fn) => fn(data));
38
40
  });
39
41
 
@@ -163,15 +163,25 @@ class ClaudeWrapper extends BaseCliWrapper {
163
163
  // Check if this is an existing session (DB is source of truth)
164
164
  const isExistingSession = this.isExistingSession(conversationId);
165
165
 
166
+ // Detect alternative models early (needed for args construction)
167
+ const isDeepSeek = model.startsWith('deepseek-');
168
+ const isGLM = model === 'glm-4-6';
169
+ const isAlternativeModel = isDeepSeek || isGLM;
170
+
166
171
  // Build Claude Code CLI args
167
172
  const args = [
168
173
  '--dangerously-skip-permissions', // Auto-approve all tool use
169
- '--model', model,
170
174
  '--print', // Non-interactive mode
171
175
  '--verbose', // Enable detailed output
172
176
  '--output-format', 'stream-json', // JSON streaming events
173
177
  ];
174
178
 
179
+ // Only pass --model for native Claude models
180
+ // Alternative models (DeepSeek, GLM) use ANTHROPIC_MODEL env var
181
+ if (!isAlternativeModel) {
182
+ args.push('--model', model);
183
+ }
184
+
175
185
  // Session management: -r (resume) or --session-id (new)
176
186
  if (isExistingSession) {
177
187
  args.push('-r', conversationId); // Resume with full history
@@ -187,9 +197,8 @@ class ClaudeWrapper extends BaseCliWrapper {
187
197
  // Termux compatibility: make sure ripgrep path exists before spawn
188
198
  this.ensureRipgrepForTermux();
189
199
 
190
- // Build environment - detect DeepSeek models and configure API accordingly
200
+ // Build environment - configure API for alternative models
191
201
  const spawnEnv = { ...process.env };
192
- const isDeepSeek = model.startsWith('deepseek-');
193
202
 
194
203
  if (isDeepSeek) {
195
204
  // Get API key from database (priority) or fallback to env var
@@ -217,10 +226,40 @@ class ClaudeWrapper extends BaseCliWrapper {
217
226
  // DeepSeek uses Anthropic-compatible API at different endpoint
218
227
  spawnEnv.ANTHROPIC_BASE_URL = 'https://api.deepseek.com/anthropic';
219
228
  spawnEnv.ANTHROPIC_AUTH_TOKEN = deepseekKey;
229
+ spawnEnv.ANTHROPIC_MODEL = model; // Pass model name to API
220
230
  console.log(`[ClaudeWrapper] DeepSeek detected - using api.deepseek.com/anthropic`);
231
+ } else if (isGLM) {
232
+ // Get API key from database (priority) or fallback to env var
233
+ const glmKey = getApiKey('zai') || process.env.ZAI_API_KEY;
234
+
235
+ if (!glmKey) {
236
+ const errorMsg = `Z.ai API key not configured for GLM-4.6!\n\n` +
237
+ `Run this command to add your API key:\n` +
238
+ ` nexuscli api set zai YOUR_API_KEY\n\n` +
239
+ `Get your key at: https://z.ai`;
240
+
241
+ console.error(`[ClaudeWrapper] ❌ ${errorMsg}`);
242
+
243
+ if (onStatus) {
244
+ onStatus({
245
+ type: 'error',
246
+ category: 'config',
247
+ message: errorMsg
248
+ });
249
+ }
250
+
251
+ return reject(new Error(errorMsg));
252
+ }
253
+
254
+ // GLM-4.6 uses Z.ai Anthropic-compatible API
255
+ spawnEnv.ANTHROPIC_BASE_URL = 'https://api.z.ai/api/anthropic';
256
+ spawnEnv.ANTHROPIC_AUTH_TOKEN = glmKey;
257
+ spawnEnv.ANTHROPIC_MODEL = 'GLM-4.6'; // Z.ai model name
258
+ spawnEnv.API_TIMEOUT_MS = '3000000'; // 50 minutes timeout
259
+ console.log(`[ClaudeWrapper] GLM-4.6 detected - using Z.ai API with extended timeout`);
221
260
  }
222
261
 
223
- console.log(`[ClaudeWrapper] Model: ${model}${isDeepSeek ? ' (DeepSeek API)' : ''}`);
262
+ console.log(`[ClaudeWrapper] Model: ${model}${isDeepSeek ? ' (DeepSeek API)' : isGLM ? ' (Z.ai API)' : ''}`);
224
263
  console.log(`[ClaudeWrapper] Session: ${conversationId} (${isExistingSession ? 'RESUME' : 'NEW'})`);
225
264
  console.log(`[ClaudeWrapper] Working dir: ${cwd}`);
226
265
 
@@ -246,13 +285,63 @@ class ClaudeWrapper extends BaseCliWrapper {
246
285
 
247
286
  let ptyProcess;
248
287
  try {
249
- ptyProcess = pty.spawn(command, spawnArgs, {
250
- name: 'xterm-color',
251
- cols: 80,
252
- rows: 30,
253
- cwd: cwd, // Use session-specific workspace
254
- env: spawnEnv, // Use configured env (includes DeepSeek API if needed)
255
- });
288
+ // Try direct spawn for Termux compatibility
289
+ const { spawn } = require('child_process');
290
+ ptyProcess = spawn(command, spawnArgs, {
291
+ cwd: cwd,
292
+ env: spawnEnv,
293
+ shell: false,
294
+ stdio: ['pipe', 'pipe', 'pipe']
295
+ });
296
+
297
+ // Wrap to PTY interface
298
+ const onDataHandlers = [];
299
+ const onExitHandlers = [];
300
+ const onErrorHandlers = [];
301
+ const killProc = ptyProcess.kill.bind(ptyProcess);
302
+
303
+ ptyProcess.stdout.on('data', (data) => {
304
+ const text = data.toString();
305
+ console.log('[ClaudeWrapper] Direct stdout:', text.substring(0, 200));
306
+ onDataHandlers.forEach(fn => fn(text));
307
+ });
308
+
309
+ ptyProcess.stderr.on('data', (data) => {
310
+ const text = data.toString();
311
+ console.log('[ClaudeWrapper] Direct stderr:', text.substring(0, 200));
312
+ onDataHandlers.forEach(fn => fn(text));
313
+ });
314
+
315
+ ptyProcess.on('close', (code) => {
316
+ console.log('[ClaudeWrapper] Process closed with code:', code);
317
+ onExitHandlers.forEach(fn => fn({ exitCode: code ?? 0 }));
318
+ });
319
+
320
+ ptyProcess.on('error', (err) => {
321
+ console.error('[ClaudeWrapper] Process error:', err.message);
322
+ onErrorHandlers.forEach(fn => fn(err));
323
+ });
324
+
325
+ // PTY-compatible wrapper
326
+ ptyProcess.onData = (fn) => onDataHandlers.push(fn);
327
+ ptyProcess.onExit = (fn) => onExitHandlers.push(fn);
328
+ ptyProcess.onError = (fn) => onErrorHandlers.push(fn);
329
+ ptyProcess.write = (data) => ptyProcess.stdin?.write(data);
330
+ ptyProcess.sendEsc = () => {
331
+ if (ptyProcess.stdin?.writable) {
332
+ ptyProcess.stdin.write('\x1b');
333
+ return true;
334
+ }
335
+ return false;
336
+ };
337
+ ptyProcess.kill = (signal) => killProc(signal);
338
+ ptyProcess.pid = ptyProcess.pid;
339
+
340
+ // Claude Code blocks waiting for stdin EOF when stdin is not a TTY.
341
+ // In --print mode we don't need stdin, so close it to avoid hanging.
342
+ if (ptyProcess.stdin && !ptyProcess.stdin.destroyed) {
343
+ ptyProcess.stdin.end();
344
+ }
256
345
  } catch (err) {
257
346
  const msg = `Failed to spawn Claude CLI: ${err.message}`;
258
347
  console.error('[ClaudeWrapper]', msg);
@@ -267,11 +356,21 @@ class ClaudeWrapper extends BaseCliWrapper {
267
356
 
268
357
  let stdout = '';
269
358
 
270
- // Timeout after 10 minutes (same as Codex wrapper)
359
+ // Dynamic timeout based on model
360
+ let timeoutMs = 600000; // 10 minutes default
361
+ let timeoutLabel = '10 minutes';
362
+ if (isGLM) {
363
+ timeoutMs = 3600000; // 60 minutes for GLM-4.6 (slow responses)
364
+ timeoutLabel = '60 minutes';
365
+ } else if (isDeepSeek) {
366
+ timeoutMs = 900000; // 15 minutes for DeepSeek
367
+ timeoutLabel = '15 minutes';
368
+ }
369
+
271
370
  const timeout = setTimeout(() => {
272
- console.error('[ClaudeWrapper] Timeout after 10 minutes');
371
+ console.error(`[ClaudeWrapper] Timeout after ${timeoutLabel}`);
273
372
  if (onStatus) {
274
- onStatus({ type: 'error', category: 'timeout', message: 'Claude CLI timeout after 10 minutes' });
373
+ onStatus({ type: 'error', category: 'timeout', message: `Claude CLI timeout after ${timeoutLabel}` });
275
374
  }
276
375
  try {
277
376
  ptyProcess.kill();
@@ -280,9 +379,9 @@ class ClaudeWrapper extends BaseCliWrapper {
280
379
  }
281
380
  if (!promiseSettled) {
282
381
  promiseSettled = true;
283
- reject(new Error('Claude CLI timeout after 10 minutes'));
382
+ reject(new Error(`Claude CLI timeout after ${timeoutLabel}`));
284
383
  }
285
- }, 600000);
384
+ }, timeoutMs);
286
385
 
287
386
  // Process output chunks
288
387
  ptyProcess.onData((data) => {
@@ -404,10 +404,19 @@ class OutputParser {
404
404
  }
405
405
 
406
406
  /**
407
- * Get usage statistics
407
+ * Get usage statistics (normalized for different API formats)
408
+ * Supports both Claude naming (input_tokens) and OpenAI naming (prompt_tokens)
408
409
  */
409
410
  getUsage() {
410
- return this.usage;
411
+ if (!this.usage) return null;
412
+
413
+ // Normalize field names for different API providers (Claude, DeepSeek, GLM)
414
+ return {
415
+ input_tokens: this.usage.input_tokens || this.usage.prompt_tokens || 0,
416
+ output_tokens: this.usage.output_tokens || this.usage.completion_tokens || 0,
417
+ cache_creation_input_tokens: this.usage.cache_creation_input_tokens || 0,
418
+ cache_read_input_tokens: this.usage.cache_read_input_tokens || 0,
419
+ };
411
420
  }
412
421
 
413
422
  /**
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@mmmbuto/nexuscli",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
4
4
  "description": "NexusCLI - TRI CLI Control Plane (Claude/Codex/Gemini)",
5
5
  "main": "lib/server/server.js",
6
6
  "bin": {
7
- "nexuscli": "./bin/nexuscli.js"
7
+ "nexuscli": "bin/nexuscli.js"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node lib/server/server.js",