opencode-command-hooks 0.1.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 (46) hide show
  1. package/README.md +796 -0
  2. package/dist/config/agent.d.ts +82 -0
  3. package/dist/config/agent.d.ts.map +1 -0
  4. package/dist/config/agent.js +145 -0
  5. package/dist/config/agent.js.map +1 -0
  6. package/dist/config/global.d.ts +36 -0
  7. package/dist/config/global.d.ts.map +1 -0
  8. package/dist/config/global.js +219 -0
  9. package/dist/config/global.js.map +1 -0
  10. package/dist/config/markdown.d.ts +119 -0
  11. package/dist/config/markdown.d.ts.map +1 -0
  12. package/dist/config/markdown.js +373 -0
  13. package/dist/config/markdown.js.map +1 -0
  14. package/dist/config/merge.d.ts +67 -0
  15. package/dist/config/merge.d.ts.map +1 -0
  16. package/dist/config/merge.js +192 -0
  17. package/dist/config/merge.js.map +1 -0
  18. package/dist/execution/shell.d.ts +55 -0
  19. package/dist/execution/shell.d.ts.map +1 -0
  20. package/dist/execution/shell.js +187 -0
  21. package/dist/execution/shell.js.map +1 -0
  22. package/dist/execution/template.d.ts +55 -0
  23. package/dist/execution/template.d.ts.map +1 -0
  24. package/dist/execution/template.js +106 -0
  25. package/dist/execution/template.js.map +1 -0
  26. package/dist/executor.d.ts +54 -0
  27. package/dist/executor.d.ts.map +1 -0
  28. package/dist/executor.js +314 -0
  29. package/dist/executor.js.map +1 -0
  30. package/dist/index.d.ts +22 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +359 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/logging.d.ts +24 -0
  35. package/dist/logging.d.ts.map +1 -0
  36. package/dist/logging.js +57 -0
  37. package/dist/logging.js.map +1 -0
  38. package/dist/schemas.d.ts +425 -0
  39. package/dist/schemas.d.ts.map +1 -0
  40. package/dist/schemas.js +150 -0
  41. package/dist/schemas.js.map +1 -0
  42. package/dist/types/hooks.d.ts +635 -0
  43. package/dist/types/hooks.d.ts.map +1 -0
  44. package/dist/types/hooks.js +12 -0
  45. package/dist/types/hooks.js.map +1 -0
  46. package/package.json +66 -0
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Shell command execution module for opencode-command-hooks
3
+ *
4
+ * Provides functions to execute shell commands using Bun's $ template literals
5
+ * with proper error handling, output capture, and truncation.
6
+ *
7
+ * Key features:
8
+ * - Execute single or multiple commands sequentially
9
+ * - Capture stdout, stderr, and exit codes
10
+ * - Truncate output to configurable limit (default: 30,000 chars, matching OpenCode)
11
+ * - Never throw errors - always return results
12
+ * - Support debug logging via OPENCODE_HOOKS_DEBUG
13
+ */
14
+ import type { HookExecutionResult } from "../types/hooks.js";
15
+ /**
16
+ * Execute a single shell command
17
+ *
18
+ * @param command - Shell command to execute
19
+ * @param options - Execution options
20
+ * @returns HookExecutionResult with command output and exit code
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * const result = await executeCommand("pnpm test")
25
+ * console.log(result.exitCode, result.stdout)
26
+ * ```
27
+ */
28
+ export declare function executeCommand(command: string, options?: {
29
+ truncateOutput?: number;
30
+ }): Promise<HookExecutionResult>;
31
+ /**
32
+ * Execute multiple shell commands sequentially
33
+ *
34
+ * Commands run one after another, even if earlier commands fail.
35
+ * Each command's result is captured and returned separately.
36
+ *
37
+ * @param commands - Single command string or array of command strings
38
+ * @param hookId - Hook ID for tracking (included in results)
39
+ * @param options - Execution options
40
+ * @returns Array of HookExecutionResult, one per command
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const results = await executeCommands(
45
+ * ["pnpm lint", "pnpm test"],
46
+ * "my-hook",
47
+ * { truncateOutput: 2000 }
48
+ * )
49
+ * results.forEach(r => console.log(r.exitCode))
50
+ * ```
51
+ */
52
+ export declare function executeCommands(commands: string | string[], hookId: string, options?: {
53
+ truncateOutput?: number;
54
+ }): Promise<HookExecutionResult[]>;
55
+ //# sourceMappingURL=shell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../../src/execution/shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAA;AA+B5D;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAuC9B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,EAC3B,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GACpC,OAAO,CAAC,mBAAmB,EAAE,CAAC,CA+ChC"}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Shell command execution module for opencode-command-hooks
3
+ *
4
+ * Provides functions to execute shell commands using Bun's $ template literals
5
+ * with proper error handling, output capture, and truncation.
6
+ *
7
+ * Key features:
8
+ * - Execute single or multiple commands sequentially
9
+ * - Capture stdout, stderr, and exit codes
10
+ * - Truncate output to configurable limit (default: 30,000 chars, matching OpenCode)
11
+ * - Never throw errors - always return results
12
+ * - Support debug logging via OPENCODE_HOOKS_DEBUG
13
+ */
14
+ import { logger } from "../logging.js";
15
+ const DEFAULT_TRUNCATE_LIMIT = 30000;
16
+ /**
17
+ * Check if debug logging is enabled
18
+ */
19
+ function isDebugEnabled() {
20
+ return process.env.OPENCODE_HOOKS_DEBUG === "1" || process.env.OPENCODE_HOOKS_DEBUG === "true";
21
+ }
22
+ /**
23
+ * Truncate text to a maximum length, matching OpenCode's bash tool behavior
24
+ */
25
+ function truncateText(text, limit) {
26
+ if (!text)
27
+ return "";
28
+ if (text.length <= limit)
29
+ return text;
30
+ const truncated = text.slice(0, limit);
31
+ const metadata = [
32
+ "",
33
+ "",
34
+ "<bash_metadata>",
35
+ `bash tool truncated output as it exceeded ${limit} char limit`,
36
+ "</bash_metadata>"
37
+ ].join("\n");
38
+ return truncated + metadata;
39
+ }
40
+ /**
41
+ * Execute a single shell command
42
+ *
43
+ * @param command - Shell command to execute
44
+ * @param options - Execution options
45
+ * @returns HookExecutionResult with command output and exit code
46
+ *
47
+ * @example
48
+ * ```typescript
49
+ * const result = await executeCommand("pnpm test")
50
+ * console.log(result.exitCode, result.stdout)
51
+ * ```
52
+ */
53
+ export async function executeCommand(command, options) {
54
+ const truncateLimit = options?.truncateOutput ?? DEFAULT_TRUNCATE_LIMIT;
55
+ const hookId = "command"; // Will be set by caller
56
+ if (isDebugEnabled()) {
57
+ logger.debug(`Executing command: ${command}`);
58
+ }
59
+ try {
60
+ // Execute command using Bun's $ template literal with nothrow to prevent throwing on non-zero exit
61
+ // We need to use dynamic template literal evaluation
62
+ const result = await executeShellCommand(command);
63
+ const stdout = truncateText(result.stdout, truncateLimit);
64
+ const stderr = truncateText(result.stderr, truncateLimit);
65
+ const exitCode = result.exitCode ?? 0;
66
+ const success = exitCode === 0;
67
+ if (isDebugEnabled()) {
68
+ logger.debug(`Command completed: exit ${exitCode}, stdout length: ${stdout.length}, stderr length: ${stderr.length}`);
69
+ }
70
+ return {
71
+ hookId,
72
+ success,
73
+ exitCode,
74
+ stdout,
75
+ stderr,
76
+ };
77
+ }
78
+ catch (err) {
79
+ const errorMessage = err instanceof Error ? err.message : String(err);
80
+ logger.error(`Failed to execute command: ${errorMessage}`);
81
+ return {
82
+ hookId,
83
+ success: false,
84
+ error: errorMessage,
85
+ };
86
+ }
87
+ }
88
+ /**
89
+ * Execute multiple shell commands sequentially
90
+ *
91
+ * Commands run one after another, even if earlier commands fail.
92
+ * Each command's result is captured and returned separately.
93
+ *
94
+ * @param commands - Single command string or array of command strings
95
+ * @param hookId - Hook ID for tracking (included in results)
96
+ * @param options - Execution options
97
+ * @returns Array of HookExecutionResult, one per command
98
+ *
99
+ * @example
100
+ * ```typescript
101
+ * const results = await executeCommands(
102
+ * ["pnpm lint", "pnpm test"],
103
+ * "my-hook",
104
+ * { truncateOutput: 2000 }
105
+ * )
106
+ * results.forEach(r => console.log(r.exitCode))
107
+ * ```
108
+ */
109
+ export async function executeCommands(commands, hookId, options) {
110
+ const truncateLimit = options?.truncateOutput ?? DEFAULT_TRUNCATE_LIMIT;
111
+ const commandArray = Array.isArray(commands) ? commands : [commands];
112
+ if (isDebugEnabled()) {
113
+ logger.debug(`Executing ${commandArray.length} command(s) for hook "${hookId}"`);
114
+ }
115
+ const results = [];
116
+ for (const command of commandArray) {
117
+ try {
118
+ if (isDebugEnabled()) {
119
+ logger.debug(`[${hookId}] Executing: ${command}`);
120
+ }
121
+ const result = await executeShellCommand(command);
122
+ const stdout = truncateText(result.stdout, truncateLimit);
123
+ const stderr = truncateText(result.stderr, truncateLimit);
124
+ const exitCode = result.exitCode ?? 0;
125
+ const success = exitCode === 0;
126
+ if (isDebugEnabled()) {
127
+ logger.debug(`[${hookId}] Command completed: exit ${exitCode}, stdout length: ${stdout.length}, stderr length: ${stderr.length}`);
128
+ }
129
+ results.push({
130
+ hookId,
131
+ success,
132
+ exitCode,
133
+ stdout,
134
+ stderr,
135
+ });
136
+ }
137
+ catch (err) {
138
+ const errorMessage = err instanceof Error ? err.message : String(err);
139
+ logger.error(`[${hookId}] Failed to execute command: ${errorMessage}`);
140
+ results.push({
141
+ hookId,
142
+ success: false,
143
+ error: errorMessage,
144
+ });
145
+ }
146
+ }
147
+ return results;
148
+ }
149
+ /**
150
+ * Internal helper to execute a shell command using Bun's $ API
151
+ *
152
+ * This function handles the actual shell execution with proper error handling.
153
+ * Uses Bun's $ template literal with .nothrow() to prevent throwing on non-zero
154
+ * exit codes and .quiet() to capture output without printing to console.
155
+ *
156
+ * @param command - Shell command to execute
157
+ * @returns Object with stdout, stderr, and exitCode
158
+ */
159
+ async function executeShellCommand(command) {
160
+ try {
161
+ // Use Bun's $ template literal to execute the command
162
+ // The nothrow() method prevents throwing on non-zero exit codes
163
+ // The quiet() method suppresses output and returns Buffers
164
+ const result = await Bun.$ `sh -c ${command}`.nothrow().quiet();
165
+ // Extract stdout and stderr as text
166
+ // result.stdout and result.stderr are Buffers, convert to string
167
+ const stdout = result.stdout instanceof Buffer ? result.stdout.toString() : String(result.stdout);
168
+ const stderr = result.stderr instanceof Buffer ? result.stderr.toString() : String(result.stderr);
169
+ const exitCode = result.exitCode ?? 0;
170
+ return {
171
+ stdout,
172
+ stderr,
173
+ exitCode,
174
+ };
175
+ }
176
+ catch (error) {
177
+ // If Bun shell execution fails unexpectedly, return error details
178
+ const errorMessage = error instanceof Error ? error.message : String(error);
179
+ logger.error(`Unexpected error executing command: ${errorMessage}`);
180
+ return {
181
+ stdout: "",
182
+ stderr: errorMessage,
183
+ exitCode: 1,
184
+ };
185
+ }
186
+ }
187
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell.js","sourceRoot":"","sources":["../../src/execution/shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEtC,MAAM,sBAAsB,GAAG,KAAM,CAAA;AAErC;;GAEG;AACH,SAAS,cAAc;IACpB,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,MAAM,CAAA;AACjG,CAAC;AAED;;GAEG;AACH,SAAS,YAAY,CAAC,IAAwB,EAAE,KAAa;IAC3D,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IACpB,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK;QAAE,OAAO,IAAI,CAAA;IAErC,MAAM,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAA;IACtC,MAAM,QAAQ,GAAG;QACf,EAAE;QACF,EAAE;QACF,iBAAiB;QACjB,6CAA6C,KAAK,aAAa;QAC/D,kBAAkB;KACnB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAEZ,OAAO,SAAS,GAAG,QAAQ,CAAA;AAC7B,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAe,EACf,OAAqC;IAErC,MAAM,aAAa,GAAG,OAAO,EAAE,cAAc,IAAI,sBAAsB,CAAA;IACvE,MAAM,MAAM,GAAG,SAAS,CAAA,CAAC,wBAAwB;IAE/C,IAAI,cAAc,EAAE,EAAE,CAAC;QACrB,MAAM,CAAC,KAAK,CAAC,sBAAsB,OAAO,EAAE,CAAC,CAAA;IAC/C,CAAC;IAEH,IAAI,CAAC;QACH,mGAAmG;QACnG,qDAAqD;QACrD,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAA;QAEjD,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;QACzD,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;QACzD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAA;QACrC,MAAM,OAAO,GAAG,QAAQ,KAAK,CAAC,CAAA;QAE5B,IAAI,cAAc,EAAE,EAAE,CAAC;YACrB,MAAM,CAAC,KAAK,CAAC,2BAA2B,QAAQ,oBAAoB,MAAM,CAAC,MAAM,oBAAoB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;QACvH,CAAC;QAEH,OAAO;YACL,MAAM;YACN,OAAO;YACP,QAAQ;YACR,MAAM;YACN,MAAM;SACP,CAAA;IACD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,YAAY,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACrE,MAAM,CAAC,KAAK,CAAC,8BAA8B,YAAY,EAAE,CAAC,CAAA;QAE5D,OAAO;YACL,MAAM;YACN,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,YAAY;SACpB,CAAA;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,QAA2B,EAC3B,MAAc,EACd,OAAqC;IAErC,MAAM,aAAa,GAAG,OAAO,EAAE,cAAc,IAAI,sBAAsB,CAAA;IACvE,MAAM,YAAY,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;IAElE,IAAI,cAAc,EAAE,EAAE,CAAC;QACrB,MAAM,CAAC,KAAK,CAAC,aAAa,YAAY,CAAC,MAAM,yBAAyB,MAAM,GAAG,CAAC,CAAA;IAClF,CAAC;IAEH,MAAM,OAAO,GAA0B,EAAE,CAAA;IAEzC,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;QACnC,IAAI,CAAC;YACF,IAAI,cAAc,EAAE,EAAE,CAAC;gBACpB,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,gBAAgB,OAAO,EAAE,CAAC,CAAA;YACnD,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,OAAO,CAAC,CAAA;YAEjD,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;YACzD,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;YACzD,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAA;YACrC,MAAM,OAAO,GAAG,QAAQ,KAAK,CAAC,CAAA;YAE5B,IAAI,cAAc,EAAE,EAAE,CAAC;gBACrB,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,6BAA6B,QAAQ,oBAAoB,MAAM,CAAC,MAAM,oBAAoB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAA;YACnI,CAAC;YAEH,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM;gBACN,OAAO;gBACP,QAAQ;gBACR,MAAM;gBACN,MAAM;aACP,CAAC,CAAA;QACF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,YAAY,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACrE,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,gCAAgC,YAAY,EAAE,CAAC,CAAA;YAExE,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM;gBACN,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,YAAY;aACpB,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,KAAK,UAAU,mBAAmB,CAChC,OAAe;IAEd,IAAI,CAAC;QACH,sDAAsD;QACtD,gEAAgE;QAChE,2DAA2D;QAC3D,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,CAAA,SAAS,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,CAAA;QAE9D,oCAAoC;QACpC,iEAAiE;QACjE,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACjG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,YAAY,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QACjG,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,CAAC,CAAA;QAErC,OAAO;YACL,MAAM;YACN,MAAM;YACN,QAAQ;SACT,CAAA;IACF,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,kEAAkE;QAClE,MAAM,YAAY,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAC3E,MAAM,CAAC,KAAK,CAAC,uCAAuC,YAAY,EAAE,CAAC,CAAA;QAErE,OAAO;YACL,MAAM,EAAE,EAAE;YACV,MAAM,EAAE,YAAY;YACpB,QAAQ,EAAE,CAAC;SACZ,CAAA;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Template interpolation module for opencode-command-hooks
3
+ *
4
+ * Provides functions to replace placeholders in template strings with values
5
+ * from hook execution context. Supports graceful handling of missing values
6
+ * and multi-line templates.
7
+ *
8
+ * Supported placeholders:
9
+ * - {id} - hook ID
10
+ * - {agent} - agent name (if available)
11
+ * - {tool} - tool name (if available)
12
+ * - {cmd} - command string (if available)
13
+ * - {stdout} - command stdout (if available)
14
+ * - {stderr} - command stderr (if available)
15
+ * - {exitCode} - command exit code (if available)
16
+ */
17
+ import type { TemplateContext } from "../types/hooks.js";
18
+ /**
19
+ * Interpolate a template string by replacing placeholders with context values
20
+ *
21
+ * Replaces the following placeholders:
22
+ * - {id} - hook ID (required, always available)
23
+ * - {agent} - agent name (optional)
24
+ * - {tool} - tool name (optional)
25
+ * - {cmd} - command string (optional)
26
+ * - {stdout} - command stdout (optional)
27
+ * - {stderr} - command stderr (optional)
28
+ * - {exitCode} - command exit code (optional)
29
+ *
30
+ * Missing values are replaced with empty strings. The function never throws
31
+ * and always returns a valid string.
32
+ *
33
+ * @param template - Template string with placeholders
34
+ * @param context - TemplateContext with values for substitution
35
+ * @returns Interpolated string with placeholders replaced
36
+ *
37
+ * @example
38
+ * ```typescript
39
+ * const context: TemplateContext = {
40
+ * id: "tests-after-task",
41
+ * agent: "build",
42
+ * tool: "task",
43
+ * cmd: "pnpm test",
44
+ * stdout: "✓ All tests passed",
45
+ * stderr: "",
46
+ * exitCode: 0
47
+ * }
48
+ *
49
+ * const template = "Hook {id} for {tool} completed: exit {exitCode}\n```\n{stdout}\n```"
50
+ * const result = interpolateTemplate(template, context)
51
+ * // Result: "Hook tests-after-task for task completed: exit 0\n```\n✓ All tests passed\n```"
52
+ * ```
53
+ */
54
+ export declare function interpolateTemplate(template: string | undefined, context: TemplateContext): string;
55
+ //# sourceMappingURL=template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../../src/execution/template.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AA2BxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,CAmClG"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Template interpolation module for opencode-command-hooks
3
+ *
4
+ * Provides functions to replace placeholders in template strings with values
5
+ * from hook execution context. Supports graceful handling of missing values
6
+ * and multi-line templates.
7
+ *
8
+ * Supported placeholders:
9
+ * - {id} - hook ID
10
+ * - {agent} - agent name (if available)
11
+ * - {tool} - tool name (if available)
12
+ * - {cmd} - command string (if available)
13
+ * - {stdout} - command stdout (if available)
14
+ * - {stderr} - command stderr (if available)
15
+ * - {exitCode} - command exit code (if available)
16
+ */
17
+ import { logger } from "../logging.js";
18
+ /**
19
+ * Check if debug logging is enabled
20
+ */
21
+ function isDebugEnabled() {
22
+ return process.env.OPENCODE_HOOKS_DEBUG === "1" || process.env.OPENCODE_HOOKS_DEBUG === "true";
23
+ }
24
+ /**
25
+ * Replace a placeholder in a template string with a value
26
+ *
27
+ * @param template - The template string
28
+ * @param placeholder - The placeholder name (without braces, e.g., "id", "stdout")
29
+ * @param value - The value to replace with (undefined becomes empty string)
30
+ * @returns The template with the placeholder replaced
31
+ */
32
+ function replacePlaceholder(template, placeholder, value) {
33
+ // Convert value to string, handling undefined/null
34
+ const stringValue = value === undefined || value === null ? "" : String(value);
35
+ // Create regex to match {placeholder} globally
36
+ const regex = new RegExp(`\\{${placeholder}\\}`, "g");
37
+ return template.replace(regex, stringValue);
38
+ }
39
+ /**
40
+ * Interpolate a template string by replacing placeholders with context values
41
+ *
42
+ * Replaces the following placeholders:
43
+ * - {id} - hook ID (required, always available)
44
+ * - {agent} - agent name (optional)
45
+ * - {tool} - tool name (optional)
46
+ * - {cmd} - command string (optional)
47
+ * - {stdout} - command stdout (optional)
48
+ * - {stderr} - command stderr (optional)
49
+ * - {exitCode} - command exit code (optional)
50
+ *
51
+ * Missing values are replaced with empty strings. The function never throws
52
+ * and always returns a valid string.
53
+ *
54
+ * @param template - Template string with placeholders
55
+ * @param context - TemplateContext with values for substitution
56
+ * @returns Interpolated string with placeholders replaced
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * const context: TemplateContext = {
61
+ * id: "tests-after-task",
62
+ * agent: "build",
63
+ * tool: "task",
64
+ * cmd: "pnpm test",
65
+ * stdout: "✓ All tests passed",
66
+ * stderr: "",
67
+ * exitCode: 0
68
+ * }
69
+ *
70
+ * const template = "Hook {id} for {tool} completed: exit {exitCode}\n```\n{stdout}\n```"
71
+ * const result = interpolateTemplate(template, context)
72
+ * // Result: "Hook tests-after-task for task completed: exit 0\n```\n✓ All tests passed\n```"
73
+ * ```
74
+ */
75
+ export function interpolateTemplate(template, context) {
76
+ // Handle null/undefined template
77
+ if (!template) {
78
+ return "";
79
+ }
80
+ if (isDebugEnabled()) {
81
+ logger.debug(`Interpolating template with context: ${JSON.stringify({
82
+ id: context.id,
83
+ agent: context.agent,
84
+ tool: context.tool,
85
+ cmd: context.cmd ? `${context.cmd.substring(0, 50)}...` : undefined,
86
+ stdout: context.stdout ? `${context.stdout.substring(0, 50)}...` : undefined,
87
+ stderr: context.stderr ? `${context.stderr.substring(0, 50)}...` : undefined,
88
+ exitCode: context.exitCode,
89
+ })}`);
90
+ }
91
+ let result = template;
92
+ // Replace all placeholders
93
+ // Order doesn't matter since each placeholder is unique
94
+ result = replacePlaceholder(result, "id", context.id);
95
+ result = replacePlaceholder(result, "agent", context.agent);
96
+ result = replacePlaceholder(result, "tool", context.tool);
97
+ result = replacePlaceholder(result, "cmd", context.cmd);
98
+ result = replacePlaceholder(result, "stdout", context.stdout);
99
+ result = replacePlaceholder(result, "stderr", context.stderr);
100
+ result = replacePlaceholder(result, "exitCode", context.exitCode);
101
+ if (isDebugEnabled()) {
102
+ logger.debug(`Template interpolation complete, result length: ${result.length}`);
103
+ }
104
+ return result;
105
+ }
106
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/execution/template.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Unified hook executor for opencode-command-hooks
3
+ *
4
+ * Consolidates the logic for executing hooks across all event types (tool.execute.before,
5
+ * tool.execute.after, session.start, session.idle, etc.). This module provides a single
6
+ * entry point for hook execution that handles:
7
+ *
8
+ * 1. Filtering hooks based on execution context (event type, tool name, agent, etc.)
9
+ * 2. Executing hook commands via shell
10
+ * 3. Capturing and formatting command output
11
+ * 4. Injecting results into sessions via OpenCode SDK
12
+ * 5. Graceful error handling with non-blocking semantics
13
+ *
14
+ * Non-blocking error semantics: Hook failures never prevent tool/session execution.
15
+ * Errors are logged and optionally injected into the session, but never thrown.
16
+ */
17
+ import type { OpencodeClient } from "@opencode-ai/sdk";
18
+ import type { ToolHook, SessionHook, HookExecutionContext } from "./types/hooks.js";
19
+ /**
20
+ * Execute a collection of hooks
21
+ *
22
+ * Main entry point for hook execution. Handles both tool and session hooks,
23
+ * executing each matched hook in sequence. Implements non-blocking error semantics:
24
+ * any errors during hook execution are logged and optionally injected into the
25
+ * session, but never prevent the tool/session from executing.
26
+ *
27
+ * This function consolidates the logic that was previously duplicated across
28
+ * individual event handlers (tool-before, tool-after, session-start, session-idle).
29
+ *
30
+ * @param hooks - Array of hooks to execute (can be tool or session hooks)
31
+ * @param context - Execution context with session ID, agent, tool name, etc.
32
+ * @param client - OpenCode SDK client for message injection
33
+ * @returns Promise that resolves when all hooks are processed
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * // Execute tool hooks for a tool.execute.before event
38
+ * await executeHooks(matchedToolHooks, {
39
+ * sessionId: "session-123",
40
+ * agent: "build",
41
+ * tool: "task",
42
+ * slashCommand: undefined,
43
+ * callId: "call-456"
44
+ * }, client)
45
+ *
46
+ * // Execute session hooks for a session.start event
47
+ * await executeHooks(matchedSessionHooks, {
48
+ * sessionId: "session-123",
49
+ * agent: "build"
50
+ * }, client)
51
+ * ```
52
+ */
53
+ export declare function executeHooks(hooks: (ToolHook | SessionHook)[], context: HookExecutionContext, client: OpencodeClient): Promise<void>;
54
+ //# sourceMappingURL=executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,KAAK,EACT,QAAQ,EACR,WAAW,EAEX,oBAAoB,EACtB,MAAM,kBAAkB,CAAA;AAqTzB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,YAAY,CAC/B,KAAK,EAAE,CAAC,QAAQ,GAAG,WAAW,CAAC,EAAE,EACjC,OAAO,EAAE,oBAAoB,EAC7B,MAAM,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CAmCf"}