Doppel keeps terminal sessions alive behind a small daemon, then gives you a CLI, HTTP/tRPC API, WebSocket terminal stream, browser terminal view, and embeddable Node.js engine to control them.
Use it when a workflow needs more than exec: dev servers that should stay up,
agent-owned shell sessions, commands that should be sent into an existing
terminal, or cron-like jobs that should run in a durable local automation layer.
CLI | daemon | web UI | embeddable engine | schedules | JSON output
Doppel is published on npm as three packages:
@c3-oss/doppel- thedoppelCLI.@c3-oss/doppel-server- the
doppel-serverdaemon and HTTP/tRPC server.
- the
@c3-oss/doppel-core- the embeddable terminal and schedule engine.
Install the CLI and server globally:
npm install -g @c3-oss/doppel @c3-oss/doppel-serverRun the published binaries through npx by command name:
npx doppel-server start --daemon
npx doppel healthInstall the core package when embedding Doppel in an app:
npm install @c3-oss/doppel-coreRequires Node.js 22+.
Start the daemon in the background:
doppel-server start --daemonCheck that the server is reachable:
doppel healthCreate a named terminal session and send work into it:
doppel session start dev
doppel send-cmd --session dev "pnpm dev"Watch the session in your terminal, print the served browser terminal view URL, or open the browser terminal view directly:
doppel session watch dev
doppel session view dev
doppel session view dev --openSchedule a command:
doppel schedule add \
--name daily-health \
--command "pnpm test" \
--cron "0 9 * * *" \
--enabledUse JSON output for scripts:
doppel session list --json
doppel schedule list --json
doppel-server status --json- Persistent named terminal sessions managed by a local daemon.
- CLI commands for health checks, session lifecycle, terminal input, and schedules.
- Browser session view for a single terminal via
/session-view. - Optional administrative web UI served on a separate port.
- HTTP and tRPC API for structured clients.
- WebSocket terminal streams for interactive browser clients.
- Scheduled commands that run in either ephemeral processes or existing sessions.
- Human-readable tables by default, JSON output for automation.
- Embeddable core engine through
@c3-oss/doppel-core.
doppel health
doppel session list
doppel session start [name]
doppel session watch [name]
doppel session view [name]
doppel session view [name] --open
doppel session kill [name]
doppel send-cmd [-s name] "command text"
doppel send-key [-s name] enter
doppel schedule list
doppel schedule add --name name --command "cmd" --cron "*/5 * * * *"
doppel schedule enable <id>
doppel schedule disable <id>
doppel schedule run <id>
doppel schedule remove <id>Pass --url to target a non-default server:
doppel health --url http://localhost:3000Pass --json on supported commands when another program should consume the
output.
doppel-server start runs the daemon HTTP/tRPC server on port 3000 by default.
It exposes:
/health/trpc/ws/terminal/:sessionName/session-view
Run it in the foreground:
doppel-server startRun it as a background daemon:
doppel-server start --daemonCheck or stop the daemon:
doppel-server status
doppel-server stopServer request logs are pretty-printed by default. Use --json-logs for raw
newline-delimited JSON logs, or --no-logger to disable request logging.
doppel session view [name] ensures the session and prints the minimal
terminal-only page URL on the daemon port. Use doppel session view [name] --open to launch that served view in Chrome through Playwright. It is not the
administrative web UI.
Start the administrative web UI explicitly:
doppel-server start --web-uiBy default, the web UI binds to port 3001 and talks to the daemon at
http://localhost:3000.
Useful overrides:
doppel-server start \
--web-ui \
--web-ui-port 3001 \
--web-ui-host 127.0.0.1 \
--web-ui-server-url http://localhost:3000Use @c3-oss/doppel-core when you want the engine without the daemon binary:
import { createDoppel } from '@c3-oss/doppel-core';
const doppel = createDoppel();
const session = doppel.terminal.ensure({ name: 'dev' });
doppel.terminal.send(session.name, 'pnpm test\n');
const result = await doppel.terminal.runEphemeral('printf doppel');
console.log(result.output);
doppel.close();Use @c3-oss/doppel-server when you want the Fastify/tRPC adapter:
import { startServer, type AppRouter } from '@c3-oss/doppel-server';@c3-oss/doppelpublishes thedoppelCLI.@c3-oss/doppel-serverpublishes thedoppel-serverdaemon and server library.@c3-oss/doppel-corepublishes the transport-agnostic engine.
This repository is a Node 22 TypeScript monorepo:
packages/doppel-core- terminal sessions, schedules, and persistence.apps/server- Fastify HTTP server, tRPC router, daemon CLI, and web UI host.apps/web- Vite React administrative web UI.apps/cli- Commander-baseddoppelCLI.
Development setup:
devbox shell
pnpm installWithout Devbox, use Node.js 22 and pnpm 10.
Common commands:
pnpm dev
pnpm build
pnpm typecheck
pnpm test
pnpm test:coverage
pnpm lint
pnpm lint:fix
pnpm cleanFocused examples:
pnpm --filter @c3-oss/doppel-server dev
pnpm --filter @c3-oss/doppel health --url http://localhost:3000
pnpm --filter @c3-oss/doppel-web devThe root package is private. Publishable packages are:
@c3-oss/doppel@c3-oss/doppel-core@c3-oss/doppel-server
Use Changesets for releases:
pnpm changeset
pnpm version-packages
pnpm releaseDoppel is a small control plane for terminals: keep sessions alive, send them work, observe their output, and schedule commands without rebuilding that layer in every tool.