A herdr plugin for exe.dev VMs.
exe.dev VMs are persistent computers, not disposable sandboxes: many run 24/7. This plugin treats them that way. It links a VM to a herdr workspace, gets you a shell on it in one keystroke, shows its state in the sidebar, lends it your Claude subscription while you work there, and never stops or deletes a VM on its own.
- Link a workspace to a VM, new or existing, and
exe: shellsplits a pane into it. The sidebar shows$exe/$exe_statusfor linked workspaces. - Ctrl+click an
*.exe.xyzURL in any pane: shell, browser, web terminal, Shelley, share link. Other people's exe.xyz links still open in the browser. - A VM per git worktree (opt-in):
herdr worktree createmakes or reuses a VM, ships the branch to it as a git bundle, and opens a tab on it. - Claude on the VM without leaving a credential there, via imp, or by pushing a token that can't be refreshed (see Claude on the VM).
- VMs as herdr machines: pick VMs and
herdr machine addthem, so agents running on them show up in your herdr sidebar.
Needs herdr 0.9.0+, bash, jq, fzf, python3 (for the bundled imp; on
macOS, the Command Line Tools), and an SSH key registered with exe.dev.
ssh exe.dev whoami should work without a prompt, so ssh-add a key with a
passphrase first: plugin hooks run in the background.
herdr plugin install metcalfc/herdr-exe
# or, from a checkout:
herdr plugin link ~/src/github.com/metcalfc/herdr-exe
cp config.env.example "$(herdr plugin config-dir exedev)/.env" # optionalAdd keys and the sidebar tokens to ~/.config/herdr/config.toml:
[[keys.command]]
key = "prefix+shift+e"
type = "plugin_action"
command = "exedev.vms"
description = "exe.dev VMs"
[[keys.command]]
key = "prefix+alt+e"
type = "plugin_action"
command = "exedev.ssh"
description = "shell into this workspace's VM"
[ui.sidebar.spaces]
rows = [
["state_icon", "workspace"],
["branch", "git_status"],
["$exe", { token = "$exe_status", rules = [
{ equals = "running", fg = "#a6e3a1" },
{ equals = "creating", fg = "#f9e2af" },
{ equals = "failed", fg = "#f38ba8" },
] }],
]| Action | What it does |
|---|---|
exedev.vms |
Picker over your VMs with a live stat preview. enter: shell · ctrl-a: link to this workspace · ctrl-u: unlink · ctrl-o: browser · ctrl-l: share link · ctrl-x: delete · ctrl-r: refresh |
exedev.ssh |
Split a pane and ssh into this workspace's VM (opens the picker if none is linked). The pane is the session: leaving the VM closes it |
exedev.new |
Create a VM for this workspace: name, optional Shelley prompt, then a shell |
exedev.machines |
Add VMs as herdr machines. herdr installs itself on a VM if needed and asks first |
exedev.auth |
Claude login for VMs: store a setup token, push, status, revoke |
exedev.refresh |
Re-read ssh exe.dev ls and repaint the sidebar (also runs at startup) |
A workspace is linked to a VM by its git checkout root, so every workspace or pane in that repo finds the same VM.
EXE_CLAUDE_AUTH decides how claude on a VM gets your subscription.
| Mode | On the VM | Revoke |
|---|---|---|
imp |
A loopback URL and an inert capability. The credential stays in your keychain; imp-proxy runs in an imp:<vm> tab and injects it per request |
Ctrl-C the imp:<vm> tab, or close the last exe:<vm> pane |
setup-token |
A long-lived claude setup-token token, no refresh token |
exe: Claude login → revoke |
rolling |
This machine's current access token, no refresh token. Lasts hours; topped up on attach, on focusing an exe: pane, and at startup |
exe: Claude login → revoke |
none |
Nothing |
auto (the default) is imp whenever python3 is available, else
setup-token if you stored one, else none.
imp is bundled: vendor/imp/imp-proxy is a pinned copy of
metcalfc/imp (MIT), so there is nothing
else to install. scripts/vendor-imp [ref|checkout] refreshes it, and
EXE_IMP_PROXY points the plugin at another copy, such as an imp checkout
you are working on. The bundled copy wins over an imp-proxy on your PATH,
since it is the version the plugin is tested against.
The plugin never copies a refresh token to a VM. Anthropic refresh tokens are
single-use, so two machines holding the same chain log each other out. Tokens
only travel over ssh stdin, never in a command line, and a VM where you ran
claude login yourself is left alone. rolling reads Claude Code's keychain
item through /usr/bin/security; macOS may ask once.
A setup token has fewer scopes than a full login. If a model is gated on your
plan, use imp or rolling.
Codex: don't copy ~/.codex/auth.json (a reused ChatGPT refresh token is
permanently fatal). Connect your ChatGPT subscription as an exe.dev LLM
integration instead, attach it (EXE_INTEGRATIONS), and set
EXE_CODEX_EXE_LLM=1 to point Codex on the VM at it.
With EXE_VM_PER_WORKTREE=1 (and ideally EXE_WORKTREE_REPOS), creating a
herdr worktree:
- creates
<repo>-<branch>, or reuses it if this plugin made it before; - clones the branch into
~/work/<repo>from a git bundle, withoriginpointing back at your remote; - opens an
exe:<vm>tab there, logged in to Claude, optionally runningEXE_WORKTREE_COMMAND(e.g.claude).
Removing the worktree only sends a notification. The VMs picker then shows the
link as (gone).
- Stop, restart, or delete a VM from a hook, a refresh, or on exit. Tests pin this down.
- Delete a VM it didn't create. ctrl-x works only on VMs recorded at creation
or marked with the
herdrtag or aherdr:comment, and asks you to type the name. A tag you share with other VMs (saydev) doesn't make them deletable. - Take over one of your VMs that happens to share a generated name.
- Tag-scoped keys (
ssh-key add --tag=X) can only create VMs tagged X. The plugin notices exe.dev's refusal, retries with X, and remembers it. SetEXE_TAGSto choose tags yourself. - herdr machines: herdr installs to
~/.local/binon the VM, which isn't on a non-login sshPATHon exeuntu. herdr warns and works anyway. - Scripted invocations: if you
plugin.action.invokewith a partial context, herdr fills the missing fields from the active workspace, so.worktreecan belong to a different workspace than.workspace_id. The plugin resolves the workspace through herdr's API rather than trusting those fields. - Toasts: herdr's
ui.toast.deliveryisoffby default; set it toherdrto see the plugin's notifications.
tests/run.sh # stubbed herdr/ssh/fzf/keychain; real git and jq
tests/run.sh /bin/bash # again with macOS's bash 3.2
shellcheck bin/exe-herdr tests/run.sh tests/stubs/* scripts/vendor-impThe stubs simulate exe.dev (including its error shapes, tag-scoped keys, and
its habit of exiting 0 on failure), a herdr server whose pane labels stay
consistent, a keychain, and a "VM" that runs remote scripts in a throwaway
$HOME, so credential files, permissions, and clones are checked for real.
MIT. The bundled vendor/imp/imp-proxy is imp, also MIT; see vendor/imp/LICENSE.