A coding assistant that lives in your terminal. It reads your files and runs your commands โ and waits for you before every one. Connects to any OpenAI-compatible LLM endpoint.
โ
โโโ โโโ boxcode v1.0.0
โโโโโโโโโโโโโ a terminal coding assistant
โโ โโโโโ โโ
โโโโโโโโโโโโโ Welcome back, you!
โโโ โโโ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
model deepseek-chat
endpoint https://api.deepseek.com
cwd ~/Desktop/HolboxAI/boxcode
/provider switch provider or endpoint
/model switch model
Ask about this project โ it can read files and run commands.
Every command and every write waits for your approval.
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โโฏ add a health check endpoint โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
โต send ยท โฅโต newline ยท โโ history ยท ^c exit
While a turn runs, a spinner sits at the end of the transcript โ right above the prompt, where you're already looking:
โฏ add a health check endpoint
I'll add it to the router and run the tests.
ยท $ cargo test โ 42 lines
โ น Respondingโฆ (4s ยท ~120 tokens ยท esc to interrupt)
macOS / Linux:
curl -fsSL https://boxcode.sh/install.sh | bashWindows (PowerShell):
irm https://boxcode.sh/install.ps1 | iexboxcode.sh serves the very same install.sh/install.ps1 that sit in this
repo โ it is published from main by .github/workflows/pages.yml, not
maintained as a second copy. If you'd rather fetch from GitHub directly,
https://raw.githubusercontent.com/HolboxAI/boxcode/main/install.sh still
works and always will.
Downloads a prebuilt binary for your platform (macOS/Linux/Windows,
x86_64/arm64) from the latest
release and verifies it
against a published checksum โ no Rust toolchain needed, installed in
seconds. Also installs Python's ddgs package if it's missing, since
web_search needs it.
On macOS/Linux, if your platform has no prebuilt binary yet, install.sh
falls back to installing Rust (if missing) and building from source instead,
same as before, just automatically. There is no such fallback on Windows โ
building from source there needs the MSVC Build Tools, a much bigger ask than
rustup alone, so install.ps1 will tell you plainly if it can't find a
prebuilt binary rather than trying to set up a C++ toolchain unasked; install
Rust yourself and run cargo build --release, or use WSL with the regular
install.sh.
Or build from source yourself:
git clone https://github.com/HolboxAI/boxcode
cd boxcode
cargo build --release
./target/release/boxcodeFastest way: launch boxcode and type /provider โ pick a provider from the
list (arrow keys, Enter), then pick a model. If you already have that provider's
conventional API key exported (e.g. DEEPSEEK_API_KEY for DeepSeek,
OPENAI_API_KEY for OpenAI โ pattern is {PROVIDER}_API_KEY), it's picked up
automatically; otherwise you're prompted to paste or type it (input hidden). The
choice is written to ~/.boxcode/config.toml so it's remembered next launch.
Not on the list, or pointing at a self-hosted/internal endpoint? Pick
"Custom endpoint..." at the bottom of the list instead โ you'll be walked
through endpoint, model, and API key manually, same as filling in the file by hand.
Alternatively, skip the picker and set environment variables or write
~/.boxcode/config.toml directly:
[llm]
endpoint = "https://llm.company.internal:8443"
model = "company-llm-70b-v1.2"
api_key = "sk_company_xxx"Or use environment variables:
export BOXCODE_ENDPOINT=https://llm.company.internal:8443
export BOXCODE_MODEL=company-llm-70b-v1.2
export BOXCODE_API_KEY=sk_company_xxxboxcodeboxcode --upgradeChecks main for a newer version and, if there is one, reinstalls in place โ
no need to dig out the curl command again. It removes stale copies from other
directories on your $PATH and confirms the shell resolves to the new build.
main can also carry changes that haven't been given a new version number yet.
To rebuild from the latest source regardless, use boxcode --upgrade --force.
Upgrading from 0.2.0 or earlier? Those builds predate this flag โ run the install command from step 1 once more, and
--upgradeworks from then on.
Running somewhere with no route to github.com? Point upgrades at a fork or an
internal mirror serving the same Cargo.toml and install.sh/install.ps1
(whichever your platform uses):
export BOXCODE_UPGRADE_URL_BASE=https://git.company.internal/boxcode/raw/mainThe model has three tools in the directory you launched from, so you can ask about the actual project instead of pasting code in, and have it create or change files directly instead of hand-encoding writes into shell commands:
> create hello.py and run it
๐ /Users/you/project/hello.py
Created it โ printing "Hello, World!" and running it now.
$ python3 hello.py โ 1 line
read_file/write_file handle reading and creating/overwriting a single
file. run_command is for everything else โ search (grep), builds, tests,
running a program, listing an archive (unzip -l), extracting a PDF
(pdftotext) โ anything installed on your machine.
Each one stops and waits for you โ a write shows the file's full new content, not a shell string:
โญ Write this file? โโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ ๐ hello.py โ
โ โ
โ print("Hello, World!") โ
โ โ
โ in /Users/you/project โ
โ โ
โ y write ยท n skip ยท esc skip โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
y does it ยท n or Esc skips it and tells the model to try
something else. Every action is asked about individually โ there is
deliberately no "allow everything from now on" key, so one impatient keystroke
can never cover commands the model has not thought of yet. Reads of a short,
conservative allowlist (ls, cat, grep, git status/diff, ...) skip the
prompt by default โ see auto_approve_read_only below.
These prompts are the only thing limiting what the model can do. run_command
can read any file your user can read, write anywhere, and delete anything โ
there is no sandbox, and there is no honest way to build one by inspecting
command strings. write_file/read_file are checked against the project
directory before anything happens (see tools::resolve_in_workspace), which
a raw shell command cannot offer, but that is a guardrail against typos and
injected paths, not a sandbox either. Read each prompt before pressing y.
Commands run with stdin closed and are killed after a timeout, so anything
interactive (vim, a dev server, a REPL) will time out rather than hang.
A third tier sits above the prompt. Genuinely catastrophic commands are never
run and are never even offered for approval โ offering rm -rf / as a y/n
question is itself the bug, since one mistyped keystroke accepts it and there
is no undo:
โ $ rm -rf / โ blocked
`rm` aimed at `/`, which is outside the project directory
Refused: deleting anything outside the project directory or the project itself
(rm -rf /, ~, /etc, ../.., ., *), --no-preserve-root, disk
formatting (mkfs, fdisk, dd of=/dev/sda), writing to raw devices, fork
bombs, shutdown/reboot, piping a download into a shell (curl โฆ | sh),
executing base64-decoded data, kill -9 1, and recursive chmod/chown on
system paths. Every segment of a chained command is checked, so
ls && rm -rf / is caught too.
Windows and PowerShell are covered by the same rules: del /f /s /q C:\,
rd /s /q C:\, Remove-Item -Recurse -Force C:\, format, diskpart,
cipher /w, bcdedit, reg delete HKLM, Clear-Disk, and
vssadmin delete shadows (which destroys the backups that would let you
recover).
No setting reaches this. Not require_approval = false, not
auto_approve_read_only. There is deliberately no config option to turn it off.
A middle tier โ destructive but legitimate โ always stops for an explicit
decision, even with approval switched off entirely: rm -rf build,
git reset --hard,
git clean -fd, force-push, sudo anything, find โฆ -delete, uninstalls,
docker prune. The prompt shows a red DESTRUCTIVE banner and why.
This is not a sandbox, and no blocklist can be one. A command that builds its argument at runtime (
rm -rf $(printf '\x2f')) defeats any static check โ such commands are forced to the always-ask tier rather than judged safe, but the honest claim is narrow: this catches destructive commands a model produces by mistake, which is the realistic failure mode. It does not stop a determined attacker. Real containment needs an OS sandbox.
[tools]
enabled = true # false sends no tool schema at all
workspace = "." # "." = the directory you launched from
require_approval = true # false = the model runs commands unattended
auto_approve_read_only = true # skip the prompt for a narrow read-only allowlist
command_timeout_secs = 60
max_output_bytes = 65536 # ceiling on one command's output
max_steps = 10 # command rounds per prompt before the model must answerPer-run: BOXCODE_WORKSPACE=/path/to/project, BOXCODE_TOOLS_ENABLED=0.
require_approval = falsehands the model an unattended shell on your machine. It exists for scripted testing. If you set it, the welcome screen saysUNATTENDEDin red every launch.
auto_approve_read_only skips the popup only for a short, conservative
allowlist of commands that cannot change anything on disk -- ls, cat,
grep, git status/diff/log/show, and similar (see
tools::is_read_only). Anything chained with ;, |, &&, >, or a
subshell falls back to asking, even if it starts with one of those. Everything
else -- writes, deletes, git push, find -delete, arbitrary other commands
-- still stops for a decision regardless of this setting. Set it to false to
go back to asking about every command, including reads.
Works on macOS, Linux, and Windows โ commands run through sh -c, or cmd /C
on Windows, and the model is told which platform it is on so it reaches for
dir/type/findstr rather than ls/cat/grep.
Your endpoint needs to support OpenAI-style tool calling. If it doesn't, the request comes back as
HTTP 400โ setenabled = falseunder[tools]and everything else keeps working as before.
The colours adapt to your terminal's background. On launch the app works out
whether you are on a dark or a light terminal โ first from [ui] theme if you
set it, then from COLORFGBG, then by asking the terminal directly (OSC 11).
Most terminals answer, but not all: VS Code, iTerm2 and Apple Terminal set no
COLORFGBG, and Windows consoles do not reply to the query. When nothing can
be established, a third palette is used whose colours are legible on a dark
and a light background โ safe, just less vivid than either tuned one.
If the guess is wrong, or you just want the vivid version, say so outright:
[ui]
theme = "auto" # auto | dark | lightTwo things make a wrong guess survivable. Body text never picks a colour at all โ it takes the terminal's own foreground, which cannot clash with the terminal's own background, so what you type is always readable. And every other colour that carries words is contrast-checked in CI against both backgrounds, so landing on the wrong palette costs vividness rather than legibility.
- Type prompt โ Bottom input line (paste works too)
- Enter โ Send prompt
- Alt-Enter / Shift-Enter โ Insert a newline for multi-line prompts
- Esc โ Cancel ongoing request
- โ / โ โ Recall previous prompts. Inside a multi-line prompt they move between its lines first, so a stray โ can't swallow what you were writing
- Your terminal's own scrollback โ the session is printed as ordinary output, so the wheel, text selection and your terminal's search all work on it, and it is still there after you quit
- Ctrl-A / Ctrl-E โ Jump to start / end of line
- Ctrl-W โ Delete previous word
- Ctrl-U / Ctrl-K โ Delete to start / end of line
- Ctrl-C โ Exit
endpoint may be given as https://host, https://host/v1, or the full
https://host/v1/chat/completions โ all three resolve correctly. Environment
variables override values in config.toml.
/providerโ Opens a picker (โ/โ to navigate, Enter to select, Esc to cancel) of built-in providers, plus a "Custom endpoint..." entry that preserves the "any OpenAI-compatible endpoint" support above โ it's not limited to the built-in list. Selecting a provider chains straight into a model picker for it./modelโ Re-picks just the model for whichever provider is currently configured, without going through/provideragain. If no provider has been set yet (e.g. you're only usingBOXCODE_*env vars or a custom endpoint), this shows an inline error telling you to run/providerfirst./newโ Forgets the current conversation. The configured provider and model are untouched; only the message history and tool-step count reset./usageโ Prints your token usage from~/.boxcode/usage.jsonl: today, the last 7 days, and all time. This is local and per-install only โ there is no login, so it is the only place this number exists; nothing here is ever sent anywhere (see "Anonymous usage pings" below for the one thing that is).
/provider and /model write the result to ~/.boxcode/config.toml
and apply it immediately โ no restart needed, even mid-session.
There is no login, so there is no way to attribute usage to a person โ what
this app can see instead is a random ID generated once per install
(~/.boxcode/device_id), which labels a machine, not a person. Two
things, and only these two things, ever leave your machine:
install.sh/install.ps1sends oneinstallping on a fresh install or an--upgrade.- The app itself sends one
activeping per calendar day (UTC) it's actually used, checked against~/.boxcode/last_activeso a long session doesn't send more than one.
Each ping carries only {anon_id, event, version, os, date} โ no prompts, no
file paths, no command text, no conversation content. Both are silent,
best-effort, and never block startup or fail an install: see src/telemetry.rs
and the ping_install/Send-InstallPing functions in install.sh/install.ps1.
BOXCODE_TELEMETRY_URL="" disables it on macOS/Linux; PowerShell cannot
represent an explicitly-blank environment variable ($env:X = '' deletes it
outright), so use BOXCODE_TELEMETRY_URL=off on Windows instead.
The aggregate counts are public: tui-telemetry.dhruvm307.workers.dev
shows total installs, distinct anonymous devices seen, and daily-active counts,
live. That page is also the entire ingestion endpoint (see
telemetry-worker.js in the repo root) โ it's as publicly writable as it is
readable, so treat the numbers as self-reported, not verified.
Set BOXCODE_TELEMETRY_URL="" (explicitly blank, not just unset) before
installing or running the binary to opt out entirely.
This is entirely separate from /usage above, which never leaves your
machine at all.
- Rust + Ratatui โ Terminal UI framework
- tokio โ Async event loop (handles keyboard + streaming simultaneously)
- OpenAI-compatible API โ Works with any endpoint (self-hosted, Bedrock, etc.)
Clean, modular structure for easy feature additions:
src/main.rsโ Event loopsrc/app.rsโ State machinesrc/ui.rsโ Terminal renderingsrc/theme.rsโ Colours, glyphs, and the spinner, in one placesrc/llm.rsโ LLM client + streamingsrc/config.rsโ Configuration loadingsrc/providers.rsโ Built-in provider/model registry for/providerand/modelsrc/tools.rsโ The model's tools (run_command,read_file,write_file): schemas, execution, timeoutssrc/workspace.rsโ The working directory commands run insrc/usage.rsโ Local per-install token usage log (/usage), never transmittedsrc/telemetry.rsโ Anonymous install/daily-active pings, disabled by defaultsrc/dateutil.rsโ Calendar-date helpers shared by the two abovetelemetry-worker.jsโ The Cloudflare Worker thattelemetry.rs/install.shping and that serves the public viewdocs/index.htmlโ The boxcode.sh landing page, deployed with the installers by.github/workflows/pages.yml
- A diff preview when a command is about to modify tracked files
- Remembering per-command approvals across a session
- GitHub integration (VPC-only)
- Test generation
# Build
cargo build --release
# Run with debug logging
RUST_LOG=debug boxcode
# Test (if you add tests)
cargo testMIT