@phnx-labs/agents-cli 1.20.21 → 1.20.23

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 (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/commands/cloud.js +142 -13
  3. package/dist/commands/exec.js +13 -1
  4. package/dist/commands/menubar.d.ts +10 -0
  5. package/dist/commands/menubar.js +83 -0
  6. package/dist/commands/routines.js +34 -1
  7. package/dist/commands/secrets.d.ts +1 -1
  8. package/dist/commands/secrets.js +95 -38
  9. package/dist/index.js +292 -225
  10. package/dist/lib/agents.js +8 -0
  11. package/dist/lib/cloud/antigravity.d.ts +70 -0
  12. package/dist/lib/cloud/antigravity.js +196 -0
  13. package/dist/lib/cloud/codex.d.ts +1 -0
  14. package/dist/lib/cloud/codex.js +8 -2
  15. package/dist/lib/cloud/factory.d.ts +79 -18
  16. package/dist/lib/cloud/factory.js +324 -26
  17. package/dist/lib/cloud/registry.d.ts +18 -2
  18. package/dist/lib/cloud/registry.js +28 -4
  19. package/dist/lib/cloud/types.d.ts +73 -2
  20. package/dist/lib/cloud/types.js +17 -0
  21. package/dist/lib/exec.d.ts +2 -0
  22. package/dist/lib/exec.js +5 -0
  23. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +20 -0
  24. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  25. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +115 -0
  26. package/dist/lib/menubar/install-menubar.d.ts +57 -0
  27. package/dist/lib/menubar/install-menubar.js +291 -0
  28. package/dist/lib/secrets/agent.d.ts +9 -1
  29. package/dist/lib/secrets/agent.js +91 -10
  30. package/dist/lib/secrets/bundles.d.ts +19 -12
  31. package/dist/lib/secrets/bundles.js +22 -14
  32. package/dist/lib/self-update.d.ts +34 -0
  33. package/dist/lib/self-update.js +63 -2
  34. package/dist/lib/startup/command-registry.d.ts +99 -0
  35. package/dist/lib/startup/command-registry.js +136 -0
  36. package/dist/lib/types.d.ts +8 -0
  37. package/dist/lib/version.d.ts +11 -0
  38. package/dist/lib/version.js +20 -0
  39. package/package.json +5 -3
  40. package/scripts/postinstall.js +35 -0
@@ -39,15 +39,21 @@ export interface VarMeta {
39
39
  note?: string;
40
40
  }
41
41
  /**
42
- * How a bundle interacts with the macOS secrets-agent:
43
- * - `biometry` (default): only an explicit `agents secrets unlock` populates the
44
- * agent; every other read pops Touch ID. Use for high-value bundles you want
45
- * to confirm each session.
46
- * - `session`: eligible for the agent `unlock`, and (when `secrets.agent.auto`
47
- * is enabled) the first real keychain read auto-loads it so concurrent runs
48
- * read it silently.
42
+ * A bundle's prompt policy how often macOS asks for Touch ID to read it:
43
+ * - `always` (default): asks every time. Only an explicit `agents secrets
44
+ * unlock` ever holds it in the secrets-agent; every other read pops Touch ID.
45
+ * Use for high-value bundles you want to confirm every time.
46
+ * - `daily`: ask once, then hold it silently. Eligible for the secrets-agent
47
+ * `unlock` it, or (when `secrets.agent.auto` is enabled) the first real
48
+ * keychain read auto-loads it so concurrent runs read it silently. Held up to
49
+ * ~24h from that unlock (not refreshed on use); re-asks sooner after
50
+ * screen-lock, sleep, logout, or `agents secrets lock`.
51
+ *
52
+ * Stored on disk under the legacy `tier` key (`session` == `daily`; absent ==
53
+ * `always`) so bundles stay readable across mixed CLI versions on synced
54
+ * machines. The in-memory and user-facing vocabulary is `policy`/`always`/`daily`.
49
55
  */
50
- export type SecretsTier = 'biometry' | 'session';
56
+ export type SecretsPolicy = 'always' | 'daily';
51
57
  /** A named set of environment variable definitions backed by various secret providers. */
52
58
  export interface SecretsBundle {
53
59
  name: string;
@@ -55,8 +61,9 @@ export interface SecretsBundle {
55
61
  allow_exec?: boolean;
56
62
  /** Which store carries this bundle's items. Absent ⇒ `keychain` (the default). */
57
63
  backend?: SecretsBackend;
58
- /** Secrets-agent interaction tier. Absent ⇒ `biometry` (the safe default). */
59
- tier?: SecretsTier;
64
+ /** Prompt policy. Absent ⇒ `always` (the safe default). Serialized under the
65
+ * legacy `tier` key — see SecretsPolicy. */
66
+ policy?: SecretsPolicy;
60
67
  /** ISO 8601 UTC timestamp. Set once on the first writeBundle() for a bundle. */
61
68
  created_at?: string;
62
69
  /** ISO 8601 UTC timestamp. Refreshed on every writeBundle(). */
@@ -90,8 +97,8 @@ export declare function validateSecretType(t: string): asserts t is SecretType;
90
97
  export declare function validateExpiresFutureDated(iso: string): void;
91
98
  export declare function bundleExists(name: string): boolean;
92
99
  export declare function readBundle(name: string): SecretsBundle;
93
- /** The effective tier of a bundle (absent ⇒ `biometry`). */
94
- export declare function bundleTier(bundle: SecretsBundle): SecretsTier;
100
+ /** The effective prompt policy of a bundle (absent ⇒ `always`). */
101
+ export declare function bundlePolicy(bundle: SecretsBundle): SecretsPolicy;
95
102
  export declare function writeBundle(bundle: SecretsBundle): void;
96
103
  export declare function deleteBundle(name: string): boolean;
97
104
  export declare function listBundles(): SecretsBundle[];
@@ -223,10 +223,11 @@ export function readBundle(name) {
223
223
  name,
224
224
  description: parsed.description,
225
225
  allow_exec: Boolean(parsed.allow_exec),
226
- // Absent ⇒ keychain (mirrors `tier`); only set when file-backed so a
227
- // keychain bundle round-trips byte-for-byte.
226
+ // Absent ⇒ keychain; only set when file-backed so a keychain bundle
227
+ // round-trips byte-for-byte.
228
228
  backend: backend === 'file' ? 'file' : undefined,
229
- tier: parseTier(parsed.tier),
229
+ // Legacy wire key: the policy is persisted under `tier` (`session` == `daily`).
230
+ policy: parsePolicy(parsed.tier),
230
231
  vars: parsed.vars && typeof parsed.vars === 'object' ? parsed.vars : {},
231
232
  };
232
233
  if (typeof parsed.created_at === 'string')
@@ -243,13 +244,16 @@ export function readBundle(name) {
243
244
  }
244
245
  return bundle;
245
246
  }
246
- /** Normalize a persisted `tier` value; anything but `session` default tier. */
247
- function parseTier(raw) {
248
- return raw === 'session' ? 'session' : undefined;
247
+ /** Normalize the persisted prompt policy. The on-disk `tier` key uses the
248
+ * legacy `session` token for `daily` (and `biometry`/absent for the default),
249
+ * so accept both the legacy and current tokens. Anything but `daily`/`session`
250
+ * ⇒ undefined (resolves to the `always` default). */
251
+ function parsePolicy(raw) {
252
+ return raw === 'daily' || raw === 'session' ? 'daily' : undefined;
249
253
  }
250
- /** The effective tier of a bundle (absent ⇒ `biometry`). */
251
- export function bundleTier(bundle) {
252
- return bundle.tier ?? 'biometry';
254
+ /** The effective prompt policy of a bundle (absent ⇒ `always`). */
255
+ export function bundlePolicy(bundle) {
256
+ return bundle.policy ?? 'always';
253
257
  }
254
258
  export function writeBundle(bundle) {
255
259
  validateBundleName(bundle.name);
@@ -288,7 +292,9 @@ export function writeBundle(bundle) {
288
292
  description: bundle.description,
289
293
  allow_exec: bundle.allow_exec ? true : undefined,
290
294
  backend: backend === 'file' ? 'file' : undefined,
291
- tier: bundle.tier === 'session' ? 'session' : undefined,
295
+ // Wire format: persist `daily` under the legacy `tier`/`session` token so
296
+ // older CLI versions on other synced machines keep reading the policy.
297
+ tier: bundle.policy === 'daily' ? 'session' : undefined,
292
298
  created_at: bundle.created_at,
293
299
  updated_at: bundle.updated_at,
294
300
  last_used: bundle.last_used,
@@ -328,7 +334,8 @@ function parseBundleMeta(name, json, backend) {
328
334
  description: parsed.description,
329
335
  allow_exec: Boolean(parsed.allow_exec),
330
336
  backend: backend === 'file' ? 'file' : undefined,
331
- tier: parseTier(parsed.tier),
337
+ // Legacy wire key: the policy is persisted under `tier` (`session` == `daily`).
338
+ policy: parsePolicy(parsed.tier),
332
339
  vars: parsed.vars && typeof parsed.vars === 'object' ? parsed.vars : {},
333
340
  };
334
341
  if (typeof parsed.created_at === 'string')
@@ -568,7 +575,8 @@ export function readAndResolveBundleEnv(name, opts = {}) {
568
575
  description: parsed.description,
569
576
  allow_exec: Boolean(parsed.allow_exec),
570
577
  backend: backend === 'file' ? 'file' : undefined,
571
- tier: parseTier(parsed.tier),
578
+ // Legacy wire key: the policy is persisted under `tier` (`session` == `daily`).
579
+ policy: parsePolicy(parsed.tier),
572
580
  vars: parsed.vars && typeof parsed.vars === 'object' ? parsed.vars : {},
573
581
  };
574
582
  if (typeof parsed.created_at === 'string')
@@ -639,14 +647,14 @@ export function readAndResolveBundleEnv(name, opts = {}) {
639
647
  }
640
648
  emitReadAudit('success');
641
649
  // Auto-cache: this was a real keychain read (the agent fast-path returned
642
- // earlier on a hit). If the bundle opts into the session tier and the user
650
+ // earlier on a hit). If the bundle opts into the `daily` policy and the user
643
651
  // enabled `secrets.agent.auto`, populate the broker in the background so the
644
652
  // next concurrent run reads silently. Skipped when noAgent (e.g. `unlock`,
645
653
  // which loads the agent itself). Fire-and-forget — never blocks this read.
646
654
  if (backend === 'keychain' &&
647
655
  !opts.noAgent &&
648
656
  process.env.AGENTS_SECRETS_NO_AGENT !== '1' &&
649
- bundleTier(bundle) === 'session' &&
657
+ bundlePolicy(bundle) === 'daily' &&
650
658
  secretsAgentAutoEnabled()) {
651
659
  agentAutoLoadSync(name, bundle, env, DEFAULT_TTL_MS);
652
660
  }
@@ -12,6 +12,31 @@
12
12
  * to PATH resolution.
13
13
  */
14
14
  export declare const NPM_PACKAGE_NAME = "@phnx-labs/agents-cli";
15
+ export type PackageManager = 'npm' | 'bun';
16
+ /**
17
+ * The directory bun installs global packages into:
18
+ * <BUN_INSTALL>/install/global (BUN_INSTALL defaults to ~/.bun)
19
+ *
20
+ * A globally-installed scoped package then lives at
21
+ * `<bunGlobalDir>/node_modules/@phnx-labs/agents-cli` — note there is NO `lib`
22
+ * segment, unlike npm's POSIX layout. That single difference is why an
23
+ * npm-based upgrade silently misses a bun install (see deriveGlobalPrefix).
24
+ */
25
+ export declare function bunGlobalDir(): string;
26
+ /**
27
+ * Identify which package manager owns the install at `packageRoot`, so the
28
+ * upgrade can shell out to the one that actually replaces this copy.
29
+ *
30
+ * bun lays a global package out as `<bunGlobalDir>/node_modules/<scoped pkg>`,
31
+ * so the prefix (the parent of `node_modules`) is the bun global dir itself.
32
+ * Everything else — npm's `<prefix>/lib/node_modules` and the Windows
33
+ * `<prefix>/node_modules` — is treated as npm.
34
+ *
35
+ * Detection is path-based (no subprocess): it matches the resolved bun global
36
+ * dir from BUN_INSTALL/$HOME, and falls back to the structural `.bun/install/
37
+ * global` tail for a relocated BUN_INSTALL not exported into this process.
38
+ */
39
+ export declare function detectPackageManager(packageRoot: string): PackageManager;
15
40
  export interface UpdateCheckCache {
16
41
  lastCheck: number;
17
42
  latestVersion: string;
@@ -49,6 +74,15 @@ export declare function deriveGlobalPrefix(packageRoot: string): string;
49
74
  * refreshAliasShims().
50
75
  */
51
76
  export declare function installPackageIntoPrefix(spec: string, prefix: string): Promise<void>;
77
+ /**
78
+ * Install `spec` into bun's global store with `bun add -g`. bun writes to
79
+ * `<bunGlobalDir>/node_modules/<pkg>`, which is exactly the running package
80
+ * root for a bun install — so verifyInstalledVersion() sees the new version
81
+ * in place. bun skips untrusted lifecycle scripts, so the caller refreshes
82
+ * alias shims afterwards via refreshAliasShims() rather than relying on the
83
+ * package's postinstall hook.
84
+ */
85
+ export declare function installPackageWithBun(spec: string): Promise<void>;
52
86
  /** Read the version field of the package.json at `packageRoot`, fresh from disk. */
53
87
  export declare function readInstalledVersion(packageRoot: string): string;
54
88
  /**
@@ -12,10 +12,55 @@
12
12
  * to PATH resolution.
13
13
  */
14
14
  import * as fs from 'fs';
15
+ import * as os from 'os';
15
16
  import * as path from 'path';
16
17
  import { spawnSync } from 'child_process';
17
18
  import { compareVersions } from './versions.js';
18
19
  export const NPM_PACKAGE_NAME = '@phnx-labs/agents-cli';
20
+ /**
21
+ * The directory bun installs global packages into:
22
+ * <BUN_INSTALL>/install/global (BUN_INSTALL defaults to ~/.bun)
23
+ *
24
+ * A globally-installed scoped package then lives at
25
+ * `<bunGlobalDir>/node_modules/@phnx-labs/agents-cli` — note there is NO `lib`
26
+ * segment, unlike npm's POSIX layout. That single difference is why an
27
+ * npm-based upgrade silently misses a bun install (see deriveGlobalPrefix).
28
+ */
29
+ export function bunGlobalDir() {
30
+ const bunInstall = process.env.BUN_INSTALL || path.join(os.homedir(), '.bun');
31
+ return path.join(bunInstall, 'install', 'global');
32
+ }
33
+ /**
34
+ * Identify which package manager owns the install at `packageRoot`, so the
35
+ * upgrade can shell out to the one that actually replaces this copy.
36
+ *
37
+ * bun lays a global package out as `<bunGlobalDir>/node_modules/<scoped pkg>`,
38
+ * so the prefix (the parent of `node_modules`) is the bun global dir itself.
39
+ * Everything else — npm's `<prefix>/lib/node_modules` and the Windows
40
+ * `<prefix>/node_modules` — is treated as npm.
41
+ *
42
+ * Detection is path-based (no subprocess): it matches the resolved bun global
43
+ * dir from BUN_INSTALL/$HOME, and falls back to the structural `.bun/install/
44
+ * global` tail for a relocated BUN_INSTALL not exported into this process.
45
+ */
46
+ export function detectPackageManager(packageRoot) {
47
+ const resolved = path.resolve(packageRoot);
48
+ const prefix = path.dirname(path.dirname(path.dirname(resolved))); // strip <scope>/<pkg>/node_modules
49
+ if (prefix === path.resolve(bunGlobalDir()))
50
+ return 'bun';
51
+ const parts = prefix.split(path.sep);
52
+ const n = parts.length;
53
+ if (n >= 3 && parts[n - 1] === 'global' && parts[n - 2] === 'install' && parts[n - 3] === '.bun') {
54
+ return 'bun';
55
+ }
56
+ return 'npm';
57
+ }
58
+ /** The shell command a user can run by hand to reproduce the upgrade for `manager`. */
59
+ function manualInstallHint(manager, packageRoot, spec) {
60
+ if (manager === 'bun')
61
+ return `bun add -g ${spec}`;
62
+ return `npm install -g --prefix ${deriveGlobalPrefix(packageRoot)} ${spec}`;
63
+ }
19
64
  /** Read the cached update-check state from disk. Returns null if the file is missing or corrupt. */
20
65
  export function readUpdateCache(file) {
21
66
  try {
@@ -102,6 +147,20 @@ export async function installPackageIntoPrefix(spec, prefix) {
102
147
  const execFileAsync = promisify(execFile);
103
148
  await execFileAsync('npm', ['install', '-g', '--prefix', prefix, spec, '--ignore-scripts']);
104
149
  }
150
+ /**
151
+ * Install `spec` into bun's global store with `bun add -g`. bun writes to
152
+ * `<bunGlobalDir>/node_modules/<pkg>`, which is exactly the running package
153
+ * root for a bun install — so verifyInstalledVersion() sees the new version
154
+ * in place. bun skips untrusted lifecycle scripts, so the caller refreshes
155
+ * alias shims afterwards via refreshAliasShims() rather than relying on the
156
+ * package's postinstall hook.
157
+ */
158
+ export async function installPackageWithBun(spec) {
159
+ const { execFile } = await import('child_process');
160
+ const { promisify } = await import('util');
161
+ const execFileAsync = promisify(execFile);
162
+ await execFileAsync('bun', ['add', '-g', spec]);
163
+ }
105
164
  /** Read the version field of the package.json at `packageRoot`, fresh from disk. */
106
165
  export function readInstalledVersion(packageRoot) {
107
166
  return JSON.parse(fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8')).version;
@@ -113,8 +172,10 @@ export function readInstalledVersion(packageRoot) {
113
172
  export function verifyInstalledVersion(packageRoot, expectedVersion) {
114
173
  const actual = readInstalledVersion(packageRoot);
115
174
  if (actual !== expectedVersion) {
116
- throw new Error(`npm reported success but ${packageRoot} is still ${actual} (expected ${expectedVersion}). ` +
117
- `Run manually: npm install -g --prefix ${deriveGlobalPrefix(packageRoot)} ${NPM_PACKAGE_NAME}@${expectedVersion}`);
175
+ const manager = detectPackageManager(packageRoot);
176
+ const hint = manualInstallHint(manager, packageRoot, `${NPM_PACKAGE_NAME}@${expectedVersion}`);
177
+ throw new Error(`the package manager reported success but ${packageRoot} is still ${actual} (expected ${expectedVersion}). ` +
178
+ `Run manually: ${hint}`);
118
179
  }
119
180
  }
120
181
  /**
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Lazy command registry.
3
+ *
4
+ * The CLI entry point (src/index.ts) used to statically import every command
5
+ * module and call its `registerXCommand(program)` on every invocation. That
6
+ * loaded the entire command tree (~50 modules) before the first line of output,
7
+ * dominating cold-start latency.
8
+ *
9
+ * This module maps each user-typed top-level command name to a thunk that
10
+ * dynamically imports ONLY the module(s) that command needs. Fast commands
11
+ * (`--version`, `view`, ...) now pay for just the one module they use; the full
12
+ * tree is loaded only on the rare slow paths (unknown-command spellcheck, bare
13
+ * help) via `registerAllEagerCommands` in src/index.ts.
14
+ *
15
+ * Parity is non-negotiable: the name -> loader map below mirrors exactly which
16
+ * module registers which top-level command on `main`. Multi-command modules
17
+ * (versions, packages) map several names to the same loader; `prune` needs BOTH
18
+ * versions (which creates `prune <specs...>`) and prune.js (which attaches the
19
+ * `cleanup` subcommand to it), in that order — see commands/prune.ts.
20
+ */
21
+ import type { Command } from 'commander';
22
+ /** A function that registers one or more commands onto the root program. */
23
+ export type Registrar = (program: Command) => void;
24
+ /** A thunk that dynamically imports a command module and returns its registrar. */
25
+ export type ModuleLoader = () => Promise<Registrar>;
26
+ export declare const loadView: ModuleLoader;
27
+ export declare const loadInspect: ModuleLoader;
28
+ export declare const loadFeedback: ModuleLoader;
29
+ export declare const loadCommands: ModuleLoader;
30
+ export declare const loadHooks: ModuleLoader;
31
+ export declare const loadSkills: ModuleLoader;
32
+ export declare const loadRules: ModuleLoader;
33
+ export declare const loadPermissions: ModuleLoader;
34
+ export declare const loadMcp: ModuleLoader;
35
+ export declare const loadCli: ModuleLoader;
36
+ export declare const loadSubagents: ModuleLoader;
37
+ export declare const loadPlugins: ModuleLoader;
38
+ export declare const loadWorkflows: ModuleLoader;
39
+ export declare const loadWorktree: ModuleLoader;
40
+ export declare const loadVersions: ModuleLoader;
41
+ export declare const loadImport: ModuleLoader;
42
+ export declare const loadPackages: ModuleLoader;
43
+ export declare const loadDaemon: ModuleLoader;
44
+ export declare const loadRoutines: ModuleLoader;
45
+ export declare const loadRun: ModuleLoader;
46
+ export declare const loadDefaults: ModuleLoader;
47
+ export declare const loadModels: ModuleLoader;
48
+ export declare const loadPrune: ModuleLoader;
49
+ export declare const loadTrash: ModuleLoader;
50
+ export declare const loadRestore: ModuleLoader;
51
+ export declare const loadDoctor: ModuleLoader;
52
+ export declare const loadProfiles: ModuleLoader;
53
+ export declare const loadSecrets: ModuleLoader;
54
+ export declare const loadWallet: ModuleLoader;
55
+ export declare const loadHelper: ModuleLoader;
56
+ export declare const loadMenubar: ModuleLoader;
57
+ export declare const loadBeta: ModuleLoader;
58
+ export declare const loadSync: ModuleLoader;
59
+ export declare const loadRefreshRules: ModuleLoader;
60
+ export declare const loadDrive: ModuleLoader;
61
+ export declare const loadFactory: ModuleLoader;
62
+ export declare const loadUsage: ModuleLoader;
63
+ export declare const loadCost: ModuleLoader;
64
+ export declare const loadBudget: ModuleLoader;
65
+ export declare const loadAlias: ModuleLoader;
66
+ export declare const loadPty: ModuleLoader;
67
+ export declare const loadTmux: ModuleLoader;
68
+ export declare const loadBrowser: ModuleLoader;
69
+ export declare const loadComputer: ModuleLoader;
70
+ export declare const loadPull: ModuleLoader;
71
+ export declare const loadPush: ModuleLoader;
72
+ export declare const loadRepo: ModuleLoader;
73
+ export declare const loadSetup: ModuleLoader;
74
+ export declare const loadSessions: ModuleLoader;
75
+ export declare const loadTeams: ModuleLoader;
76
+ export declare const loadCloud: ModuleLoader;
77
+ /**
78
+ * Commands whose modules pull in the SQLite-backed session/cloud stack. They are
79
+ * registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
80
+ * conventions at module top-level, lazy registration just before parse), so they
81
+ * inherit the root's custom help formatter rather than getting the per-command
82
+ * recursive pass. Keeping that ordering preserves their `--help` output exactly.
83
+ */
84
+ export declare const LAZY_COMMAND_NAMES: ReadonlySet<string>;
85
+ /**
86
+ * User-typed top-level command name -> ordered list of module loaders to run.
87
+ *
88
+ * Most names map to a single loader. The exceptions encode real coupling on main:
89
+ * - `add`/`use`/`list`/`remove`/`rm`/`purge` all come from the versions module.
90
+ * - `registry`/`search`/`install` all come from the packages module.
91
+ * - `trash` and `restore` are separate registrars in the trash module.
92
+ * - `prune` needs versions FIRST (it creates `prune <specs...>`) then prune.js
93
+ * (which finds that command and attaches the `cleanup` subcommand).
94
+ *
95
+ * Inline deprecated aliases (memory/perms/exec/jobs/cron) and the inline
96
+ * `upgrade` command are NOT here — they are closures over entry-point state and
97
+ * are handled directly in src/index.ts.
98
+ */
99
+ export declare const COMMAND_LOADERS: Record<string, ModuleLoader[]>;
@@ -0,0 +1,136 @@
1
+ // One loader per command module. Each dynamically imports the module and hands
2
+ // back its register function. Kept as named consts so src/index.ts can compose
3
+ // them into the exact main-branch registration order for the slow path.
4
+ export const loadView = async () => (await import('../../commands/view.js')).registerViewCommand;
5
+ export const loadInspect = async () => (await import('../../commands/inspect.js')).registerInspectCommand;
6
+ export const loadFeedback = async () => (await import('../../commands/feedback.js')).registerFeedbackCommand;
7
+ export const loadCommands = async () => (await import('../../commands/commands.js')).registerCommandsCommands;
8
+ export const loadHooks = async () => (await import('../../commands/hooks.js')).registerHooksCommands;
9
+ export const loadSkills = async () => (await import('../../commands/skills.js')).registerSkillsCommands;
10
+ export const loadRules = async () => (await import('../../commands/rules.js')).registerRulesCommands;
11
+ export const loadPermissions = async () => (await import('../../commands/permissions.js')).registerPermissionsCommands;
12
+ export const loadMcp = async () => (await import('../../commands/mcp.js')).registerMcpCommands;
13
+ export const loadCli = async () => (await import('../../commands/cli.js')).registerCliCommands;
14
+ export const loadSubagents = async () => (await import('../../commands/subagents.js')).registerSubagentsCommands;
15
+ export const loadPlugins = async () => (await import('../../commands/plugins.js')).registerPluginsCommands;
16
+ export const loadWorkflows = async () => (await import('../../commands/workflows.js')).registerWorkflowsCommands;
17
+ export const loadWorktree = async () => (await import('../../commands/worktree.js')).registerWorktreeCommands;
18
+ export const loadVersions = async () => (await import('../../commands/versions.js')).registerVersionsCommands;
19
+ export const loadImport = async () => (await import('../../commands/import.js')).registerImportCommand;
20
+ export const loadPackages = async () => (await import('../../commands/packages.js')).registerPackagesCommands;
21
+ export const loadDaemon = async () => (await import('../../commands/daemon.js')).registerDaemonCommands;
22
+ export const loadRoutines = async () => (await import('../../commands/routines.js')).registerRoutinesCommands;
23
+ export const loadRun = async () => (await import('../../commands/exec.js')).registerRunCommand;
24
+ export const loadDefaults = async () => (await import('../../commands/defaults.js')).registerDefaultsCommands;
25
+ export const loadModels = async () => (await import('../../commands/models.js')).registerModelsCommand;
26
+ export const loadPrune = async () => (await import('../../commands/prune.js')).registerPruneCommand;
27
+ export const loadTrash = async () => (await import('../../commands/trash.js')).registerTrashCommands;
28
+ export const loadRestore = async () => (await import('../../commands/trash.js')).registerRestoreCommand;
29
+ export const loadDoctor = async () => (await import('../../commands/doctor.js')).registerDoctorCommand;
30
+ export const loadProfiles = async () => (await import('../../commands/profiles.js')).registerProfilesCommands;
31
+ export const loadSecrets = async () => (await import('../../commands/secrets.js')).registerSecretsCommands;
32
+ export const loadWallet = async () => (await import('../../commands/wallet.js')).registerWalletCommands;
33
+ export const loadHelper = async () => (await import('../../commands/helper.js')).registerHelperCommand;
34
+ export const loadMenubar = async () => (await import('../../commands/menubar.js')).registerMenubarCommands;
35
+ export const loadBeta = async () => (await import('../../commands/beta.js')).registerBetaCommands;
36
+ export const loadSync = async () => (await import('../../commands/sync.js')).registerSyncCommand;
37
+ export const loadRefreshRules = async () => (await import('../../commands/refresh-rules.js')).registerRefreshRulesCommand;
38
+ export const loadDrive = async () => (await import('../../commands/drive.js')).registerDriveCommands;
39
+ export const loadFactory = async () => (await import('../../commands/factory.js')).registerFactoryCommands;
40
+ export const loadUsage = async () => (await import('../../commands/usage.js')).registerUsageCommand;
41
+ export const loadCost = async () => (await import('../../commands/cost.js')).registerCostCommand;
42
+ export const loadBudget = async () => (await import('../../commands/budget.js')).registerBudgetCommand;
43
+ export const loadAlias = async () => (await import('../../commands/alias.js')).registerAliasCommand;
44
+ export const loadPty = async () => (await import('../../commands/pty.js')).registerPtyCommands;
45
+ export const loadTmux = async () => (await import('../../commands/tmux.js')).registerTmuxCommands;
46
+ export const loadBrowser = async () => (await import('../../commands/browser.js')).registerBrowserCommand;
47
+ export const loadComputer = async () => (await import('../../commands/computer.js')).registerComputerCommand;
48
+ export const loadPull = async () => (await import('../../commands/pull.js')).registerPullCommand;
49
+ export const loadPush = async () => (await import('../../commands/push.js')).registerPushCommand;
50
+ export const loadRepo = async () => (await import('../../commands/repo.js')).registerRepoCommands;
51
+ export const loadSetup = async () => (await import('../../commands/setup.js')).registerSetupCommand;
52
+ export const loadSessions = async () => (await import('../../commands/sessions.js')).registerSessionsCommands;
53
+ export const loadTeams = async () => (await import('../../commands/teams.js')).registerTeamsCommands;
54
+ export const loadCloud = async () => (await import('../../commands/cloud.js')).registerCloudCommands;
55
+ /**
56
+ * Commands whose modules pull in the SQLite-backed session/cloud stack. They are
57
+ * registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
58
+ * conventions at module top-level, lazy registration just before parse), so they
59
+ * inherit the root's custom help formatter rather than getting the per-command
60
+ * recursive pass. Keeping that ordering preserves their `--help` output exactly.
61
+ */
62
+ export const LAZY_COMMAND_NAMES = new Set(['sessions', 'teams', 'cloud']);
63
+ /**
64
+ * User-typed top-level command name -> ordered list of module loaders to run.
65
+ *
66
+ * Most names map to a single loader. The exceptions encode real coupling on main:
67
+ * - `add`/`use`/`list`/`remove`/`rm`/`purge` all come from the versions module.
68
+ * - `registry`/`search`/`install` all come from the packages module.
69
+ * - `trash` and `restore` are separate registrars in the trash module.
70
+ * - `prune` needs versions FIRST (it creates `prune <specs...>`) then prune.js
71
+ * (which finds that command and attaches the `cleanup` subcommand).
72
+ *
73
+ * Inline deprecated aliases (memory/perms/exec/jobs/cron) and the inline
74
+ * `upgrade` command are NOT here — they are closures over entry-point state and
75
+ * are handled directly in src/index.ts.
76
+ */
77
+ export const COMMAND_LOADERS = {
78
+ view: [loadView],
79
+ inspect: [loadInspect],
80
+ feedback: [loadFeedback],
81
+ commands: [loadCommands],
82
+ hooks: [loadHooks],
83
+ skills: [loadSkills],
84
+ rules: [loadRules],
85
+ permissions: [loadPermissions],
86
+ mcp: [loadMcp],
87
+ cli: [loadCli],
88
+ subagents: [loadSubagents],
89
+ plugins: [loadPlugins],
90
+ workflows: [loadWorkflows],
91
+ worktree: [loadWorktree],
92
+ add: [loadVersions],
93
+ use: [loadVersions],
94
+ list: [loadVersions],
95
+ remove: [loadVersions],
96
+ rm: [loadVersions],
97
+ purge: [loadVersions],
98
+ prune: [loadVersions, loadPrune],
99
+ import: [loadImport],
100
+ registry: [loadPackages],
101
+ search: [loadPackages],
102
+ install: [loadPackages],
103
+ daemon: [loadDaemon],
104
+ routines: [loadRoutines],
105
+ run: [loadRun],
106
+ defaults: [loadDefaults],
107
+ models: [loadModels],
108
+ trash: [loadTrash],
109
+ restore: [loadRestore],
110
+ doctor: [loadDoctor],
111
+ profiles: [loadProfiles],
112
+ secrets: [loadSecrets],
113
+ wallet: [loadWallet],
114
+ helper: [loadHelper],
115
+ menubar: [loadMenubar],
116
+ beta: [loadBeta],
117
+ sync: [loadSync],
118
+ 'refresh-rules': [loadRefreshRules],
119
+ drive: [loadDrive],
120
+ factory: [loadFactory],
121
+ usage: [loadUsage],
122
+ cost: [loadCost],
123
+ budget: [loadBudget],
124
+ alias: [loadAlias],
125
+ pty: [loadPty],
126
+ tmux: [loadTmux],
127
+ browser: [loadBrowser],
128
+ computer: [loadComputer],
129
+ pull: [loadPull],
130
+ push: [loadPush],
131
+ repo: [loadRepo],
132
+ setup: [loadSetup],
133
+ sessions: [loadSessions],
134
+ teams: [loadTeams],
135
+ cloud: [loadCloud],
136
+ };
@@ -5,6 +5,7 @@
5
5
  * configuration schemas, resource tracking, registry types, and permission
6
6
  * formats for each supported agent.
7
7
  */
8
+ import type { CloudProviderId } from './cloud/types.js';
8
9
  /** Unique identifier for a supported AI coding agent. */
9
10
  export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'roo' | 'antigravity' | 'grok' | 'kimi' | 'droid';
10
11
  /** How `agents run <agent>` chooses an installed version when none is pinned. */
@@ -83,6 +84,13 @@ export interface AgentConfig {
83
84
  variableSyntax: string;
84
85
  supportsHooks: boolean;
85
86
  nativeAgentsSkillsDir?: boolean;
87
+ /**
88
+ * This agent's *own* cloud backend. `agents cloud run --agent <id>` routes
89
+ * here when no `--provider` is given (precedence: --provider > this >
90
+ * cloud.default_provider > rush). Undefined means the agent has no native
91
+ * cloud and falls back to the configured default.
92
+ */
93
+ cloudProvider?: CloudProviderId;
86
94
  capabilities: {
87
95
  hooks: Capability;
88
96
  mcp: Capability;
@@ -5,3 +5,14 @@
5
5
  * get stale behavior without this check.
6
6
  */
7
7
  export declare function getCliVersion(): string;
8
+ /**
9
+ * Read the version from package.json on disk every call, bypassing the cache.
10
+ *
11
+ * `getCliVersion()` memoizes the version a long-running process *started* with.
12
+ * After `npm i -g` overwrites the install in place, the on-disk package.json
13
+ * changes but the running process keeps its old in-memory code. Comparing this
14
+ * fresh read against the cached startup value is how a daemon/broker detects it
15
+ * is now stale and should reload onto the new code (self-healing). Returns
16
+ * 'unknown' on any error.
17
+ */
18
+ export declare function getCliVersionFresh(): string;
@@ -23,3 +23,23 @@ export function getCliVersion() {
23
23
  }
24
24
  return cached;
25
25
  }
26
+ /**
27
+ * Read the version from package.json on disk every call, bypassing the cache.
28
+ *
29
+ * `getCliVersion()` memoizes the version a long-running process *started* with.
30
+ * After `npm i -g` overwrites the install in place, the on-disk package.json
31
+ * changes but the running process keeps its old in-memory code. Comparing this
32
+ * fresh read against the cached startup value is how a daemon/broker detects it
33
+ * is now stale and should reload onto the new code (self-healing). Returns
34
+ * 'unknown' on any error.
35
+ */
36
+ 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
+ }
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.21",
3
+ "version": "1.20.23",
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",
@@ -25,6 +25,7 @@
25
25
  "dist/**/*.d.ts",
26
26
  "dist/**/*.json",
27
27
  "dist/lib/secrets/Agents CLI.app/**",
28
+ "dist/lib/menubar/MenubarHelper.app/**",
28
29
  "scripts/postinstall.js",
29
30
  "scripts/install-helper.js",
30
31
  "CHANGELOG.md",
@@ -43,8 +44,9 @@
43
44
  "url": "https://github.com/phnx-labs/agents-cli/issues"
44
45
  },
45
46
  "scripts": {
46
- "build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' && ([ \"$(uname)\" = \"Darwin\" ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true)",
47
- "prepack": "scripts/verify-keychain-helper.sh",
47
+ "build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ \"$(uname)\" = \"Darwin\" ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true) && ([ \"$(uname)\" = \"Darwin\" ] && [ -d 'bin/MenubarHelper.app' ] && mkdir -p 'dist/lib/menubar' && cp -R 'bin/MenubarHelper.app' 'dist/lib/menubar/MenubarHelper.app' || true)",
48
+ "build:bin": "scripts/build-bin.sh",
49
+ "prepack": "scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh",
48
50
  "postinstall": "node scripts/postinstall.js",
49
51
  "dev": "tsx src/index.ts",
50
52
  "start": "node dist/index.js",