@catalyst-cloud/cli 0.14.10 → 0.15.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.
- package/CHANGELOG.md +9 -0
- package/README.md +41 -0
- package/dist/args.js +34 -2
- package/dist/capabilities.js +2 -1
- package/dist/cli-update-notice.js +76 -0
- package/dist/cli.js +263 -101
- package/dist/oauth.js +48 -18
- package/dist/onboard-accounts.js +34 -5
- package/dist/onboard-automation-management.js +27 -9
- package/dist/onboard-capacity.js +10 -1
- package/dist/onboard-existing.js +132 -31
- package/dist/onboard-github.js +171 -6
- package/dist/onboard-headless.js +647 -0
- package/dist/onboard-interrupts.js +41 -0
- package/dist/onboard-next.js +215 -0
- package/dist/onboard-permissions.js +100 -0
- package/dist/onboard-personal.js +46 -10
- package/dist/onboard-runner-cloud.js +213 -0
- package/dist/onboard-runner-egress.js +67 -0
- package/dist/onboard-runner.js +780 -0
- package/dist/onboard-runtime.js +48 -8
- package/dist/onboard-standalone-copy.js +110 -0
- package/dist/onboard-team-create.js +267 -0
- package/dist/onboard-ui.js +623 -29
- package/dist/onboard-workflow.js +311 -168
- package/dist/onboard-workspace.js +52 -5
- package/dist/onboard.js +359 -212
- package/dist/setup-events.js +100 -0
- package/dist/setup-onboard-copy.js +438 -0
- package/dist/setup-prompt-frame.js +94 -0
- package/dist/setup-prompts.js +108 -0
- package/dist/setup-render.js +339 -0
- package/dist/setup-signin.js +187 -0
- package/dist/setup.js +426 -0
- package/package.json +7 -3
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +3 -2
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +1 -1
- package/vendor/README.md +4 -0
- package/vendor/self-host/compose.yaml +110 -0
- package/vendor/self-host/linux-session-egress/catalyst-session-egress-attest.service +14 -0
- package/vendor/self-host/linux-session-egress/catalyst-session-egress-attest.timer +10 -0
- package/vendor/self-host/linux-session-egress/catalyst-session-egress.service +19 -0
- package/vendor/self-host/linux-session-egress/session-egress.py +291 -0
- package/vendor/self-host/provenance.json +11 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.0
|
|
4
|
+
|
|
5
|
+
- Installation and onboarding share a plan and progress display. Browser approvals stay in the same flow, and resumed runs recheck saved choices against current access.
|
|
6
|
+
- Linear permission updates open an explicit review page. Opening that page does not change the connection.
|
|
7
|
+
- Onboarding can choose an existing Linear team or create one when the connected person has permission. Missing permission names the action needed to continue.
|
|
8
|
+
- Onboarding can configure this machine as a Catalyst runner. It checks Docker, image compatibility, enrollment, capacity and team admission, and reports prerequisites that still need attention.
|
|
9
|
+
- Headless and JSON runs avoid interactive prompts and report missing inputs. Readiness reports distinguish completed setup from evidence of a first ticket running.
|
|
10
|
+
- The CLI uses SDK 0.14.1, which accepts the current cloud contract while refusing unknown write routes.
|
|
11
|
+
|
|
3
12
|
## 0.14.10
|
|
4
13
|
|
|
5
14
|
- `catalyst onboard` refreshes its sign-in when it starts and between steps. A run started a while after signing in no longer asks for the browser sign-in again, and a long run of approvals no longer stops with "Renew your login".
|
package/README.md
CHANGED
|
@@ -126,6 +126,47 @@ catalyst ready --onboarding --json
|
|
|
126
126
|
|
|
127
127
|
The dry run writes no files. `--only <step>` runs one step and reports its scope separately from full onboarding. Exit 10 means a failed step, 11 means waiting, and 12 means a guard refused the action. The private receipt is `install/last-run.json` under your machine's Catalyst state directory; credentials stay in the login config. A fake HOME reports old services and keeps them intact.
|
|
128
128
|
|
|
129
|
+
### Headless setup (CI, image builds, agent VMs)
|
|
130
|
+
|
|
131
|
+
`catalyst onboard --headless` (or `CATALYST_ONBOARD_HEADLESS=1`) is for a machine with no person at it. It assumes every browser approval already happened: the person's key exists, and Linear and GitHub are connected for the workspace and the person. It never prompts, never opens a browser and never starts the device sign-in. Every input comes from a flag, an environment variable or a file:
|
|
132
|
+
|
|
133
|
+
| Input | Flag | Environment |
|
|
134
|
+
|--|--|--|
|
|
135
|
+
| Personal key (Settings → API keys) | `--key-file <path>` | `CATALYST_CLOUD_TOKEN`, or `CATALYST_CLOUD_TOKEN_FILE` |
|
|
136
|
+
| Linear team | `--team <ID or key>` | `CATALYST_ONBOARD_TEAM` |
|
|
137
|
+
| Repositories | `--repo <owner/name>`, repeatable | `CATALYST_ONBOARD_REPOS` (comma or space separated) |
|
|
138
|
+
| Coding account | `--coding-account <slot>` (`catalyst accounts` lists them) | `CATALYST_ONBOARD_CODING_ACCOUNT` |
|
|
139
|
+
| Runner opt-in | `--runner yes\|no` | `CATALYST_ONBOARD_RUNNER` |
|
|
140
|
+
| Cloud | `--base-url <url>` | `CATALYST_CLOUD_BASE_URL` |
|
|
141
|
+
|
|
142
|
+
A flag wins over its variable. A machine that already has a saved login needs no key. The key is never accepted on the command line: `--key` under `--headless` exits 12, because other processes can read argv. A key file must be a regular file, owned by this user or root, that other users cannot write. A key for another person than the machine's saved login or setup record exits 12 and leaves the saved login as it was. With `--dry-run`, nothing is saved and no network call is made. The steps run without the key in their environment. The key is saved in the login config (mode 0600), the same as `catalyst login --key`, and it never appears in the output, the log or the receipt. In an image build, run setup when the container starts, or remove `~/.config/catalyst-cloud/customer.json` in the same step, so the key does not stay in a layer.
|
|
143
|
+
|
|
144
|
+
Before any network call, the run checks that every input is present. Each missing input is named with its flag and variable, and the run exits 11 at once. A browser approval that does not exist yet is never started. Its step waits with a named reason, and the item's `url` is the page that fixes it:
|
|
145
|
+
|
|
146
|
+
| Step | Reason | Page |
|
|
147
|
+
|--|--|--|
|
|
148
|
+
| Linear for the workspace | `linear_workspace_grant_missing` | Settings → Connections |
|
|
149
|
+
| GitHub App for the workspace | `github_app_grant_missing` | Settings → Connections |
|
|
150
|
+
| The person's Linear | `linear_personal_grant_missing` | Settings → Connected accounts |
|
|
151
|
+
| The person's GitHub | `github_personal_grant_missing` | Settings → Connected accounts |
|
|
152
|
+
|
|
153
|
+
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.
|
|
154
|
+
|
|
155
|
+
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.
|
|
156
|
+
|
|
157
|
+
| Exit | Meaning |
|
|
158
|
+
|--|--|
|
|
159
|
+
| 0 | Ready for work. Optional steps can still be listed under `deferred`. A `--dry-run` exits 0 for a plan; its verdict stays `not-ready`. |
|
|
160
|
+
| 10 | A step failed. See `failed`. |
|
|
161
|
+
| 11 | Something is missing: an input, a browser approval or a step that waits. See `missing`. |
|
|
162
|
+
| 12 | Refused: a key on the command line, a key file other users can write or own, a host key instead of a personal key, a key for another person than the saved login, a malformed runner choice or an unknown option. See `refused`. |
|
|
163
|
+
|
|
164
|
+
```sh
|
|
165
|
+
# CI or an agent VM: the key comes from the secret store as an environment variable.
|
|
166
|
+
CATALYST_CLOUD_TOKEN="$CATALYST_KEY" catalyst onboard --headless --json \
|
|
167
|
+
--team ENG --repo acme/api --coding-account claude-one --runner no
|
|
168
|
+
```
|
|
169
|
+
|
|
129
170
|
Local sync is optional. Include it in the plan with `--local-sync` when you need local SQL or sustained local reads. A resume keeps that choice. Readiness distinguishes failed checks from missing evidence and reports observed project work separately from unfinished setup.
|
|
130
171
|
|
|
131
172
|
## Requirements
|
package/dist/args.js
CHANGED
|
@@ -41,6 +41,7 @@ export const FLAG_TABLES = {
|
|
|
41
41
|
},
|
|
42
42
|
install: {},
|
|
43
43
|
onboard: {
|
|
44
|
+
verbose: { value: false, help: "show granted permissions for connected steps in interactive setup" },
|
|
44
45
|
team: {
|
|
45
46
|
value: true,
|
|
46
47
|
help: "explicitly select an existing Linear team by ID or unique key",
|
|
@@ -54,6 +55,15 @@ export const FLAG_TABLES = {
|
|
|
54
55
|
value: false,
|
|
55
56
|
help: "include the optional local sync setup in this plan",
|
|
56
57
|
},
|
|
58
|
+
runner: {
|
|
59
|
+
value: false,
|
|
60
|
+
optionalValue: true,
|
|
61
|
+
help: "run Catalyst's work on this machine; with --headless, supply yes|no (or CATALYST_ONBOARD_RUNNER)",
|
|
62
|
+
},
|
|
63
|
+
"no-runner": {
|
|
64
|
+
value: false,
|
|
65
|
+
help: "do not run Catalyst's work on this machine, and do not ask",
|
|
66
|
+
},
|
|
57
67
|
"resume-from": {
|
|
58
68
|
value: true,
|
|
59
69
|
help: "resume at a verified onboarding step",
|
|
@@ -64,6 +74,18 @@ export const FLAG_TABLES = {
|
|
|
64
74
|
value: false,
|
|
65
75
|
help: "print the plan without taking the lock or changing files",
|
|
66
76
|
},
|
|
77
|
+
headless: {
|
|
78
|
+
value: false,
|
|
79
|
+
help: "never prompt or open a browser; every input by flag, env or file; exit 11 names what is missing (or CATALYST_ONBOARD_HEADLESS=1)",
|
|
80
|
+
},
|
|
81
|
+
"key-file": {
|
|
82
|
+
value: true,
|
|
83
|
+
help: "headless: read the personal key from this file (or CATALYST_CLOUD_TOKEN_FILE, or the key in CATALYST_CLOUD_TOKEN); never the key itself on the command line",
|
|
84
|
+
},
|
|
85
|
+
"coding-account": {
|
|
86
|
+
value: true,
|
|
87
|
+
help: "verify this coding-account slot (catalyst accounts lists them; or CATALYST_ONBOARD_CODING_ACCOUNT)",
|
|
88
|
+
},
|
|
67
89
|
},
|
|
68
90
|
status: {},
|
|
69
91
|
notice: {},
|
|
@@ -435,8 +457,9 @@ export const FLAG_TABLES = {
|
|
|
435
457
|
},
|
|
436
458
|
};
|
|
437
459
|
export const VERB_USAGE = {
|
|
460
|
+
setup: "setup --engine <file> --engine-sha256 <hex> [-- <install options>] (run by install.sh; install.sh --help lists the options)",
|
|
438
461
|
login: "login [--base-url <url>] [--start-replica] (keyless; or --key <personal-key> / CATALYST_CLOUD_TOKEN)",
|
|
439
|
-
onboard: "onboard [--team <ID|key>] [--repo <owner/name>]... [--resume-from <step>] [--only <step>] [--local-sync] [--yes] [--dry-run] [--json]",
|
|
462
|
+
onboard: "onboard [--team <ID|key>] [--repo <owner/name>]... [--coding-account <slot>] [--resume-from <step>] [--only <step>] [--local-sync] [--runner|--no-runner] [--yes] [--dry-run] [--json] | onboard --headless [--key-file <path>] [--team ...] [--repo ...]... [--coding-account ...] [--runner yes|no] [--json]",
|
|
440
463
|
install: "install [--skills-dir <dir>] [--force]",
|
|
441
464
|
status: "status",
|
|
442
465
|
notice: "notice",
|
|
@@ -512,7 +535,16 @@ export function parseArgs(argv) {
|
|
|
512
535
|
? `unknown option for ${out.command}: --${name}`
|
|
513
536
|
: `unknown option: --${name}`);
|
|
514
537
|
}
|
|
515
|
-
if (spec.
|
|
538
|
+
if (spec.optionalValue) {
|
|
539
|
+
const next = argv[i + 1];
|
|
540
|
+
if (inlineValue !== undefined)
|
|
541
|
+
out.flags[name] = inlineValue;
|
|
542
|
+
else if (next !== undefined && !next.startsWith("-"))
|
|
543
|
+
out.flags[name] = argv[++i];
|
|
544
|
+
else
|
|
545
|
+
out.flags[name] = true;
|
|
546
|
+
}
|
|
547
|
+
else if (spec.value) {
|
|
516
548
|
const v = inlineValue ?? requireValue(argv, ++i, `--${name}`);
|
|
517
549
|
if (spec.repeat) {
|
|
518
550
|
const prev = out.flags[name];
|
package/dist/capabilities.js
CHANGED
|
@@ -5,7 +5,8 @@ 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: "
|
|
8
|
+
{ verb: "setup", does: "show install.sh's plan, progress and sign-in in this terminal, and ask its questions", needs: "member", routes: [], since: "0.15.0", engineProtocol: 1 },
|
|
9
|
+
{ verb: "onboard", does: "run and resume Catalyst setup with a saved step record", needs: "member", routes: [], since: "0.14.0", bootstrapHandoff: true, headless: true },
|
|
9
10
|
{ verb: "status", does: "say whether this machine is connected, as whom, and to which cloud", needs: "member", routes: [], since: "0.1.0" },
|
|
10
11
|
{ verb: "me", does: "read the person's seat, role and Linear identity", needs: "member", routes: [], since: "0.1.0" },
|
|
11
12
|
{ verb: "ready", does: "read the machine and the workspace's per-project readiness in one verdict", needs: "member", routes: [], since: "0.1.0" },
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { lstatSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { configDirFor, publishedCachePathFor } from "./config.js";
|
|
5
|
+
import { latestPublishedVersion } from "./published.js";
|
|
6
|
+
import { semverOlder } from "./semver.js";
|
|
7
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
8
|
+
const RELEASE = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
|
|
9
|
+
/** A notice never installs anything, shares setup's frame or writes machine-readable stdout. */
|
|
10
|
+
export async function maybePrintPublishedUpdateNotice(ctx, options) {
|
|
11
|
+
if (!options.interactive ||
|
|
12
|
+
options.json ||
|
|
13
|
+
options.command === "setup" ||
|
|
14
|
+
options.command === "onboard")
|
|
15
|
+
return;
|
|
16
|
+
const directory = configDirFor(ctx.home);
|
|
17
|
+
const state = join(directory, "update-notice.json");
|
|
18
|
+
const published = publishedCachePathFor(ctx.home);
|
|
19
|
+
const now = ctx.now();
|
|
20
|
+
if (!Number.isFinite(now.getTime()))
|
|
21
|
+
return;
|
|
22
|
+
try {
|
|
23
|
+
// Optional metadata must not follow a user-created link or replace a special file.
|
|
24
|
+
for (const path of [
|
|
25
|
+
join(ctx.home, ".config"),
|
|
26
|
+
directory,
|
|
27
|
+
state,
|
|
28
|
+
published,
|
|
29
|
+
`${published}.tmp`,
|
|
30
|
+
]) {
|
|
31
|
+
try {
|
|
32
|
+
const info = lstatSync(path);
|
|
33
|
+
if (info.isSymbolicLink() || (!info.isDirectory() && !info.isFile()))
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error.code !== "ENOENT")
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const previous = JSON.parse(readFileSync(state, "utf8"));
|
|
43
|
+
if (previous &&
|
|
44
|
+
typeof previous === "object" &&
|
|
45
|
+
"checkedAt" in previous &&
|
|
46
|
+
typeof previous.checkedAt === "string") {
|
|
47
|
+
const age = now.getTime() - Date.parse(previous.checkedAt);
|
|
48
|
+
if (age >= 0 && age < DAY_MS)
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
/* Missing or malformed optional metadata can be refreshed. */
|
|
54
|
+
}
|
|
55
|
+
mkdirSync(directory, { recursive: true });
|
|
56
|
+
const temporary = `${state}.${randomUUID()}.tmp`;
|
|
57
|
+
try {
|
|
58
|
+
writeFileSync(temporary, JSON.stringify({ version: 1, checkedAt: now.toISOString() }) + "\n", { mode: 0o600, flag: "wx" });
|
|
59
|
+
renameSync(temporary, state);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
rmSync(temporary, { force: true });
|
|
63
|
+
}
|
|
64
|
+
// Record the attempt before the bounded lookup, including failures, so offline commands do
|
|
65
|
+
// not repeatedly contact npm. Reuse ready's anonymous request and version cache.
|
|
66
|
+
const reply = await latestPublishedVersion(ctx);
|
|
67
|
+
if (reply.latest &&
|
|
68
|
+
RELEASE.test(reply.latest) &&
|
|
69
|
+
semverOlder(options.version, reply.latest)) {
|
|
70
|
+
ctx.stderr(`Catalyst ${reply.latest} is out. Update with: npm install -g @catalyst-cloud/cli@latest`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
/* An optional update notice must never stop the requested command. */
|
|
75
|
+
}
|
|
76
|
+
}
|