@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.
- package/CHANGELOG.md +13 -0
- package/README.md +4 -2
- package/dist/args.js +5 -1
- package/dist/cli.js +44 -20
- package/dist/oauth.js +1 -1
- package/dist/onboard-accounts.js +67 -171
- package/dist/onboard-checklist.js +310 -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 +278 -105
- package/dist/onboard-values.js +47 -0
- package/dist/onboard.js +63 -18
- package/dist/ready-copy.js +87 -0
- package/dist/ready.js +1 -57
- package/dist/setup-brand.js +202 -0
- package/dist/setup-onboard-copy.js +314 -42
- package/dist/setup-render.js +99 -17
- package/dist/setup.js +6 -3
- package/dist/terminal-background.js +220 -0
- 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
package/dist/onboard-ui.js
CHANGED
|
@@ -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,
|
|
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 {
|
|
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
|
-
|
|
107
|
-
renderer.
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
|
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 (
|
|
208
|
-
renderer.line(`${
|
|
209
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
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
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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)}:
|
|
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
|
-
{
|
|
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",
|
|
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
|
|
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
|
-
|
|
586
|
-
|
|
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"
|
|
753
|
+
if (id === "projects")
|
|
595
754
|
return;
|
|
596
|
-
|
|
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 (
|
|
807
|
+
if (step.id === "github.repos" &&
|
|
657
808
|
step.state === "done")
|
|
658
809
|
return;
|
|
659
|
-
if (
|
|
660
|
-
journal?.steps.find((s) => s.id ===
|
|
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
|
-
|
|
665
|
-
|
|
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
|
-
!
|
|
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
|
-
|
|
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
|
|
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
|
|
751
|
-
|
|
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(
|
|
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
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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(
|
|
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
|
}
|