vps-agent
v0.1.0
Published
VPS Agent CLI for SprintFlint Autoplay - connects your VPS to SprintFlint for automated job execution
Downloads
181
Maintainers
Readme
vps-agent
The SprintFlint VPS agent. Install it on a VPS (or any Linux/macOS host) to turn that machine into a SprintFlint runner: it registers with SprintFlint, heartbeats its health, claims Autoplay jobs, runs them with a code harness (Claude Code), opens a pull request with the result, and streams logs back to your SprintFlint dashboard in real time.
It is a Node/TypeScript CLI that talks to the SprintFlint server API over HTTPS
(X-Runner-Token auth). It never opens inbound ports; the agent only makes
outbound requests.
Requirements
- Node.js >= 18 and npm.
- git (>= 2.20).
- GitHub CLI (
gh), authenticated withgh auth loginbefore starting the agent. The agent clones, pushes, and opens PRs using this machine's ambient git +ghcredentials; it never handles GitHub tokens itself. - Claude Code (
claude), authenticated once, when running theclaudeharness.
Run vps-agent doctor at any time to verify these.
Install
npm install -g vps-agentOr run it without installing:
npx vps-agent --helpOr use the installer script (checks Node, installs globally, prints next steps):
curl -fsSL https://raw.githubusercontent.com/sprintflint/vps-agent/main/scripts/install.sh | bashQuick start
# 1. Register this host with your organization (calls the server, saves a token)
vps-agent register --org-id <your-org-id> --name "$(hostname)"
# 2. Verify prerequisites
vps-agent doctor
# 3. Start in the background
vps-agent start --daemon
# 4. Observe
vps-agent status
vps-agent logs -fCommands
| Command | What it does |
| -------------------------- | --------------------------------------------------------------------------- |
| register | Register this host as a runner (two modes; see below). |
| start [--daemon] | Run the heartbeat + job-poll loop. --daemon detaches into the background. |
| stop | Signal the running (daemonized) agent to stop. |
| status | Print config + whether the agent is running (JSON). |
| logs [-f] [-n <count>] | Show the last -n log lines (default 200); -f follows. |
| unregister | Clear the local token + runner id (local-only; see note). |
| config show | Print the effective config (token redacted). |
| config set <key> <value> | Persist a single config key. |
| doctor | Check that git/gh/claude are installed, authenticated, and compatible. |
| version | Print the version and runtime info. |
register — two modes
Organization id + name (calls the server, which returns the runner token):
vps-agent register --org-id org_123 --name "build-box-1"Pre-issued token (created in the SprintFlint web UI; no server call, just saves it locally):
vps-agent register --token <runner-token>Both modes accept --api-url <url> to override the server base URL (defaults to
https://sprintflint.com; use http://localhost:3000 for local development).
unregister
This is local-only: it clears the token and runner id from config.json and
stops a running agent, but the SprintFlint server has no unregister endpoint.
To fully revoke a runner, remove it in your SprintFlint dashboard.
Configuration
Configuration is resolved with this precedence (highest wins):
CLI flags > environment variables > .env file > ~/.vps-agent/config.json > defaultsThe token and runner id are saved to ~/.vps-agent/config.json by register.
That file holds a secret, so it is written owner-only (0600) inside an
owner-only (0700) directory.
| Key | Env var | Default | Meaning |
| -------------------- | ------------------------------ | ------------------------- | ---------------------------------------------------- |
| api_url | VPS_AGENT_API_URL | https://sprintflint.com | SprintFlint server base URL. |
| token | VPS_AGENT_TOKEN | (none) | Runner token, sent as X-Runner-Token. |
| harness | VPS_AGENT_HARNESS | noop | Job harness: noop or claude. |
| permission_mode | VPS_AGENT_PERMISSION_MODE | default | Permission mode for the harness (see below). |
| heartbeat_interval | VPS_AGENT_HEARTBEAT_INTERVAL | 30 | Seconds between heartbeats (server may shorten). |
| poll_interval | VPS_AGENT_POLL_INTERVAL | 5 | Seconds between idle next_job polls. |
| max_log_batch_size | VPS_AGENT_MAX_LOG_BATCH_SIZE | 100 | Max log lines per append_log call. |
| log_level | VPS_AGENT_LOG_LEVEL | info | error | warn | info | debug. |
| runner_id | VPS_AGENT_RUNNER_ID | (set by register) | This runner's numeric id. |
| config_dir | VPS_AGENT_CONFIG_DIR | ~/.vps-agent | Directory for config, logs, pidfile, job workspaces. |
Set a key:
vps-agent config set harness claude
vps-agent config set permission_mode acceptEditsPermission modes (claude harness)
default/acceptEdits— let Claude edit files; you review via the PR.plan— plan only.bypassPermissions— full autonomy (explicit opt-in).
Git authentication
The agent always uses this machine's ambient git + gh credentials. Run
gh auth login before starting the agent; the gh CLI then handles clone,
push, and PR creation. The agent neither handles nor injects any GitHub token,
so there is no token-handling surface to secure.
Job flow
When the agent is running, for each claimed job it:
- Heartbeats status (
online, orbusywhile a job runs) with system stats, on an interval. - Polls
next_job. On a job, it flips tobusy(single-job concurrency: it will not claim another job until this one finishes). - Marks the job
runningand prepares an isolated workspace: clones the repo into~/.vps-agent/jobs/<job_id>/repoand checks out the job's branch. - Runs the configured harness (e.g. Claude Code, headless) in that repo,
streaming its stdout/stderr to the job log (
append_log) as it goes. - If the harness changed files: commits, pushes the branch, and opens a
pull request with
gh pr create(it never merges). - Finalizes the job with
update_job(completedorfailed, with a result summary and PR URL), flushing remaining logs first. - Cleans up the workspace (on success or failure).
Network/API hiccups never tear the agent down: the heartbeat and poll loops log the error and back off exponentially, then resume.
Security model
- No arbitrary remote command execution. The agent only ever invokes the
fixed binaries
git,gh, andclaude, each with a controlled array of arguments and no shell. It does not execute any command string supplied by the server. Job payload fields (branch names, repo URLs, prompts) are passed as discrete arguments, never interpolated into a shell. - Secret redaction. Tokens (the runner token and provider keys such as
ghp_…/sk-ant-…) are scrubbed from both the local agent log and the server-streamed job log. - Least-privilege files. The config/token file is
0600inside a0700directory; permissions are re-tightened on every write. - No GitHub tokens in the agent. Git/
ghoperations use the host's ambient credentials only; the agent never receives, stores, or injects a GitHub token.
Logs and state
Everything lives under config_dir (default ~/.vps-agent):
config.json— persisted config (token redacted inconfig show).agent.log(rotated) — structured agent log; view withvps-agent logs.daemon.out.log— captured stdout/stderr of a--daemonstart.agent.pid— pidfile forstart --daemon/stop.jobs/<job_id>/— per-job workspaces (auto-removed after each job).
Troubleshooting
No runner token configured— runvps-agent registerfirst.doctorfails ongh— rungh auth loginso the host has an authenticatedghsession.doctorfails onclaude— install Claude Code and runclaudeonce to authenticate; only required whenharness=claude.Agent already running— a live pidfile exists. Usevps-agent stop, or pass a different--pidfile.- Nothing happens after
start— checkvps-agent logs -f; confirmapi_urland the token are correct viavps-agent config show. - Private repo clone fails — ensure the host's git/
ghcredentials can reach it (rungh auth loginand confirm withgh auth status).
Development
npm install
npm run lint # eslint
npm run typecheck # tsc --noEmit
npm test # vitest
npm run build # tsup -> dist/The library surface is exported from src/index.ts for embedding/testing. The
CLI entrypoint is src/cli.ts.
License
MIT. See LICENSE.
