CLI: Command-Line Interface
Nexion ships with a built-in CLI tool (nexion) that lets you control the app programmatically from any terminal. Manage tabs, send commands to terminals, automate browser actions, stream events, and more: all via a fast Unix socket connection.
Installation
The CLI binary is bundled inside Nexion.app. To install it system-wide:
This creates a symlink at /usr/local/bin/nexion pointing to the binary inside the app bundle. You can also run it directly:
Architecture
The CLI communicates with the running Nexion app over a Unix domain socket using the JSON-RPC 2.0 protocol with length-prefixed framing.
-
•
Socket:
~/.nexion/nexion.sock. User-scoped, created when Nexion starts. -
•
Authentication: Token-based. Nexion generates a random 32-byte token at startup, written to
~/.nexion/auth-token(mode0600). The CLI reads it automatically. - • Framing: 4-byte big-endian length prefix + UTF-8 JSON payload. This avoids ambiguity with newline-delimited JSON when payloads contain terminal output or HTML.
- • No dependencies: The CLI is a lightweight (~2 MB) standalone binary. It does not link GhosttyKit or any app frameworks.
Global Flags
| Flag | Description |
|---|---|
--json |
Output raw JSON instead of formatted tables |
--plain |
Output plain TSV (no colors, pipe-friendly) |
--socket |
Override the IPC socket path (default: ~/.nexion/nexion.sock) |
Quick Open & Browse
Top-level shortcuts to spin up a new project tab (terminal + file tree) or a new browser tab in one command. Both are convenience wrappers over tab new and activate the new tab by default.
nexion open
Open a directory as a new project tab. The path defaults to the current working directory, so you can cd into a project and run nexion open with no arguments.
nexion browse
Open a new browser tab. Bare hostnames are auto-prefixed with https://. Without an argument, opens the browser homepage.
Tab Commands
Tabs are unified: both project tabs and browser tabs are managed with the same commands.
nexion tab list
List all open tabs.
nexion tab active
Show information about the currently active tab.
nexion tab new
Create a new tab.
nexion tab activate
Switch to a tab by index or UUID.
nexion tab close / rename
Terminal Commands
Send text and key sequences to the terminal in the active pane.
Every one of these acts on the focused pane of the active tab unless you name a pane with --pane-id, which you get from nexion pane list --json. Without it a script writes wherever the user happens to be standing, which is what you want when the script is driving its own terminal and a trap everywhere else. A pane id that names nothing is an error, not a quiet fall back to the pane in front.
send-keys understands Ctrl-, Alt-, Shift- and Cmd- combinations, the arrows, Tab and Shift-Tab, Home, End, PageUp, PageDown, Delete, Escape, Enter and the function keys. A name it does not know is refused rather than typed out as text.
Keep Awake
Stop the Mac from sleeping while a job runs. Nexion does this on its own while a watched job holds a terminal's foreground, but a script that knows exactly how long it needs can say so.
awake status exits 0 when the Mac is being held awake and 1 when it is not, so if nexion awake status; then … works without parsing anything. With --json it reports active, source (manual or automatic), remainingSeconds when a timer is running, and reason naming the job responsible when Nexion decided on its own.
awake off releases your hold only. Anything Nexion is holding open on its own, because an agent or a build is still running, carries on: turning off your own request should not tell the Mac to sleep through somebody else's work.
Notify Commands
Send notifications to Nexion from any script, terminal, or AI agent. Notifications appear as a floating bubble, tab badge, native macOS notification, and optional sound.
nexion notify
Send a notification message to the current tab.
AI Agent Hooks
Nexion installs wrapper scripts at ~/.nexion/bin/ for Claude Code and Codex. These wrappers inject notification hooks so Nexion is notified when the agent finishes a task or needs your attention: no configuration required.
-
•
Claude Code: Hooks into
NotificationandStoplifecycle events via--settings -
•
Codex: Injected via
-c notify=[...]configuration flag -
•
Other agents: Use
nexion notifyor the IPC methodai.notificationdirectly
Session Context & MCP
Inside a Nexion pane, an agent also learns the workspace automatically. The Claude Code wrapper injects a session-start hook that names what the workspace can do (it re-fires after /clear and compaction, so the context never disappears mid-session), and wires up an MCP server that hands the agent typed tools.
That hook is a table of contents, not a manual. It is paid for by every session in every pane, so what stays in it is the identity of the workspace and the two things an agent would never think to ask about: how to hand you a file, and how to get your attention. Everything else lives in pages the agent pulls when it needs one, which is also the only way the detail that matters could exist at all — that simulator screenshots are pixels while its input is points, which wait condition is the useless one, what makes a canvas render empty boxes. None of that fits in a document injected into every session.
-
•
nexion agent-context: prints the document injected at session start. Add--hook-jsonfor the raw hook envelope. -
•
nexion skills list|search <question>|get <id>, and the same as askillsMCP tool: the pages themselves. Ten of them, one per part of the workspace, searchable in the words somebody would actually type. They are read-only text, so the tool executes nothing, needs no permission, and answers even when the app is not running. Plugins contribute pages under their ownplugin.<id>.namespace, which is the first way a plugin’s commands are documented to an agent at all. -
•
nexion mcp: a Model Context Protocol server over stdio, exposing 50 typedmcp__nexion__*tools (attach, notify, and the simulator, browser, canvas, notes and keep-awake surface) that bridge to the running app. Registered for Claude Code, Codex, Gemini and opencode; a self-definednexionserver is left untouched. The browser and canvas tools take an optionalpane_id, so an agent can work in one pane of a split rather than a whole tab; omit it when the tab holds exactly one pane of that kind.
What Agents Cannot Do
The MCP surface is narrower than the CLI on purpose. There is no tool to type into a terminal, run a command, change configuration, load a plugin, or touch git or SSH. An agent talked into misbehaving by something it read cannot make Nexion execute anything, and that is a property of the tool list rather than a promise.
Page content is the obvious way to talk an agent into something, so browser_text, browser_html and browser_find return their result wrapped and labelled as untrusted data, carrying the URL it came from. An agent that finds an instruction inside is told to report it rather than follow it.
Three tools sit outside that comfort and are off until you turn them on in Settings → Privacy → Agent tools:
-
•
browser_evalruns JavaScript inside whatever session the pane is logged into. -
•
browser_historyandbrowser_history_forgetread where you have been, and delete the record of it.
While off they are not refused, they are absent from tools/list: a tool that exists and fails teaches a model to rephrase and retry, one that was never offered gives it nothing to work with. The same panel withdraws agent access altogether, which removes Nexion’s entry from the agent config files it wrote rather than merely refusing calls.
Shell Integration
Nexion installs a shell integration script at ~/.nexion/shell-integration.zsh (sourced from .zshrc). It provides automatic notifications without any manual setup:
-
•
Long command completion: Commands running longer than
NEXION_NOTIFY_THRESHOLD(default: 30s) emit a notification with exit status when they finish -
•
Idle detection: Commands running longer than
NEXION_IDLE_THRESHOLD(default: 5s) trigger an alert: useful forsudo,python input(), and other programs waiting for user input -
•
Smart filtering: Interactive commands (
vim,ssh,tmux) and fast commands (ls,cd,git) are automatically excluded
Configure thresholds in your .zshrc (before the Nexion source line):
tmux Support
Notifications work inside tmux sessions managed by Nexion. Environment variables (NEXION_PANE_ID, NEXION_SOCKET, etc.) are automatically propagated into the tmux session environment via setenv, so new panes and splits inherit them. Both the IPC notification path (Claude/Codex hooks) and the shell integration path (OSC 9) work correctly in tmux.
IPC Method: ai.notification
For direct socket integration, send a JSON-RPC request to the ai.notification method:
Resolution priority: pane_id (exact match) → project_id (tab UUID) → project_path (prefix match). At least one must be provided.
Pane Commands
Control split panes within the active tab. The same pane commands work for terminal (project) tabs and web browser tabs: the handler dispatches by tab kind. Browser tabs add a --url seed on split and a pane move with quadrant drop zones; terminal-specific ops (zoom, layout, resize, rotate, break) stay terminal-only.
pane detach moves the live pane, it does not open a second one: the shell keeps running, a page keeps the form you had half filled in, a drawing keeps its editor. Closing that window is what brings the pane home, and pane recall is the same thing from a script. A pane returns as a split beside the pane you are in now; a tab that left whole returns to its own row, in its project and its space.
pane list returns a uniform shape across tab kinds: terminal panes carry cwd, browser panes carry url, title, and profile_id, and canvas panes carry file and dirty. Terminal panes also report what they are running: foregroundJob (absent when the pane is sitting at a shell prompt), sshTarget for an ssh typed by hand, and agentState of working, waiting or idle. Web and canvas panes each cap at 4 per tab; pane split returns an explicit error when a cap is hit. A canvas pane needs a file: --kind canvas --file creates it when it is not there yet, since a drawing with nowhere to save would be a pane that loses work.
Browser Commands
Automate the built-in web browser from the command line. Every browser action defaults to the focused leaf; pass --pane (0-based DFS) to target a specific pane inside a split tab. browser state is the canonical aggregate read: it replaces the old browser url and browser title.
Simulator Commands
Drive the iOS Simulator from the command line: list and boot devices, capture the screen into the Attach card, install and launch apps, open deep links, and send test pushes. Available on machines with Xcode installed.
An AI agent can drive the device UI too. The see-and-tap verbs need a one-time nexion sim setup (a small verified tool download):
The loop for an agent is: screenshot, read the image, tap --pixels where it wants, then screenshot again to confirm. Coordinates read off a screenshot are in pixels, so pass --pixels; without it, taps are interpreted in points.
Event Streaming
Subscribe to real-time events from Nexion. Events are streamed as JSON-RPC notifications over a persistent connection.
Each event is a single JSON line, making it easy to pipe to jq or other tools:
Available event types:
| Category | Events |
|---|---|
tab.* | created, closed, activated, renamed |
pane.* | created, closed, focused |
terminal.* | cwd-changed, shell-exited |
editor.* | mode-changed |
browser.* | navigated, loaded, title-changed |
workspace.* | activated, saved |
git.* | status-changed, branch-changed |
plugin.* | activated, deactivated |
config.* | changed |
Other Commands
Workspace
SSH
Configuration
Values are typed on the way in. A bare true or false is stored as a yes/no, a bare number as a number, and anything else as text. Quote it to force text where a value would otherwise be read as one of those: nexion config set some.key '"true"'.
A setting written this way takes effect immediately, exactly as if you had changed it in Preferences: the listener starts, the timer re-arms, the assertion drops. No relaunch.
Agents talking to each other
One agent can find the others, read what they said, send them something and wait for the answer. The same five things are a typed panes tool over MCP, so an agent uses them without shelling out.
Read the transcript, not the screen. --source transcript returns what the agent SAID, from the file it writes its conversation in, as turns with roles. An agent terminal redraws in place, so its screen is not what it said: measured while building this, an agent answered a question twice and its screen showed an empty input box both times. Claude Code, Codex, Gemini and opencode are read; screen, history and output remain for everything else.
Wait for a reply, not for quiet. --for reply ends when a new turn from that agent appears, and hands the text back with it. idle is already true the moment before an agent starts, and --for text matches raw output, where a phrase a TUI paints in colour is split by escape sequences: wait for a marker you control, not for a phrase the model chose.
Some agents cannot be read at all. Copilot and Antigravity keep what the agent DID, the files it touched and the commands it ran, and nowhere the words it said. Those answer by reporting: terminal report posts a structured result on their own pane, and a --for reply waiting there receives it. It costs one call and works for anything that can make one.
Writing is off until you allow it. panes send over MCP asks for Settings > Privacy > Agent tools > "Allow agents to type into each other's panes", off by default. Reading, listing, waiting and reporting never ask. And a pane with no agent in it refuses text unless the caller says to_shell: at a prompt a sentence is not read, it is run.
A handle to an agent, not to a pane. Every listing carries an occupancy token, , that changes when the agent in that pane is replaced. Pass it back on a write and a pane whose occupant has changed refuses instead of accepting your sentence on the new one's behalf.
Git
git status and git branch answer from a fresh look whenever anything in the working tree has moved since the last one, so an agent that commits and asks in the same breath is not handed the state from before its own commit. On a quiet tree the answer comes from what is already known. Measured on the Nexion repository: 47 ms for a question about a quiet tree, 137 ms for one right after a change. git refresh is still there for the rest of the app.
Notes
Spaces
A space is a context: its own projects, its own tabs, its own colour and browser profile. A script can build one and drop you into it.
Projects and tasks
A project is a folder with a list of tasks that outlives the tabs. A task is one piece of work: its tab, its worktree when it has one, and the agent conversation it carries. Closing a task keeps the record, so it can be reopened where it left off.
A task is named by its title, or with --id when two share one; --project narrows the search. With --branch a new task gets a git worktree of its own and the project's setup entries are linked into it, so two tasks on the same repository never fight over one checkout.
Devices
The phones and iPads paired with this Mac, which was the one thing in the app a script could not reach.
Window
There is one window, and these speak about it. The contexts you switch between are spaces, above. A --window flag on any command is still accepted and ignored, so scripts written before spaces keep working.
Editor
Control the built-in code editor and inspector pane.
Attachments
Manage file attachments in the active project tab. Files are stored in .nexion/attachments/ inside the project root.
Icon Packs
Manage file icon themes.
Plugins
plugin list reports two different facts: enabled is what you asked for, and ACTIVE is what is actually running. They differ when a plugin fails to load, and by design when Nexion was started without its plugins: hold Shift while it launches, or pass --safe-mode, or set NEXION_SAFE_MODE=1. nexion version --json says whether this is such a start, and why.
Theme
Switch appearance mode and theme presets on the fly.
Status & Version
Uninstall
To make notifications and MCP tools work, Nexion writes itself into places outside its own folder: a # Nexion block in ~/.zshrc, a claude shim, and server or hook entries in Codex, Gemini and opencode. Dragging the app to the Trash leaves all of it behind, still launching a server for an app that is gone.
Removal is surgical: entries are matched by the markers and paths the installer wrote, so a nexion server you configured by hand, and anybody else's hooks in the same files, survive. Your data in ~/.nexion is never touched, only the integration artifacts. Open a new terminal afterwards for the shell change to land.
Environment Variables
Nexion injects environment variables into every terminal session, so scripts and tools can detect and interact with the app:
| Variable | Example | Description |
|---|---|---|
NEXION | 1 | Always set: detect if running inside Nexion |
NEXION_VERSION | 2026.8 | App version (YYYY.M) |
NEXION_SOCKET | ~/.nexion/nexion.sock | IPC socket path |
NEXION_TOKEN_FILE | ~/.nexion/auth-token | Auth token file path |
NEXION_PROJECT_ID | a1b2c3d4-... | UUID of the parent tab |
NEXION_PROJECT_PATH | /path/to/project | Project root directory |
NEXION_PROJECT_NAME | MyProject | Tab / project name |
NEXION_PANE_ID | e5f6g7h8-... | UUID of the terminal pane |
You can use these in shell scripts to build Nexion-aware workflows:
IPC Protocol
For advanced integrations, you can bypass the CLI and talk to Nexion’s IPC socket directly. This is useful for building autonomous workflows, MCP servers, or custom integrations that need low-latency, bidirectional communication.
The protocol is straightforward: connect to the Unix socket, authenticate, and send JSON-RPC 2.0 messages with length-prefixed framing. Every method available via nexion maps to a JSON-RPC method (e.g. nexion tab list → tab.list).
Connection Details
| Socket | ~/.nexion/nexion.sock, or read $NEXION_SOCKET |
| Auth token | ~/.nexion/auth-token, or read $NEXION_TOKEN_FILE. File is mode 0600, regenerated on each app launch. |
| Framing | 4-byte big-endian uint32 length prefix, followed by UTF-8 JSON payload. |
| Timeout | Unauthenticated connections are dropped after 2 seconds. |
Handshake
The first message on every connection must be an auth request:
Request & Response
Event Subscription
After authenticating, you can subscribe to events. Nexion will push notifications (no id field) over the same connection:
Method Reference
Every CLI command maps to a JSON-RPC method using dot notation. Parameters are passed as a JSON object:
| CLI Command | JSON-RPC Method | Params |
|---|---|---|
nexion tab list | tab.list | {"type": "project"} (optional) |
nexion tab new --path /foo | tab.new | {"path": "/foo"} |
nexion tab activate 2 | tab.activate | {"index": 2} or {"id": "uuid"} |
nexion tab close | tab.close | {"id": "uuid"} (optional) |
nexion terminal send "text" | terminal.send | {"text": "echo hello"} |
nexion terminal send-keys "Ctrl-C" | terminal.sendKeys | {"keys": "Ctrl-C"} |
nexion pane list | pane.list | {}. Works for terminal & browser tabs. |
nexion pane split --dir v | pane.split | {"dir": "v", "kind": "web|canvas", "url": "https://…", "file": "….excalidraw", "private": true, "profile_id": "…"}. url is web-only, file canvas-only and required, private / profile_id web-only and optional. |
nexion pane move --target-index N --zone right | pane.move | {"target_index": 0, "zone": "left|right|top|bottom"}. Browser-only. |
nexion browser navigate | browser.navigate | {"url": "https://…", "pane": 1}. pane is optional. |
nexion browser state | browser.state | {"pane": 0}. Aggregate read; replaces browser.url & browser.title. |
nexion browser eval "js" | browser.eval | {"script": "document.title"} |
nexion config get | config.get | {"key": "terminal.theme"} |
nexion config set | config.set | {"key": "terminal.theme", "value": "dracula"} |
nexion ssh bookmarks | ssh.bookmarks | {} |
nexion ssh connect user@host | ssh.connect | {"host": "user@host"}, {"port": 2222} (optional) |
nexion ssh disconnect host | ssh.disconnect | {"host": "..."}, {"user": "root"}, {"port": 2222} (all optional; omit the host for all). A connection is identified by user, host and port together, so a host on a non-default port needs them. |
nexion ssh status | ssh.status | {} |
nexion editor open | editor.open | {"path": "src/main.swift"}, {"mode": "edit"} (optional) |
nexion editor status | editor.status | {} |
nexion editor mode | editor.mode | {"mode": "edit"} |
nexion editor save | editor.save | {} |
nexion attach list | attach.list | {} |
nexion attach add | attach.add | {"paths": ["/abs/path"]} |
nexion attach remove | attach.remove | {"name": "file.png"} |
nexion attach clear | attach.clear | {} |
nexion attach open | attach.open | {"name": "file.png"} |
nexion icon-pack list | icon-pack.list | {} |
nexion icon-pack activate | icon-pack.activate | {"id": "catppuccin-mocha"} |
nexion icon-pack deactivate | icon-pack.deactivate | {} |
nexion theme dark --terminal "X" | theme.apply | {"mode": "dark"}, {"terminal": "..."} {"preview": "..."} (optional) |
nexion event subscribe "tab.*" | event.subscribe | {"pattern": "tab.*"} |
Python Example
Node.js Example
Shell Completions
Generate completions for your shell:
LLM Integration
A plain-text CLI reference is available at /llms.txt for use with LLMs and AI coding agents. This is a compact, token-efficient version of the full CLI documentation: ideal for feeding to Claude, ChatGPT, Copilot, or any other AI assistant.
Claude Code
Add the following to the CLAUDE.md file in your project root so Claude Code can use the Nexion CLI:
OpenAI Codex
Add the reference to your AGENTS.md file (Codex reads this automatically):
ChatGPT
When using ChatGPT with Code Interpreter or browsing enabled, paste this at the start of your conversation:
You can also use ChatGPT’s Custom Instructions or a GPT’s system prompt to include the reference permanently.
OpenClaw
Add the reference to your system prompt or workspace instructions:
Any Other Agent
For any LLM or agent that can execute shell commands, you can fetch the reference inline: