@catalyst-cloud/catalyst-skills 0.2.1 → 0.3.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 (43) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/README.md +39 -29
  3. package/dist/args.js +3 -2
  4. package/dist/ask.js +26 -5
  5. package/dist/cli.js +14 -4
  6. package/dist/config.js +1 -1
  7. package/dist/contract.js +2 -2
  8. package/dist/http.js +22 -3
  9. package/dist/prompt.js +1 -1
  10. package/dist/ready.js +2 -2
  11. package/package.json +1 -1
  12. package/skills/catalyst-github/references/what-a-pr-accumulates.md +1 -1
  13. package/skills/catalyst-github/scripts/lib/cli.mjs +2 -2
  14. package/skills/catalyst-github/scripts/read-pr.mjs +1 -1
  15. package/skills/catalyst-linear/references/reading-a-ticket.md +2 -2
  16. package/skills/catalyst-linear/references/writing-to-linear.md +1 -1
  17. package/skills/catalyst-linear/scripts/lib/cli.mjs +1 -1
  18. package/skills/catalyst-setup/SKILL.md +2 -2
  19. package/skills/catalyst-setup/references/what-each-check-means.md +6 -5
  20. package/skills/catalyst-setup/scripts/lib/cli.mjs +2 -2
  21. package/skills/catalyst-setup/scripts/replica-status.mjs +1 -1
  22. package/skills/connect-me/SKILL.md +8 -8
  23. package/skills/connect-me/agents/openai.yaml +1 -1
  24. package/skills/connect-me/scripts/lib/cli.mjs +2 -2
  25. package/skills/how-catalyst-works/SKILL.md +1 -1
  26. package/skills/how-catalyst-works/references/coding-accounts.md +2 -2
  27. package/skills/how-catalyst-works/references/when-a-phase-fails.md +2 -2
  28. package/skills/how-catalyst-works/scripts/lib/cli.mjs +1 -1
  29. package/skills/how-catalyst-works/scripts/whats-running.mjs +1 -2
  30. package/skills/run-this-project/SKILL.md +1 -1
  31. package/skills/run-this-project/references/reacting-to-events.md +1 -1
  32. package/skills/run-this-project/references/stalls-and-escalation.md +1 -1
  33. package/skills/run-this-project/scripts/lib/cli.mjs +2 -2
  34. package/skills/what-needs-me/SKILL.md +1 -1
  35. package/skills/what-needs-me/references/reading-the-inbox.md +1 -1
  36. package/skills/what-needs-me/scripts/inbox.mjs +15 -6
  37. package/skills/what-needs-me/scripts/lib/cli.mjs +2 -2
  38. package/skills/whats-happening/SKILL.md +2 -2
  39. package/skills/whats-happening/assets/status-reply.json +1 -1
  40. package/skills/whats-happening/references/reading-the-board.md +1 -1
  41. package/skills/whats-happening/references/status-reply.md +1 -1
  42. package/skills/whats-happening/references/why-is-it-stuck.md +1 -1
  43. package/skills/whats-happening/scripts/lib/cli.mjs +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0
4
+
5
+ You connect with your own personal key now, not the tenant's shared account key: mint it yourself at Settings → API keys in the Catalyst Cloud app (every member can; no admin needed), and `login` prints who you connected as beside which tenant. Your agent acts as you — an ask it raises names you, and "what needs me" means you: `ask list` (and the what-needs-me skill) keeps only the asks assigned to your Linear user by default, `--anyone` lists the whole tenant, and when your Linear identity is not matched yet it says so and shows everything rather than an empty list. `customer.json` gains a `user` block (id, label, role, your Linear user id); an older config is still read unchanged. Connecting with the account key still works — it is the right credential for a host or daemon — but `login` tells you it names no person. Every skill, reference and help line that said "account key", or said execution history and coding-account status were not readable, has been rewritten: `explain --history` and `accounts` read them, and the two things your key genuinely cannot do (release a park, read PR labels or reactions) are named instead. The catalyst-setup reference now lists all eleven team checks, including `environment_declared`. Needs a cloud that admits personal keys on the agent routes; on an older cloud the contract read says so by name, and the account key still works until it is updated.
6
+
3
7
  ## 0.2.1
4
8
 
5
9
  Fixes five defects found by running 0.2.0's verbs against a live tenant. Three skills (catalyst-github, catalyst-setup, how-catalyst-works) carried a colon-and-space inside an unquoted description, which YAML reads as a nested map rather than text, so `npx skills add` skipped them and still reported success — every Codex, Cursor and OpenCode install of 0.2.0 quietly got five of the eight skills, and reinstalling with this version gives you all eight. `running` no longer asks for lease attributions it has no coordinates for, so the headline "what's happening?" verb works instead of failing for every tenant; the lease read is still available as `running --ticket T --phase P`. The CLI no longer truncates its own output at 64KB when a skill reads it through a pipe, which made `query issues --json` on a tenant past roughly eighty tickets return invalid JSON with a success code. `accounts` now actually reads your coding-account slots and renders them, `history <ticket>` (also `explain --history`) shows a ticket's execution history, and both say plainly when your cloud is older than this bundle rather than printing an empty success. `queue` with no `--team` reads every team on your tenant instead of being refused, as its help always promised. `query changes` works at all now: it reads the feed's NDJSON, where before it called any successful response a non-JSON body — nothing noticed because the documented `--since 0` is refused on any tenant whose feed has rotated, so a success was never reached. That refusal now names a cursor you can actually use, and `--since head` starts from now.
package/README.md CHANGED
@@ -4,9 +4,25 @@
4
4
 
5
5
  ## Install
6
6
 
7
- Two ways in. The plugin installs the set as a managed bundle that updates when we ship. `npx skills` copies editable files into your project. Pick one; installing both leaves you with every skill twice.
7
+ Start by pasting one sentence into the coding agent you already use:
8
8
 
9
- <details><summary><strong>Claude Code</strong></summary>
9
+ ```
10
+ Help me understand and set up Catalyst Cloud. Read https://staging.catalystcloud.dev/agent-guide.md first, then walk me through it step by step.
11
+ ```
12
+
13
+ Your agent reads the guide, explains Catalyst Cloud in terms of your own repositories and tickets, finds out where it is running and what is already installed, and does the setup below itself — asking once before it writes to your machine. Everything else on this page is the reference it follows.
14
+
15
+ Or by hand. One command, for every coding agent on the machine:
16
+
17
+ ```sh
18
+ npx skills@latest add coalesce-labs/catalyst-cloud-skills --all
19
+ ```
20
+
21
+ It installs all eight skills for each agent it detects (Claude Code, Codex, Cursor, OpenCode and the rest). Add `-g` to install into your home directory instead of the project. Skills installed this way do not auto-update; run `npx skills update -y` to refresh them.
22
+
23
+ <details><summary><strong>Alternative for Claude Code: the plugin marketplace</strong></summary>
24
+
25
+ The plugin installs the set as a managed bundle that updates when we ship. It needs a GitHub SSH key, and it does not load into the session you are already in — run `/reload-plugins` or restart afterwards. Pick one rail; installing both leaves you with every skill twice.
10
26
 
11
27
  ```
12
28
  /plugin marketplace add coalesce-labs/catalyst-cloud-skills
@@ -14,65 +30,59 @@ Two ways in. The plugin installs the set as a managed bundle that updates when w
14
30
  ```
15
31
  </details>
16
32
 
17
- <details><summary><strong>Codex</strong></summary>
33
+ <details><summary><strong>One agent at a time</strong></summary>
18
34
 
19
- Run this command in your project:
35
+ Codex:
20
36
 
21
37
  ```sh
22
38
  npx skills@latest add coalesce-labs/catalyst-cloud-skills -a codex
23
39
  ```
24
- </details>
25
40
 
26
- <details><summary><strong>Cursor</strong></summary>
27
-
28
- Run this command in your project:
41
+ Cursor:
29
42
 
30
43
  ```sh
31
44
  npx skills@latest add coalesce-labs/catalyst-cloud-skills -a cursor
32
45
  ```
33
- </details>
34
-
35
- <details><summary><strong>OpenCode, Amp, Windsurf and the rest</strong></summary>
36
46
 
37
- Run this command in your project:
47
+ OpenCode, Amp, Windsurf and the rest:
38
48
 
39
49
  ```sh
40
50
  npx skills@latest add coalesce-labs/catalyst-cloud-skills
41
51
  ```
42
52
 
43
- The installer asks which skills to take and which agents to install them on. Add `-g` to install into your home directory instead of the project. Skills installed this way do not auto-update; run `npx skills update -y` to refresh them.
53
+ Without `--all` the installer asks which skills to take and which agents to install them on.
44
54
  </details>
45
55
 
46
56
  ### Then connect to your tenant
47
57
 
48
- The skills call one CLI, and the CLI holds your credential. Install it once and connect this machine with your account key:
58
+ The skills call one CLI, and the CLI holds your credential. Install it once and connect this machine with your own **personal key** — mint it at Settings → API keys in the Catalyst Cloud app (every member can; no admin needed):
49
59
 
50
60
  ```sh
51
61
  npm install -g @catalyst-cloud/catalyst-skills
52
- CATALYST_CLOUD_TOKEN=<your-account-key> catalyst-skills login
62
+ CATALYST_CLOUD_TOKEN=<your-personal-key> catalyst-skills login
53
63
  catalyst-skills ready
54
64
  ```
55
65
 
56
- Passing the key as an environment variable is the recommended form, because a key typed into a command line lands in your shell history. With no key in the environment and a terminal attached, `catalyst-skills login` prompts for it without echoing it; `--key <your-account-key>` is the third form, for a script. `npx @catalyst-cloud/catalyst-skills login` works without the global install, and `bunx` works in place of `npx`. A non-default cloud is set with `CATALYST_CLOUD_BASE_URL` or `--base-url <url>`.
66
+ Passing the key as an environment variable is the recommended form, because a key typed into a command line lands in your shell history. With no key in the environment and a terminal attached, `catalyst-skills login` prompts for it without echoing it; `--key <your-personal-key>` is the third form, for a script. `npx @catalyst-cloud/catalyst-skills login` works without the global install, and `bunx` works in place of `npx`. A non-default cloud is set with `CATALYST_CLOUD_BASE_URL` or `--base-url <url>`.
57
67
 
58
68
  That is the whole setup. Everything below explains what you just installed.
59
69
 
60
70
  ## What this is
61
71
 
62
- Eight skills that let your coding agent run your own Catalyst Cloud tenant (https://catalystcloud.dev) from your seat: what is happening, what needs you, and what to do about it. They read your tenant through the Catalyst Cloud SDK, write to it through the tenant's agent proxy, and never compose a URL or run a tool of their own; every read, write and subscription is a `catalyst-skills` verb with `--help`. Every skill is plain Markdown under `skills/<name>/SKILL.md` in this repository, and the account key from your tenant admin is the only credential.
72
+ Eight skills that let your coding agent run your own Catalyst Cloud tenant (https://staging.catalystcloud.dev) from your seat: what is happening, what needs you, and what to do about it. They read your tenant through the Catalyst Cloud SDK, write to it through the tenant's agent proxy, and never compose a URL or run a tool of their own; every read, write and subscription is a `catalyst-skills` verb with `--help`. Every skill is plain Markdown under `skills/<name>/SKILL.md` in this repository, and your own personal key is the only credential. Your agent acts as you: the asks it raises and the comments it posts carry your name, and "what needs me" means you.
63
73
 
64
74
  ## What connecting does
65
75
 
66
- `login` does three things: it calls `GET /api/v1/me` with your key, which discovers your tenant from the key alone; it writes `~/.config/catalyst-cloud/customer.json` with file mode `0600`, holding your key and the absolute path of the CLI so every skill script can spawn the same binary; and it fetches the tenant contract (`GET /api/v1/agent/contract`) and caches it at `~/.config/catalyst-cloud/contract.json` with its ETag, so stage names and ids, label ids, the ask template, thresholds and the route table come from your tenant, live, and no skill restates them.
76
+ `login` does three things: it calls `GET /api/v1/me` with your key, which discovers your tenant and who you are from the key alone; it writes `~/.config/catalyst-cloud/customer.json` with file mode `0600`, holding your key, who you are (id, label, role, your Linear user id) and the absolute path of the CLI so every skill script can spawn the same binary; and it fetches the tenant contract (`GET /api/v1/agent/contract`) and caches it at `~/.config/catalyst-cloud/contract.json` with its ETag, so stage names and ids, label ids, the ask template, thresholds and the route table come from your tenant, live, and no skill restates them.
67
77
 
68
- `login` does not install skills; the install command above did that. Re-running `login` after a key rotation rewrites the config. `catalyst-skills status` prints which tenant this machine is connected to, `catalyst-skills ready` prints one READY or NOT READY verdict with the fix for each failure and who can apply it, and `catalyst-skills --version` prints the package version and its pinned tenant contract range.
78
+ `login` does not install skills; the install command above did that. Re-running `login` after a key rotation rewrites the config. `catalyst-skills status` prints which tenant this machine is connected to and as whom, `catalyst-skills ready` prints one READY or NOT READY verdict with the fix for each failure and who can apply it, and `catalyst-skills --version` prints the package version and its pinned tenant contract range.
69
79
 
70
- The setup skill Catalyst seeds into your repository ends by pointing at this same command. That served skill lives in the Catalyst Cloud application, not here; this is the only connect step a customer runs, and the credential is called the account key in both places.
80
+ The setup skill Catalyst seeds into your repository ends by pointing at this same command. That served skill lives in the Catalyst Cloud application, not here; this is the only connect step a customer runs. The tenant's **account key** (Settings → Account keys, admin-minted) is a different credential — for a host or daemon that runs unattended — and is not what a person connects their own agent with: it would strip your name from everything your agent writes, and `login` says so if you use one.
71
81
 
72
82
  ## Requirements
73
83
 
74
84
  - Node 22 or newer. The bundle uses Node's built-in SQLite module for the optional local replica, so there is no native dependency to build; if `better-sqlite3` resolves on the machine it is used instead. `catalyst-skills ready` names the exact reason when an older Node is found.
75
- - An account key from your tenant admin. The key is the only tenant selector: you never type a tenant or account id.
85
+ - Your personal key, minted by you at Settings → API keys. The key is the only tenant selector: you never type a tenant or account id. If your Linear identity is not matched yet, `login` says so; an admin matches it in Settings → Members, and until then "what needs me" shows everyone's asks.
76
86
  - An agent that discovers skills. Claude Code loads the plugin; Codex, Cursor, OpenCode and the rest read the `skills/<name>/SKILL.md` files the `npx skills` installer writes.
77
87
  - Bun is optional, only if you prefer `bunx` over `npx`.
78
88
 
@@ -110,16 +120,16 @@ Nothing rotates. The replica is upserts and deletes into one file, the cursor is
110
120
  | `catalyst-linear` | "Show me the ticket, the history, what Catalyst wrote on it." | Reads a ticket with its comments, relations, labels, linked pull requests and agent sessions inline, from the replica when fresh and the API otherwise, always naming the source; writes comments, card moves, labels and new tickets as the app actor; knows what a ticket accumulates as Catalyst works it. | [`SKILL.md`](skills/catalyst-linear/SKILL.md) |
111
121
  | `catalyst-github` | "Show me the PR, the checks, the review, the queue." | A ticket's pull request with its checks, reviews and review threads; whether it is mergeable under the repository's policy; what a PR accumulates as the ticket moves (the branch, the draft, the rewrite, the force-pushes, the labels, the queue). | [`SKILL.md`](skills/catalyst-github/SKILL.md) |
112
122
  | `how-catalyst-works` | "How does this work? Why did it do that? How does it prioritise?" | The execution model as references loaded on demand: the eight-phase ladder, the eleven board slots and this team's live stage map, what happens when a phase fails, how the queue is ordered and routed, every exclusion reason, and the coding-account model. Scripts explain one ticket's eligibility in plain English. | [`SKILL.md`](skills/how-catalyst-works/SKILL.md) |
113
- | `connect-me` | "Connect this machine to my tenant." | Connects the machine to the tenant with the account key, caches the tenant contract, verifies, and offers to start the replica. | [`SKILL.md`](skills/connect-me/SKILL.md) |
123
+ | `connect-me` | "Connect this machine to my tenant." | Connects the machine to the tenant with your own personal key, caches the tenant contract, verifies, and offers to start the replica. | [`SKILL.md`](skills/connect-me/SKILL.md) |
114
124
 
115
125
  Four skills only read (`whats-happening`, `catalyst-setup`, `catalyst-github`, `how-catalyst-works`). The four that write anything (`catalyst-linear`, `what-needs-me`, `run-this-project`, `connect-me`) are marked so an agent cannot invoke them on its own; the person asks for them. Every skill declares `allowed-tools` scoped to this package's own binary, so none of them needs a blanket shell grant.
116
126
 
117
127
  ## What a key cannot see yet
118
128
 
119
- Two facts have no tenant-facing route today, and the skills say so by name rather than guess:
129
+ Your personal key reads everything the skills need — tickets, pull requests, the eligibility explainer, the dispatch queue, fleet activity, per-ticket execution history (`catalyst-skills explain --history <ticket>`: phase attempts, remediation rounds, park state) and coding-account status (`catalyst-skills accounts`: provider, declared and observed state, window usage, walls, quarantine — never a credential; enrolling or pausing one is `<your cloud>/settings/coding-accounts`). Two things it cannot do, and the skills say so by name rather than guess:
120
130
 
121
- - Coding-account status (provider, declared and observed state, window usage, walls, quarantine). `catalyst-skills accounts` prints "not visible to an account key yet" and points at `<your cloud>/settings/coding-accounts`, where the tenant's settings page shows it.
122
- - Per-ticket execution history (phase attempts, remediation rounds, park state). `catalyst-skills explain --history <ticket>` prints the same kind of line and points at `<your cloud>/settings`. What a key can see is the eligibility explainer, the dispatch queue, fleet activity, agent sessions and lease attributions, which is what `explain`, `running` and `queue` read.
131
+ - Release a park. When a ticket is parked after repeated failures, an operator releases it; the skill names the park and points at `<your cloud>/settings`.
132
+ - Read pull-request labels or the reviewer's reaction. The mirror does not carry them; GitHub's own page does.
123
133
 
124
134
  ## Versions and origins
125
135
 
@@ -127,11 +137,11 @@ The package pins the tenant contract range `1.x`, recorded in `package.json` und
127
137
 
128
138
  ## What it writes on your machine
129
139
 
130
- - `~/.config/catalyst-cloud/customer.json`, written with mode `0600`, holding your account key and the CLI path.
140
+ - `~/.config/catalyst-cloud/customer.json`, written with mode `0600`, holding your personal key, who you are, and the CLI path.
131
141
  - `~/.config/catalyst-cloud/contract.json`, the cached tenant contract.
132
142
  - Only if you start them: `~/.config/catalyst-cloud/replica.db` with its `.pid` and `.writer.lock` sidecars, and `~/.config/catalyst-cloud/watch-cursor.json`.
133
143
 
134
- The skill files themselves are written by whichever install command you ran, in that tool's own location. Your account key goes into that one config file and nowhere else.
144
+ The skill files themselves are written by whichever install command you ran, in that tool's own location. Your personal key goes into that one config file and nowhere else.
135
145
 
136
146
  ## Updating
137
147
 
@@ -156,11 +166,11 @@ npm uninstall -g @catalyst-cloud/catalyst-skills
156
166
 
157
167
  ## If login fails
158
168
 
159
- - `catalyst-skills: GET /me failed (401): credential not accepted — ask your tenant admin for a valid account key` — the key is stale or mistyped. Ask your tenant admin for a valid account key, then run `login` again.
169
+ - `catalyst-skills: GET /me failed (401): credential not accepted — mint a personal key at Settings → API keys and log in again` — the key is stale, mistyped or revoked. Mint a new one, then run `login` again.
160
170
  - `catalyst-skills: GET /me failed (403): account-not-operational` — the tenant is suspended. This is an admin conversation on the tenant, not a local fix.
161
171
  - `catalyst-skills: could not reach <url>: <detail>` — the machine cannot reach the cloud. The URL is named in the message; check `CATALYST_CLOUD_BASE_URL` or `--base-url`.
162
172
  - A line naming two contract versions after `Connected to` — the tenant serves a contract outside this bundle's `1.x` range. The config is written; update the bundle before using the other skills.
163
- - `[catalyst-skills] GET /api/v1/agent/contract refused (403): the contract needs an account key` on stderr — the key is a workstation key, which connects but cannot read the contract or the machine-only routes. Ask your tenant admin for an account key for the full set.
173
+ - `[catalyst-skills] GET /api/v1/agent/contract refused (403): this cloud is older than the bundle …` on stderr — the cloud has not yet deployed personal-key access to the contract. Update the cloud, or connect with the tenant's account key until it has.
164
174
 
165
175
  ## License
166
176
 
package/dist/args.js CHANGED
@@ -3,7 +3,7 @@
3
3
  // error naming the verb, and every verb's --help is generated from its own table.
4
4
  import { UsageError } from "./errors.js";
5
5
  const GLOBAL_FLAGS = {
6
- key: { value: true, help: "account key (or CATALYST_CLOUD_TOKEN)" },
6
+ key: { value: true, help: "your personal key (or CATALYST_CLOUD_TOKEN)" },
7
7
  "base-url": { value: true, help: "Catalyst Cloud origin (or CATALYST_CLOUD_BASE_URL)" },
8
8
  "skills-dir": { value: true, help: "where the skills are copied (default ~/.claude/skills)" },
9
9
  force: { value: false, help: "replace a skill directory this package did not install" },
@@ -85,6 +85,7 @@ export const FLAG_TABLES = {
85
85
  default: { value: true, help: "raise: the default if silent" },
86
86
  blocks: { value: true, repeat: true, help: "raise: a ticket this decision blocks (repeatable)" },
87
87
  "nothing-to-block": { value: false, help: "raise: declare that nothing is blocked" },
88
+ anyone: { value: false, help: "list: every open ask in the tenant, not only the ones assigned to you" },
88
89
  "ask-key": { value: true, help: "raise: idempotency key" },
89
90
  answer: { value: true, help: "accept: the answering comment id" },
90
91
  role: { value: true, help: "accept: the role recording the answer" },
@@ -107,7 +108,7 @@ export const VERB_USAGE = {
107
108
  queue: "queue [--team K] [--json]",
108
109
  watch: "watch [--team K] [--ticket T]... [--project P] [--exec CMD] [--cursor-file <path>] [--from cursor|head]",
109
110
  write: "write <comment <ticket> --body|--stdin [--parent] [--bookkeeping] [--as-user] | state <ticket> --slot|--state-id|--state-type | label <ticket> --add... --remove... | create --team --title [--label] [--priority] | reaction <ticket>|--comment <id> --emoji <e> | attachment <ticket> --title --url | session <ticket> [--title] [--plan-file] [--activity]>",
110
- ask: "ask <raise --team --title [--context] [--option]... [--default] --blocks <ticket>...|--nothing-to-block [--ask-key] | accept <askTicket> --answer <commentId> --role <role> | list [--json]>",
111
+ ask: "ask <raise --team --title [--context] [--option]... [--default] --blocks <ticket>...|--nothing-to-block [--ask-key] | accept <askTicket> --answer <commentId> --role <role> | list [--anyone] [--json]>",
111
112
  ready: "ready [--json]",
112
113
  accounts: "accounts [--json]",
113
114
  };
package/dist/ask.js CHANGED
@@ -24,7 +24,7 @@ export async function cmdAsk(args, ctx) {
24
24
  case "accept":
25
25
  return accept(args, ctx, doc, api, rest);
26
26
  case "list":
27
- return list(args, ctx, doc, api);
27
+ return list(args, ctx, doc, api, cfg);
28
28
  default:
29
29
  throw new UsageError(`unknown ask subcommand: ${sub}`);
30
30
  }
@@ -74,6 +74,18 @@ async function accept(args, ctx, doc, api, rest) {
74
74
  ctx.stdout(args.json ? JSON.stringify(result) : `ask ${ask.identifier}: answer ${answer} recorded by ${role}`);
75
75
  return 0;
76
76
  }
77
+ /** Decide the inbox scope from the connected identity and the `--anyone` flag. A personal key with a
78
+ * matched Linear identity reads "mine" by default; `--anyone` widens; an unmatched identity or a
79
+ * host key cannot mean "me" and says so rather than filtering to nothing. */
80
+ export function inboxScope(cfg, anyone) {
81
+ if (anyone)
82
+ return { kind: "anyone" };
83
+ if (!cfg.user)
84
+ return { kind: "no-person" };
85
+ if (!cfg.user.linearUserId)
86
+ return { kind: "unmatched", label: cfg.user.label };
87
+ return { kind: "mine", linearUserId: cfg.user.linearUserId, label: cfg.user.label };
88
+ }
77
89
  /** Rank open asks by the priority-weighted count of open tickets each one blocks. Priority 1 (urgent)
78
90
  * weighs 4, 4 (low) weighs 1, 0 (none) weighs 1. */
79
91
  export function rankAsks(issues, doc, openState) {
@@ -113,22 +125,31 @@ export function rankAsks(issues, doc, openState) {
113
125
  state: String(issue.state ?? ""),
114
126
  blocks: blocked,
115
127
  score: blocked.reduce((sum, id) => sum + weight(byIdentifier.get(id)), 0),
128
+ assigneeId: typeof issue.assignee_id === "string" && issue.assignee_id !== "" ? issue.assignee_id : null,
116
129
  });
117
130
  }
118
131
  return out.sort((a, b) => b.score - a.score || a.identifier.localeCompare(b.identifier));
119
132
  }
120
- async function list(args, ctx, doc, api) {
133
+ async function list(args, ctx, doc, api, cfg) {
121
134
  const [issuesRes, states] = await Promise.all([api.getJson("/api/v1/issues", { query: { limit: 500 } }), fetchWorkflowStates(api)]);
122
135
  const issues = rowsOf(issuesRes.body);
123
136
  const terminal = new Set(states.filter((s) => ["completed", "canceled", "cancelled"].includes(s.type.toLowerCase())).map((s) => s.name.toLowerCase()));
124
137
  const openState = (issue) => !terminal.has(String(issue.state ?? "").toLowerCase());
125
- const ranked = rankAsks(issues, doc, openState);
138
+ const all = rankAsks(issues, doc, openState);
139
+ const scope = inboxScope(cfg, flagBool(args, "anyone"));
140
+ const ranked = scope.kind === "mine" ? all.filter((a) => a.assigneeId === scope.linearUserId) : all;
126
141
  if (args.json) {
127
- ctx.stdout(JSON.stringify(ranked));
142
+ ctx.stdout(JSON.stringify({ scope, asks: ranked }));
128
143
  return 0;
129
144
  }
145
+ if (scope.kind === "unmatched") {
146
+ ctx.stderr(`[catalyst-skills] your Linear identity is not matched yet (an admin matches it in Settings → Members), so this is every open ask, not only yours`);
147
+ }
148
+ else if (scope.kind === "no-person") {
149
+ ctx.stderr(`[catalyst-skills] connected with the tenant's account key, which names no person — this is every open ask; log in with your personal key to see only yours`);
150
+ }
130
151
  if (ranked.length === 0) {
131
- ctx.stdout("no open asks");
152
+ ctx.stdout(scope.kind === "mine" ? `no open asks assigned to ${scope.label} (${all.length} open in the tenant — add --anyone to see them)` : "no open asks");
132
153
  return 0;
133
154
  }
134
155
  for (const a of ranked)
package/dist/cli.js CHANGED
@@ -37,7 +37,7 @@ export function usageText() {
37
37
  `${PACKAGE_NAME} — the Catalyst Cloud customer CLI: connect to your tenant, read through the SDK, write through the agent proxy`,
38
38
  "",
39
39
  "Usage:",
40
- " catalyst-skills login [--key <account-key>] [--base-url <url>] [--start-replica]",
40
+ " catalyst-skills login [--key <personal-key>] [--base-url <url>] [--start-replica]",
41
41
  " catalyst-skills join ... (deprecated alias of login; removed in the next minor version)",
42
42
  " catalyst-skills install [--skills-dir <dir>] [--force] (repair path; your agent's own command installs the skills)",
43
43
  " catalyst-skills status | notice | me | ready | accounts",
@@ -192,10 +192,10 @@ async function cmdLogin(args, ctx, deps) {
192
192
  const manifest = readManifest();
193
193
  let key = (args.key ?? ctx.env.CATALYST_CLOUD_TOKEN ?? "").trim();
194
194
  if (!key && (deps.isTty ?? stdinIsTty)()) {
195
- key = (await (deps.promptSecret ?? promptSecret)("Account key (not echoed): ")).trim();
195
+ key = (await (deps.promptSecret ?? promptSecret)("Personal key (not echoed): ")).trim();
196
196
  }
197
197
  if (!key)
198
- throw new UsageError("login needs an account key: pass --key <account-key> or set CATALYST_CLOUD_TOKEN");
198
+ throw new UsageError("login needs your personal key: pass --key <personal-key> or set CATALYST_CLOUD_TOKEN");
199
199
  const baseUrl = normalizeBaseUrl(args.baseUrl ?? ctx.env.CATALYST_CLOUD_BASE_URL ?? DEFAULT_BASE_URL);
200
200
  const me = await fetchMe(baseUrl, key, ctx.fetch);
201
201
  let existing;
@@ -215,6 +215,7 @@ async function cmdLogin(args, ctx, deps) {
215
215
  name: me.name,
216
216
  permissions: me.permissions,
217
217
  principal: me.principal,
218
+ ...(me.user ? { user: me.user } : {}),
218
219
  joinedAt: ctx.now().toISOString(),
219
220
  lastSkillBundleVersion: manifest.version,
220
221
  skillsDir,
@@ -223,6 +224,12 @@ async function cmdLogin(args, ctx, deps) {
223
224
  };
224
225
  const written = writeConfig(ctx.home, config);
225
226
  ctx.stdout(`Connected to ${me.name} (${me.slug}) — account ${me.account}`);
227
+ if (me.user) {
228
+ ctx.stdout(`Connected as ${me.user.label} (${me.user.role})${me.user.linearUserId ? "" : " — your Linear identity is not matched yet, so \"what needs me\" will show everyone's asks until an admin matches it in Settings"}`);
229
+ }
230
+ else {
231
+ ctx.stderr(`[catalyst-skills] this is the tenant's account key (a host credential), not your own — the skills work, but nothing your agent writes will carry your name and "what needs me" cannot mean you. Mint a personal key at Settings → API keys and log in with that.`);
232
+ }
226
233
  ctx.stdout(`Config written to ${written.path} (mode ${formatMode(written.mode)}, holds your key and the CLI path)${written.mode === CONFIG_MODE ? "" : ` — expected ${formatMode(CONFIG_MODE)}; chmod it by hand`}`);
227
234
  ctx.stdout(`Tenant contract range: ${manifest.tenantContractRange}`);
228
235
  try {
@@ -273,11 +280,12 @@ function cmdStatus(ctx) {
273
280
  const manifest = readManifest();
274
281
  const cfg = loadConfig(ctx.home);
275
282
  if (!cfg) {
276
- ctx.stdout(`Not connected yet — run: CATALYST_CLOUD_TOKEN=<your account key> npx ${PACKAGE_NAME} login`);
283
+ ctx.stdout(`Not connected yet — run: CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE_NAME} login`);
277
284
  return 0;
278
285
  }
279
286
  ctx.stdout(`Tenant: ${cfg.name} (${cfg.slug}) — account ${cfg.account}`);
280
287
  ctx.stdout(`API: ${cfg.baseUrl} (principal: ${cfg.principal})`);
288
+ ctx.stdout(cfg.user ? `As: ${cfg.user.label} (${cfg.user.role})` : "As: the tenant's account key (a host credential — no person)");
281
289
  ctx.stdout(`Bundle: ${PACKAGE_NAME} ${manifest.version} (tenant contract range: ${manifest.tenantContractRange})`);
282
290
  if (cfg.cliPath)
283
291
  ctx.stdout(`CLI: ${cfg.cliPath}${existsSync(cfg.cliPath) ? "" : " (missing — re-run login)"}`);
@@ -292,6 +300,8 @@ async function cmdMe(args, ctx) {
292
300
  else {
293
301
  ctx.stdout(`${me.name} (${me.slug}) — account ${me.account}`);
294
302
  ctx.stdout(`principal: ${me.principal}; permissions: ${me.permissions ? me.permissions.join(", ") : "unrestricted"}`);
303
+ if (me.user)
304
+ ctx.stdout(`user: ${me.user.label} (${me.user.role}); linear: ${me.user.linearUserId ?? "unmatched"}`);
295
305
  }
296
306
  return 0;
297
307
  }
package/dist/config.js CHANGED
@@ -70,7 +70,7 @@ export function loadConfig(home) {
70
70
  export function requireConfig(ctx) {
71
71
  const cfg = loadConfig(ctx.home);
72
72
  if (!cfg) {
73
- throw new CliError(`not connected yet — run: CATALYST_CLOUD_TOKEN=<account key> npx ${PACKAGE_NAME} login`, "not-configured");
73
+ throw new CliError(`not connected yet — run: CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE_NAME} login`, "not-configured");
74
74
  }
75
75
  return cfg;
76
76
  }
package/dist/contract.js CHANGED
@@ -52,7 +52,7 @@ export function assertContractRange(version, range) {
52
52
  * Load the contract per the cloud's own cache policy: a cache younger than `doc.cache.maxAgeSeconds`
53
53
  * is used as-is; otherwise a conditional GET revalidates it (304 refreshes `fetchedAt`, 200 replaces
54
54
  * the document). A network failure on a cache older than `staleRefusalSeconds` refuses; a 403 (a
55
- * workstation key) refuses naming the account key; a major version outside the bundle's range refuses
55
+ * personal key on an older cloud) refuses naming the cloud version; a major version outside the bundle's range refuses
56
56
  * naming both versions.
57
57
  */
58
58
  export async function loadContract(ctx, cfg, opts = {}) {
@@ -79,7 +79,7 @@ export async function loadContract(ctx, cfg, opts = {}) {
79
79
  }
80
80
  catch (err) {
81
81
  if (err instanceof CliError && err.status === 403) {
82
- throw new CliError(`GET ${CONTRACT_ROUTE} refused (403): the contract needs an account key (ctc_acct_…); this key is a workstation key — ask your tenant admin for an account key`, "contract-forbidden", 2, 403);
82
+ throw new CliError(`GET ${CONTRACT_ROUTE} refused (403): this cloud is older than the bundle and does not yet admit a personal key on the contract — update the cloud, or log in with the tenant's account key until it is`, "contract-forbidden", 2, 403);
83
83
  }
84
84
  if (err instanceof MeError && err.kind === "network" && cached) {
85
85
  if (ageSeconds >= cached.doc.cache.staleRefusalSeconds) {
package/dist/http.js CHANGED
@@ -108,10 +108,10 @@ export class ApiClient {
108
108
  reason = raw.slice(0, 200);
109
109
  }
110
110
  if (res.status === 401) {
111
- throw new CliError(`${what} failed (401): credential not accepted — ask your tenant admin for a valid account key`, "unauthorized", 2, 401);
111
+ throw new CliError(`${what} failed (401): credential not accepted — mint a personal key at Settings → API keys and log in again`, "unauthorized", 2, 401);
112
112
  }
113
113
  if (res.status === 403) {
114
- throw new CliError(`${what} failed (403): this key may not reach that route${reason ? ` (${reason})` : ""} — a machine-gated route needs an account key (ctc_acct_…), not a workstation key`, "forbidden", 2, 403);
114
+ throw new CliError(`${what} failed (403): this key may not reach that route${reason ? ` (${reason})` : ""} — a route that acts as a host (claiming work, publishing artifacts) needs the tenant's account key; every read and write a person makes takes your personal key`, "forbidden", 2, 403);
115
115
  }
116
116
  if (res.status === 429) {
117
117
  const retryAfter = res.headers.get("retry-after");
@@ -147,7 +147,7 @@ export async function fetchMe(baseUrl, key, fetchImpl, timeoutMs = REQUEST_TIMEO
147
147
  reason = "";
148
148
  }
149
149
  const detail = res.status === 401
150
- ? "credential not accepted — ask your tenant admin for a valid account key"
150
+ ? "credential not accepted — mint a personal key at Settings → API keys and log in again"
151
151
  : reason || `HTTP ${res.status}`;
152
152
  throw new MeError(`GET /me failed (${res.status}): ${detail}`, "http", res.status);
153
153
  }
@@ -165,11 +165,30 @@ export async function fetchMe(baseUrl, key, fetchImpl, timeoutMs = REQUEST_TIMEO
165
165
  (body.principal !== "service" && body.principal !== "session")) {
166
166
  throw new MeError("GET /me returned an unexpected shape", "shape");
167
167
  }
168
+ const user = parseMeUser(body.user);
168
169
  return {
169
170
  account: body.account,
170
171
  slug: body.slug,
171
172
  name: body.name,
172
173
  permissions: body.permissions,
173
174
  principal: body.principal,
175
+ ...(user ? { user } : {}),
174
176
  };
175
177
  }
178
+ /** The `user` block a personal key gets from /me. Absent (undefined) is "a host key, no person";
179
+ * present-but-malformed is a shape error like any other field, never silently dropped. */
180
+ function parseMeUser(raw) {
181
+ if (raw === undefined)
182
+ return undefined;
183
+ if (typeof raw !== "object" || raw === null)
184
+ throw new MeError("GET /me returned an unexpected user shape", "shape");
185
+ const u = raw;
186
+ if (typeof u.id !== "string" ||
187
+ typeof u.label !== "string" ||
188
+ (u.email !== null && typeof u.email !== "string") ||
189
+ (u.role !== "owner" && u.role !== "admin" && u.role !== "member") ||
190
+ (u.linearUserId !== null && typeof u.linearUserId !== "string")) {
191
+ throw new MeError("GET /me returned an unexpected user shape", "shape");
192
+ }
193
+ return { id: u.id, label: u.label, email: u.email, role: u.role, linearUserId: u.linearUserId };
194
+ }
package/dist/prompt.js CHANGED
@@ -1,4 +1,4 @@
1
- // prompt.ts — the one interactive read in the CLI: asking for the account key on a terminal without
1
+ // prompt.ts — the one interactive read in the CLI: asking for the personal key on a terminal without
2
2
  // echoing it. The streams are parameters so the tests can drive it without a real TTY.
3
3
  import { createInterface } from "node:readline";
4
4
  /** True when there is a terminal to prompt on. Both ends matter: the question needs somewhere to go. */
package/dist/ready.js CHANGED
@@ -23,7 +23,7 @@ export async function readyReport(ctx, deps) {
23
23
  cfg = loadConfig(ctx.home);
24
24
  checks.push(cfg
25
25
  ? { id: "config", ok: true, line: `config: joined ${cfg.name} (${cfg.slug}) as ${cfg.principal}` }
26
- : { id: "config", ok: false, line: "config: not connected", fix: "CATALYST_CLOUD_TOKEN=<account key> npx @catalyst-cloud/catalyst-skills login", who: "you (the key comes from your tenant admin)" });
26
+ : { id: "config", ok: false, line: "config: not connected", fix: "CATALYST_CLOUD_TOKEN=<your personal key> npx @catalyst-cloud/catalyst-skills login", who: "you (mint the key at Settings → API keys)" });
27
27
  }
28
28
  catch (err) {
29
29
  checks.push({ id: "config", ok: false, line: `config: ${err instanceof CliError ? err.message : String(err)}`, fix: "re-run login to rewrite it", who: "you" });
@@ -31,7 +31,7 @@ export async function readyReport(ctx, deps) {
31
31
  const cache = readContractCache(ctx.home);
32
32
  const range = readManifest().tenantContractRange;
33
33
  if (!cache) {
34
- checks.push({ id: "contract", ok: false, line: "contract: not cached", fix: "catalyst-skills contract --refresh (needs an account key, not a workstation key)", who: "you" });
34
+ checks.push({ id: "contract", ok: false, line: "contract: not cached", fix: "catalyst-skills contract --refresh", who: "you" });
35
35
  }
36
36
  else if (contractVersionInRange(cache.contractVersion, range) !== true) {
37
37
  checks.push({ id: "contract", ok: false, line: `contract: version ${cache.contractVersion} is outside this bundle's range ${range}`, fix: "npm update -g @catalyst-cloud/catalyst-skills", who: "you" });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catalyst-cloud/catalyst-skills",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "Customer skill bundle for Catalyst Cloud: eight skills named from the executive's seat (whats-happening, what-needs-me, run-this-project, catalyst-setup, catalyst-linear, catalyst-github, how-catalyst-works, connect-me) plus the catalyst-skills CLI that reads through the Catalyst Cloud SDK, writes through the agent proxy, and connects your machine to your tenant with one command.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -42,7 +42,7 @@ Four labels matter, all named on `merge.prLabels`, all matched exactly (a queue'
42
42
  | `handStepsHold` | the cloud, automatically | the PR touches a path the queue configuration excludes (schema or migration paths, typically), so a person has to merge it by hand. |
43
43
  | `preview` | a human | "deploy me a preview and hold the merge until I have looked". Removing the label is the approval. |
44
44
 
45
- The mirror does not carry PR labels or reactions, so an account key cannot read which holds a PR carries or whether the reviewer reacted; the scripts say so by name. GitHub's own page, or the tenant's settings, is where those live.
45
+ The mirror does not carry PR labels or reactions, so your key cannot read which holds a PR carries or whether the reviewer reacted; the scripts say so by name. GitHub's own page, or the tenant's settings, is where those live.
46
46
 
47
47
  ## The queue is optional and opt-out
48
48
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // lib/cli.mjs — the one way a skill script reaches the Catalyst Cloud SDK and API: by spawning the
3
3
  // catalyst-skills CLI this bundle installed. It reads customer.json to find that CLI and nothing
4
- // else; it never holds the account key itself. This file is a library — run a sibling script with
4
+ // else; it never holds the key itself. This file is a library — run a sibling script with
5
5
  // --help for usage. Identical in every skill of this bundle on purpose (skills install one directory
6
6
  // at a time, so nothing shared outside the skill would ever be installed).
7
7
  import { spawnSync } from "node:child_process";
@@ -10,7 +10,7 @@ import { join } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
 
12
12
  export const PACKAGE = "@catalyst-cloud/catalyst-skills";
13
- export const CONNECT_HINT = `this machine is not connected to a tenant yet — run: CATALYST_CLOUD_TOKEN=<account key> npx ${PACKAGE} login`;
13
+ export const CONNECT_HINT = `this machine is not connected to a tenant yet — run: CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE} login`;
14
14
 
15
15
  /** ~/.config/catalyst-cloud/customer.json, honouring CATALYST_SKILLS_HOME before HOME. */
16
16
  export function configPath() {
@@ -93,5 +93,5 @@ console.log(`reviews (${reviews.length}):`);
93
93
  if (reviews.length === 0) console.log(" none mirrored; a reviewer's clean pass is a reaction, which this read does not carry");
94
94
  for (const r of reviews) console.log(` ${fmt(r.state)} ${fmt(r.reviewer_name, fmt(r.reviewer_id))} ${when(r.submitted_at)}`);
95
95
 
96
- console.log("not visible to an account key here: PR labels (holds, queue attestation) and the reviewer's reaction — see references/what-a-pr-accumulates.md");
96
+ console.log("not mirrored here: PR labels (holds, queue attestation) and the reviewer's reaction — see references/what-a-pr-accumulates.md");
97
97
  process.exit(0);
@@ -34,9 +34,9 @@ The comment shapes in `references/what-a-ticket-accumulates.md` tell you which c
34
34
 
35
35
  A phase's transcript (the full session log) exists in the cloud per ticket, but this bundle has no verb for it yet. Read the artifact document first; it is the phase's own account of what it did. Reach for the transcript only when the document leaves the question open, and say that the bundle cannot fetch it so the human opens it from the ticket's attachments.
36
36
 
37
- ## What an account key cannot read
37
+ ## What the ledger adds to the comments
38
38
 
39
- Per-ticket execution history beyond the comments (the attempt ledger, the remediation round count against the cap, park state and what releases it) is not readable with an account key yet; `catalyst-skills explain --history <ticket>` prints that by name and where to read it. Do not reconstruct a round count from comments and present it as the cap's count.
39
+ Per-ticket execution history beyond the comments (the attempt ledger, the remediation round count against the cap, park state and what releases it) is `catalyst-skills explain --history <ticket>`, read from the cloud's own relay ledger. Do not reconstruct a round count from comments and present it as the cap's count; read it.
40
40
 
41
41
  ## How to cite
42
42
 
@@ -12,7 +12,7 @@ This reference restates the write mechanism. The live values it depends on — t
12
12
  | a new ticket | `node scripts/create-ticket.mjs --team <key> --title <text>` |
13
13
  | a decision for a human | not here: the `what-needs-me` skill raises an ask with options, a default and what it blocks |
14
14
 
15
- Each script wraps one `catalyst-skills write` verb, which posts to the route the contract names, as the tenant's app actor, with the account key this machine connected with. No script composes a URL, and none needs a Linear credential of its own.
15
+ Each script wraps one `catalyst-skills write` verb, which posts to the route the contract names, as the tenant's app actor, with the personal key this machine connected with — so the write carries the person's identity for attribution, and an ask names them. No script composes a URL, and none needs a Linear credential of its own.
16
16
 
17
17
  ## App actor versus personal identity
18
18
 
@@ -8,7 +8,7 @@ import { join } from "node:path";
8
8
  import { pathToFileURL } from "node:url";
9
9
 
10
10
  export const PACKAGE = "@catalyst-cloud/catalyst-skills";
11
- export const CONNECT_HINT = `CATALYST_CLOUD_TOKEN=<account key> npx ${PACKAGE} login`;
11
+ export const CONNECT_HINT = `CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE} login`;
12
12
 
13
13
  /** ~/.config/catalyst-cloud/customer.json, honouring CATALYST_SKILLS_HOME (used by tests) over HOME. */
14
14
  export function configPath() {
@@ -23,10 +23,10 @@ You answer one question with one verdict and one list. The verdict is READY or N
23
23
 
24
24
  ## Rules
25
25
 
26
- - Report, never repair. No tenant-reachable repair verb exists for an account key yet; the settings page is where a tenant owner or admin fixes a mapping, a connection or a label, and you say which one.
26
+ - Report, never repair. No tenant-reachable repair verb exists for a key yet; the settings page is where a tenant owner or admin fixes a mapping, a connection or a label, and you say which one.
27
27
  - End every answer with the verdict and the who-can-click-what list, in that order.
28
28
  - A stale or absent replica is a note, never a failure: every skill reads the API meanwhile and says so. Do not tell the person to start it unless they want local SQL or cheaper repeated reads.
29
- - Not connected (exit 2) means the connect step, not a retry: `CATALYST_CLOUD_TOKEN=<account key> npx @catalyst-cloud/catalyst-skills login`, with the key from their tenant admin. Never guess a tenant; the key is the only selector.
29
+ - Not connected (exit 2) means the connect step, not a retry: `CATALYST_CLOUD_TOKEN=<your personal key> npx @catalyst-cloud/catalyst-skills login`, with the person's own key from Settings → API keys. Never guess a tenant; the key is the only selector.
30
30
  - Waiting is not failing. "No write observed", "no delivery observed" and "no host connected" clear themselves the first time the thing happens; say that instead of raising them.
31
31
  - Unknown is not a pass. A check the engine could not run is reported as such, never rounded up.
32
32
  - Never run a check in a loop. If the person wants to know when a waiting check clears, that is the project-running skill's watch.
@@ -4,9 +4,9 @@ This page restates invariants: the ten per-team readiness checks the cloud runs,
4
4
 
5
5
  ## How a team's readiness is scored
6
6
 
7
- A team's status is one of `ready`, `degraded`, `blocked` or `unchecked`. The engine always reports all ten checks; a check it could not run is `unknown`, never `pass`. Any failing check the contract marks as blocking makes the team blocked; any unknown, and any failing check marked degrading, makes it degraded. Unchecked means no readiness pass has run for that team yet, which is a note, not a failure. Readiness is stamped with the account-wide mapping revision it was computed against, so a stale verdict is visible as such.
7
+ A team's status is one of `ready`, `degraded`, `blocked` or `unchecked`. The engine always reports all eleven checks; a check it could not run is `unknown`, never `pass`. Any failing check the contract marks as blocking makes the team blocked; any unknown, and any failing check marked degrading, makes it degraded. Unchecked means no readiness pass has run for that team yet, which is a note, not a failure. Readiness is stamped with the account-wide mapping revision it was computed against, so a stale verdict is visible as such.
8
8
 
9
- ## The ten checks
9
+ ## The eleven checks
10
10
 
11
11
  | check id | proves | when it fails, the fix | who clicks |
12
12
  | -- | -- | -- | -- |
@@ -20,6 +20,7 @@ A team's status is one of `ready`, `degraded`, `blocked` or `unchecked`. The eng
20
20
  | `writes_land` | Catalyst has written to this team successfully | "no write observed" is waiting, not failing: it clears the first time Catalyst moves a ticket. "Write refused" means Linear rejected the last write: check the connection and the team's permissions | owner or admin when refused; otherwise nobody |
21
21
  | `webhook_covers_team` | events for this team are arriving | confirmed once a repository is registered and events flow; "no delivery observed" is waiting | owner or admin, by registering the repository |
22
22
  | `hosts_current` | no connected host runs an older mapping revision | a host that is behind re-loads the mapping on its next connect; a host that did not report its revision is flagged rather than assumed current; "no host connected" is waiting | whoever runs that host |
23
+ | `environment_declared` | a committed environment declaration for the team's default repository is in effect (`.catalyst/environment.json` was ingested, is valid, and its latest proposal is approved) | `no_team_repo_default`: register a repository and make it the team's default; `no_environment_declaration`: commit the declaration file to that repository; `declaration_invalid` / `declaration_read_failed`: fix the file (the ingest names the error); `declaration_awaiting_approval`: an owner or admin approves the proposal in Settings → Environment |
23
24
 
24
25
  Three checks are informational by design (labels, event delivery, host currency): they degrade a team but never block it. Which are which is served on `readinessChecks[].severity`; do not memorise the split.
25
26
 
@@ -36,8 +37,8 @@ Three checks are informational by design (labels, event delivery, host currency)
36
37
  | id | proves | fix |
37
38
  | -- | -- | -- |
38
39
  | `node` | Node 22 or newer, which the SDK's built-in SQLite engine needs | install Node 22+ |
39
- | `config` | this machine is connected: `customer.json` exists and loads | `CATALYST_CLOUD_TOKEN=<account key> npx @catalyst-cloud/catalyst-skills login`; the key comes from the tenant admin |
40
- | `contract` | the tenant contract is cached and its major version is one this bundle accepts | `catalyst-skills contract --refresh`; a version outside the range means update the bundle. A refresh needs an account key, not a workstation key |
40
+ | `config` | this machine is connected: `customer.json` exists and loads | `CATALYST_CLOUD_TOKEN=<your personal key> npx @catalyst-cloud/catalyst-skills login`; the person mints the key at Settings → API keys |
41
+ | `contract` | the tenant contract is cached and its major version is one this bundle accepts | `catalyst-skills contract --refresh`; a version outside the range means update the bundle. A 403 naming an older cloud means the cloud has not yet deployed personal-key access |
41
42
  | `cliPath` | the CLI path recorded at login still exists, so skill scripts can spawn it | re-run login |
42
43
  | `skills` | every skill of this bundle is present where the CLI installed them | `catalyst-skills install` |
43
44
  | `sdk` | the SDK loads, so the replica and the watch are available | run under Node 22.15 or newer; every read still works through the API meanwhile |
@@ -58,4 +59,4 @@ Three checks are informational by design (labels, event delivery, host currency)
58
59
 
59
60
  ## Who can click what
60
61
 
61
- The last block `check.mjs` prints groups every failure by the person it needs. Machine fixes name "you", the person at the keyboard. Tenant fixes name the owner or admin roles the contract lists (as Linear user ids, since that is how the cloud knows them), because the settings page that repairs a mapping, a connection or a label is theirs. A check the contract marks as not needing an answer names nobody: it is informational or self-clearing. This skill reports; it never repairs, because no tenant-reachable repair verb exists for an account key yet.
62
+ The last block `check.mjs` prints groups every failure by the person it needs. Machine fixes name "you", the person at the keyboard. Tenant fixes name the owner or admin roles the contract lists (as Linear user ids, since that is how the cloud knows them), because the settings page that repairs a mapping, a connection or a label is theirs. A check the contract marks as not needing an answer names nobody: it is informational or self-clearing. This skill reports; it never repairs, because no key-reachable repair verb exists yet.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // lib/cli.mjs — the one way a skill script reaches the Catalyst Cloud SDK and API: by spawning the
3
3
  // catalyst-skills CLI this bundle installed. It reads customer.json to find that CLI and nothing
4
- // else; it never holds the account key itself. This file is a library — run a sibling script with
4
+ // else; it never holds the key itself. This file is a library — run a sibling script with
5
5
  // --help for usage. Identical in every skill of this bundle on purpose (skills install one directory
6
6
  // at a time, so nothing shared outside the skill would ever be installed).
7
7
  import { spawnSync } from "node:child_process";
@@ -10,7 +10,7 @@ import { join } from "node:path";
10
10
  import { fileURLToPath } from "node:url";
11
11
 
12
12
  export const PACKAGE = "@catalyst-cloud/catalyst-skills";
13
- export const CONNECT_HINT = `this machine is not connected to a tenant yet — run: CATALYST_CLOUD_TOKEN=<account key> npx ${PACKAGE} login`;
13
+ export const CONNECT_HINT = `this machine is not connected to a tenant yet — run: CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE} login`;
14
14
 
15
15
  /** ~/.config/catalyst-cloud/customer.json, honouring CATALYST_SKILLS_HOME before HOME. */
16
16
  export function configPath() {
@@ -14,7 +14,7 @@ const HELP = `Usage: node scripts/replica-status.mjs [--probe] [--json]
14
14
  Exit codes, passed through from catalyst-skills replica status:
15
15
  0 fresh: a live writer, a young heartbeat, a cursor — skills read the replica
16
16
  1 stale: the file exists but the writer is gone or behind — skills read the API and say so
17
- 2 not connected to a tenant — run: CATALYST_CLOUD_TOKEN=<account key> npx @catalyst-cloud/catalyst-skills login
17
+ 2 not connected to a tenant — run: CATALYST_CLOUD_TOKEN=<your personal key> npx @catalyst-cloud/catalyst-skills login
18
18
  3 absent: no replica file — optional; start one with: catalyst-skills replica start --detach`;
19
19
 
20
20
  const args = process.argv.slice(2);
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: connect-me
3
3
  description: >-
4
- Connect this machine to the customer's Catalyst Cloud tenant with their account key, cache the tenant contract, and verify. Use when a person is getting started, when their config is missing or broken, when they rotated their key, when a skill script exits 2 saying the machine is not connected, or when they ask which tenant this machine belongs to. The key names the tenant, so nobody types an account id. Installing the skills is not this skill's job; the agent's own install command did that.
4
+ Connect this machine to the person's Catalyst Cloud tenant with their own personal key, cache the tenant contract, and verify. Use when a person is getting started, when their config is missing or broken, when they rotated their key, when a skill script exits 2 saying the machine is not connected, or when they ask which tenant this machine belongs to. The key names the tenant and the person, so nobody types an account id. Installing the skills is not this skill's job; the agent's own install command did that.
5
5
  disable-model-invocation: true
6
6
  allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:*)
7
7
  ---
@@ -9,28 +9,28 @@ allowed-tools: Bash(catalyst-skills:*) Bash(npx @catalyst-cloud/catalyst-skills:
9
9
 
10
10
  # Connect me
11
11
 
12
- Connecting is one command. It asks the cloud who the key belongs to, writes the config every skill and the CLI read, and caches the tenant contract beside it.
12
+ Connecting is one command. It asks the cloud who the key belongs to — which tenant, and which person — writes the config every skill and the CLI read, and caches the tenant contract beside it.
13
13
 
14
14
  ```sh
15
- CATALYST_CLOUD_TOKEN=<account-key> catalyst-skills login
15
+ CATALYST_CLOUD_TOKEN=<your-personal-key> catalyst-skills login
16
16
  ```
17
17
 
18
- With no environment variable and a terminal attached, `catalyst-skills login` prompts for the key without echoing it. `--key <account-key>` is the third form, for a script. `npx @catalyst-cloud/catalyst-skills login` works when the package is not installed globally. A non-default cloud is pinned with `CATALYST_CLOUD_BASE_URL` or `--base-url`.
18
+ With no environment variable and a terminal attached, `catalyst-skills login` prompts for the key without echoing it. `--key <personal-key>` is the third form, for a script. `npx @catalyst-cloud/catalyst-skills login` works when the package is not installed globally. A non-default cloud is pinned with `CATALYST_CLOUD_BASE_URL` or `--base-url`.
19
19
 
20
20
  **This skill never installs skills.** You are already reading one, so the person's agent installed the set with its own command. Connecting is only about the credential and the contract.
21
21
 
22
- **What it writes.** `~/.config/catalyst-cloud/customer.json` (mode 0600) holding the key, the tenant it resolved, and the absolute path of this CLI so skill scripts can spawn it; `~/.config/catalyst-cloud/contract.json`, the tenant contract with its ETag.
22
+ **What it writes.** `~/.config/catalyst-cloud/customer.json` (mode 0600) holding the key, the tenant it resolved, the person it resolved (`user`: id, label, role, Linear user id), and the absolute path of this CLI so skill scripts can spawn it; `~/.config/catalyst-cloud/contract.json`, the tenant contract with its ETag.
23
23
 
24
24
  **The replica is optional and one command away.** Nothing has to be running for reads, writes, asks or explanations; the API is origin-fresh. `catalyst-skills replica start --detach` starts the local replica for cheap repeated reads and ad hoc SQL; `login --start-replica` does the same at the end. Every skill checks `catalyst-skills replica status` first and falls back to the API when the replica is absent or stale, saying so.
25
25
 
26
26
  ## What you do
27
27
 
28
- 1. Ask the person for their account key. Their tenant admin mints it in settings; you cannot. A workstation key connects but is refused by the contract and by machine-only reads (a one-line notice, not a failure); tell them an account key is needed for the full set.
29
- 2. Run the login command. Its output names the tenant (`Connected to <name> (<slug>)`), the config path, and the cached contract version.
28
+ 1. Ask the person to mint their **personal key** at Settings → API keys in the Catalyst Cloud web app (every active member can; no admin is needed) and hand it to the login command. It is shown once. Never put it in a file, a ticket, or this conversation beyond that one command. Do not ask an admin for the tenant's **account key** (`ctc_acct_`, Settings → Account keys): that is a host credential — runners, daemons, the host replica — and a person connected with it has no name on anything their agent writes, and "what needs me" cannot mean them. If they connect with one anyway, login says so on stderr and still works.
29
+ 2. Run the login command. Its output names the tenant (`Connected to <name> (<slug>)`), the person (`Connected as <label> (<role>)`), the config path, and the cached contract version. If the person line says their Linear identity is not matched yet, tell them: an admin matches it in Settings → Members, and until then "what needs me" shows everyone's asks.
30
30
  3. Verify with `node scripts/verify-connection.mjs`: one line each for the tenant, the contract version, and the replica; exit 1 when the machine is not connected.
31
31
  4. Run `catalyst-skills ready` and read the verdict to them.
32
32
  5. Offer the replica: `catalyst-skills replica start --detach`, then `catalyst-skills replica status`. For keeping it alive across a reboot, load `references/keeping-the-replica-running.md`.
33
- 6. If login fails: a `401` means a stale or mistyped key, back to the tenant admin, never a retry loop; a network error names the URL, check `--base-url`. If login succeeds but prints a line naming two contract versions, the tenant serves a contract outside this bundle's range: update the bundle before using the other skills.
33
+ 6. If login fails: a `401` means a stale, mistyped or revoked key — mint a new one at Settings → API keys, never a retry loop; a network error names the URL, check `--base-url`. A `403` on the contract naming an older cloud means the cloud has not yet deployed personal-key access: update the cloud, or connect with the account key until it has. If login succeeds but prints a line naming two contract versions, the tenant serves a contract outside this bundle's range: update the bundle before using the other skills.
34
34
 
35
35
  ## The verbs a session runs first
36
36
 
@@ -1,6 +1,6 @@
1
1
  interface:
2
2
  display_name: "Connect me"
3
- short_description: "Connect this machine to your Catalyst Cloud tenant with your account key, cache the tenant contract, and verify"
3
+ short_description: "Connect this machine to your Catalyst Cloud tenant with your own personal key, cache the tenant contract, and verify"
4
4
  default_prompt: "Use $connect-me to connect this machine to my Catalyst Cloud tenant."
5
5
  policy:
6
6
  allow_implicit_invocation: false
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // lib/cli.mjs — the one way a skill script reaches Catalyst Cloud: by spawning the catalyst-skills
3
- // CLI. The CLI holds the SDK and the account key; this file holds neither. It reads
3
+ // CLI. The CLI holds the SDK and the key; this file holds neither. It reads
4
4
  // ~/.config/catalyst-cloud/customer.json (under CATALYST_SKILLS_HOME when set, else HOME) for the
5
5
  // CLI path that login recorded and falls back to `npx @catalyst-cloud/catalyst-skills`.
6
6
  //
@@ -44,7 +44,7 @@ export function loadCustomerConfig() {
44
44
  export function requireConfigured() {
45
45
  const cfg = loadCustomerConfig();
46
46
  if (cfg) return cfg;
47
- console.error(`not connected: ${configPath()} is missing or unreadable — run: CATALYST_CLOUD_TOKEN=<account key> npx ${PACKAGE_NAME} login`);
47
+ console.error(`not connected: ${configPath()} is missing or unreadable — run: CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE_NAME} login`);
48
48
  process.exit(NOT_CONFIGURED_EXIT);
49
49
  }
50
50
 
@@ -38,5 +38,5 @@ Exit codes: 0 answered, 1 not found or a usage error, 2 this machine is not conn
38
38
  - **A reason is a layer.** Translate an exclusion reason through `references/what-runs-next.md`; name what releases it and who can do that (a clock, a comment, a push, an operator).
39
39
  - **Unknown is not absent.** An `unknown` verdict from the explainer means the cloud could not look; report it as inconclusive.
40
40
  - **System causes are one alert.** A provider outage, the runner image breaker or a paused repository holds many tickets for one reason; never escalate it ticket by ticket.
41
- - **Say what a key cannot see.** Coding-account status and per-ticket execution history are not readable with an account key yet; the scripts say so and name the settings page. Do not fill the gap with a guess.
41
+ - **Say what a key cannot see.** A few PR facts are not mirrored (labels, the reviewer's reaction) and a park is released only by an operator; the scripts say so and name the settings page. Do not fill the gap with a guess.
42
42
  - **Depth lives elsewhere.** What a ticket accumulates in Linear is `catalyst-linear`; what a PR accumulates and whether it is mergeable is `catalyst-github`; raising a decision is `what-needs-me`.
@@ -1,6 +1,6 @@
1
1
  # Coding accounts: slots, windows, walls, and what a key cannot read yet
2
2
 
3
- This reference restates the model, which does not vary per tenant. Your tenant's actual accounts, their usage and their state are NOT readable with an account key today: `node scripts/whats-running.mjs --accounts` prints that by name, with the settings page where the facts live. When a tenant-facing read ships, the same script will print the live rows and this reference stays true.
3
+ This reference restates the model, which does not vary per tenant. Your tenant's actual accounts — provider, harness, declared and observed state, usage windows, walls, quarantine — are readable with your key: `node scripts/whats-running.mjs --accounts` prints the live rows (`catalyst-skills accounts`). No credential or email ever rides in that read; the settings page (`/settings/coding-accounts`) is where an admin enrols, pauses or removes one.
4
4
 
5
5
  ## What a slot is
6
6
 
@@ -45,7 +45,7 @@ Your tenant's coding-accounts page lists each slot with a five-value status, fir
45
45
  Coding-account status (provider, declared and observed state, window percentages and resets, walls, quarantine, live holds) is served only behind the tenant's admin gate. So when a human asks "why is nothing running?":
46
46
 
47
47
  1. Run `node scripts/explain-ticket.mjs <ticket>` for a stuck ticket. `routing_unavailable` with a detail naming a slot or provider, or `no_eligible_account_slot` in the routing block, points at accounts.
48
- 2. Say plainly that the account key cannot read account state, and give the settings link the script prints. Do not guess a wall or a quarantine from silence.
48
+ 2. Read the live rows with `catalyst-skills accounts` and say what they show; an empty list means no account is enrolled, and the script prints the settings link. Do not guess a wall or a quarantine from silence — a wall is the `walled` field, a quarantine is `quarantined` with its reason.
49
49
  3. Treat it as ONE fleet-level cause for every ticket it holds, never as a per-ticket escalation.
50
50
 
51
51
  The customer session never holds or mints a coding-account credential; phases run in the cloud on the enrolled accounts, and repository access is a per-phase installation token.
@@ -38,7 +38,7 @@ Which parks release themselves:
38
38
  | missing branch | its own budgeted release loop |
39
39
  | rebase conflict | its own budgeted release loop |
40
40
 
41
- An account key cannot release a park today; the repair verbs are operator-only. When a ticket is parked, say so, name the failure class the explainer shows, and hand the release to whoever administers the tenant.
41
+ Your key cannot release a park; the repair verbs are operator-only. When a ticket is parked, say so, name the failure class the explainer shows, and hand the release to whoever administers the tenant.
42
42
 
43
43
  ## Two holds that are not failures
44
44
 
@@ -47,7 +47,7 @@ An account key cannot release a park today; the repair verbs are operator-only.
47
47
 
48
48
  ## What a human sees on the ticket
49
49
 
50
- Each attempt posts a phase-outcome comment (complete or FAILED, with phase, attempt, artifact, a summary and any park or hold block) and each remediation round posts a remediate-attempt comment naming the failure class it is repairing. `catalyst-linear` describes the shapes. The round count of the cap and the park history are not readable with an account key yet; `catalyst-skills explain --history <ticket>` says so and names where to read them.
50
+ Each attempt posts a phase-outcome comment (complete or FAILED, with phase, attempt, artifact, a summary and any park or hold block) and each remediation round posts a remediate-attempt comment naming the failure class it is repairing. `catalyst-linear` describes the shapes. The attempt ledger, the round count against the cap and the park history are readable: `catalyst-skills explain --history <ticket>` prints them from the cloud's own relay ledger.
51
51
 
52
52
  ## Rule of thumb for answering "why is it stuck?"
53
53
 
@@ -8,7 +8,7 @@ import { join } from "node:path";
8
8
  import { pathToFileURL } from "node:url";
9
9
 
10
10
  export const PACKAGE = "@catalyst-cloud/catalyst-skills";
11
- export const CONNECT_HINT = `CATALYST_CLOUD_TOKEN=<account key> npx ${PACKAGE} login`;
11
+ export const CONNECT_HINT = `CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE} login`;
12
12
 
13
13
  /** ~/.config/catalyst-cloud/customer.json, honouring CATALYST_SKILLS_HOME (used by tests) over HOME. */
14
14
  export function configPath() {
@@ -9,8 +9,7 @@ Prints what is executing on your tenant. Wraps: catalyst-skills running, queue,
9
9
 
10
10
  --queue also print the dispatch queue (what runs next, in order)
11
11
  --team <key> with --queue: one team's queue
12
- --accounts also print the coding-account line (today: where to read it, since an account key
13
- cannot see coding-account status yet)
12
+ --accounts also print the coding-account line (provider, state, windows — never a credential)
14
13
  --json one JSON document: { running, queue?, accounts? }
15
14
  --help this text
16
15
 
@@ -42,4 +42,4 @@ Every script exits 2 when this machine is not connected (run `catalyst-skills lo
42
42
  - **Escalate inward.** Instrument, then you, then the desk, then the human as an ask filed through `what-needs-me` with what it blocks. Decide the technical calls yourself; take the sane default and record it; a fleet or provider condition is one note, never one ask per ticket.
43
43
  - **Reply where the message arrived,** in-thread, tagged, as the app actor. Never post as the human, never answer someone else's ask. Bookkeeping records take the contract's marker (`catalyst-skills write comment --bookkeeping`).
44
44
  - **One status summary,** kept current after every reaction: in flight, blocked and on whom, closed, next, each line with a ticket id. Where a human decision is pending, the line carries the ask's id.
45
- - **Say what a key cannot see.** Execution history and coding-account status are not visible to an account key yet; the CLI says so by name and points at settings. Do not guess in their place.
45
+ - **Say what a key cannot see.** A park is released only by an operator, and PR labels and reactions are not mirrored; the CLI says so by name and points at settings. Execution history (`explain --history`) and coding-account status (`accounts`) ARE readable — read them rather than reconstructing them from comments.
@@ -73,4 +73,4 @@ Start with `--from head` when you are picking up a project that has been running
73
73
 
74
74
  ## The honest limit
75
75
 
76
- The stream carries the mirror's entity changes. It does not carry the relay ledger's phase failures, parks, remediate rounds or backoff timers as first-class frames. Until a tenant-facing execution-history route ships, you learn those from the outcome comments the cloud posts on the ticket, which do arrive as `comments` frames: a phase-failed card names the phase, the attempt and the failure class; a remediate-attempt card names the round; a board-health comment names a stall the cloud itself noticed. For the current verdict on any ticket, ask the explainer: `catalyst-skills explain <ticket>` turns the exclusion reason and the last failure into one paragraph. `catalyst-skills explain --history <ticket>` says, by name, that the history is not visible to an account key yet and where in settings a human can read it.
76
+ The stream carries the mirror's entity changes. It does not carry the relay ledger's phase failures, parks, remediate rounds or backoff timers as first-class frames. Between frames you learn those from the outcome comments the cloud posts on the ticket, which do arrive as `comments` frames: a phase-failed card names the phase, the attempt and the failure class; a remediate-attempt card names the round; a board-health comment names a stall the cloud itself noticed. For the current verdict on any ticket, ask the explainer: `catalyst-skills explain <ticket>` turns the exclusion reason and the last failure into one paragraph. `catalyst-skills explain --history <ticket>` prints the per-phase attempt ledger, the remediation rounds against the cap, and any park with what releases it.
@@ -40,7 +40,7 @@ Where the explainer names a reason not in this table, it prints the raw reason;
40
40
 
41
41
  ## Capacity and fleet conditions are one note, not many asks
42
42
 
43
- When several tickets in scope are offered and nothing picks them up, or the explainer names a routing, slot, provider or image condition, the cause is shared: coding-account headroom, a provider outage, a paused repository, a poisoned runner image. Write one line in the status summary naming the condition and the tickets it holds. Do not file an ask per ticket. Coding-account status is not visible to an account key yet; `catalyst-skills accounts` says so and names the settings page where a human can read it, and that page, not you, is where the answer lives.
43
+ When several tickets in scope are offered and nothing picks them up, or the explainer names a routing, slot, provider or image condition, the cause is shared: coding-account headroom, a provider outage, a paused repository, a poisoned runner image. Write one line in the status summary naming the condition and the tickets it holds. Do not file an ask per ticket. `catalyst-skills accounts` reads coding-account status (state, usage windows, walls, quarantine); enrolling, pausing or removing an account is the settings page, not you.
44
44
 
45
45
  ## Escalate inward, never outward
46
46
 
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // lib/cli.mjs — the one way a skill script reaches Catalyst Cloud: by spawning the catalyst-skills
3
- // CLI. The CLI holds the SDK and the account key; this file holds neither. It reads
3
+ // CLI. The CLI holds the SDK and the key; this file holds neither. It reads
4
4
  // ~/.config/catalyst-cloud/customer.json (under CATALYST_SKILLS_HOME when set, else HOME) for the
5
5
  // CLI path that login recorded and falls back to `npx @catalyst-cloud/catalyst-skills`.
6
6
  //
@@ -44,7 +44,7 @@ export function loadCustomerConfig() {
44
44
  export function requireConfigured() {
45
45
  const cfg = loadCustomerConfig();
46
46
  if (cfg) return cfg;
47
- console.error(`not connected: ${configPath()} is missing or unreadable — run: CATALYST_CLOUD_TOKEN=<account key> npx ${PACKAGE_NAME} login`);
47
+ console.error(`not connected: ${configPath()} is missing or unreadable — run: CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE_NAME} login`);
48
48
  process.exit(NOT_CONFIGURED_EXIT);
49
49
  }
50
50
 
@@ -15,7 +15,7 @@ An ask is one decision only the human can make, filed as a ticket in their own L
15
15
 
16
16
  Scripts are run, never read. Each prints `--help`; exit 2 means this machine is not connected (run the `connect-me` skill), exit 1 means the check itself failed or an argument was missing.
17
17
 
18
- - `node scripts/inbox.mjs --help` — the open asks, ranked by the priority-weighted work each holds; `--json` for the rows.
18
+ - `node scripts/inbox.mjs --help` — the open asks assigned to the connected person, ranked by the priority-weighted work each holds; `--anyone` for the whole tenant; `--json` for `{scope, asks}`.
19
19
  - `node scripts/raise.mjs --help` — file one decision: `--team`, `--title`, `--option` (repeated), `--default`, and `--blocks` (repeated) or `--nothing-to-block`.
20
20
  - `node scripts/settle.mjs --help` — record the answering comment on an ask and post a release note on every ticket it held; `--close` moves the ask to the done slot.
21
21
 
@@ -17,7 +17,7 @@ So an ask that holds nothing is real, but the person may never see it in their o
17
17
 
18
18
  ## What the script reads
19
19
 
20
- `catalyst-skills ask list` reads the open tickets carrying the team's ask label (by the label ids and prefix the contract serves), follows each one's blocking relations to the tickets it holds, drops held tickets that are already terminal, and scores the rest by priority weight: urgent 4, high 3, medium 2, low 1, none 1. It does not filter by assignee, because the account key reads the tenant, not one person. When the human asks "what needs me", the answer is the ranked list; when they ask "what needs anyone", it is the same list.
20
+ `catalyst-skills ask list` reads the open tickets carrying the team's ask label (by the label ids and prefix the contract serves), follows each one's blocking relations to the tickets it holds, drops held tickets that are already terminal, and scores the rest by priority weight: urgent 4, high 3, medium 2, low 1, none 1. It filters to **the connected person** by default: `login` recorded who they are and their Linear user id, and the list keeps only asks assigned to that id. `--anyone` lists the whole tenant. The JSON answer carries a `scope` — `mine` (with the label and Linear id), `anyone`, `unmatched` (the person's Linear identity is not matched yet: an admin does that in Settings → Members, and until then the whole list is shown with a stderr line saying so), or `no-person` (the machine is connected with the tenant's account key, which names nobody: the whole list, with a line saying to log in with a personal key). Read the scope before presenting the list, and say which you got: "what needs me" is the `mine` list, "what needs anyone" is `--anyone`, and an empty `mine` list names the wider count so it never reads as "nothing needs anyone".
21
21
 
22
22
  ## Presenting the queue
23
23
 
@@ -2,18 +2,20 @@
2
2
  // inbox.mjs — "what needs me?": the open asks on this tenant, ranked by what each answer releases.
3
3
  // A thin wrapper over `catalyst-skills ask list`, which reads the open tickets carrying the team's
4
4
  // ask label (from the contract), follows each one's blocking relations, and weights held work by
5
- // priority. Prints the ranked list; --json prints the CLI's rows unchanged.
5
+ // priority. By default the CLI keeps only the asks assigned to the connected person; --anyone widens
6
+ // to the whole tenant. Prints the ranked list; --json prints the CLI's {scope, asks} unchanged.
6
7
  import { mustRun, parseFlags, parseJson, printHelp } from "./lib/cli.mjs";
7
8
 
8
9
  const SPEC = {
9
- json: { value: false, help: "print the ranked rows as JSON: {identifier, title, state, blocks[], score}" },
10
+ json: { value: false, help: "print the CLI's answer as JSON: {scope, asks: [{identifier, title, state, blocks[], score, assigneeId}]}" },
10
11
  team: { value: true, help: "only asks whose identifier carries this team key" },
11
12
  all: { value: false, help: "include asks that hold nothing (by default they are listed last, marked)" },
13
+ anyone: { value: false, help: "every open ask in the tenant, not only the ones assigned to you" },
12
14
  };
13
15
 
14
16
  const { help, flags } = parseFlags(process.argv.slice(2), SPEC);
15
17
  if (help) {
16
- printHelp("node scripts/inbox.mjs [--json] [--team K] [--all] [--help]", SPEC, [
18
+ printHelp("node scripts/inbox.mjs [--json] [--team K] [--all] [--anyone] [--help]", SPEC, [
17
19
  "Rank: the sum over held open tickets of a priority weight (urgent 4 ... low 1, none 1), highest first.",
18
20
  "An ask that holds nothing is real but will not appear in the tenant's Waiting-on-me view; it is",
19
21
  "listed last with a marker so you can attach the work it should block (references/reading-the-inbox.md).",
@@ -21,14 +23,21 @@ if (help) {
21
23
  process.exit(0);
22
24
  }
23
25
 
24
- const rows = parseJson(mustRun(["ask", "list", "--json"], { quiet: true }).stdout, "ask list");
26
+ const answer = parseJson(mustRun(["ask", "list", "--json", ...(flags.anyone ? ["--anyone"] : [])], { quiet: true }).stdout, "ask list");
27
+ const rows = answer.asks;
28
+ const scope = answer.scope;
25
29
  const wanted = flags.team ? rows.filter((r) => String(r.identifier).toUpperCase().startsWith(`${flags.team.toUpperCase()}-`)) : rows;
26
30
  if (flags.json) {
27
- process.stdout.write(JSON.stringify(wanted) + "\n");
31
+ process.stdout.write(JSON.stringify({ scope, asks: wanted }) + "\n");
28
32
  process.exit(0);
29
33
  }
34
+ // Say whose list this is before the list, so an empty "mine" never reads as "nothing needs anyone".
35
+ if (scope.kind === "mine") process.stdout.write(`asks assigned to ${scope.label}:\n`);
36
+ else if (scope.kind === "anyone") process.stdout.write("every open ask in the tenant:\n");
37
+ else if (scope.kind === "unmatched") process.stdout.write(`every open ask (your Linear identity is not matched yet — an admin matches it in Settings → Members):\n`);
38
+ else process.stdout.write("every open ask (connected with the tenant's account key, which names no person — log in with your personal key to see only yours):\n");
30
39
  if (wanted.length === 0) {
31
- process.stdout.write("nothing needs you: no open asks\n");
40
+ process.stdout.write(scope.kind === "mine" ? "nothing needs you: no open asks assigned to you (--anyone lists the tenant's)\n" : "nothing needs anyone: no open asks\n");
32
41
  process.exit(0);
33
42
  }
34
43
  const holding = wanted.filter((r) => r.blocks.length > 0);
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
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 account key; this file holds neither. It reads customer.json only
3
+ // The CLI holds the SDK and the key; this file holds neither. It reads customer.json only
4
4
  // to learn where the CLI lives, and it exits 2 with one line when the machine is not connected.
5
5
  // Run any script beside this one with --help; this file is a library and is never run directly.
6
6
  import { spawnSync } from "node:child_process";
@@ -30,7 +30,7 @@ export function loadConfig() {
30
30
  }
31
31
 
32
32
  function notConfigured(why) {
33
- process.stderr.write(`not connected (${why}) — run: CATALYST_CLOUD_TOKEN=<account key> npx ${PACKAGE} login\n`);
33
+ process.stderr.write(`not connected (${why}) — run: CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE} login\n`);
34
34
  process.exit(NOT_CONFIGURED_EXIT);
35
35
  }
36
36
 
@@ -33,10 +33,10 @@ For depth beyond these, load the fact skills: `how-catalyst-works` (the ladder,
33
33
 
34
34
  ## Rules
35
35
 
36
- - **Their tenant only.** Every read goes through the CLI, which holds the account key and the tenant. You never name, guess at, or try another tenant, and you never paste the key anywhere.
36
+ - **Their tenant, as them.** Every read goes through the CLI, which holds the person's own key, the tenant and who they are. You never name, guess at, or try another tenant, and you never paste the key anywhere. "You" in your reply means the connected person: their assigned tickets, their asks.
37
37
  - **Tenant facts come from the contract, live.** Stage names, label names, team keys, thresholds and the ladder are in the snapshot's `tenant` block; read them there each time and never restate them from memory.
38
38
  - **One reply, ticket ids on every line, source named.** The reply opens with when the snapshot was taken and whether the replica or the API answered. A stale replica is stated, never hidden.
39
- - **Say what a key cannot see.** Coding-account status and per-ticket execution history are not visible to an account key yet; the CLI prints the settings URL, and you repeat it instead of guessing.
39
+ - **Say what a key cannot see.** PR labels and reactions are not mirrored, and a park is released only by an operator; the CLI prints the settings URL, and you repeat it instead of guessing. Coding-account status (`accounts`) and per-ticket execution history (`explain --history`) are readable — read them.
40
40
  - **No polling.** One snapshot per question. Waiting on a change is the project owner's job (`run-this-project` subscribes to the tenant stream); the desk never loops a read.
41
41
  - **You are not the owner.** You route work and make it visible; you do not dispatch, overrule a project owner, or run long work in this session.
42
42
  - **Never answer as the human.** A decision is an ask through `what-needs-me`, filed before anyone proceeds on its default; the answer is recorded there as the app actor.
@@ -57,7 +57,7 @@
57
57
  },
58
58
  "cannotSee": {
59
59
  "type": "array",
60
- "description": "Facts the account key cannot read yet, named, with where the human can read them (the CLI prints the URL).",
60
+ "description": "Facts your key cannot read (PR labels and reactions; an operator-only park release), named, with where the human can read them (the CLI prints the URL).",
61
61
  "items": { "type": "string" }
62
62
  }
63
63
  },
@@ -35,7 +35,7 @@ Everything else is **waiting**, and the reply says what it is waiting on. Callin
35
35
 
36
36
  ## The signals the cloud posts on the ticket itself
37
37
 
38
- Before declaring anything, read the ticket's comments (`catalyst-skills query issue <id>`; the `catalyst-linear` skill explains each shape). The cloud posts a phase-outcome card on every completion and failure, a remediate-attempt card per repair round, a board-health note when it detects a stall itself, and a merge-wait note when a merge is held. Those comments are the execution history a key can read today; the per-ticket ledger behind them is not visible to an account key yet, and `explain --history` says so.
38
+ Before declaring anything, read the ticket's comments (`catalyst-skills query issue <id>`; the `catalyst-linear` skill explains each shape). The cloud posts a phase-outcome card on every completion and failure, a remediate-attempt card per repair round, a board-health note when it detects a stall itself, and a merge-wait note when a merge is held. Those comments are the narrative; the per-ticket ledger behind them — attempts, rounds against the cap, park state — is `catalyst-skills explain --history <ticket>`, and the two must agree.
39
39
 
40
40
  ## Two things that look like work and are not
41
41
 
@@ -14,7 +14,7 @@ The person asked one question and gets one reply. If they would need a second su
14
14
  4. **Closed.** What reached the done slot in the window the person asked about (or since your last reply). Source: `query issues` filtered by the team's done-slot stage name from the contract, or the change feed for a time window. When you did not read a window, say "since my last reply" and mean it.
15
15
  5. **Next.** What the queue picks up next, in the cloud's order, with the phase each will run. Source: the snapshot's `queue` block. Do not reorder it to what you think should be next; the levers are in `references/reprioritising.md`.
16
16
 
17
- A sixth block, **cannot see**, appears only when it is non-empty: the facts an account key cannot read yet (coding-account status, per-ticket execution history), each with the URL the CLI printed.
17
+ A sixth block, **cannot see**, appears only when it is non-empty: the facts your key cannot read (PR labels and reactions, which are not mirrored; an operator-only park release), each with the URL the CLI printed.
18
18
 
19
19
  ## The header line
20
20
 
@@ -38,7 +38,7 @@ This reference restates an invariant: the vocabulary the cloud's eligibility exp
38
38
 
39
39
  | reason | what it means | note |
40
40
  | -- | -- | -- |
41
- | `cooling_down` (parked after repeated failures, or the round cap was spent) | the phase is parked and does not release itself | an operator unparks it; an account key has no unpark verb yet, so the reply names the park and the settings page |
41
+ | `cooling_down` (parked after repeated failures, or the round cap was spent) | the phase is parked and does not release itself | an operator unparks it; your key has no unpark verb, so the reply names the park (from `explain --history`) and the settings page |
42
42
  | `remediate_parked` | the repair phase itself is parked, so the failing phase has nowhere to be repaired | as above |
43
43
  | `validate_class_spent` | this validate failure already spent its one repair round in this episode | as above; a human comment may release the hold |
44
44
  | `no_branch_to_remediate` / `branch_missing` / `branch_gone` | a branch-dependent phase has no branch to clone | the cloud releases missing-branch parks on its own budget; a deleted branch needs a human to decide |
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
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 account key; this file holds neither. It reads customer.json only
3
+ // The CLI holds the SDK and the key; this file holds neither. It reads customer.json only
4
4
  // to learn where the CLI lives, and it exits 2 with one line when the machine is not connected.
5
5
  // Run any script beside this one with --help; this file is a library and is never run directly.
6
6
  import { spawnSync } from "node:child_process";
@@ -30,7 +30,7 @@ export function loadConfig() {
30
30
  }
31
31
 
32
32
  function notConfigured(why) {
33
- process.stderr.write(`not connected (${why}) — run: CATALYST_CLOUD_TOKEN=<account key> npx ${PACKAGE} login\n`);
33
+ process.stderr.write(`not connected (${why}) — run: CATALYST_CLOUD_TOKEN=<your personal key> npx ${PACKAGE} login\n`);
34
34
  process.exit(NOT_CONFIGURED_EXIT);
35
35
  }
36
36