Skip to content
initoriginPublic

About

A terminal agent that shows you what it is allowed to do, what it is spending, and what it refused — while it works.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

1 watching

Forks

InitOrigin

IO CLI

A terminal agent that shows you what it is allowed to do, what it is spending, and what it refused — while it works.

CI Release MSRV License

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.

Contents

A session at rest: the IO CLI card in the terminal's own scrollback, carrying the version and the tagline above the model, the permission posture and the workspace; a muted line saying this is a new conversation and the last one is still in /resume; an empty prompt below it; and a two-row footer under a rule, naming the state, the model and the clock on one row and the keys and the posture on the next.

What you get

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

It never takes your terminal

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.

Install

macOS and Linux:

curl -fsSL https://raw.githubusercontent.com/initorigin/io-cli/main/install.sh | sh

Windows, in PowerShell:

irm https://raw.githubusercontent.com/initorigin/io-cli/main/install.ps1 | iex

Or 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/io

Windows:

scoop bucket add io-cli https://github.com/initorigin/io-cli
scoop install io

The 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.

First run

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.

Guides

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 support

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.

Stability

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.

Security

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.

Contributing

See CONTRIBUTING.md. develop is the working branch; nothing reaches it or main except through a pull request.

Licence

Apache-2.0. Copyright 2026 Aakash Pawar (InitOrigin). See LICENSE and NOTICE.

About

A terminal agent that shows you what it is allowed to do, what it is spending, and what it refused — while it works.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages