Files
skills/plugins/library-navigator/skills/sp-readline/SKILL.md
2026-05-29 08:20:56 +01:00

3.9 KiB

name, description
name description
sp-readline Terminal I/O primitive — AsyncGenerator line reader and interactive prompt interface for Node.js streams and Web Streams.

Readline for Suaveplan

AsyncGenerator line reader and interactive prompt interface for Node.js streams, file paths, and Web Streams. readLines yields lines from any source with optional empty-line skipping, max-line-length guards, and AbortSignal support. createInterface builds a REPL-like prompt object with question() and async-iterator semantics.

Architecture

Component Responsibility
createScanner Low-level byte scanner over a Readable stream; yields lines, enforces maxLineLength, respects AbortSignal.
readLines Public AsyncGenerator that adapts file paths, Node Readable, or Web ReadableStream to createScanner.
createInterface Interactive prompt interface with question(prompt) and [Symbol.asyncIterator] for piped input.
ReadlineError Typed error carrying READLINE_CODES.SCANNER.*, READLINE_CODES.INTERFACE.*, or READLINE_CODES.READER.*.

File Structure

devtools/tooling/readline/
  src/scanner/          # createScanner — byte-level line scanner
  src/reader/           # readLines — multi-source AsyncGenerator
  src/interface/        # createInterface — interactive prompt / REPL
  src/errors/           # READLINE_CODES registry + ReadlineError

Sibling packages:

  • @suaveplan/linter — unified markdown + biome linting pipeline
  • @suaveplan/profiler — runtime performance profiling toolkit
  • @suaveplan/sandbox — sandboxed code execution
  • @suaveplan/test-vite — Vite + Vitest configuration factories

Patterns

Quick Start — iterate lines from a file

import { readLines } from "@suaveplan/readline/reader/reader";

for await (const line of readLines("/var/log/app.log")) {
  console.log(line);
}

From a Node.js Readable with options

import { createReadStream } from "node:fs";
import { readLines } from "@suaveplan/readline/reader/reader";

for await (const line of readLines(createReadStream("data.csv"), { skipEmpty: true })) {
  process(line);
}

Abort support and line-length guard

import { readLines } from "@suaveplan/readline/reader/reader";

const ac = new AbortController();
setTimeout(() => ac.abort(), 5_000);
for await (const line of readLines("huge.txt", { maxLineLength: 4096, signal: ac.signal })) {
  console.log(line);
}

Interactive prompt

import { createInterface } from "@suaveplan/readline/interface/interface";

const rl = createInterface({ input: process.stdin, output: process.stdout });

const name = await rl.question("Your name: ");
console.log(`Hello, ${name}!`);

for await (const line of rl) {
  if (line === "quit") break;
  console.log(`echo: ${line}`);
}

rl.close();

Constraints

  • Node ≥ 20 — relies on node:fs/createReadStream and node:stream.
  • Scanner default maxLineLength is 4096 — exceeding it throws ReadlineError(READLINE_CODES.SCANNER.LINE_TOO_LONG).
  • CR characters are stripped — \r is ignored; only \n terminates a line.
  • AbortSignal unblocks the generator cleanly — the scanner pushes a done item when the signal fires, so the loop exits without throwing.
  • Interface operations throw after close() — question() and next() on a closed interface raise ReadlineError(READLINE_CODES.INTERFACE.CLOSED).
  • Error codes go through defineCodes — READLINE_CODES groups READLINE.SCANNER.*, READLINE.INTERFACE.*, and READLINE.READER.*; never hand-write the string literal.

Checklist

  1. Use readLines for batch line processing; use createInterface for interactive prompts.
  2. Always pass signal when reading from potentially unbounded streams.
  3. Set maxLineLength when reading untrusted input to prevent memory exhaustion.
  4. Close the interface with rl.close() when done to end the OTel span.