@mmmbuto/nexuscli 0.9.7004-termux → 0.10.0
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/CHANGELOG.md +84 -0
- package/README.md +89 -158
- package/bin/nexuscli.js +12 -0
- package/frontend/dist/assets/{index-D8XkscmI.js → index-Bztt9hew.js} +1704 -1704
- package/frontend/dist/assets/{index-CoLEGBO4.css → index-Dj7jz2fy.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +1 -1
- package/lib/cli/api.js +19 -1
- package/lib/cli/config.js +27 -5
- package/lib/cli/engines.js +84 -202
- package/lib/cli/init.js +56 -2
- package/lib/cli/model.js +17 -7
- package/lib/cli/start.js +37 -24
- package/lib/cli/stop.js +12 -41
- package/lib/cli/update.js +28 -0
- package/lib/cli/workspaces.js +4 -0
- package/lib/config/manager.js +112 -8
- package/lib/config/models.js +388 -192
- package/lib/server/db/migrations/001_ultra_light_schema.sql +1 -1
- package/lib/server/db/migrations/006_runtime_lane_tracking.sql +79 -0
- package/lib/server/lib/getPty.js +51 -0
- package/lib/server/lib/pty-adapter.js +101 -57
- package/lib/server/lib/pty-provider.js +63 -0
- package/lib/server/lib/pty-utils-loader.js +136 -0
- package/lib/server/middleware/auth.js +27 -4
- package/lib/server/models/Conversation.js +7 -3
- package/lib/server/models/Message.js +29 -5
- package/lib/server/routes/chat.js +27 -4
- package/lib/server/routes/codex.js +35 -8
- package/lib/server/routes/config.js +9 -1
- package/lib/server/routes/gemini.js +24 -5
- package/lib/server/routes/jobs.js +15 -156
- package/lib/server/routes/models.js +12 -10
- package/lib/server/routes/qwen.js +26 -7
- package/lib/server/routes/runtimes.js +68 -0
- package/lib/server/server.js +3 -0
- package/lib/server/services/claude-wrapper.js +60 -62
- package/lib/server/services/cli-loader.js +1 -1
- package/lib/server/services/codex-wrapper.js +79 -10
- package/lib/server/services/gemini-wrapper.js +9 -4
- package/lib/server/services/job-runner.js +156 -0
- package/lib/server/services/qwen-wrapper.js +26 -11
- package/lib/server/services/runtime-manager.js +467 -0
- package/lib/server/services/session-importer.js +6 -1
- package/lib/server/services/session-manager.js +56 -14
- package/lib/server/services/workspace-manager.js +121 -0
- package/lib/server/tests/integration.test.js +12 -0
- package/lib/server/tests/runtime-manager.test.js +46 -0
- package/lib/server/tests/runtime-persistence.test.js +97 -0
- package/lib/setup/postinstall-pty-check.js +183 -0
- package/lib/setup/postinstall.js +60 -41
- package/lib/utils/restart-warning.js +18 -0
- package/lib/utils/server.js +88 -0
- package/lib/utils/termux.js +1 -1
- package/lib/utils/update-check.js +153 -0
- package/lib/utils/update-runner.js +62 -0
- package/package.json +6 -5
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
ALTER TABLE sessions ADD COLUMN lane TEXT;
|
|
2
|
+
|
|
3
|
+
ALTER TABLE sessions ADD COLUMN runtime_id TEXT;
|
|
4
|
+
|
|
5
|
+
ALTER TABLE sessions ADD COLUMN provider_id TEXT;
|
|
6
|
+
|
|
7
|
+
ALTER TABLE sessions ADD COLUMN model_id TEXT;
|
|
8
|
+
|
|
9
|
+
ALTER TABLE messages ADD COLUMN lane TEXT;
|
|
10
|
+
|
|
11
|
+
ALTER TABLE messages ADD COLUMN runtime_id TEXT;
|
|
12
|
+
|
|
13
|
+
ALTER TABLE messages ADD COLUMN provider_id TEXT;
|
|
14
|
+
|
|
15
|
+
ALTER TABLE messages ADD COLUMN model_id TEXT;
|
|
16
|
+
|
|
17
|
+
UPDATE sessions
|
|
18
|
+
SET engine = 'claude'
|
|
19
|
+
WHERE lower(engine) = 'claude-code';
|
|
20
|
+
|
|
21
|
+
UPDATE messages
|
|
22
|
+
SET engine = 'claude'
|
|
23
|
+
WHERE lower(engine) = 'claude-code';
|
|
24
|
+
|
|
25
|
+
UPDATE sessions
|
|
26
|
+
SET
|
|
27
|
+
lane = COALESCE(lane, 'native'),
|
|
28
|
+
runtime_id = COALESCE(runtime_id,
|
|
29
|
+
CASE
|
|
30
|
+
WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'codex-native'
|
|
31
|
+
WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'gemini-native'
|
|
32
|
+
WHEN lower(engine) LIKE '%qwen%' THEN 'qwen-native'
|
|
33
|
+
ELSE 'claude-native'
|
|
34
|
+
END
|
|
35
|
+
),
|
|
36
|
+
provider_id = COALESCE(provider_id,
|
|
37
|
+
CASE
|
|
38
|
+
WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'openai'
|
|
39
|
+
WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'google'
|
|
40
|
+
WHEN lower(engine) LIKE '%qwen%' THEN 'qwen'
|
|
41
|
+
ELSE 'anthropic'
|
|
42
|
+
END
|
|
43
|
+
),
|
|
44
|
+
model_id = COALESCE(model_id,
|
|
45
|
+
CASE
|
|
46
|
+
WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'gpt-5.3-codex'
|
|
47
|
+
WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'gemini-3-pro-preview'
|
|
48
|
+
WHEN lower(engine) LIKE '%qwen%' THEN 'qwen3-coder-plus'
|
|
49
|
+
ELSE 'sonnet'
|
|
50
|
+
END
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
UPDATE messages
|
|
54
|
+
SET
|
|
55
|
+
lane = COALESCE(lane, 'native'),
|
|
56
|
+
runtime_id = COALESCE(runtime_id,
|
|
57
|
+
CASE
|
|
58
|
+
WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'codex-native'
|
|
59
|
+
WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'gemini-native'
|
|
60
|
+
WHEN lower(engine) LIKE '%qwen%' THEN 'qwen-native'
|
|
61
|
+
ELSE 'claude-native'
|
|
62
|
+
END
|
|
63
|
+
),
|
|
64
|
+
provider_id = COALESCE(provider_id,
|
|
65
|
+
CASE
|
|
66
|
+
WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'openai'
|
|
67
|
+
WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'google'
|
|
68
|
+
WHEN lower(engine) LIKE '%qwen%' THEN 'qwen'
|
|
69
|
+
ELSE 'anthropic'
|
|
70
|
+
END
|
|
71
|
+
),
|
|
72
|
+
model_id = COALESCE(model_id,
|
|
73
|
+
CASE
|
|
74
|
+
WHEN lower(engine) LIKE '%codex%' OR lower(engine) LIKE '%openai%' THEN 'gpt-5.3-codex'
|
|
75
|
+
WHEN lower(engine) LIKE '%gemini%' OR lower(engine) LIKE '%google%' THEN 'gemini-3-pro-preview'
|
|
76
|
+
WHEN lower(engine) LIKE '%qwen%' THEN 'qwen3-coder-plus'
|
|
77
|
+
ELSE 'sonnet'
|
|
78
|
+
END
|
|
79
|
+
);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PTY Loader for NexusCLI (legacy compatibility wrapper)
|
|
3
|
+
* Delegates to @mmmbuto/pty-termux-utils multi-provider strategy.
|
|
4
|
+
*
|
|
5
|
+
* Provider priority:
|
|
6
|
+
* 1. Termux: @mmmbuto/node-pty-android-arm64
|
|
7
|
+
* 2. Linux ARM64: @lydell/node-pty-linux-arm64
|
|
8
|
+
* 3. Fallback: child_process adapter (pty-adapter.js)
|
|
9
|
+
*
|
|
10
|
+
* @version 1.1.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const { getPtyFn, getLogDebugFn } = require('./pty-utils-loader');
|
|
14
|
+
|
|
15
|
+
const getSharedPty = getPtyFn();
|
|
16
|
+
const logDebug = getLogDebugFn();
|
|
17
|
+
const tryRequire = (moduleName) => {
|
|
18
|
+
try {
|
|
19
|
+
require(moduleName);
|
|
20
|
+
return true;
|
|
21
|
+
} catch (_) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
/**
|
|
28
|
+
* Get PTY implementation with fallback
|
|
29
|
+
* @returns {Promise<{module: any, name: string}|null>}
|
|
30
|
+
*/
|
|
31
|
+
async getPty() {
|
|
32
|
+
const pty = await getSharedPty();
|
|
33
|
+
if (pty) {
|
|
34
|
+
logDebug(`Using native PTY provider: ${pty.name}`);
|
|
35
|
+
return pty;
|
|
36
|
+
}
|
|
37
|
+
logDebug('Native PTY not available, using fallback adapter');
|
|
38
|
+
return null;
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Synchronous check if PTY is available
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
isPtyAvailable() {
|
|
46
|
+
return (
|
|
47
|
+
tryRequire('@mmmbuto/node-pty-android-arm64') ||
|
|
48
|
+
tryRequire('@lydell/node-pty-linux-arm64')
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
@@ -1,81 +1,125 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PTY Adapter for NexusCLI (Termux)
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* PTY Adapter for NexusCLI (Termux + Linux ARM64)
|
|
3
|
+
* Wrapper around @mmmbuto/pty-termux-utils shared library
|
|
4
|
+
* Provides node-pty-like interface with graceful fallback
|
|
5
5
|
*
|
|
6
|
-
* @version 0.
|
|
6
|
+
* @version 1.0.0
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
const {
|
|
9
|
+
const { getPtyFn, getFallbackAdapter } = require('./pty-utils-loader');
|
|
10
|
+
|
|
11
|
+
const getSharedPty = getPtyFn();
|
|
12
|
+
const tryRequire = (moduleName) => {
|
|
13
|
+
try {
|
|
14
|
+
require(moduleName);
|
|
15
|
+
return true;
|
|
16
|
+
} catch (_) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
// Create shared fallback adapter (always uses child_process)
|
|
22
|
+
const fallbackAdapter = getFallbackAdapter();
|
|
23
|
+
|
|
24
|
+
// Cache for native PTY (sync access)
|
|
25
|
+
let cachedNativePty = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Initialize native PTY (called at startup)
|
|
29
|
+
*/
|
|
30
|
+
async function initNativePty() {
|
|
31
|
+
if (cachedNativePty !== null) {
|
|
32
|
+
return cachedNativePty;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
cachedNativePty = await getSharedPty();
|
|
37
|
+
return cachedNativePty;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
cachedNativePty = false; // Error loading
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
10
43
|
|
|
11
44
|
/**
|
|
12
|
-
*
|
|
45
|
+
* Synchronous spawn (for compatibility with existing wrapper code)
|
|
46
|
+
* Uses fallback adapter for simplicity (no native PTY in sync mode)
|
|
13
47
|
* @param {string} command - Command to spawn
|
|
14
48
|
* @param {string[]} args - Arguments
|
|
15
49
|
* @param {Object} options - Spawn options (cwd, env)
|
|
16
|
-
* @returns {Object} PTY-like interface
|
|
50
|
+
* @returns {Object} PTY-like interface
|
|
17
51
|
*/
|
|
18
52
|
function spawn(command, args, options = {}) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
env: options.env,
|
|
22
|
-
shell: false,
|
|
23
|
-
stdio: ['pipe', 'pipe', 'pipe'], // stdin enabled for interrupt support
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const dataHandlers = [];
|
|
27
|
-
const exitHandlers = [];
|
|
28
|
-
const errorHandlers = [];
|
|
53
|
+
return fallbackAdapter.spawn(command, args, options);
|
|
54
|
+
}
|
|
29
55
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Async spawn with native PTY support
|
|
58
|
+
* @param {string} command - Command to spawn
|
|
59
|
+
* @param {string[]} args - Arguments
|
|
60
|
+
* @param {Object} options - Spawn options (cwd, env)
|
|
61
|
+
* @returns {Promise<Object>} PTY-like interface
|
|
62
|
+
*/
|
|
63
|
+
async function spawnAsync(command, args, options = {}) {
|
|
64
|
+
const pty = await initNativePty();
|
|
65
|
+
|
|
66
|
+
if (pty) {
|
|
67
|
+
// Use native PTY
|
|
68
|
+
const proc = pty.module.spawn(command, args, {
|
|
69
|
+
cols: options.cols || 80,
|
|
70
|
+
rows: options.rows || 24,
|
|
71
|
+
cwd: options.cwd,
|
|
72
|
+
env: options.env,
|
|
73
|
+
});
|
|
35
74
|
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
console.log('[PTY-Adapter] stderr:', data.substring(0, 200));
|
|
39
|
-
dataHandlers.forEach((fn) => fn(data));
|
|
40
|
-
});
|
|
75
|
+
const dataHandlers = [];
|
|
76
|
+
const exitHandlers = [];
|
|
41
77
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
78
|
+
proc.on('data', (data) => {
|
|
79
|
+
dataHandlers.forEach((fn) => fn(data));
|
|
80
|
+
});
|
|
45
81
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
});
|
|
82
|
+
proc.on('exit', (code, signal) => {
|
|
83
|
+
exitHandlers.forEach((fn) => fn({ exitCode: code, signal }));
|
|
84
|
+
});
|
|
50
85
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
* Used to gracefully stop Claude/Gemini CLI execution
|
|
59
|
-
* @returns {boolean} true if ESC was sent successfully
|
|
60
|
-
*/
|
|
61
|
-
sendEsc: () => {
|
|
62
|
-
if (proc.stdin?.writable) {
|
|
63
|
-
proc.stdin.write('\x1B');
|
|
86
|
+
return {
|
|
87
|
+
onData: (fn) => dataHandlers.push(fn),
|
|
88
|
+
onExit: (fn) => exitHandlers.push(fn),
|
|
89
|
+
onError: (fn) => {}, // No error event in native PTY
|
|
90
|
+
write: (data) => proc.write(data),
|
|
91
|
+
sendEsc: () => {
|
|
92
|
+
proc.write('\x1B');
|
|
64
93
|
return true;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
94
|
+
},
|
|
95
|
+
resize: (cols, rows) => proc.resize(cols, rows),
|
|
96
|
+
kill: (signal = 'SIGTERM') => proc.kill(signal),
|
|
97
|
+
pid: proc.pid,
|
|
98
|
+
};
|
|
99
|
+
} else {
|
|
100
|
+
// Use fallback adapter
|
|
101
|
+
return fallbackAdapter.spawn(command, args, options);
|
|
102
|
+
}
|
|
71
103
|
}
|
|
72
104
|
|
|
73
105
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
106
|
+
* Synchronous check if PTY is available
|
|
107
|
+
* @returns {boolean}
|
|
76
108
|
*/
|
|
77
109
|
function isPtyAvailable() {
|
|
78
|
-
return
|
|
110
|
+
return (
|
|
111
|
+
tryRequire('@mmmbuto/node-pty-android-arm64') ||
|
|
112
|
+
tryRequire('@lydell/node-pty-linux-arm64')
|
|
113
|
+
);
|
|
79
114
|
}
|
|
80
115
|
|
|
81
|
-
|
|
116
|
+
// Initialize native PTY in background (non-blocking)
|
|
117
|
+
initNativePty().catch(() => {
|
|
118
|
+
// Silently ignore initialization errors
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
module.exports = {
|
|
122
|
+
spawn, // Sync spawn (uses fallback)
|
|
123
|
+
spawnAsync, // Async spawn (uses native PTY if available)
|
|
124
|
+
isPtyAvailable,
|
|
125
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PTY Provider for NexusCLI (Termux + Linux ARM64)
|
|
3
|
+
* Provides PTY detection with fallback to child_process adapter
|
|
4
|
+
* Uses @mmmbuto/pty-termux-utils shared library
|
|
5
|
+
*
|
|
6
|
+
* @version 1.0.0
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
getPtyFn,
|
|
11
|
+
getSpawnPtyFn,
|
|
12
|
+
getFallbackAdapter: getSafeFallbackAdapter
|
|
13
|
+
} = require('./pty-utils-loader');
|
|
14
|
+
|
|
15
|
+
const getSharedPty = getPtyFn();
|
|
16
|
+
const spawnPty = getSpawnPtyFn();
|
|
17
|
+
const tryRequire = (moduleName) => {
|
|
18
|
+
try {
|
|
19
|
+
require(moduleName);
|
|
20
|
+
return true;
|
|
21
|
+
} catch (_) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get PTY implementation with fallback
|
|
28
|
+
* @returns {Promise<{module: any, name: string}|null>}
|
|
29
|
+
*/
|
|
30
|
+
async function getPty() {
|
|
31
|
+
const pty = await getSharedPty();
|
|
32
|
+
if (pty) {
|
|
33
|
+
return pty;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get fallback adapter (child_process)
|
|
40
|
+
* @returns {Object} Adapter with spawn method
|
|
41
|
+
*/
|
|
42
|
+
function getFallbackAdapter() {
|
|
43
|
+
return getSafeFallbackAdapter();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Synchronous check if PTY is available
|
|
48
|
+
* @returns {boolean}
|
|
49
|
+
*/
|
|
50
|
+
function isPtyAvailable() {
|
|
51
|
+
return (
|
|
52
|
+
tryRequire('@mmmbuto/node-pty-android-arm64') ||
|
|
53
|
+
tryRequire('@lydell/node-pty-linux-arm64')
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
module.exports = {
|
|
58
|
+
getPty,
|
|
59
|
+
getFallbackAdapter,
|
|
60
|
+
isPtyAvailable,
|
|
61
|
+
// Re-export from shared library
|
|
62
|
+
spawnPty,
|
|
63
|
+
};
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe loader for @mmmbuto/pty-termux-utils with fallback adapter.
|
|
3
|
+
* Prevents hard crashes if the dependency is missing or broken.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
|
|
8
|
+
let cachedUtils;
|
|
9
|
+
let warned = false;
|
|
10
|
+
|
|
11
|
+
function warnOnce(error) {
|
|
12
|
+
if (warned) return;
|
|
13
|
+
warned = true;
|
|
14
|
+
const message = error && error.message ? error.message : String(error);
|
|
15
|
+
console.warn('[PTY] Failed to load @mmmbuto/pty-termux-utils. Using fallback adapter.');
|
|
16
|
+
console.warn(`[PTY] Reason: ${message}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function loadPtyUtils() {
|
|
20
|
+
if (cachedUtils !== undefined) return cachedUtils;
|
|
21
|
+
try {
|
|
22
|
+
cachedUtils = require('@mmmbuto/pty-termux-utils');
|
|
23
|
+
} catch (err) {
|
|
24
|
+
warnOnce(err);
|
|
25
|
+
cachedUtils = null;
|
|
26
|
+
}
|
|
27
|
+
return cachedUtils;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ensurePtyInterface(proc) {
|
|
31
|
+
if (!proc) return proc;
|
|
32
|
+
if (typeof proc.onData === 'function' && typeof proc.onExit === 'function') {
|
|
33
|
+
return proc;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const hasChildStd = !!(proc.stdout || proc.stderr);
|
|
37
|
+
const dataHandlers = [];
|
|
38
|
+
const exitHandlers = [];
|
|
39
|
+
|
|
40
|
+
if (typeof proc.on === 'function' && !hasChildStd) {
|
|
41
|
+
proc.on('data', (data) => {
|
|
42
|
+
dataHandlers.forEach((fn) => fn(data));
|
|
43
|
+
});
|
|
44
|
+
proc.on('exit', (code, signal) => {
|
|
45
|
+
exitHandlers.forEach((fn) => fn({ exitCode: code, signal }));
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
proc.stdout?.on('data', (data) => {
|
|
49
|
+
dataHandlers.forEach((fn) => fn(data.toString()));
|
|
50
|
+
});
|
|
51
|
+
proc.stderr?.on('data', (data) => {
|
|
52
|
+
dataHandlers.forEach((fn) => fn(data.toString()));
|
|
53
|
+
});
|
|
54
|
+
proc.on?.('exit', (code, signal) => {
|
|
55
|
+
exitHandlers.forEach((fn) => fn({ exitCode: code ?? -1, signal }));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
proc.onData = (fn) => dataHandlers.push(fn);
|
|
60
|
+
proc.onExit = (fn) => exitHandlers.push(fn);
|
|
61
|
+
|
|
62
|
+
if (typeof proc.onError !== 'function' && typeof proc.on === 'function') {
|
|
63
|
+
proc.onError = (fn) => proc.on('error', fn);
|
|
64
|
+
}
|
|
65
|
+
if (typeof proc.write !== 'function') {
|
|
66
|
+
proc.write = (data) => proc.stdin?.write(data);
|
|
67
|
+
}
|
|
68
|
+
if (typeof proc.sendEsc !== 'function') {
|
|
69
|
+
proc.sendEsc = () => {
|
|
70
|
+
if (proc.stdin?.writable) {
|
|
71
|
+
proc.stdin.write('\x1B');
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
return false;
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (typeof proc.resize !== 'function') {
|
|
78
|
+
proc.resize = () => {};
|
|
79
|
+
}
|
|
80
|
+
if (typeof proc.kill !== 'function' && proc.process?.kill) {
|
|
81
|
+
proc.kill = (signal = 'SIGTERM') => proc.process.kill(signal);
|
|
82
|
+
}
|
|
83
|
+
if (proc.pid == null && proc.process?.pid != null) {
|
|
84
|
+
proc.pid = proc.process.pid;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return proc;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function createLocalFallbackAdapter() {
|
|
91
|
+
return {
|
|
92
|
+
spawn: (file, args, options = {}) => {
|
|
93
|
+
const child = spawn(file, args, {
|
|
94
|
+
cwd: options.cwd,
|
|
95
|
+
env: options.env ? { ...process.env, ...options.env } : undefined,
|
|
96
|
+
});
|
|
97
|
+
return ensurePtyInterface(child);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function getPtyFn() {
|
|
103
|
+
const utils = loadPtyUtils();
|
|
104
|
+
return utils?.getPty ? utils.getPty : async () => null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getSpawnPtyFn() {
|
|
108
|
+
const utils = loadPtyUtils();
|
|
109
|
+
return utils?.spawnPty
|
|
110
|
+
? utils.spawnPty
|
|
111
|
+
: async () => { throw new Error('Native PTY utilities unavailable'); };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function getLogDebugFn() {
|
|
115
|
+
const utils = loadPtyUtils();
|
|
116
|
+
return utils?.logDebug ? utils.logDebug : () => {};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getFallbackAdapter() {
|
|
120
|
+
const utils = loadPtyUtils();
|
|
121
|
+
if (utils?.createFallbackAdapter) {
|
|
122
|
+
const base = utils.createFallbackAdapter();
|
|
123
|
+
return {
|
|
124
|
+
spawn: (...args) => ensurePtyInterface(base.spawn(...args))
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return createLocalFallbackAdapter();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
getPtyFn,
|
|
132
|
+
getSpawnPtyFn,
|
|
133
|
+
getLogDebugFn,
|
|
134
|
+
getFallbackAdapter,
|
|
135
|
+
ensurePtyInterface
|
|
136
|
+
};
|
|
@@ -1,8 +1,31 @@
|
|
|
1
|
+
const crypto = require('crypto');
|
|
1
2
|
const jwt = require('jsonwebtoken');
|
|
2
3
|
const User = require('../models/User');
|
|
3
4
|
const { getConfig } = require('../../config/manager');
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
let warnedEphemeralSecret = false;
|
|
7
|
+
|
|
8
|
+
function getJwtSecret() {
|
|
9
|
+
if (process.env.JWT_SECRET) {
|
|
10
|
+
return process.env.JWT_SECRET;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const config = getConfig();
|
|
14
|
+
if (config.auth?.jwt_secret) {
|
|
15
|
+
return config.auth.jwt_secret;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (!global.__NEXUSCLI_EPHEMERAL_JWT_SECRET__) {
|
|
19
|
+
global.__NEXUSCLI_EPHEMERAL_JWT_SECRET__ = crypto.randomBytes(48).toString('hex');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!warnedEphemeralSecret) {
|
|
23
|
+
warnedEphemeralSecret = true;
|
|
24
|
+
console.warn('[Auth] JWT secret missing from env and config; using ephemeral in-memory secret for this process');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return global.__NEXUSCLI_EPHEMERAL_JWT_SECRET__;
|
|
28
|
+
}
|
|
6
29
|
|
|
7
30
|
/**
|
|
8
31
|
* Get config user (admin from init) by id
|
|
@@ -31,14 +54,14 @@ function generateToken(user) {
|
|
|
31
54
|
username: user.username,
|
|
32
55
|
role: user.role
|
|
33
56
|
},
|
|
34
|
-
|
|
57
|
+
getJwtSecret(),
|
|
35
58
|
{ expiresIn: JWT_EXPIRES_IN }
|
|
36
59
|
);
|
|
37
60
|
}
|
|
38
61
|
|
|
39
62
|
function verifyToken(token) {
|
|
40
63
|
try {
|
|
41
|
-
return jwt.verify(token,
|
|
64
|
+
return jwt.verify(token, getJwtSecret());
|
|
42
65
|
} catch (err) {
|
|
43
66
|
return null;
|
|
44
67
|
}
|
|
@@ -99,5 +122,5 @@ module.exports = {
|
|
|
99
122
|
verifyToken,
|
|
100
123
|
authMiddleware,
|
|
101
124
|
adminOnly,
|
|
102
|
-
|
|
125
|
+
getJwtSecret
|
|
103
126
|
};
|
|
@@ -28,15 +28,19 @@ class Conversation {
|
|
|
28
28
|
|
|
29
29
|
const sessionStmt = prepare(`
|
|
30
30
|
INSERT INTO sessions (
|
|
31
|
-
id, engine, workspace_path, title,
|
|
31
|
+
id, engine, lane, runtime_id, provider_id, model_id, workspace_path, title,
|
|
32
32
|
last_used_at, created_at, message_count
|
|
33
33
|
)
|
|
34
|
-
VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
34
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
35
35
|
`);
|
|
36
36
|
|
|
37
37
|
sessionStmt.run(
|
|
38
38
|
id,
|
|
39
|
-
'claude
|
|
39
|
+
'claude',
|
|
40
|
+
'native',
|
|
41
|
+
'claude-native',
|
|
42
|
+
'anthropic',
|
|
43
|
+
'sonnet',
|
|
40
44
|
workspace,
|
|
41
45
|
title,
|
|
42
46
|
now,
|
|
@@ -14,15 +14,31 @@ class Message {
|
|
|
14
14
|
* @param {Object} metadata - Optional metadata
|
|
15
15
|
* @param {number} createdAt - Optional timestamp override
|
|
16
16
|
* @param {string} engine - Engine used ('claude' | 'codex' | 'gemini' | 'qwen')
|
|
17
|
+
* @param {Object} runtimeMeta - Optional runtime-aware metadata
|
|
17
18
|
* @returns {Object} Created message
|
|
18
19
|
*/
|
|
19
|
-
static create(
|
|
20
|
+
static create(
|
|
21
|
+
conversationId,
|
|
22
|
+
role,
|
|
23
|
+
content,
|
|
24
|
+
metadata = null,
|
|
25
|
+
createdAt = Date.now(),
|
|
26
|
+
engine = 'claude',
|
|
27
|
+
runtimeMeta = {}
|
|
28
|
+
) {
|
|
20
29
|
const id = uuidv4();
|
|
21
30
|
const now = createdAt || Date.now();
|
|
31
|
+
const lane = runtimeMeta.lane || 'native';
|
|
32
|
+
const runtimeId = runtimeMeta.runtimeId || `${engine}-native`;
|
|
33
|
+
const providerId = runtimeMeta.providerId || null;
|
|
34
|
+
const modelId = runtimeMeta.modelId || null;
|
|
22
35
|
|
|
23
36
|
const stmt = prepare(`
|
|
24
|
-
INSERT INTO messages (
|
|
25
|
-
|
|
37
|
+
INSERT INTO messages (
|
|
38
|
+
id, conversation_id, role, content, created_at, metadata, engine,
|
|
39
|
+
lane, runtime_id, provider_id, model_id
|
|
40
|
+
)
|
|
41
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
26
42
|
`);
|
|
27
43
|
|
|
28
44
|
stmt.run(
|
|
@@ -32,7 +48,11 @@ class Message {
|
|
|
32
48
|
content,
|
|
33
49
|
now,
|
|
34
50
|
metadata ? JSON.stringify(metadata) : null,
|
|
35
|
-
engine
|
|
51
|
+
engine,
|
|
52
|
+
lane,
|
|
53
|
+
runtimeId,
|
|
54
|
+
providerId,
|
|
55
|
+
modelId
|
|
36
56
|
);
|
|
37
57
|
|
|
38
58
|
// Touch conversation (update updated_at)
|
|
@@ -45,7 +65,11 @@ class Message {
|
|
|
45
65
|
content,
|
|
46
66
|
created_at: now,
|
|
47
67
|
metadata,
|
|
48
|
-
engine
|
|
68
|
+
engine,
|
|
69
|
+
lane,
|
|
70
|
+
runtime_id: runtimeId,
|
|
71
|
+
provider_id: providerId,
|
|
72
|
+
model_id: modelId
|
|
49
73
|
};
|
|
50
74
|
}
|
|
51
75
|
|