@withandeo/cli 0.19.8 → 0.19.10

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 (44) hide show
  1. package/README.md +44 -3
  2. package/dist/api.d.ts +27 -0
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +20 -0
  5. package/dist/api.js.map +1 -1
  6. package/dist/artifact.d.ts.map +1 -1
  7. package/dist/artifact.js +26 -0
  8. package/dist/artifact.js.map +1 -1
  9. package/dist/capability-preflight.d.ts.map +1 -1
  10. package/dist/capability-preflight.js +19 -0
  11. package/dist/capability-preflight.js.map +1 -1
  12. package/dist/contracts.d.ts +46 -3
  13. package/dist/contracts.d.ts.map +1 -1
  14. package/dist/documentation.d.ts +13 -3
  15. package/dist/documentation.d.ts.map +1 -1
  16. package/dist/documentation.js +108 -12
  17. package/dist/documentation.js.map +1 -1
  18. package/dist/flagship-contract.d.ts +63 -0
  19. package/dist/flagship-contract.d.ts.map +1 -0
  20. package/dist/flagship-contract.js +117 -0
  21. package/dist/flagship-contract.js.map +1 -0
  22. package/dist/flagship-package.d.ts +3 -0
  23. package/dist/flagship-package.d.ts.map +1 -0
  24. package/dist/flagship-package.js +31 -0
  25. package/dist/flagship-package.js.map +1 -0
  26. package/dist/main.d.ts.map +1 -1
  27. package/dist/main.js +177 -17
  28. package/dist/main.js.map +1 -1
  29. package/dist/public-skill-contract.d.ts +21 -0
  30. package/dist/public-skill-contract.d.ts.map +1 -0
  31. package/dist/public-skill-contract.js +47 -0
  32. package/dist/public-skill-contract.js.map +1 -0
  33. package/dist/workflow-dev.d.ts.map +1 -1
  34. package/dist/workflow-dev.js +2 -0
  35. package/dist/workflow-dev.js.map +1 -1
  36. package/dist/workflow-package.d.ts.map +1 -1
  37. package/dist/workflow-package.js +3 -0
  38. package/dist/workflow-package.js.map +1 -1
  39. package/package.json +1 -1
  40. package/skill/publication.json +3 -0
  41. package/skill/tender-accounts/SKILL.md +3 -1
  42. package/skill/tender-accounts/references/feature-flags.md +90 -0
  43. package/skill/tender-accounts/references/managed-source.md +45 -2
  44. package/skill/tender-accounts/references/skills.md +7 -0
package/dist/main.js CHANGED
@@ -20,6 +20,7 @@ import { initializeStarter } from "./starter.js";
20
20
  import { normalizeAndeoEnvironment } from "./andeo-environment.js";
21
21
  import { DOC_TOPICS, readDocumentation, installDocumentation, installedSkillCheck } from "./documentation.js";
22
22
  import { capabilityPreflight } from "./capability-preflight.js";
23
+ import { flagEnvironment, flagKey, rolloutPercentage, customerIds } from "./flagship-contract.js";
23
24
  const packageMetadata = createRequire(import.meta.url)("../package.json");
24
25
  if (typeof packageMetadata.version !== "string" || packageMetadata.version.length === 0) {
25
26
  throw new Error("Andeo CLI package version is missing.");
@@ -101,6 +102,8 @@ async function execute(topic, options, cwd, runtime) {
101
102
  return { value: await workflowCommand(topic.slice(10), cwd, options, runtime) };
102
103
  if (topic.startsWith("email "))
103
104
  return { value: await emailCommand(topic.slice(6), cwd, options, runtime) };
105
+ if (topic.startsWith("flagship "))
106
+ return { value: await flagshipCommand(topic.slice(9), cwd, options, runtime) };
104
107
  switch (topic) {
105
108
  case "docs": {
106
109
  const name = options.positionals[0];
@@ -225,7 +228,27 @@ async function execute(topic, options, cwd, runtime) {
225
228
  return { value: await sourceChangeCreate(cwd, options, runtime) };
226
229
  case "source merge": {
227
230
  const value = await sourceMerge(cwd, options, runtime);
228
- return { value, exitCode: value.status === "timeout" ? 3 : value.merge && ["awaiting_approval", "needs_attention", "superseded", "authorization_required", "cancelled"].includes(value.merge.stage) ? 2 : 0 };
231
+ return { value, exitCode: value.status === "timeout" ? 3 : value.merge && ["awaiting_approval", "needs_attention", "superseded", "authorization_required", "reconciliation_required", "cancelled"].includes(value.merge.stage) ? 2 : 0 };
232
+ }
233
+ case "source merge status":
234
+ case "source merge resume":
235
+ case "source merge cancel":
236
+ case "source merge take-over": {
237
+ const mergeId = requiredFlag(options, "merge");
238
+ if (!/^smi_[A-Za-z0-9_-]+$/.test(mergeId))
239
+ throw new CliError("argument_invalid", "--merge must be a merge intent ID (smi_...).");
240
+ const key = topic === "source merge take-over" ? requiredFlag(options, "idempotency-key") : undefined;
241
+ if (key && !/^[A-Za-z0-9:_-]{8,128}$/.test(key))
242
+ throw new CliError("argument_invalid", "--idempotency-key must contain 8–128 letters, numbers, colons, underscores or hyphens.");
243
+ const access = await sourceReadAccess(cwd, options, runtime);
244
+ const current = await access.client.sourceMergeStatus(mergeId);
245
+ requireLinkedSourceChange(access.projectId, current.merge.projectId);
246
+ if (current.merge.id !== mergeId || current.merge.repositoryId !== access.repository.id)
247
+ throw new CliError("source_merge_changed", "The returned merge does not match this repository and intent.");
248
+ if (topic === "source merge status")
249
+ return { value: current };
250
+ return { value: topic === "source merge resume" ? await access.client.resumeSourceMergeById(mergeId)
251
+ : key ? await access.client.takeOverSourceMergeById(mergeId, key) : await access.client.cancelSourceMergeById(mergeId) };
229
252
  }
230
253
  case "source change resume": {
231
254
  const access = await sourceReadAccess(cwd, options, runtime);
@@ -249,13 +272,17 @@ async function execute(topic, options, cwd, runtime) {
249
272
  if (topic === "source merge-policy show")
250
273
  return { value: await access.client.sourceMergePolicy(access.repository.id) };
251
274
  const mode = requiredFlag(options, "mode");
252
- if (!["human-review", "agent"].includes(mode))
253
- throw new CliError("source_merge_policy_invalid", "--mode must be human-review or agent.");
275
+ if (!["human-review", "agent", "administrator-authorized"].includes(mode))
276
+ throw new CliError("source_merge_policy_invalid", "--mode must be human-review, agent or administrator-authorized.");
277
+ const principalIds = (options.flags.get("principals") ?? "").split(",").filter(Boolean);
278
+ if (mode === "administrator-authorized" && principalIds.length)
279
+ throw new CliError("source_merge_policy_invalid", "Administrator-authorized mode requires exact per-operation approval; omit --principals.");
254
280
  const executionSeconds = options.flags.has("execution-seconds") ? Number(options.flags.get("execution-seconds")) : undefined;
255
281
  if (executionSeconds !== undefined && (!Number.isSafeInteger(executionSeconds) || executionSeconds < 300 || executionSeconds > 86400))
256
282
  throw new CliError("argument_invalid", "--execution-seconds must be between 300 and 86400.");
257
283
  return { value: await access.client.sourceMergePolicy(access.repository.id, { enabled: mode === "agent",
258
- principalIds: (options.flags.get("principals") ?? "").split(",").filter(Boolean),
284
+ principalIds, validationMode: mode === "administrator-authorized" ? "administrator_authorized" : "native_preview",
285
+ buildAdmission: mode === "administrator-authorized" ? "source_only" : "native",
259
286
  expectedRevision: Number(requiredFlag(options, "expected-revision")), idempotencyKey: requiredFlag(options, "idempotency-key"), executionSeconds }) };
260
287
  }
261
288
  case "source change list":
@@ -1329,9 +1356,34 @@ async function sourceMerge(cwd, options, runtime) {
1329
1356
  throw new CliError("argument_invalid", "--expected-head must be a commit SHA.");
1330
1357
  if (expectedSnapshot && (!changeId || !Number.isSafeInteger(Number(expectedSnapshot)) || Number(expectedSnapshot) < 1))
1331
1358
  throw new CliError("argument_invalid", "--expected-snapshot requires --change and a positive integer.");
1359
+ const expectedBase = options.flags.get("expected-base"), policyRevision = options.flags.get("expected-policy-revision");
1360
+ const validationMode = options.flags.get("validation-mode"), continuation = options.flags.get("continuation");
1361
+ const authorize = options.booleans.has("authorize"), dryRun = options.booleans.has("dry-run"), note = options.flags.get("note");
1362
+ if (expectedBase && !/^[a-f0-9]{40}$/.test(expectedBase))
1363
+ throw new CliError("argument_invalid", "--expected-base must be a commit SHA.");
1364
+ if (policyRevision !== undefined && (!/^\d+$/.test(policyRevision) || !Number.isSafeInteger(Number(policyRevision))))
1365
+ throw new CliError("argument_invalid", "--expected-policy-revision must be a nonnegative integer.");
1366
+ if (validationMode !== undefined && validationMode !== "native_preview" && validationMode !== "administrator_authorized")
1367
+ throw new CliError("argument_invalid", "--validation-mode must be native_preview or administrator_authorized.");
1368
+ if (continuation !== undefined && continuation !== "source_only")
1369
+ throw new CliError("argument_invalid", "--continuation must be source_only.");
1370
+ if (note !== undefined && (note.length < 1 || note.length > 1000))
1371
+ throw new CliError("argument_invalid", "--note must contain 1–1000 characters.");
1372
+ if (validationMode === "administrator_authorized") {
1373
+ if (changeId || expectedSnapshot)
1374
+ throw new CliError("argument_invalid", "Administrator-authorized merges use --head and --title without a native change request.");
1375
+ if (!dryRun && (!authorize || !expectedBase || !expectedHead || policyRevision === undefined))
1376
+ throw new CliError("argument_invalid", "Administrator-authorized merges require --authorize, --expected-base, --expected-head and --expected-policy-revision. Use --dry-run to inspect the exact snapshot first.");
1377
+ }
1378
+ else if (authorize || expectedBase !== undefined || policyRevision !== undefined || continuation !== undefined || note !== undefined) {
1379
+ throw new CliError("argument_invalid", "Administrator authorization flags require --validation-mode administrator_authorized.");
1380
+ }
1332
1381
  const access = await sourceReadAccess(cwd, options, runtime);
1333
1382
  const result = await access.client.sourceMerge(access.repository.id, { headBranch, title, changeId, idempotencyKey,
1334
- expectedHead, expectedSnapshot: expectedSnapshot ? Number(expectedSnapshot) : undefined, dryRun: options.booleans.has("dry-run") });
1383
+ expectedHead, expectedSnapshot: expectedSnapshot ? Number(expectedSnapshot) : undefined, dryRun,
1384
+ expectedBase, expectedPolicyRevision: policyRevision === undefined ? undefined : Number(policyRevision),
1385
+ validationMode, authorize: validationMode === "administrator_authorized" ? authorize : undefined,
1386
+ continuation: validationMode === "administrator_authorized" ? "source_only" : undefined, note });
1335
1387
  if (result.merge) {
1336
1388
  requireLinkedSourceChange(access.projectId, result.merge.projectId);
1337
1389
  if (result.merge.repositoryId !== access.repository.id)
@@ -1341,13 +1393,14 @@ async function sourceMerge(cwd, options, runtime) {
1341
1393
  return result;
1342
1394
  let merge = result.merge;
1343
1395
  for (let poll = 0; poll < Math.ceil(timeout / 5); poll++) {
1344
- if (["deployed", "needs_attention", "publication_not_enabled", "superseded", "authorization_required", "cancelled"].includes(merge.stage))
1396
+ if (merge.terminal || ["merged", "deployed", "needs_attention", "publication_not_enabled", "superseded", "authorization_required", "reconciliation_required", "cancelled"].includes(merge.stage))
1345
1397
  return { ...result, merge };
1346
1398
  if (merge.stage === "awaiting_approval")
1347
1399
  return { ...result, merge };
1348
1400
  await runtime.sleep(Math.min(5000, timeout * 1000 - poll * 5000));
1349
- const current = await access.client.sourceChangeRequest(merge.changeId);
1350
- requireLinkedSourceChange(access.projectId, current.changeRequest.projectId);
1401
+ const current = merge.changeId ? await access.client.sourceChangeRequest(merge.changeId) : await access.client.sourceMergeStatus(merge.id);
1402
+ if ("changeRequest" in current)
1403
+ requireLinkedSourceChange(access.projectId, current.changeRequest.projectId);
1351
1404
  if (!current.merge || current.merge.id !== merge.id)
1352
1405
  throw new CliError("source_merge_changed", "The merge intent changed. Inspect this exact change before continuing.");
1353
1406
  requireLinkedSourceChange(access.projectId, current.merge.projectId);
@@ -1355,7 +1408,10 @@ async function sourceMerge(cwd, options, runtime) {
1355
1408
  throw new CliError("source_merge_changed", "The returned merge belongs to another repository.");
1356
1409
  merge = current.merge;
1357
1410
  }
1358
- return { ...result, status: "timeout", merge: { ...merge, waitTimedOut: true }, next: `andeo source change show --change ${merge.changeId} --json` };
1411
+ if (merge.terminal || ["merged", "deployed", "awaiting_approval", "needs_attention", "publication_not_enabled", "superseded", "authorization_required", "reconciliation_required", "cancelled"].includes(merge.stage))
1412
+ return { ...result, merge };
1413
+ return { ...result, status: "timeout", merge: { ...merge, waitTimedOut: true }, next: merge.changeId
1414
+ ? `andeo source change show --change ${merge.changeId} --json` : `andeo source merge status --merge ${merge.id} --json` };
1359
1415
  }
1360
1416
  async function sourceChangeCreate(cwd, options, runtime) {
1361
1417
  const access = await sourceReadAccess(cwd, options, runtime);
@@ -1834,7 +1890,7 @@ async function linkProject(cwd, options, runtime) {
1834
1890
  }
1835
1891
  async function doctor(cwd, options, runtime) {
1836
1892
  const checks = [];
1837
- checks.push(await installedSkillCheck(runtime.homeDirectory));
1893
+ checks.push(await installedSkillCheck(runtime.homeDirectory, { fetcher: runtime.fetcher, cliVersion: CLI_VERSION }));
1838
1894
  const nodeMajor = Number(process.versions.node.split(".")[0]);
1839
1895
  checks.push({
1840
1896
  name: "node",
@@ -2380,7 +2436,7 @@ async function installSkill(cwd, options, runtime) {
2380
2436
  if (!base) {
2381
2437
  throw new CliError("skill_target_invalid", "--agent must be codex or claude unless --directory is provided.");
2382
2438
  }
2383
- return { status: "installed", agent, ...await installDocumentation(base, options.booleans.has("force"), CLI_VERSION) };
2439
+ return { status: "installed", agent, ...await installDocumentation(base, options.booleans.has("force"), CLI_VERSION, { fetcher: runtime.fetcher, bundled: options.booleans.has("bundled") }) };
2384
2440
  }
2385
2441
  async function readSkill() {
2386
2442
  return await readFile(skillPath(), "utf8");
@@ -2410,7 +2466,82 @@ function exampleConfig() {
2410
2466
  returnPath: "/account",
2411
2467
  };
2412
2468
  }
2469
+ async function flagshipCommand(command, cwd, options, runtime) {
2470
+ let environment;
2471
+ try {
2472
+ environment = flagEnvironment(requiredFlag(options, "env"));
2473
+ }
2474
+ catch {
2475
+ throw new CliError("flagship_environment_invalid", "Use --env development, preview, or production explicitly.");
2476
+ }
2477
+ const context = await optionalContext(cwd);
2478
+ const projectId = options.flags.get("project") ?? context?.link?.projectId;
2479
+ if (!projectId)
2480
+ throw new CliError("project_required", "Link a project or pass --project.");
2481
+ const { client } = await authenticatedClient(cwd, context?.link ?? null, options, runtime);
2482
+ if (command.startsWith("apps "))
2483
+ return client.flagship(projectId, ["apps", ...(command === "apps ensure" ? ["ensure"] : [])], environment, command === "apps ensure" ? { body: {} } : {});
2484
+ const action = command.slice(6), app = options.positionals[0], key = options.positionals[1];
2485
+ const segments = ["flags", app, ...(key ? [flagKey(key)] : [])];
2486
+ if (action === "list" || action === "get")
2487
+ return client.flagship(projectId, segments, environment);
2488
+ segments.push(action);
2489
+ if (action === "changelog")
2490
+ return client.flagship(projectId, segments, environment);
2491
+ if (action === "evaluate") {
2492
+ const context = requiredFlag(options, "context");
2493
+ if (!context.startsWith("customerId="))
2494
+ throw new CliError("flagship_context_invalid", "Use --context customerId=IDENTIFIER.");
2495
+ const [customerId] = customerIds([context.slice(11)]);
2496
+ return client.flagship(projectId, segments, environment, { customerId });
2497
+ }
2498
+ const revision = options.flags.get("expected-revision");
2499
+ // Read before mutation by default. Server rechecks this revision inside serialization.
2500
+ let expectedRevision;
2501
+ if (revision !== undefined) {
2502
+ expectedRevision = Number(revision);
2503
+ if (!/^\d+$/.test(revision) || !Number.isSafeInteger(expectedRevision))
2504
+ throw new CliError("flagship_revision_invalid", "Expected revision must be a nonnegative integer.");
2505
+ }
2506
+ else if (action === "create")
2507
+ expectedRevision = 0;
2508
+ else
2509
+ expectedRevision = (await client.flagship(projectId, segments.slice(0, -1), environment)).revision;
2510
+ const body = { expectedRevision };
2511
+ if (action === "audience") {
2512
+ const all = options.booleans.has("all"), file = options.flags.get("customer-ids-file");
2513
+ if (all === !!file)
2514
+ throw new CliError("flagship_audience_invalid", "Choose --all or --customer-ids-file.");
2515
+ if (all)
2516
+ body.all = true;
2517
+ else {
2518
+ let value;
2519
+ try {
2520
+ value = JSON.parse(new TextDecoder().decode(await readBoundedFile(resolve(cwd, file), 192 * 1024)));
2521
+ }
2522
+ catch {
2523
+ throw new CliError("flagship_audience_invalid", "The customer ID file must contain a bounded JSON array.");
2524
+ }
2525
+ body.customerIds = customerIds(value);
2526
+ }
2527
+ }
2528
+ if (action === "rollout") {
2529
+ if (requiredFlag(options, "to") !== "on" || requiredFlag(options, "by") !== "customerId")
2530
+ throw new CliError("flagship_rollout_invalid", "V1 supports --to on --by customerId.");
2531
+ const percentage = requiredFlag(options, "percentage");
2532
+ if (!/^(?:\d+)(?:\.\d{1,2})?$/.test(percentage))
2533
+ throw new CliError("flagship_percentage_invalid", "Use a percentage from 0 to 100 with at most two decimals.");
2534
+ body.percentage = rolloutPercentage(Number(percentage));
2535
+ }
2536
+ return client.flagship(projectId, segments, environment, { body });
2537
+ }
2413
2538
  function commandTopic(argv) {
2539
+ if (argv[0] === "flagship") {
2540
+ const topic = argv.slice(0, 3).join(" ");
2541
+ if (argv.length < 3 || argv.includes("--help") || argv.includes("-h"))
2542
+ return { topic: "help", values: ["flagship"] };
2543
+ return { topic, values: argv.slice(3) };
2544
+ }
2414
2545
  if (argv.length === 0 || argv[0] === "help" || argv[0] === "--help" || argv[0] === "-h") {
2415
2546
  return { topic: "help", values: argv.slice(1) };
2416
2547
  }
@@ -2425,6 +2556,10 @@ function commandTopic(argv) {
2425
2556
  const child = argv[1];
2426
2557
  if (!child || child.startsWith("--"))
2427
2558
  return { topic: "help", values: [parent] };
2559
+ if (parent === "source" && child === "merge" && argv[2] && !argv[2].startsWith("-")) {
2560
+ const topic = `source merge ${argv[2]}`;
2561
+ return argv.includes("--help") || argv.includes("-h") ? { topic: "help", values: [topic] } : { topic, values: argv.slice(3) };
2562
+ }
2428
2563
  if (parent === "source" && (child === "default" || child === "history" || child === "operation" || child === "change" || child === "policy" || child === "merge-policy" || child === "reconnect" || child === "branch")) {
2429
2564
  const grandchild = argv[2];
2430
2565
  if (!grandchild || grandchild.startsWith("--"))
@@ -2460,7 +2595,7 @@ function parseOptions(values) {
2460
2595
  const booleans = new Set();
2461
2596
  const positionals = [];
2462
2597
  const booleanNames = new Set([
2463
- "json", "token-stdin", "dry-run", "no-wait", "force", "reauthorize",
2598
+ "json", "token-stdin", "dry-run", "no-wait", "force", "reauthorize", "authorize", "bundled",
2464
2599
  "device", "wait", "no-open", "local", "all", "production",
2465
2600
  ]);
2466
2601
  for (let index = 0; index < values.length; index += 1) {
@@ -2490,6 +2625,20 @@ function parseOptions(values) {
2490
2625
  return { flags, booleans, positionals, json: booleans.has("json") };
2491
2626
  }
2492
2627
  function assertAllowedOptions(topic, options) {
2628
+ if (topic.startsWith("flagship ")) {
2629
+ const common = ["cwd", "json", "api-url", "token-stdin", "profile", "project", "env"];
2630
+ const extras = { "flags create": ["expected-revision"], "flags enable": ["expected-revision"], "flags disable": ["expected-revision"],
2631
+ "flags audience": ["expected-revision", "customer-ids-file", "all"], "flags rollout": ["expected-revision", "to", "percentage", "by"], "flags evaluate": ["context"], "flags reconcile": ["expected-revision"] };
2632
+ const sub = topic.slice(9);
2633
+ if (!["apps ensure", "apps list", "flags list", "flags get", "flags create", "flags enable", "flags disable", "flags audience", "flags rollout", "flags evaluate", "flags changelog", "flags reconcile"].includes(sub))
2634
+ throw new CliError("argument_unknown", "Unknown flagship command.");
2635
+ if ([...options.flags.keys(), ...options.booleans].some(k => ![...common, ...(extras[sub] ?? [])].includes(k)))
2636
+ throw new CliError("argument_unknown", "Unsupported flagship option.");
2637
+ const length = sub.startsWith("apps") ? 0 : sub === "flags list" ? 1 : 2;
2638
+ if (options.positionals.length !== length)
2639
+ throw new CliError("argument_required", `Expected ${length} positional arguments for ${topic}.`);
2640
+ return;
2641
+ }
2493
2642
  const common = ["cwd", "json"];
2494
2643
  const authentication = ["api-url", "token-stdin", "profile"];
2495
2644
  const allowed = {
@@ -2521,7 +2670,7 @@ function assertAllowedOptions(topic, options) {
2521
2670
  init: [...common, "name", "slug", "directory", "template", "dry-run"],
2522
2671
  "config example": common,
2523
2672
  "skill print": common,
2524
- "skill install": [...common, "agent", "directory", "force"],
2673
+ "skill install": [...common, "agent", "directory", "force", "bundled"],
2525
2674
  "skills list": [...common, ...authentication],
2526
2675
  "skills get": [...common, ...authentication],
2527
2676
  "skills file": [...common, ...authentication, "path"],
@@ -2588,7 +2737,12 @@ function assertAllowedOptions(topic, options) {
2588
2737
  "source operation status": [...common, ...authentication, "operation"],
2589
2738
  "source operation retry": [...common, ...authentication, "operation", "no-wait", "timeout-seconds"],
2590
2739
  "source change create": [...common, ...authentication, "project", "title", "description", "base", "head"],
2591
- "source merge": [...common, ...authentication, "project", "head", "title", "change", "expected-head", "expected-snapshot", "idempotency-key", "dry-run", "wait", "no-wait", "timeout-seconds"],
2740
+ "source merge": [...common, ...authentication, "project", "head", "title", "change", "expected-head", "expected-snapshot", "idempotency-key", "dry-run", "wait", "no-wait", "timeout-seconds",
2741
+ "expected-base", "expected-policy-revision", "validation-mode", "authorize", "continuation", "note"],
2742
+ "source merge status": [...common, ...authentication, "project", "merge"],
2743
+ "source merge resume": [...common, ...authentication, "project", "merge"],
2744
+ "source merge cancel": [...common, ...authentication, "project", "merge"],
2745
+ "source merge take-over": [...common, ...authentication, "project", "merge", "idempotency-key"],
2592
2746
  "source change resume": [...common, ...authentication, "project", "change"],
2593
2747
  "source change cancel": [...common, ...authentication, "project", "change"],
2594
2748
  "source change take-over": [...common, ...authentication, "project", "change", "idempotency-key"],
@@ -2726,6 +2880,8 @@ function writeOutput(value, options, runtime) {
2726
2880
  runtime.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
2727
2881
  }
2728
2882
  function helpText(topic) {
2883
+ if (topic === "flagship")
2884
+ return "Usage: andeo flagship apps ensure|list --env development|preview|production [--project ID] [--json]\n andeo flagship flags list APP_ID --env ENV\n andeo flagship flags create|get|enable|disable|changelog|reconcile APP_ID KEY --env ENV [--expected-revision N]\n andeo flagship flags audience APP_ID KEY --env ENV --customer-ids-file FILE|--all\n andeo flagship flags rollout APP_ID KEY --env ENV --to on --percentage 5 --by customerId\n andeo flagship flags evaluate APP_ID KEY --env ENV --context customerId=synthetic-customer-a\n\nDefaults off. Audience and percentage edits preserve each other and do not enable a disabled flag. Changes propagate; pending writes must be reconciled.";
2729
2885
  if (topic === "email" || topic?.startsWith("email "))
2730
2886
  return `Usage: andeo email <capabilities|create|list|cursor|wait|read|messages|retire> [options]
2731
2887
 
@@ -2805,11 +2961,15 @@ Account workflow entitlement is required for new starts. Run status is authorita
2805
2961
  "source change": "Usage: andeo source change <create|list|show|close|resume|cancel|take-over> [options]\n\nCreate and inspect exact preview-backed changes, or recover an accepted merge intent.",
2806
2962
  "source change cancel": "Usage: andeo source change cancel --change scr_... [--json]\n\nWithdraw new work for an accepted intent. Already-issued effects require readback; source may already be merged.",
2807
2963
  "source change take-over": "Usage: andeo source change take-over --change scr_... --idempotency-key KEY [--json]\n\nAdministrator only. Records a new bounded authorization for the same exact inputs; then use source change resume.",
2808
- "source merge": "Usage: andeo source merge --head BRANCH --title TITLE --idempotency-key KEY [--dry-run] [--wait] [--timeout-seconds N] [--json]\n andeo source merge --change scr_... --idempotency-key KEY [--wait] [--json]\n\nRequest exact fast-forward landing and follow the saved publication policy. Human review remains the default; agent merging requires an administrator policy and principal grant. Waiting is bounded and never cancels hosted work.\n\nExamples:\n andeo source merge --head feature/copy --title \"Update copy\" --idempotency-key account-copy-v1 --dry-run --json\n andeo source merge --head feature/copy --title \"Update copy\" --idempotency-key account-copy-v1 --wait --json",
2964
+ "source merge": "Usage: andeo source merge --head BRANCH --title TITLE --idempotency-key KEY [--dry-run] [--wait] [--timeout-seconds N] [--json]\n andeo source merge --change scr_... --idempotency-key KEY [--wait] [--json]\n andeo source merge --head BRANCH --title TITLE --validation-mode administrator_authorized --expected-base SHA --expected-head SHA --expected-policy-revision N --authorize --idempotency-key KEY [--continuation source_only] [--note TEXT] [--wait] [--json]\n andeo source merge <status|resume|cancel|take-over> --merge smi_... [options]\n\nRequest exact fast-forward landing. Native review remains the default. Administrator-authorized source-only mode requires saved policy and a current administrator; Andeo does not independently verify checks. Inspect refs and blockers with --dry-run before authorizing. Waiting is bounded and never cancels hosted work.\n\nExamples:\n andeo source merge --head feature/copy --title \"Update copy\" --idempotency-key account-copy-v1 --dry-run --json\n andeo source merge --head feature/copy --title \"Update copy\" --idempotency-key account-copy-v1 --wait --json",
2965
+ "source merge status": "Usage: andeo source merge status --merge smi_... [--json]\n\nRead the retained source, validation, authorization, continuation and cleanup outcomes. Source-only merged is successful completion.",
2966
+ "source merge resume": "Usage: andeo source merge resume --merge smi_... [--json]\n\nResume the same exact eligible intent after revalidating authority. Never substitutes a new source snapshot.",
2967
+ "source merge cancel": "Usage: andeo source merge cancel --merge smi_... [--json]\n\nWithdraw new work. Already-issued effects require readback; cancellation cannot undo a merge.",
2968
+ "source merge take-over": "Usage: andeo source merge take-over --merge smi_... --idempotency-key KEY [--json]\n\nAdministrator only. Take ownership of the retained exact source snapshot before resuming.",
2809
2969
  "source merge-policy": "Usage: andeo source merge-policy <show|set> [options]",
2810
2970
  "source change resume": "Usage: andeo source change resume --change scr_... [--json]\n\nResume the same eligible merge intent or exact failed publication. Revalidates authority and retained lineage; never creates a replacement build.\n\nExample:\n andeo source change resume --change scr_... --json",
2811
2971
  "source merge-policy show": "Usage: andeo source merge-policy show [--json]\n\nRead the saved merge policy and explicit principal grants.",
2812
- "source merge-policy set": "Usage: andeo source merge-policy set --mode human-review|agent --principals PRINCIPAL_IDS --expected-revision N --idempotency-key KEY [--json]\n\nAdministrator only. Grants are comma-separated stable principal IDs. This never changes publication policy.\n\nExample:\n andeo source merge-policy set --mode agent --principals principal_example --expected-revision 0 --idempotency-key merge-policy-v1 --json",
2972
+ "source merge-policy set": "Usage: andeo source merge-policy set --mode human-review|agent|administrator-authorized [--principals PRINCIPAL_IDS] --expected-revision N --idempotency-key KEY [--json]\n\nAdministrator only. Agent grants are comma-separated stable principal IDs. Administrator-authorized mode requires controlled transport, source-only continuation and exact per-operation approval; omit --principals. This never changes publication policy.\n\nExample:\n andeo source merge-policy set --mode agent --principals principal_example --expected-revision 0 --idempotency-key merge-policy-v1 --json",
2813
2973
  "source change create": "Usage: andeo source change create --head BRANCH --title TITLE [--description TEXT] [--base BRANCH] [--json]\n\nOpen a review for the exact protected-base and feature-branch SHAs. Andeo binds checks and approval to this immutable snapshot.",
2814
2974
  "source change list": "Usage: andeo source change list [--state open|closed|all] [--json]\n\nList reviewed source changes for this exact linked project.",
2815
2975
  "source change show": "Usage: andeo source change show --change scr_... [--json]\n\nRead exact snapshot, preview, decision, and landing state.",
@@ -2831,7 +2991,7 @@ Account workflow entitlement is required for new starts. Run status is authorita
2831
2991
  "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.",
2832
2992
  skill: "Usage: andeo skill <print|install> [options]",
2833
2993
  "skill print": "Usage: andeo skill print [--json]",
2834
- "skill install": "Usage: andeo skill install [--agent codex|claude] [--directory PATH] [--force] [--json]\n\nInstall globally for the current user by default: ~/.agents/skills for Codex or ~/.claude/skills for Claude. --directory is an explicit custom or repository-local override.",
2994
+ "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",
2835
2995
  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",
2836
2996
  "skills list": "Usage: andeo skills list [--profile NAME] [--api-url URL] [--json]\n\nList the practice skills available to the authenticated Andeo user.",
2837
2997
  "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",