opencode-command-hooks 0.1.8 → 0.4.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 +71 -19
- package/dist/config/global.d.ts +20 -8
- package/dist/config/global.d.ts.map +1 -1
- package/dist/config/global.js +189 -71
- package/dist/config/global.js.map +1 -1
- package/dist/config/markdown.d.ts.map +1 -1
- package/dist/config/markdown.js +35 -10
- package/dist/config/markdown.js.map +1 -1
- package/dist/config/merge.d.ts.map +1 -1
- package/dist/config/merge.js +87 -34
- package/dist/config/merge.js.map +1 -1
- package/dist/execution/shell.js +1 -1
- package/dist/execution/shell.js.map +1 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +1 -2
- package/dist/executor.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +87 -12
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +17 -2
- package/dist/schemas.js.map +1 -1
- package/dist/types/hooks.d.ts +11 -0
- package/dist/types/hooks.d.ts.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 🪝 OpenCode Command Hooks 🪝
|
|
2
2
|
|
|
3
|
-
Use simple configs to declaratively define shell command hooks on tool/subagent invocations. With a single line of
|
|
3
|
+
Use simple configs to declaratively define shell command hooks on tool/subagent invocations. With a single line of config, you can inject a hook's output directly into context for your agent to read.
|
|
4
4
|
|
|
5
5
|
## Markdown Frontmatter Hooks
|
|
6
6
|
|
|
@@ -12,7 +12,7 @@ description: Analyzes the codebase and implements code changes.
|
|
|
12
12
|
mode: subagent
|
|
13
13
|
hooks:
|
|
14
14
|
after:
|
|
15
|
-
- run: "npm
|
|
15
|
+
- run: "npm test"
|
|
16
16
|
inject: "Test Output:\n{stdout}\n{stderr}"
|
|
17
17
|
---
|
|
18
18
|
```
|
|
@@ -75,11 +75,12 @@ hooks:
|
|
|
75
75
|
|
|
76
76
|
### Hook Configuration Options
|
|
77
77
|
|
|
78
|
-
| Option
|
|
79
|
-
|
|
|
80
|
-
| `run`
|
|
81
|
-
| `inject`
|
|
82
|
-
| `toast`
|
|
78
|
+
| Option | Type | Description |
|
|
79
|
+
| ---------------- | ---------------------- | ------------------------------------------------------------------------ |
|
|
80
|
+
| `run` | `string` \| `string[]` | Command(s) to execute |
|
|
81
|
+
| `inject` | `string` | Message injected into the session |
|
|
82
|
+
| `toast` | `object` | Toast notification configuration |
|
|
83
|
+
| `overrideGlobal` | `boolean` | When `true`, suppresses global hooks matching the same event/phase+tool. Must be a JSON boolean (`true`/`false`), not a string. |
|
|
83
84
|
|
|
84
85
|
### Toast Configuration
|
|
85
86
|
|
|
@@ -173,13 +174,21 @@ Add to your `opencode.json`:
|
|
|
173
174
|
|
|
174
175
|
## Configuration
|
|
175
176
|
|
|
176
|
-
###
|
|
177
|
+
### Config Locations
|
|
178
|
+
|
|
179
|
+
The plugin loads hooks from two locations:
|
|
177
180
|
|
|
178
|
-
|
|
181
|
+
1. **User global**: `~/.config/opencode/command-hooks.jsonc` — hooks that apply to all projects
|
|
182
|
+
2. **Project**: `.opencode/command-hooks.jsonc` — project-specific hooks (searches upward from cwd)
|
|
183
|
+
|
|
184
|
+
Both are merged by default. See [Configuration Precedence](#configuration-precedence) for details.
|
|
185
|
+
|
|
186
|
+
### JSON Config
|
|
179
187
|
|
|
180
188
|
```jsonc
|
|
181
189
|
{
|
|
182
190
|
"truncationLimit": 30000,
|
|
191
|
+
"ignoreGlobalConfig": false,
|
|
183
192
|
"tool": [
|
|
184
193
|
// Tool hooks
|
|
185
194
|
],
|
|
@@ -191,11 +200,12 @@ Create `.opencode/command-hooks.jsonc` in your project (the plugin searches upwa
|
|
|
191
200
|
|
|
192
201
|
#### JSON Config Options
|
|
193
202
|
|
|
194
|
-
| Option
|
|
195
|
-
|
|
|
196
|
-
| `truncationLimit`
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
203
|
+
| Option | Type | Description |
|
|
204
|
+
| ------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
205
|
+
| `truncationLimit` | `number` | Maximum characters to capture from command output. Defaults to 30,000 (matching OpenCode's bash tool). Must be a positive integer. Project config overrides global when both are set. |
|
|
206
|
+
| `ignoreGlobalConfig`| `boolean` | When `true`, skip loading `~/.config/opencode/command-hooks.jsonc`. Defaults to `false`. Must be a JSON boolean (`true`/`false`), not a string. |
|
|
207
|
+
| `tool` | `ToolHook[]` | Array of tool execution hooks |
|
|
208
|
+
| `session` | `SessionHook[]` | Array of session lifecycle hooks |
|
|
199
209
|
|
|
200
210
|
### Markdown Frontmatter
|
|
201
211
|
|
|
@@ -216,11 +226,53 @@ hooks:
|
|
|
216
226
|
|
|
217
227
|
### Configuration Precedence
|
|
218
228
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
229
|
+
Hooks are loaded from two locations and merged:
|
|
230
|
+
|
|
231
|
+
1. **User global config**: `~/.config/opencode/command-hooks.jsonc`
|
|
232
|
+
2. **Project config**: `.opencode/command-hooks.jsonc` (searches upward from cwd)
|
|
233
|
+
|
|
234
|
+
**Merge behavior:**
|
|
235
|
+
|
|
236
|
+
| Scenario | Result |
|
|
237
|
+
|----------|--------|
|
|
238
|
+
| Different hook IDs | Both run (concatenation) |
|
|
239
|
+
| Same hook ID | Project replaces global |
|
|
240
|
+
| `overrideGlobal: true` on hook | Suppresses all global hooks for same event/phase+tool |
|
|
241
|
+
| `ignoreGlobalConfig: true` in project | Skips global config entirely |
|
|
242
|
+
| Both set `truncationLimit` | Project value wins |
|
|
243
|
+
|
|
244
|
+
**Example: Override all global hooks for an event**
|
|
245
|
+
|
|
246
|
+
```jsonc
|
|
247
|
+
{
|
|
248
|
+
"session": [
|
|
249
|
+
{
|
|
250
|
+
"id": "my-session-idle",
|
|
251
|
+
"when": { "event": "session.idle" },
|
|
252
|
+
"run": "echo only this runs",
|
|
253
|
+
"overrideGlobal": true
|
|
254
|
+
}
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
**Example: Ignore global config entirely**
|
|
260
|
+
|
|
261
|
+
```jsonc
|
|
262
|
+
{
|
|
263
|
+
"ignoreGlobalConfig": true,
|
|
264
|
+
"tool": [
|
|
265
|
+
// Only these hooks will run
|
|
266
|
+
]
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
Additional precedence rules:
|
|
271
|
+
- Markdown hooks are converted to normal hooks with auto-generated IDs
|
|
272
|
+
- If a markdown hook and a config hook share the same `id`, the markdown hook wins
|
|
273
|
+
- Duplicate IDs within the same source are errors
|
|
274
|
+
- Tool override matching uses canonical keys, so `"bash"` and `["bash"]` are treated as equivalent
|
|
275
|
+
- Config files are schema-validated; invalid value types (for example `"false"` for a boolean field) make that source invalid
|
|
224
276
|
|
|
225
277
|
---
|
|
226
278
|
|
package/dist/config/global.d.ts
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global configuration parser for loading hooks from .opencode/command-hooks.jsonc
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Loads both user global config (~/.config/opencode/command-hooks.jsonc) and
|
|
5
|
+
* project config (.opencode/command-hooks.jsonc), merging them with project taking precedence.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - `ignoreGlobalConfig: true` in project config to skip user global entirely
|
|
9
|
+
* - `overrideGlobal: true` on individual hooks to suppress matching global hooks
|
|
6
10
|
*/
|
|
7
11
|
import type { CommandHooksConfig } from "../types/hooks.js";
|
|
8
12
|
export type GlobalConfigResult = {
|
|
9
13
|
config: CommandHooksConfig;
|
|
10
14
|
error: string | null;
|
|
11
15
|
};
|
|
16
|
+
export declare const clearGlobalConfigCacheForTests: () => void;
|
|
12
17
|
/**
|
|
13
|
-
* Load and
|
|
18
|
+
* Load and merge command hooks configuration from both sources
|
|
19
|
+
*
|
|
20
|
+
* Loads both user global config (~/.config/opencode/command-hooks.jsonc) and
|
|
21
|
+
* project config (.opencode/command-hooks.jsonc), then merges them.
|
|
14
22
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
23
|
+
* Merge behavior:
|
|
24
|
+
* - If project config has `ignoreGlobalConfig: true`, skip user global entirely
|
|
25
|
+
* - Otherwise, merge with project hooks taking precedence:
|
|
26
|
+
* - Same hook `id` → project version wins
|
|
27
|
+
* - Hook with `overrideGlobal: true` → suppresses matching global hooks
|
|
28
|
+
* - Different `id` without override → both run (concatenation)
|
|
17
29
|
*
|
|
18
30
|
* Error handling:
|
|
19
|
-
* - If no config
|
|
20
|
-
* - If
|
|
21
|
-
* - If
|
|
31
|
+
* - If no config files found: returns empty config (not an error)
|
|
32
|
+
* - If user global has parse error: warns and uses project config only
|
|
33
|
+
* - If project has parse error: returns error
|
|
22
34
|
* - Never throws errors - always returns a valid config
|
|
23
35
|
*
|
|
24
36
|
* @returns Promise resolving to GlobalConfigResult
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"global.d.ts","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAkB5D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB,CAAC;AA0KF,eAAO,MAAM,8BAA8B,QAAO,IAGjD,CAAA;AAqGD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gBAAgB,QAAa,OAAO,CAAC,kBAAkB,CAkEnE,CAAA"}
|
package/dist/config/global.js
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Global configuration parser for loading hooks from .opencode/command-hooks.jsonc
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Loads both user global config (~/.config/opencode/command-hooks.jsonc) and
|
|
5
|
+
* project config (.opencode/command-hooks.jsonc), merging them with project taking precedence.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - `ignoreGlobalConfig: true` in project config to skip user global entirely
|
|
9
|
+
* - `overrideGlobal: true` on individual hooks to suppress matching global hooks
|
|
6
10
|
*/
|
|
7
|
-
import {
|
|
11
|
+
import { ConfigSchema } from "../schemas.js";
|
|
12
|
+
import { mergeConfigs } from "./merge.js";
|
|
8
13
|
import { join, dirname } from "path";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
import { stat } from "fs/promises";
|
|
9
16
|
import { logger } from "../logging.js";
|
|
17
|
+
/**
|
|
18
|
+
* Get the user's global config directory path
|
|
19
|
+
* Uses ~/.config/opencode/ following XDG convention
|
|
20
|
+
*/
|
|
21
|
+
const getUserConfigPath = () => {
|
|
22
|
+
const envHome = process.env.HOME || process.env.USERPROFILE;
|
|
23
|
+
const baseHome = envHome && envHome.length > 0 ? envHome : homedir();
|
|
24
|
+
return join(baseHome, ".config", "opencode", "command-hooks.jsonc");
|
|
25
|
+
};
|
|
26
|
+
const CONFIG_CACHE_TTL_MS = 250;
|
|
27
|
+
const projectConfigPathCache = new Map();
|
|
28
|
+
const configBlobCache = new Map();
|
|
10
29
|
/**
|
|
11
30
|
* Strip comments from JSONC content
|
|
12
31
|
* Handles both line comments and block comments
|
|
@@ -90,27 +109,45 @@ const parseJson = (content) => {
|
|
|
90
109
|
throw new Error(`Failed to parse JSON: ${message}`);
|
|
91
110
|
}
|
|
92
111
|
};
|
|
112
|
+
const getFileMtimeMs = async (path) => {
|
|
113
|
+
try {
|
|
114
|
+
const stats = await stat(path);
|
|
115
|
+
if (!stats.isFile()) {
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
return stats.mtimeMs;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
93
124
|
/**
|
|
94
|
-
* Find
|
|
95
|
-
* Looks for .opencode/command-hooks.jsonc
|
|
125
|
+
* Find project config file by walking up directory tree
|
|
126
|
+
* Looks for .opencode/command-hooks.jsonc in project directories
|
|
96
127
|
*/
|
|
97
|
-
const
|
|
128
|
+
const findProjectConfigFile = async (startDir) => {
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
const cached = projectConfigPathCache.get(startDir);
|
|
131
|
+
if (cached && now - cached.cachedAt < CONFIG_CACHE_TTL_MS) {
|
|
132
|
+
if (cached.path === null) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
const cachedPathStillExists = (await getFileMtimeMs(cached.path)) !== null;
|
|
136
|
+
if (cachedPathStillExists) {
|
|
137
|
+
return cached.path;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
98
140
|
let currentDir = startDir;
|
|
99
141
|
// Limit search depth to avoid infinite loops
|
|
100
142
|
const maxDepth = 20;
|
|
101
143
|
let depth = 0;
|
|
102
144
|
while (depth < maxDepth) {
|
|
103
|
-
// Try .opencode/command-hooks.jsonc
|
|
104
145
|
const configPath = join(currentDir, ".opencode", "command-hooks.jsonc");
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// Continue searching
|
|
146
|
+
const mtimeMs = await getFileMtimeMs(configPath);
|
|
147
|
+
if (mtimeMs !== null) {
|
|
148
|
+
logger.debug(`Found project config file: ${configPath}`);
|
|
149
|
+
projectConfigPathCache.set(startDir, { path: configPath, cachedAt: now });
|
|
150
|
+
return configPath;
|
|
114
151
|
}
|
|
115
152
|
// Move up one directory
|
|
116
153
|
const parentDir = dirname(currentDir);
|
|
@@ -121,84 +158,165 @@ const findConfigFile = async (startDir) => {
|
|
|
121
158
|
currentDir = parentDir;
|
|
122
159
|
depth++;
|
|
123
160
|
}
|
|
124
|
-
logger.debug(`No config file found after searching ${depth} directories`);
|
|
161
|
+
logger.debug(`No project config file found after searching ${depth} directories`);
|
|
162
|
+
projectConfigPathCache.set(startDir, { path: null, cachedAt: now });
|
|
125
163
|
return null;
|
|
126
164
|
};
|
|
165
|
+
const emptyConfig = () => ({ tool: [], session: [] });
|
|
166
|
+
export const clearGlobalConfigCacheForTests = () => {
|
|
167
|
+
projectConfigPathCache.clear();
|
|
168
|
+
configBlobCache.clear();
|
|
169
|
+
};
|
|
127
170
|
/**
|
|
128
|
-
* Load and parse
|
|
171
|
+
* Load and parse config from a specific file path
|
|
129
172
|
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
173
|
+
* @param configPath - Path to the config file
|
|
174
|
+
* @param source - Source identifier for logging ("project" or "user global")
|
|
175
|
+
* @returns GlobalConfigResult with parsed config or error
|
|
176
|
+
*/
|
|
177
|
+
const loadConfigFromPath = async (configPath, source) => {
|
|
178
|
+
const now = Date.now();
|
|
179
|
+
const mtimeMs = await getFileMtimeMs(configPath);
|
|
180
|
+
const cached = configBlobCache.get(configPath);
|
|
181
|
+
if (cached) {
|
|
182
|
+
if (mtimeMs === null && cached.mtimeMs === null && now - cached.cachedAt < CONFIG_CACHE_TTL_MS) {
|
|
183
|
+
return { config: cached.config, error: cached.error };
|
|
184
|
+
}
|
|
185
|
+
if (mtimeMs !== null && cached.mtimeMs === mtimeMs) {
|
|
186
|
+
return { config: cached.config, error: cached.error };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
if (mtimeMs === null) {
|
|
190
|
+
const missingResult = { config: emptyConfig(), error: null };
|
|
191
|
+
configBlobCache.set(configPath, { ...missingResult, mtimeMs: null, cachedAt: now });
|
|
192
|
+
return missingResult;
|
|
193
|
+
}
|
|
194
|
+
// Read file
|
|
195
|
+
let content;
|
|
196
|
+
try {
|
|
197
|
+
content = await Bun.file(configPath).text();
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
201
|
+
logger.info(`Failed to read ${source} config file ${configPath}: ${message}`);
|
|
202
|
+
const readErrorResult = {
|
|
203
|
+
config: emptyConfig(),
|
|
204
|
+
error: `Failed to read ${source} config file ${configPath}: ${message}`,
|
|
205
|
+
};
|
|
206
|
+
configBlobCache.set(configPath, { ...readErrorResult, mtimeMs, cachedAt: now });
|
|
207
|
+
return readErrorResult;
|
|
208
|
+
}
|
|
209
|
+
// Parse JSONC
|
|
210
|
+
let parsed;
|
|
211
|
+
try {
|
|
212
|
+
const stripped = stripJsoncComments(content);
|
|
213
|
+
parsed = parseJson(stripped);
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
217
|
+
logger.info(`Failed to parse ${source} config file ${configPath}: ${message}`);
|
|
218
|
+
const parseErrorResult = {
|
|
219
|
+
config: emptyConfig(),
|
|
220
|
+
error: `Failed to parse ${source} config file ${configPath}: ${message}`,
|
|
221
|
+
};
|
|
222
|
+
configBlobCache.set(configPath, { ...parseErrorResult, mtimeMs, cachedAt: now });
|
|
223
|
+
return parseErrorResult;
|
|
224
|
+
}
|
|
225
|
+
// Validate and parse with the full schema
|
|
226
|
+
const parseResult = ConfigSchema.safeParse(parsed);
|
|
227
|
+
if (!parseResult.success) {
|
|
228
|
+
const issueSummary = parseResult.error.issues
|
|
229
|
+
.map(issue => {
|
|
230
|
+
const path = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
231
|
+
return `${path}: ${issue.message}`;
|
|
232
|
+
})
|
|
233
|
+
.join("; ");
|
|
234
|
+
logger.info(`${source} config file failed schema validation, using empty config: ${issueSummary}`);
|
|
235
|
+
const validationErrorResult = {
|
|
236
|
+
config: emptyConfig(),
|
|
237
|
+
error: `${source} config file failed schema validation`,
|
|
238
|
+
};
|
|
239
|
+
configBlobCache.set(configPath, { ...validationErrorResult, mtimeMs, cachedAt: now });
|
|
240
|
+
return validationErrorResult;
|
|
241
|
+
}
|
|
242
|
+
// Return with defaults for missing arrays
|
|
243
|
+
const result = {
|
|
244
|
+
truncationLimit: parseResult.data.truncationLimit,
|
|
245
|
+
ignoreGlobalConfig: parseResult.data.ignoreGlobalConfig,
|
|
246
|
+
tool: parseResult.data.tool ?? [],
|
|
247
|
+
session: parseResult.data.session ?? [],
|
|
248
|
+
};
|
|
249
|
+
logger.debug(`Loaded ${source} config: truncationLimit=${result.truncationLimit}, ${result.tool?.length ?? 0} tool hooks, ${result.session?.length ?? 0} session hooks`);
|
|
250
|
+
const successResult = { config: result, error: null };
|
|
251
|
+
configBlobCache.set(configPath, { ...successResult, mtimeMs, cachedAt: now });
|
|
252
|
+
return successResult;
|
|
253
|
+
};
|
|
254
|
+
/**
|
|
255
|
+
* Load and merge command hooks configuration from both sources
|
|
256
|
+
*
|
|
257
|
+
* Loads both user global config (~/.config/opencode/command-hooks.jsonc) and
|
|
258
|
+
* project config (.opencode/command-hooks.jsonc), then merges them.
|
|
259
|
+
*
|
|
260
|
+
* Merge behavior:
|
|
261
|
+
* - If project config has `ignoreGlobalConfig: true`, skip user global entirely
|
|
262
|
+
* - Otherwise, merge with project hooks taking precedence:
|
|
263
|
+
* - Same hook `id` → project version wins
|
|
264
|
+
* - Hook with `overrideGlobal: true` → suppresses matching global hooks
|
|
265
|
+
* - Different `id` without override → both run (concatenation)
|
|
132
266
|
*
|
|
133
267
|
* Error handling:
|
|
134
|
-
* - If no config
|
|
135
|
-
* - If
|
|
136
|
-
* - If
|
|
268
|
+
* - If no config files found: returns empty config (not an error)
|
|
269
|
+
* - If user global has parse error: warns and uses project config only
|
|
270
|
+
* - If project has parse error: returns error
|
|
137
271
|
* - Never throws errors - always returns a valid config
|
|
138
272
|
*
|
|
139
273
|
* @returns Promise resolving to GlobalConfigResult
|
|
140
274
|
*/
|
|
141
275
|
export const loadGlobalConfig = async () => {
|
|
142
|
-
let configPath = null;
|
|
143
276
|
try {
|
|
144
|
-
// Find config file
|
|
145
277
|
logger.debug(`loadGlobalConfig: starting search from: ${process.cwd()}`);
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const file = Bun.file(configPath);
|
|
155
|
-
content = await file.text();
|
|
278
|
+
// Step 1: Load project config first (to check ignoreGlobalConfig flag)
|
|
279
|
+
const projectConfigPath = await findProjectConfigFile(process.cwd());
|
|
280
|
+
const projectResult = projectConfigPath
|
|
281
|
+
? await loadConfigFromPath(projectConfigPath, "project")
|
|
282
|
+
: { config: emptyConfig(), error: null };
|
|
283
|
+
// If project config had an error, return it
|
|
284
|
+
if (projectResult.error) {
|
|
285
|
+
return projectResult;
|
|
156
286
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
logger.
|
|
160
|
-
return
|
|
161
|
-
config: { tool: [], session: [] },
|
|
162
|
-
error: `Failed to read config file ${configPath}: ${message}`,
|
|
163
|
-
};
|
|
287
|
+
// Step 2: If project says ignore global, return project only
|
|
288
|
+
if (projectResult.config.ignoreGlobalConfig) {
|
|
289
|
+
logger.debug("Project config has ignoreGlobalConfig: true, skipping user global");
|
|
290
|
+
return projectResult;
|
|
164
291
|
}
|
|
165
|
-
//
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
292
|
+
// Step 3: Load user global config
|
|
293
|
+
const userGlobalPath = getUserConfigPath();
|
|
294
|
+
const userGlobalResult = await loadConfigFromPath(userGlobalPath, "user global");
|
|
295
|
+
// Step 4: If user global had parse error, log and use project only
|
|
296
|
+
if (userGlobalResult.error) {
|
|
297
|
+
logger.info(`Failed to load user global config (${userGlobalPath}): ${userGlobalResult.error}. Using project config only.`);
|
|
298
|
+
return projectResult;
|
|
170
299
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
300
|
+
// Step 5: If neither config has hooks, return empty
|
|
301
|
+
const hasUserGlobalHooks = (userGlobalResult.config.tool?.length ?? 0) > 0 ||
|
|
302
|
+
(userGlobalResult.config.session?.length ?? 0) > 0;
|
|
303
|
+
const hasProjectHooks = (projectResult.config.tool?.length ?? 0) > 0 ||
|
|
304
|
+
(projectResult.config.session?.length ?? 0) > 0;
|
|
305
|
+
if (!hasUserGlobalHooks && !hasProjectHooks) {
|
|
306
|
+
logger.debug("No hooks found in either config, using empty config");
|
|
307
|
+
return { config: emptyConfig(), error: null };
|
|
178
308
|
}
|
|
179
|
-
//
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
config: { tool: [], session: [] },
|
|
184
|
-
error: "Config file is not a valid CommandHooksConfig (expected { tool?: [], session?: [] })",
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
// Return with defaults for missing arrays
|
|
188
|
-
const result = {
|
|
189
|
-
truncationLimit: parsed.truncationLimit,
|
|
190
|
-
tool: parsed.tool ?? [],
|
|
191
|
-
session: parsed.session ?? [],
|
|
192
|
-
};
|
|
193
|
-
logger.debug(`Loaded global config: truncationLimit=${result.truncationLimit}, ${result.tool?.length ?? 0} tool hooks, ${result.session?.length ?? 0} session hooks`);
|
|
194
|
-
return { config: result, error: null };
|
|
309
|
+
// Step 6: Merge configs - user global as base, project as override
|
|
310
|
+
const { config: mergedConfig } = mergeConfigs(userGlobalResult.config, projectResult.config);
|
|
311
|
+
logger.debug(`Merged configs: ${mergedConfig.tool?.length ?? 0} tool hooks, ${mergedConfig.session?.length ?? 0} session hooks`);
|
|
312
|
+
return { config: mergedConfig, error: null };
|
|
195
313
|
}
|
|
196
314
|
catch (error) {
|
|
197
315
|
// Catch-all for unexpected errors
|
|
198
316
|
const message = error instanceof Error ? error.message : String(error);
|
|
199
317
|
logger.info(`Unexpected error loading global config: ${message}`);
|
|
200
318
|
return {
|
|
201
|
-
config:
|
|
319
|
+
config: emptyConfig(),
|
|
202
320
|
error: `Unexpected error loading global config: ${message}`,
|
|
203
321
|
};
|
|
204
322
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"global.js","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"global.js","sourceRoot":"","sources":["../../src/config/global.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAEvC;;;GAGG;AACH,MAAM,iBAAiB,GAAG,GAAW,EAAE;IACrC,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IAC5D,MAAM,QAAQ,GAAG,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IACrE,OAAO,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,qBAAqB,CAAC,CAAC;AACtE,CAAC,CAAC;AAOF,MAAM,mBAAmB,GAAG,GAAG,CAAC;AAYhC,MAAM,sBAAsB,GAAG,IAAI,GAAG,EAAuC,CAAC;AAC9E,MAAM,eAAe,GAAG,IAAI,GAAG,EAAgC,CAAC;AAEhE;;;GAGG;AACH,MAAM,kBAAkB,GAAG,CAAC,OAAe,EAAU,EAAE;IACrD,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,IAAI,CAAC,GAAG,CAAC,CAAC;IACV,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,IAAI,WAAW,GAAsB,IAAI,CAAC;IAC1C,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,OAAO,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAE5B,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,IAAI,OAAO,CAAC;YAClB,IAAI,SAAS,EAAE,CAAC;gBACd,SAAS,GAAG,KAAK,CAAC;YACpB,CAAC;iBAAM,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;gBAC5B,SAAS,GAAG,IAAI,CAAC;YACnB,CAAC;iBAAM,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;gBACnC,QAAQ,GAAG,KAAK,CAAC;gBACjB,WAAW,GAAG,IAAI,CAAC;YACrB,CAAC;YACD,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,GAAG,EAAE,CAAC;YACxC,QAAQ,GAAG,IAAI,CAAC;YAChB,WAAW,GAAG,OAAqB,CAAC;YACpC,MAAM,IAAI,OAAO,CAAC;YAClB,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QAED,yBAAyB;QACzB,IAAI,OAAO,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACpC,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,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;YACpC,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;QAClB,CAAC,EAAE,CAAC;IACN,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC,CAAA;AAED;;GAEG;AACH,MAAM,SAAS,GAAG,CAAC,OAAe,EAAW,EAAE;IAC7C,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,CAAA;AAED,MAAM,cAAc,GAAG,KAAK,EAAE,IAAY,EAA0B,EAAE;IACpE,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;YACpB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC,OAAO,CAAC;IACvB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC,CAAA;AAGD;;;GAGG;AACH,MAAM,qBAAqB,GAAG,KAAK,EAAE,QAAgB,EAA0B,EAAE;IAC9E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,MAAM,GAAG,sBAAsB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACpD,IAAI,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,QAAQ,GAAG,mBAAmB,EAAE,CAAC;QAC1D,IAAI,MAAM,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,qBAAqB,GAAG,CAAC,MAAM,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,CAAC;QAC3E,IAAI,qBAAqB,EAAE,CAAC;YAC1B,OAAO,MAAM,CAAC,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;IAED,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,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,qBAAqB,CAAC,CAAC;QACxE,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,CAAC;QAC/C,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;YACrB,MAAM,CAAC,KAAK,CAAC,8BAA8B,UAAU,EAAE,CAAC,CAAC;YACzD,sBAAsB,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;YAC1E,OAAO,UAAU,CAAC;QACpB,CAAC;QAEJ,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;IAED,MAAM,CAAC,KAAK,CAAC,gDAAgD,KAAK,cAAc,CAAC,CAAC;IAClF,sBAAsB,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;IACpE,OAAO,IAAI,CAAC;AACd,CAAC,CAAA;AAED,MAAM,WAAW,GAAG,GAAuB,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;AAE1E,MAAM,CAAC,MAAM,8BAA8B,GAAG,GAAS,EAAE;IACvD,sBAAsB,CAAC,KAAK,EAAE,CAAC;IAC/B,eAAe,CAAC,KAAK,EAAE,CAAC;AAC1B,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,kBAAkB,GAAG,KAAK,EAC9B,UAAkB,EAClB,MAAc,EACe,EAAE;IAC/B,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,OAAO,GAAG,MAAM,cAAc,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,eAAe,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAE/C,IAAI,MAAM,EAAE,CAAC;QACX,IAAI,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,IAAI,GAAG,GAAG,MAAM,CAAC,QAAQ,GAAG,mBAAmB,EAAE,CAAC;YAC/F,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QACxD,CAAC;QAED,IAAI,OAAO,KAAK,IAAI,IAAI,MAAM,CAAC,OAAO,KAAK,OAAO,EAAE,CAAC;YACnD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;QACxD,CAAC;IACH,CAAC;IAED,IAAI,OAAO,KAAK,IAAI,EAAE,CAAC;QACrB,MAAM,aAAa,GAAuB,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACjF,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QACpF,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,YAAY;IACZ,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,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,CAAC,IAAI,CAAC,kBAAkB,MAAM,gBAAgB,UAAU,KAAK,OAAO,EAAE,CAAC,CAAC;QAC9E,MAAM,eAAe,GAAuB;YAC1C,MAAM,EAAE,WAAW,EAAE;YACrB,KAAK,EAAE,kBAAkB,MAAM,gBAAgB,UAAU,KAAK,OAAO,EAAE;SACxE,CAAC;QACF,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,eAAe,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QAChF,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,cAAc;IACd,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAC;QAC7C,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC/B,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,CAAC,IAAI,CAAC,mBAAmB,MAAM,gBAAgB,UAAU,KAAK,OAAO,EAAE,CAAC,CAAC;QAC/E,MAAM,gBAAgB,GAAuB;YAC3C,MAAM,EAAE,WAAW,EAAE;YACrB,KAAK,EAAE,mBAAmB,MAAM,gBAAgB,UAAU,KAAK,OAAO,EAAE;SACzE,CAAC;QACF,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,gBAAgB,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QACjF,OAAO,gBAAgB,CAAC;IAC1B,CAAC;IAED,0CAA0C;IAC1C,MAAM,WAAW,GAAG,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QACzB,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC,MAAM;aAC1C,GAAG,CAAC,KAAK,CAAC,EAAE;YACX,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;YACnE,OAAO,GAAG,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACrC,CAAC,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,CAAC;QACd,MAAM,CAAC,IAAI,CACT,GAAG,MAAM,8DAA8D,YAAY,EAAE,CACtF,CAAC;QACF,MAAM,qBAAqB,GAAuB;YAChD,MAAM,EAAE,WAAW,EAAE;YACrB,KAAK,EAAE,GAAG,MAAM,uCAAuC;SACxD,CAAC;QACF,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,qBAAqB,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;QACtF,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IAED,0CAA0C;IAC1C,MAAM,MAAM,GAAuB;QACjC,eAAe,EAAE,WAAW,CAAC,IAAI,CAAC,eAAe;QACjD,kBAAkB,EAAE,WAAW,CAAC,IAAI,CAAC,kBAAkB;QACvD,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE;QACjC,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,OAAO,IAAI,EAAE;KACxC,CAAC;IAEF,MAAM,CAAC,KAAK,CACV,UAAU,MAAM,4BAA4B,MAAM,CAAC,eAAe,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,gBAAgB,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,gBAAgB,CAC3J,CAAC;IAEF,MAAM,aAAa,GAAuB,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC1E,eAAe,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,CAAC;IAC9E,OAAO,aAAa,CAAC;AACvB,CAAC,CAAA;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,IAAiC,EAAE;IACtE,IAAI,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,2CAA2C,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QAEzE,uEAAuE;QACvE,MAAM,iBAAiB,GAAG,MAAM,qBAAqB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACrE,MAAM,aAAa,GAAG,iBAAiB;YACrC,CAAC,CAAC,MAAM,kBAAkB,CAAC,iBAAiB,EAAE,SAAS,CAAC;YACxD,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAE3C,4CAA4C;QAC5C,IAAI,aAAa,CAAC,KAAK,EAAE,CAAC;YACxB,OAAO,aAAa,CAAC;QACvB,CAAC;QAED,6DAA6D;QAC7D,IAAI,aAAa,CAAC,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC5C,MAAM,CAAC,KAAK,CAAC,mEAAmE,CAAC,CAAC;YAClF,OAAO,aAAa,CAAC;QACvB,CAAC;QAED,kCAAkC;QAClC,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAC;QAC3C,MAAM,gBAAgB,GAAG,MAAM,kBAAkB,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;QAEjF,mEAAmE;QACnE,IAAI,gBAAgB,CAAC,KAAK,EAAE,CAAC;YAC3B,MAAM,CAAC,IAAI,CACT,sCAAsC,cAAc,MAAM,gBAAgB,CAAC,KAAK,8BAA8B,CAC/G,CAAC;YACF,OAAO,aAAa,CAAC;QACvB,CAAC;QAED,oDAAoD;QACpD,MAAM,kBAAkB,GACtB,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC;YAC/C,CAAC,gBAAgB,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACrD,MAAM,eAAe,GACnB,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC;YAC5C,CAAC,aAAa,CAAC,MAAM,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAElD,IAAI,CAAC,kBAAkB,IAAI,CAAC,eAAe,EAAE,CAAC;YAC5C,MAAM,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;YACpE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QAChD,CAAC;QAED,mEAAmE;QACnE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,YAAY,CAC3C,gBAAgB,CAAC,MAAM,EACvB,aAAa,CAAC,MAAM,CACrB,CAAC;QAEF,MAAM,CAAC,KAAK,CACV,mBAAmB,YAAY,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,gBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,IAAI,CAAC,gBAAgB,CACnH,CAAC;QAEF,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC/C,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;QACvE,MAAM,CAAC,IAAI,CAAC,2CAA2C,OAAO,EAAE,CAAC,CAAC;QAClE,OAAO;YACL,MAAM,EAAE,WAAW,EAAE;YACrB,KAAK,EAAE,2CAA2C,OAAO,EAAE;SAC5D,CAAC;IACJ,CAAC;AACH,CAAC,CAAA"}
|
|
@@ -1 +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;AAOlG;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,
|
|
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;AAOlG;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,sBAAsB,GAAI,SAAS,MAAM,KAAG,MAAM,GAAG,IA2BjE,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,OAStD,CAAA;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,eAAe,GAC1B,aAAa,MAAM,KAClB,UAAU,GAAG,IA0Ef,CAAA;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,2BAA2B,GACtC,YAAY,UAAU,EACtB,WAAW,MAAM,KAChB,kBAqCF,CAAA;AAqDD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,kBAAkB,GAC3B,UAAU,MAAM,KACjB,OAAO,CAAC,kBAAkB,CAmG5B,CAAA"}
|
package/dist/config/markdown.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Supports both agent markdown files (typically in .opencode/agents/) and slash-command
|
|
6
6
|
* markdown files (typically in .opencode/commands/).
|
|
7
7
|
*/
|
|
8
|
-
import { isValidCommandHooksConfig } from "../schemas.js";
|
|
8
|
+
import { isValidCommandHooksConfig, SimplifiedHookEntrySchema } from "../schemas.js";
|
|
9
9
|
import { load as parseYaml } from "js-yaml";
|
|
10
10
|
import { logger } from "../logging.js";
|
|
11
11
|
/**
|
|
@@ -30,19 +30,26 @@ import { logger } from "../logging.js";
|
|
|
30
30
|
* Returns the YAML between the delimiters
|
|
31
31
|
*/
|
|
32
32
|
export const extractYamlFrontmatter = (content) => {
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
const normalized = content.replace(/\r\n/g, "\n");
|
|
34
|
+
const lines = normalized.split("\n");
|
|
35
|
+
const firstLine = (lines[0] ?? "").replace(/^\uFEFF/, "");
|
|
36
|
+
// Opening delimiter must be the first line
|
|
37
|
+
if (firstLine !== "---") {
|
|
35
38
|
return null;
|
|
36
39
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
let closingDelimiterLine = -1;
|
|
41
|
+
for (let i = 1; i < lines.length; i += 1) {
|
|
42
|
+
if (/^\s*---\s*$/.test(lines[i])) {
|
|
43
|
+
closingDelimiterLine = i;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (closingDelimiterLine === -1) {
|
|
41
48
|
// No closing delimiter found
|
|
42
49
|
return null;
|
|
43
50
|
}
|
|
44
51
|
// Extract YAML between the delimiters
|
|
45
|
-
const yamlContent =
|
|
52
|
+
const yamlContent = lines.slice(1, closingDelimiterLine).join("\n").trim();
|
|
46
53
|
return yamlContent;
|
|
47
54
|
};
|
|
48
55
|
/**
|
|
@@ -101,13 +108,31 @@ export const parseAgentHooks = (yamlContent) => {
|
|
|
101
108
|
}
|
|
102
109
|
const agentHooks = hooks;
|
|
103
110
|
const result = {};
|
|
111
|
+
const parseHookEntries = (phase, entries) => {
|
|
112
|
+
const validEntries = [];
|
|
113
|
+
entries.forEach((entry, index) => {
|
|
114
|
+
const parsedEntry = SimplifiedHookEntrySchema.safeParse(entry);
|
|
115
|
+
if (!parsedEntry.success) {
|
|
116
|
+
const issues = parsedEntry.error.issues
|
|
117
|
+
.map((issue) => {
|
|
118
|
+
const issuePath = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
119
|
+
return `${issuePath}: ${issue.message}`;
|
|
120
|
+
})
|
|
121
|
+
.join("; ");
|
|
122
|
+
logger.info(`Skipping invalid hooks.${phase}[${index}] entry: ${issues}`);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
validEntries.push(parsedEntry.data);
|
|
126
|
+
});
|
|
127
|
+
return validEntries;
|
|
128
|
+
};
|
|
104
129
|
// Parse before array
|
|
105
130
|
if (agentHooks.before !== undefined) {
|
|
106
131
|
if (!Array.isArray(agentHooks.before)) {
|
|
107
132
|
logger.debug("hooks.before is not an array");
|
|
108
133
|
return null;
|
|
109
134
|
}
|
|
110
|
-
result.before = agentHooks.before;
|
|
135
|
+
result.before = parseHookEntries("before", agentHooks.before);
|
|
111
136
|
}
|
|
112
137
|
// Parse after array
|
|
113
138
|
if (agentHooks.after !== undefined) {
|
|
@@ -115,7 +140,7 @@ export const parseAgentHooks = (yamlContent) => {
|
|
|
115
140
|
logger.debug("hooks.after is not an array");
|
|
116
141
|
return null;
|
|
117
142
|
}
|
|
118
|
-
result.after = agentHooks.after;
|
|
143
|
+
result.after = parseHookEntries("after", agentHooks.after);
|
|
119
144
|
}
|
|
120
145
|
// Return null if both are empty/undefined
|
|
121
146
|
if (!result.before?.length && !result.after?.length) {
|