@orkestrel/console 0.0.10 → 0.0.12

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.
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # @orkestrel/console
2
2
 
3
3
  A unified output-control system for the `@orkestrel` line — one
4
- environment-agnostic engine composing five concerns over a shared substrate:
4
+ environment-agnostic engine composing style, logging, reporting, capture, and
5
+ animation over a shared substrate:
5
6
  a **style engine** (`Styler` + `ANSIRenderer`, style as data), **structured
6
7
  logging** (`Logger`, `LoggerManager`), **narrative reporting** (`Reporter`),
7
8
  **console & stream capture** (`Capture`, `ProcessCapture`), and **live
@@ -25,27 +26,27 @@ npm install @orkestrel/console
25
26
  The same code retargets to any environment by swapping the `sink`:
26
27
 
27
28
  ```ts
28
- import { createLogger, createReporter, createSpinner } from '@src/core'
29
+ import { Logger, Reporter, Spinner } from '@orkestrel/console'
29
30
 
30
- const logger = createLogger({ name: 'http', level: 'info' }) // ANSI to the console by default
31
+ const logger = new Logger({ name: 'http', level: 'info' }) // ANSI to the console by default
31
32
  logger.info('request', { method: 'GET', path: '/' }) // a styled, leveled line + an `entry` event
32
33
  logger.emitter.on('entry', (record) => archive(record)) // the transport seam — file / JSON / remote
33
34
 
34
- const reporter = createReporter()
35
+ const reporter = new Reporter()
35
36
  reporter.section('Build')
36
37
  reporter.step('bundling', { index: 2, total: 5 }) // [2/5] bundling
37
38
  reporter.status('success', 'built in 1.2s') // ✔ built in 1.2s
38
39
 
39
- const spinner = createSpinner({ message: 'deploying' })
40
+ const spinner = new Spinner({ message: 'deploying' })
40
41
  spinner.start() // a self-driving glyph cycle, `\r`-redrawn by an overwrite-capable sink
41
- spinner.success('deployed') // ✔ deployed — the timer cleared, the line committed
42
+ spinner.succeed('deployed') // ✔ deployed — the timer cleared, the line committed
42
43
  ```
43
44
 
44
45
  Style is data — a `Style` is a frozen record rendered through a swappable
45
46
  `RendererInterface` (`ANSIRenderer` by default):
46
47
 
47
48
  ```ts
48
- import { createStyler } from '@src/core'
49
+ import { createStyler } from '@orkestrel/console'
49
50
 
50
51
  const styler = createStyler()
51
52
  console.log(styler.red.bold('hi')) // renders through the injected renderer
@@ -54,9 +55,9 @@ console.log(styler.red.bold('hi')) // renders through the injected renderer
54
55
  Take control of `console.*` on the read side with `Capture`:
55
56
 
56
57
  ```ts
57
- import { createCapture } from '@src/core'
58
+ import { Capture } from '@orkestrel/console'
58
59
 
59
- const capture = createCapture({ mirror: true })
60
+ const capture = new Capture({ mirror: true })
60
61
  capture.start()
61
62
  console.log('hello')
62
63
  capture.messages() // [{ level: 'log', text: 'hello', time: ... }]
@@ -64,24 +65,25 @@ capture.stop()
64
65
  ```
65
66
 
66
67
  On the server, `ProcessCapture` takes over the whole `process` output surface
67
- (direct `process.stdout`/`stderr` writes, not just `console.*`):
68
+ (direct `process.stdout`/`stderr` writes, not `console.*`):
68
69
 
69
70
  ```ts
70
- import { createProcessCapture } from '@src/server'
71
+ import { ProcessCapture } from '@orkestrel/console/server'
71
72
 
72
- const capture = createProcessCapture({ levels: ['stderr'], mirror: true })
73
+ const capture = new ProcessCapture({ levels: ['stderr'], mirror: true })
73
74
  capture.start()
74
75
  ```
75
76
 
76
77
  ## Guide
77
78
 
78
- See [guides/src/console.md](./guides/src/console.md) for the full documented
79
+ See [guides/console.md](./guides/console.md) for the full documented
79
80
  surface — styling, logging, reporting, capture, and animations.
80
81
 
81
82
  ## Package
82
83
 
83
- Published as three environment-scoped entry points per the `exports` field
84
- in `package.json`: `.` (the shared, environment-agnostic core engine, the
84
+ Published as the `.`, `./server`, and `./browser` environment-scoped entry
85
+ points per the `exports` field in `package.json`: `.` (the shared,
86
+ environment-agnostic core engine, the
85
87
  default ANSI renderer, and the console sink), `./server` (adds the server
86
88
  sink and `ProcessCapture`), and `./browser` (adds the browser sink
87
89
  translating ANSI to `console.log('%c…', css)`). Core and `./server` ship
@@ -3,22 +3,22 @@ import { Color } from '@orkestrel/console';
3
3
  import { SinkInterface } from '@orkestrel/console';
4
4
 
5
5
  /**
6
- * Translate an ANSI-styled string into a browser `console.log`-ready {@link ConsoleOutput} — a
6
+ * Translates an ANSI-styled string into a browser `console.log`-ready {@link ConsoleOutput} — a
7
7
  * `%c`-segmented format string and the parallel array of CSS declarations, so a DevTools console
8
- * renders the SAME styling a terminal would (the C-f sink calls `console[method](format, ...styles)`).
8
+ * renders the same styling a terminal would (the browser sink calls `console[method](format, ...styles)`).
9
9
  *
10
10
  * @remarks
11
11
  * - **SGR runs → `%c` segments.** The text is scanned for SGR sequences ({@link SGR_PATTERN} —
12
- * `ESC[…m`); each delimits a run. A run carrying VISIBLE text emits one `%c` directive plus that
12
+ * `ESC[…m`); each delimits a run. A run carrying visible text emits one `%c` directive plus that
13
13
  * text into `format` and the run's accumulated CSS into `styles`, so the browser switches style at
14
14
  * each `%c`. Foreground / background / attribute codes accumulate; the reset code (`0`, or a bare
15
- * `ESC[m`) clears the accumulated style back to none. A later color of the same channel REPLACES
15
+ * `ESC[m`) clears the accumulated style back to none. A later color of the same channel replaces
16
16
  * the earlier one; an attribute is added once. Non-SGR escapes (cursor / erase / OSC) are not style
17
17
  * and are left in the text verbatim.
18
- * - **`%`-safe.** Every LITERAL `%` in the text is doubled to `%%` so the console never treats it as
18
+ * - **`%`-safe.** Every literal `%` in the text is doubled to `%%` so the console never treats it as
19
19
  * a directive — only the `%c`s this function inserts are real directives. So `format`'s real `%c`
20
20
  * count always equals `styles.length`, and `console.log(format, ...styles)` lines up exactly.
21
- * - **Plain text short-circuits.** A string with NO SGR sequence yields `{ format: <escaped text>,
21
+ * - **Plain text short-circuits.** A string with no SGR sequence yields `{ format: <escaped text>,
22
22
  * styles: [] }` — no `%c`, no styles (the text is still `%`-escaped).
23
23
  * - **Partial palette.** A supplied palette overrides only its named colors and attributes. Every
24
24
  * omitted entry resolves through {@link COLOR_HEX} or {@link ATTRIBUTE_CSS}, so defaults and
@@ -40,11 +40,11 @@ import { SinkInterface } from '@orkestrel/console';
40
40
  export declare function ansiToConsole(text: string, palette?: BrowserPalette): ConsoleOutput;
41
41
 
42
42
  /**
43
- * Each text-{@link Attribute}'s SGR "on" number its equivalent CSS declaration — the browser
43
+ * Maps each text-{@link Attribute}'s SGR "on" number to its equivalent CSS declaration — the browser
44
44
  * counterpart to the terminal's SGR text effects (`bold` 1 → `font-weight:bold`, `dim` 2 →
45
45
  * `opacity:0.6`, `italic` 3 → `font-style:italic`, `underline` 4 → `text-decoration:underline`,
46
46
  * `inverse` 7 → best-effort, `strikethrough` 9 → `text-decoration:line-through`). Keyed by the SGR
47
- * NUMBER (derived from core's {@link ATTRIBUTE_CODES}) so the sink looks a parameter up directly
47
+ * number (derived from core's {@link ATTRIBUTE_CODES}) so the sink looks a parameter up directly
48
48
  * while scanning a run.
49
49
  *
50
50
  * @remarks
@@ -55,7 +55,7 @@ export declare function ansiToConsole(text: string, palette?: BrowserPalette): C
55
55
  export declare const ATTRIBUTE_CSS: Readonly<Record<number, string>>;
56
56
 
57
57
  /**
58
- * Partial browser CSS overrides for the core color and attribute axes. Omitted entries retain the
58
+ * Holds partial browser CSS overrides for the core color and attribute axes. Omitted entries retain the
59
59
  * built-in browser mappings, so one override changes only its named value.
60
60
  *
61
61
  * @remarks
@@ -69,7 +69,7 @@ export declare interface BrowserPalette {
69
69
  }
70
70
 
71
71
  /**
72
- * Options for {@link import('./factories.js').createBrowserSink}.
72
+ * Configures {@link import('./factories.js').createBrowserSink}.
73
73
  *
74
74
  * @remarks
75
75
  * `palette` partially overrides the browser's default color and attribute CSS mappings.
@@ -79,8 +79,8 @@ export declare interface BrowserSinkOptions {
79
79
  }
80
80
 
81
81
  /**
82
- * Each named {@link Color}'s hex value — the 16 standard terminal colors a browser DevTools
83
- * console renders the SAME {@link Color} names as. The source of truth for the BROWSER color
82
+ * Maps each named {@link Color} to its hex value — the 16 standard terminal colors a browser DevTools
83
+ * console renders the same {@link Color} names as. The source of truth for the browser color
84
84
  * axis: the ANSI renderer maps a `Color` name to an SGR number, and this maps the same name to
85
85
  * the CSS color the `%c` sink paints with, so a browser shows the same 16 colors a terminal does.
86
86
  *
@@ -91,20 +91,20 @@ export declare interface BrowserSinkOptions {
91
91
  export declare const COLOR_HEX: Readonly<Record<Exclude<Color, 'default'>, string>>;
92
92
 
93
93
  /**
94
- * The `console.log`-ready output {@link import('./helpers.js').ansiToConsole} produces from
94
+ * Represents the `console.log`-ready output {@link import('./helpers.js').ansiToConsole} produces from
95
95
  * an ANSI-styled string — a format string of `%c`-prefixed segments and the parallel array
96
96
  * of CSS declarations, ready to spread into a browser `console` call as
97
97
  * `console.log(format, ...styles)`.
98
98
  *
99
99
  * @remarks
100
100
  * - `format` — the text with each styled run prefixed by one `%c` directive (the directive
101
- * the browser console consumes to switch the active style) and every LITERAL `%` doubled to
101
+ * the browser console consumes to switch the active style) and every literal `%` doubled to
102
102
  * `%%` so it is not mistaken for a directive. A plain (no-ANSI) input yields the text
103
- * verbatim with NO `%c` and an empty `styles` (still `%`-escaped).
103
+ * verbatim with no `%c` and an empty `styles` (still `%`-escaped).
104
104
  * - `styles` — one CSS declaration string per `%c` in `format`, in order: the browser applies
105
105
  * `styles[n]` from the n-th `%c` onward. Each entry is the accumulated style for that run
106
- * (an SGR reset clears it back to `''`). `format`'s `%c` count always equals `styles.length`,
107
- * so the spread `console.log(format, ...styles)` lines up exactly.
106
+ * (an SGR reset clears it back to the empty declaration string). `format`'s `%c` count always
107
+ * equals `styles.length`, so the spread `console.log(format, ...styles)` lines up exactly.
108
108
  */
109
109
  export declare interface ConsoleOutput {
110
110
  readonly format: string;
@@ -112,10 +112,10 @@ export declare interface ConsoleOutput {
112
112
  }
113
113
 
114
114
  /**
115
- * Create the browser `%c` {@link SinkInterface} — the C-f browser output backend. `write(text, level?)`
115
+ * Creates the browser `%c` {@link SinkInterface} — the browser output backend. `write(text, level?)`
116
116
  * translates the ANSI-styled `text` into a browser `console` call (`console[method](format, ...styles)`)
117
- * via {@link ansiToConsole}, so a DevTools console renders the SAME styling a terminal does. Drop it in
118
- * as a logger / reporter / spinner sink (`createLogger({ sink: createBrowserSink() })`) to retarget the
117
+ * through {@link ansiToConsole}, so a DevTools console renders the same styling a terminal does. Drop it in
118
+ * as a logger / reporter / spinner sink (`new Logger({ sink: createBrowserSink() })`) to retarget the
119
119
  * core output to the browser console with no change to the core.
120
120
  *
121
121
  * @param options - See {@link BrowserSinkOptions}
@@ -127,38 +127,39 @@ export declare interface ConsoleOutput {
127
127
  * — pure, total, and `%`-safe), so the styling survives the trip to a console that can't render ANSI.
128
128
  * `options.palette` supplies partial named color and attribute overrides to that translation.
129
129
  * - **Routes by level.** `error` → `console.error`, `warn` → `console.warn`, every other level (and an
130
- * omitted level) → `console.log` — the SAME routing as core's `createConsoleSink`, so a logger's level
131
- * reaches the matching DevTools stream.
130
+ * omitted level) → `console.log` — the same routing as core's `createConsoleSink`, so a logger's level
131
+ * reaches the matching DevTools stream. Both call the one
132
+ * {@link import('@src/core').selectWriter} leaf, which is what keeps them identical.
132
133
  * - **Animation degrade (locked).** A browser console cannot overwrite a line, so a `text` beginning with
133
- * a carriage return `\r` (a spinner / progress redraw) has the leading `\r` STRIPPED and is written as a
134
- * fresh, non-overwriting line — the locked browser degrade. Only a LEADING `\r` is stripped; an interior
134
+ * a carriage return `\r` (a spinner / progress redraw) has the leading `\r` stripped and is written as a
135
+ * fresh, non-overwriting line — the locked browser degrade. Only a leading `\r` is stripped; an interior
135
136
  * one is left to the console.
136
- * - **Snapshotted — no capture loop.** It captures `console.log` / `console.warn` / `console.error` AT
137
- * CREATION and writes through those references, so a later `Capture` that PATCHES `console.*` can never
138
- * feed this sink's output back into itself (the no-capture-loop principle, AGENTS / the core sink's
139
- * precedent). Create the sink (or the logger) BEFORE installing a capture.
137
+ * - **Snapshotted — no capture loop.** It captures `console.log` / `console.warn` / `console.error` at
138
+ * creation and writes through those references, so a later `Capture` that patches `console.*` can never
139
+ * feed this sink's output back into itself (the no-capture-loop principle, following the core
140
+ * sink's precedent). Create the sink (or the logger) before installing a capture.
140
141
  *
141
142
  * @example
142
143
  * ```ts
143
- * import { createLogger } from '@src/core'
144
- * import { createBrowserSink } from '@src/browser'
144
+ * import { Logger } from '@orkestrel/console'
145
+ * import { createBrowserSink } from '@orkestrel/console/browser'
145
146
  *
146
- * const logger = createLogger({ name: 'app', sink: createBrowserSink() })
147
+ * const logger = new Logger({ name: 'app', sink: createBrowserSink() })
147
148
  * logger.error('boom') // → console.error('%c…', 'color:#cd0000;…') in DevTools
148
149
  * ```
149
150
  */
150
151
  export declare function createBrowserSink(options?: BrowserSinkOptions): SinkInterface;
151
152
 
152
153
  /**
153
- * The browser console directive that switches the active style — one `%c` prefixes every styled run
154
+ * Names the browser console directive that switches the active style — one `%c` prefixes every styled run
154
155
  * in the {@link import('./types.js').ConsoleOutput} format string, consuming the next entry of the
155
156
  * parallel CSS array. The single source of truth for the directive token.
156
157
  */
157
158
  export declare const DIRECTIVE = "%c";
158
159
 
159
160
  /**
160
- * Double every literal `%` in `text` to `%%` — the `%`-escape that keeps a browser console from
161
- * reading a stray `%` (e.g. in `50%` or `%s`) as a format directive. The single escape the
161
+ * Doubles every literal `%` in `text` to `%%` — the `%`-escape that keeps a browser console from
162
+ * reading a stray `%` (for example in `50%` or `%s`) as a format directive. The single escape the
162
163
  * {@link ansiToConsole} translation applies to every text segment before assembling the format
163
164
  * string (so only the `%c`s it inserts are real directives).
164
165
  *
@@ -173,56 +174,57 @@ export declare const DIRECTIVE = "%c";
173
174
  export declare function escapePercent(text: string): string;
174
175
 
175
176
  /**
176
- * Parse an SGR parameter list (the `;`-separated numeric string captured by {@link SGR_PATTERN})
177
- * into its numeric codes — `'1;31'` → `[1, 31]`. An EMPTY list (a bare `ESC[m`) yields `[0]`, since
178
- * the SGR spec treats a parameterless sequence as a reset; an empty field within a list (`'1;;4'`)
179
- * likewise counts as a `0` reset, matching the spec.
177
+ * Walks an SGR parameter list (the `;`-separated numeric string captured by {@link SGR_PATTERN})
178
+ * and returns its numeric codes — `'1;31'` → `[1, 31]`. It is total: every input yields a code
179
+ * list. An empty list (a bare `ESC[m`) yields `[0]`, because the SGR spec treats a parameterless
180
+ * sequence as a reset; an empty field within a list (`'1;;4'`) likewise counts as a `0` reset,
181
+ * matching the spec, and a non-numeric field yields `NaN`, which the caller then ignores.
180
182
  *
181
183
  * @param parameters - The raw `;`-separated parameter string (the regex capture)
182
- * @returns The parsed SGR codes (a parameterless / empty field becoming `0`)
184
+ * @returns The SGR codes found (a parameterless / empty field becoming `0`)
183
185
  *
184
186
  * @example
185
187
  * ```ts
186
- * parseParameters('1;31') // [1, 31]
187
- * parseParameters('') // [0]
188
+ * scanParameters('1;31') // [1, 31]
189
+ * scanParameters('') // [0]
188
190
  * ```
189
191
  */
190
- export declare function parseParameters(parameters: string): readonly number[];
192
+ export declare function scanParameters(parameters: string): readonly number[];
191
193
 
192
194
  /**
193
- * Matches one SGR sequence (`ESC[ <params> m`) and CAPTURES its `;`-separated numeric parameters —
194
- * the subset of ANSI {@link import('@src/core').strip} cares about that carries STYLE (color /
195
+ * Matches one SGR sequence (`ESC[ <params> m`) and captures its `;`-separated numeric parameters —
196
+ * the subset of ANSI {@link import('@src/core').strip} cares about that carries style (color /
195
197
  * attribute / reset), as opposed to cursor / erase / OSC sequences. Global, so the scanner walks
196
198
  * every SGR run in a string; built from core's {@link ESC} so no control-character literal appears
197
199
  * in source (the codebase idiom). The capture group is the parameter list (`''` for a bare `ESC[m`,
198
200
  * which the spec treats as a reset).
199
201
  *
200
202
  * @remarks
201
- * A global `RegExp` carries a mutable `lastIndex`; a scan builds a FRESH `RegExp` from this one's
203
+ * A global `RegExp` carries a mutable `lastIndex`; a scan builds a fresh `RegExp` from this one's
202
204
  * `source` + `flags` rather than reuse this instance, so concurrent scans never collide. This is the
203
205
  * canonical definition, not a shared scanner.
204
206
  */
205
207
  export declare const SGR_PATTERN: RegExp;
206
208
 
207
209
  /**
208
- * The immutable accumulator {@link import('./helpers.js').ansiToConsole} carries across a run while
210
+ * Represents the immutable accumulator {@link import('./helpers.js').ansiToConsole} carries across a run while
209
211
  * translating SGR codes to CSS — a single `foreground` and `background` declaration (each channel
210
- * REPLACEABLE by a later color of the same channel) plus an ordered, de-duplicated list of attribute
211
- * declarations. An SGR reset empties all three; {@link import('./helpers.js').ansiToConsole} folds
212
- * it into the `;`-joined CSS string a run emits.
212
+ * replaceable by a later color of the same channel) plus an ordered, de-duplicated list of attribute
213
+ * declarations. An SGR reset drops both channels and empties the list;
214
+ * {@link import('./helpers.js').ansiToConsole} folds it into the `;`-joined CSS string a run emits.
213
215
  *
214
216
  * @remarks
215
217
  * Each SGR sequence produces a new frozen value; earlier run snapshots never drift when a later
216
- * sequence changes a channel. A channel holds the FULL CSS declaration (`'color:#cd0000'`, not a
217
- * bare hex), or `''` when unset.
218
- * - `foreground` — the current `color:<hex>` declaration, or `''` (unset / post-reset).
219
- * - `background` — the current `background:<hex>` declaration, or `''`.
218
+ * sequence changes a channel. A channel holds the full CSS declaration (`'color:#cd0000'`, not a
219
+ * bare hex), and is absent when unset.
220
+ * - `foreground` — the current `color:<hex>` declaration; absent when unset or after a reset.
221
+ * - `background` — the current `background:<hex>` declaration; absent on the same terms.
220
222
  * - `attributes` — the active text-effect declarations in insertion order (`'font-weight:bold'`, …),
221
223
  * each present at most once.
222
224
  */
223
225
  export declare interface StyleAccumulator {
224
- readonly foreground: string;
225
- readonly background: string;
226
+ readonly foreground?: string;
227
+ readonly background?: string;
226
228
  readonly attributes: readonly string[];
227
229
  }
228
230
 
@@ -1,8 +1,8 @@
1
- import { ATTRIBUTES, ATTRIBUTE_CODES, BACKGROUND_CODES, COLORS, ESC, FOREGROUND_CODES, RESET_CODE } from "../core/index.js";
1
+ import { ATTRIBUTES, ATTRIBUTE_CODES, BACKGROUND_CODES, COLORS, ESC, FOREGROUND_CODES, RESET_CODE, selectWriter } from "../core/index.js";
2
2
  //#region src/browser/constants.ts
3
3
  /**
4
- * Each named {@link Color}'s hex value — the 16 standard terminal colors a browser DevTools
5
- * console renders the SAME {@link Color} names as. The source of truth for the BROWSER color
4
+ * Maps each named {@link Color} to its hex value — the 16 standard terminal colors a browser DevTools
5
+ * console renders the same {@link Color} names as. The source of truth for the browser color
6
6
  * axis: the ANSI renderer maps a `Color` name to an SGR number, and this maps the same name to
7
7
  * the CSS color the `%c` sink paints with, so a browser shows the same 16 colors a terminal does.
8
8
  *
@@ -29,11 +29,11 @@ var COLOR_HEX = Object.freeze({
29
29
  brightWhite: "#ffffff"
30
30
  });
31
31
  /**
32
- * Each text-{@link Attribute}'s SGR "on" number its equivalent CSS declaration — the browser
32
+ * Maps each text-{@link Attribute}'s SGR "on" number to its equivalent CSS declaration — the browser
33
33
  * counterpart to the terminal's SGR text effects (`bold` 1 → `font-weight:bold`, `dim` 2 →
34
34
  * `opacity:0.6`, `italic` 3 → `font-style:italic`, `underline` 4 → `text-decoration:underline`,
35
35
  * `inverse` 7 → best-effort, `strikethrough` 9 → `text-decoration:line-through`). Keyed by the SGR
36
- * NUMBER (derived from core's {@link ATTRIBUTE_CODES}) so the sink looks a parameter up directly
36
+ * number (derived from core's {@link ATTRIBUTE_CODES}) so the sink looks a parameter up directly
37
37
  * while scanning a run.
38
38
  *
39
39
  * @remarks
@@ -50,21 +50,21 @@ var ATTRIBUTE_CSS = Object.freeze({
50
50
  [ATTRIBUTE_CODES.strikethrough]: "text-decoration:line-through"
51
51
  });
52
52
  /**
53
- * The browser console directive that switches the active style — one `%c` prefixes every styled run
53
+ * Names the browser console directive that switches the active style — one `%c` prefixes every styled run
54
54
  * in the {@link import('./types.js').ConsoleOutput} format string, consuming the next entry of the
55
55
  * parallel CSS array. The single source of truth for the directive token.
56
56
  */
57
57
  var DIRECTIVE = "%c";
58
58
  /**
59
- * Matches one SGR sequence (`ESC[ <params> m`) and CAPTURES its `;`-separated numeric parameters —
60
- * the subset of ANSI {@link import('@src/core').strip} cares about that carries STYLE (color /
59
+ * Matches one SGR sequence (`ESC[ <params> m`) and captures its `;`-separated numeric parameters —
60
+ * the subset of ANSI {@link import('@src/core').strip} cares about that carries style (color /
61
61
  * attribute / reset), as opposed to cursor / erase / OSC sequences. Global, so the scanner walks
62
62
  * every SGR run in a string; built from core's {@link ESC} so no control-character literal appears
63
63
  * in source (the codebase idiom). The capture group is the parameter list (`''` for a bare `ESC[m`,
64
64
  * which the spec treats as a reset).
65
65
  *
66
66
  * @remarks
67
- * A global `RegExp` carries a mutable `lastIndex`; a scan builds a FRESH `RegExp` from this one's
67
+ * A global `RegExp` carries a mutable `lastIndex`; a scan builds a fresh `RegExp` from this one's
68
68
  * `source` + `flags` rather than reuse this instance, so concurrent scans never collide. This is the
69
69
  * canonical definition, not a shared scanner.
70
70
  */
@@ -72,22 +72,22 @@ var SGR_PATTERN = new RegExp(`${ESC}\\[([0-9;]*)m`, "g");
72
72
  //#endregion
73
73
  //#region src/browser/helpers.ts
74
74
  /**
75
- * Translate an ANSI-styled string into a browser `console.log`-ready {@link ConsoleOutput} — a
75
+ * Translates an ANSI-styled string into a browser `console.log`-ready {@link ConsoleOutput} — a
76
76
  * `%c`-segmented format string and the parallel array of CSS declarations, so a DevTools console
77
- * renders the SAME styling a terminal would (the C-f sink calls `console[method](format, ...styles)`).
77
+ * renders the same styling a terminal would (the browser sink calls `console[method](format, ...styles)`).
78
78
  *
79
79
  * @remarks
80
80
  * - **SGR runs → `%c` segments.** The text is scanned for SGR sequences ({@link SGR_PATTERN} —
81
- * `ESC[…m`); each delimits a run. A run carrying VISIBLE text emits one `%c` directive plus that
81
+ * `ESC[…m`); each delimits a run. A run carrying visible text emits one `%c` directive plus that
82
82
  * text into `format` and the run's accumulated CSS into `styles`, so the browser switches style at
83
83
  * each `%c`. Foreground / background / attribute codes accumulate; the reset code (`0`, or a bare
84
- * `ESC[m`) clears the accumulated style back to none. A later color of the same channel REPLACES
84
+ * `ESC[m`) clears the accumulated style back to none. A later color of the same channel replaces
85
85
  * the earlier one; an attribute is added once. Non-SGR escapes (cursor / erase / OSC) are not style
86
86
  * and are left in the text verbatim.
87
- * - **`%`-safe.** Every LITERAL `%` in the text is doubled to `%%` so the console never treats it as
87
+ * - **`%`-safe.** Every literal `%` in the text is doubled to `%%` so the console never treats it as
88
88
  * a directive — only the `%c`s this function inserts are real directives. So `format`'s real `%c`
89
89
  * count always equals `styles.length`, and `console.log(format, ...styles)` lines up exactly.
90
- * - **Plain text short-circuits.** A string with NO SGR sequence yields `{ format: <escaped text>,
90
+ * - **Plain text short-circuits.** A string with no SGR sequence yields `{ format: <escaped text>,
91
91
  * styles: [] }` — no `%c`, no styles (the text is still `%`-escaped).
92
92
  * - **Partial palette.** A supplied palette overrides only its named colors and attributes. Every
93
93
  * omitted entry resolves through {@link COLOR_HEX} or {@link ATTRIBUTE_CSS}, so defaults and
@@ -108,11 +108,7 @@ var SGR_PATTERN = new RegExp(`${ESC}\\[([0-9;]*)m`, "g");
108
108
  */
109
109
  function ansiToConsole(text, palette) {
110
110
  const scanner = new RegExp(SGR_PATTERN.source, SGR_PATTERN.flags);
111
- let active = Object.freeze({
112
- foreground: "",
113
- background: "",
114
- attributes: Object.freeze([])
115
- });
111
+ let active = Object.freeze({ attributes: Object.freeze([]) });
116
112
  const segments = [];
117
113
  const styles = [];
118
114
  let cursor = 0;
@@ -128,19 +124,15 @@ function ansiToConsole(text, palette) {
128
124
  if (pending !== "") {
129
125
  segments.push(`%c${pending}`);
130
126
  const declarations = [...active.attributes];
131
- if (active.foreground !== "") declarations.push(active.foreground);
132
- if (active.background !== "") declarations.push(active.background);
127
+ if (active.foreground !== void 0) declarations.push(active.foreground);
128
+ if (active.background !== void 0) declarations.push(active.background);
133
129
  styles.push(declarations.join(";"));
134
130
  pending = "";
135
131
  }
136
132
  if (match === null) break;
137
- for (const code of parseParameters(match[1] ?? "")) {
133
+ for (const code of scanParameters(match[1] ?? "")) {
138
134
  if (code === RESET_CODE) {
139
- active = Object.freeze({
140
- foreground: "",
141
- background: "",
142
- attributes: Object.freeze([])
143
- });
135
+ active = Object.freeze({ attributes: Object.freeze([]) });
144
136
  continue;
145
137
  }
146
138
  const foreground = COLORS.find((color) => FOREGROUND_CODES[color] === code);
@@ -177,8 +169,8 @@ function ansiToConsole(text, palette) {
177
169
  };
178
170
  }
179
171
  /**
180
- * Double every literal `%` in `text` to `%%` — the `%`-escape that keeps a browser console from
181
- * reading a stray `%` (e.g. in `50%` or `%s`) as a format directive. The single escape the
172
+ * Doubles every literal `%` in `text` to `%%` — the `%`-escape that keeps a browser console from
173
+ * reading a stray `%` (for example in `50%` or `%s`) as a format directive. The single escape the
182
174
  * {@link ansiToConsole} translation applies to every text segment before assembling the format
183
175
  * string (so only the `%c`s it inserts are real directives).
184
176
  *
@@ -194,31 +186,32 @@ function escapePercent(text) {
194
186
  return text.replace(/%/g, "%%");
195
187
  }
196
188
  /**
197
- * Parse an SGR parameter list (the `;`-separated numeric string captured by {@link SGR_PATTERN})
198
- * into its numeric codes — `'1;31'` → `[1, 31]`. An EMPTY list (a bare `ESC[m`) yields `[0]`, since
199
- * the SGR spec treats a parameterless sequence as a reset; an empty field within a list (`'1;;4'`)
200
- * likewise counts as a `0` reset, matching the spec.
189
+ * Walks an SGR parameter list (the `;`-separated numeric string captured by {@link SGR_PATTERN})
190
+ * and returns its numeric codes — `'1;31'` → `[1, 31]`. It is total: every input yields a code
191
+ * list. An empty list (a bare `ESC[m`) yields `[0]`, because the SGR spec treats a parameterless
192
+ * sequence as a reset; an empty field within a list (`'1;;4'`) likewise counts as a `0` reset,
193
+ * matching the spec, and a non-numeric field yields `NaN`, which the caller then ignores.
201
194
  *
202
195
  * @param parameters - The raw `;`-separated parameter string (the regex capture)
203
- * @returns The parsed SGR codes (a parameterless / empty field becoming `0`)
196
+ * @returns The SGR codes found (a parameterless / empty field becoming `0`)
204
197
  *
205
198
  * @example
206
199
  * ```ts
207
- * parseParameters('1;31') // [1, 31]
208
- * parseParameters('') // [0]
200
+ * scanParameters('1;31') // [1, 31]
201
+ * scanParameters('') // [0]
209
202
  * ```
210
203
  */
211
- function parseParameters(parameters) {
204
+ function scanParameters(parameters) {
212
205
  if (parameters === "") return [RESET_CODE];
213
206
  return parameters.split(";").map((field) => field === "" ? RESET_CODE : Number(field));
214
207
  }
215
208
  //#endregion
216
209
  //#region src/browser/factories.ts
217
210
  /**
218
- * Create the browser `%c` {@link SinkInterface} — the C-f browser output backend. `write(text, level?)`
211
+ * Creates the browser `%c` {@link SinkInterface} — the browser output backend. `write(text, level?)`
219
212
  * translates the ANSI-styled `text` into a browser `console` call (`console[method](format, ...styles)`)
220
- * via {@link ansiToConsole}, so a DevTools console renders the SAME styling a terminal does. Drop it in
221
- * as a logger / reporter / spinner sink (`createLogger({ sink: createBrowserSink() })`) to retarget the
213
+ * through {@link ansiToConsole}, so a DevTools console renders the same styling a terminal does. Drop it in
214
+ * as a logger / reporter / spinner sink (`new Logger({ sink: createBrowserSink() })`) to retarget the
222
215
  * core output to the browser console with no change to the core.
223
216
  *
224
217
  * @param options - See {@link BrowserSinkOptions}
@@ -230,23 +223,24 @@ function parseParameters(parameters) {
230
223
  * — pure, total, and `%`-safe), so the styling survives the trip to a console that can't render ANSI.
231
224
  * `options.palette` supplies partial named color and attribute overrides to that translation.
232
225
  * - **Routes by level.** `error` → `console.error`, `warn` → `console.warn`, every other level (and an
233
- * omitted level) → `console.log` — the SAME routing as core's `createConsoleSink`, so a logger's level
234
- * reaches the matching DevTools stream.
226
+ * omitted level) → `console.log` — the same routing as core's `createConsoleSink`, so a logger's level
227
+ * reaches the matching DevTools stream. Both call the one
228
+ * {@link import('@src/core').selectWriter} leaf, which is what keeps them identical.
235
229
  * - **Animation degrade (locked).** A browser console cannot overwrite a line, so a `text` beginning with
236
- * a carriage return `\r` (a spinner / progress redraw) has the leading `\r` STRIPPED and is written as a
237
- * fresh, non-overwriting line — the locked browser degrade. Only a LEADING `\r` is stripped; an interior
230
+ * a carriage return `\r` (a spinner / progress redraw) has the leading `\r` stripped and is written as a
231
+ * fresh, non-overwriting line — the locked browser degrade. Only a leading `\r` is stripped; an interior
238
232
  * one is left to the console.
239
- * - **Snapshotted — no capture loop.** It captures `console.log` / `console.warn` / `console.error` AT
240
- * CREATION and writes through those references, so a later `Capture` that PATCHES `console.*` can never
241
- * feed this sink's output back into itself (the no-capture-loop principle, AGENTS / the core sink's
242
- * precedent). Create the sink (or the logger) BEFORE installing a capture.
233
+ * - **Snapshotted — no capture loop.** It captures `console.log` / `console.warn` / `console.error` at
234
+ * creation and writes through those references, so a later `Capture` that patches `console.*` can never
235
+ * feed this sink's output back into itself (the no-capture-loop principle, following the core
236
+ * sink's precedent). Create the sink (or the logger) before installing a capture.
243
237
  *
244
238
  * @example
245
239
  * ```ts
246
- * import { createLogger } from '@src/core'
247
- * import { createBrowserSink } from '@src/browser'
240
+ * import { Logger } from '@orkestrel/console'
241
+ * import { createBrowserSink } from '@orkestrel/console/browser'
248
242
  *
249
- * const logger = createLogger({ name: 'app', sink: createBrowserSink() })
243
+ * const logger = new Logger({ name: 'app', sink: createBrowserSink() })
250
244
  * logger.error('boom') // → console.error('%c…', 'color:#cd0000;…') in DevTools
251
245
  * ```
252
246
  */
@@ -256,18 +250,14 @@ function createBrowserSink(options) {
256
250
  const error = console.error.bind(console);
257
251
  return { write(text, level) {
258
252
  const { format, styles } = ansiToConsole(text.startsWith("\r") ? text.slice(1) : text, options?.palette);
259
- if (level === "error") {
260
- error(format, ...styles);
261
- return;
262
- }
263
- if (level === "warn") {
264
- warn(format, ...styles);
265
- return;
266
- }
267
- log(format, ...styles);
253
+ selectWriter(level, {
254
+ log,
255
+ warn,
256
+ error
257
+ })(format, ...styles);
268
258
  } };
269
259
  }
270
260
  //#endregion
271
- export { ATTRIBUTE_CSS, COLOR_HEX, DIRECTIVE, SGR_PATTERN, ansiToConsole, createBrowserSink, escapePercent, parseParameters };
261
+ export { ATTRIBUTE_CSS, COLOR_HEX, DIRECTIVE, SGR_PATTERN, ansiToConsole, createBrowserSink, escapePercent, scanParameters };
272
262
 
273
263
  //# sourceMappingURL=index.js.map