How Streaming Output Works in Supported Terminals in the Summarize CLI
The Summarize CLI detects rich TTY capabilities to enable incremental markdown-to-ANSI rendering with live slide parsing, falling back to plain-text streaming with configurable line or delta modes in non-interactive environments.
The steipete/summarize repository provides a command-line interface that streams AI-generated summaries incrementally rather than waiting for complete responses. When running in a supported terminal, the tool leverages rich TTY features to render formatted markdown with colors and hyperlinks in real-time. Understanding how streaming output works in supported terminals helps developers customize the experience for both interactive and CI/CD environments.
Detecting Rich TTY Capabilities
The foundation of streaming behavior rests in src/run/terminal.ts, specifically the isRichTty function. This utility checks the isTTY property of the output stream to determine if the terminal supports interactive features like cursor positioning and color codes.
// src/run/terminal.ts
export function isRichTty(stream: NodeJS.WritableStream): boolean {
// Returns true only if the stream reports `isTTY`.
return Boolean((stream as unknown as { isTTY?: boolean }).isTTY);
}
All downstream streaming logic checks isRichTty(stdout) to decide whether to render markdown as ANSI or fall back to plain-text streaming. This boolean also determines whether the CLI displays inline images and OSC-8 hyperlinks.
Plain-Text Streaming for Non-TTY Environments
When running in pipes or CI environments where isRichTty returns false, or when the user specifies --plain, the CLI uses the stream-output gate implemented in src/run/stream-output.ts. The createStreamOutputGate function normalizes output through two distinct strategies:
- Line mode: Buffers content and flushes only when complete lines (ending in newlines) are received, preventing broken words in log files.
- Delta mode: Writes incremental fragments immediately as they arrive, creating a "live-typing" effect ideal for watching LLM generation in real-time.
// src/run/stream-output.ts
export function createStreamOutputGate({
stdout, clearProgressForStdout, restoreProgressAfterStdout, outputMode, richTty,
}) {
// – `handleChunk(streamed, prevStreamed)` is called for every new chunk.
// – In line mode it looks for the last newline and flushes up to that point.
// – In delta mode it writes any appended text if the new chunk is a strict
// continuation of the previous one, otherwise it rewrites the whole buffer.
}
The gate tracks previously flushed content via plainFlushedLen and coordinates with progress spinners through clearProgressForStdout and restoreProgressAfterStdout callbacks to prevent UI corruption during concurrent updates.
Markdown-to-ANSI Streaming in Rich Terminals
For rich TTY environments, the CLI creates a markdown streamer via createMarkdownStreamer in src/run/flows/url/slides-output.ts. This component converts raw markdown into terminal-ready ANSI sequences using the markdansi library.
// src/run/flows/url/slides-output.ts
const streamer = shouldRenderMarkdown
? createMarkdownStreamer({
render: (markdown) =>
renderMarkdownAnsi(
prepareMarkdownForTerminalStreaming(markdown),
{
width: markdownRenderWidth(stdout, env),
wrap: true,
color: supportsColor(stdout, envForRun),
hyperlinks: true,
},
),
spacing: "single",
})
: null;
The pipeline first calls prepareMarkdownForTerminalStreaming to trim whitespace and prevent "phantom blanks" when content reaches the exact terminal width. The renderMarkdownAnsi function then produces colored output respecting the terminal's actual width, color support level, and hyperlink capabilities.
Parsing Slides During Stream Execution
When processing slide-based content (activated via --slides), the createSlidesSummaryStreamHandler manages the stream by parsing special markers in the incoming text. The handler buffers incoming chunks and scans for slide references using slideTagRegex, slideLabelRegex, and fallback patterns to detect markers like [slide 1] or slide 1 – title.
Upon detecting a marker, the handler invokes renderSlide(index, title), which may:
- Display an inline image if
inlineEnabledand the terminal supports image protocols (e.g., iTerm2 inline images). - Write a formatted header line with timestamp links.
- Update progress counters via
onProgressTextcallbacks.
This architecture ensures that slide boundaries are rendered immediately while preserving the incremental nature of the underlying LLM stream.
Wiring the Pipeline Together
The orchestration happens in src/run/flows/url/summary.ts, where createSlidesTerminalOutput assembles the appropriate streaming components based on flags and terminal capabilities.
// src/run/flows/url/summary.ts (excerpt)
const slidesOutput = createSlidesTerminalOutput({
io,
flags,
extracted,
slides,
enabled: flags.slides,
outputMode: flags.outputMode, // "line" | "delta"
clearProgressForStdout,
restoreProgressAfterStdout,
onProgressText,
});
if (slidesOutput) {
daemon.on("slideChunk", slidesOutput.onSlideChunk);
daemon.on("slidesExtracted", slidesOutput.onSlidesExtracted);
daemon.on("slidesDone", slidesOutput.onSlidesDone);
await runSummaryEngine({
...,
streamHandler: slidesOutput.streamHandler,
});
}
The factory registers callbacks with the daemon to receive slideChunk, slidesExtracted, and slidesDone events, routing them through the streaming handler. If flags.plain is set or the terminal lacks TTY support, the factory automatically substitutes the plain-text output gate for the markdown streamer.
Summary
- Rich TTY detection via
isRichTtyinsrc/run/terminal.tsdetermines whether to use markdown-to-ANSI rendering or plain-text streaming. - Plain-text streaming employs
createStreamOutputGatefromsrc/run/stream-output.ts, supporting both "line" and "delta" output modes for different use cases. - Markdown streaming uses the
markdansilibrary throughcreateMarkdownStreamer, respecting terminal width, color support, and hyperlink capabilities. - Slide parsing handles
[slide …]markers via regex patterns in the stream handler, enabling inline image rendering and progress updates. - Pipeline integration in
src/run/flows/url/summary.tswires daemon events to the appropriate stream handler based on runtime flags and terminal detection.
Frequently Asked Questions
How does Summarize detect if my terminal supports streaming markdown?
The CLI calls isRichTty(stdout) from src/run/terminal.ts, which checks the isTTY property of the output stream. When this returns true, the terminal supports rich features and the CLI enables markdown-to-ANSI conversion; otherwise, it falls back to plain-text streaming through the output gate.
What is the difference between line mode and delta mode?
Line mode buffers output until complete newline-terminated lines are received, making it ideal for log files and CI pipelines where atomic lines prevent fragmented output. Delta mode writes incremental fragments immediately as they arrive from the LLM, creating a "live-typing" effect. Both modes are implemented in createStreamOutputGate within src/run/stream-output.ts.
Can I disable ANSI formatting even in a supported terminal?
Yes. Passing the --plain flag forces the CLI to bypass markdown rendering and use the plain-text output gate, effectively disabling colors, hyperlinks, and inline images regardless of whether isRichTty returns true.
How are slide markers parsed during streaming?
The createSlidesSummaryStreamHandler in src/run/flows/url/slides-output.ts uses regular expressions (slideTagRegex, slideLabelRegex) to detect markers like [slide 1] in the incoming character stream. When detected, the handler immediately renders the slide header and triggers inline image display if the terminal protocol supports it.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →