@phnx-labs/agents-cli 1.20.43 → 1.20.44
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 +10 -0
- package/README.md +4 -3
- package/dist/commands/exec.js +30 -2
- package/dist/commands/hosts.js +5 -4
- package/dist/commands/logs.d.ts +4 -0
- package/dist/commands/logs.js +19 -13
- package/dist/commands/routines.d.ts +6 -0
- package/dist/commands/routines.js +70 -12
- package/dist/commands/sessions.d.ts +6 -5
- package/dist/commands/sessions.js +50 -22
- package/dist/commands/teams.js +43 -5
- package/dist/lib/daemon.js +34 -9
- package/dist/lib/exec.d.ts +8 -0
- package/dist/lib/exec.js +70 -6
- package/dist/lib/hosts/logs.d.ts +14 -5
- package/dist/lib/hosts/logs.js +39 -13
- package/dist/lib/redact.js +1 -0
- package/dist/lib/session/active.d.ts +6 -0
- package/dist/lib/session/active.js +10 -1
- package/dist/lib/shims.d.ts +1 -1
- package/dist/lib/shims.js +17 -3
- package/dist/lib/teams/agents.js +16 -7
- package/dist/lib/tmux/session.d.ts +40 -0
- package/dist/lib/tmux/session.js +92 -0
- package/dist/lib/versions.d.ts +54 -1
- package/dist/lib/versions.js +138 -1
- package/package.json +1 -1
package/dist/lib/versions.d.ts
CHANGED
|
@@ -180,7 +180,9 @@ export declare function setGlobalDefault(agent: AgentId, version: string | undef
|
|
|
180
180
|
/**
|
|
181
181
|
* Install a specific version of an agent.
|
|
182
182
|
*/
|
|
183
|
-
export declare function installVersion(agent: AgentId, version: string, onProgress?: (message: string) => void
|
|
183
|
+
export declare function installVersion(agent: AgentId, version: string, onProgress?: (message: string) => void, opts?: {
|
|
184
|
+
clean?: boolean;
|
|
185
|
+
}): Promise<{
|
|
184
186
|
success: boolean;
|
|
185
187
|
installedVersion: string;
|
|
186
188
|
error?: string;
|
|
@@ -291,6 +293,57 @@ export { compareVersions };
|
|
|
291
293
|
* Get actual version from an installed 'latest' directory.
|
|
292
294
|
*/
|
|
293
295
|
export declare function getInstalledVersion(agent: AgentId, version: string): Promise<string | null>;
|
|
296
|
+
/**
|
|
297
|
+
* True when a probe's combined output/error looks like the runnable binary (or a
|
|
298
|
+
* native sub-binary it execs) is MISSING — the "gutted install" signature. This
|
|
299
|
+
* is the exact class of failure behind the ENOENT crash: an npm package whose JS
|
|
300
|
+
* wrapper landed at node_modules/.bin/<cli> while its native platform binary
|
|
301
|
+
* (shipped via an optional per-arch dependency, e.g. @openai/codex-<platform>)
|
|
302
|
+
* did not — so the wrapper spawns and immediately dies with `spawn … ENOENT`.
|
|
303
|
+
*
|
|
304
|
+
* Deliberately narrow: only the missing-file signature counts. An agent that
|
|
305
|
+
* merely dislikes `--version` (nonzero exit, ordinary error text) or ignores it
|
|
306
|
+
* (times out) must NOT match, so a healthy install is never falsely condemned.
|
|
307
|
+
*/
|
|
308
|
+
export declare function isMissingBinarySignature(output: string): boolean;
|
|
309
|
+
/**
|
|
310
|
+
* Verify a freshly-installed agent can actually LAUNCH — not merely that its JS
|
|
311
|
+
* wrapper exists. getBinaryPath()/isVersionInstalled() only check the wrapper, so
|
|
312
|
+
* a gutted install (wrapper present, native binary missing) reads as healthy,
|
|
313
|
+
* gets pinned as the default, and gets picked to run — then dies with ENOENT the
|
|
314
|
+
* instant it spawns (which, wrapped in tmux, showed up as a silent `[detached]`).
|
|
315
|
+
*
|
|
316
|
+
* We probe `<binary> --version` under the version's isolated HOME so config
|
|
317
|
+
* resolution matches a real launch. Because the ENOENT originates in the child
|
|
318
|
+
* (the wrapper spawns fine, then fails to exec the absent native binary), we
|
|
319
|
+
* inspect the child's OUTPUT, not just whether our spawn succeeded. Only the
|
|
320
|
+
* missing-binary signature (see isMissingBinarySignature) fails the check; a
|
|
321
|
+
* plain nonzero exit or a timeout is treated as healthy so we never false-fail.
|
|
322
|
+
*/
|
|
323
|
+
export declare function verifyInstalledBinaryLaunches(agent: AgentId, version: string): Promise<{
|
|
324
|
+
ok: boolean;
|
|
325
|
+
detail?: string;
|
|
326
|
+
}>;
|
|
327
|
+
/**
|
|
328
|
+
* Launch-path self-heal. Given the concrete version `agents run` is about to
|
|
329
|
+
* spawn, make sure it will actually run — and if not, repair it instead of
|
|
330
|
+
* letting the agent die with a raw `ENOENT` deep inside its own wrapper.
|
|
331
|
+
*
|
|
332
|
+
* Steps, cheapest first:
|
|
333
|
+
* 1. Probe the version (verifyInstalledBinaryLaunches). Healthy → return it.
|
|
334
|
+
* 2. Broken → **clean** reinstall in place (wipes the partial node_modules so
|
|
335
|
+
* npm actually re-fetches the platform binary). Re-probe; good → return it.
|
|
336
|
+
* 3. Still broken → fall back to another INSTALLED version that launches,
|
|
337
|
+
* re-pinning it as the default so the shim path heals too. Return it.
|
|
338
|
+
* 4. Nothing runnable installed → install `latest`, pin it, return it.
|
|
339
|
+
* 5. Give up → return null (caller surfaces a clear error).
|
|
340
|
+
*
|
|
341
|
+
* Gated to npm-package agents: their native binary ships as an optional per-arch
|
|
342
|
+
* dependency whose tarball can extract partially (interrupted/raced install) —
|
|
343
|
+
* the exact failure this repairs. Agents with a global/native binary (grok,
|
|
344
|
+
* droid) have no such tarball and are returned unchanged.
|
|
345
|
+
*/
|
|
346
|
+
export declare function ensureAgentRunnable(agent: AgentId, version: string, log?: (message: string) => void): Promise<string | null>;
|
|
294
347
|
/** Outcome of syncing resources to a version home, keyed by resource type. */
|
|
295
348
|
export interface SyncResult {
|
|
296
349
|
commands: boolean;
|
package/dist/lib/versions.js
CHANGED
|
@@ -1054,7 +1054,7 @@ export function setGlobalDefault(agent, version) {
|
|
|
1054
1054
|
/**
|
|
1055
1055
|
* Install a specific version of an agent.
|
|
1056
1056
|
*/
|
|
1057
|
-
export async function installVersion(agent, version, onProgress) {
|
|
1057
|
+
export async function installVersion(agent, version, onProgress, opts) {
|
|
1058
1058
|
const agentConfig = AGENTS[agent];
|
|
1059
1059
|
// Validate before deriving filesystem paths or npm package specs. The CLI
|
|
1060
1060
|
// parser already enforces this for user input; this guard protects direct
|
|
@@ -1143,6 +1143,14 @@ export async function installVersion(agent, version, onProgress) {
|
|
|
1143
1143
|
}
|
|
1144
1144
|
ensureAgentsDir();
|
|
1145
1145
|
const versionDir = getVersionDir(agent, version);
|
|
1146
|
+
// A `clean` (repair) reinstall wipes a possibly partially-extracted
|
|
1147
|
+
// node_modules first. npm treats a present-but-gutted platform package (its
|
|
1148
|
+
// package.json landed, its vendored native binary did not) as already
|
|
1149
|
+
// installed and would skip re-fetching it — so without this the corrupt
|
|
1150
|
+
// vendor/ survives the reinstall and the ENOENT persists. home/ is preserved.
|
|
1151
|
+
if (opts?.clean && fs.existsSync(versionDir)) {
|
|
1152
|
+
removeInstallArtifacts(versionDir);
|
|
1153
|
+
}
|
|
1146
1154
|
// Create version directory and isolated home
|
|
1147
1155
|
fs.mkdirSync(versionDir, { recursive: true });
|
|
1148
1156
|
fs.mkdirSync(path.join(versionDir, 'home'), { recursive: true });
|
|
@@ -1218,6 +1226,24 @@ export async function installVersion(agent, version, onProgress) {
|
|
|
1218
1226
|
/* non-fatal; the install itself succeeded */
|
|
1219
1227
|
}
|
|
1220
1228
|
}
|
|
1229
|
+
// Integrity gate: confirm the install actually launches, not just that the
|
|
1230
|
+
// JS wrapper landed. A gutted install (wrapper present, native platform
|
|
1231
|
+
// binary missing) otherwise gets silently pinned as the default and crashes
|
|
1232
|
+
// with ENOENT on run. Fail loudly here so `agents add` never records a
|
|
1233
|
+
// broken version as healthy — the caller then won't set it as default.
|
|
1234
|
+
const health = await verifyInstalledBinaryLaunches(agent, installedVersion);
|
|
1235
|
+
if (!health.ok) {
|
|
1236
|
+
if (fs.existsSync(versionDir))
|
|
1237
|
+
removeInstallArtifacts(versionDir);
|
|
1238
|
+
const detail = health.detail ? ` (${health.detail})` : '';
|
|
1239
|
+
emit('version.install', { agent, version: installedVersion, error: `binary failed to launch${detail}` });
|
|
1240
|
+
return {
|
|
1241
|
+
success: false,
|
|
1242
|
+
installedVersion,
|
|
1243
|
+
error: `${agentConfig.name}@${installedVersion} installed but its binary failed to launch${detail}. `
|
|
1244
|
+
+ `The install is incomplete — the platform binary is missing. Re-run: agents add ${agent}@${installedVersion}`,
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1221
1247
|
emit('version.install', { agent, version: installedVersion });
|
|
1222
1248
|
return { success: true, installedVersion };
|
|
1223
1249
|
}
|
|
@@ -1566,6 +1592,117 @@ export async function getInstalledVersion(agent, version) {
|
|
|
1566
1592
|
return version;
|
|
1567
1593
|
}
|
|
1568
1594
|
}
|
|
1595
|
+
/**
|
|
1596
|
+
* True when a probe's combined output/error looks like the runnable binary (or a
|
|
1597
|
+
* native sub-binary it execs) is MISSING — the "gutted install" signature. This
|
|
1598
|
+
* is the exact class of failure behind the ENOENT crash: an npm package whose JS
|
|
1599
|
+
* wrapper landed at node_modules/.bin/<cli> while its native platform binary
|
|
1600
|
+
* (shipped via an optional per-arch dependency, e.g. @openai/codex-<platform>)
|
|
1601
|
+
* did not — so the wrapper spawns and immediately dies with `spawn … ENOENT`.
|
|
1602
|
+
*
|
|
1603
|
+
* Deliberately narrow: only the missing-file signature counts. An agent that
|
|
1604
|
+
* merely dislikes `--version` (nonzero exit, ordinary error text) or ignores it
|
|
1605
|
+
* (times out) must NOT match, so a healthy install is never falsely condemned.
|
|
1606
|
+
*/
|
|
1607
|
+
export function isMissingBinarySignature(output) {
|
|
1608
|
+
return /\bENOENT\b|no such file|cannot find|command not found|is not recognized/i.test(output);
|
|
1609
|
+
}
|
|
1610
|
+
/**
|
|
1611
|
+
* Verify a freshly-installed agent can actually LAUNCH — not merely that its JS
|
|
1612
|
+
* wrapper exists. getBinaryPath()/isVersionInstalled() only check the wrapper, so
|
|
1613
|
+
* a gutted install (wrapper present, native binary missing) reads as healthy,
|
|
1614
|
+
* gets pinned as the default, and gets picked to run — then dies with ENOENT the
|
|
1615
|
+
* instant it spawns (which, wrapped in tmux, showed up as a silent `[detached]`).
|
|
1616
|
+
*
|
|
1617
|
+
* We probe `<binary> --version` under the version's isolated HOME so config
|
|
1618
|
+
* resolution matches a real launch. Because the ENOENT originates in the child
|
|
1619
|
+
* (the wrapper spawns fine, then fails to exec the absent native binary), we
|
|
1620
|
+
* inspect the child's OUTPUT, not just whether our spawn succeeded. Only the
|
|
1621
|
+
* missing-binary signature (see isMissingBinarySignature) fails the check; a
|
|
1622
|
+
* plain nonzero exit or a timeout is treated as healthy so we never false-fail.
|
|
1623
|
+
*/
|
|
1624
|
+
export async function verifyInstalledBinaryLaunches(agent, version) {
|
|
1625
|
+
// Windows: `getBinaryPath` returns the extensionless `.bin/<cli>` (a shell
|
|
1626
|
+
// wrapper), NOT the `.cmd`/`.exe` that actually launches there — `execFile`ing
|
|
1627
|
+
// it would ENOENT on a perfectly healthy install, and the integrity gate would
|
|
1628
|
+
// then WIPE it. The gutted-native-binary failure this guards against is a POSIX
|
|
1629
|
+
// concern in practice; treat win32 as healthy rather than risk destroying a
|
|
1630
|
+
// good install. (isVersionInstalled already validates presence on Windows.)
|
|
1631
|
+
if (process.platform === 'win32')
|
|
1632
|
+
return { ok: true };
|
|
1633
|
+
const binary = getBinaryPath(agent, version);
|
|
1634
|
+
if (!fs.existsSync(binary)) {
|
|
1635
|
+
return { ok: false, detail: `binary not found at ${binary}` };
|
|
1636
|
+
}
|
|
1637
|
+
try {
|
|
1638
|
+
await execFileAsync(binary, ['--version'], {
|
|
1639
|
+
timeout: 15000,
|
|
1640
|
+
env: { ...process.env, HOME: getVersionHomePath(agent, version) },
|
|
1641
|
+
});
|
|
1642
|
+
return { ok: true };
|
|
1643
|
+
}
|
|
1644
|
+
catch (err) {
|
|
1645
|
+
const blob = `${err?.code ?? ''} ${err?.stdout ?? ''} ${err?.stderr ?? ''} ${err?.message ?? ''}`;
|
|
1646
|
+
if (err?.code === 'ENOENT' || isMissingBinarySignature(blob)) {
|
|
1647
|
+
const detail = String(err?.stderr || err?.message || '')
|
|
1648
|
+
.split('\n').map((s) => s.trim()).filter(Boolean)[0];
|
|
1649
|
+
return { ok: false, detail: detail || 'native binary missing (ENOENT)' };
|
|
1650
|
+
}
|
|
1651
|
+
// Launched but exited nonzero without a missing-file signature, or timed out
|
|
1652
|
+
// waiting for input: the binary is present and runnable. Healthy.
|
|
1653
|
+
return { ok: true };
|
|
1654
|
+
}
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Launch-path self-heal. Given the concrete version `agents run` is about to
|
|
1658
|
+
* spawn, make sure it will actually run — and if not, repair it instead of
|
|
1659
|
+
* letting the agent die with a raw `ENOENT` deep inside its own wrapper.
|
|
1660
|
+
*
|
|
1661
|
+
* Steps, cheapest first:
|
|
1662
|
+
* 1. Probe the version (verifyInstalledBinaryLaunches). Healthy → return it.
|
|
1663
|
+
* 2. Broken → **clean** reinstall in place (wipes the partial node_modules so
|
|
1664
|
+
* npm actually re-fetches the platform binary). Re-probe; good → return it.
|
|
1665
|
+
* 3. Still broken → fall back to another INSTALLED version that launches,
|
|
1666
|
+
* re-pinning it as the default so the shim path heals too. Return it.
|
|
1667
|
+
* 4. Nothing runnable installed → install `latest`, pin it, return it.
|
|
1668
|
+
* 5. Give up → return null (caller surfaces a clear error).
|
|
1669
|
+
*
|
|
1670
|
+
* Gated to npm-package agents: their native binary ships as an optional per-arch
|
|
1671
|
+
* dependency whose tarball can extract partially (interrupted/raced install) —
|
|
1672
|
+
* the exact failure this repairs. Agents with a global/native binary (grok,
|
|
1673
|
+
* droid) have no such tarball and are returned unchanged.
|
|
1674
|
+
*/
|
|
1675
|
+
export async function ensureAgentRunnable(agent, version, log) {
|
|
1676
|
+
const cfg = AGENTS[agent];
|
|
1677
|
+
if (!cfg?.npmPackage)
|
|
1678
|
+
return version;
|
|
1679
|
+
if ((await verifyInstalledBinaryLaunches(agent, version)).ok)
|
|
1680
|
+
return version;
|
|
1681
|
+
log?.(`${cfg.name}@${version} is broken (platform binary missing) — repairing…`);
|
|
1682
|
+
const repair = await installVersion(agent, version, undefined, { clean: true });
|
|
1683
|
+
if (repair.success && (await verifyInstalledBinaryLaunches(agent, version)).ok) {
|
|
1684
|
+
log?.(`repaired ${cfg.name}@${version}.`);
|
|
1685
|
+
return version;
|
|
1686
|
+
}
|
|
1687
|
+
// In-place repair failed → adopt another installed version that launches.
|
|
1688
|
+
const others = listInstalledVersions(agent).filter(v => v !== version).sort(compareVersions).reverse();
|
|
1689
|
+
for (const cand of others) {
|
|
1690
|
+
if ((await verifyInstalledBinaryLaunches(agent, cand)).ok) {
|
|
1691
|
+
setGlobalDefault(agent, cand);
|
|
1692
|
+
log?.(`${cfg.name}@${version} could not be repaired — using ${cfg.name}@${cand} instead (now the default).`);
|
|
1693
|
+
return cand;
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1696
|
+
// Nothing runnable installed → last resort: install latest and pin it.
|
|
1697
|
+
log?.(`no runnable ${cfg.name} version installed — installing ${cfg.name}@latest…`);
|
|
1698
|
+
const latest = await installVersion(agent, 'latest', undefined, { clean: true });
|
|
1699
|
+
if (latest.success) {
|
|
1700
|
+
setGlobalDefault(agent, latest.installedVersion);
|
|
1701
|
+
log?.(`installed ${cfg.name}@${latest.installedVersion} and set it as the default.`);
|
|
1702
|
+
return latest.installedVersion;
|
|
1703
|
+
}
|
|
1704
|
+
return null;
|
|
1705
|
+
}
|
|
1569
1706
|
async function getCliVersionFromPath(agent) {
|
|
1570
1707
|
const agentConfig = AGENTS[agent];
|
|
1571
1708
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@phnx-labs/agents-cli",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.44",
|
|
4
4
|
"description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|