@catalyst-cloud/cli 0.14.7 → 0.14.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +2 -2
- package/dist/args.js +17 -5
- package/dist/browser.js +37 -3
- package/dist/cli.js +4 -6
- package/dist/config.js +1 -2
- package/dist/consent-browser.js +58 -0
- package/dist/env/onboard-draft-store.js +10 -3
- package/dist/env/settings-authority.js +3 -1
- package/dist/events-cloud-tail.js +25 -13
- package/dist/events-cloud.js +124 -5
- package/dist/events.js +175 -29
- package/dist/oauth.js +4 -1
- package/dist/onboard-github.js +14 -3
- package/dist/onboard-next.js +139 -0
- package/dist/onboard-personal.js +14 -3
- package/dist/onboard-repositories.js +3 -4
- package/dist/onboard-runtime.js +4 -4
- package/dist/onboard-ui.js +14 -76
- package/dist/onboard-workspace.js +14 -3
- package/dist/onboard.js +63 -12
- package/dist/pagination.js +13 -1
- package/dist/query.js +39 -16
- package/dist/repository-id.js +5 -0
- package/package.json +1 -1
- package/skills/catalyst-github/SKILL.md +3 -1
- package/skills/catalyst-github/references/local-replica.md +78 -0
- package/skills/catalyst-github/references/reading-from-the-cloud.md +24 -0
- package/skills/catalyst-linear/SKILL.md +3 -1
- package/skills/catalyst-linear/references/local-replica.md +91 -0
- package/skills/catalyst-linear/references/reading-from-the-cloud.md +38 -0
- package/skills/catalyst-onboard/SKILL.md +3 -2
- package/skills/catalyst-onboard/references/local-replica.md +40 -0
- package/skills/catalyst-onboard/references/local-sync.md +5 -3
- package/skills/catalyst-onboard/scripts/local-sync.mjs +2 -2
- package/skills/catalyst-onboard/scripts/where-am-i.mjs +1 -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 +3 -1
- package/skills/whats-happening/references/local-replica.md +51 -0
- package/skills/whats-happening/references/reading-from-the-cloud.md +21 -0
package/dist/onboard-ui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createOnboardProgress, } from "./onboard-progress.js";
|
|
2
|
-
import { ONBOARD_STEPS, ONBOARD_TITLES, onboardIdentityLines, } from "./onboard.js";
|
|
2
|
+
import { ONBOARD_STEPS, ONBOARD_TITLES, onboardIdentityLines, onboardNextActions, onboardReadyForWork, onboardStepAction, } from "./onboard.js";
|
|
3
3
|
export function shouldUseOnboardUi(args, stdoutTty) {
|
|
4
4
|
return (stdoutTty &&
|
|
5
5
|
!args.json &&
|
|
@@ -194,81 +194,9 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
194
194
|
}
|
|
195
195
|
start(ONBOARD_TITLES[id]);
|
|
196
196
|
},
|
|
197
|
-
stepEnd(step) {
|
|
197
|
+
stepEnd(step, journal) {
|
|
198
198
|
stop();
|
|
199
|
-
const
|
|
200
|
-
account_enrollment_required: "No coding account is enrolled. An administrator can add one in the workspace's coding-account settings, then run catalyst onboard again.",
|
|
201
|
-
account_identity_unverified: "Your login changed while coding accounts were checked. Run catalyst onboard again to verify your person and workspace.",
|
|
202
|
-
account_inventory_unavailable: "Coding accounts could not be read. Run catalyst onboard again to retry.",
|
|
203
|
-
account_inventory_unverified: "The server did not return a fresh supported coding-account list. Run catalyst onboard after the server update.",
|
|
204
|
-
account_validation_admin_required: "An administrator must check the workspace's coding-account access.",
|
|
205
|
-
account_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard.",
|
|
206
|
-
account_validation_unavailable: "The provider check could not finish. Run catalyst onboard again to retry.",
|
|
207
|
-
account_validation_unverified: "The server did not return a fresh provider check. Run catalyst onboard after the server update.",
|
|
208
|
-
account_provider_access_unverified: "Coding-account provider access is not freshly verified. Check the account in the workspace's coding-account settings, then resume setup.",
|
|
209
|
-
codex_provider_access_unverified: "Codex credentials are stored, but provider access is not freshly verified. Setup did not refresh or rotate the Codex login.",
|
|
210
|
-
account_provider_walled: "Claude's usage limit is spent for now. Check the account's reset time, then run catalyst onboard again.",
|
|
211
|
-
account_provider_rejected: "Claude refused the stored login. Replace it in the workspace's coding-account settings, then resume setup.",
|
|
212
|
-
personal_consent_handoff: "The browser approval link could not be verified. Run catalyst onboard to try again.",
|
|
213
|
-
personal_identity_refused: "Your current login no longer matches this setup. Resume with the original workspace and person.",
|
|
214
|
-
personal_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
215
|
-
onboard_login_refresh_required: "Renew your login with catalyst login, then run catalyst onboard to resume.",
|
|
216
|
-
personal_status_unavailable: "Your personal connection could not be checked. Run catalyst onboard to try again.",
|
|
217
|
-
personal_browser_unavailable: "The browser could not open. Run catalyst onboard to try again.",
|
|
218
|
-
cloud_capability_unavailable: "This setup step is not available on this server yet. Use the web app link shown above, then resume after the server update.",
|
|
219
|
-
onboarding_capability_unavailable: "The server's setup capabilities could not be checked. Run catalyst onboard to try again.",
|
|
220
|
-
onboarding_capability_identity_unverified: "The server's setup capabilities belong to another login or workspace. Sign in to the original workspace to resume.",
|
|
221
|
-
onboarding_capability_login_refresh_required: "Your login needs a refresh before setup capabilities can be checked. Run catalyst login, then catalyst onboard.",
|
|
222
|
-
workspace_consent_refused: "The server did not accept this login for Linear workspace setup. Sign in again or ask your workspace administrator.",
|
|
223
|
-
github_installation_status_unavailable: "Your GitHub App installation could not be checked. Run catalyst onboard to try again.",
|
|
224
|
-
github_installation_status_shape: "The server returned a GitHub installation result that could not be verified.",
|
|
225
|
-
github_installation_consent_handoff: "The GitHub installation approval link could not be verified. Run catalyst onboard to try again.",
|
|
226
|
-
github_installation_consent_refused: "The server did not accept this login for GitHub App setup. Sign in again or ask your workspace administrator.",
|
|
227
|
-
github_installation_identity_refused: "GitHub setup belongs to another login or workspace. Sign in to the original workspace to resume.",
|
|
228
|
-
github_installation_admin_required: "A workspace owner or administrator approves the GitHub App installation.",
|
|
229
|
-
github_installation_login_refresh_required: "Your login needs a refresh. Run catalyst onboard to resume.",
|
|
230
|
-
github_installation_browser_unavailable: "The GitHub App approval page could not be opened. Use your workspace's Connections page, then resume.",
|
|
231
|
-
step_not_available_in_this_release: "This setup step is not available yet.",
|
|
232
|
-
prerequisite_not_ready: "Waiting for an earlier setup step.",
|
|
233
|
-
local_sync_not_selected: "Using cloud reads. Local sync was not selected.",
|
|
234
|
-
local_sync_capability_unavailable: "Local sync was selected but could not be verified.",
|
|
235
|
-
member_scope: "Your workspace administrator handles this step.",
|
|
236
|
-
onboarding_checks_pending: "Some required checks are still unverified.",
|
|
237
|
-
interrupted: "Setup paused. Run the same command to resume.",
|
|
238
|
-
signin_timeout: "Sign-in timed out. Run catalyst onboard to try again.",
|
|
239
|
-
linear_workspace_unverified: "Your Linear workspace connection could not be verified.",
|
|
240
|
-
team_inventory_empty: "No existing Linear teams could be verified.",
|
|
241
|
-
team_inventory_shape: "The cloud returned a team list that could not be verified.",
|
|
242
|
-
team_read_unavailable: "Your existing Linear teams could not be read. Run catalyst onboard to try again.",
|
|
243
|
-
team_read_identity_unverified: "Your login changed while setup was reading this workspace. Resume with the original workspace and person.",
|
|
244
|
-
project_identity_unverified: "Your login changed while setup was checking this project. Resume with the original workspace and person.",
|
|
245
|
-
project_options_unavailable: "Available teams and repositories could not be checked. Run catalyst onboard to try again.",
|
|
246
|
-
project_provider_inventory_unavailable: "Linear or GitHub access could not be verified. Check this workspace's connections, then resume.",
|
|
247
|
-
project_options_unverified: "The server returned a team or repository list that could not be verified.",
|
|
248
|
-
project_registration_visibility_pending: "Your project exists, but its repository binding is not visible yet. Run catalyst onboard to check again.",
|
|
249
|
-
project_binding_conflict: "This team or repository is already bound to a project. Ask your workspace administrator to check its binding, then resume.",
|
|
250
|
-
project_login_refresh_required: "Renew your login with catalyst onboard, then review the plan before creating this project.",
|
|
251
|
-
project_create_unverified: "Project creation could not be confirmed. Run catalyst onboard to check for the project before trying again.",
|
|
252
|
-
team_read_login_refresh_required: "Your login needs a refresh. Run catalyst onboard to resume.",
|
|
253
|
-
team_choice_required: "Choose an existing team with catalyst onboard --team <team ID or key>.",
|
|
254
|
-
team_selection_unverified: "Your selected Linear team could not be verified. Run catalyst onboard --team <team ID or key> to choose again.",
|
|
255
|
-
repository_choice_required: "Choose repositories with catalyst onboard --repo <owner/name>. Repeat --repo to choose more than one.",
|
|
256
|
-
repository_inventory_empty: "No accessible repositories are registered to this Linear team yet.",
|
|
257
|
-
repository_read_unavailable: "Your repository list could not be read. Run catalyst onboard to try again.",
|
|
258
|
-
repository_inventory_shape: "The cloud returned a repository list that could not be verified.",
|
|
259
|
-
repository_contract_unavailable: "Current repository IDs could not be checked. Run catalyst onboard to try again.",
|
|
260
|
-
repository_contract_unverified: "The cloud returned repository IDs that could not be verified for this workspace.",
|
|
261
|
-
repository_id_unverified: "A repository's current ID could not be verified. Keep your selection and try again.",
|
|
262
|
-
repository_binding_ambiguous: "A repository's Linear team binding is ambiguous. Your administrator needs to check it.",
|
|
263
|
-
repository_identity_unverified: "Sign in to the original workspace to choose repositories.",
|
|
264
|
-
settings_review_timeout: "The settings review timed out. Run catalyst onboard to resume.",
|
|
265
|
-
settings_checkout_unverified: "A selected repository's local checkout or full settings file could not be verified. Resume from its checkout.",
|
|
266
|
-
settings_draft_storage_unavailable: "A private settings draft could not be kept. Run catalyst onboard to retry.",
|
|
267
|
-
settings_draft_storage_timeout: "Keeping your settings draft took too long. Run catalyst onboard to retry.",
|
|
268
|
-
settings_approval_unverified: "Settings still need review and approval. Kept drafts and local validation do not approve them.",
|
|
269
|
-
repository_selection_unverified: "Your selected repositories could not be verified. Run catalyst onboard --repo <owner/name> to choose again.",
|
|
270
|
-
};
|
|
271
|
-
const text = `${ONBOARD_TITLES[step.id]}${step.reason ? `: ${reasons[step.reason] ?? step.reason.replaceAll("_", " ")}` : ""}`;
|
|
199
|
+
const text = `${ONBOARD_TITLES[step.id]}${step.reason ? `: ${onboardStepAction(journal, step, deps.baseUrl?.())}` : ""}`;
|
|
272
200
|
const kind = step.state === "done"
|
|
273
201
|
? "info"
|
|
274
202
|
: step.state === "failed"
|
|
@@ -283,12 +211,22 @@ export function createClackOnboardUi(prompts, streams, deps = {}) {
|
|
|
283
211
|
message,
|
|
284
212
|
finish(journal, only) {
|
|
285
213
|
stop();
|
|
286
|
-
let text =
|
|
214
|
+
let text = [
|
|
215
|
+
"Setup still needs these steps:",
|
|
216
|
+
...onboardNextActions(journal, deps.baseUrl?.(), only),
|
|
217
|
+
"resume: catalyst onboard",
|
|
218
|
+
].join("\n");
|
|
287
219
|
if (abort.signal.aborted)
|
|
288
220
|
text =
|
|
289
221
|
"Setup paused. Your progress is saved.\nresume: catalyst onboard";
|
|
290
222
|
else if (journal.complete && journal.exit === 0 && !only)
|
|
291
223
|
text = "Onboarding complete.";
|
|
224
|
+
else if (onboardReadyForWork(journal, only))
|
|
225
|
+
text = [
|
|
226
|
+
"Ready for work.",
|
|
227
|
+
"Next, when you want:",
|
|
228
|
+
...onboardNextActions(journal, deps.baseUrl?.()),
|
|
229
|
+
].join("\n");
|
|
292
230
|
else if (only && journal.exit === 0)
|
|
293
231
|
text = `${ONBOARD_TITLES[only]} finished. Onboarding still has other steps.\nresume: catalyst onboard`;
|
|
294
232
|
prompts.outro(text, { output: streams.output });
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { finishOnTheWeb } from "./consent-browser.js";
|
|
1
2
|
import { loadConfig, normalizeBaseUrl } from "./config.js";
|
|
2
3
|
import { pollConsent } from "./onboard-consent.js";
|
|
3
4
|
const STATUS = "/api/v1/me/connections/linear/workspace";
|
|
@@ -252,11 +253,15 @@ export function linearWorkspaceAdapter(options) {
|
|
|
252
253
|
return { state: "refused", reason: "workspace_admin_required" };
|
|
253
254
|
ctx.stderr("Approve the organization's Linear connection in your browser. Your personal approval follows.");
|
|
254
255
|
// The signed URL is transient credential material: only the browser receives it.
|
|
256
|
+
let browserUnavailable = false;
|
|
255
257
|
try {
|
|
256
|
-
options.openBrowser(url);
|
|
258
|
+
await options.openBrowser(url, signal);
|
|
257
259
|
}
|
|
258
260
|
catch {
|
|
259
|
-
|
|
261
|
+
if (signal?.aborted)
|
|
262
|
+
return waiting("interrupted");
|
|
263
|
+
browserUnavailable = true;
|
|
264
|
+
ctx.stderr(finishOnTheWeb(current.baseUrl, "connections", "connect Linear"));
|
|
260
265
|
}
|
|
261
266
|
let latest = waiting("workspace_status_unavailable");
|
|
262
267
|
const run = () => pollConsent({
|
|
@@ -271,6 +276,8 @@ export function linearWorkspaceAdapter(options) {
|
|
|
271
276
|
outcome: "waiting",
|
|
272
277
|
reason: "cloud_capability_unavailable",
|
|
273
278
|
};
|
|
279
|
+
if (latest.reason === "workspace_login_refresh_required")
|
|
280
|
+
return { outcome: "waiting", reason: latest.reason };
|
|
274
281
|
if (latest.state === "done")
|
|
275
282
|
return { outcome: "connected" };
|
|
276
283
|
if (latest.state === "refused" || latest.state === "failed")
|
|
@@ -283,7 +290,11 @@ export function linearWorkspaceAdapter(options) {
|
|
|
283
290
|
const result = options.wait
|
|
284
291
|
? await options.wait("Waiting for workspace approval", run)
|
|
285
292
|
: await run();
|
|
286
|
-
|
|
293
|
+
if (result.state === "done")
|
|
294
|
+
return latest;
|
|
295
|
+
return browserUnavailable && result.reason === "consent_timeout"
|
|
296
|
+
? waiting("workspace_browser_unavailable")
|
|
297
|
+
: result;
|
|
287
298
|
},
|
|
288
299
|
};
|
|
289
300
|
}
|
package/dist/onboard.js
CHANGED
|
@@ -8,6 +8,7 @@ import { machinePathsFile } from "../vendor/paths/node.js";
|
|
|
8
8
|
import { configPathFor } from "./config.js";
|
|
9
9
|
import { CliError, UsageError } from "./errors.js";
|
|
10
10
|
import { onboardFileSnapshot } from "./onboard-file-snapshot.js";
|
|
11
|
+
import { onboardReasonText, onboardTeamKey, savedOnboardBaseUrl, } from "./onboard-next.js";
|
|
11
12
|
import { parseBootstrapPlan, bootstrapPlanHash, bootstrapPlanLines, } from "./onboard-bootstrap.js";
|
|
12
13
|
export const ONBOARD_STEPS = [
|
|
13
14
|
"machine",
|
|
@@ -102,15 +103,14 @@ export const ONBOARD_DEPENDENCIES = {
|
|
|
102
103
|
values: ["settings"],
|
|
103
104
|
capacity: ["projects"],
|
|
104
105
|
daemon: ["signin"],
|
|
106
|
+
// What dispatch needs. Environment approval gates only implement and pr, and the first phase
|
|
107
|
+
// needs no imported values, so repository settings and local sync do not hold a first ticket.
|
|
105
108
|
"first-ticket": [
|
|
106
109
|
"projects",
|
|
107
110
|
"linear.adopt",
|
|
108
111
|
"linear.automations",
|
|
109
112
|
"accounts",
|
|
110
|
-
"settings",
|
|
111
|
-
"values",
|
|
112
113
|
"capacity",
|
|
113
|
-
"daemon",
|
|
114
114
|
],
|
|
115
115
|
};
|
|
116
116
|
const ADMIN_STEPS = new Set([
|
|
@@ -604,6 +604,41 @@ function stepSatisfied(step) {
|
|
|
604
604
|
(step.id === "linear.automations" &&
|
|
605
605
|
step.reason === "automation_management_unavailable"))));
|
|
606
606
|
}
|
|
607
|
+
/** Steps whose wait does not hold a full run's exit code: work runs without them. A failure still
|
|
608
|
+
* fails the run, and the receipt's `complete` still needs them satisfied. Never used for gating. */
|
|
609
|
+
export const ONBOARD_DEFERRED_STEPS = new Set([
|
|
610
|
+
"settings",
|
|
611
|
+
"values",
|
|
612
|
+
"housekeeping",
|
|
613
|
+
"first-ticket",
|
|
614
|
+
]);
|
|
615
|
+
/** A full run that exited 0 with deferred steps still open: ready for work, not complete. */
|
|
616
|
+
export function onboardReadyForWork(journal, only) {
|
|
617
|
+
return !only && journal.exit === 0 && !journal.complete;
|
|
618
|
+
}
|
|
619
|
+
/** A recorded step's next action; a prerequisite wait names the first unfinished step it needs. */
|
|
620
|
+
export function onboardStepAction(journal, step, baseUrl) {
|
|
621
|
+
if (!step.reason)
|
|
622
|
+
return "Not checked yet. Run catalyst onboard.";
|
|
623
|
+
const waitsFor = journal &&
|
|
624
|
+
(ONBOARD_DEPENDENCIES[step.id] ?? []).find((parent) => !stepSatisfied(journalStep(journal, parent)));
|
|
625
|
+
return onboardReasonText(step, {
|
|
626
|
+
baseUrl,
|
|
627
|
+
journal,
|
|
628
|
+
...(waitsFor ? { waitsFor: ONBOARD_TITLES[waitsFor] } : {}),
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
/** "<title>: <action>" for each recorded step still blocking onboarding, then the first-ticket hint
|
|
632
|
+
* once the steps a ticket needs are done. Display only: completeness and exits stay with the engine. */
|
|
633
|
+
export function onboardNextActions(journal, baseUrl, only) {
|
|
634
|
+
const lines = journal.steps
|
|
635
|
+
.filter((step) => (!only || step.id === only) && !stepSatisfied(step))
|
|
636
|
+
.sort((a, b) => ONBOARD_STEPS.indexOf(a.id) - ONBOARD_STEPS.indexOf(b.id))
|
|
637
|
+
.map((step) => `${ONBOARD_TITLES[step.id]}: ${onboardStepAction(journal, step, baseUrl)}`);
|
|
638
|
+
if (["projects", "accounts", "linear.adopt", "capacity"].every((id) => journalStep(journal, id)?.state === "done"))
|
|
639
|
+
lines.push(`Move a ticket in ${onboardTeamKey(journal) ?? "<TEAM KEY>"} to Todo; \`catalyst explain <ticket>\` says why it is or is not starting.`);
|
|
640
|
+
return lines;
|
|
641
|
+
}
|
|
607
642
|
function requireMatchingIdentity(journal, identity) {
|
|
608
643
|
const account = journal.account ?? journal.tenant;
|
|
609
644
|
if ((account && (!identity || account !== identity.account)) ||
|
|
@@ -920,7 +955,10 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
920
955
|
process.on(name, interrupted);
|
|
921
956
|
const finish = (code) => {
|
|
922
957
|
journal.exit = code;
|
|
923
|
-
journal.complete =
|
|
958
|
+
journal.complete =
|
|
959
|
+
!only &&
|
|
960
|
+
code === 0 &&
|
|
961
|
+
ONBOARD_STEPS.every((id) => stepSatisfied(journalStep(journal, id)));
|
|
924
962
|
writeOnboardJournal(statePath, journal);
|
|
925
963
|
if (args.json)
|
|
926
964
|
ctx.stdout(JSON.stringify(journal));
|
|
@@ -929,10 +967,19 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
929
967
|
else {
|
|
930
968
|
if (journal.complete)
|
|
931
969
|
ctx.stdout("Onboarding complete.");
|
|
970
|
+
else if (onboardReadyForWork(journal, only)) {
|
|
971
|
+
ctx.stdout("Ready for work.");
|
|
972
|
+
ctx.stdout("Next, when you want:");
|
|
973
|
+
for (const line of onboardNextActions(journal, savedOnboardBaseUrl(ctx.home)))
|
|
974
|
+
ctx.stdout(line);
|
|
975
|
+
}
|
|
932
976
|
else if (only && code === 0)
|
|
933
977
|
ctx.stdout(`${ONBOARD_TITLES[only]} finished. Onboarding still has other steps.`);
|
|
934
|
-
else
|
|
978
|
+
else {
|
|
935
979
|
ctx.stdout(`Setup still needs ${journal.steps.filter((step) => !stepSatisfied(step)).length} checks.`);
|
|
980
|
+
for (const line of onboardNextActions(journal, savedOnboardBaseUrl(ctx.home), only))
|
|
981
|
+
ctx.stdout(line);
|
|
982
|
+
}
|
|
936
983
|
ctx.stdout(bootstrap && journalStep(journal, "machine")?.state !== "done"
|
|
937
984
|
? "resume: run the original setup command"
|
|
938
985
|
: "resume: catalyst onboard");
|
|
@@ -1086,7 +1133,7 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1086
1133
|
reason: "member_scope",
|
|
1087
1134
|
at: isoNow(ctx, deps),
|
|
1088
1135
|
});
|
|
1089
|
-
deps.ui?.stepEnd(journalStep(journal, id));
|
|
1136
|
+
deps.ui?.stepEnd(journalStep(journal, id), journal);
|
|
1090
1137
|
continue;
|
|
1091
1138
|
}
|
|
1092
1139
|
const missing = (ONBOARD_DEPENDENCIES[id] ?? []).find((parent) => !stepSatisfied(journalStep(journal, parent)));
|
|
@@ -1098,7 +1145,7 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1098
1145
|
at: isoNow(ctx, deps),
|
|
1099
1146
|
});
|
|
1100
1147
|
writeOnboardJournal(statePath, journal);
|
|
1101
|
-
deps.ui?.stepEnd(journalStep(journal, id));
|
|
1148
|
+
deps.ui?.stepEnd(journalStep(journal, id), journal);
|
|
1102
1149
|
continue;
|
|
1103
1150
|
}
|
|
1104
1151
|
let adapter = deps.adapters?.[id];
|
|
@@ -1122,7 +1169,7 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1122
1169
|
at: isoNow(ctx, deps),
|
|
1123
1170
|
});
|
|
1124
1171
|
writeOnboardJournal(statePath, journal);
|
|
1125
|
-
deps.ui?.stepEnd(journalStep(journal, id));
|
|
1172
|
+
deps.ui?.stepEnd(journalStep(journal, id), journal);
|
|
1126
1173
|
continue;
|
|
1127
1174
|
}
|
|
1128
1175
|
try {
|
|
@@ -1142,7 +1189,7 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1142
1189
|
at: isoNow(ctx, deps),
|
|
1143
1190
|
});
|
|
1144
1191
|
writeOnboardJournal(statePath, journal);
|
|
1145
|
-
deps.ui?.stepEnd(journalStep(journal, id));
|
|
1192
|
+
deps.ui?.stepEnd(journalStep(journal, id), journal);
|
|
1146
1193
|
continue;
|
|
1147
1194
|
}
|
|
1148
1195
|
}
|
|
@@ -1169,7 +1216,7 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1169
1216
|
at: isoNow(ctx, deps),
|
|
1170
1217
|
});
|
|
1171
1218
|
writeOnboardJournal(statePath, journal);
|
|
1172
|
-
deps.ui?.stepEnd(journalStep(journal, id));
|
|
1219
|
+
deps.ui?.stepEnd(journalStep(journal, id), journal);
|
|
1173
1220
|
continue;
|
|
1174
1221
|
}
|
|
1175
1222
|
}
|
|
@@ -1223,8 +1270,11 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1223
1270
|
});
|
|
1224
1271
|
return finish(EXIT_WAITING);
|
|
1225
1272
|
}
|
|
1226
|
-
deps.ui?.stepEnd(journalStep(journal, id));
|
|
1273
|
+
deps.ui?.stepEnd(journalStep(journal, id), journal);
|
|
1227
1274
|
writeOnboardJournal(statePath, journal);
|
|
1275
|
+
// Later steps share the same login; preserve the actionable cause instead of cascading failures.
|
|
1276
|
+
if (journalStep(journal, id)?.reason?.endsWith("_login_refresh_required"))
|
|
1277
|
+
return finish(EXIT_WAITING);
|
|
1228
1278
|
if (refused)
|
|
1229
1279
|
break;
|
|
1230
1280
|
}
|
|
@@ -1234,7 +1284,8 @@ export async function cmdOnboard(args, ctx, deps = {}, cliVersion = "0.13.1") {
|
|
|
1234
1284
|
? EXIT_REFUSED
|
|
1235
1285
|
: scope.some((id) => journalStep(journal, id)?.state === "failed")
|
|
1236
1286
|
? EXIT_FAILED
|
|
1237
|
-
: scope.every((id) => stepSatisfied(journalStep(journal, id))
|
|
1287
|
+
: scope.every((id) => stepSatisfied(journalStep(journal, id)) ||
|
|
1288
|
+
(!only && ONBOARD_DEFERRED_STEPS.has(id)))
|
|
1238
1289
|
? 0
|
|
1239
1290
|
: EXIT_WAITING;
|
|
1240
1291
|
return finish(code);
|
package/dist/pagination.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
import { CliError } from "./errors.js";
|
|
13
13
|
export const NEXT_CURSOR_HEADER = "x-mirror-next-cursor";
|
|
14
14
|
export const TOTAL_HEADER = "x-mirror-total";
|
|
15
|
+
/** CTC-4556 — the `/issues` params that narrowed the page, comma-separated; absent on older mirrors. */
|
|
16
|
+
export const SCOPE_HEADER = "x-mirror-scope";
|
|
15
17
|
/** The server clamps every page to this regardless of the requested `limit` (MAX_VIEW_LIMIT). */
|
|
16
18
|
export const SERVER_PAGE_CAP = 500;
|
|
17
19
|
/** 100 pages × 500 rows = 50,000. Reaching it is a refusal, not an ending. */
|
|
@@ -29,6 +31,12 @@ export function rowsOf(body) {
|
|
|
29
31
|
}
|
|
30
32
|
return [];
|
|
31
33
|
}
|
|
34
|
+
function readScope(headers) {
|
|
35
|
+
const raw = headers.get(SCOPE_HEADER);
|
|
36
|
+
if (raw === null)
|
|
37
|
+
return null;
|
|
38
|
+
return raw.split(",").map((p) => p.trim()).filter(Boolean);
|
|
39
|
+
}
|
|
32
40
|
function readTotal(headers) {
|
|
33
41
|
const raw = headers.get(TOTAL_HEADER);
|
|
34
42
|
if (raw === null)
|
|
@@ -43,6 +51,7 @@ export async function fetchPage(api, path, query) {
|
|
|
43
51
|
rows: rowsOf(res.body),
|
|
44
52
|
nextCursor: next === null || next === "" ? null : next,
|
|
45
53
|
total: readTotal(res.headers),
|
|
54
|
+
scope: readScope(res.headers),
|
|
46
55
|
};
|
|
47
56
|
}
|
|
48
57
|
export async function fetchAllPages(api, path, query) {
|
|
@@ -50,13 +59,16 @@ export async function fetchAllPages(api, path, query) {
|
|
|
50
59
|
const seen = new Set();
|
|
51
60
|
let after;
|
|
52
61
|
let total = null;
|
|
62
|
+
let scope = null;
|
|
53
63
|
for (let pages = 1; pages <= MAX_PAGES; pages++) {
|
|
54
64
|
const page = await fetchPage(api, path, { ...query, limit: SERVER_PAGE_CAP, after });
|
|
55
65
|
rows.push(...page.rows);
|
|
56
66
|
if (page.total !== null)
|
|
57
67
|
total = page.total;
|
|
68
|
+
// A param counts as applied only if every page says so.
|
|
69
|
+
scope = pages === 1 ? page.scope : scope && page.scope && scope.filter((p) => page.scope?.includes(p));
|
|
58
70
|
if (page.nextCursor === null)
|
|
59
|
-
return { rows, total, pages };
|
|
71
|
+
return { rows, total, pages, scope };
|
|
60
72
|
// ⛔ A cursor that does not advance is an infinite loop, not a long read.
|
|
61
73
|
if (seen.has(page.nextCursor)) {
|
|
62
74
|
throw new CliError(`GET ${path} repeated a page cursor after ${rows.length} rows — the cloud is not advancing the keyset, so --all cannot finish`, "pagination-stalled", 1);
|
package/dist/query.js
CHANGED
|
@@ -20,9 +20,10 @@ export async function cmdQuery(args, ctx, deps = {}) {
|
|
|
20
20
|
throw new UsageError("query needs a subcommand: issues | issue <id> | pulls | pull <id> | projects | cycles | search <terms> | changes --since <n>");
|
|
21
21
|
const cfg = requireConfig(ctx);
|
|
22
22
|
const filters = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
// CTC-4556 — trimmed here, so the value sent and the value compared are the one the mirror reads.
|
|
24
|
+
team: trimmedFlag(args, "team"),
|
|
25
|
+
project: trimmedFlag(args, "project"),
|
|
26
|
+
state: trimmedFlag(args, "state"),
|
|
26
27
|
limit: flagInt(args, "limit", 50),
|
|
27
28
|
all: flagBool(args, "all"),
|
|
28
29
|
};
|
|
@@ -115,23 +116,25 @@ async function fromReplica(ctx, cfg, sub, rest, f, deps) {
|
|
|
115
116
|
}
|
|
116
117
|
async function fromApi(ctx, cfg, sub, rest, f, args) {
|
|
117
118
|
const api = apiClient(cfg, ctx);
|
|
118
|
-
|
|
119
|
-
|
|
119
|
+
// CTC-4556 — the mirror narrows `/issues` by `team_key` and `state_name`; `team` and a state name
|
|
120
|
+
// in `state` narrow nothing there.
|
|
121
|
+
const issueScope = {
|
|
122
|
+
team_key: f.team,
|
|
120
123
|
project: f.project,
|
|
121
|
-
|
|
122
|
-
limit: f.limit,
|
|
124
|
+
state_name: f.state,
|
|
123
125
|
};
|
|
124
126
|
switch (sub) {
|
|
125
127
|
case "issues": {
|
|
126
128
|
if (f.all) {
|
|
127
|
-
const { rows } = await fetchAllPages(api, "/api/v1/issues",
|
|
128
|
-
|
|
129
|
-
project: f.project,
|
|
130
|
-
state: f.state,
|
|
131
|
-
});
|
|
129
|
+
const { rows, scope } = await fetchAllPages(api, "/api/v1/issues", issueScope);
|
|
130
|
+
requireIssueScope(scope, f);
|
|
132
131
|
return applyFilters(rows, f); // no .slice — --all means the whole scope
|
|
133
132
|
}
|
|
134
|
-
const page = await fetchPage(api, "/api/v1/issues",
|
|
133
|
+
const page = await fetchPage(api, "/api/v1/issues", {
|
|
134
|
+
...issueScope,
|
|
135
|
+
limit: f.limit,
|
|
136
|
+
});
|
|
137
|
+
requireIssueScope(page.scope, f);
|
|
135
138
|
const rows = applyFilters(page.rows, f).slice(0, f.limit);
|
|
136
139
|
const notice = truncationNotice(page);
|
|
137
140
|
if (notice)
|
|
@@ -247,6 +250,27 @@ function needArg(rest, usage) {
|
|
|
247
250
|
throw new UsageError(`query ${usage}`);
|
|
248
251
|
return v;
|
|
249
252
|
}
|
|
253
|
+
/** CTC-4556 — refuse a page the cloud did not narrow by a filter the user asked for. Filtering it
|
|
254
|
+
* here would print the matches among the newest rows and call that the answer. */
|
|
255
|
+
function requireIssueScope(scope, f) {
|
|
256
|
+
const asked = [
|
|
257
|
+
["--team", "team_key", f.team],
|
|
258
|
+
["--state", "state_name", f.state],
|
|
259
|
+
["--project", "project", f.project],
|
|
260
|
+
];
|
|
261
|
+
for (const [flag, param, value] of asked) {
|
|
262
|
+
if (!value || scope?.includes(param))
|
|
263
|
+
continue;
|
|
264
|
+
throw new CliError(`the cloud did not apply ${flag} (${scope === null ? "it sent no x-mirror-scope header" : `it applied: ${scope.join(", ") || "nothing"}`}), so the page is not that scope; nothing printed. Read it with --source replica, or drop ${flag}`, "filter-not-applied", 1);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function trimmedFlag(args, name) {
|
|
268
|
+
const value = flagString(args, name)?.trim();
|
|
269
|
+
return value === "" ? undefined : value;
|
|
270
|
+
}
|
|
271
|
+
/** The mirror's comparison for team keys and state names: trimmed and lowercased, nothing more.
|
|
272
|
+
* "In Progress" and "In_Progress" stay different names. */
|
|
273
|
+
const sameName = (a, b) => String(a ?? "").trim().toLowerCase() === b.trim().toLowerCase();
|
|
250
274
|
export function applyFilters(rows, f) {
|
|
251
275
|
return rows.filter((r) => {
|
|
252
276
|
if (f.team &&
|
|
@@ -254,12 +278,11 @@ export function applyFilters(rows, f) {
|
|
|
254
278
|
.toUpperCase()
|
|
255
279
|
.startsWith(`${f.team.toUpperCase()}-`) ||
|
|
256
280
|
r.team_id === f.team ||
|
|
257
|
-
r.team_key
|
|
281
|
+
sameName(r.team_key, f.team)))
|
|
258
282
|
return false;
|
|
259
283
|
if (f.project && r.project_id !== f.project)
|
|
260
284
|
return false;
|
|
261
|
-
if (f.state &&
|
|
262
|
-
String(r.state ?? "").toLowerCase() !== f.state.toLowerCase())
|
|
285
|
+
if (f.state && !sameName(r.state, f.state))
|
|
263
286
|
return false;
|
|
264
287
|
return true;
|
|
265
288
|
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Repository row IDs are opaque cloud identifiers, including tenant-prefixed legacy names.
|
|
2
|
+
* Syntax validation never establishes tenant ownership; callers must join fresh ACL and contract reads. */
|
|
3
|
+
export function isRepositoryId(value) {
|
|
4
|
+
return typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/.test(value);
|
|
5
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catalyst-cloud/cli",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.8",
|
|
4
4
|
"description": "Customer skill bundle for Catalyst Cloud, named from the executive's seat \u2014 get set up, see what is happening, see what needs you, run a project, unstick what is stuck \u2014 plus the catalyst CLI that reads through the Catalyst Cloud SDK, writes through the agent proxy, and connects your machine to your cloud account with one command. The skills directory is the roster; this line never counts it.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -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.14.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.14.8 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Catalyst's GitHub
|
|
10
10
|
|
|
@@ -23,6 +23,8 @@ Both read through `catalyst query` and `contract` from the cloud by default; the
|
|
|
23
23
|
| -- | -- |
|
|
24
24
|
| the person asks what Catalyst did to the branch or PR, why the title changed, why history was rewritten, what a label means, or what happens on merge | `references/what-a-pr-accumulates.md` |
|
|
25
25
|
| the person asks why a PR has not merged, what the queue needs, what a clean review pass looks like, or what a policy requires | `references/is-it-mergeable.md` |
|
|
26
|
+
| which command answers a PR question from the cloud, and what the cloud detail cannot see | `references/reading-from-the-cloud.md` |
|
|
27
|
+
| local SQL or `--local-threads`, only when `catalyst replica status --json` reports `configured: true` | `references/local-replica.md` |
|
|
26
28
|
|
|
27
29
|
## Rules
|
|
28
30
|
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Reading pull requests from the local replica (opt-in)
|
|
2
|
+
|
|
3
|
+
Load this only when `catalyst replica status --json` reports `configured: true`. On any other machine, read from the cloud (`references/reading-from-the-cloud.md`) and do not mention the replica.
|
|
4
|
+
|
|
5
|
+
The replica is an on-machine SQLite copy of the account. It exists only when the person opted in to local sync. It adds ad hoc SQL over mirrored pull requests and one leg the cloud PR detail lacks: review-thread rows. It is never required.
|
|
6
|
+
|
|
7
|
+
## Check it is caught up first
|
|
8
|
+
|
|
9
|
+
Run `catalyst replica status --probe --json` before every local read. Read locally only when `verdict` is `fresh` and `cursor` equals `head`. `stale` or `absent` with `configured: true` means the person opted in and the writer is down. Answer from the cloud and say the local copy is behind.
|
|
10
|
+
|
|
11
|
+
Quote the cursor with any local answer, for example "from the local replica at cursor 4810".
|
|
12
|
+
|
|
13
|
+
## Threads: the one leg only the replica adds
|
|
14
|
+
|
|
15
|
+
**Zero thread rows does not mean zero open threads.** The thread table is filled only by resolve and unresolve events, and nothing seeds it from GitHub. A thread opened and never touched leaves no row. So zero rows can mean every thread is still open. A local "all resolved" answers only "of the threads seen resolving, which are resolved now". It never replaces the cloud's merge evaluator, which also judges a resolution against force-pushes.
|
|
16
|
+
|
|
17
|
+
`node scripts/is-it-mergeable.mjs <ticket> --local-threads` reads the thread leg from the replica. It does so only after a probe proves the replica is caught up with the cloud head. Without the flag, the thread leg is inconclusive and the cloud's evaluator decides.
|
|
18
|
+
|
|
19
|
+
The query the script runs, for one pull request:
|
|
20
|
+
|
|
21
|
+
```sql
|
|
22
|
+
select resolved, count(*) as n from pr_review_threads
|
|
23
|
+
where repo_id = 'acme/widgets' and pr_number = 42
|
|
24
|
+
group by resolved
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`resolved` is 1 for resolved, 0 for unresolved, and null when no flag was mirrored.
|
|
28
|
+
|
|
29
|
+
## Running SQL yourself
|
|
30
|
+
|
|
31
|
+
`catalyst replica sql "<one SELECT>" --json` runs one read-only `SELECT` and prints rows. `catalyst replica schema <table>` prints a table's columns; read them before querying a table this page does not cover. Timestamps are milliseconds since the epoch.
|
|
32
|
+
|
|
33
|
+
| table | one row is | key |
|
|
34
|
+
| -- | -- | -- |
|
|
35
|
+
| `pull_requests` | a pull request: `state`, `draft`, `merged`, `merged_at`, `head_sha`, `head_ref`, `base_ref`, `mergeable`, `mergeable_state`, `auto_merge`, `linear_issue_identifier` | `repo_id`, `number`; also `node_id` |
|
|
36
|
+
| `check_runs` | one check at one commit: `name`, `status`, `conclusion` | `repo_id`, `head_sha` |
|
|
37
|
+
| `commit_statuses` | a legacy commit status: `context`, `state` | `repo_id`, `sha` |
|
|
38
|
+
| `reviews` | a submitted review: `user_id`, `state`, `submitted_at` | `repo_id`, `pr_number` |
|
|
39
|
+
| `pr_review_threads` | a review thread seen resolving: `resolved`, `resolved_at` | `repo_id`, `pr_number` |
|
|
40
|
+
| `pr_review_comments`, `pr_conversation_comments` | an inline comment, and a PR conversation comment | `repo_id`, `pr_number` |
|
|
41
|
+
|
|
42
|
+
Replace `acme/widgets`, `42` and `ENG-123` with real values.
|
|
43
|
+
|
|
44
|
+
Every pull request that names a ticket:
|
|
45
|
+
|
|
46
|
+
```sql
|
|
47
|
+
select repo_id, number, state, draft, merged, head_sha, mergeable_state
|
|
48
|
+
from pull_requests where linear_issue_identifier = 'ENG-123'
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The checks at a pull request's current head:
|
|
52
|
+
|
|
53
|
+
```sql
|
|
54
|
+
select c.name, c.status, c.conclusion
|
|
55
|
+
from check_runs c join pull_requests p
|
|
56
|
+
on p.repo_id = c.repo_id and p.head_sha = c.head_sha
|
|
57
|
+
where p.repo_id = 'acme/widgets' and p.number = 42
|
|
58
|
+
order by c.name
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The reviews on a pull request, newest first:
|
|
62
|
+
|
|
63
|
+
```sql
|
|
64
|
+
select user_id, state, submitted_at from reviews
|
|
65
|
+
where repo_id = 'acme/widgets' and pr_number = 42
|
|
66
|
+
order by submitted_at desc
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Open pull requests that GitHub reports in conflict:
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
select repo_id, number, linear_issue_identifier from pull_requests
|
|
73
|
+
where state = 'open' and mergeable_state = 'dirty'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## What the replica does not answer
|
|
77
|
+
|
|
78
|
+
Reactions, PR labels and the commit each review was submitted against are not mirrored anywhere a read can reach. The cloud's evaluator holds them. `references/is-it-mergeable.md` explains what that leaves inconclusive.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Reading pull request facts from the cloud
|
|
2
|
+
|
|
3
|
+
Cloud reads are the default on every machine. They need no local writer and no local database. Every command below reads the cloud through the `catalyst` CLI.
|
|
4
|
+
|
|
5
|
+
| question | command |
|
|
6
|
+
| -- | -- |
|
|
7
|
+
| Which pull requests name this ticket? | `catalyst query pulls --ticket <KEY-123>` |
|
|
8
|
+
| What state is the PR in: draft, merged, conflict, auto-merge? | `node scripts/read-pr.mjs <KEY-123>` |
|
|
9
|
+
| Which checks are red or still running at the head? | `node scripts/read-pr.mjs <KEY-123>` |
|
|
10
|
+
| Is it mergeable under this repository's policy? | `node scripts/is-it-mergeable.mjs <KEY-123>`, which also reads `catalyst contract --path merge` |
|
|
11
|
+
| Which stage is the linked ticket in? | `catalyst query issue <KEY-123>` |
|
|
12
|
+
| What did the last pr or merge phase do? | `catalyst history <KEY-123>` |
|
|
13
|
+
|
|
14
|
+
The first stderr line names the source: `source: api (...)`. Quote it when freshness matters.
|
|
15
|
+
|
|
16
|
+
## What the cloud PR detail does not carry
|
|
17
|
+
|
|
18
|
+
The detail has no review-thread count. So the thread leg of `is-it-mergeable.mjs` is inconclusive from a cloud read, and the cloud's own merge evaluator decides. A queue-ready label on the PR means it already said yes. Reactions, PR labels and the commit each review was submitted against are not in the detail either.
|
|
19
|
+
|
|
20
|
+
A smaller ticket read is coming: the cloud will answer with only the status fields and the linked pull requests. No CLI flag selects it yet, so read the full record.
|
|
21
|
+
|
|
22
|
+
## The local replica
|
|
23
|
+
|
|
24
|
+
Only on a machine where `catalyst replica status --json` reports `configured: true`, `references/local-replica.md` adds local SQL and the opt-in `--local-threads` read. Everywhere else, these cloud reads are the whole answer.
|
|
@@ -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.14.
|
|
7
|
+
<!-- vendored-from: @catalyst-cloud/catalyst-skills@0.14.8 — written in this repository for customer accounts -->
|
|
8
8
|
|
|
9
9
|
# Catalyst Linear
|
|
10
10
|
|
|
@@ -30,6 +30,8 @@ Exit codes: 0 done, 1 not found or a usage error, 2 this machine is not connecte
|
|
|
30
30
|
| "what did Catalyst write on this ticket?", a comment shape, a label, the marker, the 👀 | `references/what-a-ticket-accumulates.md` |
|
|
31
31
|
| before any read you will report on; "is this current?"; citing; searching | `references/reading-a-ticket.md` |
|
|
32
32
|
| before any comment, move, label or new ticket; identity, budget, slots, ids | `references/writing-to-linear.md` |
|
|
33
|
+
| which command answers a ticket question from the cloud: stage, last phase, PR, what changed | `references/reading-from-the-cloud.md` |
|
|
34
|
+
| local SQL, only when `catalyst replica status --json` reports `configured: true` | `references/local-replica.md` |
|
|
33
35
|
|
|
34
36
|
## Rules
|
|
35
37
|
|