@catalyst-cloud/cli 0.14.6 → 0.14.7

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.
Files changed (62) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/README.md +5 -3
  3. package/dist/args.js +51 -13
  4. package/dist/cli.js +58 -10
  5. package/dist/config.js +16 -3
  6. package/dist/daemon-authority-lifetime.js +171 -0
  7. package/dist/daemon-authority.js +216 -0
  8. package/dist/daemon-cache.js +348 -0
  9. package/dist/daemon-store.js +370 -0
  10. package/dist/daemon-supervisor.js +460 -0
  11. package/dist/env/onboard-draft-store.js +243 -0
  12. package/dist/env/onboard-draft.js +33 -0
  13. package/dist/env/selected-import-writer.js +163 -0
  14. package/dist/env/settings-authority.js +203 -0
  15. package/dist/events-cloud-tail.js +134 -0
  16. package/dist/events-cloud.js +500 -0
  17. package/dist/events.js +13 -4
  18. package/dist/oauth.js +125 -33
  19. package/dist/onboard-accounts.js +259 -0
  20. package/dist/onboard-automation-management.js +15 -0
  21. package/dist/onboard-bootstrap.js +162 -0
  22. package/dist/onboard-capabilities.js +15 -7
  23. package/dist/onboard-capacity.js +226 -0
  24. package/dist/onboard-existing.js +106 -32
  25. package/dist/onboard-file-snapshot.js +45 -0
  26. package/dist/onboard-login-candidate.js +261 -0
  27. package/dist/onboard-projects.js +350 -0
  28. package/dist/onboard-repositories.js +147 -37
  29. package/dist/onboard-runtime.js +104 -9
  30. package/dist/onboard-settings-context.js +141 -0
  31. package/dist/onboard-settings-import.js +127 -0
  32. package/dist/onboard-settings.js +234 -0
  33. package/dist/onboard-ui.js +99 -11
  34. package/dist/onboard-workflow.js +316 -0
  35. package/dist/onboard.js +359 -27
  36. package/dist/owned-fetch.js +182 -0
  37. package/dist/query.js +77 -27
  38. package/dist/ready.js +143 -35
  39. package/dist/replica.js +11 -4
  40. package/dist/sdk.js +33 -0
  41. package/dist/watch.js +2 -2
  42. package/package.json +3 -2
  43. package/skills/catalyst-github/SKILL.md +2 -2
  44. package/skills/catalyst-github/references/is-it-mergeable.md +1 -1
  45. package/skills/catalyst-github/references/what-a-pr-accumulates.md +1 -1
  46. package/skills/catalyst-github/scripts/is-it-mergeable.mjs +210 -45
  47. package/skills/catalyst-github/scripts/read-pr.mjs +51 -15
  48. package/skills/catalyst-linear/SKILL.md +3 -3
  49. package/skills/catalyst-linear/references/reading-a-ticket.md +3 -3
  50. package/skills/catalyst-linear/scripts/read-ticket.mjs +37 -12
  51. package/skills/catalyst-onboard/SKILL.md +5 -4
  52. package/skills/catalyst-onboard/references/local-sync.md +3 -1
  53. package/skills/run-this-project/SKILL.md +1 -1
  54. package/skills/unstick/SKILL.md +1 -1
  55. package/skills/what-needs-me/SKILL.md +1 -1
  56. package/skills/what-this-repo-needs/SKILL.md +1 -1
  57. package/skills/whats-happening/SKILL.md +2 -2
  58. package/skills/whats-happening/references/status-reply.md +1 -1
  59. package/skills/whats-happening/scripts/snapshot.mjs +104 -26
  60. package/vendor/settings/index.d.ts +4 -0
  61. package/vendor/settings/index.js +2937 -0
  62. package/vendor/settings/provenance.json +148 -0
package/dist/ready.js CHANGED
@@ -1,19 +1,19 @@
1
1
  // ready.ts — `ready`: the machine checks plus the tenant's own readiness vector from the contract,
2
2
  // one verdict, and per failure the fix and who can apply it. The replica is optional, so its absence
3
- // is a note, never a failure; the SDK loading IS a check, because the replica and watch need it.
4
- import { accessSync, constants, existsSync, readFileSync, realpathSync, statSync } from "node:fs";
3
+ // is a note, never a failure; the HTTP SDK loading is a check for cloud reads.
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 { 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";
10
- import { replicaStatus, writerIsRunning } from "./replica.js";
11
- import { loadSdk } from "./sdk.js";
12
- import { FIX_COMMAND, detectRuntime, runtimeVerdict, supportedRangeText } from "./runtime.js";
10
+ import { replicaStatus, writerIsRunning, } from "./replica.js";
11
+ import { loadHttpSdk } from "./sdk.js";
12
+ import { FIX_COMMAND, detectRuntime, runtimeVerdict, supportedRangeText, } from "./runtime.js";
13
13
  import { semverOlder } from "./semver.js";
14
14
  import { FIRST_STAMPED_VERSION } from "./skill-shape.js";
15
15
  import { installedBundleVersion } from "./skills.js";
16
- import { onboardingReadyReport, observeCloudOnboarding } from "./onboard-ready.js";
16
+ import { onboardingReadyReport, observeCloudOnboarding, } from "./onboard-ready.js";
17
17
  import { selectedOnboardTeam } from "./onboard-existing.js";
18
18
  import { onboardStatePath, readOnboardJournal } from "./onboard.js";
19
19
  export { semverOlder };
@@ -98,7 +98,11 @@ export function catalystCommandCheck(env, platform = process.platform) {
98
98
  if (found === null)
99
99
  return null;
100
100
  if (isThisCliLauncher(found))
101
- return { id: "command", ok: true, line: `command: catalyst on PATH is this CLI (${found})` };
101
+ return {
102
+ id: "command",
103
+ ok: true,
104
+ line: `command: catalyst on PATH is this CLI (${found})`,
105
+ };
102
106
  return {
103
107
  id: "command",
104
108
  ok: false,
@@ -108,7 +112,9 @@ export function catalystCommandCheck(env, platform = process.platform) {
108
112
  };
109
113
  }
110
114
  function nameList(v) {
111
- return Array.isArray(v) ? v.filter((n) => typeof n === "string" && n.length > 0) : [];
115
+ return Array.isArray(v)
116
+ ? v.filter((n) => typeof n === "string" && n.length > 0)
117
+ : [];
112
118
  }
113
119
  /** Entries whose name and references are both present; anything malformed is dropped, not guessed. */
114
120
  function unresolvedList(v) {
@@ -119,7 +125,9 @@ function unresolvedList(v) {
119
125
  return [];
120
126
  const { name, references } = u;
121
127
  const refs = nameList(references);
122
- return typeof name === "string" && name.length > 0 && refs.length > 0 ? [{ name, references: refs }] : [];
128
+ return typeof name === "string" && name.length > 0 && refs.length > 0
129
+ ? [{ name, references: refs }]
130
+ : [];
123
131
  });
124
132
  }
125
133
  function unresolvedLine(u) {
@@ -155,7 +163,10 @@ function teamCheckFix(label, c) {
155
163
  for (const u of unresolved)
156
164
  parts.push(unresolvedLine(u));
157
165
  for (const note of Array.isArray(c.repos) ? c.repos : []) {
158
- if (typeof note !== "object" || note === null || typeof note.repo !== "string" || note.repo.length === 0)
166
+ if (typeof note !== "object" ||
167
+ note === null ||
168
+ typeof note.repo !== "string" ||
169
+ note.repo.length === 0)
159
170
  continue;
160
171
  const repoUnresolved = unresolvedList(note.unresolved);
161
172
  const skip = new Set(repoUnresolved.map((u) => u.name));
@@ -171,7 +182,9 @@ function teamCheckFix(label, c) {
171
182
  }
172
183
  function whoCanAnswer(doc) {
173
184
  const roles = doc.humans.map((h) => `${h.role} ${h.linearUserId}`);
174
- return roles.length ? roles.join(", ") : "a tenant owner or admin (none resolved on the contract)";
185
+ return roles.length
186
+ ? roles.join(", ")
187
+ : "a tenant owner or admin (none resolved on the contract)";
175
188
  }
176
189
  export async function readyReport(ctx, deps) {
177
190
  const checks = [];
@@ -197,21 +210,49 @@ export async function readyReport(ctx, deps) {
197
210
  ? `config: joined ${cfg.name} as ${cfg.user.label} (${cfg.user.role})`
198
211
  : `config: joined ${cfg.name} (${cfg.slug}) as ${cfg.principal}`,
199
212
  }
200
- : { id: "config", ok: false, line: "config: not connected", fix: "npx -p @catalyst-cloud/cli catalyst login (keyless; or pass --key / set CATALYST_CLOUD_TOKEN)", who: "you (approve the login in your browser)" });
213
+ : {
214
+ id: "config",
215
+ ok: false,
216
+ line: "config: not connected",
217
+ fix: "npx -p @catalyst-cloud/cli catalyst login (keyless; or pass --key / set CATALYST_CLOUD_TOKEN)",
218
+ who: "you (approve the login in your browser)",
219
+ });
201
220
  }
202
221
  catch (err) {
203
- checks.push({ id: "config", ok: false, line: `config: ${err instanceof CliError ? err.message : String(err)}`, fix: "re-run login to rewrite it", who: "you" });
222
+ checks.push({
223
+ id: "config",
224
+ ok: false,
225
+ line: `config: ${err instanceof CliError ? err.message : String(err)}`,
226
+ fix: "re-run login to rewrite it",
227
+ who: "you",
228
+ });
204
229
  }
205
230
  const cache = readContractCache(ctx.home);
206
231
  const range = readManifest().tenantContractRange;
207
232
  if (!cache) {
208
- checks.push({ id: "contract", ok: false, line: "contract: not cached", fix: "catalyst contract --refresh", who: "you" });
233
+ checks.push({
234
+ id: "contract",
235
+ ok: false,
236
+ line: "contract: not cached",
237
+ fix: "catalyst contract --refresh",
238
+ who: "you",
239
+ });
209
240
  }
210
241
  else if (contractVersionInRange(cache.contractVersion, range) !== true) {
211
- checks.push({ id: "contract", ok: false, line: `contract: version ${cache.contractVersion} is outside this bundle's range ${range}`, fix: upgradeCommand(), who: "you" });
242
+ checks.push({
243
+ id: "contract",
244
+ ok: false,
245
+ line: `contract: version ${cache.contractVersion} is outside this bundle's range ${range}`,
246
+ fix: upgradeCommand(),
247
+ who: "you",
248
+ });
212
249
  }
213
250
  else {
214
- checks.push({ id: "contract", ok: true, line: `contract: ${cache.contractVersion} cached ${cache.fetchedAt} (range ${range})` });
251
+ checks.push({
252
+ id: "contract",
253
+ ok: true,
254
+ line: `contract: ${cache.contractVersion} cached ${cache.fetchedAt} (range ${range})`,
255
+ });
215
256
  }
216
257
  // The cloud MAY publish the bundle it expects (catalyst-cloud#3746). Read it defensively: warn (never
217
258
  // refuse) when this installed bundle is older than the minimum, and emit nothing when the field is
@@ -265,7 +306,8 @@ export async function readyReport(ctx, deps) {
265
306
  // names are always printed rather than hidden behind whichever branch happened to win.
266
307
  const found = installedBundleVersion(skillsDir, deps.skillNames);
267
308
  const stampBehind = found.version !== null && semverOlder(found.version, pub.latest);
268
- const unstampedBehind = found.unstamped.length > 0 && !semverOlder(pub.latest, FIRST_STAMPED_VERSION);
309
+ const unstampedBehind = found.unstamped.length > 0 &&
310
+ !semverOlder(pub.latest, FIRST_STAMPED_VERSION);
269
311
  if (stampBehind || unstampedBehind) {
270
312
  const facts = [];
271
313
  if (stampBehind)
@@ -286,7 +328,13 @@ export async function readyReport(ctx, deps) {
286
328
  if (cfg) {
287
329
  checks.push(cfg.cliPath && existsSync(cfg.cliPath)
288
330
  ? { id: "cliPath", ok: true, line: `cliPath: ${cfg.cliPath}` }
289
- : { id: "cliPath", ok: false, line: `cliPath: ${cfg.cliPath ? `${cfg.cliPath} does not exist` : "not recorded"}`, fix: "re-run login so the skill scripts can find this CLI", who: "you" });
331
+ : {
332
+ id: "cliPath",
333
+ ok: false,
334
+ line: `cliPath: ${cfg.cliPath ? `${cfg.cliPath} does not exist` : "not recorded"}`,
335
+ fix: "re-run login so the skill scripts can find this CLI",
336
+ who: "you",
337
+ });
290
338
  }
291
339
  const command = catalystCommandCheck(ctx.env);
292
340
  if (command)
@@ -297,17 +345,36 @@ export async function readyReport(ctx, deps) {
297
345
  const present = deps.skillNames.filter((n) => existsSync(join(skillsDir, n, "SKILL.md")));
298
346
  const missing = deps.skillNames.filter((n) => !present.includes(n));
299
347
  if (present.length === 0) {
300
- checks.push({ id: "skills", ok: true, note: true, line: `skills: none copied to ${skillsDir} — you are reading one, so your agent installed them its own way` });
348
+ checks.push({
349
+ id: "skills",
350
+ ok: true,
351
+ note: true,
352
+ line: `skills: none copied to ${skillsDir} — you are reading one, so your agent installed them its own way`,
353
+ });
301
354
  }
302
355
  else if (missing.length === 0) {
303
- checks.push({ id: "skills", ok: true, line: `skills: all ${deps.skillNames.length} present in ${skillsDir}` });
356
+ checks.push({
357
+ id: "skills",
358
+ ok: true,
359
+ line: `skills: all ${deps.skillNames.length} present in ${skillsDir}`,
360
+ });
304
361
  }
305
362
  else {
306
- checks.push({ id: "skills", ok: false, line: `skills: ${present.length} of ${deps.skillNames.length} in ${skillsDir}, missing ${missing.join(", ")}`, fix: "catalyst install", who: "you" });
363
+ checks.push({
364
+ id: "skills",
365
+ ok: false,
366
+ line: `skills: ${present.length} of ${deps.skillNames.length} in ${skillsDir}, missing ${missing.join(", ")}`,
367
+ fix: "catalyst install",
368
+ who: "you",
369
+ });
307
370
  }
308
371
  try {
309
- await (deps.loadSdk ?? loadSdk)();
310
- checks.push({ id: "sdk", ok: true, line: "sdk: loads (replica and watch are available)" });
372
+ await (deps.loadSdk ?? loadHttpSdk)();
373
+ checks.push({
374
+ id: "sdk",
375
+ ok: true,
376
+ line: "sdk: loads (cloud HTTP reads are available)",
377
+ });
311
378
  }
312
379
  catch (err) {
313
380
  checks.push({
@@ -319,7 +386,12 @@ export async function readyReport(ctx, deps) {
319
386
  });
320
387
  }
321
388
  const replica = replicaStatus(ctx, cfg);
322
- checks.push({ id: "replica", ok: replica.verdict === "fresh", note: true, line: readyReplicaLine(replica) });
389
+ checks.push({
390
+ id: "replica",
391
+ ok: replica.verdict === "fresh",
392
+ note: true,
393
+ line: readyReplicaLine(replica),
394
+ });
323
395
  if (cache) {
324
396
  const doc = cache.doc;
325
397
  const who = whoCanAnswer(doc);
@@ -334,22 +406,36 @@ export async function readyReport(ctx, deps) {
334
406
  if (dg && typeof dg.status === "string") {
335
407
  const slots = (dg.missingSlots ?? []).join(", ");
336
408
  checks.push(dg.status === "open"
337
- ? { id: `team:${label}:dispatchGate`, ok: true, line: `team ${label}: dispatch gate open` }
409
+ ? {
410
+ id: `team:${label}:dispatchGate`,
411
+ ok: true,
412
+ line: `team ${label}: dispatch gate open`,
413
+ }
338
414
  : {
339
415
  id: `team:${label}:dispatchGate`,
340
416
  ok: false,
341
417
  line: `team ${label}: dispatch gate ${dg.status}${slots ? ` (${slots})` : ""}, blocking`,
342
- fix: dg.remedy ?? `open settings for team ${label} and map its stages`,
418
+ fix: dg.remedy ??
419
+ `open settings for team ${label} and map its stages`,
343
420
  who,
344
421
  });
345
422
  }
346
423
  if (team.readiness.status === "unchecked") {
347
- checks.push({ id: `team:${label}`, ok: true, note: true, line: `team ${label}: readiness not checked yet` });
424
+ checks.push({
425
+ id: `team:${label}`,
426
+ ok: true,
427
+ note: true,
428
+ line: `team ${label}: readiness not checked yet`,
429
+ });
348
430
  continue;
349
431
  }
350
432
  const bad = team.readiness.checks.filter((c) => c.state !== "pass");
351
433
  if (bad.length === 0 && team.readiness.status === "ready") {
352
- checks.push({ id: `team:${label}`, ok: true, line: `team ${label}: ready` });
434
+ checks.push({
435
+ id: `team:${label}`,
436
+ ok: true,
437
+ line: `team ${label}: ready`,
438
+ });
353
439
  continue;
354
440
  }
355
441
  for (const c of bad) {
@@ -365,7 +451,14 @@ export async function readyReport(ctx, deps) {
365
451
  });
366
452
  }
367
453
  if (bad.length === 0 && team.readiness.status !== "ready") {
368
- checks.push({ id: `team:${label}`, ok: team.readiness.status !== "blocked", note: team.readiness.status === "degraded", line: `team ${label}: ${team.readiness.status}`, fix: `open settings for team ${label}`, who });
454
+ checks.push({
455
+ id: `team:${label}`,
456
+ ok: team.readiness.status !== "blocked",
457
+ note: team.readiness.status === "degraded",
458
+ line: `team ${label}: ${team.readiness.status}`,
459
+ fix: `open settings for team ${label}`,
460
+ who,
461
+ });
369
462
  }
370
463
  }
371
464
  }
@@ -378,10 +471,19 @@ export async function cmdReady(args, ctx, deps) {
378
471
  if (!onboarding) {
379
472
  const cfg = loadConfig(ctx.home);
380
473
  const journal = readOnboardJournal(onboardStatePath(ctx.home, ctx.env), readManifest().version, ctx.now());
381
- const bound = cfg?.user && journal && (journal.account ?? journal.tenant) === cfg.account &&
382
- journal.membershipId === cfg.user.id && typeof journal.baseUrl === "string" && normalizeBaseUrl(journal.baseUrl) === normalizeBaseUrl(cfg.baseUrl);
474
+ const bound = cfg?.user &&
475
+ journal &&
476
+ (journal.account ?? journal.tenant) === cfg.account &&
477
+ journal.membershipId === cfg.user.id &&
478
+ typeof journal.baseUrl === "string" &&
479
+ normalizeBaseUrl(journal.baseUrl) === normalizeBaseUrl(cfg.baseUrl);
383
480
  const team = bound ? selectedOnboardTeam(journal) : undefined;
384
- onboarding = { teamIds: team ? [team] : undefined, localSync: args.flags["local-sync"] === true || Boolean(bound && journal.localSync), observe: observeCloudOnboarding };
481
+ onboarding = {
482
+ teamIds: team ? [team] : undefined,
483
+ localSync: args.flags["local-sync"] === true ||
484
+ Boolean(bound && journal.localSync),
485
+ observe: observeCloudOnboarding,
486
+ };
385
487
  }
386
488
  const report = await onboardingReadyReport(ctx, onboarding);
387
489
  if (args.json)
@@ -393,9 +495,15 @@ export async function cmdReady(args, ctx, deps) {
393
495
  ctx.stdout(`Work observed${report.work.ticket ? ` on ${report.work.ticket}` : ""}.`);
394
496
  else
395
497
  ctx.stdout("Fresh project work has not been verified by this check.");
396
- ctx.stdout(report.state === "complete" ? "Onboarding complete." : "Setup still needs checks. Resume: catalyst onboard");
498
+ ctx.stdout(report.state === "complete"
499
+ ? "Onboarding complete."
500
+ : "Setup still needs checks. Resume: catalyst onboard");
397
501
  }
398
- return report.state === "complete" ? 0 : report.checks.some(check => check.required && check.state === "fail") ? 10 : 11;
502
+ return report.state === "complete"
503
+ ? 0
504
+ : report.checks.some((check) => check.required && check.state === "fail")
505
+ ? 10
506
+ : 11;
399
507
  }
400
508
  const report = await readyReport(ctx, deps);
401
509
  if (args.json) {
package/dist/replica.js CHANGED
@@ -6,7 +6,7 @@
6
6
  import { existsSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
7
7
  import { createRequire } from "node:module";
8
8
  import { flagBool, flagInt, flagString, positionals } from "./args.js";
9
- import { apiBase, loadConfig, replicaDbPath } from "./config.js";
9
+ import { apiBase, loadConfig, replicaDbPath, replicaOptedIn } from "./config.js";
10
10
  import { detachSelf } from "./detach.js";
11
11
  import { CliError, UsageError } from "./errors.js";
12
12
  import { apiClient } from "./transport.js";
@@ -133,11 +133,14 @@ export function readCursor(dbPath) {
133
133
  db?.close();
134
134
  }
135
135
  }
136
+ const NOT_CONNECTED = "not connected";
137
+ const CLOUD_READS_LINE = "replica: not configured (cloud reads; local sync is opt-in)";
136
138
  /** The in-process status logic `query` and `ready` reuse. */
137
139
  export function replicaStatus(ctx, cfg, opts = {}) {
138
140
  const base = {
139
141
  verdict: "not-configured",
140
142
  exitCode: 2,
143
+ configured: false,
141
144
  dbPath: null,
142
145
  cursor: null,
143
146
  heartbeatAgeMs: null,
@@ -148,7 +151,7 @@ export function replicaStatus(ctx, cfg, opts = {}) {
148
151
  writer: null,
149
152
  };
150
153
  if (!cfg)
151
- return { ...base, reasons: ["not connected"] };
154
+ return { ...base, reasons: [NOT_CONNECTED] };
152
155
  let dbPath;
153
156
  try {
154
157
  dbPath = opts.dbPath ?? replicaDbPath(cfg, ctx.home, ctx.env);
@@ -158,9 +161,10 @@ export function replicaStatus(ctx, cfg, opts = {}) {
158
161
  return { ...base, reasons: [error.message] };
159
162
  throw error;
160
163
  }
164
+ const configured = replicaOptedIn(ctx.home, ctx.env);
161
165
  const writer = readWriterState(dbPath);
162
166
  if (!existsSync(dbPath))
163
- return { ...base, verdict: "absent", exitCode: 3, dbPath, reasons: ["no replica file"], writer };
167
+ return { ...base, verdict: "absent", exitCode: 3, configured, dbPath, reasons: ["no replica file"], writer };
164
168
  const staleMs = opts.staleMs ?? DEFAULT_STALE_MS;
165
169
  const nowMs = opts.nowMs ?? ctx.now().getTime();
166
170
  const lock = readLock(dbPath);
@@ -181,6 +185,7 @@ export function replicaStatus(ctx, cfg, opts = {}) {
181
185
  return {
182
186
  verdict: fresh ? "fresh" : "stale",
183
187
  exitCode: fresh ? 0 : 1,
188
+ configured,
184
189
  dbPath,
185
190
  cursor,
186
191
  heartbeatAgeMs,
@@ -194,8 +199,10 @@ export function replicaStatus(ctx, cfg, opts = {}) {
194
199
  function baseStatusLine(s) {
195
200
  switch (s.verdict) {
196
201
  case "not-configured":
197
- return `replica: not configured (${s.reasons.join("; ")})`;
202
+ return s.reasons[0] === NOT_CONNECTED ? `replica: not configured (${s.reasons.join("; ")})` : CLOUD_READS_LINE;
198
203
  case "absent":
204
+ if (!s.configured)
205
+ return CLOUD_READS_LINE;
199
206
  return `replica: absent at ${s.dbPath} — start it with: catalyst replica start --detach`;
200
207
  case "fresh":
201
208
  return `replica: fresh at ${s.dbPath} (cursor ${s.cursor}, heartbeat ${s.heartbeatAgeMs}ms ago${s.lag !== undefined ? `, ${s.lag} behind head ${s.head}` : ""})`;
package/dist/sdk.js CHANGED
@@ -4,8 +4,13 @@ import { readManifest } from "./config.js";
4
4
  import { FIX_COMMAND, supportedRangeText } from "./runtime.js";
5
5
  let cached = null;
6
6
  let cachedHttp = null;
7
+ let cachedLive = null;
8
+ let cachedEvents = null;
7
9
  const realImport = () => import("@catalyst-cloud/sdk/node");
8
10
  const realHttpImport = () => import("@catalyst-cloud/sdk");
11
+ // SDK 0.13 has no `./live` entry; its root entry carries LiveSyncClient and no replica code. Point
12
+ // this at `@catalyst-cloud/sdk/live` once the pin reaches 0.14.
13
+ const realLiveImport = () => import("@catalyst-cloud/sdk");
9
14
  /** The isomorphic typed HTTP client; keeps the SDK import in this module. */
10
15
  export function loadHttpSdk(importer = realHttpImport) {
11
16
  if (!cachedHttp) {
@@ -16,6 +21,16 @@ export function loadHttpSdk(importer = realHttpImport) {
16
21
  }
17
22
  return cachedHttp;
18
23
  }
24
+ /** The live push client for `watch`, loaded without the replica-bearing node entry (CTC-4508). */
25
+ export function loadLiveSdk(importer = realLiveImport) {
26
+ if (!cachedLive) {
27
+ cachedLive = importer().catch((err) => {
28
+ cachedLive = null;
29
+ throw new CliError(`the Catalyst Cloud SDK live client could not be loaded: ${err instanceof Error ? err.message : String(err)}`, "sdk-unavailable");
30
+ });
31
+ }
32
+ return cachedLive;
33
+ }
19
34
  /** Import the SDK's node entry, installing the type-stripping loader first. Cached per process. */
20
35
  export function loadSdk(importer = realImport) {
21
36
  if (!cached) {
@@ -35,10 +50,28 @@ export function loadSdk(importer = realImport) {
35
50
  }
36
51
  return cached;
37
52
  }
53
+ /** Events use the same supported dependency loader; capability markers are checked by the caller. */
54
+ export function loadEventsSdk(importer = () => import("@catalyst-cloud/sdk/events")) {
55
+ if (!cachedEvents) {
56
+ cachedEvents = (async () => {
57
+ installTsDepsLoader();
58
+ try {
59
+ return await importer();
60
+ }
61
+ catch {
62
+ cachedEvents = null;
63
+ throw new CliError("the Catalyst Cloud event SDK could not be loaded; reinstall the skills bundle", "sdk-unavailable");
64
+ }
65
+ })();
66
+ }
67
+ return cachedEvents;
68
+ }
38
69
  /** Test seam: forget the cached import. */
39
70
  export function resetSdkCache() {
40
71
  cached = null;
41
72
  cachedHttp = null;
73
+ cachedLive = null;
74
+ cachedEvents = null;
42
75
  }
43
76
  /** HTTP tenant methods live on the SDK's root entry, separate from replica/node exports. */
44
77
  export async function loadTenantSdk() {
package/dist/watch.js CHANGED
@@ -9,7 +9,7 @@ import { loadContract } from "./contract.js";
9
9
  import { CliError, UsageError } from "./errors.js";
10
10
  import { apiClient } from "./transport.js";
11
11
  import { authStrategyFor, bearerFor } from "./oauth.js";
12
- import { loadSdk } from "./sdk.js";
12
+ import { loadLiveSdk } from "./sdk.js";
13
13
  import { createLiveEventsClient } from "./watch/consumer.js";
14
14
  import { CursorFileError } from "./watch/cursor-file.js";
15
15
  const ROW_ISSUE_REFS = ["identifier", "linear_issue_identifier", "issue_identifier", "issue_id", "linear_issue_id"];
@@ -109,7 +109,7 @@ export function execReaction(cmd, env) {
109
109
  });
110
110
  }
111
111
  export async function runWatch(ctx, cfg, opts, deps = {}) {
112
- const sdk = deps.sdk ?? (await loadSdk());
112
+ const sdk = deps.sdk ?? (await loadLiveSdk());
113
113
  const resolve = deps.resolver ?? apiIssueResolver(cfg, ctx);
114
114
  const react = opts.exec ? execReaction(opts.exec, ctx.env) : undefined;
115
115
  let handle;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catalyst-cloud/cli",
3
- "version": "0.14.6",
3
+ "version": "0.14.7",
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",
@@ -46,7 +46,8 @@
46
46
  "skills:scan": "node scripts/scan-skills.mjs",
47
47
  "evals": "node scripts/run-evals.mjs",
48
48
  "paths:check": "node scripts/vendor-paths.mjs --check",
49
- "prose:check": "node scripts/check-public-prose.mjs"
49
+ "prose:check": "node scripts/check-public-prose.mjs",
50
+ "settings:check": "node scripts/vendor-settings.mjs --check"
50
51
  },
51
52
  "dependencies": {
52
53
  "@catalyst-cloud/sdk": "^0.13.1",
@@ -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.6 — written in this repository for customer accounts -->
7
+ <!-- vendored-from: @catalyst-cloud/catalyst-skills@0.14.7 — written in this repository for customer accounts -->
8
8
 
9
9
  # Catalyst's GitHub
10
10
 
@@ -15,7 +15,7 @@ You answer for the pull requests Catalyst opens and moves. The person asking wan
15
15
  - `node scripts/read-pr.mjs --help`: a ticket's PR (or a PR by node id): state, branch, head, linked ticket, GitHub's mergeable state, every check, status and review. `--all` lists every PR for a ticket.
16
16
  - `node scripts/is-it-mergeable.mjs --help`: the three legs (checks, reviewer signal, unresolved threads) judged under the repository's policy, plus the prerequisites; exit 1 when a leg is red.
17
17
 
18
- Both read through `catalyst query`, `contract` and, when fresh, `replica`; the first stderr line names the source, and your answer repeats it.
18
+ Both read through `catalyst query` and `contract` from the cloud by default; the first stderr line names the source, and your answer repeats it.
19
19
 
20
20
  ## Load on demand
21
21
 
@@ -42,7 +42,7 @@ The mirrored PR detail carries checks, legacy commit statuses, review objects wi
42
42
 
43
43
  - checks: judged fully from the detail.
44
44
  - reviewer: reported as inconclusive unless the policy waives it. The script says whether the reviewer left review objects, but whether a clean-pass reaction followed, and whether those reviews stand at the current head or an earlier one, is not mirrored.
45
- - threads: read from the local replica's review-thread rows when the replica is fresh (resolved flag per thread); inconclusive otherwise, with the command that starts the replica. Thread ancestry against force-pushes is judged by the cloud, not here.
45
+ - threads: inconclusive when cloud PR detail carries no thread count. The optional `--local-threads` reads local review-thread rows only after the replica proves it is caught up to a probed cloud head. Thread ancestry against force-pushes is judged by the cloud, not here.
46
46
 
47
47
  The exit code is 1 only for a proven-red leg or prerequisite. An inconclusive leg leaves exit 0 and a verdict that says "not proven mergeable from this read". The cloud's own evaluator, which holds the reaction and the ancestry, is the authority; a queue-ready label on the PR means it already said yes.
48
48
 
@@ -58,4 +58,4 @@ The ticket moves to Done when the real merge webhook arrives, keyed to the pull
58
58
 
59
59
  ## Reading a PR through this skill
60
60
 
61
- `node scripts/read-pr.mjs <ticket>` follows the ticket's own record to the pull requests that name it, picks the open one (else the merged one, else the newest), and prints the mirrored detail: state, draft and merged flags, branch and base, head commit, the linked ticket and its stage, GitHub's own mergeable state and auto-merge flag, every check with status and conclusion, legacy commit statuses, and every mirrored review with its state. `--all` lists every PR for the ticket; `--json` prints the raw document. The first stderr line names the source, a fresh replica or the API, and the answer should repeat it.
61
+ `node scripts/read-pr.mjs <ticket>` follows the ticket's own record to the pull requests that name it, picks the open one (else the merged one, else the newest), and prints the mirrored detail: state, draft and merged flags, branch and base, head commit, the linked ticket and its stage, GitHub's own mergeable state and auto-merge flag, every check with status and conclusion, legacy commit statuses, and every mirrored review with its state. `--all` lists every PR for the ticket; `--json` prints the raw document. Reads use the cloud by default. Repeat the source from the first stderr line.