@phnx-labs/agents-cli 1.20.71 → 1.20.73

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 (238) hide show
  1. package/CHANGELOG.md +325 -1
  2. package/README.md +40 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +12 -0
  5. package/dist/commands/activity.js +68 -0
  6. package/dist/commands/apply.d.ts +13 -0
  7. package/dist/commands/apply.js +28 -3
  8. package/dist/commands/browser.js +1 -1
  9. package/dist/commands/cloud.js +21 -11
  10. package/dist/commands/commands.js +2 -0
  11. package/dist/commands/doctor.js +8 -4
  12. package/dist/commands/events.d.ts +11 -10
  13. package/dist/commands/events.js +30 -21
  14. package/dist/commands/exec.d.ts +34 -0
  15. package/dist/commands/exec.js +337 -39
  16. package/dist/commands/feed.d.ts +13 -0
  17. package/dist/commands/feed.js +130 -29
  18. package/dist/commands/hq.d.ts +2 -0
  19. package/dist/commands/hq.js +58 -0
  20. package/dist/commands/lease.d.ts +19 -0
  21. package/dist/commands/lease.js +138 -11
  22. package/dist/commands/login.d.ts +2 -0
  23. package/dist/commands/login.js +123 -0
  24. package/dist/commands/logs.js +39 -10
  25. package/dist/commands/mcp.js +2 -0
  26. package/dist/commands/mine.d.ts +27 -0
  27. package/dist/commands/mine.js +207 -0
  28. package/dist/commands/monitors.js +79 -49
  29. package/dist/commands/plugins.js +1 -0
  30. package/dist/commands/profiles.js +158 -0
  31. package/dist/commands/repo.js +2 -60
  32. package/dist/commands/resource-view.d.ts +2 -0
  33. package/dist/commands/resource-view.js +8 -0
  34. package/dist/commands/resources.d.ts +5 -0
  35. package/dist/commands/resources.js +95 -0
  36. package/dist/commands/routines.d.ts +2 -0
  37. package/dist/commands/routines.js +139 -37
  38. package/dist/commands/secrets.js +234 -47
  39. package/dist/commands/send.d.ts +15 -0
  40. package/dist/commands/send.js +62 -0
  41. package/dist/commands/sessions-tail.d.ts +1 -0
  42. package/dist/commands/sessions-tail.js +20 -9
  43. package/dist/commands/sessions.d.ts +6 -0
  44. package/dist/commands/sessions.js +26 -3
  45. package/dist/commands/setup-mine.d.ts +18 -0
  46. package/dist/commands/setup-mine.js +106 -0
  47. package/dist/commands/setup-share.js +2 -2
  48. package/dist/commands/setup.js +3 -1
  49. package/dist/commands/share.d.ts +8 -1
  50. package/dist/commands/share.js +122 -37
  51. package/dist/commands/skills.js +2 -0
  52. package/dist/commands/ssh.d.ts +9 -0
  53. package/dist/commands/ssh.js +196 -26
  54. package/dist/commands/status.js +8 -2
  55. package/dist/commands/subagents.js +3 -1
  56. package/dist/commands/sync.js +1 -1
  57. package/dist/commands/teams.js +11 -2
  58. package/dist/commands/uninstall.d.ts +11 -0
  59. package/dist/commands/uninstall.js +158 -0
  60. package/dist/commands/utils.d.ts +29 -0
  61. package/dist/commands/utils.js +24 -0
  62. package/dist/commands/versions.js +120 -11
  63. package/dist/index.js +83 -12
  64. package/dist/lib/activity.d.ts +124 -0
  65. package/dist/lib/activity.js +542 -0
  66. package/dist/lib/agents.js +16 -5
  67. package/dist/lib/artifact-actions.d.ts +1 -1
  68. package/dist/lib/artifact-actions.js +1 -1
  69. package/dist/lib/ask-classifier.d.ts +2 -1
  70. package/dist/lib/ask-classifier.js +3 -3
  71. package/dist/lib/auth-health.d.ts +32 -4
  72. package/dist/lib/auth-health.js +40 -1
  73. package/dist/lib/auto-pull.js +8 -1
  74. package/dist/lib/brand.d.ts +40 -0
  75. package/dist/lib/brand.js +122 -0
  76. package/dist/lib/browser/drivers/ssh.js +4 -7
  77. package/dist/lib/browser/service.js +6 -8
  78. package/dist/lib/channels/providers/index.d.ts +2 -0
  79. package/dist/lib/channels/providers/index.js +20 -0
  80. package/dist/lib/channels/providers/mailbox.d.ts +2 -0
  81. package/dist/lib/channels/providers/mailbox.js +26 -0
  82. package/dist/lib/channels/providers/openclaw-telegram.d.ts +2 -0
  83. package/dist/lib/channels/providers/openclaw-telegram.js +34 -0
  84. package/dist/lib/channels/providers/rush.d.ts +6 -0
  85. package/dist/lib/channels/providers/rush.js +55 -0
  86. package/dist/lib/channels/registry.d.ts +42 -0
  87. package/dist/lib/channels/registry.js +20 -0
  88. package/dist/lib/channels/resolve.d.ts +11 -0
  89. package/dist/lib/channels/resolve.js +12 -0
  90. package/dist/lib/cli-resources.d.ts +18 -0
  91. package/dist/lib/cli-resources.js +53 -4
  92. package/dist/lib/cloud/codex.js +6 -3
  93. package/dist/lib/cloud/factory.d.ts +1 -0
  94. package/dist/lib/cloud/factory.js +10 -4
  95. package/dist/lib/cloud/rush.d.ts +3 -1
  96. package/dist/lib/cloud/rush.js +6 -1
  97. package/dist/lib/cloud/types.d.ts +2 -0
  98. package/dist/lib/codex-home.d.ts +35 -0
  99. package/dist/lib/codex-home.js +136 -0
  100. package/dist/lib/crabbox/cli.d.ts +44 -0
  101. package/dist/lib/crabbox/cli.js +120 -20
  102. package/dist/lib/crabbox/lease.d.ts +52 -1
  103. package/dist/lib/crabbox/lease.js +117 -16
  104. package/dist/lib/crabbox/progress.d.ts +31 -0
  105. package/dist/lib/crabbox/progress.js +76 -0
  106. package/dist/lib/crabbox/runtimes.d.ts +4 -0
  107. package/dist/lib/crabbox/runtimes.js +33 -8
  108. package/dist/lib/crabbox/setup-copy.d.ts +87 -0
  109. package/dist/lib/crabbox/setup-copy.js +127 -0
  110. package/dist/lib/daemon.d.ts +14 -5
  111. package/dist/lib/daemon.js +52 -9
  112. package/dist/lib/drive-sync.js +6 -3
  113. package/dist/lib/event-stream.d.ts +36 -0
  114. package/dist/lib/event-stream.js +68 -0
  115. package/dist/lib/events.d.ts +1 -1
  116. package/dist/lib/exec.d.ts +11 -0
  117. package/dist/lib/exec.js +23 -2
  118. package/dist/lib/feed-policy.d.ts +1 -1
  119. package/dist/lib/feed-policy.js +14 -9
  120. package/dist/lib/feed-ranking.d.ts +32 -0
  121. package/dist/lib/feed-ranking.js +224 -0
  122. package/dist/lib/feed.d.ts +35 -2
  123. package/dist/lib/feed.js +66 -3
  124. package/dist/lib/fleet/auth-sync.d.ts +53 -0
  125. package/dist/lib/fleet/auth-sync.js +92 -0
  126. package/dist/lib/fleet/remote-login.d.ts +187 -0
  127. package/dist/lib/fleet/remote-login.js +556 -0
  128. package/dist/lib/format.d.ts +30 -3
  129. package/dist/lib/format.js +34 -5
  130. package/dist/lib/fs-atomic.d.ts +7 -2
  131. package/dist/lib/fs-atomic.js +8 -12
  132. package/dist/lib/git.d.ts +16 -0
  133. package/dist/lib/git.js +79 -2
  134. package/dist/lib/heal.js +11 -3
  135. package/dist/lib/hosts/logs.d.ts +12 -0
  136. package/dist/lib/hosts/logs.js +18 -0
  137. package/dist/lib/hosts/progress.d.ts +28 -10
  138. package/dist/lib/hosts/progress.js +79 -22
  139. package/dist/lib/hosts/remote-cmd.js +4 -0
  140. package/dist/lib/hq/floor.d.ts +87 -0
  141. package/dist/lib/hq/floor.js +226 -0
  142. package/dist/lib/menubar/install-menubar.js +14 -20
  143. package/dist/lib/notify.d.ts +1 -0
  144. package/dist/lib/notify.js +3 -3
  145. package/dist/lib/open-url.d.ts +2 -0
  146. package/dist/lib/open-url.js +19 -0
  147. package/dist/lib/openclaw-keychain.d.ts +56 -0
  148. package/dist/lib/openclaw-keychain.js +236 -0
  149. package/dist/lib/overdue.js +10 -0
  150. package/dist/lib/permissions.d.ts +44 -1
  151. package/dist/lib/permissions.js +284 -7
  152. package/dist/lib/project-launch.d.ts +6 -12
  153. package/dist/lib/project-launch.js +13 -228
  154. package/dist/lib/project-resources.d.ts +7 -0
  155. package/dist/lib/project-resources.js +291 -0
  156. package/dist/lib/pty-client.d.ts +27 -0
  157. package/dist/lib/pty-client.js +136 -10
  158. package/dist/lib/refresh.js +14 -10
  159. package/dist/lib/resource-profiles.d.ts +26 -0
  160. package/dist/lib/resource-profiles.js +157 -0
  161. package/dist/lib/resources/permissions.js +7 -1
  162. package/dist/lib/resources/types.d.ts +1 -1
  163. package/dist/lib/resources.d.ts +1 -1
  164. package/dist/lib/resources.js +32 -3
  165. package/dist/lib/routines.d.ts +11 -0
  166. package/dist/lib/routines.js +56 -15
  167. package/dist/lib/runner.d.ts +1 -0
  168. package/dist/lib/runner.js +66 -5
  169. package/dist/lib/secrets/bundles.d.ts +16 -3
  170. package/dist/lib/secrets/bundles.js +206 -37
  171. package/dist/lib/secrets/icloud-import.d.ts +2 -2
  172. package/dist/lib/secrets/icloud-import.js +9 -6
  173. package/dist/lib/secrets/mcp.js +1 -1
  174. package/dist/lib/secrets/vault-age-helper.d.ts +1 -0
  175. package/dist/lib/secrets/vault-age-helper.js +34 -0
  176. package/dist/lib/secrets/vault.d.ts +49 -0
  177. package/dist/lib/secrets/vault.js +397 -0
  178. package/dist/lib/self-heal/checks/path.js +3 -1
  179. package/dist/lib/self-heal/checks/shadowing.js +10 -2
  180. package/dist/lib/self-heal/checks/shims.js +19 -11
  181. package/dist/lib/session/cloud.d.ts +2 -2
  182. package/dist/lib/session/cloud.js +2 -2
  183. package/dist/lib/session/db.d.ts +4 -0
  184. package/dist/lib/session/db.js +44 -7
  185. package/dist/lib/session/discover.d.ts +2 -0
  186. package/dist/lib/session/discover.js +114 -3
  187. package/dist/lib/session/stream-render.d.ts +3 -0
  188. package/dist/lib/session/stream-render.js +130 -0
  189. package/dist/lib/session/types.d.ts +6 -0
  190. package/dist/lib/share/analytics.d.ts +13 -0
  191. package/dist/lib/share/analytics.js +45 -0
  192. package/dist/lib/share/config.d.ts +19 -5
  193. package/dist/lib/share/config.js +44 -8
  194. package/dist/lib/share/provision.d.ts +58 -6
  195. package/dist/lib/share/provision.js +97 -22
  196. package/dist/lib/share/publish.d.ts +36 -13
  197. package/dist/lib/share/publish.js +55 -8
  198. package/dist/lib/share/worker-template.js +83 -17
  199. package/dist/lib/shims.d.ts +36 -0
  200. package/dist/lib/shims.js +120 -16
  201. package/dist/lib/ssh-exec.d.ts +14 -0
  202. package/dist/lib/ssh-exec.js +57 -0
  203. package/dist/lib/staleness/detectors/hooks.js +25 -1
  204. package/dist/lib/staleness/detectors/permissions.js +66 -0
  205. package/dist/lib/staleness/detectors/workflows.js +20 -0
  206. package/dist/lib/staleness/writers/hooks.js +58 -4
  207. package/dist/lib/staleness/writers/permissions.js +2 -2
  208. package/dist/lib/staleness/writers/skills.js +1 -1
  209. package/dist/lib/staleness/writers/sources.d.ts +10 -1
  210. package/dist/lib/staleness/writers/sources.js +68 -1
  211. package/dist/lib/star-nudge.d.ts +45 -0
  212. package/dist/lib/star-nudge.js +91 -0
  213. package/dist/lib/startup/command-registry.d.ts +7 -1
  214. package/dist/lib/startup/command-registry.js +19 -3
  215. package/dist/lib/state.d.ts +2 -0
  216. package/dist/lib/state.js +3 -0
  217. package/dist/lib/subagents-registry.d.ts +2 -0
  218. package/dist/lib/subagents-registry.js +3 -0
  219. package/dist/lib/teams/parsers.js +214 -6
  220. package/dist/lib/teams/supervisor.d.ts +7 -0
  221. package/dist/lib/teams/supervisor.js +2 -1
  222. package/dist/lib/template.d.ts +1 -1
  223. package/dist/lib/template.js +1 -1
  224. package/dist/lib/triggers/webhook.js +36 -3
  225. package/dist/lib/types.d.ts +73 -0
  226. package/dist/lib/uninstall.d.ts +84 -0
  227. package/dist/lib/uninstall.js +347 -0
  228. package/dist/lib/usage.d.ts +13 -1
  229. package/dist/lib/usage.js +32 -14
  230. package/dist/lib/version.d.ts +40 -0
  231. package/dist/lib/version.js +94 -16
  232. package/dist/lib/versions.d.ts +27 -0
  233. package/dist/lib/versions.js +248 -79
  234. package/dist/lib/workflows.d.ts +2 -0
  235. package/dist/lib/workflows.js +88 -0
  236. package/package.json +2 -1
  237. package/dist/commands/daemon.d.ts +0 -10
  238. package/dist/commands/daemon.js +0 -121
package/dist/lib/usage.js CHANGED
@@ -25,6 +25,22 @@ const CLAUDE_TOKEN_URL = 'https://platform.claude.com/v1/oauth/token';
25
25
  const CLAUDE_CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
26
26
  const CLAUDE_OAUTH_BETA_HEADER = 'oauth-2025-04-20';
27
27
  const CLAUDE_REFRESH_LEEWAY_MS = 5 * 60 * 1000;
28
+ /**
29
+ * True when a Claude OAuth access token is within the refresh leeway of expiry
30
+ * (or already expired) — i.e. it "would need a refresh" before the next use.
31
+ *
32
+ * Single source of truth for the expiry gate, shared by the two callers that
33
+ * must agree on it but act differently: the run/usage hot path
34
+ * (`getClaudeAccessToken`) refreshes when this is true; the health probe
35
+ * (`probeClaudeStatus`) must NOT refresh and instead reports the non-fatal
36
+ * `expired` state (RUSH-1822). A missing `expiresAt` is treated as "still
37
+ * fresh" (never force a refresh on a token with no known expiry).
38
+ */
39
+ export function claudeAccessTokenNeedsRefresh(expiresAt, nowMs = Date.now()) {
40
+ if (expiresAt == null)
41
+ return false;
42
+ return nowMs + CLAUDE_REFRESH_LEEWAY_MS >= expiresAt;
43
+ }
28
44
  const CLAUDE_SCOPES = [
29
45
  'user:profile',
30
46
  'user:inference',
@@ -609,26 +625,29 @@ async function getDroidUsageInfo(options) {
609
625
  return { snapshot: null, error: null };
610
626
  }
611
627
  }
612
- /** Probe Claude's OAuth token against the usage endpoint. Refreshes an expired access token (safe for Claude). */
628
+ /** Probe Claude's OAuth token against the usage endpoint. Never refreshes — reports `expired` for a near-expiry token; see the comment below (RUSH-1822). */
613
629
  export async function probeClaudeStatus(home, cliVersion) {
614
630
  const oauth = await loadClaudeOauth(home);
615
- if (!oauth?.accessToken)
631
+ const accessToken = oauth?.accessToken?.trim();
632
+ if (!accessToken)
616
633
  return { status: null, token: 'missing' };
617
- let access = null;
618
- try {
619
- access = await getClaudeAccessToken(oauth, home);
620
- }
621
- catch {
622
- access = null;
634
+ // Never refresh from a health probe. Claude's refresh token is single-use and
635
+ // rotates on every refresh; with one account signed into several machines the
636
+ // daemon's every-3-min fleet-cache warm (probeLocalFleetAuth -> here) would
637
+ // stampede that one rotating token and silently invalidate every other
638
+ // holder, dropping the fleet to "run /login" (RUSH-1822). Mirror the sibling
639
+ // Kimi/Droid probes, which never refresh: if the stored token is within the
640
+ // refresh leeway of expiry, report the non-fatal `expired` state ("would need
641
+ // a refresh") instead of rotating it, and leave the single legitimate refresh
642
+ // to the run/usage hot path (getClaudeAccessToken).
643
+ if (claudeAccessTokenNeedsRefresh(oauth?.expiresAt ?? null)) {
644
+ return { status: null, token: 'expired' };
623
645
  }
624
- // Fall back to the stored (possibly stale) token so the server returns the
625
- // real verdict — a 401 here IS the revoked/expired signal we want to surface.
626
- const bearer = access || oauth.accessToken.trim();
627
646
  try {
628
647
  const response = await fetch(CLAUDE_USAGE_URL, {
629
648
  method: 'GET',
630
649
  headers: {
631
- Authorization: `Bearer ${bearer}`,
650
+ Authorization: `Bearer ${accessToken}`,
632
651
  'Content-Type': 'application/json',
633
652
  'anthropic-beta': CLAUDE_OAUTH_BETA_HEADER,
634
653
  'User-Agent': getClaudeUserAgent(cliVersion),
@@ -1058,8 +1077,7 @@ async function getClaudeAccessToken(oauth, home) {
1058
1077
  if (!accessToken) {
1059
1078
  return null;
1060
1079
  }
1061
- const expiresAt = oauth.expiresAt ?? null;
1062
- if (expiresAt === null || Date.now() + CLAUDE_REFRESH_LEEWAY_MS < expiresAt) {
1080
+ if (!claudeAccessTokenNeedsRefresh(oauth.expiresAt ?? null)) {
1063
1081
  return accessToken;
1064
1082
  }
1065
1083
  if (!oauth.refreshToken) {
@@ -1,8 +1,48 @@
1
+ /**
2
+ * Well-known locations of the `agents` launcher for PATH-less GUI/launchd
3
+ * processes (the menu-bar helper inherits no login PATH). Also the anchor for
4
+ * recovering the on-disk install layout when the CLI runs as a Bun single-file
5
+ * binary — see {@link resolveInstalledLayout}.
6
+ */
7
+ export declare function resolveAgentsBin(): string | null;
8
+ export interface InstallLayout {
9
+ /** The install's `dist/` directory (holds `index.js` + `lib/`). */
10
+ distDir: string;
11
+ /** The compiled CLI entry, `dist/index.js`. */
12
+ entryPath: string;
13
+ /** The shipping `package.json`, `dist/../package.json`. */
14
+ pkgJsonPath: string;
15
+ }
16
+ /**
17
+ * Pure derivation (no I/O) of an install's on-disk layout from the realpath of
18
+ * its `agents` launcher. A launcher lives at `<pkg>/dist/bin/agents`, so two
19
+ * levels up is `<pkg>/dist`. Exported for unit testing so the dirname chain the
20
+ * Bun-binary fallback depends on stays locked.
21
+ */
22
+ export declare function installLayoutFromBin(realBin: string): InstallLayout;
23
+ /**
24
+ * Resolve the on-disk install layout of the running CLI by following the
25
+ * `agents` launcher symlink.
26
+ *
27
+ * When the CLI runs as a Bun single-file executable, `import.meta.url` points
28
+ * inside the virtual bundle (`/$bunfs/…`), so sibling-relative resolution can't
29
+ * see the shipped `package.json`, `dist/index.js`, or `MenubarHelper.app` on
30
+ * disk. The launcher symlink points at the real files; walk up from it. Returns
31
+ * null when no launcher is found (dev/tsx runs, or a box without the helper) —
32
+ * callers keep their in-bundle resolution as the primary path and use this only
33
+ * as the fallback.
34
+ */
35
+ export declare function resolveInstalledLayout(): InstallLayout | null;
1
36
  /**
2
37
  * Resolve the CLI version from the shipping package.json. Used by the daemon
3
38
  * to answer `IPCAction: 'version'` and by the client to detect daemon drift —
4
39
  * a dev-build CLI talking to a launchd-managed registry daemon would silently
5
40
  * get stale behavior without this check.
41
+ *
42
+ * Primary read is relative to this module; when that fails (the Bun single-file
43
+ * binary can't read its own bundled package.json), fall back to the on-disk
44
+ * install found via the launcher symlink so callers like the menu bar don't see
45
+ * a bogus `unknown`.
6
46
  */
7
47
  export declare function getCliVersion(): string;
8
48
  /**
@@ -1,28 +1,111 @@
1
1
  import * as fs from 'fs';
2
+ import * as os from 'os';
2
3
  import * as path from 'path';
3
4
  import { fileURLToPath } from 'url';
4
5
  const __filename = fileURLToPath(import.meta.url);
5
6
  const __dirname = path.dirname(__filename);
6
7
  let cached = null;
8
+ /** Read a `version` string from a package.json, or null if unreadable. */
9
+ function readVersionAt(pkgPath) {
10
+ try {
11
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
12
+ return String(pkg.version || '') || null;
13
+ }
14
+ catch {
15
+ return null;
16
+ }
17
+ }
18
+ /**
19
+ * Well-known locations of the `agents` launcher for PATH-less GUI/launchd
20
+ * processes (the menu-bar helper inherits no login PATH). Also the anchor for
21
+ * recovering the on-disk install layout when the CLI runs as a Bun single-file
22
+ * binary — see {@link resolveInstalledLayout}.
23
+ */
24
+ export function resolveAgentsBin() {
25
+ const home = os.homedir();
26
+ const candidates = [
27
+ path.join(home, '.local', 'bin', 'agents'),
28
+ '/opt/homebrew/bin/agents',
29
+ '/usr/local/bin/agents',
30
+ path.join(home, '.npm-global', 'bin', 'agents'),
31
+ ];
32
+ for (const c of candidates) {
33
+ try {
34
+ fs.accessSync(c, fs.constants.X_OK);
35
+ return c;
36
+ }
37
+ catch {
38
+ /* try next */
39
+ }
40
+ }
41
+ return null;
42
+ }
43
+ /**
44
+ * Pure derivation (no I/O) of an install's on-disk layout from the realpath of
45
+ * its `agents` launcher. A launcher lives at `<pkg>/dist/bin/agents`, so two
46
+ * levels up is `<pkg>/dist`. Exported for unit testing so the dirname chain the
47
+ * Bun-binary fallback depends on stays locked.
48
+ */
49
+ export function installLayoutFromBin(realBin) {
50
+ const distDir = path.dirname(path.dirname(realBin)); // <pkg>/dist/bin/agents -> <pkg>/dist
51
+ return {
52
+ distDir,
53
+ entryPath: path.join(distDir, 'index.js'),
54
+ pkgJsonPath: path.join(distDir, '..', 'package.json'),
55
+ };
56
+ }
57
+ /**
58
+ * Resolve the on-disk install layout of the running CLI by following the
59
+ * `agents` launcher symlink.
60
+ *
61
+ * When the CLI runs as a Bun single-file executable, `import.meta.url` points
62
+ * inside the virtual bundle (`/$bunfs/…`), so sibling-relative resolution can't
63
+ * see the shipped `package.json`, `dist/index.js`, or `MenubarHelper.app` on
64
+ * disk. The launcher symlink points at the real files; walk up from it. Returns
65
+ * null when no launcher is found (dev/tsx runs, or a box without the helper) —
66
+ * callers keep their in-bundle resolution as the primary path and use this only
67
+ * as the fallback.
68
+ */
69
+ export function resolveInstalledLayout() {
70
+ const bin = resolveAgentsBin();
71
+ if (!bin)
72
+ return null;
73
+ try {
74
+ const layout = installLayoutFromBin(fs.realpathSync(bin));
75
+ // Validate we landed on a real dist (guards an unexpected launcher layout).
76
+ if (fs.existsSync(layout.entryPath))
77
+ return layout;
78
+ }
79
+ catch {
80
+ /* ignore */
81
+ }
82
+ return null;
83
+ }
7
84
  /**
8
85
  * Resolve the CLI version from the shipping package.json. Used by the daemon
9
86
  * to answer `IPCAction: 'version'` and by the client to detect daemon drift —
10
87
  * a dev-build CLI talking to a launchd-managed registry daemon would silently
11
88
  * get stale behavior without this check.
89
+ *
90
+ * Primary read is relative to this module; when that fails (the Bun single-file
91
+ * binary can't read its own bundled package.json), fall back to the on-disk
92
+ * install found via the launcher symlink so callers like the menu bar don't see
93
+ * a bogus `unknown`.
12
94
  */
13
95
  export function getCliVersion() {
14
96
  if (cached)
15
97
  return cached;
16
- try {
17
- const pkgPath = path.join(__dirname, '..', '..', 'package.json');
18
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
19
- cached = String(pkg.version || 'unknown');
20
- }
21
- catch {
22
- cached = 'unknown';
23
- }
98
+ cached =
99
+ readVersionAt(path.join(__dirname, '..', '..', 'package.json')) ??
100
+ readInstalledPackageVersion() ??
101
+ 'unknown';
24
102
  return cached;
25
103
  }
104
+ /** Version from the on-disk install (Bun-binary fallback). */
105
+ function readInstalledPackageVersion() {
106
+ const layout = resolveInstalledLayout();
107
+ return layout ? readVersionAt(layout.pkgJsonPath) : null;
108
+ }
26
109
  /**
27
110
  * Read the version from package.json on disk every call, bypassing the cache.
28
111
  *
@@ -34,12 +117,7 @@ export function getCliVersion() {
34
117
  * 'unknown' on any error.
35
118
  */
36
119
  export function getCliVersionFresh() {
37
- try {
38
- const pkgPath = path.join(__dirname, '..', '..', 'package.json');
39
- const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
40
- return String(pkg.version || 'unknown');
41
- }
42
- catch {
43
- return 'unknown';
44
- }
120
+ return (readVersionAt(path.join(__dirname, '..', '..', 'package.json')) ??
121
+ readInstalledPackageVersion() ??
122
+ 'unknown');
45
123
  }
@@ -216,6 +216,22 @@ export declare function getGlobalDefault(agent: AgentId): string | null;
216
216
  * Set the global default version for an agent.
217
217
  */
218
218
  export declare function setGlobalDefault(agent: AgentId, version: string | undefined): void;
219
+ /**
220
+ * Mark an installed version as an isolated install (`agents add --isolated`).
221
+ *
222
+ * Isolated versions are fully self-contained: they never become the global
223
+ * default and never own the user's real `~/.<agent>` config directory. This
224
+ * flag is what keeps every "adopting" code path away from them.
225
+ */
226
+ export declare function markVersionIsolated(agent: AgentId, version: string): void;
227
+ /**
228
+ * Whether a version was installed as an isolated install (`agents add --isolated`).
229
+ *
230
+ * Used to exclude such versions from global-default promotion and from any
231
+ * flow that would touch the user's real `~/.<agent>` directory, and to gate the
232
+ * `--isolated` safety check on `agents remove`.
233
+ */
234
+ export declare function isVersionIsolated(agent: AgentId, version: string): boolean;
219
235
  /**
220
236
  * Install a specific version of an agent.
221
237
  */
@@ -399,6 +415,17 @@ export declare function verifyInstalledBinaryLaunches(agent: AgentId, version: s
399
415
  * 4. Nothing runnable installed → install `latest`, pin it, return it.
400
416
  * 5. Give up → return null (caller surfaces a clear error).
401
417
  *
418
+ * Isolation boundary (both directions — steps 3/4 are the only mutating ones):
419
+ * - An ISOLATED target gets step 2 and nothing else. Falling back would let
420
+ * `agents run <agent>@<isolated>` silently repoint the user's NORMAL default,
421
+ * and installing `latest` would materialize a version they never asked for.
422
+ * A failed repair returns null so the caller says so plainly.
423
+ * - An isolated version is never a fallback CANDIDATE either, whatever the
424
+ * target is: promoting one to the global default is exactly what `agents use`
425
+ * refuses and what removeVersion's promotion filter excludes, so it must not
426
+ * happen here by the back door (the daemon's launch-health pass calls this
427
+ * unattended every ~6h).
428
+ *
402
429
  * Gated to npm-package agents: their native binary ships as an optional per-arch
403
430
  * dependency whose tarball can extract partially (interrupted/raced install) —
404
431
  * the exact failure this repairs. Agents with a global/native binary (grok,