agent-base-kit 0.1.0 → 0.3.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
@@ -4,253 +4,201 @@
4
4
 
5
5
  # agent-base-kit
6
6
 
7
- **Turn any check into a fail-closed PreToolUse hook for Claude Code and Codex. One function in, a verdict out. Zero dependencies.**
7
+ **The base kit for Claude Code and Codex hooks.**
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/agent-base-kit?color=cb3837)](https://www.npmjs.com/package/agent-base-kit)
10
10
  [![npm downloads](https://img.shields.io/npm/dm/agent-base-kit)](https://www.npmjs.com/package/agent-base-kit)
11
- [![CI](https://github.com/devmaxxx/agent-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/devmaxxx/agent-kit/actions/workflows/ci.yml)
11
+ [![CI](https://github.com/devmaxxx/agent-base-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/devmaxxx/agent-base-kit/actions/workflows/ci.yml)
12
12
  [![types](https://img.shields.io/npm/types/agent-base-kit)](https://www.npmjs.com/package/agent-base-kit)
13
13
  [![zero dependencies](https://img.shields.io/badge/dependencies-0-brightgreen)](./package.json)
14
14
  [![node](https://img.shields.io/node/v/agent-base-kit)](./package.json)
15
15
  [![license: MIT](https://img.shields.io/npm/l/agent-base-kit)](./LICENSE)
16
16
 
17
- [Install](#install) · [Quick start](#quick-start) · [Kinds](#kinds-and-matchers) · [Fail-closed](#the-fail-closed-contract) · [Install layout](#what-install-writes) · [Claude Code plugin](#shipping-as-a-claude-code-plugin) · [API](#api)
17
+ [Why](#why) · [What you get](#what-you-get) · [Usage](#usage) · [What install writes](#what-install-writes) · [Fail-closed](#the-fail-closed-contract) · [Kinds](#kinds-and-matchers) · [Other events](#stop-and-userpromptsubmit) · [API](#api)
18
18
 
19
19
  </div>
20
20
 
21
- <details>
22
- <summary><b>Table of contents</b></summary>
23
-
24
- - [Why](#why)
25
- - [Features](#features)
26
- - [Install](#install)
27
- - [Quick start](#quick-start)
28
- - [Why a devDependency](#why-a-devdependency)
29
- - [Kinds and matchers](#kinds-and-matchers)
30
- - [The fail-closed contract](#the-fail-closed-contract)
31
- - [What install writes](#what-install-writes)
32
- - [Shipping as a Claude Code plugin](#shipping-as-a-claude-code-plugin)
33
- - [API](#api)
34
- - [Design notes](#design-notes)
35
- - [Contributing](#contributing)
36
- - [License](#license)
37
-
38
- </details>
39
-
40
21
  ## Why
41
22
 
42
- A [PreToolUse hook](https://code.claude.com/docs/en/hooks) is the last point where you can stop an agent's tool call before it runs. Writing one well is mostly not about the check. The agents treat every exit except 2 as "go on", so a hook that crashes, cannot parse its input, or loses its Node.js after an upgrade lets every call through without a word.
23
+ A [PreToolUse hook](https://code.claude.com/docs/en/hooks) is the last point where you can stop an agent's tool call. Every hook needs the same plumbing before its own check: read the agent's payload, write the agent's config without breaking anyone else's entries, keep running across Node.js upgrades, and block when the check cannot run, since the agents treat every exit except 2 as "go on".
43
24
 
44
- agent-base-kit is that plumbing, for any guard you build. You write the check; it makes sure the check actually runs, and blocks when it cannot.
25
+ agent-base-kit is that plumbing, shared by every hook you build. You write the check; reuse the rest for a secret scanner, a policy guard, or any other hook.
45
26
 
46
- ## Features
27
+ ## What you get
47
28
 
48
- - **One function.** `check(call)` gets a typed call (`shell`, `write`, `edit` or `patch`) and returns `{ block, reason }`. Sync or async.
49
- - **Fails closed.** Unreadable payloads, throws, bad verdicts, hangs and a hook that cannot start all block.
50
- - **Both agents.** Claude Code and Codex, user or project scope, with matchers derived from the kinds you list.
51
- - **Survives upgrades.** A user install runs its own copy of your build on the Node.js that installed it, so an nvm or Homebrew upgrade does not silently switch it off.
52
- - **Plays well with other hooks.** Install and uninstall touch only their own entry and refuse a config they cannot extend safely.
29
+ - **One function to write.** `check(call)` gets a typed call (`shell`, `write`, `edit`, `patch`, or a named tool such as an MCP server's) and returns `{ block, reason }`, sync or async.
30
+ - **Stop and UserPromptSubmit too.** Check the reply before the turn ends or the prompt before the model sees it, failing open where a broken hook would trap the session.
31
+ - **Both agents.** Claude Code and Codex, with matchers derived from the kinds you list.
32
+ - **Fail-closed by default.** Unreadable payloads, throws, bad verdicts, hangs and a hook that cannot start all block.
33
+ - **One command to install.** Your bin gets `install` and `uninstall` for `claude` or `codex`, at user or project scope.
34
+ - **Other hooks left alone.** Install and uninstall touch only their own entry, and refuse a config they cannot extend safely.
35
+ - **Survives upgrades.** A user install runs its own copy of your build on the Node.js that installed it, so an nvm or Homebrew upgrade or a rebuild does not switch it off.
53
36
  - **Claude Code plugin files.** One call writes the manifest, `hooks.json` and a marketplace.
54
37
  - **Zero dependencies**, ESM only, typed.
55
38
 
56
- ## Install
39
+ ## Usage
57
40
 
58
41
  ```sh
59
42
  pnpm add -D agent-base-kit
60
- # or: npm i -D agent-base-kit · yarn add -D agent-base-kit · bun add -d agent-base-kit
61
43
  ```
62
44
 
63
- Requires Node.js 22 or later and a POSIX shell. It goes in `devDependencies`; [here is why](#why-a-devdependency).
64
-
65
- ## Quick start
66
-
67
- A toy check that stops force pushes, as a plugin in three files:
45
+ Write the check and hand it to `runCli` in your bin:
68
46
 
69
47
  ```ts
70
- // src/plugin.ts
71
- import { defineHook } from 'agent-base-kit'
48
+ #!/usr/bin/env node
49
+ import { defineHook, runCli } from 'agent-base-kit'
72
50
 
73
- export const plugin = defineHook({
51
+ const plugin = defineHook({
74
52
  name: 'no-force-push',
75
53
  kinds: ['shell'],
76
- statusMessage: 'Checking for force pushes',
77
54
  check: call => /\bgit\s+push\b.*\s(?:-f|--force)(?:\s|$)/.test(call.command)
78
55
  ? { block: true, reason: 'Blocked by no-force-push: push without --force, or use --force-with-lease.' }
79
56
  : { block: false },
80
57
  })
81
- ```
82
-
83
- ```ts
84
- #!/usr/bin/env node
85
- // src/cli.ts
86
- import { runCli } from 'agent-base-kit'
87
- import { plugin } from './plugin'
88
58
 
89
59
  void runCli(plugin, import.meta.url)
90
60
  ```
91
61
 
92
- ```jsonc
93
- // package.json (the parts that matter)
94
- {
95
- "name": "no-force-push",
96
- "type": "module",
97
- "bin": { "no-force-push": "./dist/cli.mjs" },
98
- "files": ["dist"],
99
- "engines": { "node": ">=22" },
100
- "devDependencies": {
101
- "agent-base-kit": "^0.1.0",
102
- "tsdown": "^0.23.1"
103
- }
104
- }
105
- ```
106
-
107
- Build `src/cli.ts` with tsdown. That gives you this CLI:
62
+ Bundle the bin into `.mjs` files with agent-base-kit inlined, for example with tsdown, since the stable copy has no `node_modules`. Your users then run:
108
63
 
109
64
  ```sh
110
65
  npx no-force-push install claude # ~/.claude/settings.json
111
66
  npx no-force-push install codex --project # ./.codex/hooks.json
112
67
  npx no-force-push uninstall claude
113
- npx no-force-push hook # what the agent runs, with the tool call on stdin
114
68
  ```
115
69
 
116
- ### Why a devDependency
70
+ Requires Node.js 22 or later and a POSIX shell.
71
+
72
+ ## What install writes
117
73
 
118
- A user-scope install copies the `.mjs` files next to your bin into `~/.<name>/<build-id>/` and runs them from there. A runtime dependency would be imported from `node_modules`, and the copy has none. List agent-base-kit under `devDependencies` instead, and tsdown bundles it into your build. It also inlines its types into your `.d.mts`, so `export type { ToolCall } from 'agent-base-kit'` works for your users. If the copy cannot start anyway, `install` refuses and says why. It runs the copy once before writing any config.
74
+ A user install (the default scope) does four things:
119
75
 
120
- ## Kinds and matchers
76
+ 1. **Copies the build.** The `.mjs` files beside your bin go to `~/.<name>/<build-id>/`, where the id is a hash of their content. The same build reuses its directory, a new build gets a new one, and the copy is staged and renamed in, so a hook never loads a half-written one.
77
+ 2. **Runs the copy once.** Before any config points at it, the copy runs on a tool no kind covers. If it cannot start, `install` refuses and changes nothing.
78
+ 3. **Swaps `~/.<name>/current`.** One rename moves the link to the new build, so the hook command stays the same across versions and Codex keeps trusting it.
79
+ 4. **Writes the hook:**
121
80
 
122
- `kinds` decides which calls reach your check. Each agent's matcher is derived from it.
81
+ ```sh
82
+ n='/path/to/the/installing/node'; [ -x "$n" ] || n=node; "$n" '/home/you/.<name>/current/cli.mjs' hook --event PreToolUse --agent claude || exit 2
83
+ ```
123
84
 
124
- | Kind | The call your check gets | Claude Code tools | Codex tools |
125
- | --- | --- | --- | --- |
126
- | `shell` | `{ kind: 'shell', command }` | `Bash` | `Bash` |
127
- | `write` | `{ kind: 'write', path, content }` | `Write`, `NotebookEdit` | `apply_patch` |
128
- | `edit` | `{ kind: 'edit', path, before, after }` (a MultiEdit's edits joined by newlines) | `Edit`, `MultiEdit` | `apply_patch` |
129
- | `patch` | `{ kind: 'patch', patch }` (the whole patch text) | none | `apply_patch` |
85
+ `--project` writes `npx --no-install <packageName> hook --event PreToolUse --agent claude || exit 2` instead, which is safe to commit. Each of the plugin's [other events](#stop-and-userpromptsubmit) gets an entry of its own with the same command, naming its own event, minus `|| exit 2`. Uninstall removes every entry and leaves the copy, since another agent may still run it.
130
86
 
131
- `check` is typed to the kinds you list. It is never called with any other kind, even if a hand-written matcher sends one.
87
+ `--event` tells the hook which event it runs for without reading the payload, so a Stop or UserPromptSubmit payload that arrives cut short or empty still passes instead of being blocked as unreadable. `--agent` tells it which agent to answer, so a prompt block sent to Codex carries only the fields Codex accepts. A command written before the flags existed still works: the hook then takes the event from the payload's `hook_event_name`, and treats a UserPromptSubmit payload carrying `turn_id` (which Codex's input schema requires and Claude Code does not send) as Codex's. Install recognizes those older commands as its own and replaces them; Codex asks to trust the hook once more, since its command changed.
132
88
 
133
- Codex sends every file change as one `apply_patch`. A plugin whose kinds include `write` or `edit` but not `patch` would match those calls and then never see them. So `install codex` refuses that plugin until you add `patch` and handle the patch text. A plugin with no tool in an agent, such as `['patch']` for Claude Code, is refused the same way.
89
+ Install replaces any entry it wrote before, at any event, and keeps every other one; an entry an earlier version wrote for an event this one no longer has is removed. It refuses, and changes nothing, when the settings file is not JSON, not an object with a `hooks` object, or has a `PreToolUse`, `Stop` or `UserPromptSubmit` that is not a list, or when `~/.<name>/current` exists and is not a link.
90
+
91
+ `onInstall` runs once the config is written, for what else the hook needs, such as a skill copied into the agent's directory; `onUninstall` runs after uninstall. Each gets the agent, scope, home, working directory, bin and config file, and may return `{ notes, warnings }`, which the command line prints after the config file, to stdout and stderr. A step that throws puts the config and `~/.<name>/current` back as they were and fails the command, so the hooks already installed keep their build.
92
+
93
+ A hook that wrote other commands before it moved to agent-base-kit lists them in `ownCommands`, so install replaces and uninstall removes those entries too.
134
94
 
135
95
  ## The fail-closed contract
136
96
 
137
- The agents treat exit 2 as "block" and every other exit as "go on". So the hook exits 0 only when your check passes the call:
97
+ The hook exits 0 only when your check passes the call:
138
98
 
139
99
  | What happens | Exit |
140
100
  | --- | --- |
141
101
  | `check` returns `{ block: false }` | 0 |
142
102
  | `check` returns `{ block: true, reason }` (the reason goes to stderr as is) | 2 |
143
- | `check` throws or rejects | 2 |
144
- | `check` returns anything else: `undefined`, `{ block: true }` without a reason, a string | 2 |
145
- | `check` returns a promise that never settles | 2 |
146
- | The payload is not JSON, not an object, names no tool, or has a field of the wrong shape (a `command` sent as an array, `edits` as an object) | 2 |
147
- | The tool is not one of your kinds | 0, and `check` is not called |
103
+ | `check` throws, rejects, never settles, or returns anything else | 2 |
104
+ | The payload is not JSON, names no tool, has a field of the wrong shape, or names an event other than PreToolUse, Stop or UserPromptSubmit | 2 |
105
+ | The command says `--event PreToolUse` and the payload names another event | 2 |
106
+ | The command has an argument `hook` does not take | 2, or 0 when its first valid `--event` names Stop or UserPromptSubmit or, with none, the payload does |
107
+ | The tool is not one of your kinds or `tools` | 0, and `check` is not called |
148
108
  | The hook cannot start: Node.js is missing, the build was deleted, a dependency was not bundled | 2, from `\|\| exit 2` |
149
109
  | The hook runs on a Node.js older than 22 | 2, with a message naming the fix |
150
- | The hook times out | Claude Code blocks it (`onFailure: "block"`). Codex lets the call run. |
151
-
152
- `check` may be async. Keep it well inside `timeout`, which defaults to 10 seconds. Past the timeout Codex runs the call unchecked.
110
+ | The hook times out | Claude Code blocks it (`onFailure: "block"`, Claude Code 2.1.295 or later). Codex lets the call run. |
153
111
 
154
- ## What install writes
155
-
156
- The user scope is the default. Install copies the build to `~/.<name>/<build-id>/` and swaps the `~/.<name>/current` link onto it with a rename. Then it writes:
157
-
158
- ```sh
159
- n='/path/to/the/installing/node'; [ -x "$n" ] || n=node; "$n" '/home/you/.<name>/current/cli.mjs' hook || exit 2
160
- ```
161
-
162
- - The hook keeps running on the Node.js that installed it, so a project that pins an older Node does not change it.
163
- - If that Node.js is removed, the hook falls back to the `node` on PATH.
164
- - The copy outlives the package. An nvm upgrade or an `npx` cache clean does not remove it.
165
- - The command stays the same across versions installed by the same Node.js, so Codex keeps trusting it.
112
+ Keep `check` well inside `timeout`, which defaults to 10 seconds.
166
113
 
167
- `--project` writes `npx --no-install <packageName> hook || exit 2`, which is safe to commit.
114
+ ## Kinds and matchers
168
115
 
169
- Install replaces any entry it wrote before, including the older forms listed below, and keeps every other entry as it is. It refuses, and changes nothing, when:
116
+ `kinds` decides which calls reach your check, and each agent's matcher is derived from it.
170
117
 
171
- - the settings file is not JSON, or not an object with a `hooks` object and a `PreToolUse` list (a `null` there counts as absent);
172
- - `~/.<name>/current` exists and is not a link;
173
- - the bin is not a built `.mjs` file;
174
- - the copied build does not start.
118
+ | Kind | The call your check gets | Claude Code tools | Codex tools |
119
+ | --- | --- | --- | --- |
120
+ | `shell` | `{ kind: 'shell', command }` | `Bash` | `Bash` |
121
+ | `write` | `{ kind: 'write', path, content }` | `Write`, `NotebookEdit` | `apply_patch` |
122
+ | `edit` | `{ kind: 'edit', path, before, after }` (a MultiEdit's edits one call each) | `Edit`, `MultiEdit` | `apply_patch` |
123
+ | `patch` | `{ kind: 'patch', patch }` (the whole patch text) | none | `apply_patch` |
124
+ | `tool` | `{ kind: 'tool', name, input }` (the tool's input object) | the names in `tools` | the names in `tools` |
175
125
 
176
- Uninstall leaves the copy in place, since another agent may still run it.
126
+ `tools` names tools beyond the agents' own, such as `mcp__github__create_pull_request`, and `kinds` lists `tool` exactly when it does. A name holds only letters, digits, `_` and `-`, and may not be a tool a kind already covers (`Bash`, `Write`, `Edit`, `MultiEdit`, `NotebookEdit`, `apply_patch`). A listed tool's input that is not an object blocks the call; an absent one reaches the check as `{}`.
177
127
 
178
- In Claude Code the entry also sets `"onFailure": "block"`. That needs Claude Code 2.1.295 or later. Codex has no such field.
128
+ Codex sends every file change as one `apply_patch`, so `install codex` refuses a plugin with `write` or `edit` but no `patch`. A plugin with no tool in an agent, such as `['patch']` for Claude Code, is refused too.
179
129
 
180
- ## Shipping as a Claude Code plugin
130
+ ## Stop and UserPromptSubmit
181
131
 
182
- A Claude Code plugin needs no install step and no copy, because Claude Code keeps its own copy of the plugin. Generate the plugin files from your build, for example in tsdown's `onSuccess`:
132
+ `events` adds checks on two more events, installed beside PreToolUse:
183
133
 
184
134
  ```ts
185
- // tsdown.config.ts
186
- import { writeClaudePlugin } from 'agent-base-kit'
187
- import { defineConfig } from 'tsdown'
188
- import pkg from './package.json' with { type: 'json' }
189
- import { plugin } from './src/plugin'
190
-
191
- export default defineConfig({
192
- entry: ['src/cli.ts'],
193
- onSuccess() {
194
- writeClaudePlugin(import.meta.dirname, {
195
- plugin,
196
- entry: 'dist/cli.mjs',
197
- version: pkg.version,
198
- description: 'Stops force pushes',
199
- author: { name: 'you' },
200
- license: 'MIT',
201
- marketplace: { name: 'no-force-push', owner: { name: 'you' } },
202
- })
135
+ function verdict(text: string): HookVerdict {
136
+ return /\bsk-[\w-]{20,}/.test(text)
137
+ ? { block: true, reason: 'no-pasted-keys: reference the key through an environment variable instead.' }
138
+ : { block: false }
139
+ }
140
+
141
+ defineHook({
142
+ name: 'no-pasted-keys',
143
+ kinds: ['shell'],
144
+ check: call => verdict(call.command),
145
+ events: {
146
+ Stop: { check: ({ reply }) => verdict(reply) },
147
+ UserPromptSubmit: { check: ({ prompt }) => verdict(prompt), hidePrompt: true },
203
148
  },
204
149
  })
205
150
  ```
206
151
 
207
- This writes three files:
152
+ A block goes to stdout as `{"decision":"block","reason":…}`. At Stop the agent keeps going with the reason as its next instruction; at UserPromptSubmit it drops the prompt and shows the reason, and `hidePrompt` keeps Claude Code from printing the prompt back under it (Codex has no such field, and a reply carrying it would let the prompt through, so it is not sent there). Both fail open, since an exit 2 at Stop would keep the agent going forever and one at UserPromptSubmit would drop every prompt:
208
153
 
209
- - `.claude-plugin/plugin.json` holds the manifest.
210
- - `hooks/hooks.json` holds the hook: `node "${CLAUDE_PLUGIN_ROOT}/dist/cli.mjs" hook || exit 2`, with the matcher from `kinds`, your `timeout` and `statusMessage`, and `"onFailure": "block"`.
211
- - `.claude-plugin/marketplace.json` is written only with `marketplace`. It is a one-plugin marketplace whose source is your npm package.
212
-
213
- The build is not committed to git, and Claude Code copies a plugin as it is, without building it. So the marketplace fetches the plugin from npm, where `dist` is published. Add `.claude-plugin` and `hooks` to `files` in package.json, publish, and commit `.claude-plugin/marketplace.json` to the repository. Users then run:
214
-
215
- ```text
216
- /plugin marketplace add you/no-force-push
217
- /plugin install no-force-push@no-force-push
218
- ```
154
+ | What happens | Exit |
155
+ | --- | --- |
156
+ | `check` returns `{ block: true, reason }` | 0, with the decision on stdout |
157
+ | `check` returns `{ block: false }` | 0, silently |
158
+ | `check` throws, rejects, never settles, returns anything else, or the field has the wrong shape | 0, with the cause on stderr |
159
+ | With `--event`: the payload is not JSON, is empty or cut short, or names another event | 0, with the cause on stderr |
160
+ | A Stop payload with `stop_hook_active`, the reply to the hook's own block | 0, and `check` is not called |
161
+ | The hook cannot start, or runs on a Node.js older than 22 | anything but 2: the event passes |
219
162
 
220
- Use either the plugin or `install claude`, not both, or the check runs twice.
163
+ Each event takes its own `statusMessage` and `timeout`, defaulting to the hook's. Without `--event`, a payload that is not JSON is blocked with exit 2 whatever event it was meant for, which is why install writes the flag. A payload with no `hook_event_name` is read as PreToolUse; one naming any other event (`PostToolUse`, `pre_tool_use`, an empty string) is blocked with exit 2, since the tool call it may carry would otherwise run unchecked.
221
164
 
222
165
  ## API
223
166
 
224
167
  ```ts
225
168
  function defineHook<const K extends ToolKind>(definition: HookDefinition<K>): HookPlugin
226
- function runHook(plugin: HookPlugin, stdin: string, options?: { cwd?: string, stderr?: (text: string) => void }): Promise<number>
227
- function createCli(plugin: HookPlugin, options?: { bin?: string }): (argv: readonly string[], io: CliIO) => Promise<number>
228
169
  function runCli(plugin: HookPlugin, entryUrl: string, options?: { bin?: string }): Promise<void>
170
+ function createCli(plugin: HookPlugin, options?: { bin?: string }): (argv: readonly string[], io: CliIO) => Promise<number>
171
+ function runHook(plugin: HookPlugin, stdin: string, options?: { cwd?: string, stderr?: (text: string) => void, stdout?: (text: string) => void, event?: HookEvent, agent?: Agent }): Promise<number>
229
172
  function readStdin(stream?: AsyncIterable<unknown>): Promise<string>
230
- function install(options: InstallOptions): { file: string, command?: string }
231
- function uninstall(options: UninstallOptions): { file: string }
173
+ function install(options: InstallOptions): InstallResult // { file, command, commands, notes, warnings }
174
+ function uninstall(options: UninstallOptions): InstallResult
232
175
  function configFile(agent: Agent, scope: Scope, place: { home: string, cwd: string }): string
233
176
  function matcherFor(agent: Agent, kinds: readonly ToolKind[]): string
234
177
  function claudePluginFiles(options: ClaudePluginOptions): Record<string, string>
235
178
  function writeClaudePlugin(dir: string, options: ClaudePluginOptions): string[]
236
179
  ```
237
180
 
238
- `install`, `uninstall` and `createCli` take the home directory, the working directory, the bin path and the Node.js path as arguments, and never read them from the process. `runCli` is the one function that does, which keeps tests away from your real `~`.
181
+ `defineHook` takes `name`, `kinds` and `check`, and optionally:
239
182
 
240
- ## Design notes
183
+ | Field | Default | What it does |
184
+ | --- | --- | --- |
185
+ | `packageName` | `name` | The npm package a project install runs through `npx --no-install`. |
186
+ | `statusMessage` | `Running <name>` | What the agent shows while the hook runs. |
187
+ | `timeout` | `10` | Seconds the agent waits for the hook. |
188
+ | `ownCommands` | `[]` | Patterns for commands an earlier version of your hook wrote, which install replaces and uninstall removes, at any event. A pattern must match the whole command, not a part of it; the `g`, `m` and `y` flags are refused. |
189
+ | `tools` | `[]` | Tools beyond the agents' own, checked as `tool` calls. |
190
+ | `events` | `{}` | `Stop` and `UserPromptSubmit` checks, each with an optional `statusMessage` and `timeout`, and `hidePrompt` for UserPromptSubmit. |
191
+ | `onInstall`, `onUninstall` | none | Steps run after the config is written; see [What install writes](#what-install-writes). They do not run for a Claude Code plugin. |
241
192
 
242
- - **Async checks.** A sync-only API would have to break later to allow I/O. A promise costs nothing now.
243
- - **The reason is printed as is.** Your plugin owns its message, so start it with the plugin's name. Errors get a `Blocked by <name>, it could not check the call: …` prefix.
244
- - **Which entries count as its own.** The `npx` form must match exactly. The pinned form matches any file under `~/.<name>/current/`, so a bin renamed between releases still replaces its old entry. The bare `node '<path>' hook` form from before the copy existed needs a directory named exactly after the plugin. So a hook that only mentions the plugin, or another plugin whose path merely contains this one's name, is never removed.
245
- - **POSIX shells only.** Hook commands rely on `|| exit 2` and single-quoted paths, so Windows is not supported yet.
193
+ `runCli` is the only function that reads the home directory, the working directory and the Node.js path from the process; the others take them as arguments, which keeps tests away from your real `~`. `writeClaudePlugin` writes the files to ship the same hook as a Claude Code plugin instead; use either the plugin or `install claude`, not both.
246
194
 
247
- ## Contributing
195
+ The commands install and `writeClaudePlugin` write pass `--event` and `--agent`, which `createCli` hands to `runHook`. A bin that wires its own hook command passes them as `runHook`'s `event` and `agent` options, so a cut-short payload at Stop or UserPromptSubmit passes there too.
248
196
 
249
- Issues and pull requests are welcome.
197
+ ## Contributing
250
198
 
251
199
  ```sh
252
- git clone https://github.com/devmaxxx/agent-kit.git
253
- cd agent-kit
200
+ git clone https://github.com/devmaxxx/agent-base-kit.git
201
+ cd agent-base-kit
254
202
  pnpm install
255
203
  pnpm lint && pnpm typecheck && pnpm test
256
204
  ```