@catalyst-cloud/catalyst-skills 0.4.0 → 0.7.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 (73) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/README.md +19 -12
  3. package/dist/args.js +34 -4
  4. package/dist/ask.js +6 -3
  5. package/dist/cli.js +19 -5
  6. package/dist/config.js +4 -0
  7. package/dist/contract.js +6 -0
  8. package/dist/environment.js +207 -0
  9. package/dist/events.js +106 -0
  10. package/dist/execution.js +146 -18
  11. package/dist/pagination.js +76 -0
  12. package/dist/published.js +75 -0
  13. package/dist/query.js +41 -18
  14. package/dist/ready.js +110 -19
  15. package/dist/release.js +142 -0
  16. package/dist/replica.js +296 -30
  17. package/dist/semver.js +18 -0
  18. package/dist/skill-shape.js +10 -0
  19. package/dist/skills.js +52 -1
  20. package/dist/transport.js +5 -2
  21. package/dist/ts-deps-loader.js +46 -1
  22. package/package.json +7 -4
  23. package/skills/catalyst-github/SKILL.md +1 -1
  24. package/skills/catalyst-github/scripts/lib/cli.mjs +3 -2
  25. package/skills/catalyst-github/scripts/lib/credential.mjs +29 -0
  26. package/skills/catalyst-linear/SKILL.md +1 -1
  27. package/skills/catalyst-linear/references/reading-a-ticket.md +2 -0
  28. package/skills/catalyst-linear/scripts/lib/cli.mjs +4 -3
  29. package/skills/catalyst-linear/scripts/lib/credential.mjs +29 -0
  30. package/skills/catalyst-onboard/SKILL.md +49 -0
  31. package/skills/catalyst-onboard/agents/openai.yaml +6 -0
  32. package/skills/catalyst-onboard/agents/portability.yaml +5 -0
  33. package/skills/catalyst-onboard/references/the-one-path.md +143 -0
  34. package/skills/catalyst-onboard/references/what-the-browser-owns.md +50 -0
  35. package/skills/catalyst-onboard/references/who-fixes-what.md +41 -0
  36. package/skills/catalyst-onboard/scripts/lib/cli.mjs +117 -0
  37. package/skills/catalyst-onboard/scripts/lib/credential.mjs +29 -0
  38. package/skills/catalyst-onboard/scripts/where-am-i.mjs +245 -0
  39. package/skills/catalyst-setup/SKILL.md +3 -3
  40. package/skills/catalyst-setup/references/what-each-check-means.md +22 -6
  41. package/skills/catalyst-setup/scripts/lib/cli.mjs +3 -2
  42. package/skills/catalyst-setup/scripts/lib/credential.mjs +29 -0
  43. package/skills/catalyst-setup/scripts/replica-status.mjs +1 -1
  44. package/skills/connect-me/SKILL.md +1 -1
  45. package/skills/connect-me/scripts/lib/cli.mjs +3 -2
  46. package/skills/connect-me/scripts/lib/credential.mjs +29 -0
  47. package/skills/how-catalyst-works/SKILL.md +1 -1
  48. package/skills/how-catalyst-works/references/stages-and-mapping.md +2 -2
  49. package/skills/how-catalyst-works/references/what-runs-next.md +1 -1
  50. package/skills/how-catalyst-works/references/when-a-phase-fails.md +4 -4
  51. package/skills/how-catalyst-works/scripts/lib/cli.mjs +4 -3
  52. package/skills/how-catalyst-works/scripts/lib/credential.mjs +29 -0
  53. package/skills/run-this-project/SKILL.md +2 -2
  54. package/skills/run-this-project/references/making-work-ready.md +1 -1
  55. package/skills/run-this-project/references/stalls-and-escalation.md +2 -2
  56. package/skills/run-this-project/scripts/lib/cli.mjs +3 -2
  57. package/skills/run-this-project/scripts/lib/credential.mjs +29 -0
  58. package/skills/unstick/SKILL.md +41 -0
  59. package/skills/unstick/agents/openai.yaml +6 -0
  60. package/skills/unstick/agents/portability.yaml +5 -0
  61. package/skills/unstick/references/playbook.md +51 -0
  62. package/skills/unstick/scripts/lib/cli.mjs +135 -0
  63. package/skills/unstick/scripts/lib/credential.mjs +29 -0
  64. package/skills/unstick/scripts/unstick.mjs +57 -0
  65. package/skills/what-needs-me/SKILL.md +1 -1
  66. package/skills/what-needs-me/references/reading-the-inbox.md +1 -1
  67. package/skills/what-needs-me/scripts/lib/cli.mjs +3 -2
  68. package/skills/what-needs-me/scripts/lib/credential.mjs +29 -0
  69. package/skills/whats-happening/SKILL.md +2 -2
  70. package/skills/whats-happening/references/why-is-it-stuck.md +9 -4
  71. package/skills/whats-happening/scripts/lib/cli.mjs +3 -2
  72. package/skills/whats-happening/scripts/lib/credential.mjs +29 -0
  73. package/skills/whats-happening/scripts/snapshot.mjs +4 -0
@@ -5,7 +5,7 @@ description: >-
5
5
  allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
6
6
  disable-model-invocation: true
7
7
  ---
8
- <!-- vendored-from: @catalyst-cloud/catalyst-skills — written in this repository for customer tenants -->
8
+ <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.7.0 — written in this repository for customer tenants -->
9
9
 
10
10
  # Catalyst Linear
11
11
 
@@ -48,3 +48,5 @@ Per-ticket execution history beyond the comments (the attempt ledger, the remedi
48
48
  ## Searching and listing
49
49
 
50
50
  `node scripts/search.mjs <terms>` matches ticket identifiers and titles, PR titles, project and initiative names and returns a few of each. It is the only search; a list read with a filter is not a search and will hand back the ordinary first page, which reads as a false "not found". Lists (`catalyst-skills query issues --team <key>`, `query projects`, `query cycles`) are for a board view, not for finding one ticket.
51
+
52
+ A list verb now says when the cloud cut it short: without `--all`, `query issues`/`query pulls` print `truncated at N of M` on stderr the moment a scope holds more than one page. `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 — use `--all` when the count, not just a sample, has to be right.
@@ -6,9 +6,10 @@ import { spawnSync } from "node:child_process";
6
6
  import { existsSync, readFileSync } from "node:fs";
7
7
  import { join } from "node:path";
8
8
  import { pathToFileURL } from "node:url";
9
+ import { CONNECT_COMMAND, hasCredential } from "./credential.mjs";
9
10
 
10
11
  export const PACKAGE = "@catalyst-cloud/catalyst-skills";
11
- export const CONNECT_HINT = `CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE} login`;
12
+ export const CONNECT_HINT = CONNECT_COMMAND;
12
13
 
13
14
  /** ~/.config/catalyst-cloud/customer.json, honouring CATALYST_SKILLS_HOME (used by tests) over HOME. */
14
15
  export function configPath() {
@@ -22,8 +23,8 @@ export function loadConfig() {
22
23
  if (!existsSync(path)) return { ok: false, reason: `no config at ${path}` };
23
24
  try {
24
25
  const cfg = JSON.parse(readFileSync(path, "utf8"));
25
- if (!cfg || typeof cfg.key !== "string" || typeof cfg.account !== "string") {
26
- return { ok: false, reason: `${path} is missing the key or account` };
26
+ if (!hasCredential(cfg) || typeof cfg.account !== "string") {
27
+ return { ok: false, reason: `${path} is missing a key or login, or the account` };
27
28
  }
28
29
  return { ok: true, cfg, path };
29
30
  } catch (err) {
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ // lib/credential.mjs — is this machine connected? The ONE place a skill script decides it, vendored
3
+ // byte-identical into every skill's scripts/lib/ from skill-lib/credential.mjs at the package root
4
+ // (`npm run skill-lib:sync`; a test fails on any drift). Skills install one directory at a time, so
5
+ // each carries its own copy. This file is a library — run a sibling script with --help for usage.
6
+ //
7
+ // customer.json carries exactly one credential: a personal key (`key`), or the keyless login's
8
+ // session (`auth`, the recommended rail). A script never reads either for its value: it spawns the
9
+ // catalyst-skills CLI, which authenticates with whichever is present and refreshes a login's token
10
+ // itself. A new credential kind lands here, once.
11
+
12
+ /** The command that connects this machine, as every not-connected line names it. */
13
+ export const CONNECT_COMMAND =
14
+ "npx @catalyst-cloud/catalyst-skills login (or, with a personal key: CATALYST_CLOUD_TOKEN=<your personal key> npx @catalyst-cloud/catalyst-skills login)";
15
+
16
+ /** True when `cfg` (parsed customer.json) holds a usable credential of either kind. Never throws. */
17
+ export function hasCredential(cfg) {
18
+ if (cfg === null || typeof cfg !== "object") return false;
19
+ const key = cfg["key"];
20
+ if (typeof key === "string" && key !== "") return true;
21
+ const login = cfg["auth"];
22
+ return (
23
+ login !== null &&
24
+ typeof login === "object" &&
25
+ login["kind"] === "oauth" &&
26
+ typeof login["refreshToken"] === "string" &&
27
+ login["refreshToken"] !== ""
28
+ );
29
+ }
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: catalyst-onboard
3
+ description: >-
4
+ Walk a person from nothing to their first Catalyst Cloud ticket running, one step at a time, hand-held. Use when someone says "set me up", "onboard me", "I just signed up", "get me started", "what do I do first", or when they have the skills installed and nothing else. Reads each part of the setup with the instrument that owns it — this machine, the person, the account, one project, one repository — never folding one into another, does every step a key can do through the catalyst-skills CLI, and for the steps only a browser can do hands over the exact page and says what to come back with. Never claims a step it did not watch succeed.
5
+ disable-model-invocation: true
6
+ allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
7
+ ---
8
+ <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.7.0 — written in this repository for customer tenants -->
9
+
10
+ # Onboard me
11
+
12
+ You take one person from "the skills are installed" to "a ticket is running on my own tenant". You do it **one step at a time**: do the step, show them what actually came back, say what it means and what is next, then stop and let them answer. You never print the whole ladder at them and you never batch several steps into one turn.
13
+
14
+ Setup has five parts and each is read by its own instrument: this **machine**, the **person**, the **account**, one **project** (a project is one Linear team), one **repository**. A failure in one is not a failure in another, and a failing part names who can fix it and where. Getting that wrong is the one mistake that wastes a person's afternoon: a project problem reported as a machine problem sends them retrying a local command that was never going to help.
15
+
16
+ ## Run first
17
+
18
+ Scripts are run, never read. Each prints `--help`.
19
+
20
+ - `node scripts/where-am-i.mjs` — every part, each with the instrument that read it, its verdict, and for anything unfinished who can fix it and the page it is on. Works before the machine is connected; that is one of the states it reports.
21
+ - `node scripts/where-am-i.mjs --next` — the same reading, reduced to the single next step.
22
+ - `node scripts/where-am-i.mjs --json` — the same document for you to branch on.
23
+
24
+ Start every session with it, and run it again after every step the person completes. It is the only thing that decides where you are.
25
+
26
+ ## Load on demand
27
+
28
+ | when | read |
29
+ | -- | -- |
30
+ | walking the steps — what each one does, what to read back, when it is done | `references/the-one-path.md` |
31
+ | anything reports not ready, or you are about to say who should fix something | `references/who-fixes-what.md` |
32
+ | the next step is a browser page, or a page said it worked and you have to confirm it | `references/what-the-browser-owns.md` |
33
+ | the person asks what Catalyst actually is, or how a ticket gets worked | the `how-catalyst-works` skill |
34
+ | the machine will not connect, or a login expired | the `connect-me` skill |
35
+ | setup is finished and they want the standing readiness verdict | the `catalyst-setup` skill |
36
+ | the first ticket did not start and you need the reason | the `how-catalyst-works` skill, then `unstick` |
37
+
38
+ ## Rules
39
+
40
+ - **One step, then stop.** Say what you are about to do, do it, show the real output, say what it means and what comes next. Never queue several steps into one message, and never move on from a step you did not watch finish.
41
+ - **Report what you observed, not what you expected.** Print the lines the command actually produced. "That worked" without the output it produced is the single easiest thing to get wrong here, and a person who later finds it did not work stops trusting every other step you reported.
42
+ - **Each part by its own instrument.** Read the machine with the machine's instrument and the project with the project's, and label every finding with the part it belongs to. The script does this for you; keep it that way when you summarize.
43
+ - **Not ready is a question about who, not a reason to retry.** When something reports not ready, name which check, who can fix it, and where. If the owner is not the person in front of you, say so and stop — re-running a local command cannot move a check that belongs to a tenant owner, an admin, or a browser page.
44
+ - **Never invent a count or a list.** Every number and every name comes from what a command printed. If you want to tell them how many projects are mapped, read it off the script's output; do not carry one over from an earlier turn.
45
+ - **Three steps belong to a browser and always will**: approving the login, connecting Linear, and installing the GitHub App. Hand over the page and say what you need back. Do not claim you did them.
46
+ - **Some steps a key cannot do yet.** Listing every project, saving a stage mapping, adopting the workflow, registering a repository and declaring one repository's environment are settings-page work today; a key-callable path for them is being built. Route those through the browser and say plainly that it is a gap, not the design. Never guess at a route for them.
47
+ - **The account-wide environment declaration is the exception, and the one setup write you can perform.** `catalyst-skills environment` reads it, proposes it and approves it. Use the verb; do not send them to a page for it.
48
+ - **Their tenant, as them.** Everything goes through the CLI and the person's own login. You never name another tenant, and you never ask for a key you could avoid — the keyless login needs nothing pasted.
49
+ - **Stop at a wall you cannot pass.** A suspended account, a seat that is not active, a person who is not an owner or admin where one is required: say what you found, name who can act, and stop. Do not loop.
@@ -0,0 +1,6 @@
1
+ interface:
2
+ display_name: "Onboard me"
3
+ short_description: "Walk a person from nothing to their first Catalyst Cloud ticket running, one step at a time, saying who owns each step"
4
+ default_prompt: "Use $catalyst-onboard to set me up on Catalyst Cloud, one step at a time."
5
+ policy:
6
+ allow_implicit_invocation: false
@@ -0,0 +1,5 @@
1
+ identity: { pack: catalyst-cloud-skills, skill: catalyst-onboard }
2
+ effects: [local-write, external-write]
3
+ mutating: true
4
+ invocation: explicit
5
+ exposure: [catalog]
@@ -0,0 +1,143 @@
1
+ # The one path
2
+
3
+ Eight steps, in this order. The order is the product's own: the tenant-side steps run Linear, then the project, then GitHub, then the repository, because each one is the cheapest place to catch the failure the next one would otherwise hide.
4
+
5
+ Walk them **one at a time**. Before each step say what you are about to do and why; after it, show what actually came back. `node scripts/where-am-i.mjs --next` decides which step you are on — never your memory of the last turn.
6
+
7
+ Each step below states: what it is for, what you run or hand over, **what you read back to prove it landed**, and **who owns it**.
8
+
9
+ ---
10
+
11
+ ## 0 — Where are we
12
+
13
+ **For:** starting from the truth instead of from an assumption. A person arrives here having done anything from nothing to most of it.
14
+
15
+ **You run:** `node scripts/where-am-i.mjs`
16
+
17
+ **Read back:** the whole thing, as it printed. Then say in one sentence which part is unfinished and whose it is.
18
+
19
+ **Owner:** you.
20
+
21
+ ---
22
+
23
+ ## 1 — Connect this machine
24
+
25
+ **For:** giving this machine a credential, so every later read is the person's own.
26
+
27
+ **You run:** `catalyst-skills login`. It prints a short code and a URL. The person approves it in a browser — from a phone, if this machine has none.
28
+
29
+ **Read back:** the `Connected to …` and `Connected as …` lines the command printed, then `node scripts/where-am-i.mjs` again so the machine part flips.
30
+
31
+ **Owner:** you run it; **the approval is the person's, in a browser, and always will be.** Wait for them. Do not re-run the command while a code is outstanding — that invalidates the code they are typing.
32
+
33
+ If it refuses, stop here and use the `connect-me` skill; it owns every failure mode of this step.
34
+
35
+ ---
36
+
37
+ ## 2 — Who you are
38
+
39
+ **For:** the person grain. A connected machine does not mean an active seat, and an active seat does not mean their Linear identity is matched.
40
+
41
+ **You run:** it is already in the step 0 script's `person` line.
42
+
43
+ **Read back:** their label and role, and whether their Linear identity is matched.
44
+
45
+ **Owner:** an unmatched Linear identity is fixed by a tenant owner or admin in Settings → Members. A seat that is not active is the same. Say which, and move on — neither blocks the steps below, but an unmatched identity means "what needs me" will show everyone's asks until it is fixed, and they should know that now rather than later.
46
+
47
+ ---
48
+
49
+ ## 3 — Connect Linear
50
+
51
+ **For:** the account grain. Nothing about a project can be read or mapped until the tenant's Linear workspace is connected.
52
+
53
+ **You hand over:** `<their cloud>/settings/connections`, and say: connect Linear.
54
+
55
+ **Read back:** after they say it is done, re-run `node scripts/where-am-i.mjs` and read them the `account` line. A resolved workspace is proof. If it still reads unresolved, say the contract may be cached and run `catalyst-skills contract --refresh`, then read it again.
56
+
57
+ **Owner:** a tenant owner or admin, in a browser. **This is a browser step by construction** — it is an authorization grant, and no key can perform one.
58
+
59
+ ---
60
+
61
+ ## 4 — Pick one project, and map its stages
62
+
63
+ **For:** the project grain. A project is one Linear team. Until a project's stages are mapped, a card moved into it does nothing at all — this is the single most common reason a new tenant sees no activity.
64
+
65
+ **You hand over:** `<their cloud>/settings/linear-teams`. They pick one project and press **Map my stages**, or **Adopt the Catalyst workflow** if they want Catalyst's own stages created for them.
66
+
67
+ **Read back:** re-run the script and read the `projects` line. A project that now appears with a readiness verdict is proof it was saved. Read them the verdict and any failing checks by name.
68
+
69
+ **Owner:** a tenant owner or admin. ⛔ **Listing the projects and saving a mapping are settings-page work today** — a key cannot do either yet, and a key-callable path is being built. Say that plainly; it is a gap in the product, not something they did wrong.
70
+
71
+ ⭐ **One project at a time is safe, and lead with this.** Mapping one project changes no other project's stages and moves no other project's tickets. Encourage a pilot: pick the project they care least about breaking.
72
+
73
+ ---
74
+
75
+ ## 5 — Install the GitHub App
76
+
77
+ **For:** the account grain again. Without it Catalyst can read tickets but cannot touch code.
78
+
79
+ **You hand over:** `<their cloud>/settings/connections`, and say: install the GitHub App, and grant it the repository they want worked.
80
+
81
+ **Read back:** it is confirmed by step 6 succeeding — a repository cannot be registered through an app that is not installed. Say that is what you are waiting for rather than claiming you verified it here.
82
+
83
+ **Owner:** a tenant owner or admin, in a browser. **Browser by construction**, same reason as step 3.
84
+
85
+ ---
86
+
87
+ ## 6 — Register the repository
88
+
89
+ **For:** the repository grain. Registering is what makes a repository something a project can dispatch work into.
90
+
91
+ **You hand over:** `<their cloud>/settings/repositories`, and say: add the repository, **and attach it to the project you mapped in step 4**.
92
+
93
+ **Read back:** re-run the script and read the `repositories` line. The repository appearing there is proof it was registered — and **only that**. It is not proof it can be dispatched to; see `references/who-fixes-what.md` for what registration does and does not prove.
94
+
95
+ **Owner:** a tenant owner or admin. ⛔ **Registering is settings-page work today**; a key-callable path is being built. ⛔ A repository registered without a project attached is the trap here: the call succeeds, the repository is listed, and nothing can ever dispatch into it. Make sure they attach the project in the same form, and say why.
96
+
97
+ ---
98
+
99
+ ## 7 — Declare what the containers need
100
+
101
+ **For:** the environment a phase runs in — the names of the variables and secrets the person's code needs. Names leave the machine; values are entered once, by them, in the app.
102
+
103
+ ⭐ **This is the one setup step you can actually do.** Every other step above is a page. This one is a command, and it is worth saying so to the person.
104
+
105
+ **You run:** `catalyst-skills environment` first, to read what the tenant already declares — the current revision, whether it is approved, and which revision a phase's checkout actually carries. Those last two are different things more often than people expect: a proposal that nobody approved changes nothing.
106
+
107
+ To change it, write the declaration to a JSON file and propose it:
108
+
109
+ ```sh
110
+ catalyst-skills environment propose --file declaration.json
111
+ ```
112
+
113
+ Add `--approve` to approve exactly the revision that propose just returned, which is the one-command form and the one to prefer — it is a compare-and-set, and nothing gets copied between two commands by hand. `catalyst-skills environment approve` on its own reads the current revision and approves that.
114
+
115
+ **Read back:** the revision and hash the command printed, and whether it says the declaration is now what a phase's checkout carries. If it prints `referenced but not set on this tenant yet`, read those names out: the declaration names them and the tenant has no value for them, so a phase that needs one will fail on it until someone adds it in the app.
116
+
117
+ **Owner:** you can read it from any active seat; proposing and approving need an admin or owner seat, and the cloud refuses with that sentence if the person does not have one — read the refusal to them rather than retrying.
118
+
119
+ ⛔ **Values never pass through you.** The declaration carries the *names* a build needs. The values are entered by the person, once, in the app, and nothing you run ever sees them. Say that plainly; a person asked for a secret by an agent is right to be suspicious.
120
+
121
+ ⛔ **Repository scope is still a page.** Anything only one repository needs lives in that repository's environment section under `<their cloud>/settings/repositories`, and the route behind it takes a browser session, not a key. Account scope is the half that is a command.
122
+
123
+ If they do not know what their build needs yet, skip this step. It blocks nothing until a phase needs a secret.
124
+
125
+ ---
126
+
127
+ ## 8 — Verify, then run the first ticket
128
+
129
+ **For:** the only thing that proves setup worked.
130
+
131
+ **You run:** `catalyst-skills ready`. Read them the verdict and every failing line, each with its own fix and owner. If it says NOT READY, go to `references/who-fixes-what.md` before you touch anything — a project check failing is not something re-running anything on this machine can fix.
132
+
133
+ **Then:** have them move one card into the project's dispatch stage, and watch. `catalyst-skills explain <ticket>` says why it is or is not about to run.
134
+
135
+ **Read back:** what `explain` actually said. If it says the ticket cannot start, the reason it names is the answer — read it to them and use the `how-catalyst-works` skill for what the reason means, then `unstick` if something is holding it.
136
+
137
+ **Owner:** the card move is theirs. The verdict is the tenant's.
138
+
139
+ ---
140
+
141
+ ## When you are done
142
+
143
+ Say what is set up, name anything still unfinished with its owner, and tell them the standing question "am I set up?" now belongs to the `catalyst-setup` skill, and "what's happening?" to `whats-happening`. You do not need to be invoked again.
@@ -0,0 +1,50 @@
1
+ # What the browser owns
2
+
3
+ Some steps you cannot do, and the honest thing is to say which and why. There are two kinds, and they deserve different sentences.
4
+
5
+ ## Kind one: browser by construction
6
+
7
+ These will never be a command, on any release. Each is an authorization a person grants in their own session; a credential that could perform one would defeat the point of it.
8
+
9
+ | step | where | what you say |
10
+ | -- | -- | -- |
11
+ | approving the login | the URL and short code that `catalyst-skills login` printed | "I have started the login. It printed this code and this URL — approve it in your browser, or on your phone, and tell me when it is done." |
12
+ | connecting Linear | `<their cloud>/settings/connections` | "Open this page and connect Linear. It will send you to Linear to authorize it and bring you back." |
13
+ | installing the GitHub App | `<their cloud>/settings/connections` | "Open the same page and install the GitHub App, granting it the repository you want worked." |
14
+
15
+ Say **by construction**, not "not supported yet". A person who thinks it is a missing feature will wait for it.
16
+
17
+ ## Kind two: not a command yet
18
+
19
+ These are settings pages today because the routes behind them take a browser session and not a key. A key-callable path is being built. They are gaps, and you should say so in those words — but you must still route the person through the browser, and you must not guess at a route. Trying one and being refused wastes their time and teaches them the tool is unreliable.
20
+
21
+ | step | where | what you say |
22
+ | -- | -- | -- |
23
+ | seeing every project they could set up | `<their cloud>/settings/linear-teams` | "I can read the projects that are already mapped, but the full list is only on this page today. Open it and tell me the ones you see." |
24
+ | checking a project's readiness, or re-checking it | the same page | "I can read the verdict your tenant last stored, from the contract. Asking for a fresh check is on that page." |
25
+ | mapping stages, or adopting the workflow | the same page, per project | "Pick one project, then **Map my stages** — or **Adopt the Catalyst workflow** if you want Catalyst's stages created for you." |
26
+ | registering a repository | `<their cloud>/settings/repositories` | "Add the repository here, and attach it to the project you just mapped, in the same form." |
27
+ | declaring the environment **for one repository** | that repository's environment section under `<their cloud>/settings/repositories` | "The names only this repository needs go here. Values are entered once, by you — nothing I run ever sees them." (Account-wide names are **not** on this list: `catalyst-skills environment` does those.) |
28
+
29
+ ⛔ **Do not compose a request for any of these.** A skill script never makes a request of its own; only the CLI does, and the CLI has no verb for them. If you find yourself constructing a URL, stop.
30
+
31
+ ## The URL to hand over
32
+
33
+ Never type a host from memory. `catalyst-skills status` prints the API it is connected to on its `API:` line, and `node scripts/where-am-i.mjs` prints ready-made links built from it. Use those. A person pointed at the wrong tenant's settings page has a worse afternoon than one pointed at no page at all.
34
+
35
+ ## Handing over, and coming back
36
+
37
+ The shape is always the same three parts, and all three matter:
38
+
39
+ 1. **What to open.** One link, and what they will see on it.
40
+ 2. **What to do there.** One action, named the way the page names it.
41
+ 3. **What you need back.** Not "let me know when you are done" — say what you will check and how. "When you have saved it, say so and I will re-read your tenant and tell you what it now says."
42
+
43
+ Then **wait**. Do not run anything while they are mid-flow, do not narrate, and do not move to the next step.
44
+
45
+ When they come back:
46
+
47
+ - Re-run `node scripts/where-am-i.mjs` and read them the part that should have changed.
48
+ - If it changed, say what it now says and move on.
49
+ - If it did not, refresh the contract once (`catalyst-skills contract --refresh`) and read it again — the contract is cached, and a page can be ahead of it by a few seconds.
50
+ - If it still did not, report both: what the page told them, and what the instrument says. Ask what they saw. **Never mark a step done because the person said a page worked** — the instrument is the record, and this is exactly where a confident false "all set" costs them an hour later.
@@ -0,0 +1,41 @@
1
+ # Who fixes what
2
+
3
+ Setup is five parts. Each has one instrument, and each instrument answers about its own part and nothing else. Read this before you tell a person that something is not ready, and before you tell them to do anything about it.
4
+
5
+ ## The five parts, and the instrument that owns each
6
+
7
+ | part | instrument | what a pass proves | what it does **not** prove | who fixes a failure, and where |
8
+ | -- | -- | -- | -- | -- |
9
+ | **machine** | `catalyst-skills status`, and the non-team checks of `catalyst-skills ready` | Node is new enough, this machine holds a credential, the CLI is where the config says, the contract is cached, the skills are on disk | anything at all about the tenant | the person at this keyboard, here |
10
+ | **person** | `catalyst-skills me` | the credential resolves to this person, with a role, and whether their Linear identity is matched | that their seat is active, or that they may change tenant settings | a tenant owner or admin, Settings → Members |
11
+ | **account** | `catalyst-skills contract --path account` | a resolved Linear workspace means the tenant's Linear grant landed | that the GitHub App is installed — the contract does not carry it | a tenant owner or admin, `<their cloud>/settings/connections` |
12
+ | **project** | `catalyst-skills contract --path teams`, and the `team:` checks of `ready` | for each project **that has been mapped**: its readiness verdict and each failing check by name | that this is every project they have — see below | a tenant owner or admin, `<their cloud>/settings/linear-teams` |
13
+ | **repository** | `catalyst-skills contract --path merge.repositories` | the repository is registered to the account | that it is active, that a project can dispatch into it, or that its environment is declared | a tenant owner or admin, `<their cloud>/settings/repositories` |
14
+
15
+ `node scripts/where-am-i.mjs` runs all five and labels each finding with its part. Use it rather than composing this by hand.
16
+
17
+ ## The two silences that are not absences
18
+
19
+ ⛔ **An empty project list means nothing is mapped yet — it does not mean they have no projects.** The tenant contract carries a project only once someone has saved a stage mapping for it. A brand-new tenant has projects in Linear and an empty list here, and reading that as "you have no projects" sends the person looking for a problem that does not exist. Say: *nothing is mapped yet*, and go to step 4. The list of every project they could map is browser-only today.
20
+
21
+ ⛔ **A registered repository is not a dispatchable one.** The contract's repository list carries no status and no project attachment, so a paused repository and an active one look identical there, and a repository registered without a project attached looks exactly like a correctly attached one. Registration is all it proves. If a card does not start, `catalyst-skills explain <ticket>` names the real reason; do not conclude anything about the repository from its presence in a list.
22
+
23
+ ⭐ And the general form of both: **an instrument that answers about a part it does not own will answer confidently and wrongly.** When you are unsure which part something belongs to, look it up in the table above rather than guessing from the wording of an error.
24
+
25
+ ## Triaging NOT READY
26
+
27
+ `catalyst-skills ready` prints one verdict over every check, machine and project together. That single verdict is correct for "can work run?" and useless for "what should I do now?", because it folds two parts into one word. Split it before you act:
28
+
29
+ 1. Run `catalyst-skills ready --json`. Every check has an `id`.
30
+ 2. A check whose id begins with `team:` is a **project** finding. Everything else is a **machine** finding.
31
+ 3. Report the two groups separately, in that order, each with its own owner.
32
+
33
+ - **A machine check failed.** This is the person's, here, now. Each failing check carries its own `fix` line; read it and do it. A missing or partial skill set is `catalyst-skills install`; a stale contract is `catalyst-skills contract --refresh`; a missing CLI path is one more `catalyst-skills login`.
34
+ - **A project check failed.** ⛔ **Nothing you run on this machine can move it.** Name the check, name the project, and name the owner — the `who` field carries the tenant's own owners and admins. Point at `<their cloud>/settings/linear-teams`. Then stop. Re-running `ready` in a loop is the failure this section exists to prevent: it will keep saying NOT READY for a reason that lives somewhere else entirely.
35
+ - **A check is a note.** Notes never move the verdict. A stale or absent replica is optional; a check that has never been run is waiting, not failing; a check the engine could not run is unknown, which is not a pass and not a failure. Say which of the three it is.
36
+
37
+ ## When to stop rather than continue
38
+
39
+ - **The account is suspended.** Setup cannot proceed and no retry changes that. Say so and name the conversation they need to have.
40
+ - **Their seat is not active, or they are not an owner or admin where one is required.** Say who is, and what to ask for. Do not offer a workaround.
41
+ - **A page said it worked and the instrument still disagrees.** Refresh the contract once (`catalyst-skills contract --refresh`) and read it again. If it still disagrees, report both facts — what the page said and what the instrument says — and let the person decide. Do not pick one for them.
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ // lib/cli.mjs — the one way a skill script reaches Catalyst Cloud: spawn the catalyst-skills CLI.
3
+ // The CLI holds the SDK and the key; this file holds neither. It reads customer.json only to learn
4
+ // where the CLI lives. Run any script beside this one with --help; this file is a library.
5
+ //
6
+ // ⛔ THIS LAUNCHER DOES NOT EXIT ON AN UNCONNECTED MACHINE, and that is deliberate. Every other
7
+ // skill runs against a tenant, so "not connected" is a precondition it refuses on. This skill's job
8
+ // is to REPORT where setup has got to, and "this machine holds no credential" is one of the states
9
+ // it has to be able to report — refusing to run would make the first step of onboarding unreadable.
10
+ // So `tryLoadConfig()` returns null instead of exiting, and the caller decides what that means.
11
+ import { spawnSync } from "node:child_process";
12
+ import { existsSync, readFileSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { CONNECT_COMMAND, hasCredential } from "./credential.mjs";
15
+
16
+ export const PACKAGE = "@catalyst-cloud/catalyst-skills";
17
+ export const NOT_CONFIGURED_EXIT = 2;
18
+ export const CONNECT_LINE = CONNECT_COMMAND;
19
+
20
+ export function configPath() {
21
+ const home = process.env.CATALYST_SKILLS_HOME ?? process.env.HOME ?? "/";
22
+ return join(home, ".config", "catalyst-cloud", "customer.json");
23
+ }
24
+
25
+ /** The stored config, or null when it is absent, unreadable, or holds no credential. Never exits. */
26
+ export function tryLoadConfig() {
27
+ const path = configPath();
28
+ if (!existsSync(path)) return null;
29
+ try {
30
+ const cfg = JSON.parse(readFileSync(path, "utf8"));
31
+ return hasCredential(cfg) ? cfg : null;
32
+ } catch {
33
+ return null;
34
+ }
35
+ }
36
+
37
+ /** Where the CLI is on this machine: the path login recorded when it still exists, else npx. */
38
+ export function cliTarget(cfg = tryLoadConfig()) {
39
+ const recorded = cfg !== null && typeof cfg.cliPath === "string" && existsSync(cfg.cliPath);
40
+ return recorded
41
+ ? { command: process.execPath, prefix: [cfg.cliPath], via: `node ${cfg.cliPath}`, recorded: true }
42
+ : { command: "npx", prefix: [PACKAGE], via: `npx ${PACKAGE}`, recorded: false };
43
+ }
44
+
45
+ /**
46
+ * Run one catalyst-skills verb. Returns {code, stdout, stderr, ran} and never exits: a verb that
47
+ * refuses because the machine is not connected is a READING, not a failure of this script.
48
+ * `ran` is false when the CLI could not be started at all.
49
+ */
50
+ export function runCli(args) {
51
+ const target = cliTarget();
52
+ const res = spawnSync(target.command, [...target.prefix, ...args], {
53
+ encoding: "utf8",
54
+ maxBuffer: 64 * 1024 * 1024,
55
+ shell: !target.recorded && process.platform === "win32",
56
+ env: process.env,
57
+ });
58
+ if (res.error) return { code: 1, stdout: "", stderr: `could not run ${target.via}: ${res.error.message}`, ran: false };
59
+ return { code: res.status ?? 1, stdout: res.stdout ?? "", stderr: res.stderr ?? "", ran: true };
60
+ }
61
+
62
+ /** Parse a verb's --json stdout, or null when it did not answer with JSON. Never exits. */
63
+ export function tryJson(stdout) {
64
+ try {
65
+ const value = JSON.parse(stdout.trim());
66
+ return value === null || typeof value !== "object" ? null : value;
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ /** Minimal flag parsing: --name value, --name=value, --flag. */
73
+ export function parseFlags(argv, spec) {
74
+ const flags = {};
75
+ const positionals = [];
76
+ for (let i = 0; i < argv.length; i++) {
77
+ const a = argv[i];
78
+ if (a === "--help" || a === "-h") return { help: true, flags, positionals };
79
+ if (!a.startsWith("--")) {
80
+ positionals.push(a);
81
+ continue;
82
+ }
83
+ let name = a.slice(2);
84
+ let value;
85
+ const eq = name.indexOf("=");
86
+ if (eq !== -1) {
87
+ value = name.slice(eq + 1);
88
+ name = name.slice(0, eq);
89
+ }
90
+ const s = spec[name];
91
+ if (!s) {
92
+ process.stderr.write(`unknown option --${name} (try --help)\n`);
93
+ process.exit(1);
94
+ }
95
+ if (s.value) {
96
+ if (value === undefined) value = argv[++i];
97
+ if (value === undefined || value === "") {
98
+ process.stderr.write(`--${name} needs a value\n`);
99
+ process.exit(1);
100
+ }
101
+ flags[name] = value;
102
+ } else flags[name] = true;
103
+ }
104
+ return { help: false, flags, positionals };
105
+ }
106
+
107
+ export function printHelp(usage, spec, notes = []) {
108
+ const lines = [`Usage: ${usage}`, ""];
109
+ const names = Object.keys(spec);
110
+ if (names.length) {
111
+ lines.push("Options:");
112
+ for (const n of names) lines.push(` --${n}${spec[n].value ? " <value>" : ""} ${spec[n].help}`);
113
+ lines.push("");
114
+ }
115
+ lines.push(...notes, "", "Exit codes: 0 every readable part is finished · 1 something is unfinished (the report says what and whose) · 2 the CLI could not be run at all");
116
+ process.stdout.write(lines.join("\n") + "\n");
117
+ }
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+ // lib/credential.mjs — is this machine connected? The ONE place a skill script decides it, vendored
3
+ // byte-identical into every skill's scripts/lib/ from skill-lib/credential.mjs at the package root
4
+ // (`npm run skill-lib:sync`; a test fails on any drift). Skills install one directory at a time, so
5
+ // each carries its own copy. This file is a library — run a sibling script with --help for usage.
6
+ //
7
+ // customer.json carries exactly one credential: a personal key (`key`), or the keyless login's
8
+ // session (`auth`, the recommended rail). A script never reads either for its value: it spawns the
9
+ // catalyst-skills CLI, which authenticates with whichever is present and refreshes a login's token
10
+ // itself. A new credential kind lands here, once.
11
+
12
+ /** The command that connects this machine, as every not-connected line names it. */
13
+ export const CONNECT_COMMAND =
14
+ "npx @catalyst-cloud/catalyst-skills login (or, with a personal key: CATALYST_CLOUD_TOKEN=<your personal key> npx @catalyst-cloud/catalyst-skills login)";
15
+
16
+ /** True when `cfg` (parsed customer.json) holds a usable credential of either kind. Never throws. */
17
+ export function hasCredential(cfg) {
18
+ if (cfg === null || typeof cfg !== "object") return false;
19
+ const key = cfg["key"];
20
+ if (typeof key === "string" && key !== "") return true;
21
+ const login = cfg["auth"];
22
+ return (
23
+ login !== null &&
24
+ typeof login === "object" &&
25
+ login["kind"] === "oauth" &&
26
+ typeof login["refreshToken"] === "string" &&
27
+ login["refreshToken"] !== ""
28
+ );
29
+ }