@catalyst-cloud/cli 0.16.0 → 0.16.2
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 +9 -6
- package/dist/args.js +6 -0
- package/dist/capabilities.js +3 -1
- package/dist/cli.js +10 -5
- package/dist/environment.js +1 -1
- package/dist/execution.js +2 -2
- package/dist/hosts.js +89 -0
- package/dist/legacy.js +12 -2
- package/dist/machine-inventory.js +37 -0
- package/dist/onboard-capacity.js +5 -6
- package/dist/onboard-headless.js +2 -2
- package/dist/onboard-next.js +18 -10
- package/dist/onboard-project-cleanup.js +100 -0
- package/dist/onboard-returning.js +172 -0
- package/dist/onboard-runner.js +149 -34
- package/dist/onboard-runtime.js +26 -1
- package/dist/onboard-ui.js +61 -0
- package/dist/onboard.js +9 -2
- package/dist/ready-copy.js +30 -24
- package/dist/ready.js +2 -2
- package/dist/secret.js +2 -2
- package/dist/setup-onboard-copy.js +37 -32
- package/dist/setup.js +4 -0
- package/dist/transport.js +18 -2
- package/package.json +3 -3
- 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/connecting-this-machine.md +4 -4
- package/skills/catalyst-onboard/references/declaring-a-repository.md +1 -1
- package/skills/catalyst-onboard/references/reading-ready.md +13 -13
- package/skills/catalyst-onboard/references/replacing-a-credential.md +1 -1
- package/skills/catalyst-onboard/references/the-one-path.md +5 -5
- package/skills/catalyst-onboard/references/what-a-phase-needs.md +4 -4
- package/skills/catalyst-onboard/references/what-the-browser-owns.md +20 -10
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +49 -26
- package/skills/run-this-project/SKILL.md +1 -1
- package/skills/run-this-project/references/stalls-and-escalation.md +1 -1
- package/skills/unstick/SKILL.md +1 -1
- package/skills/what-needs-me/SKILL.md +1 -1
- package/skills/what-needs-me/references/raising-a-decision.md +1 -1
- package/skills/what-needs-me/references/reading-the-inbox.md +1 -1
- package/skills/what-this-repo-needs/SKILL.md +1 -1
- package/skills/whats-happening/SKILL.md +3 -2
- package/skills/whats-happening/references/coding-accounts.md +1 -1
- package/skills/whats-happening/references/machines.md +9 -0
- package/skills/whats-happening/references/settings-and-where-they-live.md +8 -8
- package/skills/whats-happening/references/stages-and-mapping.md +1 -1
package/dist/onboard-ui.js
CHANGED
|
@@ -444,6 +444,49 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
444
444
|
...(answer === "signin" ? { signin: true } : {}),
|
|
445
445
|
};
|
|
446
446
|
},
|
|
447
|
+
async reviewProjects(projects) {
|
|
448
|
+
stop();
|
|
449
|
+
if (abort.signal.aborted)
|
|
450
|
+
return null;
|
|
451
|
+
const answer = await select({
|
|
452
|
+
...options,
|
|
453
|
+
message: "What should setup do with this workspace's projects?",
|
|
454
|
+
initialValue: "move-on",
|
|
455
|
+
options: [
|
|
456
|
+
{ value: "move-on", label: "Move on and finish this computer's setup", hint: "default" },
|
|
457
|
+
...projects.map(p => ({ value: `repair:${p.id}`, label: `Review or repair ${p.name} (${p.key})` })),
|
|
458
|
+
...projects.map(p => ({ value: `cleanup:${p.id}`, label: `Preview archiving ${p.name} (${p.key})` })),
|
|
459
|
+
{ value: "new", label: "Create a new Catalyst project" },
|
|
460
|
+
],
|
|
461
|
+
});
|
|
462
|
+
if (prompts.isCancel(answer)) {
|
|
463
|
+
abort.abort();
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
if (answer === "move-on" || answer === "new")
|
|
467
|
+
return answer;
|
|
468
|
+
if (typeof answer === "string" && answer.startsWith("cleanup:"))
|
|
469
|
+
return { cleanup: answer.slice(8) };
|
|
470
|
+
return typeof answer === "string" && answer.startsWith("repair:") ? { repair: answer.slice(7) } : null;
|
|
471
|
+
},
|
|
472
|
+
async confirmProjectCleanup(project, lines) {
|
|
473
|
+
stop();
|
|
474
|
+
for (const line of lines)
|
|
475
|
+
message(line);
|
|
476
|
+
if (abort.signal.aborted)
|
|
477
|
+
return false;
|
|
478
|
+
const answer = await select({ ...options,
|
|
479
|
+
message: `Archive ${project.name} after reviewing these changes?`,
|
|
480
|
+
initialValue: "keep",
|
|
481
|
+
options: [{ value: "keep", label: "Keep the project and finish this computer's setup" },
|
|
482
|
+
{ value: "archive", label: "Archive this project" }],
|
|
483
|
+
});
|
|
484
|
+
if (prompts.isCancel(answer)) {
|
|
485
|
+
abort.abort();
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
return answer === "archive";
|
|
489
|
+
},
|
|
447
490
|
async chooseTeam(teams, create) {
|
|
448
491
|
stop();
|
|
449
492
|
// Creating needs a name prompt; a port without one offers only the existing teams.
|
|
@@ -671,6 +714,23 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
671
714
|
}
|
|
672
715
|
return typeof answer === "string" ? answer : null;
|
|
673
716
|
},
|
|
717
|
+
async nameMachine(defaultName) {
|
|
718
|
+
if (abort.signal.aborted)
|
|
719
|
+
return null;
|
|
720
|
+
const answer = await text({
|
|
721
|
+
...options,
|
|
722
|
+
message: "Name for this machine",
|
|
723
|
+
initialValue: defaultName,
|
|
724
|
+
validate: (value) => /^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$/.test(value ?? "")
|
|
725
|
+
? undefined
|
|
726
|
+
: "Use 1 to 63 letters, digits, dots, underscores or hyphens, starting with a letter or digit.",
|
|
727
|
+
});
|
|
728
|
+
if (typeof answer !== "string") {
|
|
729
|
+
abort.abort();
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
return answer;
|
|
733
|
+
},
|
|
674
734
|
async chooseRunner() {
|
|
675
735
|
if (abort.signal.aborted)
|
|
676
736
|
return null;
|
|
@@ -1114,6 +1174,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
1114
1174
|
delete ui.chooseFirstRepository;
|
|
1115
1175
|
delete ui.reviewSettings;
|
|
1116
1176
|
delete ui.chooseRunner;
|
|
1177
|
+
delete ui.nameMachine;
|
|
1117
1178
|
delete ui.chooseFirstTicket;
|
|
1118
1179
|
delete ui.retryTimedOut;
|
|
1119
1180
|
delete ui.confirmWorkflowAdoption;
|
package/dist/onboard.js
CHANGED
|
@@ -268,6 +268,7 @@ function normalizeStep(value, fallbackAt) {
|
|
|
268
268
|
"membershipId",
|
|
269
269
|
"checkedAt",
|
|
270
270
|
"project",
|
|
271
|
+
"projectCount",
|
|
271
272
|
"repository",
|
|
272
273
|
"installation",
|
|
273
274
|
"ticket",
|
|
@@ -673,6 +674,7 @@ export function stepSatisfied(step) {
|
|
|
673
674
|
// Optional: a computer without the daily update still runs work.
|
|
674
675
|
step.id === "housekeeping" ||
|
|
675
676
|
step.reason === "member_scope" ||
|
|
677
|
+
step.reason === "returning_workspace_move_on" ||
|
|
676
678
|
(step.id === "daemon" && step.reason === "local_sync_not_selected") ||
|
|
677
679
|
(step.id === "runner" && step.reason === "runner_not_selected") ||
|
|
678
680
|
(step.id === "first-ticket" && step.reason === "first_ticket_skipped") ||
|
|
@@ -711,7 +713,8 @@ export function onboardRequiredStepsWaiting(journal) {
|
|
|
711
713
|
}
|
|
712
714
|
/** A full run that exited 0 with deferred steps still open: ready for work, not complete. */
|
|
713
715
|
export function onboardReadyForWork(journal, only) {
|
|
714
|
-
return !only && journal.exit === 0 && !journal.complete
|
|
716
|
+
return !only && journal.exit === 0 && !journal.complete &&
|
|
717
|
+
!journal.steps.some(step => step.reason === "returning_workspace_move_on");
|
|
715
718
|
}
|
|
716
719
|
/** A recorded step's next action; a prerequisite wait names the first unfinished step it needs. */
|
|
717
720
|
export function onboardStepAction(journal, step, baseUrl) {
|
|
@@ -1143,7 +1146,11 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1143
1146
|
else if (deps.ui)
|
|
1144
1147
|
deps.ui.finish(journal, only);
|
|
1145
1148
|
else {
|
|
1146
|
-
if (journal.complete)
|
|
1149
|
+
if (journal.complete && journal.steps.some(step => step.reason === "returning_workspace_move_on")) {
|
|
1150
|
+
ctx.stdout("This computer is set up. Existing projects were kept in place.");
|
|
1151
|
+
ctx.stdout("Next: run catalyst ready to review their checks.");
|
|
1152
|
+
}
|
|
1153
|
+
else if (journal.complete)
|
|
1147
1154
|
ctx.stdout("Onboarding complete.");
|
|
1148
1155
|
else if (onboardReadyForWork(journal, only) &&
|
|
1149
1156
|
(deps.requiredSteps ?? []).every((id) => journalStep(journal, id)?.state === "done")) {
|
package/dist/ready-copy.js
CHANGED
|
@@ -90,11 +90,11 @@ export function valuesFacts(value) {
|
|
|
90
90
|
: [],
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
/** How the named variables get their values: the CLI's own verbs, which read each value from a
|
|
94
|
+
* hidden prompt. Names only: nothing here reads or prints a value. */
|
|
95
|
+
function setValuesFix(names) {
|
|
96
|
+
const name = names.length === 1 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(names[0]) ? names[0] : "NAME";
|
|
97
|
+
return `set ${names.join(", ")} with catalyst var set ${name} --repo <owner/name> (catalyst secret set ${name} --repo <owner/name> for a secret)`;
|
|
98
98
|
}
|
|
99
99
|
function nameList(v) {
|
|
100
100
|
return Array.isArray(v)
|
|
@@ -127,13 +127,13 @@ function unresolvedLine(u) {
|
|
|
127
127
|
* identifier or a repository name, never a value, and nothing here reads a value. Any other check
|
|
128
128
|
* keeps the generic line. Shared with setup's values step, which has no web address when the
|
|
129
129
|
* saved login has none. */
|
|
130
|
-
export function valuesFix(
|
|
130
|
+
export function valuesFix(_base, c) {
|
|
131
131
|
const unresolved = unresolvedList(c.unresolved);
|
|
132
132
|
const unresolvedNames = new Set(unresolved.map((u) => u.name));
|
|
133
133
|
const names = nameList(c.names).filter((n) => !unresolvedNames.has(n));
|
|
134
134
|
const parts = [];
|
|
135
135
|
if (names.length > 0)
|
|
136
|
-
parts.push(setValuesFix(
|
|
136
|
+
parts.push(setValuesFix(names));
|
|
137
137
|
for (const u of unresolved)
|
|
138
138
|
parts.push(unresolvedLine(u));
|
|
139
139
|
for (const note of Array.isArray(c.repos) ? c.repos : []) {
|
|
@@ -152,10 +152,6 @@ export function valuesFix(base, c) {
|
|
|
152
152
|
}
|
|
153
153
|
return parts.length === 0 ? null : parts.join(". ");
|
|
154
154
|
}
|
|
155
|
-
/** The team's own page in Catalyst settings, optionally one of its sub-pages (`map`, `adopt`). */
|
|
156
|
-
export function teamPage(baseUrl, key, sub) {
|
|
157
|
-
return `${origin(baseUrl)}/settings/linear-teams/${encodeURIComponent(key)}${sub ? `/${sub}` : ""}`;
|
|
158
|
-
}
|
|
159
155
|
/** The dispatch gate: whether Catalyst may start any ticket in the team. */
|
|
160
156
|
export function dispatchGateCopy(baseUrl, key, status, missingSlots) {
|
|
161
157
|
if (status === "open")
|
|
@@ -170,18 +166,18 @@ export function dispatchGateCopy(baseUrl, key, status, missingSlots) {
|
|
|
170
166
|
if (status === "mapping_missing")
|
|
171
167
|
return {
|
|
172
168
|
line: `team ${key}: Catalyst doesn't know which ${key} stages to use for ${slots}. Until it does, it starts no ${key} tickets.`,
|
|
173
|
-
fix: `
|
|
169
|
+
fix: `run catalyst team map ${key}`,
|
|
174
170
|
};
|
|
175
171
|
if (status === "mapping_state_unresolved")
|
|
176
172
|
// The gate reads Catalyst's own copy of the team, which can lag Linear by a minute after a team
|
|
177
173
|
// is created or its workflow is set up (CTC-4680 Rerun A). Say that, not "map your stages".
|
|
178
174
|
return {
|
|
179
175
|
line: `team ${key}: Catalyst can't find the stages it uses for ${slots} in its copy of ${key} yet. Until it can, it starts no ${key} tickets.`,
|
|
180
|
-
fix: `if you just set up ${key}, run catalyst ready again in a minute. If this stays,
|
|
176
|
+
fix: `if you just set up ${key}, run catalyst ready again in a minute. If this stays, run catalyst team check ${key}`,
|
|
181
177
|
};
|
|
182
178
|
return {
|
|
183
179
|
line: `team ${key}: Catalyst can't start ${key} tickets yet.`,
|
|
184
|
-
fix: `
|
|
180
|
+
fix: `run catalyst team check ${key} to see why`,
|
|
185
181
|
};
|
|
186
182
|
}
|
|
187
183
|
const AUTOMATION_RULES = {
|
|
@@ -237,6 +233,13 @@ const CHECK_PAGES = {
|
|
|
237
233
|
github_app_installed: "/settings/projects",
|
|
238
234
|
thoughts_reachable: "/settings/projects",
|
|
239
235
|
};
|
|
236
|
+
/** The command that fixes a check, offered before its page. A check with no command keeps the page. */
|
|
237
|
+
const CHECK_COMMANDS = {
|
|
238
|
+
webhook_covers_team: (key) => `an owner or admin registers the repository with catalyst onboard --team ${key} --repo <owner/name>`,
|
|
239
|
+
hosts_current: () => "run catalyst onboard --runner on the machine that runs the work",
|
|
240
|
+
tools_resolvable: () => "set the missing secret with catalyst secret set NAME --repo <owner/name>, or fix the repository's .catalyst/catalyst.toml",
|
|
241
|
+
reviewer_required: () => "relax the merge policy with catalyst var set CATALYST_MERGE_EVIDENCE_POLICY --repo <owner/name> (if .catalyst/catalyst.toml declares the policy, change it there instead, because the declaration wins), or configure a reviewer",
|
|
242
|
+
};
|
|
240
243
|
const AGAIN = "run catalyst ready again in a few minutes";
|
|
241
244
|
/** One failing or unknown team check, in plain words. `valuesFix` is the variable-naming fix the
|
|
242
245
|
* caller already built for `required_values`, or null when the cloud named no variable. */
|
|
@@ -258,17 +261,17 @@ export function teamCheckCopy(baseUrl, key, id, state, valuesFix) {
|
|
|
258
261
|
switch (id) {
|
|
259
262
|
case "oauth_scope":
|
|
260
263
|
return unknown
|
|
261
|
-
? { line: team("Catalyst couldn't check its Linear connection."), fix:
|
|
264
|
+
? { line: team("Catalyst couldn't check its Linear connection."), fix: "run catalyst onboard to check the Linear connection" }
|
|
262
265
|
: {
|
|
263
266
|
line: team("Catalyst's Linear connection is missing a permission it needs."),
|
|
264
|
-
fix:
|
|
267
|
+
fix: "run catalyst onboard and have an owner or admin approve the Linear link it prints",
|
|
265
268
|
};
|
|
266
269
|
case "token_live":
|
|
267
270
|
return unknown
|
|
268
271
|
? { line: team("Catalyst couldn't reach Linear to check its connection."), fix: AGAIN }
|
|
269
272
|
: {
|
|
270
273
|
line: team("Linear stopped accepting Catalyst's connection. It was revoked or expired."),
|
|
271
|
-
fix:
|
|
274
|
+
fix: "run catalyst onboard and have an owner or admin approve the Linear link it prints",
|
|
272
275
|
};
|
|
273
276
|
case "team_visible":
|
|
274
277
|
return unknown
|
|
@@ -285,16 +288,16 @@ export function teamCheckCopy(baseUrl, key, id, state, valuesFix) {
|
|
|
285
288
|
if (id === "mapped_states_exist")
|
|
286
289
|
return {
|
|
287
290
|
line: team(`A stage Catalyst uses in ${key} was deleted or renamed in Linear.`),
|
|
288
|
-
fix: `pick its replacement
|
|
291
|
+
fix: `pick its replacement with catalyst team map ${key}`,
|
|
289
292
|
};
|
|
290
293
|
if (id === "mapping_total")
|
|
291
294
|
return {
|
|
292
295
|
line: team(`${key}'s stages aren't all mapped yet, so Catalyst doesn't know where to move its tickets.`),
|
|
293
|
-
fix: `map them
|
|
296
|
+
fix: `map them with catalyst team map ${key}, or set up Catalyst's workflow with catalyst team adopt ${key}`,
|
|
294
297
|
};
|
|
295
298
|
return {
|
|
296
299
|
line: team(`A stage mapped in ${key} is the wrong kind, for example a Done stage that Linear treats as in progress.`),
|
|
297
|
-
fix: `choose the right stages
|
|
300
|
+
fix: `choose the right stages with catalyst team map ${key}`,
|
|
298
301
|
};
|
|
299
302
|
case "required_values":
|
|
300
303
|
return unknown
|
|
@@ -304,22 +307,25 @@ export function teamCheckCopy(baseUrl, key, id, state, valuesFix) {
|
|
|
304
307
|
}
|
|
305
308
|
: {
|
|
306
309
|
line: team(`A variable ${key}'s repository needs has no value, so Catalyst can't run its work.`),
|
|
307
|
-
fix: valuesFix ??
|
|
310
|
+
fix: valuesFix ?? setValuesFix(["the missing values"]),
|
|
308
311
|
};
|
|
309
312
|
case "coding_account_enrolled":
|
|
310
313
|
return unknown
|
|
311
314
|
? { line: team("Catalyst couldn't check your AI accounts."), fix: AGAIN }
|
|
312
315
|
: {
|
|
313
316
|
line: team("Catalyst has no AI account to do the work with."),
|
|
314
|
-
fix: `
|
|
317
|
+
fix: `an owner or admin adds one at ${base}/settings/coding-accounts; there is no command for that yet`,
|
|
315
318
|
};
|
|
316
319
|
}
|
|
317
320
|
const name = CHECK_NAMES[id] ?? id.replaceAll("_", " ");
|
|
318
|
-
const page = CHECK_PAGES[id]
|
|
321
|
+
const page = CHECK_PAGES[id];
|
|
322
|
+
const command = CHECK_COMMANDS[id]?.(key);
|
|
319
323
|
return unknown
|
|
320
324
|
? { line: team(`Catalyst hasn't checked ${name} yet.`), fix: AGAIN }
|
|
321
325
|
: {
|
|
322
326
|
line: team(`${name.charAt(0).toUpperCase()}${name.slice(1)} ${PLURAL.has(id) ? "need" : "needs"} attention.`),
|
|
323
|
-
fix:
|
|
327
|
+
fix: command
|
|
328
|
+
? page ? `${command}, or open ${base}${page}` : command
|
|
329
|
+
: page ? `open ${base}${page}` : `run catalyst team check ${key} to see why`,
|
|
324
330
|
};
|
|
325
331
|
}
|
package/dist/ready.js
CHANGED
|
@@ -15,7 +15,7 @@ import { FIRST_STAMPED_VERSION } from "./skill-shape.js";
|
|
|
15
15
|
import { installedBundleVersion } from "./skills.js";
|
|
16
16
|
import { onboardingReadyReport, observeCloudOnboarding, } from "./onboard-ready.js";
|
|
17
17
|
import { selectedOnboardTeam } from "./onboard-existing.js";
|
|
18
|
-
import { dispatchGateCopy, teamCheckCopy,
|
|
18
|
+
import { dispatchGateCopy, teamCheckCopy, valuesFix, whoFixes } from "./ready-copy.js";
|
|
19
19
|
import { onboardStatePath, readOnboardJournal } from "./onboard.js";
|
|
20
20
|
export { semverOlder };
|
|
21
21
|
/** `ready`'s own wording for the replica. It never recommends adopting the replica — until the
|
|
@@ -393,7 +393,7 @@ export async function readyReport(ctx, deps) {
|
|
|
393
393
|
line: team.readiness.status === "degraded"
|
|
394
394
|
? `team ${label}: works, with some optional settings still missing`
|
|
395
395
|
: `team ${label}: not ready for work yet`,
|
|
396
|
-
fix: `
|
|
396
|
+
fix: `run catalyst team check ${label} to see what is missing`,
|
|
397
397
|
who: whoFixes("mapping_total"),
|
|
398
398
|
});
|
|
399
399
|
}
|
package/dist/secret.js
CHANGED
|
@@ -36,7 +36,7 @@ const IMPORT_REASONS = {
|
|
|
36
36
|
name_taken_by_env_var: "already a plain environment variable; a name is a secret or a variable, never both",
|
|
37
37
|
invalid_name: "not an env-style name (A-Z, 0-9 and _, starting with a letter)",
|
|
38
38
|
invalid_value: "the value is empty",
|
|
39
|
-
config_rotation_unconfirmed: "the existing entry is a
|
|
39
|
+
config_rotation_unconfirmed: "the existing entry is a plain variable; change it with catalyst var set",
|
|
40
40
|
probe_refused: "the value failed this kind's check",
|
|
41
41
|
};
|
|
42
42
|
/** A command's stdout, minus the one trailing newline almost every command prints after a value. */
|
|
@@ -74,7 +74,7 @@ function refusalLine(status, body, repo) {
|
|
|
74
74
|
return "refused (401): the cloud did not accept your credential here. If `catalyst me` works, this cloud predates secret writes from the CLI and needs a newer release; otherwise log in again.";
|
|
75
75
|
}
|
|
76
76
|
if (status === 404)
|
|
77
|
-
return `refused (404): ${repo} is not a repository of this
|
|
77
|
+
return `refused (404): ${repo} is not a repository of this workspace. Register it first with catalyst onboard --team <KEY> --repo ${repo}`;
|
|
78
78
|
if (status === 409 && body.error === "registry_not_migrated") {
|
|
79
79
|
return "refused (409): the cloud's secret audit is not set up yet, so nothing was stored. Tell your Catalyst operator.";
|
|
80
80
|
}
|
|
@@ -508,9 +508,7 @@ function action(step, base, journal) {
|
|
|
508
508
|
case "linear.adopt": {
|
|
509
509
|
const team = journal?.steps.find((row) => row.id === "linear.team" && row.state === "done");
|
|
510
510
|
const key = team && value(team, "teamKey");
|
|
511
|
-
return
|
|
512
|
-
? `/settings/linear-teams/${encodeURIComponent(key)}/adopt`
|
|
513
|
-
: "/settings/linear-teams", "set up the team's Catalyst workflow");
|
|
511
|
+
return `Run catalyst team adopt ${key || "<TEAM KEY>"} to preview the team's Catalyst workflow, then run it again with the --yes --plan-hash value it prints.`;
|
|
514
512
|
}
|
|
515
513
|
case "linear.automations":
|
|
516
514
|
return "Open your Linear team settings and set each pull request automation to No action.";
|
|
@@ -572,17 +570,20 @@ export function setupFinalScreen(journal, base, paused = false, required = new S
|
|
|
572
570
|
: undefined
|
|
573
571
|
: clean(onboardReasonText(readiness, { baseUrl: base, journal }))
|
|
574
572
|
: undefined;
|
|
573
|
+
const machineOnly = journal.steps.some(step => step.reason === "returning_workspace_move_on");
|
|
575
574
|
const heading = paused
|
|
576
575
|
? "Setup paused"
|
|
577
576
|
: journal.steps.some((step) => step.id === "runner" &&
|
|
578
577
|
(step.evidence?.selected === true || required.has(step.id)) &&
|
|
579
578
|
step.state !== "done")
|
|
580
579
|
? "Not ready for work yet"
|
|
581
|
-
: journal.exit === 0 &&
|
|
582
|
-
? "
|
|
583
|
-
: journal.exit === 0
|
|
584
|
-
? "
|
|
585
|
-
:
|
|
580
|
+
: journal.exit === 0 && machineOnly
|
|
581
|
+
? "This computer is set up"
|
|
582
|
+
: journal.exit === 0 && journal.complete
|
|
583
|
+
? "Setup complete"
|
|
584
|
+
: journal.exit === 0
|
|
585
|
+
? "Ready for work"
|
|
586
|
+
: "Not ready for work yet";
|
|
586
587
|
const firstTicket = journal.steps.find((s) => s.id === "first-ticket");
|
|
587
588
|
const ticket = firstTicket?.state === "done" ? firstTicket : undefined;
|
|
588
589
|
const key = ticket && (value(ticket, "ticket") ?? value(ticket, "ticketKey"));
|
|
@@ -590,25 +591,27 @@ export function setupFinalScreen(journal, base, paused = false, required = new S
|
|
|
590
591
|
const ticketUrl = savedTicketUrl && page(base, savedTicketUrl);
|
|
591
592
|
const next = paused
|
|
592
593
|
? "Next: run catalyst onboard to continue."
|
|
593
|
-
:
|
|
594
|
-
?
|
|
595
|
-
:
|
|
596
|
-
? `
|
|
597
|
-
:
|
|
598
|
-
? `Next: ${
|
|
599
|
-
:
|
|
600
|
-
?
|
|
601
|
-
: personal.length ===
|
|
602
|
-
? "Next: run catalyst onboard after you finish 1
|
|
603
|
-
: personal.length
|
|
604
|
-
? "Next: run catalyst onboard after you finish 1
|
|
605
|
-
|
|
606
|
-
"
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
:
|
|
610
|
-
? "Next:
|
|
611
|
-
:
|
|
594
|
+
: machineOnly && journal.exit === 0
|
|
595
|
+
? "Next: run catalyst ready to review your existing projects."
|
|
596
|
+
: key
|
|
597
|
+
? `Follow ${ticketUrl ?? key} in Linear; Catalyst comments there as each phase finishes.`
|
|
598
|
+
: heading === "Setup complete"
|
|
599
|
+
? `Next: move a ticket to ${dispatchStageName(firstTicket?.evidence?.stage)} in Linear.`
|
|
600
|
+
: journal.exit === 0 && personal[0]
|
|
601
|
+
? `Next: ${personal[0].text[0].toLowerCase() + personal[0].text.slice(1)}`
|
|
602
|
+
: personal.length === 1
|
|
603
|
+
? "Next: run catalyst onboard after you finish 1."
|
|
604
|
+
: personal.length === 2
|
|
605
|
+
? "Next: run catalyst onboard after you finish 1 and 2."
|
|
606
|
+
: personal.length
|
|
607
|
+
? "Next: run catalyst onboard after you finish 1 to " +
|
|
608
|
+
personal.length +
|
|
609
|
+
"."
|
|
610
|
+
: actions.length
|
|
611
|
+
? "Next: nothing for you to do. catalyst ready shows when Catalyst has read the settings."
|
|
612
|
+
: readyStep?.reason === "member_team_required"
|
|
613
|
+
? "Next: run catalyst onboard --team <KEY> to check again."
|
|
614
|
+
: "Next: run catalyst onboard to check again.";
|
|
612
615
|
return {
|
|
613
616
|
heading,
|
|
614
617
|
readinessCause,
|
|
@@ -688,11 +691,13 @@ export function onboardJsonView(journal, base, paused = false, options = {}) {
|
|
|
688
691
|
? "paused"
|
|
689
692
|
: journal.exit === 10
|
|
690
693
|
? "failed"
|
|
691
|
-
: screen.heading === "
|
|
692
|
-
? "complete"
|
|
693
|
-
: screen.heading === "
|
|
694
|
-
? "
|
|
695
|
-
: "
|
|
694
|
+
: screen.heading === "This computer is set up"
|
|
695
|
+
? "machine-complete"
|
|
696
|
+
: screen.heading === "Setup complete"
|
|
697
|
+
? "complete"
|
|
698
|
+
: screen.heading === "Ready for work"
|
|
699
|
+
? "ready"
|
|
700
|
+
: "not-ready";
|
|
696
701
|
const actions = screen.actions.map((action) => {
|
|
697
702
|
const step = view.steps.find((step) => step.id === action.id);
|
|
698
703
|
const role = view.steps.find((step) => step.id === "signin" && step.state === "done")?.evidence?.role;
|
package/dist/setup.js
CHANGED
package/dist/transport.js
CHANGED
|
@@ -112,6 +112,22 @@ export class ApiClient {
|
|
|
112
112
|
await this.refuseIfNotOk(res, `PUT ${path}`);
|
|
113
113
|
return { status: res.status, body: await this.parseBody(res, `PUT ${path}`), headers: res.headers };
|
|
114
114
|
}
|
|
115
|
+
async deleteJson(path, opts = {}) {
|
|
116
|
+
const res = await this.send(this.url(path), {
|
|
117
|
+
method: "DELETE",
|
|
118
|
+
headers: {
|
|
119
|
+
authorization: `Bearer ${await this.credential()}`,
|
|
120
|
+
accept: "application/json",
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
if (!opts.accept?.includes(res.status))
|
|
124
|
+
await this.refuseIfNotOk(res, `DELETE ${path}`);
|
|
125
|
+
return {
|
|
126
|
+
status: res.status,
|
|
127
|
+
body: await this.parseBody(res, `DELETE ${path}`),
|
|
128
|
+
headers: res.headers,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
115
131
|
async send(url, init) {
|
|
116
132
|
try {
|
|
117
133
|
return await this.fetchImpl(url, { ...init, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
|
|
@@ -142,7 +158,7 @@ export class ApiClient {
|
|
|
142
158
|
reason = raw.slice(0, 200);
|
|
143
159
|
}
|
|
144
160
|
if (res.status === 401) {
|
|
145
|
-
throw new CliError(`${what} failed (401): credential not accepted
|
|
161
|
+
throw new CliError(`${what} failed (401): credential not accepted. Run catalyst login to sign in again; an unattended machine needs a new personal key from Settings → API keys`, "unauthorized", 2, 401);
|
|
146
162
|
}
|
|
147
163
|
if (res.status === 403) {
|
|
148
164
|
throw new CliError(`${what} failed (403): this key may not reach that route${reason ? ` (${reason})` : ""} — a route that acts as a host (claiming work, publishing artifacts) needs the tenant's account key; every read and write a person makes takes your personal key`, "forbidden", 2, 403);
|
|
@@ -183,7 +199,7 @@ export async function fetchMe(baseUrl, key, fetchImpl, timeoutMs = REQUEST_TIMEO
|
|
|
183
199
|
reason = "";
|
|
184
200
|
}
|
|
185
201
|
const detail = res.status === 401
|
|
186
|
-
? "credential not accepted
|
|
202
|
+
? "credential not accepted. Run catalyst login to sign in again; an unattended machine needs a new personal key from Settings → API keys"
|
|
187
203
|
: reason || `HTTP ${res.status}`;
|
|
188
204
|
throw new MeError(`GET /me failed (${res.status}): ${detail}`, "http", res.status);
|
|
189
205
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-cloud/cli",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.2",
|
|
4
4
|
"description": "Set up Catalyst Cloud, check your tickets and manage your account from the command line.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"prepublishOnly": "node scripts/release-train-check.mjs --publish cli"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@catalyst-cloud/sdk": "0.16.
|
|
56
|
+
"@catalyst-cloud/sdk": "0.16.1",
|
|
57
57
|
"@clack/prompts": "1.8.1",
|
|
58
58
|
"fast-string-width": "3.0.2",
|
|
59
59
|
"fast-wrap-ansi": "0.2.2",
|
|
60
60
|
"smol-toml": "^1.7.1",
|
|
61
61
|
"yaml": "^2.9.0",
|
|
62
|
-
"@catalyst-cloud/sdk-replica-node": "0.16.
|
|
62
|
+
"@catalyst-cloud/sdk-replica-node": "0.16.1"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@catalyst-cloud/schema": "0.16.0",
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Catalyst's GitHub: show a ticket's pull request with its checks, reviews and review threads, say whether it is mergeable under this repository's policy, and explain what a PR accumulates as the ticket moves (the branch, the draft, the rewrite, the force-pushes, the labels, the queue). Use when someone asks "show me the PR", "what are the checks saying", "why hasn't it merged", "what does the queue need", "what does that label mean", or wants the branch and merge conventions Catalyst follows.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.2 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Catalyst's GitHub
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Catalyst's view of Linear on the customer's own cloud account. Reads a ticket with its comments, relations, labels, linked pull requests and agent sessions inline, from the cloud API by default, with local replica reads available as an explicit opt-in, always naming the source; searches tickets, PRs, projects and initiatives; writes comments, card moves, labels and new tickets through the account's agent proxy as the app actor, with every route, stage id, label id and marker read from the account's contract. Knows what a ticket accumulates as Catalyst works it (phase-outcome comments, the document per phase, the agent session, the labels, the single blocks relation, the bookkeeping marker, the eyes acknowledgement). Use when a person says "show me the ticket", "what did Catalyst write on it", "comment on it", "move it", "label it" or "file a ticket". Not for raising a decision for a human (what-needs-me) and not for pull requests (catalyst-github).
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.2 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Catalyst Linear
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Set up Catalyst Cloud and say whether setup is working. Walks a person from nothing to their first ticket running, one step at a time: the coding account, the project, the integrations and connected accounts, each repository's settings file, then one real ticket moving. Also logs this machine in, reads the readiness verdict with who can fix each failure, and checks the optional local replica. Use when someone says "set me up", "onboard me", "I just signed up", "get me started", "what do I do first", "log me in", "which account is this machine on", "am I set up", "what is missing", "why does nothing happen" or "is the replica running", when a login expired, or when a skill script exits 2 saying this machine is not connected. Does every step a key can do through the catalyst CLI, hands over the exact page for the steps only a browser can do, and never claims a step it did not watch succeed.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.16.2 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Onboard me
|
|
10
10
|
|
|
@@ -36,7 +36,7 @@ Default invited-member setup handles personal accounts and leaves workspace admi
|
|
|
36
36
|
|
|
37
37
|
1. Run `node scripts/where-am-i.mjs --next` and read its one line; trust it over your memory of the last turn. A `note:` after it is worth one clause.
|
|
38
38
|
2. Say what the step is for.
|
|
39
|
-
3. Ask one question or do one thing.
|
|
39
|
+
3. Ask one question or do one thing. Offer to run every step a command can do, run it on a yes and show its output; hand over a browser step only as `references/what-the-browser-owns.md` says.
|
|
40
40
|
4. Wait for the answer. Two or three short sentences a turn; the full report is for you, not to paste.
|
|
41
41
|
5. Re-run the script and read back the part that should have changed. Still unchanged after one `catalyst contract --refresh`: report both what the page said and what the instrument says.
|
|
42
42
|
|
|
@@ -55,7 +55,7 @@ Scripts are run, never read; each prints `--help`.
|
|
|
55
55
|
| logging in, a refused or expired login, which account this is, the Linear identity | `references/connecting-this-machine.md` |
|
|
56
56
|
| anything reports not ready, a check id, or who fixes something | `references/reading-ready.md` |
|
|
57
57
|
| the coding account or the `host` part, or before saying work can run | `references/what-a-phase-needs.md`; a credential to replace, `references/replacing-a-credential.md` |
|
|
58
|
-
| a browser
|
|
58
|
+
| which steps a command does and which need a browser, or confirming a page that said it worked | `references/what-the-browser-owns.md` |
|
|
59
59
|
| the repository's `.catalyst/catalyst.toml` and its approval; its AGENTS.md block and agent layout | `references/declaring-a-repository.md`; `references/repository-agent-setup.md` |
|
|
60
60
|
| the replica or event cache | `references/local-sync.md` |
|
|
61
61
|
| local SQL, only when `catalyst replica status --json` reports `configured: true` | `references/local-replica.md` |
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
Keyless is the preferred rail: `catalyst login` with no key prints a short code and a URL the person approves in a browser, or from a phone. Ask first, and run it once per code. The session refreshes silently, so they stay connected for months.
|
|
10
10
|
|
|
11
|
-
A **personal key** is the fallback for a script or an unattended shell.
|
|
11
|
+
A **personal key** is the fallback for a script or an unattended shell, where nobody can approve a code. Offer `catalyst login` first; a key is minted only in the browser, so for an unattended machine any active member mints one at Settings → API keys. The environment form keeps it out of shell history:
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
14
|
CATALYST_CLOUD_TOKEN=<your-personal-key> catalyst login
|
|
@@ -18,7 +18,7 @@ CATALYST_CLOUD_TOKEN=<your-personal-key> catalyst login
|
|
|
18
18
|
|
|
19
19
|
## An unmatched Linear identity
|
|
20
20
|
|
|
21
|
-
An unmatched Linear identity is the person's to match: `catalyst identity linear status`, then `catalyst identity linear options`; they pick their own listed identity and you run `catalyst identity linear set <linearUserId>`. An identity someone else claimed, and an inactive seat, need an owner or admin at Settings → Members. Connecting Linear personally does not set the identity: the identity and the personal grant are two different stored facts. Until the identity is matched, "what needs me" shows everyone's asks.
|
|
21
|
+
An unmatched Linear identity is the person's to match: `catalyst identity linear status`, then `catalyst identity linear options`; they pick their own listed identity and you run `catalyst identity linear set <linearUserId>`. An identity someone else claimed, and an inactive seat, need an owner or admin at Settings → Members; no command does this yet, so raise one ask to them. Connecting Linear personally does not set the identity: the identity and the personal grant are two different stored facts. Until the identity is matched, "what needs me" shows everyone's asks.
|
|
22
22
|
|
|
23
23
|
## What login writes
|
|
24
24
|
|
|
@@ -27,9 +27,9 @@ An unmatched Linear identity is the person's to match: `catalyst identity linear
|
|
|
27
27
|
## When login fails
|
|
28
28
|
|
|
29
29
|
- **"Your login expired or was revoked."** One fresh `catalyst login`.
|
|
30
|
-
- **A 401 on the key rail.** A stale, mistyped or revoked key:
|
|
30
|
+
- **A 401 on the key rail.** A stale, mistyped or revoked key: offer a keyless `catalyst login` instead; an unattended machine needs a new key from Settings → API keys.
|
|
31
31
|
- **A 403 on the contract naming an older cloud.** The cloud lacks personal-key access: update the cloud, or use the account key until then.
|
|
32
32
|
- **A line naming two contract versions.** `npm install -g @catalyst-cloud/cli@latest && catalyst login` before using the other skills.
|
|
33
|
-
- **A refusal naming `jwt-no-membership`.** The cloud learns of a person at their first sign-in to the app.
|
|
33
|
+
- **A refusal naming `jwt-no-membership`.** The cloud learns of a person at their first sign-in to the app. Hand over the app's sign-in link once, then offer to run `catalyst login` again. If it repeats, their seat is inactive and an owner or admin activates it.
|
|
34
34
|
|
|
35
35
|
Retry a failed login once, by hand, after the fix. A key goes into the login command or `CATALYST_CLOUD_TOKEN` and nowhere else: never a ticket, a transcript, or a file you write. After a bundle update, the next command prints a one-line notice; read it to the person if they are watching.
|
|
@@ -14,7 +14,7 @@ The assistant prepares `.catalyst/catalyst.toml` from the repository. Never ask
|
|
|
14
14
|
|
|
15
15
|
Show a short preview with the project/team, setup and verify commands, variable names, which are marked secret, and confirmation that every variable is optional. Do not include values. Ask the person to approve this exact draft. Before approval, do not write or replace `.catalyst/catalyst.toml`, remove `catalyst.env.json`, or open a settings PR.
|
|
16
16
|
|
|
17
|
-
After approval, write the validated file. When converting a legacy-only repository, remove `catalyst.env.json` in the same change. Open the settings PR, then offer to watch it merge. A reviewed merge is the approval. Catalyst approves the exact merged revision, with no second step, when someone other than the PR's author approved the merged head commit, or when a workspace owner or admin merged the PR. Tell the person this before they merge: the approving review is their consent to the setup commands and secret names in the file, so a reviewer should read it like code. A direct push, an unreviewed merge, or an approval left before the last commit leaves the revision proposed.
|
|
17
|
+
After approval, write the validated file. When converting a legacy-only repository, remove `catalyst.env.json` in the same change. Open the settings PR, then offer to watch it merge. A reviewed merge is the approval. Catalyst approves the exact merged revision, with no second step, when someone other than the PR's author approved the merged head commit, or when a workspace owner or admin merged the PR. Tell the person this before they merge: the approving review is their consent to the setup commands and secret names in the file, so a reviewer should read it like code. A direct push, an unreviewed merge, or an approval left before the last commit leaves the revision proposed. No command approves one repository's revision yet, so only then give an owner or admin a direct link to approve it at Settings → Your projects → the project → Repositories → the repository → Environment → Setup declaration → Approve this revision. The direct route is `/settings/projects/$projectId/repositories/$repoId/environment/declaration`; use the project and repository ids from Catalyst instead of guessing them.
|
|
18
18
|
|
|
19
19
|
## File shape
|
|
20
20
|
|