@mmmbuto/nexuscli 0.9.2 → 0.9.3
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 +6 -9
- package/frontend/dist/sw.js +1 -1
- package/lib/server/routes/chat.js +6 -2
- package/lib/server/routes/codex.js +6 -2
- package/lib/server/routes/gemini.js +6 -2
- package/lib/server/routes/workspaces.js +17 -1
- package/lib/server/services/session-importer.js +44 -2
- package/lib/utils/workspace.js +40 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -27,15 +27,12 @@ NexusCLI is a lightweight, Termux-first AI cockpit to orchestrate Claude Code, C
|
|
|
27
27
|
|
|
28
28
|
---
|
|
29
29
|
|
|
30
|
-
## Highlights (v0.9.
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
- Termux Claude fix: close stdin in `--print` mode when non‑TTY to prevent hangs (fixes GLM‑4.6/DeepSeek “Processing request”).
|
|
37
|
-
- Native resume & engine bridge: resume existing Claude/Codex/Gemini sessions; engine switches bridge context with summaries/history; Gemini now includes Gemini 3 Flash preview (via gemini-cli-termux testing channel).
|
|
38
|
-
- Voice input ready: Whisper STT + auto HTTPS for remote microphone access; stop button reliably interrupts Claude/Codex/Gemini.
|
|
30
|
+
## Highlights (v0.9.3)
|
|
31
|
+
|
|
32
|
+
- Fix: sanitize Termux workspace paths (auto-correct `/data/data/com/termux/...`), preventing Claude spawn ENOENT and stalled GLM-4.6/DeepSeek runs.
|
|
33
|
+
- Workspaces API now filters/merges invalid paths so the UI dropdown stays clean.
|
|
34
|
+
- Session importer reads `cwd` from Claude session files when available (more accurate workspace mapping).
|
|
35
|
+
- Stable mobile layout, resilient chats, and safe interrupts retained from previous releases.
|
|
39
36
|
|
|
40
37
|
## Features
|
|
41
38
|
|
package/frontend/dist/sw.js
CHANGED
|
@@ -9,6 +9,7 @@ const sessionManager = require('../services/session-manager');
|
|
|
9
9
|
const SummaryGenerator = require('../services/summary-generator');
|
|
10
10
|
const contextBridge = require('../services/context-bridge');
|
|
11
11
|
const { invalidateConversations } = require('../services/cache');
|
|
12
|
+
const { resolveWorkspacePath } = require('../../utils/workspace');
|
|
12
13
|
|
|
13
14
|
const router = express.Router();
|
|
14
15
|
const claudeWrapper = new ClaudeWrapper();
|
|
@@ -62,8 +63,11 @@ router.post('/', async (req, res) => {
|
|
|
62
63
|
return res.status(400).json({ error: 'message required' });
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
// Resolve workspace path
|
|
66
|
-
const workspacePath = workspace
|
|
66
|
+
// Resolve workspace path (fix Termux com/termux typos, fallback to cwd)
|
|
67
|
+
const workspacePath = resolveWorkspacePath(workspace, process.cwd());
|
|
68
|
+
if (workspace && workspacePath !== workspace) {
|
|
69
|
+
console.warn(`[Chat] Workspace corrected: ${workspace} → ${workspacePath}`);
|
|
70
|
+
}
|
|
67
71
|
|
|
68
72
|
// Use SessionManager for session sync pattern
|
|
69
73
|
// conversationId → sessionId (per engine)
|
|
@@ -5,6 +5,7 @@ const { prepare } = require('../db');
|
|
|
5
5
|
const { v4: uuidv4 } = require('uuid');
|
|
6
6
|
const sessionManager = require('../services/session-manager');
|
|
7
7
|
const contextBridge = require('../services/context-bridge');
|
|
8
|
+
const { resolveWorkspacePath } = require('../../utils/workspace');
|
|
8
9
|
|
|
9
10
|
const router = express.Router();
|
|
10
11
|
const codexWrapper = new CodexWrapper();
|
|
@@ -69,8 +70,11 @@ router.post('/', async (req, res) => {
|
|
|
69
70
|
return res.status(503).json({ error: 'Codex CLI does not support exec subcommand. Please update to 0.62.1+' });
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
// Resolve workspace path
|
|
73
|
-
const workspacePath = workspace
|
|
73
|
+
// Resolve workspace path (fix Termux com/termux typos, fallback to cwd)
|
|
74
|
+
const workspacePath = resolveWorkspacePath(workspace, process.cwd());
|
|
75
|
+
if (workspace && workspacePath !== workspace) {
|
|
76
|
+
console.warn(`[Codex] Workspace corrected: ${workspace} → ${workspacePath}`);
|
|
77
|
+
}
|
|
74
78
|
|
|
75
79
|
// Use SessionManager for session sync pattern
|
|
76
80
|
// conversationId → sessionId (per engine)
|
|
@@ -11,6 +11,7 @@ const Message = require('../models/Message');
|
|
|
11
11
|
const { v4: uuidv4 } = require('uuid');
|
|
12
12
|
const sessionManager = require('../services/session-manager');
|
|
13
13
|
const contextBridge = require('../services/context-bridge');
|
|
14
|
+
const { resolveWorkspacePath } = require('../../utils/workspace');
|
|
14
15
|
|
|
15
16
|
const router = express.Router();
|
|
16
17
|
const geminiWrapper = new GeminiWrapper();
|
|
@@ -73,8 +74,11 @@ router.post('/', async (req, res) => {
|
|
|
73
74
|
});
|
|
74
75
|
}
|
|
75
76
|
|
|
76
|
-
// Resolve workspace path
|
|
77
|
-
const workspacePath = workspace
|
|
77
|
+
// Resolve workspace path (fix Termux com/termux typos, fallback to cwd)
|
|
78
|
+
const workspacePath = resolveWorkspacePath(workspace, process.cwd());
|
|
79
|
+
if (workspace && workspacePath !== workspace) {
|
|
80
|
+
console.warn(`[Gemini] Workspace corrected: ${workspace} → ${workspacePath}`);
|
|
81
|
+
}
|
|
78
82
|
|
|
79
83
|
// Use SessionManager for session sync pattern
|
|
80
84
|
const frontendConversationId = conversationId || uuidv4();
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const express = require('express');
|
|
2
2
|
const WorkspaceManager = require('../services/workspace-manager');
|
|
3
3
|
const { prepare } = require('../db');
|
|
4
|
+
const { sanitizeWorkspacePath } = require('../../utils/workspace');
|
|
4
5
|
|
|
5
6
|
const router = express.Router();
|
|
6
7
|
const workspaceManager = new WorkspaceManager();
|
|
@@ -23,7 +24,22 @@ router.get('/', async (req, res) => {
|
|
|
23
24
|
|
|
24
25
|
const workspaces = stmt.all();
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
// Filter out invalid paths and fix common Termux path typos
|
|
28
|
+
const byPath = new Map();
|
|
29
|
+
for (const ws of workspaces) {
|
|
30
|
+
const sanitized = sanitizeWorkspacePath(ws.workspace_path);
|
|
31
|
+
if (!sanitized) continue;
|
|
32
|
+
const existing = byPath.get(sanitized);
|
|
33
|
+
if (!existing) {
|
|
34
|
+
byPath.set(sanitized, { ...ws, workspace_path: sanitized });
|
|
35
|
+
} else {
|
|
36
|
+
// Merge counts/last activity for duplicates
|
|
37
|
+
existing.session_count += ws.session_count;
|
|
38
|
+
existing.last_activity = Math.max(existing.last_activity || 0, ws.last_activity || 0);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
res.json({ workspaces: Array.from(byPath.values()) });
|
|
27
43
|
} catch (error) {
|
|
28
44
|
console.error('[Workspaces] Error listing workspaces:', error);
|
|
29
45
|
res.status(500).json({ error: error.message });
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
const { prepare, saveDb } = require('../db');
|
|
18
|
+
const { sanitizeWorkspacePath } = require('../../utils/workspace');
|
|
18
19
|
|
|
19
20
|
const HOME = process.env.HOME || '';
|
|
20
21
|
|
|
@@ -53,7 +54,8 @@ class SessionImporter {
|
|
|
53
54
|
const sessionId = file.replace('.jsonl', '');
|
|
54
55
|
if (this.sessionExists(sessionId)) continue;
|
|
55
56
|
|
|
56
|
-
const
|
|
57
|
+
const sessionFile = path.join(projectDir, file);
|
|
58
|
+
const workspacePath = this.resolveWorkspacePath(sessionFile, slug);
|
|
57
59
|
this.insertSession(sessionId, 'claude', workspacePath, null);
|
|
58
60
|
imported++;
|
|
59
61
|
}
|
|
@@ -140,7 +142,7 @@ class SessionImporter {
|
|
|
140
142
|
/**
|
|
141
143
|
* Best-effort reverse di slug Claude in path
|
|
142
144
|
* -path-to-dir → /path/to/dir
|
|
143
|
-
*
|
|
145
|
+
* Nota: i punti non sono recuperabili dallo slug (fallback only).
|
|
144
146
|
*/
|
|
145
147
|
slugToPath(slug) {
|
|
146
148
|
if (!slug) return '';
|
|
@@ -150,6 +152,46 @@ class SessionImporter {
|
|
|
150
152
|
if (tmp.startsWith('-')) tmp = '/' + tmp.slice(1);
|
|
151
153
|
return tmp.replace(/-/g, '/');
|
|
152
154
|
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Extract workspace path from a Claude session file (reads first chunk)
|
|
158
|
+
*/
|
|
159
|
+
extractCwdFromSession(sessionFile) {
|
|
160
|
+
try {
|
|
161
|
+
const fd = fs.openSync(sessionFile, 'r');
|
|
162
|
+
const buf = Buffer.alloc(64 * 1024);
|
|
163
|
+
const bytesRead = fs.readSync(fd, buf, 0, buf.length, 0);
|
|
164
|
+
fs.closeSync(fd);
|
|
165
|
+
if (!bytesRead) return null;
|
|
166
|
+
|
|
167
|
+
const chunk = buf.slice(0, bytesRead).toString('utf8');
|
|
168
|
+
const lines = chunk.split('\n');
|
|
169
|
+
for (const line of lines) {
|
|
170
|
+
if (!line) continue;
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(line);
|
|
173
|
+
if (parsed && parsed.cwd) return parsed.cwd;
|
|
174
|
+
} catch (_) {
|
|
175
|
+
// ignore malformed line
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
// ignore read/parse errors
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Resolve workspace path for Claude sessions
|
|
186
|
+
*/
|
|
187
|
+
resolveWorkspacePath(sessionFile, slug) {
|
|
188
|
+
const cwd = this.extractCwdFromSession(sessionFile);
|
|
189
|
+
const sanitizedCwd = sanitizeWorkspacePath(cwd);
|
|
190
|
+
if (sanitizedCwd) return sanitizedCwd;
|
|
191
|
+
|
|
192
|
+
const fallback = sanitizeWorkspacePath(this.slugToPath(slug));
|
|
193
|
+
return fallback || '';
|
|
194
|
+
}
|
|
153
195
|
}
|
|
154
196
|
|
|
155
197
|
module.exports = new SessionImporter();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
|
|
4
|
+
function normalizeWorkspacePath(p) {
|
|
5
|
+
if (!p) return '';
|
|
6
|
+
if (p === '/') return '/';
|
|
7
|
+
return p.replace(/\/+$/, '');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function fixTermuxPath(p) {
|
|
11
|
+
if (!p) return p;
|
|
12
|
+
if (p.includes('/data/data/com/termux/')) {
|
|
13
|
+
const fixed = p.replace('/data/data/com/termux/', '/data/data/com.termux/');
|
|
14
|
+
if (fs.existsSync(fixed)) return fixed;
|
|
15
|
+
}
|
|
16
|
+
return p;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function sanitizeWorkspacePath(p) {
|
|
20
|
+
const normalized = normalizeWorkspacePath(p);
|
|
21
|
+
if (!normalized) return '';
|
|
22
|
+
if (fs.existsSync(normalized)) return normalized;
|
|
23
|
+
const fixed = fixTermuxPath(normalized);
|
|
24
|
+
if (fixed !== normalized && fs.existsSync(fixed)) return fixed;
|
|
25
|
+
return '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveWorkspacePath(p, fallback) {
|
|
29
|
+
const sanitized = sanitizeWorkspacePath(p);
|
|
30
|
+
if (sanitized) return sanitized;
|
|
31
|
+
const fallbackPath = sanitizeWorkspacePath(fallback || os.homedir());
|
|
32
|
+
return fallbackPath || os.homedir();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = {
|
|
36
|
+
normalizeWorkspacePath,
|
|
37
|
+
fixTermuxPath,
|
|
38
|
+
sanitizeWorkspacePath,
|
|
39
|
+
resolveWorkspacePath
|
|
40
|
+
};
|