@orkestrel/console 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,443 @@
1
+ import { EmitterErrorHandler } from '@orkestrel/emitter';
2
+ import { EmitterHooks } from '@orkestrel/emitter';
3
+ import { EmitterInterface } from '@orkestrel/emitter';
4
+ import { LogLevel } from '../core/index.js';
5
+ import { SinkInterface } from '../core/index.js';
6
+
7
+ /**
8
+ * One intercepted process-stream write — the immutable, serializable record a
9
+ * {@link ProcessCaptureInterface} buffers and emits, the server analogue of the core
10
+ * `CapturedMessage`.
11
+ *
12
+ * @remarks
13
+ * - `level` — the {@link StreamLevel} naming which stream (`stdout` / `stderr`) was written.
14
+ * - `text` — the chunk decoded to a string (via {@link import('./helpers.js').decodeChunk} —
15
+ * total, never throws), VERBATIM: no trailing-newline trimming and no ANSI stripping, so the
16
+ * captured text is exactly the bytes the program emitted.
17
+ * - `time` — the capture instant as epoch milliseconds (`Date.now()`); a plain number so the record
18
+ * stays serializable and orderable.
19
+ * - Frozen at construction — a consumer (or a `capture` listener) reads it, never mutates it.
20
+ */
21
+ export declare interface CapturedChunk {
22
+ readonly level: StreamLevel;
23
+ readonly text: string;
24
+ readonly time: number;
25
+ }
26
+
27
+ /**
28
+ * The width in character cells of a stream target — its live `columns` when it is a TTY, else the
29
+ * non-interactive {@link DEFAULT_COLUMNS} fallback. The basis a {@link import('./types.js').ServerSinkInterface}
30
+ * reports through `columns` so a `Reporter` / `Progress` can size its layout to the terminal.
31
+ *
32
+ * @remarks
33
+ * Reads `target.columns` ON EACH CALL (so a getter-backed real stream reflects a live resize) and
34
+ * accepts it only when it is a positive finite number; a missing / `0` / non-finite `columns` (a
35
+ * piped, non-TTY stream) falls back to {@link DEFAULT_COLUMNS}. Total — never throws.
36
+ *
37
+ * @param target - The stream whose width to probe
38
+ * @returns The terminal column count, or {@link DEFAULT_COLUMNS} when not a TTY
39
+ */
40
+ export declare function columnsOf(target: StreamTargetInterface): number;
41
+
42
+ /**
43
+ * Create an observable {@link ProcessCaptureInterface} — the server "own ALL output" capture. It
44
+ * intercepts the RAW `process.stdout.write` / `process.stderr.write` (not just `console.*`, which is
45
+ * the core `Capture`), so it catches direct `process` writes, library output, and child-process
46
+ * pipes. Each intercepted write becomes a frozen {@link import('./types.js').CapturedChunk},
47
+ * buffered (bounded, per-stream) and emitted on `capture`; per options it is mirrored back to the
48
+ * real stream and/or forwarded to a {@link import('../core/index.js').SinkInterface}.
49
+ *
50
+ * @param options - See {@link ProcessCaptureOptions}
51
+ * @returns A {@link ProcessCaptureInterface}
52
+ *
53
+ * @remarks
54
+ * - **The wrapper never throws and passes backpressure through** — a throw in `process.stdout.write`
55
+ * would crash the host, so chunks are decoded totally and the original's `boolean` is returned.
56
+ * - **Snapshot-at-start + non-reentrant + process-global** — `start()` snapshots and swaps the
57
+ * pristine `write`; `stop()` restores the EXACT original. At most ONE may be active at a time.
58
+ * Create any server sink BEFORE installing a capture so the mirror's replay is not re-captured.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * import { createProcessCapture } from '@src/server'
63
+ *
64
+ * const capture = createProcessCapture({ levels: ['stderr'], mirror: true })
65
+ * capture.start()
66
+ * process.stderr.write('a library diagnostic\n') // captured AND still shown
67
+ * capture.stop()
68
+ * ```
69
+ */
70
+ export declare function createProcessCapture(options?: ProcessCaptureOptions): ProcessCaptureInterface;
71
+
72
+ /**
73
+ * Create the server TTY {@link ServerSinkInterface} — the C-g server output backend, the
74
+ * env-symmetric sibling of `createBrowserSink` / core's `createConsoleSink`. `write(text, level?)`
75
+ * routes by level to the process streams and is isTTY-aware: it sends ANSI straight to a terminal
76
+ * (which renders it, with a leading `\r` overwriting the line natively — that is how the C-e
77
+ * animations become a LIVE redraw here, with no extra code) but {@link import('../core/index.js').strip}s
78
+ * the ANSI to clean text when the stream is piped to a file or another process.
79
+ *
80
+ * @param options - See {@link ServerSinkOptions}
81
+ * @returns A {@link ServerSinkInterface} — a {@link import('../core/index.js').SinkInterface} that also
82
+ * exposes the terminal `columns` width
83
+ *
84
+ * @remarks
85
+ * - **Routes by level.** `error` / `warn` → the error stream (`process.stderr` by default), every
86
+ * other level (and an omitted level) → the out stream (`process.stdout`) — the SAME routing as
87
+ * core's `createConsoleSink`, so a logger's `error` reaches `stderr`.
88
+ * - **isTTY-aware ANSI.** For each write, if the TARGET stream is a TTY the styled `text` is written
89
+ * VERBATIM (the terminal renders the ANSI, and a leading `\r` overwrites the current line — live
90
+ * animations for free); if it is NOT a TTY (a pipe / redirect to a log file), the ANSI is stripped
91
+ * so the file gets clean text. The decision is per-stream and per-write, re-read live, so it stays
92
+ * correct if a stream's TTY-ness ever differs between out and err.
93
+ * - **Width.** `columns` reflects the live `out.columns` (so it tracks a terminal resize), falling
94
+ * back to {@link import('./constants.js').DEFAULT_COLUMNS} when the out stream is not a TTY — or a
95
+ * fixed value when `options.columns` is supplied. Feed it to a `Reporter` / `Progress` `width`.
96
+ * - **Injectable + guard-narrowed.** `options.out` / `options.err` default to `process.stdout` /
97
+ * `process.stderr` but accept ANY {@link import('./types.js').StreamTargetInterface}, resolved
98
+ * through {@link isStreamTarget} (AGENTS §14 — narrow the boundary, never `as`), so a test drives
99
+ * the sink (and the isTTY-strip path) with a fake stream that never touches the real process
100
+ * streams.
101
+ *
102
+ * @example
103
+ * ```ts
104
+ * import { createLogger, createReporter } from '../core/index.js'
105
+ * import { createServerSink } from '@src/server'
106
+ *
107
+ * const sink = createServerSink()
108
+ * const logger = createLogger({ name: 'app', sink })
109
+ * logger.error('boom') // → process.stderr, ANSI rendered on a TTY / stripped to a pipe
110
+ * const reporter = createReporter({ sink, width: sink.columns })
111
+ * ```
112
+ */
113
+ export declare function createServerSink(options?: ServerSinkOptions): ServerSinkInterface;
114
+
115
+ /**
116
+ * Decode one `process.stdout.write` / `process.stderr.write` chunk to a string — TOTAL, never
117
+ * throws (AGENTS §14). The process write signature accepts `string | Uint8Array` plus an optional
118
+ * encoding; the capture wrapper reuses this so intercepting a raw stream write can never crash the
119
+ * host (a throw inside `process.stdout.write` would take the program down).
120
+ *
121
+ * @remarks
122
+ * - A `string` chunk is returned verbatim — the common case (`console.log`, most library output,
123
+ * and `process.stdout.write('text')` all pass a string).
124
+ * - A `Buffer` chunk is decoded with the supplied `encoding` when it is a recognized
125
+ * {@link BufferEncoding} (`process` write supports `'utf8'` / `'hex'` / `'base64'` / …), defaulting
126
+ * to `'utf8'`; a bare `Uint8Array` is decoded via `TextDecoder` (always utf-8 — the `encoding`
127
+ * argument applies ONLY to a `Buffer`, never a plain `Uint8Array`).
128
+ * - Anything else is coerced with `String(chunk)` (a number / object / bigint / symbol a misbehaving
129
+ * writer hands the stream). The coercion is itself guarded: a value whose `toString` /
130
+ * `Symbol.toPrimitive` throws yields the stable `'[unprintable]'` placeholder. So the helper is
131
+ * TOTAL on every input — it always yields SOME string, never an exception (a throw here would
132
+ * escape into `process.*.write` and crash the host).
133
+ *
134
+ * @param chunk - The chunk passed to the stream's `write`
135
+ * @param encoding - The optional encoding argument passed alongside the chunk
136
+ * @returns The chunk as text
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * decodeChunk('hi') // 'hi'
141
+ * decodeChunk(Buffer.from('hi')) // 'hi'
142
+ * decodeChunk(new Uint8Array([104, 105])) // 'hi'
143
+ * ```
144
+ */
145
+ export declare function decodeChunk(chunk: unknown, encoding?: unknown): string;
146
+
147
+ /**
148
+ * The default set of {@link StreamLevel}s a process capture patches when `options.levels` is omitted
149
+ * — BOTH streams ({@link STREAM_LEVELS}). A consumer narrows it (e.g. just `['stderr']`) via
150
+ * `options.levels`.
151
+ */
152
+ export declare const DEFAULT_CAPTURE_LEVELS: readonly StreamLevel[];
153
+
154
+ /**
155
+ * The default bounded-buffer cap for a {@link import('./types.js').ProcessCaptureInterface} — at
156
+ * most this many recent {@link import('./types.js').CapturedChunk}s are retained per buffer (the
157
+ * total buffer AND each per-stream bucket; oldest dropped first). Mirrors the core `Capture`'s
158
+ * `DEFAULT_CAPTURE_LIMIT`; a consumer overrides it via `options.limit`.
159
+ */
160
+ export declare const DEFAULT_CAPTURE_LIMIT = 1000;
161
+
162
+ /**
163
+ * The terminal width {@link import('./factories.js').createServerSink} reports through
164
+ * {@link import('./types.js').ServerSinkInterface.columns} when the out stream is NOT a TTY (so
165
+ * `.columns` is `undefined`) and no explicit `options.columns` was supplied — the conventional
166
+ * 80-column default a non-interactive context (a pipe, a CI log) assumes.
167
+ */
168
+ export declare const DEFAULT_COLUMNS = 80;
169
+
170
+ /**
171
+ * Whether `encoding` is a {@link BufferEncoding} accepted by `Buffer.prototype.toString` — a total
172
+ * guard used by {@link decodeChunk} to honor a process-write `encoding` argument only when it is a
173
+ * real Node encoding (otherwise utf-8 is assumed).
174
+ *
175
+ * @param encoding - The candidate encoding (the second `write` argument, possibly a callback)
176
+ * @returns `true` when `encoding` names a supported buffer encoding
177
+ */
178
+ export declare function isBufferEncoding(encoding: unknown): encoding is BufferEncoding;
179
+
180
+ /**
181
+ * Whether `value` is a usable {@link StreamTargetInterface} — a record with a callable `write`. A
182
+ * total type guard (AGENTS §14): it NEVER throws and returns `false` for anything off-shape, so it
183
+ * narrows the one unavoidable boundary (the real `process.stdout` / `process.stderr`, or a fake
184
+ * stream a test injects) to the exact slice the sink + capture touch — no `as`.
185
+ *
186
+ * @remarks
187
+ * Only `write` is required (the irreducible output method); `isTTY` and `columns` are optional on
188
+ * {@link StreamTargetInterface}, so their absence does not disqualify a target — a piped stream
189
+ * (no `isTTY`) is still a valid write target, just a non-terminal one.
190
+ *
191
+ * @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)
192
+ * @returns `true` when `value` has a callable `write`
193
+ *
194
+ * @example
195
+ * ```ts
196
+ * isStreamTarget(process.stdout) // true
197
+ * isStreamTarget({ write: () => true }) // true
198
+ * isStreamTarget({}) // false (no write)
199
+ * ```
200
+ */
201
+ export declare function isStreamTarget(value: unknown): value is StreamTargetInterface;
202
+
203
+ /**
204
+ * An observable interceptor of the RAW process output streams (AGENTS §13) — it takes control of
205
+ * `process.stdout.write` / `process.stderr.write` on the WRITE side. While `active`, every write to
206
+ * a configured {@link StreamLevel} is captured as a frozen {@link CapturedChunk}, buffered (total +
207
+ * per-stream, bounded), emitted on `capture`, and — per options — mirrored to the real stream and/or
208
+ * forwarded to a {@link SinkInterface}.
209
+ *
210
+ * @remarks
211
+ * Where the core `Capture` patches `console.*` (the high-level read side), this patches the
212
+ * low-level stream `write`, so it owns ALL server output: a direct `process.stdout.write`, a
213
+ * third-party library's writes, a child-process pipe — not only `console.*`.
214
+ *
215
+ * - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the CURRENT
216
+ * `process[stream].write` for each configured level, then installs the wrappers. The mirror
217
+ * replays through that snapshot (bound to its stream) — so a server sink created from the same
218
+ * streams BEFORE the capture is never re-captured: this catches OTHER writers, not the mirror's
219
+ * own replay. Create your sinks before installing a capture.
220
+ * - **Idempotent + PROCESS-GLOBAL + NON-REENTRANT.** `start()` while `active` is a no-op (never
221
+ * double-patches — that would snapshot the wrapper as the "original" and break restore); `stop()`
222
+ * while inactive is a no-op. It patches the ONE global `process`, so at most ONE process capture
223
+ * may be active at a time — two concurrently would interleave buffers and clobber each other's
224
+ * restore.
225
+ * - **The wrapper NEVER throws and passes backpressure through.** A throw inside
226
+ * `process.stdout.write` would crash the host, so the wrapper decodes the chunk through the total
227
+ * {@link decodeChunk}, and returns the snapshot-original's `boolean` when mirroring (so a caller's
228
+ * `write` backpressure handling still works) or `true` when capture-only (the buffer never fills).
229
+ * - **Bounded buffers.** The total buffer and each per-stream bucket are each capped at `limit`
230
+ * (oldest dropped first), never unbounded — the same retention precedent as the core `Capture`.
231
+ * - **Lifecycle (§10).** `start` / `stop` toggle interception (emitting `start` / `stop`);
232
+ * `destroy()` stops (restoring the PRISTINE `write`) then destroys the emitter.
233
+ *
234
+ * @example
235
+ * ```ts
236
+ * const capture = new ProcessCapture({ levels: ['stderr'], mirror: true })
237
+ * capture.start()
238
+ * process.stderr.write('a library diagnostic\n') // captured AND still written to the terminal
239
+ * capture.messages('stderr') // [{ level: 'stderr', text: 'a library diagnostic\n', time: … }]
240
+ * capture.stop() // process.stderr.write restored
241
+ * ```
242
+ */
243
+ export declare class ProcessCapture implements ProcessCaptureInterface {
244
+ #private;
245
+ constructor(options?: ProcessCaptureOptions);
246
+ get emitter(): EmitterInterface<ProcessCaptureEventMap>;
247
+ get active(): boolean;
248
+ start(): void;
249
+ stop(): void;
250
+ messages(): readonly CapturedChunk[];
251
+ messages(level: StreamLevel): readonly CapturedChunk[];
252
+ clear(): void;
253
+ destroy(): void;
254
+ }
255
+
256
+ /**
257
+ * The observable events a {@link ProcessCaptureInterface} emits (AGENTS §13) — mirrors the core
258
+ * `Capture`'s `CaptureEventMap`, but the captured record is a {@link CapturedChunk} (stream-keyed).
259
+ *
260
+ * @remarks
261
+ * - `capture` — an intercepted `process.stdout` / `process.stderr` write, carrying the frozen
262
+ * {@link CapturedChunk}. The hook a live log viewer / tee subscribes to.
263
+ * - `start` / `stop` — the interception toggled on / off (pure signals, empty tuples).
264
+ *
265
+ * Listener isolation is the emitter's (§13): a listener throw routes to the emitter's `error`
266
+ * handler, never onto this map — so a buggy `capture` listener can never escape into the host's
267
+ * `process.stdout.write` call (which would crash the program).
268
+ *
269
+ * Declared as a `type` alias (not `interface extends EventMap`, §4.5): a type-literal satisfies the
270
+ * `EventMap` constraint structurally, whereas an interface lacks the index signature.
271
+ */
272
+ export declare type ProcessCaptureEventMap = {
273
+ /** An intercepted process-stream write — the frozen {@link CapturedChunk}. */
274
+ readonly capture: readonly [chunk: CapturedChunk];
275
+ /** Interception began (`process.*.write` patched). */
276
+ readonly start: readonly [];
277
+ /** Interception ended (`process.*.write` restored). */
278
+ readonly stop: readonly [];
279
+ };
280
+
281
+ /**
282
+ * An observable interceptor of the RAW process output streams (AGENTS §13) — the server's
283
+ * "own ALL output" capture. Where the core `Capture` patches `console.*` (the high-level read
284
+ * side), this patches `process.stdout.write` / `process.stderr.write` (the low-level stream), so it
285
+ * catches DIRECT `process.stdout.write`, third-party library output, and child-process pipes —
286
+ * everything that reaches the streams, not just `console.*`.
287
+ *
288
+ * @remarks
289
+ * - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the CURRENT
290
+ * `process[stream].write` for each configured {@link StreamLevel}, then installs the wrappers. The
291
+ * mirror replays through that snapshot — so a server sink created from the same streams BEFORE the
292
+ * capture is never re-captured. Create your sinks before installing a capture.
293
+ * - **Idempotent + PROCESS-GLOBAL + NON-REENTRANT.** `start()` while `active` is a no-op (never
294
+ * double-patches); `stop()` while inactive is a no-op. It patches the ONE global `process`, so at
295
+ * most ONE process capture may be active at a time — running two concurrently interleaves their
296
+ * buffers and clobbers each other's restore.
297
+ * - **The wrapper NEVER throws and passes through backpressure.** A throw inside
298
+ * `process.stdout.write` would crash the host, so the wrapper builds its record through a total
299
+ * decode, and returns the snapshot-original's boolean (or `true` when mirroring is off) so a
300
+ * caller's backpressure handling keeps working.
301
+ * - **Bounded buffers.** The total buffer and each per-stream bucket are each capped at `limit`
302
+ * (oldest dropped first), never unbounded.
303
+ * - **Lifecycle (§10).** `start` / `stop` toggle interception (emitting `start` / `stop`);
304
+ * `destroy()` stops (restoring the pristine `write`) then destroys the emitter.
305
+ */
306
+ export declare interface ProcessCaptureInterface {
307
+ readonly emitter: EmitterInterface<ProcessCaptureEventMap>;
308
+ /** Whether interception is currently installed (`start`ed and not yet `stop`ped). */
309
+ readonly active: boolean;
310
+ /** Begin intercepting the configured process streams (idempotent; emits `start`). */
311
+ start(): void;
312
+ /** Restore the pristine `process.*.write` references (idempotent; emits `stop`). */
313
+ stop(): void;
314
+ /** A copy of the full captured buffer, oldest first (capped at `limit`). */
315
+ messages(): readonly CapturedChunk[];
316
+ /** A copy of the captured buffer for ONE {@link StreamLevel}, oldest first (capped at `limit`). */
317
+ messages(level: StreamLevel): readonly CapturedChunk[];
318
+ /** Drop every buffered chunk (total + per-stream); interception is unaffected. */
319
+ clear(): void;
320
+ /** Stop interception (restoring the streams) and tear down the emitter. */
321
+ destroy(): void;
322
+ }
323
+
324
+ /**
325
+ * Options for {@link import('./factories.js').createProcessCapture} — every field optional, so a
326
+ * bare `createProcessCapture()` buffers both streams without mirroring or forwarding.
327
+ *
328
+ * @remarks
329
+ * - `on` — initial {@link ProcessCaptureEventMap} listeners, wired at construction (e.g.
330
+ * `{ capture: (c) => tee(c) }`).
331
+ * - `error` — the listener-error handler forwarded to the entity's emitter (§13).
332
+ * - `levels` — which streams to intercept; defaults to {@link import('./constants.js').DEFAULT_CAPTURE_LEVELS}
333
+ * (both `stdout` and `stderr`). Narrow it (e.g. just `['stderr']`) to capture one stream.
334
+ * - `mirror` — when `true`, each intercepted write is ALSO replayed to the snapshot-original
335
+ * `write` (bound to its stream), so the output still reaches the terminal while being captured;
336
+ * defaults to `false` (capture-only, the program's output is swallowed into the buffer).
337
+ * - `sink` — an optional {@link SinkInterface} each intercepted chunk is also written to
338
+ * (`sink.write(text, level)` with the {@link StreamLevel} mapped to a {@link import('../core/index.js').LogLevel} via
339
+ * {@link import('./constants.js').STREAM_LEVEL_MAP}), to tee captured output into the logging
340
+ * pipeline / a file. Absent by default.
341
+ * - `limit` — the bounded-buffer cap (total AND each per-stream bucket); defaults to
342
+ * {@link import('./constants.js').DEFAULT_CAPTURE_LIMIT}. Retention is ALWAYS bounded.
343
+ */
344
+ export declare interface ProcessCaptureOptions {
345
+ readonly on?: EmitterHooks<ProcessCaptureEventMap>;
346
+ readonly error?: EmitterErrorHandler;
347
+ readonly levels?: readonly StreamLevel[];
348
+ readonly mirror?: boolean;
349
+ readonly sink?: SinkInterface;
350
+ readonly limit?: number;
351
+ }
352
+
353
+ /**
354
+ * A {@link SinkInterface} that also exposes the target terminal's {@link columns} width — the shape
355
+ * {@link import('./factories.js').createServerSink} returns. It is a drop-in {@link SinkInterface}
356
+ * (so a `Logger` / `Reporter` / `Spinner` / `Progress` takes it as `sink`) whose extra `columns`
357
+ * getter lets a consumer size a `Reporter`'s layout to the live terminal.
358
+ *
359
+ * @remarks
360
+ * `columns` is a getter, re-read on every access — so it reflects the CURRENT terminal width (a
361
+ * resize is observed) unless a fixed `options.columns` was supplied, in which case it is constant.
362
+ */
363
+ export declare interface ServerSinkInterface extends SinkInterface {
364
+ readonly columns: number;
365
+ }
366
+
367
+ /**
368
+ * Options for {@link import('./factories.js').createServerSink} — all optional, so a bare
369
+ * `createServerSink()` writes to the real process streams.
370
+ *
371
+ * @remarks
372
+ * - `out` — the stream `info` / `debug` (and an omitted level) are written to; defaults to
373
+ * `process.stdout`. Any {@link StreamTargetInterface} is accepted, so a test injects a fake.
374
+ * - `err` — the stream `error` / `warn` are written to; defaults to `process.stderr`.
375
+ * - `columns` — an explicit width override for {@link ServerSinkInterface.columns}. When omitted,
376
+ * the sink reads the live `out.columns` (so it tracks a terminal resize), falling back to
377
+ * {@link import('./constants.js').DEFAULT_COLUMNS} when the out stream is not a TTY.
378
+ */
379
+ export declare interface ServerSinkOptions {
380
+ readonly out?: StreamTargetInterface;
381
+ readonly err?: StreamTargetInterface;
382
+ readonly columns?: number;
383
+ }
384
+
385
+ /**
386
+ * Each {@link StreamLevel}'s {@link LogLevel} for the optional sink forward — the projection a
387
+ * process capture routes through when writing an intercepted chunk to a
388
+ * {@link import('../core/index.js').SinkInterface}
389
+ * (`sink.write(text, STREAM_LEVEL_MAP[level])`). `stderr` is conventionally the error/diagnostic
390
+ * stream → `error`; `stdout` is the normal output stream → `info`. The source of truth for the
391
+ * stream-to-log projection (the server analogue of the core `CAPTURE_LEVEL_MAP`).
392
+ */
393
+ export declare const STREAM_LEVEL_MAP: Readonly<Record<StreamLevel, LogLevel>>;
394
+
395
+ /**
396
+ * The two process streams a {@link import('./types.js').ProcessCaptureInterface} can intercept, in
397
+ * `stdout`-then-`stderr` order — the {@link StreamLevel} universe and the default configured set.
398
+ */
399
+ export declare const STREAM_LEVELS: readonly StreamLevel[];
400
+
401
+ /**
402
+ * Which process stream a {@link CapturedChunk} came from — the "level" axis of the process-stream
403
+ * {@link ProcessCaptureInterface}, the server analogue of the core `Capture`'s `CaptureLevel`.
404
+ *
405
+ * @remarks
406
+ * DISTINCT from {@link import('../core/index.js').LogLevel}: a `StreamLevel` names the ORIGINATING process stream
407
+ * (`process.stdout` vs `process.stderr`), not a severity. It is a named value family (it indexes
408
+ * {@link import('./constants.js').STREAM_LEVEL_MAP} to a {@link import('../core/index.js').LogLevel} for the optional sink
409
+ * forward), never a binary toggle — so it stays a union (AGENTS §4.4).
410
+ */
411
+ export declare type StreamLevel = 'stdout' | 'stderr';
412
+
413
+ /**
414
+ * The minimal writable-stream shape the C-g server sink and process capture address — exactly the
415
+ * slice of a Node `tty.WriteStream` / `process.stdout` they touch, and no more. A
416
+ * {@link ServerSinkOptions} target and a {@link ProcessCaptureInterface}'s patched streams are
417
+ * narrowed to this via {@link import('./helpers.js').isStreamTarget} (AGENTS §14 — narrow the
418
+ * boundary, never `as`), so a test can drive either with a hand-built fake stream that never
419
+ * touches the real `process` streams.
420
+ *
421
+ * @remarks
422
+ * - `write(text)` — the one required method: push a chunk to the stream, returning the host's
423
+ * backpressure boolean (`false` when the kernel buffer is full). A `process` stream returns it;
424
+ * a fake may return `void` (read as truthy / no backpressure).
425
+ * - `isTTY` — present and `true` on a real terminal, absent / `false` when the stream is piped to a
426
+ * file or another process. The sink reads it to decide whether to keep ANSI (a terminal renders
427
+ * it, and a leading `\r` overwrites natively) or {@link import('../core/index.js').strip} it to clean
428
+ * text (a log file should not carry escape codes).
429
+ * - `columns` — the terminal width in character cells when the stream is a TTY, `undefined`
430
+ * otherwise; the sink surfaces it as {@link ServerSinkInterface.columns} so a consumer can feed a
431
+ * `Reporter` / `Progress` its render width.
432
+ */
433
+ export declare interface StreamTargetInterface {
434
+ write(text: string): boolean | void;
435
+ readonly isTTY?: boolean;
436
+ readonly columns?: number;
437
+ }
438
+
439
+ export declare type StreamWriteCallback = (error?: Error | null) => void;
440
+
441
+ export declare type StreamWriteFunction = NodeJS.WriteStream['write'];
442
+
443
+ export { }