@phnx-labs/agents-cli 1.22.23 → 1.22.24

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 (88) hide show
  1. package/CHANGELOG.md +193 -0
  2. package/README.md +5 -4
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/attach.d.ts +2 -0
  5. package/dist/commands/attach.js +32 -7
  6. package/dist/commands/defaults.js +2 -0
  7. package/dist/commands/doctor.js +5 -0
  8. package/dist/commands/exec.js +80 -28
  9. package/dist/commands/feed.d.ts +18 -0
  10. package/dist/commands/feed.js +44 -1
  11. package/dist/commands/focus.d.ts +34 -3
  12. package/dist/commands/focus.js +300 -56
  13. package/dist/commands/go.d.ts +14 -1
  14. package/dist/commands/go.js +49 -5
  15. package/dist/commands/insights.js +6 -2
  16. package/dist/commands/inspect.js +39 -5
  17. package/dist/commands/menubar.js +6 -1
  18. package/dist/commands/models.js +1 -0
  19. package/dist/commands/modes.d.ts +12 -0
  20. package/dist/commands/modes.js +147 -0
  21. package/dist/commands/secrets-sync.js +11 -13
  22. package/dist/commands/secrets.d.ts +2 -0
  23. package/dist/commands/secrets.js +54 -17
  24. package/dist/commands/sessions-browser.d.ts +35 -0
  25. package/dist/commands/sessions-browser.js +140 -14
  26. package/dist/commands/sessions-resume.d.ts +2 -0
  27. package/dist/commands/sessions-resume.js +26 -11
  28. package/dist/commands/sessions.d.ts +10 -0
  29. package/dist/commands/sessions.js +34 -59
  30. package/dist/commands/setup-secrets.js +1 -1
  31. package/dist/commands/sync.js +246 -42
  32. package/dist/commands/view.js +2 -0
  33. package/dist/index.js +2 -1
  34. package/dist/lib/agent-modes.d.ts +49 -0
  35. package/dist/lib/agent-modes.js +70 -0
  36. package/dist/lib/doctor-diff.d.ts +3 -0
  37. package/dist/lib/doctor-diff.js +15 -13
  38. package/dist/lib/event-stream.d.ts +3 -1
  39. package/dist/lib/event-stream.js +14 -1
  40. package/dist/lib/exec.d.ts +12 -0
  41. package/dist/lib/exec.js +88 -9
  42. package/dist/lib/hooks/cache.js +36 -3
  43. package/dist/lib/hooks.d.ts +19 -7
  44. package/dist/lib/hooks.js +88 -38
  45. package/dist/lib/hosts/session-index.d.ts +4 -0
  46. package/dist/lib/hosts/session-index.js +7 -0
  47. package/dist/lib/manifest.d.ts +12 -2
  48. package/dist/lib/manifest.js +60 -5
  49. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  50. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +5 -1
  51. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  52. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  53. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
  54. package/dist/lib/menubar/install-menubar.d.ts +52 -2
  55. package/dist/lib/menubar/install-menubar.js +128 -6
  56. package/dist/lib/refresh.d.ts +5 -0
  57. package/dist/lib/refresh.js +37 -33
  58. package/dist/lib/resource-inventory.d.ts +79 -0
  59. package/dist/lib/resource-inventory.js +122 -0
  60. package/dist/lib/resources.js +8 -5
  61. package/dist/lib/run-defaults.d.ts +2 -0
  62. package/dist/lib/run-defaults.js +23 -2
  63. package/dist/lib/runner.js +22 -17
  64. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  65. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
  66. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  67. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  68. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
  69. package/dist/lib/secrets/bundles.js +157 -65
  70. package/dist/lib/secrets/filestore.d.ts +5 -3
  71. package/dist/lib/secrets/filestore.js +12 -8
  72. package/dist/lib/secrets/index.js +34 -8
  73. package/dist/lib/secrets/reaper.d.ts +10 -0
  74. package/dist/lib/secrets/reaper.js +50 -15
  75. package/dist/lib/secrets/sync-passphrase.d.ts +27 -0
  76. package/dist/lib/secrets/sync-passphrase.js +78 -0
  77. package/dist/lib/session/recovery.d.ts +37 -0
  78. package/dist/lib/session/recovery.js +95 -0
  79. package/dist/lib/shims.d.ts +2 -2
  80. package/dist/lib/shims.js +24 -5
  81. package/dist/lib/startup/command-registry.d.ts +1 -0
  82. package/dist/lib/startup/command-registry.js +2 -0
  83. package/dist/lib/sync-umbrella.d.ts +5 -0
  84. package/dist/lib/sync-umbrella.js +5 -4
  85. package/dist/lib/tmux/session.d.ts +4 -2
  86. package/dist/lib/tmux/session.js +5 -5
  87. package/dist/lib/types.d.ts +2 -0
  88. package/package.json +1 -1
@@ -0,0 +1,95 @@
1
+ import { AGENTS } from '../agents.js';
2
+ import { isSelfHost } from '../devices/self-host.js';
3
+ import { nativeResume } from '../exec.js';
4
+ import { machineId, normalizeHost } from '../machine-id.js';
5
+ import { collectRunCandidates, formatNoHealthyAccountError, pickBalancedCandidate, readinessFromCandidate, } from '../rotate.js';
6
+ export class SessionRecoveryError extends Error {
7
+ constructor(message) {
8
+ super(message);
9
+ this.name = 'SessionRecoveryError';
10
+ }
11
+ }
12
+ /** Canonical origin-device label for every recovery consumer. */
13
+ export function sessionOriginDevice(session, self = machineId()) {
14
+ return normalizeHost(session.machine ?? self);
15
+ }
16
+ /** The peer that must execute recovery, or undefined when this is the origin. */
17
+ export function sessionRecoveryPeer(session, selfCheck = isSelfHost) {
18
+ if (!session.machine || selfCheck(session.machine))
19
+ return undefined;
20
+ return normalizeHost(session.machine);
21
+ }
22
+ /** Whether an explicit placement names the session's origin device. */
23
+ export function sessionRecoveryDestinationMatches(session, requestedHost, self = machineId()) {
24
+ const requested = normalizeHost(requestedHost.split('@').pop() || requestedHost);
25
+ return requested === sessionOriginDevice(session, self);
26
+ }
27
+ function runnableSessionAgent(session) {
28
+ if (!(session.agent in AGENTS)) {
29
+ throw new SessionRecoveryError(`Session ${session.shortId} belongs to ${session.agent}, which is indexed but cannot be launched by agents run.`);
30
+ }
31
+ return session.agent;
32
+ }
33
+ function sourceReason(session, candidates) {
34
+ if (!session.version)
35
+ return 'the origin version was not recorded';
36
+ const source = candidates.find((candidate) => candidate.version === session.version);
37
+ if (!source)
38
+ return `origin ${session.agent}@${session.version} is not installed`;
39
+ const readiness = readinessFromCandidate(source);
40
+ return readiness.ready
41
+ ? `origin ${session.agent}@${session.version} has no native resume form`
42
+ : `origin ${session.agent}@${session.version} is ${readiness.reason}`;
43
+ }
44
+ /**
45
+ * Decide how a durable session resumes on the device that owns it.
46
+ *
47
+ * Native resume is legal only in the exact origin version's isolated home and
48
+ * only while that account is healthy. Every other successful path stays on the
49
+ * same harness and uses `/continue`, whose indexed transcript reader can reach
50
+ * retained version trash. No healthy same-harness account is a loud failure.
51
+ */
52
+ export function resolveSessionRecoveryFromCandidates(session, candidates, supportsNative = nativeResume) {
53
+ const agent = runnableSessionAgent(session);
54
+ const device = sessionOriginDevice(session);
55
+ const source = session.version
56
+ ? candidates.find((candidate) => candidate.version === session.version)
57
+ : undefined;
58
+ const sourceReady = source ? readinessFromCandidate(source).ready : false;
59
+ // An exact healthy origin is deterministic: preserve its isolated home. If
60
+ // native resume is unavailable for that harness, /continue still launches in
61
+ // that same healthy home. Only an unusable/missing origin enters balanced
62
+ // account selection.
63
+ const selection = sourceReady
64
+ ? { picked: source }
65
+ : pickBalancedCandidate(candidates);
66
+ if (!selection) {
67
+ const detail = formatNoHealthyAccountError(agent, 'balanced', candidates);
68
+ throw new SessionRecoveryError(`Cannot recover session ${session.shortId} on ${device}; origin ${agent}@${session.version ?? 'unknown'}. ${detail}`);
69
+ }
70
+ const version = selection.picked.version;
71
+ if (session.version === version && supportsNative(agent, version)) {
72
+ return {
73
+ mode: 'native',
74
+ agent,
75
+ version,
76
+ reason: `origin ${agent}@${version} is installed and healthy`,
77
+ };
78
+ }
79
+ return {
80
+ mode: 'continue',
81
+ agent,
82
+ version,
83
+ reason: `${sourceReason(session, candidates)}; continuing with healthy ${agent}@${version}`,
84
+ };
85
+ }
86
+ export async function resolveSessionRecovery(session) {
87
+ const agent = runnableSessionAgent(session);
88
+ return resolveSessionRecoveryFromCandidates(session, await collectRunCandidates(agent));
89
+ }
90
+ /** Stable self-command used by focus, resume, and attach. The owning device runs
91
+ * the recovery resolver above; callers must not native-resume another version's
92
+ * isolated home themselves. */
93
+ export function sessionRecoveryRunArgs(session) {
94
+ return ['run', 'auto', '--resume', session.id, '--interactive'];
95
+ }
@@ -77,7 +77,7 @@ export interface ConflictInfo {
77
77
  * top-level entry add/remove — deep edits to plugin contents won't
78
78
  * trigger auto-resync, run `agents sync` for that.
79
79
  */
80
- export declare const SHIM_SCHEMA_VERSION = 28;
80
+ export declare const SHIM_SCHEMA_VERSION = 29;
81
81
  /**
82
82
  * Generate the full bash shim script for the given agent. The returned string
83
83
  * is written to ~/.agents/shims/{cliCommand} and made executable.
@@ -160,7 +160,7 @@ export declare function removeShim(agent: AgentId): boolean;
160
160
  * the versioned home (installer run with GROK_HOME set, or grok
161
161
  * self-update under the shim).
162
162
  */
163
- export declare const VERSIONED_ALIAS_SCHEMA_VERSION = 14;
163
+ export declare const VERSIONED_ALIAS_SCHEMA_VERSION = 15;
164
164
  /**
165
165
  * Agents whose config directory can be relocated by an environment variable
166
166
  * (the per-agent `managedEnv` block in `generateVersionedAliasScript` below).
package/dist/lib/shims.js CHANGED
@@ -223,7 +223,7 @@ async function promptConflictStrategy(conflictInfos) {
223
223
  // The old dispatcher checked only the global dir, so a pinned grok that
224
224
  // installed into the versioned home fell through to the "not installed"
225
225
  // error.
226
- export const SHIM_SCHEMA_VERSION = 28;
226
+ export const SHIM_SCHEMA_VERSION = 29;
227
227
  /** Internal marker string used to embed the schema version in shim scripts. */
228
228
  const SHIM_VERSION_MARKER = 'agents-shim-version:';
229
229
  function shellQuote(value) {
@@ -299,7 +299,18 @@ export OPENCODE_CONFIG_DIR="$VERSION_DIR/home/.config/opencode"
299
299
  # mcp.json, sessions, skills, hooks). Point it at the versioned home.
300
300
  export KIMI_CODE_HOME="$VERSION_DIR/home/${configDirName}"
301
301
  `
302
- : '';
302
+ : agent === 'muse'
303
+ ? `
304
+ # Muse Code has no MUSE_CONFIG_DIR. It resolves config via XDG:
305
+ # $XDG_CONFIG_HOME/muse (settings, skills, hooks, auth)
306
+ # $XDG_DATA_HOME/muse (sessions, plugins)
307
+ # Pin XDG into the version home so managed runs never walk the adopt-time
308
+ # ~/.config/muse -> version-home symlink — Muse refuses agent-definition
309
+ # sources that are SymlinkOrReparse (exit 1). Same idea as CLAUDE_CONFIG_DIR.
310
+ export XDG_CONFIG_HOME="$VERSION_DIR/home/.config"
311
+ export XDG_DATA_HOME="$VERSION_DIR/home/.local/share"
312
+ `
313
+ : '';
303
314
  const launchArgs = agent === 'codex' ? ` ${codexShimLaunchArgs()}` : '';
304
315
  return `#!/bin/bash
305
316
  # Auto-generated by agents-cli - do not edit
@@ -871,7 +882,7 @@ export function removeShim(agent) {
871
882
  * the versioned home (installer run with GROK_HOME set, or grok
872
883
  * self-update under the shim).
873
884
  */
874
- export const VERSIONED_ALIAS_SCHEMA_VERSION = 14;
885
+ export const VERSIONED_ALIAS_SCHEMA_VERSION = 15;
875
886
  /** Internal marker string used to embed the schema version in versioned alias scripts. */
876
887
  const VERSIONED_ALIAS_VERSION_MARKER = 'agents-versioned-alias-version:';
877
888
  // The version string is interpolated into a generated bash script and into
@@ -898,7 +909,7 @@ function assertSafeVersion(version) {
898
909
  * KEEP IN SYNC with the `managedEnv` switch in `generateVersionedAliasScript`.
899
910
  * The colocated test `shims.isolation-capability.test.ts` enforces this.
900
911
  */
901
- export const CONFIG_ENV_ISOLATED_AGENTS = ['claude', 'codex', 'copilot', 'grok', 'kimi', 'opencode'];
912
+ export const CONFIG_ENV_ISOLATED_AGENTS = ['claude', 'codex', 'copilot', 'grok', 'kimi', 'opencode', 'muse'];
902
913
  /**
903
914
  * Whether an agent supports a clean `--isolated` install — i.e. its config
904
915
  * location can be redirected by an env var so the isolated copy stays fully
@@ -956,7 +967,15 @@ export OPENCODE_CONFIG_DIR="$HOME/.agents/.history/versions/${agent}/${version}/
956
967
  # mcp.json, sessions, skills, hooks). Point direct aliases at the versioned home.
957
968
  export KIMI_CODE_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/${configDirName}"
958
969
  `
959
- : '';
970
+ : agent === 'muse'
971
+ ? `
972
+ # Muse Code: no dedicated config env var. Pin XDG so config/sessions live under
973
+ # the version home as real directories (not via the adopt symlink at
974
+ # ~/.config/muse, which Muse rejects with SymlinkOrReparse).
975
+ export XDG_CONFIG_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/.config"
976
+ export XDG_DATA_HOME="$HOME/.agents/.history/versions/${agent}/${version}/home/.local/share"
977
+ `
978
+ : '';
960
979
  const launchArgs = agent === 'codex' ? ` ${codexShimLaunchArgs()}` : '';
961
980
  // Resolve the binary the same way the main shim does (see generateShimScript).
962
981
  // Grok and Droid do NOT ship into node_modules/.bin — Grok downloads a native
@@ -51,6 +51,7 @@ export declare const loadFork: ModuleLoader;
51
51
  export declare const loadDefaults: ModuleLoader;
52
52
  export declare const loadSet: ModuleLoader;
53
53
  export declare const loadModels: ModuleLoader;
54
+ export declare const loadModes: ModuleLoader;
54
55
  export declare const loadPrune: ModuleLoader;
55
56
  export declare const loadTrash: ModuleLoader;
56
57
  export declare const loadRestore: ModuleLoader;
@@ -29,6 +29,7 @@ export const loadFork = async () => (await import('../../commands/fork.js')).reg
29
29
  export const loadDefaults = async () => (await import('../../commands/defaults.js')).registerDefaultsCommands;
30
30
  export const loadSet = async () => (await import('../../commands/set.js')).registerSetCommand;
31
31
  export const loadModels = async () => (await import('../../commands/models.js')).registerModelsCommand;
32
+ export const loadModes = async () => (await import('../../commands/modes.js')).registerModesCommand;
32
33
  export const loadPrune = async () => (await import('../../commands/prune.js')).registerPruneCommand;
33
34
  export const loadTrash = async () => (await import('../../commands/trash.js')).registerTrashCommands;
34
35
  export const loadRestore = async () => (await import('../../commands/trash.js')).registerRestoreCommand;
@@ -154,6 +155,7 @@ export const COMMAND_LOADERS = {
154
155
  defaults: [loadDefaults],
155
156
  set: [loadSet],
156
157
  models: [loadModels],
158
+ modes: [loadModes],
157
159
  trash: [loadTrash],
158
160
  restore: [loadRestore],
159
161
  doctor: [loadDoctor],
@@ -68,6 +68,11 @@ export interface RunUmbrellaArgs {
68
68
  yes: boolean;
69
69
  /** Secrets passphrase, if available (env var or prompt). Undefined => skip secrets. */
70
70
  passphrase?: string;
71
+ /**
72
+ * Suppress human progress from the reconcile stage (`refresh`). Required so
73
+ * `agents sync --json` / fleet fan-out leave stdout as a single JSON object.
74
+ */
75
+ quiet?: boolean;
71
76
  }
72
77
  /**
73
78
  * Execute the planned stages in order: repos -> secrets -> reconcile.
@@ -18,6 +18,7 @@
18
18
  import { pullRepo } from './git.js';
19
19
  import { getUserAgentsDir, getEnabledExtraRepos } from './state.js';
20
20
  import { listRemoteBundles, pullBundle } from './secrets/sync.js';
21
+ import { SYNC_PASSPHRASE_ENV } from './secrets/sync-passphrase.js';
21
22
  /**
22
23
  * Decide which stages run. Pure — no I/O. Semantics:
23
24
  * bare (no flags) fetch repos, then reconcile
@@ -52,7 +53,7 @@ export function planUmbrellaStages(f) {
52
53
  * reconcile — `agents sync` should make as much current as it can in one pass.
53
54
  */
54
55
  export async function runUmbrellaSync(args) {
55
- const { flags, log, yes, passphrase } = args;
56
+ const { flags, log, yes, passphrase, quiet = false } = args;
56
57
  const plan = planUmbrellaStages(flags);
57
58
  const result = { plan, reconciled: false };
58
59
  if (plan.fetchRepos) {
@@ -79,10 +80,10 @@ export async function runUmbrellaSync(args) {
79
80
  result.secrets = {
80
81
  pulled: 0,
81
82
  skipped: true,
82
- reason: 'no passphrase — set AGENTS_SECRETS_PASSPHRASE or run `agents login` (#366)',
83
+ reason: `no passphrase — set ${SYNC_PASSPHRASE_ENV} or run \`agents login\` (#366)`,
83
84
  errors: [],
84
85
  };
85
- log('secrets: skipped (no passphrase available)');
86
+ log(`secrets: skipped (no passphrase — set ${SYNC_PASSPHRASE_ENV})`);
86
87
  }
87
88
  else {
88
89
  let pulled = 0;
@@ -108,7 +109,7 @@ export async function runUmbrellaSync(args) {
108
109
  }
109
110
  if (plan.reconcile) {
110
111
  const { refresh } = await import('./refresh.js');
111
- await refresh({ skipPrompts: yes });
112
+ await refresh({ skipPrompts: yes, quiet });
112
113
  result.reconciled = true;
113
114
  // Keep already-registered devices' reachability current, and surface newly
114
115
  // appeared tailnet nodes as "pending" for the menu-bar Register/Ignore gate
@@ -114,6 +114,8 @@ export interface TmuxClient {
114
114
  export declare function listClients(socket?: string): Promise<TmuxClient[]>;
115
115
  /** A dead pane's exit status, read from tmux while the pane lingers under remain-on-exit. */
116
116
  export interface PaneExit {
117
+ /** False when the pane/socket no longer exists or tmux could not query it. */
118
+ found: boolean;
117
119
  /** True once the process that ran in the pane has exited (pane is dead). */
118
120
  dead: boolean;
119
121
  /** Exit status of the dead pane's process, when tmux reports it. */
@@ -122,8 +124,8 @@ export interface PaneExit {
122
124
  /**
123
125
  * Read whether a pane's process has exited and, if so, its exit status. Used by
124
126
  * the spawn-wrap path to recover the wrapped agent's exit code after the attach
125
- * client returns. Returns `{ dead: false }` when tmux can't answer (session gone,
126
- * pane missing) so the caller treats an unreadable pane as "still alive / detach".
127
+ * client returns. `found: false` distinguishes a missing pane from a living one;
128
+ * focus must recover the former instead of attaching a retained/stale target.
127
129
  */
128
130
  export declare function paneExitStatus(pane: string, socket?: string): Promise<PaneExit>;
129
131
  /**
@@ -248,8 +248,8 @@ export async function listClients(socket) {
248
248
  /**
249
249
  * Read whether a pane's process has exited and, if so, its exit status. Used by
250
250
  * the spawn-wrap path to recover the wrapped agent's exit code after the attach
251
- * client returns. Returns `{ dead: false }` when tmux can't answer (session gone,
252
- * pane missing) so the caller treats an unreadable pane as "still alive / detach".
251
+ * client returns. `found: false` distinguishes a missing pane from a living one;
252
+ * focus must recover the former instead of attaching a retained/stale target.
253
253
  */
254
254
  export async function paneExitStatus(pane, socket) {
255
255
  let res;
@@ -261,13 +261,13 @@ export async function paneExitStatus(pane, socket) {
261
261
  });
262
262
  }
263
263
  catch {
264
- return { dead: false };
264
+ return { found: false, dead: false };
265
265
  }
266
266
  if (res.code !== 0)
267
- return { dead: false };
267
+ return { found: false, dead: false };
268
268
  const [deadRaw, statusRaw] = res.stdout.trim().split(/\s+/);
269
269
  const status = statusRaw !== undefined && statusRaw !== '' ? parseInt(statusRaw, 10) : undefined;
270
- return { dead: deadRaw === '1', status: Number.isFinite(status) ? status : undefined };
270
+ return { found: true, dead: deadRaw === '1', status: Number.isFinite(status) ? status : undefined };
271
271
  }
272
272
  /**
273
273
  * Bind a per-session hook. Used by the spawn-wrap path to install a `pane-died`
@@ -11,6 +11,7 @@ import type { FeedBroadcastConfig } from './feed-broadcast.js';
11
11
  export type AgentId = 'claude' | 'codex' | 'gemini' | 'cursor' | 'opencode' | 'openclaw' | 'copilot' | 'amp' | 'kiro' | 'goose' | 'antigravity' | 'grok' | 'kimi' | 'droid' | 'hermes' | 'pi' | 'muse';
12
12
  /** How `agents run <agent>` chooses an installed version when none is pinned. */
13
13
  export type RunStrategy = 'pinned' | 'available' | 'balanced';
14
+ export type RunEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max' | 'auto';
14
15
  /**
15
16
  * Reserved `<agent>` keyword for `agents run auto` — full-auto dispatch:
16
17
  * host (14d launch affinity) → harness (best-account headroom, weighted) →
@@ -36,6 +37,7 @@ export interface AgentRunConfig {
36
37
  export interface RunDefaults {
37
38
  mode?: Mode;
38
39
  model?: string;
40
+ effort?: RunEffort;
39
41
  }
40
42
  /** `run:` section in agents.yaml. Agent keys keep strategy; `defaults` stores selector rules. */
41
43
  export type RunConfig = Partial<Record<AgentId, AgentRunConfig>> & {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.22.23",
3
+ "version": "1.22.24",
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",