@lewishowles/cli-style 0.5.0 → 0.6.0

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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.6.0 - 2026-07-03
6
+
7
+ ### Added
8
+
9
+ - Added result-state support to `row()` and `cli_style_row`, so label/value rows can render failed, warning, success, or other status cues while keeping column alignment.
10
+ - Added the `span()` inline emphasis primitive and `cli_style_span` Bash helper for highlighting commands, files, or values inside existing line renderers such as `hint()`.
11
+ - Added `span` support to `cli-style render` and the public JavaScript export.
12
+
13
+ ### Changed
14
+
15
+ - Reworked the README around integration paths, available renderers, primitive data shapes, patterns, options, profiles, result types, and gallery commands.
16
+
5
17
  ## 0.5.0 - 2026-07-02
6
18
 
7
19
  ### Added
package/README.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # CLI style
2
2
 
3
- Shared terminal output styles for tools, diagnostics, and package CLIs.
3
+ Shared terminal output styles for local tools, diagnostics, agent-facing reports, and package CLIs.
4
+
5
+ Use it directly from JavaScript, or call the same renderers from Bash and Python through the `cli-style` binary.
4
6
 
5
7
  ## Requirements
6
8
 
7
9
  - Bun and Node.js 20 or newer for JavaScript projects and local binary builds
8
- - The standalone binary for Bash or Python projects that should not install Bun
10
+ - The standalone `cli-style` binary for Bash or Python projects that should not install Bun
9
11
 
10
12
  ## Install
11
13
 
@@ -21,7 +23,29 @@ Bash and Python projects call the `cli-style` binary through thin adapters. If t
21
23
  export PATH="/path/to/cli-style/bin:$PATH"
22
24
  ```
23
25
 
24
- ## JavaScript
26
+ Build a standalone binary when a repo should not need Bun at runtime:
27
+
28
+ ```bash
29
+ bun run build:binary
30
+ bun run smoke:binary
31
+ ```
32
+
33
+ Create a binary install tarball:
34
+
35
+ ```bash
36
+ bun run package:binary
37
+ tar -tzf dist/release/cli-style-*.tar.gz
38
+ ```
39
+
40
+ The package contains `bin/cli-style` and `adapters/`.
41
+
42
+ ## Choose an integration
43
+
44
+ All renderers return text. JavaScript imports functions directly. CLI, Bash, and Python integrations pass JSON-compatible data to a named renderer.
45
+
46
+ ### JavaScript
47
+
48
+ Create the `ui` instance at the CLI entrypoint, then pass `ui.options` to renderers. That keeps flag, environment, stream, colour, Unicode, and width detection in one place.
25
49
 
26
50
  ```js
27
51
  import { createCliStyle, status } from "@lewishowles/cli-style";
@@ -42,54 +66,9 @@ ui.print(output);
42
66
 
43
67
  Renderer methods return strings. `ui.print()`, `ui.write()`, and CLI commands handle stdout and stderr.
44
68
 
45
- Create the `ui` instance at the CLI entrypoint, then pass it to command handlers. That keeps flag, environment, stream, colour, Unicode, and width detection in one place.
46
-
47
- ## Reporter
48
-
49
- Use a reporter when a script needs grouped output rather than one row per file.
50
-
51
- ```js
52
- import { createReporter } from "@lewishowles/cli-style";
53
-
54
- const reporter = createReporter({
55
- colour: false,
56
- });
57
-
58
- reporter.divider("Project setup", "Claude + Codex");
59
- reporter.group(
60
- "Agent scripts",
61
- [
62
- {
63
- label: "project-diagnostics.py",
64
- result: "unchanged",
65
- },
66
- {
67
- label: "repo-context.py",
68
- result: "unchanged",
69
- },
70
- ],
71
- {
72
- summary: "2 already linked",
73
- },
74
- );
75
- reporter.status("success", "Done.");
76
-
77
- console.log(reporter.render());
78
- ```
79
-
80
- Use `reporter.divider()` for major phases. Use `reporter.section()` when you want a normal `info` status row inside a phase. Panels are for framed content blocks, not headings.
81
-
82
- Groups can include verbose detail rows when a caller opts in:
83
-
84
- ```js
85
- const reporter = createReporter({
86
- verbose: process.env.AGENTS_VERBOSE === "1",
87
- });
88
- ```
89
-
90
- ## CLI
69
+ ### CLI render
91
70
 
92
- Use `cli-style render <renderer>` when a script needs rendered text from JSON input.
71
+ Use `cli-style render <renderer>` when a script needs rendered text from JSON input:
93
72
 
94
73
  ```bash
95
74
  cli-style render status --profile diagnostic <<'JSON'
@@ -101,35 +80,11 @@ cli-style render status --profile diagnostic <<'JSON'
101
80
  JSON
102
81
  ```
103
82
 
104
- ```bash
105
- cli-style render task-summary --plain <<'JSON'
106
- {
107
- "title": "Task summary",
108
- "completed": ["Added render command"],
109
- "remaining": ["Add adapters"]
110
- }
111
- JSON
112
- ```
83
+ Use renderer names from the tables below, for example `status`, `row`, `diagnostic-report`, or `task-summary`.
113
84
 
114
- `render` supports `--profile`, `--plain`, `--no-colour`, `--no-color`, `--no-unicode`, and `--width`. It rejects `--json`; JSON profile behaviour is for library calls and machine-readable commands.
85
+ ### Bash
115
86
 
116
- Build a local standalone binary when a repo should not need Bun at runtime:
117
-
118
- ```bash
119
- bun run build:binary
120
- bun run smoke:binary
121
- ```
122
-
123
- Create a binary install tarball:
124
-
125
- ```bash
126
- bun run package:binary
127
- tar -tzf dist/release/cli-style-*.tar.gz
128
- ```
129
-
130
- The package contains `bin/cli-style` and `adapters/`. Install it into a repo-local tools directory, then call that repo-local `bin/cli-style` from scripts.
131
-
132
- ## Bash
87
+ Source the Bash adapter, then use convenience helpers for common primitive renderers:
133
88
 
134
89
  ```bash
135
90
  source "$(cli-style adapter-path bash)"
@@ -139,17 +94,13 @@ detail="/path/to/project"
139
94
 
140
95
  cli_style_status success "$label" "$detail" --profile diagnostic
141
96
  cli_style_row "Workspace" "$detail" --plain
142
- cli_style_hint "Run \"check\" before release" --plain
97
+ cli_style_row "Bundle" "is 22.3 KB, above the 12.0 KB budget" failed --plain
98
+ command="$(cli_style_span "npm run docs:readme" info --plain)"
99
+ cli_style_hint "Run $command before release" --plain
143
100
  cli_style_divider "Project setup" --plain
144
101
  ```
145
102
 
146
- Use the convenience functions for dynamic Bash values. They build JSON internally, so callers do not need to escape quotes, backslashes, or paths by hand.
147
-
148
- `cli_style_status` accepts `type`, `label`, `detail`, then render flags. Pass an empty detail when you only need a label:
149
-
150
- ```bash
151
- cli_style_status success "Done" "" --plain
152
- ```
103
+ The convenience functions build JSON internally, so callers do not need to escape quotes, backslashes, or paths by hand.
153
104
 
154
105
  Set `CLI_STYLE_BIN` when `cli-style` is not on `PATH`:
155
106
 
@@ -157,20 +108,25 @@ Set `CLI_STYLE_BIN` when `cli-style` is not on `PATH`:
157
108
  CLI_STYLE_BIN="/path/to/cli-style" cli_style_status success "Build passed" "" --plain
158
109
  ```
159
110
 
160
- Keep `cli_style_render` for literal or prebuilt JSON:
111
+ Use `cli_style_render` for literal or prebuilt JSON, especially for structured patterns:
161
112
 
162
113
  ```bash
163
- cli_style_render status --plain <<'JSON'
114
+ cli_style_render diagnostic-report --profile diagnostic <<'JSON'
164
115
  {
165
- "type": "success",
166
- "label": "Build passed"
116
+ "title": "Release checks",
117
+ "checks": [
118
+ {
119
+ "name": "README",
120
+ "result": "success"
121
+ }
122
+ ]
167
123
  }
168
124
  JSON
169
125
  ```
170
126
 
171
- ## Python
127
+ ### Python
172
128
 
173
- Add the adapter directory to `PYTHONPATH`, then import the helper:
129
+ Add the adapter directory to `PYTHONPATH`, then import the generic `render()` helper:
174
130
 
175
131
  ```bash
176
132
  export PYTHONPATH="$(cli-style adapter-path python):$PYTHONPATH"
@@ -206,6 +162,308 @@ output = render(
206
162
  )
207
163
  ```
208
164
 
165
+ ## Available renderers
166
+
167
+ Renderer names are stable for `cli-style render`, `cli_style_render`, and Python `render()`. JavaScript uses camel-case function names.
168
+
169
+ | Renderer | JavaScript | Bash helper | Purpose |
170
+ | --------------- | ------------------------------------- | ------------------------------------ | --------------------------------------------------------------- |
171
+ | `status` | `status(resultType, detail, options)` | `cli_style_status type label detail` | Result line with symbol, label, and detail. |
172
+ | `row` | `row(label, value, options)` | `cli_style_row label value [result]` | Aligned label/value row, optionally marked with a result state. |
173
+ | `span` | `span(value, tone, options)` | `cli_style_span value [tone]` | Inline colour or weight for a word, command, file, or value. |
174
+ | `hint` | `hint(message, options)` | `cli_style_hint message` | Informational hint line. |
175
+ | `divider` | `divider(options)` | `cli_style_divider label` | Section divider with an optional label. |
176
+ | `chip` | `chip(label, tone, options)` | Use `cli_style_render` | Compact labelled state. |
177
+ | `panel` | `panel(options)` | Use `cli_style_render` | Framed content block for grouped output. |
178
+ | `table` | `table(options)` | Use `cli_style_render` | Tabular records with narrow-width fallback. |
179
+ | `progress-bar` | `progressBar(options)` | Use `cli_style_render` | Single progress value with numeric text. |
180
+ | `bar-chart` | `barChart(options)` | Use `cli_style_render` | Multi-row bar chart. |
181
+ | `step` | `step(label, state, options)` | Use `cli_style_render` | One workflow step. |
182
+ | `step-progress` | `stepProgress(options)` | Use `cli_style_render` | Numbered workflow steps. |
183
+ | `empty-state` | `emptyState(title, detail, options)` | Use `cli_style_render` | Empty result message. |
184
+ | `error-block` | `errorBlock(title, lines, options)` | Use `cli_style_render` | Structured error block. |
185
+
186
+ Python uses `render("<renderer>", data, ...)` for every renderer.
187
+
188
+ ## Primitive data shapes
189
+
190
+ Use these shapes with `cli-style render`, `cli_style_render`, or Python `render()`. JavaScript callers pass the listed function arguments directly.
191
+
192
+ ### `status`
193
+
194
+ ```json
195
+ {
196
+ "type": "success",
197
+ "label": "Build passed",
198
+ "detail": "184 tests"
199
+ }
200
+ ```
201
+
202
+ - `type`: one of the result types listed below
203
+ - `label`: optional label override
204
+ - `detail`: optional text after the label
205
+
206
+ ### `row`
207
+
208
+ ```json
209
+ {
210
+ "label": "Bundle",
211
+ "value": "is 22.3 KB, above the 12.0 KB budget",
212
+ "result": "failed",
213
+ "labelWidth": 8
214
+ }
215
+ ```
216
+
217
+ - `label`: left-hand label
218
+ - `value`: right-hand value
219
+ - `result`: optional result type for a symbol and tone
220
+ - `labelWidth`: optional minimum label width
221
+ - `labelColour`, `valueColour`, `separator`: optional presentation overrides
222
+
223
+ ### `span`
224
+
225
+ ```json
226
+ {
227
+ "value": "npm run docs:readme",
228
+ "tone": "info"
229
+ }
230
+ ```
231
+
232
+ - `value`: inline text to emphasise
233
+ - `tone`: optional tone, defaulting to `info`
234
+ - `weight`: optional ANSI style, defaulting to `bold`
235
+
236
+ ### `hint`
237
+
238
+ ```json
239
+ {
240
+ "message": "Run npm run docs:readme before release"
241
+ }
242
+ ```
243
+
244
+ Pre-render a `span` when only part of the hint needs emphasis.
245
+
246
+ ### Structured primitives
247
+
248
+ | Renderer | Main fields |
249
+ | --------------- | -------------------------------------- |
250
+ | `divider` | `label`, `dividerWidth`, `character` |
251
+ | `chip` | `label`, `tone` |
252
+ | `panel` | `title`, `lines`, `tone`, `panelWidth` |
253
+ | `table` | `columns`, `rows`, `width` |
254
+ | `progress-bar` | `value`, `max`, `barWidth`, `tone` |
255
+ | `bar-chart` | `rows`, `barWidth` |
256
+ | `step` | `label`, `state` |
257
+ | `step-progress` | `steps`, `current` |
258
+ | `empty-state` | `title`, `detail` |
259
+ | `error-block` | `title`, `lines` |
260
+
261
+ For visual examples, run `cli-style gallery --section primitives`.
262
+
263
+ ## Patterns
264
+
265
+ Patterns are built-in composite renderers for common report shapes. They use the same lower-level primitives internally, but accept structured objects so callers do not need to assemble panels, statuses, tables, and rows by hand.
266
+
267
+ | Renderer | JavaScript | Purpose | Main fields |
268
+ | --------------------- | ------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------ |
269
+ | `diagnostic-report` | `diagnosticReport(report, options)` | Aggregate check report. | `title`, `checks`, `findings`, `skippedChecks`, `nextActions` |
270
+ | `command-result` | `commandResult(result, options)` | Single command outcome with metadata. | `title`, `result`, `summary`, `command`, `exitCode`, `duration`, `details` |
271
+ | `audit-finding` | `auditFinding(finding, options)` | Single audit finding. | `title`, `result`, `finding`, `location`, `evidence`, `recommendation`, `references` |
272
+ | `task-summary` | `taskSummary(summary, options)` | Completed and remaining task summary. | `title`, `task`, `result`, `summary`, `completed`, `remaining` |
273
+ | `agent-transcript` | `agentTranscript(transcript, options)` | User, agent, and tool transcript. | `title`, `entries` |
274
+ | `confirmation-result` | `confirmationResult(confirmation, options)` | Confirmation outcome with detail. | `title`, `state`, `action`, `item`, `detail` |
275
+ | `next-step-block` | `nextStepBlock(nextStep, options)` | Next-step guidance. | `title`, `next`, `reason`, `commands`, `alternatives` |
276
+ | `compact-data-table` | `compactDataTable(data, options)` | Titled compact data table. | `title`, `summary`, `columns`, `rows` |
277
+
278
+ For visual examples, run `cli-style gallery --section patterns`.
279
+
280
+ ### Pattern example
281
+
282
+ JavaScript:
283
+
284
+ ```js
285
+ import { createCliStyle, diagnosticReport } from "@lewishowles/cli-style";
286
+
287
+ const ui = createCliStyle({
288
+ argv: process.argv.slice(2),
289
+ env: process.env,
290
+ stdout: process.stdout,
291
+ });
292
+
293
+ const output = diagnosticReport(
294
+ {
295
+ title: "Release checks",
296
+ checks: [
297
+ {
298
+ name: "README",
299
+ result: "success",
300
+ },
301
+ {
302
+ detail: "object-BmsQavd_.js over budget",
303
+ name: "Package size",
304
+ result: "failed",
305
+ },
306
+ ],
307
+ findings: [
308
+ {
309
+ message: "dist/object-BmsQavd_.js is 22.3 KB, above the 12.0 KB budget",
310
+ result: "failed",
311
+ },
312
+ ],
313
+ nextActions: ["Reduce package size, or raise the budget with a release-note-worthy reason."],
314
+ },
315
+ ui.options,
316
+ );
317
+
318
+ ui.print(output);
319
+ ```
320
+
321
+ CLI:
322
+
323
+ ```bash
324
+ cli-style render diagnostic-report --profile diagnostic <<'JSON'
325
+ {
326
+ "title": "Release checks",
327
+ "checks": [
328
+ {
329
+ "name": "README",
330
+ "result": "success"
331
+ },
332
+ {
333
+ "name": "Package size",
334
+ "result": "failed",
335
+ "detail": "object-BmsQavd_.js over budget"
336
+ }
337
+ ],
338
+ "findings": [
339
+ {
340
+ "result": "failed",
341
+ "message": "dist/object-BmsQavd_.js is 22.3 KB, above the 12.0 KB budget"
342
+ }
343
+ ],
344
+ "nextActions": [
345
+ "Reduce package size, or raise the budget with a release-note-worthy reason."
346
+ ]
347
+ }
348
+ JSON
349
+ ```
350
+
351
+ Python:
352
+
353
+ ```python
354
+ from cli_style import render
355
+
356
+ output = render(
357
+ "diagnostic-report",
358
+ {
359
+ "title": "Release checks",
360
+ "checks": [
361
+ {
362
+ "name": "README",
363
+ "result": "success",
364
+ },
365
+ {
366
+ "name": "Package size",
367
+ "result": "failed",
368
+ "detail": "object-BmsQavd_.js over budget",
369
+ },
370
+ ],
371
+ "findings": [
372
+ {
373
+ "result": "failed",
374
+ "message": "dist/object-BmsQavd_.js is 22.3 KB, above the 12.0 KB budget",
375
+ },
376
+ ],
377
+ "nextActions": [
378
+ "Reduce package size, or raise the budget with a release-note-worthy reason.",
379
+ ],
380
+ },
381
+ profile="diagnostic",
382
+ )
383
+ ```
384
+
385
+ ## Reporter
386
+
387
+ Use a reporter when a JavaScript script needs grouped output built over time.
388
+
389
+ ```js
390
+ import { createReporter } from "@lewishowles/cli-style";
391
+
392
+ const reporter = createReporter({
393
+ colour: false,
394
+ });
395
+
396
+ reporter.divider("Project setup", "Claude + Codex");
397
+ reporter.group(
398
+ "Agent scripts",
399
+ [
400
+ {
401
+ label: "project-diagnostics.py",
402
+ result: "unchanged",
403
+ },
404
+ {
405
+ label: "repo-context.py",
406
+ result: "unchanged",
407
+ },
408
+ ],
409
+ {
410
+ summary: "2 already linked",
411
+ },
412
+ );
413
+ reporter.status("success", "Done.");
414
+
415
+ console.log(reporter.render());
416
+ ```
417
+
418
+ Use `reporter.divider()` for major phases. Use `reporter.section()` for a normal `info` status row inside a phase. Panels are for framed content blocks, not headings.
419
+
420
+ Groups can include verbose detail rows when a caller opts in:
421
+
422
+ ```js
423
+ const reporter = createReporter({
424
+ verbose: process.env.AGENTS_VERBOSE === "1",
425
+ });
426
+ ```
427
+
428
+ ## Options and flags
429
+
430
+ ### JavaScript options
431
+
432
+ Most renderers accept the same output options:
433
+
434
+ | Option | Purpose |
435
+ | --------- | ------------------------------------------------------------------------- |
436
+ | `profile` | Output profile: `human`, `agent`, `diagnostic`, `ci`, `plain`, or `json`. |
437
+ | `colour` | Whether ANSI colour should be applied. |
438
+ | `unicode` | Whether Unicode symbols should be used. |
439
+ | `width` | Available output width for renderers that adapt layout. |
440
+
441
+ Use `createCliStyle()` to resolve these options from `argv`, `env`, `stdout`, CI, TTY, `NO_COLOR`, and terminal capability.
442
+
443
+ ### CLI and Bash flags
444
+
445
+ | Flag | Purpose |
446
+ | ---------------------------- | -------------------------------------- |
447
+ | `--profile <name>` | Render with a named profile. |
448
+ | `--plain` | Disable colour and Unicode decoration. |
449
+ | `--no-colour` / `--no-color` | Disable ANSI colour. |
450
+ | `--no-unicode` | Use ASCII symbols. |
451
+ | `--width <columns>` | Override detected output width. |
452
+
453
+ `cli-style render` rejects `--json`. JSON profile behaviour is for library calls and machine-readable commands.
454
+
455
+ ### Python options
456
+
457
+ | Argument | Purpose |
458
+ | ------------ | ------------------------------------------------ |
459
+ | `profile` | Render with a named profile. |
460
+ | `width` | Override detected output width. |
461
+ | `plain` | Disable colour and Unicode decoration. |
462
+ | `no_colour` | Disable ANSI colour. |
463
+ | `no_unicode` | Use ASCII symbols. |
464
+ | `binary` | Path or command name for the `cli-style` binary. |
465
+ | `extra_args` | Extra CLI arguments to pass through. |
466
+
209
467
  ## Profiles
210
468
 
211
469
  | Profile | Behaviour |
@@ -217,11 +475,31 @@ output = render(
217
475
  | `plain` | No ANSI, no Unicode dependency, simple text layout. |
218
476
  | `json` | Machine-readable only; library calls return structured data unchanged. |
219
477
 
478
+ ## Result types and tones
479
+
480
+ Result types drive symbols, labels, priority, and semantic colour:
481
+
482
+ | Result | Label | Unicode | ASCII | Tone |
483
+ | ----------- | --------- | ------- | ----- | --------- |
484
+ | `success` | Success | `✓` | `OK` | `success` |
485
+ | `info` | Info | `→` | `>` | `info` |
486
+ | `warning` | Warning | `⚠` | `!` | `warning` |
487
+ | `partial` | Partial | `◐` | `!` | `warning` |
488
+ | `unchanged` | Unchanged | `↪` | `-` | `muted` |
489
+ | `skipped` | Skipped | `–` | `-` | `muted` |
490
+ | `unknown` | Unknown | `?` | `?` | `muted` |
491
+ | `failed` | Failed | `×` | `x` | `danger` |
492
+
493
+ Tone values used by public APIs include `info`, `success`, `warning`, `danger`, and `muted`.
494
+
220
495
  ## Gallery
221
496
 
497
+ Use the gallery to review current renderer output:
498
+
222
499
  ```bash
223
500
  cli-style gallery
224
501
  cli-style gallery no-colour
502
+ cli-style gallery --section primitives
225
503
  cli-style gallery --section patterns
226
504
  cli-style gallery --fixture audit-finding
227
505
  cli-style gallery --profile agent
@@ -229,4 +507,4 @@ cli-style gallery --width 64
229
507
  cli-style gallery --variants
230
508
  ```
231
509
 
232
- Use the gallery to review current renderer output. Use `--interactive` to select a section or fixture with `fzf` when it is installed.
510
+ Use `--interactive` to select a section or fixture with `fzf` when it is installed.
@@ -116,24 +116,57 @@ cli_style_status() {
116
116
  # Row label.
117
117
  # @param {string} value
118
118
  # Row value.
119
+ # @param {string} result
120
+ # Optional result type before render flags.
119
121
  # @param {string} ...
120
122
  # Render flags passed through to `cli-style render`.
121
123
  cli_style_row() {
122
124
  local label="${1:-}"
123
125
  local value="${2:-}"
126
+ local result=""
124
127
  local json
125
128
 
126
- if (($# >= 2)); then
129
+ if (($# >= 3)) && [[ "${3:-}" != --* ]]; then
130
+ result="${3:-}"
131
+ shift 3
132
+ elif (($# >= 2)); then
127
133
  shift 2
128
134
  else
129
135
  set --
130
136
  fi
131
137
 
132
- json="{\"label\":$(cli_style_json_string "$label"),\"value\":$(cli_style_json_string "$value")}"
138
+ json="{\"label\":$(cli_style_json_string "$label"),\"value\":$(cli_style_json_string "$value"),\"result\":$(cli_style_json_string "$result")}"
133
139
 
134
140
  cli_style_render_json row "$json" "$@"
135
141
  }
136
142
 
143
+ # Render an inline span from Bash string values.
144
+ #
145
+ # @param {string} value
146
+ # Span text.
147
+ # @param {string} tone
148
+ # Optional colour tone.
149
+ # @param {string} ...
150
+ # Render flags passed through to `cli-style render`.
151
+ cli_style_span() {
152
+ local value="${1:-}"
153
+ local tone="info"
154
+ local json
155
+
156
+ if (($# >= 2)) && [[ "${2:-}" != --* ]]; then
157
+ tone="${2:-}"
158
+ shift 2
159
+ elif (($# >= 1)); then
160
+ shift
161
+ else
162
+ set --
163
+ fi
164
+
165
+ json="{\"value\":$(cli_style_json_string "$value"),\"tone\":$(cli_style_json_string "$tone")}"
166
+
167
+ cli_style_render_json span "$json" "$@"
168
+ }
169
+
137
170
  # Render a divider from a Bash string value.
138
171
  #
139
172
  # @param {string} label
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lewishowles/cli-style",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Shared terminal output styles for Lewis Howles tools, diagnostics, and package CLIs.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -13,6 +13,7 @@ import { emptyState, errorBlock, hint } from "../primitives/feedback.js";
13
13
  import { panel } from "../primitives/panel.js";
14
14
  import { progressBar } from "../primitives/progress-bar.js";
15
15
  import { row } from "../primitives/row.js";
16
+ import { span } from "../primitives/span.js";
16
17
  import { status } from "../primitives/status.js";
17
18
  import { step, stepProgress } from "../primitives/step-progress.js";
18
19
  import { table } from "../primitives/table.js";
@@ -72,6 +73,11 @@ const renderers = {
72
73
  ...input,
73
74
  ...options,
74
75
  }),
76
+ span: (input, options) =>
77
+ span(input.value, input.tone, {
78
+ ...input,
79
+ ...options,
80
+ }),
75
81
  status: (input, options) =>
76
82
  status(input.type, input.detail, {
77
83
  ...input,
@@ -14,6 +14,7 @@ import { emptyState, errorBlock, hint } from "../primitives/feedback.js";
14
14
  import { panel } from "../primitives/panel.js";
15
15
  import { progressBar } from "../primitives/progress-bar.js";
16
16
  import { row } from "../primitives/row.js";
17
+ import { span } from "../primitives/span.js";
17
18
  import { status } from "../primitives/status.js";
18
19
  import { step, stepProgress, stepStates } from "../primitives/step-progress.js";
19
20
  import { table } from "../primitives/table.js";
@@ -676,7 +677,7 @@ function renderPrimitives(options) {
676
677
  }),
677
678
  "",
678
679
  "Feedback",
679
- hint("tone: info", options),
680
+ hint(`Run ${span("npm run docs:readme", "info", options)}`, options),
680
681
  emptyState("No results", "tone: muted", options),
681
682
  errorBlock("Failed", ["tone: danger"], {
682
683
  ...options,
@@ -709,6 +710,11 @@ function renderPrimitives(options) {
709
710
  ...options,
710
711
  labelWidth: 8,
711
712
  }),
713
+ row("Bundle", "is 22.3 KB, above the 12.0 KB budget", {
714
+ ...options,
715
+ labelWidth: 8,
716
+ result: resultTypes.FAILED,
717
+ }),
712
718
  ].join("\n");
713
719
  }
714
720
 
package/src/index.js CHANGED
@@ -35,6 +35,7 @@ export { emptyState, errorBlock, hint } from "./primitives/feedback.js";
35
35
  export { panel } from "./primitives/panel.js";
36
36
  export { progressBar } from "./primitives/progress-bar.js";
37
37
  export { row } from "./primitives/row.js";
38
+ export { span } from "./primitives/span.js";
38
39
  export { status } from "./primitives/status.js";
39
40
  export { step, stepProgress, stepStates } from "./primitives/step-progress.js";
40
41
  export { table } from "./primitives/table.js";
@@ -1,4 +1,5 @@
1
1
  import { foreground } from "../formatters/ansi.js";
2
+ import { getResultSymbol, getResultToken } from "../theme/results.js";
2
3
 
3
4
  // Row labels use the same subdued tone as table headers by default.
4
5
  const defaultLabelColour = "muted";
@@ -21,6 +22,8 @@ const defaultSeparator = " ";
21
22
  * Colour token for the label.
22
23
  * @param {number} options.labelWidth
23
24
  * Minimum label width before the separator.
25
+ * @param {string} options.result
26
+ * Optional semantic result state for the row.
24
27
  * @param {string} options.separator
25
28
  * Text between label and value.
26
29
  * @param {string} options.valueColour
@@ -29,8 +32,10 @@ const defaultSeparator = " ";
29
32
  * Rendered row.
30
33
  */
31
34
  export function row(label, value = "", options = {}) {
32
- const labelWidth = Math.max(label.length, options.labelWidth ?? 0);
33
- const labelText = label.padEnd(labelWidth, " ");
35
+ const resultToken = resolveResultToken(options);
36
+ const displayLabel = renderDisplayLabel(label, resultToken, options);
37
+ const labelWidth = Math.max(displayLabel.length, options.labelWidth ?? 0);
38
+ const labelText = displayLabel.padEnd(labelWidth, " ");
34
39
  const renderedLabel = renderLabel(labelText, options);
35
40
 
36
41
  if (value === "") {
@@ -43,6 +48,44 @@ export function row(label, value = "", options = {}) {
43
48
  return `${renderedLabel}${separator}${renderedValue}`;
44
49
  }
45
50
 
51
+ /**
52
+ * Resolve the row's optional result token.
53
+ *
54
+ * @param {object} options
55
+ * Rendering options.
56
+ * @returns {object|null}
57
+ * Result token when a row result is provided.
58
+ */
59
+ function resolveResultToken(options) {
60
+ if (typeof options.result !== "string" || options.result === "") {
61
+ return null;
62
+ }
63
+
64
+ return getResultToken(options.result);
65
+ }
66
+
67
+ /**
68
+ * Render the visible row label, including any result symbol.
69
+ *
70
+ * @param {string} label
71
+ * Label text to render.
72
+ * @param {object|null} resultToken
73
+ * Optional result token.
74
+ * @param {object} options
75
+ * Rendering options.
76
+ * @returns {string}
77
+ * Label text with any result prefix.
78
+ */
79
+ function renderDisplayLabel(label, resultToken, options) {
80
+ if (resultToken === null) {
81
+ return label;
82
+ }
83
+
84
+ const symbol = getResultSymbol(options.result, options);
85
+
86
+ return `${symbol} ${label}`;
87
+ }
88
+
46
89
  /**
47
90
  * Render a row label.
48
91
  *
@@ -58,7 +101,7 @@ function renderLabel(label, options) {
58
101
  return label;
59
102
  }
60
103
 
61
- return foreground(label, options.labelColour ?? defaultLabelColour, options);
104
+ return foreground(label, getLabelColour(options), options);
62
105
  }
63
106
 
64
107
  /**
@@ -72,9 +115,55 @@ function renderLabel(label, options) {
72
115
  * Rendered value.
73
116
  */
74
117
  function renderValue(value, options) {
75
- if (options.colour !== true || options.valueColour === undefined) {
118
+ if (options.colour !== true) {
119
+ return value;
120
+ }
121
+
122
+ const colour = getValueColour(options);
123
+
124
+ if (colour === undefined) {
76
125
  return value;
77
126
  }
78
127
 
79
- return foreground(value, options.valueColour, options);
128
+ return foreground(value, colour, options);
129
+ }
130
+
131
+ /**
132
+ * Resolve the colour token for the row label.
133
+ *
134
+ * @param {object} options
135
+ * Rendering options.
136
+ * @returns {string}
137
+ * Colour token for the label.
138
+ */
139
+ function getLabelColour(options) {
140
+ if (options.labelColour !== undefined) {
141
+ return options.labelColour;
142
+ }
143
+
144
+ if (typeof options.result === "string" && options.result !== "") {
145
+ return getResultToken(options.result).tone;
146
+ }
147
+
148
+ return defaultLabelColour;
149
+ }
150
+
151
+ /**
152
+ * Resolve the colour token for the row value.
153
+ *
154
+ * @param {object} options
155
+ * Rendering options.
156
+ * @returns {string|undefined}
157
+ * Colour token for the value.
158
+ */
159
+ function getValueColour(options) {
160
+ if (options.valueColour !== undefined) {
161
+ return options.valueColour;
162
+ }
163
+
164
+ if (typeof options.result === "string" && options.result !== "") {
165
+ return getResultToken(options.result).tone;
166
+ }
167
+
168
+ return undefined;
80
169
  }
@@ -0,0 +1,33 @@
1
+ import { foreground, style } from "../formatters/ansi.js";
2
+
3
+ // Inline spans use the same informational tone as hints by default.
4
+ const defaultTone = "info";
5
+
6
+ // Inline emphasis should remain visible without forcing a block-level renderer.
7
+ const defaultWeight = "bold";
8
+
9
+ /**
10
+ * Render an inline text span.
11
+ *
12
+ * @param {string} value
13
+ * Text to render.
14
+ * @param {string} tone
15
+ * Colour tone for the span.
16
+ * @param {object} options
17
+ * Rendering options.
18
+ * @param {boolean} options.colour
19
+ * Whether ANSI colour should be applied.
20
+ * @param {string} options.weight
21
+ * Optional ANSI text weight.
22
+ * @returns {string}
23
+ * Rendered span.
24
+ */
25
+ export function span(value = "", tone = defaultTone, options = {}) {
26
+ if (options.colour !== true) {
27
+ return value;
28
+ }
29
+
30
+ const renderedValue = foreground(value, tone, options);
31
+
32
+ return style(renderedValue, options.weight ?? defaultWeight, options);
33
+ }