Skip to content

Commit 145a6eb

Browse files
Use enhanced UI as the default for CLI (#2014)
- users can switch back to classic mode using `--classicUI` flag at launch - Update the welcome header
1 parent f21be33 commit 145a6eb

2 files changed

Lines changed: 126 additions & 23 deletions

File tree

ts/packages/cli/src/commands/interactive.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ export default class Interactive extends Command {
132132
default: true,
133133
allowNo: true,
134134
}),
135-
testUI: Flags.boolean({
135+
classicUI: Flags.boolean({
136136
description:
137-
"Enable enhanced terminal UI with spinners and visual prompts",
137+
"Use classic terminal UI instead of enhanced UI with spinners and visual prompts",
138138
default: false,
139139
}),
140140
verbose: Flags.string({
@@ -169,29 +169,31 @@ export default class Interactive extends Command {
169169
enableVerboseFromFlag(namespaces);
170170
}
171171

172+
const enhancedUI = !flags.classicUI;
173+
172174
// Install debug interceptor for enhanced UI so all stderr debug
173175
// output (whether from /verbose, --verbose, or DEBUG env var)
174176
// renders in the indented panel.
175-
if (flags.testUI) {
177+
if (enhancedUI) {
176178
const { installDebugInterceptor } = await import(
177179
"../debugInterceptor.js"
178180
);
179181
installDebugInterceptor();
180182
}
181183

182184
// Choose between standard and enhanced UI
183-
const withClientIO = flags.testUI
185+
const withClientIO = enhancedUI
184186
? withEnhancedConsoleClientIO
185187
: withConsoleClientIO;
186-
const processCommandsFn = flags.testUI
188+
const processCommandsFn = enhancedUI
187189
? processCommandsEnhanced
188190
: processCommands;
189-
const getPromptFn = flags.testUI
191+
const getPromptFn = enhancedUI
190192
? getEnhancedConsolePrompt
191193
: getConsolePrompt;
192194

193195
// Only create readline for standard console - enhanced console creates its own
194-
const rl = flags.testUI
196+
const rl = enhancedUI
195197
? undefined
196198
: createInterface({
197199
input: process.stdin,
@@ -242,10 +244,10 @@ export default class Interactive extends Command {
242244
dispatcher.processCommand(command),
243245
dispatcher,
244246
undefined, // inputs
245-
flags.testUI
247+
enhancedUI
246248
? (line: string) => getCompletionsData(line, dispatcher)
247249
: undefined,
248-
flags.testUI ? dispatcher : undefined,
250+
enhancedUI ? dispatcher : undefined,
249251
);
250252
} finally {
251253
await dispatcher.close();

ts/packages/cli/src/enhancedConsole.ts

Lines changed: 115 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ setSpinnerAccessor(() => currentSpinner);
5555
// Pending choice promise — main loop awaits this before showing next prompt
5656
let pendingChoicePromise: Promise<void> | null = null;
5757

58+
// Active custom prompt renderer (set by questionWithCompletion)
59+
let activePromptRenderer: PromptRenderer | null = null;
60+
5861
// Track the active request for cancellation support
5962
let currentRequestId: string | undefined;
6063
let isProcessing = false;
@@ -290,6 +293,28 @@ export function createEnhancedClientIO(
290293
currentSpinner.flushStream();
291294
currentSpinner.writeAbove(displayText);
292295
}
296+
} else if (activePromptRenderer) {
297+
// Custom prompt (questionWithCompletion) is active.
298+
// Clear the prompt rows, write content above, then re-render.
299+
const rows = activePromptRenderer.rows();
300+
for (let i = 0; i < rows; i++) {
301+
process.stdout.write("\x1b[1A\x1b[2K");
302+
}
303+
if (appendMode !== "inline") {
304+
if (lastAppendMode === "inline") {
305+
process.stdout.write("\n");
306+
}
307+
process.stdout.write(displayText);
308+
process.stdout.write("\n");
309+
} else {
310+
process.stdout.write(displayText);
311+
}
312+
// Also re-render any collapsed debug panel summary
313+
const dp = getDebugPanel();
314+
if (dp && dp.lineCount > 0) {
315+
dp.renderStaticSummary();
316+
}
317+
activePromptRenderer.redraw();
293318
} else if (rl) {
294319
// Readline is active - write above the prompt
295320
// Clear current line, write content, then let readline redraw prompt
@@ -979,14 +1004,22 @@ async function questionWithCompletion(
9791004
// Initial render
9801005
render();
9811006

982-
// Register prompt renderer so debug panel can render above the input
983-
const PROMPT_ROWS = 3; // input line + bottom rule + hint
1007+
// Register prompt renderer so debug panel and displayContent
1008+
// can render above the input.
1009+
// PROMPT_ROWS = separator + input line + bottom rule + hint
1010+
const PROMPT_ROWS = 4;
9841011
const panel = getDebugPanel();
1012+
const renderWithSeparator = () => {
1013+
const w = process.stdout.columns || 80;
1014+
stdout.write(ANSI.dim + "─".repeat(w) + ANSI.reset + "\n");
1015+
render();
1016+
};
9851017
const promptRenderer: PromptRenderer = {
9861018
rows: () => PROMPT_ROWS,
987-
redraw: () => render(),
1019+
redraw: () => renderWithSeparator(),
9881020
};
9891021
panel?.setPromptRenderer(promptRenderer);
1022+
activePromptRenderer = promptRenderer;
9901023

9911024
// Handle keypresses
9921025
const onData = async (chunk: Buffer) => {
@@ -1075,12 +1108,12 @@ async function questionWithCompletion(
10751108
// Ctrl+D — dump debug buffer above the prompt
10761109
const dp = getDebugPanel();
10771110
if (dp && dp.lineCount > 0) {
1078-
// Clear prompt lines, dump buffer, re-render prompt
1111+
// Clear prompt lines (including separator), dump buffer, re-render
10791112
for (let i = 0; i < PROMPT_ROWS; i++) {
10801113
stdout.write("\x1b[1A\x1b[2K");
10811114
}
10821115
dp.dumpBuffer();
1083-
render();
1116+
renderWithSeparator();
10841117
}
10851118
return;
10861119
} else if (code === 13) {
@@ -1186,6 +1219,7 @@ async function questionWithCompletion(
11861219

11871220
const cleanup = () => {
11881221
panel?.setPromptRenderer(null);
1222+
activePromptRenderer = null;
11891223
stdin.removeListener("data", onData);
11901224
if (stdin.isTTY) {
11911225
stdin.setRawMode(wasRaw || false);
@@ -1254,6 +1288,80 @@ function initializeEnhancedConsole(
12541288

12551289
let usingEnhancedConsole = false;
12561290

1291+
// ── Startup Banner ──────────────────────────────────────────────────────
1292+
1293+
// Logo mark — hexagonal badge with floating "T" (4 lines tall, 12 chars wide)
1294+
const LOGO_LINES = [
1295+
" ╱▔▔▔▔▔╲ ",
1296+
" ╱ ▀▀█▀▀ ╲ ",
1297+
" ╲ █ ╱ ",
1298+
" ╲▁▁▁▁▁╱ ",
1299+
];
1300+
const LOGO_WIDTH = 12;
1301+
1302+
function renderStartupBanner(): void {
1303+
const width = process.stdout.columns || 80;
1304+
const innerWidth = width - 4; // "│ " + " │"
1305+
const version = "0.0.1";
1306+
1307+
// Content lines to render alongside the logo
1308+
const contentLines = [
1309+
chalk.cyan.bold("TypeAgent") + chalk.dim(` v${version}`),
1310+
chalk.dim("Your personal AI assistant"),
1311+
"",
1312+
chalk.dim("Type a request or use /help to see commands."),
1313+
];
1314+
1315+
const hintLine =
1316+
" " +
1317+
chalk.dim(
1318+
"/help commands · /verbose debug · ctrl+d debug · ctrl+c exit",
1319+
);
1320+
1321+
// Build the box
1322+
const top = chalk.dim("╭" + "─".repeat(width - 2) + "╮");
1323+
const bottom = chalk.dim("╰" + "─".repeat(width - 2) + "╯");
1324+
1325+
const lines: string[] = [];
1326+
lines.push(top);
1327+
1328+
// Render logo + content side by side
1329+
const totalRows = Math.max(LOGO_LINES.length, contentLines.length);
1330+
for (let i = 0; i < totalRows; i++) {
1331+
const logo =
1332+
i < LOGO_LINES.length ? LOGO_LINES[i] : " ".repeat(LOGO_WIDTH);
1333+
const coloredLogo = chalk.cyan(logo);
1334+
const content = i < contentLines.length ? contentLines[i] : "";
1335+
const contentVisible = content.replace(
1336+
// eslint-disable-next-line no-control-regex
1337+
/\x1b\[[0-9;]*m/g,
1338+
"",
1339+
);
1340+
const padding = Math.max(
1341+
0,
1342+
innerWidth - LOGO_WIDTH - contentVisible.length,
1343+
);
1344+
lines.push(
1345+
chalk.dim("│") +
1346+
" " +
1347+
coloredLogo +
1348+
content +
1349+
" ".repeat(padding) +
1350+
chalk.dim("│"),
1351+
);
1352+
}
1353+
1354+
// Empty line before close
1355+
lines.push(chalk.dim("│") + " ".repeat(width - 2) + chalk.dim("│"));
1356+
lines.push(bottom);
1357+
lines.push(hintLine);
1358+
lines.push("");
1359+
1360+
for (const line of lines) {
1361+
process.stdout.write(line + "\n");
1362+
}
1363+
}
1364+
12571365
/**
12581366
* Wrapper for using enhanced console ClientIO
12591367
*/
@@ -1273,15 +1381,8 @@ export async function withEnhancedConsoleClientIO(
12731381
const dispatcherRef: { current?: Dispatcher } = {};
12741382
initializeEnhancedConsole(rl, dispatcherRef);
12751383

1276-
// Show welcome header
1277-
const width = process.stdout.columns || 80;
1278-
console.log(ANSI.dim + "═".repeat(width) + ANSI.reset);
1279-
console.log(
1280-
chalk.bold(" TypeAgent Interactive Mode ") +
1281-
chalk.dim("(Enhanced UI)"),
1282-
);
1283-
console.log(ANSI.dim + "═".repeat(width) + ANSI.reset);
1284-
console.log("");
1384+
// Show welcome banner
1385+
renderStartupBanner();
12851386
await callback(
12861387
createEnhancedClientIO(rl, dispatcherRef),
12871388
(d: Dispatcher) => {

0 commit comments

Comments
 (0)