@goodfoot/agent-hooks 1.0.0

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 (68) hide show
  1. package/README.md +137 -0
  2. package/dist/agents/antigravity/events.js +42 -0
  3. package/dist/agents/antigravity/hooks.js +59 -0
  4. package/dist/agents/antigravity/index.js +28 -0
  5. package/dist/agents/antigravity/outputs.js +81 -0
  6. package/dist/agents/antigravity/transport.js +84 -0
  7. package/dist/agents/antigravity/types.js +11 -0
  8. package/dist/agents/claude-code/env.js +27 -0
  9. package/dist/agents/claude-code/events.js +87 -0
  10. package/dist/agents/claude-code/hooks.js +1000 -0
  11. package/dist/agents/claude-code/index.js +57 -0
  12. package/dist/agents/claude-code/outputs.js +579 -0
  13. package/dist/agents/claude-code/tool-helpers.js +840 -0
  14. package/dist/agents/claude-code/transport.js +254 -0
  15. package/dist/agents/claude-code/types.js +11 -0
  16. package/dist/agents/codex/cli-support.js +318 -0
  17. package/dist/agents/codex/constants.js +29 -0
  18. package/dist/agents/codex/events.js +68 -0
  19. package/dist/agents/codex/hooks.js +93 -0
  20. package/dist/agents/codex/index.js +21 -0
  21. package/dist/agents/codex/outputs.js +189 -0
  22. package/dist/agents/codex/scaffold.js +273 -0
  23. package/dist/agents/codex/transport.js +139 -0
  24. package/dist/agents/codex/types.js +1 -0
  25. package/dist/cli.js +1357 -0
  26. package/dist/constants.js +39 -0
  27. package/dist/core/define-hook.js +61 -0
  28. package/dist/core/env.js +186 -0
  29. package/dist/core/logger.js +508 -0
  30. package/dist/core/stdin.js +38 -0
  31. package/dist/core/transport.js +231 -0
  32. package/dist/core/types.js +10 -0
  33. package/dist/index.js +17 -0
  34. package/dist/scaffold.js +512 -0
  35. package/package.json +61 -0
  36. package/types/agents/antigravity/events.d.ts +50 -0
  37. package/types/agents/antigravity/hooks.d.ts +76 -0
  38. package/types/agents/antigravity/index.d.ts +27 -0
  39. package/types/agents/antigravity/outputs.d.ts +107 -0
  40. package/types/agents/antigravity/transport.d.ts +47 -0
  41. package/types/agents/antigravity/types.d.ts +58 -0
  42. package/types/agents/claude-code/env.d.ts +27 -0
  43. package/types/agents/claude-code/events.d.ts +69 -0
  44. package/types/agents/claude-code/hooks.d.ts +1182 -0
  45. package/types/agents/claude-code/index.d.ts +23 -0
  46. package/types/agents/claude-code/outputs.d.ts +1160 -0
  47. package/types/agents/claude-code/tool-helpers.d.ts +807 -0
  48. package/types/agents/claude-code/transport.d.ts +119 -0
  49. package/types/agents/claude-code/types.d.ts +557 -0
  50. package/types/agents/codex/cli-support.d.ts +105 -0
  51. package/types/agents/codex/constants.d.ts +10 -0
  52. package/types/agents/codex/events.d.ts +63 -0
  53. package/types/agents/codex/hooks.d.ts +135 -0
  54. package/types/agents/codex/index.d.ts +19 -0
  55. package/types/agents/codex/outputs.d.ts +190 -0
  56. package/types/agents/codex/scaffold.d.ts +14 -0
  57. package/types/agents/codex/transport.d.ts +74 -0
  58. package/types/agents/codex/types.d.ts +131 -0
  59. package/types/cli.d.ts +315 -0
  60. package/types/constants.d.ts +9 -0
  61. package/types/core/define-hook.d.ts +39 -0
  62. package/types/core/env.d.ts +150 -0
  63. package/types/core/logger.d.ts +476 -0
  64. package/types/core/stdin.d.ts +21 -0
  65. package/types/core/transport.d.ts +131 -0
  66. package/types/core/types.d.ts +137 -0
  67. package/types/index.d.ts +18 -0
  68. package/types/scaffold.d.ts +46 -0
@@ -0,0 +1,840 @@
1
+ /**
2
+ * Type guards and helper functions for Claude Code tool inputs.
3
+ *
4
+ * Provides safe type narrowing for tool inputs and utility functions
5
+ * for common patterns like file path extraction and content inspection.
6
+ * @example
7
+ * ```typescript
8
+ * import {
9
+ * preToolUseHook,
10
+ * preToolUseOutput,
11
+ * isWriteTool,
12
+ * getFilePath,
13
+ * isTsFile,
14
+ * checkContentForPattern
15
+ * } from '@goodfoot/agent-hooks/claude-code';
16
+ *
17
+ * export default preToolUseHook({ matcher: 'Write|Edit|MultiEdit' }, (input) => {
18
+ * const filePath = getFilePath(input);
19
+ * if (!filePath || !isTsFile(filePath)) return preToolUseOutput({});
20
+ *
21
+ * const result = checkContentForPattern(input, /@ts-expect-error/g);
22
+ * if (result?.isAddition) {
23
+ * return preToolUseOutput({
24
+ * hookSpecificOutput: {
25
+ * permissionDecision: 'deny',
26
+ * permissionDecisionReason: `Cannot add: ${result.matches.join(', ')}`
27
+ * }
28
+ * });
29
+ * }
30
+ *
31
+ * return preToolUseOutput({});
32
+ * });
33
+ * ```
34
+ * @see https://code.claude.com/docs/en/hooks
35
+ * @module
36
+ */
37
+ // ============================================================================
38
+ // Type Guards
39
+ // ============================================================================
40
+ /**
41
+ * Type guard for Write tool inputs.
42
+ *
43
+ * Narrows the input type to include a typed WriteToolInput.
44
+ * @param input - The hook input to check
45
+ * @returns True if the input is for a Write tool
46
+ * @example
47
+ * ```typescript
48
+ * if (isWriteTool(input)) {
49
+ * // input.tool_input is now typed as WriteToolInput
50
+ * console.log(input.tool_input.file_path);
51
+ * console.log(input.tool_input.content);
52
+ * }
53
+ * ```
54
+ */
55
+ export function isWriteTool(input) {
56
+ return input.tool_name === "Write";
57
+ }
58
+ /**
59
+ * Type guard for Edit tool inputs.
60
+ *
61
+ * Narrows the input type to include a typed EditToolInput.
62
+ * @param input - The hook input to check
63
+ * @returns True if the input is for an Edit tool
64
+ * @example
65
+ * ```typescript
66
+ * if (isEditTool(input)) {
67
+ * console.log(input.tool_input.old_string);
68
+ * console.log(input.tool_input.new_string);
69
+ * }
70
+ * ```
71
+ */
72
+ export function isEditTool(input) {
73
+ return input.tool_name === "Edit";
74
+ }
75
+ /**
76
+ * Type guard for MultiEdit tool inputs.
77
+ *
78
+ * Narrows the input type to include a typed MultiEditToolInput.
79
+ * @param input - The hook input to check
80
+ * @returns True if the input is for a MultiEdit tool
81
+ * @example
82
+ * ```typescript
83
+ * if (isMultiEditTool(input)) {
84
+ * for (const edit of input.tool_input.edits) {
85
+ * console.log(`${edit.old_string} -> ${edit.new_string}`);
86
+ * }
87
+ * }
88
+ * ```
89
+ */
90
+ export function isMultiEditTool(input) {
91
+ return input.tool_name === "MultiEdit";
92
+ }
93
+ /**
94
+ * Type guard for any file-modifying tool (Write, Edit, or MultiEdit).
95
+ *
96
+ * Use this when you need to handle all file modifications generically.
97
+ * @param input - The hook input to check
98
+ * @returns True if the input is for a Write, Edit, or MultiEdit tool
99
+ * @example
100
+ * ```typescript
101
+ * if (isFileModifyingTool(input)) {
102
+ * const filePath = getFilePath(input); // Works for all three types
103
+ * }
104
+ * ```
105
+ */
106
+ export function isFileModifyingTool(input) {
107
+ return input.tool_name === "Write" || input.tool_name === "Edit" || input.tool_name === "MultiEdit";
108
+ }
109
+ /**
110
+ * Type guard for Read tool inputs.
111
+ *
112
+ * Narrows the input type to include a typed ReadToolInput.
113
+ * @param input - The hook input to check
114
+ * @returns True if the input is for a Read tool
115
+ * @example
116
+ * ```typescript
117
+ * if (isReadTool(input)) {
118
+ * console.log(input.tool_input.file_path);
119
+ * console.log(input.tool_input.offset);
120
+ * }
121
+ * ```
122
+ */
123
+ export function isReadTool(input) {
124
+ return input.tool_name === "Read";
125
+ }
126
+ /**
127
+ * Type guard for Bash tool inputs.
128
+ *
129
+ * Narrows the input type to include a typed BashToolInput.
130
+ * @param input - The hook input to check
131
+ * @returns True if the input is for a Bash tool
132
+ * @example
133
+ * ```typescript
134
+ * if (isBashTool(input)) {
135
+ * console.log(input.tool_input.command);
136
+ * console.log(input.tool_input.timeout);
137
+ * }
138
+ * ```
139
+ */
140
+ export function isBashTool(input) {
141
+ return input.tool_name === "Bash";
142
+ }
143
+ /**
144
+ * Type guard for Glob tool inputs.
145
+ *
146
+ * Narrows the input type to include a typed GlobToolInput.
147
+ * @param input - The hook input to check
148
+ * @returns True if the input is for a Glob tool
149
+ * @example
150
+ * ```typescript
151
+ * if (isGlobTool(input)) {
152
+ * console.log(input.tool_input.pattern);
153
+ * console.log(input.tool_input.path);
154
+ * }
155
+ * ```
156
+ */
157
+ export function isGlobTool(input) {
158
+ return input.tool_name === "Glob";
159
+ }
160
+ /**
161
+ * Type guard for Grep tool inputs.
162
+ *
163
+ * Narrows the input type to include a typed GrepToolInput.
164
+ * @param input - The hook input to check
165
+ * @returns True if the input is for a Grep tool
166
+ * @example
167
+ * ```typescript
168
+ * if (isGrepTool(input)) {
169
+ * console.log(input.tool_input.pattern);
170
+ * console.log(input.tool_input.glob);
171
+ * }
172
+ * ```
173
+ */
174
+ export function isGrepTool(input) {
175
+ return input.tool_name === "Grep";
176
+ }
177
+ /**
178
+ * Type guard for Agent tool inputs.
179
+ *
180
+ * Narrows the input type to include a typed AgentInput.
181
+ * @param input - The hook input to check
182
+ * @returns True if the input is for an Agent tool
183
+ * @example
184
+ * ```typescript
185
+ * if (isTaskTool(input)) {
186
+ * console.log(input.tool_input.prompt);
187
+ * console.log(input.tool_input.subagent_type);
188
+ * }
189
+ * ```
190
+ */
191
+ export function isTaskTool(input) {
192
+ return input.tool_name === "Agent";
193
+ }
194
+ /**
195
+ * Type guard for TaskOutput tool inputs.
196
+ *
197
+ * Narrows the input type to include a typed TaskOutputInput.
198
+ * @param input - The hook input to check
199
+ * @returns True if the input is for a TaskOutput tool
200
+ * @example
201
+ * ```typescript
202
+ * if (isTaskOutputTool(input)) {
203
+ * console.log(input.tool_input.task_id);
204
+ * }
205
+ * ```
206
+ */
207
+ export function isTaskOutputTool(input) {
208
+ return input.tool_name === "TaskOutput";
209
+ }
210
+ /**
211
+ * Type guard for ExitPlanMode tool inputs.
212
+ *
213
+ * Narrows the input type to include a typed ExitPlanModeInput.
214
+ * @param input - The hook input to check
215
+ * @returns True if the input is for an ExitPlanMode tool
216
+ * @example
217
+ * ```typescript
218
+ * if (isExitPlanModeTool(input)) {
219
+ * console.log(input.tool_input.allowedPrompts);
220
+ * }
221
+ * ```
222
+ */
223
+ export function isExitPlanModeTool(input) {
224
+ return input.tool_name === "ExitPlanMode";
225
+ }
226
+ /**
227
+ * Type guard for KillShell tool inputs.
228
+ *
229
+ * Narrows the input type to include a typed KillShellInput.
230
+ * @param input - The hook input to check
231
+ * @returns True if the input is for a KillShell tool
232
+ * @example
233
+ * ```typescript
234
+ * if (isKillShellTool(input)) {
235
+ * console.log(input.tool_input.shell_id);
236
+ * }
237
+ * ```
238
+ */
239
+ export function isKillShellTool(input) {
240
+ return input.tool_name === "KillShell";
241
+ }
242
+ /**
243
+ * Type guard for NotebookEdit tool inputs.
244
+ *
245
+ * Narrows the input type to include a typed NotebookEditInput.
246
+ * @param input - The hook input to check
247
+ * @returns True if the input is for a NotebookEdit tool
248
+ * @example
249
+ * ```typescript
250
+ * if (isNotebookEditTool(input)) {
251
+ * console.log(input.tool_input.notebook_path);
252
+ * console.log(input.tool_input.new_source);
253
+ * }
254
+ * ```
255
+ */
256
+ export function isNotebookEditTool(input) {
257
+ return input.tool_name === "NotebookEdit";
258
+ }
259
+ /**
260
+ * Type guard for TodoWrite tool inputs.
261
+ *
262
+ * Narrows the input type to include a typed TodoWriteInput.
263
+ * @param input - The hook input to check
264
+ * @returns True if the input is for a TodoWrite tool
265
+ * @example
266
+ * ```typescript
267
+ * if (isTodoWriteTool(input)) {
268
+ * console.log(input.tool_input.todos);
269
+ * }
270
+ * ```
271
+ */
272
+ export function isTodoWriteTool(input) {
273
+ return input.tool_name === "TodoWrite";
274
+ }
275
+ /**
276
+ * Type guard for WebFetch tool inputs.
277
+ *
278
+ * Narrows the input type to include a typed WebFetchInput.
279
+ * @param input - The hook input to check
280
+ * @returns True if the input is for a WebFetch tool
281
+ * @example
282
+ * ```typescript
283
+ * if (isWebFetchTool(input)) {
284
+ * console.log(input.tool_input.url);
285
+ * console.log(input.tool_input.prompt);
286
+ * }
287
+ * ```
288
+ */
289
+ export function isWebFetchTool(input) {
290
+ return input.tool_name === "WebFetch";
291
+ }
292
+ /**
293
+ * Type guard for WebSearch tool inputs.
294
+ *
295
+ * Narrows the input type to include a typed WebSearchInput.
296
+ * @param input - The hook input to check
297
+ * @returns True if the input is for a WebSearch tool
298
+ * @example
299
+ * ```typescript
300
+ * if (isWebSearchTool(input)) {
301
+ * console.log(input.tool_input.query);
302
+ * }
303
+ * ```
304
+ */
305
+ export function isWebSearchTool(input) {
306
+ return input.tool_name === "WebSearch";
307
+ }
308
+ /**
309
+ * Type guard for AskUserQuestion tool inputs.
310
+ *
311
+ * Narrows the input type to include a typed AskUserQuestionInput.
312
+ * @param input - The hook input to check
313
+ * @returns True if the input is for an AskUserQuestion tool
314
+ * @example
315
+ * ```typescript
316
+ * if (isAskUserQuestionTool(input)) {
317
+ * console.log(input.tool_input.questions);
318
+ * }
319
+ * ```
320
+ */
321
+ export function isAskUserQuestionTool(input) {
322
+ return input.tool_name === "AskUserQuestion";
323
+ }
324
+ /**
325
+ * Type guard for ListMcpResources tool inputs.
326
+ *
327
+ * Narrows the input type to include a typed ListMcpResourcesInput.
328
+ * @param input - The hook input to check
329
+ * @returns True if the input is for a ListMcpResources tool
330
+ * @example
331
+ * ```typescript
332
+ * if (isListMcpResourcesTool(input)) {
333
+ * console.log(input.tool_input.server);
334
+ * }
335
+ * ```
336
+ */
337
+ export function isListMcpResourcesTool(input) {
338
+ return input.tool_name === "ListMcpResources";
339
+ }
340
+ /**
341
+ * Type guard for Mcp tool inputs.
342
+ *
343
+ * Narrows the input type to include a typed McpInput.
344
+ * @param input - The hook input to check
345
+ * @returns True if the input is for an Mcp tool
346
+ * @example
347
+ * ```typescript
348
+ * if (isMcpTool(input)) {
349
+ * // input.tool_input is now typed as McpInput
350
+ * }
351
+ * ```
352
+ */
353
+ export function isMcpTool(input) {
354
+ return input.tool_name === "Mcp";
355
+ }
356
+ /**
357
+ * Type guard for ReadMcpResource tool inputs.
358
+ *
359
+ * Narrows the input type to include a typed ReadMcpResourceInput.
360
+ * @param input - The hook input to check
361
+ * @returns True if the input is for a ReadMcpResource tool
362
+ * @example
363
+ * ```typescript
364
+ * if (isReadMcpResourceTool(input)) {
365
+ * console.log(input.tool_input.server);
366
+ * console.log(input.tool_input.uri);
367
+ * }
368
+ * ```
369
+ */
370
+ export function isReadMcpResourceTool(input) {
371
+ return input.tool_name === "ReadMcpResource";
372
+ }
373
+ /**
374
+ * Type guard for Config tool inputs.
375
+ *
376
+ * Narrows the input type to include a typed ConfigInput.
377
+ * @param input - The hook input to check
378
+ * @returns True if the input is for a Config tool
379
+ * @example
380
+ * ```typescript
381
+ * if (isConfigTool(input)) {
382
+ * console.log(input.tool_input.setting);
383
+ * console.log(input.tool_input.value);
384
+ * }
385
+ * ```
386
+ */
387
+ export function isConfigTool(input) {
388
+ return input.tool_name === "Config";
389
+ }
390
+ /**
391
+ * Type guard for TaskCreate tool inputs.
392
+ *
393
+ * Narrows the input type to include a typed TaskCreateInput.
394
+ * @param input - The hook input to check
395
+ * @returns True if the input is for a TaskCreate tool
396
+ * @example
397
+ * ```typescript
398
+ * if (isTaskCreateTool(input)) {
399
+ * console.log(input.tool_input.subject);
400
+ * }
401
+ * ```
402
+ */
403
+ export function isTaskCreateTool(input) {
404
+ return input.tool_name === "TaskCreate";
405
+ }
406
+ /**
407
+ * Type guard for TaskGet tool inputs.
408
+ *
409
+ * Narrows the input type to include a typed TaskGetInput.
410
+ * @param input - The hook input to check
411
+ * @returns True if the input is for a TaskGet tool
412
+ * @example
413
+ * ```typescript
414
+ * if (isTaskGetTool(input)) {
415
+ * console.log(input.tool_input.taskId);
416
+ * }
417
+ * ```
418
+ */
419
+ export function isTaskGetTool(input) {
420
+ return input.tool_name === "TaskGet";
421
+ }
422
+ /**
423
+ * Type guard for TaskList tool inputs.
424
+ *
425
+ * Narrows the input type to include a typed TaskListInput.
426
+ * @param input - The hook input to check
427
+ * @returns True if the input is for a TaskList tool
428
+ * @example
429
+ * ```typescript
430
+ * if (isTaskListTool(input)) {
431
+ * // input.tool_input is now typed as TaskListInput
432
+ * }
433
+ * ```
434
+ */
435
+ export function isTaskListTool(input) {
436
+ return input.tool_name === "TaskList";
437
+ }
438
+ /**
439
+ * Type guard for TaskUpdate tool inputs.
440
+ *
441
+ * Narrows the input type to include a typed TaskUpdateInput.
442
+ * @param input - The hook input to check
443
+ * @returns True if the input is for a TaskUpdate tool
444
+ * @example
445
+ * ```typescript
446
+ * if (isTaskUpdateTool(input)) {
447
+ * console.log(input.tool_input.taskId, input.tool_input.status);
448
+ * }
449
+ * ```
450
+ */
451
+ export function isTaskUpdateTool(input) {
452
+ return input.tool_name === "TaskUpdate";
453
+ }
454
+ /**
455
+ * Type guard for CronCreate tool inputs.
456
+ *
457
+ * Narrows the input type to include a typed CronCreateInput.
458
+ * @param input - The hook input to check
459
+ * @returns True if the input is for a CronCreate tool
460
+ * @example
461
+ * ```typescript
462
+ * if (isCronCreateTool(input)) {
463
+ * console.log(input.tool_input.cron, input.tool_input.prompt);
464
+ * }
465
+ * ```
466
+ */
467
+ export function isCronCreateTool(input) {
468
+ return input.tool_name === "CronCreate";
469
+ }
470
+ /**
471
+ * Type guard for CronDelete tool inputs.
472
+ *
473
+ * Narrows the input type to include a typed CronDeleteInput.
474
+ * @param input - The hook input to check
475
+ * @returns True if the input is for a CronDelete tool
476
+ * @example
477
+ * ```typescript
478
+ * if (isCronDeleteTool(input)) {
479
+ * console.log(input.tool_input.id);
480
+ * }
481
+ * ```
482
+ */
483
+ export function isCronDeleteTool(input) {
484
+ return input.tool_name === "CronDelete";
485
+ }
486
+ /**
487
+ * Type guard for CronList tool inputs.
488
+ *
489
+ * Narrows the input type to include a typed CronListInput.
490
+ * @param input - The hook input to check
491
+ * @returns True if the input is for a CronList tool
492
+ * @example
493
+ * ```typescript
494
+ * if (isCronListTool(input)) {
495
+ * // input.tool_input is now typed as CronListInput
496
+ * }
497
+ * ```
498
+ */
499
+ export function isCronListTool(input) {
500
+ return input.tool_name === "CronList";
501
+ }
502
+ /**
503
+ * Type guard for ScheduleWakeup tool inputs.
504
+ *
505
+ * Narrows the input type to include a typed ScheduleWakeupInput.
506
+ * @param input - The hook input to check
507
+ * @returns True if the input is for a ScheduleWakeup tool
508
+ * @example
509
+ * ```typescript
510
+ * if (isScheduleWakeupTool(input)) {
511
+ * console.log(input.tool_input.delaySeconds);
512
+ * }
513
+ * ```
514
+ */
515
+ export function isScheduleWakeupTool(input) {
516
+ return input.tool_name === "ScheduleWakeup";
517
+ }
518
+ /**
519
+ * Type guard for Monitor tool inputs.
520
+ *
521
+ * Narrows the input type to include a typed MonitorInput.
522
+ * @param input - The hook input to check
523
+ * @returns True if the input is for a Monitor tool
524
+ * @example
525
+ * ```typescript
526
+ * if (isMonitorTool(input)) {
527
+ * console.log(input.tool_input.command);
528
+ * }
529
+ * ```
530
+ */
531
+ export function isMonitorTool(input) {
532
+ return input.tool_name === "Monitor";
533
+ }
534
+ /**
535
+ * Type guard for RemoteTrigger tool inputs.
536
+ *
537
+ * Narrows the input type to include a typed RemoteTriggerInput.
538
+ * @param input - The hook input to check
539
+ * @returns True if the input is for a RemoteTrigger tool
540
+ * @example
541
+ * ```typescript
542
+ * if (isRemoteTriggerTool(input)) {
543
+ * console.log(input.tool_input.action);
544
+ * }
545
+ * ```
546
+ */
547
+ export function isRemoteTriggerTool(input) {
548
+ return input.tool_name === "RemoteTrigger";
549
+ }
550
+ /**
551
+ * Type guard for PushNotification tool inputs.
552
+ *
553
+ * Narrows the input type to include a typed PushNotificationInput.
554
+ * @param input - The hook input to check
555
+ * @returns True if the input is for a PushNotification tool
556
+ * @example
557
+ * ```typescript
558
+ * if (isPushNotificationTool(input)) {
559
+ * console.log(input.tool_input.message);
560
+ * }
561
+ * ```
562
+ */
563
+ export function isPushNotificationTool(input) {
564
+ return input.tool_name === "PushNotification";
565
+ }
566
+ /**
567
+ * Type guard for EnterPlanMode tool inputs.
568
+ *
569
+ * Narrows the input type to include a typed EnterPlanModeInput.
570
+ * @param input - The hook input to check
571
+ * @returns True if the input is for an EnterPlanMode tool
572
+ * @example
573
+ * ```typescript
574
+ * if (isEnterPlanModeTool(input)) {
575
+ * // input.tool_input is now typed as EnterPlanModeInput
576
+ * }
577
+ * ```
578
+ */
579
+ export function isEnterPlanModeTool(input) {
580
+ return input.tool_name === "EnterPlanMode";
581
+ }
582
+ /**
583
+ * Type guard for EnterWorktree tool inputs.
584
+ *
585
+ * Narrows the input type to include a typed EnterWorktreeInput.
586
+ * @param input - The hook input to check
587
+ * @returns True if the input is for an EnterWorktree tool
588
+ * @example
589
+ * ```typescript
590
+ * if (isEnterWorktreeTool(input)) {
591
+ * console.log(input.tool_input.name);
592
+ * }
593
+ * ```
594
+ */
595
+ export function isEnterWorktreeTool(input) {
596
+ return input.tool_name === "EnterWorktree";
597
+ }
598
+ /**
599
+ * Type guard for ExitWorktree tool inputs.
600
+ *
601
+ * Narrows the input type to include a typed ExitWorktreeInput.
602
+ * @param input - The hook input to check
603
+ * @returns True if the input is for an ExitWorktree tool
604
+ * @example
605
+ * ```typescript
606
+ * if (isExitWorktreeTool(input)) {
607
+ * console.log(input.tool_input.action);
608
+ * }
609
+ * ```
610
+ */
611
+ export function isExitWorktreeTool(input) {
612
+ return input.tool_name === "ExitWorktree";
613
+ }
614
+ /**
615
+ * Type guard for REPL tool inputs.
616
+ *
617
+ * Narrows the input type to include a typed REPLInput.
618
+ * @param input - The hook input to check
619
+ * @returns True if the input is for a REPL tool
620
+ * @example
621
+ * ```typescript
622
+ * if (isReplTool(input)) {
623
+ * console.log(input.tool_input.code);
624
+ * }
625
+ * ```
626
+ */
627
+ export function isReplTool(input) {
628
+ return input.tool_name === "REPL";
629
+ }
630
+ /**
631
+ * Type guard for Workflow tool inputs.
632
+ *
633
+ * Narrows the input type to include a typed WorkflowInput.
634
+ * @param input - The hook input to check
635
+ * @returns True if the input is for a Workflow tool
636
+ * @example
637
+ * ```typescript
638
+ * if (isWorkflowTool(input)) {
639
+ * console.log(input.tool_input.name);
640
+ * }
641
+ * ```
642
+ */
643
+ export function isWorkflowTool(input) {
644
+ return input.tool_name === "Workflow";
645
+ }
646
+ // ============================================================================
647
+ // File Path Utilities
648
+ // ============================================================================
649
+ /**
650
+ * Extracts the file path from a tool input.
651
+ *
652
+ * Works with Write, Edit, MultiEdit, and Read tools.
653
+ * Returns null for other tools or if file_path is missing.
654
+ * @param input - The hook input to extract from
655
+ * @returns The file path, or null if not applicable
656
+ * @example
657
+ * ```typescript
658
+ * const filePath = getFilePath(input);
659
+ * if (filePath && isTsFile(filePath)) {
660
+ * // Handle TypeScript file
661
+ * }
662
+ * ```
663
+ */
664
+ export function getFilePath(input) {
665
+ const toolInput = input.tool_input;
666
+ if (toolInput && typeof toolInput === "object" && "file_path" in toolInput) {
667
+ const filePath = toolInput.file_path;
668
+ return typeof filePath === "string" ? filePath : null;
669
+ }
670
+ return null;
671
+ }
672
+ /**
673
+ * Checks if a file path is a JavaScript or TypeScript file.
674
+ *
675
+ * Matches .js, .jsx, .ts, .tsx, .mjs, .mts, .cjs, .cts extensions.
676
+ * @param filePath - The file path to check
677
+ * @returns True if the file is JavaScript or TypeScript
678
+ * @example
679
+ * ```typescript
680
+ * if (isJsTsFile(filePath)) {
681
+ * // Check for TypeScript-specific patterns
682
+ * }
683
+ * ```
684
+ */
685
+ export function isJsTsFile(filePath) {
686
+ return /\.[cm]?[jt]sx?$/.test(filePath);
687
+ }
688
+ /**
689
+ * Checks if a file path is a TypeScript file.
690
+ *
691
+ * Matches .ts, .tsx, .mts, .cts extensions.
692
+ * @param filePath - The file path to check
693
+ * @returns True if the file is TypeScript
694
+ * @example
695
+ * ```typescript
696
+ * if (isTsFile(filePath)) {
697
+ * // Enforce TypeScript-specific rules
698
+ * }
699
+ * ```
700
+ */
701
+ export function isTsFile(filePath) {
702
+ return /\.[cm]?tsx?$/.test(filePath);
703
+ }
704
+ /**
705
+ * Checks if a pattern exists in the content being written or edited.
706
+ *
707
+ * For Write: checks the content being written
708
+ * For Edit: checks new_string (and old_string to detect additions)
709
+ * For MultiEdit: checks all edits and aggregates results
710
+ * @param input - The PreToolUse hook input
711
+ * @param pattern - The regex pattern to search for (global flag will be used)
712
+ * @returns Result object, or null if not a file-modifying tool
713
+ * @example
714
+ * ```typescript
715
+ * // Block @ts-expect-error being added
716
+ * const result = checkContentForPattern(input, /@ts-expect-error/g);
717
+ * if (result?.isAddition) {
718
+ * return preToolUseOutput({
719
+ * hookSpecificOutput: {
720
+ * permissionDecision: 'deny',
721
+ * permissionDecisionReason: `Cannot add: ${result.matches.join(', ')}`
722
+ * }
723
+ * });
724
+ * }
725
+ * ```
726
+ */
727
+ export function checkContentForPattern(input, pattern) {
728
+ // Ensure pattern has global flag for matchAll
729
+ const globalPattern = pattern.global ? pattern : new RegExp(pattern.source, `${pattern.flags}g`);
730
+ if (isWriteTool(input)) {
731
+ const matches = [...input.tool_input.content.matchAll(globalPattern)].map((m) => m[0]);
732
+ const uniqueMatches = [...new Set(matches)];
733
+ return {
734
+ found: uniqueMatches.length > 0,
735
+ isAddition: uniqueMatches.length > 0, // For Write, any match is an addition
736
+ matches: uniqueMatches,
737
+ };
738
+ }
739
+ if (isEditTool(input)) {
740
+ const newMatches = [...input.tool_input.new_string.matchAll(globalPattern)].map((m) => m[0]);
741
+ const oldMatches = [...input.tool_input.old_string.matchAll(globalPattern)].map((m) => m[0]);
742
+ const uniqueNewMatches = [...new Set(newMatches)];
743
+ const uniqueOldMatches = new Set(oldMatches);
744
+ // Addition = found in new but not in old
745
+ const additions = uniqueNewMatches.filter((m) => !uniqueOldMatches.has(m));
746
+ return {
747
+ found: uniqueNewMatches.length > 0,
748
+ isAddition: additions.length > 0,
749
+ matches: uniqueNewMatches,
750
+ };
751
+ }
752
+ if (isMultiEditTool(input)) {
753
+ const details = [];
754
+ const allMatches = new Set();
755
+ let anyFound = false;
756
+ let anyAddition = false;
757
+ for (let i = 0; i < input.tool_input.edits.length; i++) {
758
+ const edit = input.tool_input.edits[i];
759
+ const newMatches = [...edit.new_string.matchAll(globalPattern)].map((m) => m[0]);
760
+ const oldMatches = [...edit.old_string.matchAll(globalPattern)].map((m) => m[0]);
761
+ const uniqueNewMatches = [...new Set(newMatches)];
762
+ const uniqueOldMatches = new Set(oldMatches);
763
+ const additions = uniqueNewMatches.filter((m) => !uniqueOldMatches.has(m));
764
+ const found = uniqueNewMatches.length > 0;
765
+ const isAddition = additions.length > 0;
766
+ if (found)
767
+ anyFound = true;
768
+ if (isAddition)
769
+ anyAddition = true;
770
+ for (const m of uniqueNewMatches) {
771
+ allMatches.add(m);
772
+ }
773
+ details.push({
774
+ index: i,
775
+ found,
776
+ isAddition,
777
+ matches: uniqueNewMatches,
778
+ });
779
+ }
780
+ return {
781
+ found: anyFound,
782
+ isAddition: anyAddition,
783
+ matches: [...allMatches],
784
+ details,
785
+ };
786
+ }
787
+ return null;
788
+ }
789
+ /**
790
+ * Iterates over content in Write/Edit/MultiEdit operations.
791
+ *
792
+ * Provides a unified way to inspect content regardless of operation type.
793
+ * Return false from the callback to stop iteration early.
794
+ * @param input - The PreToolUse hook input
795
+ * @param callback - Function called for each content piece, return false to stop
796
+ * @returns True if all callbacks returned true, false if stopped early or not applicable
797
+ * @example
798
+ * ```typescript
799
+ * // Check all content for sensitive data
800
+ * const hasSensitive = !forEachContent(input, ({ newContent }) => {
801
+ * if (/password|secret|api.?key/i.test(newContent)) {
802
+ * return false; // Stop - found sensitive data
803
+ * }
804
+ * return true; // Continue
805
+ * });
806
+ * ```
807
+ */
808
+ export function forEachContent(input, callback) {
809
+ if (isWriteTool(input)) {
810
+ return callback({
811
+ newContent: input.tool_input.content,
812
+ oldContent: null,
813
+ index: 0,
814
+ isWrite: true,
815
+ });
816
+ }
817
+ if (isEditTool(input)) {
818
+ return callback({
819
+ newContent: input.tool_input.new_string,
820
+ oldContent: input.tool_input.old_string,
821
+ index: 0,
822
+ isWrite: false,
823
+ });
824
+ }
825
+ if (isMultiEditTool(input)) {
826
+ for (let i = 0; i < input.tool_input.edits.length; i++) {
827
+ const edit = input.tool_input.edits[i];
828
+ const shouldContinue = callback({
829
+ newContent: edit.new_string,
830
+ oldContent: edit.old_string,
831
+ index: i,
832
+ isWrite: false,
833
+ });
834
+ if (!shouldContinue)
835
+ return false;
836
+ }
837
+ return true;
838
+ }
839
+ return false;
840
+ }