feat(cli): alt-buffer, top-of-screen draw, -h/--help flags

- Enter alternate screen buffer (\x1b[?1049h) before drawing and exit
  it (\x1b[?1049l) on cleanup. This preserves the user's terminal
  content when the app quits.
- Clear screen once after entering alt buffer so drawing starts from
  the top-left.
- Add -h and --help flags that print usage, stdin/file modes, and
  controls (q / Ctrl-C to quit).
- Update tests for new alt-buffer helpers and help text.
- Rebuild CLI bundle (dist/qr-terminal.js 95.4kb) and redeploy webapp.
This commit is contained in:
Hermes Agent
2026-05-03 22:14:50 +00:00
parent c0baff8d42
commit 352fc2d459
3 changed files with 85 additions and 6 deletions
+43 -4
View File
@@ -19,10 +19,40 @@ import { generateQRMatrix } from '../core/qr/qr_encode';
import { packetize } from '../core/sender/packetizer';
import { scheduleFrames } from '../core/sender/scheduler';
import { QR_VERSION, ECC_LEVEL } from '../core/protocol/constants';
import { clearScreen, hideCursor, showCursor, renderToTerminal, moveCursorUp } from './terminal_raster';
import {
enterAltBuffer,
exitAltBuffer,
clearScreen,
hideCursor,
showCursor,
renderToTerminal,
moveCursorUp,
} from './terminal_raster';
const FPS_MS = 100;
// ─────────────────────────────────────────────────────────────────────────────────
// Help text
// ─────────────────────────────────────────────────────────────────────────────────
const HELP_TEXT = `
QR Terminal Display encode text or a file into a looping QR-code sequence.
Usage:
qr-terminal [file] read from file
echo "text" | qr-terminal read from stdin
Controls:
q, Q quit
Ctrl-C quit
The app uses the same V10-M QR protocol as the web transfer demo.
`;
function showHelp(): void {
console.log(HELP_TEXT.trim());
}
// ─────────────────────────────────────────────────────────────────────────────────
// Argument parsing
// ─────────────────────────────────────────────────────────────────────────────────
@@ -62,6 +92,12 @@ function buildFrames(data: Uint8Array): Uint8Array[] {
// ─────────────────────────────────────────────────────────────────────────────────
function main() {
const args = process.argv.slice(2);
if (args.includes('-h') || args.includes('--help')) {
showHelp();
process.exit(0);
}
let data: Uint8Array;
try {
data = readInput();
@@ -114,7 +150,6 @@ function main() {
}
if (firstDraw) {
clearScreen();
firstDraw = false;
} else {
// Move cursor back to the first QR line so we overwrite in-place
@@ -128,15 +163,19 @@ function main() {
function cleanup() {
running = false;
clearInterval(interval);
clearScreen();
exitAltBuffer();
showCursor();
if (ttyFd !== null) {
try { closeSync(ttyFd); } catch {}
}
process.stdout.write('QR terminal display stopped.\n');
console.log('QR terminal display stopped.');
process.exit(0);
}
// Switch to alternate buffer and clear it before drawing
enterAltBuffer();
clearScreen();
// Keyboard handling — try /dev/tty first so it works even when stdin is a pipe
let ttyFd: number | null = null;
try {
+14
View File
@@ -65,6 +65,20 @@ export function renderToTerminal(matrix: boolean[][], quietZone: number = 4): st
return lines;
}
/**
* Enter the alternate screen buffer (preserves normal buffer on exit).
*/
export function enterAltBuffer(): void {
process.stdout.write('\x1b[?1049h');
}
/**
* Exit the alternate screen buffer and restore the normal buffer.
*/
export function exitAltBuffer(): void {
process.stdout.write('\x1b[?1049l');
}
/**
* Clear the terminal screen and move cursor to home position.
*/
+28 -2
View File
@@ -148,15 +148,41 @@ describe('Terminal Rasterizer', () => {
});
describe('CLI Screen Helpers', () => {
it('should produce escape sequences', async () => {
it('should expose clearScreen', async () => {
const { clearScreen } = await import('@/cli/terminal_raster');
expect(typeof clearScreen).toBe('function');
});
it('should produce cursor-up sequence', async () => {
it('should expose moveCursorUp', async () => {
const { moveCursorUp } = await import('@/cli/terminal_raster');
expect(typeof moveCursorUp).toBe('function');
});
it('should expose alt-buffer helpers', async () => {
const { enterAltBuffer, exitAltBuffer } = await import('@/cli/terminal_raster');
expect(typeof enterAltBuffer).toBe('function');
expect(typeof exitAltBuffer).toBe('function');
});
});
describe('CLI Help Flag', () => {
it('should not throw when help text is constructed', () => {
const helpText = `
QR Terminal Display encode text or a file into a looping QR-code sequence.
Usage:
qr-terminal [file] read from file
echo "text" | qr-terminal read from stdin
Controls:
q, Q quit
Ctrl-C quit
The app uses the same V10-M QR protocol as the web transfer demo.
`;
expect(helpText).toContain('Usage:');
expect(helpText).toContain('quit');
});
});
describe('CLI Encoder Pipeline', () => {