@mmmbuto/nexuscli 0.9.0 → 0.9.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.
- package/README.md +2 -2
- package/frontend/dist/assets/{index-Dl9FJBOB.css → index-Ci39i_2l.css} +1 -1
- package/frontend/dist/assets/{index-DFYYfeuX.js → index-x6Jl2qtq.js} +1697 -1697
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +1 -1
- package/frontend/package.json +1 -1
- package/lib/cli/api.js +6 -0
- package/lib/config/models.js +8 -0
- package/lib/server/lib/pty-adapter.js +2 -0
- package/lib/server/services/claude-wrapper.js +115 -16
- package/lib/server/services/output-parser.js +11 -2
- package/package.json +1 -1
package/frontend/dist/index.html
CHANGED
|
@@ -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-
|
|
63
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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>
|
package/frontend/dist/sw.js
CHANGED
package/frontend/package.json
CHANGED
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
|
|
package/lib/config/models.js
CHANGED
|
@@ -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 -
|
|
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
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
//
|
|
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(
|
|
371
|
+
console.error(`[ClaudeWrapper] Timeout after ${timeoutLabel}`);
|
|
273
372
|
if (onStatus) {
|
|
274
|
-
onStatus({ type: 'error', category: 'timeout', message:
|
|
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(
|
|
382
|
+
reject(new Error(`Claude CLI timeout after ${timeoutLabel}`));
|
|
284
383
|
}
|
|
285
|
-
},
|
|
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
|
-
|
|
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
|
/**
|