@mmmbuto/nexuscli 0.9.7003-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.
@@ -59,7 +59,7 @@
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-D2yxK2v8.js"></script>
62
+ <script type="module" crossorigin src="/assets/index-D8XkscmI.js"></script>
63
63
  <link rel="stylesheet" crossorigin href="/assets/index-CoLEGBO4.css">
64
64
  </head>
65
65
  <body>
@@ -1,5 +1,5 @@
1
1
  // NexusCLI Service Worker
2
- const CACHE_VERSION = 'nexuscli-v1766928124982';
2
+ const CACHE_VERSION = 'nexuscli-v1766931086050';
3
3
  const STATIC_CACHE = `${CACHE_VERSION}-static`;
4
4
  const DYNAMIC_CACHE = `${CACHE_VERSION}-dynamic`;
5
5
 
@@ -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.7003-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": {