@lewishowles/cli-style 0.4.1 → 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
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
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
+
17
+ ## 0.5.0 - 2026-07-02
18
+
19
+ ### Added
20
+
21
+ - Added Bash adapter convenience functions for common renderers: `cli_style_status`, `cli_style_row`, `cli_style_divider`, and `cli_style_hint`.
22
+ - Added Bash adapter escaping for dynamic string values, so scripts can pass quotes, backslashes, paths, and messages without hand-building JSON.
23
+ - Added README examples for JS CLI entrypoint wiring with `createCliStyle({ argv, env, stdout })` and helper-first Bash adapter usage.
24
+
25
+ ### Changed
26
+
27
+ - Lightened the muted colour token so secondary text stays readable on dark surfaces.
28
+
3
29
  ## 0.4.1 - 2026-07-01
4
30
 
5
31
  ### Fixed
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,14 +23,37 @@ 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";
28
52
 
29
53
  const ui = createCliStyle({
30
- profile: "human",
31
- width: 80,
54
+ argv: process.argv.slice(2),
55
+ env: process.env,
56
+ stdout: process.stdout,
32
57
  });
33
58
 
34
59
  const output = status("success", "184 tests", {
@@ -41,52 +66,9 @@ ui.print(output);
41
66
 
42
67
  Renderer methods return strings. `ui.print()`, `ui.write()`, and CLI commands handle stdout and stderr.
43
68
 
44
- ## Reporter
45
-
46
- Use a reporter when a script needs grouped output rather than one row per file.
47
-
48
- ```js
49
- import { createReporter } from "@lewishowles/cli-style";
50
-
51
- const reporter = createReporter({
52
- colour: false,
53
- });
54
-
55
- reporter.divider("Project setup", "Claude + Codex");
56
- reporter.group(
57
- "Agent scripts",
58
- [
59
- {
60
- label: "project-diagnostics.py",
61
- result: "unchanged",
62
- },
63
- {
64
- label: "repo-context.py",
65
- result: "unchanged",
66
- },
67
- ],
68
- {
69
- summary: "2 already linked",
70
- },
71
- );
72
- reporter.status("success", "Done.");
73
-
74
- console.log(reporter.render());
75
- ```
76
-
77
- 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.
78
-
79
- Groups can include verbose detail rows when a caller opts in:
80
-
81
- ```js
82
- const reporter = createReporter({
83
- verbose: process.env.AGENTS_VERBOSE === "1",
84
- });
85
- ```
86
-
87
- ## CLI
69
+ ### CLI render
88
70
 
89
- 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:
90
72
 
91
73
  ```bash
92
74
  cli-style render status --profile diagnostic <<'JSON'
@@ -98,62 +80,53 @@ cli-style render status --profile diagnostic <<'JSON'
98
80
  JSON
99
81
  ```
100
82
 
101
- ```bash
102
- cli-style render task-summary --plain <<'JSON'
103
- {
104
- "title": "Task summary",
105
- "completed": ["Added render command"],
106
- "remaining": ["Add adapters"]
107
- }
108
- JSON
109
- ```
83
+ Use renderer names from the tables below, for example `status`, `row`, `diagnostic-report`, or `task-summary`.
110
84
 
111
- `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
112
86
 
113
- Build a local standalone binary when a repo should not need Bun at runtime:
87
+ Source the Bash adapter, then use convenience helpers for common primitive renderers:
114
88
 
115
89
  ```bash
116
- bun run build:binary
117
- bun run smoke:binary
118
- ```
90
+ source "$(cli-style adapter-path bash)"
119
91
 
120
- Create a binary install tarball:
92
+ label='Build "passed"'
93
+ detail="/path/to/project"
121
94
 
122
- ```bash
123
- bun run package:binary
124
- tar -tzf dist/release/cli-style-*.tar.gz
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
125
101
  ```
126
102
 
127
- 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.
103
+ The convenience functions build JSON internally, so callers do not need to escape quotes, backslashes, or paths by hand.
128
104
 
129
- ## Bash
105
+ Set `CLI_STYLE_BIN` when `cli-style` is not on `PATH`:
130
106
 
131
107
  ```bash
132
- source "$(cli-style adapter-path bash)"
133
-
134
- cli_style_render status --profile diagnostic <<'JSON'
135
- {
136
- "type": "success",
137
- "label": "Build passed",
138
- "detail": "184 tests"
139
- }
140
- JSON
108
+ CLI_STYLE_BIN="/path/to/cli-style" cli_style_status success "Build passed" "" --plain
141
109
  ```
142
110
 
143
- Set `CLI_STYLE_BIN` when `cli-style` is not on `PATH`:
111
+ Use `cli_style_render` for literal or prebuilt JSON, especially for structured patterns:
144
112
 
145
113
  ```bash
146
- CLI_STYLE_BIN="/path/to/cli-style" cli_style_render status --plain <<'JSON'
114
+ cli_style_render diagnostic-report --profile diagnostic <<'JSON'
147
115
  {
148
- "type": "success",
149
- "label": "Build passed"
116
+ "title": "Release checks",
117
+ "checks": [
118
+ {
119
+ "name": "README",
120
+ "result": "success"
121
+ }
122
+ ]
150
123
  }
151
124
  JSON
152
125
  ```
153
126
 
154
- ## Python
127
+ ### Python
155
128
 
156
- Add the adapter directory to `PYTHONPATH`, then import the helper:
129
+ Add the adapter directory to `PYTHONPATH`, then import the generic `render()` helper:
157
130
 
158
131
  ```bash
159
132
  export PYTHONPATH="$(cli-style adapter-path python):$PYTHONPATH"
@@ -189,6 +162,308 @@ output = render(
189
162
  )
190
163
  ```
191
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
+
192
467
  ## Profiles
193
468
 
194
469
  | Profile | Behaviour |
@@ -200,11 +475,31 @@ output = render(
200
475
  | `plain` | No ANSI, no Unicode dependency, simple text layout. |
201
476
  | `json` | Machine-readable only; library calls return structured data unchanged. |
202
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
+
203
495
  ## Gallery
204
496
 
497
+ Use the gallery to review current renderer output:
498
+
205
499
  ```bash
206
500
  cli-style gallery
207
501
  cli-style gallery no-colour
502
+ cli-style gallery --section primitives
208
503
  cli-style gallery --section patterns
209
504
  cli-style gallery --fixture audit-finding
210
505
  cli-style gallery --profile agent
@@ -212,4 +507,4 @@ cli-style gallery --width 64
212
507
  cli-style gallery --variants
213
508
  ```
214
509
 
215
- 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.
@@ -23,6 +23,40 @@ cli_style_has_binary() {
23
23
  command -v "$binary" >/dev/null 2>&1
24
24
  }
25
25
 
26
+ # Escape a Bash string for JSON string values.
27
+ #
28
+ # @param {string} value
29
+ # String value to encode.
30
+ cli_style_json_string() {
31
+ local value="${1:-}"
32
+
33
+ value="${value//\\/\\\\}"
34
+ value="${value//\"/\\\"}"
35
+ value="${value//$'\b'/\\b}"
36
+ value="${value//$'\f'/\\f}"
37
+ value="${value//$'\n'/\\n}"
38
+ value="${value//$'\r'/\\r}"
39
+ value="${value//$'\t'/\\t}"
40
+
41
+ printf '"%s"' "$value"
42
+ }
43
+
44
+ # Render a JSON object through the shared cli-style command.
45
+ #
46
+ # @param {string} renderer
47
+ # Stable renderer name accepted by `cli-style render`.
48
+ # @param {string} json
49
+ # Prebuilt JSON object.
50
+ # @param {string} ...
51
+ # Render flags passed through to `cli-style render`.
52
+ cli_style_render_json() {
53
+ local renderer="${1:-}"
54
+ local json="${2:-}"
55
+
56
+ shift 2
57
+ printf '%s\n' "$json" | cli_style_render "$renderer" "$@"
58
+ }
59
+
26
60
  # Render stdin JSON through the shared cli-style command.
27
61
  #
28
62
  # @param {string} renderer
@@ -48,3 +82,129 @@ cli_style_render() {
48
82
 
49
83
  "$binary" render "$renderer" "$@"
50
84
  }
85
+
86
+ # Render a result status line from Bash string values.
87
+ #
88
+ # @param {string} type
89
+ # Result type such as success, failed, warning, or skipped.
90
+ # @param {string} label
91
+ # Status label.
92
+ # @param {string} detail
93
+ # Optional detail text.
94
+ # @param {string} ...
95
+ # Render flags passed through to `cli-style render`.
96
+ cli_style_status() {
97
+ local type="${1:-}"
98
+ local label="${2:-}"
99
+ local detail="${3:-}"
100
+ local json
101
+
102
+ if (($# >= 3)); then
103
+ shift 3
104
+ else
105
+ set --
106
+ fi
107
+
108
+ json="{\"type\":$(cli_style_json_string "$type"),\"label\":$(cli_style_json_string "$label"),\"detail\":$(cli_style_json_string "$detail")}"
109
+
110
+ cli_style_render_json status "$json" "$@"
111
+ }
112
+
113
+ # Render a labelled value row from Bash string values.
114
+ #
115
+ # @param {string} label
116
+ # Row label.
117
+ # @param {string} value
118
+ # Row value.
119
+ # @param {string} result
120
+ # Optional result type before render flags.
121
+ # @param {string} ...
122
+ # Render flags passed through to `cli-style render`.
123
+ cli_style_row() {
124
+ local label="${1:-}"
125
+ local value="${2:-}"
126
+ local result=""
127
+ local json
128
+
129
+ if (($# >= 3)) && [[ "${3:-}" != --* ]]; then
130
+ result="${3:-}"
131
+ shift 3
132
+ elif (($# >= 2)); then
133
+ shift 2
134
+ else
135
+ set --
136
+ fi
137
+
138
+ json="{\"label\":$(cli_style_json_string "$label"),\"value\":$(cli_style_json_string "$value"),\"result\":$(cli_style_json_string "$result")}"
139
+
140
+ cli_style_render_json row "$json" "$@"
141
+ }
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
+
170
+ # Render a divider from a Bash string value.
171
+ #
172
+ # @param {string} label
173
+ # Optional divider label.
174
+ # @param {string} ...
175
+ # Render flags passed through to `cli-style render`.
176
+ cli_style_divider() {
177
+ local label="${1:-}"
178
+ local json
179
+
180
+ if (($# >= 1)); then
181
+ shift
182
+ else
183
+ set --
184
+ fi
185
+
186
+ json="{\"label\":$(cli_style_json_string "$label")}"
187
+
188
+ cli_style_render_json divider "$json" "$@"
189
+ }
190
+
191
+ # Render a hint from a Bash string value.
192
+ #
193
+ # @param {string} message
194
+ # Hint message.
195
+ # @param {string} ...
196
+ # Render flags passed through to `cli-style render`.
197
+ cli_style_hint() {
198
+ local message="${1:-}"
199
+ local json
200
+
201
+ if (($# >= 1)); then
202
+ shift
203
+ else
204
+ set --
205
+ fi
206
+
207
+ json="{\"message\":$(cli_style_json_string "$message")}"
208
+
209
+ cli_style_render_json hint "$json" "$@"
210
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lewishowles/cli-style",
3
- "version": "0.4.1",
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
+ }
@@ -5,7 +5,7 @@ export const colourTokens = {
5
5
  border: "#2a3a42",
6
6
  danger: "#ff7272",
7
7
  info: "#8bbdff",
8
- muted: "#6f7f87",
8
+ muted: "#7a8a92",
9
9
  success: "#8fdf72",
10
10
  surface: "#0f181b",
11
11
  surfaceRaised: "#132024",