Let an AI agent run commands on your servers over SSH without ever giving it the credentials. srv-wrapper is a local daemon + CLI for macOS that hides every hostname, IP, port, username, password and private key behind an opaque server-id like srv-a1 — so your coding agent can srv exec srv-a1 "npm run build" while the real connection details stay in the macOS Keychain, and every command it runs is streamed to a local dashboard and written to a permanent audit log.
- Quick start
- CLI reference
- Why use it
- How it works
- Dashboard
- Security model
- Running the daemon on login
- Maintenance
- Requirements
- Development
- License
From zero to a working srv exec in four steps.
1. Install
npm install -g @nhic-lab/srv-wrapper # exposes `srv` and `srvd` globallyOr install from a clone of this repo
git clone https://github.com/nhic-lab/srv-wrapper.git
cd srv-wrapper
npm install
npm run build
npm link # exposes `srv` and `srvd` globallyTo run the daemon without building or linking anything: npm run dev:daemon.
2. Start the daemon
srvdIt listens on a Unix socket at ~/.srv/srv.sock and serves the dashboard on 127.0.0.1:4280. (See Running the daemon on login to have it start automatically.)
3. Register a server
Open http://127.0.0.1:4280 and add a server: pick an id (srv-a1), then enter the host, port, username, auth method, and password or key passphrase. Those details are stored by the daemon and never leave it.
4. Run a command
srv exec srv-a1 "uname -a" --agent my-agent-labelstdout and stderr stream back to your terminal, the CLI exits with the remote command's exit code, and the run appears live in the dashboard.
That's it — from here, point your AI agent at srv and it can operate the box without ever learning where the box is.
# list the server ids you can talk to (ids only — no hosts)
srv list
# one-shot command
srv exec <server-id> "<command>" --agent <label>
# persistent session: state (cwd, env vars) survives across calls
srv session start <server-id> --agent <label> # prints a session id
srv session send <session-id> "cd /var/www && ls"
srv session send <session-id> "pwd" # still /var/www
srv session stop <session-id>--agent <label> is required on every exec and session start. It is how the dashboard's live view tells concurrent agents apart and how the audit log attributes each run.
Sessions auto-close after 30 minutes of inactivity, so an abandoned session never pins a connection open.
A Claude Code skill (.claude/skills/srv-wrapper/SKILL.md, also symlinked into ~/.claude/skills/) documents this CLI, so any Claude Code agent picks up the commands automatically — you can just say "check the nginx logs on srv-a1".
Handing an AI agent a real SSH key or password has two problems: the secret can leak (into a prompt, a transcript, a log file, a model provider's context), and you lose any reliable record of what the agent actually did on the remote box.
srv-wrapper sits between the agent and your servers. You register a server once; the agent only ever gets a short id; the daemon resolves that id to the real connection internally and records every byte of the result.
srvd— a background daemon holding the server registry (SQLite), secrets (macOS Keychain), SSH connections (ssh2, including jump-host chains), and the audit log (SQLite). It exposes exactly two local-only surfaces:- a Unix domain socket at
~/.srv/srv.sock(mode0600) speaking newline-delimited JSON — this is what the CLI talks to; - an Express + WebSocket dashboard bound to
127.0.0.1only.
- a Unix domain socket at
srv— the CLI an agent invokes. One-shotexec, persistent PTY sessions, andlist. It resolves nothing itself; it knows only the socket and the id you give it.- Dashboard — register servers, test reachability, watch a live feed of what every agent is running right now, and browse paginated history.
Served at http://127.0.0.1:4280 while srvd runs. It has three views:
Register servers one at a time or via bulk JSON import, edit them, test connectivity individually or all at once, and delete them. Reachability results are persisted, so a refresh doesn't discard your last "Test all".
Every exec and session in flight, streamed over WebSocket, labelled by the --agent value that started it.
Every past run: server id, agent label, command, exit code, timing, and full captured output. Paginated, newest first.
The dashboard supports light and dark themes and follows your system setting until you choose one explicitly — the Servers and Live screenshots above are in dark mode; History is shown in light mode for contrast.
- Id-only boundary. Servers are referenced everywhere by id. Real connection details never reach the CLI process, its output, or the audit log. SSH errors are sanitized before they are surfaced, because raw Node/
ssh2errors routinely embed the realhost:port. - Secrets in the Keychain. Passwords and key passphrases live in the macOS Keychain, scoped by ACL to the daemon's own resolved path — not in the SQLite registry.
- Pinned host keys. SSH host keys are pinned on first use (TOFU) and persisted across daemon restarts.
- Confined key files. Private key files are restricted to paths that resolve (through symlinks) inside
~/.ssh. - Local-only surfaces. The socket is
0600and the dashboard binds to127.0.0.1with an Origin check on the WebSocket upgrade. - No dashboard auth. This is a deliberate trade-off for a tool that only ever binds to loopback on your own machine, not an oversight. Don't expose port 4280 beyond localhost.
srv daemon installThis installs a launchd agent (~/Library/LaunchAgents/com.srv-wrapper.daemon.plist) that runs srvd and restarts it if it crashes. Logs go to ~/.srv/daemon.log and ~/.srv/daemon.error.log. Check whether it's loaded with srv daemon status, and remove it with srv daemon uninstall.
This writes outside the project directory and changes what starts on login. Run it deliberately.
Captured output is capped at write time (128 KB head + 128 KB tail, with an elision marker), so a runaway mysqldump can't bloat the audit database. To retro-fit that cap onto rows recorded before the cap existed and reclaim the space:
# stop the daemon first
npm run compact-log -- --yes- macOS (the daemon uses the macOS Keychain via the
securityCLI) - Node.js >= 20
npm install
npm run build # src/ -> dist/
npm test # vitest run
npx tsc -p tsconfig.json --noEmit # typecheck
npm run dev:daemon # run the daemon via tsx, no build stepArchitecture notes and conventions live in CLAUDE.md; the full design spec and implementation plan are under docs/superpowers/.
MIT — see LICENSE.



