@seanmozeik/tripwire 0.7.0 → 0.7.2

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 (57) hide show
  1. package/README.md +16 -14
  2. package/dist/index.js +35 -0
  3. package/dist/tripwire-cli.js +3 -0
  4. package/dist/tripwire-hook.js +3 -0
  5. package/dist/tripwire-pi.js +6 -4
  6. package/dist/tripwire.js +141 -0
  7. package/dist/types/dispatch.d.ts +18 -0
  8. package/dist/types/index.d.ts +6 -0
  9. package/dist/types/lib/bash.d.ts +27 -0
  10. package/dist/types/lib/config.d.ts +110 -0
  11. package/dist/types/lib/cursor.d.ts +16 -0
  12. package/dist/types/lib/decision.d.ts +13 -0
  13. package/dist/types/lib/diff.d.ts +3 -0
  14. package/dist/types/lib/event.d.ts +45 -0
  15. package/dist/types/lib/log.d.ts +2 -0
  16. package/dist/types/lib/secrets.d.ts +41 -0
  17. package/dist/types/rules/bash-deny.d.ts +5 -0
  18. package/dist/types/rules/bash-git.d.ts +5 -0
  19. package/dist/types/rules/bash-network-install.d.ts +4 -0
  20. package/dist/types/rules/bash-redirect.d.ts +4 -0
  21. package/dist/types/rules/bash-scoped-rm.d.ts +5 -0
  22. package/dist/types/rules/bash-tar-explosion.d.ts +4 -0
  23. package/dist/types/rules/config-custom.d.ts +6 -0
  24. package/dist/types/rules/lazy-code.d.ts +4 -0
  25. package/dist/types/rules/path-protect.d.ts +12 -0
  26. package/dist/types/rules/post-secret-scrub.d.ts +12 -0
  27. package/dist/types/rules/read-protect.d.ts +4 -0
  28. package/dist/types/rules/tool-policy.d.ts +5 -0
  29. package/package.json +16 -13
  30. package/dist/tripwire +0 -0
  31. package/scripts/tripwire-cli +0 -12
  32. package/src/cli.ts +0 -271
  33. package/src/dispatch.ts +0 -562
  34. package/src/index.ts +0 -6
  35. package/src/lib/bash.ts +0 -1328
  36. package/src/lib/config.ts +0 -174
  37. package/src/lib/cursor.ts +0 -336
  38. package/src/lib/decision.ts +0 -36
  39. package/src/lib/diff.ts +0 -29
  40. package/src/lib/event.ts +0 -105
  41. package/src/lib/install.ts +0 -610
  42. package/src/lib/log.ts +0 -23
  43. package/src/lib/secrets.ts +0 -184
  44. package/src/main.ts +0 -31
  45. package/src/pi-extension.ts +0 -337
  46. package/src/rules/bash-deny.ts +0 -404
  47. package/src/rules/bash-git.ts +0 -590
  48. package/src/rules/bash-network-install.ts +0 -75
  49. package/src/rules/bash-redirect.ts +0 -91
  50. package/src/rules/bash-scoped-rm.ts +0 -84
  51. package/src/rules/bash-tar-explosion.ts +0 -77
  52. package/src/rules/config-custom.ts +0 -166
  53. package/src/rules/lazy-code.ts +0 -95
  54. package/src/rules/path-protect.ts +0 -131
  55. package/src/rules/post-secret-scrub.ts +0 -49
  56. package/src/rules/read-protect.ts +0 -57
  57. package/src/rules/tool-policy.ts +0 -54
package/src/cli.ts DELETED
@@ -1,271 +0,0 @@
1
- #!/usr/bin/env bun
2
- // CLI commands for testing policy and installing agent integrations.
3
- // Usage examples:
4
- // Bun src/main.ts test 'rm -rf /etc'
5
- // Bun src/main.ts test --tool=Read --path=.env
6
- // Bun src/main.ts test --post --tool=Bash --stdout='ghp_<token>'
7
- // Bun src/main.ts install claude
8
- // Bun src/main.ts install codex
9
- // Bun src/main.ts install cursor
10
- // Bun src/main.ts install pi
11
- // Bun src/main.ts install all
12
-
13
- import { BunServices } from '@effect/platform-bun';
14
- import { Effect, Option } from 'effect';
15
- import { Argument, Command, Flag } from 'effect/unstable/cli';
16
-
17
- import pkg from '../package.json' with { type: 'json' };
18
- import {
19
- installAll,
20
- installClaude,
21
- installCodex,
22
- installCursor,
23
- installOhMyPi,
24
- installPi,
25
- } from './lib/install';
26
-
27
- const hookCommand = (): string[] => {
28
- const isBunCli = /\/bun(?<ext>\.exe)?$/.test(process.execPath);
29
- if (isBunCli) {
30
- return [process.execPath, new URL('main.ts', import.meta.url).pathname, '--tripwire-hook'];
31
- }
32
- return [process.execPath, '--tripwire-hook'];
33
- };
34
-
35
- interface BuiltEvent {
36
- hook_event_name: string;
37
- tool_name: string;
38
- cwd: string;
39
- session_id: string;
40
- tool_input?: unknown;
41
- tool_response?: unknown;
42
- }
43
-
44
- const buildToolInput = (
45
- tool: string,
46
- command: string | undefined,
47
- path: string | undefined,
48
- content: string | undefined,
49
- ) => {
50
- if (tool === 'Bash') {
51
- return { command: command ?? '' };
52
- }
53
- if (tool === 'Read') {
54
- return { file_path: path ?? '' };
55
- }
56
- if (tool === 'Write') {
57
- return { file_path: path ?? '', content: content ?? '' };
58
- }
59
- if (tool === 'Edit' || tool === 'MultiEdit') {
60
- return { file_path: path ?? '', old_string: '', new_string: content ?? '' };
61
- }
62
- return null;
63
- };
64
-
65
- interface EventParams {
66
- readonly tool: string;
67
- readonly post: boolean;
68
- readonly command: string | undefined;
69
- readonly path: string | undefined;
70
- readonly stdout: string | undefined;
71
- readonly stderr: string | undefined;
72
- readonly content: string | undefined;
73
- }
74
-
75
- const buildEvent = (params: EventParams): BuiltEvent => {
76
- const { tool, post, command, path, stdout, stderr, content } = params;
77
- const eventName = post ? 'PostToolUse' : 'PreToolUse';
78
- const toolInput = buildToolInput(tool, command, path, content);
79
- const event: BuiltEvent = {
80
- hook_event_name: eventName,
81
- tool_name: tool,
82
- cwd: process.cwd(),
83
- session_id: 'tripwire-cli-test',
84
- ...(toolInput !== null && { tool_input: toolInput }),
85
- };
86
- if (post) {
87
- event.tool_response =
88
- tool === 'Bash' ? { stdout: stdout ?? '', stderr: stderr ?? '' } : { content: content ?? '' };
89
- }
90
- return event;
91
- };
92
-
93
- const prettyJson = (output: string): string => {
94
- try {
95
- return JSON.stringify(JSON.parse(output) as unknown, null, 2);
96
- } catch {
97
- return output;
98
- }
99
- };
100
-
101
- const runTest = (config: {
102
- readonly command: string | undefined;
103
- readonly content: string | undefined;
104
- readonly path: string | undefined;
105
- readonly post: boolean;
106
- readonly stderr: string | undefined;
107
- readonly stdout: string | undefined;
108
- readonly tool: string;
109
- }): Effect.Effect<void> =>
110
- Effect.sync(() => {
111
- const { command, content, path, post, stderr, stdout, tool } = config;
112
- const event = buildEvent({ tool, post, command, path, stdout, stderr, content });
113
- const result = Bun.spawnSync(hookCommand(), {
114
- stdin: new TextEncoder().encode(JSON.stringify(event)),
115
- timeout: 10_000,
116
- stdout: 'pipe',
117
- stderr: 'pipe',
118
- });
119
- if (result.exitCode !== 0) {
120
- const errorOutput = new TextDecoder().decode(result.stderr);
121
- console.error(`error: ${errorOutput}`);
122
- process.exit(1);
123
- }
124
- const output = new TextDecoder().decode(result.stdout);
125
- console.log(prettyJson(output));
126
- });
127
-
128
- const testCommand = Command.make(
129
- 'test',
130
- {
131
- command: Argument.string('command').pipe(
132
- Argument.optional,
133
- Argument.withDescription('Command to test (for Bash tool)'),
134
- ),
135
- content: Flag.string('content').pipe(
136
- Flag.optional,
137
- Flag.withDescription('Content for Write/Edit tools'),
138
- ),
139
- path: Flag.string('path').pipe(
140
- Flag.optional,
141
- Flag.withDescription('File path for Read/Write/Edit tools'),
142
- ),
143
- post: Flag.boolean('post').pipe(
144
- Flag.withDefault(false),
145
- Flag.withDescription('Test PostToolUse instead of PreToolUse'),
146
- ),
147
- stderr: Flag.string('stderr').pipe(
148
- Flag.optional,
149
- Flag.withDescription('Stderr for PostToolUse Bash'),
150
- ),
151
- stdout: Flag.string('stdout').pipe(
152
- Flag.optional,
153
- Flag.withDescription('Stdout for PostToolUse Bash'),
154
- ),
155
- tool: Flag.string('tool').pipe(
156
- Flag.withDefault('Bash'),
157
- Flag.withDescription('Tool name (Bash, Read, Write, Edit, MultiEdit)'),
158
- ),
159
- },
160
- ({ command, content, path, post, stderr, stdout, tool }) =>
161
- runTest({
162
- command: Option.getOrUndefined(command),
163
- content: Option.getOrUndefined(content),
164
- path: Option.getOrUndefined(path),
165
- post,
166
- stderr: Option.getOrUndefined(stderr),
167
- stdout: Option.getOrUndefined(stdout),
168
- tool,
169
- }),
170
- ).pipe(Command.withDescription('Test a synthetic hook event'));
171
-
172
- const runInstall = (target: string): Effect.Effect<void> =>
173
- Effect.gen(function* runInstallEffect() {
174
- if (!['claude', 'codex', 'cursor', 'pi', 'oh-my-pi', 'omp', 'all'].includes(target)) {
175
- console.error(`error: unknown target "${target}"`);
176
- console.error('Valid targets: claude, codex, cursor, pi, oh-my-pi, all');
177
- process.exit(1);
178
- }
179
-
180
- let results: {
181
- readonly target: string;
182
- readonly result: { readonly success: boolean; readonly message: string };
183
- }[];
184
-
185
- switch (target) {
186
- case 'claude': {
187
- const result = yield* Effect.promise(() => installClaude());
188
- results = [{ target: 'claude', result }];
189
- break;
190
- }
191
- case 'codex': {
192
- const result = yield* Effect.promise(() => installCodex());
193
- results = [{ target: 'codex', result }];
194
- break;
195
- }
196
- case 'pi': {
197
- const result = yield* Effect.promise(() => installPi());
198
- results = [{ target: 'pi', result }];
199
- break;
200
- }
201
- case 'oh-my-pi':
202
- case 'omp': {
203
- const result = yield* Effect.promise(() => installOhMyPi());
204
- results = [{ target: 'oh-my-pi', result }];
205
- break;
206
- }
207
- case 'cursor': {
208
- const result = yield* Effect.promise(() => installCursor());
209
- results = [{ target: 'cursor', result }];
210
- break;
211
- }
212
- case 'all': {
213
- const installResults = yield* Effect.promise(() => installAll());
214
- results = installResults.map((r) => ({ target: r.target, result: r }));
215
- break;
216
- }
217
- default: {
218
- results = [];
219
- break;
220
- }
221
- }
222
-
223
- let hasFailure = false;
224
- for (const { target: t, result: r } of results) {
225
- if (r.success) {
226
- const symbol = r.message.startsWith('Already configured') ? '⊙' : '✓';
227
- console.log(`${symbol} [${t}] ${r.message}`);
228
- } else {
229
- console.error(`✗ [${t}] ${r.message}`);
230
- hasFailure = true;
231
- }
232
- }
233
-
234
- if (hasFailure) {
235
- process.exit(1);
236
- }
237
- });
238
-
239
- const installCommand = Command.make(
240
- 'install',
241
- {
242
- target: Argument.string('target').pipe(
243
- Argument.withDescription('Target agent (claude, codex, cursor, pi, oh-my-pi, or all)'),
244
- ),
245
- },
246
- ({ target }) => runInstall(target),
247
- ).pipe(Command.withDescription('Install tripwire hooks for AI agents'));
248
-
249
- const app = Command.make('tripwire').pipe(
250
- Command.withDescription('Opinionated hooks dispatcher for AI coding agents'),
251
- Command.withSubcommands([testCommand, installCommand]),
252
- );
253
-
254
- const program = Command.run(app, { version: pkg.version });
255
-
256
- const runCli = async (): Promise<void> => {
257
- try {
258
- await Effect.runPromise(program.pipe(Effect.provide(BunServices.layer)));
259
- } catch (error) {
260
- const message = error instanceof Error ? error.message : String(error);
261
- console.error(message);
262
- process.exitCode = 1;
263
- }
264
- };
265
-
266
- if (import.meta.main) {
267
- // oxlint-disable-next-line no-void -- runCli reports failures through process.exitCode.
268
- void runCli();
269
- }
270
-
271
- export { runCli };