@catalyst-cloud/cli 0.16.0 → 0.16.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +9 -6
- package/dist/args.js +6 -0
- package/dist/capabilities.js +3 -1
- package/dist/cli.js +10 -5
- package/dist/environment.js +1 -1
- package/dist/execution.js +2 -2
- package/dist/hosts.js +89 -0
- package/dist/legacy.js +12 -2
- package/dist/machine-inventory.js +37 -0
- package/dist/onboard-capacity.js +5 -6
- package/dist/onboard-headless.js +2 -2
- package/dist/onboard-next.js +18 -10
- package/dist/onboard-project-cleanup.js +100 -0
- package/dist/onboard-returning.js +172 -0
- package/dist/onboard-runner.js +149 -34
- package/dist/onboard-runtime.js +26 -1
- package/dist/onboard-ui.js +61 -0
- package/dist/onboard.js +9 -2
- package/dist/ready-copy.js +30 -24
- package/dist/ready.js +2 -2
- package/dist/secret.js +2 -2
- package/dist/setup-onboard-copy.js +37 -32
- package/dist/setup.js +4 -0
- package/dist/transport.js +18 -2
- package/package.json +3 -3
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +3 -3
- package/skills/catalyst-onboard/references/connecting-this-machine.md +4 -4
- package/skills/catalyst-onboard/references/declaring-a-repository.md +1 -1
- package/skills/catalyst-onboard/references/reading-ready.md +13 -13
- package/skills/catalyst-onboard/references/replacing-a-credential.md +1 -1
- package/skills/catalyst-onboard/references/the-one-path.md +5 -5
- package/skills/catalyst-onboard/references/what-a-phase-needs.md +4 -4
- package/skills/catalyst-onboard/references/what-the-browser-owns.md +20 -10
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +49 -26
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/run-this-project/references/stalls-and-escalation.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-needs-me/references/raising-a-decision.md +1 -1
- package/skills/what-needs-me/references/reading-the-inbox.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +3 -2
- package/skills/whats-happening/references/coding-accounts.md +1 -1
- package/skills/whats-happening/references/machines.md +9 -0
- package/skills/whats-happening/references/settings-and-where-they-live.md +8 -8
- package/skills/whats-happening/references/stages-and-mapping.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.16.2
|
|
4
|
+
|
|
5
|
+
Setup keeps verified earlier Catalyst skill copies and reports where it moved them.
|
|
6
|
+
|
|
7
|
+
## 0.16.1
|
|
8
|
+
|
|
9
|
+
Setup lists your existing projects and lets you finish this computer without choosing a team again.
|
|
10
|
+
|
|
11
|
+
- Keep your saved machine name when setup finds an existing installation, even if its settings file is missing.
|
|
12
|
+
- Check readiness across projects that share a Linear team, with a clear next action when a check cannot finish.
|
|
13
|
+
- Remove recognized older background jobs automatically. Preserve shared data and explain any cleanup that needs attention.
|
|
14
|
+
- Recheck the existing installation before offering a replacement, and distinguish an ended enrollment from one that could not be verified.
|
|
15
|
+
|
|
3
16
|
## 0.16.0
|
|
4
17
|
|
|
5
18
|
The CLI, skills and plugin now share the 0.16 release line with the SDK and installer. The CLI installs the matching Node replica module for local cache commands. Update the CLI and update the Catalyst plugin through your agent's plugin manager.
|
package/README.md
CHANGED
|
@@ -85,7 +85,7 @@ catalyst ready
|
|
|
85
85
|
|
|
86
86
|
`catalyst login` with no key opens a device-code login: it prints a short code and a URL, you approve it in your browser, and this machine is connected as you. On a machine with no browser (a remote box, a container) the code and URL still work — approve them from your phone. The short-lived session refreshes silently afterwards, so you log in about once a year. `npx -p @catalyst-cloud/cli catalyst 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>`.
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
For a script or an unattended machine, where nobody can approve a code, pass a **personal key** instead. Minting one is a browser step: Settings → API keys in the Catalyst Cloud app (every member can; no admin needed). The environment form keeps it out of your shell history:
|
|
89
89
|
|
|
90
90
|
```sh
|
|
91
91
|
CATALYST_CLOUD_TOKEN=<your-personal-key> catalyst login
|
|
@@ -98,7 +98,8 @@ After a workspace owner connects your cloud account's Linear workspace, connect
|
|
|
98
98
|
```sh
|
|
99
99
|
catalyst connections personal linear start
|
|
100
100
|
catalyst connections personal linear status
|
|
101
|
-
# First install the workspace's GitHub App and register its repository
|
|
101
|
+
# First install the workspace's GitHub App and register its repository:
|
|
102
|
+
# catalyst onboard --team <KEY> --repo <owner/name>
|
|
102
103
|
catalyst connections personal github start
|
|
103
104
|
catalyst connections personal github status
|
|
104
105
|
```
|
|
@@ -121,7 +122,7 @@ A keyless session's access token is short-lived and rotates on its own: every re
|
|
|
121
122
|
|
|
122
123
|
`login` does not install skills; the install command above did that. Re-running `login` after a key rotation, or to switch rails, rewrites the config. `catalyst status` prints which cloud account this machine is connected to and as whom, `catalyst ready` prints one READY or NOT READY verdict with the fix for each failure and who can apply it, and `catalyst --version` prints the package version and its pinned contract range.
|
|
123
124
|
|
|
124
|
-
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. Your cloud account's **account key** (
|
|
125
|
+
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. Your cloud account's **account key** (minted by `catalyst onboard --runner`, run by an owner or admin) 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.
|
|
125
126
|
|
|
126
127
|
## Run and resume setup
|
|
127
128
|
|
|
@@ -161,6 +162,8 @@ Before any network call, the run checks that every input is present. Each missin
|
|
|
161
162
|
| The person's Linear | `linear_personal_grant_missing` | Settings → Connected accounts |
|
|
162
163
|
| The person's GitHub | `github_personal_grant_missing` | Settings → Connected accounts |
|
|
163
164
|
|
|
165
|
+
With a person at the terminal, `catalyst onboard` starts each of these approvals itself and prints the exact link to approve.
|
|
166
|
+
|
|
164
167
|
With `--runner yes`, setup starts the self-hosted runner and verifies enrollment, advertised capacity and team admission. This needs Docker with Compose, pinned host images and an organization key file (`CATALYST_RUNNER_ORG_KEY_FILE`). When images, a key or admission are missing, exit 11 names that gate. A failed setup step exits 10. Use `--runner no` to skip the machine's runner. Unlike the interactive optional runner step, an explicitly requested headless runner must finish before exit 0.
|
|
165
168
|
|
|
166
169
|
With `--json`, stdout carries exactly one document: the onboarding receipt plus a `headless` block (`schema: "catalyst-onboard-headless/1"`). The block holds the resolved `inputs` (the key appears only as its source: `env`, `file` or `saved`), and lists of items under `missing`, `refused`, `failed`, `deferred` and `warnings`. Each item has `id`, `kind` (`input`, `grant` or `step`), `reason` and a one-line `text`, plus `flag`, `env` and `url` where they apply. Everything else goes to stderr.
|
|
@@ -183,7 +186,7 @@ Local sync is optional. Include it in the plan with `--local-sync` when you need
|
|
|
183
186
|
## Requirements
|
|
184
187
|
|
|
185
188
|
- Node 22.15 or newer (Node 26 works), or bun 1.4 or newer. The bundle uses Node's built-in SQLite module (and, on bun, bun's own `node:sqlite`) for the optional local replica, so there is no native dependency to build; if `better-sqlite3` resolves on the machine it is used instead. `catalyst ready` names the exact reason when the runtime is too old, and `catalyst runtime install` installs a pinned Node under this CLI's own cache — without touching your machine's default Node — if you would rather not upgrade it.
|
|
186
|
-
- Your personal key
|
|
189
|
+
- Your own login: a keyless `catalyst login`, or for an unattended machine a personal key you mint at Settings → API keys. The credential is the only account selector: you never type an account id. If your Linear identity is not matched yet, `login` says so; you match it yourself with `catalyst identity linear set` (an identity someone else already claims needs an admin at Settings → Members), and until then "what needs me" shows everyone's asks.
|
|
187
190
|
- 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.
|
|
188
191
|
- Bun is optional, only if you prefer `bunx` over `npx` — bun 1.4 or newer, which is when `node:sqlite` arrives; older bun cannot run this CLI at all.
|
|
189
192
|
|
|
@@ -233,7 +236,7 @@ Some of these only read, and some write: a comment, a card move, an ask, a relea
|
|
|
233
236
|
|
|
234
237
|
## What a key cannot see yet
|
|
235
238
|
|
|
236
|
-
Your personal key reads everything the skills need — tickets, pull requests, the eligibility explainer, the dispatch queue, fleet activity, per-ticket execution history (`catalyst explain --history <ticket>`: phase attempts, remediation rounds, park state) and coding-account status (`catalyst accounts`: provider, declared and observed state, usage limits, walls, quarantine — never a credential; enrolling or pausing one is `<your cloud>/settings/coding-accounts
|
|
239
|
+
Your personal key reads everything the skills need — tickets, pull requests, the eligibility explainer, the dispatch queue, fleet activity, per-ticket execution history (`catalyst explain --history <ticket>`: phase attempts, remediation rounds, park state) and coding-account status (`catalyst accounts`: provider, declared and observed state, usage limits, walls, quarantine — never a credential; enrolling or pausing one is `<your cloud>/settings/coding-accounts`, with no command yet). It also releases a parked or held ticket once its cause is fixed: `catalyst release <ticket> --because <what changed>` (the `unstick` skill runs it), recorded against your name and shown in the ticket's history. And it declares what your containers need: `catalyst environment` reads the account-wide declaration, `environment propose --file <path> --approve` proposes and approves it in one compare-and-set, and the values behind the names stay in the cloud — reading needs any active seat, proposing and approving need an admin or owner one. An admin or owner can put a repository's values in from the terminal. `catalyst secret import .env --repo owner/name` stores every name in the file and lists the declared names that still have no value. `catalyst secret set NAME --repo owner/name --command 'op read op://Vault/item/field'` runs the command on your machine and stores its output; with no `--command` it reads the value from stdin, or asks for it without echoing. No value is ever printed, and the cloud's audit records the command, not its output. That is the account's own declaration — a repository's own `.catalyst/catalyst.toml`, which `catalyst-onboard` already reports on, is a separate thing. `catalyst env inventory` lists the environment variable names a repository needs — never a value; `env check` checks the TOML syntax and environment variable table in `.catalyst/catalyst.toml` offline; `env migrate [catalyst.env.json]` prints a TOML environment table from a legacy declaration, with all names optional and values omitted. These commands need no login (the `what-this-repo-needs` skill walks through them). Two things it cannot do, and the skills say so by name rather than guess:
|
|
237
240
|
|
|
238
241
|
- Read pull-request labels or the reviewer's reaction. The mirror does not carry them; GitHub's own page does.
|
|
239
242
|
- Compute a flow number — cycle time, throughput, or how long pull requests have been open. Nothing serves those yet, so say they are not computed rather than counting something else and calling it that.
|
|
@@ -277,7 +280,7 @@ npm uninstall -g @catalyst-cloud/cli
|
|
|
277
280
|
|
|
278
281
|
## If login fails
|
|
279
282
|
|
|
280
|
-
- `catalyst: GET /me failed (401): credential not accepted
|
|
283
|
+
- `catalyst: GET /me failed (401): credential not accepted. Run catalyst login to sign in again; an unattended machine needs a new personal key from Settings → API keys` — the key is stale, mistyped or revoked. Run a keyless `catalyst login`, or on an unattended machine mint a new key, then run `login` again.
|
|
281
284
|
- `catalyst: GET /me failed (403): account-not-operational` — your cloud account is suspended. This is a conversation with your account's admin, not a local fix.
|
|
282
285
|
- `catalyst: 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`.
|
|
283
286
|
- A line naming two contract versions after `Connected to` — your account serves a contract outside this bundle's `1.x || 2.x` range. The config is written; update the bundle before using the other skills.
|
package/dist/args.js
CHANGED
|
@@ -20,6 +20,7 @@ const GLOBAL_FLAGS = {
|
|
|
20
20
|
};
|
|
21
21
|
/** Per-verb flag tables. A verb absent here accepts only the global flags. */
|
|
22
22
|
export const FLAG_TABLES = {
|
|
23
|
+
hosts: { yes: { value: false, help: "confirm removal without a terminal" } },
|
|
23
24
|
login: {
|
|
24
25
|
"start-replica": {
|
|
25
26
|
value: false,
|
|
@@ -41,6 +42,10 @@ export const FLAG_TABLES = {
|
|
|
41
42
|
},
|
|
42
43
|
install: {},
|
|
43
44
|
onboard: {
|
|
45
|
+
"machine-name": {
|
|
46
|
+
value: true,
|
|
47
|
+
help: "name this self-hosted machine (default: its hostname)",
|
|
48
|
+
},
|
|
44
49
|
verbose: { value: false, help: "show granted permissions for connected steps in interactive setup" },
|
|
45
50
|
team: {
|
|
46
51
|
value: true,
|
|
@@ -480,6 +485,7 @@ export const FLAG_TABLES = {
|
|
|
480
485
|
},
|
|
481
486
|
};
|
|
482
487
|
export const VERB_USAGE = {
|
|
488
|
+
hosts: "hosts [list] | hosts remove <name-or-id> [--yes] | hosts rename <name-or-id> <new-name> [--json]",
|
|
483
489
|
setup: "setup [onboard options] (sets up Catalyst step by step; same as catalyst onboard) | setup --engine <file> --engine-sha256 <hex> [-- <install options>] (run by install.sh; install.sh --help lists the options)",
|
|
484
490
|
login: "login [--base-url <url>] [--start-replica] (keyless; or --key <personal-key> / CATALYST_CLOUD_TOKEN)",
|
|
485
491
|
onboard: "onboard [--team <ID|key>] [--repo <owner/name>]... [--coding-account <slot>] [--resume-from <step>] [--only <step>] [--local-sync] [--runner|--no-runner] [--no-color] [--yes] [--dry-run] [--json] | onboard --headless [--key-file <path>] [--team ...] [--repo ...]... [--coding-account ...] [--runner yes|no] [--json]",
|
package/dist/capabilities.js
CHANGED
|
@@ -5,9 +5,11 @@ const agent = (method, name) => ({ method, path: `/api/v1/agent/${name}` });
|
|
|
5
5
|
* the person, the workspace, the project, the repository, then work. A new verb is a new row here
|
|
6
6
|
* in the same change, or `catalyst capabilities` lies to every guide that reads it. */
|
|
7
7
|
export const CAPABILITIES = [
|
|
8
|
+
{ verb: "hosts", does: "list this account's machines, current use and live capacity", needs: "member", routes: [], since: "0.16.0" },
|
|
9
|
+
{ verb: "hosts remove|rename", does: "remove or rename a self-hosted machine in this account", needs: "admin", routes: [], since: "0.16.0" },
|
|
8
10
|
// CTC-4680: plain `catalyst setup` runs the onboard flow, so install.sh may hand off to it. install.sh
|
|
9
11
|
// reads this flag rather than `setup --help`, which already exits 0 on 0.15.x.
|
|
10
|
-
{ verb: "setup", does: "set up Catalyst step by step, or show install.sh's plan, progress and sign-in in this terminal", needs: "member", routes: [], since: "0.15.0", engineProtocol: 1, bootstrapHandoff: true },
|
|
12
|
+
{ verb: "setup", does: "set up Catalyst step by step, or show install.sh's plan, progress and sign-in in this terminal", needs: "member", routes: [], since: "0.15.0", engineProtocol: 1, bootstrapHandoff: true, bootstrapHandoffVersion: 2 },
|
|
11
13
|
{ verb: "onboard", does: "run and resume Catalyst setup with a saved step record", needs: "member", routes: [], since: "0.14.0", bootstrapHandoff: true, headless: true },
|
|
12
14
|
{ verb: "status", does: "say whether this machine is connected, as whom, and to which cloud", needs: "member", routes: [], since: "0.1.0" },
|
|
13
15
|
{ verb: "me", does: "read the person's seat, role and Linear identity", needs: "member", routes: [], since: "0.1.0" },
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { cmdHosts } from "./hosts.js";
|
|
1
2
|
// cli.ts — the `catalyst` dispatcher (npm: @catalyst-cloud/cli). Verbs live in
|
|
2
3
|
// their own modules; this file keeps every export the 0.1 tests and the bin import.
|
|
3
4
|
import { createSetupRenderer, setupBanner, } from "./setup-render.js";
|
|
@@ -79,6 +80,7 @@ export function usageText() {
|
|
|
79
80
|
" catalyst onboard [--resume-from <step>] [--only <step>] [--runner|--no-runner] [--yes] [--dry-run] [--json]",
|
|
80
81
|
` catalyst ${SETUP_USAGE}`,
|
|
81
82
|
" catalyst status | notice | me | ready | accounts",
|
|
83
|
+
" catalyst hosts [list] | hosts remove <name-or-id> [--yes] | hosts rename <name-or-id> <new-name>",
|
|
82
84
|
" catalyst mcp add|list|remove (vault references only)",
|
|
83
85
|
" catalyst contract [--refresh] [--path <a.b.c>]",
|
|
84
86
|
" catalyst query <issues|issue <id>|pulls|pull <id>|projects|cycles|search <terms>|changes --since <cursor|head>>",
|
|
@@ -372,11 +374,11 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
372
374
|
...(deps.setupApproved
|
|
373
375
|
? { reviewedSetup: true, confirm: async () => true }
|
|
374
376
|
: {}),
|
|
375
|
-
beforeStep: async () => {
|
|
376
|
-
if (args.flags["dry-run"] !== true)
|
|
377
|
-
await refreshSessionIfShort(ctx, ONBOARD_STEP_MIN_MS);
|
|
378
|
-
},
|
|
379
377
|
...createOnboardRuntime(args, ctx, {
|
|
378
|
+
beforeStep: async () => {
|
|
379
|
+
if (args.flags["dry-run"] !== true)
|
|
380
|
+
await refreshSessionIfShort(ctx, ONBOARD_STEP_MIN_MS);
|
|
381
|
+
},
|
|
380
382
|
ui,
|
|
381
383
|
signinTimeoutMs: deps.onboardSigninTimeoutMs,
|
|
382
384
|
stageSignin: (signal) => {
|
|
@@ -511,6 +513,8 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
511
513
|
return await cmdLegacy(args, ctx, deps.legacy ?? {});
|
|
512
514
|
case "identity":
|
|
513
515
|
return await cmdIdentity(args, ctx, deps.identity ?? {});
|
|
516
|
+
case "hosts":
|
|
517
|
+
return await cmdHosts(args, ctx, deps.hosts);
|
|
514
518
|
case "connections":
|
|
515
519
|
return await cmdConnections(args, ctx, deps.connections ?? {});
|
|
516
520
|
default:
|
|
@@ -620,6 +624,7 @@ async function runSetup(argv, ctx, deps) {
|
|
|
620
624
|
...(flags.json ? ["--json"] : []),
|
|
621
625
|
...(flags.verbose ? ["--verbose"] : []),
|
|
622
626
|
...(flags.localSync ? ["--local-sync"] : []),
|
|
627
|
+
...(flags.runner === undefined ? [] : [flags.runner ? "--runner" : "--no-runner"]),
|
|
623
628
|
...(flags.yes || flags.json || !terminal ? ["--yes"] : []),
|
|
624
629
|
];
|
|
625
630
|
let ui;
|
|
@@ -726,7 +731,7 @@ async function cmdLogin(args, ctx, deps, personalOnly = false) {
|
|
|
726
731
|
: ' — your Linear identity is not matched yet, so "what needs me" will show everyone\'s asks until an admin matches it in Settings'}`);
|
|
727
732
|
}
|
|
728
733
|
else {
|
|
729
|
-
ctx.stderr(`[catalyst] this is your Catalyst workspace's shared 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.
|
|
734
|
+
ctx.stderr(`[catalyst] this is your Catalyst workspace's shared 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. Run catalyst login to sign in as yourself; for an unattended machine, make a personal key at Settings → API keys and sign in with that.`);
|
|
730
735
|
}
|
|
731
736
|
ctx.stdout(`Config written to ${written.path} (mode ${formatMode(written.mode)}, holds your ${auth ? "login session" : "key"} and the CLI path)${written.mode === CONFIG_MODE
|
|
732
737
|
? ""
|
package/dist/environment.js
CHANGED
|
@@ -79,7 +79,7 @@ export function renderRead(r) {
|
|
|
79
79
|
// never a refusal: the secret may be created next, and a repo may supply the name at checkout.
|
|
80
80
|
const unresolved = r.unresolvedReferences ?? [];
|
|
81
81
|
if (unresolved.length > 0) {
|
|
82
|
-
lines.push(`referenced but not set
|
|
82
|
+
lines.push(`referenced but not set in this workspace yet: ${unresolved.join(", ")}. Set them with catalyst var set or catalyst secret set, or a phase that needs one will fail on it`);
|
|
83
83
|
}
|
|
84
84
|
return lines;
|
|
85
85
|
}
|
package/dist/execution.js
CHANGED
|
@@ -51,7 +51,7 @@ export const EXCLUSION_REASONS = {
|
|
|
51
51
|
export const UNKNOWN_REASONS = {
|
|
52
52
|
ordering_never_published: "the dispatch order has never been published for this team — usually because the team has no saved stage mapping, which does not clear by itself",
|
|
53
53
|
ordering_stale: "the dispatch order is stale",
|
|
54
|
-
workflow_mapping_unknown: "the team has no saved stage mapping for dispatch, pr, done and canceled (or a mapped stage was deleted); it does not clear by itself
|
|
54
|
+
workflow_mapping_unknown: "the team has no saved stage mapping for dispatch, pr, done and canceled (or a mapped stage was deleted); it does not clear by itself; a workspace owner or admin runs catalyst team map <KEY>, or catalyst team adopt <KEY> to create Catalyst's stages",
|
|
55
55
|
ticket_unknown: "the ticket is not in the mirror",
|
|
56
56
|
dependency_snapshot_unknown: "the dependency snapshot has never completed",
|
|
57
57
|
blocker_unknown: "a blocking relation could not be resolved",
|
|
@@ -373,7 +373,7 @@ export async function cmdAccounts(args, ctx) {
|
|
|
373
373
|
return 0;
|
|
374
374
|
}
|
|
375
375
|
if (accounts.length === 0) {
|
|
376
|
-
ctx.stdout(`No coding accounts are enrolled
|
|
376
|
+
ctx.stdout(`No coding accounts are enrolled in this workspace. A workspace owner or admin adds one at ${normalizeBaseUrl(cfg.baseUrl)}/settings/coding-accounts; there is no command for that yet.`);
|
|
377
377
|
return 0;
|
|
378
378
|
}
|
|
379
379
|
for (const a of accounts)
|
package/dist/hosts.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { createInterface } from "node:readline/promises";
|
|
2
|
+
import { positionals } from "./args.js";
|
|
3
|
+
import { requireConfig } from "./config.js";
|
|
4
|
+
import { CliError, UsageError } from "./errors.js";
|
|
5
|
+
import { apiClient } from "./transport.js";
|
|
6
|
+
import { isMachineInventory, isMachineMutation, } from "./machine-inventory.js";
|
|
7
|
+
import { stdinIsTty } from "./prompt.js";
|
|
8
|
+
async function confirmRemove(name) {
|
|
9
|
+
if (!stdinIsTty())
|
|
10
|
+
return false;
|
|
11
|
+
const prompt = createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stderr,
|
|
14
|
+
});
|
|
15
|
+
try {
|
|
16
|
+
return /^(y|yes)$/i.test((await prompt.question(`Remove ${name} from this account? It will stop receiving work. [y/N] `)).trim());
|
|
17
|
+
}
|
|
18
|
+
finally {
|
|
19
|
+
prompt.close();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function machineLines(inventory) {
|
|
23
|
+
const lines = inventory.machines.map((m) => `${m.name} | ${m.ownership === "self_hosted" ? "Self-hosted" : "Catalyst's"} | ${m.teams.join(", ")} | ${m.slots} slots, ${m.inUse} in use, ${m.free} free | ${m.status} | last check-in ${m.lastCheckInAtMs === null ? "never" : new Date(m.lastCheckInAtMs).toISOString()}`);
|
|
24
|
+
if (lines.length === 0)
|
|
25
|
+
lines.push("No machines are enrolled for this account.");
|
|
26
|
+
const c = inventory.capacity;
|
|
27
|
+
lines.push(`Live capacity: ${c.slots} slots (${c.selfHostedSlots} self-hosted, ${c.catalystSlots} from Catalyst), ${c.inUse} in use, ${c.free} free. Stale machines are excluded.`);
|
|
28
|
+
return lines;
|
|
29
|
+
}
|
|
30
|
+
export async function cmdHosts(args, ctx, deps = {}) {
|
|
31
|
+
const parts = positionals(args);
|
|
32
|
+
const action = parts[0] ?? "list";
|
|
33
|
+
if (!((action === "list" && parts.length <= 1) ||
|
|
34
|
+
(action === "remove" && parts.length === 2) ||
|
|
35
|
+
(action === "rename" && parts.length === 3)))
|
|
36
|
+
throw new UsageError("hosts takes: [list] | remove <name-or-id> [--yes] | rename <name-or-id> <new-name>");
|
|
37
|
+
if (args.flags.yes === true && action !== "remove")
|
|
38
|
+
throw new UsageError("--yes is only valid with hosts remove");
|
|
39
|
+
if (action === "rename" &&
|
|
40
|
+
!/^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$/.test(parts[2]))
|
|
41
|
+
throw new UsageError("Use 1 to 63 letters, digits, dots, underscores or hyphens, starting with a letter or digit.");
|
|
42
|
+
const cfg = requireConfig(ctx);
|
|
43
|
+
const client = apiClient(cfg, ctx);
|
|
44
|
+
const listed = await client.getJson("/api/v1/hosts/machines", {
|
|
45
|
+
query: { account: cfg.account },
|
|
46
|
+
});
|
|
47
|
+
if (!isMachineInventory(listed.body) || listed.body.account !== cfg.account)
|
|
48
|
+
throw new CliError("The cloud returned an unreadable machine list.", "shape");
|
|
49
|
+
if (action === "list") {
|
|
50
|
+
if (args.json)
|
|
51
|
+
ctx.stdout(JSON.stringify(listed.body));
|
|
52
|
+
else
|
|
53
|
+
for (const line of machineLines(listed.body))
|
|
54
|
+
ctx.stdout(line);
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
const matches = listed.body.machines.filter((m) => m.id === parts[1] || m.name === parts[1]);
|
|
58
|
+
if (matches.length !== 1)
|
|
59
|
+
throw new CliError(matches.length === 0
|
|
60
|
+
? "No machine matches that name or ID."
|
|
61
|
+
: "More than one machine has that name. Use its ID.", "machine_not_unique");
|
|
62
|
+
const machine = matches[0];
|
|
63
|
+
if (!(action === "remove" ? machine.removable : machine.renameable))
|
|
64
|
+
throw new CliError("Catalyst manages this cloud machine. Only self-hosted machines can be changed here.", "managed_machine");
|
|
65
|
+
if (action === "remove" &&
|
|
66
|
+
args.flags.yes !== true &&
|
|
67
|
+
!(await (deps.confirm ?? confirmRemove)(machine.name))) {
|
|
68
|
+
ctx.stdout(args.json
|
|
69
|
+
? JSON.stringify({ removed: false, reason: "confirmation_required" })
|
|
70
|
+
: "Machine kept. Use --yes to confirm removal without a terminal.");
|
|
71
|
+
return 2;
|
|
72
|
+
}
|
|
73
|
+
const path = `/api/v1/hosts/${encodeURIComponent(machine.id)}${action === "rename" ? "/name" : ""}?account=${encodeURIComponent(cfg.account)}`;
|
|
74
|
+
const result = action === "remove"
|
|
75
|
+
? await client.deleteJson(path, { accept: [403] })
|
|
76
|
+
: await client.postJson(path, { name: parts[2] }, { accept: [403] });
|
|
77
|
+
if (result.status === 403)
|
|
78
|
+
throw new CliError("Admin access is required to change machines in this account.", "admin_required", 2, 403);
|
|
79
|
+
if (!isMachineMutation(result.body) ||
|
|
80
|
+
result.body.hostId !== machine.id ||
|
|
81
|
+
(action === "rename" && result.body.name !== parts[2]))
|
|
82
|
+
throw new CliError("The cloud did not confirm the machine change.", "shape");
|
|
83
|
+
ctx.stdout(args.json
|
|
84
|
+
? JSON.stringify(result.body)
|
|
85
|
+
: action === "remove"
|
|
86
|
+
? `Removed ${machine.name}.`
|
|
87
|
+
: `Renamed ${machine.name} to ${result.body.name}.`);
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
package/dist/legacy.js
CHANGED
|
@@ -189,8 +189,18 @@ function remove(item, run, platform, uid) {
|
|
|
189
189
|
const why = platform === "darwin"
|
|
190
190
|
? tool("launchctl", ["bootout", `gui/${uid}/${item.name}`], "launchctl bootout")
|
|
191
191
|
: tool("systemctl", ["--user", "disable", "--now", item.name], "systemctl --user disable --now");
|
|
192
|
-
if (why !== null)
|
|
193
|
-
|
|
192
|
+
if (why !== null) {
|
|
193
|
+
if (platform !== "darwin")
|
|
194
|
+
return why;
|
|
195
|
+
// A nonzero bootout may mean the exact owned service was already unloaded.
|
|
196
|
+
// Prove the scheduler domain works before interpreting its absence response.
|
|
197
|
+
const domain = run("launchctl", ["print", `gui/${uid}`]);
|
|
198
|
+
const service = run("launchctl", ["print", `gui/${uid}/${item.name}`]);
|
|
199
|
+
if (domain.status !== 0 ||
|
|
200
|
+
service.status !== 113 ||
|
|
201
|
+
!/could not find service|could not find specified service/i.test(service.stderr || service.stdout))
|
|
202
|
+
return `${why}. Check launchctl print gui/${uid}/${item.name}, fix the reported scheduler error, then run catalyst legacy --remove.`;
|
|
203
|
+
}
|
|
194
204
|
unlinkSync(item.path);
|
|
195
205
|
return null;
|
|
196
206
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const object = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2
|
+
const count = (value) => typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
|
|
3
|
+
function machine(value) {
|
|
4
|
+
return (object(value) &&
|
|
5
|
+
typeof value.id === "string" &&
|
|
6
|
+
typeof value.name === "string" &&
|
|
7
|
+
(value.ownership === "self_hosted" || value.ownership === "catalyst") &&
|
|
8
|
+
Array.isArray(value.teams) &&
|
|
9
|
+
value.teams.every((team) => typeof team === "string") &&
|
|
10
|
+
count(value.slots) &&
|
|
11
|
+
count(value.inUse) &&
|
|
12
|
+
count(value.free) &&
|
|
13
|
+
(value.lastCheckInAtMs === null || count(value.lastCheckInAtMs)) &&
|
|
14
|
+
(value.status === "live" || value.status === "stale") &&
|
|
15
|
+
typeof value.removable === "boolean" &&
|
|
16
|
+
typeof value.renameable === "boolean");
|
|
17
|
+
}
|
|
18
|
+
export function isMachineInventory(value) {
|
|
19
|
+
if (!object(value) ||
|
|
20
|
+
typeof value.account !== "string" ||
|
|
21
|
+
!count(value.readAtMs) ||
|
|
22
|
+
!Array.isArray(value.machines) ||
|
|
23
|
+
!value.machines.every(machine) ||
|
|
24
|
+
!object(value.capacity))
|
|
25
|
+
return false;
|
|
26
|
+
return (count(value.capacity.slots) &&
|
|
27
|
+
count(value.capacity.inUse) &&
|
|
28
|
+
count(value.capacity.free) &&
|
|
29
|
+
count(value.capacity.selfHostedSlots) &&
|
|
30
|
+
count(value.capacity.catalystSlots));
|
|
31
|
+
}
|
|
32
|
+
export function isMachineMutation(value) {
|
|
33
|
+
return (object(value) &&
|
|
34
|
+
value.ok === true &&
|
|
35
|
+
typeof value.hostId === "string" &&
|
|
36
|
+
(value.name === undefined || typeof value.name === "string"));
|
|
37
|
+
}
|
package/dist/onboard-capacity.js
CHANGED
|
@@ -163,7 +163,7 @@ export function onboardCapacityAdapter(input = {}) {
|
|
|
163
163
|
const expected = binding(ctx, journal), selected = selection(journal), teamId = selectedOnboardTeam(journal), repos = selectedOnboardRepositories(journal);
|
|
164
164
|
if (!expected)
|
|
165
165
|
return waiting("capacity_identity_unverified");
|
|
166
|
-
if (!teamId || repos.length
|
|
166
|
+
if (!teamId || repos.length === 0 || repos.some(r => r.teamId !== teamId))
|
|
167
167
|
return waiting("capacity_context_unverified");
|
|
168
168
|
const controller = new AbortController(), signal = external
|
|
169
169
|
? AbortSignal.any([external, controller.signal])
|
|
@@ -191,10 +191,9 @@ export function onboardCapacityAdapter(input = {}) {
|
|
|
191
191
|
const inventory = await readOnboardRepositoryInventory(ctx, journal, signal);
|
|
192
192
|
if ("reason" in inventory ||
|
|
193
193
|
!current() ||
|
|
194
|
-
!inventory.repositories.some(
|
|
195
|
-
r.
|
|
196
|
-
r.
|
|
197
|
-
r.name.toLowerCase() === repos[0].name.toLowerCase()))
|
|
194
|
+
!repos.every(selectedRepo => inventory.repositories.some(r => r.repoId === selectedRepo.repoId && r.teamId === teamId &&
|
|
195
|
+
r.owner.toLowerCase() === selectedRepo.owner.toLowerCase() &&
|
|
196
|
+
r.name.toLowerCase() === selectedRepo.name.toLowerCase())))
|
|
198
197
|
return pause("capacity_context_unverified");
|
|
199
198
|
const read = await readExistingOnboardJson(ctx, route, signal);
|
|
200
199
|
if ("reason" in read || !current())
|
|
@@ -202,7 +201,7 @@ export function onboardCapacityAdapter(input = {}) {
|
|
|
202
201
|
if (!(await liveIdentity(ctx, journal, signal)) || !current())
|
|
203
202
|
return pause("capacity_identity_unverified");
|
|
204
203
|
const observed = snapshot(read.body, ctx.now().getTime());
|
|
205
|
-
const bucket = observed?.buckets.find(
|
|
204
|
+
const bucket = observed?.buckets.find(b => b.teams.some(t => t.key === teamKey) && repos.some(r => r.repoId === b.repo)), mapped = bucket?.teams.find((t) => t.key === teamKey);
|
|
206
205
|
if (!observed || !bucket || !mapped)
|
|
207
206
|
return pause("capacity_context_unverified");
|
|
208
207
|
if (!mapped.admission || mapped.remaining === null)
|
package/dist/onboard-headless.js
CHANGED
|
@@ -29,8 +29,8 @@ const pages = (baseUrl) => {
|
|
|
29
29
|
keys: `${web}/settings/api-keys`,
|
|
30
30
|
connections: `${web}/a/account/connections`,
|
|
31
31
|
personal: `${web}/settings/connected-accounts`,
|
|
32
|
-
teams: `${web}/settings/
|
|
33
|
-
repos: `${web}/settings/
|
|
32
|
+
teams: `${web}/settings/projects`,
|
|
33
|
+
repos: `${web}/settings/projects`,
|
|
34
34
|
codingAccounts: `${web}/settings/coding-accounts`,
|
|
35
35
|
};
|
|
36
36
|
};
|
package/dist/onboard-next.js
CHANGED
|
@@ -5,6 +5,7 @@ import { githubInstallationPage } from "./onboard-permissions.js";
|
|
|
5
5
|
import { valuesFacts, valuesFix } from "./ready-copy.js";
|
|
6
6
|
/** What a person reads for a step that is not done. Each line ends in an action they can take. */
|
|
7
7
|
const REASONS = {
|
|
8
|
+
legacy_cleanup_failed: "An earlier Catalyst installation could not be removed. Run catalyst legacy --remove to see the exact item and its blocker, fix that blocker, then run catalyst setup again. Shared data folders are kept.",
|
|
8
9
|
account_identity_unverified: "Your login changed while AI accounts were checked. Run catalyst onboard again to verify your person and workspace.",
|
|
9
10
|
account_inventory_unavailable: "AI accounts could not be read. Run catalyst onboard again to retry.",
|
|
10
11
|
account_inventory_unverified: "The server did not return a fresh supported AI account list. Run catalyst onboard after the server update.",
|
|
@@ -53,6 +54,11 @@ const REASONS = {
|
|
|
53
54
|
local_sync_not_selected: "Using cloud reads. Local sync was not selected.",
|
|
54
55
|
local_sync_capability_unavailable: "Local sync was selected but could not be verified.",
|
|
55
56
|
member_scope: "Your workspace administrator handles this step.",
|
|
57
|
+
returning_workspace_move_on: "Existing projects were left in place while this computer was set up. Run catalyst ready to review their checks, or catalyst setup --team <TEAM KEY> to repair one.",
|
|
58
|
+
returning_inventory_unverified: "Setup could not verify this workspace's existing projects. Run catalyst ready to check the workspace, then run catalyst setup again. Its projects have been kept in place.",
|
|
59
|
+
returning_project_cleanup_requested: "Review the selected project's archive preview before making any change.",
|
|
60
|
+
returning_project_cleanup_unverified: "Setup could not confirm this project's archive state. Run catalyst project list to inspect it, then run catalyst setup again to review any remaining changes. Do not repeat an archive based on this message alone.",
|
|
61
|
+
returning_project_cleanup_changed: "This project's repositories changed after the preview. Run catalyst setup again to review its current project list before archiving.",
|
|
56
62
|
member_team_required: "Setup needs your Linear team to check what Catalyst can work on. Run catalyst setup again with --team <KEY>, using your team's key from Linear.",
|
|
57
63
|
onboarding_checks_pending: "Some required checks are still unverified.",
|
|
58
64
|
interrupted: "Setup paused. Run the same command to resume.",
|
|
@@ -73,7 +79,7 @@ const REASONS = {
|
|
|
73
79
|
team_read_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
74
80
|
team_create_unavailable: "The Linear team could not be created, and nothing was created. Run catalyst onboard to try again, or create the team in Linear and pick it.",
|
|
75
81
|
team_create_unverified: "Setup could not confirm whether the Linear team was created. Check Linear for it before creating it again.",
|
|
76
|
-
team_create_grant_required: "Your own Linear connection is missing or has lapsed. Reconnect it
|
|
82
|
+
team_create_grant_required: "Your own Linear connection is missing or has lapsed. Reconnect it with catalyst connections personal linear start and approve the link it prints, then run catalyst onboard. Nothing was created.",
|
|
77
83
|
team_create_identity_unverified: "Your login changed while setup was creating a team. Resume with the original workspace and person.",
|
|
78
84
|
team_choice_required: "Choose an existing team with catalyst onboard --team <team ID or key>.",
|
|
79
85
|
team_selection_unverified: "Your selected Linear team could not be verified. Run catalyst onboard --team <team ID or key> to choose again.",
|
|
@@ -101,13 +107,15 @@ const REASONS = {
|
|
|
101
107
|
verification_pending: "Setup made the change but could not confirm it yet. Run catalyst onboard again to check.",
|
|
102
108
|
automations_compatible: "Nothing to change.",
|
|
103
109
|
workflow_unavailable: "Setup could not read the team's workflow. Run catalyst onboard again to retry.",
|
|
110
|
+
capacity_context_unverified: "Setup could not confirm this project's registered repositories and its default repository for capacity. Run catalyst ready to see the project checks, then run catalyst setup again. Starting a ticket checks its own repository's admission.",
|
|
111
|
+
capacity_identity_unverified: "Setup could not confirm your workspace and administrator role for the capacity check. Run catalyst login, then run catalyst setup again.",
|
|
104
112
|
capacity_admission_unverified: "No runner is allowed to take work for this workspace yet. Enroll a self-hosted runner host, or ask Catalyst to enable cloud runners for it. Then run catalyst onboard.",
|
|
105
113
|
capacity_currently_full: "Every runner slot for this repository is in use. Run catalyst onboard again after current work finishes.",
|
|
106
114
|
capacity_unavailable: "Setup could not read runner capacity. Run catalyst onboard again to retry.",
|
|
107
115
|
runner_not_selected: "This setup does not start a runner on this machine. To start one, run catalyst onboard --runner.",
|
|
108
116
|
runner_docker_missing: "A runner needs Docker with Compose (Docker Desktop, OrbStack or Docker Engine), and setup could not reach it. Start Docker, or install it, then run catalyst onboard --runner.",
|
|
109
117
|
runner_identity_unverified: "A workspace owner or administrator enrolls a runner. Sign in as one with catalyst login, then run catalyst onboard --runner.",
|
|
110
|
-
runner_context_unverified: "Setup could not confirm
|
|
118
|
+
runner_context_unverified: "Setup could not confirm a project for this machine. Keep its existing installation and credentials. Run catalyst setup --runner --team <TEAM KEY> for the project this machine should serve.",
|
|
111
119
|
runner_directory_unavailable: "Setup could not prepare the runner folder in Catalyst's state directory. Check its permissions, then run catalyst onboard again.",
|
|
112
120
|
runner_image_unpinned: "Each runner image must be pinned as name@sha256:<digest>, and Catalyst's runner image is not public yet. Ask Catalyst support to load it here and set CATALYST_RUNNER_IMAGE to the reference they give you. A CATALYST_SUPERVISOR_IMAGE or CATALYST_WATCHDOG_IMAGE override takes the same form. Then run catalyst onboard --runner.",
|
|
113
121
|
runner_image_emulated: "A runner image here is built for another processor, and Catalyst does not run work under emulation. Ask Catalyst support for the image built for this processor, then run catalyst onboard again.",
|
|
@@ -115,9 +123,12 @@ const REASONS = {
|
|
|
115
123
|
runner_session_network_failed: "Docker could not create the catalyst-session-v1 network. Check that Docker is running, then run catalyst onboard again.",
|
|
116
124
|
runner_docker_socket_unreadable: "Setup could not read the group of the Docker socket at /var/run/docker.sock. Check that Docker is running, then run catalyst onboard again.",
|
|
117
125
|
runner_engine_unsupported: "Runner setup needs local Docker Desktop or OrbStack on Mac, or native Linux Docker at /var/run/docker.sock. Select a supported local engine, then run catalyst onboard --runner.",
|
|
126
|
+
runner_installation_unverified: "Setup found a possible existing machine installation but could not verify its Compose files and identity. Keep its files and credentials. Run docker compose -p catalyst-host logs supervisor to check it, then run catalyst setup --runner again.",
|
|
118
127
|
runner_enrollment_unavailable: "Setup could not read this workspace's runner hosts. Run catalyst onboard again to retry.",
|
|
119
128
|
runner_enrolled_for_other_team: "This runner is enrolled for another Linear team. Keep using that team, or ask your administrator to revoke the old enrollment before setting up this machine for the selected team.",
|
|
120
|
-
|
|
129
|
+
runner_enrollment_revoked: "Catalyst confirms that this machine's saved enrollment was revoked. Keep its credential and organization key. Ask your workspace administrator to review the enrollment before replacing it, then run catalyst setup --runner.",
|
|
130
|
+
runner_credential_unverified: "Setup could not read this machine's saved enrollment. Keep its credential and organization key. Run docker compose -p catalyst-host logs supervisor to check the error, then run catalyst setup --runner.",
|
|
131
|
+
runner_enrollment_stale: "Setup could not match this machine's saved enrollment with Catalyst. Keep its credential and organization key, then run catalyst setup --runner to check again.",
|
|
121
132
|
runner_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard.",
|
|
122
133
|
runner_join_token_unavailable: "Catalyst did not issue a join token for this machine. Run catalyst onboard again to retry.",
|
|
123
134
|
runner_session_network_unavailable: "Docker could not inspect the runner session network. Check engine access and run the same setup command again. Setup has not created a replacement network.",
|
|
@@ -337,13 +348,10 @@ export function onboardReasonText(step, context = {}) {
|
|
|
337
348
|
// A member is told which admin action is left and where an admin takes it.
|
|
338
349
|
if (reason === "member_scope") {
|
|
339
350
|
const key = onboardTeamKey(context.journal);
|
|
351
|
+
if (step.id === "linear.adopt")
|
|
352
|
+
return `Only a Catalyst owner or admin can set up the team's Catalyst workflow. Ask one to run catalyst team adopt ${key ?? "<TEAM KEY>"}.`;
|
|
340
353
|
const adminOnly = {
|
|
341
354
|
"linear.workspace": ["connect Linear", "/settings/connections", " and connect it there"],
|
|
342
|
-
"linear.adopt": [
|
|
343
|
-
"set up the team's Catalyst workflow",
|
|
344
|
-
key ? `/settings/linear-teams/${encodeURIComponent(key)}/adopt` : "/settings/linear-teams",
|
|
345
|
-
"",
|
|
346
|
-
],
|
|
347
355
|
};
|
|
348
356
|
const admin = adminOnly[step.id];
|
|
349
357
|
if (admin) {
|
|
@@ -374,7 +382,7 @@ export function onboardReasonText(step, context = {}) {
|
|
|
374
382
|
const where = context.baseUrl
|
|
375
383
|
? `at ${normalizeBaseUrl(context.baseUrl)}/settings/coding-accounts`
|
|
376
384
|
: "in the web app on the Settings → AI accounts page";
|
|
377
|
-
return `No AI account in this workspace can take work yet. A workspace owner or administrator
|
|
385
|
+
return `No AI account in this workspace can take work yet. Run catalyst accounts to see which one needs work. A workspace owner or administrator fixes it ${where}, since no command does that yet: one may need its key replaced, or may have reached its usage limit. Then run catalyst onboard.`;
|
|
378
386
|
}
|
|
379
387
|
if (reason === "account_enrollment_required") {
|
|
380
388
|
const where = context.baseUrl
|
|
@@ -425,7 +433,7 @@ export function onboardReasonText(step, context = {}) {
|
|
|
425
433
|
const where = context.baseUrl
|
|
426
434
|
? `at ${normalizeBaseUrl(context.baseUrl)}/settings/account-keys`
|
|
427
435
|
: "on the web app's Account API keys page";
|
|
428
|
-
return `The runner is enrolled, but it takes no work without an organization key. A workspace owner or administrator
|
|
436
|
+
return `The runner is enrolled, but it takes no work without an organization key. A workspace owner or administrator runs catalyst onboard --runner on this machine; it mints the key and stores it for the runner. If their cloud cannot mint one yet, they create one with the mirror:read, mirror:write and mirror:feed scopes ${where}, save it in a file only they can read, and run catalyst onboard --runner with CATALYST_RUNNER_ORG_KEY_FILE set to that file.`;
|
|
429
437
|
}
|
|
430
438
|
if (reason === "runner_host_not_ready") {
|
|
431
439
|
const failing = step.evidence?.failing;
|