guardrail-cli 2.4.13 → 2.5.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/dist/index.js CHANGED
@@ -164,6 +164,8 @@ const styles = {
164
164
  bgBlue: '\x1b[44m',
165
165
  bgMagenta: '\x1b[45m',
166
166
  bgCyan: '\x1b[46m',
167
+ // Symbols
168
+ bullet: '•',
167
169
  };
168
170
  // Styled text helpers
169
171
  const style = {
@@ -300,25 +302,58 @@ function printStatusBadge(status) {
300
302
  };
301
303
  console.log(` ${badges[status] || badges.free}`);
302
304
  }
303
- // Enterprise-styled prompt helpers
305
+ // Enterprise-styled prompt helpers with arrow key navigation
304
306
  async function promptSelect(message, choices) {
305
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
306
307
  return new Promise((resolve) => {
307
- console.log('');
308
- console.log(` ${styles.brightCyan}${styles.bold}?${styles.reset} ${styles.bold}${message}${styles.reset}`);
309
- console.log(` ${styles.dim}${box.teeLeft}${box.horizontal.repeat(50)}${styles.reset}`);
310
- choices.forEach((c, i) => {
311
- const num = `${styles.cyan}${styles.bold}[${i + 1}]${styles.reset}`;
312
- const badge = c.badge ? ` ${c.badge}` : '';
313
- console.log(` ${styles.dim}${box.vertical}${styles.reset} ${num} ${c.name}${badge}`);
314
- });
315
- console.log(` ${styles.dim}${box.bottomLeft}${box.horizontal.repeat(50)}${styles.reset}`);
316
- console.log('');
317
- rl.question(` ${styles.brightCyan}❯${styles.reset} Enter choice ${styles.dim}(1-${choices.length})${styles.reset}: `, (answer) => {
318
- rl.close();
319
- const idx = parseInt(answer, 10) - 1;
320
- resolve(choices[Math.max(0, Math.min(idx, choices.length - 1))].value);
308
+ const rl = readline.createInterface({
309
+ input: process.stdin,
310
+ output: process.stdout,
311
+ terminal: true
321
312
  });
313
+ let selectedIndex = 0;
314
+ const renderMenu = () => {
315
+ console.clear();
316
+ console.log('');
317
+ console.log(` ${styles.brightCyan}${styles.bold}?${styles.reset} ${styles.bold}${message}${styles.reset}`);
318
+ console.log(` ${styles.dim}${box.teeLeft}${box.horizontal.repeat(50)}${styles.reset}`);
319
+ choices.forEach((choice, i) => {
320
+ const isSelected = i === selectedIndex;
321
+ const prefix = isSelected ? `${styles.brightCyan}${styles.bold}❯${styles.reset}` : ' ';
322
+ const badge = choice.badge ? ` ${choice.badge}` : '';
323
+ const color = isSelected ? styles.brightWhite : styles.dim;
324
+ console.log(` ${styles.dim}${box.vertical}${styles.reset} ${prefix} ${color}${choice.name}${badge}${styles.reset}`);
325
+ });
326
+ console.log(` ${styles.dim}${box.bottomLeft}${box.horizontal.repeat(50)}${styles.reset}`);
327
+ console.log('');
328
+ console.log(` ${styles.dim}Use ↑↓ arrows to move, Enter to select${styles.reset}`);
329
+ };
330
+ renderMenu();
331
+ // Handle keypress events
332
+ readline.emitKeypressEvents(process.stdin);
333
+ process.stdin.setRawMode(true);
334
+ const onKeyPress = (str, key) => {
335
+ if (key.name === 'up') {
336
+ selectedIndex = selectedIndex > 0 ? selectedIndex - 1 : choices.length - 1;
337
+ renderMenu();
338
+ }
339
+ else if (key.name === 'down') {
340
+ selectedIndex = selectedIndex < choices.length - 1 ? selectedIndex + 1 : 0;
341
+ renderMenu();
342
+ }
343
+ else if (key.name === 'return' || key.name === 'enter') {
344
+ process.stdin.setRawMode(false);
345
+ process.stdin.removeListener('keypress', onKeyPress);
346
+ rl.close();
347
+ resolve(choices[selectedIndex].value);
348
+ }
349
+ else if (key.ctrl && key.name === 'c') {
350
+ process.stdin.setRawMode(false);
351
+ process.stdin.removeListener('keypress', onKeyPress);
352
+ rl.close();
353
+ process.exit(0);
354
+ }
355
+ };
356
+ process.stdin.on('keypress', onKeyPress);
322
357
  });
323
358
  }
324
359
  async function promptInput(message, defaultValue) {
@@ -1494,7 +1529,7 @@ program
1494
1529
  console.log(` ${styles.dim}List backups:${styles.reset} ${styles.bold}guardrail fix rollback --list${styles.reset}`);
1495
1530
  console.log('');
1496
1531
  }
1497
- (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.INVALID_INPUT, 'Run ID required');
1532
+ (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.USER_ERROR, 'Run ID required');
1498
1533
  }
1499
1534
  if (!options.json) {
1500
1535
  console.log('');
@@ -1667,38 +1702,156 @@ program
1667
1702
  });
1668
1703
  }
1669
1704
  }
1670
- if (options.badge && shipResult.verdict === 'ship') {
1671
- const badgeLines = [
1672
- `${styles.brightGreen}${styles.bold}${icons.success} SHIP BADGE READY${styles.reset}`,
1673
- '',
1674
- `${styles.dim}Permalink:${styles.reset} ${shipResult.permalink}`,
1675
- `${styles.dim}Embed code:${styles.reset} ${shipResult.embedCode}`,
1676
- ];
1677
- const framedBadge = frameLines(badgeLines, { padding: 2 });
1678
- console.log(framedBadge.join('\n'));
1705
+ // Show badge embed code
1706
+ if (shipResult.embedCode) {
1707
+ console.log(`${styles.bold}BADGE EMBED CODE${styles.reset}`);
1708
+ printDivider();
1709
+ console.log(` ${styles.dim}${shipResult.embedCode}${styles.reset}`);
1679
1710
  console.log('');
1680
1711
  }
1681
1712
  }
1682
1713
  if (options.output) {
1683
- const reportData = {
1714
+ const output = {
1684
1715
  ship: shipResult,
1685
1716
  mockproof: mockproofResult,
1686
- generated: new Date().toISOString()
1717
+ timestamp: new Date().toISOString(),
1718
+ project: {
1719
+ name: projectName,
1720
+ path: projectPath
1721
+ }
1687
1722
  };
1688
- (0, fs_1.writeFileSync)(options.output, JSON.stringify(reportData, null, 2));
1689
- console.log(` ${styles.brightGreen}${icons.success}${styles.reset} Report written to ${options.output}`);
1723
+ (0, fs_1.writeFileSync)(options.output, JSON.stringify(output, null, 2));
1724
+ console.log(`${styles.dim}Report saved to:${styles.reset} ${options.output}`);
1725
+ }
1726
+ // Exit with appropriate code
1727
+ const exitCode = shipResult.verdict === 'ship' ? exit_codes_1.ExitCode.SUCCESS : exit_codes_1.ExitCode.POLICY_FAIL;
1728
+ (0, exit_codes_1.exitWith)(exitCode);
1729
+ }
1730
+ catch (error) {
1731
+ console.error(`${styles.brightRed}Error:${styles.reset} ${error.message}`);
1732
+ (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Ship check failed');
1733
+ }
1734
+ });
1735
+ // Pro Ship command (Pro feature - $99/month)
1736
+ program
1737
+ .command('ship:pro')
1738
+ .description('Pro Ship Check - Comprehensive scanning with all services (Pro $99/mo)')
1739
+ .option('-p, --path <path>', 'Project path to analyze', '.')
1740
+ .option('-f, --format <format>', 'Output format: table, json, markdown', 'table')
1741
+ .option('-o, --output <file>', 'Output file path')
1742
+ .option('--url <baseUrl>', 'Base URL for reality mode scanning')
1743
+ .option('--no-reality', 'Skip reality mode scan')
1744
+ .option('--no-security', 'Skip security scan')
1745
+ .option('--no-performance', 'Skip performance check')
1746
+ .option('--no-accessibility', 'Skip accessibility check')
1747
+ .option('--badge', 'Generate dynamic badge', true)
1748
+ .action(async (options) => {
1749
+ const config = requireAuth('pro');
1750
+ printLogo();
1751
+ const projectPath = (0, path_1.resolve)(options.path);
1752
+ const projectName = (0, path_1.basename)(projectPath);
1753
+ (0, ui_1.printCommandHeader)({
1754
+ title: 'PRO SHIP CHECK',
1755
+ icon: icons.ship,
1756
+ projectName,
1757
+ projectPath,
1758
+ metadata: [
1759
+ { key: 'Reality Mode', value: !options.noReality ? 'Enabled' : 'Disabled' },
1760
+ { key: 'Security Scan', value: !options.noSecurity ? 'Enabled' : 'Disabled' },
1761
+ { key: 'Performance', value: !options.noPerformance ? 'Enabled' : 'Disabled' },
1762
+ { key: 'Accessibility', value: !options.noAccessibility ? 'Enabled' : 'Disabled' },
1763
+ { key: 'Dynamic Badge', value: options.badge ? 'Enabled' : 'Disabled' },
1764
+ ],
1765
+ tier: (await config).tier,
1766
+ authenticated: !!(await config).apiKey,
1767
+ });
1768
+ try {
1769
+ // Import pro ship scanner
1770
+ const { ProShipScanner } = require('guardrail-ship');
1771
+ const proShipScanner = new ProShipScanner();
1772
+ const scanConfig = {
1773
+ projectPath,
1774
+ baseUrl: options.url,
1775
+ includeRealityMode: !options.noReality,
1776
+ includeSecurityScan: !options.noSecurity,
1777
+ includePerformanceCheck: !options.noPerformance,
1778
+ includeAccessibilityCheck: !options.noAccessibility,
1779
+ };
1780
+ console.log(`${styles.dim}Running comprehensive scan...${styles.reset}`);
1781
+ console.log('');
1782
+ const result = await proShipScanner.runComprehensiveScan(scanConfig);
1783
+ if (options.format === 'json') {
1784
+ console.log(JSON.stringify(result, null, 2));
1785
+ }
1786
+ else {
1787
+ // Display comprehensive results
1788
+ const verdictColor = result.verdict === 'SHIP' ? styles.brightGreen :
1789
+ result.verdict === 'NO-SHIP' ? styles.brightRed : styles.brightYellow;
1790
+ const verdictIcon = result.verdict === 'SHIP' ? icons.success :
1791
+ result.verdict === 'NO-SHIP' ? icons.error : icons.warning;
1792
+ const verdictLines = [
1793
+ `${verdictColor}${styles.bold}${verdictIcon} ${result.verdict}${styles.reset}`,
1794
+ '',
1795
+ `${styles.dim}Overall Score:${styles.reset} ${styles.bold}${result.overallScore}${styles.reset}/100`,
1796
+ `${styles.dim}Scans Completed:${styles.reset} ${result.summary.totalScans}/${result.summary.totalScans}`,
1797
+ `${styles.dim}Passed:${styles.reset} ${styles.brightGreen}${result.summary.passedScans}${styles.reset}`,
1798
+ `${styles.dim}Failed:${styles.reset} ${styles.brightRed}${result.summary.failedScans}${styles.reset}`,
1799
+ `${styles.dim}Critical Issues:${styles.reset} ${styles.brightRed}${result.summary.criticalIssues}${styles.reset}`,
1800
+ `${styles.dim}Warnings:${styles.reset} ${styles.brightYellow}${result.summary.warnings}${styles.reset}`,
1801
+ `${styles.dim}Duration:${styles.reset} ${(result.summary.totalDuration / 1000).toFixed(2)}s`,
1802
+ ];
1803
+ const framedVerdict = frameLines(verdictLines, { padding: 2 });
1804
+ console.log(framedVerdict.join('\n'));
1805
+ console.log('');
1806
+ // Show individual scan results
1807
+ console.log(`${styles.bold}SCAN RESULTS${styles.reset}`);
1808
+ printDivider();
1809
+ result.scans.forEach((scan, index) => {
1810
+ const statusColor = scan.status === 'pass' ? styles.brightGreen :
1811
+ scan.status === 'fail' ? styles.brightRed :
1812
+ scan.status === 'warning' ? styles.brightYellow : styles.brightRed;
1813
+ const statusIcon = scan.status === 'pass' ? icons.success :
1814
+ scan.status === 'fail' ? icons.error :
1815
+ scan.status === 'warning' ? icons.warning : icons.error;
1816
+ console.log(`${styles.cyan}${index + 1}.${styles.reset} ${styles.bold}${scan.name}${styles.reset}`);
1817
+ console.log(` Status: ${statusColor}${statusIcon} ${scan.status.toUpperCase()}${styles.reset}`);
1818
+ console.log(` Score: ${styles.bold}${scan.score}${styles.reset}/100`);
1819
+ console.log(` Duration: ${(scan.duration / 1000).toFixed(2)}s`);
1820
+ if (scan.criticalIssues > 0) {
1821
+ console.log(` Critical: ${styles.brightRed}${scan.criticalIssues}${styles.reset}`);
1822
+ }
1823
+ if (scan.warnings > 0) {
1824
+ console.log(` Warnings: ${styles.brightYellow}${scan.warnings}${styles.reset}`);
1825
+ }
1826
+ console.log('');
1827
+ });
1828
+ // Show recommendation
1829
+ console.log(`${styles.bold}RECOMMENDATION${styles.reset}`);
1830
+ printDivider();
1831
+ console.log(`${styles.dim}${result.recommendation}${styles.reset}`);
1690
1832
  console.log('');
1833
+ // Show badge info
1834
+ if (options.badge && result.badge) {
1835
+ console.log(`${styles.bold}DYNAMIC BADGE${styles.reset}`);
1836
+ printDivider();
1837
+ console.log(`${styles.dim}SVG URL:${styles.reset} ${result.badge.svgUrl}`);
1838
+ console.log(`${styles.dim}JSON URL:${styles.reset} ${result.badge.jsonUrl}`);
1839
+ console.log(`${styles.dim}Embed Code:${styles.reset}`);
1840
+ console.log(` ${styles.dim}${result.badge.embedCode}${styles.reset}`);
1841
+ console.log('');
1842
+ }
1691
1843
  }
1692
- // Exit with error if not ready
1693
- if (shipResult.verdict !== 'ship' || (mockproofResult && mockproofResult.verdict === 'fail')) {
1694
- (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.POLICY_FAIL, 'Ship check failed');
1844
+ if (options.output) {
1845
+ (0, fs_1.writeFileSync)(options.output, JSON.stringify(result, null, 2));
1846
+ console.log(`${styles.dim}Report saved to:${styles.reset} ${options.output}`);
1695
1847
  }
1848
+ // Exit with appropriate code
1849
+ const exitCode = result.verdict === 'SHIP' ? exit_codes_1.ExitCode.SUCCESS : exit_codes_1.ExitCode.POLICY_FAIL;
1850
+ (0, exit_codes_1.exitWith)(exitCode);
1696
1851
  }
1697
1852
  catch (error) {
1698
- console.log('');
1699
- console.log(` ${styles.brightRed}${icons.error}${styles.reset} ${styles.bold}Ship check failed:${styles.reset} ${error.message}`);
1700
- console.log('');
1701
- (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Ship check execution failed');
1853
+ console.error(`${styles.brightRed}Error:${styles.reset} ${error.message}`);
1854
+ (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Pro ship check failed');
1702
1855
  }
1703
1856
  });
1704
1857
  // Reality command (Starter+ feature)
@@ -1755,22 +1908,53 @@ program
1755
1908
  // Check dependencies first
1756
1909
  const depCheck = checkPlaywrightDependencies(projectPath);
1757
1910
  if (!depCheck.playwrightInstalled) {
1758
- console.log(` ${styles.brightRed}${icons.error} Playwright not installed${styles.reset}`);
1911
+ console.log(` ${styles.brightYellow}${icons.warning} Playwright not installed${styles.reset}`);
1759
1912
  console.log('');
1760
- console.log(` ${styles.bold}Install commands:${styles.reset}`);
1761
- depCheck.installCommands.forEach(cmd => {
1762
- console.log(` ${styles.brightCyan}${cmd}${styles.reset}`);
1763
- });
1913
+ // Try to install automatically
1914
+ console.log(` ${styles.brightCyan}${icons.info} Attempting automatic installation...${styles.reset}`);
1915
+ const installResult = await installPlaywrightDependencies(projectPath);
1916
+ if (!installResult.success) {
1917
+ console.log(` ${styles.brightRed}${icons.error} Auto-installation failed: ${installResult.error}${styles.reset}`);
1918
+ console.log('');
1919
+ console.log(` ${styles.bold}Manual install commands:${styles.reset}`);
1920
+ depCheck.installCommands.forEach(cmd => {
1921
+ console.log(` ${styles.brightCyan}${cmd}${styles.reset}`);
1922
+ });
1923
+ console.log('');
1924
+ (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright not installed');
1925
+ }
1926
+ console.log(` ${styles.brightGreen}${icons.success} Playwright installed successfully${styles.reset}`);
1764
1927
  console.log('');
1765
- (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright not installed');
1766
1928
  }
1767
1929
  if (!depCheck.browsersInstalled) {
1768
1930
  console.log(` ${styles.brightYellow}${icons.warning} Playwright browsers not installed${styles.reset}`);
1769
1931
  console.log('');
1770
- console.log(` ${styles.bold}Install command:${styles.reset}`);
1771
- console.log(` ${styles.brightCyan}npx playwright install${styles.reset}`);
1772
- console.log('');
1773
- (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright browsers not installed');
1932
+ // Try to install browsers only
1933
+ console.log(` ${styles.brightCyan}${icons.info} Installing browsers...${styles.reset}`);
1934
+ try {
1935
+ await new Promise((resolve, reject) => {
1936
+ const browserInstall = spawn('npx', ['playwright', 'install'], {
1937
+ cwd: projectPath,
1938
+ stdio: 'pipe'
1939
+ });
1940
+ browserInstall.on('close', (code) => {
1941
+ if (code === 0) {
1942
+ console.log(` ${styles.brightGreen}${icons.success} Browsers installed successfully${styles.reset}`);
1943
+ resolve();
1944
+ }
1945
+ else {
1946
+ reject(new Error('browser install failed'));
1947
+ }
1948
+ });
1949
+ browserInstall.on('error', reject);
1950
+ });
1951
+ }
1952
+ catch (error) {
1953
+ console.log(` ${styles.brightRed}${icons.error} Browser installation failed: ${error.message}${styles.reset}`);
1954
+ console.log(` ${styles.brightCyan}npx playwright install${styles.reset}`);
1955
+ console.log('');
1956
+ (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright browsers not installed');
1957
+ }
1774
1958
  }
1775
1959
  // Create artifact directory for recorded test
1776
1960
  const artifacts = createArtifactDirectory(projectPath, options.flow);
@@ -1852,22 +2036,60 @@ program
1852
2036
  console.log('');
1853
2037
  const depCheck = checkPlaywrightDependencies(projectPath);
1854
2038
  if (!depCheck.playwrightInstalled) {
1855
- console.log(` ${styles.brightRed}${icons.error} Playwright not installed${styles.reset}`);
2039
+ console.log(` ${styles.brightYellow}${icons.warning} Playwright not installed${styles.reset}`);
1856
2040
  console.log('');
1857
- console.log(` ${styles.bold}Install commands:${styles.reset}`);
1858
- depCheck.installCommands.forEach(cmd => {
1859
- console.log(` ${styles.brightCyan}${cmd}${styles.reset}`);
1860
- });
2041
+ // Try to install automatically
2042
+ console.log(` ${styles.brightCyan}${icons.info} Attempting automatic installation...${styles.reset}`);
2043
+ const installResult = await installPlaywrightDependencies(projectPath);
2044
+ if (!installResult.success) {
2045
+ console.log(` ${styles.brightRed}${icons.error} Auto-installation failed: ${installResult.error}${styles.reset}`);
2046
+ console.log('');
2047
+ console.log(` ${styles.bold}Manual install commands:${styles.reset}`);
2048
+ depCheck.installCommands.forEach(cmd => {
2049
+ console.log(` ${styles.brightCyan}${cmd}${styles.reset}`);
2050
+ });
2051
+ console.log('');
2052
+ (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright not installed');
2053
+ }
2054
+ // Re-check after installation
2055
+ const newDepCheck = checkPlaywrightDependencies(projectPath);
2056
+ if (!newDepCheck.playwrightInstalled) {
2057
+ console.log(` ${styles.brightRed}${icons.error} Installation verification failed${styles.reset}`);
2058
+ (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright installation failed');
2059
+ }
2060
+ console.log(` ${styles.brightGreen}${icons.success} Playwright installed successfully${styles.reset}`);
1861
2061
  console.log('');
1862
- (0, exit_codes_1.exitWith)(exit_codes_1.ExitCode.SYSTEM_ERROR, 'Playwright not installed');
1863
2062
  }
1864
2063
  if (!depCheck.browsersInstalled) {
1865
2064
  console.log(` ${styles.brightYellow}${icons.warning} Playwright browsers not installed${styles.reset}`);
1866
2065
  console.log('');
1867
- console.log(` ${styles.bold}Install command:${styles.reset}`);
1868
- console.log(` ${styles.brightCyan}npx playwright install${styles.reset}`);
1869
- console.log('');
1870
- process.exit(2);
2066
+ // Try to install browsers only
2067
+ console.log(` ${styles.brightCyan}${icons.info} Installing browsers...${styles.reset}`);
2068
+ try {
2069
+ const { spawn } = require('child_process');
2070
+ await new Promise((resolve, reject) => {
2071
+ const browserInstall = spawn('npx', ['playwright', 'install'], {
2072
+ cwd: projectPath,
2073
+ stdio: 'pipe'
2074
+ });
2075
+ browserInstall.on('close', (code) => {
2076
+ if (code === 0) {
2077
+ console.log(` ${styles.brightGreen}${icons.success} Browsers installed successfully${styles.reset}`);
2078
+ resolve();
2079
+ }
2080
+ else {
2081
+ reject(new Error('browser install failed'));
2082
+ }
2083
+ });
2084
+ browserInstall.on('error', reject);
2085
+ });
2086
+ }
2087
+ catch (error) {
2088
+ console.log(` ${styles.brightRed}${icons.error} Browser installation failed: ${error.message}${styles.reset}`);
2089
+ console.log(` ${styles.brightCyan}npx playwright install${styles.reset}`);
2090
+ console.log('');
2091
+ process.exit(2);
2092
+ }
1871
2093
  }
1872
2094
  console.log(` ${styles.brightGreen}${icons.success}${styles.reset} Playwright installed`);
1873
2095
  console.log(` ${styles.brightGreen}${icons.success}${styles.reset} Browsers available`);
@@ -2674,23 +2896,64 @@ async function runScanEnterprise(projectPath, options) {
2674
2896
  since: options.since,
2675
2897
  baseline: options.baseline,
2676
2898
  });
2899
+ // Adapter functions for baseline management
2900
+ const secretToBaselineFinding = (secret) => ({
2901
+ type: secret.type,
2902
+ category: 'secret',
2903
+ title: secret.type,
2904
+ file: secret.file,
2905
+ line: secret.line,
2906
+ match: secret.match,
2907
+ snippet: secret.match,
2908
+ });
2909
+ const vulnToBaselineFinding = (vuln) => ({
2910
+ type: 'vulnerability',
2911
+ category: vuln.severity,
2912
+ title: vuln.title || vuln.cve,
2913
+ file: vuln.path || 'package.json',
2914
+ line: 1,
2915
+ match: vuln.cve,
2916
+ snippet: `${vuln.package}@${vuln.version}`,
2917
+ });
2918
+ const baselineToSecretFinding = (finding) => ({
2919
+ type: finding.type || 'unknown',
2920
+ file: finding.file,
2921
+ line: finding.line,
2922
+ risk: 'medium', // Default risk
2923
+ confidence: 0.8,
2924
+ entropy: 0,
2925
+ match: finding.match || '',
2926
+ isTest: false,
2927
+ recommendation: 'Review and remediate',
2928
+ });
2929
+ const baselineToVulnFinding = (finding) => ({
2930
+ package: finding.snippet?.split('@')[0] || 'unknown',
2931
+ version: finding.snippet?.split('@')[1] || 'unknown',
2932
+ severity: finding.category || 'medium',
2933
+ cve: finding.match || 'unknown',
2934
+ title: finding.title,
2935
+ fixedIn: 'unknown',
2936
+ path: finding.file,
2937
+ });
2677
2938
  if (options.baseline) {
2678
2939
  if (results.secrets) {
2679
- const { filtered, suppressed } = BaselineManager.filterFindings(results.secrets.findings, options.baseline);
2680
- results.secrets.findings = filtered;
2940
+ const secretFindings = results.secrets.findings.map(secretToBaselineFinding);
2941
+ const { filtered, suppressed } = BaselineManager.filterFindings(secretFindings, options.baseline);
2942
+ results.secrets.findings = filtered.map(baselineToSecretFinding);
2681
2943
  results.secrets.summary.total = filtered.length;
2682
2944
  results.secrets.suppressedByBaseline = suppressed;
2683
2945
  }
2684
2946
  if (results.vulnerabilities) {
2685
- const { filtered, suppressed } = BaselineManager.filterFindings(results.vulnerabilities.findings, options.baseline);
2686
- results.vulnerabilities.findings = filtered;
2947
+ const vulnFindings = results.vulnerabilities.findings.map(vulnToBaselineFinding);
2948
+ const { filtered, suppressed } = BaselineManager.filterFindings(vulnFindings, options.baseline);
2949
+ results.vulnerabilities.findings = filtered.map(baselineToVulnFinding);
2687
2950
  const summary = {
2688
2951
  critical: filtered.filter((f) => f.severity === 'critical').length,
2689
2952
  high: filtered.filter((f) => f.severity === 'high').length,
2690
2953
  medium: filtered.filter((f) => f.severity === 'medium').length,
2691
2954
  low: filtered.filter((f) => f.severity === 'low').length,
2692
2955
  };
2693
- results.vulnerabilities.summary = summary;
2956
+ results.vulnerabilities.summary = { ...results.vulnerabilities.summary, ...summary };
2694
2957
  results.vulnerabilities.suppressedByBaseline = suppressed;
2695
2958
  }
2696
2959
  }
@@ -2893,9 +3156,15 @@ async function initProject(projectPath, options) {
2893
3156
  if (!validation.success) {
2894
3157
  s3.stop(false, 'Configuration validation failed');
2895
3158
  console.log(` ${styles.brightRed}${icons.error}${styles.reset} Config validation errors:`);
2896
- validation.error.errors.forEach(err => {
2897
- console.log(` ${styles.dim}${err.path.join('.')}:${styles.reset} ${err.message}`);
2898
- });
3159
+ const validationError = validation;
3160
+ if (validationError.error && Array.isArray(validationError.error.errors)) {
3161
+ validationError.error.errors.forEach((err) => {
3162
+ console.log(` ${styles.dim}${err.path?.join('.') || 'field'}:${styles.reset} ${err.message}`);
3163
+ });
3164
+ }
3165
+ else {
3166
+ console.log(` ${styles.dim}Unknown validation error${styles.reset}`);
3167
+ }
2899
3168
  return;
2900
3169
  }
2901
3170
  // Atomic write
@@ -3162,6 +3431,54 @@ function outputComplianceResults(results, options) {
3162
3431
  }
3163
3432
  console.log(` ${styles.dim}Run${styles.reset} ${styles.bold}guardrail scan:compliance --framework gdpr${styles.reset} ${styles.dim}for other frameworks.${styles.reset}\n`);
3164
3433
  }
3434
+ /**
3435
+ * Install Playwright dependencies automatically
3436
+ */
3437
+ async function installPlaywrightDependencies(projectPath) {
3438
+ const { spawn } = require('child_process');
3439
+ try {
3440
+ console.log(` ${styles.brightCyan}${icons.info} Installing Playwright...${styles.reset}`);
3441
+ // Install @playwright/test
3442
+ await new Promise((resolve, reject) => {
3443
+ const npmInstall = spawn('npm', ['install', '-D', '@playwright/test'], {
3444
+ cwd: projectPath,
3445
+ stdio: 'pipe'
3446
+ });
3447
+ npmInstall.on('close', (code) => {
3448
+ if (code === 0) {
3449
+ console.log(` ${styles.brightGreen}${icons.success} Playwright package installed${styles.reset}`);
3450
+ resolve();
3451
+ }
3452
+ else {
3453
+ reject(new Error('npm install failed'));
3454
+ }
3455
+ });
3456
+ npmInstall.on('error', reject);
3457
+ });
3458
+ // Install browsers
3459
+ console.log(` ${styles.brightCyan}${icons.info} Installing Playwright browsers...${styles.reset}`);
3460
+ await new Promise((resolve, reject) => {
3461
+ const browserInstall = spawn('npx', ['playwright', 'install'], {
3462
+ cwd: projectPath,
3463
+ stdio: 'pipe'
3464
+ });
3465
+ browserInstall.on('close', (code) => {
3466
+ if (code === 0) {
3467
+ console.log(` ${styles.brightGreen}${icons.success} Playwright browsers installed${styles.reset}`);
3468
+ resolve();
3469
+ }
3470
+ else {
3471
+ reject(new Error('browser install failed'));
3472
+ }
3473
+ });
3474
+ browserInstall.on('error', reject);
3475
+ });
3476
+ return { success: true };
3477
+ }
3478
+ catch (error) {
3479
+ return { success: false, error: error.message };
3480
+ }
3481
+ }
3165
3482
  async function runInteractiveMenu() {
3166
3483
  const cfg = loadConfig();
3167
3484
  while (true) {
@@ -3172,6 +3489,9 @@ async function runInteractiveMenu() {
3172
3489
  { name: `${styles.brightGreen}${icons.scan}${styles.reset} Vulnerability Scan ${styles.dim}Check dependencies for CVEs${styles.reset}`, value: 'scan_vulns' },
3173
3490
  { name: `${styles.brightYellow}${icons.compliance}${styles.reset} Compliance Scan ${proBadge} ${styles.dim}SOC2/GDPR/HIPAA${styles.reset}`, value: 'scan_compliance' },
3174
3491
  { name: `${styles.brightBlue}${icons.sbom}${styles.reset} Generate SBOM ${proBadge} ${styles.dim}Software bill of materials${styles.reset}`, value: 'sbom' },
3492
+ { name: `${styles.brightMagenta}${icons.reality}${styles.reset} Reality Mode ${styles.dim}Browser testing & auth flows${styles.reset}`, value: 'reality' },
3493
+ { name: `${styles.brightGreen}${icons.ship}${styles.reset} Ship Check ${styles.dim}Pre-deployment validation${styles.reset}`, value: 'ship' },
3494
+ { name: `${styles.cyan}${icons.autopilot}${styles.reset} Initialize Project ${styles.dim}Setup Guardrail configuration${styles.reset}`, value: 'init' },
3175
3495
  { name: `${styles.brightMagenta}${icons.auth}${styles.reset} Auth / Status ${styles.dim}Login, logout, view status${styles.reset}`, value: 'auth' },
3176
3496
  { name: `${styles.dim}${icons.error} Exit${styles.reset}`, value: 'exit' },
3177
3497
  ]);
@@ -3319,6 +3639,127 @@ async function runInteractiveMenu() {
3319
3639
  console.log(`${c.success('✓')} SBOM written to ${output}\n`);
3320
3640
  continue;
3321
3641
  }
3642
+ if (action === 'reality') {
3643
+ requireAuth('starter');
3644
+ const url = await promptInput('Base URL of running app', 'http://localhost:3000');
3645
+ const flow = await promptSelect('Flow to test', [
3646
+ { name: 'Authentication Flow', value: 'auth' },
3647
+ { name: 'Checkout Flow', value: 'checkout' },
3648
+ { name: 'Dashboard Flow', value: 'dashboard' },
3649
+ ]);
3650
+ const mode = await promptSelect('Mode', [
3651
+ { name: 'Generate test only', value: 'generate' },
3652
+ { name: 'Generate and run', value: 'run' },
3653
+ { name: 'Record user actions', value: 'record' },
3654
+ ]);
3655
+ console.log(`\n${c.dim('Command:')} ${c.bold(`guardrail reality --url "${url}" --flow ${flow}${mode === 'run' ? ' --run' : mode === 'record' ? ' --record' : ''}`)}\n`);
3656
+ printLogo();
3657
+ console.log(`\n${c.bold('🌐 REALITY MODE')}\n`);
3658
+ // Check dependencies and install if needed
3659
+ const { checkPlaywrightDependencies } = require('./reality/reality-runner');
3660
+ const depCheck = checkPlaywrightDependencies(projectPath);
3661
+ if (!depCheck.playwrightInstalled || !depCheck.browsersInstalled) {
3662
+ console.log(` ${styles.brightYellow}${icons.warning} Playwright dependencies missing${styles.reset}`);
3663
+ console.log('');
3664
+ const shouldInstall = await promptConfirm('Install Playwright dependencies automatically?', true);
3665
+ if (shouldInstall) {
3666
+ const installResult = await installPlaywrightDependencies(projectPath);
3667
+ if (!installResult.success) {
3668
+ console.log(` ${styles.brightRed}${icons.error} Failed to install: ${installResult.error}${styles.reset}`);
3669
+ console.log('');
3670
+ console.log(` ${styles.bold}Manual install commands:${styles.reset}`);
3671
+ depCheck.installCommands.forEach(cmd => {
3672
+ console.log(` ${styles.brightCyan}${cmd}${styles.reset}`);
3673
+ });
3674
+ console.log('');
3675
+ continue;
3676
+ }
3677
+ }
3678
+ else {
3679
+ console.log(` ${styles.dim}Installation skipped. Run manually when ready.${styles.reset}`);
3680
+ console.log('');
3681
+ continue;
3682
+ }
3683
+ }
3684
+ // Execute reality mode based on selection
3685
+ const { spawn } = require('child_process');
3686
+ const args = ['reality', '--url', url, '--flow', flow];
3687
+ if (mode === 'run')
3688
+ args.push('--run');
3689
+ if (mode === 'record')
3690
+ args.push('--record');
3691
+ const realityProc = spawn('guardrail', args, {
3692
+ stdio: 'inherit',
3693
+ shell: process.platform === 'win32',
3694
+ cwd: projectPath
3695
+ });
3696
+ realityProc.on('close', (code) => {
3697
+ if (code === 0) {
3698
+ console.log(`\n ${styles.brightGreen}${icons.success} Reality mode completed${styles.reset}`);
3699
+ }
3700
+ else {
3701
+ console.log(`\n ${styles.brightRed}${icons.error} Reality mode failed${styles.reset}`);
3702
+ }
3703
+ });
3704
+ continue;
3705
+ }
3706
+ if (action === 'ship') {
3707
+ requireAuth();
3708
+ const baseline = await promptConfirm('Use baseline file?', false);
3709
+ const output = await promptConfirm('Generate ship report?', true);
3710
+ const outputPath = output ? defaultReportPath(projectPath, 'ship', 'json') : undefined;
3711
+ console.log(`\n${c.dim('Command:')} ${c.bold(`guardrail ship -p "${projectPath}"${baseline ? ' --baseline .guardrail/baseline.json' : ''}${outputPath ? ` --output "${outputPath}"` : ''}`)}\n`);
3712
+ printLogo();
3713
+ console.log(`\n${c.bold('🚀 SHIP CHECK')}\n`);
3714
+ // Import ship functionality
3715
+ const { runShipCheck } = require('guardrail-ship');
3716
+ try {
3717
+ const shipResult = await runShipCheck(projectPath, {
3718
+ baseline: baseline ? '.guardrail/baseline.json' : undefined,
3719
+ output: outputPath
3720
+ });
3721
+ if (shipResult.verdict === 'ship') {
3722
+ console.log(` ${styles.brightGreen}${icons.success} Ready to ship!${styles.reset}`);
3723
+ }
3724
+ else {
3725
+ console.log(` ${styles.brightYellow}${icons.warning} Issues need to be addressed before shipping${styles.reset}`);
3726
+ }
3727
+ if (outputPath) {
3728
+ console.log(` ${styles.dim}Report saved to ${outputPath}${styles.reset}`);
3729
+ }
3730
+ }
3731
+ catch (error) {
3732
+ console.log(` ${styles.brightRed}${icons.error} Ship check failed: ${error.message}${styles.reset}`);
3733
+ }
3734
+ console.log('');
3735
+ continue;
3736
+ }
3737
+ if (action === 'init') {
3738
+ const template = await promptSelect('Configuration template', [
3739
+ { name: 'Startup - Fast, minimal setup', value: 'startup' },
3740
+ { name: 'Enterprise - Strict, compliant', value: 'enterprise' },
3741
+ { name: 'OSS - Supply chain focus', value: 'oss' },
3742
+ ]);
3743
+ const setupCI = await promptConfirm('Setup CI/CD integration?', false);
3744
+ const setupHooks = await promptConfirm('Install git hooks?', false);
3745
+ console.log(`\n${c.dim('Command:')} ${c.bold(`guardrail init -p "${projectPath}" --template ${template}${setupCI ? ' --ci' : ''}${setupHooks ? ' --hooks' : ''}`)}\n`);
3746
+ printLogo();
3747
+ console.log(`\n${c.bold('🔧 INITIALIZING PROJECT')}\n`);
3748
+ try {
3749
+ await initProject(projectPath, {
3750
+ template,
3751
+ ci: setupCI,
3752
+ hooks: setupHooks,
3753
+ interactive: true
3754
+ });
3755
+ console.log(` ${styles.brightGreen}${icons.success} Project initialized successfully${styles.reset}`);
3756
+ }
3757
+ catch (error) {
3758
+ console.log(` ${styles.brightRed}${icons.error} Initialization failed: ${error.message}${styles.reset}`);
3759
+ }
3760
+ console.log('');
3761
+ continue;
3762
+ }
3322
3763
  }
3323
3764
  }
3324
3765
  // Register cache management commands