@scopebond/hook 0.3.0 → 0.4.0

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/README.md CHANGED
@@ -10,17 +10,47 @@ optionally mirrored to your Scopebond Cloud workspace for monitoring.
10
10
  - **Ceiling:** it governs actions the harness routes through its tool system
11
11
  (shell, file, MCP, web). A process started outside the harness is not covered.
12
12
 
13
- ## Enroll
13
+ ## Install once for your machine
14
+
15
+ ```
16
+ npm i -g @scopebond/hook
17
+ scopebond install # detects Cursor; add --claude / --cursor to pick
18
+ ```
19
+
20
+ `install` sets Scopebond up **once per developer machine**, not per repository: it
21
+ scaffolds a user-level home (`~/.scopebond`, override `SCOPEBOND_HOME`) with a
22
+ signing key, a countersigning key and a starter policy, and registers the hook by
23
+ absolute path in your user-level `~/.claude/settings.json` (and `~/.cursor/hooks.json`
24
+ when Cursor is present). Every project you open is then governed, and a project-local
25
+ `.scopebond/` still takes precedence when you want a per-repo policy.
26
+
27
+ ```
28
+ scopebond status # home, which agents are configured, cloud, receipts
29
+ scopebond doctor # node version, config location, cloud reachability
30
+ scopebond log # recent decisions scopebond verify # offline
31
+ scopebond uninstall # remove the hook (add --purge to delete keys too)
32
+ ```
33
+
34
+ ### Install as a Claude Code plugin
35
+
36
+ Scopebond is also a Claude Code plugin (this repo is a plugin marketplace):
37
+
38
+ ```
39
+ /plugin marketplace add avouro-com/scopebond
40
+ /plugin install scopebond
41
+ ```
42
+
43
+ ### Per-repository enroll (alternative)
14
44
 
15
45
  ```
16
46
  npx @scopebond/hook init # Claude Code (or: init --cursor)
17
47
  ```
18
48
 
19
- `init` scaffolds `.scopebond/` (a machine signing key, a countersigning key, a
20
- starter policy — "protect main and production paths" — and a `.gitignore` so none
21
- of it is committed) and prints the hook configuration to add to
22
- `.claude/settings.json` or `.cursor/hooks.json`. Then run one safe command in the
23
- agent and see the receipt in `.scopebond/receipts.db`.
49
+ `init` scaffolds `.scopebond/` in the current project (a machine signing key, a
50
+ countersigning key, a starter policy — "protect main and production paths" — and a
51
+ `.gitignore` so none of it is committed) and configures `.claude/settings.json` or
52
+ `.cursor/hooks.json`. Then run one safe command in the agent and see the receipt in
53
+ `.scopebond/receipts.db`.
24
54
 
25
55
  ## Connect it to your workspace (optional)
26
56
 
package/dist/cli.js CHANGED
@@ -23,7 +23,10 @@ import { openReceiptStore, loadOrCreateAttester } from "@scopebond/gateway/node"
23
23
  import { mapClaudeToolUse, mapCursorEvent, fillPushBranch } from "./map.js";
24
24
  import { createHookRuntime } from "./runtime.js";
25
25
  import { scaffold, harnessSnippet, installHarness } from "./init.js";
26
+ import { userHome, userHarnessFile, resolveConfigDir, writeHarnessConfig, removeHarnessConfig, cursorDetected, absoluteHookCommand, isHarnessConfigured, purgeHome, } from "./install.js";
26
27
  import { connectCloud, loadConnection } from "./cloud.js";
28
+ import { cliCommand, hookVersion } from "./version.js";
29
+ import { fileURLToPath } from "node:url";
27
30
  /** The current git branch in `cwd` (best-effort). A bare `git push` pushes it, so
28
31
  * the runtime fills it in before evaluating; on failure the ref stays absent and
29
32
  * the starter policy fails closed. */
@@ -100,7 +103,7 @@ async function runClaude() {
100
103
  }
101
104
  try {
102
105
  const cwd = input?.cwd ? String(input.cwd) : process.cwd();
103
- const runtime = createHookRuntime(runtimePaths(configDir()));
106
+ const runtime = createHookRuntime(runtimePaths(resolveConfigDir(cwd)));
104
107
  const decision = await runtime.evaluate(fillPushBranch(mapClaudeToolUse(input), currentBranch(cwd)));
105
108
  await runtime.flush();
106
109
  if (decision.decision === "deny")
@@ -111,7 +114,7 @@ async function runClaude() {
111
114
  process.exit(0);
112
115
  }
113
116
  catch (error) {
114
- denyClaude(`Scopebond hook failed closed: ${error.message}. Repair: run \`scopebond-hook init\`.`);
117
+ denyClaude(`Scopebond hook failed closed: ${error.message}. Repair: run \`${cliCommand("init")}\`.`);
115
118
  }
116
119
  }
117
120
  async function runCursor() {
@@ -125,7 +128,7 @@ async function runCursor() {
125
128
  let message = "Scopebond hook failed closed";
126
129
  try {
127
130
  const cwd = input?.cwd ? String(input.cwd) : process.cwd();
128
- const runtime = createHookRuntime(runtimePaths(configDir()));
131
+ const runtime = createHookRuntime(runtimePaths(resolveConfigDir(cwd)));
129
132
  const decision = await runtime.evaluate(fillPushBranch(mapCursorEvent(event, input), currentBranch(cwd)));
130
133
  await runtime.flush();
131
134
  // Only an out-of-policy action is denied outright; an allowed or unevaluated
@@ -135,7 +138,7 @@ async function runCursor() {
135
138
  }
136
139
  catch (error) {
137
140
  permission = "deny";
138
- message = `Scopebond hook failed closed: ${error.message}. Repair: run \`scopebond-hook init\`.`;
141
+ message = `Scopebond hook failed closed: ${error.message}. Repair: run \`${cliCommand("init")}\`.`;
139
142
  }
140
143
  process.stdout.write(JSON.stringify({ permission, agentMessage: message }) + "\n");
141
144
  process.exit(0);
@@ -159,9 +162,11 @@ function runInit(args) {
159
162
  console.log(harnessSnippet(harness));
160
163
  }
161
164
  console.log("");
162
- console.log("Next: run one command in the agent, then `scopebond-hook log` to see the receipt");
163
- console.log("and `scopebond-hook verify` to check it offline. Try `scopebond-hook test \"rm -rf /\"`.");
164
- console.log("To send receipts to a workspace: scopebond-hook connect <workspace-url> <enrollment>");
165
+ // Print the runnable `npx` form: after `npx @scopebond/hook init` there is no
166
+ // `scopebond-hook` binary on PATH, so a bare `scopebond-hook log` would fail.
167
+ console.log(`Next: run one command in the agent, then \`${cliCommand("log")}\` to see the receipt`);
168
+ console.log(`and \`${cliCommand("verify")}\` to check it offline. Try \`${cliCommand('test "rm -rf /"')}\`.`);
169
+ console.log(`To send receipts to a workspace: ${cliCommand("connect <workspace-url> <enrollment>")}`);
165
170
  }
166
171
  function decisionOf(payload) {
167
172
  const rr = String(payload.realtime_result ?? "");
@@ -183,7 +188,7 @@ function describeIntent(payload) {
183
188
  return `${String(intent.action_type ?? "?")}${bits ? ` ${bits}` : ""}`;
184
189
  }
185
190
  async function runLog(args) {
186
- const dir = configDir();
191
+ const dir = resolveConfigDir(process.cwd());
187
192
  const dbPath = join(dir, "receipts.db");
188
193
  if (!existsSync(dbPath)) {
189
194
  console.log("no receipts yet — run a command in the agent first.");
@@ -202,10 +207,10 @@ async function runLog(args) {
202
207
  const p = r.payload;
203
208
  console.log(`${String(p.timestamp ?? "")} ${decisionOf(p).padEnd(13)} ${describeIntent(p)}`);
204
209
  }
205
- console.log(`\n${recent.length} of ${all.length} receipt(s). Verify them: scopebond-hook verify`);
210
+ console.log(`\n${recent.length} of ${all.length} receipt(s). Verify them: ${cliCommand("verify")}`);
206
211
  }
207
212
  async function runVerify() {
208
- const dir = configDir();
213
+ const dir = resolveConfigDir(process.cwd());
209
214
  const dbPath = join(dir, "receipts.db");
210
215
  const attesterPath = join(dir, "attester.key");
211
216
  if (!existsSync(dbPath) || !existsSync(attesterPath)) {
@@ -240,7 +245,7 @@ async function runTest(args) {
240
245
  console.error('usage: scopebond-hook test "<shell command>"');
241
246
  process.exit(1);
242
247
  }
243
- const dir = configDir();
248
+ const dir = resolveConfigDir(process.cwd());
244
249
  if (!existsSync(join(dir, "policy.json"))) {
245
250
  console.error("no policy yet — run `scopebond-hook init` first.");
246
251
  process.exit(1);
@@ -313,7 +318,7 @@ async function runConnect(args) {
313
318
  }
314
319
  }
315
320
  async function runFlush() {
316
- const dir = configDir();
321
+ const dir = resolveConfigDir(process.cwd());
317
322
  if (!loadConnection(dir)) {
318
323
  console.error("not connected to a workspace; run `scopebond-hook connect` first");
319
324
  process.exit(1);
@@ -324,6 +329,109 @@ async function runFlush() {
324
329
  console.log(`flushed; ${status?.pending ?? 0} receipt(s) still pending${status?.lastError ? ` (last error: ${status.lastError})` : ""}`);
325
330
  process.exit(status && status.pending > 0 ? 1 : 0);
326
331
  }
332
+ /** The absolute path to this CLI file, for registering the hook by absolute path. */
333
+ function cliPath() {
334
+ return fileURLToPath(import.meta.url);
335
+ }
336
+ /** `install` — the once-per-machine, user-level install (SB112). Scaffolds the
337
+ * user home and registers the hook by absolute path in the user-level agent config,
338
+ * so every project a developer opens is governed without a per-repo `init`. */
339
+ function runInstall(args) {
340
+ const dir = userHome();
341
+ const { agentKid, policyPath } = scaffold(dir, { force: args.includes("--force") });
342
+ console.log(`Scopebond installed for this user in ${dir}`);
343
+ console.log(` machine key ${agentKid}`);
344
+ console.log(` policy ${policyPath} (starter — edit the limits)`);
345
+ console.log("");
346
+ const harnesses = args.includes("--cursor")
347
+ ? ["cursor"]
348
+ : args.includes("--claude") ? ["claude"] : (cursorDetected() ? ["claude", "cursor"] : ["claude"]);
349
+ if (!args.includes("--no-install")) {
350
+ for (const h of harnesses) {
351
+ const file = writeHarnessConfig(userHarnessFile(h), h, absoluteHookCommand(cliPath(), h));
352
+ console.log(`✓ ${h === "cursor" ? "Cursor" : "Claude Code"} configured in ${file}`);
353
+ }
354
+ }
355
+ else {
356
+ console.log("Add this to your user-level agent config:");
357
+ console.log(harnessSnippet(harnesses[0]));
358
+ }
359
+ console.log("");
360
+ console.log("A project-local .scopebond still takes precedence when present.");
361
+ console.log(`Check it: ${cliCommand("doctor")} · see decisions: ${cliCommand("log")}`);
362
+ console.log(`To send receipts to a workspace: ${cliCommand("connect <workspace-url> <enrollment>")}`);
363
+ }
364
+ function runStatus() {
365
+ const home = userHome();
366
+ const installed = existsSync(join(home, "policy.json"));
367
+ const claude = isHarnessConfigured(userHarnessFile("claude"));
368
+ const cursor = isHarnessConfigured(userHarnessFile("cursor"));
369
+ const connected = !!loadConnection(resolveConfigDir(process.cwd()));
370
+ const dbPath = join(resolveConfigDir(process.cwd()), "receipts.db");
371
+ console.log(`Scopebond hook ${hookVersion()}`);
372
+ console.log(` user home ${home} ${installed ? "(installed)" : "(not installed — run `scopebond install`)"}`);
373
+ console.log(` active config ${resolveConfigDir(process.cwd())}`);
374
+ console.log(` Claude Code ${claude ? "configured" : "not configured"}`);
375
+ console.log(` Cursor ${cursor ? "configured" : cursorDetected() ? "detected, not configured" : "not detected"}`);
376
+ console.log(` cloud workspace ${connected ? "connected" : "not connected (local only)"}`);
377
+ console.log(` local receipts ${existsSync(dbPath) ? dbPath : "none yet"}`);
378
+ }
379
+ async function runDoctor() {
380
+ const problems = [];
381
+ const [major, minor] = process.versions.node.split(".").map(Number);
382
+ const nodeOk = major > 22 || (major === 22 && minor >= 13);
383
+ console.log(`Scopebond doctor`);
384
+ console.log(` node ${process.versions.node} ${nodeOk ? "ok" : "TOO OLD (need >=22.13)"}`);
385
+ if (!nodeOk)
386
+ problems.push("node >=22.13 is required (the Cloud outbox uses node:sqlite)");
387
+ const cli = cliPath();
388
+ console.log(` cli ${cli} ${existsSync(cli) ? "ok" : "MISSING"}`);
389
+ const active = resolveConfigDir(process.cwd());
390
+ const hasPolicy = existsSync(join(active, "policy.json"));
391
+ console.log(` active config ${active} ${hasPolicy ? "ok" : "no policy (run `scopebond install` or `init`)"}`);
392
+ if (!hasPolicy)
393
+ problems.push("no policy found in the active config dir");
394
+ const connection = loadConnection(active);
395
+ if (!connection) {
396
+ console.log(` cloud not connected (local only) — receipts stay on this machine`);
397
+ }
398
+ else {
399
+ let reachable = "unknown";
400
+ try {
401
+ const res = await fetch(new URL("/healthz", connection.url).toString(), { method: "GET" });
402
+ reachable = res.ok ? "reachable" : `unhealthy (${res.status})`;
403
+ }
404
+ catch (error) {
405
+ reachable = `unreachable (${error.message})`;
406
+ }
407
+ console.log(` cloud ${connection.url} — ${reachable}`);
408
+ }
409
+ console.log(problems.length ? `\n${problems.length} problem(s): ${problems.join("; ")}` : `\nAll good.`);
410
+ process.exit(problems.length ? 1 : 0);
411
+ }
412
+ function runUninstall(args) {
413
+ let removed = 0;
414
+ for (const h of ["claude", "cursor"]) {
415
+ if (removeHarnessConfig(userHarnessFile(h))) {
416
+ console.log(`✓ removed the Scopebond hook from ${userHarnessFile(h)}`);
417
+ removed++;
418
+ }
419
+ }
420
+ if (removed === 0)
421
+ console.log("no user-level harness config found.");
422
+ if (args.includes("--purge")) {
423
+ purgeHome();
424
+ console.log(`✓ purged ${userHome()} (keys, policy, receipts)`);
425
+ }
426
+ else
427
+ console.log(`Kept ${userHome()} (keys, policy, receipts). Use --purge to remove it too.`);
428
+ }
429
+ function runLogin() {
430
+ console.log("Device-code login is not available yet.");
431
+ console.log(`For now, connect with a one-time enrollment from your workspace:`);
432
+ console.log(` ${cliCommand("connect <workspace-url> <enrollment>")}`);
433
+ process.exit(0);
434
+ }
327
435
  const [cmd, ...rest] = process.argv.slice(2);
328
436
  if (cmd === "claude") {
329
437
  await runClaude();
@@ -334,6 +442,9 @@ else if (cmd === "cursor") {
334
442
  else if (cmd === "init") {
335
443
  runInit(rest);
336
444
  }
445
+ else if (cmd === "install") {
446
+ runInstall(rest);
447
+ }
337
448
  else if (cmd === "connect") {
338
449
  await runConnect(rest);
339
450
  }
@@ -349,8 +460,20 @@ else if (cmd === "test") {
349
460
  else if (cmd === "flush") {
350
461
  await runFlush();
351
462
  }
463
+ else if (cmd === "status") {
464
+ runStatus();
465
+ }
466
+ else if (cmd === "doctor") {
467
+ await runDoctor();
468
+ }
469
+ else if (cmd === "uninstall") {
470
+ runUninstall(rest);
471
+ }
472
+ else if (cmd === "login") {
473
+ runLogin();
474
+ }
352
475
  else {
353
- console.error("usage: scopebond-hook <claude|cursor|init|connect|log|verify|test|flush> [--cursor] [--force] [--strict] [--no-install]");
476
+ console.error("usage: scopebond <claude|cursor|init|install|connect|log|verify|test|flush|status|doctor|uninstall|login> [--cursor] [--claude] [--force] [--strict] [--no-install] [--purge]");
354
477
  process.exit(1);
355
478
  }
356
479
  //# sourceMappingURL=cli.js.map
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// scopebond-hook — govern a coding agent's tool calls against policy, in-path,\n// before they run, with a signed local receipt.\n//\n// scopebond-hook claude evaluate a Claude Code PreToolUse call (stdin JSON)\n// scopebond-hook cursor evaluate a Cursor hook event (stdin JSON)\n// scopebond-hook init [--cursor] [--no-install] scaffold keys + starter policy and configure the agent\n// scopebond-hook connect <url> <bundle.json> [--cursor]\n// enroll with a Cloud workspace and start exporting\n// scopebond-hook log [-n N] show the most recent local receipts\n// scopebond-hook verify verify every local receipt offline against the attester key\n// scopebond-hook test \"<shell command>\" show the decision for a command without recording it\n// scopebond-hook flush deliver any queued receipts to Cloud now\n//\n// Config dir: $SCOPEBOND_HOOK_DIR, else ./.scopebond\n// Fail-closed: any error denies the action with a repair message.\n\nimport { readFileSync, existsSync, mkdtempSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { execFileSync } from \"node:child_process\";\nimport type { CloudEnrollmentBundle } from \"@scopebond/gateway\";\nimport { verifyReceipt } from \"@scopebond/gateway\";\nimport { openReceiptStore, loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport { mapClaudeToolUse, mapCursorEvent, fillPushBranch } from \"./map.js\";\nimport { createHookRuntime } from \"./runtime.js\";\nimport { scaffold, harnessSnippet, installHarness } from \"./init.js\";\nimport { connectCloud, loadConnection, connectionPath } from \"./cloud.js\";\n\n/** The current git branch in `cwd` (best-effort). A bare `git push` pushes it, so\n * the runtime fills it in before evaluating; on failure the ref stays absent and\n * the starter policy fails closed. */\nfunction currentBranch(cwd: string): string | null {\n try {\n return execFileSync(\"git\", [\"-C\", cwd, \"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"], {\n encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim() || null;\n } catch { return null; }\n}\n\n/** Read an enrollment bundle from a file path, an inline base64 blob, or inline raw\n * JSON (the portal hands out a base64 blob so it is one clean argument). */\nfunction readBundleArg(bundleArg: string | undefined, stdin: () => string): CloudEnrollmentBundle {\n let text: string;\n if (!bundleArg) text = stdin();\n else if (existsSync(bundleArg)) text = readFileSync(bundleArg, \"utf8\");\n else if (bundleArg.trim().startsWith(\"{\")) text = bundleArg;\n else { try { text = Buffer.from(bundleArg, \"base64\").toString(\"utf8\"); } catch { text = bundleArg; } }\n return JSON.parse(text) as CloudEnrollmentBundle;\n}\n\nfunction configDir(): string {\n return process.env.SCOPEBOND_HOOK_DIR ?? join(process.cwd(), \".scopebond\");\n}\nfunction runtimePaths(dir: string) {\n const connection = loadConnection(dir);\n return {\n policyPath: join(dir, \"policy.json\"),\n keyPath: join(dir, \"agent.key\"),\n attesterPath: join(dir, \"attester.key\"),\n dbPath: join(dir, \"receipts.db\"),\n // Strict: deny (not just observe) tools with no taxonomy mapping.\n strict: process.argv.includes(\"--strict\") || process.env.SCOPEBOND_HOOK_STRICT === \"1\",\n // When connected, auto-export receipts. A short bounded flush keeps the hot path\n // fast; undelivered receipts persist in the durable outbox and flush next time\n // (or via `scopebond-hook flush`, e.g. on a session-end hook).\n ...(connection ? { cloud: { connection, flushTimeoutMs: Number(process.env.SCOPEBOND_HOOK_FLUSH_MS ?? 800) } } : {}),\n };\n}\nfunction readStdin(): string {\n try { return readFileSync(0, \"utf8\"); } catch { return \"\"; }\n}\n\nfunction denyClaude(reason: string): never {\n process.stdout.write(JSON.stringify({\n hookSpecificOutput: { hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: reason },\n }) + \"\\n\");\n process.stderr.write(`Scopebond: ${reason}\\n`);\n process.exit(2);\n}\n\nasync function runClaude(): Promise<void> {\n let input: Record<string, unknown>;\n try { input = JSON.parse(readStdin()); } catch { denyClaude(\"hook received invalid JSON on stdin\"); }\n try {\n const cwd = input?.cwd ? String(input.cwd) : process.cwd();\n const runtime = createHookRuntime(runtimePaths(configDir()));\n const decision = await runtime.evaluate(fillPushBranch(mapClaudeToolUse(input!), currentBranch(cwd)));\n await runtime.flush();\n if (decision.decision === \"deny\") denyClaude(decision.reason);\n // allow and not_evaluated both DEFER to Claude Code's own permission flow: the\n // hook records the receipt but never returns permissionDecision:\"allow\", which\n // would suppress the user's normal review. Scopebond blocks; it does not approve.\n process.exit(0);\n } catch (error) {\n denyClaude(`Scopebond hook failed closed: ${(error as Error).message}. Repair: run \\`scopebond-hook init\\`.`);\n }\n}\n\nasync function runCursor(): Promise<void> {\n let input: Record<string, unknown> = {};\n try { input = JSON.parse(readStdin()); } catch { /* fall through to fail-closed deny below */ }\n const event = String(input?.hook_event_name ?? input?.event ?? process.argv[3] ?? \"\");\n let permission: \"allow\" | \"deny\" | \"ask\" = \"deny\";\n let message = \"Scopebond hook failed closed\";\n try {\n const cwd = input?.cwd ? String(input.cwd) : process.cwd();\n const runtime = createHookRuntime(runtimePaths(configDir()));\n const decision = await runtime.evaluate(fillPushBranch(mapCursorEvent(event, input), currentBranch(cwd)));\n await runtime.flush();\n // Only an out-of-policy action is denied outright; an allowed or unevaluated\n // action defers to Cursor's own prompt (\"ask\"), never a silent auto-allow.\n permission = decision.decision === \"deny\" ? \"deny\" : \"ask\";\n message = decision.reason;\n } catch (error) {\n permission = \"deny\";\n message = `Scopebond hook failed closed: ${(error as Error).message}. Repair: run \\`scopebond-hook init\\`.`;\n }\n process.stdout.write(JSON.stringify({ permission, agentMessage: message }) + \"\\n\");\n process.exit(0);\n}\n\nfunction runInit(args: string[]): void {\n const harness = args.includes(\"--cursor\") ? \"cursor\" : \"claude\";\n const dir = configDir();\n const { agentKid, policyPath } = scaffold(dir, { force: args.includes(\"--force\") });\n console.log(`Scopebond hook enrolled in ${dir}`);\n console.log(` machine key ${agentKid}`);\n console.log(` policy ${policyPath} (starter — edit the limits)`);\n console.log(\"\");\n // Configure the agent automatically by default (idempotent), so there is no\n // hand-editing step; --no-install prints the snippet instead.\n if (!args.includes(\"--no-install\")) {\n const file = installHarness(harness);\n console.log(`✓ ${harness === \"cursor\" ? \"Cursor\" : \"Claude Code\"} configured in ${file}`);\n } else {\n console.log(`Add this to your ${harness === \"cursor\" ? \".cursor/hooks.json\" : \".claude/settings.json\"}:`);\n console.log(harnessSnippet(harness));\n }\n console.log(\"\");\n console.log(\"Next: run one command in the agent, then `scopebond-hook log` to see the receipt\");\n console.log(\"and `scopebond-hook verify` to check it offline. Try `scopebond-hook test \\\"rm -rf /\\\"`.\");\n console.log(\"To send receipts to a workspace: scopebond-hook connect <workspace-url> <enrollment>\");\n}\n\nfunction decisionOf(payload: Record<string, unknown>): string {\n const rr = String(payload.realtime_result ?? \"\");\n const state = String((payload.execution as Record<string, unknown> | undefined)?.state ?? \"\");\n if (state === \"observed_not_evaluated\") return \"not_evaluated\";\n if (rr === \"deny\") return state === \"cooperative_allow\" || state === \"executed\" ? \"monitor\" : \"deny\";\n return \"allow\";\n}\n\nfunction describeIntent(payload: Record<string, unknown>): string {\n const intent = (payload.intent ?? {}) as Record<string, unknown>;\n const p = (intent.params ?? {}) as Record<string, unknown>;\n const bits = intent.action_type === \"shell.exec\" ? String(p.program ?? \"\")\n : intent.action_type === \"git.push\" ? `${p.remote ?? \"\"} ${p.ref ?? \"\"}`.trim()\n : intent.action_type === \"file.write\" || intent.action_type === \"file.read\" ? String(p.path ?? \"\")\n : intent.action_type === \"mcp.tool.call\" ? `${p.server ?? \"\"}/${p.tool ?? \"\"}`\n : intent.action_type === \"net.fetch\" ? String(p.host ?? \"\") : \"\";\n return `${String(intent.action_type ?? \"?\")}${bits ? ` ${bits}` : \"\"}`;\n}\n\nasync function runLog(args: string[]): Promise<void> {\n const dir = configDir();\n const dbPath = join(dir, \"receipts.db\");\n if (!existsSync(dbPath)) { console.log(\"no receipts yet — run a command in the agent first.\"); process.exit(0); }\n const nIdx = args.indexOf(\"-n\");\n const n = nIdx >= 0 ? Math.max(1, Number(args[nIdx + 1]) || 20) : 20;\n const { store } = openReceiptStore({ db: dbPath });\n const all = await Promise.resolve(store.list());\n const recent = all.slice(-n);\n if (recent.length === 0) { console.log(\"no receipts yet.\"); process.exit(0); }\n for (const r of recent) {\n const p = r.payload as unknown as Record<string, unknown>;\n console.log(`${String(p.timestamp ?? \"\")} ${decisionOf(p).padEnd(13)} ${describeIntent(p)}`);\n }\n console.log(`\\n${recent.length} of ${all.length} receipt(s). Verify them: scopebond-hook verify`);\n}\n\nasync function runVerify(): Promise<void> {\n const dir = configDir();\n const dbPath = join(dir, \"receipts.db\");\n const attesterPath = join(dir, \"attester.key\");\n if (!existsSync(dbPath) || !existsSync(attesterPath)) { console.log(\"nothing to verify yet (no receipts or no attester key).\"); process.exit(0); }\n const { attester } = loadOrCreateAttester({ file: attesterPath });\n const { store } = openReceiptStore({ db: dbPath });\n const all = await Promise.resolve(store.list());\n let ok = 0;\n const bad: string[] = [];\n for (const r of all) {\n const result = verifyReceipt(r, attester.publicKeyPem) as unknown as Record<string, boolean>;\n if (result.valid) ok += 1;\n else {\n const failed = Object.entries(result).filter(([k, v]) => k.endsWith(\"_valid\") && v === false).map(([k]) => k);\n bad.push(`${String((r.payload as unknown as Record<string, unknown>).timestamp ?? \"\")}: ${failed.join(\", \") || \"invalid\"}`);\n }\n }\n console.log(`${ok}/${all.length} receipt(s) verify offline against ${attesterPath}.`);\n if (bad.length) { for (const b of bad) console.error(` ✗ ${b}`); process.exit(1); }\n process.exit(0);\n}\n\nasync function runTest(args: string[]): Promise<void> {\n const command = args.find((a) => !a.startsWith(\"-\"));\n if (!command) { console.error('usage: scopebond-hook test \"<shell command>\"'); process.exit(1); }\n const dir = configDir();\n if (!existsSync(join(dir, \"policy.json\"))) { console.error(\"no policy yet — run `scopebond-hook init` first.\"); process.exit(1); }\n // Evaluate against the real policy and keys, but a throwaway store, so `test`\n // never records a receipt or exports anything.\n const tmp = mkdtempSync(join(tmpdir(), \"sb-hook-test-\"));\n try {\n const runtime = createHookRuntime({\n policyPath: join(dir, \"policy.json\"), keyPath: join(dir, \"agent.key\"),\n attesterPath: join(dir, \"attester.key\"), dbPath: join(tmp, \"receipts.db\"),\n strict: process.argv.includes(\"--strict\"),\n });\n const mapped = fillPushBranch(mapClaudeToolUse({ tool_name: \"Bash\", tool_input: { command } }), currentBranch(process.cwd()));\n console.log(`command: ${command}`);\n for (const m of mapped) {\n const d = await runtime.evaluateOne(m);\n console.log(` ${describeIntent({ intent: m.intent }).padEnd(28)} → ${d.decision}${d.reason ? ` (${d.reason})` : \"\"}`);\n }\n const overall = await runtime.evaluate(mapped);\n console.log(`\\noverall: ${overall.decision}${overall.reason ? ` · ${overall.reason}` : \"\"}`);\n process.exit(overall.decision === \"deny\" ? 2 : 0);\n } finally {\n rmSync(tmp, { recursive: true, force: true });\n }\n}\n\nasync function runConnect(args: string[]): Promise<void> {\n const positional = args.filter((a) => !a.startsWith(\"--\"));\n const url = positional[0];\n const bundleArg = positional[1];\n const harness = args.includes(\"--cursor\") ? \"cursor\" : \"claude\";\n if (!url) {\n console.error(\"usage: scopebond-hook connect <workspace-url> <enrollment> [--cursor] [--no-install]\");\n process.exit(1);\n }\n const dir = configDir();\n // One command sets everything up: scaffold the key, attester and starter policy if\n // they do not exist, then enroll and persist the scoped machine credential. The\n // enrollment can be a file, an inline base64 blob (what the portal hands out) or\n // raw JSON — the user never has to save or open a JSON file.\n scaffold(dir, {});\n let bundle: CloudEnrollmentBundle;\n try { bundle = readBundleArg(bundleArg, readStdin); }\n catch { console.error(\"could not read the enrollment (expected a file, inline blob, or JSON on stdin)\"); process.exit(1); }\n try {\n const c = await connectCloud(dir, url, bundle);\n console.log(`✓ Connected to ${c.url}`);\n // Configure the agent automatically (merges into the existing config), unless the\n // caller opts out. This removes the \"paste this snippet\" step.\n if (!args.includes(\"--no-install\")) {\n const file = installHarness(harness);\n console.log(`✓ ${harness === \"cursor\" ? \"Cursor\" : \"Claude Code\"} configured in ${file}`);\n } else {\n console.log(`Add this to your ${harness === \"cursor\" ? \".cursor/hooks.json\" : \".claude/settings.json\"}:`);\n console.log(harnessSnippet(harness));\n }\n console.log(\"\");\n console.log(\"Run your agent — the first action appears in your workspace within seconds.\");\n } catch (error) {\n console.error(`connect failed: ${(error as Error).message}`);\n process.exit(1);\n }\n}\n\nasync function runFlush(): Promise<void> {\n const dir = configDir();\n if (!loadConnection(dir)) { console.error(\"not connected to a workspace; run `scopebond-hook connect` first\"); process.exit(1); }\n const runtime = createHookRuntime(runtimePaths(dir));\n await runtime.exporter?.flush();\n const status = runtime.exporter?.status();\n console.log(`flushed; ${status?.pending ?? 0} receipt(s) still pending${status?.lastError ? ` (last error: ${status.lastError})` : \"\"}`);\n process.exit(status && status.pending > 0 ? 1 : 0);\n}\n\nconst [cmd, ...rest] = process.argv.slice(2);\nif (cmd === \"claude\") { await runClaude(); }\nelse if (cmd === \"cursor\") { await runCursor(); }\nelse if (cmd === \"init\") { runInit(rest); }\nelse if (cmd === \"connect\") { await runConnect(rest); }\nelse if (cmd === \"log\") { await runLog(rest); }\nelse if (cmd === \"verify\") { await runVerify(); }\nelse if (cmd === \"test\") { await runTest(rest); }\nelse if (cmd === \"flush\") { await runFlush(); }\nelse {\n console.error(\"usage: scopebond-hook <claude|cursor|init|connect|log|verify|test|flush> [--cursor] [--force] [--strict] [--no-install]\");\n process.exit(1);\n}\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n// scopebond-hook — govern a coding agent's tool calls against policy, in-path,\n// before they run, with a signed local receipt.\n//\n// scopebond-hook claude evaluate a Claude Code PreToolUse call (stdin JSON)\n// scopebond-hook cursor evaluate a Cursor hook event (stdin JSON)\n// scopebond-hook init [--cursor] [--no-install] scaffold keys + starter policy and configure the agent\n// scopebond-hook connect <url> <bundle.json> [--cursor]\n// enroll with a Cloud workspace and start exporting\n// scopebond-hook log [-n N] show the most recent local receipts\n// scopebond-hook verify verify every local receipt offline against the attester key\n// scopebond-hook test \"<shell command>\" show the decision for a command without recording it\n// scopebond-hook flush deliver any queued receipts to Cloud now\n//\n// Config dir: $SCOPEBOND_HOOK_DIR, else ./.scopebond\n// Fail-closed: any error denies the action with a repair message.\n\nimport { readFileSync, existsSync, mkdtempSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { tmpdir } from \"node:os\";\nimport { execFileSync } from \"node:child_process\";\nimport type { CloudEnrollmentBundle } from \"@scopebond/gateway\";\nimport { verifyReceipt } from \"@scopebond/gateway\";\nimport { openReceiptStore, loadOrCreateAttester } from \"@scopebond/gateway/node\";\nimport { mapClaudeToolUse, mapCursorEvent, fillPushBranch } from \"./map.js\";\nimport { createHookRuntime } from \"./runtime.js\";\nimport { scaffold, harnessSnippet, installHarness } from \"./init.js\";\nimport {\n userHome, userHarnessFile, resolveConfigDir, writeHarnessConfig, removeHarnessConfig,\n cursorDetected, absoluteHookCommand, isHarnessConfigured, purgeHome, type Harness,\n} from \"./install.js\";\nimport { connectCloud, loadConnection, connectionPath } from \"./cloud.js\";\nimport { cliCommand, hookVersion } from \"./version.js\";\nimport { fileURLToPath } from \"node:url\";\n\n/** The current git branch in `cwd` (best-effort). A bare `git push` pushes it, so\n * the runtime fills it in before evaluating; on failure the ref stays absent and\n * the starter policy fails closed. */\nfunction currentBranch(cwd: string): string | null {\n try {\n return execFileSync(\"git\", [\"-C\", cwd, \"symbolic-ref\", \"--quiet\", \"--short\", \"HEAD\"], {\n encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"],\n }).trim() || null;\n } catch { return null; }\n}\n\n/** Read an enrollment bundle from a file path, an inline base64 blob, or inline raw\n * JSON (the portal hands out a base64 blob so it is one clean argument). */\nfunction readBundleArg(bundleArg: string | undefined, stdin: () => string): CloudEnrollmentBundle {\n let text: string;\n if (!bundleArg) text = stdin();\n else if (existsSync(bundleArg)) text = readFileSync(bundleArg, \"utf8\");\n else if (bundleArg.trim().startsWith(\"{\")) text = bundleArg;\n else { try { text = Buffer.from(bundleArg, \"base64\").toString(\"utf8\"); } catch { text = bundleArg; } }\n return JSON.parse(text) as CloudEnrollmentBundle;\n}\n\nfunction configDir(): string {\n return process.env.SCOPEBOND_HOOK_DIR ?? join(process.cwd(), \".scopebond\");\n}\nfunction runtimePaths(dir: string) {\n const connection = loadConnection(dir);\n return {\n policyPath: join(dir, \"policy.json\"),\n keyPath: join(dir, \"agent.key\"),\n attesterPath: join(dir, \"attester.key\"),\n dbPath: join(dir, \"receipts.db\"),\n // Strict: deny (not just observe) tools with no taxonomy mapping.\n strict: process.argv.includes(\"--strict\") || process.env.SCOPEBOND_HOOK_STRICT === \"1\",\n // When connected, auto-export receipts. A short bounded flush keeps the hot path\n // fast; undelivered receipts persist in the durable outbox and flush next time\n // (or via `scopebond-hook flush`, e.g. on a session-end hook).\n ...(connection ? { cloud: { connection, flushTimeoutMs: Number(process.env.SCOPEBOND_HOOK_FLUSH_MS ?? 800) } } : {}),\n };\n}\nfunction readStdin(): string {\n try { return readFileSync(0, \"utf8\"); } catch { return \"\"; }\n}\n\nfunction denyClaude(reason: string): never {\n process.stdout.write(JSON.stringify({\n hookSpecificOutput: { hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: reason },\n }) + \"\\n\");\n process.stderr.write(`Scopebond: ${reason}\\n`);\n process.exit(2);\n}\n\nasync function runClaude(): Promise<void> {\n let input: Record<string, unknown>;\n try { input = JSON.parse(readStdin()); } catch { denyClaude(\"hook received invalid JSON on stdin\"); }\n try {\n const cwd = input?.cwd ? String(input.cwd) : process.cwd();\n const runtime = createHookRuntime(runtimePaths(resolveConfigDir(cwd)));\n const decision = await runtime.evaluate(fillPushBranch(mapClaudeToolUse(input!), currentBranch(cwd)));\n await runtime.flush();\n if (decision.decision === \"deny\") denyClaude(decision.reason);\n // allow and not_evaluated both DEFER to Claude Code's own permission flow: the\n // hook records the receipt but never returns permissionDecision:\"allow\", which\n // would suppress the user's normal review. Scopebond blocks; it does not approve.\n process.exit(0);\n } catch (error) {\n denyClaude(`Scopebond hook failed closed: ${(error as Error).message}. Repair: run \\`${cliCommand(\"init\")}\\`.`);\n }\n}\n\nasync function runCursor(): Promise<void> {\n let input: Record<string, unknown> = {};\n try { input = JSON.parse(readStdin()); } catch { /* fall through to fail-closed deny below */ }\n const event = String(input?.hook_event_name ?? input?.event ?? process.argv[3] ?? \"\");\n let permission: \"allow\" | \"deny\" | \"ask\" = \"deny\";\n let message = \"Scopebond hook failed closed\";\n try {\n const cwd = input?.cwd ? String(input.cwd) : process.cwd();\n const runtime = createHookRuntime(runtimePaths(resolveConfigDir(cwd)));\n const decision = await runtime.evaluate(fillPushBranch(mapCursorEvent(event, input), currentBranch(cwd)));\n await runtime.flush();\n // Only an out-of-policy action is denied outright; an allowed or unevaluated\n // action defers to Cursor's own prompt (\"ask\"), never a silent auto-allow.\n permission = decision.decision === \"deny\" ? \"deny\" : \"ask\";\n message = decision.reason;\n } catch (error) {\n permission = \"deny\";\n message = `Scopebond hook failed closed: ${(error as Error).message}. Repair: run \\`${cliCommand(\"init\")}\\`.`;\n }\n process.stdout.write(JSON.stringify({ permission, agentMessage: message }) + \"\\n\");\n process.exit(0);\n}\n\nfunction runInit(args: string[]): void {\n const harness = args.includes(\"--cursor\") ? \"cursor\" : \"claude\";\n const dir = configDir();\n const { agentKid, policyPath } = scaffold(dir, { force: args.includes(\"--force\") });\n console.log(`Scopebond hook enrolled in ${dir}`);\n console.log(` machine key ${agentKid}`);\n console.log(` policy ${policyPath} (starter — edit the limits)`);\n console.log(\"\");\n // Configure the agent automatically by default (idempotent), so there is no\n // hand-editing step; --no-install prints the snippet instead.\n if (!args.includes(\"--no-install\")) {\n const file = installHarness(harness);\n console.log(`✓ ${harness === \"cursor\" ? \"Cursor\" : \"Claude Code\"} configured in ${file}`);\n } else {\n console.log(`Add this to your ${harness === \"cursor\" ? \".cursor/hooks.json\" : \".claude/settings.json\"}:`);\n console.log(harnessSnippet(harness));\n }\n console.log(\"\");\n // Print the runnable `npx` form: after `npx @scopebond/hook init` there is no\n // `scopebond-hook` binary on PATH, so a bare `scopebond-hook log` would fail.\n console.log(`Next: run one command in the agent, then \\`${cliCommand(\"log\")}\\` to see the receipt`);\n console.log(`and \\`${cliCommand(\"verify\")}\\` to check it offline. Try \\`${cliCommand('test \"rm -rf /\"')}\\`.`);\n console.log(`To send receipts to a workspace: ${cliCommand(\"connect <workspace-url> <enrollment>\")}`);\n}\n\nfunction decisionOf(payload: Record<string, unknown>): string {\n const rr = String(payload.realtime_result ?? \"\");\n const state = String((payload.execution as Record<string, unknown> | undefined)?.state ?? \"\");\n if (state === \"observed_not_evaluated\") return \"not_evaluated\";\n if (rr === \"deny\") return state === \"cooperative_allow\" || state === \"executed\" ? \"monitor\" : \"deny\";\n return \"allow\";\n}\n\nfunction describeIntent(payload: Record<string, unknown>): string {\n const intent = (payload.intent ?? {}) as Record<string, unknown>;\n const p = (intent.params ?? {}) as Record<string, unknown>;\n const bits = intent.action_type === \"shell.exec\" ? String(p.program ?? \"\")\n : intent.action_type === \"git.push\" ? `${p.remote ?? \"\"} ${p.ref ?? \"\"}`.trim()\n : intent.action_type === \"file.write\" || intent.action_type === \"file.read\" ? String(p.path ?? \"\")\n : intent.action_type === \"mcp.tool.call\" ? `${p.server ?? \"\"}/${p.tool ?? \"\"}`\n : intent.action_type === \"net.fetch\" ? String(p.host ?? \"\") : \"\";\n return `${String(intent.action_type ?? \"?\")}${bits ? ` ${bits}` : \"\"}`;\n}\n\nasync function runLog(args: string[]): Promise<void> {\n const dir = resolveConfigDir(process.cwd());\n const dbPath = join(dir, \"receipts.db\");\n if (!existsSync(dbPath)) { console.log(\"no receipts yet — run a command in the agent first.\"); process.exit(0); }\n const nIdx = args.indexOf(\"-n\");\n const n = nIdx >= 0 ? Math.max(1, Number(args[nIdx + 1]) || 20) : 20;\n const { store } = openReceiptStore({ db: dbPath });\n const all = await Promise.resolve(store.list());\n const recent = all.slice(-n);\n if (recent.length === 0) { console.log(\"no receipts yet.\"); process.exit(0); }\n for (const r of recent) {\n const p = r.payload as unknown as Record<string, unknown>;\n console.log(`${String(p.timestamp ?? \"\")} ${decisionOf(p).padEnd(13)} ${describeIntent(p)}`);\n }\n console.log(`\\n${recent.length} of ${all.length} receipt(s). Verify them: ${cliCommand(\"verify\")}`);\n}\n\nasync function runVerify(): Promise<void> {\n const dir = resolveConfigDir(process.cwd());\n const dbPath = join(dir, \"receipts.db\");\n const attesterPath = join(dir, \"attester.key\");\n if (!existsSync(dbPath) || !existsSync(attesterPath)) { console.log(\"nothing to verify yet (no receipts or no attester key).\"); process.exit(0); }\n const { attester } = loadOrCreateAttester({ file: attesterPath });\n const { store } = openReceiptStore({ db: dbPath });\n const all = await Promise.resolve(store.list());\n let ok = 0;\n const bad: string[] = [];\n for (const r of all) {\n const result = verifyReceipt(r, attester.publicKeyPem) as unknown as Record<string, boolean>;\n if (result.valid) ok += 1;\n else {\n const failed = Object.entries(result).filter(([k, v]) => k.endsWith(\"_valid\") && v === false).map(([k]) => k);\n bad.push(`${String((r.payload as unknown as Record<string, unknown>).timestamp ?? \"\")}: ${failed.join(\", \") || \"invalid\"}`);\n }\n }\n console.log(`${ok}/${all.length} receipt(s) verify offline against ${attesterPath}.`);\n if (bad.length) { for (const b of bad) console.error(` ✗ ${b}`); process.exit(1); }\n process.exit(0);\n}\n\nasync function runTest(args: string[]): Promise<void> {\n const command = args.find((a) => !a.startsWith(\"-\"));\n if (!command) { console.error('usage: scopebond-hook test \"<shell command>\"'); process.exit(1); }\n const dir = resolveConfigDir(process.cwd());\n if (!existsSync(join(dir, \"policy.json\"))) { console.error(\"no policy yet — run `scopebond-hook init` first.\"); process.exit(1); }\n // Evaluate against the real policy and keys, but a throwaway store, so `test`\n // never records a receipt or exports anything.\n const tmp = mkdtempSync(join(tmpdir(), \"sb-hook-test-\"));\n try {\n const runtime = createHookRuntime({\n policyPath: join(dir, \"policy.json\"), keyPath: join(dir, \"agent.key\"),\n attesterPath: join(dir, \"attester.key\"), dbPath: join(tmp, \"receipts.db\"),\n strict: process.argv.includes(\"--strict\"),\n });\n const mapped = fillPushBranch(mapClaudeToolUse({ tool_name: \"Bash\", tool_input: { command } }), currentBranch(process.cwd()));\n console.log(`command: ${command}`);\n for (const m of mapped) {\n const d = await runtime.evaluateOne(m);\n console.log(` ${describeIntent({ intent: m.intent }).padEnd(28)} → ${d.decision}${d.reason ? ` (${d.reason})` : \"\"}`);\n }\n const overall = await runtime.evaluate(mapped);\n console.log(`\\noverall: ${overall.decision}${overall.reason ? ` · ${overall.reason}` : \"\"}`);\n process.exit(overall.decision === \"deny\" ? 2 : 0);\n } finally {\n rmSync(tmp, { recursive: true, force: true });\n }\n}\n\nasync function runConnect(args: string[]): Promise<void> {\n const positional = args.filter((a) => !a.startsWith(\"--\"));\n const url = positional[0];\n const bundleArg = positional[1];\n const harness = args.includes(\"--cursor\") ? \"cursor\" : \"claude\";\n if (!url) {\n console.error(\"usage: scopebond-hook connect <workspace-url> <enrollment> [--cursor] [--no-install]\");\n process.exit(1);\n }\n const dir = configDir();\n // One command sets everything up: scaffold the key, attester and starter policy if\n // they do not exist, then enroll and persist the scoped machine credential. The\n // enrollment can be a file, an inline base64 blob (what the portal hands out) or\n // raw JSON — the user never has to save or open a JSON file.\n scaffold(dir, {});\n let bundle: CloudEnrollmentBundle;\n try { bundle = readBundleArg(bundleArg, readStdin); }\n catch { console.error(\"could not read the enrollment (expected a file, inline blob, or JSON on stdin)\"); process.exit(1); }\n try {\n const c = await connectCloud(dir, url, bundle);\n console.log(`✓ Connected to ${c.url}`);\n // Configure the agent automatically (merges into the existing config), unless the\n // caller opts out. This removes the \"paste this snippet\" step.\n if (!args.includes(\"--no-install\")) {\n const file = installHarness(harness);\n console.log(`✓ ${harness === \"cursor\" ? \"Cursor\" : \"Claude Code\"} configured in ${file}`);\n } else {\n console.log(`Add this to your ${harness === \"cursor\" ? \".cursor/hooks.json\" : \".claude/settings.json\"}:`);\n console.log(harnessSnippet(harness));\n }\n console.log(\"\");\n console.log(\"Run your agent — the first action appears in your workspace within seconds.\");\n } catch (error) {\n console.error(`connect failed: ${(error as Error).message}`);\n process.exit(1);\n }\n}\n\nasync function runFlush(): Promise<void> {\n const dir = resolveConfigDir(process.cwd());\n if (!loadConnection(dir)) { console.error(\"not connected to a workspace; run `scopebond-hook connect` first\"); process.exit(1); }\n const runtime = createHookRuntime(runtimePaths(dir));\n await runtime.exporter?.flush();\n const status = runtime.exporter?.status();\n console.log(`flushed; ${status?.pending ?? 0} receipt(s) still pending${status?.lastError ? ` (last error: ${status.lastError})` : \"\"}`);\n process.exit(status && status.pending > 0 ? 1 : 0);\n}\n\n/** The absolute path to this CLI file, for registering the hook by absolute path. */\nfunction cliPath(): string {\n return fileURLToPath(import.meta.url);\n}\n\n/** `install` — the once-per-machine, user-level install (SB112). Scaffolds the\n * user home and registers the hook by absolute path in the user-level agent config,\n * so every project a developer opens is governed without a per-repo `init`. */\nfunction runInstall(args: string[]): void {\n const dir = userHome();\n const { agentKid, policyPath } = scaffold(dir, { force: args.includes(\"--force\") });\n console.log(`Scopebond installed for this user in ${dir}`);\n console.log(` machine key ${agentKid}`);\n console.log(` policy ${policyPath} (starter — edit the limits)`);\n console.log(\"\");\n const harnesses: Harness[] = args.includes(\"--cursor\")\n ? [\"cursor\"]\n : args.includes(\"--claude\") ? [\"claude\"] : (cursorDetected() ? [\"claude\", \"cursor\"] : [\"claude\"]);\n if (!args.includes(\"--no-install\")) {\n for (const h of harnesses) {\n const file = writeHarnessConfig(userHarnessFile(h), h, absoluteHookCommand(cliPath(), h));\n console.log(`✓ ${h === \"cursor\" ? \"Cursor\" : \"Claude Code\"} configured in ${file}`);\n }\n } else {\n console.log(\"Add this to your user-level agent config:\");\n console.log(harnessSnippet(harnesses[0]));\n }\n console.log(\"\");\n console.log(\"A project-local .scopebond still takes precedence when present.\");\n console.log(`Check it: ${cliCommand(\"doctor\")} · see decisions: ${cliCommand(\"log\")}`);\n console.log(`To send receipts to a workspace: ${cliCommand(\"connect <workspace-url> <enrollment>\")}`);\n}\n\nfunction runStatus(): void {\n const home = userHome();\n const installed = existsSync(join(home, \"policy.json\"));\n const claude = isHarnessConfigured(userHarnessFile(\"claude\"));\n const cursor = isHarnessConfigured(userHarnessFile(\"cursor\"));\n const connected = !!loadConnection(resolveConfigDir(process.cwd()));\n const dbPath = join(resolveConfigDir(process.cwd()), \"receipts.db\");\n console.log(`Scopebond hook ${hookVersion()}`);\n console.log(` user home ${home} ${installed ? \"(installed)\" : \"(not installed — run `scopebond install`)\"}`);\n console.log(` active config ${resolveConfigDir(process.cwd())}`);\n console.log(` Claude Code ${claude ? \"configured\" : \"not configured\"}`);\n console.log(` Cursor ${cursor ? \"configured\" : cursorDetected() ? \"detected, not configured\" : \"not detected\"}`);\n console.log(` cloud workspace ${connected ? \"connected\" : \"not connected (local only)\"}`);\n console.log(` local receipts ${existsSync(dbPath) ? dbPath : \"none yet\"}`);\n}\n\nasync function runDoctor(): Promise<void> {\n const problems: string[] = [];\n const [major, minor] = process.versions.node.split(\".\").map(Number);\n const nodeOk = major > 22 || (major === 22 && minor >= 13);\n console.log(`Scopebond doctor`);\n console.log(` node ${process.versions.node} ${nodeOk ? \"ok\" : \"TOO OLD (need >=22.13)\"}`);\n if (!nodeOk) problems.push(\"node >=22.13 is required (the Cloud outbox uses node:sqlite)\");\n const cli = cliPath();\n console.log(` cli ${cli} ${existsSync(cli) ? \"ok\" : \"MISSING\"}`);\n const active = resolveConfigDir(process.cwd());\n const hasPolicy = existsSync(join(active, \"policy.json\"));\n console.log(` active config ${active} ${hasPolicy ? \"ok\" : \"no policy (run `scopebond install` or `init`)\"}`);\n if (!hasPolicy) problems.push(\"no policy found in the active config dir\");\n const connection = loadConnection(active);\n if (!connection) {\n console.log(` cloud not connected (local only) — receipts stay on this machine`);\n } else {\n let reachable = \"unknown\";\n try {\n const res = await fetch(new URL(\"/healthz\", connection.url).toString(), { method: \"GET\" });\n reachable = res.ok ? \"reachable\" : `unhealthy (${res.status})`;\n } catch (error) { reachable = `unreachable (${(error as Error).message})`; }\n console.log(` cloud ${connection.url} — ${reachable}`);\n }\n console.log(problems.length ? `\\n${problems.length} problem(s): ${problems.join(\"; \")}` : `\\nAll good.`);\n process.exit(problems.length ? 1 : 0);\n}\n\nfunction runUninstall(args: string[]): void {\n let removed = 0;\n for (const h of [\"claude\", \"cursor\"] as Harness[]) {\n if (removeHarnessConfig(userHarnessFile(h))) { console.log(`✓ removed the Scopebond hook from ${userHarnessFile(h)}`); removed++; }\n }\n if (removed === 0) console.log(\"no user-level harness config found.\");\n if (args.includes(\"--purge\")) { purgeHome(); console.log(`✓ purged ${userHome()} (keys, policy, receipts)`); }\n else console.log(`Kept ${userHome()} (keys, policy, receipts). Use --purge to remove it too.`);\n}\n\nfunction runLogin(): void {\n console.log(\"Device-code login is not available yet.\");\n console.log(`For now, connect with a one-time enrollment from your workspace:`);\n console.log(` ${cliCommand(\"connect <workspace-url> <enrollment>\")}`);\n process.exit(0);\n}\n\nconst [cmd, ...rest] = process.argv.slice(2);\nif (cmd === \"claude\") { await runClaude(); }\nelse if (cmd === \"cursor\") { await runCursor(); }\nelse if (cmd === \"init\") { runInit(rest); }\nelse if (cmd === \"install\") { runInstall(rest); }\nelse if (cmd === \"connect\") { await runConnect(rest); }\nelse if (cmd === \"log\") { await runLog(rest); }\nelse if (cmd === \"verify\") { await runVerify(); }\nelse if (cmd === \"test\") { await runTest(rest); }\nelse if (cmd === \"flush\") { await runFlush(); }\nelse if (cmd === \"status\") { runStatus(); }\nelse if (cmd === \"doctor\") { await runDoctor(); }\nelse if (cmd === \"uninstall\") { runUninstall(rest); }\nelse if (cmd === \"login\") { runLogin(); }\nelse {\n console.error(\"usage: scopebond <claude|cursor|init|install|connect|log|verify|test|flush|status|doctor|uninstall|login> [--cursor] [--claude] [--force] [--strict] [--no-install] [--purge]\");\n process.exit(1);\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ export type { Mapped, NormalizedIntent } from "./map.js";
3
3
  export { createHookRuntime, starterPolicy } from "./runtime.js";
4
4
  export type { RuntimeConfig, Decision } from "./runtime.js";
5
5
  export { scaffold, harnessSnippet, installHarness } from "./init.js";
6
+ export { userHome, userHarnessFile, resolveConfigDir, writeHarnessConfig, removeHarnessConfig, cursorDetected, absoluteHookCommand, isHarnessConfigured, purgeHome, } from "./install.js";
7
+ export type { Harness } from "./install.js";
6
8
  export { connectCloud, loadConnection, attachExporter, flushBounded, connectionPath } from "./cloud.js";
7
9
  export type { HookConnection } from "./cloud.js";
8
10
  export { scrubSecrets, scrubParam, redactCommand, digest, sha256 } from "./minimize.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC5E,YAAY,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACxG,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC5E,YAAY,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACzD,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EACL,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,mBAAmB,EACpF,cAAc,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,SAAS,GACpE,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACxG,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC"}
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@
4
4
  export { mapClaudeToolUse, mapCursorEvent, fillPushBranch } from "./map.js";
5
5
  export { createHookRuntime, starterPolicy } from "./runtime.js";
6
6
  export { scaffold, harnessSnippet, installHarness } from "./init.js";
7
+ export { userHome, userHarnessFile, resolveConfigDir, writeHarnessConfig, removeHarnessConfig, cursorDetected, absoluteHookCommand, isHarnessConfigured, purgeHome, } from "./install.js";
7
8
  export { connectCloud, loadConnection, attachExporter, flushBounded, connectionPath } from "./cloud.js";
8
9
  export { scrubSecrets, scrubParam, redactCommand, digest, sha256 } from "./minimize.js";
9
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,4EAA4E;AAC5E,gDAAgD;AAEhD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE5E,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAExG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC","sourcesContent":["// @scopebond/hook — the Claude Code + Cursor connector. The library surface is\n// the deterministic mapper and the check-only runtime; the `scopebond-hook`\n// binary is the thin harness adapter over them.\n\nexport { mapClaudeToolUse, mapCursorEvent, fillPushBranch } from \"./map.js\";\nexport type { Mapped, NormalizedIntent } from \"./map.js\";\nexport { createHookRuntime, starterPolicy } from \"./runtime.js\";\nexport type { RuntimeConfig, Decision } from \"./runtime.js\";\nexport { scaffold, harnessSnippet, installHarness } from \"./init.js\";\nexport { connectCloud, loadConnection, attachExporter, flushBounded, connectionPath } from \"./cloud.js\";\nexport type { HookConnection } from \"./cloud.js\";\nexport { scrubSecrets, scrubParam, redactCommand, digest, sha256 } from \"./minimize.js\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,4EAA4E;AAC5E,gDAAgD;AAEhD,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE5E,OAAO,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EACL,QAAQ,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,mBAAmB,EACpF,cAAc,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,SAAS,GACpE,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,cAAc,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAExG,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC","sourcesContent":["// @scopebond/hook — the Claude Code + Cursor connector. The library surface is\n// the deterministic mapper and the check-only runtime; the `scopebond-hook`\n// binary is the thin harness adapter over them.\n\nexport { mapClaudeToolUse, mapCursorEvent, fillPushBranch } from \"./map.js\";\nexport type { Mapped, NormalizedIntent } from \"./map.js\";\nexport { createHookRuntime, starterPolicy } from \"./runtime.js\";\nexport type { RuntimeConfig, Decision } from \"./runtime.js\";\nexport { scaffold, harnessSnippet, installHarness } from \"./init.js\";\nexport {\n userHome, userHarnessFile, resolveConfigDir, writeHarnessConfig, removeHarnessConfig,\n cursorDetected, absoluteHookCommand, isHarnessConfigured, purgeHome,\n} from \"./install.js\";\nexport type { Harness } from \"./install.js\";\nexport { connectCloud, loadConnection, attachExporter, flushBounded, connectionPath } from \"./cloud.js\";\nexport type { HookConnection } from \"./cloud.js\";\nexport { scrubSecrets, scrubParam, redactCommand, digest, sha256 } from \"./minimize.js\";\n"]}
@@ -0,0 +1,26 @@
1
+ export type Harness = "claude" | "cursor";
2
+ /** The user-level Scopebond home. `SCOPEBOND_HOME` overrides it (tests, CI). */
3
+ export declare function userHome(): string;
4
+ /** The user-level agent config file for a harness. `HOME`/`USERPROFILE` (via homedir)
5
+ * roots it; SCOPEBOND_HOME does not move the agent's own config. */
6
+ export declare function userHarnessFile(harness: Harness): string;
7
+ /** Resolve the config dir for a hook event, most specific first:
8
+ * explicit override → the payload's project → $CLAUDE_PROJECT_DIR → the user home.
9
+ * A project dir counts only when it has been scaffolded (a policy.json), so a bare
10
+ * working directory never shadows the user-level install. */
11
+ export declare function resolveConfigDir(payloadCwd: string | undefined): string;
12
+ /** Write (idempotently) a harness hook entry pointing at an explicit command string.
13
+ * Shared by the project installer (npx command) and the user installer (absolute path). */
14
+ export declare function writeHarnessConfig(file: string, harness: Harness, command: string): string;
15
+ /** Remove any Scopebond hook entry from a user-level harness config, leaving the rest
16
+ * intact. Returns true if the file existed. */
17
+ export declare function removeHarnessConfig(file: string): boolean;
18
+ /** Detect a Cursor install by its user config directory. */
19
+ export declare function cursorDetected(): boolean;
20
+ /** The absolute command a user-level harness entry runs: the current Node executable
21
+ * and the absolute path to this CLI, so it does not depend on PATH. */
22
+ export declare function absoluteHookCommand(cliPath: string, harness: Harness): string;
23
+ export declare function isHarnessConfigured(file: string): boolean;
24
+ /** Remove the user-level home (keys, policy, receipts). */
25
+ export declare function purgeHome(): void;
26
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAUA,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE1C,gFAAgF;AAChF,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED;qEACqE;AACrE,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAExD;AAED;;;8DAG8D;AAC9D,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAYvE;AAaD;4FAC4F;AAC5F,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAqB1F;AAED;gDACgD;AAChD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAUzD;AAED,4DAA4D;AAC5D,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;wEACwE;AACxE,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CAG7E;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAOzD;AAED,2DAA2D;AAC3D,wBAAgB,SAAS,IAAI,IAAI,CAEhC"}
@@ -0,0 +1,132 @@
1
+ // SB112 — the user-level installer. One install per developer machine (not per repo):
2
+ // keys and a starter policy live in a user-level home (~/.scopebond, override
3
+ // SCOPEBOND_HOME), and the hook is registered by absolute path in the user-level agent
4
+ // config (~/.claude/settings.json, ~/.cursor/hooks.json). A project-local `.scopebond`
5
+ // still wins when present, so per-project policies keep working.
6
+ import { mkdirSync, writeFileSync, existsSync, readFileSync, rmSync } from "node:fs";
7
+ import { homedir } from "node:os";
8
+ import { dirname, join } from "node:path";
9
+ /** The user-level Scopebond home. `SCOPEBOND_HOME` overrides it (tests, CI). */
10
+ export function userHome() {
11
+ return process.env.SCOPEBOND_HOME ?? join(homedir(), ".scopebond");
12
+ }
13
+ /** The user-level agent config file for a harness. `HOME`/`USERPROFILE` (via homedir)
14
+ * roots it; SCOPEBOND_HOME does not move the agent's own config. */
15
+ export function userHarnessFile(harness) {
16
+ return harness === "cursor" ? join(homedir(), ".cursor", "hooks.json") : join(homedir(), ".claude", "settings.json");
17
+ }
18
+ /** Resolve the config dir for a hook event, most specific first:
19
+ * explicit override → the payload's project → $CLAUDE_PROJECT_DIR → the user home.
20
+ * A project dir counts only when it has been scaffolded (a policy.json), so a bare
21
+ * working directory never shadows the user-level install. */
22
+ export function resolveConfigDir(payloadCwd) {
23
+ if (process.env.SCOPEBOND_HOOK_DIR)
24
+ return process.env.SCOPEBOND_HOOK_DIR;
25
+ const candidates = [];
26
+ if (payloadCwd)
27
+ candidates.push(join(payloadCwd, ".scopebond"));
28
+ if (process.env.CLAUDE_PROJECT_DIR)
29
+ candidates.push(join(process.env.CLAUDE_PROJECT_DIR, ".scopebond"));
30
+ for (const dir of candidates) {
31
+ if (existsSync(join(dir, "policy.json")))
32
+ return dir;
33
+ }
34
+ const home = userHome();
35
+ if (existsSync(join(home, "policy.json")))
36
+ return home;
37
+ // Nothing scaffolded yet: prefer the payload project, else the user home.
38
+ return candidates[0] ?? home;
39
+ }
40
+ const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
41
+ const isScopebond = (cmd) => typeof cmd === "string" && (/@scopebond\/hook|scopebond-hook(\s|$)/.test(cmd) ||
42
+ // the user-level absolute form: `<node> <…/cli.js> claude|cursor`
43
+ /cli\.js["']?\s+(claude|cursor)\s*$/.test(cmd) ||
44
+ /(^|["\s])scopebond(["'\s]).*\b(claude|cursor)\b/.test(cmd));
45
+ const entryMatches = (e) => isRecord(e) && (isScopebond(e.command) || (Array.isArray(e.hooks) && e.hooks.some((h) => isRecord(h) && isScopebond(h.command))));
46
+ /** Write (idempotently) a harness hook entry pointing at an explicit command string.
47
+ * Shared by the project installer (npx command) and the user installer (absolute path). */
48
+ export function writeHarnessConfig(file, harness, command) {
49
+ mkdirSync(dirname(file), { recursive: true });
50
+ let config = {};
51
+ if (existsSync(file)) {
52
+ try {
53
+ const p = JSON.parse(readFileSync(file, "utf8"));
54
+ if (isRecord(p))
55
+ config = p;
56
+ }
57
+ catch { /* start fresh on unreadable */ }
58
+ }
59
+ const hooks = isRecord(config.hooks) ? config.hooks : (config.hooks = {});
60
+ if (harness === "cursor") {
61
+ config.version = config.version ?? 1;
62
+ for (const event of ["beforeShellExecution", "beforeMCPExecution", "beforeReadFile", "afterFileEdit"]) {
63
+ const list = Array.isArray(hooks[event]) ? hooks[event] : (hooks[event] = []);
64
+ const at = list.findIndex(entryMatches);
65
+ const entry = { command: command.replace(/\bclaude$/, "cursor") };
66
+ if (at >= 0)
67
+ list[at] = entry;
68
+ else
69
+ list.push(entry);
70
+ }
71
+ }
72
+ else {
73
+ const list = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : (hooks.PreToolUse = []);
74
+ const at = list.findIndex(entryMatches);
75
+ const entry = { matcher: "*", hooks: [{ type: "command", command }] };
76
+ if (at >= 0)
77
+ list[at] = entry;
78
+ else
79
+ list.push(entry);
80
+ }
81
+ writeFileSync(file, JSON.stringify(config, null, 2) + "\n");
82
+ return file;
83
+ }
84
+ /** Remove any Scopebond hook entry from a user-level harness config, leaving the rest
85
+ * intact. Returns true if the file existed. */
86
+ export function removeHarnessConfig(file) {
87
+ if (!existsSync(file))
88
+ return false;
89
+ let config;
90
+ try {
91
+ const p = JSON.parse(readFileSync(file, "utf8"));
92
+ config = isRecord(p) ? p : {};
93
+ }
94
+ catch {
95
+ return true;
96
+ }
97
+ const hooks = isRecord(config.hooks) ? config.hooks : {};
98
+ for (const [event, value] of Object.entries(hooks)) {
99
+ if (Array.isArray(value))
100
+ hooks[event] = value.filter((e) => !entryMatches(e));
101
+ }
102
+ writeFileSync(file, JSON.stringify(config, null, 2) + "\n");
103
+ return true;
104
+ }
105
+ /** Detect a Cursor install by its user config directory. */
106
+ export function cursorDetected() {
107
+ return existsSync(join(homedir(), ".cursor"));
108
+ }
109
+ /** The absolute command a user-level harness entry runs: the current Node executable
110
+ * and the absolute path to this CLI, so it does not depend on PATH. */
111
+ export function absoluteHookCommand(cliPath, harness) {
112
+ const quote = (s) => (/\s/.test(s) ? `"${s}"` : s);
113
+ return `${quote(process.execPath)} ${quote(cliPath)} ${harness}`;
114
+ }
115
+ export function isHarnessConfigured(file) {
116
+ if (!existsSync(file))
117
+ return false;
118
+ try {
119
+ const p = JSON.parse(readFileSync(file, "utf8"));
120
+ if (!isRecord(p) || !isRecord(p.hooks))
121
+ return false;
122
+ return Object.values(p.hooks).some((v) => Array.isArray(v) && v.some(entryMatches));
123
+ }
124
+ catch {
125
+ return false;
126
+ }
127
+ }
128
+ /** Remove the user-level home (keys, policy, receipts). */
129
+ export function purgeHome() {
130
+ rmSync(userHome(), { recursive: true, force: true });
131
+ }
132
+ //# sourceMappingURL=install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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","sourcesContent":["// SB112 — the user-level installer. One install per developer machine (not per repo):\n// keys and a starter policy live in a user-level home (~/.scopebond, override\n// SCOPEBOND_HOME), and the hook is registered by absolute path in the user-level agent\n// config (~/.claude/settings.json, ~/.cursor/hooks.json). A project-local `.scopebond`\n// still wins when present, so per-project policies keep working.\n\nimport { mkdirSync, writeFileSync, existsSync, readFileSync, rmSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nexport type Harness = \"claude\" | \"cursor\";\n\n/** The user-level Scopebond home. `SCOPEBOND_HOME` overrides it (tests, CI). */\nexport function userHome(): string {\n return process.env.SCOPEBOND_HOME ?? join(homedir(), \".scopebond\");\n}\n\n/** The user-level agent config file for a harness. `HOME`/`USERPROFILE` (via homedir)\n * roots it; SCOPEBOND_HOME does not move the agent's own config. */\nexport function userHarnessFile(harness: Harness): string {\n return harness === \"cursor\" ? join(homedir(), \".cursor\", \"hooks.json\") : join(homedir(), \".claude\", \"settings.json\");\n}\n\n/** Resolve the config dir for a hook event, most specific first:\n * explicit override → the payload's project → $CLAUDE_PROJECT_DIR → the user home.\n * A project dir counts only when it has been scaffolded (a policy.json), so a bare\n * working directory never shadows the user-level install. */\nexport function resolveConfigDir(payloadCwd: string | undefined): string {\n if (process.env.SCOPEBOND_HOOK_DIR) return process.env.SCOPEBOND_HOOK_DIR;\n const candidates: string[] = [];\n if (payloadCwd) candidates.push(join(payloadCwd, \".scopebond\"));\n if (process.env.CLAUDE_PROJECT_DIR) candidates.push(join(process.env.CLAUDE_PROJECT_DIR, \".scopebond\"));\n for (const dir of candidates) {\n if (existsSync(join(dir, \"policy.json\"))) return dir;\n }\n const home = userHome();\n if (existsSync(join(home, \"policy.json\"))) return home;\n // Nothing scaffolded yet: prefer the payload project, else the user home.\n return candidates[0] ?? home;\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> => typeof v === \"object\" && v !== null && !Array.isArray(v);\nconst isScopebond = (cmd: unknown): boolean =>\n typeof cmd === \"string\" && (\n /@scopebond\\/hook|scopebond-hook(\\s|$)/.test(cmd) ||\n // the user-level absolute form: `<node> <…/cli.js> claude|cursor`\n /cli\\.js[\"']?\\s+(claude|cursor)\\s*$/.test(cmd) ||\n /(^|[\"\\s])scopebond([\"'\\s]).*\\b(claude|cursor)\\b/.test(cmd)\n );\nconst entryMatches = (e: unknown): boolean =>\n isRecord(e) && (isScopebond(e.command) || (Array.isArray(e.hooks) && e.hooks.some((h) => isRecord(h) && isScopebond(h.command))));\n\n/** Write (idempotently) a harness hook entry pointing at an explicit command string.\n * Shared by the project installer (npx command) and the user installer (absolute path). */\nexport function writeHarnessConfig(file: string, harness: Harness, command: string): string {\n mkdirSync(dirname(file), { recursive: true });\n let config: Record<string, unknown> = {};\n if (existsSync(file)) { try { const p = JSON.parse(readFileSync(file, \"utf8\")); if (isRecord(p)) config = p; } catch { /* start fresh on unreadable */ } }\n const hooks = isRecord(config.hooks) ? config.hooks : (config.hooks = {});\n if (harness === \"cursor\") {\n config.version = config.version ?? 1;\n for (const event of [\"beforeShellExecution\", \"beforeMCPExecution\", \"beforeReadFile\", \"afterFileEdit\"]) {\n const list = Array.isArray((hooks as Record<string, unknown>)[event]) ? (hooks as Record<string, unknown[]>)[event] : ((hooks as Record<string, unknown[]>)[event] = []);\n const at = list.findIndex(entryMatches);\n const entry = { command: command.replace(/\\bclaude$/, \"cursor\") };\n if (at >= 0) list[at] = entry; else list.push(entry);\n }\n } else {\n const list = Array.isArray((hooks as Record<string, unknown>).PreToolUse) ? (hooks as Record<string, unknown[]>).PreToolUse : ((hooks as Record<string, unknown[]>).PreToolUse = []);\n const at = list.findIndex(entryMatches);\n const entry = { matcher: \"*\", hooks: [{ type: \"command\", command }] };\n if (at >= 0) list[at] = entry; else list.push(entry);\n }\n writeFileSync(file, JSON.stringify(config, null, 2) + \"\\n\");\n return file;\n}\n\n/** Remove any Scopebond hook entry from a user-level harness config, leaving the rest\n * intact. Returns true if the file existed. */\nexport function removeHarnessConfig(file: string): boolean {\n if (!existsSync(file)) return false;\n let config: Record<string, unknown>;\n try { const p = JSON.parse(readFileSync(file, \"utf8\")); config = isRecord(p) ? p : {}; } catch { return true; }\n const hooks = isRecord(config.hooks) ? config.hooks : {};\n for (const [event, value] of Object.entries(hooks)) {\n if (Array.isArray(value)) (hooks as Record<string, unknown[]>)[event] = value.filter((e) => !entryMatches(e));\n }\n writeFileSync(file, JSON.stringify(config, null, 2) + \"\\n\");\n return true;\n}\n\n/** Detect a Cursor install by its user config directory. */\nexport function cursorDetected(): boolean {\n return existsSync(join(homedir(), \".cursor\"));\n}\n\n/** The absolute command a user-level harness entry runs: the current Node executable\n * and the absolute path to this CLI, so it does not depend on PATH. */\nexport function absoluteHookCommand(cliPath: string, harness: Harness): string {\n const quote = (s: string) => (/\\s/.test(s) ? `\"${s}\"` : s);\n return `${quote(process.execPath)} ${quote(cliPath)} ${harness}`;\n}\n\nexport function isHarnessConfigured(file: string): boolean {\n if (!existsSync(file)) return false;\n try {\n const p = JSON.parse(readFileSync(file, \"utf8\"));\n if (!isRecord(p) || !isRecord(p.hooks)) return false;\n return Object.values(p.hooks).some((v) => Array.isArray(v) && v.some(entryMatches));\n } catch { return false; }\n}\n\n/** Remove the user-level home (keys, policy, receipts). */\nexport function purgeHome(): void {\n rmSync(userHome(), { recursive: true, force: true });\n}\n"]}
package/dist/map.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AACD,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,gBAAgB,CAAC;IACzB;;kFAE8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;CAChB;AA+DD;;;uCAGuC;AACvC,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAM5F;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAkBzE;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAkBxF"}
1
+ {"version":3,"file":"map.d.ts","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC;AACD,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,gBAAgB,CAAC;IACzB;;kFAE8E;IAC9E,SAAS,EAAE,OAAO,CAAC;IACnB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC;CAChB;AAuGD;;;uCAGuC;AACvC,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAM5F;AAED,oFAAoF;AACpF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAuBzE;AAED,uEAAuE;AACvE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,CAkBxF"}
package/dist/map.js CHANGED
@@ -9,20 +9,58 @@
9
9
  import { digest, redactCommand, scrubParam, scrubSecrets } from "./minimize.js";
10
10
  import { decomposeShell, parseGitPush } from "./shell.js";
11
11
  const basename = (t) => t.replace(/^.*[\\/]/, "");
12
+ // Normalize Windows backslash separators to "/" so a path guard written with "/"
13
+ // (the hook's own .scopebond/**, .claude/settings*, *.key rules) cannot be bypassed
14
+ // on Windows by a backslash path. The taxonomy path space is "/"-separated.
15
+ const normPath = (s) => s.replace(/\\/g, "/");
12
16
  const rel = (value, cwd) => {
13
- const s = String(value ?? "");
14
- if (cwd && s.startsWith(cwd))
15
- return s.slice(cwd.length).replace(/^[\\/]+/, "");
17
+ const s = normPath(String(value ?? ""));
18
+ const c = cwd ? normPath(cwd) : undefined;
19
+ if (c && s.startsWith(c))
20
+ return s.slice(c.length).replace(/^\/+/, "");
16
21
  return s;
17
22
  };
18
- /** Map one parsed simple command to a git.push or shell.exec intent. An opaque
19
- * (unparseable) command becomes an un-evaluated shell.exec so it fails closed. */
23
+ // Shell programs that READ a file given as an operand. A read of the signing keys
24
+ // or a secret file through the shell (`cat .scopebond/attester.key`) must reach the
25
+ // same file.read guard as the Read tool, or the cooperative protection is a fiction.
26
+ const READERS = new Set([
27
+ "cat", "tac", "less", "more", "head", "tail", "nl", "od", "xxd", "hexdump",
28
+ "strings", "base64", "bat", "type", "get-content", "gc",
29
+ ]);
30
+ /** Derive the additional file.read / file.write intents a simple shell command
31
+ * implies: operands of a reader program, and the targets of `>`/`>>` redirections.
32
+ * These flow through the same protect-read / protect-write clauses as the native
33
+ * file tools. A false positive (a non-protected operand) is a harmless extra
34
+ * receipt that the starter policy allows. */
35
+ function fileOpsFromShell(sc, cwd) {
36
+ const ops = [];
37
+ if (READERS.has(sc.program.toLowerCase())) {
38
+ for (const t of sc.argv) {
39
+ if (t.startsWith("-") || /^\d+$/.test(t))
40
+ continue; // skip flags and flag values (head -n 5)
41
+ ops.push({ intent: { action_type: "file.read", params: { path: rel(scrubParam(t), cwd) } }, evaluated: true, source: "shell" });
42
+ }
43
+ }
44
+ for (let i = 0; i < sc.argv.length; i++) {
45
+ // `> file`, `>>file`, `2>file`, `&>file` — the token after the redirection op is a write target.
46
+ const m = /^(?:\d|&)?(>>?)(.*)$/.exec(sc.argv[i]);
47
+ if (!m)
48
+ continue;
49
+ const target = m[2] || sc.argv[i + 1];
50
+ if (target && !target.startsWith("-") && !/^(?:\d|&)?>>?/.test(target))
51
+ ops.push({ intent: { action_type: "file.write", params: { path: rel(scrubParam(target), cwd) } }, evaluated: true, source: "shell" });
52
+ }
53
+ return ops;
54
+ }
55
+ /** Map one parsed simple command to the intents it implies: the git.push or
56
+ * shell.exec itself, plus any file reads/writes it performs. An opaque
57
+ * (unparseable) command becomes a single un-evaluated shell.exec so it fails closed. */
20
58
  function mapSimpleCommand(sc, cwd) {
21
59
  if (sc.opaque) {
22
- return {
23
- intent: { action_type: "shell.exec", params: { command: redactCommand(sc.raw), program: "", ...(cwd ? { cwd } : {}) } },
24
- evaluated: false, source: "shell",
25
- };
60
+ return [{
61
+ intent: { action_type: "shell.exec", params: { command: redactCommand(sc.raw), program: "", ...(cwd ? { cwd } : {}) } },
62
+ evaluated: false, source: "shell",
63
+ }];
26
64
  }
27
65
  const push = parseGitPush(sc);
28
66
  if (push) {
@@ -31,9 +69,9 @@ function mapSimpleCommand(sc, cwd) {
31
69
  params.remote = scrubParam(push.remote);
32
70
  if (push.ref !== undefined)
33
71
  params.ref = scrubParam(push.ref);
34
- return { intent: { action_type: "git.push", params }, evaluated: true, source: "shell" };
72
+ return [{ intent: { action_type: "git.push", params }, evaluated: true, source: "shell" }];
35
73
  }
36
- return {
74
+ const exec = {
37
75
  intent: {
38
76
  action_type: "shell.exec",
39
77
  // Scrub before storing: the raw command through the blob-aware scrubber, and
@@ -43,15 +81,16 @@ function mapSimpleCommand(sc, cwd) {
43
81
  },
44
82
  evaluated: true, source: "shell",
45
83
  };
84
+ return [exec, ...fileOpsFromShell(sc, cwd)];
46
85
  }
47
- /** Decompose a shell command into one intent per simple command it will run. An
48
- * empty command yields a single un-evaluated placeholder (nothing to grant). */
86
+ /** Decompose a shell command into the intents it will run. An empty command yields
87
+ * a single un-evaluated placeholder (nothing to grant). */
49
88
  function mapShell(command, cwd) {
50
89
  const commands = decomposeShell(command);
51
90
  if (commands.length === 0) {
52
91
  return [{ intent: { action_type: "shell.exec", params: { command: redactCommand(command), program: "" } }, evaluated: false, source: "shell" }];
53
92
  }
54
- return commands.map((sc) => mapSimpleCommand(sc, cwd));
93
+ return commands.flatMap((sc) => mapSimpleCommand(sc, cwd));
55
94
  }
56
95
  function parseMcpName(name) {
57
96
  if (!name.startsWith("mcp__"))
@@ -88,7 +127,11 @@ export function mapClaudeToolUse(input) {
88
127
  const name = String(input?.tool_name ?? "");
89
128
  const ti = (input?.tool_input ?? {});
90
129
  const cwd = input?.cwd ? String(input.cwd) : undefined;
91
- if (name === "Bash")
130
+ // Every shell-executing tool decomposes the same way. Claude Code exposes Bash;
131
+ // some hosts/agents expose PowerShell or a generic Shell tool — mapping only Bash
132
+ // let a PowerShell command (e.g. `Remove-Item -Recurse -Force .`) fall through to
133
+ // an un-evaluated tool.<name> and be allowed.
134
+ if (name === "Bash" || name === "PowerShell" || name === "Shell")
92
135
  return mapShell(String(ti.command ?? ""), cwd);
93
136
  if (name === "Write" || name === "Edit" || name === "MultiEdit")
94
137
  return one({ action_type: "file.write", params: { path: rel(ti.file_path, cwd) } }, true, name);
package/dist/map.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"map.js","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,iFAAiF;AACjF,kEAAkE;AAClE,EAAE;AACF,kFAAkF;AAClF,6EAA6E;AAC7E,kFAAkF;AAClF,gDAAgD;AAEhD,OAAO,EAAE,MAAM,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,cAAc,EAAE,YAAY,EAAsB,MAAM,YAAY,CAAC;AAgB9E,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;AAElE,MAAM,GAAG,GAAG,CAAC,KAAc,EAAE,GAAY,EAAU,EAAE;IACnD,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAC9B,IAAI,GAAG,IAAI,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAChF,OAAO,CAAC,CAAC;AACX,CAAC,CAAC;AAEF;mFACmF;AACnF,SAAS,gBAAgB,CAAC,EAAiB,EAAE,GAAY;IACvD,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC;QACd,OAAO;YACL,MAAM,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACvH,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO;SAClC,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,YAAY,CAAC,EAAE,CAAC,CAAC;IAC9B,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,MAAM,GAA4B,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9D,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,MAAM,CAAC,MAAM,GAAG,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACvE,IAAI,IAAI,CAAC,GAAG,KAAK,SAAS;YAAE,MAAM,CAAC,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9D,OAAO,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IAC3F,CAAC;IACD,OAAO;QACL,MAAM,EAAE;YACN,WAAW,EAAE,YAAY;YACzB,6EAA6E;YAC7E,6EAA6E;YAC7E,6DAA6D;YAC7D,MAAM,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE;SACpH;QACD,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO;KACjC,CAAC;AACJ,CAAC;AAED;iFACiF;AACjF,SAAS,QAAQ,CAAC,OAAe,EAAE,GAAY;IAC7C,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACzC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,CAAC;IAClJ,CAAC;IACD,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,gBAAgB,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC;AACzD,CAAC;AAED,SAAS,YAAY,CAAC,IAAY;IAChC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC/D,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW;IAC3B,sFAAsF;IACtF,IAAI,CAAC;QAAC,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC;IAAC,CAAC;IACtF,MAAM,CAAC;QAAC,OAAO,EAAE,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC;IAAC,CAAC;AACvD,CAAC;AAED,MAAM,GAAG,GAAG,CAAC,MAAwB,EAAE,SAAkB,EAAE,MAAc,EAAY,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;AAExH;;;uCAGuC;AACvC,MAAM,UAAU,cAAc,CAAC,MAAgB,EAAE,MAAiC;IAChF,IAAI,CAAC,MAAM;QAAE,OAAO,MAAM,CAAC;IAC3B,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACtB,CAAC,CAAC,MAAM,CAAC,WAAW,KAAK,UAAU,IAAI,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,KAAK,SAAS;QACtE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE;QAC5F,CAAC,CAAC,CAAC,CAAC,CAAC;AACX,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,gBAAgB,CAAC,KAA8B;IAC7D,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,EAAE,SAAS,IAAI,EAAE,CAAC,CAAC;IAC5C,MAAM,EAAE,GAAG,CAAC,KAAK,EAAE,UAAU,IAAI,EAAE,CAA4B,CAAC;IAChE,MAAM,GAAG,GAAG,KAAK,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACvD,IAAI,IAAI,KAAK,MAAM;QAAE,OAAO,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;IACpE,IAAI,IAAI,KAAK,OAAO,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,WAAW;QAC7D,OAAO,GAAG,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAClG,IAAI,IAAI,KAAK,cAAc;QACzB,OAAO,GAAG,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,aAAa,IAAI,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACtH,IAAI,IAAI,KAAK,MAAM;QACjB,OAAO,GAAG,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACjG,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;QACxB,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,CAAC,CAAC;QACtD,OAAO,GAAG,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9F,CAAC;IACD,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC3I,OAAO,GAAG,CAAC,EAAE,WAAW,EAAE,QAAQ,IAAI,CAAC,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;AACrF,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,OAAgC;IAC5E,MAAM,CAAC,GAAG,OAAO,IAAI,EAAE,CAAC;IACxB,MAAM,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC9C,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,sBAAsB;YACzB,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC;QAChD,KAAK,gBAAgB;YACnB,OAAO,GAAG,CAAC,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAC3G,KAAK,eAAe;YAClB,OAAO,GAAG,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,EAAE,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAC5G,KAAK,oBAAoB,CAAC,CAAC,CAAC;YAC1B,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC;YACvD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;YACjD,OAAO,GAAG,CAAC,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACxI,CAAC;QACD;YACE,OAAO,GAAG,CAAC,EAAE,WAAW,EAAE,QAAQ,KAAK,CAAC,WAAW,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACzF,CAAC;AACH,CAAC","sourcesContent":["// The mapper: a pure function from a coding agent's native tool call to the\n// normalized Scopebond actions (Action Taxonomy v1) it represents. Deterministic\n// and side-effect free, so it can be conformance-tested directly.\n//\n// A shell tool call can carry several commands (`a && b`, `$(c)`, `bash -c '…'`);\n// the mapper decomposes it and returns one intent per simple command, so the\n// runtime evaluates every one and denies if any is out of policy. Non-shell tools\n// map to a single intent (a one-element array).\n\nimport { digest, redactCommand, scrubParam, scrubSecrets } from \"./minimize.js\";\nimport { decomposeShell, parseGitPush, type SimpleCommand } from \"./shell.js\";\n\nexport interface NormalizedIntent {\n action_type: string;\n params: Record<string, unknown>;\n}\nexport interface Mapped {\n intent: NormalizedIntent;\n /** false when no taxonomy type applies (an unknown tool, or a shell command that\n * could not be parsed): emitted to the observation path (not_evaluated) in normal\n * mode and denied by the closed allowlist in strict mode — never granted. */\n evaluated: boolean;\n /** The native tool/event name, for diagnostics. */\n source: string;\n}\n\nconst basename = (t: string): string => t.replace(/^.*[\\\\/]/, \"\");\n\nconst rel = (value: unknown, cwd?: string): string => {\n const s = String(value ?? \"\");\n if (cwd && s.startsWith(cwd)) return s.slice(cwd.length).replace(/^[\\\\/]+/, \"\");\n return s;\n};\n\n/** Map one parsed simple command to a git.push or shell.exec intent. An opaque\n * (unparseable) command becomes an un-evaluated shell.exec so it fails closed. */\nfunction mapSimpleCommand(sc: SimpleCommand, cwd?: string): Mapped {\n if (sc.opaque) {\n return {\n intent: { action_type: \"shell.exec\", params: { command: redactCommand(sc.raw), program: \"\", ...(cwd ? { cwd } : {}) } },\n evaluated: false, source: \"shell\",\n };\n }\n const push = parseGitPush(sc);\n if (push) {\n const params: Record<string, unknown> = { force: push.force };\n if (push.remote !== undefined) params.remote = scrubParam(push.remote);\n if (push.ref !== undefined) params.ref = scrubParam(push.ref);\n return { intent: { action_type: \"git.push\", params }, evaluated: true, source: \"shell\" };\n }\n return {\n intent: {\n action_type: \"shell.exec\",\n // Scrub before storing: the raw command through the blob-aware scrubber, and\n // the whole first token BEFORE taking its basename, so a bare-secret command\n // containing \"/\" cannot leak a path-fragment as the program.\n params: { command: redactCommand(sc.raw), program: basename(scrubSecrets(sc.programRaw)), ...(cwd ? { cwd } : {}) },\n },\n evaluated: true, source: \"shell\",\n };\n}\n\n/** Decompose a shell command into one intent per simple command it will run. An\n * empty command yields a single un-evaluated placeholder (nothing to grant). */\nfunction mapShell(command: string, cwd?: string): Mapped[] {\n const commands = decomposeShell(command);\n if (commands.length === 0) {\n return [{ intent: { action_type: \"shell.exec\", params: { command: redactCommand(command), program: \"\" } }, evaluated: false, source: \"shell\" }];\n }\n return commands.map((sc) => mapSimpleCommand(sc, cwd));\n}\n\nfunction parseMcpName(name: string): { server: string; tool: string } | null {\n if (!name.startsWith(\"mcp__\")) return null;\n const parts = name.split(\"__\");\n if (parts.length < 3) return null;\n return { server: parts[1], tool: parts.slice(2).join(\"__\") };\n}\n\nfunction splitUrl(url: string): { host: string; path: string } {\n // The query string is dropped; a token in the path or an unparseable URL is scrubbed.\n try { const u = new URL(url); return { host: u.host, path: scrubParam(u.pathname) }; }\n catch { return { host: scrubParam(url), path: \"\" }; }\n}\n\nconst one = (intent: NormalizedIntent, evaluated: boolean, source: string): Mapped[] => [{ intent, evaluated, source }];\n\n/** A bare `git push` (no ref) pushes the current branch. The mapper cannot know it,\n * so the runtime resolves it and fills it in here, before evaluation — otherwise the\n * starter policy's ref bound would fail-closed on every plain push. Only git.push\n * intents with no ref are touched. */\nexport function fillPushBranch(mapped: Mapped[], branch: string | null | undefined): Mapped[] {\n if (!branch) return mapped;\n return mapped.map((m) =>\n m.intent.action_type === \"git.push\" && m.intent.params.ref === undefined\n ? { ...m, intent: { ...m.intent, params: { ...m.intent.params, ref: scrubParam(branch) } } }\n : m);\n}\n\n/** Map a Claude Code PreToolUse payload to the normalized actions it represents. */\nexport function mapClaudeToolUse(input: Record<string, unknown>): Mapped[] {\n const name = String(input?.tool_name ?? \"\");\n const ti = (input?.tool_input ?? {}) as Record<string, unknown>;\n const cwd = input?.cwd ? String(input.cwd) : undefined;\n if (name === \"Bash\") return mapShell(String(ti.command ?? \"\"), cwd);\n if (name === \"Write\" || name === \"Edit\" || name === \"MultiEdit\")\n return one({ action_type: \"file.write\", params: { path: rel(ti.file_path, cwd) } }, true, name);\n if (name === \"NotebookEdit\")\n return one({ action_type: \"file.write\", params: { path: rel(ti.notebook_path ?? ti.file_path, cwd) } }, true, name);\n if (name === \"Read\")\n return one({ action_type: \"file.read\", params: { path: rel(ti.file_path, cwd) } }, true, name);\n if (name === \"WebFetch\") {\n const { host, path } = splitUrl(String(ti.url ?? \"\"));\n return one({ action_type: \"net.fetch\", params: { host, path, method: \"GET\" } }, true, name);\n }\n const mcp = parseMcpName(name);\n if (mcp) return one({ action_type: \"mcp.tool.call\", params: { server: mcp.server, tool: mcp.tool, args_digest: digest(ti) } }, true, name);\n return one({ action_type: `tool.${name.toLowerCase()}`, params: {} }, false, name);\n}\n\n/** Map a Cursor hook event to the normalized actions it represents. */\nexport function mapCursorEvent(event: string, payload: Record<string, unknown>): Mapped[] {\n const p = payload ?? {};\n const cwd = p.cwd ? String(p.cwd) : undefined;\n switch (event) {\n case \"beforeShellExecution\":\n return mapShell(String(p.command ?? \"\"), cwd);\n case \"beforeReadFile\":\n return one({ action_type: \"file.read\", params: { path: rel(p.path ?? p.file_path, cwd) } }, true, event);\n case \"afterFileEdit\":\n return one({ action_type: \"file.write\", params: { path: rel(p.path ?? p.file_path, cwd) } }, true, event);\n case \"beforeMCPExecution\": {\n const server = String(p.server ?? p.server_name ?? \"\");\n const tool = String(p.tool ?? p.tool_name ?? \"\");\n return one({ action_type: \"mcp.tool.call\", params: { server, tool, args_digest: digest(p.args ?? p.arguments ?? {}) } }, true, event);\n }\n default:\n return one({ action_type: `tool.${event.toLowerCase()}`, params: {} }, false, event);\n }\n}\n"]}
1
+ {"version":3,"file":"map.js","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"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","sourcesContent":["// The mapper: a pure function from a coding agent's native tool call to the\n// normalized Scopebond actions (Action Taxonomy v1) it represents. Deterministic\n// and side-effect free, so it can be conformance-tested directly.\n//\n// A shell tool call can carry several commands (`a && b`, `$(c)`, `bash -c '…'`);\n// the mapper decomposes it and returns one intent per simple command, so the\n// runtime evaluates every one and denies if any is out of policy. Non-shell tools\n// map to a single intent (a one-element array).\n\nimport { digest, redactCommand, scrubParam, scrubSecrets } from \"./minimize.js\";\nimport { decomposeShell, parseGitPush, type SimpleCommand } from \"./shell.js\";\n\nexport interface NormalizedIntent {\n action_type: string;\n params: Record<string, unknown>;\n}\nexport interface Mapped {\n intent: NormalizedIntent;\n /** false when no taxonomy type applies (an unknown tool, or a shell command that\n * could not be parsed): emitted to the observation path (not_evaluated) in normal\n * mode and denied by the closed allowlist in strict mode — never granted. */\n evaluated: boolean;\n /** The native tool/event name, for diagnostics. */\n source: string;\n}\n\nconst basename = (t: string): string => t.replace(/^.*[\\\\/]/, \"\");\n\n// Normalize Windows backslash separators to \"/\" so a path guard written with \"/\"\n// (the hook's own .scopebond/**, .claude/settings*, *.key rules) cannot be bypassed\n// on Windows by a backslash path. The taxonomy path space is \"/\"-separated.\nconst normPath = (s: string): string => s.replace(/\\\\/g, \"/\");\n\nconst rel = (value: unknown, cwd?: string): string => {\n const s = normPath(String(value ?? \"\"));\n const c = cwd ? normPath(cwd) : undefined;\n if (c && s.startsWith(c)) return s.slice(c.length).replace(/^\\/+/, \"\");\n return s;\n};\n\n// Shell programs that READ a file given as an operand. A read of the signing keys\n// or a secret file through the shell (`cat .scopebond/attester.key`) must reach the\n// same file.read guard as the Read tool, or the cooperative protection is a fiction.\nconst READERS = new Set([\n \"cat\", \"tac\", \"less\", \"more\", \"head\", \"tail\", \"nl\", \"od\", \"xxd\", \"hexdump\",\n \"strings\", \"base64\", \"bat\", \"type\", \"get-content\", \"gc\",\n]);\n\n/** Derive the additional file.read / file.write intents a simple shell command\n * implies: operands of a reader program, and the targets of `>`/`>>` redirections.\n * These flow through the same protect-read / protect-write clauses as the native\n * file tools. A false positive (a non-protected operand) is a harmless extra\n * receipt that the starter policy allows. */\nfunction fileOpsFromShell(sc: SimpleCommand, cwd?: string): Mapped[] {\n const ops: Mapped[] = [];\n if (READERS.has(sc.program.toLowerCase())) {\n for (const t of sc.argv) {\n if (t.startsWith(\"-\") || /^\\d+$/.test(t)) continue; // skip flags and flag values (head -n 5)\n ops.push({ intent: { action_type: \"file.read\", params: { path: rel(scrubParam(t), cwd) } }, evaluated: true, source: \"shell\" });\n }\n }\n for (let i = 0; i < sc.argv.length; i++) {\n // `> file`, `>>file`, `2>file`, `&>file` — the token after the redirection op is a write target.\n const m = /^(?:\\d|&)?(>>?)(.*)$/.exec(sc.argv[i]);\n if (!m) continue;\n const target = m[2] || sc.argv[i + 1];\n if (target && !target.startsWith(\"-\") && !/^(?:\\d|&)?>>?/.test(target))\n ops.push({ intent: { action_type: \"file.write\", params: { path: rel(scrubParam(target), cwd) } }, evaluated: true, source: \"shell\" });\n }\n return ops;\n}\n\n/** Map one parsed simple command to the intents it implies: the git.push or\n * shell.exec itself, plus any file reads/writes it performs. An opaque\n * (unparseable) command becomes a single un-evaluated shell.exec so it fails closed. */\nfunction mapSimpleCommand(sc: SimpleCommand, cwd?: string): Mapped[] {\n if (sc.opaque) {\n return [{\n intent: { action_type: \"shell.exec\", params: { command: redactCommand(sc.raw), program: \"\", ...(cwd ? { cwd } : {}) } },\n evaluated: false, source: \"shell\",\n }];\n }\n const push = parseGitPush(sc);\n if (push) {\n const params: Record<string, unknown> = { force: push.force };\n if (push.remote !== undefined) params.remote = scrubParam(push.remote);\n if (push.ref !== undefined) params.ref = scrubParam(push.ref);\n return [{ intent: { action_type: \"git.push\", params }, evaluated: true, source: \"shell\" }];\n }\n const exec: Mapped = {\n intent: {\n action_type: \"shell.exec\",\n // Scrub before storing: the raw command through the blob-aware scrubber, and\n // the whole first token BEFORE taking its basename, so a bare-secret command\n // containing \"/\" cannot leak a path-fragment as the program.\n params: { command: redactCommand(sc.raw), program: basename(scrubSecrets(sc.programRaw)), ...(cwd ? { cwd } : {}) },\n },\n evaluated: true, source: \"shell\",\n };\n return [exec, ...fileOpsFromShell(sc, cwd)];\n}\n\n/** Decompose a shell command into the intents it will run. An empty command yields\n * a single un-evaluated placeholder (nothing to grant). */\nfunction mapShell(command: string, cwd?: string): Mapped[] {\n const commands = decomposeShell(command);\n if (commands.length === 0) {\n return [{ intent: { action_type: \"shell.exec\", params: { command: redactCommand(command), program: \"\" } }, evaluated: false, source: \"shell\" }];\n }\n return commands.flatMap((sc) => mapSimpleCommand(sc, cwd));\n}\n\nfunction parseMcpName(name: string): { server: string; tool: string } | null {\n if (!name.startsWith(\"mcp__\")) return null;\n const parts = name.split(\"__\");\n if (parts.length < 3) return null;\n return { server: parts[1], tool: parts.slice(2).join(\"__\") };\n}\n\nfunction splitUrl(url: string): { host: string; path: string } {\n // The query string is dropped; a token in the path or an unparseable URL is scrubbed.\n try { const u = new URL(url); return { host: u.host, path: scrubParam(u.pathname) }; }\n catch { return { host: scrubParam(url), path: \"\" }; }\n}\n\nconst one = (intent: NormalizedIntent, evaluated: boolean, source: string): Mapped[] => [{ intent, evaluated, source }];\n\n/** A bare `git push` (no ref) pushes the current branch. The mapper cannot know it,\n * so the runtime resolves it and fills it in here, before evaluation — otherwise the\n * starter policy's ref bound would fail-closed on every plain push. Only git.push\n * intents with no ref are touched. */\nexport function fillPushBranch(mapped: Mapped[], branch: string | null | undefined): Mapped[] {\n if (!branch) return mapped;\n return mapped.map((m) =>\n m.intent.action_type === \"git.push\" && m.intent.params.ref === undefined\n ? { ...m, intent: { ...m.intent, params: { ...m.intent.params, ref: scrubParam(branch) } } }\n : m);\n}\n\n/** Map a Claude Code PreToolUse payload to the normalized actions it represents. */\nexport function mapClaudeToolUse(input: Record<string, unknown>): Mapped[] {\n const name = String(input?.tool_name ?? \"\");\n const ti = (input?.tool_input ?? {}) as Record<string, unknown>;\n const cwd = input?.cwd ? String(input.cwd) : undefined;\n // Every shell-executing tool decomposes the same way. Claude Code exposes Bash;\n // some hosts/agents expose PowerShell or a generic Shell tool — mapping only Bash\n // let a PowerShell command (e.g. `Remove-Item -Recurse -Force .`) fall through to\n // an un-evaluated tool.<name> and be allowed.\n if (name === \"Bash\" || name === \"PowerShell\" || name === \"Shell\")\n return mapShell(String(ti.command ?? \"\"), cwd);\n if (name === \"Write\" || name === \"Edit\" || name === \"MultiEdit\")\n return one({ action_type: \"file.write\", params: { path: rel(ti.file_path, cwd) } }, true, name);\n if (name === \"NotebookEdit\")\n return one({ action_type: \"file.write\", params: { path: rel(ti.notebook_path ?? ti.file_path, cwd) } }, true, name);\n if (name === \"Read\")\n return one({ action_type: \"file.read\", params: { path: rel(ti.file_path, cwd) } }, true, name);\n if (name === \"WebFetch\") {\n const { host, path } = splitUrl(String(ti.url ?? \"\"));\n return one({ action_type: \"net.fetch\", params: { host, path, method: \"GET\" } }, true, name);\n }\n const mcp = parseMcpName(name);\n if (mcp) return one({ action_type: \"mcp.tool.call\", params: { server: mcp.server, tool: mcp.tool, args_digest: digest(ti) } }, true, name);\n return one({ action_type: `tool.${name.toLowerCase()}`, params: {} }, false, name);\n}\n\n/** Map a Cursor hook event to the normalized actions it represents. */\nexport function mapCursorEvent(event: string, payload: Record<string, unknown>): Mapped[] {\n const p = payload ?? {};\n const cwd = p.cwd ? String(p.cwd) : undefined;\n switch (event) {\n case \"beforeShellExecution\":\n return mapShell(String(p.command ?? \"\"), cwd);\n case \"beforeReadFile\":\n return one({ action_type: \"file.read\", params: { path: rel(p.path ?? p.file_path, cwd) } }, true, event);\n case \"afterFileEdit\":\n return one({ action_type: \"file.write\", params: { path: rel(p.path ?? p.file_path, cwd) } }, true, event);\n case \"beforeMCPExecution\": {\n const server = String(p.server ?? p.server_name ?? \"\");\n const tool = String(p.tool ?? p.tool_name ?? \"\");\n return one({ action_type: \"mcp.tool.call\", params: { server, tool, args_digest: digest(p.args ?? p.arguments ?? {}) } }, true, event);\n }\n default:\n return one({ action_type: `tool.${event.toLowerCase()}`, params: {} }, false, event);\n }\n}\n"]}
package/dist/runtime.js CHANGED
@@ -12,8 +12,8 @@ import { attachExporter, flushBounded } from "./cloud.js";
12
12
  // receipts stop meaning anything. These patterns are "allow if the path does NOT
13
13
  // match a protected location"; they run against the cwd-relative path the mapper
14
14
  // produces (and equally against an absolute one).
15
- const PROTECTED_WRITE = "^(?!(?:.*/)?\\.scopebond/)(?!(?:.*/)?\\.claude/settings)(?!(?:.*/)?\\.cursor/hooks)(?!(?:.*/)?\\.git/hooks/).+";
16
- const PROTECTED_READ = "^(?!(?:.*/)?\\.scopebond/)(?!.*\\.key$).+";
15
+ const PROTECTED_WRITE = "^(?!(?:.*/)?\\.scopebond/)(?!(?:.*/)?\\.claude/settings)(?!(?:.*/)?\\.cursor/hooks)(?!(?:.*/)?\\.git/hooks/)(?!(?:.*/)?\\.github/workflows/)(?!(?:.*/)?\\.github/actions/)(?!(?:.*/)?\\.gitlab-ci\\.yml$)(?!(?:.*/)?\\.circleci/)(?!(?:.*/)?azure-pipelines\\.yml$)(?!(?:.*/)?Jenkinsfile$).+";
16
+ const PROTECTED_READ = "^(?!(?:.*/)?\\.scopebond/)(?!.*\\.key$)(?!(?:.*/)?\\.env(?:\\.(?!example$|sample$|template$)[^/]*)?$).+";
17
17
  /** The default starter policy for a coding agent: protect release branches, deny
18
18
  * destructive programs, allow workspace file access except the hook's own config
19
19
  * and keys, and trust only the enrolled machine key. Every threshold is the
@@ -29,18 +29,18 @@ export function starterPolicy(agentKid) {
29
29
  },
30
30
  {
31
31
  id: "safe-shell", type: "action_allowlist", mode: "enforce", action_types: ["shell.exec"],
32
- param_bounds: { program: { pattern: "^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd)$).+" } },
33
- description: "Deny destructive programs (rm, sudo, …). An empty program (an unparseable command) is denied.",
32
+ param_bounds: { program: { pattern: "^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd|del|rd|rmdir|erase|deltree|format|Remove-Item|ri)$).+" } },
33
+ description: "Deny destructive programs — POSIX (rm, sudo, shutdown, reboot, mkfs, dd) and Windows/PowerShell (del, rd, rmdir, erase, deltree, format, Remove-Item). An empty program (an unparseable command) is denied.",
34
34
  },
35
35
  {
36
36
  id: "protect-write", type: "action_allowlist", mode: "enforce", action_types: ["file.write"],
37
37
  param_bounds: { path: { pattern: PROTECTED_WRITE } },
38
- description: "Allow workspace writes, but never to the hook's policy/keys, .claude/settings, .cursor/hooks or git hooks.",
38
+ description: "Allow workspace writes, but never to the hook's policy/keys, .claude/settings, .cursor/hooks, git hooks, or CI config (.github/workflows, .github/actions, .gitlab-ci.yml, .circleci, azure-pipelines.yml, Jenkinsfile).",
39
39
  },
40
40
  {
41
41
  id: "protect-read", type: "action_allowlist", mode: "enforce", action_types: ["file.read"],
42
42
  param_bounds: { path: { pattern: PROTECTED_READ } },
43
- description: "Allow workspace reads, but never the signing keys (*.key) or the hook's own .scopebond directory.",
43
+ description: "Allow workspace reads, but never the signing keys (*.key), environment secret files (.env, .env.*, except .env.example/.sample/.template) or the hook's own .scopebond directory.",
44
44
  },
45
45
  {
46
46
  id: "observe-net-mcp", type: "action_allowlist", mode: "monitor",
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["// The hook runtime: a check-only (M0) gateway built once from the enrolled\n// machine key, policy and a durable local receipt log. Each mapped action is\n// signed by the machine key, decided against policy and countersigned — locally,\n// with no HTTP server and no Cloud dependency in 0.1.\n\nimport { readFileSync } from \"node:fs\";\nimport { createGateway, StaticPrincipalKeyRegistry, type CloudExporter } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester, openReceiptStore } from \"@scopebond/gateway/node\";\nimport { createSigner } from \"@scopebond/sdk\";\nimport type { Mapped } from \"./map.js\";\nimport { attachExporter, flushBounded, type HookConnection } from \"./cloud.js\";\n\nexport interface RuntimeConfig {\n policyPath: string;\n keyPath: string;\n attesterPath: string;\n dbPath: string;\n /** Strict mode: an unmapped tool is policy-checked (and denied by a closed\n * allowlist) instead of observed. Fail-closed for tools with no taxonomy\n * mapping; default false (observe, matching the connector conformance vector). */\n strict?: boolean;\n /** When connected to a Cloud workspace, receipts are auto-exported to the portal.\n * Export is best-effort and never changes the local decision. */\n cloud?: { connection: HookConnection; fetch?: typeof fetch; flushTimeoutMs?: number };\n}\n\nexport interface Decision {\n decision: \"allow\" | \"deny\" | \"not_evaluated\";\n reason: string;\n /** The deciding receipt (the denied one, or the first allow). */\n receipt?: unknown;\n /** Every receipt produced — one per simple command in a decomposed shell call. */\n receipts?: unknown[];\n}\n\n// The hook's own config and keys must be off-limits to the agent it governs:\n// otherwise the agent could rewrite its policy or read the signing keys and the\n// receipts stop meaning anything. These patterns are \"allow if the path does NOT\n// match a protected location\"; they run against the cwd-relative path the mapper\n// produces (and equally against an absolute one).\nconst PROTECTED_WRITE = \"^(?!(?:.*/)?\\\\.scopebond/)(?!(?:.*/)?\\\\.claude/settings)(?!(?:.*/)?\\\\.cursor/hooks)(?!(?:.*/)?\\\\.git/hooks/).+\";\nconst PROTECTED_READ = \"^(?!(?:.*/)?\\\\.scopebond/)(?!.*\\\\.key$).+\";\n\n/** The default starter policy for a coding agent: protect release branches, deny\n * destructive programs, allow workspace file access except the hook's own config\n * and keys, and trust only the enrolled machine key. Every threshold is the\n * operator's to edit. */\nexport function starterPolicy(agentKid: string): Record<string, unknown> {\n return {\n vocabulary_version: \"1.0\", policy_id: \"coding-agent\", version: 1,\n clauses: [\n {\n id: \"protect-branches\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"git.push\"],\n param_bounds: { ref: { pattern: \"^(?!(?:main|master)$)(?!release/).+\" } },\n description: \"Deny pushes to main, master and release/*.\",\n },\n {\n id: \"safe-shell\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"shell.exec\"],\n param_bounds: { program: { pattern: \"^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd)$).+\" } },\n description: \"Deny destructive programs (rm, sudo, …). An empty program (an unparseable command) is denied.\",\n },\n {\n id: \"protect-write\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.write\"],\n param_bounds: { path: { pattern: PROTECTED_WRITE } },\n description: \"Allow workspace writes, but never to the hook's policy/keys, .claude/settings, .cursor/hooks or git hooks.\",\n },\n {\n id: \"protect-read\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.read\"],\n param_bounds: { path: { pattern: PROTECTED_READ } },\n description: \"Allow workspace reads, but never the signing keys (*.key) or the hook's own .scopebond directory.\",\n },\n {\n id: \"observe-net-mcp\", type: \"action_allowlist\", mode: \"monitor\",\n action_types: [\"net.fetch\", \"mcp.tool.call\"],\n description: \"Observe network fetches and MCP tool calls (recorded, not blocked) — add bounds to enforce.\",\n },\n { id: \"keys\", type: \"key_policy\", active_keys: [agentKid], description: \"Only the enrolled machine key may sign.\" },\n ],\n };\n}\n\n/** Build the runtime. Throws on any setup failure (unparseable policy, missing\n * key, unavailable store) — the CLI turns that into a fail-closed deny. */\nexport function createHookRuntime(config: RuntimeConfig) {\n const policy = JSON.parse(readFileSync(config.policyPath, \"utf8\"));\n const agent = createSigner({ privateKeyPem: readFileSync(config.keyPath, \"utf8\") });\n const keys = new StaticPrincipalKeyRegistry([\n { kid: agent.kid, publicKeyPem: agent.publicKeyPem, purposes: [\"agent\"], status: \"active\" },\n ]);\n const { attester } = loadOrCreateAttester({ file: config.attesterPath });\n const { store: baseStore } = openReceiptStore({ db: config.dbPath });\n // When connected, mirror every stored receipt to the hosted portal through a\n // durable outbox. The wrapped store's decision is unchanged; export is best-effort.\n let store = baseStore;\n let exporter: CloudExporter | undefined;\n if (config.cloud) {\n const attached = attachExporter(config.dbPath + \".cloud-outbox.db\", config.cloud.connection, baseStore, config.cloud.fetch);\n store = attached.store;\n exporter = attached.exporter;\n }\n const gateway = createGateway({ policy, authentication: { keys }, attester, store, mode: \"check_only\" });\n\n return {\n gateway,\n agentKid: agent.kid,\n exporter,\n /** Deliver queued receipts to Cloud with a bounded timeout, then it is safe to\n * exit. Undelivered receipts persist in the durable outbox for the next run. */\n async flush(): Promise<void> {\n if (exporter) await flushBounded(exporter, config.cloud?.flushTimeoutMs);\n },\n /** Decide one mapped action, recording a receipt either way. */\n async evaluateOne(mapped: Mapped): Promise<Decision> {\n const signed = agent.sign(mapped.intent);\n if (!mapped.evaluated && !config.strict) {\n // Unknown tool or unparseable command, non-strict: observe without\n // evaluating — grants nothing (D30).\n const { receipt } = await gateway.observeAction({ intent: signed.intent, authorization: signed.authorization });\n return { decision: \"not_evaluated\", reason: `no policy applies to ${mapped.intent.action_type}`, receipt };\n }\n // Evaluated actions, and (in strict mode) unmapped tool.<name>/opaque commands,\n // go through policy — a closed allowlist denies an unlisted action.\n const result = await gateway.handleAction({ intent: signed.intent, authorization: signed.authorization });\n return { decision: result.allowed ? \"allow\" : \"deny\", reason: result.reason, receipt: result.receipt };\n },\n /** Decide a whole tool call. A shell call decomposes into several simple\n * commands; every one is recorded, and a single deny denies the call. */\n async evaluate(mapped: Mapped | Mapped[]): Promise<Decision> {\n const list = Array.isArray(mapped) ? mapped : [mapped];\n if (list.length === 0) return { decision: \"not_evaluated\", reason: \"no action\", receipts: [] };\n const receipts: unknown[] = [];\n let allow: Decision | null = null;\n let notEvaluated: Decision | null = null;\n for (const m of list) {\n const d = await this.evaluateOne(m);\n if (d.receipt !== undefined) receipts.push(d.receipt);\n if (d.decision === \"deny\") return { ...d, receipts }; // any deny denies the call\n if (d.decision === \"allow\" && !allow) allow = d;\n if (d.decision === \"not_evaluated\" && !notEvaluated) notEvaluated = d;\n }\n // No deny: allow if any command was evaluated-and-allowed, else not_evaluated.\n const chosen = allow ?? notEvaluated!;\n return { ...chosen, receipts };\n },\n };\n}\n"]}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["// The hook runtime: a check-only (M0) gateway built once from the enrolled\n// machine key, policy and a durable local receipt log. Each mapped action is\n// signed by the machine key, decided against policy and countersigned — locally,\n// with no HTTP server and no Cloud dependency in 0.1.\n\nimport { readFileSync } from \"node:fs\";\nimport { createGateway, StaticPrincipalKeyRegistry, type CloudExporter } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester, openReceiptStore } from \"@scopebond/gateway/node\";\nimport { createSigner } from \"@scopebond/sdk\";\nimport type { Mapped } from \"./map.js\";\nimport { attachExporter, flushBounded, type HookConnection } from \"./cloud.js\";\n\nexport interface RuntimeConfig {\n policyPath: string;\n keyPath: string;\n attesterPath: string;\n dbPath: string;\n /** Strict mode: an unmapped tool is policy-checked (and denied by a closed\n * allowlist) instead of observed. Fail-closed for tools with no taxonomy\n * mapping; default false (observe, matching the connector conformance vector). */\n strict?: boolean;\n /** When connected to a Cloud workspace, receipts are auto-exported to the portal.\n * Export is best-effort and never changes the local decision. */\n cloud?: { connection: HookConnection; fetch?: typeof fetch; flushTimeoutMs?: number };\n}\n\nexport interface Decision {\n decision: \"allow\" | \"deny\" | \"not_evaluated\";\n reason: string;\n /** The deciding receipt (the denied one, or the first allow). */\n receipt?: unknown;\n /** Every receipt produced — one per simple command in a decomposed shell call. */\n receipts?: unknown[];\n}\n\n// The hook's own config and keys must be off-limits to the agent it governs:\n// otherwise the agent could rewrite its policy or read the signing keys and the\n// receipts stop meaning anything. These patterns are \"allow if the path does NOT\n// match a protected location\"; they run against the cwd-relative path the mapper\n// produces (and equally against an absolute one).\nconst PROTECTED_WRITE = \"^(?!(?:.*/)?\\\\.scopebond/)(?!(?:.*/)?\\\\.claude/settings)(?!(?:.*/)?\\\\.cursor/hooks)(?!(?:.*/)?\\\\.git/hooks/)(?!(?:.*/)?\\\\.github/workflows/)(?!(?:.*/)?\\\\.github/actions/)(?!(?:.*/)?\\\\.gitlab-ci\\\\.yml$)(?!(?:.*/)?\\\\.circleci/)(?!(?:.*/)?azure-pipelines\\\\.yml$)(?!(?:.*/)?Jenkinsfile$).+\";\nconst PROTECTED_READ = \"^(?!(?:.*/)?\\\\.scopebond/)(?!.*\\\\.key$)(?!(?:.*/)?\\\\.env(?:\\\\.(?!example$|sample$|template$)[^/]*)?$).+\";\n\n/** The default starter policy for a coding agent: protect release branches, deny\n * destructive programs, allow workspace file access except the hook's own config\n * and keys, and trust only the enrolled machine key. Every threshold is the\n * operator's to edit. */\nexport function starterPolicy(agentKid: string): Record<string, unknown> {\n return {\n vocabulary_version: \"1.0\", policy_id: \"coding-agent\", version: 1,\n clauses: [\n {\n id: \"protect-branches\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"git.push\"],\n param_bounds: { ref: { pattern: \"^(?!(?:main|master)$)(?!release/).+\" } },\n description: \"Deny pushes to main, master and release/*.\",\n },\n {\n id: \"safe-shell\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"shell.exec\"],\n param_bounds: { program: { pattern: \"^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd|del|rd|rmdir|erase|deltree|format|Remove-Item|ri)$).+\" } },\n description: \"Deny destructive programs — POSIX (rm, sudo, shutdown, reboot, mkfs, dd) and Windows/PowerShell (del, rd, rmdir, erase, deltree, format, Remove-Item). An empty program (an unparseable command) is denied.\",\n },\n {\n id: \"protect-write\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.write\"],\n param_bounds: { path: { pattern: PROTECTED_WRITE } },\n description: \"Allow workspace writes, but never to the hook's policy/keys, .claude/settings, .cursor/hooks, git hooks, or CI config (.github/workflows, .github/actions, .gitlab-ci.yml, .circleci, azure-pipelines.yml, Jenkinsfile).\",\n },\n {\n id: \"protect-read\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.read\"],\n param_bounds: { path: { pattern: PROTECTED_READ } },\n description: \"Allow workspace reads, but never the signing keys (*.key), environment secret files (.env, .env.*, except .env.example/.sample/.template) or the hook's own .scopebond directory.\",\n },\n {\n id: \"observe-net-mcp\", type: \"action_allowlist\", mode: \"monitor\",\n action_types: [\"net.fetch\", \"mcp.tool.call\"],\n description: \"Observe network fetches and MCP tool calls (recorded, not blocked) — add bounds to enforce.\",\n },\n { id: \"keys\", type: \"key_policy\", active_keys: [agentKid], description: \"Only the enrolled machine key may sign.\" },\n ],\n };\n}\n\n/** Build the runtime. Throws on any setup failure (unparseable policy, missing\n * key, unavailable store) — the CLI turns that into a fail-closed deny. */\nexport function createHookRuntime(config: RuntimeConfig) {\n const policy = JSON.parse(readFileSync(config.policyPath, \"utf8\"));\n const agent = createSigner({ privateKeyPem: readFileSync(config.keyPath, \"utf8\") });\n const keys = new StaticPrincipalKeyRegistry([\n { kid: agent.kid, publicKeyPem: agent.publicKeyPem, purposes: [\"agent\"], status: \"active\" },\n ]);\n const { attester } = loadOrCreateAttester({ file: config.attesterPath });\n const { store: baseStore } = openReceiptStore({ db: config.dbPath });\n // When connected, mirror every stored receipt to the hosted portal through a\n // durable outbox. The wrapped store's decision is unchanged; export is best-effort.\n let store = baseStore;\n let exporter: CloudExporter | undefined;\n if (config.cloud) {\n const attached = attachExporter(config.dbPath + \".cloud-outbox.db\", config.cloud.connection, baseStore, config.cloud.fetch);\n store = attached.store;\n exporter = attached.exporter;\n }\n const gateway = createGateway({ policy, authentication: { keys }, attester, store, mode: \"check_only\" });\n\n return {\n gateway,\n agentKid: agent.kid,\n exporter,\n /** Deliver queued receipts to Cloud with a bounded timeout, then it is safe to\n * exit. Undelivered receipts persist in the durable outbox for the next run. */\n async flush(): Promise<void> {\n if (exporter) await flushBounded(exporter, config.cloud?.flushTimeoutMs);\n },\n /** Decide one mapped action, recording a receipt either way. */\n async evaluateOne(mapped: Mapped): Promise<Decision> {\n const signed = agent.sign(mapped.intent);\n if (!mapped.evaluated && !config.strict) {\n // Unknown tool or unparseable command, non-strict: observe without\n // evaluating — grants nothing (D30).\n const { receipt } = await gateway.observeAction({ intent: signed.intent, authorization: signed.authorization });\n return { decision: \"not_evaluated\", reason: `no policy applies to ${mapped.intent.action_type}`, receipt };\n }\n // Evaluated actions, and (in strict mode) unmapped tool.<name>/opaque commands,\n // go through policy — a closed allowlist denies an unlisted action.\n const result = await gateway.handleAction({ intent: signed.intent, authorization: signed.authorization });\n return { decision: result.allowed ? \"allow\" : \"deny\", reason: result.reason, receipt: result.receipt };\n },\n /** Decide a whole tool call. A shell call decomposes into several simple\n * commands; every one is recorded, and a single deny denies the call. */\n async evaluate(mapped: Mapped | Mapped[]): Promise<Decision> {\n const list = Array.isArray(mapped) ? mapped : [mapped];\n if (list.length === 0) return { decision: \"not_evaluated\", reason: \"no action\", receipts: [] };\n const receipts: unknown[] = [];\n let allow: Decision | null = null;\n let notEvaluated: Decision | null = null;\n for (const m of list) {\n const d = await this.evaluateOne(m);\n if (d.receipt !== undefined) receipts.push(d.receipt);\n if (d.decision === \"deny\") return { ...d, receipts }; // any deny denies the call\n if (d.decision === \"allow\" && !allow) allow = d;\n if (d.decision === \"not_evaluated\" && !notEvaluated) notEvaluated = d;\n }\n // No deny: allow if any command was evaluated-and-allowed, else not_evaluated.\n const chosen = allow ?? notEvaluated!;\n return { ...chosen, receipts };\n },\n };\n}\n"]}
package/dist/version.d.ts CHANGED
@@ -3,4 +3,9 @@ export declare function hookVersion(): string;
3
3
  * invocation so a missing global binary is fetched rather than silently skipped
4
4
  * (which a harness can treat as "no hook" and fail open). */
5
5
  export declare function hookCommand(harness: "claude" | "cursor"): string;
6
+ /** The command a user runs by hand for a CLI subcommand (`log`, `verify`, `test`,
7
+ * `init`, `connect`). After `npx @scopebond/hook init` there is no `scopebond-hook`
8
+ * binary on PATH, so printed guidance must use the same version-pinned `npx` form,
9
+ * which also resolves to a global install when one exists. */
10
+ export declare function cliCommand(sub: string): string;
6
11
  //# sourceMappingURL=version.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAOA,wBAAgB,WAAW,IAAI,MAAM,CASpC;AAED;;8DAE8D;AAC9D,wBAAgB,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAEhE"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAOA,wBAAgB,WAAW,IAAI,MAAM,CASpC;AAED;;8DAE8D;AAC9D,wBAAgB,WAAW,CAAC,OAAO,EAAE,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAEhE;AAED;;;+DAG+D;AAC/D,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C"}
package/dist/version.js CHANGED
@@ -20,4 +20,11 @@ export function hookVersion() {
20
20
  export function hookCommand(harness) {
21
21
  return `npx -y @scopebond/hook@${hookVersion()} ${harness}`;
22
22
  }
23
+ /** The command a user runs by hand for a CLI subcommand (`log`, `verify`, `test`,
24
+ * `init`, `connect`). After `npx @scopebond/hook init` there is no `scopebond-hook`
25
+ * binary on PATH, so printed guidance must use the same version-pinned `npx` form,
26
+ * which also resolves to a global install when one exists. */
27
+ export function cliCommand(sub) {
28
+ return `npx -y @scopebond/hook@${hookVersion()} ${sub}`;
29
+ }
23
30
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,kFAAkF;AAClF,6EAA6E;AAC7E,mBAAmB;AAEnB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC;QACH,2EAA2E;QAC3E,kCAAkC;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1F,OAAO,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED;;8DAE8D;AAC9D,MAAM,UAAU,WAAW,CAAC,OAA4B;IACtD,OAAO,0BAA0B,WAAW,EAAE,IAAI,OAAO,EAAE,CAAC;AAC9D,CAAC","sourcesContent":["// The installed package version, read from the package's own package.json at\n// runtime. Used to pin the `npx` command the hook installs into the agent config,\n// so the harness always runs the exact version the user set up rather than a\n// floating latest.\n\nimport { readFileSync } from \"node:fs\";\n\nexport function hookVersion(): string {\n try {\n // dist/version.js → ../package.json is the package root in both the source\n // tree and the published tarball.\n const pkg = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\"));\n return typeof pkg.version === \"string\" ? pkg.version : \"latest\";\n } catch {\n return \"latest\";\n }\n}\n\n/** The command the agent harness runs for each tool call: a version-pinned npx\n * invocation so a missing global binary is fetched rather than silently skipped\n * (which a harness can treat as \"no hook\" and fail open). */\nexport function hookCommand(harness: \"claude\" | \"cursor\"): string {\n return `npx -y @scopebond/hook@${hookVersion()} ${harness}`;\n}\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,kFAAkF;AAClF,6EAA6E;AAC7E,mBAAmB;AAEnB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC;QACH,2EAA2E;QAC3E,kCAAkC;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1F,OAAO,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;AACH,CAAC;AAED;;8DAE8D;AAC9D,MAAM,UAAU,WAAW,CAAC,OAA4B;IACtD,OAAO,0BAA0B,WAAW,EAAE,IAAI,OAAO,EAAE,CAAC;AAC9D,CAAC;AAED;;;+DAG+D;AAC/D,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,0BAA0B,WAAW,EAAE,IAAI,GAAG,EAAE,CAAC;AAC1D,CAAC","sourcesContent":["// The installed package version, read from the package's own package.json at\n// runtime. Used to pin the `npx` command the hook installs into the agent config,\n// so the harness always runs the exact version the user set up rather than a\n// floating latest.\n\nimport { readFileSync } from \"node:fs\";\n\nexport function hookVersion(): string {\n try {\n // dist/version.js → ../package.json is the package root in both the source\n // tree and the published tarball.\n const pkg = JSON.parse(readFileSync(new URL(\"../package.json\", import.meta.url), \"utf8\"));\n return typeof pkg.version === \"string\" ? pkg.version : \"latest\";\n } catch {\n return \"latest\";\n }\n}\n\n/** The command the agent harness runs for each tool call: a version-pinned npx\n * invocation so a missing global binary is fetched rather than silently skipped\n * (which a harness can treat as \"no hook\" and fail open). */\nexport function hookCommand(harness: \"claude\" | \"cursor\"): string {\n return `npx -y @scopebond/hook@${hookVersion()} ${harness}`;\n}\n\n/** The command a user runs by hand for a CLI subcommand (`log`, `verify`, `test`,\n * `init`, `connect`). After `npx @scopebond/hook init` there is no `scopebond-hook`\n * binary on PATH, so printed guidance must use the same version-pinned `npx` form,\n * which also resolves to a global install when one exists. */\nexport function cliCommand(sub: string): string {\n return `npx -y @scopebond/hook@${hookVersion()} ${sub}`;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scopebond/hook",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Scopebond hook for Claude Code and Cursor — checks each coding-agent tool call against policy before it runs (in-path, cooperative M0) and records a signed receipt, locally.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Avouro LLC",
@@ -22,7 +22,7 @@
22
22
  "policy"
23
23
  ],
24
24
  "engines": {
25
- "node": ">=22"
25
+ "node": ">=22.13"
26
26
  },
27
27
  "type": "module",
28
28
  "main": "dist/index.js",
@@ -34,6 +34,7 @@
34
34
  }
35
35
  },
36
36
  "bin": {
37
+ "scopebond": "dist/cli.js",
37
38
  "scopebond-hook": "dist/cli.js"
38
39
  },
39
40
  "files": [