@datasynx/agentic-ai-cartography 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts","../src/preflight.ts","../src/db.ts","../src/tools.ts","../src/safety.ts","../src/agent.ts","../src/exporter.ts","../src/hex.ts","../src/cluster.ts","../src/mapper.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { checkPrerequisites } from './preflight.js';\nimport { CartographyDB } from './db.js';\nimport { defaultConfig } from './types.js';\nimport { runDiscovery } from './agent.js';\nimport type { DiscoveryEvent } from './agent.js';\nimport { exportAll, exportCartographyMap } from './exporter.js';\nimport { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';\nimport { resolve } from 'path';\nimport { createInterface } from 'readline';\nimport { execSync } from 'child_process';\n\n// ── Shared color helpers ─────────────────────────────────────────────────────\nconst bold = (s: string) => `\\x1b[1m${s}\\x1b[0m`;\nconst dim = (s: string) => `\\x1b[2m${s}\\x1b[0m`;\nconst cyan = (s: string) => `\\x1b[36m${s}\\x1b[0m`;\nconst green = (s: string) => `\\x1b[32m${s}\\x1b[0m`;\nconst yellow = (s: string) => `\\x1b[33m${s}\\x1b[0m`;\nconst magenta = (s: string) => `\\x1b[35m${s}\\x1b[0m`;\nconst red = (s: string) => `\\x1b[31m${s}\\x1b[0m`;\n\nmain();\n\nfunction main(): void {\n const program = new Command();\n\n const CMD = 'datasynx-cartography';\n const VERSION = '0.2.3';\n\n program\n .name(CMD)\n .description('AI-powered Infrastructure Cartography & SOP Generation')\n .version(VERSION);\n\n // ── DISCOVERY ──────────────────────────────────────────────────────────────\n\n program\n .command('discover')\n .description('Scan and map your infrastructure')\n .option('--entry <hosts...>', 'Entry points', ['localhost'])\n .option('--depth <n>', 'Max crawl depth', '8')\n .option('--max-turns <n>', 'Max agent turns', '50')\n .option('--model <m>', 'Agent model', 'claude-sonnet-4-5-20250929')\n .option('--org <name>', 'Organization name (for Backstage)')\n .option('-o, --output <dir>', 'Output directory', './datasynx-output')\n .option('--db <path>', 'DB path')\n .option('-v, --verbose', 'Show agent reasoning', false)\n .action(async (opts) => {\n checkPrerequisites();\n\n const config = defaultConfig({\n entryPoints: opts.entry,\n maxDepth: parseInt(opts.depth, 10),\n maxTurns: parseInt(opts.maxTurns, 10),\n agentModel: opts.model,\n organization: opts.org,\n outputDir: opts.output,\n ...(opts.db ? { dbPath: opts.db } : {}),\n verbose: opts.verbose,\n });\n\n const db = new CartographyDB(config.dbPath);\n const sessionId = db.createSession('discover', config);\n\n const w = process.stderr.write.bind(process.stderr);\n\n const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n let spinIdx = 0;\n let spinnerTimer: ReturnType<typeof setInterval> | null = null;\n let spinnerMsg = '';\n\n const startSpinner = (msg: string) => {\n spinnerMsg = msg;\n if (spinnerTimer) clearInterval(spinnerTimer);\n spinnerTimer = setInterval(() => {\n const frame = cyan(SPINNER[spinIdx % SPINNER.length] ?? '⠋');\n w(`\\r ${frame} ${spinnerMsg}\\x1b[K`);\n spinIdx++;\n }, 80);\n };\n\n const stopSpinner = () => {\n if (spinnerTimer) { clearInterval(spinnerTimer); spinnerTimer = null; }\n w(`\\r\\x1b[K`);\n };\n\n const startTime = Date.now();\n let turnNum = 0;\n let nodeCount = 0;\n let edgeCount = 0;\n\n w('\\n');\n w(` ${bold('CARTOGRAPHY')} ${dim(config.entryPoints.join(', '))}\\n`);\n w(` ${dim('Model: ' + config.agentModel + ' | MaxTurns: ' + config.maxTurns)}\\n`);\n w(dim(' ────────────────────────────────────────────────\\n'));\n w('\\n');\n\n const logLine = (icon: string, msg: string) => {\n stopSpinner();\n const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);\n w(` ${icon} ${msg} ${dim(elapsed + 's')}\\n`);\n };\n\n const handleEvent = (event: DiscoveryEvent) => {\n switch (event.kind) {\n case 'turn':\n turnNum = event.turn;\n startSpinner(`Turn ${turnNum}/${config.maxTurns} ${dim(`nodes:${nodeCount} edges:${edgeCount}`)}`);\n break;\n\n case 'thinking':\n if (config.verbose) {\n stopSpinner();\n const lines = event.text.split('\\n').slice(0, 3);\n for (const line of lines) {\n w(` ${dim(' ' + line.substring(0, 80))}\\n`);\n }\n }\n break;\n\n case 'tool_call': {\n const toolName = event.tool.replace('mcp__cartograph__', '');\n\n if (toolName === 'Bash') {\n const cmd = (event.input['command'] as string ?? '').substring(0, 70);\n startSpinner(`${yellow('$')} ${cmd}`);\n } else if (toolName === 'save_node') {\n const id = event.input['id'] as string ?? '?';\n const type = event.input['type'] as string ?? '?';\n nodeCount++;\n logLine(green('+'), `${bold('Node')} ${cyan(id)} ${dim('(' + type + ')')}`);\n startSpinner(`Turn ${turnNum}/${config.maxTurns} ${dim(`nodes:${nodeCount} edges:${edgeCount}`)}`);\n } else if (toolName === 'save_edge') {\n const src = event.input['sourceId'] as string ?? '?';\n const tgt = event.input['targetId'] as string ?? '?';\n const rel = event.input['relationship'] as string ?? '→';\n edgeCount++;\n logLine(magenta('~'), `${bold('Edge')} ${cyan(src)} ${dim('─' + rel + '→')} ${cyan(tgt)}`);\n startSpinner(`Turn ${turnNum}/${config.maxTurns} ${dim(`nodes:${nodeCount} edges:${edgeCount}`)}`);\n } else if (toolName === 'get_catalog') {\n startSpinner(`Catalog check ${dim('(avoiding duplicates)')}`);\n } else if (toolName === 'scan_bookmarks') {\n logLine(cyan('🔖'), `Scanning browser bookmarks…`);\n startSpinner(`scan_bookmarks`);\n } else if (toolName === 'scan_browser_history') {\n logLine(cyan('📜'), `Scanning browser history (anonymized hostnames only)…`);\n startSpinner(`scan_browser_history`);\n } else if (toolName === 'scan_installed_apps') {\n const sh = event.input['searchHint'] as string | undefined;\n logLine(cyan('🖥'), sh ? `Searching installed apps: ${bold(sh)}` : `Scanning all installed apps…`);\n startSpinner(`scan_installed_apps`);\n } else if (toolName === 'scan_local_databases') {\n logLine(cyan('🗄'), `Scanning local databases (PostgreSQL, MySQL, SQLite…)`);\n startSpinner(`scan_local_databases`);\n } else if (toolName === 'ask_user') {\n // Just display; actual interaction is handled by onAskUser below\n const q = (event.input['question'] as string ?? '').substring(0, 100);\n logLine(yellow('?'), `${bold('Agent asks:')} ${q}`);\n } else {\n startSpinner(`${toolName}...`);\n }\n break;\n }\n\n case 'tool_result':\n // Spinner continues, no special output for results\n break;\n\n case 'done':\n stopSpinner();\n break;\n }\n };\n\n // Human-in-the-loop: agent can ask clarifying questions\n const onAskUser = async (question: string, context?: string): Promise<string> => {\n stopSpinner();\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${yellow(bold('?'))} ${bold('Agent asks:')} ${question}\\n`);\n if (context) w(` ${dim(context)}\\n`);\n\n if (!process.stdin.isTTY) {\n w(` ${dim('(No terminal — agent will continue without your answer)')}\\n\\n`);\n return '(Non-interactive mode — please continue without this information)';\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const answer = await new Promise<string>(resolve => rl.question(` ${cyan('→')} `, resolve));\n rl.close();\n w('\\n');\n return answer || '(No answer — please continue)';\n };\n\n try {\n await runDiscovery(config, db, sessionId, handleEvent, onAskUser, undefined);\n } catch (err) {\n stopSpinner();\n w(`\\n ${bold('\\x1b[31m✗\\x1b[0m')} Discovery failed: ${err}\\n`);\n db.close();\n process.exitCode = 1;\n return;\n }\n\n stopSpinner();\n db.endSession(sessionId);\n const stats = db.getStats(sessionId);\n const totalSec = ((Date.now() - startTime) / 1000).toFixed(1);\n\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${green(bold('DONE'))} ${bold(String(stats.nodes))} nodes, ${bold(String(stats.edges))} edges ${dim('in ' + totalSec + 's')}\\n`);\n w('\\n');\n\n // ── Interactive Node & Edge Review ──────────────────────────────────────\n const allNodes = db.getNodes(sessionId);\n const allEdges = db.getEdges(sessionId);\n\n if (allNodes.length > 0 && process.stdin.isTTY) {\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${bold('REVIEW')} ${bold(String(allNodes.length))} nodes discovered\\n`);\n w(dim(' Enter numbers to remove nodes (e.g. \"1 3 5\"), Enter = keep all\\n'));\n w('\\n');\n\n const PAD_ID = 42;\n const PAD_TYPE = 16;\n allNodes.forEach((n, i) => {\n const num = String(i + 1).padStart(3);\n const id = n.id.padEnd(PAD_ID).substring(0, PAD_ID);\n const type = dim(`[${n.type}]`.padEnd(PAD_TYPE));\n const conf = dim(`${Math.round(n.confidence * 100)}%`);\n const src = dim(n.discoveredVia === 'bookmark' ? ' 🔖' : n.discoveredVia === 'browser_history' ? ' 📜' : '');\n w(` ${dim(num)} ${cyan('●')} ${id} ${type} ${conf}${src}\\n`);\n });\n\n // Show edges summary\n if (allEdges.length > 0) {\n w('\\n');\n w(` ${bold(String(allEdges.length))} edges (relationships):\\n`);\n for (const e of allEdges.slice(0, 20)) {\n w(` ${dim(' ')}${cyan(e.sourceId)} ${dim('─' + e.relationship + '→')} ${cyan(e.targetId)} ${dim(Math.round(e.confidence * 100) + '%')}\\n`);\n }\n if (allEdges.length > 20) w(` ${dim(' … and ' + (allEdges.length - 20) + ' more edges')}\\n`);\n }\n\n w('\\n');\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const answer = await new Promise<string>(resolve =>\n rl.question(` ${yellow('?')} Remove nodes (numbers, empty = keep all): `, resolve)\n );\n rl.close();\n\n const toRemove = answer.trim().split(/[\\s,]+/).map(Number).filter(n => n >= 1 && n <= allNodes.length);\n if (toRemove.length > 0) {\n for (const idx of toRemove) {\n const node = allNodes[idx - 1];\n if (node) db.deleteNode(sessionId, node.id);\n }\n w(`\\n ${green('✓')} ${bold(String(toRemove.length))} node(s) removed\\n`);\n } else {\n w(`\\n ${green('✓')} All nodes kept\\n`);\n }\n }\n\n // ── Export ─────────────────────────────────────────────────────────────\n exportAll(db, sessionId, config.outputDir);\n\n // ── Diagram links ───────────────────────────────────────────────────────\n const osc8 = (url: string, label: string) => `\\x1b]8;;${url}\\x1b\\\\${label}\\x1b]8;;\\x1b\\\\`;\n const htmlPath = resolve(config.outputDir, 'topology.html');\n const mapPath = resolve(config.outputDir, 'cartography-map.html');\n const discoveryPath = resolve(config.outputDir, 'discovery.html');\n const topoPath = resolve(config.outputDir, 'topology.mermaid');\n\n w('\\n');\n if (existsSync(discoveryPath)) {\n w(` ${green('→')} ${osc8(`file://${discoveryPath}`, bold('Open discovery.html'))} ${dim('← Enterprise Discovery Frontend')}\\n`);\n }\n if (existsSync(mapPath)) {\n w(` ${green('→')} ${osc8(`file://${mapPath}`, bold('Open cartography-map.html'))} ${dim('← Hex Map')}\\n`);\n }\n if (existsSync(htmlPath)) {\n w(` ${green('→')} ${osc8(`file://${htmlPath}`, bold('Open topology.html'))}\\n`);\n }\n if (existsSync(topoPath)) {\n try {\n const code = readFileSync(topoPath, 'utf8');\n const b64 = Buffer.from(JSON.stringify({ code, mermaid: { theme: 'dark' } })).toString('base64');\n w(` ${cyan('→')} ${osc8(`https://mermaid.live/view#base64:${b64}`, bold('Open in mermaid.live'))}\\n`);\n } catch { /* ignore */ }\n }\n w('\\n');\n\n // ── Human-in-the-Loop: Follow-up Discovery Chat ───────────────────────\n if (process.stdin.isTTY) {\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${bold('SEARCH MORE')} ${dim('Refine discovery interactively')}\\n`);\n w(dim(' Enter search terms (e.g. \"hubspot windsurf cursor\") or press Enter to finish.\\n'));\n w('\\n');\n\n // Reset event counters for follow-up rounds\n nodeCount = 0;\n edgeCount = 0;\n\n let continueDiscovery = true;\n while (continueDiscovery) {\n const rlFollowup = createInterface({ input: process.stdin, output: process.stderr });\n const followupHint = await new Promise<string>(resolve =>\n rlFollowup.question(` ${yellow('→')} Search for (Enter = finish): `, resolve)\n );\n rlFollowup.close();\n\n if (!followupHint.trim()) {\n continueDiscovery = false;\n break;\n }\n\n const followupHintTrimmed = followupHint.trim();\n w('\\n');\n w(` ${cyan(bold('⟳'))} Searching for: ${bold(followupHintTrimmed)}\\n`);\n w('\\n');\n\n try {\n await runDiscovery(config, db, sessionId, handleEvent, onAskUser, followupHintTrimmed);\n } catch (err) {\n stopSpinner();\n w(`\\n ${red('✗')} Error: ${err}\\n`);\n }\n\n stopSpinner();\n const followupStats = db.getStats(sessionId);\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${green(bold('✓'))} Total now: ${bold(String(followupStats.nodes))} nodes, ${bold(String(followupStats.edges))} edges\\n`);\n w('\\n');\n\n // Re-export with updated data\n exportAll(db, sessionId, config.outputDir);\n if (existsSync(htmlPath)) {\n w(` ${green('→')} ${osc8(`file://${htmlPath}`, bold('topology.html updated'))}\\n`);\n }\n w('\\n');\n }\n }\n\n db.close();\n });\n\n // ── ANALYSE & EXPORT ──────────────────────────────────────────────────────\n\n program\n .command('export [session-id]')\n .description('Generate all output files')\n .option('-o, --output <dir>', 'Output directory', './datasynx-output')\n .option('--format <fmt...>', 'Formats: mermaid,json,yaml,html,map,sops')\n .action((sessionId: string | undefined, opts) => {\n const config = defaultConfig({ outputDir: opts.output });\n const db = new CartographyDB(config.dbPath);\n\n const session = sessionId\n ? db.getSession(sessionId)\n : db.getLatestSession();\n\n if (!session) {\n process.stderr.write('❌ No session found\\n');\n db.close();\n process.exitCode = 1;\n return;\n }\n\n const formats = opts.format ?? ['mermaid', 'json', 'yaml', 'html', 'map', 'sops'];\n exportAll(db, session.id, opts.output, formats);\n process.stderr.write(`✓ Exported to: ${opts.output}\\n`);\n\n db.close();\n });\n\n // ── map command ─────────────────────────────────────────────────────────────\n program\n .command('map [session-id]')\n .description('Open the interactive Data Cartography hex map in your browser')\n .option('-o, --output <dir>', 'Output directory', './datasynx-output')\n .option('--theme <theme>', 'Theme: light or dark', 'light')\n .action((sessionId: string | undefined, opts) => {\n const config = defaultConfig({ outputDir: opts.output });\n const db = new CartographyDB(config.dbPath);\n\n const session = sessionId\n ? db.getSession(sessionId)\n : db.getLatestSession();\n\n if (!session) {\n process.stderr.write('No session found. Run discover first.\\n');\n db.close();\n process.exitCode = 1;\n return;\n }\n\n const nodes = db.getNodes(session.id);\n const edges = db.getEdges(session.id);\n const outDir = resolve(opts.output);\n mkdirSync(outDir, { recursive: true });\n const outPath = resolve(outDir, 'cartography-map.html');\n\n writeFileSync(outPath, exportCartographyMap(nodes, edges, { theme: opts.theme }));\n db.close();\n\n const osc8 = (url: string, label: string) => `\\x1b]8;;${url}\\x1b\\\\${label}\\x1b]8;;\\x1b\\\\`;\n const fileUrl = `file://${outPath}`;\n process.stderr.write(`\\n ${green('OK')} ${osc8(fileUrl, bold('Open cartography-map.html'))}\\n`);\n process.stderr.write(` ${dim(fileUrl)}\\n\\n`);\n\n try {\n const cmd = process.platform === 'darwin'\n ? `open \"${outPath}\"`\n : process.platform === 'win32'\n ? `start \"\" \"${outPath}\"`\n : `xdg-open \"${outPath}\"`;\n execSync(cmd, { stdio: 'ignore' });\n } catch { /* user can open manually */ }\n });\n\n program\n .command('show [session-id]')\n .description('Show session details')\n .action((sessionId?: string) => {\n const config = defaultConfig();\n const db = new CartographyDB(config.dbPath);\n\n const session = sessionId\n ? db.getSession(sessionId)\n : db.getLatestSession();\n\n if (!session) {\n process.stderr.write('❌ No session found\\n');\n db.close();\n process.exitCode = 1;\n return;\n }\n\n const stats = db.getStats(session.id);\n const nodes = db.getNodes(session.id);\n\n process.stdout.write(`\\nSession: ${session.id}\\n`);\n process.stdout.write(` Mode: ${session.mode}\\n`);\n process.stdout.write(` Started: ${session.startedAt}\\n`);\n if (session.completedAt) process.stdout.write(` Ended: ${session.completedAt}\\n`);\n process.stdout.write(` Nodes: ${stats.nodes}\\n`);\n process.stdout.write(` Edges: ${stats.edges}\\n`);\n process.stdout.write(` Events: ${stats.events}\\n`);\n process.stdout.write(` Tasks: ${stats.tasks}\\n`);\n\n if (nodes.length > 0) {\n process.stdout.write('\\n Discovered nodes:\\n');\n for (const node of nodes.slice(0, 20)) {\n process.stdout.write(` ${node.id} (${node.type}, confidence: ${node.confidence})\\n`);\n }\n if (nodes.length > 20) {\n process.stdout.write(` ... and ${nodes.length - 20} more\\n`);\n }\n }\n\n process.stdout.write('\\n');\n db.close();\n });\n\n program\n .command('sessions')\n .description('List all sessions')\n .action(() => {\n const config = defaultConfig();\n const db = new CartographyDB(config.dbPath);\n const sessions = db.getSessions();\n\n if (sessions.length === 0) {\n process.stdout.write('No sessions found\\n');\n db.close();\n return;\n }\n\n for (const session of sessions) {\n const stats = db.getStats(session.id);\n const status = session.completedAt ? '✓' : '●';\n process.stdout.write(\n `${status} ${session.id.substring(0, 8)} [${session.mode}] ` +\n `${session.startedAt.substring(0, 19)} ` +\n `nodes:${stats.nodes} edges:${stats.edges}\\n`\n );\n }\n\n db.close();\n });\n\n // ── OVERVIEW ──────────────────────────────────────────────────────────────\n\n program\n .command('overview')\n .description('Overview of all cartography sessions + SOPs')\n .option('--db <path>', 'DB-Pfad')\n .action((opts) => {\n const config = defaultConfig();\n const db = new CartographyDB((opts as { db?: string }).db ?? config.dbPath);\n const sessions = db.getSessions();\n\n const b = bold, d = dim;\n const w = (s: string) => process.stdout.write(s);\n\n w('\\n');\n w(` ${b('CARTOGRAPHY OVERVIEW')}\\n`);\n w(d(' ─────────────────────────────────────────────────────\\n'));\n\n if (sessions.length === 0) {\n w(` ${d('No sessions yet. Start with:')} ${green('datasynx-cartography discover')}\\n\\n`);\n db.close();\n return;\n }\n\n // Aggregate totals\n let totalNodes = 0, totalEdges = 0, totalSops = 0;\n for (const s of sessions) {\n const st = db.getStats(s.id);\n totalNodes += st.nodes; totalEdges += st.edges;\n totalSops += db.getSOPs(s.id).length;\n }\n\n w(` ${b(String(sessions.length))} Sessions · ${b(String(totalNodes))} Nodes · `);\n w(`${b(String(totalEdges))} Edges · ${b(String(totalSops))} SOPs\\n\\n`);\n\n for (const session of sessions) {\n const stats = db.getStats(session.id);\n const nodes = db.getNodes(session.id);\n const sops = db.getSOPs(session.id);\n const status = session.completedAt ? green('✓') : yellow('●');\n const age = session.startedAt.substring(0, 16).replace('T', ' ');\n const sid = cyan(session.id.substring(0, 8));\n\n w(` ${status} ${sid} ${b('[' + session.mode + ']')} ${d(age)}\\n`);\n w(` ${d('Nodes: ' + stats.nodes + ' Edges: ' + stats.edges + ' SOPs: ' + sops.length)}\\n`);\n\n // Node type breakdown\n const byType = new Map<string, number>();\n for (const n of nodes) byType.set(n.type, (byType.get(n.type) ?? 0) + 1);\n if (byType.size > 0) {\n const parts = [...byType.entries()].map(([t, c]) => `${t}:${c}`).join(' ');\n w(` ${d(parts)}\\n`);\n }\n\n // Top nodes\n const topNodes = nodes.slice(0, 5).map(n => n.id).join(', ');\n if (topNodes) w(` ${d('Nodes: ' + topNodes + (nodes.length > 5 ? ' …' : ''))}\\n`);\n\n // SOPs\n for (const sop of sops.slice(0, 3)) {\n w(` ${green('►')} ${sop.title} ${d('(' + sop.estimatedDuration + ')')}\\n`);\n }\n if (sops.length > 3) w(` ${d('… +' + (sops.length - 3) + ' more SOPs')}\\n`);\n\n w('\\n');\n }\n\n db.close();\n });\n\n // ── CHAT ──────────────────────────────────────────────────────────────────\n\n program\n .command('chat [session-id]')\n .description('Interactive chat about your mapped infrastructure')\n .option('--db <path>', 'DB-Pfad')\n .option('--model <m>', 'Model', 'claude-sonnet-4-5-20250929')\n .action(async (sessionIdArg: string | undefined, opts) => {\n const config = defaultConfig();\n const db = new CartographyDB((opts as { db?: string }).db ?? config.dbPath);\n const sessions = db.getSessions();\n\n const session = sessionIdArg\n ? sessions.find(s => s.id.startsWith(sessionIdArg))\n : sessions.filter(s => s.completedAt).at(-1) ?? sessions.at(-1);\n\n if (!session) {\n process.stderr.write('No session found. Run discover first.\\n');\n db.close();\n return;\n }\n\n const nodes = db.getNodes(session.id);\n const edges = db.getEdges(session.id);\n const sops = db.getSOPs(session.id);\n\n const w = (s: string) => process.stdout.write(s);\n\n w('\\n');\n w(dim(` ─────────────────────────────────────────────────────\\n`));\n w(` ${bold('CARTOGRAPHY CHAT')} ${dim('Session ' + session.id.substring(0, 8))}\\n`);\n w(` ${dim(String(nodes.length) + ' Nodes · ' + edges.length + ' Edges · ' + sops.length + ' SOPs')}\\n`);\n w(dim(` ─────────────────────────────────────────────────────\\n`));\n w(` ${dim('Ask anything about your infrastructure. exit = quit.\\n\\n')}`);\n\n const Anthropic = (await import('@anthropic-ai/sdk')).default;\n const client = new Anthropic();\n\n // Build a compact infra summary for context (avoid token overflow)\n const infraSummary = JSON.stringify({\n nodes: nodes.map(n => ({\n id: n.id, name: n.name, type: n.type,\n confidence: n.confidence,\n metadata: n.metadata,\n tags: n.tags,\n })),\n edges: edges.map(e => ({ from: e.sourceId, to: e.targetId, rel: e.relationship, conf: e.confidence })),\n sops: sops.map(s => ({ title: s.title, description: s.description, steps: s.steps.length, duration: s.estimatedDuration })),\n });\n\n const systemPrompt = `You are an infrastructure analyst for Cartography.\nYou have access to the fully mapped infrastructure of this session.\nAnswer questions precisely and helpfully. Use the data concretely.\nYou can explain SOPs, analyze dependencies, identify risks, suggest optimizations.\n\nINFRASTRUCTURE SNAPSHOT (${nodes.length} nodes, ${edges.length} edges, ${sops.length} SOPs):\n${infraSummary.substring(0, 12000)}`;\n\n // Multi-turn conversation history\n type MsgParam = { role: 'user' | 'assistant'; content: string };\n const history: MsgParam[] = [];\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const ask = () => new Promise<string>(resolve => rl.question(` ${cyan('>')} `, resolve));\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n let userInput: string;\n try { userInput = await ask(); } catch { break; }\n\n if (!userInput.trim()) continue;\n if (['exit', 'quit', ':q'].includes(userInput.trim().toLowerCase())) break;\n\n history.push({ role: 'user', content: userInput });\n\n try {\n const resp = await client.messages.create({\n model: (opts as { model: string }).model,\n max_tokens: 1024,\n system: systemPrompt,\n messages: history,\n });\n\n const reply = resp.content.find(b => b.type === 'text')?.text ?? '';\n history.push({ role: 'assistant', content: reply });\n\n w('\\n');\n // Word-wrap at 80 cols with indent\n for (const line of reply.split('\\n')) {\n w(` ${line}\\n`);\n }\n w('\\n');\n } catch (err) {\n w(` ${red('✗')} Error: ${err}\\n\\n`);\n }\n }\n\n rl.close();\n db.close();\n w(`\\n ${dim('Chat ended.')}\\n\\n`);\n });\n\n // ── DOCS ──────────────────────────────────────────────────────────────────\n\n program\n .command('docs')\n .description('Full feature reference and all commands')\n .action(() => {\n const out = process.stdout.write.bind(process.stdout);\n const b = bold;\n const line = () => out(dim('─'.repeat(60)) + '\\n');\n\n out('\\n');\n out(b(' DATASYNX CARTOGRAPHY') + ' ' + dim('v' + VERSION) + '\\n');\n out(dim(' AI-powered Infrastructure Cartography & SOP Generation\\n'));\n out('\\n');\n line();\n\n // ── CARTOGRAPHY\n out(b(cyan(' CARTOGRAPHY\\n')));\n out('\\n');\n out(` ${green('datasynx-cartography discover')}\\n`);\n out(` Scans your local infrastructure (Claude Sonnet).\\n`);\n out(` Claude autonomously runs ss, ps, curl, docker inspect, kubectl get\\n`);\n out(` and stores everything in SQLite.\\n`);\n out('\\n');\n out(dim(' Options:\\n'));\n out(dim(' --entry <hosts...> Entry points (default: localhost)\\n'));\n out(dim(' --depth <n> Max depth (default: 8)\\n'));\n out(dim(' --max-turns <n> Max agent turns (default: 50)\\n'));\n out(dim(' --model <m> Model (default: claude-sonnet-4-5-...)\\n'));\n out(dim(' --org <name> Organization for Backstage YAML\\n'));\n out(dim(' -o, --output <dir> Output directory (default: ./datasynx-output)\\n'));\n out(dim(' -v, --verbose Show agent reasoning\\n'));\n out('\\n');\n out(dim(' Output:\\n'));\n out(dim(' datasynx-output/\\n'));\n out(dim(' catalog.json Machine-readable full dump\\n'));\n out(dim(' catalog-info.yaml Backstage service catalog\\n'));\n out(dim(' topology.mermaid Infrastructure topology (graph TB)\\n'));\n out(dim(' dependencies.mermaid Service dependencies (graph LR)\\n'));\n out(dim(' discovery.html Enterprise discovery frontend (Map + Topology)\\n'));\n out(dim(' topology.html Interactive D3.js force graph\\n'));\n out(dim(' cartography-map.html Hex grid data cartography map\\n'));\n out(dim(' sops/ Generated SOPs as Markdown\\n'));\n out(dim(' workflows/ Workflow flowcharts as Mermaid\\n'));\n out('\\n');\n line();\n\n // ── ANALYSIS & EXPORT\n out(b(cyan(' ANALYSIS & EXPORT\\n')));\n out('\\n');\n out(` ${green('datasynx-cartography export [session-id]')}\\n`);\n out(dim(' --format <fmt...> mermaid, json, yaml, html, sops (default: all)\\n'));\n out(dim(' -o, --output <dir> Output directory\\n'));\n out('\\n');\n out(` ${green('datasynx-cartography show [session-id]')} ${dim('Session details + node list')}\\n`);\n out(` ${green('datasynx-cartography sessions')} ${dim('List all sessions')}\\n`);\n out('\\n');\n line();\n\n // ── COST\n out(b(cyan(' COST ESTIMATES\\n')));\n out('\\n');\n out(yellow(' Mode Model Interval per Hour per 8h Day\\n'));\n out(dim(' ─────────────────────────────────────────────────────────────\\n'));\n out(` Discovery Sonnet one-shot $0.15–0.50 one-shot\\n`);\n out('\\n');\n line();\n\n // ── ARCHITECTURE\n out(b(cyan(' ARCHITECTURE\\n')));\n out('\\n');\n out(dim(' CLI (Commander)\\n'));\n out(dim(' └── Preflight: Claude CLI check + API key + interval validation\\n'));\n out(dim(' └── Agent Orchestrator (agent.ts)\\n'));\n out(dim(' └── runDiscovery() → Claude Sonnet + Bash + MCP Tools\\n'));\n out(dim(' └── Custom MCP Tools (tools.ts)\\n'));\n out(dim(' save_node, save_edge,\\n'));\n out(dim(' scan_bookmarks, scan_browser_history,\\n'));\n out(dim(' scan_installed_apps, scan_local_databases\\n'));\n out(dim(' └── CartographyDB (SQLite WAL)\\n'));\n out('\\n');\n line();\n\n // ── SETUP\n out(b(cyan(' SETUP\\n')));\n out('\\n');\n out(dim(' # 1. Claude CLI (runtime dependency)\\n'));\n out(' npm install -g @anthropic-ai/claude-code\\n');\n out(' claude login\\n');\n out('\\n');\n out(dim(' # 2. API Key (if not using claude login)\\n'));\n out(' export ANTHROPIC_API_KEY=sk-ant-...\\n');\n out('\\n');\n out(dim(' # 3. Go\\n'));\n out(' datasynx-cartography discover\\n');\n out('\\n');\n out(dim(' Data: ~/.cartography/cartography.db\\n'));\n out('\\n');\n });\n\n // ── Bookmarks ──────────────────────────────────────────────────────────────\n\n program\n .command('bookmarks')\n .description('View all browser bookmarks (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)')\n .action(async () => {\n const { scanAllBookmarks } = await import('./bookmarks.js');\n const out = (s: string) => process.stdout.write(s);\n\n process.stderr.write(' Scanning bookmarks...\\n\\n');\n const hosts = await scanAllBookmarks();\n\n if (hosts.length === 0) {\n out(' (No bookmarks found — Chrome, Edge, Brave, Vivaldi, Opera and Firefox are supported)\\n\\n');\n return;\n }\n\n // Group by source browser\n const bySource = new Map<string, typeof hosts>();\n for (const h of hosts) {\n if (!bySource.has(h.source)) bySource.set(h.source, []);\n bySource.get(h.source)!.push(h);\n }\n\n for (const [source, entries] of bySource) {\n out(bold(cyan(` ${source.toUpperCase()}`)) + dim(` (${entries.length} Hosts)\\n`));\n out(dim(' ─────────────────────────────────────────\\n'));\n for (const h of entries) {\n const isDefault = (h.protocol === 'https' && h.port === 443) || (h.protocol === 'http' && h.port === 80);\n const portStr = isDefault ? '' : `:${h.port}`;\n out(` ${cyan(h.protocol + '://')}${h.hostname}${dim(portStr)}\\n`);\n }\n out('\\n');\n }\n\n out(dim(` Total: ${hosts.length} unique hosts\\n\\n`));\n out(dim(' Tip: ') + 'datasynx-cartography discover' + dim(' — scans + classifies all bookmarks automatically\\n\\n'));\n });\n\n // ── Seed ───────────────────────────────────────────────────────────────────\n\n program\n .command('seed')\n .description('Manually add known infrastructure (tools, DBs, APIs, etc.)')\n .option('--file <path>', 'JSON file with node definitions')\n .option('--session <id>', 'Add to existing session (default: new session)')\n .option('--db <path>', 'DB path')\n .action(async (opts) => {\n const config = defaultConfig({ ...(opts.db ? { dbPath: opts.db } : {}) });\n const db = new CartographyDB(config.dbPath);\n const sessionId = opts.session ?? db.createSession('discover', config);\n\n const out = (s: string) => process.stdout.write(s);\n const w = (s: string) => process.stderr.write(s);\n\n // ── File mode ────────────────────────────────────────────────────────\n if (opts.file) {\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(resolve(opts.file), 'utf8'));\n } catch (e) {\n w(red(`\\n ✗ Could not read file: ${e}\\n\\n`));\n process.exitCode = 1;\n return;\n }\n\n if (!Array.isArray(raw)) {\n w(red('\\n ✗ JSON must be an array: [{ \"type\": \"...\", \"name\": \"...\", \"host\": \"...\" }]\\n\\n'));\n process.exitCode = 1;\n return;\n }\n\n let saved = 0;\n for (const entry of raw as Record<string, unknown>[]) {\n const type = entry['type'] as string;\n const name = entry['name'] as string;\n const host = entry['host'] as string | undefined;\n const port = entry['port'] as number | undefined;\n const tags = (entry['tags'] as string[] | undefined) ?? [];\n const metadata = (entry['metadata'] as Record<string, unknown> | undefined) ?? {};\n\n if (!type || !name) {\n w(yellow(` ⚠ Skipped (no type/name): ${JSON.stringify(entry)}\\n`));\n continue;\n }\n\n const id = host\n ? `${type}:${host}${port ? ':' + port : ''}`\n : `${type}:${name.toLowerCase().replace(/\\s+/g, '-')}`;\n\n db.upsertNode(sessionId, {\n id,\n type: type as typeof import('./types.js').NODE_TYPES[number],\n name,\n discoveredVia: 'manual',\n confidence: 1.0,\n metadata: { ...metadata, ...(host ? { host } : {}), ...(port ? { port } : {}) },\n tags,\n });\n out(` ${green('+')} ${cyan(id)} ${dim('(' + type + ')')}\\n`);\n saved++;\n }\n\n db.endSession(sessionId);\n w(`\\n ${green(bold('DONE'))} ${saved} nodes saved ${dim('Session: ' + sessionId)}\\n\\n`);\n return;\n }\n\n // ── Interactive mode ─────────────────────────────────────────────────\n const { NODE_TYPES } = await import('./types.js');\n\n if (!process.stdin.isTTY) {\n w(red('\\n ✗ Interactive mode requires a terminal (use --file for non-interactive)\\n\\n'));\n process.exitCode = 1;\n return;\n }\n\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(bold(' Add known infrastructure\\n'));\n w(dim(' Examples: databases, APIs, SaaS tools, cloud services\\n'));\n w(dim(' ────────────────────────────────────────────────\\n'));\n w('\\n');\n\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const ask = (q: string): Promise<string> => new Promise(res => rl.question(q, res));\n\n let saved = 0;\n\n const typeList = NODE_TYPES.map((t, i) => `${dim((i + 1).toString().padStart(2))} ${t}`).join('\\n ');\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n w('\\n');\n w(dim(' Node types:\\n'));\n w(` ${typeList}\\n\\n`);\n\n const typeInput = (await ask(` ${cyan('Type')} ${dim('[number or name, Enter=cancel]')}: `)).trim();\n if (!typeInput) break;\n\n let nodeType: string;\n const asNum = parseInt(typeInput, 10);\n if (!isNaN(asNum) && asNum >= 1 && asNum <= NODE_TYPES.length) {\n nodeType = NODE_TYPES[asNum - 1] as string;\n } else if (NODE_TYPES.includes(typeInput as typeof NODE_TYPES[number])) {\n nodeType = typeInput;\n } else {\n w(yellow(` ⚠ Unknown type: \"${typeInput}\"\\n`));\n continue;\n }\n\n const name = (await ask(` ${cyan('Name')} ${dim('[e.g. \"Prod PostgreSQL\"]')}: `)).trim();\n if (!name) { w(dim(' (Cancelled)\\n')); continue; }\n\n const hostRaw = (await ask(` ${cyan('Host / IP')} ${dim('[optional, Enter=skip]')}: `)).trim();\n const portRaw = (await ask(` ${cyan('Port')} ${dim('[optional]')}: `)).trim();\n const tagsRaw = (await ask(` ${cyan('Tags')} ${dim('[comma-separated, optional]')}: `)).trim();\n\n const host = hostRaw || undefined;\n const port = portRaw ? parseInt(portRaw, 10) : undefined;\n const tags = tagsRaw ? tagsRaw.split(',').map(t => t.trim()).filter(Boolean) : [];\n\n const id = host\n ? `${nodeType}:${host}${port ? ':' + port : ''}`\n : `${nodeType}:${name.toLowerCase().replace(/\\s+/g, '-')}`;\n\n db.upsertNode(sessionId, {\n id,\n type: nodeType as typeof NODE_TYPES[number],\n name,\n discoveredVia: 'manual',\n confidence: 1.0,\n metadata: { ...(host ? { host } : {}), ...(port ? { port } : {}) },\n tags,\n });\n out(` ${green('+')} ${cyan(id)}\\n`);\n saved++;\n\n const again = (await ask(` ${dim('Add another node? [Y/n]')}: `)).trim().toLowerCase();\n if (again === 'n' || again === 'no') break;\n }\n\n rl.close();\n db.endSession(sessionId);\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${green(bold('DONE'))} ${saved} node${saved !== 1 ? 's' : ''} saved\\n`);\n w(` ${dim('Session: ' + sessionId)}\\n`);\n w(` ${dim('Tip: datasynx-cartography show ' + sessionId)}\\n\\n`);\n });\n\n // ── Doctor ─────────────────────────────────────────────────────────────────\n\n program\n .command('doctor')\n .description('Check all requirements and cloud CLIs')\n .action(async () => {\n const { execSync } = await import('node:child_process');\n const { existsSync, readFileSync } = await import('node:fs');\n const { join } = await import('node:path');\n const out = (s: string) => process.stdout.write(s);\n const ok = (msg: string) => out(` \\x1b[32m✓\\x1b[0m ${msg}\\n`);\n const err = (msg: string) => out(` \\x1b[31m✗\\x1b[0m ${msg}\\n`);\n const warn = (msg: string) => out(` \\x1b[33m⚠\\x1b[0m ${msg}\\n`);\n const dim = (s: string) => `\\x1b[2m${s}\\x1b[0m`;\n let allGood = true;\n\n out('\\n \\x1b[1mDatasynx Cartography — Doctor\\x1b[0m\\n');\n out(dim(' ─────────────────────────────────\\n'));\n\n // 1. Node.js Version\n const nodeVer = process.versions.node;\n const [major] = nodeVer.split('.').map(Number);\n if ((major ?? 0) >= 18) {\n ok(`Node.js ${nodeVer}`);\n } else {\n err(`Node.js ${nodeVer} — benötigt >=18`);\n allGood = false;\n }\n\n // 2. Claude CLI\n try {\n const v = execSync('claude --version', { stdio: 'pipe' }).toString().trim();\n ok(`Claude CLI ${dim(v)}`);\n } catch {\n err('Claude CLI not found — npm i -g @anthropic-ai/claude-code');\n allGood = false;\n }\n\n // 3. Auth\n const hasApiKey = Boolean(process.env.ANTHROPIC_API_KEY);\n const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';\n let hasOAuth = false;\n try {\n const creds = JSON.parse(readFileSync(join(home, '.claude', '.credentials.json'), 'utf8')) as Record<string, unknown>;\n const oauth = creds['claudeAiOauth'] as Record<string, unknown> | undefined;\n hasOAuth = typeof oauth?.['accessToken'] === 'string' && oauth['accessToken'].length > 0;\n } catch { /* no creds file */ }\n\n if (hasApiKey) {\n ok('ANTHROPIC_API_KEY set');\n } else if (hasOAuth) {\n ok('claude login (subscription)');\n } else {\n err('No authentication — run: claude login or export ANTHROPIC_API_KEY=sk-ant-...');\n allGood = false;\n }\n\n // 4. kubectl — important for K8s discovery\n try {\n const v = execSync('kubectl version --client --short 2>/dev/null || kubectl version --client', { stdio: 'pipe' }).toString().split('\\n')[0]?.trim() ?? '';\n ok(`kubectl ${dim(v || '(client OK)')}`);\n } catch {\n warn(`kubectl not found ${dim('— install: https://kubernetes.io/docs/tasks/tools/')}`);\n }\n\n // 5. Cloud CLIs (optional)\n const cloudClis: Array<[string, string, string]> = [\n ['aws', 'aws --version', 'AWS CLI — https://aws.amazon.com/cli/'],\n ['gcloud', 'gcloud --version', 'Google Cloud SDK — https://cloud.google.com/sdk/'],\n ['az', 'az --version', 'Azure CLI — https://aka.ms/installazurecliwindows'],\n ];\n for (const [name, cmd, hint] of cloudClis) {\n try {\n execSync(cmd, { stdio: 'pipe' });\n ok(`${name} ${dim('(cloud scanning available)')}`);\n } catch {\n warn(`${name} not found ${dim('— cloud scan skipped | ' + hint)}`);\n }\n }\n\n // 6. Local discovery tools\n const localTools: Array<[string, string]> = [\n ['docker', 'docker --version'],\n ['ss', 'ss --version'],\n ];\n for (const [name, cmd] of localTools) {\n try {\n execSync(cmd, { stdio: 'pipe' });\n ok(`${name} ${dim('(discovery tool)')}`);\n } catch {\n warn(`${name} not found ${dim('— discovery without ' + name + ' will be limited')}`);\n }\n }\n\n // 7. SQLite data dir\n const dbDir = join(home, '.cartography');\n if (existsSync(dbDir)) {\n ok(`~/.cartography ${dim('(data directory exists)')}`);\n } else {\n warn('~/.cartography does not exist yet ' + dim('— will be created on first run'));\n }\n\n out(dim(' ─────────────────────────────────\\n'));\n if (allGood) {\n out(' \\x1b[32m\\x1b[1mAll checks passed — datasynx-cartography discover\\x1b[0m\\n\\n');\n } else {\n out(' \\x1b[31m\\x1b[1mSome checks failed. Please fix the issues above.\\x1b[0m\\n\\n');\n process.exitCode = 1;\n }\n });\n\n // ── Banner (always show) ──────────────────────────────────────────────────\n\n const o = (s: string) => process.stderr.write(s);\n const _b = (s: string) => `\\x1b[1m${s}\\x1b[0m`;\n const _d = (s: string) => `\\x1b[2m${s}\\x1b[0m`;\n const _c = (s: string) => `\\x1b[36m${s}\\x1b[0m`;\n const _g = (s: string) => `\\x1b[32m${s}\\x1b[0m`;\n const _m = (s: string) => `\\x1b[35m${s}\\x1b[0m`;\n\n o('\\n');\n o(_c(' ____ _ ____ ') + '\\n');\n o(_c(' | _ \\\\ __ _| |_ __ _/ ___| _ _ _ __ __ __') + '\\n');\n o(_c(' | | | |/ _` | __/ _` \\\\___ \\\\| | | | \\'_ \\\\\\\\ \\\\/ /') + '\\n');\n o(_c(' | |_| | (_| | || (_| |___) | |_| | | | |> < ') + '\\n');\n o(_c(' |____/ \\\\__,_|\\\\__\\\\__,_|____/ \\\\__, |_| |_/_/\\\\_\\\\') + '\\n');\n o(_c(' |___/ ') + '\\n');\n o('\\n');\n o(_b(' Cartography') + ' ' + _d('v' + VERSION) + '\\n');\n o(_d(' AI-powered Infrastructure Discovery & SOP Generation\\n'));\n o(_d(' Built on Claude Agent SDK\\n'));\n o('\\n');\n\n // ── Welcome Screen (no args → Befehlsübersicht) ─────────────────────────\n\n if (process.argv.length <= 2) {\n o(_d(' ────────────────────────────────────────────────\\n'));\n o('\\n');\n o(_b(' Commands:\\n'));\n o('\\n');\n o(` ${_g('discover')} ${_d('Scan infrastructure (Claude Sonnet)')}\\n`);\n o(` ${_g('seed')} ${_d('Manually add known tools/DBs/APIs')}\\n`);\n o(` ${_g('bookmarks')} ${_d('View browser bookmarks')}\\n`);\n o(` ${_g('export')} ${_d('[session]')} ${_d('Export Mermaid, JSON, YAML, HTML')}\\n`);\n o(` ${_g('show')} ${_d('[session]')} ${_d('Show session details')}\\n`);\n o(` ${_g('sessions')} ${_d('List all sessions')}\\n`);\n o(` ${_g('doctor')} ${_d('Check requirements (kubectl, aws, gcloud, az)')}\\n`);\n o(` ${_g('docs')} ${_d('Full feature reference')}\\n`);\n o('\\n');\n o(_d(' ────────────────────────────────────────────────\\n'));\n o('\\n');\n o(_b(' Quick Start:\\n'));\n o('\\n');\n o(` ${_m('$')} ${_b('datasynx-cartography doctor')} ${_d('Check requirements')}\\n`);\n o(` ${_m('$')} ${_b('datasynx-cartography seed')} ${_d('Add known infrastructure')}\\n`);\n o(` ${_m('$')} ${_b('datasynx-cartography discover')} ${_d('One-time scan')}\\n`);\n o('\\n');\n o(_d(' Docs: datasynx-cartography docs\\n'));\n o(_d(' Help: datasynx-cartography --help\\n'));\n o(_d(' npm: @datasynx/agentic-ai-cartography\\n'));\n o('\\n');\n return;\n }\n\n o(_d(' ────────────────────────────────────────────────\\n'));\n o('\\n');\n\n // ── Parse ──────────────────────────────────────────────────────────────────\n\n program.exitOverride((err) => {\n if (err.code === 'commander.helpDisplayed') {\n process.exitCode = 0;\n } else {\n process.exitCode = 2;\n }\n });\n\n program.parse(process.argv);\n}\n","import { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nfunction isOAuthLoggedIn(): boolean {\n // Claude CLI speichert OAuth-Tokens in ~/.claude/.credentials.json\n const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';\n const credFile = join(home, '.claude', '.credentials.json');\n if (!existsSync(credFile)) return false;\n try {\n const creds = JSON.parse(readFileSync(credFile, 'utf8')) as Record<string, unknown>;\n const oauth = creds['claudeAiOauth'] as Record<string, unknown> | undefined;\n return typeof oauth?.['accessToken'] === 'string' && oauth['accessToken'].length > 0;\n } catch {\n return false;\n }\n}\n\nexport function checkPrerequisites(): void {\n // Claude CLI vorhanden?\n try {\n execSync('claude --version', { stdio: 'pipe' });\n } catch {\n process.stderr.write(\n '\\n❌ Claude CLI nicht gefunden.\\n' +\n ' Datasynx Cartography braucht die Claude CLI als Runtime-Dependency.\\n\\n' +\n ' Installieren:\\n' +\n ' npm install -g @anthropic-ai/claude-code\\n' +\n ' # oder\\n' +\n ' curl -fsSL https://claude.ai/install.sh | bash\\n\\n' +\n ' Danach: claude login\\n\\n'\n );\n process.exitCode = 1;\n throw new Error('Claude CLI not found');\n }\n\n // Auth prüfen: API Key ODER OAuth-Login (claude.ai Subscription)\n const hasApiKey = Boolean(process.env.ANTHROPIC_API_KEY);\n const hasOAuth = isOAuthLoggedIn();\n\n if (!hasApiKey && !hasOAuth) {\n process.stderr.write(\n '⚠ Keine Authentifizierung gefunden. Bitte eine der folgenden Optionen:\\n\\n' +\n ' Option A — claude.ai Subscription (empfohlen):\\n' +\n ' claude login\\n\\n' +\n ' Option B — API Key:\\n' +\n ' export ANTHROPIC_API_KEY=sk-ant-...\\n\\n'\n );\n } else if (hasOAuth && !hasApiKey) {\n process.stderr.write('✓ Eingeloggt via claude login (Subscription)\\n');\n }\n}\n","import Database from 'better-sqlite3';\nimport { mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type {\n CartographyConfig, DiscoveryNode, DiscoveryEdge,\n NodeRow, EdgeRow, SessionRow, SOP, Connection,\n} from './types.js';\n\nexport interface ConnectionRow extends Connection {\n sessionId: string;\n createdAt: string;\n}\n\n// ── DB Row Types ──\n\nexport interface EventRow {\n id: string;\n sessionId: string;\n taskId?: string;\n timestamp: string;\n eventType: string;\n process: string;\n pid: number;\n target?: string;\n targetType?: string;\n port?: number;\n durationMs?: number;\n}\n\nexport interface TaskRow {\n id: string;\n sessionId: string;\n description?: string;\n startedAt: string;\n completedAt?: string;\n steps: string;\n involvedServices: string;\n status: 'active' | 'completed' | 'cancelled';\n isSOPCandidate: boolean;\n}\n\nexport interface WorkflowRow {\n id: string;\n sessionId: string;\n name?: string;\n pattern: string;\n taskIds: string;\n occurrences: number;\n firstSeen: string;\n lastSeen: string;\n avgDurationMs: number;\n involvedServices: string;\n}\n\nconst SCHEMA = `\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\nPRAGMA busy_timeout = 5000;\n\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n mode TEXT NOT NULL CHECK (mode IN ('discover')),\n started_at TEXT NOT NULL,\n completed_at TEXT,\n config TEXT NOT NULL DEFAULT '{}'\n);\n\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT NOT NULL,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n discovered_via TEXT,\n discovered_at TEXT NOT NULL,\n path_id TEXT,\n depth INTEGER DEFAULT 0,\n confidence REAL DEFAULT 0.5,\n metadata TEXT NOT NULL DEFAULT '{}',\n tags TEXT NOT NULL DEFAULT '[]',\n domain TEXT,\n sub_domain TEXT,\n quality_score REAL,\n PRIMARY KEY (id, session_id)\n);\n\nCREATE TABLE IF NOT EXISTS connections (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_asset_id TEXT NOT NULL,\n target_asset_id TEXT NOT NULL,\n type TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_id TEXT NOT NULL,\n target_id TEXT NOT NULL,\n relationship TEXT NOT NULL,\n evidence TEXT,\n confidence REAL DEFAULT 0.5,\n discovered_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS activity_events (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n task_id TEXT,\n timestamp TEXT NOT NULL,\n event_type TEXT NOT NULL,\n process TEXT NOT NULL,\n pid INTEGER NOT NULL,\n target TEXT,\n target_type TEXT,\n port INTEGER,\n duration_ms INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS tasks (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n description TEXT,\n started_at TEXT NOT NULL,\n completed_at TEXT,\n steps TEXT NOT NULL DEFAULT '[]',\n involved_services TEXT NOT NULL DEFAULT '[]',\n status TEXT DEFAULT 'active' CHECK (status IN ('active','completed','cancelled')),\n is_sop_candidate INTEGER DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS workflows (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n name TEXT,\n pattern TEXT NOT NULL,\n task_ids TEXT NOT NULL DEFAULT '[]',\n occurrences INTEGER DEFAULT 1,\n first_seen TEXT NOT NULL,\n last_seen TEXT NOT NULL,\n avg_duration_ms INTEGER,\n involved_services TEXT NOT NULL DEFAULT '[]'\n);\n\nCREATE TABLE IF NOT EXISTS sops (\n id TEXT PRIMARY KEY,\n workflow_id TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT NOT NULL,\n steps TEXT NOT NULL,\n involved_systems TEXT NOT NULL DEFAULT '[]',\n estimated_duration TEXT,\n frequency TEXT,\n generated_at TEXT NOT NULL,\n confidence REAL DEFAULT 0.5\n);\n\nCREATE TABLE IF NOT EXISTS node_approvals (\n pattern TEXT PRIMARY KEY,\n action TEXT NOT NULL CHECK (action IN ('save','ignore','auto')),\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_nodes_session ON nodes(session_id);\nCREATE INDEX IF NOT EXISTS idx_edges_session ON edges(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_session ON activity_events(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_task ON activity_events(task_id);\nCREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);\nCREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);\n`;\n\nexport class CartographyDB {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n mkdirSync(dirname(dbPath), { recursive: true });\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('busy_timeout = 5000');\n this.migrate();\n }\n\n private migrate(): void {\n const version = (this.db.pragma('user_version', { simple: true }) as number);\n if (version === 0) {\n this.db.exec(SCHEMA);\n this.db.pragma('user_version = 2');\n } else if (version === 1) {\n // v1 → v2: add hex map columns to nodes + connections table\n const cols = (this.db.prepare(\"PRAGMA table_info(nodes)\").all() as Array<{ name: string }>).map(c => c.name);\n if (!cols.includes('domain')) this.db.exec('ALTER TABLE nodes ADD COLUMN domain TEXT');\n if (!cols.includes('sub_domain')) this.db.exec('ALTER TABLE nodes ADD COLUMN sub_domain TEXT');\n if (!cols.includes('quality_score')) this.db.exec('ALTER TABLE nodes ADD COLUMN quality_score REAL');\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS connections (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_asset_id TEXT NOT NULL,\n target_asset_id TEXT NOT NULL,\n type TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);\n `);\n this.db.pragma('user_version = 2');\n }\n }\n\n close(): void {\n this.db.pragma('optimize');\n this.db.close();\n }\n\n // ── Sessions ────────────────────────────\n\n createSession(mode: 'discover', config: CartographyConfig): string {\n const id = crypto.randomUUID();\n this.db.prepare(\n 'INSERT INTO sessions (id, mode, started_at, config) VALUES (?, ?, ?, ?)'\n ).run(id, mode, new Date().toISOString(), JSON.stringify(config));\n return id;\n }\n\n endSession(id: string): void {\n this.db.prepare('UPDATE sessions SET completed_at = ? WHERE id = ?')\n .run(new Date().toISOString(), id);\n }\n\n getSession(id: string): SessionRow | undefined {\n const row = this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n getLatestSession(mode?: string): SessionRow | undefined {\n const row = mode\n ? this.db.prepare('SELECT * FROM sessions WHERE mode = ? ORDER BY rowid DESC LIMIT 1').get(mode) as Record<string, unknown> | undefined\n : this.db.prepare('SELECT * FROM sessions ORDER BY rowid DESC LIMIT 1').get() as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n getSessions(): SessionRow[] {\n const rows = this.db.prepare('SELECT * FROM sessions ORDER BY rowid DESC').all() as Record<string, unknown>[];\n return rows.map(r => this.mapSession(r));\n }\n\n private mapSession(r: Record<string, unknown>): SessionRow {\n return {\n id: r['id'] as string,\n mode: r['mode'] as 'discover',\n startedAt: r['started_at'] as string,\n completedAt: (r['completed_at'] as string | null) ?? undefined,\n config: r['config'] as string,\n };\n }\n\n // ── Nodes ───────────────────────────────\n\n upsertNode(sessionId: string, node: DiscoveryNode, depth = 0): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO nodes\n (id, session_id, type, name, discovered_via, discovered_at, depth, confidence, metadata, tags,\n domain, sub_domain, quality_score)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n node.id, sessionId, node.type, node.name, node.discoveredVia,\n new Date().toISOString(), depth, node.confidence,\n JSON.stringify(node.metadata ?? {}),\n JSON.stringify(node.tags ?? []),\n node.domain ?? null,\n node.subDomain ?? null,\n node.qualityScore ?? null,\n );\n }\n\n getNodes(sessionId: string): NodeRow[] {\n const rows = this.db.prepare('SELECT * FROM nodes WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => this.mapNode(r));\n }\n\n private mapNode(r: Record<string, unknown>): NodeRow {\n return {\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n type: r['type'] as NodeRow['type'],\n name: r['name'] as string,\n discoveredVia: r['discovered_via'] as string,\n discoveredAt: r['discovered_at'] as string,\n depth: r['depth'] as number,\n confidence: r['confidence'] as number,\n metadata: JSON.parse(r['metadata'] as string) as Record<string, unknown>,\n tags: JSON.parse(r['tags'] as string) as string[],\n pathId: r['path_id'] as string | undefined,\n domain: (r['domain'] as string | null) ?? undefined,\n subDomain: (r['sub_domain'] as string | null) ?? undefined,\n qualityScore: (r['quality_score'] as number | null) ?? undefined,\n };\n }\n\n deleteNode(sessionId: string, nodeId: string): void {\n this.db.prepare('DELETE FROM nodes WHERE session_id = ? AND id = ?').run(sessionId, nodeId);\n // Remove orphaned edges\n this.db.prepare(\n 'DELETE FROM edges WHERE session_id = ? AND (source_id = ? OR target_id = ?)'\n ).run(sessionId, nodeId, nodeId);\n }\n\n // ── Edges ───────────────────────────────\n\n insertEdge(sessionId: string, edge: DiscoveryEdge): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT OR IGNORE INTO edges\n (id, session_id, source_id, target_id, relationship, evidence, confidence, discovered_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, edge.sourceId, edge.targetId,\n edge.relationship, edge.evidence, edge.confidence,\n new Date().toISOString(),\n );\n }\n\n getEdges(sessionId: string): EdgeRow[] {\n const rows = this.db.prepare('SELECT * FROM edges WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n sourceId: r['source_id'] as string,\n targetId: r['target_id'] as string,\n relationship: r['relationship'] as EdgeRow['relationship'],\n evidence: r['evidence'] as string,\n confidence: r['confidence'] as number,\n discoveredAt: r['discovered_at'] as string,\n }));\n }\n\n // ── Events ──────────────────────────────\n\n insertEvent(sessionId: string, event: Pick<EventRow, 'eventType' | 'process' | 'pid' | 'target' | 'targetType' | 'port'>, taskId?: string): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO activity_events\n (id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, taskId ?? null, new Date().toISOString(),\n event.eventType, event.process, event.pid,\n event.target ?? null, event.targetType ?? null, event.port ?? null,\n );\n }\n\n getEvents(sessionId: string, since?: string): EventRow[] {\n const rows = since\n ? this.db.prepare('SELECT * FROM activity_events WHERE session_id = ? AND timestamp > ? ORDER BY timestamp').all(sessionId, since) as Record<string, unknown>[]\n : this.db.prepare('SELECT * FROM activity_events WHERE session_id = ? ORDER BY timestamp').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n taskId: r['task_id'] as string | undefined,\n timestamp: r['timestamp'] as string,\n eventType: r['event_type'] as EventRow['eventType'],\n process: r['process'] as string,\n pid: r['pid'] as number,\n target: r['target'] as string | undefined,\n targetType: r['target_type'] as EventRow['targetType'],\n port: r['port'] as number | undefined,\n durationMs: r['duration_ms'] as number | undefined,\n }));\n }\n\n // ── Tasks ───────────────────────────────\n\n startTask(sessionId: string, description?: string): string {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO tasks (id, session_id, description, started_at, steps, involved_services, status)\n VALUES (?, ?, ?, ?, '[]', '[]', 'active')\n `).run(id, sessionId, description ?? null, new Date().toISOString());\n return id;\n }\n\n endCurrentTask(sessionId: string): void {\n this.db.prepare(`\n UPDATE tasks SET status = 'completed', completed_at = ?\n WHERE session_id = ? AND status = 'active'\n `).run(new Date().toISOString(), sessionId);\n }\n\n updateTaskDescription(sessionId: string, description: string): void {\n this.db.prepare(`\n UPDATE tasks SET description = ?\n WHERE session_id = ? AND status = 'active'\n `).run(description, sessionId);\n }\n\n getActiveTask(sessionId: string): TaskRow | undefined {\n const row = this.db.prepare(\n \"SELECT * FROM tasks WHERE session_id = ? AND status = 'active' LIMIT 1\"\n ).get(sessionId) as Record<string, unknown> | undefined;\n return row ? this.mapTask(row) : undefined;\n }\n\n getTasks(sessionId: string): TaskRow[] {\n const rows = this.db.prepare('SELECT * FROM tasks WHERE session_id = ? ORDER BY started_at').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => this.mapTask(r));\n }\n\n private mapTask(r: Record<string, unknown>): TaskRow {\n return {\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n description: r['description'] as string | undefined,\n startedAt: r['started_at'] as string,\n completedAt: r['completed_at'] as string | undefined,\n steps: r['steps'] as string,\n involvedServices: r['involved_services'] as string,\n status: r['status'] as TaskRow['status'],\n isSOPCandidate: Boolean(r['is_sop_candidate']),\n };\n }\n\n // ── Workflows ───────────────────────────\n\n insertWorkflow(sessionId: string, data: Omit<WorkflowRow, 'id'>): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO workflows\n (id, session_id, name, pattern, task_ids, occurrences,\n first_seen, last_seen, avg_duration_ms, involved_services)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, data.name ?? null, data.pattern,\n data.taskIds, data.occurrences,\n data.firstSeen, data.lastSeen, data.avgDurationMs,\n data.involvedServices,\n );\n }\n\n getWorkflows(sessionId: string): WorkflowRow[] {\n const rows = this.db.prepare('SELECT * FROM workflows WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n name: r['name'] as string | undefined,\n pattern: r['pattern'] as string,\n taskIds: r['task_ids'] as string,\n occurrences: r['occurrences'] as number,\n firstSeen: r['first_seen'] as string,\n lastSeen: r['last_seen'] as string,\n avgDurationMs: r['avg_duration_ms'] as number,\n involvedServices: r['involved_services'] as string,\n }));\n }\n\n // ── SOPs ────────────────────────────────\n\n insertSOP(sop: { workflowId: string } & SOP): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO sops\n (id, workflow_id, title, description, steps, involved_systems,\n estimated_duration, frequency, generated_at, confidence)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sop.workflowId, sop.title, sop.description,\n JSON.stringify(sop.steps),\n JSON.stringify(sop.involvedSystems),\n sop.estimatedDuration, sop.frequency,\n new Date().toISOString(), sop.confidence,\n );\n }\n\n getSOPs(sessionId: string): Array<SOP & { id: string; workflowId: string }> {\n const rows = this.db.prepare(`\n SELECT s.* FROM sops s\n JOIN workflows w ON s.workflow_id = w.id\n WHERE w.session_id = ?\n `).all(sessionId) as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n workflowId: r['workflow_id'] as string,\n title: r['title'] as string,\n description: r['description'] as string,\n steps: JSON.parse(r['steps'] as string) as SOP['steps'],\n involvedSystems: JSON.parse(r['involved_systems'] as string) as string[],\n estimatedDuration: r['estimated_duration'] as string,\n frequency: r['frequency'] as string,\n confidence: r['confidence'] as number,\n }));\n }\n\n markTaskAsSOPCandidate(taskId: string): void {\n this.db.prepare('UPDATE tasks SET is_sop_candidate = 1 WHERE id = ?').run(taskId);\n }\n\n getAllSOPs(): Array<SOP & { id: string; workflowId: string; generatedAt: string }> {\n const rows = this.db.prepare('SELECT * FROM sops ORDER BY generated_at DESC').all() as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n workflowId: r['workflow_id'] as string,\n title: r['title'] as string,\n description: r['description'] as string,\n steps: JSON.parse(r['steps'] as string) as SOP['steps'],\n involvedSystems: JSON.parse(r['involved_systems'] as string) as string[],\n estimatedDuration: r['estimated_duration'] as string,\n frequency: r['frequency'] as string,\n confidence: r['confidence'] as number,\n generatedAt: r['generated_at'] as string,\n }));\n }\n\n // ── Connections (user-created hex map links) ─────────────────────────────\n\n upsertConnection(sessionId: string, conn: Omit<Connection, 'id'>): string {\n // Idempotent: same source+target+type = same connection\n const existing = this.db.prepare(\n 'SELECT id FROM connections WHERE session_id = ? AND source_asset_id = ? AND target_asset_id = ?'\n ).get(sessionId, conn.sourceAssetId, conn.targetAssetId) as { id: string } | undefined;\n if (existing) return existing.id;\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO connections (id, session_id, source_asset_id, target_asset_id, type, created_at)\n VALUES (?, ?, ?, ?, ?, ?)\n `).run(id, sessionId, conn.sourceAssetId, conn.targetAssetId, conn.type ?? null, new Date().toISOString());\n return id;\n }\n\n getConnections(sessionId: string): ConnectionRow[] {\n const rows = this.db.prepare('SELECT * FROM connections WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n sourceAssetId: r['source_asset_id'] as string,\n targetAssetId: r['target_asset_id'] as string,\n type: (r['type'] as string | null) ?? undefined,\n createdAt: r['created_at'] as string,\n }));\n }\n\n deleteConnection(sessionId: string, connectionId: string): void {\n this.db.prepare('DELETE FROM connections WHERE session_id = ? AND id = ?').run(sessionId, connectionId);\n }\n\n // ── Approvals ───────────────────────────\n\n setApproval(pattern: string, action: 'save' | 'ignore' | 'auto'): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO node_approvals (pattern, action, created_at) VALUES (?, ?, ?)\n `).run(pattern, action, new Date().toISOString());\n }\n\n getApproval(pattern: string): string | undefined {\n const row = this.db.prepare('SELECT action FROM node_approvals WHERE pattern = ?').get(pattern) as { action: string } | undefined;\n return row?.action;\n }\n\n // ── Stats ───────────────────────────────\n\n getStats(sessionId: string): { nodes: number; edges: number; events: number; tasks: number } {\n const nodes = (this.db.prepare('SELECT COUNT(*) as c FROM nodes WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const edges = (this.db.prepare('SELECT COUNT(*) as c FROM edges WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const events = (this.db.prepare('SELECT COUNT(*) as c FROM activity_events WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const tasks = (this.db.prepare('SELECT COUNT(*) as c FROM tasks WHERE session_id = ?').get(sessionId) as { c: number }).c;\n return { nodes, edges, events, tasks };\n }\n}\n","import { z } from 'zod';\nimport type { CartographyDB } from './db.js';\nimport { NODE_TYPES, EDGE_RELATIONSHIPS, SOPStepSchema } from './types.js';\nimport { scanAllBookmarks, scanAllHistory } from './bookmarks.js';\n\n// Lazy import to avoid hard-wiring SDK at module parse time\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype McpServer = any;\n\nexport interface CartographyToolsOptions {\n /** Called when the agent needs a human answer. Return the user's response. */\n onAskUser?: (question: string, context?: string) => Promise<string>;\n}\n\nexport function stripSensitive(target: string): string {\n try {\n const url = new URL(target.startsWith('http') ? target : `tcp://${target}`);\n return `${url.hostname}${url.port ? ':' + url.port : ''}`;\n } catch {\n return target\n .replace(/\\/.*$/, '')\n .replace(/\\?.*$/, '')\n .replace(/@.*:/, ':');\n }\n}\n\nexport async function createCartographyTools(\n db: CartographyDB,\n sessionId: string,\n opts: CartographyToolsOptions = {},\n): Promise<McpServer> {\n // Dynamically import the SDK so missing package doesn't crash at load time\n const sdk = await import('@anthropic-ai/claude-code');\n const { tool, createSdkMcpServer } = sdk as {\n tool: (name: string, description: string, schema: z.ZodRawShape, handler: (args: Record<string, unknown>) => Promise<{ content: Array<{ type: string; text: string }> }>) => unknown;\n createSdkMcpServer: (opts: { name: string; version: string; tools: unknown[] }) => McpServer;\n };\n\n const tools = [\n tool('save_node', 'Save an infrastructure node to the catalog', {\n id: z.string(),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discoveredVia: z.string(),\n confidence: z.number().min(0).max(1),\n metadata: z.record(z.unknown()).optional(),\n tags: z.array(z.string()).optional(),\n domain: z.string().optional().describe('Business domain, e.g. \"Marketing\", \"Finance\"'),\n subDomain: z.string().optional().describe('Sub-domain, e.g. \"Forecast client orders\"'),\n qualityScore: z.number().min(0).max(100).optional().describe('Data quality score 0–100'),\n }, async (args) => {\n const node = {\n id: stripSensitive(args['id'] as string),\n type: args['type'] as typeof NODE_TYPES[number],\n name: args['name'] as string,\n discoveredVia: args['discoveredVia'] as string,\n confidence: args['confidence'] as number,\n metadata: (args['metadata'] as Record<string, unknown>) ?? {},\n tags: (args['tags'] as string[]) ?? [],\n domain: args['domain'] as string | undefined,\n subDomain: args['subDomain'] as string | undefined,\n qualityScore: args['qualityScore'] as number | undefined,\n };\n db.upsertNode(sessionId, node);\n return { content: [{ type: 'text', text: `✓ Node: ${node.id}` }] };\n }),\n\n tool('save_edge', 'Save a relationship (edge) between two nodes — ALWAYS save edges when connections are clear', {\n sourceId: z.string(),\n targetId: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string(),\n confidence: z.number().min(0).max(1),\n }, async (args) => {\n db.insertEdge(sessionId, {\n sourceId: args['sourceId'] as string,\n targetId: args['targetId'] as string,\n relationship: args['relationship'] as typeof EDGE_RELATIONSHIPS[number],\n evidence: args['evidence'] as string,\n confidence: args['confidence'] as number,\n });\n return { content: [{ type: 'text', text: `✓ ${args['sourceId']}→${args['targetId']}` }] };\n }),\n\n tool('get_catalog', 'Get the current catalog — use before save_node to avoid duplicates', {\n includeEdges: z.boolean().default(true),\n }, async (args) => {\n const nodes = db.getNodes(sessionId);\n const edges = (args['includeEdges'] as boolean) ? db.getEdges(sessionId) : [];\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: { nodes: nodes.length, edges: edges.length },\n nodeIds: nodes.map(n => n.id),\n }),\n }],\n };\n }),\n\n tool('ask_user', 'Ask the user a question — for clarifications, missing context, or consent (e.g. before scanning browser history)', {\n question: z.string().describe('The question for the user (clear and specific)'),\n context: z.string().optional().describe('Optional context explaining why this is relevant'),\n }, async (args) => {\n const question = args['question'] as string;\n const context = args['context'] as string | undefined;\n\n if (opts.onAskUser) {\n const answer = await opts.onAskUser(question, context);\n return { content: [{ type: 'text', text: answer }] };\n }\n\n // Fallback when not interactive (piped input, daemon, etc.)\n return {\n content: [{ type: 'text', text: '(Non-interactive mode — please continue without this information)' }],\n };\n }),\n\n tool('scan_bookmarks', 'Scan all browser bookmarks — hostnames only, no personal data (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)', {\n minConfidence: z.number().min(0).max(1).default(0.5).optional(),\n }, async () => {\n const hosts = await scanAllBookmarks();\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: hosts.length,\n hosts: hosts.map(h => ({\n hostname: h.hostname,\n port: h.port,\n protocol: h.protocol,\n source: h.source,\n })),\n note: 'Hostnames only — no paths, no personal data. Classify each as a business tool (save_node) or ignore (social media, news, shopping).',\n }),\n }],\n };\n }),\n\n tool('scan_browser_history', 'Scan browser history — anonymized hostnames + visit frequency. ALWAYS call ask_user for consent before using this tool.', {\n minVisits: z.number().min(1).default(3).optional().describe('Minimum visit count to include a host (filters rarely-visited sites)'),\n }, async (args) => {\n const minVisits = (args['minVisits'] as number | undefined) ?? 3;\n const hosts = await scanAllHistory();\n const filtered = hosts.filter(h => h.visitCount >= minVisits);\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: filtered.length,\n note: 'Anonymized — hostnames only, no URLs, no paths, no personal data. Classify business tools as saas_tool nodes.',\n hosts: filtered.map(h => ({\n hostname: h.hostname,\n visitCount: h.visitCount,\n protocol: h.protocol,\n source: h.source,\n })),\n }),\n }],\n };\n }),\n\n tool('scan_local_databases', 'Scan for local database files and running DB servers — PostgreSQL databases, MySQL, SQLite files from installed apps', {\n deep: z.boolean().default(false).optional().describe('Also search home directory recursively for SQLite/DB files (slower)'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const { homedir } = await import('node:os');\n const { existsSync } = await import('node:fs');\n const deep = (args['deep'] as boolean | undefined) ?? false;\n const HOME = homedir();\n\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 10_000, shell: '/bin/sh' }).toString().trim();\n } catch {\n return '';\n }\n };\n\n const results: Record<string, string> = {};\n\n // PostgreSQL\n results['POSTGRES_DATABASES'] = run('psql -lqt 2>/dev/null | grep -v \"template0\\\\|template1\" | awk \\'{print $1}\\' | grep -v \"^$\\\\|^|\"') || '(psql not running or not available)';\n results['POSTGRES_CLUSTERS'] = run('pg_lsclusters 2>/dev/null') || '(pg_lsclusters not available)';\n\n // MySQL / MariaDB\n results['MYSQL_DATABASES'] = run('mysql --connect-timeout=3 -e \"SHOW DATABASES;\" 2>/dev/null') || '(mysql not running or requires auth)';\n\n // MongoDB\n results['MONGODB_DATABASES'] = run('mongosh --quiet --eval \"db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join(\\'\\\\n\\')\" 2>/dev/null') || '(mongosh not available)';\n\n // Redis\n results['REDIS_INFO'] = run('redis-cli info server 2>/dev/null | head -5') || '(redis-cli not available)';\n\n // SQLite files in app data directories\n const appDirs = [`${HOME}/.config`, `${HOME}/.local/share`, `${HOME}/Library/Application Support`, '/var/lib'].filter(d => existsSync(d));\n if (appDirs.length > 0) {\n const findCmds = appDirs.map(d => `find \"${d}\" -maxdepth 4 \\\\( -name \"*.sqlite\" -o -name \"*.sqlite3\" -o -name \"*.db\" \\\\) 2>/dev/null`).join('; ');\n results['SQLITE_APP_FILES'] = run(`{ ${findCmds}; } | head -80`) || '(none found)';\n }\n\n // Deep home scan\n if (deep) {\n results['SQLITE_DEEP_SCAN'] = run(`find \"${HOME}\" -maxdepth 6 \\\\( -name \"*.sqlite\" -o -name \"*.sqlite3\" -o -name \"*.db\" \\\\) -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" 2>/dev/null | head -100`) || '(none found)';\n }\n\n // DB config files (no credentials extracted)\n results['DB_CONFIG_FILES'] = run(`find \"${HOME}\" -maxdepth 4 \\\\( -name \".env\" -o -name \".env.local\" -o -name \"database.yml\" -o -name \"database.json\" -o -name \"docker-compose.yml\" \\\\) 2>/dev/null | head -20`) || '(none found)';\n\n const out = Object.entries(results).map(([k, v]) => `=== ${k} ===\\n${v}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_k8s_resources', 'Scan Kubernetes cluster via kubectl — 100% readonly (get, describe)', {\n namespace: z.string().optional().describe('Filter by namespace — empty = all namespaces'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const ns = args['namespace'] as string | undefined;\n const nsFlag = ns ? `-n ${ns}` : '--all-namespaces';\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 15_000, shell: '/bin/sh' }).toString().trim();\n } catch (e) {\n return `(error: ${e instanceof Error ? e.message.split('\\n')[0] : String(e)})`;\n }\n };\n const sections: [string, string][] = [\n ['CONTEXT', 'kubectl config current-context 2>/dev/null || echo \"(no context set)\"'],\n ['NODES', 'kubectl get nodes -o wide'],\n ['NAMESPACES', 'kubectl get namespaces'],\n ['SERVICES', `kubectl get services ${nsFlag}`],\n ['DEPLOYMENTS', `kubectl get deployments ${nsFlag}`],\n ['STATEFULSETS', `kubectl get statefulsets ${nsFlag}`],\n ['INGRESSES', `kubectl get ingress ${nsFlag} 2>/dev/null || echo \"(none)\"`],\n ['PODS_RUNNING', `kubectl get pods ${nsFlag} --field-selector=status.phase=Running 2>/dev/null | head -60`],\n ['CONFIGMAPS_SYSTEM', 'kubectl get configmaps -n kube-system 2>/dev/null | head -30'],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${run(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_aws_resources', 'Scan AWS infrastructure via AWS CLI — 100% readonly (describe, list)', {\n region: z.string().optional().describe('AWS Region — default: AWS_DEFAULT_REGION or profile'),\n profile: z.string().optional().describe('AWS CLI profile'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const region = args['region'] as string | undefined;\n const profile = args['profile'] as string | undefined;\n const env: NodeJS.ProcessEnv = { ...process.env };\n if (region) env['AWS_DEFAULT_REGION'] = region;\n const pf = profile ? `--profile ${profile}` : '';\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 20_000, shell: '/bin/sh', env }).toString().trim();\n } catch (e) {\n return `(error: ${e instanceof Error ? e.message.split('\\n')[0] : String(e)})`;\n }\n };\n const sections: [string, string][] = [\n ['IDENTITY', `aws sts get-caller-identity ${pf} --output json`],\n ['EC2', `aws ec2 describe-instances ${pf} --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,PublicIpAddress,PrivateIpAddress,Tags[?Key==\\`Name\\`].Value|[0]]' --output table`],\n ['RDS', `aws rds describe-db-instances ${pf} --query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus,Endpoint.Address,Endpoint.Port]' --output table`],\n ['ELB_V2', `aws elbv2 describe-load-balancers ${pf} --query 'LoadBalancers[*].[LoadBalancerName,DNSName,Type,State.Code]' --output table`],\n ['EKS', `aws eks list-clusters ${pf} --output json`],\n ['ELASTICACHE', `aws elasticache describe-cache-clusters ${pf} --query 'CacheClusters[*].[CacheClusterId,Engine,CacheClusterStatus]' --output table 2>/dev/null || echo \"(not available)\"`],\n ['S3', `aws s3 ls ${pf} 2>/dev/null || echo \"(not available)\"`],\n ['VPC', `aws ec2 describe-vpcs ${pf} --query 'Vpcs[*].[VpcId,CidrBlock,IsDefault,Tags[?Key==\\`Name\\`].Value|[0]]' --output table`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${run(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_gcp_resources', 'Scan Google Cloud Platform via gcloud CLI — 100% readonly (list, describe)', {\n project: z.string().optional().describe('GCP Project ID — default: current gcloud project'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const project = args['project'] as string | undefined;\n const pf = project ? `--project ${project}` : '';\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 20_000, shell: '/bin/sh' }).toString().trim();\n } catch (e) {\n return `(error: ${e instanceof Error ? e.message.split('\\n')[0] : String(e)})`;\n }\n };\n const sections: [string, string][] = [\n ['IDENTITY', `gcloud config list account --format='value(core.account)' 2>/dev/null; gcloud config get-value project 2>/dev/null`],\n ['COMPUTE_INSTANCES', `gcloud compute instances list ${pf} 2>/dev/null || echo \"(error)\"`],\n ['SQL_INSTANCES', `gcloud sql instances list ${pf} 2>/dev/null || echo \"(error)\"`],\n ['GKE_CLUSTERS', `gcloud container clusters list ${pf} 2>/dev/null || echo \"(error)\"`],\n ['CLOUD_RUN', `gcloud run services list ${pf} --platform managed 2>/dev/null || echo \"(error)\"`],\n ['CLOUD_FUNCTIONS', `gcloud functions list ${pf} 2>/dev/null || echo \"(error)\"`],\n ['REDIS', `gcloud redis instances list ${pf} --regions=- 2>/dev/null || echo \"(error)\"`],\n ['PUBSUB', `gcloud pubsub topics list ${pf} 2>/dev/null || echo \"(error)\"`],\n ['SPANNER', `gcloud spanner instances list ${pf} 2>/dev/null || echo \"(error)\"`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${run(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_azure_resources', 'Scan Azure infrastructure via az CLI — 100% readonly (list, show)', {\n subscription: z.string().optional().describe('Azure Subscription ID'),\n resourceGroup: z.string().optional().describe('Filter by resource group'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const sub = args['subscription'] as string | undefined;\n const rg = args['resourceGroup'] as string | undefined;\n const sf = sub ? `--subscription ${sub}` : '';\n const rf = rg ? `--resource-group ${rg}` : '';\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 20_000, shell: '/bin/sh' }).toString().trim();\n } catch (e) {\n return `(error: ${e instanceof Error ? e.message.split('\\n')[0] : String(e)})`;\n }\n };\n const sections: [string, string][] = [\n ['IDENTITY', `az account show --output json ${sf} 2>/dev/null || echo \"(not logged in — az login)\"`],\n ['VMS', `az vm list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['AKS', `az aks list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['SQL_SERVERS', `az sql server list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['POSTGRES', `az postgres server list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['REDIS', `az redis list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['WEBAPPS', `az webapp list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['CONTAINER_APPS', `az containerapp list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['FUNCTIONS', `az functionapp list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${run(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_installed_apps', 'Scan all installed apps and tools — IDEs, office, dev tools, business apps, databases', {\n searchHint: z.string().optional().describe('Optional search term to find specific tools (e.g. \"hubspot windsurf cursor\")'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const hint = args['searchHint'] as string | undefined;\n\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 15_000, shell: '/bin/sh' }).toString().trim();\n } catch {\n return '';\n }\n };\n\n const platform = process.platform;\n const results: Record<string, string> = {};\n\n if (platform === 'darwin') {\n // macOS: scan /Applications\n results['APPLICATIONS'] = run('ls /Applications/ 2>/dev/null | head -200') || '(empty)';\n results['USER_APPLICATIONS'] = run('ls ~/Applications/ 2>/dev/null | head -100') || '(empty)';\n // Homebrew\n results['BREW_CASKS'] = run('brew list --cask 2>/dev/null | head -100') || '(brew not installed)';\n results['BREW_FORMULAE'] = run('brew list --formula 2>/dev/null | head -150') || '(brew not installed)';\n // Spotlight — find .app bundles\n results['SPOTLIGHT_APPS'] = run('mdfind \"kMDItemKind == \\'Application\\'\" 2>/dev/null | grep -v \"^/System\" | grep -v \"^/Library/Apple\" | head -100') || '(Spotlight not available)';\n } else if (platform === 'linux') {\n // Linux: dpkg, snap, flatpak, .desktop files\n results['DPKG'] = run('dpkg --list 2>/dev/null | awk \\'{print $2}\\' | head -200') || '(dpkg not available)';\n results['SNAP'] = run('snap list 2>/dev/null | head -50') || '(snap not available)';\n results['FLATPAK'] = run('flatpak list 2>/dev/null | head -50') || '(flatpak not available)';\n results['DESKTOP_FILES'] = run('ls /usr/share/applications/*.desktop ~/.local/share/applications/*.desktop 2>/dev/null | xargs -I{} basename {} .desktop 2>/dev/null | head -100') || '(no .desktop files)';\n results['RPM'] = run('rpm -qa 2>/dev/null | head -200') || '(rpm not available)';\n } else if (platform === 'win32') {\n results['WINGET'] = run('winget list 2>/dev/null | head -100') || '(winget not available)';\n results['PROGRAMS_x64'] = run('reg query \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\" /s /v DisplayName 2>/dev/null | findstr DisplayName | head -100') || '(not available)';\n }\n\n // Check known dev/business tools via `which`\n const knownTools = [\n // IDEs & Editors\n 'code', 'code-insiders', 'cursor', 'windsurf', 'zed', 'vim', 'nvim', 'emacs', 'nano', 'sublime_text', 'atom',\n 'idea', 'webstorm', 'pycharm', 'goland', 'datagrip', 'clion', 'rider', 'phpstorm', 'rubymine', 'appcode',\n // Dev Tools\n 'git', 'gh', 'docker', 'docker-compose', 'podman', 'kubectl', 'helm', 'terraform', 'ansible',\n 'node', 'npm', 'npx', 'yarn', 'pnpm', 'bun', 'deno',\n 'python', 'python3', 'pip', 'pip3', 'pipenv', 'poetry', 'conda',\n 'ruby', 'gem', 'bundler', 'rails',\n 'java', 'mvn', 'gradle', 'kotlin',\n 'go', 'cargo', 'rustc',\n 'php', 'composer',\n 'dotnet', 'dotnet-sdk',\n // Databases\n 'psql', 'mysql', 'mysqladmin', 'mongo', 'mongosh', 'redis-cli', 'sqlite3', 'clickhouse-client',\n // Cloud CLIs\n 'aws', 'gcloud', 'az', 'heroku', 'fly', 'vercel', 'netlify', 'wrangler',\n // Infra\n 'vagrant', 'packer', 'consul', 'vault', 'nomad',\n // Communication / SaaS\n 'slack', 'discord', 'zoom', 'teams', 'skype', 'telegram', 'signal',\n // Browsers\n 'google-chrome', 'chromium', 'firefox', 'safari', 'brave', 'opera', 'edge',\n // Monitoring / Analytics\n 'datadog-agent', 'newrelic-agent', 'prometheus', 'grafana-cli',\n // Other tools\n 'ngrok', 'stripe', 'supabase', 'neon',\n ];\n\n const found: string[] = [];\n const notFound: string[] = [];\n for (const t of knownTools) {\n const r = run(`which ${t} 2>/dev/null`);\n if (r) found.push(`${t}: ${r}`);\n else notFound.push(t);\n }\n results['WHICH_FOUND'] = found.join('\\n') || '(none found)';\n results['WHICH_NOT_FOUND'] = notFound.join(', ');\n\n // Hint-based search: if user asks for specific tools, do targeted search\n if (hint) {\n const terms = hint.split(/[\\s,]+/).filter(Boolean);\n const hintResults: string[] = [];\n for (const term of terms) {\n const safe = term.replace(/[^a-zA-Z0-9._-]/g, '');\n if (!safe) continue;\n const r = run(`which ${safe} 2>/dev/null || find /Applications ~/Applications /usr/bin /usr/local/bin /opt/homebrew/bin ~/.local/bin 2>/dev/null -iname \"*${safe}*\" -maxdepth 3 2>/dev/null | head -5`);\n if (r) hintResults.push(`${term}: ${r}`);\n else hintResults.push(`${term}: (not found)`);\n }\n results['HINT_SEARCH'] = hintResults.join('\\n');\n }\n\n const out = Object.entries(results)\n .map(([k, v]) => `=== ${k} ===\\n${v}`)\n .join('\\n\\n');\n\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('save_sop', 'Save a Standard Operating Procedure', {\n workflowId: z.string(),\n title: z.string(),\n description: z.string(),\n steps: z.array(SOPStepSchema),\n involvedSystems: z.array(z.string()),\n estimatedDuration: z.string(),\n frequency: z.string(),\n confidence: z.number().min(0).max(1),\n }, async (args) => {\n db.insertSOP({\n workflowId: args['workflowId'] as string,\n title: args['title'] as string,\n description: args['description'] as string,\n steps: args['steps'] as ReturnType<typeof SOPStepSchema.parse>[],\n involvedSystems: args['involvedSystems'] as string[],\n estimatedDuration: args['estimatedDuration'] as string,\n frequency: args['frequency'] as string,\n confidence: args['confidence'] as number,\n });\n return { content: [{ type: 'text', text: `✓ SOP: ${args['title']}` }] };\n }),\n ];\n\n return createSdkMcpServer({\n name: 'cartography',\n version: '0.1.0',\n tools,\n });\n}\n","// PreToolUse Safety Hook — enforces read-only policy on all Bash calls\n\nimport type { HookCallback } from '@anthropic-ai/claude-code';\n\n// Word-boundary matched dangerous commands\nconst BLOCKED_CMDS =\n /\\b(rm|mv|cp|dd|mkfs|chmod|chown|chgrp|kill|killall|pkill|reboot|shutdown|poweroff|halt|systemctl\\s+(start|stop|restart|enable|disable)|service\\s+(start|stop|restart)|docker\\s+(rm|rmi|stop|kill|exec|run|build|push)|kubectl\\s+(delete|apply|edit|exec|run|create|patch)|apt|yum|dnf|pacman|pip\\s+install|npm\\s+(install|uninstall)|curl\\s+.*-X\\s*(POST|PUT|DELETE|PATCH)|wget\\s+-O|tee\\s)\\b/i;\n// Redirect operators (no word boundary needed)\nconst BLOCKED_REDIRECTS = />>|>[^>]/;\n\nexport type { HookCallback };\n\nexport const safetyHook: HookCallback = async (input) => {\n // Only intercept PreToolUse events (other hook events don't have tool_name)\n if (!('tool_name' in input)) return {};\n if ((input as { tool_name: string }).tool_name !== 'Bash') return {};\n\n const cmd = ((input as { tool_input: { command?: string } }).tool_input)?.command ?? '';\n\n if (BLOCKED_CMDS.test(cmd) || BLOCKED_REDIRECTS.test(cmd)) {\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `BLOCKED: \"${cmd}\" — read-only policy`,\n },\n };\n }\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n },\n };\n};\n","import type { CartographyDB } from './db.js';\nimport { createCartographyTools } from './tools.js';\nimport { safetyHook } from './safety.js';\nimport type { CartographyConfig } from './types.js';\n\n// ── Discovery Event Types ────────────────────────────────────────────────────\n\nexport type DiscoveryEvent =\n | { kind: 'thinking'; text: string }\n | { kind: 'tool_call'; tool: string; input: Record<string, unknown> }\n | { kind: 'tool_result'; tool: string; output: string }\n | { kind: 'turn'; turn: number }\n | { kind: 'done' };\n\nexport type AskUserFn = (question: string, context?: string) => Promise<string>;\n\n// ── runDiscovery ─────────────────────────────────────────────────────────────\n\nexport async function runDiscovery(\n config: CartographyConfig,\n db: CartographyDB,\n sessionId: string,\n onEvent?: (event: DiscoveryEvent) => void,\n onAskUser?: AskUserFn,\n hint?: string,\n): Promise<void> {\n const { query } = await import('@anthropic-ai/claude-code');\n const tools = await createCartographyTools(db, sessionId, { onAskUser });\n\n const hintSection = hint\n ? `\\n⚡ USER HINT (HIGH PRIORITY): The user wants to find these specific tools: \"${hint}\"\\n → Run scan_installed_apps(searchHint: \"${hint}\") IMMEDIATELY and save found tools as saas_tool nodes!\\n`\n : '';\n\n const systemPrompt = `You are an infrastructure discovery agent. Map the complete system landscape — local services, SaaS tools, AND all installed apps/tools of the user.\n${hintSection}\n━━ MANDATORY SEQUENCE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSTEP 1 — Browser Bookmarks (ALWAYS FIRST):\n Call scan_bookmarks() → classify every returned domain:\n • Business tools (GitHub, Notion, Jira, Linear, Vercel, AWS, Datadog, etc.) → save_node as saas_tool\n • Internal hosts (IPs, custom.company.com:PORT) → save_node as web_service\n • Personal (social media, news, streaming, shopping) → IGNORE, do NOT save\n\nSTEP 2 — Browser History (ASK FOR CONSENT FIRST):\n Call ask_user with question: \"May I scan your browser history anonymously? I only extract hostnames (no URLs, no personal data) to discover additional tools you use regularly. Answer yes or no.\"\n If user says yes → call scan_browser_history(minVisits: 5) → classify business tools as saas_tool nodes\n If user says no → skip and proceed to Step 3\n\nSTEP 3 — Installed Apps & Tools (VERY IMPORTANT):\n Call scan_installed_apps() → classify ALL found apps/tools:\n • IDEs (VS Code, Cursor, Windsurf, JetBrains, etc.) → save_node as saas_tool with category=\"ide\"\n • Office & productivity (Word, Excel, Notion, Obsidian, etc.) → save_node as saas_tool with category=\"productivity\"\n • Dev tools (Docker, kubectl, git, Node, Python, etc.) → save_node as saas_tool with category=\"dev-tool\"\n • Business apps (Slack, Zoom, HubSpot, Salesforce, etc.) → save_node as saas_tool with category=\"business\"\n • Browsers (Chrome, Firefox, Safari, etc.) → save_node as saas_tool with category=\"browser\"\n • Design tools (Figma, Sketch, Adobe, etc.) → save_node as saas_tool with category=\"design\"\n Save ALL relevant tools — even offline/local ones!\n\nSTEP 4 — Local Databases & Infrastructure:\n Call scan_local_databases() → discover running DB servers and SQLite files from installed apps\n • PostgreSQL running → save_node as database_server (id: \"database_server:localhost:5432\")\n • MySQL running → save_node as database_server (id: \"database_server:localhost:3306\")\n • MongoDB running → save_node as database_server\n • Redis running → save_node as cache_server\n • SQLite files in app directories → save_node as database if clearly a business app DB\n Then run: ss -tlnp && ps aux → identify all listening ports/processes\n Deepen each service: DB→schemas, API→endpoints, Queue→topics\n\nSTEP 5 — Cloud & Kubernetes (if CLI available):\n scan_k8s_resources() → Nodes, Services, Pods, Deployments, Ingresses\n scan_aws_resources() → EC2, RDS, ELB, EKS, ElastiCache, S3 (if AWS CLI + credentials)\n scan_gcp_resources() → Compute, SQL, GKE, Cloud Run, Functions (if gcloud + auth)\n scan_azure_resources() → VMs, AKS, SQL, Redis, WebApps (if az CLI + login)\n Errors / \"not available\" → ignore, continue with next tool\n\nSTEP 6 — Config Files:\n .env, docker-compose.yml, application.yml, kubernetes/*.yml\n Extract host:port only — NO credentials\n\nSTEP 7 — Clarifying Questions:\n Use ask_user() when: a service is unclear, context is missing, or user input would be helpful\n Examples: \"What environment is this (dev/staging/prod)?\", \"Is <host> an internal tool?\"\n\nSTEP 8 — EDGES (CRITICAL — do NOT skip!):\n After discovering nodes, ALWAYS map relationships with save_edge:\n • Developer uses IDE → save_edge(\"saas_tool:vscode\", \"saas_tool:github.com\", \"uses\")\n • App connects to Database → save_edge(app_id, db_id, \"connects_to\")\n • Service calls API → save_edge(service_id, api_id, \"calls\")\n • Container contains Service → save_edge(container_id, service_id, \"contains\")\n • Service reads from Queue → save_edge(service_id, queue_id, \"reads_from\")\n • Service writes to Database → save_edge(service_id, db_id, \"writes_to\")\n • App depends on Cache → save_edge(app_id, cache_id, \"depends_on\")\n Think: which tools does the developer use together? What connects to what?\n Use get_catalog to see all node IDs before saving edges.\n\nSTEP 9 — Done when all leads are exhausted.\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nPORT MAPPING: 5432=postgres, 3306=mysql, 27017=mongodb, 6379=redis,\n9092=kafka, 5672=rabbitmq, 80/443/8080/3000=web_service,\n9090=prometheus, 8500=consul, 8200=vault, 2379=etcd\n\nRULES:\n• Read-only only (ss, ps, cat, head, curl -s, docker inspect, kubectl get)\n• Node IDs: \"type:host:port\" or \"type:name\" — no paths, no credentials\n• saas_tool IDs: \"saas_tool:github.com\", \"saas_tool:vscode\", \"saas_tool:cursor\"\n• Installed-app IDs: \"saas_tool:<appname>\" e.g. \"saas_tool:slack\", \"saas_tool:docker-desktop\"\n• Confidence: 0.9 directly observed, 0.7 from config/bookmarks/apps, 0.5 inferred\n• metadata allowed: { description, category, port, version, path } — no passwords\n• Call get_catalog before save_node → avoid duplicates\n• Save edges whenever connections are clearly identifiable\n\nEntry points: ${config.entryPoints.join(', ')}`;\n\n const initialPrompt = hint\n ? `Start discovery with USER HINT: \"${hint}\".\nImmediately run scan_installed_apps(searchHint: \"${hint}\") to search for these tools.\nThen scan_bookmarks, then local services.\nUse ask_user when you need context from the user.`\n : `Start discovery now.\nFirst, IMMEDIATELY run scan_bookmarks — before using ss or ps.\nThen ask for browser history consent (Step 2).\nThen scan_installed_apps() for all installed apps and tools.\nThen scan_local_databases() for database servers and SQLite files.\nThen systematically scan local services, then config files.\nFinally, map all edges (Step 8 — critical!) before finishing.\nUse ask_user when you need context from the user.`;\n\n let turnCount = 0;\n\n for await (const msg of query({\n prompt: initialPrompt,\n options: {\n model: config.agentModel,\n maxTurns: config.maxTurns,\n customSystemPrompt: systemPrompt,\n mcpServers: { cartography: tools },\n allowedTools: [\n 'Bash',\n 'mcp__cartograph__save_node',\n 'mcp__cartograph__save_edge',\n 'mcp__cartograph__get_catalog',\n 'mcp__cartograph__scan_bookmarks',\n 'mcp__cartograph__scan_browser_history',\n 'mcp__cartograph__scan_installed_apps',\n 'mcp__cartograph__scan_local_databases',\n 'mcp__cartograph__scan_k8s_resources',\n 'mcp__cartograph__scan_aws_resources',\n 'mcp__cartograph__scan_gcp_resources',\n 'mcp__cartograph__scan_azure_resources',\n 'mcp__cartograph__ask_user',\n ],\n hooks: {\n PreToolUse: [{ matcher: 'Bash', hooks: [safetyHook] }],\n },\n permissionMode: 'bypassPermissions',\n },\n })) {\n if (!onEvent) continue;\n\n if (msg.type === 'assistant') {\n turnCount++;\n onEvent({ kind: 'turn', turn: turnCount });\n\n for (const block of msg.message.content) {\n if (block.type === 'text') {\n onEvent({ kind: 'thinking', text: block.text });\n }\n if (block.type === 'tool_use') {\n onEvent({\n kind: 'tool_call',\n tool: block.name as string,\n input: block.input as Record<string, unknown>,\n });\n }\n }\n }\n\n if (msg.type === 'user') {\n const content = msg.message?.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (typeof block === 'object' && block !== null && 'type' in block && (block as { type: string }).type === 'tool_result') {\n const tb = block as { tool_use_id?: string; content?: unknown };\n const text = typeof tb.content === 'string' ? tb.content : '';\n onEvent({ kind: 'tool_result', tool: tb.tool_use_id ?? '', output: text });\n }\n }\n }\n }\n\n if (msg.type === 'result') {\n onEvent({ kind: 'done' });\n return;\n }\n }\n}\n\n","import { mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { CartographyDB } from './db.js';\nimport type { NodeRow, EdgeRow, SOP } from './types.js';\nimport { buildMapData } from './mapper.js';\nimport { shadeVariant } from './cluster.js';\nimport { hexToPixel } from './hex.js';\n\n// ── Layer assignment ─────────────────────────────────────────────────────────\n\nfunction nodeLayer(type: string): string {\n if (type === 'saas_tool') return 'saas';\n if (['web_service', 'api_endpoint'].includes(type)) return 'web';\n if (['database_server', 'database', 'table', 'cache_server'].includes(type)) return 'data';\n if (['message_broker', 'queue', 'topic'].includes(type)) return 'messaging';\n if (['host', 'container', 'pod', 'k8s_cluster'].includes(type)) return 'infra';\n if (type === 'config_file') return 'config';\n return 'other';\n}\n\nconst LAYER_LABELS: Record<string, string> = {\n saas: '☁ SaaS Tools',\n web: '🌐 Web / API',\n data: '🗄 Data Layer',\n messaging: '📨 Messaging',\n infra: '🖥 Infrastructure',\n config: '📄 Config',\n other: '❓ Other',\n};\n\nconst LAYER_ORDER = ['saas', 'web', 'data', 'messaging', 'infra', 'config', 'other'];\n\n// ── Icons & Labels ───────────────────────────────────────────────────────────\n\nconst MERMAID_ICONS: Record<string, string> = {\n host: '🖥',\n database_server: '🗄',\n database: '🗄',\n table: '📋',\n web_service: '🌐',\n api_endpoint: '🔌',\n cache_server: '⚡',\n message_broker: '📨',\n queue: '📬',\n topic: '📢',\n container: '📦',\n pod: '☸',\n k8s_cluster: '☸',\n config_file: '📄',\n saas_tool: '☁',\n unknown: '❓',\n};\n\nconst EDGE_LABELS: Record<string, string> = {\n connects_to: '→',\n reads_from: 'reads',\n writes_to: 'writes',\n calls: 'calls',\n contains: 'contains',\n depends_on: 'depends on',\n};\n\n// Class colors per type (dark-theme friendly)\nconst MERMAID_CLASSES: Record<string, string> = {\n host: 'fill:#1e3352,stroke:#4a82c4,color:#cce',\n database_server:'fill:#1e3352,stroke:#4a82c4,color:#cce',\n database: 'fill:#163352,stroke:#3a8ad4,color:#bdf',\n table: 'fill:#0f2a40,stroke:#2a6090,color:#9bd',\n web_service: 'fill:#1a3a1a,stroke:#3a9a3a,color:#bfb',\n api_endpoint: 'fill:#0f2a0f,stroke:#2a7a2a,color:#9d9',\n cache_server: 'fill:#3a2a0a,stroke:#ca8a0a,color:#fda',\n message_broker: 'fill:#2a1a3a,stroke:#7a3aaa,color:#daf',\n queue: 'fill:#1f1030,stroke:#5a2a8a,color:#caf',\n topic: 'fill:#1f1030,stroke:#5a2a8a,color:#caf',\n container: 'fill:#1a2a3a,stroke:#3a6a9a,color:#acd',\n pod: 'fill:#0f1f2f,stroke:#2a5a8a,color:#8bc',\n k8s_cluster: 'fill:#0a1520,stroke:#1a4a7a,color:#7ab',\n config_file: 'fill:#2a2a1a,stroke:#7a7a2a,color:#ddc',\n saas_tool: 'fill:#2a1a2a,stroke:#9a3a9a,color:#daf',\n unknown: 'fill:#2a2a2a,stroke:#5a5a5a,color:#aaa',\n};\n\n// ── Mermaid ──────────────────────────────────────────────────────────────────\n\nfunction sanitize(id: string): string {\n return id.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n\nfunction nodeLabel(node: NodeRow): string {\n const icon = MERMAID_ICONS[node.type] ?? '?';\n const parts = node.id.split(':');\n const location = parts.length >= 3 ? `${parts[1]}:${parts[2]}` : parts[1] ?? '';\n const conf = `${Math.round(node.confidence * 100)}%`;\n\n // Pull 1-2 key metadata fields (no credentials)\n const meta = node.metadata as Record<string, unknown>;\n const extras: string[] = [];\n for (const key of ['category', 'version', 'description']) {\n const v = meta[key];\n if (typeof v === 'string' && v.length > 0) {\n extras.push(v.substring(0, 28));\n break; // max 1 extra line for readability\n }\n }\n\n const locLine = location ? `<br/><small>${location}</small>` : '';\n const extraLine = extras.length ? `<br/><small>${extras[0]}</small>` : '';\n return `[\"${icon} <b>${node.name}</b>${locLine}${extraLine}<br/><small>${node.type} · ${conf}</small>\"]`;\n}\n\nexport function generateTopologyMermaid(nodes: NodeRow[], edges: EdgeRow[]): string {\n if (nodes.length === 0) return 'graph TB\\n empty[\"No nodes discovered yet\"]';\n\n const lines: string[] = ['graph TB'];\n\n // classDef per used type\n const usedTypes = new Set(nodes.map(n => n.type));\n for (const type of usedTypes) {\n const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES['unknown']!;\n lines.push(` classDef ${type.replace(/_/g, '')} ${style}`);\n }\n lines.push('');\n\n // Group by semantic layer (ordered top→bottom)\n const layerMap = new Map<string, NodeRow[]>();\n for (const node of nodes) {\n const layer = nodeLayer(node.type);\n if (!layerMap.has(layer)) layerMap.set(layer, []);\n layerMap.get(layer)!.push(node);\n }\n\n for (const layerKey of LAYER_ORDER) {\n const layerNodes = layerMap.get(layerKey);\n if (!layerNodes || layerNodes.length === 0) continue;\n const label = LAYER_LABELS[layerKey] ?? layerKey;\n lines.push(` subgraph ${layerKey}[\"${label}\"]`);\n for (const node of layerNodes) {\n lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, '')}`);\n }\n lines.push(' end');\n lines.push('');\n }\n\n // Edges: dashed for low-confidence (<0.6), solid otherwise\n for (const edge of edges) {\n const src = sanitize(edge.sourceId);\n const tgt = sanitize(edge.targetId);\n const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;\n const arrow = edge.confidence < 0.6 ? `-. \"${label}\" .->` : `-->|\"${label}\"|`;\n lines.push(` ${src} ${arrow} ${tgt}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function generateDependencyMermaid(nodes: NodeRow[], edges: EdgeRow[]): string {\n const depEdges = edges.filter(e =>\n ['calls', 'reads_from', 'writes_to', 'depends_on'].includes(e.relationship)\n );\n\n if (depEdges.length === 0) return 'graph LR\\n empty[\"No dependency edges found\"]';\n\n const lines: string[] = ['graph LR'];\n\n const usedIds = new Set<string>();\n for (const edge of depEdges) {\n usedIds.add(edge.sourceId);\n usedIds.add(edge.targetId);\n }\n\n const usedNodes = nodes.filter(n => usedIds.has(n.id));\n const usedTypes = new Set(usedNodes.map(n => n.type));\n for (const type of usedTypes) {\n const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES['unknown']!;\n lines.push(` classDef ${type.replace(/_/g, '')} ${style}`);\n }\n lines.push('');\n\n for (const node of usedNodes) {\n lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, '')}`);\n }\n lines.push('');\n\n for (const edge of depEdges) {\n const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;\n lines.push(` ${sanitize(edge.sourceId)} -->|\"${label}\"| ${sanitize(edge.targetId)}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function generateWorkflowMermaid(sop: SOP): string {\n const lines: string[] = ['flowchart TD'];\n\n for (const step of sop.steps) {\n const nodeId = `S${step.order}`;\n const label = `${step.order}. ${step.instruction.substring(0, 60)}`;\n lines.push(` ${nodeId}[\"${label}\"]`);\n\n if (step.order > 1) {\n lines.push(` S${step.order - 1} --> ${nodeId}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n// ── Backstage YAML ───────────────────────────────────────────────────────────\n\nexport function exportBackstageYAML(nodes: NodeRow[], edges: EdgeRow[], org?: string): string {\n const owner = org ?? 'unknown';\n const docs: string[] = [];\n\n for (const node of nodes) {\n const isComponent = ['web_service', 'container', 'pod'].includes(node.type);\n const isAPI = node.type === 'api_endpoint';\n const kind = isComponent ? 'Component' : isAPI ? 'API' : 'Resource';\n\n const deps = edges\n .filter(e => e.sourceId === node.id)\n .map(e => ` - resource:default/${sanitize(e.targetId)}`);\n\n const doc = [\n `apiVersion: backstage.io/v1alpha1`,\n `kind: ${kind}`,\n `metadata:`,\n ` name: ${sanitize(node.id)}`,\n ` annotations:`,\n ` cartography/discovered-at: \"${node.discoveredAt}\"`,\n ` cartography/confidence: \"${node.confidence}\"`,\n `spec:`,\n ` type: ${node.type}`,\n ` lifecycle: production`,\n ` owner: ${owner}`,\n ...(deps.length > 0 ? [' dependsOn:', ...deps] : []),\n ].join('\\n');\n\n docs.push(doc);\n }\n\n return docs.join('\\n---\\n');\n}\n\n// ── JSON ─────────────────────────────────────────────────────────────────────\n\nexport function exportJSON(db: CartographyDB, sessionId: string): string {\n const nodes = db.getNodes(sessionId);\n const edges = db.getEdges(sessionId);\n const events = db.getEvents(sessionId);\n const tasks = db.getTasks(sessionId);\n const sops = db.getSOPs(sessionId);\n const stats = db.getStats(sessionId);\n\n return JSON.stringify({\n sessionId,\n exportedAt: new Date().toISOString(),\n stats,\n nodes,\n edges,\n events,\n tasks,\n sops,\n }, null, 2);\n}\n\n// ── HTML (D3.js Hexagonal Cartography Map) ────────────────────────────────────\n\nexport function exportHTML(nodes: NodeRow[], edges: EdgeRow[]): string {\n const graphData = JSON.stringify({\n nodes: nodes.map(n => ({\n id: n.id,\n name: n.name,\n type: n.type,\n layer: nodeLayer(n.type),\n confidence: n.confidence,\n discoveredVia: n.discoveredVia,\n discoveredAt: n.discoveredAt,\n tags: n.tags,\n metadata: n.metadata,\n })),\n links: edges.map(e => ({\n source: e.sourceId,\n target: e.targetId,\n relationship: e.relationship,\n confidence: e.confidence,\n evidence: e.evidence,\n })),\n });\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Cartography — Infrastructure Map</title>\n <script src=\"https://d3js.org/d3.v7.min.js\"></script>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { background: #0a0e14; color: #e6edf3; font-family: 'SF Mono','Fira Code','Cascadia Code',monospace; display: flex; overflow: hidden; height: 100vh; }\n\n /* ── Left node panel ─────────────────────────────── */\n #node-panel {\n width: 220px; min-width: 220px; height: 100vh; overflow: hidden;\n background: #0d1117; border-right: 1px solid #1b2028;\n display: flex; flex-direction: column;\n }\n #node-panel-header {\n padding: 10px 12px 8px; border-bottom: 1px solid #1b2028;\n font-size: 11px; color: #6e7681; text-transform: uppercase; letter-spacing: 0.6px;\n }\n #node-search {\n width: calc(100% - 16px); margin: 8px; padding: 5px 8px;\n background: #161b22; border: 1px solid #30363d; border-radius: 5px;\n color: #e6edf3; font-size: 11px; font-family: inherit; outline: none;\n }\n #node-search:focus { border-color: #58a6ff; }\n #node-list { flex: 1; overflow-y: auto; padding-bottom: 8px; }\n .node-list-item {\n padding: 5px 12px; cursor: pointer; font-size: 11px;\n display: flex; align-items: center; gap: 6px; border-left: 2px solid transparent;\n }\n .node-list-item:hover { background: #161b22; }\n .node-list-item.active { background: #1a2436; border-left-color: #58a6ff; }\n .node-list-dot { width: 7px; height: 7px; border-radius: 2px; flex-shrink: 0; }\n .node-list-name { color: #c9d1d9; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }\n .node-list-type { color: #484f58; font-size: 9px; flex-shrink: 0; }\n\n /* ── Center graph ────────────────────────────────── */\n #graph { flex: 1; height: 100vh; position: relative; }\n svg { width: 100%; height: 100%; }\n .hull { opacity: 0.12; stroke-width: 1.5; stroke-opacity: 0.25; }\n .hull-label { font-size: 13px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; fill-opacity: 0.5; pointer-events: none; }\n .link { stroke-opacity: 0.4; }\n .link-label { font-size: 8px; fill: #6e7681; pointer-events: none; opacity: 0; }\n .node-hex { stroke-width: 1.8; cursor: pointer; transition: opacity 0.15s; }\n .node-hex:hover { filter: brightness(1.3); stroke-width: 3; }\n .node-hex.selected { stroke-width: 3.5; filter: brightness(1.5); }\n .node-label { font-size: 10px; fill: #c9d1d9; pointer-events: none; opacity: 0; }\n\n /* ── Right sidebar ───────────────────────────────── */\n #sidebar {\n width: 300px; min-width: 300px; height: 100vh; overflow-y: auto;\n background: #0d1117; border-left: 1px solid #1b2028;\n padding: 16px; font-size: 12px; line-height: 1.6;\n }\n #sidebar h2 { margin: 0 0 8px; font-size: 14px; color: #58a6ff; }\n #sidebar .meta-table { width: 100%; border-collapse: collapse; }\n #sidebar .meta-table td { padding: 3px 6px; border-bottom: 1px solid #161b22; vertical-align: top; }\n #sidebar .meta-table td:first-child { color: #6e7681; white-space: nowrap; width: 90px; }\n #sidebar .tag { display: inline-block; background: #161b22; border-radius: 3px; padding: 1px 5px; margin: 1px; font-size: 10px; }\n #sidebar .conf-bar { height: 5px; border-radius: 3px; background: #161b22; margin-top: 3px; }\n #sidebar .conf-fill { height: 100%; border-radius: 3px; }\n #sidebar .edges-list { margin-top: 12px; }\n #sidebar .edge-item { padding: 4px 0; border-bottom: 1px solid #161b22; color: #6e7681; font-size: 11px; }\n #sidebar .edge-item span { color: #c9d1d9; }\n #sidebar .action-row { display: flex; gap: 6px; margin-top: 14px; }\n .btn-delete {\n flex: 1; padding: 6px 10px; background: transparent; border: 1px solid #6e191d;\n color: #f85149; border-radius: 5px; font-size: 11px; font-family: inherit;\n cursor: pointer; text-align: center;\n }\n .btn-delete:hover { background: #3d0c0c; }\n .hint { color: #3d434b; font-size: 11px; margin-top: 8px; }\n\n /* ── HUD ─────────────────────────────────────────── */\n #hud { position: absolute; top: 10px; left: 10px; background: rgba(10,14,20,0.88);\n padding: 10px 14px; border-radius: 8px; font-size: 12px; border: 1px solid #1b2028; pointer-events: none; }\n #hud strong { color: #58a6ff; }\n #hud .stats { color: #6e7681; }\n #hud .zoom-level { color: #3d434b; font-size: 10px; margin-top: 2px; }\n\n /* ── Toolbar (filters + JGF export) ─────────────── */\n #toolbar { position: absolute; top: 10px; right: 10px; display: flex; flex-wrap: wrap; gap: 4px; pointer-events: auto; align-items: center; }\n .filter-btn {\n background: rgba(10,14,20,0.85); border: 1px solid #1b2028; border-radius: 6px;\n color: #c9d1d9; padding: 4px 10px; font-size: 11px; cursor: pointer;\n font-family: inherit; display: flex; align-items: center; gap: 5px;\n }\n .filter-btn:hover { border-color: #30363d; }\n .filter-btn.off { opacity: 0.35; }\n .filter-dot { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }\n .export-btn {\n background: rgba(10,14,20,0.85); border: 1px solid #1b2028; border-radius: 6px;\n color: #58a6ff; padding: 4px 12px; font-size: 11px; cursor: pointer;\n font-family: inherit;\n }\n .export-btn:hover { border-color: #58a6ff; background: rgba(88,166,255,0.08); }\n </style>\n</head>\n<body>\n\n<!-- Left: node list panel -->\n<div id=\"node-panel\">\n <div id=\"node-panel-header\">Nodes (${nodes.length})</div>\n <input id=\"node-search\" type=\"text\" placeholder=\"Search nodes…\" autocomplete=\"off\" spellcheck=\"false\">\n <div id=\"node-list\"></div>\n</div>\n\n<!-- Center: graph -->\n<div id=\"graph\">\n <div id=\"hud\">\n <strong>Cartography</strong> &nbsp;\n <span class=\"stats\" id=\"hud-stats\">${nodes.length} nodes · ${edges.length} edges</span><br>\n <span class=\"zoom-level\">Scroll = zoom · Drag = pan · Click = details</span>\n </div>\n <div id=\"toolbar\"></div>\n <svg></svg>\n</div>\n\n<!-- Right: detail sidebar -->\n<div id=\"sidebar\">\n <h2>Infrastructure Map</h2>\n <p class=\"hint\">Click a node to view details.</p>\n</div>\n\n<script>\nconst data = ${graphData};\n\n// ── Color palette per node type ───────────────────────────────────────────\nconst TYPE_COLORS = {\n host: '#4a9eff', database_server: '#ff6b6b', database: '#ff8c42',\n web_service: '#6bcb77', api_endpoint: '#4d96ff', cache_server: '#ffd93d',\n message_broker: '#c77dff', queue: '#e0aaff', topic: '#9d4edd',\n container: '#48cae4', pod: '#00b4d8', k8s_cluster: '#0077b6',\n config_file: '#adb5bd', saas_tool: '#c084fc', table: '#f97316', unknown: '#6c757d',\n};\n\nconst LAYER_COLORS = {\n saas: '#c084fc', web: '#6bcb77', data: '#ff6b6b',\n messaging: '#c77dff', infra: '#4a9eff', config: '#adb5bd', other: '#6c757d',\n};\nconst LAYER_NAMES = {\n saas: 'SaaS Tools', web: 'Web / API', data: 'Data Layer',\n messaging: 'Messaging', infra: 'Infrastructure', config: 'Config', other: 'Other',\n};\n\n// ── Hexagon path ──────────────────────────────────────────────────────────\nconst HEX_SIZE = { saas_tool: 16, host: 18, database_server: 18, k8s_cluster: 20, default: 14 };\nfunction hexSize(d) { return HEX_SIZE[d.type] || HEX_SIZE.default; }\nfunction hexPath(size) {\n const pts = [];\n for (let i = 0; i < 6; i++) {\n const angle = (Math.PI / 3) * i - Math.PI / 6;\n pts.push([size * Math.cos(angle), size * Math.sin(angle)]);\n }\n return 'M' + pts.map(p => p.join(',')).join('L') + 'Z';\n}\n\n// ── Left panel ────────────────────────────────────────────────────────────\nconst nodeListEl = document.getElementById('node-list');\nconst nodeSearchEl = document.getElementById('node-search');\nlet selectedNodeId = null;\n\nfunction buildNodeList(filter) {\n const q = (filter || '').toLowerCase();\n nodeListEl.innerHTML = '';\n const sorted = [...data.nodes].sort((a, b) => a.name.localeCompare(b.name));\n for (const d of sorted) {\n if (q && !d.name.toLowerCase().includes(q) && !d.type.includes(q) && !d.id.toLowerCase().includes(q)) continue;\n const item = document.createElement('div');\n item.className = 'node-list-item' + (d.id === selectedNodeId ? ' active' : '');\n item.dataset.id = d.id;\n const color = TYPE_COLORS[d.type] || '#aaa';\n item.innerHTML = \\`<span class=\"node-list-dot\" style=\"background:\\${color}\"></span>\n <span class=\"node-list-name\" title=\"\\${d.id}\">\\${d.name}</span>\n <span class=\"node-list-type\">\\${d.type.replace(/_/g,' ')}</span>\\`;\n item.onclick = () => { selectNode(d); focusNode(d); };\n nodeListEl.appendChild(item);\n }\n}\n\nnodeSearchEl.addEventListener('input', e => buildNodeList(e.target.value));\n\n// ── Sidebar detail view ───────────────────────────────────────────────────\nconst sidebar = document.getElementById('sidebar');\n\nfunction selectNode(d) {\n selectedNodeId = d.id;\n buildNodeList(nodeSearchEl.value);\n showNode(d);\n // highlight hex\n d3.selectAll('.node-hex').classed('selected', nd => nd.id === d.id);\n}\n\nfunction showNode(d) {\n const c = TYPE_COLORS[d.type] || '#aaa';\n const confPct = Math.round(d.confidence * 100);\n const tags = (d.tags || []).map(t => \\`<span class=\"tag\">\\${t}</span>\\`).join('');\n const metaRows = Object.entries(d.metadata || {})\n .filter(([,v]) => v !== null && v !== undefined && String(v).length > 0)\n .map(([k,v]) => \\`<tr><td>\\${k}</td><td>\\${JSON.stringify(v)}</td></tr>\\`)\n .join('');\n const related = data.links.filter(l =>\n (l.source.id||l.source) === d.id || (l.target.id||l.target) === d.id\n );\n const edgeItems = related.map(l => {\n const isOut = (l.source.id||l.source) === d.id;\n const other = isOut ? (l.target.id||l.target) : (l.source.id||l.source);\n return \\`<div class=\"edge-item\">\\${isOut ? '→' : '←'} <span>\\${other}</span> <small>[\\${l.relationship}]</small></div>\\`;\n }).join('');\n\n sidebar.innerHTML = \\`\n <h2>\\${d.name}</h2>\n <table class=\"meta-table\">\n <tr><td>ID</td><td style=\"font-size:10px;word-break:break-all\">\\${d.id}</td></tr>\n <tr><td>Type</td><td><span style=\"color:\\${c}\">\\${d.type}</span></td></tr>\n <tr><td>Layer</td><td>\\${d.layer}</td></tr>\n <tr><td>Confidence</td><td>\n \\${confPct}%\n <div class=\"conf-bar\"><div class=\"conf-fill\" style=\"width:\\${confPct}%;background:\\${c}\"></div></div>\n </td></tr>\n <tr><td>Discovered via</td><td>\\${d.discoveredVia || '—'}</td></tr>\n <tr><td>Timestamp</td><td>\\${d.discoveredAt ? d.discoveredAt.substring(0,19).replace('T',' ') : '—'}</td></tr>\n \\${tags ? '<tr><td>Tags</td><td>'+tags+'</td></tr>' : ''}\n \\${metaRows}\n </table>\n \\${related.length > 0 ? '<div class=\"edges-list\"><strong>Connections (' + related.length + '):</strong>'+edgeItems+'</div>' : ''}\n <div class=\"action-row\">\n <button class=\"btn-delete\" onclick=\"deleteNode('\\${d.id}')\">🗑 Delete node</button>\n </div>\n \\`;\n}\n\n// ── Delete node ───────────────────────────────────────────────────────────\nfunction deleteNode(id) {\n const idx = data.nodes.findIndex(n => n.id === id);\n if (idx === -1) return;\n data.nodes.splice(idx, 1);\n data.links = data.links.filter(l =>\n (l.source.id || l.source) !== id && (l.target.id || l.target) !== id\n );\n selectedNodeId = null;\n sidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Node deleted.</p>';\n document.getElementById('hud-stats').textContent =\n data.nodes.length + ' nodes · ' + data.links.length + ' edges';\n rebuildGraph();\n buildNodeList(nodeSearchEl.value);\n}\n\n// ── SVG setup ─────────────────────────────────────────────────────────────\nconst svgEl = d3.select('svg');\nconst graphDiv = document.getElementById('graph');\nconst W = () => graphDiv.clientWidth;\nconst H = () => graphDiv.clientHeight;\nconst g = svgEl.append('g');\n\nsvgEl.append('defs').append('marker')\n .attr('id', 'arrow').attr('viewBox', '0 0 10 6')\n .attr('refX', 10).attr('refY', 3)\n .attr('markerWidth', 8).attr('markerHeight', 6)\n .attr('orient', 'auto')\n .append('path').attr('d', 'M0,0 L10,3 L0,6 Z').attr('fill', '#555');\n\nlet currentZoom = 1;\nconst zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', e => {\n g.attr('transform', e.transform);\n currentZoom = e.transform.k;\n updateLOD(currentZoom);\n});\nsvgEl.call(zoomBehavior);\n\n// ── Layer filter state ────────────────────────────────────────────────────\nconst layers = [...new Set(data.nodes.map(d => d.layer))];\nconst layerVisible = {};\nlayers.forEach(l => layerVisible[l] = true);\n\nconst toolbarEl = document.getElementById('toolbar');\n\n// Filter buttons\nlayers.forEach(layer => {\n const btn = document.createElement('button');\n btn.className = 'filter-btn';\n btn.innerHTML = \\`<span class=\"filter-dot\" style=\"background:\\${LAYER_COLORS[layer]||'#666'}\"></span>\\${LAYER_NAMES[layer]||layer}\\`;\n btn.onclick = () => {\n layerVisible[layer] = !layerVisible[layer];\n btn.classList.toggle('off', !layerVisible[layer]);\n updateVisibility();\n };\n toolbarEl.appendChild(btn);\n});\n\n// JGF export button\nconst jgfBtn = document.createElement('button');\njgfBtn.className = 'export-btn';\njgfBtn.textContent = '↓ JGF';\njgfBtn.title = 'Export JSON Graph Format';\njgfBtn.onclick = exportJGF;\ntoolbarEl.appendChild(jgfBtn);\n\n// ── JGF export ────────────────────────────────────────────────────────────\nfunction exportJGF() {\n const jgf = {\n graph: {\n directed: true,\n type: 'cartography',\n label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(data.nodes.map(n => [n.id, {\n label: n.name,\n metadata: { type: n.type, layer: n.layer, confidence: n.confidence,\n discoveredVia: n.discoveredVia, discoveredAt: n.discoveredAt,\n tags: n.tags, ...n.metadata }\n }])),\n edges: data.links.map(l => ({\n source: l.source.id || l.source,\n target: l.target.id || l.target,\n relation: l.relationship,\n metadata: { confidence: l.confidence, evidence: l.evidence }\n })),\n }\n };\n const blob = new Blob([JSON.stringify(jgf, null, 2)], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url; a.download = 'cartography-graph.jgf.json'; a.click();\n URL.revokeObjectURL(url);\n}\n\n// ── Cluster force ─────────────────────────────────────────────────────────\nfunction clusterForce(alpha) {\n const centroids = {};\n const counts = {};\n data.nodes.forEach(d => {\n if (!centroids[d.layer]) { centroids[d.layer] = { x: 0, y: 0 }; counts[d.layer] = 0; }\n centroids[d.layer].x += d.x || 0;\n centroids[d.layer].y += d.y || 0;\n counts[d.layer]++;\n });\n for (const l in centroids) { centroids[l].x /= counts[l]; centroids[l].y /= counts[l]; }\n const strength = alpha * 0.15;\n data.nodes.forEach(d => {\n const c = centroids[d.layer];\n if (c) { d.vx += (c.x - d.x) * strength; d.vy += (c.y - d.y) * strength; }\n });\n}\n\n// ── Hull group ────────────────────────────────────────────────────────────\nconst hullGroup = g.append('g').attr('class', 'hulls');\nconst hullPaths = {};\nconst hullLabels = {};\nlayers.forEach(layer => {\n hullPaths[layer] = hullGroup.append('path').attr('class', 'hull')\n .attr('fill', LAYER_COLORS[layer] || '#666').attr('stroke', LAYER_COLORS[layer] || '#666');\n hullLabels[layer] = hullGroup.append('text').attr('class', 'hull-label')\n .attr('fill', LAYER_COLORS[layer] || '#666').text(LAYER_NAMES[layer] || layer);\n});\n\nfunction updateHulls() {\n layers.forEach(layer => {\n if (!layerVisible[layer]) { hullPaths[layer].attr('d', null); hullLabels[layer].attr('x', -9999); return; }\n const pts = data.nodes.filter(d => d.layer === layer && layerVisible[d.layer]).map(d => [d.x, d.y]);\n if (pts.length < 3) {\n hullPaths[layer].attr('d', null);\n if (pts.length > 0) hullLabels[layer].attr('x', pts[0][0]).attr('y', pts[0][1] - 30);\n else hullLabels[layer].attr('x', -9999);\n return;\n }\n const hull = d3.polygonHull(pts);\n if (!hull) { hullPaths[layer].attr('d', null); return; }\n const cx = d3.mean(hull, p => p[0]);\n const cy = d3.mean(hull, p => p[1]);\n const padded = hull.map(p => {\n const dx = p[0] - cx, dy = p[1] - cy;\n const len = Math.sqrt(dx*dx + dy*dy) || 1;\n return [p[0] + dx/len * 40, p[1] + dy/len * 40];\n });\n hullPaths[layer].attr('d', 'M' + padded.join('L') + 'Z');\n hullLabels[layer].attr('x', cx).attr('y', cy - d3.max(hull, p => Math.abs(p[1] - cy)) - 30);\n });\n}\n\n// ── Graph rendering (rebuildable after delete) ────────────────────────────\nlet linkSel, linkLabelSel, nodeSel, nodeLabelSel, sim;\nconst linkGroup = g.append('g');\nconst nodeGroup = g.append('g');\n\nfunction focusNode(d) {\n if (!d.x || !d.y) return;\n const w = W(), h = H();\n svgEl.transition().duration(500).call(\n zoomBehavior.transform,\n d3.zoomIdentity.translate(w / 2, h / 2).scale(Math.min(3, currentZoom < 1 ? 1.5 : currentZoom)).translate(-d.x, -d.y)\n );\n}\n\nfunction rebuildGraph() {\n if (sim) sim.stop();\n\n // Links\n linkSel = linkGroup.selectAll('line').data(data.links, d => \\`\\${d.source.id||d.source}>\\${d.target.id||d.target}\\`);\n linkSel.exit().remove();\n const linkEnter = linkSel.enter().append('line').attr('class', 'link');\n linkSel = linkEnter.merge(linkSel)\n .attr('stroke', d => d.confidence < 0.6 ? '#2a2e35' : '#3d434b')\n .attr('stroke-dasharray', d => d.confidence < 0.6 ? '4 3' : null)\n .attr('stroke-width', d => d.confidence < 0.6 ? 0.8 : 1.2)\n .attr('marker-end', 'url(#arrow)');\n linkSel.select('title').remove();\n linkSel.append('title').text(d => \\`\\${d.relationship} (\\${Math.round(d.confidence*100)}%)\\n\\${d.evidence||''}\\`);\n\n // Link labels\n linkLabelSel = linkGroup.selectAll('text').data(data.links, d => \\`\\${d.source.id||d.source}>\\${d.target.id||d.target}\\`);\n linkLabelSel.exit().remove();\n linkLabelSel = linkLabelSel.enter().append('text').attr('class', 'link-label').merge(linkLabelSel)\n .text(d => d.relationship);\n\n // Nodes\n nodeSel = nodeGroup.selectAll('g').data(data.nodes, d => d.id);\n nodeSel.exit().remove();\n const nodeEnter = nodeSel.enter().append('g')\n .call(d3.drag()\n .on('start', (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })\n .on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })\n .on('end', (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })\n )\n .on('click', (e, d) => { e.stopPropagation(); selectNode(d); });\n nodeEnter.append('path').attr('class', 'node-hex');\n nodeEnter.append('title');\n nodeEnter.append('text').attr('class', 'node-label').attr('text-anchor', 'middle');\n\n nodeSel = nodeEnter.merge(nodeSel);\n nodeSel.select('.node-hex')\n .attr('d', d => hexPath(hexSize(d)))\n .attr('fill', d => TYPE_COLORS[d.type] || '#aaa')\n .attr('stroke', d => { const c = d3.color(TYPE_COLORS[d.type] || '#aaa'); return c ? c.brighter(0.8).formatHex() : '#ccc'; })\n .attr('fill-opacity', d => 0.6 + d.confidence * 0.4)\n .classed('selected', d => d.id === selectedNodeId);\n nodeSel.select('title').text(d => \\`\\${d.name} (\\${d.type})\\nconf: \\${Math.round(d.confidence*100)}%\\`);\n nodeLabelSel = nodeSel.select('.node-label')\n .attr('dy', d => hexSize(d) + 13)\n .text(d => d.name.length > 20 ? d.name.substring(0, 18) + '…' : d.name);\n\n // Simulation\n sim = d3.forceSimulation(data.nodes)\n .force('link', d3.forceLink(data.links).id(d => d.id).distance(d => d.relationship === 'contains' ? 50 : 100).strength(0.4))\n .force('charge', d3.forceManyBody().strength(-280))\n .force('center', d3.forceCenter(W() / 2, H() / 2))\n .force('collision', d3.forceCollide().radius(d => hexSize(d) + 10))\n .force('cluster', clusterForce)\n .on('tick', () => {\n updateHulls();\n linkSel.attr('x1', d => d.source.x).attr('y1', d => d.source.y)\n .attr('x2', d => d.target.x).attr('y2', d => d.target.y);\n linkLabelSel.attr('x', d => (d.source.x + d.target.x) / 2)\n .attr('y', d => (d.source.y + d.target.y) / 2 - 4);\n nodeSel.attr('transform', d => \\`translate(\\${d.x},\\${d.y})\\`);\n });\n}\n\n// ── LOD & visibility ──────────────────────────────────────────────────────\nfunction updateLOD(k) {\n if (nodeLabelSel) nodeLabelSel.style('opacity', k > 0.5 ? Math.min(1, (k - 0.5) * 2) : 0);\n if (linkLabelSel) linkLabelSel.style('opacity', k > 1.2 ? Math.min(1, (k - 1.2) * 3) : 0);\n d3.selectAll('.hull-label').style('font-size', k < 0.4 ? '18px' : '13px');\n}\n\nfunction updateVisibility() {\n if (!nodeSel) return;\n nodeSel.style('display', d => layerVisible[d.layer] ? null : 'none');\n linkSel.style('display', d => {\n const s = data.nodes.find(n => n.id === (d.source.id||d.source));\n const t = data.nodes.find(n => n.id === (d.target.id||d.target));\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n linkLabelSel.style('display', d => {\n const s = data.nodes.find(n => n.id === (d.source.id||d.source));\n const t = data.nodes.find(n => n.id === (d.target.id||d.target));\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n}\n\n// ── Init ──────────────────────────────────────────────────────────────────\nrebuildGraph();\nbuildNodeList();\nupdateLOD(1);\n\nsvgEl.on('click', () => {\n selectedNodeId = null;\n d3.selectAll('.node-hex').classed('selected', false);\n buildNodeList(nodeSearchEl.value);\n sidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Click a node to view details.</p>';\n});\n</script>\n</body>\n</html>`;\n}\n\n// ── SOP Markdown ─────────────────────────────────────────────────────────────\n\nexport function exportSOPMarkdown(sop: SOP): string {\n const lines: string[] = [\n `# ${sop.title}`,\n '',\n `**Description:** ${sop.description}`,\n `**Systems:** ${sop.involvedSystems.join(', ')}`,\n `**Duration:** ${sop.estimatedDuration}`,\n `**Frequency:** ${sop.frequency}`,\n `**Confidence:** ${sop.confidence.toFixed(2)}`,\n '',\n '## Steps',\n '',\n ];\n\n for (const step of sop.steps) {\n lines.push(`${step.order}. **${step.tool}**${step.target ? ` → \\`${step.target}\\`` : ''}`);\n lines.push(` ${step.instruction}`);\n if (step.notes) lines.push(` _${step.notes}_`);\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n// ── SOP Dashboard HTML ───────────────────────────────────────────────────────\n\nexport function exportSOPDashboard(sops: Array<SOP & { id: string; workflowId: string; generatedAt?: string }>): string {\n const sopsJson = JSON.stringify(sops.map(s => ({\n id: s.id,\n title: s.title,\n description: s.description,\n steps: s.steps,\n systems: s.involvedSystems,\n duration: s.estimatedDuration,\n frequency: s.frequency,\n confidence: s.confidence,\n generatedAt: s.generatedAt ?? new Date().toISOString(),\n })));\n\n // System frequency: how many SOPs reference each system\n const systemCount: Record<string, number> = {};\n for (const sop of sops) {\n for (const sys of sop.involvedSystems) {\n systemCount[sys] = (systemCount[sys] ?? 0) + 1;\n }\n }\n const systemsJson = JSON.stringify(\n Object.entries(systemCount).sort((a, b) => b[1] - a[1])\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Cartography — SOP Dashboard</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: #0d1117; color: #e6edf3;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;\n padding: 0; line-height: 1.6;\n }\n .header {\n background: linear-gradient(135deg, #161b22 0%, #1a1f2e 100%);\n border-bottom: 1px solid #30363d; padding: 32px 40px;\n }\n .header h1 { font-size: 24px; color: #58a6ff; margin-bottom: 8px; }\n .header .subtitle { color: #8b949e; font-size: 14px; }\n .stats-row {\n display: flex; gap: 24px; margin-top: 16px; flex-wrap: wrap;\n }\n .stat-card {\n background: #21262d; border: 1px solid #30363d; border-radius: 8px;\n padding: 12px 20px; min-width: 140px;\n }\n .stat-card .value { font-size: 28px; font-weight: 700; color: #58a6ff; }\n .stat-card .label { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; }\n .container { max-width: 1200px; margin: 0 auto; padding: 24px 40px; }\n .section-title { font-size: 18px; color: #c9d1d9; margin: 32px 0 16px; border-bottom: 1px solid #21262d; padding-bottom: 8px; }\n /* Systems bar chart */\n .systems-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; }\n .sys-tag {\n background: #21262d; border: 1px solid #30363d; border-radius: 6px;\n padding: 6px 12px; font-size: 12px; cursor: default;\n }\n .sys-tag .count { color: #58a6ff; font-weight: 600; margin-left: 4px; }\n /* SOP cards */\n .sop-card {\n background: #161b22; border: 1px solid #30363d; border-radius: 8px;\n margin-bottom: 16px; overflow: hidden; transition: border-color 0.2s;\n }\n .sop-card:hover { border-color: #58a6ff; }\n .sop-header {\n padding: 16px 20px; cursor: pointer; display: flex;\n justify-content: space-between; align-items: center;\n }\n .sop-header h3 { font-size: 16px; color: #e6edf3; }\n .sop-meta { display: flex; gap: 16px; align-items: center; font-size: 12px; color: #8b949e; }\n .sop-meta .freq { color: #3fb950; font-weight: 600; }\n .sop-meta .dur { color: #d29922; }\n .sop-meta .conf {\n display: inline-flex; align-items: center; gap: 4px;\n }\n .conf-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }\n .sop-body { display: none; padding: 0 20px 20px; border-top: 1px solid #21262d; }\n .sop-body.open { display: block; padding-top: 16px; }\n .sop-desc { color: #8b949e; font-size: 13px; margin-bottom: 12px; }\n .sop-systems { margin-bottom: 12px; }\n .sop-systems span { background: #0d419d33; color: #58a6ff; border-radius: 4px; padding: 2px 8px; font-size: 11px; margin-right: 4px; }\n .steps-list { list-style: none; counter-reset: step; }\n .steps-list li {\n counter-increment: step; position: relative;\n padding: 10px 12px 10px 44px; border-left: 2px solid #30363d;\n margin-left: 14px; font-size: 13px;\n }\n .steps-list li:last-child { border-left-color: transparent; }\n .steps-list li::before {\n content: counter(step);\n position: absolute; left: -14px; top: 8px;\n width: 26px; height: 26px; border-radius: 50%;\n background: #21262d; border: 2px solid #30363d;\n display: flex; align-items: center; justify-content: center;\n font-size: 12px; font-weight: 600; color: #58a6ff;\n }\n .step-tool { color: #d2a8ff; font-weight: 600; }\n .step-target { color: #7ee787; font-size: 12px; }\n .step-notes { color: #8b949e; font-style: italic; font-size: 12px; margin-top: 2px; }\n .step-instr { color: #c9d1d9; }\n .toggle-icon { color: #8b949e; font-size: 18px; transition: transform 0.2s; }\n .toggle-icon.open { transform: rotate(90deg); }\n .empty { color: #484f58; font-size: 14px; padding: 40px; text-align: center; }\n .gen-time { color: #484f58; font-size: 11px; margin-top: 8px; }\n </style>\n</head>\n<body>\n<div class=\"header\">\n <h1>SOP Dashboard</h1>\n <div class=\"subtitle\">Cartography — Standard Operating Procedures</div>\n <div class=\"stats-row\">\n <div class=\"stat-card\"><div class=\"value\" id=\"sop-count\">0</div><div class=\"label\">SOPs</div></div>\n <div class=\"stat-card\"><div class=\"value\" id=\"step-count\">0</div><div class=\"label\">Total Steps</div></div>\n <div class=\"stat-card\"><div class=\"value\" id=\"sys-count\">0</div><div class=\"label\">Systems</div></div>\n <div class=\"stat-card\"><div class=\"value\" id=\"avg-conf\">—</div><div class=\"label\">Avg Confidence</div></div>\n </div>\n</div>\n<div class=\"container\">\n <h2 class=\"section-title\">Involved Systems</h2>\n <div class=\"systems-grid\" id=\"systems\"></div>\n\n <h2 class=\"section-title\">SOPs</h2>\n <div id=\"sop-list\"></div>\n</div>\n<script>\nconst sops = ${sopsJson};\nconst systems = ${systemsJson};\n\ndocument.getElementById('sop-count').textContent = sops.length;\ndocument.getElementById('step-count').textContent = sops.reduce((a, s) => a + s.steps.length, 0);\ndocument.getElementById('sys-count').textContent = systems.length;\nconst avgConf = sops.length > 0\n ? (sops.reduce((a, s) => a + s.confidence, 0) / sops.length * 100).toFixed(0) + '%'\n : '—';\ndocument.getElementById('avg-conf').textContent = avgConf;\n\nconst sysDiv = document.getElementById('systems');\nsystems.forEach(([name, count]) => {\n const el = document.createElement('div');\n el.className = 'sys-tag';\n el.innerHTML = name + '<span class=\"count\">x' + count + '</span>';\n sysDiv.appendChild(el);\n});\n\nconst listDiv = document.getElementById('sop-list');\nif (sops.length === 0) {\n listDiv.innerHTML = '<div class=\"empty\">No SOPs found. Run a discovery session first.</div>';\n}\n\nsops.forEach((sop, i) => {\n const confColor = sop.confidence >= 0.8 ? '#3fb950' : sop.confidence >= 0.5 ? '#d29922' : '#f85149';\n const card = document.createElement('div');\n card.className = 'sop-card';\n card.innerHTML = \\`\n <div class=\"sop-header\" onclick=\"toggle(\\${i})\">\n <h3>\\${sop.title}</h3>\n <div class=\"sop-meta\">\n <span class=\"freq\">\\${sop.frequency}</span>\n <span class=\"dur\">\\${sop.duration}</span>\n <span class=\"conf\"><span class=\"conf-dot\" style=\"background:\\${confColor}\"></span>\\${Math.round(sop.confidence*100)}%</span>\n <span class=\"toggle-icon\" id=\"icon-\\${i}\">▸</span>\n </div>\n </div>\n <div class=\"sop-body\" id=\"body-\\${i}\">\n <div class=\"sop-desc\">\\${sop.description}</div>\n <div class=\"sop-systems\">\\${sop.systems.map(s => '<span>'+s+'</span>').join('')}</div>\n <ol class=\"steps-list\">\n \\${sop.steps.map(st => \\`\n <li>\n <span class=\"step-tool\">\\${st.tool}</span>\n \\${st.target ? '<span class=\"step-target\"> → '+st.target+'</span>' : ''}\n <div class=\"step-instr\">\\${st.instruction}</div>\n \\${st.notes ? '<div class=\"step-notes\">'+st.notes+'</div>' : ''}\n </li>\n \\`).join('')}\n </ol>\n <div class=\"gen-time\">Generated: \\${sop.generatedAt ? sop.generatedAt.substring(0,19).replace('T',' ') : '—'}</div>\n </div>\n \\`;\n listDiv.appendChild(card);\n});\n\nfunction toggle(i) {\n const body = document.getElementById('body-'+i);\n const icon = document.getElementById('icon-'+i);\n body.classList.toggle('open');\n icon.classList.toggle('open');\n}\n</script>\n</body>\n</html>`;\n}\n\n// ── Cartography Map Export ─────────────────────────────────────────────────────\n\nexport function exportCartographyMap(\n nodes: NodeRow[],\n edges: EdgeRow[],\n options?: { theme?: 'light' | 'dark' },\n): string {\n const mapData = buildMapData(nodes, edges, options);\n const { assets, clusters, connections, meta } = mapData;\n const isEmpty = assets.length === 0;\n const HEX_SIZE = 24;\n\n const dataJson = JSON.stringify({\n assets: assets.map(a => ({\n id: a.id, name: a.name, domain: a.domain, subDomain: a.subDomain ?? null,\n qualityScore: a.qualityScore ?? null, metadata: a.metadata,\n q: a.position.q, r: a.position.r,\n })),\n clusters: clusters.map(c => ({\n id: c.id, label: c.label, domain: c.domain, color: c.color,\n assetIds: c.assetIds, centroid: c.centroid,\n })),\n connections: connections.map(c => ({\n id: c.id, sourceAssetId: c.sourceAssetId, targetAssetId: c.targetAssetId,\n type: c.type ?? 'connection',\n })),\n });\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/>\n<title>Data Cartography Map</title>\n<style>\n*{box-sizing:border-box;margin:0;padding:0}\nhtml,body{width:100%;height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\nbody{display:flex;flex-direction:column;background:${meta.theme === 'dark' ? '#0f172a' : '#f8fafc'};color:${meta.theme === 'dark' ? '#e2e8f0' : '#1e293b'}}\n#topbar{\n height:48px;display:flex;align-items:center;gap:16px;padding:0 20px;\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};border-bottom:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};z-index:10;flex-shrink:0;\n}\n#topbar h1{font-size:15px;font-weight:600;letter-spacing:-0.01em}\n#search-box{\n display:flex;align-items:center;gap:8px;background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'};\n border-radius:8px;padding:5px 10px;margin-left:auto;\n}\n#search-box input{\n border:none;background:transparent;font-size:13px;outline:none;width:180px;color:inherit;\n}\n#search-box input::placeholder{color:#94a3b8}\n#main{flex:1;display:flex;overflow:hidden;position:relative}\n#canvas-wrap{flex:1;position:relative;overflow:hidden;cursor:grab}\n#canvas-wrap.dragging{cursor:grabbing}\n#canvas-wrap.connecting{cursor:crosshair}\ncanvas{display:block;width:100%;height:100%}\n/* Detail panel */\n#detail-panel{\n width:280px;background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};border-left:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n display:flex;flex-direction:column;transform:translateX(100%);\n transition:transform .2s ease;z-index:5;flex-shrink:0;overflow-y:auto;\n}\n#detail-panel.open{transform:translateX(0)}\n#detail-panel .panel-header{\n padding:16px;border-bottom:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};display:flex;align-items:center;gap:10px;\n}\n#detail-panel .panel-header h3{font-size:14px;font-weight:600;flex:1;word-break:break-word}\n#detail-panel .close-btn{\n width:24px;height:24px;border:none;background:transparent;cursor:pointer;\n color:#94a3b8;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:16px;\n}\n#detail-panel .close-btn:hover{background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'}}\n#detail-panel .panel-body{padding:12px 16px;display:flex;flex-direction:column;gap:12px}\n#detail-panel .meta-row{display:flex;flex-direction:column;gap:3px}\n#detail-panel .meta-label{font-size:11px;font-weight:500;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em}\n#detail-panel .meta-value{font-size:13px;word-break:break-all}\n#detail-panel .quality-bar{height:6px;border-radius:3px;background:${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};margin-top:4px}\n#detail-panel .quality-fill{height:6px;border-radius:3px;transition:width .3s}\n/* Bottom-left toolbar */\n#toolbar-left{\n position:absolute;bottom:20px;left:20px;display:flex;gap:8px;z-index:10;\n}\n.tb-btn{\n width:40px;height:40px;border-radius:10px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};box-shadow:0 1px 4px rgba(0,0,0,.08);cursor:pointer;\n display:flex;align-items:center;justify-content:center;font-size:18px;\n transition:all .15s;color:inherit;\n}\n.tb-btn:hover{border-color:#94a3b8}\n.tb-btn.active{background:${meta.theme === 'dark' ? '#1e3a5f' : '#eff6ff'};border-color:#3b82f6}\n/* Bottom-right toolbar */\n#toolbar-right{\n position:absolute;bottom:20px;right:20px;display:flex;flex-direction:column;\n align-items:flex-end;gap:8px;z-index:10;\n}\n#zoom-controls{display:flex;align-items:center;gap:6px}\n.zoom-btn{\n width:34px;height:34px;border-radius:8px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:18px;color:inherit;display:flex;align-items:center;justify-content:center;\n}\n.zoom-btn:hover{background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'}}\n#zoom-pct{font-size:12px;font-weight:500;color:#64748b;min-width:38px;text-align:center}\n#detail-selector{display:flex;flex-direction:column;gap:4px}\n.detail-btn{\n width:34px;height:34px;border-radius:8px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:12px;font-weight:600;color:#64748b;display:flex;align-items:center;justify-content:center;\n}\n.detail-btn:hover{background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'}}\n.detail-btn.active{background:${meta.theme === 'dark' ? '#1e3a5f' : '#eff6ff'};border-color:#3b82f6;color:#2563eb}\n#connect-btn{\n width:40px;height:40px;border-radius:10px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:18px;display:flex;align-items:center;justify-content:center;color:inherit;\n}\n#connect-btn.active{background:#fef3c7;border-color:#f59e0b}\n/* Tooltip */\n#tooltip{\n position:fixed;background:#1e293b;color:#fff;border-radius:8px;\n padding:8px 12px;font-size:12px;pointer-events:none;z-index:100;\n display:none;max-width:220px;box-shadow:0 4px 12px rgba(0,0,0,.15);\n}\n#tooltip .tt-name{font-weight:600;margin-bottom:2px}\n#tooltip .tt-domain{color:#94a3b8;font-size:11px}\n#tooltip .tt-quality{font-size:11px;margin-top:2px}\n/* Empty state */\n#empty-state{\n position:absolute;inset:0;display:flex;flex-direction:column;\n align-items:center;justify-content:center;gap:12px;color:#94a3b8;\n}\n#empty-state p{font-size:14px}\n/* Theme toggle */\n#theme-btn{\n width:40px;height:40px;border-radius:10px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:18px;display:flex;align-items:center;justify-content:center;color:inherit;\n}\n/* Dark mode overrides (toggled via JS) */\nbody.dark{background:#0f172a;color:#e2e8f0}\nbody.dark #topbar{background:#1e293b;border-color:#334155}\nbody.dark #search-box{background:#334155}\nbody.dark #detail-panel{background:#1e293b;border-color:#334155}\nbody.dark .tb-btn,body.dark .zoom-btn,body.dark .detail-btn,body.dark #connect-btn,body.dark #theme-btn{\n background:#1e293b;border-color:#334155;color:#e2e8f0;\n}\n/* Light mode overrides */\nbody.light{background:#f8fafc;color:#1e293b}\nbody.light #topbar{background:#fff;border-color:#e2e8f0}\n/* Connection hint */\n#connect-hint{\n position:absolute;top:12px;left:50%;transform:translateX(-50%);\n background:#fef3c7;border:1px solid #f59e0b;color:#92400e;\n padding:6px 14px;border-radius:20px;font-size:12px;font-weight:500;\n display:none;z-index:20;pointer-events:none;\n}\n/* Screen reader only */\n.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}\n</style>\n</head>\n<body class=\"${meta.theme}\">\n<!-- Top bar -->\n<div id=\"topbar\">\n <h1>Data Cartography Map</h1>\n <div id=\"search-box\">\n <span style=\"color:#94a3b8;font-size:14px\">&#8981;</span>\n <input id=\"search-input\" type=\"text\" placeholder=\"Search assets...\" aria-label=\"Search data assets\"/>\n </div>\n <button id=\"theme-btn\" title=\"Toggle dark/light mode\" aria-label=\"Toggle theme\">${meta.theme === 'dark' ? '&#9788;' : '&#9790;'}</button>\n</div>\n<!-- SR summary -->\n<div class=\"sr-only\" role=\"status\" aria-live=\"polite\" id=\"sr-summary\">\n Data cartography map with ${assets.length} assets in ${clusters.length} clusters.\n</div>\n<!-- Main area -->\n<div id=\"main\">\n <div id=\"canvas-wrap\" role=\"application\" aria-label=\"Data cartography hex map\" tabindex=\"0\">\n <canvas id=\"hexmap\" aria-hidden=\"true\"></canvas>\n ${isEmpty ? '<div id=\"empty-state\"><p style=\"font-size:48px\">&#128506;</p><p>No data assets available</p><p style=\"font-size:12px\">Run <code>datasynx-cartography discover</code> to populate the map</p></div>' : ''}\n </div>\n <div id=\"detail-panel\" role=\"complementary\" aria-label=\"Asset details\">\n <div class=\"panel-header\">\n <h3 id=\"dp-name\">&mdash;</h3>\n <button class=\"close-btn\" id=\"dp-close\" aria-label=\"Close panel\">&#10005;</button>\n </div>\n <div class=\"panel-body\" id=\"dp-body\"></div>\n </div>\n</div>\n<!-- Bottom-left toolbar -->\n<div id=\"toolbar-left\">\n <button class=\"tb-btn active\" id=\"btn-labels\" title=\"Show labels\" aria-pressed=\"true\" aria-label=\"Toggle labels\">&#127991;</button>\n <button class=\"tb-btn\" id=\"btn-quality\" title=\"Quality layer\" aria-pressed=\"false\" aria-label=\"Toggle quality layer\">&#128065;</button>\n</div>\n<!-- Bottom-right toolbar -->\n<div id=\"toolbar-right\">\n <div id=\"zoom-controls\">\n <button class=\"zoom-btn\" id=\"zoom-out\" aria-label=\"Zoom out\">&minus;</button>\n <span id=\"zoom-pct\">100%</span>\n <button class=\"zoom-btn\" id=\"zoom-in\" aria-label=\"Zoom in\">+</button>\n </div>\n <div id=\"detail-selector\">\n <button class=\"detail-btn\" id=\"dl-1\" aria-label=\"Detail level 1\">1</button>\n <button class=\"detail-btn active\" id=\"dl-2\" aria-label=\"Detail level 2\">2</button>\n <button class=\"detail-btn\" id=\"dl-3\" aria-label=\"Detail level 3\">3</button>\n <button class=\"detail-btn\" id=\"dl-4\" aria-label=\"Detail level 4\">4</button>\n </div>\n <button id=\"connect-btn\" title=\"Connection tool\" aria-label=\"Toggle connection tool\">&#128279;</button>\n</div>\n<!-- Connection hint -->\n<div id=\"connect-hint\">Click two assets to create a connection</div>\n<!-- Tooltip -->\n<div id=\"tooltip\" role=\"tooltip\">\n <div class=\"tt-name\" id=\"tt-name\"></div>\n <div class=\"tt-domain\" id=\"tt-domain\"></div>\n <div class=\"tt-quality\" id=\"tt-quality\"></div>\n</div>\n\n<script>\n(function() {\n'use strict';\n\n// ── Data ─────────────────────────────────────────────────────────────────────\nconst MAP = ${dataJson};\nconst HEX_SIZE = ${HEX_SIZE};\nconst IS_EMPTY = ${isEmpty};\n\n// Build asset index\nconst assetIndex = new Map();\nconst clusterByAsset = new Map();\nfor (const c of MAP.clusters) {\n for (const aid of c.assetIds) {\n clusterByAsset.set(aid, c);\n }\n}\nfor (const a of MAP.assets) {\n assetIndex.set(a.id, a);\n}\n\n// ── Canvas ────────────────────────────────────────────────────────────────────\nconst canvas = document.getElementById('hexmap');\nconst ctx = canvas.getContext('2d');\nconst wrap = document.getElementById('canvas-wrap');\nlet W = 0, H = 0;\n\nfunction resize() {\n const dpr = window.devicePixelRatio || 1;\n W = wrap.clientWidth; H = wrap.clientHeight;\n canvas.width = W * dpr; canvas.height = H * dpr;\n canvas.style.width = W + 'px'; canvas.style.height = H + 'px';\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n draw();\n}\nwindow.addEventListener('resize', resize);\n\n// ── Viewport ──────────────────────────────────────────────────────────────────\nlet vx = 0, vy = 0, scale = 1;\nlet detailLevel = 2, showLabels = true, showQuality = false;\nlet isDark = document.body.classList.contains('dark');\nlet connectMode = false, connectFirst = null;\nlet hoveredAssetId = null, selectedAssetId = null;\nlet searchQuery = '';\nlet localConnections = [...MAP.connections];\n\n// Flat-top hex math\nfunction htp_x(q, r) { return HEX_SIZE * (3/2 * q); }\nfunction htp_y(q, r) { return HEX_SIZE * (Math.sqrt(3)/2 * q + Math.sqrt(3) * r); }\nfunction w2s(wx, wy) { return { x: wx*scale+vx, y: wy*scale+vy }; }\nfunction s2w(sx, sy) { return { x: (sx-vx)/scale, y: (sy-vy)/scale }; }\n\nfunction fitToView() {\n if (IS_EMPTY || MAP.assets.length === 0) { vx = 0; vy = 0; scale = 1; return; }\n let mnx=Infinity,mny=Infinity,mxx=-Infinity,mxy=-Infinity;\n for (const a of MAP.assets) {\n const px=htp_x(a.q,a.r), py=htp_y(a.q,a.r);\n if(px<mnx)mnx=px;if(py<mny)mny=py;if(px>mxx)mxx=px;if(py>mxy)mxy=py;\n }\n const pw=mxx-mnx+HEX_SIZE*4, ph=mxy-mny+HEX_SIZE*4;\n scale = Math.min(W/pw, H/ph, 2) * 0.85;\n vx = W/2 - ((mnx+mxx)/2)*scale;\n vy = H/2 - ((mny+mxy)/2)*scale;\n}\n\n// ── Drawing ───────────────────────────────────────────────────────────────────\nfunction hexPath(cx, cy, r) {\n ctx.beginPath();\n for (let i=0;i<6;i++) {\n const angle = Math.PI/180*(60*i);\n const x=cx+r*Math.cos(angle), y=cy+r*Math.sin(angle);\n i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);\n }\n ctx.closePath();\n}\n\nfunction shadeV(hex, amt) {\n if(!hex||hex.length<7)return hex;\n const n=parseInt(hex.replace('#',''),16);\n const r=Math.min(255,(n>>16)+amt), g=Math.min(255,((n>>8)&0xff)+amt), b=Math.min(255,(n&0xff)+amt);\n return '#'+r.toString(16).padStart(2,'0')+g.toString(16).padStart(2,'0')+b.toString(16).padStart(2,'0');\n}\n\nfunction draw() {\n ctx.clearRect(0,0,W,H);\n ctx.fillStyle = isDark ? '#0f172a' : '#f8fafc';\n ctx.fillRect(0,0,W,H);\n if (IS_EMPTY) return;\n\n const size = HEX_SIZE * scale;\n const matchedIds = getSearchMatches();\n const hasSearch = searchQuery.length > 0;\n\n // Draw connections\n ctx.save();\n ctx.strokeStyle = isDark ? 'rgba(148,163,184,0.35)' : 'rgba(100,116,139,0.25)';\n ctx.lineWidth = 1.5;\n ctx.setLineDash([4,4]);\n for (const conn of localConnections) {\n const src = assetIndex.get(conn.sourceAssetId);\n const tgt = assetIndex.get(conn.targetAssetId);\n if (!src||!tgt) continue;\n const sp=w2s(htp_x(src.q,src.r),htp_y(src.q,src.r));\n const tp=w2s(htp_x(tgt.q,tgt.r),htp_y(tgt.q,tgt.r));\n ctx.beginPath();ctx.moveTo(sp.x,sp.y);ctx.lineTo(tp.x,tp.y);ctx.stroke();\n }\n ctx.setLineDash([]);\n ctx.restore();\n\n // Draw hexagons per cluster\n for (const cluster of MAP.clusters) {\n const baseColor = cluster.color;\n const clusterAssets = cluster.assetIds.map(id=>assetIndex.get(id)).filter(Boolean);\n const isClusterMatch = !hasSearch || clusterAssets.some(a => matchedIds.has(a.id));\n const clusterDim = hasSearch && !isClusterMatch;\n\n for (let ai=0; ai<clusterAssets.length; ai++) {\n const asset = clusterAssets[ai];\n const wx=htp_x(asset.q,asset.r), wy=htp_y(asset.q,asset.r);\n const s=w2s(wx,wy);\n const cx=s.x, cy=s.y;\n\n // Frustum cull\n if(cx+size<0||cx-size>W||cy+size<0||cy-size>H) continue;\n\n // Shade variation\n const shade = ai%3===0?18:ai%3===1?8:0;\n let fillColor = shadeV(baseColor, shade);\n\n // Quality overlay\n if (showQuality && asset.qualityScore !== null && asset.qualityScore !== undefined) {\n const q = asset.qualityScore;\n if (q < 40) fillColor = '#ef4444';\n else if (q < 70) fillColor = '#f97316';\n }\n\n const alpha = clusterDim ? 0.18 : 1;\n const isHovered = asset.id === hoveredAssetId;\n const isSelected = asset.id === selectedAssetId;\n const isConnectFirst = asset.id === connectFirst;\n\n ctx.save();\n ctx.globalAlpha = alpha;\n hexPath(cx, cy, size*0.92);\n\n if (isDark && (isHovered||isSelected||isConnectFirst)) {\n ctx.shadowColor = fillColor;\n ctx.shadowBlur = isSelected ? 16 : 8;\n }\n\n ctx.fillStyle = fillColor;\n ctx.fill();\n\n if (isSelected||isConnectFirst) {\n ctx.strokeStyle = isConnectFirst ? '#f59e0b' : '#fff';\n ctx.lineWidth = 2.5;\n ctx.stroke();\n } else if (isHovered) {\n ctx.strokeStyle = isDark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.2)';\n ctx.lineWidth = 1.5;\n ctx.stroke();\n } else {\n ctx.strokeStyle = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.4)';\n ctx.lineWidth = 1;\n ctx.stroke();\n }\n ctx.restore();\n\n // Quality dot\n if (showQuality && asset.qualityScore!==null && asset.qualityScore!==undefined && size>8) {\n const q = asset.qualityScore;\n if (q < 70) {\n ctx.beginPath();\n ctx.arc(cx+size*0.4, cy-size*0.4, Math.max(3,size*0.14), 0, Math.PI*2);\n ctx.fillStyle = q<40?'#ef4444':'#f97316';\n ctx.fill();\n }\n }\n\n // Asset labels (detail 4, or 3 at high zoom)\n const showAssetLabel = showLabels && !clusterDim &&\n ((detailLevel>=4)||(detailLevel===3 && scale>=0.8));\n if (showAssetLabel && size>14) {\n const label = asset.name.length>12 ? asset.name.substring(0,11)+'...' : asset.name;\n ctx.save();\n ctx.font = Math.max(8,Math.min(11,size*0.38))+'px -apple-system,sans-serif';\n ctx.fillStyle = isDark ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.9)';\n ctx.textAlign='center';ctx.textBaseline='middle';\n ctx.fillText(label, cx, cy);\n ctx.restore();\n }\n }\n }\n\n // Cluster labels (pill badges)\n if (showLabels && detailLevel>=1) {\n for (const cluster of MAP.clusters) {\n if (cluster.assetIds.length===0) continue;\n if (hasSearch && !cluster.assetIds.some(id=>matchedIds.has(id))) continue;\n const s=w2s(cluster.centroid.x, cluster.centroid.y);\n drawPill(s.x, s.y-size*1.2, cluster.label, cluster.color, 14);\n }\n }\n\n // Sub-domain labels (detail 2+)\n if (showLabels && detailLevel>=2) {\n const subGroups = new Map();\n for (const a of MAP.assets) {\n if (!a.subDomain) continue;\n const key = a.domain+'|'+a.subDomain;\n if (!subGroups.has(key)) subGroups.set(key, []);\n subGroups.get(key).push(a);\n }\n for (const [, group] of subGroups) {\n let sx=0,sy=0;\n for (const a of group) { sx+=htp_x(a.q,a.r); sy+=htp_y(a.q,a.r); }\n const cx=sx/group.length, cy=sy/group.length;\n const s = w2s(cx, cy);\n drawPill(s.x, s.y+size*1.5, group[0].subDomain, '#64748b', 11);\n }\n }\n}\n\nfunction drawPill(x, y, text, color, fontSize) {\n if(!text) return;\n ctx.save();\n ctx.font = '600 '+fontSize+'px -apple-system,sans-serif';\n const tw=ctx.measureText(text).width;\n const ph=fontSize+8, pw=tw+20;\n ctx.beginPath();\n if (ctx.roundRect) ctx.roundRect(x-pw/2, y-ph/2, pw, ph, ph/2);\n else { ctx.rect(x-pw/2, y-ph/2, pw, ph); }\n ctx.fillStyle = isDark ? 'rgba(30,41,59,0.9)' : 'rgba(255,255,255,0.92)';\n ctx.shadowColor='rgba(0,0,0,0.15)'; ctx.shadowBlur=6;\n ctx.fill(); ctx.shadowBlur=0;\n ctx.fillStyle = isDark ? '#e2e8f0' : '#0f172a';\n ctx.textAlign='center'; ctx.textBaseline='middle';\n ctx.fillText(text, x, y);\n ctx.restore();\n}\n\n// ── Hit testing ───────────────────────────────────────────────────────────────\nfunction getAssetAt(sx, sy) {\n const w=s2w(sx,sy);\n for (const a of MAP.assets) {\n const wx=htp_x(a.q,a.r), wy=htp_y(a.q,a.r);\n const dx=Math.abs(w.x-wx), dy=Math.abs(w.y-wy);\n if (dx>HEX_SIZE||dy>HEX_SIZE) continue;\n if (dx*dx+dy*dy < HEX_SIZE*HEX_SIZE) return a;\n }\n return null;\n}\n\n// ── Search ────────────────────────────────────────────────────────────────────\nfunction getSearchMatches() {\n if(!searchQuery) return new Set();\n const q=searchQuery.toLowerCase();\n const m=new Set();\n for(const a of MAP.assets){\n if(a.name.toLowerCase().includes(q)||(a.domain&&a.domain.toLowerCase().includes(q))||\n (a.subDomain&&a.subDomain.toLowerCase().includes(q))) m.add(a.id);\n }\n return m;\n}\n\n// ── Pan & Zoom ────────────────────────────────────────────────────────────────\nlet dragging=false, lastMX=0, lastMY=0;\n\nwrap.addEventListener('mousedown', e=>{\n if(e.button!==0)return;\n dragging=true; lastMX=e.clientX; lastMY=e.clientY;\n wrap.classList.add('dragging');\n});\nwindow.addEventListener('mouseup', ()=>{dragging=false;wrap.classList.remove('dragging');});\nwindow.addEventListener('mousemove', e=>{\n if(dragging){\n vx+=e.clientX-lastMX; vy+=e.clientY-lastMY;\n lastMX=e.clientX; lastMY=e.clientY; draw(); return;\n }\n const rect=wrap.getBoundingClientRect();\n const sx=e.clientX-rect.left, sy=e.clientY-rect.top;\n const asset=getAssetAt(sx,sy);\n const newId=asset?asset.id:null;\n if(newId!==hoveredAssetId){hoveredAssetId=newId;draw();}\n const tt=document.getElementById('tooltip');\n if(asset){\n document.getElementById('tt-name').textContent=asset.name;\n document.getElementById('tt-domain').textContent=asset.domain+(asset.subDomain?' > '+asset.subDomain:'');\n document.getElementById('tt-quality').textContent=asset.qualityScore!==null?'Quality: '+asset.qualityScore+'/100':'';\n tt.style.display='block';tt.style.left=(e.clientX+12)+'px';tt.style.top=(e.clientY-8)+'px';\n } else { tt.style.display='none'; }\n});\n\nwrap.addEventListener('click', e=>{\n const rect=wrap.getBoundingClientRect();\n const sx=e.clientX-rect.left, sy=e.clientY-rect.top;\n const asset=getAssetAt(sx,sy);\n if(connectMode){\n if(!asset) return;\n if(!connectFirst){connectFirst=asset.id;draw();}\n else if(connectFirst!==asset.id){\n localConnections.push({id:crypto.randomUUID(),sourceAssetId:connectFirst,targetAssetId:asset.id,type:'connection'});\n connectFirst=null;draw();\n }\n return;\n }\n if(asset){selectedAssetId=asset.id;showDetailPanel(asset);}\n else{selectedAssetId=null;document.getElementById('detail-panel').classList.remove('open');}\n draw();\n});\n\n// Touch\nlet lastTouches=[];\nwrap.addEventListener('touchstart',e=>{lastTouches=[...e.touches];},{passive:true});\nwrap.addEventListener('touchmove',e=>{\n if(e.touches.length===1){\n vx+=e.touches[0].clientX-lastTouches[0].clientX;\n vy+=e.touches[0].clientY-lastTouches[0].clientY;draw();\n } else if(e.touches.length===2){\n const d0=Math.hypot(lastTouches[0].clientX-lastTouches[1].clientX,lastTouches[0].clientY-lastTouches[1].clientY);\n const d1=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY);\n const mx=(e.touches[0].clientX+e.touches[1].clientX)/2;\n const my=(e.touches[0].clientY+e.touches[1].clientY)/2;\n applyZoom(d1/d0,mx,my);\n }\n lastTouches=[...e.touches];\n},{passive:true});\n\nwrap.addEventListener('wheel',e=>{\n e.preventDefault();\n const rect=wrap.getBoundingClientRect();\n applyZoom(e.deltaY<0?1.12:1/1.12,e.clientX-rect.left,e.clientY-rect.top);\n},{passive:false});\n\nfunction applyZoom(factor,sx,sy){\n const ns=Math.max(0.05,Math.min(8,scale*factor));\n const wx=(sx-vx)/scale,wy=(sy-vy)/scale;\n scale=ns;vx=sx-wx*scale;vy=sy-wy*scale;\n document.getElementById('zoom-pct').textContent=Math.round(scale*100)+'%';draw();\n}\ndocument.getElementById('zoom-in').addEventListener('click',()=>applyZoom(1.25,W/2,H/2));\ndocument.getElementById('zoom-out').addEventListener('click',()=>applyZoom(1/1.25,W/2,H/2));\n\n// Keyboard\nwrap.addEventListener('keydown',e=>{\n const step=40;\n if(e.key==='ArrowLeft'){vx+=step;draw();}\n else if(e.key==='ArrowRight'){vx-=step;draw();}\n else if(e.key==='ArrowUp'){vy+=step;draw();}\n else if(e.key==='ArrowDown'){vy-=step;draw();}\n else if(e.key==='+'||e.key==='=')applyZoom(1.2,W/2,H/2);\n else if(e.key==='-')applyZoom(1/1.2,W/2,H/2);\n else if(e.key==='Escape'){\n selectedAssetId=null;document.getElementById('detail-panel').classList.remove('open');\n if(connectMode)toggleConnect();draw();\n }\n});\n\n// ── Detail Panel ──────────────────────────────────────────────────────────────\nfunction showDetailPanel(asset) {\n document.getElementById('dp-name').textContent=asset.name;\n const body=document.getElementById('dp-body');\n const rows=[['Domain',asset.domain],['Sub-domain',asset.subDomain],\n ['Quality Score',asset.qualityScore!==null?renderQuality(asset.qualityScore):null],\n ...Object.entries(asset.metadata||{}).slice(0,8).map(([k,v])=>[k,String(v)])\n ].filter(([,v])=>v!==null&&v!==undefined&&v!=='');\n body.innerHTML=rows.map(([l,v])=>'<div class=\"meta-row\"><div class=\"meta-label\">'+esc(String(l))+'</div><div class=\"meta-value\">'+v+'</div></div>').join('');\n const related=localConnections.filter(c=>c.sourceAssetId===asset.id||c.targetAssetId===asset.id);\n if(related.length>0){\n body.innerHTML+='<div class=\"meta-row\"><div class=\"meta-label\">Connections ('+related.length+')</div><div>'+\n related.map(c=>{const oid=c.sourceAssetId===asset.id?c.targetAssetId:c.sourceAssetId;\n const o=assetIndex.get(oid);return '<div class=\"meta-value\" style=\"margin-top:4px;font-size:12px\">'+(o?esc(o.name):oid)+'</div>';}).join('')+'</div></div>';\n }\n document.getElementById('detail-panel').classList.add('open');\n}\nfunction renderQuality(s){\n const c=s>=70?'#22c55e':s>=40?'#f97316':'#ef4444';\n return s+'/100 <div class=\"quality-bar\"><div class=\"quality-fill\" style=\"width:'+s+'%;background:'+c+'\"></div></div>';\n}\nfunction esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}\ndocument.getElementById('dp-close').addEventListener('click',()=>{\n document.getElementById('detail-panel').classList.remove('open');selectedAssetId=null;draw();\n});\n\n// ── Toolbar ───────────────────────────────────────────────────────────────────\n[1,2,3,4].forEach(n=>{\n document.getElementById('dl-'+n).addEventListener('click',()=>{\n detailLevel=n;document.querySelectorAll('.detail-btn').forEach(b=>b.classList.remove('active'));\n document.getElementById('dl-'+n).classList.add('active');draw();\n });\n});\ndocument.getElementById('btn-labels').addEventListener('click',()=>{\n showLabels=!showLabels;document.getElementById('btn-labels').classList.toggle('active',showLabels);draw();\n});\ndocument.getElementById('btn-quality').addEventListener('click',()=>{\n showQuality=!showQuality;document.getElementById('btn-quality').classList.toggle('active',showQuality);draw();\n});\nfunction toggleConnect(){\n connectMode=!connectMode;connectFirst=null;\n document.getElementById('connect-btn').classList.toggle('active',connectMode);\n wrap.classList.toggle('connecting',connectMode);\n document.getElementById('connect-hint').style.display=connectMode?'block':'none';draw();\n}\ndocument.getElementById('connect-btn').addEventListener('click',toggleConnect);\ndocument.getElementById('theme-btn').addEventListener('click',()=>{\n isDark=!isDark;\n document.body.classList.toggle('dark',isDark);document.body.classList.toggle('light',!isDark);\n document.getElementById('theme-btn').innerHTML=isDark?'&#9788;':'&#9790;';draw();\n});\ndocument.getElementById('search-input').addEventListener('input',e=>{searchQuery=e.target.value.trim();draw();});\n\n// ── Init ──────────────────────────────────────────────────────────────────────\nresize(); fitToView();\ndocument.getElementById('zoom-pct').textContent=Math.round(scale*100)+'%';\ndraw();\n})();\n</script>\n</body>\n</html>`;\n}\n\n// ── Discovery App (Combined Enterprise Frontend) ──────────────────────────────\n\nexport function exportDiscoveryApp(\n nodes: NodeRow[],\n edges: EdgeRow[],\n options?: { theme?: 'light' | 'dark' },\n): string {\n const theme = options?.theme ?? 'dark';\n\n // ── Topology D3 data ──────────────────────────────────────────────────────\n const graphData = JSON.stringify({\n nodes: nodes.map(n => ({\n id: n.id, name: n.name, type: n.type, layer: nodeLayer(n.type),\n confidence: n.confidence, discoveredVia: n.discoveredVia,\n discoveredAt: n.discoveredAt, tags: n.tags, metadata: n.metadata,\n })),\n links: edges.map(e => ({\n source: e.sourceId, target: e.targetId,\n relationship: e.relationship, confidence: e.confidence, evidence: e.evidence,\n })),\n });\n\n // ── Hex map data ──────────────────────────────────────────────────────────\n const { assets, clusters, connections } = buildMapData(nodes, edges, { theme });\n const isEmpty = assets.length === 0;\n const HEX_SIZE = 24;\n const mapJson = JSON.stringify({\n assets: assets.map(a => ({\n id: a.id, name: a.name, domain: a.domain, subDomain: a.subDomain ?? null,\n qualityScore: a.qualityScore ?? null, metadata: a.metadata,\n q: a.position.q, r: a.position.r,\n })),\n clusters: clusters.map(c => ({\n id: c.id, label: c.label, domain: c.domain, color: c.color,\n assetIds: c.assetIds, centroid: c.centroid,\n })),\n connections: connections.map(c => ({\n id: c.id, sourceAssetId: c.sourceAssetId, targetAssetId: c.targetAssetId,\n type: c.type ?? 'connection',\n })),\n });\n\n const nodeCount = nodes.length;\n const edgeCount = edges.length;\n const assetCount = assets.length;\n const clusterCount = clusters.length;\n\n return `<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"${theme}\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n<title>Cartography \\u2014 Datasynx Discovery</title>\n<script src=\"https://d3js.org/d3.v7.min.js\"><\\/script>\n<style>\n/* ── CSS Custom Properties ──────────────────────────────────────────────── */\n:root{\n --bg-base:#0f172a;--bg-surface:#1e293b;--bg-elevated:#273148;\n --border:#334155;--border-dim:#1e293b;\n --text:#e2e8f0;--text-muted:#94a3b8;--text-dim:#475569;\n --accent:#3b82f6;--accent-hover:#2563eb;--accent-dim:rgba(59,130,246,.12);\n}\n[data-theme=\"light\"]{\n --bg-base:#f8fafc;--bg-surface:#ffffff;--bg-elevated:#f1f5f9;\n --border:#e2e8f0;--border-dim:#f1f5f9;\n --text:#0f172a;--text-muted:#64748b;--text-dim:#94a3b8;\n --accent:#2563eb;--accent-hover:#1d4ed8;--accent-dim:rgba(37,99,235,.08);\n}\n\n/* ── Reset ──────────────────────────────────────────────────────────────── */\n*{box-sizing:border-box;margin:0;padding:0}\nhtml,body{width:100%;height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Inter',sans-serif}\nbody{display:flex;flex-direction:column;background:var(--bg-base);color:var(--text)}\n\n/* ── Topbar ─────────────────────────────────────────────────────────────── */\n#topbar{\n height:56px;display:flex;align-items:center;gap:16px;padding:0 20px;\n background:var(--bg-surface);border-bottom:1px solid var(--border);z-index:100;flex-shrink:0;\n}\n.tb-left{display:flex;align-items:center;gap:10px}\n.brand-logo{flex-shrink:0}\n.brand-name{font-size:15px;font-weight:700;color:var(--accent);letter-spacing:-.02em}\n.brand-product{font-size:14px;font-weight:500;color:var(--text-muted);margin-left:2px}\n.brand-sep{width:1px;height:24px;background:var(--border);margin:0 6px}\n.tb-center{display:flex;align-items:center;gap:2px;margin-left:auto;\n background:var(--bg-elevated);border-radius:8px;padding:3px}\n.tab-btn{\n padding:6px 16px;border:none;border-radius:6px;font-size:13px;font-weight:500;\n cursor:pointer;color:var(--text-muted);background:transparent;font-family:inherit;\n transition:all .15s;\n}\n.tab-btn:hover{color:var(--text)}\n.tab-btn.active{background:var(--accent);color:#fff;box-shadow:0 1px 3px rgba(0,0,0,.2)}\n.tb-right{display:flex;align-items:center;gap:8px;margin-left:auto}\n.tb-search{\n display:flex;align-items:center;gap:6px;background:var(--bg-elevated);\n border:1px solid var(--border);border-radius:8px;padding:5px 10px;\n}\n.tb-search input{\n border:none;background:transparent;font-size:13px;outline:none;width:160px;\n color:var(--text);font-family:inherit;\n}\n.tb-search input::placeholder{color:var(--text-dim)}\n.tb-search svg{flex-shrink:0;color:var(--text-dim)}\n.icon-btn{\n width:36px;height:36px;border-radius:8px;border:1px solid var(--border);\n background:var(--bg-surface);cursor:pointer;display:flex;align-items:center;\n justify-content:center;color:var(--text-muted);text-decoration:none;transition:all .15s;font-size:16px;\n}\n.icon-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-dim)}\n.tb-stats{font-size:11px;color:var(--text-dim);white-space:nowrap}\n\n/* ── Views ──────────────────────────────────────────────────────────────── */\n.view{flex:1;display:none;overflow:hidden;position:relative}\n.view.active{display:flex}\n\n/* ═══════════════════════════════════════════════════════════════════════════\n MAP VIEW\n ═══════════════════════════════════════════════════════════════════════════ */\n#map-wrap{flex:1;position:relative;overflow:hidden;cursor:grab}\n#map-wrap.dragging{cursor:grabbing}\n#map-wrap.connecting{cursor:crosshair}\n#map-wrap canvas{display:block;width:100%;height:100%}\n#map-detail{\n width:280px;background:var(--bg-surface);border-left:1px solid var(--border);\n display:flex;flex-direction:column;transform:translateX(100%);\n transition:transform .2s ease;z-index:5;flex-shrink:0;overflow-y:auto;\n}\n#map-detail.open{transform:translateX(0)}\n#map-detail .panel-header{\n padding:16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;\n}\n#map-detail .panel-header h3{font-size:14px;font-weight:600;flex:1;word-break:break-word}\n.close-btn{\n width:24px;height:24px;border:none;background:transparent;cursor:pointer;\n color:var(--text-muted);border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:16px;\n}\n.close-btn:hover{background:var(--bg-elevated)}\n.panel-body{padding:12px 16px;display:flex;flex-direction:column;gap:12px}\n.meta-row{display:flex;flex-direction:column;gap:3px}\n.meta-label{font-size:11px;font-weight:500;color:var(--text-dim);text-transform:uppercase;letter-spacing:.05em}\n.meta-value{font-size:13px;word-break:break-all}\n.quality-bar{height:6px;border-radius:3px;background:var(--bg-elevated);margin-top:4px}\n.quality-fill{height:6px;border-radius:3px;transition:width .3s}\n\n/* Map toolbars */\n#map-tb-left{position:absolute;bottom:20px;left:20px;display:flex;gap:8px;z-index:10}\n#map-tb-right{position:absolute;bottom:20px;right:20px;display:flex;flex-direction:column;align-items:flex-end;gap:8px;z-index:10}\n.tb-tool{\n width:40px;height:40px;border-radius:10px;border:1px solid var(--border);\n background:var(--bg-surface);box-shadow:0 1px 4px rgba(0,0,0,.08);cursor:pointer;\n display:flex;align-items:center;justify-content:center;font-size:18px;\n transition:all .15s;color:var(--text);\n}\n.tb-tool:hover{border-color:var(--text-muted)}\n.tb-tool.active{background:var(--accent-dim);border-color:var(--accent)}\n.map-zoom{display:flex;align-items:center;gap:6px}\n.zoom-btn{\n width:34px;height:34px;border-radius:8px;border:1px solid var(--border);\n background:var(--bg-surface);cursor:pointer;font-size:18px;color:var(--text);\n display:flex;align-items:center;justify-content:center;\n}\n.zoom-btn:hover{background:var(--bg-elevated)}\n#map-zoom-pct{font-size:12px;font-weight:500;color:var(--text-dim);min-width:38px;text-align:center}\n#map-connect-hint{\n position:absolute;top:12px;left:50%;transform:translateX(-50%);\n background:#fef3c7;border:1px solid #f59e0b;color:#92400e;\n padding:6px 14px;border-radius:20px;font-size:12px;font-weight:500;\n display:none;z-index:20;pointer-events:none;\n}\n#map-tooltip{\n position:fixed;background:var(--bg-surface);color:var(--text);border-radius:8px;\n padding:8px 12px;font-size:12px;pointer-events:none;z-index:200;\n display:none;max-width:220px;box-shadow:0 4px 12px rgba(0,0,0,.25);border:1px solid var(--border);\n}\n#map-tooltip .tt-name{font-weight:600;margin-bottom:2px}\n#map-tooltip .tt-domain{color:var(--text-muted);font-size:11px}\n#map-tooltip .tt-quality{font-size:11px;margin-top:2px}\n#map-empty{\n position:absolute;inset:0;display:flex;flex-direction:column;\n align-items:center;justify-content:center;gap:12px;color:var(--text-muted);\n}\n#map-empty p{font-size:14px}\n\n/* ═══════════════════════════════════════════════════════════════════════════\n TOPOLOGY VIEW\n ═══════════════════════════════════════════════════════════════════════════ */\n#topo-panel{\n width:220px;min-width:220px;height:100%;overflow:hidden;\n background:var(--bg-surface);border-right:1px solid var(--border);\n display:flex;flex-direction:column;\n}\n#topo-panel-header{\n padding:10px 12px 8px;border-bottom:1px solid var(--border);\n font-size:11px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.6px;\n}\n#topo-search{\n width:calc(100% - 16px);margin:8px;padding:5px 8px;\n background:var(--bg-elevated);border:1px solid var(--border);border-radius:5px;\n color:var(--text);font-size:11px;font-family:inherit;outline:none;\n}\n#topo-search:focus{border-color:var(--accent)}\n#topo-list{flex:1;overflow-y:auto;padding-bottom:8px}\n.topo-item{\n padding:5px 12px;cursor:pointer;font-size:11px;\n display:flex;align-items:center;gap:6px;border-left:2px solid transparent;\n}\n.topo-item:hover{background:var(--bg-elevated)}\n.topo-item.active{background:var(--accent-dim);border-left-color:var(--accent)}\n.topo-dot{width:7px;height:7px;border-radius:2px;flex-shrink:0}\n.topo-name{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}\n.topo-type{color:var(--text-dim);font-size:9px;flex-shrink:0}\n\n#topo-graph{flex:1;height:100%;position:relative}\n#topo-graph svg{width:100%;height:100%}\n.hull{opacity:.12;stroke-width:1.5;stroke-opacity:.25}\n.hull-label{font-size:13px;font-weight:700;letter-spacing:1px;text-transform:uppercase;fill-opacity:.5;pointer-events:none}\n.link{stroke-opacity:.4}\n.link-label{font-size:8px;fill:var(--text-dim);pointer-events:none;opacity:0}\n.node-hex{stroke-width:1.8;cursor:pointer;transition:opacity .15s}\n.node-hex:hover{filter:brightness(1.3);stroke-width:3}\n.node-hex.selected{stroke-width:3.5;filter:brightness(1.5)}\n.node-label{font-size:10px;fill:var(--text);pointer-events:none;opacity:0}\n\n#topo-sidebar{\n width:300px;min-width:300px;height:100%;overflow-y:auto;\n background:var(--bg-surface);border-left:1px solid var(--border);\n padding:16px;font-size:12px;line-height:1.6;\n}\n#topo-sidebar h2{margin:0 0 8px;font-size:14px;color:var(--accent)}\n#topo-sidebar .meta-table{width:100%;border-collapse:collapse}\n#topo-sidebar .meta-table td{padding:3px 6px;border-bottom:1px solid var(--border-dim);vertical-align:top}\n#topo-sidebar .meta-table td:first-child{color:var(--text-dim);white-space:nowrap;width:90px}\n#topo-sidebar .tag{display:inline-block;background:var(--bg-elevated);border-radius:3px;padding:1px 5px;margin:1px;font-size:10px}\n#topo-sidebar .conf-bar{height:5px;border-radius:3px;background:var(--bg-elevated);margin-top:3px}\n#topo-sidebar .conf-fill{height:100%;border-radius:3px}\n#topo-sidebar .edges-list{margin-top:12px}\n#topo-sidebar .edge-item{padding:4px 0;border-bottom:1px solid var(--border-dim);color:var(--text-dim);font-size:11px}\n#topo-sidebar .edge-item span{color:var(--text)}\n.hint{color:var(--text-dim);font-size:11px;margin-top:8px}\n\n#topo-hud{\n position:absolute;top:10px;left:10px;background:rgba(15,23,42,.88);\n padding:10px 14px;border-radius:8px;font-size:12px;border:1px solid var(--border);pointer-events:none;\n}\n#topo-hud strong{color:var(--accent)}\n#topo-hud .stats{color:var(--text-dim)}\n#topo-hud .zoom-level{color:var(--text-dim);font-size:10px;margin-top:2px}\n\n#topo-toolbar{position:absolute;top:10px;right:10px;display:flex;flex-wrap:wrap;gap:4px;pointer-events:auto;align-items:center}\n.filter-btn{\n background:rgba(15,23,42,.85);border:1px solid var(--border);border-radius:6px;\n color:var(--text);padding:4px 10px;font-size:11px;cursor:pointer;\n font-family:inherit;display:flex;align-items:center;gap:5px;\n}\n.filter-btn:hover{border-color:var(--text-dim)}\n.filter-btn.off{opacity:.35}\n.filter-dot{width:8px;height:8px;border-radius:2px;display:inline-block}\n.export-btn{\n background:rgba(15,23,42,.85);border:1px solid var(--border);border-radius:6px;\n color:var(--accent);padding:4px 12px;font-size:11px;cursor:pointer;font-family:inherit;\n}\n.export-btn:hover{border-color:var(--accent);background:var(--accent-dim)}\n</style>\n</head>\n<body>\n\n<!-- ═══════════════════════════════════════════════════════════════════════════\n TOPBAR\n ═══════════════════════════════════════════════════════════════════════════ -->\n<header id=\"topbar\">\n <div class=\"tb-left\">\n <svg class=\"brand-logo\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" fill=\"none\">\n <path d=\"M16 1.5L29.5 8.75V23.25L16 30.5L2.5 23.25V8.75L16 1.5Z\" fill=\"#0F2347\" stroke=\"#2563EB\" stroke-width=\"1.2\"/>\n <circle cx=\"10\" cy=\"16\" r=\"2.8\" fill=\"#60A5FA\"/><circle cx=\"22\" cy=\"10.5\" r=\"2.2\" fill=\"#38BDF8\"/>\n <circle cx=\"22\" cy=\"21.5\" r=\"2.2\" fill=\"#38BDF8\"/>\n <line x1=\"12.5\" y1=\"14.8\" x2=\"19.8\" y2=\"11.2\" stroke=\"#93C5FD\" stroke-width=\"1.2\"/>\n <line x1=\"12.5\" y1=\"17.2\" x2=\"19.8\" y2=\"20.8\" stroke=\"#93C5FD\" stroke-width=\"1.2\"/>\n <line x1=\"22\" y1=\"12.7\" x2=\"22\" y2=\"19.3\" stroke=\"#93C5FD\" stroke-width=\"1\" stroke-dasharray=\"2 1.5\"/>\n </svg>\n <span class=\"brand-name\">datasynx</span>\n <span class=\"brand-sep\"></span>\n <span class=\"brand-product\">Cartography</span>\n </div>\n <div class=\"tb-center\">\n <button class=\"tab-btn active\" id=\"tab-map-btn\" data-tab=\"map\">Map</button>\n <button class=\"tab-btn\" id=\"tab-topo-btn\" data-tab=\"topo\">Topology</button>\n </div>\n <div class=\"tb-right\">\n <span class=\"tb-stats\">${nodeCount} nodes &middot; ${edgeCount} edges</span>\n <div class=\"tb-search\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"M21 21l-4.35-4.35\"/>\n </svg>\n <input id=\"global-search\" type=\"text\" placeholder=\"Search...\" autocomplete=\"off\" spellcheck=\"false\"/>\n </div>\n <a href=\"https://www.linkedin.com/company/datasynx-ai/\" target=\"_blank\" rel=\"noopener noreferrer\"\n class=\"icon-btn\" title=\"Datasynx on LinkedIn\" aria-label=\"LinkedIn\">\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z\"/>\n </svg>\n </a>\n <button id=\"theme-btn\" class=\"icon-btn\" title=\"Toggle theme\" aria-label=\"Toggle theme\">\n ${theme === 'dark' ? '&#9788;' : '&#9790;'}\n </button>\n </div>\n</header>\n\n<!-- ═══════════════════════════════════════════════════════════════════════════\n MAP VIEW\n ═══════════════════════════════════════════════════════════════════════════ -->\n<div id=\"view-map\" class=\"view active\">\n <div id=\"map-wrap\" tabindex=\"0\" aria-label=\"Data cartography hex map\">\n <canvas id=\"hexmap\" aria-hidden=\"true\"></canvas>\n ${isEmpty ? '<div id=\"map-empty\"><p style=\"font-size:48px\">&#128506;</p><p>No data assets discovered yet</p><p style=\"font-size:12px\">Run <code>datasynx-cartography discover</code> to populate the map</p></div>' : ''}\n </div>\n <div id=\"map-detail\">\n <div class=\"panel-header\">\n <h3 id=\"md-name\">&mdash;</h3>\n <button class=\"close-btn\" id=\"md-close\" aria-label=\"Close\">&#10005;</button>\n </div>\n <div class=\"panel-body\" id=\"md-body\"></div>\n </div>\n <div id=\"map-tb-left\">\n <button class=\"tb-tool active\" id=\"btn-labels\" title=\"Toggle labels\">&#127991;</button>\n <button class=\"tb-tool\" id=\"btn-quality\" title=\"Quality layer\">&#128065;</button>\n <button class=\"tb-tool\" id=\"btn-connect\" title=\"Connection tool\">&#128279;</button>\n </div>\n <div id=\"map-tb-right\">\n <div class=\"map-zoom\">\n <button class=\"zoom-btn\" id=\"mz-out\">&minus;</button>\n <span id=\"map-zoom-pct\">100%</span>\n <button class=\"zoom-btn\" id=\"mz-in\">+</button>\n </div>\n </div>\n <div id=\"map-connect-hint\">Click two assets to create a connection</div>\n <div id=\"map-tooltip\"><div class=\"tt-name\" id=\"mtt-name\"></div><div class=\"tt-domain\" id=\"mtt-domain\"></div><div class=\"tt-quality\" id=\"mtt-quality\"></div></div>\n</div>\n\n<!-- ═══════════════════════════════════════════════════════════════════════════\n TOPOLOGY VIEW\n ═══════════════════════════════════════════════════════════════════════════ -->\n<div id=\"view-topo\" class=\"view\">\n <div id=\"topo-panel\">\n <div id=\"topo-panel-header\">Nodes (${nodeCount})</div>\n <input id=\"topo-search\" type=\"text\" placeholder=\"Search nodes\\u2026\" autocomplete=\"off\" spellcheck=\"false\"/>\n <div id=\"topo-list\"></div>\n </div>\n <div id=\"topo-graph\">\n <div id=\"topo-hud\">\n <strong>Topology</strong>&nbsp;\n <span class=\"stats\">${nodeCount} nodes &middot; ${edgeCount} edges</span><br/>\n <span class=\"zoom-level\">Scroll = zoom &middot; Drag = pan &middot; Click = details</span>\n </div>\n <div id=\"topo-toolbar\"></div>\n <svg></svg>\n </div>\n <div id=\"topo-sidebar\">\n <h2>Infrastructure Map</h2>\n <p class=\"hint\">Click a node to view details.</p>\n </div>\n</div>\n\n<script>\n// ═══════════════════════════════════════════════════════════════════════════════\n// SHARED STATE\n// ═══════════════════════════════════════════════════════════════════════════════\nlet isDark = document.documentElement.getAttribute('data-theme') === 'dark';\nlet currentTab = 'map';\nlet topoInited = false;\n\n// ── Theme toggle ─────────────────────────────────────────────────────────────\ndocument.getElementById('theme-btn').addEventListener('click', function() {\n isDark = !isDark;\n document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');\n this.innerHTML = isDark ? '\\\\u2606' : '\\\\u263E';\n if (typeof drawMap === 'function') drawMap();\n});\n\n// ── Tab switching ────────────────────────────────────────────────────────────\ndocument.querySelectorAll('.tab-btn').forEach(function(btn) {\n btn.addEventListener('click', function() {\n var tab = this.getAttribute('data-tab');\n if (tab === currentTab) return;\n currentTab = tab;\n document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });\n this.classList.add('active');\n document.querySelectorAll('.view').forEach(function(v) { v.classList.remove('active'); });\n document.getElementById('view-' + tab).classList.add('active');\n if (tab === 'topo' && !topoInited) { initTopology(); topoInited = true; }\n if (tab === 'map' && typeof drawMap === 'function') { resizeMap(); }\n });\n});\n\n// ── Global search ────────────────────────────────────────────────────────────\ndocument.getElementById('global-search').addEventListener('input', function(e) {\n var q = e.target.value.trim();\n if (typeof setMapSearch === 'function') setMapSearch(q);\n if (typeof setTopoSearch === 'function') setTopoSearch(q);\n});\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// MAP VIEW\n// ═══════════════════════════════════════════════════════════════════════════════\nvar MAP = ${mapJson};\nvar MAP_HEX = ${HEX_SIZE};\nvar MAP_EMPTY = ${isEmpty};\n\nvar mapAssetIndex = new Map();\nvar mapClusterByAsset = new Map();\nfor (var ci = 0; ci < MAP.clusters.length; ci++) {\n var c = MAP.clusters[ci];\n for (var ai = 0; ai < c.assetIds.length; ai++) mapClusterByAsset.set(c.assetIds[ai], c);\n}\nfor (var ni = 0; ni < MAP.assets.length; ni++) mapAssetIndex.set(MAP.assets[ni].id, MAP.assets[ni]);\n\nvar mapCanvas = document.getElementById('hexmap');\nvar mapCtx = mapCanvas.getContext('2d');\nvar mapWrap = document.getElementById('map-wrap');\nvar mW = 0, mH = 0;\nvar mvx = 0, mvy = 0, mScale = 1;\nvar mDetailLevel = 2, mShowLabels = true, mShowQuality = false;\nvar mConnectMode = false, mConnectFirst = null;\nvar mHoveredId = null, mSelectedId = null;\nvar mSearchQuery = '';\nvar mLocalConns = MAP.connections.slice();\n\nfunction setMapSearch(q) { mSearchQuery = q; drawMap(); }\n\nfunction resizeMap() {\n var dpr = window.devicePixelRatio || 1;\n mW = mapWrap.clientWidth; mH = mapWrap.clientHeight;\n mapCanvas.width = mW * dpr; mapCanvas.height = mH * dpr;\n mapCanvas.style.width = mW + 'px'; mapCanvas.style.height = mH + 'px';\n mapCtx.setTransform(dpr, 0, 0, dpr, 0, 0);\n drawMap();\n}\nwindow.addEventListener('resize', function() { if (currentTab === 'map') resizeMap(); });\n\nfunction mHtp_x(q, r) { return MAP_HEX * (1.5 * q); }\nfunction mHtp_y(q, r) { return MAP_HEX * (Math.sqrt(3) / 2 * q + Math.sqrt(3) * r); }\nfunction mW2s(wx, wy) { return { x: wx * mScale + mvx, y: wy * mScale + mvy }; }\nfunction mS2w(sx, sy) { return { x: (sx - mvx) / mScale, y: (sy - mvy) / mScale }; }\n\nfunction mapFitToView() {\n if (MAP_EMPTY || MAP.assets.length === 0) { mvx = 0; mvy = 0; mScale = 1; return; }\n var mnx = Infinity, mny = Infinity, mxx = -Infinity, mxy = -Infinity;\n for (var i = 0; i < MAP.assets.length; i++) {\n var a = MAP.assets[i], px = mHtp_x(a.q, a.r), py = mHtp_y(a.q, a.r);\n if (px < mnx) mnx = px; if (py < mny) mny = py; if (px > mxx) mxx = px; if (py > mxy) mxy = py;\n }\n var pw = mxx - mnx + MAP_HEX * 4, ph = mxy - mny + MAP_HEX * 4;\n mScale = Math.min(mW / pw, mH / ph, 2) * 0.85;\n mvx = mW / 2 - ((mnx + mxx) / 2) * mScale;\n mvy = mH / 2 - ((mny + mxy) / 2) * mScale;\n}\n\nfunction mHexPath(cx, cy, r) {\n mapCtx.beginPath();\n for (var i = 0; i < 6; i++) {\n var angle = Math.PI / 180 * (60 * i);\n var x = cx + r * Math.cos(angle), y = cy + r * Math.sin(angle);\n i === 0 ? mapCtx.moveTo(x, y) : mapCtx.lineTo(x, y);\n }\n mapCtx.closePath();\n}\n\nfunction mShadeV(hex, amt) {\n if (!hex || hex.length < 7) return hex;\n var n = parseInt(hex.replace('#', ''), 16);\n var r = Math.min(255, (n >> 16) + amt), g = Math.min(255, ((n >> 8) & 0xff) + amt), b = Math.min(255, (n & 0xff) + amt);\n return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');\n}\n\nfunction mGetSearchMatches() {\n if (!mSearchQuery) return new Set();\n var q = mSearchQuery.toLowerCase(), m = new Set();\n for (var i = 0; i < MAP.assets.length; i++) {\n var a = MAP.assets[i];\n if (a.name.toLowerCase().includes(q) || (a.domain && a.domain.toLowerCase().includes(q)) ||\n (a.subDomain && a.subDomain.toLowerCase().includes(q))) m.add(a.id);\n }\n return m;\n}\n\nfunction mDrawPill(x, y, text, color, fontSize) {\n if (!text) return;\n mapCtx.save();\n mapCtx.font = '600 ' + fontSize + 'px -apple-system,sans-serif';\n var tw = mapCtx.measureText(text).width;\n var ph = fontSize + 8, pw = tw + 20;\n mapCtx.beginPath();\n if (mapCtx.roundRect) mapCtx.roundRect(x - pw / 2, y - ph / 2, pw, ph, ph / 2);\n else mapCtx.rect(x - pw / 2, y - ph / 2, pw, ph);\n mapCtx.fillStyle = isDark ? 'rgba(30,41,59,0.9)' : 'rgba(255,255,255,0.92)';\n mapCtx.shadowColor = 'rgba(0,0,0,0.15)'; mapCtx.shadowBlur = 6;\n mapCtx.fill(); mapCtx.shadowBlur = 0;\n mapCtx.fillStyle = isDark ? '#e2e8f0' : '#0f172a';\n mapCtx.textAlign = 'center'; mapCtx.textBaseline = 'middle';\n mapCtx.fillText(text, x, y);\n mapCtx.restore();\n}\n\nfunction drawMap() {\n mapCtx.clearRect(0, 0, mW, mH);\n var bg = getComputedStyle(document.documentElement).getPropertyValue('--bg-base').trim();\n mapCtx.fillStyle = bg || (isDark ? '#0f172a' : '#f8fafc');\n mapCtx.fillRect(0, 0, mW, mH);\n if (MAP_EMPTY) return;\n\n var size = MAP_HEX * mScale;\n var matchedIds = mGetSearchMatches();\n var hasSearch = mSearchQuery.length > 0;\n\n // Connections\n mapCtx.save();\n mapCtx.strokeStyle = isDark ? 'rgba(148,163,184,0.35)' : 'rgba(100,116,139,0.25)';\n mapCtx.lineWidth = 1.5; mapCtx.setLineDash([4, 4]);\n for (var ci = 0; ci < mLocalConns.length; ci++) {\n var conn = mLocalConns[ci];\n var src = mapAssetIndex.get(conn.sourceAssetId), tgt = mapAssetIndex.get(conn.targetAssetId);\n if (!src || !tgt) continue;\n var sp = mW2s(mHtp_x(src.q, src.r), mHtp_y(src.q, src.r));\n var tp = mW2s(mHtp_x(tgt.q, tgt.r), mHtp_y(tgt.q, tgt.r));\n mapCtx.beginPath(); mapCtx.moveTo(sp.x, sp.y); mapCtx.lineTo(tp.x, tp.y); mapCtx.stroke();\n }\n mapCtx.setLineDash([]); mapCtx.restore();\n\n // Hexagons per cluster\n for (var cli = 0; cli < MAP.clusters.length; cli++) {\n var cluster = MAP.clusters[cli];\n var baseColor = cluster.color;\n var clusterAssets = cluster.assetIds.map(function(id) { return mapAssetIndex.get(id); }).filter(Boolean);\n var isClusterMatch = !hasSearch || clusterAssets.some(function(a) { return matchedIds.has(a.id); });\n var clusterDim = hasSearch && !isClusterMatch;\n\n for (var ai = 0; ai < clusterAssets.length; ai++) {\n var asset = clusterAssets[ai];\n var wx = mHtp_x(asset.q, asset.r), wy = mHtp_y(asset.q, asset.r);\n var s = mW2s(wx, wy), cx = s.x, cy = s.y;\n if (cx + size < 0 || cx - size > mW || cy + size < 0 || cy - size > mH) continue;\n\n var shade = ai % 3 === 0 ? 18 : ai % 3 === 1 ? 8 : 0;\n var fillColor = mShadeV(baseColor, shade);\n if (mShowQuality && asset.qualityScore !== null && asset.qualityScore !== undefined) {\n if (asset.qualityScore < 40) fillColor = '#ef4444';\n else if (asset.qualityScore < 70) fillColor = '#f97316';\n }\n\n var alpha = clusterDim ? 0.18 : 1;\n var isHov = asset.id === mHoveredId, isSel = asset.id === mSelectedId, isCF = asset.id === mConnectFirst;\n\n mapCtx.save(); mapCtx.globalAlpha = alpha;\n mHexPath(cx, cy, size * 0.92);\n if (isDark && (isHov || isSel || isCF)) { mapCtx.shadowColor = fillColor; mapCtx.shadowBlur = isSel ? 16 : 8; }\n mapCtx.fillStyle = fillColor; mapCtx.fill();\n if (isSel || isCF) { mapCtx.strokeStyle = isCF ? '#f59e0b' : '#fff'; mapCtx.lineWidth = 2.5; mapCtx.stroke(); }\n else if (isHov) { mapCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.2)'; mapCtx.lineWidth = 1.5; mapCtx.stroke(); }\n else { mapCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.4)'; mapCtx.lineWidth = 1; mapCtx.stroke(); }\n mapCtx.restore();\n\n if (mShowQuality && asset.qualityScore !== null && asset.qualityScore !== undefined && size > 8 && asset.qualityScore < 70) {\n mapCtx.beginPath(); mapCtx.arc(cx + size * 0.4, cy - size * 0.4, Math.max(3, size * 0.14), 0, Math.PI * 2);\n mapCtx.fillStyle = asset.qualityScore < 40 ? '#ef4444' : '#f97316'; mapCtx.fill();\n }\n\n var showAssetLabel = mShowLabels && !clusterDim && ((mDetailLevel >= 4) || (mDetailLevel === 3 && mScale >= 0.8));\n if (showAssetLabel && size > 14) {\n var label = asset.name.length > 12 ? asset.name.substring(0, 11) + '...' : asset.name;\n mapCtx.save();\n mapCtx.font = Math.max(8, Math.min(11, size * 0.38)) + 'px -apple-system,sans-serif';\n mapCtx.fillStyle = isDark ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.9)';\n mapCtx.textAlign = 'center'; mapCtx.textBaseline = 'middle';\n mapCtx.fillText(label, cx, cy); mapCtx.restore();\n }\n }\n }\n\n // Cluster labels\n if (mShowLabels && mDetailLevel >= 1) {\n for (var cli2 = 0; cli2 < MAP.clusters.length; cli2++) {\n var cl = MAP.clusters[cli2];\n if (cl.assetIds.length === 0) continue;\n if (hasSearch && !cl.assetIds.some(function(id) { return matchedIds.has(id); })) continue;\n var sc = mW2s(cl.centroid.x, cl.centroid.y);\n mDrawPill(sc.x, sc.y - size * 1.2, cl.label, cl.color, 14);\n }\n }\n\n // Sub-domain labels\n if (mShowLabels && mDetailLevel >= 2) {\n var subGroups = new Map();\n for (var si = 0; si < MAP.assets.length; si++) {\n var sa = MAP.assets[si];\n if (!sa.subDomain) continue;\n var key = sa.domain + '|' + sa.subDomain;\n if (!subGroups.has(key)) subGroups.set(key, []);\n subGroups.get(key).push(sa);\n }\n subGroups.forEach(function(group) {\n var sx = 0, sy = 0;\n for (var gi = 0; gi < group.length; gi++) { sx += mHtp_x(group[gi].q, group[gi].r); sy += mHtp_y(group[gi].q, group[gi].r); }\n var cxs = sx / group.length, cys = sy / group.length;\n var spt = mW2s(cxs, cys);\n mDrawPill(spt.x, spt.y + size * 1.5, group[0].subDomain, '#64748b', 11);\n });\n }\n}\n\n// ── Map hit test ─────────────────────────────────────────────────────────────\nfunction mGetAssetAt(sx, sy) {\n var w = mS2w(sx, sy);\n for (var i = 0; i < MAP.assets.length; i++) {\n var a = MAP.assets[i], wx = mHtp_x(a.q, a.r), wy = mHtp_y(a.q, a.r);\n var dx = Math.abs(w.x - wx), dy = Math.abs(w.y - wy);\n if (dx > MAP_HEX || dy > MAP_HEX) continue;\n if (dx * dx + dy * dy < MAP_HEX * MAP_HEX) return a;\n }\n return null;\n}\n\n// ── Map pan / zoom ───────────────────────────────────────────────────────────\nvar mDragging = false, mLastMX = 0, mLastMY = 0;\nmapWrap.addEventListener('mousedown', function(e) {\n if (e.button !== 0) return;\n mDragging = true; mLastMX = e.clientX; mLastMY = e.clientY;\n mapWrap.classList.add('dragging');\n});\nwindow.addEventListener('mouseup', function() { mDragging = false; mapWrap.classList.remove('dragging'); });\nwindow.addEventListener('mousemove', function(e) {\n if (currentTab !== 'map') return;\n if (mDragging) {\n mvx += e.clientX - mLastMX; mvy += e.clientY - mLastMY;\n mLastMX = e.clientX; mLastMY = e.clientY; drawMap(); return;\n }\n var rect = mapWrap.getBoundingClientRect();\n var sx = e.clientX - rect.left, sy = e.clientY - rect.top;\n var asset = mGetAssetAt(sx, sy);\n var newId = asset ? asset.id : null;\n if (newId !== mHoveredId) { mHoveredId = newId; drawMap(); }\n var tt = document.getElementById('map-tooltip');\n if (asset) {\n document.getElementById('mtt-name').textContent = asset.name;\n document.getElementById('mtt-domain').textContent = asset.domain + (asset.subDomain ? ' > ' + asset.subDomain : '');\n document.getElementById('mtt-quality').textContent = asset.qualityScore !== null ? 'Quality: ' + asset.qualityScore + '/100' : '';\n tt.style.display = 'block'; tt.style.left = (e.clientX + 12) + 'px'; tt.style.top = (e.clientY - 8) + 'px';\n } else { tt.style.display = 'none'; }\n});\n\nmapWrap.addEventListener('click', function(e) {\n var rect = mapWrap.getBoundingClientRect();\n var sx = e.clientX - rect.left, sy = e.clientY - rect.top;\n var asset = mGetAssetAt(sx, sy);\n if (mConnectMode) {\n if (!asset) return;\n if (!mConnectFirst) { mConnectFirst = asset.id; drawMap(); }\n else if (mConnectFirst !== asset.id) {\n mLocalConns.push({ id: crypto.randomUUID(), sourceAssetId: mConnectFirst, targetAssetId: asset.id, type: 'connection' });\n mConnectFirst = null; drawMap();\n }\n return;\n }\n if (asset) { mSelectedId = asset.id; mShowDetail(asset); }\n else { mSelectedId = null; document.getElementById('map-detail').classList.remove('open'); }\n drawMap();\n});\n\nmapWrap.addEventListener('wheel', function(e) {\n e.preventDefault();\n var rect = mapWrap.getBoundingClientRect();\n mApplyZoom(e.deltaY < 0 ? 1.12 : 1 / 1.12, e.clientX - rect.left, e.clientY - rect.top);\n}, { passive: false });\n\nfunction mApplyZoom(factor, sx, sy) {\n var ns = Math.max(0.05, Math.min(8, mScale * factor));\n var wx = (sx - mvx) / mScale, wy = (sy - mvy) / mScale;\n mScale = ns; mvx = sx - wx * mScale; mvy = sy - wy * mScale;\n document.getElementById('map-zoom-pct').textContent = Math.round(mScale * 100) + '%';\n drawMap();\n}\n\ndocument.getElementById('mz-in').addEventListener('click', function() { mApplyZoom(1.25, mW / 2, mH / 2); });\ndocument.getElementById('mz-out').addEventListener('click', function() { mApplyZoom(1 / 1.25, mW / 2, mH / 2); });\n\n// ── Map detail panel ─────────────────────────────────────────────────────────\nfunction mEsc(s) { return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }\nfunction mRenderQ(s) {\n var c = s >= 70 ? '#22c55e' : s >= 40 ? '#f97316' : '#ef4444';\n return s + '/100 <div class=\"quality-bar\"><div class=\"quality-fill\" style=\"width:' + s + '%;background:' + c + '\"></div></div>';\n}\nfunction mShowDetail(asset) {\n document.getElementById('md-name').textContent = asset.name;\n var body = document.getElementById('md-body');\n var rows = [['Domain', asset.domain], ['Sub-domain', asset.subDomain],\n ['Quality', asset.qualityScore !== null ? mRenderQ(asset.qualityScore) : null]\n ].concat(Object.entries(asset.metadata || {}).slice(0, 8).map(function(kv) { return [kv[0], String(kv[1])]; }))\n .filter(function(r) { return r[1] !== null && r[1] !== undefined && r[1] !== ''; });\n body.innerHTML = rows.map(function(r) {\n return '<div class=\"meta-row\"><div class=\"meta-label\">' + mEsc(String(r[0])) + '</div><div class=\"meta-value\">' + r[1] + '</div></div>';\n }).join('');\n var related = mLocalConns.filter(function(cn) { return cn.sourceAssetId === asset.id || cn.targetAssetId === asset.id; });\n if (related.length > 0) {\n body.innerHTML += '<div class=\"meta-row\"><div class=\"meta-label\">Connections (' + related.length + ')</div><div>' +\n related.map(function(cn) {\n var oid = cn.sourceAssetId === asset.id ? cn.targetAssetId : cn.sourceAssetId;\n var o = mapAssetIndex.get(oid);\n return '<div class=\"meta-value\" style=\"margin-top:4px;font-size:12px\">' + (o ? mEsc(o.name) : oid) + '</div>';\n }).join('') + '</div></div>';\n }\n document.getElementById('map-detail').classList.add('open');\n}\ndocument.getElementById('md-close').addEventListener('click', function() {\n document.getElementById('map-detail').classList.remove('open'); mSelectedId = null; drawMap();\n});\n\n// ── Map toolbar ──────────────────────────────────────────────────────────────\ndocument.getElementById('btn-labels').addEventListener('click', function() {\n mShowLabels = !mShowLabels; this.classList.toggle('active', mShowLabels); drawMap();\n});\ndocument.getElementById('btn-quality').addEventListener('click', function() {\n mShowQuality = !mShowQuality; this.classList.toggle('active', mShowQuality); drawMap();\n});\ndocument.getElementById('btn-connect').addEventListener('click', function() {\n mConnectMode = !mConnectMode; mConnectFirst = null;\n this.classList.toggle('active', mConnectMode);\n mapWrap.classList.toggle('connecting', mConnectMode);\n document.getElementById('map-connect-hint').style.display = mConnectMode ? 'block' : 'none'; drawMap();\n});\n\n// Map keyboard\nmapWrap.addEventListener('keydown', function(e) {\n if (e.key === 'ArrowLeft') { mvx += 40; drawMap(); }\n else if (e.key === 'ArrowRight') { mvx -= 40; drawMap(); }\n else if (e.key === 'ArrowUp') { mvy += 40; drawMap(); }\n else if (e.key === 'ArrowDown') { mvy -= 40; drawMap(); }\n else if (e.key === '+' || e.key === '=') mApplyZoom(1.2, mW / 2, mH / 2);\n else if (e.key === '-') mApplyZoom(1 / 1.2, mW / 2, mH / 2);\n else if (e.key === 'Escape') {\n mSelectedId = null; document.getElementById('map-detail').classList.remove('open');\n if (mConnectMode) { mConnectMode = false; mConnectFirst = null; mapWrap.classList.remove('connecting'); document.getElementById('map-connect-hint').style.display = 'none'; document.getElementById('btn-connect').classList.remove('active'); }\n drawMap();\n }\n});\n\n// Map init\nresizeMap(); mapFitToView();\ndocument.getElementById('map-zoom-pct').textContent = Math.round(mScale * 100) + '%';\ndrawMap();\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// TOPOLOGY VIEW (lazy init)\n// ═══════════════════════════════════════════════════════════════════════════════\nvar TOPO = ${graphData};\n\nvar TYPE_COLORS = {\n host:'#4a9eff',database_server:'#ff6b6b',database:'#ff8c42',\n web_service:'#6bcb77',api_endpoint:'#4d96ff',cache_server:'#ffd93d',\n message_broker:'#c77dff',queue:'#e0aaff',topic:'#9d4edd',\n container:'#48cae4',pod:'#00b4d8',k8s_cluster:'#0077b6',\n config_file:'#adb5bd',saas_tool:'#c084fc',table:'#f97316',unknown:'#6c757d'\n};\nvar LAYER_COLORS = { saas:'#c084fc',web:'#6bcb77',data:'#ff6b6b',messaging:'#c77dff',infra:'#4a9eff',config:'#adb5bd',other:'#6c757d' };\nvar LAYER_NAMES = { saas:'SaaS Tools',web:'Web / API',data:'Data Layer',messaging:'Messaging',infra:'Infrastructure',config:'Config',other:'Other' };\n\nvar topoSelectedId = null;\n\nfunction setTopoSearch(q) {\n var el = document.getElementById('topo-search');\n if (el) { el.value = q; buildTopoList(q); }\n}\n\nfunction buildTopoList(filter) {\n var listEl = document.getElementById('topo-list');\n var q = (filter || '').toLowerCase();\n listEl.innerHTML = '';\n var sorted = TOPO.nodes.slice().sort(function(a, b) { return a.name.localeCompare(b.name); });\n for (var i = 0; i < sorted.length; i++) {\n var d = sorted[i];\n if (q && !d.name.toLowerCase().includes(q) && !d.type.includes(q) && !d.id.toLowerCase().includes(q)) continue;\n var item = document.createElement('div');\n item.className = 'topo-item' + (d.id === topoSelectedId ? ' active' : '');\n item.dataset.id = d.id;\n var color = TYPE_COLORS[d.type] || '#aaa';\n item.innerHTML = '<span class=\"topo-dot\" style=\"background:' + color + '\"></span>' +\n '<span class=\"topo-name\" title=\"' + d.id + '\">' + d.name + '</span>' +\n '<span class=\"topo-type\">' + d.type.replace(/_/g, ' ') + '</span>';\n (function(dd) { item.onclick = function() { selectTopoNode(dd); focusTopoNode(dd); }; })(d);\n listEl.appendChild(item);\n }\n}\n\ndocument.getElementById('topo-search').addEventListener('input', function(e) { buildTopoList(e.target.value); });\n\nvar topoSidebar = document.getElementById('topo-sidebar');\n\nfunction selectTopoNode(d) {\n topoSelectedId = d.id;\n buildTopoList(document.getElementById('topo-search').value);\n showTopoNode(d);\n if (typeof d3 !== 'undefined') d3.selectAll('.node-hex').classed('selected', function(nd) { return nd.id === d.id; });\n}\n\nfunction showTopoNode(d) {\n var c = TYPE_COLORS[d.type] || '#aaa';\n var confPct = Math.round(d.confidence * 100);\n var tags = (d.tags || []).map(function(t) { return '<span class=\"tag\">' + t + '</span>'; }).join('');\n var metaRows = Object.entries(d.metadata || {})\n .filter(function(kv) { return kv[1] !== null && kv[1] !== undefined && String(kv[1]).length > 0; })\n .map(function(kv) { return '<tr><td>' + kv[0] + '</td><td>' + JSON.stringify(kv[1]) + '</td></tr>'; }).join('');\n var related = TOPO.links.filter(function(l) {\n return (l.source.id || l.source) === d.id || (l.target.id || l.target) === d.id;\n });\n var edgeItems = related.map(function(l) {\n var isOut = (l.source.id || l.source) === d.id;\n var other = isOut ? (l.target.id || l.target) : (l.source.id || l.source);\n return '<div class=\"edge-item\">' + (isOut ? '\\\\u2192' : '\\\\u2190') + ' <span>' + other + '</span> <small>[' + l.relationship + ']</small></div>';\n }).join('');\n\n topoSidebar.innerHTML =\n '<h2>' + d.name + '</h2>' +\n '<table class=\"meta-table\">' +\n '<tr><td>ID</td><td style=\"font-size:10px;word-break:break-all\">' + d.id + '</td></tr>' +\n '<tr><td>Type</td><td><span style=\"color:' + c + '\">' + d.type + '</span></td></tr>' +\n '<tr><td>Layer</td><td>' + d.layer + '</td></tr>' +\n '<tr><td>Confidence</td><td>' + confPct + '% <div class=\"conf-bar\"><div class=\"conf-fill\" style=\"width:' + confPct + '%;background:' + c + '\"></div></div></td></tr>' +\n '<tr><td>Via</td><td>' + (d.discoveredVia || '\\\\u2014') + '</td></tr>' +\n '<tr><td>Timestamp</td><td>' + (d.discoveredAt ? d.discoveredAt.substring(0, 19).replace('T', ' ') : '\\\\u2014') + '</td></tr>' +\n (tags ? '<tr><td>Tags</td><td>' + tags + '</td></tr>' : '') +\n metaRows + '</table>' +\n (related.length > 0 ? '<div class=\"edges-list\"><strong>Connections (' + related.length + '):</strong>' + edgeItems + '</div>' : '') +\n '<div style=\"margin-top:14px\"><button class=\"export-btn\" style=\"width:100%\" onclick=\"deleteTopoNode(\\\\'' + d.id.replace(/'/g, \"\\\\\\\\'\") + '\\\\')\">Delete node</button></div>';\n}\n\nfunction deleteTopoNode(id) {\n var idx = TOPO.nodes.findIndex(function(n) { return n.id === id; });\n if (idx === -1) return;\n TOPO.nodes.splice(idx, 1);\n TOPO.links = TOPO.links.filter(function(l) {\n return (l.source.id || l.source) !== id && (l.target.id || l.target) !== id;\n });\n topoSelectedId = null;\n topoSidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Node deleted.</p>';\n if (typeof rebuildTopoGraph === 'function') rebuildTopoGraph();\n buildTopoList(document.getElementById('topo-search').value);\n}\n\nfunction initTopology() {\n if (typeof d3 === 'undefined') return;\n\n var svgEl = d3.select('#topo-graph svg');\n var graphDiv = document.getElementById('topo-graph');\n var gW = function() { return graphDiv.clientWidth; };\n var gH = function() { return graphDiv.clientHeight; };\n var g = svgEl.append('g');\n\n svgEl.append('defs').append('marker')\n .attr('id', 'arrow').attr('viewBox', '0 0 10 6')\n .attr('refX', 10).attr('refY', 3)\n .attr('markerWidth', 8).attr('markerHeight', 6)\n .attr('orient', 'auto')\n .append('path').attr('d', 'M0,0 L10,3 L0,6 Z').attr('fill', '#555');\n\n var currentZoom = 1;\n var zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', function(e) {\n g.attr('transform', e.transform); currentZoom = e.transform.k; updateTopoLOD(currentZoom);\n });\n svgEl.call(zoomBehavior);\n\n // Layer filters\n var layers = Array.from(new Set(TOPO.nodes.map(function(d) { return d.layer; })));\n var layerVisible = {};\n layers.forEach(function(l) { layerVisible[l] = true; });\n\n var toolbarEl = document.getElementById('topo-toolbar');\n layers.forEach(function(layer) {\n var btn = document.createElement('button');\n btn.className = 'filter-btn';\n btn.innerHTML = '<span class=\"filter-dot\" style=\"background:' + (LAYER_COLORS[layer] || '#666') + '\"></span>' + (LAYER_NAMES[layer] || layer);\n btn.onclick = function() { layerVisible[layer] = !layerVisible[layer]; btn.classList.toggle('off', !layerVisible[layer]); updateTopoVisibility(); };\n toolbarEl.appendChild(btn);\n });\n\n // JGF export button\n var jgfBtn = document.createElement('button');\n jgfBtn.className = 'export-btn'; jgfBtn.textContent = '\\\\u2193 JGF'; jgfBtn.title = 'Export JSON Graph Format';\n jgfBtn.onclick = function() {\n var jgf = { graph: { directed: true, type: 'cartography', label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(TOPO.nodes.map(function(n) { return [n.id, { label: n.name, metadata: { type: n.type, layer: n.layer, confidence: n.confidence, discoveredVia: n.discoveredVia, discoveredAt: n.discoveredAt, tags: n.tags } }]; })),\n edges: TOPO.links.map(function(l) { return { source: l.source.id || l.source, target: l.target.id || l.target, relation: l.relationship, metadata: { confidence: l.confidence, evidence: l.evidence } }; })\n }};\n var blob = new Blob([JSON.stringify(jgf, null, 2)], { type: 'application/json' });\n var url = URL.createObjectURL(blob);\n var a = document.createElement('a'); a.href = url; a.download = 'cartography-graph.jgf.json'; a.click();\n URL.revokeObjectURL(url);\n };\n toolbarEl.appendChild(jgfBtn);\n\n // Hex helpers\n var T_HEX = { saas_tool: 16, host: 18, database_server: 18, k8s_cluster: 20, default: 14 };\n function tHexSize(d) { return T_HEX[d.type] || T_HEX.default; }\n function tHexPath(size) {\n var pts = [];\n for (var i = 0; i < 6; i++) {\n var angle = (Math.PI / 3) * i - Math.PI / 6;\n pts.push([size * Math.cos(angle), size * Math.sin(angle)]);\n }\n return 'M' + pts.map(function(p) { return p.join(','); }).join('L') + 'Z';\n }\n\n // Cluster force\n function clusterForce(alpha) {\n var centroids = {}, counts = {};\n TOPO.nodes.forEach(function(d) {\n if (!centroids[d.layer]) { centroids[d.layer] = { x: 0, y: 0 }; counts[d.layer] = 0; }\n centroids[d.layer].x += d.x || 0; centroids[d.layer].y += d.y || 0; counts[d.layer]++;\n });\n for (var l in centroids) { centroids[l].x /= counts[l]; centroids[l].y /= counts[l]; }\n var strength = alpha * 0.15;\n TOPO.nodes.forEach(function(d) {\n var cn = centroids[d.layer];\n if (cn) { d.vx += (cn.x - d.x) * strength; d.vy += (cn.y - d.y) * strength; }\n });\n }\n\n // Hulls\n var hullGroup = g.append('g').attr('class', 'hulls');\n var hullPaths = {}, hullLabels = {};\n layers.forEach(function(layer) {\n hullPaths[layer] = hullGroup.append('path').attr('class', 'hull')\n .attr('fill', LAYER_COLORS[layer] || '#666').attr('stroke', LAYER_COLORS[layer] || '#666');\n hullLabels[layer] = hullGroup.append('text').attr('class', 'hull-label')\n .attr('fill', LAYER_COLORS[layer] || '#666').text(LAYER_NAMES[layer] || layer);\n });\n\n function updateHulls() {\n layers.forEach(function(layer) {\n if (!layerVisible[layer]) { hullPaths[layer].attr('d', null); hullLabels[layer].attr('x', -9999); return; }\n var pts = TOPO.nodes.filter(function(d) { return d.layer === layer && layerVisible[d.layer]; }).map(function(d) { return [d.x, d.y]; });\n if (pts.length < 3) {\n hullPaths[layer].attr('d', null);\n if (pts.length > 0) hullLabels[layer].attr('x', pts[0][0]).attr('y', pts[0][1] - 30);\n else hullLabels[layer].attr('x', -9999);\n return;\n }\n var hull = d3.polygonHull(pts);\n if (!hull) { hullPaths[layer].attr('d', null); return; }\n var cx = d3.mean(hull, function(p) { return p[0]; });\n var cy = d3.mean(hull, function(p) { return p[1]; });\n var padded = hull.map(function(p) {\n var dx = p[0] - cx, dy = p[1] - cy;\n var len = Math.sqrt(dx * dx + dy * dy) || 1;\n return [p[0] + dx / len * 40, p[1] + dy / len * 40];\n });\n hullPaths[layer].attr('d', 'M' + padded.join('L') + 'Z');\n hullLabels[layer].attr('x', cx).attr('y', cy - d3.max(hull, function(p) { return Math.abs(p[1] - cy); }) - 30);\n });\n }\n\n // Graph\n var linkSel, linkLabelSel, nodeSel, nodeLabelSel, sim;\n var linkGroup = g.append('g');\n var nodeGroup = g.append('g');\n\n function focusTopoNode(d) {\n if (!d.x || !d.y) return;\n var w = gW(), h = gH();\n svgEl.transition().duration(500).call(\n zoomBehavior.transform,\n d3.zoomIdentity.translate(w / 2, h / 2).scale(Math.min(3, currentZoom < 1 ? 1.5 : currentZoom)).translate(-d.x, -d.y)\n );\n }\n window.focusTopoNode = focusTopoNode;\n\n function rebuildTopoGraph() {\n if (sim) sim.stop();\n\n linkSel = linkGroup.selectAll('line').data(TOPO.links, function(d) { return (d.source.id || d.source) + '>' + (d.target.id || d.target); });\n linkSel.exit().remove();\n var linkEnter = linkSel.enter().append('line').attr('class', 'link');\n linkSel = linkEnter.merge(linkSel)\n .attr('stroke', function(d) { return d.confidence < 0.6 ? '#2a2e35' : '#3d434b'; })\n .attr('stroke-dasharray', function(d) { return d.confidence < 0.6 ? '4 3' : null; })\n .attr('stroke-width', function(d) { return d.confidence < 0.6 ? 0.8 : 1.2; })\n .attr('marker-end', 'url(#arrow)');\n linkSel.select('title').remove();\n linkSel.append('title').text(function(d) { return d.relationship + ' (' + Math.round(d.confidence * 100) + '%)\\\\n' + (d.evidence || ''); });\n\n linkLabelSel = linkGroup.selectAll('text').data(TOPO.links, function(d) { return (d.source.id || d.source) + '>' + (d.target.id || d.target); });\n linkLabelSel.exit().remove();\n linkLabelSel = linkLabelSel.enter().append('text').attr('class', 'link-label').merge(linkLabelSel).text(function(d) { return d.relationship; });\n\n nodeSel = nodeGroup.selectAll('g').data(TOPO.nodes, function(d) { return d.id; });\n nodeSel.exit().remove();\n var nodeEnter = nodeSel.enter().append('g')\n .call(d3.drag()\n .on('start', function(e, d) { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })\n .on('drag', function(e, d) { d.fx = e.x; d.fy = e.y; })\n .on('end', function(e, d) { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })\n )\n .on('click', function(e, d) { e.stopPropagation(); selectTopoNode(d); });\n nodeEnter.append('path').attr('class', 'node-hex');\n nodeEnter.append('title');\n nodeEnter.append('text').attr('class', 'node-label').attr('text-anchor', 'middle');\n\n nodeSel = nodeEnter.merge(nodeSel);\n nodeSel.select('.node-hex')\n .attr('d', function(d) { return tHexPath(tHexSize(d)); })\n .attr('fill', function(d) { return TYPE_COLORS[d.type] || '#aaa'; })\n .attr('stroke', function(d) { var c = d3.color(TYPE_COLORS[d.type] || '#aaa'); return c ? c.brighter(0.8).formatHex() : '#ccc'; })\n .attr('fill-opacity', function(d) { return 0.6 + d.confidence * 0.4; })\n .classed('selected', function(d) { return d.id === topoSelectedId; });\n nodeSel.select('title').text(function(d) { return d.name + ' (' + d.type + ')\\\\nconf: ' + Math.round(d.confidence * 100) + '%'; });\n nodeLabelSel = nodeSel.select('.node-label')\n .attr('dy', function(d) { return tHexSize(d) + 13; })\n .text(function(d) { return d.name.length > 20 ? d.name.substring(0, 18) + '\\\\u2026' : d.name; });\n\n sim = d3.forceSimulation(TOPO.nodes)\n .force('link', d3.forceLink(TOPO.links).id(function(d) { return d.id; }).distance(function(d) { return d.relationship === 'contains' ? 50 : 100; }).strength(0.4))\n .force('charge', d3.forceManyBody().strength(-280))\n .force('center', d3.forceCenter(gW() / 2, gH() / 2))\n .force('collision', d3.forceCollide().radius(function(d) { return tHexSize(d) + 10; }))\n .force('cluster', clusterForce)\n .on('tick', function() {\n updateHulls();\n linkSel.attr('x1', function(d) { return d.source.x; }).attr('y1', function(d) { return d.source.y; })\n .attr('x2', function(d) { return d.target.x; }).attr('y2', function(d) { return d.target.y; });\n linkLabelSel.attr('x', function(d) { return (d.source.x + d.target.x) / 2; })\n .attr('y', function(d) { return (d.source.y + d.target.y) / 2 - 4; });\n nodeSel.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });\n });\n }\n window.rebuildTopoGraph = rebuildTopoGraph;\n\n function updateTopoLOD(k) {\n if (nodeLabelSel) nodeLabelSel.style('opacity', k > 0.5 ? Math.min(1, (k - 0.5) * 2) : 0);\n if (linkLabelSel) linkLabelSel.style('opacity', k > 1.2 ? Math.min(1, (k - 1.2) * 3) : 0);\n d3.selectAll('.hull-label').style('font-size', k < 0.4 ? '18px' : '13px');\n }\n\n function updateTopoVisibility() {\n if (!nodeSel) return;\n nodeSel.style('display', function(d) { return layerVisible[d.layer] ? null : 'none'; });\n linkSel.style('display', function(d) {\n var s = TOPO.nodes.find(function(n) { return n.id === (d.source.id || d.source); });\n var t = TOPO.nodes.find(function(n) { return n.id === (d.target.id || d.target); });\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n linkLabelSel.style('display', function(d) {\n var s = TOPO.nodes.find(function(n) { return n.id === (d.source.id || d.source); });\n var t = TOPO.nodes.find(function(n) { return n.id === (d.target.id || d.target); });\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n }\n\n rebuildTopoGraph();\n buildTopoList();\n updateTopoLOD(1);\n\n svgEl.on('click', function() {\n topoSelectedId = null;\n d3.selectAll('.node-hex').classed('selected', false);\n buildTopoList(document.getElementById('topo-search').value);\n topoSidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Click a node to view details.</p>';\n });\n}\n\n// Init topology node list (non-D3 part)\nbuildTopoList();\n<\\/script>\n</body>\n</html>`;\n}\n\n// ── exportAll ─────────────────────────────────────────────────────────────────\n\nexport function exportAll(\n db: CartographyDB,\n sessionId: string,\n outputDir: string,\n formats: string[] = ['mermaid', 'json', 'yaml', 'html', 'map', 'discovery', 'sops'],\n): void {\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(join(outputDir, 'sops'), { recursive: true });\n mkdirSync(join(outputDir, 'workflows'), { recursive: true });\n\n const nodes = db.getNodes(sessionId);\n const edges = db.getEdges(sessionId);\n\n if (formats.includes('mermaid')) {\n writeFileSync(join(outputDir, 'topology.mermaid'), generateTopologyMermaid(nodes, edges));\n writeFileSync(join(outputDir, 'dependencies.mermaid'), generateDependencyMermaid(nodes, edges));\n process.stderr.write('✓ topology.mermaid, dependencies.mermaid\\n');\n }\n\n if (formats.includes('json')) {\n writeFileSync(join(outputDir, 'catalog.json'), exportJSON(db, sessionId));\n process.stderr.write('✓ catalog.json\\n');\n }\n\n if (formats.includes('yaml')) {\n writeFileSync(join(outputDir, 'catalog-info.yaml'), exportBackstageYAML(nodes, edges));\n process.stderr.write('✓ catalog-info.yaml\\n');\n }\n\n if (formats.includes('html')) {\n writeFileSync(join(outputDir, 'topology.html'), exportHTML(nodes, edges));\n process.stderr.write('✓ topology.html\\n');\n }\n\n if (formats.includes('map')) {\n writeFileSync(join(outputDir, 'cartography-map.html'), exportCartographyMap(nodes, edges));\n process.stderr.write('✓ cartography-map.html\\n');\n }\n\n if (formats.includes('discovery')) {\n writeFileSync(join(outputDir, 'discovery.html'), exportDiscoveryApp(nodes, edges));\n process.stderr.write('✓ discovery.html\\n');\n }\n\n if (formats.includes('sops')) {\n const sops = db.getSOPs(sessionId);\n for (const sop of sops) {\n const filename = sop.title.toLowerCase().replace(/[^a-z0-9]+/g, '-') + '.md';\n writeFileSync(join(outputDir, 'sops', filename), exportSOPMarkdown(sop));\n\n const wfFilename = `workflow-${sop.workflowId.substring(0, 8)}.mermaid`;\n writeFileSync(join(outputDir, 'workflows', wfFilename), generateWorkflowMermaid(sop));\n }\n if (sops.length > 0) {\n process.stderr.write(`✓ ${sops.length} SOPs + workflow diagrams\\n`);\n }\n }\n}\n","/**\n * Hex Grid Engine — flat-top axial coordinate system.\n * Reference: https://www.redblobgames.com/grids/hexagons/\n */\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AxialCoord {\n q: number;\n r: number;\n}\n\nexport interface PixelCoord {\n x: number;\n y: number;\n}\n\n// ── Geometry ─────────────────────────────────────────────────────────────────\n\n/**\n * Convert axial hex coordinates to pixel coordinates (flat-top).\n */\nexport function hexToPixel(q: number, r: number, size: number): PixelCoord {\n const x = size * (3 / 2 * q);\n const y = size * (Math.sqrt(3) / 2 * q + Math.sqrt(3) * r);\n return { x, y };\n}\n\n/**\n * Convert pixel coordinates to nearest axial hex (flat-top).\n */\nexport function pixelToHex(x: number, y: number, size: number): AxialCoord {\n const q = (2 / 3 * x) / size;\n const r = (-1 / 3 * x + Math.sqrt(3) / 3 * y) / size;\n return hexRound(q, r);\n}\n\n/**\n * Round floating-point axial coordinates to the nearest hex.\n */\nexport function hexRound(q: number, r: number): AxialCoord {\n const s = -q - r;\n let rq = Math.round(q);\n let rr = Math.round(r);\n const rs = Math.round(s);\n const dq = Math.abs(rq - q);\n const dr = Math.abs(rr - r);\n const ds = Math.abs(rs - s);\n if (dq > dr && dq > ds) {\n rq = -rr - rs;\n } else if (dr > ds) {\n rr = -rq - rs;\n }\n // Normalize -0 to 0\n return { q: rq || 0, r: rr || 0 };\n}\n\n/**\n * Return the 6 pixel corners of a flat-top hexagon.\n */\nexport function hexCorners(cx: number, cy: number, size: number): PixelCoord[] {\n const corners: PixelCoord[] = [];\n for (let i = 0; i < 6; i++) {\n const angle = (Math.PI / 180) * (60 * i); // flat-top: start at 0°\n corners.push({\n x: cx + size * Math.cos(angle),\n y: cy + size * Math.sin(angle),\n });\n }\n return corners;\n}\n\n// ── Neighbors & Distance ─────────────────────────────────────────────────────\n\nconst HEX_DIRECTIONS: AxialCoord[] = [\n { q: 1, r: 0 }, { q: 1, r: -1 }, { q: 0, r: -1 },\n { q: -1, r: 0 }, { q: -1, r: 1 }, { q: 0, r: 1 },\n];\n\nexport function hexNeighbors(q: number, r: number): AxialCoord[] {\n return HEX_DIRECTIONS.map(d => ({ q: q + d.q, r: r + d.r }));\n}\n\nexport function hexDistance(a: AxialCoord, b: AxialCoord): number {\n return (Math.abs(a.q - b.q) + Math.abs(a.q + a.r - b.q - b.r) + Math.abs(a.r - b.r)) / 2;\n}\n\n/**\n * Generate all hex coordinates on a given ring around center.\n */\nexport function hexRing(center: AxialCoord, radius: number): AxialCoord[] {\n if (radius === 0) return [{ q: center.q, r: center.r }];\n const results: AxialCoord[] = [];\n let hex = {\n q: center.q + HEX_DIRECTIONS[4].q * radius,\n r: center.r + HEX_DIRECTIONS[4].r * radius,\n };\n for (let side = 0; side < 6; side++) {\n for (let step = 0; step < radius; step++) {\n results.push({ q: hex.q, r: hex.r });\n hex = {\n q: hex.q + HEX_DIRECTIONS[side].q,\n r: hex.r + HEX_DIRECTIONS[side].r,\n };\n }\n }\n return results;\n}\n\n/**\n * Generate a spiral sequence of hex positions (ring 0, ring 1, ring 2, …).\n * Returns exactly `count` positions.\n */\nexport function hexSpiral(center: AxialCoord, count: number): AxialCoord[] {\n const positions: AxialCoord[] = [];\n let ring = 0;\n while (positions.length < count) {\n const ringPositions = hexRing(center, ring);\n for (const pos of ringPositions) {\n if (positions.length >= count) break;\n positions.push(pos);\n }\n ring++;\n }\n return positions;\n}\n","/**\n * Domain-based clustering and hex grid positioning.\n * Groups data assets by domain, assigns organic hex positions via spiral fill,\n * computes cluster centroids and assigns colors from the domain palette.\n */\n\nimport { hexSpiral, hexToPixel, hexDistance, type AxialCoord } from './hex.js';\nimport type { DataAsset, Cluster } from './types.js';\nimport { DOMAIN_COLORS, DOMAIN_PALETTE } from './types.js';\n\n// ── Color Assignment ──────────────────────────────────────────────────────────\n\n/**\n * Assign a deterministic color from the palette to a domain name.\n * Uses the predefined DOMAIN_COLORS map first, then falls back to the palette.\n */\nexport function assignColor(domain: string, allDomains: string[]): string {\n if (DOMAIN_COLORS[domain]) return DOMAIN_COLORS[domain];\n const idx = allDomains.indexOf(domain);\n return DOMAIN_PALETTE[idx % DOMAIN_PALETTE.length];\n}\n\n/**\n * Assign colors to all domains in the dataset.\n */\nexport function assignColors(domains: string[]): Record<string, string> {\n const result: Record<string, string> = {};\n for (const d of domains) {\n result[d] = assignColor(d, domains);\n }\n return result;\n}\n\n/**\n * Generate a slightly lighter shade of a hex color string.\n */\nexport function shadeVariant(hex: string, amount: number): string {\n const num = parseInt(hex.replace('#', ''), 16);\n const r = Math.min(255, (num >> 16) + amount);\n const g = Math.min(255, ((num >> 8) & 0xff) + amount);\n const b = Math.min(255, (num & 0xff) + amount);\n return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;\n}\n\n// ── Grouping ──────────────────────────────────────────────────────────────────\n\n/**\n * Group assets by their `domain` field.\n */\nexport function groupByDomain(assets: DataAsset[]): Map<string, DataAsset[]> {\n const map = new Map<string, DataAsset[]>();\n for (const a of assets) {\n const d = a.domain || 'Other';\n if (!map.has(d)) map.set(d, []);\n map.get(d)!.push(a);\n }\n return map;\n}\n\n// ── Cluster Layout ────────────────────────────────────────────────────────────\n\nconst CLUSTER_GAP = 3; // min hex distance between cluster borders\n\n/**\n * Arrange domain clusters on the hex grid without overlap.\n * Places largest clusters first at the origin, subsequent clusters spiral outward.\n */\nexport function layoutClusters(\n groups: Map<string, DataAsset[]>,\n hexSize: number,\n): { clusters: Cluster[]; assets: DataAsset[] } {\n const allDomains = Array.from(groups.keys());\n const colors = assignColors(allDomains);\n\n // Sort domains by size descending (largest first → center)\n const sorted = Array.from(groups.entries())\n .sort((a, b) => b[1].length - a[1].length);\n\n const occupied = new Set<string>();\n const key = (q: number, r: number) => `${q},${r}`;\n\n const clusters: Cluster[] = [];\n const allAssets: DataAsset[] = [];\n\n for (const [domain, domainAssets] of sorted) {\n // Find a free origin for this cluster\n const origin = clusters.length === 0\n ? { q: 0, r: 0 }\n : findFreeOrigin(occupied, domainAssets.length, CLUSTER_GAP);\n\n // Pack assets in a spiral around the origin\n const positions = hexSpiral(origin, domainAssets.length);\n\n const assetIds: string[] = [];\n for (let i = 0; i < domainAssets.length; i++) {\n const asset = domainAssets[i];\n asset.position = positions[i];\n assetIds.push(asset.id);\n occupied.add(key(positions[i].q, positions[i].r));\n allAssets.push(asset);\n }\n\n const centroid = computeCentroid(positions, hexSize);\n\n clusters.push({\n id: `cluster:${domain}`,\n label: domain,\n domain,\n color: colors[domain],\n assetIds,\n centroid,\n });\n }\n\n return { clusters, assets: allAssets };\n}\n\n/**\n * Find a cluster origin that doesn't overlap any occupied hexes.\n */\nfunction findFreeOrigin(\n occupied: Set<string>,\n count: number,\n gap: number,\n): AxialCoord {\n const key = (q: number, r: number) => `${q},${r}`;\n\n // Search in expanding rings around the global origin\n for (let searchRadius = 1; searchRadius < 100; searchRadius++) {\n const candidates = hexSpiral({ q: 0, r: 0 }, 1 + 6 * searchRadius * (searchRadius + 1) / 2);\n\n for (const candidate of candidates) {\n const testPositions = hexSpiral(candidate, count);\n let fits = true;\n\n for (const tp of testPositions) {\n // Check that the cell itself and its gap neighbors are free\n if (occupied.has(key(tp.q, tp.r))) { fits = false; break; }\n\n for (const oKey of occupied) {\n const [oq, or] = oKey.split(',').map(Number);\n if (hexDistance(tp, { q: oq, r: or }) < gap) {\n fits = false;\n break;\n }\n }\n if (!fits) break;\n }\n\n if (fits) return candidate;\n }\n }\n\n // Fallback\n return { q: occupied.size * 5, r: 0 };\n}\n\n// ── Centroid ──────────────────────────────────────────────────────────────────\n\nexport function computeCentroid(positions: AxialCoord[], hexSize: number): { x: number; y: number } {\n if (positions.length === 0) return { x: 0, y: 0 };\n let sx = 0, sy = 0;\n for (const { q, r } of positions) {\n const { x, y } = hexToPixel(q, r, hexSize);\n sx += x;\n sy += y;\n }\n return { x: sx / positions.length, y: sy / positions.length };\n}\n\n// ── Cluster Bounds ────────────────────────────────────────────────────────────\n\nexport function computeClusterBounds(\n assets: DataAsset[],\n hexSize: number,\n): { minX: number; minY: number; maxX: number; maxY: number } {\n if (assets.length === 0) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const a of assets) {\n const { x, y } = hexToPixel(a.position.q, a.position.r, hexSize);\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n }\n return { minX, minY, maxX, maxY };\n}\n","/**\n * Node-zu-Asset Mapping.\n * Converts existing DiscoveryNode/Edge data into the CartographyMap data model.\n */\n\nimport type { NodeRow, EdgeRow, DataAsset, Connection, CartographyMapData } from './types.js';\nimport { layoutClusters, groupByDomain } from './cluster.js';\n\n// ── Domain Mapping ───────────────────────────────────────────────────────────\n\nconst TYPE_TO_DOMAIN: Record<string, string> = {\n database_server: 'Data Layer',\n database: 'Data Layer',\n table: 'Data Layer',\n cache_server: 'Data Layer',\n web_service: 'Web / API',\n api_endpoint: 'Web / API',\n message_broker: 'Messaging',\n queue: 'Messaging',\n topic: 'Messaging',\n host: 'Infrastructure',\n container: 'Infrastructure',\n pod: 'Infrastructure',\n k8s_cluster: 'Infrastructure',\n config_file: 'Infrastructure',\n saas_tool: 'SaaS Tools',\n};\n\n/**\n * Determine the domain for a node.\n * Priority: explicit node.domain > metadata.category > tag-based > type-based > \"Other\"\n */\nfunction resolveDomain(node: NodeRow): string {\n // 1. Explicit domain field\n if (node.domain) return node.domain;\n\n // 2. Metadata category\n const meta = node.metadata as Record<string, unknown>;\n if (typeof meta['category'] === 'string' && meta['category'].length > 0) {\n return meta['category'];\n }\n\n // 3. Tags — use first tag that looks like a domain\n for (const tag of node.tags ?? []) {\n if (tag.length > 2 && tag[0] === tag[0].toUpperCase()) {\n return tag;\n }\n }\n\n // 4. Type-based mapping\n return TYPE_TO_DOMAIN[node.type] ?? 'Other';\n}\n\n// ── Conversion ───────────────────────────────────────────────────────────────\n\n/**\n * Convert NodeRow[] to DataAsset[].\n */\nexport function nodesToAssets(nodes: NodeRow[]): DataAsset[] {\n return nodes.map(n => ({\n id: n.id,\n name: n.name,\n domain: resolveDomain(n),\n subDomain: n.subDomain,\n qualityScore: n.qualityScore ?? Math.round(n.confidence * 100),\n metadata: n.metadata ?? {},\n position: { q: 0, r: 0 }, // will be assigned by layoutClusters\n }));\n}\n\n/**\n * Convert EdgeRow[] to Connection[].\n */\nexport function edgesToConnections(edges: EdgeRow[]): Connection[] {\n return edges.map(e => ({\n id: e.id,\n sourceAssetId: e.sourceId,\n targetAssetId: e.targetId,\n type: e.relationship,\n }));\n}\n\n// ── Full Pipeline ─────────────────────────────────────────────────────────────\n\nconst HEX_SIZE = 24;\n\n/**\n * Build a complete CartographyMapData from raw nodes and edges.\n */\nexport function buildMapData(\n nodes: NodeRow[],\n edges: EdgeRow[],\n options?: { theme?: 'light' | 'dark' },\n): CartographyMapData {\n const rawAssets = nodesToAssets(nodes);\n const connections = edgesToConnections(edges);\n\n if (rawAssets.length === 0) {\n return {\n assets: [],\n clusters: [],\n connections,\n meta: { exportedAt: new Date().toISOString(), theme: options?.theme ?? 'light' },\n };\n }\n\n const groups = groupByDomain(rawAssets);\n const { clusters, assets } = layoutClusters(groups, HEX_SIZE);\n\n return {\n assets,\n clusters,\n connections,\n meta: { exportedAt: new Date().toISOString(), theme: options?.theme ?? 'light' },\n };\n}\n"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAqDrD,OAAO;AAAA,gBACHA,SAAQ;AAAA,kBACN,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aA0VZ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgUtB;AAIO,SAAS,UACd,IA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","names":["execSync","existsSync","mkdirSync","join","HEX_SIZE","mkdirSync","join","readFileSync","existsSync","mkdirSync","writeFileSync","execSync","resolve","scanAllBookmarks","saved","NODE_TYPES","join","dim"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/preflight.ts","../src/db.ts","../src/tools.ts","../src/safety.ts","../src/agent.ts","../src/exporter.ts","../src/hex.ts","../src/cluster.ts","../src/mapper.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { checkPrerequisites } from './preflight.js';\nimport { CartographyDB } from './db.js';\nimport { defaultConfig } from './types.js';\nimport { runDiscovery } from './agent.js';\nimport type { DiscoveryEvent } from './agent.js';\nimport { exportAll } from './exporter.js';\nimport { readFileSync, existsSync } from 'fs';\nimport { resolve } from 'path';\nimport { createInterface } from 'readline';\n\n\n// ── Shared color helpers ─────────────────────────────────────────────────────\nconst bold = (s: string) => `\\x1b[1m${s}\\x1b[0m`;\nconst dim = (s: string) => `\\x1b[2m${s}\\x1b[0m`;\nconst cyan = (s: string) => `\\x1b[36m${s}\\x1b[0m`;\nconst green = (s: string) => `\\x1b[32m${s}\\x1b[0m`;\nconst yellow = (s: string) => `\\x1b[33m${s}\\x1b[0m`;\nconst magenta = (s: string) => `\\x1b[35m${s}\\x1b[0m`;\nconst red = (s: string) => `\\x1b[31m${s}\\x1b[0m`;\n\nmain();\n\nfunction main(): void {\n const program = new Command();\n\n const CMD = 'datasynx-cartography';\n const VERSION = '0.2.3';\n\n program\n .name(CMD)\n .description('AI-powered Infrastructure Cartography & SOP Generation')\n .version(VERSION);\n\n // ── DISCOVERY ──────────────────────────────────────────────────────────────\n\n program\n .command('discover')\n .description('Scan and map your infrastructure')\n .option('--entry <hosts...>', 'Entry points', ['localhost'])\n .option('--depth <n>', 'Max crawl depth', '8')\n .option('--max-turns <n>', 'Max agent turns', '50')\n .option('--model <m>', 'Agent model', 'claude-sonnet-4-5-20250929')\n .option('--org <name>', 'Organization name (for Backstage)')\n .option('-o, --output <dir>', 'Output directory', './datasynx-output')\n .option('--db <path>', 'DB path')\n .option('-v, --verbose', 'Show agent reasoning', false)\n .action(async (opts) => {\n checkPrerequisites();\n\n const config = defaultConfig({\n entryPoints: opts.entry,\n maxDepth: parseInt(opts.depth, 10),\n maxTurns: parseInt(opts.maxTurns, 10),\n agentModel: opts.model,\n organization: opts.org,\n outputDir: opts.output,\n ...(opts.db ? { dbPath: opts.db } : {}),\n verbose: opts.verbose,\n });\n\n const db = new CartographyDB(config.dbPath);\n const sessionId = db.createSession('discover', config);\n\n const w = process.stderr.write.bind(process.stderr);\n\n const SPINNER = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];\n let spinIdx = 0;\n let spinnerTimer: ReturnType<typeof setInterval> | null = null;\n let spinnerMsg = '';\n\n const startSpinner = (msg: string) => {\n spinnerMsg = msg;\n if (spinnerTimer) clearInterval(spinnerTimer);\n spinnerTimer = setInterval(() => {\n const frame = cyan(SPINNER[spinIdx % SPINNER.length] ?? '⠋');\n w(`\\r ${frame} ${spinnerMsg}\\x1b[K`);\n spinIdx++;\n }, 80);\n };\n\n const stopSpinner = () => {\n if (spinnerTimer) { clearInterval(spinnerTimer); spinnerTimer = null; }\n w(`\\r\\x1b[K`);\n };\n\n const startTime = Date.now();\n let turnNum = 0;\n let nodeCount = 0;\n let edgeCount = 0;\n\n w('\\n');\n w(` ${bold('CARTOGRAPHY')} ${dim(config.entryPoints.join(', '))}\\n`);\n w(` ${dim('Model: ' + config.agentModel + ' | MaxTurns: ' + config.maxTurns)}\\n`);\n w(dim(' ────────────────────────────────────────────────\\n'));\n w('\\n');\n\n const logLine = (icon: string, msg: string) => {\n stopSpinner();\n const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);\n w(` ${icon} ${msg} ${dim(elapsed + 's')}\\n`);\n };\n\n const handleEvent = (event: DiscoveryEvent) => {\n switch (event.kind) {\n case 'turn':\n turnNum = event.turn;\n startSpinner(`Turn ${turnNum}/${config.maxTurns} ${dim(`nodes:${nodeCount} edges:${edgeCount}`)}`);\n break;\n\n case 'thinking':\n if (config.verbose) {\n stopSpinner();\n const lines = event.text.split('\\n').slice(0, 3);\n for (const line of lines) {\n w(` ${dim(' ' + line.substring(0, 80))}\\n`);\n }\n }\n break;\n\n case 'tool_call': {\n const toolName = event.tool.replace('mcp__cartograph__', '');\n\n if (toolName === 'Bash') {\n const cmd = (event.input['command'] as string ?? '').substring(0, 70);\n startSpinner(`${yellow('$')} ${cmd}`);\n } else if (toolName === 'save_node') {\n const id = event.input['id'] as string ?? '?';\n const type = event.input['type'] as string ?? '?';\n nodeCount++;\n logLine(green('+'), `${bold('Node')} ${cyan(id)} ${dim('(' + type + ')')}`);\n startSpinner(`Turn ${turnNum}/${config.maxTurns} ${dim(`nodes:${nodeCount} edges:${edgeCount}`)}`);\n } else if (toolName === 'save_edge') {\n const src = event.input['sourceId'] as string ?? '?';\n const tgt = event.input['targetId'] as string ?? '?';\n const rel = event.input['relationship'] as string ?? '→';\n edgeCount++;\n logLine(magenta('~'), `${bold('Edge')} ${cyan(src)} ${dim('─' + rel + '→')} ${cyan(tgt)}`);\n startSpinner(`Turn ${turnNum}/${config.maxTurns} ${dim(`nodes:${nodeCount} edges:${edgeCount}`)}`);\n } else if (toolName === 'get_catalog') {\n startSpinner(`Catalog check ${dim('(avoiding duplicates)')}`);\n } else if (toolName === 'scan_bookmarks') {\n logLine(cyan('🔖'), `Scanning browser bookmarks…`);\n startSpinner(`scan_bookmarks`);\n } else if (toolName === 'scan_browser_history') {\n logLine(cyan('📜'), `Scanning browser history (anonymized hostnames only)…`);\n startSpinner(`scan_browser_history`);\n } else if (toolName === 'scan_installed_apps') {\n const sh = event.input['searchHint'] as string | undefined;\n logLine(cyan('🖥'), sh ? `Searching installed apps: ${bold(sh)}` : `Scanning all installed apps…`);\n startSpinner(`scan_installed_apps`);\n } else if (toolName === 'scan_local_databases') {\n logLine(cyan('🗄'), `Scanning local databases (PostgreSQL, MySQL, SQLite…)`);\n startSpinner(`scan_local_databases`);\n } else if (toolName === 'ask_user') {\n // Just display; actual interaction is handled by onAskUser below\n const q = (event.input['question'] as string ?? '').substring(0, 100);\n logLine(yellow('?'), `${bold('Agent asks:')} ${q}`);\n } else {\n startSpinner(`${toolName}...`);\n }\n break;\n }\n\n case 'tool_result':\n // Spinner continues, no special output for results\n break;\n\n case 'done':\n stopSpinner();\n break;\n }\n };\n\n // Human-in-the-loop: agent can ask clarifying questions\n const onAskUser = async (question: string, context?: string): Promise<string> => {\n stopSpinner();\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${yellow(bold('?'))} ${bold('Agent asks:')} ${question}\\n`);\n if (context) w(` ${dim(context)}\\n`);\n\n if (!process.stdin.isTTY) {\n w(` ${dim('(No terminal — agent will continue without your answer)')}\\n\\n`);\n return '(Non-interactive mode — please continue without this information)';\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const answer = await new Promise<string>(resolve => rl.question(` ${cyan('→')} `, resolve));\n rl.close();\n w('\\n');\n return answer || '(No answer — please continue)';\n };\n\n try {\n await runDiscovery(config, db, sessionId, handleEvent, onAskUser, undefined);\n } catch (err) {\n stopSpinner();\n w(`\\n ${bold('\\x1b[31m✗\\x1b[0m')} Discovery failed: ${err}\\n`);\n db.close();\n process.exitCode = 1;\n return;\n }\n\n stopSpinner();\n db.endSession(sessionId);\n const stats = db.getStats(sessionId);\n const totalSec = ((Date.now() - startTime) / 1000).toFixed(1);\n\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${green(bold('DONE'))} ${bold(String(stats.nodes))} nodes, ${bold(String(stats.edges))} edges ${dim('in ' + totalSec + 's')}\\n`);\n w('\\n');\n\n // ── Interactive Node & Edge Review ──────────────────────────────────────\n const allNodes = db.getNodes(sessionId);\n const allEdges = db.getEdges(sessionId);\n\n if (allNodes.length > 0 && process.stdin.isTTY) {\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${bold('REVIEW')} ${bold(String(allNodes.length))} nodes discovered\\n`);\n w(dim(' Enter numbers to remove nodes (e.g. \"1 3 5\"), Enter = keep all\\n'));\n w('\\n');\n\n const PAD_ID = 42;\n const PAD_TYPE = 16;\n allNodes.forEach((n, i) => {\n const num = String(i + 1).padStart(3);\n const id = n.id.padEnd(PAD_ID).substring(0, PAD_ID);\n const type = dim(`[${n.type}]`.padEnd(PAD_TYPE));\n const conf = dim(`${Math.round(n.confidence * 100)}%`);\n const src = dim(n.discoveredVia === 'bookmark' ? ' 🔖' : n.discoveredVia === 'browser_history' ? ' 📜' : '');\n w(` ${dim(num)} ${cyan('●')} ${id} ${type} ${conf}${src}\\n`);\n });\n\n // Show edges summary\n if (allEdges.length > 0) {\n w('\\n');\n w(` ${bold(String(allEdges.length))} edges (relationships):\\n`);\n for (const e of allEdges.slice(0, 20)) {\n w(` ${dim(' ')}${cyan(e.sourceId)} ${dim('─' + e.relationship + '→')} ${cyan(e.targetId)} ${dim(Math.round(e.confidence * 100) + '%')}\\n`);\n }\n if (allEdges.length > 20) w(` ${dim(' … and ' + (allEdges.length - 20) + ' more edges')}\\n`);\n }\n\n w('\\n');\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const answer = await new Promise<string>(resolve =>\n rl.question(` ${yellow('?')} Remove nodes (numbers, empty = keep all): `, resolve)\n );\n rl.close();\n\n const toRemove = answer.trim().split(/[\\s,]+/).map(Number).filter(n => n >= 1 && n <= allNodes.length);\n if (toRemove.length > 0) {\n for (const idx of toRemove) {\n const node = allNodes[idx - 1];\n if (node) db.deleteNode(sessionId, node.id);\n }\n w(`\\n ${green('✓')} ${bold(String(toRemove.length))} node(s) removed\\n`);\n } else {\n w(`\\n ${green('✓')} All nodes kept\\n`);\n }\n }\n\n // ── Export ─────────────────────────────────────────────────────────────\n exportAll(db, sessionId, config.outputDir);\n\n // ── Diagram links ───────────────────────────────────────────────────────\n const osc8 = (url: string, label: string) => `\\x1b]8;;${url}\\x1b\\\\${label}\\x1b]8;;\\x1b\\\\`;\n const discoveryPath = resolve(config.outputDir, 'discovery.html');\n\n w('\\n');\n if (existsSync(discoveryPath)) {\n w(` ${green('→')} ${osc8(`file://${discoveryPath}`, bold('Open discovery.html'))} ${dim('← Map + Topology')}\\n`);\n }\n w('\\n');\n\n // ── Human-in-the-Loop: Follow-up Discovery Chat ───────────────────────\n if (process.stdin.isTTY) {\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${bold('SEARCH MORE')} ${dim('Refine discovery interactively')}\\n`);\n w(dim(' Enter search terms (e.g. \"hubspot windsurf cursor\") or press Enter to finish.\\n'));\n w('\\n');\n\n // Reset event counters for follow-up rounds\n nodeCount = 0;\n edgeCount = 0;\n\n let continueDiscovery = true;\n while (continueDiscovery) {\n const rlFollowup = createInterface({ input: process.stdin, output: process.stderr });\n const followupHint = await new Promise<string>(resolve =>\n rlFollowup.question(` ${yellow('→')} Search for (Enter = finish): `, resolve)\n );\n rlFollowup.close();\n\n if (!followupHint.trim()) {\n continueDiscovery = false;\n break;\n }\n\n const followupHintTrimmed = followupHint.trim();\n w('\\n');\n w(` ${cyan(bold('⟳'))} Searching for: ${bold(followupHintTrimmed)}\\n`);\n w('\\n');\n\n try {\n await runDiscovery(config, db, sessionId, handleEvent, onAskUser, followupHintTrimmed);\n } catch (err) {\n stopSpinner();\n w(`\\n ${red('✗')} Error: ${err}\\n`);\n }\n\n stopSpinner();\n const followupStats = db.getStats(sessionId);\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${green(bold('✓'))} Total now: ${bold(String(followupStats.nodes))} nodes, ${bold(String(followupStats.edges))} edges\\n`);\n w('\\n');\n\n // Re-export with updated data\n exportAll(db, sessionId, config.outputDir);\n if (existsSync(discoveryPath)) {\n w(` ${green('→')} ${osc8(`file://${discoveryPath}`, bold('discovery.html updated'))}\\n`);\n }\n w('\\n');\n }\n }\n\n db.close();\n });\n\n // ── ANALYSE & EXPORT ──────────────────────────────────────────────────────\n\n program\n .command('export [session-id]')\n .description('Generate all output files')\n .option('-o, --output <dir>', 'Output directory', './datasynx-output')\n .option('--format <fmt...>', 'Formats: mermaid,json,yaml,html,map,sops')\n .action((sessionId: string | undefined, opts) => {\n const config = defaultConfig({ outputDir: opts.output });\n const db = new CartographyDB(config.dbPath);\n\n const session = sessionId\n ? db.getSession(sessionId)\n : db.getLatestSession();\n\n if (!session) {\n process.stderr.write('❌ No session found\\n');\n db.close();\n process.exitCode = 1;\n return;\n }\n\n const formats = opts.format ?? ['mermaid', 'json', 'yaml', 'html', 'map', 'sops'];\n exportAll(db, session.id, opts.output, formats);\n process.stderr.write(`✓ Exported to: ${opts.output}\\n`);\n\n db.close();\n });\n\n program\n .command('show [session-id]')\n .description('Show session details')\n .action((sessionId?: string) => {\n const config = defaultConfig();\n const db = new CartographyDB(config.dbPath);\n\n const session = sessionId\n ? db.getSession(sessionId)\n : db.getLatestSession();\n\n if (!session) {\n process.stderr.write('❌ No session found\\n');\n db.close();\n process.exitCode = 1;\n return;\n }\n\n const stats = db.getStats(session.id);\n const nodes = db.getNodes(session.id);\n\n process.stdout.write(`\\nSession: ${session.id}\\n`);\n process.stdout.write(` Mode: ${session.mode}\\n`);\n process.stdout.write(` Started: ${session.startedAt}\\n`);\n if (session.completedAt) process.stdout.write(` Ended: ${session.completedAt}\\n`);\n process.stdout.write(` Nodes: ${stats.nodes}\\n`);\n process.stdout.write(` Edges: ${stats.edges}\\n`);\n process.stdout.write(` Events: ${stats.events}\\n`);\n process.stdout.write(` Tasks: ${stats.tasks}\\n`);\n\n if (nodes.length > 0) {\n process.stdout.write('\\n Discovered nodes:\\n');\n for (const node of nodes.slice(0, 20)) {\n process.stdout.write(` ${node.id} (${node.type}, confidence: ${node.confidence})\\n`);\n }\n if (nodes.length > 20) {\n process.stdout.write(` ... and ${nodes.length - 20} more\\n`);\n }\n }\n\n process.stdout.write('\\n');\n db.close();\n });\n\n program\n .command('sessions')\n .description('List all sessions')\n .action(() => {\n const config = defaultConfig();\n const db = new CartographyDB(config.dbPath);\n const sessions = db.getSessions();\n\n if (sessions.length === 0) {\n process.stdout.write('No sessions found\\n');\n db.close();\n return;\n }\n\n for (const session of sessions) {\n const stats = db.getStats(session.id);\n const status = session.completedAt ? '✓' : '●';\n process.stdout.write(\n `${status} ${session.id.substring(0, 8)} [${session.mode}] ` +\n `${session.startedAt.substring(0, 19)} ` +\n `nodes:${stats.nodes} edges:${stats.edges}\\n`\n );\n }\n\n db.close();\n });\n\n // ── OVERVIEW ──────────────────────────────────────────────────────────────\n\n program\n .command('overview')\n .description('Overview of all cartography sessions + SOPs')\n .option('--db <path>', 'DB-Pfad')\n .action((opts) => {\n const config = defaultConfig();\n const db = new CartographyDB((opts as { db?: string }).db ?? config.dbPath);\n const sessions = db.getSessions();\n\n const b = bold, d = dim;\n const w = (s: string) => process.stdout.write(s);\n\n w('\\n');\n w(` ${b('CARTOGRAPHY OVERVIEW')}\\n`);\n w(d(' ─────────────────────────────────────────────────────\\n'));\n\n if (sessions.length === 0) {\n w(` ${d('No sessions yet. Start with:')} ${green('datasynx-cartography discover')}\\n\\n`);\n db.close();\n return;\n }\n\n // Aggregate totals\n let totalNodes = 0, totalEdges = 0, totalSops = 0;\n for (const s of sessions) {\n const st = db.getStats(s.id);\n totalNodes += st.nodes; totalEdges += st.edges;\n totalSops += db.getSOPs(s.id).length;\n }\n\n w(` ${b(String(sessions.length))} Sessions · ${b(String(totalNodes))} Nodes · `);\n w(`${b(String(totalEdges))} Edges · ${b(String(totalSops))} SOPs\\n\\n`);\n\n for (const session of sessions) {\n const stats = db.getStats(session.id);\n const nodes = db.getNodes(session.id);\n const sops = db.getSOPs(session.id);\n const status = session.completedAt ? green('✓') : yellow('●');\n const age = session.startedAt.substring(0, 16).replace('T', ' ');\n const sid = cyan(session.id.substring(0, 8));\n\n w(` ${status} ${sid} ${b('[' + session.mode + ']')} ${d(age)}\\n`);\n w(` ${d('Nodes: ' + stats.nodes + ' Edges: ' + stats.edges + ' SOPs: ' + sops.length)}\\n`);\n\n // Node type breakdown\n const byType = new Map<string, number>();\n for (const n of nodes) byType.set(n.type, (byType.get(n.type) ?? 0) + 1);\n if (byType.size > 0) {\n const parts = [...byType.entries()].map(([t, c]) => `${t}:${c}`).join(' ');\n w(` ${d(parts)}\\n`);\n }\n\n // Top nodes\n const topNodes = nodes.slice(0, 5).map(n => n.id).join(', ');\n if (topNodes) w(` ${d('Nodes: ' + topNodes + (nodes.length > 5 ? ' …' : ''))}\\n`);\n\n // SOPs\n for (const sop of sops.slice(0, 3)) {\n w(` ${green('►')} ${sop.title} ${d('(' + sop.estimatedDuration + ')')}\\n`);\n }\n if (sops.length > 3) w(` ${d('… +' + (sops.length - 3) + ' more SOPs')}\\n`);\n\n w('\\n');\n }\n\n db.close();\n });\n\n // ── CHAT ──────────────────────────────────────────────────────────────────\n\n program\n .command('chat [session-id]')\n .description('Interactive chat about your mapped infrastructure')\n .option('--db <path>', 'DB-Pfad')\n .option('--model <m>', 'Model', 'claude-sonnet-4-5-20250929')\n .action(async (sessionIdArg: string | undefined, opts) => {\n const config = defaultConfig();\n const db = new CartographyDB((opts as { db?: string }).db ?? config.dbPath);\n const sessions = db.getSessions();\n\n const session = sessionIdArg\n ? sessions.find(s => s.id.startsWith(sessionIdArg))\n : sessions.filter(s => s.completedAt).at(-1) ?? sessions.at(-1);\n\n if (!session) {\n process.stderr.write('No session found. Run discover first.\\n');\n db.close();\n return;\n }\n\n const nodes = db.getNodes(session.id);\n const edges = db.getEdges(session.id);\n const sops = db.getSOPs(session.id);\n\n const w = (s: string) => process.stdout.write(s);\n\n w('\\n');\n w(dim(` ─────────────────────────────────────────────────────\\n`));\n w(` ${bold('CARTOGRAPHY CHAT')} ${dim('Session ' + session.id.substring(0, 8))}\\n`);\n w(` ${dim(String(nodes.length) + ' Nodes · ' + edges.length + ' Edges · ' + sops.length + ' SOPs')}\\n`);\n w(dim(` ─────────────────────────────────────────────────────\\n`));\n w(` ${dim('Ask anything about your infrastructure. exit = quit.\\n\\n')}`);\n\n const Anthropic = (await import('@anthropic-ai/sdk')).default;\n const client = new Anthropic();\n\n // Build a compact infra summary for context (avoid token overflow)\n const infraSummary = JSON.stringify({\n nodes: nodes.map(n => ({\n id: n.id, name: n.name, type: n.type,\n confidence: n.confidence,\n metadata: n.metadata,\n tags: n.tags,\n })),\n edges: edges.map(e => ({ from: e.sourceId, to: e.targetId, rel: e.relationship, conf: e.confidence })),\n sops: sops.map(s => ({ title: s.title, description: s.description, steps: s.steps.length, duration: s.estimatedDuration })),\n });\n\n const systemPrompt = `You are an infrastructure analyst for Cartography.\nYou have access to the fully mapped infrastructure of this session.\nAnswer questions precisely and helpfully. Use the data concretely.\nYou can explain SOPs, analyze dependencies, identify risks, suggest optimizations.\n\nINFRASTRUCTURE SNAPSHOT (${nodes.length} nodes, ${edges.length} edges, ${sops.length} SOPs):\n${infraSummary.substring(0, 12000)}`;\n\n // Multi-turn conversation history\n type MsgParam = { role: 'user' | 'assistant'; content: string };\n const history: MsgParam[] = [];\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n\n const ask = () => new Promise<string>(resolve => rl.question(` ${cyan('>')} `, resolve));\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n let userInput: string;\n try { userInput = await ask(); } catch { break; }\n\n if (!userInput.trim()) continue;\n if (['exit', 'quit', ':q'].includes(userInput.trim().toLowerCase())) break;\n\n history.push({ role: 'user', content: userInput });\n\n try {\n const resp = await client.messages.create({\n model: (opts as { model: string }).model,\n max_tokens: 1024,\n system: systemPrompt,\n messages: history,\n });\n\n const reply = resp.content.find(b => b.type === 'text')?.text ?? '';\n history.push({ role: 'assistant', content: reply });\n\n w('\\n');\n // Word-wrap at 80 cols with indent\n for (const line of reply.split('\\n')) {\n w(` ${line}\\n`);\n }\n w('\\n');\n } catch (err) {\n w(` ${red('✗')} Error: ${err}\\n\\n`);\n }\n }\n\n rl.close();\n db.close();\n w(`\\n ${dim('Chat ended.')}\\n\\n`);\n });\n\n // ── DOCS ──────────────────────────────────────────────────────────────────\n\n program\n .command('docs')\n .description('Full feature reference and all commands')\n .action(() => {\n const out = process.stdout.write.bind(process.stdout);\n const b = bold;\n const line = () => out(dim('─'.repeat(60)) + '\\n');\n\n out('\\n');\n out(b(' DATASYNX CARTOGRAPHY') + ' ' + dim('v' + VERSION) + '\\n');\n out(dim(' AI-powered Infrastructure Cartography & SOP Generation\\n'));\n out('\\n');\n line();\n\n // ── CARTOGRAPHY\n out(b(cyan(' CARTOGRAPHY\\n')));\n out('\\n');\n out(` ${green('datasynx-cartography discover')}\\n`);\n out(` Scans your local infrastructure (Claude Sonnet).\\n`);\n out(` Claude autonomously runs ss, ps, curl, docker inspect, kubectl get\\n`);\n out(` and stores everything in SQLite.\\n`);\n out('\\n');\n out(dim(' Options:\\n'));\n out(dim(' --entry <hosts...> Entry points (default: localhost)\\n'));\n out(dim(' --depth <n> Max depth (default: 8)\\n'));\n out(dim(' --max-turns <n> Max agent turns (default: 50)\\n'));\n out(dim(' --model <m> Model (default: claude-sonnet-4-5-...)\\n'));\n out(dim(' --org <name> Organization for Backstage YAML\\n'));\n out(dim(' -o, --output <dir> Output directory (default: ./datasynx-output)\\n'));\n out(dim(' -v, --verbose Show agent reasoning\\n'));\n out('\\n');\n out(dim(' Output:\\n'));\n out(dim(' datasynx-output/\\n'));\n out(dim(' catalog.json Machine-readable full dump\\n'));\n out(dim(' catalog-info.yaml Backstage service catalog\\n'));\n out(dim(' topology.mermaid Infrastructure topology (graph TB)\\n'));\n out(dim(' dependencies.mermaid Service dependencies (graph LR)\\n'));\n out(dim(' discovery.html Enterprise discovery frontend (Map + Topology)\\n'));\n out(dim(' sops/ Generated SOPs as Markdown\\n'));\n out(dim(' workflows/ Workflow flowcharts as Mermaid\\n'));\n out('\\n');\n line();\n\n // ── ANALYSIS & EXPORT\n out(b(cyan(' ANALYSIS & EXPORT\\n')));\n out('\\n');\n out(` ${green('datasynx-cartography export [session-id]')}\\n`);\n out(dim(' --format <fmt...> mermaid, json, yaml, html, sops (default: all)\\n'));\n out(dim(' -o, --output <dir> Output directory\\n'));\n out('\\n');\n out(` ${green('datasynx-cartography show [session-id]')} ${dim('Session details + node list')}\\n`);\n out(` ${green('datasynx-cartography sessions')} ${dim('List all sessions')}\\n`);\n out('\\n');\n line();\n\n // ── COST\n out(b(cyan(' COST ESTIMATES\\n')));\n out('\\n');\n out(yellow(' Mode Model Interval per Hour per 8h Day\\n'));\n out(dim(' ─────────────────────────────────────────────────────────────\\n'));\n out(` Discovery Sonnet one-shot $0.15–0.50 one-shot\\n`);\n out('\\n');\n line();\n\n // ── ARCHITECTURE\n out(b(cyan(' ARCHITECTURE\\n')));\n out('\\n');\n out(dim(' CLI (Commander)\\n'));\n out(dim(' └── Preflight: Claude CLI check + API key + interval validation\\n'));\n out(dim(' └── Agent Orchestrator (agent.ts)\\n'));\n out(dim(' └── runDiscovery() → Claude Sonnet + Bash + MCP Tools\\n'));\n out(dim(' └── Custom MCP Tools (tools.ts)\\n'));\n out(dim(' save_node, save_edge,\\n'));\n out(dim(' scan_bookmarks, scan_browser_history,\\n'));\n out(dim(' scan_installed_apps, scan_local_databases\\n'));\n out(dim(' └── CartographyDB (SQLite WAL)\\n'));\n out('\\n');\n line();\n\n // ── SETUP\n out(b(cyan(' SETUP\\n')));\n out('\\n');\n out(dim(' # 1. Claude CLI (runtime dependency)\\n'));\n out(' npm install -g @anthropic-ai/claude-code\\n');\n out(' claude login\\n');\n out('\\n');\n out(dim(' # 2. API Key (if not using claude login)\\n'));\n out(' export ANTHROPIC_API_KEY=sk-ant-...\\n');\n out('\\n');\n out(dim(' # 3. Go\\n'));\n out(' datasynx-cartography discover\\n');\n out('\\n');\n out(dim(' Data: ~/.cartography/cartography.db\\n'));\n out('\\n');\n });\n\n // ── Bookmarks ──────────────────────────────────────────────────────────────\n\n program\n .command('bookmarks')\n .description('View all browser bookmarks (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)')\n .action(async () => {\n const { scanAllBookmarks } = await import('./bookmarks.js');\n const out = (s: string) => process.stdout.write(s);\n\n process.stderr.write(' Scanning bookmarks...\\n\\n');\n const hosts = await scanAllBookmarks();\n\n if (hosts.length === 0) {\n out(' (No bookmarks found — Chrome, Edge, Brave, Vivaldi, Opera and Firefox are supported)\\n\\n');\n return;\n }\n\n // Group by source browser\n const bySource = new Map<string, typeof hosts>();\n for (const h of hosts) {\n if (!bySource.has(h.source)) bySource.set(h.source, []);\n bySource.get(h.source)!.push(h);\n }\n\n for (const [source, entries] of bySource) {\n out(bold(cyan(` ${source.toUpperCase()}`)) + dim(` (${entries.length} Hosts)\\n`));\n out(dim(' ─────────────────────────────────────────\\n'));\n for (const h of entries) {\n const isDefault = (h.protocol === 'https' && h.port === 443) || (h.protocol === 'http' && h.port === 80);\n const portStr = isDefault ? '' : `:${h.port}`;\n out(` ${cyan(h.protocol + '://')}${h.hostname}${dim(portStr)}\\n`);\n }\n out('\\n');\n }\n\n out(dim(` Total: ${hosts.length} unique hosts\\n\\n`));\n out(dim(' Tip: ') + 'datasynx-cartography discover' + dim(' — scans + classifies all bookmarks automatically\\n\\n'));\n });\n\n // ── Seed ───────────────────────────────────────────────────────────────────\n\n program\n .command('seed')\n .description('Manually add known infrastructure (tools, DBs, APIs, etc.)')\n .option('--file <path>', 'JSON file with node definitions')\n .option('--session <id>', 'Add to existing session (default: new session)')\n .option('--db <path>', 'DB path')\n .action(async (opts) => {\n const config = defaultConfig({ ...(opts.db ? { dbPath: opts.db } : {}) });\n const db = new CartographyDB(config.dbPath);\n const sessionId = opts.session ?? db.createSession('discover', config);\n\n const out = (s: string) => process.stdout.write(s);\n const w = (s: string) => process.stderr.write(s);\n\n // ── File mode ────────────────────────────────────────────────────────\n if (opts.file) {\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(resolve(opts.file), 'utf8'));\n } catch (e) {\n w(red(`\\n ✗ Could not read file: ${e}\\n\\n`));\n process.exitCode = 1;\n return;\n }\n\n if (!Array.isArray(raw)) {\n w(red('\\n ✗ JSON must be an array: [{ \"type\": \"...\", \"name\": \"...\", \"host\": \"...\" }]\\n\\n'));\n process.exitCode = 1;\n return;\n }\n\n let saved = 0;\n for (const entry of raw as Record<string, unknown>[]) {\n const type = entry['type'] as string;\n const name = entry['name'] as string;\n const host = entry['host'] as string | undefined;\n const port = entry['port'] as number | undefined;\n const tags = (entry['tags'] as string[] | undefined) ?? [];\n const metadata = (entry['metadata'] as Record<string, unknown> | undefined) ?? {};\n\n if (!type || !name) {\n w(yellow(` ⚠ Skipped (no type/name): ${JSON.stringify(entry)}\\n`));\n continue;\n }\n\n const id = host\n ? `${type}:${host}${port ? ':' + port : ''}`\n : `${type}:${name.toLowerCase().replace(/\\s+/g, '-')}`;\n\n db.upsertNode(sessionId, {\n id,\n type: type as typeof import('./types.js').NODE_TYPES[number],\n name,\n discoveredVia: 'manual',\n confidence: 1.0,\n metadata: { ...metadata, ...(host ? { host } : {}), ...(port ? { port } : {}) },\n tags,\n });\n out(` ${green('+')} ${cyan(id)} ${dim('(' + type + ')')}\\n`);\n saved++;\n }\n\n db.endSession(sessionId);\n w(`\\n ${green(bold('DONE'))} ${saved} nodes saved ${dim('Session: ' + sessionId)}\\n\\n`);\n return;\n }\n\n // ── Interactive mode ─────────────────────────────────────────────────\n const { NODE_TYPES } = await import('./types.js');\n\n if (!process.stdin.isTTY) {\n w(red('\\n ✗ Interactive mode requires a terminal (use --file for non-interactive)\\n\\n'));\n process.exitCode = 1;\n return;\n }\n\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(bold(' Add known infrastructure\\n'));\n w(dim(' Examples: databases, APIs, SaaS tools, cloud services\\n'));\n w(dim(' ────────────────────────────────────────────────\\n'));\n w('\\n');\n\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const ask = (q: string): Promise<string> => new Promise(res => rl.question(q, res));\n\n let saved = 0;\n\n const typeList = NODE_TYPES.map((t, i) => `${dim((i + 1).toString().padStart(2))} ${t}`).join('\\n ');\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n w('\\n');\n w(dim(' Node types:\\n'));\n w(` ${typeList}\\n\\n`);\n\n const typeInput = (await ask(` ${cyan('Type')} ${dim('[number or name, Enter=cancel]')}: `)).trim();\n if (!typeInput) break;\n\n let nodeType: string;\n const asNum = parseInt(typeInput, 10);\n if (!isNaN(asNum) && asNum >= 1 && asNum <= NODE_TYPES.length) {\n nodeType = NODE_TYPES[asNum - 1] as string;\n } else if (NODE_TYPES.includes(typeInput as typeof NODE_TYPES[number])) {\n nodeType = typeInput;\n } else {\n w(yellow(` ⚠ Unknown type: \"${typeInput}\"\\n`));\n continue;\n }\n\n const name = (await ask(` ${cyan('Name')} ${dim('[e.g. \"Prod PostgreSQL\"]')}: `)).trim();\n if (!name) { w(dim(' (Cancelled)\\n')); continue; }\n\n const hostRaw = (await ask(` ${cyan('Host / IP')} ${dim('[optional, Enter=skip]')}: `)).trim();\n const portRaw = (await ask(` ${cyan('Port')} ${dim('[optional]')}: `)).trim();\n const tagsRaw = (await ask(` ${cyan('Tags')} ${dim('[comma-separated, optional]')}: `)).trim();\n\n const host = hostRaw || undefined;\n const port = portRaw ? parseInt(portRaw, 10) : undefined;\n const tags = tagsRaw ? tagsRaw.split(',').map(t => t.trim()).filter(Boolean) : [];\n\n const id = host\n ? `${nodeType}:${host}${port ? ':' + port : ''}`\n : `${nodeType}:${name.toLowerCase().replace(/\\s+/g, '-')}`;\n\n db.upsertNode(sessionId, {\n id,\n type: nodeType as typeof NODE_TYPES[number],\n name,\n discoveredVia: 'manual',\n confidence: 1.0,\n metadata: { ...(host ? { host } : {}), ...(port ? { port } : {}) },\n tags,\n });\n out(` ${green('+')} ${cyan(id)}\\n`);\n saved++;\n\n const again = (await ask(` ${dim('Add another node? [Y/n]')}: `)).trim().toLowerCase();\n if (again === 'n' || again === 'no') break;\n }\n\n rl.close();\n db.endSession(sessionId);\n w('\\n');\n w(dim(' ────────────────────────────────────────────────\\n'));\n w(` ${green(bold('DONE'))} ${saved} node${saved !== 1 ? 's' : ''} saved\\n`);\n w(` ${dim('Session: ' + sessionId)}\\n`);\n w(` ${dim('Tip: datasynx-cartography show ' + sessionId)}\\n\\n`);\n });\n\n // ── Doctor ─────────────────────────────────────────────────────────────────\n\n program\n .command('doctor')\n .description('Check all requirements and cloud CLIs')\n .action(async () => {\n const { execSync } = await import('node:child_process');\n const { existsSync, readFileSync } = await import('node:fs');\n const { join } = await import('node:path');\n const out = (s: string) => process.stdout.write(s);\n const ok = (msg: string) => out(` \\x1b[32m✓\\x1b[0m ${msg}\\n`);\n const err = (msg: string) => out(` \\x1b[31m✗\\x1b[0m ${msg}\\n`);\n const warn = (msg: string) => out(` \\x1b[33m⚠\\x1b[0m ${msg}\\n`);\n const dim = (s: string) => `\\x1b[2m${s}\\x1b[0m`;\n let allGood = true;\n\n out('\\n \\x1b[1mDatasynx Cartography — Doctor\\x1b[0m\\n');\n out(dim(' ─────────────────────────────────\\n'));\n\n // 1. Node.js Version\n const nodeVer = process.versions.node;\n const [major] = nodeVer.split('.').map(Number);\n if ((major ?? 0) >= 18) {\n ok(`Node.js ${nodeVer}`);\n } else {\n err(`Node.js ${nodeVer} — benötigt >=18`);\n allGood = false;\n }\n\n // 2. Claude CLI\n try {\n const v = execSync('claude --version', { stdio: 'pipe' }).toString().trim();\n ok(`Claude CLI ${dim(v)}`);\n } catch {\n err('Claude CLI not found — npm i -g @anthropic-ai/claude-code');\n allGood = false;\n }\n\n // 3. Auth\n const hasApiKey = Boolean(process.env.ANTHROPIC_API_KEY);\n const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';\n let hasOAuth = false;\n try {\n const creds = JSON.parse(readFileSync(join(home, '.claude', '.credentials.json'), 'utf8')) as Record<string, unknown>;\n const oauth = creds['claudeAiOauth'] as Record<string, unknown> | undefined;\n hasOAuth = typeof oauth?.['accessToken'] === 'string' && oauth['accessToken'].length > 0;\n } catch { /* no creds file */ }\n\n if (hasApiKey) {\n ok('ANTHROPIC_API_KEY set');\n } else if (hasOAuth) {\n ok('claude login (subscription)');\n } else {\n err('No authentication — run: claude login or export ANTHROPIC_API_KEY=sk-ant-...');\n allGood = false;\n }\n\n // 4. kubectl — important for K8s discovery\n try {\n const v = execSync('kubectl version --client --short 2>/dev/null || kubectl version --client', { stdio: 'pipe' }).toString().split('\\n')[0]?.trim() ?? '';\n ok(`kubectl ${dim(v || '(client OK)')}`);\n } catch {\n warn(`kubectl not found ${dim('— install: https://kubernetes.io/docs/tasks/tools/')}`);\n }\n\n // 5. Cloud CLIs (optional)\n const cloudClis: Array<[string, string, string]> = [\n ['aws', 'aws --version', 'AWS CLI — https://aws.amazon.com/cli/'],\n ['gcloud', 'gcloud --version', 'Google Cloud SDK — https://cloud.google.com/sdk/'],\n ['az', 'az --version', 'Azure CLI — https://aka.ms/installazurecliwindows'],\n ];\n for (const [name, cmd, hint] of cloudClis) {\n try {\n execSync(cmd, { stdio: 'pipe' });\n ok(`${name} ${dim('(cloud scanning available)')}`);\n } catch {\n warn(`${name} not found ${dim('— cloud scan skipped | ' + hint)}`);\n }\n }\n\n // 6. Local discovery tools\n const localTools: Array<[string, string]> = [\n ['docker', 'docker --version'],\n ['ss', 'ss --version'],\n ];\n for (const [name, cmd] of localTools) {\n try {\n execSync(cmd, { stdio: 'pipe' });\n ok(`${name} ${dim('(discovery tool)')}`);\n } catch {\n warn(`${name} not found ${dim('— discovery without ' + name + ' will be limited')}`);\n }\n }\n\n // 7. SQLite data dir\n const dbDir = join(home, '.cartography');\n if (existsSync(dbDir)) {\n ok(`~/.cartography ${dim('(data directory exists)')}`);\n } else {\n warn('~/.cartography does not exist yet ' + dim('— will be created on first run'));\n }\n\n out(dim(' ─────────────────────────────────\\n'));\n if (allGood) {\n out(' \\x1b[32m\\x1b[1mAll checks passed — datasynx-cartography discover\\x1b[0m\\n\\n');\n } else {\n out(' \\x1b[31m\\x1b[1mSome checks failed. Please fix the issues above.\\x1b[0m\\n\\n');\n process.exitCode = 1;\n }\n });\n\n // ── Banner (always show) ──────────────────────────────────────────────────\n\n const o = (s: string) => process.stderr.write(s);\n const _b = (s: string) => `\\x1b[1m${s}\\x1b[0m`;\n const _d = (s: string) => `\\x1b[2m${s}\\x1b[0m`;\n const _c = (s: string) => `\\x1b[36m${s}\\x1b[0m`;\n const _g = (s: string) => `\\x1b[32m${s}\\x1b[0m`;\n const _m = (s: string) => `\\x1b[35m${s}\\x1b[0m`;\n\n o('\\n');\n o(_c(' ____ _ ____ ') + '\\n');\n o(_c(' | _ \\\\ __ _| |_ __ _/ ___| _ _ _ __ __ __') + '\\n');\n o(_c(' | | | |/ _` | __/ _` \\\\___ \\\\| | | | \\'_ \\\\\\\\ \\\\/ /') + '\\n');\n o(_c(' | |_| | (_| | || (_| |___) | |_| | | | |> < ') + '\\n');\n o(_c(' |____/ \\\\__,_|\\\\__\\\\__,_|____/ \\\\__, |_| |_/_/\\\\_\\\\') + '\\n');\n o(_c(' |___/ ') + '\\n');\n o('\\n');\n o(_b(' Cartography') + ' ' + _d('v' + VERSION) + '\\n');\n o(_d(' AI-powered Infrastructure Discovery & SOP Generation\\n'));\n o(_d(' Built on Claude Agent SDK\\n'));\n o('\\n');\n\n // ── Welcome Screen (no args → Befehlsübersicht) ─────────────────────────\n\n if (process.argv.length <= 2) {\n o(_d(' ────────────────────────────────────────────────\\n'));\n o('\\n');\n o(_b(' Commands:\\n'));\n o('\\n');\n o(` ${_g('discover')} ${_d('Scan infrastructure (Claude Sonnet)')}\\n`);\n o(` ${_g('seed')} ${_d('Manually add known tools/DBs/APIs')}\\n`);\n o(` ${_g('bookmarks')} ${_d('View browser bookmarks')}\\n`);\n o(` ${_g('export')} ${_d('[session]')} ${_d('Export Mermaid, JSON, YAML, HTML')}\\n`);\n o(` ${_g('show')} ${_d('[session]')} ${_d('Show session details')}\\n`);\n o(` ${_g('sessions')} ${_d('List all sessions')}\\n`);\n o(` ${_g('doctor')} ${_d('Check requirements (kubectl, aws, gcloud, az)')}\\n`);\n o(` ${_g('docs')} ${_d('Full feature reference')}\\n`);\n o('\\n');\n o(_d(' ────────────────────────────────────────────────\\n'));\n o('\\n');\n o(_b(' Quick Start:\\n'));\n o('\\n');\n o(` ${_m('$')} ${_b('datasynx-cartography doctor')} ${_d('Check requirements')}\\n`);\n o(` ${_m('$')} ${_b('datasynx-cartography seed')} ${_d('Add known infrastructure')}\\n`);\n o(` ${_m('$')} ${_b('datasynx-cartography discover')} ${_d('One-time scan')}\\n`);\n o('\\n');\n o(_d(' Docs: datasynx-cartography docs\\n'));\n o(_d(' Help: datasynx-cartography --help\\n'));\n o(_d(' npm: @datasynx/agentic-ai-cartography\\n'));\n o('\\n');\n return;\n }\n\n o(_d(' ────────────────────────────────────────────────\\n'));\n o('\\n');\n\n // ── Parse ──────────────────────────────────────────────────────────────────\n\n program.exitOverride((err) => {\n if (err.code === 'commander.helpDisplayed') {\n process.exitCode = 0;\n } else {\n process.exitCode = 2;\n }\n });\n\n program.parse(process.argv);\n}\n","import { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nfunction isOAuthLoggedIn(): boolean {\n // Claude CLI speichert OAuth-Tokens in ~/.claude/.credentials.json\n const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';\n const credFile = join(home, '.claude', '.credentials.json');\n if (!existsSync(credFile)) return false;\n try {\n const creds = JSON.parse(readFileSync(credFile, 'utf8')) as Record<string, unknown>;\n const oauth = creds['claudeAiOauth'] as Record<string, unknown> | undefined;\n return typeof oauth?.['accessToken'] === 'string' && oauth['accessToken'].length > 0;\n } catch {\n return false;\n }\n}\n\nexport function checkPrerequisites(): void {\n // Claude CLI vorhanden?\n try {\n execSync('claude --version', { stdio: 'pipe' });\n } catch {\n process.stderr.write(\n '\\n❌ Claude CLI nicht gefunden.\\n' +\n ' Datasynx Cartography braucht die Claude CLI als Runtime-Dependency.\\n\\n' +\n ' Installieren:\\n' +\n ' npm install -g @anthropic-ai/claude-code\\n' +\n ' # oder\\n' +\n ' curl -fsSL https://claude.ai/install.sh | bash\\n\\n' +\n ' Danach: claude login\\n\\n'\n );\n process.exitCode = 1;\n throw new Error('Claude CLI not found');\n }\n\n // Auth prüfen: API Key ODER OAuth-Login (claude.ai Subscription)\n const hasApiKey = Boolean(process.env.ANTHROPIC_API_KEY);\n const hasOAuth = isOAuthLoggedIn();\n\n if (!hasApiKey && !hasOAuth) {\n process.stderr.write(\n '⚠ Keine Authentifizierung gefunden. Bitte eine der folgenden Optionen:\\n\\n' +\n ' Option A — claude.ai Subscription (empfohlen):\\n' +\n ' claude login\\n\\n' +\n ' Option B — API Key:\\n' +\n ' export ANTHROPIC_API_KEY=sk-ant-...\\n\\n'\n );\n } else if (hasOAuth && !hasApiKey) {\n process.stderr.write('✓ Eingeloggt via claude login (Subscription)\\n');\n }\n}\n","import Database from 'better-sqlite3';\nimport { mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport type {\n CartographyConfig, DiscoveryNode, DiscoveryEdge,\n NodeRow, EdgeRow, SessionRow, SOP, Connection,\n} from './types.js';\n\nexport interface ConnectionRow extends Connection {\n sessionId: string;\n createdAt: string;\n}\n\n// ── DB Row Types ──\n\nexport interface EventRow {\n id: string;\n sessionId: string;\n taskId?: string;\n timestamp: string;\n eventType: string;\n process: string;\n pid: number;\n target?: string;\n targetType?: string;\n port?: number;\n durationMs?: number;\n}\n\nexport interface TaskRow {\n id: string;\n sessionId: string;\n description?: string;\n startedAt: string;\n completedAt?: string;\n steps: string;\n involvedServices: string;\n status: 'active' | 'completed' | 'cancelled';\n isSOPCandidate: boolean;\n}\n\nexport interface WorkflowRow {\n id: string;\n sessionId: string;\n name?: string;\n pattern: string;\n taskIds: string;\n occurrences: number;\n firstSeen: string;\n lastSeen: string;\n avgDurationMs: number;\n involvedServices: string;\n}\n\nconst SCHEMA = `\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\nPRAGMA busy_timeout = 5000;\n\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n mode TEXT NOT NULL CHECK (mode IN ('discover')),\n started_at TEXT NOT NULL,\n completed_at TEXT,\n config TEXT NOT NULL DEFAULT '{}'\n);\n\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT NOT NULL,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n discovered_via TEXT,\n discovered_at TEXT NOT NULL,\n path_id TEXT,\n depth INTEGER DEFAULT 0,\n confidence REAL DEFAULT 0.5,\n metadata TEXT NOT NULL DEFAULT '{}',\n tags TEXT NOT NULL DEFAULT '[]',\n domain TEXT,\n sub_domain TEXT,\n quality_score REAL,\n PRIMARY KEY (id, session_id)\n);\n\nCREATE TABLE IF NOT EXISTS connections (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_asset_id TEXT NOT NULL,\n target_asset_id TEXT NOT NULL,\n type TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_id TEXT NOT NULL,\n target_id TEXT NOT NULL,\n relationship TEXT NOT NULL,\n evidence TEXT,\n confidence REAL DEFAULT 0.5,\n discovered_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS activity_events (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n task_id TEXT,\n timestamp TEXT NOT NULL,\n event_type TEXT NOT NULL,\n process TEXT NOT NULL,\n pid INTEGER NOT NULL,\n target TEXT,\n target_type TEXT,\n port INTEGER,\n duration_ms INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS tasks (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n description TEXT,\n started_at TEXT NOT NULL,\n completed_at TEXT,\n steps TEXT NOT NULL DEFAULT '[]',\n involved_services TEXT NOT NULL DEFAULT '[]',\n status TEXT DEFAULT 'active' CHECK (status IN ('active','completed','cancelled')),\n is_sop_candidate INTEGER DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS workflows (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n name TEXT,\n pattern TEXT NOT NULL,\n task_ids TEXT NOT NULL DEFAULT '[]',\n occurrences INTEGER DEFAULT 1,\n first_seen TEXT NOT NULL,\n last_seen TEXT NOT NULL,\n avg_duration_ms INTEGER,\n involved_services TEXT NOT NULL DEFAULT '[]'\n);\n\nCREATE TABLE IF NOT EXISTS sops (\n id TEXT PRIMARY KEY,\n workflow_id TEXT NOT NULL,\n title TEXT NOT NULL,\n description TEXT NOT NULL,\n steps TEXT NOT NULL,\n involved_systems TEXT NOT NULL DEFAULT '[]',\n estimated_duration TEXT,\n frequency TEXT,\n generated_at TEXT NOT NULL,\n confidence REAL DEFAULT 0.5\n);\n\nCREATE TABLE IF NOT EXISTS node_approvals (\n pattern TEXT PRIMARY KEY,\n action TEXT NOT NULL CHECK (action IN ('save','ignore','auto')),\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_nodes_session ON nodes(session_id);\nCREATE INDEX IF NOT EXISTS idx_edges_session ON edges(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_session ON activity_events(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_task ON activity_events(task_id);\nCREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);\nCREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);\n`;\n\nexport class CartographyDB {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n mkdirSync(dirname(dbPath), { recursive: true });\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('busy_timeout = 5000');\n this.migrate();\n }\n\n private migrate(): void {\n const version = (this.db.pragma('user_version', { simple: true }) as number);\n if (version === 0) {\n this.db.exec(SCHEMA);\n this.db.pragma('user_version = 2');\n } else if (version === 1) {\n // v1 → v2: add hex map columns to nodes + connections table\n const cols = (this.db.prepare(\"PRAGMA table_info(nodes)\").all() as Array<{ name: string }>).map(c => c.name);\n if (!cols.includes('domain')) this.db.exec('ALTER TABLE nodes ADD COLUMN domain TEXT');\n if (!cols.includes('sub_domain')) this.db.exec('ALTER TABLE nodes ADD COLUMN sub_domain TEXT');\n if (!cols.includes('quality_score')) this.db.exec('ALTER TABLE nodes ADD COLUMN quality_score REAL');\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS connections (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_asset_id TEXT NOT NULL,\n target_asset_id TEXT NOT NULL,\n type TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);\n `);\n this.db.pragma('user_version = 2');\n }\n }\n\n close(): void {\n this.db.pragma('optimize');\n this.db.close();\n }\n\n // ── Sessions ────────────────────────────\n\n createSession(mode: 'discover', config: CartographyConfig): string {\n const id = crypto.randomUUID();\n this.db.prepare(\n 'INSERT INTO sessions (id, mode, started_at, config) VALUES (?, ?, ?, ?)'\n ).run(id, mode, new Date().toISOString(), JSON.stringify(config));\n return id;\n }\n\n endSession(id: string): void {\n this.db.prepare('UPDATE sessions SET completed_at = ? WHERE id = ?')\n .run(new Date().toISOString(), id);\n }\n\n getSession(id: string): SessionRow | undefined {\n const row = this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n getLatestSession(mode?: string): SessionRow | undefined {\n const row = mode\n ? this.db.prepare('SELECT * FROM sessions WHERE mode = ? ORDER BY rowid DESC LIMIT 1').get(mode) as Record<string, unknown> | undefined\n : this.db.prepare('SELECT * FROM sessions ORDER BY rowid DESC LIMIT 1').get() as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n getSessions(): SessionRow[] {\n const rows = this.db.prepare('SELECT * FROM sessions ORDER BY rowid DESC').all() as Record<string, unknown>[];\n return rows.map(r => this.mapSession(r));\n }\n\n private mapSession(r: Record<string, unknown>): SessionRow {\n return {\n id: r['id'] as string,\n mode: r['mode'] as 'discover',\n startedAt: r['started_at'] as string,\n completedAt: (r['completed_at'] as string | null) ?? undefined,\n config: r['config'] as string,\n };\n }\n\n // ── Nodes ───────────────────────────────\n\n upsertNode(sessionId: string, node: DiscoveryNode, depth = 0): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO nodes\n (id, session_id, type, name, discovered_via, discovered_at, depth, confidence, metadata, tags,\n domain, sub_domain, quality_score)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n node.id, sessionId, node.type, node.name, node.discoveredVia,\n new Date().toISOString(), depth, node.confidence,\n JSON.stringify(node.metadata ?? {}),\n JSON.stringify(node.tags ?? []),\n node.domain ?? null,\n node.subDomain ?? null,\n node.qualityScore ?? null,\n );\n }\n\n getNodes(sessionId: string): NodeRow[] {\n const rows = this.db.prepare('SELECT * FROM nodes WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => this.mapNode(r));\n }\n\n private mapNode(r: Record<string, unknown>): NodeRow {\n return {\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n type: r['type'] as NodeRow['type'],\n name: r['name'] as string,\n discoveredVia: r['discovered_via'] as string,\n discoveredAt: r['discovered_at'] as string,\n depth: r['depth'] as number,\n confidence: r['confidence'] as number,\n metadata: JSON.parse(r['metadata'] as string) as Record<string, unknown>,\n tags: JSON.parse(r['tags'] as string) as string[],\n pathId: r['path_id'] as string | undefined,\n domain: (r['domain'] as string | null) ?? undefined,\n subDomain: (r['sub_domain'] as string | null) ?? undefined,\n qualityScore: (r['quality_score'] as number | null) ?? undefined,\n };\n }\n\n deleteNode(sessionId: string, nodeId: string): void {\n this.db.prepare('DELETE FROM nodes WHERE session_id = ? AND id = ?').run(sessionId, nodeId);\n // Remove orphaned edges\n this.db.prepare(\n 'DELETE FROM edges WHERE session_id = ? AND (source_id = ? OR target_id = ?)'\n ).run(sessionId, nodeId, nodeId);\n }\n\n // ── Edges ───────────────────────────────\n\n insertEdge(sessionId: string, edge: DiscoveryEdge): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT OR IGNORE INTO edges\n (id, session_id, source_id, target_id, relationship, evidence, confidence, discovered_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, edge.sourceId, edge.targetId,\n edge.relationship, edge.evidence, edge.confidence,\n new Date().toISOString(),\n );\n }\n\n getEdges(sessionId: string): EdgeRow[] {\n const rows = this.db.prepare('SELECT * FROM edges WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n sourceId: r['source_id'] as string,\n targetId: r['target_id'] as string,\n relationship: r['relationship'] as EdgeRow['relationship'],\n evidence: r['evidence'] as string,\n confidence: r['confidence'] as number,\n discoveredAt: r['discovered_at'] as string,\n }));\n }\n\n // ── Events ──────────────────────────────\n\n insertEvent(sessionId: string, event: Pick<EventRow, 'eventType' | 'process' | 'pid' | 'target' | 'targetType' | 'port'>, taskId?: string): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO activity_events\n (id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, taskId ?? null, new Date().toISOString(),\n event.eventType, event.process, event.pid,\n event.target ?? null, event.targetType ?? null, event.port ?? null,\n );\n }\n\n getEvents(sessionId: string, since?: string): EventRow[] {\n const rows = since\n ? this.db.prepare('SELECT * FROM activity_events WHERE session_id = ? AND timestamp > ? ORDER BY timestamp').all(sessionId, since) as Record<string, unknown>[]\n : this.db.prepare('SELECT * FROM activity_events WHERE session_id = ? ORDER BY timestamp').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n taskId: r['task_id'] as string | undefined,\n timestamp: r['timestamp'] as string,\n eventType: r['event_type'] as EventRow['eventType'],\n process: r['process'] as string,\n pid: r['pid'] as number,\n target: r['target'] as string | undefined,\n targetType: r['target_type'] as EventRow['targetType'],\n port: r['port'] as number | undefined,\n durationMs: r['duration_ms'] as number | undefined,\n }));\n }\n\n // ── Tasks ───────────────────────────────\n\n startTask(sessionId: string, description?: string): string {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO tasks (id, session_id, description, started_at, steps, involved_services, status)\n VALUES (?, ?, ?, ?, '[]', '[]', 'active')\n `).run(id, sessionId, description ?? null, new Date().toISOString());\n return id;\n }\n\n endCurrentTask(sessionId: string): void {\n this.db.prepare(`\n UPDATE tasks SET status = 'completed', completed_at = ?\n WHERE session_id = ? AND status = 'active'\n `).run(new Date().toISOString(), sessionId);\n }\n\n updateTaskDescription(sessionId: string, description: string): void {\n this.db.prepare(`\n UPDATE tasks SET description = ?\n WHERE session_id = ? AND status = 'active'\n `).run(description, sessionId);\n }\n\n getActiveTask(sessionId: string): TaskRow | undefined {\n const row = this.db.prepare(\n \"SELECT * FROM tasks WHERE session_id = ? AND status = 'active' LIMIT 1\"\n ).get(sessionId) as Record<string, unknown> | undefined;\n return row ? this.mapTask(row) : undefined;\n }\n\n getTasks(sessionId: string): TaskRow[] {\n const rows = this.db.prepare('SELECT * FROM tasks WHERE session_id = ? ORDER BY started_at').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => this.mapTask(r));\n }\n\n private mapTask(r: Record<string, unknown>): TaskRow {\n return {\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n description: r['description'] as string | undefined,\n startedAt: r['started_at'] as string,\n completedAt: r['completed_at'] as string | undefined,\n steps: r['steps'] as string,\n involvedServices: r['involved_services'] as string,\n status: r['status'] as TaskRow['status'],\n isSOPCandidate: Boolean(r['is_sop_candidate']),\n };\n }\n\n // ── Workflows ───────────────────────────\n\n insertWorkflow(sessionId: string, data: Omit<WorkflowRow, 'id'>): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO workflows\n (id, session_id, name, pattern, task_ids, occurrences,\n first_seen, last_seen, avg_duration_ms, involved_services)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, data.name ?? null, data.pattern,\n data.taskIds, data.occurrences,\n data.firstSeen, data.lastSeen, data.avgDurationMs,\n data.involvedServices,\n );\n }\n\n getWorkflows(sessionId: string): WorkflowRow[] {\n const rows = this.db.prepare('SELECT * FROM workflows WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n name: r['name'] as string | undefined,\n pattern: r['pattern'] as string,\n taskIds: r['task_ids'] as string,\n occurrences: r['occurrences'] as number,\n firstSeen: r['first_seen'] as string,\n lastSeen: r['last_seen'] as string,\n avgDurationMs: r['avg_duration_ms'] as number,\n involvedServices: r['involved_services'] as string,\n }));\n }\n\n // ── SOPs ────────────────────────────────\n\n insertSOP(sop: { workflowId: string } & SOP): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO sops\n (id, workflow_id, title, description, steps, involved_systems,\n estimated_duration, frequency, generated_at, confidence)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sop.workflowId, sop.title, sop.description,\n JSON.stringify(sop.steps),\n JSON.stringify(sop.involvedSystems),\n sop.estimatedDuration, sop.frequency,\n new Date().toISOString(), sop.confidence,\n );\n }\n\n getSOPs(sessionId: string): Array<SOP & { id: string; workflowId: string }> {\n const rows = this.db.prepare(`\n SELECT s.* FROM sops s\n JOIN workflows w ON s.workflow_id = w.id\n WHERE w.session_id = ?\n `).all(sessionId) as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n workflowId: r['workflow_id'] as string,\n title: r['title'] as string,\n description: r['description'] as string,\n steps: JSON.parse(r['steps'] as string) as SOP['steps'],\n involvedSystems: JSON.parse(r['involved_systems'] as string) as string[],\n estimatedDuration: r['estimated_duration'] as string,\n frequency: r['frequency'] as string,\n confidence: r['confidence'] as number,\n }));\n }\n\n markTaskAsSOPCandidate(taskId: string): void {\n this.db.prepare('UPDATE tasks SET is_sop_candidate = 1 WHERE id = ?').run(taskId);\n }\n\n getAllSOPs(): Array<SOP & { id: string; workflowId: string; generatedAt: string }> {\n const rows = this.db.prepare('SELECT * FROM sops ORDER BY generated_at DESC').all() as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n workflowId: r['workflow_id'] as string,\n title: r['title'] as string,\n description: r['description'] as string,\n steps: JSON.parse(r['steps'] as string) as SOP['steps'],\n involvedSystems: JSON.parse(r['involved_systems'] as string) as string[],\n estimatedDuration: r['estimated_duration'] as string,\n frequency: r['frequency'] as string,\n confidence: r['confidence'] as number,\n generatedAt: r['generated_at'] as string,\n }));\n }\n\n // ── Connections (user-created hex map links) ─────────────────────────────\n\n upsertConnection(sessionId: string, conn: Omit<Connection, 'id'>): string {\n // Idempotent: same source+target+type = same connection\n const existing = this.db.prepare(\n 'SELECT id FROM connections WHERE session_id = ? AND source_asset_id = ? AND target_asset_id = ?'\n ).get(sessionId, conn.sourceAssetId, conn.targetAssetId) as { id: string } | undefined;\n if (existing) return existing.id;\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO connections (id, session_id, source_asset_id, target_asset_id, type, created_at)\n VALUES (?, ?, ?, ?, ?, ?)\n `).run(id, sessionId, conn.sourceAssetId, conn.targetAssetId, conn.type ?? null, new Date().toISOString());\n return id;\n }\n\n getConnections(sessionId: string): ConnectionRow[] {\n const rows = this.db.prepare('SELECT * FROM connections WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => ({\n id: r['id'] as string,\n sessionId: r['session_id'] as string,\n sourceAssetId: r['source_asset_id'] as string,\n targetAssetId: r['target_asset_id'] as string,\n type: (r['type'] as string | null) ?? undefined,\n createdAt: r['created_at'] as string,\n }));\n }\n\n deleteConnection(sessionId: string, connectionId: string): void {\n this.db.prepare('DELETE FROM connections WHERE session_id = ? AND id = ?').run(sessionId, connectionId);\n }\n\n // ── Approvals ───────────────────────────\n\n setApproval(pattern: string, action: 'save' | 'ignore' | 'auto'): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO node_approvals (pattern, action, created_at) VALUES (?, ?, ?)\n `).run(pattern, action, new Date().toISOString());\n }\n\n getApproval(pattern: string): string | undefined {\n const row = this.db.prepare('SELECT action FROM node_approvals WHERE pattern = ?').get(pattern) as { action: string } | undefined;\n return row?.action;\n }\n\n // ── Stats ───────────────────────────────\n\n getStats(sessionId: string): { nodes: number; edges: number; events: number; tasks: number } {\n const nodes = (this.db.prepare('SELECT COUNT(*) as c FROM nodes WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const edges = (this.db.prepare('SELECT COUNT(*) as c FROM edges WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const events = (this.db.prepare('SELECT COUNT(*) as c FROM activity_events WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const tasks = (this.db.prepare('SELECT COUNT(*) as c FROM tasks WHERE session_id = ?').get(sessionId) as { c: number }).c;\n return { nodes, edges, events, tasks };\n }\n}\n","import { z } from 'zod';\nimport type { CartographyDB } from './db.js';\nimport { NODE_TYPES, EDGE_RELATIONSHIPS, SOPStepSchema } from './types.js';\nimport { scanAllBookmarks, scanAllHistory } from './bookmarks.js';\n\n// Lazy import to avoid hard-wiring SDK at module parse time\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype McpServer = any;\n\nexport interface CartographyToolsOptions {\n /** Called when the agent needs a human answer. Return the user's response. */\n onAskUser?: (question: string, context?: string) => Promise<string>;\n}\n\nexport function stripSensitive(target: string): string {\n try {\n const url = new URL(target.startsWith('http') ? target : `tcp://${target}`);\n return `${url.hostname}${url.port ? ':' + url.port : ''}`;\n } catch {\n return target\n .replace(/\\/.*$/, '')\n .replace(/\\?.*$/, '')\n .replace(/@.*:/, ':');\n }\n}\n\nexport async function createCartographyTools(\n db: CartographyDB,\n sessionId: string,\n opts: CartographyToolsOptions = {},\n): Promise<McpServer> {\n // Dynamically import the SDK so missing package doesn't crash at load time\n const sdk = await import('@anthropic-ai/claude-code');\n const { tool, createSdkMcpServer } = sdk as {\n tool: (name: string, description: string, schema: z.ZodRawShape, handler: (args: Record<string, unknown>) => Promise<{ content: Array<{ type: string; text: string }> }>) => unknown;\n createSdkMcpServer: (opts: { name: string; version: string; tools: unknown[] }) => McpServer;\n };\n\n const tools = [\n tool('save_node', 'Save an infrastructure node to the catalog', {\n id: z.string(),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discoveredVia: z.string(),\n confidence: z.number().min(0).max(1),\n metadata: z.record(z.unknown()).optional(),\n tags: z.array(z.string()).optional(),\n domain: z.string().optional().describe('Business domain, e.g. \"Marketing\", \"Finance\"'),\n subDomain: z.string().optional().describe('Sub-domain, e.g. \"Forecast client orders\"'),\n qualityScore: z.number().min(0).max(100).optional().describe('Data quality score 0–100'),\n }, async (args) => {\n const node = {\n id: stripSensitive(args['id'] as string),\n type: args['type'] as typeof NODE_TYPES[number],\n name: args['name'] as string,\n discoveredVia: args['discoveredVia'] as string,\n confidence: args['confidence'] as number,\n metadata: (args['metadata'] as Record<string, unknown>) ?? {},\n tags: (args['tags'] as string[]) ?? [],\n domain: args['domain'] as string | undefined,\n subDomain: args['subDomain'] as string | undefined,\n qualityScore: args['qualityScore'] as number | undefined,\n };\n db.upsertNode(sessionId, node);\n return { content: [{ type: 'text', text: `✓ Node: ${node.id}` }] };\n }),\n\n tool('save_edge', 'Save a relationship (edge) between two nodes — ALWAYS save edges when connections are clear', {\n sourceId: z.string(),\n targetId: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string(),\n confidence: z.number().min(0).max(1),\n }, async (args) => {\n db.insertEdge(sessionId, {\n sourceId: args['sourceId'] as string,\n targetId: args['targetId'] as string,\n relationship: args['relationship'] as typeof EDGE_RELATIONSHIPS[number],\n evidence: args['evidence'] as string,\n confidence: args['confidence'] as number,\n });\n return { content: [{ type: 'text', text: `✓ ${args['sourceId']}→${args['targetId']}` }] };\n }),\n\n tool('get_catalog', 'Get the current catalog — use before save_node to avoid duplicates', {\n includeEdges: z.boolean().default(true),\n }, async (args) => {\n const nodes = db.getNodes(sessionId);\n const edges = (args['includeEdges'] as boolean) ? db.getEdges(sessionId) : [];\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: { nodes: nodes.length, edges: edges.length },\n nodeIds: nodes.map(n => n.id),\n }),\n }],\n };\n }),\n\n tool('ask_user', 'Ask the user a question — for clarifications, missing context, or consent (e.g. before scanning browser history)', {\n question: z.string().describe('The question for the user (clear and specific)'),\n context: z.string().optional().describe('Optional context explaining why this is relevant'),\n }, async (args) => {\n const question = args['question'] as string;\n const context = args['context'] as string | undefined;\n\n if (opts.onAskUser) {\n const answer = await opts.onAskUser(question, context);\n return { content: [{ type: 'text', text: answer }] };\n }\n\n // Fallback when not interactive (piped input, daemon, etc.)\n return {\n content: [{ type: 'text', text: '(Non-interactive mode — please continue without this information)' }],\n };\n }),\n\n tool('scan_bookmarks', 'Scan all browser bookmarks — hostnames only, no personal data (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)', {\n minConfidence: z.number().min(0).max(1).default(0.5).optional(),\n }, async () => {\n const hosts = await scanAllBookmarks();\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: hosts.length,\n hosts: hosts.map(h => ({\n hostname: h.hostname,\n port: h.port,\n protocol: h.protocol,\n source: h.source,\n })),\n note: 'Hostnames only — no paths, no personal data. Classify each as a business tool (save_node) or ignore (social media, news, shopping).',\n }),\n }],\n };\n }),\n\n tool('scan_browser_history', 'Scan browser history — anonymized hostnames + visit frequency. ALWAYS call ask_user for consent before using this tool.', {\n minVisits: z.number().min(1).default(3).optional().describe('Minimum visit count to include a host (filters rarely-visited sites)'),\n }, async (args) => {\n const minVisits = (args['minVisits'] as number | undefined) ?? 3;\n const hosts = await scanAllHistory();\n const filtered = hosts.filter(h => h.visitCount >= minVisits);\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: filtered.length,\n note: 'Anonymized — hostnames only, no URLs, no paths, no personal data. Classify business tools as saas_tool nodes.',\n hosts: filtered.map(h => ({\n hostname: h.hostname,\n visitCount: h.visitCount,\n protocol: h.protocol,\n source: h.source,\n })),\n }),\n }],\n };\n }),\n\n tool('scan_local_databases', 'Scan for local database files and running DB servers — PostgreSQL databases, MySQL, SQLite files from installed apps', {\n deep: z.boolean().default(false).optional().describe('Also search home directory recursively for SQLite/DB files (slower)'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const { homedir } = await import('node:os');\n const { existsSync } = await import('node:fs');\n const deep = (args['deep'] as boolean | undefined) ?? false;\n const HOME = homedir();\n\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 10_000, shell: '/bin/sh' }).toString().trim();\n } catch {\n return '';\n }\n };\n\n const results: Record<string, string> = {};\n\n // PostgreSQL\n results['POSTGRES_DATABASES'] = run('psql -lqt 2>/dev/null | grep -v \"template0\\\\|template1\" | awk \\'{print $1}\\' | grep -v \"^$\\\\|^|\"') || '(psql not running or not available)';\n results['POSTGRES_CLUSTERS'] = run('pg_lsclusters 2>/dev/null') || '(pg_lsclusters not available)';\n\n // MySQL / MariaDB\n results['MYSQL_DATABASES'] = run('mysql --connect-timeout=3 -e \"SHOW DATABASES;\" 2>/dev/null') || '(mysql not running or requires auth)';\n\n // MongoDB\n results['MONGODB_DATABASES'] = run('mongosh --quiet --eval \"db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join(\\'\\\\n\\')\" 2>/dev/null') || '(mongosh not available)';\n\n // Redis\n results['REDIS_INFO'] = run('redis-cli info server 2>/dev/null | head -5') || '(redis-cli not available)';\n\n // SQLite files in app data directories\n const appDirs = [`${HOME}/.config`, `${HOME}/.local/share`, `${HOME}/Library/Application Support`, '/var/lib'].filter(d => existsSync(d));\n if (appDirs.length > 0) {\n const findCmds = appDirs.map(d => `find \"${d}\" -maxdepth 4 \\\\( -name \"*.sqlite\" -o -name \"*.sqlite3\" -o -name \"*.db\" \\\\) 2>/dev/null`).join('; ');\n results['SQLITE_APP_FILES'] = run(`{ ${findCmds}; } | head -80`) || '(none found)';\n }\n\n // Deep home scan\n if (deep) {\n results['SQLITE_DEEP_SCAN'] = run(`find \"${HOME}\" -maxdepth 6 \\\\( -name \"*.sqlite\" -o -name \"*.sqlite3\" -o -name \"*.db\" \\\\) -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" 2>/dev/null | head -100`) || '(none found)';\n }\n\n // DB config files (no credentials extracted)\n results['DB_CONFIG_FILES'] = run(`find \"${HOME}\" -maxdepth 4 \\\\( -name \".env\" -o -name \".env.local\" -o -name \"database.yml\" -o -name \"database.json\" -o -name \"docker-compose.yml\" \\\\) 2>/dev/null | head -20`) || '(none found)';\n\n const out = Object.entries(results).map(([k, v]) => `=== ${k} ===\\n${v}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_k8s_resources', 'Scan Kubernetes cluster via kubectl — 100% readonly (get, describe)', {\n namespace: z.string().optional().describe('Filter by namespace — empty = all namespaces'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const ns = args['namespace'] as string | undefined;\n const nsFlag = ns ? `-n ${ns}` : '--all-namespaces';\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 15_000, shell: '/bin/sh' }).toString().trim();\n } catch (e) {\n return `(error: ${e instanceof Error ? e.message.split('\\n')[0] : String(e)})`;\n }\n };\n const sections: [string, string][] = [\n ['CONTEXT', 'kubectl config current-context 2>/dev/null || echo \"(no context set)\"'],\n ['NODES', 'kubectl get nodes -o wide'],\n ['NAMESPACES', 'kubectl get namespaces'],\n ['SERVICES', `kubectl get services ${nsFlag}`],\n ['DEPLOYMENTS', `kubectl get deployments ${nsFlag}`],\n ['STATEFULSETS', `kubectl get statefulsets ${nsFlag}`],\n ['INGRESSES', `kubectl get ingress ${nsFlag} 2>/dev/null || echo \"(none)\"`],\n ['PODS_RUNNING', `kubectl get pods ${nsFlag} --field-selector=status.phase=Running 2>/dev/null | head -60`],\n ['CONFIGMAPS_SYSTEM', 'kubectl get configmaps -n kube-system 2>/dev/null | head -30'],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${run(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_aws_resources', 'Scan AWS infrastructure via AWS CLI — 100% readonly (describe, list)', {\n region: z.string().optional().describe('AWS Region — default: AWS_DEFAULT_REGION or profile'),\n profile: z.string().optional().describe('AWS CLI profile'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const region = args['region'] as string | undefined;\n const profile = args['profile'] as string | undefined;\n const env: NodeJS.ProcessEnv = { ...process.env };\n if (region) env['AWS_DEFAULT_REGION'] = region;\n const pf = profile ? `--profile ${profile}` : '';\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 20_000, shell: '/bin/sh', env }).toString().trim();\n } catch (e) {\n return `(error: ${e instanceof Error ? e.message.split('\\n')[0] : String(e)})`;\n }\n };\n const sections: [string, string][] = [\n ['IDENTITY', `aws sts get-caller-identity ${pf} --output json`],\n ['EC2', `aws ec2 describe-instances ${pf} --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,PublicIpAddress,PrivateIpAddress,Tags[?Key==\\`Name\\`].Value|[0]]' --output table`],\n ['RDS', `aws rds describe-db-instances ${pf} --query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus,Endpoint.Address,Endpoint.Port]' --output table`],\n ['ELB_V2', `aws elbv2 describe-load-balancers ${pf} --query 'LoadBalancers[*].[LoadBalancerName,DNSName,Type,State.Code]' --output table`],\n ['EKS', `aws eks list-clusters ${pf} --output json`],\n ['ELASTICACHE', `aws elasticache describe-cache-clusters ${pf} --query 'CacheClusters[*].[CacheClusterId,Engine,CacheClusterStatus]' --output table 2>/dev/null || echo \"(not available)\"`],\n ['S3', `aws s3 ls ${pf} 2>/dev/null || echo \"(not available)\"`],\n ['VPC', `aws ec2 describe-vpcs ${pf} --query 'Vpcs[*].[VpcId,CidrBlock,IsDefault,Tags[?Key==\\`Name\\`].Value|[0]]' --output table`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${run(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_gcp_resources', 'Scan Google Cloud Platform via gcloud CLI — 100% readonly (list, describe)', {\n project: z.string().optional().describe('GCP Project ID — default: current gcloud project'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const project = args['project'] as string | undefined;\n const pf = project ? `--project ${project}` : '';\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 20_000, shell: '/bin/sh' }).toString().trim();\n } catch (e) {\n return `(error: ${e instanceof Error ? e.message.split('\\n')[0] : String(e)})`;\n }\n };\n const sections: [string, string][] = [\n ['IDENTITY', `gcloud config list account --format='value(core.account)' 2>/dev/null; gcloud config get-value project 2>/dev/null`],\n ['COMPUTE_INSTANCES', `gcloud compute instances list ${pf} 2>/dev/null || echo \"(error)\"`],\n ['SQL_INSTANCES', `gcloud sql instances list ${pf} 2>/dev/null || echo \"(error)\"`],\n ['GKE_CLUSTERS', `gcloud container clusters list ${pf} 2>/dev/null || echo \"(error)\"`],\n ['CLOUD_RUN', `gcloud run services list ${pf} --platform managed 2>/dev/null || echo \"(error)\"`],\n ['CLOUD_FUNCTIONS', `gcloud functions list ${pf} 2>/dev/null || echo \"(error)\"`],\n ['REDIS', `gcloud redis instances list ${pf} --regions=- 2>/dev/null || echo \"(error)\"`],\n ['PUBSUB', `gcloud pubsub topics list ${pf} 2>/dev/null || echo \"(error)\"`],\n ['SPANNER', `gcloud spanner instances list ${pf} 2>/dev/null || echo \"(error)\"`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${run(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_azure_resources', 'Scan Azure infrastructure via az CLI — 100% readonly (list, show)', {\n subscription: z.string().optional().describe('Azure Subscription ID'),\n resourceGroup: z.string().optional().describe('Filter by resource group'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const sub = args['subscription'] as string | undefined;\n const rg = args['resourceGroup'] as string | undefined;\n const sf = sub ? `--subscription ${sub}` : '';\n const rf = rg ? `--resource-group ${rg}` : '';\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 20_000, shell: '/bin/sh' }).toString().trim();\n } catch (e) {\n return `(error: ${e instanceof Error ? e.message.split('\\n')[0] : String(e)})`;\n }\n };\n const sections: [string, string][] = [\n ['IDENTITY', `az account show --output json ${sf} 2>/dev/null || echo \"(not logged in — az login)\"`],\n ['VMS', `az vm list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['AKS', `az aks list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['SQL_SERVERS', `az sql server list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['POSTGRES', `az postgres server list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['REDIS', `az redis list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['WEBAPPS', `az webapp list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['CONTAINER_APPS', `az containerapp list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ['FUNCTIONS', `az functionapp list ${sf} ${rf} --output table 2>/dev/null || echo \"(error)\"`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${run(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_installed_apps', 'Scan all installed apps and tools — IDEs, office, dev tools, business apps, databases', {\n searchHint: z.string().optional().describe('Optional search term to find specific tools (e.g. \"hubspot windsurf cursor\")'),\n }, async (args) => {\n const { execSync } = await import('node:child_process');\n const hint = args['searchHint'] as string | undefined;\n\n const run = (cmd: string): string => {\n try {\n return execSync(cmd, { stdio: 'pipe', timeout: 15_000, shell: '/bin/sh' }).toString().trim();\n } catch {\n return '';\n }\n };\n\n const platform = process.platform;\n const results: Record<string, string> = {};\n\n if (platform === 'darwin') {\n // macOS: scan /Applications\n results['APPLICATIONS'] = run('ls /Applications/ 2>/dev/null | head -200') || '(empty)';\n results['USER_APPLICATIONS'] = run('ls ~/Applications/ 2>/dev/null | head -100') || '(empty)';\n // Homebrew\n results['BREW_CASKS'] = run('brew list --cask 2>/dev/null | head -100') || '(brew not installed)';\n results['BREW_FORMULAE'] = run('brew list --formula 2>/dev/null | head -150') || '(brew not installed)';\n // Spotlight — find .app bundles\n results['SPOTLIGHT_APPS'] = run('mdfind \"kMDItemKind == \\'Application\\'\" 2>/dev/null | grep -v \"^/System\" | grep -v \"^/Library/Apple\" | head -100') || '(Spotlight not available)';\n } else if (platform === 'linux') {\n // Linux: dpkg, snap, flatpak, .desktop files\n results['DPKG'] = run('dpkg --list 2>/dev/null | awk \\'{print $2}\\' | head -200') || '(dpkg not available)';\n results['SNAP'] = run('snap list 2>/dev/null | head -50') || '(snap not available)';\n results['FLATPAK'] = run('flatpak list 2>/dev/null | head -50') || '(flatpak not available)';\n results['DESKTOP_FILES'] = run('ls /usr/share/applications/*.desktop ~/.local/share/applications/*.desktop 2>/dev/null | xargs -I{} basename {} .desktop 2>/dev/null | head -100') || '(no .desktop files)';\n results['RPM'] = run('rpm -qa 2>/dev/null | head -200') || '(rpm not available)';\n } else if (platform === 'win32') {\n results['WINGET'] = run('winget list 2>/dev/null | head -100') || '(winget not available)';\n results['PROGRAMS_x64'] = run('reg query \"HKLM\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\" /s /v DisplayName 2>/dev/null | findstr DisplayName | head -100') || '(not available)';\n }\n\n // Check known dev/business tools via `which`\n const knownTools = [\n // IDEs & Editors\n 'code', 'code-insiders', 'cursor', 'windsurf', 'zed', 'vim', 'nvim', 'emacs', 'nano', 'sublime_text', 'atom',\n 'idea', 'webstorm', 'pycharm', 'goland', 'datagrip', 'clion', 'rider', 'phpstorm', 'rubymine', 'appcode',\n // Dev Tools\n 'git', 'gh', 'docker', 'docker-compose', 'podman', 'kubectl', 'helm', 'terraform', 'ansible',\n 'node', 'npm', 'npx', 'yarn', 'pnpm', 'bun', 'deno',\n 'python', 'python3', 'pip', 'pip3', 'pipenv', 'poetry', 'conda',\n 'ruby', 'gem', 'bundler', 'rails',\n 'java', 'mvn', 'gradle', 'kotlin',\n 'go', 'cargo', 'rustc',\n 'php', 'composer',\n 'dotnet', 'dotnet-sdk',\n // Databases\n 'psql', 'mysql', 'mysqladmin', 'mongo', 'mongosh', 'redis-cli', 'sqlite3', 'clickhouse-client',\n // Cloud CLIs\n 'aws', 'gcloud', 'az', 'heroku', 'fly', 'vercel', 'netlify', 'wrangler',\n // Infra\n 'vagrant', 'packer', 'consul', 'vault', 'nomad',\n // Communication / SaaS\n 'slack', 'discord', 'zoom', 'teams', 'skype', 'telegram', 'signal',\n // Browsers\n 'google-chrome', 'chromium', 'firefox', 'safari', 'brave', 'opera', 'edge',\n // Monitoring / Analytics\n 'datadog-agent', 'newrelic-agent', 'prometheus', 'grafana-cli',\n // Other tools\n 'ngrok', 'stripe', 'supabase', 'neon',\n ];\n\n const found: string[] = [];\n const notFound: string[] = [];\n for (const t of knownTools) {\n const r = run(`which ${t} 2>/dev/null`);\n if (r) found.push(`${t}: ${r}`);\n else notFound.push(t);\n }\n results['WHICH_FOUND'] = found.join('\\n') || '(none found)';\n results['WHICH_NOT_FOUND'] = notFound.join(', ');\n\n // Hint-based search: if user asks for specific tools, do targeted search\n if (hint) {\n const terms = hint.split(/[\\s,]+/).filter(Boolean);\n const hintResults: string[] = [];\n for (const term of terms) {\n const safe = term.replace(/[^a-zA-Z0-9._-]/g, '');\n if (!safe) continue;\n const r = run(`which ${safe} 2>/dev/null || find /Applications ~/Applications /usr/bin /usr/local/bin /opt/homebrew/bin ~/.local/bin 2>/dev/null -iname \"*${safe}*\" -maxdepth 3 2>/dev/null | head -5`);\n if (r) hintResults.push(`${term}: ${r}`);\n else hintResults.push(`${term}: (not found)`);\n }\n results['HINT_SEARCH'] = hintResults.join('\\n');\n }\n\n const out = Object.entries(results)\n .map(([k, v]) => `=== ${k} ===\\n${v}`)\n .join('\\n\\n');\n\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('save_sop', 'Save a Standard Operating Procedure', {\n workflowId: z.string(),\n title: z.string(),\n description: z.string(),\n steps: z.array(SOPStepSchema),\n involvedSystems: z.array(z.string()),\n estimatedDuration: z.string(),\n frequency: z.string(),\n confidence: z.number().min(0).max(1),\n }, async (args) => {\n db.insertSOP({\n workflowId: args['workflowId'] as string,\n title: args['title'] as string,\n description: args['description'] as string,\n steps: args['steps'] as ReturnType<typeof SOPStepSchema.parse>[],\n involvedSystems: args['involvedSystems'] as string[],\n estimatedDuration: args['estimatedDuration'] as string,\n frequency: args['frequency'] as string,\n confidence: args['confidence'] as number,\n });\n return { content: [{ type: 'text', text: `✓ SOP: ${args['title']}` }] };\n }),\n ];\n\n return createSdkMcpServer({\n name: 'cartography',\n version: '0.1.0',\n tools,\n });\n}\n","// PreToolUse Safety Hook — enforces read-only policy on all Bash calls\n\nimport type { HookCallback } from '@anthropic-ai/claude-code';\n\n// Word-boundary matched dangerous commands\nconst BLOCKED_CMDS =\n /\\b(rm|mv|cp|dd|mkfs|chmod|chown|chgrp|kill|killall|pkill|reboot|shutdown|poweroff|halt|systemctl\\s+(start|stop|restart|enable|disable)|service\\s+(start|stop|restart)|docker\\s+(rm|rmi|stop|kill|exec|run|build|push)|kubectl\\s+(delete|apply|edit|exec|run|create|patch)|apt|yum|dnf|pacman|pip\\s+install|npm\\s+(install|uninstall)|curl\\s+.*-X\\s*(POST|PUT|DELETE|PATCH)|wget\\s+-O|tee\\s)\\b/i;\n// Redirect operators (no word boundary needed)\nconst BLOCKED_REDIRECTS = />>|>[^>]/;\n\nexport type { HookCallback };\n\nexport const safetyHook: HookCallback = async (input) => {\n // Only intercept PreToolUse events (other hook events don't have tool_name)\n if (!('tool_name' in input)) return {};\n if ((input as { tool_name: string }).tool_name !== 'Bash') return {};\n\n const cmd = ((input as { tool_input: { command?: string } }).tool_input)?.command ?? '';\n\n if (BLOCKED_CMDS.test(cmd) || BLOCKED_REDIRECTS.test(cmd)) {\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `BLOCKED: \"${cmd}\" — read-only policy`,\n },\n };\n }\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n },\n };\n};\n","import type { CartographyDB } from './db.js';\nimport { createCartographyTools } from './tools.js';\nimport { safetyHook } from './safety.js';\nimport type { CartographyConfig } from './types.js';\n\n// ── Discovery Event Types ────────────────────────────────────────────────────\n\nexport type DiscoveryEvent =\n | { kind: 'thinking'; text: string }\n | { kind: 'tool_call'; tool: string; input: Record<string, unknown> }\n | { kind: 'tool_result'; tool: string; output: string }\n | { kind: 'turn'; turn: number }\n | { kind: 'done' };\n\nexport type AskUserFn = (question: string, context?: string) => Promise<string>;\n\n// ── runDiscovery ─────────────────────────────────────────────────────────────\n\nexport async function runDiscovery(\n config: CartographyConfig,\n db: CartographyDB,\n sessionId: string,\n onEvent?: (event: DiscoveryEvent) => void,\n onAskUser?: AskUserFn,\n hint?: string,\n): Promise<void> {\n const { query } = await import('@anthropic-ai/claude-code');\n const tools = await createCartographyTools(db, sessionId, { onAskUser });\n\n const hintSection = hint\n ? `\\n⚡ USER HINT (HIGH PRIORITY): The user wants to find these specific tools: \"${hint}\"\\n → Run scan_installed_apps(searchHint: \"${hint}\") IMMEDIATELY and save found tools as saas_tool nodes!\\n`\n : '';\n\n const systemPrompt = `You are an infrastructure discovery agent. Map the complete system landscape — local services, SaaS tools, AND all installed apps/tools of the user.\n${hintSection}\n━━ MANDATORY SEQUENCE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSTEP 1 — Browser Bookmarks (ALWAYS FIRST):\n Call scan_bookmarks() → classify every returned domain:\n • Business tools (GitHub, Notion, Jira, Linear, Vercel, AWS, Datadog, etc.) → save_node as saas_tool\n • Internal hosts (IPs, custom.company.com:PORT) → save_node as web_service\n • Personal (social media, news, streaming, shopping) → IGNORE, do NOT save\n\nSTEP 2 — Browser History (ASK FOR CONSENT FIRST):\n Call ask_user with question: \"May I scan your browser history anonymously? I only extract hostnames (no URLs, no personal data) to discover additional tools you use regularly. Answer yes or no.\"\n If user says yes → call scan_browser_history(minVisits: 5) → classify business tools as saas_tool nodes\n If user says no → skip and proceed to Step 3\n\nSTEP 3 — Installed Apps & Tools (VERY IMPORTANT):\n Call scan_installed_apps() → classify ALL found apps/tools:\n • IDEs (VS Code, Cursor, Windsurf, JetBrains, etc.) → save_node as saas_tool with category=\"ide\"\n • Office & productivity (Word, Excel, Notion, Obsidian, etc.) → save_node as saas_tool with category=\"productivity\"\n • Dev tools (Docker, kubectl, git, Node, Python, etc.) → save_node as saas_tool with category=\"dev-tool\"\n • Business apps (Slack, Zoom, HubSpot, Salesforce, etc.) → save_node as saas_tool with category=\"business\"\n • Browsers (Chrome, Firefox, Safari, etc.) → save_node as saas_tool with category=\"browser\"\n • Design tools (Figma, Sketch, Adobe, etc.) → save_node as saas_tool with category=\"design\"\n Save ALL relevant tools — even offline/local ones!\n\nSTEP 4 — Local Databases & Infrastructure:\n Call scan_local_databases() → discover running DB servers and SQLite files from installed apps\n • PostgreSQL running → save_node as database_server (id: \"database_server:localhost:5432\")\n • MySQL running → save_node as database_server (id: \"database_server:localhost:3306\")\n • MongoDB running → save_node as database_server\n • Redis running → save_node as cache_server\n • SQLite files in app directories → save_node as database if clearly a business app DB\n Then run: ss -tlnp && ps aux → identify all listening ports/processes\n Deepen each service: DB→schemas, API→endpoints, Queue→topics\n\nSTEP 5 — Cloud & Kubernetes (if CLI available):\n scan_k8s_resources() → Nodes, Services, Pods, Deployments, Ingresses\n scan_aws_resources() → EC2, RDS, ELB, EKS, ElastiCache, S3 (if AWS CLI + credentials)\n scan_gcp_resources() → Compute, SQL, GKE, Cloud Run, Functions (if gcloud + auth)\n scan_azure_resources() → VMs, AKS, SQL, Redis, WebApps (if az CLI + login)\n Errors / \"not available\" → ignore, continue with next tool\n\nSTEP 6 — Config Files:\n .env, docker-compose.yml, application.yml, kubernetes/*.yml\n Extract host:port only — NO credentials\n\nSTEP 7 — Clarifying Questions:\n Use ask_user() when: a service is unclear, context is missing, or user input would be helpful\n Examples: \"What environment is this (dev/staging/prod)?\", \"Is <host> an internal tool?\"\n\nSTEP 8 — EDGES (CRITICAL — do NOT skip!):\n After discovering nodes, ALWAYS map relationships with save_edge:\n • Developer uses IDE → save_edge(\"saas_tool:vscode\", \"saas_tool:github.com\", \"uses\")\n • App connects to Database → save_edge(app_id, db_id, \"connects_to\")\n • Service calls API → save_edge(service_id, api_id, \"calls\")\n • Container contains Service → save_edge(container_id, service_id, \"contains\")\n • Service reads from Queue → save_edge(service_id, queue_id, \"reads_from\")\n • Service writes to Database → save_edge(service_id, db_id, \"writes_to\")\n • App depends on Cache → save_edge(app_id, cache_id, \"depends_on\")\n Think: which tools does the developer use together? What connects to what?\n Use get_catalog to see all node IDs before saving edges.\n\nSTEP 9 — Done when all leads are exhausted.\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nPORT MAPPING: 5432=postgres, 3306=mysql, 27017=mongodb, 6379=redis,\n9092=kafka, 5672=rabbitmq, 80/443/8080/3000=web_service,\n9090=prometheus, 8500=consul, 8200=vault, 2379=etcd\n\nRULES:\n• Read-only only (ss, ps, cat, head, curl -s, docker inspect, kubectl get)\n• Node IDs: \"type:host:port\" or \"type:name\" — no paths, no credentials\n• saas_tool IDs: \"saas_tool:github.com\", \"saas_tool:vscode\", \"saas_tool:cursor\"\n• Installed-app IDs: \"saas_tool:<appname>\" e.g. \"saas_tool:slack\", \"saas_tool:docker-desktop\"\n• Confidence: 0.9 directly observed, 0.7 from config/bookmarks/apps, 0.5 inferred\n• metadata allowed: { description, category, port, version, path } — no passwords\n• Call get_catalog before save_node → avoid duplicates\n• Save edges whenever connections are clearly identifiable\n\nEntry points: ${config.entryPoints.join(', ')}`;\n\n const initialPrompt = hint\n ? `Start discovery with USER HINT: \"${hint}\".\nImmediately run scan_installed_apps(searchHint: \"${hint}\") to search for these tools.\nThen scan_bookmarks, then local services.\nUse ask_user when you need context from the user.`\n : `Start discovery now.\nFirst, IMMEDIATELY run scan_bookmarks — before using ss or ps.\nThen ask for browser history consent (Step 2).\nThen scan_installed_apps() for all installed apps and tools.\nThen scan_local_databases() for database servers and SQLite files.\nThen systematically scan local services, then config files.\nFinally, map all edges (Step 8 — critical!) before finishing.\nUse ask_user when you need context from the user.`;\n\n let turnCount = 0;\n\n for await (const msg of query({\n prompt: initialPrompt,\n options: {\n model: config.agentModel,\n maxTurns: config.maxTurns,\n customSystemPrompt: systemPrompt,\n mcpServers: { cartography: tools },\n allowedTools: [\n 'Bash',\n 'mcp__cartograph__save_node',\n 'mcp__cartograph__save_edge',\n 'mcp__cartograph__get_catalog',\n 'mcp__cartograph__scan_bookmarks',\n 'mcp__cartograph__scan_browser_history',\n 'mcp__cartograph__scan_installed_apps',\n 'mcp__cartograph__scan_local_databases',\n 'mcp__cartograph__scan_k8s_resources',\n 'mcp__cartograph__scan_aws_resources',\n 'mcp__cartograph__scan_gcp_resources',\n 'mcp__cartograph__scan_azure_resources',\n 'mcp__cartograph__ask_user',\n ],\n hooks: {\n PreToolUse: [{ matcher: 'Bash', hooks: [safetyHook] }],\n },\n permissionMode: 'bypassPermissions',\n },\n })) {\n if (!onEvent) continue;\n\n if (msg.type === 'assistant') {\n turnCount++;\n onEvent({ kind: 'turn', turn: turnCount });\n\n for (const block of msg.message.content) {\n if (block.type === 'text') {\n onEvent({ kind: 'thinking', text: block.text });\n }\n if (block.type === 'tool_use') {\n onEvent({\n kind: 'tool_call',\n tool: block.name as string,\n input: block.input as Record<string, unknown>,\n });\n }\n }\n }\n\n if (msg.type === 'user') {\n const content = msg.message?.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (typeof block === 'object' && block !== null && 'type' in block && (block as { type: string }).type === 'tool_result') {\n const tb = block as { tool_use_id?: string; content?: unknown };\n const text = typeof tb.content === 'string' ? tb.content : '';\n onEvent({ kind: 'tool_result', tool: tb.tool_use_id ?? '', output: text });\n }\n }\n }\n }\n\n if (msg.type === 'result') {\n onEvent({ kind: 'done' });\n return;\n }\n }\n}\n\n","import { mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { CartographyDB } from './db.js';\nimport type { NodeRow, EdgeRow, SOP } from './types.js';\nimport { buildMapData } from './mapper.js';\nimport { shadeVariant } from './cluster.js';\nimport { hexToPixel } from './hex.js';\n\n// ── Layer assignment ─────────────────────────────────────────────────────────\n\nfunction nodeLayer(type: string): string {\n if (type === 'saas_tool') return 'saas';\n if (['web_service', 'api_endpoint'].includes(type)) return 'web';\n if (['database_server', 'database', 'table', 'cache_server'].includes(type)) return 'data';\n if (['message_broker', 'queue', 'topic'].includes(type)) return 'messaging';\n if (['host', 'container', 'pod', 'k8s_cluster'].includes(type)) return 'infra';\n if (type === 'config_file') return 'config';\n return 'other';\n}\n\nconst LAYER_LABELS: Record<string, string> = {\n saas: '☁ SaaS Tools',\n web: '🌐 Web / API',\n data: '🗄 Data Layer',\n messaging: '📨 Messaging',\n infra: '🖥 Infrastructure',\n config: '📄 Config',\n other: '❓ Other',\n};\n\nconst LAYER_ORDER = ['saas', 'web', 'data', 'messaging', 'infra', 'config', 'other'];\n\n// ── Icons & Labels ───────────────────────────────────────────────────────────\n\nconst MERMAID_ICONS: Record<string, string> = {\n host: '🖥',\n database_server: '🗄',\n database: '🗄',\n table: '📋',\n web_service: '🌐',\n api_endpoint: '🔌',\n cache_server: '⚡',\n message_broker: '📨',\n queue: '📬',\n topic: '📢',\n container: '📦',\n pod: '☸',\n k8s_cluster: '☸',\n config_file: '📄',\n saas_tool: '☁',\n unknown: '❓',\n};\n\nconst EDGE_LABELS: Record<string, string> = {\n connects_to: '→',\n reads_from: 'reads',\n writes_to: 'writes',\n calls: 'calls',\n contains: 'contains',\n depends_on: 'depends on',\n};\n\n// Class colors per type (dark-theme friendly)\nconst MERMAID_CLASSES: Record<string, string> = {\n host: 'fill:#1e3352,stroke:#4a82c4,color:#cce',\n database_server:'fill:#1e3352,stroke:#4a82c4,color:#cce',\n database: 'fill:#163352,stroke:#3a8ad4,color:#bdf',\n table: 'fill:#0f2a40,stroke:#2a6090,color:#9bd',\n web_service: 'fill:#1a3a1a,stroke:#3a9a3a,color:#bfb',\n api_endpoint: 'fill:#0f2a0f,stroke:#2a7a2a,color:#9d9',\n cache_server: 'fill:#3a2a0a,stroke:#ca8a0a,color:#fda',\n message_broker: 'fill:#2a1a3a,stroke:#7a3aaa,color:#daf',\n queue: 'fill:#1f1030,stroke:#5a2a8a,color:#caf',\n topic: 'fill:#1f1030,stroke:#5a2a8a,color:#caf',\n container: 'fill:#1a2a3a,stroke:#3a6a9a,color:#acd',\n pod: 'fill:#0f1f2f,stroke:#2a5a8a,color:#8bc',\n k8s_cluster: 'fill:#0a1520,stroke:#1a4a7a,color:#7ab',\n config_file: 'fill:#2a2a1a,stroke:#7a7a2a,color:#ddc',\n saas_tool: 'fill:#2a1a2a,stroke:#9a3a9a,color:#daf',\n unknown: 'fill:#2a2a2a,stroke:#5a5a5a,color:#aaa',\n};\n\n// ── Mermaid ──────────────────────────────────────────────────────────────────\n\nfunction sanitize(id: string): string {\n return id.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n\nfunction nodeLabel(node: NodeRow): string {\n const icon = MERMAID_ICONS[node.type] ?? '?';\n const parts = node.id.split(':');\n const location = parts.length >= 3 ? `${parts[1]}:${parts[2]}` : parts[1] ?? '';\n const conf = `${Math.round(node.confidence * 100)}%`;\n\n // Pull 1-2 key metadata fields (no credentials)\n const meta = node.metadata as Record<string, unknown>;\n const extras: string[] = [];\n for (const key of ['category', 'version', 'description']) {\n const v = meta[key];\n if (typeof v === 'string' && v.length > 0) {\n extras.push(v.substring(0, 28));\n break; // max 1 extra line for readability\n }\n }\n\n const locLine = location ? `<br/><small>${location}</small>` : '';\n const extraLine = extras.length ? `<br/><small>${extras[0]}</small>` : '';\n return `[\"${icon} <b>${node.name}</b>${locLine}${extraLine}<br/><small>${node.type} · ${conf}</small>\"]`;\n}\n\nexport function generateTopologyMermaid(nodes: NodeRow[], edges: EdgeRow[]): string {\n if (nodes.length === 0) return 'graph TB\\n empty[\"No nodes discovered yet\"]';\n\n const lines: string[] = ['graph TB'];\n\n // classDef per used type\n const usedTypes = new Set(nodes.map(n => n.type));\n for (const type of usedTypes) {\n const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES['unknown']!;\n lines.push(` classDef ${type.replace(/_/g, '')} ${style}`);\n }\n lines.push('');\n\n // Group by semantic layer (ordered top→bottom)\n const layerMap = new Map<string, NodeRow[]>();\n for (const node of nodes) {\n const layer = nodeLayer(node.type);\n if (!layerMap.has(layer)) layerMap.set(layer, []);\n layerMap.get(layer)!.push(node);\n }\n\n for (const layerKey of LAYER_ORDER) {\n const layerNodes = layerMap.get(layerKey);\n if (!layerNodes || layerNodes.length === 0) continue;\n const label = LAYER_LABELS[layerKey] ?? layerKey;\n lines.push(` subgraph ${layerKey}[\"${label}\"]`);\n for (const node of layerNodes) {\n lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, '')}`);\n }\n lines.push(' end');\n lines.push('');\n }\n\n // Edges: dashed for low-confidence (<0.6), solid otherwise\n for (const edge of edges) {\n const src = sanitize(edge.sourceId);\n const tgt = sanitize(edge.targetId);\n const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;\n const arrow = edge.confidence < 0.6 ? `-. \"${label}\" .->` : `-->|\"${label}\"|`;\n lines.push(` ${src} ${arrow} ${tgt}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function generateDependencyMermaid(nodes: NodeRow[], edges: EdgeRow[]): string {\n const depEdges = edges.filter(e =>\n ['calls', 'reads_from', 'writes_to', 'depends_on'].includes(e.relationship)\n );\n\n if (depEdges.length === 0) return 'graph LR\\n empty[\"No dependency edges found\"]';\n\n const lines: string[] = ['graph LR'];\n\n const usedIds = new Set<string>();\n for (const edge of depEdges) {\n usedIds.add(edge.sourceId);\n usedIds.add(edge.targetId);\n }\n\n const usedNodes = nodes.filter(n => usedIds.has(n.id));\n const usedTypes = new Set(usedNodes.map(n => n.type));\n for (const type of usedTypes) {\n const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES['unknown']!;\n lines.push(` classDef ${type.replace(/_/g, '')} ${style}`);\n }\n lines.push('');\n\n for (const node of usedNodes) {\n lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, '')}`);\n }\n lines.push('');\n\n for (const edge of depEdges) {\n const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;\n lines.push(` ${sanitize(edge.sourceId)} -->|\"${label}\"| ${sanitize(edge.targetId)}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function generateWorkflowMermaid(sop: SOP): string {\n const lines: string[] = ['flowchart TD'];\n\n for (const step of sop.steps) {\n const nodeId = `S${step.order}`;\n const label = `${step.order}. ${step.instruction.substring(0, 60)}`;\n lines.push(` ${nodeId}[\"${label}\"]`);\n\n if (step.order > 1) {\n lines.push(` S${step.order - 1} --> ${nodeId}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n// ── Backstage YAML ───────────────────────────────────────────────────────────\n\nexport function exportBackstageYAML(nodes: NodeRow[], edges: EdgeRow[], org?: string): string {\n const owner = org ?? 'unknown';\n const docs: string[] = [];\n\n for (const node of nodes) {\n const isComponent = ['web_service', 'container', 'pod'].includes(node.type);\n const isAPI = node.type === 'api_endpoint';\n const kind = isComponent ? 'Component' : isAPI ? 'API' : 'Resource';\n\n const deps = edges\n .filter(e => e.sourceId === node.id)\n .map(e => ` - resource:default/${sanitize(e.targetId)}`);\n\n const doc = [\n `apiVersion: backstage.io/v1alpha1`,\n `kind: ${kind}`,\n `metadata:`,\n ` name: ${sanitize(node.id)}`,\n ` annotations:`,\n ` cartography/discovered-at: \"${node.discoveredAt}\"`,\n ` cartography/confidence: \"${node.confidence}\"`,\n `spec:`,\n ` type: ${node.type}`,\n ` lifecycle: production`,\n ` owner: ${owner}`,\n ...(deps.length > 0 ? [' dependsOn:', ...deps] : []),\n ].join('\\n');\n\n docs.push(doc);\n }\n\n return docs.join('\\n---\\n');\n}\n\n// ── JSON ─────────────────────────────────────────────────────────────────────\n\nexport function exportJSON(db: CartographyDB, sessionId: string): string {\n const nodes = db.getNodes(sessionId);\n const edges = db.getEdges(sessionId);\n const events = db.getEvents(sessionId);\n const tasks = db.getTasks(sessionId);\n const sops = db.getSOPs(sessionId);\n const stats = db.getStats(sessionId);\n\n return JSON.stringify({\n sessionId,\n exportedAt: new Date().toISOString(),\n stats,\n nodes,\n edges,\n events,\n tasks,\n sops,\n }, null, 2);\n}\n\n// ── HTML (D3.js Hexagonal Cartography Map) ────────────────────────────────────\n\nexport function exportHTML(nodes: NodeRow[], edges: EdgeRow[]): string {\n const graphData = JSON.stringify({\n nodes: nodes.map(n => ({\n id: n.id,\n name: n.name,\n type: n.type,\n layer: nodeLayer(n.type),\n confidence: n.confidence,\n discoveredVia: n.discoveredVia,\n discoveredAt: n.discoveredAt,\n tags: n.tags,\n metadata: n.metadata,\n })),\n links: edges.map(e => ({\n source: e.sourceId,\n target: e.targetId,\n relationship: e.relationship,\n confidence: e.confidence,\n evidence: e.evidence,\n })),\n });\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Cartography — Infrastructure Map</title>\n <script src=\"https://d3js.org/d3.v7.min.js\"></script>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body { background: #0a0e14; color: #e6edf3; font-family: 'SF Mono','Fira Code','Cascadia Code',monospace; display: flex; overflow: hidden; height: 100vh; }\n\n /* ── Left node panel ─────────────────────────────── */\n #node-panel {\n width: 220px; min-width: 220px; height: 100vh; overflow: hidden;\n background: #0d1117; border-right: 1px solid #1b2028;\n display: flex; flex-direction: column;\n }\n #node-panel-header {\n padding: 10px 12px 8px; border-bottom: 1px solid #1b2028;\n font-size: 11px; color: #6e7681; text-transform: uppercase; letter-spacing: 0.6px;\n }\n #node-search {\n width: calc(100% - 16px); margin: 8px; padding: 5px 8px;\n background: #161b22; border: 1px solid #30363d; border-radius: 5px;\n color: #e6edf3; font-size: 11px; font-family: inherit; outline: none;\n }\n #node-search:focus { border-color: #58a6ff; }\n #node-list { flex: 1; overflow-y: auto; padding-bottom: 8px; }\n .node-list-item {\n padding: 5px 12px; cursor: pointer; font-size: 11px;\n display: flex; align-items: center; gap: 6px; border-left: 2px solid transparent;\n }\n .node-list-item:hover { background: #161b22; }\n .node-list-item.active { background: #1a2436; border-left-color: #58a6ff; }\n .node-list-dot { width: 7px; height: 7px; border-radius: 2px; flex-shrink: 0; }\n .node-list-name { color: #c9d1d9; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }\n .node-list-type { color: #484f58; font-size: 9px; flex-shrink: 0; }\n\n /* ── Center graph ────────────────────────────────── */\n #graph { flex: 1; height: 100vh; position: relative; }\n svg { width: 100%; height: 100%; }\n .hull { opacity: 0.12; stroke-width: 1.5; stroke-opacity: 0.25; }\n .hull-label { font-size: 13px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; fill-opacity: 0.5; pointer-events: none; }\n .link { stroke-opacity: 0.4; }\n .link-label { font-size: 8px; fill: #6e7681; pointer-events: none; opacity: 0; }\n .node-hex { stroke-width: 1.8; cursor: pointer; transition: opacity 0.15s; }\n .node-hex:hover { filter: brightness(1.3); stroke-width: 3; }\n .node-hex.selected { stroke-width: 3.5; filter: brightness(1.5); }\n .node-label { font-size: 10px; fill: #c9d1d9; pointer-events: none; opacity: 0; }\n\n /* ── Right sidebar ───────────────────────────────── */\n #sidebar {\n width: 300px; min-width: 300px; height: 100vh; overflow-y: auto;\n background: #0d1117; border-left: 1px solid #1b2028;\n padding: 16px; font-size: 12px; line-height: 1.6;\n }\n #sidebar h2 { margin: 0 0 8px; font-size: 14px; color: #58a6ff; }\n #sidebar .meta-table { width: 100%; border-collapse: collapse; }\n #sidebar .meta-table td { padding: 3px 6px; border-bottom: 1px solid #161b22; vertical-align: top; }\n #sidebar .meta-table td:first-child { color: #6e7681; white-space: nowrap; width: 90px; }\n #sidebar .tag { display: inline-block; background: #161b22; border-radius: 3px; padding: 1px 5px; margin: 1px; font-size: 10px; }\n #sidebar .conf-bar { height: 5px; border-radius: 3px; background: #161b22; margin-top: 3px; }\n #sidebar .conf-fill { height: 100%; border-radius: 3px; }\n #sidebar .edges-list { margin-top: 12px; }\n #sidebar .edge-item { padding: 4px 0; border-bottom: 1px solid #161b22; color: #6e7681; font-size: 11px; }\n #sidebar .edge-item span { color: #c9d1d9; }\n #sidebar .action-row { display: flex; gap: 6px; margin-top: 14px; }\n .btn-delete {\n flex: 1; padding: 6px 10px; background: transparent; border: 1px solid #6e191d;\n color: #f85149; border-radius: 5px; font-size: 11px; font-family: inherit;\n cursor: pointer; text-align: center;\n }\n .btn-delete:hover { background: #3d0c0c; }\n .hint { color: #3d434b; font-size: 11px; margin-top: 8px; }\n\n /* ── HUD ─────────────────────────────────────────── */\n #hud { position: absolute; top: 10px; left: 10px; background: rgba(10,14,20,0.88);\n padding: 10px 14px; border-radius: 8px; font-size: 12px; border: 1px solid #1b2028; pointer-events: none; }\n #hud strong { color: #58a6ff; }\n #hud .stats { color: #6e7681; }\n #hud .zoom-level { color: #3d434b; font-size: 10px; margin-top: 2px; }\n\n /* ── Toolbar (filters + JGF export) ─────────────── */\n #toolbar { position: absolute; top: 10px; right: 10px; display: flex; flex-wrap: wrap; gap: 4px; pointer-events: auto; align-items: center; }\n .filter-btn {\n background: rgba(10,14,20,0.85); border: 1px solid #1b2028; border-radius: 6px;\n color: #c9d1d9; padding: 4px 10px; font-size: 11px; cursor: pointer;\n font-family: inherit; display: flex; align-items: center; gap: 5px;\n }\n .filter-btn:hover { border-color: #30363d; }\n .filter-btn.off { opacity: 0.35; }\n .filter-dot { width: 8px; height: 8px; border-radius: 2px; display: inline-block; }\n .export-btn {\n background: rgba(10,14,20,0.85); border: 1px solid #1b2028; border-radius: 6px;\n color: #58a6ff; padding: 4px 12px; font-size: 11px; cursor: pointer;\n font-family: inherit;\n }\n .export-btn:hover { border-color: #58a6ff; background: rgba(88,166,255,0.08); }\n </style>\n</head>\n<body>\n\n<!-- Left: node list panel -->\n<div id=\"node-panel\">\n <div id=\"node-panel-header\">Nodes (${nodes.length})</div>\n <input id=\"node-search\" type=\"text\" placeholder=\"Search nodes…\" autocomplete=\"off\" spellcheck=\"false\">\n <div id=\"node-list\"></div>\n</div>\n\n<!-- Center: graph -->\n<div id=\"graph\">\n <div id=\"hud\">\n <strong>Cartography</strong> &nbsp;\n <span class=\"stats\" id=\"hud-stats\">${nodes.length} nodes · ${edges.length} edges</span><br>\n <span class=\"zoom-level\">Scroll = zoom · Drag = pan · Click = details</span>\n </div>\n <div id=\"toolbar\"></div>\n <svg></svg>\n</div>\n\n<!-- Right: detail sidebar -->\n<div id=\"sidebar\">\n <h2>Infrastructure Map</h2>\n <p class=\"hint\">Click a node to view details.</p>\n</div>\n\n<script>\nconst data = ${graphData};\n\n// ── Color palette per node type ───────────────────────────────────────────\nconst TYPE_COLORS = {\n host: '#4a9eff', database_server: '#ff6b6b', database: '#ff8c42',\n web_service: '#6bcb77', api_endpoint: '#4d96ff', cache_server: '#ffd93d',\n message_broker: '#c77dff', queue: '#e0aaff', topic: '#9d4edd',\n container: '#48cae4', pod: '#00b4d8', k8s_cluster: '#0077b6',\n config_file: '#adb5bd', saas_tool: '#c084fc', table: '#f97316', unknown: '#6c757d',\n};\n\nconst LAYER_COLORS = {\n saas: '#c084fc', web: '#6bcb77', data: '#ff6b6b',\n messaging: '#c77dff', infra: '#4a9eff', config: '#adb5bd', other: '#6c757d',\n};\nconst LAYER_NAMES = {\n saas: 'SaaS Tools', web: 'Web / API', data: 'Data Layer',\n messaging: 'Messaging', infra: 'Infrastructure', config: 'Config', other: 'Other',\n};\n\n// ── Hexagon path ──────────────────────────────────────────────────────────\nconst HEX_SIZE = { saas_tool: 16, host: 18, database_server: 18, k8s_cluster: 20, default: 14 };\nfunction hexSize(d) { return HEX_SIZE[d.type] || HEX_SIZE.default; }\nfunction hexPath(size) {\n const pts = [];\n for (let i = 0; i < 6; i++) {\n const angle = (Math.PI / 3) * i - Math.PI / 6;\n pts.push([size * Math.cos(angle), size * Math.sin(angle)]);\n }\n return 'M' + pts.map(p => p.join(',')).join('L') + 'Z';\n}\n\n// ── Left panel ────────────────────────────────────────────────────────────\nconst nodeListEl = document.getElementById('node-list');\nconst nodeSearchEl = document.getElementById('node-search');\nlet selectedNodeId = null;\n\nfunction buildNodeList(filter) {\n const q = (filter || '').toLowerCase();\n nodeListEl.innerHTML = '';\n const sorted = [...data.nodes].sort((a, b) => a.name.localeCompare(b.name));\n for (const d of sorted) {\n if (q && !d.name.toLowerCase().includes(q) && !d.type.includes(q) && !d.id.toLowerCase().includes(q)) continue;\n const item = document.createElement('div');\n item.className = 'node-list-item' + (d.id === selectedNodeId ? ' active' : '');\n item.dataset.id = d.id;\n const color = TYPE_COLORS[d.type] || '#aaa';\n item.innerHTML = \\`<span class=\"node-list-dot\" style=\"background:\\${color}\"></span>\n <span class=\"node-list-name\" title=\"\\${d.id}\">\\${d.name}</span>\n <span class=\"node-list-type\">\\${d.type.replace(/_/g,' ')}</span>\\`;\n item.onclick = () => { selectNode(d); focusNode(d); };\n nodeListEl.appendChild(item);\n }\n}\n\nnodeSearchEl.addEventListener('input', e => buildNodeList(e.target.value));\n\n// ── Sidebar detail view ───────────────────────────────────────────────────\nconst sidebar = document.getElementById('sidebar');\n\nfunction selectNode(d) {\n selectedNodeId = d.id;\n buildNodeList(nodeSearchEl.value);\n showNode(d);\n // highlight hex\n d3.selectAll('.node-hex').classed('selected', nd => nd.id === d.id);\n}\n\nfunction showNode(d) {\n const c = TYPE_COLORS[d.type] || '#aaa';\n const confPct = Math.round(d.confidence * 100);\n const tags = (d.tags || []).map(t => \\`<span class=\"tag\">\\${t}</span>\\`).join('');\n const metaRows = Object.entries(d.metadata || {})\n .filter(([,v]) => v !== null && v !== undefined && String(v).length > 0)\n .map(([k,v]) => \\`<tr><td>\\${k}</td><td>\\${JSON.stringify(v)}</td></tr>\\`)\n .join('');\n const related = data.links.filter(l =>\n (l.source.id||l.source) === d.id || (l.target.id||l.target) === d.id\n );\n const edgeItems = related.map(l => {\n const isOut = (l.source.id||l.source) === d.id;\n const other = isOut ? (l.target.id||l.target) : (l.source.id||l.source);\n return \\`<div class=\"edge-item\">\\${isOut ? '→' : '←'} <span>\\${other}</span> <small>[\\${l.relationship}]</small></div>\\`;\n }).join('');\n\n sidebar.innerHTML = \\`\n <h2>\\${d.name}</h2>\n <table class=\"meta-table\">\n <tr><td>ID</td><td style=\"font-size:10px;word-break:break-all\">\\${d.id}</td></tr>\n <tr><td>Type</td><td><span style=\"color:\\${c}\">\\${d.type}</span></td></tr>\n <tr><td>Layer</td><td>\\${d.layer}</td></tr>\n <tr><td>Confidence</td><td>\n \\${confPct}%\n <div class=\"conf-bar\"><div class=\"conf-fill\" style=\"width:\\${confPct}%;background:\\${c}\"></div></div>\n </td></tr>\n <tr><td>Discovered via</td><td>\\${d.discoveredVia || '—'}</td></tr>\n <tr><td>Timestamp</td><td>\\${d.discoveredAt ? d.discoveredAt.substring(0,19).replace('T',' ') : '—'}</td></tr>\n \\${tags ? '<tr><td>Tags</td><td>'+tags+'</td></tr>' : ''}\n \\${metaRows}\n </table>\n \\${related.length > 0 ? '<div class=\"edges-list\"><strong>Connections (' + related.length + '):</strong>'+edgeItems+'</div>' : ''}\n <div class=\"action-row\">\n <button class=\"btn-delete\" onclick=\"deleteNode('\\${d.id}')\">🗑 Delete node</button>\n </div>\n \\`;\n}\n\n// ── Delete node ───────────────────────────────────────────────────────────\nfunction deleteNode(id) {\n const idx = data.nodes.findIndex(n => n.id === id);\n if (idx === -1) return;\n data.nodes.splice(idx, 1);\n data.links = data.links.filter(l =>\n (l.source.id || l.source) !== id && (l.target.id || l.target) !== id\n );\n selectedNodeId = null;\n sidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Node deleted.</p>';\n document.getElementById('hud-stats').textContent =\n data.nodes.length + ' nodes · ' + data.links.length + ' edges';\n rebuildGraph();\n buildNodeList(nodeSearchEl.value);\n}\n\n// ── SVG setup ─────────────────────────────────────────────────────────────\nconst svgEl = d3.select('svg');\nconst graphDiv = document.getElementById('graph');\nconst W = () => graphDiv.clientWidth;\nconst H = () => graphDiv.clientHeight;\nconst g = svgEl.append('g');\n\nsvgEl.append('defs').append('marker')\n .attr('id', 'arrow').attr('viewBox', '0 0 10 6')\n .attr('refX', 10).attr('refY', 3)\n .attr('markerWidth', 8).attr('markerHeight', 6)\n .attr('orient', 'auto')\n .append('path').attr('d', 'M0,0 L10,3 L0,6 Z').attr('fill', '#555');\n\nlet currentZoom = 1;\nconst zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', e => {\n g.attr('transform', e.transform);\n currentZoom = e.transform.k;\n updateLOD(currentZoom);\n});\nsvgEl.call(zoomBehavior);\n\n// ── Layer filter state ────────────────────────────────────────────────────\nconst layers = [...new Set(data.nodes.map(d => d.layer))];\nconst layerVisible = {};\nlayers.forEach(l => layerVisible[l] = true);\n\nconst toolbarEl = document.getElementById('toolbar');\n\n// Filter buttons\nlayers.forEach(layer => {\n const btn = document.createElement('button');\n btn.className = 'filter-btn';\n btn.innerHTML = \\`<span class=\"filter-dot\" style=\"background:\\${LAYER_COLORS[layer]||'#666'}\"></span>\\${LAYER_NAMES[layer]||layer}\\`;\n btn.onclick = () => {\n layerVisible[layer] = !layerVisible[layer];\n btn.classList.toggle('off', !layerVisible[layer]);\n updateVisibility();\n };\n toolbarEl.appendChild(btn);\n});\n\n// JGF export button\nconst jgfBtn = document.createElement('button');\njgfBtn.className = 'export-btn';\njgfBtn.textContent = '↓ JGF';\njgfBtn.title = 'Export JSON Graph Format';\njgfBtn.onclick = exportJGF;\ntoolbarEl.appendChild(jgfBtn);\n\n// ── JGF export ────────────────────────────────────────────────────────────\nfunction exportJGF() {\n const jgf = {\n graph: {\n directed: true,\n type: 'cartography',\n label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(data.nodes.map(n => [n.id, {\n label: n.name,\n metadata: { type: n.type, layer: n.layer, confidence: n.confidence,\n discoveredVia: n.discoveredVia, discoveredAt: n.discoveredAt,\n tags: n.tags, ...n.metadata }\n }])),\n edges: data.links.map(l => ({\n source: l.source.id || l.source,\n target: l.target.id || l.target,\n relation: l.relationship,\n metadata: { confidence: l.confidence, evidence: l.evidence }\n })),\n }\n };\n const blob = new Blob([JSON.stringify(jgf, null, 2)], { type: 'application/json' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url; a.download = 'cartography-graph.jgf.json'; a.click();\n URL.revokeObjectURL(url);\n}\n\n// ── Cluster force ─────────────────────────────────────────────────────────\nfunction clusterForce(alpha) {\n const centroids = {};\n const counts = {};\n data.nodes.forEach(d => {\n if (!centroids[d.layer]) { centroids[d.layer] = { x: 0, y: 0 }; counts[d.layer] = 0; }\n centroids[d.layer].x += d.x || 0;\n centroids[d.layer].y += d.y || 0;\n counts[d.layer]++;\n });\n for (const l in centroids) { centroids[l].x /= counts[l]; centroids[l].y /= counts[l]; }\n const strength = alpha * 0.15;\n data.nodes.forEach(d => {\n const c = centroids[d.layer];\n if (c) { d.vx += (c.x - d.x) * strength; d.vy += (c.y - d.y) * strength; }\n });\n}\n\n// ── Hull group ────────────────────────────────────────────────────────────\nconst hullGroup = g.append('g').attr('class', 'hulls');\nconst hullPaths = {};\nconst hullLabels = {};\nlayers.forEach(layer => {\n hullPaths[layer] = hullGroup.append('path').attr('class', 'hull')\n .attr('fill', LAYER_COLORS[layer] || '#666').attr('stroke', LAYER_COLORS[layer] || '#666');\n hullLabels[layer] = hullGroup.append('text').attr('class', 'hull-label')\n .attr('fill', LAYER_COLORS[layer] || '#666').text(LAYER_NAMES[layer] || layer);\n});\n\nfunction updateHulls() {\n layers.forEach(layer => {\n if (!layerVisible[layer]) { hullPaths[layer].attr('d', null); hullLabels[layer].attr('x', -9999); return; }\n const pts = data.nodes.filter(d => d.layer === layer && layerVisible[d.layer]).map(d => [d.x, d.y]);\n if (pts.length < 3) {\n hullPaths[layer].attr('d', null);\n if (pts.length > 0) hullLabels[layer].attr('x', pts[0][0]).attr('y', pts[0][1] - 30);\n else hullLabels[layer].attr('x', -9999);\n return;\n }\n const hull = d3.polygonHull(pts);\n if (!hull) { hullPaths[layer].attr('d', null); return; }\n const cx = d3.mean(hull, p => p[0]);\n const cy = d3.mean(hull, p => p[1]);\n const padded = hull.map(p => {\n const dx = p[0] - cx, dy = p[1] - cy;\n const len = Math.sqrt(dx*dx + dy*dy) || 1;\n return [p[0] + dx/len * 40, p[1] + dy/len * 40];\n });\n hullPaths[layer].attr('d', 'M' + padded.join('L') + 'Z');\n hullLabels[layer].attr('x', cx).attr('y', cy - d3.max(hull, p => Math.abs(p[1] - cy)) - 30);\n });\n}\n\n// ── Graph rendering (rebuildable after delete) ────────────────────────────\nlet linkSel, linkLabelSel, nodeSel, nodeLabelSel, sim;\nconst linkGroup = g.append('g');\nconst nodeGroup = g.append('g');\n\nfunction focusNode(d) {\n if (!d.x || !d.y) return;\n const w = W(), h = H();\n svgEl.transition().duration(500).call(\n zoomBehavior.transform,\n d3.zoomIdentity.translate(w / 2, h / 2).scale(Math.min(3, currentZoom < 1 ? 1.5 : currentZoom)).translate(-d.x, -d.y)\n );\n}\n\nfunction rebuildGraph() {\n if (sim) sim.stop();\n\n // Links\n linkSel = linkGroup.selectAll('line').data(data.links, d => \\`\\${d.source.id||d.source}>\\${d.target.id||d.target}\\`);\n linkSel.exit().remove();\n const linkEnter = linkSel.enter().append('line').attr('class', 'link');\n linkSel = linkEnter.merge(linkSel)\n .attr('stroke', d => d.confidence < 0.6 ? '#2a2e35' : '#3d434b')\n .attr('stroke-dasharray', d => d.confidence < 0.6 ? '4 3' : null)\n .attr('stroke-width', d => d.confidence < 0.6 ? 0.8 : 1.2)\n .attr('marker-end', 'url(#arrow)');\n linkSel.select('title').remove();\n linkSel.append('title').text(d => \\`\\${d.relationship} (\\${Math.round(d.confidence*100)}%)\\n\\${d.evidence||''}\\`);\n\n // Link labels\n linkLabelSel = linkGroup.selectAll('text').data(data.links, d => \\`\\${d.source.id||d.source}>\\${d.target.id||d.target}\\`);\n linkLabelSel.exit().remove();\n linkLabelSel = linkLabelSel.enter().append('text').attr('class', 'link-label').merge(linkLabelSel)\n .text(d => d.relationship);\n\n // Nodes\n nodeSel = nodeGroup.selectAll('g').data(data.nodes, d => d.id);\n nodeSel.exit().remove();\n const nodeEnter = nodeSel.enter().append('g')\n .call(d3.drag()\n .on('start', (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })\n .on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })\n .on('end', (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })\n )\n .on('click', (e, d) => { e.stopPropagation(); selectNode(d); });\n nodeEnter.append('path').attr('class', 'node-hex');\n nodeEnter.append('title');\n nodeEnter.append('text').attr('class', 'node-label').attr('text-anchor', 'middle');\n\n nodeSel = nodeEnter.merge(nodeSel);\n nodeSel.select('.node-hex')\n .attr('d', d => hexPath(hexSize(d)))\n .attr('fill', d => TYPE_COLORS[d.type] || '#aaa')\n .attr('stroke', d => { const c = d3.color(TYPE_COLORS[d.type] || '#aaa'); return c ? c.brighter(0.8).formatHex() : '#ccc'; })\n .attr('fill-opacity', d => 0.6 + d.confidence * 0.4)\n .classed('selected', d => d.id === selectedNodeId);\n nodeSel.select('title').text(d => \\`\\${d.name} (\\${d.type})\\nconf: \\${Math.round(d.confidence*100)}%\\`);\n nodeLabelSel = nodeSel.select('.node-label')\n .attr('dy', d => hexSize(d) + 13)\n .text(d => d.name.length > 20 ? d.name.substring(0, 18) + '…' : d.name);\n\n // Simulation\n sim = d3.forceSimulation(data.nodes)\n .force('link', d3.forceLink(data.links).id(d => d.id).distance(d => d.relationship === 'contains' ? 50 : 100).strength(0.4))\n .force('charge', d3.forceManyBody().strength(-280))\n .force('center', d3.forceCenter(W() / 2, H() / 2))\n .force('collision', d3.forceCollide().radius(d => hexSize(d) + 10))\n .force('cluster', clusterForce)\n .on('tick', () => {\n updateHulls();\n linkSel.attr('x1', d => d.source.x).attr('y1', d => d.source.y)\n .attr('x2', d => d.target.x).attr('y2', d => d.target.y);\n linkLabelSel.attr('x', d => (d.source.x + d.target.x) / 2)\n .attr('y', d => (d.source.y + d.target.y) / 2 - 4);\n nodeSel.attr('transform', d => \\`translate(\\${d.x},\\${d.y})\\`);\n });\n}\n\n// ── LOD & visibility ──────────────────────────────────────────────────────\nfunction updateLOD(k) {\n if (nodeLabelSel) nodeLabelSel.style('opacity', k > 0.5 ? Math.min(1, (k - 0.5) * 2) : 0);\n if (linkLabelSel) linkLabelSel.style('opacity', k > 1.2 ? Math.min(1, (k - 1.2) * 3) : 0);\n d3.selectAll('.hull-label').style('font-size', k < 0.4 ? '18px' : '13px');\n}\n\nfunction updateVisibility() {\n if (!nodeSel) return;\n nodeSel.style('display', d => layerVisible[d.layer] ? null : 'none');\n linkSel.style('display', d => {\n const s = data.nodes.find(n => n.id === (d.source.id||d.source));\n const t = data.nodes.find(n => n.id === (d.target.id||d.target));\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n linkLabelSel.style('display', d => {\n const s = data.nodes.find(n => n.id === (d.source.id||d.source));\n const t = data.nodes.find(n => n.id === (d.target.id||d.target));\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n}\n\n// ── Init ──────────────────────────────────────────────────────────────────\nrebuildGraph();\nbuildNodeList();\nupdateLOD(1);\n\nsvgEl.on('click', () => {\n selectedNodeId = null;\n d3.selectAll('.node-hex').classed('selected', false);\n buildNodeList(nodeSearchEl.value);\n sidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Click a node to view details.</p>';\n});\n</script>\n</body>\n</html>`;\n}\n\n// ── SOP Markdown ─────────────────────────────────────────────────────────────\n\nexport function exportSOPMarkdown(sop: SOP): string {\n const lines: string[] = [\n `# ${sop.title}`,\n '',\n `**Description:** ${sop.description}`,\n `**Systems:** ${sop.involvedSystems.join(', ')}`,\n `**Duration:** ${sop.estimatedDuration}`,\n `**Frequency:** ${sop.frequency}`,\n `**Confidence:** ${sop.confidence.toFixed(2)}`,\n '',\n '## Steps',\n '',\n ];\n\n for (const step of sop.steps) {\n lines.push(`${step.order}. **${step.tool}**${step.target ? ` → \\`${step.target}\\`` : ''}`);\n lines.push(` ${step.instruction}`);\n if (step.notes) lines.push(` _${step.notes}_`);\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n// ── SOP Dashboard HTML ───────────────────────────────────────────────────────\n\nexport function exportSOPDashboard(sops: Array<SOP & { id: string; workflowId: string; generatedAt?: string }>): string {\n const sopsJson = JSON.stringify(sops.map(s => ({\n id: s.id,\n title: s.title,\n description: s.description,\n steps: s.steps,\n systems: s.involvedSystems,\n duration: s.estimatedDuration,\n frequency: s.frequency,\n confidence: s.confidence,\n generatedAt: s.generatedAt ?? new Date().toISOString(),\n })));\n\n // System frequency: how many SOPs reference each system\n const systemCount: Record<string, number> = {};\n for (const sop of sops) {\n for (const sys of sop.involvedSystems) {\n systemCount[sys] = (systemCount[sys] ?? 0) + 1;\n }\n }\n const systemsJson = JSON.stringify(\n Object.entries(systemCount).sort((a, b) => b[1] - a[1])\n );\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Cartography — SOP Dashboard</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: #0d1117; color: #e6edf3;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;\n padding: 0; line-height: 1.6;\n }\n .header {\n background: linear-gradient(135deg, #161b22 0%, #1a1f2e 100%);\n border-bottom: 1px solid #30363d; padding: 32px 40px;\n }\n .header h1 { font-size: 24px; color: #58a6ff; margin-bottom: 8px; }\n .header .subtitle { color: #8b949e; font-size: 14px; }\n .stats-row {\n display: flex; gap: 24px; margin-top: 16px; flex-wrap: wrap;\n }\n .stat-card {\n background: #21262d; border: 1px solid #30363d; border-radius: 8px;\n padding: 12px 20px; min-width: 140px;\n }\n .stat-card .value { font-size: 28px; font-weight: 700; color: #58a6ff; }\n .stat-card .label { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; }\n .container { max-width: 1200px; margin: 0 auto; padding: 24px 40px; }\n .section-title { font-size: 18px; color: #c9d1d9; margin: 32px 0 16px; border-bottom: 1px solid #21262d; padding-bottom: 8px; }\n /* Systems bar chart */\n .systems-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; }\n .sys-tag {\n background: #21262d; border: 1px solid #30363d; border-radius: 6px;\n padding: 6px 12px; font-size: 12px; cursor: default;\n }\n .sys-tag .count { color: #58a6ff; font-weight: 600; margin-left: 4px; }\n /* SOP cards */\n .sop-card {\n background: #161b22; border: 1px solid #30363d; border-radius: 8px;\n margin-bottom: 16px; overflow: hidden; transition: border-color 0.2s;\n }\n .sop-card:hover { border-color: #58a6ff; }\n .sop-header {\n padding: 16px 20px; cursor: pointer; display: flex;\n justify-content: space-between; align-items: center;\n }\n .sop-header h3 { font-size: 16px; color: #e6edf3; }\n .sop-meta { display: flex; gap: 16px; align-items: center; font-size: 12px; color: #8b949e; }\n .sop-meta .freq { color: #3fb950; font-weight: 600; }\n .sop-meta .dur { color: #d29922; }\n .sop-meta .conf {\n display: inline-flex; align-items: center; gap: 4px;\n }\n .conf-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }\n .sop-body { display: none; padding: 0 20px 20px; border-top: 1px solid #21262d; }\n .sop-body.open { display: block; padding-top: 16px; }\n .sop-desc { color: #8b949e; font-size: 13px; margin-bottom: 12px; }\n .sop-systems { margin-bottom: 12px; }\n .sop-systems span { background: #0d419d33; color: #58a6ff; border-radius: 4px; padding: 2px 8px; font-size: 11px; margin-right: 4px; }\n .steps-list { list-style: none; counter-reset: step; }\n .steps-list li {\n counter-increment: step; position: relative;\n padding: 10px 12px 10px 44px; border-left: 2px solid #30363d;\n margin-left: 14px; font-size: 13px;\n }\n .steps-list li:last-child { border-left-color: transparent; }\n .steps-list li::before {\n content: counter(step);\n position: absolute; left: -14px; top: 8px;\n width: 26px; height: 26px; border-radius: 50%;\n background: #21262d; border: 2px solid #30363d;\n display: flex; align-items: center; justify-content: center;\n font-size: 12px; font-weight: 600; color: #58a6ff;\n }\n .step-tool { color: #d2a8ff; font-weight: 600; }\n .step-target { color: #7ee787; font-size: 12px; }\n .step-notes { color: #8b949e; font-style: italic; font-size: 12px; margin-top: 2px; }\n .step-instr { color: #c9d1d9; }\n .toggle-icon { color: #8b949e; font-size: 18px; transition: transform 0.2s; }\n .toggle-icon.open { transform: rotate(90deg); }\n .empty { color: #484f58; font-size: 14px; padding: 40px; text-align: center; }\n .gen-time { color: #484f58; font-size: 11px; margin-top: 8px; }\n </style>\n</head>\n<body>\n<div class=\"header\">\n <h1>SOP Dashboard</h1>\n <div class=\"subtitle\">Cartography — Standard Operating Procedures</div>\n <div class=\"stats-row\">\n <div class=\"stat-card\"><div class=\"value\" id=\"sop-count\">0</div><div class=\"label\">SOPs</div></div>\n <div class=\"stat-card\"><div class=\"value\" id=\"step-count\">0</div><div class=\"label\">Total Steps</div></div>\n <div class=\"stat-card\"><div class=\"value\" id=\"sys-count\">0</div><div class=\"label\">Systems</div></div>\n <div class=\"stat-card\"><div class=\"value\" id=\"avg-conf\">—</div><div class=\"label\">Avg Confidence</div></div>\n </div>\n</div>\n<div class=\"container\">\n <h2 class=\"section-title\">Involved Systems</h2>\n <div class=\"systems-grid\" id=\"systems\"></div>\n\n <h2 class=\"section-title\">SOPs</h2>\n <div id=\"sop-list\"></div>\n</div>\n<script>\nconst sops = ${sopsJson};\nconst systems = ${systemsJson};\n\ndocument.getElementById('sop-count').textContent = sops.length;\ndocument.getElementById('step-count').textContent = sops.reduce((a, s) => a + s.steps.length, 0);\ndocument.getElementById('sys-count').textContent = systems.length;\nconst avgConf = sops.length > 0\n ? (sops.reduce((a, s) => a + s.confidence, 0) / sops.length * 100).toFixed(0) + '%'\n : '—';\ndocument.getElementById('avg-conf').textContent = avgConf;\n\nconst sysDiv = document.getElementById('systems');\nsystems.forEach(([name, count]) => {\n const el = document.createElement('div');\n el.className = 'sys-tag';\n el.innerHTML = name + '<span class=\"count\">x' + count + '</span>';\n sysDiv.appendChild(el);\n});\n\nconst listDiv = document.getElementById('sop-list');\nif (sops.length === 0) {\n listDiv.innerHTML = '<div class=\"empty\">No SOPs found. Run a discovery session first.</div>';\n}\n\nsops.forEach((sop, i) => {\n const confColor = sop.confidence >= 0.8 ? '#3fb950' : sop.confidence >= 0.5 ? '#d29922' : '#f85149';\n const card = document.createElement('div');\n card.className = 'sop-card';\n card.innerHTML = \\`\n <div class=\"sop-header\" onclick=\"toggle(\\${i})\">\n <h3>\\${sop.title}</h3>\n <div class=\"sop-meta\">\n <span class=\"freq\">\\${sop.frequency}</span>\n <span class=\"dur\">\\${sop.duration}</span>\n <span class=\"conf\"><span class=\"conf-dot\" style=\"background:\\${confColor}\"></span>\\${Math.round(sop.confidence*100)}%</span>\n <span class=\"toggle-icon\" id=\"icon-\\${i}\">▸</span>\n </div>\n </div>\n <div class=\"sop-body\" id=\"body-\\${i}\">\n <div class=\"sop-desc\">\\${sop.description}</div>\n <div class=\"sop-systems\">\\${sop.systems.map(s => '<span>'+s+'</span>').join('')}</div>\n <ol class=\"steps-list\">\n \\${sop.steps.map(st => \\`\n <li>\n <span class=\"step-tool\">\\${st.tool}</span>\n \\${st.target ? '<span class=\"step-target\"> → '+st.target+'</span>' : ''}\n <div class=\"step-instr\">\\${st.instruction}</div>\n \\${st.notes ? '<div class=\"step-notes\">'+st.notes+'</div>' : ''}\n </li>\n \\`).join('')}\n </ol>\n <div class=\"gen-time\">Generated: \\${sop.generatedAt ? sop.generatedAt.substring(0,19).replace('T',' ') : '—'}</div>\n </div>\n \\`;\n listDiv.appendChild(card);\n});\n\nfunction toggle(i) {\n const body = document.getElementById('body-'+i);\n const icon = document.getElementById('icon-'+i);\n body.classList.toggle('open');\n icon.classList.toggle('open');\n}\n</script>\n</body>\n</html>`;\n}\n\n// ── Cartography Map Export ─────────────────────────────────────────────────────\n\nexport function exportCartographyMap(\n nodes: NodeRow[],\n edges: EdgeRow[],\n options?: { theme?: 'light' | 'dark' },\n): string {\n const mapData = buildMapData(nodes, edges, options);\n const { assets, clusters, connections, meta } = mapData;\n const isEmpty = assets.length === 0;\n const HEX_SIZE = 24;\n\n const dataJson = JSON.stringify({\n assets: assets.map(a => ({\n id: a.id, name: a.name, domain: a.domain, subDomain: a.subDomain ?? null,\n qualityScore: a.qualityScore ?? null, metadata: a.metadata,\n q: a.position.q, r: a.position.r,\n })),\n clusters: clusters.map(c => ({\n id: c.id, label: c.label, domain: c.domain, color: c.color,\n assetIds: c.assetIds, centroid: c.centroid,\n })),\n connections: connections.map(c => ({\n id: c.id, sourceAssetId: c.sourceAssetId, targetAssetId: c.targetAssetId,\n type: c.type ?? 'connection',\n })),\n });\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"/>\n<title>Data Cartography Map</title>\n<style>\n*{box-sizing:border-box;margin:0;padding:0}\nhtml,body{width:100%;height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif}\nbody{display:flex;flex-direction:column;background:${meta.theme === 'dark' ? '#0f172a' : '#f8fafc'};color:${meta.theme === 'dark' ? '#e2e8f0' : '#1e293b'}}\n#topbar{\n height:48px;display:flex;align-items:center;gap:16px;padding:0 20px;\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};border-bottom:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};z-index:10;flex-shrink:0;\n}\n#topbar h1{font-size:15px;font-weight:600;letter-spacing:-0.01em}\n#search-box{\n display:flex;align-items:center;gap:8px;background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'};\n border-radius:8px;padding:5px 10px;margin-left:auto;\n}\n#search-box input{\n border:none;background:transparent;font-size:13px;outline:none;width:180px;color:inherit;\n}\n#search-box input::placeholder{color:#94a3b8}\n#main{flex:1;display:flex;overflow:hidden;position:relative}\n#canvas-wrap{flex:1;position:relative;overflow:hidden;cursor:grab}\n#canvas-wrap.dragging{cursor:grabbing}\n#canvas-wrap.connecting{cursor:crosshair}\ncanvas{display:block;width:100%;height:100%}\n/* Detail panel */\n#detail-panel{\n width:280px;background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};border-left:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n display:flex;flex-direction:column;transform:translateX(100%);\n transition:transform .2s ease;z-index:5;flex-shrink:0;overflow-y:auto;\n}\n#detail-panel.open{transform:translateX(0)}\n#detail-panel .panel-header{\n padding:16px;border-bottom:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};display:flex;align-items:center;gap:10px;\n}\n#detail-panel .panel-header h3{font-size:14px;font-weight:600;flex:1;word-break:break-word}\n#detail-panel .close-btn{\n width:24px;height:24px;border:none;background:transparent;cursor:pointer;\n color:#94a3b8;border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:16px;\n}\n#detail-panel .close-btn:hover{background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'}}\n#detail-panel .panel-body{padding:12px 16px;display:flex;flex-direction:column;gap:12px}\n#detail-panel .meta-row{display:flex;flex-direction:column;gap:3px}\n#detail-panel .meta-label{font-size:11px;font-weight:500;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em}\n#detail-panel .meta-value{font-size:13px;word-break:break-all}\n#detail-panel .quality-bar{height:6px;border-radius:3px;background:${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};margin-top:4px}\n#detail-panel .quality-fill{height:6px;border-radius:3px;transition:width .3s}\n/* Bottom-left toolbar */\n#toolbar-left{\n position:absolute;bottom:20px;left:20px;display:flex;gap:8px;z-index:10;\n}\n.tb-btn{\n width:40px;height:40px;border-radius:10px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};box-shadow:0 1px 4px rgba(0,0,0,.08);cursor:pointer;\n display:flex;align-items:center;justify-content:center;font-size:18px;\n transition:all .15s;color:inherit;\n}\n.tb-btn:hover{border-color:#94a3b8}\n.tb-btn.active{background:${meta.theme === 'dark' ? '#1e3a5f' : '#eff6ff'};border-color:#3b82f6}\n/* Bottom-right toolbar */\n#toolbar-right{\n position:absolute;bottom:20px;right:20px;display:flex;flex-direction:column;\n align-items:flex-end;gap:8px;z-index:10;\n}\n#zoom-controls{display:flex;align-items:center;gap:6px}\n.zoom-btn{\n width:34px;height:34px;border-radius:8px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:18px;color:inherit;display:flex;align-items:center;justify-content:center;\n}\n.zoom-btn:hover{background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'}}\n#zoom-pct{font-size:12px;font-weight:500;color:#64748b;min-width:38px;text-align:center}\n#detail-selector{display:flex;flex-direction:column;gap:4px}\n.detail-btn{\n width:34px;height:34px;border-radius:8px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:12px;font-weight:600;color:#64748b;display:flex;align-items:center;justify-content:center;\n}\n.detail-btn:hover{background:${meta.theme === 'dark' ? '#334155' : '#f1f5f9'}}\n.detail-btn.active{background:${meta.theme === 'dark' ? '#1e3a5f' : '#eff6ff'};border-color:#3b82f6;color:#2563eb}\n#connect-btn{\n width:40px;height:40px;border-radius:10px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:18px;display:flex;align-items:center;justify-content:center;color:inherit;\n}\n#connect-btn.active{background:#fef3c7;border-color:#f59e0b}\n/* Tooltip */\n#tooltip{\n position:fixed;background:#1e293b;color:#fff;border-radius:8px;\n padding:8px 12px;font-size:12px;pointer-events:none;z-index:100;\n display:none;max-width:220px;box-shadow:0 4px 12px rgba(0,0,0,.15);\n}\n#tooltip .tt-name{font-weight:600;margin-bottom:2px}\n#tooltip .tt-domain{color:#94a3b8;font-size:11px}\n#tooltip .tt-quality{font-size:11px;margin-top:2px}\n/* Empty state */\n#empty-state{\n position:absolute;inset:0;display:flex;flex-direction:column;\n align-items:center;justify-content:center;gap:12px;color:#94a3b8;\n}\n#empty-state p{font-size:14px}\n/* Theme toggle */\n#theme-btn{\n width:40px;height:40px;border-radius:10px;border:1px solid ${meta.theme === 'dark' ? '#334155' : '#e2e8f0'};\n background:${meta.theme === 'dark' ? '#1e293b' : '#fff'};cursor:pointer;\n font-size:18px;display:flex;align-items:center;justify-content:center;color:inherit;\n}\n/* Dark mode overrides (toggled via JS) */\nbody.dark{background:#0f172a;color:#e2e8f0}\nbody.dark #topbar{background:#1e293b;border-color:#334155}\nbody.dark #search-box{background:#334155}\nbody.dark #detail-panel{background:#1e293b;border-color:#334155}\nbody.dark .tb-btn,body.dark .zoom-btn,body.dark .detail-btn,body.dark #connect-btn,body.dark #theme-btn{\n background:#1e293b;border-color:#334155;color:#e2e8f0;\n}\n/* Light mode overrides */\nbody.light{background:#f8fafc;color:#1e293b}\nbody.light #topbar{background:#fff;border-color:#e2e8f0}\n/* Connection hint */\n#connect-hint{\n position:absolute;top:12px;left:50%;transform:translateX(-50%);\n background:#fef3c7;border:1px solid #f59e0b;color:#92400e;\n padding:6px 14px;border-radius:20px;font-size:12px;font-weight:500;\n display:none;z-index:20;pointer-events:none;\n}\n/* Screen reader only */\n.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}\n</style>\n</head>\n<body class=\"${meta.theme}\">\n<!-- Top bar -->\n<div id=\"topbar\">\n <h1>Data Cartography Map</h1>\n <div id=\"search-box\">\n <span style=\"color:#94a3b8;font-size:14px\">&#8981;</span>\n <input id=\"search-input\" type=\"text\" placeholder=\"Search assets...\" aria-label=\"Search data assets\"/>\n </div>\n <button id=\"theme-btn\" title=\"Toggle dark/light mode\" aria-label=\"Toggle theme\">${meta.theme === 'dark' ? '&#9788;' : '&#9790;'}</button>\n</div>\n<!-- SR summary -->\n<div class=\"sr-only\" role=\"status\" aria-live=\"polite\" id=\"sr-summary\">\n Data cartography map with ${assets.length} assets in ${clusters.length} clusters.\n</div>\n<!-- Main area -->\n<div id=\"main\">\n <div id=\"canvas-wrap\" role=\"application\" aria-label=\"Data cartography hex map\" tabindex=\"0\">\n <canvas id=\"hexmap\" aria-hidden=\"true\"></canvas>\n ${isEmpty ? '<div id=\"empty-state\"><p style=\"font-size:48px\">&#128506;</p><p>No data assets available</p><p style=\"font-size:12px\">Run <code>datasynx-cartography discover</code> to populate the map</p></div>' : ''}\n </div>\n <div id=\"detail-panel\" role=\"complementary\" aria-label=\"Asset details\">\n <div class=\"panel-header\">\n <h3 id=\"dp-name\">&mdash;</h3>\n <button class=\"close-btn\" id=\"dp-close\" aria-label=\"Close panel\">&#10005;</button>\n </div>\n <div class=\"panel-body\" id=\"dp-body\"></div>\n </div>\n</div>\n<!-- Bottom-left toolbar -->\n<div id=\"toolbar-left\">\n <button class=\"tb-btn active\" id=\"btn-labels\" title=\"Show labels\" aria-pressed=\"true\" aria-label=\"Toggle labels\">&#127991;</button>\n <button class=\"tb-btn\" id=\"btn-quality\" title=\"Quality layer\" aria-pressed=\"false\" aria-label=\"Toggle quality layer\">&#128065;</button>\n</div>\n<!-- Bottom-right toolbar -->\n<div id=\"toolbar-right\">\n <div id=\"zoom-controls\">\n <button class=\"zoom-btn\" id=\"zoom-out\" aria-label=\"Zoom out\">&minus;</button>\n <span id=\"zoom-pct\">100%</span>\n <button class=\"zoom-btn\" id=\"zoom-in\" aria-label=\"Zoom in\">+</button>\n </div>\n <div id=\"detail-selector\">\n <button class=\"detail-btn\" id=\"dl-1\" aria-label=\"Detail level 1\">1</button>\n <button class=\"detail-btn active\" id=\"dl-2\" aria-label=\"Detail level 2\">2</button>\n <button class=\"detail-btn\" id=\"dl-3\" aria-label=\"Detail level 3\">3</button>\n <button class=\"detail-btn\" id=\"dl-4\" aria-label=\"Detail level 4\">4</button>\n </div>\n <button id=\"connect-btn\" title=\"Connection tool\" aria-label=\"Toggle connection tool\">&#128279;</button>\n</div>\n<!-- Connection hint -->\n<div id=\"connect-hint\">Click two assets to create a connection</div>\n<!-- Tooltip -->\n<div id=\"tooltip\" role=\"tooltip\">\n <div class=\"tt-name\" id=\"tt-name\"></div>\n <div class=\"tt-domain\" id=\"tt-domain\"></div>\n <div class=\"tt-quality\" id=\"tt-quality\"></div>\n</div>\n\n<script>\n(function() {\n'use strict';\n\n// ── Data ─────────────────────────────────────────────────────────────────────\nconst MAP = ${dataJson};\nconst HEX_SIZE = ${HEX_SIZE};\nconst IS_EMPTY = ${isEmpty};\n\n// Build asset index\nconst assetIndex = new Map();\nconst clusterByAsset = new Map();\nfor (const c of MAP.clusters) {\n for (const aid of c.assetIds) {\n clusterByAsset.set(aid, c);\n }\n}\nfor (const a of MAP.assets) {\n assetIndex.set(a.id, a);\n}\n\n// ── Canvas ────────────────────────────────────────────────────────────────────\nconst canvas = document.getElementById('hexmap');\nconst ctx = canvas.getContext('2d');\nconst wrap = document.getElementById('canvas-wrap');\nlet W = 0, H = 0;\n\nfunction resize() {\n const dpr = window.devicePixelRatio || 1;\n W = wrap.clientWidth; H = wrap.clientHeight;\n canvas.width = W * dpr; canvas.height = H * dpr;\n canvas.style.width = W + 'px'; canvas.style.height = H + 'px';\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n draw();\n}\nwindow.addEventListener('resize', resize);\n\n// ── Viewport ──────────────────────────────────────────────────────────────────\nlet vx = 0, vy = 0, scale = 1;\nlet detailLevel = 2, showLabels = true, showQuality = false;\nlet isDark = document.body.classList.contains('dark');\nlet connectMode = false, connectFirst = null;\nlet hoveredAssetId = null, selectedAssetId = null;\nlet searchQuery = '';\nlet localConnections = [...MAP.connections];\n\n// Flat-top hex math\nfunction htp_x(q, r) { return HEX_SIZE * (3/2 * q); }\nfunction htp_y(q, r) { return HEX_SIZE * (Math.sqrt(3)/2 * q + Math.sqrt(3) * r); }\nfunction w2s(wx, wy) { return { x: wx*scale+vx, y: wy*scale+vy }; }\nfunction s2w(sx, sy) { return { x: (sx-vx)/scale, y: (sy-vy)/scale }; }\n\nfunction fitToView() {\n if (IS_EMPTY || MAP.assets.length === 0) { vx = 0; vy = 0; scale = 1; return; }\n let mnx=Infinity,mny=Infinity,mxx=-Infinity,mxy=-Infinity;\n for (const a of MAP.assets) {\n const px=htp_x(a.q,a.r), py=htp_y(a.q,a.r);\n if(px<mnx)mnx=px;if(py<mny)mny=py;if(px>mxx)mxx=px;if(py>mxy)mxy=py;\n }\n const pw=mxx-mnx+HEX_SIZE*4, ph=mxy-mny+HEX_SIZE*4;\n scale = Math.min(W/pw, H/ph, 2) * 0.85;\n vx = W/2 - ((mnx+mxx)/2)*scale;\n vy = H/2 - ((mny+mxy)/2)*scale;\n}\n\n// ── Drawing ───────────────────────────────────────────────────────────────────\nfunction hexPath(cx, cy, r) {\n ctx.beginPath();\n for (let i=0;i<6;i++) {\n const angle = Math.PI/180*(60*i);\n const x=cx+r*Math.cos(angle), y=cy+r*Math.sin(angle);\n i===0?ctx.moveTo(x,y):ctx.lineTo(x,y);\n }\n ctx.closePath();\n}\n\nfunction shadeV(hex, amt) {\n if(!hex||hex.length<7)return hex;\n const n=parseInt(hex.replace('#',''),16);\n const r=Math.min(255,(n>>16)+amt), g=Math.min(255,((n>>8)&0xff)+amt), b=Math.min(255,(n&0xff)+amt);\n return '#'+r.toString(16).padStart(2,'0')+g.toString(16).padStart(2,'0')+b.toString(16).padStart(2,'0');\n}\n\nfunction draw() {\n ctx.clearRect(0,0,W,H);\n ctx.fillStyle = isDark ? '#0f172a' : '#f8fafc';\n ctx.fillRect(0,0,W,H);\n if (IS_EMPTY) return;\n\n const size = HEX_SIZE * scale;\n const matchedIds = getSearchMatches();\n const hasSearch = searchQuery.length > 0;\n\n // Draw connections\n ctx.save();\n ctx.strokeStyle = isDark ? 'rgba(148,163,184,0.35)' : 'rgba(100,116,139,0.25)';\n ctx.lineWidth = 1.5;\n ctx.setLineDash([4,4]);\n for (const conn of localConnections) {\n const src = assetIndex.get(conn.sourceAssetId);\n const tgt = assetIndex.get(conn.targetAssetId);\n if (!src||!tgt) continue;\n const sp=w2s(htp_x(src.q,src.r),htp_y(src.q,src.r));\n const tp=w2s(htp_x(tgt.q,tgt.r),htp_y(tgt.q,tgt.r));\n ctx.beginPath();ctx.moveTo(sp.x,sp.y);ctx.lineTo(tp.x,tp.y);ctx.stroke();\n }\n ctx.setLineDash([]);\n ctx.restore();\n\n // Draw hexagons per cluster\n for (const cluster of MAP.clusters) {\n const baseColor = cluster.color;\n const clusterAssets = cluster.assetIds.map(id=>assetIndex.get(id)).filter(Boolean);\n const isClusterMatch = !hasSearch || clusterAssets.some(a => matchedIds.has(a.id));\n const clusterDim = hasSearch && !isClusterMatch;\n\n for (let ai=0; ai<clusterAssets.length; ai++) {\n const asset = clusterAssets[ai];\n const wx=htp_x(asset.q,asset.r), wy=htp_y(asset.q,asset.r);\n const s=w2s(wx,wy);\n const cx=s.x, cy=s.y;\n\n // Frustum cull\n if(cx+size<0||cx-size>W||cy+size<0||cy-size>H) continue;\n\n // Shade variation\n const shade = ai%3===0?18:ai%3===1?8:0;\n let fillColor = shadeV(baseColor, shade);\n\n // Quality overlay\n if (showQuality && asset.qualityScore !== null && asset.qualityScore !== undefined) {\n const q = asset.qualityScore;\n if (q < 40) fillColor = '#ef4444';\n else if (q < 70) fillColor = '#f97316';\n }\n\n const alpha = clusterDim ? 0.18 : 1;\n const isHovered = asset.id === hoveredAssetId;\n const isSelected = asset.id === selectedAssetId;\n const isConnectFirst = asset.id === connectFirst;\n\n ctx.save();\n ctx.globalAlpha = alpha;\n hexPath(cx, cy, size*0.92);\n\n if (isDark && (isHovered||isSelected||isConnectFirst)) {\n ctx.shadowColor = fillColor;\n ctx.shadowBlur = isSelected ? 16 : 8;\n }\n\n ctx.fillStyle = fillColor;\n ctx.fill();\n\n if (isSelected||isConnectFirst) {\n ctx.strokeStyle = isConnectFirst ? '#f59e0b' : '#fff';\n ctx.lineWidth = 2.5;\n ctx.stroke();\n } else if (isHovered) {\n ctx.strokeStyle = isDark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.2)';\n ctx.lineWidth = 1.5;\n ctx.stroke();\n } else {\n ctx.strokeStyle = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.4)';\n ctx.lineWidth = 1;\n ctx.stroke();\n }\n ctx.restore();\n\n // Quality dot\n if (showQuality && asset.qualityScore!==null && asset.qualityScore!==undefined && size>8) {\n const q = asset.qualityScore;\n if (q < 70) {\n ctx.beginPath();\n ctx.arc(cx+size*0.4, cy-size*0.4, Math.max(3,size*0.14), 0, Math.PI*2);\n ctx.fillStyle = q<40?'#ef4444':'#f97316';\n ctx.fill();\n }\n }\n\n // Asset labels (detail 4, or 3 at high zoom)\n const showAssetLabel = showLabels && !clusterDim &&\n ((detailLevel>=4)||(detailLevel===3 && scale>=0.8));\n if (showAssetLabel && size>14) {\n const label = asset.name.length>12 ? asset.name.substring(0,11)+'...' : asset.name;\n ctx.save();\n ctx.font = Math.max(8,Math.min(11,size*0.38))+'px -apple-system,sans-serif';\n ctx.fillStyle = isDark ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.9)';\n ctx.textAlign='center';ctx.textBaseline='middle';\n ctx.fillText(label, cx, cy);\n ctx.restore();\n }\n }\n }\n\n // Cluster labels (pill badges)\n if (showLabels && detailLevel>=1) {\n for (const cluster of MAP.clusters) {\n if (cluster.assetIds.length===0) continue;\n if (hasSearch && !cluster.assetIds.some(id=>matchedIds.has(id))) continue;\n const s=w2s(cluster.centroid.x, cluster.centroid.y);\n drawPill(s.x, s.y-size*1.2, cluster.label, cluster.color, 14);\n }\n }\n\n // Sub-domain labels (detail 2+)\n if (showLabels && detailLevel>=2) {\n const subGroups = new Map();\n for (const a of MAP.assets) {\n if (!a.subDomain) continue;\n const key = a.domain+'|'+a.subDomain;\n if (!subGroups.has(key)) subGroups.set(key, []);\n subGroups.get(key).push(a);\n }\n for (const [, group] of subGroups) {\n let sx=0,sy=0;\n for (const a of group) { sx+=htp_x(a.q,a.r); sy+=htp_y(a.q,a.r); }\n const cx=sx/group.length, cy=sy/group.length;\n const s = w2s(cx, cy);\n drawPill(s.x, s.y+size*1.5, group[0].subDomain, '#64748b', 11);\n }\n }\n}\n\nfunction drawPill(x, y, text, color, fontSize) {\n if(!text) return;\n ctx.save();\n ctx.font = '600 '+fontSize+'px -apple-system,sans-serif';\n const tw=ctx.measureText(text).width;\n const ph=fontSize+8, pw=tw+20;\n ctx.beginPath();\n if (ctx.roundRect) ctx.roundRect(x-pw/2, y-ph/2, pw, ph, ph/2);\n else { ctx.rect(x-pw/2, y-ph/2, pw, ph); }\n ctx.fillStyle = isDark ? 'rgba(30,41,59,0.9)' : 'rgba(255,255,255,0.92)';\n ctx.shadowColor='rgba(0,0,0,0.15)'; ctx.shadowBlur=6;\n ctx.fill(); ctx.shadowBlur=0;\n ctx.fillStyle = isDark ? '#e2e8f0' : '#0f172a';\n ctx.textAlign='center'; ctx.textBaseline='middle';\n ctx.fillText(text, x, y);\n ctx.restore();\n}\n\n// ── Hit testing ───────────────────────────────────────────────────────────────\nfunction getAssetAt(sx, sy) {\n const w=s2w(sx,sy);\n for (const a of MAP.assets) {\n const wx=htp_x(a.q,a.r), wy=htp_y(a.q,a.r);\n const dx=Math.abs(w.x-wx), dy=Math.abs(w.y-wy);\n if (dx>HEX_SIZE||dy>HEX_SIZE) continue;\n if (dx*dx+dy*dy < HEX_SIZE*HEX_SIZE) return a;\n }\n return null;\n}\n\n// ── Search ────────────────────────────────────────────────────────────────────\nfunction getSearchMatches() {\n if(!searchQuery) return new Set();\n const q=searchQuery.toLowerCase();\n const m=new Set();\n for(const a of MAP.assets){\n if(a.name.toLowerCase().includes(q)||(a.domain&&a.domain.toLowerCase().includes(q))||\n (a.subDomain&&a.subDomain.toLowerCase().includes(q))) m.add(a.id);\n }\n return m;\n}\n\n// ── Pan & Zoom ────────────────────────────────────────────────────────────────\nlet dragging=false, lastMX=0, lastMY=0;\n\nwrap.addEventListener('mousedown', e=>{\n if(e.button!==0)return;\n dragging=true; lastMX=e.clientX; lastMY=e.clientY;\n wrap.classList.add('dragging');\n});\nwindow.addEventListener('mouseup', ()=>{dragging=false;wrap.classList.remove('dragging');});\nwindow.addEventListener('mousemove', e=>{\n if(dragging){\n vx+=e.clientX-lastMX; vy+=e.clientY-lastMY;\n lastMX=e.clientX; lastMY=e.clientY; draw(); return;\n }\n const rect=wrap.getBoundingClientRect();\n const sx=e.clientX-rect.left, sy=e.clientY-rect.top;\n const asset=getAssetAt(sx,sy);\n const newId=asset?asset.id:null;\n if(newId!==hoveredAssetId){hoveredAssetId=newId;draw();}\n const tt=document.getElementById('tooltip');\n if(asset){\n document.getElementById('tt-name').textContent=asset.name;\n document.getElementById('tt-domain').textContent=asset.domain+(asset.subDomain?' > '+asset.subDomain:'');\n document.getElementById('tt-quality').textContent=asset.qualityScore!==null?'Quality: '+asset.qualityScore+'/100':'';\n tt.style.display='block';tt.style.left=(e.clientX+12)+'px';tt.style.top=(e.clientY-8)+'px';\n } else { tt.style.display='none'; }\n});\n\nwrap.addEventListener('click', e=>{\n const rect=wrap.getBoundingClientRect();\n const sx=e.clientX-rect.left, sy=e.clientY-rect.top;\n const asset=getAssetAt(sx,sy);\n if(connectMode){\n if(!asset) return;\n if(!connectFirst){connectFirst=asset.id;draw();}\n else if(connectFirst!==asset.id){\n localConnections.push({id:crypto.randomUUID(),sourceAssetId:connectFirst,targetAssetId:asset.id,type:'connection'});\n connectFirst=null;draw();\n }\n return;\n }\n if(asset){selectedAssetId=asset.id;showDetailPanel(asset);}\n else{selectedAssetId=null;document.getElementById('detail-panel').classList.remove('open');}\n draw();\n});\n\n// Touch\nlet lastTouches=[];\nwrap.addEventListener('touchstart',e=>{lastTouches=[...e.touches];},{passive:true});\nwrap.addEventListener('touchmove',e=>{\n if(e.touches.length===1){\n vx+=e.touches[0].clientX-lastTouches[0].clientX;\n vy+=e.touches[0].clientY-lastTouches[0].clientY;draw();\n } else if(e.touches.length===2){\n const d0=Math.hypot(lastTouches[0].clientX-lastTouches[1].clientX,lastTouches[0].clientY-lastTouches[1].clientY);\n const d1=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY);\n const mx=(e.touches[0].clientX+e.touches[1].clientX)/2;\n const my=(e.touches[0].clientY+e.touches[1].clientY)/2;\n applyZoom(d1/d0,mx,my);\n }\n lastTouches=[...e.touches];\n},{passive:true});\n\nwrap.addEventListener('wheel',e=>{\n e.preventDefault();\n const rect=wrap.getBoundingClientRect();\n applyZoom(e.deltaY<0?1.12:1/1.12,e.clientX-rect.left,e.clientY-rect.top);\n},{passive:false});\n\nfunction applyZoom(factor,sx,sy){\n const ns=Math.max(0.05,Math.min(8,scale*factor));\n const wx=(sx-vx)/scale,wy=(sy-vy)/scale;\n scale=ns;vx=sx-wx*scale;vy=sy-wy*scale;\n document.getElementById('zoom-pct').textContent=Math.round(scale*100)+'%';draw();\n}\ndocument.getElementById('zoom-in').addEventListener('click',()=>applyZoom(1.25,W/2,H/2));\ndocument.getElementById('zoom-out').addEventListener('click',()=>applyZoom(1/1.25,W/2,H/2));\n\n// Keyboard\nwrap.addEventListener('keydown',e=>{\n const step=40;\n if(e.key==='ArrowLeft'){vx+=step;draw();}\n else if(e.key==='ArrowRight'){vx-=step;draw();}\n else if(e.key==='ArrowUp'){vy+=step;draw();}\n else if(e.key==='ArrowDown'){vy-=step;draw();}\n else if(e.key==='+'||e.key==='=')applyZoom(1.2,W/2,H/2);\n else if(e.key==='-')applyZoom(1/1.2,W/2,H/2);\n else if(e.key==='Escape'){\n selectedAssetId=null;document.getElementById('detail-panel').classList.remove('open');\n if(connectMode)toggleConnect();draw();\n }\n});\n\n// ── Detail Panel ──────────────────────────────────────────────────────────────\nfunction showDetailPanel(asset) {\n document.getElementById('dp-name').textContent=asset.name;\n const body=document.getElementById('dp-body');\n const rows=[['Domain',asset.domain],['Sub-domain',asset.subDomain],\n ['Quality Score',asset.qualityScore!==null?renderQuality(asset.qualityScore):null],\n ...Object.entries(asset.metadata||{}).slice(0,8).map(([k,v])=>[k,String(v)])\n ].filter(([,v])=>v!==null&&v!==undefined&&v!=='');\n body.innerHTML=rows.map(([l,v])=>'<div class=\"meta-row\"><div class=\"meta-label\">'+esc(String(l))+'</div><div class=\"meta-value\">'+v+'</div></div>').join('');\n const related=localConnections.filter(c=>c.sourceAssetId===asset.id||c.targetAssetId===asset.id);\n if(related.length>0){\n body.innerHTML+='<div class=\"meta-row\"><div class=\"meta-label\">Connections ('+related.length+')</div><div>'+\n related.map(c=>{const oid=c.sourceAssetId===asset.id?c.targetAssetId:c.sourceAssetId;\n const o=assetIndex.get(oid);return '<div class=\"meta-value\" style=\"margin-top:4px;font-size:12px\">'+(o?esc(o.name):oid)+'</div>';}).join('')+'</div></div>';\n }\n document.getElementById('detail-panel').classList.add('open');\n}\nfunction renderQuality(s){\n const c=s>=70?'#22c55e':s>=40?'#f97316':'#ef4444';\n return s+'/100 <div class=\"quality-bar\"><div class=\"quality-fill\" style=\"width:'+s+'%;background:'+c+'\"></div></div>';\n}\nfunction esc(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}\ndocument.getElementById('dp-close').addEventListener('click',()=>{\n document.getElementById('detail-panel').classList.remove('open');selectedAssetId=null;draw();\n});\n\n// ── Toolbar ───────────────────────────────────────────────────────────────────\n[1,2,3,4].forEach(n=>{\n document.getElementById('dl-'+n).addEventListener('click',()=>{\n detailLevel=n;document.querySelectorAll('.detail-btn').forEach(b=>b.classList.remove('active'));\n document.getElementById('dl-'+n).classList.add('active');draw();\n });\n});\ndocument.getElementById('btn-labels').addEventListener('click',()=>{\n showLabels=!showLabels;document.getElementById('btn-labels').classList.toggle('active',showLabels);draw();\n});\ndocument.getElementById('btn-quality').addEventListener('click',()=>{\n showQuality=!showQuality;document.getElementById('btn-quality').classList.toggle('active',showQuality);draw();\n});\nfunction toggleConnect(){\n connectMode=!connectMode;connectFirst=null;\n document.getElementById('connect-btn').classList.toggle('active',connectMode);\n wrap.classList.toggle('connecting',connectMode);\n document.getElementById('connect-hint').style.display=connectMode?'block':'none';draw();\n}\ndocument.getElementById('connect-btn').addEventListener('click',toggleConnect);\ndocument.getElementById('theme-btn').addEventListener('click',()=>{\n isDark=!isDark;\n document.body.classList.toggle('dark',isDark);document.body.classList.toggle('light',!isDark);\n document.getElementById('theme-btn').innerHTML=isDark?'&#9788;':'&#9790;';draw();\n});\ndocument.getElementById('search-input').addEventListener('input',e=>{searchQuery=e.target.value.trim();draw();});\n\n// ── Init ──────────────────────────────────────────────────────────────────────\nresize(); fitToView();\ndocument.getElementById('zoom-pct').textContent=Math.round(scale*100)+'%';\ndraw();\n})();\n</script>\n</body>\n</html>`;\n}\n\n// ── Discovery App (Combined Enterprise Frontend) ──────────────────────────────\n\nexport function exportDiscoveryApp(\n nodes: NodeRow[],\n edges: EdgeRow[],\n options?: { theme?: 'light' | 'dark' },\n): string {\n const theme = options?.theme ?? 'dark';\n\n // ── Topology D3 data ──────────────────────────────────────────────────────\n const graphData = JSON.stringify({\n nodes: nodes.map(n => ({\n id: n.id, name: n.name, type: n.type, layer: nodeLayer(n.type),\n confidence: n.confidence, discoveredVia: n.discoveredVia,\n discoveredAt: n.discoveredAt, tags: n.tags, metadata: n.metadata,\n })),\n links: edges.map(e => ({\n source: e.sourceId, target: e.targetId,\n relationship: e.relationship, confidence: e.confidence, evidence: e.evidence,\n })),\n });\n\n // ── Hex map data ──────────────────────────────────────────────────────────\n const { assets, clusters, connections } = buildMapData(nodes, edges, { theme });\n const isEmpty = assets.length === 0;\n const HEX_SIZE = 24;\n const mapJson = JSON.stringify({\n assets: assets.map(a => ({\n id: a.id, name: a.name, domain: a.domain, subDomain: a.subDomain ?? null,\n qualityScore: a.qualityScore ?? null, metadata: a.metadata,\n q: a.position.q, r: a.position.r,\n })),\n clusters: clusters.map(c => ({\n id: c.id, label: c.label, domain: c.domain, color: c.color,\n assetIds: c.assetIds, centroid: c.centroid,\n })),\n connections: connections.map(c => ({\n id: c.id, sourceAssetId: c.sourceAssetId, targetAssetId: c.targetAssetId,\n type: c.type ?? 'connection',\n })),\n });\n\n const nodeCount = nodes.length;\n const edgeCount = edges.length;\n const assetCount = assets.length;\n const clusterCount = clusters.length;\n\n return `<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"${theme}\">\n<head>\n<meta charset=\"UTF-8\"/>\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"/>\n<title>Cartography \\u2014 Datasynx Discovery</title>\n<script src=\"https://d3js.org/d3.v7.min.js\"><\\/script>\n<style>\n/* ── CSS Custom Properties ──────────────────────────────────────────────── */\n:root{\n --bg-base:#0f172a;--bg-surface:#1e293b;--bg-elevated:#273148;\n --border:#334155;--border-dim:#1e293b;\n --text:#e2e8f0;--text-muted:#94a3b8;--text-dim:#475569;\n --accent:#3b82f6;--accent-hover:#2563eb;--accent-dim:rgba(59,130,246,.12);\n}\n[data-theme=\"light\"]{\n --bg-base:#f8fafc;--bg-surface:#ffffff;--bg-elevated:#f1f5f9;\n --border:#e2e8f0;--border-dim:#f1f5f9;\n --text:#0f172a;--text-muted:#64748b;--text-dim:#94a3b8;\n --accent:#2563eb;--accent-hover:#1d4ed8;--accent-dim:rgba(37,99,235,.08);\n}\n\n/* ── Reset ──────────────────────────────────────────────────────────────── */\n*{box-sizing:border-box;margin:0;padding:0}\nhtml,body{width:100%;height:100%;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Inter',sans-serif}\nbody{display:flex;flex-direction:column;background:var(--bg-base);color:var(--text)}\n\n/* ── Topbar ─────────────────────────────────────────────────────────────── */\n#topbar{\n height:56px;display:flex;align-items:center;gap:16px;padding:0 20px;\n background:var(--bg-surface);border-bottom:1px solid var(--border);z-index:100;flex-shrink:0;\n}\n.tb-left{display:flex;align-items:center;gap:10px}\n.brand-logo{flex-shrink:0}\n.brand-name{font-size:15px;font-weight:700;color:var(--accent);letter-spacing:-.02em}\n.brand-product{font-size:14px;font-weight:500;color:var(--text-muted);margin-left:2px}\n.brand-sep{width:1px;height:24px;background:var(--border);margin:0 6px}\n.tb-center{display:flex;align-items:center;gap:2px;margin-left:auto;\n background:var(--bg-elevated);border-radius:8px;padding:3px}\n.tab-btn{\n padding:6px 16px;border:none;border-radius:6px;font-size:13px;font-weight:500;\n cursor:pointer;color:var(--text-muted);background:transparent;font-family:inherit;\n transition:all .15s;\n}\n.tab-btn:hover{color:var(--text)}\n.tab-btn.active{background:var(--accent);color:#fff;box-shadow:0 1px 3px rgba(0,0,0,.2)}\n.tb-right{display:flex;align-items:center;gap:8px;margin-left:auto}\n.tb-search{\n display:flex;align-items:center;gap:6px;background:var(--bg-elevated);\n border:1px solid var(--border);border-radius:8px;padding:5px 10px;\n}\n.tb-search input{\n border:none;background:transparent;font-size:13px;outline:none;width:160px;\n color:var(--text);font-family:inherit;\n}\n.tb-search input::placeholder{color:var(--text-dim)}\n.tb-search svg{flex-shrink:0;color:var(--text-dim)}\n.icon-btn{\n width:36px;height:36px;border-radius:8px;border:1px solid var(--border);\n background:var(--bg-surface);cursor:pointer;display:flex;align-items:center;\n justify-content:center;color:var(--text-muted);text-decoration:none;transition:all .15s;font-size:16px;\n}\n.icon-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-dim)}\n.tb-stats{font-size:11px;color:var(--text-dim);white-space:nowrap}\n\n/* ── Views ──────────────────────────────────────────────────────────────── */\n.view{flex:1;display:none;overflow:hidden;position:relative}\n.view.active{display:flex}\n\n/* ═══════════════════════════════════════════════════════════════════════════\n MAP VIEW\n ═══════════════════════════════════════════════════════════════════════════ */\n#map-wrap{flex:1;position:relative;overflow:hidden;cursor:grab}\n#map-wrap.dragging{cursor:grabbing}\n#map-wrap.connecting{cursor:crosshair}\n#map-wrap canvas{display:block;width:100%;height:100%}\n#map-detail{\n width:280px;background:var(--bg-surface);border-left:1px solid var(--border);\n display:flex;flex-direction:column;transform:translateX(100%);\n transition:transform .2s ease;z-index:5;flex-shrink:0;overflow-y:auto;\n}\n#map-detail.open{transform:translateX(0)}\n#map-detail .panel-header{\n padding:16px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:10px;\n}\n#map-detail .panel-header h3{font-size:14px;font-weight:600;flex:1;word-break:break-word}\n.close-btn{\n width:24px;height:24px;border:none;background:transparent;cursor:pointer;\n color:var(--text-muted);border-radius:4px;display:flex;align-items:center;justify-content:center;font-size:16px;\n}\n.close-btn:hover{background:var(--bg-elevated)}\n.panel-body{padding:12px 16px;display:flex;flex-direction:column;gap:12px}\n.meta-row{display:flex;flex-direction:column;gap:3px}\n.meta-label{font-size:11px;font-weight:500;color:var(--text-dim);text-transform:uppercase;letter-spacing:.05em}\n.meta-value{font-size:13px;word-break:break-all}\n.quality-bar{height:6px;border-radius:3px;background:var(--bg-elevated);margin-top:4px}\n.quality-fill{height:6px;border-radius:3px;transition:width .3s}\n\n/* Map toolbars */\n#map-tb-left{position:absolute;bottom:20px;left:20px;display:flex;gap:8px;z-index:10}\n#map-tb-right{position:absolute;bottom:20px;right:20px;display:flex;flex-direction:column;align-items:flex-end;gap:8px;z-index:10}\n.tb-tool{\n width:40px;height:40px;border-radius:10px;border:1px solid var(--border);\n background:var(--bg-surface);box-shadow:0 1px 4px rgba(0,0,0,.08);cursor:pointer;\n display:flex;align-items:center;justify-content:center;font-size:18px;\n transition:all .15s;color:var(--text);font-family:-apple-system,sans-serif;\n}\n#btn-all-labels{font-size:14px;font-weight:700;letter-spacing:-.02em}\n.tb-tool:hover{border-color:var(--text-muted)}\n.tb-tool.active{background:var(--accent-dim);border-color:var(--accent)}\n.map-zoom{display:flex;align-items:center;gap:6px}\n.zoom-btn{\n width:34px;height:34px;border-radius:8px;border:1px solid var(--border);\n background:var(--bg-surface);cursor:pointer;font-size:18px;color:var(--text);\n display:flex;align-items:center;justify-content:center;\n}\n.zoom-btn:hover{background:var(--bg-elevated)}\n#map-zoom-pct{font-size:12px;font-weight:500;color:var(--text-dim);min-width:38px;text-align:center}\n#map-connect-hint{\n position:absolute;top:12px;left:50%;transform:translateX(-50%);\n background:#fef3c7;border:1px solid #f59e0b;color:#92400e;\n padding:6px 14px;border-radius:20px;font-size:12px;font-weight:500;\n display:none;z-index:20;pointer-events:none;\n}\n#map-tooltip{\n position:fixed;background:var(--bg-surface);color:var(--text);border-radius:8px;\n padding:8px 12px;font-size:12px;pointer-events:none;z-index:200;\n display:none;max-width:220px;box-shadow:0 4px 12px rgba(0,0,0,.25);border:1px solid var(--border);\n}\n#map-tooltip .tt-name{font-weight:600;margin-bottom:2px}\n#map-tooltip .tt-domain{color:var(--text-muted);font-size:11px}\n#map-tooltip .tt-quality{font-size:11px;margin-top:2px}\n#map-empty{\n position:absolute;inset:0;display:flex;flex-direction:column;\n align-items:center;justify-content:center;gap:12px;color:var(--text-muted);\n}\n#map-empty p{font-size:14px}\n\n/* ═══════════════════════════════════════════════════════════════════════════\n TOPOLOGY VIEW\n ═══════════════════════════════════════════════════════════════════════════ */\n#topo-panel{\n width:220px;min-width:220px;height:100%;overflow:hidden;\n background:var(--bg-surface);border-right:1px solid var(--border);\n display:flex;flex-direction:column;\n}\n#topo-panel-header{\n padding:10px 12px 8px;border-bottom:1px solid var(--border);\n font-size:11px;color:var(--text-dim);text-transform:uppercase;letter-spacing:.6px;\n}\n#topo-search{\n width:calc(100% - 16px);margin:8px;padding:5px 8px;\n background:var(--bg-elevated);border:1px solid var(--border);border-radius:5px;\n color:var(--text);font-size:11px;font-family:inherit;outline:none;\n}\n#topo-search:focus{border-color:var(--accent)}\n#topo-list{flex:1;overflow-y:auto;padding-bottom:8px}\n.topo-item{\n padding:5px 12px;cursor:pointer;font-size:11px;\n display:flex;align-items:center;gap:6px;border-left:2px solid transparent;\n}\n.topo-item:hover{background:var(--bg-elevated)}\n.topo-item.active{background:var(--accent-dim);border-left-color:var(--accent)}\n.topo-dot{width:7px;height:7px;border-radius:2px;flex-shrink:0}\n.topo-name{color:var(--text);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}\n.topo-type{color:var(--text-dim);font-size:9px;flex-shrink:0}\n\n#topo-graph{flex:1;height:100%;position:relative}\n#topo-graph svg{width:100%;height:100%}\n.hull{opacity:.12;stroke-width:1.5;stroke-opacity:.25}\n.hull-label{font-size:13px;font-weight:700;letter-spacing:1px;text-transform:uppercase;fill-opacity:.5;pointer-events:none}\n.link{stroke-opacity:.4}\n.link-label{font-size:8px;fill:var(--text-dim);pointer-events:none;opacity:0}\n.node-hex{stroke-width:1.8;cursor:pointer;transition:opacity .15s}\n.node-hex:hover{filter:brightness(1.3);stroke-width:3}\n.node-hex.selected{stroke-width:3.5;filter:brightness(1.5)}\n.node-label{font-size:10px;fill:var(--text);pointer-events:none;opacity:0}\n\n#topo-sidebar{\n width:300px;min-width:300px;height:100%;overflow-y:auto;\n background:var(--bg-surface);border-left:1px solid var(--border);\n padding:16px;font-size:12px;line-height:1.6;\n}\n#topo-sidebar h2{margin:0 0 8px;font-size:14px;color:var(--accent)}\n#topo-sidebar .meta-table{width:100%;border-collapse:collapse}\n#topo-sidebar .meta-table td{padding:3px 6px;border-bottom:1px solid var(--border-dim);vertical-align:top}\n#topo-sidebar .meta-table td:first-child{color:var(--text-dim);white-space:nowrap;width:90px}\n#topo-sidebar .tag{display:inline-block;background:var(--bg-elevated);border-radius:3px;padding:1px 5px;margin:1px;font-size:10px}\n#topo-sidebar .conf-bar{height:5px;border-radius:3px;background:var(--bg-elevated);margin-top:3px}\n#topo-sidebar .conf-fill{height:100%;border-radius:3px}\n#topo-sidebar .edges-list{margin-top:12px}\n#topo-sidebar .edge-item{padding:4px 0;border-bottom:1px solid var(--border-dim);color:var(--text-dim);font-size:11px}\n#topo-sidebar .edge-item span{color:var(--text)}\n.hint{color:var(--text-dim);font-size:11px;margin-top:8px}\n\n#topo-hud{\n position:absolute;top:10px;left:10px;background:rgba(15,23,42,.88);\n padding:10px 14px;border-radius:8px;font-size:12px;border:1px solid var(--border);pointer-events:none;\n}\n#topo-hud strong{color:var(--accent)}\n#topo-hud .stats{color:var(--text-dim)}\n#topo-hud .zoom-level{color:var(--text-dim);font-size:10px;margin-top:2px}\n\n#topo-toolbar{position:absolute;top:10px;right:10px;display:flex;flex-wrap:wrap;gap:4px;pointer-events:auto;align-items:center}\n.filter-btn{\n background:rgba(15,23,42,.85);border:1px solid var(--border);border-radius:6px;\n color:var(--text);padding:4px 10px;font-size:11px;cursor:pointer;\n font-family:inherit;display:flex;align-items:center;gap:5px;\n}\n.filter-btn:hover{border-color:var(--text-dim)}\n.filter-btn.off{opacity:.35}\n.filter-dot{width:8px;height:8px;border-radius:2px;display:inline-block}\n.export-btn{\n background:rgba(15,23,42,.85);border:1px solid var(--border);border-radius:6px;\n color:var(--accent);padding:4px 12px;font-size:11px;cursor:pointer;font-family:inherit;\n}\n.export-btn:hover{border-color:var(--accent);background:var(--accent-dim)}\n</style>\n</head>\n<body>\n\n<!-- ═══════════════════════════════════════════════════════════════════════════\n TOPBAR\n ═══════════════════════════════════════════════════════════════════════════ -->\n<header id=\"topbar\">\n <div class=\"tb-left\">\n <svg class=\"brand-logo\" width=\"32\" height=\"32\" viewBox=\"0 0 32 32\" fill=\"none\">\n <path d=\"M16 1.5L29.5 8.75V23.25L16 30.5L2.5 23.25V8.75L16 1.5Z\" fill=\"#0F2347\" stroke=\"#2563EB\" stroke-width=\"1.2\"/>\n <circle cx=\"10\" cy=\"16\" r=\"2.8\" fill=\"#60A5FA\"/><circle cx=\"22\" cy=\"10.5\" r=\"2.2\" fill=\"#38BDF8\"/>\n <circle cx=\"22\" cy=\"21.5\" r=\"2.2\" fill=\"#38BDF8\"/>\n <line x1=\"12.5\" y1=\"14.8\" x2=\"19.8\" y2=\"11.2\" stroke=\"#93C5FD\" stroke-width=\"1.2\"/>\n <line x1=\"12.5\" y1=\"17.2\" x2=\"19.8\" y2=\"20.8\" stroke=\"#93C5FD\" stroke-width=\"1.2\"/>\n <line x1=\"22\" y1=\"12.7\" x2=\"22\" y2=\"19.3\" stroke=\"#93C5FD\" stroke-width=\"1\" stroke-dasharray=\"2 1.5\"/>\n </svg>\n <span class=\"brand-name\">datasynx</span>\n <span class=\"brand-sep\"></span>\n <span class=\"brand-product\">Cartography</span>\n </div>\n <div class=\"tb-center\">\n <button class=\"tab-btn active\" id=\"tab-map-btn\" data-tab=\"map\">Map</button>\n <button class=\"tab-btn\" id=\"tab-topo-btn\" data-tab=\"topo\">Topology</button>\n </div>\n <div class=\"tb-right\">\n <span class=\"tb-stats\">${nodeCount} nodes &middot; ${edgeCount} edges</span>\n <div class=\"tb-search\">\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"11\" cy=\"11\" r=\"8\"/><path d=\"M21 21l-4.35-4.35\"/>\n </svg>\n <input id=\"global-search\" type=\"text\" placeholder=\"Search...\" autocomplete=\"off\" spellcheck=\"false\"/>\n </div>\n <a href=\"https://www.linkedin.com/company/datasynx-ai/\" target=\"_blank\" rel=\"noopener noreferrer\"\n class=\"icon-btn\" title=\"Datasynx on LinkedIn\" aria-label=\"LinkedIn\">\n <svg width=\"17\" height=\"17\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <path d=\"M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z\"/>\n </svg>\n </a>\n <button id=\"theme-btn\" class=\"icon-btn\" title=\"Toggle theme\" aria-label=\"Toggle theme\">\n ${theme === 'dark' ? '&#9788;' : '&#9790;'}\n </button>\n </div>\n</header>\n\n<!-- ═══════════════════════════════════════════════════════════════════════════\n MAP VIEW\n ═══════════════════════════════════════════════════════════════════════════ -->\n<div id=\"view-map\" class=\"view active\">\n <div id=\"map-wrap\" tabindex=\"0\" aria-label=\"Data cartography hex map\">\n <canvas id=\"hexmap\" aria-hidden=\"true\"></canvas>\n ${isEmpty ? '<div id=\"map-empty\"><p style=\"font-size:48px\">&#128506;</p><p>No data assets discovered yet</p><p style=\"font-size:12px\">Run <code>datasynx-cartography discover</code> to populate the map</p></div>' : ''}\n </div>\n <div id=\"map-detail\">\n <div class=\"panel-header\">\n <h3 id=\"md-name\">&mdash;</h3>\n <button class=\"close-btn\" id=\"md-close\" aria-label=\"Close\">&#10005;</button>\n </div>\n <div class=\"panel-body\" id=\"md-body\"></div>\n </div>\n <div id=\"map-tb-left\">\n <button class=\"tb-tool active\" id=\"btn-labels\" title=\"Toggle labels\">&#127991;</button>\n <button class=\"tb-tool\" id=\"btn-all-labels\" title=\"Show all hex labels\">Aa</button>\n <button class=\"tb-tool\" id=\"btn-quality\" title=\"Quality layer\">&#128065;</button>\n <button class=\"tb-tool\" id=\"btn-connect\" title=\"Connection tool\">&#128279;</button>\n </div>\n <div id=\"map-tb-right\">\n <div class=\"map-zoom\">\n <button class=\"zoom-btn\" id=\"mz-out\">&minus;</button>\n <span id=\"map-zoom-pct\">100%</span>\n <button class=\"zoom-btn\" id=\"mz-in\">+</button>\n </div>\n </div>\n <div id=\"map-connect-hint\">Click two assets to create a connection</div>\n <div id=\"map-tooltip\"><div class=\"tt-name\" id=\"mtt-name\"></div><div class=\"tt-domain\" id=\"mtt-domain\"></div><div class=\"tt-quality\" id=\"mtt-quality\"></div></div>\n</div>\n\n<!-- ═══════════════════════════════════════════════════════════════════════════\n TOPOLOGY VIEW\n ═══════════════════════════════════════════════════════════════════════════ -->\n<div id=\"view-topo\" class=\"view\">\n <div id=\"topo-panel\">\n <div id=\"topo-panel-header\">Nodes (${nodeCount})</div>\n <input id=\"topo-search\" type=\"text\" placeholder=\"Search nodes\\u2026\" autocomplete=\"off\" spellcheck=\"false\"/>\n <div id=\"topo-list\"></div>\n </div>\n <div id=\"topo-graph\">\n <div id=\"topo-hud\">\n <strong>Topology</strong>&nbsp;\n <span class=\"stats\">${nodeCount} nodes &middot; ${edgeCount} edges</span><br/>\n <span class=\"zoom-level\">Scroll = zoom &middot; Drag = pan &middot; Click = details</span>\n </div>\n <div id=\"topo-toolbar\"></div>\n <svg></svg>\n </div>\n <div id=\"topo-sidebar\">\n <h2>Infrastructure Map</h2>\n <p class=\"hint\">Click a node to view details.</p>\n </div>\n</div>\n\n<script>\n// ═══════════════════════════════════════════════════════════════════════════════\n// SHARED STATE\n// ═══════════════════════════════════════════════════════════════════════════════\nlet isDark = document.documentElement.getAttribute('data-theme') === 'dark';\nlet currentTab = 'map';\nlet topoInited = false;\n\n// ── Theme toggle ─────────────────────────────────────────────────────────────\ndocument.getElementById('theme-btn').addEventListener('click', function() {\n isDark = !isDark;\n document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');\n this.innerHTML = isDark ? '\\\\u2606' : '\\\\u263E';\n if (typeof drawMap === 'function') drawMap();\n});\n\n// ── Tab switching ────────────────────────────────────────────────────────────\ndocument.querySelectorAll('.tab-btn').forEach(function(btn) {\n btn.addEventListener('click', function() {\n var tab = this.getAttribute('data-tab');\n if (tab === currentTab) return;\n currentTab = tab;\n document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });\n this.classList.add('active');\n document.querySelectorAll('.view').forEach(function(v) { v.classList.remove('active'); });\n document.getElementById('view-' + tab).classList.add('active');\n if (tab === 'topo' && !topoInited) { initTopology(); topoInited = true; }\n if (tab === 'map' && typeof drawMap === 'function') { resizeMap(); }\n });\n});\n\n// ── Global search ────────────────────────────────────────────────────────────\ndocument.getElementById('global-search').addEventListener('input', function(e) {\n var q = e.target.value.trim();\n if (typeof setMapSearch === 'function') setMapSearch(q);\n if (typeof setTopoSearch === 'function') setTopoSearch(q);\n});\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// MAP VIEW\n// ═══════════════════════════════════════════════════════════════════════════════\nvar MAP = ${mapJson};\nvar MAP_HEX = ${HEX_SIZE};\nvar MAP_EMPTY = ${isEmpty};\n\nvar mapAssetIndex = new Map();\nvar mapClusterByAsset = new Map();\nfor (var ci = 0; ci < MAP.clusters.length; ci++) {\n var c = MAP.clusters[ci];\n for (var ai = 0; ai < c.assetIds.length; ai++) mapClusterByAsset.set(c.assetIds[ai], c);\n}\nfor (var ni = 0; ni < MAP.assets.length; ni++) mapAssetIndex.set(MAP.assets[ni].id, MAP.assets[ni]);\n\nvar mapCanvas = document.getElementById('hexmap');\nvar mapCtx = mapCanvas.getContext('2d');\nvar mapWrap = document.getElementById('map-wrap');\nvar mW = 0, mH = 0;\nvar mvx = 0, mvy = 0, mScale = 1;\nvar mDetailLevel = 2, mShowLabels = true, mShowQuality = false, mShowAllLabels = false;\nvar mConnectMode = false, mConnectFirst = null;\nvar mHoveredId = null, mSelectedId = null;\nvar mSearchQuery = '';\nvar mLocalConns = MAP.connections.slice();\n\nfunction setMapSearch(q) { mSearchQuery = q; drawMap(); }\n\nfunction resizeMap() {\n var dpr = window.devicePixelRatio || 1;\n mW = mapWrap.clientWidth; mH = mapWrap.clientHeight;\n mapCanvas.width = mW * dpr; mapCanvas.height = mH * dpr;\n mapCanvas.style.width = mW + 'px'; mapCanvas.style.height = mH + 'px';\n mapCtx.setTransform(dpr, 0, 0, dpr, 0, 0);\n drawMap();\n}\nwindow.addEventListener('resize', function() { if (currentTab === 'map') resizeMap(); });\n\nfunction mHtp_x(q, r) { return MAP_HEX * (1.5 * q); }\nfunction mHtp_y(q, r) { return MAP_HEX * (Math.sqrt(3) / 2 * q + Math.sqrt(3) * r); }\nfunction mW2s(wx, wy) { return { x: wx * mScale + mvx, y: wy * mScale + mvy }; }\nfunction mS2w(sx, sy) { return { x: (sx - mvx) / mScale, y: (sy - mvy) / mScale }; }\n\nfunction mapFitToView() {\n if (MAP_EMPTY || MAP.assets.length === 0) { mvx = 0; mvy = 0; mScale = 1; return; }\n var mnx = Infinity, mny = Infinity, mxx = -Infinity, mxy = -Infinity;\n for (var i = 0; i < MAP.assets.length; i++) {\n var a = MAP.assets[i], px = mHtp_x(a.q, a.r), py = mHtp_y(a.q, a.r);\n if (px < mnx) mnx = px; if (py < mny) mny = py; if (px > mxx) mxx = px; if (py > mxy) mxy = py;\n }\n var pw = mxx - mnx + MAP_HEX * 4, ph = mxy - mny + MAP_HEX * 4;\n mScale = Math.min(mW / pw, mH / ph, 2) * 0.85;\n mvx = mW / 2 - ((mnx + mxx) / 2) * mScale;\n mvy = mH / 2 - ((mny + mxy) / 2) * mScale;\n}\n\nfunction mHexPath(cx, cy, r) {\n mapCtx.beginPath();\n for (var i = 0; i < 6; i++) {\n var angle = Math.PI / 180 * (60 * i);\n var x = cx + r * Math.cos(angle), y = cy + r * Math.sin(angle);\n i === 0 ? mapCtx.moveTo(x, y) : mapCtx.lineTo(x, y);\n }\n mapCtx.closePath();\n}\n\nfunction mShadeV(hex, amt) {\n if (!hex || hex.length < 7) return hex;\n var n = parseInt(hex.replace('#', ''), 16);\n var r = Math.min(255, (n >> 16) + amt), g = Math.min(255, ((n >> 8) & 0xff) + amt), b = Math.min(255, (n & 0xff) + amt);\n return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0');\n}\n\nfunction mGetSearchMatches() {\n if (!mSearchQuery) return new Set();\n var q = mSearchQuery.toLowerCase(), m = new Set();\n for (var i = 0; i < MAP.assets.length; i++) {\n var a = MAP.assets[i];\n if (a.name.toLowerCase().includes(q) || (a.domain && a.domain.toLowerCase().includes(q)) ||\n (a.subDomain && a.subDomain.toLowerCase().includes(q))) m.add(a.id);\n }\n return m;\n}\n\nfunction mDrawPill(x, y, text, color, fontSize) {\n if (!text) return;\n mapCtx.save();\n mapCtx.font = '600 ' + fontSize + 'px -apple-system,sans-serif';\n var tw = mapCtx.measureText(text).width;\n var ph = fontSize + 8, pw = tw + 20;\n mapCtx.beginPath();\n if (mapCtx.roundRect) mapCtx.roundRect(x - pw / 2, y - ph / 2, pw, ph, ph / 2);\n else mapCtx.rect(x - pw / 2, y - ph / 2, pw, ph);\n mapCtx.fillStyle = isDark ? 'rgba(30,41,59,0.9)' : 'rgba(255,255,255,0.92)';\n mapCtx.shadowColor = 'rgba(0,0,0,0.15)'; mapCtx.shadowBlur = 6;\n mapCtx.fill(); mapCtx.shadowBlur = 0;\n mapCtx.fillStyle = isDark ? '#e2e8f0' : '#0f172a';\n mapCtx.textAlign = 'center'; mapCtx.textBaseline = 'middle';\n mapCtx.fillText(text, x, y);\n mapCtx.restore();\n}\n\nfunction drawMap() {\n mapCtx.clearRect(0, 0, mW, mH);\n var bg = getComputedStyle(document.documentElement).getPropertyValue('--bg-base').trim();\n mapCtx.fillStyle = bg || (isDark ? '#0f172a' : '#f8fafc');\n mapCtx.fillRect(0, 0, mW, mH);\n if (MAP_EMPTY) return;\n\n var size = MAP_HEX * mScale;\n var matchedIds = mGetSearchMatches();\n var hasSearch = mSearchQuery.length > 0;\n\n // Connections\n mapCtx.save();\n mapCtx.strokeStyle = isDark ? 'rgba(148,163,184,0.35)' : 'rgba(100,116,139,0.25)';\n mapCtx.lineWidth = 1.5; mapCtx.setLineDash([4, 4]);\n for (var ci = 0; ci < mLocalConns.length; ci++) {\n var conn = mLocalConns[ci];\n var src = mapAssetIndex.get(conn.sourceAssetId), tgt = mapAssetIndex.get(conn.targetAssetId);\n if (!src || !tgt) continue;\n var sp = mW2s(mHtp_x(src.q, src.r), mHtp_y(src.q, src.r));\n var tp = mW2s(mHtp_x(tgt.q, tgt.r), mHtp_y(tgt.q, tgt.r));\n mapCtx.beginPath(); mapCtx.moveTo(sp.x, sp.y); mapCtx.lineTo(tp.x, tp.y); mapCtx.stroke();\n }\n mapCtx.setLineDash([]); mapCtx.restore();\n\n // Hexagons per cluster\n for (var cli = 0; cli < MAP.clusters.length; cli++) {\n var cluster = MAP.clusters[cli];\n var baseColor = cluster.color;\n var clusterAssets = cluster.assetIds.map(function(id) { return mapAssetIndex.get(id); }).filter(Boolean);\n var isClusterMatch = !hasSearch || clusterAssets.some(function(a) { return matchedIds.has(a.id); });\n var clusterDim = hasSearch && !isClusterMatch;\n\n for (var ai = 0; ai < clusterAssets.length; ai++) {\n var asset = clusterAssets[ai];\n var wx = mHtp_x(asset.q, asset.r), wy = mHtp_y(asset.q, asset.r);\n var s = mW2s(wx, wy), cx = s.x, cy = s.y;\n if (cx + size < 0 || cx - size > mW || cy + size < 0 || cy - size > mH) continue;\n\n var shade = ai % 3 === 0 ? 18 : ai % 3 === 1 ? 8 : 0;\n var fillColor = mShadeV(baseColor, shade);\n if (mShowQuality && asset.qualityScore !== null && asset.qualityScore !== undefined) {\n if (asset.qualityScore < 40) fillColor = '#ef4444';\n else if (asset.qualityScore < 70) fillColor = '#f97316';\n }\n\n var alpha = clusterDim ? 0.18 : 1;\n var isHov = asset.id === mHoveredId, isSel = asset.id === mSelectedId, isCF = asset.id === mConnectFirst;\n\n mapCtx.save(); mapCtx.globalAlpha = alpha;\n mHexPath(cx, cy, size * 0.92);\n if (isDark && (isHov || isSel || isCF)) { mapCtx.shadowColor = fillColor; mapCtx.shadowBlur = isSel ? 16 : 8; }\n mapCtx.fillStyle = fillColor; mapCtx.fill();\n if (isSel || isCF) { mapCtx.strokeStyle = isCF ? '#f59e0b' : '#fff'; mapCtx.lineWidth = 2.5; mapCtx.stroke(); }\n else if (isHov) { mapCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.4)' : 'rgba(0,0,0,0.2)'; mapCtx.lineWidth = 1.5; mapCtx.stroke(); }\n else { mapCtx.strokeStyle = isDark ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.4)'; mapCtx.lineWidth = 1; mapCtx.stroke(); }\n mapCtx.restore();\n\n if (mShowQuality && asset.qualityScore !== null && asset.qualityScore !== undefined && size > 8 && asset.qualityScore < 70) {\n mapCtx.beginPath(); mapCtx.arc(cx + size * 0.4, cy - size * 0.4, Math.max(3, size * 0.14), 0, Math.PI * 2);\n mapCtx.fillStyle = asset.qualityScore < 40 ? '#ef4444' : '#f97316'; mapCtx.fill();\n }\n\n var showAssetLabel = mShowLabels && !clusterDim && (mShowAllLabels || (mDetailLevel >= 4) || (mDetailLevel === 3 && mScale >= 0.8));\n if (showAssetLabel && size > 6) {\n var label = asset.name.length > 12 ? asset.name.substring(0, 11) + '...' : asset.name;\n mapCtx.save();\n mapCtx.font = Math.max(8, Math.min(11, size * 0.38)) + 'px -apple-system,sans-serif';\n mapCtx.fillStyle = isDark ? 'rgba(255,255,255,0.85)' : 'rgba(255,255,255,0.9)';\n mapCtx.textAlign = 'center'; mapCtx.textBaseline = 'middle';\n mapCtx.fillText(label, cx, cy); mapCtx.restore();\n }\n }\n }\n\n // Cluster labels\n if (mShowLabels && mDetailLevel >= 1) {\n for (var cli2 = 0; cli2 < MAP.clusters.length; cli2++) {\n var cl = MAP.clusters[cli2];\n if (cl.assetIds.length === 0) continue;\n if (hasSearch && !cl.assetIds.some(function(id) { return matchedIds.has(id); })) continue;\n var sc = mW2s(cl.centroid.x, cl.centroid.y);\n mDrawPill(sc.x, sc.y - size * 1.2, cl.label, cl.color, 14);\n }\n }\n\n // Sub-domain labels\n if (mShowLabels && mDetailLevel >= 2) {\n var subGroups = new Map();\n for (var si = 0; si < MAP.assets.length; si++) {\n var sa = MAP.assets[si];\n if (!sa.subDomain) continue;\n var key = sa.domain + '|' + sa.subDomain;\n if (!subGroups.has(key)) subGroups.set(key, []);\n subGroups.get(key).push(sa);\n }\n subGroups.forEach(function(group) {\n var sx = 0, sy = 0;\n for (var gi = 0; gi < group.length; gi++) { sx += mHtp_x(group[gi].q, group[gi].r); sy += mHtp_y(group[gi].q, group[gi].r); }\n var cxs = sx / group.length, cys = sy / group.length;\n var spt = mW2s(cxs, cys);\n mDrawPill(spt.x, spt.y + size * 1.5, group[0].subDomain, '#64748b', 11);\n });\n }\n}\n\n// ── Map hit test ─────────────────────────────────────────────────────────────\nfunction mGetAssetAt(sx, sy) {\n var w = mS2w(sx, sy);\n for (var i = 0; i < MAP.assets.length; i++) {\n var a = MAP.assets[i], wx = mHtp_x(a.q, a.r), wy = mHtp_y(a.q, a.r);\n var dx = Math.abs(w.x - wx), dy = Math.abs(w.y - wy);\n if (dx > MAP_HEX || dy > MAP_HEX) continue;\n if (dx * dx + dy * dy < MAP_HEX * MAP_HEX) return a;\n }\n return null;\n}\n\n// ── Map pan / zoom ───────────────────────────────────────────────────────────\nvar mDragging = false, mLastMX = 0, mLastMY = 0;\nmapWrap.addEventListener('mousedown', function(e) {\n if (e.button !== 0) return;\n mDragging = true; mLastMX = e.clientX; mLastMY = e.clientY;\n mapWrap.classList.add('dragging');\n});\nwindow.addEventListener('mouseup', function() { mDragging = false; mapWrap.classList.remove('dragging'); });\nwindow.addEventListener('mousemove', function(e) {\n if (currentTab !== 'map') return;\n if (mDragging) {\n mvx += e.clientX - mLastMX; mvy += e.clientY - mLastMY;\n mLastMX = e.clientX; mLastMY = e.clientY; drawMap(); return;\n }\n var rect = mapWrap.getBoundingClientRect();\n var sx = e.clientX - rect.left, sy = e.clientY - rect.top;\n var asset = mGetAssetAt(sx, sy);\n var newId = asset ? asset.id : null;\n if (newId !== mHoveredId) { mHoveredId = newId; drawMap(); }\n var tt = document.getElementById('map-tooltip');\n if (asset) {\n document.getElementById('mtt-name').textContent = asset.name;\n document.getElementById('mtt-domain').textContent = asset.domain + (asset.subDomain ? ' > ' + asset.subDomain : '');\n document.getElementById('mtt-quality').textContent = asset.qualityScore !== null ? 'Quality: ' + asset.qualityScore + '/100' : '';\n tt.style.display = 'block'; tt.style.left = (e.clientX + 12) + 'px'; tt.style.top = (e.clientY - 8) + 'px';\n } else { tt.style.display = 'none'; }\n});\n\nmapWrap.addEventListener('click', function(e) {\n var rect = mapWrap.getBoundingClientRect();\n var sx = e.clientX - rect.left, sy = e.clientY - rect.top;\n var asset = mGetAssetAt(sx, sy);\n if (mConnectMode) {\n if (!asset) return;\n if (!mConnectFirst) { mConnectFirst = asset.id; drawMap(); }\n else if (mConnectFirst !== asset.id) {\n mLocalConns.push({ id: crypto.randomUUID(), sourceAssetId: mConnectFirst, targetAssetId: asset.id, type: 'connection' });\n mConnectFirst = null; drawMap();\n }\n return;\n }\n if (asset) { mSelectedId = asset.id; mShowDetail(asset); }\n else { mSelectedId = null; document.getElementById('map-detail').classList.remove('open'); }\n drawMap();\n});\n\nmapWrap.addEventListener('wheel', function(e) {\n e.preventDefault();\n var rect = mapWrap.getBoundingClientRect();\n mApplyZoom(e.deltaY < 0 ? 1.12 : 1 / 1.12, e.clientX - rect.left, e.clientY - rect.top);\n}, { passive: false });\n\nfunction mApplyZoom(factor, sx, sy) {\n var ns = Math.max(0.05, Math.min(8, mScale * factor));\n var wx = (sx - mvx) / mScale, wy = (sy - mvy) / mScale;\n mScale = ns; mvx = sx - wx * mScale; mvy = sy - wy * mScale;\n document.getElementById('map-zoom-pct').textContent = Math.round(mScale * 100) + '%';\n drawMap();\n}\n\ndocument.getElementById('mz-in').addEventListener('click', function() { mApplyZoom(1.25, mW / 2, mH / 2); });\ndocument.getElementById('mz-out').addEventListener('click', function() { mApplyZoom(1 / 1.25, mW / 2, mH / 2); });\n\n// ── Map detail panel ─────────────────────────────────────────────────────────\nfunction mEsc(s) { return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'); }\nfunction mRenderQ(s) {\n var c = s >= 70 ? '#22c55e' : s >= 40 ? '#f97316' : '#ef4444';\n return s + '/100 <div class=\"quality-bar\"><div class=\"quality-fill\" style=\"width:' + s + '%;background:' + c + '\"></div></div>';\n}\nfunction mShowDetail(asset) {\n document.getElementById('md-name').textContent = asset.name;\n var body = document.getElementById('md-body');\n var rows = [['Domain', asset.domain], ['Sub-domain', asset.subDomain],\n ['Quality', asset.qualityScore !== null ? mRenderQ(asset.qualityScore) : null]\n ].concat(Object.entries(asset.metadata || {}).slice(0, 8).map(function(kv) { return [kv[0], String(kv[1])]; }))\n .filter(function(r) { return r[1] !== null && r[1] !== undefined && r[1] !== ''; });\n body.innerHTML = rows.map(function(r) {\n return '<div class=\"meta-row\"><div class=\"meta-label\">' + mEsc(String(r[0])) + '</div><div class=\"meta-value\">' + r[1] + '</div></div>';\n }).join('');\n var related = mLocalConns.filter(function(cn) { return cn.sourceAssetId === asset.id || cn.targetAssetId === asset.id; });\n if (related.length > 0) {\n body.innerHTML += '<div class=\"meta-row\"><div class=\"meta-label\">Connections (' + related.length + ')</div><div>' +\n related.map(function(cn) {\n var oid = cn.sourceAssetId === asset.id ? cn.targetAssetId : cn.sourceAssetId;\n var o = mapAssetIndex.get(oid);\n return '<div class=\"meta-value\" style=\"margin-top:4px;font-size:12px\">' + (o ? mEsc(o.name) : oid) + '</div>';\n }).join('') + '</div></div>';\n }\n document.getElementById('map-detail').classList.add('open');\n}\ndocument.getElementById('md-close').addEventListener('click', function() {\n document.getElementById('map-detail').classList.remove('open'); mSelectedId = null; drawMap();\n});\n\n// ── Map toolbar ──────────────────────────────────────────────────────────────\ndocument.getElementById('btn-labels').addEventListener('click', function() {\n mShowLabels = !mShowLabels; this.classList.toggle('active', mShowLabels); drawMap();\n});\ndocument.getElementById('btn-all-labels').addEventListener('click', function() {\n mShowAllLabels = !mShowAllLabels; this.classList.toggle('active', mShowAllLabels);\n if (mShowAllLabels && !mShowLabels) { mShowLabels = true; document.getElementById('btn-labels').classList.add('active'); }\n drawMap();\n});\ndocument.getElementById('btn-quality').addEventListener('click', function() {\n mShowQuality = !mShowQuality; this.classList.toggle('active', mShowQuality); drawMap();\n});\ndocument.getElementById('btn-connect').addEventListener('click', function() {\n mConnectMode = !mConnectMode; mConnectFirst = null;\n this.classList.toggle('active', mConnectMode);\n mapWrap.classList.toggle('connecting', mConnectMode);\n document.getElementById('map-connect-hint').style.display = mConnectMode ? 'block' : 'none'; drawMap();\n});\n\n// Map keyboard\nmapWrap.addEventListener('keydown', function(e) {\n if (e.key === 'ArrowLeft') { mvx += 40; drawMap(); }\n else if (e.key === 'ArrowRight') { mvx -= 40; drawMap(); }\n else if (e.key === 'ArrowUp') { mvy += 40; drawMap(); }\n else if (e.key === 'ArrowDown') { mvy -= 40; drawMap(); }\n else if (e.key === '+' || e.key === '=') mApplyZoom(1.2, mW / 2, mH / 2);\n else if (e.key === '-') mApplyZoom(1 / 1.2, mW / 2, mH / 2);\n else if (e.key === 'Escape') {\n mSelectedId = null; document.getElementById('map-detail').classList.remove('open');\n if (mConnectMode) { mConnectMode = false; mConnectFirst = null; mapWrap.classList.remove('connecting'); document.getElementById('map-connect-hint').style.display = 'none'; document.getElementById('btn-connect').classList.remove('active'); }\n drawMap();\n }\n});\n\n// Map init\nresizeMap(); mapFitToView();\ndocument.getElementById('map-zoom-pct').textContent = Math.round(mScale * 100) + '%';\ndrawMap();\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// TOPOLOGY VIEW (lazy init)\n// ═══════════════════════════════════════════════════════════════════════════════\nvar TOPO = ${graphData};\n\nvar TYPE_COLORS = {\n host:'#4a9eff',database_server:'#ff6b6b',database:'#ff8c42',\n web_service:'#6bcb77',api_endpoint:'#4d96ff',cache_server:'#ffd93d',\n message_broker:'#c77dff',queue:'#e0aaff',topic:'#9d4edd',\n container:'#48cae4',pod:'#00b4d8',k8s_cluster:'#0077b6',\n config_file:'#adb5bd',saas_tool:'#c084fc',table:'#f97316',unknown:'#6c757d'\n};\nvar LAYER_COLORS = { saas:'#c084fc',web:'#6bcb77',data:'#ff6b6b',messaging:'#c77dff',infra:'#4a9eff',config:'#adb5bd',other:'#6c757d' };\nvar LAYER_NAMES = { saas:'SaaS Tools',web:'Web / API',data:'Data Layer',messaging:'Messaging',infra:'Infrastructure',config:'Config',other:'Other' };\n\nvar topoSelectedId = null;\n\nfunction setTopoSearch(q) {\n var el = document.getElementById('topo-search');\n if (el) { el.value = q; buildTopoList(q); }\n}\n\nfunction buildTopoList(filter) {\n var listEl = document.getElementById('topo-list');\n var q = (filter || '').toLowerCase();\n listEl.innerHTML = '';\n var sorted = TOPO.nodes.slice().sort(function(a, b) { return a.name.localeCompare(b.name); });\n for (var i = 0; i < sorted.length; i++) {\n var d = sorted[i];\n if (q && !d.name.toLowerCase().includes(q) && !d.type.includes(q) && !d.id.toLowerCase().includes(q)) continue;\n var item = document.createElement('div');\n item.className = 'topo-item' + (d.id === topoSelectedId ? ' active' : '');\n item.dataset.id = d.id;\n var color = TYPE_COLORS[d.type] || '#aaa';\n item.innerHTML = '<span class=\"topo-dot\" style=\"background:' + color + '\"></span>' +\n '<span class=\"topo-name\" title=\"' + d.id + '\">' + d.name + '</span>' +\n '<span class=\"topo-type\">' + d.type.replace(/_/g, ' ') + '</span>';\n (function(dd) { item.onclick = function() { selectTopoNode(dd); focusTopoNode(dd); }; })(d);\n listEl.appendChild(item);\n }\n}\n\ndocument.getElementById('topo-search').addEventListener('input', function(e) { buildTopoList(e.target.value); });\n\nvar topoSidebar = document.getElementById('topo-sidebar');\n\nfunction selectTopoNode(d) {\n topoSelectedId = d.id;\n buildTopoList(document.getElementById('topo-search').value);\n showTopoNode(d);\n if (typeof d3 !== 'undefined') d3.selectAll('.node-hex').classed('selected', function(nd) { return nd.id === d.id; });\n}\n\nfunction showTopoNode(d) {\n var c = TYPE_COLORS[d.type] || '#aaa';\n var confPct = Math.round(d.confidence * 100);\n var tags = (d.tags || []).map(function(t) { return '<span class=\"tag\">' + t + '</span>'; }).join('');\n var metaRows = Object.entries(d.metadata || {})\n .filter(function(kv) { return kv[1] !== null && kv[1] !== undefined && String(kv[1]).length > 0; })\n .map(function(kv) { return '<tr><td>' + kv[0] + '</td><td>' + JSON.stringify(kv[1]) + '</td></tr>'; }).join('');\n var related = TOPO.links.filter(function(l) {\n return (l.source.id || l.source) === d.id || (l.target.id || l.target) === d.id;\n });\n var edgeItems = related.map(function(l) {\n var isOut = (l.source.id || l.source) === d.id;\n var other = isOut ? (l.target.id || l.target) : (l.source.id || l.source);\n return '<div class=\"edge-item\">' + (isOut ? '\\\\u2192' : '\\\\u2190') + ' <span>' + other + '</span> <small>[' + l.relationship + ']</small></div>';\n }).join('');\n\n topoSidebar.innerHTML =\n '<h2>' + d.name + '</h2>' +\n '<table class=\"meta-table\">' +\n '<tr><td>ID</td><td style=\"font-size:10px;word-break:break-all\">' + d.id + '</td></tr>' +\n '<tr><td>Type</td><td><span style=\"color:' + c + '\">' + d.type + '</span></td></tr>' +\n '<tr><td>Layer</td><td>' + d.layer + '</td></tr>' +\n '<tr><td>Confidence</td><td>' + confPct + '% <div class=\"conf-bar\"><div class=\"conf-fill\" style=\"width:' + confPct + '%;background:' + c + '\"></div></div></td></tr>' +\n '<tr><td>Via</td><td>' + (d.discoveredVia || '\\\\u2014') + '</td></tr>' +\n '<tr><td>Timestamp</td><td>' + (d.discoveredAt ? d.discoveredAt.substring(0, 19).replace('T', ' ') : '\\\\u2014') + '</td></tr>' +\n (tags ? '<tr><td>Tags</td><td>' + tags + '</td></tr>' : '') +\n metaRows + '</table>' +\n (related.length > 0 ? '<div class=\"edges-list\"><strong>Connections (' + related.length + '):</strong>' + edgeItems + '</div>' : '') +\n '<div style=\"margin-top:14px\"><button class=\"export-btn\" style=\"width:100%\" onclick=\"deleteTopoNode(\\\\'' + d.id.replace(/'/g, \"\\\\\\\\'\") + '\\\\')\">Delete node</button></div>';\n}\n\nfunction deleteTopoNode(id) {\n var idx = TOPO.nodes.findIndex(function(n) { return n.id === id; });\n if (idx === -1) return;\n TOPO.nodes.splice(idx, 1);\n TOPO.links = TOPO.links.filter(function(l) {\n return (l.source.id || l.source) !== id && (l.target.id || l.target) !== id;\n });\n topoSelectedId = null;\n topoSidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Node deleted.</p>';\n if (typeof rebuildTopoGraph === 'function') rebuildTopoGraph();\n buildTopoList(document.getElementById('topo-search').value);\n}\n\nfunction initTopology() {\n if (typeof d3 === 'undefined') return;\n\n var svgEl = d3.select('#topo-graph svg');\n var graphDiv = document.getElementById('topo-graph');\n var gW = function() { return graphDiv.clientWidth; };\n var gH = function() { return graphDiv.clientHeight; };\n var g = svgEl.append('g');\n\n svgEl.append('defs').append('marker')\n .attr('id', 'arrow').attr('viewBox', '0 0 10 6')\n .attr('refX', 10).attr('refY', 3)\n .attr('markerWidth', 8).attr('markerHeight', 6)\n .attr('orient', 'auto')\n .append('path').attr('d', 'M0,0 L10,3 L0,6 Z').attr('fill', '#555');\n\n var currentZoom = 1;\n var zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', function(e) {\n g.attr('transform', e.transform); currentZoom = e.transform.k; updateTopoLOD(currentZoom);\n });\n svgEl.call(zoomBehavior);\n\n // Layer filters\n var layers = Array.from(new Set(TOPO.nodes.map(function(d) { return d.layer; })));\n var layerVisible = {};\n layers.forEach(function(l) { layerVisible[l] = true; });\n\n var toolbarEl = document.getElementById('topo-toolbar');\n layers.forEach(function(layer) {\n var btn = document.createElement('button');\n btn.className = 'filter-btn';\n btn.innerHTML = '<span class=\"filter-dot\" style=\"background:' + (LAYER_COLORS[layer] || '#666') + '\"></span>' + (LAYER_NAMES[layer] || layer);\n btn.onclick = function() { layerVisible[layer] = !layerVisible[layer]; btn.classList.toggle('off', !layerVisible[layer]); updateTopoVisibility(); };\n toolbarEl.appendChild(btn);\n });\n\n // JGF export button\n var jgfBtn = document.createElement('button');\n jgfBtn.className = 'export-btn'; jgfBtn.textContent = '\\\\u2193 JGF'; jgfBtn.title = 'Export JSON Graph Format';\n jgfBtn.onclick = function() {\n var jgf = { graph: { directed: true, type: 'cartography', label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(TOPO.nodes.map(function(n) { return [n.id, { label: n.name, metadata: { type: n.type, layer: n.layer, confidence: n.confidence, discoveredVia: n.discoveredVia, discoveredAt: n.discoveredAt, tags: n.tags } }]; })),\n edges: TOPO.links.map(function(l) { return { source: l.source.id || l.source, target: l.target.id || l.target, relation: l.relationship, metadata: { confidence: l.confidence, evidence: l.evidence } }; })\n }};\n var blob = new Blob([JSON.stringify(jgf, null, 2)], { type: 'application/json' });\n var url = URL.createObjectURL(blob);\n var a = document.createElement('a'); a.href = url; a.download = 'cartography-graph.jgf.json'; a.click();\n URL.revokeObjectURL(url);\n };\n toolbarEl.appendChild(jgfBtn);\n\n // Hex helpers\n var T_HEX = { saas_tool: 16, host: 18, database_server: 18, k8s_cluster: 20, default: 14 };\n function tHexSize(d) { return T_HEX[d.type] || T_HEX.default; }\n function tHexPath(size) {\n var pts = [];\n for (var i = 0; i < 6; i++) {\n var angle = (Math.PI / 3) * i - Math.PI / 6;\n pts.push([size * Math.cos(angle), size * Math.sin(angle)]);\n }\n return 'M' + pts.map(function(p) { return p.join(','); }).join('L') + 'Z';\n }\n\n // Cluster force\n function clusterForce(alpha) {\n var centroids = {}, counts = {};\n TOPO.nodes.forEach(function(d) {\n if (!centroids[d.layer]) { centroids[d.layer] = { x: 0, y: 0 }; counts[d.layer] = 0; }\n centroids[d.layer].x += d.x || 0; centroids[d.layer].y += d.y || 0; counts[d.layer]++;\n });\n for (var l in centroids) { centroids[l].x /= counts[l]; centroids[l].y /= counts[l]; }\n var strength = alpha * 0.15;\n TOPO.nodes.forEach(function(d) {\n var cn = centroids[d.layer];\n if (cn) { d.vx += (cn.x - d.x) * strength; d.vy += (cn.y - d.y) * strength; }\n });\n }\n\n // Hulls\n var hullGroup = g.append('g').attr('class', 'hulls');\n var hullPaths = {}, hullLabels = {};\n layers.forEach(function(layer) {\n hullPaths[layer] = hullGroup.append('path').attr('class', 'hull')\n .attr('fill', LAYER_COLORS[layer] || '#666').attr('stroke', LAYER_COLORS[layer] || '#666');\n hullLabels[layer] = hullGroup.append('text').attr('class', 'hull-label')\n .attr('fill', LAYER_COLORS[layer] || '#666').text(LAYER_NAMES[layer] || layer);\n });\n\n function updateHulls() {\n layers.forEach(function(layer) {\n if (!layerVisible[layer]) { hullPaths[layer].attr('d', null); hullLabels[layer].attr('x', -9999); return; }\n var pts = TOPO.nodes.filter(function(d) { return d.layer === layer && layerVisible[d.layer]; }).map(function(d) { return [d.x, d.y]; });\n if (pts.length < 3) {\n hullPaths[layer].attr('d', null);\n if (pts.length > 0) hullLabels[layer].attr('x', pts[0][0]).attr('y', pts[0][1] - 30);\n else hullLabels[layer].attr('x', -9999);\n return;\n }\n var hull = d3.polygonHull(pts);\n if (!hull) { hullPaths[layer].attr('d', null); return; }\n var cx = d3.mean(hull, function(p) { return p[0]; });\n var cy = d3.mean(hull, function(p) { return p[1]; });\n var padded = hull.map(function(p) {\n var dx = p[0] - cx, dy = p[1] - cy;\n var len = Math.sqrt(dx * dx + dy * dy) || 1;\n return [p[0] + dx / len * 40, p[1] + dy / len * 40];\n });\n hullPaths[layer].attr('d', 'M' + padded.join('L') + 'Z');\n hullLabels[layer].attr('x', cx).attr('y', cy - d3.max(hull, function(p) { return Math.abs(p[1] - cy); }) - 30);\n });\n }\n\n // Graph\n var linkSel, linkLabelSel, nodeSel, nodeLabelSel, sim;\n var linkGroup = g.append('g');\n var nodeGroup = g.append('g');\n\n function focusTopoNode(d) {\n if (!d.x || !d.y) return;\n var w = gW(), h = gH();\n svgEl.transition().duration(500).call(\n zoomBehavior.transform,\n d3.zoomIdentity.translate(w / 2, h / 2).scale(Math.min(3, currentZoom < 1 ? 1.5 : currentZoom)).translate(-d.x, -d.y)\n );\n }\n window.focusTopoNode = focusTopoNode;\n\n function rebuildTopoGraph() {\n if (sim) sim.stop();\n\n linkSel = linkGroup.selectAll('line').data(TOPO.links, function(d) { return (d.source.id || d.source) + '>' + (d.target.id || d.target); });\n linkSel.exit().remove();\n var linkEnter = linkSel.enter().append('line').attr('class', 'link');\n linkSel = linkEnter.merge(linkSel)\n .attr('stroke', function(d) { return d.confidence < 0.6 ? '#2a2e35' : '#3d434b'; })\n .attr('stroke-dasharray', function(d) { return d.confidence < 0.6 ? '4 3' : null; })\n .attr('stroke-width', function(d) { return d.confidence < 0.6 ? 0.8 : 1.2; })\n .attr('marker-end', 'url(#arrow)');\n linkSel.select('title').remove();\n linkSel.append('title').text(function(d) { return d.relationship + ' (' + Math.round(d.confidence * 100) + '%)\\\\n' + (d.evidence || ''); });\n\n linkLabelSel = linkGroup.selectAll('text').data(TOPO.links, function(d) { return (d.source.id || d.source) + '>' + (d.target.id || d.target); });\n linkLabelSel.exit().remove();\n linkLabelSel = linkLabelSel.enter().append('text').attr('class', 'link-label').merge(linkLabelSel).text(function(d) { return d.relationship; });\n\n nodeSel = nodeGroup.selectAll('g').data(TOPO.nodes, function(d) { return d.id; });\n nodeSel.exit().remove();\n var nodeEnter = nodeSel.enter().append('g')\n .call(d3.drag()\n .on('start', function(e, d) { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })\n .on('drag', function(e, d) { d.fx = e.x; d.fy = e.y; })\n .on('end', function(e, d) { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })\n )\n .on('click', function(e, d) { e.stopPropagation(); selectTopoNode(d); });\n nodeEnter.append('path').attr('class', 'node-hex');\n nodeEnter.append('title');\n nodeEnter.append('text').attr('class', 'node-label').attr('text-anchor', 'middle');\n\n nodeSel = nodeEnter.merge(nodeSel);\n nodeSel.select('.node-hex')\n .attr('d', function(d) { return tHexPath(tHexSize(d)); })\n .attr('fill', function(d) { return TYPE_COLORS[d.type] || '#aaa'; })\n .attr('stroke', function(d) { var c = d3.color(TYPE_COLORS[d.type] || '#aaa'); return c ? c.brighter(0.8).formatHex() : '#ccc'; })\n .attr('fill-opacity', function(d) { return 0.6 + d.confidence * 0.4; })\n .classed('selected', function(d) { return d.id === topoSelectedId; });\n nodeSel.select('title').text(function(d) { return d.name + ' (' + d.type + ')\\\\nconf: ' + Math.round(d.confidence * 100) + '%'; });\n nodeLabelSel = nodeSel.select('.node-label')\n .attr('dy', function(d) { return tHexSize(d) + 13; })\n .text(function(d) { return d.name.length > 20 ? d.name.substring(0, 18) + '\\\\u2026' : d.name; });\n\n sim = d3.forceSimulation(TOPO.nodes)\n .force('link', d3.forceLink(TOPO.links).id(function(d) { return d.id; }).distance(function(d) { return d.relationship === 'contains' ? 50 : 100; }).strength(0.4))\n .force('charge', d3.forceManyBody().strength(-280))\n .force('center', d3.forceCenter(gW() / 2, gH() / 2))\n .force('collision', d3.forceCollide().radius(function(d) { return tHexSize(d) + 10; }))\n .force('cluster', clusterForce)\n .on('tick', function() {\n updateHulls();\n linkSel.attr('x1', function(d) { return d.source.x; }).attr('y1', function(d) { return d.source.y; })\n .attr('x2', function(d) { return d.target.x; }).attr('y2', function(d) { return d.target.y; });\n linkLabelSel.attr('x', function(d) { return (d.source.x + d.target.x) / 2; })\n .attr('y', function(d) { return (d.source.y + d.target.y) / 2 - 4; });\n nodeSel.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });\n });\n }\n window.rebuildTopoGraph = rebuildTopoGraph;\n\n function updateTopoLOD(k) {\n if (nodeLabelSel) nodeLabelSel.style('opacity', k > 0.5 ? Math.min(1, (k - 0.5) * 2) : 0);\n if (linkLabelSel) linkLabelSel.style('opacity', k > 1.2 ? Math.min(1, (k - 1.2) * 3) : 0);\n d3.selectAll('.hull-label').style('font-size', k < 0.4 ? '18px' : '13px');\n }\n\n function updateTopoVisibility() {\n if (!nodeSel) return;\n nodeSel.style('display', function(d) { return layerVisible[d.layer] ? null : 'none'; });\n linkSel.style('display', function(d) {\n var s = TOPO.nodes.find(function(n) { return n.id === (d.source.id || d.source); });\n var t = TOPO.nodes.find(function(n) { return n.id === (d.target.id || d.target); });\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n linkLabelSel.style('display', function(d) {\n var s = TOPO.nodes.find(function(n) { return n.id === (d.source.id || d.source); });\n var t = TOPO.nodes.find(function(n) { return n.id === (d.target.id || d.target); });\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n }\n\n rebuildTopoGraph();\n buildTopoList();\n updateTopoLOD(1);\n\n svgEl.on('click', function() {\n topoSelectedId = null;\n d3.selectAll('.node-hex').classed('selected', false);\n buildTopoList(document.getElementById('topo-search').value);\n topoSidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Click a node to view details.</p>';\n });\n}\n\n// Init topology node list (non-D3 part)\nbuildTopoList();\n<\\/script>\n</body>\n</html>`;\n}\n\n// ── JGF Export ────────────────────────────────────────────────────────────────\n\nexport function exportJGF(nodes: NodeRow[], edges: EdgeRow[]): string {\n const jgf = {\n graph: {\n directed: true,\n type: 'cartography',\n label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(nodes.map(n => [n.id, {\n label: n.name,\n metadata: {\n type: n.type, layer: nodeLayer(n.type),\n confidence: n.confidence, discoveredVia: n.discoveredVia,\n discoveredAt: n.discoveredAt, tags: n.tags, metadata: n.metadata,\n },\n }])),\n edges: edges.map(e => ({\n source: e.sourceId, target: e.targetId,\n relation: e.relationship,\n metadata: { confidence: e.confidence, evidence: e.evidence },\n })),\n },\n };\n return JSON.stringify(jgf, null, 2);\n}\n\n// ── exportAll ─────────────────────────────────────────────────────────────────\n\nexport function exportAll(\n db: CartographyDB,\n sessionId: string,\n outputDir: string,\n formats: string[] = ['mermaid', 'json', 'yaml', 'html', 'map', 'discovery', 'sops'],\n): void {\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(join(outputDir, 'sops'), { recursive: true });\n mkdirSync(join(outputDir, 'workflows'), { recursive: true });\n\n const nodes = db.getNodes(sessionId);\n const edges = db.getEdges(sessionId);\n\n // Always export JGF to well-known path (overwritten each run)\n const jgfPath = join(outputDir, 'cartography-graph.jgf.json');\n writeFileSync(jgfPath, exportJGF(nodes, edges));\n process.stderr.write('✓ cartography-graph.jgf.json\\n');\n\n if (formats.includes('mermaid')) {\n writeFileSync(join(outputDir, 'topology.mermaid'), generateTopologyMermaid(nodes, edges));\n writeFileSync(join(outputDir, 'dependencies.mermaid'), generateDependencyMermaid(nodes, edges));\n process.stderr.write('✓ topology.mermaid, dependencies.mermaid\\n');\n }\n\n if (formats.includes('json')) {\n writeFileSync(join(outputDir, 'catalog.json'), exportJSON(db, sessionId));\n process.stderr.write('✓ catalog.json\\n');\n }\n\n if (formats.includes('yaml')) {\n writeFileSync(join(outputDir, 'catalog-info.yaml'), exportBackstageYAML(nodes, edges));\n process.stderr.write('✓ catalog-info.yaml\\n');\n }\n\n if (formats.includes('html') || formats.includes('map') || formats.includes('discovery')) {\n writeFileSync(join(outputDir, 'discovery.html'), exportDiscoveryApp(nodes, edges));\n process.stderr.write('✓ discovery.html\\n');\n }\n\n if (formats.includes('sops')) {\n const sops = db.getSOPs(sessionId);\n for (const sop of sops) {\n const filename = sop.title.toLowerCase().replace(/[^a-z0-9]+/g, '-') + '.md';\n writeFileSync(join(outputDir, 'sops', filename), exportSOPMarkdown(sop));\n\n const wfFilename = `workflow-${sop.workflowId.substring(0, 8)}.mermaid`;\n writeFileSync(join(outputDir, 'workflows', wfFilename), generateWorkflowMermaid(sop));\n }\n if (sops.length > 0) {\n process.stderr.write(`✓ ${sops.length} SOPs + workflow diagrams\\n`);\n }\n }\n}\n","/**\n * Hex Grid Engine — flat-top axial coordinate system.\n * Reference: https://www.redblobgames.com/grids/hexagons/\n */\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AxialCoord {\n q: number;\n r: number;\n}\n\nexport interface PixelCoord {\n x: number;\n y: number;\n}\n\n// ── Geometry ─────────────────────────────────────────────────────────────────\n\n/**\n * Convert axial hex coordinates to pixel coordinates (flat-top).\n */\nexport function hexToPixel(q: number, r: number, size: number): PixelCoord {\n const x = size * (3 / 2 * q);\n const y = size * (Math.sqrt(3) / 2 * q + Math.sqrt(3) * r);\n return { x, y };\n}\n\n/**\n * Convert pixel coordinates to nearest axial hex (flat-top).\n */\nexport function pixelToHex(x: number, y: number, size: number): AxialCoord {\n const q = (2 / 3 * x) / size;\n const r = (-1 / 3 * x + Math.sqrt(3) / 3 * y) / size;\n return hexRound(q, r);\n}\n\n/**\n * Round floating-point axial coordinates to the nearest hex.\n */\nexport function hexRound(q: number, r: number): AxialCoord {\n const s = -q - r;\n let rq = Math.round(q);\n let rr = Math.round(r);\n const rs = Math.round(s);\n const dq = Math.abs(rq - q);\n const dr = Math.abs(rr - r);\n const ds = Math.abs(rs - s);\n if (dq > dr && dq > ds) {\n rq = -rr - rs;\n } else if (dr > ds) {\n rr = -rq - rs;\n }\n // Normalize -0 to 0\n return { q: rq || 0, r: rr || 0 };\n}\n\n/**\n * Return the 6 pixel corners of a flat-top hexagon.\n */\nexport function hexCorners(cx: number, cy: number, size: number): PixelCoord[] {\n const corners: PixelCoord[] = [];\n for (let i = 0; i < 6; i++) {\n const angle = (Math.PI / 180) * (60 * i); // flat-top: start at 0°\n corners.push({\n x: cx + size * Math.cos(angle),\n y: cy + size * Math.sin(angle),\n });\n }\n return corners;\n}\n\n// ── Neighbors & Distance ─────────────────────────────────────────────────────\n\nconst HEX_DIRECTIONS: AxialCoord[] = [\n { q: 1, r: 0 }, { q: 1, r: -1 }, { q: 0, r: -1 },\n { q: -1, r: 0 }, { q: -1, r: 1 }, { q: 0, r: 1 },\n];\n\nexport function hexNeighbors(q: number, r: number): AxialCoord[] {\n return HEX_DIRECTIONS.map(d => ({ q: q + d.q, r: r + d.r }));\n}\n\nexport function hexDistance(a: AxialCoord, b: AxialCoord): number {\n return (Math.abs(a.q - b.q) + Math.abs(a.q + a.r - b.q - b.r) + Math.abs(a.r - b.r)) / 2;\n}\n\n/**\n * Generate all hex coordinates on a given ring around center.\n */\nexport function hexRing(center: AxialCoord, radius: number): AxialCoord[] {\n if (radius === 0) return [{ q: center.q, r: center.r }];\n const results: AxialCoord[] = [];\n let hex = {\n q: center.q + HEX_DIRECTIONS[4].q * radius,\n r: center.r + HEX_DIRECTIONS[4].r * radius,\n };\n for (let side = 0; side < 6; side++) {\n for (let step = 0; step < radius; step++) {\n results.push({ q: hex.q, r: hex.r });\n hex = {\n q: hex.q + HEX_DIRECTIONS[side].q,\n r: hex.r + HEX_DIRECTIONS[side].r,\n };\n }\n }\n return results;\n}\n\n/**\n * Generate a spiral sequence of hex positions (ring 0, ring 1, ring 2, …).\n * Returns exactly `count` positions.\n */\nexport function hexSpiral(center: AxialCoord, count: number): AxialCoord[] {\n const positions: AxialCoord[] = [];\n let ring = 0;\n while (positions.length < count) {\n const ringPositions = hexRing(center, ring);\n for (const pos of ringPositions) {\n if (positions.length >= count) break;\n positions.push(pos);\n }\n ring++;\n }\n return positions;\n}\n","/**\n * Domain-based clustering and hex grid positioning.\n * Groups data assets by domain, assigns organic hex positions via spiral fill,\n * computes cluster centroids and assigns colors from the domain palette.\n */\n\nimport { hexSpiral, hexToPixel, hexDistance, type AxialCoord } from './hex.js';\nimport type { DataAsset, Cluster } from './types.js';\nimport { DOMAIN_COLORS, DOMAIN_PALETTE } from './types.js';\n\n// ── Color Assignment ──────────────────────────────────────────────────────────\n\n/**\n * Assign a deterministic color from the palette to a domain name.\n * Uses the predefined DOMAIN_COLORS map first, then falls back to the palette.\n */\nexport function assignColor(domain: string, allDomains: string[]): string {\n if (DOMAIN_COLORS[domain]) return DOMAIN_COLORS[domain];\n const idx = allDomains.indexOf(domain);\n return DOMAIN_PALETTE[idx % DOMAIN_PALETTE.length];\n}\n\n/**\n * Assign colors to all domains in the dataset.\n */\nexport function assignColors(domains: string[]): Record<string, string> {\n const result: Record<string, string> = {};\n for (const d of domains) {\n result[d] = assignColor(d, domains);\n }\n return result;\n}\n\n/**\n * Generate a slightly lighter shade of a hex color string.\n */\nexport function shadeVariant(hex: string, amount: number): string {\n const num = parseInt(hex.replace('#', ''), 16);\n const r = Math.min(255, (num >> 16) + amount);\n const g = Math.min(255, ((num >> 8) & 0xff) + amount);\n const b = Math.min(255, (num & 0xff) + amount);\n return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;\n}\n\n// ── Grouping ──────────────────────────────────────────────────────────────────\n\n/**\n * Group assets by their `domain` field.\n */\nexport function groupByDomain(assets: DataAsset[]): Map<string, DataAsset[]> {\n const map = new Map<string, DataAsset[]>();\n for (const a of assets) {\n const d = a.domain || 'Other';\n if (!map.has(d)) map.set(d, []);\n map.get(d)!.push(a);\n }\n return map;\n}\n\n// ── Cluster Layout ────────────────────────────────────────────────────────────\n\nconst CLUSTER_GAP = 3; // min hex distance between cluster borders\n\n/**\n * Arrange domain clusters on the hex grid without overlap.\n * Places largest clusters first at the origin, subsequent clusters spiral outward.\n */\nexport function layoutClusters(\n groups: Map<string, DataAsset[]>,\n hexSize: number,\n): { clusters: Cluster[]; assets: DataAsset[] } {\n const allDomains = Array.from(groups.keys());\n const colors = assignColors(allDomains);\n\n // Sort domains by size descending (largest first → center)\n const sorted = Array.from(groups.entries())\n .sort((a, b) => b[1].length - a[1].length);\n\n const occupied = new Set<string>();\n const key = (q: number, r: number) => `${q},${r}`;\n\n const clusters: Cluster[] = [];\n const allAssets: DataAsset[] = [];\n\n for (const [domain, domainAssets] of sorted) {\n // Find a free origin for this cluster\n const origin = clusters.length === 0\n ? { q: 0, r: 0 }\n : findFreeOrigin(occupied, domainAssets.length, CLUSTER_GAP);\n\n // Pack assets in a spiral around the origin\n const positions = hexSpiral(origin, domainAssets.length);\n\n const assetIds: string[] = [];\n for (let i = 0; i < domainAssets.length; i++) {\n const asset = domainAssets[i];\n asset.position = positions[i];\n assetIds.push(asset.id);\n occupied.add(key(positions[i].q, positions[i].r));\n allAssets.push(asset);\n }\n\n const centroid = computeCentroid(positions, hexSize);\n\n clusters.push({\n id: `cluster:${domain}`,\n label: domain,\n domain,\n color: colors[domain],\n assetIds,\n centroid,\n });\n }\n\n return { clusters, assets: allAssets };\n}\n\n/**\n * Find a cluster origin that doesn't overlap any occupied hexes.\n */\nfunction findFreeOrigin(\n occupied: Set<string>,\n count: number,\n gap: number,\n): AxialCoord {\n const key = (q: number, r: number) => `${q},${r}`;\n\n // Search in expanding rings around the global origin\n for (let searchRadius = 1; searchRadius < 100; searchRadius++) {\n const candidates = hexSpiral({ q: 0, r: 0 }, 1 + 6 * searchRadius * (searchRadius + 1) / 2);\n\n for (const candidate of candidates) {\n const testPositions = hexSpiral(candidate, count);\n let fits = true;\n\n for (const tp of testPositions) {\n // Check that the cell itself and its gap neighbors are free\n if (occupied.has(key(tp.q, tp.r))) { fits = false; break; }\n\n for (const oKey of occupied) {\n const [oq, or] = oKey.split(',').map(Number);\n if (hexDistance(tp, { q: oq, r: or }) < gap) {\n fits = false;\n break;\n }\n }\n if (!fits) break;\n }\n\n if (fits) return candidate;\n }\n }\n\n // Fallback\n return { q: occupied.size * 5, r: 0 };\n}\n\n// ── Centroid ──────────────────────────────────────────────────────────────────\n\nexport function computeCentroid(positions: AxialCoord[], hexSize: number): { x: number; y: number } {\n if (positions.length === 0) return { x: 0, y: 0 };\n let sx = 0, sy = 0;\n for (const { q, r } of positions) {\n const { x, y } = hexToPixel(q, r, hexSize);\n sx += x;\n sy += y;\n }\n return { x: sx / positions.length, y: sy / positions.length };\n}\n\n// ── Cluster Bounds ────────────────────────────────────────────────────────────\n\nexport function computeClusterBounds(\n assets: DataAsset[],\n hexSize: number,\n): { minX: number; minY: number; maxX: number; maxY: number } {\n if (assets.length === 0) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const a of assets) {\n const { x, y } = hexToPixel(a.position.q, a.position.r, hexSize);\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n }\n return { minX, minY, maxX, maxY };\n}\n","/**\n * Node-zu-Asset Mapping.\n * Converts existing DiscoveryNode/Edge data into the CartographyMap data model.\n */\n\nimport type { NodeRow, EdgeRow, DataAsset, Connection, CartographyMapData } from './types.js';\nimport { layoutClusters, groupByDomain } from './cluster.js';\n\n// ── Domain Mapping ───────────────────────────────────────────────────────────\n\nconst TYPE_TO_DOMAIN: Record<string, string> = {\n database_server: 'Data Layer',\n database: 'Data Layer',\n table: 'Data Layer',\n cache_server: 'Data Layer',\n web_service: 'Web / API',\n api_endpoint: 'Web / API',\n message_broker: 'Messaging',\n queue: 'Messaging',\n topic: 'Messaging',\n host: 'Infrastructure',\n container: 'Infrastructure',\n pod: 'Infrastructure',\n k8s_cluster: 'Infrastructure',\n config_file: 'Infrastructure',\n saas_tool: 'SaaS Tools',\n};\n\n/**\n * Determine the domain for a node.\n * Priority: explicit node.domain > metadata.category > tag-based > type-based > \"Other\"\n */\nfunction resolveDomain(node: NodeRow): string {\n // 1. Explicit domain field\n if (node.domain) return node.domain;\n\n // 2. Metadata category\n const meta = node.metadata as Record<string, unknown>;\n if (typeof meta['category'] === 'string' && meta['category'].length > 0) {\n return meta['category'];\n }\n\n // 3. Tags — use first tag that looks like a domain\n for (const tag of node.tags ?? []) {\n if (tag.length > 2 && tag[0] === tag[0].toUpperCase()) {\n return tag;\n }\n }\n\n // 4. Type-based mapping\n return TYPE_TO_DOMAIN[node.type] ?? 'Other';\n}\n\n// ── Conversion ───────────────────────────────────────────────────────────────\n\n/**\n * Convert NodeRow[] to DataAsset[].\n */\nexport function nodesToAssets(nodes: NodeRow[]): DataAsset[] {\n return nodes.map(n => ({\n id: n.id,\n name: n.name,\n domain: resolveDomain(n),\n subDomain: n.subDomain,\n qualityScore: n.qualityScore ?? Math.round(n.confidence * 100),\n metadata: n.metadata ?? {},\n position: { q: 0, r: 0 }, // will be assigned by layoutClusters\n }));\n}\n\n/**\n * Convert EdgeRow[] to Connection[].\n */\nexport function edgesToConnections(edges: EdgeRow[]): Connection[] {\n return edges.map(e => ({\n id: e.id,\n sourceAssetId: e.sourceId,\n targetAssetId: e.targetId,\n type: e.relationship,\n }));\n}\n\n// ── Full Pipeline ─────────────────────────────────────────────────────────────\n\nconst HEX_SIZE = 24;\n\n/**\n * Build a complete CartographyMapData from raw nodes and edges.\n */\nexport function buildMapData(\n nodes: NodeRow[],\n edges: EdgeRow[],\n options?: { theme?: 'light' | 'dark' },\n): CartographyMapData {\n const rawAssets = nodesToAssets(nodes);\n const connections = edgesToConnections(edges);\n\n if (rawAssets.length === 0) {\n return {\n assets: [],\n clusters: [],\n connections,\n meta: { exportedAt: new Date().toISOString(), theme: options?.theme ?? 'light' },\n };\n }\n\n const groups = groupByDomain(rawAssets);\n const { clusters, assets } = layoutClusters(groups, HEX_SIZE);\n\n return {\n assets,\n clusters,\n connections,\n meta: { exportedAt: new Date().toISOString(), theme: options?.theme ?? 'light' },\n };\n}\n"],"mappings":"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AAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,aA+VZ,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["execSync","existsSync","mkdirSync","join","HEX_SIZE","mkdirSync","join","readFileSync","existsSync","resolve","scanAllBookmarks","saved","NODE_TYPES","execSync","join","dim"]}