@rosthq/cli 0.7.135 → 0.7.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +716 -629
- 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
|
|
|
@@ -61863,567 +61863,122 @@ function ensureTrailingSlash2(value) {
|
|
|
61863
61863
|
}
|
|
61864
61864
|
|
|
61865
61865
|
// src/skills.ts
|
|
61866
|
-
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";
|
|
61867
61872
|
import os from "node:os";
|
|
61868
61873
|
import path4 from "node:path";
|
|
61869
|
-
|
|
61874
|
+
|
|
61875
|
+
// src/skill-trust.ts
|
|
61876
|
+
import { createHash as createHash2, createPublicKey, verify } from "node:crypto";
|
|
61877
|
+
var TRUSTED_SIGNER_PUBLIC_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOXSsKe/zFjaoAdZdvP/esPoeM1fsCGFmxv0INDlCxaW rost-implementation-skill-release";
|
|
61878
|
+
var TRUSTED_SIGNER_FINGERPRINT = "SHA256:ziSyU9vhGi0Q3GaeePB1R6BI3GqXutLhepnF/4Hx4rY";
|
|
61879
|
+
var RELEASE_REPOSITORY = {
|
|
61880
|
+
owner: "agenticoperatingsystem",
|
|
61881
|
+
name: "rost-implementation-skill"
|
|
61870
61882
|
};
|
|
61871
|
-
var
|
|
61872
|
-
|
|
61873
|
-
|
|
61874
|
-
|
|
61875
|
-
|
|
61876
|
-
|
|
61877
|
-
|
|
61878
|
-
|
|
61879
|
-
`${binName} skills install <slug-or-rost/slug> [--seat-id <id>] [--client claude-code|codex|cursor] [--version <n>] [--local-only] [--json]`,
|
|
61880
|
-
`${binName} skills install-implementation [--client claude-code|codex|cursor] [--tag <tag>] [--json]`,
|
|
61881
|
-
`${binName} skills sync --seat-id <id> [--client claude-code|codex|cursor] [--json]`,
|
|
61882
|
-
`${binName} skills assign --seat-id <id> (--skill <slug>|--slug <slug>|--skill-version-id <id>) [--approve] [--allow-blocked-proposal] [--json]`,
|
|
61883
|
-
`${binName} skills revoke (--assignment-id <id>|--seat-id <id> --skill <slug>) [--json]`,
|
|
61884
|
-
`${binName} skills publish (--slug <slug>|--skill-version-id <id>) [--allow-warnings] [--json]`
|
|
61885
|
-
].join("\n");
|
|
61886
|
-
}
|
|
61887
|
-
async function runSkills(context, args, env = process.env) {
|
|
61888
|
-
try {
|
|
61889
|
-
const [action, ...rest] = args;
|
|
61890
|
-
if (action === "help" || action === "--help" || action === "-h" || action === void 0) {
|
|
61891
|
-
context.io.stdout.write(`${skillsUsage(context.binName)}
|
|
61892
|
-
`);
|
|
61893
|
-
return action === void 0 ? 1 : 0;
|
|
61894
|
-
}
|
|
61895
|
-
if (action === "list") {
|
|
61896
|
-
return context.run(context.client, "skill.list", {}, void 0);
|
|
61897
|
-
}
|
|
61898
|
-
if (action === "catalog") {
|
|
61899
|
-
const parsed = parseFlags2(rest, /* @__PURE__ */ new Set(["include-unentitled"]));
|
|
61900
|
-
return context.run(context.client, "skill.catalog", {
|
|
61901
|
-
...parsed.flags.has("include-unentitled") ? { include_unentitled: true } : {}
|
|
61902
|
-
}, parsed.json ? void 0 : formatCatalog);
|
|
61903
|
-
}
|
|
61904
|
-
if (action === "get") {
|
|
61905
|
-
const parsed = parseFlags2(rest);
|
|
61906
|
-
return context.run(context.client, "skill.get", { slug: requireValue3(parsed, "slug") }, parsed.json ? void 0 : formatSkillDetail);
|
|
61907
|
-
}
|
|
61908
|
-
if (action === "file") {
|
|
61909
|
-
const parsed = parseFlags2(rest);
|
|
61910
|
-
return context.run(context.client, "skill.file.get", {
|
|
61911
|
-
slug: requireValue3(parsed, "slug"),
|
|
61912
|
-
path: requireValue3(parsed, "path")
|
|
61913
|
-
}, (output) => {
|
|
61914
|
-
const content = asRecord4(output).content;
|
|
61915
|
-
return typeof content === "string" ? content : JSON.stringify(output, null, 2);
|
|
61916
|
-
});
|
|
61917
|
-
}
|
|
61918
|
-
if (action === "enable") {
|
|
61919
|
-
const slug2 = rest[0];
|
|
61920
|
-
if (!slug2 || slug2.startsWith("--")) {
|
|
61921
|
-
throw new UsageError2(`Usage: ${context.binName} skills enable rost/<slug> [--json]`);
|
|
61922
|
-
}
|
|
61923
|
-
const parsed = parseFlags2(rest.slice(1));
|
|
61924
|
-
return context.run(context.client, "skill.enable_catalog", { slug: slug2 }, parsed.json ? void 0 : formatEnable);
|
|
61925
|
-
}
|
|
61926
|
-
if (action === "install") {
|
|
61927
|
-
return installOne(context, rest, env);
|
|
61928
|
-
}
|
|
61929
|
-
if (action === "sync") {
|
|
61930
|
-
return syncAssigned(context, rest, env);
|
|
61931
|
-
}
|
|
61932
|
-
if (action === "assign") {
|
|
61933
|
-
const parsed = parseFlags2(rest, /* @__PURE__ */ new Set(["approve", "allow-blocked-proposal"]));
|
|
61934
|
-
const body = withDefined({
|
|
61935
|
-
seat_id: requireValue3(parsed, "seat-id"),
|
|
61936
|
-
status: parsed.flags.has("approve") ? "approved" : "proposed",
|
|
61937
|
-
rationale: optionalValue2(parsed, "rationale") ?? "Skill selected for this Seat.",
|
|
61938
|
-
allow_blocked_proposal: parsed.flags.has("allow-blocked-proposal")
|
|
61939
|
-
}, {
|
|
61940
|
-
slug: optionalValue2(parsed, "skill") ?? optionalValue2(parsed, "slug"),
|
|
61941
|
-
skill_version_id: optionalValue2(parsed, "skill-version-id")
|
|
61942
|
-
});
|
|
61943
|
-
return context.run(context.client, "skill.assign_to_seat", body, parsed.json ? void 0 : formatAssignment, { targetSeatId: body.seat_id });
|
|
61944
|
-
}
|
|
61945
|
-
if (action === "revoke") {
|
|
61946
|
-
const parsed = parseFlags2(rest);
|
|
61947
|
-
const seatId = optionalValue2(parsed, "seat-id");
|
|
61948
|
-
const body = withDefined({}, {
|
|
61949
|
-
assignment_id: optionalValue2(parsed, "assignment-id"),
|
|
61950
|
-
seat_id: seatId,
|
|
61951
|
-
slug: optionalValue2(parsed, "skill") ?? optionalValue2(parsed, "slug"),
|
|
61952
|
-
skill_version_id: optionalValue2(parsed, "skill-version-id"),
|
|
61953
|
-
rationale: optionalValue2(parsed, "rationale")
|
|
61954
|
-
});
|
|
61955
|
-
return context.run(context.client, "skill.revoke_from_seat", body, parsed.json ? void 0 : formatRevoke, seatId ? { targetSeatId: seatId } : {});
|
|
61956
|
-
}
|
|
61957
|
-
if (action === "publish") {
|
|
61958
|
-
const parsed = parseFlags2(rest, /* @__PURE__ */ new Set(["allow-warnings"]));
|
|
61959
|
-
const body = withDefined({
|
|
61960
|
-
allow_warnings: parsed.flags.has("allow-warnings")
|
|
61961
|
-
}, {
|
|
61962
|
-
slug: optionalValue2(parsed, "slug"),
|
|
61963
|
-
skill_version_id: optionalValue2(parsed, "skill-version-id")
|
|
61964
|
-
});
|
|
61965
|
-
return context.run(context.client, "skill.publish", body, parsed.json ? void 0 : formatPublish);
|
|
61966
|
-
}
|
|
61967
|
-
throw new UsageError2(`Unknown skills action: ${action}`);
|
|
61968
|
-
} catch (error51) {
|
|
61969
|
-
if (error51 instanceof UsageError2) {
|
|
61970
|
-
context.io.stderr.write(`${error51.message}
|
|
61971
|
-
`);
|
|
61972
|
-
return 1;
|
|
61973
|
-
}
|
|
61974
|
-
if (error51 instanceof CommandClientError) {
|
|
61975
|
-
return printCommandError2(context.io, error51);
|
|
61976
|
-
}
|
|
61977
|
-
throw error51;
|
|
61883
|
+
var DEFAULT_RELEASE_TAG = "v0.2.0";
|
|
61884
|
+
var EXPECTED_DEFAULT_MANIFEST_SHA256 = "bbd745e1a625c30773fac5505f32c5152925d2474ac22d2cb4a93aa5a034ca66";
|
|
61885
|
+
var SKILL_MANIFEST_PROTOCOL_VERSION = 1;
|
|
61886
|
+
var SKILL_INSTALL_DIR_NAME = "rost-implementation";
|
|
61887
|
+
var SkillTrustError = class extends Error {
|
|
61888
|
+
constructor(message) {
|
|
61889
|
+
super(message);
|
|
61890
|
+
this.name = "SkillTrustError";
|
|
61978
61891
|
}
|
|
61892
|
+
};
|
|
61893
|
+
function terminalSafe(value) {
|
|
61894
|
+
return value.replace(/[\x00-\x1f\x7f-\x9f]/g, "?");
|
|
61979
61895
|
}
|
|
61980
|
-
|
|
61981
|
-
|
|
61982
|
-
|
|
61983
|
-
|
|
61896
|
+
var SUPPORTED_CLIENTS = ["claude-code", "codex", "cursor"];
|
|
61897
|
+
var skillManifestSchema = external_exports.object({
|
|
61898
|
+
schemaVersion: external_exports.literal(1),
|
|
61899
|
+
name: external_exports.literal("rost-implementation-skill"),
|
|
61900
|
+
version: external_exports.string(),
|
|
61901
|
+
repository: external_exports.object({
|
|
61902
|
+
owner: external_exports.string(),
|
|
61903
|
+
name: external_exports.string()
|
|
61904
|
+
}).strict(),
|
|
61905
|
+
protocol: external_exports.object({
|
|
61906
|
+
skill: external_exports.number().int(),
|
|
61907
|
+
minCli: external_exports.string(),
|
|
61908
|
+
maxCli: external_exports.string().nullable()
|
|
61909
|
+
}).strict(),
|
|
61910
|
+
supportedClients: external_exports.array(external_exports.enum(SUPPORTED_CLIENTS)).min(1),
|
|
61911
|
+
doctrine: external_exports.object({
|
|
61912
|
+
guide: external_exports.string(),
|
|
61913
|
+
version: external_exports.string()
|
|
61914
|
+
}).strict(),
|
|
61915
|
+
changelogUrl: external_exports.string().url(),
|
|
61916
|
+
files: external_exports.array(
|
|
61917
|
+
external_exports.object({
|
|
61918
|
+
path: external_exports.string(),
|
|
61919
|
+
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/)
|
|
61920
|
+
}).strict()
|
|
61921
|
+
).min(1)
|
|
61922
|
+
}).strict();
|
|
61923
|
+
function sha256Hex(content) {
|
|
61924
|
+
return createHash2("sha256").update(content).digest("hex");
|
|
61925
|
+
}
|
|
61926
|
+
function readSshString(buffer, offset) {
|
|
61927
|
+
if (offset + 4 > buffer.length) {
|
|
61928
|
+
throw new SkillTrustError("Truncated SSH wire string");
|
|
61984
61929
|
}
|
|
61985
|
-
const
|
|
61986
|
-
|
|
61987
|
-
|
|
61988
|
-
|
|
61989
|
-
|
|
61990
|
-
|
|
61991
|
-
local_only: parsed.flags.has("local-only")
|
|
61992
|
-
}, {
|
|
61993
|
-
seat_id: seatId,
|
|
61994
|
-
version: optionalNumber2(parsed, "version")
|
|
61995
|
-
});
|
|
61996
|
-
const options = seatId ? { targetSeatId: seatId } : {};
|
|
61997
|
-
const output = await executeInstallCommand(context, body, options);
|
|
61998
|
-
const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: false });
|
|
61999
|
-
const outputRecord = asRecord4(output);
|
|
62000
|
-
context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
|
|
62001
|
-
` : `${formatLocalSummary(summary)}
|
|
62002
|
-
`);
|
|
62003
|
-
return 0;
|
|
61930
|
+
const length = buffer.readUInt32BE(offset);
|
|
61931
|
+
offset += 4;
|
|
61932
|
+
if (offset + length > buffer.length) {
|
|
61933
|
+
throw new SkillTrustError("Truncated SSH wire string payload");
|
|
61934
|
+
}
|
|
61935
|
+
return { value: buffer.subarray(offset, offset + length), nextOffset: offset + length };
|
|
62004
61936
|
}
|
|
62005
|
-
|
|
62006
|
-
const
|
|
62007
|
-
|
|
62008
|
-
|
|
62009
|
-
const output = await executeSkillCommand(context, "skill.sync_local", {
|
|
62010
|
-
seat_id: seatId,
|
|
62011
|
-
client
|
|
62012
|
-
}, { targetSeatId: seatId });
|
|
62013
|
-
const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: true });
|
|
62014
|
-
const outputRecord = asRecord4(output);
|
|
62015
|
-
context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
|
|
62016
|
-
` : `${formatLocalSummary(summary)}
|
|
62017
|
-
`);
|
|
62018
|
-
return 0;
|
|
61937
|
+
function makeSshString(value) {
|
|
61938
|
+
const length = Buffer.alloc(4);
|
|
61939
|
+
length.writeUInt32BE(value.length);
|
|
61940
|
+
return Buffer.concat([length, value]);
|
|
62019
61941
|
}
|
|
62020
|
-
|
|
62021
|
-
const
|
|
62022
|
-
|
|
62023
|
-
|
|
62024
|
-
|
|
62025
|
-
} catch (error51) {
|
|
62026
|
-
if (!isCommandConflict(error51)) {
|
|
62027
|
-
throw error51;
|
|
62028
|
-
}
|
|
62029
|
-
}
|
|
61942
|
+
function parseEd25519PublicKeyLine(line) {
|
|
61943
|
+
const trimmed = line.trim();
|
|
61944
|
+
const parts = trimmed.split(/\s+/);
|
|
61945
|
+
if (parts.length < 2) {
|
|
61946
|
+
throw new SkillTrustError("Malformed SSH public key line");
|
|
62030
61947
|
}
|
|
62031
|
-
|
|
62032
|
-
|
|
62033
|
-
|
|
61948
|
+
const keyType = parts[0];
|
|
61949
|
+
const base643 = parts[1];
|
|
61950
|
+
if (keyType === void 0 || base643 === void 0) {
|
|
61951
|
+
throw new SkillTrustError("Malformed SSH public key line");
|
|
61952
|
+
}
|
|
61953
|
+
if (keyType !== "ssh-ed25519") {
|
|
61954
|
+
throw new SkillTrustError(`Unsupported SSH public key type: ${keyType}`);
|
|
61955
|
+
}
|
|
61956
|
+
let blob;
|
|
62034
61957
|
try {
|
|
62035
|
-
|
|
62036
|
-
|
|
62037
|
-
|
|
62038
|
-
const pending = context.io.interactive === true ? pendingConfirmationFromError(error51) : null;
|
|
62039
|
-
if (pending && pending.commandId === commandId) {
|
|
62040
|
-
renderPendingConfirmationGate2(context.io, pending);
|
|
62041
|
-
context.io.stderr.write(`Approving ${pending.commandId} confirmation ${pending.confirmationId} as the interactive CLI user.
|
|
62042
|
-
`);
|
|
62043
|
-
const approved = await context.client.execute("confirmation.approve", { confirmation_id: pending.confirmationId, reviewed: true });
|
|
62044
|
-
return asRecord4(approved.output).output ?? approved.output;
|
|
62045
|
-
}
|
|
62046
|
-
throw error51;
|
|
61958
|
+
blob = Buffer.from(base643, "base64");
|
|
61959
|
+
} catch {
|
|
61960
|
+
throw new SkillTrustError("Invalid base64 in SSH public key");
|
|
62047
61961
|
}
|
|
62048
|
-
|
|
62049
|
-
|
|
62050
|
-
|
|
62051
|
-
|
|
62052
|
-
|
|
62053
|
-
changes.push(...removeRevokedInstalls(output, root));
|
|
61962
|
+
let offset = 0;
|
|
61963
|
+
const { value: typeBytes, nextOffset: afterType } = readSshString(blob, offset);
|
|
61964
|
+
offset = afterType;
|
|
61965
|
+
if (typeBytes.toString("utf8") !== "ssh-ed25519") {
|
|
61966
|
+
throw new SkillTrustError("Public key blob key type mismatch");
|
|
62054
61967
|
}
|
|
62055
|
-
|
|
62056
|
-
|
|
62057
|
-
|
|
62058
|
-
|
|
62059
|
-
|
|
62060
|
-
|
|
62061
|
-
|
|
62062
|
-
|
|
62063
|
-
|
|
62064
|
-
|
|
62065
|
-
|
|
62066
|
-
|
|
62067
|
-
|
|
62068
|
-
|
|
62069
|
-
}
|
|
62070
|
-
rmSync(dir, { recursive: true, force: true });
|
|
62071
|
-
mkdirSync(dir, { recursive: true });
|
|
62072
|
-
for (const file2 of files) {
|
|
62073
|
-
const relativePath = safeRelativePath(String(file2.path ?? ""));
|
|
62074
|
-
const content = String(file2.content ?? "");
|
|
62075
|
-
const target = path4.join(dir, relativePath);
|
|
62076
|
-
mkdirSync(path4.dirname(target), { recursive: true });
|
|
62077
|
-
writeFileSync(target, content, "utf8");
|
|
62078
|
-
}
|
|
62079
|
-
writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
62080
|
-
`, "utf8");
|
|
62081
|
-
return { slug: slug2, dir, status: "installed", files: files.length };
|
|
62082
|
-
});
|
|
62083
|
-
}
|
|
62084
|
-
function removeRevokedInstalls(output, root) {
|
|
62085
|
-
const installs = new Set(asArray2(asRecord4(output).installs).map((entry) => {
|
|
62086
|
-
const slug2 = asRecord4(entry).slug;
|
|
62087
|
-
return typeof slug2 === "string" ? slug2 : "";
|
|
62088
|
-
}));
|
|
62089
|
-
const revoked = asArray2(asRecord4(output).revoked);
|
|
62090
|
-
const changes = [];
|
|
62091
|
-
for (const entry of revoked) {
|
|
62092
|
-
const slug2 = safeLocalSlug(String(asRecord4(entry).slug ?? ""));
|
|
62093
|
-
if (installs.has(slug2)) {
|
|
62094
|
-
continue;
|
|
62095
|
-
}
|
|
62096
|
-
const dir = path4.join(root, slug2);
|
|
62097
|
-
if (!existsSync(dir)) {
|
|
62098
|
-
continue;
|
|
62099
|
-
}
|
|
62100
|
-
rmSync(dir, { recursive: true, force: true });
|
|
62101
|
-
changes.push({ slug: slug2, dir, status: "removed", files: 0 });
|
|
62102
|
-
}
|
|
62103
|
-
return changes;
|
|
62104
|
-
}
|
|
62105
|
-
function installRoot(client, env) {
|
|
62106
|
-
const override = env.ROST_SKILL_INSTALL_ROOT;
|
|
62107
|
-
if (override && override.length > 0) {
|
|
62108
|
-
return path4.resolve(override, client);
|
|
62109
|
-
}
|
|
62110
|
-
if (client === "codex") {
|
|
62111
|
-
return path4.join(env.CODEX_HOME ?? path4.join(os.homedir(), ".codex"), "skills", "rost");
|
|
62112
|
-
}
|
|
62113
|
-
if (client === "claude-code") {
|
|
62114
|
-
return path4.join(env.CLAUDE_HOME ?? path4.join(os.homedir(), ".claude"), "skills", "rost");
|
|
62115
|
-
}
|
|
62116
|
-
return path4.join(os.homedir(), ".cursor", "skills", "rost");
|
|
62117
|
-
}
|
|
62118
|
-
function safeRelativePath(value) {
|
|
62119
|
-
const normalized = value.trim().replaceAll("\\", "/").replace(/^\.\/+/, "");
|
|
62120
|
-
if (!normalized || normalized.startsWith("/") || normalized.includes("//") || normalized.split("/").includes("..")) {
|
|
62121
|
-
throw new UsageError2("Server returned an invalid skill file path.");
|
|
62122
|
-
}
|
|
62123
|
-
return normalized;
|
|
62124
|
-
}
|
|
62125
|
-
function safeLocalSlug(value) {
|
|
62126
|
-
if (!/^[a-z0-9][a-z0-9-]{1,80}$/.test(value)) {
|
|
62127
|
-
throw new UsageError2("Server returned an invalid skill slug.");
|
|
62128
|
-
}
|
|
62129
|
-
return value;
|
|
62130
|
-
}
|
|
62131
|
-
function existingHash(metadataPath) {
|
|
62132
|
-
if (!existsSync(metadataPath)) {
|
|
62133
|
-
return null;
|
|
62134
|
-
}
|
|
62135
|
-
try {
|
|
62136
|
-
const parsed = JSON.parse(readFileSync3(metadataPath, "utf8"));
|
|
62137
|
-
const hash2 = asRecord4(parsed).content_sha256;
|
|
62138
|
-
return typeof hash2 === "string" ? hash2 : null;
|
|
62139
|
-
} catch {
|
|
62140
|
-
return null;
|
|
62141
|
-
}
|
|
62142
|
-
}
|
|
62143
|
-
function parseFlags2(args, booleanFlags = /* @__PURE__ */ new Set()) {
|
|
62144
|
-
const values = /* @__PURE__ */ new Map();
|
|
62145
|
-
const flags = /* @__PURE__ */ new Set();
|
|
62146
|
-
let json2 = false;
|
|
62147
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
62148
|
-
const token = args[index];
|
|
62149
|
-
if (token === void 0) {
|
|
62150
|
-
continue;
|
|
62151
|
-
}
|
|
62152
|
-
if (!token.startsWith("--")) {
|
|
62153
|
-
throw new UsageError2(`Unexpected argument: ${token}`);
|
|
62154
|
-
}
|
|
62155
|
-
const key = token.slice(2);
|
|
62156
|
-
if (key === "json") {
|
|
62157
|
-
json2 = true;
|
|
62158
|
-
continue;
|
|
62159
|
-
}
|
|
62160
|
-
if (booleanFlags.has(key)) {
|
|
62161
|
-
flags.add(key);
|
|
62162
|
-
continue;
|
|
62163
|
-
}
|
|
62164
|
-
const value = args[index + 1];
|
|
62165
|
-
if (value === void 0 || value.startsWith("--")) {
|
|
62166
|
-
flags.add(key);
|
|
62167
|
-
continue;
|
|
62168
|
-
}
|
|
62169
|
-
values.set(key, value);
|
|
62170
|
-
index += 1;
|
|
62171
|
-
}
|
|
62172
|
-
return { json: json2, values, flags };
|
|
62173
|
-
}
|
|
62174
|
-
function requireValue3(parsed, key) {
|
|
62175
|
-
const value = optionalValue2(parsed, key);
|
|
62176
|
-
if (value === void 0) {
|
|
62177
|
-
throw new UsageError2(`Missing required --${key} value.`);
|
|
62178
|
-
}
|
|
62179
|
-
return value;
|
|
62180
|
-
}
|
|
62181
|
-
function optionalValue2(parsed, key) {
|
|
62182
|
-
const value = parsed.values.get(key);
|
|
62183
|
-
return value && value.length > 0 ? value : void 0;
|
|
62184
|
-
}
|
|
62185
|
-
function optionalNumber2(parsed, key) {
|
|
62186
|
-
const value = optionalValue2(parsed, key);
|
|
62187
|
-
if (value === void 0) {
|
|
62188
|
-
return void 0;
|
|
62189
|
-
}
|
|
62190
|
-
const parsedNumber = Number(value);
|
|
62191
|
-
if (!Number.isSafeInteger(parsedNumber) || parsedNumber <= 0) {
|
|
62192
|
-
throw new UsageError2(`--${key} must be a positive integer.`);
|
|
62193
|
-
}
|
|
62194
|
-
return parsedNumber;
|
|
62195
|
-
}
|
|
62196
|
-
function parseClient2(value) {
|
|
62197
|
-
if (!CLIENTS.has(value)) {
|
|
62198
|
-
throw new UsageError2("--client must be claude-code, codex, or cursor.");
|
|
62199
|
-
}
|
|
62200
|
-
return value;
|
|
62201
|
-
}
|
|
62202
|
-
function isCommandConflict(error51) {
|
|
62203
|
-
return error51 instanceof CommandClientError && error51.status === 409;
|
|
62204
|
-
}
|
|
62205
|
-
function withDefined(base, extra) {
|
|
62206
|
-
const next = { ...base };
|
|
62207
|
-
for (const [key, value] of Object.entries(extra)) {
|
|
62208
|
-
if (value !== void 0) {
|
|
62209
|
-
next[key] = value;
|
|
62210
|
-
}
|
|
62211
|
-
}
|
|
62212
|
-
return next;
|
|
62213
|
-
}
|
|
62214
|
-
function asRecord4(value) {
|
|
62215
|
-
return typeof value === "object" && value !== null ? value : {};
|
|
62216
|
-
}
|
|
62217
|
-
function asArray2(value) {
|
|
62218
|
-
return Array.isArray(value) ? value : [];
|
|
62219
|
-
}
|
|
62220
|
-
function formatCatalog(output) {
|
|
62221
|
-
const entries = asArray2(asRecord4(output).catalog);
|
|
62222
|
-
if (entries.length === 0) {
|
|
62223
|
-
return "No ROST catalog skills are available for this tenant.";
|
|
62224
|
-
}
|
|
62225
|
-
return entries.map((entry) => {
|
|
62226
|
-
const record2 = asRecord4(entry);
|
|
62227
|
-
return `${record2.catalog_slug} [${record2.tier}/${record2.entitlement_status}] ${record2.name}`;
|
|
62228
|
-
}).join("\n");
|
|
62229
|
-
}
|
|
62230
|
-
function formatSkillDetail(output) {
|
|
62231
|
-
const skill = asRecord4(asRecord4(output).skill);
|
|
62232
|
-
return `${skill.slug} v${skill.latest_version ?? "\u2014"} [${skill.status}] ${skill.name}
|
|
62233
|
-
${skill.description}`;
|
|
62234
|
-
}
|
|
62235
|
-
function formatEnable(output) {
|
|
62236
|
-
const skill = asRecord4(asRecord4(output).skill);
|
|
62237
|
-
const catalog = asRecord4(asRecord4(output).catalog);
|
|
62238
|
-
return `Enabled ${catalog.catalog_slug} as ${skill.slug} v${skill.version} (${skill.skill_version_id})`;
|
|
62239
|
-
}
|
|
62240
|
-
function formatAssignment(output) {
|
|
62241
|
-
const assignment = asRecord4(asRecord4(output).assignment);
|
|
62242
|
-
return `${assignment.status} ${assignment.slug} for seat ${assignment.seat_id}; sync: ${String(asRecord4(output).local_sync_next_step)}`;
|
|
62243
|
-
}
|
|
62244
|
-
function formatRevoke(output) {
|
|
62245
|
-
const record2 = asRecord4(output);
|
|
62246
|
-
return `Revoked ${record2.slug} for seat ${record2.seat_id}; sync: ${record2.local_sync_next_step}`;
|
|
62247
|
-
}
|
|
62248
|
-
function formatPublish(output) {
|
|
62249
|
-
const skill = asRecord4(asRecord4(output).skill);
|
|
62250
|
-
return `Published ${skill.slug} v${skill.version} (${skill.skill_version_id})`;
|
|
62251
|
-
}
|
|
62252
|
-
function formatLocalSummary(summary) {
|
|
62253
|
-
if (summary.length === 0) {
|
|
62254
|
-
return "No skills to install.";
|
|
62255
|
-
}
|
|
62256
|
-
return summary.map((entry) => `${entry.status} ${entry.slug} -> ${entry.dir} (${entry.files} files)`).join("\n");
|
|
62257
|
-
}
|
|
62258
|
-
function printCommandError2(io, error51) {
|
|
62259
|
-
if (error51.response && !error51.response.ok) {
|
|
62260
|
-
const payload = {
|
|
62261
|
-
code: error51.response.error.code,
|
|
62262
|
-
message: error51.response.error.message,
|
|
62263
|
-
...error51.response.error.details === void 0 ? {} : { details: error51.response.error.details },
|
|
62264
|
-
...error51.response.error.help === void 0 ? {} : { help: error51.response.error.help },
|
|
62265
|
-
...error51.response.requestId === void 0 ? {} : { requestId: error51.response.requestId }
|
|
62266
|
-
};
|
|
62267
|
-
io.stderr.write(`${redactForLog(payload)}
|
|
62268
|
-
`);
|
|
62269
|
-
if (error51.response.error.help !== void 0) {
|
|
62270
|
-
io.stderr.write(`\u2192 try: ${redactForLog(error51.response.error.help)}
|
|
62271
|
-
`);
|
|
62272
|
-
}
|
|
62273
|
-
return error51.status === 409 ? 3 : error51.status >= 400 && error51.status < 500 ? 1 : 2;
|
|
62274
|
-
}
|
|
62275
|
-
io.stderr.write(`${redactForLog(error51.message)}
|
|
62276
|
-
`);
|
|
62277
|
-
return error51.status >= 400 && error51.status < 500 ? 1 : 2;
|
|
62278
|
-
}
|
|
62279
|
-
function pendingConfirmationFromError(error51) {
|
|
62280
|
-
if (!(error51 instanceof CommandClientError) || !error51.response || error51.response.ok || error51.response.error.code !== "COMMAND_CONFIRMATION_REQUIRED") {
|
|
62281
|
-
return null;
|
|
62282
|
-
}
|
|
62283
|
-
const details = asRecord4(error51.response.error.details);
|
|
62284
|
-
const confirmationId = details.confirmationId;
|
|
62285
|
-
const commandId = details.commandId;
|
|
62286
|
-
if (typeof confirmationId !== "string" || confirmationId.length === 0 || typeof commandId !== "string" || commandId.length === 0) {
|
|
62287
|
-
return null;
|
|
62288
|
-
}
|
|
62289
|
-
const summary = typeof details.summary === "string" ? details.summary : void 0;
|
|
62290
|
-
const riskLevel = typeof details.riskLevel === "string" ? details.riskLevel : void 0;
|
|
62291
|
-
return {
|
|
62292
|
-
confirmationId,
|
|
62293
|
-
commandId,
|
|
62294
|
-
...summary === void 0 ? {} : { summary },
|
|
62295
|
-
...riskLevel === void 0 ? {} : { riskLevel },
|
|
62296
|
-
...details.diff === void 0 ? {} : { diff: details.diff }
|
|
62297
|
-
};
|
|
62298
|
-
}
|
|
62299
|
-
function renderPendingConfirmationGate2(io, pending) {
|
|
62300
|
-
if (pending.summary !== void 0) {
|
|
62301
|
-
io.stderr.write(`${redactForLog(pending.summary)}
|
|
62302
|
-
`);
|
|
62303
|
-
}
|
|
62304
|
-
if (pending.riskLevel !== void 0) {
|
|
62305
|
-
io.stderr.write(`Risk level: ${pending.riskLevel}
|
|
62306
|
-
`);
|
|
62307
|
-
}
|
|
62308
|
-
if (pending.diff !== void 0) {
|
|
62309
|
-
io.stderr.write(`Diff:
|
|
62310
|
-
${redactForLog(pending.diff)}
|
|
62311
|
-
`);
|
|
62312
|
-
}
|
|
62313
|
-
}
|
|
62314
|
-
|
|
62315
|
-
// src/skill-install.ts
|
|
62316
|
-
import { lstatSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
62317
|
-
import os2 from "node:os";
|
|
62318
|
-
import path5 from "node:path";
|
|
62319
|
-
|
|
62320
|
-
// src/skill-trust.ts
|
|
62321
|
-
import { createHash as createHash2, createPublicKey, verify } from "node:crypto";
|
|
62322
|
-
var TRUSTED_SIGNER_PUBLIC_KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOXSsKe/zFjaoAdZdvP/esPoeM1fsCGFmxv0INDlCxaW rost-implementation-skill-release";
|
|
62323
|
-
var TRUSTED_SIGNER_FINGERPRINT = "SHA256:ziSyU9vhGi0Q3GaeePB1R6BI3GqXutLhepnF/4Hx4rY";
|
|
62324
|
-
var RELEASE_REPOSITORY = {
|
|
62325
|
-
owner: "agenticoperatingsystem",
|
|
62326
|
-
name: "rost-implementation-skill"
|
|
62327
|
-
};
|
|
62328
|
-
var DEFAULT_RELEASE_TAG = "v0.2.0";
|
|
62329
|
-
var EXPECTED_DEFAULT_MANIFEST_SHA256 = "bbd745e1a625c30773fac5505f32c5152925d2474ac22d2cb4a93aa5a034ca66";
|
|
62330
|
-
var SKILL_MANIFEST_PROTOCOL_VERSION = 1;
|
|
62331
|
-
var SKILL_INSTALL_DIR_NAME = "rost-implementation";
|
|
62332
|
-
var SkillTrustError = class extends Error {
|
|
62333
|
-
constructor(message) {
|
|
62334
|
-
super(message);
|
|
62335
|
-
this.name = "SkillTrustError";
|
|
62336
|
-
}
|
|
62337
|
-
};
|
|
62338
|
-
function terminalSafe(value) {
|
|
62339
|
-
return value.replace(/[\x00-\x1f\x7f-\x9f]/g, "?");
|
|
62340
|
-
}
|
|
62341
|
-
var SUPPORTED_CLIENTS = ["claude-code", "codex", "cursor"];
|
|
62342
|
-
var skillManifestSchema = external_exports.object({
|
|
62343
|
-
schemaVersion: external_exports.literal(1),
|
|
62344
|
-
name: external_exports.literal("rost-implementation-skill"),
|
|
62345
|
-
version: external_exports.string(),
|
|
62346
|
-
repository: external_exports.object({
|
|
62347
|
-
owner: external_exports.string(),
|
|
62348
|
-
name: external_exports.string()
|
|
62349
|
-
}).strict(),
|
|
62350
|
-
protocol: external_exports.object({
|
|
62351
|
-
skill: external_exports.number().int(),
|
|
62352
|
-
minCli: external_exports.string(),
|
|
62353
|
-
maxCli: external_exports.string().nullable()
|
|
62354
|
-
}).strict(),
|
|
62355
|
-
supportedClients: external_exports.array(external_exports.enum(SUPPORTED_CLIENTS)).min(1),
|
|
62356
|
-
doctrine: external_exports.object({
|
|
62357
|
-
guide: external_exports.string(),
|
|
62358
|
-
version: external_exports.string()
|
|
62359
|
-
}).strict(),
|
|
62360
|
-
changelogUrl: external_exports.string().url(),
|
|
62361
|
-
files: external_exports.array(
|
|
62362
|
-
external_exports.object({
|
|
62363
|
-
path: external_exports.string(),
|
|
62364
|
-
sha256: external_exports.string().regex(/^[a-f0-9]{64}$/)
|
|
62365
|
-
}).strict()
|
|
62366
|
-
).min(1)
|
|
62367
|
-
}).strict();
|
|
62368
|
-
function sha256Hex(content) {
|
|
62369
|
-
return createHash2("sha256").update(content).digest("hex");
|
|
62370
|
-
}
|
|
62371
|
-
function readSshString(buffer, offset) {
|
|
62372
|
-
if (offset + 4 > buffer.length) {
|
|
62373
|
-
throw new SkillTrustError("Truncated SSH wire string");
|
|
62374
|
-
}
|
|
62375
|
-
const length = buffer.readUInt32BE(offset);
|
|
62376
|
-
offset += 4;
|
|
62377
|
-
if (offset + length > buffer.length) {
|
|
62378
|
-
throw new SkillTrustError("Truncated SSH wire string payload");
|
|
62379
|
-
}
|
|
62380
|
-
return { value: buffer.subarray(offset, offset + length), nextOffset: offset + length };
|
|
62381
|
-
}
|
|
62382
|
-
function makeSshString(value) {
|
|
62383
|
-
const length = Buffer.alloc(4);
|
|
62384
|
-
length.writeUInt32BE(value.length);
|
|
62385
|
-
return Buffer.concat([length, value]);
|
|
62386
|
-
}
|
|
62387
|
-
function parseEd25519PublicKeyLine(line) {
|
|
62388
|
-
const trimmed = line.trim();
|
|
62389
|
-
const parts = trimmed.split(/\s+/);
|
|
62390
|
-
if (parts.length < 2) {
|
|
62391
|
-
throw new SkillTrustError("Malformed SSH public key line");
|
|
62392
|
-
}
|
|
62393
|
-
const keyType = parts[0];
|
|
62394
|
-
const base643 = parts[1];
|
|
62395
|
-
if (keyType === void 0 || base643 === void 0) {
|
|
62396
|
-
throw new SkillTrustError("Malformed SSH public key line");
|
|
62397
|
-
}
|
|
62398
|
-
if (keyType !== "ssh-ed25519") {
|
|
62399
|
-
throw new SkillTrustError(`Unsupported SSH public key type: ${keyType}`);
|
|
62400
|
-
}
|
|
62401
|
-
let blob;
|
|
62402
|
-
try {
|
|
62403
|
-
blob = Buffer.from(base643, "base64");
|
|
62404
|
-
} catch {
|
|
62405
|
-
throw new SkillTrustError("Invalid base64 in SSH public key");
|
|
62406
|
-
}
|
|
62407
|
-
let offset = 0;
|
|
62408
|
-
const { value: typeBytes, nextOffset: afterType } = readSshString(blob, offset);
|
|
62409
|
-
offset = afterType;
|
|
62410
|
-
if (typeBytes.toString("utf8") !== "ssh-ed25519") {
|
|
62411
|
-
throw new SkillTrustError("Public key blob key type mismatch");
|
|
62412
|
-
}
|
|
62413
|
-
const { value: keyBytes, nextOffset: afterKey } = readSshString(blob, offset);
|
|
62414
|
-
offset = afterKey;
|
|
62415
|
-
if (offset !== blob.length) {
|
|
62416
|
-
throw new SkillTrustError("Trailing bytes in SSH public key blob");
|
|
62417
|
-
}
|
|
62418
|
-
if (keyBytes.length !== 32) {
|
|
62419
|
-
throw new SkillTrustError(`Invalid ed25519 public key length: ${keyBytes.length}`);
|
|
62420
|
-
}
|
|
62421
|
-
const spki = Buffer.concat([
|
|
62422
|
-
Buffer.from("302a300506032b6570032100", "hex"),
|
|
62423
|
-
keyBytes
|
|
62424
|
-
]);
|
|
62425
|
-
const key = createPublicKey({ key: spki, format: "der", type: "spki" });
|
|
62426
|
-
return { key, blob };
|
|
61968
|
+
const { value: keyBytes, nextOffset: afterKey } = readSshString(blob, offset);
|
|
61969
|
+
offset = afterKey;
|
|
61970
|
+
if (offset !== blob.length) {
|
|
61971
|
+
throw new SkillTrustError("Trailing bytes in SSH public key blob");
|
|
61972
|
+
}
|
|
61973
|
+
if (keyBytes.length !== 32) {
|
|
61974
|
+
throw new SkillTrustError(`Invalid ed25519 public key length: ${keyBytes.length}`);
|
|
61975
|
+
}
|
|
61976
|
+
const spki = Buffer.concat([
|
|
61977
|
+
Buffer.from("302a300506032b6570032100", "hex"),
|
|
61978
|
+
keyBytes
|
|
61979
|
+
]);
|
|
61980
|
+
const key = createPublicKey({ key: spki, format: "der", type: "spki" });
|
|
61981
|
+
return { key, blob };
|
|
62427
61982
|
}
|
|
62428
61983
|
function splitSignedTag(rawTagObject) {
|
|
62429
61984
|
const marker = "-----BEGIN SSH SIGNATURE-----";
|
|
@@ -62535,7 +62090,7 @@ function verifyTagSignature(payload, signatureArmored, trustedPublicKeyLine = TR
|
|
|
62535
62090
|
}
|
|
62536
62091
|
return { ok: true, reason: "Verified" };
|
|
62537
62092
|
}
|
|
62538
|
-
function
|
|
62093
|
+
function safeRelativePath(value) {
|
|
62539
62094
|
if (value.includes("\\")) {
|
|
62540
62095
|
throw new SkillTrustError("Manifest file path is not a safe relative path");
|
|
62541
62096
|
}
|
|
@@ -62629,7 +62184,7 @@ function verifyReleaseManifest(manifest, options) {
|
|
|
62629
62184
|
}
|
|
62630
62185
|
let files;
|
|
62631
62186
|
try {
|
|
62632
|
-
files = parsed.files.map((file2) => ({ ...file2, path:
|
|
62187
|
+
files = parsed.files.map((file2) => ({ ...file2, path: safeRelativePath(file2.path) }));
|
|
62633
62188
|
} catch (error51) {
|
|
62634
62189
|
const message = error51 instanceof Error ? error51.message : "Invalid path";
|
|
62635
62190
|
return { ok: false, reason: message };
|
|
@@ -62639,13 +62194,13 @@ function verifyReleaseManifest(manifest, options) {
|
|
|
62639
62194
|
function verifyFileChecksums(files, manifest) {
|
|
62640
62195
|
const manifestMap = /* @__PURE__ */ new Map();
|
|
62641
62196
|
for (const file2 of manifest.files) {
|
|
62642
|
-
manifestMap.set(
|
|
62197
|
+
manifestMap.set(safeRelativePath(file2.path), file2.sha256);
|
|
62643
62198
|
}
|
|
62644
62199
|
const seen = /* @__PURE__ */ new Set();
|
|
62645
62200
|
for (const file2 of files) {
|
|
62646
62201
|
let normalized;
|
|
62647
62202
|
try {
|
|
62648
|
-
normalized =
|
|
62203
|
+
normalized = safeRelativePath(file2.path);
|
|
62649
62204
|
} catch (error51) {
|
|
62650
62205
|
const message = error51 instanceof Error ? error51.message : "Invalid path";
|
|
62651
62206
|
return { ok: false, reason: message };
|
|
@@ -62731,13 +62286,14 @@ function verifyReleaseTrust(input) {
|
|
|
62731
62286
|
}
|
|
62732
62287
|
|
|
62733
62288
|
// src/skill-install.ts
|
|
62734
|
-
var
|
|
62289
|
+
var UsageError2 = class extends Error {
|
|
62735
62290
|
};
|
|
62291
|
+
var O_NOFOLLOW_FLAG = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
|
|
62736
62292
|
function writeErrorLine(io, message) {
|
|
62737
62293
|
io.stderr.write(`${terminalSafe(message)}
|
|
62738
62294
|
`);
|
|
62739
62295
|
}
|
|
62740
|
-
var
|
|
62296
|
+
var CLIENTS = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
|
|
62741
62297
|
var REPO_PATH = `${RELEASE_REPOSITORY.owner}/${RELEASE_REPOSITORY.name}`;
|
|
62742
62298
|
var GITHUB_JSON_HEADERS = { "User-Agent": "rost-cli", "Accept": "application/vnd.github+json" };
|
|
62743
62299
|
var GITHUB_RAW_HEADERS = { "User-Agent": "rost-cli", "Accept": "application/vnd.github.raw+json" };
|
|
@@ -62824,11 +62380,12 @@ async function runInstallImplementation(context, args) {
|
|
|
62824
62380
|
let client;
|
|
62825
62381
|
let tag;
|
|
62826
62382
|
try {
|
|
62827
|
-
parsed =
|
|
62828
|
-
client =
|
|
62829
|
-
tag =
|
|
62383
|
+
parsed = parseFlags2(args);
|
|
62384
|
+
client = parseClient2(optionalValue2(parsed, "client") ?? "codex");
|
|
62385
|
+
tag = optionalValue2(parsed, "tag") ?? DEFAULT_RELEASE_TAG;
|
|
62386
|
+
installRoot(client, context.env);
|
|
62830
62387
|
} catch (error51) {
|
|
62831
|
-
if (error51 instanceof
|
|
62388
|
+
if (error51 instanceof UsageError2) {
|
|
62832
62389
|
writeErrorLine(context.io, error51.message);
|
|
62833
62390
|
return 1;
|
|
62834
62391
|
}
|
|
@@ -62991,10 +62548,11 @@ async function installRelease(context, plan, testOverrides = {}) {
|
|
|
62991
62548
|
}
|
|
62992
62549
|
let root;
|
|
62993
62550
|
let legacyNotice;
|
|
62551
|
+
let viaOverride;
|
|
62994
62552
|
try {
|
|
62995
|
-
({ root, legacyNotice } =
|
|
62553
|
+
({ root, legacyNotice, viaOverride } = installRoot(plan.client, context.env));
|
|
62996
62554
|
} catch (error51) {
|
|
62997
|
-
if (error51 instanceof
|
|
62555
|
+
if (error51 instanceof UsageError2) {
|
|
62998
62556
|
writeErrorLine(context.io, error51.message);
|
|
62999
62557
|
return 1;
|
|
63000
62558
|
}
|
|
@@ -63003,7 +62561,7 @@ async function installRelease(context, plan, testOverrides = {}) {
|
|
|
63003
62561
|
if (legacyNotice !== null) {
|
|
63004
62562
|
writeErrorLine(context.io, legacyNotice);
|
|
63005
62563
|
}
|
|
63006
|
-
const dir =
|
|
62564
|
+
const dir = path4.join(root, SKILL_INSTALL_DIR_NAME);
|
|
63007
62565
|
const manifestSha256 = sha256Hex(plan.manifestRaw);
|
|
63008
62566
|
const existingMarker = readMarker(dir);
|
|
63009
62567
|
const installedState = existingMarker?.manifestSha256 === manifestSha256 ? verifyInstalledTree(dir, manifest) : null;
|
|
@@ -63027,28 +62585,31 @@ async function installRelease(context, plan, testOverrides = {}) {
|
|
|
63027
62585
|
})}
|
|
63028
62586
|
`
|
|
63029
62587
|
);
|
|
62588
|
+
writeErrorLine(context.io, clientLoadLine(plan.client, dir, viaOverride));
|
|
63030
62589
|
} else {
|
|
63031
62590
|
context.io.stdout.write(
|
|
63032
|
-
`
|
|
62591
|
+
`unchanged rost-implementation ${plan.tag} (${commitSha.slice(0, 7)}) -> ${humanPath(dir)} (${plan.files.length} files)
|
|
63033
62592
|
`
|
|
63034
62593
|
);
|
|
62594
|
+
context.io.stdout.write(`${clientLoadLine(plan.client, dir, viaOverride)}
|
|
62595
|
+
`);
|
|
63035
62596
|
context.io.stdout.write(`${verifiedSignerLine()}
|
|
63036
62597
|
`);
|
|
63037
62598
|
}
|
|
63038
62599
|
return 0;
|
|
63039
62600
|
}
|
|
63040
|
-
|
|
63041
|
-
|
|
62601
|
+
rmSync(dir, { recursive: true, force: true });
|
|
62602
|
+
mkdirSync(dir, { recursive: true });
|
|
63042
62603
|
for (const file2 of plan.files) {
|
|
63043
|
-
const target =
|
|
63044
|
-
const resolvedTarget =
|
|
63045
|
-
const resolvedDir =
|
|
63046
|
-
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}`)) {
|
|
63047
62608
|
writeErrorLine(context.io, `Refusing to write file outside target directory: ${file2.path}`);
|
|
63048
62609
|
return 1;
|
|
63049
62610
|
}
|
|
63050
|
-
|
|
63051
|
-
|
|
62611
|
+
mkdirSync(path4.dirname(target), { recursive: true });
|
|
62612
|
+
writeFileSync(target, file2.content, "utf8");
|
|
63052
62613
|
}
|
|
63053
62614
|
const writtenState = verifyInstalledTree(dir, manifest);
|
|
63054
62615
|
if (!writtenState.ok) {
|
|
@@ -63071,11 +62632,14 @@ async function installRelease(context, plan, testOverrides = {}) {
|
|
|
63071
62632
|
})}
|
|
63072
62633
|
`
|
|
63073
62634
|
);
|
|
62635
|
+
writeErrorLine(context.io, clientLoadLine(plan.client, dir, viaOverride));
|
|
63074
62636
|
} else {
|
|
63075
62637
|
context.io.stdout.write(
|
|
63076
|
-
`installed rost-implementation ${plan.tag} (${commitSha.slice(0, 7)}) -> ${dir} (${plan.files.length} files)
|
|
62638
|
+
`installed rost-implementation ${plan.tag} (${commitSha.slice(0, 7)}) -> ${humanPath(dir)} (${plan.files.length} files)
|
|
63077
62639
|
`
|
|
63078
62640
|
);
|
|
62641
|
+
context.io.stdout.write(`${clientLoadLine(plan.client, dir, viaOverride)}
|
|
62642
|
+
`);
|
|
63079
62643
|
context.io.stdout.write(`${verifiedSignerLine()}
|
|
63080
62644
|
`);
|
|
63081
62645
|
}
|
|
@@ -63086,6 +62650,23 @@ function verifiedSignerLine() {
|
|
|
63086
62650
|
const fingerprintBase = TRUSTED_SIGNER_FINGERPRINT.replace("SHA256:", "");
|
|
63087
62651
|
return `verified signed release; signer SHA256:${fingerprintBase.slice(-12)} (last 12 chars)`;
|
|
63088
62652
|
}
|
|
62653
|
+
function clientDisplay(client) {
|
|
62654
|
+
if (client === "claude-code") return "Claude Code";
|
|
62655
|
+
if (client === "codex") return "Codex";
|
|
62656
|
+
return "Cursor";
|
|
62657
|
+
}
|
|
62658
|
+
function humanPath(dir) {
|
|
62659
|
+
if (terminalSafe(dir) === dir) {
|
|
62660
|
+
return dir;
|
|
62661
|
+
}
|
|
62662
|
+
return JSON.stringify(dir).replace(/[\x7f-\x9f]/gu, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
62663
|
+
}
|
|
62664
|
+
function clientLoadLine(client, dir, viaOverride) {
|
|
62665
|
+
if (viaOverride) {
|
|
62666
|
+
return `ROST_SKILL_INSTALL_ROOT chose ${humanPath(dir)} \u2014 ${clientDisplay(client)} loads from its own root, which may differ`;
|
|
62667
|
+
}
|
|
62668
|
+
return `${clientDisplay(client)} will load this from ${humanPath(dir)}`;
|
|
62669
|
+
}
|
|
63089
62670
|
function writeMarker(dir, input) {
|
|
63090
62671
|
const marker = {
|
|
63091
62672
|
source: `github.com/${REPO_PATH}`,
|
|
@@ -63097,7 +62678,7 @@ function writeMarker(dir, input) {
|
|
|
63097
62678
|
// operator's evidence that the pre-launch re-run actually happened.
|
|
63098
62679
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
63099
62680
|
};
|
|
63100
|
-
|
|
62681
|
+
writeFileSync(path4.join(dir, SKILL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
63101
62682
|
`, "utf8");
|
|
63102
62683
|
}
|
|
63103
62684
|
function listInstalledFiles(dir) {
|
|
@@ -63105,7 +62686,7 @@ function listInstalledFiles(dir) {
|
|
|
63105
62686
|
const walk = (current, prefix) => {
|
|
63106
62687
|
for (const name of readdirSync(current)) {
|
|
63107
62688
|
const relative = prefix === "" ? name : `${prefix}/${name}`;
|
|
63108
|
-
const absolute =
|
|
62689
|
+
const absolute = path4.join(current, name);
|
|
63109
62690
|
const stats = lstatSync(absolute);
|
|
63110
62691
|
if (stats.isSymbolicLink() || !stats.isFile() && !stats.isDirectory()) {
|
|
63111
62692
|
throw new Error(`refusing to verify a symlink or special file: ${relative}`);
|
|
@@ -63117,7 +62698,7 @@ function listInstalledFiles(dir) {
|
|
|
63117
62698
|
if (relative === SKILL_MARKER_FILE) {
|
|
63118
62699
|
continue;
|
|
63119
62700
|
}
|
|
63120
|
-
files.push({ path: relative, absolute });
|
|
62701
|
+
files.push({ path: relative, absolute, dev: stats.dev, ino: stats.ino });
|
|
63121
62702
|
}
|
|
63122
62703
|
};
|
|
63123
62704
|
walk(dir, "");
|
|
@@ -63138,70 +62719,445 @@ function verifyInstalledTree(dir, manifest) {
|
|
|
63138
62719
|
if (present.length !== manifestPaths.size) {
|
|
63139
62720
|
return { ok: false, reason: "the installed tree is missing files listed in the signed manifest" };
|
|
63140
62721
|
}
|
|
62722
|
+
for (const file2 of present) {
|
|
62723
|
+
try {
|
|
62724
|
+
if (statSync(file2.absolute).size > MAX_RESPONSE_BYTES) {
|
|
62725
|
+
return { ok: false, reason: `an installed file exceeds the ${MAX_RESPONSE_BYTES}-byte verification cap: ${file2.path}` };
|
|
62726
|
+
}
|
|
62727
|
+
} catch (error51) {
|
|
62728
|
+
return { ok: false, reason: error51 instanceof Error ? error51.message : "an installed file is unreadable" };
|
|
62729
|
+
}
|
|
62730
|
+
}
|
|
63141
62731
|
function* readEach() {
|
|
63142
62732
|
for (const file2 of present) {
|
|
63143
|
-
|
|
62733
|
+
const fd = openSync(file2.absolute, constants.O_RDONLY | O_NOFOLLOW_FLAG);
|
|
62734
|
+
try {
|
|
62735
|
+
const opened = fstatSync(fd);
|
|
62736
|
+
if (opened.dev !== file2.dev || opened.ino !== file2.ino) {
|
|
62737
|
+
throw new Error(`an installed file changed between enumeration and verification: ${file2.path}`);
|
|
62738
|
+
}
|
|
62739
|
+
const size = opened.size;
|
|
62740
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
62741
|
+
throw new Error(`an installed file exceeds the ${MAX_RESPONSE_BYTES}-byte verification cap: ${file2.path}`);
|
|
62742
|
+
}
|
|
62743
|
+
const buffer = Buffer.alloc(size + 1);
|
|
62744
|
+
let bytesRead = 0;
|
|
62745
|
+
while (bytesRead < buffer.length) {
|
|
62746
|
+
const chunk = readSync(fd, buffer, bytesRead, buffer.length - bytesRead, null);
|
|
62747
|
+
if (chunk === 0) {
|
|
62748
|
+
break;
|
|
62749
|
+
}
|
|
62750
|
+
bytesRead += chunk;
|
|
62751
|
+
}
|
|
62752
|
+
if (bytesRead > MAX_RESPONSE_BYTES) {
|
|
62753
|
+
throw new Error(`an installed file exceeds the ${MAX_RESPONSE_BYTES}-byte verification cap: ${file2.path}`);
|
|
62754
|
+
}
|
|
62755
|
+
yield { path: file2.path, content: buffer.subarray(0, bytesRead) };
|
|
62756
|
+
} finally {
|
|
62757
|
+
closeSync(fd);
|
|
62758
|
+
}
|
|
62759
|
+
}
|
|
62760
|
+
}
|
|
62761
|
+
try {
|
|
62762
|
+
return verifyFileChecksums(readEach(), manifest);
|
|
62763
|
+
} catch (error51) {
|
|
62764
|
+
return { ok: false, reason: error51 instanceof Error ? error51.message : "an installed file is unreadable" };
|
|
62765
|
+
}
|
|
62766
|
+
}
|
|
62767
|
+
function readMarker(dir) {
|
|
62768
|
+
try {
|
|
62769
|
+
const content = readFileSync3(path4.join(dir, SKILL_MARKER_FILE), "utf8");
|
|
62770
|
+
const parsed = JSON.parse(content);
|
|
62771
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
62772
|
+
return parsed;
|
|
62773
|
+
}
|
|
62774
|
+
} catch {
|
|
62775
|
+
}
|
|
62776
|
+
return null;
|
|
62777
|
+
}
|
|
62778
|
+
function resolveClaudeSkillInstallRoot(env) {
|
|
62779
|
+
const configDir = requireNonBlank("CLAUDE_CONFIG_DIR", env.CLAUDE_CONFIG_DIR);
|
|
62780
|
+
const legacyHome = requireNonBlank("CLAUDE_HOME", env.CLAUDE_HOME);
|
|
62781
|
+
if (configDir !== null) {
|
|
62782
|
+
const disagrees = legacyHome !== null && path4.resolve(legacyHome) !== path4.resolve(configDir);
|
|
62783
|
+
return {
|
|
62784
|
+
root: path4.resolve(path4.join(configDir, "skills")),
|
|
62785
|
+
legacyNotice: disagrees ? `NOTICE: CLAUDE_HOME (${humanPath(legacyHome)}) is ignored; Claude Code reads CLAUDE_CONFIG_DIR (${humanPath(configDir)}).` : null
|
|
62786
|
+
};
|
|
62787
|
+
}
|
|
62788
|
+
if (legacyHome !== null) {
|
|
62789
|
+
throw new UsageError2(
|
|
62790
|
+
`CLAUDE_HOME (${humanPath(legacyHome)}) is set, but Claude Code does not read it \u2014 the Skill would install where Claude Code never loads it. Set CLAUDE_CONFIG_DIR=${humanPath(legacyHome)} instead, or unset CLAUDE_HOME to install under the default ~/.claude.`
|
|
62791
|
+
);
|
|
62792
|
+
}
|
|
62793
|
+
const home = resolveHome(env);
|
|
62794
|
+
return { root: path4.resolve(path4.join(home, ".claude", "skills")), legacyNotice: null };
|
|
62795
|
+
}
|
|
62796
|
+
function requireNonBlank(name, value) {
|
|
62797
|
+
if (value === void 0 || value === "") {
|
|
62798
|
+
return null;
|
|
62799
|
+
}
|
|
62800
|
+
if (value.trim().length === 0) {
|
|
62801
|
+
throw new UsageError2(`${name} is set to a blank value; unset it or give it a real path.`);
|
|
62802
|
+
}
|
|
62803
|
+
return value;
|
|
62804
|
+
}
|
|
62805
|
+
function resolveHome(env) {
|
|
62806
|
+
const explicit = requireNonBlank("HOME", env.HOME);
|
|
62807
|
+
if (explicit !== null) {
|
|
62808
|
+
return explicit;
|
|
62809
|
+
}
|
|
62810
|
+
let osHome = "";
|
|
62811
|
+
try {
|
|
62812
|
+
osHome = os.userInfo().homedir;
|
|
62813
|
+
} catch {
|
|
62814
|
+
}
|
|
62815
|
+
if (osHome.trim().length === 0) {
|
|
62816
|
+
throw new UsageError2("HOME is unset or blank and the OS reports no home directory for this user; set HOME to a real path.");
|
|
62817
|
+
}
|
|
62818
|
+
return osHome;
|
|
62819
|
+
}
|
|
62820
|
+
function resolveCodexSkillsRoot(env) {
|
|
62821
|
+
const codexHome = requireNonBlank("CODEX_HOME", env.CODEX_HOME);
|
|
62822
|
+
return path4.resolve(path4.join(codexHome ?? path4.join(resolveHome(env), ".codex"), "skills"));
|
|
62823
|
+
}
|
|
62824
|
+
function installRoot(client, env) {
|
|
62825
|
+
const override = requireNonBlank("ROST_SKILL_INSTALL_ROOT", env.ROST_SKILL_INSTALL_ROOT);
|
|
62826
|
+
if (override !== null) {
|
|
62827
|
+
return { root: path4.resolve(override, client), legacyNotice: null, viaOverride: true };
|
|
62828
|
+
}
|
|
62829
|
+
if (client === "codex") {
|
|
62830
|
+
return { root: resolveCodexSkillsRoot(env), legacyNotice: null, viaOverride: false };
|
|
62831
|
+
}
|
|
62832
|
+
if (client === "claude-code") {
|
|
62833
|
+
return { ...resolveClaudeSkillInstallRoot(env), viaOverride: false };
|
|
62834
|
+
}
|
|
62835
|
+
return { root: path4.resolve(path4.join(resolveHome(env), ".cursor", "skills")), legacyNotice: null, viaOverride: false };
|
|
62836
|
+
}
|
|
62837
|
+
function parseFlags2(args) {
|
|
62838
|
+
const values = /* @__PURE__ */ new Map();
|
|
62839
|
+
const flags = /* @__PURE__ */ new Set();
|
|
62840
|
+
let json2 = false;
|
|
62841
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
62842
|
+
const token = args[index];
|
|
62843
|
+
if (token === void 0) {
|
|
62844
|
+
continue;
|
|
62845
|
+
}
|
|
62846
|
+
if (!token.startsWith("--")) {
|
|
62847
|
+
throw new UsageError2(`Unexpected argument: ${token}`);
|
|
62848
|
+
}
|
|
62849
|
+
const key = token.slice(2);
|
|
62850
|
+
if (key === "json") {
|
|
62851
|
+
json2 = true;
|
|
62852
|
+
continue;
|
|
62853
|
+
}
|
|
62854
|
+
if (key === "client" || key === "tag") {
|
|
62855
|
+
const value = args[index + 1];
|
|
62856
|
+
if (value === void 0 || value.startsWith("--")) {
|
|
62857
|
+
throw new UsageError2(`--${key} requires a value.`);
|
|
62858
|
+
}
|
|
62859
|
+
values.set(key, value);
|
|
62860
|
+
index += 1;
|
|
62861
|
+
continue;
|
|
62862
|
+
}
|
|
62863
|
+
throw new UsageError2(`Unexpected flag: --${key}`);
|
|
62864
|
+
}
|
|
62865
|
+
return { json: json2, values, flags };
|
|
62866
|
+
}
|
|
62867
|
+
function optionalValue2(parsed, key) {
|
|
62868
|
+
const value = parsed.values.get(key);
|
|
62869
|
+
return value !== void 0 && value.length > 0 ? value : void 0;
|
|
62870
|
+
}
|
|
62871
|
+
function parseClient2(value) {
|
|
62872
|
+
if (!CLIENTS.has(value)) {
|
|
62873
|
+
throw new UsageError2("--client must be claude-code, codex, or cursor.");
|
|
62874
|
+
}
|
|
62875
|
+
return value;
|
|
62876
|
+
}
|
|
62877
|
+
|
|
62878
|
+
// src/skills.ts
|
|
62879
|
+
var UsageError3 = class extends Error {
|
|
62880
|
+
};
|
|
62881
|
+
var CLIENTS2 = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
|
|
62882
|
+
function skillsUsage(binName = cliBrand.binName) {
|
|
62883
|
+
return [
|
|
62884
|
+
`${binName} skills list [--json]`,
|
|
62885
|
+
`${binName} skills catalog [--json]`,
|
|
62886
|
+
`${binName} skills get --slug <slug> [--json]`,
|
|
62887
|
+
`${binName} skills file --slug <slug> --path SKILL.md`,
|
|
62888
|
+
`${binName} skills enable rost/<slug> [--json]`,
|
|
62889
|
+
`${binName} skills install <slug-or-rost/slug> [--seat-id <id>] [--client claude-code|codex|cursor] [--version <n>] [--local-only] [--json]`,
|
|
62890
|
+
`${binName} skills install-implementation [--client claude-code|codex|cursor] [--tag <tag>] [--json]`,
|
|
62891
|
+
`${binName} skills sync --seat-id <id> [--client claude-code|codex|cursor] [--json]`,
|
|
62892
|
+
`${binName} skills assign --seat-id <id> (--skill <slug>|--slug <slug>|--skill-version-id <id>) [--approve] [--allow-blocked-proposal] [--json]`,
|
|
62893
|
+
`${binName} skills revoke (--assignment-id <id>|--seat-id <id> --skill <slug>) [--json]`,
|
|
62894
|
+
`${binName} skills publish (--slug <slug>|--skill-version-id <id>) [--allow-warnings] [--json]`
|
|
62895
|
+
].join("\n");
|
|
62896
|
+
}
|
|
62897
|
+
async function runSkills(context, args, env = process.env) {
|
|
62898
|
+
try {
|
|
62899
|
+
const [action, ...rest] = args;
|
|
62900
|
+
if (action === "help" || action === "--help" || action === "-h" || action === void 0) {
|
|
62901
|
+
context.io.stdout.write(`${skillsUsage(context.binName)}
|
|
62902
|
+
`);
|
|
62903
|
+
return action === void 0 ? 1 : 0;
|
|
62904
|
+
}
|
|
62905
|
+
if (action === "list") {
|
|
62906
|
+
return context.run(context.client, "skill.list", {}, void 0);
|
|
62907
|
+
}
|
|
62908
|
+
if (action === "catalog") {
|
|
62909
|
+
const parsed = parseFlags3(rest, /* @__PURE__ */ new Set(["include-unentitled"]));
|
|
62910
|
+
return context.run(context.client, "skill.catalog", {
|
|
62911
|
+
...parsed.flags.has("include-unentitled") ? { include_unentitled: true } : {}
|
|
62912
|
+
}, parsed.json ? void 0 : formatCatalog);
|
|
62913
|
+
}
|
|
62914
|
+
if (action === "get") {
|
|
62915
|
+
const parsed = parseFlags3(rest);
|
|
62916
|
+
return context.run(context.client, "skill.get", { slug: requireValue3(parsed, "slug") }, parsed.json ? void 0 : formatSkillDetail);
|
|
62917
|
+
}
|
|
62918
|
+
if (action === "file") {
|
|
62919
|
+
const parsed = parseFlags3(rest);
|
|
62920
|
+
return context.run(context.client, "skill.file.get", {
|
|
62921
|
+
slug: requireValue3(parsed, "slug"),
|
|
62922
|
+
path: requireValue3(parsed, "path")
|
|
62923
|
+
}, (output) => {
|
|
62924
|
+
const content = asRecord4(output).content;
|
|
62925
|
+
return typeof content === "string" ? content : JSON.stringify(output, null, 2);
|
|
62926
|
+
});
|
|
62927
|
+
}
|
|
62928
|
+
if (action === "enable") {
|
|
62929
|
+
const slug2 = rest[0];
|
|
62930
|
+
if (!slug2 || slug2.startsWith("--")) {
|
|
62931
|
+
throw new UsageError3(`Usage: ${context.binName} skills enable rost/<slug> [--json]`);
|
|
62932
|
+
}
|
|
62933
|
+
const parsed = parseFlags3(rest.slice(1));
|
|
62934
|
+
return context.run(context.client, "skill.enable_catalog", { slug: slug2 }, parsed.json ? void 0 : formatEnable);
|
|
62935
|
+
}
|
|
62936
|
+
if (action === "install") {
|
|
62937
|
+
return await installOne(context, rest, env);
|
|
62938
|
+
}
|
|
62939
|
+
if (action === "sync") {
|
|
62940
|
+
return await syncAssigned(context, rest, env);
|
|
62941
|
+
}
|
|
62942
|
+
if (action === "assign") {
|
|
62943
|
+
const parsed = parseFlags3(rest, /* @__PURE__ */ new Set(["approve", "allow-blocked-proposal"]));
|
|
62944
|
+
const body = withDefined({
|
|
62945
|
+
seat_id: requireValue3(parsed, "seat-id"),
|
|
62946
|
+
status: parsed.flags.has("approve") ? "approved" : "proposed",
|
|
62947
|
+
rationale: optionalValue3(parsed, "rationale") ?? "Skill selected for this Seat.",
|
|
62948
|
+
allow_blocked_proposal: parsed.flags.has("allow-blocked-proposal")
|
|
62949
|
+
}, {
|
|
62950
|
+
slug: optionalValue3(parsed, "skill") ?? optionalValue3(parsed, "slug"),
|
|
62951
|
+
skill_version_id: optionalValue3(parsed, "skill-version-id")
|
|
62952
|
+
});
|
|
62953
|
+
return context.run(context.client, "skill.assign_to_seat", body, parsed.json ? void 0 : formatAssignment, { targetSeatId: body.seat_id });
|
|
62954
|
+
}
|
|
62955
|
+
if (action === "revoke") {
|
|
62956
|
+
const parsed = parseFlags3(rest);
|
|
62957
|
+
const seatId = optionalValue3(parsed, "seat-id");
|
|
62958
|
+
const body = withDefined({}, {
|
|
62959
|
+
assignment_id: optionalValue3(parsed, "assignment-id"),
|
|
62960
|
+
seat_id: seatId,
|
|
62961
|
+
slug: optionalValue3(parsed, "skill") ?? optionalValue3(parsed, "slug"),
|
|
62962
|
+
skill_version_id: optionalValue3(parsed, "skill-version-id"),
|
|
62963
|
+
rationale: optionalValue3(parsed, "rationale")
|
|
62964
|
+
});
|
|
62965
|
+
return context.run(context.client, "skill.revoke_from_seat", body, parsed.json ? void 0 : formatRevoke, seatId ? { targetSeatId: seatId } : {});
|
|
62966
|
+
}
|
|
62967
|
+
if (action === "publish") {
|
|
62968
|
+
const parsed = parseFlags3(rest, /* @__PURE__ */ new Set(["allow-warnings"]));
|
|
62969
|
+
const body = withDefined({
|
|
62970
|
+
allow_warnings: parsed.flags.has("allow-warnings")
|
|
62971
|
+
}, {
|
|
62972
|
+
slug: optionalValue3(parsed, "slug"),
|
|
62973
|
+
skill_version_id: optionalValue3(parsed, "skill-version-id")
|
|
62974
|
+
});
|
|
62975
|
+
return context.run(context.client, "skill.publish", body, parsed.json ? void 0 : formatPublish);
|
|
62976
|
+
}
|
|
62977
|
+
throw new UsageError3(`Unknown skills action: ${action}`);
|
|
62978
|
+
} catch (error51) {
|
|
62979
|
+
if (error51 instanceof UsageError3) {
|
|
62980
|
+
context.io.stderr.write(`${error51.message}
|
|
62981
|
+
`);
|
|
62982
|
+
return 1;
|
|
62983
|
+
}
|
|
62984
|
+
if (error51 instanceof CommandClientError) {
|
|
62985
|
+
return printCommandError2(context.io, error51);
|
|
62986
|
+
}
|
|
62987
|
+
throw error51;
|
|
62988
|
+
}
|
|
62989
|
+
}
|
|
62990
|
+
async function installOne(context, args, env) {
|
|
62991
|
+
const slug2 = args[0];
|
|
62992
|
+
if (!slug2 || slug2.startsWith("--")) {
|
|
62993
|
+
throw new UsageError3(`Usage: ${context.binName} skills install <slug-or-rost/slug> [--seat-id <id>] [--client claude-code|codex|cursor] [--version <n>] [--local-only] [--json]`);
|
|
62994
|
+
}
|
|
62995
|
+
const parsed = parseFlags3(args.slice(1), /* @__PURE__ */ new Set(["local-only"]));
|
|
62996
|
+
const client = parseClient3(optionalValue3(parsed, "client") ?? "codex");
|
|
62997
|
+
const seatId = optionalValue3(parsed, "seat-id");
|
|
62998
|
+
const body = withDefined({
|
|
62999
|
+
slug: slug2,
|
|
63000
|
+
client,
|
|
63001
|
+
local_only: parsed.flags.has("local-only")
|
|
63002
|
+
}, {
|
|
63003
|
+
seat_id: seatId,
|
|
63004
|
+
version: optionalNumber2(parsed, "version")
|
|
63005
|
+
});
|
|
63006
|
+
const options = seatId ? { targetSeatId: seatId } : {};
|
|
63007
|
+
const output = await executeInstallCommand(context, body, options);
|
|
63008
|
+
const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: false });
|
|
63009
|
+
const outputRecord = asRecord4(output);
|
|
63010
|
+
context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
|
|
63011
|
+
` : `${formatLocalSummary(summary)}
|
|
63012
|
+
`);
|
|
63013
|
+
return 0;
|
|
63014
|
+
}
|
|
63015
|
+
async function syncAssigned(context, args, env) {
|
|
63016
|
+
const parsed = parseFlags3(args);
|
|
63017
|
+
const seatId = requireValue3(parsed, "seat-id");
|
|
63018
|
+
const client = parseClient3(optionalValue3(parsed, "client") ?? "codex");
|
|
63019
|
+
const output = await executeSkillCommand(context, "skill.sync_local", {
|
|
63020
|
+
seat_id: seatId,
|
|
63021
|
+
client
|
|
63022
|
+
}, { targetSeatId: seatId });
|
|
63023
|
+
const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: true });
|
|
63024
|
+
const outputRecord = asRecord4(output);
|
|
63025
|
+
context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
|
|
63026
|
+
` : `${formatLocalSummary(summary)}
|
|
63027
|
+
`);
|
|
63028
|
+
return 0;
|
|
63029
|
+
}
|
|
63030
|
+
async function executeInstallCommand(context, body, options) {
|
|
63031
|
+
const slug2 = typeof body.slug === "string" ? body.slug : "";
|
|
63032
|
+
if (slug2.startsWith("rost/") && body.local_only !== true) {
|
|
63033
|
+
try {
|
|
63034
|
+
return await executeSkillCommand(context, "skill.install_local", { ...body, local_only: true }, options);
|
|
63035
|
+
} catch (error51) {
|
|
63036
|
+
if (!isCommandConflict(error51)) {
|
|
63037
|
+
throw error51;
|
|
63038
|
+
}
|
|
63144
63039
|
}
|
|
63145
63040
|
}
|
|
63041
|
+
return executeSkillCommand(context, "skill.install_local", body, options);
|
|
63042
|
+
}
|
|
63043
|
+
async function executeSkillCommand(context, commandId, body, options = {}) {
|
|
63146
63044
|
try {
|
|
63147
|
-
|
|
63045
|
+
const output = await context.client.execute(commandId, body, options);
|
|
63046
|
+
return output.output;
|
|
63148
63047
|
} catch (error51) {
|
|
63149
|
-
|
|
63048
|
+
const pending = context.io.interactive === true ? pendingConfirmationFromError(error51) : null;
|
|
63049
|
+
if (pending && pending.commandId === commandId) {
|
|
63050
|
+
renderPendingConfirmationGate2(context.io, pending);
|
|
63051
|
+
context.io.stderr.write(`Approving ${pending.commandId} confirmation ${pending.confirmationId} as the interactive CLI user.
|
|
63052
|
+
`);
|
|
63053
|
+
const approved = await context.client.execute("confirmation.approve", { confirmation_id: pending.confirmationId, reviewed: true });
|
|
63054
|
+
return asRecord4(approved.output).output ?? approved.output;
|
|
63055
|
+
}
|
|
63056
|
+
throw error51;
|
|
63150
63057
|
}
|
|
63151
63058
|
}
|
|
63152
|
-
function
|
|
63153
|
-
|
|
63154
|
-
|
|
63155
|
-
|
|
63156
|
-
|
|
63157
|
-
|
|
63059
|
+
function applyLocalSkillChanges(output, client, env, options) {
|
|
63060
|
+
const root = installRoot2(client, env);
|
|
63061
|
+
const changes = writeInstalls(output, root);
|
|
63062
|
+
if (options.removeRevoked) {
|
|
63063
|
+
changes.push(...removeRevokedInstalls(output, root));
|
|
63064
|
+
}
|
|
63065
|
+
return changes;
|
|
63066
|
+
}
|
|
63067
|
+
function writeInstalls(output, root) {
|
|
63068
|
+
const installs = asArray2(asRecord4(output).installs);
|
|
63069
|
+
return installs.map((entry) => {
|
|
63070
|
+
const install = asRecord4(entry);
|
|
63071
|
+
const slug2 = safeLocalSlug(String(install.slug ?? ""));
|
|
63072
|
+
const metadata = asRecord4(install.metadata);
|
|
63073
|
+
const files = asArray2(install.files).map(asRecord4);
|
|
63074
|
+
const dir = path5.join(root, slug2);
|
|
63075
|
+
const metadataPath = path5.join(dir, ".rost-skill.json");
|
|
63076
|
+
const expectedHash = String(metadata.content_sha256 ?? "");
|
|
63077
|
+
if (existingHash(metadataPath) === expectedHash) {
|
|
63078
|
+
return { slug: slug2, dir, status: "unchanged", files: files.length };
|
|
63158
63079
|
}
|
|
63159
|
-
|
|
63080
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
63081
|
+
mkdirSync2(dir, { recursive: true });
|
|
63082
|
+
for (const file2 of files) {
|
|
63083
|
+
const relativePath = safeRelativePath2(String(file2.path ?? ""));
|
|
63084
|
+
const content = String(file2.content ?? "");
|
|
63085
|
+
const target = path5.join(dir, relativePath);
|
|
63086
|
+
mkdirSync2(path5.dirname(target), { recursive: true });
|
|
63087
|
+
writeFileSync2(target, content, "utf8");
|
|
63088
|
+
}
|
|
63089
|
+
writeFileSync2(metadataPath, `${JSON.stringify(metadata, null, 2)}
|
|
63090
|
+
`, "utf8");
|
|
63091
|
+
return { slug: slug2, dir, status: "installed", files: files.length };
|
|
63092
|
+
});
|
|
63093
|
+
}
|
|
63094
|
+
function removeRevokedInstalls(output, root) {
|
|
63095
|
+
const installs = new Set(asArray2(asRecord4(output).installs).map((entry) => {
|
|
63096
|
+
const slug2 = asRecord4(entry).slug;
|
|
63097
|
+
return typeof slug2 === "string" ? slug2 : "";
|
|
63098
|
+
}));
|
|
63099
|
+
const revoked = asArray2(asRecord4(output).revoked);
|
|
63100
|
+
const changes = [];
|
|
63101
|
+
for (const entry of revoked) {
|
|
63102
|
+
const slug2 = safeLocalSlug(String(asRecord4(entry).slug ?? ""));
|
|
63103
|
+
if (installs.has(slug2)) {
|
|
63104
|
+
continue;
|
|
63105
|
+
}
|
|
63106
|
+
const dir = path5.join(root, slug2);
|
|
63107
|
+
if (!existsSync(dir)) {
|
|
63108
|
+
continue;
|
|
63109
|
+
}
|
|
63110
|
+
rmSync2(dir, { recursive: true, force: true });
|
|
63111
|
+
changes.push({ slug: slug2, dir, status: "removed", files: 0 });
|
|
63160
63112
|
}
|
|
63161
|
-
return
|
|
63113
|
+
return changes;
|
|
63162
63114
|
}
|
|
63163
|
-
function
|
|
63164
|
-
const
|
|
63165
|
-
|
|
63166
|
-
|
|
63167
|
-
const disagrees = legacyHome !== null && path5.resolve(legacyHome) !== path5.resolve(configDir);
|
|
63168
|
-
return {
|
|
63169
|
-
root: path5.join(configDir, "skills"),
|
|
63170
|
-
legacyNotice: disagrees ? `NOTICE: CLAUDE_HOME (${legacyHome}) is ignored; Claude Code reads CLAUDE_CONFIG_DIR (${configDir}).` : null
|
|
63171
|
-
};
|
|
63115
|
+
function installRoot2(client, env) {
|
|
63116
|
+
const override = env.ROST_SKILL_INSTALL_ROOT;
|
|
63117
|
+
if (override && override.length > 0) {
|
|
63118
|
+
return path5.resolve(override, client);
|
|
63172
63119
|
}
|
|
63173
|
-
if (
|
|
63174
|
-
|
|
63175
|
-
|
|
63176
|
-
|
|
63177
|
-
|
|
63120
|
+
if (client === "codex") {
|
|
63121
|
+
try {
|
|
63122
|
+
return path5.join(resolveCodexSkillsRoot(env), "rost");
|
|
63123
|
+
} catch (error51) {
|
|
63124
|
+
if (error51 instanceof UsageError2) {
|
|
63125
|
+
throw new UsageError3(error51.message);
|
|
63126
|
+
}
|
|
63127
|
+
throw error51;
|
|
63128
|
+
}
|
|
63129
|
+
}
|
|
63130
|
+
if (client === "claude-code") {
|
|
63131
|
+
return path5.join(env.CLAUDE_HOME ?? path5.join(os2.homedir(), ".claude"), "skills", "rost");
|
|
63178
63132
|
}
|
|
63179
|
-
|
|
63180
|
-
return { root: path5.join(home, ".claude", "skills"), legacyNotice: null };
|
|
63133
|
+
return path5.join(os2.homedir(), ".cursor", "skills", "rost");
|
|
63181
63134
|
}
|
|
63182
|
-
function
|
|
63183
|
-
|
|
63184
|
-
|
|
63135
|
+
function safeRelativePath2(value) {
|
|
63136
|
+
const normalized = value.trim().replaceAll("\\", "/").replace(/^\.\/+/, "");
|
|
63137
|
+
if (!normalized || normalized.startsWith("/") || normalized.includes("//") || normalized.split("/").includes("..")) {
|
|
63138
|
+
throw new UsageError3("Server returned an invalid skill file path.");
|
|
63185
63139
|
}
|
|
63186
|
-
|
|
63187
|
-
|
|
63140
|
+
return normalized;
|
|
63141
|
+
}
|
|
63142
|
+
function safeLocalSlug(value) {
|
|
63143
|
+
if (!/^[a-z0-9][a-z0-9-]{1,80}$/.test(value)) {
|
|
63144
|
+
throw new UsageError3("Server returned an invalid skill slug.");
|
|
63188
63145
|
}
|
|
63189
63146
|
return value;
|
|
63190
63147
|
}
|
|
63191
|
-
function
|
|
63192
|
-
|
|
63193
|
-
|
|
63194
|
-
return { root: path5.resolve(override, client), legacyNotice: null };
|
|
63195
|
-
}
|
|
63196
|
-
if (client === "codex") {
|
|
63197
|
-
return { root: path5.join(env.CODEX_HOME ?? path5.join(os2.homedir(), ".codex"), "skills"), legacyNotice: null };
|
|
63148
|
+
function existingHash(metadataPath) {
|
|
63149
|
+
if (!existsSync(metadataPath)) {
|
|
63150
|
+
return null;
|
|
63198
63151
|
}
|
|
63199
|
-
|
|
63200
|
-
|
|
63152
|
+
try {
|
|
63153
|
+
const parsed = JSON.parse(readFileSync4(metadataPath, "utf8"));
|
|
63154
|
+
const hash2 = asRecord4(parsed).content_sha256;
|
|
63155
|
+
return typeof hash2 === "string" ? hash2 : null;
|
|
63156
|
+
} catch {
|
|
63157
|
+
return null;
|
|
63201
63158
|
}
|
|
63202
|
-
return { root: path5.join(os2.homedir(), ".cursor", "skills"), legacyNotice: null };
|
|
63203
63159
|
}
|
|
63204
|
-
function parseFlags3(args) {
|
|
63160
|
+
function parseFlags3(args, booleanFlags = /* @__PURE__ */ new Set()) {
|
|
63205
63161
|
const values = /* @__PURE__ */ new Map();
|
|
63206
63162
|
const flags = /* @__PURE__ */ new Set();
|
|
63207
63163
|
let json2 = false;
|
|
@@ -63218,22 +63174,41 @@ function parseFlags3(args) {
|
|
|
63218
63174
|
json2 = true;
|
|
63219
63175
|
continue;
|
|
63220
63176
|
}
|
|
63221
|
-
if (key
|
|
63222
|
-
|
|
63223
|
-
|
|
63224
|
-
|
|
63225
|
-
|
|
63226
|
-
|
|
63227
|
-
|
|
63177
|
+
if (booleanFlags.has(key)) {
|
|
63178
|
+
flags.add(key);
|
|
63179
|
+
continue;
|
|
63180
|
+
}
|
|
63181
|
+
const value = args[index + 1];
|
|
63182
|
+
if (value === void 0 || value.startsWith("--")) {
|
|
63183
|
+
flags.add(key);
|
|
63228
63184
|
continue;
|
|
63229
63185
|
}
|
|
63230
|
-
|
|
63186
|
+
values.set(key, value);
|
|
63187
|
+
index += 1;
|
|
63231
63188
|
}
|
|
63232
63189
|
return { json: json2, values, flags };
|
|
63233
63190
|
}
|
|
63191
|
+
function requireValue3(parsed, key) {
|
|
63192
|
+
const value = optionalValue3(parsed, key);
|
|
63193
|
+
if (value === void 0) {
|
|
63194
|
+
throw new UsageError3(`Missing required --${key} value.`);
|
|
63195
|
+
}
|
|
63196
|
+
return value;
|
|
63197
|
+
}
|
|
63234
63198
|
function optionalValue3(parsed, key) {
|
|
63235
63199
|
const value = parsed.values.get(key);
|
|
63236
|
-
return value
|
|
63200
|
+
return value && value.length > 0 ? value : void 0;
|
|
63201
|
+
}
|
|
63202
|
+
function optionalNumber2(parsed, key) {
|
|
63203
|
+
const value = optionalValue3(parsed, key);
|
|
63204
|
+
if (value === void 0) {
|
|
63205
|
+
return void 0;
|
|
63206
|
+
}
|
|
63207
|
+
const parsedNumber = Number(value);
|
|
63208
|
+
if (!Number.isSafeInteger(parsedNumber) || parsedNumber <= 0) {
|
|
63209
|
+
throw new UsageError3(`--${key} must be a positive integer.`);
|
|
63210
|
+
}
|
|
63211
|
+
return parsedNumber;
|
|
63237
63212
|
}
|
|
63238
63213
|
function parseClient3(value) {
|
|
63239
63214
|
if (!CLIENTS2.has(value)) {
|
|
@@ -63241,6 +63216,118 @@ function parseClient3(value) {
|
|
|
63241
63216
|
}
|
|
63242
63217
|
return value;
|
|
63243
63218
|
}
|
|
63219
|
+
function isCommandConflict(error51) {
|
|
63220
|
+
return error51 instanceof CommandClientError && error51.status === 409;
|
|
63221
|
+
}
|
|
63222
|
+
function withDefined(base, extra) {
|
|
63223
|
+
const next = { ...base };
|
|
63224
|
+
for (const [key, value] of Object.entries(extra)) {
|
|
63225
|
+
if (value !== void 0) {
|
|
63226
|
+
next[key] = value;
|
|
63227
|
+
}
|
|
63228
|
+
}
|
|
63229
|
+
return next;
|
|
63230
|
+
}
|
|
63231
|
+
function asRecord4(value) {
|
|
63232
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
63233
|
+
}
|
|
63234
|
+
function asArray2(value) {
|
|
63235
|
+
return Array.isArray(value) ? value : [];
|
|
63236
|
+
}
|
|
63237
|
+
function formatCatalog(output) {
|
|
63238
|
+
const entries = asArray2(asRecord4(output).catalog);
|
|
63239
|
+
if (entries.length === 0) {
|
|
63240
|
+
return "No ROST catalog skills are available for this tenant.";
|
|
63241
|
+
}
|
|
63242
|
+
return entries.map((entry) => {
|
|
63243
|
+
const record2 = asRecord4(entry);
|
|
63244
|
+
return `${record2.catalog_slug} [${record2.tier}/${record2.entitlement_status}] ${record2.name}`;
|
|
63245
|
+
}).join("\n");
|
|
63246
|
+
}
|
|
63247
|
+
function formatSkillDetail(output) {
|
|
63248
|
+
const skill = asRecord4(asRecord4(output).skill);
|
|
63249
|
+
return `${skill.slug} v${skill.latest_version ?? "\u2014"} [${skill.status}] ${skill.name}
|
|
63250
|
+
${skill.description}`;
|
|
63251
|
+
}
|
|
63252
|
+
function formatEnable(output) {
|
|
63253
|
+
const skill = asRecord4(asRecord4(output).skill);
|
|
63254
|
+
const catalog = asRecord4(asRecord4(output).catalog);
|
|
63255
|
+
return `Enabled ${catalog.catalog_slug} as ${skill.slug} v${skill.version} (${skill.skill_version_id})`;
|
|
63256
|
+
}
|
|
63257
|
+
function formatAssignment(output) {
|
|
63258
|
+
const assignment = asRecord4(asRecord4(output).assignment);
|
|
63259
|
+
return `${assignment.status} ${assignment.slug} for seat ${assignment.seat_id}; sync: ${String(asRecord4(output).local_sync_next_step)}`;
|
|
63260
|
+
}
|
|
63261
|
+
function formatRevoke(output) {
|
|
63262
|
+
const record2 = asRecord4(output);
|
|
63263
|
+
return `Revoked ${record2.slug} for seat ${record2.seat_id}; sync: ${record2.local_sync_next_step}`;
|
|
63264
|
+
}
|
|
63265
|
+
function formatPublish(output) {
|
|
63266
|
+
const skill = asRecord4(asRecord4(output).skill);
|
|
63267
|
+
return `Published ${skill.slug} v${skill.version} (${skill.skill_version_id})`;
|
|
63268
|
+
}
|
|
63269
|
+
function formatLocalSummary(summary) {
|
|
63270
|
+
if (summary.length === 0) {
|
|
63271
|
+
return "No skills to install.";
|
|
63272
|
+
}
|
|
63273
|
+
return summary.map((entry) => `${entry.status} ${entry.slug} -> ${entry.dir} (${entry.files} files)`).join("\n");
|
|
63274
|
+
}
|
|
63275
|
+
function printCommandError2(io, error51) {
|
|
63276
|
+
if (error51.response && !error51.response.ok) {
|
|
63277
|
+
const payload = {
|
|
63278
|
+
code: error51.response.error.code,
|
|
63279
|
+
message: error51.response.error.message,
|
|
63280
|
+
...error51.response.error.details === void 0 ? {} : { details: error51.response.error.details },
|
|
63281
|
+
...error51.response.error.help === void 0 ? {} : { help: error51.response.error.help },
|
|
63282
|
+
...error51.response.requestId === void 0 ? {} : { requestId: error51.response.requestId }
|
|
63283
|
+
};
|
|
63284
|
+
io.stderr.write(`${redactForLog(payload)}
|
|
63285
|
+
`);
|
|
63286
|
+
if (error51.response.error.help !== void 0) {
|
|
63287
|
+
io.stderr.write(`\u2192 try: ${redactForLog(error51.response.error.help)}
|
|
63288
|
+
`);
|
|
63289
|
+
}
|
|
63290
|
+
return error51.status === 409 ? 3 : error51.status >= 400 && error51.status < 500 ? 1 : 2;
|
|
63291
|
+
}
|
|
63292
|
+
io.stderr.write(`${redactForLog(error51.message)}
|
|
63293
|
+
`);
|
|
63294
|
+
return error51.status >= 400 && error51.status < 500 ? 1 : 2;
|
|
63295
|
+
}
|
|
63296
|
+
function pendingConfirmationFromError(error51) {
|
|
63297
|
+
if (!(error51 instanceof CommandClientError) || !error51.response || error51.response.ok || error51.response.error.code !== "COMMAND_CONFIRMATION_REQUIRED") {
|
|
63298
|
+
return null;
|
|
63299
|
+
}
|
|
63300
|
+
const details = asRecord4(error51.response.error.details);
|
|
63301
|
+
const confirmationId = details.confirmationId;
|
|
63302
|
+
const commandId = details.commandId;
|
|
63303
|
+
if (typeof confirmationId !== "string" || confirmationId.length === 0 || typeof commandId !== "string" || commandId.length === 0) {
|
|
63304
|
+
return null;
|
|
63305
|
+
}
|
|
63306
|
+
const summary = typeof details.summary === "string" ? details.summary : void 0;
|
|
63307
|
+
const riskLevel = typeof details.riskLevel === "string" ? details.riskLevel : void 0;
|
|
63308
|
+
return {
|
|
63309
|
+
confirmationId,
|
|
63310
|
+
commandId,
|
|
63311
|
+
...summary === void 0 ? {} : { summary },
|
|
63312
|
+
...riskLevel === void 0 ? {} : { riskLevel },
|
|
63313
|
+
...details.diff === void 0 ? {} : { diff: details.diff }
|
|
63314
|
+
};
|
|
63315
|
+
}
|
|
63316
|
+
function renderPendingConfirmationGate2(io, pending) {
|
|
63317
|
+
if (pending.summary !== void 0) {
|
|
63318
|
+
io.stderr.write(`${redactForLog(pending.summary)}
|
|
63319
|
+
`);
|
|
63320
|
+
}
|
|
63321
|
+
if (pending.riskLevel !== void 0) {
|
|
63322
|
+
io.stderr.write(`Risk level: ${pending.riskLevel}
|
|
63323
|
+
`);
|
|
63324
|
+
}
|
|
63325
|
+
if (pending.diff !== void 0) {
|
|
63326
|
+
io.stderr.write(`Diff:
|
|
63327
|
+
${redactForLog(pending.diff)}
|
|
63328
|
+
`);
|
|
63329
|
+
}
|
|
63330
|
+
}
|
|
63244
63331
|
|
|
63245
63332
|
// src/doctor.ts
|
|
63246
63333
|
import { execFile as execFileCallback3 } from "node:child_process";
|