# Bun

> Reference for Bun runtime APIs used in this project. Use this skill whenever writing code that runs shell commands (Bun.$ or Bun.spawn), reading/writing files (Bun.file/Bun.write), running tests (bun test), or configuring the monorepo (workspaces, bunfig.toml). Prefer Bun APIs over Node.js equivalents — use Bun.$ instead of child_process, Bun.file() instead of fs.readFile, bun:test instead of jes…

- **Type:** Skill
- **Install:** `agentstack add skill-nklisch-skilltap-bun`
- **Verified:** Pending review
- **Seller:** [nklisch](https://agentstack.voostack.com/s/nklisch)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [nklisch](https://github.com/nklisch)
- **Source:** https://github.com/nklisch/skilltap/tree/main/.agents/skills/bun
- **Website:** https://skilltap.dev/

## Install

```sh
agentstack add skill-nklisch-skilltap-bun
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Bun Runtime Reference

This project uses Bun as its runtime. Prefer Bun-native APIs over Node.js equivalents.

## Bun.$ — Shell API

The primary way to run external commands. Cross-platform, auto-escapes interpolated values, throws on non-zero exit by default.

```typescript
import { $ } from "bun"
```

### Running commands

```typescript
// Simple command — prints to stdout by default
await $`git clone --depth 1 ${url} ${dest}`

// Capture output as string (.text() auto-quiets)
const sha = await $`git rev-parse HEAD`.cwd(dir).text()
// sha === "\n"

// Capture output as trimmed string
const sha = (await $`git rev-parse HEAD`.cwd(dir).text()).trim()

// Capture as JSON
const result = await $`echo '{"ok": true}'`.json()

// Read output line-by-line
for await (const line of $`git log --oneline -5`.cwd(dir).lines()) {
  console.log(line)
}

// Capture stdout and stderr as Buffers
const { stdout, stderr } = await $`git status`.cwd(dir).quiet()
```

### Error handling

Non-zero exit codes throw `ShellError` by default:

```typescript
try {
  await $`git clone ${url} ${dest}`
} catch (err) {
  console.error(`Exit code: ${err.exitCode}`)
  console.error(err.stderr.toString())
}
```

Use `.nothrow()` to handle exit codes manually:

```typescript
const { exitCode, stdout, stderr } = await $`git diff --quiet`.cwd(dir).nothrow().quiet()
if (exitCode !== 0) {
  // There are unstaged changes
}
```

### Setting cwd and env

```typescript
// Per-command
await $`git status`.cwd("/path/to/repo")
await $`echo $MY_VAR`.env({ ...process.env, MY_VAR: "value" })

// Global defaults
$.cwd("/default/dir")
$.env({ ...process.env, GIT_TERMINAL_PROMPT: "0" })
```

### Piping and redirection

```typescript
// Pipe between commands
const count = await $`git log --oneline | wc -l`.cwd(dir).text()

// Redirect to file
await $`git diff > ${Bun.file("changes.patch")}`

// Redirect stderr to stdout
const output = await $`git clone ${url} 2>&1`.text()
```

### Interpolation safety

All interpolated values are auto-escaped — no shell injection:

```typescript
const userInput = "foo; rm -rf /"
await $`echo ${userInput}`  // SAFE: treated as single argument
```

To pass raw (unescaped) strings:

```typescript
await $`echo ${{ raw: "$(date)" }}`  // Executes command substitution
```

### Pattern: skilltap git.ts module

```typescript
import { $ } from "bun"
import type { Result } from "./types"

export async function clone(
  url: string,
  dest: string,
  opts?: { depth?: number; branch?: string }
): Promise> {
  try {
    const args = ["git", "clone"]
    if (opts?.depth) args.push("--depth", String(opts.depth))
    if (opts?.branch) args.push("--branch", opts.branch)
    args.push(url, dest)

    await $`${args}`.quiet()
    return { ok: true, value: undefined }
  } catch (err) {
    return {
      ok: false,
      error: new GitError(err.stderr?.toString() ?? err.message),
    }
  }
}

export async function revParse(dir: string): Promise> {
  try {
    const sha = (await $`git rev-parse HEAD`.cwd(dir).text()).trim()
    return { ok: true, value: sha }
  } catch (err) {
    return { ok: false, error: new GitError(err.message) }
  }
}
```

## Bun.spawn — Subprocess API

Lower-level than `$`. Use when you need fine-grained control over stdin/stdout streams, IPC, or synchronous execution.

```typescript
// Async
const proc = Bun.spawn(["git", "clone", url, dest], {
  cwd: "/tmp",
  env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
  stdout: "pipe",   // capture stdout as ReadableStream
  stderr: "pipe",   // capture stderr as ReadableStream
})

const exitCode = await proc.exited
const stdout = await proc.stdout.text()

// Sync (blocking — good for CLI tools)
const result = Bun.spawnSync(["git", "rev-parse", "HEAD"], { cwd: dir })
if (result.success) {
  console.log(result.stdout.toString().trim())
}
```

### When to use Bun.$ vs Bun.spawn

| Use case | API |
|----------|-----|
| Running git commands, shell scripts | `Bun.$` |
| Piping between commands | `Bun.$` |
| Simple command + capture output | `Bun.$` |
| Streaming stdin/stdout | `Bun.spawn` |
| IPC with child process | `Bun.spawn` |
| Sync execution (CLI tools) | `Bun.spawnSync` |
| Invoking agent CLIs for semantic scan | `Bun.spawn` (need stdout stream) |

## Bun.file / Bun.write — File I/O

Faster than Node's `fs` module. Returns a `BunFile` (lazy, doesn't read until consumed).

```typescript
// Read file as string
const content = await Bun.file("config.toml").text()

// Read as JSON
const data = await Bun.file("installed.json").json()

// Read as ArrayBuffer
const buf = await Bun.file("binary.dat").arrayBuffer()

// Check if file exists
const exists = await Bun.file("config.toml").exists()

// Get file size
const size = Bun.file("config.toml").size  // bytes

// Write string to file
await Bun.write("config.toml", tomlString)

// Write from another BunFile (copy)
await Bun.write("dest.txt", Bun.file("src.txt"))

// Write JSON
await Bun.write("data.json", JSON.stringify(data, null, 2))
```

### vs Node.js fs

| Node.js | Bun |
|---------|-----|
| `fs.readFileSync(path, "utf-8")` | `await Bun.file(path).text()` |
| `fs.writeFileSync(path, data)` | `await Bun.write(path, data)` |
| `fs.existsSync(path)` | `await Bun.file(path).exists()` |
| `fs.statSync(path).size` | `Bun.file(path).size` |

Node's `fs` still works in Bun — use it when you need `fs.mkdirSync`, `fs.readdirSync`, symlink operations, etc. that `Bun.file`/`Bun.write` don't cover.

## bun test — Test Runner

Jest-compatible test runner. Import from `"bun:test"`.

```typescript
import { describe, test, expect, beforeAll, afterAll, mock, spyOn } from "bun:test"
```

See `references/testing.md` for the full test API reference.

### Quick patterns

```typescript
import { describe, test, expect, beforeAll, afterAll } from "bun:test"

describe("scanner", () => {
  let tmpDir: string

  beforeAll(async () => {
    tmpDir = await createFixtureRepo()
  })

  afterAll(async () => {
    await fs.rm(tmpDir, { recursive: true })
  })

  test("finds root SKILL.md", async () => {
    const skills = await scanForSkills(tmpDir)
    expect(skills).toHaveLength(1)
    expect(skills[0].name).toBe("test-skill")
  })

  test("validates frontmatter", async () => {
    const skills = await scanForSkills(tmpDir)
    expect(skills[0].valid).toBe(true)
    expect(skills[0].description).toMatch(/test/)
  })

  test.todo("handles deep scan with confirmation")
})
```

### Running tests

```bash
bun test                          # Run all tests
bun test scanner                  # Filter by filename
bun test -t "validates"           # Filter by test name
bun test --watch                  # Watch mode
bun test --timeout 10000          # 10s per-test timeout
bun test --bail                   # Stop on first failure
```

## Workspaces

Bun uses the standard `package.json` `workspaces` field. Same as npm/yarn.

```json
// Root package.json
{
  "name": "skilltap-monorepo",
  "private": true,
  "workspaces": ["packages/*"]
}
```

Reference workspace packages with `workspace:*`:

```json
// packages/cli/package.json
{
  "name": "skilltap",
  "dependencies": {
    "@skilltap/core": "workspace:*"
  },
  "devDependencies": {
    "@skilltap/test-utils": "workspace:*"
  }
}
```

Import workspace packages normally:

```typescript
import { installSkill } from "@skilltap/core"
```

### bunfig.toml

Bun-specific configuration. Lives at project root.

```toml
[install]
# Use exact versions by default
exact = true

[test]
# Preload files before tests
preload = ["./packages/test-utils/src/setup.ts"]
# Default timeout
timeout = 10000
```

## bun build --compile

Compile to a standalone binary with no runtime dependency:

```bash
bun build --compile packages/cli/src/index.ts --outfile skilltap
```

The binary includes the Bun runtime — runs on machines without Bun installed.

### Cross-compile targets

```bash
bun build --compile --target=bun-linux-x64 packages/cli/src/index.ts --outfile skilltap-linux
bun build --compile --target=bun-darwin-arm64 packages/cli/src/index.ts --outfile skilltap-macos
```

## Other Bun APIs

### Hashing

```typescript
const hash = Bun.hash("some string")              // fast non-crypto hash
const sha = new Bun.CryptoHasher("sha256")
  .update("content")
  .digest("hex")
```

### Temporary files

```typescript
const tmpDir = `${import.meta.dir}/../.tmp/${crypto.randomUUID()}`
await fs.mkdir(tmpDir, { recursive: true })
// ... use tmpDir ...
await fs.rm(tmpDir, { recursive: true })
```

### Path utilities

```typescript
import { join, resolve, dirname, basename } from "node:path"
// Node path utilities work perfectly in Bun
```

### Environment

```typescript
Bun.env.HOME           // same as process.env.HOME
Bun.env.XDG_CONFIG_HOME // standard config path
```

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [nklisch](https://github.com/nklisch)
- **Source:** [nklisch/skilltap](https://github.com/nklisch/skilltap)
- **License:** MIT
- **Homepage:** https://skilltap.dev/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** yes
- **Shell / process execution:** yes
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-nklisch-skilltap-bun
- Seller: https://agentstack.voostack.com/s/nklisch
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
