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.
To the extent possible under law, Caian Ertl has waived all copyright
and related or neighboring rights to this work. In the spirit of freedom of
information, I encourage you to fork, modify, change, share, or do whatever
you like with this project! ^C ^V