{"version":3,"file":"dev-view.cjs","names":["Text","icons","Box","linkify","frames","gradients","colors","divider"],"sources":["../../src/components/dev-view.tsx"],"sourcesContent":["import { Box, render, Text, useApp, useInput } from \"ink\";\nimport { useEffect, useState } from \"react\";\nimport type { SourceMode } from \"../types\";\nimport { linkify } from \"../utils/linkify\";\nimport { colors, divider, frames, gradients, icons } from \"../utils/theme\";\n\nconst PLUGIN_PREFIX = \"plugin:\";\n\nexport type ProcessStatus = \"pending\" | \"starting\" | \"ready\" | \"error\";\n\nexport interface ProcessState {\n  name: string;\n  status: ProcessStatus;\n  port: number;\n  message?: string;\n  source?: SourceMode;\n}\n\nexport interface LogEntry {\n  id: string;\n  source: string;\n  line: string;\n  timestamp: number;\n  isError?: boolean;\n}\n\ninterface DevViewProps {\n  processes: ProcessState[];\n  logs: LogEntry[];\n  description: string;\n  proxyTarget?: string;\n  onExit?: () => Promise<void> | void;\n  onExportLogs?: () => Promise<void> | void;\n}\n\nfunction StatusIcon({ status }: { status: ProcessStatus }) {\n  switch (status) {\n    case \"pending\":\n      return <Text color=\"gray\">{icons.pending}</Text>;\n    case \"starting\":\n      return <Text color=\"#00ffff\">{icons.scan}</Text>;\n    case \"ready\":\n      return <Text color=\"#00ff41\">{icons.ok}</Text>;\n    case \"error\":\n      return <Text color=\"#ff3366\">{icons.err}</Text>;\n  }\n}\n\nfunction getServiceColor(name: string): string {\n  if (name.startsWith(PLUGIN_PREFIX)) return \"#ffaa00\";\n  return name === \"host\" ? \"#00ffff\" : name === \"ui\" ? \"#ff00ff\" : \"#0080ff\";\n}\n\nfunction getDisplayName(name: string): string {\n  return name.startsWith(PLUGIN_PREFIX)\n    ? name.slice(PLUGIN_PREFIX.length).toUpperCase()\n    : name.toUpperCase();\n}\n\nfunction isPlugin(name: string): boolean {\n  return name.startsWith(PLUGIN_PREFIX);\n}\n\nfunction getSectionedProcesses(processes: ProcessState[]): Array<{\n  key: string;\n  title: string;\n  processes: ProcessState[];\n}> {\n  const plugins = processes.filter((p) => isPlugin(p.name));\n  const services = processes.filter((p) => !isPlugin(p.name));\n  const sections: Array<{ key: string; title: string; processes: ProcessState[] }> = [];\n  if (plugins.length > 0) sections.push({ key: \"plugins\", title: \"PLUGINS\", processes: plugins });\n  if (services.length > 0)\n    sections.push({ key: \"services\", title: \"SERVICES\", processes: services });\n  return sections;\n}\n\nfunction getColumnWidths(processes: ProcessState[]): { name: number; source: number } {\n  const name = Math.max(6, ...processes.map((p) => getDisplayName(p.name).length));\n  const source = Math.max(10, ...processes.map((p) => (p.source ? `(${p.source})`.length : 0)));\n  return { name, source };\n}\n\nfunction ProcessRow({\n  proc,\n  nameWidth,\n  sourceWidth,\n}: {\n  proc: ProcessState;\n  nameWidth: number;\n  sourceWidth: number;\n}) {\n  const color = getServiceColor(proc.name);\n  const isRemote = proc.source === \"remote\";\n  const isHost = proc.name === \"host\";\n  const showPort = proc.port > 0 && (isHost || !isRemote);\n  const portStr = showPort ? `:${proc.port}` : \"\";\n  const sourceLabel = proc.source ? ` (${proc.source})` : \"\";\n\n  const statusText =\n    proc.status === \"pending\"\n      ? \"waiting\"\n      : proc.status === \"starting\"\n        ? \"starting\"\n        : proc.status === \"ready\"\n          ? isRemote && !isHost\n            ? \"loaded\"\n            : \"running\"\n          : \"failed\";\n\n  return (\n    <Box>\n      <Text>{\"  \"}</Text>\n      <StatusIcon status={proc.status} />\n      <Text> </Text>\n      <Text color={color} bold>\n        {getDisplayName(proc.name).padEnd(nameWidth)}\n      </Text>\n      <Text color=\"gray\">{sourceLabel.padEnd(sourceWidth)}</Text>\n      <Text color={proc.status === \"ready\" ? \"#00ff41\" : \"gray\"}>{statusText}</Text>\n      {showPort && <Text color=\"#00ffff\"> {portStr}</Text>}\n    </Box>\n  );\n}\n\nfunction SectionHeader({ title }: { title: string }) {\n  return (\n    <Box marginBottom={0} marginTop={1}>\n      <Text color=\"#00ffff\" bold>\n        {title}\n      </Text>\n    </Box>\n  );\n}\n\nfunction LogLine({ entry }: { entry: LogEntry }) {\n  const color = getServiceColor(entry.source);\n\n  return (\n    <Box>\n      <Text color={color}>[{entry.source}]</Text>\n      <Text color={entry.isError ? \"#ff3366\" : undefined}> {linkify(entry.line)}</Text>\n    </Box>\n  );\n}\n\nfunction truncateUrl(url: string, maxLen: number): string {\n  if (url.length <= maxLen) return url;\n  try {\n    const parsed = new URL(url);\n    const host = parsed.host;\n    if (host.length > maxLen - 10) {\n      return `${host.slice(0, maxLen - 13)}...`;\n    }\n    return host;\n  } catch {\n    return `${url.slice(0, maxLen - 3)}...`;\n  }\n}\n\nfunction DevView({\n  processes,\n  logs,\n  description,\n  proxyTarget,\n  onExit,\n  onExportLogs,\n}: DevViewProps) {\n  const { exit } = useApp();\n  const [isShuttingDown, setIsShuttingDown] = useState(false);\n\n  useInput((input, key) => {\n    if (isShuttingDown) return;\n\n    if (input === \"q\" || (key.ctrl && input === \"c\")) {\n      setIsShuttingDown(true);\n      Promise.resolve(onExit?.()).then(() => {\n        exit();\n      });\n    }\n    if (input === \"l\") {\n      setIsShuttingDown(true);\n      Promise.resolve(onExportLogs?.()).then(() => {\n        exit();\n      });\n    }\n  });\n\n  const readyCount = processes.filter((p) => p.status === \"ready\").length;\n  const total = processes.length;\n  const allReady = readyCount === total;\n  const hostProcess = processes.find((p) => p.name === \"host\");\n  const hostPort = hostProcess?.port || 3000;\n  const recentLogs = logs.slice(-12);\n  const sectionedProcesses = getSectionedProcesses(processes);\n  const columnWidths = getColumnWidths(processes);\n\n  return (\n    <Box flexDirection=\"column\">\n      <Box marginBottom={0}>\n        <Text color=\"#00ffff\">{frames.top(52)}</Text>\n      </Box>\n      <Box>\n        <Text>\n          {\"  \"}\n          {icons.run} {gradients.cyber(description.toUpperCase())}\n        </Text>\n      </Box>\n      <Box marginBottom={1}>\n        <Text color=\"#00ffff\">{frames.bottom(52)}</Text>\n      </Box>\n\n      {allReady && (\n        <Box marginBottom={1} flexDirection=\"column\">\n          <Box>\n            <Text color=\"#00ff41\">\n              {\"  \"}\n              {icons.app} APP READY\n            </Text>\n          </Box>\n          <Box>\n            <Text color=\"#00ff41\" bold>\n              {\"  \"}\n              {icons.arrow} http://localhost:{hostPort}\n            </Text>\n          </Box>\n        </Box>\n      )}\n\n      {proxyTarget && (\n        <Box marginBottom={1}>\n          <Text color=\"#ffaa00\">\n            {\"  \"}\n            {icons.arrow} API PROXY → {truncateUrl(proxyTarget, 38)}\n          </Text>\n        </Box>\n      )}\n\n      <Box marginTop={0} marginBottom={0}>\n        <Text>{colors.dim(divider(52))}</Text>\n      </Box>\n\n      {sectionedProcesses.map((section) => (\n        <Box key={section.key} flexDirection=\"column\">\n          <SectionHeader title={section.title} />\n          {section.processes.map((proc) => (\n            <ProcessRow\n              key={proc.name}\n              proc={proc}\n              nameWidth={columnWidths.name}\n              sourceWidth={columnWidths.source}\n            />\n          ))}\n        </Box>\n      ))}\n\n      <Box marginTop={1} marginBottom={0}>\n        <Text>{colors.dim(divider(52))}</Text>\n      </Box>\n\n      <Box marginTop={0}>\n        <Text color={allReady ? \"#00ff41\" : \"#00ffff\"}>\n          {\"  \"}\n          {allReady\n            ? `${icons.ok} All ${total} services running`\n            : `${icons.scan} ${readyCount}/${total} ready`}\n        </Text>\n        <Text color=\"gray\">\n          {\" \"}\n          {icons.dot} q quit {icons.dot} l logs\n        </Text>\n      </Box>\n\n      {recentLogs.length > 0 && (\n        <>\n          <Box marginTop={1} marginBottom={0}>\n            <Text>{colors.dim(divider(52))}</Text>\n          </Box>\n          <Box flexDirection=\"column\" marginTop={0}>\n            {recentLogs.map((entry) => (\n              <LogLine key={entry.id} entry={entry} />\n            ))}\n          </Box>\n        </>\n      )}\n    </Box>\n  );\n}\n\nexport interface DevViewHandle {\n  updateProcess: (name: string, status: ProcessStatus, message?: string) => void;\n  addLog: (source: string, line: string, isError?: boolean) => void;\n  unmount: () => void;\n}\n\nexport function renderDevView(\n  initialProcesses: ProcessState[],\n  description: string,\n  env: Record<string, string>,\n  onExit?: () => Promise<void> | void,\n  onExportLogs?: () => Promise<void> | void,\n): DevViewHandle {\n  let processes = [...initialProcesses];\n  let logs: LogEntry[] = [];\n  let rerender: (() => void) | null = null;\n  const proxyTarget = env.API_PROXY;\n  let logSeq = 0;\n  let lastLogKey: string | null = null;\n\n  const updateProcess = (name: string, status: ProcessStatus, message?: string) => {\n    processes = processes.map((p) => (p.name === name ? { ...p, status, message } : p));\n    rerender?.();\n  };\n\n  const addLog = (source: string, line: string, isError = false) => {\n    const nextKey = `${source}:${isError ? \"1\" : \"0\"}:${line}`;\n    if (nextKey === lastLogKey) return;\n    lastLogKey = nextKey;\n\n    logs = [\n      ...logs,\n      { id: `${Date.now()}-${++logSeq}`, source, line, timestamp: Date.now(), isError },\n    ];\n    if (logs.length > 100) logs = logs.slice(-100);\n    rerender?.();\n  };\n\n  function DevViewWrapper() {\n    const [, forceUpdate] = useState(0);\n\n    useEffect(() => {\n      rerender = () => forceUpdate((n: number) => n + 1);\n      return () => {\n        rerender = null;\n      };\n    }, []);\n\n    return (\n      <DevView\n        processes={processes}\n        logs={logs}\n        description={description}\n        proxyTarget={proxyTarget}\n        onExit={onExit}\n        onExportLogs={onExportLogs}\n      />\n    );\n  }\n\n  const { unmount } = render(<DevViewWrapper />);\n  return { updateProcess, addLog, unmount };\n}\n"],"mappings":"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"}