@mmmbuto/nexuscli 0.9.6 → 0.9.7-001-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 +258 -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 +263 -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/lib/utils/attachments.js +100 -0
- package/package.json +2 -2
|
@@ -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
|
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { fixTermuxPath } = require('./workspace');
|
|
4
|
+
|
|
5
|
+
const ATTACHMENT_LINE_REGEX = /^\s*\[Attached:\s*(.+?)\s*\]\s*$/;
|
|
6
|
+
const IMAGE_EXTS = new Set([
|
|
7
|
+
'.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.tiff', '.tif', '.svg', '.heic'
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
function parseAttachmentMarkers(message = '') {
|
|
11
|
+
if (!message) {
|
|
12
|
+
return { cleanMessage: message, paths: [] };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const lines = message.split('\n');
|
|
16
|
+
const paths = [];
|
|
17
|
+
const kept = [];
|
|
18
|
+
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
const match = line.match(ATTACHMENT_LINE_REGEX);
|
|
21
|
+
if (match) {
|
|
22
|
+
paths.push(match[1]);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
kept.push(line);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const cleanMessage = kept.join('\n').trim();
|
|
29
|
+
return { cleanMessage, paths };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isImageAttachment(attachment) {
|
|
33
|
+
const mimeType = attachment?.mimeType || '';
|
|
34
|
+
if (mimeType.startsWith('image/')) return true;
|
|
35
|
+
const ext = path.extname(attachment?.path || '').toLowerCase();
|
|
36
|
+
return IMAGE_EXTS.has(ext);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function normalizeAttachmentPath(rawPath, workspacePath) {
|
|
40
|
+
if (!rawPath || typeof rawPath !== 'string') return null;
|
|
41
|
+
let fixedPath = fixTermuxPath(rawPath);
|
|
42
|
+
if (!path.isAbsolute(fixedPath) && workspacePath) {
|
|
43
|
+
fixedPath = path.resolve(workspacePath, fixedPath);
|
|
44
|
+
}
|
|
45
|
+
return fixedPath;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeAttachments({ message, rawMessage, attachments, workspacePath }) {
|
|
49
|
+
const parsed = parseAttachmentMarkers(message || '');
|
|
50
|
+
const promptMessage = rawMessage != null ? rawMessage : (parsed.cleanMessage || message || '');
|
|
51
|
+
|
|
52
|
+
const incoming = [];
|
|
53
|
+
if (Array.isArray(attachments)) {
|
|
54
|
+
incoming.push(...attachments);
|
|
55
|
+
}
|
|
56
|
+
if (parsed.paths.length > 0) {
|
|
57
|
+
parsed.paths.forEach((p) => incoming.push({ path: p }));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
const normalized = [];
|
|
62
|
+
|
|
63
|
+
for (const entry of incoming) {
|
|
64
|
+
if (!entry) continue;
|
|
65
|
+
const rawPath = typeof entry === 'string' ? entry : entry.path;
|
|
66
|
+
const resolvedPath = normalizeAttachmentPath(rawPath, workspacePath);
|
|
67
|
+
if (!resolvedPath) continue;
|
|
68
|
+
if (!fs.existsSync(resolvedPath)) continue;
|
|
69
|
+
if (seen.has(resolvedPath)) continue;
|
|
70
|
+
|
|
71
|
+
seen.add(resolvedPath);
|
|
72
|
+
const record = {
|
|
73
|
+
...entry,
|
|
74
|
+
path: resolvedPath,
|
|
75
|
+
name: entry.name || path.basename(resolvedPath)
|
|
76
|
+
};
|
|
77
|
+
normalized.push(record);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const attachmentPaths = normalized.map((att) => att.path);
|
|
81
|
+
const includeDirectories = Array.from(
|
|
82
|
+
new Set(attachmentPaths.map((p) => path.dirname(p)))
|
|
83
|
+
);
|
|
84
|
+
const imageFiles = normalized.filter(isImageAttachment).map((att) => att.path);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
promptMessage,
|
|
88
|
+
attachments: normalized,
|
|
89
|
+
attachmentPaths,
|
|
90
|
+
includeDirectories,
|
|
91
|
+
imageFiles,
|
|
92
|
+
cleanMessage: parsed.cleanMessage
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
normalizeAttachments,
|
|
98
|
+
parseAttachmentMarkers,
|
|
99
|
+
isImageAttachment
|
|
100
|
+
};
|
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-001-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"
|