protect-mcp 0.9.3 → 0.9.5

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/cli.js CHANGED
@@ -5146,6 +5146,33 @@ function deriveResource(input) {
5146
5146
  }
5147
5147
  return void 0;
5148
5148
  }
5149
+ function findField(input, names, depth = 0) {
5150
+ if (depth > 4 || input === null || typeof input !== "object") return void 0;
5151
+ const o = input;
5152
+ const keys = Object.keys(o).sort();
5153
+ for (const k of keys) {
5154
+ if (names.indexOf(k.toLowerCase()) >= 0 && o[k] !== void 0 && o[k] !== null) return o[k];
5155
+ }
5156
+ for (const k of keys) {
5157
+ const v = findField(o[k], names, depth + 1);
5158
+ if (v !== void 0) return v;
5159
+ }
5160
+ return void 0;
5161
+ }
5162
+ function derivePayment(tool, input) {
5163
+ if (deriveCapabilities(tool, input).indexOf("payment") < 0) return void 0;
5164
+ const p = { amount: null, asset: null, recipient_digest: null };
5165
+ const amt = findField(input, ["amount"]);
5166
+ if (typeof amt === "number" && Number.isFinite(amt) && amt >= 0) p.amount = amt;
5167
+ else if (typeof amt === "string" && /^\d{1,15}(\.\d{1,18})?$/.test(amt.trim()) && amt.indexOf(".") >= 0) p.amount = parseFloat(amt);
5168
+ const asset = findField(input, ["asset", "currency", "token"]);
5169
+ if (typeof asset === "string" && asset.trim()) p.asset = asset.trim().slice(0, 64);
5170
+ const to = findField(input, ["payto", "pay_to", "recipient", "destination", "to"]);
5171
+ if (typeof to === "string" && to.trim()) p.recipient_digest = sha256Hex2(to.trim().toLowerCase());
5172
+ const scheme = findField(input, ["scheme"]);
5173
+ if (typeof scheme === "string" && scheme.trim()) p.scheme = scheme.trim().slice(0, 32);
5174
+ return p;
5175
+ }
5149
5176
  function buildEnrichment(tool, input) {
5150
5177
  const e = {
5151
5178
  v: ENRICHMENT_VERSION,
@@ -5154,6 +5181,8 @@ function buildEnrichment(tool, input) {
5154
5181
  };
5155
5182
  const resource = deriveResource(input);
5156
5183
  if (resource) e.resource = resource;
5184
+ const payment = derivePayment(tool, input);
5185
+ if (payment) e.payment = payment;
5157
5186
  return e;
5158
5187
  }
5159
5188
  var ENRICHMENT_VERSION, RULES;
@@ -5174,7 +5203,11 @@ var init_receipt_enrichment = __esm({
5174
5203
  { cap: "secret.adjacent", text: /\.env\b|secret|credential|passwd|password|api[_-]?key|private[_-]?key|\.pem\b|\.key\b|id_rsa|bearer\s|aws_(access|secret)|authorization/ },
5175
5204
  { cap: "destructive", text: /rm\s+-[a-z]*[rf]|\brmdir\b|drop\s+table|truncate\s+table|delete\s+from|reset\s+--hard|--force\b|\bmkfs\b|\bdd\s+if=|shutdown|reboot|kill\s+-9|>\s*\/dev\/sd/ },
5176
5205
  { cap: "financial", text: /\b(order|trade|buy|sell|transfer|wire|payment|withdraw|deposit|swap|invoice|charge|refund|settle)\b/ },
5177
- { cap: "data.query", text: /\bselect\s+[\s\S]+\bfrom\b|\binsert\s+into\b|\bupdate\s+[\s\S]+\bset\b|\bdelete\s+from\b/ }
5206
+ { cap: "data.query", text: /\bselect\s+[\s\S]+\bfrom\b|\binsert\s+into\b|\bupdate\s+[\s\S]+\bset\b|\bdelete\s+from\b/ },
5207
+ // Agent payments (x402 / value transfer). Deliberately BROAD: a false positive
5208
+ // only makes a `claim --no payment` harder to assert (conservative); a false
5209
+ // negative would let a real payment escape the record's payment claims.
5210
+ { cap: "payment", tool: /(^|[_.-])(pay|payment|x402|checkout)($|[_.-])|wallet.*send|send.*payment/, text: /x402|x-payment|paymentrequirements|maxamountrequired|payto|"pay_to"|eip-3009|transferwithauthorization|payment_intent|send_payment|create_payment/ }
5178
5211
  ];
5179
5212
  }
5180
5213
  });
@@ -5183,15 +5216,19 @@ var init_receipt_enrichment = __esm({
5183
5216
  var claim_exports = {};
5184
5217
  __export(claim_exports, {
5185
5218
  ANCHOR_SCHEMA: () => ANCHOR_SCHEMA,
5219
+ CHECKPOINT_SCHEMA: () => CHECKPOINT_SCHEMA,
5186
5220
  CLAIM_TYPE: () => CLAIM_TYPE,
5187
5221
  DEFAULT_LOG: () => DEFAULT_LOG,
5188
5222
  anchorClaim: () => anchorClaim,
5223
+ anchorRecordCheckpoint: () => anchorRecordCheckpoint,
5189
5224
  buildAnchorEnvelope: () => buildAnchorEnvelope,
5190
5225
  buildClaim: () => buildClaim,
5226
+ buildRecordCheckpoint: () => buildRecordCheckpoint,
5191
5227
  checkClaimAnchor: () => checkClaimAnchor,
5192
5228
  claimDigest: () => claimDigest,
5193
5229
  evaluate: () => evaluate,
5194
5230
  leafHash: () => leafHash,
5231
+ lookupPinnedIdentity: () => lookupPinnedIdentity,
5195
5232
  merkleRoot: () => merkleRoot,
5196
5233
  receiptToLeaf: () => receiptToLeaf,
5197
5234
  verifyAnchorEnvelope: () => verifyAnchorEnvelope,
@@ -5211,7 +5248,13 @@ function receiptToLeaf(e) {
5211
5248
  const ms = typeof tsRaw === "number" ? tsRaw : typeof tsRaw === "string" ? Date.parse(tsRaw) : NaN;
5212
5249
  const t = isFinite(ms) ? new Date(ms).toISOString() : "";
5213
5250
  const d = sha256Hex3(canonicalJson(e));
5214
- return { d, v, c, t };
5251
+ const leaf = { d, v, c, t };
5252
+ const pay = enr && enr.payment;
5253
+ if (pay && typeof pay === "object") {
5254
+ const amt = pay.amount;
5255
+ leaf.p = typeof amt === "number" && Number.isFinite(amt) ? amt : null;
5256
+ }
5257
+ return leaf;
5215
5258
  }
5216
5259
  function leafHash(leaf) {
5217
5260
  return sha256Hex3(canonicalJson(leaf));
@@ -5244,6 +5287,10 @@ function evaluate(pred, leaves) {
5244
5287
  const matched2 = leaves.filter((l) => l.v === pred.verdict).length;
5245
5288
  return { statement: `No action was ${pred.verdict}`, holds: matched2 === 0, matched: matched2 };
5246
5289
  }
5290
+ if (pred.kind === "payment_under") {
5291
+ const matched2 = leaves.filter((l) => "p" in l && (l.p === null || l.p >= pred.cap)).length;
5292
+ return { statement: `Every payment stayed under ${pred.cap} (unknown amounts count as over)`, holds: matched2 === 0, matched: matched2 };
5293
+ }
5247
5294
  const matched = leaves.filter((l) => l.v === pred.verdict).length;
5248
5295
  return { statement: `${matched} action${matched === 1 ? " was" : "s were"} ${pred.verdict}`, holds: true, matched };
5249
5296
  }
@@ -5408,11 +5455,9 @@ async function checkClaimAnchor(pack, sidecar, opts) {
5408
5455
  return out;
5409
5456
  }
5410
5457
  }
5411
- async function anchorClaim(pack, key, opts) {
5412
- const envelope = buildAnchorEnvelope(pack, key, opts.issuedAt);
5413
- const base = (opts.log || DEFAULT_LOG).replace(/\/+$/, "");
5414
- const doFetch = opts.fetchImpl || globalThis.fetch;
5415
- if (!doFetch) return { ok: false, claim_digest: envelope.claim_digest, error: "fetch_unavailable", envelope };
5458
+ async function submitEnvelope(envelope, base, fetchImpl) {
5459
+ const doFetch = fetchImpl || globalThis.fetch;
5460
+ if (!doFetch) return { ok: false, error: "fetch_unavailable" };
5416
5461
  const encoded = toBase64(new TextEncoder().encode(JSON.stringify(envelope)));
5417
5462
  try {
5418
5463
  const resp = await doFetch(`${base}/fn/log/anchor-pack`, {
@@ -5422,22 +5467,86 @@ async function anchorClaim(pack, key, opts) {
5422
5467
  });
5423
5468
  const data = await resp.json().catch(() => null);
5424
5469
  if (!resp.ok || !data || !data.ok || typeof data.seq !== "number") {
5425
- return { ok: false, claim_digest: envelope.claim_digest, error: data && data.error || `http_${resp.status}`, envelope };
5470
+ return { ok: false, error: data && data.error || `http_${resp.status}` };
5426
5471
  }
5472
+ return { ok: true, seq: data.seq, anchored_at: data.anchored_at, already_anchored: !!data.already_anchored };
5473
+ } catch {
5474
+ return { ok: false, error: "network_error" };
5475
+ }
5476
+ }
5477
+ async function anchorClaim(pack, key, opts) {
5478
+ const envelope = buildAnchorEnvelope(pack, key, opts.issuedAt);
5479
+ const base = (opts.log || DEFAULT_LOG).replace(/\/+$/, "");
5480
+ const out = await submitEnvelope(envelope, base, opts.fetchImpl);
5481
+ if (!out.ok) return { ok: false, claim_digest: envelope.claim_digest, error: out.error, envelope };
5482
+ return {
5483
+ ok: true,
5484
+ claim_digest: envelope.claim_digest,
5485
+ seq: out.seq,
5486
+ entry_url: `${base}/fn/log/${out.seq}`,
5487
+ anchored_at: out.anchored_at,
5488
+ already_anchored: out.already_anchored,
5489
+ envelope
5490
+ };
5491
+ }
5492
+ function buildRecordCheckpoint(receipts, key, issuedAt) {
5493
+ const leaves = receipts.map(receiptToLeaf);
5494
+ const times = leaves.map((l) => l.t).filter(Boolean).sort();
5495
+ const signed = {
5496
+ type: "evidence_pack",
5497
+ schema: CHECKPOINT_SCHEMA,
5498
+ anchors: "protect-mcp-record",
5499
+ record_root: merkleRoot(leaves.map(leafHash)),
5500
+ total: leaves.length,
5501
+ from: times[0] || "",
5502
+ to: times[times.length - 1] || "",
5503
+ issued_at: issuedAt,
5504
+ verification_key: key.publicKey,
5505
+ disclosure: "internal"
5506
+ };
5507
+ const hash = sha2562(new TextEncoder().encode(JSON.stringify(anchorDeepSort(signed))));
5508
+ const digest = bytesToHex(hash);
5509
+ const signature = bytesToHex(ed25519.sign(hash, hexToBytes(key.privateKey)));
5510
+ return { ...signed, signature, digest };
5511
+ }
5512
+ async function anchorRecordCheckpoint(receipts, key, opts) {
5513
+ const checkpoint = buildRecordCheckpoint(receipts, key, opts.issuedAt);
5514
+ const base = (opts.log || DEFAULT_LOG).replace(/\/+$/, "");
5515
+ const out = await submitEnvelope(checkpoint, base, opts.fetchImpl);
5516
+ if (!out.ok) return { ok: false, record_root: checkpoint.record_root, total: checkpoint.total, checkpoint, error: out.error };
5517
+ return {
5518
+ ok: true,
5519
+ record_root: checkpoint.record_root,
5520
+ total: checkpoint.total,
5521
+ seq: out.seq,
5522
+ entry_url: `${base}/fn/log/${out.seq}`,
5523
+ anchored_at: out.anchored_at,
5524
+ already_anchored: out.already_anchored,
5525
+ checkpoint
5526
+ };
5527
+ }
5528
+ async function lookupPinnedIdentity(publicKey, opts) {
5529
+ const base = (opts && opts.log || DEFAULT_LOG).replace(/\/+$/, "");
5530
+ const doFetch = opts && opts.fetchImpl || globalThis.fetch;
5531
+ if (!doFetch || !/^[0-9a-f]{64}$/i.test(publicKey)) return null;
5532
+ try {
5533
+ const resp = await doFetch(`${base}/fn/log/keys/lookup/${publicKey.toLowerCase()}`, { headers: { accept: "application/json" } });
5534
+ const data = await resp.json().catch(() => null);
5535
+ if (!data || data.ok !== true) return null;
5536
+ if (!data.found) return { found: false };
5427
5537
  return {
5428
- ok: true,
5429
- claim_digest: envelope.claim_digest,
5430
- seq: data.seq,
5431
- entry_url: `${base}/fn/log/${data.seq}`,
5432
- anchored_at: data.anchored_at,
5433
- already_anchored: !!data.already_anchored,
5434
- envelope
5538
+ found: true,
5539
+ name: data.name,
5540
+ slug: data.slug,
5541
+ kid: data.kid,
5542
+ enrolled_at: data.enrolled_at,
5543
+ revoked: !!(data.revoked || data.revoked_at)
5435
5544
  };
5436
5545
  } catch {
5437
- return { ok: false, claim_digest: envelope.claim_digest, error: "network_error", envelope };
5546
+ return null;
5438
5547
  }
5439
5548
  }
5440
- var CLAIM_TYPE, ANCHOR_SCHEMA, DEFAULT_LOG;
5549
+ var CLAIM_TYPE, ANCHOR_SCHEMA, DEFAULT_LOG, CHECKPOINT_SCHEMA;
5441
5550
  var init_claim = __esm({
5442
5551
  "src/claim.ts"() {
5443
5552
  "use strict";
@@ -5448,6 +5557,7 @@ var init_claim = __esm({
5448
5557
  CLAIM_TYPE = "scopeblind.claim.v1";
5449
5558
  ANCHOR_SCHEMA = "scopeblind.protect-mcp.anchor.v1";
5450
5559
  DEFAULT_LOG = "https://scopeblind.com";
5560
+ CHECKPOINT_SCHEMA = "scopeblind.protect-mcp.record-checkpoint.v1";
5451
5561
  }
5452
5562
  });
5453
5563
 
@@ -6209,6 +6319,101 @@ var init_hook_patterns = __esm({
6209
6319
  }
6210
6320
  });
6211
6321
 
6322
+ // src/sample.ts
6323
+ var sample_exports = {};
6324
+ __export(sample_exports, {
6325
+ SAMPLE_KID: () => SAMPLE_KID,
6326
+ buildSampleKit: () => buildSampleKit
6327
+ });
6328
+ function signEnvelope(unsigned, privHex) {
6329
+ const msg = new TextEncoder().encode(canonicalJson(unsigned));
6330
+ const signature = bytesToHex(ed25519.sign(msg, hexToBytes(privHex)));
6331
+ return { ...unsigned, signature };
6332
+ }
6333
+ function buildSampleKit(dir, opts) {
6334
+ const receiptsPath = (0, import_node_path7.join)(dir, ".protect-mcp-receipts.jsonl");
6335
+ const keyPath = (0, import_node_path7.join)(dir, "keys", "gateway.json");
6336
+ if (!opts?.force && ((0, import_node_fs10.existsSync)(receiptsPath) || (0, import_node_fs10.existsSync)(keyPath))) {
6337
+ throw Object.assign(
6338
+ new Error(`refusing to overwrite an existing record or signing key in ${dir}`),
6339
+ { code: "SAMPLE_EXISTS" }
6340
+ );
6341
+ }
6342
+ (0, import_node_fs10.mkdirSync)((0, import_node_path7.join)(dir, "keys"), { recursive: true });
6343
+ const priv = ed25519.utils.randomPrivateKey();
6344
+ const privHex = bytesToHex(priv);
6345
+ const pub = bytesToHex(ed25519.getPublicKey(priv));
6346
+ (0, import_node_fs10.writeFileSync)(keyPath, JSON.stringify({ privateKey: privHex, publicKey: pub, kid: SAMPLE_KID }, null, 2));
6347
+ (0, import_node_fs10.writeFileSync)((0, import_node_path7.join)(dir, "keys", ".gitignore"), "# Never commit signing keys\n*.json\n");
6348
+ const now = opts?.now ?? /* @__PURE__ */ new Date();
6349
+ const stamp = (i) => new Date(now.getTime() - (7 - i) * 5 * 6e4).toISOString();
6350
+ const receipt = (i, tool, decision, caps, extra) => {
6351
+ const ts = stamp(i);
6352
+ const payload = {
6353
+ tool,
6354
+ decision,
6355
+ reason_code: decision === "deny" ? "policy_deny" : "policy_ok",
6356
+ policy_digest: SAMPLE_KID,
6357
+ scope: `${tool}-${ts}`,
6358
+ mode: "enforce",
6359
+ request_id: `${tool}-${ts}`,
6360
+ spec: "draft-farley-acta-signed-receipts-01",
6361
+ issuer_certification: "self-signed",
6362
+ public_key: pub,
6363
+ hook_event: "PreToolUse",
6364
+ enrichment: { v: 2, input_digest: sha256Hex4(tool + ts), capabilities: caps, ...extra || {} }
6365
+ };
6366
+ return signEnvelope({
6367
+ v: 2,
6368
+ type: decision === "deny" ? "gateway_restraint" : "decision_receipt",
6369
+ algorithm: "ed25519",
6370
+ kid: SAMPLE_KID,
6371
+ issuer: "protect-mcp",
6372
+ issued_at: ts,
6373
+ payload
6374
+ }, privHex);
6375
+ };
6376
+ const pay = (amount) => ({
6377
+ payment: { amount, asset: "USDC", recipient_digest: sha256Hex4("sample-merchant"), scheme: "exact" }
6378
+ });
6379
+ const rows = [
6380
+ receipt(0, "Read", "allow", ["fs.read"]),
6381
+ receipt(1, "Bash", "allow", ["exec.shell"]),
6382
+ receipt(2, "Write", "allow", ["fs.write"]),
6383
+ receipt(3, "WebFetch", "deny", ["net.egress"]),
6384
+ receipt(4, "x402_pay", "allow", ["financial", "payment"], pay(0.02)),
6385
+ receipt(5, "Read", "allow", ["fs.read", "secret.adjacent"]),
6386
+ receipt(6, "wallet_send_payment", "allow", ["financial", "payment"], pay(12.5)),
6387
+ receipt(7, "Bash", "allow", ["exec.shell", "vcs"])
6388
+ ];
6389
+ (0, import_node_fs10.writeFileSync)(receiptsPath, rows.map((r) => JSON.stringify(r)).join("\n") + "\n");
6390
+ const tampered = rows.map((r) => JSON.parse(JSON.stringify(r)));
6391
+ tampered[3].payload.decision = "allow";
6392
+ (0, import_node_fs10.writeFileSync)((0, import_node_path7.join)(dir, "demo-tampered.jsonl"), tampered.map((r) => JSON.stringify(r)).join("\n") + "\n");
6393
+ return {
6394
+ dir,
6395
+ publicKey: pub,
6396
+ kid: SAMPLE_KID,
6397
+ receipts: rows,
6398
+ paymentsUsd: [0.02, 12.5],
6399
+ files: [".protect-mcp-receipts.jsonl", "demo-tampered.jsonl", "keys/gateway.json"]
6400
+ };
6401
+ }
6402
+ var import_node_fs10, import_node_path7, SAMPLE_KID, sha256Hex4;
6403
+ var init_sample = __esm({
6404
+ "src/sample.ts"() {
6405
+ "use strict";
6406
+ import_node_fs10 = require("fs");
6407
+ import_node_path7 = require("path");
6408
+ init_sha256();
6409
+ init_utils();
6410
+ init_ed25519();
6411
+ init_receipt_enrichment();
6412
+ SAMPLE_KID = "sample-demo";
6413
+ sha256Hex4 = (s) => bytesToHex(sha2562(new TextEncoder().encode(s)));
6414
+ }
6415
+ });
6416
+
6212
6417
  // src/scopeblind-bridge.ts
6213
6418
  function getScopeBlindBridge() {
6214
6419
  if (!singleton) singleton = new ScopeBlindBridge();
@@ -6417,7 +6622,7 @@ function detectSandboxState() {
6417
6622
  }
6418
6623
  if (process.platform === "linux") {
6419
6624
  try {
6420
- const procStatus = (0, import_node_fs10.readFileSync)("/proc/self/status", "utf-8");
6625
+ const procStatus = (0, import_node_fs11.readFileSync)("/proc/self/status", "utf-8");
6421
6626
  if (procStatus.includes("Seccomp: 2")) return "enabled";
6422
6627
  } catch {
6423
6628
  }
@@ -6862,14 +7067,14 @@ function emitDecisionLog(state, entry) {
6862
7067
  process.stderr.write(`[PROTECT_MCP] ${JSON.stringify(log)}
6863
7068
  `);
6864
7069
  try {
6865
- (0, import_node_fs10.appendFileSync)(state.logFilePath, JSON.stringify(log) + "\n");
7070
+ (0, import_node_fs11.appendFileSync)(state.logFilePath, JSON.stringify(log) + "\n");
6866
7071
  } catch {
6867
7072
  }
6868
7073
  if (isSigningEnabled()) {
6869
7074
  const signed = signDecision(log);
6870
7075
  if (signed.signed) {
6871
7076
  try {
6872
- (0, import_node_fs10.appendFileSync)(state.receiptFilePath, signed.signed + "\n");
7077
+ (0, import_node_fs11.appendFileSync)(state.receiptFilePath, signed.signed + "\n");
6873
7078
  } catch {
6874
7079
  }
6875
7080
  state.receiptBuffer.add(log.request_id, signed.signed);
@@ -6893,7 +7098,7 @@ function emitDecisionLog(state, entry) {
6893
7098
  at: new Date(log.timestamp).toISOString()
6894
7099
  });
6895
7100
  try {
6896
- (0, import_node_fs10.appendFileSync)(state.receiptFilePath, tombstone + "\n");
7101
+ (0, import_node_fs11.appendFileSync)(state.receiptFilePath, tombstone + "\n");
6897
7102
  } catch {
6898
7103
  }
6899
7104
  process.stderr.write(`[PROTECT_MCP_SIGNING_FAILURE] ${tombstone}
@@ -6981,8 +7186,8 @@ async function startHookServer(options = {}) {
6981
7186
  }
6982
7187
  }
6983
7188
  if (!jsonPolicy?.signing) {
6984
- const keyPath = (0, import_node_path7.join)(process.cwd(), "keys", "gateway.json");
6985
- if ((0, import_node_fs10.existsSync)(keyPath)) {
7189
+ const keyPath = (0, import_node_path8.join)(process.cwd(), "keys", "gateway.json");
7190
+ if ((0, import_node_fs11.existsSync)(keyPath)) {
6986
7191
  const warnings = await initSigning({ key_path: keyPath, issuer: "protect-mcp", enabled: true });
6987
7192
  for (const w of warnings) {
6988
7193
  process.stderr.write(`[PROTECT_MCP] Warning: ${w}
@@ -7004,8 +7209,8 @@ async function startHookServer(options = {}) {
7004
7209
  verbose,
7005
7210
  enforce,
7006
7211
  policyDigest,
7007
- logFilePath: (0, import_node_path7.join)(process.cwd(), LOG_FILE3),
7008
- receiptFilePath: (0, import_node_path7.join)(process.cwd(), RECEIPTS_FILE2),
7212
+ logFilePath: (0, import_node_path8.join)(process.cwd(), LOG_FILE3),
7213
+ receiptFilePath: (0, import_node_path8.join)(process.cwd(), RECEIPTS_FILE2),
7009
7214
  permissionSuggestions: /* @__PURE__ */ new Map(),
7010
7215
  configAlerts: []
7011
7216
  };
@@ -7154,7 +7359,7 @@ async function startHookServer(options = {}) {
7154
7359
  `);
7155
7360
  w(`
7156
7361
  `);
7157
- const hasSlug = process.env.SCOPEBLIND_SLUG || (0, import_node_fs10.existsSync)((0, import_node_path7.join)(process.cwd(), ".scopeblind"));
7362
+ const hasSlug = process.env.SCOPEBLIND_SLUG || (0, import_node_fs11.existsSync)((0, import_node_path8.join)(process.cwd(), ".scopeblind"));
7158
7363
  if (!hasSlug) {
7159
7364
  w(` Dashboard npx protect-mcp connect
7160
7365
  `);
@@ -7185,8 +7390,8 @@ async function startHookServer(options = {}) {
7185
7390
  function findCedarDir() {
7186
7391
  for (const candidate of ["cedar", "policies", "."]) {
7187
7392
  try {
7188
- if ((0, import_node_fs10.existsSync)(candidate)) {
7189
- const files = (0, import_node_fs10.readdirSync)(candidate, { encoding: "utf-8" });
7393
+ if ((0, import_node_fs11.existsSync)(candidate)) {
7394
+ const files = (0, import_node_fs11.readdirSync)(candidate, { encoding: "utf-8" });
7190
7395
  if (files.some((f) => f.endsWith(".cedar"))) {
7191
7396
  return candidate;
7192
7397
  }
@@ -7207,14 +7412,14 @@ function normalizeHookInput(raw) {
7207
7412
  }
7208
7413
  return result;
7209
7414
  }
7210
- var import_node_http2, import_node_crypto6, import_node_fs10, import_node_path7, DEFAULT_PORT, LOG_FILE3, RECEIPTS_FILE2, PAYLOAD_HASH_THRESHOLD, SNAKE_TO_CAMEL_MAP;
7415
+ var import_node_http2, import_node_crypto6, import_node_fs11, import_node_path8, DEFAULT_PORT, LOG_FILE3, RECEIPTS_FILE2, PAYLOAD_HASH_THRESHOLD, SNAKE_TO_CAMEL_MAP;
7211
7416
  var init_hook_server = __esm({
7212
7417
  "src/hook-server.ts"() {
7213
7418
  "use strict";
7214
7419
  import_node_http2 = require("http");
7215
7420
  import_node_crypto6 = require("crypto");
7216
- import_node_fs10 = require("fs");
7217
- import_node_path7 = require("path");
7421
+ import_node_fs11 = require("fs");
7422
+ import_node_path8 = require("path");
7218
7423
  init_cedar_evaluator();
7219
7424
  init_signing();
7220
7425
  init_policy();
@@ -7440,8 +7645,8 @@ function generateReport(logPath, receiptPath, periodDays) {
7440
7645
  const now = /* @__PURE__ */ new Date();
7441
7646
  const from = new Date(now.getTime() - periodDays * 864e5);
7442
7647
  const entries = [];
7443
- if ((0, import_node_fs11.existsSync)(logPath)) {
7444
- const raw = (0, import_node_fs11.readFileSync)(logPath, "utf-8");
7648
+ if ((0, import_node_fs12.existsSync)(logPath)) {
7649
+ const raw = (0, import_node_fs12.readFileSync)(logPath, "utf-8");
7445
7650
  for (const line of raw.split("\n")) {
7446
7651
  const trimmed = line.trim();
7447
7652
  if (!trimmed) continue;
@@ -7461,8 +7666,8 @@ function generateReport(logPath, receiptPath, periodDays) {
7461
7666
  let receiptsSigned = 0;
7462
7667
  let signerKid = "";
7463
7668
  let signerIssuer = "";
7464
- if ((0, import_node_fs11.existsSync)(receiptPath)) {
7465
- const raw = (0, import_node_fs11.readFileSync)(receiptPath, "utf-8");
7669
+ if ((0, import_node_fs12.existsSync)(receiptPath)) {
7670
+ const raw = (0, import_node_fs12.readFileSync)(receiptPath, "utf-8");
7466
7671
  for (const line of raw.split("\n")) {
7467
7672
  const trimmed = line.trim();
7468
7673
  if (!trimmed) continue;
@@ -7594,11 +7799,11 @@ function formatReportMarkdown(report) {
7594
7799
  lines.push("*Generated by protect-mcp \xB7 scopeblind.com*");
7595
7800
  return lines.join("\n");
7596
7801
  }
7597
- var import_node_fs11;
7802
+ var import_node_fs12;
7598
7803
  var init_report = __esm({
7599
7804
  "src/report.ts"() {
7600
7805
  "use strict";
7601
- import_node_fs11 = require("fs");
7806
+ import_node_fs12 = require("fs");
7602
7807
  }
7603
7808
  });
7604
7809
 
@@ -8642,8 +8847,8 @@ Next: run \`npx protect-mcp dashboard --open\` and review tool inventory, policy
8642
8847
 
8643
8848
  // src/cli.ts
8644
8849
  var import_node_crypto7 = require("crypto");
8645
- var import_node_fs12 = require("fs");
8646
- var import_node_path8 = require("path");
8850
+ var import_node_fs13 = require("fs");
8851
+ var import_node_path9 = require("path");
8647
8852
  var import_node_os = require("os");
8648
8853
  function printHelp() {
8649
8854
  process.stderr.write(`
@@ -8665,14 +8870,16 @@ Usage:
8665
8870
  protect-mcp policy-packs list|show|install [pack] [--dir ./cedar] [--force]
8666
8871
  protect-mcp connect
8667
8872
  protect-mcp init [--dir <path>]
8873
+ protect-mcp sample [--dir <path>] [--force]
8668
8874
  protect-mcp demo
8669
8875
  protect-mcp trace <receipt_id> [--endpoint <url>] [--depth <n>]
8670
8876
  protect-mcp status [--dir <path>]
8671
8877
  protect-mcp digest [--today] [--dir <path>]
8672
8878
  protect-mcp receipts [--last <n>] [--dir <path>]
8673
8879
  protect-mcp record [--dir <path>] [--live] [--no-open]
8674
- protect-mcp claim [--no <cap>] [--only <c,c>] [--count <verdict>] [--dir <path>] [--output <path>]
8675
- protect-mcp verify-claim <claim.json> [--key <public-hex>]
8880
+ protect-mcp claim [--no <cap>] [--only <c,c>] [--count <verdict>] [--payment-under <amount>] [--anchor] [--dir <path>] [--output <path>]
8881
+ protect-mcp verify-claim <claim.json> [--key <public-hex>] [--check-anchor] [--offline]
8882
+ protect-mcp anchor-record [--dir <path>] [--force]
8676
8883
  protect-mcp bundle [--output <path>] [--dir <path>]
8677
8884
  protect-mcp simulate --policy <path> [--log <path>] [--tier <tier>] [--json]
8678
8885
  protect-mcp report [--period <days>d] [--format md|json] [--output <path>] [--dir <path>]
@@ -8686,6 +8893,7 @@ Options:
8686
8893
  --port <port> HTTP server port (default: 3000 for --http, 9377 for serve)
8687
8894
  --verbose Enable debug logging to stderr
8688
8895
  --help Show this help
8896
+ --version Print the installed version
8689
8897
 
8690
8898
  Commands:
8691
8899
  serve Start HTTP hook server for Claude Code integration (port 9377)
@@ -8711,8 +8919,12 @@ Commands:
8711
8919
  digest Generate a human-readable summary of agent activity
8712
8920
  receipts Show recent persisted signed receipts
8713
8921
  record Open a local, searchable view of your record in the browser
8714
- claim Attest a signed, position-blind claim over your record (e.g. no egress)
8715
- verify-claim Verify a claim attestation offline (signature + predicate + commitment)
8922
+ claim Attest a signed, position-blind claim over your record (e.g. no egress,
8923
+ no payment, every payment under a cap)
8924
+ verify-claim Verify a claim attestation offline (signature + predicate + commitment
8925
+ + the anchor sidecar and issuer identity when present)
8926
+ anchor-record Checkpoint the record's Merkle root + count into the public log
8927
+ (heartbeat-friendly: skips when unchanged; only hashes leave)
8716
8928
  bundle Export an offline-verifiable audit bundle
8717
8929
 
8718
8930
  Examples:
@@ -8792,17 +9004,17 @@ function parseArgs(argv) {
8792
9004
  return { policyPath, cedarDir, slug, enforce, verbose, childCommand };
8793
9005
  }
8794
9006
  async function handleInit(argv) {
8795
- const { writeFileSync: writeFileSync4, existsSync: existsSync9, mkdirSync: mkdirSync3 } = await import("fs");
8796
- const { join: join8 } = await import("path");
9007
+ const { writeFileSync: writeFileSync5, existsSync: existsSync10, mkdirSync: mkdirSync4 } = await import("fs");
9008
+ const { join: join9 } = await import("path");
8797
9009
  let dir = process.cwd();
8798
9010
  const dirIdx = argv.indexOf("--dir");
8799
9011
  if (dirIdx !== -1 && argv[dirIdx + 1]) {
8800
9012
  dir = argv[dirIdx + 1];
8801
9013
  }
8802
- const configPath = join8(dir, "protect-mcp.json");
8803
- const keysDir = join8(dir, "keys");
8804
- const keyPath = join8(keysDir, "gateway.json");
8805
- if (existsSync9(configPath)) {
9014
+ const configPath = join9(dir, "protect-mcp.json");
9015
+ const keysDir = join9(dir, "keys");
9016
+ const keyPath = join9(keysDir, "gateway.json");
9017
+ if (existsSync10(configPath)) {
8806
9018
  process.stderr.write(`[PROTECT_MCP] Config already exists at ${configPath}
8807
9019
  `);
8808
9020
  process.stderr.write("[PROTECT_MCP] Delete it first if you want to regenerate.\n");
@@ -8821,19 +9033,19 @@ async function handleInit(argv) {
8821
9033
  kid: "generated"
8822
9034
  };
8823
9035
  }
8824
- if (!existsSync9(keysDir)) {
8825
- mkdirSync3(keysDir, { recursive: true });
9036
+ if (!existsSync10(keysDir)) {
9037
+ mkdirSync4(keysDir, { recursive: true });
8826
9038
  }
8827
- writeFileSync4(keyPath, JSON.stringify({
9039
+ writeFileSync5(keyPath, JSON.stringify({
8828
9040
  privateKey: keypair.privateKey,
8829
9041
  publicKey: keypair.publicKey,
8830
9042
  kid: keypair.kid,
8831
9043
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
8832
9044
  warning: "KEEP THIS FILE SECRET. Never commit to version control."
8833
9045
  }, null, 2) + "\n");
8834
- const gitignorePath = join8(keysDir, ".gitignore");
8835
- if (!existsSync9(gitignorePath)) {
8836
- writeFileSync4(gitignorePath, "# Never commit signing keys\n*.json\n");
9046
+ const gitignorePath = join9(keysDir, ".gitignore");
9047
+ if (!existsSync10(gitignorePath)) {
9048
+ writeFileSync5(gitignorePath, "# Never commit signing keys\n*.json\n");
8837
9049
  }
8838
9050
  const config = {
8839
9051
  tools: {
@@ -8867,7 +9079,7 @@ async function handleInit(argv) {
8867
9079
  }
8868
9080
  }
8869
9081
  };
8870
- writeFileSync4(configPath, JSON.stringify(config, null, 2) + "\n");
9082
+ writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
8871
9083
  const claudeConfig = {
8872
9084
  "mcpServers": {
8873
9085
  "my-server": {
@@ -8905,8 +9117,8 @@ Add --enforce when ready to block policy violations.
8905
9117
  `);
8906
9118
  }
8907
9119
  async function handleDemo() {
8908
- const { existsSync: existsSync9 } = await import("fs");
8909
- const { join: join8, dirname: dirname3, resolve } = await import("path");
9120
+ const { existsSync: existsSync10 } = await import("fs");
9121
+ const { join: join9, dirname: dirname3, resolve } = await import("path");
8910
9122
  const { realpathSync } = await import("fs");
8911
9123
  const cliPath = resolve(process.argv[1] || "dist/cli.js");
8912
9124
  let cliDir;
@@ -8915,9 +9127,9 @@ async function handleDemo() {
8915
9127
  } catch {
8916
9128
  cliDir = dirname3(cliPath);
8917
9129
  }
8918
- const demoServerPath = join8(cliDir, "demo-server.js");
8919
- const configPath = join8(process.cwd(), "protect-mcp.json");
8920
- const hasConfig = existsSync9(configPath);
9130
+ const demoServerPath = join9(cliDir, "demo-server.js");
9131
+ const configPath = join9(process.cwd(), "protect-mcp.json");
9132
+ const hasConfig = existsSync10(configPath);
8921
9133
  if (!hasConfig) {
8922
9134
  process.stderr.write(`
8923
9135
  ${bold("protect-mcp demo")}
@@ -8991,15 +9203,15 @@ Starting demo server with 5 tools...
8991
9203
  await gateway.start();
8992
9204
  }
8993
9205
  async function handleStatus2(argv) {
8994
- const { readFileSync: readFileSync11, existsSync: existsSync9 } = await import("fs");
8995
- const { join: join8 } = await import("path");
9206
+ const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
9207
+ const { join: join9 } = await import("path");
8996
9208
  let dir = process.cwd();
8997
9209
  const dirIdx = argv.indexOf("--dir");
8998
9210
  if (dirIdx !== -1 && argv[dirIdx + 1]) {
8999
9211
  dir = argv[dirIdx + 1];
9000
9212
  }
9001
- const logPath = join8(dir, ".protect-mcp-log.jsonl");
9002
- if (!existsSync9(logPath)) {
9213
+ const logPath = join9(dir, ".protect-mcp-log.jsonl");
9214
+ if (!existsSync10(logPath)) {
9003
9215
  process.stderr.write(`${bold("protect-mcp status")}
9004
9216
 
9005
9217
  `);
@@ -9088,8 +9300,8 @@ ${bold("protect-mcp status")}
9088
9300
  process.stdout.write(` ${reason.padEnd(25)} ${count}
9089
9301
  `);
9090
9302
  }
9091
- const evidencePath = join8(dir, ".protect-mcp-evidence.json");
9092
- if (existsSync9(evidencePath)) {
9303
+ const evidencePath = join9(dir, ".protect-mcp-evidence.json");
9304
+ if (existsSync10(evidencePath)) {
9093
9305
  try {
9094
9306
  const evidenceRaw = readFileSync11(evidencePath, "utf-8");
9095
9307
  const evidence = JSON.parse(evidenceRaw);
@@ -9100,8 +9312,8 @@ ${bold("protect-mcp status")}
9100
9312
  } catch {
9101
9313
  }
9102
9314
  }
9103
- const keyPath = join8(dir, "keys", "gateway.json");
9104
- if (existsSync9(keyPath)) {
9315
+ const keyPath = join9(dir, "keys", "gateway.json");
9316
+ if (existsSync10(keyPath)) {
9105
9317
  try {
9106
9318
  const keyData = JSON.parse(readFileSync11(keyPath, "utf-8"));
9107
9319
  if (keyData.publicKey) {
@@ -9131,7 +9343,7 @@ function commandNeedsValue(argv, flag) {
9131
9343
  return Boolean(value && !value.startsWith("--"));
9132
9344
  }
9133
9345
  function absoluteOrCwd(pathValue) {
9134
- return (0, import_node_path8.resolve)(process.cwd(), pathValue);
9346
+ return (0, import_node_path9.resolve)(process.cwd(), pathValue);
9135
9347
  }
9136
9348
  function shellQuoteArg(arg) {
9137
9349
  if (/^[A-Za-z0-9_./:@%+=,-]+$/.test(arg)) return arg;
@@ -9150,18 +9362,18 @@ function wrapperArgsFor(command, opts) {
9150
9362
  }
9151
9363
  function claudeDesktopConfigPath() {
9152
9364
  if (process.platform === "darwin") {
9153
- return (0, import_node_path8.join)((0, import_node_os.homedir)(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
9365
+ return (0, import_node_path9.join)((0, import_node_os.homedir)(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
9154
9366
  }
9155
9367
  if (process.platform === "win32") {
9156
- return (0, import_node_path8.join)(process.env.APPDATA || (0, import_node_path8.join)((0, import_node_os.homedir)(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
9368
+ return (0, import_node_path9.join)(process.env.APPDATA || (0, import_node_path9.join)((0, import_node_os.homedir)(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
9157
9369
  }
9158
- return (0, import_node_path8.join)((0, import_node_os.homedir)(), ".config", "Claude", "claude_desktop_config.json");
9370
+ return (0, import_node_path9.join)((0, import_node_os.homedir)(), ".config", "Claude", "claude_desktop_config.json");
9159
9371
  }
9160
9372
  async function ensureLocalConfig(dir = process.cwd()) {
9161
- const { existsSync: existsSync9 } = await import("fs");
9162
- const { join: join8, resolve } = await import("path");
9163
- const configPath = join8(dir, "protect-mcp.json");
9164
- if (!existsSync9(configPath)) {
9373
+ const { existsSync: existsSync10 } = await import("fs");
9374
+ const { join: join9, resolve } = await import("path");
9375
+ const configPath = join9(dir, "protect-mcp.json");
9376
+ if (!existsSync10(configPath)) {
9165
9377
  process.stderr.write(`${bold("protect-mcp wrap")}
9166
9378
 
9167
9379
  No protect-mcp.json found; creating local shadow-mode config first.
@@ -9173,7 +9385,7 @@ No protect-mcp.json found; creating local shadow-mode config first.
9173
9385
  }
9174
9386
  function parseJsonlFile(pathValue) {
9175
9387
  try {
9176
- const raw = (0, import_node_fs12.readFileSync)(pathValue, "utf-8");
9388
+ const raw = (0, import_node_fs13.readFileSync)(pathValue, "utf-8");
9177
9389
  return raw.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
9178
9390
  try {
9179
9391
  return [JSON.parse(line)];
@@ -9187,7 +9399,7 @@ function parseJsonlFile(pathValue) {
9187
9399
  }
9188
9400
  function parseJsonlRecords(pathValue) {
9189
9401
  try {
9190
- const raw = (0, import_node_fs12.readFileSync)(pathValue, "utf-8");
9402
+ const raw = (0, import_node_fs13.readFileSync)(pathValue, "utf-8");
9191
9403
  return raw.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
9192
9404
  try {
9193
9405
  return [{
@@ -9205,8 +9417,8 @@ function parseJsonlRecords(pathValue) {
9205
9417
  }
9206
9418
  function loadPolicyJson(policyPath) {
9207
9419
  try {
9208
- if (!(0, import_node_fs12.existsSync)(policyPath)) return null;
9209
- return JSON.parse((0, import_node_fs12.readFileSync)(policyPath, "utf-8"));
9420
+ if (!(0, import_node_fs13.existsSync)(policyPath)) return null;
9421
+ return JSON.parse((0, import_node_fs13.readFileSync)(policyPath, "utf-8"));
9210
9422
  } catch {
9211
9423
  return null;
9212
9424
  }
@@ -9351,10 +9563,10 @@ function suggestedGuardrailFor(_tool, risk, reasons) {
9351
9563
  policy: { rate_limit: "100/hour" }
9352
9564
  };
9353
9565
  }
9354
- function buildDashboardSummary(dir, policyPath = (0, import_node_path8.join)(dir, "protect-mcp.json")) {
9355
- const logPath = (0, import_node_path8.join)(dir, ".protect-mcp-log.jsonl");
9356
- const receiptPath = (0, import_node_path8.join)(dir, ".protect-mcp-receipts.jsonl");
9357
- const keyPath = (0, import_node_path8.join)(dir, "keys", "gateway.json");
9566
+ function buildDashboardSummary(dir, policyPath = (0, import_node_path9.join)(dir, "protect-mcp.json")) {
9567
+ const logPath = (0, import_node_path9.join)(dir, ".protect-mcp-log.jsonl");
9568
+ const receiptPath = (0, import_node_path9.join)(dir, ".protect-mcp-receipts.jsonl");
9569
+ const keyPath = (0, import_node_path9.join)(dir, "keys", "gateway.json");
9358
9570
  const entries = parseJsonlFile(logPath);
9359
9571
  const receiptRecords = parseJsonlRecords(receiptPath);
9360
9572
  const receipts = receiptRecords.map((record) => record.value);
@@ -9399,9 +9611,9 @@ function buildDashboardSummary(dir, policyPath = (0, import_node_path8.join)(dir
9399
9611
  const pendingApprovals = entries.filter((e) => e.decision === "require_approval").slice(-25).reverse();
9400
9612
  const chains = buildReceiptChains(entries, receiptRecords);
9401
9613
  let key = null;
9402
- if ((0, import_node_fs12.existsSync)(keyPath)) {
9614
+ if ((0, import_node_fs13.existsSync)(keyPath)) {
9403
9615
  try {
9404
- const parsed = JSON.parse((0, import_node_fs12.readFileSync)(keyPath, "utf-8"));
9616
+ const parsed = JSON.parse((0, import_node_fs13.readFileSync)(keyPath, "utf-8"));
9405
9617
  key = {
9406
9618
  kid: parsed.kid || null,
9407
9619
  issuer: parsed.issuer || "protect-mcp",
@@ -9418,10 +9630,10 @@ function buildDashboardSummary(dir, policyPath = (0, import_node_path8.join)(dir
9418
9630
  receipts: receiptPath,
9419
9631
  key: keyPath,
9420
9632
  policy: policyPath,
9421
- log_exists: (0, import_node_fs12.existsSync)(logPath),
9422
- receipts_exist: (0, import_node_fs12.existsSync)(receiptPath),
9423
- key_exists: (0, import_node_fs12.existsSync)(keyPath),
9424
- policy_exists: (0, import_node_fs12.existsSync)(policyPath)
9633
+ log_exists: (0, import_node_fs13.existsSync)(logPath),
9634
+ receipts_exist: (0, import_node_fs13.existsSync)(receiptPath),
9635
+ key_exists: (0, import_node_fs13.existsSync)(keyPath),
9636
+ policy_exists: (0, import_node_fs13.existsSync)(policyPath)
9425
9637
  },
9426
9638
  totals: {
9427
9639
  decisions: entries.length,
@@ -9458,7 +9670,7 @@ function buildDashboardSummary(dir, policyPath = (0, import_node_path8.join)(dir
9458
9670
  }))
9459
9671
  },
9460
9672
  connector_pilots: {
9461
- directory: (0, import_node_path8.join)(dir, ".protect-mcp", "connectors"),
9673
+ directory: (0, import_node_path9.join)(dir, ".protect-mcp", "connectors"),
9462
9674
  installed: readInstalledConnectorPilots(dir),
9463
9675
  doctor: connectorDoctor(dir),
9464
9676
  available: CONNECTOR_PILOTS.map((pilot) => ({
@@ -9836,7 +10048,7 @@ async function handleDashboard(argv) {
9836
10048
  const { resolve } = await import("path");
9837
10049
  const port = commandNeedsValue(argv, "--port") ? parseInt(flagValue(argv, "--port") || "9877", 10) : 9877;
9838
10050
  const dir = resolve(commandNeedsValue(argv, "--dir") ? flagValue(argv, "--dir") || process.cwd() : process.cwd());
9839
- const policyPath = resolve(flagValue(argv, "--policy") || (0, import_node_path8.join)(dir, "protect-mcp.json"));
10051
+ const policyPath = resolve(flagValue(argv, "--policy") || (0, import_node_path9.join)(dir, "protect-mcp.json"));
9840
10052
  const approvalEndpoint = flagValue(argv, "--approval-endpoint");
9841
10053
  const approvalNonce = flagValue(argv, "--approval-nonce");
9842
10054
  const open = argv.includes("--open");
@@ -10041,32 +10253,32 @@ function writeToolPolicy(policyPath, tool, action) {
10041
10253
  tools,
10042
10254
  default_tier: existing.default_tier || "unknown"
10043
10255
  };
10044
- (0, import_node_fs12.writeFileSync)(policyPath, JSON.stringify(next, null, 2) + "\n");
10256
+ (0, import_node_fs13.writeFileSync)(policyPath, JSON.stringify(next, null, 2) + "\n");
10045
10257
  return next;
10046
10258
  }
10047
10259
  function policyPackDirectory(dir) {
10048
- return (0, import_node_path8.join)(dir, "cedar");
10260
+ return (0, import_node_path9.join)(dir, "cedar");
10049
10261
  }
10050
10262
  function installedPolicyPackIds(dir) {
10051
10263
  const cedarDir = policyPackDirectory(dir);
10052
10264
  return POLICY_PACKS.filter(
10053
- (pack) => pack.files.every((file) => (0, import_node_fs12.existsSync)((0, import_node_path8.join)(cedarDir, file.path)))
10265
+ (pack) => pack.files.every((file) => (0, import_node_fs13.existsSync)((0, import_node_path9.join)(cedarDir, file.path)))
10054
10266
  ).map((pack) => pack.id);
10055
10267
  }
10056
10268
  function installPolicyPackToDir(dir, packId, force = false) {
10057
10269
  const packs = packId === "all" ? POLICY_PACKS : [getPolicyPack(packId)].filter(Boolean);
10058
10270
  if (packs.length === 0) throw new Error(`Unknown policy pack: ${packId}`);
10059
10271
  const outDir = policyPackDirectory(dir);
10060
- (0, import_node_fs12.mkdirSync)(outDir, { recursive: true });
10272
+ (0, import_node_fs13.mkdirSync)(outDir, { recursive: true });
10061
10273
  const written = [];
10062
10274
  for (const pack of packs) {
10063
10275
  for (const file of pack.files) {
10064
- const outPath = (0, import_node_path8.join)(outDir, file.path);
10065
- if ((0, import_node_fs12.existsSync)(outPath) && !force) {
10276
+ const outPath = (0, import_node_path9.join)(outDir, file.path);
10277
+ if ((0, import_node_fs13.existsSync)(outPath) && !force) {
10066
10278
  throw new Error(`Refusing to overwrite ${outPath}. Pass force=true if intentional.`);
10067
10279
  }
10068
- (0, import_node_fs12.mkdirSync)((0, import_node_path8.dirname)(outPath), { recursive: true });
10069
- (0, import_node_fs12.writeFileSync)(outPath, file.contents.endsWith("\n") ? file.contents : `${file.contents}
10280
+ (0, import_node_fs13.mkdirSync)((0, import_node_path9.dirname)(outPath), { recursive: true });
10281
+ (0, import_node_fs13.writeFileSync)(outPath, file.contents.endsWith("\n") ? file.contents : `${file.contents}
10070
10282
  `);
10071
10283
  written.push(outPath);
10072
10284
  }
@@ -10074,19 +10286,19 @@ function installPolicyPackToDir(dir, packId, force = false) {
10074
10286
  return { dir: outDir, written, packs: packs.map((pack) => pack.id) };
10075
10287
  }
10076
10288
  function dashboardRegistryStatus(dir) {
10077
- const identityPath = (0, import_node_path8.join)(dir, ".protect-mcp-org.json");
10078
- const registryPath = (0, import_node_path8.join)(dir, ".protect-mcp-registry.json");
10079
- const verifierPath = (0, import_node_path8.join)(dir, "scopeblind-verifier.html");
10080
- const identity = (0, import_node_fs12.existsSync)(identityPath) ? (() => {
10289
+ const identityPath = (0, import_node_path9.join)(dir, ".protect-mcp-org.json");
10290
+ const registryPath = (0, import_node_path9.join)(dir, ".protect-mcp-registry.json");
10291
+ const verifierPath = (0, import_node_path9.join)(dir, "scopeblind-verifier.html");
10292
+ const identity = (0, import_node_fs13.existsSync)(identityPath) ? (() => {
10081
10293
  try {
10082
- return JSON.parse((0, import_node_fs12.readFileSync)(identityPath, "utf-8"));
10294
+ return JSON.parse((0, import_node_fs13.readFileSync)(identityPath, "utf-8"));
10083
10295
  } catch {
10084
10296
  return null;
10085
10297
  }
10086
10298
  })() : null;
10087
- const registry = (0, import_node_fs12.existsSync)(registryPath) ? (() => {
10299
+ const registry = (0, import_node_fs13.existsSync)(registryPath) ? (() => {
10088
10300
  try {
10089
- return JSON.parse((0, import_node_fs12.readFileSync)(registryPath, "utf-8"));
10301
+ return JSON.parse((0, import_node_fs13.readFileSync)(registryPath, "utf-8"));
10090
10302
  } catch {
10091
10303
  return null;
10092
10304
  }
@@ -10094,9 +10306,9 @@ function dashboardRegistryStatus(dir) {
10094
10306
  const anchors = Array.isArray(registry?.anchors) ? registry.anchors : [];
10095
10307
  const hosted = anchors.some((anchor) => anchor.timestamp_source === "scopeblind-hosted");
10096
10308
  return {
10097
- identity_exists: (0, import_node_fs12.existsSync)(identityPath),
10098
- registry_exists: (0, import_node_fs12.existsSync)(registryPath),
10099
- verifier_exists: (0, import_node_fs12.existsSync)(verifierPath),
10309
+ identity_exists: (0, import_node_fs13.existsSync)(identityPath),
10310
+ registry_exists: (0, import_node_fs13.existsSync)(registryPath),
10311
+ verifier_exists: (0, import_node_fs13.existsSync)(verifierPath),
10100
10312
  identity_path: identityPath,
10101
10313
  registry_path: registryPath,
10102
10314
  verifier_path: verifierPath,
@@ -10117,13 +10329,13 @@ async function readJsonBody(req) {
10117
10329
  }
10118
10330
  async function buildAuditBundleForDir(dir) {
10119
10331
  const { createAuditBundle: createAuditBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
10120
- const receiptPath = (0, import_node_path8.join)(dir, ".protect-mcp-receipts.jsonl");
10121
- const keyPath = (0, import_node_path8.join)(dir, "keys", "gateway.json");
10122
- if (!(0, import_node_fs12.existsSync)(receiptPath)) throw new Error("No receipt file found.");
10123
- if (!(0, import_node_fs12.existsSync)(keyPath)) throw new Error("No signing key found.");
10332
+ const receiptPath = (0, import_node_path9.join)(dir, ".protect-mcp-receipts.jsonl");
10333
+ const keyPath = (0, import_node_path9.join)(dir, "keys", "gateway.json");
10334
+ if (!(0, import_node_fs13.existsSync)(receiptPath)) throw new Error("No receipt file found.");
10335
+ if (!(0, import_node_fs13.existsSync)(keyPath)) throw new Error("No signing key found.");
10124
10336
  const receipts = parseJsonlFile(receiptPath);
10125
10337
  if (receipts.length === 0) throw new Error("No signed receipts found.");
10126
- const keyData = JSON.parse((0, import_node_fs12.readFileSync)(keyPath, "utf-8"));
10338
+ const keyData = JSON.parse((0, import_node_fs13.readFileSync)(keyPath, "utf-8"));
10127
10339
  return createAuditBundle2({
10128
10340
  tenant: keyData.issuer || "protect-mcp",
10129
10341
  receipts,
@@ -10141,17 +10353,17 @@ function collectSelectiveDisclosurePackages(dir) {
10141
10353
  const out = [];
10142
10354
  const seen = /* @__PURE__ */ new Set();
10143
10355
  const candidates = [];
10144
- const receiptsDir = (0, import_node_path8.join)(dir, "receipts");
10145
- if ((0, import_node_fs12.existsSync)(receiptsDir)) {
10146
- for (const name of (0, import_node_fs12.readdirSync)(receiptsDir)) {
10356
+ const receiptsDir = (0, import_node_path9.join)(dir, "receipts");
10357
+ if ((0, import_node_fs13.existsSync)(receiptsDir)) {
10358
+ for (const name of (0, import_node_fs13.readdirSync)(receiptsDir)) {
10147
10359
  if (name.includes("selective-disclosure") && name.endsWith(".json")) {
10148
- candidates.push((0, import_node_path8.join)(receiptsDir, name));
10360
+ candidates.push((0, import_node_path9.join)(receiptsDir, name));
10149
10361
  }
10150
10362
  }
10151
10363
  }
10152
- const jsonlPath = (0, import_node_path8.join)(dir, ".protect-mcp-selective-disclosures.jsonl");
10153
- if ((0, import_node_fs12.existsSync)(jsonlPath)) {
10154
- for (const line of (0, import_node_fs12.readFileSync)(jsonlPath, "utf-8").split("\n").map((s) => s.trim()).filter(Boolean)) {
10364
+ const jsonlPath = (0, import_node_path9.join)(dir, ".protect-mcp-selective-disclosures.jsonl");
10365
+ if ((0, import_node_fs13.existsSync)(jsonlPath)) {
10366
+ for (const line of (0, import_node_fs13.readFileSync)(jsonlPath, "utf-8").split("\n").map((s) => s.trim()).filter(Boolean)) {
10155
10367
  try {
10156
10368
  const parsed = JSON.parse(line);
10157
10369
  addSelectiveDisclosure(out, seen, parsed);
@@ -10161,7 +10373,7 @@ function collectSelectiveDisclosurePackages(dir) {
10161
10373
  }
10162
10374
  for (const path of candidates) {
10163
10375
  try {
10164
- const parsed = JSON.parse((0, import_node_fs12.readFileSync)(path, "utf-8"));
10376
+ const parsed = JSON.parse((0, import_node_fs13.readFileSync)(path, "utf-8"));
10165
10377
  addSelectiveDisclosure(out, seen, parsed);
10166
10378
  } catch {
10167
10379
  }
@@ -10194,7 +10406,7 @@ async function recordApprovalResolution(opts) {
10194
10406
  takeover_note: opts.body.takeover_note || void 0,
10195
10407
  payload_hash: opts.body.payload_hash || void 0
10196
10408
  };
10197
- (0, import_node_fs12.appendFileSync)((0, import_node_path8.join)(opts.dir, ".protect-mcp-approval-resolutions.jsonl"), JSON.stringify(record) + "\n");
10409
+ (0, import_node_fs13.appendFileSync)((0, import_node_path9.join)(opts.dir, ".protect-mcp-approval-resolutions.jsonl"), JSON.stringify(record) + "\n");
10198
10410
  let forwarded = null;
10199
10411
  if (resolution === "approve" && opts.approvalEndpoint && opts.approvalNonce) {
10200
10412
  const endpoint = opts.approvalEndpoint.replace(/\/$/, "") + "/approve";
@@ -10217,7 +10429,7 @@ async function recordApprovalResolution(opts) {
10217
10429
  return { recorded: true, resolution: record, forwarded };
10218
10430
  }
10219
10431
  async function handleRecommend(argv) {
10220
- const { writeFileSync: writeFileSync4 } = await import("fs");
10432
+ const { writeFileSync: writeFileSync5 } = await import("fs");
10221
10433
  const { resolve } = await import("path");
10222
10434
  const dir = resolve(commandNeedsValue(argv, "--dir") ? flagValue(argv, "--dir") || process.cwd() : process.cwd());
10223
10435
  const outputPath = resolve(flagValue(argv, "--output") || "protect-mcp.recommended.json");
@@ -10266,7 +10478,7 @@ Dry run only. Write the policy with:
10266
10478
  process.stdout.write(dim(body));
10267
10479
  return;
10268
10480
  }
10269
- writeFileSync4(outputPath, body);
10481
+ writeFileSync5(outputPath, body);
10270
10482
  process.stdout.write(`
10271
10483
  ${green("\u2713 Wrote recommended policy")}
10272
10484
  `);
@@ -10279,7 +10491,7 @@ ${green("\u2713 Wrote recommended policy")}
10279
10491
  `);
10280
10492
  }
10281
10493
  async function handleWrap(argv) {
10282
- const { existsSync: existsSync9, readFileSync: readFileSync11, writeFileSync: writeFileSync4 } = await import("fs");
10494
+ const { existsSync: existsSync10, readFileSync: readFileSync11, writeFileSync: writeFileSync5 } = await import("fs");
10283
10495
  const { resolve } = await import("path");
10284
10496
  const configFlag = flagValue(argv, "--config");
10285
10497
  const cedarFlag = flagValue(argv, "--cedar");
@@ -10316,7 +10528,7 @@ ${bold("protect-mcp wrap")}
10316
10528
  return;
10317
10529
  }
10318
10530
  const claudePath = resolve(flagValue(argv, "--path") || claudeDesktopConfigPath());
10319
- if (!claudeDesktop && !existsSync9(claudePath)) {
10531
+ if (!claudeDesktop && !existsSync10(claudePath)) {
10320
10532
  process.stdout.write(`
10321
10533
  ${bold("protect-mcp wrap")}
10322
10534
 
@@ -10333,7 +10545,7 @@ ${bold("protect-mcp wrap")}
10333
10545
  `);
10334
10546
  return;
10335
10547
  }
10336
- if (!existsSync9(claudePath)) {
10548
+ if (!existsSync10(claudePath)) {
10337
10549
  process.stderr.write(`protect-mcp wrap: Claude Desktop config not found at ${claudePath}
10338
10550
  `);
10339
10551
  process.exit(1);
@@ -10404,8 +10616,8 @@ Dry run only. Apply with:
10404
10616
  return;
10405
10617
  }
10406
10618
  const backupPath = `${claudePath}.bak.${Date.now()}`;
10407
- writeFileSync4(backupPath, readFileSync11(claudePath, "utf-8"));
10408
- writeFileSync4(claudePath, JSON.stringify(next, null, 2) + "\n");
10619
+ writeFileSync5(backupPath, readFileSync11(claudePath, "utf-8"));
10620
+ writeFileSync5(claudePath, JSON.stringify(next, null, 2) + "\n");
10409
10621
  process.stdout.write(`
10410
10622
  ${green("\u2713 Claude Desktop config updated")}
10411
10623
  `);
@@ -10431,14 +10643,14 @@ function yellow(s) {
10431
10643
  return process.env.NO_COLOR ? s : `\x1B[33m${s}\x1B[0m`;
10432
10644
  }
10433
10645
  async function handleDigest(argv) {
10434
- const { readFileSync: readFileSync11, existsSync: existsSync9 } = await import("fs");
10435
- const { join: join8 } = await import("path");
10646
+ const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
10647
+ const { join: join9 } = await import("path");
10436
10648
  let dir = process.cwd();
10437
10649
  const dirIdx = argv.indexOf("--dir");
10438
10650
  if (dirIdx !== -1 && argv[dirIdx + 1]) dir = argv[dirIdx + 1];
10439
10651
  const today = argv.includes("--today");
10440
- const logPath = join8(dir, ".protect-mcp-log.jsonl");
10441
- if (!existsSync9(logPath)) {
10652
+ const logPath = join9(dir, ".protect-mcp-log.jsonl");
10653
+ if (!existsSync10(logPath)) {
10442
10654
  process.stderr.write(`${bold("protect-mcp digest")}
10443
10655
 
10444
10656
  No log file found. Run protect-mcp first.
@@ -10522,15 +10734,15 @@ ${bold("\u{1F6E1}\uFE0F Agent Daily Digest")}
10522
10734
  `);
10523
10735
  }
10524
10736
  async function handleReceipts2(argv) {
10525
- const { readFileSync: readFileSync11, existsSync: existsSync9 } = await import("fs");
10526
- const { join: join8 } = await import("path");
10737
+ const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
10738
+ const { join: join9 } = await import("path");
10527
10739
  let dir = process.cwd();
10528
10740
  const dirIdx = argv.indexOf("--dir");
10529
10741
  if (dirIdx !== -1 && argv[dirIdx + 1]) dir = argv[dirIdx + 1];
10530
10742
  const lastIdx = argv.indexOf("--last");
10531
10743
  const count = lastIdx !== -1 && argv[lastIdx + 1] ? parseInt(argv[lastIdx + 1], 10) : 20;
10532
- const receiptsPath = join8(dir, ".protect-mcp-receipts.jsonl");
10533
- if (!existsSync9(receiptsPath)) {
10744
+ const receiptsPath = join9(dir, ".protect-mcp-receipts.jsonl");
10745
+ if (!existsSync10(receiptsPath)) {
10534
10746
  process.stderr.write(`${bold("protect-mcp receipts")}
10535
10747
 
10536
10748
  No signed receipt file found. Run protect-mcp with signing enabled first.
@@ -10564,19 +10776,19 @@ async function pkgVersion() {
10564
10776
  if (_pkgV) return _pkgV;
10565
10777
  let v = "0.0.0";
10566
10778
  try {
10567
- const { readFileSync: readFileSync11, existsSync: existsSync9, realpathSync } = await import("fs");
10568
- const { dirname: dirname3, join: join8, resolve } = await import("path");
10779
+ const { readFileSync: readFileSync11, existsSync: existsSync10, realpathSync } = await import("fs");
10780
+ const { dirname: dirname3, join: join9, resolve } = await import("path");
10569
10781
  let base = "";
10570
10782
  try {
10571
10783
  base = dirname3(realpathSync(resolve(process.argv[1] || "")));
10572
10784
  } catch {
10573
10785
  }
10574
10786
  const candidates = [
10575
- base ? join8(base, "..", "package.json") : "",
10576
- base ? join8(base, "package.json") : ""
10787
+ base ? join9(base, "..", "package.json") : "",
10788
+ base ? join9(base, "package.json") : ""
10577
10789
  ].filter(Boolean);
10578
10790
  for (const p of candidates) {
10579
- if (existsSync9(p)) {
10791
+ if (existsSync10(p)) {
10580
10792
  const parsed = JSON.parse(readFileSync11(p, "utf-8"));
10581
10793
  if (parsed && parsed.name === "protect-mcp" && parsed.version) {
10582
10794
  v = parsed.version;
@@ -10613,16 +10825,16 @@ function mapRecordEntry(e) {
10613
10825
  return { ts, tool, verdict, reason, hook, signed, caps, agent, dur, id: String(e.request_id || p.request_id || ""), digest, raw: e };
10614
10826
  }
10615
10827
  async function handleRecord(argv) {
10616
- const { readFileSync: readFileSync11, existsSync: existsSync9, writeFileSync: writeFileSync4 } = await import("fs");
10617
- const { join: join8 } = await import("path");
10828
+ const { readFileSync: readFileSync11, existsSync: existsSync10, writeFileSync: writeFileSync5 } = await import("fs");
10829
+ const { join: join9 } = await import("path");
10618
10830
  const osMod = await import("os");
10619
10831
  const cp = await import("child_process");
10620
10832
  let dir = process.cwd();
10621
10833
  const di = argv.indexOf("--dir");
10622
10834
  if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
10623
- const recPath = join8(dir, ".protect-mcp-receipts.jsonl");
10624
- const logPath = join8(dir, ".protect-mcp-log.jsonl");
10625
- const pick = () => existsSync9(recPath) ? recPath : existsSync9(logPath) ? logPath : null;
10835
+ const recPath = join9(dir, ".protect-mcp-receipts.jsonl");
10836
+ const logPath = join9(dir, ".protect-mcp-log.jsonl");
10837
+ const pick = () => existsSync10(recPath) ? recPath : existsSync10(logPath) ? logPath : null;
10626
10838
  const chosen = pick();
10627
10839
  if (!chosen) {
10628
10840
  process.stderr.write(`
@@ -10647,7 +10859,7 @@ Start the gate with ${bold("npx protect-mcp serve")}, use your agent, then run t
10647
10859
  let pinnedKey = "";
10648
10860
  let pinnedKid = "";
10649
10861
  try {
10650
- const kd = JSON.parse(readFileSync11(join8(dir, "keys", "gateway.json"), "utf-8"));
10862
+ const kd = JSON.parse(readFileSync11(join9(dir, "keys", "gateway.json"), "utf-8"));
10651
10863
  if (kd && typeof kd.publicKey === "string" && /^[0-9a-f]{64}$/i.test(kd.publicKey)) {
10652
10864
  pinnedKey = kd.publicKey;
10653
10865
  pinnedKid = typeof kd.kid === "string" ? kd.kid : "";
@@ -10708,8 +10920,8 @@ ${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} live at ${url}
10708
10920
  const recs = readRecs(chosen);
10709
10921
  const meta = { file: chosen, signed: chosen === recPath, count: recs.length, live: false, pinned_key: pinnedKey, pinned_kid: pinnedKid };
10710
10922
  const html = RECORD_HTML.replace("__DATA__", () => JSON.stringify(recs)).replace("__META__", () => JSON.stringify(meta));
10711
- const out = join8(osMod.tmpdir(), "protect-mcp-record-" + Date.now() + ".html");
10712
- writeFileSync4(out, html);
10923
+ const out = join9(osMod.tmpdir(), "protect-mcp-record-" + Date.now() + ".html");
10924
+ writeFileSync5(out, html);
10713
10925
  openTarget(out);
10714
10926
  process.stdout.write(`
10715
10927
  ${bold("\u{1F6E1}\uFE0F Your record")} ${dim("\xB7")} ${recs.length} decision${recs.length === 1 ? "" : "s"}, all on this machine
@@ -10882,27 +11094,30 @@ render();kickVerify();
10882
11094
  if(META.live){var poll=function(){fetch('/data',{cache:'no-store'}).then(function(r){return r.json()}).then(function(d){var nr=d.recs||[];var changed=nr.length!==RECORDS.length;RECORDS=nr;META.count=RECORDS.length;if(typeof d.signed==='boolean')META.signed=d.signed;if(changed){render();kickVerify()}}).catch(function(){})};poll();setInterval(poll,2000);}
10883
11095
  </script></body></html>`;
10884
11096
  async function handleClaim(argv) {
10885
- const { readFileSync: readFileSync11, existsSync: existsSync9, writeFileSync: writeFileSync4 } = await import("fs");
10886
- const { join: join8 } = await import("path");
11097
+ const { readFileSync: readFileSync11, existsSync: existsSync10, writeFileSync: writeFileSync5 } = await import("fs");
11098
+ const { join: join9 } = await import("path");
10887
11099
  const { buildClaim: buildClaim2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
10888
11100
  let dir = process.cwd();
10889
11101
  const di = argv.indexOf("--dir");
10890
11102
  if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
10891
11103
  let predicate = null;
10892
- const noIdx = argv.indexOf("--no"), onlyIdx = argv.indexOf("--only"), nvIdx = argv.indexOf("--no-verdict"), cvIdx = argv.indexOf("--count");
11104
+ const noIdx = argv.indexOf("--no"), onlyIdx = argv.indexOf("--only"), nvIdx = argv.indexOf("--no-verdict"), cvIdx = argv.indexOf("--count"), puIdx = argv.indexOf("--payment-under");
10893
11105
  if (noIdx !== -1 && argv[noIdx + 1]) predicate = { kind: "no_capability", capability: argv[noIdx + 1] };
10894
11106
  else if (onlyIdx !== -1 && argv[onlyIdx + 1]) predicate = { kind: "only_capabilities", capabilities: argv[onlyIdx + 1].split(",").map((s) => s.trim()).filter(Boolean) };
10895
11107
  else if (nvIdx !== -1 && argv[nvIdx + 1]) predicate = { kind: "no_verdict", verdict: argv[nvIdx + 1] };
10896
11108
  else if (cvIdx !== -1 && argv[cvIdx + 1]) predicate = { kind: "count_verdict", verdict: argv[cvIdx + 1] };
11109
+ else if (puIdx !== -1 && argv[puIdx + 1] && isFinite(parseFloat(argv[puIdx + 1]))) predicate = { kind: "payment_under", cap: parseFloat(argv[puIdx + 1]) };
10897
11110
  if (!predicate) {
10898
11111
  process.stderr.write(`
10899
11112
  ${bold("protect-mcp claim")}
10900
11113
 
10901
11114
  Attest a signed, position-blind claim over your record:
10902
- --no <capability> no action used it, e.g. ${dim("--no net.egress")}
11115
+ --no <capability> no action used it, e.g. ${dim("--no net.egress")} or ${dim("--no payment")}
10903
11116
  --only <c1,c2,...> all actions confined to these capabilities
10904
11117
  --no-verdict <verdict> e.g. ${dim("--no-verdict blocked")}
10905
11118
  --count <verdict> how many, e.g. ${dim("--count blocked")}
11119
+ --payment-under <cap> every agent payment stayed under the cap (amounts the
11120
+ gate could not read count as OVER, so this cannot lie)
10906
11121
  --anchor also record the claim digest in the public append-only
10907
11122
  log so a counterparty can trust it is complete (only the
10908
11123
  hash is sent; your record stays local)
@@ -10913,8 +11128,8 @@ Example: ${bold("npx protect-mcp claim --no net.egress --anchor")}
10913
11128
  process.exit(0);
10914
11129
  return;
10915
11130
  }
10916
- const keyPath = join8(dir, "keys", "gateway.json");
10917
- if (!existsSync9(keyPath)) {
11131
+ const keyPath = join9(dir, "keys", "gateway.json");
11132
+ if (!existsSync10(keyPath)) {
10918
11133
  process.stderr.write(`
10919
11134
  ${bold("protect-mcp claim")}
10920
11135
 
@@ -10943,8 +11158,8 @@ protect-mcp claim: ${keyPath} is missing privateKey/publicKey.
10943
11158
  process.exit(1);
10944
11159
  return;
10945
11160
  }
10946
- const recPath = join8(dir, ".protect-mcp-receipts.jsonl");
10947
- if (!existsSync9(recPath)) {
11161
+ const recPath = join9(dir, ".protect-mcp-receipts.jsonl");
11162
+ if (!existsSync10(recPath)) {
10948
11163
  process.stderr.write(`
10949
11164
  ${bold("protect-mcp claim")}
10950
11165
 
@@ -10971,8 +11186,8 @@ protect-mcp claim: no readable receipts in ${recPath}.
10971
11186
  }
10972
11187
  const pack = buildClaim2(receipts, predicate, { privateKey: key.privateKey, publicKey: key.publicKey, kid: key.kid || "gateway", issuer: "protect-mcp" }, (/* @__PURE__ */ new Date()).toISOString());
10973
11188
  const oi = argv.indexOf("--output");
10974
- const out = oi !== -1 && argv[oi + 1] ? argv[oi + 1] : join8(dir, "claim-" + Date.now() + ".json");
10975
- writeFileSync4(out, JSON.stringify(pack, null, 2) + "\n");
11189
+ const out = oi !== -1 && argv[oi + 1] ? argv[oi + 1] : join9(dir, "claim-" + Date.now() + ".json");
11190
+ writeFileSync5(out, JSON.stringify(pack, null, 2) + "\n");
10976
11191
  process.stdout.write(`
10977
11192
  ${bold("\u{1F6E1}\uFE0F Signed claim")}
10978
11193
  `);
@@ -10998,15 +11213,25 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
10998
11213
  );
10999
11214
  if (res.ok) {
11000
11215
  const sidecar = out.replace(/\.json$/, "") + ".anchor.json";
11001
- writeFileSync4(sidecar, JSON.stringify({ log: logBase || "https://scopeblind.com", seq: res.seq, entry_url: res.entry_url, anchored_at: res.anchored_at, claim_digest: res.claim_digest, envelope: res.envelope }, null, 2) + "\n");
11216
+ writeFileSync5(sidecar, JSON.stringify({ log: logBase || "https://scopeblind.com", seq: res.seq, entry_url: res.entry_url, anchored_at: res.anchored_at, claim_digest: res.claim_digest, envelope: res.envelope }, null, 2) + "\n");
11002
11217
  process.stdout.write(` ${green("Anchored")} as log entry ${bold("#" + res.seq)}${res.already_anchored ? dim(" (already present)") : ""} ${dim(res.entry_url || "")}
11003
11218
  `);
11004
11219
  process.stdout.write(` ${dim("A counterparty can now confirm this exact claim existed at " + (res.anchored_at || "this time") + " and cannot be quietly re-cut.")}
11005
11220
  `);
11006
11221
  process.stdout.write(` ${dim("Anchor record written to " + sidecar + ". Only the digest was sent; your record stayed local.")}
11007
11222
  `);
11008
- process.stdout.write(` ${dim("To anchor as your enrolled org identity (a key a counterparty can pin), see")} ${bold("scopeblind.com/enroll")}
11223
+ const { lookupPinnedIdentity: lookupPinnedIdentity2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
11224
+ const who = await lookupPinnedIdentity2(key.publicKey, { log: logBase });
11225
+ if (who && who.found && !who.revoked) {
11226
+ process.stdout.write(` ${green("Identity:")} anchored as ${bold(who.name || who.slug || "enrolled org")} ${dim("(key pinned in the ScopeBlind directory" + (who.enrolled_at ? ", enrolled " + who.enrolled_at.slice(0, 10) : "") + ")")}
11227
+ `);
11228
+ } else if (who && who.found && who.revoked) {
11229
+ process.stdout.write(` ${red("Identity: this key is REVOKED in the ScopeBlind directory.")}
11230
+ `);
11231
+ } else {
11232
+ process.stdout.write(` ${dim("Identity: anonymous (key not enrolled). To anchor as a named org a counterparty can pin, see")} ${bold("scopeblind.com/enroll")}
11009
11233
  `);
11234
+ }
11010
11235
  } else {
11011
11236
  process.stdout.write(` ${yellow("Anchor skipped")} ${dim("(" + (res.error || "unavailable") + "). The claim above is complete and verifiable offline without it.")}
11012
11237
  `);
@@ -11016,11 +11241,137 @@ ${bold("\u{1F6E1}\uFE0F Signed claim")}
11016
11241
  `);
11017
11242
  process.exit(0);
11018
11243
  }
11244
+ async function handleAnchorRecord(argv) {
11245
+ const { readFileSync: readFileSync11, existsSync: existsSync10, appendFileSync: appendFileSync3 } = await import("fs");
11246
+ const { join: join9 } = await import("path");
11247
+ const { anchorRecordCheckpoint: anchorRecordCheckpoint2, buildRecordCheckpoint: buildRecordCheckpoint2, lookupPinnedIdentity: lookupPinnedIdentity2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
11248
+ let dir = process.cwd();
11249
+ const di = argv.indexOf("--dir");
11250
+ if (di !== -1 && argv[di + 1]) dir = argv[di + 1];
11251
+ const li = argv.indexOf("--log");
11252
+ const logBase = li !== -1 && argv[li + 1] ? argv[li + 1] : void 0;
11253
+ const keyPath = join9(dir, "keys", "gateway.json");
11254
+ if (!existsSync10(keyPath)) {
11255
+ process.stderr.write(`
11256
+ ${bold("protect-mcp anchor-record")}
11257
+
11258
+ No signing key at ${keyPath}. A checkpoint must be signed. Run ${bold("npx protect-mcp init")} first.
11259
+
11260
+ `);
11261
+ process.exit(1);
11262
+ return;
11263
+ }
11264
+ let key;
11265
+ try {
11266
+ key = JSON.parse(readFileSync11(keyPath, "utf-8"));
11267
+ } catch {
11268
+ process.stderr.write(`
11269
+ protect-mcp anchor-record: ${keyPath} is not valid JSON.
11270
+
11271
+ `);
11272
+ process.exit(1);
11273
+ return;
11274
+ }
11275
+ if (!key.privateKey || !key.publicKey) {
11276
+ process.stderr.write(`
11277
+ protect-mcp anchor-record: ${keyPath} is missing privateKey/publicKey.
11278
+
11279
+ `);
11280
+ process.exit(1);
11281
+ return;
11282
+ }
11283
+ const recPath = join9(dir, ".protect-mcp-receipts.jsonl");
11284
+ if (!existsSync10(recPath)) {
11285
+ process.stderr.write(`
11286
+ ${bold("protect-mcp anchor-record")}
11287
+
11288
+ No signed receipts in ${dir}. Run the gate with signing on, then try again.
11289
+
11290
+ `);
11291
+ process.exit(0);
11292
+ return;
11293
+ }
11294
+ const receipts = readFileSync11(recPath, "utf-8").split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((l) => {
11295
+ try {
11296
+ return JSON.parse(l);
11297
+ } catch {
11298
+ return null;
11299
+ }
11300
+ }).filter((x) => x !== null);
11301
+ if (!receipts.length) {
11302
+ process.stderr.write(`
11303
+ protect-mcp anchor-record: no readable receipts in ${recPath}.
11304
+
11305
+ `);
11306
+ process.exit(0);
11307
+ return;
11308
+ }
11309
+ const claimKey = { privateKey: key.privateKey, publicKey: key.publicKey, kid: key.kid || "gateway", issuer: "protect-mcp" };
11310
+ const historyPath = join9(dir, ".protect-mcp-anchors.jsonl");
11311
+ const preview = buildRecordCheckpoint2(receipts, claimKey, "preview");
11312
+ if (!argv.includes("--force") && existsSync10(historyPath)) {
11313
+ const lines = readFileSync11(historyPath, "utf-8").split(/\r?\n/).filter(Boolean);
11314
+ const last = lines.length ? (() => {
11315
+ try {
11316
+ return JSON.parse(lines[lines.length - 1]);
11317
+ } catch {
11318
+ return null;
11319
+ }
11320
+ })() : null;
11321
+ if (last && last.record_root === preview.record_root && last.total === preview.total) {
11322
+ process.stdout.write(`
11323
+ ${bold("\u{1F6E1}\uFE0F Record checkpoint")}
11324
+ `);
11325
+ process.stdout.write(` Unchanged since entry ${bold("#" + last.seq)} ${dim("(" + last.total + " receipts, anchored " + (last.anchored_at || "") + ")")}. Nothing new to anchor.
11326
+ `);
11327
+ process.stdout.write(` ${dim("Use --force to re-anchor anyway.")}
11328
+
11329
+ `);
11330
+ process.exit(0);
11331
+ return;
11332
+ }
11333
+ }
11334
+ const res = await anchorRecordCheckpoint2(receipts, claimKey, { log: logBase, issuedAt: (/* @__PURE__ */ new Date()).toISOString() });
11335
+ process.stdout.write(`
11336
+ ${bold("\u{1F6E1}\uFE0F Record checkpoint")}
11337
+ `);
11338
+ process.stdout.write(` ${res.total} receipts ${dim("\xB7")} root ${dim(res.record_root.slice(0, 16) + "\u2026")} ${dim("(" + res.checkpoint.from.slice(0, 10) + " \u2192 " + res.checkpoint.to.slice(0, 10) + ")")}
11339
+ `);
11340
+ if (!res.ok) {
11341
+ process.stdout.write(` ${yellow("Anchor failed")} ${dim("(" + (res.error || "unavailable") + "). Nothing was recorded; try again.")}
11342
+
11343
+ `);
11344
+ process.exit(1);
11345
+ return;
11346
+ }
11347
+ appendFileSync3(historyPath, JSON.stringify({ schema: res.checkpoint.schema, seq: res.seq, anchored_at: res.anchored_at, total: res.total, record_root: res.record_root, entry_url: res.entry_url, digest: res.checkpoint.digest }) + "\n");
11348
+ process.stdout.write(` ${green("Anchored")} as log entry ${bold("#" + res.seq)} ${dim(res.entry_url || "")}
11349
+ `);
11350
+ process.stdout.write(` ${dim("Only the root, count, and time range were sent. History: " + historyPath)}
11351
+ `);
11352
+ const who = await lookupPinnedIdentity2(claimKey.publicKey, { log: logBase });
11353
+ if (who && who.found && !who.revoked) {
11354
+ process.stdout.write(` ${green("Identity:")} anchored as ${bold(who.name || who.slug || "enrolled org")} ${dim("(key pinned in the ScopeBlind directory)")}
11355
+ `);
11356
+ } else if (who && who.found && who.revoked) {
11357
+ process.stdout.write(` ${red("Identity: this key is REVOKED in the ScopeBlind directory.")}
11358
+ `);
11359
+ } else {
11360
+ process.stdout.write(` ${dim("Identity: anonymous (key not enrolled). Named identity: scopeblind.com/enroll")}
11361
+ `);
11362
+ }
11363
+ process.stdout.write(` ${dim("A claim whose commitment matches this root is provably over the complete record as of")}
11364
+ `);
11365
+ process.stdout.write(` ${dim("this checkpoint. Run this on a heartbeat (e.g. cron) to keep the anchored history growing.")}
11366
+
11367
+ `);
11368
+ process.exit(0);
11369
+ }
11019
11370
  async function handleVerifyClaim(argv) {
11020
- const { readFileSync: readFileSync11, existsSync: existsSync9 } = await import("fs");
11371
+ const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
11021
11372
  const { verifyClaim: verifyClaim2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
11022
11373
  const file = argv.find((a) => !a.startsWith("--"));
11023
- if (!file || !existsSync9(file)) {
11374
+ if (!file || !existsSync10(file)) {
11024
11375
  process.stderr.write(`
11025
11376
  ${bold("protect-mcp verify-claim")} <claim.json> [--key <public-hex>]
11026
11377
 
@@ -11069,7 +11420,7 @@ ${bold("protect-mcp verify-claim")}
11069
11420
  const sidecarPath = ai !== -1 && argv[ai + 1] ? argv[ai + 1] : file.replace(/\.json$/, "") + ".anchor.json";
11070
11421
  const requireAnchor = argv.includes("--check-anchor");
11071
11422
  let anchorOk = true;
11072
- if (existsSync9(sidecarPath)) {
11423
+ if (existsSync10(sidecarPath)) {
11073
11424
  const { checkClaimAnchor: checkClaimAnchor2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
11074
11425
  let sidecar = null;
11075
11426
  try {
@@ -11102,6 +11453,21 @@ ${bold("protect-mcp verify-claim")}
11102
11453
  process.stdout.write(` ${yellow("~")} log not checked ${dim(argv.includes("--offline") ? "(--offline)" : "(unreachable; local binding checks stand)")}
11103
11454
  `);
11104
11455
  }
11456
+ if (!argv.includes("--offline") && sidecar.envelope) {
11457
+ const { lookupPinnedIdentity: lookupPinnedIdentity2 } = await Promise.resolve().then(() => (init_claim(), claim_exports));
11458
+ const who = await lookupPinnedIdentity2(sidecar.envelope.verification_key, {});
11459
+ if (who && who.found && !who.revoked) {
11460
+ process.stdout.write(` ${green("\u2713")} issuer key pinned to ${bold(who.name || who.slug || "an enrolled org")} ${dim("(ScopeBlind key directory)")}
11461
+ `);
11462
+ } else if (who && who.found && who.revoked) {
11463
+ anchorOk = false;
11464
+ process.stdout.write(` ${red("\u2717")} issuer key is REVOKED in the ScopeBlind key directory
11465
+ `);
11466
+ } else if (who && !who.found) {
11467
+ process.stdout.write(` ${dim("issuer key not enrolled (anonymous issuer); named identities pin via scopeblind.com/enroll")}
11468
+ `);
11469
+ }
11470
+ }
11105
11471
  }
11106
11472
  } else if (requireAnchor) {
11107
11473
  anchorOk = false;
@@ -11125,24 +11491,24 @@ ${bold("protect-mcp verify-claim")}
11125
11491
  process.exit(finalValid ? 0 : 1);
11126
11492
  }
11127
11493
  async function handleBundle(argv) {
11128
- const { readFileSync: readFileSync11, writeFileSync: writeFileSync4, existsSync: existsSync9 } = await import("fs");
11129
- const { join: join8 } = await import("path");
11494
+ const { readFileSync: readFileSync11, writeFileSync: writeFileSync5, existsSync: existsSync10 } = await import("fs");
11495
+ const { join: join9 } = await import("path");
11130
11496
  const { createAuditBundle: createAuditBundle2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
11131
11497
  let dir = process.cwd();
11132
11498
  const dirIdx = argv.indexOf("--dir");
11133
11499
  if (dirIdx !== -1 && argv[dirIdx + 1]) dir = argv[dirIdx + 1];
11134
11500
  const outputIdx = argv.indexOf("--output");
11135
- const outputPath = outputIdx !== -1 && argv[outputIdx + 1] ? argv[outputIdx + 1] : join8(dir, "audit-bundle.json");
11136
- const receiptsPath = join8(dir, ".protect-mcp-receipts.jsonl");
11137
- const keyPath = join8(dir, "keys", "gateway.json");
11138
- if (!existsSync9(receiptsPath)) {
11501
+ const outputPath = outputIdx !== -1 && argv[outputIdx + 1] ? argv[outputIdx + 1] : join9(dir, "audit-bundle.json");
11502
+ const receiptsPath = join9(dir, ".protect-mcp-receipts.jsonl");
11503
+ const keyPath = join9(dir, "keys", "gateway.json");
11504
+ if (!existsSync10(receiptsPath)) {
11139
11505
  process.stderr.write(`${bold("protect-mcp bundle")}
11140
11506
 
11141
11507
  No signed receipt file found. Run protect-mcp with signing enabled first.
11142
11508
  `);
11143
11509
  process.exit(0);
11144
11510
  }
11145
- if (!existsSync9(keyPath)) {
11511
+ if (!existsSync10(keyPath)) {
11146
11512
  process.stderr.write(`${bold("protect-mcp bundle")}
11147
11513
 
11148
11514
  No key file found at ${keyPath}
@@ -11163,7 +11529,7 @@ No key file found at ${keyPath}
11163
11529
  use: "sig"
11164
11530
  }]
11165
11531
  });
11166
- writeFileSync4(outputPath, JSON.stringify(bundle, null, 2) + "\n");
11532
+ writeFileSync5(outputPath, JSON.stringify(bundle, null, 2) + "\n");
11167
11533
  process.stdout.write(`
11168
11534
  ${bold("protect-mcp bundle")}
11169
11535
 
@@ -11179,8 +11545,8 @@ ${bold("protect-mcp bundle")}
11179
11545
  `);
11180
11546
  }
11181
11547
  async function createSandbox() {
11182
- const { mkdirSync: mkdirSync3, writeFileSync: writeFileSync4, existsSync: existsSync9, readFileSync: readFileSync11 } = await import("fs");
11183
- const { join: join8 } = await import("path");
11548
+ const { mkdirSync: mkdirSync4, writeFileSync: writeFileSync5, existsSync: existsSync10, readFileSync: readFileSync11 } = await import("fs");
11549
+ const { join: join9 } = await import("path");
11184
11550
  const { homedir } = await import("os");
11185
11551
  let response;
11186
11552
  try {
@@ -11210,19 +11576,19 @@ async function createSandbox() {
11210
11576
  return null;
11211
11577
  }
11212
11578
  const dashboardUrl = `https://scopeblind.com/t/${data.slug}`;
11213
- const configDir = join8(homedir(), ".protect-mcp");
11214
- if (!existsSync9(configDir)) {
11215
- mkdirSync3(configDir, { recursive: true });
11579
+ const configDir = join9(homedir(), ".protect-mcp");
11580
+ if (!existsSync10(configDir)) {
11581
+ mkdirSync4(configDir, { recursive: true });
11216
11582
  }
11217
- const configPath = join8(configDir, "config.json");
11583
+ const configPath = join9(configDir, "config.json");
11218
11584
  let existing = {};
11219
- if (existsSync9(configPath)) {
11585
+ if (existsSync10(configPath)) {
11220
11586
  try {
11221
11587
  existing = JSON.parse(readFileSync11(configPath, "utf-8"));
11222
11588
  } catch {
11223
11589
  }
11224
11590
  }
11225
- writeFileSync4(configPath, JSON.stringify({
11591
+ writeFileSync5(configPath, JSON.stringify({
11226
11592
  ...existing,
11227
11593
  sandbox_slug: data.slug,
11228
11594
  dashboard_url: dashboardUrl
@@ -11255,10 +11621,10 @@ ${"\u2500".repeat(50)}
11255
11621
  }
11256
11622
  async function handleQuickstart(argv) {
11257
11623
  const connectFlag = argv.includes("--connect");
11258
- const { mkdtempSync, writeFileSync: writeFileSync4, existsSync: existsSync9, mkdirSync: mkdirSync3, readFileSync: readFileSync11 } = await import("fs");
11259
- const { join: join8 } = await import("path");
11624
+ const { mkdtempSync, writeFileSync: writeFileSync5, existsSync: existsSync10, mkdirSync: mkdirSync4, readFileSync: readFileSync11 } = await import("fs");
11625
+ const { join: join9 } = await import("path");
11260
11626
  const { tmpdir } = await import("os");
11261
- const dir = mkdtempSync(join8(tmpdir(), "protect-mcp-quickstart-"));
11627
+ const dir = mkdtempSync(join9(tmpdir(), "protect-mcp-quickstart-"));
11262
11628
  process.stdout.write(`
11263
11629
  ${bold("protect-mcp quickstart")}
11264
11630
  `);
@@ -11283,8 +11649,8 @@ ${bold("protect-mcp quickstart")}
11283
11649
  Working dir: ${dir}
11284
11650
 
11285
11651
  `);
11286
- const keysDir = join8(dir, "keys");
11287
- mkdirSync3(keysDir, { recursive: true });
11652
+ const keysDir = join9(dir, "keys");
11653
+ mkdirSync4(keysDir, { recursive: true });
11288
11654
  const { randomBytes: randomBytes4 } = await import("crypto");
11289
11655
  let keypair;
11290
11656
  try {
@@ -11304,13 +11670,13 @@ ${bold("protect-mcp quickstart")}
11304
11670
  kid: `quickstart-${Date.now()}`
11305
11671
  };
11306
11672
  }
11307
- writeFileSync4(join8(keysDir, "gateway.json"), JSON.stringify({
11673
+ writeFileSync5(join9(keysDir, "gateway.json"), JSON.stringify({
11308
11674
  privateKey: keypair.privateKey,
11309
11675
  publicKey: keypair.publicKey,
11310
11676
  kid: keypair.kid,
11311
11677
  generated_at: (/* @__PURE__ */ new Date()).toISOString()
11312
11678
  }, null, 2) + "\n");
11313
- const configPath = join8(dir, "protect-mcp.json");
11679
+ const configPath = join9(dir, "protect-mcp.json");
11314
11680
  const config = {
11315
11681
  tools: {
11316
11682
  "*": { rate_limit: "100/hour" },
@@ -11318,12 +11684,12 @@ ${bold("protect-mcp quickstart")}
11318
11684
  },
11319
11685
  default_tier: "unknown",
11320
11686
  signing: {
11321
- key_path: join8(keysDir, "gateway.json"),
11687
+ key_path: join9(keysDir, "gateway.json"),
11322
11688
  issuer: "protect-mcp-quickstart",
11323
11689
  enabled: true
11324
11690
  }
11325
11691
  };
11326
- writeFileSync4(configPath, JSON.stringify(config, null, 2) + "\n");
11692
+ writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
11327
11693
  process.stdout.write(` \u2713 Keypair generated (kid: ${keypair.kid})
11328
11694
  `);
11329
11695
  process.stdout.write(` \u2713 Policy created (shadow mode, all tools logged)
@@ -11338,7 +11704,7 @@ ${bold("protect-mcp quickstart")}
11338
11704
  const dashboardUrl = await createSandbox();
11339
11705
  if (dashboardUrl) {
11340
11706
  const updatedConfig = { ...config, dashboard_url: dashboardUrl };
11341
- writeFileSync4(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
11707
+ writeFileSync5(configPath, JSON.stringify(updatedConfig, null, 2) + "\n");
11342
11708
  process.stdout.write(green(` \u2713 Dashboard created: ${dashboardUrl}
11343
11709
  `));
11344
11710
  process.stdout.write(` Receipts will be uploaded automatically.
@@ -11374,7 +11740,7 @@ ${bold("protect-mcp quickstart")}
11374
11740
  }
11375
11741
  async function handleRegistry(argv) {
11376
11742
  const subcommand = argv[0] || "status";
11377
- const dir = (0, import_node_path8.resolve)(flagValue(argv, "--dir") || process.cwd());
11743
+ const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || process.cwd());
11378
11744
  const orgName = flagValue(argv, "--org") || process.env.SCOPEBLIND_ORG;
11379
11745
  const orgId = flagValue(argv, "--org-id") || process.env.SCOPEBLIND_ORG_ID;
11380
11746
  const billingAccountId = flagValue(argv, "--billing-account") || process.env.SCOPEBLIND_BILLING_ACCOUNT;
@@ -11456,14 +11822,14 @@ ${bold("protect-mcp registry anchor")}
11456
11822
  return;
11457
11823
  }
11458
11824
  if (subcommand === "status") {
11459
- const registryPath = (0, import_node_path8.join)(dir, registryMod.REGISTRY_FILE);
11460
- const identityPath = (0, import_node_path8.join)(dir, registryMod.ORG_IDENTITY_FILE);
11825
+ const registryPath = (0, import_node_path9.join)(dir, registryMod.REGISTRY_FILE);
11826
+ const identityPath = (0, import_node_path9.join)(dir, registryMod.ORG_IDENTITY_FILE);
11461
11827
  process.stdout.write(`
11462
11828
  ${bold("protect-mcp registry status")}
11463
11829
 
11464
11830
  `);
11465
- if ((0, import_node_fs12.existsSync)(identityPath)) {
11466
- const identity = JSON.parse((0, import_node_fs12.readFileSync)(identityPath, "utf-8"));
11831
+ if ((0, import_node_fs13.existsSync)(identityPath)) {
11832
+ const identity = JSON.parse((0, import_node_fs13.readFileSync)(identityPath, "utf-8"));
11467
11833
  process.stdout.write(` Org: ${identity.org_name || "unknown"}
11468
11834
  `);
11469
11835
  process.stdout.write(` Org ID: ${identity.org_id || "unknown"}
@@ -11474,8 +11840,8 @@ ${bold("protect-mcp registry status")}
11474
11840
  process.stdout.write(` Org identity: ${yellow("missing")} (${identityPath})
11475
11841
  `);
11476
11842
  }
11477
- if ((0, import_node_fs12.existsSync)(registryPath)) {
11478
- const registry = JSON.parse((0, import_node_fs12.readFileSync)(registryPath, "utf-8"));
11843
+ if ((0, import_node_fs13.existsSync)(registryPath)) {
11844
+ const registry = JSON.parse((0, import_node_fs13.readFileSync)(registryPath, "utf-8"));
11479
11845
  const hosted = Array.isArray(registry.anchors) && registry.anchors.some((a) => a.timestamp_source === "scopeblind-hosted");
11480
11846
  process.stdout.write(` Registry: ${registryPath}
11481
11847
  `);
@@ -11485,7 +11851,7 @@ ${bold("protect-mcp registry status")}
11485
11851
  `);
11486
11852
  process.stdout.write(` Boundary: ${hosted ? green("hosted digest anchor") : yellow("local preview only")}
11487
11853
  `);
11488
- process.stdout.write(` Verifier page: ${(0, import_node_path8.join)(dir, registryMod.VERIFIER_PAGE_FILE)}
11854
+ process.stdout.write(` Verifier page: ${(0, import_node_path9.join)(dir, registryMod.VERIFIER_PAGE_FILE)}
11489
11855
  `);
11490
11856
  } else {
11491
11857
  process.stdout.write(` Registry: ${yellow("missing")} (${registryPath})
@@ -11513,10 +11879,10 @@ async function handleKillerDemo(argv) {
11513
11879
  verifySelectiveDisclosurePackage: verifySelectiveDisclosurePackage2
11514
11880
  } = await Promise.resolve().then(() => (init_signing_committed(), signing_committed_exports));
11515
11881
  const registryMod = await Promise.resolve().then(() => (init_receipt_registry(), receipt_registry_exports));
11516
- const dir = (0, import_node_path8.resolve)(flagValue(argv, "--dir") || mkdtempSync((0, import_node_path8.join)(tmpdir(), "scopeblind-killer-demo-")));
11517
- (0, import_node_fs12.mkdirSync)(dir, { recursive: true });
11518
- (0, import_node_fs12.mkdirSync)((0, import_node_path8.join)(dir, "keys"), { recursive: true });
11519
- (0, import_node_fs12.mkdirSync)((0, import_node_path8.join)(dir, "receipts"), { recursive: true });
11882
+ const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || mkdtempSync((0, import_node_path9.join)(tmpdir(), "scopeblind-killer-demo-")));
11883
+ (0, import_node_fs13.mkdirSync)(dir, { recursive: true });
11884
+ (0, import_node_fs13.mkdirSync)((0, import_node_path9.join)(dir, "keys"), { recursive: true });
11885
+ (0, import_node_fs13.mkdirSync)((0, import_node_path9.join)(dir, "receipts"), { recursive: true });
11520
11886
  const privateKeyBytes = randomBytes4(32);
11521
11887
  const publicKeyBytes = ed255192.getPublicKey(privateKeyBytes);
11522
11888
  const keypair = {
@@ -11525,14 +11891,14 @@ async function handleKillerDemo(argv) {
11525
11891
  kid: `killer-demo-${Date.now()}`,
11526
11892
  issuer: "scopeblind-killer-demo"
11527
11893
  };
11528
- const keyPath = (0, import_node_path8.join)(dir, "keys", "gateway.json");
11529
- (0, import_node_fs12.writeFileSync)(keyPath, JSON.stringify({
11894
+ const keyPath = (0, import_node_path9.join)(dir, "keys", "gateway.json");
11895
+ (0, import_node_fs13.writeFileSync)(keyPath, JSON.stringify({
11530
11896
  ...keypair,
11531
11897
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
11532
11898
  warning: "Demo key only. Do not use for production."
11533
11899
  }, null, 2) + "\n");
11534
- const shadowConfigPath = (0, import_node_path8.join)(dir, "protect-mcp.shadow.json");
11535
- const policyPackPath = (0, import_node_path8.join)(dir, "protect-mcp.policy-pack.json");
11900
+ const shadowConfigPath = (0, import_node_path9.join)(dir, "protect-mcp.shadow.json");
11901
+ const policyPackPath = (0, import_node_path9.join)(dir, "protect-mcp.policy-pack.json");
11536
11902
  const config = {
11537
11903
  tools: { "*": { rate_limit: "100/hour" } },
11538
11904
  default_tier: "signed-known",
@@ -11551,11 +11917,11 @@ async function handleKillerDemo(argv) {
11551
11917
  signing: { key_path: keyPath, issuer: keypair.issuer, enabled: true },
11552
11918
  notes: ["Demo policy pack: approvals for GitHub, email, and PMS booking; destructive tools blocked."]
11553
11919
  };
11554
- (0, import_node_fs12.writeFileSync)(shadowConfigPath, JSON.stringify(config, null, 2) + "\n");
11555
- (0, import_node_fs12.writeFileSync)(policyPackPath, JSON.stringify(policyPack, null, 2) + "\n");
11920
+ (0, import_node_fs13.writeFileSync)(shadowConfigPath, JSON.stringify(config, null, 2) + "\n");
11921
+ (0, import_node_fs13.writeFileSync)(policyPackPath, JSON.stringify(policyPack, null, 2) + "\n");
11556
11922
  await initSigning({ enabled: true, key_path: keyPath, issuer: keypair.issuer });
11557
- const logPath = (0, import_node_path8.join)(dir, ".protect-mcp-log.jsonl");
11558
- const receiptPath = (0, import_node_path8.join)(dir, ".protect-mcp-receipts.jsonl");
11923
+ const logPath = (0, import_node_path9.join)(dir, ".protect-mcp-log.jsonl");
11924
+ const receiptPath = (0, import_node_path9.join)(dir, ".protect-mcp-receipts.jsonl");
11559
11925
  const shadowCalls = [
11560
11926
  { tool: "read_file", input: { path: "/research/macro-notes.md" }, reason: "observe_mode" },
11561
11927
  { tool: "github_create_pr", input: { repo: "scopeblind/legate", branch: "agent/pms-adapter", title: "Wire mock PMS adapter" }, reason: "observe_mode" },
@@ -11564,7 +11930,7 @@ async function handleKillerDemo(argv) {
11564
11930
  ];
11565
11931
  for (const [idx, call] of shadowCalls.entries()) {
11566
11932
  const requestId2 = `demo-shadow-${idx + 1}`;
11567
- (0, import_node_fs12.appendFileSync)(logPath, JSON.stringify({
11933
+ (0, import_node_fs13.appendFileSync)(logPath, JSON.stringify({
11568
11934
  v: 2,
11569
11935
  tool: call.tool,
11570
11936
  decision: "allow",
@@ -11599,8 +11965,8 @@ async function handleKillerDemo(argv) {
11599
11965
  policy_digest: (0, import_node_crypto7.createHash)("sha256").update(JSON.stringify(policyPack)).digest("hex").slice(0, 16),
11600
11966
  action_readback: readback
11601
11967
  };
11602
- (0, import_node_fs12.appendFileSync)(logPath, JSON.stringify(requireApprovalEntry) + "\n");
11603
- (0, import_node_fs12.appendFileSync)((0, import_node_path8.join)(dir, ".protect-mcp-approval-resolutions.jsonl"), JSON.stringify({
11968
+ (0, import_node_fs13.appendFileSync)(logPath, JSON.stringify(requireApprovalEntry) + "\n");
11969
+ (0, import_node_fs13.appendFileSync)((0, import_node_path9.join)(dir, ".protect-mcp-approval-resolutions.jsonl"), JSON.stringify({
11604
11970
  type: "scopeblind.approval_resolution.v1",
11605
11971
  at: (/* @__PURE__ */ new Date()).toISOString(),
11606
11972
  request_id: requestId,
@@ -11620,11 +11986,11 @@ async function handleKillerDemo(argv) {
11620
11986
  truncated: false
11621
11987
  }
11622
11988
  };
11623
- (0, import_node_fs12.appendFileSync)(logPath, JSON.stringify(executedEntry) + "\n");
11989
+ (0, import_node_fs13.appendFileSync)(logPath, JSON.stringify(executedEntry) + "\n");
11624
11990
  const signed = signDecision(executedEntry);
11625
11991
  if (!signed.signed) throw new Error(`demo signing failed: ${signed.warning || signed.error || "unknown"}`);
11626
- (0, import_node_fs12.appendFileSync)(receiptPath, signed.signed + "\n");
11627
- (0, import_node_fs12.writeFileSync)((0, import_node_path8.join)(dir, "receipts", "approved-pms-booking.receipt.json"), JSON.stringify(JSON.parse(signed.signed), null, 2) + "\n");
11992
+ (0, import_node_fs13.appendFileSync)(receiptPath, signed.signed + "\n");
11993
+ (0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "approved-pms-booking.receipt.json"), JSON.stringify(JSON.parse(signed.signed), null, 2) + "\n");
11628
11994
  const receiptArtifact = JSON.parse(signed.signed);
11629
11995
  const tamperedArtifact = JSON.parse(signed.signed);
11630
11996
  if (tamperedArtifact.payload && typeof tamperedArtifact.payload === "object") {
@@ -11635,7 +12001,7 @@ async function handleKillerDemo(argv) {
11635
12001
  }
11636
12002
  const validOriginal = artifacts.verifyArtifact(receiptArtifact, keypair.publicKey);
11637
12003
  const validTampered = artifacts.verifyArtifact(tamperedArtifact, keypair.publicKey);
11638
- (0, import_node_fs12.writeFileSync)((0, import_node_path8.join)(dir, "receipts", "tampered.receipt.json"), JSON.stringify(tamperedArtifact, null, 2) + "\n");
12004
+ (0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "tampered.receipt.json"), JSON.stringify(tamperedArtifact, null, 2) + "\n");
11639
12005
  const committed = signCommittedDecision2(
11640
12006
  executedEntry,
11641
12007
  ["tool", "payload_digest", "swarm"],
@@ -11647,11 +12013,11 @@ async function handleKillerDemo(argv) {
11647
12013
  const committedReceipt = JSON.parse(committed.signed);
11648
12014
  const disclosurePackage = createSelectiveDisclosurePackage2(committedReceipt, ["tool"], committed.openings);
11649
12015
  const disclosureVerification = verifySelectiveDisclosurePackage2(committedReceipt, disclosurePackage);
11650
- (0, import_node_fs12.appendFileSync)(receiptPath, committed.signed + "\n");
11651
- (0, import_node_fs12.writeFileSync)((0, import_node_path8.join)(dir, "receipts", "selective-disclosure.receipt.json"), JSON.stringify(committedReceipt, null, 2) + "\n");
11652
- (0, import_node_fs12.writeFileSync)((0, import_node_path8.join)(dir, "receipts", "selective-disclosure.package.json"), JSON.stringify(disclosurePackage, null, 2) + "\n");
11653
- (0, import_node_fs12.writeFileSync)((0, import_node_path8.join)(dir, "receipts", "selective-disclosure.tool-only.json"), JSON.stringify(disclosurePackage, null, 2) + "\n");
11654
- (0, import_node_fs12.writeFileSync)((0, import_node_path8.join)(dir, "verification-results.json"), JSON.stringify({
12016
+ (0, import_node_fs13.appendFileSync)(receiptPath, committed.signed + "\n");
12017
+ (0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "selective-disclosure.receipt.json"), JSON.stringify(committedReceipt, null, 2) + "\n");
12018
+ (0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "selective-disclosure.package.json"), JSON.stringify(disclosurePackage, null, 2) + "\n");
12019
+ (0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "receipts", "selective-disclosure.tool-only.json"), JSON.stringify(disclosurePackage, null, 2) + "\n");
12020
+ (0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "verification-results.json"), JSON.stringify({
11655
12021
  original_receipt_valid: validOriginal,
11656
12022
  tampered_receipt_valid: validTampered,
11657
12023
  selective_disclosure_valid: disclosureVerification.valid,
@@ -11734,19 +12100,19 @@ async function handleKillerDemo(argv) {
11734
12100
  "No raw prompt, payload, output, private key, or raw receipt is uploaded by the registry flow. Hosted mode submits receipt digests, request ids, org public keys, and billing account metadata only.",
11735
12101
  ""
11736
12102
  ].join("\n");
11737
- (0, import_node_fs12.writeFileSync)((0, import_node_path8.join)(dir, "DEMO-RUNBOOK.md"), runbook);
11738
- (0, import_node_fs12.writeFileSync)((0, import_node_path8.join)(dir, "demo-summary.json"), JSON.stringify({
12103
+ (0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "DEMO-RUNBOOK.md"), runbook);
12104
+ (0, import_node_fs13.writeFileSync)((0, import_node_path9.join)(dir, "demo-summary.json"), JSON.stringify({
11739
12105
  dir,
11740
12106
  dashboard_command: `npx protect-mcp dashboard --dir ${dir} --policy ${policyPackPath} --open`,
11741
12107
  policy_pack: policyPackPath,
11742
- receipt: (0, import_node_path8.join)(dir, "receipts", "approved-pms-booking.receipt.json"),
11743
- tampered_receipt: (0, import_node_path8.join)(dir, "receipts", "tampered.receipt.json"),
11744
- selective_disclosure_receipt: (0, import_node_path8.join)(dir, "receipts", "selective-disclosure.receipt.json"),
11745
- selective_disclosure_package: (0, import_node_path8.join)(dir, "receipts", "selective-disclosure.tool-only.json"),
11746
- verification_results: (0, import_node_path8.join)(dir, "verification-results.json"),
12108
+ receipt: (0, import_node_path9.join)(dir, "receipts", "approved-pms-booking.receipt.json"),
12109
+ tampered_receipt: (0, import_node_path9.join)(dir, "receipts", "tampered.receipt.json"),
12110
+ selective_disclosure_receipt: (0, import_node_path9.join)(dir, "receipts", "selective-disclosure.receipt.json"),
12111
+ selective_disclosure_package: (0, import_node_path9.join)(dir, "receipts", "selective-disclosure.tool-only.json"),
12112
+ verification_results: (0, import_node_path9.join)(dir, "verification-results.json"),
11747
12113
  registry: registry.registryPath,
11748
12114
  verifier_page: registry.verifierPath,
11749
- runbook: (0, import_node_path8.join)(dir, "DEMO-RUNBOOK.md"),
12115
+ runbook: (0, import_node_path9.join)(dir, "DEMO-RUNBOOK.md"),
11750
12116
  original_valid: validOriginal.valid,
11751
12117
  tampered_valid: validTampered.valid,
11752
12118
  selective_disclosure_valid: disclosureVerification.valid
@@ -11759,9 +12125,9 @@ ${bold("protect-mcp killer-demo")}
11759
12125
  `);
11760
12126
  process.stdout.write(` Dashboard: ${dim(`npx protect-mcp dashboard --dir ${dir} --policy ${policyPackPath} --open`)}
11761
12127
  `);
11762
- process.stdout.write(` Runbook: ${(0, import_node_path8.join)(dir, "DEMO-RUNBOOK.md")}
12128
+ process.stdout.write(` Runbook: ${(0, import_node_path9.join)(dir, "DEMO-RUNBOOK.md")}
11763
12129
  `);
11764
- process.stdout.write(` Signed receipt: ${(0, import_node_path8.join)(dir, "receipts", "approved-pms-booking.receipt.json")}
12130
+ process.stdout.write(` Signed receipt: ${(0, import_node_path9.join)(dir, "receipts", "approved-pms-booking.receipt.json")}
11765
12131
  `);
11766
12132
  process.stdout.write(` Tamper check: original=${validOriginal.valid ? green("valid") : red("invalid")} tampered=${validTampered.valid ? red("valid") : green("invalid")}
11767
12133
  `);
@@ -11781,8 +12147,8 @@ async function handleVerifyDisclosure(argv) {
11781
12147
  process.exit(1);
11782
12148
  }
11783
12149
  const { verifySelectiveDisclosurePackage: verifySelectiveDisclosurePackage2 } = await Promise.resolve().then(() => (init_signing_committed(), signing_committed_exports));
11784
- const receipt = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path8.resolve)(receiptPath), "utf-8"));
11785
- const disclosure = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path8.resolve)(disclosurePath), "utf-8"));
12150
+ const receipt = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path9.resolve)(receiptPath), "utf-8"));
12151
+ const disclosure = JSON.parse((0, import_node_fs13.readFileSync)((0, import_node_path9.resolve)(disclosurePath), "utf-8"));
11786
12152
  const result = verifySelectiveDisclosurePackage2(receipt, disclosure);
11787
12153
  process.stdout.write(`
11788
12154
  ${bold("protect-mcp verify-disclosure")}
@@ -11818,7 +12184,7 @@ ${red("Errors:")}
11818
12184
  async function handlePolicyPacks(argv) {
11819
12185
  const subcommand = argv[0] || "list";
11820
12186
  const packArg = argv[1];
11821
- const dir = (0, import_node_path8.resolve)(flagValue(argv, "--dir") || "./cedar");
12187
+ const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || "./cedar");
11822
12188
  const force = argv.includes("--force");
11823
12189
  if (subcommand === "list") {
11824
12190
  process.stdout.write(`
@@ -11873,17 +12239,17 @@ ${bold(pack.name)} (${pack.id})
11873
12239
  `);
11874
12240
  process.exit(1);
11875
12241
  }
11876
- (0, import_node_fs12.mkdirSync)(dir, { recursive: true });
12242
+ (0, import_node_fs13.mkdirSync)(dir, { recursive: true });
11877
12243
  const written = [];
11878
12244
  for (const pack of packs) {
11879
12245
  for (const file of pack.files) {
11880
- const outPath = (0, import_node_path8.join)(dir, file.path);
11881
- if ((0, import_node_fs12.existsSync)(outPath) && !force) {
12246
+ const outPath = (0, import_node_path9.join)(dir, file.path);
12247
+ if ((0, import_node_fs13.existsSync)(outPath) && !force) {
11882
12248
  process.stderr.write(`Refusing to overwrite ${outPath}. Re-run with --force if intentional.
11883
12249
  `);
11884
12250
  process.exit(1);
11885
12251
  }
11886
- (0, import_node_fs12.writeFileSync)(outPath, file.contents.endsWith("\n") ? file.contents : `${file.contents}
12252
+ (0, import_node_fs13.writeFileSync)(outPath, file.contents.endsWith("\n") ? file.contents : `${file.contents}
11887
12253
  `);
11888
12254
  written.push(outPath);
11889
12255
  }
@@ -11908,7 +12274,7 @@ Next: ${dim(`protect-mcp serve --cedar ${dir}`)} for shadow mode, then add ${dim
11908
12274
  async function handleConnectors(argv) {
11909
12275
  const subcommand = argv[0] || "list";
11910
12276
  const pilotArg = argv[1];
11911
- const dir = (0, import_node_path8.resolve)(flagValue(argv, "--dir") || process.cwd());
12277
+ const dir = (0, import_node_path9.resolve)(flagValue(argv, "--dir") || process.cwd());
11912
12278
  const force = argv.includes("--force");
11913
12279
  if (subcommand === "list") {
11914
12280
  process.stdout.write(`
@@ -12150,11 +12516,11 @@ ${"\u2500".repeat(60)}
12150
12516
  `);
12151
12517
  }
12152
12518
  async function traceLocal(receiptId) {
12153
- const { readFileSync: readFileSync11, existsSync: existsSync9 } = await import("fs");
12154
- const { join: join8 } = await import("path");
12519
+ const { readFileSync: readFileSync11, existsSync: existsSync10 } = await import("fs");
12520
+ const { join: join9 } = await import("path");
12155
12521
  const dir = process.cwd();
12156
- const receiptsDir = join8(dir, ".protect-mcp", "receipts");
12157
- if (!existsSync9(receiptsDir)) {
12522
+ const receiptsDir = join9(dir, ".protect-mcp", "receipts");
12523
+ if (!existsSync10(receiptsDir)) {
12158
12524
  process.stdout.write(` No local receipts found in ${receiptsDir}
12159
12525
 
12160
12526
  `);
@@ -12168,7 +12534,7 @@ async function traceLocal(receiptId) {
12168
12534
  const receipts = [];
12169
12535
  for (const file of files) {
12170
12536
  try {
12171
- const content = readFileSync11(join8(receiptsDir, file), "utf-8");
12537
+ const content = readFileSync11(join9(receiptsDir, file), "utf-8");
12172
12538
  const receipt = JSON.parse(content);
12173
12539
  receipts.push(receipt);
12174
12540
  } catch {
@@ -12225,8 +12591,8 @@ function getTypeEmoji(type) {
12225
12591
  }
12226
12592
  }
12227
12593
  async function handleInitHooks(argv) {
12228
- const { writeFileSync: writeFileSync4, existsSync: existsSync9, mkdirSync: mkdirSync3, readFileSync: readFileSync11 } = await import("fs");
12229
- const { join: join8 } = await import("path");
12594
+ const { writeFileSync: writeFileSync5, existsSync: existsSync10, mkdirSync: mkdirSync4, readFileSync: readFileSync11 } = await import("fs");
12595
+ const { join: join9 } = await import("path");
12230
12596
  const { generateHookSettings: generateHookSettings2, generateSampleCedarPolicy: generateSampleCedarPolicy2, generateVerifyReceiptSkill: generateVerifyReceiptSkill2 } = await Promise.resolve().then(() => (init_hook_patterns(), hook_patterns_exports));
12231
12597
  let dir = process.cwd();
12232
12598
  const dirIdx = argv.indexOf("--dir");
@@ -12240,13 +12606,13 @@ ${bold("protect-mcp init-hooks")}
12240
12606
  process.stdout.write(`${"\u2500".repeat(55)}
12241
12607
 
12242
12608
  `);
12243
- const claudeDir = join8(dir, ".claude");
12244
- const settingsPath = join8(claudeDir, "settings.json");
12609
+ const claudeDir = join9(dir, ".claude");
12610
+ const settingsPath = join9(claudeDir, "settings.json");
12245
12611
  let existingSettings = {};
12246
- if (!existsSync9(claudeDir)) {
12247
- mkdirSync3(claudeDir, { recursive: true });
12612
+ if (!existsSync10(claudeDir)) {
12613
+ mkdirSync4(claudeDir, { recursive: true });
12248
12614
  }
12249
- if (existsSync9(settingsPath)) {
12615
+ if (existsSync10(settingsPath)) {
12250
12616
  try {
12251
12617
  existingSettings = JSON.parse(readFileSync11(settingsPath, "utf-8"));
12252
12618
  } catch {
@@ -12262,7 +12628,7 @@ ${bold("protect-mcp init-hooks")}
12262
12628
  ...hookSettings.hooks
12263
12629
  }
12264
12630
  };
12265
- writeFileSync4(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n");
12631
+ writeFileSync5(settingsPath, JSON.stringify(mergedSettings, null, 2) + "\n");
12266
12632
  process.stdout.write(` ${green("\u2713")} ${settingsPath}
12267
12633
  `);
12268
12634
  process.stdout.write(` Hook URL: ${dim(hookUrl)}
@@ -12270,26 +12636,26 @@ ${bold("protect-mcp init-hooks")}
12270
12636
  process.stdout.write(` Events: PreToolUse, PostToolUse, SubagentStart/Stop, Task, Session, Config, Stop
12271
12637
 
12272
12638
  `);
12273
- const keysDir = join8(dir, "keys");
12274
- const keyPath = join8(keysDir, "gateway.json");
12275
- if (!existsSync9(keyPath)) {
12276
- if (!existsSync9(keysDir)) mkdirSync3(keysDir, { recursive: true });
12639
+ const keysDir = join9(dir, "keys");
12640
+ const keyPath = join9(keysDir, "gateway.json");
12641
+ if (!existsSync10(keyPath)) {
12642
+ if (!existsSync10(keysDir)) mkdirSync4(keysDir, { recursive: true });
12277
12643
  const { randomBytes: rb } = await import("crypto");
12278
12644
  try {
12279
12645
  const { ed25519: ed255192 } = await Promise.resolve().then(() => (init_ed25519(), ed25519_exports));
12280
12646
  const { bytesToHex: bytesToHex2 } = await Promise.resolve().then(() => (init_utils(), utils_exports));
12281
12647
  const privateKey = rb(32);
12282
12648
  const publicKey = ed255192.getPublicKey(privateKey);
12283
- writeFileSync4(keyPath, JSON.stringify({
12649
+ writeFileSync5(keyPath, JSON.stringify({
12284
12650
  privateKey: bytesToHex2(privateKey),
12285
12651
  publicKey: bytesToHex2(publicKey),
12286
12652
  kid: `hook-${Date.now()}`,
12287
12653
  generated_at: (/* @__PURE__ */ new Date()).toISOString(),
12288
12654
  warning: "KEEP THIS FILE SECRET. Never commit to version control."
12289
12655
  }, null, 2) + "\n");
12290
- const gitignorePath = join8(keysDir, ".gitignore");
12291
- if (!existsSync9(gitignorePath)) {
12292
- writeFileSync4(gitignorePath, "# Never commit signing keys\n*.json\n");
12656
+ const gitignorePath = join9(keysDir, ".gitignore");
12657
+ if (!existsSync10(gitignorePath)) {
12658
+ writeFileSync5(gitignorePath, "# Never commit signing keys\n*.json\n");
12293
12659
  }
12294
12660
  process.stdout.write(` ${green("\u2713")} ${keyPath} (Ed25519 keypair)
12295
12661
 
@@ -12304,11 +12670,11 @@ ${bold("protect-mcp init-hooks")}
12304
12670
 
12305
12671
  `);
12306
12672
  }
12307
- const policiesDir = join8(dir, "policies");
12308
- const cedarPath = join8(policiesDir, "agent.cedar");
12309
- if (!existsSync9(cedarPath)) {
12310
- if (!existsSync9(policiesDir)) mkdirSync3(policiesDir, { recursive: true });
12311
- writeFileSync4(cedarPath, generateSampleCedarPolicy2());
12673
+ const policiesDir = join9(dir, "policies");
12674
+ const cedarPath = join9(policiesDir, "agent.cedar");
12675
+ if (!existsSync10(cedarPath)) {
12676
+ if (!existsSync10(policiesDir)) mkdirSync4(policiesDir, { recursive: true });
12677
+ writeFileSync5(cedarPath, generateSampleCedarPolicy2());
12312
12678
  process.stdout.write(` ${green("\u2713")} ${cedarPath}
12313
12679
  `);
12314
12680
  process.stdout.write(` Edit to customize tool permissions. Cedar deny is AUTHORITATIVE.
@@ -12319,8 +12685,8 @@ ${bold("protect-mcp init-hooks")}
12319
12685
 
12320
12686
  `);
12321
12687
  }
12322
- const configPath = join8(dir, "protect-mcp.json");
12323
- if (!existsSync9(configPath)) {
12688
+ const configPath = join9(dir, "protect-mcp.json");
12689
+ if (!existsSync10(configPath)) {
12324
12690
  const config = {
12325
12691
  tools: { "*": { rate_limit: "100/hour" } },
12326
12692
  default_tier: "unknown",
@@ -12330,16 +12696,16 @@ ${bold("protect-mcp init-hooks")}
12330
12696
  enabled: true
12331
12697
  }
12332
12698
  };
12333
- writeFileSync4(configPath, JSON.stringify(config, null, 2) + "\n");
12699
+ writeFileSync5(configPath, JSON.stringify(config, null, 2) + "\n");
12334
12700
  process.stdout.write(` ${green("\u2713")} ${configPath}
12335
12701
 
12336
12702
  `);
12337
12703
  }
12338
- const skillsDir = join8(dir, ".claude", "skills", "verify-receipt");
12339
- const skillPath = join8(skillsDir, "SKILL.md");
12340
- if (!existsSync9(skillPath)) {
12341
- mkdirSync3(skillsDir, { recursive: true });
12342
- writeFileSync4(skillPath, generateVerifyReceiptSkill2());
12704
+ const skillsDir = join9(dir, ".claude", "skills", "verify-receipt");
12705
+ const skillPath = join9(skillsDir, "SKILL.md");
12706
+ if (!existsSync10(skillPath)) {
12707
+ mkdirSync4(skillsDir, { recursive: true });
12708
+ writeFileSync5(skillPath, generateVerifyReceiptSkill2());
12343
12709
  process.stdout.write(` ${green("\u2713")} ${skillPath}
12344
12710
  `);
12345
12711
  process.stdout.write(` Use ${dim("/verify-receipt")} in Claude Code to check audit trails.
@@ -12395,13 +12761,13 @@ ${bold("protect-mcp init-hooks")}
12395
12761
  }
12396
12762
  async function sendInstallTelemetry() {
12397
12763
  try {
12398
- const { existsSync: existsSync9, mkdirSync: mkdirSync3, writeFileSync: writeFileSync4, readFileSync: readFileSync11 } = await import("fs");
12399
- const { join: join8, dirname: dirname3 } = await import("path");
12764
+ const { existsSync: existsSync10, mkdirSync: mkdirSync4, writeFileSync: writeFileSync5, readFileSync: readFileSync11 } = await import("fs");
12765
+ const { join: join9, dirname: dirname3 } = await import("path");
12400
12766
  const { homedir } = await import("os");
12401
12767
  const { fileURLToPath } = await import("url");
12402
- const markerDir = join8(homedir(), ".protect-mcp");
12403
- const markerFile = join8(markerDir, ".telemetry-sent");
12404
- if (existsSync9(markerFile) || process.env.PROTECT_MCP_TELEMETRY === "off") {
12768
+ const markerDir = join9(homedir(), ".protect-mcp");
12769
+ const markerFile = join9(markerDir, ".telemetry-sent");
12770
+ if (existsSync10(markerFile) || process.env.PROTECT_MCP_TELEMETRY === "off") {
12405
12771
  return;
12406
12772
  }
12407
12773
  const version = await pkgVersion();
@@ -12421,10 +12787,10 @@ async function sendInstallTelemetry() {
12421
12787
  signal: controller.signal
12422
12788
  }).catch(() => {
12423
12789
  }).finally(() => clearTimeout(timeout));
12424
- if (!existsSync9(markerDir)) {
12425
- mkdirSync3(markerDir, { recursive: true });
12790
+ if (!existsSync10(markerDir)) {
12791
+ mkdirSync4(markerDir, { recursive: true });
12426
12792
  }
12427
- writeFileSync4(markerFile, String(Date.now()), "utf-8");
12793
+ writeFileSync5(markerFile, String(Date.now()), "utf-8");
12428
12794
  process.stderr.write(
12429
12795
  "[protect-mcp] Thanks for installing! Anonymous telemetry sent (disable: PROTECT_MCP_TELEMETRY=off)\n[protect-mcp] Free dashboard: npx protect-mcp connect | https://scopeblind.com\n"
12430
12796
  );
@@ -12440,8 +12806,8 @@ function loadPolicyArg(argv) {
12440
12806
  const policyFile = flagValue(argv, "--policy");
12441
12807
  try {
12442
12808
  if (cedarDir) return loadCedarPolicies(cedarDir);
12443
- if (policyFile && (0, import_node_fs12.existsSync)(policyFile)) {
12444
- return policySetFromSource((0, import_node_fs12.readFileSync)(policyFile, "utf-8"), (0, import_node_path8.basename)(policyFile));
12809
+ if (policyFile && (0, import_node_fs13.existsSync)(policyFile)) {
12810
+ return policySetFromSource((0, import_node_fs13.readFileSync)(policyFile, "utf-8"), (0, import_node_path9.basename)(policyFile));
12445
12811
  }
12446
12812
  } catch {
12447
12813
  }
@@ -12542,7 +12908,7 @@ async function handleSign(argv) {
12542
12908
  if (m.tool) tool = m.tool;
12543
12909
  }
12544
12910
  }
12545
- if (keyPath && (0, import_node_fs12.existsSync)(keyPath)) {
12911
+ if (keyPath && (0, import_node_fs13.existsSync)(keyPath)) {
12546
12912
  try {
12547
12913
  await initSigning({ enabled: true, key_path: keyPath });
12548
12914
  } catch {
@@ -12559,12 +12925,12 @@ async function handleSign(argv) {
12559
12925
  timestamp: Date.now()
12560
12926
  });
12561
12927
  try {
12562
- (0, import_node_fs12.mkdirSync)(receiptsDir, { recursive: true });
12928
+ (0, import_node_fs13.mkdirSync)(receiptsDir, { recursive: true });
12563
12929
  } catch {
12564
12930
  }
12565
12931
  const line = signed.signed ?? JSON.stringify({ tool, request_id: requestId, signed: false, note: signed.warning || "no signer configured" });
12566
12932
  try {
12567
- (0, import_node_fs12.appendFileSync)((0, import_node_path8.join)(receiptsDir, "receipts.jsonl"), line + "\n");
12933
+ (0, import_node_fs13.appendFileSync)((0, import_node_path9.join)(receiptsDir, "receipts.jsonl"), line + "\n");
12568
12934
  } catch {
12569
12935
  }
12570
12936
  if (format === "hermes") {
@@ -12574,12 +12940,55 @@ async function handleSign(argv) {
12574
12940
  process.stdout.write(JSON.stringify({ signed: Boolean(signed.signed), artifact_type: signed.artifact_type, request_id: requestId }) + "\n");
12575
12941
  process.exit(0);
12576
12942
  }
12943
+ async function handleSample(argv) {
12944
+ const dir = flagValue(argv, "--dir") || process.cwd();
12945
+ const { buildSampleKit: buildSampleKit2 } = await Promise.resolve().then(() => (init_sample(), sample_exports));
12946
+ let kit;
12947
+ try {
12948
+ kit = buildSampleKit2(dir, { force: argv.includes("--force") });
12949
+ } catch (err) {
12950
+ if (err?.code === "SAMPLE_EXISTS") {
12951
+ process.stderr.write(
12952
+ "\nprotect-mcp sample: this folder already has a record or signing key.\nThis command seeds a LABELED SAMPLE record and will not touch a real one.\nRun it in an empty folder, or pass --force to overwrite.\n\n"
12953
+ );
12954
+ process.exit(1);
12955
+ }
12956
+ throw err;
12957
+ }
12958
+ process.stdout.write(`
12959
+ ${bold("\u{1F6E1} Sample record seeded")} \xB7 8 decisions (1 blocked, 2 payments), signed with a fresh key ${dim(`(kid ${kit.kid})`)}
12960
+
12961
+ `);
12962
+ process.stdout.write(" .protect-mcp-receipts.jsonl the signed sample record\n");
12963
+ process.stdout.write(" demo-tampered.jsonl the same record with ONE decision edited after signing\n");
12964
+ process.stdout.write(` keys/gateway.json sample keypair ${dim("(never commit)")}
12965
+
12966
+ `);
12967
+ process.stdout.write(`${bold("Replay the demo")} ${dim("(the film: legate.scopeblind.com/record)")}
12968
+ `);
12969
+ process.stdout.write(" npx protect-mcp record\n");
12970
+ process.stdout.write(" npx protect-mcp claim --payment-under 100 --anchor --output payments-under-100.json\n");
12971
+ process.stdout.write(" npx protect-mcp verify-claim payments-under-100.json\n");
12972
+ process.stdout.write(" npx protect-mcp anchor-record\n\n");
12973
+ process.stdout.write(`${dim("Drop demo-tampered.jsonl into the record page to watch tampering get caught.")}
12974
+ `);
12975
+ process.stdout.write(`${dim("Everything runs locally; --anchor publishes only a digest to the public log.")}
12976
+
12977
+ `);
12978
+ process.exit(0);
12979
+ }
12577
12980
  async function main() {
12578
12981
  sendInstallTelemetry().catch(() => {
12579
12982
  });
12580
12983
  const args = process.argv.slice(2);
12581
12984
  process.env.PROTECT_MCP_VERSION = process.env.PROTECT_MCP_VERSION || await pkgVersion();
12582
- if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
12985
+ const preSep = args.includes("--") ? args.slice(0, args.indexOf("--")) : args;
12986
+ if (args[0] === "version" || preSep.includes("--version") || preSep.includes("-V")) {
12987
+ process.stdout.write(`${process.env.PROTECT_MCP_VERSION || "unknown"}
12988
+ `);
12989
+ process.exit(0);
12990
+ }
12991
+ if (args.length === 0 || args[0] === "help" || preSep.includes("--help") || preSep.includes("-h")) {
12583
12992
  printHelp();
12584
12993
  process.exit(0);
12585
12994
  }
@@ -12629,6 +13038,14 @@ async function main() {
12629
13038
  await handleVerifyClaim(args.slice(1));
12630
13039
  return;
12631
13040
  }
13041
+ if (args[0] === "anchor-record") {
13042
+ await handleAnchorRecord(args.slice(1));
13043
+ return;
13044
+ }
13045
+ if (args[0] === "sample") {
13046
+ await handleSample(args.slice(1));
13047
+ return;
13048
+ }
12632
13049
  if (args[0] === "init-hooks") {
12633
13050
  await handleInitHooks(args.slice(1));
12634
13051
  process.exit(0);
@@ -12733,10 +13150,10 @@ async function main() {
12733
13150
  let cedarPolicySet = null;
12734
13151
  let effectiveCedarDir = cedarDir;
12735
13152
  if (!effectiveCedarDir && !policyPath) {
12736
- const { existsSync: existsSync9, readdirSync: readdirSync4 } = await import("fs");
13153
+ const { existsSync: existsSync10, readdirSync: readdirSync4 } = await import("fs");
12737
13154
  for (const candidate of ["cedar", "policies", "."]) {
12738
13155
  try {
12739
- if (existsSync9(candidate) && readdirSync4(candidate).some((f) => f.endsWith(".cedar"))) {
13156
+ if (existsSync10(candidate) && readdirSync4(candidate).some((f) => f.endsWith(".cedar"))) {
12740
13157
  effectiveCedarDir = candidate;
12741
13158
  process.stderr.write(`[PROTECT_MCP] Auto-detected Cedar policies in ./${candidate}/
12742
13159
  `);
@@ -12847,8 +13264,8 @@ async function handleSimulate(args) {
12847
13264
  process.stderr.write("Usage: protect-mcp simulate --policy <path> [--log <path>] [--tier <tier>] [--json]\n");
12848
13265
  process.exit(1);
12849
13266
  }
12850
- const { existsSync: existsSync9 } = await import("fs");
12851
- if (!existsSync9(logPath)) {
13267
+ const { existsSync: existsSync10 } = await import("fs");
13268
+ if (!existsSync10(logPath)) {
12852
13269
  process.stderr.write(`Log file not found: ${logPath}
12853
13270
  `);
12854
13271
  process.stderr.write("Run protect-mcp in shadow mode first to generate a log file.\n");
@@ -12870,8 +13287,8 @@ async function handleSimulate(args) {
12870
13287
  }
12871
13288
  }
12872
13289
  async function handleDoctor() {
12873
- const { existsSync: existsSync9, readFileSync: readFileSync11, readdirSync: readdirSync4 } = await import("fs");
12874
- const { join: join8 } = await import("path");
13290
+ const { existsSync: existsSync10, readFileSync: readFileSync11, readdirSync: readdirSync4 } = await import("fs");
13291
+ const { join: join9 } = await import("path");
12875
13292
  const { execSync } = await import("child_process");
12876
13293
  const green2 = (s) => `\x1B[32m\u2713\x1B[0m ${s}`;
12877
13294
  const red2 = (s) => `\x1B[31m\u2717\x1B[0m ${s}`;
@@ -12890,8 +13307,8 @@ async function handleDoctor() {
12890
13307
  `));
12891
13308
  issues++;
12892
13309
  }
12893
- const configPath = join8(process.cwd(), "scopeblind.config.json");
12894
- if (existsSync9(configPath)) {
13310
+ const configPath = join9(process.cwd(), "scopeblind.config.json");
13311
+ if (existsSync10(configPath)) {
12895
13312
  try {
12896
13313
  const config = JSON.parse(readFileSync11(configPath, "utf-8"));
12897
13314
  if (config.signing?.private_key || config.signing?.key_file) {
@@ -12910,7 +13327,7 @@ async function handleDoctor() {
12910
13327
  let policyFound = false;
12911
13328
  for (const dir of ["cedar", "policies", "."]) {
12912
13329
  try {
12913
- if (existsSync9(dir) && readdirSync4(dir).some((f) => f.endsWith(".cedar"))) {
13330
+ if (existsSync10(dir) && readdirSync4(dir).some((f) => f.endsWith(".cedar"))) {
12914
13331
  process.stdout.write(green2(`Cedar policies found in ./${dir}/
12915
13332
  `));
12916
13333
  policyFound = true;
@@ -12921,7 +13338,7 @@ async function handleDoctor() {
12921
13338
  }
12922
13339
  if (!policyFound) {
12923
13340
  for (const name of ["policy.json", "protect-mcp.policy.json", "scopeblind-policy.json"]) {
12924
- if (existsSync9(name)) {
13341
+ if (existsSync10(name)) {
12925
13342
  process.stdout.write(green2(`JSON policy found: ${name}
12926
13343
  `));
12927
13344
  policyFound = true;
@@ -12942,9 +13359,9 @@ async function handleDoctor() {
12942
13359
  } catch {
12943
13360
  process.stdout.write(dim2(" Cedar WASM not installed\n"));
12944
13361
  }
12945
- const logFile = join8(process.cwd(), "protect-mcp-decisions.jsonl");
12946
- const receiptFile = join8(process.cwd(), "protect-mcp-receipts.jsonl");
12947
- if (existsSync9(logFile)) {
13362
+ const logFile = join9(process.cwd(), "protect-mcp-decisions.jsonl");
13363
+ const receiptFile = join9(process.cwd(), "protect-mcp-receipts.jsonl");
13364
+ if (existsSync10(logFile)) {
12948
13365
  try {
12949
13366
  const lines = readFileSync11(logFile, "utf-8").trim().split("\n").length;
12950
13367
  process.stdout.write(green2(`Decision log: ${lines} entries
@@ -12955,7 +13372,7 @@ async function handleDoctor() {
12955
13372
  } else {
12956
13373
  process.stdout.write(dim2(" No decision log yet, will be created on first tool call\n"));
12957
13374
  }
12958
- if (existsSync9(receiptFile)) {
13375
+ if (existsSync10(receiptFile)) {
12959
13376
  try {
12960
13377
  const lines = readFileSync11(receiptFile, "utf-8").trim().split("\n").length;
12961
13378
  process.stdout.write(green2(`Receipt file: ${lines} signed receipts
@@ -13026,9 +13443,9 @@ async function handleReport(args) {
13026
13443
  }
13027
13444
  }
13028
13445
  const { generateReport: generateReport2, formatReportMarkdown: formatReportMarkdown2 } = await Promise.resolve().then(() => (init_report(), report_exports));
13029
- const { join: join8 } = await import("path");
13030
- const logPath = join8(dir, ".protect-mcp-log.jsonl");
13031
- const receiptPath = join8(dir, ".protect-mcp-receipts.jsonl");
13446
+ const { join: join9 } = await import("path");
13447
+ const logPath = join9(dir, ".protect-mcp-log.jsonl");
13448
+ const receiptPath = join9(dir, ".protect-mcp-receipts.jsonl");
13032
13449
  const report = generateReport2(logPath, receiptPath, period);
13033
13450
  let output;
13034
13451
  if (format === "md") {
@@ -13037,8 +13454,8 @@ async function handleReport(args) {
13037
13454
  output = JSON.stringify(report, null, 2);
13038
13455
  }
13039
13456
  if (outputPath) {
13040
- const { writeFileSync: writeFileSync4 } = await import("fs");
13041
- writeFileSync4(outputPath, output, "utf-8");
13457
+ const { writeFileSync: writeFileSync5 } = await import("fs");
13458
+ writeFileSync5(outputPath, output, "utf-8");
13042
13459
  process.stderr.write(`Report written to ${outputPath}
13043
13460
  `);
13044
13461
  } else {