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.
- package/README.md +796 -0
- package/dist/config/agent.d.ts +82 -0
- package/dist/config/agent.d.ts.map +1 -0
- package/dist/config/agent.js +145 -0
- package/dist/config/agent.js.map +1 -0
- package/dist/config/global.d.ts +36 -0
- package/dist/config/global.d.ts.map +1 -0
- package/dist/config/global.js +219 -0
- package/dist/config/global.js.map +1 -0
- package/dist/config/markdown.d.ts +119 -0
- package/dist/config/markdown.d.ts.map +1 -0
- package/dist/config/markdown.js +373 -0
- package/dist/config/markdown.js.map +1 -0
- package/dist/config/merge.d.ts +67 -0
- package/dist/config/merge.d.ts.map +1 -0
- package/dist/config/merge.js +192 -0
- package/dist/config/merge.js.map +1 -0
- package/dist/execution/shell.d.ts +55 -0
- package/dist/execution/shell.d.ts.map +1 -0
- package/dist/execution/shell.js +187 -0
- package/dist/execution/shell.js.map +1 -0
- package/dist/execution/template.d.ts +55 -0
- package/dist/execution/template.d.ts.map +1 -0
- package/dist/execution/template.js +106 -0
- package/dist/execution/template.js.map +1 -0
- package/dist/executor.d.ts +54 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +314 -0
- package/dist/executor.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +359 -0
- package/dist/index.js.map +1 -0
- package/dist/logging.d.ts +24 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +57 -0
- package/dist/logging.js.map +1 -0
- package/dist/schemas.d.ts +425 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +150 -0
- package/dist/schemas.js.map +1 -0
- package/dist/types/hooks.d.ts +635 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/hooks.js +12 -0
- package/dist/types/hooks.js.map +1 -0
- 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":"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"}
|
|
@@ -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"}
|