@catalyst-cloud/cli 0.8.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.
Files changed (157) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/LICENSE +21 -0
  3. package/README.md +205 -0
  4. package/bin/catalyst-skills.js +8 -0
  5. package/bin/catalyst.js +5 -0
  6. package/bin/launch.js +154 -0
  7. package/dist/args.js +280 -0
  8. package/dist/ask.js +161 -0
  9. package/dist/browser.js +20 -0
  10. package/dist/cli.js +397 -0
  11. package/dist/config.js +241 -0
  12. package/dist/contract-types.js +4 -0
  13. package/dist/contract.js +184 -0
  14. package/dist/detach.js +10 -0
  15. package/dist/environment.js +207 -0
  16. package/dist/errors.js +27 -0
  17. package/dist/events.js +106 -0
  18. package/dist/execution.js +451 -0
  19. package/dist/oauth.js +300 -0
  20. package/dist/pagination.js +76 -0
  21. package/dist/prompt.js +35 -0
  22. package/dist/published.js +79 -0
  23. package/dist/query.js +248 -0
  24. package/dist/ready.js +380 -0
  25. package/dist/release.js +142 -0
  26. package/dist/replica.js +614 -0
  27. package/dist/runtime-store.js +135 -0
  28. package/dist/runtime-verb.js +66 -0
  29. package/dist/runtime.js +87 -0
  30. package/dist/sdk.js +29 -0
  31. package/dist/secret.js +190 -0
  32. package/dist/semver.js +18 -0
  33. package/dist/skill-shape.js +189 -0
  34. package/dist/skills.js +129 -0
  35. package/dist/transport.js +205 -0
  36. package/dist/ts-deps-loader.js +113 -0
  37. package/dist/watch/consumer.js +141 -0
  38. package/dist/watch/cursor-file.js +62 -0
  39. package/dist/watch.js +175 -0
  40. package/dist/write.js +224 -0
  41. package/package.json +60 -0
  42. package/skills/catalyst-github/SKILL.md +35 -0
  43. package/skills/catalyst-github/agents/openai.yaml +6 -0
  44. package/skills/catalyst-github/agents/portability.yaml +4 -0
  45. package/skills/catalyst-github/references/is-it-mergeable.md +57 -0
  46. package/skills/catalyst-github/references/what-a-pr-accumulates.md +61 -0
  47. package/skills/catalyst-github/scripts/is-it-mergeable.mjs +124 -0
  48. package/skills/catalyst-github/scripts/lib/cli.mjs +103 -0
  49. package/skills/catalyst-github/scripts/lib/credential.mjs +29 -0
  50. package/skills/catalyst-github/scripts/lib/pull.mjs +82 -0
  51. package/skills/catalyst-github/scripts/read-pr.mjs +97 -0
  52. package/skills/catalyst-linear/SKILL.md +43 -0
  53. package/skills/catalyst-linear/agents/openai.yaml +6 -0
  54. package/skills/catalyst-linear/agents/portability.yaml +5 -0
  55. package/skills/catalyst-linear/references/reading-a-ticket.md +52 -0
  56. package/skills/catalyst-linear/references/what-a-ticket-accumulates.md +53 -0
  57. package/skills/catalyst-linear/references/writing-to-linear.md +43 -0
  58. package/skills/catalyst-linear/scripts/comment.mjs +59 -0
  59. package/skills/catalyst-linear/scripts/create-ticket.mjs +44 -0
  60. package/skills/catalyst-linear/scripts/label.mjs +48 -0
  61. package/skills/catalyst-linear/scripts/lib/cli.mjs +164 -0
  62. package/skills/catalyst-linear/scripts/lib/credential.mjs +29 -0
  63. package/skills/catalyst-linear/scripts/move.mjs +41 -0
  64. package/skills/catalyst-linear/scripts/read-ticket.mjs +93 -0
  65. package/skills/catalyst-linear/scripts/search.mjs +49 -0
  66. package/skills/catalyst-onboard/SKILL.md +57 -0
  67. package/skills/catalyst-onboard/agents/openai.yaml +6 -0
  68. package/skills/catalyst-onboard/agents/portability.yaml +5 -0
  69. package/skills/catalyst-onboard/references/declaring-a-repository.md +23 -0
  70. package/skills/catalyst-onboard/references/skill-sources.md +35 -0
  71. package/skills/catalyst-onboard/references/the-one-path.md +149 -0
  72. package/skills/catalyst-onboard/references/what-a-phase-needs.md +46 -0
  73. package/skills/catalyst-onboard/references/what-the-browser-owns.md +50 -0
  74. package/skills/catalyst-onboard/references/who-fixes-what.md +44 -0
  75. package/skills/catalyst-onboard/scripts/lib/cli.mjs +117 -0
  76. package/skills/catalyst-onboard/scripts/lib/credential.mjs +29 -0
  77. package/skills/catalyst-onboard/scripts/where-am-i.mjs +345 -0
  78. package/skills/catalyst-setup/SKILL.md +36 -0
  79. package/skills/catalyst-setup/agents/openai.yaml +6 -0
  80. package/skills/catalyst-setup/agents/portability.yaml +4 -0
  81. package/skills/catalyst-setup/references/what-each-check-means.md +88 -0
  82. package/skills/catalyst-setup/scripts/check.mjs +75 -0
  83. package/skills/catalyst-setup/scripts/lib/cli.mjs +103 -0
  84. package/skills/catalyst-setup/scripts/lib/credential.mjs +29 -0
  85. package/skills/catalyst-setup/scripts/replica-status.mjs +46 -0
  86. package/skills/connect-me/SKILL.md +63 -0
  87. package/skills/connect-me/agents/openai.yaml +6 -0
  88. package/skills/connect-me/agents/portability.yaml +5 -0
  89. package/skills/connect-me/references/keeping-the-replica-running.md +88 -0
  90. package/skills/connect-me/scripts/lib/cli.mjs +185 -0
  91. package/skills/connect-me/scripts/lib/credential.mjs +29 -0
  92. package/skills/connect-me/scripts/verify-connection.mjs +68 -0
  93. package/skills/how-catalyst-works/SKILL.md +43 -0
  94. package/skills/how-catalyst-works/agents/openai.yaml +6 -0
  95. package/skills/how-catalyst-works/agents/portability.yaml +4 -0
  96. package/skills/how-catalyst-works/references/coding-accounts.md +51 -0
  97. package/skills/how-catalyst-works/references/stages-and-mapping.md +56 -0
  98. package/skills/how-catalyst-works/references/the-ladder.md +41 -0
  99. package/skills/how-catalyst-works/references/what-catalyst-is.md +30 -0
  100. package/skills/how-catalyst-works/references/what-runs-next.md +77 -0
  101. package/skills/how-catalyst-works/references/when-a-phase-fails.md +57 -0
  102. package/skills/how-catalyst-works/scripts/explain-ticket.mjs +41 -0
  103. package/skills/how-catalyst-works/scripts/lib/cli.mjs +164 -0
  104. package/skills/how-catalyst-works/scripts/lib/credential.mjs +29 -0
  105. package/skills/how-catalyst-works/scripts/show-my-map.mjs +94 -0
  106. package/skills/how-catalyst-works/scripts/whats-running.mjs +65 -0
  107. package/skills/run-this-project/SKILL.md +45 -0
  108. package/skills/run-this-project/agents/openai.yaml +6 -0
  109. package/skills/run-this-project/agents/portability.yaml +5 -0
  110. package/skills/run-this-project/assets/stall-policy.json +15 -0
  111. package/skills/run-this-project/references/making-work-ready.md +60 -0
  112. package/skills/run-this-project/references/reacting-to-events.md +76 -0
  113. package/skills/run-this-project/references/stalls-and-escalation.md +63 -0
  114. package/skills/run-this-project/scripts/lib/cli.mjs +185 -0
  115. package/skills/run-this-project/scripts/lib/credential.mjs +29 -0
  116. package/skills/run-this-project/scripts/make-ready.mjs +64 -0
  117. package/skills/run-this-project/scripts/scope-status.mjs +0 -0
  118. package/skills/run-this-project/scripts/watch-scope.mjs +61 -0
  119. package/skills/unstick/SKILL.md +41 -0
  120. package/skills/unstick/agents/openai.yaml +6 -0
  121. package/skills/unstick/agents/portability.yaml +5 -0
  122. package/skills/unstick/references/playbook.md +51 -0
  123. package/skills/unstick/scripts/lib/cli.mjs +135 -0
  124. package/skills/unstick/scripts/lib/credential.mjs +29 -0
  125. package/skills/unstick/scripts/unstick.mjs +57 -0
  126. package/skills/what-needs-me/SKILL.md +41 -0
  127. package/skills/what-needs-me/agents/openai.yaml +6 -0
  128. package/skills/what-needs-me/agents/portability.yaml +5 -0
  129. package/skills/what-needs-me/references/raising-a-decision.md +41 -0
  130. package/skills/what-needs-me/references/reading-the-inbox.md +38 -0
  131. package/skills/what-needs-me/references/settling-an-answer.md +37 -0
  132. package/skills/what-needs-me/scripts/inbox.mjs +56 -0
  133. package/skills/what-needs-me/scripts/lib/cli.mjs +135 -0
  134. package/skills/what-needs-me/scripts/lib/credential.mjs +29 -0
  135. package/skills/what-needs-me/scripts/raise.mjs +53 -0
  136. package/skills/what-needs-me/scripts/settle.mjs +73 -0
  137. package/skills/whats-happening/SKILL.md +43 -0
  138. package/skills/whats-happening/agents/openai.yaml +6 -0
  139. package/skills/whats-happening/agents/portability.yaml +4 -0
  140. package/skills/whats-happening/assets/status-reply.json +77 -0
  141. package/skills/whats-happening/references/reading-the-board.md +43 -0
  142. package/skills/whats-happening/references/reprioritising.md +37 -0
  143. package/skills/whats-happening/references/routing-work.md +36 -0
  144. package/skills/whats-happening/references/status-reply.md +34 -0
  145. package/skills/whats-happening/references/why-is-it-stuck.md +62 -0
  146. package/skills/whats-happening/scripts/explain.mjs +28 -0
  147. package/skills/whats-happening/scripts/lib/cli.mjs +135 -0
  148. package/skills/whats-happening/scripts/lib/credential.mjs +29 -0
  149. package/skills/whats-happening/scripts/snapshot.mjs +149 -0
  150. package/vendor/README.md +9 -0
  151. package/vendor/paths/index.d.ts +85 -0
  152. package/vendor/paths/index.js +148 -0
  153. package/vendor/paths/legacy-installer.d.ts +36 -0
  154. package/vendor/paths/legacy-installer.js +154 -0
  155. package/vendor/paths/node.d.ts +18 -0
  156. package/vendor/paths/node.js +102 -0
  157. package/vendor/paths/provenance.json +17 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,75 @@
1
+ # Changelog
2
+
3
+ ## 0.8.0
4
+
5
+ `catalyst-skills ready` loads the SDK on Node 26, the current Homebrew default, and has since 0.7.0: the type-stripping loader tries Node's `strip` mode before `transform`, because Node 26 accepts only `strip`. The 0.7.0 entry did not say so. If you saw `sdk: could not load` on Node 26 on 0.5.0 or earlier, updating is the fix.
6
+
7
+ The supported runtime range is now declared in one place and every message names it accurately: Node 22.15 or newer (22.15 is where `node:module.registerHooks` arrives, which the SDK's TypeScript dependencies need), or bun 1.4 or newer (1.4 is where `node:sqlite` arrives, which the replica needs). The old advice to "run under Node 22.15 or newer, or under bun" was wrong for any bun older than 1.4: that bun has no `node:sqlite` at all and could not run this CLI, so the suggestion sent you to a runtime that could not even start. No message in this package recommends bun any more without saying which version.
8
+
9
+ The CLI itself no longer dies part-way through loading on a runtime it does not support. `node:sqlite` used to be imported at the top of a module every verb loads, so a bun without it aborted the whole process before `ready` ever got a chance to explain why — you saw a raw `ResolveMessage`, not a fix. The engine now loads on first use, and its absence becomes a named `runtime` check with the one command that fixes it, on every runtime, every time.
10
+
11
+ That one command is `catalyst-skills runtime install`. It downloads a pinned Node release into this CLI's own cache, verifies it against that release's published checksum before unpacking anything, and uses it from then on — without touching your machine's default Node and without admin rights. Run it any time `ready` reports the runtime as unsupported, or ahead of time if you would rather not manage your system Node at all.
12
+
13
+ ## 0.7.0
14
+
15
+ `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.
16
+
17
+ `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.
18
+
19
+ A list read now returns the whole scope or says plainly that it did not. `catalyst-skills query issues --all` and `query pulls --all` follow the cloud's page cursor to the end of the scope instead of stopping at the first page, and a read without `--all` that was cut short now prints `truncated at N of M` on stderr, where M is the full count the cloud reports for the same scope. `catalyst-skills ask list` — and the what-needs-me skill that wraps it — reads every page, so a workspace with more than a few hundred tickets no longer gets an inbox that quietly omits the asks past the first page, or scores the ones it does show against a partial view of what they hold.
20
+
21
+ Every skill this bundle ships now carries a routing case proving it still fires on a sentence its own description promises, and a deterministic check that fails by name the moment a description drops a phrase a case relies on — this repository's own test suite now catches a broken trigger before it ever reaches an install. A publish also runs a security scanner over every skill's scripts first, so a script that reads a sensitive directory or embeds an instruction override blocks the release unless someone has written down why it does not.
22
+
23
+ ## 0.6.1
24
+
25
+ The local replica writer no longer retries a failing snapshot pull forever. After a failed or incomplete pull it backs off with jitter (30s doubling to a 15-minute cap) and gives up after five consecutive failures, recording why; a pull that completes resets the count. `catalyst-skills replica status` and `catalyst-skills ready` both name the stopped state, the count, the last error, and the command that restarts it. Until the read side of a large snapshot is safe, `ready` no longer suggests starting the replica at all — it says plainly that the replica is optional and off by default for large tenants, and every read still works through the API either way.
26
+
27
+ ## 0.6.0
28
+
29
+ A new `catalyst-onboard` skill walks you from nothing to your first ticket running, one step at a time. You install the bundle yourself and type `/catalyst-onboard`; from there your agent does each step, shows you what actually came back, and stops — no page is fetched and no instruction arrives from anywhere but the skill you installed. It reads setup as five separate parts, each with the instrument that owns it: this machine (`status` and the machine half of `ready`), you (`me`), your account's Linear workspace, the projects that have been mapped, and the repositories that have been registered. Nothing answers for a part it does not own, so a project that is not ready is reported as a project finding with the tenant owner who can fix it and the page it is on — never as something to retry here. `node scripts/where-am-i.mjs` prints the whole reading, `--next` reduces it to the one next step, and every page link it prints is built from the cloud this machine is connected to rather than typed from memory.
30
+
31
+ It also says plainly what it cannot do. Approving the login, connecting Linear and installing the GitHub App are browser steps by construction and always will be. Seeing every project you could set up, mapping stages, adopting the workflow, registering a repository and declaring an environment are settings-page steps today because the routes behind them take a browser session rather than a key; the skill hands you the page for each, says it is a gap rather than the design, and never guesses at a route. Two silences are called out because both read as absence and are not: an empty project list means nothing is mapped yet, not that you have no projects, and a registered repository is registered and nothing more — that list carries no status and no project attachment, so it never proves work can be dispatched into it.
32
+
33
+ `catalyst-skills environment` is new, and it is the first setup action an agent can perform end to end. The tenant-wide environment declaration — the names your builds need — has been callable with your own login since the routes shipped, and nothing could call it, so every part of setting a tenant up was a page in the app. `environment` reads the current revision, whether it is approved, and which revision a phase's checkout actually carries (a proposal nobody approved changes nothing, and the verb says so); `environment propose --file <path>` proposes one from a JSON file or stdin, `--expect-revision` refuses if someone moved it since you read it, and `--approve` approves exactly the revision the propose returned. `environment approve` on its own reads the current revision and approves that, so the compare-and-set the cloud requires never depends on a hash copied between two commands. Reading needs any active seat; proposing and approving need an admin or owner one, and the cloud's refusal says which. The route paths come from your tenant's own contract and the derived ones are checked against it, so a cloud that does not serve them yet is named as older instead of answering 404. Repository-scope declarations are still a page. `catalyst-onboard`'s step for declaring what the containers need is now a command rather than a handover, and its report reads the declaration back.
34
+
35
+ The package description and the README no longer count the skills or list them in prose. A count written in a sentence cannot be kept true by anything, and the last one was wrong in the direction that made a correct install report itself as failed.
36
+
37
+ ## 0.5.0
38
+
39
+ The local replica can now keep the durable tenant event backbone beside its entity database. `catalyst-skills replica start` owns both synchronizers in one process, while `catalyst-skills events tail`, `events wait-for`, and `events query` read the bounded local cache with exact event-type and ticket filters. A failed event synchronizer is reported without stopping a healthy entity replica. This requires `@catalyst-cloud/sdk` 0.10.0 or newer.
40
+
41
+ You can release a stuck ticket yourself now. `catalyst-skills release <ticket> --because "<what changed>"` asks your tenant to release every park or hold on the ticket the right way for each one — after repeated failures, a spent repair-round cap, a repair that changed nothing, a validate failure that recurred — or to release nothing and name, for each thing it will not release, what a person has to do instead (close their own pull request, read a review that will not converge, answer the ask for a spent repair budget). `--dry-run` previews it and changes nothing; `--retry-unchanged` says something changed that the cloud cannot see, such as an outage that ended; `release --class <failure-class> --team <key>` does the same for every ticket on one team parked for the same reason. A refusal exits 1. Every release is recorded against your login, and `explain --history` now prints what holds the ticket and the ticket's past releases. The new `unstick` skill runs the whole loop — read why nothing runs, read the history, judge whether the cause is fixed, preview, release — and raises an ask only for what needs a person; the desk, the project owner and the failure references no longer say a park needs an operator. The eligibility explanation knows seven more reasons, including a parked phase and a person's own pull request. Needs a cloud that serves the release route (tenant contract 1.6.0); an older cloud says so.
42
+
43
+ Every skill's scripts now work for a machine connected with the keyless login. Since 0.4.0 a keyless `login` stores a login session and no key, and every skill script except the new `unstick` one read that as "not connected" and stopped before doing anything; only a personal key worked. The scripts now accept either, spawn the CLI, and let it authenticate and refresh the session as it always has. The check lives in one file vendored into every skill, so the next kind of credential lands once, and the not-connected line now names the keyless login first.
44
+
45
+ ## 0.4.1
46
+
47
+ `explain` now tells you when a whole team cannot start work. A team whose stages are not saved (no stage chosen for dispatch, PR, done and canceled, or a chosen stage since deleted in Linear) gets no dispatch queue at all, and `explain` used to answer a ticket in that team's Todo with "state Todo is not a dispatch state" — which sent people looking at the wrong setting. It now says the ticket cannot start because the team has no saved stage mapping, names the missing stages, and names the fix: a tenant owner or admin opens Settings → Linear teams, picks the team and presses Map my stages (or Adopt the Catalyst workflow). Needs a cloud that sends the team's dispatch gate; an older cloud prints what it did before. The reason table's wording for `workflow_mapping_unknown` and `ordering_never_published` no longer reads as a passing hiccup, since neither clears on its own for an unmapped team. The references now say plainly that `teams[].gitAutomation` in the contract is a stored consent for a feature that is not built — nothing reads it, so it never starts or stops work — where they used to say enabling it could delete a team's review automation. The catalyst-setup reference gains a section on setting up one team at a time as a pilot: what starts, what stays where it is, and that no other team's stages or tickets change.
48
+
49
+ ## 0.4.0
50
+
51
+ You can connect keyless now: run `catalyst-skills login` with no key and it opens a browser device-code login — it prints a short code and a URL, you approve in your browser (or from your phone on a machine with no browser), and this machine is connected as you, with nothing to mint or paste. The short-lived session token refreshes silently on every request and the config is rewritten atomically each time, so you stay connected for months and log in again only if the session is revoked or you have been away long enough to lapse — then one clear line tells you to run `login`. `customer.json` now holds exactly one of a login session (`auth`) or a personal key; `status` names which ("Credential: your login (expires …)" vs "Credential: personal key"). The personal key rail is unchanged and remains the fallback for scripts and unattended shells — `--key` or `CATALYST_CLOUD_TOKEN` — and keyless is the preferred rail everywhere the docs and the connect-me skill lead with it. Under the hood the auth layer is reworked around one credential provider (SDK 0.9): the replica and the live watch use the SDK's bearer auth strategy and resolve a fresh token per connect, so an OAuth session rotates underneath a long-running watch without a reconnect, and every HTTP read and write refreshes on the same path.
52
+
53
+ ## 0.3.1
54
+
55
+ `ready` now names the person you connected as: when your key is a personal key, the config line reads "joined <tenant> as <you> (<role>)" instead of the generic "as service" that every api key showed, and an account (host) key still names the tenant with no person. `ready` also learns the bundle version your tenant expects, when the cloud publishes one: if this installed bundle is older than that minimum you get a one-line warning that names both versions and the upgrade command, but it stays a note — `ready` never refuses over a stale bundle, and a cloud that does not publish a minimum changes nothing. `explain` no longer calls every ticket with no dispatch row "unknown": a ticket that merely sits in Backlog (or any non-dispatch state) is now named as known to the mirror with the state it is in, and only a ticket the mirror has never seen keeps the unknown wording.
56
+
57
+ ## 0.3.0
58
+
59
+ You connect with your own personal key now, not the tenant's shared account key: mint it yourself at Settings → API keys in the Catalyst Cloud app (every member can; no admin needed), and `login` prints who you connected as beside which tenant. Your agent acts as you — an ask it raises names you, and "what needs me" means you: `ask list` (and the what-needs-me skill) keeps only the asks assigned to your Linear user by default, `--anyone` lists the whole tenant, and when your Linear identity is not matched yet it says so and shows everything rather than an empty list. `customer.json` gains a `user` block (id, label, role, your Linear user id); an older config is still read unchanged. Connecting with the account key still works — it is the right credential for a host or daemon — but `login` tells you it names no person. Every skill, reference and help line that said "account key", or said execution history and coding-account status were not readable, has been rewritten: `explain --history` and `accounts` read them, and the two things your key genuinely cannot do (release a park, read PR labels or reactions) are named instead. The catalyst-setup reference now lists all eleven team checks, including `environment_declared`. Needs a cloud that admits personal keys on the agent routes; on an older cloud the contract read says so by name, and the account key still works until it is updated.
60
+
61
+ ## 0.2.1
62
+
63
+ Fixes five defects found by running 0.2.0's verbs against a live tenant. Three skills (catalyst-github, catalyst-setup, how-catalyst-works) carried a colon-and-space inside an unquoted description, which YAML reads as a nested map rather than text, so `npx skills add` skipped them and still reported success — every Codex, Cursor and OpenCode install of 0.2.0 quietly got five of the eight skills, and reinstalling with this version gives you all eight. `running` no longer asks for lease attributions it has no coordinates for, so the headline "what's happening?" verb works instead of failing for every tenant; the lease read is still available as `running --ticket T --phase P`. The CLI no longer truncates its own output at 64KB when a skill reads it through a pipe, which made `query issues --json` on a tenant past roughly eighty tickets return invalid JSON with a success code. `accounts` now actually reads your coding-account slots and renders them, `history <ticket>` (also `explain --history`) shows a ticket's execution history, and both say plainly when your cloud is older than this bundle rather than printing an empty success. `queue` with no `--team` reads every team on your tenant instead of being refused, as its help always promised. `query changes` works at all now: it reads the feed's NDJSON, where before it called any successful response a non-JSON body — nothing noticed because the documented `--since 0` is refused on any tenant whose feed has rotated, so a success was never reached. That refusal now names a cursor you can actually use, and `--since head` starts from now.
64
+
65
+ ## 0.2.0
66
+
67
+ Eight skills named from the executive's seat (whats-happening, what-needs-me, run-this-project, catalyst-setup, catalyst-linear, catalyst-github, how-catalyst-works, connect-me) replace the six; reads and the live event stream go through the Catalyst Cloud SDK and writes through the tenant's agent proxy, all behind catalyst-skills verbs (me, contract, query, replica, explain, running, queue, watch, write, ask, ready, accounts), with every stage id, label id, threshold and route read from the tenant contract; Node 22 is now required; installing the skills has moved to your agent's own command (the Claude Code plugin marketplace in this repository, or npx skills add for Codex, Cursor, OpenCode and the rest) and the CLI no longer copies them, leaving install only as a repair path; catalyst-skills login replaces join, which stays as a deprecated alias for one minor version, and login now prompts for the key without echoing it when no CATALYST_CLOUD_TOKEN is set; every SKILL.md declares allowed-tools scoped to this package's binary, so no skill needs a blanket shell grant; your existing customer.json is still read unchanged and gains the CLI path the next time you run catalyst-skills login, which also caches the tenant contract; run catalyst-skills ready to confirm.
68
+
69
+ ## 0.1.1
70
+
71
+ The bundle now lives and publishes from its own public repository, coalesce-labs/catalyst-cloud-skills, so every skill can be read before it is installed; the README leads with the CATALYST_CLOUD_TOKEN form of join, and join's output no longer names an internal ticket.
72
+
73
+ ## 0.1.0
74
+
75
+ First published customer skill bundle: vendored customer editions of concierge, steward, ask and linearis plus the new join and setup skills, one-command tenant discovery via GET /api/v1/me, config written to ~/.config/catalyst-cloud/customer.json, and a placeholder 0.x tenant contract range (CTC-1924 in flight).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Coalesce Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,205 @@
1
+ # @catalyst-cloud/catalyst-skills
2
+
3
+ [![skills.sh](https://skills.sh/b/coalesce-labs/catalyst-cloud-skills)](https://skills.sh/coalesce-labs/catalyst-cloud-skills)
4
+
5
+ ## Install
6
+
7
+ Run the installer, then type `/catalyst-onboard` in your coding agent:
8
+
9
+ ```sh
10
+ curl -fsSL https://staging.catalystcloud.dev/install.sh | sh
11
+ ```
12
+
13
+ The installer puts the CLI and both skill packs on this machine and connects it with one browser approval. It ends by printing the next step. Then open a new session of your coding agent in any directory and type `/catalyst-onboard` (`$catalyst-onboard` in Codex). The onboarding skill reads where this machine stands and walks you through the rest one step at a time. It asks before it writes anything to your machine. Everything else on this page is the reference it follows.
14
+
15
+ This repository supplies skills for setting up and operating a Catalyst Cloud tenant. A coding workstation also uses [`coalesce-labs/catalyst-dev-skills`](https://github.com/coalesce-labs/catalyst-dev-skills) for research, planning, implementation, review, and shipping.
16
+
17
+ Or by hand. One command, for every coding agent on the machine:
18
+
19
+ On an existing machine, inspect same-named skill paths before running either add command. The
20
+ commands replace existing directories and links; the inspection rule is below.
21
+
22
+ ```sh
23
+ npx skills@latest add coalesce-labs/catalyst-cloud-skills --all -g
24
+ ```
25
+
26
+ It installs every skill in the bundle for each agent it detects (Claude Code, Codex, Cursor, OpenCode and the rest). A coding workstation also installs the development pack:
27
+
28
+ ```sh
29
+ npx skills@latest add coalesce-labs/catalyst-dev-skills --all -g
30
+ ```
31
+
32
+ The two packs have different jobs and independent versions. Omit `-g` for a project-scoped Cloud skills install. Copied skills do not auto-update. Re-adding the pack also picks up newly added skills; `npx skills update` refreshes only names already in the lock. Before an add or refresh, inspect the active global lock at `$XDG_STATE_HOME/skills/.skill-lock.json` when XDG state is set, or `~/.agents/.skill-lock.json` otherwise. A project install uses its own `skills-lock.json`. Check every same-named agent path, not just the canonical lock entry. Proceed only if each destination is absent or a verified, unmodified copy of the intended pack or its symlink. Leave independent, changed, or uncertain copies in place. Do not schedule raw add commands as an unattended refresh. After that check, re-run the Cloud add command above with `-g` for a workstation or without `-g` inside a project.
33
+
34
+ <details><summary><strong>Alternative for Claude Code: the plugin marketplace</strong></summary>
35
+
36
+ The plugin installs this pack's same `skills/` tree as a managed bundle that updates when we ship. It needs a GitHub SSH key, and it does not load into the session you are already in — run `/reload-plugins` or restart afterwards. Pick one rail for this pack; installing both leaves you with every skill twice. The development pack has its own optional Claude plugin, `catalyst-dev@catalyst-dev-skills`.
37
+
38
+ ```
39
+ /plugin marketplace add coalesce-labs/catalyst-cloud-skills
40
+ /plugin install catalyst@catalyst-cloud
41
+ ```
42
+ </details>
43
+
44
+ <details><summary><strong>One agent at a time</strong></summary>
45
+
46
+ Codex:
47
+
48
+ ```sh
49
+ npx skills@latest add coalesce-labs/catalyst-cloud-skills -a codex
50
+ ```
51
+
52
+ Cursor:
53
+
54
+ ```sh
55
+ npx skills@latest add coalesce-labs/catalyst-cloud-skills -a cursor
56
+ ```
57
+
58
+ OpenCode, Amp, Windsurf and the rest:
59
+
60
+ ```sh
61
+ npx skills@latest add coalesce-labs/catalyst-cloud-skills
62
+ ```
63
+
64
+ Without `--all` the installer asks which skills to take and which agents to install them on.
65
+ </details>
66
+
67
+ ### Then connect to your tenant
68
+
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
+
71
+ ```sh
72
+ npm install -g @catalyst-cloud/catalyst-skills
73
+ catalyst-skills login
74
+ catalyst-skills ready
75
+ ```
76
+
77
+ `catalyst-skills 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 @catalyst-cloud/catalyst-skills 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
+
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
+
81
+ ```sh
82
+ CATALYST_CLOUD_TOKEN=<your-personal-key> catalyst-skills login
83
+ ```
84
+
85
+ `--key <your-personal-key>` is the third form, for a script.
86
+
87
+ That is the whole setup. Everything below explains what you just installed.
88
+
89
+ ## What this is
90
+
91
+ 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-skills` 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.
92
+
93
+ ## What connecting does
94
+
95
+ `login` does three things: it calls `GET /api/v1/me` with your credential, which discovers your tenant and who you are from the credential alone; it writes `~/.config/catalyst-cloud/customer.json` with file mode `0600`, holding your credential (a keyless login session, or a personal key), who you are (id, label, role, your Linear user id) and the absolute path of the CLI so every skill script can spawn the same binary; and it fetches the tenant contract (`GET /api/v1/agent/contract`) and caches it at `~/.config/catalyst-cloud/contract.json` with its ETag, so stage names and ids, label ids, the ask template, thresholds and the route table come from your tenant, live, and no skill restates them.
96
+
97
+ 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`.
98
+
99
+ `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-skills status` prints which tenant this machine is connected to and as whom, `catalyst-skills ready` prints one READY or NOT READY verdict with the fix for each failure and who can apply it, and `catalyst-skills --version` prints the package version and its pinned tenant contract range.
100
+
101
+ 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.
102
+
103
+ ## Requirements
104
+
105
+ - 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-skills ready` names the exact reason when the runtime is too old, and `catalyst-skills 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.
106
+ - 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.
107
+ - 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.
108
+ - 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.
109
+
110
+ ## First use
111
+
112
+ Open a new session and ask about your own tenant. The skills read the config `login` wrote. For example:
113
+
114
+ - What's happening? Where are we, why is that stuck, what closed, what's next?
115
+ - What needs me?
116
+ - Run this project for me until it closes.
117
+ - Am I set up?
118
+
119
+ ## What has to be running
120
+
121
+ 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:
122
+
123
+ | process | needed for | what it holds on disk | lifetime |
124
+ | --- | --- | --- | --- |
125
+ | none | every read, write, ask and explain | `customer.json` and `contract.json` | the default after login |
126
+ | `catalyst-skills 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 |
127
+ | `catalyst-skills 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 |
128
+
129
+ The check every skill runs first is `catalyst-skills 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-skills replica stop` stops a detached writer.
130
+
131
+ `catalyst-skills events tail` follows new cached events, `events wait-for --type ... --ticket ... --timeout ...` performs a bounded wait, and `events query` reads retained history. These commands never write the cache or contact the cloud. 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.
132
+
133
+ 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.
134
+
135
+ ## The skills
136
+
137
+ | skill | what the person says | what it does | file |
138
+ | --- | --- | --- | --- |
139
+ | `catalyst-onboard` | "Set me up. Onboard me. I just signed up — what do I do first?" | Walks you from nothing to your first ticket running, one step at a time: connect this machine, connect Linear, map one project, register one repository, then watch a card move. Reads each part of setup with the instrument that owns it and says who can fix anything unfinished and where. Hands over the steps only a browser can do instead of pretending to have done them. | [`SKILL.md`](skills/catalyst-onboard/SKILL.md) |
140
+ | `whats-happening` | "What's happening? Where are we? Why is that stuck? What's next?" | The desk for a tenant: reads the contract, what is running and queued, the eligibility explainer and the open asks, and answers in one reply with ticket ids. Routes work to a project owner and decisions to `what-needs-me`. | [`SKILL.md`](skills/whats-happening/SKILL.md) |
141
+ | `what-needs-me` | "What needs me? What am I blocking?" | The human's decision inbox, ranked by what each answer releases, and the one way an agent raises a decision on their behalf: files an ask through the cloud's ask route with the tenant's own template and records the answer so the held work releases. | [`SKILL.md`](skills/what-needs-me/SKILL.md) |
142
+ | `run-this-project` | "Run this project for me. Own it until it closes." | Single-threaded owner of one project: subscribes to the tenant stream for its scope, reacts to each change in the same turn, makes tickets ready and moves them to dispatch, parks what should stop, chases stalls, escalates inward, and keeps one status summary current. Never polls. | [`SKILL.md`](skills/run-this-project/SKILL.md) |
143
+ | `catalyst-setup` | "Am I set up? What is missing?" | Machine readiness plus tenant readiness from the contract's per-team checks, in one verdict: what passes, what is blocked, what is merely waiting, and who can click what. Reports; never repairs. | [`SKILL.md`](skills/catalyst-setup/SKILL.md) |
144
+ | `catalyst-linear` | "Show me the ticket, the history, what Catalyst wrote on it." | Reads a ticket with its comments, relations, labels, linked pull requests and agent sessions inline, from the replica when fresh and the API otherwise, always naming the source; writes comments, card moves, labels and new tickets as the app actor; knows what a ticket accumulates as Catalyst works it. | [`SKILL.md`](skills/catalyst-linear/SKILL.md) |
145
+ | `catalyst-github` | "Show me the PR, the checks, the review, the queue." | A ticket's pull request with its checks, reviews and review threads; whether it is mergeable under the repository's policy; what a PR accumulates as the ticket moves (the branch, the draft, the rewrite, the force-pushes, the labels, the queue). | [`SKILL.md`](skills/catalyst-github/SKILL.md) |
146
+ | `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) |
147
+ | `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) |
148
+ | `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) |
149
+
150
+ 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.
151
+
152
+ ## What a key cannot see yet
153
+
154
+ Your personal key reads everything the skills need — tickets, pull requests, the eligibility explainer, the dispatch queue, fleet activity, per-ticket execution history (`catalyst-skills explain --history <ticket>`: phase attempts, remediation rounds, park state) and coding-account status (`catalyst-skills 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-skills 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-skills 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-skills secret import .env --repo owner/name` stores every name in the file and lists the declared names that still have no value. `catalyst-skills 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.env.json`, which `catalyst-setup` already reports on, is a separate thing. One thing it cannot do, and the skills say so by name rather than guess:
155
+
156
+ - Read pull-request labels or the reviewer's reaction. The mirror does not carry them; GitHub's own page does.
157
+
158
+ ## Versions and origins
159
+
160
+ The package pins the tenant contract range `1.x`, recorded in `package.json` under `catalystCloud.tenantContractRange`, and reports it in `--version`, `status` and `login`. A tenant whose contract version falls outside that range is refused with one line naming both versions; update the bundle. Every skill carries a `vendored-from:` line naming this package as its origin, and that line now also carries the version it was vendored at; all of them are written in this repository for customer tenants.
161
+
162
+ ## What it writes on your machine
163
+
164
+ - `~/.config/catalyst-cloud/customer.json`, written with mode `0600`, holding your personal key, who you are, and the CLI path.
165
+ - `~/.config/catalyst-cloud/contract.json`, the cached tenant contract.
166
+ - `~/.config/catalyst-cloud/published.json`, the cached answer to "what is the newest release" — no credential in it.
167
+ - 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`.
168
+
169
+ The skill files themselves are written by whichever install command you ran, in that tool's own location. Your personal key goes into that one config file and nowhere else.
170
+
171
+ ## Updating
172
+
173
+ 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/catalyst-skills@latest && catalyst-skills 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 @catalyst-cloud/catalyst-skills@latest login`. The next `catalyst-skills` run prints a one-line notice:
174
+
175
+ ```
176
+ [catalyst-skills] updated 0.1.1 → 0.2.0: <that version's CHANGELOG.md summary> · update with: npm install -g @catalyst-cloud/catalyst-skills@latest && catalyst-skills login
177
+ ```
178
+
179
+ 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-skills login`.
180
+
181
+ `catalyst-skills 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.
182
+
183
+ ## Uninstalling
184
+
185
+ 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:
186
+
187
+ ```sh
188
+ catalyst-skills replica stop
189
+ rm -f ~/.config/catalyst-cloud/customer.json ~/.config/catalyst-cloud/contract.json ~/.config/catalyst-cloud/published.json ~/.config/catalyst-cloud/watch-cursor.json
190
+ 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
191
+ rm -rf "${XDG_STATE_HOME:-$HOME/.local/state}/catalyst/events"
192
+ npm uninstall -g @catalyst-cloud/catalyst-skills
193
+ ```
194
+
195
+ ## If login fails
196
+
197
+ - `catalyst-skills: 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.
198
+ - `catalyst-skills: GET /me failed (403): account-not-operational` — the tenant is suspended. This is an admin conversation on the tenant, not a local fix.
199
+ - `catalyst-skills: 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`.
200
+ - A line naming two contract versions after `Connected to` — the tenant serves a contract outside this bundle's `1.x` range. The config is written; update the bundle before using the other skills.
201
+ - `[catalyst-skills] 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.
202
+
203
+ ## License
204
+
205
+ MIT — see [LICENSE](LICENSE). How to contribute and how releases happen are described in [CONTRIBUTING.md](CONTRIBUTING.md). The install commands above are one canonical block kept in [`.agents/install-block.md`](.agents/install-block.md); change them there first.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ // `catalyst-skills`: the deprecated name of `catalyst` (CTC-3479). It runs the same program and adds
3
+ // one line on stderr that names `catalyst` (after the program's output on a pipe, before it on a
4
+ // terminal; see launch.js). Kept at this path because older logins recorded it in customer.json as
5
+ // cliPath; the next run moves that record onto bin/catalyst.js. CTC-3484 removes it.
6
+ import { launch } from "./launch.js";
7
+
8
+ await launch("catalyst-skills", import.meta.url);
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ // `catalyst`: the command @catalyst-cloud/cli installs (CTC-3479). The launcher lives in ./launch.js.
3
+ import { launch } from "./launch.js";
4
+
5
+ await launch("catalyst", import.meta.url);
package/bin/launch.js ADDED
@@ -0,0 +1,154 @@
1
+ // Shared ESM launcher for @catalyst-cloud/cli (CTC-1926, CTC-3479). tsc does not carry a shebang
2
+ // through emit, so each bin is a tiny wrapper around this module and the program lives in dist/cli.js.
3
+ // bin/catalyst.js is the command; bin/catalyst-skills.js is its deprecated second name. Two files,
4
+ // not one file that sniffs process.argv[1]: npm's Windows shims pass the real script path, so the
5
+ // invoked name is only reliable as the file that was run.
6
+ //
7
+ // ⛔ NEVER `process.exit(code)` STRAIGHT AFTER A WRITE. When stdout is a PIPE — which is how every
8
+ // skill script spawns this CLI — writes are asynchronous, so `process.exit` severs whatever is
9
+ // still in flight AND STILL REPORTS THE ORIGINAL EXIT CODE. The caller then reads a truncated body
10
+ // with a success code and has no way to tell it from a complete one: `query issues --json` on a
11
+ // tenant with ~80+ tickets came back as 65,528 bytes of invalid JSON, exit 0 (0.2.0). Interactive
12
+ // use hid it, because a tty gives stdout a synchronous write path.
13
+ //
14
+ // So: park the code on `process.exitCode`, wait for both streams to drain, and only then exit. The
15
+ // explicit exit stays — `fetch`'s keep-alive sockets can hold the event loop open for seconds after
16
+ // the work is done, and a CLI that lingers is its own defect — but by then nothing is buffered.
17
+ //
18
+ // ⛔ THE PREFLIGHT BELOW RUNS BEFORE dist/cli.js IS IMPORTED (CTC-2158). It answers "can THIS
19
+ // runtime run the CLI, and if not, what is the one command?" using the SAME dist/runtime.js verdict
20
+ // every other message in the package reads — one voice, not a second copy of the floor logic.
21
+ // Importing dist/runtime.js, dist/runtime-store.js and dist/config.js here is safe on every runtime
22
+ // measured for this ticket (Node 22.14, Node 26.8.1, bun 1.3.14, bun 1.4.2): none of the three
23
+ // imports node:sqlite or the SDK, which are the only two things that used to abort module loading
24
+ // before main() ever ran. `runtime` itself is EXEMPT from every refusal below, or the fix command
25
+ // could not be run on the broken runtime it exists to fix.
26
+ //
27
+ // Policy (CATALYST_SKILLS_RUNTIME overrides: auto | pinned | ambient):
28
+ // • a pin is installed (and the policy is not "ambient") -> re-exec into it, always. A customer
29
+ // who ran `runtime install` asked for a runtime independent of the machine's default Node — this
30
+ // is where they get it (Tier 2).
31
+ // • auto (default), ambient supported, no pin -> run in process. ZERO extra process: skill
32
+ // scripts spawn this CLI constantly, and doubling process startup on every call is a real cost.
33
+ // • ambient unsupported, no pin -> print the verdict and the ONE command; exit 1, never a raw
34
+ // module-resolution error.
35
+ import { fileURLToPath } from "node:url";
36
+
37
+ /** The one stderr line the deprecated name prints in addition to what the program prints. */
38
+ export const DEPRECATED_NAME_LINE =
39
+ "catalyst-skills: this command name is deprecated. Run catalyst instead, with the same arguments.";
40
+
41
+ /**
42
+ * Run the CLI with this process's arguments. `invokedAs` is the command name the person typed,
43
+ * fixed by which bin file ran; `binUrl` is that bin's import.meta.url, which a re-exec into the
44
+ * pinned runtime runs again so the child keeps the same name.
45
+ *
46
+ * Where the deprecated name's line goes depends on who reads stderr. Skill scripts spawn the CLI
47
+ * through a pipe and report the FIRST stderr line as the error, so on a pipe the line comes last,
48
+ * after the program's own output. On a terminal a person reads it, and a long-running verb such as
49
+ * `watch` may never finish, so there it comes first. A re-exec prints nothing itself: the child is
50
+ * the same bin and prints the line once.
51
+ */
52
+ export async function launch(invokedAs, binUrl) {
53
+ const argv = process.argv.slice(2);
54
+ const deprecated = invokedAs === "catalyst-skills";
55
+ const handled = argv[0] === "runtime" ? false : await preflight(invokedAs, binUrl, argv, deprecated);
56
+ if (handled) return;
57
+ const noticeFirst = deprecated && process.stderr.isTTY === true;
58
+ if (noticeFirst) process.stderr.write(`${DEPRECATED_NAME_LINE}\n`);
59
+ const noticeLast = deprecated && !noticeFirst;
60
+ import("../dist/cli.js")
61
+ .then((m) => m.main(argv))
62
+ .then((code) => finish(code, noticeLast))
63
+ .catch((err) => {
64
+ console.error(`${invokedAs}: failed to load: ${err instanceof Error ? err.message : String(err)}`);
65
+ finish(1, noticeLast);
66
+ });
67
+ }
68
+
69
+ /** Returns true when it already handled (and exited) the invocation. */
70
+ async function preflight(invokedAs, binUrl, args, deprecated) {
71
+ const policy = process.env.CATALYST_SKILLS_RUNTIME ?? "auto";
72
+ const home = process.env.CATALYST_SKILLS_HOME ?? process.env.HOME ?? "/";
73
+
74
+ let runtimeStore, runtimeMod, config;
75
+ try {
76
+ [runtimeStore, runtimeMod, config] = await Promise.all([import("../dist/runtime-store.js"), import("../dist/runtime.js"), import("../dist/config.js")]);
77
+ } catch {
78
+ // dist/ itself failed to resolve for some other reason — fall through to the normal import
79
+ // in launch(), whose own .catch() will surface that loudly rather than hiding it here.
80
+ return false;
81
+ }
82
+
83
+ const pin = policy === "ambient" ? null : runtimeStore.readPin(home);
84
+ if (pin) {
85
+ const { existsSync } = await import("node:fs");
86
+ if (existsSync(pin.nodePath)) {
87
+ const { spawnSync } = await import("node:child_process");
88
+ const res = spawnSync(pin.nodePath, [fileURLToPath(binUrl), ...args], {
89
+ stdio: "inherit",
90
+ env: { ...process.env, CATALYST_SKILLS_RUNTIME: "ambient" },
91
+ });
92
+ await finish(res.status ?? 1, false);
93
+ return true;
94
+ }
95
+ }
96
+
97
+ if (policy === "pinned") {
98
+ console.error(`${invokedAs}: CATALYST_SKILLS_RUNTIME=pinned but no pinned runtime is installed. Run: ${runtimeMod.FIX_COMMAND}`);
99
+ await finish(1, deprecated);
100
+ return true;
101
+ }
102
+
103
+ let manifest;
104
+ try {
105
+ manifest = config.readManifest();
106
+ } catch (err) {
107
+ console.error(`${invokedAs}: ${err instanceof Error ? err.message : String(err)}`);
108
+ await finish(1, deprecated);
109
+ return true;
110
+ }
111
+
112
+ // CATALYST_SKILLS_RUNTIME_FACTS is a test-only injection seam: it lets the launcher's own tests
113
+ // drive an unsupported/supported runtime without actually installing one.
114
+ const facts = process.env.CATALYST_SKILLS_RUNTIME_FACTS ? JSON.parse(process.env.CATALYST_SKILLS_RUNTIME_FACTS) : runtimeMod.detectRuntime();
115
+ const verdict = runtimeMod.runtimeVerdict(facts, manifest.enginesNode);
116
+ if (!verdict.supported) {
117
+ console.error(`${invokedAs}: ${verdict.line}`);
118
+ if (verdict.reason) console.error(` ${verdict.reason}`);
119
+ if (verdict.fix) console.error(` fix: ${verdict.fix}`);
120
+ await finish(1, deprecated);
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Resolve once every byte already handed to `stream` has reached the other end. The empty write is
128
+ * ordered behind the real ones, so its callback is the drain signal; a destroyed or errored stream
129
+ * (a reader that hung up) resolves immediately rather than hanging the process forever.
130
+ */
131
+ function drained(stream) {
132
+ return new Promise((resolve) => {
133
+ if (!stream || stream.destroyed || stream.writableEnded) return resolve();
134
+ let settled = false;
135
+ const done = () => {
136
+ if (settled) return;
137
+ settled = true;
138
+ resolve();
139
+ };
140
+ stream.once("error", done);
141
+ try {
142
+ stream.write("", done);
143
+ } catch {
144
+ done();
145
+ }
146
+ });
147
+ }
148
+
149
+ async function finish(code, notice) {
150
+ process.exitCode = code;
151
+ if (notice) process.stderr.write(`${DEPRECATED_NAME_LINE}\n`);
152
+ await Promise.all([drained(process.stdout), drained(process.stderr)]);
153
+ process.exit(code);
154
+ }