atto-ui is a multi-window terminal UI framework built on Crossterm and Ratatui. The repository contains the Rust runtime, a Node N-API binding, a typed @atto-ui/core JavaScript facade, and a React reconciler package for JSX-driven terminal apps.
| Package | Purpose |
|---|---|
atto-ui |
Core Rust crate with desktop chrome, window management, widgets, runtime component trees, themes, and PTY-testable app hosting. |
crates/atto-ui-node / @atto-ui/node |
Native N-API binding exposing AppHost to JavaScript runtimes. |
packages/core / @atto-ui/core |
Typed CommonJS facade, native loader, low-level spec builders, and runtime types. |
packages/react / @atto-ui/react |
React reconciler, JSX host components, event bridge, and render() loop. |
crates/atto-ui-node/npm/* |
Platform binary npm packages used by optional dependencies. |
atto CLI |
External IPC client binary from the root crate for scripted query, invoke, and tree calls over ATTO_UI_SOCKET. |
crates/atto-editor-app |
Multi-window terminal editor app with Explorer, tabs, split views, command palette, file/symbol/search pickers, and LSP-backed editor features. |
crates/atto-agent-app |
Single-window TUI agent app built on atto-ui-chat, with DeepSeek protocol/client modules, local tools, skills, plan mode, context compaction, and deterministic mock PTY fixtures. |
crates/atto-ui-terminal |
Reusable terminal emulator component: PTY sessions, split panes, copy-mode, command blocks, settings, and terminal pane IPC helpers. Powers the multiplexer below. |
crates/atm |
atm (atto terminal multiplexer) — full multi-window terminal app built on atto-ui-terminal, plus a client-side tmux shim binary. |
- Rust stable with edition 2024 support.
- Node.js 22 or newer for local JavaScript tests.
- Python 3 for PTY integration tests.
- Bun and Deno are optional locally, but CI validates both runtimes.
cargo build
cargo run --example demo
cargo test --all --all-targetsUseful validation commands:
cargo fmt --all -- --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --all --all-targetsThe deterministic test target used by PTY tests is available with:
cargo run --bin snapshot_appAtto UI apps can expose the in-process DesktopInspector control plane over a Unix domain socket. For crossterm apps, set ATTO_UI_SOCKET before launch; the runner drains requests on the UI thread during each frame:
ATTO_UI_SOCKET=/tmp/atto-ui.sock cargo run --example demoThe root atto CLI is the first external client. It talks to that socket and returns either human-readable output or the raw protocol response with --json:
cargo run --bin atto -- --socket /tmp/atto-ui.sock --json tree
cargo run --bin atto -- --socket /tmp/atto-ui.sock query my-checkbox checked
cargo run --bin atto -- --socket /tmp/atto-ui.sock invoke my-checkbox toggleThe CLI currently covers the generic control-plane methods query, invoke, and tree. Terminal pane methods such as send_keys, capture_pane, split_window, and display_popup are available to Rust apps that register atto_ui_terminal::terminal_pane_ipc_handler(...) on their IpcServer; they are also the backend used by the tmux shim described below.
Launch atm with your login shell, or pass a command to make the initial terminal window run that command:
cargo run -p atm
cargo run -p atm -- topatm supports floating terminal windows, tmux-style split panes, pane zoom/resize/close, dead-session restart, OSC title sync, command-block navigation for OSC 133/7 shell integration, local selection/copy-mode, OSC 52/system clipboard integration, tmux DCS passthrough for OSC 52, alt-screen wheel routing, and a File -> Settings window backed by JSON/YAML TerminalConfig.
Key defaults: Ctrl+B is the terminal prefix, Ctrl+B [ enters copy-mode, Ctrl+B % / Ctrl+B " split panes, Ctrl+B o / Ctrl+B Tab focuses the next pane, Ctrl+B + an arrow key selects a pane geometrically, Ctrl+B Ctrl+Arrow resizes the nearest split, Ctrl+B z toggles pane zoom, Ctrl+B x closes the active pane, and Ctrl+B F10 opens the menu while capture is active. Without a saved config, atm uses Ctrl+Shift+L to release capture so plain F10 can reach the menu; saved configs can change the release shortcut.
atm also builds a client-side tmux shim:
cargo build -p atm --bin tmuxWhen an app both enables the IPC socket and registers the terminal pane IPC handler, TerminalConfig.tmux.inject=true can make child processes see $TMUX / $TMUX_PANE and put the shim directory at the front of PATH. The shim supports common client commands such as send-keys, capture-pane -p, list-panes, split-window, select-pane, break-pane, and display-popup; it is not a tmux server implementation and does not support control mode (-CC).
Configuration is loaded from --config <path> when given, else ATTO_UI_TERMINAL_CONFIG when set, then $XDG_CONFIG_HOME/atto-ui/terminal.yaml, then ~/.config/atto-ui/terminal.yaml. See crates/atm/README.md for feature details, config examples, and focused validation commands.
The built-in theme presets are dark, light, and turbo. turbo provides the classic Turbo Vision palette with a blue desktop, gray dialog surfaces, cyan menu/status bars, and green selection highlights. Theme JSON/YAML files can opt into a preset before applying overlays:
base: turbo
colors:
widget-accent:
fg: yellowLaunch the editor app with optional files and folders. Folders become workspace roots; files open as tabs. If no folder is supplied, the first file's parent folder becomes the workspace root.
cargo run -p atto-editor-app -- .
cargo run -p atto-editor-app -- path/to/file.rs path/to/projectKey entry points:
| Shortcut | Action |
|---|---|
F10 |
Open the menu bar. |
Ctrl+Q |
Quit the terminal app. |
Ctrl+Shift+P |
Open the command palette. |
Ctrl+P |
Open the file picker. |
Ctrl+Shift+F |
Open global workspace search. |
Ctrl+Alt+K |
Start the app command prefix and show which-key choices. |
F8 / Shift+F8 |
Jump to next / previous diagnostic. |
Ctrl+. |
Request LSP code actions. |
F2 |
Rename symbol. |
Ctrl+Shift+Space |
Request signature help. |
Ctrl+K Ctrl+F |
Format the active document. |
F7 |
Toggle inlay hints. |
LSP support is opt-in. Set ATTO_EDITOR_LSP_CMD_<LANGID> (for example ATTO_EDITOR_LSP_CMD_RUST="rust-analyzer") or the fallback ATTO_EDITOR_LSP_CMD; commands are split on whitespace.
Launch the agent app from a checkout:
cargo run -p atto-agent-app -- --workspace .The app selects provider: deepseek when an API key is configured and --mock is not set; otherwise it selects provider: mock and shows a startup notice with next steps unless mock mode was explicitly forced. Live DeepSeek turns stream over HTTP/SSE through the same UI action path as the deterministic mock provider, including structured error display; default tests still avoid external network access. The ignored real-API smoke test can be run manually with:
DEEPSEEK_API_KEY=... cargo test -p atto-agent-app --test deepseek_real_smoke -- --ignoredUseful runtime options include --api-key, --base-url, --model, --temperature, --max-tokens, --workspace, --plan-mode, --config, --transcript, and --mock. Configuration may also come from DEEPSEEK_* / ATTO_AGENT_* environment variables, workspace .atto-agent.toml, and ~/.config/atto-agent/config.toml.
See crates/atto-agent-app/README.md for slash commands, tool and skill behavior, transcript persistence, and validation notes.
Build the local native binding before running JS examples or tests from a checkout:
npm run build --prefix crates/atto-ui-nodeLow-level @atto-ui/core usage:
const { AppHost, Button, Text, VStack } = require('@atto-ui/core')
const host = new AppHost({ headless: true, cols: 60, rows: 16 })
const callback = host.allocCallback()
const windowId = host.addDynamicWindow('Hello', [1, 1, 40, 8], VStack({ id: 'root' }, [
Text('Hello from atto-ui', { id: 'title' }),
Button({ id: 'ok', text: 'OK', onClick: callback }),
]))
host.step()
host.sendEvent(windowId, { type: 'key', key: 'enter' })
console.log(host.drainCallbacks())
host.dispose()React usage:
const React = require('react')
const { Button, Text, VStack, render } = require('@atto-ui/react')
function App() {
const [count, setCount] = React.useState(0)
return React.createElement(VStack, null,
React.createElement(Text, null, `Count: ${count}`),
React.createElement(Button, { onClick: () => setCount((value) => value + 1) }, 'Increment'),
)
}
const handle = render(React.createElement(App), { singleWindow: true })
process.once('SIGINT', () => handle.stop())npm run typecheck --prefix packages/core
npm test --prefix packages/core
npm run typecheck --prefix packages/react
npm test --prefix packages/reactRuntime compatibility smoke tests:
npm run test:runtime:node --prefix packages/core
npm run test:runtime:bun --prefix packages/core
npm run test:runtime:deno --prefix packages/coreThe Deno smoke requires --allow-read --allow-env --allow-run --allow-ffi; the package script supplies those permissions. On POSIX platforms, the Bun and Deno PTY smokes start a real raw-mode terminal app and assert that alternate screen, cursor, mouse capture, and terminal flags are restored on exit. The PTY raw-mode smoke is skipped on Windows.
docs/NODE_API.mddocuments the Node binding,@atto-ui/core, React package, component spec shape, events, and runtime compatibility notes.crates/atto-ui-terminal/README.mddocuments the terminal emulator component, full terminal viewer demo, settings model, shortcuts, and terminal-specific validation commands.docs/RELEASE.mddocuments CI coverage, the tag-based npm release workflow, and workspace-only app release scope.NODE_BINDING.mdis the design record for the Node binding and React host architecture.
CI runs Rust formatting, clippy, full Rust tests, native N-API build, Node/Core/React tests, React PTY/e2e coverage, Bun/Deno compatibility smokes, and npm pack dry-runs.
Publishing is tag-based. Pushing a v* tag runs the release workflow, first repeats the full Linux CI gate, then builds platform .node artifacts on macOS/Linux/Windows, verifies package contents, and publishes platform packages, @atto-ui/node, @atto-ui/core, and @atto-ui/react using NPM_TOKEN.
