@s_s/agent-kit 1.0.0 → 1.2.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 CHANGED
@@ -6,7 +6,7 @@ MCP Server / Skill 开发基础设施库。提供 agent 检测、prompt 注入
6
6
 
7
7
  - **Agent 检测** — 通过文件系统特征或 MCP clientInfo 自动识别 agent 类型(OpenCode、Claude Code、OpenClaw、Codex)
8
8
  - **Prompt 注入** — 向 agent 配置文件(`AGENTS.md`、`CLAUDE.md`、manifests 等)注入自定义指令,支持幂等更新
9
- - **Intent-based Hook 系统**按意图声明,自动翻译为各 agent 原生格式,支持三层优先级和显式降级
9
+ - **Hook 安装**用户提供完整 hook 内容,agent-kit 负责写入正确路径、管理 settings.json 和生命周期
10
10
  - **跨平台数据目录** — 自动适配 macOS / Linux / Windows,支持 global / project 两种作用域
11
11
  - **零运行时依赖** — 纯 TypeScript,零 dependencies
12
12
  - **工厂模式** — `createKit(name)` 返回绑定了工具名的函数集,支持解构,无全局状态
@@ -20,42 +20,52 @@ npm install @s_s/agent-kit
20
20
  ## 快速开始
21
21
 
22
22
  ```typescript
23
- import { createKit, hooks, detectAgent } from '@s_s/agent-kit';
23
+ import { createKit, defineHooks, detectAgent } from '@s_s/agent-kit';
24
24
 
25
- // 1. 创建 kit 实例(支持解构)
25
+ // 1. 创建 kit 实例
26
26
  const { injectPrompt, installHooks, getDataDir } = createKit('my-mcp');
27
27
 
28
- // 2. 声明 hook 意图(全局 API,不绑定实例)
29
- hooks.inject({
30
- perTurn: 'Remember to use my-mcp tools when relevant.',
31
- sessionStart: 'Welcome! Check my-mcp for context.',
32
- compaction: 'Preserve my-mcp context during compaction.',
33
- });
34
-
35
- hooks.beforeToolCall({
36
- match: /^Bash/,
37
- handler: (ctx) => {
38
- if (ctx.args.command?.includes('rm -rf')) {
39
- return { block: true, reason: 'Dangerous command blocked' };
40
- }
28
+ // 2. 声明 hook(每个 agent 独立声明,内容由用户完全控制)
29
+ // hooks/claude-code.ts
30
+ const claudeHooks = defineHooks('claude-code', [
31
+ {
32
+ events: ['UserPromptSubmit'],
33
+ content: '#!/bin/bash\necho "<reminder>Use my-mcp tools.</reminder>"',
41
34
  },
35
+ {
36
+ events: ['PreToolUse'],
37
+ content:
38
+ '#!/bin/bash\n# 拦截危险操作\nTOOL_NAME="$HOOK_TOOL_NAME"\nif [[ "$TOOL_NAME" == "Bash" ]]; then echo "checked"; fi',
39
+ },
40
+ ]);
41
+
42
+ // hooks/opencode.ts
43
+ const opencodeHooks = defineHooks('opencode', {
44
+ events: ['experimental.chat.messages.transform'],
45
+ content: `export default {
46
+ name: 'my-mcp',
47
+ hooks: {
48
+ 'experimental.chat.messages.transform': async (messages) => {
49
+ messages.push({ role: 'user', content: '<reminder>Use my-mcp tools.</reminder>' });
50
+ return messages;
51
+ },
52
+ },
53
+ };`,
42
54
  });
43
55
 
44
56
  // 3. 检测当前 agent
45
57
  const agent = await detectAgent();
46
58
  if (!agent) throw new Error('No supported agent detected');
47
59
 
48
- // 4. 注入 prompt 到 agent 配置文件
60
+ // 4. 注入 prompt
49
61
  await injectPrompt(agent, '## My MCP\nInstructions for the agent...');
50
62
 
51
- // 5. 安装 hooks(自动翻译为 agent 原生格式)
52
- const result = await installHooks(agent);
63
+ // 5. 安装 hooks
64
+ const result = await installHooks(agent, [claudeHooks, opencodeHooks]);
53
65
  console.log(`Hooks installed: ${result.filesWritten.join(', ')}`);
54
- console.log(`Warnings: ${result.warnings.join('\n')}`);
55
66
 
56
67
  // 6. 获取数据目录路径
57
68
  const dataDir = getDataDir(); // global scope
58
- const projectDir = getDataDir({ scope: 'project', projectRoot: '/path/to/project' });
59
69
  ```
60
70
 
61
71
  ## API
@@ -83,23 +93,15 @@ createKit(name: string, options?: KitOptions): Kit
83
93
 
84
94
  返回的 Kit 对象包含以下方法:
85
95
 
86
- | 方法 | 说明 |
87
- | --------------------------------------- | ----------------------------- |
88
- | `injectPrompt(agent, prompt, options?)` | 注入 prompt 到 agent 配置文件 |
89
- | `hasPromptInjected(agent, options?)` | 检查 prompt 是否已注入 |
90
- | `installHooks(agent)` | 安装 hooks |
91
- | `uninstallHooks(agent)` | 卸载 hooks |
92
- | `hasHooksInstalled(agent)` | 检查 hooks 是否已安装 |
93
- | `getDataDir(options?)` | 获取跨平台数据目录路径 |
94
-
95
- ### Hook 系统
96
-
97
- Hook 系统分为两个阶段:**声明**和**安装**。
98
-
99
- 1. **声明**:通过全局 `hooks.*` API 声明意图(`inject` / `beforeToolCall` / `afterToolCall` / `onSession` / `onPermission`),以及 `raw` / `extend` 两种高级注册方式
100
- 2. **安装**:调用 `kit.installHooks(agent)` 时,从全局注册中心读取所有声明,翻译为目标 agent 的原生格式并写入磁盘
101
-
102
- 详细的 API 说明、示例、三层优先级机制和降级行为,请参阅 **[Hook 使用指南](docs/hook-usage.md)**。各 agent 原生 hook 能力的完整横向对比,请参阅 **[Hook 横向对比](docs/hooks-comparison.md)**。
96
+ | 方法 | 说明 |
97
+ | --------------------------------------- | --------------------------------------------------------- |
98
+ | `injectPrompt(agent, prompt, options?)` | 注入 prompt 到 agent 配置文件 |
99
+ | `hasPromptInjected(agent, options?)` | 检查 prompt 是否已注入 |
100
+ | `installHooks(agent, hooks)` | 安装 hooks(传入 defineHooks 声明) |
101
+ | `uninstallHooks(agent)` | 卸载 hooks |
102
+ | `hasHooksInstalled(agent)` | 检查 hooks 是否已安装 |
103
+ | `getDataDir(options?)` | 获取跨平台数据目录路径 |
104
+ | `resolvePaths(agent, options?)` | 解析 agent 相关路径(配置文件、hook 目录、settings 文件) |
103
105
 
104
106
  ### Kit 方法详情
105
107
 
@@ -115,15 +117,20 @@ injectPrompt(agent: AgentType, prompt: string, options?: ScopeOptions): Promise<
115
117
  - 后续调用:替换已有标记块(幂等更新)
116
118
  - 自动创建目录和文件
117
119
 
118
- #### `kit.installHooks(agent)`
120
+ #### `kit.installHooks(agent, hooks)`
119
121
 
120
- 为指定 agent 安装 hooks。从 hook 注册中心读取所有声明,翻译为原生格式,写入文件。
122
+ 为指定 agent 安装 hooks。接受一个或多个 `HookSet`(由 `defineHooks()` 返回),将内容写入 agent 的 hook 目录。
121
123
 
122
124
  ```typescript
123
- installHooks(agent: AgentType): Promise<HookInstallResult>
125
+ installHooks(agent: AgentType, hooks: HookSet | HookSet[]): Promise<HookInstallResult>
124
126
  ```
125
127
 
126
- 返回值包含完整的降级信息:
128
+ - 自动过滤:只处理与 `agent` 匹配的 `HookSet`,其余忽略
129
+ - Claude Code / Codex:写入 shell 脚本 + 自动注册到 `settings.json`
130
+ - OpenCode:写入 TypeScript 插件文件到 plugins 目录
131
+ - OpenClaw:生成 `HOOK.md`(YAML frontmatter)+ `handler.ts`
132
+
133
+ 返回值:
127
134
 
128
135
  ```typescript
129
136
  interface HookInstallResult {
@@ -132,20 +139,11 @@ interface HookInstallResult {
132
139
  filesWritten: string[];
133
140
  settingsUpdated: boolean;
134
141
  notes: string[];
135
- warnings: string[]; // 降级/冲突警告
136
- skipped: SkippedIntent[]; // 被跳过的意图
142
+ warnings: string[];
137
143
  error?: string;
138
144
  }
139
145
  ```
140
146
 
141
- 各 agent 的翻译方式:
142
-
143
- | Agent | 生成内容 |
144
- | ------------------- | ------------------------------------------------------- |
145
- | claude-code / codex | Shell 脚本(多个原生 hook),自动注册到 `settings.json` |
146
- | openclaw | `HOOK.md` + `handler.ts`(内部 hook + 插件 hook) |
147
- | opencode | 单个 TypeScript 插件文件(多个 hook 合并) |
148
-
149
147
  #### `kit.uninstallHooks(agent)`
150
148
 
151
149
  清理已安装的 hook 文件和 settings.json 条目。
@@ -169,6 +167,88 @@ getDataDir(options?: ScopeOptions): string
169
167
 
170
168
  全局路径可通过环境变量覆盖(默认变量名:`{NAME}_DATA_DIR`)。
171
169
 
170
+ #### `kit.resolvePaths(agent, options?)`
171
+
172
+ 解析指定 agent 的所有相关路径。自动使用 kit 的 `name` 作为 toolName,使用侧无需关心。
173
+
174
+ ```typescript
175
+ resolvePaths(agent: AgentType, options?: ScopeOptions): AgentPaths
176
+ ```
177
+
178
+ 返回值:
179
+
180
+ ```typescript
181
+ interface AgentPaths {
182
+ configFile: string; // agent 配置文件路径(始终返回)
183
+ hookDir?: string; // hook 目录路径(始终返回,因为 kit 已绑定 toolName)
184
+ settingsFile?: string; // settings.json 路径(仅 claude-code / codex)
185
+ }
186
+ ```
187
+
188
+ 示例:
189
+
190
+ ```typescript
191
+ const kit = createKit('mnemo');
192
+
193
+ // Global scope
194
+ const paths = kit.resolvePaths('claude-code');
195
+ // → {
196
+ // configFile: '~/.claude/CLAUDE.md',
197
+ // hookDir: '~/.claude/hooks/mnemo',
198
+ // settingsFile: '~/.claude/settings.json',
199
+ // }
200
+
201
+ // Project scope
202
+ const paths = kit.resolvePaths('opencode', { scope: 'project', projectRoot: '/my/project' });
203
+ // → {
204
+ // configFile: '/my/project/AGENTS.md',
205
+ // hookDir: '~/.config/opencode/plugins',
206
+ // }
207
+ ```
208
+
209
+ ### Hook 系统
210
+
211
+ Hook 系统分为两个阶段:**声明**和**安装**。
212
+
213
+ 1. **声明**:通过 `defineHooks(agent, definitions)` 纯函数声明 hook 内容。每个 agent 独立声明,内容由用户完全控制。支持分文件组织。
214
+ 2. **安装**:调用 `kit.installHooks(agent, hooks)` 时,将声明内容写入目标 agent 的 hook 目录。
215
+
216
+ #### `defineHooks(agent, definitions)`
217
+
218
+ 纯函数,不依赖任何实例或全局状态。做运行时校验(事件名合法性、content 非空),返回类型安全的 `HookSet` 数据对象。
219
+
220
+ ```typescript
221
+ import { defineHooks } from '@s_s/agent-kit';
222
+
223
+ // 单条声明
224
+ const hooks = defineHooks('claude-code', {
225
+ events: ['PreToolUse', 'PostToolUse'], // 多事件共享同一内容
226
+ content: '#!/bin/bash\necho "hook fired"',
227
+ });
228
+
229
+ // 多条声明
230
+ const hooks = defineHooks('claude-code', [
231
+ { events: ['PreToolUse'], content: '#!/bin/bash\necho "pre"' },
232
+ { events: ['PostToolUse'], content: '#!/bin/bash\necho "post"' },
233
+ ]);
234
+
235
+ // OpenClaw — 支持 description 参数
236
+ const hooks = defineHooks('openclaw', {
237
+ events: ['session_start', 'before_tool_call'],
238
+ content: 'export default async function(event) { return event; }',
239
+ description: '注入项目规范到每轮对话',
240
+ });
241
+ ```
242
+
243
+ **设计原则**:agent-kit 是"安装器"而非"翻译器"。用户提供各 agent 的原生 hook 内容,agent-kit 只负责:
244
+
245
+ - 知道正确的文件路径
246
+ - 写入文件
247
+ - 管理 settings.json 合并(Claude Code / Codex)
248
+ - 安装 / 卸载生命周期
249
+
250
+ 详细的使用示例和各 agent 事件名列表,请参阅 **[Hook 使用指南](docs/hook-usage.md)**。各 agent 原生 hook 能力的完整横向对比,请参阅 **[Hook 横向对比](docs/hooks-comparison.md)**。
251
+
172
252
  ### 独立函数
173
253
 
174
254
  以下函数不依赖 kit 实例,可直接使用。
@@ -208,23 +288,6 @@ detectProjectRoot(cwd?: string): Promise<string>
208
288
 
209
289
  优先使用 `git rev-parse --show-toplevel`,失败则向上遍历查找标记文件(`.git`、`package.json`、`pyproject.toml`、`Cargo.toml`、`go.mod`),都未找到则回退到 `cwd`。
210
290
 
211
- ### 能力矩阵 API
212
-
213
- 编程式查询各 agent 对各 intent 的支持程度。
214
-
215
- ```typescript
216
- import { CAPABILITY_MATRIX, checkDegradation, isIntentFullyUnsupported } from '@s_s/agent-kit';
217
-
218
- // 查询特定能力
219
- CAPABILITY_MATRIX.opencode.beforeToolCall.block.level; // 'partial'
220
-
221
- // 检查某 intent 在某 agent 上的降级情况
222
- const warnings = checkDegradation('opencode', 'beforeToolCall');
223
-
224
- // 检查是否完全不支持
225
- isIntentFullyUnsupported('openclaw', 'onPermission'); // true
226
- ```
227
-
228
291
  ## 类型
229
292
 
230
293
  ```typescript
@@ -246,10 +309,31 @@ interface Kit {
246
309
  readonly name: string;
247
310
  injectPrompt(agent: AgentType, prompt: string, options?: ScopeOptions): Promise<void>;
248
311
  hasPromptInjected(agent: AgentType, options?: ScopeOptions): Promise<boolean>;
249
- installHooks(agent: AgentType): Promise<HookInstallResult>;
312
+ installHooks(agent: AgentType, hooks: HookSet | HookSet[]): Promise<HookInstallResult>;
250
313
  uninstallHooks(agent: AgentType): Promise<{ success: boolean; removed: string[]; error?: string }>;
251
314
  hasHooksInstalled(agent: AgentType): Promise<boolean>;
252
315
  getDataDir(options?: ScopeOptions): string;
316
+ resolvePaths(agent: AgentType, options?: ScopeOptions): AgentPaths;
317
+ }
318
+
319
+ // Agent 路径
320
+ interface AgentPaths {
321
+ configFile: string;
322
+ hookDir?: string;
323
+ settingsFile?: string;
324
+ }
325
+
326
+ // Hook 定义
327
+ interface HookDefinition<A extends AgentType = AgentType> {
328
+ events: string[]; // agent 原生事件名
329
+ content: string; // hook 内容(shell 脚本、TypeScript 代码等)
330
+ description?: string; // OpenClaw 专属 — HOOK.md 描述
331
+ }
332
+
333
+ // defineHooks() 返回的数据对象
334
+ interface HookSet<A extends AgentType = AgentType> {
335
+ readonly agent: A;
336
+ readonly definitions: readonly HookDefinition<A>[];
253
337
  }
254
338
 
255
339
  interface HookInstallResult {
@@ -259,17 +343,8 @@ interface HookInstallResult {
259
343
  settingsUpdated: boolean;
260
344
  notes: string[];
261
345
  warnings: string[];
262
- skipped: SkippedIntent[];
263
346
  error?: string;
264
347
  }
265
-
266
- interface SkippedIntent {
267
- intent: string;
268
- agent: string;
269
- reason: string;
270
- }
271
-
272
- type SupportLevel = 'supported' | 'partial' | 'unsupported';
273
348
  ```
274
349
 
275
350
  ## 支持的 Agent
@@ -1,6 +1,6 @@
1
1
  import { injectPrompt, hasPromptInjected } from './prompt.js';
2
2
  import { installHooks, uninstallHooks, hasHooksInstalled } from './hooks.js';
3
- import { getDataDir } from './platform.js';
3
+ import { getDataDir, resolveAgentPaths } from './paths.js';
4
4
  /**
5
5
  * Create a kit instance bound to the given tool name.
6
6
  *
@@ -37,8 +37,8 @@ export function createKit(name, options) {
37
37
  hasPromptInjected(agent, scopeOptions) {
38
38
  return hasPromptInjected(name, agent, scopeOptions);
39
39
  },
40
- installHooks(agent) {
41
- return installHooks(name, agent);
40
+ installHooks(agent, hooks) {
41
+ return installHooks(name, agent, hooks);
42
42
  },
43
43
  uninstallHooks(agent) {
44
44
  return uninstallHooks(name, agent);
@@ -49,6 +49,9 @@ export function createKit(name, options) {
49
49
  getDataDir(scopeOptions) {
50
50
  return getDataDir(config, scopeOptions);
51
51
  },
52
+ resolvePaths(agent, scopeOptions) {
53
+ return resolveAgentPaths(agent, name, scopeOptions);
54
+ },
52
55
  };
53
56
  }
54
57
  //# sourceMappingURL=create-kit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"create-kit.js","sourceRoot":"","sources":["../src/create-kit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAE3C;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,OAAoB;IACxD,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,MAAM,GAAsB;QAC9B,IAAI;QACJ,IAAI,EAAE,OAAO,EAAE,IAAI;QACnB,WAAW,EAAE,OAAO,EAAE,WAAW;KACpC,CAAC;IAEF,OAAO;QACH,IAAI,IAAI;YACJ,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,YAAY,CAAC,KAAgB,EAAE,MAAc,EAAE,YAA2B;YACtE,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;YAC7E,CAAC;YACD,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;QAC3D,CAAC;QAED,iBAAiB,CAAC,KAAgB,EAAE,YAA2B;YAC3D,OAAO,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;QACxD,CAAC;QAED,YAAY,CAAC,KAAgB;YACzB,OAAO,YAAY,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACrC,CAAC;QAED,cAAc,CAAC,KAAgB;YAC3B,OAAO,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACvC,CAAC;QAED,iBAAiB,CAAC,KAAgB;YAC9B,OAAO,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC1C,CAAC;QAED,UAAU,CAAC,YAA2B;YAClC,OAAO,UAAU,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAC5C,CAAC;KACJ,CAAC;AACN,CAAC"}
1
+ {"version":3,"file":"create-kit.js","sourceRoot":"","sources":["../src/create-kit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE3D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,OAAoB;IACxD,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CAAC,kDAAkD,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,MAAM,GAAsB;QAC9B,IAAI;QACJ,IAAI,EAAE,OAAO,EAAE,IAAI;QACnB,WAAW,EAAE,OAAO,EAAE,WAAW;KACpC,CAAC;IAEF,OAAO;QACH,IAAI,IAAI;YACJ,OAAO,IAAI,CAAC;QAChB,CAAC;QAED,YAAY,CAAC,KAAgB,EAAE,MAAc,EAAE,YAA2B;YACtE,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBAC5B,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;YAC7E,CAAC;YACD,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;QAC3D,CAAC;QAED,iBAAiB,CAAC,KAAgB,EAAE,YAA2B;YAC3D,OAAO,iBAAiB,CAAC,IAAI,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;QACxD,CAAC;QAED,YAAY,CAAC,KAAgB,EAAE,KAA0B;YACrD,OAAO,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QAC5C,CAAC;QAED,cAAc,CAAC,KAAgB;YAC3B,OAAO,cAAc,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QACvC,CAAC;QAED,iBAAiB,CAAC,KAAgB;YAC9B,OAAO,iBAAiB,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;QAC1C,CAAC;QAED,UAAU,CAAC,YAA2B;YAClC,OAAO,UAAU,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;QAC5C,CAAC;QAED,YAAY,CAAC,KAAgB,EAAE,YAA2B;YACtD,OAAO,iBAAiB,CAAC,KAAK,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC;QACxD,CAAC;KACJ,CAAC;AACN,CAAC"}
@@ -0,0 +1,31 @@
1
+ import type { AgentType, HookDefinition, HookSet } from './types.js';
2
+ /**
3
+ * Define hooks for a specific agent. Returns a validated HookSet that can be
4
+ * passed to `kit.installHooks()`.
5
+ *
6
+ * Pure function — no side effects, no global state, no instance dependency.
7
+ *
8
+ * @param agent - Target agent type.
9
+ * @param definitions - A single HookDefinition or an array of HookDefinitions.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // Single definition
14
+ * const hooks = defineHooks('claude-code', {
15
+ * events: ['PreToolUse', 'PostToolUse'],
16
+ * content: '#!/bin/bash\necho "hook fired"',
17
+ * });
18
+ *
19
+ * // Multiple definitions
20
+ * const hooks = defineHooks('claude-code', [
21
+ * { events: ['PreToolUse'], content: '#!/bin/bash\necho "pre"' },
22
+ * { events: ['PostToolUse'], content: '#!/bin/bash\necho "post"' },
23
+ * ]);
24
+ * ```
25
+ */
26
+ export declare function defineHooks<A extends AgentType>(agent: A, definitions: HookDefinition<A> | HookDefinition<A>[]): HookSet<A>;
27
+ /**
28
+ * Get the set of valid event names for a given agent.
29
+ * Useful for tooling and documentation generation.
30
+ */
31
+ export declare function getValidEvents(agent: AgentType): ReadonlySet<string>;
@@ -0,0 +1,192 @@
1
+ import { AGENT_TYPES } from './types.js';
2
+ // ---------------------------------------------------------------------------
3
+ // Valid event names per agent (for runtime validation)
4
+ // ---------------------------------------------------------------------------
5
+ const VALID_EVENTS = {
6
+ 'claude-code': new Set([
7
+ 'SessionStart',
8
+ 'InstructionsLoaded',
9
+ 'UserPromptSubmit',
10
+ 'PreToolUse',
11
+ 'PermissionRequest',
12
+ 'PostToolUse',
13
+ 'PostToolUseFailure',
14
+ 'Notification',
15
+ 'SubagentStart',
16
+ 'SubagentStop',
17
+ 'Stop',
18
+ 'TeammateIdle',
19
+ 'TaskCompleted',
20
+ 'ConfigChange',
21
+ 'WorktreeCreate',
22
+ 'WorktreeRemove',
23
+ 'PreCompact',
24
+ 'PostCompact',
25
+ 'Elicitation',
26
+ 'ElicitationResult',
27
+ 'SessionEnd',
28
+ ]),
29
+ codex: new Set([
30
+ 'SessionStart',
31
+ 'InstructionsLoaded',
32
+ 'UserPromptSubmit',
33
+ 'PreToolUse',
34
+ 'PermissionRequest',
35
+ 'PostToolUse',
36
+ 'PostToolUseFailure',
37
+ 'Notification',
38
+ 'SubagentStart',
39
+ 'SubagentStop',
40
+ 'Stop',
41
+ 'TeammateIdle',
42
+ 'TaskCompleted',
43
+ 'ConfigChange',
44
+ 'WorktreeCreate',
45
+ 'WorktreeRemove',
46
+ 'PreCompact',
47
+ 'PostCompact',
48
+ 'Elicitation',
49
+ 'ElicitationResult',
50
+ 'SessionEnd',
51
+ ]),
52
+ opencode: new Set([
53
+ 'event',
54
+ 'config',
55
+ 'tool',
56
+ 'auth',
57
+ 'chat.message',
58
+ 'chat.params',
59
+ 'chat.headers',
60
+ 'permission.ask',
61
+ 'command.execute.before',
62
+ 'tool.execute.before',
63
+ 'shell.env',
64
+ 'tool.execute.after',
65
+ 'experimental.chat.messages.transform',
66
+ 'experimental.chat.system.transform',
67
+ 'experimental.session.compacting',
68
+ 'experimental.text.complete',
69
+ 'tool.definition',
70
+ ]),
71
+ openclaw: new Set([
72
+ // Plugin hooks
73
+ 'before_model_resolve',
74
+ 'before_prompt_build',
75
+ 'before_agent_start',
76
+ 'llm_input',
77
+ 'llm_output',
78
+ 'agent_end',
79
+ 'before_compaction',
80
+ 'after_compaction',
81
+ 'before_reset',
82
+ 'message_received',
83
+ 'message_sending',
84
+ 'message_sent',
85
+ 'before_tool_call',
86
+ 'after_tool_call',
87
+ 'tool_result_persist',
88
+ 'before_message_write',
89
+ 'session_start',
90
+ 'session_end',
91
+ 'subagent_spawning',
92
+ 'subagent_delivery_target',
93
+ 'subagent_spawned',
94
+ 'subagent_ended',
95
+ 'gateway_start',
96
+ 'gateway_stop',
97
+ // Internal hooks
98
+ 'command:new',
99
+ 'command:reset',
100
+ 'command:stop',
101
+ 'session:compact:before',
102
+ 'session:compact:after',
103
+ 'agent:bootstrap',
104
+ 'gateway:startup',
105
+ 'message:received',
106
+ 'message:sent',
107
+ 'message:transcribed',
108
+ 'message:preprocessed',
109
+ ]),
110
+ };
111
+ // ---------------------------------------------------------------------------
112
+ // defineHooks — pure validation + packaging function
113
+ // ---------------------------------------------------------------------------
114
+ /**
115
+ * Define hooks for a specific agent. Returns a validated HookSet that can be
116
+ * passed to `kit.installHooks()`.
117
+ *
118
+ * Pure function — no side effects, no global state, no instance dependency.
119
+ *
120
+ * @param agent - Target agent type.
121
+ * @param definitions - A single HookDefinition or an array of HookDefinitions.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * // Single definition
126
+ * const hooks = defineHooks('claude-code', {
127
+ * events: ['PreToolUse', 'PostToolUse'],
128
+ * content: '#!/bin/bash\necho "hook fired"',
129
+ * });
130
+ *
131
+ * // Multiple definitions
132
+ * const hooks = defineHooks('claude-code', [
133
+ * { events: ['PreToolUse'], content: '#!/bin/bash\necho "pre"' },
134
+ * { events: ['PostToolUse'], content: '#!/bin/bash\necho "post"' },
135
+ * ]);
136
+ * ```
137
+ */
138
+ export function defineHooks(agent, definitions) {
139
+ // Validate agent
140
+ if (!AGENT_TYPES.includes(agent)) {
141
+ throw new Error(`defineHooks: unknown agent type "${agent}". Valid types: ${AGENT_TYPES.join(', ')}`);
142
+ }
143
+ // Normalize to array
144
+ const defs = Array.isArray(definitions) ? definitions : [definitions];
145
+ if (defs.length === 0) {
146
+ throw new Error('defineHooks: definitions array cannot be empty.');
147
+ }
148
+ // OpenClaw: warn if multiple definitions provided (only first is used)
149
+ const warnings = [];
150
+ if (agent === 'openclaw' && defs.length > 1) {
151
+ warnings.push(`defineHooks: OpenClaw only supports a single hook definition. ` +
152
+ `Got ${defs.length} definitions — only the first will be used.`);
153
+ // Log warning (user can also see it in installHooks result)
154
+ console.warn(warnings[0]);
155
+ }
156
+ const validEvents = VALID_EVENTS[agent];
157
+ for (let i = 0; i < defs.length; i++) {
158
+ const def = defs[i];
159
+ // Validate events
160
+ if (!Array.isArray(def.events) || def.events.length === 0) {
161
+ throw new Error(`defineHooks: definitions[${i}].events must be a non-empty array.`);
162
+ }
163
+ for (const event of def.events) {
164
+ if (!validEvents.has(event)) {
165
+ throw new Error(`defineHooks: unknown event "${event}" for agent "${agent}". ` +
166
+ `Valid events: ${[...validEvents].join(', ')}`);
167
+ }
168
+ }
169
+ // Validate content
170
+ if (typeof def.content !== 'string' || !def.content.trim()) {
171
+ throw new Error(`defineHooks: definitions[${i}].content must be a non-empty string.`);
172
+ }
173
+ }
174
+ // For OpenClaw, only keep first definition
175
+ const effectiveDefs = agent === 'openclaw' && defs.length > 1 ? [defs[0]] : defs;
176
+ return {
177
+ __brand: 'HookSet',
178
+ agent,
179
+ definitions: Object.freeze([...effectiveDefs]),
180
+ };
181
+ }
182
+ /**
183
+ * Get the set of valid event names for a given agent.
184
+ * Useful for tooling and documentation generation.
185
+ */
186
+ export function getValidEvents(agent) {
187
+ if (!AGENT_TYPES.includes(agent)) {
188
+ throw new Error(`getValidEvents: unknown agent type "${agent}".`);
189
+ }
190
+ return VALID_EVENTS[agent];
191
+ }
192
+ //# sourceMappingURL=define-hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-hooks.js","sourceRoot":"","sources":["../src/define-hooks.ts"],"names":[],"mappings":"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"}
package/build/hooks.d.ts CHANGED
@@ -1,14 +1,17 @@
1
- import type { AgentType, HookInstallResult } from './types.js';
1
+ import type { AgentType, HookInstallResult, HookSet } from './types.js';
2
2
  /**
3
3
  * Install hooks for the given agent type.
4
4
  *
5
- * Reads all registered intents, raw hooks, and extend hooks from the hook
6
- * registry. Translates them into native hook files using the agent-specific
7
- * translator. Runs degradation checks and conflict detection.
5
+ * Accepts one or more HookSet (from defineHooks()). Filters to only those
6
+ * matching the target agent. Writes content to the agent's hook directory.
7
+ *
8
+ * For Claude Code / Codex: writes shell scripts, merges into settings.json.
9
+ * For OpenCode: writes TypeScript plugin files directly to plugins dir.
10
+ * For OpenClaw: writes HOOK.md + handler.ts, attempts CLI activation.
8
11
  *
9
12
  * @internal — called by the Kit object returned from createKit().
10
13
  */
11
- export declare function installHooks(name: string, agent: AgentType): Promise<HookInstallResult>;
14
+ export declare function installHooks(name: string, agent: AgentType, hookSets: HookSet | HookSet[]): Promise<HookInstallResult>;
12
15
  /**
13
16
  * Uninstall hooks for the given agent type.
14
17
  *