import { ActivityEvent, ProgressHandle, ProgressOptions, SpinnerHandle, SpinnerOptions, TableColumn, TableOptions } from "../schema/activity.mjs"; import { WriteFn, writeLine } from "./writer.mjs"; import { CaptureProgressHandle, CaptureSpinnerHandle, StaticProgressHandle, StaticSpinnerHandle, TTYProgressHandle, TTYSpinnerHandle, noopProgressHandle, noopSpinnerHandle } from "./activity.mjs"; import { OutputPolicy, Verbosity } from "./contracts.mjs"; import { Out } from "../schema/command.mjs"; import { Colors } from "ansispeck"; //#region src/core/output/index.d.ts /** * Configuration for creating an output channel. * * Every field is optional — sensible defaults are applied when omitted. */ interface OutputOptions { /** * Writer for stdout-bound messages (`log`, `info`, `json`). * Defaults to `process.stdout.write` when running in Node/Bun. */ readonly stdout?: WriteFn; /** * Writer for stderr-bound messages (`warn`, `error`). * In JSON mode, `log` and `info` are also redirected here so that * stdout contains only structured JSON output. * Defaults to `process.stderr.write` when running in Node/Bun. */ readonly stderr?: WriteFn; /** * Whether stdout is connected to a TTY. * When `false`, output may omit ANSI codes and decorations. * Defaults to `false` (safe default — non-TTY until proven otherwise). */ readonly isTTY?: boolean; /** * Verbosity level. * - `'normal'` (default) — emit all messages * - `'quiet'` — suppress `info` messages */ readonly verbosity?: Verbosity; /** * Enable JSON output mode. * * When `true`, `log` and `info` messages are redirected to stderr * so that stdout is reserved exclusively for structured `json()` output. * `warn` and `error` continue to write to stderr as normal. * * @defaultValue `false` */ readonly jsonMode?: boolean; /** * Explicitly enable or disable ANSI colors on `out.color`. * * When set, this wins over the auto-gate — use it to force colors in * captured test output or when embedding into a host that manages its * own color policy. * * @defaultValue auto — enabled when `isTTY` is `true`, `jsonMode` is * `false`, and the environment supports color (`NO_COLOR`, * `FORCE_COLOR`, `--no-color`, `--color`, `CI` are respected). */ readonly color?: boolean; /** * Resolved OSC 8 hyperlink support for `out.isHyperlinkSupported`. * * When set, this wins over the `isTTY` fallback. Callers pass the * `NO_HYPERLINKS`/`FORCE_HYPERLINKS` (and `--no-hyperlinks`/`--hyperlinks`) * decision here via {@linkcode resolveHyperlinkOverride}, keeping `process` * access out of the output layer. * * @defaultValue `isTTY` */ readonly hyperlinks?: boolean; } /** Fully resolved output options with no optional fields. */ interface ResolvedOutputOptions { readonly stdout: WriteFn; readonly stderr: WriteFn; readonly isTTY: boolean; readonly verbosity: Verbosity; readonly jsonMode: boolean; readonly color: boolean; readonly isHyperlinkSupported: boolean; } /** Store the exit code requested through an output channel. @internal */ declare function setRequestedExitCode(out: Out, code: number): void; /** Read the exit code requested through an output channel. @internal */ declare function getRequestedExitCode(out: Out): number | undefined; /** Clear a previously requested exit code from an output channel. @internal */ declare function clearRequestedExitCode(out: Out): void; /** * Forced hyperlink decision from `NO_HYPERLINKS`/`FORCE_HYPERLINKS` and the * `--no-hyperlinks`/`--hyperlinks` argv flags; `undefined` when unset. * * Pure over its inputs — callers pass the adapter's `env`/`argv` so the * output layer never touches `process` directly. * * @see https://no-hyperlinks.org/ */ declare function resolveHyperlinkOverride(env: Readonly>, argv: readonly string[]): boolean | undefined; /** * Concrete implementation of the {@linkcode Out} interface. * * Routes messages to the appropriate writer (stdout vs stderr) and * respects the configured verbosity level. * * Handlers interact with this via the {@linkcode Out} interface — they never see * {@linkcode OutputChannel} directly, which keeps the coupling minimal. * Prefer {@link createOutput} unless you are extending the output layer. * * @internal */ declare class OutputChannel implements Out { /** @internal Resolved configuration. */ readonly options: ResolvedOutputOptions; /** Semantic output policy snapshot shared by routing helpers. */ readonly policy: OutputPolicy; /** Whether JSON output mode is active. */ readonly jsonMode: boolean; /** * Whether stdout is connected to a TTY. * * When `jsonMode` is active, this reflects the underlying TTY status * but decorative output should still be suppressed (jsonMode takes * precedence). */ readonly isTTY: boolean; /** * Context-aware ANSI color palette. * * Enabled per the resolved `color` option — identity functions when * colors are off, so handlers can style unconditionally. */ readonly color: Colors; /** * Whether OSC 8 hyperlinks should be emitted for this channel. * * Honors `NO_HYPERLINKS`/`FORCE_HYPERLINKS` and the * `--no-hyperlinks`/`--hyperlinks` argv flags, falling back to `isTTY`. */ readonly isHyperlinkSupported: boolean; constructor(options: ResolvedOutputOptions); /** * Write to stdout (normal output). Always emitted. * * In JSON mode, redirected to stderr so stdout is reserved for * structured `json()` output only. */ log(message: string): void; /** * Informational message to stdout. * Suppressed when verbosity is `'quiet'`. * * In JSON mode, redirected to stderr. */ info(message: string): void; /** * Status line to stderr (stdout stays clean for piping). * Suppressed when verbosity is `'quiet'`. */ status(message: string): void; /** Warning to stderr. Always emitted. */ warn(message: string): void; /** Error to stderr. Always emitted. */ error(message: string): void; /** Request a successful-process exit code without writing error output. */ setExitCode(code: number): void; /** * Emit a structured JSON value to stdout. * * Serialises `value` with `JSON.stringify` and writes to stdout * regardless of JSON mode — `json()` always targets stdout. */ json(value: unknown): void; /** * Render tabular data. * * Default behavior matches the current output mode: * - normal mode → text table to stdout * - jsonMode → JSON array to stdout * * Per-call options can force text/json rendering and route text tables to * stdout or stderr. */ table>(rows: readonly T[], options: TableOptions): void; table>(rows: readonly T[], columns?: readonly TableColumn[], options?: TableOptions): void; /** * Cleanup callback for the currently active spinner/progress handle. * * Set to `undefined` when no handle is active. The callback calls the * appropriate terminal method (`.stop()` for spinners, `.done()` for * progress) on the previous handle. * * @internal */ private activeCleanup; /** * Stop the currently active spinner/progress handle (if any). * * Called internally before creating a new handle to prevent overlap, * and externally after handler execution to clean up leaked timers. * Idempotent (safe to call when no handle is active). */ stopActive(): void; /** * Create a spinner handle. * * Mode dispatch: * - `jsonMode` → noop (structured output only, spinners suppressed) * - `isTTY` → animated TTY spinner (braille frames, ANSI overwrite) * - `!isTTY && fallback: 'static'` → plain text at lifecycle boundaries * - `!isTTY && fallback: 'silent'` (default) → noop * * All activity output (transient frames, terminal messages) routes to * stderr so stdout remains clean for structured data and piping. * * If another spinner or progress handle is active, it is implicitly * stopped before the new one starts. * * @virtual */ spinner(text: string, options?: SpinnerOptions): SpinnerHandle; /** * Create a progress handle. * * Mode dispatch: * - `jsonMode` → noop (structured output only, progress suppressed) * - `isTTY` → animated TTY progress bar (determinate or indeterminate) * - `!isTTY && fallback: 'static'` → plain text at lifecycle boundaries * - `!isTTY && fallback: 'silent'` (default) → noop * * All activity output (transient frames, terminal messages) routes to * stderr so stdout remains clean for structured data and piping. * * If another spinner or progress handle is active, it is implicitly * stopped before the new one starts. * * @virtual */ progress(opts: ProgressOptions): ProgressHandle; } /** * Output channel variant that returns capture handles for spinner/progress. * * Extends {@linkcode OutputChannel} to override `spinner()` and `progress()`, routing * activity events into a shared {@linkcode ActivityEvent | ActivityEvent[]} for testkit assertion. * Text output (log/info/warn/error) is handled by the parent class. * * @internal */ declare class CaptureOutputChannel extends OutputChannel { private readonly activity; constructor(options: ResolvedOutputOptions, activity: ActivityEvent[]); /** * Return a {@link CaptureSpinnerHandle} that records events into the activity log. * @override */ spinner(text: string, _options?: SpinnerOptions): SpinnerHandle; /** * Return a {@link CaptureProgressHandle} that records events into the activity log. * @override */ progress(opts: ProgressOptions): ProgressHandle; } /** * Create an output channel. * * Low-level factory: action handlers already receive `out` automatically from * `cli()`, `.execute()`, and `runCommand()`. Call `createOutput()` when you * are embedding DreamCLI primitives into a custom runtime or need a standalone * output implementation outside the normal command pipeline. * * @param options - Optional configuration. When omitted, output is * discarded (useful for silent test runs). Pass `stdout`/`stderr` * writers to direct output somewhere useful. * * @returns An {@link Out} instance backed by an {@link OutputChannel}. * * @example * ```ts * // Production (wired by the runtime adapter) * const out = createOutput({ * stdout: (s) => process.stdout.write(s), * stderr: (s) => process.stderr.write(s), * isTTY: process.stdout.isTTY === true, * }); * * // Test (capture output) * const lines: string[] = []; * const out = createOutput({ * stdout: (s) => lines.push(s), * stderr: (s) => lines.push(s), * }); * ``` */ declare function createOutput(options?: OutputOptions): Out; /** Captured output from a `createCaptureOutput` instance. */ interface CapturedOutput { /** * Lines written to stdout. * * In normal mode: `log`, `info`, and `json` output. * In JSON mode: only `json` output (log/info redirected to stderr). */ readonly stdout: string[]; /** * Lines written to stderr. * * In normal mode: `warn` and `error` output. * In JSON mode: `warn`, `error`, `log`, and `info` output. */ readonly stderr: string[]; /** * Activity events from spinner and progress handles. * * Captured separately from stdout/stderr to allow targeted assertions * on activity lifecycle without parsing text output. Events are * recorded in chronological order. */ readonly activity: ActivityEvent[]; } /** * Create an output channel that captures all output into arrays. * * Useful in tests to assert on what a handler wrote without touching * real I/O. * * @param options - Optional {@link OutputOptions} (minus `stdout`/`stderr`, * which are wired to the capture buffers automatically). * * @returns A tuple of `[out, captured]` — the {@link Out} channel and the * {@link CapturedOutput} buffers. * * @example * ```ts * const [out, captured] = createCaptureOutput(); * out.log('hello'); * out.warn('danger'); * expect(captured.stdout).toEqual(['hello\n']); * expect(captured.stderr).toEqual(['danger\n']); * ``` */ declare function createCaptureOutput(options?: Omit): [out: Out, captured: CapturedOutput]; //#endregion export { CaptureOutputChannel, CaptureProgressHandle, CaptureSpinnerHandle, type CapturedOutput, OutputChannel, type OutputOptions, StaticProgressHandle, StaticSpinnerHandle, TTYProgressHandle, TTYSpinnerHandle, type Verbosity, type WriteFn, clearRequestedExitCode, createCaptureOutput, createOutput, getRequestedExitCode, noopProgressHandle, noopSpinnerHandle, resolveHyperlinkOverride, setRequestedExitCode, writeLine };