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,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent configuration resolution and loading for command hooks
|
|
3
|
+
*
|
|
4
|
+
* Handles finding and parsing agent markdown files (.opencode/agent/*.md or
|
|
5
|
+
* ~/.config/opencode/agent/*.md) to extract command_hooks from YAML frontmatter.
|
|
6
|
+
* These hooks are applied when the specific subagent is invoked via the task tool.
|
|
7
|
+
*/
|
|
8
|
+
import type { CommandHooksConfig } from "../types/hooks.js";
|
|
9
|
+
/**
|
|
10
|
+
* Resolve agent markdown file path by agent name
|
|
11
|
+
*
|
|
12
|
+
* Searches for agent markdown files in the following order:
|
|
13
|
+
* 1. Project-level: .opencode/agent/{name}.md
|
|
14
|
+
* 2. User-level: ~/.config/opencode/agent/{name}.md
|
|
15
|
+
*
|
|
16
|
+
* Returns the first existing path found, or null if no file exists.
|
|
17
|
+
*
|
|
18
|
+
* @param agentName - Name of the agent to resolve (without .md extension)
|
|
19
|
+
* @returns Promise resolving to absolute path of the agent markdown file, or null if not found
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const path = await resolveAgentPath("engineer");
|
|
24
|
+
* // Returns: "/Users/example/project/.opencode/agent/engineer.md" (if exists)
|
|
25
|
+
* // Or: "/Users/example/.config/opencode/agent/engineer.md" (if project path doesn't exist)
|
|
26
|
+
* // Or: null (if neither exists)
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveAgentPath(agentName: string): Promise<string | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Load command_hooks from an agent's markdown file
|
|
32
|
+
*
|
|
33
|
+
* Attempts to resolve the agent markdown file and extract command_hooks
|
|
34
|
+
* from its YAML frontmatter. Returns an empty config if the file doesn't
|
|
35
|
+
* exist or contains no valid hooks.
|
|
36
|
+
*
|
|
37
|
+
* **Caching:** This function uses the caching from loadMarkdownConfig() internally.
|
|
38
|
+
* Multiple calls for the same agent will only read the file once.
|
|
39
|
+
*
|
|
40
|
+
* Error handling:
|
|
41
|
+
* - If agent file doesn't exist: returns empty config
|
|
42
|
+
* - If file exists but has no frontmatter: returns empty config
|
|
43
|
+
* - If frontmatter is malformed: logs warning, returns empty config
|
|
44
|
+
* - If no command_hooks field: returns empty config
|
|
45
|
+
* - Never throws errors - always returns a valid config
|
|
46
|
+
*
|
|
47
|
+
* @param agentName - Name of the agent to load configuration for
|
|
48
|
+
* @returns Promise resolving to CommandHooksConfig (may be empty)
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const config = await loadAgentConfig("engineer");
|
|
53
|
+
* // Returns: { tool: [...], session: [...] } with agent-specific hooks
|
|
54
|
+
* // Or: { tool: [], session: [] } if no valid hooks found
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export declare function loadAgentConfig(agentName: string): Promise<CommandHooksConfig>;
|
|
58
|
+
/**
|
|
59
|
+
* Load and merge agent-specific configuration with global configuration
|
|
60
|
+
*
|
|
61
|
+
* Combines global command hooks with agent-specific hooks, where agent hooks
|
|
62
|
+
* take precedence for the specific agent context. This is useful when you want
|
|
63
|
+
* to apply both global hooks and agent-specific hooks together.
|
|
64
|
+
*
|
|
65
|
+
* The merge follows the same precedence rules as mergeConfigs:
|
|
66
|
+
* - Agent hooks with the same ID replace global hooks
|
|
67
|
+
* - Agent hooks with unique IDs are added alongside global hooks
|
|
68
|
+
*
|
|
69
|
+
* @param agentName - Name of the agent to load configuration for
|
|
70
|
+
* @returns Promise resolving to merged CommandHooksConfig
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const { config } = await loadAgentConfigWithGlobal("engineer");
|
|
75
|
+
* // Returns merged config with both global and engineer-specific hooks
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare function loadAgentConfigWithGlobal(agentName: string): Promise<{
|
|
79
|
+
config: CommandHooksConfig;
|
|
80
|
+
agentPath: string | null;
|
|
81
|
+
}>;
|
|
82
|
+
//# sourceMappingURL=agent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/config/agent.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAM5D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqChF;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAqBpF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,yBAAyB,CAC7C,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,kBAAkB,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAanE"}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent configuration resolution and loading for command hooks
|
|
3
|
+
*
|
|
4
|
+
* Handles finding and parsing agent markdown files (.opencode/agent/*.md or
|
|
5
|
+
* ~/.config/opencode/agent/*.md) to extract command_hooks from YAML frontmatter.
|
|
6
|
+
* These hooks are applied when the specific subagent is invoked via the task tool.
|
|
7
|
+
*/
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { loadMarkdownConfig } from "./markdown.js";
|
|
11
|
+
import { logger } from "../logging.js";
|
|
12
|
+
/**
|
|
13
|
+
* Resolve agent markdown file path by agent name
|
|
14
|
+
*
|
|
15
|
+
* Searches for agent markdown files in the following order:
|
|
16
|
+
* 1. Project-level: .opencode/agent/{name}.md
|
|
17
|
+
* 2. User-level: ~/.config/opencode/agent/{name}.md
|
|
18
|
+
*
|
|
19
|
+
* Returns the first existing path found, or null if no file exists.
|
|
20
|
+
*
|
|
21
|
+
* @param agentName - Name of the agent to resolve (without .md extension)
|
|
22
|
+
* @returns Promise resolving to absolute path of the agent markdown file, or null if not found
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const path = await resolveAgentPath("engineer");
|
|
27
|
+
* // Returns: "/Users/example/project/.opencode/agent/engineer.md" (if exists)
|
|
28
|
+
* // Or: "/Users/example/.config/opencode/agent/engineer.md" (if project path doesn't exist)
|
|
29
|
+
* // Or: null (if neither exists)
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export async function resolveAgentPath(agentName) {
|
|
33
|
+
// Validate agent name to prevent directory traversal
|
|
34
|
+
if (!agentName || agentName.includes("/") || agentName.includes("..")) {
|
|
35
|
+
logger.debug(`Invalid agent name: ${agentName}`);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const agentFileName = `${agentName}.md`;
|
|
39
|
+
// Check project-level agent file
|
|
40
|
+
const projectAgentPath = join(process.cwd(), ".opencode", "agent", agentFileName);
|
|
41
|
+
try {
|
|
42
|
+
const projectFile = Bun.file(projectAgentPath);
|
|
43
|
+
if (await projectFile.exists()) {
|
|
44
|
+
logger.debug(`Found project agent file: ${projectAgentPath}`);
|
|
45
|
+
return projectAgentPath;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
50
|
+
logger.debug(`Error checking project agent file ${projectAgentPath}: ${message}`);
|
|
51
|
+
}
|
|
52
|
+
// Check user-level agent file
|
|
53
|
+
const userAgentPath = join(homedir(), ".config", "opencode", "agent", agentFileName);
|
|
54
|
+
try {
|
|
55
|
+
const userFile = Bun.file(userAgentPath);
|
|
56
|
+
if (await userFile.exists()) {
|
|
57
|
+
logger.debug(`Found user agent file: ${userAgentPath}`);
|
|
58
|
+
return userAgentPath;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
63
|
+
logger.debug(`Error checking user agent file ${userAgentPath}: ${message}`);
|
|
64
|
+
}
|
|
65
|
+
logger.debug(`No agent file found for: ${agentName}`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Load command_hooks from an agent's markdown file
|
|
70
|
+
*
|
|
71
|
+
* Attempts to resolve the agent markdown file and extract command_hooks
|
|
72
|
+
* from its YAML frontmatter. Returns an empty config if the file doesn't
|
|
73
|
+
* exist or contains no valid hooks.
|
|
74
|
+
*
|
|
75
|
+
* **Caching:** This function uses the caching from loadMarkdownConfig() internally.
|
|
76
|
+
* Multiple calls for the same agent will only read the file once.
|
|
77
|
+
*
|
|
78
|
+
* Error handling:
|
|
79
|
+
* - If agent file doesn't exist: returns empty config
|
|
80
|
+
* - If file exists but has no frontmatter: returns empty config
|
|
81
|
+
* - If frontmatter is malformed: logs warning, returns empty config
|
|
82
|
+
* - If no command_hooks field: returns empty config
|
|
83
|
+
* - Never throws errors - always returns a valid config
|
|
84
|
+
*
|
|
85
|
+
* @param agentName - Name of the agent to load configuration for
|
|
86
|
+
* @returns Promise resolving to CommandHooksConfig (may be empty)
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```typescript
|
|
90
|
+
* const config = await loadAgentConfig("engineer");
|
|
91
|
+
* // Returns: { tool: [...], session: [...] } with agent-specific hooks
|
|
92
|
+
* // Or: { tool: [], session: [] } if no valid hooks found
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export async function loadAgentConfig(agentName) {
|
|
96
|
+
// Resolve the agent path
|
|
97
|
+
const agentPath = await resolveAgentPath(agentName);
|
|
98
|
+
if (!agentPath) {
|
|
99
|
+
logger.debug(`No agent file found for: ${agentName}, returning empty config`);
|
|
100
|
+
return { tool: [], session: [] };
|
|
101
|
+
}
|
|
102
|
+
// Load the markdown config using the existing function
|
|
103
|
+
const config = await loadMarkdownConfig(agentPath);
|
|
104
|
+
if (config.tool?.length === 0 && config.session?.length === 0) {
|
|
105
|
+
logger.debug(`Agent ${agentName} has no command_hooks in frontmatter`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
logger.debug(`Loaded agent config for ${agentName}: ${config.tool?.length ?? 0} tool hooks, ${config.session?.length ?? 0} session hooks`);
|
|
109
|
+
}
|
|
110
|
+
return config;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Load and merge agent-specific configuration with global configuration
|
|
114
|
+
*
|
|
115
|
+
* Combines global command hooks with agent-specific hooks, where agent hooks
|
|
116
|
+
* take precedence for the specific agent context. This is useful when you want
|
|
117
|
+
* to apply both global hooks and agent-specific hooks together.
|
|
118
|
+
*
|
|
119
|
+
* The merge follows the same precedence rules as mergeConfigs:
|
|
120
|
+
* - Agent hooks with the same ID replace global hooks
|
|
121
|
+
* - Agent hooks with unique IDs are added alongside global hooks
|
|
122
|
+
*
|
|
123
|
+
* @param agentName - Name of the agent to load configuration for
|
|
124
|
+
* @returns Promise resolving to merged CommandHooksConfig
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const { config } = await loadAgentConfigWithGlobal("engineer");
|
|
129
|
+
* // Returns merged config with both global and engineer-specific hooks
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export async function loadAgentConfigWithGlobal(agentName) {
|
|
133
|
+
const [globalConfig, agentConfig] = await Promise.all([
|
|
134
|
+
import("./global.js").then((m) => m.loadGlobalConfig()),
|
|
135
|
+
loadAgentConfig(agentName),
|
|
136
|
+
]);
|
|
137
|
+
return {
|
|
138
|
+
config: {
|
|
139
|
+
tool: [...(globalConfig.tool ?? []), ...(agentConfig.tool ?? [])],
|
|
140
|
+
session: [...(globalConfig.session ?? []), ...(agentConfig.session ?? [])],
|
|
141
|
+
},
|
|
142
|
+
agentPath: await resolveAgentPath(agentName),
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../../src/config/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global configuration parser for loading hooks from .opencode/command-hooks.jsonc
|
|
3
|
+
*
|
|
4
|
+
* Searches for .opencode/command-hooks.jsonc starting from the current working
|
|
5
|
+
* directory and walking up the directory tree. Parses JSONC format as CommandHooksConfig.
|
|
6
|
+
*/
|
|
7
|
+
import type { CommandHooksConfig } from "../types/hooks.js";
|
|
8
|
+
/**
|
|
9
|
+
* Load and parse global command hooks configuration
|
|
10
|
+
*
|
|
11
|
+
* Searches for .opencode/command-hooks.jsonc starting from the current working
|
|
12
|
+
* directory and walking up. Parses the entire file as CommandHooksConfig.
|
|
13
|
+
*
|
|
14
|
+
* **Caching:** This function implements in-memory caching to avoid repeated file
|
|
15
|
+
* system reads on every tool call. The cache is checked first; if null, the config
|
|
16
|
+
* is loaded from disk and cached for subsequent calls.
|
|
17
|
+
*
|
|
18
|
+
* Error handling:
|
|
19
|
+
* - If no config file found: returns empty config (not an error)
|
|
20
|
+
* - If config file is malformed: logs warning, returns empty config
|
|
21
|
+
* - If file is not a valid CommandHooksConfig: logs warning, returns empty config
|
|
22
|
+
* - Never throws errors - always returns a valid config
|
|
23
|
+
*
|
|
24
|
+
* @returns Promise resolving to CommandHooksConfig (may be empty)
|
|
25
|
+
*/
|
|
26
|
+
export declare function loadGlobalConfig(): Promise<CommandHooksConfig>;
|
|
27
|
+
/**
|
|
28
|
+
* Clear the global config cache
|
|
29
|
+
*
|
|
30
|
+
* Forces the next call to loadGlobalConfig() to reload from disk.
|
|
31
|
+
* Useful for testing or when config files may have changed.
|
|
32
|
+
*
|
|
33
|
+
* @internal For testing purposes
|
|
34
|
+
*/
|
|
35
|
+
export declare function clearGlobalConfigCache(): void;
|
|
36
|
+
//# sourceMappingURL=global.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AA0I5D;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAmFpE;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,IAAI,CAG7C"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global configuration parser for loading hooks from .opencode/command-hooks.jsonc
|
|
3
|
+
*
|
|
4
|
+
* Searches for .opencode/command-hooks.jsonc starting from the current working
|
|
5
|
+
* directory and walking up the directory tree. Parses JSONC format as CommandHooksConfig.
|
|
6
|
+
*/
|
|
7
|
+
import { join, dirname } from "path";
|
|
8
|
+
import { logger } from "../logging.js";
|
|
9
|
+
/**
|
|
10
|
+
* In-memory cache for global configuration
|
|
11
|
+
* Stores the loaded config to avoid repeated file system reads on every tool call.
|
|
12
|
+
* Set to null to force a reload on the next loadGlobalConfig() call.
|
|
13
|
+
*/
|
|
14
|
+
let cachedConfig = null;
|
|
15
|
+
/**
|
|
16
|
+
* Strip comments from JSONC content
|
|
17
|
+
* Handles both line comments and block comments
|
|
18
|
+
*/
|
|
19
|
+
function stripJsoncComments(content) {
|
|
20
|
+
let result = "";
|
|
21
|
+
let i = 0;
|
|
22
|
+
while (i < content.length) {
|
|
23
|
+
// Check for line comment
|
|
24
|
+
if (content[i] === "/" && content[i + 1] === "/") {
|
|
25
|
+
// Skip until end of line
|
|
26
|
+
while (i < content.length && content[i] !== "\n") {
|
|
27
|
+
i++;
|
|
28
|
+
}
|
|
29
|
+
// Keep the newline
|
|
30
|
+
if (i < content.length) {
|
|
31
|
+
result += "\n";
|
|
32
|
+
i++;
|
|
33
|
+
}
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
// Check for block comment
|
|
37
|
+
if (content[i] === "/" && content[i + 1] === "*") {
|
|
38
|
+
// Skip until */
|
|
39
|
+
i += 2;
|
|
40
|
+
while (i < content.length - 1) {
|
|
41
|
+
if (content[i] === "*" && content[i + 1] === "/") {
|
|
42
|
+
i += 2;
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
// Preserve newlines to maintain line numbers
|
|
46
|
+
if (content[i] === "\n") {
|
|
47
|
+
result += "\n";
|
|
48
|
+
}
|
|
49
|
+
i++;
|
|
50
|
+
}
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
// Regular character
|
|
54
|
+
result += content[i];
|
|
55
|
+
i++;
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Parse JSON content, handling parse errors gracefully
|
|
61
|
+
*/
|
|
62
|
+
function parseJson(content) {
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(content);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
68
|
+
throw new Error(`Failed to parse JSON: ${message}`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Check if a value is a valid CommandHooksConfig object
|
|
73
|
+
*/
|
|
74
|
+
function isValidCommandHooksConfig(value) {
|
|
75
|
+
if (typeof value !== "object" || value === null) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const obj = value;
|
|
79
|
+
// Both tool and session are optional
|
|
80
|
+
if (obj.tool !== undefined && !Array.isArray(obj.tool)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
if (obj.session !== undefined && !Array.isArray(obj.session)) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Find command hooks config file by walking up directory tree
|
|
90
|
+
* Looks for .opencode/command-hooks.jsonc
|
|
91
|
+
*/
|
|
92
|
+
async function findConfigFile(startDir) {
|
|
93
|
+
let currentDir = startDir;
|
|
94
|
+
// Limit search depth to avoid infinite loops
|
|
95
|
+
const maxDepth = 20;
|
|
96
|
+
let depth = 0;
|
|
97
|
+
while (depth < maxDepth) {
|
|
98
|
+
// Try .opencode/command-hooks.jsonc
|
|
99
|
+
const configPath = join(currentDir, ".opencode", "command-hooks.jsonc");
|
|
100
|
+
try {
|
|
101
|
+
const file = Bun.file(configPath);
|
|
102
|
+
if (await file.exists()) {
|
|
103
|
+
logger.debug(`Found config file: ${configPath}`);
|
|
104
|
+
return configPath;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Continue searching
|
|
109
|
+
}
|
|
110
|
+
// Move up one directory
|
|
111
|
+
const parentDir = dirname(currentDir);
|
|
112
|
+
if (parentDir === currentDir) {
|
|
113
|
+
// Reached filesystem root
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
currentDir = parentDir;
|
|
117
|
+
depth++;
|
|
118
|
+
}
|
|
119
|
+
logger.debug(`No config file found after searching ${depth} directories`);
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Load and parse global command hooks configuration
|
|
124
|
+
*
|
|
125
|
+
* Searches for .opencode/command-hooks.jsonc starting from the current working
|
|
126
|
+
* directory and walking up. Parses the entire file as CommandHooksConfig.
|
|
127
|
+
*
|
|
128
|
+
* **Caching:** This function implements in-memory caching to avoid repeated file
|
|
129
|
+
* system reads on every tool call. The cache is checked first; if null, the config
|
|
130
|
+
* is loaded from disk and cached for subsequent calls.
|
|
131
|
+
*
|
|
132
|
+
* Error handling:
|
|
133
|
+
* - If no config file found: returns empty config (not an error)
|
|
134
|
+
* - If config file is malformed: logs warning, returns empty config
|
|
135
|
+
* - If file is not a valid CommandHooksConfig: logs warning, returns empty config
|
|
136
|
+
* - Never throws errors - always returns a valid config
|
|
137
|
+
*
|
|
138
|
+
* @returns Promise resolving to CommandHooksConfig (may be empty)
|
|
139
|
+
*/
|
|
140
|
+
export async function loadGlobalConfig() {
|
|
141
|
+
// Check cache first
|
|
142
|
+
if (cachedConfig !== null) {
|
|
143
|
+
logger.debug(`Returning cached global config: ${cachedConfig.tool?.length ?? 0} tool hooks, ${cachedConfig.session?.length ?? 0} session hooks`);
|
|
144
|
+
return cachedConfig;
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
// Find config file
|
|
148
|
+
const configPath = await findConfigFile(process.cwd());
|
|
149
|
+
if (!configPath) {
|
|
150
|
+
logger.debug(`No .opencode/command-hooks.jsonc file found, using empty config`);
|
|
151
|
+
const emptyConfig = { tool: [], session: [] };
|
|
152
|
+
cachedConfig = emptyConfig;
|
|
153
|
+
return emptyConfig;
|
|
154
|
+
}
|
|
155
|
+
// Read file
|
|
156
|
+
let content;
|
|
157
|
+
try {
|
|
158
|
+
const file = Bun.file(configPath);
|
|
159
|
+
content = await file.text();
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
163
|
+
logger.info(`Failed to read config file ${configPath}: ${message}`);
|
|
164
|
+
const emptyConfig = { tool: [], session: [] };
|
|
165
|
+
cachedConfig = emptyConfig;
|
|
166
|
+
return emptyConfig;
|
|
167
|
+
}
|
|
168
|
+
// Parse JSONC
|
|
169
|
+
let parsed;
|
|
170
|
+
try {
|
|
171
|
+
const stripped = stripJsoncComments(content);
|
|
172
|
+
parsed = parseJson(stripped);
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
176
|
+
logger.info(`Failed to parse config file ${configPath}: ${message}`);
|
|
177
|
+
const emptyConfig = { tool: [], session: [] };
|
|
178
|
+
cachedConfig = emptyConfig;
|
|
179
|
+
return emptyConfig;
|
|
180
|
+
}
|
|
181
|
+
// Validate entire file as CommandHooksConfig
|
|
182
|
+
if (!isValidCommandHooksConfig(parsed)) {
|
|
183
|
+
logger.info(`Config file is not a valid CommandHooksConfig (expected { tool?: [], session?: [] }), using empty config`);
|
|
184
|
+
const emptyConfig = { tool: [], session: [] };
|
|
185
|
+
cachedConfig = emptyConfig;
|
|
186
|
+
return emptyConfig;
|
|
187
|
+
}
|
|
188
|
+
// Return with defaults for missing arrays
|
|
189
|
+
const result = {
|
|
190
|
+
tool: parsed.tool ?? [],
|
|
191
|
+
session: parsed.session ?? [],
|
|
192
|
+
};
|
|
193
|
+
logger.debug(`Loaded global config: ${result.tool?.length ?? 0} tool hooks, ${result.session?.length ?? 0} session hooks`);
|
|
194
|
+
// Cache the result
|
|
195
|
+
cachedConfig = result;
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
// Catch-all for unexpected errors
|
|
200
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
+
logger.info(`Unexpected error loading global config: ${message}`);
|
|
202
|
+
const emptyConfig = { tool: [], session: [] };
|
|
203
|
+
cachedConfig = emptyConfig;
|
|
204
|
+
return emptyConfig;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Clear the global config cache
|
|
209
|
+
*
|
|
210
|
+
* Forces the next call to loadGlobalConfig() to reload from disk.
|
|
211
|
+
* Useful for testing or when config files may have changed.
|
|
212
|
+
*
|
|
213
|
+
* @internal For testing purposes
|
|
214
|
+
*/
|
|
215
|
+
export function clearGlobalConfigCache() {
|
|
216
|
+
logger.debug("Clearing global config cache");
|
|
217
|
+
cachedConfig = null;
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=global.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"global.js","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC;;;;GAIG;AACH,IAAI,YAAY,GAA8B,IAAI,CAAC;AAEnD;;;GAGG;AACH,SAAS,kBAAkB,CAAC,OAAe;IACzC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,CAAC,GAAG,CAAC,CAAC;IAEV,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QAC1B,yBAAyB;QACzB,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACjD,yBAAyB;YACzB,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;gBACjD,CAAC,EAAE,CAAC;YACN,CAAC;YACD,mBAAmB;YACnB,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;gBACvB,MAAM,IAAI,IAAI,CAAC;gBACf,CAAC,EAAE,CAAC;YACN,CAAC;YACD,SAAS;QACX,CAAC;QAED,0BAA0B;QAC1B,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;YACjD,gBAAgB;YAChB,CAAC,IAAI,CAAC,CAAC;YACP,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9B,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;oBACjD,CAAC,IAAI,CAAC,CAAC;oBACP,MAAM;gBACR,CAAC;gBACD,6CAA6C;gBAC7C,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;oBACxB,MAAM,IAAI,IAAI,CAAC;gBACjB,CAAC;gBACD,CAAC,EAAE,CAAC;YACN,CAAC;YACD,SAAS;QACX,CAAC;QAED,oBAAoB;QACpB,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC;QACrB,CAAC,EAAE,CAAC;IACN,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAS,SAAS,CAAC,OAAe;IAChC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,yBAAyB,OAAO,EAAE,CAAC,CAAC;IACtD,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,yBAAyB,CAChC,KAAc;IAEd,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAC;IAE7C,qCAAqC;IACrC,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACvD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7D,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,cAAc,CAAC,QAAgB;IAC3C,IAAI,UAAU,GAAG,QAAQ,CAAC;IAE1B,6CAA6C;IAC7C,MAAM,QAAQ,GAAG,EAAE,CAAC;IACpB,IAAI,KAAK,GAAG,CAAC,CAAC;IAEd,OAAO,KAAK,GAAG,QAAQ,EAAE,CAAC;QACxB,oCAAoC;QACpC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,qBAAqB,CAAC,CAAC;QACzE,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACjC,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;gBACxB,MAAM,CAAC,KAAK,CAAC,sBAAsB,UAAU,EAAE,CAAC,CAAC;gBACjD,OAAO,UAAU,CAAC;YACpB,CAAC;QACJ,CAAC;QAAC,MAAM,CAAC;YACP,qBAAqB;QACvB,CAAC;QAED,wBAAwB;QACxB,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QACtC,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;YAC7B,0BAA0B;YAC1B,MAAM;QACR,CAAC;QAED,UAAU,GAAG,SAAS,CAAC;QACvB,KAAK,EAAE,CAAC;IACV,CAAC;IAEC,MAAM,CAAC,KAAK,CACV,wCAAwC,KAAK,cAAc,CAC5D,CAAC;IAEH,OAAO,IAAI,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB;IACnC,oBAAoB;IACpB,IAAI,YAAY,KAAK,IAAI,EAAE,CAAC;QACzB,MAAM,CAAC,KAAK,CAAC,mCAAmC,YAAY,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,gBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,gBAAgB,CAAC,CAAC;QAClJ,OAAO,YAAY,CAAC;IACtB,CAAC;IAED,IAAI,CAAC;QACH,mBAAmB;QACnB,MAAM,UAAU,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAErD,IAAI,CAAC,UAAU,EAAE,CAAC;YACf,MAAM,CAAC,KAAK,CACV,iEAAiE,CAClE,CAAC;YACH,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;YAC9C,YAAY,GAAG,WAAW,CAAC;YAC3B,OAAO,WAAW,CAAC;QACrB,CAAC;QAEH,YAAY;QACZ,IAAI,OAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAClC,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,EAAE,CAAC;QAC9B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtE,MAAM,CAAC,IAAI,CACT,8BAA8B,UAAU,KAAK,OAAO,EAAE,CACvD,CAAC;YACH,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;YAC9C,YAAY,GAAG,WAAW,CAAC;YAC3B,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,cAAc;QACd,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;YAC7C,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtE,MAAM,CAAC,IAAI,CACT,+BAA+B,UAAU,KAAK,OAAO,EAAE,CACxD,CAAC;YACH,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;YAC9C,YAAY,GAAG,WAAW,CAAC;YAC3B,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,6CAA6C;QAC7C,IAAI,CAAC,yBAAyB,CAAC,MAAM,CAAC,EAAE,CAAC;YACtC,MAAM,CAAC,IAAI,CACT,0GAA0G,CAC3G,CAAC;YACH,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;YAC9C,YAAY,GAAG,WAAW,CAAC;YAC3B,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,0CAA0C;QAC1C,MAAM,MAAM,GAAuB;YACjC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE;YACvB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;SAC9B,CAAC;QAEA,MAAM,CAAC,KAAK,CACV,yBAAyB,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,gBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,gBAAgB,CAC7G,CAAC;QAEH,mBAAmB;QACnB,YAAY,GAAG,MAAM,CAAC;QACtB,OAAO,MAAM,CAAC;IACjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,kCAAkC;QAClC,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,MAAM,CAAC,IAAI,CACT,2CAA2C,OAAO,EAAE,CACrD,CAAC;QACH,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QAC9C,YAAY,GAAG,WAAW,CAAC;QAC3B,OAAO,WAAW,CAAC;IACrB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB;IACnC,MAAM,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IAC7C,YAAY,GAAG,IAAI,CAAC;AACvB,CAAC"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Markdown configuration parser for loading hooks from agent and slash-command markdown files
|
|
3
|
+
*
|
|
4
|
+
* Parses YAML frontmatter from markdown files and extracts command_hooks configuration.
|
|
5
|
+
* Supports both agent markdown files (typically in .opencode/agents/) and slash-command
|
|
6
|
+
* markdown files (typically in .opencode/commands/).
|
|
7
|
+
*/
|
|
8
|
+
import type { AgentHooks, CommandHooksConfig } from "../types/hooks.js";
|
|
9
|
+
/**
|
|
10
|
+
* Extract YAML frontmatter from markdown content
|
|
11
|
+
*
|
|
12
|
+
* Frontmatter is defined as content between the first `---` and second `---`
|
|
13
|
+
* at the start of the file. Returns the raw YAML string (without delimiters).
|
|
14
|
+
*
|
|
15
|
+
* @param content - Full markdown file content
|
|
16
|
+
* @returns Raw YAML string, or null if no frontmatter found
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```
|
|
20
|
+
* ---
|
|
21
|
+
* name: my-agent
|
|
22
|
+
* command_hooks:
|
|
23
|
+
* tool: [...]
|
|
24
|
+
* ---
|
|
25
|
+
*
|
|
26
|
+
* # Agent content
|
|
27
|
+
* ```
|
|
28
|
+
* Returns the YAML between the delimiters
|
|
29
|
+
*/
|
|
30
|
+
export declare function extractYamlFrontmatter(content: string): string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Parse YAML content and return the parsed object
|
|
33
|
+
*
|
|
34
|
+
* Handles YAML parsing errors gracefully by returning null.
|
|
35
|
+
* Does not throw errors - callers should check for null return value.
|
|
36
|
+
*
|
|
37
|
+
* @param yamlContent - Raw YAML string to parse
|
|
38
|
+
* @returns Parsed YAML object, or null if parsing failed
|
|
39
|
+
*/
|
|
40
|
+
export declare function parseYamlFrontmatter(content: string): unknown;
|
|
41
|
+
/**
|
|
42
|
+
* Parse simplified agent hooks from YAML frontmatter content
|
|
43
|
+
*
|
|
44
|
+
* Extracts and validates the new simplified `hooks` format from agent markdown
|
|
45
|
+
* frontmatter. Returns null if no hooks are present or if parsing fails.
|
|
46
|
+
*
|
|
47
|
+
* @param yamlContent - Raw YAML string from frontmatter
|
|
48
|
+
* @param agentName - Name of the agent (used for auto-generating hook IDs)
|
|
49
|
+
* @returns Parsed AgentHooks object, or null if no valid hooks found
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```yaml
|
|
53
|
+
* hooks:
|
|
54
|
+
* before:
|
|
55
|
+
* - run: "echo starting"
|
|
56
|
+
* after:
|
|
57
|
+
* - run: ["npm run test"]
|
|
58
|
+
* inject: "Results:\n{stdout}"
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseAgentHooks(yamlContent: string): AgentHooks | null;
|
|
62
|
+
/**
|
|
63
|
+
* Convert simplified agent hooks to internal CommandHooksConfig format
|
|
64
|
+
*
|
|
65
|
+
* Takes the simplified AgentHooks format and converts it to the internal
|
|
66
|
+
* ToolHook[] format with auto-generated IDs and proper when clauses.
|
|
67
|
+
*
|
|
68
|
+
* @param agentHooks - Simplified agent hooks configuration
|
|
69
|
+
* @param agentName - Name of the agent (used for hook ID generation)
|
|
70
|
+
* @returns CommandHooksConfig with tool hooks converted to internal format
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```typescript
|
|
74
|
+
* const simpleHooks: AgentHooks = {
|
|
75
|
+
* after: [{ run: "npm run test", inject: "Results: {stdout}" }]
|
|
76
|
+
* };
|
|
77
|
+
* const config = convertToCommandHooksConfig(simpleHooks, "engineer");
|
|
78
|
+
* // Results in: { tool: [{ id: "engineer-after-0", when: {...}, run: ..., inject: ... }] }
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function convertToCommandHooksConfig(agentHooks: AgentHooks, agentName: string): CommandHooksConfig;
|
|
82
|
+
/**
|
|
83
|
+
* Load and parse command hooks configuration from a markdown file
|
|
84
|
+
*
|
|
85
|
+
* Reads a markdown file, extracts YAML frontmatter, parses it, and extracts
|
|
86
|
+
* either the new simplified `hooks` format or the legacy `command_hooks` format.
|
|
87
|
+
*
|
|
88
|
+
* **Caching:** This function implements in-memory caching per file path to avoid
|
|
89
|
+
* repeated file reads. The cache is checked first; if not found, the file is read
|
|
90
|
+
* from disk and cached for subsequent calls.
|
|
91
|
+
*
|
|
92
|
+
* Error handling:
|
|
93
|
+
* - If file doesn't exist: returns empty config (not an error)
|
|
94
|
+
* - If no frontmatter found: returns empty config
|
|
95
|
+
* - If YAML is malformed: logs warning, returns empty config
|
|
96
|
+
* - If hooks format is invalid: logs warning, returns empty config
|
|
97
|
+
* - Never throws errors - always returns a valid config
|
|
98
|
+
*
|
|
99
|
+
* @param filePath - Absolute path to the markdown file
|
|
100
|
+
* @returns Promise resolving to CommandHooksConfig (may be empty)
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* const config = await loadMarkdownConfig("/path/to/agent.md");
|
|
105
|
+
* // Returns { tool: [...], session: [...] } or { tool: [], session: [] }
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
export declare function loadMarkdownConfig(filePath: string): Promise<CommandHooksConfig>;
|
|
109
|
+
/**
|
|
110
|
+
* Clear the markdown config cache for a specific file
|
|
111
|
+
*
|
|
112
|
+
* Forces the next call to loadMarkdownConfig() for this file to reload from disk.
|
|
113
|
+
* Useful for testing or when config files may have changed.
|
|
114
|
+
*
|
|
115
|
+
* @param filePath - Path to clear from cache, or undefined to clear all
|
|
116
|
+
* @internal For testing purposes
|
|
117
|
+
*/
|
|
118
|
+
export declare function clearMarkdownConfigCache(filePath?: string): void;
|
|
119
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/config/markdown.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAkB,kBAAkB,EAAY,MAAM,mBAAmB,CAAC;AAWlG;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAmBrE;AAED;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAS7D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,WAAW,EAAE,MAAM,GAClB,UAAU,GAAG,IAAI,CA8CnB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,2BAA2B,CACzC,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,MAAM,GAChB,kBAAkB,CAqCpB;AA2ED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,kBAAkB,CACrC,QAAQ,EAAE,MAAM,GAChB,OAAO,CAAC,kBAAkB,CAAC,CA+H7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAQhE"}
|