@claude-code-kit/tools 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Minnzen
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,38 @@
1
+ # @claude-code-kit/tools
2
+
3
+ Built-in tool collection for the [claude-code-kit](https://github.com/Minnzen/claude-code-kit) agent framework.
4
+
5
+ ## Tools
6
+
7
+ | Tool | Description | Read-only |
8
+ |------|-------------|-----------|
9
+ | `bash` | Execute shell commands | No |
10
+ | `read` | Read file contents with line numbers | Yes |
11
+ | `edit` | Edit files via unique string replacement | No |
12
+ | `write` | Write/create files with auto-mkdir | No |
13
+ | `glob` | Find files by glob pattern | Yes |
14
+ | `grep` | Search file contents with regex | Yes |
15
+ | `web_fetch` | Make HTTP requests | Yes (GET) |
16
+
17
+ ## Usage
18
+
19
+ ```ts
20
+ import { Agent, AnthropicProvider } from "@claude-code-kit/agent";
21
+ import { builtinTools } from "@claude-code-kit/tools";
22
+
23
+ const agent = new Agent({
24
+ provider: new AnthropicProvider({ apiKey: "..." }),
25
+ model: "claude-sonnet-4-20250514",
26
+ tools: builtinTools,
27
+ });
28
+ ```
29
+
30
+ Or import individual tools:
31
+
32
+ ```ts
33
+ import { bashTool, readTool, editTool } from "@claude-code-kit/tools";
34
+ ```
35
+
36
+ ## License
37
+
38
+ MIT
@@ -0,0 +1,62 @@
1
+ import { z } from 'zod';
2
+ import { ToolDefinition } from '@claude-code-kit/agent';
3
+
4
+ declare const inputSchema$6: z.ZodObject<{
5
+ command: z.ZodString;
6
+ cwd: z.ZodOptional<z.ZodString>;
7
+ timeout: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
8
+ }, z.core.$strip>;
9
+ type Input$6 = z.infer<typeof inputSchema$6>;
10
+ declare const bashTool: ToolDefinition<Input$6>;
11
+
12
+ declare const inputSchema$5: z.ZodObject<{
13
+ path: z.ZodString;
14
+ offset: z.ZodOptional<z.ZodNumber>;
15
+ limit: z.ZodOptional<z.ZodNumber>;
16
+ }, z.core.$strip>;
17
+ type Input$5 = z.infer<typeof inputSchema$5>;
18
+ declare const readTool: ToolDefinition<Input$5>;
19
+
20
+ declare const inputSchema$4: z.ZodObject<{
21
+ path: z.ZodString;
22
+ oldString: z.ZodString;
23
+ newString: z.ZodString;
24
+ }, z.core.$strip>;
25
+ type Input$4 = z.infer<typeof inputSchema$4>;
26
+ declare const editTool: ToolDefinition<Input$4>;
27
+
28
+ declare const inputSchema$3: z.ZodObject<{
29
+ path: z.ZodString;
30
+ content: z.ZodString;
31
+ }, z.core.$strip>;
32
+ type Input$3 = z.infer<typeof inputSchema$3>;
33
+ declare const writeTool: ToolDefinition<Input$3>;
34
+
35
+ declare const inputSchema$2: z.ZodObject<{
36
+ pattern: z.ZodString;
37
+ cwd: z.ZodOptional<z.ZodString>;
38
+ }, z.core.$strip>;
39
+ type Input$2 = z.infer<typeof inputSchema$2>;
40
+ declare const globTool: ToolDefinition<Input$2>;
41
+
42
+ declare const inputSchema$1: z.ZodObject<{
43
+ pattern: z.ZodString;
44
+ path: z.ZodOptional<z.ZodString>;
45
+ glob: z.ZodOptional<z.ZodString>;
46
+ }, z.core.$strip>;
47
+ type Input$1 = z.infer<typeof inputSchema$1>;
48
+ declare const grepTool: ToolDefinition<Input$1>;
49
+
50
+ declare const inputSchema: z.ZodObject<{
51
+ url: z.ZodString;
52
+ method: z.ZodDefault<z.ZodOptional<z.ZodString>>;
53
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
54
+ body: z.ZodOptional<z.ZodString>;
55
+ }, z.core.$strip>;
56
+ type Input = z.infer<typeof inputSchema>;
57
+ declare const webFetchTool: ToolDefinition<Input>;
58
+
59
+ /** All built-in tools as an array, ready to pass to AgentConfig.tools */
60
+ declare const builtinTools: ToolDefinition[];
61
+
62
+ export { bashTool, builtinTools, editTool, globTool, grepTool, readTool, webFetchTool, writeTool };
@@ -0,0 +1,62 @@
1
+ import { z } from 'zod';
2
+ import { ToolDefinition } from '@claude-code-kit/agent';
3
+
4
+ declare const inputSchema$6: z.ZodObject<{
5
+ command: z.ZodString;
6
+ cwd: z.ZodOptional<z.ZodString>;
7
+ timeout: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
8
+ }, z.core.$strip>;
9
+ type Input$6 = z.infer<typeof inputSchema$6>;
10
+ declare const bashTool: ToolDefinition<Input$6>;
11
+
12
+ declare const inputSchema$5: z.ZodObject<{
13
+ path: z.ZodString;
14
+ offset: z.ZodOptional<z.ZodNumber>;
15
+ limit: z.ZodOptional<z.ZodNumber>;
16
+ }, z.core.$strip>;
17
+ type Input$5 = z.infer<typeof inputSchema$5>;
18
+ declare const readTool: ToolDefinition<Input$5>;
19
+
20
+ declare const inputSchema$4: z.ZodObject<{
21
+ path: z.ZodString;
22
+ oldString: z.ZodString;
23
+ newString: z.ZodString;
24
+ }, z.core.$strip>;
25
+ type Input$4 = z.infer<typeof inputSchema$4>;
26
+ declare const editTool: ToolDefinition<Input$4>;
27
+
28
+ declare const inputSchema$3: z.ZodObject<{
29
+ path: z.ZodString;
30
+ content: z.ZodString;
31
+ }, z.core.$strip>;
32
+ type Input$3 = z.infer<typeof inputSchema$3>;
33
+ declare const writeTool: ToolDefinition<Input$3>;
34
+
35
+ declare const inputSchema$2: z.ZodObject<{
36
+ pattern: z.ZodString;
37
+ cwd: z.ZodOptional<z.ZodString>;
38
+ }, z.core.$strip>;
39
+ type Input$2 = z.infer<typeof inputSchema$2>;
40
+ declare const globTool: ToolDefinition<Input$2>;
41
+
42
+ declare const inputSchema$1: z.ZodObject<{
43
+ pattern: z.ZodString;
44
+ path: z.ZodOptional<z.ZodString>;
45
+ glob: z.ZodOptional<z.ZodString>;
46
+ }, z.core.$strip>;
47
+ type Input$1 = z.infer<typeof inputSchema$1>;
48
+ declare const grepTool: ToolDefinition<Input$1>;
49
+
50
+ declare const inputSchema: z.ZodObject<{
51
+ url: z.ZodString;
52
+ method: z.ZodDefault<z.ZodOptional<z.ZodString>>;
53
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
54
+ body: z.ZodOptional<z.ZodString>;
55
+ }, z.core.$strip>;
56
+ type Input = z.infer<typeof inputSchema>;
57
+ declare const webFetchTool: ToolDefinition<Input>;
58
+
59
+ /** All built-in tools as an array, ready to pass to AgentConfig.tools */
60
+ declare const builtinTools: ToolDefinition[];
61
+
62
+ export { bashTool, builtinTools, editTool, globTool, grepTool, readTool, webFetchTool, writeTool };
package/dist/index.js ADDED
@@ -0,0 +1,412 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ bashTool: () => bashTool,
34
+ builtinTools: () => builtinTools,
35
+ editTool: () => editTool,
36
+ globTool: () => globTool,
37
+ grepTool: () => grepTool,
38
+ readTool: () => readTool,
39
+ webFetchTool: () => webFetchTool,
40
+ writeTool: () => writeTool
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/bash.ts
45
+ var import_node_child_process = require("child_process");
46
+ var import_zod = require("zod");
47
+ var MAX_RESULT_SIZE = 1e5;
48
+ var inputSchema = import_zod.z.object({
49
+ command: import_zod.z.string().describe("The shell command to execute"),
50
+ cwd: import_zod.z.string().optional().describe("Working directory for the command"),
51
+ timeout: import_zod.z.number().optional().default(3e4).describe("Timeout in milliseconds")
52
+ });
53
+ async function execute(input, ctx) {
54
+ const cwd = input.cwd ?? ctx.workingDirectory;
55
+ const timeout = input.timeout;
56
+ return new Promise((resolve5) => {
57
+ const onAbort = () => {
58
+ child.kill("SIGTERM");
59
+ resolve5({ content: "Command aborted", isError: true });
60
+ };
61
+ const child = (0, import_node_child_process.exec)(input.command, { cwd, timeout, env: { ...process.env, ...ctx.env } }, (err, stdout, stderr) => {
62
+ ctx.abortSignal.removeEventListener("abort", onAbort);
63
+ const output = (stdout + (stderr ? `
64
+ ${stderr}` : "")).slice(0, MAX_RESULT_SIZE);
65
+ if (err && err.killed) {
66
+ resolve5({ content: `Command timed out after ${timeout}ms
67
+ ${output}`, isError: true });
68
+ return;
69
+ }
70
+ if (err) {
71
+ resolve5({ content: output || err.message, isError: true, metadata: { exitCode: err.code } });
72
+ return;
73
+ }
74
+ resolve5({ content: output || "(no output)" });
75
+ });
76
+ if (ctx.abortSignal.aborted) {
77
+ onAbort();
78
+ return;
79
+ }
80
+ ctx.abortSignal.addEventListener("abort", onAbort, { once: true });
81
+ });
82
+ }
83
+ var bashTool = {
84
+ name: "bash",
85
+ description: "Execute a shell command and return its stdout/stderr output",
86
+ inputSchema,
87
+ execute,
88
+ isReadOnly: false,
89
+ requiresConfirmation: true,
90
+ timeout: 3e4
91
+ };
92
+
93
+ // src/read.ts
94
+ var fs = __toESM(require("fs/promises"));
95
+ var path = __toESM(require("path"));
96
+ var import_zod2 = require("zod");
97
+ var MAX_RESULT_SIZE2 = 1e5;
98
+ var inputSchema2 = import_zod2.z.object({
99
+ path: import_zod2.z.string().describe("Absolute or relative file path to read"),
100
+ offset: import_zod2.z.number().optional().describe("Line number to start reading from (1-based)"),
101
+ limit: import_zod2.z.number().optional().describe("Maximum number of lines to read")
102
+ });
103
+ async function execute2(input, ctx) {
104
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
105
+ const filePath = path.resolve(ctx.workingDirectory, input.path);
106
+ if (!filePath.startsWith(ctx.workingDirectory + path.sep) && filePath !== ctx.workingDirectory) {
107
+ return { content: `Error: path traversal denied \u2014 ${input.path} escapes working directory`, isError: true };
108
+ }
109
+ try {
110
+ const raw = await fs.readFile(filePath, "utf-8");
111
+ let lines = raw.split("\n");
112
+ if (input.offset !== void 0) {
113
+ lines = lines.slice(Math.max(0, input.offset - 1));
114
+ }
115
+ if (input.limit !== void 0) {
116
+ lines = lines.slice(0, input.limit);
117
+ }
118
+ const startLine = input.offset ?? 1;
119
+ const numbered = lines.map((line, i) => `${startLine + i} ${line}`);
120
+ const content = numbered.join("\n").slice(0, MAX_RESULT_SIZE2);
121
+ return { content, metadata: { totalLines: raw.split("\n").length } };
122
+ } catch (err) {
123
+ const msg = err instanceof Error ? err.message : String(err);
124
+ return { content: `Error reading file: ${msg}`, isError: true };
125
+ }
126
+ }
127
+ var readTool = {
128
+ name: "read",
129
+ description: "Read file contents with optional line offset and limit, returning numbered lines",
130
+ inputSchema: inputSchema2,
131
+ execute: execute2,
132
+ isReadOnly: true,
133
+ timeout: 1e4
134
+ };
135
+
136
+ // src/edit.ts
137
+ var fs2 = __toESM(require("fs/promises"));
138
+ var path2 = __toESM(require("path"));
139
+ var import_zod3 = require("zod");
140
+ var inputSchema3 = import_zod3.z.object({
141
+ path: import_zod3.z.string().describe("Absolute or relative file path to edit"),
142
+ oldString: import_zod3.z.string().describe("Exact string to find and replace (must be unique in file)"),
143
+ newString: import_zod3.z.string().describe("Replacement string")
144
+ });
145
+ async function execute3(input, ctx) {
146
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
147
+ const filePath = path2.resolve(ctx.workingDirectory, input.path);
148
+ if (!filePath.startsWith(ctx.workingDirectory + path2.sep) && filePath !== ctx.workingDirectory) {
149
+ return { content: `Error: path traversal denied \u2014 ${input.path} escapes working directory`, isError: true };
150
+ }
151
+ try {
152
+ const content = await fs2.readFile(filePath, "utf-8");
153
+ const occurrences = content.split(input.oldString).length - 1;
154
+ if (occurrences === 0) {
155
+ return { content: "Error: oldString not found in file", isError: true };
156
+ }
157
+ if (occurrences > 1) {
158
+ return {
159
+ content: `Error: oldString found ${occurrences} times \u2014 must be unique. Provide more context to disambiguate.`,
160
+ isError: true
161
+ };
162
+ }
163
+ const updated = content.replace(input.oldString, input.newString);
164
+ await fs2.writeFile(filePath, updated, "utf-8");
165
+ return { content: `Successfully edited ${filePath}` };
166
+ } catch (err) {
167
+ const msg = err instanceof Error ? err.message : String(err);
168
+ return { content: `Error editing file: ${msg}`, isError: true };
169
+ }
170
+ }
171
+ var editTool = {
172
+ name: "edit",
173
+ description: "Edit a file by replacing a unique string occurrence with a new string",
174
+ inputSchema: inputSchema3,
175
+ execute: execute3,
176
+ isReadOnly: false,
177
+ requiresConfirmation: true,
178
+ timeout: 1e4
179
+ };
180
+
181
+ // src/write.ts
182
+ var fs3 = __toESM(require("fs/promises"));
183
+ var path3 = __toESM(require("path"));
184
+ var import_zod4 = require("zod");
185
+ var inputSchema4 = import_zod4.z.object({
186
+ path: import_zod4.z.string().describe("Absolute or relative file path to write"),
187
+ content: import_zod4.z.string().describe("Content to write to the file")
188
+ });
189
+ async function execute4(input, ctx) {
190
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
191
+ const filePath = path3.resolve(ctx.workingDirectory, input.path);
192
+ if (!filePath.startsWith(ctx.workingDirectory + path3.sep) && filePath !== ctx.workingDirectory) {
193
+ return { content: `Error: path traversal denied \u2014 ${input.path} escapes working directory`, isError: true };
194
+ }
195
+ try {
196
+ await fs3.mkdir(path3.dirname(filePath), { recursive: true });
197
+ await fs3.writeFile(filePath, input.content, "utf-8");
198
+ return {
199
+ content: `Successfully wrote ${Buffer.byteLength(input.content)} bytes to ${filePath}`
200
+ };
201
+ } catch (err) {
202
+ const msg = err instanceof Error ? err.message : String(err);
203
+ return { content: `Error writing file: ${msg}`, isError: true };
204
+ }
205
+ }
206
+ var writeTool = {
207
+ name: "write",
208
+ description: "Write content to a file, creating parent directories as needed",
209
+ inputSchema: inputSchema4,
210
+ execute: execute4,
211
+ isReadOnly: false,
212
+ requiresConfirmation: true,
213
+ timeout: 1e4
214
+ };
215
+
216
+ // src/glob.ts
217
+ var import_fast_glob = __toESM(require("fast-glob"));
218
+ var import_zod5 = require("zod");
219
+ var MAX_RESULT_SIZE3 = 1e5;
220
+ var inputSchema5 = import_zod5.z.object({
221
+ pattern: import_zod5.z.string().describe("Glob pattern to match files (e.g. **/*.ts)"),
222
+ cwd: import_zod5.z.string().optional().describe("Directory to search in")
223
+ });
224
+ async function execute5(input, ctx) {
225
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
226
+ const cwd = input.cwd ?? ctx.workingDirectory;
227
+ try {
228
+ const files = await (0, import_fast_glob.default)(input.pattern, {
229
+ cwd,
230
+ dot: false,
231
+ ignore: ["**/node_modules/**", "**/.git/**"],
232
+ onlyFiles: true,
233
+ absolute: false
234
+ });
235
+ files.sort();
236
+ if (files.length === 0) {
237
+ return { content: "No files matched the pattern" };
238
+ }
239
+ const content = files.join("\n").slice(0, MAX_RESULT_SIZE3);
240
+ return { content, metadata: { matchCount: files.length } };
241
+ } catch (err) {
242
+ const msg = err instanceof Error ? err.message : String(err);
243
+ return { content: `Error searching files: ${msg}`, isError: true };
244
+ }
245
+ }
246
+ var globTool = {
247
+ name: "glob",
248
+ description: "Find files matching a glob pattern, excluding node_modules and .git",
249
+ inputSchema: inputSchema5,
250
+ execute: execute5,
251
+ isReadOnly: true,
252
+ timeout: 15e3
253
+ };
254
+
255
+ // src/grep.ts
256
+ var fs4 = __toESM(require("fs/promises"));
257
+ var path4 = __toESM(require("path"));
258
+ var import_zod6 = require("zod");
259
+ var import_fast_glob2 = __toESM(require("fast-glob"));
260
+ var MAX_RESULT_SIZE4 = 1e5;
261
+ var MAX_FILES = 5e3;
262
+ var inputSchema6 = import_zod6.z.object({
263
+ pattern: import_zod6.z.string().describe("Regex pattern to search for in file contents"),
264
+ path: import_zod6.z.string().optional().describe("Directory or file to search in"),
265
+ glob: import_zod6.z.string().optional().describe("Glob filter for files (e.g. *.ts)")
266
+ });
267
+ async function execute6(input, ctx) {
268
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
269
+ const searchPath = input.path ? path4.resolve(ctx.workingDirectory, input.path) : ctx.workingDirectory;
270
+ const globPattern = input.glob ?? "**/*";
271
+ try {
272
+ const regex = new RegExp(input.pattern);
273
+ const stat2 = await fs4.stat(searchPath);
274
+ let files;
275
+ if (stat2.isFile()) {
276
+ files = [searchPath];
277
+ } else {
278
+ files = await (0, import_fast_glob2.default)(globPattern, {
279
+ cwd: searchPath,
280
+ absolute: true,
281
+ onlyFiles: true,
282
+ ignore: ["**/node_modules/**", "**/.git/**", "**/*.min.*"]
283
+ });
284
+ files = files.slice(0, MAX_FILES);
285
+ }
286
+ const matches = [];
287
+ let totalSize = 0;
288
+ for (const file of files) {
289
+ if (ctx.abortSignal.aborted) break;
290
+ try {
291
+ const content = await fs4.readFile(file, "utf-8");
292
+ const lines = content.split("\n");
293
+ for (let i = 0; i < lines.length; i++) {
294
+ if (regex.test(lines[i])) {
295
+ const rel = path4.relative(ctx.workingDirectory, file);
296
+ const line = `${rel}:${i + 1}: ${lines[i]}`;
297
+ totalSize += line.length;
298
+ if (totalSize > MAX_RESULT_SIZE4) break;
299
+ matches.push(line);
300
+ }
301
+ }
302
+ } catch {
303
+ }
304
+ if (totalSize > MAX_RESULT_SIZE4) break;
305
+ }
306
+ if (matches.length === 0) {
307
+ return { content: "No matches found" };
308
+ }
309
+ return { content: matches.join("\n"), metadata: { matchCount: matches.length } };
310
+ } catch (err) {
311
+ const msg = err instanceof Error ? err.message : String(err);
312
+ return { content: `Error searching: ${msg}`, isError: true };
313
+ }
314
+ }
315
+ var grepTool = {
316
+ name: "grep",
317
+ description: "Search file contents using regex, returning matching lines with file:line format",
318
+ inputSchema: inputSchema6,
319
+ execute: execute6,
320
+ isReadOnly: true,
321
+ timeout: 3e4
322
+ };
323
+
324
+ // src/web-fetch.ts
325
+ var import_zod7 = require("zod");
326
+ var MAX_RESULT_SIZE5 = 5e4;
327
+ function isPrivateUrl(urlStr) {
328
+ const url = new URL(urlStr);
329
+ const hostname = url.hostname;
330
+ const blocked = [
331
+ /^127\./,
332
+ /^10\./,
333
+ /^172\.(1[6-9]|2\d|3[01])\./,
334
+ /^192\.168\./,
335
+ /^169\.254\./,
336
+ /^0\./,
337
+ /^localhost$/i,
338
+ /^::1$/,
339
+ /^\[::1\]$/,
340
+ /^metadata\.google/,
341
+ /^169\.254\.169\.254$/
342
+ ];
343
+ return blocked.some((re) => re.test(hostname));
344
+ }
345
+ var inputSchema7 = import_zod7.z.object({
346
+ url: import_zod7.z.string().url().describe("URL to fetch"),
347
+ method: import_zod7.z.string().optional().default("GET").describe("HTTP method"),
348
+ headers: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.string()).optional().describe("HTTP headers"),
349
+ body: import_zod7.z.string().optional().describe("Request body")
350
+ });
351
+ async function execute7(input, ctx) {
352
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
353
+ try {
354
+ if (isPrivateUrl(input.url)) {
355
+ return { content: `Error: request to private/internal address denied \u2014 ${input.url}`, isError: true };
356
+ }
357
+ } catch {
358
+ return { content: `Error: invalid URL \u2014 ${input.url}`, isError: true };
359
+ }
360
+ try {
361
+ const res = await fetch(input.url, {
362
+ method: input.method,
363
+ headers: input.headers,
364
+ body: input.body,
365
+ signal: ctx.abortSignal
366
+ });
367
+ const text = await res.text();
368
+ const truncated = text.slice(0, MAX_RESULT_SIZE5);
369
+ const suffix = text.length > MAX_RESULT_SIZE5 ? "\n...(truncated)" : "";
370
+ const content = `HTTP ${res.status} ${res.statusText}
371
+
372
+ ${truncated}${suffix}`;
373
+ return {
374
+ content,
375
+ isError: res.status >= 400,
376
+ metadata: { status: res.status, headers: Object.fromEntries(res.headers.entries()) }
377
+ };
378
+ } catch (err) {
379
+ const msg = err instanceof Error ? err.message : String(err);
380
+ return { content: `Fetch error: ${msg}`, isError: true };
381
+ }
382
+ }
383
+ var webFetchTool = {
384
+ name: "web_fetch",
385
+ description: "Make HTTP requests and return the response body",
386
+ inputSchema: inputSchema7,
387
+ execute: execute7,
388
+ isReadOnly: false,
389
+ timeout: 3e4
390
+ };
391
+
392
+ // src/index.ts
393
+ var builtinTools = [
394
+ bashTool,
395
+ readTool,
396
+ editTool,
397
+ writeTool,
398
+ globTool,
399
+ grepTool,
400
+ webFetchTool
401
+ ];
402
+ // Annotate the CommonJS export names for ESM import in node:
403
+ 0 && (module.exports = {
404
+ bashTool,
405
+ builtinTools,
406
+ editTool,
407
+ globTool,
408
+ grepTool,
409
+ readTool,
410
+ webFetchTool,
411
+ writeTool
412
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,368 @@
1
+ // src/bash.ts
2
+ import { exec } from "child_process";
3
+ import { z } from "zod";
4
+ var MAX_RESULT_SIZE = 1e5;
5
+ var inputSchema = z.object({
6
+ command: z.string().describe("The shell command to execute"),
7
+ cwd: z.string().optional().describe("Working directory for the command"),
8
+ timeout: z.number().optional().default(3e4).describe("Timeout in milliseconds")
9
+ });
10
+ async function execute(input, ctx) {
11
+ const cwd = input.cwd ?? ctx.workingDirectory;
12
+ const timeout = input.timeout;
13
+ return new Promise((resolve5) => {
14
+ const onAbort = () => {
15
+ child.kill("SIGTERM");
16
+ resolve5({ content: "Command aborted", isError: true });
17
+ };
18
+ const child = exec(input.command, { cwd, timeout, env: { ...process.env, ...ctx.env } }, (err, stdout, stderr) => {
19
+ ctx.abortSignal.removeEventListener("abort", onAbort);
20
+ const output = (stdout + (stderr ? `
21
+ ${stderr}` : "")).slice(0, MAX_RESULT_SIZE);
22
+ if (err && err.killed) {
23
+ resolve5({ content: `Command timed out after ${timeout}ms
24
+ ${output}`, isError: true });
25
+ return;
26
+ }
27
+ if (err) {
28
+ resolve5({ content: output || err.message, isError: true, metadata: { exitCode: err.code } });
29
+ return;
30
+ }
31
+ resolve5({ content: output || "(no output)" });
32
+ });
33
+ if (ctx.abortSignal.aborted) {
34
+ onAbort();
35
+ return;
36
+ }
37
+ ctx.abortSignal.addEventListener("abort", onAbort, { once: true });
38
+ });
39
+ }
40
+ var bashTool = {
41
+ name: "bash",
42
+ description: "Execute a shell command and return its stdout/stderr output",
43
+ inputSchema,
44
+ execute,
45
+ isReadOnly: false,
46
+ requiresConfirmation: true,
47
+ timeout: 3e4
48
+ };
49
+
50
+ // src/read.ts
51
+ import * as fs from "fs/promises";
52
+ import * as path from "path";
53
+ import { z as z2 } from "zod";
54
+ var MAX_RESULT_SIZE2 = 1e5;
55
+ var inputSchema2 = z2.object({
56
+ path: z2.string().describe("Absolute or relative file path to read"),
57
+ offset: z2.number().optional().describe("Line number to start reading from (1-based)"),
58
+ limit: z2.number().optional().describe("Maximum number of lines to read")
59
+ });
60
+ async function execute2(input, ctx) {
61
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
62
+ const filePath = path.resolve(ctx.workingDirectory, input.path);
63
+ if (!filePath.startsWith(ctx.workingDirectory + path.sep) && filePath !== ctx.workingDirectory) {
64
+ return { content: `Error: path traversal denied \u2014 ${input.path} escapes working directory`, isError: true };
65
+ }
66
+ try {
67
+ const raw = await fs.readFile(filePath, "utf-8");
68
+ let lines = raw.split("\n");
69
+ if (input.offset !== void 0) {
70
+ lines = lines.slice(Math.max(0, input.offset - 1));
71
+ }
72
+ if (input.limit !== void 0) {
73
+ lines = lines.slice(0, input.limit);
74
+ }
75
+ const startLine = input.offset ?? 1;
76
+ const numbered = lines.map((line, i) => `${startLine + i} ${line}`);
77
+ const content = numbered.join("\n").slice(0, MAX_RESULT_SIZE2);
78
+ return { content, metadata: { totalLines: raw.split("\n").length } };
79
+ } catch (err) {
80
+ const msg = err instanceof Error ? err.message : String(err);
81
+ return { content: `Error reading file: ${msg}`, isError: true };
82
+ }
83
+ }
84
+ var readTool = {
85
+ name: "read",
86
+ description: "Read file contents with optional line offset and limit, returning numbered lines",
87
+ inputSchema: inputSchema2,
88
+ execute: execute2,
89
+ isReadOnly: true,
90
+ timeout: 1e4
91
+ };
92
+
93
+ // src/edit.ts
94
+ import * as fs2 from "fs/promises";
95
+ import * as path2 from "path";
96
+ import { z as z3 } from "zod";
97
+ var inputSchema3 = z3.object({
98
+ path: z3.string().describe("Absolute or relative file path to edit"),
99
+ oldString: z3.string().describe("Exact string to find and replace (must be unique in file)"),
100
+ newString: z3.string().describe("Replacement string")
101
+ });
102
+ async function execute3(input, ctx) {
103
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
104
+ const filePath = path2.resolve(ctx.workingDirectory, input.path);
105
+ if (!filePath.startsWith(ctx.workingDirectory + path2.sep) && filePath !== ctx.workingDirectory) {
106
+ return { content: `Error: path traversal denied \u2014 ${input.path} escapes working directory`, isError: true };
107
+ }
108
+ try {
109
+ const content = await fs2.readFile(filePath, "utf-8");
110
+ const occurrences = content.split(input.oldString).length - 1;
111
+ if (occurrences === 0) {
112
+ return { content: "Error: oldString not found in file", isError: true };
113
+ }
114
+ if (occurrences > 1) {
115
+ return {
116
+ content: `Error: oldString found ${occurrences} times \u2014 must be unique. Provide more context to disambiguate.`,
117
+ isError: true
118
+ };
119
+ }
120
+ const updated = content.replace(input.oldString, input.newString);
121
+ await fs2.writeFile(filePath, updated, "utf-8");
122
+ return { content: `Successfully edited ${filePath}` };
123
+ } catch (err) {
124
+ const msg = err instanceof Error ? err.message : String(err);
125
+ return { content: `Error editing file: ${msg}`, isError: true };
126
+ }
127
+ }
128
+ var editTool = {
129
+ name: "edit",
130
+ description: "Edit a file by replacing a unique string occurrence with a new string",
131
+ inputSchema: inputSchema3,
132
+ execute: execute3,
133
+ isReadOnly: false,
134
+ requiresConfirmation: true,
135
+ timeout: 1e4
136
+ };
137
+
138
+ // src/write.ts
139
+ import * as fs3 from "fs/promises";
140
+ import * as path3 from "path";
141
+ import { z as z4 } from "zod";
142
+ var inputSchema4 = z4.object({
143
+ path: z4.string().describe("Absolute or relative file path to write"),
144
+ content: z4.string().describe("Content to write to the file")
145
+ });
146
+ async function execute4(input, ctx) {
147
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
148
+ const filePath = path3.resolve(ctx.workingDirectory, input.path);
149
+ if (!filePath.startsWith(ctx.workingDirectory + path3.sep) && filePath !== ctx.workingDirectory) {
150
+ return { content: `Error: path traversal denied \u2014 ${input.path} escapes working directory`, isError: true };
151
+ }
152
+ try {
153
+ await fs3.mkdir(path3.dirname(filePath), { recursive: true });
154
+ await fs3.writeFile(filePath, input.content, "utf-8");
155
+ return {
156
+ content: `Successfully wrote ${Buffer.byteLength(input.content)} bytes to ${filePath}`
157
+ };
158
+ } catch (err) {
159
+ const msg = err instanceof Error ? err.message : String(err);
160
+ return { content: `Error writing file: ${msg}`, isError: true };
161
+ }
162
+ }
163
+ var writeTool = {
164
+ name: "write",
165
+ description: "Write content to a file, creating parent directories as needed",
166
+ inputSchema: inputSchema4,
167
+ execute: execute4,
168
+ isReadOnly: false,
169
+ requiresConfirmation: true,
170
+ timeout: 1e4
171
+ };
172
+
173
+ // src/glob.ts
174
+ import fg from "fast-glob";
175
+ import { z as z5 } from "zod";
176
+ var MAX_RESULT_SIZE3 = 1e5;
177
+ var inputSchema5 = z5.object({
178
+ pattern: z5.string().describe("Glob pattern to match files (e.g. **/*.ts)"),
179
+ cwd: z5.string().optional().describe("Directory to search in")
180
+ });
181
+ async function execute5(input, ctx) {
182
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
183
+ const cwd = input.cwd ?? ctx.workingDirectory;
184
+ try {
185
+ const files = await fg(input.pattern, {
186
+ cwd,
187
+ dot: false,
188
+ ignore: ["**/node_modules/**", "**/.git/**"],
189
+ onlyFiles: true,
190
+ absolute: false
191
+ });
192
+ files.sort();
193
+ if (files.length === 0) {
194
+ return { content: "No files matched the pattern" };
195
+ }
196
+ const content = files.join("\n").slice(0, MAX_RESULT_SIZE3);
197
+ return { content, metadata: { matchCount: files.length } };
198
+ } catch (err) {
199
+ const msg = err instanceof Error ? err.message : String(err);
200
+ return { content: `Error searching files: ${msg}`, isError: true };
201
+ }
202
+ }
203
+ var globTool = {
204
+ name: "glob",
205
+ description: "Find files matching a glob pattern, excluding node_modules and .git",
206
+ inputSchema: inputSchema5,
207
+ execute: execute5,
208
+ isReadOnly: true,
209
+ timeout: 15e3
210
+ };
211
+
212
+ // src/grep.ts
213
+ import * as fs4 from "fs/promises";
214
+ import * as path4 from "path";
215
+ import { z as z6 } from "zod";
216
+ import fg2 from "fast-glob";
217
+ var MAX_RESULT_SIZE4 = 1e5;
218
+ var MAX_FILES = 5e3;
219
+ var inputSchema6 = z6.object({
220
+ pattern: z6.string().describe("Regex pattern to search for in file contents"),
221
+ path: z6.string().optional().describe("Directory or file to search in"),
222
+ glob: z6.string().optional().describe("Glob filter for files (e.g. *.ts)")
223
+ });
224
+ async function execute6(input, ctx) {
225
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
226
+ const searchPath = input.path ? path4.resolve(ctx.workingDirectory, input.path) : ctx.workingDirectory;
227
+ const globPattern = input.glob ?? "**/*";
228
+ try {
229
+ const regex = new RegExp(input.pattern);
230
+ const stat2 = await fs4.stat(searchPath);
231
+ let files;
232
+ if (stat2.isFile()) {
233
+ files = [searchPath];
234
+ } else {
235
+ files = await fg2(globPattern, {
236
+ cwd: searchPath,
237
+ absolute: true,
238
+ onlyFiles: true,
239
+ ignore: ["**/node_modules/**", "**/.git/**", "**/*.min.*"]
240
+ });
241
+ files = files.slice(0, MAX_FILES);
242
+ }
243
+ const matches = [];
244
+ let totalSize = 0;
245
+ for (const file of files) {
246
+ if (ctx.abortSignal.aborted) break;
247
+ try {
248
+ const content = await fs4.readFile(file, "utf-8");
249
+ const lines = content.split("\n");
250
+ for (let i = 0; i < lines.length; i++) {
251
+ if (regex.test(lines[i])) {
252
+ const rel = path4.relative(ctx.workingDirectory, file);
253
+ const line = `${rel}:${i + 1}: ${lines[i]}`;
254
+ totalSize += line.length;
255
+ if (totalSize > MAX_RESULT_SIZE4) break;
256
+ matches.push(line);
257
+ }
258
+ }
259
+ } catch {
260
+ }
261
+ if (totalSize > MAX_RESULT_SIZE4) break;
262
+ }
263
+ if (matches.length === 0) {
264
+ return { content: "No matches found" };
265
+ }
266
+ return { content: matches.join("\n"), metadata: { matchCount: matches.length } };
267
+ } catch (err) {
268
+ const msg = err instanceof Error ? err.message : String(err);
269
+ return { content: `Error searching: ${msg}`, isError: true };
270
+ }
271
+ }
272
+ var grepTool = {
273
+ name: "grep",
274
+ description: "Search file contents using regex, returning matching lines with file:line format",
275
+ inputSchema: inputSchema6,
276
+ execute: execute6,
277
+ isReadOnly: true,
278
+ timeout: 3e4
279
+ };
280
+
281
+ // src/web-fetch.ts
282
+ import { z as z7 } from "zod";
283
+ var MAX_RESULT_SIZE5 = 5e4;
284
+ function isPrivateUrl(urlStr) {
285
+ const url = new URL(urlStr);
286
+ const hostname = url.hostname;
287
+ const blocked = [
288
+ /^127\./,
289
+ /^10\./,
290
+ /^172\.(1[6-9]|2\d|3[01])\./,
291
+ /^192\.168\./,
292
+ /^169\.254\./,
293
+ /^0\./,
294
+ /^localhost$/i,
295
+ /^::1$/,
296
+ /^\[::1\]$/,
297
+ /^metadata\.google/,
298
+ /^169\.254\.169\.254$/
299
+ ];
300
+ return blocked.some((re) => re.test(hostname));
301
+ }
302
+ var inputSchema7 = z7.object({
303
+ url: z7.string().url().describe("URL to fetch"),
304
+ method: z7.string().optional().default("GET").describe("HTTP method"),
305
+ headers: z7.record(z7.string(), z7.string()).optional().describe("HTTP headers"),
306
+ body: z7.string().optional().describe("Request body")
307
+ });
308
+ async function execute7(input, ctx) {
309
+ if (ctx.abortSignal.aborted) return { content: "Aborted", isError: true };
310
+ try {
311
+ if (isPrivateUrl(input.url)) {
312
+ return { content: `Error: request to private/internal address denied \u2014 ${input.url}`, isError: true };
313
+ }
314
+ } catch {
315
+ return { content: `Error: invalid URL \u2014 ${input.url}`, isError: true };
316
+ }
317
+ try {
318
+ const res = await fetch(input.url, {
319
+ method: input.method,
320
+ headers: input.headers,
321
+ body: input.body,
322
+ signal: ctx.abortSignal
323
+ });
324
+ const text = await res.text();
325
+ const truncated = text.slice(0, MAX_RESULT_SIZE5);
326
+ const suffix = text.length > MAX_RESULT_SIZE5 ? "\n...(truncated)" : "";
327
+ const content = `HTTP ${res.status} ${res.statusText}
328
+
329
+ ${truncated}${suffix}`;
330
+ return {
331
+ content,
332
+ isError: res.status >= 400,
333
+ metadata: { status: res.status, headers: Object.fromEntries(res.headers.entries()) }
334
+ };
335
+ } catch (err) {
336
+ const msg = err instanceof Error ? err.message : String(err);
337
+ return { content: `Fetch error: ${msg}`, isError: true };
338
+ }
339
+ }
340
+ var webFetchTool = {
341
+ name: "web_fetch",
342
+ description: "Make HTTP requests and return the response body",
343
+ inputSchema: inputSchema7,
344
+ execute: execute7,
345
+ isReadOnly: false,
346
+ timeout: 3e4
347
+ };
348
+
349
+ // src/index.ts
350
+ var builtinTools = [
351
+ bashTool,
352
+ readTool,
353
+ editTool,
354
+ writeTool,
355
+ globTool,
356
+ grepTool,
357
+ webFetchTool
358
+ ];
359
+ export {
360
+ bashTool,
361
+ builtinTools,
362
+ editTool,
363
+ globTool,
364
+ grepTool,
365
+ readTool,
366
+ webFetchTool,
367
+ writeTool
368
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@claude-code-kit/tools",
3
+ "version": "0.2.0",
4
+ "description": "Built-in tool collection for claude-code-kit agent framework",
5
+ "license": "MIT",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.mjs",
17
+ "require": "./dist/index.js"
18
+ }
19
+ },
20
+ "dependencies": {
21
+ "fast-glob": "^3.3.0",
22
+ "zod": ">=3.20.0",
23
+ "@claude-code-kit/agent": "0.2.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "*",
27
+ "tsup": "*",
28
+ "typescript": "*"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/Minnzen/claude-code-kit.git",
33
+ "directory": "packages/tools"
34
+ },
35
+ "homepage": "https://github.com/Minnzen/claude-code-kit",
36
+ "bugs": "https://github.com/Minnzen/claude-code-kit/issues",
37
+ "keywords": [
38
+ "tools",
39
+ "agent",
40
+ "claude",
41
+ "bash",
42
+ "file",
43
+ "glob",
44
+ "grep"
45
+ ],
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "scripts": {
53
+ "build": "tsup",
54
+ "typecheck": "tsc --noEmit -p tsconfig.json",
55
+ "lint": "biome check src/"
56
+ }
57
+ }