@orkestrel/console 0.0.11 → 0.0.13

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.
@@ -1,18 +1,18 @@
1
- import { EmitterErrorHandler } from '@orkestrel/emitter';
2
- import { EmitterHooks } from '@orkestrel/emitter';
3
- import { EmitterInterface } from '@orkestrel/emitter';
4
- import { LogLevel } from '@orkestrel/console';
5
- import { SinkInterface } from '@orkestrel/console';
1
+ import type { EmitterErrorHandler } from '@orkestrel/emitter';
2
+ import type { EmitterHooks } from '@orkestrel/emitter';
3
+ import type { EmitterInterface } from '@orkestrel/emitter';
4
+ import type { LogLevel } from '@orkestrel/console';
5
+ import type { SinkInterface } from '@orkestrel/console';
6
6
 
7
7
  /**
8
- * One intercepted process-stream write — the immutable, serializable record a
8
+ * Represents one intercepted process-stream write — the immutable, serializable record a
9
9
  * {@link ProcessCaptureInterface} buffers and emits, the server analogue of the core
10
10
  * `CapturedMessage`.
11
11
  *
12
12
  * @remarks
13
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
14
+ * - `text` — the chunk decoded to a string (through {@link import('./helpers.js').decodeChunk} —
15
+ * total, never throws), verbatim: no trailing-newline trimming and no ANSI stripping, so the
16
16
  * captured text is exactly the bytes the program emitted.
17
17
  * - `time` — the capture instant as epoch milliseconds (`Date.now()`); a plain number so the record
18
18
  * stays serializable and orderable.
@@ -25,96 +25,65 @@ export declare interface CapturedChunk {
25
25
  }
26
26
 
27
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('@src/core').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 uses construction-time styled facts: it sends ANSI
76
- * straight to a styled target (with a leading `\r` overwriting a terminal line natively) but
77
- * {@link import('@src/core').strip}s ANSI to clean text for a plain target.
28
+ * Creates the server TTY {@link ServerSinkInterface}the server output backend, whose
29
+ * `write(text, level?)` routes by level to the process streams and uses construction-time styled
30
+ * facts: it sends ANSI straight to a styled target (with a leading `\r` overwriting a terminal
31
+ * line natively) but {@link import('@orkestrel/console').strip}s ANSI to clean text for a plain target.
78
32
  *
79
33
  * @param options - See {@link ServerSinkOptions}
80
- * @returns A {@link ServerSinkInterface} — a {@link import('@src/core').SinkInterface} that also
34
+ * @returns A {@link ServerSinkInterface} — a {@link import('@orkestrel/console').SinkInterface} that also
81
35
  * exposes the terminal `columns` width
82
36
  *
83
37
  * @remarks
84
38
  * - **Routes by level.** `error` / `warn` → the error stream (`process.stderr` by default), every
85
- * other level (and an omitted level) → the out stream (`process.stdout`) — the SAME routing as
86
- * core's `createConsoleSink`, so a logger's `error` reaches `stderr`.
39
+ * other level (and an omitted level) → the `stdout` stream (`process.stdout`) — the same routing
40
+ * as core's `createConsoleSink`, so a logger's `error` reaches `stderr`. Both call the one
41
+ * {@link import('@orkestrel/console').selectWriter} leaf, which is what keeps them identical.
87
42
  * - **Per-target styled facts.** At construction, each target uses `options.styled` when supplied;
88
43
  * otherwise {@link inferStyled} applies the injected `environment` (default `process.env`) and
89
44
  * then that target's `isTTY`.
90
- * Writes use those stored facts, so `styled` and the out target's strip decision never disagree;
91
- * the err target keeps its own fact internally.
92
- * - **Width.** `columns` reflects the live `out.columns` (so it tracks a terminal resize), falling
93
- * back to {@link import('./constants.js').DEFAULT_COLUMNS} when the out stream is not a TTY — or a
94
- * fixed value when `options.columns` is supplied. Feed it to a `Reporter` / `Progress` `width`.
95
- * - **Injectable + guard-narrowed.** `options.out` / `options.err` default to `process.stdout` /
96
- * `process.stderr` but accept ANY {@link import('./types.js').StreamTargetInterface}, resolved
97
- * through {@link isStreamTarget} (AGENTS §14 — narrow the boundary, never `as`), so a test drives
45
+ * Writes use those stored facts, so `styled` and the `stdout` target's strip decision never
46
+ * disagree; the `stderr` target keeps its own fact internally.
47
+ * - **Width.** `columns` reflects the live `stdout.columns` (so it tracks a terminal resize),
48
+ * falling back to {@link import('./constants.js').DEFAULT_COLUMNS} when the `stdout` stream is not
49
+ * a TTY — or a fixed value when `options.columns` is supplied. Feed it to a `Reporter` /
50
+ * `Progress` `width`.
51
+ * - **Injectable + guard-narrowed.** `options.stdout` / `options.stderr` default to `process.stdout`
52
+ * / `process.stderr` but accept any {@link import('./types.js').StreamTargetInterface}, resolved
53
+ * through {@link isStreamTarget} (narrow the boundary, never `as`), so a test drives
98
54
  * the sink (and the isTTY-strip path) with a fake stream that never touches the real process
99
55
  * streams.
100
56
  *
101
- * @example
57
+ * @example The server — a TTY sink and a process capture
102
58
  * ```ts
103
- * import { createLogger, createReporter, createStyler } from '@src/core'
104
- * import { createServerSink } from '@src/server'
105
- *
106
- * const sink = createServerSink()
107
- * const styler = createStyler({ enabled: sink.styled })
108
- * const logger = createLogger({ name: 'app', sink, styler })
109
- * logger.error('boom') // → process.stderr, ANSI rendered on a TTY / stripped to a pipe
110
- * const reporter = createReporter({ sink, width: sink.columns })
59
+ * import { createStyler, Logger, Reporter } from '@orkestrel/console'
60
+ * import { createServerSink, ProcessCapture } from '@orkestrel/console/server'
61
+ *
62
+ * const sink = createServerSink() // FORCE_COLOR, then NO_COLOR, then isTTY — per target, at construction
63
+ * const styler = createStyler({ enabled: sink.styled }) // keep generated ANSI paired with the sink's stdout stripping
64
+ * const logger = new Logger({ name: 'server', sink, styler })
65
+ * logger.error('boom') // → process.stderr (the error stream)
66
+ * const reporter = new Reporter({ sink, width: sink.columns }) // size the layout to the live terminal
67
+ *
68
+ * // `styled` overrides the inference outright — for a CI log that renders ANSI off a TTY, say.
69
+ * const forced = createServerSink({ styled: true })
70
+ * forced.styled // true, whatever the environment and the streams say
71
+ *
72
+ * // Own every output path — a direct process.stdout.write, library output, child-process pipes:
73
+ * const capture = new ProcessCapture({ levels: ['stderr'], mirror: true })
74
+ * capture.start()
75
+ * process.stderr.write('a library diagnostic\n') // captured and still shown (mirror: true)
76
+ * capture.messages('stderr') // [{ level: 'stderr', text: 'a library diagnostic\n', time: … }]
77
+ * capture.clear() // drop buffered chunks; interception is unaffected
78
+ * capture.stop()
79
+ * capture.destroy() // stop() then tear down the emitter
111
80
  * ```
112
81
  */
113
82
  export declare function createServerSink(options?: ServerSinkOptions): ServerSinkInterface;
114
83
 
115
84
  /**
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
85
+ * Decodes one `process.stdout.write` / `process.stderr.write` chunk to a string — total, never
86
+ * throws. The process write signature accepts `string | Uint8Array` plus an optional
118
87
  * encoding; the capture wrapper reuses this so intercepting a raw stream write can never crash the
119
88
  * host (a throw inside `process.stdout.write` would take the program down).
120
89
  *
@@ -123,12 +92,12 @@ export declare function createServerSink(options?: ServerSinkOptions): ServerSin
123
92
  * and `process.stdout.write('text')` all pass a string).
124
93
  * - A `Buffer` chunk is decoded with the supplied `encoding` when it is a recognized
125
94
  * {@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`).
95
+ * to `'utf8'`; a bare `Uint8Array` is decoded through `TextDecoder` (always utf-8 — the `encoding`
96
+ * argument applies only to a `Buffer`, never a plain `Uint8Array`).
128
97
  * - Anything else is coerced with `String(chunk)` (a number / object / bigint / symbol a misbehaving
129
98
  * writer hands the stream). The coercion is itself guarded: a value whose `toString` /
130
99
  * `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
100
+ * total on every input — it always yields some string, never an exception (a throw here would
132
101
  * escape into `process.*.write` and crash the host).
133
102
  *
134
103
  * @param chunk - The chunk passed to the stream's `write`
@@ -145,41 +114,54 @@ export declare function createServerSink(options?: ServerSinkOptions): ServerSin
145
114
  export declare function decodeChunk(chunk: unknown, encoding?: unknown): string;
146
115
 
147
116
  /**
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`.
117
+ * Sets the terminal width {@link import('./factories.js').createServerSink} reports through
118
+ * {@link import('./types.js').ServerSinkInterface.columns} when the `stdout` stream is not a TTY
119
+ * (so `.columns` is `undefined`) and no explicit `options.columns` was supplied — the conventional
120
+ * 80-column default a non-interactive context (a pipe, a CI log) assumes.
151
121
  */
152
- export declare const DEFAULT_CAPTURE_LEVELS: readonly StreamLevel[];
122
+ export declare const DEFAULT_COLUMNS = 80;
153
123
 
154
124
  /**
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`.
125
+ * Sets the default bounded-buffer cap for a {@link import('./types.js').ProcessCaptureInterface} —
126
+ * `1000`, so at most that many recent {@link import('./types.js').CapturedChunk}s are retained per
127
+ * buffer (the total buffer and each per-stream bucket; oldest dropped first) and retention is
128
+ * always bounded.
129
+ *
130
+ * @remarks
131
+ * It mirrors the core `Capture`'s `DEFAULT_CAPTURE_LIMIT`; a consumer overrides the cap through
132
+ * `options.limit`.
159
133
  */
160
- export declare const DEFAULT_CAPTURE_LIMIT = 1000;
134
+ export declare const DEFAULT_STREAM_LIMIT = 1000;
161
135
 
162
136
  /**
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.
137
+ * Infers the width in character cells of a stream target — its live `columns` when it is a TTY,
138
+ * else the non-interactive {@link DEFAULT_COLUMNS} fallback. The basis a
139
+ * {@link import('./types.js').ServerSinkInterface} reports through `columns` so a `Reporter` /
140
+ * `Progress` can size its layout to the terminal.
141
+ *
142
+ * @remarks
143
+ * Reads `target.columns` on each call (so a getter-backed real stream reflects a live resize) and
144
+ * accepts it only when it is a positive finite number; a missing / `0` / non-finite `columns` (a
145
+ * piped, non-TTY stream) falls back to {@link DEFAULT_COLUMNS}. Total — never throws.
146
+ *
147
+ * @param target - The stream whose width to probe
148
+ * @returns The terminal column count, or {@link DEFAULT_COLUMNS} when not a TTY
167
149
  */
168
- export declare const DEFAULT_COLUMNS = 80;
150
+ export declare function inferColumns(target: StreamTargetInterface): number;
169
151
 
170
152
  /**
171
- * Infer whether one stream target should receive styled output. The result is a construction-time
172
- * target fact for {@link import('./factories.js').createServerSink}; this helper is pure and never
173
- * reads process globals itself.
153
+ * Infers whether one stream target receives styled output a present `FORCE_COLOR` first, then a
154
+ * non-empty `NO_COLOR`, then `target.isTTY === true`.
174
155
  *
175
156
  * @remarks
176
- * A present `FORCE_COLOR` key has first precedence: only the exact value `'0'` disables styling.
177
- * Next, a non-empty `NO_COLOR` disables styling. Otherwise styling follows
178
- * `target.isTTY === true`.
157
+ * The result is a construction-time target fact for
158
+ * {@link import('./factories.js').createServerSink}; this helper is pure and
159
+ * never reads process globals itself. Under `FORCE_COLOR` only the exact value `'0'` disables
160
+ * styling.
179
161
  *
180
162
  * @param target - The stream target whose terminal capability is the fallback
181
163
  * @param environment - The environment record supplying `FORCE_COLOR` and `NO_COLOR`
182
- * @returns Whether output for the target should retain styling and control sequences
164
+ * @returns True if output for the target retains styling and control sequences; false otherwise
183
165
  *
184
166
  * @example
185
167
  * ```ts
@@ -190,28 +172,28 @@ export declare const DEFAULT_COLUMNS = 80;
190
172
  export declare function inferStyled(target: StreamTargetInterface, environment: Readonly<Record<string, string | undefined>>): boolean;
191
173
 
192
174
  /**
193
- * Whether `encoding` is a {@link BufferEncoding} accepted by `Buffer.prototype.toString` — a total
194
- * guard used by {@link decodeChunk} to honor a process-write `encoding` argument only when it is a
195
- * real Node encoding (otherwise utf-8 is assumed).
175
+ * Checks whether `encoding` is a {@link BufferEncoding} accepted by `Buffer.prototype.toString` — a
176
+ * total guard used by {@link import('./helpers.js').decodeChunk} to honor a process-write
177
+ * `encoding` argument only when it is a real Node encoding (otherwise utf-8 is assumed).
196
178
  *
197
179
  * @param encoding - The candidate encoding (the second `write` argument, possibly a callback)
198
- * @returns `true` when `encoding` names a supported buffer encoding
180
+ * @returns True if `encoding` names a supported buffer encoding; false otherwise
199
181
  */
200
182
  export declare function isBufferEncoding(encoding: unknown): encoding is BufferEncoding;
201
183
 
202
184
  /**
203
- * Whether `value` is a usable {@link StreamTargetInterface} — a record with a callable `write`. A
204
- * total type guard (AGENTS §14): it NEVER throws and returns `false` for anything off-shape, so it
185
+ * Checks whether `value` is a usable {@link StreamTargetInterface} — a record with a callable `write`. A
186
+ * total type guard: it never throws and returns `false` for anything off-shape, so it
205
187
  * narrows the one unavoidable boundary (the real `process.stdout` / `process.stderr`, or a fake
206
188
  * stream a test injects) to the exact slice the sink + capture touch — no `as`.
207
189
  *
208
190
  * @remarks
209
191
  * Only `write` is required (the irreducible output method); `isTTY` and `columns` are optional on
210
192
  * {@link StreamTargetInterface}, so their absence does not disqualify a target — a piped stream
211
- * (no `isTTY`) is still a valid write target, just a non-terminal one.
193
+ * (no `isTTY`) is still a valid write target, only a non-terminal one.
212
194
  *
213
195
  * @param value - Any value crossing the boundary (a process stream, an injected fake, `unknown`)
214
- * @returns `true` when `value` has a callable `write`
196
+ * @returns True if `value` has a callable `write`; false otherwise
215
197
  *
216
198
  * @example
217
199
  * ```ts
@@ -223,28 +205,28 @@ export declare function isBufferEncoding(encoding: unknown): encoding is BufferE
223
205
  export declare function isStreamTarget(value: unknown): value is StreamTargetInterface;
224
206
 
225
207
  /**
226
- * An observable interceptor of the RAW process output streams (AGENTS §13) — it takes control of
227
- * `process.stdout.write` / `process.stderr.write` on the WRITE side. While `active`, every write to
208
+ * Implements an observable interceptor of the raw process output streams — it takes control of
209
+ * `process.stdout.write` / `process.stderr.write` on the write side. While `active`, every write to
228
210
  * a configured {@link StreamLevel} is captured as a frozen {@link CapturedChunk}, buffered (total +
229
- * per-stream, bounded), emitted on `capture`, and — per options — mirrored to the real stream and/or
230
- * forwarded to a {@link SinkInterface}.
211
+ * per-stream, bounded), emitted on `capture`, and — per options — mirrored to the real stream,
212
+ * forwarded to a {@link SinkInterface}, or both.
231
213
  *
232
214
  * @remarks
233
215
  * Where the core `Capture` patches `console.*` (the high-level read side), this patches the
234
- * low-level stream `write`, so it owns ALL server output: a direct `process.stdout.write`, a
216
+ * low-level stream `write`, so it owns all server output: a direct `process.stdout.write`, a
235
217
  * third-party library's writes, a child-process pipe — not only `console.*`.
236
218
  *
237
- * - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the CURRENT
219
+ * - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the current
238
220
  * `process[stream].write` for each configured level, then installs the wrappers. The mirror
239
221
  * replays through that snapshot (bound to its stream) — so a server sink created from the same
240
- * streams BEFORE the capture is never re-captured: this catches OTHER writers, not the mirror's
222
+ * streams before the capture is never re-captured: this catches other writers, not the mirror's
241
223
  * own replay. Create your sinks before installing a capture.
242
- * - **Idempotent + PROCESS-GLOBAL + NON-REENTRANT.** `start()` while `active` is a no-op (never
224
+ * - **Idempotent + process-global + non-reentrant.** `start()` while `active` is a no-op (never
243
225
  * double-patches — that would snapshot the wrapper as the "original" and break restore); `stop()`
244
- * while inactive is a no-op. It patches the ONE global `process`, so at most ONE process capture
226
+ * while inactive is a no-op. It patches the one global `process`, so at most one process capture
245
227
  * may be active at a time — two concurrently would interleave buffers and clobber each other's
246
228
  * restore.
247
- * - **The wrapper NEVER throws and passes backpressure through.** A throw inside
229
+ * - **The wrapper never throws and passes backpressure through.** A throw inside
248
230
  * `process.stdout.write` would crash the host, so the wrapper decodes each chunk totally (a byte
249
231
  * chunk through the per-level streaming decoder below, everything else through the total
250
232
  * {@link decodeChunk}), and returns the snapshot-original's `boolean` when mirroring (so a caller's
@@ -255,18 +237,18 @@ export declare function isStreamTarget(value: unknown): value is StreamTargetInt
255
237
  * child-process pipe, a library, or OS buffering all produce this — carries its partial bytes to
256
238
  * the next write instead of decoding each half to `U+FFFD`. `stop()` flushes each decoder once, so
257
239
  * a codepoint left half-written at stop is still surfaced. A `string` chunk is already text and
258
- * passes through; an explicit NON-utf-8 buffer encoding (`latin1` / `hex` / `base64` / …) names a
240
+ * passes through; an explicit non-utf-8 buffer encoding (`latin1` / `hex` / `base64` / …) names a
259
241
  * self-contained per-write decode and is honored one-shot through {@link decodeChunk}.
260
242
  * - **Bounded buffers.** The total buffer and each per-stream bucket are each capped at `limit`
261
243
  * (oldest dropped first), never unbounded — the same retention precedent as the core `Capture`.
262
- * - **Lifecycle (§10).** `start` / `stop` toggle interception (emitting `start` / `stop`);
263
- * `destroy()` stops (restoring the PRISTINE `write`) then destroys the emitter.
244
+ * - **Lifecycle.** `start` / `stop` toggle interception (emitting `start` / `stop`);
245
+ * `destroy()` stops (restoring the pristine `write`) then destroys the emitter.
264
246
  *
265
247
  * @example
266
248
  * ```ts
267
249
  * const capture = new ProcessCapture({ levels: ['stderr'], mirror: true })
268
250
  * capture.start()
269
- * process.stderr.write('a library diagnostic\n') // captured AND still written to the terminal
251
+ * process.stderr.write('a library diagnostic\n') // captured and still written to the terminal
270
252
  * capture.messages('stderr') // [{ level: 'stderr', text: 'a library diagnostic\n', time: … }]
271
253
  * capture.stop() // process.stderr.write restored
272
254
  * ```
@@ -285,92 +267,101 @@ export declare class ProcessCapture implements ProcessCaptureInterface {
285
267
  }
286
268
 
287
269
  /**
288
- * The observable events a {@link ProcessCaptureInterface} emits (AGENTS §13) — mirrors the core
289
- * `Capture`'s `CaptureEventMap`, but the captured record is a {@link CapturedChunk} (stream-keyed).
270
+ * Declares the observable events a {@link ProcessCaptureInterface} emits — `capture(chunk)` per
271
+ * intercepted write, plus the `start` and `stop` signals.
290
272
  *
291
273
  * @remarks
274
+ * The map mirrors the core `Capture`'s `CaptureEventMap`, and the captured record is a
275
+ * {@link CapturedChunk} (stream-keyed).
276
+ *
292
277
  * - `capture` — an intercepted `process.stdout` / `process.stderr` write, carrying the frozen
293
278
  * {@link CapturedChunk}. The hook a live log viewer / tee subscribes to.
294
279
  * - `start` / `stop` — the interception toggled on / off (pure signals, empty tuples).
295
280
  *
296
- * Listener isolation is the emitter's (§13): a listener throw routes to the emitter's `error`
281
+ * Listener isolation is the emitter's: a listener throw routes to the emitter's `error`
297
282
  * handler, never onto this map — so a buggy `capture` listener can never escape into the host's
298
283
  * `process.stdout.write` call (which would crash the program).
299
284
  *
300
- * Declared as a `type` alias (not `interface extends EventMap`, §4.5): a type-literal satisfies the
285
+ * Declared as a `type` alias (not `interface extends EventMap`): a type-literal satisfies the
301
286
  * `EventMap` constraint structurally, whereas an interface lacks the index signature.
302
287
  */
303
288
  export declare type ProcessCaptureEventMap = {
304
- /** An intercepted process-stream write — the frozen {@link CapturedChunk}. */
289
+ /** Fires on an intercepted process-stream write — the frozen {@link CapturedChunk}. */
305
290
  readonly capture: readonly [chunk: CapturedChunk];
306
- /** Interception began (`process.*.write` patched). */
291
+ /** Fires after interception began (`process.*.write` patched). */
307
292
  readonly start: readonly [];
308
- /** Interception ended (`process.*.write` restored). */
293
+ /** Fires after interception ended (`process.*.write` restored). */
309
294
  readonly stop: readonly [];
310
295
  };
311
296
 
312
297
  /**
313
- * An observable interceptor of the RAW process output streams (AGENTS §13) — the server's
314
- * "own ALL output" capture. Where the core `Capture` patches `console.*` (the high-level read
315
- * side), this patches `process.stdout.write` / `process.stderr.write` (the low-level stream), so it
316
- * catches DIRECT `process.stdout.write`, third-party library output, and child-process pipes —
317
- * everything that reaches the streams, not just `console.*`.
298
+ * Declares an observable interceptor of the raw process output streams — the server's "own all
299
+ * output" capture, patching `process.stdout.write` / `process.stderr.write` on the low-level
300
+ * stream where the core `Capture` patches `console.*`.
318
301
  *
319
302
  * @remarks
320
- * - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the CURRENT
303
+ * Patching the stream catches a direct `process.stdout.write`, third-party library output, and
304
+ * child-process pipes — everything that reaches the streams, not only `console.*`.
305
+ *
306
+ * - **Snapshot-at-start (the no-capture-loop principle).** `start()` snapshots the current
321
307
  * `process[stream].write` for each configured {@link StreamLevel}, then installs the wrappers. The
322
- * mirror replays through that snapshot — so a server sink created from the same streams BEFORE the
308
+ * mirror replays through that snapshot — so a server sink created from the same streams before the
323
309
  * capture is never re-captured. Create your sinks before installing a capture.
324
- * - **Idempotent + PROCESS-GLOBAL + NON-REENTRANT.** `start()` while `active` is a no-op (never
325
- * double-patches); `stop()` while inactive is a no-op. It patches the ONE global `process`, so at
326
- * most ONE process capture may be active at a time — running two concurrently interleaves their
310
+ * - **Idempotent + process-global + non-reentrant.** `start()` while `active` is a no-op (never
311
+ * double-patches); `stop()` while inactive is a no-op. It patches the one global `process`, so at
312
+ * most one process capture may be active at a time — running two concurrently interleaves their
327
313
  * buffers and clobbers each other's restore.
328
- * - **The wrapper NEVER throws and passes through backpressure.** A throw inside
314
+ * - **The wrapper never throws and passes through backpressure.** A throw inside
329
315
  * `process.stdout.write` would crash the host, so the wrapper builds its record through a total
330
316
  * decode, and returns the snapshot-original's boolean (or `true` when mirroring is off) so a
331
317
  * caller's backpressure handling keeps working.
332
318
  * - **Bounded buffers.** The total buffer and each per-stream bucket are each capped at `limit`
333
319
  * (oldest dropped first), never unbounded.
334
- * - **Lifecycle (§10).** `start` / `stop` toggle interception (emitting `start` / `stop`);
320
+ * - **Lifecycle.** `start` / `stop` toggle interception (emitting `start` / `stop`);
335
321
  * `destroy()` stops (restoring the pristine `write`) then destroys the emitter.
336
322
  */
337
323
  export declare interface ProcessCaptureInterface {
338
324
  readonly emitter: EmitterInterface<ProcessCaptureEventMap>;
339
- /** Whether interception is currently installed (`start`ed and not yet `stop`ped). */
325
+ /** Reports whether interception is installed (`start`ed and not yet `stop`ped). */
340
326
  readonly active: boolean;
341
- /** Begin intercepting the configured process streams (idempotent; emits `start`). */
327
+ /** Begins intercepting the configured process streams (idempotent; emits `start`). */
342
328
  start(): void;
343
- /** Restore the pristine `process.*.write` references (idempotent; emits `stop`). */
329
+ /** Restores the pristine `process.*.write` references (idempotent; emits `stop`). */
344
330
  stop(): void;
345
- /** A copy of the full captured buffer, oldest first (capped at `limit`). */
331
+ /**
332
+ * Returns a copy of the full captured buffer, oldest first (capped at `limit`), or — given a
333
+ * {@link StreamLevel} — a copy of only that stream's bucket.
334
+ */
346
335
  messages(): readonly CapturedChunk[];
347
- /** A copy of the captured buffer for ONE {@link StreamLevel}, oldest first (capped at `limit`). */
336
+ /** Returns a copy of the captured buffer for one {@link StreamLevel}, oldest first (capped at `limit`). */
348
337
  messages(level: StreamLevel): readonly CapturedChunk[];
349
- /** Drop every buffered chunk (total + per-stream); interception is unaffected. */
338
+ /** Drops every buffered chunk (total + per-stream); interception is unaffected. */
350
339
  clear(): void;
351
- /** Stop interception (restoring the streams) and tear down the emitter. */
340
+ /** Stops interception (restoring the streams) and tears down the emitter. */
352
341
  destroy(): void;
353
342
  }
354
343
 
355
344
  /**
356
- * Options for {@link import('./factories.js').createProcessCapture} — every field optional, so a
357
- * bare `createProcessCapture()` buffers both streams without mirroring or forwarding.
345
+ * Holds the options for the {@link import('./ProcessCapture.js').ProcessCapture} constructor the
346
+ * `on` / `error` emitter keys, the `levels` intercepted, the `mirror` pass-through, the `sink`
347
+ * forward, and the buffer `limit`. Every field is optional, so a bare `new ProcessCapture()`
348
+ * buffers both streams without mirroring or forwarding.
358
349
  *
359
350
  * @remarks
360
- * - `on` — initial {@link ProcessCaptureEventMap} listeners, wired at construction (e.g.
351
+ * - `on` — initial {@link ProcessCaptureEventMap} listeners, wired at construction (for example
361
352
  * `{ capture: (c) => tee(c) }`).
362
- * - `error` — the listener-error handler forwarded to the entity's emitter (§13).
363
- * - `levels` — which streams to intercept; defaults to {@link import('./constants.js').DEFAULT_CAPTURE_LEVELS}
364
- * (both `stdout` and `stderr`). Narrow it (e.g. just `['stderr']`) to capture one stream.
365
- * - `mirror` — when `true`, each intercepted write is ALSO replayed to the snapshot-original
353
+ * - `error` — the listener-error handler forwarded to the entity's emitter.
354
+ * - `levels` — which streams to intercept; defaults to {@link import('./constants.js').STREAM_LEVELS}
355
+ * (`stdout` and `stderr`). Narrow it (for example to `['stderr']`) to capture one stream.
356
+ * - `mirror` — when `true`, each intercepted write is also replayed to the snapshot-original
366
357
  * `write` (bound to its stream), so the output still reaches the terminal while being captured;
367
358
  * defaults to `false` (capture-only, the program's output is swallowed into the buffer).
368
359
  * - `sink` — an optional {@link SinkInterface} each intercepted chunk is also written to
369
- * (`sink.write(text, level)` with the {@link StreamLevel} mapped to a {@link import('@src/core').LogLevel} via
360
+ * (`sink.write(text, level)` with the {@link StreamLevel} mapped to a {@link import('@orkestrel/console').LogLevel} through
370
361
  * {@link import('./constants.js').STREAM_LEVEL_MAP}), to tee captured output into the logging
371
362
  * pipeline / a file. Absent by default.
372
- * - `limit` — the bounded-buffer cap (total AND each per-stream bucket); defaults to
373
- * {@link import('./constants.js').DEFAULT_CAPTURE_LIMIT}. Retention is ALWAYS bounded.
363
+ * - `limit` — the bounded-buffer cap (total and each per-stream bucket); defaults to
364
+ * {@link import('./constants.js').DEFAULT_STREAM_LIMIT}. Retention is always bounded.
374
365
  */
375
366
  export declare interface ProcessCaptureOptions {
376
367
  readonly on?: EmitterHooks<ProcessCaptureEventMap>;
@@ -382,16 +373,19 @@ export declare interface ProcessCaptureOptions {
382
373
  }
383
374
 
384
375
  /**
385
- * A {@link SinkInterface} that also exposes the target terminal's {@link columns} width — the shape
386
- * {@link import('./factories.js').createServerSink} returns. It is a drop-in {@link SinkInterface}
387
- * (so a `Logger` / `Reporter` / `Spinner` / `Progress` takes it as `sink`) whose extra `columns`
388
- * getter lets a consumer size a `Reporter`'s layout to the live terminal. Its `styled` fact lets
389
- * the same consumer enable or disable its styler for the out target.
376
+ * Declares a {@link SinkInterface} that also exposes the `stdout` target's construction-time
377
+ * `styled` fact and the terminal's live or fixed {@link columns} width — the shape
378
+ * {@link import('./factories.js').createServerSink} returns.
390
379
  *
391
380
  * @remarks
392
- * - `styled` is the `out` target's construction-time fact. The sink handles `err` through its own
393
- * independently inferred fact because the two targets can differ.
394
- * - `columns` is a getter, re-read on every access so it reflects the CURRENT terminal width (a
381
+ * It is a drop-in {@link SinkInterface} (so a `Logger` / `Reporter` / `Spinner` / `Progress`
382
+ * takes it as `sink`) whose extra `columns` getter lets a consumer size a `Reporter`'s layout to
383
+ * the live terminal, and whose `styled` fact lets the same consumer enable or disable its styler
384
+ * for the `stdout` target.
385
+ *
386
+ * - `styled` is the `stdout` target's construction-time fact. The sink handles `stderr` through its
387
+ * own independently inferred fact because the two targets can differ.
388
+ * - `columns` is a getter, re-read on every access — so it reflects the current terminal width (a
395
389
  * resize is observed) unless a fixed `options.columns` was supplied, in which case it is constant.
396
390
  */
397
391
  export declare interface ServerSinkInterface extends SinkInterface {
@@ -400,71 +394,78 @@ export declare interface ServerSinkInterface extends SinkInterface {
400
394
  }
401
395
 
402
396
  /**
403
- * Options for {@link import('./factories.js').createServerSink} — all optional, so a bare
404
- * `createServerSink()` writes to the real process streams.
397
+ * Holds the options for {@link import('./factories.js').createServerSink} — the `stdout` and
398
+ * `stderr` targets, the `styled` override, the `environment` inference reads, and an explicit
399
+ * `columns` width. All are optional, so a bare `createServerSink()` writes to the real process
400
+ * streams.
405
401
  *
406
402
  * @remarks
407
- * - `out` — the stream `info` / `debug` (and an omitted level) are written to; defaults to
408
- * `process.stdout`. Any {@link StreamTargetInterface} is accepted, so a test injects a fake.
409
- * - `err` — the stream `error` / `warn` are written to; defaults to `process.stderr`.
403
+ * - `stdout` — the stream `info` / `debug` (and an omitted level) are written to; defaults to
404
+ * `process.stdout`, whose name it mirrors. Any {@link StreamTargetInterface} is accepted, so a
405
+ * test injects a fake.
406
+ * - `stderr` — the stream `error` / `warn` are written to; defaults to `process.stderr`, whose
407
+ * name it mirrors.
410
408
  * - `styled` — an explicit styling decision for both targets. When omitted, each target infers its
411
409
  * own fact from `FORCE_COLOR`, `NO_COLOR`, and `isTTY` at construction.
412
410
  * - `environment` — the environment used for inference; defaults to `process.env`.
413
411
  * - `columns` — an explicit width override for {@link ServerSinkInterface.columns}. When omitted,
414
- * the sink reads the live `out.columns` (so it tracks a terminal resize), falling back to
415
- * {@link import('./constants.js').DEFAULT_COLUMNS} when the out stream is not a TTY.
412
+ * the sink reads the live `stdout.columns` (so it tracks a terminal resize), falling back to
413
+ * {@link import('./constants.js').DEFAULT_COLUMNS} when the `stdout` stream is not a TTY.
416
414
  */
417
415
  export declare interface ServerSinkOptions {
418
- readonly out?: StreamTargetInterface;
419
- readonly err?: StreamTargetInterface;
416
+ readonly stdout?: StreamTargetInterface;
417
+ readonly stderr?: StreamTargetInterface;
420
418
  readonly styled?: boolean;
421
419
  readonly environment?: Readonly<Record<string, string | undefined>>;
422
420
  readonly columns?: number;
423
421
  }
424
422
 
425
423
  /**
426
- * Each {@link StreamLevel}'s {@link LogLevel} for the optional sink forward — the projection a
427
- * process capture routes through when writing an intercepted chunk to a
428
- * {@link import('@src/core').SinkInterface}
429
- * (`sink.write(text, STREAM_LEVEL_MAP[level])`). `stderr` is conventionally the error/diagnostic
430
- * stream → `error`; `stdout` is the normal output stream → `info`. The source of truth for the
431
- * stream-to-log projection (the server analogue of the core `CAPTURE_LEVEL_MAP`).
424
+ * Maps each {@link StreamLevel} to its {@link LogLevel} for the optional sink forward — the
425
+ * projection a process capture routes through when writing an intercepted chunk to a
426
+ * {@link import('@orkestrel/console').SinkInterface}. `sink.write(text, STREAM_LEVEL_MAP[level])` is the
427
+ * call this map backs. `stderr` is conventionally the error/diagnostic stream → `error`; `stdout`
428
+ * is the normal output stream → `info`. The source of truth for the stream-to-log projection (the
429
+ * server analogue of the core `CAPTURE_LEVEL_MAP`).
432
430
  */
433
431
  export declare const STREAM_LEVEL_MAP: Readonly<Record<StreamLevel, LogLevel>>;
434
432
 
435
433
  /**
436
- * The two process streams a {@link import('./types.js').ProcessCaptureInterface} can intercept, in
437
- * `stdout`-then-`stderr` order — the {@link StreamLevel} universe and the default configured set.
434
+ * Lists the process streams a {@link import('./types.js').ProcessCaptureInterface} can intercept,
435
+ * in `stdout`-then-`stderr` order — the {@link StreamLevel} universe and the default configured
436
+ * set.
438
437
  */
439
438
  export declare const STREAM_LEVELS: readonly StreamLevel[];
440
439
 
441
440
  /**
442
- * Which process stream a {@link CapturedChunk} came from — the "level" axis of the process-stream
443
- * {@link ProcessCaptureInterface}, the server analogue of the core `Capture`'s `CaptureLevel`.
441
+ * Names which process stream a {@link CapturedChunk} came from — `stdout` or `stderr`, the
442
+ * "level" axis of the process-stream {@link ProcessCaptureInterface} and the server analogue of
443
+ * the core `Capture`'s `CaptureLevel`.
444
444
  *
445
445
  * @remarks
446
- * DISTINCT from {@link import('@src/core').LogLevel}: a `StreamLevel` names the ORIGINATING process stream
446
+ * distinct from {@link import('@orkestrel/console').LogLevel}: a `StreamLevel` names the originating process stream
447
447
  * (`process.stdout` vs `process.stderr`), not a severity. It is a named value family (it indexes
448
- * {@link import('./constants.js').STREAM_LEVEL_MAP} to a {@link import('@src/core').LogLevel} for the optional sink
449
- * forward), never a binary toggle — so it stays a union (AGENTS §4.4).
448
+ * {@link import('./constants.js').STREAM_LEVEL_MAP} to a {@link import('@orkestrel/console').LogLevel} for the optional sink
449
+ * forward), never a binary toggle — so it stays a union.
450
450
  */
451
451
  export declare type StreamLevel = 'stdout' | 'stderr';
452
452
 
453
453
  /**
454
- * The minimal writable-stream shape the C-g server sink and process capture address — exactly the
455
- * slice of a Node `tty.WriteStream` / `process.stdout` they touch, and no more. A
456
- * {@link ServerSinkOptions} target and a {@link ProcessCaptureInterface}'s patched streams are
457
- * narrowed to this via {@link import('./helpers.js').isStreamTarget} (AGENTS §14 — narrow the
458
- * boundary, never `as`), so a test can drive either with a hand-built fake stream that never
459
- * touches the real `process` streams.
454
+ * Declares the minimal writable-stream shape the server sink and process capture address — exactly
455
+ * the slice of a Node `tty.WriteStream` / `process.stdout` they touch and no more.
460
456
  *
461
457
  * @remarks
458
+ * A {@link ServerSinkOptions} target and a {@link ProcessCaptureInterface}'s patched streams are
459
+ * narrowed to this through {@link import('./validators.js').isStreamTarget} (narrow the boundary,
460
+ * never `as`), so a test can
461
+ * drive either with a hand-built fake stream that never touches the real `process` streams.
462
+ *
462
463
  * - `write(text)` — the one required method: push a chunk to the stream, returning the host's
463
464
  * backpressure boolean (`false` when the kernel buffer is full). A `process` stream returns it;
464
465
  * a fake may return `void` (read as truthy / no backpressure).
465
466
  * - `isTTY` — present and `true` on a real terminal, absent / `false` when the stream is piped to a
466
467
  * file or another process. When no explicit styling override exists, the sink reads it at
467
- * construction to decide whether to keep ANSI or {@link import('@src/core').strip} it to clean
468
+ * construction to decide whether to keep ANSI or {@link import('@orkestrel/console').strip} it to clean
468
469
  * text.
469
470
  * - `columns` — the terminal width in character cells when the stream is a TTY, `undefined`
470
471
  * otherwise; the sink surfaces it as {@link ServerSinkInterface.columns} so a consumer can feed a
@@ -476,8 +477,30 @@ export declare interface StreamTargetInterface {
476
477
  readonly columns?: number;
477
478
  }
478
479
 
480
+ /**
481
+ * Names the completion callback `process.*.write` accepts as its last argument —
482
+ * `(error?) => void`, the Node `write` callback shape and the {@link StreamWriteFunction}
483
+ * companion.
484
+ *
485
+ * @remarks
486
+ * The capture wrapper forwards the callback verbatim to the mirror, so a caller's
487
+ * write-completion handler still fires.
488
+ */
479
489
  export declare type StreamWriteCallback = (error?: Error | null) => void;
480
490
 
491
+ /**
492
+ * Names the process-stream `write` method a {@link ProcessCaptureInterface} snapshots and swaps
493
+ * at the patch boundary — `NodeJS.WriteStream['write']` verbatim, the write-side analogue of
494
+ * {@link import('@orkestrel/console').ConsoleMethod}.
495
+ *
496
+ * @remarks
497
+ * The type is the overloaded
498
+ * `(chunk, encoding?, callback?) => boolean` of `process.stdout.write` / `process.stderr.write`.
499
+ * Using the canonical type rather than a hand-rolled approximation keeps snapshot and restore
500
+ * exact and lets the wrapper assign cleanly. A {@link StreamLevel} (`'stdout' | 'stderr'`) is
501
+ * itself the `process` property key, so `process[level]` indexes the matching `WriteStream`
502
+ * directly, with no lookup map.
503
+ */
481
504
  export declare type StreamWriteFunction = NodeJS.WriteStream['write'];
482
505
 
483
506
  export { }