@catalyst-cloud/cli 0.14.10 → 0.15.0
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 +9 -0
- package/README.md +41 -0
- package/dist/args.js +34 -2
- package/dist/capabilities.js +2 -1
- package/dist/cli-update-notice.js +76 -0
- package/dist/cli.js +263 -101
- package/dist/oauth.js +48 -18
- package/dist/onboard-accounts.js +34 -5
- package/dist/onboard-automation-management.js +27 -9
- package/dist/onboard-capacity.js +10 -1
- package/dist/onboard-existing.js +132 -31
- package/dist/onboard-github.js +171 -6
- package/dist/onboard-headless.js +647 -0
- package/dist/onboard-interrupts.js +41 -0
- package/dist/onboard-next.js +215 -0
- package/dist/onboard-permissions.js +100 -0
- package/dist/onboard-personal.js +46 -10
- package/dist/onboard-runner-cloud.js +213 -0
- package/dist/onboard-runner-egress.js +67 -0
- package/dist/onboard-runner.js +780 -0
- package/dist/onboard-runtime.js +48 -8
- package/dist/onboard-standalone-copy.js +110 -0
- package/dist/onboard-team-create.js +267 -0
- package/dist/onboard-ui.js +623 -29
- package/dist/onboard-workflow.js +311 -168
- package/dist/onboard-workspace.js +52 -5
- package/dist/onboard.js +359 -212
- package/dist/setup-events.js +100 -0
- package/dist/setup-onboard-copy.js +438 -0
- package/dist/setup-prompt-frame.js +94 -0
- package/dist/setup-prompts.js +108 -0
- package/dist/setup-render.js +339 -0
- package/dist/setup-signin.js +187 -0
- package/dist/setup.js +426 -0
- package/package.json +7 -3
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +3 -2
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +1 -1
- package/vendor/README.md +4 -0
- package/vendor/self-host/compose.yaml +110 -0
- package/vendor/self-host/linux-session-egress/catalyst-session-egress-attest.service +14 -0
- package/vendor/self-host/linux-session-egress/catalyst-session-egress-attest.timer +10 -0
- package/vendor/self-host/linux-session-egress/catalyst-session-egress.service +19 -0
- package/vendor/self-host/linux-session-egress/session-egress.py +291 -0
- package/vendor/self-host/provenance.json +11 -0
package/dist/onboard-ui.js
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
|
+
import { COMPUTER_CHECKS, standalonePlan, standalonePlanNotes, pendingContinuation, } from "./onboard-standalone-copy.js";
|
|
2
|
+
import { trackedPrompt } from "./setup-prompt-frame.js";
|
|
3
|
+
import { createOnboardInterrupts } from "./onboard-interrupts.js";
|
|
4
|
+
import { setupStepView, CONNECTION_REPAIR_REASONS, setupFinalScreen, setupBrowserInstruction, SETUP_NUMBERS, } from "./setup-onboard-copy.js";
|
|
1
5
|
import { createOnboardProgress, } from "./onboard-progress.js";
|
|
2
|
-
import {
|
|
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";
|
|
8
|
+
import { onboardStepDetail, onboardReasonText } from "./onboard-next.js";
|
|
3
9
|
export function shouldUseOnboardUi(args, stdoutTty) {
|
|
4
10
|
return (stdoutTty &&
|
|
5
11
|
!args.json &&
|
|
6
12
|
args.flags.yes !== true &&
|
|
7
13
|
args.flags["dry-run"] !== true);
|
|
8
14
|
}
|
|
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
|
+
};
|
|
9
34
|
export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
10
35
|
const abort = new AbortController();
|
|
11
36
|
const options = { ...streams, signal: abort.signal };
|
|
@@ -13,50 +38,237 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
13
38
|
const spin = deps.progress ??
|
|
14
39
|
createOnboardProgress(streams.output, abort.signal, () => abort.abort());
|
|
15
40
|
const signals = deps.signals ?? process;
|
|
16
|
-
const
|
|
41
|
+
const interrupts = createOnboardInterrupts(abort);
|
|
42
|
+
const handlers = {
|
|
43
|
+
SIGINT: () => interrupts.interrupt("SIGINT"),
|
|
44
|
+
SIGTERM: () => interrupts.interrupt("SIGTERM"),
|
|
45
|
+
SIGHUP: () => interrupts.interrupt("SIGHUP"),
|
|
46
|
+
};
|
|
17
47
|
for (const event of ["SIGINT", "SIGTERM", "SIGHUP"])
|
|
18
|
-
signals.on(event,
|
|
48
|
+
signals.on(event, handlers[event]);
|
|
19
49
|
let active = false;
|
|
20
|
-
let introduced =
|
|
50
|
+
let introduced = deps.introduced === true;
|
|
51
|
+
let currentStep;
|
|
52
|
+
const renderer = deps.renderer;
|
|
53
|
+
const interactive = deps.interactive !== false;
|
|
21
54
|
let group;
|
|
55
|
+
let browserOpened = true;
|
|
56
|
+
let questionOpen = false;
|
|
57
|
+
let signinShown = false;
|
|
58
|
+
let stagedSigninApproved = false;
|
|
59
|
+
let signinStarted = 0;
|
|
60
|
+
const machineChecks = new Map();
|
|
61
|
+
let machineScope = COMPUTER_CHECKS;
|
|
62
|
+
let machineBlocked = false;
|
|
63
|
+
const standaloneComputer = (id) => !!renderer &&
|
|
64
|
+
!deps.consentGiven &&
|
|
65
|
+
!!id &&
|
|
66
|
+
COMPUTER_CHECKS.some((check) => check === id);
|
|
67
|
+
const hidden = (id) => (!!renderer && id === "ready") ||
|
|
68
|
+
(!!renderer &&
|
|
69
|
+
!!id &&
|
|
70
|
+
((id === "signin" && stagedSigninApproved) ||
|
|
71
|
+
(deps.consentGiven === true &&
|
|
72
|
+
[
|
|
73
|
+
"machine",
|
|
74
|
+
"cli",
|
|
75
|
+
"skills",
|
|
76
|
+
"legacy",
|
|
77
|
+
"signin",
|
|
78
|
+
"daemon",
|
|
79
|
+
"housekeeping",
|
|
80
|
+
"ready",
|
|
81
|
+
].includes(id))) &&
|
|
82
|
+
!(id === "signin" && signinShown));
|
|
83
|
+
const title = (id) => setupStepView({ id, state: "running" }).title;
|
|
22
84
|
const stop = () => {
|
|
23
85
|
if (active) {
|
|
24
|
-
|
|
86
|
+
if (renderer)
|
|
87
|
+
renderer.suspendLive();
|
|
88
|
+
else
|
|
89
|
+
spin.stop();
|
|
25
90
|
active = false;
|
|
26
91
|
}
|
|
27
92
|
};
|
|
28
93
|
const message = (text) => {
|
|
94
|
+
if (renderer &&
|
|
95
|
+
(currentStep === "signin" || currentStep === undefined) &&
|
|
96
|
+
!signinShown &&
|
|
97
|
+
/(?:visit:\s*https?:|enter.*(?:code|[A-Z0-9]{4}-[A-Z0-9]{4})|https?:\/\/.*device)/i.test(text)) {
|
|
98
|
+
const reveal = currentStep === undefined || hidden(currentStep);
|
|
99
|
+
if (currentStep === undefined) {
|
|
100
|
+
currentStep = "signin";
|
|
101
|
+
signinStarted = Date.now();
|
|
102
|
+
interrupts.begin("signin");
|
|
103
|
+
}
|
|
104
|
+
signinShown = true;
|
|
105
|
+
if (reveal) {
|
|
106
|
+
group = "This computer";
|
|
107
|
+
renderer.heading(group);
|
|
108
|
+
renderer.begin(4, title("signin"), "signing in…");
|
|
109
|
+
active = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (hidden(currentStep))
|
|
113
|
+
return;
|
|
114
|
+
if (renderer &&
|
|
115
|
+
currentStep === "linear.adopt" &&
|
|
116
|
+
text.endsWith("already has every state and label"))
|
|
117
|
+
return;
|
|
118
|
+
if (renderer) {
|
|
119
|
+
if (currentStep &&
|
|
120
|
+
setupBrowserInstruction(currentStep, deps.baseUrl?.())) {
|
|
121
|
+
if (/no browser opened|could not open a browser/i.test(text))
|
|
122
|
+
browserOpened = false;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (currentStep === "capacity")
|
|
126
|
+
return;
|
|
127
|
+
renderer.detail(text);
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
29
130
|
stop();
|
|
30
131
|
prompts.log.message(text, { output: streams.output });
|
|
31
132
|
};
|
|
32
133
|
const start = (text) => {
|
|
33
134
|
stop();
|
|
34
135
|
if (!abort.signal.aborted) {
|
|
35
|
-
|
|
136
|
+
if (hidden(currentStep))
|
|
137
|
+
return;
|
|
138
|
+
if (renderer && currentStep)
|
|
139
|
+
renderer.begin(SETUP_NUMBERS[currentStep] ?? 0, title(currentStep), text === ONBOARD_TITLES[currentStep] ? "checking…" : text);
|
|
140
|
+
else if (renderer)
|
|
141
|
+
renderer.line(text);
|
|
142
|
+
else
|
|
143
|
+
spin.start(text);
|
|
144
|
+
active = true;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const question = () => {
|
|
148
|
+
if (renderer && currentStep && !questionOpen) {
|
|
149
|
+
stop();
|
|
150
|
+
renderer.begin(SETUP_NUMBERS[currentStep] ?? 0, title(currentStep), "", "ask");
|
|
151
|
+
active = true;
|
|
152
|
+
questionOpen = true;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const select = async (opts, ownFrame = false) => {
|
|
156
|
+
if (!renderer)
|
|
157
|
+
return prompts.select(opts);
|
|
158
|
+
const detached = ownFrame || !currentStep;
|
|
159
|
+
if (detached) {
|
|
160
|
+
stop();
|
|
161
|
+
renderer.begin(0, "Your choice", "", "ask");
|
|
36
162
|
active = true;
|
|
37
163
|
}
|
|
164
|
+
else
|
|
165
|
+
question();
|
|
166
|
+
const result = await trackedPrompt(streams.output, (output) => prompts.select({ ...opts, output }));
|
|
167
|
+
renderer.promptRows(result.rows);
|
|
168
|
+
questionOpen = false;
|
|
169
|
+
if (detached)
|
|
170
|
+
stop();
|
|
171
|
+
return result.answer;
|
|
172
|
+
};
|
|
173
|
+
const text = async (opts) => {
|
|
174
|
+
if (!prompts.text)
|
|
175
|
+
return null;
|
|
176
|
+
if (!renderer)
|
|
177
|
+
return prompts.text(opts);
|
|
178
|
+
question();
|
|
179
|
+
const result = await trackedPrompt(streams.output, (output) => prompts.text({ ...opts, output }));
|
|
180
|
+
renderer.promptRows(result.rows);
|
|
181
|
+
questionOpen = false;
|
|
182
|
+
return result.answer;
|
|
38
183
|
};
|
|
39
|
-
|
|
184
|
+
const multiselect = async (opts) => {
|
|
185
|
+
if (!prompts.multiselect)
|
|
186
|
+
return null;
|
|
187
|
+
if (!renderer)
|
|
188
|
+
return prompts.multiselect(opts);
|
|
189
|
+
question();
|
|
190
|
+
const result = await trackedPrompt(streams.output, (output) => prompts.multiselect({ ...opts, output }));
|
|
191
|
+
renderer.promptRows(result.rows);
|
|
192
|
+
questionOpen = false;
|
|
193
|
+
return result.answer;
|
|
194
|
+
};
|
|
195
|
+
let summaryShown = false;
|
|
196
|
+
const summary = (journal) => {
|
|
197
|
+
const screen = setupFinalScreen(journal, deps.baseUrl?.(), abort.signal.aborted);
|
|
198
|
+
if (!renderer)
|
|
199
|
+
return screen;
|
|
200
|
+
renderer.suspendLive();
|
|
201
|
+
active = false;
|
|
202
|
+
renderer.heading(screen.heading);
|
|
203
|
+
if (screen.paused)
|
|
204
|
+
renderer.line("Your progress is saved.");
|
|
205
|
+
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}`));
|
|
210
|
+
if (screen.more)
|
|
211
|
+
renderer.line(`and ${screen.more} more after these`);
|
|
212
|
+
const continuation = pendingContinuation(journal);
|
|
213
|
+
if (continuation)
|
|
214
|
+
renderer.line(continuation);
|
|
215
|
+
}
|
|
216
|
+
else if (screen.heading === "Setup complete")
|
|
217
|
+
renderer.line("Catalyst is ready to work on your team's tickets.");
|
|
218
|
+
if (deps.logPath?.())
|
|
219
|
+
renderer.line(`Full log: ${deps.logPath()}`);
|
|
220
|
+
return screen;
|
|
221
|
+
};
|
|
222
|
+
const ui = {
|
|
40
223
|
signal: abort.signal,
|
|
41
|
-
|
|
224
|
+
get stepSignal() {
|
|
225
|
+
return interrupts.signal;
|
|
226
|
+
},
|
|
227
|
+
interactive,
|
|
228
|
+
handlesSignals: true,
|
|
229
|
+
plan(journal, identity, planOptions) {
|
|
230
|
+
if (deps.consentGiven)
|
|
231
|
+
return;
|
|
232
|
+
machineScope = (planOptions?.scope ?? ONBOARD_STEPS).filter((id) => COMPUTER_CHECKS.some((check) => check === id));
|
|
233
|
+
machineChecks.clear();
|
|
234
|
+
machineBlocked = false;
|
|
42
235
|
stop();
|
|
43
236
|
if (!introduced) {
|
|
44
|
-
|
|
237
|
+
if (renderer)
|
|
238
|
+
renderer.intro("Catalyst setup");
|
|
239
|
+
else
|
|
240
|
+
prompts.intro("Catalyst setup", { output: streams.output });
|
|
45
241
|
introduced = true;
|
|
46
242
|
}
|
|
47
243
|
if (identity !== undefined)
|
|
48
244
|
for (const line of onboardIdentityLines(identity))
|
|
49
245
|
message(line);
|
|
246
|
+
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
|
+
}
|
|
257
|
+
renderer.line(standalonePlanNotes(planOptions?.localSync ?? journal.localSync === true));
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
50
260
|
const steps = new Map(journal.steps.map((step) => [step.id, step]));
|
|
51
261
|
message(ONBOARD_STEPS.map((id) => `· ${ONBOARD_TITLES[id]}${steps.get(id)?.state === "done" ? " (recheck)" : ""}`).join("\n"));
|
|
52
262
|
message("Use cloud reads by default. Local sync is optional for SQL or sustained local reads.");
|
|
53
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.");
|
|
54
264
|
},
|
|
55
265
|
async confirmPlan(localSync, signin = "unavailable") {
|
|
266
|
+
if (deps.consentGiven || !interactive)
|
|
267
|
+
return { proceed: true, localSync };
|
|
56
268
|
stop();
|
|
57
|
-
const answer = await
|
|
269
|
+
const answer = await select({
|
|
58
270
|
...options,
|
|
59
|
-
message: "Continue
|
|
271
|
+
message: "Continue setup?",
|
|
60
272
|
initialValue: signin === "required" ? "signin" : localSync ? "local" : "cloud",
|
|
61
273
|
options: [
|
|
62
274
|
...(signin === "required"
|
|
@@ -64,7 +276,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
64
276
|
: [
|
|
65
277
|
{
|
|
66
278
|
value: "cloud",
|
|
67
|
-
label: "
|
|
279
|
+
label: "Yes, continue",
|
|
68
280
|
hint: "default",
|
|
69
281
|
},
|
|
70
282
|
{ value: "local", label: "Continue and set up local sync" },
|
|
@@ -92,18 +304,35 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
92
304
|
...(answer === "signin" ? { signin: true } : {}),
|
|
93
305
|
};
|
|
94
306
|
},
|
|
95
|
-
async chooseTeam(teams) {
|
|
307
|
+
async chooseTeam(teams, create) {
|
|
96
308
|
stop();
|
|
97
|
-
|
|
309
|
+
// Creating needs a name prompt; a port without one offers only the existing teams.
|
|
310
|
+
const offer = prompts.text ? create : undefined;
|
|
311
|
+
if ((!teams.length && !offer?.available) || abort.signal.aborted)
|
|
98
312
|
return null;
|
|
99
|
-
const answer = await
|
|
313
|
+
const answer = await select({
|
|
100
314
|
...options,
|
|
101
|
-
message:
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
315
|
+
message: renderer
|
|
316
|
+
? "Which Linear team should Catalyst work in?"
|
|
317
|
+
: "Which Linear team should this project use?",
|
|
318
|
+
initialValue: teams[0]?.id ?? CREATE_TEAM_CHOICE,
|
|
319
|
+
options: [
|
|
320
|
+
...teams.map((team) => ({
|
|
321
|
+
value: team.id,
|
|
322
|
+
label: `${team.name || team.key || team.id}${team.key && team.name ? ` (${team.key})` : ""}`,
|
|
323
|
+
})),
|
|
324
|
+
...(offer
|
|
325
|
+
? [
|
|
326
|
+
{
|
|
327
|
+
value: CREATE_TEAM_CHOICE,
|
|
328
|
+
label: "Create a new Linear team…",
|
|
329
|
+
...(offer.available
|
|
330
|
+
? {}
|
|
331
|
+
: { hint: offer.reason, disabled: true }),
|
|
332
|
+
},
|
|
333
|
+
]
|
|
334
|
+
: []),
|
|
335
|
+
],
|
|
107
336
|
});
|
|
108
337
|
if (prompts.isCancel(answer)) {
|
|
109
338
|
abort.abort();
|
|
@@ -111,13 +340,90 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
111
340
|
}
|
|
112
341
|
return typeof answer === "string" ? answer : null;
|
|
113
342
|
},
|
|
343
|
+
async confirmWorkflowAdoption(team, lines) {
|
|
344
|
+
stop();
|
|
345
|
+
if (abort.signal.aborted)
|
|
346
|
+
return false;
|
|
347
|
+
if (renderer)
|
|
348
|
+
question();
|
|
349
|
+
message([`Catalyst workflow plan for ${team}:`, ...lines].join("\n"));
|
|
350
|
+
const answer = await select({
|
|
351
|
+
...options,
|
|
352
|
+
message: renderer
|
|
353
|
+
? `Apply these changes to ${team}?`
|
|
354
|
+
: `Apply this to ${team}?`,
|
|
355
|
+
initialValue: "apply",
|
|
356
|
+
options: [
|
|
357
|
+
{ value: "apply", label: "Yes, apply it" },
|
|
358
|
+
{ value: "skip", label: "No, not now" },
|
|
359
|
+
],
|
|
360
|
+
});
|
|
361
|
+
if (prompts.isCancel(answer)) {
|
|
362
|
+
abort.abort();
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
return answer === "apply";
|
|
366
|
+
},
|
|
367
|
+
...(prompts.text
|
|
368
|
+
? {
|
|
369
|
+
async nameNewTeam(question) {
|
|
370
|
+
stop();
|
|
371
|
+
if (abort.signal.aborted)
|
|
372
|
+
return null;
|
|
373
|
+
const name = await text({
|
|
374
|
+
...options,
|
|
375
|
+
message: question.problem
|
|
376
|
+
? `${question.problem}\nName for the new Linear team`
|
|
377
|
+
: "Name for the new Linear team",
|
|
378
|
+
...(question.name ? { initialValue: question.name } : {}),
|
|
379
|
+
placeholder: "Mobile app",
|
|
380
|
+
validate: teamNameProblem,
|
|
381
|
+
});
|
|
382
|
+
if (prompts.isCancel(name) || typeof name !== "string") {
|
|
383
|
+
abort.abort();
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
const key = await text({
|
|
387
|
+
...options,
|
|
388
|
+
message: "Team key (the prefix on its tickets, like MOB-12)",
|
|
389
|
+
initialValue: question.key &&
|
|
390
|
+
(!question.name || question.name === name.trim())
|
|
391
|
+
? question.key
|
|
392
|
+
: suggestTeamKey(name),
|
|
393
|
+
validate: teamKeyProblem,
|
|
394
|
+
});
|
|
395
|
+
if (prompts.isCancel(key) || typeof key !== "string") {
|
|
396
|
+
abort.abort();
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const answer = {
|
|
400
|
+
name: name.trim(),
|
|
401
|
+
key: key.trim().toUpperCase(),
|
|
402
|
+
};
|
|
403
|
+
const confirm = await select({
|
|
404
|
+
...options,
|
|
405
|
+
message: `Create the Linear team ${answer.name} (${answer.key}) and set up Catalyst's workflow on it?`,
|
|
406
|
+
initialValue: "create",
|
|
407
|
+
options: [
|
|
408
|
+
{ value: "create", label: "Create the team" },
|
|
409
|
+
{ value: "back", label: "Go back to the team list" },
|
|
410
|
+
],
|
|
411
|
+
});
|
|
412
|
+
if (prompts.isCancel(confirm)) {
|
|
413
|
+
abort.abort();
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
return confirm === "create" ? answer : null;
|
|
417
|
+
},
|
|
418
|
+
}
|
|
419
|
+
: {}),
|
|
114
420
|
...(prompts.multiselect
|
|
115
421
|
? {
|
|
116
422
|
async chooseRepositories(repositories) {
|
|
117
423
|
stop();
|
|
118
424
|
if (!repositories.length || abort.signal.aborted)
|
|
119
425
|
return null;
|
|
120
|
-
const answer = await
|
|
426
|
+
const answer = await multiselect({
|
|
121
427
|
...options,
|
|
122
428
|
message: "Which repositories should use this Linear team?",
|
|
123
429
|
required: true,
|
|
@@ -138,7 +444,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
138
444
|
stop();
|
|
139
445
|
if (!summaries.length || abort.signal.aborted)
|
|
140
446
|
return "cancel";
|
|
141
|
-
const answer = await
|
|
447
|
+
const answer = await select({
|
|
142
448
|
...options,
|
|
143
449
|
message: "Review settings for all selected repositories. Keep private copies of new drafts for review?",
|
|
144
450
|
initialValue: "keep",
|
|
@@ -161,7 +467,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
161
467
|
stop();
|
|
162
468
|
if (!repositories.length || abort.signal.aborted)
|
|
163
469
|
return null;
|
|
164
|
-
const answer = await
|
|
470
|
+
const answer = await select({
|
|
165
471
|
...options,
|
|
166
472
|
message: "Which repository should start this project?",
|
|
167
473
|
initialValue: `${repositories[0].owner}/${repositories[0].name}`,
|
|
@@ -176,7 +482,98 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
176
482
|
}
|
|
177
483
|
return typeof answer === "string" ? answer : null;
|
|
178
484
|
},
|
|
485
|
+
async chooseRunner() {
|
|
486
|
+
stop();
|
|
487
|
+
if (abort.signal.aborted)
|
|
488
|
+
return null;
|
|
489
|
+
const answer = await select({
|
|
490
|
+
...options,
|
|
491
|
+
message: "Run Catalyst's work on this machine?",
|
|
492
|
+
initialValue: "no",
|
|
493
|
+
options: [
|
|
494
|
+
{
|
|
495
|
+
value: "no",
|
|
496
|
+
label: "No",
|
|
497
|
+
hint: "default; work runs on the workspace's runner hosts",
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
value: "yes",
|
|
501
|
+
label: "Yes, start a Catalyst runner here with Docker",
|
|
502
|
+
},
|
|
503
|
+
],
|
|
504
|
+
});
|
|
505
|
+
if (prompts.isCancel(answer)) {
|
|
506
|
+
abort.abort();
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
return answer === "yes";
|
|
510
|
+
},
|
|
511
|
+
stagedSigninEnd(state, cause) {
|
|
512
|
+
if (!renderer)
|
|
513
|
+
return false;
|
|
514
|
+
if (state === "done")
|
|
515
|
+
stagedSigninApproved = true;
|
|
516
|
+
if (currentStep === "signin" && signinShown)
|
|
517
|
+
renderer.step(state === "done" ? "done" : state === "failed" ? "fail" : "act", 4, title("signin"), state === "done"
|
|
518
|
+
? "approved"
|
|
519
|
+
: state === "failed"
|
|
520
|
+
? "could not verify this account"
|
|
521
|
+
: "not finished");
|
|
522
|
+
else
|
|
523
|
+
renderer.suspendLive();
|
|
524
|
+
active = false;
|
|
525
|
+
currentStep = undefined;
|
|
526
|
+
signinShown = false;
|
|
527
|
+
if (state !== "done") {
|
|
528
|
+
if (cause)
|
|
529
|
+
renderer.line(cause.charAt(0).toUpperCase() + cause.slice(1));
|
|
530
|
+
renderer.heading(abort.signal.aborted ? "Setup paused" : "Setup is not ready yet");
|
|
531
|
+
if (!cause?.includes("Your saved connection was not changed."))
|
|
532
|
+
renderer.line("Your saved connection was not changed.");
|
|
533
|
+
renderer.outro("Next: run catalyst onboard to sign in again.");
|
|
534
|
+
}
|
|
535
|
+
return true;
|
|
536
|
+
},
|
|
179
537
|
stepStart(id) {
|
|
538
|
+
interrupts.begin(id);
|
|
539
|
+
browserOpened = true;
|
|
540
|
+
questionOpen = false;
|
|
541
|
+
currentStep = id;
|
|
542
|
+
if (id === "signin") {
|
|
543
|
+
signinShown = false;
|
|
544
|
+
signinStarted = Date.now();
|
|
545
|
+
}
|
|
546
|
+
if (renderer) {
|
|
547
|
+
if (standaloneComputer(id)) {
|
|
548
|
+
if (!machineScope.length)
|
|
549
|
+
return;
|
|
550
|
+
if (machineChecks.size === 0 && !machineBlocked) {
|
|
551
|
+
renderer.heading("This computer");
|
|
552
|
+
group = "This computer";
|
|
553
|
+
renderer.begin(1, "This computer", "checking…");
|
|
554
|
+
active = true;
|
|
555
|
+
}
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
if (hidden(id))
|
|
559
|
+
return;
|
|
560
|
+
if (id === "projects" || id === "values")
|
|
561
|
+
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
|
+
}
|
|
574
|
+
start(ONBOARD_TITLES[id]);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
180
577
|
const next = ["machine", "cli", "skills", "legacy"].includes(id)
|
|
181
578
|
? "This computer"
|
|
182
579
|
: id === "signin"
|
|
@@ -185,7 +582,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
185
582
|
? "Connections"
|
|
186
583
|
: ["projects", "accounts", "settings", "values"].includes(id)
|
|
187
584
|
? "Project setup"
|
|
188
|
-
: ["capacity", "daemon", "housekeeping"].includes(id)
|
|
585
|
+
: ["capacity", "runner", "daemon", "housekeeping"].includes(id)
|
|
189
586
|
? "Runner and services"
|
|
190
587
|
: "Work and readiness";
|
|
191
588
|
if (next !== group) {
|
|
@@ -195,8 +592,62 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
195
592
|
start(ONBOARD_TITLES[id]);
|
|
196
593
|
},
|
|
197
594
|
stepEnd(step, journal) {
|
|
595
|
+
if (renderer) {
|
|
596
|
+
if (standaloneComputer(step.id)) {
|
|
597
|
+
machineChecks.set(step.id, step);
|
|
598
|
+
if (step.state === "failed" || step.state === "waiting") {
|
|
599
|
+
stop();
|
|
600
|
+
machineBlocked = true;
|
|
601
|
+
const view = setupStepView(step, journal);
|
|
602
|
+
renderer.step(view.mark, view.number, view.title, view.outcome);
|
|
603
|
+
renderer.detail(deps.logPath?.()
|
|
604
|
+
? "Check the full log for the cause, then try this step again."
|
|
605
|
+
: "Finish the action below, then check again.");
|
|
606
|
+
active = false;
|
|
607
|
+
}
|
|
608
|
+
else if (!machineBlocked &&
|
|
609
|
+
machineScope.length > 0 &&
|
|
610
|
+
machineScope.every((id) => stepSatisfied(machineChecks.get(id)))) {
|
|
611
|
+
renderer.step("done", 1, "This computer", machineScope.length === COMPUTER_CHECKS.length
|
|
612
|
+
? machineChecks.get("legacy")?.state === "done"
|
|
613
|
+
? "the command and skills are ready; no earlier install remains, data folders kept"
|
|
614
|
+
: "the command and skills are ready"
|
|
615
|
+
: "the selected checks passed");
|
|
616
|
+
active = false;
|
|
617
|
+
}
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
if (step.id === "ready" || (hidden(step.id) && step.state !== "failed"))
|
|
621
|
+
return;
|
|
622
|
+
if ((step.id === "github.repos" || step.id === "settings") &&
|
|
623
|
+
step.state === "done")
|
|
624
|
+
return;
|
|
625
|
+
if ((step.id === "projects" || step.id === "values") &&
|
|
626
|
+
journal?.steps.find((s) => s.id === (step.id === "projects" ? "github.repos" : "settings"))?.state !== "done")
|
|
627
|
+
return;
|
|
628
|
+
const view = setupStepView(step, journal);
|
|
629
|
+
if (hidden(step.id)) {
|
|
630
|
+
renderer.heading("This computer");
|
|
631
|
+
group = "This computer";
|
|
632
|
+
}
|
|
633
|
+
renderer.step(view.mark, view.number, view.title, view.outcome);
|
|
634
|
+
const granted = deps.verbose ? onboardStepDetail(step) : undefined;
|
|
635
|
+
if (granted)
|
|
636
|
+
renderer.detail(granted);
|
|
637
|
+
if (deps.verbose && CONNECTION_REPAIR_REASONS.has(step.reason ?? ""))
|
|
638
|
+
renderer.detail(onboardReasonText(step, { baseUrl: deps.baseUrl?.(), journal }));
|
|
639
|
+
if (view.mark === "fail")
|
|
640
|
+
renderer.detail(deps.logPath?.()
|
|
641
|
+
? "Check the full log for the cause, then try this step again."
|
|
642
|
+
: "Finish the action below, then check again.");
|
|
643
|
+
active = false;
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
198
646
|
stop();
|
|
199
|
-
const
|
|
647
|
+
const detail = step.reason
|
|
648
|
+
? onboardStepAction(journal, step, deps.baseUrl?.())
|
|
649
|
+
: undefined;
|
|
650
|
+
const text = `${ONBOARD_TITLES[step.id]}${detail ? `: ${detail}` : ""}`;
|
|
200
651
|
const kind = step.state === "done"
|
|
201
652
|
? "info"
|
|
202
653
|
: step.state === "failed"
|
|
@@ -207,9 +658,72 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
207
658
|
prompts.log[kind](`${step.state === "done" ? "✓ " : ""}${text}`, {
|
|
208
659
|
output: streams.output,
|
|
209
660
|
});
|
|
661
|
+
const granted = deps.verbose ? onboardStepDetail(step) : undefined;
|
|
662
|
+
if (granted)
|
|
663
|
+
prompts.log.message(granted, { output: streams.output });
|
|
210
664
|
},
|
|
211
665
|
message,
|
|
666
|
+
async checkAgain(journal) {
|
|
667
|
+
if (!renderer ||
|
|
668
|
+
!interactive ||
|
|
669
|
+
abort.signal.aborted ||
|
|
670
|
+
!setupFinalScreen(journal, deps.baseUrl?.()).actions.length)
|
|
671
|
+
return false;
|
|
672
|
+
active = false;
|
|
673
|
+
summary(journal);
|
|
674
|
+
summaryShown = true;
|
|
675
|
+
const answer = await select({
|
|
676
|
+
...options,
|
|
677
|
+
message: "Done with these?",
|
|
678
|
+
initialValue: "again",
|
|
679
|
+
options: [
|
|
680
|
+
{ value: "again", label: "Check again now" },
|
|
681
|
+
{ value: "stop", label: "Stop here" },
|
|
682
|
+
],
|
|
683
|
+
}, true);
|
|
684
|
+
if (prompts.isCancel(answer)) {
|
|
685
|
+
abort.abort();
|
|
686
|
+
return false;
|
|
687
|
+
}
|
|
688
|
+
if (answer !== "again")
|
|
689
|
+
return false;
|
|
690
|
+
summaryShown = false;
|
|
691
|
+
machineScope = COMPUTER_CHECKS.filter((id) => !stepSatisfied(journal.steps.find((step) => step.id === id)));
|
|
692
|
+
machineChecks.clear();
|
|
693
|
+
machineBlocked = false;
|
|
694
|
+
group = undefined;
|
|
695
|
+
renderer.heading("Checking again");
|
|
696
|
+
return true;
|
|
697
|
+
},
|
|
212
698
|
finish(journal, only) {
|
|
699
|
+
if (renderer && only) {
|
|
700
|
+
stop();
|
|
701
|
+
renderer.heading(abort.signal.aborted
|
|
702
|
+
? "Setup paused"
|
|
703
|
+
: journal.exit === 0
|
|
704
|
+
? "Step complete"
|
|
705
|
+
: "Step needs you");
|
|
706
|
+
if (journal.exit === 0)
|
|
707
|
+
renderer.line(`${ONBOARD_TITLES[only]} finished. Onboarding still has other steps.`);
|
|
708
|
+
else
|
|
709
|
+
for (const action of onboardNextActions(journal, deps.baseUrl?.(), only))
|
|
710
|
+
renderer.line(action);
|
|
711
|
+
renderer.outro("Next: run catalyst onboard");
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
if (renderer) {
|
|
715
|
+
active = false;
|
|
716
|
+
const screen = summaryShown
|
|
717
|
+
? setupFinalScreen(journal, deps.baseUrl?.(), abort.signal.aborted)
|
|
718
|
+
: summary(journal);
|
|
719
|
+
if (summaryShown && screen.paused) {
|
|
720
|
+
renderer.heading(screen.heading);
|
|
721
|
+
renderer.line("Your progress is saved.");
|
|
722
|
+
}
|
|
723
|
+
summaryShown = false;
|
|
724
|
+
renderer.outro(screen.next);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
213
727
|
stop();
|
|
214
728
|
let text = [
|
|
215
729
|
"Setup still needs these steps:",
|
|
@@ -232,12 +746,80 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
232
746
|
prompts.outro(text, { output: streams.output });
|
|
233
747
|
},
|
|
234
748
|
async wait(text, run) {
|
|
235
|
-
|
|
749
|
+
const browser = currentStep &&
|
|
750
|
+
renderer &&
|
|
751
|
+
setupBrowserInstruction(currentStep, deps.baseUrl?.(), currentStep !== "accounts" && browserOpened);
|
|
752
|
+
const signinWait = Boolean(renderer && currentStep === "signin" && signinShown);
|
|
753
|
+
const seconds = signinWait
|
|
754
|
+
? Math.ceil((deps.signinTimeoutMs ?? 600000) / 1000)
|
|
755
|
+
: 600;
|
|
756
|
+
const started = signinWait ? signinStarted : Date.now();
|
|
757
|
+
const status = () => {
|
|
758
|
+
const remaining = Math.max(0, seconds - Math.floor((Date.now() - started) / 1000));
|
|
759
|
+
return `waiting for you · ${Math.floor(remaining / 60)}:${String(remaining % 60).padStart(2, "0")} left`;
|
|
760
|
+
};
|
|
761
|
+
if (renderer && browser && currentStep) {
|
|
762
|
+
stop();
|
|
763
|
+
renderer.begin(SETUP_NUMBERS[currentStep] ?? 0, title(currentStep), renderer.traits.unicode
|
|
764
|
+
? status()
|
|
765
|
+
: "waiting for you, up to 10 minutes");
|
|
766
|
+
if (browser.preparation)
|
|
767
|
+
renderer.detail(browser.preparation);
|
|
768
|
+
renderer.action(browser.instruction);
|
|
769
|
+
if (browser.url)
|
|
770
|
+
renderer.detail(renderer.link(browser.url));
|
|
771
|
+
renderer.detail(renderer.dim(interactive ? "Ctrl-C skips this step." : "Ctrl-C stops setup."));
|
|
772
|
+
active = true;
|
|
773
|
+
}
|
|
774
|
+
else if (renderer && signinWait) {
|
|
775
|
+
if (renderer.traits.unicode) {
|
|
776
|
+
renderer.update(status());
|
|
777
|
+
renderer.detail("Ctrl-C stops setup.");
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
const minutes = Math.max(0, Math.ceil((started + seconds * 1000 - Date.now()) / 60000));
|
|
781
|
+
renderer.detail(`waiting for you${minutes ? `, up to ${minutes} minute${minutes === 1 ? "" : "s"}` : ""}. Ctrl-C stops setup.`);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
else
|
|
785
|
+
start(text);
|
|
786
|
+
const timer = renderer && (browser || signinWait)
|
|
787
|
+
? renderer.traits.unicode
|
|
788
|
+
? setInterval(() => renderer.update(status()), 1000)
|
|
789
|
+
: setInterval(() => {
|
|
790
|
+
const minutes = Math.max(0, Math.ceil((seconds * 1000 - (Date.now() - started)) / 60000));
|
|
791
|
+
if (minutes > 0)
|
|
792
|
+
renderer.detail(`Still waiting, ${minutes} minute${minutes === 1 ? "" : "s"} left.`);
|
|
793
|
+
}, 60000)
|
|
794
|
+
: undefined;
|
|
795
|
+
timer?.unref();
|
|
796
|
+
interrupts.waiting(interactive);
|
|
236
797
|
try {
|
|
237
798
|
return await run();
|
|
238
799
|
}
|
|
239
800
|
finally {
|
|
240
|
-
|
|
801
|
+
if (timer)
|
|
802
|
+
clearInterval(timer);
|
|
803
|
+
interrupts.waiting(false);
|
|
804
|
+
if (renderer &&
|
|
805
|
+
browser &&
|
|
806
|
+
interrupts.signal.aborted &&
|
|
807
|
+
!abort.signal.aborted) {
|
|
808
|
+
renderer.replaceLastDetail(renderer.dim("Press Ctrl-C again to stop setup."));
|
|
809
|
+
await new Promise((resolve) => {
|
|
810
|
+
const done = () => {
|
|
811
|
+
clearTimeout(timeout);
|
|
812
|
+
abort.signal.removeEventListener("abort", done);
|
|
813
|
+
resolve();
|
|
814
|
+
};
|
|
815
|
+
const timeout = setTimeout(done, 2000);
|
|
816
|
+
abort.signal.addEventListener("abort", done, { once: true });
|
|
817
|
+
if (abort.signal.aborted)
|
|
818
|
+
done();
|
|
819
|
+
});
|
|
820
|
+
}
|
|
821
|
+
if (!renderer)
|
|
822
|
+
stop();
|
|
241
823
|
}
|
|
242
824
|
},
|
|
243
825
|
dispose() {
|
|
@@ -248,12 +830,24 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
248
830
|
abort.abort();
|
|
249
831
|
try {
|
|
250
832
|
spin.dispose();
|
|
833
|
+
if (deps.disposeRenderer)
|
|
834
|
+
renderer?.dispose();
|
|
251
835
|
}
|
|
252
836
|
finally {
|
|
253
837
|
for (const event of ["SIGINT", "SIGTERM", "SIGHUP"])
|
|
254
|
-
signals.off(event,
|
|
838
|
+
signals.off(event, handlers[event]);
|
|
255
839
|
}
|
|
256
840
|
}
|
|
257
841
|
},
|
|
258
842
|
};
|
|
843
|
+
if (!interactive) {
|
|
844
|
+
delete ui.chooseTeam;
|
|
845
|
+
delete ui.nameNewTeam;
|
|
846
|
+
delete ui.chooseRepositories;
|
|
847
|
+
delete ui.chooseFirstRepository;
|
|
848
|
+
delete ui.reviewSettings;
|
|
849
|
+
delete ui.chooseRunner;
|
|
850
|
+
delete ui.confirmWorkflowAdoption;
|
|
851
|
+
}
|
|
852
|
+
return ui;
|
|
259
853
|
}
|