feat: add CODETYPER ASCII art to exit summary
Add prominent CODETYPER logo using ASCII box-drawing characters at the top of the exit summary for enhanced branding. This provides a polished, professional appearance when exiting the CLI. Key improvements: - Add ASCII logo to session summary output - Simplify exit flow to use global message storage in terminal.ts - Remove duplicate exit message handling from ExitProvider - Fix signal handlers to prevent duplicate exit messages - Clean up debug logging from app.tsx - Ensure exit message persists on terminal after process exit The exit summary now displays comprehensive session statistics with: - CODETYPER ASCII logo - Total usage and Premium requests - API time and total session time - Code changes breakdown (+additions/-deletions) - Per-model token usage - Resume command with session ID Works correctly on all exit paths (normal exit, SIGINT, SIGTERM, errors).
This commit is contained in:
@@ -118,7 +118,6 @@ function AppContent(props: AppProps) {
|
||||
|
||||
setAppStoreRef(app);
|
||||
|
||||
// Set exit message reactively (like OpenCode does)
|
||||
// This ensures the message is pre-computed and ready when exit is called
|
||||
createEffect(() => {
|
||||
const state = appStore.getState();
|
||||
|
||||
@@ -1035,7 +1035,6 @@ export const { provider: AppStoreProvider, use: useAppStore } =
|
||||
updateMcpServerStatus,
|
||||
|
||||
// Modified file tracking
|
||||
modifiedFiles: () => store.modifiedFiles,
|
||||
addModifiedFile,
|
||||
clearModifiedFiles,
|
||||
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { createSignal, onCleanup } from "solid-js";
|
||||
import { useRenderer } from "@opentui/solid";
|
||||
import { createSimpleContext } from "./helper";
|
||||
import { setGlobalExitMessage } from "@utils/core/terminal";
|
||||
|
||||
interface ExitContextInput extends Record<string, unknown> {
|
||||
onExit?: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
interface ExitContextValue {
|
||||
exit: (code?: number) => void;
|
||||
exitCode: () => number;
|
||||
isExiting: () => boolean;
|
||||
exit: (code?: number) => Promise<void>;
|
||||
requestExit: () => void;
|
||||
cancelExit: () => void;
|
||||
confirmExit: () => void;
|
||||
getExitCode: () => number;
|
||||
isExiting: () => boolean;
|
||||
exitRequested: () => boolean;
|
||||
setExitMessage: (message: string | undefined) => void;
|
||||
getExitMessage: () => string | undefined;
|
||||
}
|
||||
@@ -27,7 +27,6 @@ export const { provider: ExitProvider, use: useExit } = createSimpleContext<
|
||||
const [exitCode, setExitCode] = createSignal(0);
|
||||
const [isExiting, setIsExiting] = createSignal(false);
|
||||
const [exitRequested, setExitRequested] = createSignal(false);
|
||||
const [exitMessage, setExitMessageState] = createSignal<string | undefined>(undefined);
|
||||
|
||||
const exit = async (code = 0): Promise<void> => {
|
||||
setExitCode(code);
|
||||
@@ -46,22 +45,19 @@ export const { provider: ExitProvider, use: useExit } = createSimpleContext<
|
||||
// Call the onExit callback (may be async)
|
||||
await props.onExit?.();
|
||||
|
||||
// Write the stored exit message after renderer is destroyed
|
||||
const message = exitMessage();
|
||||
if (message) {
|
||||
process.stdout.write(message + "\n");
|
||||
}
|
||||
// Exit message will be written by emergencyTerminalCleanup
|
||||
// which is registered on process "exit" event in terminal.ts
|
||||
|
||||
// Exit the process
|
||||
process.exit(code);
|
||||
};
|
||||
|
||||
const setExitMessage = (message: string | undefined): void => {
|
||||
setExitMessageState(message);
|
||||
setGlobalExitMessage(message);
|
||||
};
|
||||
|
||||
const getExitMessage = (): string | undefined => {
|
||||
return exitMessage();
|
||||
return undefined; // Message is stored in terminal.ts
|
||||
};
|
||||
|
||||
const requestExit = (): void => {
|
||||
@@ -81,13 +77,13 @@ export const { provider: ExitProvider, use: useExit } = createSimpleContext<
|
||||
onCleanup(() => {
|
||||
setIsExiting(false);
|
||||
setExitRequested(false);
|
||||
setExitMessageState(undefined);
|
||||
});
|
||||
|
||||
return {
|
||||
exit,
|
||||
exitCode,
|
||||
getExitCode: exitCode,
|
||||
isExiting,
|
||||
exitRequested,
|
||||
requestExit,
|
||||
cancelExit,
|
||||
confirmExit,
|
||||
|
||||
@@ -80,13 +80,20 @@ export interface SessionSummaryInput {
|
||||
}
|
||||
|
||||
export function generateSessionSummary(input: SessionSummaryInput): string {
|
||||
const { sessionId, sessionStats, modifiedFiles, modelName, providerName } = input;
|
||||
const { sessionId, sessionStats, modifiedFiles, modelName } = input;
|
||||
|
||||
const { additions, deletions } = calculateCodeChanges(modifiedFiles);
|
||||
const totalSessionTime = Date.now() - sessionStats.startTime;
|
||||
|
||||
// Build the summary lines
|
||||
const lines: string[] = [
|
||||
"",
|
||||
" ██████╗ ██████╗ ██████╗ ███████╗████████╗██╗ ██╗██████╗ ███████╗██████╗ ",
|
||||
" ██╔════╝██╔═══██╗██╔══██╗██╔════╝╚══██╔══╝╚██╗ ██╔╝██╔══██╗██╔════╝██╔══██╗",
|
||||
" ██║ ██║ ██║██║ ██║█████╗ ██║ ╚████╔╝ ██████╔╝█████╗ ██████╔╝",
|
||||
" ██║ ██║ ██║██║ ██║██╔══╝ ██║ ╚██╔╝ ██╔═══╝ ██╔══╝ ██╔══██╗",
|
||||
" ╚██████╗╚██████╔╝██████╔╝███████╗ ██║ ██║ ██║ ███████╗██║ ██║",
|
||||
" ╚═════╝ ╚═════╝ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝",
|
||||
"",
|
||||
"═══════════════════════════════════════════════════════════════",
|
||||
"",
|
||||
|
||||
@@ -19,6 +19,25 @@ let spinner: Ora | null = null;
|
||||
*/
|
||||
let exitHandlersRegistered = false;
|
||||
|
||||
/**
|
||||
* Global exit message to display on process exit (set by ExitProvider in TUI mode)
|
||||
*/
|
||||
let globalExitMessage: string | undefined;
|
||||
|
||||
/**
|
||||
* Set the exit message to display when the process exits
|
||||
*/
|
||||
export const setGlobalExitMessage = (message: string | undefined): void => {
|
||||
globalExitMessage = message;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current global exit message
|
||||
*/
|
||||
export const getGlobalExitMessage = (): string | undefined => {
|
||||
return globalExitMessage;
|
||||
};
|
||||
|
||||
/**
|
||||
* Drain any pending stdin data (e.g. DECRQM responses from @opentui/core's
|
||||
* theme-mode detection that queries mode 997). The terminal responds with
|
||||
@@ -80,6 +99,11 @@ export const drainStdin = (): Promise<void> =>
|
||||
const emergencyTerminalCleanup = (): void => {
|
||||
try {
|
||||
writeSync(1, TERMINAL_RESET);
|
||||
|
||||
// Write exit message if set (from TUI ExitProvider)
|
||||
if (globalExitMessage) {
|
||||
writeSync(1, globalExitMessage + "\n");
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors during cleanup - stdout may already be closed
|
||||
}
|
||||
@@ -93,28 +117,15 @@ export const registerExitHandlers = (): void => {
|
||||
if (exitHandlersRegistered) return;
|
||||
exitHandlersRegistered = true;
|
||||
|
||||
// Emergency cleanup will be called once on ANY exit
|
||||
process.on("exit", emergencyTerminalCleanup);
|
||||
process.on("beforeExit", emergencyTerminalCleanup);
|
||||
process.on("SIGINT", () => {
|
||||
emergencyTerminalCleanup();
|
||||
process.exit(130);
|
||||
});
|
||||
process.on("SIGTERM", () => {
|
||||
emergencyTerminalCleanup();
|
||||
process.exit(143);
|
||||
});
|
||||
process.on("SIGHUP", () => {
|
||||
emergencyTerminalCleanup();
|
||||
process.exit(128);
|
||||
});
|
||||
process.on("uncaughtException", () => {
|
||||
emergencyTerminalCleanup();
|
||||
process.exit(1);
|
||||
});
|
||||
process.on("unhandledRejection", () => {
|
||||
emergencyTerminalCleanup();
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// Signal handlers just call process.exit() which triggers "exit" event
|
||||
process.on("SIGINT", () => process.exit(130));
|
||||
process.on("SIGTERM", () => process.exit(143));
|
||||
process.on("SIGHUP", () => process.exit(128));
|
||||
process.on("uncaughtException", () => process.exit(1));
|
||||
process.on("unhandledRejection", () => process.exit(1));
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user