@lewishowles/cli-style 0.1.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/LICENSE +21 -0
- package/README.md +153 -0
- package/adapters/bash/cli-style.sh +50 -0
- package/adapters/python/cli_style.py +104 -0
- package/bin/cli-style.js +82 -0
- package/examples/diagnostic.mjs +30 -0
- package/examples/diagnostic.py +38 -0
- package/examples/diagnostic.sh +29 -0
- package/examples/gallery.mjs +18 -0
- package/package.json +42 -0
- package/src/capability/resolve-profile.js +69 -0
- package/src/capability/terminal.js +105 -0
- package/src/cli/gallery-command.js +238 -0
- package/src/cli/help.js +35 -0
- package/src/cli/render-command.js +283 -0
- package/src/create-cli-style.js +53 -0
- package/src/formatters/ansi.js +154 -0
- package/src/gallery/render-gallery.js +758 -0
- package/src/index.js +52 -0
- package/src/patterns/agent-transcript.js +180 -0
- package/src/patterns/audit-finding.js +107 -0
- package/src/patterns/command-result.js +91 -0
- package/src/patterns/compact-data-table.js +67 -0
- package/src/patterns/confirmation-result.js +88 -0
- package/src/patterns/diagnostic-report.js +144 -0
- package/src/patterns/helpers.js +106 -0
- package/src/patterns/next-step-block.js +118 -0
- package/src/patterns/task-summary.js +59 -0
- package/src/primitives/bar-chart.js +131 -0
- package/src/primitives/chip.js +33 -0
- package/src/primitives/divider.js +64 -0
- package/src/primitives/feedback.js +83 -0
- package/src/primitives/panel.js +100 -0
- package/src/primitives/progress-bar.js +121 -0
- package/src/primitives/row.js +80 -0
- package/src/primitives/status.js +39 -0
- package/src/primitives/step-progress.js +184 -0
- package/src/primitives/table.js +239 -0
- package/src/profiles/profiles.js +24 -0
- package/src/theme/colours.js +141 -0
- package/src/theme/results.js +98 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lewis Howles
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# CLI style
|
|
2
|
+
|
|
3
|
+
Shared terminal output styles for tools, diagnostics, and package CLIs.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Bun
|
|
8
|
+
- Node.js 20 or newer
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
JavaScript projects can install the package directly:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
bun add @lewishowles/cli-style
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Bash and Python projects still need the package available somewhere, because their adapters call the `cli-style` binary. If the project already has Node or Bun tooling, add it as a dev dependency. Otherwise, link or vendor this package in a stable tools directory and point the adapters at that copy:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
export CLI_STYLE_BIN="/path/to/cli-style/bin/cli-style.js"
|
|
22
|
+
export PYTHONPATH="/path/to/cli-style/adapters/python:$PYTHONPATH"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## JavaScript
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
import { createCliStyle, status } from "@lewishowles/cli-style";
|
|
29
|
+
|
|
30
|
+
const ui = createCliStyle({
|
|
31
|
+
profile: "human",
|
|
32
|
+
width: 80,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const output = status("success", "184 tests", {
|
|
36
|
+
...ui.options,
|
|
37
|
+
label: "Build passed",
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
ui.print(output);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Renderer methods return strings. `ui.print()`, `ui.write()`, and CLI commands handle stdout and stderr.
|
|
44
|
+
|
|
45
|
+
## CLI
|
|
46
|
+
|
|
47
|
+
Use `cli-style render <renderer>` when a script needs rendered text from JSON input.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cli-style render status --profile diagnostic <<'JSON'
|
|
51
|
+
{
|
|
52
|
+
"type": "success",
|
|
53
|
+
"label": "Build passed",
|
|
54
|
+
"detail": "184 tests"
|
|
55
|
+
}
|
|
56
|
+
JSON
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
cli-style render task-summary --plain <<'JSON'
|
|
61
|
+
{
|
|
62
|
+
"title": "Task summary",
|
|
63
|
+
"completed": ["Added render command"],
|
|
64
|
+
"remaining": ["Add adapters"]
|
|
65
|
+
}
|
|
66
|
+
JSON
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`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.
|
|
70
|
+
|
|
71
|
+
## Bash
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
source /path/to/node_modules/@lewishowles/cli-style/adapters/bash/cli-style.sh
|
|
75
|
+
|
|
76
|
+
cli_style_render status --profile diagnostic <<'JSON'
|
|
77
|
+
{
|
|
78
|
+
"type": "success",
|
|
79
|
+
"label": "Build passed",
|
|
80
|
+
"detail": "184 tests"
|
|
81
|
+
}
|
|
82
|
+
JSON
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Set `CLI_STYLE_BIN` when `cli-style` is not on `PATH`:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
CLI_STYLE_BIN="/path/to/cli-style" cli_style_render status --plain <<'JSON'
|
|
89
|
+
{
|
|
90
|
+
"type": "success",
|
|
91
|
+
"label": "Build passed"
|
|
92
|
+
}
|
|
93
|
+
JSON
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Python
|
|
97
|
+
|
|
98
|
+
Add `/path/to/node_modules/@lewishowles/cli-style/adapters/python` to `PYTHONPATH`, then import the helper:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from cli_style import render
|
|
102
|
+
|
|
103
|
+
output = render(
|
|
104
|
+
"status",
|
|
105
|
+
{
|
|
106
|
+
"type": "success",
|
|
107
|
+
"label": "Build passed",
|
|
108
|
+
"detail": "184 tests",
|
|
109
|
+
},
|
|
110
|
+
profile="diagnostic",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
print(output)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Pass `binary=` when `cli-style` is not on `PATH`:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
output = render(
|
|
120
|
+
"status",
|
|
121
|
+
{
|
|
122
|
+
"type": "success",
|
|
123
|
+
"label": "Build passed",
|
|
124
|
+
},
|
|
125
|
+
binary="/path/to/cli-style",
|
|
126
|
+
plain=True,
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Profiles
|
|
131
|
+
|
|
132
|
+
| Profile | Behaviour |
|
|
133
|
+
| ------------ | --------------------------------------------------------------------------------- |
|
|
134
|
+
| `human` | Colour and Unicode when supported; compact but visually pleasant. |
|
|
135
|
+
| `agent` | Markdown-like, structured, easy to quote or parse. |
|
|
136
|
+
| `diagnostic` | Optimised for check summaries, findings, skipped checks, and next actions. |
|
|
137
|
+
| `ci` | Stable, grep-friendly, no decorative panels; colour off unless explicitly forced. |
|
|
138
|
+
| `plain` | No ANSI, no Unicode dependency, simple text layout. |
|
|
139
|
+
| `json` | Machine-readable only; library calls return structured data unchanged. |
|
|
140
|
+
|
|
141
|
+
## Gallery
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
cli-style gallery
|
|
145
|
+
cli-style gallery no-colour
|
|
146
|
+
cli-style gallery --section patterns
|
|
147
|
+
cli-style gallery --fixture audit-finding
|
|
148
|
+
cli-style gallery --profile agent
|
|
149
|
+
cli-style gallery --width 64
|
|
150
|
+
cli-style gallery --variants
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Use the gallery to review current renderer output. Use `--interactive` to select a section or fixture with `fzf` when it is installed.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Sourceable Bash helpers for rendering through the cli-style binary.
|
|
2
|
+
|
|
3
|
+
# Return the configured cli-style binary.
|
|
4
|
+
#
|
|
5
|
+
# @returns {string}
|
|
6
|
+
# Binary path or command name.
|
|
7
|
+
cli_style_binary() {
|
|
8
|
+
printf '%s\n' "${CLI_STYLE_BIN:-cli-style}"
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
# Returns 0 when a binary can be executed.
|
|
12
|
+
#
|
|
13
|
+
# @param {string} binary
|
|
14
|
+
# Binary path or command name.
|
|
15
|
+
cli_style_has_binary() {
|
|
16
|
+
local binary="$1"
|
|
17
|
+
|
|
18
|
+
if [[ "$binary" == */* ]]; then
|
|
19
|
+
[[ -x "$binary" ]]
|
|
20
|
+
return
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
command -v "$binary" >/dev/null 2>&1
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# Render stdin JSON through the shared cli-style command.
|
|
27
|
+
#
|
|
28
|
+
# @param {string} renderer
|
|
29
|
+
# Stable renderer name accepted by `cli-style render`.
|
|
30
|
+
# @param {string} ...
|
|
31
|
+
# Render flags passed through to `cli-style render`.
|
|
32
|
+
cli_style_render() {
|
|
33
|
+
local renderer="${1:-}"
|
|
34
|
+
local binary
|
|
35
|
+
|
|
36
|
+
if [[ "$renderer" == "" ]]; then
|
|
37
|
+
printf 'Missing renderer\n' >&2
|
|
38
|
+
return 2
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
shift
|
|
42
|
+
binary="$(cli_style_binary)"
|
|
43
|
+
|
|
44
|
+
if ! cli_style_has_binary "$binary"; then
|
|
45
|
+
printf 'cli-style binary not found: %s\n' "$binary" >&2
|
|
46
|
+
return 127
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
"$binary" render "$renderer" "$@"
|
|
50
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Render caller data through the shared cli-style binary.
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import subprocess
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Sequence
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class CliStyleError(RuntimeError):
|
|
15
|
+
"""Base error for cli-style adapter failures."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CliStyleNotFoundError(CliStyleError):
|
|
19
|
+
"""Raised when the cli-style binary cannot be found."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CliStyleRenderError(CliStyleError):
|
|
23
|
+
"""Raised when cli-style rejects a render request."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Render caller data through `cli-style render`.
|
|
27
|
+
#
|
|
28
|
+
# @param {str} renderer
|
|
29
|
+
# Stable renderer name accepted by `cli-style render`.
|
|
30
|
+
# @param {dict} data
|
|
31
|
+
# JSON-serialisable renderer input.
|
|
32
|
+
def render(
|
|
33
|
+
renderer: str,
|
|
34
|
+
data: dict[str, Any],
|
|
35
|
+
*,
|
|
36
|
+
binary: str = "cli-style",
|
|
37
|
+
profile: str | None = None,
|
|
38
|
+
width: int | None = None,
|
|
39
|
+
plain: bool = False,
|
|
40
|
+
no_colour: bool = False,
|
|
41
|
+
no_unicode: bool = False,
|
|
42
|
+
extra_args: Sequence[str] | None = None,
|
|
43
|
+
) -> str:
|
|
44
|
+
if not isinstance(renderer, str) or renderer == "":
|
|
45
|
+
raise ValueError("renderer must be a non-empty string")
|
|
46
|
+
|
|
47
|
+
if not isinstance(data, dict):
|
|
48
|
+
raise TypeError("data must be a dict")
|
|
49
|
+
|
|
50
|
+
resolved_binary = resolve_binary(binary)
|
|
51
|
+
command = [resolved_binary, "render", renderer]
|
|
52
|
+
|
|
53
|
+
if profile is not None:
|
|
54
|
+
command.extend(["--profile", profile])
|
|
55
|
+
|
|
56
|
+
if width is not None:
|
|
57
|
+
command.extend(["--width", str(width)])
|
|
58
|
+
|
|
59
|
+
if plain:
|
|
60
|
+
command.append("--plain")
|
|
61
|
+
|
|
62
|
+
if no_colour:
|
|
63
|
+
command.append("--no-colour")
|
|
64
|
+
|
|
65
|
+
if no_unicode:
|
|
66
|
+
command.append("--no-unicode")
|
|
67
|
+
|
|
68
|
+
if extra_args is not None:
|
|
69
|
+
command.extend(extra_args)
|
|
70
|
+
|
|
71
|
+
result = subprocess.run(
|
|
72
|
+
command,
|
|
73
|
+
capture_output=True,
|
|
74
|
+
check=False,
|
|
75
|
+
input=json.dumps(data),
|
|
76
|
+
text=True,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
if result.returncode != 0:
|
|
80
|
+
message = result.stderr.strip() or f"cli-style render failed with exit code {result.returncode}"
|
|
81
|
+
raise CliStyleRenderError(message)
|
|
82
|
+
|
|
83
|
+
return result.stdout.rstrip("\n")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
# Resolve a command name or executable path for subprocess use.
|
|
87
|
+
#
|
|
88
|
+
# @param {str} binary
|
|
89
|
+
# Binary path or command name.
|
|
90
|
+
def resolve_binary(binary: str) -> str:
|
|
91
|
+
if "/" in binary:
|
|
92
|
+
path = Path(binary)
|
|
93
|
+
|
|
94
|
+
if path.is_file() and os.access(path, os.X_OK):
|
|
95
|
+
return str(path)
|
|
96
|
+
|
|
97
|
+
raise CliStyleNotFoundError(f"cli-style binary not found: {binary}")
|
|
98
|
+
|
|
99
|
+
resolved = shutil.which(binary)
|
|
100
|
+
|
|
101
|
+
if resolved is None:
|
|
102
|
+
raise CliStyleNotFoundError(f"cli-style binary not found: {binary}")
|
|
103
|
+
|
|
104
|
+
return resolved
|
package/bin/cli-style.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { Buffer } from "node:buffer";
|
|
4
|
+
|
|
5
|
+
import { createCliStyle, renderGallery, renderHelp } from "../src/index.js";
|
|
6
|
+
import {
|
|
7
|
+
parseGalleryRequest,
|
|
8
|
+
selectInteractiveGalleryRequest,
|
|
9
|
+
} from "../src/cli/gallery-command.js";
|
|
10
|
+
import {
|
|
11
|
+
parseRenderRequest,
|
|
12
|
+
renderJsonInput,
|
|
13
|
+
} from "../src/cli/render-command.js";
|
|
14
|
+
|
|
15
|
+
// Command-line arguments passed to the package binary.
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
|
|
18
|
+
// First positional command requested by the caller.
|
|
19
|
+
const command = args[0];
|
|
20
|
+
|
|
21
|
+
// Renderer configured from the current terminal.
|
|
22
|
+
const ui = createCliStyle({
|
|
23
|
+
argv: args,
|
|
24
|
+
env: process.env,
|
|
25
|
+
stdout: process.stdout,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Print text with a trailing newline.
|
|
30
|
+
*
|
|
31
|
+
* @param {string} value
|
|
32
|
+
* Text to print.
|
|
33
|
+
* @returns {void}
|
|
34
|
+
*/
|
|
35
|
+
function print(value) {
|
|
36
|
+
console.log(value);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (command === undefined || command === "--help" || command === "-h") {
|
|
40
|
+
print(renderHelp());
|
|
41
|
+
} else if (command === "gallery") {
|
|
42
|
+
try {
|
|
43
|
+
const request = parseGalleryRequest(args.slice(1));
|
|
44
|
+
const selectedRequest = selectInteractiveGalleryRequest(request);
|
|
45
|
+
|
|
46
|
+
print(renderGallery(ui.options, selectedRequest));
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(error.message);
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
}
|
|
51
|
+
} else if (command === "render") {
|
|
52
|
+
try {
|
|
53
|
+
const request = parseRenderRequest(args.slice(1));
|
|
54
|
+
const input = await readStdin();
|
|
55
|
+
|
|
56
|
+
print(renderJsonInput(input, request, ui.options));
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(error.message);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
console.error(`Unknown command: ${command}`);
|
|
63
|
+
console.error("");
|
|
64
|
+
console.error(renderHelp());
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Read all stdin data for custom render input.
|
|
70
|
+
*
|
|
71
|
+
* @returns {Promise<string>}
|
|
72
|
+
* Stdin text.
|
|
73
|
+
*/
|
|
74
|
+
async function readStdin() {
|
|
75
|
+
const chunks = [];
|
|
76
|
+
|
|
77
|
+
for await (const chunk of process.stdin) {
|
|
78
|
+
chunks.push(Buffer.from(chunk));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
82
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { diagnosticReport } from "../src/index.js";
|
|
4
|
+
|
|
5
|
+
// Example data shaped like a local diagnostic command summary.
|
|
6
|
+
const report = {
|
|
7
|
+
checks: [
|
|
8
|
+
{
|
|
9
|
+
detail: "30 tests",
|
|
10
|
+
name: "Unit tests",
|
|
11
|
+
result: "success",
|
|
12
|
+
},
|
|
13
|
+
],
|
|
14
|
+
findings: [],
|
|
15
|
+
nextActions: ["Build Bash and Python adapters"],
|
|
16
|
+
skippedChecks: [
|
|
17
|
+
{
|
|
18
|
+
name: "Full suite",
|
|
19
|
+
reason: "Not needed for adapter smoke test",
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
title: "Diagnostics",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
console.log(diagnosticReport(report, {
|
|
26
|
+
colour: false,
|
|
27
|
+
profile: "diagnostic",
|
|
28
|
+
unicode: true,
|
|
29
|
+
width: 80,
|
|
30
|
+
}));
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Render a diagnostic report through the Python adapter.
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
8
|
+
|
|
9
|
+
sys.path.insert(0, str(SCRIPT_DIR.parent / "adapters" / "python"))
|
|
10
|
+
|
|
11
|
+
from cli_style import render # noqa: E402
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
report = {
|
|
15
|
+
"title": "Diagnostics",
|
|
16
|
+
"checks": [
|
|
17
|
+
{
|
|
18
|
+
"result": "success",
|
|
19
|
+
"name": "Unit tests",
|
|
20
|
+
"detail": "30 tests",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
"findings": [],
|
|
24
|
+
"skippedChecks": [
|
|
25
|
+
{
|
|
26
|
+
"name": "Full suite",
|
|
27
|
+
"reason": "Not needed for adapter smoke test",
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
"nextActions": ["Build Bash and Python adapters"],
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
print(render(
|
|
34
|
+
"diagnostic-report",
|
|
35
|
+
report,
|
|
36
|
+
binary=str(SCRIPT_DIR.parent / "bin" / "cli-style.js"),
|
|
37
|
+
profile="diagnostic",
|
|
38
|
+
))
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Render a diagnostic report through the Bash adapter.
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
|
|
8
|
+
source "$SCRIPT_DIR/../adapters/bash/cli-style.sh"
|
|
9
|
+
|
|
10
|
+
CLI_STYLE_BIN="${CLI_STYLE_BIN:-"$SCRIPT_DIR/../bin/cli-style.js"}" cli_style_render diagnostic-report --profile diagnostic <<'JSON'
|
|
11
|
+
{
|
|
12
|
+
"title": "Diagnostics",
|
|
13
|
+
"checks": [
|
|
14
|
+
{
|
|
15
|
+
"result": "success",
|
|
16
|
+
"name": "Unit tests",
|
|
17
|
+
"detail": "30 tests"
|
|
18
|
+
}
|
|
19
|
+
],
|
|
20
|
+
"findings": [],
|
|
21
|
+
"skippedChecks": [
|
|
22
|
+
{
|
|
23
|
+
"name": "Full suite",
|
|
24
|
+
"reason": "Not needed for adapter smoke test"
|
|
25
|
+
}
|
|
26
|
+
],
|
|
27
|
+
"nextActions": ["Build Bash and Python adapters"]
|
|
28
|
+
}
|
|
29
|
+
JSON
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { renderGallery } from "../src/index.js";
|
|
4
|
+
|
|
5
|
+
// Stable options for reviewing gallery output outside the current terminal.
|
|
6
|
+
const reviewOptions = {
|
|
7
|
+
colour: false,
|
|
8
|
+
profile: "plain",
|
|
9
|
+
unicode: false,
|
|
10
|
+
width: 64,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Variants request exercises every gallery capability variant with fixed sample data.
|
|
14
|
+
const reviewRequest = {
|
|
15
|
+
variants: true,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
console.log(renderGallery(reviewOptions, reviewRequest));
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lewishowles/cli-style",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared terminal output styles for Lewis Howles tools, diagnostics, and package CLIs.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/lewishowles/cli-style.git"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"cli-style": "./bin/cli-style.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"adapters",
|
|
15
|
+
"bin",
|
|
16
|
+
"examples",
|
|
17
|
+
"src",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"exports": {
|
|
23
|
+
".": "./src/index.js"
|
|
24
|
+
},
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"registry": "https://npm.pkg.github.com/"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"check": "vp check --fix",
|
|
30
|
+
"cli-style:gallery": "bun ./bin/cli-style.js gallery",
|
|
31
|
+
"test:unit": "bun test"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@stylistic/eslint-plugin": "^5.10.0",
|
|
35
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@0.2.1",
|
|
36
|
+
"vite-plus": "0.2.1"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20"
|
|
40
|
+
},
|
|
41
|
+
"packageManager": "bun@1.3.14"
|
|
42
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { isProfile, profiles } from "../profiles/profiles.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the active output profile from explicit options, flags, and environment.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} options
|
|
7
|
+
* Profile resolution inputs.
|
|
8
|
+
* @param {string} options.profile
|
|
9
|
+
* Explicit profile name.
|
|
10
|
+
* @param {string[]} options.argv
|
|
11
|
+
* CLI arguments to inspect.
|
|
12
|
+
* @param {object} options.env
|
|
13
|
+
* Environment values to inspect.
|
|
14
|
+
* @returns {string}
|
|
15
|
+
* Resolved profile name.
|
|
16
|
+
*/
|
|
17
|
+
export function resolveProfile(options = {}) {
|
|
18
|
+
const argv = options.argv ?? [];
|
|
19
|
+
const env = options.env ?? {};
|
|
20
|
+
|
|
21
|
+
if (argv.includes("--json")) {
|
|
22
|
+
return profiles.JSON;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (argv.includes("--plain")) {
|
|
26
|
+
return profiles.PLAIN;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const flagProfile = readProfileFlag(argv);
|
|
30
|
+
|
|
31
|
+
if (flagProfile !== null) {
|
|
32
|
+
return isProfile(flagProfile) ? flagProfile : profiles.HUMAN;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isProfile(options.profile)) {
|
|
36
|
+
return options.profile;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (env.CI === "true" || env.CI === "1") {
|
|
40
|
+
return profiles.CI;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return profiles.HUMAN;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Read `--profile value` or `--profile=value` from CLI args.
|
|
48
|
+
*
|
|
49
|
+
* @param {string[]} argv
|
|
50
|
+
* CLI arguments to inspect.
|
|
51
|
+
* @returns {string|null}
|
|
52
|
+
* Profile flag value when present.
|
|
53
|
+
*/
|
|
54
|
+
function readProfileFlag(argv) {
|
|
55
|
+
const profilePrefix = "--profile=";
|
|
56
|
+
const inlineProfile = argv.find((arg) => arg.startsWith(profilePrefix));
|
|
57
|
+
|
|
58
|
+
if (inlineProfile !== undefined) {
|
|
59
|
+
return inlineProfile.slice(profilePrefix.length);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const profileFlagIndex = argv.indexOf("--profile");
|
|
63
|
+
|
|
64
|
+
if (profileFlagIndex === -1) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return argv[profileFlagIndex + 1] ?? "";
|
|
69
|
+
}
|