Keys

Terminal UI primitives: raw single-key input, live terminal size, and resize notifications.

./keys # run it in a real terminal and press keys; 'q' quits ./keys </dev/null # non-TTY: prints the size and exits cleanly

The Node-faithful surface hangs off `process` exactly where Node puts it (setRawMode, stdout.columns/.rows, the 'SIGWINCH' resize signal); the one primitive Node has no counterpart for — a synchronous single-keystroke read — comes from the bespoke `klain:tty` module.

import { readKey } from "klain:tty";

// .columns/.rows are `number | undefined` — off a TTY (piped/redirected)
// they are a real `undefined`, exactly as in Node; `?? 80` is the idiomatic
// fallback when a concrete size is needed.
console.log(
  "terminal: " +
    process.stdout.columns +
    "x" +
    process.stdout.rows +
    (process.stdin.isTTY ? " (interactive)" : " (not a tty)"),
);
console.log("usable width: " + (process.stdout.columns ?? 80));

// Piped/redirected: no keyboard to read, so report and leave. Every raw-mode
// program should guard on isTTY before touching raw mode, exactly as here.
if (!process.stdin.isTTY) {
  console.log("stdin is not a terminal — nothing to read.");
} else {
  // Print the new size whenever the window is resized.
  process.on("SIGWINCH", () => {
    console.log("\r\nresized to " + process.stdout.columns + "x" + process.stdout.rows);
  });

  // Raw mode: keystrokes arrive one at a time, Ctrl-C no longer sends SIGINT
  // (it comes through as the raw byte 3), and nothing is echoed automatically.
  process.stdin.setRawMode(true);
  console.log("Press keys (q or Ctrl-C to quit):\r");

  let running = true;
  while (running) {
    const key: string = readKey();
    const code: number = key.length > 0 ? key.charCodeAt(0) : -1;

    if (code === -1 || code === 113 || code === 3) {
      // EOF, 'q', or Ctrl-C.
      running = false;
    } else if (key === "\x1b[A") {
      process.stdout.write("up\r\n");
    } else if (key === "\x1b[B") {
      process.stdout.write("down\r\n");
    } else if (key === "\x1b[C") {
      process.stdout.write("right\r\n");
    } else if (key === "\x1b[D") {
      process.stdout.write("left\r\n");
    } else if (code >= 32 && code < 127) {
      process.stdout.write("key '" + key + "' (" + code + ")\r\n");
    } else {
      process.stdout.write("byte " + code + "\r\n");
    }
  }

  // Always restore the terminal before exiting, or the shell is left in raw
  // mode and the user has to blind-type `reset`.
  process.stdin.setRawMode(false);
  console.log("bye");
}
View source on GitHubexamples/tty/keys.ts