Agentic coding CLI on Bun, Ink, and the AI SDK. Core: streamText loop with SDK-level tool approval so a denied call provably never executes; endpoint fallback for OpenAI reasoning models; retry with backoff. Tools: read/write/edit/glob/grep/bash, path-jailed, gitignore-aware, ripgrep with a JS fallback, binary rejection, live bash streaming. Agents: five variants crossing thinking level with tool restriction; plan and review withhold mutating tools from the model. Extensibility: frontmatter skills with on-demand bodies, plugin host with blocking hooks, MCP stdio and HTTP, read-only subagents. State: durable per-project memory, session task lists, session persistence, compaction that repairs provider-item dependencies. Distribution: five-platform cross-compiled binaries with checksums, install scripts, CI on three operating systems. 404 tests, typecheck clean.
173 lines
5.2 KiB
Markdown
173 lines
5.2 KiB
Markdown
# Tools
|
|
|
|
## The approval model
|
|
|
|
Three categories.
|
|
|
|
**Free.** Read-only, no prompt: `read_file`, `glob`, `grep`, `task`.
|
|
|
|
**Session tools.** Also free, because they touch the agent's own state rather than your
|
|
files: `todo_write`, `remember`, `recall`, `forget`, `skill`, `ask`, and anything a plugin
|
|
marks auto-approved.
|
|
|
|
**Gated.** Every call stops for a decision: `write_file`, `edit_file`, `bash`, and every
|
|
`mcp__*` tool.
|
|
|
|
```
|
|
edit_file wants to run
|
|
src/users.ts +2 -1
|
|
export function paginate(offset: number, total: number) {
|
|
- if (offset < total) return next();
|
|
+ if (offset <= total) return next();
|
|
}
|
|
y allow once | a always allow edit_file | n deny
|
|
```
|
|
|
|
`a` whitelists that tool for the rest of the session. `n` tells the model it was denied and
|
|
to ask what to do instead. `--yolo` skips all prompts.
|
|
|
|
**The guard runs before all of this.** It is not an approval — it is a refusal, and `--yolo`
|
|
does not reach it. See [plugins](plugins.md).
|
|
|
|
## File tools
|
|
|
|
### `read_file`
|
|
|
|
```
|
|
path file path relative to the workspace root
|
|
offset first line, 1-based
|
|
limit max lines, default 2000
|
|
```
|
|
|
|
Returns contents with 1-based line numbers. Refuses binaries: a NUL byte in the first 8 KB
|
|
means the file is not text, and a model that reads a 90 MB executable has burned its whole
|
|
context on nothing.
|
|
|
|
### `write_file`
|
|
|
|
```
|
|
path file path
|
|
content full contents
|
|
```
|
|
|
|
New files and full rewrites only. Creates parent directories.
|
|
|
|
### `edit_file`
|
|
|
|
```
|
|
path file path
|
|
oldString exact text to find, whitespace and indentation included
|
|
newString replacement
|
|
replaceAll replace every occurrence instead of requiring exactly one
|
|
```
|
|
|
|
`oldString` must match byte-for-byte and appear exactly once unless `replaceAll` is set.
|
|
An ambiguous match is an error naming the count, which pushes the model to add surrounding
|
|
context rather than guessing which occurrence it meant.
|
|
|
|
### `glob`
|
|
|
|
```
|
|
pattern e.g. "src/**/*.ts"
|
|
limit max paths, default 200
|
|
includeIgnored also return files git ignores
|
|
```
|
|
|
|
Walks the tree honouring `.gitignore` and `.shiroignore`, skipping `.git` and
|
|
`node_modules` unconditionally. Nested ignore files apply only within their own directory,
|
|
as git does. Returns posix paths relative to the workspace root.
|
|
|
|
### `grep`
|
|
|
|
```
|
|
pattern regex source
|
|
include glob limiting the search, default "**/*"
|
|
ignoreCase case-insensitive
|
|
includeIgnored also search files git ignores
|
|
```
|
|
|
|
Shells out to ripgrep when it is on PATH — roughly 15x faster on a real repo — and falls
|
|
back to a JavaScript walker otherwise. Output is `path:line: text` either way, so the model
|
|
sees one format regardless. Skips binaries. Caps at 200 hits.
|
|
|
|
### `bash`
|
|
|
|
```
|
|
command shell command
|
|
timeout ms, default 120000, max 600000
|
|
```
|
|
|
|
Runs in the workspace root through `bash -lc` or `cmd /c`. Output streams live to the panel
|
|
above the input rather than appearing all at once when the command exits — a two-minute test
|
|
run is otherwise indistinguishable from a hang. Both pipes are drained concurrently, since a
|
|
command that fills one while you block on the other deadlocks.
|
|
|
|
Returns exit code, stdout, stderr, and a note if a signal killed it.
|
|
|
|
## Agent tools
|
|
|
|
### `task`
|
|
|
|
```
|
|
description short label shown to you
|
|
prompt self-contained instructions
|
|
kind "explore" (default) or "review"
|
|
```
|
|
|
|
Spawns a read-only subagent with `read_file`, `glob`, and `grep` only. It returns one
|
|
report, so the parent pays for findings rather than the whole search transcript. It sees
|
|
none of the parent conversation, so its prompt has to stand alone.
|
|
|
|
`explore` finds and reports. `review` critiques code in severity order. Progress streams to
|
|
the subagent panel.
|
|
|
|
### `ask`
|
|
|
|
```
|
|
question one specific question
|
|
options choices, recommendation first, each with an optional detail
|
|
multiple allow more than one
|
|
```
|
|
|
|
Stops the turn and puts the question on screen. With options it is a picker; without, free
|
|
text. `esc` skips, which tells the model to decide and state its assumption.
|
|
|
|
Withheld entirely in headless mode — a question with no one to answer it would hang.
|
|
|
|
### `todo_write`
|
|
|
|
```
|
|
todos the complete list: content, status, optional note
|
|
```
|
|
|
|
Statuses: `pending`, `in_progress`, `done`, `blocked`. Send the whole list each time; it
|
|
replaces the previous one. Warns when more than one task is `in_progress`, when nothing is
|
|
`in_progress` while work remains, or when a `blocked` task has no note.
|
|
|
|
### `remember`, `recall`, `forget`
|
|
|
|
Durable per-project notes. See [memory](memory.md).
|
|
|
|
### `skill`
|
|
|
|
```
|
|
name skill name from the catalogue
|
|
```
|
|
|
|
Loads the body of a skill. See [skills](skills.md).
|
|
|
|
## Path safety
|
|
|
|
Every path a tool receives goes through a jail: resolved against the workspace root, then
|
|
checked that it did not escape. `../../etc/passwd` and absolute paths outside the root are
|
|
both refused before any filesystem call.
|
|
|
|
The model's output is a trust boundary. It can emit any string, so the check happens on
|
|
every call rather than being assumed.
|
|
|
|
## Output caps
|
|
|
|
Any single tool result is truncated at 30,000 characters with a note saying how much was
|
|
cut. `grep` stops at 200 hits, `glob` at 200 paths, `read_file` at 2000 lines by default.
|
|
Without caps one `grep` for `function` can end a session.
|