Skip to content

Commit adb8880

Browse files
committed
feat(ui): --debug-input flag logs raw keystrokes for support
When users report "Backspace doesn't work" or "paste behaves weirdly," the fastest way to diagnose is to see the actual bytes their terminal emits. Running with --debug-input writes each Ink useInput call (input string + key flags + raw code points) to ~/.codebase/logs/input.log as JSONL. No-op when the env var is unset, so production users pay nothing.
1 parent d3a8268 commit adb8880

3 files changed

Lines changed: 95 additions & 1 deletion

File tree

src/cli.tsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,20 @@ interface ParsedRunArgs {
2525

2626
const VALID_OUTPUT_FORMATS = new Set<HeadlessOutputFormat>(["text", "json", "stream-json"]);
2727

28-
const argv = process.argv.slice(2);
28+
const rawArgv = process.argv.slice(2);
29+
30+
// Strip flags consumed at the top-level dispatcher before subcommand
31+
// matching. `--debug-input` is one of those: it sets an env var that
32+
// the Input component picks up, then disappears from argv so it can't
33+
// confuse downstream parsers.
34+
const argv: string[] = [];
35+
for (const a of rawArgv) {
36+
if (a === "--debug-input") {
37+
process.env.CODEBASE_DEBUG_INPUT = "1";
38+
continue;
39+
}
40+
argv.push(a);
41+
}
2942

3043
if (argv[0] === "--version" || argv[0] === "-v") {
3144
process.stdout.write(`${readPackageVersion()}\n`);
@@ -132,6 +145,10 @@ function printHelp(): void {
132145
" codebase --version print version and exit",
133146
" codebase --help show this message",
134147
"",
148+
"Diagnostics:",
149+
" --debug-input log every keystroke to ~/.codebase/logs/input.log",
150+
" (use when reporting a keyboard/terminal issue)",
151+
"",
135152
"More: https://github.com/codebase-foundation/codebase-cli",
136153
"",
137154
].join("\n"),

src/ui/Input.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
undo,
1616
yank,
1717
} from "./input-state.js";
18+
import { logInputEvent } from "./debug-input.js";
1819
import { completePath, findAtTokenAt } from "./path-complete.js";
1920

2021
export interface SlashCommandSuggestion {
@@ -120,6 +121,10 @@ export function Input({ disabled, onSubmit, onAbort, commands, history, cwd }: I
120121
const clampedSuggestionIdx = Math.min(suggestionIdx, Math.max(0, suggestions.length - 1));
121122

122123
useInput((input, key) => {
124+
// First thing — log every keystroke when --debug-input is on, so
125+
// we can diagnose "key X doesn't work" reports from real terminals.
126+
logInputEvent(input, key);
127+
123128
if (key.ctrl && input === "c") {
124129
onAbort?.();
125130
return;

src/ui/debug-input.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { appendFileSync, mkdirSync } from "node:fs";
2+
import { homedir } from "node:os";
3+
import { join } from "node:path";
4+
5+
/**
6+
* Opt-in raw-input logger. When CODEBASE_DEBUG_INPUT=1 (set by the
7+
* `--debug-input` flag), every keystroke Ink hands us is appended to
8+
* `~/.codebase/logs/input.log` as one JSON line. We can ask a user
9+
* with a wedged keyboard ("backspace doesn't work", "weird chars on
10+
* paste") to share that file and see exactly what bytes their
11+
* terminal emitted.
12+
*
13+
* No-op when the env var is unset, so production users pay nothing.
14+
*/
15+
16+
interface InkKeyShape {
17+
readonly leftArrow?: boolean;
18+
readonly rightArrow?: boolean;
19+
readonly upArrow?: boolean;
20+
readonly downArrow?: boolean;
21+
readonly return?: boolean;
22+
readonly escape?: boolean;
23+
readonly ctrl?: boolean;
24+
readonly shift?: boolean;
25+
readonly tab?: boolean;
26+
readonly backspace?: boolean;
27+
readonly delete?: boolean;
28+
readonly pageDown?: boolean;
29+
readonly pageUp?: boolean;
30+
readonly meta?: boolean;
31+
}
32+
33+
let logPath: string | null = null;
34+
let warned = false;
35+
36+
export function isDebugInputEnabled(): boolean {
37+
return process.env.CODEBASE_DEBUG_INPUT === "1";
38+
}
39+
40+
function resolveLogPath(): string {
41+
if (logPath) return logPath;
42+
const dir = join(homedir(), ".codebase", "logs");
43+
try {
44+
mkdirSync(dir, { recursive: true });
45+
} catch {
46+
// The fs error is non-fatal; the appendFileSync below will throw
47+
// the same problem and we'll surface it once via the `warned`
48+
// guard, not on every keystroke.
49+
}
50+
logPath = join(dir, "input.log");
51+
return logPath;
52+
}
53+
54+
export function logInputEvent(input: string, key: InkKeyShape): void {
55+
if (!isDebugInputEnabled()) return;
56+
try {
57+
const entry = {
58+
t: new Date().toISOString(),
59+
// Show the actual code points so a stray \x7f or \x1b is visible.
60+
input,
61+
codes: Array.from(input).map((ch) => ch.charCodeAt(0)),
62+
key,
63+
};
64+
appendFileSync(resolveLogPath(), `${JSON.stringify(entry)}\n`);
65+
} catch (err) {
66+
if (warned) return;
67+
warned = true;
68+
process.stderr.write(
69+
`\n[debug-input] failed to write log: ${err instanceof Error ? err.message : String(err)}\n`,
70+
);
71+
}
72+
}

0 commit comments

Comments
 (0)