@seanmozeik/tripwire 0.5.2 → 0.5.3

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.
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun @bytecode @bun-cjs
3
- (function(exports, require, module, __filename, __dirname) {var _=require("path"),C=require("@effect/platform-bun"),R=globalThis.Bun,Q=require("effect"),w=require("effect/unstable/cli");var v={name:"@seanmozeik/tripwire",version:"0.5.2",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.66",effect:"^4.0.0-beta.66","shell-quote":"^1.8.3"},devDependencies:{"@types/bun":"^1.3.14","@types/shell-quote":"^1.7.5",oxfmt:"^0.50.0",oxlint:"^1.65.0","oxlint-tsgolint":"^0.22.1",typescript:"^6.0.3"},engines:{bun:">=1.0"}};var N=require("os"),z=globalThis.Bun,X="tripwire-hook",$=(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]},J=async()=>{let b=`${N.homedir()}/.claude/settings.json`,q=z.file(b);try{let x=await q.text(),y=JSON.parse(x);y.hooks??={};let[G,L]=$(y.hooks.PreToolUse),[D,j]=$(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
- `),{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}`}}},K=async()=>{let b=`${N.homedir()}/.pi/agent/settings.json`,q=z.file(b);try{let x=await q.text(),y=JSON.parse(x);y.hooks??={};let[G,L]=$(y.hooks.PreToolUse),[D,j]=$(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
- `),{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}`}}},W=async()=>{let b=`${N.homedir()}/.codex/config.toml`,q=`${N.homedir()}/.codex/hooks.json`,x=z.file(q),y=z.file(b),G=!1,L=!1;try{let D=await x.text(),j=JSON.parse(D);j.hooks??={};let[Y,V]=$(j.hooks.PreToolUse),[B,A]=$(j.hooks.PostToolUse);if(j.hooks.PreToolUse=Y,j.hooks.PostToolUse=B,!V||!A)G=!0;let Z=(H)=>{return H?.map((O)=>({hooks:O.hooks.map((M)=>{if(M.command===X&&M.timeout===void 0)return{...M,timeout:10};return M})}))??[]};if(j.hooks.PreToolUse=Z(j.hooks.PreToolUse),j.hooks.PostToolUse=Z(j.hooks.PostToolUse),G)await x.write(`${JSON.stringify(j,null,2)}
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(`
3
+ (function(exports, require, module, __filename, __dirname) {var S=require("path"),U=require("@effect/platform-bun"),C=globalThis.Bun,Q=require("effect"),x=require("effect/unstable/cli");var W={name:"@seanmozeik/tripwire",version:"0.5.3",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.66",effect:"^4.0.0-beta.66","shell-quote":"^1.8.3"},devDependencies:{"@types/bun":"^1.3.14","@types/shell-quote":"^1.7.5",oxfmt:"^0.50.0",oxlint:"^1.65.0","oxlint-tsgolint":"^0.22.1",typescript:"^6.0.3"},engines:{bun:">=1.0"}};var N=require("os"),z=globalThis.Bun,X="tripwire-hook",$=(b)=>{if(!b)return[[{hooks:[{type:"command",command:X}]}],!1];let w=!1,q=b.map((L)=>({hooks:L.hooks.map((D)=>{if(D.command===X||D.command.endsWith("/tripwire-hook")){if(D.command!==X)return w=!0,{...D,command:X};return D}return D})}));if(q.some((L)=>L.hooks.some((D)=>D.command===X)))return[q,!w];return[[...q,{hooks:[{type:"command",command:X}]}],!1]},v=async()=>{let b=`${N.homedir()}/.claude/settings.json`,w=z.file(b);try{let q=await w.text(),y=JSON.parse(q);y.hooks??={};let[G,L]=$(y.hooks.PreToolUse),[D,j]=$(y.hooks.PostToolUse);if(y.hooks.PreToolUse=G,y.hooks.PostToolUse=D,L&&j)return{success:!0,message:`Already configured: ${b}`};return await w.write(`${JSON.stringify(y,null,2)}
4
+ `),{success:!0,message:`Updated ${b}`}}catch(q){let y=q instanceof Error?q.message:String(q);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}`}}},J=async()=>{let b=`${N.homedir()}/.pi/agent/settings.json`,w=z.file(b);try{let q=await w.text(),y=JSON.parse(q);y.hooks??={};let[G,L]=$(y.hooks.PreToolUse),[D,j]=$(y.hooks.PostToolUse);if(y.hooks.PreToolUse=G,y.hooks.PostToolUse=D,L&&j)return{success:!0,message:`Already configured: ${b}`};return await w.write(`${JSON.stringify(y,null,2)}
5
+ `),{success:!0,message:`Updated ${b}`}}catch(q){let y=q instanceof Error?q.message:String(q);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}`}}},K=async()=>{let b=`${N.homedir()}/.codex/config.toml`,w=`${N.homedir()}/.codex/hooks.json`,q=z.file(w),y=z.file(b),G=!1,L=!1;try{let D=await q.text(),j=JSON.parse(D);j.hooks??={};let[Y,V]=$(j.hooks.PreToolUse),[B,A]=$(j.hooks.PostToolUse);if(j.hooks.PreToolUse=Y,j.hooks.PostToolUse=B,!V||!A)G=!0;let Z=(R)=>{return R?.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=Z(j.hooks.PreToolUse),j.hooks.PostToolUse=Z(j.hooks.PostToolUse),G)await q.write(`${JSON.stringify(j,null,2)}
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: ${w}`};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(`
7
7
  [`,Y+1);if(V===-1)j+=`
8
8
  hooks = true`;else j=`${j.slice(0,V)}
9
9
  hooks = true${j.slice(V)}`}else j+=`
10
10
  [features]
11
- hooks = true`;if(L)await y.write(j)}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: ${b}`};return{success:!1,message:`Failed to update Codex config.toml: ${j}`}}if(!G&&!L)return{success:!0,message:`Already configured: ${b} and ${q}`};return{success:!0,message:`Updated ${b} and ${q}`}},S=async()=>{return[{target:"claude",...await J()},{target:"codex",...await W()},{target:"pi",...await K()}]};var E="/Users/sean/dev/tripwire/src",U=_.resolve(E,"tripwire-hook"),T=_.resolve(E,"../dist/tripwire.js"),I=async()=>{try{return await R.file(U).text(),U}catch{return T}},F=(b,q,x,y)=>{if(b==="Bash")return{command:q??""};if(b==="Read")return{file_path:x??""};if(b==="Write")return{file_path:x??"",content:y??""};if(b==="Edit"||b==="MultiEdit")return{file_path:x??"",old_string:"",new_string:y??""};return},P=(b)=>{let{tool:q,post:x,command:y,path:G,stdout:L,stderr:D,content:j}=b,V={hook_event_name:x?"PostToolUse":"PreToolUse",tool_name:q,cwd:process.cwd(),session_id:"tripwire-cli-test",tool_input:F(q,y,G,j)};if(x)V.tool_response=q==="Bash"?{stdout:L??"",stderr:D??""}:{content:j??""};return V},p=(b)=>Q.Effect.gen(function*(){let{command:q,content:x,path:y,post:G,stderr:L,stdout:D,tool:j}=b,Y=P({tool:j,post:G,command:q,path:y,stdout:D,stderr:L,content:x}),V=yield*Q.Effect.promise(()=>I()),B=Bun.spawnSync([V],{stdin:new TextEncoder().encode(JSON.stringify(Y)),timeout:1e4,stdout:"pipe",stderr:"pipe"});if(B.exitCode!==0){let Z=new TextDecoder().decode(B.stderr);console.error(`error: ${Z}`),process.exit(1)}let A=new TextDecoder().decode(B.stdout);try{let Z=JSON.parse(A);console.log(JSON.stringify(Z,null,2))}catch{console.log(A)}}),k=w.Command.make("test",{command:w.Argument.string("command").pipe(w.Argument.optional,w.Argument.withDescription("Command to test (for Bash tool)")),content:w.Flag.string("content").pipe(w.Flag.optional,w.Flag.withDescription("Content for Write/Edit tools")),path:w.Flag.string("path").pipe(w.Flag.optional,w.Flag.withDescription("File path for Read/Write/Edit tools")),post:w.Flag.boolean("post").pipe(w.Flag.withDescription("Test PostToolUse instead of PreToolUse")),stderr:w.Flag.string("stderr").pipe(w.Flag.optional,w.Flag.withDescription("Stderr for PostToolUse Bash")),stdout:w.Flag.string("stdout").pipe(w.Flag.optional,w.Flag.withDescription("Stdout for PostToolUse Bash")),tool:w.Flag.string("tool").pipe(w.Flag.withDefault("Bash"),w.Flag.withDescription("Tool name (Bash, Read, Write, Edit, MultiEdit)"))},({command:b,content:q,path:x,post:y,stderr:G,stdout:L,tool:D})=>p({command:Q.Option.getOrUndefined(b),content:Q.Option.getOrUndefined(q),path:Q.Option.getOrUndefined(x),post:y,stderr:Q.Option.getOrUndefined(G),stdout:Q.Option.getOrUndefined(L),tool:D})).pipe(w.Command.withDescription("Test a synthetic hook event")),c=(b)=>Q.Effect.gen(function*(){if(!["claude","codex","pi","all"].includes(b))console.error(`error: unknown target "${b}"`),console.error("Valid targets: claude, codex, pi, all"),process.exit(1);let q;switch(b){case"claude":{q=[{target:"claude",result:yield*Q.Effect.promise(()=>J())}];break}case"codex":{q=[{target:"codex",result:yield*Q.Effect.promise(()=>W())}];break}case"pi":{q=[{target:"pi",result:yield*Q.Effect.promise(()=>K())}];break}case"all":{q=(yield*Q.Effect.promise(()=>S())).map((G)=>({target:G.target,result:G}));break}default:{q=[];break}}let x=!1;for(let{target:y,result:G}of q)if(G.success){let L=G.message.startsWith("Already configured")?"\u2299":"\u2713";console.log(`${L} [${y}] ${G.message}`)}else console.error(`\u2717 [${y}] ${G.message}`),x=!0;if(x)process.exit(1)}),f=w.Command.make("install",{target:w.Argument.string("target").pipe(w.Argument.withDescription("Target agent (claude, codex, pi, or all)"))},({target:b})=>c(b)).pipe(w.Command.withDescription("Install tripwire hooks for AI agents")),h=w.Command.make("tripwire").pipe(w.Command.withDescription("Opinionated hooks dispatcher for AI coding agents"),w.Command.withSubcommands([k,f])),i=w.Command.run(h,{version:v.version}),m=async()=>{try{await Q.Effect.runPromise(i.pipe(Q.Effect.provide(C.BunServices.layer)))}catch(b){let q=b instanceof Error?b.message:String(b);console.error(q),process.exitCode=1}};m();})
11
+ hooks = true`;if(L)await y.write(j)}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: ${b}`};return{success:!1,message:`Failed to update Codex config.toml: ${j}`}}if(!G&&!L)return{success:!0,message:`Already configured: ${b} and ${w}`};return{success:!0,message:`Updated ${b} and ${w}`}},_=async()=>{return[{target:"claude",...await v()},{target:"codex",...await K()},{target:"pi",...await J()}]};var O=()=>{return/\/bun(\.exe)?$/.test(process.argv[0]??"")?process.argv[1]:process.argv[0]},u=async()=>{let b=O(),w=S.dirname(b),q=`${w}/tripwire-hook`;try{return await C.file(q).text(),q}catch{return`${w}/tripwire.js`}},T=(b,w,q,y)=>{if(b==="Bash")return{command:w??""};if(b==="Read")return{file_path:q??""};if(b==="Write")return{file_path:q??"",content:y??""};if(b==="Edit"||b==="MultiEdit")return{file_path:q??"",old_string:"",new_string:y??""};return},I=(b)=>{let{tool:w,post:q,command:y,path:G,stdout:L,stderr:D,content:j}=b,V={hook_event_name:q?"PostToolUse":"PreToolUse",tool_name:w,cwd:process.cwd(),session_id:"tripwire-cli-test",tool_input:T(w,y,G,j)};if(q)V.tool_response=w==="Bash"?{stdout:L??"",stderr:D??""}:{content:j??""};return V},F=(b)=>Q.Effect.gen(function*(){let{command:w,content:q,path:y,post:G,stderr:L,stdout:D,tool:j}=b,Y=I({tool:j,post:G,command:w,path:y,stdout:D,stderr:L,content:q}),V=yield*Q.Effect.promise(()=>u()),B=Bun.spawnSync([V],{stdin:new TextEncoder().encode(JSON.stringify(Y)),timeout:1e4,stdout:"pipe",stderr:"pipe"});if(B.exitCode!==0){let Z=new TextDecoder().decode(B.stderr);console.error(`error: ${Z}`),process.exit(1)}let A=new TextDecoder().decode(B.stdout);try{let Z=JSON.parse(A);console.log(JSON.stringify(Z,null,2))}catch{console.log(A)}}),P=x.Command.make("test",{command:x.Argument.string("command").pipe(x.Argument.optional,x.Argument.withDescription("Command to test (for Bash tool)")),content:x.Flag.string("content").pipe(x.Flag.optional,x.Flag.withDescription("Content for Write/Edit tools")),path:x.Flag.string("path").pipe(x.Flag.optional,x.Flag.withDescription("File path for Read/Write/Edit tools")),post:x.Flag.boolean("post").pipe(x.Flag.withDescription("Test PostToolUse instead of PreToolUse")),stderr:x.Flag.string("stderr").pipe(x.Flag.optional,x.Flag.withDescription("Stderr for PostToolUse Bash")),stdout:x.Flag.string("stdout").pipe(x.Flag.optional,x.Flag.withDescription("Stdout for PostToolUse Bash")),tool:x.Flag.string("tool").pipe(x.Flag.withDefault("Bash"),x.Flag.withDescription("Tool name (Bash, Read, Write, Edit, MultiEdit)"))},({command:b,content:w,path:q,post:y,stderr:G,stdout:L,tool:D})=>F({command:Q.Option.getOrUndefined(b),content:Q.Option.getOrUndefined(w),path:Q.Option.getOrUndefined(q),post:y,stderr:Q.Option.getOrUndefined(G),stdout:Q.Option.getOrUndefined(L),tool:D})).pipe(x.Command.withDescription("Test a synthetic hook event")),p=(b)=>Q.Effect.gen(function*(){if(!["claude","codex","pi","all"].includes(b))console.error(`error: unknown target "${b}"`),console.error("Valid targets: claude, codex, pi, all"),process.exit(1);let w;switch(b){case"claude":{w=[{target:"claude",result:yield*Q.Effect.promise(()=>v())}];break}case"codex":{w=[{target:"codex",result:yield*Q.Effect.promise(()=>K())}];break}case"pi":{w=[{target:"pi",result:yield*Q.Effect.promise(()=>J())}];break}case"all":{w=(yield*Q.Effect.promise(()=>_())).map((G)=>({target:G.target,result:G}));break}default:{w=[];break}}let q=!1;for(let{target:y,result:G}of w)if(G.success){let L=G.message.startsWith("Already configured")?"\u2299":"\u2713";console.log(`${L} [${y}] ${G.message}`)}else console.error(`\u2717 [${y}] ${G.message}`),q=!0;if(q)process.exit(1)}),k=x.Command.make("install",{target:x.Argument.string("target").pipe(x.Argument.withDescription("Target agent (claude, codex, pi, or all)"))},({target:b})=>p(b)).pipe(x.Command.withDescription("Install tripwire hooks for AI agents")),c=x.Command.make("tripwire").pipe(x.Command.withDescription("Opinionated hooks dispatcher for AI coding agents"),x.Command.withSubcommands([P,k])),f=x.Command.run(c,{version:W.version}),h=async()=>{try{await Q.Effect.runPromise(f.pipe(Q.Effect.provide(U.BunServices.layer)))}catch(b){let w=b instanceof Error?b.message:String(b);console.error(w),process.exitCode=1}};h();})
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seanmozeik/tripwire",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "Opinionated hooks dispatcher for AI coding agents with configurable safety rules",
5
5
  "license": "MIT",
6
6
  "bin": {
package/src/cli.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  // Bun src/cli.ts install pi
15
15
  // Bun src/cli.ts install all
16
16
 
17
- import { resolve } from 'node:path';
17
+ import { dirname } from 'node:path';
18
18
 
19
19
  import { BunServices } from '@effect/platform-bun';
20
20
  import { file } from 'bun';
@@ -24,19 +24,24 @@ import { Argument, Command, Flag } from 'effect/unstable/cli';
24
24
  import pkg from '../package.json' with { type: 'json' };
25
25
  import { installAll, installClaude, installCodex, installPi } from './lib/install';
26
26
 
27
- // Resolve tripwire-hook path relative to this CLI's installed location
28
- const cliDir = import.meta.dirname;
27
+ // Resolve tripwire-hook path at runtime using process.argv
28
+ // This works in both script mode (bun run) and compiled/bundled mode
29
+ const runtimeSelf = (): string => {
30
+ const isBunCli = /\/bun(\.exe)?$/.test(process.argv[0] ?? '');
31
+ return isBunCli ? process.argv[1]! : process.argv[0]!;
32
+ };
29
33
 
30
- // Try installed location first (both binaries in same dir), fall back to dev location
31
- const installedPath = resolve(cliDir, 'tripwire-hook');
32
- const devPath = resolve(cliDir, '../dist/tripwire.js');
33
34
  const dispatchBin = async (): Promise<string> => {
35
+ const cliPath = runtimeSelf();
36
+ const cliDir = dirname(cliPath);
37
+ // Try tripwire-hook in same directory first (installed scenario)
38
+ const installedPath = `${cliDir}/tripwire-hook`;
34
39
  try {
35
- // Check if installed path exists
36
40
  await file(installedPath).text();
37
41
  return installedPath;
38
42
  } catch {
39
- return devPath;
43
+ // Fallback to development scenario: tripwire-hook in ../dist relative to CLI
44
+ return `${cliDir}/tripwire.js`;
40
45
  }
41
46
  };
42
47