@mmmbuto/nexuscli 0.8.1 → 0.8.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.
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ NexusCLI is a lightweight AI cockpit (Termux-first) to orchestrate Claude Code,
|
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
31
|
-
## Highlights (v0.8.
|
|
31
|
+
## Highlights (v0.8.2)
|
|
32
32
|
|
|
33
33
|
- Multi-engine: Claude, Codex, Gemini
|
|
34
34
|
- Native resume: same engine resumes the session; switching engines uses handoff with summary/history
|
|
@@ -36,6 +36,7 @@ NexusCLI is a lightweight AI cockpit (Termux-first) to orchestrate Claude Code,
|
|
|
36
36
|
- Session import: on startup it imports native sessions from ~/.claude ~/.codex ~/.gemini; manual endpoint `POST /api/v1/sessions/import`
|
|
37
37
|
- Voice input (Whisper), auto HTTPS for remote microphone
|
|
38
38
|
- Mobile-first UI with SSE streaming and explicit workspace selection
|
|
39
|
+
- Termux: postinstall now installs `ripgrep` and the Claude wrapper auto-patches missing `vendor/ripgrep/arm64-android/rg` to avoid spawn errors on fresh installs
|
|
39
40
|
|
|
40
41
|
## Supported Engines
|
|
41
42
|
|
|
@@ -2,6 +2,7 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const pty = require('../lib/pty-adapter');
|
|
4
4
|
const OutputParser = require('./output-parser');
|
|
5
|
+
const BaseCliWrapper = require('./base-cli-wrapper');
|
|
5
6
|
const { getApiKey } = require('../db');
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -18,11 +19,15 @@ const { getApiKey } = require('../db');
|
|
|
18
19
|
* - Parses stdout for tool use, thinking, file ops
|
|
19
20
|
* - Emits status events → SSE stream
|
|
20
21
|
* - Returns final response text → saved in DB
|
|
22
|
+
*
|
|
23
|
+
* @version 0.5.0 - Extended BaseCliWrapper for interrupt support
|
|
21
24
|
*/
|
|
22
|
-
class ClaudeWrapper {
|
|
25
|
+
class ClaudeWrapper extends BaseCliWrapper {
|
|
23
26
|
constructor(options = {}) {
|
|
27
|
+
super(); // Initialize activeProcesses from BaseCliWrapper
|
|
24
28
|
this.claudePath = this.resolveClaudePath(options.claudePath);
|
|
25
29
|
this.workspaceDir = options.workspaceDir || process.env.NEXUSCLI_WORKSPACE || process.cwd();
|
|
30
|
+
this.rgPatched = false;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
isExistingSession(sessionId, workspacePath) {
|
|
@@ -75,6 +80,60 @@ class ClaudeWrapper {
|
|
|
75
80
|
return null;
|
|
76
81
|
}
|
|
77
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Termux fix: Claude Code bundles ripgrep only for arm64-linux, but on Android
|
|
85
|
+
* it looks for vendor/ripgrep/arm64-android/rg. Create a symlink on the fly so
|
|
86
|
+
* fresh installs work without manual steps.
|
|
87
|
+
*/
|
|
88
|
+
ensureRipgrepForTermux() {
|
|
89
|
+
if (this.rgPatched) return;
|
|
90
|
+
const isTermux = (process.env.PREFIX || '').includes('/com.termux/');
|
|
91
|
+
if (!isTermux || !this.claudePath) return;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// claudePath points to cli.js or wrapper binary; vendor lives alongside
|
|
95
|
+
const cliDir = path.dirname(this.claudePath);
|
|
96
|
+
const vendorDir = path.join(cliDir, 'vendor', 'ripgrep');
|
|
97
|
+
const linuxRg = path.join(vendorDir, 'arm64-linux', 'rg');
|
|
98
|
+
const androidDir = path.join(vendorDir, 'arm64-android');
|
|
99
|
+
const androidRg = path.join(androidDir, 'rg');
|
|
100
|
+
|
|
101
|
+
if (!fs.existsSync(vendorDir)) return;
|
|
102
|
+
|
|
103
|
+
// Prefer bundled arm64-linux rg; fallback to system rg if bundle missing
|
|
104
|
+
let rgSource = null;
|
|
105
|
+
if (fs.existsSync(linuxRg)) {
|
|
106
|
+
rgSource = linuxRg;
|
|
107
|
+
} else {
|
|
108
|
+
// Try system rg (Termux package ripgrep)
|
|
109
|
+
try {
|
|
110
|
+
const whichRg = require('child_process')
|
|
111
|
+
.execSync('which rg', { encoding: 'utf8' })
|
|
112
|
+
.trim();
|
|
113
|
+
if (whichRg && fs.existsSync(whichRg)) {
|
|
114
|
+
rgSource = whichRg;
|
|
115
|
+
console.log('[ClaudeWrapper] Using system ripgrep for Termux:', whichRg);
|
|
116
|
+
}
|
|
117
|
+
} catch (_) {
|
|
118
|
+
// leave rgSource null; we’ll exit gracefully
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (!rgSource) return;
|
|
123
|
+
if (fs.existsSync(androidRg)) {
|
|
124
|
+
this.rgPatched = true;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fs.mkdirSync(androidDir, { recursive: true });
|
|
129
|
+
fs.symlinkSync(rgSource, androidRg);
|
|
130
|
+
console.log('[ClaudeWrapper] Created ripgrep symlink for Termux:', androidRg, '→', rgSource);
|
|
131
|
+
this.rgPatched = true;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
console.warn('[ClaudeWrapper] Failed to patch ripgrep for Termux:', err.message);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
78
137
|
/**
|
|
79
138
|
* Send message to Claude Code CLI
|
|
80
139
|
*
|
|
@@ -125,6 +184,9 @@ class ClaudeWrapper {
|
|
|
125
184
|
// Use provided workspace or fallback to default
|
|
126
185
|
const cwd = workspacePath || this.workspaceDir;
|
|
127
186
|
|
|
187
|
+
// Termux compatibility: make sure ripgrep path exists before spawn
|
|
188
|
+
this.ensureRipgrepForTermux();
|
|
189
|
+
|
|
128
190
|
// Build environment - detect DeepSeek models and configure API accordingly
|
|
129
191
|
const spawnEnv = { ...process.env };
|
|
130
192
|
const isDeepSeek = model.startsWith('deepseek-');
|
|
@@ -200,8 +262,28 @@ class ClaudeWrapper {
|
|
|
200
262
|
return reject(new Error(msg));
|
|
201
263
|
}
|
|
202
264
|
|
|
265
|
+
// Register process for interrupt capability
|
|
266
|
+
this.registerProcess(conversationId, ptyProcess, 'pty');
|
|
267
|
+
|
|
203
268
|
let stdout = '';
|
|
204
269
|
|
|
270
|
+
// Timeout after 10 minutes (same as Codex wrapper)
|
|
271
|
+
const timeout = setTimeout(() => {
|
|
272
|
+
console.error('[ClaudeWrapper] Timeout after 10 minutes');
|
|
273
|
+
if (onStatus) {
|
|
274
|
+
onStatus({ type: 'error', category: 'timeout', message: 'Claude CLI timeout after 10 minutes' });
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
ptyProcess.kill();
|
|
278
|
+
} catch (e) {
|
|
279
|
+
console.error('[ClaudeWrapper] Failed to kill timed-out process:', e.message);
|
|
280
|
+
}
|
|
281
|
+
if (!promiseSettled) {
|
|
282
|
+
promiseSettled = true;
|
|
283
|
+
reject(new Error('Claude CLI timeout after 10 minutes'));
|
|
284
|
+
}
|
|
285
|
+
}, 600000);
|
|
286
|
+
|
|
205
287
|
// Process output chunks
|
|
206
288
|
ptyProcess.onData((data) => {
|
|
207
289
|
stdout += data;
|
|
@@ -255,6 +337,12 @@ class ClaudeWrapper {
|
|
|
255
337
|
}
|
|
256
338
|
|
|
257
339
|
ptyProcess.onExit(({ exitCode }) => {
|
|
340
|
+
// Clear timeout on exit
|
|
341
|
+
clearTimeout(timeout);
|
|
342
|
+
|
|
343
|
+
// Unregister process on exit
|
|
344
|
+
this.unregisterProcess(conversationId);
|
|
345
|
+
|
|
258
346
|
console.log(`[ClaudeWrapper] Exit code: ${exitCode}`);
|
|
259
347
|
|
|
260
348
|
if (exitCode !== 0) {
|
package/lib/setup/postinstall.js
CHANGED
|
@@ -137,7 +137,8 @@ async function main() {
|
|
|
137
137
|
console.log('');
|
|
138
138
|
|
|
139
139
|
// Required packages
|
|
140
|
-
|
|
140
|
+
// - ripgrep is needed by Claude CLI (vendor lookup expects it on Termux)
|
|
141
|
+
const packages = ['termux-api', 'termux-tools', 'ripgrep'];
|
|
141
142
|
|
|
142
143
|
log(colors.cyan('Installing Termux packages:'));
|
|
143
144
|
for (const pkg of packages) {
|