@catalyst-cloud/cli 0.14.6 → 0.14.8
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 +14 -0
- package/README.md +7 -5
- package/dist/args.js +68 -18
- package/dist/browser.js +37 -3
- package/dist/cli.js +58 -12
- package/dist/config.js +17 -5
- package/dist/consent-browser.js +58 -0
- package/dist/daemon-authority-lifetime.js +171 -0
- package/dist/daemon-authority.js +216 -0
- package/dist/daemon-cache.js +348 -0
- package/dist/daemon-store.js +370 -0
- package/dist/daemon-supervisor.js +460 -0
- package/dist/env/onboard-draft-store.js +250 -0
- package/dist/env/onboard-draft.js +33 -0
- package/dist/env/selected-import-writer.js +163 -0
- package/dist/env/settings-authority.js +205 -0
- package/dist/events-cloud-tail.js +146 -0
- package/dist/events-cloud.js +619 -0
- package/dist/events.js +186 -31
- package/dist/oauth.js +129 -34
- package/dist/onboard-accounts.js +259 -0
- package/dist/onboard-automation-management.js +15 -0
- package/dist/onboard-bootstrap.js +162 -0
- package/dist/onboard-capabilities.js +15 -7
- package/dist/onboard-capacity.js +226 -0
- package/dist/onboard-existing.js +106 -32
- package/dist/onboard-file-snapshot.js +45 -0
- package/dist/onboard-github.js +14 -3
- package/dist/onboard-login-candidate.js +261 -0
- package/dist/onboard-next.js +139 -0
- package/dist/onboard-personal.js +14 -3
- package/dist/onboard-projects.js +350 -0
- package/dist/onboard-repositories.js +146 -37
- package/dist/onboard-runtime.js +108 -13
- package/dist/onboard-settings-context.js +141 -0
- package/dist/onboard-settings-import.js +127 -0
- package/dist/onboard-settings.js +234 -0
- package/dist/onboard-ui.js +85 -59
- package/dist/onboard-workflow.js +316 -0
- package/dist/onboard-workspace.js +14 -3
- package/dist/onboard.js +422 -39
- package/dist/owned-fetch.js +182 -0
- package/dist/pagination.js +13 -1
- package/dist/query.js +104 -31
- package/dist/ready.js +143 -35
- package/dist/replica.js +11 -4
- package/dist/repository-id.js +5 -0
- package/dist/sdk.js +33 -0
- package/dist/watch.js +2 -2
- package/package.json +3 -2
- package/skills/catalyst-github/SKILL.md +4 -2
- package/skills/catalyst-github/references/is-it-mergeable.md +1 -1
- package/skills/catalyst-github/references/local-replica.md +78 -0
- package/skills/catalyst-github/references/reading-from-the-cloud.md +24 -0
- package/skills/catalyst-github/references/what-a-pr-accumulates.md +1 -1
- package/skills/catalyst-github/scripts/is-it-mergeable.mjs +210 -45
- package/skills/catalyst-github/scripts/read-pr.mjs +51 -15
- package/skills/catalyst-linear/SKILL.md +5 -3
- package/skills/catalyst-linear/references/local-replica.md +91 -0
- package/skills/catalyst-linear/references/reading-a-ticket.md +3 -3
- package/skills/catalyst-linear/references/reading-from-the-cloud.md +38 -0
- package/skills/catalyst-linear/scripts/read-ticket.mjs +37 -12
- package/skills/catalyst-onboard/SKILL.md +6 -4
- package/skills/catalyst-onboard/references/local-replica.md +40 -0
- package/skills/catalyst-onboard/references/local-sync.md +8 -4
- package/skills/catalyst-onboard/scripts/local-sync.mjs +2 -2
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +1 -1
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +4 -2
- package/skills/whats-happening/references/local-replica.md +51 -0
- package/skills/whats-happening/references/reading-from-the-cloud.md +21 -0
- package/skills/whats-happening/references/status-reply.md +1 -1
- package/skills/whats-happening/scripts/snapshot.mjs +104 -26
- package/vendor/settings/index.d.ts +4 -0
- package/vendor/settings/index.js +2937 -0
- package/vendor/settings/provenance.json +148 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.14.8
|
|
4
|
+
|
|
5
|
+
- Onboarding on a computer with no browser, such as a Docker container, prints the Catalyst page that finishes each Linear and GitHub approval and keeps waiting, instead of stopping.
|
|
6
|
+
- Onboarding accepts cloud repository IDs such as `tenant-9:owner__repo`, so registering a project no longer fails on them.
|
|
7
|
+
- When the sign-in expires during a provider approval, onboarding stops and says to run `catalyst login`, instead of waiting for the approval to time out.
|
|
8
|
+
- Steps that wait name the next action and the page to use, and the end of a run lists what is left.
|
|
9
|
+
- `catalyst login` says the browser opened only when it did.
|
|
10
|
+
- Onboarding finishes once everything work needs is in place. Repository settings, values and the daily update are listed as next steps instead of blocking.
|
|
11
|
+
|
|
12
|
+
## 0.14.7
|
|
13
|
+
|
|
14
|
+
- Setup uses cloud reads by default and offers local sync as an option. Onboarding preserves verified account and repository choices when it resumes.
|
|
15
|
+
- Settings drafts stay private for review; keeping a draft does not approve it or import values.
|
|
16
|
+
|
|
3
17
|
## 0.14.6
|
|
4
18
|
|
|
5
19
|
- A current Catalyst data folder no longer triggers a legacy-runtime cleanup warning. Legacy checks and removal keep current shared data and report it separately.
|
package/README.md
CHANGED
|
@@ -126,7 +126,7 @@ 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
|
-
Local sync is optional. Include it in the plan with `--local-sync` when you need local SQL or
|
|
129
|
+
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
130
|
|
|
131
131
|
## Requirements
|
|
132
132
|
|
|
@@ -153,12 +153,14 @@ Nothing, by default. After `login`, every read, write, ask and explanation goes
|
|
|
153
153
|
| process | needed for | what it holds on disk | lifetime |
|
|
154
154
|
| --- | --- | --- | --- |
|
|
155
155
|
| none | every read, write, ask and explain | `customer.json` and `contract.json` | the default after login |
|
|
156
|
-
| `catalyst replica start` | local SQL, cheap repeated reads, and local event
|
|
156
|
+
| `catalyst replica start` | local SQL, cheap repeated reads, and a shared local event cache read with `events ... --from-cache` | one SQLite file plus a bounded event cache under `$XDG_STATE_HOME/catalyst/events/` (fallback `~/.local/state/catalyst/events/`) | one long-running process; foreground by default, `--detach` writes a pidfile beside the database and returns; `login --start-replica` does the same at the end of login |
|
|
157
157
|
| `catalyst watch` | a project owner reacting to its scope | one cursor file (`~/.config/catalyst-cloud/watch-cursor.json`) stamped with the account | lives inside the session that armed it; exits with it |
|
|
158
158
|
|
|
159
|
-
|
|
159
|
+
CLI and skill reads use the cloud API by default, including on a machine with a fresh local replica. No background writer or local database is needed. `catalyst query ... --source replica` explicitly chooses the optional local view; the first stderr line names the source. Local sync does not promise offline browsing.
|
|
160
160
|
|
|
161
|
-
`catalyst
|
|
161
|
+
For a local view, `catalyst replica status` checks the writer, heartbeat and cursor. It exits `0` for fresh, `1` for present but stale, `2` for not connected, `3` for absent. `replica status --probe` compares its cursor with the cloud head. These diagnostics do not change the default read source, and an absent replica does not make `catalyst ready` fail.
|
|
162
|
+
|
|
163
|
+
`catalyst events tail`, `wait-for`, `query` and `status` read from the cloud by default, with no local file. `events tail` prints one JSON line per new event and stays in the foreground; `events wait-for --ticket ... --timeout ...` prints the first match and exits 0, or exits 1 when none arrives in time; `events query` reads one page of filtered history (`--ticket`, `--type`, `--limit` up to 200, `--order desc|asc`, `--before`, `--after`); `events status` names the cloud head. Add `--from-cache` to read this machine's local event cache instead, which pays off when several agents on one machine share one cloud connection. `catalyst events status --from-cache --probe` checks the cache's own cursor against the cloud head; `replica status --probe` checks the separate entity-replica cursor. A fresh replica does not prove event freshness. The replica process is the single writer and reports an event-sync failure without terminating a healthy entity replica. The event cache keeps closed daily segments for at most seven days or 256 MiB per account and reports an explicit gap when a requested sequence has retired.
|
|
162
164
|
|
|
163
165
|
The replica is a Node process, not a service: the supported path is the plain command, and `skills/catalyst-onboard/references/local-sync.md` gives launchd and systemd examples for people who want the writer to survive a reboot.
|
|
164
166
|
|
|
@@ -170,7 +172,7 @@ The replica is a Node process, not a service: the supported path is the plain co
|
|
|
170
172
|
| `whats-happening` | "What's happening? Why is that stuck? How does this work? How does it prioritise?" | The desk for your account: reads the contract, what is running and queued, the eligibility explainer, the coding accounts and the open asks, and answers in one reply with ticket ids. Carries the facts behind the answers: the ladder, the stage map, failures, parks and holds, the queue order and every reason a ticket is excluded. Routes work to a project owner and decisions to `what-needs-me`. | [`SKILL.md`](skills/whats-happening/SKILL.md) |
|
|
171
173
|
| `what-needs-me` | "What needs me? What am I blocking?" | The human's decision inbox, ranked by how much open work each ask holds, and the one way an agent raises a decision on their behalf: files an ask through the cloud's ask route with the account's own template and records the answer so the held work releases. | [`SKILL.md`](skills/what-needs-me/SKILL.md) |
|
|
172
174
|
| `run-this-project` | "Run this project for me. Own it until it closes." | Single-threaded owner of one project: subscribes to the account's event stream for its scope, 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. Never polls. | [`SKILL.md`](skills/run-this-project/SKILL.md) |
|
|
173
|
-
| `catalyst-linear` | "Show me the ticket, the history, what Catalyst wrote on it." | Reads a ticket with its comments, relations, labels, linked pull requests and agent sessions inline, from the
|
|
175
|
+
| `catalyst-linear` | "Show me the ticket, the history, what Catalyst wrote on it." | Reads a ticket with its comments, relations, labels, linked pull requests and agent sessions inline, from the cloud API by default, with explicit local opt-in and the source named; writes comments, card moves, labels and new tickets as the app actor; knows what a ticket accumulates as Catalyst works it. | [`SKILL.md`](skills/catalyst-linear/SKILL.md) |
|
|
174
176
|
| `catalyst-github` | "Show me the PR, the checks, the review, the queue." | A ticket's pull request with its checks, reviews and review threads; whether it is mergeable under the repository's policy; what a PR accumulates as the ticket moves (the branch, the draft, the rewrite, the force-pushes, the labels, the queue). | [`SKILL.md`](skills/catalyst-github/SKILL.md) |
|
|
175
177
|
| `unstick` | "Why is this parked? Unpark it. Get things flowing again." | Reads why a ticket is not running and every park or hold on it, decides whether the recorded cause is fixed, previews the release and releases it the right way from your own login — or one failure class across a team — and raises an ask only for what a person has to do. | [`SKILL.md`](skills/unstick/SKILL.md) |
|
|
176
178
|
| `what-this-repo-needs` | "What env vars does this repo need? What belongs in its environment declaration?" | Scans a repository offline — no login, no network — and lists the environment variable names it needs, grouped build/test, deploy-only and bindings, each with where it was found, what uses it, and where a local value would come from. Never reads or prints a value. Also checks TOML syntax and the environment variable table in `.catalyst/catalyst.toml`. | [`SKILL.md`](skills/what-this-repo-needs/SKILL.md) |
|
package/dist/args.js
CHANGED
|
@@ -41,13 +41,29 @@ export const FLAG_TABLES = {
|
|
|
41
41
|
},
|
|
42
42
|
install: {},
|
|
43
43
|
onboard: {
|
|
44
|
-
team: {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
team: {
|
|
45
|
+
value: true,
|
|
46
|
+
help: "explicitly select an existing Linear team by ID or unique key",
|
|
47
|
+
},
|
|
48
|
+
repo: {
|
|
49
|
+
value: true,
|
|
50
|
+
repeat: true,
|
|
51
|
+
help: "explicitly select an accessible repository as owner/name (repeatable)",
|
|
52
|
+
},
|
|
53
|
+
"local-sync": {
|
|
54
|
+
value: false,
|
|
55
|
+
help: "include the optional local sync setup in this plan",
|
|
56
|
+
},
|
|
57
|
+
"resume-from": {
|
|
58
|
+
value: true,
|
|
59
|
+
help: "resume at a verified onboarding step",
|
|
60
|
+
},
|
|
48
61
|
only: { value: true, help: "run only the named onboarding step" },
|
|
49
62
|
yes: { value: false, help: "accept the plan's default choices" },
|
|
50
|
-
"dry-run": {
|
|
63
|
+
"dry-run": {
|
|
64
|
+
value: false,
|
|
65
|
+
help: "print the plan without taking the lock or changing files",
|
|
66
|
+
},
|
|
51
67
|
},
|
|
52
68
|
status: {},
|
|
53
69
|
notice: {},
|
|
@@ -67,7 +83,7 @@ export const FLAG_TABLES = {
|
|
|
67
83
|
since: { value: true, help: "changes: the cursor to read after" },
|
|
68
84
|
source: {
|
|
69
85
|
value: true,
|
|
70
|
-
help: "replica | api (default: replica
|
|
86
|
+
help: "replica | api (default: api; replica is an explicit local opt-in)",
|
|
71
87
|
},
|
|
72
88
|
ticket: { value: true, help: "pulls: only PRs linked to this ticket" },
|
|
73
89
|
all: {
|
|
@@ -91,27 +107,39 @@ export const FLAG_TABLES = {
|
|
|
91
107
|
},
|
|
92
108
|
},
|
|
93
109
|
events: {
|
|
110
|
+
"from-cache": {
|
|
111
|
+
value: false,
|
|
112
|
+
help: "read the local event cache instead of the cloud (opt-in local sync)",
|
|
113
|
+
},
|
|
94
114
|
probe: {
|
|
95
115
|
value: false,
|
|
96
|
-
help: "status: compare the local event cursor with the cloud event head",
|
|
116
|
+
help: "status --from-cache: compare the local event cursor with the cloud event head",
|
|
97
117
|
},
|
|
98
118
|
type: { value: true, help: "exact event type" },
|
|
99
119
|
ticket: {
|
|
100
120
|
value: true,
|
|
101
|
-
help: "ticket identifier found in the event
|
|
121
|
+
help: "ticket identifier found in the event",
|
|
102
122
|
},
|
|
103
123
|
after: {
|
|
104
124
|
value: true,
|
|
105
|
-
help: "event sequence to read after (tail
|
|
125
|
+
help: "event sequence to read after (tail and wait-for default to new events only)",
|
|
126
|
+
},
|
|
127
|
+
before: {
|
|
128
|
+
value: true,
|
|
129
|
+
help: "query: event sequence to read before (newest-first paging)",
|
|
130
|
+
},
|
|
131
|
+
order: { value: true, help: "query: desc (newest first, default) or asc" },
|
|
132
|
+
limit: {
|
|
133
|
+
value: true,
|
|
134
|
+
help: "query: maximum matching events (default 50; at most 200 from the cloud)",
|
|
106
135
|
},
|
|
107
|
-
limit: { value: true, help: "query: maximum matching events (default 50)" },
|
|
108
136
|
timeout: {
|
|
109
137
|
value: true,
|
|
110
138
|
help: "wait-for: bounded wait in seconds (default 300)",
|
|
111
139
|
},
|
|
112
140
|
directory: {
|
|
113
141
|
value: true,
|
|
114
|
-
help: "event cache directory (default: SDK XDG path)",
|
|
142
|
+
help: "with --from-cache: event cache directory (default: SDK XDG path)",
|
|
115
143
|
},
|
|
116
144
|
},
|
|
117
145
|
explain: {
|
|
@@ -235,8 +263,14 @@ export const FLAG_TABLES = {
|
|
|
235
263
|
role: { value: true, help: "accept: the role recording the answer" },
|
|
236
264
|
},
|
|
237
265
|
ready: {
|
|
238
|
-
onboarding: {
|
|
239
|
-
|
|
266
|
+
onboarding: {
|
|
267
|
+
value: false,
|
|
268
|
+
help: "check current onboarding evidence and observed project work",
|
|
269
|
+
},
|
|
270
|
+
"local-sync": {
|
|
271
|
+
value: false,
|
|
272
|
+
help: "include the selected local sync mode",
|
|
273
|
+
},
|
|
240
274
|
offline: {
|
|
241
275
|
value: false,
|
|
242
276
|
help: "skip the published-release check (no network)",
|
|
@@ -244,13 +278,29 @@ export const FLAG_TABLES = {
|
|
|
244
278
|
},
|
|
245
279
|
accounts: {},
|
|
246
280
|
env: {
|
|
247
|
-
root: {
|
|
248
|
-
|
|
249
|
-
|
|
281
|
+
root: {
|
|
282
|
+
value: true,
|
|
283
|
+
help: "draft: repository directory (default: current directory)",
|
|
284
|
+
},
|
|
285
|
+
write: {
|
|
286
|
+
value: false,
|
|
287
|
+
help: "draft: write a new .catalyst/catalyst.toml; refuse to overwrite",
|
|
288
|
+
},
|
|
289
|
+
diff: {
|
|
290
|
+
value: false,
|
|
291
|
+
help: "draft: show the generated TOML as an additions-only diff",
|
|
292
|
+
},
|
|
250
293
|
},
|
|
251
294
|
var: {
|
|
252
|
-
repo: {
|
|
253
|
-
|
|
295
|
+
repo: {
|
|
296
|
+
value: true,
|
|
297
|
+
help: "repository scope (owner/name); omit for tenant scope",
|
|
298
|
+
},
|
|
299
|
+
names: {
|
|
300
|
+
value: true,
|
|
301
|
+
repeat: true,
|
|
302
|
+
help: "import: only send these names (repeatable or comma-separated)",
|
|
303
|
+
},
|
|
254
304
|
},
|
|
255
305
|
team: {
|
|
256
306
|
all: { value: false, help: "check every team, one request at a time" },
|
package/dist/browser.js
CHANGED
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
// this never throws into the login flow — the caller wraps it, and a spawn failure is swallowed here.
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
5
5
|
/** The platform's "open this URL" command. `open` on macOS, `start` via cmd on Windows, else xdg-open. */
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
function opener(url, platform = process.platform) {
|
|
7
|
+
return platform === "darwin"
|
|
8
8
|
? ["open", [url]]
|
|
9
|
-
:
|
|
9
|
+
: platform === "win32"
|
|
10
10
|
? ["cmd", ["/c", "start", "", url]]
|
|
11
11
|
: ["xdg-open", [url]];
|
|
12
|
+
}
|
|
13
|
+
export function openBrowser(url) {
|
|
14
|
+
const [cmd, args] = opener(url);
|
|
12
15
|
try {
|
|
13
16
|
const child = spawn(cmd, [...args], { stdio: "ignore", detached: true });
|
|
14
17
|
child.on("error", () => { }); // no opener installed — the printed URL is the fallback
|
|
@@ -18,3 +21,34 @@ export function openBrowser(url) {
|
|
|
18
21
|
// spawn itself threw (unusual) — the code and URL on screen are the fallback
|
|
19
22
|
}
|
|
20
23
|
}
|
|
24
|
+
/** Login's opener: rejects when no opener ran (none installed, or it exited non-zero, as xdg-open does
|
|
25
|
+
* with no browser), so login claims an opened browser only when one did. An opener still running
|
|
26
|
+
* after `waitMs` launched something and counts as opened. */
|
|
27
|
+
export function openBrowserChecked(url, options = {}) {
|
|
28
|
+
const [cmd, args] = opener(url, options.platform);
|
|
29
|
+
const launch = options.launch ??
|
|
30
|
+
((command, list) => spawn(command, [...list], { stdio: "ignore", detached: true }));
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const fail = () => reject(new Error("browser-unavailable"));
|
|
33
|
+
let child;
|
|
34
|
+
try {
|
|
35
|
+
child = launch(cmd, args);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return fail();
|
|
39
|
+
}
|
|
40
|
+
child.unref();
|
|
41
|
+
const timer = setTimeout(resolve, options.waitMs ?? 3_000);
|
|
42
|
+
child.once("error", () => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
fail();
|
|
45
|
+
});
|
|
46
|
+
child.once("exit", (code) => {
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
if (code === 0)
|
|
49
|
+
resolve();
|
|
50
|
+
else
|
|
51
|
+
fail();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -8,13 +8,14 @@ import { loadContract, pickPath } from "./contract.js";
|
|
|
8
8
|
import { CliError, MeError, UsageError } from "./errors.js";
|
|
9
9
|
import { fetchMe } from "./transport.js";
|
|
10
10
|
import { bearerFor, deviceFlowLogin } from "./oauth.js";
|
|
11
|
-
import {
|
|
11
|
+
import { openBrowserChecked as defaultOpenBrowser } from "./browser.js";
|
|
12
12
|
import { cmdAccounts, cmdExplain, cmdHistory, cmdQueue, cmdRunning, } from "./execution.js";
|
|
13
13
|
import { cmdQuery } from "./query.js";
|
|
14
14
|
import { cmdReady } from "./ready.js";
|
|
15
15
|
import { cmdReplica } from "./replica.js";
|
|
16
16
|
import { cmdRuntime } from "./runtime-verb.js";
|
|
17
17
|
import { cmdEvents } from "./events.js";
|
|
18
|
+
import { stageOnboardLogin } from "./onboard-login-candidate.js";
|
|
18
19
|
import { stdinIsTty } from "./prompt.js";
|
|
19
20
|
import { FIRST_STAMPED_VERSION, PROVENANCE_MARKER, parseProvenanceVersion, } from "./skill-shape.js";
|
|
20
21
|
import { installedBundleVersion, installSkills, parseChangelogEntry, readChangelog, resolveSkillsDir, skillsSourceDir, updateNoticeLine, } from "./skills.js";
|
|
@@ -35,9 +36,10 @@ import { cmdLegacy } from "./legacy.js";
|
|
|
35
36
|
import { cmdIdentity } from "./identity.js";
|
|
36
37
|
import { cmdConnections } from "./connections.js";
|
|
37
38
|
import { cmdOnboard, onboardErrorJournal } from "./onboard.js";
|
|
38
|
-
import { createClackOnboardUi, shouldUseOnboardUi } from "./onboard-ui.js";
|
|
39
|
+
import { createClackOnboardUi, shouldUseOnboardUi, } from "./onboard-ui.js";
|
|
39
40
|
import { createOnboardRuntime } from "./onboard-runtime.js";
|
|
40
|
-
import {
|
|
41
|
+
import { savedOnboardBaseUrl } from "./onboard-next.js";
|
|
42
|
+
import { onboardingReadyReport, observeCloudOnboarding, } from "./onboard-ready.js";
|
|
41
43
|
import { selectedOnboardTeam } from "./onboard-existing.js";
|
|
42
44
|
export { CONFIG_MODE, DEFAULT_BASE_URL, FIRST_STAMPED_VERSION, LEGACY_PACKAGE_NAME, PACKAGE_NAME, PROVENANCE_MARKER, CliError, MeError, UsageError, configPathFor, contractPathFor, defaultCtx, defaultReplicaDbFor, defaultSkillsDirFor, fetchMe, formatMode, installedBundleVersion, installSkills, loadConfig, modernCliPath, normalizeBaseUrl, parseArgs, parseChangelogEntry, parseProvenanceVersion, readChangelog, readManifest, resolveSkillsDir, saveConfig, skillsSourceDir, updateNoticeLine, writeConfig, };
|
|
43
45
|
export const CUSTOMER_SKILLS = [
|
|
@@ -71,7 +73,7 @@ export function usageText() {
|
|
|
71
73
|
" catalyst query <issues|issue <id>|pulls|pull <id>|projects|cycles|search <terms>|changes --since <cursor|head>>",
|
|
72
74
|
' catalyst replica <start [--detach]|stop|status [--probe]|sql "<select>"|schema [table]>',
|
|
73
75
|
" catalyst runtime <status [--json]|install|path|uninstall> (a pinned Node this CLI manages itself)",
|
|
74
|
-
" catalyst events <tail|wait-for|query|status
|
|
76
|
+
" catalyst events <tail|wait-for|query|status> [--type NAME] [--ticket CTC-N] [--after SEQUENCE] [--from-cache]",
|
|
75
77
|
" catalyst explain <ticket> | history <ticket> | running [--ticket T --phase P] | queue [--team K]",
|
|
76
78
|
" catalyst watch [--team K] [--ticket T]... [--project P] [--exec CMD]",
|
|
77
79
|
" catalyst write <comment|state|label|create|reaction|attachment|session> ...",
|
|
@@ -252,7 +254,7 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
252
254
|
}
|
|
253
255
|
if (input.isTTY) {
|
|
254
256
|
try {
|
|
255
|
-
ui = createClackOnboardUi(await import("@clack/prompts"), { input, output: process.stdout });
|
|
257
|
+
ui = createClackOnboardUi(await import("@clack/prompts"), { input, output: process.stdout }, { baseUrl: () => savedOnboardBaseUrl(ctx.home) });
|
|
256
258
|
}
|
|
257
259
|
catch (error) {
|
|
258
260
|
terminal?.destroy();
|
|
@@ -265,23 +267,64 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
265
267
|
...createOnboardRuntime(args, ctx, {
|
|
266
268
|
ui,
|
|
267
269
|
signinTimeoutMs: deps.onboardSigninTimeoutMs,
|
|
270
|
+
stageSignin: (signal) => {
|
|
271
|
+
if (args.key || ctx.env.CATALYST_CLOUD_TOKEN)
|
|
272
|
+
throw new CliError("Connect your personal key with catalyst login, then run catalyst onboard. Setup will not replace a supplied key with browser sign-in.", "onboard-person-required", 12);
|
|
273
|
+
return stageOnboardLogin({ ...ctx, stdout: ctx.stderr }, {
|
|
274
|
+
baseUrl: args.baseUrl,
|
|
275
|
+
signal,
|
|
276
|
+
timeoutMs: deps.onboardSigninTimeoutMs,
|
|
277
|
+
device: {
|
|
278
|
+
isTty: deps.isTty ?? (() => Boolean(process.stdout.isTTY)),
|
|
279
|
+
openBrowser: deps.openBrowser ?? defaultOpenBrowser,
|
|
280
|
+
sleep: deps.sleep,
|
|
281
|
+
waitForApproval: ui
|
|
282
|
+
? (run) => ui.wait("Waiting for Catalyst approval", run)
|
|
283
|
+
: undefined,
|
|
284
|
+
},
|
|
285
|
+
});
|
|
286
|
+
},
|
|
268
287
|
login: (stepCtx, signal) => cmdLogin({ ...args, command: "login", flags: {}, json: false }, stepCtx, {
|
|
269
|
-
...deps,
|
|
270
|
-
|
|
288
|
+
...deps,
|
|
289
|
+
signal,
|
|
290
|
+
isTty: () => true,
|
|
291
|
+
waitForApproval: ui
|
|
292
|
+
? (run) => ui.wait("Waiting for Catalyst approval", run)
|
|
293
|
+
: undefined,
|
|
271
294
|
}, true),
|
|
272
295
|
ready: async (stepCtx, journal) => {
|
|
273
296
|
const team = selectedOnboardTeam(journal);
|
|
274
|
-
const report = await onboardingReadyReport(stepCtx, {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
297
|
+
const report = await onboardingReadyReport(stepCtx, {
|
|
298
|
+
teamIds: team ? [team] : undefined,
|
|
299
|
+
localSync: journal?.localSync ?? args.flags["local-sync"] === true,
|
|
300
|
+
observe: observeCloudOnboarding,
|
|
301
|
+
});
|
|
302
|
+
const failed = report.checks.some((check) => check.required && check.state === "fail");
|
|
303
|
+
return {
|
|
304
|
+
state: report.state === "complete"
|
|
305
|
+
? "done"
|
|
306
|
+
: failed
|
|
307
|
+
? "failed"
|
|
308
|
+
: "waiting",
|
|
309
|
+
reason: report.state === "complete"
|
|
310
|
+
? undefined
|
|
311
|
+
: "onboarding_checks_pending",
|
|
312
|
+
evidence: {
|
|
313
|
+
checks: report.checks.length,
|
|
314
|
+
passed: report.checks.filter((check) => check.state === "pass").length,
|
|
315
|
+
},
|
|
316
|
+
};
|
|
278
317
|
},
|
|
279
318
|
legacy: deps.legacy,
|
|
280
319
|
openBrowser: deps.openBrowser,
|
|
281
320
|
sleep: deps.sleep,
|
|
282
321
|
skillNames: CUSTOMER_SKILLS,
|
|
283
322
|
}),
|
|
323
|
+
...(deps.onboardBootstrap
|
|
324
|
+
? { bootstrap: deps.onboardBootstrap }
|
|
325
|
+
: {}),
|
|
284
326
|
...(deps.isTty ? { isTty: deps.isTty } : {}),
|
|
327
|
+
...(deps.signal ? { signal: deps.signal } : {}),
|
|
285
328
|
}, readManifest().version);
|
|
286
329
|
}
|
|
287
330
|
finally {
|
|
@@ -485,7 +528,10 @@ async function cmdLogin(args, ctx, deps, personalOnly = false) {
|
|
|
485
528
|
: ` — expected ${formatMode(CONFIG_MODE)}; chmod it by hand`}`);
|
|
486
529
|
ctx.stdout(`Tenant contract range: ${manifest.tenantContractRange}`);
|
|
487
530
|
try {
|
|
488
|
-
const loaded = await loadContract(ctx, config, {
|
|
531
|
+
const loaded = await loadContract(ctx, config, {
|
|
532
|
+
refresh: true,
|
|
533
|
+
signal: deps.signal,
|
|
534
|
+
});
|
|
489
535
|
checkCancelled();
|
|
490
536
|
ctx.stdout(`Tenant contract ${loaded.doc.contractVersion} cached at ${loaded.path}`);
|
|
491
537
|
}
|
package/dist/config.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
// config.ts — ~/.config/catalyst-cloud/customer.json and its siblings. The ONLY place the base URL
|
|
2
|
-
// and the `/api/v1` prefix are joined: the SDK wants the base with the prefix, GET /me without.
|
|
3
1
|
import { chmodSync, existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
4
2
|
import { randomBytes } from "node:crypto";
|
|
5
3
|
import { dirname, join } from "node:path";
|
|
@@ -22,6 +20,7 @@ export function defaultCtx() {
|
|
|
22
20
|
env: process.env,
|
|
23
21
|
home: process.env.CATALYST_SKILLS_HOME ?? process.env.HOME ?? "/",
|
|
24
22
|
stdout: (line) => console.log(line),
|
|
23
|
+
stdoutStream: process.stdout,
|
|
25
24
|
stderr: (line) => console.error(line),
|
|
26
25
|
fetch: fetch,
|
|
27
26
|
now: () => new Date(),
|
|
@@ -144,12 +143,25 @@ function machineFilePresent(file) {
|
|
|
144
143
|
throw error;
|
|
145
144
|
}
|
|
146
145
|
}
|
|
146
|
+
/** The machine paths record, or null on a machine that has not run paths setup. */
|
|
147
|
+
function declaredMachinePaths(home, env) {
|
|
148
|
+
const file = machinePathsFile({ env: { ...env, HOME: home } });
|
|
149
|
+
if (!file || (env.CATALYST_PATHS_FILE === undefined && !machineFilePresent(file)))
|
|
150
|
+
return null;
|
|
151
|
+
return parseMachinePaths(JSON.parse(readFileSync(file, "utf8")));
|
|
152
|
+
}
|
|
153
|
+
/** True only when this machine opted in to the local replica: CATALYST_REPLICA_DB is set, or the
|
|
154
|
+
* machine paths file declares replicaDb. Every other machine reads the cloud (CTC-4508). */
|
|
155
|
+
export function replicaOptedIn(home, env = {}) {
|
|
156
|
+
if (env.CATALYST_REPLICA_DB !== undefined)
|
|
157
|
+
return true;
|
|
158
|
+
return declaredMachinePaths(home, env)?.paths.replicaDb !== undefined;
|
|
159
|
+
}
|
|
147
160
|
export function replicaDbPath(cfg, home, env = {}) {
|
|
148
161
|
if (env.CATALYST_REPLICA_DB !== undefined)
|
|
149
162
|
return resolveCatalystPath("replicaDb", { env });
|
|
150
|
-
const
|
|
151
|
-
if (
|
|
152
|
-
const machine = parseMachinePaths(JSON.parse(readFileSync(file, "utf8")));
|
|
163
|
+
const machine = declaredMachinePaths(home, env);
|
|
164
|
+
if (machine) {
|
|
153
165
|
if (machine.paths.replicaDb === undefined)
|
|
154
166
|
throw new CliError("optional replica is not configured; set CATALYST_REPLICA_DB or declare replicaDb in the machine paths file", "replica-not-configured");
|
|
155
167
|
return resolveCatalystPath("replicaDb", { env, machine });
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
/** Consent links are credentials. Keep them out of errors and join the opener before continuing. */
|
|
3
|
+
export function createConsentBrowserOpener(options = {}) {
|
|
4
|
+
const platform = options.platform ?? process.platform;
|
|
5
|
+
const timeoutMs = options.timeoutMs ?? 5_000;
|
|
6
|
+
const killGraceMs = options.killGraceMs ?? 500;
|
|
7
|
+
const launch = options.launch ?? ((command, args) => spawn(command, [...args], { stdio: "ignore", detached: false }));
|
|
8
|
+
return async (url, signal) => {
|
|
9
|
+
if (signal?.aborted)
|
|
10
|
+
throw new DOMException("Browser opening interrupted", "AbortError");
|
|
11
|
+
const [command, args] = platform === "darwin"
|
|
12
|
+
? ["open", [url]]
|
|
13
|
+
: platform === "win32"
|
|
14
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
15
|
+
: ["xdg-open", [url]];
|
|
16
|
+
let child;
|
|
17
|
+
try {
|
|
18
|
+
child = launch(command, args);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw new Error("Browser could not open");
|
|
22
|
+
}
|
|
23
|
+
await new Promise((resolve, reject) => {
|
|
24
|
+
let interrupted = false;
|
|
25
|
+
let expired = false;
|
|
26
|
+
let failed = false;
|
|
27
|
+
let killTimer;
|
|
28
|
+
const stop = () => {
|
|
29
|
+
child.kill("SIGTERM");
|
|
30
|
+
killTimer ??= setTimeout(() => { child.kill("SIGKILL"); }, killGraceMs);
|
|
31
|
+
};
|
|
32
|
+
const abort = () => { interrupted = true; stop(); };
|
|
33
|
+
const timer = setTimeout(() => { expired = true; stop(); }, timeoutMs);
|
|
34
|
+
child.once("error", () => { failed = true; });
|
|
35
|
+
child.once("close", (code) => {
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
if (killTimer)
|
|
38
|
+
clearTimeout(killTimer);
|
|
39
|
+
signal?.removeEventListener("abort", abort);
|
|
40
|
+
if (interrupted)
|
|
41
|
+
reject(new DOMException("Browser opening interrupted", "AbortError"));
|
|
42
|
+
else if (failed || expired || code !== 0)
|
|
43
|
+
reject(new Error("Browser could not open"));
|
|
44
|
+
else
|
|
45
|
+
resolve();
|
|
46
|
+
});
|
|
47
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
48
|
+
if (signal?.aborted)
|
|
49
|
+
abort();
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export const openConsentBrowser = createConsentBrowserOpener();
|
|
54
|
+
/** The web page that finishes a consent step when no browser opens here. The signed link stays private. */
|
|
55
|
+
export function finishOnTheWeb(baseUrl, page, action) {
|
|
56
|
+
const origin = baseUrl.replace(/\/+$/, "");
|
|
57
|
+
return `No browser opened on this computer. In a browser signed in to Catalyst, open ${origin}/settings/${page} and ${action}. Setup keeps waiting here.`;
|
|
58
|
+
}
|