@catalyst-cloud/cli 0.9.3 → 0.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/execution.js +2 -2
- package/package.json +1 -1
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +50 -34
- package/skills/catalyst-onboard/references/choosing-a-coding-account.md +52 -0
- package/skills/catalyst-onboard/references/declaring-a-repository.md +4 -2
- package/skills/catalyst-onboard/references/replacing-a-credential.md +2 -2
- package/skills/catalyst-onboard/references/the-one-path.md +52 -86
- package/skills/catalyst-onboard/references/what-a-phase-needs.md +1 -1
- package/skills/catalyst-onboard/references/what-the-browser-owns.md +2 -1
- package/skills/catalyst-onboard/references/who-fixes-what.md +4 -3
- package/skills/catalyst-onboard/scripts/lib/cli.mjs +15 -1
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +187 -33
- package/skills/catalyst-setup/SKILL.md +1 -1
- package/skills/connect-me/SKILL.md +1 -1
- package/skills/how-catalyst-works/SKILL.md +1 -1
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.4
|
|
4
|
+
|
|
5
|
+
`/catalyst-onboard` walks a new member through setup one step at a time, in the order a person can act on it: coding account, Linear integration, project, GitHub App and repository, connected accounts, the repository's `.catalyst/catalyst.toml` declaration, then the host, ending with one ticket moving. Each turn asks one question, says why in a line, and checks the result after. A new reference covers choosing a coding account (a Claude setup token, a Codex `auth.json`, or a GLM or Qwen API key), and it says plainly that a plain Anthropic or OpenAI API key cannot be enrolled today. Accounts are named by their label. The skills use the product's own nouns (workspace, project, repository, integration, connected account).
|
|
6
|
+
|
|
7
|
+
An ended or cancelled coding account is kept for reporting and not used. Onboarding no longer tells you to retire or delete it, and never to put another login's credential on it; reactivate it only if its subscription is live again. `catalyst accounts` marks it "subscription canceled — kept for reporting, not used".
|
|
8
|
+
|
|
9
|
+
The onboarding step engine never names a next step it cannot back with a check, and it says when a newer CLI is announced but not yet published.
|
|
10
|
+
|
|
3
11
|
## 0.9.3
|
|
4
12
|
|
|
5
13
|
A machine the Catalyst installer moved from `@catalyst-cloud/catalyst-skills` to `@catalyst-cloud/cli` works without logging in again. The recorded launcher pointed into the removed package, so `ready` failed its CLI-path check and skill scripts could not start the CLI; any command now repoints it at the launcher that is running. The CLI also leaves the installer's skills alone: it never writes through a symlinked skill folder, and when the installer owns skill placement it does not refresh skills at all. When a refresh does fail, it tells you to re-run the install command.
|
package/dist/execution.js
CHANGED
|
@@ -332,9 +332,9 @@ export function renderAccount(a) {
|
|
|
332
332
|
const harness = a.harness ? `/${a.harness}` : "";
|
|
333
333
|
const status = a.status ? (ACCOUNT_STATUS[a.status] ?? a.status) : "status unknown";
|
|
334
334
|
const bits = [`${name} ${a.provider ?? "?"}${harness} ${status}`];
|
|
335
|
-
// CTC-4199: a cancelled subscription is
|
|
335
|
+
// CTC-4199: a cancelled subscription is kept for reporting and not used; never re-credential it (Ryan, 2026-09-29).
|
|
336
336
|
if (a.renewalStatus === "canceled")
|
|
337
|
-
bits.push("subscription canceled —
|
|
337
|
+
bits.push("subscription canceled — kept for reporting, not used");
|
|
338
338
|
if (typeof a.bindingUsedPercent === "number") {
|
|
339
339
|
const resets = typeof a.bindingResetsAtMs === "number" ? `, resets ${new Date(a.bindingResetsAtMs).toISOString()}` : "";
|
|
340
340
|
bits.push(`usage ${a.bindingUsedPercent}% of the ${a.bindingWindow ?? "binding"} window${resets}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-cloud/cli",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "Customer skill bundle for Catalyst Cloud, named from the executive's seat — get set up, see what is happening, see what needs you, run a project, unstick what is stuck — plus the catalyst CLI that reads through the Catalyst Cloud SDK, writes through the agent proxy, and connects your machine to your tenant with one command. The skills directory is the roster; this line never counts it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Catalyst's GitHub: show a ticket's pull request with its checks, reviews and review threads, say whether it is mergeable under this repository's policy, and explain what a PR accumulates as the ticket moves (the branch, the draft, the rewrite, the force-pushes, the labels, the queue). Use when someone asks "show me the PR", "what are the checks saying", "why hasn't it merged", "what does the queue need", "what does that label mean", or wants the branch and merge conventions Catalyst follows.
|
|
5
5
|
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.4 — written in this repository for customer tenants -->
|
|
8
8
|
|
|
9
9
|
# Catalyst's GitHub
|
|
10
10
|
|
|
@@ -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@0.9.
|
|
8
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.4 — written in this repository for customer tenants -->
|
|
9
9
|
|
|
10
10
|
# Catalyst Linear
|
|
11
11
|
|
|
@@ -1,63 +1,79 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: catalyst-onboard
|
|
3
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 Catalyst Cloud skills installed and nothing else.
|
|
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 Catalyst Cloud skills installed and nothing else. Starts with the coding account, then the project, the integrations and the person's connected accounts, each repository's settings file, and ends with one real ticket moving. Reads each part of the setup with the instrument that owns it, 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
5
|
disable-model-invocation: true
|
|
6
6
|
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
7
7
|
---
|
|
8
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.
|
|
8
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.4 — written in this repository for customer tenants -->
|
|
9
9
|
|
|
10
10
|
# Onboard me
|
|
11
11
|
|
|
12
|
-
You take one person from "the skills are installed" to "a ticket is running on my own
|
|
12
|
+
You are the guide a new member gets on their first day with Catalyst Cloud. You take one person from "the skills are installed" to "a ticket is running on my own workspace", and you do it the way a good colleague would at their desk: one step at a time, one question at a time, saying why each step matters, and checking that it landed before you go on. You never print the whole ladder at them, and you never batch several steps into one turn.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
The path has five stops, in this order: a **coding account** the work will run on, one **project** (a Linear team plus its repositories, which needs the Linear integration first), the **integrations** and the person's own **connected accounts**, each repository's **settings file**, and then a green `ready` with **one real ticket moving**. `node scripts/where-am-i.mjs --next` always says which stop you are at; your memory of the last turn never does.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
## How a turn goes
|
|
17
|
+
|
|
18
|
+
1. Run `node scripts/where-am-i.mjs --next` and read the one line it prints. If a `note:` line follows it, say the note in one clause in the same turn (a cancelled account is kept for reporting, not used, never re-tokened); it is information, not a task.
|
|
19
|
+
2. Say, in one or two sentences, what that step is for. The person should always know why they are doing it.
|
|
20
|
+
3. Ask one question, or do one thing. If it is a command a key can run, run it and show the lines it printed. If it needs a browser, hand over one link, one action named the way the page names it, and say what you will check when they are back.
|
|
21
|
+
4. Stop. Wait for their answer.
|
|
22
|
+
5. When they answer, run the script again and read them the part that should have changed. If it changed, say so and move on. If it did not, refresh the contract once (`catalyst-skills contract --refresh`) and read it again; if it still did not, say what the page told them and what the instrument says, and ask what they saw.
|
|
23
|
+
|
|
24
|
+
Two or three short sentences per turn is the right size. A turn that lists every part's verdict is a wall of status, not help. The full report exists for you to read, not to paste.
|
|
17
25
|
|
|
18
26
|
## Run first
|
|
19
27
|
|
|
20
28
|
Scripts are run, never read. Each prints `--help`.
|
|
21
29
|
|
|
22
|
-
- `node scripts/where-am-i.mjs
|
|
23
|
-
- `node scripts/where-am-i.mjs
|
|
24
|
-
- `node scripts/where-am-i.mjs --json
|
|
25
|
-
- `node scripts/local-sync.mjs
|
|
26
|
-
|
|
27
|
-
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.
|
|
30
|
+
- `node scripts/where-am-i.mjs --next`: the single next step, with who owns it and where.
|
|
31
|
+
- `node scripts/where-am-i.mjs`: every part in the order above, each with its instrument, its verdict, and for anything unfinished who can fix it and the page it is on. Works before the machine is connected.
|
|
32
|
+
- `node scripts/where-am-i.mjs --json`: the same document for you to branch on.
|
|
33
|
+
- `node scripts/local-sync.mjs`: optional local replica and event sync status. Run `--start` only after the person chooses local sync.
|
|
28
34
|
|
|
29
35
|
## Load on demand
|
|
30
36
|
|
|
31
37
|
| when | read |
|
|
32
38
|
| -- | -- |
|
|
33
|
-
| walking the steps
|
|
34
|
-
| the
|
|
35
|
-
|
|
|
39
|
+
| walking the steps: what each is for, what to ask, what to read back, when it is done | `references/the-one-path.md` |
|
|
40
|
+
| the coding account: which kinds exist, what each one asks for, a cancelled or ended one | `references/choosing-a-coding-account.md` |
|
|
41
|
+
| the script says a coding account needs a new credential | `references/replacing-a-credential.md` |
|
|
36
42
|
| 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` |
|
|
37
|
-
|
|
|
43
|
+
| anything reports not ready, or you are about to say who should fix something | `references/who-fixes-what.md` |
|
|
44
|
+
| the repository's settings file, `.catalyst/catalyst.toml`, and its approval | `references/declaring-a-repository.md` |
|
|
45
|
+
| the `host` part is not ok, or you are about to say work can run | `references/what-a-phase-needs.md` |
|
|
46
|
+
| the person chooses an optional local event and replica cache | `references/local-sync.md` |
|
|
47
|
+
| the person asks what Catalyst is, or how a ticket gets worked | the `how-catalyst-works` skill |
|
|
38
48
|
| the machine will not connect, or a login expired | the `connect-me` skill |
|
|
39
49
|
| the person asks how to install, update, or migrate Catalyst skills | `references/skill-sources.md` |
|
|
40
|
-
| the person's repository needs environment names declared | `references/declaring-a-repository.md` |
|
|
41
|
-
| the `coding accounts` or `host` part is not ok, or you are about to say work can run | `references/what-a-phase-needs.md` |
|
|
42
|
-
| the script says a coding account needs a new credential | `references/replacing-a-credential.md` |
|
|
43
50
|
| setup is finished and they want the standing readiness verdict | the `catalyst-setup` skill |
|
|
44
51
|
| the first ticket did not start and you need the reason | the `how-catalyst-works` skill, then `unstick` |
|
|
45
52
|
|
|
53
|
+
## Words
|
|
54
|
+
|
|
55
|
+
Use the product's words, and name the page label Settings shows today when it differs. **Workspace** is the whole customer account. **Project** is one Linear team plus the repositories registered to it. **Repository** is one `owner/name`. **Integration** is a workspace-level connection, the Linear connection or the GitHub App (the page is Settings → Connections). **Connected account** is the person's own Linear or GitHub login. **Member** is a person who can sign in. A **coding account** is an AI provider login the work runs on (Settings → AI accounts). Say "tenant" and "org" only when quoting a command's output.
|
|
56
|
+
|
|
46
57
|
## Rules
|
|
47
58
|
|
|
48
|
-
- **One step, then stop.** Say what you are about to do, do it, show the real output, say what it means
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
52
|
-
- **
|
|
53
|
-
- **
|
|
54
|
-
- **
|
|
55
|
-
- **
|
|
56
|
-
- **
|
|
57
|
-
- **
|
|
58
|
-
- **
|
|
59
|
-
- **
|
|
60
|
-
- **
|
|
61
|
-
- **
|
|
62
|
-
- **
|
|
63
|
-
- **
|
|
59
|
+
- **One step, then stop.** Say what you are about to do and why, do it, show the real output, say what it means, ask the one question. Never queue several steps into one message, and never move on from a step you did not watch finish.
|
|
60
|
+
- **Open with the step, not a recap.** At most one clause on what is already done ("your coding account is in place"), then the step. Never list the parts with a status each; that is the report, and the report is for you.
|
|
61
|
+
- **One question has one answer.** Ask a yes-or-no, or one fact. Never offer two alternatives to choose between, and never pair a page to open with a second thing to do after it. If two things are needed, the second one waits for the next turn.
|
|
62
|
+
- **Say who can do a step; do not ask whether they are that person.** The script's `person` line already says their role. "This needs a workspace owner or admin, which you are" is a statement, not a question, and it never becomes a second thing to answer.
|
|
63
|
+
- **No internal labels reach the person.** Never cite a step number, a reference file, or "the script"; say what to do. The status line's `Tenant:` is their workspace; say "your workspace", never a tenant number.
|
|
64
|
+
- **Name accounts by their label.** "Your Claude account, Work laptop, is ready", never a slot id or "account 2". The script prints the label when the cloud has one.
|
|
65
|
+
- **Report what you observed, not what you expected.** Print the lines the command produced. "That worked" without the output is the easiest thing to get wrong here, and a person who later finds it did not work stops trusting every other step.
|
|
66
|
+
- **Each part by its own instrument.** The script labels every finding with the part it belongs to. Keep that when you summarize: a project problem is never a machine problem, and re-running a local command cannot move a check that belongs to an owner, an admin, or a browser page.
|
|
67
|
+
- **Not ready is a question about who.** Name which check, who can fix it, and where. If the owner is not the person in front of you, say so and stop.
|
|
68
|
+
- **Never invent a count or a list.** Every number and every name comes from what a command printed.
|
|
69
|
+
- **Provider consent belongs to the person in a browser.** Login approval, the workspace's Linear connection and GitHub App install, and the person's own connected accounts. Give the printed URL, then check `status`. Never claim a grant succeeded because a browser opened.
|
|
70
|
+
- **Some steps a key cannot do yet.** Listing every project, saving a stage mapping, adopting the workflow, registering a repository and approving one repository's settings file are settings-page work today. Route those through the browser and say plainly that it is a gap, not the design. Never guess at a route.
|
|
71
|
+
- **The workspace-wide environment declaration is the one setup write you can perform.** `catalyst-skills environment` reads it, proposes it and approves it.
|
|
72
|
+
- **Local sync is opt-in.** API-backed skills work without it. Ask before running `local-sync.mjs --start`; a detached process starting is not evidence of freshness. See `references/local-sync.md`.
|
|
73
|
+
- **A cancelled or ended coding account is kept, not retired.** It stays for reporting, is not used, and is never given a new token; a token minted from a live login must never land on it. Reactivating it is the person's call if the subscription comes back. Never tell anyone to retire or delete it.
|
|
74
|
+
- **Their workspace, as them.** Everything goes through the CLI and the person's own login. You never name another workspace, never ask for a key you could avoid, and never see a credential or a secret's value.
|
|
75
|
+
- **Ask once before you write to their machine or their repository.** Say what you found, what you will write and where, then wait for a yes.
|
|
76
|
+
- **A blocked command is the person's call.** If the harness blocks `npx`, a global install, or a tool, ask or hand them the command. Never skip it silently, never edit your own permission settings.
|
|
77
|
+
- **An older cloud is not a broken command.** When the CLI says the cloud is older than the bundle, say so and move on.
|
|
78
|
+
- **Stop at a wall you cannot pass.** A suspended workspace, 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.
|
|
79
|
+
- **Write like a capable colleague.** Plain words, short sentences, one idea each, active voice, warm and unhurried. No em dashes, no emoji, no chatbot openers or flattery, and bold only the rare thing. Reread each message before you send it.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Choosing a coding account
|
|
2
|
+
|
|
3
|
+
A coding account is the AI provider login the work runs on. It is the first thing to settle, because nothing else in setup produces any work without one, and because it needs nothing else in setup to be done. Read the part with `node scripts/where-am-i.mjs`; it says whether one is enrolled, whether each enrolled one can still take work, and who may enrol one.
|
|
4
|
+
|
|
5
|
+
## The first question
|
|
6
|
+
|
|
7
|
+
Ask it plainly: "Do you already have a coding account you want Catalyst to run on?" Then wait.
|
|
8
|
+
|
|
9
|
+
Say why in one line: Catalyst runs each stage of a ticket on an account you own, so you pay your own provider at your own rate and keep the usage under your own name.
|
|
10
|
+
|
|
11
|
+
## What we support today
|
|
12
|
+
|
|
13
|
+
Four kinds. Name the one they have in their words, then in the page's words.
|
|
14
|
+
|
|
15
|
+
| they say | kind | what enrolling asks for | how they get it |
|
|
16
|
+
| -- | -- | -- | -- |
|
|
17
|
+
| "a Claude subscription", "Claude Pro or Max", "I use Claude Code" | Claude, a setup token | the token `claude setup-token` prints | on their own machine: run `claude`, check `/status` shows the account they mean, then run `claude setup-token` and copy the token |
|
|
18
|
+
| "ChatGPT", "Codex", "a ChatGPT Plus or Pro plan" | Codex, an auth file | the contents of `~/.codex/auth.json` | run `codex login`, choose "Sign in with ChatGPT"; the login writes the file. `$CODEX_HOME/auth.json` if `CODEX_HOME` is set. On macOS `pbcopy < ~/.codex/auth.json` copies it |
|
|
19
|
+
| "a GLM key", "Z.ai", "a GLM coding plan" | GLM, an API key | the provider's API key | from the provider's console |
|
|
20
|
+
| "a Qwen key", "Alibaba Model Studio", "a Qwen coding plan" | Qwen, an API key | the provider's API key | from the provider's console |
|
|
21
|
+
|
|
22
|
+
A plain Anthropic or OpenAI API key is not something the page can enrol today. If that is what they have, say so in one sentence and ask whether they also have one of the four above. Do not improvise a way in.
|
|
23
|
+
|
|
24
|
+
Codex has one more thing to say, before they paste: Catalyst takes over that login. Its refresh token is single-use, so the copy on their machine stops working once Catalyst uses it. To keep using Codex locally, they sign in again afterwards, separately.
|
|
25
|
+
|
|
26
|
+
## Enrolling one
|
|
27
|
+
|
|
28
|
+
Enrolling is a browser step, by construction: the person pastes a credential into a write-only field, and nothing you run ever sees it.
|
|
29
|
+
|
|
30
|
+
1. Hand over the page the script printed for the part (Settings → AI accounts). Say: "Choose the provider, give the account a label you will recognise, and paste the credential the page asks for. Nothing on this side sees it."
|
|
31
|
+
2. Wait. Do not run anything while they are on the page.
|
|
32
|
+
3. When they say it is saved, run `node scripts/where-am-i.mjs` and read them the `coding accounts` part. An enrolled and active account, named by its label, is proof. If the part still says none is enrolled, refresh the contract once (`catalyst-skills contract --refresh`) and read it again.
|
|
33
|
+
|
|
34
|
+
Only a workspace owner or admin can enrol one (the page still says "tenant" in places). If the person is neither, say who can, and stop this step there.
|
|
35
|
+
|
|
36
|
+
## Naming accounts
|
|
37
|
+
|
|
38
|
+
Call an account what the page calls it: its label, else its email, else its slot. `catalyst-skills accounts --json` carries this as `displayName`. Never call one "the first account" or "account 2", and never read out a credential.
|
|
39
|
+
|
|
40
|
+
## An account that already exists
|
|
41
|
+
|
|
42
|
+
The script reads each enrolled account. Read it back in their words:
|
|
43
|
+
|
|
44
|
+
- **Active, or attested.** "Your Claude account, Work laptop, is enrolled and can take work." Move on.
|
|
45
|
+
- **Out of rotation.** Every account is deactivated. Reactivate one on its own page. Never enrol another.
|
|
46
|
+
- **Needs a new credential.** Quarantined, expired or revoked, or its last polls failed on the credential itself. Replace the credential on that account's own page, with a credential from that same login. The steps, and the login check that has to come first, are in `references/replacing-a-credential.md`. Never enrol a second account for this.
|
|
47
|
+
- **Cancelled or ended.** The subscription was cancelled, or the provider ended the account. Say so in one clause and move on: it stays for reporting, it is not used, and it is not counted against setup. Never paste a token from another login into it, which would put a live account's credential onto a dead slot, and never tell them to retire or delete it. If the subscription comes back, they reactivate it on its own page.
|
|
48
|
+
- **Could not be read.** Say the accounts could not be read this time and read again later. It is not "no accounts". Do not tell them to enrol one on this reading.
|
|
49
|
+
|
|
50
|
+
## When more than one is right
|
|
51
|
+
|
|
52
|
+
One account is enough to start. A second one, on a different provider or plan, gives the router somewhere to go when the first is at its limit. Offer it as a later improvement, not as a step now.
|
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
The cloud containers build and test the repository. They need the names of the variables and secrets it reads. You write the names; the person enters the values in the app. Never read or print a value.
|
|
4
4
|
|
|
5
|
+
This is step 8 of `references/the-one-path.md`. Offer to draft the file from the repository's own build files, say which files you will read and what you will write, and wait for a yes before you write anything. If the repository already has one, verify it against the inventory below instead of rewriting it.
|
|
6
|
+
|
|
5
7
|
## The inventory, in order
|
|
6
8
|
|
|
7
|
-
1. Start from the commands that already build and test the repository: the README's setup section, the scripts in `package.json` or its equivalent, and the build and test steps in CI. Those are what the container runs. A name they never read does not belong in the file.
|
|
9
|
+
1. Start from the commands that already build and test the repository: the README's setup section, the scripts in `package.json` or its equivalent, and the build and test steps in CI. Those are what the container runs. A name they never read does not belong in the file. Read these files directly; the inventory needs no git command.
|
|
8
10
|
2. For each name, record the file it came from: a `.env.example` (never `.env`, which holds live values), a CI secret in a workflow's `env:` block, or a platform binding. A name you cannot tie to a file is a guess. Say so instead of listing it.
|
|
9
11
|
3. Mark whether the container needs it to build and test, or only to deploy. Containers build and test. They do not deploy.
|
|
10
12
|
4. Leave platform bindings and deploy-only CI secrets out by default. The platform supplies a binding at run time, and no build or test reads a deploy-only secret. Include one only when the person says the build needs it, and say why.
|
|
@@ -41,4 +43,4 @@ run = ["npm", "ci"]
|
|
|
41
43
|
|
|
42
44
|
## After the merge
|
|
43
45
|
|
|
44
|
-
`catalyst-skills ready` reports `environment_declared` for the team's default repository and names the next step: commit the file, fix it, or approve it. The `catalyst-setup` check table has each reason. The
|
|
46
|
+
`catalyst-skills ready` reports `environment_declared` for the team's default repository and names the next step: commit the file, fix it, or approve it. The `catalyst-setup` check table has each reason. The workspace-wide declaration is separate; `catalyst-skills environment` handles it (also step 8 of `references/the-one-path.md`).
|
|
@@ -36,9 +36,9 @@ Say these two things plainly:
|
|
|
36
36
|
|
|
37
37
|
The same page takes a Claude account's new credential. Its field is "Replacement setup token". `claude setup-token` mints a token for whichever Claude account the terminal is logged into, not for the slot. So first match them: read the slot's account on the AI accounts page (its label or email), then have the person run `claude` and `/status` to see the email the terminal is logged into. If they differ, stop, and log in to the slot's account first. Only then run `claude setup-token`, paste the token into that field, and press Replace credential. Then re-run the script.
|
|
38
38
|
|
|
39
|
-
##
|
|
39
|
+
## A cancelled or ended account
|
|
40
40
|
|
|
41
|
-
An account whose subscription is cancelled, or which has ended, is not a credential problem, and no token revives it.
|
|
41
|
+
An account whose subscription is cancelled, or which has ended, is not a credential problem, and no token revives it. It stays enrolled for reporting, is not used, and is not something to retire or delete. Never replace its credential with a token from another account: that puts a working account's login onto a dead slot. If the subscription is live again, the person reactivates it on its own page.
|
|
42
42
|
|
|
43
43
|
## Any other provider
|
|
44
44
|
|
|
@@ -1,147 +1,113 @@
|
|
|
1
1
|
# The one path
|
|
2
2
|
|
|
3
|
-
The steps run in
|
|
3
|
+
The steps run in the order a person can act on them, and the order says why: the coding account first, because nothing runs without one and it needs nothing else; the Linear integration, because the project list cannot be read without it; one project; the GitHub App and the repository it will work in; the person's own connected accounts, GitHub last because a registered repository is the proof the App is usable; the repository's settings file; then the first ticket. Each step is the cheapest place to catch the failure the next one would otherwise hide.
|
|
4
4
|
|
|
5
|
-
Walk them **one at a time**. Before each step say what
|
|
5
|
+
Walk them **one at a time**. Before each step say what it is for, in a sentence, and ask one question or do one thing. 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. Two or three short sentences per turn is the right size.
|
|
6
6
|
|
|
7
|
-
Each step
|
|
7
|
+
Each step states: what it is for, what you ask or run or hand over, **what you read back to prove it landed**, and **who owns it**.
|
|
8
8
|
|
|
9
|
-
## 0
|
|
9
|
+
## 0. Where are we
|
|
10
10
|
|
|
11
|
-
**For:** starting from the truth instead of
|
|
11
|
+
**For:** starting from the truth instead of an assumption. A person arrives having done anything from nothing to most of it, and the installer has usually connected the machine already.
|
|
12
12
|
|
|
13
|
-
**You run:** `node scripts/where-am-i.mjs
|
|
14
|
-
|
|
15
|
-
**Read back:** the whole thing, as it printed. Then say in one sentence which part is unfinished and whose it is.
|
|
13
|
+
**You run:** `node scripts/where-am-i.mjs --next`. Read the one line, then say in a sentence which step is next and whose it is. Do not paste the full report.
|
|
16
14
|
|
|
17
15
|
**Owner:** you.
|
|
18
16
|
|
|
19
|
-
## 1
|
|
20
|
-
|
|
21
|
-
**For:** giving this machine a credential, so every later read is the person's own.
|
|
22
|
-
|
|
23
|
-
**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.
|
|
24
|
-
|
|
25
|
-
**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.
|
|
17
|
+
## 1. Connect this machine
|
|
26
18
|
|
|
27
|
-
**
|
|
19
|
+
**For:** giving this machine a credential, so every later read is the person's own. The installer normally did this; you are here only when the script says the machine is not connected.
|
|
28
20
|
|
|
29
|
-
|
|
21
|
+
**You ask:** "Shall I start the login now?" On a yes, 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. Do not offer the key form; it exists for a person who already holds a personal key and says so.
|
|
30
22
|
|
|
31
|
-
|
|
23
|
+
**Read back:** the `Connected to …` and `Connected as …` lines, then the script again so the machine part flips.
|
|
32
24
|
|
|
33
|
-
**
|
|
25
|
+
**Owner:** you run it; the approval is the person's, in a browser, and always will be. Do not re-run the command while a code is outstanding. If it refuses, stop and use the `connect-me` skill.
|
|
34
26
|
|
|
35
|
-
|
|
27
|
+
## 2. A coding account
|
|
36
28
|
|
|
37
|
-
**
|
|
29
|
+
**For:** what the work runs on. Catalyst runs each stage of a ticket on an account the person owns, at their own rate, under their own name. With none enrolled, every later step can be finished and nothing will ever start.
|
|
38
30
|
|
|
39
|
-
**
|
|
31
|
+
**You ask:** "Do you already have a coding account you want Catalyst to run on?" Then `references/choosing-a-coding-account.md`: the four kinds, what each asks for, how to enrol one, and what to do with an account that is out of rotation, needs a new credential, or has ended.
|
|
40
32
|
|
|
41
|
-
|
|
33
|
+
**Read back:** the `coding accounts` part: an enrolled, active account, named by its label. A cancelled or ended one is kept for reporting, not used, never re-tokened, and does not hold this step up.
|
|
42
34
|
|
|
43
|
-
**
|
|
35
|
+
**Owner:** a workspace owner or admin, in a browser, on the AI accounts page. Nothing you run ever sees the credential.
|
|
44
36
|
|
|
45
|
-
|
|
37
|
+
## 3. Connect the Linear integration
|
|
46
38
|
|
|
47
|
-
**
|
|
39
|
+
**For:** the workspace. Until the workspace's Linear connection exists, no project can be read or mapped. Say that this is the one connection the whole workspace shares, made once by an admin.
|
|
48
40
|
|
|
49
|
-
**
|
|
41
|
+
**You hand over:** `<their cloud>/settings/connections` (the Integrations page; Settings labels it Connections today), and say: connect Linear. It sends them to Linear to authorise and brings them back.
|
|
50
42
|
|
|
51
|
-
|
|
43
|
+
**Read back:** after they say it is done, re-run the script and read them the `account` line. A resolved workspace is proof. If it still reads unresolved, run `catalyst-skills contract --refresh` and read it again.
|
|
52
44
|
|
|
53
|
-
**
|
|
45
|
+
**Owner:** a workspace owner or admin, in a browser. **Browser by construction**: it is an authorization grant, and no key can perform one.
|
|
54
46
|
|
|
55
|
-
|
|
47
|
+
## 4. Pick one project, and map its stages
|
|
56
48
|
|
|
57
|
-
**
|
|
49
|
+
**For:** the project. A project is one Linear team plus the repositories registered to it. 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 workspace sees no activity.
|
|
58
50
|
|
|
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.
|
|
51
|
+
**You hand over:** `<their cloud>/settings/linear-teams`. They pick one team and press **Map my stages**, or **Adopt the Catalyst workflow** if they want Catalyst's stages created for them.
|
|
66
52
|
|
|
67
53
|
**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
54
|
|
|
69
|
-
**Owner:** a
|
|
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
|
-
## 5 — Install the GitHub App
|
|
74
|
-
|
|
75
|
-
**For:** the account grain again. Without it Catalyst can read tickets but cannot touch code.
|
|
76
|
-
|
|
77
|
-
**You hand over:** `<their cloud>/settings/connections`, and say: install the GitHub App, and grant it the repository they want worked and `<org>/thoughts` (step 5a).
|
|
78
|
-
|
|
79
|
-
**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.
|
|
80
|
-
|
|
81
|
-
**Owner:** a tenant owner or admin, in a browser. **Browser by construction**, same reason as step 3.
|
|
82
|
-
|
|
83
|
-
## 5a — The thoughts repository
|
|
84
|
-
|
|
85
|
-
**For:** Catalyst's cloud phases write their notes to `<org>/thoughts`, where `<org>` is the GitHub org that owns the code repository. If the App cannot reach it, phases run without those notes and nothing says so. **You hand over:** GitHub. Confirm `<org>/thoughts` exists; if not, create a private repository named `thoughts`, initialized with a README. Then, on the GitHub App's installation page for that org, choose All repositories, or add `thoughts` to the selected repositories. **Read back:** once a repository is registered, the script's `repositories` part carries a note from `gh repo view <org>/thoughts`. It shows the repository exists, never that the App can reach it. Say that plainly; never claim App access is verified. **Owner:** an org admin on GitHub, in a browser.
|
|
86
|
-
|
|
87
|
-
## 6 — Register the repository
|
|
55
|
+
**Owner:** a workspace owner or admin. ⛔ Listing the projects and saving a mapping are settings-page work today; a key cannot do either yet. Say that plainly; it is a gap in the product, not something they did wrong.
|
|
88
56
|
|
|
89
|
-
**
|
|
57
|
+
⭐ **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: the project they care least about breaking.
|
|
90
58
|
|
|
91
|
-
|
|
59
|
+
## 5. Install the GitHub App
|
|
92
60
|
|
|
93
|
-
**
|
|
61
|
+
**For:** the workspace's GitHub integration. Without it Catalyst can read tickets but cannot touch code.
|
|
94
62
|
|
|
95
|
-
**
|
|
63
|
+
**You hand over:** the same Connections page, and say: install the GitHub App, and grant it the repository they want worked and `<org>/thoughts`. Catalyst's cloud phases write their notes to `<org>/thoughts`, where `<org>` owns the code repository. If it does not exist, they create a private repository named `thoughts`, initialized with a README, and then on the App's installation page for that org choose All repositories, or add `thoughts` to the selected repositories. The script's `repositories` part notes whether `gh` can see that repository, never that the App can reach it, so do not claim that.
|
|
96
64
|
|
|
97
|
-
|
|
65
|
+
**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.
|
|
98
66
|
|
|
99
|
-
**
|
|
67
|
+
**Owner:** a workspace owner or admin, in a browser. Browser by construction, same reason as step 3.
|
|
100
68
|
|
|
101
|
-
##
|
|
69
|
+
## 6. Register the repository
|
|
102
70
|
|
|
103
|
-
**For:** the
|
|
71
|
+
**For:** the repository. Registering, attached to a project, is what makes a repository something that project can dispatch work into.
|
|
104
72
|
|
|
105
|
-
You
|
|
73
|
+
**You hand over:** `<their cloud>/settings/repositories`, and say: add the repository, **and attach it to the project you mapped in step 4**, in the same form.
|
|
106
74
|
|
|
107
|
-
**
|
|
75
|
+
**Read back:** re-run the script and read the `repositories` line. The repository appearing there proves it was registered, and only that; see `references/who-fixes-what.md` for what registration does not prove.
|
|
108
76
|
|
|
109
|
-
|
|
77
|
+
**Owner:** a workspace owner or admin. ⛔ Registering is settings-page work today. ⛔ A repository registered without a project attached is the trap: the call succeeds, the repository is listed, and nothing can ever dispatch into it.
|
|
110
78
|
|
|
111
|
-
|
|
112
|
-
catalyst-skills environment propose --file declaration.json
|
|
113
|
-
```
|
|
79
|
+
## 7. Connect your own accounts
|
|
114
80
|
|
|
115
|
-
|
|
81
|
+
**For:** the person. The integrations serve the whole workspace; a connected account is this person's own Linear or GitHub login, so that what Catalyst does for them is attributed to them, and asks assigned to them can be told apart from everyone else's.
|
|
116
82
|
|
|
117
|
-
**
|
|
83
|
+
**You run:** `catalyst-skills connections personal linear start`. It prints a short-lived URL and tries to open it; if the browser does not open, hand them the URL. Then `catalyst-skills connections personal linear status`. Do GitHub the same way, with `github`, only after the GitHub App is installed and the repository is registered: `catalyst-skills connections personal github start`, then `status`.
|
|
118
84
|
|
|
119
|
-
**
|
|
85
|
+
**Read back:** each `status`, and the script's `person` part: their label and role, whether their Linear identity is matched, and each grant. A URL opening is not proof a grant landed. If Linear identity stays unmatched after consent, `catalyst-skills identity linear status`, then `options`; the person picks their own listed identity and you run `catalyst-skills identity linear set <linearUserId>`. An already-resolved or claimed identity, and an inactive seat, need an owner or admin.
|
|
120
86
|
|
|
121
|
-
|
|
87
|
+
**Owner:** you start and check; the person approves in a browser.
|
|
122
88
|
|
|
123
|
-
|
|
89
|
+
## 8. The repository's settings file
|
|
124
90
|
|
|
125
|
-
|
|
91
|
+
**For:** the repository's own settings, in `.catalyst/catalyst.toml` on its default branch: the Linear team it belongs to, the names of the variables and secrets its build needs, and its setup commands. Names leave the machine; values are entered once, by them, in the app.
|
|
126
92
|
|
|
127
|
-
**
|
|
93
|
+
**You ask:** one thing at a time. First: "Shall I draft it from your repository's own build files?" On a yes, ask where the repository is checked out on this machine; if it is not, write the file here for them to add. Then `references/declaring-a-repository.md`: the inventory, the file's shape, the pull request. Say what happens after the merge as context, not as a second task for now. After the merge, an owner or admin approves the revision on the repository's Environment page (Settings → Repositories → the repository → Environment → Setup declaration → Approve this revision) and enters the values on the same page. A workspace-wide declaration, if they want one, goes through `catalyst-skills environment` (read, propose, `--approve`), the one setup write you can perform.
|
|
128
94
|
|
|
129
|
-
**
|
|
95
|
+
**Read back:** the script's `repository declarations` part, read from each project's `environment_declared` check: no file yet, invalid, awaiting approval, or in effect, per repository. Names only, never a value.
|
|
130
96
|
|
|
131
|
-
|
|
97
|
+
**Owner:** the person, with you, in the repository; a workspace owner or admin for the approval.
|
|
132
98
|
|
|
133
|
-
## 9
|
|
99
|
+
## 9. Verify, then run the first ticket
|
|
134
100
|
|
|
135
101
|
**For:** the only thing that proves setup worked.
|
|
136
102
|
|
|
137
|
-
**You run:** `catalyst-skills ready`. Its READY does not cover
|
|
103
|
+
**You run:** `catalyst-skills ready`. Its READY does not cover the coding account or the host; the script does. Read them the verdict and every failing line with its own fix and owner. If it says NOT READY, go to `references/who-fixes-what.md` before you touch anything.
|
|
138
104
|
|
|
139
|
-
**Then:**
|
|
105
|
+
**Then:** ask them to move one card into the project's start stage (usually Todo) and tell you which ticket it was. That is the whole question; do not offer to explain the pipeline or to search the backlog instead. Then watch. `catalyst-skills explain <ticket>` says why it is or is not about to run. If they opted into local sync, use the optional first-event check in `references/local-sync.md`.
|
|
140
106
|
|
|
141
|
-
**Read back:** what `explain` actually said. If
|
|
107
|
+
**Read back:** what `explain` actually said, and the first comment the agent leaves on the ticket. If `explain` says the ticket cannot start, the reason it names is the answer; use the `how-catalyst-works` skill for what it means, then `unstick` if something is holding it.
|
|
142
108
|
|
|
143
|
-
**Owner:** the card move is theirs. The verdict is the
|
|
109
|
+
**Owner:** the card move is theirs. The verdict is the workspace's.
|
|
144
110
|
|
|
145
111
|
## When you are done
|
|
146
112
|
|
|
147
|
-
Say what is set up, name anything still unfinished with its owner, and never say work can run while the `coding accounts` or `host` part is unfinished
|
|
113
|
+
Say what is set up, name anything still unfinished with its owner, and never say work can run while the `coding accounts` or `host` part is unfinished. 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.
|
|
@@ -17,7 +17,7 @@ A person can finish every other step and still see nothing run. A phase needs tw
|
|
|
17
17
|
| `inactive` | accounts exist, but every one is out of rotation | reactivate one on the page. Never tell them to enrol another |
|
|
18
18
|
| `unread` | the cloud could not read the accounts | say it could not be read. It is not "no accounts". Do not tell them to enrol one; read it again later |
|
|
19
19
|
|
|
20
|
-
**Owner:** the one the contract names. The person does it in the browser. Never ask for the credential, and never handle it. A key cannot enrol one.
|
|
20
|
+
**Owner:** the one the contract names. The person does it in the browser. Never ask for the credential, and never handle it. A key cannot enrol one. Which kinds of account exist, what each asks for, and what to do with an ended or cancelled one are in `references/choosing-a-coding-account.md`; it is step 2 of the path, the first thing asked.
|
|
21
21
|
|
|
22
22
|
**Older cloud:** if the contract has no `codingAccounts`, the script says the cloud is older and reads `catalyst-skills accounts` instead. That list counts the accounts enrolled and the ones able to take work. An expired, revoked or quarantined account does not count. The owner is then a tenant owner or admin, at `<their cloud>/settings/coding-accounts`.
|
|
23
23
|
|
|
@@ -9,7 +9,8 @@ These will never be a command, on any release. Each is an authorization a person
|
|
|
9
9
|
| step | where | what you say |
|
|
10
10
|
| -- | -- | -- |
|
|
11
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
|
-
|
|
|
12
|
+
| enrolling a coding account, or replacing its credential | the AI accounts page the script printed for the `coding accounts` part | "Choose the provider, give the account a label you will recognise, and paste the credential the page asks for. Nothing on this side sees it. Tell me when it is saved and I will read it back." |
|
|
13
|
+
| connecting the Linear integration | `<their cloud>/settings/connections` (the Integrations page; Settings labels it Connections today) | "Open this page and connect Linear. It will send you to Linear to authorize it and bring you back." |
|
|
13
14
|
| installing the GitHub App | `<their cloud>/settings/connections` | "Open the same page and install the GitHub App, granting it the repository you want worked and `<your GitHub org>/thoughts`." |
|
|
14
15
|
| creating or confirming `<your GitHub org>/thoughts`, and letting the GitHub App reach it | GitHub, then the GitHub App's installation page for that org | "Catalyst's cloud phases write their notes to `<your GitHub org>/thoughts`, in the org that owns the code repository. Check it exists. If not, create a private repository named `thoughts`, initialized with a README. Then, on the GitHub App's installation page for that org, choose All repositories, or add `thoughts` to the selected repositories." |
|
|
15
16
|
| connecting personal Linear | the URL printed by `catalyst-skills connections personal linear start` | "Approve your own Linear account in this browser. I will check the grant status afterward." |
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Who fixes what
|
|
2
2
|
|
|
3
|
-
Setup is
|
|
3
|
+
Setup is eight 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. The product's words for what they own are Workspace, Project, Repository, Integration, Connected account and Member; the parts below are the instruments' names for the same things.
|
|
4
4
|
|
|
5
|
-
## The
|
|
5
|
+
## The eight parts, and the instrument that owns each
|
|
6
6
|
|
|
7
7
|
| part | instrument | what a pass proves | what it does **not** prove | who fixes a failure, and where |
|
|
8
8
|
| -- | -- | -- | -- | -- |
|
|
@@ -12,9 +12,10 @@ Setup is seven parts. Each has one instrument, and each instrument answers about
|
|
|
12
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
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
14
|
| **coding accounts** | `codingAccounts` in `catalyst-skills contract` (`catalyst-skills accounts` on an older cloud) | an account is enrolled and active | that it has headroom left for the next phase | the enroller and page the contract names; see `references/what-a-phase-needs.md` |
|
|
15
|
+
| **repository declarations** | the `environment_declared` check in `catalyst-skills contract --path teams`, and its per-repository notes | the project's default repository has a committed `.catalyst/catalyst.toml` in effect: ingested, valid, and its latest revision approved | that the values the names refer to exist; that any other repository is declared, unless its note says so | the person, with you, in the repository for the file; a tenant owner or admin for the approval, at the repository's Environment page under `<their cloud>/settings/repositories` |
|
|
15
16
|
| **host** | the `hosts_current` check in `catalyst-skills contract --path teams` | no host is missing or behind, or the tenant runs none | anything before a project has been checked | the owner the contract names for `hosts_current`; see `references/what-a-phase-needs.md` |
|
|
16
17
|
|
|
17
|
-
`node scripts/where-am-i.mjs` runs all
|
|
18
|
+
`node scripts/where-am-i.mjs` runs all eight, in the order a person can act on them, and labels each finding with its part. Use it rather than composing this by hand.
|
|
18
19
|
|
|
19
20
|
## The two silences that are not absences
|
|
20
21
|
|
|
@@ -34,9 +34,23 @@ export function tryLoadConfig() {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/** The recorded CLI path, credential or no credential: a machine that was connected once and is now
|
|
38
|
+
* logged out still has its CLI, and `status` on it is the honest first reading. Null when the config
|
|
39
|
+
* is absent or unreadable. */
|
|
40
|
+
function recordedCliPath() {
|
|
41
|
+
try {
|
|
42
|
+
const raw = JSON.parse(readFileSync(configPath(), "utf8"));
|
|
43
|
+
return typeof raw.cliPath === "string" && existsSync(raw.cliPath) ? raw.cliPath : null;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
37
49
|
/** Where the CLI is on this machine: the path login recorded when it still exists, else npx. */
|
|
38
50
|
export function cliTarget(cfg = tryLoadConfig()) {
|
|
39
|
-
const
|
|
51
|
+
const recordedPath = cfg !== null && typeof cfg.cliPath === "string" && existsSync(cfg.cliPath) ? cfg.cliPath : recordedCliPath();
|
|
52
|
+
if (recordedPath !== null) cfg = { ...(cfg ?? {}), cliPath: recordedPath };
|
|
53
|
+
const recorded = recordedPath !== null;
|
|
40
54
|
return recorded
|
|
41
55
|
? { command: process.execPath, prefix: [cfg.cliPath], via: `node ${cfg.cliPath}`, recorded: true }
|
|
42
56
|
: { command: "npx", prefix: [PACKAGE], via: `npx ${PACKAGE}`, recorded: false };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// where-am-i.mjs — how far has setup got?
|
|
2
|
+
// where-am-i.mjs — how far has setup got? EIGHT PARTS, EACH READ BY THE INSTRUMENT THAT OWNS IT, and
|
|
3
|
+
// reported in the order a person can act on them (coding account first, then the integrations, the
|
|
4
|
+
// project, its repository, the person's own connected accounts, the repository's declaration, the host);
|
|
3
5
|
// each finding labelled with the part it belongs to. The whole point of this script is that no part
|
|
4
6
|
// answers for another: a project that is not ready is reported as a project finding with a tenant
|
|
5
7
|
// owner's name on it, never as something the person at this keyboard can fix by running anything.
|
|
@@ -18,8 +20,9 @@ const NOTES = [
|
|
|
18
20
|
"`replica status --probe --json` and `events status --probe --json` (optional local freshness),",
|
|
19
21
|
"`me --json` and personal connection statuses (person), `contract --path …` for the account, the projects and the repositories,",
|
|
20
22
|
"`contract --path codingAccounts` and `accounts --json` (coding accounts, and which one needs a new credential), and each",
|
|
21
|
-
"project's hosts_current check with its fixedWhere (host).",
|
|
23
|
+
"project's hosts_current check with its fixedWhere (host), and each project's environment_declared check with its per-repository notes (repository declarations).",
|
|
22
24
|
"Also runs `gh repo view <owner>/thoughts` for each registered repository's owner, as a note: it shows the repository exists, never that the GitHub App can reach it.",
|
|
25
|
+
"When the tenant serves a contract version this CLI refuses, runs `npm view @catalyst-cloud/cli version` once to say whether a newer CLI is published.",
|
|
23
26
|
"Writes nothing and changes nothing. Runs before this machine is connected — that is one of the states it reports.",
|
|
24
27
|
];
|
|
25
28
|
|
|
@@ -45,8 +48,28 @@ let repositoryRegistered = false;
|
|
|
45
48
|
// `blocking` is false for a finding that is real and reportable but does not stop the next step —
|
|
46
49
|
// an unmatched Linear identity is the one that matters: it must be said, and it must not become the
|
|
47
50
|
// thing the person is told to go and do before they can map a project.
|
|
48
|
-
const add = (part, instrument, verdict, lines, owner = null, where = null, blocking = true) =>
|
|
49
|
-
|
|
51
|
+
const add = (part, instrument, verdict, lines, owner = null, where = null, blocking = true) => {
|
|
52
|
+
const p = { part, instrument, verdict, lines, owner, where, blocking };
|
|
53
|
+
parts.push(p);
|
|
54
|
+
return p;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// A tenant contract outside this CLI's range makes every contract read fail the same way. When that
|
|
58
|
+
// happens, no part read from the contract can back a next step, and the only honest next step is the
|
|
59
|
+
// version itself. The CLI's own hint says to update, which is wrong when no newer CLI is published.
|
|
60
|
+
let contractMismatch = null;
|
|
61
|
+
const noteMismatch = (text) => {
|
|
62
|
+
const m =
|
|
63
|
+
(text ?? "").match(/serves contract version (\S+) but this bundle accepts (\S+)/) ??
|
|
64
|
+
(text ?? "").match(/contract: version (\S+) is outside this bundle's range (\S+)/);
|
|
65
|
+
if (m !== null) contractMismatch ??= { served: m[1], accepts: m[2] };
|
|
66
|
+
return m !== null;
|
|
67
|
+
};
|
|
68
|
+
const readContract = (args) => {
|
|
69
|
+
const r = runCli(args);
|
|
70
|
+
noteMismatch(r.stderr);
|
|
71
|
+
return r;
|
|
72
|
+
};
|
|
50
73
|
|
|
51
74
|
// ── machine ───────────────────────────────────────────────────────────────────────────────────────
|
|
52
75
|
const status = runCli(["status"]);
|
|
@@ -87,10 +110,13 @@ if (connected) {
|
|
|
87
110
|
projectChecks = checks.filter((c) => typeof c.id === "string" && c.id.startsWith("team:"));
|
|
88
111
|
const machineChecks = checks.filter((c) => !(typeof c.id === "string" && c.id.startsWith("team:")));
|
|
89
112
|
const failed = machineChecks.filter((c) => !c.ok && !c.note);
|
|
90
|
-
|
|
113
|
+
// A refused contract version is printed without the CLI's "update" fix: whether an update exists
|
|
114
|
+
// is checked below, once, against what npm has published.
|
|
115
|
+
const refused = (c) => !c.ok && !c.note && noteMismatch(c.line);
|
|
116
|
+
for (const c of machineChecks) machineLines.push(`${c.note ? "note" : c.ok ? "ok " : "FAIL"} ${c.line}${!c.ok && !c.note && c.fix && !refused(c) ? ` — fix: ${c.fix}` : ""}`);
|
|
91
117
|
if (failed.length > 0) {
|
|
92
118
|
machineVerdict = "unfinished";
|
|
93
|
-
machineFix = failed.find((c) => typeof c.fix === "string")?.fix ?? null;
|
|
119
|
+
machineFix = failed.find((c) => typeof c.fix === "string" && !refused(c))?.fix ?? null;
|
|
94
120
|
}
|
|
95
121
|
}
|
|
96
122
|
// Supplemental only: local caches are optional and do not change setup completion or --next.
|
|
@@ -111,7 +137,8 @@ add(
|
|
|
111
137
|
machineVerdict,
|
|
112
138
|
machineLines,
|
|
113
139
|
"you, on this machine",
|
|
114
|
-
|
|
140
|
+
// The keyless login alone: a person who holds a personal key already knows the key form.
|
|
141
|
+
connected ? null : CONNECT_LINE.split(" (or")[0],
|
|
115
142
|
);
|
|
116
143
|
|
|
117
144
|
// ── person ────────────────────────────────────────────────────────────────────────────────────────
|
|
@@ -122,7 +149,8 @@ if (!connected) {
|
|
|
122
149
|
const doc = tryJson(me.stdout);
|
|
123
150
|
const user = doc && typeof doc.user === "object" && doc.user !== null ? doc.user : null;
|
|
124
151
|
if (doc === null) {
|
|
125
|
-
add("person", "catalyst-skills me", "unreadable", [`me could not be read (${(me.stderr || me.stdout).trim().split("\n")[0] ?? "no output"})`], null, null)
|
|
152
|
+
add("person", "catalyst-skills me", "unreadable", [`me could not be read (${(me.stderr || me.stdout).trim().split("\n")[0] ?? "no output"})`], null, null).next =
|
|
153
|
+
"run this again; catalyst-skills me could not be read, so no step after it can be named yet";
|
|
126
154
|
} else if (user === null) {
|
|
127
155
|
add(
|
|
128
156
|
"person",
|
|
@@ -161,7 +189,7 @@ if (!connected) {
|
|
|
161
189
|
"catalyst-skills me, and catalyst-skills connections personal <provider> status --json",
|
|
162
190
|
matched && !personalGrantIncomplete ? "ok" : "unfinished",
|
|
163
191
|
[`${user.label ?? "(unnamed)"} (${user.role ?? "role unknown"})`, matched ? "Linear identity matched" : "Linear identity NOT matched — asks assigned to you cannot be told apart from everyone else's. It blocks nothing below. Run catalyst-skills identity linear options for self-service recovery; personal Linear consent normally binds its viewer automatically.", ...grantLines],
|
|
164
|
-
personalGrantIncomplete ? "you" : matched ? null : "a
|
|
192
|
+
personalGrantIncomplete ? "you" : matched ? null : "a workspace owner or admin",
|
|
165
193
|
personalGrantIncomplete ? "catalyst-skills connections personal <provider> start or status" : matched ? null : link("/settings/account"),
|
|
166
194
|
false,
|
|
167
195
|
);
|
|
@@ -172,10 +200,11 @@ if (!connected) {
|
|
|
172
200
|
if (!connected) {
|
|
173
201
|
add("account", "catalyst-skills contract --path account", "unreadable", ["not readable until this machine is connected"], null, null);
|
|
174
202
|
} else {
|
|
175
|
-
const acct =
|
|
203
|
+
const acct = readContract(["contract", "--path", "account", "--json"]);
|
|
176
204
|
const doc = tryJson(acct.stdout);
|
|
177
205
|
if (doc === null) {
|
|
178
|
-
add("account", "catalyst-skills contract --path account", "unreadable", ["the account block could not be read — try: catalyst-skills contract --refresh"], null, null)
|
|
206
|
+
add("account", "catalyst-skills contract --path account", "unreadable", ["the account block could not be read — try: catalyst-skills contract --refresh"], null, null).next =
|
|
207
|
+
"refresh the contract (catalyst-skills contract --refresh), then run this again; the account could not be read, so no step after it can be named yet";
|
|
179
208
|
} else {
|
|
180
209
|
const workspace = typeof doc.linearWorkspaceSlug === "string" && doc.linearWorkspaceSlug !== "" ? doc.linearWorkspaceSlug : typeof doc.linearWorkspaceId === "string" && doc.linearWorkspaceId !== "" ? doc.linearWorkspaceId : null;
|
|
181
210
|
workspaceResolved = workspace !== null;
|
|
@@ -204,7 +233,7 @@ if (!connected) {
|
|
|
204
233
|
"the GitHub App install is NOT carried here — the connections page is the only place that shows it",
|
|
205
234
|
...envLines,
|
|
206
235
|
],
|
|
207
|
-
"a
|
|
236
|
+
"a workspace owner or admin",
|
|
208
237
|
link("/settings/connections"),
|
|
209
238
|
);
|
|
210
239
|
}
|
|
@@ -216,12 +245,13 @@ let teamRows = null;
|
|
|
216
245
|
if (!connected) {
|
|
217
246
|
add("projects", "catalyst-skills contract --path teams", "unreadable", ["not readable until this machine is connected"], null, null);
|
|
218
247
|
} else {
|
|
219
|
-
const teams =
|
|
248
|
+
const teams = readContract(["contract", "--path", "teams", "--json"]);
|
|
220
249
|
const doc = tryJson(teams.stdout);
|
|
221
250
|
const rows = Array.isArray(doc) ? doc : null;
|
|
222
251
|
teamRows = rows;
|
|
223
252
|
if (rows === null) {
|
|
224
|
-
add("projects", "catalyst-skills contract --path teams", "unreadable", ["the project list could not be read — try: catalyst-skills contract --refresh"], null, null)
|
|
253
|
+
add("projects", "catalyst-skills contract --path teams", "unreadable", ["the project list could not be read — try: catalyst-skills contract --refresh"], null, null).next =
|
|
254
|
+
"refresh the contract (catalyst-skills contract --refresh), then run this again; the project list could not be read, so no step after it can be named yet";
|
|
225
255
|
} else {
|
|
226
256
|
const lines = [`${rows.length} mapped`];
|
|
227
257
|
for (const t of rows) {
|
|
@@ -243,7 +273,7 @@ if (!connected) {
|
|
|
243
273
|
}
|
|
244
274
|
}
|
|
245
275
|
const ready = rows.length > 0 && rows.every(setUp);
|
|
246
|
-
add("projects", "catalyst-skills contract --path teams, and the team: checks of ready", ready ? "ok" : "unfinished", lines, "a
|
|
276
|
+
add("projects", "catalyst-skills contract --path teams, and the team: checks of ready", ready ? "ok" : "unfinished", lines, "a workspace owner or admin", link("/settings/linear-teams"));
|
|
247
277
|
}
|
|
248
278
|
}
|
|
249
279
|
|
|
@@ -262,17 +292,18 @@ function thoughtsNote(owner) {
|
|
|
262
292
|
if (!connected) {
|
|
263
293
|
add("repositories", "catalyst-skills contract --path merge.repositories", "unreadable", ["not readable until this machine is connected"], null, null);
|
|
264
294
|
} else {
|
|
265
|
-
const repos =
|
|
295
|
+
const repos = readContract(["contract", "--path", "merge.repositories", "--json"]);
|
|
266
296
|
const doc = tryJson(repos.stdout);
|
|
267
297
|
const rows = Array.isArray(doc) ? doc : null;
|
|
268
298
|
if (rows === null) {
|
|
269
|
-
add("repositories", "catalyst-skills contract --path merge.repositories", "unreadable", ["the repository list could not be read — try: catalyst-skills contract --refresh"], null, null)
|
|
299
|
+
add("repositories", "catalyst-skills contract --path merge.repositories", "unreadable", ["the repository list could not be read — try: catalyst-skills contract --refresh"], null, null).next =
|
|
300
|
+
"refresh the contract (catalyst-skills contract --refresh), then run this again; the repository list could not be read, so no step after it can be named yet";
|
|
270
301
|
} else {
|
|
271
302
|
const lines = [`${rows.length} registered`, ...rows.map((r) => `${r.owner ?? "?"}/${r.name ?? "?"}`)];
|
|
272
303
|
repositoryRegistered = rows.length > 0;
|
|
273
304
|
lines.push("⛔ REGISTRATION only. This carries no status and no project attachment, so it never proves a repository can be dispatched into.");
|
|
274
305
|
for (const owner of [...new Set(rows.map((r) => r.owner).filter((o) => typeof o === "string" && o !== ""))]) lines.push(thoughtsNote(owner));
|
|
275
|
-
add("repositories", "catalyst-skills contract --path merge.repositories", rows.length > 0 ? "ok" : "unfinished", lines, "a
|
|
306
|
+
add("repositories", "catalyst-skills contract --path merge.repositories", rows.length > 0 ? "ok" : "unfinished", lines, "a workspace owner or admin", link("/settings/repositories"));
|
|
276
307
|
}
|
|
277
308
|
}
|
|
278
309
|
|
|
@@ -282,17 +313,29 @@ if (!connected) {
|
|
|
282
313
|
// The contract says the state, its sentence, who enrolls one and on which page. An older cloud's
|
|
283
314
|
// contract has no `codingAccounts`, and only then is the owner assumed.
|
|
284
315
|
let accountsNext = "enrol a coding account a phase can run on";
|
|
316
|
+
// A note --next prints beside the next step: an ended or cancelled account is not unfinished, but the
|
|
317
|
+
// person must hear that it is kept for reporting and never re-tokened, before anyone reaches for a setup token.
|
|
318
|
+
let retireNote = null;
|
|
285
319
|
// The contract counts accounts by declared rotation, so one healthy account can hide another whose
|
|
286
320
|
// credential is dead. When it says `enrolled`, each account is read as well. An account needs a new
|
|
287
321
|
// credential when it is quarantined, expired or revoked, or when its last polls failed on the
|
|
288
|
-
// credential itself. The
|
|
289
|
-
|
|
322
|
+
// credential itself. The cloud's own verdict, `needsCredential`, wins when it is sent:
|
|
323
|
+
// it leaves out ended and revoked accounts and usage-endpoint refusals, which are not verdicts on the
|
|
324
|
+
// credential. Older clouds send only the poll fields, read with the same narrowed code list.
|
|
325
|
+
const CREDENTIAL_ERROR_CODES = new Set(["no_access_token", "no_credential"]);
|
|
290
326
|
const CREDENTIAL_FAILURE_STREAK = 3;
|
|
291
327
|
// A cancelled subscription or an ended account is not a credential problem: no token brings it
|
|
292
|
-
// back. It is
|
|
328
|
+
// back. It is kept for reporting, never re-credentialed, and it does not make setup unfinished.
|
|
293
329
|
const isRetirable = (a) => a?.renewalStatus === "canceled" || a?.status === "ended" || a?.declaredState === "ended";
|
|
294
330
|
const credentialProblem = (a) => {
|
|
295
331
|
if (isRetirable(a)) return null;
|
|
332
|
+
if (typeof a?.needsCredential === "boolean") {
|
|
333
|
+
if (!a.needsCredential) return null;
|
|
334
|
+
if (a.quarantined === true) return `quarantined${typeof a.quarantineReason === "string" && a.quarantineReason !== "" ? `: ${a.quarantineReason}` : ""}`;
|
|
335
|
+
if (a.status === "expired-or-revoked") return "expired or revoked";
|
|
336
|
+
return typeof a.lastPollErrorCode === "string" ? `its last ${a.pollFailureCount} polls failed with ${a.lastPollErrorCode}` : "the cloud says it needs a new credential";
|
|
337
|
+
}
|
|
338
|
+
if (a?.status === "ended" || (a?.revokedAtMs ?? null) !== null) return null;
|
|
296
339
|
if (a?.quarantined === true) return `quarantined${typeof a.quarantineReason === "string" && a.quarantineReason !== "" ? `: ${a.quarantineReason}` : ""}`;
|
|
297
340
|
if (a?.status === "expired-or-revoked") return "expired or revoked";
|
|
298
341
|
if (CREDENTIAL_ERROR_CODES.has(a?.lastPollErrorCode) && typeof a?.pollFailureCount === "number" && a.pollFailureCount >= CREDENTIAL_FAILURE_STREAK) {
|
|
@@ -309,7 +352,7 @@ const readAccountRows = () => {
|
|
|
309
352
|
if (!connected) {
|
|
310
353
|
add("coding accounts", "codingAccounts in catalyst-skills contract", "unreadable", ["not readable until this machine is connected"], null, null);
|
|
311
354
|
} else {
|
|
312
|
-
const res =
|
|
355
|
+
const res = readContract(["contract", "--path", "codingAccounts", "--json"]);
|
|
313
356
|
const ca = tryJson(res.stdout);
|
|
314
357
|
const older = ca === null && /has nothing at/.test(res.stderr ?? "");
|
|
315
358
|
const INSTRUMENT = "codingAccounts in catalyst-skills contract";
|
|
@@ -324,7 +367,8 @@ if (!connected) {
|
|
|
324
367
|
const dead = rows === null ? [] : rows.filter((a) => credentialProblem(a) !== null);
|
|
325
368
|
const retirable = rows === null ? [] : rows.filter(isRetirable);
|
|
326
369
|
const who = (a) => [a.label, a.email].find((v) => typeof v === "string" && v !== "");
|
|
327
|
-
const retireLine = retirable.length === 0 ? [] : [`${retirable.length} account(s)
|
|
370
|
+
const retireLine = retirable.length === 0 ? [] : [`${retirable.length} account(s) cancelled or ended (${retirable.map((a) => `${who(a) ?? a.displayName ?? a.accountSlot ?? "?"} (${a.provider ?? "unknown provider"})`).join(", ")}): kept for reporting, not used, and not counted here. Never replace their credential; reactivate one on the AI accounts page only if its subscription is live again.`];
|
|
371
|
+
retireNote = retireLine[0] ?? null;
|
|
328
372
|
if (rows === null) {
|
|
329
373
|
accountsNext = "read the coding accounts again; each account's credential could not be checked";
|
|
330
374
|
add("coding accounts", DETAIL, "unreadable", [...head, `coding accounts could not be checked in detail (${error}). Whether each one still has a working credential is unknown. Do not enroll one on this reading.`], null, null);
|
|
@@ -360,6 +404,7 @@ if (!connected) {
|
|
|
360
404
|
const { rows, error } = readAccountRows();
|
|
361
405
|
const olderLine = "this cloud is older than the bundle: its contract does not say whether a coding account is enrolled, so the account list is read instead";
|
|
362
406
|
if (rows === null) {
|
|
407
|
+
accountsNext = "read the coding accounts again; the account list could not be read this time";
|
|
363
408
|
add("coding accounts", "catalyst-skills accounts", "unreadable", [olderLine, `coding accounts could not be read (${error})`], null, null);
|
|
364
409
|
} else {
|
|
365
410
|
// An expired or revoked slot, or a quarantined one, cannot take work until an admin acts on it.
|
|
@@ -367,7 +412,7 @@ if (!connected) {
|
|
|
367
412
|
const lines = [olderLine, `${rows.length} enrolled, ${usable.length} able to take work`];
|
|
368
413
|
for (const a of rows) lines.push(`${a.accountSlot ?? "?"}: ${a.provider ?? "?"}, ${a.status ?? "status unknown"}${a.quarantined ? ", quarantined" : ""}`);
|
|
369
414
|
if (usable.length === 0) lines.push("no phase can start until one is enrolled and able to take work");
|
|
370
|
-
add("coding accounts", "catalyst-skills accounts", usable.length > 0 ? "ok" : "unfinished", lines, "a
|
|
415
|
+
add("coding accounts", "catalyst-skills accounts", usable.length > 0 ? "ok" : "unfinished", lines, "a workspace owner or admin", link("/settings/coding-accounts"));
|
|
371
416
|
}
|
|
372
417
|
} else {
|
|
373
418
|
accountsNext = "read the coding accounts again; the contract could not be read";
|
|
@@ -383,7 +428,7 @@ if (!connected) {
|
|
|
383
428
|
add("host", "hosts_current in catalyst-skills contract --path teams", "unreadable", ["not readable until this machine is connected"], null, null);
|
|
384
429
|
} else {
|
|
385
430
|
const checks = (teamRows ?? []).flatMap((t) => (Array.isArray(t.readiness?.checks) ? t.readiness.checks : []).filter((c) => c.id === "hosts_current").map((c) => ({ ...c, team: t.key ?? t.id ?? "(unkeyed)" })));
|
|
386
|
-
const meta = tryJson(
|
|
431
|
+
const meta = tryJson(readContract(["contract", "--path", "readinessChecks", "--json"]).stdout);
|
|
387
432
|
const row = Array.isArray(meta) ? meta.find((r) => r.id === "hosts_current") : null;
|
|
388
433
|
const hc = checks.find((c) => c.state === "fail") ?? checks.find((c) => c.state === "unknown") ?? checks[0] ?? null;
|
|
389
434
|
// The contract's printed line for this owner is written for a host that is behind. When no host is
|
|
@@ -399,7 +444,10 @@ if (!connected) {
|
|
|
399
444
|
const how = fw === null ? null : link(fw.page);
|
|
400
445
|
const howLines = fw?.command ? [`the owner runs: ${fw.command}`] : [];
|
|
401
446
|
if (hc === null) {
|
|
402
|
-
add("host", "hosts_current in catalyst-skills contract --path teams", "unreadable", [teamRows === null ? "the project list could not be read, so the host check cannot be either" : "no project has a readiness check yet, so whether a host is connected cannot be read. Press Re-check on the projects page."], "a
|
|
447
|
+
add("host", "hosts_current in catalyst-skills contract --path teams", "unreadable", [teamRows === null ? "the project list could not be read, so the host check cannot be either" : "no project has a readiness check yet, so whether a host is connected cannot be read. Press Re-check on the projects page."], "a workspace owner or admin", link("/settings/linear-teams")).next =
|
|
448
|
+
teamRows === null
|
|
449
|
+
? "refresh the contract (catalyst-skills contract --refresh), then run this again; the project list could not be read, so the host check cannot be either"
|
|
450
|
+
: "press Re-check on the projects page, then run this again";
|
|
403
451
|
} else if (hc.state === "pass") {
|
|
404
452
|
add("host", "hosts_current in catalyst-skills contract --path teams", "ok", [`hosts_current pass (read on ${hc.team})`]);
|
|
405
453
|
} else {
|
|
@@ -408,6 +456,70 @@ if (!connected) {
|
|
|
408
456
|
}
|
|
409
457
|
}
|
|
410
458
|
|
|
459
|
+
// ── repository declarations ───────────────────────────────────────────────────────────────────────
|
|
460
|
+
// A repository's own settings live in its committed `.catalyst/catalyst.toml`. Whether the project's
|
|
461
|
+
// default repository has one in effect is the contract's `environment_declared` check; the project's
|
|
462
|
+
// other repositories appear under that check's `repos`. Names only: nothing here reads a value, and
|
|
463
|
+
// nothing here reads a file on this machine. A cloud that sends no such check has nothing to read.
|
|
464
|
+
const DECL_INSTRUMENT = "environment_declared in catalyst-skills contract --path teams";
|
|
465
|
+
const DECL_REASONS = {
|
|
466
|
+
no_team_repo_default: { text: "no repository is the project's default yet: register one and make it the default", who: "a workspace owner or admin", page: "/settings/repositories" },
|
|
467
|
+
no_environment_declaration: { text: "no .catalyst/catalyst.toml on its default branch yet: write it with the person (names only, never a value), open a pull request, merge it", who: "the person, in the repository, with your help", page: null },
|
|
468
|
+
declaration_invalid: { text: "the committed .catalyst/catalyst.toml did not validate: fix the file (the ingest names the error) and merge the fix", who: "the person, in the repository, with your help", page: null },
|
|
469
|
+
declaration_read_failed: { text: "the committed .catalyst/catalyst.toml could not be read: fix the file (the ingest names the error) and merge the fix", who: "the person, in the repository, with your help", page: null },
|
|
470
|
+
declaration_awaiting_approval: { text: "the declaration is proposed and waits for approval: Settings → Repositories → the repository → Environment → Setup declaration → Approve this revision", who: "a workspace owner or admin", page: "/settings/repositories" },
|
|
471
|
+
};
|
|
472
|
+
const DECL_DO = "write .catalyst/catalyst.toml with references/declaring-a-repository.md, then open a pull request";
|
|
473
|
+
let declNext = "commit .catalyst/catalyst.toml to the repository and have an owner or admin approve it";
|
|
474
|
+
if (!connected) {
|
|
475
|
+
add("repository declarations", DECL_INSTRUMENT, "unreadable", ["not readable until this machine is connected"], null, null);
|
|
476
|
+
} else if (teamRows === null) {
|
|
477
|
+
add("repository declarations", DECL_INSTRUMENT, "unreadable", ["the project list could not be read, so no repository declaration can be either"], null, null).next =
|
|
478
|
+
"refresh the contract (catalyst-skills contract --refresh), then run this again; the project list could not be read, so no repository declaration can be either";
|
|
479
|
+
} else if (teamRows.length === 0) {
|
|
480
|
+
add("repository declarations", DECL_INSTRUMENT, "unreadable", ["no project is mapped yet, so there is no repository whose declaration could be read"], null, null).next =
|
|
481
|
+
"map a project first; a repository declaration is read per project";
|
|
482
|
+
} else if (teamRows.every((t) => !Array.isArray(t.readiness?.checks) || t.readiness.checks.length === 0)) {
|
|
483
|
+
// Mapped, never checked: the contract carries no checks at all until someone presses Re-check, and
|
|
484
|
+
// that silence is not "nothing to declare".
|
|
485
|
+
add("repository declarations", DECL_INSTRUMENT, "unreadable", ["no project has a readiness check yet, so whether its repository is declared cannot be read. Press Re-check on the projects page."], "a workspace owner or admin", link("/settings/linear-teams")).next =
|
|
486
|
+
"press Re-check on the projects page, then run this again";
|
|
487
|
+
} else {
|
|
488
|
+
const found = teamRows.flatMap((t) => (Array.isArray(t.readiness?.checks) ? t.readiness.checks : []).filter((c) => c.id === "environment_declared").map((c) => ({ ...c, team: t.key ?? t.id ?? "(unkeyed)" })));
|
|
489
|
+
const lines = [];
|
|
490
|
+
let verdict = "ok";
|
|
491
|
+
let owner = null;
|
|
492
|
+
let where = null;
|
|
493
|
+
let unknown = false;
|
|
494
|
+
const describe = (reason) => DECL_REASONS[reason] ?? { text: `environment_declared ${reason ?? "failed"} (a reason this bundle does not know; read it on the page)`, who: "a workspace owner or admin", page: "/settings/repositories" };
|
|
495
|
+
const flag = (team, repo, reason) => {
|
|
496
|
+
const r = describe(reason);
|
|
497
|
+
lines.push(`${team}${repo ? `, ${repo}` : ""}: ${r.text}`);
|
|
498
|
+
if (verdict === "ok") declNext = `${repo ? `${repo}: ` : ""}${r.text}`;
|
|
499
|
+
verdict = "unfinished";
|
|
500
|
+
owner ??= r.who;
|
|
501
|
+
where ??= r.page === null ? DECL_DO : link(r.page);
|
|
502
|
+
};
|
|
503
|
+
if (found.length === 0) {
|
|
504
|
+
lines.push("this cloud reports no repository declaration check for the mapped projects, so there is nothing to read here; the repository's Environment page is where one would show");
|
|
505
|
+
}
|
|
506
|
+
for (const c of found) {
|
|
507
|
+
if (c.state === "pass") lines.push(`${c.team}: a declaration is in effect for the project's default repository`);
|
|
508
|
+
else if (c.state === "fail") flag(c.team, null, c.reason);
|
|
509
|
+
else {
|
|
510
|
+
unknown = true;
|
|
511
|
+
lines.push(`${c.team}: environment_declared could not be read${c.reason ? ` (${c.reason})` : ""}; press Re-check on the projects page`);
|
|
512
|
+
}
|
|
513
|
+
for (const note of Array.isArray(c.repos) ? c.repos : []) if (typeof note?.repo === "string" && note.reason) flag(c.team, note.repo, note.reason);
|
|
514
|
+
}
|
|
515
|
+
if (unknown && verdict === "ok") {
|
|
516
|
+
add("repository declarations", DECL_INSTRUMENT, "unreadable", lines, "a workspace owner or admin", link("/settings/linear-teams")).next = "press Re-check on the projects page, then run this again";
|
|
517
|
+
} else {
|
|
518
|
+
if (verdict !== "ok") lines.push("Names only: a value never passes through this script or the file. The person enters values on the repository's Environment page.");
|
|
519
|
+
add("repository declarations", DECL_INSTRUMENT, verdict, lines, verdict === "ok" ? null : owner, verdict === "ok" ? null : where);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
411
523
|
// ── the single next step ──────────────────────────────────────────────────────────────────────────
|
|
412
524
|
// The machine's next action is not one sentence: an unconnected machine needs the login, and a
|
|
413
525
|
// connected one needs whatever check failed — and `ready` already printed that check's own fix, so
|
|
@@ -424,12 +536,20 @@ const NEXT = {
|
|
|
424
536
|
: personalGithubIncomplete
|
|
425
537
|
? "install the tenant GitHub App and register its repository before connecting your personal GitHub account"
|
|
426
538
|
: "get this person's seat and Linear identity sorted",
|
|
427
|
-
account: "connect Linear
|
|
539
|
+
account: "connect the Linear integration on the Connections page (the GitHub App comes later, with the repository)",
|
|
428
540
|
projects: "pick ONE project and map its stages (or adopt the Catalyst workflow)",
|
|
429
|
-
repositories: "
|
|
541
|
+
repositories: "install the GitHub App on the Connections page, granting it the repository you want worked; registering that repository on the Repositories page, attached to the project, is the step right after",
|
|
430
542
|
"coding accounts": accountsNext,
|
|
543
|
+
"repository declarations": declNext,
|
|
431
544
|
host: "connect a Catalyst host",
|
|
432
545
|
};
|
|
546
|
+
// The parts are read in the order their data allows and reported in the order a person can act on
|
|
547
|
+
// them: the coding account first (nothing runs without one, and it needs no other step), then the
|
|
548
|
+
// workspace's Linear integration, the project, the GitHub App with its repository, the person's own
|
|
549
|
+
// connected accounts (personal GitHub after the repository proves the App), the repository's
|
|
550
|
+
// declaration, and last the host. `--next` is the first unfinished part in this order.
|
|
551
|
+
const ORDER = ["machine", "coding accounts", "account", "projects", "repositories", "person", "repository declarations", "host"];
|
|
552
|
+
parts.sort((a, b) => ORDER.indexOf(a.part) - ORDER.indexOf(b.part));
|
|
433
553
|
// A personal Linear grant cannot start until the tenant's Linear workspace exists. Once that account
|
|
434
554
|
// connection is present, a missing personal grant becomes the next member step before project setup.
|
|
435
555
|
const personPart = parts.find((p) => p.part === "person");
|
|
@@ -441,16 +561,50 @@ if (personPart && personalGrantIncomplete && workspaceResolved) {
|
|
|
441
561
|
}
|
|
442
562
|
const blocked = parts.filter((p) => p.verdict !== "ok" && p.blocking);
|
|
443
563
|
const stuck = blocked[0] ?? parts.find((p) => p.verdict !== "ok") ?? null;
|
|
444
|
-
|
|
564
|
+
// ⛔ AN UNREADABLE PART NEVER BORROWS ITS "UNFINISHED" ACTION. NEXT holds what to do when a part was
|
|
565
|
+
// read and is not done. A part that could not be read has no such basis: its step is reading it
|
|
566
|
+
// again, and the parts after it depend on it, so none of theirs is named either. Coding accounts set
|
|
567
|
+
// their own unreadable step in `accountsNext`.
|
|
568
|
+
const actionFor = (p) =>
|
|
569
|
+
p.verdict === "unreadable" && p.part !== "coding accounts"
|
|
570
|
+
? (p.next ?? `run this again; the ${p.part} part could not be read, so no step after it can be named yet`)
|
|
571
|
+
: NEXT[p.part];
|
|
572
|
+
let next =
|
|
445
573
|
stuck === null
|
|
446
574
|
? null
|
|
447
|
-
: { part: stuck.part, action:
|
|
575
|
+
: { part: stuck.part, action: actionFor(stuck), owner: stuck.owner, where: stuck.where, blocking: stuck.blocking };
|
|
576
|
+
|
|
577
|
+
// A refused contract version outranks every step above: each of them was read from, or waits on, a
|
|
578
|
+
// contract this CLI would not accept. Whether an update exists is asked of npm, once, and only here.
|
|
579
|
+
function versionAdvice() {
|
|
580
|
+
const semver = (t) => (t ?? "").match(/\b(\d+)\.(\d+)\.(\d+)\b/);
|
|
581
|
+
const installed = semver(field("Bundle"));
|
|
582
|
+
const npm = spawnSync("npm", ["view", "@catalyst-cloud/cli", "version"], { encoding: "utf8", timeout: 10_000, shell: process.platform === "win32" });
|
|
583
|
+
const latest = npm.error || npm.status !== 0 ? null : semver(npm.stdout);
|
|
584
|
+
// The installer is the update path, not `catalyst-skills install`. Its command is on the app's
|
|
585
|
+
// setup page; this names the script it fetches rather than composing the request here.
|
|
586
|
+
const reinstall = `re-run the install command from the app's setup page (it installs from ${link("/install.sh")})`;
|
|
587
|
+
if (installed === null || latest === null) {
|
|
588
|
+
return { text: `Whether a newer Catalyst CLI is published could not be checked. You can ${reinstall}; if this still appears afterwards, tell the Catalyst team.`, owner: null };
|
|
589
|
+
}
|
|
590
|
+
const order = [1, 2, 3].map((i) => Math.sign(Number(installed[i]) - Number(latest[i]))).find((d) => d !== 0) ?? 0;
|
|
591
|
+
return order >= 0
|
|
592
|
+
? { text: "A newer Catalyst CLI isn't published yet. Tell the Catalyst team; nothing on this machine needs to change.", owner: "the Catalyst team" }
|
|
593
|
+
: { text: `Update the CLI: ${reinstall}.`, owner: "you, on this machine" };
|
|
594
|
+
}
|
|
595
|
+
if (contractMismatch !== null && stuck !== null) {
|
|
596
|
+
const advice = versionAdvice();
|
|
597
|
+
const line = `the tenant serves contract ${contractMismatch.served} and this CLI accepts ${contractMismatch.accepts}. ${advice.text}`;
|
|
598
|
+
stuck.lines.push(line);
|
|
599
|
+
next = { part: stuck.part, action: line, owner: advice.owner, where: null, blocking: true };
|
|
600
|
+
}
|
|
448
601
|
const finished = parts.every((p) => p.verdict === "ok");
|
|
449
602
|
|
|
450
603
|
if (flags.json) {
|
|
451
|
-
console.log(JSON.stringify({ cli: via, connected, cloud, personalConnections, parts, localSync, next, finished }));
|
|
604
|
+
console.log(JSON.stringify({ cli: via, connected, cloud, personalConnections, parts, localSync, next, finished, notes: retireNote === null ? [] : [retireNote] }));
|
|
452
605
|
} else if (flags.next) {
|
|
453
|
-
if (
|
|
606
|
+
process.on("exit", () => { if (retireNote !== null) console.log(`note: ${retireNote}`); });
|
|
607
|
+
if (next === null) console.log("nothing left: every part is finished, a coding account is enrolled and the host check passes. Move one card into the project's start stage (usually Todo).");
|
|
454
608
|
else console.log(`${next.part}: ${next.action}${next.blocking ? "" : " (does not block the steps below)"}${next.owner ? ` — who: ${next.owner}` : ""}${next.where ? ` — ${next.where.startsWith("http") ? "where" : "do"}: ${next.where}` : ""}`);
|
|
455
609
|
} else {
|
|
456
610
|
for (const p of parts) {
|
|
@@ -463,6 +617,6 @@ if (flags.json) {
|
|
|
463
617
|
if (p.verdict !== "ok" && p.where) console.log(` ${p.where.startsWith("http") ? "where" : "do"}: ${p.where}`);
|
|
464
618
|
console.log("");
|
|
465
619
|
}
|
|
466
|
-
console.log(next === null ? "next: nothing left — move one card into the project's
|
|
620
|
+
console.log(next === null ? "next: nothing left — move one card into the project's start stage (usually Todo) and watch." : `next: ${next.part} — ${next.action}${next.owner ? ` (${next.owner})` : ""}${next.where ? ` — ${next.where}` : ""}`);
|
|
467
621
|
}
|
|
468
622
|
process.exit(finished ? 0 : 1);
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Am I set up? Machine readiness (Node, the tenant connection, the cached contract, the CLI path, the skills, the SDK, the optional replica) 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. Use when someone asks "am I set up", "what is missing", "why does nothing happen", "is the replica running", or right after connecting a new machine. Reports; never repairs.
|
|
5
5
|
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.4 — written in this repository for customer tenants -->
|
|
8
8
|
|
|
9
9
|
# Am I set up?
|
|
10
10
|
|
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
7
7
|
---
|
|
8
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.
|
|
8
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.4 — written in this repository for customer tenants -->
|
|
9
9
|
|
|
10
10
|
# Connect me
|
|
11
11
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
How Catalyst Cloud runs a ticket on the customer's own tenant, as facts an agent loads on demand: the eight-phase ladder and what each phase produces, the eleven board slots and this team's live stage map, what happens when a phase fails (retry, backoff, Remediate, park), how the queue is ordered and routed and every reason a ticket is excluded, and the coding-account model. Use when a person asks "how does this work?", "why did it do that?", "why is this stuck?", "what runs next?" or "how does it prioritise?". Read-only; its scripts explain one ticket's eligibility in plain English, show what is running and queued, and print the tenant's stage map and thresholds straight from the contract.
|
|
5
5
|
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.4 — written in this repository for customer tenants -->
|
|
8
8
|
|
|
9
9
|
# How Catalyst works
|
|
10
10
|
|
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
7
7
|
---
|
|
8
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.
|
|
8
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.4 — written in this repository for customer tenants -->
|
|
9
9
|
|
|
10
10
|
# Run this project
|
|
11
11
|
|
package/skills/unstick/SKILL.md
CHANGED
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
7
7
|
---
|
|
8
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.
|
|
8
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.4 — written in this repository for customer tenants -->
|
|
9
9
|
|
|
10
10
|
# Unstick
|
|
11
11
|
|
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
7
7
|
---
|
|
8
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.
|
|
8
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.4 — written in this repository for customer tenants -->
|
|
9
9
|
|
|
10
10
|
# What needs me
|
|
11
11
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
The desk for a Catalyst Cloud tenant. Use when the person asks "what's happening?", "where are we?", "why is that stuck?", "what closed?", "what's next?", or asks for something to be done rather than known. Reads the tenant contract, what is running and queued, the eligibility explainer and the open asks through the catalyst-skills CLI, and answers in one reply with ticket ids. Routes work to a project owner and decisions to what-needs-me. Never composes a URL, never polls, never answers as the human.
|
|
5
5
|
allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.9.4 — written in this repository for customer tenants -->
|
|
8
8
|
|
|
9
9
|
# What's happening
|
|
10
10
|
|