sickbay 1.16.2 → 1.16.3

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.
@@ -765,7 +765,7 @@ function TuiApp({ projectPath, checks, watchEnabled, refreshInterval, quotes, an
765
765
  return () => timers.forEach(clearTimeout);
766
766
  }, []);
767
767
  useEffect(() => {
768
- checkForUpdate("1.16.1").then((info) => {
768
+ checkForUpdate("1.16.2").then((info) => {
769
769
  if (info) setUpdateInfo(info);
770
770
  });
771
771
  }, []);
@@ -883,7 +883,7 @@ function TuiApp({ projectPath, checks, watchEnabled, refreshInterval, quotes, an
883
883
  const webReport = monorepoReportRef.current ?? reportRef.current;
884
884
  if (!webReport) return;
885
885
  try {
886
- const { serveWeb } = await import("./web-D5etye5Q.js");
886
+ const { serveWeb } = await import("./web-CFBLfBdc.js");
887
887
  const { default: openBrowser } = await import("open");
888
888
  let aiService;
889
889
  if (withAI && process.env.ANTHROPIC_API_KEY && !monorepoReportRef.current) {
@@ -1039,4 +1039,4 @@ function TuiApp({ projectPath, checks, watchEnabled, refreshInterval, quotes, an
1039
1039
  //#endregion
1040
1040
  export { TuiApp };
1041
1041
 
1042
- //# sourceMappingURL=TuiApp-D4Wn81Qv.js.map
1042
+ //# sourceMappingURL=TuiApp-LHb149v8.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TuiApp-D4Wn81Qv.js","names":["scoreColor","scoreBar","scoreBar","scoreColor"],"sources":["../src/components/tui/ActivityPanel.tsx","../src/components/tui/hooks/useGitStatus.ts","../src/components/tui/GitPanel.tsx","../src/components/tui/HealthPanel.tsx","../src/components/tui/HelpPanel.tsx","../src/components/tui/hooks/useFileWatcher.ts","../src/components/tui/hooks/useSickbayRunner.ts","../src/components/tui/hooks/useTerminalSize.ts","../src/components/tui/HotkeyBar.tsx","../src/components/tui/MonorepoPanel.tsx","../src/components/tui/PanelBorder.tsx","../src/components/tui/QuickWinsPanel.tsx","../src/components/tui/ScorePanel.tsx","../src/components/tui/TrendPanel.tsx","../src/components/tui/TuiApp.tsx"],"sourcesContent":["import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nexport interface ActivityEntry {\n timestamp: Date;\n type: 'scan-start' | 'scan-complete' | 'file-change' | 'git-change' | 'regression' | 'info';\n message: string;\n}\n\ninterface ActivityPanelProps {\n entries: ActivityEntry[];\n availableHeight: number;\n}\n\nconst TYPE_COLOR: Record<ActivityEntry['type'], string> = {\n 'scan-start': 'cyan',\n 'scan-complete': 'green',\n 'file-change': 'yellow',\n 'git-change': 'magenta',\n regression: 'red',\n info: 'gray',\n};\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString('en-US', { hour12: false });\n}\n\nexport function ActivityPanel({ entries, availableHeight }: ActivityPanelProps) {\n if (entries.length === 0) {\n return (\n <Box>\n <Text dimColor>No activity yet.</Text>\n </Box>\n );\n }\n\n const visible = entries.slice(-Math.max(1, availableHeight));\n\n return (\n <Box flexDirection=\"column\">\n {visible.map((entry) => (\n <Box key={`${entry.timestamp.getTime()}-${entry.type}`}>\n <Text dimColor>\n {formatTime(entry.timestamp)}\n {' '}\n </Text>\n <Text color={TYPE_COLOR[entry.type] || 'white'}>{entry.message}</Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import { execFile } from 'child_process';\nimport { promisify } from 'util';\n\nimport { useState, useEffect, useRef } from 'react';\n\nconst exec = promisify(execFile);\n\nexport interface GitStatus {\n branch: string;\n modified: number;\n staged: number;\n untracked: number;\n ahead: number;\n behind: number;\n stashes: number;\n lastCommit: string;\n lastCommitTime: string;\n}\n\nasync function git(args: string[], cwd: string): Promise<string> {\n try {\n const { stdout } = await exec('git', args, { cwd, timeout: 5000 });\n return stdout.trim();\n } catch {\n return '';\n }\n}\n\nasync function fetchGitStatus(projectPath: string): Promise<GitStatus> {\n const [branch, porcelain, revList, stashList, logLine] = await Promise.all([\n git(['branch', '--show-current'], projectPath),\n git(['status', '--porcelain'], projectPath),\n git(['rev-list', '--left-right', '--count', 'HEAD...@{upstream}'], projectPath),\n git(['stash', 'list'], projectPath),\n git(['log', '-1', '--format=%s|%cr'], projectPath),\n ]);\n\n const lines = porcelain ? porcelain.split('\\n') : [];\n const staged = lines.filter((l) => /^[MADRC]/.test(l)).length;\n const modified = lines.filter((l) => /^.[MD]/.test(l)).length;\n const untracked = lines.filter((l) => l.startsWith('??')).length;\n\n let ahead = 0;\n let behind = 0;\n if (revList) {\n const parts = revList.split(/\\s+/);\n ahead = parseInt(parts[0], 10) || 0;\n behind = parseInt(parts[1], 10) || 0;\n }\n\n const stashes = stashList ? stashList.split('\\n').filter(Boolean).length : 0;\n const [lastCommit, lastCommitTime] = logLine.split('|');\n\n return {\n branch: branch || 'unknown',\n modified,\n staged,\n untracked,\n ahead,\n behind,\n stashes,\n lastCommit: lastCommit || '',\n lastCommitTime: lastCommitTime || '',\n };\n}\n\nexport function useGitStatus(projectPath: string, pollInterval = 10000) {\n const [status, setStatus] = useState<GitStatus | null>(null);\n const intervalRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n useEffect(() => {\n let mounted = true;\n\n fetchGitStatus(projectPath).then((s) => {\n if (mounted) setStatus(s);\n });\n\n intervalRef.current = setInterval(() => {\n fetchGitStatus(projectPath).then((s) => {\n if (mounted) setStatus(s);\n });\n }, pollInterval);\n\n return () => {\n mounted = false;\n if (intervalRef.current) clearInterval(intervalRef.current);\n };\n }, [projectPath, pollInterval]);\n\n return status;\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { useGitStatus } from './hooks/useGitStatus.js';\n\ninterface GitPanelProps {\n projectPath: string;\n availableWidth?: number;\n}\n\nexport function GitPanel({ projectPath, availableWidth = 30 }: GitPanelProps) {\n const status = useGitStatus(projectPath);\n\n if (!status) {\n return (\n <Box>\n <Text dimColor>Loading git info...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text dimColor>Branch: </Text>\n <Text bold color=\"green\">\n {status.branch}\n </Text>\n </Box>\n {(status.ahead > 0 || status.behind > 0) && (\n <Box>\n {status.ahead > 0 && (\n <Text color=\"green\">\n {'\\u2191'}\n {status.ahead}{' '}\n </Text>\n )}\n {status.behind > 0 && (\n <Text color=\"red\">\n {'\\u2193'}\n {status.behind}\n </Text>\n )}\n </Box>\n )}\n <Box>\n <Text dimColor>Modified: </Text>\n <Text color={status.modified > 0 ? 'yellow' : 'green'}>{status.modified}</Text>\n </Box>\n <Box>\n <Text dimColor>Staged: </Text>\n <Text color={status.staged > 0 ? 'cyan' : 'green'}>{status.staged}</Text>\n </Box>\n <Box>\n <Text dimColor>Untracked: </Text>\n <Text>{status.untracked}</Text>\n </Box>\n {status.stashes > 0 && (\n <Box>\n <Text dimColor>Stashes: </Text>\n <Text>{status.stashes}</Text>\n </Box>\n )}\n {status.lastCommit && (\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>Last commit:</Text>\n <Text>\n {status.lastCommit.length > availableWidth\n ? status.lastCommit.slice(0, availableWidth - 3) + '...'\n : status.lastCommit}\n </Text>\n <Text dimColor>{status.lastCommitTime}</Text>\n </Box>\n )}\n </Box>\n );\n}\n","import React, { useState, useEffect } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { LOADING_MESSAGES } from '../../lib/messages.js';\n\nimport type { CheckResult } from 'sickbay-core';\n\ninterface HealthPanelProps {\n checks: CheckResult[];\n isScanning: boolean;\n progress: Array<{ name: string; status: string }>;\n scrollOffset: number;\n availableHeight: number;\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nfunction scoreBar(score: number, width = 10): string {\n const filled = Math.round((score / 100) * width);\n return '\\u2588'.repeat(filled) + '\\u2591'.repeat(width - filled);\n}\n\nfunction truncate(str: string, maxLen: number): string {\n return str.length > maxLen ? str.slice(0, maxLen - 1) + '\\u2026' : str;\n}\n\nfunction statusIcon(status: string): string {\n switch (status) {\n case 'pass':\n return '\\u2713';\n case 'fail':\n return '\\u2717';\n case 'warning':\n return '\\u26A0';\n case 'skipped':\n return '\\u25CB';\n default:\n return '?';\n }\n}\n\nexport function HealthPanel({\n checks,\n isScanning,\n progress,\n scrollOffset,\n availableHeight,\n}: HealthPanelProps) {\n const [msgIdx, setMsgIdx] = useState(0);\n\n useEffect(() => {\n if (!isScanning || progress.length > 0) return;\n const id = setInterval(() => {\n setMsgIdx((i) => (i + 1) % LOADING_MESSAGES.length);\n }, 4000);\n return () => clearInterval(id);\n }, [isScanning, progress.length]);\n\n if (isScanning && checks.length === 0) {\n if (progress.length === 0) {\n return (\n <Box>\n <Text dimColor>{LOADING_MESSAGES[msgIdx]}</Text>\n </Box>\n );\n }\n return (\n <Box flexDirection=\"column\">\n {progress.map((p) => (\n <Box key={p.name}>\n <Text\n color={p.status === 'done' ? 'green' : p.status === 'running' ? 'yellow' : 'gray'}\n >\n {p.status === 'done' ? '\\u2713' : p.status === 'running' ? '\\u25CC' : '\\u25CB'}\n </Text>\n <Text> {p.name}</Text>\n </Box>\n ))}\n </Box>\n );\n }\n\n if (checks.length === 0) {\n return (\n <Box>\n <Text dimColor>No results yet. Press [r] to scan.</Text>\n </Box>\n );\n }\n\n const maxVisible = Math.max(1, availableHeight);\n const visible = checks.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = scrollOffset + maxVisible < checks.length;\n const hasLess = scrollOffset > 0;\n\n return (\n <Box flexDirection=\"column\">\n {hasLess && <Text dimColor> \\u25B2 {scrollOffset} more above</Text>}\n {visible.map((check) => (\n <Box key={check.id}>\n <Text\n color={check.status === 'pass' ? 'green' : check.status === 'fail' ? 'red' : 'yellow'}\n >\n {statusIcon(check.status)}\n </Text>\n <Text> </Text>\n <Box width={26}>\n <Text wrap=\"truncate\">{truncate(check.name, 26)}</Text>\n </Box>\n <Text> </Text>\n <Text color={scoreColor(check.score)}>{scoreBar(check.score)}</Text>\n <Text color={scoreColor(check.score)}> {String(check.score).padStart(3)}</Text>\n </Box>\n ))}\n {hasMore && (\n <Text dimColor>\n {' \\u25BC '}\n {checks.length - scrollOffset - maxVisible} more below\n </Text>\n )}\n {isScanning && <Text color=\"yellow\">{' \\u25CC Re-scanning...'}</Text>}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\ninterface HelpRow {\n keys: string[];\n description: string;\n}\n\ninterface HelpSection {\n title: string;\n rows: HelpRow[];\n}\n\nconst SECTIONS: HelpSection[] = [\n {\n title: 'PANELS',\n rows: [\n { keys: ['h'], description: 'Focus / unfocus Health Checks' },\n { keys: ['g'], description: 'Focus / unfocus Git Status' },\n { keys: ['t'], description: 'Focus / unfocus Trend' },\n { keys: ['q'], description: 'Focus / unfocus Quick Wins' },\n { keys: ['a'], description: 'Focus / unfocus Activity' },\n { keys: ['f'], description: 'Expand focused panel to full screen' },\n { keys: ['Esc'], description: 'Unfocus panel / exit full screen' },\n ],\n },\n {\n title: 'SCROLLING',\n rows: [{ keys: ['↑', '↓'], description: 'Scroll Health Checks (when focused)' }],\n },\n {\n title: 'ACTIONS',\n rows: [\n { keys: ['r'], description: 'Re-run scan' },\n { keys: ['w'], description: 'Open web dashboard in browser' },\n { keys: ['W'], description: 'Open web dashboard with AI insights' },\n { keys: ['?'], description: 'Toggle this help screen' },\n ],\n },\n];\n\nexport function HelpPanel() {\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1}>\n <Box marginBottom={1}>\n <Text bold color=\"cyan\">\n SICKBAY TUI — KEYBOARD SHORTCUTS\n </Text>\n </Box>\n\n {SECTIONS.map((section) => (\n <Box key={section.title} flexDirection=\"column\" marginBottom={1}>\n <Box marginBottom={0}>\n <Text bold color=\"white\">\n {section.title}\n </Text>\n </Box>\n {section.rows.map((row) => (\n <Box key={row.description} marginLeft={2}>\n <Box width={16}>\n {row.keys.map((k, i) => (\n <Text key={k}>\n <Text color=\"cyan\" bold>\n [{k}]\n </Text>\n {i < row.keys.length - 1 ? <Text dimColor> / </Text> : null}\n </Text>\n ))}\n </Box>\n <Text dimColor>{row.description}</Text>\n </Box>\n ))}\n </Box>\n ))}\n\n <Box marginTop={1} borderStyle=\"single\" borderColor=\"gray\" paddingX={1}>\n <Text dimColor>Pro tip: focus a panel with its hotkey, then press </Text>\n <Text color=\"cyan\" bold>\n [f]\n </Text>\n <Text dimColor> to expand it full screen for more detail.</Text>\n </Box>\n\n <Box marginTop={1}>\n <Text dimColor>Press </Text>\n <Text color=\"cyan\" bold>\n [?]\n </Text>\n <Text dimColor> or </Text>\n <Text color=\"cyan\" bold>\n [Esc]\n </Text>\n <Text dimColor> to close</Text>\n </Box>\n </Box>\n );\n}\n","import { useState, useEffect, useRef } from 'react';\n\nimport type { FSWatcher } from 'chokidar';\n\ninterface UseFileWatcherOptions {\n projectPath: string;\n enabled: boolean;\n debounceMs?: number;\n onFilesChanged?: (files: string[]) => void;\n}\n\nexport function useFileWatcher({\n projectPath,\n enabled,\n debounceMs = 2000,\n onFilesChanged,\n}: UseFileWatcherOptions) {\n const [changedFiles, setChangedFiles] = useState<string[]>([]);\n const debounceRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const pendingRef = useRef<string[]>([]);\n const watcherRef = useRef<FSWatcher | null>(null);\n // Keep a ref so the debounce callback always calls the latest version,\n // even if the parent re-renders and passes a new function reference.\n const onFilesChangedRef = useRef(onFilesChanged);\n onFilesChangedRef.current = onFilesChanged;\n\n useEffect(() => {\n if (!enabled) return;\n\n let mounted = true;\n\n (async () => {\n try {\n const { watch } = await import('chokidar');\n if (!mounted) return;\n\n const watcher = watch(['**/*.{ts,tsx,js,jsx,json}'], {\n cwd: projectPath,\n ignored: [\n '**/node_modules/**',\n '**/dist/**',\n '**/.git/**',\n '**/build/**',\n '**/.next/**',\n '**/.turbo/**',\n '**/coverage/**',\n ],\n ignoreInitial: true,\n persistent: true,\n });\n\n watcherRef.current = watcher;\n\n watcher.on('change', (path: string) => {\n pendingRef.current.push(path);\n setChangedFiles((prev) => [...prev, path]);\n\n if (debounceRef.current) clearTimeout(debounceRef.current);\n debounceRef.current = setTimeout(() => {\n const files = [...pendingRef.current];\n pendingRef.current = [];\n onFilesChangedRef.current?.(files);\n }, debounceMs);\n });\n } catch {\n // chokidar not available — silently skip\n }\n })();\n\n return () => {\n mounted = false;\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (watcherRef.current) watcherRef.current.close();\n };\n }, [projectPath, enabled, debounceMs]);\n\n return changedFiles;\n}\n","import { useState, useCallback, useRef } from 'react';\n\nimport { runSickbay, runSickbayMonorepo, detectMonorepo, buildSummary } from 'sickbay-core';\n\nimport type { SickbayReport, MonorepoReport, CheckResult } from 'sickbay-core';\n\ninterface ProgressItem {\n name: string;\n status: 'pending' | 'running' | 'done';\n}\n\ninterface UseSickbayRunnerOptions {\n projectPath: string;\n checks?: string[];\n quotes?: boolean;\n}\n\n/**\n * Synthesize a rolled-up SickbayReport from a MonorepoReport for use in\n * TUI panels that only understand single-project reports. Strategy: merge\n * all checks from all packages, keeping the worst score per check name.\n */\nfunction rollUpMonorepoReport(monorepo: MonorepoReport): SickbayReport {\n const byId = new Map<string, CheckResult>();\n for (const pkg of monorepo.packages) {\n for (const check of pkg.checks) {\n const existing = byId.get(check.id);\n if (!existing || check.score < existing.score) {\n byId.set(check.id, check);\n }\n }\n }\n const checks = Array.from(byId.values());\n return {\n timestamp: monorepo.timestamp,\n projectPath: monorepo.rootPath,\n projectInfo: {\n name: `monorepo (${monorepo.packages.length} packages)`,\n version: '0.0.0',\n hasTypeScript: false,\n hasESLint: false,\n hasPrettier: false,\n framework: 'node',\n packageManager: monorepo.packageManager,\n totalDependencies: 0,\n dependencies: {},\n devDependencies: {},\n },\n checks,\n overallScore: monorepo.overallScore,\n summary: buildSummary(checks),\n };\n}\n\nexport function useSickbayRunner({ projectPath, checks, quotes }: UseSickbayRunnerOptions) {\n const [report, setReport] = useState<SickbayReport | null>(null);\n const [monorepoReport, setMonorepoReport] = useState<MonorepoReport | null>(null);\n const [isScanning, setIsScanning] = useState(false);\n const [progress, setProgress] = useState<ProgressItem[]>([]);\n const [error, setError] = useState<string | null>(null);\n const scanningRef = useRef(false);\n\n const scan = useCallback(async () => {\n if (scanningRef.current) return null;\n scanningRef.current = true;\n setIsScanning(true);\n setError(null);\n\n try {\n const monorepoInfo = await detectMonorepo(projectPath);\n\n if (monorepoInfo.isMonorepo) {\n const result = await runSickbayMonorepo({\n projectPath,\n checks,\n quotes,\n onPackageStart: (name) => {\n setProgress((prev) => [...prev, { name, status: 'running' }]);\n },\n onPackageComplete: (pkg) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === pkg.name ? { ...p, status: 'done' } : p)),\n );\n },\n });\n\n setMonorepoReport(result);\n const rolledUp = rollUpMonorepoReport(result);\n setReport(rolledUp);\n\n setIsScanning(false);\n scanningRef.current = false;\n return rolledUp;\n }\n\n const result = await runSickbay({\n projectPath,\n checks,\n quotes,\n onRunnersReady: (names) => {\n setProgress(names.map((name) => ({ name, status: 'pending' as const })));\n },\n onCheckStart: (name) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === name ? { ...p, status: 'running' } : p)),\n );\n },\n onCheckComplete: (check) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === check.id ? { ...p, status: 'done' } : p)),\n );\n },\n });\n\n setMonorepoReport(null);\n setReport(result);\n\n // Save to trend history\n try {\n const { saveEntry } = await import('../../../lib/history.js');\n saveEntry(result);\n } catch {\n // Non-critical\n }\n\n setIsScanning(false);\n scanningRef.current = false;\n return result;\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n setIsScanning(false);\n scanningRef.current = false;\n return null;\n }\n }, [projectPath, checks, quotes]);\n\n return { report, monorepoReport, isScanning, progress, error, scan };\n}\n","import { useState, useEffect } from 'react';\n\nexport function useTerminalSize() {\n const [size, setSize] = useState({\n columns: process.stdout.columns || 80,\n rows: process.stdout.rows || 24,\n });\n\n useEffect(() => {\n const handler = () => {\n setSize({\n columns: process.stdout.columns || 80,\n rows: process.stdout.rows || 24,\n });\n };\n process.stdout.on('resize', handler);\n return () => {\n process.stdout.off('resize', handler);\n };\n }, []);\n\n return size;\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nexport type PanelId = 'health' | 'git' | 'trend' | 'quickwins' | 'activity';\n\ninterface HotkeyBarProps {\n activePanel: PanelId | null;\n}\n\nconst HOTKEYS: Array<{ key: string; label: string; panel?: PanelId }> = [\n { key: 'h', label: 'health', panel: 'health' },\n { key: 'g', label: 'git', panel: 'git' },\n { key: 't', label: 'trend', panel: 'trend' },\n { key: 'q', label: 'quick wins', panel: 'quickwins' },\n { key: 'a', label: 'activity', panel: 'activity' },\n { key: 'r', label: 're-run' },\n { key: 'w', label: 'web' },\n { key: 'W', label: 'web+AI' },\n { key: '?', label: 'help' },\n];\n\nexport function HotkeyBar({ activePanel }: HotkeyBarProps) {\n return (\n <Box>\n {HOTKEYS.map((hk) => {\n const isActive = hk.panel !== undefined && hk.panel === activePanel;\n return (\n <Box key={hk.key} marginRight={2}>\n <Text bold={isActive} color={isActive ? 'cyan' : undefined}>\n [{hk.key}]\n </Text>\n <Text bold={isActive} color={isActive ? 'cyan' : 'gray'}>\n {' '}\n {hk.label}\n </Text>\n </Box>\n );\n })}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport type { MonorepoReport, PackageReport } from 'sickbay-core';\n\ninterface MonorepoPanelProps {\n report: MonorepoReport | null;\n availableWidth?: number;\n}\n\nfunction scoreBar(score: number, width = 8): string {\n const filled = Math.round((score / 100) * width);\n return '█'.repeat(filled) + '░'.repeat(width - filled);\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nexport function MonorepoPanel({ report, availableWidth }: MonorepoPanelProps) {\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const nameWidth = Math.max(12, Math.min(20, (availableWidth ?? 36) - 22));\n\n return (\n <Box flexDirection=\"column\">\n <Box marginBottom={1}>\n <Text bold>{report.packages.length} packages</Text>\n <Text dimColor> · </Text>\n <Text dimColor>{report.monorepoType} workspaces</Text>\n <Text dimColor> · </Text>\n <Text color={scoreColor(report.overallScore)} bold>\n {report.overallScore}\n </Text>\n <Text dimColor> avg</Text>\n </Box>\n\n {report.packages.map((pkg: PackageReport) => {\n const name = pkg.name.includes('/') ? (pkg.name.split('/').pop() ?? pkg.name) : pkg.name;\n const truncatedName =\n name.length > nameWidth\n ? name.slice(0, nameWidth - 1) + '\\u2026'\n : name.padEnd(nameWidth);\n\n return (\n <Box key={pkg.path} gap={1}>\n <Text color={scoreColor(pkg.score)}>{scoreBar(pkg.score)}</Text>\n <Text color={scoreColor(pkg.score)} bold>\n {String(pkg.score).padStart(3)}\n </Text>\n <Text>{truncatedName}</Text>\n {pkg.summary.critical > 0 && <Text color=\"red\"> ⚠</Text>}\n </Box>\n );\n })}\n\n <Box marginTop={1}>\n <Text dimColor>Press </Text>\n <Text color=\"cyan\">[w]</Text>\n <Text dimColor> for per-package web view</Text>\n </Box>\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\ninterface PanelBorderProps {\n title: string;\n color: string;\n focused?: boolean;\n visible?: boolean;\n flash?: 'green' | 'red';\n children: React.ReactNode;\n}\n\nexport function PanelBorder({\n title,\n color,\n focused,\n visible = true,\n flash,\n children,\n}: PanelBorderProps) {\n const borderColor = flash ?? (focused ? color : 'gray');\n const borderStyle = flash ? 'double' : focused ? 'double' : 'single';\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle={borderStyle}\n borderColor={borderColor}\n paddingX={1}\n flexGrow={1}\n >\n <Text bold color={flash ?? color}>\n {title}\n </Text>\n {visible ? children : <Text dimColor>···</Text>}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport type { SickbayReport } from 'sickbay-core';\n\ninterface QuickWinsPanelProps {\n report: SickbayReport | null;\n availableWidth?: number;\n}\n\nfunction replacePackageManager(cmd: string, pm: string): string {\n if (pm === 'npm') return cmd;\n const install = pm === 'pnpm' ? 'pnpm add' : pm === 'yarn' ? 'yarn add' : 'bun add';\n const uninstall = pm === 'pnpm' ? 'pnpm remove' : pm === 'yarn' ? 'yarn remove' : 'bun remove';\n const update = pm === 'pnpm' ? 'pnpm update' : pm === 'yarn' ? 'yarn upgrade' : 'bun update';\n const auditFix =\n pm === 'pnpm' ? 'pnpm audit --fix' : pm === 'yarn' ? 'yarn npm audit --fix' : 'bun audit';\n return cmd\n .replace(/^npm install(?=\\s)/, install)\n .replace(/^npm uninstall(?=\\s)/, uninstall)\n .replace(/^npm update(?=\\s)/, update)\n .replace(/^npm audit fix/, auditFix);\n}\n\nfunction shortenPath(filepath: string): string {\n const parts = filepath.split('/');\n if (parts.length <= 2) return filepath;\n return '\\u2026/' + parts.slice(-2).join('/');\n}\n\nfunction smartTruncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n // If it contains a file path, shorten the path but keep the filename\n const pathMatch = str.match(/(\\S*\\/\\S+)/);\n if (pathMatch) {\n const shortened = str.replace(pathMatch[1], shortenPath(pathMatch[1]));\n if (shortened.length <= maxLen) return shortened;\n return shortened.slice(0, maxLen - 1) + '\\u2026';\n }\n return str.slice(0, maxLen - 1) + '\\u2026';\n}\n\nfunction shortenCommand(cmd: string, maxLen: number): string {\n if (cmd.length <= maxLen) return cmd;\n // Shorten any file paths in the command first\n const shortened = cmd.replace(/(\\S*\\/\\S+)/g, (match) => shortenPath(match));\n if (shortened.length <= maxLen) return shortened;\n return shortened.slice(0, maxLen - 1) + '\\u2026';\n}\n\nexport function QuickWinsPanel({ report, availableWidth }: QuickWinsPanelProps) {\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const fixes: Array<{\n description: string;\n command: string;\n severity: string;\n }> = [];\n\n for (const check of report.checks) {\n for (const issue of check.issues) {\n if (issue.fix?.command) {\n fixes.push({\n description: issue.fix.description,\n command: issue.fix.command,\n severity: issue.severity,\n });\n }\n }\n }\n\n const order: Record<string, number> = { critical: 0, warning: 1, info: 2 };\n fixes.sort((a, b) => (order[a.severity] ?? 3) - (order[b.severity] ?? 3));\n const top = fixes.slice(0, 5);\n\n if (top.length === 0) {\n return (\n <Box>\n <Text color=\"green\">Looking good!</Text>\n </Box>\n );\n }\n\n const pm = report.projectInfo?.packageManager ?? 'npm';\n const maxTextLen = Math.max(16, (availableWidth ?? 26) - 4);\n\n return (\n <Box flexDirection=\"column\">\n {top.map((fix, i) => (\n <Box key={i} flexDirection=\"column\" marginBottom={i < top.length - 1 ? 1 : 0}>\n <Text>\n <Text\n color={\n fix.severity === 'critical' ? 'red' : fix.severity === 'warning' ? 'yellow' : 'gray'\n }\n >\n {'\\u2192'}{' '}\n </Text>\n {smartTruncate(fix.description, maxTextLen)}\n </Text>\n <Text dimColor>\n {' '}\n {shortenCommand(replacePackageManager(fix.command, pm), maxTextLen)}\n </Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import React, { useState, useEffect, useRef } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { countUniqueIssues } from '../../lib/issue-grouping.js';\n\nimport type { SickbayReport } from 'sickbay-core';\n\ninterface ScorePanelProps {\n report: SickbayReport | null;\n previousScore: number | null;\n animate?: boolean;\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nfunction scoreBar(score: number, width = 15): string {\n const filled = Math.round((score / 100) * width);\n return '\\u2588'.repeat(filled) + '\\u2591'.repeat(width - filled);\n}\n\nexport function ScorePanel({ report, previousScore, animate = true }: ScorePanelProps) {\n const [animatedScore, setAnimatedScore] = useState(0);\n const prevTargetRef = useRef(0);\n\n useEffect(() => {\n if (!animate) return;\n\n if (!report) {\n setAnimatedScore(0);\n prevTargetRef.current = 0;\n return;\n }\n\n const target = report.overallScore;\n const start = prevTargetRef.current;\n prevTargetRef.current = target;\n\n if (start === target) return;\n\n let current = start;\n const step = target > start ? 1 : -1;\n\n const id = setInterval(() => {\n current += step;\n setAnimatedScore(current);\n if (current === target) clearInterval(id);\n }, 20);\n\n return () => clearInterval(id);\n }, [report, animate]);\n\n // When animate=false, render score directly from prop (no state update needed)\n const displayScore = animate ? animatedScore : (report?.overallScore ?? 0);\n\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const delta = previousScore !== null ? report.overallScore - previousScore : null;\n const u = countUniqueIssues(report.checks);\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text color={scoreColor(displayScore)} bold>\n {displayScore}/100\n </Text>\n <Text> </Text>\n <Text color={scoreColor(displayScore)}>{scoreBar(displayScore)}</Text>\n </Box>\n {delta !== null && (\n <Text dimColor>\n {delta > 0 ? `+${delta}` : delta === 0 ? '\\u00B10' : `${delta}`} since last scan\n </Text>\n )}\n <Box marginTop={1} flexDirection=\"column\">\n <Box>\n <Text color=\"red\">\n {'\\u2717'} {u.critical} critical\n {u.totalCritical > u.critical ? ` (${u.totalCritical})` : ''}\n </Text>\n <Text>{' '}</Text>\n <Text color=\"yellow\">\n {'\\u26A0'} {u.warnings} warn\n {u.totalWarnings > u.warnings ? ` (${u.totalWarnings})` : ''}\n </Text>\n <Text>{' '}</Text>\n <Text dimColor>i {u.info} info</Text>\n </Box>\n </Box>\n {report.quote && (\n <Box marginTop={1}>\n <Text italic dimColor>\n \"{report.quote.text}\" — {report.quote.source}\n </Text>\n </Box>\n )}\n {report.config?.hasCustomConfig && (\n <Text dimColor italic>\n Custom config\n </Text>\n )}\n </Box>\n );\n}\n","import React, { useState, useEffect } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { sparkline, trendArrow } from '../../commands/trend.js';\nimport { loadHistory } from '../../lib/history.js';\n\ninterface TrendPanelProps {\n projectPath: string;\n lastScanTime: Date | null;\n availableHeight?: number;\n}\n\nconst CATEGORY_COLORS: Record<string, string> = {\n overall: 'white',\n dependencies: 'blue',\n security: 'green',\n 'code-quality': 'yellow',\n performance: 'magenta',\n git: 'cyan',\n};\n\nconst SHORT_LABELS: Record<string, string> = {\n dependencies: 'Deps',\n security: 'Security',\n 'code-quality': 'Quality',\n performance: 'Perf',\n git: 'Git',\n};\n\nexport function TrendPanel({ projectPath, lastScanTime, availableHeight }: TrendPanelProps) {\n const [trends, setTrends] = useState<\n Array<{\n label: string;\n spark: string;\n latest: number;\n arrow: string;\n color: string;\n }>\n >([]);\n\n useEffect(() => {\n const history = loadHistory(projectPath);\n if (!history || history.entries.length === 0) {\n setTrends([]);\n return;\n }\n\n const entries = history.entries.slice(-10);\n const overallValues = entries.map((e) => e.overallScore);\n const overallArrow = trendArrow(overallValues);\n\n const result: typeof trends = [\n {\n label: 'Overall',\n spark: sparkline(overallValues),\n latest: overallValues[overallValues.length - 1],\n arrow: overallArrow.label,\n color: 'white',\n },\n ];\n\n const categories = Object.keys(entries[entries.length - 1].categoryScores);\n for (const cat of categories) {\n const values = entries\n .map((e) => e.categoryScores[cat])\n .filter((v): v is number => v !== undefined);\n\n if (values.length === 0) continue;\n\n const arrow = trendArrow(values);\n result.push({\n label: SHORT_LABELS[cat] || cat.charAt(0).toUpperCase() + cat.slice(1),\n spark: sparkline(values),\n latest: values[values.length - 1],\n arrow: arrow.label,\n color: CATEGORY_COLORS[cat] || 'white',\n });\n }\n\n setTrends(result);\n }, [projectPath, lastScanTime]);\n\n if (trends.length === 0) {\n return (\n <Box>\n <Text dimColor>No trend data yet.</Text>\n </Box>\n );\n }\n\n const maxRows = availableHeight ?? trends.length;\n const visible = trends.slice(0, maxRows);\n\n return (\n <Box flexDirection=\"column\">\n {visible.map((t) => (\n <Box key={t.label}>\n <Box width={10}>\n <Text color={t.color}>{t.label}</Text>\n </Box>\n <Text>{t.spark}</Text>\n <Text bold> {String(t.latest).padStart(3)}</Text>\n <Text> {t.arrow}</Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import React, { useState, useEffect, useCallback, useRef } from 'react';\n\nimport { Box, Text, useInput } from 'ink';\n\nimport { checkForUpdate } from '../../lib/update-check.js';\nimport { UpdateNotice } from '../UpdateNotice.js';\nimport { ActivityPanel, type ActivityEntry } from './ActivityPanel.js';\nimport { GitPanel } from './GitPanel.js';\nimport { HealthPanel } from './HealthPanel.js';\nimport { HelpPanel } from './HelpPanel.js';\nimport { useFileWatcher } from './hooks/useFileWatcher.js';\nimport { useSickbayRunner } from './hooks/useSickbayRunner.js';\nimport { useTerminalSize } from './hooks/useTerminalSize.js';\nimport { HotkeyBar, type PanelId } from './HotkeyBar.js';\nimport { MonorepoPanel } from './MonorepoPanel.js';\nimport { PanelBorder } from './PanelBorder.js';\nimport { QuickWinsPanel } from './QuickWinsPanel.js';\nimport { ScorePanel } from './ScorePanel.js';\nimport { TrendPanel } from './TrendPanel.js';\n\nimport type { UpdateInfo } from '../../lib/update-check.js';\nimport type { SickbayReport, MonorepoReport } from 'sickbay-core';\n\ndeclare const __VERSION__: string;\n\ninterface TuiAppProps {\n projectPath: string;\n checks?: string[];\n watchEnabled: boolean;\n refreshInterval: number;\n quotes?: boolean;\n animateOnMount?: boolean;\n}\n\nexport function TuiApp({\n projectPath,\n checks,\n watchEnabled,\n refreshInterval,\n quotes,\n animateOnMount = true,\n}: TuiAppProps) {\n const { rows, columns } = useTerminalSize();\n const { report, monorepoReport, isScanning, progress, scan } = useSickbayRunner({\n projectPath,\n checks,\n quotes,\n });\n\n const [focusedPanel, setFocusedPanel] = useState<PanelId | null>(null);\n const [expandedPanel, setExpandedPanel] = useState<PanelId | null>(null);\n const [showHelp, setShowHelp] = useState(false);\n const [lastScanTime, setLastScanTime] = useState<Date | null>(null);\n const [previousScore, setPreviousScore] = useState<number | null>(null);\n const [activityLog, setActivityLog] = useState<ActivityEntry[]>([]);\n const [healthScrollOffset, setHealthScrollOffset] = useState(0);\n const [scoreFlash, setScoreFlash] = useState<'green' | 'red' | undefined>();\n const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);\n const ALL_PANELS = new Set(['health', 'score', 'trend', 'git', 'quickwins', 'activity']);\n const [visiblePanels, setVisiblePanels] = useState<Set<string>>(\n animateOnMount ? new Set() : ALL_PANELS,\n );\n const refreshRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n // Panel entrance animation — stagger panels appearing on mount\n useEffect(() => {\n const schedule: Array<[string, number]> = [\n ['health', 100],\n ['score', 300],\n ['trend', 500],\n ['git', 700],\n ['quickwins', 900],\n ['activity', 1100],\n ];\n const timers = schedule.map(([panel, delay]) =>\n setTimeout(() => {\n setVisiblePanels((prev) => new Set([...prev, panel]));\n }, delay),\n );\n return () => timers.forEach(clearTimeout);\n }, []);\n\n useEffect(() => {\n checkForUpdate(__VERSION__).then((info) => {\n if (info) setUpdateInfo(info);\n });\n }, []);\n\n const reportRef = useRef<SickbayReport | null>(null);\n const monorepoReportRef = useRef<MonorepoReport | null>(null);\n\n // Keep refs in sync\n useEffect(() => {\n reportRef.current = report;\n }, [report]);\n\n useEffect(() => {\n monorepoReportRef.current = monorepoReport;\n }, [monorepoReport]);\n\n const addActivity = useCallback((type: ActivityEntry['type'], message: string) => {\n setActivityLog((prev) => [...prev, { timestamp: new Date(), type, message }]);\n }, []);\n\n const handleScanComplete = useCallback(\n async (result: SickbayReport) => {\n const prevScore = reportRef.current?.overallScore ?? null;\n if (prevScore !== null) setPreviousScore(prevScore);\n setLastScanTime(new Date());\n\n // Auto-save last report snapshot\n try {\n const { saveLastReport } = await import('../../lib/history.js');\n saveLastReport(result);\n } catch {\n // Non-critical\n }\n\n // Cache dependency tree for web dashboard\n try {\n const { getDependencyTree } = await import('sickbay-core');\n const { saveDepTree } = await import('../../lib/history.js');\n const tree = await getDependencyTree(projectPath, result.projectInfo.packageManager);\n saveDepTree(projectPath, tree);\n } catch {\n /* dep tree is optional */\n }\n\n const delta = prevScore !== null ? result.overallScore - prevScore : null;\n addActivity(\n 'scan-complete',\n `Scan complete: ${result.overallScore}/100${delta !== null ? ` (${delta >= 0 ? '+' : ''}${delta})` : ''}`,\n );\n\n // Flash score panel border on score change\n if (delta !== null && delta !== 0) {\n const flash = delta > 0 ? 'green' : 'red';\n setScoreFlash(flash);\n setTimeout(() => setScoreFlash(undefined), 600);\n }\n\n // Check for regressions\n try {\n const { loadHistory, detectRegressions } = await import('../../lib/history.js');\n const history = loadHistory(projectPath);\n if (history) {\n const regressions = detectRegressions(history.entries);\n for (const reg of regressions) {\n addActivity(\n 'regression',\n `\\u26A0 ${reg.category} regressed: ${reg.from} \\u2192 ${reg.to}`,\n );\n }\n }\n } catch {\n // Non-critical\n }\n },\n [addActivity, projectPath],\n );\n\n // Initial scan\n useEffect(() => {\n addActivity('info', 'TUI started');\n addActivity('scan-start', 'Starting initial health scan...');\n scan().then((result) => {\n if (result) handleScanComplete(result);\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Auto-refresh timer\n useEffect(() => {\n if (refreshInterval <= 0) return;\n refreshRef.current = setInterval(async () => {\n addActivity('scan-start', 'Auto-scan triggered');\n const result = await scan();\n if (result) handleScanComplete(result);\n }, refreshInterval * 1000);\n\n return () => {\n if (refreshRef.current) clearInterval(refreshRef.current);\n };\n }, [refreshInterval, scan, handleScanComplete, addActivity]);\n\n // File watcher\n useFileWatcher({\n projectPath,\n enabled: watchEnabled,\n onFilesChanged: async (files) => {\n for (const f of files.slice(0, 3)) {\n addActivity('file-change', `File changed: ${f}`);\n }\n if (files.length > 3) {\n addActivity('file-change', `...and ${files.length - 3} more files`);\n }\n addActivity('scan-start', 'Re-scan triggered (file change)');\n const result = await scan();\n if (result) handleScanComplete(result);\n },\n });\n\n // Manual re-scan\n const triggerRescan = useCallback(async () => {\n addActivity('scan-start', 'Manual re-scan triggered');\n const result = await scan();\n if (result) handleScanComplete(result);\n }, [addActivity, scan, handleScanComplete]);\n\n // Keyboard input — only active when stdin supports raw mode (interactive TTY)\n const isTTY = !!(process.stdin.isTTY && process.stdin.setRawMode);\n useInput(\n (input, key) => {\n // Help overlay: ? toggles, Escape closes\n if (showHelp) {\n if (input === '?' || key.escape) {\n setShowHelp(false);\n }\n return;\n }\n\n // Expanded panel: escape or f to exit\n if (expandedPanel) {\n if (key.escape || input === 'f') {\n setExpandedPanel(null);\n return;\n }\n }\n\n if (input === '?') {\n setShowHelp(true);\n return;\n }\n\n // Panel focus toggles\n if (input === 'h') setFocusedPanel((prev) => (prev === 'health' ? null : 'health'));\n else if (input === 'g') setFocusedPanel((prev) => (prev === 'git' ? null : 'git'));\n else if (input === 't') setFocusedPanel((prev) => (prev === 'trend' ? null : 'trend'));\n else if (input === 'q')\n setFocusedPanel((prev) => (prev === 'quickwins' ? null : 'quickwins'));\n else if (input === 'a') setFocusedPanel((prev) => (prev === 'activity' ? null : 'activity'));\n else if (input === 'r') triggerRescan();\n else if (input === 'f' && focusedPanel) setExpandedPanel(focusedPanel);\n else if (input === 'w' || input === 'W') {\n const withAI = input === 'W';\n (async () => {\n const webReport = monorepoReportRef.current ?? reportRef.current;\n if (!webReport) return;\n try {\n const { serveWeb } = await import('../../commands/web.js');\n const { default: openBrowser } = await import('open');\n\n let aiService;\n if (withAI && process.env.ANTHROPIC_API_KEY && !monorepoReportRef.current) {\n const { createAIService } = await import('../../services/ai.js');\n aiService = createAIService(process.env.ANTHROPIC_API_KEY);\n addActivity('info', 'Launching web dashboard with AI...');\n } else {\n addActivity('info', 'Launching web dashboard...');\n }\n\n const url = await serveWeb(webReport, 3030, aiService);\n await openBrowser(url);\n addActivity(\n 'info',\n `Web dashboard at ${url}${withAI && aiService ? ' (AI enabled)' : ''}`,\n );\n } catch (err) {\n addActivity('info', `Failed to open web: ${err instanceof Error ? err.message : err}`);\n }\n })();\n } else if (key.escape) {\n setFocusedPanel(null);\n }\n\n // Scrolling in focused health panel\n if (focusedPanel === 'health') {\n if (key.upArrow) setHealthScrollOffset((prev) => Math.max(0, prev - 1));\n if (key.downArrow) {\n const maxOffset = Math.max(0, visibleChecks.length - 5);\n setHealthScrollOffset((prev) => Math.min(maxOffset, prev + 1));\n }\n }\n },\n { isActive: isTTY },\n );\n\n const visibleChecks = report?.checks.filter((c) => c.status !== 'skipped') ?? [];\n\n // Layout calculations — reserve 1 row for project header + 1 for hotkey bar\n const topHeight = Math.floor((rows - 2) * 0.6);\n const bottomHeight = rows - 2 - topHeight;\n\n // Help overlay\n if (showHelp) {\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexGrow={1}>\n <PanelBorder title=\"HELP\" color=\"cyan\" focused>\n <HelpPanel />\n </PanelBorder>\n </Box>\n <HotkeyBar activePanel={focusedPanel} />\n </Box>\n );\n }\n\n // Full-screen expand mode\n if (expandedPanel) {\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexGrow={1}>\n {expandedPanel === 'health' && (\n <PanelBorder title=\"HEALTH CHECKS\" color=\"green\" focused>\n <HealthPanel\n checks={visibleChecks}\n isScanning={isScanning}\n progress={progress}\n scrollOffset={healthScrollOffset}\n availableHeight={rows - 4}\n />\n </PanelBorder>\n )}\n {expandedPanel === 'git' && (\n <PanelBorder title=\"GIT STATUS\" color=\"yellow\" focused>\n <GitPanel projectPath={projectPath} availableWidth={columns - 4} />\n </PanelBorder>\n )}\n {expandedPanel === 'trend' && (\n <PanelBorder title=\"TREND\" color=\"magenta\" focused>\n <TrendPanel projectPath={projectPath} lastScanTime={lastScanTime} />\n </PanelBorder>\n )}\n {expandedPanel === 'quickwins' && (\n <PanelBorder title={monorepoReport ? 'MONOREPO' : 'QUICK WINS'} color=\"red\" focused>\n {monorepoReport ? (\n <MonorepoPanel report={monorepoReport} />\n ) : (\n <QuickWinsPanel report={report} />\n )}\n </PanelBorder>\n )}\n {expandedPanel === 'activity' && (\n <PanelBorder title=\"ACTIVITY\" color=\"cyan\" focused>\n <ActivityPanel entries={activityLog} availableHeight={rows - 4} />\n </PanelBorder>\n )}\n </Box>\n <HotkeyBar activePanel={expandedPanel} />\n </Box>\n );\n }\n\n // Normal grid layout\n const projectName = monorepoReport\n ? `${monorepoReport.rootPath.split('/').pop()} (monorepo)`\n : (report?.projectInfo?.name ?? '—');\n const projectVersion = report?.projectInfo?.version;\n const scanLabel = lastScanTime\n ? `Last scan ${lastScanTime.toLocaleTimeString()}`\n : isScanning\n ? 'Scanning…'\n : 'Not yet scanned';\n\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n {updateInfo && (\n <UpdateNotice\n currentVersion={updateInfo.currentVersion}\n latestVersion={updateInfo.latestVersion}\n />\n )}\n {/* Project header */}\n <Box paddingX={1} justifyContent=\"space-between\">\n <Box gap={1}>\n <Text bold color=\"cyan\">\n SICKBAY\n </Text>\n <Text bold>{projectName}</Text>\n {projectVersion && <Text dimColor>v{projectVersion}</Text>}\n </Box>\n <Text dimColor>{scanLabel}</Text>\n </Box>\n\n {/* Top Row */}\n <Box height={topHeight}>\n <Box width=\"55%\">\n <PanelBorder\n title=\"HEALTH CHECKS\"\n color=\"green\"\n focused={focusedPanel === 'health'}\n visible={visiblePanels.has('health')}\n >\n <HealthPanel\n checks={visibleChecks}\n isScanning={isScanning}\n progress={progress}\n scrollOffset={healthScrollOffset}\n availableHeight={topHeight - 4}\n />\n </PanelBorder>\n </Box>\n <Box width=\"45%\" flexDirection=\"column\">\n <Box height=\"50%\">\n <PanelBorder\n title=\"SCORE\"\n color=\"blue\"\n visible={visiblePanels.has('score')}\n flash={scoreFlash}\n >\n <ScorePanel report={report} previousScore={previousScore} animate={animateOnMount} />\n </PanelBorder>\n </Box>\n <Box height=\"50%\">\n <PanelBorder\n title=\"TREND\"\n color=\"magenta\"\n focused={focusedPanel === 'trend'}\n visible={visiblePanels.has('trend')}\n >\n <TrendPanel\n projectPath={projectPath}\n lastScanTime={lastScanTime}\n availableHeight={Math.floor(topHeight / 2) - 3}\n />\n </PanelBorder>\n </Box>\n </Box>\n </Box>\n\n {/* Bottom Row */}\n <Box height={bottomHeight}>\n <Box width=\"25%\">\n <PanelBorder\n title=\"GIT STATUS\"\n color=\"yellow\"\n focused={focusedPanel === 'git'}\n visible={visiblePanels.has('git')}\n >\n <GitPanel projectPath={projectPath} availableWidth={Math.floor(columns * 0.25) - 4} />\n </PanelBorder>\n </Box>\n <Box width=\"30%\">\n <PanelBorder\n title={monorepoReport ? 'MONOREPO' : 'QUICK WINS'}\n color=\"red\"\n focused={focusedPanel === 'quickwins'}\n visible={visiblePanels.has('quickwins')}\n >\n {monorepoReport ? (\n <MonorepoPanel\n report={monorepoReport}\n availableWidth={Math.floor(columns * 0.3) - 4}\n />\n ) : (\n <QuickWinsPanel report={report} availableWidth={Math.floor(columns * 0.3) - 4} />\n )}\n </PanelBorder>\n </Box>\n <Box width=\"45%\">\n <PanelBorder\n title=\"ACTIVITY\"\n color=\"cyan\"\n focused={focusedPanel === 'activity'}\n visible={visiblePanels.has('activity')}\n >\n <ActivityPanel entries={activityLog} availableHeight={bottomHeight - 4} />\n </PanelBorder>\n </Box>\n </Box>\n\n {/* Hotkey Bar */}\n <HotkeyBar activePanel={focusedPanel} />\n </Box>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TuiApp-LHb149v8.js","names":["scoreColor","scoreBar","scoreBar","scoreColor"],"sources":["../src/components/tui/ActivityPanel.tsx","../src/components/tui/hooks/useGitStatus.ts","../src/components/tui/GitPanel.tsx","../src/components/tui/HealthPanel.tsx","../src/components/tui/HelpPanel.tsx","../src/components/tui/hooks/useFileWatcher.ts","../src/components/tui/hooks/useSickbayRunner.ts","../src/components/tui/hooks/useTerminalSize.ts","../src/components/tui/HotkeyBar.tsx","../src/components/tui/MonorepoPanel.tsx","../src/components/tui/PanelBorder.tsx","../src/components/tui/QuickWinsPanel.tsx","../src/components/tui/ScorePanel.tsx","../src/components/tui/TrendPanel.tsx","../src/components/tui/TuiApp.tsx"],"sourcesContent":["import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nexport interface ActivityEntry {\n timestamp: Date;\n type: 'scan-start' | 'scan-complete' | 'file-change' | 'git-change' | 'regression' | 'info';\n message: string;\n}\n\ninterface ActivityPanelProps {\n entries: ActivityEntry[];\n availableHeight: number;\n}\n\nconst TYPE_COLOR: Record<ActivityEntry['type'], string> = {\n 'scan-start': 'cyan',\n 'scan-complete': 'green',\n 'file-change': 'yellow',\n 'git-change': 'magenta',\n regression: 'red',\n info: 'gray',\n};\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString('en-US', { hour12: false });\n}\n\nexport function ActivityPanel({ entries, availableHeight }: ActivityPanelProps) {\n if (entries.length === 0) {\n return (\n <Box>\n <Text dimColor>No activity yet.</Text>\n </Box>\n );\n }\n\n const visible = entries.slice(-Math.max(1, availableHeight));\n\n return (\n <Box flexDirection=\"column\">\n {visible.map((entry) => (\n <Box key={`${entry.timestamp.getTime()}-${entry.type}`}>\n <Text dimColor>\n {formatTime(entry.timestamp)}\n {' '}\n </Text>\n <Text color={TYPE_COLOR[entry.type] || 'white'}>{entry.message}</Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import { execFile } from 'child_process';\nimport { promisify } from 'util';\n\nimport { useState, useEffect, useRef } from 'react';\n\nconst exec = promisify(execFile);\n\nexport interface GitStatus {\n branch: string;\n modified: number;\n staged: number;\n untracked: number;\n ahead: number;\n behind: number;\n stashes: number;\n lastCommit: string;\n lastCommitTime: string;\n}\n\nasync function git(args: string[], cwd: string): Promise<string> {\n try {\n const { stdout } = await exec('git', args, { cwd, timeout: 5000 });\n return stdout.trim();\n } catch {\n return '';\n }\n}\n\nasync function fetchGitStatus(projectPath: string): Promise<GitStatus> {\n const [branch, porcelain, revList, stashList, logLine] = await Promise.all([\n git(['branch', '--show-current'], projectPath),\n git(['status', '--porcelain'], projectPath),\n git(['rev-list', '--left-right', '--count', 'HEAD...@{upstream}'], projectPath),\n git(['stash', 'list'], projectPath),\n git(['log', '-1', '--format=%s|%cr'], projectPath),\n ]);\n\n const lines = porcelain ? porcelain.split('\\n') : [];\n const staged = lines.filter((l) => /^[MADRC]/.test(l)).length;\n const modified = lines.filter((l) => /^.[MD]/.test(l)).length;\n const untracked = lines.filter((l) => l.startsWith('??')).length;\n\n let ahead = 0;\n let behind = 0;\n if (revList) {\n const parts = revList.split(/\\s+/);\n ahead = parseInt(parts[0], 10) || 0;\n behind = parseInt(parts[1], 10) || 0;\n }\n\n const stashes = stashList ? stashList.split('\\n').filter(Boolean).length : 0;\n const [lastCommit, lastCommitTime] = logLine.split('|');\n\n return {\n branch: branch || 'unknown',\n modified,\n staged,\n untracked,\n ahead,\n behind,\n stashes,\n lastCommit: lastCommit || '',\n lastCommitTime: lastCommitTime || '',\n };\n}\n\nexport function useGitStatus(projectPath: string, pollInterval = 10000) {\n const [status, setStatus] = useState<GitStatus | null>(null);\n const intervalRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n useEffect(() => {\n let mounted = true;\n\n fetchGitStatus(projectPath).then((s) => {\n if (mounted) setStatus(s);\n });\n\n intervalRef.current = setInterval(() => {\n fetchGitStatus(projectPath).then((s) => {\n if (mounted) setStatus(s);\n });\n }, pollInterval);\n\n return () => {\n mounted = false;\n if (intervalRef.current) clearInterval(intervalRef.current);\n };\n }, [projectPath, pollInterval]);\n\n return status;\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { useGitStatus } from './hooks/useGitStatus.js';\n\ninterface GitPanelProps {\n projectPath: string;\n availableWidth?: number;\n}\n\nexport function GitPanel({ projectPath, availableWidth = 30 }: GitPanelProps) {\n const status = useGitStatus(projectPath);\n\n if (!status) {\n return (\n <Box>\n <Text dimColor>Loading git info...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text dimColor>Branch: </Text>\n <Text bold color=\"green\">\n {status.branch}\n </Text>\n </Box>\n {(status.ahead > 0 || status.behind > 0) && (\n <Box>\n {status.ahead > 0 && (\n <Text color=\"green\">\n {'\\u2191'}\n {status.ahead}{' '}\n </Text>\n )}\n {status.behind > 0 && (\n <Text color=\"red\">\n {'\\u2193'}\n {status.behind}\n </Text>\n )}\n </Box>\n )}\n <Box>\n <Text dimColor>Modified: </Text>\n <Text color={status.modified > 0 ? 'yellow' : 'green'}>{status.modified}</Text>\n </Box>\n <Box>\n <Text dimColor>Staged: </Text>\n <Text color={status.staged > 0 ? 'cyan' : 'green'}>{status.staged}</Text>\n </Box>\n <Box>\n <Text dimColor>Untracked: </Text>\n <Text>{status.untracked}</Text>\n </Box>\n {status.stashes > 0 && (\n <Box>\n <Text dimColor>Stashes: </Text>\n <Text>{status.stashes}</Text>\n </Box>\n )}\n {status.lastCommit && (\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>Last commit:</Text>\n <Text>\n {status.lastCommit.length > availableWidth\n ? status.lastCommit.slice(0, availableWidth - 3) + '...'\n : status.lastCommit}\n </Text>\n <Text dimColor>{status.lastCommitTime}</Text>\n </Box>\n )}\n </Box>\n );\n}\n","import React, { useState, useEffect } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { LOADING_MESSAGES } from '../../lib/messages.js';\n\nimport type { CheckResult } from 'sickbay-core';\n\ninterface HealthPanelProps {\n checks: CheckResult[];\n isScanning: boolean;\n progress: Array<{ name: string; status: string }>;\n scrollOffset: number;\n availableHeight: number;\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nfunction scoreBar(score: number, width = 10): string {\n const filled = Math.round((score / 100) * width);\n return '\\u2588'.repeat(filled) + '\\u2591'.repeat(width - filled);\n}\n\nfunction truncate(str: string, maxLen: number): string {\n return str.length > maxLen ? str.slice(0, maxLen - 1) + '\\u2026' : str;\n}\n\nfunction statusIcon(status: string): string {\n switch (status) {\n case 'pass':\n return '\\u2713';\n case 'fail':\n return '\\u2717';\n case 'warning':\n return '\\u26A0';\n case 'skipped':\n return '\\u25CB';\n default:\n return '?';\n }\n}\n\nexport function HealthPanel({\n checks,\n isScanning,\n progress,\n scrollOffset,\n availableHeight,\n}: HealthPanelProps) {\n const [msgIdx, setMsgIdx] = useState(0);\n\n useEffect(() => {\n if (!isScanning || progress.length > 0) return;\n const id = setInterval(() => {\n setMsgIdx((i) => (i + 1) % LOADING_MESSAGES.length);\n }, 4000);\n return () => clearInterval(id);\n }, [isScanning, progress.length]);\n\n if (isScanning && checks.length === 0) {\n if (progress.length === 0) {\n return (\n <Box>\n <Text dimColor>{LOADING_MESSAGES[msgIdx]}</Text>\n </Box>\n );\n }\n return (\n <Box flexDirection=\"column\">\n {progress.map((p) => (\n <Box key={p.name}>\n <Text\n color={p.status === 'done' ? 'green' : p.status === 'running' ? 'yellow' : 'gray'}\n >\n {p.status === 'done' ? '\\u2713' : p.status === 'running' ? '\\u25CC' : '\\u25CB'}\n </Text>\n <Text> {p.name}</Text>\n </Box>\n ))}\n </Box>\n );\n }\n\n if (checks.length === 0) {\n return (\n <Box>\n <Text dimColor>No results yet. Press [r] to scan.</Text>\n </Box>\n );\n }\n\n const maxVisible = Math.max(1, availableHeight);\n const visible = checks.slice(scrollOffset, scrollOffset + maxVisible);\n const hasMore = scrollOffset + maxVisible < checks.length;\n const hasLess = scrollOffset > 0;\n\n return (\n <Box flexDirection=\"column\">\n {hasLess && <Text dimColor> \\u25B2 {scrollOffset} more above</Text>}\n {visible.map((check) => (\n <Box key={check.id}>\n <Text\n color={check.status === 'pass' ? 'green' : check.status === 'fail' ? 'red' : 'yellow'}\n >\n {statusIcon(check.status)}\n </Text>\n <Text> </Text>\n <Box width={26}>\n <Text wrap=\"truncate\">{truncate(check.name, 26)}</Text>\n </Box>\n <Text> </Text>\n <Text color={scoreColor(check.score)}>{scoreBar(check.score)}</Text>\n <Text color={scoreColor(check.score)}> {String(check.score).padStart(3)}</Text>\n </Box>\n ))}\n {hasMore && (\n <Text dimColor>\n {' \\u25BC '}\n {checks.length - scrollOffset - maxVisible} more below\n </Text>\n )}\n {isScanning && <Text color=\"yellow\">{' \\u25CC Re-scanning...'}</Text>}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\ninterface HelpRow {\n keys: string[];\n description: string;\n}\n\ninterface HelpSection {\n title: string;\n rows: HelpRow[];\n}\n\nconst SECTIONS: HelpSection[] = [\n {\n title: 'PANELS',\n rows: [\n { keys: ['h'], description: 'Focus / unfocus Health Checks' },\n { keys: ['g'], description: 'Focus / unfocus Git Status' },\n { keys: ['t'], description: 'Focus / unfocus Trend' },\n { keys: ['q'], description: 'Focus / unfocus Quick Wins' },\n { keys: ['a'], description: 'Focus / unfocus Activity' },\n { keys: ['f'], description: 'Expand focused panel to full screen' },\n { keys: ['Esc'], description: 'Unfocus panel / exit full screen' },\n ],\n },\n {\n title: 'SCROLLING',\n rows: [{ keys: ['↑', '↓'], description: 'Scroll Health Checks (when focused)' }],\n },\n {\n title: 'ACTIONS',\n rows: [\n { keys: ['r'], description: 'Re-run scan' },\n { keys: ['w'], description: 'Open web dashboard in browser' },\n { keys: ['W'], description: 'Open web dashboard with AI insights' },\n { keys: ['?'], description: 'Toggle this help screen' },\n ],\n },\n];\n\nexport function HelpPanel() {\n return (\n <Box flexDirection=\"column\" paddingX={2} paddingY={1}>\n <Box marginBottom={1}>\n <Text bold color=\"cyan\">\n SICKBAY TUI — KEYBOARD SHORTCUTS\n </Text>\n </Box>\n\n {SECTIONS.map((section) => (\n <Box key={section.title} flexDirection=\"column\" marginBottom={1}>\n <Box marginBottom={0}>\n <Text bold color=\"white\">\n {section.title}\n </Text>\n </Box>\n {section.rows.map((row) => (\n <Box key={row.description} marginLeft={2}>\n <Box width={16}>\n {row.keys.map((k, i) => (\n <Text key={k}>\n <Text color=\"cyan\" bold>\n [{k}]\n </Text>\n {i < row.keys.length - 1 ? <Text dimColor> / </Text> : null}\n </Text>\n ))}\n </Box>\n <Text dimColor>{row.description}</Text>\n </Box>\n ))}\n </Box>\n ))}\n\n <Box marginTop={1} borderStyle=\"single\" borderColor=\"gray\" paddingX={1}>\n <Text dimColor>Pro tip: focus a panel with its hotkey, then press </Text>\n <Text color=\"cyan\" bold>\n [f]\n </Text>\n <Text dimColor> to expand it full screen for more detail.</Text>\n </Box>\n\n <Box marginTop={1}>\n <Text dimColor>Press </Text>\n <Text color=\"cyan\" bold>\n [?]\n </Text>\n <Text dimColor> or </Text>\n <Text color=\"cyan\" bold>\n [Esc]\n </Text>\n <Text dimColor> to close</Text>\n </Box>\n </Box>\n );\n}\n","import { useState, useEffect, useRef } from 'react';\n\nimport type { FSWatcher } from 'chokidar';\n\ninterface UseFileWatcherOptions {\n projectPath: string;\n enabled: boolean;\n debounceMs?: number;\n onFilesChanged?: (files: string[]) => void;\n}\n\nexport function useFileWatcher({\n projectPath,\n enabled,\n debounceMs = 2000,\n onFilesChanged,\n}: UseFileWatcherOptions) {\n const [changedFiles, setChangedFiles] = useState<string[]>([]);\n const debounceRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n const pendingRef = useRef<string[]>([]);\n const watcherRef = useRef<FSWatcher | null>(null);\n // Keep a ref so the debounce callback always calls the latest version,\n // even if the parent re-renders and passes a new function reference.\n const onFilesChangedRef = useRef(onFilesChanged);\n onFilesChangedRef.current = onFilesChanged;\n\n useEffect(() => {\n if (!enabled) return;\n\n let mounted = true;\n\n (async () => {\n try {\n const { watch } = await import('chokidar');\n if (!mounted) return;\n\n const watcher = watch(['**/*.{ts,tsx,js,jsx,json}'], {\n cwd: projectPath,\n ignored: [\n '**/node_modules/**',\n '**/dist/**',\n '**/.git/**',\n '**/build/**',\n '**/.next/**',\n '**/.turbo/**',\n '**/coverage/**',\n ],\n ignoreInitial: true,\n persistent: true,\n });\n\n watcherRef.current = watcher;\n\n watcher.on('change', (path: string) => {\n pendingRef.current.push(path);\n setChangedFiles((prev) => [...prev, path]);\n\n if (debounceRef.current) clearTimeout(debounceRef.current);\n debounceRef.current = setTimeout(() => {\n const files = [...pendingRef.current];\n pendingRef.current = [];\n onFilesChangedRef.current?.(files);\n }, debounceMs);\n });\n } catch {\n // chokidar not available — silently skip\n }\n })();\n\n return () => {\n mounted = false;\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (watcherRef.current) watcherRef.current.close();\n };\n }, [projectPath, enabled, debounceMs]);\n\n return changedFiles;\n}\n","import { useState, useCallback, useRef } from 'react';\n\nimport { runSickbay, runSickbayMonorepo, detectMonorepo, buildSummary } from 'sickbay-core';\n\nimport type { SickbayReport, MonorepoReport, CheckResult } from 'sickbay-core';\n\ninterface ProgressItem {\n name: string;\n status: 'pending' | 'running' | 'done';\n}\n\ninterface UseSickbayRunnerOptions {\n projectPath: string;\n checks?: string[];\n quotes?: boolean;\n}\n\n/**\n * Synthesize a rolled-up SickbayReport from a MonorepoReport for use in\n * TUI panels that only understand single-project reports. Strategy: merge\n * all checks from all packages, keeping the worst score per check name.\n */\nfunction rollUpMonorepoReport(monorepo: MonorepoReport): SickbayReport {\n const byId = new Map<string, CheckResult>();\n for (const pkg of monorepo.packages) {\n for (const check of pkg.checks) {\n const existing = byId.get(check.id);\n if (!existing || check.score < existing.score) {\n byId.set(check.id, check);\n }\n }\n }\n const checks = Array.from(byId.values());\n return {\n timestamp: monorepo.timestamp,\n projectPath: monorepo.rootPath,\n projectInfo: {\n name: `monorepo (${monorepo.packages.length} packages)`,\n version: '0.0.0',\n hasTypeScript: false,\n hasESLint: false,\n hasPrettier: false,\n framework: 'node',\n packageManager: monorepo.packageManager,\n totalDependencies: 0,\n dependencies: {},\n devDependencies: {},\n },\n checks,\n overallScore: monorepo.overallScore,\n summary: buildSummary(checks),\n };\n}\n\nexport function useSickbayRunner({ projectPath, checks, quotes }: UseSickbayRunnerOptions) {\n const [report, setReport] = useState<SickbayReport | null>(null);\n const [monorepoReport, setMonorepoReport] = useState<MonorepoReport | null>(null);\n const [isScanning, setIsScanning] = useState(false);\n const [progress, setProgress] = useState<ProgressItem[]>([]);\n const [error, setError] = useState<string | null>(null);\n const scanningRef = useRef(false);\n\n const scan = useCallback(async () => {\n if (scanningRef.current) return null;\n scanningRef.current = true;\n setIsScanning(true);\n setError(null);\n\n try {\n const monorepoInfo = await detectMonorepo(projectPath);\n\n if (monorepoInfo.isMonorepo) {\n const result = await runSickbayMonorepo({\n projectPath,\n checks,\n quotes,\n onPackageStart: (name) => {\n setProgress((prev) => [...prev, { name, status: 'running' }]);\n },\n onPackageComplete: (pkg) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === pkg.name ? { ...p, status: 'done' } : p)),\n );\n },\n });\n\n setMonorepoReport(result);\n const rolledUp = rollUpMonorepoReport(result);\n setReport(rolledUp);\n\n setIsScanning(false);\n scanningRef.current = false;\n return rolledUp;\n }\n\n const result = await runSickbay({\n projectPath,\n checks,\n quotes,\n onRunnersReady: (names) => {\n setProgress(names.map((name) => ({ name, status: 'pending' as const })));\n },\n onCheckStart: (name) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === name ? { ...p, status: 'running' } : p)),\n );\n },\n onCheckComplete: (check) => {\n setProgress((prev) =>\n prev.map((p) => (p.name === check.id ? { ...p, status: 'done' } : p)),\n );\n },\n });\n\n setMonorepoReport(null);\n setReport(result);\n\n // Save to trend history\n try {\n const { saveEntry } = await import('../../../lib/history.js');\n saveEntry(result);\n } catch {\n // Non-critical\n }\n\n setIsScanning(false);\n scanningRef.current = false;\n return result;\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n setIsScanning(false);\n scanningRef.current = false;\n return null;\n }\n }, [projectPath, checks, quotes]);\n\n return { report, monorepoReport, isScanning, progress, error, scan };\n}\n","import { useState, useEffect } from 'react';\n\nexport function useTerminalSize() {\n const [size, setSize] = useState({\n columns: process.stdout.columns || 80,\n rows: process.stdout.rows || 24,\n });\n\n useEffect(() => {\n const handler = () => {\n setSize({\n columns: process.stdout.columns || 80,\n rows: process.stdout.rows || 24,\n });\n };\n process.stdout.on('resize', handler);\n return () => {\n process.stdout.off('resize', handler);\n };\n }, []);\n\n return size;\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nexport type PanelId = 'health' | 'git' | 'trend' | 'quickwins' | 'activity';\n\ninterface HotkeyBarProps {\n activePanel: PanelId | null;\n}\n\nconst HOTKEYS: Array<{ key: string; label: string; panel?: PanelId }> = [\n { key: 'h', label: 'health', panel: 'health' },\n { key: 'g', label: 'git', panel: 'git' },\n { key: 't', label: 'trend', panel: 'trend' },\n { key: 'q', label: 'quick wins', panel: 'quickwins' },\n { key: 'a', label: 'activity', panel: 'activity' },\n { key: 'r', label: 're-run' },\n { key: 'w', label: 'web' },\n { key: 'W', label: 'web+AI' },\n { key: '?', label: 'help' },\n];\n\nexport function HotkeyBar({ activePanel }: HotkeyBarProps) {\n return (\n <Box>\n {HOTKEYS.map((hk) => {\n const isActive = hk.panel !== undefined && hk.panel === activePanel;\n return (\n <Box key={hk.key} marginRight={2}>\n <Text bold={isActive} color={isActive ? 'cyan' : undefined}>\n [{hk.key}]\n </Text>\n <Text bold={isActive} color={isActive ? 'cyan' : 'gray'}>\n {' '}\n {hk.label}\n </Text>\n </Box>\n );\n })}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport type { MonorepoReport, PackageReport } from 'sickbay-core';\n\ninterface MonorepoPanelProps {\n report: MonorepoReport | null;\n availableWidth?: number;\n}\n\nfunction scoreBar(score: number, width = 8): string {\n const filled = Math.round((score / 100) * width);\n return '█'.repeat(filled) + '░'.repeat(width - filled);\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nexport function MonorepoPanel({ report, availableWidth }: MonorepoPanelProps) {\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const nameWidth = Math.max(12, Math.min(20, (availableWidth ?? 36) - 22));\n\n return (\n <Box flexDirection=\"column\">\n <Box marginBottom={1}>\n <Text bold>{report.packages.length} packages</Text>\n <Text dimColor> · </Text>\n <Text dimColor>{report.monorepoType} workspaces</Text>\n <Text dimColor> · </Text>\n <Text color={scoreColor(report.overallScore)} bold>\n {report.overallScore}\n </Text>\n <Text dimColor> avg</Text>\n </Box>\n\n {report.packages.map((pkg: PackageReport) => {\n const name = pkg.name.includes('/') ? (pkg.name.split('/').pop() ?? pkg.name) : pkg.name;\n const truncatedName =\n name.length > nameWidth\n ? name.slice(0, nameWidth - 1) + '\\u2026'\n : name.padEnd(nameWidth);\n\n return (\n <Box key={pkg.path} gap={1}>\n <Text color={scoreColor(pkg.score)}>{scoreBar(pkg.score)}</Text>\n <Text color={scoreColor(pkg.score)} bold>\n {String(pkg.score).padStart(3)}\n </Text>\n <Text>{truncatedName}</Text>\n {pkg.summary.critical > 0 && <Text color=\"red\"> ⚠</Text>}\n </Box>\n );\n })}\n\n <Box marginTop={1}>\n <Text dimColor>Press </Text>\n <Text color=\"cyan\">[w]</Text>\n <Text dimColor> for per-package web view</Text>\n </Box>\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\ninterface PanelBorderProps {\n title: string;\n color: string;\n focused?: boolean;\n visible?: boolean;\n flash?: 'green' | 'red';\n children: React.ReactNode;\n}\n\nexport function PanelBorder({\n title,\n color,\n focused,\n visible = true,\n flash,\n children,\n}: PanelBorderProps) {\n const borderColor = flash ?? (focused ? color : 'gray');\n const borderStyle = flash ? 'double' : focused ? 'double' : 'single';\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle={borderStyle}\n borderColor={borderColor}\n paddingX={1}\n flexGrow={1}\n >\n <Text bold color={flash ?? color}>\n {title}\n </Text>\n {visible ? children : <Text dimColor>···</Text>}\n </Box>\n );\n}\n","import React from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport type { SickbayReport } from 'sickbay-core';\n\ninterface QuickWinsPanelProps {\n report: SickbayReport | null;\n availableWidth?: number;\n}\n\nfunction replacePackageManager(cmd: string, pm: string): string {\n if (pm === 'npm') return cmd;\n const install = pm === 'pnpm' ? 'pnpm add' : pm === 'yarn' ? 'yarn add' : 'bun add';\n const uninstall = pm === 'pnpm' ? 'pnpm remove' : pm === 'yarn' ? 'yarn remove' : 'bun remove';\n const update = pm === 'pnpm' ? 'pnpm update' : pm === 'yarn' ? 'yarn upgrade' : 'bun update';\n const auditFix =\n pm === 'pnpm' ? 'pnpm audit --fix' : pm === 'yarn' ? 'yarn npm audit --fix' : 'bun audit';\n return cmd\n .replace(/^npm install(?=\\s)/, install)\n .replace(/^npm uninstall(?=\\s)/, uninstall)\n .replace(/^npm update(?=\\s)/, update)\n .replace(/^npm audit fix/, auditFix);\n}\n\nfunction shortenPath(filepath: string): string {\n const parts = filepath.split('/');\n if (parts.length <= 2) return filepath;\n return '\\u2026/' + parts.slice(-2).join('/');\n}\n\nfunction smartTruncate(str: string, maxLen: number): string {\n if (str.length <= maxLen) return str;\n // If it contains a file path, shorten the path but keep the filename\n const pathMatch = str.match(/(\\S*\\/\\S+)/);\n if (pathMatch) {\n const shortened = str.replace(pathMatch[1], shortenPath(pathMatch[1]));\n if (shortened.length <= maxLen) return shortened;\n return shortened.slice(0, maxLen - 1) + '\\u2026';\n }\n return str.slice(0, maxLen - 1) + '\\u2026';\n}\n\nfunction shortenCommand(cmd: string, maxLen: number): string {\n if (cmd.length <= maxLen) return cmd;\n // Shorten any file paths in the command first\n const shortened = cmd.replace(/(\\S*\\/\\S+)/g, (match) => shortenPath(match));\n if (shortened.length <= maxLen) return shortened;\n return shortened.slice(0, maxLen - 1) + '\\u2026';\n}\n\nexport function QuickWinsPanel({ report, availableWidth }: QuickWinsPanelProps) {\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const fixes: Array<{\n description: string;\n command: string;\n severity: string;\n }> = [];\n\n for (const check of report.checks) {\n for (const issue of check.issues) {\n if (issue.fix?.command) {\n fixes.push({\n description: issue.fix.description,\n command: issue.fix.command,\n severity: issue.severity,\n });\n }\n }\n }\n\n const order: Record<string, number> = { critical: 0, warning: 1, info: 2 };\n fixes.sort((a, b) => (order[a.severity] ?? 3) - (order[b.severity] ?? 3));\n const top = fixes.slice(0, 5);\n\n if (top.length === 0) {\n return (\n <Box>\n <Text color=\"green\">Looking good!</Text>\n </Box>\n );\n }\n\n const pm = report.projectInfo?.packageManager ?? 'npm';\n const maxTextLen = Math.max(16, (availableWidth ?? 26) - 4);\n\n return (\n <Box flexDirection=\"column\">\n {top.map((fix, i) => (\n <Box key={i} flexDirection=\"column\" marginBottom={i < top.length - 1 ? 1 : 0}>\n <Text>\n <Text\n color={\n fix.severity === 'critical' ? 'red' : fix.severity === 'warning' ? 'yellow' : 'gray'\n }\n >\n {'\\u2192'}{' '}\n </Text>\n {smartTruncate(fix.description, maxTextLen)}\n </Text>\n <Text dimColor>\n {' '}\n {shortenCommand(replacePackageManager(fix.command, pm), maxTextLen)}\n </Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import React, { useState, useEffect, useRef } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { countUniqueIssues } from '../../lib/issue-grouping.js';\n\nimport type { SickbayReport } from 'sickbay-core';\n\ninterface ScorePanelProps {\n report: SickbayReport | null;\n previousScore: number | null;\n animate?: boolean;\n}\n\nfunction scoreColor(score: number): string {\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nfunction scoreBar(score: number, width = 15): string {\n const filled = Math.round((score / 100) * width);\n return '\\u2588'.repeat(filled) + '\\u2591'.repeat(width - filled);\n}\n\nexport function ScorePanel({ report, previousScore, animate = true }: ScorePanelProps) {\n const [animatedScore, setAnimatedScore] = useState(0);\n const prevTargetRef = useRef(0);\n\n useEffect(() => {\n if (!animate) return;\n\n if (!report) {\n setAnimatedScore(0);\n prevTargetRef.current = 0;\n return;\n }\n\n const target = report.overallScore;\n const start = prevTargetRef.current;\n prevTargetRef.current = target;\n\n if (start === target) return;\n\n let current = start;\n const step = target > start ? 1 : -1;\n\n const id = setInterval(() => {\n current += step;\n setAnimatedScore(current);\n if (current === target) clearInterval(id);\n }, 20);\n\n return () => clearInterval(id);\n }, [report, animate]);\n\n // When animate=false, render score directly from prop (no state update needed)\n const displayScore = animate ? animatedScore : (report?.overallScore ?? 0);\n\n if (!report) {\n return (\n <Box>\n <Text dimColor>Waiting for scan...</Text>\n </Box>\n );\n }\n\n const delta = previousScore !== null ? report.overallScore - previousScore : null;\n const u = countUniqueIssues(report.checks);\n\n return (\n <Box flexDirection=\"column\">\n <Box>\n <Text color={scoreColor(displayScore)} bold>\n {displayScore}/100\n </Text>\n <Text> </Text>\n <Text color={scoreColor(displayScore)}>{scoreBar(displayScore)}</Text>\n </Box>\n {delta !== null && (\n <Text dimColor>\n {delta > 0 ? `+${delta}` : delta === 0 ? '\\u00B10' : `${delta}`} since last scan\n </Text>\n )}\n <Box marginTop={1} flexDirection=\"column\">\n <Box>\n <Text color=\"red\">\n {'\\u2717'} {u.critical} critical\n {u.totalCritical > u.critical ? ` (${u.totalCritical})` : ''}\n </Text>\n <Text>{' '}</Text>\n <Text color=\"yellow\">\n {'\\u26A0'} {u.warnings} warn\n {u.totalWarnings > u.warnings ? ` (${u.totalWarnings})` : ''}\n </Text>\n <Text>{' '}</Text>\n <Text dimColor>i {u.info} info</Text>\n </Box>\n </Box>\n {report.quote && (\n <Box marginTop={1}>\n <Text italic dimColor>\n \"{report.quote.text}\" — {report.quote.source}\n </Text>\n </Box>\n )}\n {report.config?.hasCustomConfig && (\n <Text dimColor italic>\n Custom config\n </Text>\n )}\n </Box>\n );\n}\n","import React, { useState, useEffect } from 'react';\n\nimport { Box, Text } from 'ink';\n\nimport { sparkline, trendArrow } from '../../commands/trend.js';\nimport { loadHistory } from '../../lib/history.js';\n\ninterface TrendPanelProps {\n projectPath: string;\n lastScanTime: Date | null;\n availableHeight?: number;\n}\n\nconst CATEGORY_COLORS: Record<string, string> = {\n overall: 'white',\n dependencies: 'blue',\n security: 'green',\n 'code-quality': 'yellow',\n performance: 'magenta',\n git: 'cyan',\n};\n\nconst SHORT_LABELS: Record<string, string> = {\n dependencies: 'Deps',\n security: 'Security',\n 'code-quality': 'Quality',\n performance: 'Perf',\n git: 'Git',\n};\n\nexport function TrendPanel({ projectPath, lastScanTime, availableHeight }: TrendPanelProps) {\n const [trends, setTrends] = useState<\n Array<{\n label: string;\n spark: string;\n latest: number;\n arrow: string;\n color: string;\n }>\n >([]);\n\n useEffect(() => {\n const history = loadHistory(projectPath);\n if (!history || history.entries.length === 0) {\n setTrends([]);\n return;\n }\n\n const entries = history.entries.slice(-10);\n const overallValues = entries.map((e) => e.overallScore);\n const overallArrow = trendArrow(overallValues);\n\n const result: typeof trends = [\n {\n label: 'Overall',\n spark: sparkline(overallValues),\n latest: overallValues[overallValues.length - 1],\n arrow: overallArrow.label,\n color: 'white',\n },\n ];\n\n const categories = Object.keys(entries[entries.length - 1].categoryScores);\n for (const cat of categories) {\n const values = entries\n .map((e) => e.categoryScores[cat])\n .filter((v): v is number => v !== undefined);\n\n if (values.length === 0) continue;\n\n const arrow = trendArrow(values);\n result.push({\n label: SHORT_LABELS[cat] || cat.charAt(0).toUpperCase() + cat.slice(1),\n spark: sparkline(values),\n latest: values[values.length - 1],\n arrow: arrow.label,\n color: CATEGORY_COLORS[cat] || 'white',\n });\n }\n\n setTrends(result);\n }, [projectPath, lastScanTime]);\n\n if (trends.length === 0) {\n return (\n <Box>\n <Text dimColor>No trend data yet.</Text>\n </Box>\n );\n }\n\n const maxRows = availableHeight ?? trends.length;\n const visible = trends.slice(0, maxRows);\n\n return (\n <Box flexDirection=\"column\">\n {visible.map((t) => (\n <Box key={t.label}>\n <Box width={10}>\n <Text color={t.color}>{t.label}</Text>\n </Box>\n <Text>{t.spark}</Text>\n <Text bold> {String(t.latest).padStart(3)}</Text>\n <Text> {t.arrow}</Text>\n </Box>\n ))}\n </Box>\n );\n}\n","import React, { useState, useEffect, useCallback, useRef } from 'react';\n\nimport { Box, Text, useInput } from 'ink';\n\nimport { checkForUpdate } from '../../lib/update-check.js';\nimport { UpdateNotice } from '../UpdateNotice.js';\nimport { ActivityPanel, type ActivityEntry } from './ActivityPanel.js';\nimport { GitPanel } from './GitPanel.js';\nimport { HealthPanel } from './HealthPanel.js';\nimport { HelpPanel } from './HelpPanel.js';\nimport { useFileWatcher } from './hooks/useFileWatcher.js';\nimport { useSickbayRunner } from './hooks/useSickbayRunner.js';\nimport { useTerminalSize } from './hooks/useTerminalSize.js';\nimport { HotkeyBar, type PanelId } from './HotkeyBar.js';\nimport { MonorepoPanel } from './MonorepoPanel.js';\nimport { PanelBorder } from './PanelBorder.js';\nimport { QuickWinsPanel } from './QuickWinsPanel.js';\nimport { ScorePanel } from './ScorePanel.js';\nimport { TrendPanel } from './TrendPanel.js';\n\nimport type { UpdateInfo } from '../../lib/update-check.js';\nimport type { SickbayReport, MonorepoReport } from 'sickbay-core';\n\ndeclare const __VERSION__: string;\n\ninterface TuiAppProps {\n projectPath: string;\n checks?: string[];\n watchEnabled: boolean;\n refreshInterval: number;\n quotes?: boolean;\n animateOnMount?: boolean;\n}\n\nexport function TuiApp({\n projectPath,\n checks,\n watchEnabled,\n refreshInterval,\n quotes,\n animateOnMount = true,\n}: TuiAppProps) {\n const { rows, columns } = useTerminalSize();\n const { report, monorepoReport, isScanning, progress, scan } = useSickbayRunner({\n projectPath,\n checks,\n quotes,\n });\n\n const [focusedPanel, setFocusedPanel] = useState<PanelId | null>(null);\n const [expandedPanel, setExpandedPanel] = useState<PanelId | null>(null);\n const [showHelp, setShowHelp] = useState(false);\n const [lastScanTime, setLastScanTime] = useState<Date | null>(null);\n const [previousScore, setPreviousScore] = useState<number | null>(null);\n const [activityLog, setActivityLog] = useState<ActivityEntry[]>([]);\n const [healthScrollOffset, setHealthScrollOffset] = useState(0);\n const [scoreFlash, setScoreFlash] = useState<'green' | 'red' | undefined>();\n const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null);\n const ALL_PANELS = new Set(['health', 'score', 'trend', 'git', 'quickwins', 'activity']);\n const [visiblePanels, setVisiblePanels] = useState<Set<string>>(\n animateOnMount ? new Set() : ALL_PANELS,\n );\n const refreshRef = useRef<ReturnType<typeof setInterval> | undefined>(undefined);\n\n // Panel entrance animation — stagger panels appearing on mount\n useEffect(() => {\n const schedule: Array<[string, number]> = [\n ['health', 100],\n ['score', 300],\n ['trend', 500],\n ['git', 700],\n ['quickwins', 900],\n ['activity', 1100],\n ];\n const timers = schedule.map(([panel, delay]) =>\n setTimeout(() => {\n setVisiblePanels((prev) => new Set([...prev, panel]));\n }, delay),\n );\n return () => timers.forEach(clearTimeout);\n }, []);\n\n useEffect(() => {\n checkForUpdate(__VERSION__).then((info) => {\n if (info) setUpdateInfo(info);\n });\n }, []);\n\n const reportRef = useRef<SickbayReport | null>(null);\n const monorepoReportRef = useRef<MonorepoReport | null>(null);\n\n // Keep refs in sync\n useEffect(() => {\n reportRef.current = report;\n }, [report]);\n\n useEffect(() => {\n monorepoReportRef.current = monorepoReport;\n }, [monorepoReport]);\n\n const addActivity = useCallback((type: ActivityEntry['type'], message: string) => {\n setActivityLog((prev) => [...prev, { timestamp: new Date(), type, message }]);\n }, []);\n\n const handleScanComplete = useCallback(\n async (result: SickbayReport) => {\n const prevScore = reportRef.current?.overallScore ?? null;\n if (prevScore !== null) setPreviousScore(prevScore);\n setLastScanTime(new Date());\n\n // Auto-save last report snapshot\n try {\n const { saveLastReport } = await import('../../lib/history.js');\n saveLastReport(result);\n } catch {\n // Non-critical\n }\n\n // Cache dependency tree for web dashboard\n try {\n const { getDependencyTree } = await import('sickbay-core');\n const { saveDepTree } = await import('../../lib/history.js');\n const tree = await getDependencyTree(projectPath, result.projectInfo.packageManager);\n saveDepTree(projectPath, tree);\n } catch {\n /* dep tree is optional */\n }\n\n const delta = prevScore !== null ? result.overallScore - prevScore : null;\n addActivity(\n 'scan-complete',\n `Scan complete: ${result.overallScore}/100${delta !== null ? ` (${delta >= 0 ? '+' : ''}${delta})` : ''}`,\n );\n\n // Flash score panel border on score change\n if (delta !== null && delta !== 0) {\n const flash = delta > 0 ? 'green' : 'red';\n setScoreFlash(flash);\n setTimeout(() => setScoreFlash(undefined), 600);\n }\n\n // Check for regressions\n try {\n const { loadHistory, detectRegressions } = await import('../../lib/history.js');\n const history = loadHistory(projectPath);\n if (history) {\n const regressions = detectRegressions(history.entries);\n for (const reg of regressions) {\n addActivity(\n 'regression',\n `\\u26A0 ${reg.category} regressed: ${reg.from} \\u2192 ${reg.to}`,\n );\n }\n }\n } catch {\n // Non-critical\n }\n },\n [addActivity, projectPath],\n );\n\n // Initial scan\n useEffect(() => {\n addActivity('info', 'TUI started');\n addActivity('scan-start', 'Starting initial health scan...');\n scan().then((result) => {\n if (result) handleScanComplete(result);\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Auto-refresh timer\n useEffect(() => {\n if (refreshInterval <= 0) return;\n refreshRef.current = setInterval(async () => {\n addActivity('scan-start', 'Auto-scan triggered');\n const result = await scan();\n if (result) handleScanComplete(result);\n }, refreshInterval * 1000);\n\n return () => {\n if (refreshRef.current) clearInterval(refreshRef.current);\n };\n }, [refreshInterval, scan, handleScanComplete, addActivity]);\n\n // File watcher\n useFileWatcher({\n projectPath,\n enabled: watchEnabled,\n onFilesChanged: async (files) => {\n for (const f of files.slice(0, 3)) {\n addActivity('file-change', `File changed: ${f}`);\n }\n if (files.length > 3) {\n addActivity('file-change', `...and ${files.length - 3} more files`);\n }\n addActivity('scan-start', 'Re-scan triggered (file change)');\n const result = await scan();\n if (result) handleScanComplete(result);\n },\n });\n\n // Manual re-scan\n const triggerRescan = useCallback(async () => {\n addActivity('scan-start', 'Manual re-scan triggered');\n const result = await scan();\n if (result) handleScanComplete(result);\n }, [addActivity, scan, handleScanComplete]);\n\n // Keyboard input — only active when stdin supports raw mode (interactive TTY)\n const isTTY = !!(process.stdin.isTTY && process.stdin.setRawMode);\n useInput(\n (input, key) => {\n // Help overlay: ? toggles, Escape closes\n if (showHelp) {\n if (input === '?' || key.escape) {\n setShowHelp(false);\n }\n return;\n }\n\n // Expanded panel: escape or f to exit\n if (expandedPanel) {\n if (key.escape || input === 'f') {\n setExpandedPanel(null);\n return;\n }\n }\n\n if (input === '?') {\n setShowHelp(true);\n return;\n }\n\n // Panel focus toggles\n if (input === 'h') setFocusedPanel((prev) => (prev === 'health' ? null : 'health'));\n else if (input === 'g') setFocusedPanel((prev) => (prev === 'git' ? null : 'git'));\n else if (input === 't') setFocusedPanel((prev) => (prev === 'trend' ? null : 'trend'));\n else if (input === 'q')\n setFocusedPanel((prev) => (prev === 'quickwins' ? null : 'quickwins'));\n else if (input === 'a') setFocusedPanel((prev) => (prev === 'activity' ? null : 'activity'));\n else if (input === 'r') triggerRescan();\n else if (input === 'f' && focusedPanel) setExpandedPanel(focusedPanel);\n else if (input === 'w' || input === 'W') {\n const withAI = input === 'W';\n (async () => {\n const webReport = monorepoReportRef.current ?? reportRef.current;\n if (!webReport) return;\n try {\n const { serveWeb } = await import('../../commands/web.js');\n const { default: openBrowser } = await import('open');\n\n let aiService;\n if (withAI && process.env.ANTHROPIC_API_KEY && !monorepoReportRef.current) {\n const { createAIService } = await import('../../services/ai.js');\n aiService = createAIService(process.env.ANTHROPIC_API_KEY);\n addActivity('info', 'Launching web dashboard with AI...');\n } else {\n addActivity('info', 'Launching web dashboard...');\n }\n\n const url = await serveWeb(webReport, 3030, aiService);\n await openBrowser(url);\n addActivity(\n 'info',\n `Web dashboard at ${url}${withAI && aiService ? ' (AI enabled)' : ''}`,\n );\n } catch (err) {\n addActivity('info', `Failed to open web: ${err instanceof Error ? err.message : err}`);\n }\n })();\n } else if (key.escape) {\n setFocusedPanel(null);\n }\n\n // Scrolling in focused health panel\n if (focusedPanel === 'health') {\n if (key.upArrow) setHealthScrollOffset((prev) => Math.max(0, prev - 1));\n if (key.downArrow) {\n const maxOffset = Math.max(0, visibleChecks.length - 5);\n setHealthScrollOffset((prev) => Math.min(maxOffset, prev + 1));\n }\n }\n },\n { isActive: isTTY },\n );\n\n const visibleChecks = report?.checks.filter((c) => c.status !== 'skipped') ?? [];\n\n // Layout calculations — reserve 1 row for project header + 1 for hotkey bar\n const topHeight = Math.floor((rows - 2) * 0.6);\n const bottomHeight = rows - 2 - topHeight;\n\n // Help overlay\n if (showHelp) {\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexGrow={1}>\n <PanelBorder title=\"HELP\" color=\"cyan\" focused>\n <HelpPanel />\n </PanelBorder>\n </Box>\n <HotkeyBar activePanel={focusedPanel} />\n </Box>\n );\n }\n\n // Full-screen expand mode\n if (expandedPanel) {\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n <Box flexGrow={1}>\n {expandedPanel === 'health' && (\n <PanelBorder title=\"HEALTH CHECKS\" color=\"green\" focused>\n <HealthPanel\n checks={visibleChecks}\n isScanning={isScanning}\n progress={progress}\n scrollOffset={healthScrollOffset}\n availableHeight={rows - 4}\n />\n </PanelBorder>\n )}\n {expandedPanel === 'git' && (\n <PanelBorder title=\"GIT STATUS\" color=\"yellow\" focused>\n <GitPanel projectPath={projectPath} availableWidth={columns - 4} />\n </PanelBorder>\n )}\n {expandedPanel === 'trend' && (\n <PanelBorder title=\"TREND\" color=\"magenta\" focused>\n <TrendPanel projectPath={projectPath} lastScanTime={lastScanTime} />\n </PanelBorder>\n )}\n {expandedPanel === 'quickwins' && (\n <PanelBorder title={monorepoReport ? 'MONOREPO' : 'QUICK WINS'} color=\"red\" focused>\n {monorepoReport ? (\n <MonorepoPanel report={monorepoReport} />\n ) : (\n <QuickWinsPanel report={report} />\n )}\n </PanelBorder>\n )}\n {expandedPanel === 'activity' && (\n <PanelBorder title=\"ACTIVITY\" color=\"cyan\" focused>\n <ActivityPanel entries={activityLog} availableHeight={rows - 4} />\n </PanelBorder>\n )}\n </Box>\n <HotkeyBar activePanel={expandedPanel} />\n </Box>\n );\n }\n\n // Normal grid layout\n const projectName = monorepoReport\n ? `${monorepoReport.rootPath.split('/').pop()} (monorepo)`\n : (report?.projectInfo?.name ?? '—');\n const projectVersion = report?.projectInfo?.version;\n const scanLabel = lastScanTime\n ? `Last scan ${lastScanTime.toLocaleTimeString()}`\n : isScanning\n ? 'Scanning…'\n : 'Not yet scanned';\n\n return (\n <Box flexDirection=\"column\" width={columns} height={rows}>\n {updateInfo && (\n <UpdateNotice\n currentVersion={updateInfo.currentVersion}\n latestVersion={updateInfo.latestVersion}\n />\n )}\n {/* Project header */}\n <Box paddingX={1} justifyContent=\"space-between\">\n <Box gap={1}>\n <Text bold color=\"cyan\">\n SICKBAY\n </Text>\n <Text bold>{projectName}</Text>\n {projectVersion && <Text dimColor>v{projectVersion}</Text>}\n </Box>\n <Text dimColor>{scanLabel}</Text>\n </Box>\n\n {/* Top Row */}\n <Box height={topHeight}>\n <Box width=\"55%\">\n <PanelBorder\n title=\"HEALTH CHECKS\"\n color=\"green\"\n focused={focusedPanel === 'health'}\n visible={visiblePanels.has('health')}\n >\n <HealthPanel\n checks={visibleChecks}\n isScanning={isScanning}\n progress={progress}\n scrollOffset={healthScrollOffset}\n availableHeight={topHeight - 4}\n />\n </PanelBorder>\n </Box>\n <Box width=\"45%\" flexDirection=\"column\">\n <Box height=\"50%\">\n <PanelBorder\n title=\"SCORE\"\n color=\"blue\"\n visible={visiblePanels.has('score')}\n flash={scoreFlash}\n >\n <ScorePanel report={report} previousScore={previousScore} animate={animateOnMount} />\n </PanelBorder>\n </Box>\n <Box height=\"50%\">\n <PanelBorder\n title=\"TREND\"\n color=\"magenta\"\n focused={focusedPanel === 'trend'}\n visible={visiblePanels.has('trend')}\n >\n <TrendPanel\n projectPath={projectPath}\n lastScanTime={lastScanTime}\n availableHeight={Math.floor(topHeight / 2) - 3}\n />\n </PanelBorder>\n </Box>\n </Box>\n </Box>\n\n {/* Bottom Row */}\n <Box height={bottomHeight}>\n <Box width=\"25%\">\n <PanelBorder\n title=\"GIT STATUS\"\n color=\"yellow\"\n focused={focusedPanel === 'git'}\n visible={visiblePanels.has('git')}\n >\n <GitPanel projectPath={projectPath} availableWidth={Math.floor(columns * 0.25) - 4} />\n </PanelBorder>\n </Box>\n <Box width=\"30%\">\n <PanelBorder\n title={monorepoReport ? 'MONOREPO' : 'QUICK WINS'}\n color=\"red\"\n focused={focusedPanel === 'quickwins'}\n visible={visiblePanels.has('quickwins')}\n >\n {monorepoReport ? (\n <MonorepoPanel\n report={monorepoReport}\n availableWidth={Math.floor(columns * 0.3) - 4}\n />\n ) : (\n <QuickWinsPanel report={report} availableWidth={Math.floor(columns * 0.3) - 4} />\n )}\n </PanelBorder>\n </Box>\n <Box width=\"45%\">\n <PanelBorder\n title=\"ACTIVITY\"\n color=\"cyan\"\n focused={focusedPanel === 'activity'}\n visible={visiblePanels.has('activity')}\n >\n <ActivityPanel entries={activityLog} availableHeight={bottomHeight - 4} />\n </PanelBorder>\n </Box>\n </Box>\n\n {/* Hotkey Bar */}\n <HotkeyBar activePanel={focusedPanel} />\n </Box>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { join } from "path";
3
+ //#region src/commands/badge.ts
4
+ function getScoreColor(score) {
5
+ if (score >= 90) return "brightgreen";
6
+ if (score >= 80) return "green";
7
+ if (score >= 60) return "yellow";
8
+ return "red";
9
+ }
10
+ function encodeLabel(label) {
11
+ return label.replace(/-/g, "--").replace(/_/g, "__").replace(/ /g, "%20");
12
+ }
13
+ /**
14
+ * Escape characters that break out of HTML attributes when interpolated.
15
+ * `--label` is user-controlled CLI input, so embedding it in `<img alt="...">`
16
+ * without escaping turns `" onload="alert(1)` into an executable handler if
17
+ * the generated snippet is pasted into a README rendered as HTML.
18
+ */
19
+ function escapeHtml(s) {
20
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
21
+ }
22
+ /**
23
+ * Escape characters that break the `![alt](url)` syntax when interpolated
24
+ * into the alt-text position. A label like `](javascript:alert(1)` would
25
+ * otherwise close the alt early and inject a link target — some renderers
26
+ * strip `javascript:` URIs, but not all, so treat it as unsafe by default.
27
+ * Backslash is escaped first so later escapes don't double up on it.
28
+ */
29
+ function escapeMarkdownAlt(s) {
30
+ return s.replace(/\\/g, "\\\\").replace(/\[/g, "\\[").replace(/\]/g, "\\]").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
31
+ }
32
+ function badgeUrl(score, label = "sickbay") {
33
+ const color = getScoreColor(score);
34
+ return `https://img.shields.io/badge/${encodeLabel(label)}-${score}%2F100-${color}`;
35
+ }
36
+ function badgeMarkdown(score, label = "sickbay") {
37
+ return `![${escapeMarkdownAlt(label)}](${badgeUrl(score, label)})`;
38
+ }
39
+ function badgeHtml(score, label = "sickbay") {
40
+ return `<img src="${escapeHtml(badgeUrl(score, label))}" alt="${escapeHtml(label)}" />`;
41
+ }
42
+ function loadScoreFromLastReport(projectPath) {
43
+ const filePath = join(projectPath, ".sickbay", "last-report.json");
44
+ if (!existsSync(filePath)) return null;
45
+ try {
46
+ const data = JSON.parse(readFileSync(filePath, "utf-8"));
47
+ if (typeof data.overallScore !== "number") return null;
48
+ return data.overallScore;
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+ //#endregion
54
+ export { badgeHtml, badgeMarkdown, badgeUrl, loadScoreFromLastReport };
55
+
56
+ //# sourceMappingURL=badge-CTayKnYV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"badge-CTayKnYV.js","names":[],"sources":["../src/commands/badge.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\n\nexport function getScoreColor(score: number): string {\n if (score >= 90) return 'brightgreen';\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nfunction encodeLabel(label: string): string {\n return label.replace(/-/g, '--').replace(/_/g, '__').replace(/ /g, '%20');\n}\n\n/**\n * Escape characters that break out of HTML attributes when interpolated.\n * `--label` is user-controlled CLI input, so embedding it in `<img alt=\"...\">`\n * without escaping turns `\" onload=\"alert(1)` into an executable handler if\n * the generated snippet is pasted into a README rendered as HTML.\n */\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&amp;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;');\n}\n\n/**\n * Escape characters that break the `![alt](url)` syntax when interpolated\n * into the alt-text position. A label like `](javascript:alert(1)` would\n * otherwise close the alt early and inject a link target — some renderers\n * strip `javascript:` URIs, but not all, so treat it as unsafe by default.\n * Backslash is escaped first so later escapes don't double up on it.\n */\nfunction escapeMarkdownAlt(s: string): string {\n return s\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\\[/g, '\\\\[')\n .replace(/\\]/g, '\\\\]')\n .replace(/\\(/g, '\\\\(')\n .replace(/\\)/g, '\\\\)');\n}\n\nexport function badgeUrl(score: number, label = 'sickbay'): string {\n const color = getScoreColor(score);\n const encoded = encodeLabel(label);\n return `https://img.shields.io/badge/${encoded}-${score}%2F100-${color}`;\n}\n\nexport function badgeMarkdown(score: number, label = 'sickbay'): string {\n return `![${escapeMarkdownAlt(label)}](${badgeUrl(score, label)})`;\n}\n\nexport function badgeHtml(score: number, label = 'sickbay'): string {\n return `<img src=\"${escapeHtml(badgeUrl(score, label))}\" alt=\"${escapeHtml(label)}\" />`;\n}\n\nexport function loadScoreFromLastReport(projectPath: string): number | null {\n const filePath = join(projectPath, '.sickbay', 'last-report.json');\n if (!existsSync(filePath)) return null;\n try {\n const data = JSON.parse(readFileSync(filePath, 'utf-8'));\n if (typeof data.overallScore !== 'number') return null;\n return data.overallScore;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;AAGA,SAAgB,cAAc,OAAuB;AACnD,KAAI,SAAS,GAAI,QAAO;AACxB,KAAI,SAAS,GAAI,QAAO;AACxB,KAAI,SAAS,GAAI,QAAO;AACxB,QAAO;;AAGT,SAAS,YAAY,OAAuB;AAC1C,QAAO,MAAM,QAAQ,MAAM,KAAK,CAAC,QAAQ,MAAM,KAAK,CAAC,QAAQ,MAAM,MAAM;;;;;;;;AAS3E,SAAS,WAAW,GAAmB;AACrC,QAAO,EACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO;;;;;;;;;AAU1B,SAAS,kBAAkB,GAAmB;AAC5C,QAAO,EACJ,QAAQ,OAAO,OAAO,CACtB,QAAQ,OAAO,MAAM,CACrB,QAAQ,OAAO,MAAM,CACrB,QAAQ,OAAO,MAAM,CACrB,QAAQ,OAAO,MAAM;;AAG1B,SAAgB,SAAS,OAAe,QAAQ,WAAmB;CACjE,MAAM,QAAQ,cAAc,MAAM;AAElC,QAAO,gCADS,YAAY,MAAM,CACa,GAAG,MAAM,SAAS;;AAGnE,SAAgB,cAAc,OAAe,QAAQ,WAAmB;AACtE,QAAO,KAAK,kBAAkB,MAAM,CAAC,IAAI,SAAS,OAAO,MAAM,CAAC;;AAGlE,SAAgB,UAAU,OAAe,QAAQ,WAAmB;AAClE,QAAO,aAAa,WAAW,SAAS,OAAO,MAAM,CAAC,CAAC,SAAS,WAAW,MAAM,CAAC;;AAGpF,SAAgB,wBAAwB,aAAoC;CAC1E,MAAM,WAAW,KAAK,aAAa,YAAY,mBAAmB;AAClE,KAAI,CAAC,WAAW,SAAS,CAAE,QAAO;AAClC,KAAI;EACF,MAAM,OAAO,KAAK,MAAM,aAAa,UAAU,QAAQ,CAAC;AACxD,MAAI,OAAO,KAAK,iBAAiB,SAAU,QAAO;AAClD,SAAO,KAAK;SACN;AACN,SAAO"}
package/dist/index.js CHANGED
@@ -130,7 +130,7 @@ function Header({ projectName }) {
130
130
  return /* @__PURE__ */ React.createElement(Box, {
131
131
  flexDirection: "column",
132
132
  marginBottom: 1
133
- }, /* @__PURE__ */ React.createElement(Text, { color: "green" }, ASCII_ART), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " v", "1.16.1")), projectName && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " Analyzing "), /* @__PURE__ */ React.createElement(Text, {
133
+ }, /* @__PURE__ */ React.createElement(Text, { color: "green" }, ASCII_ART), /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " v", "1.16.2")), projectName && /* @__PURE__ */ React.createElement(Box, { marginTop: 1 }, /* @__PURE__ */ React.createElement(Text, { dimColor: true }, " Analyzing "), /* @__PURE__ */ React.createElement(Text, {
134
134
  bold: true,
135
135
  color: "white"
136
136
  }, projectName)));
@@ -302,7 +302,7 @@ function App({ projectPath, checks, openWeb, enableAI, verbose, quotes, isMonore
302
302
  if (openWeb) {
303
303
  setPhase("opening-web");
304
304
  try {
305
- const { serveWeb } = await import("./web-D5etye5Q.js");
305
+ const { serveWeb } = await import("./web-CFBLfBdc.js");
306
306
  const { default: openBrowser } = await import("open");
307
307
  let aiService;
308
308
  if (enableAI && process.env.ANTHROPIC_API_KEY) {
@@ -369,7 +369,7 @@ function App({ projectPath, checks, openWeb, enableAI, verbose, quotes, isMonore
369
369
  if (openWeb) {
370
370
  setPhase("opening-web");
371
371
  try {
372
- const { serveWeb } = await import("./web-D5etye5Q.js");
372
+ const { serveWeb } = await import("./web-CFBLfBdc.js");
373
373
  const { default: openBrowser } = await import("open");
374
374
  let aiService;
375
375
  if (enableAI && process.env.ANTHROPIC_API_KEY) {
@@ -469,7 +469,7 @@ config({
469
469
  quiet: true
470
470
  });
471
471
  const program = new Command();
472
- program.name("sickbay").description("React project health check CLI").version("1.16.1", "-v, --version").enablePositionalOptions().passThroughOptions().option("-p, --path <path>", "project path to analyze", process.cwd()).option("-c, --checks <checks>", "comma-separated list of checks to run").option("--package <name>", "scope to a single named package (monorepo only)").option("--json", "output raw JSON report").option("--web", "open web dashboard after scan").option("--no-ai", "disable AI features").option("--no-quotes", "suppress personality quotes in output").option("--verbose", "show verbose output").action(async (options) => {
472
+ program.name("sickbay").description("React project health check CLI").version("1.16.2", "-v, --version").enablePositionalOptions().passThroughOptions().option("-p, --path <path>", "project path to analyze", process.cwd()).option("-c, --checks <checks>", "comma-separated list of checks to run").option("--package <name>", "scope to a single named package (monorepo only)").option("--json", "output raw JSON report").option("--web", "open web dashboard after scan").option("--no-ai", "disable AI features").option("--no-quotes", "suppress personality quotes in output").option("--verbose", "show verbose output").action(async (options) => {
473
473
  if (!existsSync(options.path)) {
474
474
  process.stderr.write(`Error: path does not exist: ${options.path}\n`);
475
475
  process.exit(1);
@@ -484,7 +484,7 @@ program.name("sickbay").description("React project health check CLI").version("1
484
484
  const updatePromise = (async () => {
485
485
  try {
486
486
  const { checkForUpdate } = await import("./update-check-C7bNNfvf.js");
487
- return await checkForUpdate("1.16.1");
487
+ return await checkForUpdate("1.16.2");
488
488
  } catch {
489
489
  return null;
490
490
  }
@@ -654,7 +654,7 @@ program.command("stats").description("Show a quick codebase overview and project
654
654
  }));
655
655
  });
656
656
  program.command("tui").description("Launch the persistent developer dashboard").option("-p, --path <path>", "project path to monitor", process.cwd()).option("--no-watch", "disable file-watching auto-refresh").option("--no-quotes", "suppress personality quotes in output").option("--refresh <seconds>", "auto-refresh interval in seconds", "300").option("-c, --checks <checks>", "comma-separated list of checks to run").action(async (options) => {
657
- const { TuiApp } = await import("./TuiApp-D4Wn81Qv.js");
657
+ const { TuiApp } = await import("./TuiApp-LHb149v8.js");
658
658
  const checks = options.checks ? options.checks.split(",").map((s) => s.trim()) : void 0;
659
659
  render(React.createElement(TuiApp, {
660
660
  projectPath: options.path,
@@ -689,7 +689,7 @@ program.command("badge").description("Generate a health score badge for your REA
689
689
  const { resolveProject } = await import("./resolve-package-DS27JBhN.js");
690
690
  const resolution = await resolveProject(options.path, options.package);
691
691
  const projectPath = resolution.isMonorepo ? resolution.targetPath ?? options.path : resolution.targetPath;
692
- const { loadScoreFromLastReport, badgeUrl, badgeMarkdown, badgeHtml } = await import("./badge-DB3y0fWt.js");
692
+ const { loadScoreFromLastReport, badgeUrl, badgeMarkdown, badgeHtml } = await import("./badge-CTayKnYV.js");
693
693
  let score = options.scan ? null : loadScoreFromLastReport(projectPath);
694
694
  if (score === null) {
695
695
  const { runSickbay } = await import("./dist-Bjhxbcak.js").then((n) => n.s);
@@ -94,6 +94,12 @@ async function serveWeb(report, preferredPort = 3030, aiService) {
94
94
  if (!distDir) throw new Error("Web dashboard assets not found. Try reinstalling: npx sickbay@latest --web");
95
95
  const reportJson = JSON.stringify(report);
96
96
  const port = await getFreePort(preferredPort);
97
+ const serverUrl = `http://localhost:${port}`;
98
+ const jsonHeaders = {
99
+ "Content-Type": "application/json",
100
+ "Access-Control-Allow-Origin": serverUrl,
101
+ Vary: "Origin"
102
+ };
97
103
  let aiSummary = null;
98
104
  if (aiService && !("isMonorepo" in report)) try {
99
105
  aiSummary = await aiService.generateSummary(report);
@@ -103,17 +109,17 @@ async function serveWeb(report, preferredPort = 3030, aiService) {
103
109
  const server = http.createServer(async (req, res) => {
104
110
  const url = req.url ?? "/";
105
111
  if (url === "/sickbay-report.json") {
106
- res.writeHead(200, { "Content-Type": "application/json" });
112
+ res.writeHead(200, jsonHeaders);
107
113
  res.end(reportJson);
108
114
  return;
109
115
  }
110
116
  if (url === "/sickbay-history.json") {
111
117
  const historyPath = join("isMonorepo" in report ? report.rootPath : report.projectPath, ".sickbay", "history.json");
112
118
  if (existsSync(historyPath)) {
113
- res.writeHead(200, { "Content-Type": "application/json" });
119
+ res.writeHead(200, jsonHeaders);
114
120
  res.end(readFileSync(historyPath, "utf-8"));
115
121
  } else {
116
- res.writeHead(404, { "Content-Type": "application/json" });
122
+ res.writeHead(404, jsonHeaders);
117
123
  res.end("{}");
118
124
  }
119
125
  return;
@@ -121,10 +127,10 @@ async function serveWeb(report, preferredPort = 3030, aiService) {
121
127
  if (url === "/sickbay-dep-tree.json") {
122
128
  const treePath = join("isMonorepo" in report ? report.rootPath : report.projectPath, ".sickbay", "dep-tree.json");
123
129
  if (existsSync(treePath)) {
124
- res.writeHead(200, { "Content-Type": "application/json" });
130
+ res.writeHead(200, jsonHeaders);
125
131
  res.end(readFileSync(treePath, "utf-8"));
126
132
  } else {
127
- res.writeHead(404, { "Content-Type": "application/json" });
133
+ res.writeHead(404, jsonHeaders);
128
134
  res.end("{}");
129
135
  }
130
136
  return;
@@ -135,14 +141,14 @@ async function serveWeb(report, preferredPort = 3030, aiService) {
135
141
  const { loadConfig } = await import("./dist-Bjhxbcak.js").then((n) => n.s);
136
142
  const config = await loadConfig(basePath);
137
143
  if (config) {
138
- res.writeHead(200, { "Content-Type": "application/json" });
144
+ res.writeHead(200, jsonHeaders);
139
145
  res.end(JSON.stringify(config));
140
146
  } else {
141
- res.writeHead(404, { "Content-Type": "application/json" });
147
+ res.writeHead(404, jsonHeaders);
142
148
  res.end("{}");
143
149
  }
144
150
  } catch {
145
- res.writeHead(500, { "Content-Type": "application/json" });
151
+ res.writeHead(500, jsonHeaders);
146
152
  res.end("{}");
147
153
  }
148
154
  return;
@@ -151,36 +157,35 @@ async function serveWeb(report, preferredPort = 3030, aiService) {
151
157
  const packageName = parsedUrl.searchParams.get("package");
152
158
  if (parsedUrl.pathname === "/ai/summary") {
153
159
  if (req.method === "HEAD") {
154
- if (aiService) res.writeHead(200);
155
- else res.writeHead(404);
160
+ res.writeHead(aiService ? 200 : 404, jsonHeaders);
156
161
  res.end();
157
162
  return;
158
163
  }
159
164
  if (packageName && aiService && "isMonorepo" in report) {
160
165
  const pkg = report.packages.find((p) => p.name === packageName);
161
166
  if (!pkg) {
162
- res.writeHead(404, { "Content-Type": "application/json" });
167
+ res.writeHead(404, jsonHeaders);
163
168
  res.end(JSON.stringify({ error: "Package not found" }));
164
169
  return;
165
170
  }
166
171
  try {
167
172
  const pkgReport = packageReportToSickbayReport(pkg, report);
168
173
  const summary = await aiService.generateSummary(pkgReport);
169
- res.writeHead(200, { "Content-Type": "application/json" });
174
+ res.writeHead(200, jsonHeaders);
170
175
  res.end(JSON.stringify({ summary }));
171
176
  } catch (err) {
172
177
  const message = err instanceof Error ? err.message : "Internal error";
173
- res.writeHead(500, { "Content-Type": "application/json" });
178
+ res.writeHead(500, jsonHeaders);
174
179
  res.end(JSON.stringify({ error: message }));
175
180
  }
176
181
  return;
177
182
  }
178
183
  if (aiSummary) {
179
- res.writeHead(200, { "Content-Type": "application/json" });
184
+ res.writeHead(200, jsonHeaders);
180
185
  res.end(JSON.stringify({ summary: aiSummary }));
181
186
  return;
182
187
  }
183
- res.writeHead(404, { "Content-Type": "application/json" });
188
+ res.writeHead(404, jsonHeaders);
184
189
  res.end("{}");
185
190
  return;
186
191
  }
@@ -193,7 +198,7 @@ async function serveWeb(report, preferredPort = 3030, aiService) {
193
198
  body += chunk;
194
199
  if (Buffer.byteLength(body) > MAX_BODY) {
195
200
  aborted = true;
196
- res.writeHead(413, { "Content-Type": "application/json" });
201
+ res.writeHead(413, jsonHeaders);
197
202
  res.end(JSON.stringify({ error: "Payload too large" }));
198
203
  req.destroy();
199
204
  }
@@ -206,23 +211,23 @@ async function serveWeb(report, preferredPort = 3030, aiService) {
206
211
  if (packageName && "isMonorepo" in report) {
207
212
  const pkg = report.packages.find((p) => p.name === packageName);
208
213
  if (!pkg) {
209
- res.writeHead(404, { "Content-Type": "application/json" });
214
+ res.writeHead(404, jsonHeaders);
210
215
  res.end(JSON.stringify({ error: "Package not found" }));
211
216
  return;
212
217
  }
213
218
  chatReport = packageReportToSickbayReport(pkg, report);
214
219
  } else if (!("isMonorepo" in report)) chatReport = report;
215
220
  else {
216
- res.writeHead(400, { "Content-Type": "application/json" });
221
+ res.writeHead(400, jsonHeaders);
217
222
  res.end(JSON.stringify({ error: "Package name required for monorepo" }));
218
223
  return;
219
224
  }
220
225
  const response = await aiService.chat(message, chatReport, history ?? []);
221
- res.writeHead(200, { "Content-Type": "application/json" });
226
+ res.writeHead(200, jsonHeaders);
222
227
  res.end(JSON.stringify({ response }));
223
228
  } catch (err) {
224
229
  const errMessage = err instanceof Error ? err.message : "Internal error";
225
- res.writeHead(500, { "Content-Type": "application/json" });
230
+ res.writeHead(500, jsonHeaders);
226
231
  res.end(JSON.stringify({ error: errMessage }));
227
232
  }
228
233
  });
@@ -249,11 +254,11 @@ async function serveWeb(report, preferredPort = 3030, aiService) {
249
254
  else reject(err);
250
255
  });
251
256
  server.listen(port, "127.0.0.1", () => {
252
- resolve(`http://localhost:${port}`);
257
+ resolve(serverUrl);
253
258
  });
254
259
  });
255
260
  }
256
261
  //#endregion
257
262
  export { serveWeb };
258
263
 
259
- //# sourceMappingURL=web-D5etye5Q.js.map
264
+ //# sourceMappingURL=web-CFBLfBdc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-CFBLfBdc.js","names":[],"sources":["../src/commands/web.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'fs';\nimport http from 'http';\nimport { join, extname, resolve, relative, isAbsolute } from 'path';\nimport { fileURLToPath } from 'url';\n\nimport type { AIService } from '../services/ai.js';\nimport type { SickbayReport, MonorepoReport } from 'sickbay-core';\n\n/**\n * This module implements a simple HTTP server to serve a web dashboard for visualizing Sickbay reports.\n * It dynamically serves the report data as JSON and provides endpoints for AI-generated summaries and chat interactions if an AI service is available.\n * The server also serves static files from the built dashboard located in the sickbay-web package. This allows users to interact with their health reports in a user-friendly web interface.\n */\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n};\n\n/**\n * Safely resolve a URL pathname to a file path within baseDir, rejecting\n * any attempt to escape the directory via `..`, absolute paths, URL-encoded\n * traversal (`%2e%2e`), or null bytes. Returns null if the path is unsafe.\n *\n * Note that `new URL()` (used by the request handler) already normalizes\n * `..` segments from the pathname before this function sees it, so in\n * practice most traversal attempts are neutralized upstream. This function\n * is defense-in-depth — it guards against null bytes, manually constructed\n * malformed paths, and any future code path that bypasses URL normalization.\n *\n * Even though the dashboard server binds to 127.0.0.1, the dashboard exposes\n * scan results including secret-scan findings — treat it like any public\n * static server.\n *\n * Exported for direct unit testing; not part of the stable public API.\n */\nexport function resolveSafeStaticPath(baseDir: string, pathname: string): string | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n if (decoded.includes('\\0')) return null;\n // Strip leading slashes so resolve() treats the path as relative to baseDir.\n const cleaned = decoded.replace(/^\\/+/, '');\n const resolved = resolve(baseDir, cleaned);\n const rel = relative(baseDir, resolved);\n // relative() returns '' when identical, '..'-prefixed when outside, or absolute on other drives (Windows).\n if (rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))) {\n return resolved;\n }\n return null;\n}\n\nfunction findWebDist(): string | null {\n // Resolve relative to this file's location\n const thisFile = fileURLToPath(import.meta.url);\n const candidates = [\n join(thisFile, '..', 'web'), // published: dist/web/ (embedded at build time)\n join(thisFile, '..', '..', '..', 'web', 'dist'), // monorepo dev: apps/web/dist/\n ];\n for (const p of candidates) {\n if (existsSync(join(p, 'index.html'))) return p;\n }\n return null;\n}\n\nasync function getFreePort(preferred: number, attempts = 0): Promise<number> {\n if (attempts >= 10) {\n throw new Error(\n `No free port found after 10 attempts starting from port ${preferred - attempts}`,\n );\n }\n return new Promise((resolve, reject) => {\n const server = http.createServer();\n // Probe must bind to the same host as the real server (127.0.0.1), otherwise\n // Node defaults to `::` and we can miss an IPv4-loopback-only conflict —\n // the probe says \"free\", then the real listen crashes with EADDRINUSE.\n server.listen(preferred, '127.0.0.1', () => {\n const addr = server.address() as { port: number };\n server.close(() => resolve(addr.port));\n });\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n resolve(getFreePort(preferred + 1, attempts + 1));\n } else {\n reject(err);\n }\n });\n });\n}\n\nfunction packageReportToSickbayReport(\n pkg: import('sickbay-core').PackageReport,\n parent: MonorepoReport,\n): SickbayReport {\n return {\n timestamp: parent.timestamp,\n projectPath: pkg.path,\n projectInfo: {\n name: pkg.name,\n version: 'unknown',\n hasTypeScript: false,\n hasESLint: false,\n hasPrettier: false,\n framework: pkg.framework,\n packageManager: parent.packageManager,\n totalDependencies:\n Object.keys(pkg.dependencies).length + Object.keys(pkg.devDependencies).length,\n dependencies: pkg.dependencies,\n devDependencies: pkg.devDependencies,\n },\n checks: pkg.checks,\n overallScore: pkg.score,\n summary: pkg.summary,\n };\n}\n\nexport async function serveWeb(\n report: SickbayReport | MonorepoReport,\n preferredPort = 3030,\n aiService?: AIService,\n): Promise<string> {\n const distDir = findWebDist();\n if (!distDir) {\n throw new Error('Web dashboard assets not found. Try reinstalling: npx sickbay@latest --web');\n }\n\n const reportJson = JSON.stringify(report);\n const port = await getFreePort(preferredPort);\n\n // Single source of truth for the dashboard URL. The server binds to\n // 127.0.0.1 but this string (used for both the returned URL and the\n // Access-Control-Allow-Origin header) must stay consistent — browser\n // origins are derived from the URL string the tab was opened at, not\n // from the resolved IP, so mixing `localhost` in one place and\n // `127.0.0.1` in another would produce a mismatch that only bites\n // when the user navigates via a different loopback representation.\n const serverUrl = `http://localhost:${port}`;\n\n // Lock API responses to the dashboard's own origin. Without this header,\n // browser default behavior is already to block cross-origin reads of the\n // response body (so an evil.com tab couldn't exfiltrate a report), but\n // declaring it explicitly is defense-in-depth: it makes the intent clear,\n // prevents future browser-policy relaxations from widening the surface,\n // and makes the server's contract auditable from response headers alone.\n const jsonHeaders = {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': serverUrl,\n Vary: 'Origin',\n };\n\n // Generate AI summary on startup if service is available (single-project only)\n let aiSummary: string | null = null;\n if (aiService && !('isMonorepo' in report)) {\n try {\n aiSummary = await aiService.generateSummary(report);\n } catch (err) {\n console.warn('AI summary generation failed:', err);\n }\n }\n\n const server = http.createServer(async (req, res) => {\n const url = req.url ?? '/';\n\n // Serve the report JSON directly from memory\n if (url === '/sickbay-report.json') {\n res.writeHead(200, jsonHeaders);\n res.end(reportJson);\n return;\n }\n\n // Serve project-local history\n if (url === '/sickbay-history.json') {\n const basePath = 'isMonorepo' in report ? report.rootPath : report.projectPath;\n const historyPath = join(basePath, '.sickbay', 'history.json');\n if (existsSync(historyPath)) {\n res.writeHead(200, jsonHeaders);\n res.end(readFileSync(historyPath, 'utf-8'));\n } else {\n res.writeHead(404, jsonHeaders);\n res.end('{}');\n }\n return;\n }\n\n // Serve dependency tree\n if (url === '/sickbay-dep-tree.json') {\n const basePath = 'isMonorepo' in report ? report.rootPath : report.projectPath;\n const treePath = join(basePath, '.sickbay', 'dep-tree.json');\n if (existsSync(treePath)) {\n res.writeHead(200, jsonHeaders);\n res.end(readFileSync(treePath, 'utf-8'));\n } else {\n res.writeHead(404, jsonHeaders);\n res.end('{}');\n }\n return;\n }\n\n // Serve project config\n if (url === '/sickbay-config.json') {\n const basePath = 'isMonorepo' in report ? report.rootPath : report.projectPath;\n try {\n const { loadConfig } = await import('sickbay-core');\n const config = await loadConfig(basePath);\n if (config) {\n res.writeHead(200, jsonHeaders);\n res.end(JSON.stringify(config));\n } else {\n res.writeHead(404, jsonHeaders);\n res.end('{}');\n }\n } catch {\n res.writeHead(500, jsonHeaders);\n res.end('{}');\n }\n return;\n }\n\n // AI summary endpoint\n const parsedUrl = new URL(url, 'http://localhost');\n const packageName = parsedUrl.searchParams.get('package');\n\n if (parsedUrl.pathname === '/ai/summary') {\n if (req.method === 'HEAD') {\n // Availability check. Include CORS headers for consistency with the\n // rest of the JSON endpoints even though opaque cross-origin responses\n // already hide the status from attacker scripts.\n res.writeHead(aiService ? 200 : 404, jsonHeaders);\n res.end();\n return;\n }\n if (packageName && aiService && 'isMonorepo' in report) {\n // Per-package summary: generate on demand\n const pkg = report.packages.find((p) => p.name === packageName);\n if (!pkg) {\n res.writeHead(404, jsonHeaders);\n res.end(JSON.stringify({ error: 'Package not found' }));\n return;\n }\n try {\n const pkgReport = packageReportToSickbayReport(pkg, report);\n const summary = await aiService.generateSummary(pkgReport);\n res.writeHead(200, jsonHeaders);\n res.end(JSON.stringify({ summary }));\n } catch (err) {\n // Don't leak internal error details (stack, constructor name, dep internals)\n // over the wire, even on loopback — other processes on the machine can reach it.\n const message = err instanceof Error ? err.message : 'Internal error';\n res.writeHead(500, jsonHeaders);\n res.end(JSON.stringify({ error: message }));\n }\n return;\n }\n if (aiSummary) {\n res.writeHead(200, jsonHeaders);\n res.end(JSON.stringify({ summary: aiSummary }));\n return;\n }\n res.writeHead(404, jsonHeaders);\n res.end('{}');\n return;\n }\n\n // AI chat endpoint\n if (parsedUrl.pathname === '/ai/chat' && req.method === 'POST' && aiService) {\n // Cap request body to prevent unbounded memory growth from a malicious\n // or misbehaving client on the loopback interface.\n const MAX_BODY = 1024 * 1024; // 1 MB\n let body = '';\n let aborted = false;\n req.on('data', (chunk) => {\n if (aborted) return;\n body += chunk;\n if (Buffer.byteLength(body) > MAX_BODY) {\n aborted = true;\n res.writeHead(413, jsonHeaders);\n res.end(JSON.stringify({ error: 'Payload too large' }));\n req.destroy();\n }\n });\n req.on('end', async () => {\n if (aborted) return;\n try {\n const { message, history } = JSON.parse(body);\n let chatReport: SickbayReport;\n if (packageName && 'isMonorepo' in report) {\n const pkg = report.packages.find((p) => p.name === packageName);\n if (!pkg) {\n res.writeHead(404, jsonHeaders);\n res.end(JSON.stringify({ error: 'Package not found' }));\n return;\n }\n chatReport = packageReportToSickbayReport(pkg, report);\n } else if (!('isMonorepo' in report)) {\n chatReport = report;\n } else {\n res.writeHead(400, jsonHeaders);\n res.end(JSON.stringify({ error: 'Package name required for monorepo' }));\n return;\n }\n const response = await aiService.chat(message, chatReport, history ?? []);\n res.writeHead(200, jsonHeaders);\n res.end(JSON.stringify({ response }));\n } catch (err) {\n // See above — no internal error leakage.\n const errMessage = err instanceof Error ? err.message : 'Internal error';\n res.writeHead(500, jsonHeaders);\n res.end(JSON.stringify({ error: errMessage }));\n }\n });\n return;\n }\n\n // Serve static files from dist. Use the parsed pathname (not the raw url)\n // so query strings are stripped, and route through resolveSafeStaticPath\n // to prevent path traversal (e.g. `/../../etc/passwd`, `%2e%2e/`).\n const requestedPath = parsedUrl.pathname === '/' ? 'index.html' : parsedUrl.pathname;\n const filePath = resolveSafeStaticPath(distDir, requestedPath);\n if (filePath && existsSync(filePath)) {\n const ext = extname(filePath);\n const mime = MIME_TYPES[ext] ?? 'application/octet-stream';\n res.writeHead(200, { 'Content-Type': mime });\n res.end(readFileSync(filePath));\n } else {\n // SPA fallback — serve index.html for all unknown routes\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(readFileSync(join(distDir, 'index.html')));\n }\n });\n\n return new Promise((resolve, reject) => {\n // Close server on SIGINT/SIGTERM so the process can exit cleanly when\n // the user presses Ctrl+C. Without this the HTTP handle keeps the event\n // loop alive after Ink's render loop exits.\n const shutdown = () => server.close();\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n\n // Reject the promise on any listen failure (EADDRINUSE, EACCES, etc.)\n // instead of letting an unhandled 'error' event crash the process with a\n // raw Node stack trace. The CLI renders rejection messages nicely.\n server.once('error', (err: NodeJS.ErrnoException) => {\n process.off('SIGINT', shutdown);\n process.off('SIGTERM', shutdown);\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`Port ${port} is already in use. Stop the other process or try again.`));\n } else {\n reject(err);\n }\n });\n\n // SECURITY: bind explicitly to loopback so the dashboard (which contains\n // file paths, dep lists, and secret-scan results) is not exposed to other\n // hosts on the LAN, container networks, or VPN peers.\n server.listen(port, '127.0.0.1', () => {\n resolve(serverUrl);\n });\n });\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sickbay",
3
- "version": "1.16.2",
3
+ "version": "1.16.3",
4
4
  "description": "Zero-config health check CLI for JavaScript and TypeScript projects",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,37 +0,0 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
3
- //#region src/commands/badge.ts
4
- function getScoreColor(score) {
5
- if (score >= 90) return "brightgreen";
6
- if (score >= 80) return "green";
7
- if (score >= 60) return "yellow";
8
- return "red";
9
- }
10
- function encodeLabel(label) {
11
- return label.replace(/-/g, "--").replace(/_/g, "__").replace(/ /g, "%20");
12
- }
13
- function badgeUrl(score, label = "sickbay") {
14
- const color = getScoreColor(score);
15
- return `https://img.shields.io/badge/${encodeLabel(label)}-${score}%2F100-${color}`;
16
- }
17
- function badgeMarkdown(score, label = "sickbay") {
18
- return `![${label}](${badgeUrl(score, label)})`;
19
- }
20
- function badgeHtml(score, label = "sickbay") {
21
- return `<img src="${badgeUrl(score, label)}" alt="${label}" />`;
22
- }
23
- function loadScoreFromLastReport(projectPath) {
24
- const filePath = join(projectPath, ".sickbay", "last-report.json");
25
- if (!existsSync(filePath)) return null;
26
- try {
27
- const data = JSON.parse(readFileSync(filePath, "utf-8"));
28
- if (typeof data.overallScore !== "number") return null;
29
- return data.overallScore;
30
- } catch {
31
- return null;
32
- }
33
- }
34
- //#endregion
35
- export { badgeHtml, badgeMarkdown, badgeUrl, loadScoreFromLastReport };
36
-
37
- //# sourceMappingURL=badge-DB3y0fWt.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"badge-DB3y0fWt.js","names":[],"sources":["../src/commands/badge.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'fs';\nimport { join } from 'path';\n\nexport function getScoreColor(score: number): string {\n if (score >= 90) return 'brightgreen';\n if (score >= 80) return 'green';\n if (score >= 60) return 'yellow';\n return 'red';\n}\n\nfunction encodeLabel(label: string): string {\n return label.replace(/-/g, '--').replace(/_/g, '__').replace(/ /g, '%20');\n}\n\nexport function badgeUrl(score: number, label = 'sickbay'): string {\n const color = getScoreColor(score);\n const encoded = encodeLabel(label);\n return `https://img.shields.io/badge/${encoded}-${score}%2F100-${color}`;\n}\n\nexport function badgeMarkdown(score: number, label = 'sickbay'): string {\n return `![${label}](${badgeUrl(score, label)})`;\n}\n\nexport function badgeHtml(score: number, label = 'sickbay'): string {\n return `<img src=\"${badgeUrl(score, label)}\" alt=\"${label}\" />`;\n}\n\nexport function loadScoreFromLastReport(projectPath: string): number | null {\n const filePath = join(projectPath, '.sickbay', 'last-report.json');\n if (!existsSync(filePath)) return null;\n try {\n const data = JSON.parse(readFileSync(filePath, 'utf-8'));\n if (typeof data.overallScore !== 'number') return null;\n return data.overallScore;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;AAGA,SAAgB,cAAc,OAAuB;AACnD,KAAI,SAAS,GAAI,QAAO;AACxB,KAAI,SAAS,GAAI,QAAO;AACxB,KAAI,SAAS,GAAI,QAAO;AACxB,QAAO;;AAGT,SAAS,YAAY,OAAuB;AAC1C,QAAO,MAAM,QAAQ,MAAM,KAAK,CAAC,QAAQ,MAAM,KAAK,CAAC,QAAQ,MAAM,MAAM;;AAG3E,SAAgB,SAAS,OAAe,QAAQ,WAAmB;CACjE,MAAM,QAAQ,cAAc,MAAM;AAElC,QAAO,gCADS,YAAY,MAAM,CACa,GAAG,MAAM,SAAS;;AAGnE,SAAgB,cAAc,OAAe,QAAQ,WAAmB;AACtE,QAAO,KAAK,MAAM,IAAI,SAAS,OAAO,MAAM,CAAC;;AAG/C,SAAgB,UAAU,OAAe,QAAQ,WAAmB;AAClE,QAAO,aAAa,SAAS,OAAO,MAAM,CAAC,SAAS,MAAM;;AAG5D,SAAgB,wBAAwB,aAAoC;CAC1E,MAAM,WAAW,KAAK,aAAa,YAAY,mBAAmB;AAClE,KAAI,CAAC,WAAW,SAAS,CAAE,QAAO;AAClC,KAAI;EACF,MAAM,OAAO,KAAK,MAAM,aAAa,UAAU,QAAQ,CAAC;AACxD,MAAI,OAAO,KAAK,iBAAiB,SAAU,QAAO;AAClD,SAAO,KAAK;SACN;AACN,SAAO"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"web-D5etye5Q.js","names":[],"sources":["../src/commands/web.ts"],"sourcesContent":["import { readFileSync, existsSync } from 'fs';\nimport http from 'http';\nimport { join, extname, resolve, relative, isAbsolute } from 'path';\nimport { fileURLToPath } from 'url';\n\nimport type { AIService } from '../services/ai.js';\nimport type { SickbayReport, MonorepoReport } from 'sickbay-core';\n\n/**\n * This module implements a simple HTTP server to serve a web dashboard for visualizing Sickbay reports.\n * It dynamically serves the report data as JSON and provides endpoints for AI-generated summaries and chat interactions if an AI service is available.\n * The server also serves static files from the built dashboard located in the sickbay-web package. This allows users to interact with their health reports in a user-friendly web interface.\n */\n\nconst MIME_TYPES: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n};\n\n/**\n * Safely resolve a URL pathname to a file path within baseDir, rejecting\n * any attempt to escape the directory via `..`, absolute paths, URL-encoded\n * traversal (`%2e%2e`), or null bytes. Returns null if the path is unsafe.\n *\n * Note that `new URL()` (used by the request handler) already normalizes\n * `..` segments from the pathname before this function sees it, so in\n * practice most traversal attempts are neutralized upstream. This function\n * is defense-in-depth — it guards against null bytes, manually constructed\n * malformed paths, and any future code path that bypasses URL normalization.\n *\n * Even though the dashboard server binds to 127.0.0.1, the dashboard exposes\n * scan results including secret-scan findings — treat it like any public\n * static server.\n *\n * Exported for direct unit testing; not part of the stable public API.\n */\nexport function resolveSafeStaticPath(baseDir: string, pathname: string): string | null {\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return null;\n }\n if (decoded.includes('\\0')) return null;\n // Strip leading slashes so resolve() treats the path as relative to baseDir.\n const cleaned = decoded.replace(/^\\/+/, '');\n const resolved = resolve(baseDir, cleaned);\n const rel = relative(baseDir, resolved);\n // relative() returns '' when identical, '..'-prefixed when outside, or absolute on other drives (Windows).\n if (rel === '' || (!rel.startsWith('..') && !isAbsolute(rel))) {\n return resolved;\n }\n return null;\n}\n\nfunction findWebDist(): string | null {\n // Resolve relative to this file's location\n const thisFile = fileURLToPath(import.meta.url);\n const candidates = [\n join(thisFile, '..', 'web'), // published: dist/web/ (embedded at build time)\n join(thisFile, '..', '..', '..', 'web', 'dist'), // monorepo dev: apps/web/dist/\n ];\n for (const p of candidates) {\n if (existsSync(join(p, 'index.html'))) return p;\n }\n return null;\n}\n\nasync function getFreePort(preferred: number, attempts = 0): Promise<number> {\n if (attempts >= 10) {\n throw new Error(\n `No free port found after 10 attempts starting from port ${preferred - attempts}`,\n );\n }\n return new Promise((resolve, reject) => {\n const server = http.createServer();\n // Probe must bind to the same host as the real server (127.0.0.1), otherwise\n // Node defaults to `::` and we can miss an IPv4-loopback-only conflict —\n // the probe says \"free\", then the real listen crashes with EADDRINUSE.\n server.listen(preferred, '127.0.0.1', () => {\n const addr = server.address() as { port: number };\n server.close(() => resolve(addr.port));\n });\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n resolve(getFreePort(preferred + 1, attempts + 1));\n } else {\n reject(err);\n }\n });\n });\n}\n\nfunction packageReportToSickbayReport(\n pkg: import('sickbay-core').PackageReport,\n parent: MonorepoReport,\n): SickbayReport {\n return {\n timestamp: parent.timestamp,\n projectPath: pkg.path,\n projectInfo: {\n name: pkg.name,\n version: 'unknown',\n hasTypeScript: false,\n hasESLint: false,\n hasPrettier: false,\n framework: pkg.framework,\n packageManager: parent.packageManager,\n totalDependencies:\n Object.keys(pkg.dependencies).length + Object.keys(pkg.devDependencies).length,\n dependencies: pkg.dependencies,\n devDependencies: pkg.devDependencies,\n },\n checks: pkg.checks,\n overallScore: pkg.score,\n summary: pkg.summary,\n };\n}\n\nexport async function serveWeb(\n report: SickbayReport | MonorepoReport,\n preferredPort = 3030,\n aiService?: AIService,\n): Promise<string> {\n const distDir = findWebDist();\n if (!distDir) {\n throw new Error('Web dashboard assets not found. Try reinstalling: npx sickbay@latest --web');\n }\n\n const reportJson = JSON.stringify(report);\n const port = await getFreePort(preferredPort);\n\n // Generate AI summary on startup if service is available (single-project only)\n let aiSummary: string | null = null;\n if (aiService && !('isMonorepo' in report)) {\n try {\n aiSummary = await aiService.generateSummary(report);\n } catch (err) {\n console.warn('AI summary generation failed:', err);\n }\n }\n\n const server = http.createServer(async (req, res) => {\n const url = req.url ?? '/';\n\n // Serve the report JSON directly from memory\n if (url === '/sickbay-report.json') {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(reportJson);\n return;\n }\n\n // Serve project-local history\n if (url === '/sickbay-history.json') {\n const basePath = 'isMonorepo' in report ? report.rootPath : report.projectPath;\n const historyPath = join(basePath, '.sickbay', 'history.json');\n if (existsSync(historyPath)) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(readFileSync(historyPath, 'utf-8'));\n } else {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end('{}');\n }\n return;\n }\n\n // Serve dependency tree\n if (url === '/sickbay-dep-tree.json') {\n const basePath = 'isMonorepo' in report ? report.rootPath : report.projectPath;\n const treePath = join(basePath, '.sickbay', 'dep-tree.json');\n if (existsSync(treePath)) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(readFileSync(treePath, 'utf-8'));\n } else {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end('{}');\n }\n return;\n }\n\n // Serve project config\n if (url === '/sickbay-config.json') {\n const basePath = 'isMonorepo' in report ? report.rootPath : report.projectPath;\n try {\n const { loadConfig } = await import('sickbay-core');\n const config = await loadConfig(basePath);\n if (config) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify(config));\n } else {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end('{}');\n }\n } catch {\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end('{}');\n }\n return;\n }\n\n // AI summary endpoint\n const parsedUrl = new URL(url, 'http://localhost');\n const packageName = parsedUrl.searchParams.get('package');\n\n if (parsedUrl.pathname === '/ai/summary') {\n if (req.method === 'HEAD') {\n // Availability check\n if (aiService) {\n res.writeHead(200);\n } else {\n res.writeHead(404);\n }\n res.end();\n return;\n }\n if (packageName && aiService && 'isMonorepo' in report) {\n // Per-package summary: generate on demand\n const pkg = report.packages.find((p) => p.name === packageName);\n if (!pkg) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Package not found' }));\n return;\n }\n try {\n const pkgReport = packageReportToSickbayReport(pkg, report);\n const summary = await aiService.generateSummary(pkgReport);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ summary }));\n } catch (err) {\n // Don't leak internal error details (stack, constructor name, dep internals)\n // over the wire, even on loopback — other processes on the machine can reach it.\n const message = err instanceof Error ? err.message : 'Internal error';\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: message }));\n }\n return;\n }\n if (aiSummary) {\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ summary: aiSummary }));\n return;\n }\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end('{}');\n return;\n }\n\n // AI chat endpoint\n if (parsedUrl.pathname === '/ai/chat' && req.method === 'POST' && aiService) {\n // Cap request body to prevent unbounded memory growth from a malicious\n // or misbehaving client on the loopback interface.\n const MAX_BODY = 1024 * 1024; // 1 MB\n let body = '';\n let aborted = false;\n req.on('data', (chunk) => {\n if (aborted) return;\n body += chunk;\n if (Buffer.byteLength(body) > MAX_BODY) {\n aborted = true;\n res.writeHead(413, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Payload too large' }));\n req.destroy();\n }\n });\n req.on('end', async () => {\n if (aborted) return;\n try {\n const { message, history } = JSON.parse(body);\n let chatReport: SickbayReport;\n if (packageName && 'isMonorepo' in report) {\n const pkg = report.packages.find((p) => p.name === packageName);\n if (!pkg) {\n res.writeHead(404, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Package not found' }));\n return;\n }\n chatReport = packageReportToSickbayReport(pkg, report);\n } else if (!('isMonorepo' in report)) {\n chatReport = report;\n } else {\n res.writeHead(400, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: 'Package name required for monorepo' }));\n return;\n }\n const response = await aiService.chat(message, chatReport, history ?? []);\n res.writeHead(200, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ response }));\n } catch (err) {\n // See above — no internal error leakage.\n const errMessage = err instanceof Error ? err.message : 'Internal error';\n res.writeHead(500, { 'Content-Type': 'application/json' });\n res.end(JSON.stringify({ error: errMessage }));\n }\n });\n return;\n }\n\n // Serve static files from dist. Use the parsed pathname (not the raw url)\n // so query strings are stripped, and route through resolveSafeStaticPath\n // to prevent path traversal (e.g. `/../../etc/passwd`, `%2e%2e/`).\n const requestedPath = parsedUrl.pathname === '/' ? 'index.html' : parsedUrl.pathname;\n const filePath = resolveSafeStaticPath(distDir, requestedPath);\n if (filePath && existsSync(filePath)) {\n const ext = extname(filePath);\n const mime = MIME_TYPES[ext] ?? 'application/octet-stream';\n res.writeHead(200, { 'Content-Type': mime });\n res.end(readFileSync(filePath));\n } else {\n // SPA fallback — serve index.html for all unknown routes\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(readFileSync(join(distDir, 'index.html')));\n }\n });\n\n return new Promise((resolve, reject) => {\n // Close server on SIGINT/SIGTERM so the process can exit cleanly when\n // the user presses Ctrl+C. Without this the HTTP handle keeps the event\n // loop alive after Ink's render loop exits.\n const shutdown = () => server.close();\n process.once('SIGINT', shutdown);\n process.once('SIGTERM', shutdown);\n\n // Reject the promise on any listen failure (EADDRINUSE, EACCES, etc.)\n // instead of letting an unhandled 'error' event crash the process with a\n // raw Node stack trace. The CLI renders rejection messages nicely.\n server.once('error', (err: NodeJS.ErrnoException) => {\n process.off('SIGINT', shutdown);\n process.off('SIGTERM', shutdown);\n if (err.code === 'EADDRINUSE') {\n reject(new Error(`Port ${port} is already in use. Stop the other process or try again.`));\n } else {\n reject(err);\n }\n });\n\n // SECURITY: bind explicitly to loopback so the dashboard (which contains\n // file paths, dep lists, and secret-scan results) is not exposed to other\n // hosts on the LAN, container networks, or VPN peers.\n server.listen(port, '127.0.0.1', () => {\n resolve(`http://localhost:${port}`);\n });\n });\n}\n"],"mappings":"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"}