REPL interface pattern

readline-based, works for any CLI tool

Problem

Building a CLI tool that needs to accept continuous user input — a chatbot, a debugger shell, an interactive query interface — requires managing the input/output loop, prompt rendering, signal handling (Ctrl-C, Ctrl-D), and graceful shutdown. Reimplementing these mechanics from scratch in each project is error-prone and produces inconsistent behavior across tools. Eliza needed a reliable, minimal interactive shell for the terminal-based conversation interface.

Solution

Use Node.js’s built-in readline module to create a read-eval-print loop. Configure a readline.Interface with process.stdin and process.stdout, define a prompt string, and listen for the line event to process each user input. Handle close for graceful exit on EOF (Ctrl-D) and optionally listen for SIGINT (Ctrl-C) to exit cleanly.

import * as readline from "readline";
 
function startREPL(handler: (input: string) => string): void {
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
    prompt: "> ",
  });
 
  rl.prompt();
 
  rl.on("line", (line) => {
    const input = line.trim();
    if (!input) {
      rl.prompt();
      return;
    }
    const response = handler(input);
    console.log(response);
    rl.prompt();
  });
 
  rl.on("close", () => {
    console.log("\nGoodbye.");
    process.exit(0);
  });
}

The handler function decouples the REPL mechanics from the application logic. Any function that takes a string and returns a string can be plugged in.

Usage

Apply for any Node.js CLI tool that requires interactive, stateful user input. Covers:

  • Conversational agents (Eliza-style or LLM-backed)
  • Interactive debugger shells
  • Query interfaces for local data stores
  • Development tools requiring repeated test inputs

The pattern uses zero external dependencies — Node.js’s readline is part of the standard library. For richer features (history, autocompletion, multi-line input), consider readline extensions or the enquirer package, but only introduce them if the basic pattern proves insufficient.

Trade-offs

  • Pro: Zero dependencies. Works in any Node.js environment without installation.
  • Pro: The handler abstraction keeps application logic completely independent of I/O mechanics — trivial to test the handler in isolation.
  • Con: readline does not support multi-line input or complex keybinding customization without significant additional work.
  • Con: Output written to process.stdout during async handler execution can interleave with the prompt display, producing garbled output. Async handlers require careful prompt re-display after awaiting responses.

See Also

Source

Synced from ops-core learning data. Original source: Eliza