@mmmbuto/nexuscli 0.9.7004-termux → 0.9.7005-termux

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
@@ -8,7 +8,7 @@
8
8
 
9
9
  ## Overview
10
10
 
11
- NexusCLI is a lightweight, Termux-first AI cockpit to orchestrate Claude Code, Codex CLI, Gemini CLI, and Qwen Code CLI from a single web/terminal UI. It supports live interrupts, native session resume, and voice input with HTTPS auto-setup for remote devices.
11
+ NexusCLI is a lightweight, Termux-first AI cockpit that orchestrates Claude Code, Codex CLI, Gemini CLI, and Qwen Code CLI from a single web/terminal UI. It supports live streaming, interrupts, session resume, workspace isolation, and remote voice input with auto HTTPS setup.
12
12
 
13
13
  ---
14
14
 
@@ -29,39 +29,28 @@ NexusCLI is a lightweight, Termux-first AI cockpit to orchestrate Claude Code, C
29
29
 
30
30
  ## Highlights (v0.9.7004-termux)
31
31
 
32
- - **QWEN Vision Fix**: `vision-model` now correctly selected for image prompts
32
+ - **QWEN CLI**: Integrated Qwen Code CLI with `coder-model` and `vision-model`
33
33
  - **Statusbar Realtime**: Qwen tool events streamed live in the UI
34
- - **Light Theme**: Improved contrast + proper statusbar color on mobile
34
+ - **Light Theme**: Higher contrast + correct mobile statusbar colors
35
35
 
36
- ### v0.9.6
36
+ ### Stable (v0.9.6)
37
37
 
38
- - **Jobs Runner Restored**: `jobs` route works again after cleanup regression
39
- - **Termux-Safe Execution**: no hardcoded `/bin` or `/usr/bin` paths for job tools
40
- - **Cleaner Errors**: job SSE errors now display correctly in UI
41
-
42
- ### v0.9.5
43
-
44
- - **GPT-5.2 Codex**: Added latest frontier agentic coding model as default
45
- - Updated Codex model catalog to match latest OpenAI CLI
46
-
47
- ### v0.9.4
48
-
49
- - **Dark/Light Theme**: Toggle between themes via UserMenu (persisted in localStorage)
50
- - **Rate Limiting**: Chat endpoints now protected (10 req/min per user)
51
- - **Architecture Docs**: New `docs/ARCHITECTURE.md` with system diagrams
52
- - CSS refactored to use CSS variables for theming support
38
+ - Jobs runner restored with Termux-safe execution
39
+ - Cleaner job SSE errors in UI
53
40
 
54
41
  ## Features
55
42
 
56
- - Multi-engine support (Claude, Codex, Gemini, Qwen)
57
- - Session continuity with explicit workspace selection
58
- - SSE streaming responses
59
- - Model selector with think-mode toggle and default model preference
60
- - Workspace management and conversation history
61
- - Config API endpoint for user preferences
62
- - Stop/Interrupt button across engines
63
- - Dark/Light theme toggle with localStorage persistence
64
- - Rate limiting on chat endpoints (10 req/min per user)
43
+ - Multi-engine orchestration (Claude, Codex, Gemini, Qwen)
44
+ - SSE streaming with realtime tool statusbar
45
+ - Interrupt/stop per engine
46
+ - Session resume + native session import across engines
47
+ - Workspace isolation, switching, and history
48
+ - File & image attachments (vision models supported)
49
+ - Model selector (think mode + reasoning levels where available)
50
+ - Voice input (browser STT, optional Whisper via OpenAI key)
51
+ - Conversation search + pin/bookmark
52
+ - Built-in jobs runner API for shell tasks
53
+ - Config API + rate limiting on chat endpoints
65
54
 
66
55
  ## Supported Engines
67
56
 
@@ -88,11 +77,11 @@ npm install -g github:DioNanos/nexuscli
88
77
  ### Release Channels
89
78
 
90
79
  ```bash
80
+ # Latest (default)
81
+ npm install -g @mmmbuto/nexuscli
82
+
91
83
  # Stable channel (pinned)
92
84
  npm install -g @mmmbuto/nexuscli@stable
93
-
94
- # Testing channel
95
- npm install -g @mmmbuto/nexuscli@testing
96
85
  ```
97
86
 
98
87
  ## Setup
@@ -131,8 +120,10 @@ nexuscli start
131
120
  | `nexuscli workspaces` | Manage workspaces |
132
121
  | `nexuscli model` | Default model |
133
122
  | `nexuscli api` | Additional API keys (e.g., Whisper) |
123
+ | `nexuscli logs` | View server logs |
134
124
  | `nexuscli users` | Users |
135
- | `POST /api/v1/sessions/import` | Import native sessions (admin) |
125
+ | `nexuscli setup-termux` | Termux helpers (services, paths) |
126
+ | `nexuscli uninstall` | Remove NexusCLI |
136
127
 
137
128
  ---
138
129
 
@@ -202,6 +193,9 @@ It is a **research and learning tool**.
202
193
  | `POST /api/v1/qwen/interrupt` | Qwen | Stop running generation |
203
194
  | `GET /api/v1/models` | All | List available models |
204
195
  | `GET /api/v1/config` | - | Get user preferences (default model) |
196
+ | `POST /api/v1/sessions/import` | - | Import native sessions (admin) |
197
+ | `POST /api/v1/jobs` | - | Run a background job (SSE stream) |
198
+ | `GET /api/v1/workspaces` | - | List workspaces from sessions |
205
199
  | `GET /health` | - | Health check |
206
200
 
207
201
  ---
@@ -424,7 +424,7 @@ class CliLoader {
424
424
  content = parts
425
425
  .filter(p => p && p.text)
426
426
  .map(p => p.text)
427
- .join('\\n');
427
+ .join('\n');
428
428
  } else if (typeof entry.message?.content === 'string') {
429
429
  content = entry.message.content;
430
430
  } else if (entry.text) {
@@ -161,14 +161,19 @@ class SessionImporter {
161
161
 
162
162
  /**
163
163
  * Controlla se la sessione esiste già
164
+ * Returns false if sessions table doesn't exist (allows import)
164
165
  */
165
166
  sessionExists(sessionId) {
166
167
  try {
167
168
  const stmt = prepare('SELECT 1 FROM sessions WHERE id = ?');
168
169
  return !!stmt.get(sessionId);
169
170
  } catch (err) {
171
+ // If sessions table doesn't exist, return false to allow import
172
+ if (err.message && err.message.includes('no such table')) {
173
+ return false;
174
+ }
170
175
  console.warn(`[SessionImporter] exists check failed: ${err.message}`);
171
- return true; // default to skip to avoid duplicates
176
+ return true; // default to skip to avoid duplicates on other errors
172
177
  }
173
178
  }
174
179
 
@@ -11,6 +11,7 @@ class WorkspaceManager {
11
11
  this.claudePath = path.join(process.env.HOME, '.claude');
12
12
  this.historyPath = path.join(this.claudePath, 'history.jsonl');
13
13
  this.projectsPath = path.join(this.claudePath, 'projects');
14
+ this.qwenPath = path.join(process.env.HOME, '.qwen');
14
15
  this.cacheTtlMs = 5 * 60 * 1000; // 5 minutes
15
16
  this.historyCache = {
16
17
  entries: null,
@@ -38,6 +39,17 @@ class WorkspaceManager {
38
39
  return '/' + slug.replace(/^-/, '').replace(/-/g, '/');
39
40
  }
40
41
 
42
+ /**
43
+ * Convert workspace path to Qwen project directory name
44
+ * Matches Qwen Storage.sanitizeCwd behavior
45
+ * @param {string} workspacePath - Absolute path
46
+ * @returns {string} Qwen project directory name
47
+ */
48
+ qwenPathToProject(workspacePath) {
49
+ if (!workspacePath) return 'default';
50
+ return workspacePath.replace(/[^a-zA-Z0-9]/g, '-');
51
+ }
52
+
41
53
  /**
42
54
  * Discover all workspaces from .claude/projects/
43
55
  * Reads real workspace path from session file 'cwd' field
@@ -169,6 +181,10 @@ class WorkspaceManager {
169
181
  const claudeSessions = await this.indexClaudeCodeSessions(workspacePath);
170
182
  sessions.push(...claudeSessions);
171
183
 
184
+ // Index Qwen sessions
185
+ const qwenSessions = await this.indexQwenSessions(workspacePath);
186
+ sessions.push(...qwenSessions);
187
+
172
188
  // TODO: Index other CLI tools (Codex, Aider, etc.)
173
189
 
174
190
  // Sync sessions to database (batch mode to avoid "Statement closed" errors)
@@ -332,6 +348,111 @@ class WorkspaceManager {
332
348
  return sessions;
333
349
  }
334
350
 
351
+ /**
352
+ * Index Qwen sessions from .qwen/projects/<workspace-sanitized>/chats/
353
+ * @param {string} workspacePath
354
+ * @returns {Promise<Array>}
355
+ */
356
+ async indexQwenSessions(workspacePath) {
357
+ const project = this.qwenPathToProject(workspacePath);
358
+ const projectDir = path.join(this.qwenPath, 'projects', project);
359
+
360
+ if (!fs.existsSync(projectDir)) {
361
+ // Silently skip - Qwen may not be used for this workspace
362
+ return [];
363
+ }
364
+
365
+ const chatsDir = path.join(projectDir, 'chats');
366
+ if (!fs.existsSync(chatsDir)) {
367
+ return [];
368
+ }
369
+
370
+ // List session files
371
+ const sessionFiles = fs.readdirSync(chatsDir)
372
+ .filter(f => f.endsWith('.jsonl'));
373
+
374
+ console.log(`[WorkspaceManager] Found ${sessionFiles.length} Qwen session files in ${project}`);
375
+
376
+ const sessions = [];
377
+
378
+ for (const file of sessionFiles) {
379
+ const sessionId = file.replace('.jsonl', '');
380
+ const sessionPath = path.join(chatsDir, file);
381
+
382
+ // Read session file line by line
383
+ const messages = [];
384
+ let firstTimestamp = null;
385
+ let lastTimestamp = null;
386
+
387
+ try {
388
+ const fileStream = fs.createReadStream(sessionPath);
389
+ const rl = readline.createInterface({
390
+ input: fileStream,
391
+ crlfDelay: Infinity
392
+ });
393
+
394
+ for await (const line of rl) {
395
+ if (!line.trim()) continue;
396
+ try {
397
+ const entry = JSON.parse(line);
398
+
399
+ // Only include user/assistant messages (skip system/ui_telemetry)
400
+ if (entry.type === 'user' || entry.type === 'assistant') {
401
+ const timestamp = new Date(entry.timestamp).getTime();
402
+ if (!firstTimestamp || timestamp < firstTimestamp) firstTimestamp = timestamp;
403
+ if (!lastTimestamp || timestamp > lastTimestamp) lastTimestamp = timestamp;
404
+
405
+ // Extract content from Qwen format
406
+ let content = '';
407
+ const parts = entry.message?.parts;
408
+ if (Array.isArray(parts)) {
409
+ content = parts
410
+ .filter(p => p && p.text)
411
+ .map(p => p.text)
412
+ .join('\n');
413
+ } else if (typeof entry.message?.content === 'string') {
414
+ content = entry.message.content;
415
+ } else if (entry.text) {
416
+ content = entry.text;
417
+ }
418
+
419
+ messages.push({
420
+ role: entry.type,
421
+ content: content,
422
+ timestamp: entry.timestamp
423
+ });
424
+ }
425
+ } catch (e) {
426
+ // Skip malformed lines
427
+ }
428
+ }
429
+ } catch (error) {
430
+ console.error(`[WorkspaceManager] Error reading Qwen session ${sessionId}:`, error.message);
431
+ continue;
432
+ }
433
+
434
+ if (messages.length > 0) {
435
+ sessions.push({
436
+ id: sessionId,
437
+ engine: 'qwen',
438
+ workspace_path: workspacePath,
439
+ session_path: sessionPath,
440
+ title: this.extractTitle(messages),
441
+ message_count: messages.length,
442
+ last_used_at: lastTimestamp,
443
+ created_at: firstTimestamp
444
+ // NOTE: messages NOT included - loaded on-demand by CliLoader (filesystem = source of truth)
445
+ });
446
+ }
447
+ }
448
+
449
+ // Sort by most recent first
450
+ sessions.sort((a, b) => b.last_used_at - a.last_used_at);
451
+
452
+ console.log(`[WorkspaceManager] Loaded ${sessions.length} Qwen sessions (sorted by recency)`);
453
+ return sessions;
454
+ }
455
+
335
456
  /**
336
457
  * Return cached history entries with TTL and fs.watch invalidation.
337
458
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mmmbuto/nexuscli",
3
- "version": "0.9.7004-termux",
3
+ "version": "0.9.7005-termux",
4
4
  "description": "NexusCLI - TRI CLI Control Plane (Claude/Codex/Gemini/Qwen)",
5
5
  "main": "lib/server/server.js",
6
6
  "bin": {