@vibecheckai/cli 3.0.9 → 3.0.10

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.
@@ -1,6 +1,10 @@
1
1
  /**
2
2
  * vibecheck graph - Reality Proof Graph
3
3
  *
4
+ * ═══════════════════════════════════════════════════════════════════════════════
5
+ * ENTERPRISE EDITION - World-Class Terminal Experience
6
+ * ═══════════════════════════════════════════════════════════════════════════════
7
+ *
4
8
  * Builds and visualizes the complete causal chain:
5
9
  * UI action → client function → network call → server route → handler → DB/external
6
10
  *
@@ -27,23 +31,143 @@ const {
27
31
  mergeRuntimeResults
28
32
  } = require("./lib/graph");
29
33
 
34
+ // ═══════════════════════════════════════════════════════════════════════════════
35
+ // ADVANCED TERMINAL - ANSI CODES & UTILITIES
36
+ // ═══════════════════════════════════════════════════════════════════════════════
37
+
30
38
  const c = {
31
39
  reset: '\x1b[0m',
32
40
  bold: '\x1b[1m',
33
41
  dim: '\x1b[2m',
42
+ italic: '\x1b[3m',
43
+ red: '\x1b[31m',
34
44
  green: '\x1b[32m',
35
45
  yellow: '\x1b[33m',
36
- cyan: '\x1b[36m',
37
- red: '\x1b[31m',
38
46
  blue: '\x1b[34m',
47
+ magenta: '\x1b[35m',
48
+ cyan: '\x1b[36m',
49
+ white: '\x1b[37m',
50
+ gray: '\x1b[90m',
51
+ clearLine: '\x1b[2K',
52
+ hideCursor: '\x1b[?25l',
53
+ showCursor: '\x1b[?25h',
39
54
  };
40
55
 
56
+ const rgb = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
57
+ const bgRgb = (r, g, b) => `\x1b[48;2;${r};${g};${b}m`;
58
+
59
+ const colors = {
60
+ gradient1: rgb(255, 100, 150),
61
+ gradient2: rgb(255, 80, 170),
62
+ gradient3: rgb(255, 60, 190),
63
+ shipGreen: rgb(0, 255, 150),
64
+ warnAmber: rgb(255, 200, 0),
65
+ blockRed: rgb(255, 80, 80),
66
+ accent: rgb(255, 100, 150),
67
+ muted: rgb(120, 120, 140),
68
+ node: rgb(100, 200, 255),
69
+ edge: rgb(150, 255, 150),
70
+ broken: rgb(255, 100, 100),
71
+ };
72
+
73
+ // ═══════════════════════════════════════════════════════════════════════════════
74
+ // PREMIUM BANNER
75
+ // ═══════════════════════════════════════════════════════════════════════════════
76
+
77
+ const GRAPH_BANNER = `
78
+ ${rgb(255, 100, 150)} ██████╗ ██████╗ █████╗ ██████╗ ██╗ ██╗${c.reset}
79
+ ${rgb(255, 80, 170)} ██╔════╝ ██╔══██╗██╔══██╗██╔══██╗██║ ██║${c.reset}
80
+ ${rgb(255, 60, 190)} ██║ ███╗██████╔╝███████║██████╔╝███████║${c.reset}
81
+ ${rgb(255, 40, 210)} ██║ ██║██╔══██╗██╔══██║██╔═══╝ ██╔══██║${c.reset}
82
+ ${rgb(255, 20, 230)} ╚██████╔╝██║ ██║██║ ██║██║ ██║ ██║${c.reset}
83
+ ${rgb(255, 0, 255)} ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝${c.reset}
84
+ `;
85
+
86
+ const BANNER_FULL = `
87
+ ${rgb(255, 100, 150)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${c.reset}
88
+ ${rgb(255, 90, 160)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${c.reset}
89
+ ${rgb(255, 80, 170)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${c.reset}
90
+ ${rgb(255, 60, 190)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${c.reset}
91
+ ${rgb(255, 40, 210)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${c.reset}
92
+ ${rgb(255, 20, 230)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${c.reset}
93
+
94
+ ${c.dim} ┌─────────────────────────────────────────────────────────────────────┐${c.reset}
95
+ ${c.dim} │${c.reset} ${rgb(255, 100, 150)}📊${c.reset} ${c.bold}GRAPH${c.reset} ${c.dim}•${c.reset} ${rgb(200, 200, 200)}Proof Visualization${c.reset} ${c.dim}•${c.reset} ${rgb(150, 150, 150)}Causal Chain${c.reset} ${c.dim}•${c.reset} ${rgb(100, 100, 100)}Edge Analysis${c.reset} ${c.dim}│${c.reset}
96
+ ${c.dim} └─────────────────────────────────────────────────────────────────────┘${c.reset}
97
+ `;
98
+
99
+ const ICONS = {
100
+ graph: '📊',
101
+ check: '✓',
102
+ cross: '✗',
103
+ warning: '⚠',
104
+ arrow: '→',
105
+ bullet: '•',
106
+ node: '◉',
107
+ edge: '─',
108
+ broken: '╳',
109
+ sparkle: '✨',
110
+ file: '📄',
111
+ folder: '📁',
112
+ view: '👁️',
113
+ };
114
+
115
+ const SPINNER_DOTS = ['⣾', '⣽', '⣻', '⢿', '⡿', '⣟', '⣯', '⣷'];
116
+ let spinnerIndex = 0;
117
+ let spinnerInterval = null;
118
+ let spinnerStartTime = null;
119
+
120
+ function formatDuration(ms) {
121
+ if (ms < 1000) return `${ms}ms`;
122
+ return `${(ms / 1000).toFixed(1)}s`;
123
+ }
124
+
125
+ function startSpinner(message) {
126
+ spinnerStartTime = Date.now();
127
+ process.stdout.write(c.hideCursor);
128
+ spinnerInterval = setInterval(() => {
129
+ const elapsed = formatDuration(Date.now() - spinnerStartTime);
130
+ process.stdout.write(`\r${c.clearLine} ${colors.accent}${SPINNER_DOTS[spinnerIndex]}${c.reset} ${message} ${c.dim}${elapsed}${c.reset}`);
131
+ spinnerIndex = (spinnerIndex + 1) % SPINNER_DOTS.length;
132
+ }, 80);
133
+ }
134
+
135
+ function stopSpinner(message, success = true) {
136
+ if (spinnerInterval) {
137
+ clearInterval(spinnerInterval);
138
+ spinnerInterval = null;
139
+ }
140
+ const elapsed = spinnerStartTime ? formatDuration(Date.now() - spinnerStartTime) : '';
141
+ const icon = success ? `${colors.shipGreen}${ICONS.check}${c.reset}` : `${colors.blockRed}${ICONS.cross}${c.reset}`;
142
+ process.stdout.write(`\r${c.clearLine} ${icon} ${message} ${c.dim}${elapsed}${c.reset}\n`);
143
+ process.stdout.write(c.showCursor);
144
+ spinnerStartTime = null;
145
+ }
146
+
147
+ function printDivider(char = '─', width = 69, color = c.dim) {
148
+ console.log(`${color} ${char.repeat(width)}${c.reset}`);
149
+ }
150
+
151
+ function printSection(title, icon = '◆') {
152
+ console.log();
153
+ console.log(` ${colors.accent}${icon}${c.reset} ${c.bold}${title}${c.reset}`);
154
+ printDivider();
155
+ }
156
+
157
+ function progressBar(percent, width = 25) {
158
+ const filled = Math.round((percent / 100) * width);
159
+ const empty = width - filled;
160
+ const color = percent >= 80 ? colors.shipGreen : percent >= 50 ? colors.warnAmber : colors.blockRed;
161
+ return `${color}${'█'.repeat(filled)}${c.dim}${'░'.repeat(empty)}${c.reset}`;
162
+ }
163
+
41
164
  function ensureDir(p) {
42
165
  fs.mkdirSync(p, { recursive: true });
43
166
  }
44
167
 
45
168
  async function runGraph(args) {
46
169
  const opts = parseArgs(args);
170
+ const startTime = Date.now();
47
171
 
48
172
  if (opts.help) {
49
173
  printHelp();
@@ -51,39 +175,51 @@ async function runGraph(args) {
51
175
  }
52
176
 
53
177
  const root = path.resolve(opts.path || process.cwd());
178
+ const projectName = path.basename(root);
54
179
  const outDir = path.join(root, ".vibecheck", "graph");
55
180
  ensureDir(outDir);
56
181
 
57
- console.log(`\n${c.cyan}${c.bold}🔍 vibecheck graph${c.reset}`);
58
- console.log(`${c.dim}Building Reality Proof Graph...${c.reset}\n`);
182
+ // Print banner
183
+ console.log(BANNER_FULL);
184
+ console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
185
+ console.log(` ${c.dim}Path:${c.reset} ${root}`);
186
+ console.log();
59
187
 
60
188
  // Load or build truthpack
189
+ startSpinner('Loading truthpack...');
61
190
  let truthpack = loadTruthpack(root);
62
191
  if (!truthpack) {
63
- console.log(`${c.dim}Building truthpack...${c.reset}`);
192
+ stopSpinner('Truthpack not found, building...', true);
193
+ startSpinner('Building truthpack...');
64
194
  truthpack = await buildTruthpack({ repoRoot: root, fastifyEntry: opts.fastifyEntry });
65
195
  }
196
+ stopSpinner('Truthpack loaded', true);
66
197
 
67
198
  // Extract static graph
68
- console.log(`${c.dim}Extracting static edges...${c.reset}`);
199
+ startSpinner('Extracting static edges...');
69
200
  const staticData = await extractStaticGraph(root, truthpack);
201
+ stopSpinner(`Extracted ${staticData.edges?.length || 0} edges`, true);
70
202
 
71
203
  // Build proof graph
204
+ startSpinner('Building proof graph...');
72
205
  let graph = buildProofGraph({
73
206
  nodes: staticData.nodes,
74
207
  edges: staticData.edges,
75
208
  meta: { repoRoot: root }
76
209
  });
210
+ stopSpinner(`Built graph with ${graph.nodes?.length || 0} nodes`, true);
77
211
 
78
212
  // Runtime verification (optional)
79
213
  if (opts.runtime && opts.url) {
80
- console.log(`${c.dim}Collecting runtime edges from ${opts.url}...${c.reset}`);
214
+ startSpinner(`Collecting runtime edges from ${opts.url}...`);
81
215
 
82
216
  try {
83
217
  const runtimeEdges = await collectRuntimeEdges(opts.url, graph, opts);
84
218
  graph = mergeRuntimeResults(graph, runtimeEdges);
219
+ stopSpinner('Runtime edges collected', true);
85
220
  } catch (e) {
86
- console.log(`${c.yellow}⚠️ Runtime collection failed: ${e.message}${c.reset}`);
221
+ stopSpinner('Runtime collection failed', false);
222
+ console.log(` ${c.dim}${e.message}${c.reset}`);
87
223
  }
88
224
  }
89
225
 
@@ -91,6 +227,7 @@ async function runGraph(args) {
91
227
  const findings = getFindingsFromGraph(graph);
92
228
 
93
229
  // Write outputs
230
+ startSpinner('Writing outputs...');
94
231
  fs.writeFileSync(path.join(outDir, "graph.json"), JSON.stringify(graph, null, 2), "utf8");
95
232
 
96
233
  if (!opts.jsonOnly) {
@@ -104,23 +241,31 @@ async function runGraph(args) {
104
241
  if (findings.length > 0) {
105
242
  fs.writeFileSync(path.join(outDir, "broken-edges.json"), JSON.stringify(findings, null, 2), "utf8");
106
243
  }
244
+ stopSpinner('Outputs written', true);
107
245
 
108
246
  // Print summary
109
- console.log(`\n${c.bold}Graph Summary${c.reset}`);
110
- console.log(` Nodes: ${graph.nodes.length}`);
111
- console.log(` Valid edges: ${c.green}${graph.edges.length}${c.reset}`);
112
- console.log(` Broken edges: ${graph.brokenEdges.length > 0 ? c.red : c.green}${graph.brokenEdges.length}${c.reset}`);
113
- console.log(` Coverage: ${graph.coverage.percent}%`);
247
+ const duration = Date.now() - startTime;
248
+ const coverage = graph.coverage?.percent || 0;
249
+
250
+ printSection('GRAPH SUMMARY', ICONS.graph);
251
+ console.log();
252
+ console.log(` ${colors.node}${ICONS.node}${c.reset} ${c.bold}Nodes:${c.reset} ${graph.nodes?.length || 0}`);
253
+ console.log(` ${colors.edge}${ICONS.edge}${c.reset} ${c.bold}Valid edges:${c.reset} ${colors.shipGreen}${graph.edges?.length || 0}${c.reset}`);
254
+ console.log(` ${colors.broken}${ICONS.broken}${c.reset} ${c.bold}Broken edges:${c.reset} ${graph.brokenEdges?.length > 0 ? colors.blockRed : colors.shipGreen}${graph.brokenEdges?.length || 0}${c.reset}`);
255
+ console.log();
256
+ console.log(` ${c.bold}Coverage:${c.reset} ${progressBar(coverage)} ${coverage}%`);
257
+ console.log(` ${c.bold}Duration:${c.reset} ${formatDuration(duration)}`);
114
258
 
115
259
  if (opts.broken || findings.length > 0) {
116
- console.log(`\n${c.bold}Broken Edges${c.reset}`);
260
+ printSection('BROKEN EDGES', ICONS.broken);
261
+ console.log();
117
262
 
118
263
  if (findings.length === 0) {
119
- console.log(` ${c.green} No broken edges found${c.reset}`);
264
+ console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} No broken edges found`);
120
265
  } else {
121
266
  for (const f of findings.slice(0, 10)) {
122
- const icon = f.severity === "BLOCK" ? `${c.red}✗` : `${c.yellow}⚠`;
123
- console.log(` ${icon} ${f.title}${c.reset}`);
267
+ const icon = f.severity === "BLOCK" ? `${colors.blockRed}●` : `${colors.warnAmber}●`;
268
+ console.log(` ${icon}${c.reset} ${f.title}`);
124
269
  }
125
270
  if (findings.length > 10) {
126
271
  console.log(` ${c.dim}... and ${findings.length - 10} more${c.reset}`);
@@ -128,21 +273,22 @@ async function runGraph(args) {
128
273
  }
129
274
  }
130
275
 
131
- console.log(`\n${c.bold}Output${c.reset}`);
132
- console.log(` ${c.dim}Graph:${c.reset} .vibecheck/graph/graph.json`);
276
+ printSection('OUTPUT', ICONS.folder);
277
+ console.log();
278
+ console.log(` ${ICONS.file} ${colors.accent}graph.json${c.reset} ${c.dim}Machine-readable graph${c.reset}`);
133
279
  if (!opts.jsonOnly) {
134
- console.log(` ${c.dim}Visualization:${c.reset} .vibecheck/graph/graph.html`);
135
- console.log(` ${c.dim}Mermaid:${c.reset} .vibecheck/graph/graph.mmd`);
280
+ console.log(` ${ICONS.view} ${colors.accent}graph.html${c.reset} ${c.dim}Interactive visualization${c.reset}`);
281
+ console.log(` ${ICONS.file} ${colors.accent}graph.mmd${c.reset} ${c.dim}Mermaid diagram${c.reset}`);
136
282
  }
137
283
  if (findings.length > 0) {
138
- console.log(` ${c.dim}Broken edges:${c.reset} .vibecheck/graph/broken-edges.json`);
284
+ console.log(` ${ICONS.warning} ${colors.accent}broken-edges.json${c.reset} ${c.dim}Broken edges as findings${c.reset}`);
139
285
  }
140
286
 
141
287
  if (opts.view && !opts.jsonOnly) {
142
288
  const htmlPath = path.join(outDir, "graph.html");
143
- console.log(`\n${c.dim}Opening graph visualization...${c.reset}`);
289
+ console.log();
290
+ console.log(` ${c.dim}Opening visualization...${c.reset}`);
144
291
 
145
- // Cross-platform open
146
292
  const { exec } = require("child_process");
147
293
  const cmd = process.platform === "win32" ? `start "" "${htmlPath}"` :
148
294
  process.platform === "darwin" ? `open "${htmlPath}"` :
@@ -153,7 +299,13 @@ async function runGraph(args) {
153
299
  const blocks = findings.filter(f => f.severity === "BLOCK").length;
154
300
  const warns = findings.filter(f => f.severity === "WARN").length;
155
301
 
156
- console.log(`\n${c.bold}Verdict:${c.reset} ${blocks ? `${c.red}🛑 BLOCK${c.reset}` : warns ? `${c.yellow}⚠️ WARN${c.reset}` : `${c.green}✅ CLEAN${c.reset}`}`);
302
+ // Verdict card
303
+ console.log();
304
+ const verdictColor = blocks ? colors.blockRed : warns ? colors.warnAmber : colors.shipGreen;
305
+ const verdictIcon = blocks ? '🛑' : warns ? '⚠️' : '✅';
306
+ const verdictText = blocks ? 'BLOCK' : warns ? 'WARN' : 'CLEAN';
307
+ console.log(` ${verdictColor}${c.bold}${verdictIcon} ${verdictText}${c.reset}`);
308
+ console.log();
157
309
 
158
310
  return blocks ? 2 : warns ? 1 : 0;
159
311
  }
@@ -228,56 +380,61 @@ function parseArgs(args) {
228
380
  }
229
381
 
230
382
  function printHelp() {
383
+ console.log(BANNER_FULL);
231
384
  console.log(`
232
- ${c.cyan}${c.bold}🔍 vibecheck graph${c.reset} - Reality Proof Graph
233
-
234
- Build and visualize the complete causal chain from UI to database.
235
- Identify broken edges that indicate missing routes, runtime failures, or causal contradictions.
236
-
237
- ${c.bold}USAGE${c.reset}
238
- vibecheck graph ${c.dim}# Build static graph${c.reset}
239
- vibecheck graph --runtime --url http://... ${c.dim}# Add runtime edges${c.reset}
240
- vibecheck graph --view ${c.dim}# Open visualization${c.reset}
241
- vibecheck graph --broken ${c.dim}# List broken edges${c.reset}
242
-
243
- ${c.bold}OPTIONS${c.reset}
244
- --build Build static graph (default)
245
- --runtime Collect runtime edges via Playwright
246
- --url <url> Target URL for runtime collection
247
- --view Open interactive HTML visualization
248
- --broken Show only broken edges
249
- --json Output JSON only (no HTML/Mermaid)
250
- --headed Run browser in headed mode
251
- --fastify-entry Fastify entry file (e.g. src/server.ts)
252
- --path, -p Project path (default: current directory)
253
- --help, -h Show this help
254
-
255
- ${c.bold}GRAPH NODES${c.reset}
256
- UI Action onClick, onSubmit, etc.
257
- • Client Function Functions that make network calls
258
- Network Call fetch(), axios.*()
259
- Server Route Next API routes, Fastify routes
260
- Handler Route handler functions
261
- DB Call Prisma, raw SQL
262
- • External Call Stripe, GitHub, etc.
263
-
264
- ${c.bold}BROKEN EDGE TYPES${c.reset}
265
- ${c.red}MissingRoute${c.reset} Route referenced but doesn't exist
266
- ${c.red}RuntimeFailure${c.reset} Route returns 4xx/5xx at runtime
267
- • ${c.red}CausalContradiction${c.reset} UI shows success but server returned error
268
-
269
- ${c.bold}OUTPUT${c.reset}
270
- .vibecheck/graph/
271
- graph.json Machine-readable graph
272
- graph.html Interactive D3 visualization
273
- graph.mmd Mermaid diagram
274
- broken-edges.json Broken edges as findings
275
-
276
- ${c.bold}EXAMPLES${c.reset}
277
- vibecheck graph --view ${c.dim}# Build + open visualization${c.reset}
278
- vibecheck graph --runtime --url http://localhost:3000 --view
279
- vibecheck graph --broken --json ${c.dim}# CI mode: just broken edges${c.reset}
280
- `);
385
+ ${c.bold}Usage:${c.reset} vibecheck graph [options]
386
+
387
+ ${c.bold}Reality Proof Graph${c.reset} — Visualize the complete causal chain from UI to database.
388
+
389
+ ${c.bold}Modes:${c.reset}
390
+ ${colors.accent}vibecheck graph${c.reset} ${c.dim}Build static graph${c.reset}
391
+ ${colors.accent}vibecheck graph --runtime --url http://...${c.reset} ${c.dim}Add runtime edges${c.reset}
392
+ ${colors.accent}vibecheck graph --view${c.reset} ${c.dim}Open visualization${c.reset}
393
+ ${colors.accent}vibecheck graph --broken${c.reset} ${c.dim}List broken edges${c.reset}
394
+
395
+ ${c.bold}Options:${c.reset}
396
+ ${colors.accent}--build${c.reset} Build static graph ${c.dim}(default)${c.reset}
397
+ ${colors.accent}--runtime${c.reset} Collect runtime edges via Playwright
398
+ ${colors.accent}--url <url>${c.reset} Target URL for runtime collection
399
+ ${colors.accent}--view${c.reset} Open interactive HTML visualization
400
+ ${colors.accent}--broken${c.reset} Show only broken edges
401
+ ${colors.accent}--json${c.reset} Output JSON only ${c.dim}(no HTML/Mermaid)${c.reset}
402
+ ${colors.accent}--headed${c.reset} Run browser in headed mode
403
+ ${colors.accent}--fastify-entry${c.reset} Fastify entry file ${c.dim}(e.g. src/server.ts)${c.reset}
404
+ ${colors.accent}--path, -p${c.reset} Project path ${c.dim}(default: current directory)${c.reset}
405
+ ${colors.accent}--help, -h${c.reset} Show this help
406
+
407
+ ${c.bold}Graph Nodes:${c.reset}
408
+ ${colors.node}◉${c.reset} UI Action ${c.dim}onClick, onSubmit, etc.${c.reset}
409
+ ${colors.node}◉${c.reset} Client Function ${c.dim}Functions that make network calls${c.reset}
410
+ ${colors.node}◉${c.reset} Network Call ${c.dim}fetch(), axios.*()${c.reset}
411
+ ${colors.node}◉${c.reset} Server Route ${c.dim}Next API routes, Fastify routes${c.reset}
412
+ ${colors.node}◉${c.reset} Handler ${c.dim}Route handler functions${c.reset}
413
+ ${colors.node}◉${c.reset} DB Call ${c.dim}Prisma, raw SQL${c.reset}
414
+ ${colors.node}◉${c.reset} External Call ${c.dim}Stripe, GitHub, etc.${c.reset}
415
+
416
+ ${c.bold}Broken Edge Types:${c.reset}
417
+ ${colors.blockRed}╳${c.reset} MissingRoute ${c.dim}Route referenced but doesn't exist${c.reset}
418
+ ${colors.blockRed}╳${c.reset} RuntimeFailure ${c.dim}Route returns 4xx/5xx at runtime${c.reset}
419
+ ${colors.blockRed}╳${c.reset} CausalContradiction ${c.dim}UI shows success but server returned error${c.reset}
420
+
421
+ ${c.bold}Output:${c.reset}
422
+ ${c.dim}.vibecheck/graph/${c.reset}
423
+ ${colors.accent}graph.json${c.reset} ${c.dim}Machine-readable graph${c.reset}
424
+ ${colors.accent}graph.html${c.reset} ${c.dim}Interactive D3 visualization${c.reset}
425
+ ${colors.accent}graph.mmd${c.reset} ${c.dim}Mermaid diagram${c.reset}
426
+ ${colors.accent}broken-edges.json${c.reset} ${c.dim}Broken edges as findings${c.reset}
427
+
428
+ ${c.bold}Examples:${c.reset}
429
+ ${c.dim}# Build + open visualization${c.reset}
430
+ vibecheck graph --view
431
+
432
+ ${c.dim}# Runtime verification with Playwright${c.reset}
433
+ vibecheck graph --runtime --url http://localhost:3000 --view
434
+
435
+ ${c.dim}# CI mode: just broken edges as JSON${c.reset}
436
+ vibecheck graph --broken --json
437
+ `);
281
438
  }
282
439
 
283
440
  module.exports = { runGraph };