@rosthq/cli 0.7.141 → 0.7.143
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 +90 -48
- package/dist/index.js.map +3 -3
- package/dist/skill-install.d.ts +17 -0
- package/dist/skill-install.d.ts.map +1 -1
- package/dist/skill-trust.d.ts +6 -1
- package/dist/skill-trust.d.ts.map +1 -1
- package/dist/skills.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31855,7 +31855,25 @@ var commandViolationSchema = external_exports.object({
|
|
|
31855
31855
|
/** Advisory human text. Do not parse. */
|
|
31856
31856
|
message: external_exports.string().min(1),
|
|
31857
31857
|
/** The violated bound, when the issue carries one (`max`/`min` on arrays, strings, numbers). */
|
|
31858
|
-
limit: external_exports.number().optional()
|
|
31858
|
+
limit: external_exports.number().optional(),
|
|
31859
|
+
/**
|
|
31860
|
+
* Schema vocabulary describing what was expected — a type name, a declared enum
|
|
31861
|
+
* value set, a format identifier, or a bound direction, FOR EVERY IN-REPO
|
|
31862
|
+
* SCHEMA. It is not structurally guaranteed to stay that way: a `superRefine`
|
|
31863
|
+
* can forge `issue.expected` / `issue.format` from caller data, so the value is
|
|
31864
|
+
* sanitized, capped, and secret-gated (`gateExpected`) before it can reach here
|
|
31865
|
+
* rather than trusted outright. It is never the caller's own parsed VALUE,
|
|
31866
|
+
* which is what `received` describes.
|
|
31867
|
+
*/
|
|
31868
|
+
expected: external_exports.string().optional(),
|
|
31869
|
+
/**
|
|
31870
|
+
* A type descriptor of the caller's own submitted value at the resolved path:
|
|
31871
|
+
* `missing`/`null`/`string`/`number`/`boolean`/`array`/`object`, with counts for
|
|
31872
|
+
* size violations — or, for `unrecognized_keys`, the caller's own unexpected key
|
|
31873
|
+
* NAMES (proven against the payload, secret-gated, capped). NEVER the value
|
|
31874
|
+
* itself (invariant #5).
|
|
31875
|
+
*/
|
|
31876
|
+
received: external_exports.string().optional()
|
|
31859
31877
|
}).strict();
|
|
31860
31878
|
|
|
31861
31879
|
// ../../packages/protocol/src/command-introspection.ts
|
|
@@ -53026,7 +53044,7 @@ When operating through CLI or MCP, create fewer seats first and ask for confirma
|
|
|
53026
53044
|
order: 30,
|
|
53027
53045
|
title: "Charter design playbook",
|
|
53028
53046
|
summary: "How to draft strong Charters that work for human, agent, and hybrid seats.",
|
|
53029
|
-
version: "2026-07-
|
|
53047
|
+
version: "2026-07-31.2",
|
|
53030
53048
|
public: true,
|
|
53031
53049
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
53032
53050
|
stages: ["charter_design", "staffing"],
|
|
@@ -53081,7 +53099,7 @@ List and read first, then draft, then leave activation to a human.
|
|
|
53081
53099
|
|
|
53082
53100
|
- List: \`{{cli}} command charter.list --json '{}'\` or \`rost_list_charters\` (scope: tenant). Read one with \`charter.get\` / \`rost_get_charter\`.
|
|
53083
53101
|
- Draft one: \`{{cli}} command charter.draft --json '{"seat_id":"<seat-id>"}'\` (scope: seat or tenant-admin); draft every eligible seat with \`charter.draft_all\` / \`rost_draft_all_charters\`. Draft commands are not gated \u2014 they produce a draft \`charter_version_id\`. \`charter.draft_all\` drafts every **active or vacant** non-reserved seat \u2014 a vacant seat can receive a conservative draft Charter before it is staffed, since Charters attach to seats, not people, and drafting is not activation: **Charter activation stays a separate, human-gated step** (\`charter.approve\`) whether or not the seat is staffed. Reserved system paths (e.g. \`system.ops\`, Forge role seats) are excluded entirely, before both drafting and skip accounting \u2014 they never appear in \`drafted\`, \`skipped\`, or \`skipped_seats\`. It returns the real result: \`{ drafted, skipped, draft_ids, skipped_seats: [{ seat_id, reason }], effect }\` where \`reason\` is \`seat_not_active\` (the seat is still a graph-authoring draft, not yet active or vacant) or \`charter_exists\` (it already has a draft/active Charter). Read \`skipped_seats\` to see why a freshly-built graph drafted fewer Charters than it has seats \u2014 among non-reserved seats.
|
|
53084
|
-
- **Never read a governed command's success as "it changed something."** Every converted mutating command carries an \`effect\` block \u2014 \`{ attempted, applied, skipped, outcome, skips: [{ target_kind, target_id, reason }], skips_omitted }\`, where \`outcome\` is \`applied\`, \`partial\`, or \`none\`, and \`skips.length + skips_omitted\` always equals \`skipped\` (\`skips\` is capped at 50 entries, so a very large sweep itemises the first 50 and counts the rest \u2014 the arithmetic still adds up) \u2014 so you can branch on it without knowing any one command's field names. **Read it where your surface actually delivers it:** \`response.output.effect\` over HTTP, and \`effect\` on the unwrapped command output in CLI and MCP. (The executor also carries a copy on its internal result envelope, but the HTTP adapter does not re-emit it, so \`response.effect\` is \`undefined\` \u2014 do not read it there.) \`outcome: "none"\` means the call succeeded and changed nothing; \`skips\` tells you why, per target, as stable codes you can branch on rather than prose. Check it and self-correct in the same run instead of reporting a build that did nothing: a second \`charter.draft_all\` over a graph whose seats already have a draft or active Charter returns \`outcome: "none"\` with one \`charter_exists\` skip per seat, and a graph whose seats are still graph-authoring \`draft\` seats returns \`outcome: "none"\` with one \`seat_not_active\` skip per seat. (A freshly built graph of **vacant** seats is not one of these cases \u2014 vacant seats are eligible and do get drafted, per the bullet above.) A **converted** command that skipped work without naming a reason fails its own output validation, so a silent zero-effect success is not a shape *those* commands can return. **This is not yet an API-wide guarantee** \u2014 most mutating commands are not converted yet, and an unconverted bulk or sweep command still returns a bare success that changed nothing: \`confirmation.dismiss_stale\` with no expired confirmations returns \`{ dismissed_count: 0, dismissed_ids: [] }\` with no \`effect\` at all. Treat a missing \`effect\` as "this command cannot tell you", never as "it worked" \u2014 for those, verify against live state before reporting the step done. Input that violates a schema is reported the same way rather than swallowed, naming the offending field path, the stable issue \`code\`, and the violated \`limit\` \u2014 e.g. a 6-principle Compass against \`max(5)\` gives \`{ path: "doc.principles", code: "too_big", limit: 5 }\`. **The field sits at a different place on each surface:** CLI and HTTP callers read it at \`error.details.violations\` (alongside the legacy \`fieldErrors\`), while MCP tool callers read \`violations\` at the **top level** of the error result, next to \`code\` and \`help\` \u2014 MCP results have no \`details\` envelope, so \`details.violations\` is \`undefined\` there.
|
|
53102
|
+
- **Never read a governed command's success as "it changed something."** Every converted mutating command carries an \`effect\` block \u2014 \`{ attempted, applied, skipped, outcome, skips: [{ target_kind, target_id, reason }], skips_omitted }\`, where \`outcome\` is \`applied\`, \`partial\`, or \`none\`, and \`skips.length + skips_omitted\` always equals \`skipped\` (\`skips\` is capped at 50 entries, so a very large sweep itemises the first 50 and counts the rest \u2014 the arithmetic still adds up) \u2014 so you can branch on it without knowing any one command's field names. **Read it where your surface actually delivers it:** \`response.output.effect\` over HTTP, and \`effect\` on the unwrapped command output in CLI and MCP. (The executor also carries a copy on its internal result envelope, but the HTTP adapter does not re-emit it, so \`response.effect\` is \`undefined\` \u2014 do not read it there.) \`outcome: "none"\` means the call succeeded and changed nothing; \`skips\` tells you why, per target, as stable codes you can branch on rather than prose. Check it and self-correct in the same run instead of reporting a build that did nothing: a second \`charter.draft_all\` over a graph whose seats already have a draft or active Charter returns \`outcome: "none"\` with one \`charter_exists\` skip per seat, and a graph whose seats are still graph-authoring \`draft\` seats returns \`outcome: "none"\` with one \`seat_not_active\` skip per seat. (A freshly built graph of **vacant** seats is not one of these cases \u2014 vacant seats are eligible and do get drafted, per the bullet above.) A **converted** command that skipped work without naming a reason fails its own output validation, so a silent zero-effect success is not a shape *those* commands can return. **This is not yet an API-wide guarantee** \u2014 most mutating commands are not converted yet, and an unconverted bulk or sweep command still returns a bare success that changed nothing: \`confirmation.dismiss_stale\` with no expired confirmations returns \`{ dismissed_count: 0, dismissed_ids: [] }\` with no \`effect\` at all. Treat a missing \`effect\` as "this command cannot tell you", never as "it worked" \u2014 for those, verify against live state before reporting the step done. Input that violates a schema is reported the same way rather than swallowed, naming the offending field path, the stable issue \`code\`, and the violated \`limit\` \u2014 e.g. a 6-principle Compass against \`max(5)\` gives \`{ path: "doc.principles", code: "too_big", limit: 5, expected: "<= 5", received: "array (6 items)" }\`. Each violation also carries \`expected\` (what the schema declares at that spot \u2014 a type, format, allowed values, or bound; for a cross-field or business-rule check it is the fixed marker \`a value satisfying the schema's custom check (see message)\`, telling you to read \`message\` for the authored specifics) and \`received\` (the KIND of value you sent \u2014 \`string\`, \`number\`, \`array (6 items)\`, \`missing\` \u2014 or, for undeclared extra keys, their proven, secret-gated names \u2014 never the value itself). **The field sits at a different place on each surface:** CLI and HTTP callers read it at \`error.details.violations\` (alongside the legacy \`fieldErrors\`), while MCP tool callers read \`violations\` at the **top level** of the error result, next to \`code\` and \`help\` \u2014 MCP results have no \`details\` envelope, so \`details.violations\` is \`undefined\` there. Paths are real JSON paths into YOUR OWN submitted payload: a caller-supplied key (e.g. inside a \`capabilities\` or \`headers\` map) is echoed exactly, and an array position reports as \`xs[3].on\`, because a path into input you supplied tells you nothing you do not already hold. A segment shows as \`*\` only when the server could not prove it walks your payload \u2014 a segment no schema declares and your payload does not contain (a refinement can report a path that was never in your input) \u2014 or when the segment name is itself credential-shaped and withheld as secret material. So read \`*\` as **"withheld, unproven"**, not as "this was your key" \u2014 the field names around it are still exact, and the \`code\` still tells you which constraint failed. \`limit\` is likewise only present when the schema itself declares that bound, so its absence means "not proven", not "no bound". The error envelope's \`help\` names the exact next step \u2014 \`rost command schema <id>\` prints the full input contract, and when the payload came from \`--input-file\`, fix the file and re-run it.
|
|
53085
53103
|
- Edit a draft: \`charter.update_draft\` / \`rost_update_charter_draft\` with the \`charter_version_id\` and the new \`doc\`.
|
|
53086
53104
|
|
|
53087
53105
|
In the app, \`/charters\` is both a drafting queue and a governance library. It shows active Charters, draft amendments, seats missing Charters, unsigned manifests, and steward-review gaps. \`/charters/builder\` is the seat picker for Charter Builder: start there when you need to draft a missing Charter, continue an amendment, or review which seats are still missing operating contracts. Opening a live Charter is a review mode: read the active version, inspect its governance state, and open an amendment draft when the job needs to change. Active Charter content is never silently edited in place. Both \`charter.draft_all\` triggers on this page \u2014 the "Draft missing Charters" empty-queue card and the "Draft gaps" header CTA \u2014 render the run's \`effect\` distinctly rather than a bare success: a full run (\`outcome: "applied"\`) shows an ordinary "Done" state, a run that skipped some seats (\`"partial"\`) shows "Partial effect" plus how many seats were skipped and why (the app aggregates them into product copy \u2014 "17 skipped: seat not active" \u2014 while CLI and MCP callers still get the per-target \`target_id\`/\`reason\` values from the contract), and a run that drafted nothing (\`"none"\`) shows "No effect" the same way \u2014 so a human running this from the UI gets the same self-correction signal the effect contract gives a CLI/MCP/HTTP caller, above.
|
|
@@ -53711,7 +53729,7 @@ External connectors are being rolled out provider by provider, conservatively (r
|
|
|
53711
53729
|
order: 48,
|
|
53712
53730
|
title: "CLI and MCP installation guide",
|
|
53713
53731
|
summary: "Install the public CLI, register remote token-backed MCP clients, and find the full command and tool catalog.",
|
|
53714
|
-
version: "2026-07-31.
|
|
53732
|
+
version: "2026-07-31.3",
|
|
53715
53733
|
public: true,
|
|
53716
53734
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
53717
53735
|
stages: ["company_setup", "staffing"],
|
|
@@ -54351,7 +54369,7 @@ Skills wrapper help:
|
|
|
54351
54369
|
{{cli}} skills publish (--slug <slug>|--skill-version-id <id>) [--allow-warnings] [--json]
|
|
54352
54370
|
\`\`\`
|
|
54353
54371
|
|
|
54354
|
-
\`{{cli}} skills install\` writes into the client Skills directory and records \`.rost-skill.json\` with \`skill_version_id\`, \`content_sha256\`, source, client, and \`installed_from: "rost_api"\`. Set \`ROST_SKILL_INSTALL_ROOT\` to override the local root in CI or a sandbox. Use \`install\` for one Skill and \`sync\` as the default local-agent command because it installs every approved Skill assigned to the Seat and skips
|
|
54372
|
+
\`{{cli}} skills install\` writes into the client Skills directory and records \`.rost-skill.json\` with \`skill_version_id\`, \`content_sha256\`, source, client, and \`installed_from: "rost_api"\`. Set \`ROST_SKILL_INSTALL_ROOT\` to override the local root in CI or a sandbox. Use \`install\` for one Skill and \`sync\` as the default local-agent command because it installs every approved Skill assigned to the Seat and skips a local copy only when its recorded hash matches and the installed file's own sha256 and byte size verify against the package the server returned; an edited or truncated local file is reinstalled instead of being reported unchanged. For \`--client claude-code\` the root is \`$CLAUDE_CONFIG_DIR/skills\` \u2014 \`CLAUDE_CONFIG_DIR\` is the variable Claude Code itself reads \u2014 otherwise \`~/.claude/skills\`; \`CLAUDE_HOME\` set on its own is a usage error with a migration instruction, and \`CLAUDE_HOME\` set alongside a different \`CLAUDE_CONFIG_DIR\` prints a notice that \`CLAUDE_HOME\` is ignored. For \`--client codex\` the root is \`$CODEX_HOME/skills\`, otherwise \`~/.codex/skills\`. For \`--client cursor\` the root is \`~/.cursor/skills\`. Catalog Skills land under \`<skills root>/rost/<slug>\`, except under \`ROST_SKILL_INSTALL_ROOT\`, where they land under \`<override root>/<client>/<slug>\`. A whitespace-only value for any of these variables is rejected rather than resolving somewhere unintended; an empty value means unset (the shell's \`FOO= cmd\` idiom for turning an inherited variable off) and falls back to the default root above. Entitlement failures mean the tenant cannot access that {{brand}} catalog tier yet; stale local copies are fixed by rerunning \`sync\`; private catalog access is always served by {{brand}} APIs, not by giving the agent a private GitHub credential.
|
|
54355
54373
|
|
|
54356
54374
|
{{brand}}-maintained catalog entries are seeded from first-party Skill packages with stable content hashes, application descriptors, and declared required or optional tool dependencies. Catalog seed validation is fail-closed: a package with parser warnings is not published. Enabling a catalog Skill copies the current immutable version into the company library; later catalog refreshes update the global catalog row but do not silently rewrite already-enabled tenant Skill versions.
|
|
54357
54375
|
|
|
@@ -61936,7 +61954,6 @@ function ensureTrailingSlash2(value) {
|
|
|
61936
61954
|
|
|
61937
61955
|
// src/skills.ts
|
|
61938
61956
|
import { existsSync, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
61939
|
-
import os2 from "node:os";
|
|
61940
61957
|
import path5 from "node:path";
|
|
61941
61958
|
|
|
61942
61959
|
// src/skill-install.ts
|
|
@@ -62455,7 +62472,7 @@ async function runInstallImplementation(context, args) {
|
|
|
62455
62472
|
parsed = parseFlags2(args);
|
|
62456
62473
|
client = parseClient2(optionalValue2(parsed, "client") ?? "codex");
|
|
62457
62474
|
tag = optionalValue2(parsed, "tag") ?? DEFAULT_RELEASE_TAG;
|
|
62458
|
-
|
|
62475
|
+
resolveSkillInstallRoot(client, context.env);
|
|
62459
62476
|
} catch (error51) {
|
|
62460
62477
|
if (error51 instanceof UsageError2) {
|
|
62461
62478
|
writeErrorLine(context.io, error51.message);
|
|
@@ -62622,7 +62639,7 @@ async function installRelease(context, plan, testOverrides = {}) {
|
|
|
62622
62639
|
let legacyNotice;
|
|
62623
62640
|
let viaOverride;
|
|
62624
62641
|
try {
|
|
62625
|
-
({ root, legacyNotice, viaOverride } =
|
|
62642
|
+
({ root, legacyNotice, viaOverride } = resolveSkillInstallRoot(plan.client, context.env));
|
|
62626
62643
|
} catch (error51) {
|
|
62627
62644
|
if (error51 instanceof UsageError2) {
|
|
62628
62645
|
writeErrorLine(context.io, error51.message);
|
|
@@ -62753,7 +62770,7 @@ function writeMarker(dir, input) {
|
|
|
62753
62770
|
writeFileSync(path4.join(dir, SKILL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
62754
62771
|
`, "utf8");
|
|
62755
62772
|
}
|
|
62756
|
-
function listInstalledFiles(dir) {
|
|
62773
|
+
function listInstalledFiles(dir, markerFile) {
|
|
62757
62774
|
const files = [];
|
|
62758
62775
|
const walk = (current, prefix) => {
|
|
62759
62776
|
for (const name of readdirSync(current)) {
|
|
@@ -62767,7 +62784,7 @@ function listInstalledFiles(dir) {
|
|
|
62767
62784
|
walk(absolute, relative);
|
|
62768
62785
|
continue;
|
|
62769
62786
|
}
|
|
62770
|
-
if (relative ===
|
|
62787
|
+
if (relative === markerFile) {
|
|
62771
62788
|
continue;
|
|
62772
62789
|
}
|
|
62773
62790
|
files.push({ path: relative, absolute, dev: stats.dev, ino: stats.ino });
|
|
@@ -62776,26 +62793,32 @@ function listInstalledFiles(dir) {
|
|
|
62776
62793
|
walk(dir, "");
|
|
62777
62794
|
return files;
|
|
62778
62795
|
}
|
|
62779
|
-
function verifyInstalledTree(dir, manifest) {
|
|
62796
|
+
function verifyInstalledTree(dir, manifest, options = {}) {
|
|
62797
|
+
const markerFile = options.markerFile ?? SKILL_MARKER_FILE;
|
|
62780
62798
|
let present;
|
|
62781
62799
|
try {
|
|
62782
|
-
present = listInstalledFiles(dir);
|
|
62800
|
+
present = listInstalledFiles(dir, markerFile);
|
|
62783
62801
|
} catch (error51) {
|
|
62784
62802
|
return { ok: false, reason: error51 instanceof Error ? error51.message : "install directory is unreadable" };
|
|
62785
62803
|
}
|
|
62786
|
-
const
|
|
62787
|
-
const unexpected = present.find((file2) => !
|
|
62804
|
+
const manifestByPath = new Map(manifest.files.map((file2) => [file2.path, file2]));
|
|
62805
|
+
const unexpected = present.find((file2) => !manifestByPath.has(file2.path));
|
|
62788
62806
|
if (unexpected !== void 0) {
|
|
62789
|
-
return { ok: false, reason: "an installed file is not listed in the
|
|
62807
|
+
return { ok: false, reason: "an installed file is not listed in the package" };
|
|
62790
62808
|
}
|
|
62791
|
-
if (present.length !==
|
|
62792
|
-
return { ok: false, reason: "the installed tree is missing files listed in the
|
|
62809
|
+
if (present.length !== manifestByPath.size) {
|
|
62810
|
+
return { ok: false, reason: "the installed tree is missing files listed in the package" };
|
|
62793
62811
|
}
|
|
62794
62812
|
for (const file2 of present) {
|
|
62795
62813
|
try {
|
|
62796
|
-
|
|
62814
|
+
const size = statSync(file2.absolute).size;
|
|
62815
|
+
if (size > MAX_RESPONSE_BYTES) {
|
|
62797
62816
|
return { ok: false, reason: `an installed file exceeds the ${MAX_RESPONSE_BYTES}-byte verification cap: ${file2.path}` };
|
|
62798
62817
|
}
|
|
62818
|
+
const declaredSize = manifestByPath.get(file2.path)?.sizeBytes;
|
|
62819
|
+
if (declaredSize !== void 0 && size !== declaredSize) {
|
|
62820
|
+
return { ok: false, reason: `an installed file's size does not match the declared size: ${file2.path}` };
|
|
62821
|
+
}
|
|
62799
62822
|
} catch (error51) {
|
|
62800
62823
|
return { ok: false, reason: error51 instanceof Error ? error51.message : "an installed file is unreadable" };
|
|
62801
62824
|
}
|
|
@@ -62893,7 +62916,7 @@ function resolveCodexSkillsRoot(env) {
|
|
|
62893
62916
|
const codexHome = requireNonBlank("CODEX_HOME", env.CODEX_HOME);
|
|
62894
62917
|
return path4.resolve(path4.join(codexHome ?? path4.join(resolveHome(env), ".codex"), "skills"));
|
|
62895
62918
|
}
|
|
62896
|
-
function
|
|
62919
|
+
function resolveSkillInstallRoot(client, env) {
|
|
62897
62920
|
const override = requireNonBlank("ROST_SKILL_INSTALL_ROOT", env.ROST_SKILL_INSTALL_ROOT);
|
|
62898
62921
|
if (override !== null) {
|
|
62899
62922
|
return { root: path4.resolve(override, client), legacyNotice: null, viaOverride: true };
|
|
@@ -62948,6 +62971,7 @@ function parseClient2(value) {
|
|
|
62948
62971
|
}
|
|
62949
62972
|
|
|
62950
62973
|
// src/skills.ts
|
|
62974
|
+
var CATALOG_SKILL_MARKER_FILE = ".rost-skill.json";
|
|
62951
62975
|
var UsageError3 = class extends Error {
|
|
62952
62976
|
};
|
|
62953
62977
|
var CLIENTS2 = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
|
|
@@ -63077,7 +63101,7 @@ async function installOne(context, args, env) {
|
|
|
63077
63101
|
});
|
|
63078
63102
|
const options = seatId ? { targetSeatId: seatId } : {};
|
|
63079
63103
|
const output = await executeInstallCommand(context, body, options);
|
|
63080
|
-
const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: false });
|
|
63104
|
+
const summary = applyLocalSkillChanges(context.io, output, client, env, { removeRevoked: false });
|
|
63081
63105
|
const outputRecord = asRecord4(output);
|
|
63082
63106
|
context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
|
|
63083
63107
|
` : `${formatLocalSummary(summary)}
|
|
@@ -63092,7 +63116,7 @@ async function syncAssigned(context, args, env) {
|
|
|
63092
63116
|
seat_id: seatId,
|
|
63093
63117
|
client
|
|
63094
63118
|
}, { targetSeatId: seatId });
|
|
63095
|
-
const summary = applyLocalSkillChanges(output, client, env, { removeRevoked: true });
|
|
63119
|
+
const summary = applyLocalSkillChanges(context.io, output, client, env, { removeRevoked: true });
|
|
63096
63120
|
const outputRecord = asRecord4(output);
|
|
63097
63121
|
context.io.stdout.write(parsed.json ? `${JSON.stringify({ ...outputRecord, local: summary }, null, 2)}
|
|
63098
63122
|
` : `${formatLocalSummary(summary)}
|
|
@@ -63128,15 +63152,19 @@ async function executeSkillCommand(context, commandId, body, options = {}) {
|
|
|
63128
63152
|
throw error51;
|
|
63129
63153
|
}
|
|
63130
63154
|
}
|
|
63131
|
-
function applyLocalSkillChanges(output, client, env, options) {
|
|
63132
|
-
const root =
|
|
63133
|
-
|
|
63155
|
+
function applyLocalSkillChanges(io, output, client, env, options) {
|
|
63156
|
+
const { root, legacyNotice } = installRoot(client, env);
|
|
63157
|
+
if (legacyNotice !== null) {
|
|
63158
|
+
io.stderr.write(`${legacyNotice}
|
|
63159
|
+
`);
|
|
63160
|
+
}
|
|
63161
|
+
const changes = writeInstalls(io, output, root);
|
|
63134
63162
|
if (options.removeRevoked) {
|
|
63135
63163
|
changes.push(...removeRevokedInstalls(output, root));
|
|
63136
63164
|
}
|
|
63137
63165
|
return changes;
|
|
63138
63166
|
}
|
|
63139
|
-
function writeInstalls(output, root) {
|
|
63167
|
+
function writeInstalls(io, output, root) {
|
|
63140
63168
|
const installs = asArray2(asRecord4(output).installs);
|
|
63141
63169
|
return installs.map((entry) => {
|
|
63142
63170
|
const install = asRecord4(entry);
|
|
@@ -63144,10 +63172,27 @@ function writeInstalls(output, root) {
|
|
|
63144
63172
|
const metadata = asRecord4(install.metadata);
|
|
63145
63173
|
const files = asArray2(install.files).map(asRecord4);
|
|
63146
63174
|
const dir = path5.join(root, slug2);
|
|
63147
|
-
const metadataPath = path5.join(dir,
|
|
63175
|
+
const metadataPath = path5.join(dir, CATALOG_SKILL_MARKER_FILE);
|
|
63148
63176
|
const expectedHash = String(metadata.content_sha256 ?? "");
|
|
63149
63177
|
if (existingHash(metadataPath) === expectedHash) {
|
|
63150
|
-
|
|
63178
|
+
const expected = files.map((file2) => {
|
|
63179
|
+
const declaredHash = file2.content_sha256;
|
|
63180
|
+
const sha256 = typeof declaredHash === "string" ? declaredHash : "";
|
|
63181
|
+
const sizeBytes = typeof file2.size_bytes === "number" ? file2.size_bytes : void 0;
|
|
63182
|
+
return {
|
|
63183
|
+
path: safeRelativePath2(String(file2.path ?? "")),
|
|
63184
|
+
sha256,
|
|
63185
|
+
...sizeBytes === void 0 ? {} : { sizeBytes }
|
|
63186
|
+
};
|
|
63187
|
+
});
|
|
63188
|
+
const verified = verifyInstalledTree(dir, { files: expected }, { markerFile: CATALOG_SKILL_MARKER_FILE });
|
|
63189
|
+
if (verified.ok) {
|
|
63190
|
+
return { slug: slug2, dir, status: "unchanged", files: files.length };
|
|
63191
|
+
}
|
|
63192
|
+
io.stderr.write(
|
|
63193
|
+
`NOTICE: the installed Skill ${slug2} does not match the server package (${verified.reason}); reinstalling.
|
|
63194
|
+
`
|
|
63195
|
+
);
|
|
63151
63196
|
}
|
|
63152
63197
|
rmSync2(dir, { recursive: true, force: true });
|
|
63153
63198
|
mkdirSync2(dir, { recursive: true });
|
|
@@ -63184,32 +63229,29 @@ function removeRevokedInstalls(output, root) {
|
|
|
63184
63229
|
}
|
|
63185
63230
|
return changes;
|
|
63186
63231
|
}
|
|
63187
|
-
function
|
|
63188
|
-
|
|
63189
|
-
|
|
63190
|
-
|
|
63191
|
-
}
|
|
63192
|
-
|
|
63193
|
-
|
|
63194
|
-
return path5.join(resolveCodexSkillsRoot(env), "rost");
|
|
63195
|
-
} catch (error51) {
|
|
63196
|
-
if (error51 instanceof UsageError2) {
|
|
63197
|
-
throw new UsageError3(error51.message);
|
|
63198
|
-
}
|
|
63199
|
-
throw error51;
|
|
63232
|
+
function installRoot(client, env) {
|
|
63233
|
+
let resolved;
|
|
63234
|
+
try {
|
|
63235
|
+
resolved = resolveSkillInstallRoot(client, env);
|
|
63236
|
+
} catch (error51) {
|
|
63237
|
+
if (error51 instanceof UsageError2) {
|
|
63238
|
+
throw new UsageError3(error51.message);
|
|
63200
63239
|
}
|
|
63240
|
+
throw error51;
|
|
63201
63241
|
}
|
|
63202
|
-
|
|
63203
|
-
|
|
63204
|
-
}
|
|
63205
|
-
return path5.join(os2.homedir(), ".cursor", "skills", "rost");
|
|
63242
|
+
const root = resolved.viaOverride ? resolved.root : path5.join(resolved.root, "rost");
|
|
63243
|
+
return { root, legacyNotice: resolved.legacyNotice };
|
|
63206
63244
|
}
|
|
63207
63245
|
function safeRelativePath2(value) {
|
|
63208
63246
|
const normalized = value.trim().replaceAll("\\", "/").replace(/^\.\/+/, "");
|
|
63209
63247
|
if (!normalized || normalized.startsWith("/") || normalized.includes("//") || normalized.split("/").includes("..")) {
|
|
63210
63248
|
throw new UsageError3("Server returned an invalid skill file path.");
|
|
63211
63249
|
}
|
|
63212
|
-
|
|
63250
|
+
const collapsed = normalized.split("/").filter((segment) => segment !== ".").join("/");
|
|
63251
|
+
if (!collapsed) {
|
|
63252
|
+
throw new UsageError3("Server returned an invalid skill file path.");
|
|
63253
|
+
}
|
|
63254
|
+
return collapsed;
|
|
63213
63255
|
}
|
|
63214
63256
|
function safeLocalSlug(value) {
|
|
63215
63257
|
if (!/^[a-z0-9][a-z0-9-]{1,80}$/.test(value)) {
|
|
@@ -64643,7 +64685,7 @@ import { execFile as execFileCallback4 } from "node:child_process";
|
|
|
64643
64685
|
import { createHash as createHash3, randomBytes } from "node:crypto";
|
|
64644
64686
|
import { constants as fsConstants3 } from "node:fs";
|
|
64645
64687
|
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";
|
|
64646
|
-
import
|
|
64688
|
+
import os2 from "node:os";
|
|
64647
64689
|
import path8 from "node:path";
|
|
64648
64690
|
import { setTimeout as sleep2 } from "node:timers/promises";
|
|
64649
64691
|
import { promisify as promisify4 } from "node:util";
|
|
@@ -64746,7 +64788,7 @@ function resolveSourceClaudeConfigDir(base) {
|
|
|
64746
64788
|
return explicit;
|
|
64747
64789
|
}
|
|
64748
64790
|
const home = base.HOME;
|
|
64749
|
-
const homeDir = typeof home === "string" && home.length > 0 ? home :
|
|
64791
|
+
const homeDir = typeof home === "string" && home.length > 0 ? home : os2.homedir();
|
|
64750
64792
|
return path8.join(homeDir, ".claude");
|
|
64751
64793
|
}
|
|
64752
64794
|
async function provisionIsolatedClaudeConfigDir(base, configDir, onRepair) {
|
|
@@ -64769,7 +64811,7 @@ function resolveSourceCodexHomeDir(base) {
|
|
|
64769
64811
|
return explicit;
|
|
64770
64812
|
}
|
|
64771
64813
|
const home = base.HOME;
|
|
64772
|
-
const homeDir = typeof home === "string" && home.length > 0 ? home :
|
|
64814
|
+
const homeDir = typeof home === "string" && home.length > 0 ? home : os2.homedir();
|
|
64773
64815
|
return path8.join(homeDir, ".codex");
|
|
64774
64816
|
}
|
|
64775
64817
|
var CODEX_HOME_TMP_PREFIX = ".rost-tmp-";
|