Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ jobs:
- name: Type check
run: pnpm typecheck

- name: Build
run: pnpm build

- name: Test
run: pnpm test:run

- name: Build
run: pnpm build

6 changes: 3 additions & 3 deletions AGENTS.npm.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const result = await agent.generate({
## Key Behaviors

1. **Default sandbox is just-bash** - Install `just-bash` or provide your own sandbox
2. **Working directory defaults to `./workspace`** - All files written relative to `destination`
2. **Working directory defaults to `/workspace`** - All files written relative to `destination`
3. **Files are written before tools are returned** - Sandbox is pre-populated
4. **Tool descriptions include file list** - LLM sees available files in bash tool description
5. **No `stop()` method** - Sandbox lifecycle is managed externally
Expand Down Expand Up @@ -88,11 +88,11 @@ const { tools } = await createBashTool({ sandbox: existingSandbox });
const { tools } = await createBashTool({
onBeforeBashCall: ({ command }) => {
console.log("Running:", command);
// Return modified command or undefined to proceed unchanged
return undefined; // Or return { command: modifiedCommand } to change it
},
onAfterBashCall: ({ command, result }) => {
console.log(`Exit: ${result.exitCode}`);
// Return modified result or undefined to proceed unchanged
return undefined; // Or return { result: modifiedResult } to change it
},
});
```
Expand Down
70 changes: 68 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,80 @@ const customSandbox: Sandbox = {
// Your implementation here
return "";
},
async writeFile(path, content) {
// Your implementation here
async writeFiles(files) {
// Your implementation here - files is Array<{path, content}>
},
};

const { tools } = await createBashTool({ sandbox: customSandbox });
```

## Skills (Experimental)

Skills are modular capabilities that extend agent functionality. Each skill is a directory containing a `SKILL.md` file with instructions and optional scripts.

```typescript
import {
experimental_createSkillTool as createSkillTool,
createBashTool,
} from "bash-tool";
import { ToolLoopAgent } from "ai";

// Discover skills and get files to upload
const { loadSkill, files, instructions } = await createSkillTool({
skillsDirectory: "./skills",
});

// Create bash tool with skill files
const { tools } = await createBashTool({
files,
extraInstructions: instructions,
});

// Use both tools with an agent
const agent = new ToolLoopAgent({
model,
tools: { loadSkill, ...tools },
});
```

### Skill Directory Structure

```
skills/
├── csv/
│ ├── SKILL.md # Required: instructions with YAML frontmatter
│ ├── analyze.sh # Optional: scripts the agent can run
│ └── filter.sh
└── text/
├── SKILL.md
└── search.sh
```

### SKILL.md Format

```markdown
---
name: csv
description: Analyze and transform CSV files
---

# CSV Processing

Use `./skills/csv/analyze.sh <file>` to analyze a CSV file.
```

### How It Works

1. `createSkillTool` discovers skills and returns:
- `loadSkill` - Tool for the agent to load a skill's instructions on demand
- `files` - All skill files to pass to `createBashTool`
- `instructions` - Extra instructions listing available skills

2. The agent sees skill names in the `loadSkill` tool description
3. When the agent needs a skill, it calls `loadSkill("csv")` to get detailed instructions
4. The agent uses `bash` to run scripts from `./skills/csv/`

## AI Agent Instructions

For AI agents working with bash-tool, additional guidance is available in `AGENTS.md`:
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
"indentWidth": 2
},
"files": {
"includes": ["src/**", "*.json", "*.ts"]
"includes": ["src/**", "examples/**", "*.json", "*.ts"]
}
}
107 changes: 107 additions & 0 deletions examples/skills-tool/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Skills Tool Example

This example demonstrates how to use `createSkillTool` with AI SDK's `ToolLoopAgent` to give an AI agent modular capabilities (skills) that it can discover and use on demand.

## Overview

The example includes two bash-based skills:

- **csv** - Analyze and transform CSV files using awk, cut, sort
- **text** - Analyze and search text files using grep, sed, wc

## How It Works

1. `createSkillTool` discovers skills and returns their files
2. Files are passed to `createBashTool` for sandbox upload
3. The `ToolLoopAgent`:
- Sees available skills in the `loadSkill` tool description
- Calls `loadSkill("csv")` to get detailed instructions
- Uses `bash` to run the skill's scripts
- Loops until the task is complete

## Running the Example

```bash
# From the repository root
npx tsx examples/skills-tool/index.ts
```

Requires `ANTHROPIC_API_KEY` environment variable.

## Code Overview

```typescript
import { ToolLoopAgent } from "ai";
import {
experimental_createSkillTool as createSkillTool,
createBashTool,
} from "bash-tool";

// Discover skills and get files
const { loadSkill, skills, files, instructions } = await createSkillTool({
skillsDirectory: "./skills",
});

// Create bash tool with skill files
const { tools } = await createBashTool({
files,
extraInstructions: instructions,
});

// Create agent with both tools
const agent = new ToolLoopAgent({
model: "anthropic/claude-haiku-4.5",
tools: {
loadSkill,
bash: tools.bash,
},
});

// Run the agent
const result = await agent.generate({
prompt: "Analyze this CSV data...",
});
```

## Skill Structure

Each skill is a directory containing:

```
skills/
├── csv/
│ ├── SKILL.md # Instructions (YAML frontmatter + markdown)
│ ├── analyze.sh # Bash scripts
│ ├── filter.sh
│ ├── select.sh
│ └── sort.sh
└── text/
├── SKILL.md
├── stats.sh
├── search.sh
├── extract.sh
└── wordfreq.sh
```

## Creating Your Own Skills

1. Create a directory under `skills/`
2. Add a `SKILL.md` with frontmatter:
```yaml
---
name: my-skill
description: What this skill does
---

# Instructions for the AI

Explain how to use the scripts...
```
3. Add bash scripts that the AI can execute

## Key Concepts

- **Composable**: `createSkillTool` returns files, you control the sandbox via `createBashTool`
- **ToolLoopAgent**: AI SDK's agent that automatically loops through tool calls until done
- **Progressive disclosure**: The AI only sees skill names initially, loading full instructions on demand
- **Bash-only**: Scripts use standard Unix tools (awk, sed, grep, sort, etc.)
106 changes: 106 additions & 0 deletions examples/skills-tool/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/**
* Example: Using createSkillTool with AI SDK ToolLoopAgent
*
* This example demonstrates how to create an AI agent with skills
* that can process CSV and text files using bash tools.
*
* Run with: npx tsx examples/skills-tool/index.ts
*/

import path from "node:path";
import { ToolLoopAgent } from "ai";
import {
createBashTool,
experimental_createSkillTool as createSkillTool,
} from "../../src/index.js";

async function main() {
// Discover skills and get files to upload
const { loadSkill, skills, files, instructions } = await createSkillTool({
skillsDirectory: path.join(import.meta.dirname, "skills"),
});

console.log("Available skills:");
for (const skill of skills) {
console.log(` - ${skill.name}: ${skill.description}`);
}
console.log("");

// Create bash tool with skill files
const { tools } = await createBashTool({
files,
extraInstructions: instructions,
});

// Create the agent with skills
const agent = new ToolLoopAgent({
model: "anthropic/claude-haiku-4.5",
tools: {
loadSkill,
bash: tools.bash,
},
instructions: `You are a data processing assistant with access to skills.
Use loadSkill to discover how to use a skill, then use bash to run its scripts.
Skills are located at ./skills/<skill-name>/.`,
onStepFinish: ({ toolCalls, toolResults }) => {
if (toolCalls && toolCalls.length > 0) {
for (const call of toolCalls) {
console.log(`Tool: ${call.toolName}`);
if (call.toolName === "loadSkill" && "input" in call) {
const input = call.input as { skillName: string };
console.log(` Loading skill: ${input.skillName}`);
} else if (call.toolName === "bash" && "input" in call) {
const input = call.input as { command: string };
console.log(` Command: ${input.command}`);
}
}
}
if (toolResults && toolResults.length > 0) {
for (const result of toolResults) {
if (result.toolName === "bash" && "output" in result) {
const output = result.output as {
stdout: string;
exitCode: number;
};
if (output.stdout) {
console.log(` Output:\n${output.stdout.slice(0, 500)}`);
}
}
}
console.log("");
}
},
});

// Example prompt - the AI will discover and use skills as needed
const prompt = `
I have a CSV file with sales data. Here's the content:

date,product,quantity,price,region
2024-01-15,Widget A,100,29.99,North
2024-01-15,Widget B,50,49.99,South
2024-01-16,Widget A,75,29.99,East
2024-01-16,Widget C,200,19.99,North
2024-01-17,Widget B,30,49.99,West
2024-01-17,Widget A,150,29.99,North

Please:
1. First, write this data to a file called sales.csv
2. Use the csv skill to analyze the file
3. Filter to show only North region sales
4. Sort by quantity (highest first)
`;

console.log("Sending prompt to agent...\n");

const result = await agent.generate({ prompt });

console.log("\n=== Final Response ===\n");
console.log(result.text);

console.log("\n=== Agent Stats ===");
console.log(`Steps: ${result.steps.length}`);
console.log(`Total tokens: ${result.usage.totalTokens}`);
}

main().catch(console.error);
50 changes: 50 additions & 0 deletions examples/skills-tool/skills/csv/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
---
name: csv
description: Analyze and transform CSV data using bash tools
---

# CSV Processing Skill

Process CSV files using standard bash tools (awk, cut, sort, grep).

## Available Scripts

### analyze.sh
Get statistics and summary of a CSV file.
```bash
bash /skills/csv/analyze.sh data.csv
```

### filter.sh
Filter rows where a column matches a value.
```bash
bash /skills/csv/filter.sh data.csv <column_number> <value>
```

### select.sh
Select specific columns from CSV.
```bash
bash /skills/csv/select.sh data.csv <col1,col2,col3>
```

### sort.sh
Sort CSV by a column.
```bash
bash /skills/csv/sort.sh data.csv <column_number> [--numeric] [--reverse]
```

## Examples

```bash
# Show CSV summary
bash /skills/csv/analyze.sh sales.csv

# Filter where column 3 equals "active"
bash /skills/csv/filter.sh users.csv 3 active

# Select columns 1, 2, and 4
bash /skills/csv/select.sh data.csv 1,2,4

# Sort by column 2 numerically in reverse
bash /skills/csv/sort.sh data.csv 2 --numeric --reverse
```
Loading