File operations, shell execution, code search, web fetching, and more -- everything an AI agent needs to interact with a codebase and system.
- 22 production-ready tools -- bash, grep, glob, read, edit, write, web-fetch, web-search, tool-search, memory, multi-edit, diff, task-create, task-get, task-update, task-list, lsp, http-request, context-compaction, ask-user, sleep
- Vercel AI SDK compatible -- works with
generateText(),streamText(), and any AI SDK provider (OpenAI, Anthropic, Google, etc.) - Factory + default pattern --
createBash({ cwd: '/my/project' })for custom config, or just usebashwith zero config - Dual ESM/CJS -- works everywhere with proper
exportsmap - TypeScript-first -- full type declarations, strict mode, no
any - Never throws -- every
execute()returns a descriptive error string instead of throwing - Tree-shakeable -- 22 subpath exports, only import what you need
npm install agentool ai zod
aiandzodare peer dependencies. You also need an AI SDK provider like@ai-sdk/openai,@ai-sdk/anthropic, etc.
- Node.js >= 18
- ripgrep (
rg) -- required forgrepandglobtools
# macOS
brew install ripgrep
# Ubuntu/Debian
sudo apt install ripgrep
# Windows
choco install ripgrepimport { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { bash, read, edit, glob, grep } from 'agentool';
const { text } = await generateText({
model: openai('gpt-4o'),
tools: { bash, read, edit, glob, grep },
maxSteps: 10,
prompt: 'Find all TypeScript files with TODO comments and list them',
});// Only import what you need -- no unused code loaded
import { bash } from 'agentool/bash';
import { grep } from 'agentool/grep';Use factory functions when you need to configure tools (custom cwd, timeouts, etc.):
import { createBash } from 'agentool/bash';
import { createRead } from 'agentool/read';
const myBash = createBash({ cwd: '/my/project', timeout: 60000 });
const myRead = createRead({ cwd: '/my/project' });
// Use them like any other tool
const { text } = await generateText({
model: openai('gpt-4o'),
tools: { bash: myBash, read: myRead },
maxSteps: 10,
prompt: 'List all files and read package.json',
});Execute shell commands with timeout and signal handling.
import { bash } from 'agentool/bash';
const result = await bash.execute(
{ command: 'echo hello && ls -la' },
{ toolCallId: 'id', messages: [] },
);With custom config:
import { createBash } from 'agentool/bash';
const bash = createBash({
cwd: '/my/project',
timeout: 60000, // 60s timeout (default: 120s)
shell: '/bin/zsh', // custom shell
});Parameters: command (string), timeout? (number), description? (string)
Read files with line numbers, offset/limit pagination, and dual-path reading (fast for <10MB, streaming for larger).
import { read } from 'agentool/read';
// Read entire file
const content = await read.execute(
{ file_path: '/app/src/index.ts' },
{ toolCallId: 'id', messages: [] },
);
// Returns: "1\texport function hello() {\n2\t return 'world';\n3\t}"
// Read specific range
const range = await read.execute(
{ file_path: '/app/src/index.ts', offset: 10, limit: 20 },
{ toolCallId: 'id', messages: [] },
);Parameters: file_path (string), offset? (number), limit? (number)
Exact string replacement with curly-quote normalization fallback.
import { edit } from 'agentool/edit';
const result = await edit.execute(
{
file_path: '/app/src/config.ts',
old_string: 'const PORT = 3000;',
new_string: 'const PORT = 8080;',
},
{ toolCallId: 'id', messages: [] },
);
// Replace all occurrences
const resultAll = await edit.execute(
{
file_path: '/app/src/config.ts',
old_string: 'localhost',
new_string: '0.0.0.0',
replace_all: true,
},
{ toolCallId: 'id', messages: [] },
);Parameters: file_path (string), old_string (string), new_string (string), replace_all? (boolean)
Write files with automatic parent directory creation.
import { write } from 'agentool/write';
const result = await write.execute(
{
file_path: '/app/src/utils/helpers.ts',
content: 'export function add(a: number, b: number) {\n return a + b;\n}\n',
},
{ toolCallId: 'id', messages: [] },
);
// Returns: "Created file: /app/src/utils/helpers.ts (62 bytes)"Parameters: file_path (string), content (string)
Search file contents with ripgrep. Three output modes, context lines, pagination.
import { grep } from 'agentool/grep';
// Find matching lines with context
const content = await grep.execute(
{ pattern: 'TODO|FIXME', output_mode: 'content', '-C': 2 },
{ toolCallId: 'id', messages: [] },
);
// List files containing matches (sorted by mtime)
const files = await grep.execute(
{ pattern: 'import.*react', output_mode: 'files_with_matches', glob: '*.tsx' },
{ toolCallId: 'id', messages: [] },
);
// Count matches per file
const counts = await grep.execute(
{ pattern: 'console\\.log', output_mode: 'count' },
{ toolCallId: 'id', messages: [] },
);Parameters: pattern (string), path? (string), output_mode? ('content' | 'files_with_matches' | 'count'), glob? (string), type? (string), -i? (boolean), -n? (boolean), -A? (number), -B? (number), -C? / context? (number), head_limit? (number), offset? (number), multiline? (boolean)
Find files by pattern with ripgrep, sorted by modification time.
import { glob } from 'agentool/glob';
const result = await glob.execute(
{ pattern: '**/*.test.ts' },
{ toolCallId: 'id', messages: [] },
);
// Returns: "Found 27 files\n/app/tests/unit/bash.test.ts\n..."
// Search in specific directory
const result2 = await glob.execute(
{ pattern: '*.json', path: '/app/config' },
{ toolCallId: 'id', messages: [] },
);Parameters: pattern (string), path? (string)
Atomically apply multiple edits to a single file. All succeed or none are applied.
import { multiEdit } from 'agentool/multi-edit';
const result = await multiEdit.execute(
{
file_path: '/app/src/config.ts',
edits: [
{ old_string: 'const PORT = 3000;', new_string: 'const PORT = 8080;' },
{ old_string: "const HOST = 'localhost';", new_string: "const HOST = '0.0.0.0';" },
],
},
{ toolCallId: 'id', messages: [] },
);Parameters: file_path (string), edits (array of { old_string, new_string })
Generate unified diffs between files or strings.
import { diff } from 'agentool/diff';
// Compare two files
const fileDiff = await diff.execute(
{ file_path: '/app/old.ts', other_file_path: '/app/new.ts' },
{ toolCallId: 'id', messages: [] },
);
// Compare strings
const stringDiff = await diff.execute(
{ old_content: 'hello world', new_content: 'hello universe' },
{ toolCallId: 'id', messages: [] },
);Parameters: file_path? (string), other_file_path? (string), old_content? (string), new_content? (string)
Fetch URLs with automatic HTML-to-markdown conversion.
import { webFetch } from 'agentool/web-fetch';
const result = await webFetch.execute(
{ url: 'https://example.com' },
{ toolCallId: 'id', messages: [] },
);
// Returns markdown content (HTML converted via Turndown, truncated at 100K chars)Parameters: url (string, must be valid URL)
Search the web with a callback-based implementation (bring your own search provider).
import { createWebSearch } from 'agentool/web-search';
const webSearch = createWebSearch({
onSearch: async (query, { allowed_domains, blocked_domains }) => {
// Use Tavily, SerpAPI, Google, or any search provider
const results = await mySearchProvider.search(query, { allowed_domains, blocked_domains });
return results.map(r => `${r.title}: ${r.url}\n${r.snippet}`).join('\n\n');
},
});
const result = await webSearch.execute(
{ query: 'TypeScript best practices 2024', allowed_domains: ['typescript-eslint.io'] },
{ toolCallId: 'id', messages: [] },
);Parameters: query (string, min 2 chars), allowed_domains? (string[]), blocked_domains? (string[])
Search through a registry of available tools by name or keyword.
import { createToolSearch } from 'agentool/tool-search';
const toolSearch = createToolSearch({
tools: {
bash: { description: 'Execute shell commands' },
grep: { description: 'Search file contents with regex' },
read: { description: 'Read file contents' },
},
});
const result = await toolSearch.execute(
{ query: 'file', max_results: 3 },
{ toolCallId: 'id', messages: [] },
);
// Returns matching tools sorted by relevanceParameters: query (string), max_results? (number, default 5)
Make raw HTTP requests without markdown conversion.
import { httpRequest } from 'agentool/http-request';
const result = await httpRequest.execute(
{
method: 'POST',
url: 'https://api.example.com/data',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' }),
timeout: 5000,
},
{ toolCallId: 'id', messages: [] },
);
// Returns JSON: { status, statusText, headers, body }Parameters: method ('GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD'), url (string), headers? (object), body? (string), timeout? (number)
File-based key-value store for persistent agent memory.
import { memory } from 'agentool/memory';
// Write
await memory.execute(
{ action: 'write', key: 'user-prefs', content: 'Prefers dark mode' },
{ toolCallId: 'id', messages: [] },
);
// Read
const data = await memory.execute(
{ action: 'read', key: 'user-prefs' },
{ toolCallId: 'id', messages: [] },
);
// List all keys
const keys = await memory.execute(
{ action: 'list' },
{ toolCallId: 'id', messages: [] },
);
// Delete
await memory.execute(
{ action: 'delete', key: 'user-prefs' },
{ toolCallId: 'id', messages: [] },
);Parameters: action ('read' | 'write' | 'list' | 'delete'), key? (string), content? (string)
Create a new task with subject, description, and optional metadata.
import { taskCreate } from 'agentool/task-create';
const result = await taskCreate.execute(
{ subject: 'Fix login bug', description: 'Auth fails on refresh', metadata: { priority: 'high' } },
{ toolCallId: 'id', messages: [] },
);Parameters: subject (string), description (string), metadata? (Record<string, unknown>)
Retrieve a task by ID to see full details.
import { taskGet } from 'agentool/task-get';
const result = await taskGet.execute(
{ taskId: 'abc123' },
{ toolCallId: 'id', messages: [] },
);Parameters: taskId (string)
Update a task's status, owner, metadata, and dependency relationships.
import { taskUpdate } from 'agentool/task-update';
// Update status and owner
await taskUpdate.execute(
{ taskId: 'abc123', status: 'in_progress', owner: 'agent-1' },
{ toolCallId: 'id', messages: [] },
);
// Add dependencies and merge metadata (null deletes a key)
await taskUpdate.execute(
{ taskId: 'abc123', addBlockedBy: ['def456'], metadata: { priority: null, notes: 'reviewed' } },
{ toolCallId: 'id', messages: [] },
);Parameters: taskId (string), subject? (string), description? (string), status? ('pending' | 'in_progress' | 'completed' | 'deleted'), owner? (string), activeForm? (string), addBlocks? (string[]), addBlockedBy? (string[]), metadata? (Record<string, unknown>)
List all non-deleted tasks with status and dependencies.
import { taskList } from 'agentool/task-list';
const result = await taskList.execute(
{},
{ toolCallId: 'id', messages: [] },
);Parameters: none
Language Server Protocol operations for code intelligence.
import { createLsp } from 'agentool/lsp';
const lsp = createLsp({
servers: {
'.ts': { command: 'typescript-language-server', args: ['--stdio'] },
'.py': { command: 'pylsp' },
},
});
const result = await lsp.execute(
{ operation: 'goToDefinition', filePath: 'src/index.ts', line: 10, character: 5 },
{ toolCallId: 'id', messages: [] },
);Parameters: operation ('goToDefinition' | 'findReferences' | 'hover' | 'documentSymbol' | 'workspaceSymbol' | 'goToImplementation' | 'prepareCallHierarchy' | 'incomingCalls' | 'outgoingCalls'), filePath (string), line (number, 1-based), character (number, 1-based)
Compact conversation history to fit within token budgets.
import { createContextCompaction } from 'agentool/context-compaction';
const compact = createContextCompaction({
maxTokens: 4096,
summarize: async (messages) => {
// Call your LLM to summarize
return 'Summary of previous conversation...';
},
});
const result = await compact.execute(
{
messages: [
{ role: 'user', content: 'Long conversation...' },
{ role: 'assistant', content: 'Long response...' },
],
},
{ toolCallId: 'id', messages: [] },
);Parameters: messages (array of { role, content }), maxTokens? (number)
Prompt the user for input during agent execution.
import { createAskUser } from 'agentool/ask-user';
const askUser = createAskUser({
onQuestion: async (question, options) => {
// Your UI logic to prompt the user
return 'User response here';
},
});
const answer = await askUser.execute(
{ question: 'Which database should I use?', options: ['PostgreSQL', 'MySQL', 'SQLite'] },
{ toolCallId: 'id', messages: [] },
);Parameters: question (string), options? (string[])
Pause execution for rate limiting or polling intervals.
import { sleep } from 'agentool/sleep';
const result = await sleep.execute(
{ durationMs: 2000, reason: 'Waiting for deployment' },
{ toolCallId: 'id', messages: [] },
);
// Returns: "Slept for 2001ms. Reason: Waiting for deployment"Parameters: durationMs (number, max 300000), reason? (string)
Every tool follows the factory + default pattern:
// Default instance -- uses process.cwd(), default timeouts
import { bash } from 'agentool/bash';
// Custom instance -- configure cwd, timeouts, and tool-specific options
import { createBash } from 'agentool/bash';
const myBash = createBash({
cwd: '/my/project',
timeout: 60000,
shell: '/bin/zsh',
});All tools accept BaseToolConfig:
| Option | Type | Default | Description |
|---|---|---|---|
cwd |
string |
process.cwd() |
Working directory for file operations |
Tools that support timeouts extend TimeoutConfig:
| Option | Type | Default | Description |
|---|---|---|---|
timeout |
number |
varies | Timeout in milliseconds |
| Tool | Extra Config |
|---|---|
bash |
shell?: string -- shell binary path |
read |
maxLines?: number -- max lines to return (default: 2000) |
memory |
memoryDir?: string -- storage directory |
task-create |
tasksFile?: string -- JSON file path |
task-get |
tasksFile?: string -- JSON file path |
task-update |
tasksFile?: string -- JSON file path |
task-list |
tasksFile?: string -- JSON file path |
web-search |
onSearch?: (query, opts) => Promise<string> -- search callback |
tool-search |
tools?: Record<string, { description }> -- tool registry |
lsp |
servers?: Record<string, LspServerConfig> -- LSP servers by file extension |
http-request |
defaultHeaders?: Record<string, string> -- headers merged into every request |
web-fetch |
maxContentLength?: number, userAgent?: string |
context-compaction |
summarize?: (messages) => Promise<string>, maxTokens?: number |
ask-user |
onQuestion?: (question, options?) => Promise<string> |
sleep |
maxDuration?: number -- cap in ms (default: 300000) |
Every tool's execute() catches errors internally and returns a descriptive string -- it never throws:
const result = await read.execute(
{ file_path: '/nonexistent/file.ts' },
{ toolCallId: 'id', messages: [] },
);
// Returns: "Error [read]: Failed to read file: ENOENT: no such file or directory..."Error strings follow the format: Error [tool-name]: {description} with actionable context to help the AI model recover.
import {
bash, createBash,
read, createRead,
edit, createEdit,
write, createWrite,
grep, createGrep,
glob, createGlob,
webFetch, createWebFetch,
webSearch, createWebSearch,
toolSearch, createToolSearch,
httpRequest, createHttpRequest,
memory, createMemory,
multiEdit, createMultiEdit,
diff, createDiff,
taskCreate, createTaskCreate,
taskGet, createTaskGet,
taskUpdate, createTaskUpdate,
taskList, createTaskList,
lsp, createLsp,
contextCompaction, createContextCompaction,
askUser, createAskUser,
sleep, createSleep,
} from 'agentool';import { bash } from 'agentool/bash';
import { grep } from 'agentool/grep';
import { glob } from 'agentool/glob';
import { read } from 'agentool/read';
import { edit } from 'agentool/edit';
import { write } from 'agentool/write';
import { webFetch } from 'agentool/web-fetch';
import { httpRequest } from 'agentool/http-request';
import { memory } from 'agentool/memory';
import { multiEdit } from 'agentool/multi-edit';
import { diff } from 'agentool/diff';
import { taskCreate } from 'agentool/task-create';
import { taskGet } from 'agentool/task-get';
import { taskUpdate } from 'agentool/task-update';
import { taskList } from 'agentool/task-list';
import { webSearch } from 'agentool/web-search';
import { toolSearch } from 'agentool/tool-search';
import { lsp } from 'agentool/lsp';
import { contextCompaction } from 'agentool/context-compaction';
import { askUser } from 'agentool/ask-user';
import { sleep } from 'agentool/sleep';import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { bash, read, edit, write, glob, grep, diff } from 'agentool';
const { text, steps } = await generateText({
model: openai('gpt-4o'),
tools: { bash, read, edit, write, glob, grep, diff },
maxSteps: 20,
system: `You are a coding assistant. You can read, search, edit, and write files.
Always read a file before editing it. Use grep to search for patterns.
Use glob to find files. Use bash for git, build, and test commands.`,
prompt: 'Find all console.log statements in src/ and replace them with proper logger calls',
});
console.log(`Completed in ${steps.length} steps`);
console.log(text);| Dependency | Version | Required |
|---|---|---|
| Node.js | >= 18 | Yes |
ai (Vercel AI SDK) |
>= 4.0.0 | Peer dependency |
zod |
>= 3.23.0 | Peer dependency |
ripgrep (rg) |
any | For grep/glob tools |