@mmmbuto/nexuscli 0.9.1 → 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 CHANGED
@@ -27,14 +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.1)
30
+ ## Highlights (v0.9.3)
31
31
 
32
- - Stable mobile layout: `100dvh` viewport, overscroll disabled, and a pinned input bar with safe-area padding for Android browsers.
33
- - Resilient chats: pre-flight `/health` ping plus 60s client-side timeout with clear error messaging to avoid frozen requests.
34
- - Safer shell actions: Gemini wrapper flags dangerous commands; Termux PTY adapter now supports ESC-based interrupts for clean stops.
35
- - Termux Claude fix: close stdin in `--print` mode when non‑TTY to prevent hangs (fixes GLM‑4.6/DeepSeek “Processing request”).
36
- - 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).
37
- - Voice input ready: Whisper STT + auto HTTPS for remote microphone access; stop button reliably interrupts Claude/Codex/Gemini.
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.
38
36
 
39
37
  ## Features
40
38
 
@@ -50,7 +48,8 @@ NexusCLI is a lightweight, Termux-first AI cockpit to orchestrate Claude Code, C
50
48
 
51
49
  | Engine | Models | Provider |
52
50
  |--------|--------|----------|
53
- | **Claude** | Opus 4.5, Sonnet 4.5, Haiku 4.5 | Anthropic |
51
+ | **Claude (native)** | Opus 4.5, Sonnet 4.5, Haiku 4.5 | Anthropic |
52
+ | **Claude-compatible** | DeepSeek (deepseek-*), GLM-4.6 | DeepSeek, Z.ai |
54
53
  | **Codex** | GPT-5.1, GPT-5.1 Codex (Mini/Max) | OpenAI |
55
54
  | **Gemini** | Gemini 3 Pro Preview, Gemini 3 Flash Preview | Google |
56
55
 
@@ -114,6 +113,7 @@ Configure API keys for additional providers:
114
113
  ```bash
115
114
  nexuscli api list # List configured keys
116
115
  nexuscli api set deepseek <key> # DeepSeek models
116
+ nexuscli api set zai <key> # GLM-4.6 (Z.ai Anthropic-compatible)
117
117
  nexuscli api set openai <key> # Voice input (Whisper STT)
118
118
  nexuscli api set openrouter <key> # Future: Multi-provider gateway
119
119
  nexuscli api delete <provider> # Remove key
@@ -1,5 +1,5 @@
1
1
  // NexusCLI Service Worker
2
- const CACHE_VERSION = 'nexuscli-v1766155108128';
2
+ const CACHE_VERSION = 'nexuscli-v1766174198487';
3
3
  const STATIC_CACHE = `${CACHE_VERSION}-static`;
4
4
  const DYNAMIC_CACHE = `${CACHE_VERSION}-dynamic`;
5
5
 
@@ -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 || process.cwd();
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 || process.cwd();
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 || process.cwd();
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
- res.json({ workspaces });
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 workspacePath = this.slugToPath(slug);
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
- * Conserva i punti.
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
+ };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@mmmbuto/nexuscli",
3
- "version": "0.9.1",
3
+ "version": "0.9.3",
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",