@lewishowles/cli-style 0.5.0 → 0.7.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
@@ -1,6 +1,29 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.7.0 - 2026-07-05
4
+
5
+ ### Added
6
+
7
+ - Added Python adapter convenience functions for scalar pattern usage: `command_result`, `audit_finding`, `task_summary`, `confirmation_result`, and `next_step_block`.
8
+ - Added Python adapter convenience functions for common primitives: `status`, `row`, `span`, `hint`, and `divider`.
9
+ - Added Bash adapter convenience functions for scalar pattern usage: `cli_style_command_result`, `cli_style_audit_finding`, `cli_style_task_summary`, `cli_style_confirmation_result`, and `cli_style_next_step_block`.
10
+ - Added Bash adapter helpers for JSON string arrays and integer/null fields, so pattern wrappers can safely pass single-item list fields and exit codes.
11
+
12
+ ### Changed
13
+
14
+ - Extended the README renderer table with a Python helper column covering all 20 renderers, including convenience functions and `render()` fallbacks for aggregate patterns.
15
+
16
+ ## 0.6.0 - 2026-07-03
17
+
18
+ ### Added
19
+
20
+ - 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.
21
+ - 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()`.
22
+ - Added `span` support to `cli-style render` and the public JavaScript export.
23
+
24
+ ### Changed
25
+
26
+ - Reworked the README around integration paths, available renderers, primitive data shapes, patterns, options, profiles, result types, and gallery commands.
4
27
 
5
28
  ## 0.5.0 - 2026-07-02
6
29
 
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,170 +66,428 @@ 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.
69
+ ### CLI render
46
70
 
47
- ## Reporter
71
+ Use `cli-style render <renderer>` when a script needs rendered text from JSON input:
48
72
 
49
- Use a reporter when a script needs grouped output rather than one row per file.
73
+ ```bash
74
+ cli-style render status --profile diagnostic <<'JSON'
75
+ {
76
+ "type": "success",
77
+ "label": "Build passed",
78
+ "detail": "184 tests"
79
+ }
80
+ JSON
81
+ ```
50
82
 
51
- ```js
52
- import { createReporter } from "@lewishowles/cli-style";
83
+ Use renderer names from the tables below, for example `status`, `row`, `diagnostic-report`, or `task-summary`.
53
84
 
54
- const reporter = createReporter({
55
- colour: false,
56
- });
85
+ ### Bash
57
86
 
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.");
87
+ Source the Bash adapter, then use convenience helpers for common primitive and per-script pattern renderers:
76
88
 
77
- console.log(reporter.render());
78
- ```
89
+ ```bash
90
+ source "$(cli-style adapter-path bash)"
79
91
 
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.
92
+ label='Build "passed"'
93
+ detail="/path/to/project"
81
94
 
82
- Groups can include verbose detail rows when a caller opts in:
95
+ cli_style_status success "$label" "$detail" --profile diagnostic
96
+ cli_style_row "Workspace" "$detail" --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
100
+ cli_style_divider "Project setup" --plain
83
101
 
84
- ```js
85
- const reporter = createReporter({
86
- verbose: process.env.AGENTS_VERBOSE === "1",
87
- });
102
+ cli_style_command_result success "Build passed" "bun run test:unit" 0 "1.2s" "184 tests" --plain
103
+ cli_style_task_summary partial "Adopt cli-style" "Bash wrappers added" "Updated adapter" "Update scripts" --plain
104
+ cli_style_confirmation_result confirmed "Publish release" "v0.6.0" "Tag push starts npm publish" --plain
105
+ cli_style_next_step_block "Update helpers scripts" "Wrappers are now available" "scripts/setup.sh --check" "" --plain
88
106
  ```
89
107
 
90
- ## CLI
108
+ The convenience functions build JSON internally, so callers do not need to escape quotes, backslashes, or paths by hand.
91
109
 
92
- Use `cli-style render <renderer>` when a script needs rendered text from JSON input.
110
+ Set `CLI_STYLE_BIN` when `cli-style` is not on `PATH`:
93
111
 
94
112
  ```bash
95
- cli-style render status --profile diagnostic <<'JSON'
96
- {
97
- "type": "success",
98
- "label": "Build passed",
99
- "detail": "184 tests"
100
- }
101
- JSON
113
+ CLI_STYLE_BIN="/path/to/cli-style" cli_style_status success "Build passed" "" --plain
102
114
  ```
103
115
 
116
+ Use `cli_style_render` for literal or prebuilt JSON, especially for aggregate patterns or multi-item pattern fields:
117
+
104
118
  ```bash
105
- cli-style render task-summary --plain <<'JSON'
119
+ cli_style_render diagnostic-report --profile diagnostic <<'JSON'
106
120
  {
107
- "title": "Task summary",
108
- "completed": ["Added render command"],
109
- "remaining": ["Add adapters"]
121
+ "title": "Release checks",
122
+ "checks": [
123
+ {
124
+ "name": "README",
125
+ "result": "success"
126
+ }
127
+ ]
110
128
  }
111
129
  JSON
112
130
  ```
113
131
 
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.
132
+ ### Python
115
133
 
116
- Build a local standalone binary when a repo should not need Bun at runtime:
134
+ Add the adapter directory to `PYTHONPATH`, then import the generic `render()` helper or convenience functions:
117
135
 
118
136
  ```bash
119
- bun run build:binary
120
- bun run smoke:binary
137
+ export PYTHONPATH="$(cli-style adapter-path python):$PYTHONPATH"
121
138
  ```
122
139
 
123
- Create a binary install tarball:
140
+ ```python
141
+ from cli_style import status, row, span, hint, divider
124
142
 
125
- ```bash
126
- bun run package:binary
127
- tar -tzf dist/release/cli-style-*.tar.gz
143
+ kwargs = {"binary": "./bin/cli-style.js", "plain": True}
144
+
145
+ print(status("success", "Build passed", "184 tests", **kwargs))
146
+ print(row("Workspace", "/path/to/project", **kwargs))
147
+ print(row("Bundle", "over budget", "failed", **kwargs))
148
+ command = span("npm run docs:readme", "info", **kwargs)
149
+ print(hint(f"Run {command} before release", **kwargs))
150
+ print(divider("Project setup", **kwargs))
128
151
  ```
129
152
 
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.
153
+ Convenience functions build the renderer payload internally, so callers pass plain Python strings without hand-building JSON.
131
154
 
132
- ## Bash
155
+ Pass `binary=` when `cli-style` is not on `PATH`. The generic `render()` helper remains for aggregate patterns or multi-item fields:
133
156
 
134
- ```bash
135
- source "$(cli-style adapter-path bash)"
157
+ ```python
158
+ from cli_style import render
136
159
 
137
- label='Build "passed"'
138
- detail="/path/to/project"
160
+ output = render(
161
+ "diagnostic-report",
162
+ {
163
+ "title": "Release checks",
164
+ "checks": [{"name": "README", "result": "success"}],
165
+ },
166
+ profile="diagnostic",
167
+ )
139
168
 
140
- cli_style_status success "$label" "$detail" --profile diagnostic
141
- cli_style_row "Workspace" "$detail" --plain
142
- cli_style_hint "Run \"check\" before release" --plain
143
- cli_style_divider "Project setup" --plain
169
+ print(output)
170
+ ```
171
+
172
+ ## Available renderers
173
+
174
+ Renderer names are stable for `cli-style render`, `cli_style_render`, and Python `render()`. JavaScript uses camel-case function names.
175
+
176
+ | Renderer | JavaScript | Bash helper | Python helper | Purpose |
177
+ | --------------------- | ------------------------------------- | ------------------------------------ | ---------------------------------- | --------------------------------------------------------------- |
178
+ | `status` | `status(resultType, detail, options)` | `cli_style_status type label detail` | `status(type, label, detail)` | Result line with symbol, label, and detail. |
179
+ | `row` | `row(label, value, options)` | `cli_style_row label value [result]` | `row(label, value, result)` | Aligned label/value row, optionally marked with a result state. |
180
+ | `span` | `span(value, tone, options)` | `cli_style_span value [tone]` | `span(value, tone)` | Inline colour or weight for a word, command, file, or value. |
181
+ | `hint` | `hint(message, options)` | `cli_style_hint message` | `hint(message)` | Informational hint line. |
182
+ | `divider` | `divider(options)` | `cli_style_divider label` | `divider(label)` | Section divider with an optional label. |
183
+ | `command-result` | `commandResult(options)` | `cli_style_command_result ...` | `command_result(...)` | Command execution outcome with exit code and duration. |
184
+ | `audit-finding` | `auditFinding(options)` | `cli_style_audit_finding ...` | `audit_finding(...)` | Structured audit finding with evidence and recommendation. |
185
+ | `task-summary` | `taskSummary(options)` | `cli_style_task_summary ...` | `task_summary(...)` | Task progress with completed and remaining items. |
186
+ | `confirmation-result` | `confirmationResult(options)` | `cli_style_confirmation_result ...` | `confirmation_result(...)` | Confirmation outcome with action and item. |
187
+ | `next-step-block` | `nextStepBlock(options)` | `cli_style_next_step_block ...` | `next_step_block(...)` | Next-step guidance with command and alternative. |
188
+ | `chip` | `chip(label, tone, options)` | Use `cli_style_render` | Use `render("chip", ...)` | Compact labelled state. |
189
+ | `panel` | `panel(options)` | Use `cli_style_render` | Use `render("panel", ...)` | Framed content block for grouped output. |
190
+ | `table` | `table(options)` | Use `cli_style_render` | Use `render("table", ...)` | Tabular records with narrow-width fallback. |
191
+ | `progress-bar` | `progressBar(options)` | Use `cli_style_render` | Use `render("progress-bar", ...)` | Single progress value with numeric text. |
192
+ | `bar-chart` | `barChart(options)` | Use `cli_style_render` | Use `render("bar-chart", ...)` | Multi-row bar chart. |
193
+ | `step` | `step(label, state, options)` | Use `cli_style_render` | Use `render("step", ...)` | One workflow step. |
194
+ | `step-progress` | `stepProgress(options)` | Use `cli_style_render` | Use `render("step-progress", ...)` | Numbered workflow steps. |
195
+ | `empty-state` | `emptyState(title, detail, options)` | Use `cli_style_render` | Use `render("empty-state", ...)` | Empty result message. |
196
+ | `error-block` | `errorBlock(title, lines, options)` | Use `cli_style_render` | Use `render("error-block", ...)` | Structured error block. |
197
+
198
+ Use `render("<renderer>", data, ...)` for aggregate patterns (`diagnostic-report`, `agent-transcript`, `compact-data-table`) or any renderer with multi-item fields.
199
+
200
+ ## Primitive data shapes
201
+
202
+ Use these shapes with `cli-style render`, `cli_style_render`, or Python `render()`. JavaScript callers pass the listed function arguments directly.
203
+
204
+ ### `status`
205
+
206
+ ```json
207
+ {
208
+ "type": "success",
209
+ "label": "Build passed",
210
+ "detail": "184 tests"
211
+ }
144
212
  ```
145
213
 
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.
214
+ - `type`: one of the result types listed below
215
+ - `label`: optional label override
216
+ - `detail`: optional text after the label
147
217
 
148
- `cli_style_status` accepts `type`, `label`, `detail`, then render flags. Pass an empty detail when you only need a label:
218
+ ### `row`
149
219
 
150
- ```bash
151
- cli_style_status success "Done" "" --plain
220
+ ```json
221
+ {
222
+ "label": "Bundle",
223
+ "value": "is 22.3 KB, above the 12.0 KB budget",
224
+ "result": "failed",
225
+ "labelWidth": 8
226
+ }
152
227
  ```
153
228
 
154
- Set `CLI_STYLE_BIN` when `cli-style` is not on `PATH`:
229
+ - `label`: left-hand label
230
+ - `value`: right-hand value
231
+ - `result`: optional result type for a symbol and tone
232
+ - `labelWidth`: optional minimum label width
233
+ - `labelColour`, `valueColour`, `separator`: optional presentation overrides
155
234
 
156
- ```bash
157
- CLI_STYLE_BIN="/path/to/cli-style" cli_style_status success "Build passed" "" --plain
235
+ ### `span`
236
+
237
+ ```json
238
+ {
239
+ "value": "npm run docs:readme",
240
+ "tone": "info"
241
+ }
158
242
  ```
159
243
 
160
- Keep `cli_style_render` for literal or prebuilt JSON:
244
+ - `value`: inline text to emphasise
245
+ - `tone`: optional tone, defaulting to `info`
246
+ - `weight`: optional ANSI style, defaulting to `bold`
161
247
 
162
- ```bash
163
- cli_style_render status --plain <<'JSON'
248
+ ### `hint`
249
+
250
+ ```json
164
251
  {
165
- "type": "success",
166
- "label": "Build passed"
252
+ "message": "Run npm run docs:readme before release"
167
253
  }
168
- JSON
169
254
  ```
170
255
 
171
- ## Python
256
+ Pre-render a `span` when only part of the hint needs emphasis.
257
+
258
+ ### Structured primitives
259
+
260
+ | Renderer | Main fields |
261
+ | --------------- | -------------------------------------- |
262
+ | `divider` | `label`, `dividerWidth`, `character` |
263
+ | `chip` | `label`, `tone` |
264
+ | `panel` | `title`, `lines`, `tone`, `panelWidth` |
265
+ | `table` | `columns`, `rows`, `width` |
266
+ | `progress-bar` | `value`, `max`, `barWidth`, `tone` |
267
+ | `bar-chart` | `rows`, `barWidth` |
268
+ | `step` | `label`, `state` |
269
+ | `step-progress` | `steps`, `current` |
270
+ | `empty-state` | `title`, `detail` |
271
+ | `error-block` | `title`, `lines` |
272
+
273
+ For visual examples, run `cli-style gallery --section primitives`.
274
+
275
+ ## Patterns
276
+
277
+ 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.
278
+
279
+ | Renderer | JavaScript | Purpose | Main fields |
280
+ | --------------------- | ------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------ |
281
+ | `diagnostic-report` | `diagnosticReport(report, options)` | Aggregate check report. | `title`, `checks`, `findings`, `skippedChecks`, `nextActions` |
282
+ | `command-result` | `commandResult(result, options)` | Single command outcome with metadata. | `title`, `result`, `summary`, `command`, `exitCode`, `duration`, `details` |
283
+ | `audit-finding` | `auditFinding(finding, options)` | Single audit finding. | `title`, `result`, `finding`, `location`, `evidence`, `recommendation`, `references` |
284
+ | `task-summary` | `taskSummary(summary, options)` | Completed and remaining task summary. | `title`, `task`, `result`, `summary`, `completed`, `remaining` |
285
+ | `agent-transcript` | `agentTranscript(transcript, options)` | User, agent, and tool transcript. | `title`, `entries` |
286
+ | `confirmation-result` | `confirmationResult(confirmation, options)` | Confirmation outcome with detail. | `title`, `state`, `action`, `item`, `detail` |
287
+ | `next-step-block` | `nextStepBlock(nextStep, options)` | Next-step guidance. | `title`, `next`, `reason`, `commands`, `alternatives` |
288
+ | `compact-data-table` | `compactDataTable(data, options)` | Titled compact data table. | `title`, `summary`, `columns`, `rows` |
172
289
 
173
- Add the adapter directory to `PYTHONPATH`, then import the helper:
290
+ For visual examples, run `cli-style gallery --section patterns`.
291
+
292
+ The Bash adapter also exposes scalar convenience wrappers for per-script usage:
293
+
294
+ | Bash helper | Renderer | Arguments before render flags |
295
+ | ------------------------------- | --------------------- | -------------------------------------------------------------------------- |
296
+ | `cli_style_command_result` | `command-result` | `result`, `summary`, `command`, `exit_code`, `duration`, `detail` |
297
+ | `cli_style_audit_finding` | `audit-finding` | `result`, `finding`, `location`, `recommendation`, `evidence`, `reference` |
298
+ | `cli_style_task_summary` | `task-summary` | `result`, `task`, `summary`, `completed`, `remaining` |
299
+ | `cli_style_confirmation_result` | `confirmation-result` | `state`, `action`, `item`, `detail` |
300
+ | `cli_style_next_step_block` | `next-step-block` | `next`, `reason`, `command`, `alternative` |
301
+
302
+ Pass an empty string for optional positional fields you want to skip. Use `cli_style_render` or `cli_style_render_json` for aggregate reports, tables, transcripts, and multi-item arrays.
303
+
304
+ ### Pattern example
305
+
306
+ JavaScript:
307
+
308
+ ```js
309
+ import { createCliStyle, diagnosticReport } from "@lewishowles/cli-style";
310
+
311
+ const ui = createCliStyle({
312
+ argv: process.argv.slice(2),
313
+ env: process.env,
314
+ stdout: process.stdout,
315
+ });
316
+
317
+ const output = diagnosticReport(
318
+ {
319
+ title: "Release checks",
320
+ checks: [
321
+ {
322
+ name: "README",
323
+ result: "success",
324
+ },
325
+ {
326
+ detail: "object-BmsQavd_.js over budget",
327
+ name: "Package size",
328
+ result: "failed",
329
+ },
330
+ ],
331
+ findings: [
332
+ {
333
+ message: "dist/object-BmsQavd_.js is 22.3 KB, above the 12.0 KB budget",
334
+ result: "failed",
335
+ },
336
+ ],
337
+ nextActions: ["Reduce package size, or raise the budget with a release-note-worthy reason."],
338
+ },
339
+ ui.options,
340
+ );
341
+
342
+ ui.print(output);
343
+ ```
344
+
345
+ CLI:
174
346
 
175
347
  ```bash
176
- export PYTHONPATH="$(cli-style adapter-path python):$PYTHONPATH"
348
+ cli-style render diagnostic-report --profile diagnostic <<'JSON'
349
+ {
350
+ "title": "Release checks",
351
+ "checks": [
352
+ {
353
+ "name": "README",
354
+ "result": "success"
355
+ },
356
+ {
357
+ "name": "Package size",
358
+ "result": "failed",
359
+ "detail": "object-BmsQavd_.js over budget"
360
+ }
361
+ ],
362
+ "findings": [
363
+ {
364
+ "result": "failed",
365
+ "message": "dist/object-BmsQavd_.js is 22.3 KB, above the 12.0 KB budget"
366
+ }
367
+ ],
368
+ "nextActions": [
369
+ "Reduce package size, or raise the budget with a release-note-worthy reason."
370
+ ]
371
+ }
372
+ JSON
177
373
  ```
178
374
 
375
+ Python:
376
+
179
377
  ```python
180
378
  from cli_style import render
181
379
 
182
380
  output = render(
183
- "status",
381
+ "diagnostic-report",
184
382
  {
185
- "type": "success",
186
- "label": "Build passed",
187
- "detail": "184 tests",
383
+ "title": "Release checks",
384
+ "checks": [
385
+ {
386
+ "name": "README",
387
+ "result": "success",
388
+ },
389
+ {
390
+ "name": "Package size",
391
+ "result": "failed",
392
+ "detail": "object-BmsQavd_.js over budget",
393
+ },
394
+ ],
395
+ "findings": [
396
+ {
397
+ "result": "failed",
398
+ "message": "dist/object-BmsQavd_.js is 22.3 KB, above the 12.0 KB budget",
399
+ },
400
+ ],
401
+ "nextActions": [
402
+ "Reduce package size, or raise the budget with a release-note-worthy reason.",
403
+ ],
188
404
  },
189
405
  profile="diagnostic",
190
406
  )
191
-
192
- print(output)
193
407
  ```
194
408
 
195
- Pass `binary=` when `cli-style` is not on `PATH`:
409
+ ## Reporter
196
410
 
197
- ```python
198
- output = render(
199
- "status",
411
+ Use a reporter when a JavaScript script needs grouped output built over time.
412
+
413
+ ```js
414
+ import { createReporter } from "@lewishowles/cli-style";
415
+
416
+ const reporter = createReporter({
417
+ colour: false,
418
+ });
419
+
420
+ reporter.divider("Project setup", "Claude + Codex");
421
+ reporter.group(
422
+ "Agent scripts",
423
+ [
424
+ {
425
+ label: "project-diagnostics.py",
426
+ result: "unchanged",
427
+ },
428
+ {
429
+ label: "repo-context.py",
430
+ result: "unchanged",
431
+ },
432
+ ],
200
433
  {
201
- "type": "success",
202
- "label": "Build passed",
434
+ summary: "2 already linked",
203
435
  },
204
- binary="/path/to/cli-style",
205
- plain=True,
206
- )
436
+ );
437
+ reporter.status("success", "Done.");
438
+
439
+ console.log(reporter.render());
207
440
  ```
208
441
 
442
+ 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.
443
+
444
+ Groups can include verbose detail rows when a caller opts in:
445
+
446
+ ```js
447
+ const reporter = createReporter({
448
+ verbose: process.env.AGENTS_VERBOSE === "1",
449
+ });
450
+ ```
451
+
452
+ ## Options and flags
453
+
454
+ ### JavaScript options
455
+
456
+ Most renderers accept the same output options:
457
+
458
+ | Option | Purpose |
459
+ | --------- | ------------------------------------------------------------------------- |
460
+ | `profile` | Output profile: `human`, `agent`, `diagnostic`, `ci`, `plain`, or `json`. |
461
+ | `colour` | Whether ANSI colour should be applied. |
462
+ | `unicode` | Whether Unicode symbols should be used. |
463
+ | `width` | Available output width for renderers that adapt layout. |
464
+
465
+ Use `createCliStyle()` to resolve these options from `argv`, `env`, `stdout`, CI, TTY, `NO_COLOR`, and terminal capability.
466
+
467
+ ### CLI and Bash flags
468
+
469
+ | Flag | Purpose |
470
+ | ---------------------------- | -------------------------------------- |
471
+ | `--profile <name>` | Render with a named profile. |
472
+ | `--plain` | Disable colour and Unicode decoration. |
473
+ | `--no-colour` / `--no-color` | Disable ANSI colour. |
474
+ | `--no-unicode` | Use ASCII symbols. |
475
+ | `--width <columns>` | Override detected output width. |
476
+
477
+ `cli-style render` rejects `--json`. JSON profile behaviour is for library calls and machine-readable commands.
478
+
479
+ ### Python options
480
+
481
+ | Argument | Purpose |
482
+ | ------------ | ------------------------------------------------ |
483
+ | `profile` | Render with a named profile. |
484
+ | `width` | Override detected output width. |
485
+ | `plain` | Disable colour and Unicode decoration. |
486
+ | `no_colour` | Disable ANSI colour. |
487
+ | `no_unicode` | Use ASCII symbols. |
488
+ | `binary` | Path or command name for the `cli-style` binary. |
489
+ | `extra_args` | Extra CLI arguments to pass through. |
490
+
209
491
  ## Profiles
210
492
 
211
493
  | Profile | Behaviour |
@@ -217,11 +499,31 @@ output = render(
217
499
  | `plain` | No ANSI, no Unicode dependency, simple text layout. |
218
500
  | `json` | Machine-readable only; library calls return structured data unchanged. |
219
501
 
502
+ ## Result types and tones
503
+
504
+ Result types drive symbols, labels, priority, and semantic colour:
505
+
506
+ | Result | Label | Unicode | ASCII | Tone |
507
+ | ----------- | --------- | ------- | ----- | --------- |
508
+ | `success` | Success | `✓` | `OK` | `success` |
509
+ | `info` | Info | `→` | `>` | `info` |
510
+ | `warning` | Warning | `⚠` | `!` | `warning` |
511
+ | `partial` | Partial | `◐` | `!` | `warning` |
512
+ | `unchanged` | Unchanged | `↪` | `-` | `muted` |
513
+ | `skipped` | Skipped | `–` | `-` | `muted` |
514
+ | `unknown` | Unknown | `?` | `?` | `muted` |
515
+ | `failed` | Failed | `×` | `x` | `danger` |
516
+
517
+ Tone values used by public APIs include `info`, `success`, `warning`, `danger`, and `muted`.
518
+
220
519
  ## Gallery
221
520
 
521
+ Use the gallery to review current renderer output:
522
+
222
523
  ```bash
223
524
  cli-style gallery
224
525
  cli-style gallery no-colour
526
+ cli-style gallery --section primitives
225
527
  cli-style gallery --section patterns
226
528
  cli-style gallery --fixture audit-finding
227
529
  cli-style gallery --profile agent
@@ -229,4 +531,4 @@ cli-style gallery --width 64
229
531
  cli-style gallery --variants
230
532
  ```
231
533
 
232
- Use the gallery to review current renderer output. Use `--interactive` to select a section or fixture with `fzf` when it is installed.
534
+ Use `--interactive` to select a section or fixture with `fzf` when it is installed.
@@ -41,6 +41,41 @@ cli_style_json_string() {
41
41
  printf '"%s"' "$value"
42
42
  }
43
43
 
44
+ # Escape Bash strings for a JSON string array.
45
+ #
46
+ # @param {string} ...
47
+ # String values to encode. Empty values are skipped.
48
+ cli_style_json_string_array() {
49
+ local item
50
+ local separator=""
51
+
52
+ printf '['
53
+
54
+ for item in "$@"; do
55
+ if [[ "$item" != "" ]]; then
56
+ printf '%s' "$separator"
57
+ cli_style_json_string "$item"
58
+ separator=","
59
+ fi
60
+ done
61
+
62
+ printf ']'
63
+ }
64
+
65
+ # Render a Bash value as a JSON integer or null.
66
+ #
67
+ # @param {string} value
68
+ # Integer value to encode.
69
+ cli_style_json_integer_or_null() {
70
+ local value="${1:-}"
71
+
72
+ if [[ "$value" =~ ^-?[0-9]+$ ]]; then
73
+ printf '%s' "$value"
74
+ else
75
+ printf 'null'
76
+ fi
77
+ }
78
+
44
79
  # Render a JSON object through the shared cli-style command.
45
80
  #
46
81
  # @param {string} renderer
@@ -116,24 +151,57 @@ cli_style_status() {
116
151
  # Row label.
117
152
  # @param {string} value
118
153
  # Row value.
154
+ # @param {string} result
155
+ # Optional result type before render flags.
119
156
  # @param {string} ...
120
157
  # Render flags passed through to `cli-style render`.
121
158
  cli_style_row() {
122
159
  local label="${1:-}"
123
160
  local value="${2:-}"
161
+ local result=""
124
162
  local json
125
163
 
126
- if (($# >= 2)); then
164
+ if (($# >= 3)) && [[ "${3:-}" != --* ]]; then
165
+ result="${3:-}"
166
+ shift 3
167
+ elif (($# >= 2)); then
127
168
  shift 2
128
169
  else
129
170
  set --
130
171
  fi
131
172
 
132
- json="{\"label\":$(cli_style_json_string "$label"),\"value\":$(cli_style_json_string "$value")}"
173
+ json="{\"label\":$(cli_style_json_string "$label"),\"value\":$(cli_style_json_string "$value"),\"result\":$(cli_style_json_string "$result")}"
133
174
 
134
175
  cli_style_render_json row "$json" "$@"
135
176
  }
136
177
 
178
+ # Render an inline span from Bash string values.
179
+ #
180
+ # @param {string} value
181
+ # Span text.
182
+ # @param {string} tone
183
+ # Optional colour tone.
184
+ # @param {string} ...
185
+ # Render flags passed through to `cli-style render`.
186
+ cli_style_span() {
187
+ local value="${1:-}"
188
+ local tone="info"
189
+ local json
190
+
191
+ if (($# >= 2)) && [[ "${2:-}" != --* ]]; then
192
+ tone="${2:-}"
193
+ shift 2
194
+ elif (($# >= 1)); then
195
+ shift
196
+ else
197
+ set --
198
+ fi
199
+
200
+ json="{\"value\":$(cli_style_json_string "$value"),\"tone\":$(cli_style_json_string "$tone")}"
201
+
202
+ cli_style_render_json span "$json" "$@"
203
+ }
204
+
137
205
  # Render a divider from a Bash string value.
138
206
  #
139
207
  # @param {string} label
@@ -175,3 +243,168 @@ cli_style_hint() {
175
243
 
176
244
  cli_style_render_json hint "$json" "$@"
177
245
  }
246
+
247
+ # Render a command result pattern from Bash string values.
248
+ #
249
+ # @param {string} result
250
+ # Result type such as success, failed, warning, or skipped.
251
+ # @param {string} summary
252
+ # Summary line.
253
+ # @param {string} command
254
+ # Optional command text.
255
+ # @param {string} exit_code
256
+ # Optional integer exit code.
257
+ # @param {string} duration
258
+ # Optional duration text.
259
+ # @param {string} detail
260
+ # Optional detail line.
261
+ # @param {string} ...
262
+ # Render flags passed through to `cli-style render`.
263
+ cli_style_command_result() {
264
+ local result="${1:-}"
265
+ local summary="${2:-}"
266
+ local command="${3:-}"
267
+ local exit_code="${4:-}"
268
+ local duration="${5:-}"
269
+ local detail="${6:-}"
270
+ local json
271
+
272
+ if (($# >= 6)); then
273
+ shift 6
274
+ else
275
+ set --
276
+ fi
277
+
278
+ json="{\"result\":$(cli_style_json_string "$result"),\"summary\":$(cli_style_json_string "$summary"),\"command\":$(cli_style_json_string "$command"),\"exitCode\":$(cli_style_json_integer_or_null "$exit_code"),\"duration\":$(cli_style_json_string "$duration"),\"details\":$(cli_style_json_string_array "$detail")}"
279
+
280
+ cli_style_render_json command-result "$json" "$@"
281
+ }
282
+
283
+ # Render an audit finding pattern from Bash string values.
284
+ #
285
+ # @param {string} result
286
+ # Result type such as success, failed, warning, or skipped.
287
+ # @param {string} finding
288
+ # Finding summary.
289
+ # @param {string} location
290
+ # Optional source location.
291
+ # @param {string} recommendation
292
+ # Optional recommendation text.
293
+ # @param {string} evidence
294
+ # Optional evidence line.
295
+ # @param {string} reference
296
+ # Optional reference line.
297
+ # @param {string} ...
298
+ # Render flags passed through to `cli-style render`.
299
+ cli_style_audit_finding() {
300
+ local result="${1:-}"
301
+ local finding="${2:-}"
302
+ local location="${3:-}"
303
+ local recommendation="${4:-}"
304
+ local evidence="${5:-}"
305
+ local reference="${6:-}"
306
+ local json
307
+
308
+ if (($# >= 6)); then
309
+ shift 6
310
+ else
311
+ set --
312
+ fi
313
+
314
+ json="{\"result\":$(cli_style_json_string "$result"),\"finding\":$(cli_style_json_string "$finding"),\"location\":$(cli_style_json_string "$location"),\"recommendation\":$(cli_style_json_string "$recommendation"),\"evidence\":$(cli_style_json_string_array "$evidence"),\"references\":$(cli_style_json_string_array "$reference")}"
315
+
316
+ cli_style_render_json audit-finding "$json" "$@"
317
+ }
318
+
319
+ # Render a task summary pattern from Bash string values.
320
+ #
321
+ # @param {string} result
322
+ # Result type such as success, failed, warning, or skipped.
323
+ # @param {string} task
324
+ # Task label.
325
+ # @param {string} summary
326
+ # Optional summary detail.
327
+ # @param {string} completed
328
+ # Optional completed item.
329
+ # @param {string} remaining
330
+ # Optional remaining item.
331
+ # @param {string} ...
332
+ # Render flags passed through to `cli-style render`.
333
+ cli_style_task_summary() {
334
+ local result="${1:-}"
335
+ local task="${2:-}"
336
+ local summary="${3:-}"
337
+ local completed="${4:-}"
338
+ local remaining="${5:-}"
339
+ local json
340
+
341
+ if (($# >= 5)); then
342
+ shift 5
343
+ else
344
+ set --
345
+ fi
346
+
347
+ json="{\"result\":$(cli_style_json_string "$result"),\"task\":$(cli_style_json_string "$task"),\"summary\":$(cli_style_json_string "$summary"),\"completed\":$(cli_style_json_string_array "$completed"),\"remaining\":$(cli_style_json_string_array "$remaining")}"
348
+
349
+ cli_style_render_json task-summary "$json" "$@"
350
+ }
351
+
352
+ # Render a confirmation result pattern from Bash string values.
353
+ #
354
+ # @param {string} state
355
+ # Confirmation state such as confirmed, cancelled, or skipped.
356
+ # @param {string} action
357
+ # Action label.
358
+ # @param {string} item
359
+ # Optional item label.
360
+ # @param {string} detail
361
+ # Optional detail text.
362
+ # @param {string} ...
363
+ # Render flags passed through to `cli-style render`.
364
+ cli_style_confirmation_result() {
365
+ local state="${1:-}"
366
+ local action="${2:-}"
367
+ local item="${3:-}"
368
+ local detail="${4:-}"
369
+ local json
370
+
371
+ if (($# >= 4)); then
372
+ shift 4
373
+ else
374
+ set --
375
+ fi
376
+
377
+ json="{\"state\":$(cli_style_json_string "$state"),\"action\":$(cli_style_json_string "$action"),\"item\":$(cli_style_json_string "$item"),\"detail\":$(cli_style_json_string "$detail")}"
378
+
379
+ cli_style_render_json confirmation-result "$json" "$@"
380
+ }
381
+
382
+ # Render a next-step block pattern from Bash string values.
383
+ #
384
+ # @param {string} next
385
+ # Next step text.
386
+ # @param {string} reason
387
+ # Optional reason text.
388
+ # @param {string} command
389
+ # Optional command line.
390
+ # @param {string} alternative
391
+ # Optional alternative line.
392
+ # @param {string} ...
393
+ # Render flags passed through to `cli-style render`.
394
+ cli_style_next_step_block() {
395
+ local next="${1:-}"
396
+ local reason="${2:-}"
397
+ local command="${3:-}"
398
+ local alternative="${4:-}"
399
+ local json
400
+
401
+ if (($# >= 4)); then
402
+ shift 4
403
+ else
404
+ set --
405
+ fi
406
+
407
+ json="{\"next\":$(cli_style_json_string "$next"),\"reason\":$(cli_style_json_string "$reason"),\"commands\":$(cli_style_json_string_array "$command"),\"alternatives\":$(cli_style_json_string_array "$alternative")}"
408
+
409
+ cli_style_render_json next-step-block "$json" "$@"
410
+ }
@@ -83,6 +83,221 @@ def render(
83
83
  return result.stdout.rstrip("\n")
84
84
 
85
85
 
86
+ # Render a result status line from Python values.
87
+ #
88
+ # @param {str} type
89
+ # Result type such as success, failed, warning, or skipped.
90
+ # @param {str} label
91
+ # Status label.
92
+ # @param {str} detail
93
+ # Optional detail text.
94
+ def status(
95
+ type: str,
96
+ label: str = "",
97
+ detail: str = "",
98
+ **kwargs: Any,
99
+ ) -> str:
100
+ return render("status", {"type": type, "label": label, "detail": detail}, **kwargs)
101
+
102
+
103
+ # Render a labelled value row from Python values.
104
+ #
105
+ # @param {str} label
106
+ # Row label.
107
+ # @param {str} value
108
+ # Row value.
109
+ # @param {str} result
110
+ # Optional result type for a symbol and tone.
111
+ def row(
112
+ label: str,
113
+ value: str,
114
+ result: str = "",
115
+ **kwargs: Any,
116
+ ) -> str:
117
+ return render("row", {"label": label, "value": value, "result": result}, **kwargs)
118
+
119
+
120
+ # Render an inline span from Python values.
121
+ #
122
+ # @param {str} value
123
+ # Span text.
124
+ # @param {str} tone
125
+ # Optional colour tone, defaulting to info.
126
+ def span(
127
+ value: str,
128
+ tone: str = "info",
129
+ **kwargs: Any,
130
+ ) -> str:
131
+ return render("span", {"value": value, "tone": tone}, **kwargs)
132
+
133
+
134
+ # Render a divider from a Python value.
135
+ #
136
+ # @param {str} label
137
+ # Optional divider label.
138
+ def divider(
139
+ label: str = "",
140
+ **kwargs: Any,
141
+ ) -> str:
142
+ return render("divider", {"label": label}, **kwargs)
143
+
144
+
145
+ # Render a hint from a Python value.
146
+ #
147
+ # @param {str} message
148
+ # Hint message.
149
+ def hint(
150
+ message: str,
151
+ **kwargs: Any,
152
+ ) -> str:
153
+ return render("hint", {"message": message}, **kwargs)
154
+
155
+
156
+ # Render a command result pattern from Python values.
157
+ #
158
+ # @param {str} result
159
+ # Result type such as success, failed, warning, or skipped.
160
+ # @param {str} summary
161
+ # Summary line.
162
+ # @param {str} command
163
+ # Optional command text.
164
+ # @param {int|None} exit_code
165
+ # Optional integer exit code.
166
+ # @param {str} duration
167
+ # Optional duration text.
168
+ # @param {str} detail
169
+ # Optional detail line.
170
+ def command_result(
171
+ result: str,
172
+ summary: str,
173
+ command: str = "",
174
+ exit_code: int | None = None,
175
+ duration: str = "",
176
+ detail: str = "",
177
+ **kwargs: Any,
178
+ ) -> str:
179
+ return render("command-result", {
180
+ "result": result,
181
+ "summary": summary,
182
+ "command": command,
183
+ "exitCode": exit_code,
184
+ "duration": duration,
185
+ "details": [detail] if detail else [],
186
+ }, **kwargs)
187
+
188
+
189
+ # Render an audit finding pattern from Python values.
190
+ #
191
+ # @param {str} result
192
+ # Result type such as success, failed, warning, or skipped.
193
+ # @param {str} finding
194
+ # Finding summary.
195
+ # @param {str} location
196
+ # Optional source location.
197
+ # @param {str} recommendation
198
+ # Optional recommendation text.
199
+ # @param {str} evidence
200
+ # Optional evidence line.
201
+ # @param {str} reference
202
+ # Optional reference line.
203
+ def audit_finding(
204
+ result: str,
205
+ finding: str,
206
+ location: str = "",
207
+ recommendation: str = "",
208
+ evidence: str = "",
209
+ reference: str = "",
210
+ **kwargs: Any,
211
+ ) -> str:
212
+ return render("audit-finding", {
213
+ "result": result,
214
+ "finding": finding,
215
+ "location": location,
216
+ "recommendation": recommendation,
217
+ "evidence": [evidence] if evidence else [],
218
+ "references": [reference] if reference else [],
219
+ }, **kwargs)
220
+
221
+
222
+ # Render a task summary pattern from Python values.
223
+ #
224
+ # @param {str} result
225
+ # Result type such as success, failed, warning, or skipped.
226
+ # @param {str} task
227
+ # Task label.
228
+ # @param {str} summary
229
+ # Optional summary detail.
230
+ # @param {str} completed
231
+ # Optional completed item.
232
+ # @param {str} remaining
233
+ # Optional remaining item.
234
+ def task_summary(
235
+ result: str,
236
+ task: str,
237
+ summary: str = "",
238
+ completed: str = "",
239
+ remaining: str = "",
240
+ **kwargs: Any,
241
+ ) -> str:
242
+ return render("task-summary", {
243
+ "result": result,
244
+ "task": task,
245
+ "summary": summary,
246
+ "completed": [completed] if completed else [],
247
+ "remaining": [remaining] if remaining else [],
248
+ }, **kwargs)
249
+
250
+
251
+ # Render a confirmation result pattern from Python values.
252
+ #
253
+ # @param {str} state
254
+ # Confirmation state such as confirmed, cancelled, or skipped.
255
+ # @param {str} action
256
+ # Action label.
257
+ # @param {str} item
258
+ # Optional item label.
259
+ # @param {str} detail
260
+ # Optional detail text.
261
+ def confirmation_result(
262
+ state: str,
263
+ action: str,
264
+ item: str = "",
265
+ detail: str = "",
266
+ **kwargs: Any,
267
+ ) -> str:
268
+ return render("confirmation-result", {
269
+ "state": state,
270
+ "action": action,
271
+ "item": item,
272
+ "detail": detail,
273
+ }, **kwargs)
274
+
275
+
276
+ # Render a next-step block pattern from Python values.
277
+ #
278
+ # @param {str} next_step
279
+ # Next step text.
280
+ # @param {str} reason
281
+ # Optional reason text.
282
+ # @param {str} command
283
+ # Optional command line.
284
+ # @param {str} alternative
285
+ # Optional alternative line.
286
+ def next_step_block(
287
+ next_step: str,
288
+ reason: str = "",
289
+ command: str = "",
290
+ alternative: str = "",
291
+ **kwargs: Any,
292
+ ) -> str:
293
+ return render("next-step-block", {
294
+ "next": next_step,
295
+ "reason": reason,
296
+ "commands": [command] if command else [],
297
+ "alternatives": [alternative] if alternative else [],
298
+ }, **kwargs)
299
+
300
+
86
301
  # Resolve a command name or executable path for subprocess use.
87
302
  #
88
303
  # @param {str} binary
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lewishowles/cli-style",
3
- "version": "0.5.0",
3
+ "version": "0.7.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
+ }