@catalyst-cloud/cli 0.15.1 → 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.
Files changed (40) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +13 -4
  3. package/dist/args.js +1 -1
  4. package/dist/capabilities.js +3 -1
  5. package/dist/cli.js +25 -30
  6. package/dist/consent-browser.js +3 -1
  7. package/dist/oauth.js +1 -1
  8. package/dist/onboard-accounts.js +67 -171
  9. package/dist/onboard-checklist.js +309 -0
  10. package/dist/onboard-existing.js +1 -1
  11. package/dist/onboard-first-ticket.js +199 -0
  12. package/dist/onboard-github.js +13 -1
  13. package/dist/onboard-headless.js +6 -4
  14. package/dist/onboard-housekeeping.js +93 -0
  15. package/dist/onboard-next.js +124 -13
  16. package/dist/onboard-ready.js +61 -0
  17. package/dist/onboard-runtime.js +117 -26
  18. package/dist/onboard-standalone-copy.js +73 -66
  19. package/dist/onboard-ui.js +189 -81
  20. package/dist/onboard-values.js +47 -0
  21. package/dist/onboard.js +59 -18
  22. package/dist/ready-copy.js +325 -0
  23. package/dist/ready.js +35 -98
  24. package/dist/setup-onboard-copy.js +262 -41
  25. package/dist/setup-render.js +73 -14
  26. package/dist/setup.js +6 -3
  27. package/package.json +2 -2
  28. package/skills/catalyst-github/SKILL.md +1 -1
  29. package/skills/catalyst-linear/SKILL.md +1 -1
  30. package/skills/catalyst-onboard/SKILL.md +3 -3
  31. package/skills/catalyst-onboard/references/replacing-a-credential.md +9 -15
  32. package/skills/catalyst-onboard/references/what-a-phase-needs.md +4 -11
  33. package/skills/catalyst-onboard/scripts/where-am-i.mjs +3 -2
  34. package/skills/run-this-project/SKILL.md +2 -2
  35. package/skills/unstick/SKILL.md +1 -1
  36. package/skills/what-needs-me/SKILL.md +1 -1
  37. package/skills/what-this-repo-needs/SKILL.md +1 -1
  38. package/skills/whats-happening/SKILL.md +2 -2
  39. package/skills/whats-happening/references/coding-accounts.md +1 -1
  40. package/skills/whats-happening/scripts/snapshot.mjs +1 -1
@@ -1,9 +1,10 @@
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, 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 { FIRST_TICKET_SAMPLE, FIRST_TICKET_SKIP, } from "./onboard-first-ticket.js";
7
8
  import { ONBOARD_STEPS, ONBOARD_TITLES, stepSatisfied, onboardIdentityLines, onboardNextActions, onboardReadyForWork, onboardStepAction, } from "./onboard.js";
8
9
  import { onboardStepDetail, onboardReasonText } from "./onboard-next.js";
9
10
  export function shouldUseOnboardUi(args, stdoutTty) {
@@ -12,25 +13,6 @@ export function shouldUseOnboardUi(args, stdoutTty) {
12
13
  args.flags.yes !== true &&
13
14
  args.flags["dry-run"] !== true);
14
15
  }
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
16
  export function createClackOnboardUi(prompts, streams, deps = {}) {
35
17
  const abort = new AbortController();
36
18
  const options = { ...streams, signal: abort.signal };
@@ -52,6 +34,15 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
52
34
  const renderer = deps.renderer;
53
35
  const interactive = deps.interactive !== false;
54
36
  let group;
37
+ // CTC-4680: the part whose steps are on screen, the journal and plan the tracker reads, and
38
+ // whether boundaries draw the tracker (not on a check-again pass).
39
+ let part;
40
+ // The part of the last step that ended in this run, shown or not: a boundary is a move past it.
41
+ let ranPart;
42
+ let journalSeen;
43
+ let planned = {};
44
+ let rechecking = false;
45
+ let shownPlan;
55
46
  let browserOpened = true;
56
47
  let questionOpen = false;
57
48
  let signinShown = false;
@@ -81,6 +72,41 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
81
72
  ].includes(id))) &&
82
73
  !(id === "signin" && signinShown));
83
74
  const title = (id) => setupStepView({ id, state: "running" }).title;
75
+ const number = (id) => SETUP_PART_NUMBERS[id] ?? 0;
76
+ /** A part's heading, then its plan rows, numbered from 1. */
77
+ const planPart = (r, p, journal) => {
78
+ r.heading(setupPartHeading(p, r.traits.unicode));
79
+ for (const row of standalonePlan(journal, planned.scope, planned.runner))
80
+ if (row.part === p)
81
+ r.plan(row.number, row.title, row.detail);
82
+ };
83
+ /** Starting a step in another part: its heading, and when this run has moved past a part, the
84
+ * tracker again with the new part's plan. After the install engine, part 1's checks run unshown,
85
+ * so setup opens on the part 1 boundary. */
86
+ const enterPart = (id) => {
87
+ if (!renderer)
88
+ return;
89
+ const next = SETUP_PART_OF[id];
90
+ if (next === part)
91
+ return;
92
+ const previous = ranPart;
93
+ part = next;
94
+ if (previous === undefined ||
95
+ next <= previous ||
96
+ rechecking ||
97
+ !journalSeen) {
98
+ renderer.heading(setupPartHeading(next, renderer.traits.unicode));
99
+ return;
100
+ }
101
+ const progress = setupPartProgress(journalSeen, planned.scope);
102
+ const left = progress[previous].total - progress[previous].done;
103
+ renderer.heading(left
104
+ ? `Part ${previous} has ${left === 1 ? "1 step" : `${left} steps`} left. Setup carries on with part ${next}.`
105
+ : `Part ${previous} done. ${SETUP_PARTS[previous].done}`);
106
+ renderer.tracker(setupTrackerRows(progress, { next }));
107
+ planPart(renderer, next, journalSeen);
108
+ renderer.blank();
109
+ };
84
110
  const stop = () => {
85
111
  if (active) {
86
112
  if (renderer)
@@ -103,14 +129,16 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
103
129
  }
104
130
  signinShown = true;
105
131
  if (reveal) {
106
- group = "This computer";
107
- renderer.heading(group);
108
- renderer.begin(4, title("signin"), "signing in…");
132
+ enterPart("signin");
133
+ renderer.begin(number("signin"), title("signin"), "signing in…");
109
134
  active = true;
110
135
  }
111
136
  }
112
137
  if (hidden(currentStep))
113
138
  return;
139
+ // The sign-in's own wait line: the frame already shows the countdown and how to stop.
140
+ if (renderer && /^Waiting for you to approve…/.test(text))
141
+ return;
114
142
  if (renderer &&
115
143
  currentStep === "linear.adopt" &&
116
144
  text.endsWith("already has every state and label"))
@@ -124,7 +152,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
124
152
  }
125
153
  if (currentStep === "capacity")
126
154
  return;
127
- renderer.detail(text);
155
+ renderer.detail(say(text));
128
156
  return;
129
157
  }
130
158
  stop();
@@ -136,9 +164,9 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
136
164
  if (hidden(currentStep))
137
165
  return;
138
166
  if (renderer && currentStep)
139
- renderer.begin(SETUP_NUMBERS[currentStep] ?? 0, title(currentStep), text === ONBOARD_TITLES[currentStep] ? "checking…" : text);
167
+ renderer.begin(number(currentStep), title(currentStep), text === ONBOARD_TITLES[currentStep] ? "checking…" : text);
140
168
  else if (renderer)
141
- renderer.line(text);
169
+ renderer.line(say(text));
142
170
  else
143
171
  spin.start(text);
144
172
  active = true;
@@ -147,7 +175,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
147
175
  const question = () => {
148
176
  if (renderer && currentStep && !questionOpen) {
149
177
  stop();
150
- renderer.begin(SETUP_NUMBERS[currentStep] ?? 0, title(currentStep), "", "ask");
178
+ renderer.begin(number(currentStep), title(currentStep), "", "ask");
151
179
  active = true;
152
180
  questionOpen = true;
153
181
  }
@@ -155,7 +183,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
155
183
  const select = async (opts, ownFrame = false) => {
156
184
  if (!renderer)
157
185
  return prompts.select(opts);
158
- const detached = ownFrame || !currentStep;
186
+ const detached = ownFrame || (!currentStep && !questionOpen);
159
187
  if (detached) {
160
188
  stop();
161
189
  renderer.begin(0, "Your choice", "", "ask");
@@ -202,14 +230,15 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
202
230
  renderer.heading(screen.heading);
203
231
  if (screen.paused)
204
232
  renderer.line("Your progress is saved.");
233
+ renderer.tracker(setupTrackerRows(setupPartProgress(journal, planned.scope), "end"));
205
234
  if (screen.readinessCause)
206
- renderer.line(screen.readinessCause);
235
+ renderer.line(say(screen.readinessCause));
207
236
  if (screen.actions.length) {
208
237
  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}`));
238
+ screen.actions.forEach((action, i) => renderer.item(`${i + 1}. `, say(action.text)));
210
239
  if (screen.more)
211
240
  renderer.line(`and ${screen.more} more after these`);
212
- const continuation = pendingContinuation(journal);
241
+ const continuation = pendingContinuation(journal, new Set(screen.actions.map((action) => action.id)));
213
242
  if (continuation)
214
243
  renderer.line(continuation);
215
244
  }
@@ -227,6 +256,8 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
227
256
  interactive,
228
257
  handlesSignals: true,
229
258
  plan(journal, identity, planOptions) {
259
+ planned = { scope: planOptions?.scope, runner: planOptions?.runner };
260
+ journalSeen = journal;
230
261
  if (deps.consentGiven)
231
262
  return;
232
263
  machineScope = (planOptions?.scope ?? ONBOARD_STEPS).filter((id) => COMPUTER_CHECKS.some((check) => check === id));
@@ -235,32 +266,46 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
235
266
  stop();
236
267
  if (!introduced) {
237
268
  if (renderer)
238
- renderer.intro("Catalyst setup");
269
+ renderer.brand("setup", deps.version);
239
270
  else
240
271
  prompts.intro("Catalyst setup", { output: streams.output });
241
272
  introduced = true;
242
273
  }
243
274
  if (identity !== undefined)
244
275
  for (const line of onboardIdentityLines(identity))
245
- message(line);
276
+ if (renderer)
277
+ renderer.line(line);
278
+ else
279
+ message(line);
280
+ // After sign-in the plan is asked about again. When it is the plan already on screen, the
281
+ // person sees who they are now and the question, not the header, tracker and plan twice.
282
+ const planKey = JSON.stringify([
283
+ planned.scope,
284
+ planned.runner,
285
+ planOptions?.localSync ?? journal.localSync === true,
286
+ ]);
287
+ if (renderer && planKey === shownPlan)
288
+ return;
289
+ shownPlan = planKey;
246
290
  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
- }
291
+ // CTC-4680: what setup does and the three parts first, then only the first part's steps.
292
+ // Each later part lists its own steps when it starts.
293
+ const first = standalonePlan(journal, planned.scope, planned.runner)[0]?.part ?? 1;
294
+ renderer.blank();
295
+ renderer.line("Setup gets Catalyst working on your team's Linear tickets. It has three parts.");
296
+ renderer.tracker(setupTrackerRows(setupPartProgress(journal, planned.scope), {
297
+ plan: first,
298
+ }));
299
+ planPart(renderer, first, journal);
300
+ // Its steps follow under this heading; the first one does not print it again.
301
+ part = first;
257
302
  renderer.line(standalonePlanNotes(planOptions?.localSync ?? journal.localSync === true));
258
303
  return;
259
304
  }
260
305
  const steps = new Map(journal.steps.map((step) => [step.id, step]));
261
306
  message(ONBOARD_STEPS.map((id) => `· ${ONBOARD_TITLES[id]}${steps.get(id)?.state === "done" ? " (recheck)" : ""}`).join("\n"));
262
307
  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.");
308
+ message("Setup reads your AI accounts without sending them a request.");
264
309
  },
265
310
  async confirmPlan(localSync, signin = "unavailable") {
266
311
  if (deps.consentGiven || !interactive)
@@ -482,6 +527,36 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
482
527
  }
483
528
  return typeof answer === "string" ? answer : null;
484
529
  },
530
+ async chooseFirstTicket(tickets, { teamKey, stage }) {
531
+ stop();
532
+ if (abort.signal.aborted)
533
+ return null;
534
+ const answer = await select({
535
+ ...options,
536
+ message: `Which ticket should Catalyst start first? It moves to ${stage} in ${teamKey}.`,
537
+ initialValue: tickets[0]?.identifier ?? FIRST_TICKET_SAMPLE,
538
+ options: [
539
+ ...tickets.map((ticket) => ({
540
+ value: ticket.identifier,
541
+ label: `${ticket.identifier} ${ticket.title}`,
542
+ ...(ticket.estimate !== null
543
+ ? { hint: `estimate ${ticket.estimate}` }
544
+ : {}),
545
+ })),
546
+ {
547
+ value: FIRST_TICKET_SAMPLE,
548
+ label: "Create a sample ticket",
549
+ hint: "documents how to run the tests; changes only CONTRIBUTING.md",
550
+ },
551
+ { value: FIRST_TICKET_SKIP, label: "Skip for now" },
552
+ ],
553
+ });
554
+ if (prompts.isCancel(answer)) {
555
+ abort.abort();
556
+ return null;
557
+ }
558
+ return typeof answer === "string" ? answer : null;
559
+ },
485
560
  async chooseRunner() {
486
561
  stop();
487
562
  if (abort.signal.aborted)
@@ -508,13 +583,44 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
508
583
  }
509
584
  return answer === "yes";
510
585
  },
586
+ async retryTimedOut(id) {
587
+ if (abort.signal.aborted)
588
+ return false;
589
+ if (renderer) {
590
+ // The question draws under this row, so the reason stays on screen while it is asked.
591
+ stop();
592
+ renderer.begin(number(id), title(id), "the link timed out", "ask");
593
+ active = true;
594
+ questionOpen = true;
595
+ }
596
+ else {
597
+ stop();
598
+ prompts.log.message(`${title(id)}: the link timed out.`, {
599
+ output: streams.output,
600
+ });
601
+ }
602
+ const answer = await select({
603
+ ...options,
604
+ message: "Ready to try again?",
605
+ initialValue: "again",
606
+ options: [
607
+ { value: "again", label: "Yes, give me a new link" },
608
+ { value: "stop", label: "Stop here. Run catalyst setup later to carry on." },
609
+ ],
610
+ });
611
+ if (prompts.isCancel(answer) || answer !== "again") {
612
+ abort.abort();
613
+ return false;
614
+ }
615
+ return true;
616
+ },
511
617
  stagedSigninEnd(state, cause) {
512
618
  if (!renderer)
513
619
  return false;
514
620
  if (state === "done")
515
621
  stagedSigninApproved = true;
516
622
  if (currentStep === "signin" && signinShown)
517
- renderer.step(state === "done" ? "done" : state === "failed" ? "fail" : "act", 4, title("signin"), state === "done"
623
+ renderer.step(state === "done" ? "done" : state === "failed" ? "fail" : "act", number("signin"), title("signin"), state === "done"
518
624
  ? "approved"
519
625
  : state === "failed"
520
626
  ? "could not verify this account"
@@ -526,11 +632,11 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
526
632
  signinShown = false;
527
633
  if (state !== "done") {
528
634
  if (cause)
529
- renderer.line(cause.charAt(0).toUpperCase() + cause.slice(1));
635
+ renderer.line(say(cause.charAt(0).toUpperCase() + cause.slice(1)));
530
636
  renderer.heading(abort.signal.aborted ? "Setup paused" : "Setup is not ready yet");
531
637
  if (!cause?.includes("Your saved connection was not changed."))
532
638
  renderer.line("Your saved connection was not changed.");
533
- renderer.outro("Next: run catalyst onboard to sign in again.");
639
+ renderer.outro("Next: run catalyst setup to sign in again.");
534
640
  }
535
641
  return true;
536
642
  },
@@ -548,29 +654,17 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
548
654
  if (!machineScope.length)
549
655
  return;
550
656
  if (machineChecks.size === 0 && !machineBlocked) {
551
- renderer.heading("This computer");
552
- group = "This computer";
553
- renderer.begin(1, "This computer", "checking…");
657
+ enterPart(id);
658
+ renderer.begin(number(id), "This computer", "checking…");
554
659
  active = true;
555
660
  }
556
661
  return;
557
662
  }
558
663
  if (hidden(id))
559
664
  return;
560
- if (id === "projects" || id === "values")
665
+ if (id === "projects")
561
666
  return;
562
- const next = !deps.consentGiven &&
563
- ["signin", "daemon", "housekeeping", "ready"].includes(id)
564
- ? "This computer"
565
- : id.startsWith("linear.")
566
- ? "Linear"
567
- : id.startsWith("github.")
568
- ? "GitHub"
569
- : "Work";
570
- if (next !== group) {
571
- renderer.heading(next);
572
- group = next;
573
- }
667
+ enterPart(id);
574
668
  start(ONBOARD_TITLES[id]);
575
669
  return;
576
670
  }
@@ -592,6 +686,9 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
592
686
  start(ONBOARD_TITLES[id]);
593
687
  },
594
688
  stepEnd(step, journal) {
689
+ if (journal)
690
+ journalSeen = journal;
691
+ ranPart = SETUP_PART_OF[step.id];
595
692
  if (renderer) {
596
693
  if (standaloneComputer(step.id)) {
597
694
  machineChecks.set(step.id, step);
@@ -622,20 +719,18 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
622
719
  if ((step.id === "github.repos" || step.id === "settings") &&
623
720
  step.state === "done")
624
721
  return;
625
- if ((step.id === "projects" || step.id === "values") &&
626
- journal?.steps.find((s) => s.id === (step.id === "projects" ? "github.repos" : "settings"))?.state !== "done")
722
+ if (step.id === "projects" &&
723
+ journal?.steps.find((s) => s.id === "github.repos")?.state !== "done")
627
724
  return;
628
725
  const view = setupStepView(step, journal);
629
- if (hidden(step.id)) {
630
- renderer.heading("This computer");
631
- group = "This computer";
632
- }
726
+ if (hidden(step.id))
727
+ enterPart(step.id);
633
728
  renderer.step(view.mark, view.number, view.title, view.outcome);
634
729
  const granted = deps.verbose ? onboardStepDetail(step) : undefined;
635
730
  if (granted)
636
731
  renderer.detail(granted);
637
732
  if (deps.verbose && CONNECTION_REPAIR_REASONS.has(step.reason ?? ""))
638
- renderer.detail(onboardReasonText(step, { baseUrl: deps.baseUrl?.(), journal }));
733
+ renderer.detail(say(onboardReasonText(step, { baseUrl: deps.baseUrl?.(), journal })));
639
734
  if (view.mark === "fail")
640
735
  renderer.detail(deps.logPath?.()
641
736
  ? "Check the full log for the cause, then try this step again."
@@ -663,6 +758,16 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
663
758
  prompts.log.message(granted, { output: streams.output });
664
759
  },
665
760
  message,
761
+ note(text) {
762
+ if (hidden(currentStep))
763
+ return;
764
+ if (renderer)
765
+ renderer.detail(text);
766
+ else {
767
+ stop();
768
+ prompts.log.message(text, { output: streams.output });
769
+ }
770
+ },
666
771
  async checkAgain(journal) {
667
772
  if (!renderer ||
668
773
  !interactive ||
@@ -691,7 +796,8 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
691
796
  machineScope = COMPUTER_CHECKS.filter((id) => !stepSatisfied(journal.steps.find((step) => step.id === id)));
692
797
  machineChecks.clear();
693
798
  machineBlocked = false;
694
- group = undefined;
799
+ part = undefined;
800
+ rechecking = true;
695
801
  renderer.heading("Checking again");
696
802
  return true;
697
803
  },
@@ -707,8 +813,8 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
707
813
  renderer.line(`${ONBOARD_TITLES[only]} finished. Onboarding still has other steps.`);
708
814
  else
709
815
  for (const action of onboardNextActions(journal, deps.baseUrl?.(), only))
710
- renderer.line(action);
711
- renderer.outro("Next: run catalyst onboard");
816
+ renderer.line(say(action));
817
+ renderer.outro("Next: run catalyst setup");
712
818
  return;
713
819
  }
714
820
  if (renderer) {
@@ -721,7 +827,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
721
827
  renderer.line("Your progress is saved.");
722
828
  }
723
829
  summaryShown = false;
724
- renderer.outro(screen.next);
830
+ renderer.outro(say(screen.next));
725
831
  return;
726
832
  }
727
833
  stop();
@@ -745,10 +851,12 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
745
851
  text = `${ONBOARD_TITLES[only]} finished. Onboarding still has other steps.\nresume: catalyst onboard`;
746
852
  prompts.outro(text, { output: streams.output });
747
853
  },
748
- async wait(text, run) {
749
- const browser = currentStep &&
750
- renderer &&
751
- setupBrowserInstruction(currentStep, deps.baseUrl?.(), currentStep !== "accounts" && browserOpened);
854
+ async wait(text, run, page) {
855
+ const browser = currentStep && renderer && page
856
+ ? page
857
+ : currentStep &&
858
+ renderer &&
859
+ setupBrowserInstruction(currentStep, deps.baseUrl?.(), currentStep !== "accounts" && browserOpened);
752
860
  const signinWait = Boolean(renderer && currentStep === "signin" && signinShown);
753
861
  const seconds = signinWait
754
862
  ? Math.ceil((deps.signinTimeoutMs ?? 600000) / 1000)
@@ -760,11 +868,9 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
760
868
  };
761
869
  if (renderer && browser && currentStep) {
762
870
  stop();
763
- renderer.begin(SETUP_NUMBERS[currentStep] ?? 0, title(currentStep), renderer.traits.unicode
871
+ renderer.begin(number(currentStep), title(currentStep), renderer.traits.unicode
764
872
  ? status()
765
873
  : "waiting for you, up to 10 minutes");
766
- if (browser.preparation)
767
- renderer.detail(browser.preparation);
768
874
  renderer.action(browser.instruction);
769
875
  if (browser.url)
770
876
  renderer.detail(renderer.link(browser.url));
@@ -847,6 +953,8 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
847
953
  delete ui.chooseFirstRepository;
848
954
  delete ui.reviewSettings;
849
955
  delete ui.chooseRunner;
956
+ delete ui.chooseFirstTicket;
957
+ delete ui.retryTimedOut;
850
958
  delete ui.confirmWorkflowAdoption;
851
959
  }
852
960
  return ui;
@@ -0,0 +1,47 @@
1
+ import { loadConfig } from "./config.js";
2
+ import { loadContract } from "./contract.js";
3
+ import { selectedOnboardTeam } from "./onboard-existing.js";
4
+ import { valuesFacts } from "./ready-copy.js";
5
+ const waiting = (reason, evidence) => ({
6
+ state: "waiting",
7
+ reason,
8
+ ...(evidence ? { evidence } : {}),
9
+ });
10
+ /** The selected team's `required_values` readiness check decides this step. It passes when the
11
+ * selected repositories declare no required value, or every one has a value. Nothing here reads,
12
+ * imports or prints a value: a missing one is named, with the page where it is set. A stale, absent
13
+ * or unknown check waits, so the step never claims values it could not see. */
14
+ export function onboardValuesAdapter() {
15
+ return {
16
+ check: async (ctx, journal, signal) => {
17
+ const cfg = loadConfig(ctx.home);
18
+ const teamId = selectedOnboardTeam(journal);
19
+ if (!cfg || !teamId)
20
+ return waiting("required_values_unverified");
21
+ let loaded;
22
+ try {
23
+ loaded = await loadContract(ctx, cfg, { refresh: true, signal });
24
+ }
25
+ catch {
26
+ return waiting(signal?.aborted ? "interrupted" : "required_values_unverified");
27
+ }
28
+ if (loaded.source === "cache")
29
+ return waiting("required_values_unverified");
30
+ const readiness = loaded.doc.teams.find((team) => team.id === teamId)?.readiness;
31
+ const fresh = readiness?.checkedAt != null &&
32
+ readiness.expiresAt != null &&
33
+ readiness.expiresAt > ctx.now().getTime();
34
+ const check = fresh
35
+ ? readiness.checks.find((row) => row.id === "required_values")
36
+ : undefined;
37
+ if (check?.state === "pass")
38
+ return { state: "done", evidence: { team: teamId } };
39
+ if (check?.state !== "fail")
40
+ return waiting("required_values_unverified");
41
+ return waiting("required_values_missing", {
42
+ team: teamId,
43
+ requiredValues: JSON.stringify(valuesFacts(check)),
44
+ });
45
+ },
46
+ };
47
+ }