@phnx-labs/agents-cli 1.20.26 → 1.20.28

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.
Files changed (95) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/dist/commands/doctor.d.ts +5 -2
  3. package/dist/commands/doctor.js +182 -30
  4. package/dist/commands/exec.d.ts +1 -1
  5. package/dist/commands/exec.js +177 -6
  6. package/dist/commands/hosts.d.ts +11 -0
  7. package/dist/commands/hosts.js +229 -0
  8. package/dist/commands/repo.d.ts +29 -0
  9. package/dist/commands/repo.js +174 -38
  10. package/dist/commands/secrets.d.ts +2 -7
  11. package/dist/commands/secrets.js +15 -23
  12. package/dist/commands/sessions.d.ts +2 -0
  13. package/dist/commands/sessions.js +27 -25
  14. package/dist/commands/sync.d.ts +2 -0
  15. package/dist/commands/sync.js +22 -5
  16. package/dist/commands/view.js +27 -11
  17. package/dist/index.js +4 -13
  18. package/dist/lib/agent-spec.d.ts +36 -0
  19. package/dist/lib/agent-spec.js +157 -0
  20. package/dist/lib/agents.d.ts +1 -0
  21. package/dist/lib/agents.js +45 -4
  22. package/dist/lib/browser/drivers/ssh.d.ts +47 -2
  23. package/dist/lib/browser/drivers/ssh.js +113 -24
  24. package/dist/lib/browser/profiles.js +28 -1
  25. package/dist/lib/browser/runtime-state.js +28 -8
  26. package/dist/lib/browser/types.d.ts +10 -1
  27. package/dist/lib/cli-resources.js +10 -1
  28. package/dist/lib/daemon.js +32 -0
  29. package/dist/lib/doctor-diff.d.ts +19 -0
  30. package/dist/lib/doctor-diff.js +107 -15
  31. package/dist/lib/exec.d.ts +27 -0
  32. package/dist/lib/exec.js +62 -19
  33. package/dist/lib/heal.d.ts +107 -0
  34. package/dist/lib/heal.js +279 -0
  35. package/dist/lib/hooks.d.ts +17 -0
  36. package/dist/lib/hooks.js +127 -3
  37. package/dist/lib/hosts/dispatch.d.ts +26 -0
  38. package/dist/lib/hosts/dispatch.js +71 -0
  39. package/dist/lib/hosts/progress.d.ts +21 -0
  40. package/dist/lib/hosts/progress.js +49 -0
  41. package/dist/lib/hosts/providers/local.d.ts +17 -0
  42. package/dist/lib/hosts/providers/local.js +81 -0
  43. package/dist/lib/hosts/ready.d.ts +37 -0
  44. package/dist/lib/hosts/ready.js +88 -0
  45. package/dist/lib/hosts/registry.d.ts +22 -0
  46. package/dist/lib/hosts/registry.js +65 -0
  47. package/dist/lib/hosts/ssh-config.d.ts +37 -0
  48. package/dist/lib/hosts/ssh-config.js +157 -0
  49. package/dist/lib/hosts/tasks.d.ts +32 -0
  50. package/dist/lib/hosts/tasks.js +58 -0
  51. package/dist/lib/hosts/types.d.ts +51 -0
  52. package/dist/lib/hosts/types.js +21 -0
  53. package/dist/lib/loop.d.ts +9 -0
  54. package/dist/lib/loop.js +13 -1
  55. package/dist/lib/mcp.js +12 -3
  56. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  57. package/dist/lib/migrate.js +9 -5
  58. package/dist/lib/platform/exec.d.ts +10 -0
  59. package/dist/lib/platform/exec.js +17 -0
  60. package/dist/lib/platform/index.d.ts +1 -0
  61. package/dist/lib/platform/index.js +1 -0
  62. package/dist/lib/platform/links.d.ts +15 -0
  63. package/dist/lib/platform/links.js +42 -0
  64. package/dist/lib/platform/paths.d.ts +18 -0
  65. package/dist/lib/platform/paths.js +22 -0
  66. package/dist/lib/platform/posixpath.d.ts +28 -0
  67. package/dist/lib/platform/posixpath.js +153 -0
  68. package/dist/lib/plugin-marketplace.d.ts +18 -0
  69. package/dist/lib/plugin-marketplace.js +67 -1
  70. package/dist/lib/plugins.d.ts +33 -1
  71. package/dist/lib/plugins.js +56 -11
  72. package/dist/lib/project-launch.js +6 -3
  73. package/dist/lib/sandbox.js +5 -2
  74. package/dist/lib/self-update.js +7 -2
  75. package/dist/lib/session/db.d.ts +23 -0
  76. package/dist/lib/session/db.js +76 -1
  77. package/dist/lib/session/discover.d.ts +26 -0
  78. package/dist/lib/session/discover.js +75 -4
  79. package/dist/lib/session/relative-time.d.ts +7 -0
  80. package/dist/lib/session/relative-time.js +28 -0
  81. package/dist/lib/session/remote.d.ts +61 -0
  82. package/dist/lib/session/remote.js +221 -0
  83. package/dist/lib/ssh-exec.d.ts +45 -0
  84. package/dist/lib/ssh-exec.js +61 -0
  85. package/dist/lib/staleness/detectors/commands.js +7 -6
  86. package/dist/lib/staleness/writers/commands.js +7 -12
  87. package/dist/lib/startup/command-registry.d.ts +1 -0
  88. package/dist/lib/startup/command-registry.js +2 -0
  89. package/dist/lib/startup/dev-build.d.ts +22 -0
  90. package/dist/lib/startup/dev-build.js +41 -0
  91. package/dist/lib/types.d.ts +28 -0
  92. package/dist/lib/versions.d.ts +9 -3
  93. package/dist/lib/versions.js +43 -7
  94. package/package.json +3 -3
  95. package/scripts/postinstall.js +62 -0
@@ -898,14 +898,36 @@ export async function isOldestInstalled(agent) {
898
898
  }
899
899
  return { installed: isVersionInstalled(agent, oldestVersion), version: oldestVersion };
900
900
  }
901
+ // Per-process cache for listInstalledVersions. The agent's versions dir mtime
902
+ // changes whenever a version dir is added or removed (install/remove), so a
903
+ // stamp match means the installed set is unchanged and we skip the readdir +
904
+ // N binary stats. Mirrors the readMeta() cache in state.ts. Hot path:
905
+ // resolveAgentTargets and every enumerate-style consumer hit this.
906
+ const installedVersionsCache = new Map();
907
+ /** Drop the installed-versions cache (call after install/remove mutations). */
908
+ export function invalidateInstalledVersionsCache(agent) {
909
+ if (agent)
910
+ installedVersionsCache.delete(agent);
911
+ else
912
+ installedVersionsCache.clear();
913
+ }
901
914
  /**
902
- * List all installed versions for an agent.
915
+ * List all installed versions for an agent (cached by versions-dir mtime).
903
916
  */
904
917
  export function listInstalledVersions(agent) {
905
918
  const agentVersionsDir = path.join(getVersionsDir(), agent);
906
- if (!fs.existsSync(agentVersionsDir)) {
919
+ let stamp;
920
+ try {
921
+ stamp = fs.statSync(agentVersionsDir).mtimeMs;
922
+ }
923
+ catch {
924
+ installedVersionsCache.set(agent, { stamp: 0, versions: [] });
907
925
  return [];
908
926
  }
927
+ const cached = installedVersionsCache.get(agent);
928
+ if (cached && cached.stamp === stamp) {
929
+ return cached.versions;
930
+ }
909
931
  const entries = fs.readdirSync(agentVersionsDir, { withFileTypes: true });
910
932
  const versions = [];
911
933
  for (const entry of entries) {
@@ -916,7 +938,9 @@ export function listInstalledVersions(agent) {
916
938
  }
917
939
  }
918
940
  }
919
- return versions.sort(compareVersions);
941
+ versions.sort(compareVersions);
942
+ installedVersionsCache.set(agent, { stamp, versions });
943
+ return versions;
920
944
  }
921
945
  /**
922
946
  * List every version directory for an agent, including ones missing the
@@ -1339,19 +1363,23 @@ export function resolveVersion(agent, projectPath) {
1339
1363
  /**
1340
1364
  * Normalize a user-supplied @version token across CLI subcommands.
1341
1365
  *
1342
- * undefined / "" / "default" -> undefined (caller falls back to project pin or global default)
1366
+ * undefined / "" / "default" / "pinned" -> undefined (caller falls back to project pin or global default)
1367
+ * "any" -> undefined (caller imposes no version constraint — e.g. resume across any version)
1343
1368
  * "latest" -> highest installed version (process.exit if none installed)
1344
1369
  * "oldest" -> lowest installed version (process.exit if none installed)
1345
1370
  * "x.y.z" (installed) -> "x.y.z"
1346
1371
  * "x.y.z" (not installed) -> process.exit with installed-list hint
1347
1372
  *
1373
+ * `pinned` is a synonym for `default`: both name the project pin / global
1374
+ * default, which the caller resolves.
1375
+ *
1348
1376
  * Use this anywhere the user can type `agents <cmd> claude@<token>` to keep the
1349
1377
  * vocabulary consistent. Subcommands with different semantics for `latest`
1350
1378
  * (install/remove/use, where `latest` means npm-latest) keep their existing
1351
1379
  * parsing.
1352
1380
  */
1353
1381
  export function resolveVersionAlias(agent, raw) {
1354
- if (!raw || raw === 'default')
1382
+ if (!raw || raw === 'default' || raw === 'pinned' || raw === 'any')
1355
1383
  return undefined;
1356
1384
  if (raw === 'latest' || raw === 'oldest') {
1357
1385
  const installed = listInstalledVersions(agent);
@@ -1375,12 +1403,12 @@ export function resolveVersionAlias(agent, raw) {
1375
1403
  }
1376
1404
  /**
1377
1405
  * Loose variant of resolveVersionAlias for record-filter contexts (sessions,
1378
- * team history). Same `default`/`latest`/`oldest` semantics, but explicit
1406
+ * team history). Same `default`/`pinned`/`latest`/`oldest` semantics, but explicit
1379
1407
  * versions pass through unchanged so historical records of uninstalled versions
1380
1408
  * remain queryable.
1381
1409
  */
1382
1410
  export function resolveVersionAliasLoose(agent, raw) {
1383
- if (!raw || raw === 'default')
1411
+ if (!raw || raw === 'default' || raw === 'pinned' || raw === 'any')
1384
1412
  return undefined;
1385
1413
  if (raw === 'latest' || raw === 'oldest') {
1386
1414
  const installed = listInstalledVersions(agent);
@@ -1831,7 +1859,15 @@ export function syncResourcesToVersion(agent, version, selection, options = {})
1831
1859
  // dot-dirs to keep plugin-managed subtrees (.plugins/, .promptcuts) intact.
1832
1860
  const skillsTargetSweep = path.join(agentDir, 'skills');
1833
1861
  if (!userPassedSelection && fs.existsSync(skillsTargetSweep) && !fs.lstatSync(skillsTargetSweep).isSymbolicLink()) {
1862
+ // Trust real skills AND command-skills: when commandsAsSkills, the
1863
+ // commands writer (above) materialized each command as a skill dir under
1864
+ // skills/. Those names are not in skillsToSync, so without this they'd be
1865
+ // swept as orphans — silently deleting every converted command (e.g.
1866
+ // /recap on kimi/grok).
1834
1867
  const trustedSkills = new Set(skillsToSync);
1868
+ if (commandsAsSkills)
1869
+ for (const cmd of commandsToSync)
1870
+ trustedSkills.add(cmd);
1835
1871
  for (const entry of fs.readdirSync(skillsTargetSweep, { withFileTypes: true })) {
1836
1872
  if (!entry.isDirectory() || entry.name.startsWith('.'))
1837
1873
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.26",
3
+ "version": "1.20.28",
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",
@@ -92,10 +92,10 @@
92
92
  "diff": "9.0.0",
93
93
  "marked": "15.0.12",
94
94
  "marked-terminal": "7.3.0",
95
- "ora": "9.4.0",
95
+ "ora": "9.4.1",
96
96
  "proper-lockfile": "4.1.2",
97
97
  "simple-git": "3.36.0",
98
- "smol-toml": "1.6.1",
98
+ "smol-toml": "1.7.0",
99
99
  "yaml": "2.9.0"
100
100
  },
101
101
  "devDependencies": {
@@ -239,6 +239,10 @@ To enable version-aware shims, add this to your shell config:
239
239
  console.log(` Installed shorthands: ${written.join(', ')}`);
240
240
  }
241
241
 
242
+ if (process.platform !== 'win32') {
243
+ await ensureAgentsResolvablePosix();
244
+ }
245
+
242
246
  await healLongRunningProcesses();
243
247
 
244
248
  const version = getVersion();
@@ -252,6 +256,64 @@ To enable version-aware shims, add this to your shell config:
252
256
  }
253
257
  }
254
258
 
259
+ /**
260
+ * Make the `agents` command resolvable in a *login* shell on POSIX.
261
+ *
262
+ * `agents`/`ag` reach PATH only through npm's bin symlink in the npm global-bin
263
+ * dir. Under nvm (and other per-user node prefixes) that dir is missing from a
264
+ * non-interactive login shell's PATH, so `bash -lc 'agents …'` fails with
265
+ * command-not-found — which breaks `agents secrets export --host` (it runs
266
+ * `bash -lc 'agents secrets import …'` on the remote) and the routines daemon
267
+ * (src/lib/daemon.ts falls back to bare `agents`). This is the POSIX symmetric
268
+ * counterpart of the Windows branch in main() that registers npm's global-bin
269
+ * dir on the user PATH.
270
+ *
271
+ * Self-heal, not a prompt: it fires ONLY when `agents` is otherwise
272
+ * unresolvable — the genuinely-broken state — so it acts decisively, exactly
273
+ * like the Windows PATH registration. The symlink never clobbers a dev build
274
+ * (scripts/install.sh) or any real file. Skipped in CI (ephemeral homes) and
275
+ * when AGENTS_NO_HEAL=1.
276
+ */
277
+ async function ensureAgentsResolvablePosix() {
278
+ if (process.env.CI || process.env.AGENTS_NO_HEAL === '1') return;
279
+ try {
280
+ const { localBinDir, ensureLocalBinSymlink, loginShellResolves, dirOnLoginPath } =
281
+ await import('../dist/lib/platform/posixpath.js');
282
+
283
+ // macOS/homebrew, system-node Linux, and dev-build users already resolve it.
284
+ if (loginShellResolves('agents')) return;
285
+
286
+ const binDir = localBinDir();
287
+ const results = ['agents', 'ag'].map((name) => ensureLocalBinSymlink(name, AGENTS_BIN, binDir));
288
+ if (!results.some((r) => r.created)) return; // nothing we could safely link
289
+
290
+ if (dirOnLoginPath(binDir)) {
291
+ console.log(`\n Linked 'agents' into ${binDir} (already on the login PATH) so 'bash -lc agents' resolves.`);
292
+ return;
293
+ }
294
+
295
+ // ~/.local/bin isn't on the *bash* login PATH yet. The consumers run
296
+ // `bash -lc`, so add it to the file a bash login shell reads (~/.bash_profile
297
+ // when present, else ~/.profile) — not the interactive $SHELL rc, which for a
298
+ // zsh user (.zshrc) bash would never source.
299
+ const bashRc = fs.existsSync(path.join(HOME, '.bash_profile'))
300
+ ? path.join(HOME, '.bash_profile')
301
+ : path.join(HOME, '.profile');
302
+ const marker = '# agents-cli: ensure ~/.local/bin on PATH (so the agents command resolves)';
303
+ let already = false;
304
+ try {
305
+ already = fs.existsSync(bashRc) && fs.readFileSync(bashRc, 'utf-8').includes(marker);
306
+ } catch { /* unreadable rc — fall through and append */ }
307
+ if (!already) {
308
+ fs.appendFileSync(bashRc, `\n${marker}\nexport PATH="${binDir}:$PATH"\n`);
309
+ }
310
+ console.log(`\n Linked 'agents' into ${binDir} and added it to PATH in ${path.basename(bashRc)}.`);
311
+ console.log(` Restart your shell (or run: source ~/${path.basename(bashRc)}) to pick it up.`);
312
+ } catch {
313
+ /* best-effort: a failure here must never break the install */
314
+ }
315
+ }
316
+
255
317
  /**
256
318
  * Self-heal long-running processes onto the just-installed code (macOS).
257
319
  *