@seanmozeik/tripwire 0.2.0 → 0.4.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.
package/README.md CHANGED
@@ -65,11 +65,7 @@ Create `~/.config/tripwire/config.json` to customize behavior:
65
65
  { "pattern": "dangerous-tool", "message": "Use safer-alternative instead", "action": "deny" }
66
66
  ],
67
67
  "allowedCommands": [
68
- {
69
- "pattern": "my-custom-tool",
70
- "message": "Allowing my-custom-tool per your configuration",
71
- "action": "allow"
72
- }
68
+ { "pattern": "my-custom-tool", "message": "Allowing my-custom-tool per your configuration" }
73
69
  ]
74
70
  }
75
71
  ```
@@ -100,6 +96,8 @@ Array of custom command blocks:
100
96
  - `pattern` (string) — Command pattern to block (uses shell parsing for matching)
101
97
  - `message` (string) — Error message shown when blocked
102
98
  - `action` (`"deny"` | `"ask"`, default: `"deny"`) — Whether to deny or ask for confirmation
99
+ - `requiresFlags` (string[], optional) — Match only when every listed flag is present, including `--flag=value` form
100
+ - `forbidsFlagValues` (array, optional) — Match only when each listed flag is present with one of the listed values
103
101
 
104
102
  #### `allowedCommands`
105
103
 
@@ -107,7 +105,8 @@ Array of custom command allows (overrides blocks):
107
105
 
108
106
  - `pattern` (string) — Command pattern to allow
109
107
  - `message` (string) — Message shown when allowed
110
- - `action` (string, default: `"allow"`) — Always `"allow"` for this context
108
+ - `requiresFlags` (string[], optional) — Same matching condition as `blockedCommands`
109
+ - `forbidsFlagValues` (array, optional) — Same matching condition as `blockedCommands`
111
110
 
112
111
  ### Shell-Based Command Matching
113
112
 
@@ -115,6 +114,9 @@ Command patterns in `blockedCommands` and `allowedCommands` use the same shell p
115
114
 
116
115
  - `rm` matches any `rm` command
117
116
  - `git push` matches `git push` with any arguments
117
+ - `gog calendar create` matches that head + subcommand path, not every `gog` command
118
+ - `requiresFlags: ["--attendees"]` matches `--attendees X` and `--attendees=X`
119
+ - `forbidsFlagValues: [{ "flag": "--send-updates", "values": ["all"] }]` matches `--send-updates all` and `--send-updates=all`
118
120
  - Patterns are parsed using shell-quote for accurate matching
119
121
  - More sophisticated than simple regex
120
122
 
@@ -127,6 +129,17 @@ Example:
127
129
  "pattern": "brew install",
128
130
  "message": "Use brew install with explicit version pinning",
129
131
  "action": "ask"
132
+ },
133
+ {
134
+ "pattern": "gog calendar create",
135
+ "requiresFlags": ["--attendees"],
136
+ "message": "Calendar invite sends email; draft it in chat first.",
137
+ "action": "deny"
138
+ },
139
+ {
140
+ "pattern": "gog calendar delete",
141
+ "forbidsFlagValues": [{ "flag": "--send-updates", "values": ["all", "externalOnly"] }],
142
+ "message": "Cancellation sends email; use --send-updates none or ask first."
130
143
  }
131
144
  ]
132
145
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun @bytecode @bun-cjs
3
- (function(exports, require, module, __filename, __dirname) {var S=require("@effect/platform-bun"),Q=require("effect"),w=require("effect/unstable/cli");var W={name:"@seanmozeik/tripwire",version:"0.2.0",description:"Opinionated hooks dispatcher for AI coding agents with configurable safety rules",license:"MIT",bin:{tripwire:"./dist/tripwire-cli.js","tripwire-hook":"./dist/tripwire.js"},files:["dist","package.json","src","README.md"],type:"module",exports:{".":"./src/index.ts"},publishConfig:{access:"public"},scripts:{build:"bun scripts/build.ts",prepublishOnly:"bun run build",check:"bun run format && bun run lint:fix && bun run typecheck",format:"oxfmt --write .",lint:"oxlint --tsconfig tsconfig.oxlint.json .","lint:fix":"oxlint --format agent --tsconfig tsconfig.oxlint.json --fix .",test:"bun test",typecheck:"tsc --noEmit"},dependencies:{"@effect/platform-bun":"^4.0.0-beta.65",effect:"^4.0.0-beta.65","shell-quote":"^1.8.3"},devDependencies:{"@types/bun":"^1.3.13","@types/shell-quote":"^1.7.5",oxfmt:"^0.48.0",oxlint:"^1.61.0","oxlint-tsgolint":"^0.22.1",typescript:"^6.0.3"},engines:{bun:">=1.0"}};var B=require("os"),N=globalThis.Bun,X="tripwire-hook",Z=(b)=>{if(!b)return[[{hooks:[{type:"command",command:X}]}],!1];let q=!1,x=b.map((L)=>({hooks:L.hooks.map((D)=>{if(D.command===X||D.command.endsWith("/tripwire-hook")){if(D.command!==X)return q=!0,{...D,command:X};return D}return D})}));if(x.some((L)=>L.hooks.some((D)=>D.command===X)))return[x,!q];return[[...x,{hooks:[{type:"command",command:X}]}],!1]},v=async()=>{let b=`${B.homedir()}/.claude/settings.json`,q=N.file(b);try{let x=await q.text(),y=JSON.parse(x);y.hooks??={};let[G,L]=Z(y.hooks.PreToolUse),[D,j]=Z(y.hooks.PostToolUse);if(y.hooks.PreToolUse=G,y.hooks.PostToolUse=D,L&&j)return{success:!0,message:`Already configured: ${b}`};return await q.write(`${JSON.stringify(y,null,2)}
3
+ (function(exports, require, module, __filename, __dirname) {var S=require("@effect/platform-bun"),Q=require("effect"),w=require("effect/unstable/cli");var W={name:"@seanmozeik/tripwire",version:"0.4.1",description:"Opinionated hooks dispatcher for AI coding agents with configurable safety rules",license:"MIT",bin:{tripwire:"./dist/tripwire-cli.js","tripwire-hook":"./dist/tripwire.js"},files:["dist","package.json","src","README.md"],type:"module",exports:{".":"./src/index.ts"},publishConfig:{access:"public"},scripts:{build:"bun scripts/build.ts",prepublishOnly:"bun run build",check:"bun run format && bun run lint:fix && bun run typecheck",format:"oxfmt --write .",lint:"oxlint --tsconfig tsconfig.oxlint.json .","lint:fix":"oxlint --format agent --tsconfig tsconfig.oxlint.json --fix .",test:"bun test",typecheck:"tsc --noEmit"},dependencies:{"@effect/platform-bun":"^4.0.0-beta.65",effect:"^4.0.0-beta.65","shell-quote":"^1.8.3"},devDependencies:{"@types/bun":"^1.3.13","@types/shell-quote":"^1.7.5",oxfmt:"^0.48.0",oxlint:"^1.61.0","oxlint-tsgolint":"^0.22.1",typescript:"^6.0.3"},engines:{bun:">=1.0"}};var B=require("os"),N=globalThis.Bun,X="tripwire-hook",Z=(b)=>{if(!b)return[[{hooks:[{type:"command",command:X}]}],!1];let q=!1,x=b.map((L)=>({hooks:L.hooks.map((D)=>{if(D.command===X||D.command.endsWith("/tripwire-hook")){if(D.command!==X)return q=!0,{...D,command:X};return D}return D})}));if(x.some((L)=>L.hooks.some((D)=>D.command===X)))return[x,!q];return[[...x,{hooks:[{type:"command",command:X}]}],!1]},v=async()=>{let b=`${B.homedir()}/.claude/settings.json`,q=N.file(b);try{let x=await q.text(),y=JSON.parse(x);y.hooks??={};let[G,L]=Z(y.hooks.PreToolUse),[D,j]=Z(y.hooks.PostToolUse);if(y.hooks.PreToolUse=G,y.hooks.PostToolUse=D,L&&j)return{success:!0,message:`Already configured: ${b}`};return await q.write(`${JSON.stringify(y,null,2)}
4
4
  `),{success:!0,message:`Updated ${b}`}}catch(x){let y=x instanceof Error?x.message:String(x);if(y.includes("No such file"))return{success:!1,message:`Config file not found: ${b}`};return{success:!1,message:`Failed to update Claude config: ${y}`}}},A=async()=>{let b=`${B.homedir()}/.pi/agent/settings.json`,q=N.file(b);try{let x=await q.text(),y=JSON.parse(x);y.hooks??={};let[G,L]=Z(y.hooks.PreToolUse),[D,j]=Z(y.hooks.PostToolUse);if(y.hooks.PreToolUse=G,y.hooks.PostToolUse=D,L&&j)return{success:!0,message:`Already configured: ${b}`};return await q.write(`${JSON.stringify(y,null,2)}
5
5
  `),{success:!0,message:`Updated ${b}`}}catch(x){let y=x instanceof Error?x.message:String(x);if(y.includes("No such file"))return{success:!1,message:`Config file not found: ${b}`};return{success:!1,message:`Failed to update pi config: ${y}`}}},J=async()=>{let b=`${B.homedir()}/.codex/config.toml`,q=`${B.homedir()}/.codex/hooks.json`,x=N.file(q),y=N.file(b),G=!1,L=!1;try{let D=await x.text(),j=JSON.parse(D);j.hooks??={};let[Y,V]=Z(j.hooks.PreToolUse),[z,$]=Z(j.hooks.PostToolUse);if(j.hooks.PreToolUse=Y,j.hooks.PostToolUse=z,!V||!$)G=!0;let K=(U)=>{return U?.map((E)=>({hooks:E.hooks.map((M)=>{if(M.command===X&&M.timeout===void 0)return{...M,timeout:10};return M})}))??[]};if(j.hooks.PreToolUse=K(j.hooks.PreToolUse),j.hooks.PostToolUse=K(j.hooks.PostToolUse),G)await x.write(`${JSON.stringify(j,null,2)}
6
6
  `)}catch(D){let j=D instanceof Error?D.message:String(D);if(j.includes("No such file"))return{success:!1,message:`Config file not found: ${q}`};return{success:!1,message:`Failed to update Codex hooks.json: ${j}`}}try{let j=await y.text();if(j.includes("hooks = true"));else if(L=!0,j.includes("[features]")){let Y=j.indexOf("[features]"),V=j.indexOf(`
Binary file