@rosthq/cli 0.7.124 → 0.7.126
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 +155 -43
- package/dist/index.js.map +2 -2
- package/dist/skill-install.d.ts +4 -0
- package/dist/skill-install.d.ts.map +1 -1
- package/dist/skill-trust.d.ts +5 -4
- package/dist/skill-trust.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -52231,7 +52231,7 @@ The implementation method is split into stable sections so a CLI, MCP, or in-app
|
|
|
52231
52231
|
order: 12,
|
|
52232
52232
|
title: "Install the {{brand}} implementation Skill",
|
|
52233
52233
|
summary: "Install the public implementation Skill through its signed, verified GitHub release so a general-purpose agent can run the {{brand}} implementation method.",
|
|
52234
|
-
version: "2026-07-
|
|
52234
|
+
version: "2026-07-30.1",
|
|
52235
52235
|
public: true,
|
|
52236
52236
|
audiences: ["human", "cli", "mcp", "in_app_agent"],
|
|
52237
52237
|
stages: ["company_setup"],
|
|
@@ -52285,10 +52285,14 @@ A fetched release can never introduce its own trust root. Any key material insid
|
|
|
52285
52285
|
|
|
52286
52286
|
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.
|
|
52287
52287
|
|
|
52288
|
+
For Claude Code the directory is \`$CLAUDE_CONFIG_DIR/skills/\` when that variable is set, otherwise \`~/.claude/skills/\` \u2014 \`CLAUDE_CONFIG_DIR\` is the variable Claude Code itself reads. The legacy \`CLAUDE_HOME\` is still honoured as a fallback and prints a deprecation notice; prefer \`CLAUDE_CONFIG_DIR\`, because a Skill installed under a root the client does not read leaves the agent running without it. For Codex the directory is \`$CODEX_HOME/skills/\`, otherwise \`~/.codex/skills/\`. The installer prints the resolved directory \u2014 check it matches the configuration the client launches with.
|
|
52289
|
+
|
|
52288
52290
|
## Update path
|
|
52289
52291
|
|
|
52290
52292
|
Re-run the installer to update. It re-verifies and replaces the pinned release. Use \`--tag <vX>\` to select a specific signed release. The stable install name \`rost-implementation\` is preserved across versions.
|
|
52291
52293
|
|
|
52294
|
+
Re-running is also how you confirm an existing install is intact: the installer hashes the files ON DISK against the signed manifest, not just the recorded marker, and reinstalls if any file was edited, removed, or added. A matching provenance marker alone is not evidence about the installed bytes. Note the scope of what this proves \u2014 the files matched the manifest at the moment the installer completed, so re-run it immediately before a run that depends on the Skill being unmodified.
|
|
52295
|
+
|
|
52292
52296
|
## After install
|
|
52293
52297
|
|
|
52294
52298
|
The agent bootstraps per the Skill's SKILL.md:
|
|
@@ -61717,7 +61721,7 @@ ${redactForLog(pending.diff)}
|
|
|
61717
61721
|
}
|
|
61718
61722
|
|
|
61719
61723
|
// src/skill-install.ts
|
|
61720
|
-
import { mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
61724
|
+
import { lstatSync, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
61721
61725
|
import os2 from "node:os";
|
|
61722
61726
|
import path5 from "node:path";
|
|
61723
61727
|
|
|
@@ -61729,8 +61733,8 @@ var RELEASE_REPOSITORY = {
|
|
|
61729
61733
|
owner: "agenticoperatingsystem",
|
|
61730
61734
|
name: "rost-implementation-skill"
|
|
61731
61735
|
};
|
|
61732
|
-
var DEFAULT_RELEASE_TAG = "v0.
|
|
61733
|
-
var EXPECTED_DEFAULT_MANIFEST_SHA256 = "
|
|
61736
|
+
var DEFAULT_RELEASE_TAG = "v0.2.0";
|
|
61737
|
+
var EXPECTED_DEFAULT_MANIFEST_SHA256 = "bbd745e1a625c30773fac5505f32c5152925d2474ac22d2cb4a93aa5a034ca66";
|
|
61734
61738
|
var SKILL_MANIFEST_PROTOCOL_VERSION = 1;
|
|
61735
61739
|
var SKILL_INSTALL_DIR_NAME = "rost-implementation";
|
|
61736
61740
|
var SkillTrustError = class extends Error {
|
|
@@ -62031,15 +62035,14 @@ function verifyReleaseManifest(manifest, options) {
|
|
|
62031
62035
|
};
|
|
62032
62036
|
}
|
|
62033
62037
|
}
|
|
62038
|
+
let files;
|
|
62034
62039
|
try {
|
|
62035
|
-
|
|
62036
|
-
safeRelativePath2(file2.path);
|
|
62037
|
-
}
|
|
62040
|
+
files = parsed.files.map((file2) => ({ ...file2, path: safeRelativePath2(file2.path) }));
|
|
62038
62041
|
} catch (error51) {
|
|
62039
62042
|
const message = error51 instanceof Error ? error51.message : "Invalid path";
|
|
62040
62043
|
return { ok: false, reason: message };
|
|
62041
62044
|
}
|
|
62042
|
-
return { ok: true, reason: "Manifest verified", manifest: parsed };
|
|
62045
|
+
return { ok: true, reason: "Manifest verified", manifest: { ...parsed, files } };
|
|
62043
62046
|
}
|
|
62044
62047
|
function verifyFileChecksums(files, manifest) {
|
|
62045
62048
|
const manifestMap = /* @__PURE__ */ new Map();
|
|
@@ -62138,6 +62141,10 @@ function verifyReleaseTrust(input) {
|
|
|
62138
62141
|
// src/skill-install.ts
|
|
62139
62142
|
var UsageError3 = class extends Error {
|
|
62140
62143
|
};
|
|
62144
|
+
function writeErrorLine(io, message) {
|
|
62145
|
+
io.stderr.write(`${terminalSafe(message)}
|
|
62146
|
+
`);
|
|
62147
|
+
}
|
|
62141
62148
|
var CLIENTS2 = /* @__PURE__ */ new Set(["claude-code", "codex", "cursor"]);
|
|
62142
62149
|
var REPO_PATH = `${RELEASE_REPOSITORY.owner}/${RELEASE_REPOSITORY.name}`;
|
|
62143
62150
|
var GITHUB_JSON_HEADERS = { "User-Agent": "rost-cli", "Accept": "application/vnd.github+json" };
|
|
@@ -62230,8 +62237,7 @@ async function runInstallImplementation(context, args) {
|
|
|
62230
62237
|
tag = optionalValue3(parsed, "tag") ?? DEFAULT_RELEASE_TAG;
|
|
62231
62238
|
} catch (error51) {
|
|
62232
62239
|
if (error51 instanceof UsageError3) {
|
|
62233
|
-
context.io
|
|
62234
|
-
`);
|
|
62240
|
+
writeErrorLine(context.io, error51.message);
|
|
62235
62241
|
return 1;
|
|
62236
62242
|
}
|
|
62237
62243
|
throw error51;
|
|
@@ -62244,8 +62250,7 @@ async function runInstallImplementation(context, args) {
|
|
|
62244
62250
|
return await installRelease(context, resolved.plan);
|
|
62245
62251
|
} catch (error51) {
|
|
62246
62252
|
if (error51 instanceof NetworkRequestError) {
|
|
62247
|
-
context.io
|
|
62248
|
-
`);
|
|
62253
|
+
writeErrorLine(context.io, error51.message);
|
|
62249
62254
|
return 2;
|
|
62250
62255
|
}
|
|
62251
62256
|
throw error51;
|
|
@@ -62254,8 +62259,7 @@ async function runInstallImplementation(context, args) {
|
|
|
62254
62259
|
async function resolveVerifiedPlan(context, input, testOverrides = {}) {
|
|
62255
62260
|
const fetchImpl = context.fetch ?? globalThis.fetch;
|
|
62256
62261
|
const fail2 = (message, exitCode) => {
|
|
62257
|
-
context.io
|
|
62258
|
-
`);
|
|
62262
|
+
writeErrorLine(context.io, message);
|
|
62259
62263
|
return { ok: false, exitCode };
|
|
62260
62264
|
};
|
|
62261
62265
|
const refUrl = `https://api.github.com/repos/${REPO_PATH}/git/refs/tags/${encodeURIComponent(input.tag)}`;
|
|
@@ -62370,37 +62374,55 @@ async function installRelease(context, plan, testOverrides = {}) {
|
|
|
62370
62374
|
...testOverrides.trustedPublicKeyLine === void 0 ? {} : { trustedPublicKeyLine: testOverrides.trustedPublicKeyLine }
|
|
62371
62375
|
});
|
|
62372
62376
|
if (!trustResult.ok) {
|
|
62373
|
-
context.io
|
|
62374
|
-
`);
|
|
62377
|
+
writeErrorLine(context.io, `Trust verification failed: ${trustResult.reason}`);
|
|
62375
62378
|
return 1;
|
|
62376
62379
|
}
|
|
62377
62380
|
const commitSha = trustResult.commitSha;
|
|
62378
62381
|
if (commitSha === void 0) {
|
|
62379
|
-
context.io
|
|
62382
|
+
writeErrorLine(context.io, "Trust verification succeeded but did not return a commit sha.");
|
|
62380
62383
|
return 1;
|
|
62381
62384
|
}
|
|
62382
62385
|
const manifest = trustResult.manifest;
|
|
62383
62386
|
if (manifest === void 0) {
|
|
62384
|
-
context.io
|
|
62387
|
+
writeErrorLine(context.io, "Trust verification succeeded but did not return a parsed manifest.");
|
|
62385
62388
|
return 1;
|
|
62386
62389
|
}
|
|
62387
62390
|
const unsupported = unsupportedClientReason(manifest, plan.client);
|
|
62388
62391
|
if (unsupported !== null) {
|
|
62389
|
-
context.io
|
|
62390
|
-
`);
|
|
62392
|
+
writeErrorLine(context.io, unsupported);
|
|
62391
62393
|
return 1;
|
|
62392
62394
|
}
|
|
62393
62395
|
const checksumResult = verifyFileChecksums(plan.files, manifest);
|
|
62394
62396
|
if (!checksumResult.ok) {
|
|
62395
|
-
context.io
|
|
62396
|
-
`);
|
|
62397
|
+
writeErrorLine(context.io, `File verification failed: ${checksumResult.reason}`);
|
|
62397
62398
|
return 1;
|
|
62398
62399
|
}
|
|
62399
|
-
|
|
62400
|
+
let root;
|
|
62401
|
+
let legacyNotice;
|
|
62402
|
+
try {
|
|
62403
|
+
({ root, legacyNotice } = installRoot2(plan.client, context.env));
|
|
62404
|
+
} catch (error51) {
|
|
62405
|
+
if (error51 instanceof UsageError3) {
|
|
62406
|
+
writeErrorLine(context.io, error51.message);
|
|
62407
|
+
return 1;
|
|
62408
|
+
}
|
|
62409
|
+
throw error51;
|
|
62410
|
+
}
|
|
62411
|
+
if (legacyNotice !== null) {
|
|
62412
|
+
writeErrorLine(context.io, legacyNotice);
|
|
62413
|
+
}
|
|
62400
62414
|
const dir = path5.join(root, SKILL_INSTALL_DIR_NAME);
|
|
62401
62415
|
const manifestSha256 = sha256Hex(plan.manifestRaw);
|
|
62402
62416
|
const existingMarker = readMarker(dir);
|
|
62403
|
-
|
|
62417
|
+
const installedState = existingMarker?.manifestSha256 === manifestSha256 ? verifyInstalledTree(dir, manifest) : null;
|
|
62418
|
+
if (installedState !== null && !installedState.ok) {
|
|
62419
|
+
writeErrorLine(
|
|
62420
|
+
context.io,
|
|
62421
|
+
`NOTICE: the installed Skill does not match the signed release (${installedState.reason}); reinstalling.`
|
|
62422
|
+
);
|
|
62423
|
+
}
|
|
62424
|
+
if (installedState?.ok === true) {
|
|
62425
|
+
writeMarker(dir, { tag: plan.tag, commitSha, manifestSha256 });
|
|
62404
62426
|
if (plan.json) {
|
|
62405
62427
|
context.io.stdout.write(
|
|
62406
62428
|
`${JSON.stringify({
|
|
@@ -62430,23 +62452,21 @@ async function installRelease(context, plan, testOverrides = {}) {
|
|
|
62430
62452
|
const resolvedTarget = path5.resolve(target);
|
|
62431
62453
|
const resolvedDir = path5.resolve(dir);
|
|
62432
62454
|
if (!resolvedTarget.startsWith(`${resolvedDir}${path5.sep}`)) {
|
|
62433
|
-
context.io
|
|
62434
|
-
`);
|
|
62455
|
+
writeErrorLine(context.io, `Refusing to write file outside target directory: ${file2.path}`);
|
|
62435
62456
|
return 1;
|
|
62436
62457
|
}
|
|
62437
62458
|
mkdirSync2(path5.dirname(target), { recursive: true });
|
|
62438
62459
|
writeFileSync2(target, file2.content, "utf8");
|
|
62439
62460
|
}
|
|
62440
|
-
const
|
|
62441
|
-
|
|
62442
|
-
|
|
62443
|
-
|
|
62444
|
-
|
|
62445
|
-
|
|
62446
|
-
|
|
62447
|
-
}
|
|
62448
|
-
|
|
62449
|
-
`, "utf8");
|
|
62461
|
+
const writtenState = verifyInstalledTree(dir, manifest);
|
|
62462
|
+
if (!writtenState.ok) {
|
|
62463
|
+
writeErrorLine(
|
|
62464
|
+
context.io,
|
|
62465
|
+
`Installed tree does not match the signed release after writing: ${writtenState.reason}`
|
|
62466
|
+
);
|
|
62467
|
+
return 1;
|
|
62468
|
+
}
|
|
62469
|
+
writeMarker(dir, { tag: plan.tag, commitSha, manifestSha256 });
|
|
62450
62470
|
if (plan.json) {
|
|
62451
62471
|
context.io.stdout.write(
|
|
62452
62472
|
`${JSON.stringify({
|
|
@@ -62469,13 +62489,77 @@ async function installRelease(context, plan, testOverrides = {}) {
|
|
|
62469
62489
|
}
|
|
62470
62490
|
return 0;
|
|
62471
62491
|
}
|
|
62492
|
+
var SKILL_MARKER_FILE = ".rost-skill-release.json";
|
|
62472
62493
|
function verifiedSignerLine() {
|
|
62473
62494
|
const fingerprintBase = TRUSTED_SIGNER_FINGERPRINT.replace("SHA256:", "");
|
|
62474
62495
|
return `verified signed release; signer SHA256:${fingerprintBase.slice(-12)} (last 12 chars)`;
|
|
62475
62496
|
}
|
|
62497
|
+
function writeMarker(dir, input) {
|
|
62498
|
+
const marker = {
|
|
62499
|
+
source: `github.com/${REPO_PATH}`,
|
|
62500
|
+
tag: input.tag,
|
|
62501
|
+
commit: input.commitSha,
|
|
62502
|
+
manifestSha256: input.manifestSha256,
|
|
62503
|
+
signerFingerprint: TRUSTED_SIGNER_FINGERPRINT,
|
|
62504
|
+
// Refreshed on every verification, not only on a rewrite: it is the
|
|
62505
|
+
// operator's evidence that the pre-launch re-run actually happened.
|
|
62506
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
62507
|
+
};
|
|
62508
|
+
writeFileSync2(path5.join(dir, SKILL_MARKER_FILE), `${JSON.stringify(marker, null, 2)}
|
|
62509
|
+
`, "utf8");
|
|
62510
|
+
}
|
|
62511
|
+
function listInstalledFiles(dir) {
|
|
62512
|
+
const files = [];
|
|
62513
|
+
const walk = (current, prefix) => {
|
|
62514
|
+
for (const name of readdirSync(current)) {
|
|
62515
|
+
const relative = prefix === "" ? name : `${prefix}/${name}`;
|
|
62516
|
+
const absolute = path5.join(current, name);
|
|
62517
|
+
const stats = lstatSync(absolute);
|
|
62518
|
+
if (stats.isSymbolicLink() || !stats.isFile() && !stats.isDirectory()) {
|
|
62519
|
+
throw new Error(`refusing to verify a symlink or special file: ${relative}`);
|
|
62520
|
+
}
|
|
62521
|
+
if (stats.isDirectory()) {
|
|
62522
|
+
walk(absolute, relative);
|
|
62523
|
+
continue;
|
|
62524
|
+
}
|
|
62525
|
+
if (relative === SKILL_MARKER_FILE) {
|
|
62526
|
+
continue;
|
|
62527
|
+
}
|
|
62528
|
+
files.push({ path: relative, absolute });
|
|
62529
|
+
}
|
|
62530
|
+
};
|
|
62531
|
+
walk(dir, "");
|
|
62532
|
+
return files;
|
|
62533
|
+
}
|
|
62534
|
+
function verifyInstalledTree(dir, manifest) {
|
|
62535
|
+
let present;
|
|
62536
|
+
try {
|
|
62537
|
+
present = listInstalledFiles(dir);
|
|
62538
|
+
} catch (error51) {
|
|
62539
|
+
return { ok: false, reason: error51 instanceof Error ? error51.message : "install directory is unreadable" };
|
|
62540
|
+
}
|
|
62541
|
+
const manifestPaths = new Set(manifest.files.map((file2) => file2.path));
|
|
62542
|
+
const unexpected = present.find((file2) => !manifestPaths.has(file2.path));
|
|
62543
|
+
if (unexpected !== void 0) {
|
|
62544
|
+
return { ok: false, reason: "an installed file is not listed in the signed manifest" };
|
|
62545
|
+
}
|
|
62546
|
+
if (present.length !== manifestPaths.size) {
|
|
62547
|
+
return { ok: false, reason: "the installed tree is missing files listed in the signed manifest" };
|
|
62548
|
+
}
|
|
62549
|
+
function* readEach() {
|
|
62550
|
+
for (const file2 of present) {
|
|
62551
|
+
yield { path: file2.path, content: readFileSync4(file2.absolute) };
|
|
62552
|
+
}
|
|
62553
|
+
}
|
|
62554
|
+
try {
|
|
62555
|
+
return verifyFileChecksums(readEach(), manifest);
|
|
62556
|
+
} catch (error51) {
|
|
62557
|
+
return { ok: false, reason: error51 instanceof Error ? error51.message : "an installed file is unreadable" };
|
|
62558
|
+
}
|
|
62559
|
+
}
|
|
62476
62560
|
function readMarker(dir) {
|
|
62477
62561
|
try {
|
|
62478
|
-
const content = readFileSync4(path5.join(dir,
|
|
62562
|
+
const content = readFileSync4(path5.join(dir, SKILL_MARKER_FILE), "utf8");
|
|
62479
62563
|
const parsed = JSON.parse(content);
|
|
62480
62564
|
if (typeof parsed === "object" && parsed !== null) {
|
|
62481
62565
|
return parsed;
|
|
@@ -62484,18 +62568,46 @@ function readMarker(dir) {
|
|
|
62484
62568
|
}
|
|
62485
62569
|
return null;
|
|
62486
62570
|
}
|
|
62571
|
+
function resolveClaudeSkillInstallRoot(env) {
|
|
62572
|
+
const configDir = requireNonBlank("CLAUDE_CONFIG_DIR", env.CLAUDE_CONFIG_DIR);
|
|
62573
|
+
const legacyHome = requireNonBlank("CLAUDE_HOME", env.CLAUDE_HOME);
|
|
62574
|
+
if (configDir !== null) {
|
|
62575
|
+
const disagrees = legacyHome !== null && path5.resolve(legacyHome) !== path5.resolve(configDir);
|
|
62576
|
+
return {
|
|
62577
|
+
root: path5.join(configDir, "skills"),
|
|
62578
|
+
legacyNotice: disagrees ? `NOTICE: CLAUDE_HOME (${legacyHome}) is ignored; Claude Code reads CLAUDE_CONFIG_DIR (${configDir}).` : null
|
|
62579
|
+
};
|
|
62580
|
+
}
|
|
62581
|
+
if (legacyHome !== null) {
|
|
62582
|
+
return {
|
|
62583
|
+
root: path5.join(legacyHome, "skills"),
|
|
62584
|
+
legacyNotice: "NOTICE: CLAUDE_HOME is deprecated and Claude Code does not read it; set CLAUDE_CONFIG_DIR instead."
|
|
62585
|
+
};
|
|
62586
|
+
}
|
|
62587
|
+
const home = requireNonBlank("HOME", env.HOME) ?? os2.homedir();
|
|
62588
|
+
return { root: path5.join(home, ".claude", "skills"), legacyNotice: null };
|
|
62589
|
+
}
|
|
62590
|
+
function requireNonBlank(name, value) {
|
|
62591
|
+
if (value === void 0 || value === "") {
|
|
62592
|
+
return null;
|
|
62593
|
+
}
|
|
62594
|
+
if (value.trim().length === 0) {
|
|
62595
|
+
throw new UsageError3(`${name} is set to a blank value; unset it or give it a real path.`);
|
|
62596
|
+
}
|
|
62597
|
+
return value;
|
|
62598
|
+
}
|
|
62487
62599
|
function installRoot2(client, env) {
|
|
62488
|
-
const override = env.ROST_SKILL_INSTALL_ROOT;
|
|
62489
|
-
if (override !==
|
|
62490
|
-
return path5.resolve(override, client);
|
|
62600
|
+
const override = requireNonBlank("ROST_SKILL_INSTALL_ROOT", env.ROST_SKILL_INSTALL_ROOT);
|
|
62601
|
+
if (override !== null) {
|
|
62602
|
+
return { root: path5.resolve(override, client), legacyNotice: null };
|
|
62491
62603
|
}
|
|
62492
62604
|
if (client === "codex") {
|
|
62493
|
-
return path5.join(env.CODEX_HOME ?? path5.join(os2.homedir(), ".codex"), "skills");
|
|
62605
|
+
return { root: path5.join(env.CODEX_HOME ?? path5.join(os2.homedir(), ".codex"), "skills"), legacyNotice: null };
|
|
62494
62606
|
}
|
|
62495
62607
|
if (client === "claude-code") {
|
|
62496
|
-
return
|
|
62608
|
+
return resolveClaudeSkillInstallRoot(env);
|
|
62497
62609
|
}
|
|
62498
|
-
return path5.join(os2.homedir(), ".cursor", "skills");
|
|
62610
|
+
return { root: path5.join(os2.homedir(), ".cursor", "skills"), legacyNotice: null };
|
|
62499
62611
|
}
|
|
62500
62612
|
function parseFlags3(args) {
|
|
62501
62613
|
const values = /* @__PURE__ */ new Map();
|