@openagents-org/agent-launcher 0.2.142 → 0.2.145
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/package.json +1 -1
- package/registry.json +274 -66
- package/src/adapters/aider.js +634 -0
- package/src/adapters/amp.js +477 -0
- package/src/adapters/base.js +108 -7
- package/src/adapters/claude.js +50 -34
- package/src/adapters/cline-stream.js +632 -0
- package/src/adapters/cline.js +914 -0
- package/src/adapters/codex.js +14 -16
- package/src/adapters/copilot-stream-parser.js +380 -0
- package/src/adapters/copilot.js +796 -0
- package/src/adapters/cursor.js +9 -15
- package/src/adapters/gemini.js +10 -12
- package/src/adapters/goose-stream.js +268 -0
- package/src/adapters/goose.js +649 -0
- package/src/adapters/health-status.js +166 -0
- package/src/adapters/hermes.js +8 -17
- package/src/adapters/index.js +16 -1
- package/src/adapters/nanoclaw-bridge.js +264 -0
- package/src/adapters/nanoclaw-channel-install.js +411 -0
- package/src/adapters/nanoclaw-control.js +491 -0
- package/src/adapters/nanoclaw-delivery-store.js +0 -0
- package/src/adapters/nanoclaw-protocol.js +349 -0
- package/src/adapters/nanoclaw.js +661 -9
- package/src/adapters/opencode.js +665 -79
- package/src/agent-rows.js +13 -1
- package/src/auth-guidance.js +98 -0
- package/src/cli.js +29 -7
- package/src/config.js +8 -2
- package/src/daemon.js +108 -6
- package/src/installer.js +509 -10
- package/src/paths.js +125 -2
- package/src/tui.js +29 -2
package/src/paths.js
CHANGED
|
@@ -50,6 +50,12 @@ function getExtraBinDirs() {
|
|
|
50
50
|
// Common: ~/.local/bin (pipx, user installs)
|
|
51
51
|
_push(dirs, path.join(HOME, '.local', 'bin'));
|
|
52
52
|
|
|
53
|
+
// Aider (uv tool install) — its executable honors XDG_BIN_HOME /
|
|
54
|
+
// XDG_DATA_HOME/../bin / ~/.local/bin and also always lands in the uv tools
|
|
55
|
+
// venv. A GUI/daemon process won't see the installer's PATH edit, so add the
|
|
56
|
+
// real install dirs explicitly (filtered to existing dirs by the caller).
|
|
57
|
+
for (const d of aiderBinDirs()) _push(dirs, d);
|
|
58
|
+
|
|
53
59
|
// Also add the directory containing the current node binary
|
|
54
60
|
try {
|
|
55
61
|
const nodeDir = path.dirname(process.execPath);
|
|
@@ -142,6 +148,14 @@ function whichBinary(name) {
|
|
|
142
148
|
return cached.value;
|
|
143
149
|
}
|
|
144
150
|
|
|
151
|
+
// On a non-English Windows the console OUTPUT codepage is OEM (e.g. 936/GBK on
|
|
152
|
+
// zh-CN), so `where` prints a path whose non-ASCII bytes don't match the utf-8
|
|
153
|
+
// decoding execSync does — a Chinese username comes back mangled (e.g.
|
|
154
|
+
// `C:\Users\??.?[\…`) and yields ENOENT downstream. We can't reliably re-encode
|
|
155
|
+
// it (and forcing `chcp` is unsafe under windowsHide's console-less cmd), so we
|
|
156
|
+
// existence-check every hit and only return one that's real; a mangled path
|
|
157
|
+
// fails and the caller falls through to its Node-derived tiers (built from
|
|
158
|
+
// process.env / os.homedir, which the OS hands us as correct UTF-16).
|
|
145
159
|
const cmd = IS_WINDOWS ? `where ${name}` : `which ${name}`;
|
|
146
160
|
try {
|
|
147
161
|
const result = execSync(cmd, {
|
|
@@ -150,8 +164,12 @@ function whichBinary(name) {
|
|
|
150
164
|
env: { ...process.env, PATH: getEnhancedPATH() },
|
|
151
165
|
timeout: 5000,
|
|
152
166
|
windowsHide: true,
|
|
153
|
-
})
|
|
154
|
-
|
|
167
|
+
});
|
|
168
|
+
let value = null;
|
|
169
|
+
for (const line of result.split(/\r?\n/)) {
|
|
170
|
+
const hit = line.trim();
|
|
171
|
+
if (hit && fs.existsSync(hit)) { value = hit; break; }
|
|
172
|
+
}
|
|
155
173
|
whichBinaryCache.set(cacheKey, { value, at: Date.now() });
|
|
156
174
|
return value;
|
|
157
175
|
} catch {
|
|
@@ -160,6 +178,64 @@ function whichBinary(name) {
|
|
|
160
178
|
}
|
|
161
179
|
}
|
|
162
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Codepage-safe `where`/`which` lookup, shared by every CLI adapter.
|
|
183
|
+
*
|
|
184
|
+
* Hazard: on a non-English Windows the console output codepage is OEM (e.g.
|
|
185
|
+
* 936/GBK on zh-CN), so execSync decodes `where` stdout with the wrong codepage
|
|
186
|
+
* and mangles a non-ASCII (e.g. Chinese) username in the path — yielding ENOENT
|
|
187
|
+
* downstream (the `C:\Users\??.?[\…\claude.cmd` failure). Two safe defenses,
|
|
188
|
+
* no `chcp` (which is unreliable under windowsHide's console-less cmd):
|
|
189
|
+
* 1. (Windows) check the npm-global default `%APPDATA%\npm\<name>.cmd` FIRST.
|
|
190
|
+
* APPDATA comes from the OS as UTF-16 via Node, so this path is always
|
|
191
|
+
* correctly encoded — and it's where the npm-installed CLIs actually live.
|
|
192
|
+
* 2. Fall back to `where`/`which`, but existence-check every hit so a mangled
|
|
193
|
+
* path is skipped and the caller drops to its own Node-derived tiers.
|
|
194
|
+
*
|
|
195
|
+
* @param {string|string[]} names base name(s), e.g. 'claude' or ['cursor-agent','agent'].
|
|
196
|
+
* On Windows each is tried as <name>.cmd, <name>.exe, <name>.
|
|
197
|
+
* @param {object} [env] env for the lookup (defaults to getEnhancedEnv()).
|
|
198
|
+
* @returns {string|null} an existing absolute path, or null.
|
|
199
|
+
*/
|
|
200
|
+
function whereBinary(names, env) {
|
|
201
|
+
const bases = Array.isArray(names) ? names : [names];
|
|
202
|
+
|
|
203
|
+
// 1. npm-global default, derived from Node's env (correct encoding even when
|
|
204
|
+
// the username is non-ASCII). This is where `npm i -g <cli>` drops the shim.
|
|
205
|
+
if (IS_WINDOWS && process.env.APPDATA) {
|
|
206
|
+
for (const b of bases) {
|
|
207
|
+
const c = path.join(process.env.APPDATA, 'npm', `${b}.cmd`);
|
|
208
|
+
try { if (fs.existsSync(c)) return c; } catch {}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// 2. PATH lookup, existence-guarded.
|
|
213
|
+
let cmd;
|
|
214
|
+
if (IS_WINDOWS) {
|
|
215
|
+
const parts = [];
|
|
216
|
+
for (const b of bases) {
|
|
217
|
+
parts.push(`where ${b}.cmd 2>nul`, `where ${b}.exe 2>nul`, `where ${b} 2>nul`);
|
|
218
|
+
}
|
|
219
|
+
cmd = parts.join(' || ');
|
|
220
|
+
} else {
|
|
221
|
+
cmd = bases.map((b) => `which ${b}`).join(' || ');
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const out = execSync(cmd, {
|
|
225
|
+
encoding: 'utf-8',
|
|
226
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
227
|
+
timeout: 5000,
|
|
228
|
+
windowsHide: true,
|
|
229
|
+
env: env || getEnhancedEnv(),
|
|
230
|
+
});
|
|
231
|
+
for (const line of out.split(/\r?\n/)) {
|
|
232
|
+
const hit = line.trim();
|
|
233
|
+
if (hit && fs.existsSync(hit)) return hit;
|
|
234
|
+
}
|
|
235
|
+
} catch {}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
163
239
|
// ---- Windows paths ----
|
|
164
240
|
|
|
165
241
|
function _addWindowsPaths(dirs) {
|
|
@@ -197,6 +273,12 @@ function _addWindowsPaths(dirs) {
|
|
|
197
273
|
if (localAppData) _push(dirs, path.join(localAppData, 'cursor-agent'));
|
|
198
274
|
_push(dirs, path.join(HOME, '.local', 'bin'));
|
|
199
275
|
|
|
276
|
+
// Amp CLI (irm https://ampcode.com/install.ps1 | iex) — same registry-PATH
|
|
277
|
+
// staleness as Cursor; add the canonical install dir(s) so an already-running
|
|
278
|
+
// daemon resolves amp without a reboot.
|
|
279
|
+
_push(dirs, path.join(HOME, '.amp', 'bin'));
|
|
280
|
+
if (localAppData) _push(dirs, path.join(localAppData, 'amp'));
|
|
281
|
+
|
|
200
282
|
// Hermes CLI native (no-WSL) installer drops hermes.exe in the portable
|
|
201
283
|
// venv's Scripts dir and the uv shim in %LOCALAPPDATA%\hermes\bin. Same
|
|
202
284
|
// registry-PATH staleness as Cursor — add explicitly so an already-running
|
|
@@ -291,6 +373,12 @@ function _addUnixPaths(dirs) {
|
|
|
291
373
|
|
|
292
374
|
// Cursor CLI native installer (curl https://cursor.com/install | bash)
|
|
293
375
|
_push(dirs, path.join(HOME, '.cursor', 'bin'));
|
|
376
|
+
|
|
377
|
+
// Amp CLI native installer (curl https://ampcode.com/install.sh | bash)
|
|
378
|
+
// drops the binary in ~/.amp/bin and only symlinks into ~/.local/bin when
|
|
379
|
+
// that dir is already on PATH — so a GUI- or daemon-spawned process never
|
|
380
|
+
// sees it unless the canonical dir is listed here explicitly.
|
|
381
|
+
_push(dirs, path.join(HOME, '.amp', 'bin'));
|
|
294
382
|
}
|
|
295
383
|
|
|
296
384
|
// ---- macOS-specific ----
|
|
@@ -396,15 +484,50 @@ function clearBinaryLookupCache() {
|
|
|
396
484
|
whichBinaryCache.clear();
|
|
397
485
|
}
|
|
398
486
|
|
|
487
|
+
/**
|
|
488
|
+
* Directories where the Aider CLI executable can land, in the SAME priority the
|
|
489
|
+
* official installer (aider.chat/install.{sh,ps1} → `uv tool install`) uses, so
|
|
490
|
+
* detection matches reality on every platform:
|
|
491
|
+
* 1. $XDG_BIN_HOME
|
|
492
|
+
* 2. $XDG_DATA_HOME/../bin
|
|
493
|
+
* 3. ~/.local/bin (the default uv-tool / pipx / pip --user bin)
|
|
494
|
+
* 4. the uv tools venv for aider-chat (Scripts on Windows, bin on Unix) — the
|
|
495
|
+
* executable always lands here on a successful `uv tool install`, even if
|
|
496
|
+
* the bin-dir copy/PATH edit didn't happen.
|
|
497
|
+
* Uses os.homedir()/live env so it reflects the current process (test-friendly).
|
|
498
|
+
* Returns directories only; callers join the platform binary names.
|
|
499
|
+
*/
|
|
500
|
+
function aiderBinDirs() {
|
|
501
|
+
const home = os.homedir();
|
|
502
|
+
const dirs = [];
|
|
503
|
+
if (process.env.XDG_BIN_HOME) dirs.push(process.env.XDG_BIN_HOME);
|
|
504
|
+
if (process.env.XDG_DATA_HOME) dirs.push(path.join(process.env.XDG_DATA_HOME, '..', 'bin'));
|
|
505
|
+
dirs.push(path.join(home, '.local', 'bin'));
|
|
506
|
+
if (IS_WINDOWS) {
|
|
507
|
+
const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
|
|
508
|
+
const uvTools = process.env.UV_TOOL_DIR || path.join(appData, 'uv', 'tools');
|
|
509
|
+
dirs.push(path.join(uvTools, 'aider-chat', 'Scripts'));
|
|
510
|
+
dirs.push(path.join(home, 'bin'));
|
|
511
|
+
} else {
|
|
512
|
+
const uvTools = process.env.UV_TOOL_DIR
|
|
513
|
+
|| path.join(home, '.local', 'share', 'uv', 'tools');
|
|
514
|
+
dirs.push(path.join(uvTools, 'aider-chat', 'bin'));
|
|
515
|
+
dirs.push(path.join(home, 'bin'), '/usr/local/bin', '/opt/homebrew/bin');
|
|
516
|
+
}
|
|
517
|
+
return dirs;
|
|
518
|
+
}
|
|
519
|
+
|
|
399
520
|
module.exports = {
|
|
400
521
|
getExtraBinDirs,
|
|
401
522
|
getEnhancedPATH,
|
|
402
523
|
getEnhancedEnv,
|
|
403
524
|
whichBinary,
|
|
525
|
+
whereBinary,
|
|
404
526
|
clearBinaryLookupCache,
|
|
405
527
|
getRuntimePrefix,
|
|
406
528
|
getCorePrefix,
|
|
407
529
|
defaultAgentWorkdir,
|
|
530
|
+
aiderBinDirs,
|
|
408
531
|
IS_WINDOWS,
|
|
409
532
|
IS_MACOS,
|
|
410
533
|
SEP,
|
package/src/tui.js
CHANGED
|
@@ -13,6 +13,7 @@ const path = require('path');
|
|
|
13
13
|
const fs = require('fs');
|
|
14
14
|
const { spawn } = require('child_process');
|
|
15
15
|
const { getExtraBinDirs } = require('./paths');
|
|
16
|
+
const { hasCredentialMetadata, formatAuthGuidance } = require('./auth-guidance');
|
|
16
17
|
|
|
17
18
|
const IS_WINDOWS = process.platform === 'win32';
|
|
18
19
|
|
|
@@ -67,7 +68,14 @@ function getConnector() {
|
|
|
67
68
|
|
|
68
69
|
function describeHealth(health) {
|
|
69
70
|
if (!health) return '';
|
|
70
|
-
if (!health.ready)
|
|
71
|
+
if (!health.ready) {
|
|
72
|
+
// "Not installed" is reserved for a genuinely missing executable; an
|
|
73
|
+
// installed-but-signed-out agent shows its login/config message instead.
|
|
74
|
+
if (health.reason === 'not_installed' || health.installed === false) {
|
|
75
|
+
return 'Not installed';
|
|
76
|
+
}
|
|
77
|
+
return health.message || 'Not configured';
|
|
78
|
+
}
|
|
71
79
|
const parts = ['Ready'];
|
|
72
80
|
if (health.auth_mode === 'api_key') parts.push('API key');
|
|
73
81
|
else if (health.auth_mode === 'cli_login') parts.push('CLI login');
|
|
@@ -283,11 +291,14 @@ function createTUI() {
|
|
|
283
291
|
// Local-only agents (no workspace) show a dimmed "(local)" marker.
|
|
284
292
|
const ws = r.workspace || `{gray-fg}${r.workspaceLabel}{/gray-fg}`;
|
|
285
293
|
items.push(` ${r.name.padEnd(22)} ${r.type.padEnd(14)} ${state.padEnd(30)} ${ws}`);
|
|
286
|
-
// Detail row: working dir + config warning
|
|
294
|
+
// Detail row: working dir + config warning + the REAL runtime error
|
|
295
|
+
// (spawn/join/heartbeat) when the daemon reported one, so a failure shows
|
|
296
|
+
// its actual cause instead of a swallowed/"Not installed" guess.
|
|
287
297
|
const details = [];
|
|
288
298
|
details.push(r.path || process.env.HOME || '~');
|
|
289
299
|
if (r.health) details.push(`{cyan-fg}${describeHealth(r.health)}{/cyan-fg}`);
|
|
290
300
|
if (r.notReadyMsg) details.push(`{yellow-fg}⚠ ${r.notReadyMsg}{/yellow-fg}`);
|
|
301
|
+
if (r.lastError) details.push(`{red-fg}✗ ${r.lastError}{/red-fg}`);
|
|
291
302
|
items.push(` {gray-fg} ${details.join(' | ')}{/gray-fg}`);
|
|
292
303
|
}
|
|
293
304
|
agentList.setItems(items);
|
|
@@ -754,6 +765,22 @@ function createTUI() {
|
|
|
754
765
|
currentView = 'configure';
|
|
755
766
|
const envFields = connector.registry.getEnvFields(agent.type);
|
|
756
767
|
if (!envFields || envFields.length === 0) {
|
|
768
|
+
// Some agents declare no env_config yet still REQUIRE a sign-in/credential
|
|
769
|
+
// (e.g. Gemini's OAuth login). Detect that via the registry's check_ready
|
|
770
|
+
// and show readiness + login guidance instead of a misleading "no
|
|
771
|
+
// configuration required". Agents without any credential metadata keep the
|
|
772
|
+
// original message — this is gated, so other agents are untouched.
|
|
773
|
+
const entry = connector.registry.getEntry(agent.type);
|
|
774
|
+
const checkReady = entry && entry.check_ready;
|
|
775
|
+
if (hasCredentialMetadata(checkReady)) {
|
|
776
|
+
let health = null;
|
|
777
|
+
try { health = connector.healthCheck(agent.type); } catch {}
|
|
778
|
+
const g = formatAuthGuidance(entry, health);
|
|
779
|
+
const tag = g.ready ? 'green-fg' : 'yellow-fg';
|
|
780
|
+
log(`{bold}{${tag}}${entry.label || agent.type} authentication: ${g.ready ? 'Ready' : 'Not ready'}{/}{/bold}`);
|
|
781
|
+
for (const line of g.lines) log(`{gray-fg}${line}{/gray-fg}`);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
757
784
|
log('{gray-fg}No configuration required for this agent type.{/gray-fg}');
|
|
758
785
|
return;
|
|
759
786
|
}
|