@lewishowles/cli-style 0.4.0 → 0.5.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,26 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.5.0 - 2026-07-02
6
+
7
+ ### Added
8
+
9
+ - Added Bash adapter convenience functions for common renderers: `cli_style_status`, `cli_style_row`, `cli_style_divider`, and `cli_style_hint`.
10
+ - Added Bash adapter escaping for dynamic string values, so scripts can pass quotes, backslashes, paths, and messages without hand-building JSON.
11
+ - Added README examples for JS CLI entrypoint wiring with `createCliStyle({ argv, env, stdout })` and helper-first Bash adapter usage.
12
+
13
+ ### Changed
14
+
15
+ - Lightened the muted colour token so secondary text stays readable on dark surfaces.
16
+
17
+ ## 0.4.1 - 2026-07-01
18
+
19
+ ### Fixed
20
+
21
+ - Updated grouped reporter summaries so skipped rows outrank success rows while warnings and failures still take precedence.
22
+ - Added the project-local vite-plus pre-commit hook setup so commits run the existing staged checks.
23
+
3
24
  ## 0.4.0 - 2026-06-30
4
25
 
5
26
  ### Added
package/README.md CHANGED
@@ -27,8 +27,9 @@ export PATH="/path/to/cli-style/bin:$PATH"
27
27
  import { createCliStyle, status } from "@lewishowles/cli-style";
28
28
 
29
29
  const ui = createCliStyle({
30
- profile: "human",
31
- width: 80,
30
+ argv: process.argv.slice(2),
31
+ env: process.env,
32
+ stdout: process.stdout,
32
33
  });
33
34
 
34
35
  const output = status("success", "184 tests", {
@@ -41,6 +42,8 @@ ui.print(output);
41
42
 
42
43
  Renderer methods return strings. `ui.print()`, `ui.write()`, and CLI commands handle stdout and stderr.
43
44
 
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
+
44
47
  ## Reporter
45
48
 
46
49
  Use a reporter when a script needs grouped output rather than one row per file.
@@ -53,18 +56,22 @@ const reporter = createReporter({
53
56
  });
54
57
 
55
58
  reporter.divider("Project setup", "Claude + Codex");
56
- reporter.group("Agent scripts", [
57
- {
58
- label: "project-diagnostics.py",
59
- result: "unchanged",
60
- },
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
+ ],
61
71
  {
62
- label: "repo-context.py",
63
- result: "unchanged",
72
+ summary: "2 already linked",
64
73
  },
65
- ], {
66
- summary: "2 already linked",
67
- });
74
+ );
68
75
  reporter.status("success", "Done.");
69
76
 
70
77
  console.log(reporter.render());
@@ -127,19 +134,33 @@ The package contains `bin/cli-style` and `adapters/`. Install it into a repo-loc
127
134
  ```bash
128
135
  source "$(cli-style adapter-path bash)"
129
136
 
130
- cli_style_render status --profile diagnostic <<'JSON'
131
- {
132
- "type": "success",
133
- "label": "Build passed",
134
- "detail": "184 tests"
135
- }
136
- JSON
137
+ label='Build "passed"'
138
+ detail="/path/to/project"
139
+
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
144
+ ```
145
+
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
137
152
  ```
138
153
 
139
154
  Set `CLI_STYLE_BIN` when `cli-style` is not on `PATH`:
140
155
 
141
156
  ```bash
142
- CLI_STYLE_BIN="/path/to/cli-style" cli_style_render status --plain <<'JSON'
157
+ CLI_STYLE_BIN="/path/to/cli-style" cli_style_status success "Build passed" "" --plain
158
+ ```
159
+
160
+ Keep `cli_style_render` for literal or prebuilt JSON:
161
+
162
+ ```bash
163
+ cli_style_render status --plain <<'JSON'
143
164
  {
144
165
  "type": "success",
145
166
  "label": "Build passed"
@@ -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,96 @@ 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} ...
120
+ # Render flags passed through to `cli-style render`.
121
+ cli_style_row() {
122
+ local label="${1:-}"
123
+ local value="${2:-}"
124
+ local json
125
+
126
+ if (($# >= 2)); then
127
+ shift 2
128
+ else
129
+ set --
130
+ fi
131
+
132
+ json="{\"label\":$(cli_style_json_string "$label"),\"value\":$(cli_style_json_string "$value")}"
133
+
134
+ cli_style_render_json row "$json" "$@"
135
+ }
136
+
137
+ # Render a divider from a Bash string value.
138
+ #
139
+ # @param {string} label
140
+ # Optional divider label.
141
+ # @param {string} ...
142
+ # Render flags passed through to `cli-style render`.
143
+ cli_style_divider() {
144
+ local label="${1:-}"
145
+ local json
146
+
147
+ if (($# >= 1)); then
148
+ shift
149
+ else
150
+ set --
151
+ fi
152
+
153
+ json="{\"label\":$(cli_style_json_string "$label")}"
154
+
155
+ cli_style_render_json divider "$json" "$@"
156
+ }
157
+
158
+ # Render a hint from a Bash string value.
159
+ #
160
+ # @param {string} message
161
+ # Hint message.
162
+ # @param {string} ...
163
+ # Render flags passed through to `cli-style render`.
164
+ cli_style_hint() {
165
+ local message="${1:-}"
166
+ local json
167
+
168
+ if (($# >= 1)); then
169
+ shift
170
+ else
171
+ set --
172
+ fi
173
+
174
+ json="{\"message\":$(cli_style_json_string "$message")}"
175
+
176
+ cli_style_render_json hint "$json" "$@"
177
+ }
@@ -22,9 +22,11 @@ const report = {
22
22
  title: "Diagnostics",
23
23
  };
24
24
 
25
- console.log(diagnosticReport(report, {
26
- colour: false,
27
- profile: "diagnostic",
28
- unicode: true,
29
- width: 80,
30
- }));
25
+ console.log(
26
+ diagnosticReport(report, {
27
+ colour: false,
28
+ profile: "diagnostic",
29
+ unicode: true,
30
+ width: 80,
31
+ }),
32
+ );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lewishowles/cli-style",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Shared terminal output styles for Lewis Howles tools, diagnostics, and package CLIs.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,6 +32,7 @@
32
32
  "check": "vp check --fix",
33
33
  "cli-style:gallery": "bun ./bin/cli-style.js gallery",
34
34
  "package:binary": "scripts/package-binary.sh",
35
+ "prepare": "vp config",
35
36
  "smoke:binary": "./dist/bin/cli-style render status --plain < tests/fixtures/render/status-success.json",
36
37
  "test:unit": "bun test"
37
38
  },
@@ -1,19 +1,10 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
 
3
- import {
4
- galleryFixtures,
5
- gallerySections,
6
- galleryVariants,
7
- } from "../gallery/render-gallery.js";
3
+ import { galleryFixtures, gallerySections, galleryVariants } from "../gallery/render-gallery.js";
8
4
  import { isProfile, profiles } from "../profiles/profiles.js";
9
5
 
10
6
  // Global rendering flags accepted after `gallery` for focused review commands.
11
- const globalRenderingFlags = new Set([
12
- "--no-color",
13
- "--no-colour",
14
- "--no-unicode",
15
- "--plain",
16
- ]);
7
+ const globalRenderingFlags = new Set(["--no-color", "--no-colour", "--no-unicode", "--plain"]);
17
8
 
18
9
  /**
19
10
  * Parse gallery command arguments.
@@ -25,6 +16,7 @@ const globalRenderingFlags = new Set([
25
16
  */
26
17
  export function parseGalleryRequest(args = []) {
27
18
  let hasVariant = false;
19
+
28
20
  const request = {
29
21
  fixture: undefined,
30
22
  interactive: false,
@@ -96,15 +88,13 @@ export function selectInteractiveGalleryRequest(request) {
96
88
  ...gallerySections.map((section) => `section:${section}`),
97
89
  ...galleryFixtures.map((fixture) => `fixture:${fixture}`),
98
90
  ];
99
- const selection = spawnSync("fzf", [
100
- "--height=60%",
101
- "--layout=reverse",
102
- "--prompt=Gallery > ",
103
- ], {
91
+
92
+ const selection = spawnSync("fzf", ["--height=60%", "--layout=reverse", "--prompt=Gallery > "], {
104
93
  encoding: "utf8",
105
94
  input: choices.join("\n"),
106
95
  stdio: ["pipe", "pipe", "inherit"],
107
96
  });
97
+
108
98
  const value = selection.status === 0 ? selection.stdout.trim() : "";
109
99
 
110
100
  if (value === "") {
@@ -22,73 +22,81 @@ import { isProfile, profiles } from "../profiles/profiles.js";
22
22
  const renderers = {
23
23
  "agent-transcript": (input, options) => agentTranscript(input, options),
24
24
  "audit-finding": (input, options) => auditFinding(input, options),
25
- "bar-chart": (input, options) => barChart({
26
- ...input,
27
- ...options,
28
- }),
29
- chip: (input, options) => chip(input.label, input.tone, {
30
- ...input,
31
- ...options,
32
- }),
25
+ "bar-chart": (input, options) =>
26
+ barChart({
27
+ ...input,
28
+ ...options,
29
+ }),
30
+ chip: (input, options) =>
31
+ chip(input.label, input.tone, {
32
+ ...input,
33
+ ...options,
34
+ }),
33
35
  "command-result": (input, options) => commandResult(input, options),
34
36
  "compact-data-table": (input, options) => compactDataTable(input, options),
35
37
  "confirmation-result": (input, options) => confirmationResult(input, options),
36
38
  "diagnostic-report": (input, options) => diagnosticReport(input, options),
37
- divider: (input, options) => divider({
38
- ...input,
39
- ...options,
40
- }),
41
- "empty-state": (input, options) => emptyState(input.title, input.detail, {
42
- ...input,
43
- ...options,
44
- }),
45
- "error-block": (input, options) => errorBlock(input.title, input.lines, {
46
- ...input,
47
- ...options,
48
- }),
49
- hint: (input, options) => hint(input.message, {
50
- ...input,
51
- ...options,
52
- }),
39
+ divider: (input, options) =>
40
+ divider({
41
+ ...input,
42
+ ...options,
43
+ }),
44
+ "empty-state": (input, options) =>
45
+ emptyState(input.title, input.detail, {
46
+ ...input,
47
+ ...options,
48
+ }),
49
+ "error-block": (input, options) =>
50
+ errorBlock(input.title, input.lines, {
51
+ ...input,
52
+ ...options,
53
+ }),
54
+ hint: (input, options) =>
55
+ hint(input.message, {
56
+ ...input,
57
+ ...options,
58
+ }),
53
59
  "next-step-block": (input, options) => nextStepBlock(input, options),
54
- panel: (input, options) => panel({
55
- ...input,
56
- ...options,
57
- }),
58
- "progress-bar": (input, options) => progressBar({
59
- ...input,
60
- ...options,
61
- }),
62
- row: (input, options) => row(input.label, input.value, {
63
- ...input,
64
- ...options,
65
- }),
66
- status: (input, options) => status(input.type, input.detail, {
67
- ...input,
68
- ...options,
69
- }),
70
- step: (input, options) => step(input.label, input.state, {
71
- ...input,
72
- ...options,
73
- }),
74
- "step-progress": (input, options) => stepProgress({
75
- ...input,
76
- ...options,
77
- }),
78
- table: (input, options) => table({
79
- ...input,
80
- ...options,
81
- }),
60
+ panel: (input, options) =>
61
+ panel({
62
+ ...input,
63
+ ...options,
64
+ }),
65
+ "progress-bar": (input, options) =>
66
+ progressBar({
67
+ ...input,
68
+ ...options,
69
+ }),
70
+ row: (input, options) =>
71
+ row(input.label, input.value, {
72
+ ...input,
73
+ ...options,
74
+ }),
75
+ status: (input, options) =>
76
+ status(input.type, input.detail, {
77
+ ...input,
78
+ ...options,
79
+ }),
80
+ step: (input, options) =>
81
+ step(input.label, input.state, {
82
+ ...input,
83
+ ...options,
84
+ }),
85
+ "step-progress": (input, options) =>
86
+ stepProgress({
87
+ ...input,
88
+ ...options,
89
+ }),
90
+ table: (input, options) =>
91
+ table({
92
+ ...input,
93
+ ...options,
94
+ }),
82
95
  "task-summary": (input, options) => taskSummary(input, options),
83
96
  };
84
97
 
85
98
  // Global rendering flags accepted after `render <renderer>`.
86
- const globalRenderingFlags = new Set([
87
- "--no-color",
88
- "--no-colour",
89
- "--no-unicode",
90
- "--plain",
91
- ]);
99
+ const globalRenderingFlags = new Set(["--no-color", "--no-colour", "--no-unicode", "--plain"]);
92
100
 
93
101
  // Renderer names exposed for help text and tests.
94
102
  export const rendererNames = Object.keys(renderers);
@@ -28,10 +28,11 @@ export function createCliStyle(options = {}) {
28
28
  print: (value) => {
29
29
  console.log(value);
30
30
  },
31
- reporter: (reporterOptions = {}) => createReporter({
32
- ...resolvedOptions,
33
- ...reporterOptions,
34
- }),
31
+ reporter: (reporterOptions = {}) =>
32
+ createReporter({
33
+ ...resolvedOptions,
34
+ ...reporterOptions,
35
+ }),
35
36
  write: (value) => {
36
37
  process.stdout.write(value);
37
38
  },