@mmmbuto/nexuscli 0.9.6 → 0.9.7-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 +21 -4
- package/lib/cli/engines.js +51 -2
- package/lib/config/manager.js +5 -0
- package/lib/config/models.js +28 -0
- package/lib/server/middleware/rate-limit.js +1 -1
- package/lib/server/models/Message.js +1 -1
- package/lib/server/routes/models.js +2 -0
- package/lib/server/routes/qwen.js +240 -0
- package/lib/server/routes/sessions.js +13 -1
- package/lib/server/server.js +7 -4
- package/lib/server/services/cli-loader.js +83 -3
- package/lib/server/services/context-bridge.js +4 -2
- package/lib/server/services/qwen-output-parser.js +289 -0
- package/lib/server/services/qwen-wrapper.js +251 -0
- package/lib/server/services/session-importer.js +35 -2
- package/lib/server/services/session-manager.js +32 -5
- package/lib/server/tests/history-sync.test.js +11 -2
- package/lib/server/tests/integration-session-sync.test.js +40 -8
- package/lib/server/tests/integration.test.js +33 -16
- package/lib/server/tests/performance.test.js +16 -9
- package/lib/server/tests/services.test.js +17 -10
- package/package.json +2 -2
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* - Claude: ~/.claude/projects/<slug>/<sessionId>.jsonl
|
|
6
6
|
* - Codex : ~/.codex/sessions/<sessionId>.jsonl
|
|
7
7
|
* - Gemini: ~/.gemini/sessions/<sessionId>.jsonl
|
|
8
|
+
* - Qwen : ~/.qwen/projects/<sanitized>/chats/<sessionId>.jsonl
|
|
8
9
|
*
|
|
9
10
|
* Note:
|
|
10
11
|
* - Usa FILESYSTEM come source of truth: non legge contenuti, solo metadati.
|
|
@@ -22,19 +23,21 @@ const HOME = process.env.HOME || '';
|
|
|
22
23
|
const CLAUDE_PROJECTS = path.join(HOME, '.claude', 'projects');
|
|
23
24
|
const CODEX_SESSIONS = path.join(HOME, '.codex', 'sessions');
|
|
24
25
|
const GEMINI_SESSIONS = path.join(HOME, '.gemini', 'sessions');
|
|
26
|
+
const QWEN_PROJECTS = path.join(HOME, '.qwen', 'projects');
|
|
25
27
|
|
|
26
28
|
class SessionImporter {
|
|
27
29
|
constructor() {}
|
|
28
30
|
|
|
29
31
|
/**
|
|
30
32
|
* Importa tutte le sessioni per tutti gli engine
|
|
31
|
-
* @returns {{claude:number, codex:number, gemini:number}}
|
|
33
|
+
* @returns {{claude:number, codex:number, gemini:number, qwen:number}}
|
|
32
34
|
*/
|
|
33
35
|
importAll() {
|
|
34
36
|
const claude = this.importClaudeSessions();
|
|
35
37
|
const codex = this.importCodexSessions();
|
|
36
38
|
const gemini = this.importGeminiSessions();
|
|
37
|
-
|
|
39
|
+
const qwen = this.importQwenSessions();
|
|
40
|
+
return { claude, codex, gemini, qwen };
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
/**
|
|
@@ -110,6 +113,36 @@ class SessionImporter {
|
|
|
110
113
|
return imported;
|
|
111
114
|
}
|
|
112
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Qwen: ~/.qwen/projects/<sanitized>/chats/<sessionId>.jsonl
|
|
118
|
+
*/
|
|
119
|
+
importQwenSessions() {
|
|
120
|
+
let imported = 0;
|
|
121
|
+
if (!fs.existsSync(QWEN_PROJECTS)) return imported;
|
|
122
|
+
|
|
123
|
+
const projects = fs.readdirSync(QWEN_PROJECTS);
|
|
124
|
+
for (const project of projects) {
|
|
125
|
+
const projectDir = path.join(QWEN_PROJECTS, project);
|
|
126
|
+
if (!fs.statSync(projectDir).isDirectory()) continue;
|
|
127
|
+
|
|
128
|
+
const chatsDir = path.join(projectDir, 'chats');
|
|
129
|
+
if (!fs.existsSync(chatsDir)) continue;
|
|
130
|
+
|
|
131
|
+
const files = fs.readdirSync(chatsDir).filter(f => f.endsWith('.jsonl'));
|
|
132
|
+
for (const file of files) {
|
|
133
|
+
const sessionId = file.replace('.jsonl', '');
|
|
134
|
+
if (this.sessionExists(sessionId)) continue;
|
|
135
|
+
|
|
136
|
+
this.insertSession(sessionId, 'qwen', '', null);
|
|
137
|
+
imported++;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (imported > 0) saveDb();
|
|
142
|
+
console.log(`[SessionImporter] Qwen imported: ${imported}`);
|
|
143
|
+
return imported;
|
|
144
|
+
}
|
|
145
|
+
|
|
113
146
|
/**
|
|
114
147
|
* Inserisce riga minima in sessions
|
|
115
148
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SessionManager - Session Sync Pattern Implementation (TRI CLI v0.4.0)
|
|
3
3
|
*
|
|
4
|
-
* Simplified session management for Claude/Codex/Gemini engines.
|
|
4
|
+
* Simplified session management for Claude/Codex/Gemini/Qwen engines.
|
|
5
5
|
* Principle: FILESYSTEM = SOURCE OF TRUTH
|
|
6
6
|
*
|
|
7
7
|
* Flow:
|
|
@@ -22,6 +22,7 @@ const SESSION_DIRS = {
|
|
|
22
22
|
claude: path.join(process.env.HOME || '', '.claude', 'projects'),
|
|
23
23
|
codex: path.join(process.env.HOME || '', '.codex', 'sessions'),
|
|
24
24
|
gemini: path.join(process.env.HOME || '', '.gemini', 'sessions'),
|
|
25
|
+
qwen: path.join(process.env.HOME || '', '.qwen', 'projects'),
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
class SessionManager {
|
|
@@ -52,9 +53,9 @@ class SessionManager {
|
|
|
52
53
|
sessionFileExists(sessionId, engine, workspacePath) {
|
|
53
54
|
const normalizedEngine = this._normalizeEngine(engine);
|
|
54
55
|
|
|
55
|
-
// Codex/Gemini exec mode doesn't
|
|
56
|
+
// Codex/Gemini/Qwen exec mode doesn't require filesystem checks - trust DB mapping
|
|
56
57
|
// Session continuity is managed via NexusCLI's message DB + contextBridge
|
|
57
|
-
if (normalizedEngine === 'codex' || normalizedEngine === 'gemini') {
|
|
58
|
+
if (normalizedEngine === 'codex' || normalizedEngine === 'gemini' || normalizedEngine === 'qwen') {
|
|
58
59
|
return true; // Always trust DB for exec-mode CLI sessions
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -108,6 +109,11 @@ class SessionManager {
|
|
|
108
109
|
case 'gemini':
|
|
109
110
|
// Gemini sessions: ~/.gemini/sessions/<sessionId>.jsonl
|
|
110
111
|
return path.join(SESSION_DIRS.gemini, `${sessionId}.jsonl`);
|
|
112
|
+
case 'qwen': {
|
|
113
|
+
// Qwen sessions: ~/.qwen/projects/<sanitized-cwd>/chats/<sessionId>.jsonl
|
|
114
|
+
const project = this._pathToQwenProject(workspacePath);
|
|
115
|
+
return path.join(SESSION_DIRS.qwen, project, 'chats', `${sessionId}.jsonl`);
|
|
116
|
+
}
|
|
111
117
|
|
|
112
118
|
default:
|
|
113
119
|
console.warn(`[SessionManager] Unknown engine: ${engine}`);
|
|
@@ -124,6 +130,7 @@ class SessionManager {
|
|
|
124
130
|
if (lower.includes('claude')) return 'claude';
|
|
125
131
|
if (lower.includes('codex') || lower.includes('openai')) return 'codex';
|
|
126
132
|
if (lower.includes('gemini') || lower.includes('google')) return 'gemini';
|
|
133
|
+
if (lower.includes('qwen')) return 'qwen';
|
|
127
134
|
return lower;
|
|
128
135
|
}
|
|
129
136
|
|
|
@@ -138,6 +145,15 @@ class SessionManager {
|
|
|
138
145
|
return workspacePath.replace(/\//g, '-');
|
|
139
146
|
}
|
|
140
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Convert workspace path to Qwen project dir (matches Qwen Storage.sanitizeCwd)
|
|
150
|
+
* Replaces non-alphanumeric characters with '-'
|
|
151
|
+
*/
|
|
152
|
+
_pathToQwenProject(workspacePath) {
|
|
153
|
+
if (!workspacePath) return 'default';
|
|
154
|
+
return workspacePath.replace(/[^a-zA-Z0-9]/g, '-');
|
|
155
|
+
}
|
|
156
|
+
|
|
141
157
|
/**
|
|
142
158
|
* Generate workspace hash (legacy method, kept for compatibility)
|
|
143
159
|
* @deprecated Use _pathToSlug instead (matches Claude Code behavior)
|
|
@@ -325,7 +341,12 @@ class SessionManager {
|
|
|
325
341
|
this.lastAccess.delete(cacheKey);
|
|
326
342
|
|
|
327
343
|
// Delete the original .jsonl file (SYNC DELETE)
|
|
328
|
-
const sessionFile = this._getSessionFilePath(
|
|
344
|
+
const sessionFile = this._getSessionFilePath(
|
|
345
|
+
session.id,
|
|
346
|
+
session.engine,
|
|
347
|
+
session.workspace_path,
|
|
348
|
+
session.session_path
|
|
349
|
+
);
|
|
329
350
|
if (sessionFile && fs.existsSync(sessionFile)) {
|
|
330
351
|
try {
|
|
331
352
|
fs.unlinkSync(sessionFile);
|
|
@@ -353,10 +374,11 @@ class SessionManager {
|
|
|
353
374
|
/**
|
|
354
375
|
* Get the filesystem path for a session file
|
|
355
376
|
*/
|
|
356
|
-
_getSessionFilePath(sessionId, engine, workspacePath) {
|
|
377
|
+
_getSessionFilePath(sessionId, engine, workspacePath, sessionPath) {
|
|
357
378
|
const normalizedEngine = engine?.toLowerCase().includes('claude') ? 'claude'
|
|
358
379
|
: engine?.toLowerCase().includes('codex') ? 'codex'
|
|
359
380
|
: engine?.toLowerCase().includes('gemini') ? 'gemini'
|
|
381
|
+
: engine?.toLowerCase().includes('qwen') ? 'qwen'
|
|
360
382
|
: 'claude';
|
|
361
383
|
|
|
362
384
|
switch (normalizedEngine) {
|
|
@@ -367,6 +389,11 @@ class SessionManager {
|
|
|
367
389
|
return path.join(SESSION_DIRS.codex, `${sessionId}.jsonl`);
|
|
368
390
|
case 'gemini':
|
|
369
391
|
return path.join(SESSION_DIRS.gemini, `${sessionId}.jsonl`);
|
|
392
|
+
case 'qwen': {
|
|
393
|
+
const project = this._pathToQwenProject(workspacePath);
|
|
394
|
+
const fileId = sessionPath || sessionId;
|
|
395
|
+
return path.join(SESSION_DIRS.qwen, project, 'chats', `${fileId}.jsonl`);
|
|
396
|
+
}
|
|
370
397
|
default:
|
|
371
398
|
return null;
|
|
372
399
|
}
|
|
@@ -4,6 +4,7 @@ const os = require('os');
|
|
|
4
4
|
|
|
5
5
|
describe('HistorySync', () => {
|
|
6
6
|
let tmpDir;
|
|
7
|
+
let testHome;
|
|
7
8
|
let historyPath;
|
|
8
9
|
let HistorySync;
|
|
9
10
|
let initDb;
|
|
@@ -26,7 +27,8 @@ describe('HistorySync', () => {
|
|
|
26
27
|
beforeEach(async () => {
|
|
27
28
|
// Fresh temp DB per test
|
|
28
29
|
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'nexus-db-'));
|
|
29
|
-
|
|
30
|
+
testHome = fs.mkdtempSync(path.join(os.tmpdir(), 'nexuscli-home-'));
|
|
31
|
+
process.env.HOME = testHome;
|
|
30
32
|
|
|
31
33
|
jest.resetModules();
|
|
32
34
|
({ initDb, prepare, getDb } = require('../db'));
|
|
@@ -83,7 +85,14 @@ describe('HistorySync', () => {
|
|
|
83
85
|
test('getWorkspaceSessions filters by workspace path', async () => {
|
|
84
86
|
await historySync.sync(true);
|
|
85
87
|
|
|
86
|
-
const
|
|
88
|
+
const grouped = await historySync.getWorkspaceSessions('/test/path');
|
|
89
|
+
const sessions = [
|
|
90
|
+
...grouped.today,
|
|
91
|
+
...grouped.yesterday,
|
|
92
|
+
...grouped.last7days,
|
|
93
|
+
...grouped.last30days,
|
|
94
|
+
...grouped.older
|
|
95
|
+
];
|
|
87
96
|
expect(sessions).toHaveLength(1);
|
|
88
97
|
expect(sessions[0].id).toBe('test-1');
|
|
89
98
|
});
|
|
@@ -9,16 +9,34 @@
|
|
|
9
9
|
|
|
10
10
|
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
|
-
const
|
|
13
|
-
|
|
12
|
+
const os = require('os');
|
|
13
|
+
|
|
14
|
+
let HistorySync;
|
|
15
|
+
let initDb;
|
|
16
|
+
let getDb;
|
|
17
|
+
let prepare;
|
|
14
18
|
|
|
15
19
|
describe('Session Sync Integration', () => {
|
|
16
20
|
let historySync;
|
|
17
21
|
let testHistoryPath;
|
|
18
22
|
|
|
19
23
|
beforeAll(async () => {
|
|
24
|
+
const testHome = fs.mkdtempSync(path.join(os.tmpdir(), 'nexuscli-home-'));
|
|
25
|
+
process.env.HOME = testHome;
|
|
26
|
+
|
|
27
|
+
jest.resetModules();
|
|
28
|
+
HistorySync = require('../services/history-sync');
|
|
29
|
+
({ initDb, getDb, prepare } = require('../db'));
|
|
30
|
+
|
|
20
31
|
// Setup in-memory test database
|
|
21
|
-
await initDb(
|
|
32
|
+
await initDb();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
afterAll(() => {
|
|
36
|
+
const db = getDb && getDb();
|
|
37
|
+
if (db && typeof db.close === 'function') {
|
|
38
|
+
db.close();
|
|
39
|
+
}
|
|
22
40
|
});
|
|
23
41
|
|
|
24
42
|
beforeEach(() => {
|
|
@@ -110,11 +128,25 @@ describe('Session Sync Integration', () => {
|
|
|
110
128
|
const workspace1Sessions = await historySync.getWorkspaceSessions('/test/workspace1');
|
|
111
129
|
const workspace2Sessions = await historySync.getWorkspaceSessions('/test/workspace2');
|
|
112
130
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
131
|
+
const ws1 = [
|
|
132
|
+
...workspace1Sessions.today,
|
|
133
|
+
...workspace1Sessions.yesterday,
|
|
134
|
+
...workspace1Sessions.last7days,
|
|
135
|
+
...workspace1Sessions.last30days,
|
|
136
|
+
...workspace1Sessions.older
|
|
137
|
+
];
|
|
138
|
+
expect(ws1).toHaveLength(1);
|
|
139
|
+
expect(ws1[0].id).toBe('session-001');
|
|
140
|
+
|
|
141
|
+
const ws2 = [
|
|
142
|
+
...workspace2Sessions.today,
|
|
143
|
+
...workspace2Sessions.yesterday,
|
|
144
|
+
...workspace2Sessions.last7days,
|
|
145
|
+
...workspace2Sessions.last30days,
|
|
146
|
+
...workspace2Sessions.older
|
|
147
|
+
];
|
|
148
|
+
expect(ws2).toHaveLength(1);
|
|
149
|
+
expect(ws2[0].id).toBe('session-002');
|
|
118
150
|
});
|
|
119
151
|
|
|
120
152
|
test('conversation titles are generated from first message', async () => {
|
|
@@ -3,38 +3,56 @@
|
|
|
3
3
|
* Phase 7 - End-to-end flow validation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
const TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'nexuscli-home-'));
|
|
11
|
+
process.env.HOME = TEST_HOME;
|
|
12
|
+
|
|
13
|
+
let initDb;
|
|
14
|
+
let getDb;
|
|
15
|
+
let prepare;
|
|
16
|
+
|
|
17
|
+
beforeAll(async () => {
|
|
18
|
+
jest.resetModules();
|
|
19
|
+
({ initDb, getDb, prepare } = require('../db'));
|
|
20
|
+
await initDb(); // run migrations for session tables
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterAll(() => {
|
|
24
|
+
const db = getDb && getDb();
|
|
25
|
+
if (db && typeof db.close === 'function') {
|
|
26
|
+
db.close();
|
|
27
|
+
}
|
|
28
|
+
});
|
|
7
29
|
|
|
8
30
|
describe('Database Integration', () => {
|
|
9
31
|
test('should initialize database successfully', async () => {
|
|
10
|
-
const db =
|
|
32
|
+
const db = getDb();
|
|
11
33
|
expect(db).toBeDefined();
|
|
12
34
|
expect(typeof db.exec).toBe('function');
|
|
13
35
|
});
|
|
14
36
|
|
|
15
37
|
test('should have sessions table', async () => {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
expect(result.length).toBeGreaterThan(0);
|
|
38
|
+
const row = prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='sessions'").get();
|
|
39
|
+
expect(row).not.toBeNull();
|
|
19
40
|
});
|
|
20
41
|
|
|
21
42
|
test('should have session_summaries table', async () => {
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
expect(result.length).toBeGreaterThan(0);
|
|
43
|
+
const row = prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='session_summaries'").get();
|
|
44
|
+
expect(row).not.toBeNull();
|
|
25
45
|
});
|
|
26
46
|
|
|
27
47
|
test('should have workspace_memory table', async () => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
expect(result.length).toBeGreaterThan(0);
|
|
48
|
+
const row = prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='workspace_memory'").get();
|
|
49
|
+
expect(row).not.toBeNull();
|
|
31
50
|
});
|
|
32
51
|
|
|
33
52
|
test('should query sessions successfully', async () => {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
expect(
|
|
37
|
-
expect(result[0].columns).toContain('count');
|
|
53
|
+
const row = prepare('SELECT COUNT(*) as count FROM sessions').get();
|
|
54
|
+
expect(row).toBeDefined();
|
|
55
|
+
expect(Object.prototype.hasOwnProperty.call(row, 'count')).toBe(true);
|
|
38
56
|
});
|
|
39
57
|
});
|
|
40
58
|
|
|
@@ -47,7 +65,6 @@ describe('Service Integration', () => {
|
|
|
47
65
|
const loader = new CliLoader();
|
|
48
66
|
|
|
49
67
|
expect(manager.claudePath).toBe(loader.claudePath);
|
|
50
|
-
expect(manager.historyPath).toBe(loader.historyPath);
|
|
51
68
|
});
|
|
52
69
|
|
|
53
70
|
test('All services should initialize without errors', () => {
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
* Phase 7 - Performance benchmarks
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
const TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'nexuscli-home-'));
|
|
11
|
+
process.env.HOME = TEST_HOME;
|
|
12
|
+
|
|
6
13
|
const WorkspaceManager = require('../services/workspace-manager');
|
|
7
14
|
const SummaryGenerator = require('../services/summary-generator');
|
|
8
15
|
|
|
@@ -45,7 +52,9 @@ describe('Performance Benchmarks', () => {
|
|
|
45
52
|
|
|
46
53
|
test('Workspace validation should be fast', async () => {
|
|
47
54
|
const manager = new WorkspaceManager();
|
|
48
|
-
const
|
|
55
|
+
const baseDir = path.join(process.env.HOME || os.tmpdir(), '.nexuscli-test');
|
|
56
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
57
|
+
const testPath = fs.mkdtempSync(path.join(baseDir, 'ws-'));
|
|
49
58
|
|
|
50
59
|
const start = Date.now();
|
|
51
60
|
const validated = await manager.validateWorkspace(testPath);
|
|
@@ -97,18 +106,16 @@ describe('Memory Efficiency', () => {
|
|
|
97
106
|
});
|
|
98
107
|
|
|
99
108
|
describe('Ultra-Light Compliance', () => {
|
|
100
|
-
test('should
|
|
101
|
-
const
|
|
102
|
-
const chatCode = fs.readFileSync('routes/chat.js', 'utf8');
|
|
109
|
+
test('should persist both user and assistant messages (code verification)', () => {
|
|
110
|
+
const chatCode = fs.readFileSync(path.join(__dirname, '..', 'routes', 'chat.js'), 'utf8');
|
|
103
111
|
|
|
104
|
-
// Verify
|
|
105
|
-
expect(chatCode).toContain('
|
|
106
|
-
expect(chatCode).toContain('
|
|
112
|
+
// Verify both user and assistant messages are persisted
|
|
113
|
+
expect(chatCode).toContain('[Chat] Saved user message');
|
|
114
|
+
expect(chatCode).toContain('[Chat] Saved assistant message');
|
|
107
115
|
});
|
|
108
116
|
|
|
109
117
|
test('should use cache for history.jsonl reads', () => {
|
|
110
|
-
const
|
|
111
|
-
const managerCode = fs.readFileSync('services/workspace-manager.js', 'utf8');
|
|
118
|
+
const managerCode = fs.readFileSync(path.join(__dirname, '..', 'services', 'workspace-manager.js'), 'utf8');
|
|
112
119
|
|
|
113
120
|
// Verify cache implementation exists
|
|
114
121
|
expect(managerCode).toContain('historyCache');
|
|
@@ -3,6 +3,13 @@
|
|
|
3
3
|
* Phase 7 - Testing & Deployment
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
|
|
10
|
+
const TEST_HOME = fs.mkdtempSync(path.join(os.tmpdir(), 'nexuscli-home-'));
|
|
11
|
+
process.env.HOME = TEST_HOME;
|
|
12
|
+
|
|
6
13
|
const WorkspaceManager = require('../services/workspace-manager');
|
|
7
14
|
const CliLoader = require('../services/cli-loader');
|
|
8
15
|
const SummaryGenerator = require('../services/summary-generator');
|
|
@@ -15,8 +22,10 @@ describe('WorkspaceManager', () => {
|
|
|
15
22
|
});
|
|
16
23
|
|
|
17
24
|
test('should validate workspace path', async () => {
|
|
18
|
-
// Test with allowed path
|
|
19
|
-
const
|
|
25
|
+
// Test with allowed path (Termux-first)
|
|
26
|
+
const baseDir = path.join(process.env.HOME || os.tmpdir(), '.nexuscli-test');
|
|
27
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
28
|
+
const validPath = fs.mkdtempSync(path.join(baseDir, 'ws-'));
|
|
20
29
|
const result = await manager.validateWorkspace(validPath);
|
|
21
30
|
expect(result).toBe(validPath);
|
|
22
31
|
});
|
|
@@ -28,7 +37,8 @@ describe('WorkspaceManager', () => {
|
|
|
28
37
|
});
|
|
29
38
|
|
|
30
39
|
test('should detect non-existent workspace', async () => {
|
|
31
|
-
const
|
|
40
|
+
const baseDir = path.join(process.env.HOME || os.tmpdir(), '.nexuscli-test');
|
|
41
|
+
const nonExistent = path.join(baseDir, 'nonexistent-workspace-12345');
|
|
32
42
|
await expect(manager.validateWorkspace(nonExistent)).rejects.toThrow('does not exist');
|
|
33
43
|
});
|
|
34
44
|
|
|
@@ -41,7 +51,7 @@ describe('WorkspaceManager', () => {
|
|
|
41
51
|
|
|
42
52
|
test('should extract title from messages', () => {
|
|
43
53
|
const messages = [
|
|
44
|
-
{ display: 'Implement user authentication feature' },
|
|
54
|
+
{ role: 'user', display: 'Implement user authentication feature' },
|
|
45
55
|
{ display: 'Follow up on previous discussion' }
|
|
46
56
|
];
|
|
47
57
|
const title = manager.extractTitle(messages);
|
|
@@ -62,11 +72,6 @@ describe('CliLoader', () => {
|
|
|
62
72
|
expect(loader.claudePath).toBe(expectedPath);
|
|
63
73
|
});
|
|
64
74
|
|
|
65
|
-
test('should initialize with correct historyPath', () => {
|
|
66
|
-
const expectedPath = require('path').join(process.env.HOME, '.claude', 'history.jsonl');
|
|
67
|
-
expect(loader.historyPath).toBe(expectedPath);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
75
|
test('should have loadMessagesFromCLI method', () => {
|
|
71
76
|
expect(typeof loader.loadMessagesFromCLI).toBe('function');
|
|
72
77
|
});
|
|
@@ -147,7 +152,9 @@ describe('SummaryGenerator', () => {
|
|
|
147
152
|
describe('Integration - Service Interactions', () => {
|
|
148
153
|
test('WorkspaceManager should use consistent path resolution', async () => {
|
|
149
154
|
const manager = new WorkspaceManager();
|
|
150
|
-
const
|
|
155
|
+
const baseDir = path.join(process.env.HOME || os.tmpdir(), '.nexuscli-test');
|
|
156
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
157
|
+
const testPath = fs.mkdtempSync(path.join(baseDir, 'ws-'));
|
|
151
158
|
const validated = await manager.validateWorkspace(testPath);
|
|
152
159
|
expect(validated).toBe(testPath);
|
|
153
160
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mmmbuto/nexuscli",
|
|
3
|
-
"version": "0.9.
|
|
4
|
-
"description": "NexusCLI - TRI CLI Control Plane (Claude/Codex/Gemini)",
|
|
3
|
+
"version": "0.9.7-termux",
|
|
4
|
+
"description": "NexusCLI - TRI CLI Control Plane (Claude/Codex/Gemini/Qwen)",
|
|
5
5
|
"main": "lib/server/server.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nexuscli": "bin/nexuscli.js"
|