@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/generator/flags.d.ts +1 -0
- package/dist/generator/flags.d.ts.map +1 -1
- package/dist/generator/verbs.d.ts.map +1 -1
- package/dist/index.js +778 -623
- package/dist/index.js.map +3 -3
- package/dist/skill-install.d.ts +4 -0
- package/dist/skill-install.d.ts.map +1 -1
- package/dist/skills.d.ts.map +1 -1
- package/package.json +1 -1
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.
|
|
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.
|
|
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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
61804
|
-
|
|
61805
|
-
|
|
61806
|
-
|
|
61807
|
-
|
|
61808
|
-
|
|
61809
|
-
|
|
61810
|
-
|
|
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
|
-
|
|
61913
|
-
|
|
61914
|
-
|
|
61915
|
-
|
|
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
|
|
61918
|
-
|
|
61919
|
-
|
|
61920
|
-
|
|
61921
|
-
|
|
61922
|
-
|
|
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
|
-
|
|
61938
|
-
const
|
|
61939
|
-
|
|
61940
|
-
|
|
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
|
-
|
|
61953
|
-
const
|
|
61954
|
-
|
|
61955
|
-
|
|
61956
|
-
|
|
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
|
-
|
|
61964
|
-
|
|
61965
|
-
|
|
61966
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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(
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
62760
|
-
client =
|
|
62761
|
-
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
|
|
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 } =
|
|
62553
|
+
({ root, legacyNotice, viaOverride } = installRoot(plan.client, context.env));
|
|
62928
62554
|
} catch (error51) {
|
|
62929
|
-
if (error51 instanceof
|
|
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 =
|
|
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
|
-
`
|
|
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
|
-
|
|
62973
|
-
|
|
62601
|
+
rmSync(dir, { recursive: true, force: true });
|
|
62602
|
+
mkdirSync(dir, { recursive: true });
|
|
62974
62603
|
for (const file2 of plan.files) {
|
|
62975
|
-
const target =
|
|
62976
|
-
const resolvedTarget =
|
|
62977
|
-
const resolvedDir =
|
|
62978
|
-
if (!resolvedTarget.startsWith(`${resolvedDir}${
|
|
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
|
-
|
|
62983
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
63045
|
+
const output = await context.client.execute(commandId, body, options);
|
|
63046
|
+
return output.output;
|
|
63080
63047
|
} catch (error51) {
|
|
63081
|
-
|
|
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
|
|
63085
|
-
|
|
63086
|
-
|
|
63087
|
-
|
|
63088
|
-
|
|
63089
|
-
|
|
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
|
-
|
|
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
|
|
63113
|
+
return changes;
|
|
63094
63114
|
}
|
|
63095
|
-
function
|
|
63096
|
-
const
|
|
63097
|
-
|
|
63098
|
-
|
|
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 (
|
|
63106
|
-
|
|
63107
|
-
|
|
63108
|
-
|
|
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
|
-
|
|
63112
|
-
return { root: path5.join(home, ".claude", "skills"), legacyNotice: null };
|
|
63133
|
+
return path5.join(os2.homedir(), ".cursor", "skills", "rost");
|
|
63113
63134
|
}
|
|
63114
|
-
function
|
|
63115
|
-
|
|
63116
|
-
|
|
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
|
-
|
|
63119
|
-
|
|
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
|
|
63124
|
-
|
|
63125
|
-
|
|
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
|
-
|
|
63132
|
-
|
|
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
|
|
63154
|
-
|
|
63155
|
-
|
|
63156
|
-
|
|
63157
|
-
|
|
63158
|
-
|
|
63159
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
68417
|
+
const content = await readFile9(transcriptPath, "utf8");
|
|
68263
68418
|
verdict = classifyClaudeTranscript(content);
|
|
68264
68419
|
} catch {
|
|
68265
68420
|
verdict = { kind: "missing" };
|