@phnx-labs/agents-cli 1.20.46 → 1.20.48
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 +15 -1
- package/README.md +135 -12
- package/dist/commands/secrets-import.d.ts +18 -0
- package/dist/commands/secrets-import.js +74 -0
- package/dist/commands/secrets.js +2 -0
- package/dist/lib/daemon.js +31 -0
- package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
- package/dist/lib/menubar/install-menubar.d.ts +16 -0
- package/dist/lib/menubar/install-menubar.js +43 -1
- package/dist/lib/secrets/fallback.d.ts +48 -0
- package/dist/lib/secrets/fallback.js +48 -0
- package/dist/lib/secrets/index.d.ts +11 -0
- package/dist/lib/secrets/index.js +20 -2
- package/dist/lib/secrets/linux.d.ts +7 -0
- package/dist/lib/secrets/linux.js +113 -5
- package/dist/lib/secrets/windows.d.ts +7 -0
- package/dist/lib/secrets/windows.js +110 -5
- package/dist/lib/self-heal/checks/shims.js +19 -1
- package/dist/lib/session/discover.js +41 -0
- package/dist/lib/shims.d.ts +22 -0
- package/dist/lib/shims.js +79 -0
- package/dist/lib/versions.d.ts +16 -0
- package/dist/lib/versions.js +83 -12
- package/package.json +3 -3
package/dist/lib/shims.js
CHANGED
|
@@ -1610,6 +1610,85 @@ export function isShimCurrent(agent) {
|
|
|
1610
1610
|
const version = readShimSchemaVersion(agent);
|
|
1611
1611
|
return version === SHIM_SCHEMA_VERSION;
|
|
1612
1612
|
}
|
|
1613
|
+
/** Extract the baked `AGENTS_BIN='...'` value from a shim file, or null. */
|
|
1614
|
+
function readAgentsBinFromShim(shimPath) {
|
|
1615
|
+
try {
|
|
1616
|
+
const header = fs.readFileSync(shimPath, 'utf8').split('\n', 12).join('\n');
|
|
1617
|
+
const m = header.match(/^AGENTS_BIN=(?:'([^']*)'|"([^"]*)"|(\S+))/m);
|
|
1618
|
+
return m ? (m[1] ?? m[2] ?? m[3] ?? null) : null;
|
|
1619
|
+
}
|
|
1620
|
+
catch {
|
|
1621
|
+
return null;
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* True when the agent shim's baked `AGENTS_BIN` is fine to keep: it either already
|
|
1626
|
+
* points at the install we'd generate now, OR points at some OTHER install that
|
|
1627
|
+
* still exists on disk (leave it — regenerating could ping-pong two live installs
|
|
1628
|
+
* sharing the shims dir). Returns FALSE only when the shim points at a DIFFERENT,
|
|
1629
|
+
* now-removed install — the exact drift a deleted dev build (`~/.local/agents-cli-dev`),
|
|
1630
|
+
* an old npm-global (`/opt/homebrew`), or a rotated version dir leaves behind. A shim
|
|
1631
|
+
* can pass the schema check (`isShimCurrent`) yet still carry that stale path.
|
|
1632
|
+
*/
|
|
1633
|
+
export function shimPointsAtLiveInstall(agent) {
|
|
1634
|
+
if (!shimExists(agent))
|
|
1635
|
+
return true; // missing shim is handled by ensureShimCurrent
|
|
1636
|
+
const baked = readAgentsBinFromShim(onDiskShimPath(agent));
|
|
1637
|
+
if (!baked)
|
|
1638
|
+
return true;
|
|
1639
|
+
if (baked === getAgentsBinForGeneratedShim())
|
|
1640
|
+
return true; // already the current install
|
|
1641
|
+
return fs.existsSync(baked); // a different install — keep only while it still exists
|
|
1642
|
+
}
|
|
1643
|
+
/** Shim files in the shims dir, excluding the hooks/ subdir and @-versioned aliases. */
|
|
1644
|
+
export function listShimFileNames() {
|
|
1645
|
+
try {
|
|
1646
|
+
return fs
|
|
1647
|
+
.readdirSync(getShimsDir(), { withFileTypes: true })
|
|
1648
|
+
.filter((e) => e.isFile() && !e.name.includes('@'))
|
|
1649
|
+
.map((e) => e.name);
|
|
1650
|
+
}
|
|
1651
|
+
catch {
|
|
1652
|
+
return [];
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Prune a stale, orphaned shim: one that is NOT a managed agent shim and NOT a user
|
|
1657
|
+
* alias, whose baked `AGENTS_BIN` points at an install that no longer exists. These
|
|
1658
|
+
* are legacy `exec "$AGENTS_BIN" <cmd>` command shims (browser/secrets/sessions/…)
|
|
1659
|
+
* left behind by a removed install — the current source never generates them, and
|
|
1660
|
+
* they either die with `exit 127` or shadow the real package bin on PATH. Only
|
|
1661
|
+
* removed when the baked target is gone, so a working shim is never touched.
|
|
1662
|
+
* Returns true if removed.
|
|
1663
|
+
*/
|
|
1664
|
+
export function pruneOrphanedCommandShim(fileName) {
|
|
1665
|
+
// Never touch a shim that corresponds to a real agent — agents-cli manages those.
|
|
1666
|
+
const isAgentCommand = Object.values(AGENTS).some((a) => a.cliCommand === fileName);
|
|
1667
|
+
if (isAgentCommand)
|
|
1668
|
+
return false;
|
|
1669
|
+
const shimPath = path.join(getShimsDir(), fileName);
|
|
1670
|
+
let content;
|
|
1671
|
+
try {
|
|
1672
|
+
content = fs.readFileSync(shimPath, 'utf8');
|
|
1673
|
+
}
|
|
1674
|
+
catch {
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
if (content.includes('# Alias shim:'))
|
|
1678
|
+
return false; // a user `agents alias` — leave it
|
|
1679
|
+
const bin = readAgentsBinFromShim(shimPath);
|
|
1680
|
+
if (!bin)
|
|
1681
|
+
return false; // not an AGENTS_BIN-baked shim
|
|
1682
|
+
if (fs.existsSync(bin))
|
|
1683
|
+
return false; // its install is still alive — leave it
|
|
1684
|
+
try {
|
|
1685
|
+
fs.rmSync(shimPath);
|
|
1686
|
+
return true;
|
|
1687
|
+
}
|
|
1688
|
+
catch {
|
|
1689
|
+
return false;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1613
1692
|
/**
|
|
1614
1693
|
* Regenerate the shim if it's missing or outdated. Returns a status describing
|
|
1615
1694
|
* what happened — callers can surface a one-line notice to the user ("Updated
|
package/dist/lib/versions.d.ts
CHANGED
|
@@ -320,6 +320,21 @@ export declare function isMissingBinarySignature(output: string): boolean;
|
|
|
320
320
|
* missing-binary signature (see isMissingBinarySignature) fails the check; a
|
|
321
321
|
* plain nonzero exit or a timeout is treated as healthy so we never false-fail.
|
|
322
322
|
*/
|
|
323
|
+
/**
|
|
324
|
+
* Compose the spawn spec for a `<binary> --version` launch probe. On Windows the
|
|
325
|
+
* `.cmd` wrapper runs through cmd.exe, so the path is fully quoted into ONE
|
|
326
|
+
* command line and the args array is emptied (composeWin32CommandLine) — the
|
|
327
|
+
* DEP0190-safe pattern the real launch uses. Critically this keeps a spaced
|
|
328
|
+
* Windows profile path (`C:\Users\John Doe\…\claude.cmd`) intact; passing the raw
|
|
329
|
+
* path to a shell would split it at the space and false-fail a HEALTHY install.
|
|
330
|
+
* On POSIX no shell is involved and the binary is exec'd directly. Pure/exported
|
|
331
|
+
* so the quoting is unit-testable without spawning.
|
|
332
|
+
*/
|
|
333
|
+
export declare function probeSpawnSpec(binary: string, isWin: boolean): {
|
|
334
|
+
command: string;
|
|
335
|
+
args: string[];
|
|
336
|
+
shell: boolean;
|
|
337
|
+
};
|
|
323
338
|
export declare function verifyInstalledBinaryLaunches(agent: AgentId, version: string): Promise<{
|
|
324
339
|
ok: boolean;
|
|
325
340
|
detail?: string;
|
|
@@ -344,6 +359,7 @@ export declare function verifyInstalledBinaryLaunches(agent: AgentId, version: s
|
|
|
344
359
|
* droid) have no such tarball and are returned unchanged.
|
|
345
360
|
*/
|
|
346
361
|
export declare function ensureAgentRunnable(agent: AgentId, version: string, log?: (message: string) => void): Promise<string | null>;
|
|
362
|
+
export declare function healBrokenDefaultLaunches(log?: (m: string) => void): Promise<string[]>;
|
|
347
363
|
/** Outcome of syncing resources to a version home, keyed by resource type. */
|
|
348
364
|
export interface SyncResult {
|
|
349
365
|
commands: boolean;
|
package/dist/lib/versions.js
CHANGED
|
@@ -33,7 +33,7 @@ import { discoverPermissionGroups, getActivePermissionPresetName, readPermission
|
|
|
33
33
|
import { parseMcpServerConfig } from './mcp.js';
|
|
34
34
|
import { createVersionedAlias, removeVersionedAlias, getConfigSymlinkVersion, ensureClaudeInsideSymlink } from './shims.js';
|
|
35
35
|
import { importInstallScriptBinary } from './import.js';
|
|
36
|
-
import { IS_WINDOWS } from './platform/index.js';
|
|
36
|
+
import { IS_WINDOWS, composeWin32CommandLine } from './platform/index.js';
|
|
37
37
|
import { pruneVersionHomeHookEntriesFromSettings } from './hooks.js';
|
|
38
38
|
import { supports, explainSkip } from './capabilities.js';
|
|
39
39
|
import { discoverPlugins } from './plugins.js';
|
|
@@ -1621,22 +1621,48 @@ export function isMissingBinarySignature(output) {
|
|
|
1621
1621
|
* missing-binary signature (see isMissingBinarySignature) fails the check; a
|
|
1622
1622
|
* plain nonzero exit or a timeout is treated as healthy so we never false-fail.
|
|
1623
1623
|
*/
|
|
1624
|
+
/**
|
|
1625
|
+
* Compose the spawn spec for a `<binary> --version` launch probe. On Windows the
|
|
1626
|
+
* `.cmd` wrapper runs through cmd.exe, so the path is fully quoted into ONE
|
|
1627
|
+
* command line and the args array is emptied (composeWin32CommandLine) — the
|
|
1628
|
+
* DEP0190-safe pattern the real launch uses. Critically this keeps a spaced
|
|
1629
|
+
* Windows profile path (`C:\Users\John Doe\…\claude.cmd`) intact; passing the raw
|
|
1630
|
+
* path to a shell would split it at the space and false-fail a HEALTHY install.
|
|
1631
|
+
* On POSIX no shell is involved and the binary is exec'd directly. Pure/exported
|
|
1632
|
+
* so the quoting is unit-testable without spawning.
|
|
1633
|
+
*/
|
|
1634
|
+
export function probeSpawnSpec(binary, isWin) {
|
|
1635
|
+
if (isWin)
|
|
1636
|
+
return { command: composeWin32CommandLine(binary, ['--version']), args: [], shell: true };
|
|
1637
|
+
return { command: binary, args: ['--version'], shell: false };
|
|
1638
|
+
}
|
|
1624
1639
|
export async function verifyInstalledBinaryLaunches(agent, version) {
|
|
1625
|
-
//
|
|
1626
|
-
//
|
|
1627
|
-
//
|
|
1628
|
-
//
|
|
1629
|
-
//
|
|
1630
|
-
//
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
const
|
|
1640
|
+
// The real launch target differs by platform, so probe whatever `agents run`
|
|
1641
|
+
// actually execs. On Windows that's the npm `.cmd` wrapper (exec.ts uses
|
|
1642
|
+
// `absPath + '.cmd'`), which chains to the native `.exe`; a gutted install
|
|
1643
|
+
// (renamed/missing `.exe`) makes that wrapper emit "is not recognized" — the
|
|
1644
|
+
// exact win-mini failure a vendor auto-update leaves behind. Probing the
|
|
1645
|
+
// extensionless `.bin/<cli>` instead would ENOENT even on a HEALTHY Windows
|
|
1646
|
+
// install, so we DON'T. On POSIX the `.bin/<cli>` binary is the launch target
|
|
1647
|
+
// and is probed directly.
|
|
1648
|
+
const isWin = process.platform === 'win32';
|
|
1649
|
+
const binary = isWin ? getBinaryPath(agent, version) + '.cmd' : getBinaryPath(agent, version);
|
|
1634
1650
|
if (!fs.existsSync(binary)) {
|
|
1635
|
-
|
|
1651
|
+
// Windows: a missing `.cmd` means a non-npm/global agent (droid.exe) we can't
|
|
1652
|
+
// safely probe — treat as healthy (isVersionInstalled validates presence).
|
|
1653
|
+
// POSIX: a missing launch binary is a genuine gutted install.
|
|
1654
|
+
return isWin ? { ok: true } : { ok: false, detail: `binary not found at ${binary}` };
|
|
1636
1655
|
}
|
|
1637
1656
|
try {
|
|
1638
|
-
|
|
1657
|
+
// On Windows the `.cmd` runs via cmd.exe (shell). Pass a single FULLY-QUOTED
|
|
1658
|
+
// command line + EMPTY args (composeWin32CommandLine) — the same DEP0190-safe
|
|
1659
|
+
// pattern the real launch uses (exec.ts) — so a space in the Windows profile
|
|
1660
|
+
// path (`C:\Users\John Doe\…`) can't split the path and false-fail a healthy
|
|
1661
|
+
// install into a destructive reinstall.
|
|
1662
|
+
const spec = probeSpawnSpec(binary, isWin);
|
|
1663
|
+
await execFileAsync(spec.command, spec.args, {
|
|
1639
1664
|
timeout: 15000,
|
|
1665
|
+
shell: spec.shell,
|
|
1640
1666
|
env: { ...process.env, HOME: getVersionHomePath(agent, version) },
|
|
1641
1667
|
});
|
|
1642
1668
|
return { ok: true };
|
|
@@ -1703,6 +1729,51 @@ export async function ensureAgentRunnable(agent, version, log) {
|
|
|
1703
1729
|
}
|
|
1704
1730
|
return null;
|
|
1705
1731
|
}
|
|
1732
|
+
/**
|
|
1733
|
+
* Proactive launch-health pass for the daemon. Probe the DEFAULT version of
|
|
1734
|
+
* every npm-package agent and repair any that won't launch (via
|
|
1735
|
+
* ensureAgentRunnable), so a gutted install is healed BEFORE the user's next
|
|
1736
|
+
* `agents run` hits a raw ENOENT — the run-time heal (ensureAgentRunnable) only
|
|
1737
|
+
* fires once a run is already starting; this catches it in the background.
|
|
1738
|
+
*
|
|
1739
|
+
* Returns a label (`agent@broken→healed`) for each version actually repaired, so
|
|
1740
|
+
* the daemon can log/notify. A version that already launches costs one cheap
|
|
1741
|
+
* `--version` probe and is left untouched.
|
|
1742
|
+
*/
|
|
1743
|
+
const failedRepairAt = new Map();
|
|
1744
|
+
const REPAIR_COOLDOWN_MS = 24 * 60 * 60_000;
|
|
1745
|
+
export async function healBrokenDefaultLaunches(log) {
|
|
1746
|
+
const repaired = [];
|
|
1747
|
+
for (const agent of Object.keys(AGENTS)) {
|
|
1748
|
+
if (!AGENTS[agent].npmPackage)
|
|
1749
|
+
continue; // native/global agents have no gutted-tarball failure mode
|
|
1750
|
+
const version = getGlobalDefault(agent);
|
|
1751
|
+
if (!version)
|
|
1752
|
+
continue;
|
|
1753
|
+
if ((await verifyInstalledBinaryLaunches(agent, version)).ok)
|
|
1754
|
+
continue;
|
|
1755
|
+
// Backoff: a version whose repair just failed (offline, npm 404, an arch the
|
|
1756
|
+
// registry can't serve) must NOT re-trigger a full clean-reinstall +
|
|
1757
|
+
// install-latest on every 6h pass. Skip it for a day; a daemon restart clears
|
|
1758
|
+
// the memo, giving a fresh attempt.
|
|
1759
|
+
const key = `${agent}@${version}`;
|
|
1760
|
+
const last = failedRepairAt.get(key);
|
|
1761
|
+
if (last !== undefined && Date.now() - last < REPAIR_COOLDOWN_MS) {
|
|
1762
|
+
log?.(`${AGENTS[agent].name}@${version} still won't launch — repair attempted recently, skipping until cooldown elapses.`);
|
|
1763
|
+
continue;
|
|
1764
|
+
}
|
|
1765
|
+
log?.(`${AGENTS[agent].name}@${version} won't launch — repairing…`);
|
|
1766
|
+
const healed = await ensureAgentRunnable(agent, version, log);
|
|
1767
|
+
if (healed) {
|
|
1768
|
+
failedRepairAt.delete(key);
|
|
1769
|
+
repaired.push(`${agent}@${version}${healed === version ? '' : `→${healed}`}`);
|
|
1770
|
+
}
|
|
1771
|
+
else {
|
|
1772
|
+
failedRepairAt.set(key, Date.now());
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
return repaired;
|
|
1776
|
+
}
|
|
1706
1777
|
async function getCliVersionFromPath(agent) {
|
|
1707
1778
|
const agentConfig = AGENTS[agent];
|
|
1708
1779
|
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.48",
|
|
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",
|
|
@@ -44,10 +44,10 @@
|
|
|
44
44
|
"url": "https://github.com/phnx-labs/agents-cli/issues"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
|
-
"build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([
|
|
47
|
+
"build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ -d 'bin/Agents CLI.app' ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true) && ([ -d 'bin/MenubarHelper.app' ] && mkdir -p 'dist/lib/menubar' && cp -R 'bin/MenubarHelper.app' 'dist/lib/menubar/MenubarHelper.app' || true)",
|
|
48
48
|
"build:bin": "scripts/build-bin.sh",
|
|
49
49
|
"prepare": "npm run build",
|
|
50
|
-
"prepack": "scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh",
|
|
50
|
+
"prepack": "cp ../../README.md README.md && scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh",
|
|
51
51
|
"postinstall": "node scripts/postinstall.js",
|
|
52
52
|
"dev": "tsx src/index.ts",
|
|
53
53
|
"start": "node dist/index.js",
|