@scopebond/hook 0.15.1 → 0.16.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 +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +35 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/managed.d.ts +16 -1
- package/dist/managed.d.ts.map +1 -1
- package/dist/managed.js +29 -6
- package/dist/managed.js.map +1 -1
- package/dist/onboarding.d.ts +9 -0
- package/dist/onboarding.d.ts.map +1 -0
- package/dist/onboarding.js +12 -0
- package/dist/onboarding.js.map +1 -0
- package/dist/override.d.ts +51 -0
- package/dist/override.d.ts.map +1 -0
- package/dist/override.js +193 -0
- package/dist/override.js.map +1 -0
- package/dist/runtime.d.ts +9 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +12 -3
- package/dist/runtime.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -266,6 +266,15 @@ patterns: the hook compiles its choices with the same compiler as `rules apply`.
|
|
|
266
266
|
- **What the workspace cannot change.** Protection of Scopebond's own settings and of the
|
|
267
267
|
agents' hook settings, the machine key policy, fail-closed handling of anything unreadable,
|
|
268
268
|
and this computer's own opt-ins (`allowed_roots`, `protect_remote_database`).
|
|
269
|
+
- **Overrides.** The workspace can set a rule to *Block, user may override* and say who may
|
|
270
|
+
override. A matching action is then blocked until the person at the computer allows it once,
|
|
271
|
+
with a reason, in the Scopebond Agent's window (`@scopebond/agent`): the hook asks the running
|
|
272
|
+
agent and waits up to 45 seconds (20 seconds for Codex and Cursor), then blocks. The receipt
|
|
273
|
+
records the override, the reason's digest and the rule; the workspace adds who and why. Where the
|
|
274
|
+
workspace allows it and the window is not available, Claude Code's own prompt is offered instead,
|
|
275
|
+
but only in a permission mode where Claude Code really asks; that prompt takes no reason and is
|
|
276
|
+
recorded as *offered*. Scopebond's own protection, rules set to Block and the kill switch are
|
|
277
|
+
never overridable, and the workspace's daily limit holds.
|
|
269
278
|
- **Going back.** While the workspace sets the rules, `rules` edits and `policy load` are
|
|
270
279
|
refused here. If the connection is revoked, or the workspace stops setting rules for this
|
|
271
280
|
computer, the hook recompiles `policy.json` from `rules.json`: a computer is never left
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AA+rBA,6EAA6E;AAC7E,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,GAAG,GAAE,MAAmB,GAAG,MAAM,GAAG,IAAI,CAS7F"}
|
package/dist/cli.js
CHANGED
|
@@ -35,6 +35,7 @@ import { mapClaudeToolUse, mapCodexToolUse, mapCursorEvent, fillPushBranch } fro
|
|
|
35
35
|
import { createHookRuntime } from "./runtime.js";
|
|
36
36
|
import { useDigestKey, loadOrCreateDigestKey } from "./minimize.js";
|
|
37
37
|
import { scaffold, harnessSnippet, placeHook } from "./init.js";
|
|
38
|
+
import { onboardingSteps } from "./onboarding.js";
|
|
38
39
|
import { userHome, userHarnessFile, resolveConfigDir, writeHarnessConfig, removeHarnessConfig, cursorDetected, codexDetected, absoluteHookCommand, isHarnessConfigured, purgeHome, harnessScopes, harnessScopeLabel, configuredHookCommands, hookCommandResolves, projectHarnessFile, localHarnessFile, gitShareState, isMachineSpecificCommand, trustProjectPolicy, untrustedProjectPolicy, isTrustedProject, wireLifecycleHooks, unwireLifecycleHooks, } from "./install.js";
|
|
39
40
|
import { openObservations, describeObservations, observationStatus, stopReasonFromClaude, exitFromClaudeFailure, HEARTBEAT_INTERVAL_MS, OBSERVATIONS_SCOPE, } from "./obs-emitter.js";
|
|
40
41
|
import { OBSERVATION_DB, ObservationStore } from "./obs-store.js";
|
|
@@ -44,6 +45,7 @@ import { connectCloud, ingestUrl, loadConnection } from "./cloud.js";
|
|
|
44
45
|
import { recoverEarlierReceipts } from "./recover.js";
|
|
45
46
|
import { loadPolicyExport, policyBuilds } from "./policy-load.js";
|
|
46
47
|
import { isManaged, readMeta, MANAGED_DOC_FILE } from "./managed.js";
|
|
48
|
+
import { createOverrideHandler, overrideHint } from "./override.js";
|
|
47
49
|
import { syncIfDue, syncPolicy } from "./policy-sync.js";
|
|
48
50
|
import { loadBudgetExport } from "./budget-load.js";
|
|
49
51
|
import { compile, defaultRules, describeRules, loadRules, saveRules, rulesPath, pathRuleFor } from "./rules.js";
|
|
@@ -128,6 +130,17 @@ function readStdin() {
|
|
|
128
130
|
return "";
|
|
129
131
|
}
|
|
130
132
|
}
|
|
133
|
+
/** How long a hook waits for a person in the Scopebond window: well inside each agent's hook timeout (Claude Code 60 s by
|
|
134
|
+
* default; the Codex entry Scopebond writes says 30 s), so the wait ends as a block, never as a timed-out hook. */
|
|
135
|
+
const OVERRIDE_WAIT_MS = { claude: 45_000, codex: 20_000, cursor: 20_000 };
|
|
136
|
+
/** Warn mode: Claude Code shows the person its own prompt for this action (only where the workspace allows it and Claude Code
|
|
137
|
+
* really asks; see override.ts). */
|
|
138
|
+
function askClaude(reason) {
|
|
139
|
+
process.stdout.write(JSON.stringify({
|
|
140
|
+
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: reason },
|
|
141
|
+
}) + "\n");
|
|
142
|
+
process.exit(0);
|
|
143
|
+
}
|
|
131
144
|
function denyClaude(reason) {
|
|
132
145
|
process.stdout.write(JSON.stringify({
|
|
133
146
|
hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: reason },
|
|
@@ -217,7 +230,11 @@ async function runPreToolUse(mapper, deny = denyClaude, raw, harness = "claude")
|
|
|
217
230
|
try {
|
|
218
231
|
const cwd = input?.cwd ? String(input.cwd) : process.cwd();
|
|
219
232
|
const dir = resolveConfigDir(cwd);
|
|
220
|
-
|
|
233
|
+
const permissionMode = typeof input.permission_mode === "string" ? input.permission_mode : null;
|
|
234
|
+
runtime = createHookRuntime({ ...runtimePaths(dir, cwd), override: (agentKid) => {
|
|
235
|
+
const made = createOverrideHandler({ dir, home: userHome(), agentKid, harness, permissionMode, waitMs: OVERRIDE_WAIT_MS[harness] });
|
|
236
|
+
return made ? { handler: made.handler, hint: () => overrideHint(made.note()) } : null;
|
|
237
|
+
} });
|
|
221
238
|
useDigestKey(loadOrCreateDigestKey(dir));
|
|
222
239
|
const decision = await runtime.evaluate([...fillPushBranch(mapper(input), currentBranch(cwd)), ...databaseGuard(dir, cwd, input)], { groupKey: callId(input) });
|
|
223
240
|
const observer = recordObservations(dir, cwd, input, decision, harness);
|
|
@@ -230,6 +247,12 @@ async function runPreToolUse(mapper, deny = denyClaude, raw, harness = "claude")
|
|
|
230
247
|
runtime = undefined;
|
|
231
248
|
if (decision.decision === "deny")
|
|
232
249
|
deny(decision.reason);
|
|
250
|
+
// Only Claude Code is ever offered "ask" (override.ts); any other harness treats it as a denial.
|
|
251
|
+
if (decision.decision === "ask") {
|
|
252
|
+
if (harness === "claude")
|
|
253
|
+
askClaude(decision.reason);
|
|
254
|
+
deny(decision.reason);
|
|
255
|
+
}
|
|
233
256
|
// Stay silent on allow/not_evaluated so the coding agent's normal permission
|
|
234
257
|
// flow remains in charge. Scopebond blocks; it never silently approves.
|
|
235
258
|
process.exit(0);
|
|
@@ -328,9 +351,15 @@ async function runCursor() {
|
|
|
328
351
|
try {
|
|
329
352
|
const cwd = input?.cwd ? String(input.cwd) : process.cwd();
|
|
330
353
|
const dir = resolveConfigDir(cwd);
|
|
331
|
-
|
|
332
|
-
useDigestKey(loadOrCreateDigestKey(dir));
|
|
354
|
+
const permissionMode = typeof input.permission_mode === "string" ? input.permission_mode : null;
|
|
333
355
|
const mapped = fillPushBranch(mapCursorEvent(event, input), currentBranch(cwd));
|
|
356
|
+
// An edit Cursor reports after saving it cannot be overridden: it already happened.
|
|
357
|
+
const overridable = !mapped.some((m) => m.postHoc);
|
|
358
|
+
runtime = createHookRuntime({ ...runtimePaths(dir, cwd), override: (agentKid) => {
|
|
359
|
+
const made = overridable ? createOverrideHandler({ dir, home: userHome(), agentKid, harness: "cursor", permissionMode, waitMs: OVERRIDE_WAIT_MS.cursor }) : null;
|
|
360
|
+
return made ? { handler: made.handler, hint: () => overrideHint(made.note()) } : null;
|
|
361
|
+
} });
|
|
362
|
+
useDigestKey(loadOrCreateDigestKey(dir));
|
|
334
363
|
const decision = await runtime.evaluate(mapped, { groupKey: callId(input) });
|
|
335
364
|
const observer = recordObservations(dir, cwd, input, decision, "cursor");
|
|
336
365
|
await Promise.all([runtime.flush(), observer?.flush() ?? Promise.resolve(), syncIfDue(dir, () => syncOptionsFor(dir))]);
|
|
@@ -481,7 +510,7 @@ function runInit(args) {
|
|
|
481
510
|
// `scopebond-hook` binary on PATH, so a bare `scopebond-hook log` would fail.
|
|
482
511
|
console.log(`Next: run one command in the agent, then \`${cliCommand("log")}\` to see the receipt`);
|
|
483
512
|
console.log(`and \`${cliCommand("verify")}\` to check it offline. Try \`${cliCommand('test "rm -rf /"')}\`.`);
|
|
484
|
-
console.log(
|
|
513
|
+
console.log(onboardingSteps({ harness, command: cliCommand, project: true }).join("\n"));
|
|
485
514
|
}
|
|
486
515
|
function decisionOf(payload) {
|
|
487
516
|
const rr = String(payload.realtime_result ?? "");
|
|
@@ -956,6 +985,7 @@ async function finishConnect(dir, url, bundle, harness, args) {
|
|
|
956
985
|
console.log(`Observations: ${observing.reason}`);
|
|
957
986
|
console.log("");
|
|
958
987
|
console.log("Run your agent — the first action appears in your workspace within seconds.");
|
|
988
|
+
console.log(onboardingSteps({ harness, command: cliCommand, connected: true }).join("\n"));
|
|
959
989
|
}
|
|
960
990
|
catch (error) {
|
|
961
991
|
const message = error.message;
|
|
@@ -1355,7 +1385,7 @@ function runInstall(args) {
|
|
|
1355
1385
|
console.log("");
|
|
1356
1386
|
console.log(`A project's own .scopebond policy applies only after you trust it there (${cliCommand("trust")}).`);
|
|
1357
1387
|
console.log(`Check it: ${cliCommand("doctor")} · see decisions: ${cliCommand("log")}`);
|
|
1358
|
-
console.log(
|
|
1388
|
+
console.log(onboardingSteps({ harness: harnesses[0], command: cliCommand }).join("\n"));
|
|
1359
1389
|
}
|
|
1360
1390
|
/** Size and count of the local receipt store, plus its write-ahead log. Signed evidence
|
|
1361
1391
|
* accumulates in the user's project directory and there is no automatic deletion — so
|