Minimalist coding agent with hashline-based editing.
Built for Bun — leverages Bun's native APIs for optimal performance.
See CHANGELOG.md for release history.
curl -fsSL https://raw.githubusercontent.com/sandst1/nav/main/install.sh | bashOr with wget:
wget -qO- https://raw.githubusercontent.com/sandst1/nav/main/install.sh | bashThis installs the latest binary to ~/.local/bin/nav. To install to a different location:
NAV_INSTALL_DIR=/usr/local/bin bash -c "$(curl -fsSL https://raw.githubusercontent.com/sandst1/nav/main/install.sh)"Download the latest binary for your platform from GitHub Releases:
- macOS (Apple Silicon):
nav-darwin-arm64.tar.gz - macOS (Intel):
nav-darwin-x64.tar.gz - Linux (x64):
nav-linux-x64.tar.gz - Linux (ARM64):
nav-linux-arm64.tar.gz - Windows (x64):
nav-windows-x64.zip
Extract and move to a directory in your PATH:
# macOS/Linux example
tar -xzf nav-darwin-arm64.tar.gz
mv nav-darwin-arm64 /usr/local/bin/nav
chmod +x /usr/local/bin/navRequires Bun runtime (1.0+).
# Install Bun first (if needed)
curl -fsSL https://bun.sh/install | bash# Install dependencies
bun install
# Run directly
bun run src/index.ts
# Or link globally so `nav` is available everywhere
bun linkThe recommended way to configure nav is with a config file. Create one per-project or as a user-level default:
nav config-init # creates .nav/nav.config.json in the current directoryOr create the file manually at ~/.config/nav/nav.config.json for a global default.
Priority order: CLI flags > environment variables > project config > user config > defaults.
Optional editMode in the config file: default hashline (LINE:HASH line prefixes and anchor-based edit tool). Set "editMode": "searchReplace" for plain file contents from read/skim/filegrep and edit with old_string / new_string / optional replace_all. See website/guide/configuration.md for the full key list.
Pick your provider and save as .nav/nav.config.json or ~/.config/nav/nav.config.json. Each example is a complete, ready-to-use config file.
OpenAI:
{
"provider": "openai",
"model": "gpt-4.1",
"apiKey": "sk-...",
"contextWindow": 1047576,
"handoverThreshold": 0.8,
"verbose": false,
"sandbox": false
}Anthropic:
{
"provider": "anthropic",
"model": "claude-sonnet-4-20250514",
"apiKey": "sk-ant-...",
"contextWindow": 200000,
"handoverThreshold": 0.8,
"verbose": false,
"sandbox": false
}Google Gemini:
{
"provider": "google",
"model": "gemini-2.5-flash",
"apiKey": "...",
"contextWindow": 1048576,
"handoverThreshold": 0.8,
"verbose": false,
"sandbox": false
}Azure OpenAI (set model to your deployment name):
{
"provider": "azure",
"model": "my-gpt4o-deployment",
"baseUrl": "https://my-resource.openai.azure.com/openai/v1",
"apiKey": "...",
"contextWindow": 128000,
"handoverThreshold": 0.8,
"verbose": false,
"sandbox": false
}Ollama (no API key needed, context window queried automatically):
{
"provider": "ollama",
"model": "llama3",
"handoverThreshold": 0.8,
"verbose": false,
"sandbox": false
}LM Studio (set contextWindow manually):
{
"provider": "openai",
"model": "local-model",
"baseUrl": "http://localhost:1234/v1",
"contextWindow": 32768,
"handoverThreshold": 0.8,
"verbose": false,
"sandbox": false
}OpenRouter:
{
"provider": "openai",
"model": "google/gemini-2.5-flash",
"baseUrl": "https://openrouter.ai/api/v1",
"apiKey": "or-...",
"contextWindow": 1048576,
"handoverThreshold": 0.8,
"verbose": false,
"sandbox": false
}| Key | Default | Description |
|---|---|---|
provider |
openai |
openai, anthropic, google, ollama, or azure |
model |
gpt-4.1 |
Model name |
apiKey |
— | API key for the provider |
baseUrl |
— | API base URL (for Azure, Ollama remote, LM Studio, OpenRouter) |
verbose |
false |
Show diffs, token counts, timing |
sandbox |
false |
Enable macOS Seatbelt sandboxing |
contextWindow |
auto-detected | Context window size in tokens |
handoverThreshold |
0.8 |
Auto-handover at this fraction of context (0–1) |
theme |
nordic |
Color theme (nordic or classic) |
hooks |
— | Optional lifecycle hooks (stop, taskDone, planDone) — see Hooks |
hookTimeoutMs |
600000 |
Max wall time per shell hook step (default 10 minutes). Override with NAV_HOOK_TIMEOUT_MS |
taskImplementationMaxAttempts |
3 |
Max full work+verify cycles per task in /tasks run / /plans run; loop stops if still failing. NAV_TASK_IMPLEMENTATION_MAX_ATTEMPTS |
Run shell commands (and optional custom slash commands) at fixed points: after each agent turn (stop), before a task is marked done (taskDone), and when every task in a plan is done (planDone). taskDone and planDone support maxAttempts: on failure the hook output is sent back to the model in the same conversation so it can fix issues before retrying.
See the full reference: Hooks guide.
For one-off overrides, CLI flags and environment variables take precedence over config files.
| Env var | CLI flag | Description |
|---|---|---|
NAV_MODEL |
-m, --model |
Model name |
NAV_PROVIDER |
-p, --provider |
Provider |
NAV_BASE_URL |
-b, --base-url |
API base URL |
NAV_SANDBOX |
-s, --sandbox |
Enable sandbox (macOS only) |
NAV_UI_HOST |
--ui-host |
Host for ui-server mode |
NAV_UI_PORT |
--ui-port |
Port for ui-server mode |
NAV_CONTEXT_WINDOW |
— | Context window size in tokens |
NAV_HANDOVER_THRESHOLD |
— | Auto-handover threshold (0–1) |
NAV_THEME |
— | Color theme |
NAV_HOOK_TIMEOUT_MS |
— | Shell hook step timeout in milliseconds |
NAV_TASK_IMPLEMENTATION_MAX_ATTEMPTS |
— | Max work+verify cycles per task in task/plan runs (default: 3) |
| — | -v, --verbose |
Show diffs, tokens, timing |
# Interactive mode
nav
# One-shot mode
nav "fix the type error in src/app.ts"
# With a specific model
nav -m claude-sonnet-4-20250514 "add error handling to the API routes"
# Google Gemini
nav -m gemini-2.5-flash "refactor the auth module"
# Verbose mode (shows full diffs, token counts, timing)
nav -v "refactor the auth module"
# Start websocket/http backend for UI clients
nav ui-server --ui-port 7777
# Reference files with @ — their contents are included in the prompt
nav "explain @src/auth.ts and refactor the error handling"nav ui-server exposes an optional local API transport while keeping terminal mode unchanged as the default.
- HTTP health endpoint:
/health - WebSocket endpoint:
/ws - Protocol docs:
docs/ui-server-protocol.md
This mode is designed for external UIs that want to stream assistant/tool events while still using nav's existing core behavior.
Type these in interactive mode:
/clear— clear conversation history/model [name]— show or switch the current model/handover [prompt]— summarize progress and continue in a fresh context/init— generate anAGENTS.mdfor the current project/plan— enter plan mode: discuss an idea, then save a named plan/plans— list all plans with task status summary/plans split <id>— generate implementation + test tasks from a plan/plans microsplit <id>— generate micro-tasks optimized for small LLMs/plans run <id>— work through all tasks belonging to a plan/tasks— list planned and in-progress tasks/tasks add <description>— add a new task (agent drafts name/description for confirmation)/tasks run [id]— work on a specific task, or pick the next planned one automatically/tasks rm <id>— remove a task/skills— list available skills/create-skill— create a new skill interactively/help— list available commands
Typing / shows all available commands. As you continue typing, the list filters in real-time. Press Tab to autocomplete when there's a single match.
You can create custom slash commands by adding markdown files:
| Location | Scope |
|---|---|
.nav/commands/*.md |
Project-level (takes precedence) |
~/.config/nav/commands/*.md |
User-level |
The filename (minus .md) becomes the command name. The markdown content is sent to the agent as a prompt. For example, .nav/commands/review.md:
Review the code I've changed. Focus on correctness, edge cases, and readability.
Check for common bugs and suggest improvements.Then use it with /review. You can pass arguments too — use {input} as a placeholder:
Review the following file for issues: {input}> /review src/auth.ts
Custom commands appear in /help and in the autocomplete suggestions.
Skills are reusable agent capabilities defined in SKILL.md files. They provide specialized knowledge or workflows that nav can use automatically based on the skill's description.
| Location | Scope |
|---|---|
.nav/skills/<skill-name>/SKILL.md |
Project-level (takes precedence) |
.claude/skills/<skill-name>/SKILL.md |
Project-level (Claude compatibility) |
~/.config/nav/skills/<skill-name>/SKILL.md |
User-level |
Each skill lives in its own directory and has a SKILL.md file with YAML frontmatter:
---
name: docx-creator
description: "Use this skill when the user wants to create Word documents (.docx files)"
---
# Word Document Creator
## Overview
This skill creates .docx files using...
## Instructions
1. Install the required package...
2. Use the following template...The description field tells nav when to use the skill. Write it as a trigger condition, not just what the skill does.
Commands:
/skills— list all available skills/create-skill— interactively create a new skill
Skills are automatically detected and injected into the system prompt. When nav sees a task matching a skill's description, it uses that skill's instructions.
nav has a two-level planning system: plans capture the high-level design, tasks are the concrete units of work.
Plans are stored in .nav/plans.json. Start a plan with /plan:
> /plan add dark mode to the settings screen
nav enters plan mode — it discusses the idea with you, asking one clarifying question at a time. When the plan is ready, it produces a summary and asks you to confirm:
[y]es to save plan, type feedback to refine, [a]bandon
> y
Plan #1 saved: Dark mode settings
Use /plans split 1 to generate implementation tasks.
Once saved, split it into tasks:
> /plans split 1
The agent reads the plan, explores the codebase, then creates ordered implementation tasks and test-writing tasks. Tasks are saved with IDs like 1-1, 1-2, etc. (the prefix is the plan ID).
To work through all tasks in a plan:
> /plans run 1
Working plan #1: Dark mode settings
Working on task #1-1: Add theme state to settings store
...
List all plans with a status summary:
> /plans
Plans:
#1 Dark mode settings [0/5 done, 5 planned]
Tasks without a plan use IDs like 0-1, 0-2, etc.
> /tasks add implement rate limiting for the API
The agent drafts a name and description, shows a preview, and asks for confirmation. Reply y to save, n (optionally with more instructions) to revise, or a to abandon.
> /tasks
Tasks:
#0-1 [planned ] Rate limiting
Add token-bucket rate limiting to the API middleware
> /tasks run 0-1
Working on task #0-1: Rate limiting
...
Task #0-1 marked as done.
> /tasks run # picks the next planned task automatically (all tasks)
Tasks cycle through three statuses: planned → in_progress → done. When working plan-linked tasks, the plan's description and approach are included in the agent's context alongside the status of all sibling tasks.
For long tasks, /handover lets you reset context without losing track of progress. The model summarizes what it's done, the conversation is cleared, and a fresh context starts with the summary, a current file tree, and any instructions you provide:
> /handover now write tests for the auth module
This is useful when context is getting long and you want to refocus the model on the next phase of work.
nav can automatically trigger a handover when the conversation approaches the model's context window limit. This prevents context overflow errors and keeps the model working effectively.
Context window sizes are auto-detected for most providers. For LM Studio or custom endpoints, set contextWindow in your config file. You can also adjust the handover threshold:
{
"contextWindow": 32768,
"handoverThreshold": 0.9
}When the threshold is reached mid-task, the agent completes its current step, generates a summary, and continues in a fresh context. If it's reached after the model finishes responding, the auto-handover triggers on the next user message. In verbose mode (-v), each response shows context utilization: tokens: 45.2k in / 1.2k out (3.1s) (35% of 128k ctx).
- ESC — stop the current agent execution and return to prompt
- Ctrl-D — exit nav
- Type while the agent is working to queue a follow-up message
By default, nav runs without any sandbox. Shell commands the agent executes have full access to your system — it can read, write, and delete files anywhere your user account can. This is the fastest way to work, but it means a confused or misbehaving model can cause real damage.
Enable sandboxing to restrict what the agent can do — either via CLI flag or config file:
nav -s "task"{ "sandbox": true }The sandbox uses macOS Seatbelt (sandbox-exec) and is macOS only for now. On other platforms, -s will exit with an error.
When enabled, all processes spawned by nav (including shell commands) inherit these restrictions:
- File writes are limited to the current project directory, temp, and cache directories. Writes anywhere else are denied by the kernel.
- File reads are unrestricted — the agent can still read your whole filesystem.
- Network is unrestricted — needed for LLM API calls.
The Seatbelt profile lives in sandbox/nav-permissive.sb and can be customized.
nav has 7 tools:
- read — reads files with hashline-prefixed output:
LINE:HASH|content - edit — edits files by referencing
LINE:HASHanchors from read output - write — creates new files
- skim — read a specific line range with hashline output (no shell needed)
- filegrep — search within a file with context lines and hashline output
- shell — runs shell commands
- shell_status — check on background processes
The hashline format (inspired by can.ac/the-harness-problem) gives each line a short content hash. When the model edits, it references lines by LINE:HASH instead of reproducing old content. If the file changed since the last read, hashes won't match and the edit is rejected with corrected hashes shown — so the model can retry without re-reading.
If an AGENTS.md file exists in the working directory, its content is automatically included in the system prompt. This is the standard way to give nav project-specific instructions.
Session logs are written to .nav/logs/ as JSONL files. Each line captures a message, tool call, or result with timestamps — useful for debugging and replay.
# Type check
bunx tsc --noEmit
# Run with watch mode
bun run --watch src/index.ts