@catalyst-cloud/cli 0.15.6 → 0.16.1

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 (54) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +9 -6
  3. package/dist/args.js +26 -1
  4. package/dist/ask.js +56 -0
  5. package/dist/capabilities.js +3 -1
  6. package/dist/cli.js +10 -5
  7. package/dist/environment.js +1 -1
  8. package/dist/execution.js +2 -2
  9. package/dist/hosts.js +89 -0
  10. package/dist/legacy.js +12 -2
  11. package/dist/machine-inventory.js +37 -0
  12. package/dist/onboard-capacity.js +5 -6
  13. package/dist/onboard-github-copy.js +2 -0
  14. package/dist/onboard-github.js +2 -0
  15. package/dist/onboard-headless.js +2 -2
  16. package/dist/onboard-next.js +21 -12
  17. package/dist/onboard-project-cleanup.js +100 -0
  18. package/dist/onboard-returning.js +172 -0
  19. package/dist/onboard-runner.js +149 -34
  20. package/dist/onboard-runtime.js +26 -1
  21. package/dist/onboard-ui.js +67 -1
  22. package/dist/onboard-workflow.js +32 -7
  23. package/dist/onboard.js +9 -2
  24. package/dist/ready-copy.js +30 -24
  25. package/dist/ready.js +2 -2
  26. package/dist/secret.js +2 -2
  27. package/dist/setup-onboard-copy.js +37 -32
  28. package/dist/setup.js +4 -0
  29. package/dist/transport.js +18 -2
  30. package/package.json +8 -5
  31. package/skills/catalyst-github/SKILL.md +1 -1
  32. package/skills/catalyst-linear/SKILL.md +1 -1
  33. package/skills/catalyst-onboard/SKILL.md +3 -3
  34. package/skills/catalyst-onboard/references/connecting-this-machine.md +4 -4
  35. package/skills/catalyst-onboard/references/declaring-a-repository.md +1 -1
  36. package/skills/catalyst-onboard/references/reading-ready.md +14 -14
  37. package/skills/catalyst-onboard/references/replacing-a-credential.md +1 -1
  38. package/skills/catalyst-onboard/references/the-one-path.md +5 -5
  39. package/skills/catalyst-onboard/references/what-a-phase-needs.md +4 -4
  40. package/skills/catalyst-onboard/references/what-the-browser-owns.md +20 -10
  41. package/skills/catalyst-onboard/scripts/where-am-i.mjs +50 -27
  42. package/skills/run-this-project/SKILL.md +1 -1
  43. package/skills/run-this-project/references/stalls-and-escalation.md +1 -1
  44. package/skills/unstick/SKILL.md +1 -1
  45. package/skills/what-needs-me/SKILL.md +2 -2
  46. package/skills/what-needs-me/references/raising-a-decision.md +12 -1
  47. package/skills/what-needs-me/references/reading-the-inbox.md +1 -1
  48. package/skills/what-needs-me/scripts/raise.mjs +10 -1
  49. package/skills/what-this-repo-needs/SKILL.md +1 -1
  50. package/skills/whats-happening/SKILL.md +3 -2
  51. package/skills/whats-happening/references/coding-accounts.md +1 -1
  52. package/skills/whats-happening/references/machines.md +9 -0
  53. package/skills/whats-happening/references/settings-and-where-they-live.md +8 -8
  54. package/skills/whats-happening/references/stages-and-mapping.md +1 -1
@@ -8,9 +8,9 @@ One coding account is enough to start. A second gives the router somewhere to go
8
8
 
9
9
  ### The kinds, and enrolling
10
10
 
11
- Settings → AI accounts lists the providers this workspace can connect and what each one asks for; an API key is billed per token by its provider. Do not promise a provider. Ask the person to open Settings → AI accounts and choose from the providers it lists; if the one they name is not there, say so and ask whether they have a key from one that is.
11
+ Settings → AI accounts lists the providers this workspace can connect and what each one asks for; an API key is billed per token by its provider. Do not promise a provider. Ask which provider the person has a key for. No command lists the providers yet, so check the name against that page; if it is not there, say so and ask whether they have a key from one that is.
12
12
 
13
- Enrolling is a browser step by construction: on Settings → AI accounts, an owner or admin picks the provider, labels the account, and pastes the key into a write-only field. If the page says Catalyst takes over a login, say so before the paste. Call an account by its label, else its email, else its slot (`displayName` in `catalyst accounts --json`).
13
+ Adding an account has no command yet, so it is a browser step for now: on Settings → AI accounts, an owner or admin picks the provider, labels the account, and pastes the key into a write-only field. If the page says Catalyst takes over a login, say so before the paste. Call an account by its label, else its email, else its slot (`displayName` in `catalyst accounts --json`).
14
14
 
15
15
  ### The states
16
16
 
@@ -19,8 +19,8 @@ Enrolling is a browser step by construction: on Settings → AI accounts, an own
19
19
  | state | what it says | what to do |
20
20
  | -- | -- | -- |
21
21
  | `enrolled` | one is active; another can still have a dead credential | "<provider> account <slot> needs a new credential" is the next step: `references/replacing-a-credential.md`. `unreadable` detail: read again later |
22
- | `none_enrolled` | none enrolled | hand over the page to the enroller the contract names |
23
- | `inactive` | every account is out of rotation | reactivate one on its page, never enrol another |
22
+ | `none_enrolled` | none enrolled | hand over the page to the enroller the contract names; there is no command to add one yet |
23
+ | `inactive` | every account is out of rotation | reactivate one on its page (no command does this yet), never enrol another |
24
24
  | `unread` | the cloud could not read them | it is not "no accounts"; read again later |
25
25
 
26
26
  **A cancelled or ended account** stays for reporting, unused, and does not hold setup up; say so in one clause. It keeps its own login: a token from another login would put a live account onto a dead slot. It stays enrolled rather than retired or deleted, and is reactivated on its page only if it can run again.
@@ -1,27 +1,37 @@
1
1
  # What the browser owns
2
2
 
3
- ## Browser by construction
3
+ Headless first: when the catalyst CLI, the SDK or an agent route can do a step, ask "Want me to do that for you?" and run it on a yes; name a web page only for a step that needs a person in a browser by nature (signing in, an OAuth or app-install consent), and then hand over the exact link the CLI printed. A step with no command yet is a gap: say so in one clause and hand over the exact page.
4
4
 
5
- Each is an authorization a person grants in their own session, so no command will ever do it. Say **by construction**; a person told "not supported yet" waits for a feature that will never come.
5
+ ## Commands you offer to run
6
6
 
7
- | step | where |
7
+ | step | command |
8
+ | -- | -- |
9
+ | registering a repository and attaching it to a project | `catalyst onboard --team <KEY> --repo <owner/name>` (an owner or admin) |
10
+ | checking, mapping or adopting a project's stages | `catalyst team check <KEY>`, `catalyst team map <KEY>`, `catalyst team adopt <KEY>` |
11
+ | entering a repository's values | `catalyst var set NAME --repo <owner/name>`, `catalyst secret set NAME --repo <owner/name>` |
12
+ | creating `<your GitHub org>/thoughts` | `gh repo create <org>/thoughts --private --add-readme` |
13
+
14
+ ## A browser by nature
15
+
16
+ Each is a sign-in or a consent a person grants in their own session. Start it with the command, then hand over only the link it printed.
17
+
18
+ | step | how it starts |
8
19
  | -- | -- |
9
20
  | approving the login | the URL and short code `catalyst login` printed |
10
- | enrolling a coding account, or replacing its credential | the AI accounts page the script printed |
11
- | connecting the Linear integration, installing the GitHub App | `<their cloud>/settings/connections` (Settings → Integrations) |
12
- | creating `<your GitHub org>/thoughts` and letting the App reach it | GitHub, then the App's installation page for that org: All repositories, or add `thoughts` (step 5 of `references/the-one-path.md`) |
21
+ | connecting the Linear integration, installing the GitHub App | `catalyst onboard` prints the consent link; without one, `<their cloud>/settings/connections` (Settings → Integrations) |
22
+ | letting the App reach `<your GitHub org>/thoughts` | the App's installation page for that org: All repositories, or add `thoughts` (step 5 of `references/the-one-path.md`) |
13
23
  | connecting personal Linear, then personal GitHub | the URL `catalyst connections personal <linear\|github> start` printed |
14
24
 
15
25
  ## Not a command yet
16
26
 
17
- Their routes take a browser session, not a key. Call them gaps, route the person through the browser, and never guess at a route.
27
+ These have no command in this CLI yet. Say so in one clause, hand over the page, and never guess at a route.
18
28
 
19
29
  | step | where |
20
30
  | -- | -- |
21
- | registering a repository and attaching it to a project | `<their cloud>/settings/projects` (Settings → Your projects) |
22
- | approving one repository's declaration, and entering its values | that repository's Environment page (`references/declaring-a-repository.md`) |
31
+ | adding a coding account, or replacing its credential | the AI accounts page the script printed |
32
+ | approving one repository's declaration | that repository's Environment page (`references/declaring-a-repository.md`) |
23
33
 
24
- `catalyst environment` handles a workspace-wide declaration. ⛔ Only the CLI makes requests; if you find yourself building a URL for one of these, stop.
34
+ `catalyst environment` handles a workspace-wide declaration. `catalyst capabilities` lists what this CLI can do; a verb it lists replaces the page for that step. ⛔ Only the CLI makes requests; if you find yourself building a URL for one of these, stop.
25
35
 
26
36
  ## The link to hand over
27
37
 
@@ -85,6 +85,10 @@ const verbAdminOnly = (verb) => {
85
85
  const c = capability(verb);
86
86
  return c?.availability === "available" && c.needs === "admin" && !canManage();
87
87
  };
88
+ // `catalyst onboard` starts the workspace consents and registers a repository; an older CLI has no
89
+ // such verb, and then the step stays the page.
90
+ const onboardAvailable = () => capability("onboard")?.availability === "available";
91
+ const UPDATE_CLI = "npm install -g @catalyst-cloud/cli@latest";
88
92
  // `blocking` is false for a finding that is real and reportable but does not stop the next step —
89
93
  // an unmatched Linear identity is the one that matters: it must be said, and it must not become the
90
94
  // thing the person is told to go and do before they can map a project.
@@ -335,12 +339,12 @@ if (!connected) {
335
339
  ? "you"
336
340
  : matched
337
341
  ? null
338
- : "a workspace owner or admin",
342
+ : "you",
339
343
  personalGrantIncomplete
340
344
  ? "catalyst connections personal <provider> start or status"
341
345
  : matched
342
346
  ? null
343
- : link("/settings/account"),
347
+ : "catalyst identity linear options",
344
348
  false,
345
349
  );
346
350
  }
@@ -424,7 +428,7 @@ if (!connected) {
424
428
  ...envLines,
425
429
  ],
426
430
  "a workspace owner or admin",
427
- link("/settings/connections"),
431
+ onboardAvailable() ? "catalyst onboard" : link("/settings/connections"),
428
432
  );
429
433
  }
430
434
  }
@@ -538,7 +542,7 @@ if (!connected) {
538
542
  );
539
543
  blockedProjects.push({
540
544
  key,
541
- action: `fix ${key}'s blocking check${ids.length === 1 ? "" : "s"} (${ids.join(", ") || "see the projects lines"}): ${how || owner}; then run catalyst team check ${key} (or press Re-check) and run this again`,
545
+ action: `fix ${key}'s blocking check${ids.length === 1 ? "" : "s"} (${ids.join(", ") || "see the projects lines"}): ${how || owner}; then run catalyst team check ${key} and run this again`,
542
546
  owner,
543
547
  });
544
548
  }
@@ -565,7 +569,11 @@ if (!connected) {
565
569
  : "a workspace owner or admin";
566
570
  const mapWhere = verbAvailable("team map")
567
571
  ? "catalyst team list"
568
- : link("/settings/linear-teams");
572
+ : verbAdminOnly("team map")
573
+ ? "catalyst team map <KEY>"
574
+ : capability("team map")
575
+ ? link("/settings/projects")
576
+ : UPDATE_CLI;
569
577
  const projectOwner =
570
578
  blockedProjects.length > 0 ? blockedProjects[0].owner : mapOwner;
571
579
  const projectWhere = blockedProjects.length > 0 ? null : mapWhere;
@@ -653,7 +661,9 @@ if (!connected) {
653
661
  rows.length > 0 ? "ok" : "unfinished",
654
662
  lines,
655
663
  "a workspace owner or admin",
656
- link("/settings/projects"),
664
+ onboardAvailable()
665
+ ? "catalyst onboard --team <KEY> --repo <owner/name>"
666
+ : link("/settings/projects"),
657
667
  );
658
668
  }
659
669
  }
@@ -908,8 +918,9 @@ if (!connected) {
908
918
  }
909
919
  }
910
920
 
911
- // the Re-check step, as a command when this person can run it, else as the page with the
912
- // person who can. `team check` is the CLI twin of the page's Re-check button (admin or owner).
921
+ // the re-check step, always `catalyst team check`: run it when this person can, name the owner or
922
+ // admin who can otherwise. A CLI without the verb is told to update; only a cloud that does not
923
+ // serve the route yet falls back to the projects page.
913
924
  const mappedKeys = () =>
914
925
  (teamRows ?? [])
915
926
  .map((t) => t.key ?? t.id)
@@ -925,14 +936,21 @@ const recheckStep = () => {
925
936
  };
926
937
  if (cmds.length > 0 && verbAdminOnly("team check"))
927
938
  return {
928
- action: `a workspace owner or admin runs ${cmds.join(" and ")} (or presses Re-check on the projects page); then run this again`,
939
+ action: `a workspace owner or admin runs ${cmds.join(" and ")}; then run this again`,
929
940
  owner: "a workspace owner or admin",
930
- where: link("/settings/linear-teams"),
941
+ where: cmds[0],
942
+ };
943
+ if (capability("team check"))
944
+ return {
945
+ action:
946
+ "this cloud does not serve catalyst team check yet, so a workspace owner or admin re-checks the project on the projects page; then run this again",
947
+ owner: "a workspace owner or admin",
948
+ where: link("/settings/projects"),
931
949
  };
932
950
  return {
933
- action: "press Re-check on the projects page, then run this again",
951
+ action: `update the CLI (${UPDATE_CLI}), then a workspace owner or admin runs catalyst team check ${cmds.length > 0 ? mappedKeys()[0] : "<KEY>"}; then run this again`,
934
952
  owner: "a workspace owner or admin",
935
- where: link("/settings/linear-teams"),
953
+ where: UPDATE_CLI,
936
954
  };
937
955
  };
938
956
 
@@ -989,7 +1007,7 @@ if (!connected) {
989
1007
  : "no project has a readiness check yet, so whether a host is connected cannot be read.",
990
1008
  ],
991
1009
  teamRows === null ? "a workspace owner or admin" : step.owner,
992
- teamRows === null ? link("/settings/linear-teams") : step.where,
1010
+ teamRows === null ? "catalyst contract --refresh" : step.where,
993
1011
  ).next =
994
1012
  teamRows === null
995
1013
  ? "refresh the contract (catalyst contract --refresh), then run this again; the project list could not be read, so the host check cannot be either"
@@ -1030,7 +1048,7 @@ const DECL_INSTRUMENT =
1030
1048
  "environment_declared in catalyst contract --path teams";
1031
1049
  const DECL_REASONS = {
1032
1050
  no_team_repo_default: {
1033
- text: "no repository is the project's default yet: register one and make it the default",
1051
+ text: "no repository is the project's default yet: register one and make it the default (catalyst onboard --team <KEY> --repo <owner/name> registers it; the default is chosen on Your projects, with no command yet)",
1034
1052
  who: "a workspace owner or admin",
1035
1053
  page: "/settings/projects",
1036
1054
  },
@@ -1050,7 +1068,7 @@ const DECL_REASONS = {
1050
1068
  page: null,
1051
1069
  },
1052
1070
  declaration_awaiting_approval: {
1053
- text: "the declaration is proposed and waits for approval: Settings → Your projects → the project → Repositories → the repository → Environment → Setup declaration → Approve this revision",
1071
+ text: "the declaration is proposed and waits for approval, because no review approved it (a non-author approval of the merged head, or a merge by an owner or admin): approve it at Settings → Your projects → the project → Repositories → the repository → Environment → Setup declaration → Approve this revision (no command approves one repository's revision yet)",
1054
1072
  who: "a workspace owner or admin",
1055
1073
  page: "/settings/projects",
1056
1074
  },
@@ -1058,7 +1076,7 @@ const DECL_REASONS = {
1058
1076
  const DECL_DO =
1059
1077
  "write .catalyst/catalyst.toml with references/declaring-a-repository.md, then open a pull request";
1060
1078
  let declNext =
1061
- "commit .catalyst/catalyst.toml to the repository and have an owner or admin approve it";
1079
+ "merge .catalyst/catalyst.toml through a pull request that someone other than the author approves; that review approves it";
1062
1080
  if (!connected) {
1063
1081
  add(
1064
1082
  "repository declarations",
@@ -1123,9 +1141,10 @@ if (!connected) {
1123
1141
  let unknown = false;
1124
1142
  const describe = (reason) =>
1125
1143
  DECL_REASONS[reason] ?? {
1126
- text: `environment_declared ${reason ?? "failed"} (a reason this bundle does not know; read it on the page)`,
1144
+ text: `environment_declared ${reason ?? "failed"} (a reason this bundle does not know; catalyst ready --json prints its fix line)`,
1127
1145
  who: "a workspace owner or admin",
1128
- page: "/settings/projects",
1146
+ page: null,
1147
+ do: "catalyst ready --json",
1129
1148
  };
1130
1149
  const flag = (team, repo, reason) => {
1131
1150
  const r = describe(reason);
@@ -1133,7 +1152,7 @@ if (!connected) {
1133
1152
  if (verdict === "ok") declNext = `${repo ? `${repo}: ` : ""}${r.text}`;
1134
1153
  verdict = "unfinished";
1135
1154
  owner ??= r.who;
1136
- where ??= r.page === null ? DECL_DO : link(r.page);
1155
+ where ??= r.do ?? (r.page === null ? DECL_DO : link(r.page));
1137
1156
  };
1138
1157
  if (found.length === 0) {
1139
1158
  lines.push(
@@ -1169,7 +1188,7 @@ if (!connected) {
1169
1188
  } else {
1170
1189
  if (verdict !== "ok")
1171
1190
  lines.push(
1172
- "Names only: a value never passes through this script or the file. The person enters values on the repository's Environment page.",
1191
+ "Names only: a value never passes through this script or the file. The person enters each value at a hidden prompt: catalyst var set NAME --repo <owner/name>, or catalyst secret set NAME --repo <owner/name> for a secret.",
1173
1192
  );
1174
1193
  add(
1175
1194
  "repository declarations",
@@ -1285,18 +1304,22 @@ const NEXT = {
1285
1304
  : personalGithubIncomplete
1286
1305
  ? "install the tenant GitHub App and register its repository before connecting your personal GitHub account"
1287
1306
  : "get this person's seat and Linear identity sorted",
1288
- account:
1289
- "connect the Linear integration on the Integrations page (the GitHub App comes later, with the repository)",
1307
+ account: onboardAvailable()
1308
+ ? "start the Linear integration with catalyst onboard and hand over the consent link it prints (the GitHub App comes later, with the repository)"
1309
+ : "connect the Linear integration on the Integrations page (the GitHub App comes later, with the repository)",
1290
1310
  projects:
1291
1311
  blockedProjects.length > 0
1292
1312
  ? blockedProjects.map((b) => b.action).join("; and ")
1293
1313
  : verbAvailable("team map")
1294
1314
  ? "pick ONE project: run catalyst team list, then catalyst team map <KEY> (or team adopt <KEY>) and approve its preview"
1295
1315
  : verbAdminOnly("team map")
1296
- ? "a workspace owner or admin maps ONE project: catalyst team map <KEY>, or Map my stages on the projects page"
1297
- : "pick ONE project and map its stages (or adopt the Catalyst workflow)",
1298
- repositories:
1299
- "install the GitHub App on the Integrations page, granting it the repository you want worked; register it on Your projects and attach it to the project before continuing",
1316
+ ? "a workspace owner or admin maps ONE project: catalyst team map <KEY> (or team adopt <KEY>), approving its preview"
1317
+ : capability("team map")
1318
+ ? "pick ONE project and map its stages (or adopt the Catalyst workflow) on the projects page; this cloud does not serve catalyst team map yet"
1319
+ : `pick ONE project and map its stages with catalyst team map <KEY> (or team adopt <KEY>); this CLI is too old for that verb, so update it first: ${UPDATE_CLI}`,
1320
+ repositories: onboardAvailable()
1321
+ ? "install the GitHub App from the link catalyst onboard prints, granting it the repository you want worked; then register it and attach it to the project with catalyst onboard --team <KEY> --repo <owner/name> before continuing"
1322
+ : "install the GitHub App on the Integrations page, granting it the repository you want worked; register it on Your projects and attach it to the project before continuing",
1300
1323
  "coding accounts": accountsNext,
1301
1324
  "repository declarations": declNext,
1302
1325
  "repository agent setup": repoNext ?? "read the checkout's agent setup again",
@@ -1365,7 +1388,7 @@ function versionAdvice() {
1365
1388
  const latest = npm.error || npm.status !== 0 ? null : semver(npm.stdout);
1366
1389
  // The installer is the update path, not `catalyst install`. Its command is on the app's
1367
1390
  // setup page; this names the script it fetches rather than composing the request here.
1368
- const reinstall = `re-run the install command from the app's setup page (it installs from ${link("/install.sh")})`;
1391
+ const reinstall = `run ${UPDATE_CLI}, or re-run the install script at ${link("/install.sh")}`;
1369
1392
  if (installed === null || latest === null) {
1370
1393
  return {
1371
1394
  text: `Whether a newer Catalyst CLI is published could not be checked. You can ${reinstall}; if this still appears afterwards, tell the Catalyst team.`,
@@ -4,7 +4,7 @@ description: >-
4
4
  Own one Catalyst Cloud project end to end until it closes. Use when the person says "run this project for me", "own this until it ships", "keep this moving", or hands you a project id or a set of tickets to drive. Subscribes to the account's event stream for the scope through the catalyst CLI, reacts to each change in the same turn, makes tickets ready and moves them to dispatch, parks what should stop, chases stalls, escalates inward, and keeps one status summary current. Writes to Linear as the app actor; never polls.
5
5
  allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
6
6
  ---
7
- <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.6 — written in this repository for customer accounts -->
7
+ <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.1 — written in this repository for customer accounts -->
8
8
 
9
9
  # Run this project
10
10
 
@@ -17,7 +17,7 @@ Run `catalyst explain <ticket>` and look the reason up in the `whats-happening`
17
17
  - **It releases itself:** wait for the outcome card.
18
18
  - **The person, with their own login, acts:** do it when the move is yours (re-dispatch, a comment that clears a no-change or validate-budget hold, a question routed to `what-needs-me`), and file an ask when it is theirs.
19
19
  - **A park or hold a release clears:** once its cause is fixed, the `unstick` skill releases it (`catalyst release <ticket> --because <what changed>`). A round-threshold hold and a review that will not converge refuse that release; the table says what clears each.
20
- - **An owner or admin, in settings, or an operator:** one ask per repository or setting.
20
+ - **An owner or admin, in settings, or an operator:** when the person holds that seat and a command can make the change, offer to run it; otherwise one ask per repository or setting.
21
21
  - **Finished:** close the loop in the summary.
22
22
 
23
23
  Quote a reason the table does not know as the explainer spelled it.
@@ -4,7 +4,7 @@ description: >-
4
4
  Get a stuck Catalyst Cloud ticket moving again. Use when the person asks "why is this parked and can you release it?", "unpark this", "get things flowing again", "the outage is over, retry what failed", or hands you a ticket that is not moving. Reads why nothing runs and what holds the ticket through the catalyst CLI, decides whether the recorded cause is fixed, previews the release, releases every governor holding the ticket the right way (or one failure class across a team), and raises an ask only for what a person has to do. Never releases a cause it cannot show changed without saying so.
5
5
  allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
6
6
  ---
7
- <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.6 — written in this repository for customer accounts -->
7
+ <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.1 — written in this repository for customer accounts -->
8
8
 
9
9
  # Unstick
10
10
 
@@ -4,7 +4,7 @@ description: >-
4
4
  The human's decision inbox on Catalyst Cloud, and the one way an agent raises a decision on their behalf. Use when the person asks "what needs me?", "what am I blocking?", or when active work is gated on a choice only they can make. Lists open asks ranked by how much open work each one holds, files an ask through the cloud's ask route with the account's own template, and records the answer so the held work releases. Never answers as the human, never duplicates an open ask.
5
5
  allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
6
6
  ---
7
- <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.6 — written in this repository for customer accounts -->
7
+ <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.1 — written in this repository for customer accounts -->
8
8
 
9
9
  # What needs me
10
10
 
@@ -15,7 +15,7 @@ An ask is one decision only the human can make, filed as a ticket in their own L
15
15
  Scripts are run, never read. Each prints `--help`; exit 2 means this machine is not connected (the `catalyst-onboard` skill connects it), exit 1 means the check itself failed or an argument was missing.
16
16
 
17
17
  - `node scripts/inbox.mjs --help`: the open asks assigned to the connected person, most open tickets held first, then the oldest; `--anyone` for everyone's; `--json` for `{scope, asks}`.
18
- - `node scripts/raise.mjs --help`: file one decision: `--team`, `--title`, `--option` (repeated), `--default`, and `--blocks` (repeated) or `--nothing-to-block`.
18
+ - `node scripts/raise.mjs --help`: file one decision: `--team`, `--title`, `--option` (repeated), `--default`, and `--blocks` (repeated) or `--nothing-to-block`. When the ask decides whether a held PR may merge, `--gates-pr` (repeated) with `--released-by <letter>` makes the answer lift that PR's hold label.
19
19
  - `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.
20
20
 
21
21
  ## Load on demand
@@ -4,7 +4,7 @@ The body, headings, option format and option cap come from the contract's `askTe
4
4
 
5
5
  ## When to file one
6
6
 
7
- File an ask when active work is gated on a product call, a priority call between two things that cannot both go first, an approval, or an action only the human can take (a click in settings, a credential, a payment). File it **before** proceeding on the default. A TODO line, board row, handoff note or chat question may point at the ask's identifier, never replace it.
7
+ File an ask when active work is gated on a product call, a priority call between two things that cannot both go first, an approval, or an action only the human can take (a sign-in or consent, a credential, a payment). A settings change a command can make is yours to offer, not an ask. File it **before** proceeding on the default. A TODO line, board row, handoff note or chat question may point at the ask's identifier, never replace it.
8
8
 
9
9
  Brainstorming, design back-and-forth, a question the human asked first, a retry-or-abandon call a project owner can make, and a system-level failure (one status line; the tickets retry on their own) are not asks.
10
10
 
@@ -18,6 +18,17 @@ Brainstorming, design back-and-forth, a question the human asked first, a retry-
18
18
 
19
19
  `--ask-key` is an idempotency key: a re-run with the same key does not file a second ask.
20
20
 
21
+ ## When the answer releases a PR hold
22
+
23
+ When the decision is whether a held PR may merge, name the PR so the answer lifts the hold itself:
24
+
25
+ - `--gates-pr <n>` (repeated): each PR the answer releases.
26
+ - `--released-by <letter>` (repeated, required with `--gates-pr`): the option letters that release it. Options are lettered A, B, C in the order you gave them, and a letter outside that range is refused.
27
+ - `--gates-label hold|hold:preview` (repeated, default `hold`): the labels the answer removes. No other label is accepted.
28
+ - `--gates-repo <owner/name>`: the PR's repository. It defaults to the account's repository when the account registers exactly one, and must be one the account registers.
29
+
30
+ An answer with any other letter leaves the labels in place.
31
+
21
32
  ## One ask per decision
22
33
 
23
34
  Run `node scripts/inbox.mjs` first. When the same decision is already open, attach the new held tickets to it (the `catalyst-linear` skill adds the relation or a comment) rather than filing a twin that splits its urgency.
@@ -19,7 +19,7 @@ So an ask that holds nothing is real, but the person may never see it in their o
19
19
 
20
20
  ## The identity is not the same as the personal Linear grant
21
21
 
22
- When asks are not reaching someone, check the identity first, then the grant. The **identity** is who they are in Linear, resolved by an email match or matched by the person (`catalyst identity linear status`, `options`, then `catalyst identity linear set <linearUserId>`). The **personal Linear grant** is proven by connecting Linear themselves. Connecting Linear personally does **not** set the identity. An auto-resolved identity, or one another member claims, needs an owner or admin at Settings → Members. With no identity nothing is assigned to them, even after they connect Linear, so an `unmatched` scope is not an empty queue: say "nothing is assigned to you yet, so this is everyone's list; match your identity with `catalyst identity linear set` and it becomes yours".
22
+ When asks are not reaching someone, check the identity first, then the grant. The **identity** is who they are in Linear, resolved by an email match or matched by the person (`catalyst identity linear status`, `options`, then `catalyst identity linear set <linearUserId>`). The **personal Linear grant** is proven by connecting Linear themselves. Connecting Linear personally does **not** set the identity. An auto-resolved identity, or one another member claims, needs an owner or admin at Settings → Members; no command does this yet, so raise an ask to them. With no identity nothing is assigned to them, even after they connect Linear, so an `unmatched` scope is not an empty queue: say "nothing is assigned to you yet, so this is everyone's list; match your identity with `catalyst identity linear set` and it becomes yours".
23
23
 
24
24
  ## Presenting the queue
25
25
 
@@ -14,12 +14,16 @@ const SPEC = {
14
14
  blocks: { value: true, repeat: true, help: "a ticket this decision holds (repeat per ticket)" },
15
15
  "nothing-to-block": { value: false, help: "declare that no ticket is held (the ask then never shows in Waiting on me)" },
16
16
  "ask-key": { value: true, help: "idempotency key so a re-run does not file a second ask" },
17
+ "gates-pr": { value: true, repeat: true, help: "a PR whose hold label the answer removes (repeat per PR; needs --released-by)" },
18
+ "gates-label": { value: true, repeat: true, help: "the hold the answer removes: hold or hold:preview (repeatable; default hold)" },
19
+ "released-by": { value: true, repeat: true, help: "an option letter whose answer releases the hold (repeat per letter)" },
20
+ "gates-repo": { value: true, help: "the --gates-pr repository as owner/name (default: the account's only repository)" },
17
21
  json: { value: false, help: "print the cloud's response as JSON" },
18
22
  };
19
23
 
20
24
  const { help, flags } = parseFlags(process.argv.slice(2), SPEC);
21
25
  if (help) {
22
- printHelp("node scripts/raise.mjs --team <key> --title <question> [--context <text>] [--option <text>]... [--default <text>] --blocks <ticket>... | --nothing-to-block [--ask-key <key>] [--json] [--help]", SPEC, [
26
+ printHelp("node scripts/raise.mjs --team <key> --title <question> [--context <text>] [--option <text>]... [--default <text>] --blocks <ticket>... | --nothing-to-block [--ask-key <key>] [--gates-pr <n>... --released-by <letter>... [--gates-label hold|hold:preview]... [--gates-repo <owner/name>]] [--json] [--help]", SPEC, [
23
27
  "Search for an existing ask first (node scripts/inbox.mjs); one decision, one ask.",
24
28
  "File BEFORE proceeding on the default. Cite the identifier only from this script's output.",
25
29
  ]);
@@ -41,6 +45,10 @@ if (flags.default) args.push("--default", flags.default);
41
45
  for (const b of flags.blocks ?? []) args.push("--blocks", b);
42
46
  if (flags["nothing-to-block"]) args.push("--nothing-to-block");
43
47
  if (flags["ask-key"]) args.push("--ask-key", flags["ask-key"]);
48
+ for (const n of flags["gates-pr"] ?? []) args.push("--gates-pr", n);
49
+ for (const l of flags["gates-label"] ?? []) args.push("--gates-label", l);
50
+ for (const l of flags["released-by"] ?? []) args.push("--released-by", l);
51
+ if (flags["gates-repo"]) args.push("--gates-repo", flags["gates-repo"]);
44
52
  args.push("--json");
45
53
 
46
54
  const result = parseJson(mustRun(args, { quiet: true }).stdout, "ask raise");
@@ -49,5 +57,6 @@ if (flags.json) process.stdout.write(JSON.stringify(result) + "\n");
49
57
  else {
50
58
  process.stdout.write(`ask raised: ${id ?? "(no identifier returned)"}${flags.blocks ? ` — holds ${flags.blocks.join(", ")}` : " — holds nothing"}\n`);
51
59
  if (flags.default) process.stdout.write(`default if silent: ${flags.default}\n`);
60
+ if (flags["gates-pr"]) process.stdout.write(`answer ${flags["released-by"].join(" or ")} releases the hold on PR ${flags["gates-pr"].map((n) => `#${n}`).join(", ")}\n`);
52
61
  }
53
62
  process.exit(0);
@@ -4,7 +4,7 @@ description: >-
4
4
  What environment variable names does this repository need, and where does each one come from? Scans the repository offline (no login, no network) and lists the names in three groups (build/test, deploy-only, bindings), each with where it was found, what uses it, and where a local value would come from. Never reads a value or prints one. Also checks the environment variable table in `.catalyst/catalyst.toml` offline. Use when someone asks "what does catalyst.env.json mean", "what env vars does this repo need", "why does the container need this", or before reviewing or writing the declaration.
5
5
  allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
6
6
  ---
7
- <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.6 — written in this repository for customer accounts -->
7
+ <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.1 — written in this repository for customer accounts -->
8
8
 
9
9
  # What this repository needs
10
10
 
@@ -1,10 +1,10 @@
1
1
  ---
2
2
  name: whats-happening
3
3
  description: >-
4
- Use for "what's happening?", "where are we?", "why is that stuck?", "what closed?", "what's next?", "how does this work?", "why did it do that?", "how does it prioritise?" and "what does this setting do, where is it set?" in Catalyst Cloud, or asks for something to be done rather than known. Reads the contract via catalyst CLI. Read-only: route work/decisions, never answer as the human.
4
+ Use for "what's happening?", "what machines do we have?", "how much capacity do we have?", "where are we?", "why is that stuck?", "what closed?", "what's next?", "how does this work?", "why did it do that?", "how does it prioritise?" and "what does this setting do, where is it set?" in Catalyst Cloud, or asks for something to be done rather than known. Reads the contract via catalyst CLI. Read-only: route work/decisions, never answer as the human.
5
5
  allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
6
6
  ---
7
- <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.6 — written in this repository for customer accounts -->
7
+ <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.1 — written in this repository for customer accounts -->
8
8
 
9
9
  # What's happening
10
10
 
@@ -20,6 +20,7 @@ Comments/relations: catalyst-linear. PRs: catalyst-github. Readiness: catalyst-o
20
20
 
21
21
  | question | reference |
22
22
  | -- | -- |
23
+ | machines or machine capacity? | references/machines.md |
23
24
  | status or stuck? | references/status-reply.md; schema assets/status-reply.json |
24
25
  | eligibility reason? | references/why-is-it-stuck.md |
25
26
  | order, capacity, WIP or routing? | references/what-runs-next.md |
@@ -1,6 +1,6 @@
1
1
  # Coding accounts
2
2
 
3
- snapshot.mjs --accounts omits credentials/emails. An owner/admin enrols, pauses, removes or replaces credentials at `<their cloud>/settings/coding-accounts`.
3
+ snapshot.mjs --accounts omits credentials/emails. An owner/admin enrols, pauses, removes or replaces credentials at `<their cloud>/settings/coding-accounts`; no command does these yet. `catalyst accounts` reads them.
4
4
 
5
5
  Slots identify credentials, not emails. Claude/GLM/Qwen use Claude CLI, Codex uses Codex; GLM/Qwen also use OpenCode. Routing names the model.
6
6
 
@@ -0,0 +1,9 @@
1
+ # Machines and capacity
2
+
3
+ Run `catalyst hosts --json` when someone asks what machines their account has or how much capacity is available. Name `catalyst hosts` as the command behind the answer. The same read is available through the SDK's `client.hosts.list()`.
4
+
5
+ List each machine's name, whether it is self-hosted or Catalyst's, its teams, slots in use and free, last check-in, and live or stale status. A self-hosted machine uses the name its customer chose. Catalyst's machines say Cloud followed by the server or service name.
6
+
7
+ Use the response's capacity total. It already counts a cloud machine shared across teams once and excludes stale machines. A machine is stale when its runtime proof has expired or it has not checked in for 15 minutes. A stale machine stays in the list, with its last check-in, but contributes no live capacity. A missing check-in is unknown, not proof that a machine is live. A failed read is inconclusive; never report zero machines or zero capacity from a failure.
8
+
9
+ For a requested change, an admin can run `catalyst hosts rename <name-or-id> <new-name>` or `catalyst hosts remove <name-or-id>`. Removal asks for confirmation; `--yes` records explicit consent for an unattended invocation. A member can read but cannot change machines. When a name matches more than one machine, use its ID. An admin choosing to run work on a new machine can name it during `catalyst setup`, or pass `--machine-name <name>`.
@@ -1,17 +1,17 @@
1
1
  # Settings
2
2
 
3
- Use live values and the person's cloud prefix. Owner/admin acts unless stated. Name screen, route, rule.
3
+ Use live values and the person's cloud prefix. Owner/admin acts unless stated. Name screen, route, rule. Where a command does the change, offer to run it before naming the screen.
4
4
 
5
5
  | screen and route | rule |
6
6
  | -- | -- |
7
- | Integrations, /settings/connections | Connecting Linear needs admin browser session, which chooses the account. A workspace binds to exactly one account and vice versa; a conflict needs a decision, not retry. |
8
- | Profile, /settings/profile, then Connections | The personal GitHub grant comes first; missing identity or another person's installation refusal means grant personal GitHub first. |
7
+ | Integrations, /settings/connections | Connecting Linear needs an admin's consent in their browser, which chooses the account; `catalyst onboard` starts it and prints the consent link. A workspace binds to exactly one account and vice versa; a conflict needs a decision, not retry. |
8
+ | Profile, /settings/profile, then Connections | The personal GitHub grant comes first; missing identity or another person's installation refusal means grant personal GitHub first. Offer `catalyst connections personal github start` and hand over the link it prints. |
9
9
  | Connections | Removing the App on GitHub self-heals the registry; Catalyst unlink uninstalls upstream; neither needs support. |
10
- | Your projects, /settings/projects | Each Catalyst project pairs one Linear team with its registered GitHub repositories. Manage the project, attach repositories, and choose the team's default repository here. |
11
- | Merging, /settings/projects/$projectId/repositories/$repoId/merging | Merge policy is changed here; contract merge gives value/source; catalyst-github only reads. |
12
- | Code reviews, /settings/projects/$projectId/repositories/$repoId/code-reviews | Post review requests as selects a connected member. Strict policy without reviewer never earns ready label; configure reviewer or change policy. |
10
+ | Your projects, /settings/projects | Each Catalyst project pairs one Linear team with its registered GitHub repositories. Attach a repository with `catalyst onboard --team <KEY> --repo <owner/name>`; choose the team's default repository here, with no command for that yet. |
11
+ | Merging, /settings/projects/$projectId/repositories/$repoId/merging | Merge policy is the variable `CATALYST_MERGE_EVIDENCE_POLICY`: offer `catalyst var set CATALYST_MERGE_EVIDENCE_POLICY --repo <owner/name>`, or change it here. A policy declared in `.catalyst/catalyst.toml` wins over the variable: change it there in a pull request; contract merge gives value/source; catalyst-github only reads. |
12
+ | Code reviews, /settings/projects/$projectId/repositories/$repoId/code-reviews | Post review requests as selects a connected member. Strict policy without reviewer never earns ready label; configure reviewer here (no command yet) or change policy. |
13
13
  | Merge queue, no Catalyst page | After ready label, the merge itself is whatever the repository's own setup does with it; the repo admin owns this. This bundle cannot identify its queue vendor; never guess. |
14
14
  | Repositories | Shows operator-set runner cap, default 20, paused effective zero; see what-runs-next.md. |
15
- | Secrets /settings/secrets and Environment /settings/environment, plus repository sections | Secrets write-only, variables readable; names are never both. Repository values override account values. Variables may reference vault secrets, expanded only in container. |
16
- | Repositories | Without a registered team nothing reaches a runner, and the failure is silent; raise admin/operator fix as ask. |
15
+ | Secrets /settings/secrets and Environment /settings/environment, plus repository sections | Secrets write-only, variables readable; names are never both. Repository values override account values. Variables may reference vault secrets, expanded only in container. Offer `catalyst var set NAME [--repo <owner/name>]` or `catalyst secret set NAME --repo <owner/name>`; a workspace-wide secret has no command yet. |
16
+ | Repositories | Without a registered team nothing reaches a runner, and the failure is silent; an owner or admin can register it with `catalyst onboard --team <KEY> --repo <owner/name>`, so offer that, otherwise raise the fix as an ask. |
17
17
  | Routing, no settings page for this | Global stage defaults and empty per-account table select default provider; never promise Codex-first. |
@@ -14,6 +14,6 @@ Order: dispatch, intake, research, plan, implement, remediate, verify, review, p
14
14
 
15
15
  Missing/wrong required mappings stop work; others warn. Dispatch/pr/done/canceled must be live. `teams[].gitAutomation` stores consent; nothing reads it.
16
16
 
17
- workflowMode: adopted means Catalyst-created, mapped means human-chosen, mixed means both. Configure at `<their cloud>/settings/linear-teams`, never catalyst.toml.
17
+ workflowMode: adopted means Catalyst-created, mapped means human-chosen, mixed means both. Configure with `catalyst team map <KEY>` or `catalyst team adopt <KEY>` (offer to run it for an owner or admin), never catalyst.toml.
18
18
 
19
19
  stateId is authoritative; imports can replace ids but keep names. catalyst-linear resolves slots to ids. stateStillExists: false refuses the move; remap with `catalyst team map <KEY>`. Backlog is not a slot: parking resolves a live backlog-type state. catalyst-onboard explains readiness and who fixes it.