@ramtinj95/pi-nexus0.8.0
Drive Nexus threads from Pi through the nexus CLI
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-nexus
Nexus runs coding agents in per-thread VMs, called pylons, on home-lab nodes. @ramtinj95/pi-nexus lets you list, start, message, watch and manage those threads from a Pi session instead of the dashboard. It also gives Pi's model tools to start a thread, message it and wait for its reply.
The extension talks to Nexus only through the nexus CLI, run as a child process with --json. The CLI owns the control plane's URL and proxy, so this package has no network configuration of its own. The CLI's contract is docs/cli.md in the Nexus repository.
Install
Changes in pi-nexus 0.8.0 needs Nexus CLI 0.6.0 or later (nexus changes and nexus diff). Install the nexus CLI on this Mac first and check that nexus threads works. pi-nexus 0.4.0 needs a Nexus control plane and CLI at or after nexus#36, which stream a thread's conversation and report its ports and preview URLs. With an older one, a reply without the new fields, such as show or a watch's state, fails as a contract error that names the missing field. Unpublishing a preview needs CLI 0.3.0, from nexus#39 or later, which adds nexus unpreview. An older CLI fails an unpublish with a message to update it. Adding a project needs CLI 0.4.0 or later, which adds nexus project add. An older CLI fails an add with a message to update it, and choosing which project shows works with any. Opening and closing sessions need CLI 0.5.0 or later, from nexus#44, which adds nexus session open, nexus session close and nexus attach, and sessions' titles. An older CLI fails each with a message to update it, and an older control plane's sessions show without titles. Attaching needs CLI 0.9.0 or later, which attaches through the control plane. An older CLI attaches over ssh, which upgraded nodes refuse, so pi-nexus refuses the attach before it runs and names the version it needs. A paused pylon's sessions, and those its last wake failed to bring back, show with a control plane and CLI at 0.8.0 or later, which list them as paused or failed and close a session by its ID. An older control plane lists only the sessions running in panes, as before. Closing a session needs CLI 0.8.0 or later, which passes on the refusal to close a paused session at once. With an older CLI, which would ask again for two minutes and close the session unasked if a wake came meanwhile, pi-nexus refuses the close before it runs and names the version it needs. A preview whose service failed shows as failed only with a control plane that reports a preview's failed; an older one shows it starting. A control plane with scratch projects, from CLI 0.7.0 and nexus#46, lists a scratch project with no repo URL. pi-nexus 0.5.2 or later shows it as scratch; an older pi-nexus fails everything that reads the projects, such as the project picker and /nexus project, with a contract error about repoUrl. Then:
pi install npm:@ramtinj95/pi-nexus
Run /reload or restart Pi after installation. Pi packages execute with full system access, so review extensions before installing them.
The extension runs nexus from PATH. Set NEXUS_BIN to run another binary, which the attach command it shows and copies names too.
The console
/nexus opens the thread whose agent is working, the most recently active one first, with the thread picker under it. With no agent working it opens the picker. /nexus open <thread> opens one thread. Together they do what the Nexus web dashboard does.
The console reads keys as vim does, and pi's arrow keys work beside them. A thread view opens in normal mode, where keys move through the view; i or a enters insert mode, where every key goes to the composer as in pi, and Esc or an empty Enter returns to normal mode. The line of key hints under the footer starts with the mode, NORMAL or INSERT, or FILTER while the picker filters, and the composer shows its cursor and pi's border colour only in insert mode. s or ? lists every key. The keys match the Nexus mod for Claude Code, which reads only single letters, so pi adds vim's own keys beside them, such as G, ctrl+d and gt.
| Keys | Thread view, normal mode |
|---|---|
i, a |
Write a message. Esc stops writing and keeps the draft |
/ |
Type a slash command, such as /open 8080 |
j k, ↓ ↑ |
Scroll a line, or select the next or previous row in Changes, Previews and Pylon |
d u, ctrl+d ctrl+u, PgDn PgUp |
Scroll half a page, or a page |
gg ge G, Home End |
The top, or the end, which then follows the agent. The first or last row in Changes, Previews and Pylon |
h l, ← → |
The view to the left or right. h in a diff returns to its tree; on Conversation it goes back to the thread or picker you came from |
gt gT, Tab, 1 2 3 4 |
Cycle through, or jump to, Conversation, Changes, Previews and Pylon |
Enter, o |
In Changes, fold a folder or open a file’s diff; on a paused pylon, wake and read. In Previews, publish the selected port. In Pylon, attach this terminal to the pylon's sessions |
x |
In Previews, unpublish the selected port's own preview. In Pylon, close the selected session, after asking |
n |
Open a session in the pylon: Claude Code, pi or a shell |
y |
Copy the file path in Changes, the preview link in Previews, or nexus attach <thread> in Pylon |
m, : |
The thread's actions |
r |
Read Changes again (waking the pylon if needed), or the thread's details in other views |
c |
In Changes, toggle only uncommitted changes; the choice persists between sessions |
t |
The thread picker, to switch threads |
p |
The project picker, to choose the project whose threads show |
q |
Close the console |
Esc |
Back to the tree from a diff, otherwise Conversation |
ctrl+o |
Expand or fold tool output, as in pi, whichever key you bound to it |
- Picker. Threads are grouped by project, open ones first. Each project's archived and deleted threads fold under one row, and Enter unfolds them. Project headings show
▾ project branch, with the default branch dim. Each thread starts with its coloured status dot and title, and ends with a dim, right-aligned activity age. Where the width allows, rows keep the dashboard's overview: what the thread is doing, the wanted and observed pylon state, the node, the last human and agent activity, and how long ago it was active. A thread whose pylon failed, or whose tasks were parked when the console or a follow last saw it, saysneeds you, and one whose agent did something since you last looked at it saysnew. The threads list does not report parked tasks, so pi-nexus knows them only for threads it has watched, as the Claude Code mod does; the marker stays until the agent does something after that, such as running the retried tasks. Narrow terminals drop columns from the right.jandkmove,dandumove half a page,ggandgeorGjump, Enter,oorlopens,norctrl+nstarts a thread,pchooses the project,rreads the threads again, andfor/filters by title or ID: Enter keeps the filter and Esc clears it. Esc clears a filter, then goes back, andqcloses the console. - Projects. The picker shows one project's threads, or every project's, and its header names which:
Nexus threads · spoonorNexus threads · All projects.p, in the picker or a thread view's normal mode, opens the project picker: All projects, then each project with its repo URL, orscratchfor a scratch project, whose threads start from an empty repository with no remote, then New project…, with the project shown now selected and markedcurrent.jandkmove,ggandgeorGjump, Enter orochooses, andqor Esc goes back. Choosing a project shows its threads: the picker you came from, or from a thread a picker that switches from it, astopens. The same choice scopes the strip and the status line's counts, which then name the project, such asnexus spoon · 1 working, and the start form opens on it. It is kept inpi-nexus/settings.jsonin pi's agent directory, beside the strip setting, so later sessions keep it. A project that no longer exists goes back to every project, with one notice. The model'snexus_threadsis never scoped. - New project. New project… opens a form for the ID, the repo URL, the node, which starts on the first online one, and the branch,
mainunless you type another. Tab and Shift+Tab move between fields, ←→ choose the node, Enter goes to the next field and adds the project from Branch, and Esc goes back to the project picker. It runsnexus project add <id> <repo-url> --node <node> --branch <branch>, and once the project is added the console shows its threads. A failure shows the CLI's own message, such as a project that exists already or an ID, URL or node that is not valid, and keeps the form filled. pi-nexus adds only projects with a repo URL;nexus project add <id> --scratch --node <node>at a shell adds a scratch project. - Going back. The console keeps the way you came. Opening a thread from the picker, or from a thread's picker with
t, goes forward, andhon Conversation, or Esc in the picker, returns to the thread or picker before it. A thread opened from a thread's picker replaces that picker, sohgoes straight back to the first thread. - Conversation. The title bar shows
project / title ● state, using the existing state colours. The thread fills the screen and draws its conversation with pi's own components, so it reads exactly like a pi transcript: markdown prose, tool calls with their output, edit diffs, failed commands with their exit codes. Four or more tool calls in a row fold into one line, which opens by itself when a step failed or is running. While the agent works, the editor's top border shows the run's clock and its tool count. An answer streams in as the agent writes it, redrawn at most ten times a second, and the view keeps to its end. Messages waiting for the run show above the editor. A view scrolled up stays put while the answer goes on, untilGfollows the end again. - Composer. Enter sends a message, or queues it while the run is busy, and insert mode lasts past a send. An unsent message stays with its thread while you move between views, such as to the picker and back. A send that failed is put back in the editor, and sending the same text again reuses its request ID, so Nexus never takes it twice.
/steer <text>joins the current run instead. The other slash commands are/abort,/retryfor parked tasks,/retry-pylon,/start-again,/archive,/unarchive,/delete(it asks, with Keep first),/open <port>,/unpublish <port>,/copy <port or service>,/infoand/close. A slash the view does not know is sent as a message, as pi does. - Actions.
mlists the actions that apply to the thread now: watch again after the watch ended, abort the run, retry the parked tasks, retry the pylon, start again, archive or unarchive, copy the attach command, open a session, attach, and delete, which asks with Keep first.jandkmove, Enter runs one, and Esc cancels. - States. A parked run, a failed pylon with its diagnostic and what to do, an archived thread, a deleted one and a paused pylon each show a note above the editor. A thread that cannot take messages refuses them and says why.
- Changes.
2reads the checkout, showing the file count and added/deleted line totals since the pylon booted. Folders merge single-folder chains, sort before files, and fold with Enter oro. Files show their status, line counts orbinary;j/kselect,ycopies the path, and Enter oroopens the diff. A diff shows metadata, hunk headings and the new file’s line numbers, with pi’s own edit-diff colours; long lines are cut, not wrapped. Conversation scroll keys work there.hor Esc returns to the tree on that file. Diffs stop at 20,000 rows and report truncation.ctoggles only uncommitted changes (since HEAD), persisted asuncommittedinpi-nexus/settings.json;rreads again. - Checkout activity. Changes reads only after first showing its tab, at agent turn end or changed agent activity with no turn running, and when explicitly refreshed. It never polls. Automatic reads require an open thread and a running pylon. The visit on thread open can wake it independently; Changes waits for it to run before its first checkout read. A non-running pylon explains that reading wakes it; Enter or
rexplicitly wakes and reads, with progress while the CLI waits. Archived threads must be unarchived first; deleted checkouts are gone. The last result remains during a re-read. Reads are serialized, toggles queue the latest base and discard stale replies. A missing boot base keeps the CLI’s refusal and suggestsc; an older CLI is told to update. - Pylon.
4or/infoshows the pylon's wanted and observed state, node, last seen, and human and agent activity, then the outputs, the command that attaches from a shell in the pylon, and the Sessions section below. - Previews.
3shows the previews as links, with the port each one serves, and the ports that listen with no preview yet, each port once.jandkselect a row, and its hint names the key that acts on it. Enter on a port with no preview, or/open <port>, publishes it as a preview, shows it opening while the node gets its URL ready, then links it, or says why it failed. A preview whose service failed in the pylon, or whose port has no route into it, shows failed, with no link, since its URL cannot reach the server, and says why. A preview stays published untilxon its row or/unpublish <port>closes it: the preview shows closing while the CLI wakes a paused pylon and unpublishes it, then the port is back under Ports, or the preview says why it did not close. While a port is being published or unpublished, from the console, a/nexuscommand or a model tool, any other change of that port is refused until it finishes, so an older publish can never undo a newer unpublish. Only a port's own preview, namedport-<port>, can be unpublished, since the CLI refuses to unpublish a declared or ad hoc service's preview. A link opens on a click, andyor/copyputs it on the clipboard. - Sessions in Pylon.
4lists the pylon's Herdr sessions as the dashboard does: each one's harness, title, state, pane and session ID.n,/session, or Open a session in the actions opens a form in the composer's place: ←→ choose the harness, Claude Code first, Tab moves to the optional title, which is the harness's name when empty, Enter opens and Esc cancels. The session shows opening while the CLI wakes the pylon and starts its agent, which can take a minute, then opened, or the CLI's reason it did not open.xasks to close the selected session, with Keep first, since closing ends its agent. A paused pylon lists the sessions its next wake brings back aspaused, resumes on wake, orresumes on unarchivefor an archived thread, andxsays that one can be closed once it runs rather than asking. A session the last wake did not bring back shows as failed with the reason, whole on the selected row, andxcloses it so that no wake tries it again; one in no pane shows thefailed-ID that closes it. A close the CLI refuses, such as of a pane a wake has since moved to another, says why in its words and reads the sessions again; nothing tries it again by itself. Enter,/attach, or Attach in the actions runsnexus attach <thread>in this terminal: pi steps aside, as it does for an external editor, the pylon's Herdr takes the terminal, where each session is a workspace, and pi comes back when you detach from Herdr, with a note of how the attach ended. This is how you log Claude Code in: open a session, attach, pick its workspace and type/login. Attaching needs Nexus CLI 0.9.0 or newer and goes through the control plane. - Start form.
/nexus new,/nexus new <project>ornin the picker opens a form for the title, project, node, branch and first message. It starts on the selected thread's project, or the project shown. Empty node and branch mean the project's defaults. Under the project it shows the repo URL, orscratch, and under the message what the start does, such asBoots a pylon on firebat and checks out main.A scratch project's thread starts an empty repository on the branch instead of checking it out. Start again opens it with the thread's project and title, and cancelling it goes back to the thread. - Strip. From the session's start, one line above pi's editor names the threads of the project shown that need you, then those with a new reply since you last looked, then those whose agent works, with how long ago the message it answers came. pi's status line keeps the counts:
nexus 1 working · 1 needs you · 1 new. Both come from one poll ofnexus threadsevery 30 seconds and after each command./nexus strip offhides both and stops the polling, and/nexus strip onstarts it again. The setting is kept inpi-nexus/settings.jsonin pi's agent directory, so later sessions keep it. pi's RPC mode shows the status line but cannot draw the strip.
Leaving a view never aborts the agent. Opening a thread counts as a visit, as on the dashboard, so a paused pylon wakes. When you last looked at each thread is kept in pi-nexus/viewed.json in pi's agent directory, so every pi session on this Mac shares it; a thread nobody has looked at counts its agent's activity from when that file was first written. Each poll drops the looks at deleted threads. The view gets everything through the CLI: threads, projects, project add, nodes, show, watch, changes, diff, preview, unpreview and the action commands. The console runs at most one checkout read at a time (therefore at most one changes and one diff child), and aborts it when the thread view or console closes. Without nodes, the start form leaves out node choices, with a note saying why. Without pi's custom UI, as in RPC mode, /nexus lists the threads as a note, and /nexus open shows the conversation's tail.
Commands
| Command | What it does |
|---|---|
/nexus |
Opens the working thread with the picker under it, or the picker |
/nexus open <thread> |
Opens the thread view |
/nexus new [project] |
Opens the start form |
/nexus list [project] |
Lists threads without deleted ones, as a note: ID, status, whether the agent is working, project and title |
/nexus new <project> [--title T] <message> |
Starts a thread with a first message, then follows it |
/nexus send <thread> <text> |
Sends a message, then follows the thread. A busy agent takes it after its current turn |
/nexus show <thread> |
Shows the thread's state and the tail of its conversation |
/nexus follow <thread> |
Streams new conversation entries into the session until the next turn ends |
/nexus unfollow [thread] |
Stops one follow, or all of them |
/nexus archive <thread>, unarchive |
Archives a thread, which pauses its pylon, or brings it back |
/nexus retry <thread> |
Runs a conversation's parked tasks again |
/nexus retry-pylon <thread> |
Clears a failed pylon so that Nexus boots it again |
/nexus abort <thread> |
Aborts the agent's current run |
/nexus unpublish <thread> <port> |
Unpublishes the port's preview, so its URL stops answering. A port with no preview is already unpublished, and a service's preview is refused. Publishing the port again may give it a new URL |
/nexus delete <thread> |
Deletes a thread and its pylon once you pick Delete. Keep is the highlighted answer, and dismissing the dialog keeps the thread |
/nexus strip on, off |
Shows or hides, for this and later sessions, the strip of threads that need you, have news or work above the editor |
/nexus project |
Says which project the picker, the strip and the status line show |
/nexus project <id>, all |
Shows one project's threads, or every project's, in this and later sessions |
/nexus project add <id> <repo-url> --node <node> [--branch <branch>] |
Adds a project with nexus project add, which needs CLI 0.4.0, then shows its threads |
/nexus session open <thread> [--harness claude-code|pi|shell] [--title T] |
Opens a session in the pylon, Claude Code unless --harness names another, which can take a minute |
/nexus session close <thread> [id] |
Closes a session by its pane or, for a failed session in no pane, its ID. Closing a pane ends its agent, and closing a failed session stops wakes trying it again. Without an ID it asks which session, with Keep them all first, and leaves out paused sessions, which can be closed once their pylon's wake brings them back |
/nexus session attach <thread> |
Gives this terminal to nexus attach until you detach from Herdr. A pi without a terminal to give, as in RPC mode, gets the command to run in one |
/nexus help |
Lists these commands |
Arguments split as a shell splits them: quotes work anywhere in a word, so --title="two words" is one title, a backslash outside single quotes escapes the next character, and a quote left open is refused. A message, the text after send <thread> or new <project>, is kept as typed, apostrophes and all.
A thread is its ID, such as th-0123456789ab, or any unique prefix of it, with or without th-. Tab completes command names, the thread IDs last listed, after project, all, add and the projects those threads are on, and after session, open, close and attach.
Conversation text appears in the chat as Nexus notes. Pi stores the notes in the session but never sends them to its model. A collapsed note shows its last 16 lines, and ctrl+o expands it.
A follow ends when the thread settles as nexus_wait judges it, failed pylons included, or with /nexus unfollow. After /nexus new or /nexus send, a reply that arrived before the first poll counts. /nexus follow on an idle thread waits for its next turn. A widget above the editor shows each follow's state while it runs. A follow keeps going through a control plane outage and says so in the chat.
While the strip setting is on, which it is unless /nexus strip off turned it off, the status line shows from the session's start how many threads of the project shown have a working agent, and how many need you or have something new since you last looked, such as nexus 2 working · 1 needs you · 3 new. It refreshes every 30 seconds and after each command. When the CLI cannot reach Nexus, it shows the failure kind instead, such as nexus unreachable.
Every failure is shown as an error notification with the CLI's message. When the SOCKS proxy is down, that message points to ts-home status.
Model tools
| Tool | Parameters | Result |
|---|---|---|
nexus_threads |
project? |
[{id, project, title, status, working, active}], without deleted threads. active reads like 2m ago |
nexus_start |
project, message, title? |
{id, project, title}. The title defaults to the message's first line |
nexus_send |
thread, text |
{thread, sent: true}. A working agent takes the message after its current turn |
nexus_read |
thread |
{thread, status, run, pylon, queued, failed?, ports, conversation}, with the conversation's last 6000 characters. ports lists {port, service?, url?, ready?, failed?} for each port that listens, with its preview's URL once it has one and why its service failed when it did, or is null when the node could not read them |
nexus_wait |
thread, timeout_seconds? (1 to 600, default 300) |
{thread, settled, timed_out?, cancelled?, status, run, pylon, queued, failed?, ports, outage?, needs_human?, reply}. reply is the assistant's text since the latest user message, its last 4000 characters, or null. needs_human tells the model when parked tasks or a failed pylon need /nexus retry or /nexus retry-pylon |
nexus_preview |
thread, port (1 to 65535) |
{thread, port, url, ready}, once the node serves the URL, up to 2 minutes. The preview is open to the home tailnet until it is unpublished with nexus_unpublish or /unpublish |
nexus_unpublish |
thread, port (1 to 65535) |
{thread, port, unpublished: true}, once the preview is closed, up to 2 minutes while a paused pylon wakes. A port with no preview succeeds too. A declared or ad hoc service's preview fails with the CLI's refusal. Publishing the port again may give it a new URL |
nexus_archive |
thread |
{thread, status: "archived"} |
Results are compact JSON. A failure is a failed tool call whose text reads nexus (<kind>): <the CLI's message>, where the kind is the CLI's, such as unreachable or not-found.
nexus_wait counts a thread as settled at once when it cannot run on: archived, deleted, parked, or its pylon failed. Otherwise its run must be idle with nothing queued, and either the agent has answered the latest message or this wait saw the run working, which covers a turn aborted before it wrote anything. A settling state ends the wait at once, and an entry that looks settled counts after a quiet second. It reads one nexus watch and stops it when done. Outages are waited through and reported in outage. reply joins every assistant text since the latest user message, a blank line apart, and is null when that turn wrote none.
The model cannot delete a thread, retry a pylon, retry parked tasks, add a project, or open, close or attach to a session. Those stay human-only, as /nexus delete, /nexus retry-pylon, /nexus retry, /nexus project add and /nexus session.
These tools match the Claude Code Nexus mod in parameters, results and the wait rule. Only the names differ: the mod's tools show as mcp__nexus__threads and so on, because Claude Code namespaces them, and pi does not.
Development
npm install
npm run check
npm run check type-checks, runs the tests, and verifies the npm package contents. There is no separate lint command in this repository. The tests drive the tools, commands and console against a fake nexus binary that prints contract-shaped JSON, and test/pi-rpc.test.ts loads the extension into a real Pi in RPC mode.
test/demo/ builds the console's screenshot demo. bun test/demo/build.ts <dir> writes a fake nexus whose working thread holds one scripted conversation, beside a thread with a new answer, one whose pylon failed and an archived one, and a pi agent directory whose looks store marks them. Its Changes replies include a merged folder, a binary file, a rename and a unified diff; append paused to build the paused-pylon note scenario. test/demo/faux-model.ts is a pi extension whose model plays the same conversation, so a real pi session can be put beside the thread view. To see the console against it:
repo=$PWD; bun test/demo/build.ts /tmp/demo
cd /tmp/demo/work && PI_CODING_AGENT_DIR=/tmp/demo/agent NEXUS_BIN=/tmp/demo/nexus PI_OFFLINE=1 \
pi --no-session -ne -ns -np -nc --no-mcp -e "$repo/extensions/nexus/index.ts" -e "$repo/test/demo/faux-model.ts" --model demo/demo-model
Then /nexus. For a screenshot, read the pane with its colours, such as tmux capture-pane -ep, pass it through bun test/demo/carry-sgr.ts, and render it with freeze --language ansi.
To try a local checkout against the real control plane:
pi -e ./extensions/nexus/index.ts
License
MIT