@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 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-24.1",
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.1.0-preview";
61733
- var EXPECTED_DEFAULT_MANIFEST_SHA256 = "812325e925688bb807c82c52ef3d184daf78127a7af219340292dc906988680a";
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
- for (const file2 of parsed.files) {
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.stderr.write(`${error51.message}
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.stderr.write(`${error51.message}
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.stderr.write(`${message}
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.stderr.write(`Trust verification failed: ${trustResult.reason}
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.stderr.write("Trust verification succeeded but did not return a commit sha.\n");
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.stderr.write("Trust verification succeeded but did not return a parsed manifest.\n");
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.stderr.write(`${unsupported}
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.stderr.write(`File verification failed: ${checksumResult.reason}
62396
- `);
62397
+ writeErrorLine(context.io, `File verification failed: ${checksumResult.reason}`);
62397
62398
  return 1;
62398
62399
  }
62399
- const root = installRoot2(plan.client, context.env);
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
- if (existingMarker?.manifestSha256 === manifestSha256) {
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.stderr.write(`Refusing to write file outside target directory: ${file2.path}
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 marker = {
62441
- source: `github.com/${REPO_PATH}`,
62442
- tag: plan.tag,
62443
- commit: commitSha,
62444
- manifestSha256,
62445
- signerFingerprint: TRUSTED_SIGNER_FINGERPRINT,
62446
- installedAt: (/* @__PURE__ */ new Date()).toISOString()
62447
- };
62448
- writeFileSync2(path5.join(dir, ".rost-skill-release.json"), `${JSON.stringify(marker, null, 2)}
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, ".rost-skill-release.json"), "utf8");
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 !== void 0 && override.length > 0) {
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 path5.join(env.CLAUDE_HOME ?? path5.join(os2.homedir(), ".claude"), "skills");
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();