@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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/adapters/bash/cli-style.sh +50 -0
  4. package/adapters/python/cli_style.py +104 -0
  5. package/bin/cli-style.js +82 -0
  6. package/examples/diagnostic.mjs +30 -0
  7. package/examples/diagnostic.py +38 -0
  8. package/examples/diagnostic.sh +29 -0
  9. package/examples/gallery.mjs +18 -0
  10. package/package.json +42 -0
  11. package/src/capability/resolve-profile.js +69 -0
  12. package/src/capability/terminal.js +105 -0
  13. package/src/cli/gallery-command.js +238 -0
  14. package/src/cli/help.js +35 -0
  15. package/src/cli/render-command.js +283 -0
  16. package/src/create-cli-style.js +53 -0
  17. package/src/formatters/ansi.js +154 -0
  18. package/src/gallery/render-gallery.js +758 -0
  19. package/src/index.js +52 -0
  20. package/src/patterns/agent-transcript.js +180 -0
  21. package/src/patterns/audit-finding.js +107 -0
  22. package/src/patterns/command-result.js +91 -0
  23. package/src/patterns/compact-data-table.js +67 -0
  24. package/src/patterns/confirmation-result.js +88 -0
  25. package/src/patterns/diagnostic-report.js +144 -0
  26. package/src/patterns/helpers.js +106 -0
  27. package/src/patterns/next-step-block.js +118 -0
  28. package/src/patterns/task-summary.js +59 -0
  29. package/src/primitives/bar-chart.js +131 -0
  30. package/src/primitives/chip.js +33 -0
  31. package/src/primitives/divider.js +64 -0
  32. package/src/primitives/feedback.js +83 -0
  33. package/src/primitives/panel.js +100 -0
  34. package/src/primitives/progress-bar.js +121 -0
  35. package/src/primitives/row.js +80 -0
  36. package/src/primitives/status.js +39 -0
  37. package/src/primitives/step-progress.js +184 -0
  38. package/src/primitives/table.js +239 -0
  39. package/src/profiles/profiles.js +24 -0
  40. package/src/theme/colours.js +141 -0
  41. package/src/theme/results.js +98 -0
@@ -0,0 +1,758 @@
1
+ import { agentTranscript } from "../patterns/agent-transcript.js";
2
+ import { auditFinding } from "../patterns/audit-finding.js";
3
+ import { commandResult } from "../patterns/command-result.js";
4
+ import { compactDataTable } from "../patterns/compact-data-table.js";
5
+ import { confirmationResult } from "../patterns/confirmation-result.js";
6
+ import { diagnosticReport } from "../patterns/diagnostic-report.js";
7
+ import { nextStepBlock } from "../patterns/next-step-block.js";
8
+ import { taskSummary } from "../patterns/task-summary.js";
9
+ import { stripAnsi } from "../formatters/ansi.js";
10
+ import { barChart } from "../primitives/bar-chart.js";
11
+ import { chip } from "../primitives/chip.js";
12
+ import { divider } from "../primitives/divider.js";
13
+ import { emptyState, errorBlock, hint } from "../primitives/feedback.js";
14
+ import { panel } from "../primitives/panel.js";
15
+ import { progressBar } from "../primitives/progress-bar.js";
16
+ import { row } from "../primitives/row.js";
17
+ import { status } from "../primitives/status.js";
18
+ import { step, stepProgress, stepStates } from "../primitives/step-progress.js";
19
+ import { table } from "../primitives/table.js";
20
+ import { resultTypes } from "../theme/results.js";
21
+
22
+ // Chip tone examples should expose every configured pill colour.
23
+ const chipToneExamples = [
24
+ {
25
+ label: "neutral",
26
+ tone: "neutral",
27
+ },
28
+ {
29
+ label: "info",
30
+ tone: "info",
31
+ },
32
+ {
33
+ label: "success",
34
+ tone: "success",
35
+ },
36
+ {
37
+ label: "warning",
38
+ tone: "warning",
39
+ },
40
+ {
41
+ label: "danger",
42
+ tone: "danger",
43
+ },
44
+ ];
45
+
46
+ // Status examples should expose every configured result tone and symbol.
47
+ const statusExamples = [
48
+ {
49
+ detail: "tone: success",
50
+ resultType: resultTypes.SUCCESS,
51
+ },
52
+ {
53
+ detail: "tone: warning",
54
+ resultType: resultTypes.WARNING,
55
+ },
56
+ {
57
+ detail: "tone: warning",
58
+ resultType: resultTypes.PARTIAL,
59
+ },
60
+ {
61
+ detail: "tone: muted",
62
+ resultType: resultTypes.SKIPPED,
63
+ },
64
+ {
65
+ detail: "tone: muted",
66
+ resultType: resultTypes.UNKNOWN,
67
+ },
68
+ {
69
+ detail: "tone: danger",
70
+ resultType: resultTypes.FAILED,
71
+ },
72
+ ];
73
+
74
+ // Panel examples should expose every configured semantic accent.
75
+ const panelToneExamples = [
76
+ "info",
77
+ "success",
78
+ "warning",
79
+ "danger",
80
+ ];
81
+
82
+ // Progress examples expose common completion states and semantic tones.
83
+ const progressExamples = [
84
+ {
85
+ tone: "info",
86
+ value: 0,
87
+ },
88
+ {
89
+ tone: "success",
90
+ value: 25,
91
+ },
92
+ {
93
+ tone: "warning",
94
+ value: 50,
95
+ },
96
+ {
97
+ tone: "danger",
98
+ value: 75,
99
+ },
100
+ {
101
+ tone: "success",
102
+ value: 100,
103
+ },
104
+ ];
105
+
106
+ // Gallery variants available through the public renderer and CLI.
107
+ export const galleryVariants = [
108
+ "current",
109
+ "no-colour",
110
+ "no-unicode",
111
+ "plain",
112
+ ];
113
+
114
+ // Gallery sections available for focused output.
115
+ export const gallerySections = [
116
+ "primitives",
117
+ "patterns",
118
+ ];
119
+
120
+ // Pattern fixtures available for focused output and interactive search.
121
+ export const galleryFixtures = [
122
+ "diagnostic-report",
123
+ "command-result",
124
+ "task-summary",
125
+ "agent-transcript",
126
+ "audit-finding",
127
+ "compact-data-table",
128
+ "compact-data-table-narrow",
129
+ "confirmation-result",
130
+ "next-step-block",
131
+ ];
132
+
133
+ /**
134
+ * Render the read-only style gallery.
135
+ *
136
+ * @param {object} options
137
+ * Rendering options.
138
+ * @param {object} request
139
+ * Gallery selection request.
140
+ * @returns {string}
141
+ * Gallery output.
142
+ */
143
+ export function renderGallery(options = {}, request = {}) {
144
+ const galleryOptions = request.width === undefined
145
+ ? options
146
+ : {
147
+ ...galleryWidthOptions(request.width),
148
+ ...options,
149
+ width: request.width,
150
+ };
151
+ const variants = request.variants === true || request.matrix === true
152
+ ? galleryVariants
153
+ : [request.variant ?? "current"];
154
+
155
+ const output = [
156
+ "CLI style gallery",
157
+ ...variants.flatMap((variant) => [
158
+ "",
159
+ renderVariant(variant, galleryOptions, request),
160
+ ]),
161
+ ].join("\n");
162
+
163
+ return request.width === undefined ? output : clampGalleryOutput(output, request.width);
164
+ }
165
+
166
+ /**
167
+ * Render one gallery capability variant.
168
+ *
169
+ * @param {string} variant
170
+ * Variant name.
171
+ * @param {object} options
172
+ * Rendering options.
173
+ * @param {object} request
174
+ * Gallery selection request.
175
+ * @returns {string}
176
+ * Variant gallery output.
177
+ */
178
+ function renderVariant(variant, options, request) {
179
+ const resolvedVariant = resolveVariant(variant, options);
180
+ const sections = [];
181
+
182
+ if (request.fixture !== undefined) {
183
+ sections.push(renderPatterns(resolvedVariant.options, request.fixture));
184
+ } else {
185
+ if (request.section === undefined || request.section === "primitives") {
186
+ sections.push(renderPrimitives(resolvedVariant.options));
187
+ }
188
+
189
+ if (request.section === undefined || request.section === "patterns") {
190
+ sections.push(renderPatterns(resolvedVariant.options));
191
+ }
192
+ }
193
+
194
+ return [
195
+ divider({
196
+ ...resolvedVariant.options,
197
+ label: resolvedVariant.title,
198
+ }),
199
+ "",
200
+ ...joinSections(sections),
201
+ ].join("\n");
202
+ }
203
+
204
+ /**
205
+ * Resolve one gallery variant title and rendering options.
206
+ *
207
+ * @param {string} variant
208
+ * Variant name.
209
+ * @param {object} options
210
+ * Base rendering options.
211
+ * @returns {object}
212
+ * Variant title and options.
213
+ */
214
+ function resolveVariant(variant, options) {
215
+ if (variant === "no-colour") {
216
+ return {
217
+ options: {
218
+ ...options,
219
+ colour: false,
220
+ },
221
+ title: "No colour",
222
+ };
223
+ }
224
+
225
+ if (variant === "no-unicode") {
226
+ return {
227
+ options: {
228
+ ...options,
229
+ unicode: false,
230
+ },
231
+ title: "No Unicode",
232
+ };
233
+ }
234
+
235
+ if (variant === "plain") {
236
+ return {
237
+ options: {
238
+ ...options,
239
+ colour: false,
240
+ unicode: false,
241
+ },
242
+ title: "Plain",
243
+ };
244
+ }
245
+
246
+ return {
247
+ options,
248
+ title: "Current terminal",
249
+ };
250
+ }
251
+
252
+ /**
253
+ * Add spacing between non-empty gallery sections.
254
+ *
255
+ * @param {string[]} sections
256
+ * Gallery sections.
257
+ * @returns {string[]}
258
+ * Sections with blank-line separators.
259
+ */
260
+ function joinSections(sections) {
261
+ return sections.flatMap((section, index) => index === 0 ? [section] : ["", section]);
262
+ }
263
+
264
+ /**
265
+ * Return component widths derived from a gallery width request.
266
+ *
267
+ * @param {number} width
268
+ * Requested gallery width.
269
+ * @returns {object}
270
+ * Width options for fixed-width renderers.
271
+ */
272
+ function galleryWidthOptions(width) {
273
+ return {
274
+ barWidth: Math.max(width - 18, 1),
275
+ dividerWidth: width,
276
+ panelWidth: width,
277
+ };
278
+ }
279
+
280
+ /**
281
+ * Keep generated gallery lines inside a requested visible width.
282
+ *
283
+ * @param {string} output
284
+ * Gallery output.
285
+ * @param {number} width
286
+ * Requested visible width.
287
+ * @returns {string}
288
+ * Width-constrained output.
289
+ */
290
+ function clampGalleryOutput(output, width) {
291
+ return output
292
+ .split("\n")
293
+ .map((line) => clampLine(line, width))
294
+ .join("\n");
295
+ }
296
+
297
+ /**
298
+ * Truncate one line to a visible width.
299
+ *
300
+ * @param {string} line
301
+ * Output line.
302
+ * @param {number} width
303
+ * Requested visible width.
304
+ * @returns {string}
305
+ * Truncated line.
306
+ */
307
+ function clampLine(line, width) {
308
+ const plainLine = stripAnsi(line);
309
+
310
+ if (plainLine.length <= width) {
311
+ return line;
312
+ }
313
+
314
+ if (width <= 1) {
315
+ return plainLine.slice(0, width);
316
+ }
317
+
318
+ return `${plainLine.slice(0, width - 1)}~`;
319
+ }
320
+
321
+ /**
322
+ * Render composed output pattern examples.
323
+ *
324
+ * @param {object} options
325
+ * Rendering options.
326
+ * @param {string} fixture
327
+ * Optional fixture name.
328
+ * @returns {string}
329
+ * Pattern gallery output.
330
+ */
331
+ function renderPatterns(options, fixture) {
332
+ const audit = auditFinding({
333
+ evidence: [
334
+ "Text contrast measures 3.2:1",
335
+ "Body text requires at least 4.5:1",
336
+ ],
337
+ finding: "Muted text has insufficient contrast",
338
+ location: "src/components/StatusCard.vue:42",
339
+ recommendation: "Use the standard foreground token.",
340
+ references: [
341
+ "WCAG 2.2 SC 1.4.3",
342
+ ],
343
+ result: resultTypes.FAILED,
344
+ title: "Accessibility audit",
345
+ }, options);
346
+ const compactTableData = {
347
+ columns: [
348
+ {
349
+ key: "package",
350
+ label: "Package",
351
+ },
352
+ {
353
+ key: "status",
354
+ label: "Status",
355
+ },
356
+ {
357
+ key: "version",
358
+ label: "Version",
359
+ },
360
+ ],
361
+ rows: [
362
+ {
363
+ package: "components",
364
+ status: "minor",
365
+ version: "2.8.0",
366
+ },
367
+ {
368
+ package: "helpers",
369
+ status: "current",
370
+ version: "4.1.0",
371
+ },
372
+ ],
373
+ summary: "2 packages checked",
374
+ title: "Package updates",
375
+ };
376
+ const compactTable = compactDataTable(compactTableData, options);
377
+ const narrowCompactTable = compactDataTable(compactTableData, {
378
+ ...options,
379
+ width: 20,
380
+ });
381
+ const confirmation = confirmationResult({
382
+ action: "Delete project",
383
+ detail: "Project and 12 files removed.",
384
+ item: "Website refresh",
385
+ state: "confirmed",
386
+ }, options);
387
+ const command = commandResult({
388
+ command: "bun run test:unit",
389
+ details: [
390
+ "121 tests passed",
391
+ "Log saved to .agent/diagnostics/test-unit.log",
392
+ ],
393
+ duration: "158 ms",
394
+ exitCode: 0,
395
+ result: resultTypes.SUCCESS,
396
+ summary: "Unit tests passed",
397
+ title: "Unit test command",
398
+ }, options);
399
+ const report = diagnosticReport({
400
+ checks: [
401
+ {
402
+ detail: "184 tests",
403
+ name: "unit",
404
+ result: resultTypes.SUCCESS,
405
+ },
406
+ {
407
+ detail: "2 failures",
408
+ name: "type-check",
409
+ result: resultTypes.FAILED,
410
+ },
411
+ ],
412
+ findings: [
413
+ {
414
+ message: "Coverage below target",
415
+ result: resultTypes.WARNING,
416
+ },
417
+ ],
418
+ nextActions: [
419
+ "Review failing checks",
420
+ "Re-run diagnostics",
421
+ ],
422
+ skippedChecks: [
423
+ {
424
+ name: "e2e",
425
+ reason: "Browser unavailable",
426
+ },
427
+ ],
428
+ title: "Project diagnostics",
429
+ }, options);
430
+ const nextStep = nextStepBlock({
431
+ alternatives: [
432
+ "Review the focused gallery fixture",
433
+ ],
434
+ commands: [
435
+ "git status --short",
436
+ "bun run test:unit",
437
+ ],
438
+ next: "Commit the completed pattern chunk",
439
+ reason: "Implementation and verification are complete.",
440
+ }, options);
441
+ const summary = taskSummary({
442
+ completed: [
443
+ "Added command result renderer",
444
+ "Added task summary renderer",
445
+ ],
446
+ remaining: [
447
+ "Review gallery output",
448
+ ],
449
+ result: resultTypes.PARTIAL,
450
+ summary: "Implementation complete",
451
+ task: "Add command and task patterns",
452
+ title: "Pattern implementation",
453
+ }, options);
454
+ const transcript = agentTranscript({
455
+ entries: [
456
+ {
457
+ content: "Run the focused unit tests.",
458
+ role: "user",
459
+ },
460
+ {
461
+ content: "Running project diagnostics.",
462
+ role: "agent",
463
+ },
464
+ {
465
+ content: [
466
+ "128 tests passed",
467
+ "0 tests failed",
468
+ ],
469
+ name: "project-diagnostics",
470
+ role: "tool",
471
+ },
472
+ {
473
+ content: "Tests pass. Nothing remains.",
474
+ role: "agent",
475
+ },
476
+ ],
477
+ title: "Focused verification",
478
+ }, options);
479
+ const fixtures = {
480
+ "agent-transcript": ["Agent transcript", transcript],
481
+ "audit-finding": ["Audit finding", audit],
482
+ "command-result": ["Command result", command],
483
+ "compact-data-table": ["Compact data table", compactTable],
484
+ "compact-data-table-narrow": ["Compact data table (narrow)", narrowCompactTable],
485
+ "confirmation-result": ["Confirmation result", confirmation],
486
+ "diagnostic-report": ["Diagnostic report", report],
487
+ "next-step-block": ["Next-step block", nextStep],
488
+ "task-summary": ["Task summary", summary],
489
+ };
490
+
491
+ if (fixture !== undefined) {
492
+ return renderPatternFixture(fixtures[fixture], options);
493
+ }
494
+
495
+ return [
496
+ divider({
497
+ ...options,
498
+ label: "Patterns",
499
+ }),
500
+ "",
501
+ "Diagnostic report",
502
+ frameExample(report, options),
503
+ "",
504
+ "Command result",
505
+ frameExample(command, options),
506
+ "",
507
+ "Task summary",
508
+ frameExample(summary, options),
509
+ "",
510
+ "Agent transcript",
511
+ frameExample(transcript, options),
512
+ "",
513
+ "Audit finding",
514
+ frameExample(audit, options),
515
+ "",
516
+ "Compact data table",
517
+ frameExample(compactTable, options),
518
+ "",
519
+ "Compact data table (narrow)",
520
+ frameExample(narrowCompactTable, options),
521
+ "",
522
+ "Confirmation result",
523
+ frameExample(confirmation, options),
524
+ "",
525
+ "Next-step block",
526
+ frameExample(nextStep, options),
527
+ ].join("\n");
528
+ }
529
+
530
+ /**
531
+ * Render one named pattern fixture.
532
+ *
533
+ * @param {string[]} fixture
534
+ * Fixture label and output.
535
+ * @param {object} options
536
+ * Rendering options.
537
+ * @returns {string}
538
+ * Framed fixture output.
539
+ */
540
+ function renderPatternFixture(fixture, options) {
541
+ return [
542
+ fixture[0],
543
+ frameExample(fixture[1], options),
544
+ ].join("\n");
545
+ }
546
+
547
+ /**
548
+ * Add gallery-only framing around one rendered example.
549
+ *
550
+ * @param {string} output
551
+ * Rendered example output.
552
+ * @param {object} options
553
+ * Rendering options.
554
+ * @returns {string}
555
+ * Framed gallery example.
556
+ */
557
+ function frameExample(output, options) {
558
+ const rail = options.unicode === false ? "|" : "│";
559
+
560
+ return output
561
+ .split("\n")
562
+ .map((line) => line === "" ? rail : `${rail} ${line}`)
563
+ .join("\n");
564
+ }
565
+
566
+ /**
567
+ * Render primitive examples.
568
+ *
569
+ * @param {object} options
570
+ * Rendering options.
571
+ * @returns {string}
572
+ * Primitive gallery output.
573
+ */
574
+ function renderPrimitives(options) {
575
+ return [
576
+ "Primitives",
577
+ "",
578
+ "Chips",
579
+ renderChipExamples(options),
580
+ "",
581
+ "Statuses",
582
+ ...renderStatusExamples(options),
583
+ "",
584
+ "Panels",
585
+ ...renderPanelExamples(options),
586
+ "",
587
+ "Progress bars",
588
+ ...renderProgressExamples(options),
589
+ "",
590
+ "Bar charts",
591
+ renderBarChartExample(options),
592
+ "",
593
+ "Tables",
594
+ renderTableExample(options),
595
+ "",
596
+ "Narrow tables",
597
+ renderTableExample({
598
+ ...options,
599
+ width: 18,
600
+ }),
601
+ "",
602
+ "Feedback",
603
+ hint("tone: info", options),
604
+ emptyState("No results", "tone: muted", options),
605
+ errorBlock("Failed", ["tone: danger"], {
606
+ ...options,
607
+ panelWidth: 40,
608
+ }),
609
+ "",
610
+ "Steps",
611
+ step("complete", stepStates.COMPLETE, options),
612
+ step("current", stepStates.CURRENT, options),
613
+ step("pending", stepStates.PENDING, options),
614
+ step("failed", resultTypes.FAILED, options),
615
+ "",
616
+ "Step progress",
617
+ stepProgress({
618
+ ...options,
619
+ current: 1,
620
+ steps: ["complete", "current", "pending"],
621
+ }),
622
+ "",
623
+ "Rows",
624
+ row("Package", "@lewishowles/components", {
625
+ ...options,
626
+ labelWidth: 8,
627
+ }),
628
+ row("Version", "minor: 2.7.4 -> 2.8.0", {
629
+ ...options,
630
+ labelWidth: 8,
631
+ }),
632
+ row("Checks", "type-check, unit, build", {
633
+ ...options,
634
+ labelWidth: 8,
635
+ }),
636
+ ].join("\n");
637
+ }
638
+
639
+ /**
640
+ * Render wide or narrow table diagnostics.
641
+ *
642
+ * @param {object} options
643
+ * Rendering options.
644
+ * @returns {string}
645
+ * Table example.
646
+ */
647
+ function renderTableExample(options) {
648
+ return table({
649
+ ...options,
650
+ columns: [
651
+ {
652
+ key: "check",
653
+ label: "Check",
654
+ },
655
+ {
656
+ key: "result",
657
+ label: "Result",
658
+ },
659
+ ],
660
+ rows: [
661
+ {
662
+ check: "type-check",
663
+ result: "passed",
664
+ },
665
+ {
666
+ check: "unit",
667
+ result: "warning",
668
+ },
669
+ ],
670
+ });
671
+ }
672
+
673
+ /**
674
+ * Render a chart that exposes positive, warning, and negative colours.
675
+ *
676
+ * @param {object} options
677
+ * Rendering options.
678
+ * @returns {string}
679
+ * Bar chart example.
680
+ */
681
+ function renderBarChartExample(options) {
682
+ return barChart({
683
+ ...options,
684
+ rows: [
685
+ {
686
+ label: "positive",
687
+ value: 10,
688
+ },
689
+ {
690
+ label: "warning",
691
+ tone: "warning",
692
+ value: 6,
693
+ },
694
+ {
695
+ label: "negative",
696
+ value: -3,
697
+ },
698
+ ],
699
+ });
700
+ }
701
+
702
+ /**
703
+ * Render common progress values and tones.
704
+ *
705
+ * @param {object} options
706
+ * Rendering options.
707
+ * @returns {string[]}
708
+ * Progress examples.
709
+ */
710
+ function renderProgressExamples(options) {
711
+ return progressExamples.map((example) => progressBar({
712
+ ...options,
713
+ max: 100,
714
+ tone: example.tone,
715
+ value: example.value,
716
+ }));
717
+ }
718
+
719
+ /**
720
+ * Render all panel tone examples.
721
+ *
722
+ * @param {object} options
723
+ * Rendering options.
724
+ * @returns {string[]}
725
+ * Panel examples.
726
+ */
727
+ function renderPanelExamples(options) {
728
+ return panelToneExamples.map((tone) => panel({
729
+ ...options,
730
+ lines: [`tone: ${tone}`],
731
+ title: tone,
732
+ tone,
733
+ }));
734
+ }
735
+
736
+ /**
737
+ * Render all chip tone examples.
738
+ *
739
+ * @param {object} options
740
+ * Rendering options.
741
+ * @returns {string}
742
+ * Chip examples.
743
+ */
744
+ function renderChipExamples(options) {
745
+ return chipToneExamples.map((example) => chip(example.label, example.tone, options)).join(" ");
746
+ }
747
+
748
+ /**
749
+ * Render all status examples.
750
+ *
751
+ * @param {object} options
752
+ * Rendering options.
753
+ * @returns {string[]}
754
+ * Status examples.
755
+ */
756
+ function renderStatusExamples(options) {
757
+ return statusExamples.map((example) => status(example.resultType, example.detail, options));
758
+ }