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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +328 -0
  3. package/dist/cli/commands/archive.d.ts +7 -0
  4. package/dist/cli/commands/archive.d.ts.map +1 -0
  5. package/dist/cli/commands/archive.js +322 -0
  6. package/dist/cli/commands/archive.js.map +1 -0
  7. package/dist/cli/commands/init.d.ts +6 -0
  8. package/dist/cli/commands/init.d.ts.map +1 -0
  9. package/dist/cli/commands/init.js +306 -0
  10. package/dist/cli/commands/init.js.map +1 -0
  11. package/dist/cli/commands/list.d.ts +9 -0
  12. package/dist/cli/commands/list.d.ts.map +1 -0
  13. package/dist/cli/commands/list.js +268 -0
  14. package/dist/cli/commands/list.js.map +1 -0
  15. package/dist/cli/commands/show.d.ts +6 -0
  16. package/dist/cli/commands/show.d.ts.map +1 -0
  17. package/dist/cli/commands/show.js +273 -0
  18. package/dist/cli/commands/show.js.map +1 -0
  19. package/dist/cli/commands/validate.d.ts +8 -0
  20. package/dist/cli/commands/validate.d.ts.map +1 -0
  21. package/dist/cli/commands/validate.js +209 -0
  22. package/dist/cli/commands/validate.js.map +1 -0
  23. package/dist/cli/commands/verify.d.ts +7 -0
  24. package/dist/cli/commands/verify.d.ts.map +1 -0
  25. package/dist/cli/commands/verify.js +328 -0
  26. package/dist/cli/commands/verify.js.map +1 -0
  27. package/dist/cli/commands/view.d.ts +6 -0
  28. package/dist/cli/commands/view.d.ts.map +1 -0
  29. package/dist/cli/commands/view.js +290 -0
  30. package/dist/cli/commands/view.js.map +1 -0
  31. package/dist/cli/index.d.ts +3 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/index.js +87 -0
  34. package/dist/cli/index.js.map +1 -0
  35. package/dist/cli/ui/display.d.ts +189 -0
  36. package/dist/cli/ui/display.d.ts.map +1 -0
  37. package/dist/cli/ui/display.js +449 -0
  38. package/dist/cli/ui/display.js.map +1 -0
  39. package/dist/cli/ui/index.d.ts +12 -0
  40. package/dist/cli/ui/index.d.ts.map +1 -0
  41. package/dist/cli/ui/index.js +71 -0
  42. package/dist/cli/ui/index.js.map +1 -0
  43. package/dist/cli/ui/interactive.d.ts +21 -0
  44. package/dist/cli/ui/interactive.d.ts.map +1 -0
  45. package/dist/cli/ui/interactive.js +60 -0
  46. package/dist/cli/ui/interactive.js.map +1 -0
  47. package/dist/cli/ui/palette.d.ts +301 -0
  48. package/dist/cli/ui/palette.d.ts.map +1 -0
  49. package/dist/cli/ui/palette.js +673 -0
  50. package/dist/cli/ui/palette.js.map +1 -0
  51. package/dist/cli/ui/prompts.d.ts +62 -0
  52. package/dist/cli/ui/prompts.d.ts.map +1 -0
  53. package/dist/cli/ui/prompts.js +119 -0
  54. package/dist/cli/ui/prompts.js.map +1 -0
  55. package/dist/cli/ui/spinner.d.ts +38 -0
  56. package/dist/cli/ui/spinner.d.ts.map +1 -0
  57. package/dist/cli/ui/spinner.js +72 -0
  58. package/dist/cli/ui/spinner.js.map +1 -0
  59. package/dist/core/config/project-config.d.ts +100 -0
  60. package/dist/core/config/project-config.d.ts.map +1 -0
  61. package/dist/core/config/project-config.js +87 -0
  62. package/dist/core/config/project-config.js.map +1 -0
  63. package/dist/core/parsers/spec-parser.d.ts +48 -0
  64. package/dist/core/parsers/spec-parser.d.ts.map +1 -0
  65. package/dist/core/parsers/spec-parser.js +322 -0
  66. package/dist/core/parsers/spec-parser.js.map +1 -0
  67. package/dist/core/validation/spec-validator.d.ts +32 -0
  68. package/dist/core/validation/spec-validator.d.ts.map +1 -0
  69. package/dist/core/validation/spec-validator.js +242 -0
  70. package/dist/core/validation/spec-validator.js.map +1 -0
  71. package/dist/index.d.ts +35 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +44 -0
  74. package/dist/index.js.map +1 -0
  75. package/package.json +66 -0
  76. package/schemas/superspec-workflow/schema.yaml +166 -0
  77. package/templates/design.md +71 -0
  78. package/templates/plan.md +78 -0
  79. package/templates/proposal.md +36 -0
  80. package/templates/spec.md +57 -0
  81. 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