@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.
- package/README.md +16 -14
- package/dist/index.js +35 -0
- package/dist/tripwire-cli.js +3 -0
- package/dist/tripwire-hook.js +3 -0
- package/dist/tripwire-pi.js +6 -4
- package/dist/tripwire.js +141 -0
- package/dist/types/dispatch.d.ts +18 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/lib/bash.d.ts +27 -0
- package/dist/types/lib/config.d.ts +110 -0
- package/dist/types/lib/cursor.d.ts +16 -0
- package/dist/types/lib/decision.d.ts +13 -0
- package/dist/types/lib/diff.d.ts +3 -0
- package/dist/types/lib/event.d.ts +45 -0
- package/dist/types/lib/log.d.ts +2 -0
- package/dist/types/lib/secrets.d.ts +41 -0
- package/dist/types/rules/bash-deny.d.ts +5 -0
- package/dist/types/rules/bash-git.d.ts +5 -0
- package/dist/types/rules/bash-network-install.d.ts +4 -0
- package/dist/types/rules/bash-redirect.d.ts +4 -0
- package/dist/types/rules/bash-scoped-rm.d.ts +5 -0
- package/dist/types/rules/bash-tar-explosion.d.ts +4 -0
- package/dist/types/rules/config-custom.d.ts +6 -0
- package/dist/types/rules/lazy-code.d.ts +4 -0
- package/dist/types/rules/path-protect.d.ts +12 -0
- package/dist/types/rules/post-secret-scrub.d.ts +12 -0
- package/dist/types/rules/read-protect.d.ts +4 -0
- package/dist/types/rules/tool-policy.d.ts +5 -0
- package/package.json +16 -13
- package/dist/tripwire +0 -0
- package/scripts/tripwire-cli +0 -12
- package/src/cli.ts +0 -271
- package/src/dispatch.ts +0 -562
- package/src/index.ts +0 -6
- package/src/lib/bash.ts +0 -1328
- package/src/lib/config.ts +0 -174
- package/src/lib/cursor.ts +0 -336
- package/src/lib/decision.ts +0 -36
- package/src/lib/diff.ts +0 -29
- package/src/lib/event.ts +0 -105
- package/src/lib/install.ts +0 -610
- package/src/lib/log.ts +0 -23
- package/src/lib/secrets.ts +0 -184
- package/src/main.ts +0 -31
- package/src/pi-extension.ts +0 -337
- package/src/rules/bash-deny.ts +0 -404
- package/src/rules/bash-git.ts +0 -590
- package/src/rules/bash-network-install.ts +0 -75
- package/src/rules/bash-redirect.ts +0 -91
- package/src/rules/bash-scoped-rm.ts +0 -84
- package/src/rules/bash-tar-explosion.ts +0 -77
- package/src/rules/config-custom.ts +0 -166
- package/src/rules/lazy-code.ts +0 -95
- package/src/rules/path-protect.ts +0 -131
- package/src/rules/post-secret-scrub.ts +0 -49
- package/src/rules/read-protect.ts +0 -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 };
|