@secemp/elwood 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +132 -0
- package/examples/01-basic-query.js +38 -0
- package/examples/02-bug-fixer.js +57 -0
- package/examples/03-custom-system-prompt.js +41 -0
- package/examples/04-read-only-agent.js +38 -0
- package/examples/05-find-todos.js +37 -0
- package/examples/06-session-resume.js +70 -0
- package/examples/07-hooks-pretooluse.js +74 -0
- package/examples/08-hooks-posttooluse-audit.js +66 -0
- package/examples/09-hooks-block-etc.js +68 -0
- package/examples/10-hooks-redirect-sandbox.js +70 -0
- package/examples/11-subagents.js +54 -0
- package/examples/12-mcp-stdio.js +48 -0
- package/examples/13-mcp-http.js +54 -0
- package/examples/14-custom-tool.js +84 -0
- package/examples/15-custom-tool-unit-converter.js +132 -0
- package/examples/16-mcp-github.js +71 -0
- package/examples/17-session-store-postgres.js +78 -0
- package/examples/18-session-store-redis.js +65 -0
- package/examples/19-session-store-s3.js +67 -0
- package/examples/20-session-list.js +72 -0
- package/examples/21-hooks-notification-slack.js +78 -0
- package/examples/22-hooks-webhook-posttooluse.js +78 -0
- package/examples/23-hooks-subagent-tracker.js +59 -0
- package/examples/24-v2-session-api.js +62 -0
- package/examples/README.md +95 -0
- package/examples/basic.js +240 -0
- package/examples/smoke-test.js +296 -0
- package/package.json +52 -0
- package/src/ast-tools.js +182 -0
- package/src/index.js +70 -0
- package/src/instrumenter.js +2921 -0
- package/src/loader.js +306 -0
- package/src/locate.js +296 -0
- package/src/query.js +2168 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 secemp9
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# elwood
|
|
2
|
+
|
|
3
|
+
Programmatically import and instrument the locally installed Claude Code CLI via Babel AST. Drop-in replacement for `@anthropic-ai/claude-agent-sdk` that runs **in-process** -- zero subprocess spawning.
|
|
4
|
+
|
|
5
|
+
## How it works
|
|
6
|
+
|
|
7
|
+
Instead of spawning `claude -p` as a subprocess (like the official SDK), elwood:
|
|
8
|
+
|
|
9
|
+
1. Locates the installed Claude Code CLI (`cli.js`)
|
|
10
|
+
2. Parses the 13MB bundle with Babel AST
|
|
11
|
+
3. Fingerprints internal functions via evidence-based pattern matching
|
|
12
|
+
4. Injects instrumentation hooks
|
|
13
|
+
5. Calls `agentLoop()` directly in-process
|
|
14
|
+
|
|
15
|
+
This gives you the same API as `@anthropic-ai/claude-agent-sdk` with lower latency and direct access to internals.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install elwood
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Requires Claude Code to be installed:
|
|
24
|
+
```bash
|
|
25
|
+
npm install -g @anthropic-ai/claude-code
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
import { query } from 'elwood';
|
|
32
|
+
|
|
33
|
+
for await (const message of query({
|
|
34
|
+
prompt: 'What files are in this directory?',
|
|
35
|
+
options: { maxTurns: 3 },
|
|
36
|
+
})) {
|
|
37
|
+
if (message.type === 'result') {
|
|
38
|
+
console.log(message.result);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
No API key needed if logged in via `claude login` (Pro/Max subscription).
|
|
44
|
+
|
|
45
|
+
## API
|
|
46
|
+
|
|
47
|
+
### query({ prompt, options })
|
|
48
|
+
|
|
49
|
+
Returns an async generator yielding messages. Options include:
|
|
50
|
+
|
|
51
|
+
- `model` -- model to use
|
|
52
|
+
- `systemPrompt` -- string or `{type: 'preset', append: '...'}`
|
|
53
|
+
- `maxTurns` -- maximum conversation turns
|
|
54
|
+
- `allowedTools` -- auto-approve these tools
|
|
55
|
+
- `hooks` -- `{PreToolUse: [...], PostToolUse: [...], ...}`
|
|
56
|
+
- `mcpServers` -- MCP server configs
|
|
57
|
+
- `permissionMode` -- `'default' | 'acceptEdits' | 'bypassPermissions'`
|
|
58
|
+
- `continue` / `resume` / `resumeSessionAt` / `forkSession` -- session management
|
|
59
|
+
- `env` / `stderr` -- environment and output control
|
|
60
|
+
- ...and all other official SDK options
|
|
61
|
+
|
|
62
|
+
### Query control methods
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
const conversation = query({ prompt: '...' });
|
|
66
|
+
conversation.interrupt(); // soft interrupt
|
|
67
|
+
conversation.setModel('...'); // change model mid-conversation
|
|
68
|
+
conversation.setPermissionMode('acceptEdits');
|
|
69
|
+
await conversation.supportedModels();
|
|
70
|
+
await conversation.supportedCommands();
|
|
71
|
+
conversation.close(); // cleanup
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### tool(name, description, schema, handler)
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
import { tool, createSdkMcpServer, query } from 'elwood';
|
|
78
|
+
|
|
79
|
+
const weather = tool('get_weather', 'Get weather',
|
|
80
|
+
{ type: 'object', properties: { city: { type: 'string' } } },
|
|
81
|
+
({ city }) => `72F in ${city}`
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const server = await createSdkMcpServer({ tools: [weather] });
|
|
85
|
+
|
|
86
|
+
for await (const msg of query({
|
|
87
|
+
prompt: 'Weather in NYC?',
|
|
88
|
+
options: { mcpServers: [server] },
|
|
89
|
+
})) { /* ... */ }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Session utilities
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
import { listSessions, getSessionMessages, getSessionInfo } from 'elwood';
|
|
96
|
+
|
|
97
|
+
const sessions = await listSessions();
|
|
98
|
+
const messages = await getSessionMessages(sessions[0].id);
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### V2 Session API
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import { unstable_v2_createSession, unstable_v2_prompt } from 'elwood';
|
|
105
|
+
|
|
106
|
+
const result = await unstable_v2_prompt('Explain recursion');
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Low-level AST access
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
import { locate, load, instrument, parseBundle } from 'elwood';
|
|
113
|
+
|
|
114
|
+
const { cliPath, version } = await locate();
|
|
115
|
+
const ast = parseBundle(cliPath);
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Auth
|
|
119
|
+
|
|
120
|
+
Works with all Claude Code auth methods:
|
|
121
|
+
- Claude Pro/Max subscription (`claude login`)
|
|
122
|
+
- `ANTHROPIC_API_KEY` environment variable
|
|
123
|
+
- OAuth tokens
|
|
124
|
+
- Bedrock/Vertex/Foundry
|
|
125
|
+
|
|
126
|
+
## Examples
|
|
127
|
+
|
|
128
|
+
See the [examples/](./examples/) directory for 24 working examples covering queries, hooks, MCP, custom tools, sessions, and more.
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 01-basic-query.js
|
|
3
|
+
*
|
|
4
|
+
* Equivalent of the official @anthropic-ai/claude-agent-sdk "basic query" example.
|
|
5
|
+
*
|
|
6
|
+
* Official version:
|
|
7
|
+
* import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
+
* for await (const message of query({
|
|
9
|
+
* prompt: "What files are in this directory?",
|
|
10
|
+
* options: { allowedTools: ["Bash", "Glob"] }
|
|
11
|
+
* })) {
|
|
12
|
+
* if ("result" in message) console.log(message.result);
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* This elwood version uses the in-process query() function which runs
|
|
16
|
+
* cli.js via Babel AST instrumentation instead of spawning a subprocess.
|
|
17
|
+
*
|
|
18
|
+
* Prerequisites:
|
|
19
|
+
* - ANTHROPIC_API_KEY set in environment
|
|
20
|
+
* - Claude Code CLI installed (`npm install -g @anthropic-ai/claude-code`)
|
|
21
|
+
*
|
|
22
|
+
* Run:
|
|
23
|
+
* node examples/01-basic-query.js
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { query } from '../src/index.js';
|
|
27
|
+
|
|
28
|
+
for await (const message of query({
|
|
29
|
+
prompt: 'What files are in this directory?',
|
|
30
|
+
options: {
|
|
31
|
+
allowedTools: ['Bash', 'Glob'],
|
|
32
|
+
maxTurns: 3,
|
|
33
|
+
},
|
|
34
|
+
})) {
|
|
35
|
+
if ('result' in message) {
|
|
36
|
+
console.log(message.result);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 02-bug-fixer.js
|
|
3
|
+
*
|
|
4
|
+
* Equivalent of the official quickstart "bug fixer" agent example.
|
|
5
|
+
*
|
|
6
|
+
* Official version:
|
|
7
|
+
* import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
+
* for await (const message of query({
|
|
9
|
+
* prompt: "Review utils.py for bugs that would cause crashes. Fix any issues you find.",
|
|
10
|
+
* options: {
|
|
11
|
+
* allowedTools: ["Read", "Edit", "Glob"],
|
|
12
|
+
* permissionMode: "acceptEdits"
|
|
13
|
+
* }
|
|
14
|
+
* })) {
|
|
15
|
+
* if (message.type === "assistant" && message.message?.content) {
|
|
16
|
+
* for (const block of message.message.content) {
|
|
17
|
+
* if ("text" in block) console.log(block.text);
|
|
18
|
+
* else if ("name" in block) console.log(`Tool: ${block.name}`);
|
|
19
|
+
* }
|
|
20
|
+
* } else if (message.type === "result") {
|
|
21
|
+
* console.log(`Done: ${message.subtype}`);
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* This elwood version runs the same agent loop in-process via Babel AST.
|
|
26
|
+
*
|
|
27
|
+
* Prerequisites:
|
|
28
|
+
* - ANTHROPIC_API_KEY set in environment
|
|
29
|
+
* - Claude Code CLI installed
|
|
30
|
+
*
|
|
31
|
+
* Run:
|
|
32
|
+
* node examples/02-bug-fixer.js
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import { query } from '../src/index.js';
|
|
36
|
+
|
|
37
|
+
// Agentic loop: streams messages as Claude works
|
|
38
|
+
for await (const message of query({
|
|
39
|
+
prompt: 'Review utils.py for bugs that would cause crashes. Fix any issues you find.',
|
|
40
|
+
options: {
|
|
41
|
+
allowedTools: ['Read', 'Edit', 'Glob'], // Tools Claude can use
|
|
42
|
+
permissionMode: 'acceptEdits', // Auto-approve file edits
|
|
43
|
+
},
|
|
44
|
+
})) {
|
|
45
|
+
// Print human-readable output
|
|
46
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
47
|
+
for (const block of message.message.content) {
|
|
48
|
+
if ('text' in block) {
|
|
49
|
+
console.log(block.text); // Claude's reasoning
|
|
50
|
+
} else if ('name' in block) {
|
|
51
|
+
console.log(`Tool: ${block.name}`); // Tool being called
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} else if (message.type === 'result') {
|
|
55
|
+
console.log(`Done: ${message.subtype}`); // Final result
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 03-custom-system-prompt.js
|
|
3
|
+
*
|
|
4
|
+
* Equivalent of the official "custom system prompt" example from the quickstart.
|
|
5
|
+
*
|
|
6
|
+
* Official version:
|
|
7
|
+
* options: {
|
|
8
|
+
* allowedTools: ["Read", "Edit", "Glob"],
|
|
9
|
+
* permissionMode: "acceptEdits",
|
|
10
|
+
* systemPrompt: "You are a senior Python developer. Always follow PEP 8 style guidelines."
|
|
11
|
+
* }
|
|
12
|
+
*
|
|
13
|
+
* This elwood version uses the in-process query() with a custom system prompt.
|
|
14
|
+
*
|
|
15
|
+
* Prerequisites:
|
|
16
|
+
* - ANTHROPIC_API_KEY set in environment
|
|
17
|
+
* - Claude Code CLI installed
|
|
18
|
+
*
|
|
19
|
+
* Run:
|
|
20
|
+
* node examples/03-custom-system-prompt.js
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { query } from '../src/index.js';
|
|
24
|
+
|
|
25
|
+
for await (const message of query({
|
|
26
|
+
prompt: 'Add type hints to all functions in utils.py',
|
|
27
|
+
options: {
|
|
28
|
+
allowedTools: ['Read', 'Edit', 'Glob'],
|
|
29
|
+
permissionMode: 'acceptEdits',
|
|
30
|
+
systemPrompt: 'You are a senior Python developer. Always follow PEP 8 style guidelines.',
|
|
31
|
+
maxTurns: 5,
|
|
32
|
+
},
|
|
33
|
+
})) {
|
|
34
|
+
if (message.type === 'assistant' && message.message?.content) {
|
|
35
|
+
for (const block of message.message.content) {
|
|
36
|
+
if ('text' in block) console.log(block.text);
|
|
37
|
+
}
|
|
38
|
+
} else if (message.type === 'result') {
|
|
39
|
+
console.log(`Done: ${message.subtype}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 04-read-only-agent.js
|
|
3
|
+
*
|
|
4
|
+
* Equivalent of the official "permissions / read-only agent" example.
|
|
5
|
+
*
|
|
6
|
+
* Official version:
|
|
7
|
+
* import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
+
* for await (const message of query({
|
|
9
|
+
* prompt: "Review this code for best practices",
|
|
10
|
+
* options: { allowedTools: ["Read", "Glob", "Grep"] }
|
|
11
|
+
* })) {
|
|
12
|
+
* if ("result" in message) console.log(message.result);
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* This example creates a read-only agent that can analyze but never modify code.
|
|
16
|
+
* Only Read, Glob, and Grep are allowed -- no Write, Edit, or Bash.
|
|
17
|
+
*
|
|
18
|
+
* Prerequisites:
|
|
19
|
+
* - ANTHROPIC_API_KEY set in environment
|
|
20
|
+
* - Claude Code CLI installed
|
|
21
|
+
*
|
|
22
|
+
* Run:
|
|
23
|
+
* node examples/04-read-only-agent.js
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { query } from '../src/index.js';
|
|
27
|
+
|
|
28
|
+
for await (const message of query({
|
|
29
|
+
prompt: 'Review this code for best practices',
|
|
30
|
+
options: {
|
|
31
|
+
allowedTools: ['Read', 'Glob', 'Grep'],
|
|
32
|
+
maxTurns: 5,
|
|
33
|
+
},
|
|
34
|
+
})) {
|
|
35
|
+
if ('result' in message) {
|
|
36
|
+
console.log(message.result);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 05-find-todos.js
|
|
3
|
+
*
|
|
4
|
+
* Equivalent of the official "find TODO comments" example.
|
|
5
|
+
*
|
|
6
|
+
* Official version:
|
|
7
|
+
* import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
+
* for await (const message of query({
|
|
9
|
+
* prompt: "Find all TODO comments and create a summary",
|
|
10
|
+
* options: { allowedTools: ["Read", "Glob", "Grep"] }
|
|
11
|
+
* })) {
|
|
12
|
+
* if ("result" in message) console.log(message.result);
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* This elwood version searches for TODO comments using the in-process agent.
|
|
16
|
+
*
|
|
17
|
+
* Prerequisites:
|
|
18
|
+
* - ANTHROPIC_API_KEY set in environment
|
|
19
|
+
* - Claude Code CLI installed
|
|
20
|
+
*
|
|
21
|
+
* Run:
|
|
22
|
+
* node examples/05-find-todos.js
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { query } from '../src/index.js';
|
|
26
|
+
|
|
27
|
+
for await (const message of query({
|
|
28
|
+
prompt: 'Find all TODO comments and create a summary',
|
|
29
|
+
options: {
|
|
30
|
+
allowedTools: ['Read', 'Glob', 'Grep'],
|
|
31
|
+
maxTurns: 10,
|
|
32
|
+
},
|
|
33
|
+
})) {
|
|
34
|
+
if ('result' in message) {
|
|
35
|
+
console.log(message.result);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 06-session-resume.js
|
|
3
|
+
*
|
|
4
|
+
* Equivalent of the official "sessions" example showing session capture and resume.
|
|
5
|
+
*
|
|
6
|
+
* Official version:
|
|
7
|
+
* import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
+
*
|
|
9
|
+
* let sessionId;
|
|
10
|
+
* // First query: capture the session ID
|
|
11
|
+
* for await (const message of query({
|
|
12
|
+
* prompt: "Read the authentication module",
|
|
13
|
+
* options: { allowedTools: ["Read", "Glob"] }
|
|
14
|
+
* })) {
|
|
15
|
+
* if (message.type === "system" && message.subtype === "init") {
|
|
16
|
+
* sessionId = message.session_id;
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
*
|
|
20
|
+
* // Resume with full context from the first query
|
|
21
|
+
* for await (const message of query({
|
|
22
|
+
* prompt: "Now find all places that call it",
|
|
23
|
+
* options: { resume: sessionId }
|
|
24
|
+
* })) {
|
|
25
|
+
* if ("result" in message) console.log(message.result);
|
|
26
|
+
* }
|
|
27
|
+
*
|
|
28
|
+
* This elwood version captures the session ID from the init message and
|
|
29
|
+
* resumes the session in a second query, maintaining full conversation context.
|
|
30
|
+
*
|
|
31
|
+
* Prerequisites:
|
|
32
|
+
* - ANTHROPIC_API_KEY set in environment
|
|
33
|
+
* - Claude Code CLI installed
|
|
34
|
+
*
|
|
35
|
+
* Run:
|
|
36
|
+
* node examples/06-session-resume.js
|
|
37
|
+
*/
|
|
38
|
+
|
|
39
|
+
import { query } from '../src/index.js';
|
|
40
|
+
|
|
41
|
+
let sessionId;
|
|
42
|
+
|
|
43
|
+
// First query: capture the session ID
|
|
44
|
+
console.log('--- First query: reading the authentication module ---');
|
|
45
|
+
for await (const message of query({
|
|
46
|
+
prompt: 'Read the authentication module',
|
|
47
|
+
options: {
|
|
48
|
+
allowedTools: ['Read', 'Glob'],
|
|
49
|
+
maxTurns: 3,
|
|
50
|
+
},
|
|
51
|
+
})) {
|
|
52
|
+
if (message.type === 'system' && message.subtype === 'init') {
|
|
53
|
+
sessionId = message.session_id;
|
|
54
|
+
console.log(`Session ID captured: ${sessionId}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Resume with full context from the first query
|
|
59
|
+
console.log('\n--- Second query: resuming to find callers ---');
|
|
60
|
+
for await (const message of query({
|
|
61
|
+
prompt: 'Now find all places that call it', // "it" = auth module from context
|
|
62
|
+
options: {
|
|
63
|
+
resume: sessionId,
|
|
64
|
+
maxTurns: 5,
|
|
65
|
+
},
|
|
66
|
+
})) {
|
|
67
|
+
if ('result' in message) {
|
|
68
|
+
console.log(message.result);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 07-hooks-pretooluse.js
|
|
3
|
+
*
|
|
4
|
+
* Equivalent of the official "hooks" example showing PreToolUse to protect .env files.
|
|
5
|
+
*
|
|
6
|
+
* Official version:
|
|
7
|
+
* import { query, HookCallback } from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
+
*
|
|
9
|
+
* const protectEnvFiles: HookCallback = async (input, toolUseID, { signal }) => {
|
|
10
|
+
* const preInput = input as PreToolUseHookInput;
|
|
11
|
+
* const toolInput = preInput.tool_input as Record<string, unknown>;
|
|
12
|
+
* const filePath = toolInput?.file_path as string;
|
|
13
|
+
* const fileName = filePath?.split("/").pop();
|
|
14
|
+
*
|
|
15
|
+
* if (fileName === ".env") {
|
|
16
|
+
* return {
|
|
17
|
+
* hookSpecificOutput: {
|
|
18
|
+
* hookEventName: preInput.hook_event_name,
|
|
19
|
+
* permissionDecision: "deny",
|
|
20
|
+
* permissionDecisionReason: "Cannot modify .env files"
|
|
21
|
+
* }
|
|
22
|
+
* };
|
|
23
|
+
* }
|
|
24
|
+
* return {};
|
|
25
|
+
* };
|
|
26
|
+
*
|
|
27
|
+
* This elwood version uses the same hooks API (callback-based).
|
|
28
|
+
*
|
|
29
|
+
* Prerequisites:
|
|
30
|
+
* - ANTHROPIC_API_KEY set in environment
|
|
31
|
+
* - Claude Code CLI installed
|
|
32
|
+
*
|
|
33
|
+
* Run:
|
|
34
|
+
* node examples/07-hooks-pretooluse.js
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
import { query } from '../src/index.js';
|
|
38
|
+
|
|
39
|
+
// Define a hook callback that blocks writes to .env files
|
|
40
|
+
const protectEnvFiles = async (input, toolUseID, { signal }) => {
|
|
41
|
+
const toolInput = input.tool_input;
|
|
42
|
+
const filePath = typeof toolInput === 'object' ? toolInput?.file_path : undefined;
|
|
43
|
+
const fileName = typeof filePath === 'string' ? filePath.split('/').pop() : '';
|
|
44
|
+
|
|
45
|
+
// Block the operation if targeting a .env file
|
|
46
|
+
if (fileName === '.env') {
|
|
47
|
+
return {
|
|
48
|
+
hookSpecificOutput: {
|
|
49
|
+
hookEventName: input.hook_event_name,
|
|
50
|
+
permissionDecision: 'deny',
|
|
51
|
+
permissionDecisionReason: 'Cannot modify .env files',
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Return empty object to allow the operation
|
|
57
|
+
return {};
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
for await (const message of query({
|
|
61
|
+
prompt: 'Update the database configuration',
|
|
62
|
+
options: {
|
|
63
|
+
hooks: {
|
|
64
|
+
// Register the hook for PreToolUse events
|
|
65
|
+
// The matcher filters to only Write and Edit tool calls
|
|
66
|
+
PreToolUse: [{ matcher: 'Write|Edit', hooks: [protectEnvFiles] }],
|
|
67
|
+
},
|
|
68
|
+
maxTurns: 3,
|
|
69
|
+
},
|
|
70
|
+
})) {
|
|
71
|
+
if (message.type === 'assistant' || message.type === 'result') {
|
|
72
|
+
console.log(message);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 08-hooks-posttooluse-audit.js
|
|
3
|
+
*
|
|
4
|
+
* Equivalent of the official "PostToolUse audit log" hooks example.
|
|
5
|
+
*
|
|
6
|
+
* Official version:
|
|
7
|
+
* import { query, HookCallback } from "@anthropic-ai/claude-agent-sdk";
|
|
8
|
+
* import { appendFile } from "fs/promises";
|
|
9
|
+
*
|
|
10
|
+
* const logFileChange: HookCallback = async (input) => {
|
|
11
|
+
* const filePath = (input as any).tool_input?.file_path ?? "unknown";
|
|
12
|
+
* await appendFile("./audit.log",
|
|
13
|
+
* `${new Date().toISOString()}: modified ${filePath}\n`);
|
|
14
|
+
* return {};
|
|
15
|
+
* };
|
|
16
|
+
*
|
|
17
|
+
* for await (const message of query({
|
|
18
|
+
* prompt: "Refactor utils.py to improve readability",
|
|
19
|
+
* options: {
|
|
20
|
+
* permissionMode: "acceptEdits",
|
|
21
|
+
* hooks: {
|
|
22
|
+
* PostToolUse: [{ matcher: "Edit|Write", hooks: [logFileChange] }]
|
|
23
|
+
* }
|
|
24
|
+
* }
|
|
25
|
+
* })) {
|
|
26
|
+
* if ("result" in message) console.log(message.result);
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* This elwood version logs all file changes to an audit.log file
|
|
30
|
+
* using a PostToolUse hook.
|
|
31
|
+
*
|
|
32
|
+
* Prerequisites:
|
|
33
|
+
* - ANTHROPIC_API_KEY set in environment
|
|
34
|
+
* - Claude Code CLI installed
|
|
35
|
+
*
|
|
36
|
+
* Run:
|
|
37
|
+
* node examples/08-hooks-posttooluse-audit.js
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import { query } from '../src/index.js';
|
|
41
|
+
import { appendFile } from 'node:fs/promises';
|
|
42
|
+
|
|
43
|
+
// Define a hook callback that logs file modifications
|
|
44
|
+
const logFileChange = async (input, _toolUseID, _context) => {
|
|
45
|
+
const filePath = input?.tool_input?.file_path ?? 'unknown';
|
|
46
|
+
await appendFile(
|
|
47
|
+
'./audit.log',
|
|
48
|
+
`${new Date().toISOString()}: modified ${filePath}\n`
|
|
49
|
+
);
|
|
50
|
+
return {};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
for await (const message of query({
|
|
54
|
+
prompt: 'Refactor utils.py to improve readability',
|
|
55
|
+
options: {
|
|
56
|
+
permissionMode: 'acceptEdits',
|
|
57
|
+
hooks: {
|
|
58
|
+
PostToolUse: [{ matcher: 'Edit|Write', hooks: [logFileChange] }],
|
|
59
|
+
},
|
|
60
|
+
maxTurns: 5,
|
|
61
|
+
},
|
|
62
|
+
})) {
|
|
63
|
+
if ('result' in message) {
|
|
64
|
+
console.log(message.result);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 09-hooks-block-etc.js
|
|
3
|
+
*
|
|
4
|
+
* Equivalent of the official "block writes to /etc" hooks example.
|
|
5
|
+
*
|
|
6
|
+
* Official version:
|
|
7
|
+
* const blockEtcWrites: HookCallback = async (input, toolUseID, { signal }) => {
|
|
8
|
+
* const preInput = input as PreToolUseHookInput;
|
|
9
|
+
* const toolInput = preInput.tool_input as Record<string, unknown>;
|
|
10
|
+
* const filePath = toolInput?.file_path as string;
|
|
11
|
+
* if (filePath?.startsWith("/etc")) {
|
|
12
|
+
* return {
|
|
13
|
+
* systemMessage: "Remember: system directories like /etc are protected.",
|
|
14
|
+
* hookSpecificOutput: {
|
|
15
|
+
* hookEventName: preInput.hook_event_name,
|
|
16
|
+
* permissionDecision: "deny",
|
|
17
|
+
* permissionDecisionReason: "Writing to /etc is not allowed"
|
|
18
|
+
* }
|
|
19
|
+
* };
|
|
20
|
+
* }
|
|
21
|
+
* return {};
|
|
22
|
+
* };
|
|
23
|
+
*
|
|
24
|
+
* This elwood version blocks writes to /etc and shows a system message.
|
|
25
|
+
*
|
|
26
|
+
* Prerequisites:
|
|
27
|
+
* - ANTHROPIC_API_KEY set in environment
|
|
28
|
+
* - Claude Code CLI installed
|
|
29
|
+
*
|
|
30
|
+
* Run:
|
|
31
|
+
* node examples/09-hooks-block-etc.js
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { query } from '../src/index.js';
|
|
35
|
+
|
|
36
|
+
// Block writes to /etc system directory
|
|
37
|
+
const blockEtcWrites = async (input, _toolUseID, { signal }) => {
|
|
38
|
+
const toolInput = input.tool_input;
|
|
39
|
+
const filePath = typeof toolInput === 'object' ? toolInput?.file_path : undefined;
|
|
40
|
+
|
|
41
|
+
if (typeof filePath === 'string' && filePath.startsWith('/etc')) {
|
|
42
|
+
return {
|
|
43
|
+
// Top-level field: message shown to the user
|
|
44
|
+
systemMessage: 'Remember: system directories like /etc are protected.',
|
|
45
|
+
// hookSpecificOutput: block the operation
|
|
46
|
+
hookSpecificOutput: {
|
|
47
|
+
hookEventName: input.hook_event_name,
|
|
48
|
+
permissionDecision: 'deny',
|
|
49
|
+
permissionDecisionReason: 'Writing to /etc is not allowed',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return {};
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
for await (const message of query({
|
|
57
|
+
prompt: 'Write a config file to /etc/myapp.conf',
|
|
58
|
+
options: {
|
|
59
|
+
hooks: {
|
|
60
|
+
PreToolUse: [{ matcher: 'Write|Edit', hooks: [blockEtcWrites] }],
|
|
61
|
+
},
|
|
62
|
+
maxTurns: 3,
|
|
63
|
+
},
|
|
64
|
+
})) {
|
|
65
|
+
if (message.type === 'assistant' || message.type === 'result') {
|
|
66
|
+
console.log(message);
|
|
67
|
+
}
|
|
68
|
+
}
|