auto-mode 0.2.0 → 0.3.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
|
@@ -113,6 +113,10 @@ was there. Set `onFailure` to `deny` to fail closed instead.
|
|
|
113
113
|
| `auto-mode print-prompt` | Print the assembled system prompt |
|
|
114
114
|
| `auto-mode init <harness>` | Print the hook entry to add |
|
|
115
115
|
|
|
116
|
+
`init` takes `--event permission-request` to register on the event Claude Code sends when it is
|
|
117
|
+
about to ask you, rather than on every tool call. The
|
|
118
|
+
[harness guide](./docs/guides/harnesses.md#which-event) covers the choice.
|
|
119
|
+
|
|
116
120
|
## Documentation
|
|
117
121
|
|
|
118
122
|
- [Overview](./docs/architecture/overview.md) — the two tiers, harness detection, the verdict
|
|
@@ -5663,18 +5663,30 @@ function F(t) {
|
|
|
5663
5663
|
//#endregion
|
|
5664
5664
|
//#region src/harness/render-verdict.ts
|
|
5665
5665
|
function renderVerdict(event, verdict) {
|
|
5666
|
-
const decision = M(
|
|
5667
|
-
|
|
5668
|
-
permissionDecisionReason: `[${denied.rule}] ${denied.reason}`
|
|
5669
|
-
})).exhaustive();
|
|
5670
|
-
return JSON.stringify({ hookSpecificOutput: {
|
|
5666
|
+
const decision = M(event).with("PreToolUse", () => buildToolGateDecision(verdict)).with("PermissionRequest", () => buildPermissionDecision(verdict)).exhaustive();
|
|
5667
|
+
return decision === null ? null : JSON.stringify({ hookSpecificOutput: {
|
|
5671
5668
|
hookEventName: event,
|
|
5672
5669
|
...decision
|
|
5673
5670
|
} });
|
|
5674
5671
|
}
|
|
5672
|
+
function buildToolGateDecision(verdict) {
|
|
5673
|
+
return M(verdict).with({ kind: "ask" }, () => ({ permissionDecision: "ask" })).with({ kind: "allow" }, () => ({ permissionDecision: "allow" })).with({ kind: "deny" }, (denied) => ({
|
|
5674
|
+
permissionDecision: "deny",
|
|
5675
|
+
permissionDecisionReason: formatReason(denied)
|
|
5676
|
+
})).exhaustive();
|
|
5677
|
+
}
|
|
5678
|
+
function buildPermissionDecision(verdict) {
|
|
5679
|
+
return M(verdict).with({ kind: "ask" }, () => null).with({ kind: "allow" }, () => ({ decision: { behavior: "allow" } })).with({ kind: "deny" }, (denied) => ({ decision: {
|
|
5680
|
+
behavior: "deny",
|
|
5681
|
+
message: formatReason(denied)
|
|
5682
|
+
} })).exhaustive();
|
|
5683
|
+
}
|
|
5684
|
+
function formatReason(denied) {
|
|
5685
|
+
return `[${denied.rule}] ${denied.reason}`;
|
|
5686
|
+
}
|
|
5675
5687
|
//#endregion
|
|
5676
5688
|
//#region src/install/hook-config.ts
|
|
5677
|
-
function buildHookConfig(harness, command) {
|
|
5689
|
+
function buildHookConfig(harness, command, event = "PreToolUse") {
|
|
5678
5690
|
const entry = M(harness).with("claude", () => ({
|
|
5679
5691
|
matcher: ".*",
|
|
5680
5692
|
hooks: [{
|
|
@@ -5693,13 +5705,17 @@ function buildHookConfig(harness, command) {
|
|
|
5693
5705
|
command
|
|
5694
5706
|
}]
|
|
5695
5707
|
})).exhaustive();
|
|
5696
|
-
return JSON.stringify({ hooks: {
|
|
5708
|
+
return JSON.stringify({ hooks: { [event]: [entry] } }, null, 2);
|
|
5697
5709
|
}
|
|
5698
5710
|
const SETTINGS_PATHS = {
|
|
5699
5711
|
claude: "~/.claude/settings.json",
|
|
5700
5712
|
codex: "~/.codex/hooks.json",
|
|
5701
5713
|
muse: "~/.config/muse/settings.json"
|
|
5702
5714
|
};
|
|
5715
|
+
const EVENT_NOTES = {
|
|
5716
|
+
PreToolUse: ["The hook judges every tool call, including the ones the harness would have run", "without asking."],
|
|
5717
|
+
PermissionRequest: ["Claude Code sends this event only when it is about to ask you, so the hook", "answers those prompts and judges nothing the harness already allows."]
|
|
5718
|
+
};
|
|
5703
5719
|
const SETUP_NOTES = {
|
|
5704
5720
|
claude: ["A tool your permission settings already deny is refused before the hook sees it, so", "auto-mode can only narrow what the harness would have allowed."],
|
|
5705
5721
|
codex: [
|
|
@@ -6200,4 +6216,4 @@ function toUnquoted(word) {
|
|
|
6200
6216
|
return (first === "'" || first === "\"") && word.at(-1) === first ? word.slice(1, -1) : word;
|
|
6201
6217
|
}
|
|
6202
6218
|
//#endregion
|
|
6203
|
-
export { loadPolicy as a,
|
|
6219
|
+
export { resolveConfigPath as _, loadPolicy as a, SETUP_NOTES as c, parsePayload as d, detectHarness as f, resolveApiKey as g, loadConfig as h, readTranscript as i, buildHookConfig as l, PRESETS as m, classifyWithModel as n, EVENT_NOTES as o, DEFAULT_CONFIG as p, parseModelVerdict as r, SETTINGS_PATHS as s, classifyLocally as t, renderVerdict as u };
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as loadPolicy, c as
|
|
2
|
+
import { _ as resolveConfigPath, a as loadPolicy, c as SETUP_NOTES, d as parsePayload, h as loadConfig, l as buildHookConfig, n as classifyWithModel, o as EVENT_NOTES, s as SETTINGS_PATHS, t as classifyLocally, u as renderVerdict } from "./classify-locally-BRMllwcd.js";
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { text } from "node:stream/consumers";
|
|
5
5
|
//#region src/cli.ts
|
|
@@ -13,6 +13,7 @@ Usage:
|
|
|
13
13
|
Options:
|
|
14
14
|
--classifier <path> Use this framework file instead of the shipped one
|
|
15
15
|
--rules <path> Use this rule list instead of the shipped one
|
|
16
|
+
--event <event> With init: pre-tool-use, the default, or permission-request
|
|
16
17
|
--explain With run: also write the reasoning to stderr
|
|
17
18
|
--local-only With run: skip the model tier
|
|
18
19
|
`;
|
|
@@ -31,19 +32,27 @@ async function run(explain, localOnly) {
|
|
|
31
32
|
if (payload === null) return printNote(explain, "not a tool gate this hook judges, so no verdict");
|
|
32
33
|
const local = classifyLocally(payload);
|
|
33
34
|
if (local.kind === "allow") {
|
|
34
|
-
|
|
35
|
+
writeVerdict(payload.event, { kind: "allow" });
|
|
35
36
|
return printNote(explain, `allowed by ${local.exception} (${payload.harness}, local)`);
|
|
36
37
|
}
|
|
37
38
|
if (localOnly) return printNote(explain, `${payload.toolName} needs the model tier, which --local-only skipped`);
|
|
38
39
|
const config = await loadConfig();
|
|
39
40
|
const outcome = await classifyWithModel(payload, config);
|
|
40
|
-
if (outcome.verdict !== null)
|
|
41
|
+
if (outcome.verdict !== null) writeVerdict(payload.event, outcome.verdict);
|
|
41
42
|
return printNote(explain, outcome.note);
|
|
42
43
|
}
|
|
44
|
+
function writeVerdict(event, verdict) {
|
|
45
|
+
const rendered = renderVerdict(event, verdict);
|
|
46
|
+
if (rendered !== null) process.stdout.write(rendered);
|
|
47
|
+
}
|
|
43
48
|
function printNote(explain, message) {
|
|
44
49
|
if (explain) process.stderr.write(`auto-mode: ${message}\n`);
|
|
45
50
|
return 0;
|
|
46
51
|
}
|
|
52
|
+
const HOOK_EVENTS = {
|
|
53
|
+
"pre-tool-use": "PreToolUse",
|
|
54
|
+
"permission-request": "PermissionRequest"
|
|
55
|
+
};
|
|
47
56
|
async function main(argv) {
|
|
48
57
|
const args = parseArgs({
|
|
49
58
|
args: [...argv],
|
|
@@ -51,6 +60,7 @@ async function main(argv) {
|
|
|
51
60
|
options: {
|
|
52
61
|
classifier: { type: "string" },
|
|
53
62
|
rules: { type: "string" },
|
|
63
|
+
event: { type: "string" },
|
|
54
64
|
explain: { type: "boolean" },
|
|
55
65
|
"local-only": { type: "boolean" },
|
|
56
66
|
help: {
|
|
@@ -71,10 +81,19 @@ async function main(argv) {
|
|
|
71
81
|
process.stderr.write("auto-mode init: name a harness — claude, codex or muse\n");
|
|
72
82
|
return 2;
|
|
73
83
|
}
|
|
84
|
+
const event = HOOK_EVENTS[args.values.event ?? "pre-tool-use"];
|
|
85
|
+
if (event === void 0) {
|
|
86
|
+
process.stderr.write("auto-mode init: --event takes pre-tool-use or permission-request\n");
|
|
87
|
+
return 2;
|
|
88
|
+
}
|
|
89
|
+
if (event === "PermissionRequest" && harness !== "claude") {
|
|
90
|
+
process.stderr.write(`auto-mode init: only Claude Code sends PermissionRequest\n`);
|
|
91
|
+
return 2;
|
|
92
|
+
}
|
|
74
93
|
process.stdout.write(`# Add this to ${SETTINGS_PATHS[harness]}\n`);
|
|
75
94
|
process.stdout.write(`# Configuration lives at ${resolveConfigPath()}\n`);
|
|
76
|
-
process.stdout.write(`${buildHookConfig(harness, `${process.execPath} ${process.argv[1] ?? "auto-mode"} run
|
|
77
|
-
for (const line of SETUP_NOTES[harness]) process.stdout.write(`# ${line}\n`);
|
|
95
|
+
process.stdout.write(`${buildHookConfig(harness, `${process.execPath} ${process.argv[1] ?? "auto-mode"} run`, event)}\n`);
|
|
96
|
+
for (const line of [...SETUP_NOTES[harness], ...EVENT_NOTES[event]]) process.stdout.write(`# ${line}\n`);
|
|
78
97
|
return 0;
|
|
79
98
|
}
|
|
80
99
|
if (command === "print-prompt") {
|
package/dist/index.d.ts
CHANGED
|
@@ -51,10 +51,10 @@ export declare function detectHarness(payload: Readonly<Record<string, unknown>>
|
|
|
51
51
|
export declare function parsePayload(body: unknown): HookPayload | null;
|
|
52
52
|
//#endregion
|
|
53
53
|
//#region src/harness/render-verdict.d.ts
|
|
54
|
-
export declare function renderVerdict(event: HookEvent, verdict: Verdict): string;
|
|
54
|
+
export declare function renderVerdict(event: HookEvent, verdict: Verdict): string | null;
|
|
55
55
|
//#endregion
|
|
56
56
|
//#region src/install/hook-config.d.ts
|
|
57
|
-
export declare function buildHookConfig(harness: Harness, command: string): string;
|
|
57
|
+
export declare function buildHookConfig(harness: Harness, command: string, event?: HookEvent): string;
|
|
58
58
|
export declare const SETTINGS_PATHS: Readonly<Record<Harness, string>>;
|
|
59
59
|
//#endregion
|
|
60
60
|
//#region src/model/classify-with-model.d.ts
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as resolveConfigPath, a as loadPolicy, d as parsePayload, f as detectHarness, g as resolveApiKey, h as loadConfig, i as readTranscript, l as buildHookConfig, m as PRESETS, n as classifyWithModel, p as DEFAULT_CONFIG, r as parseModelVerdict, s as SETTINGS_PATHS, t as classifyLocally, u as renderVerdict } from "./classify-locally-BRMllwcd.js";
|
|
2
2
|
export { DEFAULT_CONFIG, PRESETS, SETTINGS_PATHS, buildHookConfig, classifyLocally, classifyWithModel, detectHarness, loadConfig, loadPolicy, parseModelVerdict, parsePayload, readTranscript, renderVerdict, resolveApiKey, resolveConfigPath };
|