{"id":45364436,"url":"https://github.com/jbreite/bashkit","last_synced_at":"2026-02-21T14:01:14.829Z","repository":{"id":330739568,"uuid":"1093160678","full_name":"jbreite/bashkit","owner":"jbreite","description":"Claude Agents SDK for the Vercel AI SDK","archived":false,"fork":false,"pushed_at":"2026-02-07T20:48:47.000Z","size":499,"stargazers_count":7,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-08T04:56:36.146Z","etag":null,"topics":["ai","ai-sdk","sandbox","vercel"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jbreite.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2025-11-10T01:50:14.000Z","updated_at":"2026-02-07T20:48:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jbreite/bashkit","commit_stats":null,"previous_names":["jbreite/bashkit"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/jbreite/bashkit","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreite%2Fbashkit","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreite%2Fbashkit/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreite%2Fbashkit/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreite%2Fbashkit/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jbreite","download_url":"https://codeload.github.com/jbreite/bashkit/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jbreite%2Fbashkit/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29682748,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T13:29:26.630Z","status":"ssl_error","status_checked_at":"2026-02-21T13:26:50.125Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-sdk","sandbox","vercel"],"created_at":"2026-02-21T14:00:43.354Z","updated_at":"2026-02-21T14:01:14.815Z","avatar_url":"https://github.com/jbreite.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# bashkit\n\n[![npm](https://img.shields.io/npm/v/bashkit)](https://www.npmjs.com/package/bashkit)\n\nAgentic coding tools for Vercel AI SDK. Give AI agents the ability to execute code, read/write files, and perform coding tasks in a sandboxed environment.\n\n## Overview\n\n`bashkit` provides a set of tools that work with the Vercel AI SDK to enable agentic coding capabilities. It gives AI models like Claude the ability to:\n\n- Execute bash commands in a persistent shell\n- Read files and list directories\n- Create and write files\n- Edit existing files with string replacement\n- Search for files by pattern\n- Search file contents with regex\n- Spawn sub-agents for complex tasks\n- Track task progress with todos\n- Search the web and fetch URLs\n- Load skills on-demand via the [Agent Skills](https://agentskills.io) standard\n\n## Breaking Changes in v0.4.0\n\n### Nullable Types for OpenAI Compatibility\n\nAll optional tool parameters now use `.nullable()` instead of `.optional()` in Zod schemas. This change enables compatibility with OpenAI's structured outputs, which require all properties to be in the `required` array.\n\n**What changed:**\n- Tool input types changed from `T | undefined` to `T | null`\n- Exported interfaces (`QuestionOption`, `StructuredQuestion`) use `T | null`\n- AI models will send explicit `null` values instead of omitting properties\n\n**Migration:**\n```typescript\n// Before v0.4.0\nconst option: QuestionOption = { label: \"test\", description: undefined };\n\n// v0.4.0+\nconst option: QuestionOption = { label: \"test\", description: null };\n```\n\n**Why this matters:**\n- Works with both OpenAI and Anthropic models\n- OpenAI structured outputs require nullable (not optional) fields\n- Anthropic/Claude handles nullable fields correctly\n- The `??` operator handles both `null` and `undefined`, so runtime behavior is unchanged\n\n## Installation\n\n```bash\nbun add bashkit ai zod\n```\n\nFor web tools, also install:\n```bash\nbun add parallel-web\n```\n\n## Quick Start\n\n### With Filesystem Access (Desktop Apps, Local Scripts, Servers)\n\nWhen you have direct filesystem access, use `LocalSandbox`:\n\n```typescript\nimport { createAgentTools, createLocalSandbox } from 'bashkit';\nimport { anthropic } from '@ai-sdk/anthropic';\nimport { generateText, stepCountIs } from 'ai';\n\n// Create a local sandbox (runs directly on your filesystem)\nconst sandbox = createLocalSandbox({ cwd: '/tmp/workspace' });\n\n// Create tools bound to the sandbox\nconst { tools } = createAgentTools(sandbox);\n\n// Use with Vercel AI SDK\nconst result = await generateText({\n  model: anthropic('claude-sonnet-4-5'),\n  tools,\n  prompt: 'Create a simple Express server in server.js',\n  stopWhen: stepCountIs(10),\n});\n\nconsole.log(result.text);\n\n// Cleanup\nawait sandbox.destroy();\n```\n\n### Without Filesystem Access (Web/Serverless Environments)\n\nWhen you're in a web or serverless environment without filesystem access, use `VercelSandbox` or `E2BSandbox`:\n\n```typescript\nimport { createAgentTools, createVercelSandbox } from 'bashkit';\nimport { anthropic } from '@ai-sdk/anthropic';\nimport { streamText, stepCountIs } from 'ai';\n\n// Create a Vercel sandbox (isolated Firecracker microVM)\n// Note: async - automatically sets up ripgrep for Grep tool\nconst sandbox = await createVercelSandbox({\n  runtime: 'node22',\n  resources: { vcpus: 2 },\n});\n\nconst { tools } = createAgentTools(sandbox);\n\nconst result = streamText({\n  model: anthropic('claude-sonnet-4-5'),\n  messages,\n  tools,\n  stopWhen: stepCountIs(10),\n});\n\n// Cleanup\nawait sandbox.destroy();\n```\n\n## Available Tools\n\n### Default Tools (always included)\n\n| Tool | Purpose | Key Inputs |\n|------|---------|------------|\n| `Bash` | Execute shell commands | `command`, `timeout?`, `description?` |\n| `Read` | Read files or list directories | `file_path`, `offset?`, `limit?` |\n| `Write` | Create/overwrite files | `file_path`, `content` |\n| `Edit` | Replace strings in files | `file_path`, `old_string`, `new_string`, `replace_all?` |\n| `Glob` | Find files by pattern | `pattern`, `path?` |\n| `Grep` | Search file contents | `pattern`, `path?`, `output_mode?`, `-i?`, `-C?` |\n\n### Optional Tools (via config)\n\n| Tool | Purpose | Config Key |\n|------|---------|------------|\n| `AskUser` | Ask user clarifying questions | `askUser: { onQuestion? }` |\n| `EnterPlanMode` | Enter planning/exploration mode | `planMode: true` |\n| `ExitPlanMode` | Exit planning mode with a plan | `planMode: true` |\n| `Skill` | Execute skills | `skill: { skills }` |\n| `WebSearch` | Search the web | `webSearch: { apiKey }` |\n| `WebFetch` | Fetch URL and process with AI | `webFetch: { apiKey, model }` |\n\n### Workflow Tools (created separately)\n\n| Tool | Purpose | Factory |\n|------|---------|---------|\n| `Task` | Spawn sub-agents | `createTaskTool({ model, tools, subagentTypes? })` |\n| `TodoWrite` | Track task progress | `createTodoWriteTool(state, config?, onUpdate?)` |\n\n### Web Tools (require `parallel-web` peer dependency)\n\n| Tool | Purpose | Factory |\n|------|---------|---------|\n| `WebSearch` | Search the web | `createWebSearchTool({ apiKey })` |\n| `WebFetch` | Fetch URL and process with AI | `createWebFetchTool({ apiKey, model })` |\n\n## Sandbox Types\n\n### LocalSandbox\n\nRuns commands directly on your filesystem. **Use when you have filesystem access** (desktop apps, local scripts, servers you control).\n\n```typescript\nimport { createLocalSandbox } from 'bashkit';\n\nconst sandbox = createLocalSandbox({ cwd: '/tmp/workspace' });\n```\n\n### VercelSandbox\n\nRuns in isolated Firecracker microVMs on Vercel's infrastructure. **Use when you don't have filesystem access** (web apps, serverless functions, browser environments).\n\n```typescript\nimport { createVercelSandbox } from 'bashkit';\n\n// Async - automatically installs ripgrep for Grep tool\nconst sandbox = await createVercelSandbox({\n  runtime: 'node22',\n  resources: { vcpus: 2 },\n  // ensureTools: true (default) - auto-setup ripgrep\n  // ensureTools: false - skip for faster startup if you don't need Grep\n});\n\n// Sandbox ID available immediately after creation\nconsole.log(sandbox.id); // Sandbox ID for reconnection\n\n// Later: reconnect to the same sandbox (fast - ripgrep already installed)\nconst reconnected = await createVercelSandbox({\n  sandboxId: 'existing-sandbox-id',\n});\n```\n\n### E2BSandbox\n\nRuns in E2B's cloud sandboxes. Requires `@e2b/code-interpreter` peer dependency. **Use when you don't have filesystem access** and need E2B's features.\n\n```typescript\nimport { createE2BSandbox } from 'bashkit';\n\n// Async - automatically installs ripgrep for Grep tool\nconst sandbox = await createE2BSandbox({\n  apiKey: process.env.E2B_API_KEY,\n  // ensureTools: true (default) - auto-setup ripgrep\n  // ensureTools: false - skip for faster startup if you don't need Grep\n});\n\n// Sandbox ID available immediately after creation\nconsole.log(sandbox.id); // \"sbx_abc123...\"\n\n// Later: reconnect to the same sandbox (fast - ripgrep already installed)\nconst reconnected = await createE2BSandbox({\n  apiKey: process.env.E2B_API_KEY,\n  sandboxId: 'sbx_abc123...', // Reconnect to existing sandbox\n});\n```\n\n## Configuration\n\nYou can configure tools with security restrictions and limits, and enable optional tools:\n\n```typescript\nconst { tools, planModeState } = createAgentTools(sandbox, {\n  // Enable optional tools\n  askUser: {\n    onQuestion: async (question) =\u003e {\n      // Return user's answer, or undefined to return awaiting_response\n      return await promptUser(question);\n    },\n  },\n  planMode: true, // Enables EnterPlanMode and ExitPlanMode\n  skill: {\n    skills: discoveredSkills, // From discoverSkills()\n  },\n  webSearch: {\n    apiKey: process.env.PARALLEL_API_KEY,\n  },\n  webFetch: {\n    apiKey: process.env.PARALLEL_API_KEY,\n    model: anthropic('claude-haiku-4'),\n  },\n\n  // Tool-specific config\n  tools: {\n    Bash: {\n      timeout: 30000,\n      blockedCommands: ['rm -rf', 'curl'],\n      maxOutputLength: 10000,\n    },\n    Read: {\n      allowedPaths: ['/workspace/**'],\n    },\n    Write: {\n      maxFileSize: 1_000_000, // 1MB limit\n    },\n  },\n});\n```\n\n### Configuration Options\n\n#### Global Config\n- `defaultTimeout` (number): Default timeout for all tools in milliseconds\n- `workingDirectory` (string): Default working directory for the sandbox\n\n#### Per-Tool Config\n- `timeout` (number): Tool-specific timeout\n- `maxFileSize` (number): Maximum file size in bytes (Write)\n- `maxOutputLength` (number): Maximum output length (Bash)\n- `allowedPaths` (string[]): Restrict file operations to specific paths\n- `blockedCommands` (string[]): Block commands containing these strings (Bash)\n\n#### AI SDK Tool Options (v6+)\n\nAll tools support AI SDK v6 tool options:\n\n```typescript\nconst { tools } = createAgentTools(sandbox, {\n  tools: {\n    Bash: {\n      timeout: 30000,\n      // AI SDK v6 options\n      needsApproval: true, // Require user approval before execution\n      strict: true, // Strict schema validation\n      providerOptions: { /* provider-specific options */ },\n    },\n    Write: {\n      // Dynamic approval based on input\n      needsApproval: async ({ file_path }) =\u003e {\n        return file_path.includes('package.json');\n      },\n    },\n  },\n});\n```\n\n- `needsApproval` (boolean | function): Require user approval before tool execution\n- `strict` (boolean): Enable strict schema validation\n- `providerOptions` (object): Provider-specific tool options\n\n## Sub-agents with Task Tool\n\nThe Task tool spawns new agents for complex subtasks:\n\n```typescript\nimport { createTaskTool } from 'bashkit';\n\nconst taskTool = createTaskTool({\n  model: anthropic('claude-sonnet-4-5'),\n  tools: sandboxTools,\n  subagentTypes: {\n    research: {\n      model: anthropic('claude-haiku-4'), // Cheaper model for research\n      systemPrompt: 'You are a research specialist. Find information only.',\n      tools: ['Read', 'Grep', 'Glob'], // Limited tools\n    },\n    coding: {\n      systemPrompt: 'You are a coding expert. Write clean code.',\n      tools: ['Read', 'Write', 'Edit', 'Bash'],\n    },\n  },\n});\n\n// Add to tools\nconst allTools = { ...sandboxTools, Task: taskTool };\n```\n\nThe parent agent calls Task like any other tool:\n```typescript\n// Agent decides to delegate:\n{ tool: \"Task\", args: {\n  description: \"Research API patterns\",\n  prompt: \"Find best practices for REST APIs\",\n  subagent_type: \"research\"\n}}\n```\n\n### Dynamic Agents\n\nYou can create custom agents on the fly by passing `system_prompt` and/or `tools` directly, without predefined subagent types:\n\n```typescript\n// Agent creates a specialized agent dynamically:\n{ tool: \"Task\", args: {\n  description: \"Analyze security vulnerabilities\",\n  prompt: \"Review the auth code for security issues\",\n  subagent_type: \"custom\",\n  system_prompt: \"You are a security expert. Focus on OWASP top 10 vulnerabilities.\",\n  tools: [\"Read\", \"Grep\", \"Glob\"]\n}}\n```\n\nThis is useful when:\n- The parent agent needs to create specialized agents based on context\n- You want agents to delegate with custom instructions\n- Predefined subagent types don't fit the task\n\n### Streaming Sub-agent Activity to UI\n\nPass a `streamWriter` to stream real-time sub-agent activity to the UI:\n\n```typescript\nimport { createUIMessageStream } from 'ai';\n\nconst stream = createUIMessageStream({\n  execute: async ({ writer }) =\u003e {\n    const taskTool = createTaskTool({\n      model,\n      tools: sandboxTools,\n      streamWriter: writer, // Enable real-time streaming\n      subagentTypes: { ... },\n    });\n\n    // Use with streamText\n    const result = streamText({\n      model,\n      tools: { Task: taskTool },\n      ...\n    });\n\n    writer.merge(result.toUIMessageStream());\n  },\n});\n```\n\nWhen `streamWriter` is provided:\n- Uses `streamText` internally (instead of `generateText`)\n- Emits `data-subagent` events to the UI stream:\n  - `start` - Sub-agent begins work\n  - `tool-call` - Each tool the sub-agent uses (with args)\n  - `done` - Sub-agent finished\n  - `complete` - Full messages array for UI access\n\nThese appear in `message.parts` on the client as `{ type: \"data-subagent\", data: SubagentEventData }`.\n\n**Important:** The TaskOutput returned to the lead agent does NOT include messages (to avoid context bloat). The UI accesses the full conversation via the streamed `complete` event.\n\n## Context Management\n\n### Conversation Compaction\n\nAutomatically summarize conversations when they exceed token limits:\n\n```typescript\nimport { compactConversation, MODEL_CONTEXT_LIMITS } from 'bashkit';\n\nlet compactState = { conversationSummary: '' };\n\nconst result = await compactConversation(messages, {\n  maxTokens: MODEL_CONTEXT_LIMITS['claude-sonnet-4-5'],\n  summarizerModel: anthropic('claude-haiku-4'), // Fast/cheap model\n  compactionThreshold: 0.85, // Trigger at 85% usage\n  protectRecentMessages: 10, // Keep last 10 messages intact\n}, compactState);\n\nmessages = result.messages;\ncompactState = result.state;\n```\n\n### Context Status Monitoring\n\nMonitor context usage and inject guidance to prevent agents from rushing:\n\n```typescript\nimport { getContextStatus, contextNeedsCompaction } from 'bashkit';\n\nconst status = getContextStatus(messages, MODEL_CONTEXT_LIMITS['claude-sonnet-4-5']);\n\nif (status.guidance) {\n  // Inject into system prompt\n  system = `${system}\\n\\n\u003ccontext_status\u003e${status.guidance}\u003c/context_status\u003e`;\n}\n\nif (contextNeedsCompaction(status)) {\n  // Trigger compaction\n  const compacted = await compactConversation(messages, config, state);\n}\n```\n\n## Tool Result Caching\n\nCache tool execution results to avoid repeated expensive operations:\n\n```typescript\nconst { tools } = createAgentTools(sandbox, {\n  // Enable caching with defaults (LRU, 5min TTL)\n  cache: true,\n});\n```\n\n### Cache Configuration Options\n\n```typescript\nconst { tools } = createAgentTools(sandbox, {\n  cache: {\n    // Custom TTL (default: 5 minutes)\n    ttl: 10 * 60 * 1000,\n\n    // Enable debug logging\n    debug: true,\n\n    // Per-tool control (defaults: Read, Glob, Grep, WebFetch, WebSearch)\n    Read: true,\n    Glob: true,\n    Grep: false,  // Disable for this tool\n\n    // Enable caching for tools not cached by default\n    Bash: true,  // Use with caution - has side effects\n  },\n});\n```\n\n### Default Cached Tools\n\nBy default, these read-only tools are cached when `cache: true`:\n- `Read` - File reading\n- `Glob` - File pattern matching\n- `Grep` - Content searching\n- `WebFetch` - URL fetching\n- `WebSearch` - Web searches\n\nTools with side effects (`Bash`, `Write`, `Edit`) are NOT cached by default but can be enabled.\n\n### Custom Cache Store\n\nImplement your own cache backend (e.g., Redis):\n\n```typescript\nimport type { CacheStore } from 'bashkit';\n\nconst redisStore: CacheStore = {\n  async get(key) {\n    const data = await redis.get(key);\n    return data ? JSON.parse(data) : undefined;\n  },\n  async set(key, entry) {\n    await redis.set(key, JSON.stringify(entry));\n  },\n  async delete(key) {\n    await redis.del(key);\n  },\n  async clear() {\n    await redis.flushdb();\n  },\n  size() {\n    return redis.dbsize();\n  },\n};\n\nconst { tools } = createAgentTools(sandbox, {\n  cache: redisStore,\n});\n```\n\n### Standalone Cached Wrapper\n\nWrap individual tools with caching:\n\n```typescript\nimport { cached, LRUCacheStore } from 'bashkit';\n\nconst cachedTool = cached(myTool, 'MyTool', {\n  ttl: 5 * 60 * 1000,\n  debug: true,\n});\n\n// Check cache stats\nconsole.log(await cachedTool.getStats());\n// { hits: 5, misses: 2, hitRate: 0.71, size: 2 }\n\n// Clear cache\nawait cachedTool.clearCache();\n```\n\n## Prompt Caching\n\nEnable Anthropic prompt caching to reduce costs on repeated prefixes:\n\n```typescript\nimport { wrapLanguageModel } from 'ai';\n// AI SDK v6+\nimport { anthropicPromptCacheMiddleware } from 'bashkit';\n// AI SDK v5\n// import { anthropicPromptCacheMiddlewareV2 } from 'bashkit';\n\nconst model = wrapLanguageModel({\n  model: anthropic('claude-sonnet-4-5'),\n  middleware: anthropicPromptCacheMiddleware,\n});\n\n// Check cache stats in result\nconsole.log({\n  cacheCreation: result.providerMetadata?.anthropic?.cacheCreationInputTokens,\n  cacheRead: result.providerMetadata?.anthropic?.cacheReadInputTokens,\n});\n```\n\n## Agent Skills\n\nbashkit supports the [Agent Skills](https://agentskills.io) standard - an open format for giving agents new capabilities and expertise. Skills are folders containing a `SKILL.md` file with instructions that agents can load on-demand.\n\n\u003e **Note:** Skill discovery is designed for **LocalSandbox** use cases where the agent has access to the user's filesystem. For cloud sandboxes (VercelSandbox/E2B), you would bundle skills with your app and inject them directly into the system prompt.\n\n### Progressive Disclosure\n\nSkills use progressive disclosure to keep context lean:\n1. **At startup**: Only skill metadata (name, description, path) is loaded (~50-100 tokens per skill)\n2. **On activation**: Agent reads the full `SKILL.md` via the Read tool when needed\n\n### Discovering Skills\n\nWhen using LocalSandbox, skills are discovered from:\n1. `.skills/` in the project directory (highest priority)\n2. `~/.bashkit/skills/` for user-global skills\n\nThis allows agents to pick up project-specific skills and user-installed skills automatically.\n\n```typescript\nimport { discoverSkills, skillsToXml } from 'bashkit';\n\n// Discover skills (metadata only - fast, low context)\nconst skills = await discoverSkills();\n\n// Or with custom paths\nconst skills = await discoverSkills({\n  paths: ['.skills', '/path/to/shared/skills'],\n  cwd: '/my/project',\n});\n```\n\n### Using Skills with Agents\n\nInject skill metadata into the system prompt using XML format (recommended for Claude):\n\n```typescript\nimport { discoverSkills, skillsToXml, createAgentTools, createLocalSandbox } from 'bashkit';\n\nconst skills = await discoverSkills();\nconst sandbox = createLocalSandbox({ cwd: '/tmp/workspace' });\nconst { tools } = createAgentTools(sandbox);\n\nconst result = await generateText({\n  model: anthropic('claude-sonnet-4-5'),\n  tools,\n  system: `You are a coding assistant.\n\n${skillsToXml(skills)}\n\nWhen a task matches a skill, use the Read tool to load its full instructions from the location path.`,\n  prompt: 'Extract text from invoice.pdf',\n  stopWhen: stepCountIs(10),\n});\n\n// Agent will call Read({ file_path: \"/path/to/.skills/pdf-processing/SKILL.md\" })\n// when it decides to use the pdf-processing skill\n```\n\n### Creating Skills\n\nCreate a folder with a `SKILL.md` file:\n\n```\n.skills/\n└── pdf-processing/\n    └── SKILL.md\n```\n\nThe `SKILL.md` file has YAML frontmatter and markdown instructions:\n\n```markdown\n---\nname: pdf-processing\ndescription: Extract text and tables from PDF files, fill forms, merge documents.\nlicense: MIT\ncompatibility: Requires poppler-utils\nmetadata:\n  author: my-org\n  version: \"1.0\"\n---\n\n# PDF Processing\n\n## When to use this skill\nUse when the user needs to work with PDF files...\n\n## How to extract text\n1. Use pdftotext for text extraction...\n```\n\n**Required fields:**\n- `name`: 1-64 chars, lowercase letters, numbers, and hyphens. Must match folder name.\n- `description`: 1-1024 chars. Describes when to use this skill.\n\n**Optional fields:**\n- `license`: License info\n- `compatibility`: Environment requirements\n- `metadata`: Arbitrary key-value pairs\n- `allowed-tools`: Space-delimited list of pre-approved tools (experimental)\n\n### Using Remote Skills\n\nFetch complete skill folders from GitHub repositories, including all scripts and resources:\n\n```typescript\nimport { fetchSkill, fetchSkills, setupAgentEnvironment } from 'bashkit';\n\n// Fetch a complete skill folder from Anthropic's official skills repo\nconst pdfSkill = await fetchSkill('anthropics/skills/pdf');\n// Returns a SkillBundle:\n// {\n//   name: 'pdf',\n//   files: {\n//     'SKILL.md': '...',\n//     'scripts/extract_text.py': '...',\n//     'forms.md': '...',\n//     // ... all files in the skill folder\n//   }\n// }\n\n// Or batch fetch multiple skills\nconst remoteSkills = await fetchSkills([\n  'anthropics/skills/pdf',\n  'anthropics/skills/web-research',\n]);\n// Returns: { 'pdf': SkillBundle, 'web-research': SkillBundle }\n\n// Use with setupAgentEnvironment - writes all files to .skills/\nconst config = {\n  skills: {\n    ...remoteSkills,                    // SkillBundles (all files)\n    'my-custom': myCustomSkillContent,  // Inline string (just SKILL.md)\n  },\n};\n\nconst { skills } = await setupAgentEnvironment(sandbox, config);\n// Creates: .skills/pdf/SKILL.md, .skills/pdf/scripts/*, etc.\n```\n\n**GitHub reference format:** `owner/repo/skillName`\n- `anthropics/skills/pdf` → fetches all files from `https://github.com/anthropics/skills/tree/main/skills/pdf`\n\n### API Reference\n\n```typescript\n// Discover skills from filesystem\ndiscoverSkills(options?: DiscoverSkillsOptions): Promise\u003cSkillMetadata[]\u003e\n\n// Fetch complete skill folders from GitHub\nfetchSkill(ref: string): Promise\u003cSkillBundle\u003e\nfetchSkills(refs: string[]): Promise\u003cRecord\u003cstring, SkillBundle\u003e\u003e\n\n// SkillBundle type\ninterface SkillBundle {\n  name: string;\n  files: Record\u003cstring, string\u003e;  // relative path -\u003e content\n}\n\n// Generate XML for system prompts\nskillsToXml(skills: SkillMetadata[]): string\n\n// Parse a single SKILL.md file\nparseSkillMetadata(content: string, skillPath: string): SkillMetadata\n```\n\n## Setting Up Agent Environments\n\nFor cloud sandboxes (VercelSandbox/E2B), use `setupAgentEnvironment` to create workspace directories and seed skills.\n\n```typescript\nimport {\n  setupAgentEnvironment,\n  skillsToXml,\n  createAgentTools,\n  createVercelSandbox\n} from 'bashkit';\n\n// Define your environment config\nconst config = {\n  workspace: {\n    notes: 'files/notes/',\n    outputs: 'files/outputs/',\n  },\n  skills: {\n    'web-research': `---\nname: web-research\ndescription: Research topics using web search and save findings.\n---\n# Web Research\nUse WebSearch to find information...\n`,\n  },\n};\n\n// Create sandbox and set up environment\nconst sandbox = createVercelSandbox({});\nconst { skills } = await setupAgentEnvironment(sandbox, config);\n\n// Build prompt using the same config (stays in sync!)\nconst systemPrompt = `You are a research assistant.\n\n**ENVIRONMENT:**\n- Save notes to: ${config.workspace.notes}\n- Save outputs to: ${config.workspace.outputs}\n\n${skillsToXml(skills)}\n`;\n\n// Create tools and run\nconst { tools } = createAgentTools(sandbox);\n\nconst result = await generateText({\n  model: anthropic('claude-sonnet-4-5'),\n  tools,\n  system: systemPrompt,\n  messages,\n});\n```\n\n### What setupAgentEnvironment Does\n\n1. **Creates workspace directories** - All paths in `config.workspace` are created\n2. **Seeds skills** - Skills in `config.skills` are written to `.skills/` directory\n3. **Returns skill metadata** - For use with `skillsToXml()`\n\n### Using with Subagents\n\nUse the same config for subagent prompts:\n\n```typescript\nconst taskTool = createTaskTool({\n  model,\n  tools,\n  subagentTypes: {\n    researcher: {\n      systemPrompt: `You are a researcher.\nSave findings to: ${config.workspace.notes}`,\n      tools: ['WebSearch', 'Write'],\n    },\n    'report-writer': {\n      systemPrompt: `Read from: ${config.workspace.notes}\nSave reports to: ${config.workspace.outputs}`,\n      tools: ['Read', 'Glob', 'Write'],\n    },\n  },\n});\n```\n\n## Sandbox Interface\n\n`bashkit` uses a bring-your-own-sandbox architecture. You can implement custom sandboxes:\n\n```typescript\ninterface Sandbox {\n  exec(command: string, options?: ExecOptions): Promise\u003cExecResult\u003e;\n  readFile(path: string): Promise\u003cstring\u003e;\n  writeFile(path: string, content: string): Promise\u003cvoid\u003e;\n  readDir(path: string): Promise\u003cstring[]\u003e;\n  fileExists(path: string): Promise\u003cboolean\u003e;\n  isDirectory(path: string): Promise\u003cboolean\u003e;\n  destroy(): Promise\u003cvoid\u003e;\n\n  // Optional: Sandbox ID for reconnection (cloud providers only)\n  readonly id?: string;\n\n  // Path to ripgrep binary (set by ensureSandboxTools)\n  rgPath?: string;\n}\n```\n\nThe `id` property is available on cloud sandboxes (E2B, Vercel) after creation. Use it to persist the sandbox ID and reconnect later.\n\nThe `rgPath` property is set by `ensureSandboxTools()` (called automatically during sandbox creation). It points to the ripgrep binary for the Grep tool. Supports x86_64 and ARM64 architectures.\n\n### Custom Sandbox Example\n\n```typescript\nimport type { Sandbox } from 'bashkit';\n\nclass DockerSandbox implements Sandbox {\n  // Your implementation\n  async exec(command: string) { /* ... */ }\n  async readFile(path: string) { /* ... */ }\n  // ... other methods\n}\n\nconst sandbox = new DockerSandbox();\nconst { tools } = createAgentTools(sandbox);\n```\n\n## Architecture\n\n```\n┌─────────────────────────────────────┐\n│   Your Next.js App / Script         │\n│                                     │\n│   ┌─────────────────────────────┐   │\n│   │  Vercel AI SDK              │   │\n│   │  (streamText/generateText)  │   │\n│   └──────────┬──────────────────┘   │\n│              │                      │\n│   ┌──────────▼──────────────────┐   │\n│   │  bashkit Tools              │   │\n│   │  (Bash, Read, Write, etc)   │   │\n│   └──────────┬──────────────────┘   │\n│              │                      │\n│   ┌──────────▼──────────────────┐   │\n│   │  Sandbox                    │   │\n│   │  (Local/Vercel/E2B/Custom)  │   │\n│   └─────────────────────────────┘   │\n└─────────────────────────────────────┘\n```\n\n**Flow:**\n1. User sends prompt to AI via Vercel AI SDK\n2. AI decides it needs to use a tool (e.g., create a file)\n3. Tool receives the call and executes via the Sandbox\n4. Result returns to AI, which continues or completes\n\n## Design Principles\n\n1. **Bring Your Own Sandbox**: Start with LocalSandbox for dev, swap in VercelSandbox/E2BSandbox for production\n2. **Type-Safe**: Full TypeScript support with proper type inference\n3. **Configurable**: Security controls and limits at the tool level\n4. **Vercel AI SDK Native**: Uses standard `tool()` format\n5. **Composable**: Mix and match tools, utilities, and middleware as needed\n\n## Examples\n\nSee the `examples/` directory for complete working examples:\n\n- `basic.ts` - Full example with todos, sub-agents, and prompt caching\n- `test-tools.ts` - Testing individual tools\n- `test-web-tools.ts` - Web search and fetch examples\n\n## API Reference\n\n### `createAgentTools(sandbox, config?)`\n\nCreates a set of agent tools bound to a sandbox instance.\n\n**Parameters:**\n- `sandbox` (Sandbox): Sandbox instance for code execution\n- `config` (AgentConfig, optional): Configuration for tools and web tools\n\n**Returns:** Object with tool definitions compatible with Vercel AI SDK\n\n### Sandbox Factories\n\n- `createLocalSandbox(config?)` - Local execution sandbox (sync)\n- `createVercelSandbox(config?)` - Vercel Firecracker sandbox (async, auto-installs ripgrep)\n- `createE2BSandbox(config?)` - E2B cloud sandbox (async, auto-installs ripgrep)\n- `ensureSandboxTools(sandbox)` - Manually setup tools (called automatically by default)\n\n### Workflow Tools\n\n- `createTaskTool(config)` - Spawn sub-agents for complex tasks\n- `createTodoWriteTool(state, config?, onUpdate?)` - Track task progress\n\n### Optional Tools (also available via config)\n\n- `createAskUserTool(onQuestion?)` - Ask user for clarification\n- `createEnterPlanModeTool(state)` - Enter planning/exploration mode\n- `createExitPlanModeTool(state, onPlanSubmit?)` - Exit planning mode with a plan\n- `createSkillTool(skills)` - Execute loaded skills\n\n### Utilities\n\n- `compactConversation(messages, config, state)` - Summarize long conversations\n- `getContextStatus(messages, maxTokens, config?)` - Monitor context usage\n- `pruneMessagesByTokens(messages, config?)` - Remove old messages\n- `estimateMessagesTokens(messages)` - Estimate token count\n\n### Skills\n\n- `discoverSkills(options?)` - Discover skills from filesystem (metadata only)\n- `skillsToXml(skills)` - Generate XML for system prompts\n- `parseSkillMetadata(content, path)` - Parse a SKILL.md file\n\n### Setup\n\n- `setupAgentEnvironment(sandbox, config)` - Set up workspace directories and seed skills\n\n### Middleware\n\n- `anthropicPromptCacheMiddleware` - Enable prompt caching for Anthropic models (AI SDK v6+)\n- `anthropicPromptCacheMiddlewareV2` - Enable prompt caching for Anthropic models (AI SDK v5)\n\n## Future Roadmap\n\nThe following features are planned for future releases:\n\n### Agent Profiles Loader\n\nLoad pre-configured subagent types from JSON/TypeScript configs:\n\n```json\n// .bashkit/agents.json\n{\n  \"subagentTypes\": {\n    \"research\": {\n      \"systemPrompt\": \"You are a research specialist...\",\n      \"tools\": [\"Read\", \"Grep\", \"Glob\", \"WebSearch\"]\n    },\n    \"coding\": {\n      \"systemPrompt\": \"You are a coding expert...\",\n      \"tools\": [\"Read\", \"Write\", \"Edit\", \"Bash\"]\n    }\n  }\n}\n```\n\nHelper function to auto-load profiles:\n```typescript\nimport { createTaskToolWithProfiles } from 'bashkit';\n\nconst taskTool = createTaskToolWithProfiles({\n  model,\n  tools,\n  profilesPath: '.bashkit/agents.json', // Auto-loads\n});\n```\n\nThis will make it easy to:\n- Share agent configurations across projects\n- Standardize agent patterns within teams\n- Quickly set up specialized agents for different tasks\n\n## Contributing\n\nContributions welcome! Please open an issue or PR.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbreite%2Fbashkit","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjbreite%2Fbashkit","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjbreite%2Fbashkit/lists"}