Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,52 @@ All notable changes to this project are documented here. The format follows

## [Unreleased]

## [0.3.0] - 2026-08-17

The operator can read what the agent did to their files without leaving the
terminal and without losing the thread. An edit stops being a line saying a file
changed and becomes the change itself.

### Added

- **Edits render as diffs.** Not diffs io-cli computed — io-harness already
renders a unified diff for every edit its tools make and keeps it in the run's
durable trace, so what you see carries the file's own `@@` line numbers and is
the same text `Store::patch` would hand `patch`. Additions and removals are
coloured *and* marked, so the meaning survives `NO_COLOR`.
- **Word-level emphasis inside a changed line.** A run of removals is paired with
the run of additions after it only when the two are the same length; anything
else takes the whole wash. A `write_file` that rewrote two distant regions of a
file arrives as one hunk spanning both, and a greedier rule would emphasise the
difference between lines that have nothing to do with each other.
- **Syntax highlighting**, drawn in io-cli's own theme tokens rather than in a
second palette. The three new tokens — keyword, string, literal — are the
theme's, so a highlighted diff and the rest of the interface stay one look, and
`NO_COLOR` is still decided in one place. Green still means added: the parts of
a line both sides share are syntax coloured and the words that actually changed
keep the diff's colour, so the add/remove colour now points at the exact words
instead of washing the line.
- **A defined form below a hundred columns**, where word-level emphasis gives way
to the line — a bolded fragment in the middle of a line that now takes three
rows is harder to find than a whole row that is red. Nothing is truncated.
- **`diff = "minimal"` in `[app.io-cli]`** for reviewing by file rather than by
hunk: the changed lines and the `@@` header, without the context. Its absence
means `unified`, so no existing configuration file needs touching.

### Changed

- **An approval shows a write as a diff** against the file on disk, which is the
clause of the approval surface 0.2.0 shipped unmet — the harness hands an
approver the whole resulting file rather than a patch, so the old side is
io-cli's to supply. A file that does not exist yet reads as all addition. At
the tightest size the one row available is spent on `+3 -1` rather than on the
first line of the change, because the size of a write is the decision.

### Fixed

- The answers row in the approval overlay no longer carries a double space after
each separator.

## [0.2.0] - 2026-08-17

The operator can see the boundary the agent is working under, change it, and
Expand Down
103 changes: 102 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 22 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "io-cli"
version = "0.2.0"
version = "0.3.0"
edition = "2021"
# io-harness declares 1.95, set by `libsqlite3-sys` calling the std `cfg_select!`
# macro from its build script. Nothing here can be lower than what the harness
Expand Down Expand Up @@ -50,6 +50,27 @@ clap = { version = "4", features = ["derive"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync", "time"] }
serde = { version = "1", features = ["derive"] }
toml = "1"
# Syntax highlighting inside a diff, and the feature set is the load-bearing part
# rather than the version.
#
# `default-features = false` excludes `regex-onig`, which is oniguruma — a C
# library. Two of this product's four artifacts are cross-compiled, to
# `x86_64-unknown-linux-musl` and `x86_64-pc-windows-msvc`, and a native build
# step in that path fails inside the release workflow rather than in a test.
# `regex-fancy` is the pure-Rust engine that replaces it.
#
# `default-themes` is off on purpose: the colours come from io-cli's own token
# set, so a highlighted diff and the rest of the interface are one aesthetic
# instead of two, and `NO_COLOR` keeps working because there is only one place
# that decides colour. `html`, `plist-load` and `yaml-load` are for loading
# grammars and emitting markup at runtime, which this product never does.
#
# `tests/dependencies.rs` asserts this exact feature list. A later
# `default-features = true` would pull the C engine back in silently.
syntect = { version = "5", default-features = false, features = [
"default-syntaxes",
"regex-fancy",
] }

[dev-dependencies]
tempfile = "3"
54 changes: 43 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ writes no key to disk at all.
| `Ctrl+D` | exit, on an empty prompt |
| `Shift+Tab` | cycle the permission posture, from the next turn |
| `Ctrl+L` | clear the viewport, never the scrollback |
| `Ctrl+T` | put the whole conversation back into the scrollback |
| `y / a / n` | answer an approval: allow once, allow this session, deny |
| `Esc` | close a picker without choosing |

Expand All @@ -110,9 +111,26 @@ writes no key to disk at all.
| `/setup` | run the first-run wizard again |
| `/theme` | change the theme for this session |
| `/model` | change the model for this session |
| `/expand` | commit the last step's full detail into the scrollback |
| `/copy` | put the last answer on the system clipboard |
| `/copy diff` | put the whole run's patch on the system clipboard |

<!-- commands:end -->

**Everything that shows you more of something writes it into the terminal's own
scrollback.** `Ctrl+T` and `/expand` do not open a pane: the viewport is a few
rows and this product has no alternate screen, so the place to read something
long is the buffer where the terminal's search, selection and tmux copy-mode
already work. `/expand` reads the step's full output back out of the run's
durable trace, which is where it went in the first place — the screen is not the
archive.

`/copy` uses OSC 52, so it reaches the clipboard of the machine you are *sitting
at* rather than the one you are ssh'd into. Nothing acknowledges an OSC 52 write:
the line it prints says what was sent and how large it was, never that it
succeeded. Inside tmux it needs `set -g set-clipboard on`, and some terminals
refuse a large payload without saying so.

`/theme` and `/model` change this session only and say so. Making a choice
permanent is `io setup`.

Expand All @@ -122,6 +140,12 @@ io-cli has no configuration parser. io-harness owns discovery and layering, and
io-cli's own settings live in the `[app.io-cli]` section that io-harness
deliberately does not validate. See [`docs/config.example.toml`](docs/config.example.toml).

Two keys live there: `theme`, and `diff`, which is `unified` (the default, and
what an absent key means) or `minimal` — the changed lines and the `@@` header
without the context, for reviewing by file rather than by hunk. Because the
section is unvalidated by design, an unrecognised value reads as the default
rather than stopping a session from starting.

The file is found in this order: `$IO_CONFIG`, else `$IO_CONFIG_HOME/io.toml`,
else `$XDG_CONFIG_HOME/io/io.toml` or `~/.config/io/io.toml`, and
`%APPDATA%\io\io.toml` on Windows. A project's own `io.toml` and a gitignored
Expand Down Expand Up @@ -151,21 +175,29 @@ refusal, error and warning also carries a word.

## What this release is not

0.2.0 is the permission boundary made visible and answerable: the approval
overlay, the posture key, the refusal that names its rule and its layer, and the
status fields around them. Diffs, syntax highlighting and collapsible tool output
are 0.3.0; resume, fork and rewind are 0.4.0; the headless subcommand and NDJSON
are 0.5.0; the screen-reader mode is 0.6.0; the slash palette and type-to-filter
pickers are 0.7.0.
0.3.0 is what the agent did to your files, made readable: diffs from the
harness's own stored hunks, word-level emphasis, syntax highlighting, collapsed
tool output, the transcript on one key and the clipboard over SSH. Resume, fork
and rewind are 0.4.0; the headless subcommand and NDJSON are 0.5.0; the
screen-reader mode is 0.6.0; the slash palette and type-to-filter pickers are
0.7.0.

Two things are absent for reasons worth stating rather than hiding. **Spend
Three things are absent for reasons worth stating rather than hiding. **Spend
against the tree ceiling is not in the status line**: io-harness emits
`SpendDraw` only from a contained turn, and its contained entry point takes no
steer inbox, so rendering spend today would cost `Ctrl+C`. It arrives at 0.8.0
with the fleet, which is contained anyway. And **the overlay shows a write's
proposed content plainly rather than as a diff** — the harness hands an approver
the resulting file, not a patch, so the diff is io-cli's to compute and 0.3.0's
to design.
with the fleet, which is contained anyway. **A diff cannot be expanded beyond
the context the harness stored** — three lines either side, which is what
`diff -u` has always carried; more than that is not in the trace, and reading it
off disk would be reading a version of the file that no longer exists. And
**there is no split view**: this renderer commits into the terminal's own
scrollback at its real width, a two-column comparison doubles the horizontal
budget for every line, and word-level emphasis inside a unified diff already
answers the question split view answers.

One ceiling worth knowing about: a hunk is a fragment of a file, and each of its
lines is highlighted from a clean parse. A block comment or a multi-line string
that was opened *above* the hunk is not known here, so those lines read as code.

## Contributing

Expand Down
8 changes: 8 additions & 0 deletions docs/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,11 @@ net = "deny"
[app.io-cli]
# "dark" or "light". Absent means detect from the terminal background.
theme = "dark"
# How much of a change a diff shows.
#
# "unified" the hunk as the harness stored it, context and all. The default,
# and what an absent key means — so a file written before 0.3.0
# needs no change.
# "minimal" the changed lines and the @@ header, without the context. For
# reviewing by file rather than by hunk.
diff = "unified"
Loading