feat(cli): add gt art command with rotating GT logo animation#1079
feat(cli): add gt art command with rotating GT logo animation#1079moss-bryophyta wants to merge 9 commits intogeneraltranslation:mainfrom
gt art command with rotating GT logo animation#1079Conversation
packages/cli/src/cli/commands/art.ts
Outdated
| // Enable raw mode to capture keystrokes | ||
| if (process.stdin.isTTY) { | ||
| process.stdin.setRawMode(true); | ||
| } | ||
| process.stdin.resume(); | ||
| process.stdin.setEncoding('utf8'); |
There was a problem hiding this comment.
Infinite loop in non-TTY environments
When the command is run in a non-TTY context (piped output, CI, etc.), setRawMode is skipped so no keyboard input can ever arrive, yet process.stdin.resume() is still called and the while (running) loop runs forever with no way to break out. The function will never return.
Consider adding an early exit guard for non-TTY environments:
| // Enable raw mode to capture keystrokes | |
| if (process.stdin.isTTY) { | |
| process.stdin.setRawMode(true); | |
| } | |
| process.stdin.resume(); | |
| process.stdin.setEncoding('utf8'); | |
| // Enable raw mode to capture keystrokes | |
| if (!process.stdin.isTTY) { | |
| console.log(chalk.yellow(' gt art requires an interactive terminal (TTY).')); | |
| return; | |
| } | |
| process.stdin.setRawMode(true); | |
| process.stdin.resume(); | |
| process.stdin.setEncoding('utf8'); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/cli/src/cli/commands/art.ts
Line: 144-149
Comment:
**Infinite loop in non-TTY environments**
When the command is run in a non-TTY context (piped output, CI, etc.), `setRawMode` is skipped so no keyboard input can ever arrive, yet `process.stdin.resume()` is still called and the `while (running)` loop runs forever with no way to break out. The function will never return.
Consider adding an early exit guard for non-TTY environments:
```suggestion
// Enable raw mode to capture keystrokes
if (!process.stdin.isTTY) {
console.log(chalk.yellow(' gt art requires an interactive terminal (TTY).'));
return;
}
process.stdin.setRawMode(true);
process.stdin.resume();
process.stdin.setEncoding('utf8');
```
How can I resolve this? If you propose a fix, please make it concise.
packages/cli/src/cli/commands/art.ts
Outdated
| const cleanup = () => { | ||
| // Show cursor | ||
| process.stdout.write('\x1B[?25h'); | ||
| if (process.stdin.isTTY) { | ||
| process.stdin.setRawMode(false); | ||
| } | ||
| process.stdin.removeListener('data', onKey); | ||
| process.stdin.pause(); | ||
| console.log(); | ||
| }; | ||
|
|
||
| const FRAME_HEIGHT = 16; // lines per frame (including blank lines) | ||
|
|
||
| while (running) { | ||
| const color = COLORS[colorIndex % COLORS.length]; | ||
| const frame = frames[frameIndex % frames.length]; | ||
|
|
||
| // Move cursor up to overwrite previous frame (except first frame) | ||
| if (frameIndex > 0) { | ||
| process.stdout.write(`\x1B[${FRAME_HEIGHT}A`); | ||
| } | ||
|
|
||
| // Draw frame | ||
| const lines = frame.split('\n'); | ||
| for (const line of lines) { | ||
| process.stdout.write(`\x1B[2K${color(line)}\n`); | ||
| } | ||
| // Pad remaining lines | ||
| for (let i = lines.length; i < FRAME_HEIGHT; i++) { | ||
| process.stdout.write(`\x1B[2K\n`); | ||
| } | ||
|
|
||
| frameIndex++; | ||
| if (frameIndex % frames.length === 0) { | ||
| colorIndex++; | ||
| } | ||
|
|
||
| // ~100ms per frame = ~10 FPS | ||
| await new Promise((resolve) => setTimeout(resolve, 100)); | ||
| } | ||
|
|
||
| cleanup(); | ||
| console.log(chalk.green(' ✨ General Translation')); | ||
| } |
There was a problem hiding this comment.
Terminal left in broken state on unexpected exit
If the process is terminated externally (SIGTERM, or SIGINT sent from another process via kill -2) while the animation is running, cleanup() is never called. This leaves the terminal cursor permanently hidden (\x1B[?25l was written but \x1B[?25h is never sent) and stdin stuck in raw mode — a very disruptive experience for users.
Additionally, if an exception is thrown inside the while loop (e.g. broken pipe), the same problem occurs since there is no try/finally.
Consider registering signal handlers and using try/finally:
const sigHandler = () => { cleanup(); process.exit(); };
process.once('SIGTERM', sigHandler);
process.once('SIGINT', sigHandler);
try {
while (running) {
// ... animation loop ...
}
} finally {
cleanup();
// Remove signal handlers so they don't fire again
process.off('SIGTERM', sigHandler);
process.off('SIGINT', sigHandler);
}Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/cli/src/cli/commands/art.ts
Line: 167-210
Comment:
**Terminal left in broken state on unexpected exit**
If the process is terminated externally (SIGTERM, or SIGINT sent from another process via `kill -2`) while the animation is running, `cleanup()` is never called. This leaves the terminal cursor permanently hidden (`\x1B[?25l` was written but `\x1B[?25h` is never sent) and stdin stuck in raw mode — a very disruptive experience for users.
Additionally, if an exception is thrown inside the `while` loop (e.g. broken pipe), the same problem occurs since there is no `try/finally`.
Consider registering signal handlers and using `try/finally`:
```typescript
const sigHandler = () => { cleanup(); process.exit(); };
process.once('SIGTERM', sigHandler);
process.once('SIGINT', sigHandler);
try {
while (running) {
// ... animation loop ...
}
} finally {
cleanup();
// Remove signal handlers so they don't fire again
process.off('SIGTERM', sigHandler);
process.off('SIGINT', sigHandler);
}
```
How can I resolve this? If you propose a fix, please make it concise.d984142 to
b5c3069
Compare
Displays a rotating ASCII art animation of the GT logo in the terminal. Colors cycle through cyan, blue, magenta, red, yellow, and green. Press q or Escape to exit.
b5c3069 to
37d2b6e
Compare
- Replace hand-drawn ASCII frames with figlet slant font output - Generate rotation frames programmatically via cos(angle) horizontal scaling - Add non-TTY early exit guard (prevents infinite loop in CI/pipes) - Add SIGTERM/SIGINT handlers + try/finally for terminal cleanup - Addresses Greptile review feedback
|
Addressed both Greptile review items in 5b7ab97:
Also rewrote the whole command: replaced hand-drawn ASCII frames with figlet |
Replace figlet text with a block-character GT monogram that matches the General Translation brand mark. Three concentric strokes on the G, shared top bar, and parallel T stems — just like the real logo.
|
Updated the logo to use the GT monogram (matching the brand mark) instead of the figlet text. Three concentric strokes on the G, shared top bar, and parallel T stems — just like the real logo. The rotation/color-cycling animation works the same way, just with the new art. ASCII-safe characters ( |
Generated from assets/no-bg-gt-logo-light.png using: jp2a gt-logo.jpg --width=50 --chars='@#%+=- ' Much more accurate representation of the GT monogram than the hand-drawn version.
When running `gt init` interactively (no --src or --config flags), plays a brief 2-rotation animation of the GT logo before the setup wizard begins. Falls back to the static logo in non-TTY environments or when config flags are provided. Also exports playIntroAnimation() from art.ts for reuse.
- console/logo.ts: logo data + frame generation utilities - console/animation.ts: interactive and intro animation playback - cli/commands/art.ts: thin command handler Follows existing convention of putting display/UI code in console/.
What
Adds a
gt artcommand that displays a rotating ASCII art animation of the GT logo in the terminal.Details
BaseCLIso it's available regardless of framework detectionDemo
Requested by @ErnestM1234
Greptile Summary
This PR adds a
gt arteaster-egg command that plays a looping ASCII animation of a rotating GT monogram in the terminal, with cycling colors. The integration intoBaseCLIis clean and minimal. However,art.tshas two logic issues that can leave the user's terminal in a broken state (hidden cursor, stuck raw mode) and can cause an infinite hang in non-interactive environments.Key changes:
packages/cli/src/cli/commands/art.ts— New file: 7-frame ASCII art, rotation sequence builder, color cycling, stdin raw-mode handling, and ahandleArt()async function that drives the animation loop.packages/cli/src/cli/base.ts— Addsimport { handleArt }and callsthis.setupArtCommand()in the constructor, exposinggt artin the CLI.Issues found:
art.tsline ~144): When the TTY check fails andsetRawModeis skipped, thewhile (running)loop still runs indefinitely because no keyboard data can ever arrive to setrunning = false. A TTY guard with an early return is needed.art.tsline ~167): External SIGTERM/SIGINT and uncaught exceptions inside the animation loop skipcleanup(), leaving the terminal with a permanently hidden cursor and stdin in raw mode. Signal handlers (process.once('SIGTERM'),process.once('SIGINT')) and atry/finallyblock are needed to guarantee cleanup.Confidence Score: 3/5
Important Files Changed
artcommand viasetupArtCommand()called in the constructor. Minimal, clean integration — no issues found.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A([gt art]) --> B{stdin.isTTY?} B -- Yes --> C[setRawMode true\nresume + setEncoding] B -- No --> C2[resume + setEncoding\n⚠️ no exit path] C --> D[Hide cursor\nPrint instructions] C2 --> D D --> E{running?} E -- Yes --> F[Render frame\nwait 100ms] F --> G{key pressed?} G -- q/Q/Esc/Ctrl+C --> H[running = false] G -- other --> E H --> E E -- No --> I[cleanup\nShow cursor\nReset raw mode\nPause stdin] I --> J([Exit]) K([SIGTERM / external SIGINT]) -.->|⚠️ cleanup never called| L([Broken terminal state]) M([Exception in loop]) -.->|⚠️ no try/finally| LLast reviewed commit: d984142