refactor: clean up main entry point
- Use shared terminal helpers (hideCursor, showCursor, clearLine, clearScreen, visibleLength, padRight) from terminal.ts - Simplify allCommandDefs dedup with new Set() - Centralize terminal state restore into exitMenu() closure + finally Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
// gai — AI-powered git commit and PR helper
|
||||
// v0.1.3
|
||||
|
||||
import { runCLI, registerCommands, formatHelp, type CommandDef, type ParsedArgs } from "./src/cli";
|
||||
import { handleCommit } from "./src/commands/commit";
|
||||
@@ -11,8 +10,7 @@ import { handleExplain } from "./src/commands/explain";
|
||||
import { handleReview } from "./src/commands/review";
|
||||
import { handleChangelog } from "./src/commands/changelog";
|
||||
import { handleSuggest } from "./src/commands/suggest";
|
||||
import { setColorEnabled } from "./src/terminal";
|
||||
import { BOLD, GREEN, CYAN, DIM, RESET } from "./src/terminal";
|
||||
import { setColorEnabled, BOLD, GREEN, CYAN, DIM, RESET, hideCursor, showCursor, clearLine, clearScreen, visibleLength, padRight } from "./src/terminal";
|
||||
import { isStdinTTY, initTTY } from "./src/tty";
|
||||
import { VERSION } from "./src/brand";
|
||||
import { SKIP_WAIT } from "./src/menu";
|
||||
@@ -37,11 +35,6 @@ const MENU_ITEMS: MenuItem[] = [
|
||||
{ key: "config", label: "Config", description: "Configure API settings", group: "Project" },
|
||||
];
|
||||
|
||||
function hideCursor() { process.stdout.write("\x1b[?25l"); }
|
||||
function showCursor() { process.stdout.write("\x1b[?25h"); }
|
||||
|
||||
function clearLine() { process.stdout.write("\r\x1b[2K"); }
|
||||
|
||||
async function readKey(): Promise<string> {
|
||||
return new Promise((resolve) => {
|
||||
const onData = (data: Buffer) => {
|
||||
@@ -52,14 +45,6 @@ async function readKey(): Promise<string> {
|
||||
});
|
||||
}
|
||||
|
||||
function visibleLen(s: string): number {
|
||||
return s.replace(/\x1b\[[0-9;]*m/g, "").length;
|
||||
}
|
||||
|
||||
function padRight(value: string, width: number): string {
|
||||
return value + " ".repeat(Math.max(0, width - visibleLen(value)));
|
||||
}
|
||||
|
||||
function renderMenu(cursor: number): number {
|
||||
process.stdout.write("\x1b[H"); // cursor home
|
||||
|
||||
@@ -84,7 +69,7 @@ function renderMenu(cursor: number): number {
|
||||
write("");
|
||||
|
||||
const keyWidth = 3;
|
||||
const labelWidth = Math.max(...MENU_ITEMS.map((m) => visibleLen(m.label))) + 2;
|
||||
const labelWidth = Math.max(...MENU_ITEMS.map((m) => visibleLength(m.label))) + 2;
|
||||
let currentGroup: MenuItem["group"] | null = null;
|
||||
|
||||
for (let i = 0; i < MENU_ITEMS.length; i++) {
|
||||
@@ -165,14 +150,14 @@ async function waitForEnter(): Promise<void> {
|
||||
// Resume stdin in case it was paused
|
||||
process.stdin.resume();
|
||||
});
|
||||
process.stdout.write("\x1b[2J\x1b[H"); // clear screen
|
||||
clearScreen();
|
||||
}
|
||||
|
||||
async function dispatchAndWait(item: MenuItem, wasRaw: boolean): Promise<number> {
|
||||
showCursor();
|
||||
process.stdin.setRawMode(wasRaw === true);
|
||||
process.stdin.pause();
|
||||
process.stdout.write("\x1b[2J\x1b[H"); // clear screen
|
||||
clearScreen();
|
||||
const result = await dispatchMenuAction(item.key);
|
||||
if (result === (SKIP_WAIT as unknown as number)) {
|
||||
return 0; // user explicitly backed out — skip "Press Enter" and return directly
|
||||
@@ -197,6 +182,13 @@ async function showMenu(): Promise<number> {
|
||||
// Initial render
|
||||
renderMenu(cursor);
|
||||
|
||||
const exitMenu = (exitCode: number): number => {
|
||||
showCursor();
|
||||
process.stdin.setRawMode(wasRaw === true);
|
||||
process.stdin.pause();
|
||||
return exitCode;
|
||||
};
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
const raw = await readKey();
|
||||
@@ -214,7 +206,7 @@ async function showMenu(): Promise<number> {
|
||||
// Enter
|
||||
if (raw === "\r" || raw === "\n") {
|
||||
const result = await dispatchAndWait(MENU_ITEMS[cursor]!, wasRaw);
|
||||
if (result !== 0) return result;
|
||||
if (result !== 0) return exitMenu(result);
|
||||
hideCursor();
|
||||
if (wasRaw !== true) process.stdin.setRawMode(true);
|
||||
process.stdin.resume();
|
||||
@@ -224,11 +216,8 @@ async function showMenu(): Promise<number> {
|
||||
|
||||
// Ctrl+C
|
||||
if (raw === "\x03") {
|
||||
showCursor();
|
||||
process.stdin.setRawMode(wasRaw === true);
|
||||
process.stdin.pause();
|
||||
process.stdout.write("\n");
|
||||
return 0;
|
||||
return exitMenu(0);
|
||||
}
|
||||
|
||||
// Number hotkeys (1-8)
|
||||
@@ -238,7 +227,7 @@ async function showMenu(): Promise<number> {
|
||||
cursor = idx;
|
||||
renderMenu(cursor);
|
||||
const result = await dispatchAndWait(MENU_ITEMS[idx]!, wasRaw);
|
||||
if (result !== 0) return result;
|
||||
if (result !== 0) return exitMenu(result);
|
||||
hideCursor();
|
||||
if (wasRaw !== true) process.stdin.setRawMode(true);
|
||||
process.stdin.resume();
|
||||
@@ -250,27 +239,18 @@ async function showMenu(): Promise<number> {
|
||||
// Letter hotkeys
|
||||
const lower = raw.toLowerCase();
|
||||
if (lower === "h") {
|
||||
showCursor();
|
||||
process.stdin.setRawMode(wasRaw === true);
|
||||
process.stdin.pause();
|
||||
process.stdout.write("\x1b[2J\x1b[H");
|
||||
clearScreen();
|
||||
console.log(formatHelp(commands));
|
||||
return 0;
|
||||
return exitMenu(0);
|
||||
}
|
||||
if (lower === "v") {
|
||||
showCursor();
|
||||
process.stdin.setRawMode(wasRaw === true);
|
||||
process.stdin.pause();
|
||||
process.stdout.write("\x1b[2J\x1b[H");
|
||||
clearScreen();
|
||||
console.log(`gai v${VERSION}`);
|
||||
return 0;
|
||||
return exitMenu(0);
|
||||
}
|
||||
if (lower === "q") {
|
||||
showCursor();
|
||||
process.stdin.setRawMode(wasRaw === true);
|
||||
process.stdin.pause();
|
||||
process.stdout.write("\n");
|
||||
return 0;
|
||||
return exitMenu(0);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@@ -428,10 +408,8 @@ const commands = registerCommands(
|
||||
},
|
||||
);
|
||||
|
||||
// Keep the defs accessible for help command
|
||||
const allCommandDefs = [...commands.values()].filter(
|
||||
(c, i, arr) => arr.findIndex((x) => x.name === c.name) === i,
|
||||
);
|
||||
// Keep canonical command defs accessible for help command (deduplicate by reference)
|
||||
const allCommandDefs = [...new Set(commands.values())];
|
||||
|
||||
// ── Main ───────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gai",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"description": "AI-powered git helper — commit messages, PRs, code review, changelogs, and more",
|
||||
"module": "index.ts",
|
||||
"type": "module",
|
||||
|
||||
Reference in New Issue
Block a user