@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/src/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export { createCliStyle, profiles } from "./create-cli-style.js";
|
|
2
|
+
|
|
3
|
+
export { resolveProfile } from "./capability/resolve-profile.js";
|
|
4
|
+
export { resolveTerminalCapabilities } from "./capability/terminal.js";
|
|
5
|
+
export { isProfile } from "./profiles/profiles.js";
|
|
6
|
+
|
|
7
|
+
export { renderHelp } from "./cli/help.js";
|
|
8
|
+
export {
|
|
9
|
+
parseRenderRequest,
|
|
10
|
+
rendererNames,
|
|
11
|
+
renderJsonInput,
|
|
12
|
+
} from "./cli/render-command.js";
|
|
13
|
+
export {
|
|
14
|
+
galleryFixtures,
|
|
15
|
+
gallerySections,
|
|
16
|
+
galleryVariants,
|
|
17
|
+
renderGallery,
|
|
18
|
+
} from "./gallery/render-gallery.js";
|
|
19
|
+
|
|
20
|
+
export { background, foreground, stripAnsi, style } from "./formatters/ansi.js";
|
|
21
|
+
export { agentTranscript } from "./patterns/agent-transcript.js";
|
|
22
|
+
export { auditFinding } from "./patterns/audit-finding.js";
|
|
23
|
+
export { commandResult } from "./patterns/command-result.js";
|
|
24
|
+
export { compactDataTable } from "./patterns/compact-data-table.js";
|
|
25
|
+
export { confirmationResult } from "./patterns/confirmation-result.js";
|
|
26
|
+
export { diagnosticReport } from "./patterns/diagnostic-report.js";
|
|
27
|
+
export { nextStepBlock } from "./patterns/next-step-block.js";
|
|
28
|
+
export { taskSummary } from "./patterns/task-summary.js";
|
|
29
|
+
export { barChart } from "./primitives/bar-chart.js";
|
|
30
|
+
export { chip } from "./primitives/chip.js";
|
|
31
|
+
export { divider } from "./primitives/divider.js";
|
|
32
|
+
export { emptyState, errorBlock, hint } from "./primitives/feedback.js";
|
|
33
|
+
export { panel } from "./primitives/panel.js";
|
|
34
|
+
export { progressBar } from "./primitives/progress-bar.js";
|
|
35
|
+
export { row } from "./primitives/row.js";
|
|
36
|
+
export { status } from "./primitives/status.js";
|
|
37
|
+
export { step, stepProgress, stepStates } from "./primitives/step-progress.js";
|
|
38
|
+
export { table } from "./primitives/table.js";
|
|
39
|
+
export {
|
|
40
|
+
chartColours,
|
|
41
|
+
chipColours,
|
|
42
|
+
colourTokens,
|
|
43
|
+
getColourToken,
|
|
44
|
+
getToneColour,
|
|
45
|
+
panelColours,
|
|
46
|
+
promptColours,
|
|
47
|
+
resolveColourValue,
|
|
48
|
+
tableColours,
|
|
49
|
+
terminalColours,
|
|
50
|
+
toneColours,
|
|
51
|
+
} from "./theme/colours.js";
|
|
52
|
+
export { getResultSymbol, getResultToken, resultTokens, resultTypes } from "./theme/results.js";
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { profiles } from "../profiles/profiles.js";
|
|
2
|
+
import { chip } from "../primitives/chip.js";
|
|
3
|
+
import {
|
|
4
|
+
isNonEmptyString,
|
|
5
|
+
isRecord,
|
|
6
|
+
normaliseStringList,
|
|
7
|
+
renderTitle,
|
|
8
|
+
} from "./helpers.js";
|
|
9
|
+
|
|
10
|
+
// Transcript roles map machine-facing values to stable textual labels and tones.
|
|
11
|
+
const transcriptRoles = {
|
|
12
|
+
agent: {
|
|
13
|
+
label: "Agent",
|
|
14
|
+
tone: "agent",
|
|
15
|
+
},
|
|
16
|
+
system: {
|
|
17
|
+
label: "System",
|
|
18
|
+
tone: "neutral",
|
|
19
|
+
},
|
|
20
|
+
tool: {
|
|
21
|
+
label: "Tool",
|
|
22
|
+
tone: "info",
|
|
23
|
+
},
|
|
24
|
+
user: {
|
|
25
|
+
label: "User",
|
|
26
|
+
tone: "user",
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Unsupported roles remain visible without inventing new semantics.
|
|
31
|
+
const fallbackRole = {
|
|
32
|
+
label: "Participant",
|
|
33
|
+
tone: "neutral",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Agent transcripts use a stable title when the caller does not provide one.
|
|
37
|
+
const defaultTitle = "Agent transcript";
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Render role-labelled agent exchanges and tool output.
|
|
41
|
+
*
|
|
42
|
+
* @param {object} transcript
|
|
43
|
+
* Structured transcript.
|
|
44
|
+
* @param {object[]} transcript.entries
|
|
45
|
+
* Role-labelled transcript entries.
|
|
46
|
+
* @param {string} transcript.title
|
|
47
|
+
* Transcript title.
|
|
48
|
+
* @param {object} options
|
|
49
|
+
* Rendering options.
|
|
50
|
+
* @returns {object|string}
|
|
51
|
+
* Structured JSON input or rendered transcript.
|
|
52
|
+
*/
|
|
53
|
+
export function agentTranscript(transcript, options = {}) {
|
|
54
|
+
if (options.profile === profiles.JSON) {
|
|
55
|
+
return transcript;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!isRecord(transcript)) {
|
|
59
|
+
return "";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const entries = normaliseEntries(transcript.entries);
|
|
63
|
+
const title = isNonEmptyString(transcript.title) ? transcript.title : defaultTitle;
|
|
64
|
+
|
|
65
|
+
if (entries.length === 0) {
|
|
66
|
+
return renderTitle(title, options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return [
|
|
70
|
+
renderTitle(title, options),
|
|
71
|
+
...entries.map((entry) => renderEntry(entry, options)),
|
|
72
|
+
].join("\n\n");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Return transcript entries with usable content.
|
|
77
|
+
*
|
|
78
|
+
* @param {*} entries
|
|
79
|
+
* Entries to normalise.
|
|
80
|
+
* @returns {object[]}
|
|
81
|
+
* Valid transcript entries.
|
|
82
|
+
*/
|
|
83
|
+
function normaliseEntries(entries) {
|
|
84
|
+
if (!Array.isArray(entries)) {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return entries
|
|
89
|
+
.filter(isRecord)
|
|
90
|
+
.map((entry) => ({
|
|
91
|
+
...entry,
|
|
92
|
+
content: normaliseContent(entry.content),
|
|
93
|
+
}))
|
|
94
|
+
.filter((entry) => entry.content.length > 0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Return transcript content as non-empty lines.
|
|
99
|
+
*
|
|
100
|
+
* @param {*} content
|
|
101
|
+
* Entry content.
|
|
102
|
+
* @returns {string[]}
|
|
103
|
+
* Content lines.
|
|
104
|
+
*/
|
|
105
|
+
function normaliseContent(content) {
|
|
106
|
+
if (isNonEmptyString(content)) {
|
|
107
|
+
return content.split("\n").filter(isNonEmptyString);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return normaliseStringList(content);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Render one transcript entry for the active profile.
|
|
115
|
+
*
|
|
116
|
+
* @param {object} entry
|
|
117
|
+
* Transcript entry.
|
|
118
|
+
* @param {object} options
|
|
119
|
+
* Rendering options.
|
|
120
|
+
* @returns {string}
|
|
121
|
+
* Rendered transcript entry.
|
|
122
|
+
*/
|
|
123
|
+
function renderEntry(entry, options) {
|
|
124
|
+
const role = transcriptRoles[entry.role] ?? fallbackRole;
|
|
125
|
+
const label = formatRoleLabel(role.label, entry.name);
|
|
126
|
+
|
|
127
|
+
if (options.profile === profiles.AGENT) {
|
|
128
|
+
return renderAgentEntry(label, entry.content, entry.role);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const renderedLabel = chip(label, role.tone, options);
|
|
132
|
+
|
|
133
|
+
if (entry.content.length === 1) {
|
|
134
|
+
return `${renderedLabel} ${entry.content[0]}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return [
|
|
138
|
+
renderedLabel,
|
|
139
|
+
...entry.content.map((line) => ` ${line}`),
|
|
140
|
+
].join("\n");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Render one Markdown transcript entry.
|
|
145
|
+
*
|
|
146
|
+
* @param {string} label
|
|
147
|
+
* Entry role label.
|
|
148
|
+
* @param {string[]} content
|
|
149
|
+
* Entry content lines.
|
|
150
|
+
* @param {string} role
|
|
151
|
+
* Machine-facing entry role.
|
|
152
|
+
* @returns {string}
|
|
153
|
+
* Markdown transcript entry.
|
|
154
|
+
*/
|
|
155
|
+
function renderAgentEntry(label, content, role) {
|
|
156
|
+
if (role === "tool") {
|
|
157
|
+
return [
|
|
158
|
+
`## ${label}`,
|
|
159
|
+
"```text",
|
|
160
|
+
...content,
|
|
161
|
+
"```",
|
|
162
|
+
].join("\n");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return [`## ${label}`, ...content].join("\n");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Add a source name to a role label when provided.
|
|
170
|
+
*
|
|
171
|
+
* @param {string} label
|
|
172
|
+
* Base role label.
|
|
173
|
+
* @param {*} name
|
|
174
|
+
* Optional source name.
|
|
175
|
+
* @returns {string}
|
|
176
|
+
* Complete role label.
|
|
177
|
+
*/
|
|
178
|
+
function formatRoleLabel(label, name) {
|
|
179
|
+
return isNonEmptyString(name) ? `${label}: ${name}` : label;
|
|
180
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { profiles } from "../profiles/profiles.js";
|
|
2
|
+
import { row } from "../primitives/row.js";
|
|
3
|
+
import { status } from "../primitives/status.js";
|
|
4
|
+
import { resultTypes } from "../theme/results.js";
|
|
5
|
+
import {
|
|
6
|
+
isNonEmptyString,
|
|
7
|
+
isRecord,
|
|
8
|
+
normaliseStringList,
|
|
9
|
+
renderSection,
|
|
10
|
+
renderTitle,
|
|
11
|
+
} from "./helpers.js";
|
|
12
|
+
|
|
13
|
+
// Audit findings use a stable title when the caller does not provide one.
|
|
14
|
+
const defaultTitle = "Audit finding";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Render one audit finding with evidence and remediation guidance.
|
|
18
|
+
*
|
|
19
|
+
* @param {object} finding
|
|
20
|
+
* Structured audit finding.
|
|
21
|
+
* @param {string[]} finding.evidence
|
|
22
|
+
* Evidence supporting the finding.
|
|
23
|
+
* @param {string} finding.finding
|
|
24
|
+
* Finding summary.
|
|
25
|
+
* @param {string} finding.location
|
|
26
|
+
* Source location or affected area.
|
|
27
|
+
* @param {string} finding.recommendation
|
|
28
|
+
* Recommended remediation.
|
|
29
|
+
* @param {string[]} finding.references
|
|
30
|
+
* Supporting standards or documentation.
|
|
31
|
+
* @param {string} finding.result
|
|
32
|
+
* Semantic severity state.
|
|
33
|
+
* @param {string} finding.title
|
|
34
|
+
* Pattern title.
|
|
35
|
+
* @param {object} options
|
|
36
|
+
* Rendering options.
|
|
37
|
+
* @returns {object|string}
|
|
38
|
+
* Structured JSON input or rendered audit finding.
|
|
39
|
+
*/
|
|
40
|
+
export function auditFinding(finding, options = {}) {
|
|
41
|
+
if (options.profile === profiles.JSON) {
|
|
42
|
+
return finding;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!isRecord(finding)) {
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const title = isNonEmptyString(finding.title) ? finding.title : defaultTitle;
|
|
50
|
+
const summary = isNonEmptyString(finding.finding) ? finding.finding : "";
|
|
51
|
+
const evidence = normaliseStringList(finding.evidence).map((item) => `- ${item}`);
|
|
52
|
+
const references = normaliseStringList(finding.references).map((item) => `- ${item}`);
|
|
53
|
+
const sections = [
|
|
54
|
+
status(finding.result ?? resultTypes.UNKNOWN, summary, options),
|
|
55
|
+
renderLocation(finding.location, options),
|
|
56
|
+
renderSection("Evidence", evidence, options, false),
|
|
57
|
+
renderTextSection("Recommendation", finding.recommendation, options),
|
|
58
|
+
renderSection("References", references, options, false),
|
|
59
|
+
].filter((section) => section !== "");
|
|
60
|
+
|
|
61
|
+
return [renderTitle(title, options), ...sections].join("\n\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Render the finding location for the active profile.
|
|
66
|
+
*
|
|
67
|
+
* @param {*} location
|
|
68
|
+
* Finding location.
|
|
69
|
+
* @param {object} options
|
|
70
|
+
* Rendering options.
|
|
71
|
+
* @returns {string}
|
|
72
|
+
* Rendered location.
|
|
73
|
+
*/
|
|
74
|
+
function renderLocation(location, options) {
|
|
75
|
+
if (!isNonEmptyString(location)) {
|
|
76
|
+
return "";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (options.profile === profiles.AGENT) {
|
|
80
|
+
return renderSection("Location", [location], options, false);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return row("Location", location, {
|
|
84
|
+
...options,
|
|
85
|
+
labelWidth: 8,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Render a section containing one text value.
|
|
91
|
+
*
|
|
92
|
+
* @param {string} title
|
|
93
|
+
* Section title.
|
|
94
|
+
* @param {*} value
|
|
95
|
+
* Section content.
|
|
96
|
+
* @param {object} options
|
|
97
|
+
* Rendering options.
|
|
98
|
+
* @returns {string}
|
|
99
|
+
* Rendered text section.
|
|
100
|
+
*/
|
|
101
|
+
function renderTextSection(title, value, options) {
|
|
102
|
+
if (!isNonEmptyString(value)) {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return renderSection(title, [value], options, false);
|
|
107
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { profiles } from "../profiles/profiles.js";
|
|
2
|
+
import { row } from "../primitives/row.js";
|
|
3
|
+
import { status } from "../primitives/status.js";
|
|
4
|
+
import { resultTypes } from "../theme/results.js";
|
|
5
|
+
import {
|
|
6
|
+
isNonEmptyString,
|
|
7
|
+
isRecord,
|
|
8
|
+
normaliseStringList,
|
|
9
|
+
renderSection,
|
|
10
|
+
renderTitle,
|
|
11
|
+
} from "./helpers.js";
|
|
12
|
+
|
|
13
|
+
// Command results use a stable title when the caller does not provide one.
|
|
14
|
+
const defaultTitle = "Command result";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Render one command outcome with metadata and supporting details.
|
|
18
|
+
*
|
|
19
|
+
* @param {object} result
|
|
20
|
+
* Structured command result.
|
|
21
|
+
* @param {string} result.command
|
|
22
|
+
* Executed command.
|
|
23
|
+
* @param {string[]} result.details
|
|
24
|
+
* Supporting result details.
|
|
25
|
+
* @param {string} result.duration
|
|
26
|
+
* Human-readable command duration.
|
|
27
|
+
* @param {number} result.exitCode
|
|
28
|
+
* Command exit code.
|
|
29
|
+
* @param {string} result.result
|
|
30
|
+
* Semantic result state.
|
|
31
|
+
* @param {string} result.summary
|
|
32
|
+
* Short outcome summary.
|
|
33
|
+
* @param {string} result.title
|
|
34
|
+
* Pattern title.
|
|
35
|
+
* @param {object} options
|
|
36
|
+
* Rendering options.
|
|
37
|
+
* @returns {object|string}
|
|
38
|
+
* Structured JSON input or rendered command result.
|
|
39
|
+
*/
|
|
40
|
+
export function commandResult(result, options = {}) {
|
|
41
|
+
if (options.profile === profiles.JSON) {
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!isRecord(result)) {
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const title = isNonEmptyString(result.title) ? result.title : defaultTitle;
|
|
50
|
+
const metadata = renderMetadata(result, options);
|
|
51
|
+
const details = normaliseStringList(result.details).map((detail) => `- ${detail}`);
|
|
52
|
+
const summary = isNonEmptyString(result.summary) ? result.summary : "";
|
|
53
|
+
const sections = [
|
|
54
|
+
status(result.result ?? resultTypes.UNKNOWN, summary, options),
|
|
55
|
+
metadata,
|
|
56
|
+
renderSection("Details", details, options, false),
|
|
57
|
+
].filter((section) => section !== "");
|
|
58
|
+
|
|
59
|
+
return [renderTitle(title, options), ...sections].join("\n\n");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Render command metadata for human or agent output.
|
|
64
|
+
*
|
|
65
|
+
* @param {object} result
|
|
66
|
+
* Structured command result.
|
|
67
|
+
* @param {object} options
|
|
68
|
+
* Rendering options.
|
|
69
|
+
* @returns {string}
|
|
70
|
+
* Rendered metadata.
|
|
71
|
+
*/
|
|
72
|
+
function renderMetadata(result, options) {
|
|
73
|
+
const entries = [
|
|
74
|
+
isNonEmptyString(result.command) ? ["Command", result.command] : null,
|
|
75
|
+
Number.isInteger(result.exitCode) ? ["Exit code", String(result.exitCode)] : null,
|
|
76
|
+
isNonEmptyString(result.duration) ? ["Duration", result.duration] : null,
|
|
77
|
+
].filter((entry) => entry !== null);
|
|
78
|
+
|
|
79
|
+
if (options.profile === profiles.AGENT) {
|
|
80
|
+
const lines = entries.map(([label, value]) => `- ${label}: ${value}`);
|
|
81
|
+
|
|
82
|
+
return renderSection("Metadata", lines, options, false);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return entries
|
|
86
|
+
.map(([label, value]) => row(label, value, {
|
|
87
|
+
...options,
|
|
88
|
+
labelWidth: 9,
|
|
89
|
+
}))
|
|
90
|
+
.join("\n");
|
|
91
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { profiles } from "../profiles/profiles.js";
|
|
2
|
+
import { table } from "../primitives/table.js";
|
|
3
|
+
import {
|
|
4
|
+
isNonEmptyString,
|
|
5
|
+
isRecord,
|
|
6
|
+
renderTitle,
|
|
7
|
+
} from "./helpers.js";
|
|
8
|
+
|
|
9
|
+
// Compact data tables use a stable title when the caller does not provide one.
|
|
10
|
+
const defaultTitle = "Data";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Render dense records with optional summary context.
|
|
14
|
+
*
|
|
15
|
+
* @param {object} data
|
|
16
|
+
* Structured table data.
|
|
17
|
+
* @param {object[]} data.columns
|
|
18
|
+
* Table column keys and labels.
|
|
19
|
+
* @param {object[]} data.rows
|
|
20
|
+
* Table records.
|
|
21
|
+
* @param {string} data.summary
|
|
22
|
+
* Optional summary context.
|
|
23
|
+
* @param {string} data.title
|
|
24
|
+
* Pattern title.
|
|
25
|
+
* @param {object} options
|
|
26
|
+
* Rendering options.
|
|
27
|
+
* @returns {object|string}
|
|
28
|
+
* Structured JSON input or rendered compact table.
|
|
29
|
+
*/
|
|
30
|
+
export function compactDataTable(data, options = {}) {
|
|
31
|
+
if (options.profile === profiles.JSON) {
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!isRecord(data) || !hasEntries(data.columns) || !hasEntries(data.rows)) {
|
|
36
|
+
return "";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const title = isNonEmptyString(data.title) ? data.title : defaultTitle;
|
|
40
|
+
const summary = isNonEmptyString(data.summary) ? data.summary : "";
|
|
41
|
+
const renderedTable = table({
|
|
42
|
+
...options,
|
|
43
|
+
columns: data.columns,
|
|
44
|
+
rows: data.rows,
|
|
45
|
+
});
|
|
46
|
+
const heading = summary === ""
|
|
47
|
+
? renderTitle(title, options)
|
|
48
|
+
: [renderTitle(title, options), summary].join("\n");
|
|
49
|
+
|
|
50
|
+
if (renderedTable === "") {
|
|
51
|
+
return "";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return [heading, renderedTable].join("\n\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check whether a value is a non-empty array.
|
|
59
|
+
*
|
|
60
|
+
* @param {*} value
|
|
61
|
+
* Value to check.
|
|
62
|
+
* @returns {boolean}
|
|
63
|
+
* Whether the value contains entries.
|
|
64
|
+
*/
|
|
65
|
+
function hasEntries(value) {
|
|
66
|
+
return Array.isArray(value) && value.length > 0;
|
|
67
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { profiles } from "../profiles/profiles.js";
|
|
2
|
+
import { status } from "../primitives/status.js";
|
|
3
|
+
import { resultTypes } from "../theme/results.js";
|
|
4
|
+
import {
|
|
5
|
+
formatLabel,
|
|
6
|
+
isNonEmptyString,
|
|
7
|
+
isRecord,
|
|
8
|
+
renderSection,
|
|
9
|
+
renderTitle,
|
|
10
|
+
} from "./helpers.js";
|
|
11
|
+
|
|
12
|
+
// Confirmation states map workflow outcomes onto shared result semantics.
|
|
13
|
+
const confirmationStates = {
|
|
14
|
+
cancelled: {
|
|
15
|
+
label: "Cancelled",
|
|
16
|
+
result: resultTypes.SKIPPED,
|
|
17
|
+
},
|
|
18
|
+
confirmed: {
|
|
19
|
+
label: "Confirmed",
|
|
20
|
+
result: resultTypes.SUCCESS,
|
|
21
|
+
},
|
|
22
|
+
declined: {
|
|
23
|
+
label: "Declined",
|
|
24
|
+
result: resultTypes.WARNING,
|
|
25
|
+
},
|
|
26
|
+
failed: {
|
|
27
|
+
label: "Failed",
|
|
28
|
+
result: resultTypes.FAILED,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Unsupported confirmation states remain visibly unknown.
|
|
33
|
+
const fallbackState = {
|
|
34
|
+
label: "Unknown",
|
|
35
|
+
result: resultTypes.UNKNOWN,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Confirmation results use a stable title when the caller does not provide one.
|
|
39
|
+
const defaultTitle = "Confirmation result";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Render one confirmation workflow outcome.
|
|
43
|
+
*
|
|
44
|
+
* @param {object} confirmation
|
|
45
|
+
* Structured confirmation result.
|
|
46
|
+
* @param {string} confirmation.action
|
|
47
|
+
* Action requested or completed.
|
|
48
|
+
* @param {string} confirmation.detail
|
|
49
|
+
* Supporting outcome detail.
|
|
50
|
+
* @param {string} confirmation.item
|
|
51
|
+
* Affected item.
|
|
52
|
+
* @param {string} confirmation.state
|
|
53
|
+
* Confirmation state.
|
|
54
|
+
* @param {string} confirmation.title
|
|
55
|
+
* Pattern title.
|
|
56
|
+
* @param {object} options
|
|
57
|
+
* Rendering options.
|
|
58
|
+
* @returns {object|string}
|
|
59
|
+
* Structured JSON input or rendered confirmation result.
|
|
60
|
+
*/
|
|
61
|
+
export function confirmationResult(confirmation, options = {}) {
|
|
62
|
+
if (options.profile === profiles.JSON) {
|
|
63
|
+
return confirmation;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!isRecord(confirmation)) {
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const title = isNonEmptyString(confirmation.title)
|
|
71
|
+
? confirmation.title
|
|
72
|
+
: defaultTitle;
|
|
73
|
+
const action = isNonEmptyString(confirmation.action) ? confirmation.action : "";
|
|
74
|
+
const summary = formatLabel(action, confirmation.item, options);
|
|
75
|
+
const state = confirmationStates[confirmation.state] ?? fallbackState;
|
|
76
|
+
const detail = isNonEmptyString(confirmation.detail)
|
|
77
|
+
? renderSection("Detail", [confirmation.detail], options, false)
|
|
78
|
+
: "";
|
|
79
|
+
const sections = [
|
|
80
|
+
status(state.result, summary, {
|
|
81
|
+
...options,
|
|
82
|
+
label: state.label,
|
|
83
|
+
}),
|
|
84
|
+
detail,
|
|
85
|
+
].filter((section) => section !== "");
|
|
86
|
+
|
|
87
|
+
return [renderTitle(title, options), ...sections].join("\n\n");
|
|
88
|
+
}
|