Agent skill (portable): skills/node-repl-cli/SKILL.md — see skills/README.md for the skills/ layout.
Named, persistent Node.js REPL sessions for automation and AI agents — without asking a model to “type into” an interactive shell. Each session is a real node:repl context in its own process, so variables and module state survive across evaluations until you remove the session or restart the machine.
- Run JavaScript as in the Node REPL (shared context per session), from scripts, CI, or LLM tool calls.
- Give AI coding agents a small, predictable surface: create session → run code → read history → tear down — no terminal UI, no
stdinREPL loop. - Keep multiple isolated sessions at once (e.g.
app,tests,scratch) without one session polluting another.
flowchart LR
subgraph cli["node-repl-cli (short-lived)"]
C1[create / exec / …]
end
subgraph coord["Coordinator serve"]
M[ReplSessionManager Map]
T[TCP JSON-RPC]
end
subgraph daemons["Per-session daemons"]
D1[REPL + TCP]
D2[REPL + TCP]
end
C1 -->|loopback| T
T --> M
M --> D1
M --> D2
- Session daemon — For each session name, a detached child process runs
ReplDaemonMainwithprocess.cwd()set to a user-local workspace (…/node-repl-cli/sessions/<name>/): oneNodeReplSession(REPL server) plus a loopback TCP control plane (eval,history,shutdown). The parent reads a one-line JSON handshake from the child’s stdout to learnport/pid. The running session table stays in the coordinator’s memory only; dependencies for that name live in that folder (package.json,node_modules). - Coordinator —
node-repl-cli serveholds an in-memory name → (host, port, pid, workspace) table and exposes newline-delimited JSON-RPC over TCP (ping,create,eval,history,remove,list,pkgInstall,pkgRemove,pkgList). Stateless CLI commands talk to this coordinator. - Coordinator lock file — After
servebinds and self-pings, it writescoordinator.jsonunder the state directory (REPL_CLI_STATE_DIRor the default OS-specific path; see below) so later CLI invocations can find it without flags. On graceful shutdown (signal or last session removed), the lock is cleared. - MCP mode —
node-repl-cli mcpembeds the sameReplSessionManagerinside the MCP process: no coordinator TCP, no lock file. Each tool call drives daemons the same way, but the registry lives only for the lifetime of that MCP server.
| Feature | Description |
|---|---|
| True REPL semantics | node:repl per session — let / require / globals persist across exec calls in that session. |
| Multi-session | Many named sessions; each has its own process and REPL context. |
| Agent-friendly CLI | No interactive shell required; each subcommand is one-shot and scriptable. |
| Zero-config by default | Autodiscovery via lock file + autostart of serve when needed (REPL_CLI_AUTOSTART=0 to disable). |
| Per-session npm | `node-repl-cli pkg install |
| Disk layout | Coordinator lock file plus optional sessions/<name>/ trees for npm; no per-session daemon metadata files. |
| Coordinator auto-exit | With serve, after remove deletes the last session, the coordinator exits and clears the lock (no orphan serve). |
| MCP | Model Context Protocol tools for Cursor, Claude Desktop, etc. |
git clone <repo-url>
cd node-repl-cli
npm install
npm run buildGlobal CLI (after npm link or npm install -g .):
node-repl-cli --helpLocal / CI:
npx node-repl-cli --help
# or
node dist/cli.js --helpTypical flow — no address flags; the first command may autostart serve in the background:
node-repl-cli pkg mycli install lodash # optional: before or after create
node-repl-cli pkg mycli list # declared dependencies from package.json
node-repl-cli create mycli
node-repl-cli exec mycli -c "const fs = await import('node:fs'); fs.readFileSync.length"
node-repl-cli exec mycli -c "globalThis.n = (globalThis.n ?? 0) + 1; globalThis.n"
node-repl-cli history mycli # JSON array: input, output, error, captured console streams
node-repl-cli list # JSON array of session records (name, port, workspace, …)
node-repl-cli pkg mycli remove lodash
node-repl-cli remove mycli- Session names:
^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$. pkg <name> <action>—actionisinstall(aliasesadd),remove(aliasesrm,uninstall), orlist. Install supports-D/--save-dev. It uses the same session workspace directory the daemon uses. Removing a session does not delete that folder.
Paths and ports differ per machine; below shows shape only.
create — prints one JSON object (session metadata + workspace):
$ node-repl-cli create demo
{"name":"demo","status":"ready","port":58432,"pid":12345,"createdAt":"2026-04-13T12:00:00.000Z","workspace":"/home/you/.local/state/node-repl-cli/sessions/demo"}
exec success — one JSON line per invocation; inspect error (should be null) and output:
$ node-repl-cli exec demo -c "1+1"
{"input":"1+1","output":"2","error":null,"streams":{"stdout":"","stderr":""}}
Exit code 0.
exec runtime error — error is a string (often a stack); CLI sets exit code 1:
$ node-repl-cli exec demo -c "missingVar"
{"input":"missingVar","output":null,"error":"ReferenceError: missingVar is not defined\n at ...","streams":{"stdout":"","stderr":""}}
Exit code 1.
list — running sessions only (JSON array of session records):
$ node-repl-cli list
[{"name":"demo","status":"ready","port":58432,"pid":12345,"createdAt":"...","workspace":"/home/you/.local/state/node-repl-cli/sessions/demo"}]
pkg … list — reads package.json (no npm network):
$ node-repl-cli pkg demo list
{"workspace":"/home/you/.local/state/node-repl-cli/sessions/demo","dependencies":{"lodash":"^4.17.21"},"devDependencies":{}}
pkg … install — npm may print progress to stderr / stdout first; the CLI then appends a final JSON line:
$ node-repl-cli pkg demo install semver
…npm progress on stderr/stdout…
{"ok":true,"code":0}
Non‑zero npm exit → same stderr noise, no trailing {"ok":true,...}; process exit code ≠ 0.
remove:
$ node-repl-cli remove demo
{"ok":true}
| Variable | Purpose |
|---|---|
REPL_CLI_STATE_DIR |
State root directory (absolute or cwd-relative). Contains coordinator.json and sessions/<name>/. Overrides the default OS paths below. |
REPL_CLI_NPM_CACHE |
If set, passed to npm as npm_config_cache for pkg … install / remove subprocesses (isolated npm cache per project or CI job). |
REPL_CLI_AUTOSTART |
Default on. Set to 0 / false / off / no to never spawn serve automatically; you must run node-repl-cli serve yourself. |
REPL_CLI_ADDR |
Force host:port of the coordinator (CI, multiple coordinators, or debugging). |
node-repl-cli serveDefault --port 0 (OS-assigned). Use a fixed --port if you pin REPL_CLI_ADDR.
Unless REPL_CLI_STATE_DIR is set, the state root is:
- Windows:
%LOCALAPPDATA%\node-repl-cli\— holdscoordinator.jsonandsessions\ - Linux / macOS (XDG):
$XDG_STATE_HOME/node-repl-cli/, or~/.local/state/node-repl-cli/ifXDG_STATE_HOMEis unset
With REPL_CLI_STATE_DIR=/path/to/foo, paths become /path/to/foo/coordinator.json and /path/to/foo/sessions/<name>/.
Agents should not drive an interactive REPL. Use one of these patterns.
Portable instructions for any skill-capable host: skills/node-repl-cli/SKILL.md (see skills/README.md for the layout).
Run the server once in the host config (stdio):
node-repl-cli mcpRegister tools (arguments are JSON; responses are text bodies containing JSON):
| Tool | Arguments (JSON) | Typical use |
|---|---|---|
repl_cli_ping |
— | Health check |
repl_cli_session_create |
{ "name": "mycli" } |
New session + daemon |
repl_cli_session_exec |
{ "name": "mycli", "code": "..." } |
Run code in that session’s REPL |
repl_cli_session_history |
{ "name": "mycli" } |
Returns { "ok", "entries": [...] } |
repl_cli_session_remove |
{ "name": "mycli" } |
Stop daemon + drop name |
repl_cli_pkg_install |
{ "name": "mycli", "packages": ["lodash"], "saveDev": false } |
npm install in that session’s workspace |
repl_cli_pkg_remove |
{ "name": "mycli", "packages": ["lodash"] } |
npm uninstall in that workspace |
repl_cli_pkg_list |
{ "name": "mycli" } |
Declared dependencies / devDependencies from that workspace package.json |
OpenAPI (logical MCP tool API): openapi/node-repl-cli-mcp.yaml — OpenAPI 3.0 schemas and examples for every tool; transport remains MCP stdio, not the placeholder servers.url.
Important: MCP mode keeps sessions only inside that MCP process. It does not share the coordinator used by standalone node-repl-cli create unless you design that yourself.
Suggested agent loop: optional repl_cli_pkg_install (or CLI pkg <name> install …) → create → repeated exec → optional history → repl_cli_pkg_list when you need declared deps → remove when done (workspace + node_modules remain on disk unless you delete the folder manually).
Use the same commands a human would; each invocation is stateless from the agent’s perspective, while state lives in the coordinator + daemons. See CLI examples (under Usage) for command → JSON stdout shapes, npm interleaved output, and exit codes.
node-repl-cli exec mycli -c "code"Rely on autostart + lock file so you do not pass --addr. If the sandbox cannot spawn background processes, set REPL_CLI_AUTOSTART=0 and start node-repl-cli serve in a long-lived sidecar first.
The coordinator speaks one JSON object per line over TCP on the loopback address from the lock file:
- Request:
{ "id": <any>, "method": "ping"|"create"|"eval"|"history"|"remove"|"list"|"pkgInstall"|"pkgRemove"|"pkgList", "params": { ... } }list:{}→ JSON array[ { "name", "status", "port", "pid", "createdAt", "workspace" }, ... ]pkgInstall:{ "name": "<session>", "packages": ["<spec>", ...], "saveDev"?: boolean }→{ code, stdout, stderr }pkgRemove:{ "name": "<session>", "packages": ["<name>", ...] }→{ code, stdout, stderr }pkgList:{ "name": "<session>" }→{ workspace, dependencies, devDependencies }
- Response:
{ "id": <same>, "ok": true, "result": ... }or{ "ok": false, "error": "..." }
Session daemons use a similar line protocol (ping, eval, history, shutdown).
- Listeners are bound to 127.0.0.1 only.
- User code runs with full Node privileges in session daemons (same as
node). Only use with trusted code and environments. npm install/npm uninstallrun normal npm lifecycle scripts with your user permissions; treat package specs like any shell command input.- npm subprocess: when Node ships the bundled
npm-cli.jsnext tonode.exe, this tool runsnode …/npm-cli.js <args>(nocmd.exeshell,windowsHide: trueon Windows) so package installs work even from a detachedserveparent and normally without a flashing console. If that file is missing (unusual installs), it falls back tonpmwithshell: trueon Windows only. After upgrading the package, restartserve(or let autostart spawn a fresh coordinator) so the coordinator loads the new code.
MIT