@rosthq/cli 0.7.134 → 0.7.136

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/dist/index.js CHANGED
@@ -52585,7 +52585,7 @@ The implementation method is split into stable sections so a CLI, MCP, or in-app
52585
52585
  order: 12,
52586
52586
  title: "Install the {{brand}} implementation Skill",
52587
52587
  summary: "Install the public implementation Skill through its signed, verified GitHub release so a general-purpose agent can run the {{brand}} implementation method.",
52588
- version: "2026-07-30.1",
52588
+ version: "2026-07-30.2",
52589
52589
  public: true,
52590
52590
  audiences: ["human", "cli", "mcp", "in_app_agent"],
52591
52591
  stages: ["company_setup"],
@@ -52639,7 +52639,7 @@ A fetched release can never introduce its own trust root. Any key material insid
52639
52639
 
52640
52640
  Files are placed under the client's skills directory as \`rost-implementation/\`, plus a \`.rost-skill-release.json\` provenance marker. The marker records source repository, tag, commit, manifest sha256, and signer fingerprint. The agent quotes this marker when reporting which implementation method ran.
52641
52641
 
52642
- For Claude Code the directory is \`$CLAUDE_CONFIG_DIR/skills/\` when that variable is set, otherwise \`~/.claude/skills/\` \u2014 \`CLAUDE_CONFIG_DIR\` is the variable Claude Code itself reads. The legacy \`CLAUDE_HOME\` is still honoured as a fallback and prints a deprecation notice; prefer \`CLAUDE_CONFIG_DIR\`, because a Skill installed under a root the client does not read leaves the agent running without it. For Codex the directory is \`$CODEX_HOME/skills/\`, otherwise \`~/.codex/skills/\`. The installer prints the resolved directory \u2014 check it matches the configuration the client launches with.
52642
+ For Claude Code the directory is \`$CLAUDE_CONFIG_DIR/skills/\` when that variable is set, otherwise \`~/.claude/skills/\` \u2014 \`CLAUDE_CONFIG_DIR\` is the variable Claude Code itself reads. If \`CLAUDE_HOME\` is set without \`CLAUDE_CONFIG_DIR\`, the installer fails with a migration instruction (\`Set CLAUDE_CONFIG_DIR=<value> instead, or unset CLAUDE_HOME to install under the default ~/.claude\`); when both variables are set and name different directories, the installer uses \`CLAUDE_CONFIG_DIR\` and prints a notice that \`CLAUDE_HOME\` is ignored (an equivalent spelling prints no notice). For Codex the directory is \`$CODEX_HOME/skills/\`, otherwise \`~/.codex/skills/\`. The installer prints the resolved directory and a line such as \`Claude Code will load this from <path>\` \u2014 check it matches the configuration the client launches with.
52643
52643
 
52644
52644
  ## Update path
52645
52645
 
@@ -53680,7 +53680,7 @@ External connectors are being rolled out provider by provider, conservatively (r
53680
53680
  order: 48,
53681
53681
  title: "CLI and MCP installation guide",
53682
53682
  summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
53683
- version: "2026-07-30.2",
53683
+ version: "2026-07-30.3",
53684
53684
  public: true,
53685
53685
  audiences: ["human", "cli", "mcp", "in_app_agent"],
53686
53686
  stages: ["company_setup", "staffing"],
@@ -54265,7 +54265,7 @@ Ergonomic subcommands for everyday operation. Each wrapper is sugar over the sam
54265
54265
 
54266
54266
  These ergonomic wrappers (including the \`{{cli}} agent\` group) require **{{cli}} 0.2.0 or newer** \u2014 run \`npx {{cliPackage}}@latest \u2026\` to get them. Older versions can still reach every command through the \`{{cli}} command <id> --json\` floor, since command execution is server-side.
54267
54267
 
54268
- **Generated verbs (0.6.0+).** Every registry command now has a first-class \`{{cli}} <namespace> <action>\` verb, projected directly from the command's input schema \u2014 so namespaces that previously had no ergonomic wrapper (for example \`{{cli}} measurable create\`, \`{{cli}} work_order enqueue\`, \`{{cli}} mcp_token create\`) are usable without dropping to the raw \`command <id>\` path. Flags follow the schema field names (\`--seat-id\`, \`--name\`, \u2026); read a command's exact input offline with \`{{cli}} <namespace> <action> --schema\` before calling it, and pass a nested payload with \`--input '<json>'\` when a field is a complex object. \`--json\` still selects machine output, \`--seat <id>\` still sets seat scope, and \`{{cli}} command <id> --json '<body>'\` remains the universal fallback.
54268
+ **Generated verbs (0.6.0+).** Every registry command now has a first-class \`{{cli}} <namespace> <action>\` verb, projected directly from the command's input schema \u2014 so namespaces that previously had no ergonomic wrapper (for example \`{{cli}} measurable create\`, \`{{cli}} work_order enqueue\`, \`{{cli}} mcp_token create\`) are usable without dropping to the raw \`command <id>\` path. Flags follow the schema field names (\`--seat-id\`, \`--name\`, \u2026); read a command's exact input offline with \`{{cli}} <namespace> <action> --schema\` before calling it, and pass a nested payload with \`--input '<json>'\` or \`--input-file <path>\` (a JSON file read client-side) when a field is a complex object \u2014 explicit field flags merge on top of either payload form. On these generated verbs, a flag the verb does not support is a usage error naming the flag before anything is sent, never silently ignored (the bespoke handler verbs, such as \`status record\` and \`agent create\`, still parse flags by hand and are being hardened separately). \`--json\` still selects machine output, \`--seat <id>\` still sets seat scope, and \`{{cli}} command <id> --json '<body>'\` remains the universal fallback.
54269
54269
 
54270
54270
  For a complete Seat, inspect \`{{cli}} seat create-complete --schema\`, then pass the strict nested request with \`{{cli}} seat create-complete --input '<json>' --json\`. The generic \`{{cli}} command seat.create_complete --json '<json>'\` path and the generated MCP tool \`rost_create_complete_seat\` expose the same registry schema and server-side command. \`seat create-complete\` authenticates with an existing implementation-bootstrap credential first (it is one of the vetted bootstrap staging primitives) and falls back to the user session. Staging surfaces as the CLI's confirmation-required envelope: a \`COMMAND_CONFIRMATION_REQUIRED\` JSON object on stderr, exit code 3, whose \`details\` carry the pending confirmation id and the exact approval URL (\`details.approveVia.webUrl\`) \u2014 read the URL from there; the command has done its job at that point. The human owner reviews one card covering the Seat, canonical Charter and permission manifest, disclosed staffing, effective agent lane/readiness, exact pinned Skill versions and dependency state, and schedule. Approval applies the proposal in one transaction and returns its application and receipt ids. Any failure rolls back the whole proposal. An optional agent remains in \`dry_run\`; this command never approves go-live. Direct calls use existing parent and Steward Seat ids\u2014logical references are reserved for the later composite onboarding flow. Retained source ingestion is not available on this direct command yet, so \`sources\` and every \`source_uses\` collection must be empty and planned staffing must omit \`source_ref\`; the command rejects source-backed provenance instead of accepting an unverified upload id or caller-supplied digest.
54271
54271
 
@@ -59836,10 +59836,36 @@ function explicitArgvValue(entry, field4, parsed, positional, overrides, aliasFl
59836
59836
  }
59837
59837
  return { provided: true, value: coerceScalar(field4, raw) };
59838
59838
  }
59839
+ var RESERVED_FLAGS = /* @__PURE__ */ new Set(["json", "seat", "input", "body", "yes", "y", "schema"]);
59840
+ function knownFlagsFor(entry, overrides) {
59841
+ const known = new Set(RESERVED_FLAGS);
59842
+ for (const field4 of entry.fields) {
59843
+ known.add(field4.flag);
59844
+ }
59845
+ for (const aliasFlag of Object.keys(overrides.flagAliases ?? {})) {
59846
+ known.add(aliasFlag);
59847
+ }
59848
+ for (const negatedFlag of Object.keys(overrides.negatedFlags ?? {})) {
59849
+ known.add(negatedFlag);
59850
+ }
59851
+ return known;
59852
+ }
59853
+ function assertKnownFlags(entry, parsed, overrides, extraKnownFlags = /* @__PURE__ */ new Set()) {
59854
+ const knownFlags = knownFlagsFor(entry, overrides);
59855
+ for (const flag of extraKnownFlags) {
59856
+ knownFlags.add(flag);
59857
+ }
59858
+ for (const key of [...parsed.values.keys(), ...parsed.flags]) {
59859
+ if (!knownFlags.has(key)) {
59860
+ throw new UsageError(`Unknown flag: --${key}. Run this command with --schema to see its supported flags.`);
59861
+ }
59862
+ }
59863
+ }
59839
59864
  function buildBodyFromFlags(entry, parsed, positional, overrides = {}) {
59840
59865
  if (entry.secretBlocked) {
59841
59866
  throw new UsageError(SECRET_ARGV_GUIDANCE);
59842
59867
  }
59868
+ assertKnownFlags(entry, parsed, overrides);
59843
59869
  const aliasFlags = new Map(Object.entries(overrides.flagAliases ?? {}));
59844
59870
  const booleanFields = new Set(entry.fields.filter((field4) => field4.type === "boolean").map((field4) => field4.name));
59845
59871
  for (const [negatedFlag, fieldName] of Object.entries(overrides.negatedFlags ?? {})) {
@@ -60022,7 +60048,12 @@ function renderCommandOutput(output) {
60022
60048
  }
60023
60049
 
60024
60050
  // src/generator/verbs.ts
60051
+ import { readFile as readFile6 } from "node:fs/promises";
60025
60052
  var MANIFEST_BY_ID = new Map(COMMAND_MANIFEST.map((entry) => [entry.id, entry]));
60053
+ function hasBespokeHandler(verbSpec) {
60054
+ return verbSpec.handler !== void 0;
60055
+ }
60056
+ var INPUT_FILE_FLAG = /* @__PURE__ */ new Set(["input-file"]);
60026
60057
  function manifestById(commandId) {
60027
60058
  return MANIFEST_BY_ID.get(commandId);
60028
60059
  }
@@ -60047,6 +60078,7 @@ function renderVerbSchema(entry) {
60047
60078
  }
60048
60079
  if (entry.hasComplexInput) {
60049
60080
  lines.push(" --input '<json>' \u2014 nested payload (this command has complex input fields)");
60081
+ lines.push(" --input-file <path> \u2014 read the nested payload from a JSON file (same merge rules as --input)");
60050
60082
  }
60051
60083
  }
60052
60084
  if (entry.example !== void 0) {
@@ -60054,12 +60086,12 @@ function renderVerbSchema(entry) {
60054
60086
  }
60055
60087
  return lines.join("\n");
60056
60088
  }
60057
- function verbUsageLine(bin, group, verb, entry) {
60089
+ function verbUsageLine(bin, group, verb, entry, advertiseComplexInput) {
60058
60090
  const parts = entry.fields.map((field4) => {
60059
60091
  const token = field4.type === "boolean" ? `--${field4.flag}` : `--${field4.flag} <${field4.type === "enum" && field4.enumValues ? field4.enumValues.join("|") : field4.name}>`;
60060
60092
  return field4.required ? token : `[${token}]`;
60061
60093
  });
60062
- const complex = entry.hasComplexInput ? " [--input '<json>']" : "";
60094
+ const complex = entry.hasComplexInput && advertiseComplexInput ? " [--input '<json>' | --input-file <path>]" : "";
60063
60095
  return ` ${bin} ${group} ${verb} ${parts.join(" ")}${complex}`.replace(/\s+$/, "");
60064
60096
  }
60065
60097
  function groupUsage(bin, group, spec) {
@@ -60071,16 +60103,47 @@ function groupUsage(bin, group, spec) {
60071
60103
  if (verbSpec.usage) {
60072
60104
  lines.push(` ${verbSpec.usage}`);
60073
60105
  } else if (entry) {
60074
- lines.push(verbUsageLine(bin, group, verb, entry));
60106
+ lines.push(verbUsageLine(bin, group, verb, entry, !hasBespokeHandler(verbSpec)));
60075
60107
  }
60076
60108
  }
60077
60109
  return lines.join("\n");
60078
60110
  }
60111
+ async function resolveInputFileFlag(parsed) {
60112
+ const inputFile = parsed.values.get("input-file");
60113
+ if (inputFile === void 0) {
60114
+ if (parsed.flags.has("input-file")) {
60115
+ throw new UsageError("Missing required --input-file value.");
60116
+ }
60117
+ return;
60118
+ }
60119
+ if (parsed.values.has("input") || parsed.values.has("body") || parsed.flags.has("input") || parsed.flags.has("body")) {
60120
+ throw new UsageError("Use only one of --input, --body, or --input-file.");
60121
+ }
60122
+ let text;
60123
+ try {
60124
+ text = await readFile6(inputFile, "utf8");
60125
+ } catch (error51) {
60126
+ throw new UsageError(
60127
+ `Could not read --input-file ${inputFile}: ${error51 instanceof Error ? error51.message : String(error51)}`
60128
+ );
60129
+ }
60130
+ let fileJson;
60131
+ try {
60132
+ fileJson = JSON.parse(text);
60133
+ } catch {
60134
+ throw new UsageError(`--input-file ${inputFile} must be valid JSON.`);
60135
+ }
60136
+ if (typeof fileJson !== "object" || fileJson === null || Array.isArray(fileJson)) {
60137
+ throw new UsageError(`--input-file ${inputFile} must contain a JSON object.`);
60138
+ }
60139
+ parsed.values.set("input", text);
60140
+ parsed.values.delete("input-file");
60141
+ }
60079
60142
  function makeVerb(commandId, verbSpec) {
60080
- if (verbSpec.handler) {
60143
+ if (hasBespokeHandler(verbSpec)) {
60081
60144
  return verbSpec.handler;
60082
60145
  }
60083
- return (context, rest) => {
60146
+ return async (context, rest) => {
60084
60147
  const entry = MANIFEST_BY_ID.get(commandId);
60085
60148
  if (!entry) {
60086
60149
  throw new UsageError(`Unknown command ${commandId}.`);
@@ -60105,6 +60168,11 @@ function makeVerb(commandId, verbSpec) {
60105
60168
  const positional = hasPositional ? rest[0] : void 0;
60106
60169
  const flagArgs = hasPositional ? rest.slice(1) : rest;
60107
60170
  const parsed = parseFlags(flagArgs, booleanFlagsFor(entry, overrides));
60171
+ assertKnownFlags(entry, parsed, overrides, INPUT_FILE_FLAG);
60172
+ if (flagArgs.filter((token) => token === "--input-file").length > 1) {
60173
+ throw new UsageError("Pass --input-file at most once.");
60174
+ }
60175
+ await resolveInputFileFlag(parsed);
60108
60176
  const body = buildBodyFromFlags(entry, parsed, positional, overrides);
60109
60177
  const render = verbSpec.render ?? renderCommandOutput;
60110
60178
  return execute(context, parsed, commandId, body, render);
@@ -61795,540 +61863,95 @@ function ensureTrailingSlash2(value) {
61795
61863
  }
61796
61864
 
61797
61865
  // src/skills.ts
61798
- import { existsSync, mkdirSync, readFileSync as readFileSync3, rmSync, writeFileSync } from "node:fs";
61866
+ import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
61867
+ import os2 from "node:os";
61868
+ import path5 from "node:path";
61869
+
61870
+ // src/skill-install.ts
61871
+ import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync as readFileSync3, readSync, rmSync, statSync, writeFileSync } from "node:fs";
61799
61872
  import os from "node:os";
61800
61873
  import path4 from "node:path";
61801
- var UsageError2 = class extends Error {
61874
+
61875
+ // src/skill-trust.ts
61876
+ import { createHash as createHash2, createPublicKey, verify } from "node:crypto";
61877
+ var TRUSTED_SIGNER_PUBLIC_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOXSsKe/zFjaoAdZdvP/esPoeM1fsCGFmxv0INDlCxaW rost-implementation-skill-release";
61878
+ var TRUSTED_SIGNER_FINGERPRINT = "SHA256:ziSyU9vhGi0Q3GaeePB1R6BI3GqXutLhepnF/4Hx4rY";
61879
+ var RELEASE_REPOSITORY = {
61880
+ owner: "agenticoperatingsystem",
61881
+ name: "rost-implementation-skill"
61802
61882
  };
61803
- var CLIENTS = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
61804
- function skillsUsage(binName = cliBrand.binName) {
61805
- return [
61806
- `${binName} skills list [--json]`,
61807
- `${binName} skills catalog [--json]`,
61808
- `${binName} skills get --slug <slug> [--json]`,
61809
- `${binName} skills file --slug <slug> --path SKILL.md`,
61810
- `${binName} skills enable rost/<slug> [--json]`,
61811
- `${binName} skills install <slug-or-rost/slug> [--seat-id <id>] [--client claude-code|codex|cursor] [--version <n>] [--local-only] [--json]`,
61812
- `${binName} skills install-implementation [--client claude-code|codex|cursor] [--tag <tag>] [--json]`,
61813
- `${binName} skills sync --seat-id <id> [--client claude-code|codex|cursor] [--json]`,
61814
- `${binName} skills assign --seat-id <id> (--skill <slug>|--slug <slug>|--skill-version-id <id>) [--approve] [--allow-blocked-proposal] [--json]`,
61815
- `${binName} skills revoke (--assignment-id <id>|--seat-id <id> --skill <slug>) [--json]`,
61816
- `${binName} skills publish (--slug <slug>|--skill-version-id <id>) [--allow-warnings] [--json]`
61817
- ].join("\n");
61818
- }
61819
- async function runSkills(context, args, env = process.env) {
61820
- try {
61821
- const [action, ...rest] = args;
61822
- if (action === "help" || action === "--help" || action === "-h" || action === void 0) {
61823
- context.io.stdout.write(`${skillsUsage(context.binName)}
61824
- `);
61825
- return action === void 0 ? 1 : 0;
61826
- }
61827
- if (action === "list") {
61828
- return context.run(context.client, "skill.list", {}, void 0);
61829
- }
61830
- if (action === "catalog") {
61831
- const parsed = parseFlags2(rest, /* @__PURE__ */ new Set(["include-unentitled"]));
61832
- return context.run(context.client, "skill.catalog", {
61833
- ...parsed.flags.has("include-unentitled") ? { include_unentitled: true } : {}
61834
- }, parsed.json ? void 0 : formatCatalog);
61835
- }
61836
- if (action === "get") {
61837
- const parsed = parseFlags2(rest);
61838
- return context.run(context.client, "skill.get", { slug: requireValue3(parsed, "slug") }, parsed.json ? void 0 : formatSkillDetail);
61839
- }
61840
- if (action === "file") {
61841
- const parsed = parseFlags2(rest);
61842
- return context.run(context.client, "skill.file.get", {
61843
- slug: requireValue3(parsed, "slug"),
61844
- path: requireValue3(parsed, "path")
61845
- }, (output) => {
61846
- const content = asRecord4(output).content;
61847
- return typeof content === "string" ? content : JSON.stringify(output, null, 2);
61848
- });
61849
- }
61850
- if (action === "enable") {
61851
- const slug2 = rest[0];
61852
- if (!slug2 || slug2.startsWith("--")) {
61853
- throw new UsageError2(`Usage: ${context.binName} skills enable rost/<slug> [--json]`);
61854
- }
61855
- const parsed = parseFlags2(rest.slice(1));
61856
- return context.run(context.client, "skill.enable_catalog", { slug: slug2 }, parsed.json ? void 0 : formatEnable);
61857
- }
61858
- if (action === "install") {
61859
- return installOne(context, rest, env);
61860
- }
61861
- if (action === "sync") {
61862
- return syncAssigned(context, rest, env);
61863
- }
61864
- if (action === "assign") {
61865
- const parsed = parseFlags2(rest, /* @__PURE__ */ new Set(["approve", "allow-blocked-proposal"]));
61866
- const body = withDefined({
61867
- seat_id: requireValue3(parsed, "seat-id"),
61868
- status: parsed.flags.has("approve") ? "approved" : "proposed",
61869
- rationale: optionalValue2(parsed, "rationale") ?? "Skill selected for this Seat.",
61870
- allow_blocked_proposal: parsed.flags.has("allow-blocked-proposal")
61871
- }, {
61872
- slug: optionalValue2(parsed, "skill") ?? optionalValue2(parsed, "slug"),
61873
- skill_version_id: optionalValue2(parsed, "skill-version-id")
61874
- });
61875
- return context.run(context.client, "skill.assign_to_seat", body, parsed.json ? void 0 : formatAssignment, { targetSeatId: body.seat_id });
61876
- }
61877
- if (action === "revoke") {
61878
- const parsed = parseFlags2(rest);
61879
- const seatId = optionalValue2(parsed, "seat-id");
61880
- const body = withDefined({}, {
61881
- assignment_id: optionalValue2(parsed, "assignment-id"),
61882
- seat_id: seatId,
61883
- slug: optionalValue2(parsed, "skill") ?? optionalValue2(parsed, "slug"),
61884
- skill_version_id: optionalValue2(parsed, "skill-version-id"),
61885
- rationale: optionalValue2(parsed, "rationale")
61886
- });
61887
- return context.run(context.client, "skill.revoke_from_seat", body, parsed.json ? void 0 : formatRevoke, seatId ? { targetSeatId: seatId } : {});
61888
- }
61889
- if (action === "publish") {
61890
- const parsed = parseFlags2(rest, /* @__PURE__ */ new Set(["allow-warnings"]));
61891
- const body = withDefined({
61892
- allow_warnings: parsed.flags.has("allow-warnings")
61893
- }, {
61894
- slug: optionalValue2(parsed, "slug"),
61895
- skill_version_id: optionalValue2(parsed, "skill-version-id")
61896
- });
61897
- return context.run(context.client, "skill.publish", body, parsed.json ? void 0 : formatPublish);
61898
- }
61899
- throw new UsageError2(`Unknown skills action: ${action}`);
61900
- } catch (error51) {
61901
- if (error51 instanceof UsageError2) {
61902
- context.io.stderr.write(`${error51.message}
61903
- `);
61904
- return 1;
61905
- }
61906
- if (error51 instanceof CommandClientError) {
61907
- return printCommandError2(context.io, error51);
61908
- }
61909
- throw error51;
61883
+ var DEFAULT_RELEASE_TAG = "v0.2.0";
61884
+ var EXPECTED_DEFAULT_MANIFEST_SHA256 = "bbd745e1a625c30773fac5505f32c5152925d2474ac22d2cb4a93aa5a034ca66";
61885
+ var SKILL_MANIFEST_PROTOCOL_VERSION = 1;
61886
+ var SKILL_INSTALL_DIR_NAME = "rost-implementation";
61887
+ var SkillTrustError = class extends Error {
61888
+ constructor(message) {
61889
+ super(message);
61890
+ this.name = "SkillTrustError";
61910
61891
  }
61892
+ };
61893
+ function terminalSafe(value) {
61894
+ return value.replace(/[\x00-\x1f\x7f-\x9f]/g, "?");
61911
61895
  }
61912
- async function installOne(context, args, env) {
61913
- const slug2 = args[0];
61914
- if (!slug2 || slug2.startsWith("--")) {
61915
- throw new UsageError2(`Usage: ${context.binName} skills install <slug-or-rost/slug> [--seat-id <id>] [--client claude-code|codex|cursor] [--version <n>] [--local-only] [--json]`);
61896
+ var SUPPORTED_CLIENTS = ["claude-code", "codex", "cursor"];
61897
+ var skillManifestSchema = external_exports.object({
61898
+ schemaVersion: external_exports.literal(1),
61899
+ name: external_exports.literal("rost-implementation-skill"),
61900
+ version: external_exports.string(),
61901
+ repository: external_exports.object({
61902
+ owner: external_exports.string(),
61903
+ name: external_exports.string()
61904
+ }).strict(),
61905
+ protocol: external_exports.object({
61906
+ skill: external_exports.number().int(),
61907
+ minCli: external_exports.string(),
61908
+ maxCli: external_exports.string().nullable()
61909
+ }).strict(),
61910
+ supportedClients: external_exports.array(external_exports.enum(SUPPORTED_CLIENTS)).min(1),
61911
+ doctrine: external_exports.object({
61912
+ guide: external_exports.string(),
61913
+ version: external_exports.string()
61914
+ }).strict(),
61915
+ changelogUrl: external_exports.string().url(),
61916
+ files: external_exports.array(
61917
+ external_exports.object({
61918
+ path: external_exports.string(),
61919
+ sha256: external_exports.string().regex(/^[a-f0-9]{64}$/)
61920
+ }).strict()
61921
+ ).min(1)
61922
+ }).strict();
61923
+ function sha256Hex(content) {
61924
+ return createHash2("sha256").update(content).digest("hex");
61925
+ }
61926
+ function readSshString(buffer, offset) {
61927
+ if (offset + 4 > buffer.length) {
61928
+ throw new SkillTrustError("Truncated SSH wire string");
61916
61929
  }
61917
- const parsed = parseFlags2(args.slice(1), /* @__PURE__ */ new Set(["local-only"]));
61918
- const client = parseClient2(optionalValue2(parsed, "client") ?? "codex");
61919
- const seatId = optionalValue2(parsed, "seat-id");
61920
- const body = withDefined({
61921
- slug: slug2,
61922
- client,
61923
- local_only: parsed.flags.has("local-only")
61924
- }, {
61925
- seat_id: seatId,
61926
- version: optionalNumber2(parsed, "version")
61927
- });
61928
- const options = seatId ? { targetSeatId: seatId } : {};
61929
- const output = await executeInstallCommand(context, body, options);
61930
- const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: false });
61931
- const outputRecord = asRecord4(output);
61932
- context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
61933
- ` : `${formatLocalSummary(summary)}
61934
- `);
61935
- return 0;
61930
+ const length = buffer.readUInt32BE(offset);
61931
+ offset += 4;
61932
+ if (offset + length > buffer.length) {
61933
+ throw new SkillTrustError("Truncated SSH wire string payload");
61934
+ }
61935
+ return { value: buffer.subarray(offset, offset + length), nextOffset: offset + length };
61936
61936
  }
61937
- async function syncAssigned(context, args, env) {
61938
- const parsed = parseFlags2(args);
61939
- const seatId = requireValue3(parsed, "seat-id");
61940
- const client = parseClient2(optionalValue2(parsed, "client") ?? "codex");
61941
- const output = await executeSkillCommand(context, "skill.sync_local", {
61942
- seat_id: seatId,
61943
- client
61944
- }, { targetSeatId: seatId });
61945
- const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: true });
61946
- const outputRecord = asRecord4(output);
61947
- context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
61948
- ` : `${formatLocalSummary(summary)}
61949
- `);
61950
- return 0;
61937
+ function makeSshString(value) {
61938
+ const length = Buffer.alloc(4);
61939
+ length.writeUInt32BE(value.length);
61940
+ return Buffer.concat([length, value]);
61951
61941
  }
61952
- async function executeInstallCommand(context, body, options) {
61953
- const slug2 = typeof body.slug === "string" ? body.slug : "";
61954
- if (slug2.startsWith("rost/") && body.local_only !== true) {
61955
- try {
61956
- return await executeSkillCommand(context, "skill.install_local", { ...body, local_only: true }, options);
61957
- } catch (error51) {
61958
- if (!isCommandConflict(error51)) {
61959
- throw error51;
61960
- }
61961
- }
61942
+ function parseEd25519PublicKeyLine(line) {
61943
+ const trimmed = line.trim();
61944
+ const parts = trimmed.split(/\s+/);
61945
+ if (parts.length < 2) {
61946
+ throw new SkillTrustError("Malformed SSH public key line");
61962
61947
  }
61963
- return executeSkillCommand(context, "skill.install_local", body, options);
61964
- }
61965
- async function executeSkillCommand(context, commandId, body, options = {}) {
61966
- try {
61967
- const output = await context.client.execute(commandId, body, options);
61968
- return output.output;
61969
- } catch (error51) {
61970
- const pending = context.io.interactive === true ? pendingConfirmationFromError(error51) : null;
61971
- if (pending && pending.commandId === commandId) {
61972
- renderPendingConfirmationGate2(context.io, pending);
61973
- context.io.stderr.write(`Approving ${pending.commandId} confirmation ${pending.confirmationId} as the interactive CLI user.
61974
- `);
61975
- const approved = await context.client.execute("confirmation.approve", { confirmation_id: pending.confirmationId, reviewed: true });
61976
- return asRecord4(approved.output).output ?? approved.output;
61977
- }
61978
- throw error51;
61948
+ const keyType = parts[0];
61949
+ const base643 = parts[1];
61950
+ if (keyType === void 0 || base643 === void 0) {
61951
+ throw new SkillTrustError("Malformed SSH public key line");
61979
61952
  }
61980
- }
61981
- function applyLocalSkillChanges(output, client, env, options) {
61982
- const root = installRoot(client, env);
61983
- const changes = writeInstalls(output, root);
61984
- if (options.removeRevoked) {
61985
- changes.push(...removeRevokedInstalls(output, root));
61986
- }
61987
- return changes;
61988
- }
61989
- function writeInstalls(output, root) {
61990
- const installs = asArray2(asRecord4(output).installs);
61991
- return installs.map((entry) => {
61992
- const install = asRecord4(entry);
61993
- const slug2 = safeLocalSlug(String(install.slug ?? ""));
61994
- const metadata = asRecord4(install.metadata);
61995
- const files = asArray2(install.files).map(asRecord4);
61996
- const dir = path4.join(root, slug2);
61997
- const metadataPath = path4.join(dir, ".rost-skill.json");
61998
- const expectedHash = String(metadata.content_sha256 ?? "");
61999
- if (existingHash(metadataPath) === expectedHash) {
62000
- return { slug: slug2, dir, status: "unchanged", files: files.length };
62001
- }
62002
- rmSync(dir, { recursive: true, force: true });
62003
- mkdirSync(dir, { recursive: true });
62004
- for (const file2 of files) {
62005
- const relativePath = safeRelativePath(String(file2.path ?? ""));
62006
- const content = String(file2.content ?? "");
62007
- const target = path4.join(dir, relativePath);
62008
- mkdirSync(path4.dirname(target), { recursive: true });
62009
- writeFileSync(target, content, "utf8");
62010
- }
62011
- writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}
62012
- `, "utf8");
62013
- return { slug: slug2, dir, status: "installed", files: files.length };
62014
- });
62015
- }
62016
- function removeRevokedInstalls(output, root) {
62017
- const installs = new Set(asArray2(asRecord4(output).installs).map((entry) => {
62018
- const slug2 = asRecord4(entry).slug;
62019
- return typeof slug2 === "string" ? slug2 : "";
62020
- }));
62021
- const revoked = asArray2(asRecord4(output).revoked);
62022
- const changes = [];
62023
- for (const entry of revoked) {
62024
- const slug2 = safeLocalSlug(String(asRecord4(entry).slug ?? ""));
62025
- if (installs.has(slug2)) {
62026
- continue;
62027
- }
62028
- const dir = path4.join(root, slug2);
62029
- if (!existsSync(dir)) {
62030
- continue;
62031
- }
62032
- rmSync(dir, { recursive: true, force: true });
62033
- changes.push({ slug: slug2, dir, status: "removed", files: 0 });
62034
- }
62035
- return changes;
62036
- }
62037
- function installRoot(client, env) {
62038
- const override = env.ROST_SKILL_INSTALL_ROOT;
62039
- if (override && override.length > 0) {
62040
- return path4.resolve(override, client);
62041
- }
62042
- if (client === "codex") {
62043
- return path4.join(env.CODEX_HOME ?? path4.join(os.homedir(), ".codex"), "skills", "rost");
62044
- }
62045
- if (client === "claude-code") {
62046
- return path4.join(env.CLAUDE_HOME ?? path4.join(os.homedir(), ".claude"), "skills", "rost");
62047
- }
62048
- return path4.join(os.homedir(), ".cursor", "skills", "rost");
62049
- }
62050
- function safeRelativePath(value) {
62051
- const normalized = value.trim().replaceAll("\\", "/").replace(/^\.\/+/, "");
62052
- if (!normalized || normalized.startsWith("/") || normalized.includes("//") || normalized.split("/").includes("..")) {
62053
- throw new UsageError2("Server returned an invalid skill file path.");
62054
- }
62055
- return normalized;
62056
- }
62057
- function safeLocalSlug(value) {
62058
- if (!/^[a-z0-9][a-z0-9-]{1,80}$/.test(value)) {
62059
- throw new UsageError2("Server returned an invalid skill slug.");
62060
- }
62061
- return value;
62062
- }
62063
- function existingHash(metadataPath) {
62064
- if (!existsSync(metadataPath)) {
62065
- return null;
62066
- }
62067
- try {
62068
- const parsed = JSON.parse(readFileSync3(metadataPath, "utf8"));
62069
- const hash2 = asRecord4(parsed).content_sha256;
62070
- return typeof hash2 === "string" ? hash2 : null;
62071
- } catch {
62072
- return null;
62073
- }
62074
- }
62075
- function parseFlags2(args, booleanFlags = /* @__PURE__ */ new Set()) {
62076
- const values = /* @__PURE__ */ new Map();
62077
- const flags = /* @__PURE__ */ new Set();
62078
- let json2 = false;
62079
- for (let index = 0; index < args.length; index += 1) {
62080
- const token = args[index];
62081
- if (token === void 0) {
62082
- continue;
62083
- }
62084
- if (!token.startsWith("--")) {
62085
- throw new UsageError2(`Unexpected argument: ${token}`);
62086
- }
62087
- const key = token.slice(2);
62088
- if (key === "json") {
62089
- json2 = true;
62090
- continue;
62091
- }
62092
- if (booleanFlags.has(key)) {
62093
- flags.add(key);
62094
- continue;
62095
- }
62096
- const value = args[index + 1];
62097
- if (value === void 0 || value.startsWith("--")) {
62098
- flags.add(key);
62099
- continue;
62100
- }
62101
- values.set(key, value);
62102
- index += 1;
62103
- }
62104
- return { json: json2, values, flags };
62105
- }
62106
- function requireValue3(parsed, key) {
62107
- const value = optionalValue2(parsed, key);
62108
- if (value === void 0) {
62109
- throw new UsageError2(`Missing required --${key} value.`);
62110
- }
62111
- return value;
62112
- }
62113
- function optionalValue2(parsed, key) {
62114
- const value = parsed.values.get(key);
62115
- return value && value.length > 0 ? value : void 0;
62116
- }
62117
- function optionalNumber2(parsed, key) {
62118
- const value = optionalValue2(parsed, key);
62119
- if (value === void 0) {
62120
- return void 0;
62121
- }
62122
- const parsedNumber = Number(value);
62123
- if (!Number.isSafeInteger(parsedNumber) || parsedNumber <= 0) {
62124
- throw new UsageError2(`--${key} must be a positive integer.`);
62125
- }
62126
- return parsedNumber;
62127
- }
62128
- function parseClient2(value) {
62129
- if (!CLIENTS.has(value)) {
62130
- throw new UsageError2("--client must be claude-code, codex, or cursor.");
62131
- }
62132
- return value;
62133
- }
62134
- function isCommandConflict(error51) {
62135
- return error51 instanceof CommandClientError && error51.status === 409;
62136
- }
62137
- function withDefined(base, extra) {
62138
- const next = { ...base };
62139
- for (const [key, value] of Object.entries(extra)) {
62140
- if (value !== void 0) {
62141
- next[key] = value;
62142
- }
62143
- }
62144
- return next;
62145
- }
62146
- function asRecord4(value) {
62147
- return typeof value === "object" && value !== null ? value : {};
62148
- }
62149
- function asArray2(value) {
62150
- return Array.isArray(value) ? value : [];
62151
- }
62152
- function formatCatalog(output) {
62153
- const entries = asArray2(asRecord4(output).catalog);
62154
- if (entries.length === 0) {
62155
- return "No ROST catalog skills are available for this tenant.";
62156
- }
62157
- return entries.map((entry) => {
62158
- const record2 = asRecord4(entry);
62159
- return `${record2.catalog_slug} [${record2.tier}/${record2.entitlement_status}] ${record2.name}`;
62160
- }).join("\n");
62161
- }
62162
- function formatSkillDetail(output) {
62163
- const skill = asRecord4(asRecord4(output).skill);
62164
- return `${skill.slug} v${skill.latest_version ?? "\u2014"} [${skill.status}] ${skill.name}
62165
- ${skill.description}`;
62166
- }
62167
- function formatEnable(output) {
62168
- const skill = asRecord4(asRecord4(output).skill);
62169
- const catalog = asRecord4(asRecord4(output).catalog);
62170
- return `Enabled ${catalog.catalog_slug} as ${skill.slug} v${skill.version} (${skill.skill_version_id})`;
62171
- }
62172
- function formatAssignment(output) {
62173
- const assignment = asRecord4(asRecord4(output).assignment);
62174
- return `${assignment.status} ${assignment.slug} for seat ${assignment.seat_id}; sync: ${String(asRecord4(output).local_sync_next_step)}`;
62175
- }
62176
- function formatRevoke(output) {
62177
- const record2 = asRecord4(output);
62178
- return `Revoked ${record2.slug} for seat ${record2.seat_id}; sync: ${record2.local_sync_next_step}`;
62179
- }
62180
- function formatPublish(output) {
62181
- const skill = asRecord4(asRecord4(output).skill);
62182
- return `Published ${skill.slug} v${skill.version} (${skill.skill_version_id})`;
62183
- }
62184
- function formatLocalSummary(summary) {
62185
- if (summary.length === 0) {
62186
- return "No skills to install.";
62187
- }
62188
- return summary.map((entry) => `${entry.status} ${entry.slug} -> ${entry.dir} (${entry.files} files)`).join("\n");
62189
- }
62190
- function printCommandError2(io, error51) {
62191
- if (error51.response && !error51.response.ok) {
62192
- const payload = {
62193
- code: error51.response.error.code,
62194
- message: error51.response.error.message,
62195
- ...error51.response.error.details === void 0 ? {} : { details: error51.response.error.details },
62196
- ...error51.response.error.help === void 0 ? {} : { help: error51.response.error.help },
62197
- ...error51.response.requestId === void 0 ? {} : { requestId: error51.response.requestId }
62198
- };
62199
- io.stderr.write(`${redactForLog(payload)}
62200
- `);
62201
- if (error51.response.error.help !== void 0) {
62202
- io.stderr.write(`\u2192 try: ${redactForLog(error51.response.error.help)}
62203
- `);
62204
- }
62205
- return error51.status === 409 ? 3 : error51.status >= 400 && error51.status < 500 ? 1 : 2;
62206
- }
62207
- io.stderr.write(`${redactForLog(error51.message)}
62208
- `);
62209
- return error51.status >= 400 && error51.status < 500 ? 1 : 2;
62210
- }
62211
- function pendingConfirmationFromError(error51) {
62212
- if (!(error51 instanceof CommandClientError) || !error51.response || error51.response.ok || error51.response.error.code !== "COMMAND_CONFIRMATION_REQUIRED") {
62213
- return null;
62214
- }
62215
- const details = asRecord4(error51.response.error.details);
62216
- const confirmationId = details.confirmationId;
62217
- const commandId = details.commandId;
62218
- if (typeof confirmationId !== "string" || confirmationId.length === 0 || typeof commandId !== "string" || commandId.length === 0) {
62219
- return null;
62220
- }
62221
- const summary = typeof details.summary === "string" ? details.summary : void 0;
62222
- const riskLevel = typeof details.riskLevel === "string" ? details.riskLevel : void 0;
62223
- return {
62224
- confirmationId,
62225
- commandId,
62226
- ...summary === void 0 ? {} : { summary },
62227
- ...riskLevel === void 0 ? {} : { riskLevel },
62228
- ...details.diff === void 0 ? {} : { diff: details.diff }
62229
- };
62230
- }
62231
- function renderPendingConfirmationGate2(io, pending) {
62232
- if (pending.summary !== void 0) {
62233
- io.stderr.write(`${redactForLog(pending.summary)}
62234
- `);
62235
- }
62236
- if (pending.riskLevel !== void 0) {
62237
- io.stderr.write(`Risk level: ${pending.riskLevel}
62238
- `);
62239
- }
62240
- if (pending.diff !== void 0) {
62241
- io.stderr.write(`Diff:
62242
- ${redactForLog(pending.diff)}
62243
- `);
62244
- }
62245
- }
62246
-
62247
- // src/skill-install.ts
62248
- import { lstatSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
62249
- import os2 from "node:os";
62250
- import path5 from "node:path";
62251
-
62252
- // src/skill-trust.ts
62253
- import { createHash as createHash2, createPublicKey, verify } from "node:crypto";
62254
- var TRUSTED_SIGNER_PUBLIC_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOXSsKe/zFjaoAdZdvP/esPoeM1fsCGFmxv0INDlCxaW rost-implementation-skill-release";
62255
- var TRUSTED_SIGNER_FINGERPRINT = "SHA256:ziSyU9vhGi0Q3GaeePB1R6BI3GqXutLhepnF/4Hx4rY";
62256
- var RELEASE_REPOSITORY = {
62257
- owner: "agenticoperatingsystem",
62258
- name: "rost-implementation-skill"
62259
- };
62260
- var DEFAULT_RELEASE_TAG = "v0.2.0";
62261
- var EXPECTED_DEFAULT_MANIFEST_SHA256 = "bbd745e1a625c30773fac5505f32c5152925d2474ac22d2cb4a93aa5a034ca66";
62262
- var SKILL_MANIFEST_PROTOCOL_VERSION = 1;
62263
- var SKILL_INSTALL_DIR_NAME = "rost-implementation";
62264
- var SkillTrustError = class extends Error {
62265
- constructor(message) {
62266
- super(message);
62267
- this.name = "SkillTrustError";
62268
- }
62269
- };
62270
- function terminalSafe(value) {
62271
- return value.replace(/[\x00-\x1f\x7f-\x9f]/g, "?");
62272
- }
62273
- var SUPPORTED_CLIENTS = ["claude-code", "codex", "cursor"];
62274
- var skillManifestSchema = external_exports.object({
62275
- schemaVersion: external_exports.literal(1),
62276
- name: external_exports.literal("rost-implementation-skill"),
62277
- version: external_exports.string(),
62278
- repository: external_exports.object({
62279
- owner: external_exports.string(),
62280
- name: external_exports.string()
62281
- }).strict(),
62282
- protocol: external_exports.object({
62283
- skill: external_exports.number().int(),
62284
- minCli: external_exports.string(),
62285
- maxCli: external_exports.string().nullable()
62286
- }).strict(),
62287
- supportedClients: external_exports.array(external_exports.enum(SUPPORTED_CLIENTS)).min(1),
62288
- doctrine: external_exports.object({
62289
- guide: external_exports.string(),
62290
- version: external_exports.string()
62291
- }).strict(),
62292
- changelogUrl: external_exports.string().url(),
62293
- files: external_exports.array(
62294
- external_exports.object({
62295
- path: external_exports.string(),
62296
- sha256: external_exports.string().regex(/^[a-f0-9]{64}$/)
62297
- }).strict()
62298
- ).min(1)
62299
- }).strict();
62300
- function sha256Hex(content) {
62301
- return createHash2("sha256").update(content).digest("hex");
62302
- }
62303
- function readSshString(buffer, offset) {
62304
- if (offset + 4 > buffer.length) {
62305
- throw new SkillTrustError("Truncated SSH wire string");
62306
- }
62307
- const length = buffer.readUInt32BE(offset);
62308
- offset += 4;
62309
- if (offset + length > buffer.length) {
62310
- throw new SkillTrustError("Truncated SSH wire string payload");
62311
- }
62312
- return { value: buffer.subarray(offset, offset + length), nextOffset: offset + length };
62313
- }
62314
- function makeSshString(value) {
62315
- const length = Buffer.alloc(4);
62316
- length.writeUInt32BE(value.length);
62317
- return Buffer.concat([length, value]);
62318
- }
62319
- function parseEd25519PublicKeyLine(line) {
62320
- const trimmed = line.trim();
62321
- const parts = trimmed.split(/\s+/);
62322
- if (parts.length < 2) {
62323
- throw new SkillTrustError("Malformed SSH public key line");
62324
- }
62325
- const keyType = parts[0];
62326
- const base643 = parts[1];
62327
- if (keyType === void 0 || base643 === void 0) {
62328
- throw new SkillTrustError("Malformed SSH public key line");
62329
- }
62330
- if (keyType !== "ssh-ed25519") {
62331
- throw new SkillTrustError(`Unsupported SSH public key type: ${keyType}`);
61953
+ if (keyType !== "ssh-ed25519") {
61954
+ throw new SkillTrustError(`Unsupported SSH public key type: ${keyType}`);
62332
61955
  }
62333
61956
  let blob;
62334
61957
  try {
@@ -62467,7 +62090,7 @@ function verifyTagSignature(payload, signatureArmored, trustedPublicKeyLine = TR
62467
62090
  }
62468
62091
  return { ok: true, reason: "Verified" };
62469
62092
  }
62470
- function safeRelativePath2(value) {
62093
+ function safeRelativePath(value) {
62471
62094
  if (value.includes("\\")) {
62472
62095
  throw new SkillTrustError("Manifest file path is not a safe relative path");
62473
62096
  }
@@ -62561,7 +62184,7 @@ function verifyReleaseManifest(manifest, options) {
62561
62184
  }
62562
62185
  let files;
62563
62186
  try {
62564
- files = parsed.files.map((file2) => ({ ...file2, path: safeRelativePath2(file2.path) }));
62187
+ files = parsed.files.map((file2) => ({ ...file2, path: safeRelativePath(file2.path) }));
62565
62188
  } catch (error51) {
62566
62189
  const message = error51 instanceof Error ? error51.message : "Invalid path";
62567
62190
  return { ok: false, reason: message };
@@ -62571,13 +62194,13 @@ function verifyReleaseManifest(manifest, options) {
62571
62194
  function verifyFileChecksums(files, manifest) {
62572
62195
  const manifestMap = /* @__PURE__ */ new Map();
62573
62196
  for (const file2 of manifest.files) {
62574
- manifestMap.set(safeRelativePath2(file2.path), file2.sha256);
62197
+ manifestMap.set(safeRelativePath(file2.path), file2.sha256);
62575
62198
  }
62576
62199
  const seen = /* @__PURE__ */ new Set();
62577
62200
  for (const file2 of files) {
62578
62201
  let normalized;
62579
62202
  try {
62580
- normalized = safeRelativePath2(file2.path);
62203
+ normalized = safeRelativePath(file2.path);
62581
62204
  } catch (error51) {
62582
62205
  const message = error51 instanceof Error ? error51.message : "Invalid path";
62583
62206
  return { ok: false, reason: message };
@@ -62663,13 +62286,14 @@ function verifyReleaseTrust(input) {
62663
62286
  }
62664
62287
 
62665
62288
  // src/skill-install.ts
62666
- var UsageError3 = class extends Error {
62289
+ var UsageError2 = class extends Error {
62667
62290
  };
62291
+ var O_NOFOLLOW_FLAG = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
62668
62292
  function writeErrorLine(io, message) {
62669
62293
  io.stderr.write(`${terminalSafe(message)}
62670
62294
  `);
62671
62295
  }
62672
- var CLIENTS2 = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
62296
+ var CLIENTS = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
62673
62297
  var REPO_PATH = `${RELEASE_REPOSITORY.owner}/${RELEASE_REPOSITORY.name}`;
62674
62298
  var GITHUB_JSON_HEADERS = { "User-Agent": "rost-cli", "Accept": "application/vnd.github+json" };
62675
62299
  var GITHUB_RAW_HEADERS = { "User-Agent": "rost-cli", "Accept": "application/vnd.github.raw+json" };
@@ -62756,11 +62380,12 @@ async function runInstallImplementation(context, args) {
62756
62380
  let client;
62757
62381
  let tag;
62758
62382
  try {
62759
- parsed = parseFlags3(args);
62760
- client = parseClient3(optionalValue3(parsed, "client") ?? "codex");
62761
- tag = optionalValue3(parsed, "tag") ?? DEFAULT_RELEASE_TAG;
62383
+ parsed = parseFlags2(args);
62384
+ client = parseClient2(optionalValue2(parsed, "client") ?? "codex");
62385
+ tag = optionalValue2(parsed, "tag") ?? DEFAULT_RELEASE_TAG;
62386
+ installRoot(client, context.env);
62762
62387
  } catch (error51) {
62763
- if (error51 instanceof UsageError3) {
62388
+ if (error51 instanceof UsageError2) {
62764
62389
  writeErrorLine(context.io, error51.message);
62765
62390
  return 1;
62766
62391
  }
@@ -62923,10 +62548,11 @@ async function installRelease(context, plan, testOverrides = {}) {
62923
62548
  }
62924
62549
  let root;
62925
62550
  let legacyNotice;
62551
+ let viaOverride;
62926
62552
  try {
62927
- ({ root, legacyNotice } = installRoot2(plan.client, context.env));
62553
+ ({ root, legacyNotice, viaOverride } = installRoot(plan.client, context.env));
62928
62554
  } catch (error51) {
62929
- if (error51 instanceof UsageError3) {
62555
+ if (error51 instanceof UsageError2) {
62930
62556
  writeErrorLine(context.io, error51.message);
62931
62557
  return 1;
62932
62558
  }
@@ -62935,7 +62561,7 @@ async function installRelease(context, plan, testOverrides = {}) {
62935
62561
  if (legacyNotice !== null) {
62936
62562
  writeErrorLine(context.io, legacyNotice);
62937
62563
  }
62938
- const dir = path5.join(root, SKILL_INSTALL_DIR_NAME);
62564
+ const dir = path4.join(root, SKILL_INSTALL_DIR_NAME);
62939
62565
  const manifestSha256 = sha256Hex(plan.manifestRaw);
62940
62566
  const existingMarker = readMarker(dir);
62941
62567
  const installedState = existingMarker?.manifestSha256 === manifestSha256 ? verifyInstalledTree(dir, manifest) : null;
@@ -62959,28 +62585,31 @@ async function installRelease(context, plan, testOverrides = {}) {
62959
62585
  })}
62960
62586
  `
62961
62587
  );
62588
+ writeErrorLine(context.io, clientLoadLine(plan.client, dir, viaOverride));
62962
62589
  } else {
62963
62590
  context.io.stdout.write(
62964
- `installed rost-implementation ${plan.tag} (${commitSha.slice(0, 7)}) -> ${dir} (${plan.files.length} files)
62591
+ `unchanged rost-implementation ${plan.tag} (${commitSha.slice(0, 7)}) -> ${humanPath(dir)} (${plan.files.length} files)
62965
62592
  `
62966
62593
  );
62594
+ context.io.stdout.write(`${clientLoadLine(plan.client, dir, viaOverride)}
62595
+ `);
62967
62596
  context.io.stdout.write(`${verifiedSignerLine()}
62968
62597
  `);
62969
62598
  }
62970
62599
  return 0;
62971
62600
  }
62972
- rmSync2(dir, { recursive: true, force: true });
62973
- mkdirSync2(dir, { recursive: true });
62601
+ rmSync(dir, { recursive: true, force: true });
62602
+ mkdirSync(dir, { recursive: true });
62974
62603
  for (const file2 of plan.files) {
62975
- const target = path5.join(dir, file2.path);
62976
- const resolvedTarget = path5.resolve(target);
62977
- const resolvedDir = path5.resolve(dir);
62978
- if (!resolvedTarget.startsWith(`${resolvedDir}${path5.sep}`)) {
62604
+ const target = path4.join(dir, file2.path);
62605
+ const resolvedTarget = path4.resolve(target);
62606
+ const resolvedDir = path4.resolve(dir);
62607
+ if (!resolvedTarget.startsWith(`${resolvedDir}${path4.sep}`)) {
62979
62608
  writeErrorLine(context.io, `Refusing to write file outside target directory: ${file2.path}`);
62980
62609
  return 1;
62981
62610
  }
62982
- mkdirSync2(path5.dirname(target), { recursive: true });
62983
- writeFileSync2(target, file2.content, "utf8");
62611
+ mkdirSync(path4.dirname(target), { recursive: true });
62612
+ writeFileSync(target, file2.content, "utf8");
62984
62613
  }
62985
62614
  const writtenState = verifyInstalledTree(dir, manifest);
62986
62615
  if (!writtenState.ok) {
@@ -63003,11 +62632,14 @@ async function installRelease(context, plan, testOverrides = {}) {
63003
62632
  })}
63004
62633
  `
63005
62634
  );
62635
+ writeErrorLine(context.io, clientLoadLine(plan.client, dir, viaOverride));
63006
62636
  } else {
63007
62637
  context.io.stdout.write(
63008
- `installed rost-implementation ${plan.tag} (${commitSha.slice(0, 7)}) -> ${dir} (${plan.files.length} files)
62638
+ `installed rost-implementation ${plan.tag} (${commitSha.slice(0, 7)}) -> ${humanPath(dir)} (${plan.files.length} files)
63009
62639
  `
63010
62640
  );
62641
+ context.io.stdout.write(`${clientLoadLine(plan.client, dir, viaOverride)}
62642
+ `);
63011
62643
  context.io.stdout.write(`${verifiedSignerLine()}
63012
62644
  `);
63013
62645
  }
@@ -63018,6 +62650,23 @@ function verifiedSignerLine() {
63018
62650
  const fingerprintBase = TRUSTED_SIGNER_FINGERPRINT.replace("SHA256:", "");
63019
62651
  return `verified signed release; signer SHA256:${fingerprintBase.slice(-12)} (last 12 chars)`;
63020
62652
  }
62653
+ function clientDisplay(client) {
62654
+ if (client === "claude-code") return "Claude Code";
62655
+ if (client === "codex") return "Codex";
62656
+ return "Cursor";
62657
+ }
62658
+ function humanPath(dir) {
62659
+ if (terminalSafe(dir) === dir) {
62660
+ return dir;
62661
+ }
62662
+ return JSON.stringify(dir).replace(/[\x7f-\x9f]/gu, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
62663
+ }
62664
+ function clientLoadLine(client, dir, viaOverride) {
62665
+ if (viaOverride) {
62666
+ return `ROST_SKILL_INSTALL_ROOT chose ${humanPath(dir)} \u2014 ${clientDisplay(client)} loads from its own root, which may differ`;
62667
+ }
62668
+ return `${clientDisplay(client)} will load this from ${humanPath(dir)}`;
62669
+ }
63021
62670
  function writeMarker(dir, input) {
63022
62671
  const marker = {
63023
62672
  source: `github.com/${REPO_PATH}`,
@@ -63029,7 +62678,7 @@ function writeMarker(dir, input) {
63029
62678
  // operator's evidence that the pre-launch re-run actually happened.
63030
62679
  installedAt: (/* @__PURE__ */ new Date()).toISOString()
63031
62680
  };
63032
- writeFileSync2(path5.join(dir, SKILL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
62681
+ writeFileSync(path4.join(dir, SKILL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
63033
62682
  `, "utf8");
63034
62683
  }
63035
62684
  function listInstalledFiles(dir) {
@@ -63037,7 +62686,7 @@ function listInstalledFiles(dir) {
63037
62686
  const walk = (current, prefix) => {
63038
62687
  for (const name of readdirSync(current)) {
63039
62688
  const relative = prefix === "" ? name : `${prefix}/${name}`;
63040
- const absolute = path5.join(current, name);
62689
+ const absolute = path4.join(current, name);
63041
62690
  const stats = lstatSync(absolute);
63042
62691
  if (stats.isSymbolicLink() || !stats.isFile() && !stats.isDirectory()) {
63043
62692
  throw new Error(`refusing to verify a symlink or special file: ${relative}`);
@@ -63049,7 +62698,7 @@ function listInstalledFiles(dir) {
63049
62698
  if (relative === SKILL_MARKER_FILE) {
63050
62699
  continue;
63051
62700
  }
63052
- files.push({ path: relative, absolute });
62701
+ files.push({ path: relative, absolute, dev: stats.dev, ino: stats.ino });
63053
62702
  }
63054
62703
  };
63055
62704
  walk(dir, "");
@@ -63070,70 +62719,445 @@ function verifyInstalledTree(dir, manifest) {
63070
62719
  if (present.length !== manifestPaths.size) {
63071
62720
  return { ok: false, reason: "the installed tree is missing files listed in the signed manifest" };
63072
62721
  }
62722
+ for (const file2 of present) {
62723
+ try {
62724
+ if (statSync(file2.absolute).size > MAX_RESPONSE_BYTES) {
62725
+ return { ok: false, reason: `an installed file exceeds the ${MAX_RESPONSE_BYTES}-byte verification cap: ${file2.path}` };
62726
+ }
62727
+ } catch (error51) {
62728
+ return { ok: false, reason: error51 instanceof Error ? error51.message : "an installed file is unreadable" };
62729
+ }
62730
+ }
63073
62731
  function* readEach() {
63074
62732
  for (const file2 of present) {
63075
- yield { path: file2.path, content: readFileSync4(file2.absolute) };
62733
+ const fd = openSync(file2.absolute, constants.O_RDONLY | O_NOFOLLOW_FLAG);
62734
+ try {
62735
+ const opened = fstatSync(fd);
62736
+ if (opened.dev !== file2.dev || opened.ino !== file2.ino) {
62737
+ throw new Error(`an installed file changed between enumeration and verification: ${file2.path}`);
62738
+ }
62739
+ const size = opened.size;
62740
+ if (size > MAX_RESPONSE_BYTES) {
62741
+ throw new Error(`an installed file exceeds the ${MAX_RESPONSE_BYTES}-byte verification cap: ${file2.path}`);
62742
+ }
62743
+ const buffer = Buffer.alloc(size + 1);
62744
+ let bytesRead = 0;
62745
+ while (bytesRead < buffer.length) {
62746
+ const chunk = readSync(fd, buffer, bytesRead, buffer.length - bytesRead, null);
62747
+ if (chunk === 0) {
62748
+ break;
62749
+ }
62750
+ bytesRead += chunk;
62751
+ }
62752
+ if (bytesRead > MAX_RESPONSE_BYTES) {
62753
+ throw new Error(`an installed file exceeds the ${MAX_RESPONSE_BYTES}-byte verification cap: ${file2.path}`);
62754
+ }
62755
+ yield { path: file2.path, content: buffer.subarray(0, bytesRead) };
62756
+ } finally {
62757
+ closeSync(fd);
62758
+ }
62759
+ }
62760
+ }
62761
+ try {
62762
+ return verifyFileChecksums(readEach(), manifest);
62763
+ } catch (error51) {
62764
+ return { ok: false, reason: error51 instanceof Error ? error51.message : "an installed file is unreadable" };
62765
+ }
62766
+ }
62767
+ function readMarker(dir) {
62768
+ try {
62769
+ const content = readFileSync3(path4.join(dir, SKILL_MARKER_FILE), "utf8");
62770
+ const parsed = JSON.parse(content);
62771
+ if (typeof parsed === "object" && parsed !== null) {
62772
+ return parsed;
62773
+ }
62774
+ } catch {
62775
+ }
62776
+ return null;
62777
+ }
62778
+ function resolveClaudeSkillInstallRoot(env) {
62779
+ const configDir = requireNonBlank("CLAUDE_CONFIG_DIR", env.CLAUDE_CONFIG_DIR);
62780
+ const legacyHome = requireNonBlank("CLAUDE_HOME", env.CLAUDE_HOME);
62781
+ if (configDir !== null) {
62782
+ const disagrees = legacyHome !== null && path4.resolve(legacyHome) !== path4.resolve(configDir);
62783
+ return {
62784
+ root: path4.resolve(path4.join(configDir, "skills")),
62785
+ legacyNotice: disagrees ? `NOTICE: CLAUDE_HOME (${humanPath(legacyHome)}) is ignored; Claude Code reads CLAUDE_CONFIG_DIR (${humanPath(configDir)}).` : null
62786
+ };
62787
+ }
62788
+ if (legacyHome !== null) {
62789
+ throw new UsageError2(
62790
+ `CLAUDE_HOME (${humanPath(legacyHome)}) is set, but Claude Code does not read it \u2014 the Skill would install where Claude Code never loads it. Set CLAUDE_CONFIG_DIR=${humanPath(legacyHome)} instead, or unset CLAUDE_HOME to install under the default ~/.claude.`
62791
+ );
62792
+ }
62793
+ const home = resolveHome(env);
62794
+ return { root: path4.resolve(path4.join(home, ".claude", "skills")), legacyNotice: null };
62795
+ }
62796
+ function requireNonBlank(name, value) {
62797
+ if (value === void 0 || value === "") {
62798
+ return null;
62799
+ }
62800
+ if (value.trim().length === 0) {
62801
+ throw new UsageError2(`${name} is set to a blank value; unset it or give it a real path.`);
62802
+ }
62803
+ return value;
62804
+ }
62805
+ function resolveHome(env) {
62806
+ const explicit = requireNonBlank("HOME", env.HOME);
62807
+ if (explicit !== null) {
62808
+ return explicit;
62809
+ }
62810
+ let osHome = "";
62811
+ try {
62812
+ osHome = os.userInfo().homedir;
62813
+ } catch {
62814
+ }
62815
+ if (osHome.trim().length === 0) {
62816
+ throw new UsageError2("HOME is unset or blank and the OS reports no home directory for this user; set HOME to a real path.");
62817
+ }
62818
+ return osHome;
62819
+ }
62820
+ function resolveCodexSkillsRoot(env) {
62821
+ const codexHome = requireNonBlank("CODEX_HOME", env.CODEX_HOME);
62822
+ return path4.resolve(path4.join(codexHome ?? path4.join(resolveHome(env), ".codex"), "skills"));
62823
+ }
62824
+ function installRoot(client, env) {
62825
+ const override = requireNonBlank("ROST_SKILL_INSTALL_ROOT", env.ROST_SKILL_INSTALL_ROOT);
62826
+ if (override !== null) {
62827
+ return { root: path4.resolve(override, client), legacyNotice: null, viaOverride: true };
62828
+ }
62829
+ if (client === "codex") {
62830
+ return { root: resolveCodexSkillsRoot(env), legacyNotice: null, viaOverride: false };
62831
+ }
62832
+ if (client === "claude-code") {
62833
+ return { ...resolveClaudeSkillInstallRoot(env), viaOverride: false };
62834
+ }
62835
+ return { root: path4.resolve(path4.join(resolveHome(env), ".cursor", "skills")), legacyNotice: null, viaOverride: false };
62836
+ }
62837
+ function parseFlags2(args) {
62838
+ const values = /* @__PURE__ */ new Map();
62839
+ const flags = /* @__PURE__ */ new Set();
62840
+ let json2 = false;
62841
+ for (let index = 0; index < args.length; index += 1) {
62842
+ const token = args[index];
62843
+ if (token === void 0) {
62844
+ continue;
62845
+ }
62846
+ if (!token.startsWith("--")) {
62847
+ throw new UsageError2(`Unexpected argument: ${token}`);
62848
+ }
62849
+ const key = token.slice(2);
62850
+ if (key === "json") {
62851
+ json2 = true;
62852
+ continue;
62853
+ }
62854
+ if (key === "client" || key === "tag") {
62855
+ const value = args[index + 1];
62856
+ if (value === void 0 || value.startsWith("--")) {
62857
+ throw new UsageError2(`--${key} requires a value.`);
62858
+ }
62859
+ values.set(key, value);
62860
+ index += 1;
62861
+ continue;
62862
+ }
62863
+ throw new UsageError2(`Unexpected flag: --${key}`);
62864
+ }
62865
+ return { json: json2, values, flags };
62866
+ }
62867
+ function optionalValue2(parsed, key) {
62868
+ const value = parsed.values.get(key);
62869
+ return value !== void 0 && value.length > 0 ? value : void 0;
62870
+ }
62871
+ function parseClient2(value) {
62872
+ if (!CLIENTS.has(value)) {
62873
+ throw new UsageError2("--client must be claude-code, codex, or cursor.");
62874
+ }
62875
+ return value;
62876
+ }
62877
+
62878
+ // src/skills.ts
62879
+ var UsageError3 = class extends Error {
62880
+ };
62881
+ var CLIENTS2 = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
62882
+ function skillsUsage(binName = cliBrand.binName) {
62883
+ return [
62884
+ `${binName} skills list [--json]`,
62885
+ `${binName} skills catalog [--json]`,
62886
+ `${binName} skills get --slug <slug> [--json]`,
62887
+ `${binName} skills file --slug <slug> --path SKILL.md`,
62888
+ `${binName} skills enable rost/<slug> [--json]`,
62889
+ `${binName} skills install <slug-or-rost/slug> [--seat-id <id>] [--client claude-code|codex|cursor] [--version <n>] [--local-only] [--json]`,
62890
+ `${binName} skills install-implementation [--client claude-code|codex|cursor] [--tag <tag>] [--json]`,
62891
+ `${binName} skills sync --seat-id <id> [--client claude-code|codex|cursor] [--json]`,
62892
+ `${binName} skills assign --seat-id <id> (--skill <slug>|--slug <slug>|--skill-version-id <id>) [--approve] [--allow-blocked-proposal] [--json]`,
62893
+ `${binName} skills revoke (--assignment-id <id>|--seat-id <id> --skill <slug>) [--json]`,
62894
+ `${binName} skills publish (--slug <slug>|--skill-version-id <id>) [--allow-warnings] [--json]`
62895
+ ].join("\n");
62896
+ }
62897
+ async function runSkills(context, args, env = process.env) {
62898
+ try {
62899
+ const [action, ...rest] = args;
62900
+ if (action === "help" || action === "--help" || action === "-h" || action === void 0) {
62901
+ context.io.stdout.write(`${skillsUsage(context.binName)}
62902
+ `);
62903
+ return action === void 0 ? 1 : 0;
62904
+ }
62905
+ if (action === "list") {
62906
+ return context.run(context.client, "skill.list", {}, void 0);
62907
+ }
62908
+ if (action === "catalog") {
62909
+ const parsed = parseFlags3(rest, /* @__PURE__ */ new Set(["include-unentitled"]));
62910
+ return context.run(context.client, "skill.catalog", {
62911
+ ...parsed.flags.has("include-unentitled") ? { include_unentitled: true } : {}
62912
+ }, parsed.json ? void 0 : formatCatalog);
62913
+ }
62914
+ if (action === "get") {
62915
+ const parsed = parseFlags3(rest);
62916
+ return context.run(context.client, "skill.get", { slug: requireValue3(parsed, "slug") }, parsed.json ? void 0 : formatSkillDetail);
62917
+ }
62918
+ if (action === "file") {
62919
+ const parsed = parseFlags3(rest);
62920
+ return context.run(context.client, "skill.file.get", {
62921
+ slug: requireValue3(parsed, "slug"),
62922
+ path: requireValue3(parsed, "path")
62923
+ }, (output) => {
62924
+ const content = asRecord4(output).content;
62925
+ return typeof content === "string" ? content : JSON.stringify(output, null, 2);
62926
+ });
62927
+ }
62928
+ if (action === "enable") {
62929
+ const slug2 = rest[0];
62930
+ if (!slug2 || slug2.startsWith("--")) {
62931
+ throw new UsageError3(`Usage: ${context.binName} skills enable rost/<slug> [--json]`);
62932
+ }
62933
+ const parsed = parseFlags3(rest.slice(1));
62934
+ return context.run(context.client, "skill.enable_catalog", { slug: slug2 }, parsed.json ? void 0 : formatEnable);
62935
+ }
62936
+ if (action === "install") {
62937
+ return await installOne(context, rest, env);
62938
+ }
62939
+ if (action === "sync") {
62940
+ return await syncAssigned(context, rest, env);
62941
+ }
62942
+ if (action === "assign") {
62943
+ const parsed = parseFlags3(rest, /* @__PURE__ */ new Set(["approve", "allow-blocked-proposal"]));
62944
+ const body = withDefined({
62945
+ seat_id: requireValue3(parsed, "seat-id"),
62946
+ status: parsed.flags.has("approve") ? "approved" : "proposed",
62947
+ rationale: optionalValue3(parsed, "rationale") ?? "Skill selected for this Seat.",
62948
+ allow_blocked_proposal: parsed.flags.has("allow-blocked-proposal")
62949
+ }, {
62950
+ slug: optionalValue3(parsed, "skill") ?? optionalValue3(parsed, "slug"),
62951
+ skill_version_id: optionalValue3(parsed, "skill-version-id")
62952
+ });
62953
+ return context.run(context.client, "skill.assign_to_seat", body, parsed.json ? void 0 : formatAssignment, { targetSeatId: body.seat_id });
62954
+ }
62955
+ if (action === "revoke") {
62956
+ const parsed = parseFlags3(rest);
62957
+ const seatId = optionalValue3(parsed, "seat-id");
62958
+ const body = withDefined({}, {
62959
+ assignment_id: optionalValue3(parsed, "assignment-id"),
62960
+ seat_id: seatId,
62961
+ slug: optionalValue3(parsed, "skill") ?? optionalValue3(parsed, "slug"),
62962
+ skill_version_id: optionalValue3(parsed, "skill-version-id"),
62963
+ rationale: optionalValue3(parsed, "rationale")
62964
+ });
62965
+ return context.run(context.client, "skill.revoke_from_seat", body, parsed.json ? void 0 : formatRevoke, seatId ? { targetSeatId: seatId } : {});
62966
+ }
62967
+ if (action === "publish") {
62968
+ const parsed = parseFlags3(rest, /* @__PURE__ */ new Set(["allow-warnings"]));
62969
+ const body = withDefined({
62970
+ allow_warnings: parsed.flags.has("allow-warnings")
62971
+ }, {
62972
+ slug: optionalValue3(parsed, "slug"),
62973
+ skill_version_id: optionalValue3(parsed, "skill-version-id")
62974
+ });
62975
+ return context.run(context.client, "skill.publish", body, parsed.json ? void 0 : formatPublish);
62976
+ }
62977
+ throw new UsageError3(`Unknown skills action: ${action}`);
62978
+ } catch (error51) {
62979
+ if (error51 instanceof UsageError3) {
62980
+ context.io.stderr.write(`${error51.message}
62981
+ `);
62982
+ return 1;
62983
+ }
62984
+ if (error51 instanceof CommandClientError) {
62985
+ return printCommandError2(context.io, error51);
62986
+ }
62987
+ throw error51;
62988
+ }
62989
+ }
62990
+ async function installOne(context, args, env) {
62991
+ const slug2 = args[0];
62992
+ if (!slug2 || slug2.startsWith("--")) {
62993
+ throw new UsageError3(`Usage: ${context.binName} skills install <slug-or-rost/slug> [--seat-id <id>] [--client claude-code|codex|cursor] [--version <n>] [--local-only] [--json]`);
62994
+ }
62995
+ const parsed = parseFlags3(args.slice(1), /* @__PURE__ */ new Set(["local-only"]));
62996
+ const client = parseClient3(optionalValue3(parsed, "client") ?? "codex");
62997
+ const seatId = optionalValue3(parsed, "seat-id");
62998
+ const body = withDefined({
62999
+ slug: slug2,
63000
+ client,
63001
+ local_only: parsed.flags.has("local-only")
63002
+ }, {
63003
+ seat_id: seatId,
63004
+ version: optionalNumber2(parsed, "version")
63005
+ });
63006
+ const options = seatId ? { targetSeatId: seatId } : {};
63007
+ const output = await executeInstallCommand(context, body, options);
63008
+ const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: false });
63009
+ const outputRecord = asRecord4(output);
63010
+ context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
63011
+ ` : `${formatLocalSummary(summary)}
63012
+ `);
63013
+ return 0;
63014
+ }
63015
+ async function syncAssigned(context, args, env) {
63016
+ const parsed = parseFlags3(args);
63017
+ const seatId = requireValue3(parsed, "seat-id");
63018
+ const client = parseClient3(optionalValue3(parsed, "client") ?? "codex");
63019
+ const output = await executeSkillCommand(context, "skill.sync_local", {
63020
+ seat_id: seatId,
63021
+ client
63022
+ }, { targetSeatId: seatId });
63023
+ const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: true });
63024
+ const outputRecord = asRecord4(output);
63025
+ context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
63026
+ ` : `${formatLocalSummary(summary)}
63027
+ `);
63028
+ return 0;
63029
+ }
63030
+ async function executeInstallCommand(context, body, options) {
63031
+ const slug2 = typeof body.slug === "string" ? body.slug : "";
63032
+ if (slug2.startsWith("rost/") && body.local_only !== true) {
63033
+ try {
63034
+ return await executeSkillCommand(context, "skill.install_local", { ...body, local_only: true }, options);
63035
+ } catch (error51) {
63036
+ if (!isCommandConflict(error51)) {
63037
+ throw error51;
63038
+ }
63076
63039
  }
63077
63040
  }
63041
+ return executeSkillCommand(context, "skill.install_local", body, options);
63042
+ }
63043
+ async function executeSkillCommand(context, commandId, body, options = {}) {
63078
63044
  try {
63079
- return verifyFileChecksums(readEach(), manifest);
63045
+ const output = await context.client.execute(commandId, body, options);
63046
+ return output.output;
63080
63047
  } catch (error51) {
63081
- return { ok: false, reason: error51 instanceof Error ? error51.message : "an installed file is unreadable" };
63048
+ const pending = context.io.interactive === true ? pendingConfirmationFromError(error51) : null;
63049
+ if (pending && pending.commandId === commandId) {
63050
+ renderPendingConfirmationGate2(context.io, pending);
63051
+ context.io.stderr.write(`Approving ${pending.commandId} confirmation ${pending.confirmationId} as the interactive CLI user.
63052
+ `);
63053
+ const approved = await context.client.execute("confirmation.approve", { confirmation_id: pending.confirmationId, reviewed: true });
63054
+ return asRecord4(approved.output).output ?? approved.output;
63055
+ }
63056
+ throw error51;
63082
63057
  }
63083
63058
  }
63084
- function readMarker(dir) {
63085
- try {
63086
- const content = readFileSync4(path5.join(dir, SKILL_MARKER_FILE), "utf8");
63087
- const parsed = JSON.parse(content);
63088
- if (typeof parsed === "object" && parsed !== null) {
63089
- return parsed;
63059
+ function applyLocalSkillChanges(output, client, env, options) {
63060
+ const root = installRoot2(client, env);
63061
+ const changes = writeInstalls(output, root);
63062
+ if (options.removeRevoked) {
63063
+ changes.push(...removeRevokedInstalls(output, root));
63064
+ }
63065
+ return changes;
63066
+ }
63067
+ function writeInstalls(output, root) {
63068
+ const installs = asArray2(asRecord4(output).installs);
63069
+ return installs.map((entry) => {
63070
+ const install = asRecord4(entry);
63071
+ const slug2 = safeLocalSlug(String(install.slug ?? ""));
63072
+ const metadata = asRecord4(install.metadata);
63073
+ const files = asArray2(install.files).map(asRecord4);
63074
+ const dir = path5.join(root, slug2);
63075
+ const metadataPath = path5.join(dir, ".rost-skill.json");
63076
+ const expectedHash = String(metadata.content_sha256 ?? "");
63077
+ if (existingHash(metadataPath) === expectedHash) {
63078
+ return { slug: slug2, dir, status: "unchanged", files: files.length };
63090
63079
  }
63091
- } catch {
63080
+ rmSync2(dir, { recursive: true, force: true });
63081
+ mkdirSync2(dir, { recursive: true });
63082
+ for (const file2 of files) {
63083
+ const relativePath = safeRelativePath2(String(file2.path ?? ""));
63084
+ const content = String(file2.content ?? "");
63085
+ const target = path5.join(dir, relativePath);
63086
+ mkdirSync2(path5.dirname(target), { recursive: true });
63087
+ writeFileSync2(target, content, "utf8");
63088
+ }
63089
+ writeFileSync2(metadataPath, `${JSON.stringify(metadata, null, 2)}
63090
+ `, "utf8");
63091
+ return { slug: slug2, dir, status: "installed", files: files.length };
63092
+ });
63093
+ }
63094
+ function removeRevokedInstalls(output, root) {
63095
+ const installs = new Set(asArray2(asRecord4(output).installs).map((entry) => {
63096
+ const slug2 = asRecord4(entry).slug;
63097
+ return typeof slug2 === "string" ? slug2 : "";
63098
+ }));
63099
+ const revoked = asArray2(asRecord4(output).revoked);
63100
+ const changes = [];
63101
+ for (const entry of revoked) {
63102
+ const slug2 = safeLocalSlug(String(asRecord4(entry).slug ?? ""));
63103
+ if (installs.has(slug2)) {
63104
+ continue;
63105
+ }
63106
+ const dir = path5.join(root, slug2);
63107
+ if (!existsSync(dir)) {
63108
+ continue;
63109
+ }
63110
+ rmSync2(dir, { recursive: true, force: true });
63111
+ changes.push({ slug: slug2, dir, status: "removed", files: 0 });
63092
63112
  }
63093
- return null;
63113
+ return changes;
63094
63114
  }
63095
- function resolveClaudeSkillInstallRoot(env) {
63096
- const configDir = requireNonBlank("CLAUDE_CONFIG_DIR", env.CLAUDE_CONFIG_DIR);
63097
- const legacyHome = requireNonBlank("CLAUDE_HOME", env.CLAUDE_HOME);
63098
- if (configDir !== null) {
63099
- const disagrees = legacyHome !== null && path5.resolve(legacyHome) !== path5.resolve(configDir);
63100
- return {
63101
- root: path5.join(configDir, "skills"),
63102
- legacyNotice: disagrees ? `NOTICE: CLAUDE_HOME (${legacyHome}) is ignored; Claude Code reads CLAUDE_CONFIG_DIR (${configDir}).` : null
63103
- };
63115
+ function installRoot2(client, env) {
63116
+ const override = env.ROST_SKILL_INSTALL_ROOT;
63117
+ if (override && override.length > 0) {
63118
+ return path5.resolve(override, client);
63104
63119
  }
63105
- if (legacyHome !== null) {
63106
- return {
63107
- root: path5.join(legacyHome, "skills"),
63108
- legacyNotice: "NOTICE: CLAUDE_HOME is deprecated and Claude Code does not read it; set CLAUDE_CONFIG_DIR instead."
63109
- };
63120
+ if (client === "codex") {
63121
+ try {
63122
+ return path5.join(resolveCodexSkillsRoot(env), "rost");
63123
+ } catch (error51) {
63124
+ if (error51 instanceof UsageError2) {
63125
+ throw new UsageError3(error51.message);
63126
+ }
63127
+ throw error51;
63128
+ }
63129
+ }
63130
+ if (client === "claude-code") {
63131
+ return path5.join(env.CLAUDE_HOME ?? path5.join(os2.homedir(), ".claude"), "skills", "rost");
63110
63132
  }
63111
- const home = requireNonBlank("HOME", env.HOME) ?? os2.homedir();
63112
- return { root: path5.join(home, ".claude", "skills"), legacyNotice: null };
63133
+ return path5.join(os2.homedir(), ".cursor", "skills", "rost");
63113
63134
  }
63114
- function requireNonBlank(name, value) {
63115
- if (value === void 0 || value === "") {
63116
- return null;
63135
+ function safeRelativePath2(value) {
63136
+ const normalized = value.trim().replaceAll("\\", "/").replace(/^\.\/+/, "");
63137
+ if (!normalized || normalized.startsWith("/") || normalized.includes("//") || normalized.split("/").includes("..")) {
63138
+ throw new UsageError3("Server returned an invalid skill file path.");
63117
63139
  }
63118
- if (value.trim().length === 0) {
63119
- throw new UsageError3(`${name} is set to a blank value; unset it or give it a real path.`);
63140
+ return normalized;
63141
+ }
63142
+ function safeLocalSlug(value) {
63143
+ if (!/^[a-z0-9][a-z0-9-]{1,80}$/.test(value)) {
63144
+ throw new UsageError3("Server returned an invalid skill slug.");
63120
63145
  }
63121
63146
  return value;
63122
63147
  }
63123
- function installRoot2(client, env) {
63124
- const override = requireNonBlank("ROST_SKILL_INSTALL_ROOT", env.ROST_SKILL_INSTALL_ROOT);
63125
- if (override !== null) {
63126
- return { root: path5.resolve(override, client), legacyNotice: null };
63127
- }
63128
- if (client === "codex") {
63129
- return { root: path5.join(env.CODEX_HOME ?? path5.join(os2.homedir(), ".codex"), "skills"), legacyNotice: null };
63148
+ function existingHash(metadataPath) {
63149
+ if (!existsSync(metadataPath)) {
63150
+ return null;
63130
63151
  }
63131
- if (client === "claude-code") {
63132
- return resolveClaudeSkillInstallRoot(env);
63152
+ try {
63153
+ const parsed = JSON.parse(readFileSync4(metadataPath, "utf8"));
63154
+ const hash2 = asRecord4(parsed).content_sha256;
63155
+ return typeof hash2 === "string" ? hash2 : null;
63156
+ } catch {
63157
+ return null;
63133
63158
  }
63134
- return { root: path5.join(os2.homedir(), ".cursor", "skills"), legacyNotice: null };
63135
63159
  }
63136
- function parseFlags3(args) {
63160
+ function parseFlags3(args, booleanFlags = /* @__PURE__ */ new Set()) {
63137
63161
  const values = /* @__PURE__ */ new Map();
63138
63162
  const flags = /* @__PURE__ */ new Set();
63139
63163
  let json2 = false;
@@ -63150,22 +63174,41 @@ function parseFlags3(args) {
63150
63174
  json2 = true;
63151
63175
  continue;
63152
63176
  }
63153
- if (key === "client" || key === "tag") {
63154
- const value = args[index + 1];
63155
- if (value === void 0 || value.startsWith("--")) {
63156
- throw new UsageError3(`--${key} requires a value.`);
63157
- }
63158
- values.set(key, value);
63159
- index += 1;
63177
+ if (booleanFlags.has(key)) {
63178
+ flags.add(key);
63179
+ continue;
63180
+ }
63181
+ const value = args[index + 1];
63182
+ if (value === void 0 || value.startsWith("--")) {
63183
+ flags.add(key);
63160
63184
  continue;
63161
63185
  }
63162
- throw new UsageError3(`Unexpected flag: --${key}`);
63186
+ values.set(key, value);
63187
+ index += 1;
63163
63188
  }
63164
63189
  return { json: json2, values, flags };
63165
63190
  }
63191
+ function requireValue3(parsed, key) {
63192
+ const value = optionalValue3(parsed, key);
63193
+ if (value === void 0) {
63194
+ throw new UsageError3(`Missing required --${key} value.`);
63195
+ }
63196
+ return value;
63197
+ }
63166
63198
  function optionalValue3(parsed, key) {
63167
63199
  const value = parsed.values.get(key);
63168
- return value !== void 0 && value.length > 0 ? value : void 0;
63200
+ return value && value.length > 0 ? value : void 0;
63201
+ }
63202
+ function optionalNumber2(parsed, key) {
63203
+ const value = optionalValue3(parsed, key);
63204
+ if (value === void 0) {
63205
+ return void 0;
63206
+ }
63207
+ const parsedNumber = Number(value);
63208
+ if (!Number.isSafeInteger(parsedNumber) || parsedNumber <= 0) {
63209
+ throw new UsageError3(`--${key} must be a positive integer.`);
63210
+ }
63211
+ return parsedNumber;
63169
63212
  }
63170
63213
  function parseClient3(value) {
63171
63214
  if (!CLIENTS2.has(value)) {
@@ -63173,6 +63216,118 @@ function parseClient3(value) {
63173
63216
  }
63174
63217
  return value;
63175
63218
  }
63219
+ function isCommandConflict(error51) {
63220
+ return error51 instanceof CommandClientError && error51.status === 409;
63221
+ }
63222
+ function withDefined(base, extra) {
63223
+ const next = { ...base };
63224
+ for (const [key, value] of Object.entries(extra)) {
63225
+ if (value !== void 0) {
63226
+ next[key] = value;
63227
+ }
63228
+ }
63229
+ return next;
63230
+ }
63231
+ function asRecord4(value) {
63232
+ return typeof value === "object" && value !== null ? value : {};
63233
+ }
63234
+ function asArray2(value) {
63235
+ return Array.isArray(value) ? value : [];
63236
+ }
63237
+ function formatCatalog(output) {
63238
+ const entries = asArray2(asRecord4(output).catalog);
63239
+ if (entries.length === 0) {
63240
+ return "No ROST catalog skills are available for this tenant.";
63241
+ }
63242
+ return entries.map((entry) => {
63243
+ const record2 = asRecord4(entry);
63244
+ return `${record2.catalog_slug} [${record2.tier}/${record2.entitlement_status}] ${record2.name}`;
63245
+ }).join("\n");
63246
+ }
63247
+ function formatSkillDetail(output) {
63248
+ const skill = asRecord4(asRecord4(output).skill);
63249
+ return `${skill.slug} v${skill.latest_version ?? "\u2014"} [${skill.status}] ${skill.name}
63250
+ ${skill.description}`;
63251
+ }
63252
+ function formatEnable(output) {
63253
+ const skill = asRecord4(asRecord4(output).skill);
63254
+ const catalog = asRecord4(asRecord4(output).catalog);
63255
+ return `Enabled ${catalog.catalog_slug} as ${skill.slug} v${skill.version} (${skill.skill_version_id})`;
63256
+ }
63257
+ function formatAssignment(output) {
63258
+ const assignment = asRecord4(asRecord4(output).assignment);
63259
+ return `${assignment.status} ${assignment.slug} for seat ${assignment.seat_id}; sync: ${String(asRecord4(output).local_sync_next_step)}`;
63260
+ }
63261
+ function formatRevoke(output) {
63262
+ const record2 = asRecord4(output);
63263
+ return `Revoked ${record2.slug} for seat ${record2.seat_id}; sync: ${record2.local_sync_next_step}`;
63264
+ }
63265
+ function formatPublish(output) {
63266
+ const skill = asRecord4(asRecord4(output).skill);
63267
+ return `Published ${skill.slug} v${skill.version} (${skill.skill_version_id})`;
63268
+ }
63269
+ function formatLocalSummary(summary) {
63270
+ if (summary.length === 0) {
63271
+ return "No skills to install.";
63272
+ }
63273
+ return summary.map((entry) => `${entry.status} ${entry.slug} -> ${entry.dir} (${entry.files} files)`).join("\n");
63274
+ }
63275
+ function printCommandError2(io, error51) {
63276
+ if (error51.response && !error51.response.ok) {
63277
+ const payload = {
63278
+ code: error51.response.error.code,
63279
+ message: error51.response.error.message,
63280
+ ...error51.response.error.details === void 0 ? {} : { details: error51.response.error.details },
63281
+ ...error51.response.error.help === void 0 ? {} : { help: error51.response.error.help },
63282
+ ...error51.response.requestId === void 0 ? {} : { requestId: error51.response.requestId }
63283
+ };
63284
+ io.stderr.write(`${redactForLog(payload)}
63285
+ `);
63286
+ if (error51.response.error.help !== void 0) {
63287
+ io.stderr.write(`\u2192 try: ${redactForLog(error51.response.error.help)}
63288
+ `);
63289
+ }
63290
+ return error51.status === 409 ? 3 : error51.status >= 400 && error51.status < 500 ? 1 : 2;
63291
+ }
63292
+ io.stderr.write(`${redactForLog(error51.message)}
63293
+ `);
63294
+ return error51.status >= 400 && error51.status < 500 ? 1 : 2;
63295
+ }
63296
+ function pendingConfirmationFromError(error51) {
63297
+ if (!(error51 instanceof CommandClientError) || !error51.response || error51.response.ok || error51.response.error.code !== "COMMAND_CONFIRMATION_REQUIRED") {
63298
+ return null;
63299
+ }
63300
+ const details = asRecord4(error51.response.error.details);
63301
+ const confirmationId = details.confirmationId;
63302
+ const commandId = details.commandId;
63303
+ if (typeof confirmationId !== "string" || confirmationId.length === 0 || typeof commandId !== "string" || commandId.length === 0) {
63304
+ return null;
63305
+ }
63306
+ const summary = typeof details.summary === "string" ? details.summary : void 0;
63307
+ const riskLevel = typeof details.riskLevel === "string" ? details.riskLevel : void 0;
63308
+ return {
63309
+ confirmationId,
63310
+ commandId,
63311
+ ...summary === void 0 ? {} : { summary },
63312
+ ...riskLevel === void 0 ? {} : { riskLevel },
63313
+ ...details.diff === void 0 ? {} : { diff: details.diff }
63314
+ };
63315
+ }
63316
+ function renderPendingConfirmationGate2(io, pending) {
63317
+ if (pending.summary !== void 0) {
63318
+ io.stderr.write(`${redactForLog(pending.summary)}
63319
+ `);
63320
+ }
63321
+ if (pending.riskLevel !== void 0) {
63322
+ io.stderr.write(`Risk level: ${pending.riskLevel}
63323
+ `);
63324
+ }
63325
+ if (pending.diff !== void 0) {
63326
+ io.stderr.write(`Diff:
63327
+ ${redactForLog(pending.diff)}
63328
+ `);
63329
+ }
63330
+ }
63176
63331
 
63177
63332
  // src/doctor.ts
63178
63333
  import { execFile as execFileCallback3 } from "node:child_process";
@@ -63575,7 +63730,7 @@ async function sweepStaleRunnerTempFiles(dir, options = {}) {
63575
63730
  // src/runner-service.ts
63576
63731
  import { execFile as execFileCallback2 } from "node:child_process";
63577
63732
  import { realpathSync as realpathSync2 } from "node:fs";
63578
- import { access, mkdir as mkdir4, readFile as readFile6, readdir as readdir2, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
63733
+ import { access, mkdir as mkdir4, readFile as readFile7, readdir as readdir2, rm as rm4, writeFile as writeFile4 } from "node:fs/promises";
63579
63734
  import { homedir as homedir4 } from "node:os";
63580
63735
  import path7 from "node:path";
63581
63736
  import { promisify as promisify2 } from "node:util";
@@ -63989,7 +64144,7 @@ async function inspectRunnerService(options) {
63989
64144
  const paths = runnerServicePaths(options.homeDir, safeName);
63990
64145
  const label = serviceLabel(safeName);
63991
64146
  const installed = await fileExists(paths.launchAgentPath);
63992
- const plist = installed ? await readFile6(paths.launchAgentPath, "utf8").catch(() => null) : null;
64147
+ const plist = installed ? await readFile7(paths.launchAgentPath, "utf8").catch(() => null) : null;
63993
64148
  const programArgs = plist ? extractProgramArguments(plist) : [];
63994
64149
  const appUrl2 = plist ? extractPlistEnvironmentValue(plist, `${cliBrand.envPrefix}_APP_URL`) : null;
63995
64150
  const urlPolicyEnv = extractPlistEnvironmentValue(plist ?? "", allowInsecureAppUrlEnv) === "1" ? "set" : "unset";
@@ -64257,7 +64412,7 @@ async function fileExists(file2) {
64257
64412
  }
64258
64413
  async function readTail(file2, maxBytes) {
64259
64414
  try {
64260
- const content = await readFile6(file2, "utf8");
64415
+ const content = await readFile7(file2, "utf8");
64261
64416
  return content.length > maxBytes ? content.slice(content.length - maxBytes) : content;
64262
64417
  } catch {
64263
64418
  return null;
@@ -64405,7 +64560,7 @@ function parseSemver2(value) {
64405
64560
  import { execFile as execFileCallback5, spawn } from "node:child_process";
64406
64561
  import { createHash as createHash4, randomUUID } from "node:crypto";
64407
64562
  import { constants as fsConstants4 } from "node:fs";
64408
- import { access as access3, chmod as chmod2, lstat as lstat2, mkdir as mkdir6, open as open2, readFile as readFile8, rename as rename2, rm as rm6, statfs, writeFile as writeFile6 } from "node:fs/promises";
64563
+ import { access as access3, chmod as chmod2, lstat as lstat2, mkdir as mkdir6, open as open2, readFile as readFile9, rename as rename2, rm as rm6, statfs, writeFile as writeFile6 } from "node:fs/promises";
64409
64564
  import { cpus, homedir as homedir5, hostname as hostname3, platform, totalmem, tmpdir as tmpdir2 } from "node:os";
64410
64565
  import path10 from "node:path";
64411
64566
  import { setTimeout as sleep3 } from "node:timers/promises";
@@ -64415,7 +64570,7 @@ import { promisify as promisify5 } from "node:util";
64415
64570
  import { execFile as execFileCallback4 } from "node:child_process";
64416
64571
  import { createHash as createHash3, randomBytes } from "node:crypto";
64417
64572
  import { constants as fsConstants3 } from "node:fs";
64418
- import { chmod, link, lstat, mkdir as mkdir5, mkdtemp, open, readdir as readdir3, readFile as readFile7, realpath, rename, rm as rm5, stat as stat4, writeFile as writeFile5 } from "node:fs/promises";
64573
+ import { chmod, link, lstat, mkdir as mkdir5, mkdtemp, open, readdir as readdir3, readFile as readFile8, realpath, rename, rm as rm5, stat as stat4, writeFile as writeFile5 } from "node:fs/promises";
64419
64574
  import os3 from "node:os";
64420
64575
  import path8 from "node:path";
64421
64576
  import { setTimeout as sleep2 } from "node:timers/promises";
@@ -64527,7 +64682,7 @@ async function provisionIsolatedClaudeConfigDir(base, configDir, onRepair) {
64527
64682
  const source = resolveSourceClaudeConfigDir(base);
64528
64683
  for (const file2 of CLAUDE_CONFIG_AUTH_FILES) {
64529
64684
  try {
64530
- const data = await readFile7(path8.join(source, file2));
64685
+ const data = await readFile8(path8.join(source, file2));
64531
64686
  await writeFileAtomicNoFollow(path8.join(configDir, file2), data, { mode: 384 });
64532
64687
  } catch {
64533
64688
  }
@@ -64730,7 +64885,7 @@ trust_level = "untrusted"
64730
64885
  await rm5(dest, { recursive: true, force: true });
64731
64886
  }
64732
64887
  const source = resolveSourceCodexHomeDir(base);
64733
- const data = await readFile7(path8.join(source, CODEX_HOME_AUTH_FILE)).catch(() => null);
64888
+ const data = await readFile8(path8.join(source, CODEX_HOME_AUTH_FILE)).catch(() => null);
64734
64889
  if (data === null) {
64735
64890
  return homeDir;
64736
64891
  }
@@ -64813,7 +64968,7 @@ async function acquireCodexHomeLock(homeDir, options = {}) {
64813
64968
  ino = String((await handle.stat()).ino);
64814
64969
  } catch (error51) {
64815
64970
  await handle.close().catch(() => void 0);
64816
- const stillOurs = await readFile7(lockPath, "utf8").then((raw) => JSON.parse(raw).nonce === nonce).catch(() => false);
64971
+ const stillOurs = await readFile8(lockPath, "utf8").then((raw) => JSON.parse(raw).nonce === nonce).catch(() => false);
64817
64972
  if (stillOurs) {
64818
64973
  await rm5(lockPath, { force: true }).catch(() => void 0);
64819
64974
  }
@@ -64856,7 +65011,7 @@ async function breakCodexHomeLock(lockPath, expected) {
64856
65011
  }
64857
65012
  let raw;
64858
65013
  try {
64859
- raw = await readFile7(lockPath, "utf8");
65014
+ raw = await readFile8(lockPath, "utf8");
64860
65015
  } catch {
64861
65016
  return;
64862
65017
  }
@@ -64873,7 +65028,7 @@ async function judgeCodexHomeLock(lockPath, staleMs) {
64873
65028
  }
64874
65029
  let raw;
64875
65030
  try {
64876
- raw = await readFile7(lockPath, "utf8");
65031
+ raw = await readFile8(lockPath, "utf8");
64877
65032
  } catch (error51) {
64878
65033
  if (error51.code === "ENOENT") {
64879
65034
  return { stale: false, identity: null };
@@ -66436,7 +66591,7 @@ async function migrateOneLegacyCodexHome(legacyHome, newHome, io) {
66436
66591
  }
66437
66592
  async function loadState(filePath, expectedTenantId) {
66438
66593
  try {
66439
- const parsed = JSON.parse(await readFile8(filePath, "utf8"));
66594
+ const parsed = JSON.parse(await readFile9(filePath, "utf8"));
66440
66595
  if (typeof parsed.runner_id === "string" && typeof parsed.runner_secret === "string") {
66441
66596
  if (expectedTenantId && parsed.tenant_id !== expectedTenantId) {
66442
66597
  return null;
@@ -68259,7 +68414,7 @@ async function reconcileOrphanedInFlightWorkOrders(deps) {
68259
68414
  const transcriptPath = await findClaudeTranscriptPath(entry.claude_config_dir, entry.claude_session_id);
68260
68415
  if (transcriptPath) {
68261
68416
  try {
68262
- const content = await readFile8(transcriptPath, "utf8");
68417
+ const content = await readFile9(transcriptPath, "utf8");
68263
68418
  verdict = classifyClaudeTranscript(content);
68264
68419
  } catch {
68265
68420
  verdict = { kind: "missing" };