@catalyst-cloud/catalyst-skills 0.3.0 → 0.3.1

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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1
4
+
5
+ `ready` now names the person you connected as: when your key is a personal key, the config line reads "joined <tenant> as <you> (<role>)" instead of the generic "as service" that every api key showed, and an account (host) key still names the tenant with no person. `ready` also learns the bundle version your tenant expects, when the cloud publishes one: if this installed bundle is older than that minimum you get a one-line warning that names both versions and the upgrade command, but it stays a note — `ready` never refuses over a stale bundle, and a cloud that does not publish a minimum changes nothing. `explain` no longer calls every ticket with no dispatch row "unknown": a ticket that merely sits in Backlog (or any non-dispatch state) is now named as known to the mirror with the state it is in, and only a ticket the mirror has never seen keeps the unknown wording.
6
+
3
7
  ## 0.3.0
4
8
 
5
9
  You connect with your own personal key now, not the tenant's shared account key: mint it yourself at Settings → API keys in the Catalyst Cloud app (every member can; no admin needed), and `login` prints who you connected as beside which tenant. Your agent acts as you — an ask it raises names you, and "what needs me" means you: `ask list` (and the what-needs-me skill) keeps only the asks assigned to your Linear user by default, `--anyone` lists the whole tenant, and when your Linear identity is not matched yet it says so and shows everything rather than an empty list. `customer.json` gains a `user` block (id, label, role, your Linear user id); an older config is still read unchanged. Connecting with the account key still works — it is the right credential for a host or daemon — but `login` tells you it names no person. Every skill, reference and help line that said "account key", or said execution history and coding-account status were not readable, has been rewritten: `explain --history` and `accounts` read them, and the two things your key genuinely cannot do (release a park, read PR labels or reactions) are named instead. The catalyst-setup reference now lists all eleven team checks, including `environment_declared`. Needs a cloud that admits personal keys on the agent routes; on an older cloud the contract read says so by name, and the account key still works until it is updated.
package/README.md CHANGED
@@ -145,10 +145,10 @@ The skill files themselves are written by whichever install command you ran, in
145
145
 
146
146
  ## Updating
147
147
 
148
- A plugin install updates when we ship. Skills copied by `npx skills add` do not; run `npx skills update -y` to refresh them. Update the CLI with `npm update -g @catalyst-cloud/catalyst-skills`, or run any command against the latest publish with `npx @catalyst-cloud/catalyst-skills@latest login`. The next `catalyst-skills` run prints a one-line notice:
148
+ A plugin install updates when we ship. Skills copied by `npx skills add` do not; run `npx skills update -y` to refresh them. Update the CLI with `npm install -g @catalyst-cloud/catalyst-skills@latest && catalyst-skills login` — the re-login rewrites the CLI path the skills spawn, so they stop running the old bundle. To run one command against the latest publish without installing, use `npx @catalyst-cloud/catalyst-skills@latest login`. The next `catalyst-skills` run prints a one-line notice:
149
149
 
150
150
  ```
151
- [catalyst-skills] updated 0.1.1 → 0.2.0: <that version's CHANGELOG.md summary> · update with: npm update -g @catalyst-cloud/catalyst-skills (or: npx @catalyst-cloud/catalyst-skills@latest login)
151
+ [catalyst-skills] updated 0.1.1 → 0.2.0: <that version's CHANGELOG.md summary> · update with: npm install -g @catalyst-cloud/catalyst-skills@latest && catalyst-skills login
152
152
  ```
153
153
 
154
154
  A `customer.json` written by an older bundle is still read unchanged; it gains the CLI path and the cached contract the next time you run `catalyst-skills login`.
package/dist/contract.js CHANGED
@@ -45,7 +45,7 @@ export function assertContractRange(version, range) {
45
45
  if (ok === null)
46
46
  throw new CliError(`tenantContractRange "${range}" in package.json is not a range this CLI understands`, "contract-range");
47
47
  if (!ok) {
48
- throw new CliError(`the tenant serves contract version ${version} but this bundle accepts ${range} — update the bundle (npm update -g @catalyst-cloud/catalyst-skills) or ask your tenant admin which version is live`, "contract-version");
48
+ throw new CliError(`the tenant serves contract version ${version} but this bundle accepts ${range} — update the bundle (npm install -g @catalyst-cloud/catalyst-skills@latest && catalyst-skills login) or ask your tenant admin which version is live`, "contract-version");
49
49
  }
50
50
  }
51
51
  /**
package/dist/execution.js CHANGED
@@ -61,9 +61,15 @@ export function describeReason(reason) {
61
61
  return "no reason was given";
62
62
  return EXCLUSION_REASONS[reason] ?? UNKNOWN_REASONS[reason] ?? `reason "${reason}" (not in this bundle's table; read it as the cloud spelled it)`;
63
63
  }
64
- export function renderExplain(ticket, row, team) {
65
- if (!row)
64
+ export function renderExplain(ticket, row, team, knownState) {
65
+ if (!row) {
66
+ // A null dispatch row is not proof the ticket does not exist — the explainer is a dispatch scan. When
67
+ // `knownState` is set (the mirror answered GET /issues/:id) the ticket is real; it just sits outside a
68
+ // dispatch column. Only a 404 (knownState null/absent) is truly unknown to the mirror.
69
+ if (knownState)
70
+ return `${ticket}: known to the mirror; state ${knownState} is not a dispatch state.`;
66
71
  return `${ticket}: not in the ${team} eligibility explainer — the ticket is unknown to the mirror, terminal, or on another team.`;
72
+ }
67
73
  const parts = [];
68
74
  const pos = typeof row.position === "number" ? `position ${row.position}` : "no queue position";
69
75
  const status = row.status ?? "unknown";
@@ -115,11 +121,20 @@ export async function cmdExplain(args, ctx) {
115
121
  });
116
122
  const rows = res.body.eligibility?.rows ?? res.body.rows ?? [];
117
123
  const row = rows.find((r) => String(r.ticket ?? "").toUpperCase() === ticket.toUpperCase()) ?? null;
124
+ // No dispatch row is not proof of non-existence: probe the mirror so a Backlog ticket reads as known.
125
+ // The mirror compares identifiers exactly, so normalize (uppercase) as the row lookup above does —
126
+ // otherwise `explain eng-7` probes a lowercase id, 404s, and reports an existing ENG-7 as unknown.
127
+ let knownState = null;
128
+ if (!row) {
129
+ const probe = await api.getJson(`/api/v1/issues/${encodeURIComponent(ticket.toUpperCase())}`, { accept: [404] });
130
+ if (probe.status !== 404)
131
+ knownState = typeof probe.body?.state === "string" ? probe.body.state : "unknown";
132
+ }
118
133
  if (args.json) {
119
- ctx.stdout(JSON.stringify({ ticket, row, explanation: renderExplain(ticket, row, team) }));
134
+ ctx.stdout(JSON.stringify({ ticket, row, explanation: renderExplain(ticket, row, team, knownState) }));
120
135
  }
121
136
  else {
122
- ctx.stdout(renderExplain(ticket, row, team));
137
+ ctx.stdout(renderExplain(ticket, row, team, knownState));
123
138
  }
124
139
  return 0;
125
140
  }
package/dist/ready.js CHANGED
@@ -8,6 +8,22 @@ import { contractVersionInRange, readContractCache } from "./contract.js";
8
8
  import { CliError } from "./errors.js";
9
9
  import { replicaStatus, statusLine } from "./replica.js";
10
10
  import { loadSdk } from "./sdk.js";
11
+ /** True when `a` is a semver-older release than `b`. Prerelease/build metadata is ignored; a segment
12
+ * that does not parse counts as 0. A tiny compare on purpose — no dependency for three fields. */
13
+ export function semverOlder(a, b) {
14
+ const parse = (v) => {
15
+ const core = v.split("-")[0].split("+")[0];
16
+ const p = core.split(".");
17
+ return [Number(p[0]) || 0, Number(p[1]) || 0, Number(p[2]) || 0];
18
+ };
19
+ const [a0, a1, a2] = parse(a);
20
+ const [b0, b1, b2] = parse(b);
21
+ if (a0 !== b0)
22
+ return a0 < b0;
23
+ if (a1 !== b1)
24
+ return a1 < b1;
25
+ return a2 < b2;
26
+ }
11
27
  function whoCanAnswer(doc) {
12
28
  const roles = doc.humans.map((h) => `${h.role} ${h.linearUserId}`);
13
29
  return roles.length ? roles.join(", ") : "a tenant owner or admin (none resolved on the contract)";
@@ -22,7 +38,16 @@ export async function readyReport(ctx, deps) {
22
38
  try {
23
39
  cfg = loadConfig(ctx.home);
24
40
  checks.push(cfg
25
- ? { id: "config", ok: true, line: `config: joined ${cfg.name} (${cfg.slug}) as ${cfg.principal}` }
41
+ ? {
42
+ id: "config",
43
+ ok: true,
44
+ // `principal` is "service" for every api key (it means "a key, not a browser session"); it
45
+ // never names a person. Since CTC-2076 a personal key's /me carries a `user` block, so name
46
+ // the connected person when there is one, and fall back to the account for a host key.
47
+ line: cfg.user
48
+ ? `config: joined ${cfg.name} as ${cfg.user.label} (${cfg.user.role})`
49
+ : `config: joined ${cfg.name} (${cfg.slug}) as ${cfg.principal}`,
50
+ }
26
51
  : { id: "config", ok: false, line: "config: not connected", fix: "CATALYST_CLOUD_TOKEN=<your personal key> npx @catalyst-cloud/catalyst-skills login", who: "you (mint the key at Settings → API keys)" });
27
52
  }
28
53
  catch (err) {
@@ -34,11 +59,26 @@ export async function readyReport(ctx, deps) {
34
59
  checks.push({ id: "contract", ok: false, line: "contract: not cached", fix: "catalyst-skills contract --refresh", who: "you" });
35
60
  }
36
61
  else if (contractVersionInRange(cache.contractVersion, range) !== true) {
37
- checks.push({ id: "contract", ok: false, line: `contract: version ${cache.contractVersion} is outside this bundle's range ${range}`, fix: "npm update -g @catalyst-cloud/catalyst-skills", who: "you" });
62
+ checks.push({ id: "contract", ok: false, line: `contract: version ${cache.contractVersion} is outside this bundle's range ${range}`, fix: "npm install -g @catalyst-cloud/catalyst-skills@latest && catalyst-skills login", who: "you" });
38
63
  }
39
64
  else {
40
65
  checks.push({ id: "contract", ok: true, line: `contract: ${cache.contractVersion} cached ${cache.fetchedAt} (range ${range})` });
41
66
  }
67
+ // The cloud MAY publish the bundle it expects (catalyst-cloud#3746). Read it defensively: warn (never
68
+ // refuse) when this installed bundle is older than the minimum, and emit nothing when the field is
69
+ // absent (an older cloud) — a stale bundle is a degrading note, so `ready` still returns READY.
70
+ const minVersion = cache?.doc.skillsBundle?.minVersion;
71
+ if (minVersion) {
72
+ const installed = readManifest().version;
73
+ if (semverOlder(installed, minVersion)) {
74
+ checks.push({
75
+ id: "bundle",
76
+ ok: false,
77
+ note: true,
78
+ line: `bundle: ${installed} installed is older than the tenant's minimum ${minVersion} — upgrade: npm install -g @catalyst-cloud/catalyst-skills@latest && catalyst-skills login`,
79
+ });
80
+ }
81
+ }
42
82
  if (cfg) {
43
83
  checks.push(cfg.cliPath && existsSync(cfg.cliPath)
44
84
  ? { id: "cliPath", ok: true, line: `cliPath: ${cfg.cliPath}` }
package/dist/skills.js CHANGED
@@ -70,5 +70,9 @@ export function readChangelog() {
70
70
  }
71
71
  export function updateNoticeLine(previous, current, entry) {
72
72
  const summary = entry ?? "see CHANGELOG.md";
73
- return `[catalyst-skills] updated ${previous} → ${current}: ${summary} · update with: npm update -g ${PACKAGE_NAME} (or: npx ${PACKAGE_NAME}@latest login)`;
73
+ // `npm update -g` treats a global install as a caret range, and a caret below 1.0.0 never moves a
74
+ // minor — so it never crosses 0.2.x → 0.3.0. Pin @latest via `npm install`, THEN re-run login: the
75
+ // install alone does not rewrite customer.json.cliPath, so the skill helpers would keep spawning the
76
+ // stale recorded bundle and repeat this notice forever; the new global bin records its own path.
77
+ return `[catalyst-skills] updated ${previous} → ${current}: ${summary} · update with: npm install -g ${PACKAGE_NAME}@latest && catalyst-skills login`;
74
78
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@catalyst-cloud/catalyst-skills",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Customer skill bundle for Catalyst Cloud: eight skills named from the executive's seat (whats-happening, what-needs-me, run-this-project, catalyst-setup, catalyst-linear, catalyst-github, how-catalyst-works, connect-me) plus the catalyst-skills CLI that reads through the Catalyst Cloud SDK, writes through the agent proxy, and connects your machine to your tenant with one command.",
5
5
  "license": "MIT",
6
6
  "type": "module",