@catalyst-cloud/cli 0.15.1 → 0.15.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 +8 -0
- package/README.md +11 -2
- package/dist/args.js +1 -1
- package/dist/capabilities.js +3 -1
- package/dist/cli.js +20 -13
- package/dist/consent-browser.js +3 -1
- package/dist/onboard-github.js +13 -1
- package/dist/onboard-headless.js +4 -2
- package/dist/onboard-next.js +16 -4
- package/dist/onboard-runtime.js +62 -4
- package/dist/onboard-ui.js +35 -0
- package/dist/onboard.js +10 -5
- package/dist/ready-copy.js +238 -0
- package/dist/ready.js +39 -46
- package/dist/setup-onboard-copy.js +28 -3
- package/dist/setup-render.js +3 -1
- package/package.json +2 -2
- package/skills/catalyst-github/SKILL.md +1 -1
- package/skills/catalyst-linear/SKILL.md +1 -1
- package/skills/catalyst-onboard/SKILL.md +1 -1
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +2 -1
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.15.2
|
|
4
|
+
|
|
5
|
+
- `catalyst setup` is the command to set up Catalyst. It runs the same steps as `catalyst onboard`, which still works.
|
|
6
|
+
- When a browser link times out, setup says so and asks whether you are ready to try again. Yes gives a new link; stopping pauses setup without moving on to steps that depend on it.
|
|
7
|
+
- The GitHub link opens GitHub's install page directly. An install request already waiting on an organization owner is not requested again, and setup still shows the link to install it yourself.
|
|
8
|
+
- Setup asks whether to run work on this machine only after Catalyst is installed on GitHub.
|
|
9
|
+
- Sign-in says which Catalyst workspace you joined, without internal account numbers. `catalyst ready` describes each team problem in plain words, with the page to fix it and who can fix it.
|
|
10
|
+
|
|
3
11
|
## 0.15.1
|
|
4
12
|
|
|
5
13
|
- Runner setup accepts verified personal team admission before a repository has been mapped to that team. Older cloud servers keep their existing admission check.
|
package/README.md
CHANGED
|
@@ -4,13 +4,22 @@
|
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Set up Catalyst Cloud and manage your account from the command line.
|
|
8
|
+
|
|
9
|
+
Check ticket progress and see which decisions need your attention.
|
|
10
|
+
|
|
11
|
+
Install: `npm install -g @catalyst-cloud/cli`
|
|
12
|
+
|
|
13
|
+
[CLI documentation](https://github.com/coalesce-labs/catalyst-cloud-skills#readme)
|
|
14
|
+
|
|
15
|
+
Run the installer, then run `catalyst onboard`:
|
|
8
16
|
|
|
9
17
|
```sh
|
|
10
18
|
curl -fsSL https://staging.catalystcloud.dev/install.sh | sh
|
|
19
|
+
catalyst onboard
|
|
11
20
|
```
|
|
12
21
|
|
|
13
|
-
The installer puts the CLI and both skill packs on this machine and connects it with one browser approval.
|
|
22
|
+
The installer puts the CLI and both skill packs on this machine and connects it with one browser approval. Run `catalyst onboard` to finish setup from the command line. You can also use the onboarding skill in your coding agent after that. Everything else on this page is the reference.
|
|
14
23
|
|
|
15
24
|
This repository supplies skills for setting up and operating your Catalyst Cloud account. A coding workstation also uses [`coalesce-labs/catalyst-dev-skills`](https://github.com/coalesce-labs/catalyst-dev-skills) for research, planning, implementation, review, and shipping.
|
|
16
25
|
|
package/dist/args.js
CHANGED
|
@@ -457,7 +457,7 @@ export const FLAG_TABLES = {
|
|
|
457
457
|
},
|
|
458
458
|
};
|
|
459
459
|
export const VERB_USAGE = {
|
|
460
|
-
setup: "setup --engine <file> --engine-sha256 <hex> [-- <install options>] (run by install.sh; install.sh --help lists the options)",
|
|
460
|
+
setup: "setup [onboard options] (sets up Catalyst step by step; same as catalyst onboard) | setup --engine <file> --engine-sha256 <hex> [-- <install options>] (run by install.sh; install.sh --help lists the options)",
|
|
461
461
|
login: "login [--base-url <url>] [--start-replica] (keyless; or --key <personal-key> / CATALYST_CLOUD_TOKEN)",
|
|
462
462
|
onboard: "onboard [--team <ID|key>] [--repo <owner/name>]... [--coding-account <slot>] [--resume-from <step>] [--only <step>] [--local-sync] [--runner|--no-runner] [--yes] [--dry-run] [--json] | onboard --headless [--key-file <path>] [--team ...] [--repo ...]... [--coding-account ...] [--runner yes|no] [--json]",
|
|
463
463
|
install: "install [--skills-dir <dir>] [--force]",
|
package/dist/capabilities.js
CHANGED
|
@@ -5,7 +5,9 @@ const agent = (method, name) => ({ method, path: `/api/v1/agent/${name}` });
|
|
|
5
5
|
* the person, the workspace, the project, the repository, then work. A new verb is a new row here
|
|
6
6
|
* in the same change, or `catalyst capabilities` lies to every guide that reads it. */
|
|
7
7
|
export const CAPABILITIES = [
|
|
8
|
-
|
|
8
|
+
// CTC-4680: plain `catalyst setup` runs the onboard flow, so install.sh may hand off to it. install.sh
|
|
9
|
+
// reads this flag rather than `setup --help`, which already exits 0 on 0.15.x.
|
|
10
|
+
{ verb: "setup", does: "set up Catalyst step by step, or show install.sh's plan, progress and sign-in in this terminal", needs: "member", routes: [], since: "0.15.0", engineProtocol: 1, bootstrapHandoff: true },
|
|
9
11
|
{ verb: "onboard", does: "run and resume Catalyst setup with a saved step record", needs: "member", routes: [], since: "0.14.0", bootstrapHandoff: true, headless: true },
|
|
10
12
|
{ verb: "status", does: "say whether this machine is connected, as whom, and to which cloud", needs: "member", routes: [], since: "0.1.0" },
|
|
11
13
|
{ verb: "me", does: "read the person's seat, role and Linear identity", needs: "member", routes: [], since: "0.1.0" },
|
package/dist/cli.js
CHANGED
|
@@ -69,7 +69,7 @@ const VERB_ALIASES = { join: "login" };
|
|
|
69
69
|
const HUMAN_VERBS = new Set(["login", "install", "status", "notice"]);
|
|
70
70
|
export function usageText() {
|
|
71
71
|
return [
|
|
72
|
-
`${PACKAGE_NAME} — the Catalyst Cloud customer CLI: connect to your
|
|
72
|
+
`${PACKAGE_NAME} — the Catalyst Cloud customer CLI: connect to your Catalyst workspace, read through the SDK, write through the agent proxy`,
|
|
73
73
|
"",
|
|
74
74
|
"Usage:",
|
|
75
75
|
" catalyst login [--base-url <url>] [--start-replica] (keyless: logs you in as yourself)",
|
|
@@ -96,7 +96,7 @@ export function usageText() {
|
|
|
96
96
|
" catalyst var set <NAME> | var import <file> [--repo <owner/name>] [--names NAME[,NAME...]] (plain environment variables)",
|
|
97
97
|
" catalyst team <list|check|map|adopt|migrate|checklist> ...",
|
|
98
98
|
" catalyst capabilities [--json] (what this CLI can do, the role each verb needs, and whether this cloud serves it)",
|
|
99
|
-
" catalyst project list [--json] (all
|
|
99
|
+
" catalyst project list [--json] (all workspace projects, including those with no stage mapping)",
|
|
100
100
|
" catalyst project wip-limit <get|set <n>|set default> [--team K] (a project's new-start WIP limit; members read, owners and admins set)",
|
|
101
101
|
" catalyst repo <agents-block <path> [--write]|agent-setup <path> [--apply] [--with-check]> (a checkout's AGENTS.md block and portable agent layout; working tree only)",
|
|
102
102
|
" catalyst legacy [--remove [--data] [--yes]] (leftovers of the old local Catalyst runtime: list them; remove them only on a yes)",
|
|
@@ -110,8 +110,8 @@ export function usageText() {
|
|
|
110
110
|
"--key (or set CATALYST_CLOUD_TOKEN) to use a personal or account key instead. The base URL defaults",
|
|
111
111
|
`to CATALYST_CLOUD_BASE_URL or ${DEFAULT_BASE_URL}.`,
|
|
112
112
|
"",
|
|
113
|
-
"login
|
|
114
|
-
"(0600) holding your login session (or key) and this CLI's path, and caches the
|
|
113
|
+
"login finds your Catalyst workspace, writes ~/.config/catalyst-cloud/customer.json",
|
|
114
|
+
"(0600) holding your login session (or key) and this CLI's path, and caches the workspace contract",
|
|
115
115
|
"beside it. A keyless session's short-lived token refreshes silently on every request. login does",
|
|
116
116
|
"not install skills: your agent's own install command does that, and `install` is only here to",
|
|
117
117
|
"repair a copy this package made.",
|
|
@@ -208,9 +208,15 @@ function maybePrintUpdateNotice(args, ctx) {
|
|
|
208
208
|
}
|
|
209
209
|
export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
210
210
|
// CTC-4625: setup passes the install options through to its engine untouched, so they never meet
|
|
211
|
-
// this CLI's own flag tables.
|
|
212
|
-
|
|
213
|
-
|
|
211
|
+
// this CLI's own flag tables. CTC-4680: without --engine, `setup` is the name a person types for
|
|
212
|
+
// the onboard flow, so it runs exactly that.
|
|
213
|
+
if (argv[0] === "setup") {
|
|
214
|
+
const rest = argv.slice(1);
|
|
215
|
+
const own = rest.indexOf("--") === -1 ? rest : rest.slice(0, rest.indexOf("--"));
|
|
216
|
+
if (own.includes("--engine") || own.includes("--help") || own.includes("-h"))
|
|
217
|
+
return runSetup(rest, ctx, deps);
|
|
218
|
+
argv = ["onboard", ...rest];
|
|
219
|
+
}
|
|
214
220
|
let args;
|
|
215
221
|
try {
|
|
216
222
|
args = parseArgs(argv.map((a, i) => (i === 0 && VERB_ALIASES[a] ? VERB_ALIASES[a] : a)));
|
|
@@ -231,7 +237,9 @@ export async function main(argv, ctx = defaultCtx(), deps = {}) {
|
|
|
231
237
|
onboardHeadlessRequested(args, ctx.env))
|
|
232
238
|
return headlessUsageRefusal(ctx, "--help and --version print text, not the headless document; run them without --json", true, manifest.version);
|
|
233
239
|
if (args.version) {
|
|
234
|
-
ctx.stdout(
|
|
240
|
+
ctx.stdout(
|
|
241
|
+
// Machine-read: install.sh and catalyst-cloud's onboarding harness match this exact line.
|
|
242
|
+
`${PACKAGE_NAME} ${manifest.version} (tenant contract range: ${manifest.tenantContractRange})`);
|
|
235
243
|
return 0;
|
|
236
244
|
}
|
|
237
245
|
if (args.command === null) {
|
|
@@ -687,26 +695,25 @@ async function cmdLogin(args, ctx, deps, personalOnly = false) {
|
|
|
687
695
|
};
|
|
688
696
|
checkCancelled();
|
|
689
697
|
const written = writeConfig(ctx.home, config);
|
|
690
|
-
ctx.stdout(`
|
|
698
|
+
ctx.stdout(`Catalyst workspace: ${me.name} (${me.slug})`);
|
|
691
699
|
if (me.user) {
|
|
692
|
-
ctx.stdout(`
|
|
700
|
+
ctx.stdout(`Signed in to Catalyst as ${me.user.label} (${me.user.role})${me.user.linearUserId
|
|
693
701
|
? ""
|
|
694
702
|
: ' — your Linear identity is not matched yet, so "what needs me" will show everyone\'s asks until an admin matches it in Settings'}`);
|
|
695
703
|
}
|
|
696
704
|
else {
|
|
697
|
-
ctx.stderr(`[catalyst] this is
|
|
705
|
+
ctx.stderr(`[catalyst] this is your Catalyst workspace's shared key (a host credential), not your own. The skills work, but nothing your agent writes will carry your name, and "what needs me" cannot mean you. Make a personal key at Settings → API keys and sign in with that.`);
|
|
698
706
|
}
|
|
699
707
|
ctx.stdout(`Config written to ${written.path} (mode ${formatMode(written.mode)}, holds your ${auth ? "login session" : "key"} and the CLI path)${written.mode === CONFIG_MODE
|
|
700
708
|
? ""
|
|
701
709
|
: ` — expected ${formatMode(CONFIG_MODE)}; chmod it by hand`}`);
|
|
702
|
-
ctx.stdout(`Tenant contract range: ${manifest.tenantContractRange}`);
|
|
703
710
|
try {
|
|
704
711
|
const loaded = await loadContract(ctx, config, {
|
|
705
712
|
refresh: true,
|
|
706
713
|
signal: deps.signal,
|
|
707
714
|
});
|
|
708
715
|
checkCancelled();
|
|
709
|
-
ctx.stdout(`
|
|
716
|
+
ctx.stdout(`Saved your workspace settings (version ${loaded.doc.contractVersion}) to ${loaded.path}`);
|
|
710
717
|
}
|
|
711
718
|
catch (err) {
|
|
712
719
|
checkCancelled();
|
package/dist/consent-browser.js
CHANGED
|
@@ -54,5 +54,7 @@ export const openConsentBrowser = createConsentBrowserOpener();
|
|
|
54
54
|
/** The web page that finishes a consent step when no browser opens here. The signed link stays private. */
|
|
55
55
|
export function finishOnTheWeb(baseUrl, page, action) {
|
|
56
56
|
const origin = baseUrl.replace(/\/+$/, "");
|
|
57
|
-
|
|
57
|
+
// CTC-4680: the GitHub install has a route that goes straight to GitHub; Integrations starts nothing.
|
|
58
|
+
const path = page === "github-install" ? "/connect/github/start" : `/settings/${page}`;
|
|
59
|
+
return `No browser opened on this computer. In a browser signed in to Catalyst, open ${origin}${path} and ${action}. Setup keeps waiting here.`;
|
|
58
60
|
}
|
package/dist/onboard-github.js
CHANGED
|
@@ -215,6 +215,18 @@ function observation(body, now) {
|
|
|
215
215
|
return waiting("github_installation_status_unavailable");
|
|
216
216
|
if (outdated)
|
|
217
217
|
return outdated;
|
|
218
|
+
// CTC-4680 round 5: a request already waiting for an org owner on GitHub is not a new install.
|
|
219
|
+
if (row.installations.length === 0 && row.pending.length > 0) {
|
|
220
|
+
const org = object(row.pending[0])?.githubOrg;
|
|
221
|
+
return {
|
|
222
|
+
state: "waiting",
|
|
223
|
+
reason: "github_installation_approval_pending",
|
|
224
|
+
evidence: {
|
|
225
|
+
provider: "github",
|
|
226
|
+
...(typeof org === "string" ? { org } : {}),
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
218
230
|
if (!allConnected)
|
|
219
231
|
return { state: "pending" };
|
|
220
232
|
if (permissionsUnverified)
|
|
@@ -464,7 +476,7 @@ export function githubInstallationAdapter(options) {
|
|
|
464
476
|
if (signal?.aborted)
|
|
465
477
|
return waiting("interrupted");
|
|
466
478
|
browserUnavailable = true;
|
|
467
|
-
ctx.stderr(finishOnTheWeb(current.baseUrl, "
|
|
479
|
+
ctx.stderr(finishOnTheWeb(current.baseUrl, "github-install", "install Catalyst on your GitHub organization"));
|
|
468
480
|
}
|
|
469
481
|
let latest = waiting("github_installation_status_unavailable");
|
|
470
482
|
const run = () => pollConsent({
|
package/dist/onboard-headless.js
CHANGED
|
@@ -323,7 +323,9 @@ function headlessRetryText(text, keys) {
|
|
|
323
323
|
return text
|
|
324
324
|
.replace(/\b(?:Renew your login with|Sign in as one with|Sign in with|[Rr]un) catalyst login\b/g, key)
|
|
325
325
|
.replace(/\bRenew your login with catalyst onboard\b/g, key)
|
|
326
|
-
|
|
326
|
+
// CTC-4680: `catalyst setup` is the same flow as `catalyst onboard`, and bare, it drops the
|
|
327
|
+
// headless flags and inputs just the same.
|
|
328
|
+
.replace(/\bcatalyst (?:onboard|setup)\b(?! --help\b)(?: --runner)?(?: again)?/g, "the same headless setup command");
|
|
327
329
|
}
|
|
328
330
|
/** The report's view of a finished journal: each unsatisfied step as one named item. */
|
|
329
331
|
export function headlessStepItems(journal, baseUrl, requireRunner = false, only) {
|
|
@@ -457,7 +459,7 @@ export async function runOnboardHeadless(args, ctx, hooks, cliVersion) {
|
|
|
457
459
|
const loginGuidance = headlessLoginGuidance(journal.steps.find((step) => step.id === action.step)?.reason ?? "", keys);
|
|
458
460
|
return { ...action, text: loginGuidance ?? headlessRetryText(action.text, keys), ...(loginGuidance ? { url: keys } : {}) };
|
|
459
461
|
});
|
|
460
|
-
const next = headlessRetryText(view.next.replace(/(?:run )?catalyst onboard(?: --runner)?(?: again)?/g, "run the same headless setup command"), keys);
|
|
462
|
+
const next = headlessRetryText(view.next.replace(/(?:run )?catalyst (?:onboard|setup)(?: --runner)?(?: again)?/g, "run the same headless setup command"), keys);
|
|
461
463
|
ctx.stdout(JSON.stringify({ ...view, actions, next, headless: report }));
|
|
462
464
|
}
|
|
463
465
|
else
|
package/dist/onboard-next.js
CHANGED
|
@@ -20,8 +20,8 @@ const REASONS = {
|
|
|
20
20
|
personal_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
21
21
|
onboard_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
22
22
|
personal_status_unavailable: "Your personal connection could not be checked. Run catalyst onboard to try again.",
|
|
23
|
-
workspace_browser_unavailable: "
|
|
24
|
-
personal_browser_unavailable: "
|
|
23
|
+
workspace_browser_unavailable: "The link to connect Linear timed out. Run catalyst setup for a new one.",
|
|
24
|
+
personal_browser_unavailable: "The link to connect your own account timed out. Run catalyst setup for a new one.",
|
|
25
25
|
onboarding_capability_unavailable: "The server's setup capabilities could not be checked. Run catalyst onboard to try again.",
|
|
26
26
|
onboarding_capability_identity_unverified: "The server's setup capabilities belong to another login or workspace. Sign in to the original workspace to resume.",
|
|
27
27
|
onboarding_capability_login_refresh_required: "Your login needs a refresh before setup capabilities can be checked. Run catalyst login, then catalyst onboard.",
|
|
@@ -34,7 +34,10 @@ const REASONS = {
|
|
|
34
34
|
github_installation_identity_refused: "GitHub setup belongs to another login or workspace. Sign in to the original workspace to resume.",
|
|
35
35
|
github_installation_admin_required: "A workspace owner or administrator approves the GitHub App installation.",
|
|
36
36
|
github_installation_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
37
|
-
github_installation_browser_unavailable: "
|
|
37
|
+
github_installation_browser_unavailable: "The link to install Catalyst on GitHub timed out. Run catalyst setup for a new one.",
|
|
38
|
+
github_installation_approval_pending: "An install request is waiting on GitHub. An owner of your GitHub organization approves it there, then run catalyst setup.",
|
|
39
|
+
github_install_pending: "Runs after Catalyst is installed on GitHub.",
|
|
40
|
+
consent_timeout: "The link timed out. Run catalyst setup for a new one.",
|
|
38
41
|
skills_install_unverified: "The Catalyst skills are not in this user's skills folder. Run catalyst install, then run catalyst onboard.",
|
|
39
42
|
step_not_available_in_this_release: "This setup step is not available yet.",
|
|
40
43
|
prerequisite_not_ready: "Waiting for an earlier setup step.",
|
|
@@ -269,13 +272,22 @@ export function onboardReasonText(step, context = {}) {
|
|
|
269
272
|
const path = reason === "workspace_approval_required"
|
|
270
273
|
? "/settings/connections?connect=linear"
|
|
271
274
|
: reason === "github_installation_approval_required"
|
|
272
|
-
? "/
|
|
275
|
+
? "/connect/github/start"
|
|
273
276
|
: `/settings/connected-accounts?connect=${step.id === "github.personal" ? "github" : "linear"}`;
|
|
274
277
|
const where = context.baseUrl
|
|
275
278
|
? `${normalizeBaseUrl(context.baseUrl)}${path}`
|
|
276
279
|
: "Catalyst settings";
|
|
277
280
|
return `Open ${where} and approve this connection, then run catalyst onboard.`;
|
|
278
281
|
}
|
|
282
|
+
// CTC-4680: only a Catalyst owner or admin can start the GitHub install (the start route refuses
|
|
283
|
+
// anyone else), so a member is told who to ask and where, on every channel.
|
|
284
|
+
if (step.id === "github.install" &&
|
|
285
|
+
(reason === "github_installation_admin_required" || reason === "member_scope"))
|
|
286
|
+
return `Only an owner or admin of your Catalyst workspace can install Catalyst on GitHub. Ask one to open ${context.baseUrl ? `${normalizeBaseUrl(context.baseUrl)}/settings/connections` : "Catalyst settings, Integrations,"} and install it there.`;
|
|
287
|
+
// An install request can sit on GitHub for good (a wrong organization, or an owner who said no),
|
|
288
|
+
// so the step always keeps a way to install it directly.
|
|
289
|
+
if (reason === "github_installation_approval_pending" && context.baseUrl)
|
|
290
|
+
return `${REASONS[reason] ?? ""} If you own the organization, or want a different one, install it at ${normalizeBaseUrl(context.baseUrl)}/connect/github/start.`;
|
|
279
291
|
if (reason === "workflow_mapping_unverified")
|
|
280
292
|
return `This team's workflow mapping is not verified yet. Run catalyst team adopt ${onboardTeamKey(context.journal) ?? "<TEAM KEY>"} to preview the adoption plan. Run it again with the --yes --plan-hash value it prints. Then run catalyst onboard.`;
|
|
281
293
|
if (reason === "team_created_not_adopted" ||
|
package/dist/onboard-runtime.js
CHANGED
|
@@ -18,7 +18,15 @@ import { onboardAccountsAdapter } from "./onboard-accounts.js";
|
|
|
18
18
|
import { githubInstallationAdapter } from "./onboard-github.js";
|
|
19
19
|
import { guardOnboardCapabilities, } from "./onboard-capabilities.js";
|
|
20
20
|
import { onboardSettingsAdapter, } from "./onboard-settings.js";
|
|
21
|
+
import { TIMED_OUT_REASONS } from "./setup-onboard-copy.js";
|
|
21
22
|
import { fetchMe } from "./transport.js";
|
|
23
|
+
const HOLD_ON_TIMEOUT = [
|
|
24
|
+
"linear.workspace",
|
|
25
|
+
"linear.personal",
|
|
26
|
+
"github.install",
|
|
27
|
+
"github.personal",
|
|
28
|
+
"accounts",
|
|
29
|
+
];
|
|
22
30
|
const waiting = (reason) => ({
|
|
23
31
|
state: "waiting",
|
|
24
32
|
reason,
|
|
@@ -136,12 +144,19 @@ export function createOnboardRuntime(args, ctx, hooks) {
|
|
|
136
144
|
!args.json &&
|
|
137
145
|
args.flags.yes !== true &&
|
|
138
146
|
args.flags.headless !== true;
|
|
147
|
+
// CTC-4680 round 5: whether the step now acting ran a browser wait, so a timed-out wait can be
|
|
148
|
+
// re-offered with a fresh link instead of moving on to steps that need it.
|
|
149
|
+
let waited = false;
|
|
150
|
+
const waitFor = (message, run) => {
|
|
151
|
+
waited = true;
|
|
152
|
+
return hooks.ui.wait(message, run);
|
|
153
|
+
};
|
|
139
154
|
const personalAdapter = (provider) => personalConsentAdapter({
|
|
140
155
|
provider,
|
|
141
156
|
openBrowser: hooks.openBrowser ?? openConsentBrowser,
|
|
142
157
|
sleep: hooks.sleep,
|
|
143
158
|
wait: interactiveWait
|
|
144
|
-
? (message, run) =>
|
|
159
|
+
? (message, run) => waitFor(message, run)
|
|
145
160
|
: undefined,
|
|
146
161
|
});
|
|
147
162
|
const linear = existingLinearAdapters(args, hooks.ui?.chooseTeam
|
|
@@ -218,14 +233,14 @@ export function createOnboardRuntime(args, ctx, hooks) {
|
|
|
218
233
|
fallback: linear["linear.workspace"],
|
|
219
234
|
openBrowser: hooks.openBrowser ?? openConsentBrowser,
|
|
220
235
|
wait: interactiveWait
|
|
221
|
-
? (message, work) =>
|
|
236
|
+
? (message, work) => waitFor(message, work)
|
|
222
237
|
: undefined,
|
|
223
238
|
sleep: hooks.sleep,
|
|
224
239
|
}),
|
|
225
240
|
"github.install": githubInstallationAdapter({
|
|
226
241
|
openBrowser: hooks.openBrowser ?? openConsentBrowser,
|
|
227
242
|
wait: interactiveWait
|
|
228
|
-
? (message, work) =>
|
|
243
|
+
? (message, work) => waitFor(message, work)
|
|
229
244
|
: undefined,
|
|
230
245
|
sleep: hooks.sleep,
|
|
231
246
|
}),
|
|
@@ -254,7 +269,7 @@ export function createOnboardRuntime(args, ctx, hooks) {
|
|
|
254
269
|
args.flags.yes !== true &&
|
|
255
270
|
args.flags.headless !== true
|
|
256
271
|
? {
|
|
257
|
-
waitForAccount: (work) =>
|
|
272
|
+
waitForAccount: (work) => waitFor("Waiting for a coding account", work),
|
|
258
273
|
sleep: hooks.sleep,
|
|
259
274
|
}
|
|
260
275
|
: {}),
|
|
@@ -376,6 +391,49 @@ export function createOnboardRuntime(args, ctx, hooks) {
|
|
|
376
391
|
message: (text) => hooks.ui ? hooks.ui.message(text) : ctx.stderr(text),
|
|
377
392
|
});
|
|
378
393
|
}
|
|
394
|
+
// CTC-4680 round 5: a browser link that ran out of time holds the step. The person gets a fresh
|
|
395
|
+
// link on yes; on stop the UI pauses setup, so nothing that depends on the step runs.
|
|
396
|
+
const retry = interactiveWait ? hooks.ui?.retryTimedOut?.bind(hooks.ui) : undefined;
|
|
397
|
+
if (retry)
|
|
398
|
+
for (const step of HOLD_ON_TIMEOUT) {
|
|
399
|
+
const adapter = adapters[step];
|
|
400
|
+
if (!adapter?.act)
|
|
401
|
+
continue;
|
|
402
|
+
const act = adapter.act;
|
|
403
|
+
adapters[step] = {
|
|
404
|
+
...adapter,
|
|
405
|
+
act: async (stepCtx, journal, signal) => {
|
|
406
|
+
for (;;) {
|
|
407
|
+
waited = false;
|
|
408
|
+
const result = await act(stepCtx, journal, signal);
|
|
409
|
+
const timedOut = result.state === "waiting" &&
|
|
410
|
+
(TIMED_OUT_REASONS.has(result.reason ?? "") ||
|
|
411
|
+
(step === "accounts" &&
|
|
412
|
+
result.reason === "account_enrollment_required"));
|
|
413
|
+
if (!timedOut || !waited || signal?.aborted || hooks.ui?.signal.aborted)
|
|
414
|
+
return result;
|
|
415
|
+
if (!(await retry(step)))
|
|
416
|
+
return result;
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
// Round 5: the runner question waits until Catalyst is on GitHub; asking it earlier left people
|
|
422
|
+
// answering later steps while nothing could work.
|
|
423
|
+
const runner = adapters.runner;
|
|
424
|
+
if (runner?.act && hooks.ui?.chooseRunner && args.flags.runner !== true && args.flags["no-runner"] !== true) {
|
|
425
|
+
const { check, act } = runner;
|
|
426
|
+
const githubPending = (journal) => {
|
|
427
|
+
const github = journal.steps.find((step) => step.id === "github.install");
|
|
428
|
+
return Boolean(github && github.state !== "done" && github.state !== "skipped");
|
|
429
|
+
};
|
|
430
|
+
// Both: the engine runs check before act, and the runner's check is where the question is asked.
|
|
431
|
+
adapters.runner = {
|
|
432
|
+
...runner,
|
|
433
|
+
check: async (stepCtx, journal, signal) => githubPending(journal) ? waiting("github_install_pending") : check(stepCtx, journal, signal),
|
|
434
|
+
act: async (stepCtx, journal, signal) => githubPending(journal) ? waiting("github_install_pending") : act(stepCtx, journal, signal),
|
|
435
|
+
};
|
|
436
|
+
}
|
|
379
437
|
// Public auth discovery and the existing /me sign-in bootstrap establish identity before the
|
|
380
438
|
// tenant-bound step guards can run. Unsupported/local-only steps make no endpoint requests.
|
|
381
439
|
return {
|
package/dist/onboard-ui.js
CHANGED
|
@@ -508,6 +508,40 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
508
508
|
}
|
|
509
509
|
return answer === "yes";
|
|
510
510
|
},
|
|
511
|
+
async retryTimedOut(id) {
|
|
512
|
+
if (abort.signal.aborted)
|
|
513
|
+
return false;
|
|
514
|
+
if (renderer && active) {
|
|
515
|
+
// The wait frame is still live: its status and a last line say why it ended.
|
|
516
|
+
renderer.update("the link timed out");
|
|
517
|
+
renderer.detail("The link timed out.");
|
|
518
|
+
}
|
|
519
|
+
else if (renderer) {
|
|
520
|
+
renderer.begin(SETUP_NUMBERS[id] ?? 0, title(id), "the link timed out");
|
|
521
|
+
renderer.detail("The link timed out.");
|
|
522
|
+
active = true;
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
stop();
|
|
526
|
+
prompts.log.message(`${title(id)}: the link timed out.`, {
|
|
527
|
+
output: streams.output,
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
const answer = await select({
|
|
531
|
+
...options,
|
|
532
|
+
message: "Ready to try again?",
|
|
533
|
+
initialValue: "again",
|
|
534
|
+
options: [
|
|
535
|
+
{ value: "again", label: "Yes, give me a new link" },
|
|
536
|
+
{ value: "stop", label: "Stop here. Run catalyst setup later to carry on." },
|
|
537
|
+
],
|
|
538
|
+
});
|
|
539
|
+
if (prompts.isCancel(answer) || answer !== "again") {
|
|
540
|
+
abort.abort();
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
543
|
+
return true;
|
|
544
|
+
},
|
|
511
545
|
stagedSigninEnd(state, cause) {
|
|
512
546
|
if (!renderer)
|
|
513
547
|
return false;
|
|
@@ -847,6 +881,7 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
847
881
|
delete ui.chooseFirstRepository;
|
|
848
882
|
delete ui.reviewSettings;
|
|
849
883
|
delete ui.chooseRunner;
|
|
884
|
+
delete ui.retryTimedOut;
|
|
850
885
|
delete ui.confirmWorkflowAdoption;
|
|
851
886
|
}
|
|
852
887
|
return ui;
|
package/dist/onboard.js
CHANGED
|
@@ -6,7 +6,7 @@ import { dirname, isAbsolute, join } from "node:path";
|
|
|
6
6
|
import { parseMachinePaths, resolveCatalystPath, } from "../vendor/paths/index.js";
|
|
7
7
|
import { machinePathsFile } from "../vendor/paths/node.js";
|
|
8
8
|
import { configPathFor } from "./config.js";
|
|
9
|
-
import { onboardJsonView } from "./setup-onboard-copy.js";
|
|
9
|
+
import { onboardJsonView, TIMED_OUT_REASONS } from "./setup-onboard-copy.js";
|
|
10
10
|
import { standalonePlan, standalonePlanNotes, } from "./onboard-standalone-copy.js";
|
|
11
11
|
import { CliError, UsageError } from "./errors.js";
|
|
12
12
|
import { onboardFileSnapshot } from "./onboard-file-snapshot.js";
|
|
@@ -51,10 +51,11 @@ export function onboardIdentityLines(identity) {
|
|
|
51
51
|
const person = label(display?.personLabel || identity.membershipId);
|
|
52
52
|
const email = display?.email ? label(display.email) : "";
|
|
53
53
|
const workspace = label(display?.workspaceName || identity.account);
|
|
54
|
-
|
|
54
|
+
// CTC-4680 (Ryan, round 2): "Signed in to Catalyst as …" and "Catalyst workspace: …", with no
|
|
55
|
+
// repeated email and no internal slug.
|
|
55
56
|
return [
|
|
56
|
-
`Signed in as ${person}${email ? ` (${email})` : ""}`,
|
|
57
|
-
`
|
|
57
|
+
`Signed in to Catalyst as ${person}${email && email !== person ? ` (${email})` : ""}`,
|
|
58
|
+
`Catalyst workspace: ${workspace} · ${identity.role}`,
|
|
58
59
|
];
|
|
59
60
|
}
|
|
60
61
|
const EXIT_FAILED = 10;
|
|
@@ -1398,7 +1399,11 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1398
1399
|
}
|
|
1399
1400
|
if (signal.aborted) {
|
|
1400
1401
|
const settled = journalStep(journal, id);
|
|
1401
|
-
|
|
1402
|
+
// CTC-4680 round 5: a person who stopped at "Ready to try again?" stopped a step whose link
|
|
1403
|
+
// timed out; keep that reason so the summary says so, not "skipped for now".
|
|
1404
|
+
if (settled?.state !== "done" &&
|
|
1405
|
+
settled?.state !== "failed" &&
|
|
1406
|
+
!TIMED_OUT_REASONS.has(settled?.reason ?? ""))
|
|
1402
1407
|
recordStep({
|
|
1403
1408
|
id,
|
|
1404
1409
|
state: "waiting",
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// ready-copy.ts — CTC-4680: what `catalyst ready` says about a team, in words a person can act on.
|
|
2
|
+
//
|
|
3
|
+
// The check ids, states and reason codes stay in `--json` (`id`) for scripts. The `line`, `fix` and
|
|
4
|
+
// `who` a person reads never carry a check id, a reason code, an API route or the word "tenant", and
|
|
5
|
+
// every fix is either a full URL or a step in Linear's own settings. `line` keeps its `team KEY: `
|
|
6
|
+
// prefix because install.sh strips exactly that before printing a team row.
|
|
7
|
+
const FIXER = {
|
|
8
|
+
oauth_scope: "owner_or_admin",
|
|
9
|
+
token_live: "owner_or_admin",
|
|
10
|
+
team_visible: "owner_or_admin_in_linear",
|
|
11
|
+
mapped_states_exist: "owner_or_admin",
|
|
12
|
+
mapping_total: "owner_or_admin",
|
|
13
|
+
types_compatible: "owner_or_admin",
|
|
14
|
+
labels_present: "nobody",
|
|
15
|
+
writes_land: "owner_or_admin",
|
|
16
|
+
webhook_covers_team: "owner_or_admin",
|
|
17
|
+
hosts_current: "host_operator",
|
|
18
|
+
environment_declared: "owner_or_admin",
|
|
19
|
+
tools_resolvable: "owner_or_admin",
|
|
20
|
+
required_values: "owner_or_admin",
|
|
21
|
+
reviewer_required: "owner_or_admin",
|
|
22
|
+
reviewer_configured: "nobody",
|
|
23
|
+
reviewer_answering: "repository_admin",
|
|
24
|
+
linear_automation_pr_open: "owner_or_admin_in_linear",
|
|
25
|
+
linear_automation_pr_review: "owner_or_admin_in_linear",
|
|
26
|
+
linear_automation_pr_ready: "owner_or_admin_in_linear",
|
|
27
|
+
linear_automation_pr_merge: "owner_or_admin_in_linear",
|
|
28
|
+
merge_queue_configured: "repository_admin",
|
|
29
|
+
coding_account_enrolled: "owner_or_admin",
|
|
30
|
+
github_app_installed: "repository_admin",
|
|
31
|
+
thoughts_reachable: "repository_admin",
|
|
32
|
+
};
|
|
33
|
+
const WHO = {
|
|
34
|
+
nobody: "nobody; it clears on its own",
|
|
35
|
+
owner_or_admin: "an owner or admin of your Catalyst workspace",
|
|
36
|
+
owner_or_admin_in_linear: "an admin of your Linear workspace",
|
|
37
|
+
host_operator: "whoever runs your Catalyst host",
|
|
38
|
+
repository_admin: "an admin of the team's GitHub repository",
|
|
39
|
+
};
|
|
40
|
+
/** Who fixes a check, as a phrase. An unknown check goes to the workspace's owners and admins. */
|
|
41
|
+
export function whoFixes(checkId) {
|
|
42
|
+
return WHO[FIXER[checkId] ?? "owner_or_admin"];
|
|
43
|
+
}
|
|
44
|
+
const SLOT_WORDS = {
|
|
45
|
+
dispatch: "starting work",
|
|
46
|
+
intake: "intake",
|
|
47
|
+
research: "research",
|
|
48
|
+
plan: "planning",
|
|
49
|
+
implement: "writing the code",
|
|
50
|
+
remediate: "fixing review findings",
|
|
51
|
+
verify: "checking the work",
|
|
52
|
+
review: "review",
|
|
53
|
+
pr: "pull requests",
|
|
54
|
+
done: "Done",
|
|
55
|
+
canceled: "Canceled",
|
|
56
|
+
};
|
|
57
|
+
function wordList(items) {
|
|
58
|
+
if (items.length <= 1)
|
|
59
|
+
return items.join("");
|
|
60
|
+
return `${items.slice(0, -1).join(", ")} and ${items.at(-1)}`;
|
|
61
|
+
}
|
|
62
|
+
function slotList(slots) {
|
|
63
|
+
return wordList(slots.map((s) => SLOT_WORDS[s] ?? s.replaceAll("_", " ")));
|
|
64
|
+
}
|
|
65
|
+
function origin(baseUrl) {
|
|
66
|
+
return baseUrl.replace(/\/+$/, "");
|
|
67
|
+
}
|
|
68
|
+
/** The team's own page in Catalyst settings, optionally one of its sub-pages (`map`, `adopt`). */
|
|
69
|
+
export function teamPage(baseUrl, key, sub) {
|
|
70
|
+
return `${origin(baseUrl)}/settings/linear-teams/${encodeURIComponent(key)}${sub ? `/${sub}` : ""}`;
|
|
71
|
+
}
|
|
72
|
+
/** The dispatch gate: whether Catalyst may start any ticket in the team. */
|
|
73
|
+
export function dispatchGateCopy(baseUrl, key, status, missingSlots) {
|
|
74
|
+
if (status === "open")
|
|
75
|
+
return { line: `team ${key}: Catalyst can start ${key} tickets` };
|
|
76
|
+
// CTC-4708: the cloud says the mapping is saved and its copy of Linear hasn't caught up. That is a
|
|
77
|
+
// wait, not something to fix.
|
|
78
|
+
if (status === "stages_syncing")
|
|
79
|
+
return {
|
|
80
|
+
line: `team ${key}: Catalyst is still copying ${key}'s stages from Linear. It starts ${key} tickets once that finishes.`,
|
|
81
|
+
};
|
|
82
|
+
const slots = missingSlots.length > 0 ? slotList(missingSlots) : "its moves";
|
|
83
|
+
if (status === "mapping_missing")
|
|
84
|
+
return {
|
|
85
|
+
line: `team ${key}: Catalyst doesn't know which ${key} stages to use for ${slots}. Until it does, it starts no ${key} tickets.`,
|
|
86
|
+
fix: `pick them at ${teamPage(baseUrl, key, "map")}`,
|
|
87
|
+
};
|
|
88
|
+
if (status === "mapping_state_unresolved")
|
|
89
|
+
// The gate reads Catalyst's own copy of the team, which can lag Linear by a minute after a team
|
|
90
|
+
// is created or its workflow is set up (CTC-4680 Rerun A). Say that, not "map your stages".
|
|
91
|
+
return {
|
|
92
|
+
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.`,
|
|
93
|
+
fix: `if you just set up ${key}, run catalyst ready again in a minute. If this stays, press Re-check at ${teamPage(baseUrl, key)}`,
|
|
94
|
+
};
|
|
95
|
+
return {
|
|
96
|
+
line: `team ${key}: Catalyst can't start ${key} tickets yet.`,
|
|
97
|
+
fix: `open ${teamPage(baseUrl, key)} to see why`,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const AUTOMATION_RULES = {
|
|
101
|
+
linear_automation_pr_open: { rule: "On PR open", when: "when a pull request opens" },
|
|
102
|
+
linear_automation_pr_review: {
|
|
103
|
+
rule: "On PR review request or activity",
|
|
104
|
+
when: "when a pull request gets a review",
|
|
105
|
+
},
|
|
106
|
+
linear_automation_pr_ready: {
|
|
107
|
+
rule: "On PR ready for merge",
|
|
108
|
+
when: "when a pull request is ready to merge",
|
|
109
|
+
},
|
|
110
|
+
linear_automation_pr_merge: { rule: "On PR merge", when: "when a pull request merges" },
|
|
111
|
+
};
|
|
112
|
+
/** Plain names for checks with no sentence of their own below. */
|
|
113
|
+
const CHECK_NAMES = {
|
|
114
|
+
labels_present: "Catalyst's labels in Linear",
|
|
115
|
+
writes_land: "Catalyst's changes to Linear",
|
|
116
|
+
webhook_covers_team: "events from Linear and GitHub for this team",
|
|
117
|
+
hosts_current: "your connected Catalyst hosts",
|
|
118
|
+
environment_declared: "the repository's settings file",
|
|
119
|
+
tools_resolvable: "the tools the repository's settings name",
|
|
120
|
+
reviewer_required: "the required code reviewer",
|
|
121
|
+
reviewer_configured: "a code reviewer",
|
|
122
|
+
reviewer_answering: "the code reviewer's answers",
|
|
123
|
+
merge_queue_configured: "the repository's merge queue",
|
|
124
|
+
github_app_installed: "Catalyst on GitHub",
|
|
125
|
+
thoughts_reachable: "the thoughts repository",
|
|
126
|
+
};
|
|
127
|
+
/** The names above that read as plural ("labels need attention"). */
|
|
128
|
+
const PLURAL = new Set([
|
|
129
|
+
"labels_present",
|
|
130
|
+
"writes_land",
|
|
131
|
+
"webhook_covers_team",
|
|
132
|
+
"hosts_current",
|
|
133
|
+
"tools_resolvable",
|
|
134
|
+
"reviewer_answering",
|
|
135
|
+
]);
|
|
136
|
+
/** The settings page that owns each check, from the cloud's CHECK_COPY settingsPath. */
|
|
137
|
+
const CHECK_PAGES = {
|
|
138
|
+
oauth_scope: "/settings/connections",
|
|
139
|
+
token_live: "/settings/connections",
|
|
140
|
+
webhook_covers_team: "/settings/projects",
|
|
141
|
+
hosts_current: "/settings/projects",
|
|
142
|
+
environment_declared: "/settings/projects",
|
|
143
|
+
tools_resolvable: "/settings/environment",
|
|
144
|
+
required_values: "/settings/projects",
|
|
145
|
+
reviewer_required: "/settings/projects",
|
|
146
|
+
reviewer_configured: "/settings/projects",
|
|
147
|
+
reviewer_answering: "/settings/projects",
|
|
148
|
+
merge_queue_configured: "/settings/projects",
|
|
149
|
+
coding_account_enrolled: "/settings/coding-accounts",
|
|
150
|
+
github_app_installed: "/settings/projects",
|
|
151
|
+
thoughts_reachable: "/settings/projects",
|
|
152
|
+
};
|
|
153
|
+
const AGAIN = "run catalyst ready again in a few minutes";
|
|
154
|
+
/** One failing or unknown team check, in plain words. `valuesFix` is the variable-naming fix the
|
|
155
|
+
* caller already built for `required_values`, or null when the cloud named no variable. */
|
|
156
|
+
export function teamCheckCopy(baseUrl, key, id, state, valuesFix) {
|
|
157
|
+
const team = (text) => `team ${key}: ${text}`;
|
|
158
|
+
const base = origin(baseUrl);
|
|
159
|
+
const unknown = state === "unknown";
|
|
160
|
+
const automation = AUTOMATION_RULES[id];
|
|
161
|
+
if (automation)
|
|
162
|
+
return unknown
|
|
163
|
+
? {
|
|
164
|
+
line: team(`Catalyst couldn't read ${key}'s pull request automations in Linear.`),
|
|
165
|
+
fix: AGAIN,
|
|
166
|
+
}
|
|
167
|
+
: {
|
|
168
|
+
line: team(`Linear moves ${key}'s tickets by itself ${automation.when}. That fights Catalyst, which moves them itself.`),
|
|
169
|
+
fix: `in Linear, open Settings → Teams → ${key} → Workflow → Pull request and commit automations and set "${automation.rule}" to No action, including any branch overrides. Then run catalyst ready again`,
|
|
170
|
+
};
|
|
171
|
+
switch (id) {
|
|
172
|
+
case "oauth_scope":
|
|
173
|
+
return unknown
|
|
174
|
+
? { line: team("Catalyst couldn't check its Linear connection."), fix: `check that Linear is connected at ${base}/settings/connections` }
|
|
175
|
+
: {
|
|
176
|
+
line: team("Catalyst's Linear connection is missing a permission it needs."),
|
|
177
|
+
fix: `reconnect Linear at ${base}/settings/connections?reauthorize=linear`,
|
|
178
|
+
};
|
|
179
|
+
case "token_live":
|
|
180
|
+
return unknown
|
|
181
|
+
? { line: team("Catalyst couldn't reach Linear to check its connection."), fix: AGAIN }
|
|
182
|
+
: {
|
|
183
|
+
line: team("Linear stopped accepting Catalyst's connection. It was revoked or expired."),
|
|
184
|
+
fix: `reconnect Linear at ${base}/settings/connections`,
|
|
185
|
+
};
|
|
186
|
+
case "team_visible":
|
|
187
|
+
return unknown
|
|
188
|
+
? { line: team(`Catalyst hasn't confirmed it can see ${key} in Linear yet.`), fix: AGAIN }
|
|
189
|
+
: {
|
|
190
|
+
line: team(`Catalyst can't see ${key} in Linear, usually because the team is private.`),
|
|
191
|
+
fix: `in Linear, open Settings → API → Catalyst Cloud → Team access and add ${key}`,
|
|
192
|
+
};
|
|
193
|
+
case "mapped_states_exist":
|
|
194
|
+
case "mapping_total":
|
|
195
|
+
case "types_compatible":
|
|
196
|
+
if (unknown)
|
|
197
|
+
return { line: team(`Catalyst is still reading ${key}'s stages from Linear.`), fix: "run catalyst ready again in a minute" };
|
|
198
|
+
if (id === "mapped_states_exist")
|
|
199
|
+
return {
|
|
200
|
+
line: team(`A stage Catalyst uses in ${key} was deleted or renamed in Linear.`),
|
|
201
|
+
fix: `pick its replacement at ${teamPage(baseUrl, key, "map")}`,
|
|
202
|
+
};
|
|
203
|
+
if (id === "mapping_total")
|
|
204
|
+
return {
|
|
205
|
+
line: team(`${key}'s stages aren't all mapped yet, so Catalyst doesn't know where to move its tickets.`),
|
|
206
|
+
fix: `map them at ${teamPage(baseUrl, key, "map")}, or set up Catalyst's workflow at ${teamPage(baseUrl, key, "adopt")}`,
|
|
207
|
+
};
|
|
208
|
+
return {
|
|
209
|
+
line: team(`A stage mapped in ${key} is the wrong kind, for example a Done stage that Linear treats as in progress.`),
|
|
210
|
+
fix: `choose the right stages at ${teamPage(baseUrl, key, "map")}`,
|
|
211
|
+
};
|
|
212
|
+
case "required_values":
|
|
213
|
+
return unknown
|
|
214
|
+
? {
|
|
215
|
+
line: team(`Catalyst hasn't read the settings of ${key}'s repository yet.`),
|
|
216
|
+
fix: "catalyst setup reads them when you choose repositories. Run catalyst setup",
|
|
217
|
+
}
|
|
218
|
+
: {
|
|
219
|
+
line: team(`A variable ${key}'s repository needs has no value, so Catalyst can't run its work.`),
|
|
220
|
+
fix: valuesFix ?? `add the missing values on the repository's Environment page: open ${base}/settings/projects, then the project, then the repository`,
|
|
221
|
+
};
|
|
222
|
+
case "coding_account_enrolled":
|
|
223
|
+
return unknown
|
|
224
|
+
? { line: team("Catalyst couldn't check your AI accounts."), fix: AGAIN }
|
|
225
|
+
: {
|
|
226
|
+
line: team("Catalyst has no AI account to do the work with."),
|
|
227
|
+
fix: `run catalyst setup to add one, or add it at ${base}/settings/coding-accounts`,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const name = CHECK_NAMES[id] ?? id.replaceAll("_", " ");
|
|
231
|
+
const page = CHECK_PAGES[id] ?? `/settings/linear-teams/${encodeURIComponent(key)}`;
|
|
232
|
+
return unknown
|
|
233
|
+
? { line: team(`Catalyst hasn't checked ${name} yet.`), fix: AGAIN }
|
|
234
|
+
: {
|
|
235
|
+
line: team(`${name.charAt(0).toUpperCase()}${name.slice(1)} ${PLURAL.has(id) ? "need" : "needs"} attention.`),
|
|
236
|
+
fix: `open ${base}${page}`,
|
|
237
|
+
};
|
|
238
|
+
}
|
package/dist/ready.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// is a note, never a failure; the HTTP SDK loading is a check for cloud reads.
|
|
4
4
|
import { accessSync, constants, existsSync, readFileSync, realpathSync, statSync, } from "node:fs";
|
|
5
5
|
import { basename, delimiter, dirname, join } from "node:path";
|
|
6
|
-
import { LEGACY_PACKAGE_NAME, PACKAGE_NAME, defaultSkillsDirFor, loadConfig, normalizeBaseUrl, readManifest, upgradeCommand, } from "./config.js";
|
|
6
|
+
import { DEFAULT_BASE_URL, LEGACY_PACKAGE_NAME, PACKAGE_NAME, defaultSkillsDirFor, loadConfig, normalizeBaseUrl, readManifest, upgradeCommand, } from "./config.js";
|
|
7
7
|
import { contractVersionInRange, readContractCache } from "./contract.js";
|
|
8
8
|
import { CliError } from "./errors.js";
|
|
9
9
|
import { latestPublishedVersion } from "./published.js";
|
|
@@ -15,6 +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, teamPage, whoFixes } from "./ready-copy.js";
|
|
18
19
|
import { onboardStatePath, readOnboardJournal } from "./onboard.js";
|
|
19
20
|
export { semverOlder };
|
|
20
21
|
/** `ready`'s own wording for the replica. It never recommends adopting the replica — until the
|
|
@@ -40,7 +41,7 @@ function readyReplicaLine(s) {
|
|
|
40
41
|
case "not-configured":
|
|
41
42
|
return "replica: not configured — run login first";
|
|
42
43
|
case "absent":
|
|
43
|
-
return `replica: absent at ${s.dbPath} — optional, and off by default for large
|
|
44
|
+
return `replica: absent at ${s.dbPath} — optional, and off by default for large workspaces while the snapshot path is being made safe; every read works through the API`;
|
|
44
45
|
case "fresh":
|
|
45
46
|
return `replica: fresh at ${s.dbPath} (cursor ${s.cursor}, heartbeat ${s.heartbeatAgeMs}ms ago${s.lag !== undefined ? `, ${s.lag} behind head ${s.head}` : ""})`;
|
|
46
47
|
case "stale":
|
|
@@ -132,7 +133,7 @@ function unresolvedList(v) {
|
|
|
132
133
|
}
|
|
133
134
|
function unresolvedLine(u) {
|
|
134
135
|
const which = u.references.length === 1 ? "which has" : "which have";
|
|
135
|
-
return `${u.name}
|
|
136
|
+
return `${u.name} refers to ${u.references.join(", ")}, ${which} no value, so Catalyst can't start work there`;
|
|
136
137
|
}
|
|
137
138
|
/** A team check's fix line. A check that carries `names` (CTC-3561: `required_values`, contract
|
|
138
139
|
* 1.24.0) names them and where to set them. CTC-3606: it also names each `unresolved` variable and
|
|
@@ -141,24 +142,13 @@ function unresolvedLine(u) {
|
|
|
141
142
|
* "set". Every field is optional so an older cloud still works. Everything printed is a declared
|
|
142
143
|
* identifier or a repository name, never a value, and nothing here reads a value. Any other check
|
|
143
144
|
* keeps the generic line. */
|
|
144
|
-
function
|
|
145
|
-
const automationRules = {
|
|
146
|
-
linear_automation_pr_open: "On PR open",
|
|
147
|
-
linear_automation_pr_review: "On PR review request or activity",
|
|
148
|
-
linear_automation_pr_ready: "On PR ready for merge",
|
|
149
|
-
linear_automation_pr_merge: "On PR merge",
|
|
150
|
-
};
|
|
151
|
-
const rule = automationRules[c.id];
|
|
152
|
-
if (rule) {
|
|
153
|
-
return `in Linear, open Settings → Teams → ${label} → Workflow → Workflows & automations → Pull request and commit automations and set ${rule} to No action, including branch-specific overrides; then run catalyst team check ${label}`;
|
|
154
|
-
}
|
|
145
|
+
function valuesFix(base, c) {
|
|
155
146
|
const unresolved = unresolvedList(c.unresolved);
|
|
156
147
|
const unresolvedNames = new Set(unresolved.map((u) => u.name));
|
|
157
148
|
const names = nameList(c.names).filter((n) => !unresolvedNames.has(n));
|
|
158
149
|
const parts = [];
|
|
159
150
|
if (names.length > 0) {
|
|
160
|
-
parts.push(`set ${names.join(", ")} on the repository's Environment page
|
|
161
|
-
`(team ${label}; ${names.length === 1 ? "it has" : "they have"} no value at repository or account scope)`);
|
|
151
|
+
parts.push(`set ${names.join(", ")} on the repository's Environment page: open ${base}/settings/projects, then the project, then the repository`);
|
|
162
152
|
}
|
|
163
153
|
for (const u of unresolved)
|
|
164
154
|
parts.push(unresolvedLine(u));
|
|
@@ -176,15 +166,7 @@ function teamCheckFix(label, c) {
|
|
|
176
166
|
for (const u of repoUnresolved)
|
|
177
167
|
parts.push(`in ${note.repo}, ${unresolvedLine(u)}`);
|
|
178
168
|
}
|
|
179
|
-
|
|
180
|
-
return `open settings for team ${label} and resolve ${c.id}`;
|
|
181
|
-
return parts.join(". ");
|
|
182
|
-
}
|
|
183
|
-
function whoCanAnswer(doc) {
|
|
184
|
-
const roles = doc.humans.map((h) => `${h.role} ${h.linearUserId}`);
|
|
185
|
-
return roles.length
|
|
186
|
-
? roles.join(", ")
|
|
187
|
-
: "a tenant owner or admin (none resolved on the contract)";
|
|
169
|
+
return parts.length === 0 ? null : parts.join(". ");
|
|
188
170
|
}
|
|
189
171
|
export async function readyReport(ctx, deps) {
|
|
190
172
|
const checks = [];
|
|
@@ -265,7 +247,7 @@ export async function readyReport(ctx, deps) {
|
|
|
265
247
|
id: "bundle",
|
|
266
248
|
ok: false,
|
|
267
249
|
note: true,
|
|
268
|
-
line: `bundle: ${installed} installed is older than
|
|
250
|
+
line: `bundle: ${installed} installed is older than your workspace's minimum ${minVersion} — upgrade: ${upgradeCommand()}`,
|
|
269
251
|
});
|
|
270
252
|
}
|
|
271
253
|
}
|
|
@@ -394,7 +376,7 @@ export async function readyReport(ctx, deps) {
|
|
|
394
376
|
});
|
|
395
377
|
if (cache) {
|
|
396
378
|
const doc = cache.doc;
|
|
397
|
-
const
|
|
379
|
+
const base = (cfg?.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
398
380
|
for (const team of doc.teams) {
|
|
399
381
|
const label = team.key ?? team.id;
|
|
400
382
|
// The team's dispatch gate, straight off the cached contract: an older cloud omits it and this
|
|
@@ -404,28 +386,36 @@ export async function readyReport(ctx, deps) {
|
|
|
404
386
|
// checked still reports its gate, which is exactly the team most likely to be unmapped.
|
|
405
387
|
const dg = team.dispatchGate;
|
|
406
388
|
if (dg && typeof dg.status === "string") {
|
|
407
|
-
|
|
408
|
-
|
|
389
|
+
// CTC-4680: the cloud's own remedy text names an API route and says "tenant", so the words
|
|
390
|
+
// come from ready-copy; the status and slots stay readable in the check id's JSON neighbours.
|
|
391
|
+
const copy = dispatchGateCopy(base, label, dg.status, dg.missingSlots ?? []);
|
|
392
|
+
checks.push(dg.status === "stages_syncing"
|
|
409
393
|
? {
|
|
410
|
-
id: `team:${label}:dispatchGate`,
|
|
411
|
-
ok: true,
|
|
412
|
-
line: `team ${label}: dispatch gate open`,
|
|
413
|
-
}
|
|
414
|
-
: {
|
|
415
394
|
id: `team:${label}:dispatchGate`,
|
|
416
395
|
ok: false,
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
396
|
+
note: true,
|
|
397
|
+
line: copy.line,
|
|
398
|
+
}
|
|
399
|
+
: dg.status === "open"
|
|
400
|
+
? {
|
|
401
|
+
id: `team:${label}:dispatchGate`,
|
|
402
|
+
ok: true,
|
|
403
|
+
line: copy.line,
|
|
404
|
+
}
|
|
405
|
+
: {
|
|
406
|
+
id: `team:${label}:dispatchGate`,
|
|
407
|
+
ok: false,
|
|
408
|
+
line: copy.line,
|
|
409
|
+
...(copy.fix ? { fix: copy.fix } : {}),
|
|
410
|
+
who: whoFixes("mapping_total"),
|
|
411
|
+
});
|
|
422
412
|
}
|
|
423
413
|
if (team.readiness.status === "unchecked") {
|
|
424
414
|
checks.push({
|
|
425
415
|
id: `team:${label}`,
|
|
426
416
|
ok: true,
|
|
427
417
|
note: true,
|
|
428
|
-
line: `team ${label}:
|
|
418
|
+
line: `team ${label}: Catalyst hasn't checked this team yet`,
|
|
429
419
|
});
|
|
430
420
|
continue;
|
|
431
421
|
}
|
|
@@ -441,13 +431,14 @@ export async function readyReport(ctx, deps) {
|
|
|
441
431
|
for (const c of bad) {
|
|
442
432
|
const meta = doc.readinessChecks.find((r) => r.id === c.id);
|
|
443
433
|
const needsAnswer = meta?.needsAnswer ?? true;
|
|
434
|
+
const copy = teamCheckCopy(base, label, c.id, c.state === "fail" ? "fail" : "unknown", valuesFix(base, c));
|
|
444
435
|
checks.push({
|
|
445
436
|
id: `team:${label}:${c.id}`,
|
|
446
437
|
ok: !needsAnswer && c.state !== "fail",
|
|
447
438
|
note: !needsAnswer,
|
|
448
|
-
line:
|
|
449
|
-
fix:
|
|
450
|
-
who: needsAnswer ?
|
|
439
|
+
line: copy.line,
|
|
440
|
+
...(copy.fix ? { fix: copy.fix } : {}),
|
|
441
|
+
who: needsAnswer ? whoFixes(c.id) : "nobody yet; it is informational",
|
|
451
442
|
});
|
|
452
443
|
}
|
|
453
444
|
if (bad.length === 0 && team.readiness.status !== "ready") {
|
|
@@ -455,9 +446,11 @@ export async function readyReport(ctx, deps) {
|
|
|
455
446
|
id: `team:${label}`,
|
|
456
447
|
ok: team.readiness.status !== "blocked",
|
|
457
448
|
note: team.readiness.status === "degraded",
|
|
458
|
-
line:
|
|
459
|
-
|
|
460
|
-
|
|
449
|
+
line: team.readiness.status === "degraded"
|
|
450
|
+
? `team ${label}: works, with some optional settings still missing`
|
|
451
|
+
: `team ${label}: not ready for work yet`,
|
|
452
|
+
fix: `open ${teamPage(base, label)}`,
|
|
453
|
+
who: whoFixes("mapping_total"),
|
|
461
454
|
});
|
|
462
455
|
}
|
|
463
456
|
}
|
|
@@ -47,6 +47,16 @@ export const CONNECTION_REPAIR_REASONS = new Set([
|
|
|
47
47
|
"github_app_permissions_unverified", "github_app_repository_access_unverified",
|
|
48
48
|
"linear_workspace_permissions_unverified", "personal_permissions_unverified",
|
|
49
49
|
]);
|
|
50
|
+
/** CTC-4680: the signed-in browser route that goes straight to GitHub's install page. The settings
|
|
51
|
+
* page it replaces opened Integrations and started nothing. */
|
|
52
|
+
export const GITHUB_INSTALL_START = "/connect/github/start";
|
|
53
|
+
/** CTC-4680 round 5: a browser wait that ran its whole window without the person finishing. */
|
|
54
|
+
export const TIMED_OUT_REASONS = new Set([
|
|
55
|
+
"consent_timeout",
|
|
56
|
+
"workspace_browser_unavailable",
|
|
57
|
+
"personal_browser_unavailable",
|
|
58
|
+
"github_installation_browser_unavailable",
|
|
59
|
+
]);
|
|
50
60
|
const clean = (s) => s.replace(/[\u0000-\u001f\u007f]/g, "").slice(0, 300);
|
|
51
61
|
function value(step, key) {
|
|
52
62
|
const v = step.evidence?.[key];
|
|
@@ -114,6 +124,10 @@ function unfinished(step) {
|
|
|
114
124
|
return "could not read them; checked again before work starts";
|
|
115
125
|
if (step.reason === "interrupted")
|
|
116
126
|
return "skipped for now";
|
|
127
|
+
if (TIMED_OUT_REASONS.has(step.reason ?? ""))
|
|
128
|
+
return "the link timed out";
|
|
129
|
+
if (step.reason === "github_installation_approval_pending")
|
|
130
|
+
return "waiting for an owner of your GitHub organization";
|
|
117
131
|
if (step.state === "failed")
|
|
118
132
|
return "could not finish this step";
|
|
119
133
|
switch (step.id) {
|
|
@@ -143,6 +157,7 @@ export function setupStepView(step, journal, required = false) {
|
|
|
143
157
|
: step.state === "failed"
|
|
144
158
|
? "fail"
|
|
145
159
|
: step.reason === "prerequisite_not_ready" ||
|
|
160
|
+
step.reason === "github_install_pending" ||
|
|
146
161
|
(step.state === "pending" && !step.reason)
|
|
147
162
|
? "later"
|
|
148
163
|
: !required &&
|
|
@@ -160,6 +175,8 @@ export function setupStepView(step, journal, required = false) {
|
|
|
160
175
|
.map((id) => SETUP_NUMBERS[id])
|
|
161
176
|
.filter((n) => n !== undefined)),
|
|
162
177
|
];
|
|
178
|
+
if (step.reason === "github_install_pending")
|
|
179
|
+
numbers.splice(0, numbers.length, SETUP_NUMBERS["github.install"]);
|
|
163
180
|
outcome =
|
|
164
181
|
step.state === "pending" && !step.reason
|
|
165
182
|
? "not checked yet"
|
|
@@ -188,7 +205,8 @@ function page(base, path) {
|
|
|
188
205
|
}
|
|
189
206
|
}
|
|
190
207
|
function action(step, base, journal) {
|
|
191
|
-
if (CONNECTION_REPAIR_REASONS.has(step.reason ?? "")
|
|
208
|
+
if (CONNECTION_REPAIR_REASONS.has(step.reason ?? "") ||
|
|
209
|
+
step.reason === "github_installation_approval_pending")
|
|
192
210
|
return onboardReasonText(step, { baseUrl: base, journal });
|
|
193
211
|
const link = (path, verb) => {
|
|
194
212
|
const url = page(base, path);
|
|
@@ -202,7 +220,12 @@ function action(step, base, journal) {
|
|
|
202
220
|
case "linear.personal":
|
|
203
221
|
return link("/settings/connected-accounts?connect=linear", "connect your Linear account");
|
|
204
222
|
case "github.install":
|
|
205
|
-
|
|
223
|
+
// The start route answers 403 to anyone but an owner or admin, so a member gets the
|
|
224
|
+
// Integrations page and who to ask instead.
|
|
225
|
+
if (step.reason === "github_installation_admin_required" ||
|
|
226
|
+
step.reason === "member_scope")
|
|
227
|
+
return onboardReasonText(step, { baseUrl: base, journal });
|
|
228
|
+
return link(GITHUB_INSTALL_START, "install Catalyst on your GitHub organization");
|
|
206
229
|
case "github.personal":
|
|
207
230
|
return link("/settings/connected-accounts?connect=github", "connect your GitHub account");
|
|
208
231
|
case "accounts":
|
|
@@ -317,7 +340,7 @@ export function setupBrowserInstruction(id, base, opened = false) {
|
|
|
317
340
|
"Connect your Linear account",
|
|
318
341
|
],
|
|
319
342
|
"github.install": [
|
|
320
|
-
|
|
343
|
+
GITHUB_INSTALL_START,
|
|
321
344
|
"install Catalyst on your GitHub organization",
|
|
322
345
|
"Install Catalyst on your GitHub organization",
|
|
323
346
|
],
|
|
@@ -386,6 +409,8 @@ export function onboardJsonView(journal, base, paused = false, options = {}) {
|
|
|
386
409
|
const step = view.steps.find((step) => step.id === action.id);
|
|
387
410
|
const role = view.steps.find((step) => step.id === "signin" && step.state === "done")?.evidence?.role;
|
|
388
411
|
const admin = step.reason === "member_scope" ||
|
|
412
|
+
// CTC-4680: refused by the person's Catalyst role, not by GitHub, so a workspace admin acts.
|
|
413
|
+
step.reason === "github_installation_admin_required" ||
|
|
389
414
|
step.reason?.endsWith("_identity_unverified") ||
|
|
390
415
|
(action.id === "accounts" && role !== "owner" && role !== "admin");
|
|
391
416
|
const url = action.text
|
package/dist/setup-render.js
CHANGED
|
@@ -86,7 +86,9 @@ export function createSetupRenderer(stream, env) {
|
|
|
86
86
|
.replace(/\u001b\]8;;[^\u001b]*\u001b\\/g, "")
|
|
87
87
|
.replace(/\u001b\[[0-9;]*m/g, "");
|
|
88
88
|
const rail = traits.unicode ? "│" : "";
|
|
89
|
-
|
|
89
|
+
// CTC-4680: details sit 4 columns in (under the rail in Unicode), not under the step title at 6 or
|
|
90
|
+
// 11, so the eye doesn't travel across a small screen to read them.
|
|
91
|
+
const titleCol = 4;
|
|
90
92
|
const outcomeCol = traits.unicode ? 38 : 43;
|
|
91
93
|
const prefix = (col) => rail + " ".repeat(col - rail.length);
|
|
92
94
|
const mark = (kind) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-cloud/cli",
|
|
3
|
-
"version": "0.15.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.15.2",
|
|
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",
|
|
7
7
|
"publishConfig": {
|
|
@@ -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.15.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.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.15.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.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.15.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.2 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Onboard me
|
|
10
10
|
|
|
@@ -140,7 +140,8 @@ const field = (name) => {
|
|
|
140
140
|
const line = statusLines.find((l) => l.startsWith(`${name}:`));
|
|
141
141
|
return line ? line.slice(name.length + 1).trim() : null;
|
|
142
142
|
};
|
|
143
|
-
|
|
143
|
+
// A newer CLI prints "Catalyst workspace:"; an older one prints "Tenant:".
|
|
144
|
+
const tenant = field("Catalyst workspace") ?? field("Tenant");
|
|
144
145
|
const api = field("API");
|
|
145
146
|
const connected = tenant !== null && tryLoadConfig() !== null;
|
|
146
147
|
// The app and the API share an origin; the base URL is whatever this machine was connected to, so
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Own one Catalyst Cloud project end to end until it closes. Use when the person says "run this project for me", "own this until it ships", "keep this moving", or hands you a project id or a set of tickets to drive. Subscribes to the account's event stream for the scope through the catalyst CLI, reacts to each change in the same turn, makes tickets ready and moves them to dispatch, parks what should stop, chases stalls, escalates inward, and keeps one status summary current. Writes to Linear as the app actor; never polls.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.2 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Run this project
|
|
10
10
|
|
package/skills/unstick/SKILL.md
CHANGED
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Get a stuck Catalyst Cloud ticket moving again. Use when the person asks "why is this parked and can you release it?", "unpark this", "get things flowing again", "the outage is over, retry what failed", or hands you a ticket that is not moving. Reads why nothing runs and what holds the ticket through the catalyst CLI, decides whether the recorded cause is fixed, previews the release, releases every governor holding the ticket the right way (or one failure class across a team), and raises an ask only for what a person has to do. Never releases a cause it cannot show changed without saying so.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.2 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Unstick
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
The human's decision inbox on Catalyst Cloud, and the one way an agent raises a decision on their behalf. Use when the person asks "what needs me?", "what am I blocking?", or when active work is gated on a choice only they can make. Lists open asks ranked by how much open work each one holds, files an ask through the cloud's ask route with the account's own template, and records the answer so the held work releases. Never answers as the human, never duplicates an open ask.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.2 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# What needs me
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
What environment variable names does this repository need, and where does each one come from? Scans the repository offline (no login, no network) and lists the names in three groups (build/test, deploy-only, bindings), each with where it was found, what uses it, and where a local value would come from. Never reads a value or prints one. Also checks the environment variable table in `.catalyst/catalyst.toml` offline. Use when someone asks "what does catalyst.env.json mean", "what env vars does this repo need", "why does the container need this", or before reviewing or writing the declaration.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.2 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# What this repository needs
|
|
10
10
|
|
|
@@ -4,7 +4,7 @@ description: >-
|
|
|
4
4
|
Use for "what's happening?", "where are we?", "why is that stuck?", "what closed?", "what's next?", "how does this work?", "why did it do that?", "how does it prioritise?" and "what does this setting do, where is it set?" in Catalyst Cloud, or asks for something to be done rather than known. Reads the contract via catalyst CLI. Read-only: route work/decisions, never answer as the human.
|
|
5
5
|
allowed-tools: Bash(catalyst:*) Bash(npx -p @catalyst-cloud/cli catalyst:*)
|
|
6
6
|
---
|
|
7
|
-
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.15.2 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# What's happening
|
|
10
10
|
|