@catalyst-cloud/cli 0.9.4 → 0.13.0
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 +34 -0
- package/README.md +38 -32
- package/dist/args.js +16 -2
- package/dist/ask.js +2 -2
- package/dist/capabilities.js +70 -0
- package/dist/cli.js +42 -29
- package/dist/config.js +1 -1
- package/dist/connections.js +5 -5
- package/dist/env/declaration-rules.js +163 -0
- package/dist/env/group.js +51 -0
- package/dist/env/inventory.js +102 -0
- package/dist/env/render.js +39 -0
- package/dist/env/scan-env-example.js +62 -0
- package/dist/env/scan-source.js +43 -0
- package/dist/env/scan-workflow.js +123 -0
- package/dist/env/scan-wrangler.js +120 -0
- package/dist/env/types.js +1 -0
- package/dist/env/walk.js +87 -0
- package/dist/env.js +76 -0
- package/dist/environment.js +4 -4
- package/dist/events.js +1 -1
- package/dist/execution.js +3 -2
- package/dist/identity.js +3 -3
- package/dist/oauth.js +3 -3
- package/dist/query.js +27 -1
- package/dist/ready.js +3 -3
- package/dist/release.js +1 -1
- package/dist/replica.js +9 -7
- package/dist/runtime-store.js +2 -2
- package/dist/runtime.js +1 -1
- package/dist/secret.js +1 -1
- package/dist/skills.js +1 -1
- package/dist/team.js +329 -0
- package/package.json +4 -4
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +7 -7
- package/skills/catalyst-onboard/references/choosing-a-coding-account.md +2 -2
- package/skills/catalyst-onboard/references/declaring-a-repository.md +1 -1
- package/skills/catalyst-onboard/references/local-sync.md +3 -3
- package/skills/catalyst-onboard/references/skill-sources.md +1 -1
- package/skills/catalyst-onboard/references/the-one-path.md +12 -10
- package/skills/catalyst-onboard/references/what-a-phase-needs.md +5 -5
- package/skills/catalyst-onboard/references/what-the-browser-owns.md +6 -9
- package/skills/catalyst-onboard/references/who-fixes-what.md +35 -16
- package/skills/catalyst-onboard/scripts/local-sync.mjs +6 -6
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +133 -54
- package/skills/catalyst-setup/SKILL.md +1 -1
- package/skills/connect-me/SKILL.md +3 -3
- package/skills/how-catalyst-works/SKILL.md +5 -3
- package/skills/how-catalyst-works/references/settings-and-where-they-live.md +20 -0
- package/skills/how-catalyst-works/references/what-runs-next.md +19 -1
- 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-needs-me/references/reading-the-inbox.md +4 -0
- package/skills/what-this-repo-needs/SKILL.md +40 -0
- package/skills/what-this-repo-needs/agents/openai.yaml +6 -0
- package/skills/what-this-repo-needs/agents/portability.yaml +4 -0
- package/skills/what-this-repo-needs/references/what-each-group-means.md +68 -0
- package/skills/what-this-repo-needs/scripts/check.mjs +64 -0
- package/skills/what-this-repo-needs/scripts/inventory.mjs +61 -0
- package/skills/what-this-repo-needs/scripts/lib/cli.mjs +114 -0
- package/skills/what-this-repo-needs/scripts/lib/credential.mjs +29 -0
- package/skills/whats-happening/SKILL.md +1 -1
- package/skills/whats-happening/references/reading-the-board.md +1 -1
- package/skills/whats-happening/references/reprioritising.md +1 -1
- package/skills/whats-happening/references/status-reply.md +2 -0
- package/skills/whats-happening/references/why-is-it-stuck.md +41 -33
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.0
|
|
4
|
+
|
|
5
|
+
Catalyst's released pieces now share one version line, 0.13: `@catalyst-cloud/cli` and the deprecated `@catalyst-cloud/catalyst-skills` name, these skills, `@catalyst-cloud/sdk`, the installer, and the schema, replicate and read-model libraries. A CLI on 0.13.x belongs with a cloud whose tenant contract names `releaseLine` 0.13. The CLI moves from 0.9 straight to 0.13 and runs on `@catalyst-cloud/sdk` 0.13. The tenant contract keeps its own version, which tracks the document's shape.
|
|
6
|
+
|
|
7
|
+
Ask your agent what a setting does and where it is set, and it names the screen, the route and the rule instead of guessing: connecting Linear, installing the GitHub App, registering a repository, the merge policy and its reviewers, secrets versus environment variables, and why routing has no write route yet all have a home in `how-catalyst-works`. `whats-happening` now maps every reason a ticket can be held to a next action and who takes it — the person with their own login, a tenant owner or admin in settings, an operator, or "no one acts" when it is genuinely just waiting — and three reasons that were missing from the stuck-state table (a live container holding a later phase, too many claims in an hour, and a repository at capacity) are documented for the first time. Connecting your own Linear account and being matched as a Linear identity are now explained as two different, sometimes disagreeing, facts, so "I connected Linear, why am I still not getting asks?" has a real answer. And the README says plainly what it still does not know: flow metrics — cycle time, throughput, how long a pull request has been open — are not computed yet, rather than counting something else and calling it that.
|
|
8
|
+
|
|
9
|
+
## 0.9.5
|
|
10
|
+
|
|
11
|
+
The CLI calls itself `catalyst` everywhere: usage lines, every verb's `--help`, hints, remedies and errors. `catalyst-skills` still works as a deprecated alias. The npx form is `npx -p @catalyst-cloud/cli catalyst …`, because bare `npx @catalyst-cloud/cli` cannot pick a binary. A replica read on the `node:sqlite` fallback no longer prints a notice every time; `CATALYST_DEBUG=1` still shows it.
|
|
12
|
+
|
|
13
|
+
`catalyst team` sets up a Linear team as a Catalyst project from the terminal: `list`, `check`, `map`, `adopt`, `migrate` and `checklist`. Writes need a reviewed plan and the cloud's mapping, adoption or migration hash.
|
|
14
|
+
|
|
15
|
+
`catalyst capabilities` lists what this CLI can do, the role each verb needs (member or admin), and whether this cloud serves the route. It reads only the cached contract, so it never touches the network.
|
|
16
|
+
|
|
17
|
+
`/catalyst-onboard` runs `team check` and `team map` itself for an admin, where it used to send you to a page. It tells a member which role can run a step. With an older CLI or cloud it keeps the page, as before. A project whose readiness reads blocked is no longer counted as set up: the guide names the blocking checks and who fixes each.
|
|
18
|
+
|
|
19
|
+
The how-catalyst-works and whats-happening skills explain the WIP limit as the code applies it. The limit is per Catalyst project (the project's setting, else the tenant-wide value, else 12; 0 holds every new start). It holds only new starts, and a ticket already in progress keeps getting phases. `catalyst explain` prints the `wip_limit` reason in words.
|
|
20
|
+
|
|
3
21
|
## 0.9.4
|
|
4
22
|
|
|
5
23
|
`/catalyst-onboard` walks a new member through setup one step at a time, in the order a person can act on it: coding account, Linear integration, project, GitHub App and repository, connected accounts, the repository's `.catalyst/catalyst.toml` declaration, then the host, ending with one ticket moving. Each turn asks one question, says why in a line, and checks the result after. A new reference covers choosing a coding account (a Claude setup token, a Codex `auth.json`, or a GLM or Qwen API key), and it says plainly that a plain Anthropic or OpenAI API key cannot be enrolled today. Accounts are named by their label. The skills use the product's own nouns (workspace, project, repository, integration, connected account).
|
|
@@ -48,6 +66,22 @@ That one command is `catalyst-skills runtime install`. It downloads a pinned Nod
|
|
|
48
66
|
|
|
49
67
|
## 0.7.0
|
|
50
68
|
|
|
69
|
+
`catalyst-skills env inventory [path]` scans a repository offline — no login, no network — and lists
|
|
70
|
+
the environment variable NAMES it needs, never a value. Names are grouped build/test (needed to
|
|
71
|
+
install, build and run the tests), deploy-only (a CI secret only a deploy job uses) and bindings (a
|
|
72
|
+
Cloudflare or other platform binding, which is not an environment value). Each name shows where it
|
|
73
|
+
was found (file:line), what uses it, and where a local value would come from — a `.env` file, your
|
|
74
|
+
shell, a CI secret, or a Cloudflare binding. `--json` gives the same data in machine form. `env check
|
|
75
|
+
<file>` validates a `catalyst.env.json` offline, against this bundle's own dated, cited copy of the
|
|
76
|
+
cloud's validation rules (it does not compute the cloud's canonical hash — only a `valid` / `invalid`
|
|
77
|
+
verdict and the refusal reasons). The new `what-this-repo-needs` skill explains why a cloud container
|
|
78
|
+
needs these names before it scans, then asks you to review the list: keep, drop or move each one.
|
|
79
|
+
|
|
80
|
+
Reading a GitHub Actions workflow now goes through a real YAML parser (`yaml`, now a runtime
|
|
81
|
+
dependency of this package — it carries no transitive dependencies of its own) rather than a
|
|
82
|
+
hand-written line scanner, so a workflow that declares its `env:` in flow style (`{ A: 1, B: 2 }`) is
|
|
83
|
+
read correctly instead of silently missed.
|
|
84
|
+
|
|
51
85
|
`explain` and `ready` now name a team that cannot start work even when the live read is not available. Your tenant's contract carries each team's dispatch gate, and this machine already keeps a copy of that contract on disk; until now only the live eligibility read could name the gate, so a network hiccup, or a cloud older than that read, left `explain` printing nothing at all. `explain` now leads with the gate and the fix from the cached contract when the live read is unavailable or sends no gate, and says which of the two it read; when both answer and disagree, the live read wins and the paragraph says the cached one disagreed. A refusal from the cloud — a credential that is not accepted, for instance — is still a refusal, never quietly replaced by a cached answer. `ready` now prints one dispatch-gate line per team: open teams read `ok`, and a team whose stages are not saved reads `FAIL` with the remedy your tenant sent and turns the verdict to NOT READY, because nothing in that team can start. A cloud that does not send the gate changes nothing.
|
|
52
86
|
|
|
53
87
|
`catalyst-skills ready` now tells you when the skills on this machine, or the CLI itself, are behind the newest published release — it names the version you have, the version that is out, and the one command that updates each, as a note that never changes the verdict. Every skill file now records the bundle version it was vendored at, so a copy installed months ago is visible as such instead of only by diffing files. The check reads the npm registry's `dist-tags.latest`, capped at 1500ms and cached for six hours, and never blocks: an unreachable registry is one note that says so, and `--offline` (or `CATALYST_SKILLS_OFFLINE=1`) skips the lookup entirely.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @catalyst-cloud/
|
|
1
|
+
# @catalyst-cloud/cli
|
|
2
2
|
|
|
3
3
|
[](https://skills.sh/coalesce-labs/catalyst-cloud-skills)
|
|
4
4
|
|
|
@@ -69,17 +69,17 @@ Without `--all` the installer asks which skills to take and which agents to inst
|
|
|
69
69
|
The skills call one CLI, and the CLI holds your credential. Install it once and connect this machine — the keyless way logs you in as yourself, with nothing to mint or paste:
|
|
70
70
|
|
|
71
71
|
```sh
|
|
72
|
-
npm install -g @catalyst-cloud/
|
|
73
|
-
catalyst
|
|
74
|
-
catalyst
|
|
72
|
+
npm install -g @catalyst-cloud/cli
|
|
73
|
+
catalyst login
|
|
74
|
+
catalyst ready
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
-
`catalyst
|
|
77
|
+
`catalyst login` with no key opens a device-code login: it prints a short code and a URL, you approve it in your browser, and this machine is connected as you. On a machine with no browser (a remote box, a container) the code and URL still work — approve them from your phone. The short-lived session refreshes silently afterwards, so you log in about once a year. `npx -p @catalyst-cloud/cli catalyst login` works without the global install, and `bunx` works in place of `npx`. A non-default cloud is set with `CATALYST_CLOUD_BASE_URL` or `--base-url <url>`.
|
|
78
78
|
|
|
79
79
|
Prefer a key? Pass one instead — mint a **personal key** at Settings → API keys in the Catalyst Cloud app (every member can; no admin needed). The environment form keeps it out of your shell history:
|
|
80
80
|
|
|
81
81
|
```sh
|
|
82
|
-
CATALYST_CLOUD_TOKEN=<your-personal-key> catalyst
|
|
82
|
+
CATALYST_CLOUD_TOKEN=<your-personal-key> catalyst login
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
`--key <your-personal-key>` is the third form, for a script.
|
|
@@ -87,20 +87,22 @@ CATALYST_CLOUD_TOKEN=<your-personal-key> catalyst-skills login
|
|
|
87
87
|
After a tenant owner connects the tenant's Linear workspace, connect your personal Linear account. Connect your personal GitHub account only after the tenant's GitHub App is installed and its repository is registered. These personal grants are separate from the tenant's Linear connection and GitHub App installation. Your agent can start each connection and check whether it landed, but you approve each one in your browser:
|
|
88
88
|
|
|
89
89
|
```sh
|
|
90
|
-
catalyst
|
|
91
|
-
catalyst
|
|
90
|
+
catalyst connections personal linear start
|
|
91
|
+
catalyst connections personal linear status
|
|
92
92
|
# First install the tenant's GitHub App and register its repository in Settings.
|
|
93
|
-
catalyst
|
|
94
|
-
catalyst
|
|
93
|
+
catalyst connections personal github start
|
|
94
|
+
catalyst connections personal github status
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
Each `start` prints a short-lived URL and attempts to open your browser. On a remote machine, open the printed URL on your own device. `status --json` gives the same result to an agent; `start --wait 60` waits up to one minute for approval. A connected result is the proof that the grant landed. If a provider reports unavailable, retry status later rather than starting a second approval.
|
|
98
98
|
|
|
99
99
|
The `catalyst-onboard` skill checks both grants before taking you through the rest of tenant setup and the first ticket.
|
|
100
100
|
|
|
101
|
+
The command is `catalyst`. `catalyst-skills` still works as a deprecated alias, and `@catalyst-cloud/catalyst-skills` is the deprecated package name that forwards to this one.
|
|
102
|
+
|
|
101
103
|
## What this is
|
|
102
104
|
|
|
103
|
-
A set of skills that let your coding agent run your own Catalyst Cloud tenant (https://staging.catalystcloud.dev) from your seat: what is happening, what needs you, and what to do about it. They read your tenant through the Catalyst Cloud SDK, write to it through the tenant's agent proxy, and never compose a URL or run a tool of their own; every read, write and subscription is a `catalyst
|
|
105
|
+
A set of skills that let your coding agent run your own Catalyst Cloud tenant (https://staging.catalystcloud.dev) from your seat: what is happening, what needs you, and what to do about it. They read your tenant through the Catalyst Cloud SDK, write to it through the tenant's agent proxy, and never compose a URL or run a tool of their own; every read, write and subscription is a `catalyst` verb with `--help`. Every skill is plain Markdown under `skills/<name>/SKILL.md` in this repository, and your own login — a keyless device-code session, or a personal key — is the only credential. Your agent acts as you: the asks it raises and the comments it posts carry your name, and "what needs me" means you.
|
|
104
106
|
|
|
105
107
|
## What connecting does
|
|
106
108
|
|
|
@@ -108,13 +110,13 @@ A set of skills that let your coding agent run your own Catalyst Cloud tenant (h
|
|
|
108
110
|
|
|
109
111
|
A keyless session's access token is short-lived and rotates on its own: every request refreshes it within a minute of expiry and rewrites the config atomically, so you stay connected for months without logging in again. You only log in again if the session is revoked or you have been away long enough to lapse — then one clear line tells you to run `login`.
|
|
110
112
|
|
|
111
|
-
`login` does not install skills; the install command above did that. Re-running `login` after a key rotation, or to switch rails, rewrites the config. `catalyst
|
|
113
|
+
`login` does not install skills; the install command above did that. Re-running `login` after a key rotation, or to switch rails, rewrites the config. `catalyst status` prints which tenant this machine is connected to and as whom, `catalyst ready` prints one READY or NOT READY verdict with the fix for each failure and who can apply it, and `catalyst --version` prints the package version and its pinned tenant contract range.
|
|
112
114
|
|
|
113
115
|
The setup skill Catalyst seeds into your repository ends by pointing at this same command. That served skill lives in the Catalyst Cloud application, not here; this is the only connect step a customer runs. The tenant's **account key** (Settings → Account keys, admin-minted) is a different credential — for a host or daemon that runs unattended — and is not what a person connects their own agent with: it would strip your name from everything your agent writes, and `login` says so if you use one.
|
|
114
116
|
|
|
115
117
|
## Requirements
|
|
116
118
|
|
|
117
|
-
- Node 22.15 or newer (Node 26 works), or bun 1.4 or newer. The bundle uses Node's built-in SQLite module (and, on bun, bun's own `node:sqlite`) for the optional local replica, so there is no native dependency to build; if `better-sqlite3` resolves on the machine it is used instead. `catalyst
|
|
119
|
+
- Node 22.15 or newer (Node 26 works), or bun 1.4 or newer. The bundle uses Node's built-in SQLite module (and, on bun, bun's own `node:sqlite`) for the optional local replica, so there is no native dependency to build; if `better-sqlite3` resolves on the machine it is used instead. `catalyst ready` names the exact reason when the runtime is too old, and `catalyst runtime install` installs a pinned Node under this CLI's own cache — without touching your machine's default Node — if you would rather not upgrade it.
|
|
118
120
|
- Your personal key, minted by you at Settings → API keys. The key is the only tenant selector: you never type a tenant or account id. If your Linear identity is not matched yet, `login` says so; an admin matches it in Settings → Members, and until then "what needs me" shows everyone's asks.
|
|
119
121
|
- An agent that discovers skills. Claude Code loads the plugin; Codex, Cursor, OpenCode and the rest read the `skills/<name>/SKILL.md` files the `npx skills` installer writes.
|
|
120
122
|
- Bun is optional, only if you prefer `bunx` over `npx` — bun 1.4 or newer, which is when `node:sqlite` arrives; older bun cannot run this CLI at all.
|
|
@@ -128,6 +130,8 @@ Open a new session and ask about your own tenant. The skills read the config `lo
|
|
|
128
130
|
- Run this project for me until it closes.
|
|
129
131
|
- Am I set up?
|
|
130
132
|
|
|
133
|
+
`catalyst team list` inventories visible teams without running readiness checks. `team check ENG` prints readiness for one team; `team check --all` runs checks for every team. `team map ENG`, `team adopt ENG`, and `team migrate ENG` print a plan and exit 3 before changing configuration; review the plan and rerun the chosen command with `--yes --plan-hash <hash>` from that preview. `team adopt ENG --undo` previews the exact previously created stages, and `team migrate ENG --retire` requires its own confirmation after the ticket move. `team checklist ENG` prints the same manual setup lines as the browser. These commands use the member's own login; an admin or owner seat is required for setup, and Adopt and Migrate also need that member's personal Linear connection.
|
|
134
|
+
|
|
131
135
|
## What has to be running
|
|
132
136
|
|
|
133
137
|
Nothing, by default. After `login`, every read, write, ask and explanation goes to the cloud's origin-fresh API with the config file and the cached contract on disk. Two optional processes exist for people who want them:
|
|
@@ -135,12 +139,12 @@ Nothing, by default. After `login`, every read, write, ask and explanation goes
|
|
|
135
139
|
| process | needed for | what it holds on disk | lifetime |
|
|
136
140
|
| --- | --- | --- | --- |
|
|
137
141
|
| none | every read, write, ask and explain | `customer.json` and `contract.json` | the default after login |
|
|
138
|
-
| `catalyst
|
|
139
|
-
| `catalyst
|
|
142
|
+
| `catalyst replica start` | local SQL, cheap repeated reads, and local event `tail`, `wait-for`, and `query` | one SQLite file plus a bounded event cache under `$XDG_STATE_HOME/catalyst/events/` (fallback `~/.local/state/catalyst/events/`) | one long-running process; foreground by default, `--detach` writes a pidfile beside the database and returns; `login --start-replica` does the same at the end of login |
|
|
143
|
+
| `catalyst watch` | a project owner reacting to its scope | one cursor file (`~/.config/catalyst-cloud/watch-cursor.json`) stamped with the tenant | lives inside the session that armed it; exits with it |
|
|
140
144
|
|
|
141
|
-
The check every skill runs first is `catalyst
|
|
145
|
+
The check every skill runs first is `catalyst replica status`, which needs no network: is the pidfile's process alive, is the writer-lock heartbeat younger than the staleness threshold, and is the cursor non-empty. It exits `0` for fresh, `1` for present but stale, `2` for not connected, `3` for absent, and prints one line either way (`--json` for scripts). A fresh replica is used; anything else falls back to the API and the skill says so in its answer. A skill never refuses to work because the replica is down and never silently reads a stale one. `replica status --probe` compares the local cursor against the cloud's head for the honest "how far behind" number; that is the only form that touches the network. `catalyst replica stop` stops a detached writer.
|
|
142
146
|
|
|
143
|
-
`catalyst
|
|
147
|
+
`catalyst events status --probe` checks the event cache's own cursor and compares it with the cloud head; `replica status --probe` checks the separate entity-replica cursor. A fresh replica does not prove event freshness. `events tail` follows new cached events, `events wait-for --ticket ... --timeout ...` performs a bounded wait, and `events query` reads retained history. Those three commands only read the cache; `events status --probe` makes the cloud comparison. The replica process is the single writer and reports an event-sync failure without terminating a healthy entity replica. The event cache keeps closed daily segments for at most seven days or 256 MiB per tenant and reports an explicit gap when a requested sequence has retired.
|
|
144
148
|
|
|
145
149
|
The replica is a Node process, not a service: the supported path is the plain command, and `skills/connect-me/references/keeping-the-replica-running.md` gives launchd and systemd examples for people who want the writer to survive a reboot.
|
|
146
150
|
|
|
@@ -158,14 +162,16 @@ The replica is a Node process, not a service: the supported path is the plain co
|
|
|
158
162
|
| `how-catalyst-works` | "How does this work? Why did it do that? How does it prioritise?" | The execution model as references loaded on demand: the eight-phase ladder, the eleven board slots and this team's live stage map, what happens when a phase fails, how the queue is ordered and routed, every exclusion reason, and the coding-account model. Scripts explain one ticket's eligibility in plain English. | [`SKILL.md`](skills/how-catalyst-works/SKILL.md) |
|
|
159
163
|
| `unstick` | "Why is this parked? Unpark it. Get things flowing again." | Reads why a ticket is not running and every park or hold on it, decides whether the recorded cause is fixed, previews the release and releases it the right way from your own login — or one failure class across a team — and raises an ask only for what a person has to do. | [`SKILL.md`](skills/unstick/SKILL.md) |
|
|
160
164
|
| `connect-me` | "Connect this machine to my tenant." | Connects the machine to the tenant with your own personal key, caches the tenant contract, verifies, and offers to start the replica. | [`SKILL.md`](skills/connect-me/SKILL.md) |
|
|
165
|
+
| `what-this-repo-needs` | "What env vars does this repo need? What does catalyst.env.json mean?" | Scans a repository offline — no login, no network — and lists the environment variable names it needs, grouped build/test, deploy-only and bindings, each with where it was found, what uses it, and where a local value would come from. Never reads or prints a value. Also validates a `catalyst.env.json` file with the same rules the cloud applies. | [`SKILL.md`](skills/what-this-repo-needs/SKILL.md) |
|
|
161
166
|
|
|
162
167
|
Some of these only read; the ones that write anything — or that drive you through writes, as `catalyst-onboard` does — are marked so an agent cannot invoke them on its own, and you ask for them by name. Each skill's own `SKILL.md` says which it is, in its frontmatter; no count lives in this sentence, because a count in a sentence goes wrong the first time the table above gains a row. Every skill declares `allowed-tools` scoped to this package's own binary, so none of them needs a blanket shell grant.
|
|
163
168
|
|
|
164
169
|
## What a key cannot see yet
|
|
165
170
|
|
|
166
|
-
Your personal key reads everything the skills need — tickets, pull requests, the eligibility explainer, the dispatch queue, fleet activity, per-ticket execution history (`catalyst
|
|
171
|
+
Your personal key reads everything the skills need — tickets, pull requests, the eligibility explainer, the dispatch queue, fleet activity, per-ticket execution history (`catalyst explain --history <ticket>`: phase attempts, remediation rounds, park state) and coding-account status (`catalyst accounts`: provider, declared and observed state, window usage, walls, quarantine — never a credential; enrolling or pausing one is `<your cloud>/settings/coding-accounts`). It also releases a parked or held ticket once its cause is fixed: `catalyst release <ticket> --because <what changed>` (the `unstick` skill runs it), recorded against your name and shown in the ticket's history. And it declares what your containers need: `catalyst environment` reads the tenant-wide declaration, `environment propose --file <path> --approve` proposes and approves it in one compare-and-set, and the values behind the names stay in the cloud — reading needs any active seat, proposing and approving need an admin or owner one. An admin or owner can put a repository's values in from the terminal. `catalyst secret import .env --repo owner/name` stores every name in the file and lists the declared names that still have no value. `catalyst secret set NAME --repo owner/name --command 'op read op://Vault/item/field'` runs the command on your machine and stores its output; with no `--command` it reads the value from stdin, or asks for it without echoing. No value is ever printed, and the cloud's audit records the command, not its output. That is the account's own declaration — a repository's own `.catalyst/catalyst.toml`, which `catalyst-setup` already reports on, is a separate thing. `catalyst env inventory` lists the environment variable names a repository needs — never a value — and `env check` validates a `catalyst.env.json` file offline; neither needs a login (the `what-this-repo-needs` skill walks through both). Two things it cannot do, and the skills say so by name rather than guess:
|
|
167
172
|
|
|
168
173
|
- Read pull-request labels or the reviewer's reaction. The mirror does not carry them; GitHub's own page does.
|
|
174
|
+
- Compute a flow number — cycle time, throughput, or how long pull requests have been open. Nothing serves those yet, so say they are not computed rather than counting something else and calling it that.
|
|
169
175
|
|
|
170
176
|
## Versions and origins
|
|
171
177
|
|
|
@@ -182,35 +188,35 @@ The skill files themselves are written by whichever install command you ran, in
|
|
|
182
188
|
|
|
183
189
|
## Updating
|
|
184
190
|
|
|
185
|
-
A plugin install updates when we ship. Skills copied by `npx skills add` do not. After the source and destination check in Install, re-run the Cloud add command to refresh existing skills and pick up new ones. Update the CLI with `npm install -g @catalyst-cloud/
|
|
191
|
+
A plugin install updates when we ship. Skills copied by `npx skills add` do not. After the source and destination check in Install, re-run the Cloud add command to refresh existing skills and pick up new ones. Update the CLI 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:
|
|
186
192
|
|
|
187
193
|
```
|
|
188
|
-
[catalyst
|
|
194
|
+
[catalyst] updated 0.1.1 → 0.2.0: <that version's CHANGELOG.md summary> · update with: npm install -g @catalyst-cloud/cli@latest && catalyst login
|
|
189
195
|
```
|
|
190
196
|
|
|
191
|
-
A `customer.json` written by an older bundle is still read unchanged; it gains the CLI path and the cached contract the next time you run `catalyst
|
|
197
|
+
A `customer.json` written by an older bundle is still read unchanged; it gains the CLI path and the cached contract the next time you run `catalyst login`.
|
|
192
198
|
|
|
193
|
-
`catalyst
|
|
199
|
+
`catalyst ready` also says when either the installed skills or the CLI is behind the latest publish, naming both versions and the command for each; it is a note, never a failure, and `--offline` (or `CATALYST_SKILLS_OFFLINE=1`) skips the lookup.
|
|
194
200
|
|
|
195
201
|
## Uninstalling
|
|
196
202
|
|
|
197
|
-
Remove the skills the way you installed them: `/plugin uninstall catalyst@catalyst-cloud` in Claude Code, or delete the skill directories (`catalyst-github`, `catalyst-linear`, `catalyst-onboard`, `catalyst-setup`, `connect-me`, `how-catalyst-works`, `run-this-project`, `unstick`, `what-needs-me`, `whats-happening`) from wherever `npx skills add` wrote them. Then remove what the CLI wrote:
|
|
203
|
+
Remove the skills the way you installed them: `/plugin uninstall catalyst@catalyst-cloud` in Claude Code, or delete the skill directories (`catalyst-github`, `catalyst-linear`, `catalyst-onboard`, `catalyst-setup`, `connect-me`, `how-catalyst-works`, `run-this-project`, `unstick`, `what-needs-me`, `what-this-repo-needs`, `whats-happening`) from wherever `npx skills add` wrote them. Then remove what the CLI wrote:
|
|
198
204
|
|
|
199
205
|
```sh
|
|
200
|
-
catalyst
|
|
206
|
+
catalyst replica stop
|
|
201
207
|
rm -f ~/.config/catalyst-cloud/customer.json ~/.config/catalyst-cloud/contract.json ~/.config/catalyst-cloud/published.json ~/.config/catalyst-cloud/watch-cursor.json
|
|
202
208
|
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
|
|
203
209
|
rm -rf "${XDG_STATE_HOME:-$HOME/.local/state}/catalyst/events"
|
|
204
|
-
npm uninstall -g @catalyst-cloud/
|
|
210
|
+
npm uninstall -g @catalyst-cloud/cli
|
|
205
211
|
```
|
|
206
212
|
|
|
207
213
|
## If login fails
|
|
208
214
|
|
|
209
|
-
- `catalyst
|
|
210
|
-
- `catalyst
|
|
211
|
-
- `catalyst
|
|
215
|
+
- `catalyst: GET /me failed (401): credential not accepted — mint a personal key at Settings → API keys and log in again` — the key is stale, mistyped or revoked. Mint a new one, then run `login` again.
|
|
216
|
+
- `catalyst: GET /me failed (403): account-not-operational` — the tenant is suspended. This is an admin conversation on the tenant, not a local fix.
|
|
217
|
+
- `catalyst: could not reach <url>: <detail>` — the machine cannot reach the cloud. The URL is named in the message; check `CATALYST_CLOUD_BASE_URL` or `--base-url`.
|
|
212
218
|
- A line naming two contract versions after `Connected to` — the tenant serves a contract outside this bundle's `1.x || 2.x` range. The config is written; update the bundle before using the other skills.
|
|
213
|
-
- `[catalyst
|
|
219
|
+
- `[catalyst] GET /api/v1/agent/contract refused (403): this cloud is older than the bundle …` on stderr — the cloud has not yet deployed personal-key access to the contract. Update the cloud, or connect with the tenant's account key until it has.
|
|
214
220
|
|
|
215
221
|
## License
|
|
216
222
|
|
|
@@ -221,9 +227,9 @@ MIT — see [LICENSE](LICENSE). How to contribute and how releases happen are de
|
|
|
221
227
|
Personal Linear consent normally binds the provider viewer automatically. For an unmatched identity, inspect your choices and explicitly select yourself:
|
|
222
228
|
|
|
223
229
|
```sh
|
|
224
|
-
catalyst
|
|
225
|
-
catalyst
|
|
226
|
-
catalyst
|
|
230
|
+
catalyst identity linear status
|
|
231
|
+
catalyst identity linear options --json
|
|
232
|
+
catalyst identity linear set <linearUserId>
|
|
227
233
|
```
|
|
228
234
|
|
|
229
235
|
The command uses your personal credential and reads the result back after selection. It cannot change another member, replace an automatic match, or take an already-claimed identity. A missing options field means no choice was offered, which can include a temporarily unreadable roster. This command depends on the pending SDK 0.12.0 release with identity support.
|
package/dist/args.js
CHANGED
|
@@ -111,6 +111,16 @@ export const FLAG_TABLES = {
|
|
|
111
111
|
offline: { value: false, help: "skip the published-release check (no network)" },
|
|
112
112
|
},
|
|
113
113
|
accounts: {},
|
|
114
|
+
env: {},
|
|
115
|
+
team: {
|
|
116
|
+
all: { value: false, help: "check every team, one request at a time" },
|
|
117
|
+
stage: { value: true, repeat: true, help: "map a role to a live Linear state name (role=StateName)" },
|
|
118
|
+
choice: { value: true, repeat: true, help: "migrate a source state to a destination (sourceId=destinationId)" },
|
|
119
|
+
yes: { value: false, help: "apply the plan you have reviewed with the person" },
|
|
120
|
+
"plan-hash": { value: true, help: "apply only the exact preview hash the person reviewed" },
|
|
121
|
+
undo: { value: false, help: "adopt: preview or archive stages a prior adoption created" },
|
|
122
|
+
retire: { value: false, help: "migrate: separately preview or retire emptied source stages" },
|
|
123
|
+
},
|
|
114
124
|
environment: {
|
|
115
125
|
file: { value: true, help: "propose: a JSON file holding the declaration" },
|
|
116
126
|
stdin: { value: false, help: "propose: read the declaration from stdin" },
|
|
@@ -125,6 +135,7 @@ export const FLAG_TABLES = {
|
|
|
125
135
|
rotate: { value: true, repeat: true, help: "import: replace this name if it is already set (repeatable)" },
|
|
126
136
|
},
|
|
127
137
|
identity: {},
|
|
138
|
+
capabilities: {},
|
|
128
139
|
connections: {
|
|
129
140
|
wait: { value: true, help: "start: wait up to this many seconds for browser approval (0-600)" },
|
|
130
141
|
},
|
|
@@ -158,9 +169,12 @@ export const VERB_USAGE = {
|
|
|
158
169
|
ready: "ready [--json] [--offline]",
|
|
159
170
|
accounts: "accounts [--json]",
|
|
160
171
|
mcp: "mcp <add <name> --url URL <--auth none|--bearer SECRET_NAME|--header NAME=SECRET_NAME...>|list|remove <name>> [--json]",
|
|
161
|
-
|
|
172
|
+
team: "team <list|check <KEY>|check --all|map <KEY> [--stage role=StateName]... [--yes --plan-hash H]|adopt <KEY> [--undo] [--yes --plan-hash H]|migrate <KEY> [--choice sourceId=destinationId]... [--retire] [--yes --plan-hash H]|checklist <KEY>> [--json]",
|
|
173
|
+
env: "env <inventory [path] | check <file>> [--json] (THIS repository, offline — no login, no network)",
|
|
174
|
+
environment: "environment [read] [--json] | environment propose --file <path>|--stdin [--expect-revision N] [--approve] [--json] | environment approve [--revision N --hash H] [--json] (your ACCOUNT's declaration; needs login)",
|
|
162
175
|
secret: "secret set <NAME> --repo <owner/name> [--command '<cmd>'] [--json] (value from --command, stdin, or a hidden prompt) | secret import <file> --repo <owner/name> [--rotate NAME]... [--json]",
|
|
163
176
|
identity: "identity linear <status|options|set> [<linearUserId>] [--json]",
|
|
177
|
+
capabilities: "capabilities [--json]",
|
|
164
178
|
connections: "connections personal <linear|github> <start|status> [--wait <seconds>] [--json]",
|
|
165
179
|
release: "release <ticket> --because <what changed> [--retry-unchanged] [--dry-run] [--json] | release --class <failure-class> --team <K> --because <what changed> [--retry-unchanged] [--dry-run] [--limit N] [--json]",
|
|
166
180
|
};
|
|
@@ -277,7 +291,7 @@ export function flagInt(args, name, fallback) {
|
|
|
277
291
|
}
|
|
278
292
|
/** The help text for one verb: usage line, then every flag in its table and the global ones. */
|
|
279
293
|
export function verbHelp(verb) {
|
|
280
|
-
const lines = [`Usage: catalyst
|
|
294
|
+
const lines = [`Usage: catalyst ${VERB_USAGE[verb] ?? verb}`, ""];
|
|
281
295
|
const table = FLAG_TABLES[verb] ?? {};
|
|
282
296
|
const names = Object.keys(table);
|
|
283
297
|
if (names.length > 0) {
|
package/dist/ask.js
CHANGED
|
@@ -146,10 +146,10 @@ async function list(args, ctx, doc, api, cfg) {
|
|
|
146
146
|
return 0;
|
|
147
147
|
}
|
|
148
148
|
if (scope.kind === "unmatched") {
|
|
149
|
-
ctx.stderr(`[catalyst
|
|
149
|
+
ctx.stderr(`[catalyst] your Linear identity is not matched yet (an admin matches it in Settings → Members), so this is every open ask, not only yours`);
|
|
150
150
|
}
|
|
151
151
|
else if (scope.kind === "no-person") {
|
|
152
|
-
ctx.stderr(`[catalyst
|
|
152
|
+
ctx.stderr(`[catalyst] connected with the tenant's account key, which names no person — this is every open ask; log in with your personal key to see only yours`);
|
|
153
153
|
}
|
|
154
154
|
if (ranked.length === 0) {
|
|
155
155
|
ctx.stdout(scope.kind === "mine" ? `no open asks assigned to ${scope.label} (${all.length} open in the tenant — add --anyone to see them)` : "no open asks");
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { readManifest } from "./config.js";
|
|
2
|
+
import { readContractCache } from "./contract.js";
|
|
3
|
+
const agent = (method, name) => ({ method, path: `/api/v1/agent/${name}` });
|
|
4
|
+
/** ⭐ THE ONE PLACE THIS CLI SAYS WHAT IT CAN DO. Keep it in the order a setup walks: the machine,
|
|
5
|
+
* the person, the workspace, the project, the repository, then work. A new verb is a new row here
|
|
6
|
+
* in the same change, or `catalyst capabilities` lies to every guide that reads it. */
|
|
7
|
+
export const CAPABILITIES = [
|
|
8
|
+
{ verb: "status", does: "say whether this machine is connected, as whom, and to which cloud", needs: "member", routes: [], since: "0.1.0" },
|
|
9
|
+
{ verb: "me", does: "read the person's seat, role and Linear identity", needs: "member", routes: [], since: "0.1.0" },
|
|
10
|
+
{ verb: "ready", does: "read the machine and the workspace's per-project readiness in one verdict", needs: "member", routes: [], since: "0.1.0" },
|
|
11
|
+
{ verb: "contract", does: "read the workspace contract this cloud serves", needs: "member", routes: [agent("GET", "contract")], since: "0.1.0" },
|
|
12
|
+
{ verb: "capabilities", does: "list what this CLI can do, the role each needs, and whether this cloud serves it", needs: "member", routes: [], since: "0.9.5" },
|
|
13
|
+
{ verb: "connections personal linear start|status", does: "start and confirm the person's own Linear connected account (the consent itself is theirs, in a browser)", needs: "member", routes: [], since: "0.9.0" },
|
|
14
|
+
{ verb: "connections personal github start|status", does: "start and confirm the person's own GitHub connected account (the consent itself is theirs, in a browser)", needs: "member", routes: [], since: "0.9.0" },
|
|
15
|
+
{ verb: "identity linear status|options|set", does: "match the person's Linear identity from the roster", needs: "member", routes: [], since: "0.9.0" },
|
|
16
|
+
{ verb: "team list", does: "list the workspace's Linear teams and which are mapped as projects", needs: "member", routes: [agent("GET", "teams")], since: "0.9.5" },
|
|
17
|
+
{ verb: "team check", does: "run a project's readiness check now", needs: "admin", routes: [agent("POST", "team-workflow/check")], since: "0.9.5" },
|
|
18
|
+
{ verb: "team map", does: "map a project's stages, from a preview the person approves", needs: "admin", routes: [agent("GET", "team-workflow"), agent("POST", "team-workflow/save")], since: "0.9.5" },
|
|
19
|
+
{ verb: "team adopt", does: "create Catalyst's stages in a Linear team, from a preview the person approves", needs: "admin", routes: [agent("POST", "team-workflow/adopt")], since: "0.9.5" },
|
|
20
|
+
{ verb: "team migrate", does: "move tickets between stages, from a preview the person approves", needs: "admin", routes: [agent("POST", "team-workflow/migrate")], since: "0.9.5" },
|
|
21
|
+
{ verb: "team checklist", does: "print what adopting the workflow would do to a team", needs: "admin", routes: [agent("GET", "team-workflow")], since: "0.9.5" },
|
|
22
|
+
{ verb: "environment read", does: "read the workspace-wide environment declaration", needs: "member", routes: [agent("GET", "account-environment")], since: "0.8.0" },
|
|
23
|
+
{ verb: "environment propose", does: "propose the workspace-wide environment declaration (names only)", needs: "admin", routes: [agent("POST", "account-environment/propose")], since: "0.8.0" },
|
|
24
|
+
{ verb: "environment approve", does: "approve the workspace-wide environment declaration", needs: "admin", routes: [agent("POST", "account-environment/approve")], since: "0.8.0" },
|
|
25
|
+
{ verb: "secret set|import", does: "enter a repository secret's value from this terminal, never echoed", needs: "admin", routes: [], since: "0.8.0" },
|
|
26
|
+
{ verb: "accounts", does: "read the workspace's coding accounts and which can take work", needs: "member", routes: [], since: "0.5.0" },
|
|
27
|
+
{ 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" },
|
|
28
|
+
{ 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" },
|
|
29
|
+
{ 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" },
|
|
30
|
+
{ verb: "release", does: "release a parked ticket or a whole failure class back to work", needs: "admin", routes: [agent("POST", "ticket-release"), agent("POST", "ticket-release-class")], since: "0.6.0" },
|
|
31
|
+
{ verb: "mcp add|list|remove", does: "register an MCP server for the workspace by vault-secret name", needs: "admin", routes: [agent("POST", "mcp")], since: "0.9.0" },
|
|
32
|
+
];
|
|
33
|
+
/** Judge each capability against the routes a contract serves. `null` means no contract is cached,
|
|
34
|
+
* so nothing that rides a route can be called available; local and read-plane verbs still are. */
|
|
35
|
+
export function reportCapabilities(routes) {
|
|
36
|
+
return CAPABILITIES.map((c) => {
|
|
37
|
+
if (c.routes.length === 0)
|
|
38
|
+
return { ...c, availability: "available", missing: [] };
|
|
39
|
+
if (routes === null)
|
|
40
|
+
return { ...c, availability: "cloud_unread", missing: [] };
|
|
41
|
+
const missing = c.routes.filter((r) => !routes.some((s) => s.method === r.method && s.path === r.path));
|
|
42
|
+
return { ...c, availability: missing.length === 0 ? "available" : "needs_newer_cloud", missing };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export async function cmdCapabilities(args, ctx) {
|
|
46
|
+
const manifest = readManifest();
|
|
47
|
+
const cache = readContractCache(ctx.home);
|
|
48
|
+
const report = reportCapabilities(cache === null ? null : cache.doc.routes);
|
|
49
|
+
if (args.json) {
|
|
50
|
+
ctx.stdout(JSON.stringify({
|
|
51
|
+
cli: { name: "catalyst", version: manifest.version },
|
|
52
|
+
contract: cache === null ? null : { version: cache.contractVersion, fetchedAt: cache.fetchedAt },
|
|
53
|
+
capabilities: report,
|
|
54
|
+
}));
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
ctx.stdout(cache === null
|
|
58
|
+
? `catalyst ${manifest.version}; no contract cached yet, so nothing that rides a cloud route can be called available: run \`catalyst contract\` after connecting`
|
|
59
|
+
: `catalyst ${manifest.version} against contract ${cache.contractVersion} (cached ${cache.fetchedAt})`);
|
|
60
|
+
const width = Math.max(...report.map((r) => r.verb.length));
|
|
61
|
+
for (const r of report) {
|
|
62
|
+
const why = r.availability === "needs_newer_cloud"
|
|
63
|
+
? ` — this cloud does not serve ${r.missing.map((m) => `${m.method} ${m.path}`).join(", ")}`
|
|
64
|
+
: r.availability === "cloud_unread"
|
|
65
|
+
? " — rides a cloud route; no contract cached"
|
|
66
|
+
: "";
|
|
67
|
+
ctx.stdout(`${r.verb.padEnd(width)} ${r.needs.padEnd(6)} ${r.availability.padEnd(17)} ${r.does}${why}`);
|
|
68
|
+
}
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -23,7 +23,10 @@ import { cmdAsk } from "./ask.js";
|
|
|
23
23
|
import { cmdMcp } from "./mcp.js";
|
|
24
24
|
import { cmdRelease } from "./release.js";
|
|
25
25
|
import { cmdEnvironment } from "./environment.js";
|
|
26
|
+
import { cmdEnv } from "./env.js";
|
|
26
27
|
import { cmdSecret } from "./secret.js";
|
|
28
|
+
import { cmdTeam } from "./team.js";
|
|
29
|
+
import { cmdCapabilities } from "./capabilities.js";
|
|
27
30
|
import { cmdIdentity } from "./identity.js";
|
|
28
31
|
import { cmdConnections } from "./connections.js";
|
|
29
32
|
export { CONFIG_MODE, DEFAULT_BASE_URL, FIRST_STAMPED_VERSION, LEGACY_PACKAGE_NAME, PACKAGE_NAME, PROVENANCE_MARKER, CliError, MeError, UsageError, configPathFor, contractPathFor, defaultCtx, defaultReplicaDbFor, defaultSkillsDirFor, fetchMe, formatMode, installedBundleVersion, installSkills, loadConfig, modernCliPath, normalizeBaseUrl, parseArgs, parseChangelogEntry, parseProvenanceVersion, readChangelog, readManifest, resolveSkillsDir, saveConfig, skillsSourceDir, updateNoticeLine, writeConfig, };
|
|
@@ -37,6 +40,7 @@ export const CUSTOMER_SKILLS = [
|
|
|
37
40
|
"run-this-project",
|
|
38
41
|
"unstick",
|
|
39
42
|
"what-needs-me",
|
|
43
|
+
"what-this-repo-needs",
|
|
40
44
|
"whats-happening",
|
|
41
45
|
];
|
|
42
46
|
/** `login` is the verb; `join` is the 0.1 name, kept as a deprecated alias for one minor version. */
|
|
@@ -49,26 +53,29 @@ export function usageText() {
|
|
|
49
53
|
`${PACKAGE_NAME} — the Catalyst Cloud customer CLI: connect to your tenant, read through the SDK, write through the agent proxy`,
|
|
50
54
|
"",
|
|
51
55
|
"Usage:",
|
|
52
|
-
" catalyst
|
|
53
|
-
" catalyst
|
|
54
|
-
" catalyst
|
|
55
|
-
" catalyst
|
|
56
|
-
" catalyst
|
|
57
|
-
" catalyst
|
|
58
|
-
" catalyst
|
|
59
|
-
" catalyst
|
|
60
|
-
" catalyst
|
|
61
|
-
" catalyst
|
|
62
|
-
" catalyst
|
|
63
|
-
" catalyst
|
|
64
|
-
" catalyst
|
|
65
|
-
" catalyst
|
|
66
|
-
" catalyst
|
|
67
|
-
" catalyst
|
|
68
|
-
" catalyst
|
|
69
|
-
" catalyst
|
|
70
|
-
" catalyst
|
|
71
|
-
" catalyst
|
|
56
|
+
" catalyst login [--base-url <url>] [--start-replica] (keyless: logs you in as yourself)",
|
|
57
|
+
" catalyst login --key <personal-key> [--base-url <url>] (or CATALYST_CLOUD_TOKEN, for a key)",
|
|
58
|
+
" catalyst join ... (deprecated alias of login; removed in the next minor version)",
|
|
59
|
+
" catalyst install [--skills-dir <dir>] [--force] (repair path; your agent's own command installs the skills)",
|
|
60
|
+
" catalyst status | notice | me | ready | accounts",
|
|
61
|
+
" catalyst mcp add|list|remove (vault references only)",
|
|
62
|
+
" catalyst contract [--refresh] [--path <a.b.c>]",
|
|
63
|
+
" catalyst query <issues|issue <id>|pulls|pull <id>|projects|cycles|search <terms>|changes --since <cursor|head>>",
|
|
64
|
+
" catalyst replica <start [--detach]|stop|status [--probe]|sql \"<select>\"|schema [table]>",
|
|
65
|
+
" catalyst runtime <status [--json]|install|path|uninstall> (a pinned Node this CLI manages itself)",
|
|
66
|
+
" catalyst events <tail|wait-for|query|status [--probe]> [--type NAME] [--ticket CTC-N] [--after SEQUENCE]",
|
|
67
|
+
" catalyst explain <ticket> | history <ticket> | running [--ticket T --phase P] | queue [--team K]",
|
|
68
|
+
" catalyst watch [--team K] [--ticket T]... [--project P] [--exec CMD]",
|
|
69
|
+
" catalyst write <comment|state|label|create|reaction|attachment|session> ...",
|
|
70
|
+
" catalyst ask <raise|accept|list> ...",
|
|
71
|
+
" catalyst release <ticket> --because <what changed> [--retry-unchanged] [--dry-run] | release --class <c> --team <K> ...",
|
|
72
|
+
" catalyst environment [read] | environment propose --file <path>|--stdin [--approve] | environment approve (your account; needs login)",
|
|
73
|
+
" catalyst env <inventory [path] | check <file>> (this repository, offline — no login, no network)",
|
|
74
|
+
" catalyst secret set <NAME> --repo <owner/name> [--command '<cmd>'] | secret import <file> --repo <owner/name>",
|
|
75
|
+
" catalyst team <list|check|map|adopt|migrate|checklist> ...",
|
|
76
|
+
" catalyst capabilities [--json] (what this CLI can do, the role each verb needs, and whether this cloud serves it)",
|
|
77
|
+
" catalyst identity linear <status|options|set> [<linearUserId>] [--json]",
|
|
78
|
+
" catalyst connections personal <linear|github> <start|status> [--wait <seconds>] [--json]",
|
|
72
79
|
"",
|
|
73
80
|
"Every verb takes --help. --json makes the output machine-readable.",
|
|
74
81
|
"",
|
|
@@ -156,13 +163,13 @@ function maybePrintUpdateNotice(args, ctx) {
|
|
|
156
163
|
refreshed = installSkills(skillsDir, { force: false, onlyExisting: true });
|
|
157
164
|
}
|
|
158
165
|
catch (err) {
|
|
159
|
-
ctx.stderr(`[catalyst
|
|
166
|
+
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}`);
|
|
160
167
|
return;
|
|
161
168
|
}
|
|
162
169
|
if (refreshed.installed.length > 0)
|
|
163
|
-
say(`[catalyst
|
|
170
|
+
say(`[catalyst] refreshed ${refreshed.installed.join(", ")} at ${skillsDir} to ${manifest.version}`);
|
|
164
171
|
for (const s of refreshed.skipped) {
|
|
165
|
-
say(`[catalyst
|
|
172
|
+
say(`[catalyst] left "${s.name}" alone: ${skillsDir}/${s.name} was not installed by this package (catalyst install --force to replace)`);
|
|
166
173
|
}
|
|
167
174
|
cfg.lastSkillBundleVersion = manifest.version;
|
|
168
175
|
cfg.skillsDir = skillsDir;
|
|
@@ -241,8 +248,14 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
241
248
|
return await cmdRelease(args, ctx);
|
|
242
249
|
case "environment":
|
|
243
250
|
return await cmdEnvironment(args, ctx, deps.environment ?? {});
|
|
251
|
+
case "env":
|
|
252
|
+
return await cmdEnv(args, ctx, deps.env ?? {});
|
|
244
253
|
case "secret":
|
|
245
254
|
return await cmdSecret(args, ctx, deps.secret ?? {});
|
|
255
|
+
case "team":
|
|
256
|
+
return await cmdTeam(args, ctx, deps.team);
|
|
257
|
+
case "capabilities":
|
|
258
|
+
return await cmdCapabilities(args, ctx);
|
|
246
259
|
case "identity":
|
|
247
260
|
return await cmdIdentity(args, ctx, deps.identity ?? {});
|
|
248
261
|
case "connections":
|
|
@@ -260,17 +273,17 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
260
273
|
return 1;
|
|
261
274
|
}
|
|
262
275
|
if (err instanceof CliError) {
|
|
263
|
-
ctx.stderr(`catalyst
|
|
276
|
+
ctx.stderr(`catalyst: ${err.message}`);
|
|
264
277
|
return err.exitCode;
|
|
265
278
|
}
|
|
266
279
|
if (err instanceof MeError) {
|
|
267
|
-
ctx.stderr(`catalyst
|
|
280
|
+
ctx.stderr(`catalyst: ${err.message}`);
|
|
268
281
|
return 2;
|
|
269
282
|
}
|
|
270
283
|
throw err;
|
|
271
284
|
}
|
|
272
285
|
}
|
|
273
|
-
const VERB_HELP_KNOWN = Object.fromEntries(["login", "join", "install", "status", "notice", "me", "contract", "query", "replica", "runtime", "events", "explain", "running", "queue", "watch", "write", "ask", "ready", "accounts", "release", "secret", "environment", "connections", "identity", "mcp"].map((v) => [v, true]));
|
|
286
|
+
const VERB_HELP_KNOWN = Object.fromEntries(["login", "join", "install", "status", "notice", "me", "contract", "query", "replica", "runtime", "events", "explain", "running", "queue", "watch", "write", "ask", "ready", "accounts", "release", "secret", "env", "environment", "connections", "identity", "mcp", "team", "capabilities"].map((v) => [v, true]));
|
|
274
287
|
async function cmdLogin(args, ctx, deps) {
|
|
275
288
|
const manifest = readManifest();
|
|
276
289
|
const key = (args.key ?? ctx.env.CATALYST_CLOUD_TOKEN ?? "").trim();
|
|
@@ -317,7 +330,7 @@ async function cmdLogin(args, ctx, deps) {
|
|
|
317
330
|
ctx.stdout(`Connected as ${me.user.label} (${me.user.role})${me.user.linearUserId ? "" : " — your Linear identity is not matched yet, so \"what needs me\" will show everyone's asks until an admin matches it in Settings"}`);
|
|
318
331
|
}
|
|
319
332
|
else {
|
|
320
|
-
ctx.stderr(`[catalyst
|
|
333
|
+
ctx.stderr(`[catalyst] this is the tenant's account key (a host credential), not your own — the skills work, but nothing your agent writes will carry your name and "what needs me" cannot mean you. Mint a personal key at Settings → API keys and log in with that.`);
|
|
321
334
|
}
|
|
322
335
|
ctx.stdout(`Config written to ${written.path} (mode ${formatMode(written.mode)}, holds your ${auth ? "login session" : "key"} and the CLI path)${written.mode === CONFIG_MODE ? "" : ` — expected ${formatMode(CONFIG_MODE)}; chmod it by hand`}`);
|
|
323
336
|
ctx.stdout(`Tenant contract range: ${manifest.tenantContractRange}`);
|
|
@@ -327,7 +340,7 @@ async function cmdLogin(args, ctx, deps) {
|
|
|
327
340
|
}
|
|
328
341
|
catch (err) {
|
|
329
342
|
if (err instanceof CliError && (err.code === "contract-forbidden" || err.code === "contract-version")) {
|
|
330
|
-
ctx.stderr(`[catalyst
|
|
343
|
+
ctx.stderr(`[catalyst] ${err.message}`);
|
|
331
344
|
}
|
|
332
345
|
else {
|
|
333
346
|
throw err;
|
|
@@ -379,7 +392,7 @@ function cmdStatus(ctx) {
|
|
|
379
392
|
ctx.stdout(`Bundle: ${PACKAGE_NAME} ${manifest.version} (tenant contract range: ${manifest.tenantContractRange})`);
|
|
380
393
|
if (cfg.cliPath)
|
|
381
394
|
ctx.stdout(`CLI: ${cfg.cliPath}${existsSync(cfg.cliPath) ? "" : " (missing — re-run login)"}`);
|
|
382
|
-
ctx.stdout(`Contract: ${existsSync(contractPathFor(ctx.home)) ? contractPathFor(ctx.home) : "not cached (run: catalyst
|
|
395
|
+
ctx.stdout(`Contract: ${existsSync(contractPathFor(ctx.home)) ? contractPathFor(ctx.home) : "not cached (run: catalyst contract --refresh)"}`);
|
|
383
396
|
return 0;
|
|
384
397
|
}
|
|
385
398
|
/** A short human relative time for an ISO expiry, e.g. "in 14m", "in 2h", "in 6d", or "now" once past. */
|
package/dist/config.js
CHANGED
|
@@ -96,7 +96,7 @@ export const INSTALL_COMMAND = "curl -fsSL https://staging.catalystcloud.dev/ins
|
|
|
96
96
|
/** The one upgrade command every hint prints: update the installed package, then log in again so
|
|
97
97
|
* customer.json records the new launcher path. */
|
|
98
98
|
export function upgradeCommand(root) {
|
|
99
|
-
return `npm install -g ${updatePackageName(root)}@latest && catalyst
|
|
99
|
+
return `npm install -g ${updatePackageName(root)}@latest && catalyst login`;
|
|
100
100
|
}
|
|
101
101
|
/**
|
|
102
102
|
* CTC-3479 — logins before this release recorded bin/catalyst-skills.js as cliPath. That launcher is
|
package/dist/connections.js
CHANGED
|
@@ -24,9 +24,9 @@ function statusLine(provider, result) {
|
|
|
24
24
|
case "connected":
|
|
25
25
|
return `Personal ${name}: connected as ${result.provider === "linear" ? result.linearUserId : result.githubLogin}`;
|
|
26
26
|
case "absent":
|
|
27
|
-
return `Personal ${name}: not connected — run catalyst
|
|
27
|
+
return `Personal ${name}: not connected — run catalyst connections personal ${provider} start`;
|
|
28
28
|
case "lapsed":
|
|
29
|
-
return `Personal ${name}: connection expired — run catalyst
|
|
29
|
+
return `Personal ${name}: connection expired — run catalyst connections personal ${provider} start`;
|
|
30
30
|
case "unavailable":
|
|
31
31
|
return `Personal ${name}: temporarily unavailable — the grant state is unknown; retry status later`;
|
|
32
32
|
default:
|
|
@@ -40,7 +40,7 @@ export async function cmdConnections(args, ctx, deps = {}) {
|
|
|
40
40
|
const { provider, action, waitSeconds } = parseCommand(args);
|
|
41
41
|
const cfg = requireConfig(ctx);
|
|
42
42
|
if (!cfg.user)
|
|
43
|
-
throw new CliError("this machine uses a tenant account key, not a member credential — run catalyst
|
|
43
|
+
throw new CliError("this machine uses a tenant account key, not a member credential — run catalyst login as yourself", "member-required");
|
|
44
44
|
const createClient = deps.createClient ?? (async (options) => (await loadHttpSdk()).createTenantClient(options));
|
|
45
45
|
const client = await createClient({ key: await bearerFor(ctx, cfg), baseUrl: cfg.baseUrl, fetch: ctx.fetch });
|
|
46
46
|
if (action === "status") {
|
|
@@ -62,7 +62,7 @@ export async function cmdConnections(args, ctx, deps = {}) {
|
|
|
62
62
|
if (args.json)
|
|
63
63
|
ctx.stdout(JSON.stringify(started));
|
|
64
64
|
else
|
|
65
|
-
ctx.stdout(`After approval, run: catalyst
|
|
65
|
+
ctx.stdout(`After approval, run: catalyst connections personal ${provider} status`);
|
|
66
66
|
return 0;
|
|
67
67
|
}
|
|
68
68
|
const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
@@ -79,7 +79,7 @@ export async function cmdConnections(args, ctx, deps = {}) {
|
|
|
79
79
|
else {
|
|
80
80
|
ctx.stdout(statusLine(provider, status));
|
|
81
81
|
if (status.outcome === "absent" || status.outcome === "lapsed")
|
|
82
|
-
ctx.stdout(`Approval has not appeared yet; run catalyst
|
|
82
|
+
ctx.stdout(`Approval has not appeared yet; run catalyst connections personal ${provider} status later.`);
|
|
83
83
|
}
|
|
84
84
|
return status.outcome === "connected" ? 0 : status.outcome === "absent" || status.outcome === "lapsed" ? 2 : 1;
|
|
85
85
|
}
|