vivagents
v1.2.0
Published
Standalone CLI Agents server — wraps Claude, Codex, and Gemini CLIs behind a simple HTTP API
Maintainers
Readme
Run it on your Mac, a Linux VPS, or anywhere Node.js runs — then connect your apps over the network for AI text processing without needing each device to have CLI tools installed.
Built primarily as a companion server for VivaDicta (iOS/macOS speech-to-text app with AI processing), but the API is simple and generic — any app, script, or automation can use it to send text and get AI-processed results back over HTTP.
Disclaimer: Claude Code, Codex CLI, and Gemini CLI are designed and licensed for software development use. Using them for general text processing may fall outside the intended use of their respective providers and could lead to account restrictions. By using VivAgents you acknowledge this and proceed at your own risk. VivAgents is not affiliated with Anthropic, OpenAI, or Google. For unrestricted usage, consider using direct API keys from each provider instead.
How It Works
VivAgents discovers installed CLI tools on the host machine, starts an HTTP server, and proxies text processing requests to the appropriate CLI:
Your App ──► VivAgents Server ──► Claude / Codex / Gemini CLI
(iOS) (Mac or VPS) (spawns process, returns result)- No API keys needed — uses your existing CLI subscriptions (Claude Pro/Max, ChatGPT Plus, Google account)
- Three providers — Claude Code (Anthropic), Codex CLI (OpenAI), Gemini CLI (Google)
- Two request shapes — simple
POST /processfor single-shot text processing, OpenAI-compatiblePOST /v1/chat/completionsfor multi-turn chat with SSE streaming - Auto-discovery — finds CLI binaries via PATH, nvm, and common install locations
- Bearer token auth — auto-generated, stored in
~/.vivagents/token - Stateless — the server holds no conversation state; multi-turn chat works by having the client re-send the full
messages[]history on each turn (same model as OpenAI's Chat Completions API) - Zero external dependencies — just Node.js standard library
Beyond VivaDicta, you can use VivAgents for any text processing workflow — shell scripts, iOS Shortcuts, Automator actions, or any app that can make HTTP requests. Because the chat endpoint is OpenAI-compatible, any app that already speaks OpenAI or Ollama can point at VivAgents with just a URL change.
Prerequisites
Install and authenticate at least one CLI tool:
| CLI | Install | Authenticate |
|-----|---------|-------------|
| Claude Code | curl -fsSL https://claude.ai/install.sh \| bash | Run claude and sign in with your Anthropic account |
| Codex CLI | npm install -g @openai/codex or brew install codex | Run codex and select "Sign in with ChatGPT" |
| Gemini CLI | npm install -g @google/gemini-cli or brew install gemini-cli | Run gemini and select "Sign in with Google" |
Requires Node.js 20+ (for Codex and Gemini CLIs). Claude Code installs standalone with no dependencies.
Quick Start
Option A: npm (recommended)
npx vivagents check # Check which CLIs are available
npx vivagents start # Start the serverOr install globally:
npm install -g vivagents
vivagents startOption B: from source
git clone https://github.com/n0an/vivagents.git
cd vivagents
npm install
npm run build
node dist/index.js startThe server starts on port 3456 by default. An auth token is auto-generated on first run and stored in ~/.vivagents/token. Use vivagents token to view it.
2026-03-28T10:38:42.619Z [INFO] VivAgents server running on http://0.0.0.0:3456
2026-03-28T10:38:42.619Z [INFO] Auth token: an0mVfYI...
2026-03-28T10:38:42.619Z [INFO] ✓ Claude CLI (/Users/you/.local/bin/claude)
2026-03-28T10:38:42.619Z [INFO] ✓ Codex CLI (/Users/you/.nvm/versions/node/v23.11.1/bin/codex)
2026-03-28T10:38:42.619Z [INFO] ✓ Gemini CLI (/Users/you/.nvm/versions/node/v23.11.1/bin/gemini)CLI Commands
vivagents start # Start the server (default)
vivagents check # Show which CLIs are available
vivagents doctor # Diagnose issues (binary, auth, port, config)
vivagents token # Print the current auth token
vivagents token --reset # Generate a new auth token
vivagents help # Show helpOptions
vivagents start --port 8080 # Custom port
vivagents start --host 127.0.0.1 # Bind to localhost only
vivagents start --token mysecret # Use a specific auth token
vivagents start --claude-path /path # Custom CLI binary pathAPI
All endpoints require an Authorization: Bearer <token> header (or a "token" field in the JSON body).
GET /health
Check server status and provider availability.
curl -H "Authorization: Bearer $TOKEN" http://localhost:3456/health{
"status": "ok",
"claude_available": true,
"claude_path": "/Users/you/.local/bin/claude",
"codex_available": true,
"codex_path": "/Users/you/.nvm/versions/node/v23.11.1/bin/codex",
"gemini_available": true,
"gemini_path": "/Users/you/.nvm/versions/node/v23.11.1/bin/gemini",
"version": "1.0.0"
}GET /models
List available models for each provider.
curl -H "Authorization: Bearer $TOKEN" http://localhost:3456/models{
"models": ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"],
"default": "claude-sonnet-4-6",
"codex_models": ["gpt-5.4", "gpt-5.4-mini", "gpt-5.2", "gpt-5.1"],
"codex_default": "gpt-5.4",
"gemini_models": ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"],
"gemini_default": "gemini-2.5-flash"
}POST /process
Process text through a CLI provider.
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"text": "hello wrold how are yuo",
"systemPrompt": "Fix grammar and spelling. Return only the corrected text.",
"provider": "gemini",
"model": "gemini-2.5-flash"
}' \
http://localhost:3456/process{
"result": "Hello world, how are you?",
"model": "gemini-2.5-flash",
"provider": "gemini",
"duration": 3.45
}Parameters:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| text | string | Yes | Text to process |
| systemPrompt | string | No | Instructions for the AI |
| provider | string | No | "claude" (default), "codex", or "gemini". Vendor aliases are also accepted: "anthropic" → claude, "openai" → codex, "google" / "google-ai" → gemini |
| model | string | No | Model to use (defaults to provider's default) |
POST /v1/chat/completions
OpenAI-compatible chat completions endpoint. Accepts a messages[] array and returns either a single JSON response or Server-Sent Events (SSE), matching the shape of OpenAI's Chat Completions API so any OpenAI-compatible client (including the "Ollama" provider path in many apps) can point at VivAgents with zero code changes.
Currently Claude-only. Codex and Gemini fall through to a clean "provider does not support chat completions" error until their session models are wired up.
Stateless: the server does not store conversation history. On every request, flatten your full conversation into messages[] and resend it. This matches how OpenAI's own endpoint behaves.
Non-streaming example:
curl -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"messages": [
{"role": "system", "content": "You reply in one short sentence."},
{"role": "user", "content": "say hi"}
],
"stream": false
}' \
http://localhost:3456/v1/chat/completions{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1776783809,
"model": "claude-sonnet-4-5",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "Hi!" },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0 }
}Note on
usage: VivAgents currently returns zero placeholders for token counts. The surrounding shape is faithful to OpenAI so SDK consumers don't choke; real token counts can be wired in later when a tokenizer is added.
Streaming example (stream: true):
curl -N -X POST -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-4-5",
"messages": [{"role": "user", "content": "count from 1 to 5"}],
"stream": true
}' \
http://localhost:3456/v1/chat/completionsReturns text/event-stream chunks:
data: {"id":"chatcmpl-...","choices":[{"delta":{"role":"assistant"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","choices":[{"delta":{"content":"1, 2, 3, 4, 5"},"finish_reason":null}]}
data: {"id":"chatcmpl-...","choices":[{"delta":{},"finish_reason":"stop"}]}
data: [DONE]If the client disconnects mid-stream, the underlying Claude CLI subprocess is terminated to avoid wasted work.
Parameters:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| messages | array | Yes | OpenAI-style [{role, content}] with roles system / user / assistant |
| model | string | No | Model to use (defaults to provider's default) |
| stream | boolean | No | true for SSE, false (default) for single JSON response |
| provider | string | No | "claude" (default). Also accepts the anthropic / openai / google vendor aliases |
| max_tokens | number | No | Accepted for OpenAI compatibility; currently informational |
| temperature | number | No | Accepted for OpenAI compatibility; currently not forwarded to the CLI |
Error Codes
| Code | HTTP Status | Meaning |
|------|-------------|---------|
| UNAUTHORIZED | 403 | Invalid or missing auth token |
| INVALID_REQUEST | 400 | Bad JSON or missing required fields |
| BINARY_NOT_FOUND | 500 | CLI tool not installed |
| NOT_AUTHENTICATED | 401 | CLI tool not logged in |
| RATE_LIMITED | 429 | Provider rate limit reached |
| EMPTY_RESPONSE | 500 | CLI returned no output |
| EXECUTION_FAILED | 500 | CLI process error |
Configuration
Configuration is loaded with cascading priority: CLI args > env vars > config file > defaults.
Config File
Create ~/.vivagents/config.json (or vivagents.config.json in the working directory):
{
"port": 3456,
"host": "0.0.0.0",
"timeout": 90000,
"logLevel": "info",
"providers": {
"claude": {
"enabled": true,
"path": null,
"models": ["claude-sonnet-4-6", "claude-opus-4-6", "claude-haiku-4-5"],
"defaultModel": "claude-sonnet-4-6"
},
"codex": {
"enabled": true,
"path": null,
"models": ["gpt-5.4", "gpt-5.4-mini", "gpt-5.2", "gpt-5.1"],
"defaultModel": "gpt-5.4"
},
"gemini": {
"enabled": true,
"path": null,
"models": ["gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.5-flash-lite"],
"defaultModel": "gemini-2.5-flash"
}
}
}Set "path" to override auto-detection for a specific CLI. Set "enabled": false to disable a provider.
Example: only allow Claude, disable Codex and Gemini:
{
"providers": {
"claude": { "enabled": true },
"codex": { "enabled": false },
"gemini": { "enabled": false }
}
}Disabled providers won't appear in /health or /models responses, and /process requests to them will return an error. You only need to include the fields you want to change — defaults apply for everything else.
Note on model lists: The default model lists are derived empirically — they include models confirmed to work with each CLI tool at the time of release. CLI providers may support additional models not listed here. You can customize the model list per provider in the config file.
Environment Variables
VIVAGENTS_PORT=3456
VIVAGENTS_HOST=0.0.0.0
VIVAGENTS_TOKEN=your-secret-token
VIVAGENTS_TIMEOUT=90000
VIVAGENTS_LOG_LEVEL=info # debug, info, warn, error
VIVAGENTS_CLAUDE_PATH=/path/to/claude
VIVAGENTS_CODEX_PATH=/path/to/codex
VIVAGENTS_GEMINI_PATH=/path/to/geminiDeployment
Run with PM2 (recommended for always-on)
npm install -g vivagents pm2
pm2 start vivagents -- start
pm2 save
pm2 startup # Auto-start on bootRun as systemd service (Linux)
First install globally: npm install -g vivagents, then find the path: which vivagents.
Create /etc/systemd/system/vivagents.service:
[Unit]
Description=VivAgents CLI Server
After=network.target
[Service]
Type=simple
User=your-user
ExecStart=/path/from/which/vivagents start
Restart=always
RestartSec=5
Environment=PATH=/usr/local/bin:/usr/bin:/your/node/bin
[Install]
WantedBy=multi-user.targetsudo systemctl enable vivagents
sudo systemctl start vivagentsSecurity
Warning: VivAgents serves plain HTTP with no TLS. Do not expose it directly to the public internet. Your auth token and all request/response data will be transmitted in plaintext and can be intercepted. Use one of these approaches for secure remote access:
- Tailscale (recommended) — encrypted private network, no public exposure
- Reverse proxy (Caddy, nginx) — terminate TLS in front of VivAgents
- SSH tunnel —
ssh -L 3456:localhost:3456 user@vpsFor local-only use (same machine), bind to localhost:
vivagents start --host 127.0.0.1
Secure access with Tailscale
For VPS deployments, Tailscale provides encrypted private networking without exposing the server to the public internet:
# On VPS
tailscale up
vivagents start # Binds to 0.0.0.0:3456
# From your phone/laptop (within tailnet)
curl http://<tailscale-ip>:3456/healthPublic access with HTTPS
If you need to expose VivAgents on a public IP, put it behind a reverse proxy with TLS. Caddy is the easiest option — it auto-provisions HTTPS certificates via Let's Encrypt:
- Install Caddy:
sudo apt install caddy - Edit
/etc/caddy/Caddyfile:
vivagents.yourdomain.com {
reverse_proxy localhost:3456
}- Bind VivAgents to localhost only:
vivagents start --host 127.0.0.1- Restart Caddy:
sudo systemctl restart caddy
Caddy handles TLS on port 443, forwards to VivAgents on localhost:3456. Your auth token is transmitted encrypted. Point your DNS A record to your server's public IP.
CLI authentication on a headless server
CLI tools require browser-based authentication on first use. On a headless VPS:
- SSH with port forwarding:
ssh -L 8080:localhost:8080 user@vps, then run the CLI auth flow — it will open the callback URL on your local machine. - Or authenticate locally first, then copy the credential directories to the server:
- Claude:
~/.claude/ - Codex:
~/.codex/ - Gemini:
~/.config/gemini/(or~/.gemini/)
- Claude:
Project Structure
vivagents/
├── src/
│ ├── index.ts # Entry point, CLI commands
│ ├── server.ts # HTTP server, routing, auth
│ ├── config.ts # Config loading (file + env + args)
│ ├── logger.ts # Structured logging
│ ├── providers/
│ │ ├── types.ts # CLIProvider interface + chat types
│ │ ├── aliases.ts # Vendor-name aliases (anthropic → claude, etc.)
│ │ ├── discovery.ts # Binary auto-discovery
│ │ ├── claude.ts # Claude Code provider (enhance + chat)
│ │ ├── codex.ts # Codex CLI provider
│ │ └── gemini.ts # Gemini CLI provider
│ ├── routes/
│ │ ├── health.ts # GET /health
│ │ ├── models.ts # GET /models
│ │ ├── enhance.ts # POST /process handler
│ │ └── chatCompletions.ts # POST /v1/chat/completions handler
│ ├── translator/
│ │ ├── openaiToClaude.ts # messages[] → flattened prompt
│ │ └── claudeToOpenaiSSE.ts # Claude stream-json → OpenAI SSE chunks
│ └── utils/
│ ├── process.ts # Child process spawning (sync, stdin, streaming)
│ └── error-detection.ts
├── bin/vivagents.js # CLI entry point
├── package.json
└── tsconfig.jsonContributing
Contributions are welcome! Feel free to open issues and pull requests.
git clone https://github.com/n0an/vivagents.git
cd vivagents
npm install
npm run build
node dist/index.js checkIf you'd like to add support for a new CLI provider, look at src/providers/claude.ts as a reference — each provider implements the CLIProvider interface.
License
MIT
