CLI Companion
oxt CLI is a standalone command-line binary that communicates with the running OxideTerm GUI via IPC (Inter-Process Communication). From any terminal or shell script, you can:
- Query OxideTerm status and health information
- List connections, sessions, local terminals, and port forwards
- Perform session operations: connect, disconnect, focus, attach (mirror)
- Create/delete port forwarding rules
- Read configuration (groups, connection details)
- Use AI (
ask/exec) with streaming output, conversation continuation, and terminal Markdown rendering
Key design decisions:
- Standalone binary:
oxtis a separate Rust crate (cli/), not compiled into the Tauri backend, keeping the CLI lightweight (~1 MB) and decoupled from the GUI lifecycle. - Bundled, not auto-installed: The CLI binary ships as a Tauri resource inside the app bundle. Users install it via Settings → General → CLI Companion, which creates a symlink on Unix or copies the binary to
~/.local/bin/oxton Windows. - IPC, not HTTP: Communication uses Unix Domain Socket (macOS/Linux) or Named Pipe (Windows) — no network exposure, no port conflicts, no TLS overhead.
Architecture
Section titled “Architecture”┌─────────────┐ JSON-RPC 2.0 ┌─────────────────┐│ oxt CLI │◄── (newline-delimited) ────►│ OxideTerm GUI ││ (Rust bin) │ │ cli_server │└──────┬──────┘ └────────┬────────┘ │ │ Unix Socket Tokio async ~/.oxideterm/oxt.sock accept loop │ │ Named Pipe (Windows) methods.rs \\.\pipe\OxideTerm-CLI-{user} ├─ status/list/health methods ├─ session & tab operations ├─ forwarding & config methods └─ ask (streaming AI)Module Structure
Section titled “Module Structure”Server (src-tauri/src/cli_server/):
| Module | Responsibility |
|---|---|
mod.rs | CliServer — lifecycle management (start, accept loop, shutdown) |
transport.rs | IpcListener / IpcStream — abstraction over Unix Socket and Named Pipe |
handler.rs | Per-connection request loop with read limits and idle timeout |
protocol.rs | JSON-RPC 2.0 Request, Response, RpcError, Notification types |
methods.rs | RPC method dispatch and implementation |
Client (cli/src/):
| Module | Responsibility |
|---|---|
main.rs | Clap CLI entry point, command definitions |
connect.rs | IpcConnection — synchronous IPC client (Unix Socket / Named Pipe) |
protocol.rs | Client-side JSON-RPC request/response types |
output.rs | OutputMode — human-readable or JSON output formatting |
GUI integration (src-tauri/src/commands/cli.rs):
| Command | Responsibility |
|---|---|
cli_get_status | Returns bundled/installed status for the settings UI |
cli_install | Creates symlink on Unix, copies binary on Windows to install path |
cli_uninstall | Removes the installed CLI binary |
Communication Protocol
Section titled “Communication Protocol”Transport Format
Section titled “Transport Format”- Transport layer: Unix Domain Socket or Named Pipe
- Frame format: Newline-delimited JSON (one JSON object per line, terminated by
\n) - Encoding: UTF-8
JSON-RPC 2.0
Section titled “JSON-RPC 2.0”Request example:
{"id": 1, "method": "status", "params": {}}Success response:
{"id": 1, "result": {"version": "0.21.0", "sessions": 5, "connections": {"ssh": 3, "local": 2}, "pid": 12345}}Error response:
{"id": 1, "error": {"code": -32601, "message": "Method not found: foo"}}Error Codes
Section titled “Error Codes”| Code | Constant | Meaning |
|---|---|---|
-32600 | ERR_INVALID_REQUEST | Malformed JSON-RPC request |
-32601 | ERR_METHOD_NOT_FOUND | Unknown method name |
-32602 | ERR_INVALID_PARAMS | Invalid method parameters |
-32603 | ERR_INTERNAL | Internal server error |
1001 | ERR_NOT_CONNECTED | No active connection (reserved) |
1003 | ERR_TIMEOUT | Operation timed out (reserved) |
Available Methods (17 + 1 Streaming)
Section titled “Available Methods (17 + 1 Streaming)”Note:
askis classified as a streaming method (the server pushes incremental text viastream_chunknotifications). All others are standard JSON-RPC request/response.
Method Overview
Section titled “Method Overview”| Category | Methods |
|---|---|
| Status/Probe | status, ping |
| List/Query | list_saved_connections, list_sessions, list_active_connections, list_forwards, list_local_terminals, health |
| Session/Tab Ops | disconnect, connect, open_tab, focus_tab, attach |
| Forwarding | create_forward, delete_forward |
| Config | config_list, config_get |
| AI (streaming) | ask |
status
Section titled “status”Returns a status summary of the OxideTerm instance.
Parameters: {}
Response:
{ "version": "0.21.0", "sessions": 5, "connections": { "ssh": 3, "local": 2 }, "pid": 12345}list_saved_connections
Section titled “list_saved_connections”Returns all saved connection configurations (from connections.json).
Parameters: {}
Response:
[ { "name": "prod-server", "host": "10.0.1.5", "port": 22, "username": "deploy", "auth_type": "key" }]Note: Passwords and private key contents are never included in responses.
list_sessions
Section titled “list_sessions”Returns all active sessions from the SessionRegistry.
Parameters: {}
Response:
[ { "id": "abc123...", "name": "prod-server", "host": "10.0.1.5", "state": "active", "uptime_secs": 3600 }]list_active_connections
Section titled “list_active_connections”Returns all active SSH connections from the SshConnectionRegistry.
Parameters: {}
Response:
[ { "id": "conn-456...", "host": "10.0.1.5", "port": 22, "username": "deploy", "state": "active", "ref_count": 2 }]list_forwards
Section titled “list_forwards”Lists port forwarding rules, optionally filtered by session.
Parameters: { "session_id": "abc123" } or {} (list all)
Response:
[ { "session_id": "abc123...", "id": "fwd-456...", "forward_type": "local", "bind_address": "127.0.0.1", "bind_port": 8080, "target_host": "localhost", "target_port": 80, "status": "active", "description": "Web server" }]health
Section titled “health”Gets connection health status.
Parameters: { "session_id": "abc123" } (single session) or {} (all sessions)
Single session response:
{ "session_id": "abc123...", "status": "healthy", "latency_ms": 42, "message": "Connected • 42ms"}All sessions response:
{ "abc123...": { "session_id": "abc123...", "status": "healthy", "latency_ms": 42, "message": "Connected • 42ms" }, "def456...": { "session_id": "def456...", "status": "degraded", "latency_ms": 350, "message": "High latency: 350ms" }}Health status values:
healthy,degraded,unresponsive,disconnected,unknown
disconnect
Section titled “disconnect”Disconnects a specific session. Supports matching by session ID or name.
Parameters: { "target": "abc123" } or { "target": "prod-server" }
Response:
{ "success": true, "session_id": "abc123..."}The disconnect operation executes in sequence: persist terminal buffer → stop port forwards → close SSH session → clean up WebSocket bridge → clean up SFTP cache → clean up health checker → release connection pool.
Connectivity check, returns immediately.
Parameters: {}
Response:
{"pong": true}list_local_terminals
Section titled “list_local_terminals”Returns the list of local terminal sessions (available when the local-terminal feature is enabled).
Parameters: {}
Response example:
[ { "id": "local-123...", "shell_name": "zsh", "cwd": "/Users/name/project" }]connect
Section titled “connect”Matches a saved connection by name/ID/host and initiates a connection (opens a session in the GUI).
Parameters: { "target": "prod-server" }
open_tab
Section titled “open_tab”Opens a new local terminal tab.
Parameters: { "path": "/path/to/workdir" }
focus_tab
Section titled “focus_tab”Focuses an existing tab (SSH session or local terminal).
Parameters: { "target": "session-id-or-name" }
attach
Section titled “attach”Attaches to a running session (SSH/local) for terminal mirroring.
Parameters: { "session_id": "abc123" }
Response example:
{ "ws_url": "ws://127.0.0.1:55321", "ws_token": "single-use-token", "terminal_type": "ssh", "cols": 160, "rows": 48}create_forward
Section titled “create_forward”Creates a port forwarding rule for a session (local / remote / dynamic).
delete_forward
Section titled “delete_forward”Deletes a specific port forwarding rule.
config_list
Section titled “config_list”Lists connection groups and statistics.
config_get
Section titled “config_get”Queries a single connection’s details (passwords and private key contents are never returned).
ask (Streaming)
Section titled “ask (Streaming)”AI question interface. Supports:
context(stdin piped context)session_id(attach terminal buffer context)model,provideroverridesstreamstreaming outputconversation_idfor continuation
During streaming, the server sends:
{"method":"stream_chunk","params":{"text":"..."}}Final response example:
{ "text": "...", "model": "moonshot-v1-8k", "done": true, "conversation_id": "b1d0..."}CLI Usage
Section titled “CLI Usage”Installation
Section titled “Installation”The CLI tool is bundled inside the OxideTerm app package. To install:
- Open OxideTerm → Settings → General
- Find the CLI Companion section
- Click Install
This creates a symlink on macOS/Linux or copies the binary on Windows.
Install paths:
- macOS/Linux:
~/.local/bin/oxt - Windows:
%LOCALAPPDATA%\OxideTerm\bin\oxt.exe
Make sure the install directory is in your $PATH.
Commands
Section titled “Commands”# Check OxideTerm statusoxt status
# List saved connectionsoxt list connections
# List active sessionsoxt list sessions
# List all port forwardsoxt list forwards
# List forwards for a specific sessionoxt list forwards <session-id>
# View health status of all sessionsoxt health
# View health of a specific sessionoxt health <session-id>
# Disconnect a session (by ID or name)oxt disconnect <session-id-or-name>
# Connectivity checkoxt ping
# AI question (streaming by default)oxt ask "explain this log"
# Pipe context from stdinecho "$(cat app.log)" | oxt ask "find root cause"
# Force raw text output (no Markdown rendering)oxt ask --raw "show me command"
# Continue a conversationoxt ask --continue <conversation-id> "give me safer variant"
# Code/command generation mode (code-first output)oxt exec "write a bash script to rotate logs"
# Connect by name and open in GUIoxt connect prod-server
# Open a local terminal tab (optional path)oxt open ~/work
# Focus a taboxt focus <session-id-or-name>
# Attach to mirror a sessionoxt attach <session-id-or-name>
# Create/delete port forwardsoxt forward add 8080:localhost:80 --session <session-id>oxt forward remove <forward-id> --session <session-id>
# Config queriesoxt config listoxt config get <connection-name-or-id>
# Show versionoxt version
# Generate shell completion scriptsoxt completions bashoxt completions zshoxt completions fishoxt completions powershellGlobal Options
Section titled “Global Options”| Option | Default | Description |
|---|---|---|
--json | Auto-detect | Force JSON output (default: JSON when piped, human-readable in terminal) |
--timeout <ms> | 30000 | IPC timeout in milliseconds |
--socket <path> | Platform default | Custom socket/pipe path (for debugging) |
Output Modes
Section titled “Output Modes”The CLI auto-detects whether stdout is a terminal:
- Terminal → Human-readable formatted tables
- Pipe/redirect → Compact single-line JSON
For AI output (oxt ask):
- TTY without
--raw: Rendered as Markdown with ANSI after completion - Pipe/redirect or
--raw: Raw text output
Use --json to always force JSON output.
Examples
Section titled “Examples”# Human-readable status$ oxt statusOxideTerm v1.3.3 Sessions: 5 active Connections: 3 SSH, 2 local
# JSON output (for scripting)$ oxt status --json{"version":"1.3.3","sessions":5,"connections":{"ssh":3,"local":2},"pid":12345}
# List connections in scripts$ oxt list connections --json | jq '.[].host'"10.0.1.5""192.168.1.100"
# View all session health$ oxt health SESSION STATUS LATENCY MESSAGE abc123de... healthy 42ms Connected • 42ms def456gh... degraded 350ms High latency: 350ms
# Check for unhealthy sessions in monitoring scripts$ oxt health --json | jq 'to_entries[] | select(.value.status != "healthy")'
# List port forwards$ oxt list forwards SESSION TYPE BIND TARGET STATUS DESC abc123de local 127.0.0.1:8080 localhost:80 active Web server abc123de dynamic 127.0.0.1:1080 SOCKS5 active SOCKS5 Proxy
# Disconnect a session (by name)$ oxt disconnect prod-serverDisconnected session: abc123...
# Check if OxideTerm is running$ oxt ping && echo "Running" || echo "Not running"
# Generate and install zsh completions$ oxt completions zsh > ~/.zfunc/_oxt
# Custom timeout$ oxt status --timeout 5000
# AI: streaming + Markdown terminal rendering$ oxt ask "explain pwd command"
# AI: use raw for scripts/pipes$ oxt ask --raw "generate curl command" | pbcopy
# AI: multi-turn conversation$ oxt ask "summarize this" --provider openai...Conversation: b1d0...$ oxt ask --continue b1d0... "give me concise version"
# Attach: session mirroring, `~.` to exit$ oxt attach prod-serverEnvironment Variables
Section titled “Environment Variables”| Variable | Platform | Description |
|---|---|---|
OXIDETERM_SOCK | macOS/Linux | Override default socket path |
OXIDETERM_PIPE | Windows | Override default named pipe path |
Security
Section titled “Security”IPC Transport Security
Section titled “IPC Transport Security”- Unix Socket: Created at
~/.oxideterm/oxt.sockwith permissions0o600(owner read/write only) - Named Pipe:
\\.\pipe\OxideTerm-CLI-{username}— scoped to the current user - Stale socket detection: On startup, the server probes any existing socket. If unreachable, the stale socket is deleted; if reachable, startup fails with “another OxideTerm instance is running”.
Resource Limits
Section titled “Resource Limits”| Limit | Value | Purpose |
|---|---|---|
| Max concurrent connections | 16 | Prevent resource exhaustion (Semaphore) |
| Max request size | 1 MB | Bounded line reads, prevent memory abuse |
| Max response size | 4 MB | Client .take() limit |
| Idle timeout | 60 seconds | Disconnect inactive clients |
AI-specific limits:
| Limit | Value | Description |
|---|---|---|
MAX_PROMPT_SIZE | 50 KB | Single prompt size cap |
MAX_CONTEXT_SIZE | 500 KB | stdin context size cap |
MAX_TERMINAL_BUFFER_LINES | 2,000 lines | Session buffer injection cap |
| Streaming read timeout | 180 seconds | oxt ask client read timeout |
Data Exposure Principles
Section titled “Data Exposure Principles”- No RPC method ever returns passwords
list_saved_connectionsexposes private key file paths only, never key contents- The IPC server binds only to local IPC, never opens any network-accessible port
Build & Distribution
Section titled “Build & Distribution”Local Build
Section titled “Local Build”# Check compilationcd cli && cargo check
# Release build (size-optimized)cd cli && cargo build --releaseThe release profile uses lto = true, strip = true, opt-level = "z", producing a binary of approximately 1 MB.
CI Integration
Section titled “CI Integration”The CLI is automatically built and packaged for all 6 platform targets in GitHub Actions:
- Build step: Runs
cargo build --release --target ${{ matrix.target }}in thecli/directory - Copy: Binary placed in
src-tauri/cli-bin/ - Packaging:
tauri.conf.json’sbundle.resourcesincludes the"cli-bin/*"glob, so Tauri automatically bundles matching CLI binaries as resources - Distribution: Tauri includes the CLI binary in the final
.app/.deb/.exeinstaller
During local pnpm tauri dev, the cli-bin/ directory doesn’t exist; the glob matches zero files, causing no errors.
Supported Targets
Section titled “Supported Targets”| Platform | Build Target | Binary Name |
|---|---|---|
| macOS (ARM) | aarch64-apple-darwin | oxt |
| macOS (Intel) | x86_64-apple-darwin | oxt |
| Linux (x64) | x86_64-unknown-linux-gnu | oxt |
| Linux (ARM) | aarch64-unknown-linux-gnu | oxt |
| Windows (x64) | x86_64-pc-windows-msvc | oxt.exe |
| Windows (ARM) | aarch64-pc-windows-msvc | oxt.exe |
Version Sync
Section titled “Version Sync”The CLI version stays in sync with OxideTerm via pnpm version:bump, which simultaneously updates cli/Cargo.toml, package.json, src-tauri/Cargo.toml, and src-tauri/tauri.conf.json.
GUI Integration
Section titled “GUI Integration”Settings UI
Section titled “Settings UI”The CLI Companion section is located in Settings → General:
- Status badge: Shows “Installed”, “Not Installed”, or “Not Bundled”
- Install button: Symlinks/copies the bundled CLI to
~/.local/bin/oxt - Uninstall button: Removes the installed CLI binary
- Auto-detect: Status refreshes automatically when the General tab is opened
Tauri Commands
Section titled “Tauri Commands”| IPC Command | Description |
|---|---|
cli_get_status | Returns { bundled: bool, installed: bool, install_path: string } |
cli_install | Installs the CLI from bundled resources to the install path |
cli_uninstall | Removes the installed CLI binary |
Resource Resolution Order
Section titled “Resource Resolution Order”find_bundled_cli() searches for the CLI binary in order:
- Tauri resource path:
cli-bin/{binary_name}(fromtauri.conf.jsonstatic config) - Direct resource path:
{binary_name}(fallback) - Executable directory:
{exe_dir}/{binary_name}(development fallback)
Server Lifecycle
Section titled “Server Lifecycle”Startup
Section titled “Startup”The CLI server starts automatically in OxideTerm’s Tauri setup callback:
App startup → setup() → CliServer::start(app_handle) → spawn accept loopStartup failure does not affect the main app: if the IPC server fails to bind (e.g., another instance is running), the GUI continues normally and the error is logged.
Shutdown
Section titled “Shutdown”On app exit, the CLI server shuts down gracefully:
App exit → server.shutdown() → oneshot signal → accept loop exits → socket cleanupShutdown uses the Mutex<Option<oneshot::Sender>> pattern for reliable single-signal delivery.
Connection Handling
Section titled “Connection Handling”Each CLI connection runs in an independent Tokio task:
Accept connection → Semaphore permit → spawn task → read/dispatch/respond loop → release permitThe Semaphore limits concurrent connections to 16; excess connections are immediately rejected.
Shell Completions
Section titled “Shell Completions”oxt has built-in shell completion script generation via clap_complete, supporting bash, zsh, fish, and PowerShell.
Installing Completions
Section titled “Installing Completions”Bash:
oxt completions bash > ~/.local/share/bash-completion/completions/oxtZsh:
# Ensure ~/.zfunc is in fpath (add fpath=(~/.zfunc $fpath) to .zshrc)oxt completions zsh > ~/.zfunc/_oxtFish:
oxt completions fish > ~/.config/fish/completions/oxt.fishPowerShell:
oxt completions powershell >> $PROFILEReload your shell after installation to enable Tab completion.
Future Plans
Section titled “Future Plans”Features that may be added in future releases:
oxt transfer <name> <local> <remote>— SFTP file transfersoxt config set— Modify connection configuration from the command lineoxt chat— Interactive multi-turn REPL (currentlyask --continue)- Server-initiated push notifications (event streams)
- Dynamic shell completions (live querying of connection names and session IDs)
You can add the binary location to your PATH for convenient access from any terminal.
Use Cases
Section titled “Use Cases”Scripting
Section titled “Scripting”#!/bin/bash# Check if OxideTerm is running before attempting operationsif oxt status --json | jq -e '.status == "running"' > /dev/null 2>&1; then echo "OxideTerm is running" oxt list --json | jq '.sessions[] | .host'fiMonitoring
Section titled “Monitoring”# Watch active sessions (refresh every 5s)watch -n 5 oxt list