#!/usr/bin/env node /** * Standalone TTY repro for fish-style readline (lone `>`, missing `[user@host:path]`). * * Run in a real terminal (not piped): * cd packages/bare-os-booter && node scripts/fish-tty-repro.js * # or: npm run repro:fish-tty -w bare-os-booter * * Automated PTY (drains stdout so Node does not block; checks for green prompt): * python3 scripts/run-fish-repro-pty.py * # or: npm run repro:fish-tty:pty -w bare-os-booter * * Env: * BARE_OS_REPRO_SKIP_STDERR=1 — omit stderr preamble (isolates stdout/stderr ordering). * BARE_OS_REPRO_SKIP_PREAMBLE=1 — skip fake kernel lines; only fish prompt. * BARE_OS_FISH_RESYNC_LINE=0 — disable leading newline before each prompt (default is on). * DEBUG=1 — verbose fish/TTY logs on the same terminal as the shell (debug-repl.js). * * Type a few commands; use `exit` or Ctrl+D to quit. Compare first prompt vs after ^C. */ import b4a from 'b4a' import { bindReplDebugStream, unbindReplDebugStream } from '../lib/debug-repl.js' import { createFishReadLine, disableFishRawMode, releaseFishStdin } from '../lib/fish-readline.js' function mockCtx() { return { vfs: { getcwd: () => '/home/user', resolveLogical: (p) => (p === '' ? '/' : p), async readdir() { return [] } }, drive: { readdir() { async function* empty() { /* no /bin entries */ } return empty() } }, personalDrive: { async get() { return null }, async put() {} }, b4a, env: { HOME: '/home/user', USER: 'user', HOSTNAME: 'bare-os', PATH: '/bin', PWD: '/home/user' } } } /** Same idea as executeKernel writeScreen (TTY clear hook). */ function makeWriteScreen(stdout) { return (chunk) => { const s = typeof chunk === 'string' ? chunk : String(chunk) stdout.write(s) } } function kernelPreambleToStdout(stdout) { const lines = [ 'NAME="BareOS"', 'VERSION="0.1.0"', 'VARIANT="hyperdrive-only"', '', 'Bare operating system — POSIX-ish shell: cd, export, exit, quit | try: help, ls /bin, pwd' ] for (const line of lines) stdout.write(line + '\n') } async function main() { const stdin = process.stdin const stdout = process.stdout if (!stdin.isTTY || typeof stdin.setRawMode !== 'function') { process.stderr.write( 'fish-tty-repro: need a TTY with setRawMode (run in a terminal, not a pipe).\n' ) process.exit(1) } const skipStderr = process.env.BARE_OS_REPRO_SKIP_STDERR === '1' const skipPreamble = process.env.BARE_OS_REPRO_SKIP_PREAMBLE === '1' if (!skipStderr) { process.stderr.write( '\x1b[0m[bare-os-booter] Fish-style TTY line editor active\n' ) } const ctx = mockCtx() const writeScreen = makeWriteScreen(stdout) if (process.env.DEBUG === '1') bindReplDebugStream(stdout) const fishRead = await createFishReadLine(ctx, { stdin, stdout, writeScreen }) if (!fishRead) { process.stderr.write('createFishReadLine returned null.\n') process.exit(1) } if (!skipPreamble) { kernelPreambleToStdout(stdout) } const readLine = (prompt) => fishRead(prompt) process.stderr.write( '\n--- repro ready: first prompts should show full [user@bare-os:~] > if healthy ---\n' ) try { while (true) { const line = await readLine('repro> ') if (line == null) { stdout.write('\n') break } const t = line.trim() if (t === 'exit' || t === 'quit') break if (t === 'cls' || t === 'clear') { writeScreen('\x1b[H\x1b[2J\x1b[3J') continue } if (t) stdout.write(`(cmd) ${t}\n`) } } finally { disableFishRawMode(stdin) releaseFishStdin(stdin) unbindReplDebugStream() } } main().catch((err) => { process.stderr.write(String(err?.stack || err) + '\n') process.exit(1) })