@mmmbuto/nexuscrew 0.2.0 → 0.2.2
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 +124 -56
- package/bin/nexuscrew.js +79 -152
- package/frontend/dist/assets/{index-8pw4eMB-.js → index-BsldfYnr.js} +1704 -1716
- package/frontend/dist/assets/index-DcQGg3dp.css +1 -0
- package/frontend/dist/index.html +2 -2
- package/lib/server/routes/hosts.js +3 -2
- package/lib/server/routes/launchers.js +12 -0
- package/lib/server/routes/models.js +16 -63
- package/lib/server/routes/runtime.js +10 -0
- package/lib/server/routes/send.js +97 -218
- package/lib/server/routes/sessions.js +7 -39
- package/lib/server/routes/status.js +16 -18
- package/lib/server/routes/tmux.js +88 -0
- package/lib/server/server.js +45 -65
- package/lib/services/engine-discovery.js +192 -445
- package/lib/services/session-store.js +86 -60
- package/lib/services/tmux-manager.js +103 -154
- package/package.json +3 -7
- package/frontend/dist/assets/index-BF0tdvNT.css +0 -1
- package/lib/config/manager.js +0 -362
- package/lib/config/models.js +0 -408
- package/lib/server/db/adapter.js +0 -274
- package/lib/server/db/drivers/sql-js.js +0 -75
- package/lib/server/db/migrate.js +0 -174
- package/lib/server/db/migrations/001_base_schema.sql +0 -70
- package/lib/server/middleware/auth.js +0 -134
- package/lib/server/middleware/rate-limit.js +0 -63
- package/lib/server/models/User.js +0 -128
- package/lib/server/routes/auth.js +0 -168
- package/lib/server/routes/keys.js +0 -15
- package/lib/server/routes/runtimes.js +0 -34
- package/lib/server/routes/speech.js +0 -75
- package/lib/server/routes/upload.js +0 -134
- package/lib/server/routes/wake-lock.js +0 -95
- package/lib/server/routes/workspaces.js +0 -101
- package/lib/server/services/context-bridge.js +0 -425
- package/lib/server/services/runtime-manager.js +0 -462
- package/lib/server/services/summary-generator.js +0 -309
- package/lib/server/services/workspace-manager.js +0 -79
- package/lib/utils/paths.js +0 -107
- package/lib/utils/termux.js +0 -145
|
@@ -1,504 +1,251 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* LauncherDiscovery — environment-agnostic launcher detection
|
|
3
3
|
*
|
|
4
|
-
* Discovers:
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
4
|
+
* Discovers launchers from:
|
|
5
|
+
* - the current runtime shell via live introspection
|
|
6
|
+
* - generic shell rc/profile files as fallback
|
|
7
|
+
* - PATH binaries
|
|
8
|
+
*
|
|
9
|
+
* No user-specific config is assumed and no model catalog is hardcoded.
|
|
8
10
|
*/
|
|
9
11
|
|
|
10
|
-
const { execSync } = require('child_process');
|
|
12
|
+
const { execFileSync, execSync } = require('child_process');
|
|
11
13
|
const path = require('path');
|
|
12
14
|
const fs = require('fs');
|
|
15
|
+
const os = require('os');
|
|
13
16
|
|
|
14
|
-
const
|
|
15
|
-
claude: [
|
|
16
|
-
'${HOME}/.local/bin/claude',
|
|
17
|
-
'${HOME}/.claude/local/claude',
|
|
18
|
-
'/usr/local/bin/claude',
|
|
19
|
-
'/usr/bin/claude',
|
|
20
|
-
'${PREFIX}/bin/claude'
|
|
21
|
-
],
|
|
22
|
-
codex: [
|
|
23
|
-
'${HOME}/.local/codex-lts/bin/codex',
|
|
24
|
-
'${HOME}/.local/codex-lts/node_modules/.bin/codex',
|
|
25
|
-
'${HOME}/.local/codex-lts/package/bin/android-arm64/codex',
|
|
26
|
-
'/usr/local/bin/codex'
|
|
27
|
-
],
|
|
28
|
-
gemini: [
|
|
29
|
-
'${HOME}/.local/bin/gemini',
|
|
30
|
-
'${HOME}/.gemini/bin/gemini',
|
|
31
|
-
'/usr/local/bin/gemini'
|
|
32
|
-
],
|
|
33
|
-
qwen: [
|
|
34
|
-
'${HOME}/.local/bin/qwen',
|
|
35
|
-
'${HOME}/.qwen/bin/qwen',
|
|
36
|
-
'/usr/local/bin/qwen'
|
|
37
|
-
]
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const SHELL_SOURCES = [
|
|
41
|
-
'${HOME}/.zshrc',
|
|
42
|
-
'${HOME}/.bashrc',
|
|
43
|
-
'${HOME}/.bash_profile',
|
|
44
|
-
'${HOME}/.profile',
|
|
45
|
-
'${HOME}/.zprofile',
|
|
46
|
-
'${HOME}/.config/ai-shell/providers.zsh',
|
|
47
|
-
'${HOME}/.config/zsh/chutes-wrapper.zsh',
|
|
48
|
-
'${HOME}/.nexuscrew/providers.zsh'
|
|
49
|
-
];
|
|
50
|
-
|
|
51
|
-
const ENGINE_PREFIXES = ['claude', 'codex', 'gemini', 'qwen'];
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Engine types:
|
|
55
|
-
* - interactive: runs as REPL, needs PTY-like handling (send text + Enter)
|
|
56
|
-
* - exec: runs one-shot commands, returns structured output
|
|
57
|
-
*/
|
|
58
|
-
const ENGINE_TYPES = {
|
|
59
|
-
claude: 'interactive',
|
|
60
|
-
codex: 'exec',
|
|
61
|
-
gemini: 'interactive',
|
|
62
|
-
qwen: 'interactive'
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Build command templates for each engine
|
|
67
|
-
*/
|
|
68
|
-
const ENGINE_COMMANDS = {
|
|
69
|
-
claude: {
|
|
70
|
-
new: (bin, opts) => `${bin} --output-format json${opts.model ? ` --model ${opts.model}` : ''}`,
|
|
71
|
-
resume: (bin, opts) => `${bin} --output-format json -r ${opts.sessionId}${opts.model ? ` --model ${opts.model}` : ''}`,
|
|
72
|
-
interactive: (bin) => `${bin} --output-format json`
|
|
73
|
-
},
|
|
74
|
-
codex: {
|
|
75
|
-
new: (bin, opts) => `${bin} exec --json${opts.model ? ` -m ${opts.model}` : ''}${opts.reasoningEffort ? ` --reasoning ${opts.reasoningEffort}` : ''}`,
|
|
76
|
-
resume: (bin, opts) => `${bin} exec --json resume ${opts.threadId}`,
|
|
77
|
-
interactive: (bin) => bin
|
|
78
|
-
},
|
|
79
|
-
gemini: {
|
|
80
|
-
new: (bin, opts) => `${bin}${opts.model ? ` -m ${opts.model}` : ''}`,
|
|
81
|
-
resume: (bin, opts) => `${bin} --resume ${opts.threadId}${opts.model ? ` -m ${opts.model}` : ''}`,
|
|
82
|
-
interactive: (bin) => bin
|
|
83
|
-
},
|
|
84
|
-
qwen: {
|
|
85
|
-
new: (bin, opts) => `${bin}${opts.model ? ` -m ${opts.model}` : ''}`,
|
|
86
|
-
resume: (bin, opts) => `${bin} --resume ${opts.threadId}${opts.model ? ` -m ${opts.model}` : ''}`,
|
|
87
|
-
interactive: (bin) => bin
|
|
88
|
-
}
|
|
89
|
-
};
|
|
17
|
+
const KNOWN_FAMILIES = ['claude', 'codex', 'gemini', 'qwen'];
|
|
90
18
|
|
|
91
19
|
class EngineDiscovery {
|
|
92
|
-
constructor(
|
|
93
|
-
this.
|
|
20
|
+
constructor(config = {}) {
|
|
21
|
+
this.preferredShell = config.preferredShell || '';
|
|
22
|
+
this.extraShellSources = Array.isArray(config.extraShellSources) ? config.extraShellSources : [];
|
|
94
23
|
this._cache = null;
|
|
95
24
|
}
|
|
96
25
|
|
|
97
|
-
/**
|
|
98
|
-
* Discover all available engines, aliases, and models
|
|
99
|
-
* @returns {Object} engines map
|
|
100
|
-
*/
|
|
101
26
|
discover() {
|
|
102
27
|
if (this._cache) return this._cache;
|
|
103
28
|
|
|
104
|
-
const
|
|
29
|
+
const shellPath = this.getShellPath();
|
|
30
|
+
const shellName = path.basename(shellPath);
|
|
31
|
+
const launchers = [];
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
|
|
34
|
+
const addLauncher = (launcher) => {
|
|
35
|
+
const id = launcher.id || `${launcher.kind}:${launcher.command}`;
|
|
36
|
+
if (!launcher.command || seen.has(id)) return;
|
|
37
|
+
seen.add(id);
|
|
38
|
+
launchers.push({
|
|
39
|
+
id,
|
|
40
|
+
label: launcher.label || launcher.name || launcher.command,
|
|
41
|
+
family: launcher.family || this._detectFamily(launcher.name || launcher.command),
|
|
42
|
+
kind: launcher.kind,
|
|
43
|
+
command: launcher.command,
|
|
44
|
+
commandPreview: launcher.commandPreview || launcher.command,
|
|
45
|
+
source: launcher.source,
|
|
46
|
+
shell: launcher.shell || shellName,
|
|
47
|
+
available: launcher.available !== false,
|
|
48
|
+
runnable: launcher.runnable !== false
|
|
49
|
+
});
|
|
50
|
+
};
|
|
105
51
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
52
|
+
this._discoverPathBinaries().forEach(addLauncher);
|
|
53
|
+
this._discoverLiveShell(shellPath).forEach(addLauncher);
|
|
54
|
+
this._discoverShellFiles(shellName).forEach(addLauncher);
|
|
110
55
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
command: alias.command,
|
|
119
|
-
description: this._describeAlias(alias.name),
|
|
120
|
-
modelHint: this._extractModelHint(alias.command)
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 3. Build model lists for each engine
|
|
126
|
-
for (const [name, engine] of Object.entries(engines)) {
|
|
127
|
-
engine.models = this._buildModels(name, engine);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
this._cache = engines;
|
|
131
|
-
return engines;
|
|
56
|
+
launchers.sort((a, b) => a.label.localeCompare(b.label));
|
|
57
|
+
this._cache = {
|
|
58
|
+
shell: shellPath,
|
|
59
|
+
homeDir: os.homedir(),
|
|
60
|
+
launchers
|
|
61
|
+
};
|
|
62
|
+
return this._cache;
|
|
132
63
|
}
|
|
133
64
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
* @returns {Array<{id, name, engine, type, alias?}>}
|
|
137
|
-
*/
|
|
138
|
-
getAllModels() {
|
|
139
|
-
const engines = this.discover();
|
|
140
|
-
const models = [];
|
|
141
|
-
for (const [engineName, engine] of Object.entries(engines)) {
|
|
142
|
-
for (const model of engine.models) {
|
|
143
|
-
models.push({ ...model, engine: engineName });
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return models;
|
|
65
|
+
getLaunchers() {
|
|
66
|
+
return this.discover().launchers;
|
|
147
67
|
}
|
|
148
68
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
*/
|
|
152
|
-
getForFrontend() {
|
|
153
|
-
const engines = this.discover();
|
|
154
|
-
const result = {};
|
|
155
|
-
for (const [name, engine] of Object.entries(engines)) {
|
|
156
|
-
result[name] = {
|
|
157
|
-
available: engine.available,
|
|
158
|
-
type: engine.type,
|
|
159
|
-
path: engine.path,
|
|
160
|
-
models: engine.models,
|
|
161
|
-
aliases: engine.aliases,
|
|
162
|
-
endpoint: '/api/v1/send' // Single unified endpoint
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
return result;
|
|
69
|
+
getLauncher(id) {
|
|
70
|
+
return this.getLaunchers().find((launcher) => launcher.id === id) || null;
|
|
166
71
|
}
|
|
167
72
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const resolved = searchPaths.map(p =>
|
|
172
|
-
p.replace(/\$\{HOME\}/g, process.env.HOME || '')
|
|
173
|
-
.replace(/\$\{PREFIX\}/g, process.env.PREFIX || '')
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
let foundPath = null;
|
|
177
|
-
for (const p of resolved) {
|
|
178
|
-
try {
|
|
179
|
-
if (fs.existsSync(p) && fs.statSync(p).mode & 0o111) {
|
|
180
|
-
foundPath = p;
|
|
181
|
-
break;
|
|
182
|
-
}
|
|
183
|
-
} catch {}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// Fallback: which
|
|
187
|
-
if (!foundPath) {
|
|
188
|
-
try {
|
|
189
|
-
foundPath = execSync(`which ${name} 2>/dev/null`).toString().trim();
|
|
190
|
-
} catch {}
|
|
191
|
-
}
|
|
73
|
+
getShellPath() {
|
|
74
|
+
return this.preferredShell || process.env.SHELL || '/bin/sh';
|
|
75
|
+
}
|
|
192
76
|
|
|
77
|
+
getRuntimeInfo() {
|
|
193
78
|
return {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
aliases: [],
|
|
198
|
-
models: []
|
|
79
|
+
homeDir: os.homedir(),
|
|
80
|
+
shell: this.getShellPath(),
|
|
81
|
+
platform: process.platform
|
|
199
82
|
};
|
|
200
83
|
}
|
|
201
84
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
return this._discoverShellSources();
|
|
85
|
+
clearCache() {
|
|
86
|
+
this._cache = null;
|
|
205
87
|
}
|
|
206
88
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
for (const filePath of searchPaths) {
|
|
225
|
-
if (!fs.existsSync(filePath) || processedFiles.has(filePath)) continue;
|
|
226
|
-
processedFiles.add(filePath);
|
|
227
|
-
|
|
228
|
-
try {
|
|
229
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
230
|
-
const lines = content.split('\n');
|
|
231
|
-
|
|
232
|
-
for (let i = 0; i < lines.length; i++) {
|
|
233
|
-
const line = lines[i].trim();
|
|
234
|
-
|
|
235
|
-
// Skip comments and empty lines
|
|
236
|
-
if (!line || line.startsWith('#')) continue;
|
|
237
|
-
|
|
238
|
-
// Follow source/. directives (1 level deep)
|
|
239
|
-
if (line.startsWith('source ') || line.startsWith('. ')) {
|
|
240
|
-
const sourcedFile = line.replace(/^source\s+|^\.\s+/, '').replace(/['"]/g, '').trim();
|
|
241
|
-
const resolvedPath = sourcedFile.startsWith('~')
|
|
242
|
-
? path.join(HOME, sourcedFile.slice(1))
|
|
243
|
-
: sourcedFile;
|
|
244
|
-
|
|
245
|
-
if (fs.existsSync(resolvedPath) && !processedFiles.has(resolvedPath)) {
|
|
246
|
-
processedFiles.add(resolvedPath);
|
|
247
|
-
try {
|
|
248
|
-
const sourcedContent = fs.readFileSync(resolvedPath, 'utf8');
|
|
249
|
-
this._parseShellContent(sourcedContent, aliases, resolvedPath);
|
|
250
|
-
} catch (err) {
|
|
251
|
-
// Skip unreadable sourced files
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
continue;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// Parse current line
|
|
258
|
-
this._parseShellLine(line, aliases, filePath);
|
|
259
|
-
}
|
|
260
|
-
} catch (err) {
|
|
261
|
-
// Skip unreadable files
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
return aliases;
|
|
89
|
+
_discoverPathBinaries() {
|
|
90
|
+
return KNOWN_FAMILIES.flatMap((family) => {
|
|
91
|
+
const binPath = this._resolveCommand(family);
|
|
92
|
+
if (!binPath) return [];
|
|
93
|
+
return [{
|
|
94
|
+
id: `binary:${family}`,
|
|
95
|
+
label: family,
|
|
96
|
+
family,
|
|
97
|
+
kind: 'binary',
|
|
98
|
+
command: binPath,
|
|
99
|
+
commandPreview: binPath,
|
|
100
|
+
source: 'path',
|
|
101
|
+
runnable: true
|
|
102
|
+
}];
|
|
103
|
+
});
|
|
266
104
|
}
|
|
267
105
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Match: name() { ... } (function definition - single line)
|
|
289
|
-
const funcRegex = /^([a-z0-9][a-z0-9-]*)\(\)\s*\{([^}]*)\}/;
|
|
290
|
-
const funcMatch = line.match(funcRegex);
|
|
291
|
-
if (funcMatch) {
|
|
292
|
-
const name = funcMatch[1];
|
|
293
|
-
if (this._isEngineAlias(name)) {
|
|
294
|
-
aliases.push({
|
|
295
|
-
name,
|
|
296
|
-
command: funcMatch[2].trim(),
|
|
297
|
-
source,
|
|
298
|
-
modelHint: this._extractModelHint(funcMatch[2])
|
|
299
|
-
});
|
|
300
|
-
}
|
|
106
|
+
_discoverLiveShell(shellPath) {
|
|
107
|
+
const shellName = path.basename(shellPath);
|
|
108
|
+
if (!fs.existsSync(shellPath)) return [];
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
const script = [
|
|
112
|
+
'alias -p 2>/dev/null || true',
|
|
113
|
+
'printf "\\n__NEXUSCREW_FUNCTIONS__\\n"',
|
|
114
|
+
'typeset -f 2>/dev/null || declare -f 2>/dev/null || true'
|
|
115
|
+
].join('; ');
|
|
116
|
+
|
|
117
|
+
const output = execFileSync(shellPath, this._shellArgs(shellName, script), {
|
|
118
|
+
encoding: 'utf8',
|
|
119
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
120
|
+
});
|
|
121
|
+
return this._parseShellDump(output, shellName, 'live-shell');
|
|
122
|
+
} catch {
|
|
123
|
+
return [];
|
|
301
124
|
}
|
|
302
125
|
}
|
|
303
126
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
_parseShellContent(content, aliases, source) {
|
|
308
|
-
const lines = content.split('\n');
|
|
309
|
-
|
|
310
|
-
// Multi-line function buffer
|
|
311
|
-
let inFunction = false;
|
|
312
|
-
let funcName = null;
|
|
313
|
-
let funcBody = [];
|
|
314
|
-
|
|
315
|
-
for (const line of lines) {
|
|
316
|
-
const trimmed = line.trim();
|
|
127
|
+
_discoverShellFiles(shellName) {
|
|
128
|
+
const homeDir = os.homedir();
|
|
129
|
+
const shellFiles = new Set();
|
|
317
130
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
if (funcStart) {
|
|
325
|
-
const name = funcStart[1];
|
|
326
|
-
if (this._isEngineAlias(name)) {
|
|
327
|
-
inFunction = true;
|
|
328
|
-
funcName = name;
|
|
329
|
-
funcBody = [];
|
|
330
|
-
}
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Single-line alias/function
|
|
335
|
-
this._parseShellLine(trimmed, aliases, source);
|
|
336
|
-
} else {
|
|
337
|
-
// Function end
|
|
338
|
-
if (trimmed === '}') {
|
|
339
|
-
inFunction = false;
|
|
340
|
-
if (funcName) {
|
|
341
|
-
aliases.push({
|
|
342
|
-
name: funcName,
|
|
343
|
-
command: funcBody.join(' ').trim(),
|
|
344
|
-
source,
|
|
345
|
-
modelHint: this._extractModelHint(funcBody.join(' '))
|
|
346
|
-
});
|
|
347
|
-
funcName = null;
|
|
348
|
-
funcBody = [];
|
|
349
|
-
}
|
|
350
|
-
continue;
|
|
351
|
-
}
|
|
131
|
+
for (const candidate of this._defaultShellFiles(shellName)) {
|
|
132
|
+
shellFiles.add(path.join(homeDir, candidate));
|
|
133
|
+
}
|
|
134
|
+
for (const candidate of this.extraShellSources) {
|
|
135
|
+
if (candidate) shellFiles.add(candidate);
|
|
136
|
+
}
|
|
352
137
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
138
|
+
const discovered = [];
|
|
139
|
+
for (const filePath of shellFiles) {
|
|
140
|
+
if (!fs.existsSync(filePath)) continue;
|
|
141
|
+
try {
|
|
142
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
143
|
+
discovered.push(...this._parseShellFile(content, shellName, filePath));
|
|
144
|
+
} catch {}
|
|
356
145
|
}
|
|
146
|
+
return discovered;
|
|
357
147
|
}
|
|
358
148
|
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
149
|
+
_parseShellDump(content, shellName, source) {
|
|
150
|
+
const [aliasSection = '', functionSection = ''] = content.split('\n__NEXUSCREW_FUNCTIONS__\n');
|
|
151
|
+
return [
|
|
152
|
+
...this._parseAliases(aliasSection, shellName, source),
|
|
153
|
+
...this._parseFunctions(functionSection, shellName, source)
|
|
154
|
+
];
|
|
364
155
|
}
|
|
365
156
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
return null;
|
|
157
|
+
_parseShellFile(content, shellName, filePath) {
|
|
158
|
+
return [
|
|
159
|
+
...this._parseAliases(content, shellName, `shell-file:${filePath}`),
|
|
160
|
+
...this._parseFunctions(content, shellName, `shell-file:${filePath}`)
|
|
161
|
+
];
|
|
372
162
|
}
|
|
373
163
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
const
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
'glm5': 'GLM-5',
|
|
395
|
-
'glm5ist': 'GLM-5 Turbo',
|
|
396
|
-
'kimi': 'Kimi K2.5',
|
|
397
|
-
'minimax': 'MiniMax',
|
|
398
|
-
'minimax27': 'MiniMax M2.7',
|
|
399
|
-
'qwencodex': 'Qwen Coder',
|
|
400
|
-
'qwennext': 'Qwen Coder Next',
|
|
401
|
-
'qwen3next': 'Qwen 3 Next',
|
|
402
|
-
'glm5turbo': 'GLM-5 Turbo',
|
|
403
|
-
'deepseek': 'DeepSeek V3.2'
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
const descriptions = [];
|
|
407
|
-
for (const part of rest) {
|
|
408
|
-
if (providerMap[part]) descriptions.push(providerMap[part]);
|
|
409
|
-
else if (modelMap[part]) descriptions.push(modelMap[part]);
|
|
410
|
-
else descriptions.push(part);
|
|
164
|
+
_parseAliases(content, shellName, source) {
|
|
165
|
+
const aliases = [];
|
|
166
|
+
const regex = /^alias\s+([A-Za-z0-9_.:-]+)=['"]([^'"]+)['"]$/gm;
|
|
167
|
+
let match;
|
|
168
|
+
while ((match = regex.exec(content)) !== null) {
|
|
169
|
+
const name = match[1];
|
|
170
|
+
const body = match[2].trim();
|
|
171
|
+
if (!this._isLauncherCandidate(name, body)) continue;
|
|
172
|
+
aliases.push({
|
|
173
|
+
id: `alias:${name}`,
|
|
174
|
+
label: name,
|
|
175
|
+
name,
|
|
176
|
+
family: this._detectFamily(name, body),
|
|
177
|
+
kind: 'alias',
|
|
178
|
+
command: name,
|
|
179
|
+
commandPreview: body,
|
|
180
|
+
source,
|
|
181
|
+
shell: shellName,
|
|
182
|
+
runnable: source === 'live-shell'
|
|
183
|
+
});
|
|
411
184
|
}
|
|
412
|
-
|
|
413
|
-
return `${engine} → ${descriptions.join(' / ')}`;
|
|
185
|
+
return aliases;
|
|
414
186
|
}
|
|
415
187
|
|
|
416
|
-
|
|
417
|
-
|
|
188
|
+
_parseFunctions(content, shellName, source) {
|
|
189
|
+
const functions = [];
|
|
190
|
+
const regex = /^([A-Za-z0-9_.:-]+)\s*(?:\(\))?\s*\{/gm;
|
|
191
|
+
let match;
|
|
192
|
+
while ((match = regex.exec(content)) !== null) {
|
|
193
|
+
const name = match[1];
|
|
194
|
+
if (!this._isLauncherCandidate(name, name)) continue;
|
|
195
|
+
functions.push({
|
|
196
|
+
id: `function:${name}`,
|
|
197
|
+
label: name,
|
|
198
|
+
name,
|
|
199
|
+
family: this._detectFamily(name),
|
|
200
|
+
kind: 'function',
|
|
201
|
+
command: name,
|
|
202
|
+
commandPreview: `${name}()`,
|
|
203
|
+
source,
|
|
204
|
+
shell: shellName,
|
|
205
|
+
runnable: source === 'live-shell'
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
return functions;
|
|
209
|
+
}
|
|
418
210
|
|
|
419
|
-
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
211
|
+
_isLauncherCandidate(name, body = '') {
|
|
212
|
+
const haystack = `${name} ${body}`.toLowerCase();
|
|
213
|
+
return KNOWN_FAMILIES.some((family) => haystack.includes(family));
|
|
214
|
+
}
|
|
424
215
|
|
|
425
|
-
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
216
|
+
_detectFamily(name, body = '') {
|
|
217
|
+
const haystack = `${name || ''} ${body}`.toLowerCase();
|
|
218
|
+
return KNOWN_FAMILIES.find((family) => haystack.includes(family)) || 'custom';
|
|
219
|
+
}
|
|
430
220
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
221
|
+
_resolveCommand(command) {
|
|
222
|
+
try {
|
|
223
|
+
const resolved = execSync(`command -v ${command} 2>/dev/null`, {
|
|
224
|
+
encoding: 'utf8',
|
|
225
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
226
|
+
}).trim();
|
|
227
|
+
return resolved || null;
|
|
228
|
+
} catch {
|
|
229
|
+
return null;
|
|
435
230
|
}
|
|
231
|
+
}
|
|
436
232
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
233
|
+
_defaultShellFiles(shellName) {
|
|
234
|
+
switch (shellName) {
|
|
235
|
+
case 'zsh':
|
|
236
|
+
return ['.zshrc', '.zprofile', '.zshenv'];
|
|
237
|
+
case 'bash':
|
|
238
|
+
return ['.bashrc', '.bash_profile', '.profile'];
|
|
239
|
+
default:
|
|
240
|
+
return ['.profile'];
|
|
445
241
|
}
|
|
446
|
-
|
|
447
|
-
return null;
|
|
448
242
|
}
|
|
449
243
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if (!engine.available) return models;
|
|
454
|
-
|
|
455
|
-
// Native models per engine
|
|
456
|
-
const nativeModels = {
|
|
457
|
-
claude: [
|
|
458
|
-
{ id: 'sonnet', name: 'Claude Sonnet', lane: 'native' },
|
|
459
|
-
{ id: 'opus', name: 'Claude Opus', lane: 'native' },
|
|
460
|
-
{ id: 'haiku', name: 'Claude Haiku', lane: 'native' }
|
|
461
|
-
],
|
|
462
|
-
codex: [
|
|
463
|
-
{ id: 'gpt-5.4', name: 'GPT-5.4', lane: 'native' },
|
|
464
|
-
{ id: 'gpt-5.3-codex', name: 'GPT-5.3 Codex', lane: 'native' },
|
|
465
|
-
{ id: 'codex-mini-latest', name: 'Codex Mini', lane: 'native' }
|
|
466
|
-
],
|
|
467
|
-
gemini: [
|
|
468
|
-
{ id: 'gemini-3-pro-preview', name: 'Gemini 3 Pro', lane: 'native' },
|
|
469
|
-
{ id: 'gemini-3-flash-preview', name: 'Gemini 3 Flash', lane: 'native' },
|
|
470
|
-
{ id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro', lane: 'native' }
|
|
471
|
-
],
|
|
472
|
-
qwen: [
|
|
473
|
-
{ id: 'qwen3-coder-plus', name: 'Qwen 3 Coder Plus', lane: 'native' },
|
|
474
|
-
{ id: 'qwen3.5-plus', name: 'Qwen 3.5 Plus', lane: 'native' },
|
|
475
|
-
{ id: 'qwen3-max', name: 'Qwen 3 Max', lane: 'native' }
|
|
476
|
-
]
|
|
477
|
-
};
|
|
478
|
-
|
|
479
|
-
models.push(...(nativeModels[engineName] || []));
|
|
480
|
-
|
|
481
|
-
// Alias-based custom models
|
|
482
|
-
for (const alias of engine.aliases) {
|
|
483
|
-
if (alias.modelHint) {
|
|
484
|
-
models.push({
|
|
485
|
-
id: `${engineName}-${alias.name}`,
|
|
486
|
-
name: alias.description || alias.name,
|
|
487
|
-
lane: 'custom',
|
|
488
|
-
alias: alias.name,
|
|
489
|
-
modelHint: alias.modelHint
|
|
490
|
-
});
|
|
491
|
-
} else {
|
|
492
|
-
models.push({
|
|
493
|
-
id: `${engineName}-${alias.name}`,
|
|
494
|
-
name: alias.description || alias.name,
|
|
495
|
-
lane: 'custom',
|
|
496
|
-
alias: alias.name
|
|
497
|
-
});
|
|
498
|
-
}
|
|
244
|
+
_shellArgs(shellName, script) {
|
|
245
|
+
if (shellName === 'bash' || shellName === 'zsh') {
|
|
246
|
+
return ['-ilc', script];
|
|
499
247
|
}
|
|
500
|
-
|
|
501
|
-
return models;
|
|
248
|
+
return ['-lc', script];
|
|
502
249
|
}
|
|
503
250
|
}
|
|
504
251
|
|