superspec 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 +328 -0
- package/dist/cli/commands/archive.d.ts +7 -0
- package/dist/cli/commands/archive.d.ts.map +1 -0
- package/dist/cli/commands/archive.js +322 -0
- package/dist/cli/commands/archive.js.map +1 -0
- package/dist/cli/commands/init.d.ts +6 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +306 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/list.d.ts +9 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +268 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/show.d.ts +6 -0
- package/dist/cli/commands/show.d.ts.map +1 -0
- package/dist/cli/commands/show.js +273 -0
- package/dist/cli/commands/show.js.map +1 -0
- package/dist/cli/commands/validate.d.ts +8 -0
- package/dist/cli/commands/validate.d.ts.map +1 -0
- package/dist/cli/commands/validate.js +209 -0
- package/dist/cli/commands/validate.js.map +1 -0
- package/dist/cli/commands/verify.d.ts +7 -0
- package/dist/cli/commands/verify.d.ts.map +1 -0
- package/dist/cli/commands/verify.js +328 -0
- package/dist/cli/commands/verify.js.map +1 -0
- package/dist/cli/commands/view.d.ts +6 -0
- package/dist/cli/commands/view.d.ts.map +1 -0
- package/dist/cli/commands/view.js +290 -0
- package/dist/cli/commands/view.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +87 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/ui/display.d.ts +189 -0
- package/dist/cli/ui/display.d.ts.map +1 -0
- package/dist/cli/ui/display.js +449 -0
- package/dist/cli/ui/display.js.map +1 -0
- package/dist/cli/ui/index.d.ts +12 -0
- package/dist/cli/ui/index.d.ts.map +1 -0
- package/dist/cli/ui/index.js +71 -0
- package/dist/cli/ui/index.js.map +1 -0
- package/dist/cli/ui/interactive.d.ts +21 -0
- package/dist/cli/ui/interactive.d.ts.map +1 -0
- package/dist/cli/ui/interactive.js +60 -0
- package/dist/cli/ui/interactive.js.map +1 -0
- package/dist/cli/ui/palette.d.ts +301 -0
- package/dist/cli/ui/palette.d.ts.map +1 -0
- package/dist/cli/ui/palette.js +673 -0
- package/dist/cli/ui/palette.js.map +1 -0
- package/dist/cli/ui/prompts.d.ts +62 -0
- package/dist/cli/ui/prompts.d.ts.map +1 -0
- package/dist/cli/ui/prompts.js +119 -0
- package/dist/cli/ui/prompts.js.map +1 -0
- package/dist/cli/ui/spinner.d.ts +38 -0
- package/dist/cli/ui/spinner.d.ts.map +1 -0
- package/dist/cli/ui/spinner.js +72 -0
- package/dist/cli/ui/spinner.js.map +1 -0
- package/dist/core/config/project-config.d.ts +100 -0
- package/dist/core/config/project-config.d.ts.map +1 -0
- package/dist/core/config/project-config.js +87 -0
- package/dist/core/config/project-config.js.map +1 -0
- package/dist/core/parsers/spec-parser.d.ts +48 -0
- package/dist/core/parsers/spec-parser.d.ts.map +1 -0
- package/dist/core/parsers/spec-parser.js +322 -0
- package/dist/core/parsers/spec-parser.js.map +1 -0
- package/dist/core/validation/spec-validator.d.ts +32 -0
- package/dist/core/validation/spec-validator.d.ts.map +1 -0
- package/dist/core/validation/spec-validator.js +242 -0
- package/dist/core/validation/spec-validator.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/package.json +66 -0
- package/schemas/superspec-workflow/schema.yaml +166 -0
- package/templates/design.md +71 -0
- package/templates/plan.md +78 -0
- package/templates/proposal.md +36 -0
- package/templates/spec.md +57 -0
- package/templates/tasks.md +29 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SuperSpec CLI Visual System
|
|
3
|
+
*
|
|
4
|
+
* Design Direction: "Precision Terminal"
|
|
5
|
+
* Inspired by space mission control panels, precision instruments,
|
|
6
|
+
* and Japanese minimalism with technical precision.
|
|
7
|
+
*
|
|
8
|
+
* Color Philosophy:
|
|
9
|
+
* - Teal as primary: represents precision and professionalism
|
|
10
|
+
* - Amber as accent: draws attention to important elements
|
|
11
|
+
* - Muted grays: clean, non-distracting background text
|
|
12
|
+
*/
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
15
|
+
// COLOR PALETTE
|
|
16
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
17
|
+
export const PALETTE = {
|
|
18
|
+
// Primary colors - Teal spectrum
|
|
19
|
+
primary: chalk.hex('#14b8a6'), // Teal-500
|
|
20
|
+
primaryBright: chalk.hex('#2dd4bf'), // Teal-400
|
|
21
|
+
primaryDim: chalk.hex('#0d9488'), // Teal-600
|
|
22
|
+
primaryMuted: chalk.hex('#115e59'), // Teal-800
|
|
23
|
+
// Accent - Amber for emphasis
|
|
24
|
+
accent: chalk.hex('#f59e0b'), // Amber-500
|
|
25
|
+
accentBright: chalk.hex('#fbbf24'), // Amber-400
|
|
26
|
+
accentDim: chalk.hex('#d97706'), // Amber-600
|
|
27
|
+
// Secondary - Slate for sophistication
|
|
28
|
+
secondary: chalk.hex('#64748b'), // Slate-500
|
|
29
|
+
secondaryBright: chalk.hex('#94a3b8'), // Slate-400
|
|
30
|
+
// Status colors - Refined
|
|
31
|
+
success: chalk.hex('#10b981'), // Emerald-500
|
|
32
|
+
successBright: chalk.hex('#34d399'), // Emerald-400
|
|
33
|
+
warning: chalk.hex('#f59e0b'), // Amber-500
|
|
34
|
+
error: chalk.hex('#f87171'), // Red-400
|
|
35
|
+
errorBright: chalk.hex('#fca5a5'), // Red-300
|
|
36
|
+
info: chalk.hex('#38bdf8'), // Sky-400
|
|
37
|
+
// Neutral colors - Cool gray spectrum
|
|
38
|
+
white: chalk.hex('#f8fafc'), // Slate-50
|
|
39
|
+
light: chalk.hex('#e2e8f0'), // Slate-200
|
|
40
|
+
muted: chalk.hex('#94a3b8'), // Slate-400
|
|
41
|
+
dim: chalk.hex('#64748b'), // Slate-500
|
|
42
|
+
subtle: chalk.hex('#475569'), // Slate-600
|
|
43
|
+
dark: chalk.hex('#334155'), // Slate-700
|
|
44
|
+
darker: chalk.hex('#1e293b'), // Slate-800
|
|
45
|
+
// Text styles
|
|
46
|
+
bold: chalk.bold,
|
|
47
|
+
italic: chalk.italic,
|
|
48
|
+
underline: chalk.underline,
|
|
49
|
+
strikethrough: chalk.strikethrough,
|
|
50
|
+
// Legacy aliases for compatibility
|
|
51
|
+
cyan: chalk.hex('#14b8a6'),
|
|
52
|
+
lightGray: chalk.hex('#94a3b8'),
|
|
53
|
+
midGray: chalk.hex('#64748b'),
|
|
54
|
+
darkGray: chalk.hex('#475569'),
|
|
55
|
+
};
|
|
56
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
57
|
+
// ICONS & SYMBOLS
|
|
58
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
59
|
+
export const ICONS = {
|
|
60
|
+
// Status indicators - Modern geometric
|
|
61
|
+
success: PALETTE.success('◆'),
|
|
62
|
+
error: PALETTE.error('◆'),
|
|
63
|
+
warning: PALETTE.warning('◆'),
|
|
64
|
+
info: PALETTE.info('◆'),
|
|
65
|
+
pending: PALETTE.dim('◇'),
|
|
66
|
+
// Check marks
|
|
67
|
+
check: PALETTE.success('✓'),
|
|
68
|
+
cross: PALETTE.error('✗'),
|
|
69
|
+
dot: PALETTE.primary('•'),
|
|
70
|
+
// Arrows and pointers
|
|
71
|
+
arrowRight: PALETTE.primary('→'),
|
|
72
|
+
arrowLeft: PALETTE.primary('←'),
|
|
73
|
+
arrowUp: PALETTE.primary('↑'),
|
|
74
|
+
arrowDown: PALETTE.primary('↓'),
|
|
75
|
+
pointer: PALETTE.accent('▸'),
|
|
76
|
+
chevron: PALETTE.primary('›'),
|
|
77
|
+
// Document and file
|
|
78
|
+
file: PALETTE.dim('□'),
|
|
79
|
+
fileFilled: PALETTE.primary('■'),
|
|
80
|
+
folder: PALETTE.accent('◫'),
|
|
81
|
+
spec: PALETTE.primary('◈'),
|
|
82
|
+
// Progress and selection
|
|
83
|
+
radioOn: PALETTE.success('◉'),
|
|
84
|
+
radioOff: PALETTE.dim('○'),
|
|
85
|
+
checkboxOn: PALETTE.success('☑'),
|
|
86
|
+
checkboxOff: PALETTE.dim('☐'),
|
|
87
|
+
// Decorative
|
|
88
|
+
star: PALETTE.accent('★'),
|
|
89
|
+
diamond: PALETTE.primary('◆'),
|
|
90
|
+
square: PALETTE.dim('■'),
|
|
91
|
+
circle: PALETTE.dim('●'),
|
|
92
|
+
// Workflow
|
|
93
|
+
play: PALETTE.success('▶'),
|
|
94
|
+
pause: PALETTE.warning('⏸'),
|
|
95
|
+
stop: PALETTE.error('⏹'),
|
|
96
|
+
refresh: PALETTE.info('↻'),
|
|
97
|
+
// Brand
|
|
98
|
+
logo: PALETTE.primary('⬡'),
|
|
99
|
+
logoAlt: PALETTE.accent('⬢'),
|
|
100
|
+
};
|
|
101
|
+
// Legacy alias
|
|
102
|
+
export const SYMBOLS = {
|
|
103
|
+
success: ICONS.check,
|
|
104
|
+
error: ICONS.cross,
|
|
105
|
+
warning: PALETTE.warning('⚠'),
|
|
106
|
+
info: PALETTE.info('ℹ'),
|
|
107
|
+
arrow: ICONS.chevron,
|
|
108
|
+
pointer: ICONS.pointer,
|
|
109
|
+
selected: ICONS.radioOn,
|
|
110
|
+
unselected: ICONS.radioOff,
|
|
111
|
+
checkbox: ICONS.checkboxOn,
|
|
112
|
+
uncheckbox: ICONS.checkboxOff,
|
|
113
|
+
filledBar: PALETTE.success('█'),
|
|
114
|
+
emptyBar: PALETTE.dark('░'),
|
|
115
|
+
spinner: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
|
|
116
|
+
bullet: ICONS.dot,
|
|
117
|
+
dash: PALETTE.dim('─'),
|
|
118
|
+
verticalLine: PALETTE.dim('│'),
|
|
119
|
+
corner: PALETTE.dim('└'),
|
|
120
|
+
tee: PALETTE.dim('├'),
|
|
121
|
+
};
|
|
122
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
123
|
+
// BOX DRAWING CHARACTERS
|
|
124
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
125
|
+
export const BOX = {
|
|
126
|
+
// Single line (default)
|
|
127
|
+
topLeft: '╭',
|
|
128
|
+
topRight: '╮',
|
|
129
|
+
bottomLeft: '╰',
|
|
130
|
+
bottomRight: '╯',
|
|
131
|
+
horizontal: '─',
|
|
132
|
+
vertical: '│',
|
|
133
|
+
teeLeft: '├',
|
|
134
|
+
teeRight: '┤',
|
|
135
|
+
teeTop: '┬',
|
|
136
|
+
teeBottom: '┴',
|
|
137
|
+
cross: '┼',
|
|
138
|
+
// Double line (for emphasis)
|
|
139
|
+
doubleH: '═',
|
|
140
|
+
doubleV: '║',
|
|
141
|
+
doubleTopLeft: '╔',
|
|
142
|
+
doubleTopRight: '╗',
|
|
143
|
+
doubleBottomLeft: '╚',
|
|
144
|
+
doubleBottomRight: '╝',
|
|
145
|
+
// Heavy line (for headers)
|
|
146
|
+
heavyH: '━',
|
|
147
|
+
heavyV: '┃',
|
|
148
|
+
heavyTopLeft: '┏',
|
|
149
|
+
heavyTopRight: '┓',
|
|
150
|
+
heavyBottomLeft: '┗',
|
|
151
|
+
heavyBottomRight: '┛',
|
|
152
|
+
// Mixed corners
|
|
153
|
+
roundTopLeft: '╭',
|
|
154
|
+
roundTopRight: '╮',
|
|
155
|
+
roundBottomLeft: '╰',
|
|
156
|
+
roundBottomRight: '╯',
|
|
157
|
+
};
|
|
158
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
159
|
+
// DECORATIVE LINES
|
|
160
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
161
|
+
export const LINES = {
|
|
162
|
+
thin: '─',
|
|
163
|
+
thick: '━',
|
|
164
|
+
double: '═',
|
|
165
|
+
dotted: '┄',
|
|
166
|
+
dashed: '┈',
|
|
167
|
+
};
|
|
168
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
169
|
+
// BANNER - Modern Geometric Design
|
|
170
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
171
|
+
const BANNER_ART = `
|
|
172
|
+
╭────────────────────────────────────────────────────────────────────────────────╮
|
|
173
|
+
│ │
|
|
174
|
+
│ ███████╗██╗ ██╗██████╗ ███████╗██████╗ ███████╗██████╗ ███████╗ ██████╗ │
|
|
175
|
+
│ ██╔════╝██║ ██║██╔══██╗██╔════╝██╔══██╗██╔════╝██╔══██╗██╔════╝██╔════╝ │
|
|
176
|
+
│ ███████╗██║ ██║██████╔╝█████╗ ██████╔╝███████╗██████╔╝█████╗ ██║ │
|
|
177
|
+
│ ╚════██║██║ ██║██╔═══╝ ██╔══╝ ██╔══██╗╚════██║██╔═══╝ ██╔══╝ ██║ │
|
|
178
|
+
│ ███████║╚██████╔╝██║ ███████╗██║ ██║███████║██║ ███████╗╚██████╗ │
|
|
179
|
+
│ ╚══════╝ ╚═════╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝ ╚═════╝ │
|
|
180
|
+
│ │
|
|
181
|
+
╰────────────────────────────────────────────────────────────────────────────────╯
|
|
182
|
+
`;
|
|
183
|
+
/**
|
|
184
|
+
* Apply clean, uniform teal color to banner
|
|
185
|
+
*/
|
|
186
|
+
function applyBannerGradient(text) {
|
|
187
|
+
const colors = {
|
|
188
|
+
main: '#2dd4bf', // Teal-400 (bright, clean)
|
|
189
|
+
border: '#14b8a6', // Teal-500 (letter borders)
|
|
190
|
+
box: '#475569', // Slate-600 (frame)
|
|
191
|
+
};
|
|
192
|
+
const lines = text.split('\n');
|
|
193
|
+
return lines.map((line) => {
|
|
194
|
+
return line.split('').map((char) => {
|
|
195
|
+
if (char === ' ' || char === '\n')
|
|
196
|
+
return char;
|
|
197
|
+
// Box frame characters
|
|
198
|
+
if ('╭╮╰╯─│'.includes(char)) {
|
|
199
|
+
return chalk.hex(colors.box)(char);
|
|
200
|
+
}
|
|
201
|
+
// Decorative border characters within letters
|
|
202
|
+
if ('╗╝╚╔║═'.includes(char)) {
|
|
203
|
+
return chalk.hex(colors.border)(char);
|
|
204
|
+
}
|
|
205
|
+
// All main block characters - uniform bright teal
|
|
206
|
+
return chalk.hex(colors.main)(char);
|
|
207
|
+
}).join('');
|
|
208
|
+
}).join('\n');
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Minimal banner for tight spaces (with two-tone design)
|
|
212
|
+
*/
|
|
213
|
+
const MINIMAL_BANNER_TEMPLATE = {
|
|
214
|
+
top: ' ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓',
|
|
215
|
+
title: ' ┃ ⬡ ',
|
|
216
|
+
super: 'S U P E R',
|
|
217
|
+
spec: 'S P E C',
|
|
218
|
+
titleEnd: ' ┃',
|
|
219
|
+
subtitle: ' ┃ Spec-Driven Development Framework ┃',
|
|
220
|
+
bottom: ' ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛',
|
|
221
|
+
};
|
|
222
|
+
/**
|
|
223
|
+
* Build minimal banner with uniform teal
|
|
224
|
+
*/
|
|
225
|
+
function buildMinimalBanner() {
|
|
226
|
+
const t = MINIMAL_BANNER_TEMPLATE;
|
|
227
|
+
const teal = chalk.hex('#2dd4bf');
|
|
228
|
+
return [
|
|
229
|
+
PALETTE.subtle(t.top),
|
|
230
|
+
PALETTE.subtle(t.title.slice(0, 5)) + PALETTE.primary('⬡') + PALETTE.subtle(' ') +
|
|
231
|
+
teal(t.super + t.spec) + PALETTE.subtle(t.titleEnd),
|
|
232
|
+
PALETTE.subtle(t.subtitle),
|
|
233
|
+
PALETTE.subtle(t.bottom),
|
|
234
|
+
].join('\n');
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Brand name styling - clean bright teal
|
|
238
|
+
*/
|
|
239
|
+
function styledBrandName() {
|
|
240
|
+
return chalk.hex('#2dd4bf').bold('SuperSpec');
|
|
241
|
+
}
|
|
242
|
+
export const BANNER = `${applyBannerGradient(BANNER_ART)}
|
|
243
|
+
${PALETTE.white('Welcome to')} ${styledBrandName()}
|
|
244
|
+
${PALETTE.dim('Spec-Driven Development Framework')}
|
|
245
|
+
${PALETTE.subtle('─'.repeat(40))}
|
|
246
|
+
`;
|
|
247
|
+
export const MINI_BANNER = buildMinimalBanner();
|
|
248
|
+
export const INLINE_LOGO = `${PALETTE.primary('⬡')} ${styledBrandName()}`;
|
|
249
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
250
|
+
// HEADER & SECTION DECORATORS
|
|
251
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
252
|
+
/**
|
|
253
|
+
* Command header with modern styling
|
|
254
|
+
*/
|
|
255
|
+
export function commandHeader(command, description) {
|
|
256
|
+
const width = 64;
|
|
257
|
+
const topLine = PALETTE.subtle(BOX.heavyTopLeft + BOX.heavyH.repeat(width - 2) + BOX.heavyTopRight);
|
|
258
|
+
const bottomLine = PALETTE.subtle(BOX.heavyBottomLeft + BOX.heavyH.repeat(width - 2) + BOX.heavyBottomRight);
|
|
259
|
+
const logo = PALETTE.primary('⬡');
|
|
260
|
+
const cmdText = PALETTE.bold(PALETTE.white(`superspec ${command}`));
|
|
261
|
+
const title = `${PALETTE.subtle(BOX.heavyV)} ${logo} ${cmdText}`;
|
|
262
|
+
const padding = ' '.repeat(Math.max(0, width - 6 - `superspec ${command}`.length));
|
|
263
|
+
const titleLine = `${title}${padding}${PALETTE.subtle(BOX.heavyV)}`;
|
|
264
|
+
let output = `\n${topLine}\n${titleLine}`;
|
|
265
|
+
if (description) {
|
|
266
|
+
const desc = PALETTE.dim(description);
|
|
267
|
+
const descPadding = ' '.repeat(Math.max(0, width - 4 - description.length));
|
|
268
|
+
output += `\n${PALETTE.subtle(BOX.heavyV)} ${desc}${descPadding}${PALETTE.subtle(BOX.heavyV)}`;
|
|
269
|
+
}
|
|
270
|
+
output += `\n${bottomLine}\n`;
|
|
271
|
+
return output;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Section header
|
|
275
|
+
*/
|
|
276
|
+
export function sectionHeader(title, icon) {
|
|
277
|
+
const iconStr = icon ? `${icon} ` : '';
|
|
278
|
+
return `\n ${PALETTE.dim('┌─')} ${iconStr}${PALETTE.bold(PALETTE.white(title))}`;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Section divider
|
|
282
|
+
*/
|
|
283
|
+
export function sectionDivider() {
|
|
284
|
+
return PALETTE.subtle(' ' + '─'.repeat(60));
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Subsection divider (lighter)
|
|
288
|
+
*/
|
|
289
|
+
export function subsectionDivider() {
|
|
290
|
+
return PALETTE.dark(' ' + '┄'.repeat(50));
|
|
291
|
+
}
|
|
292
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
293
|
+
// BOX COMPONENTS
|
|
294
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
295
|
+
/**
|
|
296
|
+
* Styled content box
|
|
297
|
+
*/
|
|
298
|
+
export function styledBox(title, content, options = {}) {
|
|
299
|
+
const { width = 60, style = 'default' } = options;
|
|
300
|
+
const colorFn = {
|
|
301
|
+
default: PALETTE.subtle,
|
|
302
|
+
accent: PALETTE.accent,
|
|
303
|
+
success: PALETTE.success,
|
|
304
|
+
warning: PALETTE.warning,
|
|
305
|
+
error: PALETTE.error,
|
|
306
|
+
}[style];
|
|
307
|
+
const top = colorFn(BOX.topLeft + BOX.horizontal.repeat(width - 2) + BOX.topRight);
|
|
308
|
+
const bottom = colorFn(BOX.bottomLeft + BOX.horizontal.repeat(width - 2) + BOX.bottomRight);
|
|
309
|
+
const titleIcon = style === 'accent' ? PALETTE.accent('◆') :
|
|
310
|
+
style === 'success' ? PALETTE.success('◆') :
|
|
311
|
+
style === 'warning' ? PALETTE.warning('◆') :
|
|
312
|
+
style === 'error' ? PALETTE.error('◆') :
|
|
313
|
+
PALETTE.primary('◆');
|
|
314
|
+
const titleText = ` ${titleIcon} ${PALETTE.bold(PALETTE.white(title))}`;
|
|
315
|
+
const titlePad = ' '.repeat(Math.max(0, width - 5 - title.length));
|
|
316
|
+
const titleLine = `${colorFn(BOX.vertical)}${titleText}${titlePad}${colorFn(BOX.vertical)}`;
|
|
317
|
+
const divider = colorFn(BOX.teeLeft + BOX.horizontal.repeat(width - 2) + BOX.teeRight);
|
|
318
|
+
const lines = content.map(line => {
|
|
319
|
+
// Strip ANSI codes for length calculation
|
|
320
|
+
const visibleLength = line.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
321
|
+
const pad = ' '.repeat(Math.max(0, width - 4 - visibleLength));
|
|
322
|
+
return `${colorFn(BOX.vertical)} ${line}${pad}${colorFn(BOX.vertical)}`;
|
|
323
|
+
});
|
|
324
|
+
return [top, titleLine, divider, ...lines, bottom].join('\n');
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Info card (compact box for single info)
|
|
328
|
+
*/
|
|
329
|
+
export function infoCard(label, value, icon) {
|
|
330
|
+
const iconStr = icon ? `${icon} ` : '';
|
|
331
|
+
return ` ${PALETTE.subtle('│')} ${iconStr}${PALETTE.dim(label + ':')} ${PALETTE.white(value)}`;
|
|
332
|
+
}
|
|
333
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
334
|
+
// PROGRESS & STATUS
|
|
335
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
336
|
+
/**
|
|
337
|
+
* Modern progress bar
|
|
338
|
+
*/
|
|
339
|
+
export function createProgressBar(current, total, options = {}) {
|
|
340
|
+
const { width = 20, showPercent = true, showCount = false, style = 'default' } = options;
|
|
341
|
+
if (total === 0) {
|
|
342
|
+
return PALETTE.dim(`[${'░'.repeat(width)}]`) + (showPercent ? PALETTE.dim(' 0%') : '');
|
|
343
|
+
}
|
|
344
|
+
const percentage = Math.min(current / total, 1);
|
|
345
|
+
const filled = Math.round(percentage * width);
|
|
346
|
+
const empty = width - filled;
|
|
347
|
+
// Different fill styles
|
|
348
|
+
const fillChar = style === 'minimal' ? '─' : '█';
|
|
349
|
+
const emptyChar = style === 'minimal' ? '┄' : '░';
|
|
350
|
+
// Color based on percentage
|
|
351
|
+
const fillColor = percentage >= 1 ? PALETTE.success :
|
|
352
|
+
percentage >= 0.7 ? PALETTE.primaryBright :
|
|
353
|
+
percentage >= 0.3 ? PALETTE.primary :
|
|
354
|
+
PALETTE.accent;
|
|
355
|
+
const filledBar = fillColor(fillChar.repeat(filled));
|
|
356
|
+
const emptyBar = PALETTE.dark(emptyChar.repeat(empty));
|
|
357
|
+
let result = `[${filledBar}${emptyBar}]`;
|
|
358
|
+
if (showPercent) {
|
|
359
|
+
const pct = Math.round(percentage * 100);
|
|
360
|
+
const pctColor = percentage >= 1 ? PALETTE.success : PALETTE.white;
|
|
361
|
+
result += ` ${pctColor(`${pct}%`)}`;
|
|
362
|
+
}
|
|
363
|
+
if (showCount) {
|
|
364
|
+
result += PALETTE.dim(` (${current}/${total})`);
|
|
365
|
+
}
|
|
366
|
+
return result;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Status indicator with label
|
|
370
|
+
*/
|
|
371
|
+
export function statusIndicator(status) {
|
|
372
|
+
const indicators = {
|
|
373
|
+
success: PALETTE.success('◆'),
|
|
374
|
+
warning: PALETTE.warning('◆'),
|
|
375
|
+
error: PALETTE.error('◆'),
|
|
376
|
+
info: PALETTE.info('◆'),
|
|
377
|
+
pending: PALETTE.dim('◇'),
|
|
378
|
+
active: PALETTE.primaryBright('◆'),
|
|
379
|
+
};
|
|
380
|
+
return indicators[status] || PALETTE.dim('◇');
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Status badge with text
|
|
384
|
+
*/
|
|
385
|
+
export function statusBadge(status) {
|
|
386
|
+
const badges = {
|
|
387
|
+
active: `${PALETTE.success('◆')} ${PALETTE.success('active')}`,
|
|
388
|
+
draft: `${PALETTE.accent('◇')} ${PALETTE.accent('draft')}`,
|
|
389
|
+
completed: `${PALETTE.info('◆')} ${PALETTE.info('completed')}`,
|
|
390
|
+
archived: `${PALETTE.dim('◆')} ${PALETTE.dim('archived')}`,
|
|
391
|
+
error: `${PALETTE.error('◆')} ${PALETTE.error('error')}`,
|
|
392
|
+
};
|
|
393
|
+
return badges[status] || `${PALETTE.accent('◇')} ${PALETTE.accent('draft')}`;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Phase indicator for workflow
|
|
397
|
+
*/
|
|
398
|
+
export function phaseIndicator(current, total, labels) {
|
|
399
|
+
const phases = [];
|
|
400
|
+
for (let i = 1; i <= total; i++) {
|
|
401
|
+
const label = labels?.[i - 1] ?? `Phase ${i}`;
|
|
402
|
+
if (i < current) {
|
|
403
|
+
phases.push(`${PALETTE.success('●')} ${PALETTE.dim(label)}`);
|
|
404
|
+
}
|
|
405
|
+
else if (i === current) {
|
|
406
|
+
phases.push(`${PALETTE.accent('●')} ${PALETTE.white(label)}`);
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
phases.push(`${PALETTE.dark('○')} ${PALETTE.dark(label)}`);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return phases.join(PALETTE.dim(' → '));
|
|
413
|
+
}
|
|
414
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
415
|
+
// LIST ITEMS
|
|
416
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
417
|
+
/**
|
|
418
|
+
* Styled list item
|
|
419
|
+
*/
|
|
420
|
+
export function listItem(text, options = {}) {
|
|
421
|
+
const { indent = 0, icon, style = 'default' } = options;
|
|
422
|
+
const indentStr = ' '.repeat(indent);
|
|
423
|
+
const iconStr = icon ?? PALETTE.primary('›');
|
|
424
|
+
const textFn = style === 'dim' ? PALETTE.dim :
|
|
425
|
+
style === 'highlight' ? PALETTE.white :
|
|
426
|
+
(t) => t;
|
|
427
|
+
return `${indentStr}${iconStr} ${textFn(text)}`;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Numbered list item
|
|
431
|
+
*/
|
|
432
|
+
export function numberedItem(num, text, options = {}) {
|
|
433
|
+
const { indent = 0, style = 'default' } = options;
|
|
434
|
+
const indentStr = ' '.repeat(indent);
|
|
435
|
+
const numStr = PALETTE.accent(`${num}.`);
|
|
436
|
+
const textFn = style === 'dim' ? PALETTE.dim :
|
|
437
|
+
style === 'highlight' ? PALETTE.white :
|
|
438
|
+
(t) => t;
|
|
439
|
+
return `${indentStr}${numStr} ${textFn(text)}`;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Task item (checkbox style)
|
|
443
|
+
*/
|
|
444
|
+
export function taskItem(text, done, options = {}) {
|
|
445
|
+
const { indent = 0 } = options;
|
|
446
|
+
const indentStr = ' '.repeat(indent);
|
|
447
|
+
const icon = done ? PALETTE.success('☑') : PALETTE.dim('☐');
|
|
448
|
+
const textStyle = done ? PALETTE.dim : PALETTE.white;
|
|
449
|
+
return `${indentStr}${icon} ${textStyle(text)}`;
|
|
450
|
+
}
|
|
451
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
452
|
+
// KEY HINTS & HELP
|
|
453
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
454
|
+
/**
|
|
455
|
+
* Keyboard shortcut hint
|
|
456
|
+
*/
|
|
457
|
+
export function keyHint(keys, action) {
|
|
458
|
+
return `${PALETTE.primary(keys)} ${PALETTE.dim(action)}`;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Multiple key hints
|
|
462
|
+
*/
|
|
463
|
+
export function keyHints(hints) {
|
|
464
|
+
return hints.map(([keys, action]) => keyHint(keys, action)).join(PALETTE.dark(' · '));
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Command hint
|
|
468
|
+
*/
|
|
469
|
+
export function commandHint(cmd, desc) {
|
|
470
|
+
const descStr = desc ? PALETTE.dim(` - ${desc}`) : '';
|
|
471
|
+
return ` ${PALETTE.primary('›')} ${PALETTE.accent(cmd)}${descStr}`;
|
|
472
|
+
}
|
|
473
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
474
|
+
// TABLES
|
|
475
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
476
|
+
/**
|
|
477
|
+
* Simple table row
|
|
478
|
+
*/
|
|
479
|
+
export function tableRow(columns, widths) {
|
|
480
|
+
return columns.map((col, i) => {
|
|
481
|
+
const width = widths[i] ?? 20;
|
|
482
|
+
const visibleLength = col.replace(/\x1b\[[0-9;]*m/g, '').length;
|
|
483
|
+
const pad = ' '.repeat(Math.max(0, width - visibleLength));
|
|
484
|
+
return col + pad;
|
|
485
|
+
}).join(PALETTE.dark(' │ '));
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Table header
|
|
489
|
+
*/
|
|
490
|
+
export function tableHeader(columns, widths) {
|
|
491
|
+
const header = columns.map((col, i) => {
|
|
492
|
+
const width = widths[i] ?? 20;
|
|
493
|
+
return PALETTE.bold(PALETTE.dim(col.padEnd(width)));
|
|
494
|
+
}).join(PALETTE.dark(' │ '));
|
|
495
|
+
const divider = widths.map(w => PALETTE.dark('─'.repeat(w))).join(PALETTE.dark('─┼─'));
|
|
496
|
+
return `${header}\n${divider}`;
|
|
497
|
+
}
|
|
498
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
499
|
+
// UTILITY FUNCTIONS
|
|
500
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
501
|
+
/**
|
|
502
|
+
* Highlight text within a string
|
|
503
|
+
*/
|
|
504
|
+
export function highlight(text, term) {
|
|
505
|
+
if (!term)
|
|
506
|
+
return text;
|
|
507
|
+
const regex = new RegExp(`(${term})`, 'gi');
|
|
508
|
+
return text.replace(regex, PALETTE.accent('$1'));
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Truncate text with ellipsis
|
|
512
|
+
*/
|
|
513
|
+
export function truncate(text, maxLength) {
|
|
514
|
+
if (text.length <= maxLength)
|
|
515
|
+
return text;
|
|
516
|
+
return text.slice(0, maxLength - 1) + PALETTE.dim('…');
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Format a path
|
|
520
|
+
*/
|
|
521
|
+
export function formatPath(path) {
|
|
522
|
+
const parts = path.split('/');
|
|
523
|
+
if (parts.length <= 2)
|
|
524
|
+
return PALETTE.dim(path);
|
|
525
|
+
const filename = parts.pop();
|
|
526
|
+
const dir = parts.join('/');
|
|
527
|
+
return `${PALETTE.dark(dir + '/')}${PALETTE.white(filename)}`;
|
|
528
|
+
}
|
|
529
|
+
/**
|
|
530
|
+
* Format a date
|
|
531
|
+
*/
|
|
532
|
+
export function formatDate(date) {
|
|
533
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
534
|
+
return PALETTE.dim(d.toLocaleDateString('en-US', {
|
|
535
|
+
year: 'numeric',
|
|
536
|
+
month: 'short',
|
|
537
|
+
day: 'numeric',
|
|
538
|
+
}));
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Format file size
|
|
542
|
+
*/
|
|
543
|
+
export function formatSize(bytes) {
|
|
544
|
+
const units = ['B', 'KB', 'MB', 'GB'];
|
|
545
|
+
let size = bytes;
|
|
546
|
+
let unitIndex = 0;
|
|
547
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
548
|
+
size /= 1024;
|
|
549
|
+
unitIndex++;
|
|
550
|
+
}
|
|
551
|
+
return `${size.toFixed(1)} ${units[unitIndex]}`;
|
|
552
|
+
}
|
|
553
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
554
|
+
// MESSAGE HELPERS
|
|
555
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
556
|
+
/**
|
|
557
|
+
* Success message
|
|
558
|
+
*/
|
|
559
|
+
export function successMessage(text) {
|
|
560
|
+
return ` ${ICONS.success} ${PALETTE.success(text)}`;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Error message
|
|
564
|
+
*/
|
|
565
|
+
export function errorMessage(text) {
|
|
566
|
+
return ` ${ICONS.error} ${PALETTE.error(text)}`;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Warning message
|
|
570
|
+
*/
|
|
571
|
+
export function warningMessage(text) {
|
|
572
|
+
return ` ${ICONS.warning} ${PALETTE.warning(text)}`;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Info message
|
|
576
|
+
*/
|
|
577
|
+
export function infoMessage(text) {
|
|
578
|
+
return ` ${ICONS.info} ${PALETTE.info(text)}`;
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Tip/hint message
|
|
582
|
+
*/
|
|
583
|
+
export function tipMessage(text) {
|
|
584
|
+
return ` ${PALETTE.accent('💡')} ${PALETTE.dim(text)}`;
|
|
585
|
+
}
|
|
586
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
587
|
+
// CARDS (for list/show commands)
|
|
588
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
589
|
+
/**
|
|
590
|
+
* Change card for list view
|
|
591
|
+
*/
|
|
592
|
+
export function changeCard(options) {
|
|
593
|
+
const { id, title, status, phase, progress, specs } = options;
|
|
594
|
+
const statusIcon = statusIndicator(status === 'active' ? 'active' :
|
|
595
|
+
status === 'draft' ? 'pending' :
|
|
596
|
+
status === 'completed' ? 'success' : 'info');
|
|
597
|
+
const lines = [];
|
|
598
|
+
// Header
|
|
599
|
+
lines.push(` ${PALETTE.subtle('┌─')} ${statusIcon} ${PALETTE.bold(PALETTE.white(id))}`);
|
|
600
|
+
// Title (if different from id)
|
|
601
|
+
if (title && title !== id) {
|
|
602
|
+
lines.push(` ${PALETTE.subtle('│')} ${PALETTE.dim(title)}`);
|
|
603
|
+
}
|
|
604
|
+
// Phase
|
|
605
|
+
if (phase) {
|
|
606
|
+
lines.push(` ${PALETTE.subtle('│')} ${PALETTE.dim('Phase:')} ${PALETTE.accent(phase)}`);
|
|
607
|
+
}
|
|
608
|
+
// Progress
|
|
609
|
+
if (progress) {
|
|
610
|
+
const bar = createProgressBar(progress.current, progress.total, { width: 15 });
|
|
611
|
+
lines.push(` ${PALETTE.subtle('│')} ${bar}`);
|
|
612
|
+
}
|
|
613
|
+
// Specs count
|
|
614
|
+
if (specs !== undefined) {
|
|
615
|
+
lines.push(` ${PALETTE.subtle('│')} ${PALETTE.dim('Specs:')} ${PALETTE.white(String(specs))}`);
|
|
616
|
+
}
|
|
617
|
+
// Footer
|
|
618
|
+
lines.push(` ${PALETTE.subtle('└─')}`);
|
|
619
|
+
return lines.join('\n');
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Spec card for list view
|
|
623
|
+
*/
|
|
624
|
+
export function specCard(options) {
|
|
625
|
+
const { name, path, requirements, scenarios } = options;
|
|
626
|
+
const lines = [];
|
|
627
|
+
lines.push(` ${PALETTE.subtle('┌─')} ${ICONS.spec} ${PALETTE.bold(PALETTE.primaryBright(name))}`);
|
|
628
|
+
if (path) {
|
|
629
|
+
lines.push(` ${PALETTE.subtle('│')} ${formatPath(path)}`);
|
|
630
|
+
}
|
|
631
|
+
const stats = [];
|
|
632
|
+
if (requirements !== undefined)
|
|
633
|
+
stats.push(`${requirements} requirements`);
|
|
634
|
+
if (scenarios !== undefined)
|
|
635
|
+
stats.push(`${scenarios} scenarios`);
|
|
636
|
+
if (stats.length > 0) {
|
|
637
|
+
lines.push(` ${PALETTE.subtle('│')} ${PALETTE.dim(stats.join(' · '))}`);
|
|
638
|
+
}
|
|
639
|
+
lines.push(` ${PALETTE.subtle('└─')}`);
|
|
640
|
+
return lines.join('\n');
|
|
641
|
+
}
|
|
642
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
643
|
+
// FOOTER & ACTIONS
|
|
644
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
645
|
+
/**
|
|
646
|
+
* Quick actions footer
|
|
647
|
+
*/
|
|
648
|
+
export function quickActions(actions) {
|
|
649
|
+
const lines = [
|
|
650
|
+
'',
|
|
651
|
+
sectionHeader('Quick Actions', '🚀'),
|
|
652
|
+
'',
|
|
653
|
+
];
|
|
654
|
+
actions.forEach((action, i) => {
|
|
655
|
+
lines.push(numberedItem(i + 1, `${PALETTE.white(action.cmd)} ${PALETTE.dim('- ' + action.desc)}`));
|
|
656
|
+
});
|
|
657
|
+
return lines.join('\n');
|
|
658
|
+
}
|
|
659
|
+
/**
|
|
660
|
+
* Next steps suggestion
|
|
661
|
+
*/
|
|
662
|
+
export function nextSteps(steps) {
|
|
663
|
+
const lines = [
|
|
664
|
+
'',
|
|
665
|
+
sectionHeader('Next Steps', '→'),
|
|
666
|
+
'',
|
|
667
|
+
];
|
|
668
|
+
steps.forEach(step => {
|
|
669
|
+
lines.push(listItem(step, { icon: PALETTE.accent('→') }));
|
|
670
|
+
});
|
|
671
|
+
return lines.join('\n');
|
|
672
|
+
}
|
|
673
|
+
//# sourceMappingURL=palette.js.map
|