@rosthq/cli 0.7.135 → 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
 
@@ -61863,567 +61863,122 @@ function ensureTrailingSlash2(value) {
61863
61863
  }
61864
61864
 
61865
61865
  // src/skills.ts
61866
- 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";
61867
61872
  import os from "node:os";
61868
61873
  import path4 from "node:path";
61869
- 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"
61870
61882
  };
61871
- var CLIENTS = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
61872
- function skillsUsage(binName = cliBrand.binName) {
61873
- return [
61874
- `${binName} skills list [--json]`,
61875
- `${binName} skills catalog [--json]`,
61876
- `${binName} skills get --slug <slug> [--json]`,
61877
- `${binName} skills file --slug <slug> --path SKILL.md`,
61878
- `${binName} skills enable rost/<slug> [--json]`,
61879
- `${binName} skills install <slug-or-rost/slug> [--seat-id <id>] [--client claude-code|codex|cursor] [--version <n>] [--local-only] [--json]`,
61880
- `${binName} skills install-implementation [--client claude-code|codex|cursor] [--tag <tag>] [--json]`,
61881
- `${binName} skills sync --seat-id <id> [--client claude-code|codex|cursor] [--json]`,
61882
- `${binName} skills assign --seat-id <id> (--skill <slug>|--slug <slug>|--skill-version-id <id>) [--approve] [--allow-blocked-proposal] [--json]`,
61883
- `${binName} skills revoke (--assignment-id <id>|--seat-id <id> --skill <slug>) [--json]`,
61884
- `${binName} skills publish (--slug <slug>|--skill-version-id <id>) [--allow-warnings] [--json]`
61885
- ].join("\n");
61886
- }
61887
- async function runSkills(context, args, env = process.env) {
61888
- try {
61889
- const [action, ...rest] = args;
61890
- if (action === "help" || action === "--help" || action === "-h" || action === void 0) {
61891
- context.io.stdout.write(`${skillsUsage(context.binName)}
61892
- `);
61893
- return action === void 0 ? 1 : 0;
61894
- }
61895
- if (action === "list") {
61896
- return context.run(context.client, "skill.list", {}, void 0);
61897
- }
61898
- if (action === "catalog") {
61899
- const parsed = parseFlags2(rest, /* @__PURE__ */ new Set(["include-unentitled"]));
61900
- return context.run(context.client, "skill.catalog", {
61901
- ...parsed.flags.has("include-unentitled") ? { include_unentitled: true } : {}
61902
- }, parsed.json ? void 0 : formatCatalog);
61903
- }
61904
- if (action === "get") {
61905
- const parsed = parseFlags2(rest);
61906
- return context.run(context.client, "skill.get", { slug: requireValue3(parsed, "slug") }, parsed.json ? void 0 : formatSkillDetail);
61907
- }
61908
- if (action === "file") {
61909
- const parsed = parseFlags2(rest);
61910
- return context.run(context.client, "skill.file.get", {
61911
- slug: requireValue3(parsed, "slug"),
61912
- path: requireValue3(parsed, "path")
61913
- }, (output) => {
61914
- const content = asRecord4(output).content;
61915
- return typeof content === "string" ? content : JSON.stringify(output, null, 2);
61916
- });
61917
- }
61918
- if (action === "enable") {
61919
- const slug2 = rest[0];
61920
- if (!slug2 || slug2.startsWith("--")) {
61921
- throw new UsageError2(`Usage: ${context.binName} skills enable rost/<slug> [--json]`);
61922
- }
61923
- const parsed = parseFlags2(rest.slice(1));
61924
- return context.run(context.client, "skill.enable_catalog", { slug: slug2 }, parsed.json ? void 0 : formatEnable);
61925
- }
61926
- if (action === "install") {
61927
- return installOne(context, rest, env);
61928
- }
61929
- if (action === "sync") {
61930
- return syncAssigned(context, rest, env);
61931
- }
61932
- if (action === "assign") {
61933
- const parsed = parseFlags2(rest, /* @__PURE__ */ new Set(["approve", "allow-blocked-proposal"]));
61934
- const body = withDefined({
61935
- seat_id: requireValue3(parsed, "seat-id"),
61936
- status: parsed.flags.has("approve") ? "approved" : "proposed",
61937
- rationale: optionalValue2(parsed, "rationale") ?? "Skill selected for this Seat.",
61938
- allow_blocked_proposal: parsed.flags.has("allow-blocked-proposal")
61939
- }, {
61940
- slug: optionalValue2(parsed, "skill") ?? optionalValue2(parsed, "slug"),
61941
- skill_version_id: optionalValue2(parsed, "skill-version-id")
61942
- });
61943
- return context.run(context.client, "skill.assign_to_seat", body, parsed.json ? void 0 : formatAssignment, { targetSeatId: body.seat_id });
61944
- }
61945
- if (action === "revoke") {
61946
- const parsed = parseFlags2(rest);
61947
- const seatId = optionalValue2(parsed, "seat-id");
61948
- const body = withDefined({}, {
61949
- assignment_id: optionalValue2(parsed, "assignment-id"),
61950
- seat_id: seatId,
61951
- slug: optionalValue2(parsed, "skill") ?? optionalValue2(parsed, "slug"),
61952
- skill_version_id: optionalValue2(parsed, "skill-version-id"),
61953
- rationale: optionalValue2(parsed, "rationale")
61954
- });
61955
- return context.run(context.client, "skill.revoke_from_seat", body, parsed.json ? void 0 : formatRevoke, seatId ? { targetSeatId: seatId } : {});
61956
- }
61957
- if (action === "publish") {
61958
- const parsed = parseFlags2(rest, /* @__PURE__ */ new Set(["allow-warnings"]));
61959
- const body = withDefined({
61960
- allow_warnings: parsed.flags.has("allow-warnings")
61961
- }, {
61962
- slug: optionalValue2(parsed, "slug"),
61963
- skill_version_id: optionalValue2(parsed, "skill-version-id")
61964
- });
61965
- return context.run(context.client, "skill.publish", body, parsed.json ? void 0 : formatPublish);
61966
- }
61967
- throw new UsageError2(`Unknown skills action: ${action}`);
61968
- } catch (error51) {
61969
- if (error51 instanceof UsageError2) {
61970
- context.io.stderr.write(`${error51.message}
61971
- `);
61972
- return 1;
61973
- }
61974
- if (error51 instanceof CommandClientError) {
61975
- return printCommandError2(context.io, error51);
61976
- }
61977
- 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";
61978
61891
  }
61892
+ };
61893
+ function terminalSafe(value) {
61894
+ return value.replace(/[\x00-\x1f\x7f-\x9f]/g, "?");
61979
61895
  }
61980
- async function installOne(context, args, env) {
61981
- const slug2 = args[0];
61982
- if (!slug2 || slug2.startsWith("--")) {
61983
- 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");
61984
61929
  }
61985
- const parsed = parseFlags2(args.slice(1), /* @__PURE__ */ new Set(["local-only"]));
61986
- const client = parseClient2(optionalValue2(parsed, "client") ?? "codex");
61987
- const seatId = optionalValue2(parsed, "seat-id");
61988
- const body = withDefined({
61989
- slug: slug2,
61990
- client,
61991
- local_only: parsed.flags.has("local-only")
61992
- }, {
61993
- seat_id: seatId,
61994
- version: optionalNumber2(parsed, "version")
61995
- });
61996
- const options = seatId ? { targetSeatId: seatId } : {};
61997
- const output = await executeInstallCommand(context, body, options);
61998
- const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: false });
61999
- const outputRecord = asRecord4(output);
62000
- context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
62001
- ` : `${formatLocalSummary(summary)}
62002
- `);
62003
- 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 };
62004
61936
  }
62005
- async function syncAssigned(context, args, env) {
62006
- const parsed = parseFlags2(args);
62007
- const seatId = requireValue3(parsed, "seat-id");
62008
- const client = parseClient2(optionalValue2(parsed, "client") ?? "codex");
62009
- const output = await executeSkillCommand(context, "skill.sync_local", {
62010
- seat_id: seatId,
62011
- client
62012
- }, { targetSeatId: seatId });
62013
- const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: true });
62014
- const outputRecord = asRecord4(output);
62015
- context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
62016
- ` : `${formatLocalSummary(summary)}
62017
- `);
62018
- return 0;
61937
+ function makeSshString(value) {
61938
+ const length = Buffer.alloc(4);
61939
+ length.writeUInt32BE(value.length);
61940
+ return Buffer.concat([length, value]);
62019
61941
  }
62020
- async function executeInstallCommand(context, body, options) {
62021
- const slug2 = typeof body.slug === "string" ? body.slug : "";
62022
- if (slug2.startsWith("rost/") && body.local_only !== true) {
62023
- try {
62024
- return await executeSkillCommand(context, "skill.install_local", { ...body, local_only: true }, options);
62025
- } catch (error51) {
62026
- if (!isCommandConflict(error51)) {
62027
- throw error51;
62028
- }
62029
- }
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");
62030
61947
  }
62031
- return executeSkillCommand(context, "skill.install_local", body, options);
62032
- }
62033
- async function executeSkillCommand(context, commandId, body, options = {}) {
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");
61952
+ }
61953
+ if (keyType !== "ssh-ed25519") {
61954
+ throw new SkillTrustError(`Unsupported SSH public key type: ${keyType}`);
61955
+ }
61956
+ let blob;
62034
61957
  try {
62035
- const output = await context.client.execute(commandId, body, options);
62036
- return output.output;
62037
- } catch (error51) {
62038
- const pending = context.io.interactive === true ? pendingConfirmationFromError(error51) : null;
62039
- if (pending && pending.commandId === commandId) {
62040
- renderPendingConfirmationGate2(context.io, pending);
62041
- context.io.stderr.write(`Approving ${pending.commandId} confirmation ${pending.confirmationId} as the interactive CLI user.
62042
- `);
62043
- const approved = await context.client.execute("confirmation.approve", { confirmation_id: pending.confirmationId, reviewed: true });
62044
- return asRecord4(approved.output).output ?? approved.output;
62045
- }
62046
- throw error51;
61958
+ blob = Buffer.from(base643, "base64");
61959
+ } catch {
61960
+ throw new SkillTrustError("Invalid base64 in SSH public key");
62047
61961
  }
62048
- }
62049
- function applyLocalSkillChanges(output, client, env, options) {
62050
- const root = installRoot(client, env);
62051
- const changes = writeInstalls(output, root);
62052
- if (options.removeRevoked) {
62053
- changes.push(...removeRevokedInstalls(output, root));
61962
+ let offset = 0;
61963
+ const { value: typeBytes, nextOffset: afterType } = readSshString(blob, offset);
61964
+ offset = afterType;
61965
+ if (typeBytes.toString("utf8") !== "ssh-ed25519") {
61966
+ throw new SkillTrustError("Public key blob key type mismatch");
62054
61967
  }
62055
- return changes;
62056
- }
62057
- function writeInstalls(output, root) {
62058
- const installs = asArray2(asRecord4(output).installs);
62059
- return installs.map((entry) => {
62060
- const install = asRecord4(entry);
62061
- const slug2 = safeLocalSlug(String(install.slug ?? ""));
62062
- const metadata = asRecord4(install.metadata);
62063
- const files = asArray2(install.files).map(asRecord4);
62064
- const dir = path4.join(root, slug2);
62065
- const metadataPath = path4.join(dir, ".rost-skill.json");
62066
- const expectedHash = String(metadata.content_sha256 ?? "");
62067
- if (existingHash(metadataPath) === expectedHash) {
62068
- return { slug: slug2, dir, status: "unchanged", files: files.length };
62069
- }
62070
- rmSync(dir, { recursive: true, force: true });
62071
- mkdirSync(dir, { recursive: true });
62072
- for (const file2 of files) {
62073
- const relativePath = safeRelativePath(String(file2.path ?? ""));
62074
- const content = String(file2.content ?? "");
62075
- const target = path4.join(dir, relativePath);
62076
- mkdirSync(path4.dirname(target), { recursive: true });
62077
- writeFileSync(target, content, "utf8");
62078
- }
62079
- writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}
62080
- `, "utf8");
62081
- return { slug: slug2, dir, status: "installed", files: files.length };
62082
- });
62083
- }
62084
- function removeRevokedInstalls(output, root) {
62085
- const installs = new Set(asArray2(asRecord4(output).installs).map((entry) => {
62086
- const slug2 = asRecord4(entry).slug;
62087
- return typeof slug2 === "string" ? slug2 : "";
62088
- }));
62089
- const revoked = asArray2(asRecord4(output).revoked);
62090
- const changes = [];
62091
- for (const entry of revoked) {
62092
- const slug2 = safeLocalSlug(String(asRecord4(entry).slug ?? ""));
62093
- if (installs.has(slug2)) {
62094
- continue;
62095
- }
62096
- const dir = path4.join(root, slug2);
62097
- if (!existsSync(dir)) {
62098
- continue;
62099
- }
62100
- rmSync(dir, { recursive: true, force: true });
62101
- changes.push({ slug: slug2, dir, status: "removed", files: 0 });
62102
- }
62103
- return changes;
62104
- }
62105
- function installRoot(client, env) {
62106
- const override = env.ROST_SKILL_INSTALL_ROOT;
62107
- if (override && override.length > 0) {
62108
- return path4.resolve(override, client);
62109
- }
62110
- if (client === "codex") {
62111
- return path4.join(env.CODEX_HOME ?? path4.join(os.homedir(), ".codex"), "skills", "rost");
62112
- }
62113
- if (client === "claude-code") {
62114
- return path4.join(env.CLAUDE_HOME ?? path4.join(os.homedir(), ".claude"), "skills", "rost");
62115
- }
62116
- return path4.join(os.homedir(), ".cursor", "skills", "rost");
62117
- }
62118
- function safeRelativePath(value) {
62119
- const normalized = value.trim().replaceAll("\\", "/").replace(/^\.\/+/, "");
62120
- if (!normalized || normalized.startsWith("/") || normalized.includes("//") || normalized.split("/").includes("..")) {
62121
- throw new UsageError2("Server returned an invalid skill file path.");
62122
- }
62123
- return normalized;
62124
- }
62125
- function safeLocalSlug(value) {
62126
- if (!/^[a-z0-9][a-z0-9-]{1,80}$/.test(value)) {
62127
- throw new UsageError2("Server returned an invalid skill slug.");
62128
- }
62129
- return value;
62130
- }
62131
- function existingHash(metadataPath) {
62132
- if (!existsSync(metadataPath)) {
62133
- return null;
62134
- }
62135
- try {
62136
- const parsed = JSON.parse(readFileSync3(metadataPath, "utf8"));
62137
- const hash2 = asRecord4(parsed).content_sha256;
62138
- return typeof hash2 === "string" ? hash2 : null;
62139
- } catch {
62140
- return null;
62141
- }
62142
- }
62143
- function parseFlags2(args, booleanFlags = /* @__PURE__ */ new Set()) {
62144
- const values = /* @__PURE__ */ new Map();
62145
- const flags = /* @__PURE__ */ new Set();
62146
- let json2 = false;
62147
- for (let index = 0; index < args.length; index += 1) {
62148
- const token = args[index];
62149
- if (token === void 0) {
62150
- continue;
62151
- }
62152
- if (!token.startsWith("--")) {
62153
- throw new UsageError2(`Unexpected argument: ${token}`);
62154
- }
62155
- const key = token.slice(2);
62156
- if (key === "json") {
62157
- json2 = true;
62158
- continue;
62159
- }
62160
- if (booleanFlags.has(key)) {
62161
- flags.add(key);
62162
- continue;
62163
- }
62164
- const value = args[index + 1];
62165
- if (value === void 0 || value.startsWith("--")) {
62166
- flags.add(key);
62167
- continue;
62168
- }
62169
- values.set(key, value);
62170
- index += 1;
62171
- }
62172
- return { json: json2, values, flags };
62173
- }
62174
- function requireValue3(parsed, key) {
62175
- const value = optionalValue2(parsed, key);
62176
- if (value === void 0) {
62177
- throw new UsageError2(`Missing required --${key} value.`);
62178
- }
62179
- return value;
62180
- }
62181
- function optionalValue2(parsed, key) {
62182
- const value = parsed.values.get(key);
62183
- return value && value.length > 0 ? value : void 0;
62184
- }
62185
- function optionalNumber2(parsed, key) {
62186
- const value = optionalValue2(parsed, key);
62187
- if (value === void 0) {
62188
- return void 0;
62189
- }
62190
- const parsedNumber = Number(value);
62191
- if (!Number.isSafeInteger(parsedNumber) || parsedNumber <= 0) {
62192
- throw new UsageError2(`--${key} must be a positive integer.`);
62193
- }
62194
- return parsedNumber;
62195
- }
62196
- function parseClient2(value) {
62197
- if (!CLIENTS.has(value)) {
62198
- throw new UsageError2("--client must be claude-code, codex, or cursor.");
62199
- }
62200
- return value;
62201
- }
62202
- function isCommandConflict(error51) {
62203
- return error51 instanceof CommandClientError && error51.status === 409;
62204
- }
62205
- function withDefined(base, extra) {
62206
- const next = { ...base };
62207
- for (const [key, value] of Object.entries(extra)) {
62208
- if (value !== void 0) {
62209
- next[key] = value;
62210
- }
62211
- }
62212
- return next;
62213
- }
62214
- function asRecord4(value) {
62215
- return typeof value === "object" && value !== null ? value : {};
62216
- }
62217
- function asArray2(value) {
62218
- return Array.isArray(value) ? value : [];
62219
- }
62220
- function formatCatalog(output) {
62221
- const entries = asArray2(asRecord4(output).catalog);
62222
- if (entries.length === 0) {
62223
- return "No ROST catalog skills are available for this tenant.";
62224
- }
62225
- return entries.map((entry) => {
62226
- const record2 = asRecord4(entry);
62227
- return `${record2.catalog_slug} [${record2.tier}/${record2.entitlement_status}] ${record2.name}`;
62228
- }).join("\n");
62229
- }
62230
- function formatSkillDetail(output) {
62231
- const skill = asRecord4(asRecord4(output).skill);
62232
- return `${skill.slug} v${skill.latest_version ?? "\u2014"} [${skill.status}] ${skill.name}
62233
- ${skill.description}`;
62234
- }
62235
- function formatEnable(output) {
62236
- const skill = asRecord4(asRecord4(output).skill);
62237
- const catalog = asRecord4(asRecord4(output).catalog);
62238
- return `Enabled ${catalog.catalog_slug} as ${skill.slug} v${skill.version} (${skill.skill_version_id})`;
62239
- }
62240
- function formatAssignment(output) {
62241
- const assignment = asRecord4(asRecord4(output).assignment);
62242
- return `${assignment.status} ${assignment.slug} for seat ${assignment.seat_id}; sync: ${String(asRecord4(output).local_sync_next_step)}`;
62243
- }
62244
- function formatRevoke(output) {
62245
- const record2 = asRecord4(output);
62246
- return `Revoked ${record2.slug} for seat ${record2.seat_id}; sync: ${record2.local_sync_next_step}`;
62247
- }
62248
- function formatPublish(output) {
62249
- const skill = asRecord4(asRecord4(output).skill);
62250
- return `Published ${skill.slug} v${skill.version} (${skill.skill_version_id})`;
62251
- }
62252
- function formatLocalSummary(summary) {
62253
- if (summary.length === 0) {
62254
- return "No skills to install.";
62255
- }
62256
- return summary.map((entry) => `${entry.status} ${entry.slug} -> ${entry.dir} (${entry.files} files)`).join("\n");
62257
- }
62258
- function printCommandError2(io, error51) {
62259
- if (error51.response && !error51.response.ok) {
62260
- const payload = {
62261
- code: error51.response.error.code,
62262
- message: error51.response.error.message,
62263
- ...error51.response.error.details === void 0 ? {} : { details: error51.response.error.details },
62264
- ...error51.response.error.help === void 0 ? {} : { help: error51.response.error.help },
62265
- ...error51.response.requestId === void 0 ? {} : { requestId: error51.response.requestId }
62266
- };
62267
- io.stderr.write(`${redactForLog(payload)}
62268
- `);
62269
- if (error51.response.error.help !== void 0) {
62270
- io.stderr.write(`\u2192 try: ${redactForLog(error51.response.error.help)}
62271
- `);
62272
- }
62273
- return error51.status === 409 ? 3 : error51.status >= 400 && error51.status < 500 ? 1 : 2;
62274
- }
62275
- io.stderr.write(`${redactForLog(error51.message)}
62276
- `);
62277
- return error51.status >= 400 && error51.status < 500 ? 1 : 2;
62278
- }
62279
- function pendingConfirmationFromError(error51) {
62280
- if (!(error51 instanceof CommandClientError) || !error51.response || error51.response.ok || error51.response.error.code !== "COMMAND_CONFIRMATION_REQUIRED") {
62281
- return null;
62282
- }
62283
- const details = asRecord4(error51.response.error.details);
62284
- const confirmationId = details.confirmationId;
62285
- const commandId = details.commandId;
62286
- if (typeof confirmationId !== "string" || confirmationId.length === 0 || typeof commandId !== "string" || commandId.length === 0) {
62287
- return null;
62288
- }
62289
- const summary = typeof details.summary === "string" ? details.summary : void 0;
62290
- const riskLevel = typeof details.riskLevel === "string" ? details.riskLevel : void 0;
62291
- return {
62292
- confirmationId,
62293
- commandId,
62294
- ...summary === void 0 ? {} : { summary },
62295
- ...riskLevel === void 0 ? {} : { riskLevel },
62296
- ...details.diff === void 0 ? {} : { diff: details.diff }
62297
- };
62298
- }
62299
- function renderPendingConfirmationGate2(io, pending) {
62300
- if (pending.summary !== void 0) {
62301
- io.stderr.write(`${redactForLog(pending.summary)}
62302
- `);
62303
- }
62304
- if (pending.riskLevel !== void 0) {
62305
- io.stderr.write(`Risk level: ${pending.riskLevel}
62306
- `);
62307
- }
62308
- if (pending.diff !== void 0) {
62309
- io.stderr.write(`Diff:
62310
- ${redactForLog(pending.diff)}
62311
- `);
62312
- }
62313
- }
62314
-
62315
- // src/skill-install.ts
62316
- import { lstatSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
62317
- import os2 from "node:os";
62318
- import path5 from "node:path";
62319
-
62320
- // src/skill-trust.ts
62321
- import { createHash as createHash2, createPublicKey, verify } from "node:crypto";
62322
- var TRUSTED_SIGNER_PUBLIC_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOXSsKe/zFjaoAdZdvP/esPoeM1fsCGFmxv0INDlCxaW rost-implementation-skill-release";
62323
- var TRUSTED_SIGNER_FINGERPRINT = "SHA256:ziSyU9vhGi0Q3GaeePB1R6BI3GqXutLhepnF/4Hx4rY";
62324
- var RELEASE_REPOSITORY = {
62325
- owner: "agenticoperatingsystem",
62326
- name: "rost-implementation-skill"
62327
- };
62328
- var DEFAULT_RELEASE_TAG = "v0.2.0";
62329
- var EXPECTED_DEFAULT_MANIFEST_SHA256 = "bbd745e1a625c30773fac5505f32c5152925d2474ac22d2cb4a93aa5a034ca66";
62330
- var SKILL_MANIFEST_PROTOCOL_VERSION = 1;
62331
- var SKILL_INSTALL_DIR_NAME = "rost-implementation";
62332
- var SkillTrustError = class extends Error {
62333
- constructor(message) {
62334
- super(message);
62335
- this.name = "SkillTrustError";
62336
- }
62337
- };
62338
- function terminalSafe(value) {
62339
- return value.replace(/[\x00-\x1f\x7f-\x9f]/g, "?");
62340
- }
62341
- var SUPPORTED_CLIENTS = ["claude-code", "codex", "cursor"];
62342
- var skillManifestSchema = external_exports.object({
62343
- schemaVersion: external_exports.literal(1),
62344
- name: external_exports.literal("rost-implementation-skill"),
62345
- version: external_exports.string(),
62346
- repository: external_exports.object({
62347
- owner: external_exports.string(),
62348
- name: external_exports.string()
62349
- }).strict(),
62350
- protocol: external_exports.object({
62351
- skill: external_exports.number().int(),
62352
- minCli: external_exports.string(),
62353
- maxCli: external_exports.string().nullable()
62354
- }).strict(),
62355
- supportedClients: external_exports.array(external_exports.enum(SUPPORTED_CLIENTS)).min(1),
62356
- doctrine: external_exports.object({
62357
- guide: external_exports.string(),
62358
- version: external_exports.string()
62359
- }).strict(),
62360
- changelogUrl: external_exports.string().url(),
62361
- files: external_exports.array(
62362
- external_exports.object({
62363
- path: external_exports.string(),
62364
- sha256: external_exports.string().regex(/^[a-f0-9]{64}$/)
62365
- }).strict()
62366
- ).min(1)
62367
- }).strict();
62368
- function sha256Hex(content) {
62369
- return createHash2("sha256").update(content).digest("hex");
62370
- }
62371
- function readSshString(buffer, offset) {
62372
- if (offset + 4 > buffer.length) {
62373
- throw new SkillTrustError("Truncated SSH wire string");
62374
- }
62375
- const length = buffer.readUInt32BE(offset);
62376
- offset += 4;
62377
- if (offset + length > buffer.length) {
62378
- throw new SkillTrustError("Truncated SSH wire string payload");
62379
- }
62380
- return { value: buffer.subarray(offset, offset + length), nextOffset: offset + length };
62381
- }
62382
- function makeSshString(value) {
62383
- const length = Buffer.alloc(4);
62384
- length.writeUInt32BE(value.length);
62385
- return Buffer.concat([length, value]);
62386
- }
62387
- function parseEd25519PublicKeyLine(line) {
62388
- const trimmed = line.trim();
62389
- const parts = trimmed.split(/\s+/);
62390
- if (parts.length < 2) {
62391
- throw new SkillTrustError("Malformed SSH public key line");
62392
- }
62393
- const keyType = parts[0];
62394
- const base643 = parts[1];
62395
- if (keyType === void 0 || base643 === void 0) {
62396
- throw new SkillTrustError("Malformed SSH public key line");
62397
- }
62398
- if (keyType !== "ssh-ed25519") {
62399
- throw new SkillTrustError(`Unsupported SSH public key type: ${keyType}`);
62400
- }
62401
- let blob;
62402
- try {
62403
- blob = Buffer.from(base643, "base64");
62404
- } catch {
62405
- throw new SkillTrustError("Invalid base64 in SSH public key");
62406
- }
62407
- let offset = 0;
62408
- const { value: typeBytes, nextOffset: afterType } = readSshString(blob, offset);
62409
- offset = afterType;
62410
- if (typeBytes.toString("utf8") !== "ssh-ed25519") {
62411
- throw new SkillTrustError("Public key blob key type mismatch");
62412
- }
62413
- const { value: keyBytes, nextOffset: afterKey } = readSshString(blob, offset);
62414
- offset = afterKey;
62415
- if (offset !== blob.length) {
62416
- throw new SkillTrustError("Trailing bytes in SSH public key blob");
62417
- }
62418
- if (keyBytes.length !== 32) {
62419
- throw new SkillTrustError(`Invalid ed25519 public key length: ${keyBytes.length}`);
62420
- }
62421
- const spki = Buffer.concat([
62422
- Buffer.from("302a300506032b6570032100", "hex"),
62423
- keyBytes
62424
- ]);
62425
- const key = createPublicKey({ key: spki, format: "der", type: "spki" });
62426
- return { key, blob };
61968
+ const { value: keyBytes, nextOffset: afterKey } = readSshString(blob, offset);
61969
+ offset = afterKey;
61970
+ if (offset !== blob.length) {
61971
+ throw new SkillTrustError("Trailing bytes in SSH public key blob");
61972
+ }
61973
+ if (keyBytes.length !== 32) {
61974
+ throw new SkillTrustError(`Invalid ed25519 public key length: ${keyBytes.length}`);
61975
+ }
61976
+ const spki = Buffer.concat([
61977
+ Buffer.from("302a300506032b6570032100", "hex"),
61978
+ keyBytes
61979
+ ]);
61980
+ const key = createPublicKey({ key: spki, format: "der", type: "spki" });
61981
+ return { key, blob };
62427
61982
  }
62428
61983
  function splitSignedTag(rawTagObject) {
62429
61984
  const marker = "-----BEGIN SSH SIGNATURE-----";
@@ -62535,7 +62090,7 @@ function verifyTagSignature(payload, signatureArmored, trustedPublicKeyLine = TR
62535
62090
  }
62536
62091
  return { ok: true, reason: "Verified" };
62537
62092
  }
62538
- function safeRelativePath2(value) {
62093
+ function safeRelativePath(value) {
62539
62094
  if (value.includes("\\")) {
62540
62095
  throw new SkillTrustError("Manifest file path is not a safe relative path");
62541
62096
  }
@@ -62629,7 +62184,7 @@ function verifyReleaseManifest(manifest, options) {
62629
62184
  }
62630
62185
  let files;
62631
62186
  try {
62632
- files = parsed.files.map((file2) => ({ ...file2, path: safeRelativePath2(file2.path) }));
62187
+ files = parsed.files.map((file2) => ({ ...file2, path: safeRelativePath(file2.path) }));
62633
62188
  } catch (error51) {
62634
62189
  const message = error51 instanceof Error ? error51.message : "Invalid path";
62635
62190
  return { ok: false, reason: message };
@@ -62639,13 +62194,13 @@ function verifyReleaseManifest(manifest, options) {
62639
62194
  function verifyFileChecksums(files, manifest) {
62640
62195
  const manifestMap = /* @__PURE__ */ new Map();
62641
62196
  for (const file2 of manifest.files) {
62642
- manifestMap.set(safeRelativePath2(file2.path), file2.sha256);
62197
+ manifestMap.set(safeRelativePath(file2.path), file2.sha256);
62643
62198
  }
62644
62199
  const seen = /* @__PURE__ */ new Set();
62645
62200
  for (const file2 of files) {
62646
62201
  let normalized;
62647
62202
  try {
62648
- normalized = safeRelativePath2(file2.path);
62203
+ normalized = safeRelativePath(file2.path);
62649
62204
  } catch (error51) {
62650
62205
  const message = error51 instanceof Error ? error51.message : "Invalid path";
62651
62206
  return { ok: false, reason: message };
@@ -62731,13 +62286,14 @@ function verifyReleaseTrust(input) {
62731
62286
  }
62732
62287
 
62733
62288
  // src/skill-install.ts
62734
- var UsageError3 = class extends Error {
62289
+ var UsageError2 = class extends Error {
62735
62290
  };
62291
+ var O_NOFOLLOW_FLAG = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
62736
62292
  function writeErrorLine(io, message) {
62737
62293
  io.stderr.write(`${terminalSafe(message)}
62738
62294
  `);
62739
62295
  }
62740
- var CLIENTS2 = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
62296
+ var CLIENTS = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
62741
62297
  var REPO_PATH = `${RELEASE_REPOSITORY.owner}/${RELEASE_REPOSITORY.name}`;
62742
62298
  var GITHUB_JSON_HEADERS = { "User-Agent": "rost-cli", "Accept": "application/vnd.github+json" };
62743
62299
  var GITHUB_RAW_HEADERS = { "User-Agent": "rost-cli", "Accept": "application/vnd.github.raw+json" };
@@ -62824,11 +62380,12 @@ async function runInstallImplementation(context, args) {
62824
62380
  let client;
62825
62381
  let tag;
62826
62382
  try {
62827
- parsed = parseFlags3(args);
62828
- client = parseClient3(optionalValue3(parsed, "client") ?? "codex");
62829
- 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);
62830
62387
  } catch (error51) {
62831
- if (error51 instanceof UsageError3) {
62388
+ if (error51 instanceof UsageError2) {
62832
62389
  writeErrorLine(context.io, error51.message);
62833
62390
  return 1;
62834
62391
  }
@@ -62991,10 +62548,11 @@ async function installRelease(context, plan, testOverrides = {}) {
62991
62548
  }
62992
62549
  let root;
62993
62550
  let legacyNotice;
62551
+ let viaOverride;
62994
62552
  try {
62995
- ({ root, legacyNotice } = installRoot2(plan.client, context.env));
62553
+ ({ root, legacyNotice, viaOverride } = installRoot(plan.client, context.env));
62996
62554
  } catch (error51) {
62997
- if (error51 instanceof UsageError3) {
62555
+ if (error51 instanceof UsageError2) {
62998
62556
  writeErrorLine(context.io, error51.message);
62999
62557
  return 1;
63000
62558
  }
@@ -63003,7 +62561,7 @@ async function installRelease(context, plan, testOverrides = {}) {
63003
62561
  if (legacyNotice !== null) {
63004
62562
  writeErrorLine(context.io, legacyNotice);
63005
62563
  }
63006
- const dir = path5.join(root, SKILL_INSTALL_DIR_NAME);
62564
+ const dir = path4.join(root, SKILL_INSTALL_DIR_NAME);
63007
62565
  const manifestSha256 = sha256Hex(plan.manifestRaw);
63008
62566
  const existingMarker = readMarker(dir);
63009
62567
  const installedState = existingMarker?.manifestSha256 === manifestSha256 ? verifyInstalledTree(dir, manifest) : null;
@@ -63027,28 +62585,31 @@ async function installRelease(context, plan, testOverrides = {}) {
63027
62585
  })}
63028
62586
  `
63029
62587
  );
62588
+ writeErrorLine(context.io, clientLoadLine(plan.client, dir, viaOverride));
63030
62589
  } else {
63031
62590
  context.io.stdout.write(
63032
- `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)
63033
62592
  `
63034
62593
  );
62594
+ context.io.stdout.write(`${clientLoadLine(plan.client, dir, viaOverride)}
62595
+ `);
63035
62596
  context.io.stdout.write(`${verifiedSignerLine()}
63036
62597
  `);
63037
62598
  }
63038
62599
  return 0;
63039
62600
  }
63040
- rmSync2(dir, { recursive: true, force: true });
63041
- mkdirSync2(dir, { recursive: true });
62601
+ rmSync(dir, { recursive: true, force: true });
62602
+ mkdirSync(dir, { recursive: true });
63042
62603
  for (const file2 of plan.files) {
63043
- const target = path5.join(dir, file2.path);
63044
- const resolvedTarget = path5.resolve(target);
63045
- const resolvedDir = path5.resolve(dir);
63046
- 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}`)) {
63047
62608
  writeErrorLine(context.io, `Refusing to write file outside target directory: ${file2.path}`);
63048
62609
  return 1;
63049
62610
  }
63050
- mkdirSync2(path5.dirname(target), { recursive: true });
63051
- writeFileSync2(target, file2.content, "utf8");
62611
+ mkdirSync(path4.dirname(target), { recursive: true });
62612
+ writeFileSync(target, file2.content, "utf8");
63052
62613
  }
63053
62614
  const writtenState = verifyInstalledTree(dir, manifest);
63054
62615
  if (!writtenState.ok) {
@@ -63071,11 +62632,14 @@ async function installRelease(context, plan, testOverrides = {}) {
63071
62632
  })}
63072
62633
  `
63073
62634
  );
62635
+ writeErrorLine(context.io, clientLoadLine(plan.client, dir, viaOverride));
63074
62636
  } else {
63075
62637
  context.io.stdout.write(
63076
- `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)
63077
62639
  `
63078
62640
  );
62641
+ context.io.stdout.write(`${clientLoadLine(plan.client, dir, viaOverride)}
62642
+ `);
63079
62643
  context.io.stdout.write(`${verifiedSignerLine()}
63080
62644
  `);
63081
62645
  }
@@ -63086,6 +62650,23 @@ function verifiedSignerLine() {
63086
62650
  const fingerprintBase = TRUSTED_SIGNER_FINGERPRINT.replace("SHA256:", "");
63087
62651
  return `verified signed release; signer SHA256:${fingerprintBase.slice(-12)} (last 12 chars)`;
63088
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
+ }
63089
62670
  function writeMarker(dir, input) {
63090
62671
  const marker = {
63091
62672
  source: `github.com/${REPO_PATH}`,
@@ -63097,7 +62678,7 @@ function writeMarker(dir, input) {
63097
62678
  // operator's evidence that the pre-launch re-run actually happened.
63098
62679
  installedAt: (/* @__PURE__ */ new Date()).toISOString()
63099
62680
  };
63100
- 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)}
63101
62682
  `, "utf8");
63102
62683
  }
63103
62684
  function listInstalledFiles(dir) {
@@ -63105,7 +62686,7 @@ function listInstalledFiles(dir) {
63105
62686
  const walk = (current, prefix) => {
63106
62687
  for (const name of readdirSync(current)) {
63107
62688
  const relative = prefix === "" ? name : `${prefix}/${name}`;
63108
- const absolute = path5.join(current, name);
62689
+ const absolute = path4.join(current, name);
63109
62690
  const stats = lstatSync(absolute);
63110
62691
  if (stats.isSymbolicLink() || !stats.isFile() && !stats.isDirectory()) {
63111
62692
  throw new Error(`refusing to verify a symlink or special file: ${relative}`);
@@ -63117,7 +62698,7 @@ function listInstalledFiles(dir) {
63117
62698
  if (relative === SKILL_MARKER_FILE) {
63118
62699
  continue;
63119
62700
  }
63120
- files.push({ path: relative, absolute });
62701
+ files.push({ path: relative, absolute, dev: stats.dev, ino: stats.ino });
63121
62702
  }
63122
62703
  };
63123
62704
  walk(dir, "");
@@ -63138,70 +62719,445 @@ function verifyInstalledTree(dir, manifest) {
63138
62719
  if (present.length !== manifestPaths.size) {
63139
62720
  return { ok: false, reason: "the installed tree is missing files listed in the signed manifest" };
63140
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
+ }
63141
62731
  function* readEach() {
63142
62732
  for (const file2 of present) {
63143
- 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
+ }
63144
63039
  }
63145
63040
  }
63041
+ return executeSkillCommand(context, "skill.install_local", body, options);
63042
+ }
63043
+ async function executeSkillCommand(context, commandId, body, options = {}) {
63146
63044
  try {
63147
- return verifyFileChecksums(readEach(), manifest);
63045
+ const output = await context.client.execute(commandId, body, options);
63046
+ return output.output;
63148
63047
  } catch (error51) {
63149
- 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;
63150
63057
  }
63151
63058
  }
63152
- function readMarker(dir) {
63153
- try {
63154
- const content = readFileSync4(path5.join(dir, SKILL_MARKER_FILE), "utf8");
63155
- const parsed = JSON.parse(content);
63156
- if (typeof parsed === "object" && parsed !== null) {
63157
- 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 };
63158
63079
  }
63159
- } 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 });
63160
63112
  }
63161
- return null;
63113
+ return changes;
63162
63114
  }
63163
- function resolveClaudeSkillInstallRoot(env) {
63164
- const configDir = requireNonBlank("CLAUDE_CONFIG_DIR", env.CLAUDE_CONFIG_DIR);
63165
- const legacyHome = requireNonBlank("CLAUDE_HOME", env.CLAUDE_HOME);
63166
- if (configDir !== null) {
63167
- const disagrees = legacyHome !== null && path5.resolve(legacyHome) !== path5.resolve(configDir);
63168
- return {
63169
- root: path5.join(configDir, "skills"),
63170
- legacyNotice: disagrees ? `NOTICE: CLAUDE_HOME (${legacyHome}) is ignored; Claude Code reads CLAUDE_CONFIG_DIR (${configDir}).` : null
63171
- };
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);
63172
63119
  }
63173
- if (legacyHome !== null) {
63174
- return {
63175
- root: path5.join(legacyHome, "skills"),
63176
- legacyNotice: "NOTICE: CLAUDE_HOME is deprecated and Claude Code does not read it; set CLAUDE_CONFIG_DIR instead."
63177
- };
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");
63178
63132
  }
63179
- const home = requireNonBlank("HOME", env.HOME) ?? os2.homedir();
63180
- return { root: path5.join(home, ".claude", "skills"), legacyNotice: null };
63133
+ return path5.join(os2.homedir(), ".cursor", "skills", "rost");
63181
63134
  }
63182
- function requireNonBlank(name, value) {
63183
- if (value === void 0 || value === "") {
63184
- 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.");
63185
63139
  }
63186
- if (value.trim().length === 0) {
63187
- 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.");
63188
63145
  }
63189
63146
  return value;
63190
63147
  }
63191
- function installRoot2(client, env) {
63192
- const override = requireNonBlank("ROST_SKILL_INSTALL_ROOT", env.ROST_SKILL_INSTALL_ROOT);
63193
- if (override !== null) {
63194
- return { root: path5.resolve(override, client), legacyNotice: null };
63195
- }
63196
- if (client === "codex") {
63197
- 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;
63198
63151
  }
63199
- if (client === "claude-code") {
63200
- 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;
63201
63158
  }
63202
- return { root: path5.join(os2.homedir(), ".cursor", "skills"), legacyNotice: null };
63203
63159
  }
63204
- function parseFlags3(args) {
63160
+ function parseFlags3(args, booleanFlags = /* @__PURE__ */ new Set()) {
63205
63161
  const values = /* @__PURE__ */ new Map();
63206
63162
  const flags = /* @__PURE__ */ new Set();
63207
63163
  let json2 = false;
@@ -63218,22 +63174,41 @@ function parseFlags3(args) {
63218
63174
  json2 = true;
63219
63175
  continue;
63220
63176
  }
63221
- if (key === "client" || key === "tag") {
63222
- const value = args[index + 1];
63223
- if (value === void 0 || value.startsWith("--")) {
63224
- throw new UsageError3(`--${key} requires a value.`);
63225
- }
63226
- values.set(key, value);
63227
- 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);
63228
63184
  continue;
63229
63185
  }
63230
- throw new UsageError3(`Unexpected flag: --${key}`);
63186
+ values.set(key, value);
63187
+ index += 1;
63231
63188
  }
63232
63189
  return { json: json2, values, flags };
63233
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
+ }
63234
63198
  function optionalValue3(parsed, key) {
63235
63199
  const value = parsed.values.get(key);
63236
- 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;
63237
63212
  }
63238
63213
  function parseClient3(value) {
63239
63214
  if (!CLIENTS2.has(value)) {
@@ -63241,6 +63216,118 @@ function parseClient3(value) {
63241
63216
  }
63242
63217
  return value;
63243
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
+ }
63244
63331
 
63245
63332
  // src/doctor.ts
63246
63333
  import { execFile as execFileCallback3 } from "node:child_process";