@catalyst-cloud/cli 0.15.2 → 0.15.3
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 +7 -0
- package/README.md +2 -2
- package/dist/cli.js +5 -17
- package/dist/oauth.js +1 -1
- package/dist/onboard-accounts.js +67 -171
- package/dist/onboard-checklist.js +309 -0
- package/dist/onboard-existing.js +1 -1
- package/dist/onboard-first-ticket.js +199 -0
- package/dist/onboard-headless.js +2 -2
- package/dist/onboard-housekeeping.js +93 -0
- package/dist/onboard-next.js +108 -9
- package/dist/onboard-ready.js +61 -0
- package/dist/onboard-runtime.js +60 -27
- package/dist/onboard-standalone-copy.js +73 -66
- package/dist/onboard-ui.js +162 -89
- package/dist/onboard-values.js +47 -0
- package/dist/onboard.js +49 -13
- package/dist/ready-copy.js +87 -0
- package/dist/ready.js +1 -57
- package/dist/setup-onboard-copy.js +237 -41
- package/dist/setup-render.js +73 -16
- package/dist/setup.js +6 -3
- package/package.json +1 -1
- 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/replacing-a-credential.md +9 -15
- package/skills/catalyst-onboard/references/what-a-phase-needs.md +4 -11
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +1 -1
- package/skills/run-this-project/SKILL.md +2 -2
- 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 +2 -2
- package/skills/whats-happening/references/coding-accounts.md +1 -1
- package/skills/whats-happening/scripts/snapshot.mjs +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ONBOARD_DEPENDENCIES, ONBOARD_STEPS, ONBOARD_TITLES, } from "./onboard.js";
|
|
2
|
-
import { onboardReasonText } from "./onboard-next.js";
|
|
2
|
+
import { dispatchStageName, onboardReasonText } from "./onboard-next.js";
|
|
3
|
+
/** The step numbers `--json` reports. People read SETUP_PART_NUMBERS instead (CTC-4680). */
|
|
3
4
|
export const SETUP_NUMBERS = {
|
|
4
5
|
machine: 1,
|
|
5
6
|
cli: 1,
|
|
@@ -23,13 +24,94 @@ export const SETUP_NUMBERS = {
|
|
|
23
24
|
values: 16,
|
|
24
25
|
"first-ticket": 17,
|
|
25
26
|
};
|
|
27
|
+
/** CTC-4680: setup runs in three parts. The times are estimates until setup logs give medians. */
|
|
28
|
+
export const SETUP_PARTS = {
|
|
29
|
+
1: {
|
|
30
|
+
title: "This computer",
|
|
31
|
+
estimate: "about 2 min",
|
|
32
|
+
summary: "skills and sign-in",
|
|
33
|
+
done: "This computer is set up.",
|
|
34
|
+
},
|
|
35
|
+
2: {
|
|
36
|
+
title: "Linear and GitHub",
|
|
37
|
+
estimate: "about 10 min",
|
|
38
|
+
summary: "approvals in your browser",
|
|
39
|
+
done: "Linear and GitHub are connected.",
|
|
40
|
+
},
|
|
41
|
+
3: {
|
|
42
|
+
title: "Ready for work",
|
|
43
|
+
estimate: "about 5 min",
|
|
44
|
+
summary: "a first ticket",
|
|
45
|
+
done: "Catalyst is ready for work.",
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
export const SETUP_PART_OF = {
|
|
49
|
+
machine: 1,
|
|
50
|
+
cli: 1,
|
|
51
|
+
skills: 1,
|
|
52
|
+
legacy: 1,
|
|
53
|
+
signin: 1,
|
|
54
|
+
daemon: 1,
|
|
55
|
+
housekeeping: 1,
|
|
56
|
+
"linear.workspace": 2,
|
|
57
|
+
"linear.personal": 2,
|
|
58
|
+
"linear.team": 2,
|
|
59
|
+
"linear.adopt": 2,
|
|
60
|
+
"linear.automations": 2,
|
|
61
|
+
"github.install": 2,
|
|
62
|
+
"github.personal": 2,
|
|
63
|
+
"github.repos": 3,
|
|
64
|
+
projects: 3,
|
|
65
|
+
accounts: 3,
|
|
66
|
+
capacity: 3,
|
|
67
|
+
runner: 3,
|
|
68
|
+
settings: 3,
|
|
69
|
+
values: 3,
|
|
70
|
+
"first-ticket": 3,
|
|
71
|
+
ready: 3,
|
|
72
|
+
};
|
|
73
|
+
/** What a person reads: each part restarts at 1, in the order setup runs the steps. Steps that
|
|
74
|
+
* share a row (the computer checks, repositories and projects) share a number. */
|
|
75
|
+
export const SETUP_PART_NUMBERS = {
|
|
76
|
+
machine: 1,
|
|
77
|
+
cli: 1,
|
|
78
|
+
skills: 1,
|
|
79
|
+
legacy: 1,
|
|
80
|
+
signin: 2,
|
|
81
|
+
housekeeping: 3,
|
|
82
|
+
"linear.workspace": 1,
|
|
83
|
+
"linear.personal": 2,
|
|
84
|
+
"linear.team": 3,
|
|
85
|
+
"linear.adopt": 4,
|
|
86
|
+
"linear.automations": 5,
|
|
87
|
+
"github.install": 6,
|
|
88
|
+
"github.personal": 7,
|
|
89
|
+
"github.repos": 1,
|
|
90
|
+
projects: 1,
|
|
91
|
+
accounts: 2,
|
|
92
|
+
capacity: 3,
|
|
93
|
+
runner: 4,
|
|
94
|
+
settings: 5,
|
|
95
|
+
values: 6,
|
|
96
|
+
"first-ticket": 7,
|
|
97
|
+
};
|
|
98
|
+
/** CTC-4680: people type `catalyst setup`; `catalyst onboard` stays its alias. What setup draws
|
|
99
|
+
* names setup. `--json` and headless output keep their own words. */
|
|
100
|
+
export function setupCommandWords(text) {
|
|
101
|
+
return text.replace(/\bcatalyst onboard\b/g, "catalyst setup");
|
|
102
|
+
}
|
|
103
|
+
/** "Part 1 of 3 · This computer", with a colon where the output is plain. */
|
|
104
|
+
export function setupPartHeading(part, unicode) {
|
|
105
|
+
return `Part ${part} of 3${unicode ? " ·" : ":"} ${SETUP_PARTS[part].title}`;
|
|
106
|
+
}
|
|
26
107
|
const TITLES = {
|
|
27
108
|
"linear.adopt": "Set up the team's workflow",
|
|
28
109
|
"linear.automations": "Check Linear's PR automations",
|
|
29
110
|
projects: "Choose repositories",
|
|
30
|
-
accounts: "Add
|
|
111
|
+
accounts: "Add an AI account",
|
|
112
|
+
housekeeping: "Daily update",
|
|
31
113
|
capacity: "Check runners",
|
|
32
|
-
values: "
|
|
114
|
+
values: "Check repository values",
|
|
33
115
|
};
|
|
34
116
|
const OPTIONAL = new Set([
|
|
35
117
|
"local_sync_not_selected",
|
|
@@ -83,18 +165,23 @@ function succeeded(step) {
|
|
|
83
165
|
case "projects":
|
|
84
166
|
return "repositories are now Catalyst projects";
|
|
85
167
|
case "accounts":
|
|
86
|
-
return `${value(step, "provider") === "claude" ? "Claude" : value(step, "provider") === "codex" ? "Codex" : "
|
|
168
|
+
return `${value(step, "provider") === "claude" ? "Claude" : value(step, "provider") === "codex" ? "Codex" : "an AI account"} can take work`;
|
|
169
|
+
case "housekeeping":
|
|
170
|
+
return "scheduled, runs daily";
|
|
87
171
|
case "capacity":
|
|
88
172
|
return typeof step.evidence?.remainingUnits === "number"
|
|
89
173
|
? `${step.evidence.remainingUnits} runner slots available`
|
|
90
174
|
: "runners can take work";
|
|
91
175
|
case "settings":
|
|
92
|
-
case "values":
|
|
93
176
|
return "settings reviewed";
|
|
94
|
-
case "
|
|
95
|
-
return
|
|
96
|
-
|
|
177
|
+
case "values":
|
|
178
|
+
return "values set";
|
|
179
|
+
case "first-ticket": {
|
|
180
|
+
const key = value(step, "ticket") ?? value(step, "ticketKey");
|
|
181
|
+
return key
|
|
182
|
+
? `${key} moved to ${dispatchStageName(step.evidence?.stage)}. Catalyst picks it up next.`
|
|
97
183
|
: "first ticket started";
|
|
184
|
+
}
|
|
98
185
|
case "daemon":
|
|
99
186
|
return "local sync is running";
|
|
100
187
|
case "runner":
|
|
@@ -114,6 +201,12 @@ function unfinished(step) {
|
|
|
114
201
|
return "could not verify the connection's permissions";
|
|
115
202
|
if (step.reason === "housekeeping_service_unverified")
|
|
116
203
|
return "no scheduler here. catalyst says when an update is out";
|
|
204
|
+
if (step.reason === "housekeeping_off_chosen")
|
|
205
|
+
return "skipped, as you chose";
|
|
206
|
+
if (step.reason === "housekeeping_no_scheduler")
|
|
207
|
+
return "can't run on this computer";
|
|
208
|
+
if (step.id === "housekeeping")
|
|
209
|
+
return "not scheduled; the install command turns it on";
|
|
117
210
|
if (step.reason === "local_sync_not_selected")
|
|
118
211
|
return "off, reads come from the cloud";
|
|
119
212
|
if (step.reason === "runner_docker_missing")
|
|
@@ -122,7 +215,7 @@ function unfinished(step) {
|
|
|
122
215
|
return "an admin handles this step";
|
|
123
216
|
if (step.reason === "automation_management_unavailable")
|
|
124
217
|
return "could not read them; checked again before work starts";
|
|
125
|
-
if (step.reason === "interrupted")
|
|
218
|
+
if (step.reason === "interrupted" || step.reason === "first_ticket_skipped")
|
|
126
219
|
return "skipped for now";
|
|
127
220
|
if (TIMED_OUT_REASONS.has(step.reason ?? ""))
|
|
128
221
|
return "the link timed out";
|
|
@@ -134,7 +227,7 @@ function unfinished(step) {
|
|
|
134
227
|
case "github.install":
|
|
135
228
|
return "not installed yet";
|
|
136
229
|
case "accounts":
|
|
137
|
-
return "no
|
|
230
|
+
return "no AI account ready yet";
|
|
138
231
|
case "linear.workspace":
|
|
139
232
|
case "linear.personal":
|
|
140
233
|
case "github.personal":
|
|
@@ -144,8 +237,13 @@ function unfinished(step) {
|
|
|
144
237
|
case "capacity":
|
|
145
238
|
return "no runner can take work yet";
|
|
146
239
|
case "settings":
|
|
147
|
-
case "values":
|
|
148
240
|
return "optional; not reviewed yet";
|
|
241
|
+
case "values":
|
|
242
|
+
return step.reason === "required_values_missing"
|
|
243
|
+
? "a value is missing"
|
|
244
|
+
: "not checked yet";
|
|
245
|
+
case "first-ticket":
|
|
246
|
+
return "no ticket chosen yet";
|
|
149
247
|
default:
|
|
150
248
|
return step.state === "skipped" ? "not chosen" : "not done yet";
|
|
151
249
|
}
|
|
@@ -166,31 +264,118 @@ export function setupStepView(step, journal, required = false) {
|
|
|
166
264
|
: "act";
|
|
167
265
|
let outcome = step.state === "done" ? succeeded(step) : unfinished(step);
|
|
168
266
|
if (mark === "later") {
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
267
|
+
const waitsOn = step.reason === "github_install_pending"
|
|
268
|
+
? ["github.install"]
|
|
269
|
+
: (ONBOARD_DEPENDENCIES[step.id] ?? []).filter((id) => {
|
|
172
270
|
const s = journal?.steps.find((s) => s.id === id);
|
|
173
271
|
return s?.state !== "done" && s?.state !== "skipped";
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
272
|
+
});
|
|
273
|
+
// Numbers restart in each part: this part's steps come bare, then each other part once.
|
|
274
|
+
const byPart = new Map();
|
|
275
|
+
for (const id of waitsOn) {
|
|
276
|
+
const n = SETUP_PART_NUMBERS[id];
|
|
277
|
+
if (n === undefined)
|
|
278
|
+
continue;
|
|
279
|
+
const p = SETUP_PART_OF[id];
|
|
280
|
+
const list = byPart.get(p) ?? [];
|
|
281
|
+
if (!list.includes(n))
|
|
282
|
+
byPart.set(p, [...list, n]);
|
|
283
|
+
}
|
|
284
|
+
const own = SETUP_PART_OF[step.id];
|
|
285
|
+
const steps = (ns) => {
|
|
286
|
+
const sorted = [...ns].sort((x, y) => x - y);
|
|
287
|
+
const last = sorted.pop();
|
|
288
|
+
return sorted.length
|
|
289
|
+
? `steps ${sorted.join(", ")} and ${last}`
|
|
290
|
+
: `step ${last}`;
|
|
291
|
+
};
|
|
292
|
+
const groups = [...byPart]
|
|
293
|
+
.sort(([x], [y]) => (x === own ? -1 : y === own ? 1 : x - y))
|
|
294
|
+
.map(([p, ns]) => (p === own ? steps(ns) : `${steps(ns)} of part ${p}`));
|
|
180
295
|
outcome =
|
|
181
296
|
step.state === "pending" && !step.reason
|
|
182
297
|
? "not checked yet"
|
|
183
|
-
:
|
|
184
|
-
? `after ${
|
|
298
|
+
: groups.length
|
|
299
|
+
? `after ${groups.length > 1 ? `${groups.slice(0, -1).join(", ")}, and ${groups.at(-1)}` : groups[0]}`
|
|
185
300
|
: "after an earlier step";
|
|
186
301
|
}
|
|
187
302
|
return {
|
|
188
|
-
number:
|
|
303
|
+
number: SETUP_PART_NUMBERS[step.id] ?? 0,
|
|
189
304
|
title: TITLES[step.id] ?? ONBOARD_TITLES[step.id],
|
|
190
305
|
mark,
|
|
191
306
|
outcome,
|
|
192
307
|
};
|
|
193
308
|
}
|
|
309
|
+
/** How far each part has got, by its numbered rows. A row is done when every step in it is done
|
|
310
|
+
* or skipped, and needs someone when any of its steps does. */
|
|
311
|
+
export function setupPartProgress(journal, scope) {
|
|
312
|
+
const progress = {
|
|
313
|
+
1: { total: 0, done: 0, needs: 0, failed: 0 },
|
|
314
|
+
2: { total: 0, done: 0, needs: 0, failed: 0 },
|
|
315
|
+
3: { total: 0, done: 0, needs: 0, failed: 0 },
|
|
316
|
+
};
|
|
317
|
+
const rows = new Map();
|
|
318
|
+
for (const id of ONBOARD_STEPS) {
|
|
319
|
+
const number = SETUP_PART_NUMBERS[id];
|
|
320
|
+
if (number === undefined || (scope && !scope.includes(id)))
|
|
321
|
+
continue;
|
|
322
|
+
const step = journal.steps.find((s) => s.id === id) ?? {
|
|
323
|
+
id,
|
|
324
|
+
state: "pending",
|
|
325
|
+
};
|
|
326
|
+
const key = `${SETUP_PART_OF[id]}:${number}`;
|
|
327
|
+
rows.set(key, [...(rows.get(key) ?? []), setupStepView(step, journal).mark]);
|
|
328
|
+
}
|
|
329
|
+
for (const [key, marks] of rows) {
|
|
330
|
+
const part = progress[Number(key.split(":")[0])];
|
|
331
|
+
part.total++;
|
|
332
|
+
if (marks.every((m) => m === "done" || m === "skip"))
|
|
333
|
+
part.done++;
|
|
334
|
+
else if (marks.some((m) => m === "act" || m === "fail")) {
|
|
335
|
+
part.needs++;
|
|
336
|
+
if (marks.includes("fail"))
|
|
337
|
+
part.failed++;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return progress;
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* The tracker's rows: on the first screen (`plan`, the part about to start), at a boundary
|
|
344
|
+
* (`next`, the part about to start), or at the end. A part with no rows in this run is left out.
|
|
345
|
+
*/
|
|
346
|
+
export function setupTrackerRows(progress, moment) {
|
|
347
|
+
const steps = (n) => `${n} ${n === 1 ? "step" : "steps"}`;
|
|
348
|
+
return [1, 2, 3]
|
|
349
|
+
.filter((p) => progress[p].total > 0)
|
|
350
|
+
.map((p) => {
|
|
351
|
+
const { title, estimate, summary } = SETUP_PARTS[p];
|
|
352
|
+
const got = progress[p];
|
|
353
|
+
const row = (mark, note) => ({
|
|
354
|
+
mark,
|
|
355
|
+
number: p,
|
|
356
|
+
title,
|
|
357
|
+
note,
|
|
358
|
+
});
|
|
359
|
+
if (typeof moment === "object" && "plan" in moment)
|
|
360
|
+
return row(p === moment.plan ? "now" : "later", `${estimate} · ${summary}`);
|
|
361
|
+
const finished = got.done === got.total;
|
|
362
|
+
const needsMark = got.failed ? "fail" : "act";
|
|
363
|
+
if (typeof moment === "object") {
|
|
364
|
+
if (p === moment.next)
|
|
365
|
+
return row("now", `next · ${estimate}`);
|
|
366
|
+
if (p > moment.next)
|
|
367
|
+
return row("later", estimate);
|
|
368
|
+
return finished
|
|
369
|
+
? row("done", "done")
|
|
370
|
+
: row(needsMark, `${steps(got.total - got.done)} not finished`);
|
|
371
|
+
}
|
|
372
|
+
if (finished)
|
|
373
|
+
return row("done", "");
|
|
374
|
+
if (got.needs)
|
|
375
|
+
return row(needsMark, `${steps(got.needs)} ${got.needs === 1 ? "needs" : "need"} someone`);
|
|
376
|
+
return row("later", `${got.done} of ${steps(got.total)} done`);
|
|
377
|
+
});
|
|
378
|
+
}
|
|
194
379
|
function page(base, path) {
|
|
195
380
|
try {
|
|
196
381
|
const url = new URL(path, base);
|
|
@@ -206,7 +391,8 @@ function page(base, path) {
|
|
|
206
391
|
}
|
|
207
392
|
function action(step, base, journal) {
|
|
208
393
|
if (CONNECTION_REPAIR_REASONS.has(step.reason ?? "") ||
|
|
209
|
-
step.reason === "github_installation_approval_pending"
|
|
394
|
+
step.reason === "github_installation_approval_pending" ||
|
|
395
|
+
step.reason === "member_scope")
|
|
210
396
|
return onboardReasonText(step, { baseUrl: base, journal });
|
|
211
397
|
const link = (path, verb) => {
|
|
212
398
|
const url = page(base, path);
|
|
@@ -229,7 +415,9 @@ function action(step, base, journal) {
|
|
|
229
415
|
case "github.personal":
|
|
230
416
|
return link("/settings/connected-accounts?connect=github", "connect your GitHub account");
|
|
231
417
|
case "accounts":
|
|
232
|
-
return
|
|
418
|
+
return step.reason === "ai_account_not_usable"
|
|
419
|
+
? onboardReasonText(step, { baseUrl: base, journal })
|
|
420
|
+
: link("/settings/coding-accounts", "add an AI account");
|
|
233
421
|
case "linear.adopt": {
|
|
234
422
|
const team = journal?.steps.find((row) => row.id === "linear.team" && row.state === "done");
|
|
235
423
|
const key = team && value(team, "teamKey");
|
|
@@ -245,24 +433,30 @@ function action(step, base, journal) {
|
|
|
245
433
|
? onboardReasonText(step, { baseUrl: base, journal })
|
|
246
434
|
: "Run catalyst onboard --runner to check whether this computer can take work.";
|
|
247
435
|
case "settings":
|
|
248
|
-
case "values":
|
|
249
436
|
return "Open your repository checkout and review its settings.";
|
|
437
|
+
case "values":
|
|
438
|
+
return onboardReasonText({ ...step, reason: step.reason ?? "required_values_unverified" }, { baseUrl: base, journal });
|
|
250
439
|
case "linear.team":
|
|
251
440
|
return "Choose a Linear team when setup checks again.";
|
|
252
441
|
case "github.repos":
|
|
253
442
|
case "projects":
|
|
254
443
|
return "Choose repositories when setup checks again.";
|
|
255
444
|
case "first-ticket":
|
|
256
|
-
return "
|
|
445
|
+
return onboardReasonText({ ...step, reason: step.reason ?? "first_ticket_choice_required" }, { baseUrl: base, journal });
|
|
257
446
|
default:
|
|
258
447
|
return "Try this step again when setup checks again.";
|
|
259
448
|
}
|
|
260
449
|
}
|
|
261
450
|
export function setupFinalScreen(journal, base, paused = false, required = new Set()) {
|
|
451
|
+
// A member's run skips admin steps; readiness names the ones an admin has not finished yet.
|
|
452
|
+
const readyStep = journal.steps.find((step) => step.id === "ready");
|
|
453
|
+
const adminLeft = new Set(readyStep?.reason === "admin_setup_pending" && typeof readyStep.evidence?.admin === "string"
|
|
454
|
+
? readyStep.evidence.admin.split(",")
|
|
455
|
+
: []);
|
|
262
456
|
const roots = journal.steps
|
|
263
457
|
.filter((step) => {
|
|
264
458
|
const m = setupStepView(step, journal, required.has(step.id)).mark;
|
|
265
|
-
return ((m === "act" || m === "fail") &&
|
|
459
|
+
return ((m === "act" || m === "fail" || adminLeft.has(step.id)) &&
|
|
266
460
|
(ONBOARD_DEPENDENCIES[step.id] ?? []).every((id) => {
|
|
267
461
|
const parent = journal.steps.find((s) => s.id === id);
|
|
268
462
|
return parent?.state === "done" || parent?.state === "skipped";
|
|
@@ -270,7 +464,7 @@ export function setupFinalScreen(journal, base, paused = false, required = new S
|
|
|
270
464
|
})
|
|
271
465
|
.filter((step) => step.id !== "ready")
|
|
272
466
|
.sort((a, b) => ONBOARD_STEPS.indexOf(a.id) - ONBOARD_STEPS.indexOf(b.id));
|
|
273
|
-
const folded = (id) => id === "projects" ? "github.repos" : id
|
|
467
|
+
const folded = (id) => id === "projects" ? "github.repos" : id;
|
|
274
468
|
const unique = roots.filter((step, i) => roots.findIndex((s) => folded(s.id) === folded(step.id)) === i);
|
|
275
469
|
const actions = unique
|
|
276
470
|
.slice(0, 5)
|
|
@@ -297,16 +491,17 @@ export function setupFinalScreen(journal, base, paused = false, required = new S
|
|
|
297
491
|
: journal.exit === 0
|
|
298
492
|
? "Ready for work"
|
|
299
493
|
: "Not ready for work yet";
|
|
300
|
-
const
|
|
301
|
-
const
|
|
494
|
+
const firstTicket = journal.steps.find((s) => s.id === "first-ticket");
|
|
495
|
+
const ticket = firstTicket?.state === "done" ? firstTicket : undefined;
|
|
496
|
+
const key = ticket && (value(ticket, "ticket") ?? value(ticket, "ticketKey"));
|
|
302
497
|
const savedTicketUrl = ticket && (value(ticket, "url") ?? value(ticket, "ticketUrl"));
|
|
303
498
|
const ticketUrl = savedTicketUrl && page(base, savedTicketUrl);
|
|
304
499
|
const next = paused
|
|
305
500
|
? "Next: run catalyst onboard to continue."
|
|
306
|
-
: key
|
|
307
|
-
? `
|
|
501
|
+
: key
|
|
502
|
+
? `Follow ${ticketUrl ?? key} in Linear; Catalyst comments there as each phase finishes.`
|
|
308
503
|
: heading === "Setup complete"
|
|
309
|
-
?
|
|
504
|
+
? `Next: move a ticket to ${dispatchStageName(firstTicket?.evidence?.stage)} in Linear.`
|
|
310
505
|
: journal.exit === 0 && actions[0]
|
|
311
506
|
? `Next: ${actions[0].text[0].toLowerCase() + actions[0].text.slice(1)}`
|
|
312
507
|
: actions.length === 1
|
|
@@ -317,7 +512,9 @@ export function setupFinalScreen(journal, base, paused = false, required = new S
|
|
|
317
512
|
? "Next: run catalyst onboard after you finish 1 to " +
|
|
318
513
|
actions.length +
|
|
319
514
|
"."
|
|
320
|
-
:
|
|
515
|
+
: readyStep?.reason === "member_team_required"
|
|
516
|
+
? "Next: run catalyst onboard --team <KEY> to check again."
|
|
517
|
+
: "Next: run catalyst onboard to check again.";
|
|
321
518
|
return {
|
|
322
519
|
heading,
|
|
323
520
|
readinessCause,
|
|
@@ -351,8 +548,8 @@ export function setupBrowserInstruction(id, base, opened = false) {
|
|
|
351
548
|
],
|
|
352
549
|
accounts: [
|
|
353
550
|
"/settings/coding-accounts",
|
|
354
|
-
"add
|
|
355
|
-
"Add
|
|
551
|
+
"add an API key",
|
|
552
|
+
"Add an API key",
|
|
356
553
|
],
|
|
357
554
|
};
|
|
358
555
|
const words = copy[id];
|
|
@@ -363,9 +560,6 @@ export function setupBrowserInstruction(id, base, opened = false) {
|
|
|
363
560
|
? `Opened your browser. ${words[2]} there.`
|
|
364
561
|
: `Open this link and ${words[1]}:`,
|
|
365
562
|
url: page(base, words[0]),
|
|
366
|
-
preparation: id === "accounts"
|
|
367
|
-
? "In another terminal, run claude setup-token."
|
|
368
|
-
: undefined,
|
|
369
563
|
};
|
|
370
564
|
}
|
|
371
565
|
/** JSON facts use the same reachable actions as the terminal, while retaining the existing journal. */
|
|
@@ -412,7 +606,9 @@ export function onboardJsonView(journal, base, paused = false, options = {}) {
|
|
|
412
606
|
// CTC-4680: refused by the person's Catalyst role, not by GitHub, so a workspace admin acts.
|
|
413
607
|
step.reason === "github_installation_admin_required" ||
|
|
414
608
|
step.reason?.endsWith("_identity_unverified") ||
|
|
415
|
-
(action.id === "accounts"
|
|
609
|
+
((action.id === "accounts" || action.id === "values") &&
|
|
610
|
+
role !== "owner" &&
|
|
611
|
+
role !== "admin");
|
|
416
612
|
const url = action.text
|
|
417
613
|
.match(/https?:\/\/[^\s<>]+/)?.[0]
|
|
418
614
|
?.replace(/[.,;:!?]+$/, "");
|
package/dist/setup-render.js
CHANGED
|
@@ -60,6 +60,7 @@ const ASCII_MARKS = {
|
|
|
60
60
|
later: "[later]",
|
|
61
61
|
run: "[..]",
|
|
62
62
|
ask: "[?]",
|
|
63
|
+
now: "[next]",
|
|
63
64
|
};
|
|
64
65
|
const UNICODE_MARKS = {
|
|
65
66
|
done: ["✓", "green"],
|
|
@@ -69,7 +70,34 @@ const UNICODE_MARKS = {
|
|
|
69
70
|
later: ["·", "gray"],
|
|
70
71
|
run: ["◐", "cyan"],
|
|
71
72
|
ask: ["◆", "cyan"],
|
|
73
|
+
now: ["◆", "cyan"],
|
|
72
74
|
};
|
|
75
|
+
/** CTC-4680: the Pixel Nucleus core, copper on a dark terminal and rust on a light one
|
|
76
|
+
* (catalyst-cloud docs/brand-mark.md), with the nearest 256- and 16-colour background. */
|
|
77
|
+
const CORE = {
|
|
78
|
+
dark: { rgb: "210;142;99", x256: 173, x16: 43 },
|
|
79
|
+
light: { rgb: "169;81;47", x256: 130, x16: 41 },
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* The mark's 4×4 grid in two terminal lines, each cell 2 columns by half a line. The ring is a
|
|
83
|
+
* glyph in the terminal's own foreground colour; the core is a background colour, so no
|
|
84
|
+
* foreground is ever set and the C reads on light and dark terminals alike. The second line
|
|
85
|
+
* reverses video so the core's half block takes the copper and its other half the ring.
|
|
86
|
+
*/
|
|
87
|
+
export function pixelNucleusLines(env) {
|
|
88
|
+
// COLORFGBG is "fg;bg" (rxvt, Konsole, iTerm2); 7 and 15 are the light backgrounds.
|
|
89
|
+
const light = ["7", "15"].includes((env.COLORFGBG ?? "").split(";").at(-1) ?? "");
|
|
90
|
+
const core = light ? CORE.light : CORE.dark;
|
|
91
|
+
const bg = /^(truecolor|24bit)$/i.test(env.COLORTERM ?? "")
|
|
92
|
+
? `48;2;${core.rgb}`
|
|
93
|
+
: /256/.test(env.TERM ?? "")
|
|
94
|
+
? `48;5;${core.x256}`
|
|
95
|
+
: String(core.x16);
|
|
96
|
+
return [
|
|
97
|
+
`▄▄\u001b[${bg}m▀▀▀▀\u001b[49m▀▀`,
|
|
98
|
+
`▀▀\u001b[7;${bg}m▀▀▀▀\u001b[27;49m▄▄`,
|
|
99
|
+
];
|
|
100
|
+
}
|
|
73
101
|
export function createSetupRenderer(stream, env) {
|
|
74
102
|
let traits = terminalTraits(stream, env);
|
|
75
103
|
const style = (format, text) => traits.color ? styleText(format, text, { validateStream: false }) : text;
|
|
@@ -85,12 +113,14 @@ export function createSetupRenderer(stream, env) {
|
|
|
85
113
|
const visible = (text) => text
|
|
86
114
|
.replace(/\u001b\]8;;[^\u001b]*\u001b\\/g, "")
|
|
87
115
|
.replace(/\u001b\[[0-9;]*m/g, "");
|
|
88
|
-
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
const
|
|
92
|
-
const
|
|
93
|
-
const
|
|
116
|
+
// CTC-4680 (round 2): headings and prose at column 2, step rows at 4, and their details 4 more,
|
|
117
|
+
// at 8, so the eye doesn't travel across a small screen to read them. No rail: the same columns
|
|
118
|
+
// on a terminal and in a pipe.
|
|
119
|
+
const textCol = 2;
|
|
120
|
+
const rowCol = 4;
|
|
121
|
+
const titleCol = rowCol + 4;
|
|
122
|
+
const outcomeCol = rowCol + (traits.unicode ? 38 : 43);
|
|
123
|
+
const prefix = (col) => " ".repeat(col);
|
|
94
124
|
const mark = (kind) => {
|
|
95
125
|
if (!traits.unicode)
|
|
96
126
|
return ASCII_MARKS[kind];
|
|
@@ -111,9 +141,7 @@ export function createSetupRenderer(stream, env) {
|
|
|
111
141
|
.map((line) => {
|
|
112
142
|
const plain = visible(line);
|
|
113
143
|
const indent = /^https?:\/\/\S+$/.test(plain) && stringWidth(plain) > width - col
|
|
114
|
-
?
|
|
115
|
-
? 3
|
|
116
|
-
: 0
|
|
144
|
+
? textCol
|
|
117
145
|
: col;
|
|
118
146
|
return prefix(indent) + line;
|
|
119
147
|
});
|
|
@@ -122,13 +150,13 @@ export function createSetupRenderer(stream, env) {
|
|
|
122
150
|
refresh();
|
|
123
151
|
const lead = plan
|
|
124
152
|
? traits.unicode
|
|
125
|
-
? "
|
|
153
|
+
? " "
|
|
126
154
|
: " "
|
|
127
155
|
: traits.unicode
|
|
128
156
|
? mark(kind)
|
|
129
157
|
: mark(kind).padEnd(7);
|
|
130
158
|
const titleWidth = stringWidth(visible(title));
|
|
131
|
-
const label = `${lead}${traits.unicode ? " " : " "}${String(number || "").padStart(2)} ${title}`;
|
|
159
|
+
const label = `${prefix(rowCol)}${lead}${traits.unicode ? " " : " "}${String(number || "").padStart(2)} ${title}`;
|
|
132
160
|
if (!outcome)
|
|
133
161
|
return [label];
|
|
134
162
|
if (width < 80 || titleWidth > 30)
|
|
@@ -214,6 +242,7 @@ export function createSetupRenderer(stream, env) {
|
|
|
214
242
|
clear();
|
|
215
243
|
abandon();
|
|
216
244
|
};
|
|
245
|
+
let branded = false;
|
|
217
246
|
const r = {
|
|
218
247
|
get traits() {
|
|
219
248
|
refresh();
|
|
@@ -221,11 +250,31 @@ export function createSetupRenderer(stream, env) {
|
|
|
221
250
|
},
|
|
222
251
|
intro(title) {
|
|
223
252
|
refresh();
|
|
224
|
-
ordinary([
|
|
253
|
+
ordinary([prefix(textCol) + style("bold", title)]);
|
|
254
|
+
},
|
|
255
|
+
brand(program, version) {
|
|
256
|
+
if (branded)
|
|
257
|
+
return;
|
|
258
|
+
branded = true;
|
|
259
|
+
refresh();
|
|
260
|
+
if (!traits.color || traits.columns < 50) {
|
|
261
|
+
ordinary([
|
|
262
|
+
`${prefix(textCol)}Catalyst Cloud ${program}${version ? ` ${version}` : ""}`,
|
|
263
|
+
]);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const [one, two] = pixelNucleusLines(env);
|
|
267
|
+
ordinary([
|
|
268
|
+
`${prefix(textCol)}${one} ${style("bold", "Catalyst Cloud")}`,
|
|
269
|
+
`${prefix(textCol)}${two} ${style("dim", `${program}${version ? ` · ${version}` : ""}`)}`,
|
|
270
|
+
]);
|
|
271
|
+
},
|
|
272
|
+
tracker(parts) {
|
|
273
|
+
ordinary(parts.flatMap((p) => stepRows(p.mark, p.number, p.title, p.note)));
|
|
225
274
|
},
|
|
226
275
|
outro(text) {
|
|
227
276
|
abandon();
|
|
228
|
-
write(
|
|
277
|
+
write(["", prefix(textCol) + text]);
|
|
229
278
|
},
|
|
230
279
|
plan(number, title, text) {
|
|
231
280
|
ordinary(stepRows("run", number, title, text, true));
|
|
@@ -318,14 +367,22 @@ export function createSetupRenderer(stream, env) {
|
|
|
318
367
|
},
|
|
319
368
|
heading(text) {
|
|
320
369
|
abandon();
|
|
321
|
-
write(
|
|
370
|
+
write(["", prefix(textCol) + style("bold", text)]);
|
|
322
371
|
},
|
|
323
372
|
line(text) {
|
|
324
|
-
ordinary(rows(text,
|
|
373
|
+
ordinary(rows(text, textCol));
|
|
374
|
+
},
|
|
375
|
+
item(label, text) {
|
|
376
|
+
const col = textCol + stringWidth(label);
|
|
377
|
+
const lines = rows(text, col);
|
|
378
|
+
const hang = prefix(col);
|
|
379
|
+
ordinary(lines[0].startsWith(hang)
|
|
380
|
+
? [prefix(textCol) + label + lines[0].slice(col), ...lines.slice(1)]
|
|
381
|
+
: [prefix(textCol) + label.trimEnd(), ...lines]);
|
|
325
382
|
},
|
|
326
383
|
blank() {
|
|
327
384
|
refresh();
|
|
328
|
-
ordinary([
|
|
385
|
+
ordinary([""]);
|
|
329
386
|
},
|
|
330
387
|
link(url) {
|
|
331
388
|
return traits.links
|
package/dist/setup.js
CHANGED
|
@@ -19,6 +19,7 @@ import { Readable, Writable } from "node:stream";
|
|
|
19
19
|
import { VERB_USAGE } from "./args.js";
|
|
20
20
|
import { plainSetupLine, SetupEventStream, } from "./setup-events.js";
|
|
21
21
|
import { createSetupRenderer, } from "./setup-render.js";
|
|
22
|
+
import { setupPartHeading } from "./setup-onboard-copy.js";
|
|
22
23
|
const MACHINE_TITLES = {
|
|
23
24
|
1: "Install the catalyst command",
|
|
24
25
|
2: "Add the Catalyst skills",
|
|
@@ -107,7 +108,7 @@ function planRenderer(r) {
|
|
|
107
108
|
},
|
|
108
109
|
more(text) {
|
|
109
110
|
if (!hidden)
|
|
110
|
-
r.line(`
|
|
111
|
+
r.line(` ${text}`);
|
|
111
112
|
},
|
|
112
113
|
close() {
|
|
113
114
|
open = false;
|
|
@@ -238,7 +239,9 @@ async function runEngine(engine, args, flags, ctx, deps) {
|
|
|
238
239
|
}
|
|
239
240
|
if (!deps.onboard ||
|
|
240
241
|
!["What’s left", "What\'s left"].includes(event.text))
|
|
241
|
-
r.heading(event.text === "Setting up"
|
|
242
|
+
r.heading(event.text === "Setting up"
|
|
243
|
+
? setupPartHeading(1, r.traits.unicode)
|
|
244
|
+
: event.text);
|
|
242
245
|
return;
|
|
243
246
|
case "plan":
|
|
244
247
|
plan.plan(event.number, event.title, event.text);
|
|
@@ -399,7 +402,7 @@ async function runEngine(engine, args, flags, ctx, deps) {
|
|
|
399
402
|
return await deps.onboard(r, flags);
|
|
400
403
|
}
|
|
401
404
|
catch {
|
|
402
|
-
errors.line("catalyst setup: onboarding could not finish. Run catalyst
|
|
405
|
+
errors.line("catalyst setup: onboarding could not finish. Run catalyst setup to try again.");
|
|
403
406
|
if (flags.json)
|
|
404
407
|
deps.writeJson(`${JSON.stringify({ schema: "catalyst-install-last-run/1", state: "failed", exitCode: 10, steps: [] })}\n`);
|
|
405
408
|
return 10;
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Catalyst's GitHub: show a ticket's pull request with its checks, reviews and review threads, say whether it is mergeable under this repository's policy, and explain what a PR accumulates as the ticket moves (the branch, the draft, the rewrite, the force-pushes, the labels, the queue). Use when someone asks "show me the PR", "what are the checks saying", "why hasn't it merged", "what does the queue need", "what does that label mean", or wants the branch and merge conventions Catalyst follows.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.3 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Catalyst's GitHub
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Catalyst's view of Linear on the customer's own cloud account. Reads a ticket with its comments, relations, labels, linked pull requests and agent sessions inline, from the cloud API by default, with local replica reads available as an explicit opt-in, always naming the source; searches tickets, PRs, projects and initiatives; writes comments, card moves, labels and new tickets through the account's agent proxy as the app actor, with every route, stage id, label id and marker read from the account's contract. Knows what a ticket accumulates as Catalyst works it (phase-outcome comments, the document per phase, the agent session, the labels, the single blocks relation, the bookkeeping marker, the eyes acknowledgement). Use when a person says "show me the ticket", "what did Catalyst write on it", "comment on it", "move it", "label it" or "file a ticket". Not for raising a decision for a human (what-needs-me) and not for pull requests (catalyst-github).
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.3 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Catalyst Linear
|
|
10
10
|
|