/** * Operation tracing system. * * Provides step-by-step tracing for all operations across CLI, MCP, and SDK. * - **CLI mode**: Logs to stderr with chalk colors (no spinners in trace) * - **MCP mode**: Collects steps silently, returns in response * - **SDK mode**: Fires callback if provided, otherwise collects * * @module */ /** * A single trace step. */ export interface ITraceStep { /** Timestamp */ ts: string; /** Step label */ step: string; /** Duration in ms (filled after completion) */ durationMs?: number; /** Optional detail */ detail?: string; } /** * Complete operation trace. */ export interface IOperationTrace { /** Operation name */ operation: string; /** All steps */ steps: ITraceStep[]; /** Total duration */ totalMs: number; /** Whether operation succeeded */ success: boolean; /** Error message if failed */ error?: string; } /** * Callback for real-time trace updates. */ export type TraceCallback = (step: string, detail?: string) => void; /** Detect if running as MCP server (stdout is the transport). */ export function isMcpMode(): boolean { // MCP servers communicate over stdout — if we're piped, assume MCP return !process.stdout.isTTY; } /** * Operation tracer that collects steps. */ export class OperationTracer { private steps: ITraceStep[] = []; private startTime: number; private stepStart: number; private onStep?: TraceCallback; constructor( public readonly operation: string, onStep?: TraceCallback, ) { this.startTime = Date.now(); this.stepStart = this.startTime; this.onStep = onStep; } /** * Records a trace step. * * @param step - Step description * @param detail - Optional detail */ step(step: string, detail?: string): void { const now = Date.now(); const traceStep: ITraceStep = { ts: new Date(now).toISOString(), step, durationMs: now - this.stepStart, detail, }; this.steps.push(traceStep); this.stepStart = now; // Fire callback for real-time updates this.onStep?.(step, detail); // In TTY (CLI) mode, log to stderr so it doesn't pollute MCP stdout if (process.stderr.isTTY) { const prefix = `\x1b[90m[${this.operation}]\x1b[0m`; const elapsed = `\x1b[33m${traceStep.durationMs}ms\x1b[0m`; const detailStr = detail ? ` \x1b[90m(${detail})\x1b[0m` : ""; process.stderr.write(`${prefix} ${step} ${elapsed}${detailStr}\n`); } } /** * Finalizes the trace. * * @param success - Whether operation succeeded * @param error - Error message if failed * @returns Complete operation trace */ finish(success: boolean, error?: string): IOperationTrace { return { operation: this.operation, steps: this.steps, totalMs: Date.now() - this.startTime, success, error, }; } }