bigpowers 1.3.2 → 1.4.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 (69) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/SKILL-INDEX.md +9 -9
  3. package/audit-code/SKILL.md +12 -0
  4. package/build-epic/SKILL.md +7 -4
  5. package/commit-message/SKILL.md +12 -0
  6. package/compose-workflow/SKILL.md +2 -0
  7. package/dashboard/bin/dashboard.js +93 -0
  8. package/dashboard/package-lock.json +1035 -0
  9. package/dashboard/package.json +18 -0
  10. package/dashboard/src/data/gate-status.js +32 -0
  11. package/dashboard/src/data/metrics.js +89 -0
  12. package/dashboard/src/data/pipeline-map.js +32 -0
  13. package/dashboard/src/data/reader.js +122 -0
  14. package/dashboard/src/data/watcher.js +108 -0
  15. package/dashboard/src/loaders/gate-status.js +32 -0
  16. package/dashboard/src/loaders/metrics.js +89 -0
  17. package/dashboard/src/loaders/pipeline-map.js +32 -0
  18. package/dashboard/src/loaders/reader.js +122 -0
  19. package/dashboard/src/loaders/watcher.js +108 -0
  20. package/dashboard/src/tui/epic-queue.js +36 -0
  21. package/dashboard/src/tui/filesystem.js +95 -0
  22. package/dashboard/src/tui/index.js +161 -0
  23. package/dashboard/src/tui/ledger.js +66 -0
  24. package/dashboard/src/tui/metrics-bar.js +30 -0
  25. package/dashboard/src/tui/pipeline.js +46 -0
  26. package/dashboard/src/tui/state-yaml.js +49 -0
  27. package/dashboard/src/web/client.html +477 -0
  28. package/dashboard/src/web/server.js +112 -0
  29. package/dashboard/test/fixtures/state.yaml +2 -0
  30. package/deepen-architecture/SKILL.md +2 -0
  31. package/define-language/SKILL.md +2 -0
  32. package/define-success/SKILL.md +4 -0
  33. package/delegate-task/SKILL.md +2 -0
  34. package/design-interface/SKILL.md +2 -0
  35. package/develop-tdd/SKILL.md +32 -0
  36. package/dispatch-agents/SKILL.md +2 -0
  37. package/edit-document/SKILL.md +6 -0
  38. package/elaborate-spec/SKILL.md +2 -0
  39. package/enforce-first/SKILL.md +2 -0
  40. package/grill-me/SKILL.md +2 -0
  41. package/guard-git/SKILL.md +2 -0
  42. package/hook-commits/SKILL.md +2 -0
  43. package/inspect-quality/SKILL.md +2 -0
  44. package/kickoff-branch/SKILL.md +5 -0
  45. package/map-codebase/SKILL.md +2 -0
  46. package/model-domain/SKILL.md +4 -0
  47. package/orchestrate-project/REFERENCE.md +1 -1
  48. package/orchestrate-project/SKILL.md +3 -1
  49. package/organize-workspace/SKILL.md +2 -0
  50. package/package.json +4 -2
  51. package/plan-refactor/SKILL.md +2 -0
  52. package/plan-work/SKILL.md +44 -0
  53. package/release-branch/SKILL.md +28 -0
  54. package/respond-review/SKILL.md +2 -0
  55. package/run-planning/SKILL.md +2 -0
  56. package/search-skills/SKILL.md +2 -0
  57. package/seed-conventions/SKILL.md +2 -0
  58. package/session-state/SKILL.md +16 -0
  59. package/setup-environment/SKILL.md +2 -0
  60. package/simulate-agents/SKILL.md +2 -0
  61. package/spike-prototype/SKILL.md +2 -0
  62. package/stocktake-skills/SKILL.md +2 -0
  63. package/survey-context/SKILL.md +23 -0
  64. package/terse-mode/SKILL.md +2 -0
  65. package/using-bigpowers/SKILL.md +4 -0
  66. package/validate-fix/SKILL.md +2 -0
  67. package/verify-work/SKILL.md +33 -0
  68. package/visual-dashboard/SKILL.md +2 -0
  69. package/wire-observability/SKILL.md +2 -0
@@ -0,0 +1,108 @@
1
+ const EventEmitter = require('events');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ let chokidar;
6
+ try {
7
+ chokidar = require('chokidar');
8
+ } catch (err) {
9
+ chokidar = null;
10
+ }
11
+
12
+ function watch(projectRoot) {
13
+ const emitter = new EventEmitter();
14
+ const debounceTimers = {};
15
+
16
+ function notifyChange(file) {
17
+ const key = file;
18
+ if (debounceTimers[key]) {
19
+ clearTimeout(debounceTimers[key]);
20
+ }
21
+ debounceTimers[key] = setTimeout(() => {
22
+ emitter.emit('change', { file, data: null });
23
+ delete debounceTimers[key];
24
+ }, 300);
25
+ }
26
+
27
+ if (chokidar) {
28
+ const filesToWatch = [
29
+ path.join(projectRoot, 'specs/state.yaml'),
30
+ path.join(projectRoot, 'specs/execution-status.yaml'),
31
+ path.join(projectRoot, 'specs/metrics/cycle-times.yaml'),
32
+ path.join(projectRoot, 'specs/epics')
33
+ ];
34
+
35
+ const watcher = chokidar.watch(filesToWatch, {
36
+ persistent: true,
37
+ awaitWriteFinish: {
38
+ stabilityThreshold: 100,
39
+ pollInterval: 100
40
+ }
41
+ });
42
+
43
+ watcher.on('change', (file) => {
44
+ notifyChange(file);
45
+ });
46
+
47
+ watcher.on('add', (file) => {
48
+ notifyChange(file);
49
+ });
50
+
51
+ emitter.close = () => watcher.close();
52
+ } else {
53
+ const fileStats = {};
54
+ const filesToWatch = [
55
+ path.join(projectRoot, 'specs/state.yaml'),
56
+ path.join(projectRoot, 'specs/execution-status.yaml'),
57
+ path.join(projectRoot, 'specs/metrics/cycle-times.yaml')
58
+ ];
59
+
60
+ const pollInterval = setInterval(() => {
61
+ filesToWatch.forEach((file) => {
62
+ try {
63
+ const stat = fs.statSync(file);
64
+ const mtime = stat.mtime.getTime();
65
+
66
+ if (!fileStats[file]) {
67
+ fileStats[file] = mtime;
68
+ } else if (fileStats[file] !== mtime) {
69
+ fileStats[file] = mtime;
70
+ notifyChange(file);
71
+ }
72
+ } catch (err) {
73
+ // File doesn't exist yet or was deleted
74
+ if (fileStats[file]) {
75
+ delete fileStats[file];
76
+ notifyChange(file);
77
+ }
78
+ }
79
+ });
80
+
81
+ // Check specs/epics directory
82
+ const epicsDir = path.join(projectRoot, 'specs/epics');
83
+ try {
84
+ const entries = fs.readdirSync(epicsDir);
85
+ entries.forEach((entry) => {
86
+ const fullPath = path.join(epicsDir, entry);
87
+ const stat = fs.statSync(fullPath);
88
+ const mtime = stat.mtime.getTime();
89
+
90
+ if (!fileStats[fullPath]) {
91
+ fileStats[fullPath] = mtime;
92
+ } else if (fileStats[fullPath] !== mtime) {
93
+ fileStats[fullPath] = mtime;
94
+ notifyChange(fullPath);
95
+ }
96
+ });
97
+ } catch (err) {
98
+ // epics directory doesn't exist yet
99
+ }
100
+ }, 200);
101
+
102
+ emitter.close = () => clearInterval(pollInterval);
103
+ }
104
+
105
+ return emitter;
106
+ }
107
+
108
+ module.exports = { watch };
@@ -0,0 +1,36 @@
1
+ const { gateIcon, gateColor } = require('../loaders/gate-status');
2
+
3
+ function renderEpicQueue(box, epics, executionStatus) {
4
+ if (!box || typeof box.setContent !== 'function') {
5
+ return;
6
+ }
7
+
8
+ if (!epics || epics.length === 0) {
9
+ box.setContent('{dim}no epic shards found{/dim}');
10
+ return;
11
+ }
12
+
13
+ let lines = [];
14
+ let totalBcps = 0;
15
+
16
+ epics.forEach(epic => {
17
+ const epicStatusIcon = gateIcon[epic.status] || '?';
18
+ lines.push(`${epicStatusIcon} ${epic.id} ${epic.title}`);
19
+
20
+ if (epic.stories && epic.stories.length > 0) {
21
+ epic.stories.forEach(story => {
22
+ const storyStatusIcon = gateIcon[story.status] || '?';
23
+ const bcpCount = story.bcps || 0;
24
+ totalBcps += bcpCount;
25
+ lines.push(` ${storyStatusIcon} ${story.id} ${story.title} (${bcpCount} BCPs)`);
26
+ });
27
+ }
28
+ });
29
+
30
+ lines.push('');
31
+ lines.push(`Total: ${totalBcps} BCPs`);
32
+
33
+ box.setContent(lines.join('\n'));
34
+ }
35
+
36
+ module.exports = { renderEpicQueue };
@@ -0,0 +1,95 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const chalk = require('chalk');
4
+
5
+ function renderFilesystem(box, projectRoot) {
6
+ if (!box || typeof box.setContent !== 'function') {
7
+ return;
8
+ }
9
+
10
+ const specsPath = path.join(projectRoot, 'specs');
11
+
12
+ if (!fs.existsSync(specsPath)) {
13
+ box.setContent(chalk.dim('{center}specs/ not found{/center}'));
14
+ return;
15
+ }
16
+
17
+ const now = Date.now();
18
+ const lines = [];
19
+ lines.push('{bold}{cyan}Filesystem{/cyan}{/bold}');
20
+
21
+ let fileCount = 0;
22
+
23
+ try {
24
+ const entries = fs.readdirSync(specsPath, { withFileTypes: true });
25
+
26
+ // Count files for badge
27
+ function countFiles(dir, depth = 0) {
28
+ if (depth > 1) return 0;
29
+ let count = 0;
30
+ try {
31
+ const dirEntries = fs.readdirSync(dir, { withFileTypes: true });
32
+ dirEntries.forEach((entry) => {
33
+ if (entry.isFile()) count++;
34
+ else if (entry.isDirectory() && depth < 1) {
35
+ count += countFiles(path.join(dir, entry.name), depth + 1);
36
+ }
37
+ });
38
+ } catch (e) {
39
+ // ignore
40
+ }
41
+ return count;
42
+ }
43
+
44
+ fileCount = countFiles(specsPath);
45
+ lines.push(`{dim}[${fileCount} files]{/dim}`);
46
+ lines.push('');
47
+
48
+ // Build tree
49
+ function buildTree(dir, prefix = '', depth = 0) {
50
+ if (depth > 1) return;
51
+ try {
52
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
53
+ entries.forEach((entry, index) => {
54
+ const isLast = index === entries.length - 1;
55
+ const connector = isLast ? '└─ ' : '├─ ';
56
+ const fullPath = path.join(dir, entry.name);
57
+
58
+ let displayName = entry.name;
59
+
60
+ if (entry.isDirectory()) {
61
+ displayName = chalk.blue(displayName);
62
+ lines.push(prefix + connector + displayName);
63
+
64
+ if (depth < 1) {
65
+ const childPrefix =
66
+ prefix + (isLast ? ' ' : '│ ');
67
+ buildTree(fullPath, childPrefix, depth + 1);
68
+ }
69
+ } else if (entry.isFile()) {
70
+ // Check if modified in last 60 seconds
71
+ const stat = fs.statSync(fullPath);
72
+ const mtime = stat.mtimeMs;
73
+ const age = now - mtime;
74
+
75
+ if (age < 60000) {
76
+ displayName = chalk.yellow(displayName + ' ★');
77
+ }
78
+
79
+ lines.push(prefix + connector + displayName);
80
+ }
81
+ });
82
+ } catch (e) {
83
+ // ignore
84
+ }
85
+ }
86
+
87
+ buildTree(specsPath);
88
+ } catch (err) {
89
+ lines.push(chalk.dim('Error reading specs/'));
90
+ }
91
+
92
+ box.setContent(lines.join('\n'));
93
+ }
94
+
95
+ module.exports = { renderFilesystem };
@@ -0,0 +1,161 @@
1
+ const path = require('path');
2
+ const { watch } = require('../loaders/watcher');
3
+ const { readStateYaml, readExecutionStatus, readEpicShards, readCycleTimes } = require('../loaders/reader');
4
+ const { getMetrics } = require('../loaders/metrics');
5
+ const { renderPipeline } = require('./pipeline');
6
+ const { renderEpicQueue } = require('./epic-queue');
7
+ const { renderMetricsBar } = require('./metrics-bar');
8
+ const { renderStateYaml } = require('./state-yaml');
9
+ const { renderFilesystem } = require('./filesystem');
10
+ const { renderLedger } = require('./ledger');
11
+
12
+ let blessed;
13
+ try {
14
+ blessed = require('blessed');
15
+ } catch (err) {
16
+ blessed = null;
17
+ }
18
+
19
+ function start(projectRoot) {
20
+ if (!blessed) {
21
+ console.error('Error: blessed module is not installed. Please run: npm install blessed');
22
+ process.exit(1);
23
+ }
24
+
25
+ // Create screen
26
+ const screen = blessed.screen({
27
+ smartCSR: true,
28
+ mouse: true,
29
+ title: 'BigPowers Dashboard',
30
+ 256: true,
31
+ fullUnicode: true
32
+ });
33
+
34
+ // Check terminal size
35
+ if (screen.width < 120 || screen.height < 30) {
36
+ const msg = blessed.box({
37
+ parent: screen,
38
+ top: 'center',
39
+ left: 'center',
40
+ width: 60,
41
+ height: 10,
42
+ content: '{center}Terminal must be at least 120x30{/center}\n{center}Please resize and try again{/center}',
43
+ border: 'line',
44
+ style: { border: { fg: 'yellow' } }
45
+ });
46
+ screen.render();
47
+ return;
48
+ }
49
+
50
+ // Create layout zones
51
+ const metricsBar = blessed.box({
52
+ parent: screen,
53
+ top: 0,
54
+ left: 0,
55
+ width: '100%',
56
+ height: 3,
57
+ border: 'line',
58
+ style: { border: { fg: 'blue' } }
59
+ });
60
+
61
+ const pipeline = blessed.box({
62
+ parent: screen,
63
+ top: 3,
64
+ left: 0,
65
+ width: '100%',
66
+ height: 5,
67
+ border: 'line',
68
+ style: { border: { fg: 'cyan' } }
69
+ });
70
+
71
+ const epicQueue = blessed.box({
72
+ parent: screen,
73
+ top: 8,
74
+ left: 0,
75
+ width: '33%',
76
+ height: screen.height - 20,
77
+ border: 'line',
78
+ scrollable: true,
79
+ mouse: true,
80
+ keys: true,
81
+ style: { border: { fg: 'green' } }
82
+ });
83
+
84
+ const actionLog = blessed.box({
85
+ parent: screen,
86
+ top: 8,
87
+ left: '33%',
88
+ width: '33%',
89
+ height: screen.height - 20,
90
+ border: 'line',
91
+ scrollable: true,
92
+ mouse: true,
93
+ keys: true,
94
+ style: { border: { fg: 'yellow' } }
95
+ });
96
+
97
+ const fsPanel = blessed.box({
98
+ parent: screen,
99
+ top: 8,
100
+ left: '66%',
101
+ width: '34%',
102
+ height: screen.height - 20,
103
+ border: 'line',
104
+ scrollable: true,
105
+ mouse: true,
106
+ keys: true,
107
+ style: { border: { fg: 'magenta' } }
108
+ });
109
+
110
+ const ledger = blessed.box({
111
+ parent: screen,
112
+ top: screen.height - 12,
113
+ left: 0,
114
+ width: '100%',
115
+ height: 12,
116
+ border: 'line',
117
+ scrollable: true,
118
+ mouse: true,
119
+ keys: true,
120
+ style: { border: { fg: 'white' } }
121
+ });
122
+
123
+ // Refresh function
124
+ function refresh() {
125
+ const stateData = readStateYaml(projectRoot);
126
+ const executionStatus = readExecutionStatus(projectRoot);
127
+ const epics = readEpicShards(projectRoot);
128
+ const cycleTimes = readCycleTimes(projectRoot);
129
+ const metrics = getMetrics(cycleTimes);
130
+
131
+ renderMetricsBar(metricsBar, metrics, stateData);
132
+ renderPipeline(pipeline, stateData);
133
+ renderEpicQueue(epicQueue, epics, executionStatus);
134
+ renderStateYaml(actionLog, stateData);
135
+ renderFilesystem(fsPanel, projectRoot);
136
+ renderLedger(ledger, cycleTimes);
137
+
138
+ screen.render();
139
+ }
140
+
141
+ // Set up file watcher
142
+ const watcher = watch(projectRoot);
143
+ watcher.on('change', () => {
144
+ refresh();
145
+ });
146
+
147
+ // Initial refresh
148
+ refresh();
149
+
150
+ // Key bindings
151
+ screen.key(['q', 'C-c'], () => {
152
+ watcher.close();
153
+ process.exit(0);
154
+ });
155
+
156
+ screen.key(['r'], () => {
157
+ refresh();
158
+ });
159
+ }
160
+
161
+ module.exports = { start };
@@ -0,0 +1,66 @@
1
+ const chalk = require('chalk');
2
+
3
+ function renderLedger(box, cycleTimes) {
4
+ if (!box || typeof box.setContent !== 'function') {
5
+ return;
6
+ }
7
+
8
+ if (!cycleTimes || cycleTimes.length === 0) {
9
+ box.setContent(chalk.dim('{center}no completed stories yet{/center}'));
10
+ return;
11
+ }
12
+
13
+ const lines = [];
14
+ lines.push('{bold}{cyan}Ledger{/cyan}{/bold}');
15
+ lines.push('');
16
+
17
+ // Header row
18
+ lines.push(
19
+ '{bold}' +
20
+ '{cyan}Story ID{/cyan} ' +
21
+ '{cyan}Epic{/cyan} ' +
22
+ '{cyan}BCPs{/cyan} ' +
23
+ '{cyan}Minutes{/cyan} ' +
24
+ '{cyan}BCP/hr{/cyan}' +
25
+ '{/bold}'
26
+ );
27
+
28
+ let totalBCPs = 0;
29
+ let totalMinutes = 0;
30
+
31
+ // Data rows
32
+ cycleTimes.forEach((cycle) => {
33
+ const storyId = cycle.story_id || '—';
34
+ const epicPrefix = cycle.epic || '—';
35
+ const bcps = cycle.bcps || 0;
36
+ const minutes = cycle.cycle_minutes || 0;
37
+ const bcpPerHour = minutes > 0 ? ((bcps * 60) / minutes).toFixed(1) : '—';
38
+
39
+ totalBCPs += bcps;
40
+ totalMinutes += minutes;
41
+
42
+ lines.push(
43
+ ` ${storyId} ` +
44
+ `${epicPrefix} ` +
45
+ `${bcps} ` +
46
+ `${minutes} ` +
47
+ `${bcpPerHour}`
48
+ );
49
+ });
50
+
51
+ lines.push('');
52
+
53
+ // Totals row
54
+ const avgBcpPerHour =
55
+ totalMinutes > 0 ? ((totalBCPs * 60) / totalMinutes).toFixed(1) : '—';
56
+
57
+ lines.push(
58
+ '{bold}{yellow}' +
59
+ `TOTAL — ${totalBCPs} ${totalMinutes} ${avgBcpPerHour}` +
60
+ '{/yellow}{/bold}'
61
+ );
62
+
63
+ box.setContent(lines.join('\n'));
64
+ }
65
+
66
+ module.exports = { renderLedger };
@@ -0,0 +1,30 @@
1
+ function renderMetricsBar(box, projectMetrics, stateData) {
2
+ if (!box || typeof box.setContent !== 'function') {
3
+ return;
4
+ }
5
+
6
+ const totalBcps = projectMetrics?.totalBcps ?? '-';
7
+ const totalMin = projectMetrics?.totalMin ?? '-';
8
+ const avgBcpPerHour = projectMetrics?.avgBcpPerHour ?? '-';
9
+ const version = stateData?.release?.target_version ?? '-';
10
+
11
+ let bcpHrColor = 'white';
12
+ if (typeof avgBcpPerHour === 'number') {
13
+ if (avgBcpPerHour >= 2.0) {
14
+ bcpHrColor = 'green';
15
+ } else if (avgBcpPerHour >= 1.0) {
16
+ bcpHrColor = 'yellow';
17
+ } else {
18
+ bcpHrColor = 'red';
19
+ }
20
+ }
21
+
22
+ const avgCycleTime = totalMin === '-' ? '-' : `${totalMin}m`;
23
+ const bcpHrDisplay = typeof avgBcpPerHour === 'number' ? avgBcpPerHour.toFixed(2) : avgBcpPerHour;
24
+
25
+ const line = `BCPs: ${totalBcps} | Cycle: ${avgCycleTime} | {${bcpHrColor}}BCP/hr: ${bcpHrDisplay}{/${bcpHrColor}} | v${version}`;
26
+
27
+ box.setContent(line);
28
+ }
29
+
30
+ module.exports = { renderMetricsBar };
@@ -0,0 +1,46 @@
1
+ const { STEPS } = require('../loaders/pipeline-map');
2
+
3
+ function renderPipeline(box, stateData) {
4
+ if (!box || typeof box.setContent !== 'function') {
5
+ return;
6
+ }
7
+
8
+ if (!stateData) {
9
+ box.setContent('{dim}state.yaml not loaded{/dim}');
10
+ return;
11
+ }
12
+
13
+ const currentStep = stateData.epicCycle?.current_step || null;
14
+ const completedSteps = stateData.epicCycle?.completed_steps || [];
15
+
16
+ // Build pipeline visualization
17
+ const lines = [];
18
+ lines.push('{bold}{cyan}Pipeline{/cyan}{/bold}');
19
+ lines.push('');
20
+
21
+ // Create step display
22
+ const stepDisplay = STEPS.map((step, index) => {
23
+ const isCompleted = completedSteps.includes(step);
24
+ const isCurrent = step === currentStep;
25
+
26
+ let display = ` ${index + 1}. ${step}`;
27
+
28
+ if (isCurrent) {
29
+ display = `{reverse}{bold}${display}{/bold}{/reverse}`;
30
+ } else if (isCompleted) {
31
+ display = `{green-fg}${display}{/green-fg}`;
32
+ } else {
33
+ display = `{gray-fg}${display}{/gray-fg}`;
34
+ }
35
+
36
+ return display;
37
+ }).join('\n');
38
+
39
+ lines.push(stepDisplay);
40
+ lines.push('');
41
+ lines.push(`{dim}Current: ${currentStep || '—'}{/dim}`);
42
+
43
+ box.setContent(lines.join('\n'));
44
+ }
45
+
46
+ module.exports = { renderPipeline };
@@ -0,0 +1,49 @@
1
+ const chalk = require('chalk');
2
+
3
+ function renderStateYaml(box, stateData) {
4
+ if (!box || typeof box.setContent !== 'function') {
5
+ return;
6
+ }
7
+
8
+ if (!stateData) {
9
+ box.setContent(chalk.dim('{center}state.yaml not found{/center}'));
10
+ return;
11
+ }
12
+
13
+ const lines = [];
14
+ lines.push('{bold}{cyan}state.yaml{/cyan}{/bold}');
15
+ lines.push('');
16
+
17
+ const pairs = [
18
+ { key: 'active_flow', value: stateData.active_flow },
19
+ { key: 'active_epic', value: stateData.active_epic },
20
+ { key: 'active_story', value: stateData.active_story },
21
+ { key: 'current_step', value: stateData.current_step },
22
+ { key: 'next_skill', value: stateData.next_skill },
23
+ { key: 'git.branch', value: stateData.git?.branch },
24
+ { key: 'metrics.story_start', value: stateData.metrics?.story_start },
25
+ ];
26
+
27
+ pairs.forEach(({ key, value }) => {
28
+ let displayValue = String(value || '—');
29
+
30
+ // Color rules
31
+ if (key === 'git.branch') {
32
+ if (displayValue === 'main') {
33
+ displayValue = chalk.green(displayValue);
34
+ } else if (displayValue.startsWith('feat/')) {
35
+ displayValue = chalk.yellow(displayValue);
36
+ }
37
+ }
38
+
39
+ if (key === 'next_skill' && value) {
40
+ displayValue = chalk.green(displayValue);
41
+ }
42
+
43
+ lines.push(` {dim}${key}:{/dim} ${displayValue}`);
44
+ });
45
+
46
+ box.setContent(lines.join('\n'));
47
+ }
48
+
49
+ module.exports = { renderStateYaml };