auto-mode 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Geoff Whatley
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,145 @@
1
+ # auto-mode
2
+
3
+ A permission classifier that runs as a hook. It reads the agent's next action and
4
+ answers allow, deny, or nothing at all — so you can leave an agent unattended
5
+ without approving every step.
6
+
7
+ Built for coding agents that are **not** Claude, in harnesses that are not Claude
8
+ Code. Claude Code has its own auto mode; this gives the same shape to everything
9
+ else.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ npm i -g auto-mode
15
+ auto-mode init claude # or codex, or muse
16
+ ```
17
+
18
+ `init` prints the hook entry and the path to paste it into. It does not write
19
+ that file. Your settings are yours, and a tool that installs its own restraints
20
+ can remove them.
21
+
22
+ ## The two tiers
23
+
24
+ 1. **Local rules.** Deterministic matching, no network call, well under a
25
+ millisecond. It answers **allow** or **escalate** and never denies — a wrong
26
+ local allow costs one unwatched action, a wrong local deny stops work you
27
+ asked for.
28
+ 2. **The model.** Everything the first tier declines goes to a small reasoning
29
+ model with the policy as its system prompt. Measured at 13-24 seconds on a
30
+ hard case, which is why tier one has to catch the ordinary ones.
31
+
32
+ ## The policy
33
+
34
+ Two files, and everything in them is what the model reads. Nothing there is
35
+ addressed to you.
36
+
37
+ | File | What it holds | Change it to |
38
+ |---|---|---|
39
+ | `policy/rules.md` | 22 rules, 7 exceptions | Change what is blocked |
40
+ | `policy/classifier.md` | Threat model, consent bar, evaluation rules, output contract | Change how judgement works |
41
+
42
+ `classifier.md` carries a `<rules>` marker on its own line. At run time auto-mode
43
+ replaces it with the whole of `policy/rules.md` and the two become one prompt.
44
+ `auto-mode print-prompt` writes exactly what the model receives.
45
+
46
+ **Read `policy/rules.md` before you turn this on.** If you disagree with a rule,
47
+ change the rule.
48
+
49
+ ## Configuration
50
+
51
+ `~/.config/auto-mode/config.json`. Everything has a default; the file is
52
+ optional.
53
+
54
+ ```json
55
+ {
56
+ "preset": "spark",
57
+ "provider": { "apiKeyCommand": "my-key-helper" },
58
+ "onFailure": "defer"
59
+ }
60
+ ```
61
+
62
+ Three presets: `spark` (the default), `claude`, `glm`. Each names a base URL, a
63
+ model, a key source, and the three settings that move together — whether the
64
+ model reasons, its token budget, and its timeout.
65
+
66
+ | Key | Default | Meaning |
67
+ |---|---|---|
68
+ | `preset` | `spark` | Which provider to start from |
69
+ | `provider.*` | from the preset | Override any single field |
70
+ | `classifierPath`, `rulesPath` | the shipped files | Use your own policy |
71
+ | `transcriptEntries` | `40` | How much history the model sees |
72
+ | `onFailure` | `defer` | `defer` writes nothing; `deny` fails closed |
73
+
74
+ **Muse Spark is the default** because it reasons, it was correct on every case
75
+ measured, and a contributor-tier call costs roughly a fortieth of the
76
+ alternatives. Its token budget is a floor, not a preference: below it, Spark
77
+ returns nothing at all.
78
+
79
+ The key comes from `provider.apiKeyEnv` first, then from `provider.apiKeyCommand`
80
+ if that is unset. Muse runs hooks with a scrubbed environment, so under Muse the
81
+ command is the only one that works.
82
+
83
+ ## Writing nothing is an answer
84
+
85
+ It means auto-mode has no opinion, and the harness then does what it would have
86
+ done alone. That is the output for an unknown harness, an event that is not a
87
+ tool gate, malformed input, a missing key, and a model that times out. So this
88
+ can only turn an ask into an allow or a deny — it never removes a gate that was
89
+ already there.
90
+
91
+ Set `onFailure` to `deny` if you would rather fail closed.
92
+
93
+ ## Harnesses
94
+
95
+ Claude Code, Codex, and Muse Code. The verdict contract is the same for all
96
+ three, and all three were verified end to end: the hook denied a real tool call
97
+ and each harness stopped it.
98
+
99
+ They differ in how the hook is registered, and each difference makes the hook
100
+ silently not run if you get it wrong. `auto-mode init` prints these.
101
+
102
+ | Harness | Match every tool | Also needed |
103
+ |---|---|---|
104
+ | Claude | `"matcher": ".*"` | — |
105
+ | Codex | omit `matcher` | `hooks = true`, and trust the hook once |
106
+ | Muse | `"matcher": ""` | no `timeout_ms` key |
107
+
108
+ Three traps worth naming:
109
+
110
+ - Claude matches tool names against a **regular expression**. `"*"` is not valid
111
+ regex and `""` matches only a tool named empty; both leave the hook installed
112
+ and never firing. Muse reads `""` as every tool, so the same value means
113
+ opposite things in the two harnesses.
114
+ - Codex will not run an **untrusted** hook. It is skipped silently until you
115
+ trust it once; the answer is recorded under `[hooks.state]` in `config.toml`.
116
+ - Muse **rejects a handler carrying `timeout_ms`**, reporting an unknown field
117
+ and skipping the whole handler.
118
+
119
+ Detection order matters too. Muse is identified by `model_provider`, which the
120
+ others do not send. Claude sends `prompt_id`. Codex sends `turn_id` — and so
121
+ does Muse, so Muse has to be tested first.
122
+
123
+ ## What this cannot do
124
+
125
+ - **A hook that cannot start fails open.** If the command is missing or crashes,
126
+ the harness logs it and carries on. `onFailure: "deny"` cannot help, because
127
+ the process never runs.
128
+ - **It can only narrow what the harness already allows.** A tool your own
129
+ permission settings deny is refused before the hook sees it.
130
+ - **The agent's own judgement comes first.** A model that refuses to issue a
131
+ command means the hook is never consulted for it.
132
+
133
+ ## Commands
134
+
135
+ | Command | What it does |
136
+ |---|---|
137
+ | `auto-mode run` | Read a payload on stdin, write a verdict on stdout |
138
+ | `auto-mode run --explain` | Also write the reasoning to stderr |
139
+ | `auto-mode run --local-only` | Skip the model tier |
140
+ | `auto-mode print-prompt` | Print the assembled system prompt |
141
+ | `auto-mode init <harness>` | Print the hook entry to add |
142
+
143
+ ## Licence
144
+
145
+ MIT.