A terminal agent that shows you what it is allowed to do, what it is spending, and what it refused — while it works.
io is an interface. The agent loop, the providers, the tools, the sandbox, the
permission boundary and the session store are all
io-harness, and none of them are
reimplemented here. A test asserts that: tests/dependencies.rs fails the build
if this crate ever grows an HTTP client, a TLS stack, a database or a sandbox.
- What you get
- It never takes your terminal
- Install
- First run
- Guides
- Platform support
- Stability
- Security
- Contributing
- Licence
| What it gives you | |
|---|---|
| A session you can read | Every finished line in the terminal's own scrollback, designed rather than defaulted: a tool call as a verb and a path, a thought as a thought, an answer that ends the turn |
| A working view | Two sticky rows while a turn runs — a word for the turn with its clock and spend, and a line under it saying what is happening now |
| The boundary, visible | The posture on the footer, a refusal that names the act, the target, the rule and the layer, and Shift+Tab to change it from the next turn |
| A tool held back | /context withhold <tool> builds the session's tool mask and keeps that tool refused until /context allow; io-harness sends a byte-identical catalogue either way, so the request grows by the one sentence naming what is withheld |
| Approvals in place | A write stops the run and shows the diff it proposes; y, a or n, answered where it was asked |
| A refusal that asks instead of ending the work | When the agent is stopped by a default rather than by a rule you wrote, io asks — y for that call, a for the session, w to write the rule into your own configuration, in the user scope and never a workspace file. /policy revoke takes back what io wrote, and only what io wrote. A [[policy.layers]] deny you wrote still refuses, silently and always; so does a path outside the workspace root. It changes when io asks, never what io permits |
| Unconfined when you mean it | io --full-access sets every default to allow and takes the sandbox out of the way, which is the only thing that reaches a call the sandbox refuses structurally — a bind(), say. It is not in the Shift+Tab cycle, it is marked on every frame, it is never written to a file, and it still does not unlock .env |
| A fan-out you can watch | Contained turns spawn children under one shared ceiling; Ctrl+F shows the tree and what it is costing |
| Your file, in force | Every section of io.toml bounds a session turn as it bounds io exec; the budgets in force are on the status line with what is left of them, and /status commits the whole state — policy layers, sandbox backend, caps, budgets, connections — into the scrollback |
| Undo | Esc at an empty prompt asks whether to rewind the last turn — its files, its memory and the conversation head |
| Conversations that survive | /resume reopens an earlier session and answers whatever its last run stopped on, /fork continues from an earlier turn, /clear starts fresh without leaving |
| A paused run, answered | A question, a plan or an interrupted call is decided where it was left and the run carries on from the step it stopped at — from a session, or from a script with io resume |
| Everything it asks, in one place | Several questions asked at once arrive as one overlay, answered one at a time and sent together; an offer may explain itself and preview what taking it would do; Space marks where a question takes more than one answer |
| Headless | io exec runs one goal to completion with documented exit codes and --json, and io resume carries a parked one on. Exit 6 is a run that was judged and did not hold up: a verification gate that failed, or io-harness's RunOutcome::SchemaUnsatisfied — the schema contract, a run that never produced the shape [run] output_schema asked for |
| From your editor | io acp serves the Agent Client Protocol (ACP) as newline-delimited JSON-RPC 2.0 over stdio, so Zed or a JetBrains IDE runs io against this machine's own configuration; an action in the grey tier arrives as a session/request_permission you answer in the editor |
| Bundles from a marketplace you named | /plugin marketplace add <owner>/<repo> clones an index into your own home and /plugin add <name> installs a bundle out of it — the same words from a shell, as io plugin marketplace add and io plugin add — and what a bundle is allowed to do is shown before it may do it |
| Readable without seeing it | --plain animates nothing and commits every state change as text, for a screen reader, a braille display or a log |
| Markdown, rendered | Headings, bullets, code and emphasis drawn as themselves rather than printed as notation |
| Documents, read and written | Spreadsheets, Word, slide decks, PDFs and barcodes through io-harness's own tools — twelve of them, six of which write, every one under the same gate as any other read or write |
| Your setup, brought across | /import finds the agent tools already on this machine and offers their instructions, MCP servers, skills and model, item by item, with the whole plan shown before a byte is written and no credential read at any point |
| What it spent, and whether it worked | /cost commits the money and the token split — this run, this session, by model, by day — and /stats commits the outcomes, the first-try counts, the gate failures and the latencies. Nothing is estimated: an unpriced model makes a total a floor and the page says so |
io does not enter the alternate screen and does not capture the mouse, in any
mode, behind any flag. Every finished message, tool call and system line is
committed into the terminal's own scrollback; a few rows at the bottom hold the
unfinished tail of a streaming answer, a blank row, the activity line, a rule,
one row of composer and a three-row footer, and only those repaint. Eight rows is
the floor: a surface that needs more — a question with its offers, a plan with its
steps, the messages you have queued — takes what it needs and gives it back,
stopping four rows short of the top so the exchange it is about stays visible.
So when the session ends the whole conversation is still there. Your terminal's search finds it, tmux copy-mode scrolls it, and a mouse drag selects it — none of which is implemented here. It works because it was never taken away.
That is a property, not an intention. tests/structure.rs captures every byte
io writes to the terminal over a scripted session and fails if the
alternate-screen or mouse-capture sequences appear, so no later release can
reintroduce fullscreen without turning a named test red.
macOS and Linux:
curl -fsSL https://raw.githubusercontent.com/initorigin/io-cli/main/install.sh | shWindows, in PowerShell:
irm https://raw.githubusercontent.com/initorigin/io-cli/main/install.ps1 | iexOr with a package manager you already have. macOS and Linux:
brew tap initorigin/io-cli https://github.com/initorigin/io-cli
brew install initorigin/io-cli/ioWindows:
scoop bucket add io-cli https://github.com/initorigin/io-cli
scoop install ioThe tap and the bucket live in this repository rather than in two of their own —
Formula/io.rb and bucket/io.json — which is
why brew tap and scoop bucket add name a URL: this repository is not named
homebrew-io-cli, so neither tool can derive the URL from the name. They install
the same checksum-verified artifacts the scripts do.
Both scripts pick the right build for your machine, verify it against the
published SHA256SUMS before unpacking it, and install into a directory you
own — ~/.local/bin, or %LOCALAPPDATA%\io\bin on Windows. Neither needs
administrator rights and neither edits your shell profile or your PATH: if
the directory is not on your PATH, the script prints the line to add.
io upgrade prints the command that updates the binary you are running:
brew upgrade io from a Homebrew install, scoop update io from a scoop one,
and the installer line above from a script install. It prints and does not run,
and it asks nothing about what the newest version is. There is still no
auto-update and no version check: a terminal tool that contacts a server you did
not ask it to contact is one of this product's stated non-goals, and io upgrade
reads the path of the running binary rather than a network.
Set IO_VERSION to install a specific version, and IO_INSTALL_DIR to install
somewhere else.
What the checksum is and is not. It defends against a truncated download and
a tampered asset. It does not defend against a compromised repository — piping a
script from the internet into a shell is a trust-the-publisher model however the
script is written, and this is the honest description of it. Read
install.sh first if you would rather not take that on trust.
There is no crates.io publish and cargo install io-cli is not a path. To build
from source, clone this repository and run cargo build --release; the binary is
target/release/io.
Run io in a repository. With no configuration it walks you through a provider,
a key it verifies against the live endpoint before continuing, a model from that
provider's catalogue, a theme with the sample re-rendering as you move, and a
default permission posture. It shows exactly what it will write and where, and
writes nothing until you say so. The file is io-harness's own, at mode 0600.
Run it again at any time with io setup.
Your key never appears on screen, in the scrollback, or in a log line. If the provider's environment variable is already set, the wizard offers to use it and writes no key to disk at all.
One page per capability, each carrying the depth this page does not — including the limits that capability actually has.
| Guide | What it covers |
|---|---|
| While it works | What a running turn shows you, how the agent's manner is drawn, answering without opening a run, how much reasoning a turn buys, background jobs, and the one surface the agent asks everything through |
| Keys | Every default binding, and moving one |
| Commands | All thirty-six, grouped by what you are doing |
| Bringing your setup across | What /import finds on this machine, item by item, and the credential it never reads |
| When a run stops for you | A question, a plan or an interrupted call answered where it was left — and one io at a time on one conversation |
| What it costs | The money and the token split, where a price comes from, and why an unpriced model makes a total a floor |
| Skills | The five shipped, yours, and what a duplicate name costs |
| Capability bundles | A directory contributing to seven subsystems, the executable one of them ships and where a run finds it, marketplaces, installing a Claude Code or Codex plugin, and what a bundle is allowed to do being shown before it may do it |
| Hooks | Reacting to a run from io.toml, and the failure that is quiet |
| The fleet | Contained turns, the tree Ctrl+F shows, and the plan a contained turn proposes |
| Verification gates | The criterion, what it costs per step, and exit 6 |
| Which model a run asks | The chain, the routing rules, and the one that is inert |
| Git | /commit, the refusal it repairs, and a checkout of its own |
| Pictures and documents | Attaching an image, drawing it where the terminal can, and the twelve document tools |
| Reading it without seeing it | --plain, and what a screen reader, a braille display or a log gets |
| Headless | io exec, the exit codes, the JSON, resuming without a terminal, and managing the configuration from a shell |
| From an editor | io acp, pointing Zed or a JetBrains IDE at it, what the editor is sent, how it is asked for permission, and what it is still not |
| Configuration | Every section of io.toml, changing it without leaving the session, and where io keeps your things |
| What the store is holding | Reading the store, putting work back, and taking the work out |
| What this release is not | The limits, stated plainly, and the ones that are decisions rather than gaps |
docs/CAPABILITIES.md indexes them. docs/CONTRACT.md is what a script may depend on: the argv surface, the exit codes, the configuration keys and the paths io writes.
| Platform | Build | Containment |
|---|---|---|
| macOS, Apple silicon | aarch64-apple-darwin |
io-harness's own: sandbox-exec |
| macOS, Intel | x86_64-apple-darwin |
as above |
| Linux | x86_64-unknown-linux-musl, statically linked |
io-harness's chain: Landlock, bwrap, namespaces, floor |
| Windows | x86_64-pc-windows-msvc |
Job Object, with AppContainer opt-in |
The four artifacts and their SHA256SUMS are attached to every GitHub Release,
and the full test suite runs on Ubuntu, macOS and Windows in CI. What confines
a command is io-harness's, not this product's — io shows you which backend
actually answered on this host, in the footer, because the mode asked for and the
backend that applied are not the same fact.
Rust 1.95 or later to build from source. There is no crates.io publish: the
distribution channel is the GitHub Release, and publish = false makes an
accidental one impossible rather than merely discouraged.
Pre-1.0 and staying there until the owner says otherwise. A minor release may
change what a session looks like — 0.11.0 rewrote the transcript's vocabulary,
and the release before it moved where a question is answered. What you can rely
on is that every one of those is in CHANGELOG.md, said plainly,
and that a configuration file written for an older release keeps working. One
key has been removed in the product's life so far: [app.io-cli] max_steps,
deprecated in 0.14.0 and removed in 0.16.0, with two releases' notice given in
the terminal, the README and the changelog. A file that still carries it loads
exactly as before — the key is ignored rather than rejected — and the session
says so once at startup, naming the number that is no longer in force and
[run] max_steps as where the cap lives now. A section that was ignored may
start being read, which 0.14.0 did to eleven of them, and that is a behaviour
change for a file that already carried one; it is the migration note in
Configuration and in the changelog rather than something to
find out from a turn.
Report vulnerabilities per SECURITY.md. Your provider key is never printed, never committed to the scrollback, and never written to disk by the wizard when the provider's own environment variable is already set.
See CONTRIBUTING.md. develop is the working branch; nothing
reaches it or main except through a pull request.
Apache-2.0. Copyright 2026 Aakash Pawar (InitOrigin). See LICENSE and NOTICE.
