@withandeo/cli 0.19.11 → 0.19.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -18,7 +18,10 @@ import { captureCommand, runProjectCommand } from "./processes.js";
18
18
  import { createDefaultProfileBindings, } from "./profile-bindings.js";
19
19
  import { initializeStarter } from "./starter.js";
20
20
  import { normalizeAndeoEnvironment } from "./andeo-environment.js";
21
- import { DOC_TOPICS, readDocumentation, installDocumentation, installedSkillCheck } from "./documentation.js";
21
+ import { readNetworkSecret } from "./network-secret.js";
22
+ import { cloudAgentClient } from "./cloud-agent.js";
23
+ import { DOC_TOPICS, readDocumentation, installDocumentation, installedSkillCheck, skillDocumentationStatus, syncDocumentation } from "./documentation.js";
24
+ import { CLI_PACKAGE_DIRECTORY, configureAutoUpgrade, runUpdateCommand, upgradeCli } from "./updates.js";
22
25
  import { capabilityPreflight } from "./capability-preflight.js";
23
26
  import { flagEnvironment, flagKey, validateFlagDefinition, flagContext, FLAGSHIP_SCHEMA, FLAG_DELETION_GUIDANCE, FlagCompatibilityError } from "./flagship-contract.js";
24
27
  const packageMetadata = createRequire(import.meta.url)("../package.json");
@@ -38,6 +41,8 @@ const DEFAULT_RUNTIME = {
38
41
  sleep: async (milliseconds) => await new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds)),
39
42
  runProjectCommand,
40
43
  captureCommand,
44
+ updateCommand: runUpdateCommand,
45
+ packageDirectory: CLI_PACKAGE_DIRECTORY,
41
46
  authStore: createDefaultAuthStore(process.env),
42
47
  profileBindings: createDefaultProfileBindings(process.env),
43
48
  openBrowser: openSystemBrowser,
@@ -112,9 +117,21 @@ async function execute(topic, options, cwd, runtime) {
112
117
  return { value: await workflowCommand(topic.slice(10), cwd, options, runtime) };
113
118
  if (topic.startsWith("email "))
114
119
  return { value: await emailCommand(topic.slice(6), cwd, options, runtime) };
120
+ if (topic.startsWith("agents "))
121
+ return { value: await agentsCommand(topic.slice(7), cwd, options, runtime) };
115
122
  if (topic.startsWith("flagship "))
116
123
  return { value: await flagshipCommand(topic.slice(9), cwd, options, runtime) };
117
124
  switch (topic) {
125
+ case "upgrade":
126
+ if (options.positionals.length)
127
+ throw new CliError("argument_invalid", "upgrade does not accept positional arguments.");
128
+ return { value: await upgradeCli(updateRuntime(runtime)) };
129
+ case "config autoupgrade on":
130
+ case "config autoupgrade off":
131
+ case "config autoupgrade status":
132
+ if (options.positionals.length)
133
+ throw new CliError("argument_invalid", "autoupgrade does not accept positional arguments.");
134
+ return { value: await configureAutoUpgrade(topic.split(" ")[2], updateRuntime(runtime)) };
118
135
  case "docs": {
119
136
  const name = options.positionals[0];
120
137
  if (!name)
@@ -144,6 +161,14 @@ async function execute(topic, options, cwd, runtime) {
144
161
  : { raw: await readSkill() };
145
162
  case "skill install":
146
163
  return { value: await installSkill(cwd, options, runtime) };
164
+ case "skill status":
165
+ case "skill sync": {
166
+ if (options.positionals.length)
167
+ throw new CliError("argument_invalid", "skill status/sync do not accept positional arguments.");
168
+ const { agent, base } = skillDestination(cwd, options, runtime);
169
+ const action = topic === "skill sync" ? syncDocumentation : skillDocumentationStatus;
170
+ return { value: { agent, ...await action(base, CLI_VERSION, { fetcher: runtime.fetcher }) } };
171
+ }
147
172
  case "skills list": {
148
173
  if (options.positionals.length)
149
174
  throw new CliError("skills_arguments_invalid", "skills list does not accept positional arguments.");
@@ -173,6 +198,17 @@ async function execute(topic, options, cwd, runtime) {
173
198
  return { value: await preview(cwd, options, runtime, true) };
174
199
  case "auth status":
175
200
  return { value: await authStatus(cwd, options, runtime) };
201
+ case "agent status":
202
+ if (runtime.environment.ANDEO_CLOUD_BOOTSTRAP === undefined)
203
+ throw new CliError("cloud_agent_required", "This command requires Cloud task-run authentication.");
204
+ return { value: await authStatus(cwd, options, runtime) };
205
+ case "agent complete":
206
+ case "agent cancel": {
207
+ const client = await cloudAgentClient(runtime.environment, cwd, runtime.fetcher, { tokenStdin: options.booleans.has("token-stdin"), profile: options.flags.has("profile"), apiUrl: selectedApiUrl(options, null, runtime.environment, runtime.defaultApiUrl) });
208
+ if (!client)
209
+ throw new CliError("cloud_agent_required", "This command requires Cloud task-run authentication.");
210
+ return { value: await client.finish(topic === "agent complete" ? "complete" : "cancel") };
211
+ }
176
212
  case "auth list":
177
213
  return { value: await authList(runtime) };
178
214
  case "auth create":
@@ -351,6 +387,45 @@ async function execute(topic, options, cwd, runtime) {
351
387
  throw new CliError("command_unknown", `Unknown command: ${topic}.`, "tender-accounts --help");
352
388
  }
353
389
  }
390
+ async function agentsCommand(action, cwd, options, runtime) {
391
+ if (!["list", "show", "suspend", "revoke", "resume", "cancel", "complete"].includes(action))
392
+ throw new CliError("argument_unknown", "Unknown agents command.");
393
+ if (runtime.environment.ANDEO_CLOUD_BOOTSTRAP !== undefined || runtime.environment.ANDEO_NETWORK_TOKEN !== undefined
394
+ || runtime.environment.TENDER_ACCOUNTS_TOKEN !== undefined || options.booleans.has("token-stdin"))
395
+ throw new CliError("human_cli_required", "Agent management requires a human administrator login profile.", "andeo auth login");
396
+ const id = options.positionals[0];
397
+ if (id !== undefined && !/^agt_[A-Za-z0-9_-]+$/u.test(id))
398
+ throw new CliError("argument_invalid", "Use an agent ID returned by agents list.");
399
+ let revision;
400
+ if (!["list", "show"].includes(action)) {
401
+ const value = requiredFlag(options, "expected-revision");
402
+ revision = Number(value);
403
+ if (!/^\d+$/u.test(value) || !Number.isSafeInteger(revision))
404
+ throw new CliError("argument_invalid", "Use a nonnegative observed agent revision.");
405
+ }
406
+ const targets = ["grant", "installation", "bootstrap", "run"].filter(kind => options.flags.has(kind));
407
+ if (targets.length > 1 || ["cancel", "complete"].includes(action) && targets[0] !== "run"
408
+ || ["suspend", "resume"].includes(action) && ["bootstrap", "run"].includes(targets[0] ?? ""))
409
+ throw new CliError("argument_invalid", "Choose one target compatible with this lifecycle action.");
410
+ const target = targets.length ? { kind: { grant: "grants", installation: "installations", bootstrap: "bootstraps", run: "runs" }[targets[0]], id: options.flags.get(targets[0]) } : undefined;
411
+ if (target && !/^[A-Za-z0-9_-]{3,180}$/u.test(target.id))
412
+ throw new CliError("argument_invalid", "Use a target ID returned by agents show.");
413
+ const access = await authenticatedClient(cwd, (await optionalContext(cwd))?.link ?? null, options, runtime);
414
+ if (access.source !== "login")
415
+ throw new CliError("human_cli_required", "Agent management requires a human administrator login profile.", "andeo auth login");
416
+ const result = await access.client.agents(id, ["list", "show"].includes(action) ? undefined : action, revision, target);
417
+ if (action === "resume") {
418
+ const response = result;
419
+ if (response.status !== "human_confirmation_required" || typeof response.approvalUrl !== "string")
420
+ throw new CliApiError("invalid_api_response", "Fresh browser confirmation instructions are required.");
421
+ const url = new URL(response.approvalUrl);
422
+ if (url.protocol !== "https:" || url.username || url.password)
423
+ throw new CliApiError("invalid_api_response", "The approval URL must be HTTPS.");
424
+ if (!options.booleans.has("no-open"))
425
+ await runtime.openBrowser(url.href);
426
+ }
427
+ return result;
428
+ }
354
429
  async function emailCommand(action, cwd, options, runtime) {
355
430
  if (options.positionals.length)
356
431
  throw new CliError("email_arguments_invalid", "Use named email command options.");
@@ -1180,7 +1255,11 @@ async function sourcePush(cwd, options, runtime) {
1180
1255
  const remote = options.flags.get("remote") ?? "tender";
1181
1256
  await ensureGitRemote(gitRoot, remote, source.repository.remoteUrl, runtime);
1182
1257
  const currentBranch = (await runtime.captureCommand("git", ["branch", "--show-current"], gitRoot)).stdout;
1183
- const branch = options.flags.get("branch") ?? (currentBranch || source.repository.defaultBranch);
1258
+ const branch = options.flags.get("branch") ?? source.repository.assignedFeatureBranch ?? (currentBranch || source.repository.defaultBranch);
1259
+ if (source.repository.assignedFeatureBranch && branch !== source.repository.assignedFeatureBranch)
1260
+ throw new CliError("agent_branch_scope_mismatch", "This run may push only its assigned feature branch.");
1261
+ if (source.repository.assignedFeatureBranch && source.repository.changeRequestProtection !== "enabled")
1262
+ throw new CliError("agent_source_protection_required", "A human administrator must enable reviewed source protection before agent writes.");
1184
1263
  assertGitBranchName(branch);
1185
1264
  const expectedCurrentSha = options.flags.has("expected-current")
1186
1265
  ? requiredSourceSha(options, "expected-current")
@@ -1429,7 +1508,9 @@ async function sourceChangeCreate(cwd, options, runtime) {
1429
1508
  throw new CliError("source_review_not_enabled", "A merchant administrator must enable reviewed changes for this managed repository first.");
1430
1509
  }
1431
1510
  const baseBranch = options.flags.get("base") ?? access.repository.defaultBranch;
1432
- const headBranch = requiredFlag(options, "head");
1511
+ const headBranch = options.flags.get("head") ?? access.repository.assignedFeatureBranch ?? requiredFlag(options, "head");
1512
+ if (access.repository.assignedFeatureBranch && headBranch !== access.repository.assignedFeatureBranch)
1513
+ throw new CliError("agent_branch_scope_mismatch", "This run may prepare review only for its assigned feature branch.");
1433
1514
  const title = requiredFlag(options, "title");
1434
1515
  assertGitBranchName(baseBranch);
1435
1516
  assertGitBranchName(headBranch);
@@ -1673,6 +1754,9 @@ async function authList(runtime) {
1673
1754
  };
1674
1755
  }
1675
1756
  async function authLogin(cwd, options, runtime) {
1757
+ if (runtime.environment.ANDEO_NETWORK_TOKEN !== undefined || runtime.environment.ANDEO_CLOUD_BOOTSTRAP !== undefined) {
1758
+ assertNoEnvironmentCredentialForProfileManagement(runtime.environment);
1759
+ }
1676
1760
  return await authenticateProfile(cwd, options, runtime, DEFAULT_AUTH_PROFILE, options.booleans.has("force"));
1677
1761
  }
1678
1762
  async function authCreate(cwd, options, runtime) {
@@ -1734,8 +1818,8 @@ async function authenticateProfile(cwd, options, runtime, profile, replaceExisti
1734
1818
  };
1735
1819
  }
1736
1820
  const token = await waitForDeviceToken(oauth, pending, profile, runtime);
1737
- await saveTokenResponse(runtime.authStore, profile, apiUrl, token, oauth, pending);
1738
- return await authenticatedLoginSummary(apiUrl, token, profile, runtime);
1821
+ const stored = await saveTokenResponse(runtime.authStore, profile, apiUrl, token, oauth, pending);
1822
+ return await authenticatedLoginSummary(apiUrl, token, profile, runtime, stored);
1739
1823
  }
1740
1824
  const pkce = createPkcePair();
1741
1825
  const callback = await runtime.waitForLoopbackAuthorization({
@@ -1759,8 +1843,8 @@ async function authenticateProfile(cwd, options, runtime, profile, replaceExisti
1759
1843
  codeVerifier: pkce.verifier,
1760
1844
  redirectUri: callback.redirectUri,
1761
1845
  });
1762
- await saveTokenResponse(runtime.authStore, profile, apiUrl, token, oauth, null);
1763
- return await authenticatedLoginSummary(apiUrl, token, profile, runtime);
1846
+ const stored = await saveTokenResponse(runtime.authStore, profile, apiUrl, token, oauth, null);
1847
+ return await authenticatedLoginSummary(apiUrl, token, profile, runtime, stored);
1764
1848
  }
1765
1849
  async function authActivate(cwd, options, runtime) {
1766
1850
  assertNoEnvironmentCredentialForProfileManagement(runtime.environment);
@@ -1811,11 +1895,19 @@ async function authDelete(_cwd, options, runtime) {
1811
1895
  };
1812
1896
  }
1813
1897
  function assertNoEnvironmentCredentialForProfileManagement(environment) {
1898
+ if (environment.ANDEO_CLOUD_BOOTSTRAP !== undefined)
1899
+ throw new CliError("auth_profile_environment_override", "Cloud agent mode disables human profile management.", "Manage the installation in Andeo and the bootstrap in the Cloud network-secret vault.");
1900
+ if (environment.ANDEO_NETWORK_TOKEN !== undefined) {
1901
+ throw new CliError("auth_profile_environment_override", "ANDEO_NETWORK_TOKEN selects proxy-supplied authentication; local profile management is disabled.", "Manage the network secret in the cloud environment and revoke its credential in Andeo.");
1902
+ }
1814
1903
  if (!environment.TENDER_ACCOUNTS_TOKEN)
1815
1904
  return;
1816
1905
  throw new CliError("auth_profile_environment_override", "TENDER_ACCOUNTS_TOKEN overrides local auth profiles, so profile management is disabled for this command.", "Unset TENDER_ACCOUNTS_TOKEN, then retry the auth profile command.");
1817
1906
  }
1818
1907
  async function authLogout(_cwd, options, runtime) {
1908
+ if (runtime.environment.ANDEO_NETWORK_TOKEN !== undefined || runtime.environment.ANDEO_CLOUD_BOOTSTRAP !== undefined) {
1909
+ assertNoEnvironmentCredentialForProfileManagement(runtime.environment);
1910
+ }
1819
1911
  if (options.booleans.has("all") && options.booleans.has("local")) {
1820
1912
  await runtime.authStore.clearAll();
1821
1913
  await runtime.profileBindings.clearAll();
@@ -2144,6 +2236,16 @@ async function deliveryRetry(cwd, options, runtime) {
2144
2236
  }
2145
2237
  async function authenticatedClient(cwd, link, options, runtime) {
2146
2238
  let apiUrl = selectedApiUrl(options, link, runtime.environment, runtime.defaultApiUrl);
2239
+ const cloud = await cloudAgentClient(runtime.environment, cwd, runtime.fetcher, { tokenStdin: options.booleans.has("token-stdin"), profile: options.flags.has("profile"), apiUrl });
2240
+ if (cloud)
2241
+ return { client: new AgentApiClient(apiUrl, "task-run", cloud.fetcher), apiUrl, source: "cloud-agent", accessTokenExpiresAt: cloud.accessTokenExpiresAt };
2242
+ const networkSecret = readNetworkSecret(runtime.environment, apiUrl);
2243
+ if (networkSecret !== undefined) {
2244
+ if (options.booleans.has("token-stdin") || options.flags.has("profile")) {
2245
+ throw new CliError("network_secret_auth_conflict", "ANDEO_NETWORK_TOKEN cannot be combined with --token-stdin or --profile.");
2246
+ }
2247
+ return { client: new AgentApiClient(apiUrl, networkSecret, runtime.fetcher), apiUrl, source: "network-secret" };
2248
+ }
2147
2249
  const stdin = options.booleans.has("token-stdin") ? runtime.stdin : undefined;
2148
2250
  const explicit = await readOptionalToken(cwd, runtime.environment, stdin);
2149
2251
  if (explicit) {
@@ -2247,13 +2349,23 @@ function authCommandForProfile(profile) {
2247
2349
  }
2248
2350
  async function saveTokenResponse(store, profile, apiUrl, token, oauth, previous, acceptConcurrentRefreshWinner = false) {
2249
2351
  const now = Date.now();
2352
+ const accessDeadline = Date.parse(tokenDeadline(token.expires_at, token.expires_in, now));
2353
+ let sessionDeadline = Date.parse(tokenDeadline(token.session_expires_at, token.refresh_expires_in, now));
2354
+ if (previous?.refreshToken && previous.refreshTokenExpiresAt) {
2355
+ // Rotation cannot extend a human session, including against older servers
2356
+ // that advertise the original full duration again on every refresh.
2357
+ const previousDeadline = Date.parse(previous.refreshTokenExpiresAt);
2358
+ if (Number.isFinite(previousDeadline)) {
2359
+ sessionDeadline = Math.min(previousDeadline, sessionDeadline);
2360
+ }
2361
+ }
2250
2362
  const stored = {
2251
2363
  schema: CLI_AUTH_SCHEMA,
2252
2364
  apiUrl,
2253
2365
  accessToken: token.access_token,
2254
- accessTokenExpiresAt: new Date(now + token.expires_in * 1000).toISOString(),
2366
+ accessTokenExpiresAt: new Date(Math.min(accessDeadline, sessionDeadline)).toISOString(),
2255
2367
  refreshToken: token.refresh_token,
2256
- refreshTokenExpiresAt: new Date(now + token.refresh_expires_in * 1000).toISOString(),
2368
+ refreshTokenExpiresAt: new Date(sessionDeadline).toISOString(),
2257
2369
  ...(previous?.principal ? { principal: previous.principal } : {}),
2258
2370
  };
2259
2371
  if (!await store.compareAndSwap(profile, previous, stored)) {
@@ -2272,6 +2384,21 @@ async function saveTokenResponse(store, profile, apiUrl, token, oauth, previous,
2272
2384
  }
2273
2385
  return stored;
2274
2386
  }
2387
+ function tokenDeadline(absolute, seconds, now) {
2388
+ if (absolute !== undefined) {
2389
+ if (typeof absolute !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(absolute)
2390
+ || !Number.isFinite(Date.parse(absolute)) || new Date(absolute).toISOString() !== absolute) {
2391
+ throw new CliApiError("invalid_api_response", "Andeo returned an invalid credential deadline.");
2392
+ }
2393
+ return absolute;
2394
+ }
2395
+ // Compatibility with APIs released before absolute deadline reporting.
2396
+ if (!Number.isSafeInteger(seconds) || seconds < 0 || !Number.isFinite(now + seconds * 1000)
2397
+ || now + seconds * 1000 > 8.64e15) {
2398
+ throw new CliApiError("invalid_api_response", "Andeo returned an invalid credential lifetime.");
2399
+ }
2400
+ return new Date(now + seconds * 1000).toISOString();
2401
+ }
2275
2402
  async function concurrentRefreshWinner(store, profile, previous) {
2276
2403
  const current = await store.read(profile);
2277
2404
  if (!current
@@ -2320,7 +2447,7 @@ async function waitForDeviceToken(oauth, pending, profile, runtime) {
2320
2447
  await clearStoredProfile(runtime.authStore, profile, pending);
2321
2448
  throw new CliError("device_authorization_expired", "The device authorization expired.", `${authCommandForProfile(profile)} --device --json`);
2322
2449
  }
2323
- async function authenticatedLoginSummary(apiUrl, token, profile, runtime) {
2450
+ async function authenticatedLoginSummary(apiUrl, token, profile, runtime, stored) {
2324
2451
  const principal = await new AgentApiClient(apiUrl, token.access_token, runtime.fetcher).whoami();
2325
2452
  await savePrincipalSummary(runtime.authStore, profile, principal);
2326
2453
  return {
@@ -2329,9 +2456,10 @@ async function authenticatedLoginSummary(apiUrl, token, profile, runtime) {
2329
2456
  apiUrl,
2330
2457
  profile,
2331
2458
  ownerEmail: principal.ownerEmail,
2459
+ ...(principal.ownerPrincipalId ? { ownerPrincipalId: principal.ownerPrincipalId } : {}),
2332
2460
  tenantId: principal.tenantId,
2333
2461
  projects: principal.projects,
2334
- accessTokenExpiresAt: new Date(Date.now() + token.expires_in * 1000).toISOString(),
2462
+ accessTokenExpiresAt: stored.accessTokenExpiresAt,
2335
2463
  storage: runtime.authStore.description(),
2336
2464
  containsCredential: false,
2337
2465
  };
@@ -2434,6 +2562,14 @@ async function optionalContext(cwd) {
2434
2562
  }
2435
2563
  }
2436
2564
  async function installSkill(cwd, options, runtime) {
2565
+ const { agent, base } = skillDestination(cwd, options, runtime);
2566
+ return { status: "installed", agent, ...await installDocumentation(base, options.booleans.has("force"), CLI_VERSION, { fetcher: runtime.fetcher, bundled: options.booleans.has("bundled") }) };
2567
+ }
2568
+ function updateRuntime(runtime) {
2569
+ return { ...runtime, version: CLI_VERSION, runner: runtime.updateCommand, now: Date.now,
2570
+ notify: message => { runtime.stderr.write(`${message}\n`); } };
2571
+ }
2572
+ function skillDestination(cwd, options, runtime) {
2437
2573
  const agent = options.flags.get("agent") ?? "codex";
2438
2574
  const requestedDirectory = options.flags.get("directory");
2439
2575
  const base = requestedDirectory
@@ -2446,7 +2582,7 @@ async function installSkill(cwd, options, runtime) {
2446
2582
  if (!base) {
2447
2583
  throw new CliError("skill_target_invalid", "--agent must be codex or claude unless --directory is provided.");
2448
2584
  }
2449
- return { status: "installed", agent, ...await installDocumentation(base, options.booleans.has("force"), CLI_VERSION, { fetcher: runtime.fetcher, bundled: options.booleans.has("bundled") }) };
2585
+ return { agent, base };
2450
2586
  }
2451
2587
  async function readSkill() {
2452
2588
  return await readFile(skillPath(), "utf8");
@@ -2570,10 +2706,16 @@ function commandTopic(argv) {
2570
2706
  const topic = `preview ${argv[1]}`;
2571
2707
  return argv.includes("--help") || argv.includes("-h") ? { topic: "help", values: [topic] } : { topic, values: argv.slice(2) };
2572
2708
  }
2573
- if (parent === "artifact" || parent === "email" || parent === "auth" || parent === "delivery" || parent === "source" || parent === "vars" || parent === "skill" || parent === "skills" || parent === "config" || parent === "production" || parent === "workflows") {
2709
+ if (parent === "agents" || parent === "agent" || parent === "artifact" || parent === "email" || parent === "auth" || parent === "delivery" || parent === "source" || parent === "vars" || parent === "skill" || parent === "skills" || parent === "config" || parent === "production" || parent === "workflows") {
2574
2710
  const child = argv[1];
2575
2711
  if (!child || child.startsWith("--"))
2576
2712
  return { topic: "help", values: [parent] };
2713
+ if (parent === "config" && child === "autoupgrade") {
2714
+ const action = argv[2];
2715
+ if (!action || action.startsWith("-") || argv.includes("--help") || argv.includes("-h"))
2716
+ return { topic: "help", values: ["config autoupgrade"] };
2717
+ return { topic: `config autoupgrade ${action}`, values: argv.slice(3) };
2718
+ }
2577
2719
  if (parent === "source" && child === "merge" && argv[2] && !argv[2].startsWith("-")) {
2578
2720
  const topic = `source merge ${argv[2]}`;
2579
2721
  return argv.includes("--help") || argv.includes("-h") ? { topic: "help", values: [topic] } : { topic, values: argv.slice(3) };
@@ -2661,6 +2803,10 @@ function assertAllowedOptions(topic, options) {
2661
2803
  const authentication = ["api-url", "token-stdin", "profile"];
2662
2804
  const allowed = {
2663
2805
  "artifact finalize": [...common, "artifact-directory"],
2806
+ upgrade: common,
2807
+ "config autoupgrade on": common,
2808
+ "config autoupgrade off": common,
2809
+ "config autoupgrade status": common,
2664
2810
  docs: common,
2665
2811
  capabilities: [...common, ...authentication, "project"],
2666
2812
  "email capabilities": [...common, ...authentication, "tenant", "project"],
@@ -2690,6 +2836,8 @@ function assertAllowedOptions(topic, options) {
2690
2836
  "config example": common,
2691
2837
  "skill print": common,
2692
2838
  "skill install": [...common, "agent", "directory", "force", "bundled"],
2839
+ "skill status": [...common, "agent", "directory"],
2840
+ "skill sync": [...common, "agent", "directory"],
2693
2841
  "skills list": [...common, ...authentication],
2694
2842
  "skills get": [...common, ...authentication],
2695
2843
  "skills file": [...common, ...authentication, "path"],
@@ -2705,6 +2853,16 @@ function assertAllowedOptions(topic, options) {
2705
2853
  "auth deactivate": common,
2706
2854
  "auth login": [...common, "api-url", "project", "client-name", "timeout-seconds", "device", "wait", "no-open", "force"],
2707
2855
  "auth logout": [...common, "local", "all"],
2856
+ "agent status": [...common, ...authentication],
2857
+ "agent complete": [...common],
2858
+ "agent cancel": [...common],
2859
+ "agents list": [...common, ...authentication],
2860
+ "agents show": [...common, ...authentication],
2861
+ "agents suspend": [...common, ...authentication, "expected-revision", "grant", "installation"],
2862
+ "agents revoke": [...common, ...authentication, "expected-revision", "grant", "installation", "bootstrap", "run"],
2863
+ "agents resume": [...common, ...authentication, "expected-revision", "no-open", "grant", "installation"],
2864
+ "agents cancel": [...common, ...authentication, "expected-revision", "run"],
2865
+ "agents complete": [...common, ...authentication, "expected-revision", "run"],
2708
2866
  link: [...common, ...authentication, "project"],
2709
2867
  doctor: [...common, ...authentication],
2710
2868
  preview: [
@@ -2811,6 +2969,12 @@ function assertAllowedOptions(topic, options) {
2811
2969
  "auth delete": { minimum: 1, maximum: 1 },
2812
2970
  "auth activate": { minimum: 1, maximum: 2 },
2813
2971
  "auth deactivate": { minimum: 0, maximum: 1 },
2972
+ "agents show": { minimum: 1, maximum: 1 },
2973
+ "agents suspend": { minimum: 1, maximum: 1 },
2974
+ "agents revoke": { minimum: 1, maximum: 1 },
2975
+ "agents resume": { minimum: 1, maximum: 1 },
2976
+ "agents cancel": { minimum: 1, maximum: 1 },
2977
+ "agents complete": { minimum: 1, maximum: 1 },
2814
2978
  };
2815
2979
  const limit = positionalLimits[topic] ?? { minimum: 0, maximum: 0 };
2816
2980
  if (options.positionals.length < limit.minimum) {
@@ -2899,6 +3063,26 @@ function writeOutput(value, options, runtime) {
2899
3063
  runtime.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
2900
3064
  }
2901
3065
  function helpText(topic) {
3066
+ if (topic === "agents" || topic?.startsWith("agents "))
3067
+ return `Usage: andeo agents list [--profile HUMAN_PROFILE] [--json]
3068
+ andeo agents show AGENT_ID [--profile HUMAN_PROFILE] [--json]
3069
+ andeo agents <suspend|revoke|resume> AGENT_ID --expected-revision N [--profile HUMAN_PROFILE] [--json]
3070
+ andeo agents <cancel|complete> AGENT_ID --run RUN_ID --expected-revision N [--profile HUMAN_PROFILE] [--json]
3071
+
3072
+ Human administrators inspect and manage persistent agents within their CLI project's ceiling.
3073
+ Use authorization_epoch from agents show as --expected-revision. Suspend and revoke commit immediately.
3074
+ Use --grant ID or --installation ID for one descendant; revoke also accepts --bootstrap ID or --run ID.
3075
+ Resume opens the organization's Persistent agents panel for fresh browser review and confirmation;
3076
+ it reports human_confirmation_required and does not resume authority itself. Use --no-open for instructions.
3077
+ Resuming permits new runs only. Lost or compromised credentials require grant replacement in the panel.
3078
+ Agents are separate from human login profiles; agent task credentials cannot manage grants.`;
3079
+ if (topic === "agent" || topic?.startsWith("agent "))
3080
+ return `Usage: andeo agent <status|complete|cancel> [--json]
3081
+
3082
+ Inspect or end this Cloud task's exact admitted run. Complete and cancel remove local task keys only after Andeo confirms the outcome. A completed, cancelled or expired task cannot re-admit with the same state directory.
3083
+ Requires the Cloud bootstrap placeholder, dedicated admission origin, private task state directory, explicit scope and run duration. Human profiles and legacy tokens cannot be combined with Cloud mode.`;
3084
+ if (topic?.startsWith("config autoupgrade "))
3085
+ topic = "config autoupgrade";
2902
3086
  if (topic === "flagship")
2903
3087
  return `Usage: andeo flagship apps ensure|list --env ENV [--collection NAME] [--project ID] [--json]
2904
3088
  andeo flagship apps get|rename|disconnect|delete|reconcile APP_ID --env ENV [--expected-revision N] [--name NAME] [--confirm EXACT_ID]
@@ -2945,6 +3129,8 @@ Choose background preview routing with preview-service-set --target-project PROJ
2945
3129
  Only non-secret JSON belongs in workflow params/events. Keep credentials in the connected service's native secrets.
2946
3130
  Account workflow entitlement is required for new starts. Run status is authoritative; list results are a timestamped projection.`;
2947
3131
  const topics = {
3132
+ upgrade: "Usage: andeo upgrade [--json]\n\nUpgrade a verified global npm, pnpm or Yarn 1 installation to the latest stable CLI. Explicit upgrades can cross compatibility boundaries. CI, temporary npx and project-local installs must use their package manager. The next invocation uses the new CLI.",
3133
+ "config autoupgrade": "Usage: andeo config autoupgrade <on|off|status> [--json]\n\nGlobal CLI upgrades default on and run after successful commands at most once daily. Before 1.0, only patches within the current minor are automatic; afterward, updates stay in the current major. Off retains daily reminders and independent global skill refresh. Help, version, CI and explicit maintenance commands skip automatic maintenance.",
2948
3134
  vars: "Usage: andeo vars <list|set|unset> --environment preview|production [options]\n\nManage saved non-secret runtime variables. Changes apply on the next delivery.",
2949
3135
  "source reconnect": "Usage: andeo source reconnect <prepare|status|complete|cancel> [options]\n\nMove a legacy managed repository to its tenant namespace by preparing, pushing, and completing.",
2950
3136
  "source reconnect prepare": "Usage: andeo source reconnect prepare --repository SRC_ID --idempotency-key KEY [--project PROJECT_ID] [--json]\n\nPause the old source and prepare its tenant destination. Merchant administrator required.",
@@ -3023,19 +3209,21 @@ Account workflow entitlement is required for new starts. Run status is authorita
3023
3209
  "preview entries": "Usage: andeo preview entries [--json]\n\nList eligible published entry apps and the selected default for this linked app.",
3024
3210
  "preview open": "Usage: andeo preview open --from dly_...|sbl_... [--through PROJECT_ID] [--return-path /account] [--json]\n\nOpen the exact deployed preview without rebuilding or uploading. Private apps open through their published connections. Opening again renews access.",
3025
3211
  "delivery retry": "Usage: andeo delivery retry --delivery dly_... [--reauthorize] [--json]\n\nRetry one requested or failed developer preview idempotently. --reauthorize explicitly authorizes a legacy preview for the same owner and linked project using the current CLI login; it cannot replace another login's recorded grant or publish production. Normal credential refresh needs no flag. A dwf_ production workflow is resumed by a merchant administrator from Activity.",
3026
- skill: "Usage: andeo skill <print|install> [options]",
3212
+ skill: "Usage: andeo skill <print|install|status|sync> [options]",
3213
+ "skill status": "Usage: andeo skill status [--agent codex|claude] [--directory PATH] [--json]\n\nCheck the installed guide against the compatible public revision. Reports local_changes, unmanaged or not_installed without overwriting files. Offline and incompatible checks fail explicitly.",
3214
+ "skill sync": "Usage: andeo skill sync [--agent codex|claude] [--directory PATH] [--json]\n\nRefresh an existing, unchanged Andeo-managed guide. Preserve local edits and unrelated files. Outages and incompatible bundles retain installed guidance. After status=updated, reread SKILL.md and relevant references in active chats. Use skill install for a missing guide.",
3027
3215
  "skill print": "Usage: andeo skill print [--json]",
3028
3216
  "skill install": "Usage: andeo skill install [--agent codex|claude] [--directory PATH] [--force] [--bundled] [--json]\n\nFetch the latest compatible public Andeo guide without login. A fresh install falls back to the bundled guide during an outage; existing installations are preserved. --bundled explicitly selects this CLI's offline guide. --force replaces owned files, preserving unrelated files. Installs globally: ~/.agents/skills for Codex or ~/.claude/skills for Claude. --directory overrides the location.\n\nExamples:\n andeo skill install --agent codex --json\n andeo skill install --agent codex --force --json\n andeo skill install --bundled --directory ./skills --json",
3029
3217
  skills: "Usage: andeo skills <list|get|file> [options]\n\nAuthenticated, session-only access to Andeo practice skills. Content is not installed into the repository.\n\nExamples:\n andeo skills list --json\n andeo skills get recharge-client\n andeo skills file recharge-customer-operations --path references/subscriptions.md",
3030
3218
  "skills list": "Usage: andeo skills list [--profile NAME] [--api-url URL] [--json]\n\nList the practice skills available to the authenticated Andeo user.",
3031
3219
  "skills get": "Usage: andeo skills get SKILL_ID [--profile NAME] [--api-url URL] [--json]\n\nRead one skill entrypoint for the current invocation.\n\nExample:\n andeo skills get guardrailed-design-system",
3032
3220
  "skills file": "Usage: andeo skills file SKILL_ID --path PATH [--profile NAME] [--api-url URL] [--json]\n\nRead one catalog-listed supporting file for the current invocation.\n\nExample:\n andeo skills file recharge-customer-operations --path references/subscriptions.md",
3033
- config: "Usage: andeo config example [--json]",
3221
+ config: "Usage: andeo config example [--json]\n andeo config autoupgrade <on|off|status> [--json]",
3034
3222
  "config example": "Usage: andeo config example [--json]\n\nPrint the committed tender-accounts.json contract.",
3035
3223
  };
3036
3224
  if (topic && topics[topic])
3037
3225
  return topics[topic];
3038
- return `Andeo merchant developer CLI ${CLI_VERSION}\n\nCommands:\n docs Read version-matched offline guides\n capabilities Inspect scoped support, enablement and permissions\n init Create a new Shopify account starter\n auth login Create or refresh the default login\n auth create Create or re-authorize a named login\n auth activate Bind a named login to a directory tree\n auth deactivate Remove an exact directory binding\n auth status Validate one login or scoped credential\n auth list List local login profiles and bindings\n auth delete Revoke one named login\n auth logout Revoke the default or every login\n link Link this checkout to one permitted project\n doctor Check developer readiness\n dev Run the project-owned local dev command\n check Run project validation\n build Package and verify a portable artifact\n upload Retain a build for administrator publication\n preview Build and deploy an exact preview\n preview entries List eligible connected entry apps\n preview open Reopen an exact deployed preview\n source status Inspect Andeo-managed Git and recent builds\n source branches List exact managed branches and default policy\n source compare Compare two managed branches\n source connect Connect this linked project to managed Git\n source reconnect Prepare, inspect, complete or cancel a tenant namespace cutover\n source pull Clone or fast-forward the exact connected repository\n source token Issue a short-lived repository credential\n source push Push the exact commit without persisting a token\n source promote Fast-forward the default after successful preview checks\n source history Run guarded, archived source-history migration\n email Create and reuse merchant email identities\n workflows Inspect and control app-defined background runs\n vars Manage saved per-environment runtime variables\n source policy Inspect or change automatic publication\n tail Stream logs for one exact linked project\n delivery status Read preview workflow state\n delivery retry Retry a failed preview workflow\n skill install Install the Andeo agent skill\n skills list Discover authenticated practice skills\n skills get Read an authenticated practice skill\n skills file Read one authenticated skill resource\n config example Print the project configuration contract\n\nThere is intentionally no production publish command.\nRun andeo <command> --help for examples.`;
3226
+ return `Andeo merchant developer CLI ${CLI_VERSION}\n\nCommands:\n docs Read version-matched offline guides\n capabilities Inspect scoped support, enablement and permissions\n init Create a new Shopify account starter\n auth login Create or refresh the default login\n auth create Create or re-authorize a named login\n auth activate Bind a named login to a directory tree\n auth deactivate Remove an exact directory binding\n auth status Validate one login or scoped credential\n auth list List local login profiles and bindings\n auth delete Revoke one named login\n auth logout Revoke the default or every login\n agents Human administration of persistent agents\n agent Inspect or end this Cloud task's run\n link Link this checkout to one permitted project\n doctor Check developer readiness\n dev Run the project-owned local dev command\n check Run project validation\n build Package and verify a portable artifact\n upload Retain a build for administrator publication\n preview Build and deploy an exact preview\n preview entries List eligible connected entry apps\n preview open Reopen an exact deployed preview\n source status Inspect Andeo-managed Git and recent builds\n source branches List exact managed branches and default policy\n source compare Compare two managed branches\n source connect Connect this linked project to managed Git\n source reconnect Prepare, inspect, complete or cancel a tenant namespace cutover\n source pull Clone or fast-forward the exact connected repository\n source token Issue a short-lived repository credential\n source push Push the exact commit without persisting a token\n source promote Fast-forward the default after successful preview checks\n source history Run guarded, archived source-history migration\n email Create and reuse merchant email identities\n workflows Inspect and control app-defined background runs\n vars Manage saved per-environment runtime variables\n source policy Inspect or change automatic publication\n tail Stream logs for one exact linked project\n delivery status Read preview workflow state\n delivery retry Retry a failed preview workflow\n upgrade Upgrade a verified global CLI installation\n config autoupgrade Configure automatic CLI upgrades\n skill install Install the Andeo agent skill\n skill status Inspect installed guidance freshness\n skill sync Refresh unchanged managed guidance\n skills list Discover authenticated practice skills\n skills get Read an authenticated practice skill\n skills file Read one authenticated skill resource\n config example Print the project configuration contract\n\nThere is intentionally no production publish command.\nRun andeo <command> --help for examples.`;
3039
3227
  }
3040
3228
  function normalizeError(error) {
3041
3229
  if (error instanceof FlagCompatibilityError)