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/dist/index.d.mts CHANGED
@@ -1,13 +1,17 @@
1
1
  /**
2
2
  * What a hook declares it checks. Each kind selects the agents' tools that
3
3
  * make that change, and is the `kind` of the {@link ToolCall} the check gets.
4
+ * `tool` selects the tools the definition names in `tools`, such as an MCP
5
+ * server's.
4
6
  */
5
- type ToolKind = 'shell' | 'write' | 'edit' | 'patch';
7
+ type ToolKind = 'shell' | 'write' | 'edit' | 'patch' | 'tool';
6
8
  /**
7
9
  * An agent's tool call, reduced to what a check needs. `shell` is Bash in
8
- * either agent; `write` is Claude Code's Write or NotebookEdit; `edit` is Edit
9
- * or MultiEdit, whose edits arrive joined by newlines; `patch` is Codex's
10
- * apply_patch, the whole patch text, which can add, change and delete files.
10
+ * either agent; `write` is Claude Code's Write or NotebookEdit; `edit` is Edit,
11
+ * or one edit of a MultiEdit, which the check gets edit by edit; `patch` is
12
+ * Codex's apply_patch, the whole patch text, which can add, change and delete
13
+ * files; `tool` is one of the tools named in `tools`, with its input as the
14
+ * agent sent it, an object.
11
15
  */
12
16
  type ToolCall = {
13
17
  kind: 'shell';
@@ -24,6 +28,10 @@ type ToolCall = {
24
28
  } | {
25
29
  kind: 'patch';
26
30
  patch: string;
31
+ } | {
32
+ kind: 'tool';
33
+ name: string;
34
+ input: Readonly<Record<string, unknown>>;
27
35
  };
28
36
  /** What a check declared with kinds `K` receives: only the matching shapes of {@link ToolCall}. */
29
37
  type ToolCallOf<K extends ToolKind> = Extract<ToolCall, {
@@ -45,6 +53,65 @@ interface HookContext {
45
53
  /** The agent's working directory, from the payload, else the hook's own. */
46
54
  cwd: string;
47
55
  }
56
+ /**
57
+ * The agent events a hook takes part in. Every hook has PreToolUse, which
58
+ * fails closed; Stop and UserPromptSubmit are optional and fail open, since a
59
+ * hook that cannot run must not trap the session in a loop or swallow every
60
+ * prompt.
61
+ */
62
+ type HookEvent = 'PreToolUse' | 'Stop' | 'UserPromptSubmit';
63
+ /** What a Stop check gets: the reply the agent is about to end its turn on. */
64
+ interface StopInput {
65
+ reply: string;
66
+ }
67
+ /** What a UserPromptSubmit check gets: the prompt the user sent. */
68
+ interface PromptInput {
69
+ prompt: string;
70
+ }
71
+ /**
72
+ * A check on one event other than PreToolUse. A block makes the agent take
73
+ * the reason instead: at Stop it keeps going with the reason as its next
74
+ * instruction, at UserPromptSubmit it drops the prompt and shows the reason.
75
+ * A check that throws or returns anything but a verdict lets the event pass.
76
+ */
77
+ interface EventDefinition<I> {
78
+ check: (input: I, context: HookContext) => HookVerdict | Promise<HookVerdict>;
79
+ /** What the agent shows while the check runs. Defaults to the hook's `statusMessage`. */
80
+ statusMessage?: string;
81
+ /** Seconds the agent waits. Defaults to the hook's `timeout`; past it, the event passes. */
82
+ timeout?: number;
83
+ }
84
+ interface PromptDefinition extends EventDefinition<PromptInput> {
85
+ /**
86
+ * Keeps Claude Code from printing the blocked prompt back under the reason,
87
+ * for a check that blocks a prompt because of what it holds, such as a
88
+ * secret.
89
+ */
90
+ hidePrompt?: boolean;
91
+ }
92
+ /** The events a hook adds on top of PreToolUse. */
93
+ interface EventDefinitions {
94
+ Stop?: EventDefinition<StopInput>;
95
+ UserPromptSubmit?: PromptDefinition;
96
+ }
97
+ /** Where {@link HookDefinition.onInstall} and `onUninstall` run. */
98
+ interface InstallPlace {
99
+ agent: Agent;
100
+ scope: Scope;
101
+ home: string;
102
+ cwd: string;
103
+ /** The built bin install runs from, to find files shipped beside it. */
104
+ script: string;
105
+ /** The agent config just written. */
106
+ file: string;
107
+ }
108
+ /** Lines an install step adds to what the command line prints. */
109
+ interface InstallNotes {
110
+ /** Printed to stdout after the config file. */
111
+ notes?: readonly string[];
112
+ /** Printed to stderr. */
113
+ warnings?: readonly string[];
114
+ }
48
115
  /** What a plugin author writes; {@link defineHook} checks it and fills in the defaults. */
49
116
  interface HookDefinition<K extends ToolKind> {
50
117
  /**
@@ -73,6 +140,38 @@ interface HookDefinition<K extends ToolKind> {
73
140
  * the call run, and Claude Code blocks it.
74
141
  */
75
142
  timeout?: number;
143
+ /**
144
+ * Commands an earlier version of the hook wrote in a form install no longer
145
+ * recognizes, such as a path that does not sit in a directory named after
146
+ * the hook. A command any pattern matches is taken as this hook's own:
147
+ * install replaces it and uninstall removes it. A pattern must match the
148
+ * whole command, not a part of it, and may not carry the `g`, `m` or `y`
149
+ * flag.
150
+ */
151
+ ownCommands?: readonly RegExp[];
152
+ /**
153
+ * Tools no kind covers, by exact name, such as
154
+ * `mcp__github__create_pull_request`. They reach the check as `tool` calls,
155
+ * so `kinds` must list `tool` exactly when this names any.
156
+ */
157
+ tools?: readonly string[];
158
+ /** Checks on events other than PreToolUse, installed beside it. */
159
+ events?: EventDefinitions;
160
+ /**
161
+ * Runs after install writes the config, to set up what else the hook needs,
162
+ * such as files in the agent's directory. Throwing puts the config and the
163
+ * `current` link back as they were and fails the install. Not run for a
164
+ * Claude Code plugin, which Claude Code installs itself.
165
+ */
166
+ onInstall?: (place: InstallPlace) => InstallNotes | void;
167
+ /** Runs after uninstall, even when the config held no entry; throwing puts the config back. */
168
+ onUninstall?: (place: InstallPlace) => InstallNotes | void;
169
+ }
170
+ /** An event hook with its defaults filled in. */
171
+ interface EventHook<I> {
172
+ readonly check: (input: I, context: HookContext) => HookVerdict | Promise<HookVerdict>;
173
+ readonly statusMessage: string;
174
+ readonly timeout: number;
76
175
  }
77
176
  /** A checked hook definition, as {@link defineHook} returns it. */
78
177
  interface HookPlugin {
@@ -81,6 +180,16 @@ interface HookPlugin {
81
180
  readonly kinds: readonly ToolKind[];
82
181
  readonly statusMessage: string;
83
182
  readonly timeout: number;
183
+ readonly ownCommands: readonly RegExp[];
184
+ readonly tools: readonly string[];
185
+ readonly events: Readonly<{
186
+ Stop?: EventHook<StopInput>;
187
+ UserPromptSubmit?: EventHook<PromptInput> & {
188
+ readonly hidePrompt: boolean;
189
+ };
190
+ }>;
191
+ readonly onInstall?: (place: InstallPlace) => InstallNotes | void;
192
+ readonly onUninstall?: (place: InstallPlace) => InstallNotes | void;
84
193
  /** Called only with a call whose kind is in `kinds`. */
85
194
  readonly check: (call: ToolCall, context: HookContext) => HookVerdict | Promise<HookVerdict>;
86
195
  }
@@ -130,10 +239,12 @@ interface ClaudePluginOptions {
130
239
  * to the package root: `.claude-plugin/plugin.json`, `hooks/hooks.json`, and
131
240
  * with `marketplace`, `.claude-plugin/marketplace.json`.
132
241
  *
133
- * The hook runs `node "${CLAUDE_PLUGIN_ROOT}/<entry>" hook || exit 2` with
134
- * `onFailure: "block"`, so a plugin that cannot start or times out blocks the
135
- * call. Claude Code copies the plugin and installs nothing into it, so the
136
- * entry's directory must hold the whole build. Throws when the plugin checks
242
+ * The PreToolUse hook runs `node "${CLAUDE_PLUGIN_ROOT}/<entry>" hook --event
243
+ * PreToolUse --agent claude || exit 2` with `onFailure: "block"`, so a plugin that cannot start or times out blocks
244
+ * the call; each of the plugin's other events runs the same command, naming
245
+ * its own event, without either, so it passes instead. Claude Code copies the plugin and installs
246
+ * nothing into it, and the plugin's `onInstall` does not run, so the entry's
247
+ * directory must hold the whole build. Throws when the plugin checks
137
248
  * no tool Claude Code has, or a field would make Claude Code refuse the
138
249
  * plugin.
139
250
  */
@@ -170,13 +281,19 @@ interface CliOptions {
170
281
  type CliMain = (argv: readonly string[], io: CliIO) => Promise<number>;
171
282
  /**
172
283
  * Builds the plugin's command line: `install` and `uninstall <claude|codex>
173
- * [--project]`, and `hook`, which reads the tool call from `io.stdin`.
284
+ * [--project]`, and `hook [--event <event>] [--agent <claude|codex>]`, which
285
+ * reads the payload from `io.stdin` and hands the flags to {@link runHook}.
174
286
  *
175
- * Exit codes: `hook` returns what {@link runHook} returns, 0 or 2. `install`
176
- * and `uninstall` return 0, or 1 with the reason on stderr. Anything else,
177
- * including an unknown flag or an extra argument, prints the usage and
178
- * returns 1. On a Node.js older than 22, `hook` returns 2
179
- * and `install` 1, each with a message naming the fix; `uninstall` still runs.
287
+ * Exit codes: `hook` returns what {@link runHook} returns, 0 or 2; an
288
+ * argument it does not take is a fault, 0 at Stop or UserPromptSubmit and 2
289
+ * otherwise. `install`
290
+ * and `uninstall` return 0, or 1 with the reason on stderr; the notes the
291
+ * plugin's `onInstall` or `onUninstall` returns follow the config file on
292
+ * stdout, its warnings go to stderr. Anything else, including an unknown flag
293
+ * or an extra argument, prints the usage and returns 1. On a Node.js older
294
+ * than 22, `hook` returns 2 for PreToolUse and 0 for Stop and
295
+ * UserPromptSubmit, and `install` 1, each with a message naming the fix;
296
+ * `uninstall` still runs.
180
297
  */
181
298
  export declare function createCli(plugin: HookPlugin, options?: CliOptions): CliMain;
182
299
  /**
@@ -191,9 +308,10 @@ export declare function readStdin(stream?: AsyncIterable<unknown>): Promise<stri
191
308
  * `hook`, and sets `process.exitCode`. Call it without `await`, passing the
192
309
  * bin's own `import.meta.url`, which is what install copies and runs.
193
310
  *
194
- * For `hook` the exit code is 2 until a verdict is in. Node.js exits 0 once
195
- * nothing is left to run, so a check whose promise never settles would
196
- * otherwise let the call run.
311
+ * For a PreToolUse `hook` the exit code is 2 until a verdict is in. Node.js
312
+ * exits 0 once nothing is left to run, so a check whose promise never settles
313
+ * would otherwise let the call run. A Stop or UserPromptSubmit check that
314
+ * never settles exits 0, as any other fault there does.
197
315
  */
198
316
  export declare function runCli(plugin: HookPlugin, entryUrl: string, options?: CliOptions): Promise<void>;
199
317
  /**
@@ -208,16 +326,45 @@ interface RunHookOptions {
208
326
  cwd?: string;
209
327
  /** Receives the block reason or the error, newline-terminated. */
210
328
  stderr?: (text: string) => void;
329
+ /** Receives the decision a Stop or UserPromptSubmit check blocks with, as a JSON line. */
330
+ stdout?: (text: string) => void;
331
+ /**
332
+ * The event the agent runs the hook for, as the installed command names it
333
+ * with `--event`. Given, it decides how a fault exits without reading the
334
+ * payload, so a Stop or UserPromptSubmit payload that is cut short or not
335
+ * JSON passes, and a payload naming another event is a fault. Absent, the
336
+ * event comes from the payload's `hook_event_name`.
337
+ */
338
+ event?: HookEvent;
339
+ /**
340
+ * The agent that runs the hook, as the installed command names it with
341
+ * `--agent`. Absent, a UserPromptSubmit payload carrying `turn_id`, which
342
+ * only Codex sends, reads as Codex.
343
+ */
344
+ agent?: Agent;
211
345
  }
212
346
  /**
213
- * Runs one PreToolUse call through the plugin and returns the exit code the
214
- * agent reads: 2 blocks the call, 0 lets it run.
347
+ * Runs one hook payload through the plugin and returns the exit code the
348
+ * agent reads.
349
+ *
350
+ * A PreToolUse call returns 2 to block it and 0 to let it run, and fails
351
+ * closed: a payload that is not JSON or names no tool, a field of the wrong
352
+ * shape, a check that throws or rejects, and a result that is not a verdict
353
+ * all return 2 with the error on stderr, because any other exit lets the call
354
+ * run unchecked. A tool the plugin did not ask for returns 0 without calling
355
+ * the check. A MultiEdit calls the check once per edit and blocks on the first
356
+ * edit it blocks. A payload with no `hook_event_name` is read as PreToolUse;
357
+ * one naming any event other than PreToolUse, Stop or UserPromptSubmit, or
358
+ * a `hook_event_name` that is not a string, returns 2.
359
+ *
360
+ * Stop and UserPromptSubmit always return 0. A block goes to stdout as
361
+ * `{"decision":"block","reason":…}`, and a fault to stderr, letting the event
362
+ * pass. A Stop with `stop_hook_active` set passes unchecked, and so does an
363
+ * event the plugin has no check for.
215
364
  *
216
- * It fails closed. A payload that is not JSON or names no tool, a field of the
217
- * wrong shape, a check that throws or rejects, and a result that is not a
218
- * verdict all return 2 with the error on stderr, because any other exit lets
219
- * the call run unchecked. A tool the plugin did not ask for returns 0 without
220
- * calling the check.
365
+ * With `options.event`, the event is the one named, not the payload's: a
366
+ * Stop or UserPromptSubmit returns 0 on any fault, a payload that is not JSON
367
+ * included, and a payload naming another event is a fault for that event.
221
368
  */
222
369
  export declare function runHook(plugin: HookPlugin, stdin: string, options?: RunHookOptions): Promise<number>;
223
370
  /** Where {@link uninstall} looks: everything is passed in, so nothing reaches past what the caller names. */
@@ -237,10 +384,17 @@ interface InstallOptions extends UninstallOptions {
237
384
  /** The Node.js a user-scope hook keeps running on, as `process.execPath` gives it. */
238
385
  execPath: string;
239
386
  }
240
- /** The config file written and, for an install, the command in it. */
387
+ /** The config file written and, for an install, the commands in it. */
241
388
  interface InstallResult {
242
389
  file: string;
390
+ /** The PreToolUse command. */
243
391
  command?: string;
392
+ /** The command written for each event. */
393
+ commands?: Partial<Record<HookEvent, string>>;
394
+ /** What the plugin's `onInstall` or `onUninstall` returned, for stdout. */
395
+ notes: string[];
396
+ /** And for stderr. */
397
+ warnings: string[];
244
398
  }
245
399
  /** The agent config a scope writes: `~/.claude/settings.json`, `<cwd>/.codex/hooks.json` and so on. */
246
400
  export declare function configFile(agent: Agent, scope: Scope, place: {
@@ -248,26 +402,31 @@ export declare function configFile(agent: Agent, scope: Scope, place: {
248
402
  cwd: string;
249
403
  }): string;
250
404
  /**
251
- * Adds the plugin's PreToolUse hook to one agent's config, replacing any entry
252
- * it wrote before and keeping every other one.
405
+ * Adds the plugin's hooks to one agent's config, PreToolUse and each event in
406
+ * its `events`, replacing any entry it wrote before, for any event, and
407
+ * keeping every other one.
253
408
  *
254
409
  * The user scope copies the bin's directory to `~/.<name>/<build-id>/`, checks
255
410
  * the copy runs, points `~/.<name>/current` at it and runs it from there on
256
411
  * `execPath`. The project scope runs `npx --no-install <packageName> hook`.
257
- * Either command ends in `|| exit 2`, so a hook that cannot start blocks the
258
- * call. The Claude Code entry also sets `onFailure: "block"` for a hook that
259
- * times out.
412
+ * Each command names its event and agent, `hook --event <event> --agent
413
+ * <agent>`, and the PreToolUse one ends in `|| exit 2`, so a hook that cannot start
414
+ * blocks the call, and the Claude Code entry sets `onFailure: "block"` for one
415
+ * that times out. The other events' commands do not, so they pass instead.
416
+ * Then the plugin's `onInstall` runs.
260
417
  *
261
- * Throws, and leaves the config as it was, when the config is not a settings
262
- * object it can extend, when the plugin's kinds leave tools the agent routes
263
- * to it unchecked, or when the build cannot be copied or does not run.
418
+ * Throws, and leaves the config and `current` as they were, when the config
419
+ * is not a settings object it can extend, when the plugin's kinds leave tools
420
+ * the agent routes to it unchecked, when the build cannot be copied or does
421
+ * not run, or when `onInstall` throws.
264
422
  */
265
423
  export declare function install(options: InstallOptions): InstallResult;
266
424
  /**
267
- * Removes the plugin's hook from one agent's config, whichever form an earlier
268
- * install wrote, and keeps every other entry. The stable copy stays: another
269
- * agent may still run it. Throws, and leaves the file as it was, when it is not
270
- * a settings object.
425
+ * Removes the plugin's hooks from one agent's config, for every event and
426
+ * whichever form an earlier install wrote, keeps every other entry, and runs
427
+ * the plugin's `onUninstall`. The stable copy stays: another agent may still
428
+ * run it. Throws, and leaves the file as it was, when it is not a settings
429
+ * object or `onUninstall` throws.
271
430
  */
272
431
  export declare function uninstall(options: UninstallOptions): InstallResult;
273
- export type { Agent, ClaudePluginOptions, ClaudePluginPerson, CliIO, CliMain, CliOptions, HookContext, HookDefinition, HookPlugin, HookVerdict, InstallOptions, InstallResult, RunHookOptions, Scope, ToolCall, ToolCallOf, ToolKind, UninstallOptions };
432
+ export type { Agent, ClaudePluginOptions, ClaudePluginPerson, CliIO, CliMain, CliOptions, EventDefinition, EventDefinitions, EventHook, HookContext, HookDefinition, HookEvent, HookPlugin, HookVerdict, InstallNotes, InstallOptions, InstallPlace, InstallResult, PromptDefinition, PromptInput, RunHookOptions, Scope, StopInput, ToolCall, ToolCallOf, ToolKind, UninstallOptions };