@catalyst-cloud/cli 0.16.33 → 0.16.35
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/CHANGELOG.md +14 -0
- package/README.md +20 -1
- package/bin/postinstall.js +13 -0
- package/dist/agent-repoint.js +241 -0
- package/dist/args.js +25 -1
- package/dist/capabilities.js +2 -0
- package/dist/cli.js +36 -46
- package/dist/codex-attribution.js +685 -0
- package/dist/codex-env.js +222 -0
- package/dist/config.js +4 -0
- package/dist/runner-auto-update.js +5 -3
- package/dist/runner-command.js +5 -3
- package/dist/runner-update.js +62 -16
- package/dist/update-inventory.js +13 -28
- package/dist/update-plugin.js +181 -58
- package/dist/update-skill-source.js +145 -0
- package/dist/update-skill-state.js +175 -0
- package/dist/update-skills.js +247 -48
- package/dist/update.js +62 -51
- package/package.json +2 -1
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +1 -1
- package/skills/catalyst-onboard/references/skill-sources.md +5 -4
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.35
|
|
4
|
+
|
|
5
|
+
On a self-hosted machine set up before 0.16.33, the runner update agent kept running the CLI it was installed with, and that CLI could not update itself. The daily job installed newer versions into its own folder, but the agent never ran them. Installing this release globally (`npm install -g @catalyst-cloud/cli`) now points that agent at the newly installed copy. It only ever moves the agent to a newer catalyst, and keeps the agent's runner folder and PATH. It saves the previous agent file beside it, as `<file>.before-0.16.35`, so you can roll back. On a Mac it waits for an update already in progress to finish. An agent file you cannot change is left alone. Each outcome is written to the runner's `update.log`, and a failed attempt puts the agent back exactly as it was. The install itself never fails because of this. To turn it off, set `CATALYST_NO_AGENT_REPOINT=1`.
|
|
6
|
+
|
|
7
|
+
`catalyst runner update --check` now says when the last update failed or was rolled back, with its time, its reason and the release it was offered, between the image result and the component report. The JSON output carries the same as `lastProblem`.
|
|
8
|
+
|
|
9
|
+
## 0.16.34
|
|
10
|
+
|
|
11
|
+
`catalyst runner update` now tells Catalyst how its previous run went when it reads the approved release. A self-hosted machine that keeps waiting, refusing or rolling back an update shows up without anyone reading its local log. It sends a short report: the outcome, its reason code, the release it runs, the release that run was offered, when it ran and the CLI version. `--check` sends nothing, and nothing beyond that fixed set of words leaves the machine. When a self-hosted Mac cannot re-check its private storage during an update, the update log now names the reason instead of a generic failure.
|
|
12
|
+
|
|
13
|
+
`catalyst update` now puts the Cloud and Dev skills packs and the Cloud Claude plugin at the exact commits the approved release names. Before, a matching version number read as current even from a different commit, and a pack ahead of the approved release stayed ahead. The Cloud plugin moves through Claude Code from the same verified copy, and the CLI no longer copies its bundled skills over an approved installation when it starts.
|
|
14
|
+
|
|
15
|
+
`catalyst codex-env` adds the AI account and the machine name to the telemetry attributes a Codex launch reports, so its usage can be summed per account. `--print` prints the value, `--apply` sets it for apps opened from the Dock on macOS, and `--refresh` fetches the workspace's AI accounts again. `catalyst setup` now installs this for you when Codex is on the machine: a small `codex` wrapper in `~/.zshrc` and `~/.bashrc` for terminal launches, and on macOS a login item that covers Codex opened from the Dock. If the CLI cannot answer, Codex starts exactly as before. `catalyst setup --no-shell-hook` skips both, `catalyst setup --remove-codex-attribution` removes them, and `catalyst status` says whether they are installed.
|
|
16
|
+
|
|
3
17
|
## 0.16.33
|
|
4
18
|
|
|
5
19
|
On a self-hosted machine, `catalyst update` now sees the approved versions of the CLI and the Cloud and Dev skills, and moves them. Before this release it reported "no approved version" for all three on every self-hosted machine and left them where they were. The runner images were not affected.
|
package/README.md
CHANGED
|
@@ -138,6 +138,14 @@ catalyst ready --onboarding --json
|
|
|
138
138
|
|
|
139
139
|
The dry run writes no files. `--only <step>` runs one step and reports its scope separately from full onboarding. Exit 10 means a failed step, 11 means waiting, and 12 means a guard refused the action. The private receipt is `install/last-run.json` under your machine's Catalyst state directory; credentials stay in the login config. A fake HOME reports old services and keeps them intact.
|
|
140
140
|
|
|
141
|
+
### Codex launches name their coding account
|
|
142
|
+
|
|
143
|
+
When Codex is on this computer (a `codex` command on your PATH, or `Codex.app` in `/Applications` or `~/Applications`), setup adds a `codex` shell function to `~/.zshrc` and `~/.bashrc`, between the lines `# >>> catalyst codex attribution >>>` and `# <<< catalyst codex attribution <<<`. It writes to each of those files that exists, and creates one only for your login shell (`$SHELL`), so a Mac that uses only zsh gets no `~/.bashrc`. Bash on macOS reads `~/.bash_profile` rather than `~/.bashrc`, so if you use bash and your `~/.bash_profile` does not already source `~/.bashrc`, add the line `[ -f ~/.bashrc ] && . ~/.bashrc` to it. Each time you run `codex`, the function asks `catalyst codex-env --print` which coding account the Codex home in use (`$CODEX_HOME`, else `~/.codex`) is signed in to, and passes the answer to that one launch as `OTEL_RESOURCE_ATTRIBUTES`. The answer comes from a local cache, so a launch never waits on the network. Running setup again replaces the block in place. Setup leaves a file alone, and says why, when it already defines `codex` as a function or an alias of yours, or when it is a link to a file outside your home folder. A link to a file inside your home folder is followed, and the file keeps its permissions.
|
|
144
|
+
|
|
145
|
+
On a Mac, setup also installs the `dev.catalystcloud.codex-attribution` LaunchAgent. It runs `catalyst codex-env --apply` when you log in, and again whenever Codex signs in to another account in its default home, so apps you start from the Dock or Finder, the Codex desktop app included, carry the same attributes. An app that is already open keeps the environment it started with. It picks up a change the next time it starts. On Linux, setup adds the shell function only. Without Codex, setup adds nothing and says it skipped this. Run `catalyst setup` again after you install Codex to add it.
|
|
146
|
+
|
|
147
|
+
`catalyst status` ends with one line that says whether this is installed, which coding account the Codex home resolves to, and how old the cached account list is. `catalyst setup --no-shell-hook` skips all of it. `catalyst setup --remove-codex-attribution` removes it and runs nothing else. Each file goes back to exactly what it was before the block was added, the LaunchAgent is unloaded and deleted, and the two attributes it set (the coding account and the machine name) are taken back out of the launch environment. Attributes you set there yourself are kept. A fake HOME gets none of this.
|
|
148
|
+
|
|
141
149
|
### Headless setup (CI, image builds, agent VMs)
|
|
142
150
|
|
|
143
151
|
`catalyst onboard --headless` (or `CATALYST_ONBOARD_HEADLESS=1`) is for a machine with no person at it. It assumes every browser approval already happened: the person's key exists, and Linear and GitHub are connected for the workspace and the person. It never prompts, never opens a browser and never starts the device sign-in. Every input comes from a flag, an environment variable or a file:
|
|
@@ -298,6 +306,7 @@ The package pins the contract range `1.x || 2.x`, recorded in `package.json` und
|
|
|
298
306
|
- `~/.config/catalyst-cloud/customer.json`, written with mode `0600`, holding your personal key, who you are, and the CLI path.
|
|
299
307
|
- `~/.config/catalyst-cloud/contract.json`, your account's cached contract.
|
|
300
308
|
- `~/.config/catalyst-cloud/published.json`, the cached answer to "what is the newest release" — no credential in it.
|
|
309
|
+
- The Codex launch hook: one marked block in `~/.zshrc` and `~/.bashrc`, and on a Mac `~/Library/LaunchAgents/dev.catalystcloud.codex-attribution.plist` with its log at `~/.config/catalyst-cloud/codex-attribution.log` (see "Codex launches name their coding account").
|
|
301
310
|
- After `catalyst update` moves the CLI: `~/.config/catalyst-cloud/cli-runtimes/`, one directory per CLI version it installed and `active.json` naming the one your `catalyst` command runs.
|
|
302
311
|
- Only if you start them: `~/.config/catalyst-cloud/replica.db` with its `.pid`, `.writer.lock` and `.writer.state` sidecars, `$XDG_STATE_HOME/catalyst/events/<tenant>/backbone/` (or the home-directory fallback) with bounded daily event segments, and `~/.config/catalyst-cloud/watch-cursor.json`.
|
|
303
312
|
|
|
@@ -305,7 +314,15 @@ The skill files themselves are written by whichever install command you ran, in
|
|
|
305
314
|
|
|
306
315
|
## Updating
|
|
307
316
|
|
|
308
|
-
`catalyst update --check` lists each installed component beside the version your workspace approved
|
|
317
|
+
`catalyst update --check` lists each installed component beside the version your workspace approved and changes nothing. For skills packs and the Cloud plugin, the report also compares `installedCommit` with `approvedCommit`. Matching versions alone do not establish that the installed skills match approval. Add `--json` for the machine-readable report, or `--project <absolute path>` to include a project's SDK. A project's files change only when you run `catalyst update sdk --project <path>`, or after you turn its SDK updates on with `catalyst update policy sdk on --project <path>`.
|
|
318
|
+
|
|
319
|
+
`catalyst update` moves enabled components to the approved release. Owned skills packs move to the exact approved repository commits, including correction of a pack ahead of approval. It verifies existing contents before replacing them, preserves independent skills and local edits, and adds no missing skill names. The Cloud plugin uses the same verified Cloud checkout as a local user-scope marketplace. Run `/reload-plugins` or restart Claude Code after it updates. A missing `claude` executable, unsupported plugin scope or uncertain ownership leaves the plugin in place with a reason. Dev skills move only when the approved release includes a Dev pack; `pack_not_approved` means the installed pack was kept.
|
|
320
|
+
|
|
321
|
+
The new CLI is installed beside the old one, and your `catalyst` command runs it at the same path. A running local replica restarts on it and keeps its synced data; a stopped one stays stopped. CLI, SDK and image updates keep their existing policy against moving to an older version. Ordinary CLI startup never replaces skills with its bundled copy.
|
|
322
|
+
|
|
323
|
+
On a machine checking for releases every 15 minutes (`catalyst runner auto-update on`), that check applies the same component policies. Otherwise components move when you run `catalyst update`. That check moves the runner images to a newer release only at set times of day, 03:00, 11:00, 16:00 and 22:00 in the machine's time zone, so a busy machine is not restarted every time a release is approved. `catalyst update policy windows 06:00,18:00 --time-zone America/Chicago` sets your own times, `catalyst update policy windows default` goes back to these, and `catalyst runner update` or `catalyst update` applies a waiting release at once. Each component is on by default; turn one off with `catalyst update policy <component> off` (for example `cli`), and `catalyst update policy` lists every setting. If you turned the installer's daily update off, the CLI and both skills packs start held, and `catalyst update policy cli on` turns the CLI back on.
|
|
324
|
+
|
|
325
|
+
The installer's daily job runs the same release check, so it moves the CLI and the Cloud skills only to the approved release and keeps your holds. Until the development skills join the release, it refreshes them from their repository.
|
|
309
326
|
|
|
310
327
|
A CLI older than `catalyst update` cannot move itself: install it once with `npm install -g @catalyst-cloud/cli@latest && catalyst login`. The re-login rewrites the CLI path the skills spawn, so they stop running the old bundle. To run one command against the latest publish without installing, use `npx -p @catalyst-cloud/cli@latest catalyst login`. The next `catalyst` run prints a one-line notice:
|
|
311
328
|
|
|
@@ -325,10 +342,12 @@ Remove the skills the way you installed them: `/plugin uninstall catalyst@cataly
|
|
|
325
342
|
|
|
326
343
|
```sh
|
|
327
344
|
catalyst replica stop
|
|
345
|
+
catalyst setup --remove-codex-attribution
|
|
328
346
|
rm -f ~/.config/catalyst-cloud/customer.json ~/.config/catalyst-cloud/contract.json ~/.config/catalyst-cloud/published.json ~/.config/catalyst-cloud/watch-cursor.json
|
|
329
347
|
rm -f ~/.config/catalyst-cloud/replica.db ~/.config/catalyst-cloud/replica.db.pid ~/.config/catalyst-cloud/replica.db.writer.lock ~/.config/catalyst-cloud/replica.db.writer.state
|
|
330
348
|
rm -rf "${XDG_STATE_HOME:-$HOME/.local/state}/catalyst/events"
|
|
331
349
|
rm -rf ~/.config/catalyst-cloud/cli-runtimes
|
|
350
|
+
rm -f ~/.config/catalyst-cloud/codex-accounts.json ~/.config/catalyst-cloud/codex-attribution.log
|
|
332
351
|
npm uninstall -g @catalyst-cloud/cli
|
|
333
352
|
```
|
|
334
353
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// npm's postinstall for @catalyst-cloud/cli (CTC-6975): on a global install, point an older runner
|
|
3
|
+
// update agent at this copy (src/agent-repoint.ts). A local or workspace install does nothing, and
|
|
4
|
+
// nothing here ever fails the install. CATALYST_NO_AGENT_REPOINT=1 turns it off.
|
|
5
|
+
if (process.env.npm_config_global === "true" && process.env.CATALYST_NO_AGENT_REPOINT !== "1") {
|
|
6
|
+
try {
|
|
7
|
+
const { postinstallRepoint } = await import("../dist/agent-repoint.js");
|
|
8
|
+
const line = postinstallRepoint();
|
|
9
|
+
if (line) console.error(line);
|
|
10
|
+
} catch (err) {
|
|
11
|
+
console.error(`catalyst: could not check the runner update agent (${err instanceof Error ? err.message : String(err)}). Run: catalyst runner auto-update on`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// agent-repoint.ts — CTC-6975. A machine set up before 0.16.33 runs its runner update agent from a CLI
|
|
2
|
+
// that can never move itself: those CLIs read no approved component release on a self-hosted machine,
|
|
3
|
+
// so the agent's own copy stays where it was installed, while the daily job installs the newest CLI
|
|
4
|
+
// into its own global prefix. npm runs this from that install (bin/postinstall.js) and points an older
|
|
5
|
+
// agent at the copy updates do move.
|
|
6
|
+
//
|
|
7
|
+
// It only ever moves the agent forward, touches only this CLI's own agent, keeps the agent's runner
|
|
8
|
+
// directory and PATH, and never fails the install. The agent file it replaces is kept beside it, named
|
|
9
|
+
// `<file>.before-<version>`, which neither launchd nor systemd loads: that copy is the rollback.
|
|
10
|
+
// Removing the stale CLI copy is the installer's job.
|
|
11
|
+
import { accessSync, appendFileSync, constants, copyFileSync, existsSync, readFileSync, realpathSync, unlinkSync } from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { compareVersions, exactVersion } from "./approved-release.js";
|
|
15
|
+
import { LEGACY_PACKAGE_NAME, readManifest } from "./config.js";
|
|
16
|
+
import { AUTO_UPDATE_LABEL, defaultRun, enableAutoUpdate, launchAgentPath, scheduledPath, systemdUnitPaths, writeFileAtomic, } from "./runner-auto-update.js";
|
|
17
|
+
import { CLAUDE_PATH_ENV, resolveClaude } from "./update-plugin.js";
|
|
18
|
+
/** Long enough for an in-place update (under a minute on a Mac measured 2026-10-24), well inside the
|
|
19
|
+
* installer's 600-second budget for the whole npm install. */
|
|
20
|
+
export const BUSY_WAIT_MS = 240_000;
|
|
21
|
+
const BUSY_POLL_MS = 5_000;
|
|
22
|
+
const blockingSleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
23
|
+
const unxml = (value) => value.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/&/g, "&");
|
|
24
|
+
/** The agent's command, from a plist this CLI wrote (launchAgentPlist). Anything else is unrecognized. */
|
|
25
|
+
function plistProgram(text) {
|
|
26
|
+
const array = /<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/.exec(text)?.[1];
|
|
27
|
+
if (array === undefined)
|
|
28
|
+
return null;
|
|
29
|
+
const args = [...array.matchAll(/<string>([\s\S]*?)<\/string>/g)].map((m) => unxml(m[1] ?? ""));
|
|
30
|
+
const env = (key) => {
|
|
31
|
+
const m = new RegExp(`<key>${key}</key>\\s*<string>([\\s\\S]*?)</string>`).exec(text);
|
|
32
|
+
return m ? unxml(m[1] ?? "") : null;
|
|
33
|
+
};
|
|
34
|
+
return programOf(args, env("PATH"), env(CLAUDE_PATH_ENV));
|
|
35
|
+
}
|
|
36
|
+
/** systemd's quoted words, as systemdUnits writes them. */
|
|
37
|
+
function unitWords(line) {
|
|
38
|
+
return [...line.matchAll(/"((?:[^"\\]|\\.)*)"/g)].map((m) => (m[1] ?? "").replace(/\\(.)/g, "$1").replace(/%%/g, "%"));
|
|
39
|
+
}
|
|
40
|
+
function unitProgram(text) {
|
|
41
|
+
const lines = text.split("\n");
|
|
42
|
+
const exec = lines.find((l) => l.startsWith("ExecStart="));
|
|
43
|
+
if (exec === undefined)
|
|
44
|
+
return null;
|
|
45
|
+
const envs = lines.filter((l) => l.startsWith("Environment=")).flatMap((l) => unitWords(l));
|
|
46
|
+
const env = (key) => envs.find((e) => e.startsWith(`${key}=`))?.slice(key.length + 1) ?? null;
|
|
47
|
+
return programOf(unitWords(exec), env("PATH"), env(CLAUDE_PATH_ENV));
|
|
48
|
+
}
|
|
49
|
+
function programOf(args, path, claude) {
|
|
50
|
+
const [cli, ...rest] = args;
|
|
51
|
+
if (cli === undefined || !cli.startsWith("/") || rest.length !== 5)
|
|
52
|
+
return null;
|
|
53
|
+
const [a, b, c, d, dir] = rest;
|
|
54
|
+
if (a !== "runner" || b !== "update" || c !== "--auto" || d !== "--dir" || dir === undefined || !dir.startsWith("/"))
|
|
55
|
+
return null;
|
|
56
|
+
return { cli, dir, path, claude };
|
|
57
|
+
}
|
|
58
|
+
const real = (path) => {
|
|
59
|
+
try {
|
|
60
|
+
return realpathSync(path);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const writable = (path) => {
|
|
67
|
+
try {
|
|
68
|
+
accessSync(path, constants.W_OK);
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
/** The version of the catalyst package a command belongs to, or null when it is not one. */
|
|
76
|
+
export function cliVersionAt(command) {
|
|
77
|
+
const resolved = real(command);
|
|
78
|
+
if (resolved === null)
|
|
79
|
+
return null;
|
|
80
|
+
let dir = dirname(resolved);
|
|
81
|
+
for (let i = 0; i < 4; i++) {
|
|
82
|
+
const manifest = join(dir, "package.json");
|
|
83
|
+
if (existsSync(manifest)) {
|
|
84
|
+
try {
|
|
85
|
+
const pkg = JSON.parse(readFileSync(manifest, "utf8"));
|
|
86
|
+
if (pkg.name === "@catalyst-cloud/cli" || pkg.name === LEGACY_PACKAGE_NAME)
|
|
87
|
+
return exactVersion(pkg.version);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
dir = dirname(dir);
|
|
95
|
+
}
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
/** Whether the macOS agent is idle, waiting up to the budget for a running update to finish. */
|
|
99
|
+
function waitIdle(deps) {
|
|
100
|
+
const run = deps.run ?? defaultRun;
|
|
101
|
+
const sleep = deps.sleep ?? blockingSleep;
|
|
102
|
+
const target = `gui/${deps.uid ?? process.getuid?.() ?? 0}/${AUTO_UPDATE_LABEL}`;
|
|
103
|
+
for (let waited = 0;; waited += BUSY_POLL_MS) {
|
|
104
|
+
if (!/^\s*state = running\s*$/m.test(run("launchctl", ["print", target]).stdout))
|
|
105
|
+
return true;
|
|
106
|
+
if (waited >= (deps.busyWaitMs ?? BUSY_WAIT_MS))
|
|
107
|
+
return false;
|
|
108
|
+
sleep(BUSY_POLL_MS);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Put the agent file back and load it again, best effort, then drop the backup. */
|
|
112
|
+
function restore(deps, platform, agent, original, backup) {
|
|
113
|
+
if (!existsSync(agent) || !readFileSync(agent).equals(original)) {
|
|
114
|
+
writeFileAtomic(agent, original.toString("utf8"));
|
|
115
|
+
const run = deps.run ?? defaultRun;
|
|
116
|
+
try {
|
|
117
|
+
if (platform === "darwin")
|
|
118
|
+
run("launchctl", ["bootstrap", `gui/${deps.uid ?? process.getuid?.() ?? 0}`, agent]);
|
|
119
|
+
else
|
|
120
|
+
run("systemctl", ["--user", "daemon-reload"]);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// the file is back; the scheduler picks it up at the next login or reload
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
unlinkSync(backup);
|
|
127
|
+
}
|
|
128
|
+
/** Point an older runner update agent at this install. Never throws. Every outcome for an agent this
|
|
129
|
+
* CLI recognizes is also appended to the agent's own update.log: npm hides a postinstall's output on
|
|
130
|
+
* a global install, so that log is where a person, or a later run, reads what happened. */
|
|
131
|
+
export function repointAgent(deps) {
|
|
132
|
+
const seen = {};
|
|
133
|
+
const result = decide(deps, seen);
|
|
134
|
+
if (seen.dir !== undefined) {
|
|
135
|
+
try {
|
|
136
|
+
appendFileSync(join(seen.dir, "update.log"), `${new Date().toISOString()} ${logLine(result, deps.version)}\n`);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// the agent's log is a courtesy; the outcome is already decided
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
function decide(deps, seen) {
|
|
145
|
+
try {
|
|
146
|
+
const platform = deps.platform ?? process.platform;
|
|
147
|
+
let program;
|
|
148
|
+
let agent;
|
|
149
|
+
if (platform === "darwin") {
|
|
150
|
+
agent = launchAgentPath(deps.home);
|
|
151
|
+
if (!existsSync(agent))
|
|
152
|
+
return { action: "skipped", reason: "no_agent" };
|
|
153
|
+
program = plistProgram(readFileSync(agent, "utf8"));
|
|
154
|
+
}
|
|
155
|
+
else if (platform === "linux") {
|
|
156
|
+
agent = systemdUnitPaths(deps.home).service;
|
|
157
|
+
if (!existsSync(agent))
|
|
158
|
+
return { action: "skipped", reason: "no_agent" };
|
|
159
|
+
program = unitProgram(readFileSync(agent, "utf8"));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
return { action: "skipped", reason: "unsupported_platform" };
|
|
163
|
+
}
|
|
164
|
+
if (program === null)
|
|
165
|
+
return { action: "skipped", reason: "agent_unrecognized" };
|
|
166
|
+
seen.dir = program.dir;
|
|
167
|
+
const from = real(program.cli);
|
|
168
|
+
if (from !== null && from === real(deps.self))
|
|
169
|
+
return { action: "skipped", reason: "already_current" };
|
|
170
|
+
// A program that no longer exists runs nothing, so any working copy is better. One that exists
|
|
171
|
+
// moves only when it is a catalyst CLI older than this one.
|
|
172
|
+
let fromVersion = null;
|
|
173
|
+
if (from !== null) {
|
|
174
|
+
fromVersion = cliVersionAt(program.cli);
|
|
175
|
+
if (fromVersion === null)
|
|
176
|
+
return { action: "skipped", reason: "agent_cli_unrecognized" };
|
|
177
|
+
if (compareVersions(fromVersion, deps.version) >= 0)
|
|
178
|
+
return { action: "skipped", reason: "agent_cli_not_older" };
|
|
179
|
+
}
|
|
180
|
+
const canWrite = deps.canWrite ?? writable;
|
|
181
|
+
const files = platform === "linux" ? [agent, systemdUnitPaths(deps.home).timer].filter((f) => existsSync(f)) : [agent];
|
|
182
|
+
if (![...files, dirname(agent)].every(canWrite))
|
|
183
|
+
return { action: "skipped", reason: "agent_not_writable", agent };
|
|
184
|
+
// launchctl bootout stops a running job, and the daily job starts on the same quarter hour as the
|
|
185
|
+
// agent does: wait for an update in progress to finish rather than cut it off.
|
|
186
|
+
if (platform === "darwin" && !waitIdle(deps))
|
|
187
|
+
return { action: "skipped", reason: "agent_busy" };
|
|
188
|
+
const original = readFileSync(agent);
|
|
189
|
+
const backup = `${agent}.before-${deps.version}`;
|
|
190
|
+
copyFileSync(agent, backup);
|
|
191
|
+
let status;
|
|
192
|
+
try {
|
|
193
|
+
status = enableAutoUpdate({
|
|
194
|
+
platform,
|
|
195
|
+
home: deps.home,
|
|
196
|
+
uid: deps.uid,
|
|
197
|
+
cli: deps.self,
|
|
198
|
+
path: scheduledPath(deps.self, deps.nodeExecutable, program.path ?? deps.env.PATH),
|
|
199
|
+
claude: program.claude ?? resolveClaude(deps.home, deps.env),
|
|
200
|
+
run: deps.run,
|
|
201
|
+
}, program.dir);
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
// A repoint that did not take puts the agent back exactly as it was, and leaves no backup.
|
|
205
|
+
if (status?.state !== "on")
|
|
206
|
+
restore(deps, platform, agent, original, backup);
|
|
207
|
+
}
|
|
208
|
+
if (status.state !== "on")
|
|
209
|
+
return { action: "failed", detail: status.detail };
|
|
210
|
+
return { action: "repointed", from: program.cli, fromVersion, to: deps.self, backup };
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
return { action: "failed", detail: (err instanceof Error ? err.message : String(err)).slice(0, 160) };
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/** One line for the person, or null when there is nothing to say. */
|
|
217
|
+
export function repointLine(result, version) {
|
|
218
|
+
if (result.action === "repointed")
|
|
219
|
+
return `catalyst: the runner update agent now runs ${result.to} (${version}) instead of ${result.from} (${result.fromVersion ?? "missing"}). The previous agent file is kept at ${result.backup}.`;
|
|
220
|
+
if (result.action === "skipped" && result.reason === "agent_not_writable")
|
|
221
|
+
return `catalyst: the runner update agent at ${result.agent} runs an older catalyst, and this install cannot change it. Ask whoever installed it to run: catalyst runner auto-update on`;
|
|
222
|
+
if (result.action === "skipped" && result.reason === "agent_busy")
|
|
223
|
+
return "catalyst: the runner update agent was busy updating, so it keeps its current catalyst until the next install. Or run: catalyst runner auto-update on";
|
|
224
|
+
if (result.action === "failed")
|
|
225
|
+
return `catalyst: could not point the runner update agent at this install (${result.detail}). Run: catalyst runner auto-update on`;
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
/** The update.log line for any outcome: the person's line, or for a quiet skip, its reason. */
|
|
229
|
+
export function logLine(result, version) {
|
|
230
|
+
return repointLine(result, version) ?? `catalyst ${version}: the runner update agent was left as it is (${result.action === "skipped" ? result.reason : result.action}).`;
|
|
231
|
+
}
|
|
232
|
+
/** npm's postinstall, for a global install: this install's own command, version and node. */
|
|
233
|
+
export function postinstallRepoint(env = process.env) {
|
|
234
|
+
const version = readManifest().version;
|
|
235
|
+
const linked = env.npm_config_prefix ? join(env.npm_config_prefix, "bin", "catalyst") : null;
|
|
236
|
+
const self = linked !== null && existsSync(linked) ? linked : fileURLToPath(new URL("../bin/catalyst.js", import.meta.url));
|
|
237
|
+
const home = env.HOME;
|
|
238
|
+
if (!home)
|
|
239
|
+
return null;
|
|
240
|
+
return repointLine(repointAgent({ home, self, version, nodeExecutable: process.execPath, env }), version);
|
|
241
|
+
}
|
package/dist/args.js
CHANGED
|
@@ -91,6 +91,14 @@ export const FLAG_TABLES = {
|
|
|
91
91
|
value: false,
|
|
92
92
|
help: "print setup in plain text, without colour or the Catalyst Cloud mark",
|
|
93
93
|
},
|
|
94
|
+
"no-shell-hook": {
|
|
95
|
+
value: false,
|
|
96
|
+
help: "do not add the Codex launch hooks: the codex function in ~/.zshrc and ~/.bashrc, and on macOS the dev.catalystcloud.codex-attribution LaunchAgent",
|
|
97
|
+
},
|
|
98
|
+
"remove-codex-attribution": {
|
|
99
|
+
value: false,
|
|
100
|
+
help: "remove the Codex launch hooks setup added (each rc file goes back to what it was), unload and delete the LaunchAgent, and take its attributes out of the launch environment; runs nothing else",
|
|
101
|
+
},
|
|
94
102
|
"resume-from": {
|
|
95
103
|
value: true,
|
|
96
104
|
help: "resume at a verified onboarding step",
|
|
@@ -527,6 +535,21 @@ export const FLAG_TABLES = {
|
|
|
527
535
|
work: {
|
|
528
536
|
team: { value: true, help: "available: one team's key (default: every team in the workspace contract)" },
|
|
529
537
|
},
|
|
538
|
+
"codex-env": {
|
|
539
|
+
print: {
|
|
540
|
+
value: false,
|
|
541
|
+
help: "print the OTEL_RESOURCE_ATTRIBUTES value a Codex launch should carry: the attributes already set, plus the coding account's slot and this machine's short name",
|
|
542
|
+
},
|
|
543
|
+
apply: {
|
|
544
|
+
value: false,
|
|
545
|
+
help: "macOS: set that value with launchctl for apps started from now on, the Codex desktop app included, for the default Codex home",
|
|
546
|
+
},
|
|
547
|
+
refresh: {
|
|
548
|
+
value: false,
|
|
549
|
+
help: "fetch this workspace's coding accounts now and update the cache --print and --apply read",
|
|
550
|
+
},
|
|
551
|
+
"codex-home": { value: true, help: "print: the Codex home to read (default: $CODEX_HOME, else ~/.codex)" },
|
|
552
|
+
},
|
|
530
553
|
repo: {
|
|
531
554
|
reason: { value: true, help: "pause: required; resume: optional; 1 to 500 characters after trimming (owner or admin)" },
|
|
532
555
|
write: {
|
|
@@ -584,7 +607,7 @@ export const VERB_USAGE = {
|
|
|
584
607
|
update: "update [--check] [--project <absolute path>] [--dir <runner directory>] | update sdk --project <absolute path> | update policy [<component> on|off] | update policy sdk on|off --project <absolute path> | update policy windows <HH:MM,...>|default [--time-zone <zone>] [--json] (components: runner, supervisor, watchdog, cli, skills-cloud, skills-dev, replica)",
|
|
585
608
|
setup: "setup [onboard options] (sets up Catalyst step by step; same as catalyst onboard) | setup --engine <file> --engine-sha256 <hex> [-- <install options>] (run by install.sh; install.sh --help lists the options)",
|
|
586
609
|
login: "login [--base-url <url>] [--start-replica] (keyless; or --key <personal-key> / CATALYST_CLOUD_TOKEN)",
|
|
587
|
-
onboard: "onboard [--team <ID|key>] [--repo <owner/name>]... [--coding-account <slot>] [--resume-from <step>] [--only <step>] [--local-sync] [--remove-local-data] [--runner|--no-runner] [--no-color] [--yes] [--dry-run] [--json] | onboard --headless [--key-file <path>] [--team ...] [--repo ...]... [--coding-account ...] [--runner yes|no] [--remove-local-data] [--json]",
|
|
610
|
+
onboard: "onboard [--team <ID|key>] [--repo <owner/name>]... [--coding-account <slot>] [--resume-from <step>] [--only <step>] [--local-sync] [--remove-local-data] [--runner|--no-runner] [--no-shell-hook] [--no-color] [--yes] [--dry-run] [--json] | onboard --remove-codex-attribution [--json] | onboard --headless [--key-file <path>] [--team ...] [--repo ...]... [--coding-account ...] [--runner yes|no] [--remove-local-data] [--json]",
|
|
588
611
|
install: "install [--skills-dir <dir>] [--force]",
|
|
589
612
|
status: "status",
|
|
590
613
|
notice: "notice",
|
|
@@ -615,6 +638,7 @@ export const VERB_USAGE = {
|
|
|
615
638
|
capabilities: "capabilities [--json]",
|
|
616
639
|
project: "project <list [--json] | wip-limit <get|set <n>|set default> [--team K] [--json]>",
|
|
617
640
|
lease: "lease <take <ticket> --agent <seat> [--ttl <minutes>] | renew <ticket> (--commit <sha>|--artifact <path or id>|--finding <id>|--pr <number>) [--note <text>] [--agent <seat>] [--generation <n>] | release <ticket> [--agent <seat>] [--generation <n>] | show <ticket>> [--json] (a seat's ticket lease: take, renew with new progress, release, read; needs login)",
|
|
641
|
+
"codex-env": "codex-env --print [--codex-home <dir>] | --apply | --refresh [--json] (the telemetry attributes naming the coding account and machine a Codex launch runs as; only --refresh waits on the network)",
|
|
618
642
|
work: "work available [--team K] [--json] (per team: tickets nobody holds, with the last holder and progress, and open pull requests no live lease covers, drafts included)",
|
|
619
643
|
repo: "repo <status|pause <owner/name> --reason <text>|resume <owner/name> [--reason <text>]|agents-block <path> [--write]|agent-setup <path> [--apply] [--with-check]> [--json] (status: visible workspace repositories; agents-block/agent-setup: local checkout)",
|
|
620
644
|
legacy: "legacy [--remove [--data] [--yes]] [--json]",
|
package/dist/capabilities.js
CHANGED
|
@@ -48,6 +48,8 @@ export const CAPABILITIES = [
|
|
|
48
48
|
{ verb: "var set|import", does: "enter plain environment variable values from this terminal, never echoed", needs: "admin", routes: [], since: "0.13.2" },
|
|
49
49
|
{ verb: "env import", does: "store this computer's values for the names .catalyst/catalyst.toml declares, never printed", needs: "admin", routes: [], since: "0.16.20" },
|
|
50
50
|
{ verb: "accounts", does: "read the workspace's coding accounts and which can take work", needs: "member", routes: [], since: "0.5.0" },
|
|
51
|
+
{ verb: "codex-env", does: "name the coding account and the machine on every Codex launch's telemetry: print the attributes, set them for apps on macOS, or refresh the account cache", needs: "member", routes: [], since: "0.16.33" },
|
|
52
|
+
{ verb: "setup --remove-codex-attribution", does: "take out the Codex launch hooks setup adds: the codex function in the shell rc files, and on macOS the login agent and the attributes it set", needs: "member", routes: [], since: "0.16.34" },
|
|
51
53
|
{ verb: "explain|running|queue|history", does: "read why a ticket runs or waits, what runs now, and what is queued", needs: "member", routes: [], since: "0.1.0" },
|
|
52
54
|
{ verb: "write comment|state|label|create|reaction|attachment|session", does: "act on a ticket as the person, through the agent proxy", needs: "member", routes: [agent("POST", "issue-comment"), agent("POST", "issue-state"), agent("POST", "issue-label"), agent("POST", "issue-create")], since: "0.1.0" },
|
|
53
55
|
{ verb: "ask raise|accept|list", does: "raise a decision to a human, or answer one", needs: "member", routes: [agent("POST", "ask"), agent("POST", "ask-accept")], since: "0.1.0" },
|
package/dist/cli.js
CHANGED
|
@@ -8,9 +8,9 @@ import { createSetupRenderer, setupBanner, } from "./setup-render.js";
|
|
|
8
8
|
import { existsSync, openSync, closeSync } from "node:fs";
|
|
9
9
|
import { ReadStream as TerminalInput } from "node:tty";
|
|
10
10
|
import { Writable } from "node:stream";
|
|
11
|
-
import { parseArgs, positionals, verbHelp } from "./args.js";
|
|
11
|
+
import { flagBool, parseArgs, positionals, verbHelp } from "./args.js";
|
|
12
12
|
import { reportCliVersion } from "./cli-version.js";
|
|
13
|
-
import { CONFIG_MODE, DEFAULT_BASE_URL, LEGACY_PACKAGE_NAME, PACKAGE_NAME, cliPath, configPathFor, contractPathFor, defaultCtx, defaultReplicaDbFor, defaultSkillsDirFor, formatMode,
|
|
13
|
+
import { CONFIG_MODE, DEFAULT_BASE_URL, LEGACY_PACKAGE_NAME, PACKAGE_NAME, cliPath, configPathFor, contractPathFor, defaultCtx, defaultReplicaDbFor, defaultSkillsDirFor, formatMode, loadConfig, modernCliPath, normalizeBaseUrl, readManifest, requireConfig, saveConfig, writeConfig, } from "./config.js";
|
|
14
14
|
import { loadContract, pickPath } from "./contract.js";
|
|
15
15
|
import { CliError, MeError, UsageError } from "./errors.js";
|
|
16
16
|
import { fetchMe } from "./transport.js";
|
|
@@ -50,6 +50,8 @@ import { cmdProject } from "./project.js";
|
|
|
50
50
|
import { cmdRepo } from "./repo.js";
|
|
51
51
|
import { cmdLease, cmdWork } from "./lease.js";
|
|
52
52
|
import { cmdLegacy } from "./legacy.js";
|
|
53
|
+
import { cmdCodexEnv } from "./codex-env.js";
|
|
54
|
+
import { codexAttributionAfterSetup, codexAttributionStatusLine, removeCodexAttribution, } from "./codex-attribution.js";
|
|
53
55
|
import { cmdIdentity } from "./identity.js";
|
|
54
56
|
import { cmdMessages } from "./messages.js";
|
|
55
57
|
import { cmdConnections } from "./connections.js";
|
|
@@ -85,7 +87,8 @@ export function usageText() {
|
|
|
85
87
|
" catalyst login --key <personal-key> [--base-url <url>] (or CATALYST_CLOUD_TOKEN, for a key)",
|
|
86
88
|
" catalyst join ... (deprecated alias of login; removed in the next minor version)",
|
|
87
89
|
" catalyst install [--skills-dir <dir>] [--force] (repair path; your agent's own command installs the skills)",
|
|
88
|
-
" catalyst onboard [--resume-from <step>] [--only <step>] [--runner|--no-runner] [--yes] [--dry-run] [--json]",
|
|
90
|
+
" catalyst onboard [--resume-from <step>] [--only <step>] [--runner|--no-runner] [--no-shell-hook] [--yes] [--dry-run] [--json]",
|
|
91
|
+
" catalyst setup --remove-codex-attribution (take out the Codex launch hook and login agent setup added)",
|
|
89
92
|
` catalyst ${SETUP_USAGE}`,
|
|
90
93
|
" catalyst status | notice | me | ready | accounts",
|
|
91
94
|
" catalyst seat checkout --size big|med --harness claude,codex,opencode [--session-id ID] [--stage STAGE]",
|
|
@@ -119,6 +122,7 @@ export function usageText() {
|
|
|
119
122
|
" catalyst repo <agents-block <path> [--write]|agent-setup <path> [--apply] [--with-check]> (a checkout's AGENTS.md block and portable agent layout; working tree only)",
|
|
120
123
|
" catalyst lease <take <ticket> --agent <seat> [--ttl <minutes>]|renew <ticket> --commit|--artifact|--finding|--pr <ref> [--note <text>]|release <ticket>|show <ticket>> [--json] (a seat's ticket lease; needs login)",
|
|
121
124
|
" catalyst work available [--team K] [--json] (tickets nobody holds and open pull requests no lease covers)",
|
|
125
|
+
" catalyst codex-env --print [--codex-home <dir>] | --apply | --refresh (the telemetry attributes naming the coding account and machine a Codex launch runs as)",
|
|
122
126
|
" catalyst legacy [--remove [--data] [--yes]] (leftovers of the old local Catalyst runtime: list them; remove them only on a yes)",
|
|
123
127
|
" catalyst identity linear <status|options|set> [<linearUserId>] [--json]",
|
|
124
128
|
" catalyst messages read --ticket CTC-N [--since SEQUENCE] [--json] (messages sent to a ticket on your teams)",
|
|
@@ -183,10 +187,7 @@ function migrateLegacyCliPath(ctx) {
|
|
|
183
187
|
// Best effort: the old launcher still works, it only prints the notice.
|
|
184
188
|
}
|
|
185
189
|
}
|
|
186
|
-
/**
|
|
187
|
-
* The Tier-2 update path: when the bundle on disk is newer than the version the config last
|
|
188
|
-
* recorded, refresh the copied skills FIRST and record the new version only once that succeeded.
|
|
189
|
-
*/
|
|
190
|
+
/** Announce a new CLI bundle without changing approved skills. */
|
|
190
191
|
function maybePrintUpdateNotice(args, ctx) {
|
|
191
192
|
const say = HUMAN_VERBS.has(args.command ?? "") ? ctx.stdout : ctx.stderr;
|
|
192
193
|
let cfg;
|
|
@@ -199,46 +200,16 @@ function maybePrintUpdateNotice(args, ctx) {
|
|
|
199
200
|
if (!cfg)
|
|
200
201
|
return;
|
|
201
202
|
const manifest = readManifest();
|
|
202
|
-
if (cfg.lastSkillBundleVersion === manifest.version)
|
|
203
|
-
return;
|
|
204
|
-
// CTC-6741: a newer CLI selected by `catalyst update` records its own bundle here, and the older
|
|
205
|
-
// npm-installed one still runs for `catalyst runtime`. Copied skills only ever move forward.
|
|
206
|
-
if (cfg.lastSkillBundleVersion && semverOlder(manifest.version, cfg.lastSkillBundleVersion))
|
|
207
|
-
return;
|
|
208
203
|
const previous = cfg.lastSkillBundleVersion;
|
|
209
|
-
if (previous)
|
|
210
|
-
const entry = parseChangelogEntry(readChangelog(), manifest.version);
|
|
211
|
-
say(updateNoticeLine(previous, manifest.version, entry));
|
|
212
|
-
}
|
|
213
|
-
if (installerOwnsSkills(ctx.home, ctx.env)) {
|
|
214
|
-
cfg.lastSkillBundleVersion = manifest.version;
|
|
215
|
-
try {
|
|
216
|
-
saveConfig(ctx.home, cfg);
|
|
217
|
-
}
|
|
218
|
-
catch {
|
|
219
|
-
// Best effort: the notice prints again next time, and nothing else depends on it.
|
|
220
|
-
}
|
|
204
|
+
if (previous === manifest.version || previous && semverOlder(manifest.version, previous))
|
|
221
205
|
return;
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
206
|
+
if (previous)
|
|
207
|
+
say(updateNoticeLine(previous, manifest.version, parseChangelogEntry(readChangelog(), manifest.version)));
|
|
208
|
+
cfg.lastSkillBundleVersion = manifest.version;
|
|
225
209
|
try {
|
|
226
|
-
|
|
227
|
-
}
|
|
228
|
-
catch (err) {
|
|
229
|
-
ctx.stderr(`[catalyst] could not refresh the skills at ${skillsDir} (${err instanceof Error ? err.message : String(err)}) — the ${manifest.version} skills are not installed yet; re-run the install command: ${INSTALL_COMMAND}`);
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
if (refreshed.installed.length > 0)
|
|
233
|
-
say(`[catalyst] refreshed ${refreshed.installed.join(", ")} at ${skillsDir} to ${manifest.version}`);
|
|
234
|
-
if (refreshed.removed.length > 0)
|
|
235
|
-
say(`[catalyst] removed retired skills ${refreshed.removed.join(", ")} from ${skillsDir}; their work moved into the skills this bundle ships`);
|
|
236
|
-
for (const s of refreshed.skipped) {
|
|
237
|
-
say(`[catalyst] left "${s.name}" alone: ${skillsDir}/${s.name} was not installed by this package (catalyst install --force to replace)`);
|
|
210
|
+
saveConfig(ctx.home, cfg);
|
|
238
211
|
}
|
|
239
|
-
|
|
240
|
-
cfg.skillsDir = skillsDir;
|
|
241
|
-
saveConfig(ctx.home, cfg);
|
|
212
|
+
catch { /* The notice can repeat on the next ordinary command. */ }
|
|
242
213
|
}
|
|
243
214
|
export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
244
215
|
// CTC-4625: setup passes the install options through to its engine untouched, so they never meet
|
|
@@ -305,7 +276,7 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
305
276
|
}),
|
|
306
277
|
};
|
|
307
278
|
try {
|
|
308
|
-
if (args.command !== "onboard") {
|
|
279
|
+
if (args.command !== "onboard" && !(flagBool(args, "check") && (args.command === "update" || args.command === "runner"))) {
|
|
309
280
|
migrateLegacyCliPath(ctx);
|
|
310
281
|
maybePrintUpdateNotice(args, ctx);
|
|
311
282
|
}
|
|
@@ -323,6 +294,19 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
323
294
|
throw new UsageError(`--${flag} needs --headless`);
|
|
324
295
|
if (typeof args.flags.runner === "string" && !onboardHeadlessRequested(args, ctx.env))
|
|
325
296
|
throw new UsageError("--runner yes|no needs --headless; use --runner or --no-runner interactively");
|
|
297
|
+
// The Codex launch hooks' removal runs on its own, with no onboarding around it.
|
|
298
|
+
if (args.flags["remove-codex-attribution"] === true) {
|
|
299
|
+
if (args.flags["dry-run"] === true)
|
|
300
|
+
throw new UsageError("--remove-codex-attribution changes files; run it without --dry-run");
|
|
301
|
+
deps.setupUi?.dispose();
|
|
302
|
+
const removal = removeCodexAttribution(ctx, deps.codexAttribution);
|
|
303
|
+
if (args.json)
|
|
304
|
+
ctx.stdout(JSON.stringify({ removed: removal.ok, files: removal.files, agent: removal.agent }));
|
|
305
|
+
else
|
|
306
|
+
for (const line of removal.lines)
|
|
307
|
+
ctx.stdout(line);
|
|
308
|
+
return removal.ok ? 0 : 1;
|
|
309
|
+
}
|
|
326
310
|
// Headless runs drop the terminal UI, browser sign-in and every browser action (CTC-4633).
|
|
327
311
|
const runOnboard = async (args, ctx, headless) => {
|
|
328
312
|
let ui = headless ? undefined : deps.setupUi;
|
|
@@ -485,6 +469,8 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
485
469
|
}
|
|
486
470
|
for (const line of jsonLines)
|
|
487
471
|
ctx.stdout(line);
|
|
472
|
+
// The Codex launch hooks follow every full run, done or waiting, and never change its exit code.
|
|
473
|
+
codexAttributionAfterSetup(args, ctx, deps.codexAttribution);
|
|
488
474
|
return code;
|
|
489
475
|
};
|
|
490
476
|
if (!onboardHeadlessRequested(args, ctx.env))
|
|
@@ -497,7 +483,7 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
497
483
|
case "notice":
|
|
498
484
|
return 0;
|
|
499
485
|
case "status":
|
|
500
|
-
return cmdStatus(ctx);
|
|
486
|
+
return cmdStatus(ctx, deps);
|
|
501
487
|
case "me":
|
|
502
488
|
return await cmdMe(args, ctx);
|
|
503
489
|
case "contract":
|
|
@@ -564,6 +550,8 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
564
550
|
return await cmdWork(args, ctx);
|
|
565
551
|
case "legacy":
|
|
566
552
|
return await cmdLegacy(args, ctx, { localSyncSelected: savedLocalSync(ctx), ...deps.legacy });
|
|
553
|
+
case "codex-env":
|
|
554
|
+
return await cmdCodexEnv(args, ctx, deps.codexEnv ?? {});
|
|
567
555
|
case "identity":
|
|
568
556
|
return await cmdIdentity(args, ctx, deps.identity ?? {});
|
|
569
557
|
case "messages":
|
|
@@ -643,6 +631,7 @@ const VERB_HELP_KNOWN = Object.fromEntries([
|
|
|
643
631
|
"lease",
|
|
644
632
|
"work",
|
|
645
633
|
"legacy",
|
|
634
|
+
"codex-env",
|
|
646
635
|
"onboard",
|
|
647
636
|
"update",
|
|
648
637
|
].map((v) => [v, true]));
|
|
@@ -852,7 +841,7 @@ function cmdInstall(args, ctx) {
|
|
|
852
841
|
ctx.stdout(`Removed retired skills from ${skillsDir}: ${result.removed.join(", ")}`);
|
|
853
842
|
return 0;
|
|
854
843
|
}
|
|
855
|
-
function cmdStatus(ctx) {
|
|
844
|
+
function cmdStatus(ctx, deps = {}) {
|
|
856
845
|
const manifest = readManifest();
|
|
857
846
|
const cfg = loadConfig(ctx.home);
|
|
858
847
|
if (!cfg) {
|
|
@@ -871,6 +860,7 @@ function cmdStatus(ctx) {
|
|
|
871
860
|
if (cfg.cliPath)
|
|
872
861
|
ctx.stdout(`CLI: ${cfg.cliPath}${existsSync(cfg.cliPath) ? "" : " (missing — re-run login)"}`);
|
|
873
862
|
ctx.stdout(`Contract: ${existsSync(contractPathFor(ctx.home)) ? contractPathFor(ctx.home) : "not cached (run: catalyst contract --refresh)"}`);
|
|
863
|
+
ctx.stdout(codexAttributionStatusLine(ctx, deps.codexAttribution));
|
|
874
864
|
return 0;
|
|
875
865
|
}
|
|
876
866
|
/** A short human relative time for an ISO expiry, e.g. "in 14m", "in 2h", "in 6d", or "now" once past. */
|