great-cto 3.70.0 → 3.71.1

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 (37) hide show
  1. package/README.md +9 -1
  2. package/board/.claude-plugin/plugin.json +93 -1
  3. package/board/packages/board/lib/routes.mjs +14 -1
  4. package/board/packages/board/lib/work.mjs +34 -3
  5. package/board/packages/board/public/assets/admin-shell.css +63 -0
  6. package/board/packages/board/public/assets/admin-shell.js +39 -5
  7. package/board/packages/board/public/assets/work-view.js +320 -37
  8. package/board/packages/board/public/index.html +43 -1
  9. package/board/scripts/codex-pipeline.mjs +7 -1
  10. package/board/scripts/lib/claude-exec.mjs +49 -6
  11. package/board/scripts/lib/codex-exec.mjs +42 -18
  12. package/board/scripts/lib/codex-host-state.mjs +18 -2
  13. package/board/scripts/lib/codex-pipeline.mjs +44 -7
  14. package/board/scripts/lib/host-graph-bindings.mjs +135 -0
  15. package/board/scripts/lib/host-graph-events.mjs +157 -0
  16. package/board/scripts/lib/host-graph-journal.mjs +210 -0
  17. package/board/scripts/lib/host-graph-observer.mjs +61 -0
  18. package/board/scripts/lib/host-graph-runtime.mjs +38 -0
  19. package/board/scripts/work-task.mjs +17 -4
  20. package/dist/adapt.js +10 -21
  21. package/dist/codex.js +4 -15
  22. package/dist/companion.js +6 -122
  23. package/dist/installer.js +4 -72
  24. package/dist/main.js +51 -118
  25. package/dist/release-activation.mjs +561 -0
  26. package/dist/release-archive.mjs +165 -0
  27. package/dist/release-trust.mjs +514 -0
  28. package/dist/release-update.mjs +303 -0
  29. package/dist/self-upgrade.js +10 -39
  30. package/dist/update-check.js +1 -0
  31. package/dist/upgrade.js +5 -120
  32. package/package.json +1 -1
  33. package/board/packages/board/fixtures/beads-read-owner.mjs +0 -3
  34. package/board/packages/board/fixtures/fake-bd-controlled.mjs +0 -55
  35. package/board/packages/board/fixtures/fake-bd-fail.sh +0 -9
  36. package/board/packages/board/fixtures/fake-bd-slow.sh +0 -12
  37. package/board/packages/board/fixtures/held-receipt-git.mjs +0 -21
package/dist/codex.js CHANGED
@@ -72,7 +72,6 @@ export function codexInstallPlan({ repoDir, codexOnPath }) {
72
72
  import { existsSync as fsExists, readdirSync, readFileSync as fsRead } from "node:fs";
73
73
  import { homedir as home } from "node:os";
74
74
  import { join as pjoin } from "node:path";
75
- import { execFileSync } from "node:child_process";
76
75
  /** A usable `codex` binary: PATH first, then nvm installs, then the desktop app. Null when none. */
77
76
  export function findCodexBinary(env = process.env, h = home()) {
78
77
  for (const dir of String(env.PATH || "").split(":")) {
@@ -134,18 +133,8 @@ export function upgradeCodexPlugin(opts = {}) {
134
133
  return { status: "skipped", fromVersion: null, toVersion: null, reason: "great_cto is not installed for Codex" };
135
134
  }
136
135
  const from = installedCodexVersion(codexHome);
137
- const bin = opts.bin === undefined ? findCodexBinary() : opts.bin;
138
- if (!bin) {
139
- return { status: "skipped", fromVersion: from, toVersion: from, reason: "codex not found on PATH or under ~/.nvm — run: codex plugin marketplace upgrade great-cto" };
140
- }
141
- try {
142
- execFileSync(bin, ["plugin", "marketplace", "upgrade", "great-cto"], {
143
- stdio: ["ignore", "pipe", "pipe"], timeout: 180_000, env: { ...process.env, CODEX_HOME: codexHome },
144
- });
145
- }
146
- catch (e) {
147
- return { status: "failed", fromVersion: from, toVersion: from, reason: String(e.message).split("\n")[0] };
148
- }
149
- const to = installedCodexVersion(codexHome);
150
- return { status: to && to !== from ? "upgraded" : "already_latest", fromVersion: from, toVersion: to };
136
+ return {
137
+ status: "failed", fromVersion: from, toVersion: from,
138
+ reason: "verified native Codex activation is unsupported until no-fetch runtime and preactivation/recovery admission are proven; marketplace refresh was not invoked",
139
+ };
151
140
  }
package/dist/companion.js CHANGED
@@ -1,25 +1,4 @@
1
- /**
2
- * Companion plugin installer — superpowers + beads.
3
- *
4
- * great_cto requires these two plugins to be present. This module installs
5
- * them automatically during `great-cto init/install` so users get a working
6
- * setup in one command without reading the "Requires:" line in the README.
7
- *
8
- * Install strategy:
9
- * - Clone (depth=1) the latest semver tag from GitHub into
10
- * ~/.claude/plugins/cache/local/<name>/<version>/
11
- * - Enable <name>@local in ~/.claude/settings.json
12
- * - Idempotent — skips if any version is already present in the cache dir
13
- * - Best-effort — never fails the parent install; logs a human-friendly
14
- * hint if git is unavailable or clone fails
15
- */
16
- import { spawnSync, execFileSync } from "node:child_process";
17
- import { existsSync, mkdirSync, readdirSync } from "node:fs";
18
- import { homedir } from "node:os";
19
- import { join } from "node:path";
20
- import { dim, success, log, warn } from "./ui.js";
21
- import { enablePlugin } from "./settings.js";
22
- import { semverDescending } from "./semver.js";
1
+ /** Companion metadata is discoverable; installation requires separate artifact admission. */
23
2
  export const COMPANION_PLUGINS = [
24
3
  {
25
4
  name: "superpowers",
@@ -32,107 +11,12 @@ export const COMPANION_PLUGINS = [
32
11
  repoUrl: "https://github.com/steveyegge/beads.git",
33
12
  },
34
13
  ];
35
- function getPluginCacheDir(name) {
36
- return join(homedir(), ".claude", "plugins", "cache", "local", name);
37
- }
38
- /** Returns true if any version folder already exists in the cache dir. */
39
- function isAlreadyInstalled(name) {
40
- const base = getPluginCacheDir(name);
41
- if (!existsSync(base))
42
- return null;
43
- try {
44
- const versions = readdirSync(base).filter((v) => /\S/.test(v));
45
- if (versions.length === 0)
46
- return null;
47
- // Return the highest version, not filesystem order — matches
48
- // upgrade.ts/installer.ts which both sort before picking.
49
- return versions.sort(semverDescending)[0];
50
- }
51
- catch {
52
- return null;
53
- }
54
- }
55
- /** Detect the latest semver tag from a remote repo without cloning. */
56
- export function detectLatestTag(repoUrl) {
57
- try {
58
- const out = execFileSync("git", ["ls-remote", "--tags", repoUrl], {
59
- encoding: "utf-8",
60
- timeout: 15_000,
61
- stdio: ["ignore", "pipe", "pipe"],
62
- });
63
- const tags = out
64
- .split("\n")
65
- .map((line) => line.match(/refs\/tags\/v?([0-9]+\.[0-9]+\.[0-9]+)(?!\^)/)?.[1])
66
- .filter((t) => !!t)
67
- .sort(semverDescending);
68
- return tags[0] ?? null;
69
- }
70
- catch {
71
- return null;
72
- }
73
- }
74
- /**
75
- * Install a single companion plugin.
76
- * Silent no-op if already present. Best-effort on failure.
77
- */
14
+ /** Third-party snapshots have no publisher admission or managed recovery contract. */
15
+ export const COMPANION_ADMISSION_REQUIRED = "UNSUPPORTED: companion artifact admission and managed activation are not established; no files changed";
16
+ /** Refuse before network, cache inspection, settings enablement or overlay writes. */
78
17
  export function installCompanionPlugin(plugin) {
79
- const { name, pluginKey, repoUrl } = plugin;
80
- // ── already installed? ─────────────────────────────────────────────────
81
- const existing = isAlreadyInstalled(name);
82
- if (existing) {
83
- // Make sure it's enabled in settings even if it was manually placed
84
- enablePlugin(pluginKey);
85
- return { name, status: "already_present", version: existing };
86
- }
87
- // ── git available? ─────────────────────────────────────────────────────
88
- try {
89
- execFileSync("git", ["--version"], { stdio: "ignore", timeout: 5_000 });
90
- }
91
- catch {
92
- const msg = `git not found — install git, then run: npx great-cto install`;
93
- warn(`${name}: ${msg}`);
94
- return { name, status: "skipped", version: "—", reason: msg };
95
- }
96
- // ── resolve version ────────────────────────────────────────────────────
97
- const tag = detectLatestTag(repoUrl);
98
- const version = tag ?? "main";
99
- const ref = tag ? `v${tag}` : "main";
100
- const destDir = join(getPluginCacheDir(name), version);
101
- mkdirSync(getPluginCacheDir(name), { recursive: true });
102
- log(dim(` installing ${name} ${version}…`));
103
- // ── clone ──────────────────────────────────────────────────────────────
104
- const cloneArgs = tag
105
- ? ["clone", "--depth=1", "--branch", ref, repoUrl, destDir]
106
- : ["clone", "--depth=1", repoUrl, destDir];
107
- const result = spawnSync("git", cloneArgs, {
108
- stdio: ["ignore", "pipe", "pipe"],
109
- timeout: 60_000,
110
- });
111
- if (result.status !== 0) {
112
- const stderr = (result.stderr?.toString() ?? "").slice(0, 200);
113
- const msg = `clone failed: ${stderr}`;
114
- warn(`${name}: ${msg}`);
115
- warn(` install manually: claude plugin install github.com/${repoUrl.replace("https://github.com/", "").replace(".git", "")}`);
116
- return { name, status: "skipped", version: "—", reason: msg };
117
- }
118
- // ── enable in settings ─────────────────────────────────────────────────
119
- enablePlugin(pluginKey);
120
- success(`${name} ${version} installed`);
121
- return { name, status: "installed", version };
18
+ return { name: plugin.name, status: "skipped", version: "—", reason: COMPANION_ADMISSION_REQUIRED };
122
19
  }
123
- /**
124
- * Install all companion plugins declared in COMPANION_PLUGINS.
125
- * Returns a summary array. Never throws — best-effort for all.
126
- */
127
20
  export function installAllCompanions() {
128
- return COMPANION_PLUGINS.map((plugin) => {
129
- try {
130
- return installCompanionPlugin(plugin);
131
- }
132
- catch (e) {
133
- const msg = e instanceof Error ? e.message : String(e);
134
- warn(`${plugin.name}: unexpected error — ${msg}`);
135
- return { name: plugin.name, status: "skipped", version: "—", reason: msg };
136
- }
137
- });
21
+ return COMPANION_PLUGINS.map(installCompanionPlugin);
138
22
  }
package/dist/installer.js CHANGED
@@ -1,12 +1,11 @@
1
1
  // Install the great_cto plugin into ~/.claude/plugins/cache/local/great_cto/<version>/.
2
2
  // Uses git clone. Falls back to tarball fetch if git is unavailable.
3
- import { spawnSync, execFileSync } from "node:child_process";
3
+ import { execFileSync } from "node:child_process";
4
4
  import { cpSync } from "node:fs";
5
- import { existsSync, mkdirSync, rmSync, readFileSync, readdirSync } from "node:fs";
5
+ import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
6
6
  import { homedir } from "node:os";
7
7
  import { dirname, join } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
- import { log, success, warn, dim } from "./ui.js";
10
9
  const REPO_URL = "https://github.com/avelikiy/great_cto.git";
11
10
  export function hasGit() {
12
11
  try {
@@ -60,75 +59,8 @@ export function findInstalledVersions() {
60
59
  }
61
60
  }
62
61
  export function install(opts = {}) {
63
- if (!hasGit()) {
64
- throw new Error("git is required to install great_cto. Install git first: https://git-scm.com/downloads");
65
- }
66
- // Resolve version
67
- let version = opts.version;
68
- if (!version) {
69
- const latest = detectLatestVersion();
70
- if (!latest) {
71
- warn("Could not detect latest version from GitHub tags — falling back to main branch.");
72
- version = "main";
73
- }
74
- else {
75
- version = latest;
76
- }
77
- }
78
- const pluginDir = join(getPluginBaseDir(), version);
79
- if (existsSync(pluginDir)) {
80
- const manifest = join(pluginDir, ".claude-plugin", "plugin.json");
81
- const looksValid = existsSync(manifest);
82
- if (looksValid && !opts.force) {
83
- return { installed: false, pluginDir, version, alreadyInstalled: true };
84
- }
85
- // Either corrupted, or --force: wipe and reinstall
86
- if (!looksValid) {
87
- warn(`Previous install at ${pluginDir} looks corrupted — reinstalling.`);
88
- }
89
- rmSync(pluginDir, { recursive: true, force: true });
90
- }
91
- mkdirSync(getPluginBaseDir(), { recursive: true });
92
- log(dim(` cloning ${REPO_URL} into ${pluginDir}`));
93
- const ref = /^[0-9]+\.[0-9]+\.[0-9]+$/.test(version) ? `v${version}` : version;
94
- const result = spawnSync("git", ["clone", "--depth=1", "--branch", ref, REPO_URL, pluginDir], {
95
- stdio: ["ignore", "pipe", "pipe"],
96
- timeout: 120_000,
97
- });
98
- if (result.status !== 0) {
99
- const stderr = result.stderr?.toString() ?? "";
100
- // If branch/tag doesn't exist, try plain clone of main
101
- if (stderr.includes("not found") || stderr.includes("Remote branch")) {
102
- warn(`Tag ${ref} not found — cloning default branch.`);
103
- rmSync(pluginDir, { recursive: true, force: true });
104
- const r2 = spawnSync("git", ["clone", "--depth=1", REPO_URL, pluginDir], {
105
- stdio: ["ignore", "pipe", "pipe"],
106
- timeout: 120_000,
107
- });
108
- if (r2.status !== 0) {
109
- throw new Error(`git clone failed: ${r2.stderr?.toString() ?? "unknown error"}`);
110
- }
111
- // Re-read version from actual plugin.json
112
- version = readPluginVersion(pluginDir) ?? "main";
113
- }
114
- else {
115
- throw new Error(`git clone failed: ${stderr}`);
116
- }
117
- }
118
- // A clone carries sources; the plugin runs on the build. Supply it from this
119
- // CLI's own dist before checking whether the result can run.
120
- const supplied = supplyBuiltDist(pluginDir);
121
- if (supplied != null)
122
- log(dim(` supplied ${supplied} built file(s) into packages/cli/dist/`));
123
- // Sanity check: can it RUN, not merely "did files arrive".
124
- const missing = missingRuntimeParts(pluginDir);
125
- if (missing.length) {
126
- throw new Error(`Install appeared to succeed but the plugin cannot run — missing:\n` +
127
- missing.map((m) => ` - ${m}`).join("\n") +
128
- `\nThis is an install bug, not a configuration problem. Please report it with this list.`);
129
- }
130
- success(`plugin installed at ${pluginDir}`);
131
- return { installed: true, pluginDir, version, alreadyInstalled: false };
62
+ throw new Error("Plugin installation refused: a signed complete runtime and verified managed activation are required. " +
63
+ "Mutable Git/main fallback and supplying dist from the running CLI are disabled.");
132
64
  }
133
65
  /**
134
66
  * The plugin needs BUILT JavaScript, and a git clone does not contain any.
package/dist/main.js CHANGED
@@ -121,6 +121,13 @@ function parseArgs(argv) {
121
121
  args.command = "console";
122
122
  else if (a === "telemetry")
123
123
  args.command = "telemetry";
124
+ else if (a === "--no-telemetry")
125
+ continue;
126
+ else if (a === "distribution") {
127
+ args.command = "distribution";
128
+ args.taskArgs = argv.slice(i + 1);
129
+ break;
130
+ }
124
131
  else if (a === "register")
125
132
  args.command = "register";
126
133
  else if (a === "ci")
@@ -759,8 +766,8 @@ ${bold("Usage:")}
759
766
  npx great-cto run "describe the task" [--host claude-code|codex] [--dir PATH]
760
767
  npx great-cto status [RUN_UUID] [--host codex] [--json]
761
768
  npx great-cto resume [RUN_UUID] [--host codex]
762
- npx great-cto install [options] Same as init
763
- npx great-cto [init] [options] Detect + bootstrap
769
+ npx great-cto install [options] Refused until managed plugin admission is supported
770
+ npx great-cto [init] [options] Plugin activation currently unsupported (ADR-045)
764
771
  npx great-cto board [--port 3141] [--no-open]
765
772
  npx great-cto console [--port 8788] [--bind 0.0.0.0] [--no-open]
766
773
  npx great-cto register [--dir PATH]
@@ -770,8 +777,9 @@ ${bold("Usage:")}
770
777
  npx great-cto task work decisions|approve|verify|complete|metrics|preview|publish ...
771
778
  npx great-cto codex-host doctor|list|start|resume|status ...
772
779
  npx great-cto serve [--port 3142]
773
- npx great-cto upgrade [superpowers|beads] Re-clone companions to latest tag + re-apply overlays
774
- npx great-cto upgrade --self Upgrade the great-cto CLI itself, in place
780
+ npx great-cto upgrade [superpowers|beads] Unsupported pending companion artifact admission
781
+ npx great-cto upgrade --self Verified update for an owned plain npm installation
782
+ npx great-cto distribution status Inspect separately configured verifier toolchain
775
783
  npx great-cto statusline install Record Claude's plan use (5h / weekly %) for the board; keeps your status line
776
784
  npx great-cto statusline uninstall Put your previous status line back
777
785
  npx great-cto uninstall [--yes] Show what great_cto installed; --yes removes it (your data stays)
@@ -807,16 +815,18 @@ ${bold("Register:")}
807
815
  run this if the project doesn't appear in board)
808
816
 
809
817
  ${bold("Upgrade:")}
810
- great-cto upgrade Upgrade superpowers + beads to latest, re-apply critic overlays
811
- great-cto upgrade superpowers Upgrade superpowers only
812
- great-cto upgrade beads Upgrade beads only
813
- great-cto upgrade --self Upgrade the great-cto CLI itself (also: upgrade self)
814
- great-cto upgrade codex Refresh great_cto in Codex (also part of plain upgrade)
818
+ great-cto upgrade Refused: choose a supported explicit update route
819
+ great-cto upgrade superpowers Unsupported until companion runtime admission is available
820
+ great-cto upgrade beads Unsupported until companion runtime admission is available
821
+ great-cto upgrade --self Verify signed artifacts, then update an owned plain npm prefix
822
+ great-cto upgrade codex Unsupported until native no-fetch activation is proven
823
+ great-cto distribution status Inspect operator verifier configuration; no release admission
824
+ great-cto distribution configure ... Register explicit tool paths and digests (see ADR-045)
815
825
  great-cto uninstall Plan: what would be removed, what is kept (changes nothing)
816
826
  great-cto uninstall --yes Remove plugin, agents, commands, board service, caches
817
827
  --purge-data Also move ~/.great_cto aside (reversible, never deleted)
818
828
  --projects Also remove the pre-push hooks init wrote into your projects
819
- ${dim("(Safe to run any time — idempotent if already on latest)")}
829
+ ${dim("(Missing artifact proof preserves the selected installation; no mutable fallback)")}
820
830
 
821
831
  ${bold("CI gate:")}
822
832
  great-cto ci Single-command CI gate (archetype + budget check)
@@ -879,57 +889,8 @@ ${bold("Links:")}
879
889
  `);
880
890
  }
881
891
  async function runInitCodex(args) {
882
- const { codexInstallPlan } = await import("./codex.js");
883
- const { execFileSync, spawnSync } = await import("node:child_process");
884
- log(bold("great-cto · Codex host install"));
885
- log("");
886
- // Is the host actually there? The previous version of this function wrote
887
- // hook scripts and a config fragment without checking, into
888
- // `~/.codex/skills/great_cto/` — a directory a working Codex install does not
889
- // have. It reported success either way.
890
- let codexOnPath = false;
891
- try {
892
- execFileSync("codex", ["--version"], { stdio: "ignore", timeout: 15000 });
893
- codexOnPath = true;
894
- }
895
- catch {
896
- codexOnPath = false;
897
- }
898
- const plan = codexInstallPlan({ repoDir: args.dir, codexOnPath });
899
- if (!plan.ok) {
900
- error(plan.why);
901
- return 1;
902
- }
903
- log(`This registers ${cyan(args.dir)} as a Codex plugin marketplace and installs from it.`);
904
- log("");
905
- log(bold("Carries over:"));
906
- for (const s2 of plan.supported)
907
- log(` ${green("✓")} ${s2}`);
908
- log("");
909
- // Printed on every run, not buried in docs. An install that lists only what it
910
- // gives promises a pipeline this host cannot run.
911
- log(bold("Does NOT carry over on Codex:"));
912
- for (const s2 of plan.notSupported)
913
- log(` ${yellow("—")} ${s2}`);
914
- log("");
915
- if (args.dryRun) {
916
- log(yellow("dry-run: these commands would run."));
917
- for (const c of plan.commands)
918
- log(` ${dim(c)}`);
919
- return 0;
920
- }
921
- for (const c of plan.commands) {
922
- log(dim(` $ ${c}`));
923
- const parts = c.split(" ");
924
- const r = spawnSync(parts[0], parts.slice(1), { stdio: "inherit", timeout: 120000 });
925
- if (r.status !== 0) {
926
- error(`command failed: ${c}`);
927
- return 1;
928
- }
929
- }
930
- log("");
931
- log(`${green("✓")} installed. Verify with: ${cyan("codex plugin list")}`);
932
- return 0;
892
+ error("Codex activation is unsupported until a verified no-fetch local runtime and recovery route are proven. No marketplace or installed plugin was changed.");
893
+ return 1;
933
894
  }
934
895
  async function runInit(args) {
935
896
  // Route to Codex-specific install if --host codex
@@ -1428,40 +1389,11 @@ function installPrePushHook(projectDir) {
1428
1389
  }
1429
1390
  }
1430
1391
  async function runSelfUpgrade() {
1431
- const { performSelfUpgrade, resolveRunningBinaryPath } = await import("./self-upgrade.js");
1432
- const currentVersion = getCliVersion();
1433
- const binaryPath = resolveRunningBinaryPath();
1434
- log(`${bold("great-cto upgrade --self")} — current version ${cyan(currentVersion)}`);
1435
- log(dim(` running binary: ${binaryPath}`));
1436
- log("");
1437
- const result = performSelfUpgrade({ currentVersion, binaryPath });
1438
- if (result.manager === "npx") {
1439
- log(result.message);
1440
- return 0;
1441
- }
1442
- if (result.exitCode !== 0) {
1443
- error(result.message);
1444
- return result.exitCode;
1445
- }
1446
- success(result.message);
1447
- // The CLI is upgraded; the plugin Claude Code actually loads may not be.
1448
- // `npm i -g` never touches the plugin cache, so without this the user is told
1449
- // the upgrade succeeded and keeps running the previous release's hooks and
1450
- // agent prompts with nothing anywhere saying so.
1451
- try {
1452
- const { pluginCacheLagWarning } = await import("./self-upgrade.js");
1453
- const { readdirSync } = await import("node:fs");
1454
- const { join } = await import("node:path");
1455
- const cacheRoot = join(homedir(), ".claude", "plugins", "cache", "local", "great_cto");
1456
- const dirs = readdirSync(cacheRoot);
1457
- const warning = pluginCacheLagWarning(result.newVersion ?? "", dirs);
1458
- if (warning) {
1459
- log("");
1460
- log(dim(warning));
1461
- }
1462
- }
1463
- catch { /* no plugin cache is not a lag — the plugin is simply not installed */ }
1464
- return 0;
1392
+ const { resolveRunningBinaryPath } = await import("./self-upgrade.js");
1393
+ const { runVerifiedSelfUpgrade } = await import("./release-update.mjs");
1394
+ const result = await runVerifiedSelfUpgrade({ currentVersion: getCliVersion(), binaryPath: resolveRunningBinaryPath() });
1395
+ log(JSON.stringify(result));
1396
+ return ["SMOKE_PASSED", "ALREADY_ACTIVE", "ALREADY_CURRENT"].includes(result.state) ? 0 : 1;
1465
1397
  }
1466
1398
  async function runUpgrade(rawArgv, args) {
1467
1399
  // `upgrade --self` / `upgrade self` — upgrade the CLI itself, not companion plugins.
@@ -1490,22 +1422,10 @@ async function runUpgrade(rawArgv, args) {
1490
1422
  else {
1491
1423
  results = await upgradeAll();
1492
1424
  }
1493
- for (const r of results) {
1494
- if (r.status === "upgraded") {
1495
- success(`${r.name} ${r.fromVersion} → ${r.toVersion}`);
1496
- }
1497
- else if (r.status === "already_latest") {
1498
- log(` ${dim(`${r.name} ${r.toVersion} already at latest (overlays re-applied)`)}`);
1499
- }
1500
- else {
1501
- warn(`${r.name} skipped — ${r.reason ?? "unknown reason"}`);
1502
- }
1503
- }
1504
- // Codex keeps its own snapshot of great_cto and never refreshes it; without this
1505
- // step every Codex guard shipped after the first install stays out of reach.
1506
- if (!targetPlugin)
1507
- await reportCodexUpgrade();
1508
- return 0;
1425
+ // Unadmitted companion routes fail before fetching, deleting caches or enabling plugins.
1426
+ for (const r of results)
1427
+ warn(`${r.name}: ${r.reason ?? "UNSUPPORTED: companion artifact admission required"}`);
1428
+ return 1;
1509
1429
  }
1510
1430
  async function reportCodexUpgrade() {
1511
1431
  const { upgradeCodexPlugin } = await import("./codex.js");
@@ -1538,12 +1458,13 @@ async function main() {
1538
1458
  // widen types at every call site.
1539
1459
  const finish = async (code) => {
1540
1460
  try {
1541
- await sendUsagePing({
1542
- cliVersion: getCliVersion(),
1543
- subcommand: args.command,
1544
- exitCode: code,
1545
- durationMs: Date.now() - __tStart,
1546
- });
1461
+ if (!rawArgv.includes("--no-telemetry"))
1462
+ await sendUsagePing({
1463
+ cliVersion: getCliVersion(),
1464
+ subcommand: args.command,
1465
+ exitCode: code,
1466
+ durationMs: Date.now() - __tStart,
1467
+ });
1547
1468
  }
1548
1469
  catch { /* telemetry never affects the exit */ }
1549
1470
  // Update hint / prompt — printed after the command's own output, never
@@ -1561,6 +1482,12 @@ async function main() {
1561
1482
  process.exit(code);
1562
1483
  };
1563
1484
  // `great-cto telemetry <on|off|status|whoami>` — inspect / toggle, never sends.
1485
+ if (args.command === "distribution") {
1486
+ const { runDistributionCommand } = await import("./release-update.mjs");
1487
+ const result = runDistributionCommand(rawArgv.slice(rawArgv.indexOf("distribution") + 1));
1488
+ process.stdout.write(JSON.stringify(result) + "\n");
1489
+ await finish(result.state === "REFUSED" ? 1 : 0);
1490
+ }
1564
1491
  if (args.command === "telemetry") {
1565
1492
  const { exitCode, output } = telemetrySubcommand(args.positional[0]);
1566
1493
  process.stdout.write(output);
@@ -1680,7 +1607,13 @@ async function main() {
1680
1607
  if (args.command === "run" || args.command === "status" || args.command === "resume") {
1681
1608
  const { runDaily } = await import('./run.js');
1682
1609
  const dailyArgs = [...(args.hostInput !== undefined ? ['--host', args.hostInput] : []), ...(args.taskArgs ?? [])];
1683
- await finish(runDaily(args.command, dailyArgs, { cwd: args.dir }));
1610
+ const code = runDaily(args.command, dailyArgs, { cwd: args.dir });
1611
+ if (args.command === 'status') {
1612
+ await new Promise((resolve, reject) => {
1613
+ process.stdout.write('', error => error ? reject(error) : resolve());
1614
+ });
1615
+ }
1616
+ await finish(code);
1684
1617
  }
1685
1618
  if (args.command === "serve") {
1686
1619
  try {