@catalyst-cloud/cli 0.15.2 → 0.15.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +4 -2
  3. package/dist/args.js +5 -1
  4. package/dist/cli.js +44 -20
  5. package/dist/oauth.js +1 -1
  6. package/dist/onboard-accounts.js +67 -171
  7. package/dist/onboard-checklist.js +310 -0
  8. package/dist/onboard-existing.js +1 -1
  9. package/dist/onboard-first-ticket.js +199 -0
  10. package/dist/onboard-headless.js +2 -2
  11. package/dist/onboard-housekeeping.js +93 -0
  12. package/dist/onboard-next.js +108 -9
  13. package/dist/onboard-ready.js +61 -0
  14. package/dist/onboard-runtime.js +60 -27
  15. package/dist/onboard-standalone-copy.js +73 -66
  16. package/dist/onboard-ui.js +278 -105
  17. package/dist/onboard-values.js +47 -0
  18. package/dist/onboard.js +63 -18
  19. package/dist/ready-copy.js +87 -0
  20. package/dist/ready.js +1 -57
  21. package/dist/setup-brand.js +202 -0
  22. package/dist/setup-onboard-copy.js +314 -42
  23. package/dist/setup-render.js +99 -17
  24. package/dist/setup.js +6 -3
  25. package/dist/terminal-background.js +220 -0
  26. package/package.json +1 -1
  27. package/skills/catalyst-github/SKILL.md +1 -1
  28. package/skills/catalyst-linear/SKILL.md +1 -1
  29. package/skills/catalyst-onboard/SKILL.md +3 -3
  30. package/skills/catalyst-onboard/references/replacing-a-credential.md +9 -15
  31. package/skills/catalyst-onboard/references/what-a-phase-needs.md +4 -11
  32. package/skills/catalyst-onboard/scripts/where-am-i.mjs +1 -1
  33. package/skills/run-this-project/SKILL.md +2 -2
  34. package/skills/unstick/SKILL.md +1 -1
  35. package/skills/what-needs-me/SKILL.md +1 -1
  36. package/skills/what-this-repo-needs/SKILL.md +1 -1
  37. package/skills/whats-happening/SKILL.md +2 -2
  38. package/skills/whats-happening/references/coding-accounts.md +1 -1
  39. package/skills/whats-happening/scripts/snapshot.mjs +1 -1
@@ -1,36 +1,19 @@
1
1
  import { COMPUTER_CHECKS, standalonePlan, standalonePlanNotes, pendingContinuation, } from "./onboard-standalone-copy.js";
2
2
  import { trackedPrompt } from "./setup-prompt-frame.js";
3
3
  import { createOnboardInterrupts } from "./onboard-interrupts.js";
4
- import { setupStepView, CONNECTION_REPAIR_REASONS, setupFinalScreen, setupBrowserInstruction, SETUP_NUMBERS, } from "./setup-onboard-copy.js";
4
+ import { setupStepView, CONNECTION_REPAIR_REASONS, setupFinalScreen, setupBrowserInstruction, setupCommandWords as say, setupWaitsOnAdmin, TIMED_OUT_REASONS, setupPartHeading, setupPartProgress, setupTrackerRows, SETUP_PART_NUMBERS, SETUP_PART_OF, SETUP_PARTS, } from "./setup-onboard-copy.js";
5
5
  import { createOnboardProgress, } from "./onboard-progress.js";
6
6
  import { CREATE_TEAM_CHOICE, suggestTeamKey, teamKeyProblem, teamNameProblem, } from "./onboard-team-create.js";
7
- import { ONBOARD_STEPS, ONBOARD_TITLES, stepSatisfied, onboardIdentityLines, onboardNextActions, onboardReadyForWork, onboardStepAction, } from "./onboard.js";
7
+ import { FIRST_TICKET_SAMPLE, FIRST_TICKET_SKIP, } from "./onboard-first-ticket.js";
8
+ import { ONBOARD_STEPS, ONBOARD_TITLES, stepSatisfied, onboardHeaderIdentity, onboardIdentityLines, onboardNextActions, onboardReadyForWork, onboardStepAction, } from "./onboard.js";
8
9
  import { onboardStepDetail, onboardReasonText } from "./onboard-next.js";
10
+ import { CONNECT_PAGE_PATH } from "./onboard-checklist.js";
9
11
  export function shouldUseOnboardUi(args, stdoutTty) {
10
12
  return (stdoutTty &&
11
13
  !args.json &&
12
14
  args.flags.yes !== true &&
13
15
  args.flags["dry-run"] !== true);
14
16
  }
15
- export const STEP_NUMBERS = {
16
- signin: 4,
17
- housekeeping: 5,
18
- "linear.workspace": 6,
19
- "linear.personal": 7,
20
- "linear.team": 8,
21
- "linear.adopt": 9,
22
- "linear.automations": 10,
23
- "github.install": 11,
24
- "github.personal": 12,
25
- "github.repos": 13,
26
- projects: 13,
27
- accounts: 14,
28
- capacity: 15,
29
- runner: 15,
30
- settings: 16,
31
- values: 16,
32
- "first-ticket": 17,
33
- };
34
17
  export function createClackOnboardUi(prompts, streams, deps = {}) {
35
18
  const abort = new AbortController();
36
19
  const options = { ...streams, signal: abort.signal };
@@ -52,6 +35,20 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
52
35
  const renderer = deps.renderer;
53
36
  const interactive = deps.interactive !== false;
54
37
  let group;
38
+ // CTC-4680: the part whose steps are on screen, the journal and plan the tracker reads, and
39
+ // whether boundaries draw the tracker (not on a check-again pass).
40
+ let part;
41
+ // The part of the last step that ended in this run, shown or not: a boundary is a move past it.
42
+ let ranPart;
43
+ let journalSeen;
44
+ let planned = {};
45
+ let rechecking = false;
46
+ let shownPlan;
47
+ // CTC-4680: steps whose wait was on the Connect accounts page. That page does not expire, so a
48
+ // wait that ran out is setup giving up, and the way back is the same page.
49
+ const connectWaits = new Map();
50
+ const onConnectPage = (step) => connectWaits.has(step.id) && TIMED_OUT_REASONS.has(step.reason ?? "");
51
+ const outcome = (step, view) => onConnectPage(step) ? "setup stopped waiting" : view.outcome;
55
52
  let browserOpened = true;
56
53
  let questionOpen = false;
57
54
  let signinShown = false;
@@ -81,6 +78,45 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
81
78
  ].includes(id))) &&
82
79
  !(id === "signin" && signinShown));
83
80
  const title = (id) => setupStepView({ id, state: "running" }).title;
81
+ const number = (id) => SETUP_PART_NUMBERS[id] ?? 0;
82
+ /** A part's heading, then its plan rows, numbered from 1. */
83
+ const planPart = (r, p, journal) => {
84
+ r.heading(setupPartHeading(p, r.traits.unicode));
85
+ for (const row of standalonePlan(journal, planned.scope, planned.runner))
86
+ if (row.part === p)
87
+ r.plan(row.number, row.title, row.detail);
88
+ };
89
+ /** Starting a step in another part: its heading, and when this run has moved past a part, the
90
+ * tracker again with the new part's plan. After the install engine, part 1's checks run unshown,
91
+ * so setup opens on the part 1 boundary. */
92
+ const enterPart = (id) => {
93
+ if (!renderer)
94
+ return;
95
+ const next = SETUP_PART_OF[id];
96
+ if (next === part)
97
+ return;
98
+ const previous = ranPart;
99
+ part = next;
100
+ if (previous === undefined ||
101
+ next <= previous ||
102
+ rechecking ||
103
+ !journalSeen) {
104
+ renderer.heading(setupPartHeading(next, renderer.traits.unicode));
105
+ return;
106
+ }
107
+ const progress = setupPartProgress(journalSeen, planned.scope, "run");
108
+ const got = progress[previous];
109
+ const left = got.total - got.done;
110
+ // Mid-run nothing yet says whether an owner or admin has done their part, so this is neutral.
111
+ renderer.heading(!left
112
+ ? `Part ${previous} done. ${SETUP_PARTS[previous].done}`
113
+ : left === got.admin + got.aside
114
+ ? `The rest of part ${previous} is for an owner or admin of your Catalyst workspace.`
115
+ : `Part ${previous} has ${left === 1 ? "1 step" : `${left} steps`} left. Setup carries on with part ${next}.`);
116
+ renderer.tracker(setupTrackerRows(progress, { next }));
117
+ planPart(renderer, next, journalSeen);
118
+ renderer.blank();
119
+ };
84
120
  const stop = () => {
85
121
  if (active) {
86
122
  if (renderer)
@@ -103,14 +139,16 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
103
139
  }
104
140
  signinShown = true;
105
141
  if (reveal) {
106
- group = "This computer";
107
- renderer.heading(group);
108
- renderer.begin(4, title("signin"), "signing in…");
142
+ enterPart("signin");
143
+ renderer.begin(number("signin"), title("signin"), "signing in…");
109
144
  active = true;
110
145
  }
111
146
  }
112
147
  if (hidden(currentStep))
113
148
  return;
149
+ // The sign-in's own wait line: the frame already shows the countdown and how to stop.
150
+ if (renderer && /^Waiting for you to approve…/.test(text))
151
+ return;
114
152
  if (renderer &&
115
153
  currentStep === "linear.adopt" &&
116
154
  text.endsWith("already has every state and label"))
@@ -124,7 +162,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
124
162
  }
125
163
  if (currentStep === "capacity")
126
164
  return;
127
- renderer.detail(text);
165
+ renderer.detail(say(text));
128
166
  return;
129
167
  }
130
168
  stop();
@@ -136,9 +174,9 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
136
174
  if (hidden(currentStep))
137
175
  return;
138
176
  if (renderer && currentStep)
139
- renderer.begin(SETUP_NUMBERS[currentStep] ?? 0, title(currentStep), text === ONBOARD_TITLES[currentStep] ? "checking…" : text);
177
+ renderer.begin(number(currentStep), title(currentStep), text === ONBOARD_TITLES[currentStep] ? "checking…" : text);
140
178
  else if (renderer)
141
- renderer.line(text);
179
+ renderer.line(say(text));
142
180
  else
143
181
  spin.start(text);
144
182
  active = true;
@@ -147,7 +185,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
147
185
  const question = () => {
148
186
  if (renderer && currentStep && !questionOpen) {
149
187
  stop();
150
- renderer.begin(SETUP_NUMBERS[currentStep] ?? 0, title(currentStep), "", "ask");
188
+ renderer.begin(number(currentStep), title(currentStep), "", "ask");
151
189
  active = true;
152
190
  questionOpen = true;
153
191
  }
@@ -155,7 +193,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
155
193
  const select = async (opts, ownFrame = false) => {
156
194
  if (!renderer)
157
195
  return prompts.select(opts);
158
- const detached = ownFrame || !currentStep;
196
+ const detached = ownFrame || (!currentStep && !questionOpen);
159
197
  if (detached) {
160
198
  stop();
161
199
  renderer.begin(0, "Your choice", "", "ask");
@@ -193,23 +231,90 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
193
231
  return result.answer;
194
232
  };
195
233
  let summaryShown = false;
196
- const summary = (journal) => {
234
+ /**
235
+ * The end screen's actions, split by who acts: the person's own, and an owner's or admin's
236
+ * (CTC-4680). A step paused with Ctrl-C is not an action: the last line says how to carry on.
237
+ */
238
+ const sortActions = (journal) => {
197
239
  const screen = setupFinalScreen(journal, deps.baseUrl?.(), abort.signal.aborted);
240
+ const step = (id) => journal.steps.find((s) => s.id === id) ?? { id, state: "pending" };
241
+ const text = (action) => {
242
+ const row = step(action.id);
243
+ if (!onConnectPage(row))
244
+ return say(action.text);
245
+ const kinds = row.evidence?.aiAccountKinds;
246
+ // #277's rule: subscription wording only for a workspace the cloud enables for it.
247
+ const what = action.id === "accounts"
248
+ ? typeof kinds === "string" && kinds.split(",").includes("subscription")
249
+ ? "add an API key or connect a subscription"
250
+ : "add an API key"
251
+ : action.id === "github.install"
252
+ ? "install Catalyst on GitHub"
253
+ : `finish "${title(action.id)}" there`;
254
+ return `Open ${connectWaits.get(action.id)} and ${what}.`;
255
+ };
256
+ const live = screen.actions.filter((action) => step(action.id).reason !== "interrupted");
257
+ const theirs = live.filter((action) => setupWaitsOnAdmin(step(action.id), journal));
258
+ const mine = live.filter((action) => !theirs.includes(action));
259
+ // Steps only an owner or admin can do, less any already listed as their task above.
260
+ const skipped = journal.steps.filter((s) => s.reason === "member_scope" &&
261
+ setupWaitsOnAdmin(s, journal) &&
262
+ SETUP_PART_NUMBERS[s.id] !== undefined &&
263
+ s.id !== "projects" &&
264
+ !theirs.some((action) => action.id === s.id));
265
+ const waiting = theirs.length > 0 || skipped.length > 0;
266
+ const own = mine.length;
267
+ const next = screen.paused ||
268
+ !/^Next: run catalyst onboard after you finish/.test(screen.next)
269
+ ? say(screen.next)
270
+ : own
271
+ ? `Next: run catalyst setup after you finish ${own === 1 ? "1" : own === 2 ? "1 and 2" : `1 to ${own}`}.`
272
+ : waiting
273
+ ? "Next: run catalyst setup once they have."
274
+ : "Next: run catalyst setup to check again.";
275
+ return { screen, mine, theirs, skipped, waiting, next, text, live };
276
+ };
277
+ const summary = (journal) => {
278
+ const sorted = sortActions(journal);
279
+ const { screen, mine, theirs, skipped } = sorted;
198
280
  if (!renderer)
199
- return screen;
281
+ return sorted;
200
282
  renderer.suspendLive();
201
283
  active = false;
202
284
  renderer.heading(screen.heading);
203
285
  if (screen.paused)
204
286
  renderer.line("Your progress is saved.");
287
+ renderer.tracker(setupTrackerRows(setupPartProgress(journal, planned.scope), "end"));
205
288
  if (screen.readinessCause)
206
- renderer.line(screen.readinessCause);
207
- if (screen.actions.length) {
208
- renderer.line(`${screen.actions.length} ${screen.actions.length === 1 ? "thing needs" : "things need"} you:`);
209
- screen.actions.forEach((action, i) => renderer.line(`${i + 1}. ${action.text}`));
289
+ renderer.line(say(screen.readinessCause));
290
+ if (mine.length) {
291
+ renderer.line(`${mine.length} ${mine.length === 1 ? "thing needs" : "things need"} you:`);
292
+ mine.forEach((action, i) => renderer.item(`${i + 1}. `, sorted.text(action)));
210
293
  if (screen.more)
211
294
  renderer.line(`and ${screen.more} more after these`);
212
- const continuation = pendingContinuation(journal);
295
+ }
296
+ if (sorted.waiting) {
297
+ renderer.line("Waiting on an owner or admin of your Catalyst workspace:");
298
+ for (const action of theirs)
299
+ renderer.item("· ", sorted.text(action));
300
+ if (skipped.length) {
301
+ renderer.item("· ", `Steps only they can do: ${skipped.map((s) => title(s.id)).join(", ")}.`);
302
+ const base = deps.baseUrl?.();
303
+ const integrations = base
304
+ ? new URL("/settings/connections", base).href
305
+ : undefined;
306
+ if (integrations && !theirs.some((a) => a.text.includes(integrations)))
307
+ renderer.item("· ", `They start at ${integrations}, the Integrations page.`);
308
+ }
309
+ }
310
+ // A step skipped with Ctrl-C is no action, but it is still to come: the line below names it.
311
+ if (screen.actions.length || skipped.length) {
312
+ const continuation = pendingContinuation(journal,
313
+ // A step paused with Ctrl-C is still to come, so it stays in this line.
314
+ new Set([
315
+ ...sorted.live.map((action) => action.id),
316
+ ...skipped.map((s) => s.id),
317
+ ]));
213
318
  if (continuation)
214
319
  renderer.line(continuation);
215
320
  }
@@ -217,7 +322,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
217
322
  renderer.line("Catalyst is ready to work on your team's tickets.");
218
323
  if (deps.logPath?.())
219
324
  renderer.line(`Full log: ${deps.logPath()}`);
220
- return screen;
325
+ return sorted;
221
326
  };
222
327
  const ui = {
223
328
  signal: abort.signal,
@@ -227,40 +332,58 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
227
332
  interactive,
228
333
  handlesSignals: true,
229
334
  plan(journal, identity, planOptions) {
335
+ planned = { scope: planOptions?.scope, runner: planOptions?.runner };
336
+ journalSeen = journal;
230
337
  if (deps.consentGiven)
231
338
  return;
232
339
  machineScope = (planOptions?.scope ?? ONBOARD_STEPS).filter((id) => COMPUTER_CHECKS.some((check) => check === id));
233
340
  machineChecks.clear();
234
341
  machineBlocked = false;
235
342
  stop();
343
+ // Signed in already, the header carries who and where, so the lines below would repeat it.
344
+ let identityShown = false;
236
345
  if (!introduced) {
237
346
  if (renderer)
238
- renderer.intro("Catalyst setup");
347
+ identityShown = renderer.brand("setup", deps.version, identity ? onboardHeaderIdentity(identity) : undefined);
239
348
  else
240
349
  prompts.intro("Catalyst setup", { output: streams.output });
241
350
  introduced = true;
242
351
  }
243
- if (identity !== undefined)
352
+ if (identity !== undefined && !identityShown)
244
353
  for (const line of onboardIdentityLines(identity))
245
- message(line);
354
+ if (renderer)
355
+ renderer.line(line);
356
+ else
357
+ message(line);
358
+ // After sign-in the plan is asked about again. When it is the plan already on screen, the
359
+ // person sees who they are now and the question, not the header, tracker and plan twice.
360
+ const planKey = JSON.stringify([
361
+ planned.scope,
362
+ planned.runner,
363
+ planOptions?.localSync ?? journal.localSync === true,
364
+ ]);
365
+ if (renderer && planKey === shownPlan)
366
+ return;
367
+ shownPlan = planKey;
246
368
  if (renderer) {
247
- renderer.heading("The plan");
248
- let planGroup;
249
- for (const row of standalonePlan(journal, planOptions?.scope, planOptions?.runner)) {
250
- if (row.group !== planGroup) {
251
- renderer.blank();
252
- renderer.line(renderer.bold(row.group));
253
- planGroup = row.group;
254
- }
255
- renderer.plan(row.number, row.title, row.detail);
256
- }
369
+ // CTC-4680: what setup does and the three parts first, then only the first part's steps.
370
+ // Each later part lists its own steps when it starts.
371
+ const first = standalonePlan(journal, planned.scope, planned.runner)[0]?.part ?? 1;
372
+ renderer.blank();
373
+ renderer.line("Setup gets Catalyst working on your team's Linear tickets. It has three parts.");
374
+ renderer.tracker(setupTrackerRows(setupPartProgress(journal, planned.scope), {
375
+ plan: first,
376
+ }));
377
+ planPart(renderer, first, journal);
378
+ // Its steps follow under this heading; the first one does not print it again.
379
+ part = first;
257
380
  renderer.line(standalonePlanNotes(planOptions?.localSync ?? journal.localSync === true));
258
381
  return;
259
382
  }
260
383
  const steps = new Map(journal.steps.map((step) => [step.id, step]));
261
384
  message(ONBOARD_STEPS.map((id) => `· ${ONBOARD_TITLES[id]}${steps.get(id)?.state === "done" ? " (recheck)" : ""}`).join("\n"));
262
385
  message("Use cloud reads by default. Local sync is optional for SQL or sustained local reads.");
263
- message("Setup may check one stored Claude account using a one-token provider request. This may use Claude quota. It does not refresh Codex credentials.");
386
+ message("Setup reads your AI accounts without sending them a request.");
264
387
  },
265
388
  async confirmPlan(localSync, signin = "unavailable") {
266
389
  if (deps.consentGiven || !interactive)
@@ -482,6 +605,36 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
482
605
  }
483
606
  return typeof answer === "string" ? answer : null;
484
607
  },
608
+ async chooseFirstTicket(tickets, { teamKey, stage }) {
609
+ stop();
610
+ if (abort.signal.aborted)
611
+ return null;
612
+ const answer = await select({
613
+ ...options,
614
+ message: `Which ticket should Catalyst start first? It moves to ${stage} in ${teamKey}.`,
615
+ initialValue: tickets[0]?.identifier ?? FIRST_TICKET_SAMPLE,
616
+ options: [
617
+ ...tickets.map((ticket) => ({
618
+ value: ticket.identifier,
619
+ label: `${ticket.identifier} ${ticket.title}`,
620
+ ...(ticket.estimate !== null
621
+ ? { hint: `estimate ${ticket.estimate}` }
622
+ : {}),
623
+ })),
624
+ {
625
+ value: FIRST_TICKET_SAMPLE,
626
+ label: "Create a sample ticket",
627
+ hint: "documents how to run the tests; changes only CONTRIBUTING.md",
628
+ },
629
+ { value: FIRST_TICKET_SKIP, label: "Skip for now" },
630
+ ],
631
+ });
632
+ if (prompts.isCancel(answer)) {
633
+ abort.abort();
634
+ return null;
635
+ }
636
+ return typeof answer === "string" ? answer : null;
637
+ },
485
638
  async chooseRunner() {
486
639
  stop();
487
640
  if (abort.signal.aborted)
@@ -511,28 +664,35 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
511
664
  async retryTimedOut(id) {
512
665
  if (abort.signal.aborted)
513
666
  return false;
514
- if (renderer && active) {
515
- // The wait frame is still live: its status and a last line say why it ended.
516
- renderer.update("the link timed out");
517
- renderer.detail("The link timed out.");
518
- }
519
- else if (renderer) {
520
- renderer.begin(SETUP_NUMBERS[id] ?? 0, title(id), "the link timed out");
521
- renderer.detail("The link timed out.");
667
+ // The Connect accounts page does not expire: setup stopped waiting, and can wait again.
668
+ const page = connectWaits.has(id);
669
+ const why = page
670
+ ? "Setup stopped waiting for the Connect accounts page."
671
+ : "The link timed out.";
672
+ if (renderer) {
673
+ // The question draws under this row, so the reason stays on screen while it is asked.
674
+ stop();
675
+ renderer.begin(number(id), title(id), page ? "setup stopped waiting" : "the link timed out", "ask");
676
+ if (page)
677
+ renderer.detail(why);
522
678
  active = true;
679
+ questionOpen = true;
523
680
  }
524
681
  else {
525
682
  stop();
526
- prompts.log.message(`${title(id)}: the link timed out.`, {
683
+ prompts.log.message(`${title(id)}: ${why}`, {
527
684
  output: streams.output,
528
685
  });
529
686
  }
530
687
  const answer = await select({
531
688
  ...options,
532
- message: "Ready to try again?",
689
+ message: page ? "Keep waiting?" : "Ready to try again?",
533
690
  initialValue: "again",
534
691
  options: [
535
- { value: "again", label: "Yes, give me a new link" },
692
+ {
693
+ value: "again",
694
+ label: page ? "Yes, keep waiting" : "Yes, give me a new link",
695
+ },
536
696
  { value: "stop", label: "Stop here. Run catalyst setup later to carry on." },
537
697
  ],
538
698
  });
@@ -548,7 +708,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
548
708
  if (state === "done")
549
709
  stagedSigninApproved = true;
550
710
  if (currentStep === "signin" && signinShown)
551
- renderer.step(state === "done" ? "done" : state === "failed" ? "fail" : "act", 4, title("signin"), state === "done"
711
+ renderer.step(state === "done" ? "done" : state === "failed" ? "fail" : "act", number("signin"), title("signin"), state === "done"
552
712
  ? "approved"
553
713
  : state === "failed"
554
714
  ? "could not verify this account"
@@ -560,11 +720,11 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
560
720
  signinShown = false;
561
721
  if (state !== "done") {
562
722
  if (cause)
563
- renderer.line(cause.charAt(0).toUpperCase() + cause.slice(1));
723
+ renderer.line(say(cause.charAt(0).toUpperCase() + cause.slice(1)));
564
724
  renderer.heading(abort.signal.aborted ? "Setup paused" : "Setup is not ready yet");
565
725
  if (!cause?.includes("Your saved connection was not changed."))
566
726
  renderer.line("Your saved connection was not changed.");
567
- renderer.outro("Next: run catalyst onboard to sign in again.");
727
+ renderer.outro("Next: run catalyst setup to sign in again.");
568
728
  }
569
729
  return true;
570
730
  },
@@ -582,29 +742,17 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
582
742
  if (!machineScope.length)
583
743
  return;
584
744
  if (machineChecks.size === 0 && !machineBlocked) {
585
- renderer.heading("This computer");
586
- group = "This computer";
587
- renderer.begin(1, "This computer", "checking…");
745
+ enterPart(id);
746
+ renderer.begin(number(id), "This computer", "checking…");
588
747
  active = true;
589
748
  }
590
749
  return;
591
750
  }
592
751
  if (hidden(id))
593
752
  return;
594
- if (id === "projects" || id === "values")
753
+ if (id === "projects")
595
754
  return;
596
- const next = !deps.consentGiven &&
597
- ["signin", "daemon", "housekeeping", "ready"].includes(id)
598
- ? "This computer"
599
- : id.startsWith("linear.")
600
- ? "Linear"
601
- : id.startsWith("github.")
602
- ? "GitHub"
603
- : "Work";
604
- if (next !== group) {
605
- renderer.heading(next);
606
- group = next;
607
- }
755
+ enterPart(id);
608
756
  start(ONBOARD_TITLES[id]);
609
757
  return;
610
758
  }
@@ -626,6 +774,9 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
626
774
  start(ONBOARD_TITLES[id]);
627
775
  },
628
776
  stepEnd(step, journal) {
777
+ if (journal)
778
+ journalSeen = journal;
779
+ ranPart = SETUP_PART_OF[step.id];
629
780
  if (renderer) {
630
781
  if (standaloneComputer(step.id)) {
631
782
  machineChecks.set(step.id, step);
@@ -653,23 +804,21 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
653
804
  }
654
805
  if (step.id === "ready" || (hidden(step.id) && step.state !== "failed"))
655
806
  return;
656
- if ((step.id === "github.repos" || step.id === "settings") &&
807
+ if (step.id === "github.repos" &&
657
808
  step.state === "done")
658
809
  return;
659
- if ((step.id === "projects" || step.id === "values") &&
660
- journal?.steps.find((s) => s.id === (step.id === "projects" ? "github.repos" : "settings"))?.state !== "done")
810
+ if (step.id === "projects" &&
811
+ journal?.steps.find((s) => s.id === "github.repos")?.state !== "done")
661
812
  return;
662
813
  const view = setupStepView(step, journal);
663
- if (hidden(step.id)) {
664
- renderer.heading("This computer");
665
- group = "This computer";
666
- }
667
- renderer.step(view.mark, view.number, view.title, view.outcome);
814
+ if (hidden(step.id))
815
+ enterPart(step.id);
816
+ renderer.step(view.mark, view.number, view.title, outcome(step, view));
668
817
  const granted = deps.verbose ? onboardStepDetail(step) : undefined;
669
818
  if (granted)
670
819
  renderer.detail(granted);
671
820
  if (deps.verbose && CONNECTION_REPAIR_REASONS.has(step.reason ?? ""))
672
- renderer.detail(onboardReasonText(step, { baseUrl: deps.baseUrl?.(), journal }));
821
+ renderer.detail(say(onboardReasonText(step, { baseUrl: deps.baseUrl?.(), journal })));
673
822
  if (view.mark === "fail")
674
823
  renderer.detail(deps.logPath?.()
675
824
  ? "Check the full log for the cause, then try this step again."
@@ -697,11 +846,21 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
697
846
  prompts.log.message(granted, { output: streams.output });
698
847
  },
699
848
  message,
849
+ note(text) {
850
+ if (hidden(currentStep))
851
+ return;
852
+ if (renderer)
853
+ renderer.detail(text);
854
+ else {
855
+ stop();
856
+ prompts.log.message(text, { output: streams.output });
857
+ }
858
+ },
700
859
  async checkAgain(journal) {
701
860
  if (!renderer ||
702
861
  !interactive ||
703
862
  abort.signal.aborted ||
704
- !setupFinalScreen(journal, deps.baseUrl?.()).actions.length)
863
+ !sortActions(journal).mine.length)
705
864
  return false;
706
865
  active = false;
707
866
  summary(journal);
@@ -725,7 +884,8 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
725
884
  machineScope = COMPUTER_CHECKS.filter((id) => !stepSatisfied(journal.steps.find((step) => step.id === id)));
726
885
  machineChecks.clear();
727
886
  machineBlocked = false;
728
- group = undefined;
887
+ part = undefined;
888
+ rechecking = true;
729
889
  renderer.heading("Checking again");
730
890
  return true;
731
891
  },
@@ -741,21 +901,20 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
741
901
  renderer.line(`${ONBOARD_TITLES[only]} finished. Onboarding still has other steps.`);
742
902
  else
743
903
  for (const action of onboardNextActions(journal, deps.baseUrl?.(), only))
744
- renderer.line(action);
745
- renderer.outro("Next: run catalyst onboard");
904
+ renderer.line(say(action));
905
+ renderer.outro("Next: run catalyst setup");
746
906
  return;
747
907
  }
748
908
  if (renderer) {
749
909
  active = false;
750
- const screen = summaryShown
751
- ? setupFinalScreen(journal, deps.baseUrl?.(), abort.signal.aborted)
752
- : summary(journal);
910
+ const sorted = summaryShown ? sortActions(journal) : summary(journal);
911
+ const { screen } = sorted;
753
912
  if (summaryShown && screen.paused) {
754
913
  renderer.heading(screen.heading);
755
914
  renderer.line("Your progress is saved.");
756
915
  }
757
916
  summaryShown = false;
758
- renderer.outro(screen.next);
917
+ renderer.outro(sorted.next);
759
918
  return;
760
919
  }
761
920
  stop();
@@ -779,10 +938,25 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
779
938
  text = `${ONBOARD_TITLES[only]} finished. Onboarding still has other steps.\nresume: catalyst onboard`;
780
939
  prompts.outro(text, { output: streams.output });
781
940
  },
782
- async wait(text, run) {
783
- const browser = currentStep &&
784
- renderer &&
785
- setupBrowserInstruction(currentStep, deps.baseUrl?.(), currentStep !== "accounts" && browserOpened);
941
+ async wait(text, run, page) {
942
+ if (currentStep && page) {
943
+ let path = "";
944
+ try {
945
+ path = new URL(page.url).pathname;
946
+ }
947
+ catch {
948
+ // Not a URL, so not the Connect accounts page.
949
+ }
950
+ if (path === CONNECT_PAGE_PATH)
951
+ connectWaits.set(currentStep, page.url);
952
+ else
953
+ connectWaits.delete(currentStep);
954
+ }
955
+ const browser = currentStep && renderer && page
956
+ ? page
957
+ : currentStep &&
958
+ renderer &&
959
+ setupBrowserInstruction(currentStep, deps.baseUrl?.(), currentStep !== "accounts" && browserOpened);
786
960
  const signinWait = Boolean(renderer && currentStep === "signin" && signinShown);
787
961
  const seconds = signinWait
788
962
  ? Math.ceil((deps.signinTimeoutMs ?? 600000) / 1000)
@@ -794,11 +968,9 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
794
968
  };
795
969
  if (renderer && browser && currentStep) {
796
970
  stop();
797
- renderer.begin(SETUP_NUMBERS[currentStep] ?? 0, title(currentStep), renderer.traits.unicode
971
+ renderer.begin(number(currentStep), title(currentStep), renderer.traits.unicode
798
972
  ? status()
799
973
  : "waiting for you, up to 10 minutes");
800
- if (browser.preparation)
801
- renderer.detail(browser.preparation);
802
974
  renderer.action(browser.instruction);
803
975
  if (browser.url)
804
976
  renderer.detail(renderer.link(browser.url));
@@ -881,6 +1053,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
881
1053
  delete ui.chooseFirstRepository;
882
1054
  delete ui.reviewSettings;
883
1055
  delete ui.chooseRunner;
1056
+ delete ui.chooseFirstTicket;
884
1057
  delete ui.retryTimedOut;
885
1058
  delete ui.confirmWorkflowAdoption;
886
1059
  }