@seanmozeik/tripwire 0.6.7 → 0.7.1

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.
Files changed (55) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -141
  3. package/dist/index.js +35 -0
  4. package/dist/tripwire-cli.js +2 -10
  5. package/dist/tripwire-hook.js +3 -0
  6. package/dist/tripwire-pi.js +4 -0
  7. package/dist/tripwire.js +135 -90
  8. package/dist/types/dispatch.d.ts +18 -0
  9. package/dist/types/index.d.ts +6 -0
  10. package/dist/types/lib/bash.d.ts +27 -0
  11. package/dist/types/lib/config.d.ts +110 -0
  12. package/dist/types/lib/cursor.d.ts +16 -0
  13. package/dist/types/lib/decision.d.ts +13 -0
  14. package/dist/types/lib/diff.d.ts +3 -0
  15. package/dist/types/lib/event.d.ts +45 -0
  16. package/dist/types/lib/log.d.ts +2 -0
  17. package/dist/types/lib/secrets.d.ts +41 -0
  18. package/dist/types/rules/bash-deny.d.ts +5 -0
  19. package/dist/types/rules/bash-git.d.ts +5 -0
  20. package/dist/types/rules/bash-network-install.d.ts +4 -0
  21. package/dist/types/rules/bash-redirect.d.ts +4 -0
  22. package/dist/types/rules/bash-scoped-rm.d.ts +5 -0
  23. package/dist/types/rules/bash-tar-explosion.d.ts +4 -0
  24. package/dist/types/rules/config-custom.d.ts +6 -0
  25. package/dist/types/rules/lazy-code.d.ts +4 -0
  26. package/dist/types/rules/path-protect.d.ts +12 -0
  27. package/dist/types/rules/post-secret-scrub.d.ts +12 -0
  28. package/dist/types/rules/read-protect.d.ts +4 -0
  29. package/dist/types/rules/tool-policy.d.ts +5 -0
  30. package/package.json +53 -22
  31. package/dist/tripwire-cli.js.jsc +0 -0
  32. package/dist/tripwire.js.jsc +0 -0
  33. package/src/cli.ts +0 -264
  34. package/src/dispatch.ts +0 -354
  35. package/src/index.ts +0 -6
  36. package/src/lib/bash.ts +0 -1284
  37. package/src/lib/config.ts +0 -127
  38. package/src/lib/decision.ts +0 -36
  39. package/src/lib/diff.ts +0 -26
  40. package/src/lib/event.ts +0 -106
  41. package/src/lib/install.ts +0 -238
  42. package/src/lib/log.ts +0 -24
  43. package/src/lib/secrets.ts +0 -121
  44. package/src/rules/bash-deny.ts +0 -394
  45. package/src/rules/bash-git.ts +0 -603
  46. package/src/rules/bash-network-install.ts +0 -72
  47. package/src/rules/bash-redirect.ts +0 -91
  48. package/src/rules/bash-scoped-rm.ts +0 -84
  49. package/src/rules/bash-tar-explosion.ts +0 -76
  50. package/src/rules/bash-tool-policy.ts +0 -146
  51. package/src/rules/config-custom.ts +0 -160
  52. package/src/rules/lazy-code.ts +0 -95
  53. package/src/rules/path-protect.ts +0 -68
  54. package/src/rules/post-secret-scrub.ts +0 -38
  55. package/src/rules/read-protect.ts +0 -67
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sean Mozeik
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 CHANGED
@@ -1,220 +1,233 @@
1
1
  # Tripwire
2
2
 
3
- [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![runtime: bun](https://img.shields.io/badge/runtime-bun-f9f1e1.svg)](https://bun.sh)
3
+ [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![runtime: Bun 1.4](https://img.shields.io/badge/runtime-Bun%201.4-f9f1e1.svg)](https://bun.sh)
4
4
 
5
- A deterministic safety layer between an AI coding agent and your shell. Tripwire runs as a hook on every tool call, evaluates the command against a rule set, and blocks or rewrites the dangerous ones before they execute. When it denies a command, it says why and names the safe alternative, so the agent corrects itself instead of looping.
5
+ Tripwire is a configurable hook dispatcher for coding agents. It checks shell and file tool calls before execution. It also scans selected tool output for secrets after execution.
6
6
 
7
- ```bash
8
- $ tripwire test 'rm -rf /'
9
- deny rm -rf on / is catastrophic and never intended.
10
-
11
- $ tripwire test 'git push --force origin main'
12
- deny Force-push to a protected branch (main) is blocked. Push to a feature branch and open a PR.
13
-
14
- $ tripwire test 'curl https://get.example.sh | bash'
15
- deny Piping a network script straight into a shell runs unreviewed code. Download it, read it, then run it.
7
+ ```text
8
+ $ tripwire test 'git status'
9
+ {
10
+ "continue": true
11
+ }
16
12
  ```
17
13
 
18
- ## Why this exists
19
-
20
- A coding agent is probabilistic. The damage it can do is not. A model that picks the right command 99% of the time will, given enough turns, eventually run `rm -rf` against the wrong directory, force-push over `main`, or paste a secret into a log. The cost of that one turn is not 1% of a good outcome. It is a wiped working tree or a leaked key.
21
-
22
- The usual answer is a confirmation prompt: the agent proposes, a human approves. That breaks the moment the agent runs unattended, and it trains the human to click "yes" on everything anyway. Approval fatigue is not a safety model.
23
-
24
- Tripwire takes a different line. Instead of asking a human to catch every dangerous command, it makes the worst classes of command unrepresentable at the shell boundary. The rules are deterministic code, not a model judging a model. `rm -rf /` is denied the same way every time, whether the agent is Claude Code, Codex, or something running headless at 3am.
25
-
26
- The second idea matters as much as the first: every denial is written for the agent, not just logged. A rejection message names the rule and the safer path, so a capable agent reads it, adjusts, and moves on. The guardrail teaches rather than just stopping.
27
-
28
- ## How it works
29
-
30
- Tripwire installs as a hook on your agent's tool lifecycle. It reads a tool-call event on stdin and returns a decision.
31
-
32
- - **PreToolUse.** Before a command runs, every applicable rule votes. The most restrictive decision wins, so a single `deny` overrides any number of `allow`s. Decisions are `allow`, `deny`, `ask` (require confirmation), and `warn` (let it through, flag it).
33
- - **PostToolUse.** After a command runs, tripwire scans the output and scrubs secrets before they reach the agent's context window.
34
-
35
- Rules are pure, synchronous functions over the parsed command. Bash commands are tokenized with a real shell parser, not regex, so `git push` matches `git push` with any arguments while leaving `git push-mirror` alone, and a destructive `rm` buried inside a wrapper command is still seen for what it is.
36
-
37
- ## What it protects against
38
-
39
- The defaults are opinionated but conservative. Nothing here blocks ordinary work.
14
+ ## Package support
40
15
 
41
- **Catastrophic commands.** `rm -rf /`, fork bombs, `dd` to raw disks, and the handful of one-liners that have no safe use.
16
+ The main package installs on every platform supported by Bun 1.4. It contains a minified Bun runtime bundle, a separate library bundle, and TypeScript declarations.
42
17
 
43
- **Scoped destruction.** `rm` and `find -delete` are allowed only inside build and cache directories (`dist`, `build`, `node_modules`, `.next`, `/tmp`, and the rest). A delete anywhere else is denied with a pointer to `trash` or a graveyard tool, both recoverable.
44
-
45
- **Git policy.** Read-only git is free. History rewriting (`rebase -i`, `filter-branch`, `commit --amend`), working-tree destruction (`reset --hard`, `clean -fd`, `checkout .`), force-push, and direct push to protected branches (`main`, `master`, `develop`, `production`, `release`) are blocked. Commits are required to use Conventional Commits format and an inline `-m` message.
18
+ ```bash
19
+ bun install --global @seanmozeik/tripwire
20
+ ```
46
21
 
47
- **Network install scripts.** `curl | bash` and `wget | sh` are denied. Unreviewed code from the network does not get a shell.
22
+ On Apple Silicon Macs, npm also installs the optional `@seanmozeik/tripwire-darwin-arm64` package. Tripwire selects its compiled Bun bytecode executable. On Linux, Windows, and Intel Macs, Tripwire runs the portable JavaScript bundle with the installed Bun runtime.
48
23
 
49
- **Tar bombs.** Extractions that would escape the target directory or overwrite outside it are caught before they unpack.
24
+ The main package installs two commands:
50
25
 
51
- **Package-manager and tool policy.** Configurable nudges toward a single toolchain (for example bun over npm/pnpm/yarn) and toward modern equivalents of common utilities.
26
+ - `tripwire` runs interactive commands.
27
+ - `tripwire-hook` handles agent hook events.
52
28
 
53
- **File protection.** Reads and writes to `.env`, `.ssh/`, `*.pem`, `id_rsa*`, and similar are blocked so credentials never enter agent context.
29
+ Run `tripwire install <host>` after installation. On Apple Silicon, the installer writes the direct native executable path into agent settings to keep hook startup fast. Other platforms use Bun and the portable bundle. Pi and Oh My Pi use the same runtime selection through their adapter.
54
30
 
55
- **Secret scrubbing.** Tokens and keys in command output are redacted in the PostToolUse pass.
31
+ Library imports always use `dist/index.js` and `dist/types`. They do not load or execute the platform binary.
56
32
 
57
- **Lazy-code warnings.** `TODO`, `FIXME`, and placeholder stubs in written code are flagged so half-finished work does not land silently.
33
+ ## Secret scanner requirement
58
34
 
59
- Every default is configurable, and you can add your own allow and deny rules on top.
35
+ Post-tool scanning requires Betterleaks 1.5.0 or later. Install `betterleaks` on `PATH` before you enable a post-tool hook. The default command is `betterleaks`. You can set another executable path in personal config.
60
36
 
61
- ## Install
37
+ Tripwire runs this command without a shell or temporary file:
62
38
 
63
- ```bash
64
- bun install -g @seanmozeik/tripwire
39
+ ```text
40
+ betterleaks stdin --report-format json --report-path -
65
41
  ```
66
42
 
67
- This puts two binaries on your PATH: `tripwire` (the CLI) and `tripwire-hook` (the dispatcher your agent calls).
43
+ Scanner failures are closed failures. If the executable is missing, times out, exits with an error, or returns malformed JSON, Tripwire sends a post-tool denial to hosts that support one. The error does not include scanned text, secret values, or raw scanner stderr.
68
44
 
69
- ## Wiring it into an agent
45
+ ## Install agent hooks
70
46
 
71
- Use the installer to configure hooks automatically:
47
+ Run one installer after the package and Betterleaks are available:
72
48
 
73
49
  ```bash
74
- tripwire install claude # Claude Code
75
- tripwire install codex # Codex
76
- tripwire install pi # pi-guardrails
77
- tripwire install all # every supported agent
78
- ```
79
-
80
- To wire it by hand, point the agent's hook events at `tripwire-hook`.
81
-
82
- **Claude Code** (`~/.claude/settings.json`):
83
-
84
- ```jsonc
85
- {
86
- "hooks": {
87
- "PreToolUse": [{ "hooks": [{ "type": "command", "command": "/path/to/tripwire-hook" }] }],
88
- "PostToolUse": [{ "hooks": [{ "type": "command", "command": "/path/to/tripwire-hook" }] }],
89
- },
90
- }
50
+ tripwire install claude
51
+ tripwire install codex
52
+ tripwire install cursor
53
+ tripwire install pi
54
+ tripwire install oh-my-pi
55
+ tripwire install all
91
56
  ```
92
57
 
93
- **Codex** uses the same hook format as Claude Code.
58
+ The installers update these paths:
94
59
 
95
- **Devin** and other agents: configure the agent to call `tripwire-hook` on tool events.
60
+ | Host | Files |
61
+ | ------------ | ----------------------------------------------------------------- |
62
+ | Claude Code | `~/.claude/settings.json` |
63
+ | Codex | `~/.codex/hooks.json`, `~/.codex/config.toml` |
64
+ | Cursor Agent | `~/.cursor/hooks.json` |
65
+ | Pi | `~/.pi/agent/settings.json`, `~/.pi/agent/extensions/tripwire.js` |
66
+ | Oh My Pi | `~/.omp/agent/extensions/tripwire.js` |
96
67
 
97
- ## Testing rules
68
+ Settings updates use same-directory atomic replacement. Existing file modes, unknown JSON fields, and unrelated Codex TOML bytes remain unchanged. Existing config symlinks remain symlinks.
98
69
 
99
- `tripwire test` evaluates a command without running it, so you can check what a rule does before trusting it in a live loop.
70
+ Pi and Oh My Pi use the native extension API. Their extension paths point to the packaged `tripwire-pi.js` adapter. Pi removes old Claude-style Tripwire hooks from its settings after the extension link is available. The adapter sends one batch to Tripwire for a multi-file edit.
100
71
 
101
- ```bash
102
- tripwire test 'rm -rf /' # a bash command
103
- tripwire test --tool=Read --path=.env # a file read
104
- tripwire test --post --tool=Bash --stdout='ghp_TOKEN' # a PostToolUse output scan
105
- ```
72
+ ### Manual hook command
106
73
 
107
- ## Configuration
74
+ Claude Code and Codex use `tripwire-hook` for `PreToolUse` and `PostToolUse`. Codex also requires `hooks = true` in the `[features]` table of `~/.codex/config.toml`.
108
75
 
109
- Drop a `~/.config/tripwire/config.json` to extend or adjust the defaults. Unknown keys are rejected loudly rather than ignored, so a typo fails fast instead of silently disabling a rule.
76
+ Cursor uses event-specific commands because some payloads do not include the configured event name:
110
77
 
111
78
  ```json
112
79
  {
113
- "git": {
114
- "protectedBranches": ["main", "master", "develop", "production", "release"],
115
- "enforceConventionalCommits": true
116
- },
117
- "safePaths": {
118
- "relative": ["dist", "build", ".next", "node_modules"],
119
- "absolute": ["/tmp", "/var/tmp"]
120
- },
121
- "blockedCommands": [
122
- { "pattern": "dangerous-tool", "message": "Use safer-alternative instead", "action": "deny" }
123
- ],
124
- "allowedCommands": [
125
- { "pattern": "my-custom-tool", "message": "Allowing my-custom-tool per your configuration" }
126
- ]
80
+ "version": 1,
81
+ "hooks": {
82
+ "preToolUse": [{ "command": "tripwire-hook --cursor-event preToolUse", "failClosed": true }],
83
+ "postToolUse": [{ "command": "tripwire-hook --cursor-event postToolUse" }],
84
+ "beforeShellExecution": [
85
+ { "command": "tripwire-hook --cursor-event beforeShellExecution", "failClosed": true }
86
+ ],
87
+ "afterShellExecution": [{ "command": "tripwire-hook --cursor-event afterShellExecution" }],
88
+ "beforeReadFile": [
89
+ { "command": "tripwire-hook --cursor-event beforeReadFile", "failClosed": true }
90
+ ],
91
+ "afterFileEdit": [{ "command": "tripwire-hook --cursor-event afterFileEdit" }]
92
+ }
127
93
  }
128
94
  ```
129
95
 
130
- ### Options
96
+ ## Decisions and failure policy
97
+
98
+ Each applicable rule returns `allow`, `warn`, `ask`, or `deny`. The most restrictive result wins.
131
99
 
132
- **`git`**
100
+ Production hook evaluation isolates each rule. If one rule throws, Tripwire logs the defect, treats that rule as `allow`, and continues with later rules. The synchronous library API has the same throw isolation. Effect timeouts do not interrupt synchronous CPU work.
133
101
 
134
- - `protectedBranches` (string[], default `["main", "master", "develop", "production", "release"]`): branches that cannot be pushed to directly.
135
- - `enforceConventionalCommits` (boolean, default `true`): require Conventional Commits format for commit messages.
102
+ Failure behavior depends on the stage and host:
136
103
 
137
- **`safePaths`**
104
+ - A missing personal config uses defaults. Invalid JSON, unknown keys, permission errors, and other read errors deny the next pre-tool call.
105
+ - Post-tool scanning still runs when personal config is invalid. It uses the default scanner settings for that call.
106
+ - A malformed native single-event hook input returns the host allow response. A malformed private batch is denied.
107
+ - Invalid Cursor pre-tool input is denied. Cursor post-tool output cannot be replaced after execution, so Tripwire returns the host allow response.
108
+ - Pi and Oh My Pi deny a tool call when the dispatcher fails. A post-tool denial or dispatcher failure stops the session.
138
109
 
139
- - `relative` (string[]): additional relative paths where destructive operations are allowed.
140
- - `absolute` (string[]): additional absolute paths where destructive operations are allowed.
110
+ PowerShell pre-tool calls are denied because Tripwire does not parse PowerShell grammar. PowerShell post-tool output is still scanned.
141
111
 
142
- Built-in safe paths already cover `dist`, `build`, `.next`, `node_modules`, `/tmp`, `/var/tmp`, and other common build and cache directories.
112
+ Internal errors are written to `~/.claude/tripwire.log`. Logging failure does not change the hook response.
143
113
 
144
- **`blockedCommands`** is an array of custom denials:
114
+ ## Built-in checks
145
115
 
146
- - `pattern` (string): the command to match, parsed as shell tokens.
147
- - `message` (string): what the agent sees when blocked.
148
- - `action` (`"deny"` | `"ask"`, default `"deny"`): deny outright or require confirmation.
149
- - `requiresFlags` (string[]): match only when every listed flag is present, including `--flag=value` form.
150
- - `forbidsFlagValues` (array): match only when each listed flag carries one of the listed values.
116
+ Tripwire includes checks for these operations:
151
117
 
152
- **`allowedCommands`** is an array of custom allows that override blocks. Same fields as `blockedCommands`.
118
+ - catastrophic shell commands such as `rm -rf /`, fork bombs, and raw-disk writes
119
+ - destructive Git commands and optional protected-branch policy
120
+ - network scripts piped into a shell
121
+ - deletion outside configured build, cache, and temporary paths
122
+ - protected files such as `.env`, SSH keys, private keys, and cloud credentials
123
+ - redirects, copies, and moves that target protected paths through symlink aliases
124
+ - optional package-manager and utility preferences from personal config
125
+ - new `TODO`, `FIXME`, fallback, and placeholder markers in code edits
153
126
 
154
- ### How command matching works
127
+ The archive check is narrow. It denies `tar` extraction with `x` or `--extract` when `-C` or `--directory` targets `/` or the home directory. It applies the same destination rule to `unzip -d`. Archive listing, including `tar -tf archive.tar -C /`, is allowed. Tripwire does not inspect archive member paths.
155
128
 
156
- Patterns are parsed with the same shell tokenizer as the rest of tripwire, so matching is structural rather than substring.
129
+ Compound Bash forms are inspected conservatively. Tripwire follows executable commands after `if`, `elif`, `then`, `else`, `while`, `until`, and `do`. It denies unsupported structures when it cannot identify every executable branch.
157
130
 
158
- - `rm` matches any `rm` invocation.
159
- - `git push` matches `git push` with any arguments.
160
- - `gog calendar create` matches that head plus subcommand path, not every `gog` command.
161
- - `requiresFlags: ["--attendees"]` matches `--attendees X` and `--attendees=X`.
162
- - `forbidsFlagValues: [{ "flag": "--send-updates", "values": ["all"] }]` matches `--send-updates all` and `--send-updates=all`.
131
+ Protected-path checks compare the submitted path and its resolved target. New writes resolve the deepest existing parent, which prevents a symlink alias from hiding a protected destination.
163
132
 
164
- A worked example, blocking calendar invites that would send email until a human has reviewed them:
133
+ ## Personal config
134
+
135
+ Personal workflow preferences belong in `~/.config/tripwire/config.json`. A missing file uses open-source defaults. A present file with unknown keys or invalid values fails loudly.
165
136
 
166
137
  ```json
167
138
  {
139
+ "git": { "protectedBranches": ["main", "production"], "enforceConventionalCommits": true },
140
+ "safePaths": { "relative": ["dist", "build", "node_modules"], "absolute": ["/tmp", "/var/tmp"] },
141
+ "toolPolicies": [
142
+ {
143
+ "rule": "project-package-manager",
144
+ "executables": ["npm", "pnpm", "yarn"],
145
+ "action": "deny",
146
+ "message": "Use the package manager selected by this workspace."
147
+ }
148
+ ],
168
149
  "blockedCommands": [
169
150
  {
170
151
  "pattern": "brew install",
171
- "message": "Pin an explicit version when installing.",
152
+ "message": "Pin an explicit version before installation.",
172
153
  "action": "ask"
173
- },
174
- {
175
- "pattern": "gog calendar create",
176
- "requiresFlags": ["--attendees"],
177
- "message": "Calendar invite sends email; draft it in chat first.",
178
- "action": "deny"
179
- },
180
- {
181
- "pattern": "gog calendar delete",
182
- "forbidsFlagValues": [{ "flag": "--send-updates", "values": ["all", "externalOnly"] }],
183
- "message": "Cancellation sends email; use --send-updates none or ask first."
184
154
  }
185
- ]
155
+ ],
156
+ "allowedCommands": [
157
+ { "pattern": "project-safe-tool", "message": "This command is allowed by personal config." }
158
+ ],
159
+ "secretScanner": { "executable": "betterleaks", "timeoutMs": 5000 }
186
160
  }
187
161
  ```
188
162
 
189
- ## Bypassing a rule
163
+ `toolPolicies` accepts these optional match fields:
164
+
165
+ - `argumentsIncludeAll`
166
+ - `argumentsStartWith`
167
+ - `shortFlagsIncludeAll`
168
+
169
+ Custom blocked commands can use `requiresFlags` and `forbidsFlagValues`. Patterns are parsed as shell tokens, so `git push` matches that command path and does not match `git push-mirror`.
190
170
 
191
- When a blocked command is genuinely what you want, append a reason and tripwire lets it through:
171
+ ## Rule bypass
172
+
173
+ A bypass requires a shell comment, a colon, and a non-empty reason:
192
174
 
193
175
  ```bash
194
- rm -rf /tmp/test # tripwire-allow: cleaning a test directory
195
- git reset --hard HEAD~1 # tripwire-allow: undoing a mistaken commit
176
+ git reset --hard HEAD~1 # tripwire-allow: discard the local experiment after review
196
177
  ```
197
178
 
198
- The reason is required, which keeps the bypass deliberate and leaves a trail in the command itself.
179
+ These forms do not bypass a rule:
180
+
181
+ ```bash
182
+ git reset --hard HEAD~1 # tripwire-allow
183
+ git reset --hard HEAD~1 # tripwire-allow:
184
+ ```
185
+
186
+ The lazy-code rule accepts the same marker in an edited line. Catastrophic rules remain denied when a bypass reason is present.
187
+
188
+ ## Test a rule
189
+
190
+ `tripwire test` creates a hook event and does not run the command:
199
191
 
200
- ## Library usage
192
+ ```bash
193
+ tripwire test 'rm -rf /'
194
+ tripwire test --tool=Read --path=.env
195
+ tripwire test --post --tool=Bash --stdout='example output'
196
+ ```
201
197
 
202
- The decision primitives are exported for building custom rules or embedding tripwire elsewhere:
198
+ The post-tool example requires Betterleaks.
199
+
200
+ ## Library API
203
201
 
204
202
  ```typescript
205
- import { allow, deny, ask, warn } from '@seanmozeik/tripwire';
206
- import type { Decision, Config } from '@seanmozeik/tripwire';
203
+ import { allow, ask, deny, warn } from '@seanmozeik/tripwire';
204
+ import type { Config, Decision } from '@seanmozeik/tripwire';
207
205
  ```
208
206
 
209
207
  ## Development
210
208
 
211
209
  ```bash
212
- bun install
213
- bun run build # build dist/tripwire.js and dist/tripwire-cli.js
214
- bun run check # format, lint, typecheck
210
+ bun install --frozen-lockfile
211
+ bun run format:check
212
+ bun run lint
213
+ bun run typecheck
215
214
  bun test
215
+ bun run build
216
+ bun run verify
216
217
  ```
217
218
 
219
+ `bun run build` creates these artifacts:
220
+
221
+ - `dist/tripwire.js`: minified portable runtime for Bun 1.4 or later.
222
+ - `dist/index.js` and `dist/types`: public library bundle and declarations.
223
+ - `dist/tripwire-cli.js` and `dist/tripwire-hook.js`: runtime-selecting command launchers.
224
+ - `dist/tripwire-pi.js`: Pi and Oh My Pi adapter.
225
+ - `packages/darwin-arm64/bin/tripwire`: Apple Silicon bytecode executable.
226
+
227
+ `bun run verify` runs the format check, lint, type check, tests, and build. `prepublishOnly` calls the same local command.
228
+
229
+ Publish `@seanmozeik/tripwire-darwin-arm64` before `@seanmozeik/tripwire` for each release. The main package uses an exact optional dependency on the matching native package version.
230
+
218
231
  ## License
219
232
 
220
- MIT.
233
+ MIT. See [LICENSE](LICENSE).