@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.
- package/README.md +30 -33
- package/frontend/dist/assets/{index-D2yxK2v8.js → index-D8XkscmI.js} +25 -25
- package/frontend/dist/index.html +1 -1
- package/frontend/dist/sw.js +1 -1
- package/lib/server/services/cli-loader.js +1 -1
- package/lib/server/services/session-importer.js +6 -1
- package/lib/server/services/workspace-manager.js +121 -0
- package/package.json +1 -1
package/frontend/dist/index.html
CHANGED
|
@@ -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-
|
|
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>
|
package/frontend/dist/sw.js
CHANGED
|
@@ -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
|
*/
|