@datasynx/agentic-ai-cartography 2.5.0 → 2.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/auth/types.ts","../src/providers/types.ts","../src/safety.ts","../src/audit.ts","../src/providers/claude.ts","../src/providers/shell.ts","../src/providers/zod-schema.ts","../src/providers/audit.ts","../src/providers/loop.ts","../src/providers/openai.ts","../src/providers/ollama.ts","../src/providers/registry.ts","../src/agent.ts","../src/config.ts","../src/schedule.ts","../src/exporter.ts","../src/hex.ts","../src/cluster.ts","../src/mapper.ts","../src/compliance/report.ts","../src/cost.ts","../src/sharing.ts","../src/sync/hash.ts","../src/sync/classify.ts","../src/sync/push.ts","../src/sync/index.ts","../src/installer/format.ts","../src/installer/merge.ts","../src/installer/shapes.ts","../src/installer/entry.ts","../src/installer/install.ts","../src/installer/registry.ts","../src/installer/deeplinks.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { Command } from 'commander';\nimport { checkPrerequisites } from './preflight.js';\nimport { CartographyDB, deriveSessionName, normalizeTenant } from './db.js';\nimport { hashToken } from './auth/identity.js';\nimport { RoleSchema } from './auth/types.js';\nimport { defaultConfig } from './types.js';\nimport type { TopologyDiff } from './types.js';\nimport { runDiscovery } from './agent.js';\nimport type { DiscoveryEvent } from './agent.js';\nimport { runLocalDiscovery } from './discovery/local.js';\nimport { loadConfig, ConfigError } from './config.js';\nimport { runOnce, nextRun } from './schedule.js';\nimport type { ScheduledRunResult } from './schedule.js';\nimport { defaultProviderRegistry } from './providers/registry.js';\nimport type { ProviderName } from './types.js';\nimport { exportAll, generateDiffMermaid, exportComplianceReport } from './exporter.js';\nimport { getRuleset, listRulesets } from './compliance/rulesets/registry.js';\nimport { formatComplianceText } from './compliance/report.js';\nimport { DriftConfigSchema } from './types.js';\nimport { runDrift } from './drift.js';\nimport { enrichCosts, CsvCostSource } from './cost.js';\nimport { readFileSync, existsSync, writeFileSync } from 'fs';\nimport { resolve, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { createInterface } from 'readline';\nimport { IS_WIN, IS_MAC, PLATFORM, commandExists } from './platform.js';\nimport { logInfo, logError, logWarn, setVerbose } from './logger.js';\nimport { cleanupTempFiles } from './bookmarks.js';\nimport { stripSensitive } from './tools.js';\nimport { startMcp } from './mcp/start.js';\nimport { startApi } from './api/start.js';\nimport { SharingLevelSchema } from './types.js';\nimport { loadOrgKey, rotateOrgKey } from './orgkey.js';\nimport { reversePseudonym } from './anonymize.js';\nimport { previewShare } from './sharing.js';\nimport { runSyncClassify, pushDeltas } from './sync/index.js';\nimport type { PushItem } from './sync/push.js';\nimport type { CartographyConfig, PendingShareRow } from './types.js';\nimport { getClient, listClients, planInstall, applyInstall, renderDiff, defaultContext, defaultServerEntry, DEFAULT_SERVER_NAME, cursorDeeplink, vscodeDeeplink, codeAddMcpCommand } from './installer/index.js';\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\n/**\n * Render a {@link TopologyDiff} as human-readable text. Module-scope so both the\n * `diff` command and the `discover --update` (incremental rescan) path can reuse it.\n */\nfunction renderDiffText(d: TopologyDiff): string {\n const out: string[] = [];\n out.push(`${bold('Topology diff')} ${dim(d.base.sessionId.slice(0, 8))} → ${dim(d.current.sessionId.slice(0, 8))}`);\n out.push(` base: ${d.base.nodeCount} nodes, ${d.base.edgeCount} edges ${dim(d.base.startedAt)}`);\n out.push(` current: ${d.current.nodeCount} nodes, ${d.current.edgeCount} edges ${dim(d.current.startedAt)}`);\n out.push('');\n out.push(` nodes: ${green('+' + d.summary.nodesAdded)} ${red('-' + d.summary.nodesRemoved)} ${yellow('~' + d.summary.nodesChanged)} edges: ${green('+' + d.summary.edgesAdded)} ${red('-' + d.summary.edgesRemoved)}`);\n if (d.summary.nodesAdded + d.summary.nodesRemoved + d.summary.nodesChanged + d.summary.edgesAdded + d.summary.edgesRemoved === 0) {\n out.push('');\n out.push(` ${green('✓')} No drift between the two sessions.`);\n return out.join('\\n');\n }\n out.push('');\n for (const n of d.nodes.added) out.push(` ${green('+')} ${n.id} ${dim('(' + n.type + ')')}`);\n for (const n of d.nodes.removed) out.push(` ${red('-')} ${n.id} ${dim('(' + n.type + ')')}`);\n for (const c of d.nodes.changed) out.push(` ${yellow('~')} ${c.id} ${dim('[' + c.changedFields.join(', ') + ']')}`);\n for (const e of d.edges.added) out.push(` ${green('+')} edge ${e.sourceId} ${dim('─' + e.relationship + '→')} ${e.targetId}`);\n for (const e of d.edges.removed) out.push(` ${red('-')} edge ${e.sourceId} ${dim('─' + e.relationship + '→')} ${e.targetId}`);\n return out.join('\\n');\n}\n\n/** One-line human-readable summary of a scheduled drift run (`text` output format). */\nfunction renderDriftSummaryText(r: ScheduledRunResult): string {\n const s = r.delta.summary;\n const base = r.baseSessionId ? r.baseSessionId.slice(0, 8) : '∅';\n return (\n `${green('+' + s.nodesAdded)}/${red('-' + s.nodesRemoved)}/${yellow('~' + s.nodesChanged)} nodes, ` +\n `${green('+' + s.edgesAdded)}/${red('-' + s.edgesRemoved)} edges ` +\n `${dim('(session ' + r.sessionId.slice(0, 8) + ', base ' + base + ')')}`\n );\n}\n\n/**\n * Post-scan central-DB sync hook (2.11). Classifies + enqueues; prints a one-line\n * stderr summary. No-op (and silent) when `centralDb` is unconfigured. Never throws\n * out — a sync failure must not fail the scan that produced the data.\n */\nfunction maybeQueueForSync(\n db: CartographyDB,\n sessionId: string,\n config: CartographyConfig,\n w: (s: string) => void,\n): void {\n if (!config.centralDb?.url) return;\n try {\n const r = runSyncClassify(db, sessionId, config);\n if (r.enqueued > 0) {\n w(` ${cyan('⇪')} ${bold(String(r.enqueued))} item(s) queued for review — run ${bold(\"'datasynx-cartography sync review'\")}\\n`);\n } else if (r.autoShared > 0) {\n w(` ${cyan('⇪')} ${bold(String(r.autoShared))} item(s) auto-approved by policy — run ${bold(\"'datasynx-cartography sync push'\")}\\n`);\n }\n } catch (err) {\n logWarn(`central-DB sync classify skipped: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\nmain();\n\nfunction main(): void {\n // ── Graceful shutdown ──\n let activeDb: CartographyDB | null = null;\n const shutdown = (signal: NodeJS.Signals) => {\n logWarn(`Received ${signal}, shutting down gracefully…`);\n if (activeDb) {\n try { activeDb.close(); } catch { /* already closed */ }\n activeDb = null;\n }\n // Re-raise with the default disposition so the process terminates with the\n // correct signal exit status (e.g. 130 for SIGINT) without process.exit().\n process.removeListener('SIGTERM', shutdown);\n process.removeListener('SIGINT', shutdown);\n process.kill(process.pid, signal);\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n // Clean up orphaned temp files from previous bookmark/history scans\n cleanupTempFiles();\n\n const program = new Command();\n\n const CMD = 'datasynx-cartography';\n const __dirname = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));\n let VERSION = '0.0.0';\n try {\n VERSION = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8')).version ?? VERSION;\n } catch {\n logWarn('Could not read package.json version; falling back to 0.0.0');\n }\n\n program\n .name(CMD)\n .description('AI-powered Infrastructure Discovery & Agentic AI Cartography')\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('--provider <name>', 'Agent provider: claude, openai, ollama (or CARTOGRAPHY_PROVIDER)')\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('--name <name>', 'Custom session name (default: auto-derived from the topology)')\n .option('--update [sessionId]', 'Re-scan an existing session in place (deterministic local scan; default: latest discover session)')\n .option('--output-format <fmt>', 'Progress/result format: text, json, stream-json', 'text')\n .option('-v, --verbose', 'Show agent reasoning', false)\n .action(async (opts) => {\n // ── Provider selection (--provider, then CARTOGRAPHY_PROVIDER, default claude) ──\n const providerName = (opts.provider ?? process.env.CARTOGRAPHY_PROVIDER ?? 'claude') as string;\n if (!defaultProviderRegistry.has(providerName)) {\n process.stderr.write(\n `❌ Unknown provider \"${providerName}\" (valid: ${defaultProviderRegistry.names().join(', ')})\\n`,\n );\n process.exitCode = 2;\n return;\n }\n const provider = providerName as ProviderName;\n\n checkPrerequisites(provider);\n\n // ── Input validation ──\n const parsedDepth = parseInt(opts.depth, 10);\n const parsedMaxTurns = parseInt(opts.maxTurns, 10);\n if (Number.isNaN(parsedDepth) || parsedDepth < 1 || parsedDepth > 50) {\n process.stderr.write(`❌ Invalid --depth: \"${opts.depth}\" (must be 1–50)\\n`);\n process.exitCode = 2;\n return;\n }\n if (Number.isNaN(parsedMaxTurns) || parsedMaxTurns < 1 || parsedMaxTurns > 500) {\n process.stderr.write(`❌ Invalid --max-turns: \"${opts.maxTurns}\" (must be 1–500)\\n`);\n process.exitCode = 2;\n return;\n }\n\n const fmt = (opts.outputFormat ?? 'text') as 'text' | 'json' | 'stream-json';\n if (!['text', 'json', 'stream-json'].includes(fmt)) {\n process.stderr.write(`❌ Invalid --output-format: \"${opts.outputFormat}\" (must be text, json, or stream-json)\\n`);\n process.exitCode = 2;\n return;\n }\n // Headless modes keep stdout machine-readable: human progress (spinner, banners)\n // goes to stderr only, and interactive review/follow-up are skipped entirely.\n const isText = fmt === 'text';\n\n setVerbose(opts.verbose);\n\n const config = defaultConfig({\n entryPoints: opts.entry,\n maxDepth: parsedDepth,\n maxTurns: parsedMaxTurns,\n provider,\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 logInfo('Discovery started', {\n entryPoints: config.entryPoints,\n provider: config.provider,\n model: config.agentModel,\n maxTurns: config.maxTurns,\n maxDepth: config.maxDepth,\n });\n\n const db = new CartographyDB(config.dbPath);\n activeDb = db;\n\n const w = process.stderr.write.bind(process.stderr);\n\n // ── Incremental rescan (2.1): `--update` re-scans an existing session in\n // place via the deterministic local scanner registry, prints the delta,\n // and exits — it never invokes the agent loop or creates a new session. ──\n if (opts.update) {\n const tenantId = normalizeTenant(opts.org);\n const targetId = typeof opts.update === 'string'\n ? opts.update\n : db.getLatestSession('discover', tenantId)?.id;\n const targetSession = targetId ? db.getSession(targetId) : undefined;\n if (!targetId || !targetSession) {\n process.stderr.write(\n `❌ No discover session to update${typeof opts.update === 'string' ? ` (id \"${opts.update}\")` : ''}; run \\`discover\\` first.\\n`,\n );\n process.exitCode = 2;\n db.close();\n activeDb = null;\n return;\n }\n const baseNodeCount = db.getNodes(targetId).length;\n const baseEdgeCount = db.getEdges(targetId).length;\n if (isText) {\n w('\\n');\n w(` ${bold('CARTOGRAPHY')} ${dim('incremental rescan · ' + targetId.slice(0, 8))}\\n`);\n w(dim(' ────────────────────────────────────────────────\\n\\n'));\n }\n try {\n const r = await runLocalDiscovery(db, targetId, { mode: 'update' });\n const updated = db.getSession(targetId);\n const diff: TopologyDiff = {\n base: { sessionId: targetId, startedAt: targetSession.startedAt, nodeCount: baseNodeCount, edgeCount: baseEdgeCount },\n current: { sessionId: targetId, startedAt: updated?.lastScannedAt ?? new Date().toISOString(), nodeCount: r.nodes, edgeCount: r.edges },\n nodes: r.delta?.nodes ?? { added: [], removed: [], changed: [], unchanged: 0 },\n edges: r.delta?.edges ?? { added: [], removed: [], unchanged: 0 },\n summary: r.delta?.summary ?? { nodesAdded: 0, nodesRemoved: 0, nodesChanged: 0, edgesAdded: 0, edgesRemoved: 0 },\n anomalies: { base: [], current: [], added: [] },\n };\n if (fmt === 'text') w(renderDiffText(diff) + '\\n\\n');\n else process.stdout.write(JSON.stringify(diff, null, 2) + '\\n');\n logInfo('Incremental rescan complete', { sessionId: targetId, ...diff.summary });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logError('Incremental rescan failed', { sessionId: targetId, error: errMsg });\n w(`\\n ${bold(red('✗'))} Rescan failed: ${errMsg}\\n`);\n process.exitCode = 1;\n }\n db.close();\n activeDb = null;\n return;\n }\n\n const sessionId = db.createSession('discover', config, opts.org);\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 if (isText) {\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\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 if (!isText) {\n // stream-json emits one JSON object per line on stdout; json buffers (final catalog below).\n if (fmt === 'stream-json') process.stdout.write(JSON.stringify(event) + '\\n');\n return;\n }\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__cartography__', '');\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 if (!isText) return '(Non-interactive mode — please continue without this information)';\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 const rawMsg = err instanceof Error ? err.message : String(err);\n const errMsg = rawMsg.replace(/https?:\\/\\/[^\\s]+/g, u => stripSensitive(u));\n logError('Discovery failed', { sessionId, error: errMsg });\n w(`\\n ${bold('\\x1b[31m✗\\x1b[0m')} Discovery failed: ${errMsg}\\n`);\n db.close();\n activeDb = null;\n process.exitCode = 1;\n return;\n }\n\n stopSpinner();\n db.endSession(sessionId);\n // 2.11 central-DB sync: classify the freshly-scanned topology against the\n // persisted policy and enqueue new/unmatched items for review. No-op unless\n // `centralDb` is configured (config.json / CARTOGRAPHY_CENTRAL_*). This is the\n // manual caller; the scheduled engine (`schedule`) wires the same helper.\n maybeQueueForSync(db, sessionId, config, w);\n // Deterministic, human-friendly session label (override with --name).\n const sessionName = (opts.name as string | undefined)?.trim()\n || deriveSessionName(db.getGraphSummary(sessionId), db.getSession(sessionId)?.startedAt ?? new Date().toISOString());\n db.setSessionName(sessionId, sessionName);\n const stats = db.getStats(sessionId);\n const totalSec = ((Date.now() - startTime) / 1000).toFixed(1);\n\n logInfo('Discovery completed', {\n sessionId,\n nodes: stats.nodes,\n edges: stats.edges,\n durationSec: parseFloat(totalSec),\n });\n\n if (!isText) {\n // Headless: emit machine-readable result on stdout, write artifacts, finish.\n const durationMs = Date.now() - startTime;\n if (fmt === 'stream-json') {\n process.stdout.write(JSON.stringify({ kind: 'result', sessionId, nodes: stats.nodes, edges: stats.edges, durationMs }) + '\\n');\n } else {\n process.stdout.write(JSON.stringify(\n { sessionId, stats, nodes: db.getNodes(sessionId), edges: db.getEdges(sessionId), durationMs },\n null, 2,\n ) + '\\n');\n }\n exportAll(db, sessionId, config.outputDir, ['discovery']);\n db.close();\n activeDb = null;\n return;\n }\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, ['discovery']);\n\n const discoveryPath = resolve(config.outputDir, 'discovery.html');\n w('\\n');\n if (existsSync(discoveryPath)) {\n w(` ${green('✓')} ${bold('discovery.html')} ${dim('← Enterprise Map')}\\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, ['discovery']);\n if (existsSync(discoveryPath)) {\n w(` ${green('✓')} ${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,cost')\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'];\n exportAll(db, session.id, opts.output, formats);\n process.stderr.write(`✓ Exported to: ${opts.output}\\n`);\n\n db.close();\n });\n\n // ── DIFF / DRIFT ───────────────────────────────────────────────────────────\n\n program\n .command('diff [base] [current]')\n .description('Compare two discovery sessions (drift detection). Defaults to the two most recent.')\n .option('--format <fmt>', 'Output format: text, json, mermaid', 'text')\n .option('-o, --output <file>', 'Write to a file instead of stdout')\n .option('--db <path>', 'DB path')\n .action((base: string | undefined, current: string | undefined, opts) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n activeDb = db;\n try {\n // getSessions() is newest-first (rowid DESC): [0] = latest, [1] = previous.\n const sessions = db.getSessions();\n const currentId = current ?? sessions[0]?.id;\n const baseId = base ?? sessions[1]?.id;\n if (!baseId || !currentId) {\n process.stderr.write('❌ Need at least two discovery sessions to diff\\n');\n process.exitCode = 1;\n return;\n }\n if (baseId === currentId) {\n process.stderr.write('❌ Base and current session are the same\\n');\n process.exitCode = 1;\n return;\n }\n const d = db.diffSessions(baseId, currentId);\n const out = opts.format === 'json' ? JSON.stringify(d, null, 2)\n : opts.format === 'mermaid' ? generateDiffMermaid(d)\n : renderDiffText(d);\n if (opts.output) {\n writeFileSync(opts.output, out + '\\n');\n process.stderr.write(`✓ Wrote diff to: ${opts.output}\\n`);\n } else {\n process.stdout.write(out + '\\n');\n }\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n } finally {\n db.close();\n activeDb = null;\n }\n });\n\n // ── COMPLIANCE SCORING (3.4) ─────────────────────────────────────────────────\n // Grade a session against a declarative ruleset (CIS/SOC2/ISO 27001 starter sets).\n // Deterministic, read-only; degrades cleanly (unknown ruleset → exit 1, no throw).\n\n program\n .command('compliance [session-id]')\n .description('Score a session against a compliance ruleset (CIS/SOC2/ISO 27001 starter sets)')\n .option('--ruleset <name>', 'Ruleset: baseline, cis, soc2, iso27001', 'baseline')\n .option('--format <fmt>', 'Output format: text, json, markdown, mermaid', 'text')\n .option('-o, --output <file>', 'Write to a file instead of stdout')\n .option('--db <path>', 'DB path')\n .action((sessionId: string | undefined, opts) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n activeDb = db;\n try {\n const ruleset = getRuleset(opts.ruleset);\n if (!ruleset) {\n process.stderr.write(`❌ Unknown ruleset: \"${opts.ruleset}\" (available: ${listRulesets().map((r) => r.name).join(', ')})\\n`);\n process.exitCode = 1;\n return;\n }\n const sid = sessionId ?? db.getLatestSession()?.id;\n if (!sid) {\n process.stderr.write('❌ No session to score (run discovery first or pass a session id)\\n');\n process.exitCode = 1;\n return;\n }\n const report = db.scoreSession(sid, ruleset);\n const out = opts.format === 'json' || opts.format === 'markdown' || opts.format === 'mermaid'\n ? exportComplianceReport(report, opts.format)\n : formatComplianceText(report);\n if (opts.output) {\n writeFileSync(opts.output, out + '\\n');\n process.stderr.write(`✓ Wrote compliance report to: ${opts.output}\\n`);\n } else {\n process.stdout.write(out + '\\n');\n }\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n } finally {\n db.close();\n activeDb = null;\n }\n });\n\n // ── DRIFT DETECTION + ALERTING (3.1) ─────────────────────────────────────────\n // Classify drift between two sessions into a severity-ranked alert and emit it\n // to configured sinks (stdout default; opt-in outbound webhook). Passive monitor:\n // a single-session catalog is a clean no-op (exit 0), not an error.\n\n program\n .command('drift [base] [current]')\n .description('Classify drift between two sessions and emit to configured sinks (default: stdout). Defaults to the two most recent.')\n .option('--min-severity <s>', 'Minimum severity to emit: info|warning|critical', 'info')\n .option('--webhook <url>', 'Outbound webhook URL (overrides config; token via CARTOGRAPHY_DRIFT_TOKEN)')\n .option('--db <path>', 'DB path')\n .action(async (base: string | undefined, current: string | undefined, opts) => {\n let drift;\n try {\n drift = DriftConfigSchema.parse({\n minSeverity: opts.minSeverity,\n sinks: opts.webhook ? [{ type: 'webhook', url: opts.webhook }] : [{ type: 'stdout' }],\n });\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n return;\n }\n const config = defaultConfig({ ...(opts.db ? { dbPath: opts.db } : {}), drift });\n const db = new CartographyDB(config.dbPath);\n activeDb = db;\n try {\n const alert = await runDrift(db, config, { base, current, minSeverity: drift.minSeverity });\n if (!alert) {\n process.stderr.write('ℹ Need at least two discovery sessions for drift; nothing to do.\\n');\n return;\n }\n // The stdout sink (if configured) already wrote the payload; summary to stderr.\n process.stderr.write(`✓ drift severity=${alert.severity} items=${alert.items.length}\\n`);\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n } finally {\n db.close();\n activeDb = null;\n }\n });\n\n // ── SCHEDULED DISCOVERY (2.5) ────────────────────────────────────────────────\n // Recurring, headless, read-only rescans that record per-run topology drift.\n // Reuses the deterministic local discovery path (no agent loop, no API key) and\n // the incremental `mode:'update'` rescan, persisting one `drift_runs` row per pass.\n\n program\n .command('schedule')\n .description('Run discovery recurringly and record per-run topology drift')\n .requiredOption('--config <file>', 'Path to a JSON config file with a schedule block')\n .option('--once', 'Run a single pass and exit (cron-driver friendly; default)', false)\n .option('--watch', 'Run continuously on the configured cron schedule', false)\n .option('--output-format <fmt>', 'Result format: text, json, stream-json (overrides config)')\n .option('--db <path>', 'DB path (overrides config)')\n .action(async (opts) => {\n let cfg;\n try {\n cfg = loadConfig(opts.config);\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof ConfigError ? err.message : String(err)}\\n`);\n process.exitCode = 2;\n return;\n }\n if (opts.db) cfg = defaultConfig({ ...cfg, dbPath: opts.db });\n\n const fmt = (opts.outputFormat ?? cfg.schedule?.outputFormat ?? 'json') as 'text' | 'json' | 'stream-json';\n if (!['text', 'json', 'stream-json'].includes(fmt)) {\n process.stderr.write(`❌ Invalid --output-format: \"${fmt}\" (must be text, json, or stream-json)\\n`);\n process.exitCode = 2;\n return;\n }\n if (opts.once && opts.watch) {\n process.stderr.write('❌ --once and --watch are mutually exclusive\\n');\n process.exitCode = 2;\n return;\n }\n\n const cron = cfg.schedule?.cron;\n if (opts.watch && !cron) {\n process.stderr.write('❌ --watch requires a `schedule.cron` in the config file\\n');\n process.exitCode = 2;\n return;\n }\n // Validate cron up-front so a bad expression fails before any scan.\n if (cron) {\n try {\n nextRun(cron, new Date());\n } catch (err) {\n process.stderr.write(`❌ Invalid cron \"${cron}\": ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 2;\n return;\n }\n }\n\n const db = new CartographyDB(cfg.dbPath);\n activeDb = db;\n\n const emit = (r: ScheduledRunResult): void => {\n if (fmt === 'text') {\n process.stdout.write(renderDriftSummaryText(r) + '\\n');\n } else {\n const payload = { sessionId: r.sessionId, baseSessionId: r.baseSessionId ?? null, summary: r.delta.summary };\n process.stdout.write(JSON.stringify(payload) + '\\n');\n }\n };\n\n const doRun = async (): Promise<void> => {\n const r = await runOnce(cfg, db);\n db.recordDriftRun(r.sessionId, r.baseSessionId, r.delta);\n // 2.11: enqueue this run's topology for central-DB review (no-op unless\n // `centralDb` is configured in the loaded config file).\n maybeQueueForSync(db, r.sessionId, cfg, (s) => process.stderr.write(s));\n emit(r);\n };\n\n if (opts.watch) {\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n // setTimeout delays are 32-bit ms (~24.8 days). For a far-future cron\n // instant we sleep in capped chunks and re-check, so the timer never\n // overflows (which would fire immediately) — see TimeoutOverflowWarning.\n const MAX_DELAY_MS = 24 * 60 * 60 * 1000; // 1 day per chunk\n let nextAnnounced: string | null = null;\n const schedule = (): void => {\n if (stopped) return;\n const next = nextRun(cron!, new Date());\n const targetMs = next.getTime();\n if (next.toISOString() !== nextAnnounced) {\n logInfo(`next scheduled run at ${next.toISOString()}`);\n nextAnnounced = next.toISOString();\n }\n const remaining = targetMs - Date.now();\n if (remaining > MAX_DELAY_MS) {\n // Not due yet — sleep a bounded chunk, then re-evaluate.\n timer = setTimeout(schedule, MAX_DELAY_MS);\n return;\n }\n timer = setTimeout(() => {\n void (async () => {\n try {\n await doRun();\n } catch (err) {\n // One bad run never kills the schedule — log and continue to the next tick.\n logError(`scheduled run failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n nextAnnounced = null; // force re-announce of the following instant\n schedule();\n })();\n }, Math.max(0, remaining));\n };\n // Cooperate with the module-level SIGINT/SIGTERM shutdown: stop the loop and\n // clear the pending timer so the event loop drains after `shutdown` closes the DB.\n const stop = (): void => {\n stopped = true;\n if (timer) clearTimeout(timer);\n };\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n schedule();\n return; // event loop kept alive by the pending timer\n }\n\n // Default: a single pass (--once or no flag).\n try {\n await doRun();\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n } finally {\n db.close();\n activeDb = null;\n }\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 if (session.name) process.stdout.write(` Name: ${session.name}\\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 const events = db.getEvents(session.id);\n if (events.length > 0) {\n process.stdout.write('\\n Recent activity:\\n');\n for (const e of events.slice(-15)) {\n const kb = e.resultBytes != null ? ` (${(e.resultBytes / 1024).toFixed(1)} KB)` : '';\n process.stdout.write(` ${e.timestamp} ${e.process} ${(e.command ?? '').slice(0, 60)}${kb}\\n`);\n }\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 `${session.name ? ` ${session.name}` : ''}\\n`\n );\n }\n\n db.close();\n });\n\n // ── OVERVIEW ──────────────────────────────────────────────────────────────\n\n program\n .command('overview')\n .description('Overview of all cartography sessions')\n .option('--db <path>', 'DB path')\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;\n for (const s of sessions) {\n const st = db.getStats(s.id);\n totalNodes += st.nodes; totalEdges += st.edges;\n }\n\n w(` ${b(String(sessions.length))} Sessions · ${b(String(totalNodes))} Nodes · `);\n w(`${b(String(totalEdges))} Edges\\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 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)}${session.name ? ` ${d(session.name)}` : ''}\\n`);\n w(` ${d('Nodes: ' + stats.nodes + ' Edges: ' + stats.edges)}\\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 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 path')\n .option('--model <m>', 'Model (defaults to the fast helper model)')\n .action(async (sessionIdArg: string | undefined, opts) => {\n const config = defaultConfig();\n const model = (opts as { model?: string }).model ?? config.models.fast;\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\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')}\\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 });\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 analyze dependencies, identify risks, suggest optimizations.\n\nINFRASTRUCTURE SNAPSHOT (${nodes.length} nodes, ${edges.length} edges):\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,\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 const platformName = IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux';\n\n out('\\n');\n out(b(' DATASYNX CARTOGRAPHY') + ' ' + dim('v' + VERSION) + '\\n');\n out(dim(' AI-powered Infrastructure Discovery & Agentic AI Cartography\\n'));\n out(dim(` Platform: ${platformName}\\n`));\n out('\\n');\n line();\n\n // ── PLATFORM SUPPORT\n out(b(cyan(' CROSS-PLATFORM SUPPORT\\n')));\n out('\\n');\n out(` ${green('Linux')} ss, ps, dpkg/snap/flatpak, find, /bin/sh\\n`);\n out(` ${green('macOS')} lsof, ps, /Applications, Homebrew, Spotlight, /bin/sh\\n`);\n out(` ${green('Windows')} Get-NetTCPConnection, Get-Process, Get-Service, Registry,\\n`);\n out(` winget, choco, scoop, PowerShell\\n`);\n out('\\n');\n out(dim(' Network scanning:\\n'));\n out(dim(' Linux: ss -tlnp\\n'));\n out(dim(' macOS: lsof -iTCP -sTCP:LISTEN -n -P\\n'));\n out(dim(' Windows: Get-NetTCPConnection -State Listen\\n'));\n out('\\n');\n out(dim(' Installed apps:\\n'));\n out(dim(' Linux: dpkg, rpm, snap, flatpak, .desktop files\\n'));\n out(dim(' macOS: /Applications, brew list, Spotlight (mdfind)\\n'));\n out(dim(' Windows: winget list, Registry scan, choco, scoop\\n'));\n out('\\n');\n out(dim(' Browser bookmarks & history:\\n'));\n out(dim(' Linux: ~/.config/google-chrome, Snap/Flatpak variants, ~/.mozilla\\n'));\n out(dim(' macOS: ~/Library/Application Support/Google/Chrome, ~/Library/.../Firefox\\n'));\n out(dim(' Windows: %LOCALAPPDATA%\\\\Google\\\\Chrome\\\\User Data, %APPDATA%\\\\Mozilla\\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 (provider-agnostic: claude, openai, ollama).\\n`);\n out(` The agent autonomously runs ${IS_WIN ? 'Get-NetTCPConnection, Get-Process' : IS_MAC ? 'lsof, ps' : '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(' discovery.html Enterprise Map\\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, map, cost (default: all but cost)\\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\\n'));\n out(dim(' └── Platform Detection (platform.ts)\\n'));\n out(dim(' └── Shell: /bin/sh (Unix) | PowerShell (Windows)\\n'));\n out(dim(' └── Agent Orchestrator (agent.ts)\\n'));\n out(dim(' └── runDiscovery() → AgentProvider (claude|openai|ollama) + 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(' scan_k8s, scan_aws, scan_gcp, scan_azure\\n'));\n out(dim(' └── CartographyDB (SQLite WAL)\\n'));\n out('\\n');\n line();\n\n // ── SAFETY\n out(b(cyan(' SAFETY\\n')));\n out('\\n');\n out(dim(' PreToolUse hook blocks ALL destructive commands:\\n'));\n out(dim(' Unix: rm, mv, dd, chmod, kill, docker rm/run, kubectl delete, >\\n'));\n out(dim(' PowerShell: Remove-Item, Stop-Process, Stop-Service, Out-File, etc.\\n'));\n out(dim(' Claude only reads — never writes, never deletes.\\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 if (IS_WIN) {\n out(' $env:ANTHROPIC_API_KEY=\"sk-ant-...\"\\n');\n } else {\n out(' export ANTHROPIC_API_KEY=sk-ant-...\\n');\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('cost')\n .description('Import cost/owner attribution from a CSV and enrich a session (FinOps)')\n .requiredOption('--file <path>', 'CSV: nodeId,owner,amount,currency,period[,source]')\n .option('--session <id>', 'Session to enrich (default: latest)')\n .option('--match <strategy>', 'Row→node match: nodeId | name | tag', 'nodeId')\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 activeDb = db;\n try {\n const sessionId = opts.session ?? db.getLatestSession('discover')?.id;\n if (!sessionId) {\n process.stderr.write('❌ No session to enrich (run discovery first or pass --session)\\n');\n process.exitCode = 1;\n return;\n }\n const match = opts.match as 'nodeId' | 'name' | 'tag';\n if (!['nodeId', 'name', 'tag'].includes(match)) {\n process.stderr.write(`❌ Invalid --match: \"${match}\" (nodeId | name | tag)\\n`);\n process.exitCode = 1;\n return;\n }\n const source = new CsvCostSource({ filePath: opts.file, match, db, sessionId });\n const r = await enrichCosts(db, sessionId, source);\n process.stderr.write(`✓ cost: ${r.matched} matched, ${r.unmatched} unmatched (of ${r.total}) from ${r.source}\\n`);\n if (r.unmatchedIds.length > 0) {\n process.stderr.write(` unmatched ids: ${r.unmatchedIds.slice(0, 20).join(', ')}${r.unmatchedIds.length > 20 ? ' …' : ''}\\n`);\n }\n if (r.matched === 0 && r.total > 0) process.exitCode = 1;\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n } finally {\n db.close();\n activeDb = null;\n }\n });\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('--org <name>', 'Tenant/organization to scope the session to (default: local)')\n .option('--db <path>', 'DB path')\n .action(async (opts) => {\n const config = defaultConfig({ ...(opts.db ? { dbPath: opts.db } : {}), ...(opts.org ? { organization: opts.org } : {}) });\n const db = new CartographyDB(config.dbPath);\n const sessionId = opts.session ?? db.createSession('discover', config, opts.org);\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) >= 20) {\n ok(`Node.js ${nodeVer}`);\n } else {\n err(`Node.js ${nodeVer} — requires >=20`);\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 (platform-aware)\n const localTools: Array<[string, string, string]> = [\n ['docker', 'docker --version', 'all'],\n ];\n // Network scanning tool varies by platform\n if (IS_WIN) {\n localTools.push(['PowerShell (Get-NetTCPConnection)', 'powershell -Command \"Get-NetTCPConnection -State Listen | Select-Object -First 1\"', 'win32']);\n } else if (IS_MAC) {\n localTools.push(['lsof', 'lsof -v 2>&1 | head -1', 'darwin']);\n } else {\n localTools.push(['ss', 'ss --version', 'linux']);\n }\n for (const [name, cmd] of localTools) {\n try {\n execSync(cmd, { stdio: 'pipe', timeout: 10_000 });\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 // ── Prune ──────────────────────────────────────────────────────────────────\n\n program\n .command('prune')\n .description('Delete old sessions and their data')\n .option('--older-than <days>', 'Delete sessions older than N days', '30')\n .option('--db <path>', 'DB path')\n .option('--dry-run', 'Show what would be deleted without actually deleting', false)\n .action((opts) => {\n const days = parseInt(opts.olderThan, 10);\n if (Number.isNaN(days) || days < 1) {\n process.stderr.write(`Invalid --older-than: \"${opts.olderThan}\" (must be >= 1)\\n`);\n process.exitCode = 2;\n return;\n }\n\n const config = defaultConfig({ ...(opts.db ? { dbPath: opts.db } : {}) });\n const db = new CartographyDB(config.dbPath);\n const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();\n\n const sessions = db.getSessions().filter(s => s.startedAt < cutoff);\n\n if (sessions.length === 0) {\n process.stderr.write(`No sessions older than ${days} days.\\n`);\n db.close();\n return;\n }\n\n if (opts.dryRun) {\n process.stderr.write(`Would delete ${sessions.length} session(s) older than ${days} days:\\n`);\n for (const s of sessions) {\n const stats = db.getStats(s.id);\n process.stderr.write(` ${s.id.substring(0, 8)} ${s.startedAt.substring(0, 19)} nodes:${stats.nodes} edges:${stats.edges}\\n`);\n }\n } else {\n const deleted = db.pruneSessions(cutoff);\n logInfo('Sessions pruned', { deleted, olderThanDays: days });\n process.stderr.write(`Deleted ${deleted} session(s) older than ${days} days.\\n`);\n }\n\n db.close();\n });\n\n // ── MCP server ────────────────────────────────────────────────────────────\n\n program\n .command('list-clients')\n .description('List the AI hosts the installer can configure')\n .action(() => {\n o('\\n' + bold(' Supported MCP hosts:') + '\\n\\n');\n for (const c of listClients()) {\n o(` ${green(c.id.padEnd(16))} ${bold(c.label.padEnd(20))} ${dim(c.format)}\\n`);\n if (c.note) o(` ${' '.repeat(16)} ${dim('↳ ' + c.note)}\\n`);\n }\n o('\\n' + dim(` Install: ${CMD} install --client <id> [--project] [--dry-run]`) + '\\n\\n');\n });\n\n program\n .command('install')\n .description('Register the Cartography MCP server into an AI host\\'s config (parse-merge, never clobber)')\n .requiredOption('--client <id>', 'Target host id (see `list-clients`)')\n .option('--global', 'Write the global/user config (default)', false)\n .option('--project', 'Write the project-local config instead', false)\n .option('--dry-run', 'Show the merge diff without writing', false)\n .option('--deeplink', 'Print a one-click install deeplink instead of writing (Cursor / VS Code)', false)\n .option('--name <name>', 'Server name to register', DEFAULT_SERVER_NAME)\n .option('--http', 'Register the Streamable HTTP endpoint instead of stdio', false)\n .option('--url <url>', 'HTTP endpoint (with --http)')\n .option('--db <path>', 'Pass --db <path> to the server')\n .option('--session <id>', 'Pass --session <id> to the server')\n .action((opts) => {\n const spec = getClient(opts.client);\n if (!spec) {\n logError(`Unknown client \"${opts.client}\". Run \\`${CMD} list-clients\\` to see options.`);\n process.exitCode = 1;\n return;\n }\n const scope = opts.project ? 'project' : 'global';\n const packageArgs: string[] = [];\n if (opts.db) packageArgs.push('--db', opts.db);\n if (opts.session) packageArgs.push('--session', opts.session);\n const entry = defaultServerEntry({\n transport: opts.http ? 'http' : 'stdio',\n ...(opts.url ? { url: opts.url } : {}),\n ...(packageArgs.length ? { packageArgs } : {}),\n });\n if (opts.deeplink) {\n if (opts.client === 'cursor') {\n o('\\n' + bold(' Cursor one-click:') + '\\n ' + cyan(cursorDeeplink(opts.name, entry)) + '\\n\\n');\n } else if (opts.client === 'vscode') {\n o('\\n' + bold(' VS Code one-click:') + '\\n ' + cyan(vscodeDeeplink(opts.name, entry)) + '\\n');\n o(' ' + dim('or: ') + codeAddMcpCommand(opts.name, entry) + '\\n\\n');\n } else {\n logWarn(`No deeplink available for \"${opts.client}\". Deeplinks exist for: cursor, vscode.`);\n }\n return;\n }\n try {\n const plan = planInstall(spec, defaultContext(scope), { serverName: opts.name, entry });\n o('\\n' + bold(` ${plan.label}`) + dim(` (${plan.format}, ${scope})`) + '\\n');\n o(dim(` ${plan.path}`) + '\\n');\n if (plan.note) o(yellow(` ⚠ ${plan.note}`) + '\\n');\n o('\\n' + renderDiff(plan.before, plan.after) + '\\n\\n');\n if (!plan.changed) {\n o(green(' ✓ Already up to date — nothing to write.') + '\\n\\n');\n return;\n }\n if (opts.dryRun) {\n o(yellow(' Dry run — no file written.') + '\\n\\n');\n return;\n }\n applyInstall(plan);\n o(green(` ✓ Wrote ${plan.fileExists ? 'updated' : 'new'} config.`) + ' ' + dim('Restart the host to pick it up.') + '\\n\\n');\n } catch (err) {\n logError(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n }\n });\n\n program\n // ── consent: persistent sharing policy + admin anonymization (2.10) ─────────\n const consent = program\n .command('consent')\n .description('Manage the per-employee data-sharing policy (none|anonymized|full) + admin anonymization');\n\n consent\n .command('default <level>')\n .description('Set the global default sharing level (none|anonymized|full)')\n .option('--db <path>', 'DB path')\n .action((level: string, opts: { db?: string }) => {\n const parsed = SharingLevelSchema.safeParse(level);\n if (!parsed.success) {\n logError(`Invalid level \"${level}\" — expected one of: none, anonymized, full`);\n process.exitCode = 1;\n return;\n }\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n db.setSharingLevel('*', parsed.data);\n logInfo(`default sharing level set to \"${parsed.data}\"`);\n } finally {\n db.close();\n }\n });\n\n consent\n .command('set <pattern> <level>')\n .description('Set a pattern override (glob over the node id; * = within-segment, ** = any)')\n .option('--db <path>', 'DB path')\n .action((pattern: string, level: string, opts: { db?: string }) => {\n const parsed = SharingLevelSchema.safeParse(level);\n if (!parsed.success) {\n logError(`Invalid level \"${level}\" — expected one of: none, anonymized, full`);\n process.exitCode = 1;\n return;\n }\n if (pattern === '*' || pattern === '**') {\n logError('Use `consent default <level>` to set the global default; `set` is for narrower overrides');\n process.exitCode = 1;\n return;\n }\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n db.setSharingLevel(pattern, parsed.data);\n logInfo(`override \"${pattern}\" → \"${parsed.data}\"`);\n } finally {\n db.close();\n }\n });\n\n consent\n .command('clear <pattern>')\n .description('Remove a pattern override (the global default cannot be cleared)')\n .option('--db <path>', 'DB path')\n .action((pattern: string, opts: { db?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n db.clearSharingOverride(pattern);\n logInfo(`override \"${pattern}\" cleared`);\n } finally {\n db.close();\n }\n });\n\n consent\n .command('list')\n .description('Show the global default + every pattern override')\n .option('--db <path>', 'DB path')\n .action((opts: { db?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n const policy = db.getSharingPolicy();\n process.stdout.write(JSON.stringify(policy, null, 2) + '\\n');\n } finally {\n db.close();\n }\n });\n\n consent\n .command('preview [session]')\n .description('Show exactly what would leave the machine for a session (default: latest)')\n .option('--db <path>', 'DB path')\n .option('--org <name>', 'Organization namespace for the org key')\n .action((session: string | undefined, opts: { db?: string; org?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n const sid = session && session !== 'latest' ? session : db.getLatestSession('discover')?.id;\n if (!sid) {\n logError('No session found to preview');\n process.exitCode = 1;\n return;\n }\n const orgKey = loadOrgKey({ organization: opts.org });\n const policy = db.getSharingPolicy();\n const preview = previewShare(db, sid, orgKey, policy);\n process.stdout.write(JSON.stringify(preview, null, 2) + '\\n');\n } finally {\n db.close();\n }\n });\n\n const consentKey = consent.command('key').description('Org-key administration');\n consentKey\n .command('rotate')\n .description('Rotate the org key (prior reversal entries become unrecoverable)')\n .option('--org <name>', 'Organization namespace for the org key')\n .action((opts: { org?: string }) => {\n rotateOrgKey({ organization: opts.org });\n logInfo('org key rotated');\n });\n\n consent\n .command('reverse <token>')\n .description('Admin: recover the original plaintext behind a pseudonym token')\n .option('--db <path>', 'DB path')\n .option('--org <name>', 'Organization namespace for the org key')\n .action((token: string, opts: { db?: string; org?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n const orgKey = loadOrgKey({ organization: opts.org });\n const plaintext = reversePseudonym(token, orgKey, db);\n if (plaintext === undefined) {\n logError(`Could not reverse \"${token}\" (unknown token or wrong/rotated org key)`);\n process.exitCode = 1;\n return;\n }\n process.stdout.write(plaintext + '\\n');\n } finally {\n db.close();\n }\n });\n\n // ── sync: central-DB pending-review queue + consent-gated push (2.11) ────────\n const sync = program\n .command('sync')\n .description('Central-DB outbound sync: review queued items and push approved deltas (opt-in)');\n\n sync\n .command('status')\n .description('Show the pending-review queue (counts by status + pending items)')\n .option('--db <path>', 'DB path')\n .action((opts: { db?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n if (!config.centralDb?.url) {\n logWarn('centralDb is not configured — sync is inert (set centralDb in ~/.cartography/config.json or CARTOGRAPHY_CENTRAL_URL/TOKEN)');\n }\n const counts = db.countPendingByStatus();\n process.stdout.write(JSON.stringify(counts, null, 2) + '\\n');\n const pending = db.getPendingShares({ status: 'pending' });\n for (const p of pending.slice(0, 50)) {\n process.stdout.write(` ${p.kind === 'node' ? '●' : '→'} ${p.nodeId ?? p.contentHash.slice(0, 12)} ${dim('(' + p.kind + ')')}\\n`);\n }\n if (pending.length > 50) process.stdout.write(` ${dim('… and ' + (pending.length - 50) + ' more')}\\n`);\n } finally {\n db.close();\n }\n });\n\n sync\n .command('review')\n .description('Interactively approve/withhold each pending item (decisions are remembered)')\n .option('--db <path>', 'DB path')\n .action(async (opts: { db?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n const pending = db.getPendingShares({ status: 'pending' });\n if (pending.length === 0) {\n logInfo('no pending items to review');\n return;\n }\n if (!process.stdin.isTTY) {\n logWarn(`${pending.length} pending item(s); run \\`sync review\\` in an interactive terminal to decide them`);\n return;\n }\n const w = process.stderr.write.bind(process.stderr);\n // Pattern a decision should remember: the node id (so the same node never\n // re-prompts). Edges have no id — they are decided per-item without a rule.\n const patternFor = (p: PendingShareRow): string | undefined => p.nodeId;\n for (const p of pending) {\n w('\\n');\n w(` ${yellow(bold('?'))} Share ${p.kind} ${bold(p.nodeId ?? p.contentHash.slice(0, 12))}?\\n`);\n w(` ${dim(JSON.stringify(p.payload))}\\n`);\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const ans = (await new Promise<string>((res) => rl.question(` ${cyan('→')} [s]hare / [w]ithhold / [a]lways / [n]ever / [q]uit: `, res))).trim().toLowerCase();\n rl.close();\n const pat = patternFor(p);\n if (ans === 'q') break;\n if (ans === 's') {\n db.setPendingStatus(p.contentHash, 'approved', 'user');\n } else if (ans === 'w') {\n db.setPendingStatus(p.contentHash, 'withheld', 'user');\n } else if (ans === 'a') {\n if (pat) db.setSharingLevel(pat, 'full');\n db.setPendingStatus(p.contentHash, 'approved', 'user');\n } else if (ans === 'n') {\n if (pat) db.setSharingLevel(pat, 'none');\n db.setPendingStatus(p.contentHash, 'withheld', 'user');\n } else {\n w(` ${dim('skipped (left pending)')}\\n`);\n }\n }\n const counts = db.countPendingByStatus();\n logInfo(`review done — approved ${counts.approved}, withheld ${counts.withheld}, pending ${counts.pending}`);\n } finally {\n db.close();\n }\n });\n\n sync\n .command('push')\n .description('Push approved deltas to the central ingest endpoint (bearer-auth HTTPS)')\n .option('--db <path>', 'DB path')\n .option('--dry-run', 'Preview the batches without sending', false)\n .action(async (opts: { db?: string; dryRun?: boolean }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n if (!config.centralDb?.url) {\n logError('centralDb is not configured — nothing to push (set centralDb.url + token)');\n process.exitCode = 1;\n return;\n }\n const db = new CartographyDB(config.dbPath);\n try {\n const approved = db.getApprovedShares();\n const items: PushItem[] = approved.map((p) => ({ contentHash: p.contentHash, kind: p.kind, payload: p.payload }));\n const result = await pushDeltas(config, items, { dryRun: opts.dryRun });\n if (!opts.dryRun) {\n for (const hash of result.sentHashes) db.setPendingStatus(hash, 'shared');\n }\n logInfo(`sync push: sent ${result.sent}, batches ${result.batches}, failed ${result.failed}${opts.dryRun ? ' (dry-run)' : ''}`);\n } catch (err) {\n logError(`sync push failed: ${err instanceof Error ? err.message : String(err)}`);\n process.exitCode = 1;\n } finally {\n db.close();\n }\n });\n\n program\n .command('mcp')\n .description('Run the Model Context Protocol server (stdio by default) — the primary interface for AI agents')\n .option('--http', 'Use Streamable HTTP transport instead of stdio', false)\n .option('--port <n>', 'HTTP port', '3737')\n .option('--host <h>', 'HTTP host', '127.0.0.1')\n .option('--allowed-hosts <list>', 'Comma-separated Host allowlist (required for non-loopback --host)')\n .option('--token <secret>', 'Bearer token required on HTTP requests (or CARTOGRAPHY_HTTP_TOKEN); mandatory for non-loopback --host')\n .option('--db <path>', 'DB path')\n .option('--session <id>', 'Session to serve (id or \"latest\")', 'latest')\n .option('--tenant <id>', 'Tenant/organization whose topology to serve (alias: --org; default: local)')\n .option('--org <id>', 'Alias for --tenant')\n .option('--no-semantic', 'Disable semantic (vector) search')\n .option('--plugins <list>', 'Comma-separated scanner plugin package names to load (opt-in; or CARTOGRAPHY_PLUGINS)')\n .option('--server-mode', 'Run as a central collector: enable the authenticated POST /ingest write route + org-wide summary (implies --http; opt-in)', false)\n .option('--anon-mode <mode>', 'On ingest, reject|strip un-anonymized identifying fragments (server-mode)', 'reject')\n .option('--auth-required', 'Reject unauthenticated requests even on loopback (RBAC required mode)', false)\n .action(async (opts) => {\n try {\n const anonMode = opts.anonMode;\n if (anonMode !== 'reject' && anonMode !== 'strip') {\n process.stderr.write(`\\nerror: --anon-mode must be 'reject' or 'strip' (got '${anonMode}')\\n`);\n process.exitCode = 1;\n return;\n }\n await startMcp({\n transport: opts.http ? 'http' : 'stdio',\n port: parseInt(opts.port, 10),\n host: opts.host,\n allowedHosts: opts.allowedHosts ? String(opts.allowedHosts).split(',').map((h: string) => h.trim()).filter(Boolean) : undefined,\n token: opts.token,\n dbPath: opts.db,\n session: opts.session,\n tenant: opts.tenant ?? opts.org,\n semantic: opts.semantic,\n plugins: opts.plugins ? String(opts.plugins).split(',').map((p: string) => p.trim()).filter(Boolean) : undefined,\n serverMode: opts.serverMode === true,\n anonMode,\n authRequired: opts.authRequired === true,\n });\n } catch (err) {\n process.stderr.write(`\\nerror: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n }\n });\n\n program\n .command('api')\n .description('Run the read-only REST/GraphQL API server over the topology store (4.2)')\n .option('--http', 'Use HTTP transport (default; kept for symmetry with mcp)', true)\n .option('--port <n>', 'HTTP port', '3737')\n .option('--host <h>', 'HTTP host', '127.0.0.1')\n .option('--allowed-hosts <list>', 'Comma-separated Host allowlist (required for non-loopback --host)')\n .option('--allowed-origins <list>', 'Comma-separated CORS Origin allowlist (default: same-origin only)')\n .option('--token <secret>', 'Bearer token required on requests (or CARTOGRAPHY_HTTP_TOKEN); mandatory for non-loopback --host')\n .option('--db <path>', 'DB path')\n .option('--session <id>', 'Session to serve (id or \"latest\")', 'latest')\n .option('--tenant <id>', 'Default tenant whose topology to serve (alias: --org; default: local)')\n .option('--org <id>', 'Alias for --tenant')\n .option('--no-graphql', 'Disable the /graphql endpoint (REST only)')\n .option('--auth-required', 'Reject unauthenticated requests even on loopback (RBAC required mode)', false)\n .action(async (opts) => {\n try {\n await startApi({\n authRequired: opts.authRequired === true,\n port: parseInt(opts.port, 10),\n host: opts.host,\n allowedHosts: opts.allowedHosts ? String(opts.allowedHosts).split(',').map((h: string) => h.trim()).filter(Boolean) : undefined,\n allowedOrigins: opts.allowedOrigins ? String(opts.allowedOrigins).split(',').map((o: string) => o.trim()).filter(Boolean) : undefined,\n token: opts.token,\n dbPath: opts.db,\n session: opts.session,\n tenant: opts.tenant ?? opts.org,\n graphql: opts.graphql,\n });\n } catch (err) {\n process.stderr.write(`\\nerror: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n }\n });\n\n const authCmd = program\n .command('auth')\n .description('Manage RBAC credentials for the HTTP surfaces (MCP transport + API server, 4.5)');\n\n authCmd\n .command('add <subject>')\n .description('Create a credential and print its bearer token ONCE (only the hash is stored)')\n .option('--role <role>', 'viewer | operator | admin', 'viewer')\n .option('--tenant <id>', 'Tenant the credential is scoped to (default: local)')\n .option('--token <secret>', 'Use this token instead of generating one')\n .option('--db <path>', 'DB path')\n .action((subject: string, opts: { role: string; tenant?: string; token?: string; db?: string }) => {\n const role = RoleSchema.safeParse(opts.role);\n if (!role.success) {\n process.stderr.write(`\\nerror: --role must be one of viewer|operator|admin (got '${opts.role}')\\n`);\n process.exitCode = 1;\n return;\n }\n const db = new CartographyDB(opts.db ?? defaultConfig().dbPath);\n try {\n const token = opts.token ?? randomBytes(24).toString('base64url');\n const tenant = normalizeTenant(opts.tenant);\n db.addCredential({ tokenHash: hashToken(token), subject, tenant, role: role.data });\n process.stderr.write(`\\n✓ credential added: subject=${subject} role=${role.data} tenant=${tenant}\\n`);\n process.stderr.write(` Bearer token (shown once — store it now):\\n\\n`);\n process.stdout.write(token + '\\n');\n } finally {\n db.close();\n }\n });\n\n authCmd\n .command('list')\n .description('List credentials (subjects/roles/tenants — token hashes only, never the raw token)')\n .option('--db <path>', 'DB path')\n .action((opts: { db?: string }) => {\n const db = new CartographyDB(opts.db ?? defaultConfig().dbPath);\n try {\n const creds = db.listCredentials();\n if (creds.length === 0) { process.stderr.write('No credentials configured (open/shared-token mode).\\n'); return; }\n for (const c of creds) process.stdout.write(`${c.subject}\\t${c.role}\\t${c.tenant}\\t${c.createdAt}\\n`);\n } finally {\n db.close();\n }\n });\n\n authCmd\n .command('revoke <subject>')\n .description('Revoke all credentials for a subject')\n .option('--db <path>', 'DB path')\n .action((subject: string, opts: { db?: string }) => {\n const db = new CartographyDB(opts.db ?? defaultConfig().dbPath);\n try {\n const n = db.revokeCredentialsBySubject(subject);\n process.stderr.write(n > 0 ? `✓ revoked ${n} credential(s) for ${subject}\\n` : `no credentials found for ${subject}\\n`);\n } finally {\n db.close();\n }\n });\n\n // ── Banner (always show) ──────────────────────────────────────────────────\n\n const o = (s: string) => process.stderr.write(s);\n\n o('\\n');\n o(cyan(' ____ _ ____ ') + '\\n');\n o(cyan(' | _ \\\\ __ _| |_ __ _/ ___| _ _ _ __ __ __') + '\\n');\n o(cyan(' | | | |/ _` | __/ _` \\\\___ \\\\| | | | \\'_ \\\\\\\\ \\\\/ /') + '\\n');\n o(cyan(' | |_| | (_| | || (_| |___) | |_| | | | |> < ') + '\\n');\n o(cyan(' |____/ \\\\__,_|\\\\__\\\\__,_|____/ \\\\__, |_| |_/_/\\\\_\\\\') + '\\n');\n o(cyan(' |___/ ') + '\\n');\n o('\\n');\n o(bold(' Cartography') + ' ' + dim('v' + VERSION) + '\\n');\n o(dim(' AI-powered Infrastructure Discovery & Agentic AI Cartography\\n'));\n o(dim(' Autonomous infrastructure discovery — zero-config, provider-agnostic\\n'));\n o('\\n');\n\n // ── Welcome Screen (no args → command overview) ─────────────────────────\n\n if (process.argv.length <= 2) {\n o(dim(' ────────────────────────────────────────────────\\n'));\n o('\\n');\n o(bold(' Commands:\\n'));\n o('\\n');\n o(` ${green('discover')} ${dim('Scan infrastructure (provider: claude|openai|ollama)')}\\n`);\n o(` ${green('seed')} ${dim('Manually add known tools/DBs/APIs')}\\n`);\n o(` ${green('bookmarks')} ${dim('View browser bookmarks')}\\n`);\n o(` ${green('export')} ${dim('[session]')} ${dim('Export Mermaid, JSON, YAML, HTML')}\\n`);\n o(` ${green('show')} ${dim('[session]')} ${dim('Show session details')}\\n`);\n o(` ${green('sessions')} ${dim('List all sessions')}\\n`);\n o(` ${green('doctor')} ${dim('Check requirements (kubectl, aws, gcloud, az)')}\\n`);\n o(` ${green('docs')} ${dim('Full feature reference')}\\n`);\n o('\\n');\n o(dim(' ────────────────────────────────────────────────\\n'));\n o('\\n');\n o(bold(' Quick Start:\\n'));\n o('\\n');\n o(` ${magenta('$')} ${bold('datasynx-cartography doctor')} ${dim('Check requirements')}\\n`);\n o(` ${magenta('$')} ${bold('datasynx-cartography seed')} ${dim('Add known infrastructure')}\\n`);\n o(` ${magenta('$')} ${bold('datasynx-cartography discover')} ${dim('One-time scan')}\\n`);\n o('\\n');\n o(dim(' Docs: datasynx-cartography docs\\n'));\n o(dim(' Help: datasynx-cartography --help\\n'));\n o(dim(' npm: @datasynx/agentic-ai-cartography\\n'));\n o('\\n');\n return;\n }\n\n o(dim(' ────────────────────────────────────────────────\\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';\nimport type { ProviderName } from './types.js';\n\nfunction isOAuthLoggedIn(): boolean {\n // Claude CLI stores 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\n/**\n * Provider-aware preflight. Defaults to `'claude'` so existing zero-arg callers are\n * unaffected. For non-Claude providers, checks only what each backend needs from the\n * environment; deeper reachability (e.g. Ollama host) is deferred to the provider's\n * `ensureAvailable`, which degrades at run with an actionable message.\n */\nexport function checkPrerequisites(provider: ProviderName = 'claude'): void {\n if (provider === 'openai') {\n checkOpenAIPrerequisites();\n return;\n }\n if (provider === 'ollama') {\n // No key/CLI required at preflight; the provider checks host reachability at run.\n process.stderr.write(\n `✓ Ollama provider selected (host: ${process.env.OLLAMA_HOST ?? 'http://127.0.0.1:11434'})\\n`,\n );\n return;\n }\n checkClaudePrerequisites();\n}\n\nfunction checkOpenAIPrerequisites(): void {\n if (!process.env.OPENAI_API_KEY) {\n process.stderr.write(\n '\\n❌ OpenAI provider selected but OPENAI_API_KEY is not set.\\n\\n' +\n ' Set your key:\\n' +\n ' export OPENAI_API_KEY=sk-...\\n\\n' +\n ' Install the SDK if needed:\\n' +\n ' npm install openai\\n\\n' +\n ' Tip: pass a non-Claude model, e.g. --model gpt-4.1\\n\\n'\n );\n process.exitCode = 1;\n throw new Error('OPENAI_API_KEY not set');\n }\n}\n\nfunction checkClaudePrerequisites(): void {\n // Claude CLI present?\n try {\n execSync('claude --version', { stdio: 'pipe' });\n } catch {\n process.stderr.write(\n '\\n❌ Claude CLI not found.\\n' +\n ' Datasynx Cartography requires the Claude CLI as a runtime dependency.\\n\\n' +\n ' Install:\\n' +\n ' npm install -g @anthropic-ai/claude-code\\n' +\n ' # or\\n' +\n ' curl -fsSL https://claude.ai/install.sh | bash\\n\\n' +\n ' Then: claude login\\n\\n'\n );\n process.exitCode = 1;\n throw new Error('Claude CLI not found');\n }\n\n // Check auth: API Key OR 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 '⚠ No authentication found. Please choose one of the following options:\\n\\n' +\n ' Option A — claude.ai Subscription (recommended):\\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('✓ Logged in via claude login (Subscription)\\n');\n }\n}\n","/**\n * RBAC identity types (4.5). A bearer credential resolves to a {@link Principal}\n * `{ subject, tenant, role }`; the HTTP surfaces (MCP transport + REST/GraphQL API)\n * enforce a deny-by-default `can(role, action)` matrix and pin every read to the\n * principal's tenant. Kept dependency-light and free of any import from `db.ts`/\n * `server.ts` so it can be reused by both transports without a cycle.\n */\n\nimport { z } from 'zod';\n\n/** Roles, least → most privileged. `admin ⊇ operator ⊇ viewer` (rank-ordered). */\nexport const ROLES = ['viewer', 'operator', 'admin'] as const;\nexport type Role = typeof ROLES[number];\nexport const RoleSchema = z.enum(ROLES);\n\n/**\n * Gated action classes:\n * - `read` — any read-only query/resource (viewer+).\n * - `discovery` — trigger a scan that mutates the catalog, e.g. `run_discovery` (operator+).\n * - `admin` — manage credentials / admin-only surfaces (admin only).\n */\nexport const ACTIONS = ['read', 'discovery', 'admin'] as const;\nexport type Action = typeof ACTIONS[number];\nexport const ActionSchema = z.enum(ACTIONS);\n\n/** The authenticated caller, bound to exactly one tenant. */\nexport interface Principal {\n /** Stable identity (token label / username / OIDC `sub`). */\n subject: string;\n /** Org-scope this principal may read/act within. */\n tenant: string;\n role: Role;\n}\nexport const PrincipalSchema = z.object({\n subject: z.string().min(1),\n tenant: z.string().min(1),\n role: RoleSchema,\n});\n\n/** A seeded credential (config-supplied). The token is hashed before storage; never persisted raw. */\nexport const CredentialConfigSchema = z.object({\n token: z.string().min(1),\n subject: z.string().min(1),\n tenant: z.string().optional(),\n role: RoleSchema.default('viewer'),\n});\nexport type CredentialConfig = z.infer<typeof CredentialConfigSchema>;\n\n/**\n * Opt-in auth block on {@link CartographyConfig}. Absent → today's behavior exactly\n * (loopback no-token → implicit admin; a configured shared token → one implicit admin).\n * When `credentials` are present (here or in the SQLite store), the server runs in RBAC\n * mode: only a known token resolves to a principal, everything else is 401.\n */\nexport const AuthConfigSchema = z.object({\n /** Seed credentials (merged into the SQLite store on startup). */\n credentials: z.array(CredentialConfigSchema).optional(),\n /** Reject unauthenticated requests even on loopback (default: loopback dev stays open). */\n required: z.boolean().optional(),\n});\nexport type AuthConfig = z.infer<typeof AuthConfigSchema>;\n\n/** A stored credential record (token already hashed). */\nexport interface CredentialRecord {\n tokenHash: string;\n subject: string;\n tenant: string;\n role: Role;\n createdAt: string;\n}\n\n/** Resolves a stored credential by its token hash. Implemented over SQLite (and, later, OIDC). */\nexport interface CredentialStore {\n /** Number of stored credentials — `0` means \"no RBAC configured\" (fall back to shared/loopback). */\n count(): number;\n findByHash(tokenHash: string): CredentialRecord | undefined;\n}\n","// Provider-neutral agent layer — the seam that decouples the discovery loop from\n// any single LLM SDK. Each provider yields the existing DiscoveryEvent stream, so\n// runDiscovery() and every caller of it stay backend-agnostic.\n\nimport type { CartographyDB } from '../db.js';\nimport type { CartographyConfig, ProviderName } from '../types.js';\nimport type { DiscoveryEvent, AskUserFn } from '../agent.js';\n\nexport type { ProviderName } from '../types.js';\n\n/** Inputs a provider needs to run one discovery session. */\nexport interface AgentRunContext {\n config: CartographyConfig;\n db: CartographyDB;\n sessionId: string;\n systemPrompt: string;\n initialPrompt: string;\n onAskUser?: AskUserFn;\n /** Wall-clock deadline (epoch ms). Providers MUST stop and emit `done` past this. */\n deadlineMs: number;\n}\n\n/**\n * A provider-neutral agent backend. Yields the existing `DiscoveryEvent` stream.\n * Implementations MUST: enforce `config.maxTurns`, honor `ctx.deadlineMs`, route\n * every shell command through `checkReadOnly` + `run()`, and write an audit row per\n * executed tool so the audit trail is identical across providers.\n */\nexport interface AgentProvider {\n readonly name: ProviderName;\n /** Throw a clear Error if the optional dep / API key / CLI is missing. */\n ensureAvailable(config: CartographyConfig): Promise<void>;\n run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent>;\n}\n\nexport type ProviderFactory = () => AgentProvider;\n\n/** Registry of provider factories; the single source of truth for valid provider names. */\nexport class ProviderRegistry {\n private readonly factories = new Map<ProviderName, ProviderFactory>();\n\n register(name: ProviderName, factory: ProviderFactory): void {\n this.factories.set(name, factory);\n }\n\n has(name: string): name is ProviderName {\n return this.factories.has(name as ProviderName);\n }\n\n resolve(name: ProviderName): AgentProvider {\n const f = this.factories.get(name);\n if (!f) throw new Error(`Unknown provider \"${name}\". Available: ${this.names().join(', ')}`);\n return f();\n }\n\n names(): ProviderName[] {\n return [...this.factories.keys()];\n }\n}\n","// PreToolUse Safety Hook — enforces the read-only allowlist on all Bash calls.\n//\n// This is the Claude-Code-specific adapter. The authoritative policy lives in\n// ./allowlist.ts and is shared with the MCP server, so safety is identical no\n// matter which agent or model drives discovery.\n\nimport type { HookCallback } from '@anthropic-ai/claude-agent-sdk';\nimport { checkReadOnly } from './allowlist.js';\n\nexport type { HookCallback };\n\nexport const safetyHook: HookCallback = async (input, _toolUseID, _options) => {\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 ?? '').trim();\n\n // An empty command runs nothing — allow it (matches Claude Code's no-op behavior).\n if (!cmd) {\n return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };\n }\n\n const decision = checkReadOnly(cmd);\n if (!decision.allowed) {\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `BLOCKED: ${decision.reason} — read-only allowlist policy`,\n },\n };\n }\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n },\n };\n};\n","// PostToolUse Audit Hook — records every executed tool call into activity_events.\n//\n// Governance/audit trail: for each tool the agent runs during discovery we persist\n// { tool, command, result bytes, timestamp } so an operator can review what ran.\n// Audit failures must never break discovery, so the write is best-effort.\n\nimport type { HookCallback } from '@anthropic-ai/claude-agent-sdk';\nimport type { CartographyDB } from './db.js';\nimport { logDebug } from './logger.js';\n\n/** Build a PostToolUse hook bound to a session that logs executed tools to the catalog. */\nexport function createAuditHook(db: CartographyDB, sessionId: string): HookCallback {\n return async (input) => {\n try {\n if (!('tool_name' in input)) return {};\n const i = input as { tool_name: string; tool_input?: { command?: string }; tool_response?: unknown };\n const command = i.tool_input?.command ?? JSON.stringify(i.tool_input ?? {}).slice(0, 2000);\n const response = typeof i.tool_response === 'string' ? i.tool_response : JSON.stringify(i.tool_response ?? '');\n db.insertEvent(sessionId, {\n eventType: 'tool_executed',\n process: i.tool_name,\n pid: process.pid,\n command,\n resultBytes: Buffer.byteLength(response),\n });\n } catch (err) {\n logDebug(`audit hook failed to record event: ${String(err)}`);\n }\n return {};\n };\n}\n","// Claude provider — the Anthropic Claude Agent SDK backend. This is the verbatim\n// move of the original runDiscovery() loop (src/agent.ts) behind the AgentProvider\n// seam, so the Claude path is behavior-preserved. The SDK is dynamically imported\n// so its absence degrades gracefully (ProviderUnavailableError).\n\nimport { createCartographyTools } from '../tools.js';\nimport { safetyHook } from '../safety.js';\nimport { createAuditHook } from '../audit.js';\nimport type { CartographyConfig } from '../types.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentProvider, AgentRunContext } from './types.js';\n\nfunction createClaudeProvider(): AgentProvider {\n return {\n name: 'claude',\n\n async ensureAvailable(_config: CartographyConfig): Promise<void> {\n try {\n await import('@anthropic-ai/claude-agent-sdk');\n } catch {\n throw new Error(\n 'Claude provider unavailable: the @anthropic-ai/claude-agent-sdk package is not installed.\\n' +\n ' Install: npm install @anthropic-ai/claude-agent-sdk',\n );\n }\n },\n\n async *run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent> {\n const { config, db, sessionId, systemPrompt, initialPrompt, onAskUser, deadlineMs } = ctx;\n const { query } = await import('@anthropic-ai/claude-agent-sdk');\n const tools = await createCartographyTools(db, sessionId, {\n onAskUser,\n maxResponseBytes: config.maxToolResponseBytes,\n });\n\n let turnCount = 0;\n\n for await (const msg of query({\n prompt: initialPrompt,\n options: {\n model: config.models.lead,\n maxTurns: config.maxTurns,\n systemPrompt,\n mcpServers: { cartography: tools },\n allowedTools: [\n 'Bash',\n 'mcp__cartography__save_node',\n 'mcp__cartography__save_edge',\n 'mcp__cartography__get_catalog',\n 'mcp__cartography__scan_bookmarks',\n 'mcp__cartography__scan_browser_history',\n 'mcp__cartography__scan_installed_apps',\n 'mcp__cartography__scan_local_databases',\n 'mcp__cartography__scan_k8s_resources',\n 'mcp__cartography__scan_aws_resources',\n 'mcp__cartography__scan_gcp_resources',\n 'mcp__cartography__scan_azure_resources',\n 'mcp__cartography__ask_user',\n ],\n hooks: {\n PreToolUse: [{ matcher: 'Bash', hooks: [safetyHook] }],\n PostToolUse: [{ hooks: [createAuditHook(db, sessionId)] }],\n },\n permissionMode: 'bypassPermissions',\n },\n })) {\n // Wall-clock timeout guard\n if (Date.now() > deadlineMs) {\n yield { kind: 'error', text: 'Discovery timeout — wall-clock limit reached' };\n yield { kind: 'done' };\n return;\n }\n\n if (msg.type === 'assistant') {\n turnCount++;\n yield { kind: 'turn', turn: turnCount };\n\n for (const block of msg.message.content) {\n if (block.type === 'text') {\n yield { kind: 'thinking', text: block.text };\n }\n if (block.type === 'tool_use') {\n yield {\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 (\n typeof block === 'object' &&\n block !== null &&\n 'type' in block &&\n (block as { type: string }).type === 'tool_result'\n ) {\n const tb = block as { tool_use_id?: string; content?: unknown };\n const text = typeof tb.content === 'string' ? tb.content : '';\n yield { kind: 'tool_result', tool: tb.tool_use_id ?? '', output: text };\n }\n }\n }\n }\n\n if (msg.type === 'result') {\n yield { kind: 'done' };\n return;\n }\n }\n },\n };\n}\n\nexport { createClaudeProvider };\n","// The neutral Bash tool for non-SDK providers (OpenAI, Ollama).\n//\n// The Claude Agent SDK ships a built-in `Bash` tool gated by `safetyHook`. OpenAI\n// and Ollama have no such built-in, so this exposes an in-process `Bash` tool whose\n// handler enforces the read-only allowlist via `checkReadOnly` BEFORE delegating to\n// `run()` (which re-checks `checkReadOnly` again — defense in depth). This is the\n// load-bearing safety parity: every provider's shell calls pass through the same\n// authoritative policy in src/allowlist.ts.\n\nimport { z } from 'zod';\nimport { checkReadOnly } from '../allowlist.js';\nimport { run, IS_WIN } from '../platform.js';\nimport type { AgentTool } from '../tools.js';\n\nexport function createBashTool(): AgentTool {\n const shell = IS_WIN ? 'powershell' : 'posix';\n return {\n name: 'Bash',\n description:\n 'Run a read-only shell command (inspect ports, processes, config). ' +\n 'Mutating or destructive commands are blocked by the read-only allowlist.',\n inputShape: { command: z.string().describe('The read-only shell command to run') },\n annotations: { readOnlyHint: true, openWorldHint: true },\n handler: async (args) => {\n const command = String(args['command'] ?? '').trim();\n if (!command) return { content: [{ type: 'text', text: '' }] };\n const decision = checkReadOnly(command, { shell });\n if (!decision.allowed) {\n return {\n content: [\n { type: 'text', text: `BLOCKED: ${decision.reason ?? 'not read-only'} — read-only allowlist policy` },\n ],\n };\n }\n const output = run(command) || '(no output)';\n return { content: [{ type: 'text', text: output }] };\n },\n };\n}\n","// Minimal zod → JSON Schema converter for the discovery tools' input shapes.\n//\n// OpenAI and Ollama function-calling both expect JSON Schema for tool parameters.\n// Rather than add the `zod-to-json-schema` runtime dependency, this implements a\n// small, sufficient converter covering exactly the zod constructs the discovery\n// tools use (string, number with min/max, boolean, enum, array, record, optional,\n// default, describe). An unsupported construct throws a clear Error naming the\n// field, so a future tool can't be silently mis-converted.\n\nimport { z } from 'zod';\n\nexport interface JsonSchema {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n additionalProperties: boolean;\n}\n\n/** Unwrap optional/default/describe wrappers, tracking whether the field is required. */\ninterface Unwrapped {\n schema: z.ZodTypeAny;\n required: boolean;\n description?: string;\n}\n\nfunction unwrap(schema: z.ZodTypeAny): Unwrapped {\n let current = schema;\n let required = true;\n let description: string | undefined = current.description;\n\n // Peel wrapper types until we reach the concrete inner type.\n // zod v4 exposes the inner type via `.def.innerType`.\n for (;;) {\n const def = (current as unknown as { def?: { type?: string; innerType?: z.ZodTypeAny } }).def;\n const typeName = def?.type;\n if (typeName === 'optional' || typeName === 'default') {\n required = false;\n const inner = def?.innerType;\n if (!inner) break;\n current = inner;\n description = description ?? current.description;\n continue;\n }\n if (typeName === 'nullable') {\n const inner = def?.innerType;\n if (!inner) break;\n current = inner;\n description = description ?? current.description;\n continue;\n }\n break;\n }\n return { schema: current, required, description };\n}\n\nfunction convert(schema: z.ZodTypeAny, field: string): Record<string, unknown> {\n const def = (schema as unknown as { def?: Record<string, unknown> }).def;\n const typeName = def?.['type'] as string | undefined;\n\n switch (typeName) {\n case 'string':\n return { type: 'string' };\n case 'number': {\n const out: Record<string, unknown> = { type: 'number' };\n // zod v4 stores checks under def.checks\n const checks = (def?.['checks'] as { _zod?: { def?: { check?: string; value?: number } } }[]) ?? [];\n for (const c of checks) {\n const cd = c?._zod?.def;\n if (cd?.check === 'greater_than') out['minimum'] = cd.value;\n if (cd?.check === 'less_than') out['maximum'] = cd.value;\n }\n return out;\n }\n case 'boolean':\n return { type: 'boolean' };\n case 'enum': {\n const entries = def?.['entries'] as Record<string, string> | undefined;\n const values = entries ? Object.values(entries) : [];\n return { type: 'string', enum: values };\n }\n case 'array': {\n const element = def?.['element'] as z.ZodTypeAny | undefined;\n return { type: 'array', items: element ? convert(unwrap(element).schema, field) : {} };\n }\n case 'record':\n return { type: 'object', additionalProperties: true };\n default:\n throw new Error(\n `zod-schema: unsupported zod construct \"${typeName ?? 'unknown'}\" on field \"${field}\". ` +\n 'Extend src/providers/zod-schema.ts to support it.',\n );\n }\n}\n\n/** Convert a flat ZodRawShape used by the discovery tools to a JSON Schema object. */\nexport function shapeToJsonSchema(shape: z.ZodRawShape): JsonSchema {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const [key, raw] of Object.entries(shape)) {\n const { schema, required: isRequired, description } = unwrap(raw as z.ZodTypeAny);\n const prop = convert(schema, key);\n if (description) prop['description'] = description;\n properties[key] = prop;\n if (isRequired) required.push(key);\n }\n\n return { type: 'object', properties, required, additionalProperties: false };\n}\n","// Provider-neutral audit writer — extracted from the Claude PostToolUse hook\n// (src/audit.ts) so non-SDK providers (OpenAI, Ollama) share the exact same audit\n// trail. Every executed tool writes one `tool_executed` row into `activity_events`.\n// Audit failures must never break discovery, so the write is best-effort.\n\nimport type { CartographyDB } from '../db.js';\nimport { logDebug } from '../logger.js';\n\n/** Record one executed tool call into the catalog's audit trail (best-effort). */\nexport function recordToolEvent(\n db: CartographyDB,\n sessionId: string,\n evt: { tool: string; command: string; response: string },\n): void {\n try {\n db.insertEvent(sessionId, {\n eventType: 'tool_executed',\n process: evt.tool,\n pid: process.pid,\n command: evt.command,\n resultBytes: Buffer.byteLength(evt.response),\n });\n } catch (err) {\n logDebug(`audit writer failed to record event: ${String(err)}`);\n }\n}\n","// Shared tool-calling loop for non-SDK providers (OpenAI, Ollama).\n//\n// Both providers delegate here, parameterized by a `chat()` callback that performs\n// one model round-trip. This loop owns: turn counting + maxTurns enforcement,\n// deadlineMs (wall-clock) checks, dispatch of tool calls to AgentTool handlers\n// (matched by name), the neutral Bash tool, the audit write (db.insertEvent via\n// recordToolEvent), and emission of turn/thinking/tool_call/tool_result/error/done.\n// This guarantees OpenAI and Ollama emit identical DiscoveryEvent kinds.\n\nimport type { CartographyDB } from '../db.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentTool } from '../tools.js';\nimport { recordToolEvent } from './audit.js';\n\n/** A model's request to call one tool. */\nexport interface ToolCall {\n /** Opaque id correlating the call to its result (provider-specific). */\n id: string;\n name: string;\n args: Record<string, unknown>;\n}\n\n/** The neutral result of one model round-trip. */\nexport interface ChatTurn {\n /** Assistant free text for this turn (may be empty). */\n text: string;\n /** Tool calls the model requested this turn (empty ⇒ the model is done). */\n toolCalls: ToolCall[];\n}\n\n/** One executed tool call's result, fed back to the model on the next round-trip. */\nexport interface ToolOutcome {\n id: string;\n name: string;\n output: string;\n}\n\n/**\n * Performs one model round-trip given the prior tool outcomes. On the first call\n * `outcomes` is empty. Returns the assistant turn (text + any tool calls). The\n * callback owns building/threading the provider-specific message history.\n */\nexport type ChatFn = (outcomes: ToolOutcome[]) => Promise<ChatTurn>;\n\nexport interface LoopOptions {\n db: CartographyDB;\n sessionId: string;\n tools: AgentTool[];\n maxTurns: number;\n deadlineMs: number;\n}\n\n/** Dispatch a single tool call to its handler, recording the audit row. */\nasync function dispatchTool(\n call: ToolCall,\n tools: AgentTool[],\n db: CartographyDB,\n sessionId: string,\n): Promise<string> {\n const tool = tools.find((t) => t.name === call.name);\n if (!tool) {\n const text = `ERROR: unknown tool \"${call.name}\"`;\n recordToolEvent(db, sessionId, { tool: call.name, command: JSON.stringify(call.args).slice(0, 2000), response: text });\n return text;\n }\n let output: string;\n try {\n const result = await tool.handler(call.args);\n output = result.content.map((c) => c.text).join('\\n');\n } catch (err) {\n output = `ERROR: ${err instanceof Error ? err.message : String(err)}`;\n }\n // Audit: the Bash command for Bash, else the (clamped) JSON args — mirrors src/audit.ts.\n const command =\n call.name === 'Bash'\n ? String(call.args['command'] ?? '')\n : JSON.stringify(call.args).slice(0, 2000);\n recordToolEvent(db, sessionId, { tool: call.name, command, response: output });\n return output;\n}\n\n/**\n * Drive a provider's tool-calling loop, yielding the neutral DiscoveryEvent stream.\n * Honors maxTurns and the wall-clock deadline; always terminates with `done` (or an\n * `error` + `done` pair on failure).\n */\nexport async function* runToolLoop(opts: LoopOptions, chat: ChatFn): AsyncIterable<DiscoveryEvent> {\n const { db, sessionId, tools, maxTurns, deadlineMs } = opts;\n let outcomes: ToolOutcome[] = [];\n let turn = 0;\n\n try {\n while (turn < maxTurns) {\n if (Date.now() > deadlineMs) {\n yield { kind: 'error', text: 'Discovery timeout — wall-clock limit reached' };\n yield { kind: 'done' };\n return;\n }\n\n const result = await chat(outcomes);\n turn++;\n yield { kind: 'turn', turn };\n\n if (result.text) yield { kind: 'thinking', text: result.text };\n\n if (result.toolCalls.length === 0) {\n // No tool calls ⇒ the model has finished.\n yield { kind: 'done' };\n return;\n }\n\n const nextOutcomes: ToolOutcome[] = [];\n for (const call of result.toolCalls) {\n yield { kind: 'tool_call', tool: call.name, input: call.args };\n const output = await dispatchTool(call, tools, db, sessionId);\n yield { kind: 'tool_result', tool: call.name, output };\n nextOutcomes.push({ id: call.id, name: call.name, output });\n }\n outcomes = nextOutcomes;\n }\n\n // maxTurns exhausted without a natural stop — terminate cleanly.\n yield { kind: 'done' };\n } catch (err) {\n yield { kind: 'error', text: `Discovery error: ${err instanceof Error ? err.message : String(err)}` };\n yield { kind: 'done' };\n }\n}\n","// OpenAI provider — drives discovery via the chat-completions function-calling\n// loop over the SAME neutral tool handlers the Claude path uses. The `openai`\n// package is an optional dependency, lazily imported; its absence (or a missing\n// OPENAI_API_KEY) degrades with a clear Error, never a crash.\n\nimport type { CartographyConfig } from '../types.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentProvider, AgentRunContext } from './types.js';\nimport { buildCartographyToolHandlers, type AgentTool } from '../tools.js';\nimport { createBashTool } from './shell.js';\nimport { shapeToJsonSchema } from './zod-schema.js';\nimport { runToolLoop, type ChatFn, type ToolOutcome } from './loop.js';\n\n/** Minimal structural types for the slice of the OpenAI SDK we use (no `any`). */\ninterface OpenAIToolCall {\n id: string;\n type: 'function';\n function: { name: string; arguments: string };\n}\ninterface OpenAIMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content: string | null;\n tool_calls?: OpenAIToolCall[];\n tool_call_id?: string;\n}\ninterface OpenAIChoice {\n message: { role: 'assistant'; content: string | null; tool_calls?: OpenAIToolCall[] };\n}\ninterface OpenAICompletion {\n choices: OpenAIChoice[];\n}\ninterface OpenAIClient {\n chat: {\n completions: {\n create(req: {\n model: string;\n messages: OpenAIMessage[];\n tools: { type: 'function'; function: { name: string; description: string; parameters: unknown } }[];\n tool_choice: 'auto';\n }): Promise<OpenAICompletion>;\n };\n };\n}\ntype OpenAIModule = { default: new (opts: { apiKey: string; baseURL?: string }) => OpenAIClient };\n\nfunction toOpenAITools(tools: AgentTool[]) {\n return tools.map((t) => ({\n type: 'function' as const,\n function: { name: t.name, description: t.description, parameters: shapeToJsonSchema(t.inputShape) },\n }));\n}\n\nfunction createOpenAIProvider(): AgentProvider {\n return {\n name: 'openai',\n\n async ensureAvailable(_config: CartographyConfig): Promise<void> {\n try {\n await import('openai' as string);\n } catch {\n throw new Error(\n 'OpenAI provider unavailable: the `openai` package is not installed.\\n' +\n ' Install: npm install openai',\n );\n }\n if (!process.env['OPENAI_API_KEY']) {\n throw new Error(\n 'OpenAI provider unavailable: OPENAI_API_KEY is not set.\\n' +\n ' Set it: export OPENAI_API_KEY=sk-...',\n );\n }\n },\n\n async *run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent> {\n const { config, db, sessionId, systemPrompt, initialPrompt, onAskUser, deadlineMs } = ctx;\n const mod = (await import('openai' as string)) as unknown as OpenAIModule;\n const apiKey = process.env['OPENAI_API_KEY'] ?? '';\n const baseURL = process.env['OPENAI_BASE_URL'];\n const client = new mod.default({ apiKey, ...(baseURL ? { baseURL } : {}) });\n\n const handlers = await buildCartographyToolHandlers(db, sessionId, {\n onAskUser,\n maxResponseBytes: config.maxToolResponseBytes,\n });\n const tools: AgentTool[] = [...handlers, createBashTool()];\n const openaiTools = toOpenAITools(tools);\n\n const messages: OpenAIMessage[] = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: initialPrompt },\n ];\n\n const chat: ChatFn = async (outcomes: ToolOutcome[]) => {\n // Append the previous turn's tool results before the next round-trip.\n for (const oc of outcomes) {\n messages.push({ role: 'tool', tool_call_id: oc.id, content: oc.output });\n }\n\n const completion = await client.chat.completions.create({\n model: config.models.lead,\n messages,\n tools: openaiTools,\n tool_choice: 'auto',\n });\n const choice = completion.choices[0]?.message;\n const text = choice?.content ?? '';\n const toolCalls = choice?.tool_calls ?? [];\n\n // Record the assistant turn so the next round-trip has full context.\n messages.push({ role: 'assistant', content: text || null, ...(toolCalls.length ? { tool_calls: toolCalls } : {}) });\n\n return {\n text,\n toolCalls: toolCalls.map((tc) => ({\n id: tc.id,\n name: tc.function.name,\n args: parseArgs(tc.function.arguments),\n })),\n };\n };\n\n yield* runToolLoop({ db, sessionId, tools, maxTurns: config.maxTurns, deadlineMs }, chat);\n },\n };\n}\n\nfunction parseArgs(raw: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(raw || '{}') as unknown;\n return typeof parsed === 'object' && parsed !== null ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nexport { createOpenAIProvider };\n","// Ollama provider — drives discovery via Ollama's native /api/chat endpoint using\n// built-in `fetch` (no SDK dependency). Reuses the SAME neutral tool handlers and\n// the shared tool-calling loop as OpenAI, so it emits identical DiscoveryEvent\n// kinds and shares the read-only/audit safety path. Resolves the host from\n// OLLAMA_HOST (default http://127.0.0.1:11434); an unreachable host degrades with a\n// clear `ollama serve` hint rather than crashing.\n\nimport type { CartographyConfig } from '../types.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentProvider, AgentRunContext } from './types.js';\nimport { buildCartographyToolHandlers, type AgentTool } from '../tools.js';\nimport { createBashTool } from './shell.js';\nimport { shapeToJsonSchema } from './zod-schema.js';\nimport { runToolLoop, type ChatFn, type ToolOutcome } from './loop.js';\n\nconst DEFAULT_HOST = 'http://127.0.0.1:11434';\n\nfunction host(): string {\n return (process.env['OLLAMA_HOST'] || DEFAULT_HOST).replace(/\\/+$/, '');\n}\n\n/** Minimal structural types for the slice of the Ollama /api/chat API we use. */\ninterface OllamaToolCall {\n function: { name: string; arguments: Record<string, unknown> };\n}\ninterface OllamaMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content: string;\n tool_calls?: OllamaToolCall[];\n}\ninterface OllamaChatResponse {\n message?: { role: 'assistant'; content?: string; tool_calls?: OllamaToolCall[] };\n}\n\nfunction toOllamaTools(tools: AgentTool[]) {\n return tools.map((t) => ({\n type: 'function' as const,\n function: { name: t.name, description: t.description, parameters: shapeToJsonSchema(t.inputShape) },\n }));\n}\n\nfunction createOllamaProvider(): AgentProvider {\n return {\n name: 'ollama',\n\n async ensureAvailable(_config: CartographyConfig): Promise<void> {\n const base = host();\n try {\n const res = await fetch(`${base}/api/tags`, { method: 'GET' });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n } catch {\n throw new Error(\n `Ollama provider unavailable: not reachable at ${base}.\\n` +\n ' Start it: ollama serve (or set OLLAMA_HOST=<url>)',\n );\n }\n },\n\n async *run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent> {\n const { config, db, sessionId, systemPrompt, initialPrompt, onAskUser, deadlineMs } = ctx;\n const base = host();\n\n const handlers = await buildCartographyToolHandlers(db, sessionId, {\n onAskUser,\n maxResponseBytes: config.maxToolResponseBytes,\n });\n const tools: AgentTool[] = [...handlers, createBashTool()];\n const ollamaTools = toOllamaTools(tools);\n\n const messages: OllamaMessage[] = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: initialPrompt },\n ];\n\n const chat: ChatFn = async (outcomes: ToolOutcome[]) => {\n // Ollama tool results are matched positionally (no per-call id).\n for (const oc of outcomes) {\n messages.push({ role: 'tool', content: oc.output });\n }\n\n const res = await fetch(`${base}/api/chat`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ model: config.models.lead, messages, tools: ollamaTools, stream: false }),\n });\n if (!res.ok) {\n throw new Error(`Ollama /api/chat returned HTTP ${res.status}`);\n }\n const data = (await res.json()) as OllamaChatResponse;\n const text = data.message?.content ?? '';\n const toolCalls = data.message?.tool_calls ?? [];\n\n messages.push({\n role: 'assistant',\n content: text,\n ...(toolCalls.length ? { tool_calls: toolCalls } : {}),\n });\n\n return {\n text,\n toolCalls: toolCalls.map((tc, i) => ({\n id: `${tc.function.name}:${i}`,\n name: tc.function.name,\n args: tc.function.arguments ?? {},\n })),\n };\n };\n\n yield* runToolLoop({ db, sessionId, tools, maxTurns: config.maxTurns, deadlineMs }, chat);\n },\n };\n}\n\nexport { createOllamaProvider };\n","// The default provider registry: the single source of truth for valid provider\n// names. Factories lazily build providers; each provider lazily imports its own\n// optional dependency, so importing this module never pulls an optional SDK.\n\nimport { ProviderRegistry } from './types.js';\nimport { createClaudeProvider } from './claude.js';\nimport { createOpenAIProvider } from './openai.js';\nimport { createOllamaProvider } from './ollama.js';\n\nexport function createDefaultRegistry(): ProviderRegistry {\n const r = new ProviderRegistry();\n r.register('claude', createClaudeProvider);\n r.register('openai', createOpenAIProvider);\n r.register('ollama', createOllamaProvider);\n return r;\n}\n\nexport const defaultProviderRegistry = createDefaultRegistry();\n","import type { CartographyDB } from './db.js';\nimport type { CartographyConfig } from './types.js';\nimport { IS_WIN, IS_MAC, PLATFORM } from './platform.js';\nimport { defaultProviderRegistry } from './providers/registry.js';\nimport type { AgentRunContext } from './providers/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: 'error'; text: string }\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 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 // Platform-specific instructions for the agent\n const platformName = IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux';\n const networkScanCmd = IS_WIN\n ? 'Get-NetTCPConnection -State Listen (PowerShell) → identify all listening ports/processes'\n : IS_MAC\n ? 'lsof -iTCP -sTCP:LISTEN -n -P && ps aux → identify all listening ports/processes'\n : 'ss -tlnp && ps aux → identify all listening ports/processes';\n const readOnlyTools = IS_WIN\n ? 'Get-NetTCPConnection, Get-Process, Get-Service, Get-ChildItem, curl, docker inspect, kubectl get'\n : IS_MAC\n ? 'lsof, ps, cat, head, curl -s, docker inspect, kubectl get'\n : 'ss, ps, cat, head, curl -s, docker inspect, kubectl get';\n const processCmd = IS_WIN ? 'Get-Process' : 'ps aux';\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.\nPLATFORM: ${platformName} (${PLATFORM})\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: ${networkScanCmd}\n Also run: ${processCmd} → identify running services\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\nPLATFORM-SPECIFIC NOTES (${platformName}):\n${IS_WIN ? `• Use PowerShell commands: Get-NetTCPConnection, Get-Process, Get-Service, Get-ChildItem\n• Do NOT use Unix commands (ss, ps aux, find, which, head, grep) — they won't work on Windows\n• Use $env:LOCALAPPDATA, $env:APPDATA for app data paths\n• Registry scan for installed programs is handled by scan_installed_apps` : IS_MAC ? `• Use lsof -iTCP -sTCP:LISTEN -n -P for port scanning (ss is NOT available on macOS)\n• Use ps aux for process listing\n• Applications are in /Applications and ~/Applications\n• Homebrew (brew) for package management` : `• Use ss -tlnp for port scanning\n• Use ps aux for process listing\n• Check dpkg, snap, flatpak for installed packages\n• Check Snap/Flatpak browser variants for bookmarks`}\n\nRULES:\n• Read-only only (${readOnlyTools})\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• Max crawl depth: ${config.maxDepth} hops from an entry point — do not chase leads deeper than this\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 const MAX_DISCOVERY_MS = 30 * 60 * 1000; // 30-minute wall-clock timeout\n const startTime = Date.now();\n const deadlineMs = startTime + MAX_DISCOVERY_MS;\n\n // Resolve the agent backend from the registry. The Claude path is byte-for-byte\n // the original loop, moved verbatim into src/providers/claude.ts; OpenAI/Ollama\n // run the shared tool-calling loop over the same neutral tool handlers.\n const provider = defaultProviderRegistry.resolve(config.provider ?? 'claude');\n await provider.ensureAvailable(config);\n\n const ctx: AgentRunContext = {\n config,\n db,\n sessionId,\n systemPrompt,\n initialPrompt,\n onAskUser,\n deadlineMs,\n };\n\n try {\n for await (const event of provider.run(ctx)) {\n onEvent?.(event);\n if (event.kind === 'done') return;\n // Fallback wall-clock guard in addition to the provider's own check.\n if (Date.now() > deadlineMs) {\n onEvent?.({ kind: 'error', text: `Discovery timeout after ${MAX_DISCOVERY_MS / 60000} minutes` });\n onEvent?.({ kind: 'done' });\n return;\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n onEvent?.({ kind: 'error', text: `Discovery error: ${message}` });\n throw err;\n }\n}\n\n","/**\n * Config-file layer (2.5).\n *\n * Reads and validates a JSON `cartography.config.json` file and resolves it into\n * a fully-populated {@link CartographyConfig}. The reader is deliberately general:\n * WS 2.11 (central-org sync) consumes this same module and extends the file schema\n * (`ConfigFileSchema`) with its own block, so all file → config merging logic lives\n * here in one place.\n *\n * Security: untrusted file input is parsed with `JSON.parse` (no `eval`, no dynamic\n * `require`) and validated with a `.strict()` Zod schema that rejects unknown keys.\n * File contents are never executed; values flow only into the existing sanitized\n * config/discovery sinks.\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { CartographyConfig, CentralDbConfig } from './types.js';\nimport { ConfigFileSchema, centralDbFromEnv, defaultConfig } from './types.js';\n\n/** Raised when a config file cannot be read, parsed, or validated. */\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\n/**\n * Read and validate a JSON config file at `path`, returning a fully-resolved\n * {@link CartographyConfig}. Merges the file's `schedule`/`entryPoints`/`dbPath`/\n * `organization` into `defaultConfig` so every existing config invariant (e.g.\n * `agentModel === models.lead`) is preserved.\n *\n * Precedence for the shared `entryPoints`/`dbPath`: a value inside the `schedule`\n * block wins over the same file-level key (the schedule block is the more specific\n * intent for a scheduled run).\n *\n * @throws {ConfigError} when the file is missing/unreadable, the JSON is malformed,\n * or the content fails schema validation (including unknown keys via `.strict()`).\n */\nexport function loadConfig(path: string): CartographyConfig {\n const file = readConfigFile(path);\n\n const overrides: Partial<CartographyConfig> = {};\n if (file.organization) overrides.organization = file.organization;\n\n const entryPoints = file.schedule?.entryPoints ?? file.entryPoints;\n if (entryPoints) overrides.entryPoints = [...entryPoints];\n\n const dbPath = file.schedule?.dbPath ?? file.dbPath;\n if (dbPath) overrides.dbPath = dbPath;\n\n if (file.schedule) overrides.schedule = file.schedule;\n\n // 2.11 central-DB sync. Precedence (low→high): file `centralDb` < env\n // (`CARTOGRAPHY_CENTRAL_*`) < anything `defaultConfig` later layers. We merge the\n // file block under env here (env wins per field) and pass the result as the\n // explicit `centralDb` override; `defaultConfig` then validates it once. A file\n // `url` + env `token` compose into a valid block; an invalid one is dropped there.\n if (file.centralDb) {\n // `defaultConfig` re-validates the assembled block; pass the (possibly partial)\n // merge as a centralDb override. The cast is sound because resolveCentralDb\n // safeParses before it lands on the resolved config.\n const merged: Partial<CentralDbConfig> = { ...file.centralDb, ...centralDbFromEnv() };\n overrides.centralDb = merged as CentralDbConfig;\n }\n\n return defaultConfig(overrides);\n}\n\n/**\n * Lower-level reader: parse + validate a config file into its typed shape without\n * resolving it against `defaultConfig`. Useful for callers (e.g. WS 2.11) that need\n * the raw file shape rather than a merged runtime config.\n *\n * @throws {ConfigError} on missing file, malformed JSON, or schema-validation failure.\n */\nexport function readConfigFile(path: string): import('./types.js').ConfigFile {\n let raw: string;\n try {\n raw = readFileSync(path, 'utf-8');\n } catch (err) {\n throw new ConfigError(\n `Cannot read config file ${path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n let json: unknown;\n try {\n json = JSON.parse(raw);\n } catch (err) {\n throw new ConfigError(\n `Invalid JSON in ${path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const parsed = ConfigFileSchema.safeParse(json);\n if (!parsed.success) {\n const detail = parsed.error.issues\n .map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('; ');\n throw new ConfigError(`Invalid config in ${path}: ${detail}`);\n }\n return parsed.data;\n}\n","/**\n * Scheduled discovery (2.5).\n *\n * A thin, dependency-free cron driver over the existing read-only discovery and\n * drift machinery. Two pure pieces — {@link parseCron} and {@link nextRun} — own\n * a minimal 5-field cron grammar (UTC, no NPM dependency); {@link runOnce} runs a\n * single deterministic local scan and returns the topology drift relative to the\n * prior run. `runOnce` never invokes the Claude/agent loop and needs no API key.\n *\n * Reuse, not reimplementation: discovery is `runLocalDiscovery` and diffing is the\n * pure `diffTopology` engine (surfaced here via the 2.1 incremental `mode:'update'`\n * rescan, which already returns the delta). This module adds scheduling + per-run\n * persistence around them.\n */\n\nimport type { CartographyConfig } from './types.js';\nimport type { TopologyDelta } from './diff.js';\nimport type { CartographyDB } from './db.js';\nimport { runLocalDiscovery } from './discovery/local.js';\nimport { diffTopology } from './diff.js';\nimport { logInfo } from './logger.js';\n\n/** Parsed allowed-value sets for each cron field (all UTC). */\nexport interface CronFields {\n /** 0–59 */\n minute: Set<number>;\n /** 0–23 */\n hour: Set<number>;\n /** 1–31 */\n dom: Set<number>;\n /** 1–12 */\n month: Set<number>;\n /** 0–6 (Sunday = 0) */\n dow: Set<number>;\n}\n\ninterface FieldSpec {\n name: keyof CronFields;\n min: number;\n max: number;\n}\n\nconst FIELD_SPECS: readonly FieldSpec[] = [\n { name: 'minute', min: 0, max: 59 },\n { name: 'hour', min: 0, max: 23 },\n { name: 'dom', min: 1, max: 31 },\n { name: 'month', min: 1, max: 12 },\n { name: 'dow', min: 0, max: 7 }, // 7 and 0 both mean Sunday; normalized to 0 below\n];\n\n/** Parse one cron field into the set of integers it matches within [min, max]. */\nfunction parseField(raw: string, spec: FieldSpec): Set<number> {\n const out = new Set<number>();\n const add = (n: number): void => {\n if (!Number.isInteger(n) || n < spec.min || n > spec.max) {\n throw new RangeError(`Invalid value \"${n}\" in cron field \"${spec.name}\" (allowed ${spec.min}-${spec.max})`);\n }\n // Day-of-week 7 normalizes to 0 (both Sunday).\n out.add(spec.name === 'dow' && n === 7 ? 0 : n);\n };\n\n for (const part of raw.split(',')) {\n if (part === '') {\n throw new RangeError(`Empty term in cron field \"${spec.name}\"`);\n }\n // Split an optional step: \"<range>/<step>\".\n const [rangePart, stepPart, ...rest] = part.split('/');\n if (rest.length > 0) {\n throw new RangeError(`Malformed step in cron field \"${spec.name}\": \"${part}\"`);\n }\n let step = 1;\n if (stepPart !== undefined) {\n step = Number(stepPart);\n if (!Number.isInteger(step) || step < 1) {\n throw new RangeError(`Invalid step \"${stepPart}\" in cron field \"${spec.name}\"`);\n }\n }\n\n let lo: number;\n let hi: number;\n if (rangePart === '*') {\n lo = spec.min;\n hi = spec.max;\n } else if (rangePart.includes('-')) {\n const [a, b, ...extra] = rangePart.split('-');\n if (extra.length > 0) {\n throw new RangeError(`Malformed range in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n lo = Number(a);\n hi = Number(b);\n if (!Number.isInteger(lo) || !Number.isInteger(hi)) {\n throw new RangeError(`Non-numeric range in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n if (lo > hi) {\n throw new RangeError(`Descending range in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n } else {\n const n = Number(rangePart);\n if (!Number.isInteger(n)) {\n throw new RangeError(`Non-numeric value in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n // A bare value with a step (\"5/10\") steps from the value to the field max.\n lo = n;\n hi = stepPart !== undefined ? spec.max : n;\n }\n\n for (let v = lo; v <= hi; v += step) add(v);\n }\n\n if (out.size === 0) {\n throw new RangeError(`Cron field \"${spec.name}\" matched no values`);\n }\n return out;\n}\n\n/**\n * Parse a 5-field cron expression (`minute hour dom month dow`, UTC) into its\n * matching value sets. Grammar per field: star, star-slash-n, `a`, `a-b`,\n * `a-b`-slash-n, `a`-slash-n, and comma lists of those. Day-of-week `7`\n * normalizes to `0` (Sunday).\n *\n * @throws {RangeError} when the expression is not exactly 5 fields, or any field\n * is out of range / non-numeric / malformed.\n */\nexport function parseCron(expr: string): CronFields {\n const fields = expr.trim().split(/\\s+/);\n if (fields.length !== 5) {\n throw new RangeError(`Cron expression must have 5 fields (got ${fields.length}): \"${expr}\"`);\n }\n const [minute, hour, dom, month, dow] = FIELD_SPECS.map((spec, i) => parseField(fields[i]!, spec));\n return { minute: minute!, hour: hour!, dom: dom!, month: month!, dow: dow! };\n}\n\n/** True when `date` (UTC) satisfies all cron fields, honoring dom/dow OR-semantics. */\nfunction matches(fields: CronFields, date: Date): boolean {\n if (!fields.minute.has(date.getUTCMinutes())) return false;\n if (!fields.hour.has(date.getUTCHours())) return false;\n if (!fields.month.has(date.getUTCMonth() + 1)) return false;\n\n const domRestricted = fields.dom.size !== 31;\n const dowRestricted = fields.dow.size !== 7;\n const domOk = fields.dom.has(date.getUTCDate());\n const dowOk = fields.dow.has(date.getUTCDay());\n\n // Standard cron OR-semantics: when both dom and dow are restricted, either may\n // match; when only one is restricted, only that one constrains.\n if (domRestricted && dowRestricted) return domOk || dowOk;\n if (domRestricted) return domOk;\n if (dowRestricted) return dowOk;\n return true;\n}\n\n/** Upper bound on the forward search: ~4 years of minutes (covers Feb-29 schedules). */\nconst MAX_SEARCH_MINUTES = 4 * 366 * 24 * 60;\n\n/**\n * The earliest scheduled instant strictly after `after` (UTC, second/ms truncated).\n * Deterministic and pure: same `expr` + `after` always yields the same Date.\n *\n * @throws {RangeError} when `expr` is invalid, or no match is found within ~4 years.\n */\nexport function nextRun(expr: string, after: Date): Date {\n const fields = parseCron(expr);\n // Truncate to the minute and step forward at least one minute.\n const cursor = new Date(after.getTime());\n cursor.setUTCSeconds(0, 0);\n cursor.setUTCMinutes(cursor.getUTCMinutes() + 1);\n\n for (let i = 0; i < MAX_SEARCH_MINUTES; i++) {\n if (matches(fields, cursor)) return new Date(cursor.getTime());\n cursor.setUTCMinutes(cursor.getUTCMinutes() + 1);\n }\n throw new RangeError(`No cron match for \"${expr}\" within ~4 years after ${after.toISOString()}`);\n}\n\nexport interface ScheduledRunResult {\n /** The session this run scanned (reused in place across runs). */\n sessionId: string;\n /** The prior session used as the diff base, or `undefined` on the first run. */\n baseSessionId?: string;\n /** Topology drift this run observed. */\n delta: TopologyDelta;\n /** Node count after the scan. */\n nodes: number;\n /** Edge count after the scan. */\n edges: number;\n /** Ids of the scanners that ran. */\n scanners: string[];\n}\n\n/**\n * Run one deterministic local-discovery pass and return its topology drift.\n *\n * Reuses the most recent prior `discover` session for this config's tenant and\n * rescans it in place via the 2.1 incremental `mode:'update'` path (same session\n * id, prunes vanished entities, stamps `last_scanned_at`), which already returns\n * the delta from the pure `diffTopology` engine. When no prior session exists, it\n * creates a fresh session, replace-scans it, and reports the whole topology as\n * `added` (diffed against an empty base).\n *\n * Read-only and API-key-free. Does **not** persist the drift run — the caller does\n * (so tests can inspect the delta first). All progress goes to stderr.\n */\nexport async function runOnce(cfg: CartographyConfig, db: CartographyDB): Promise<ScheduledRunResult> {\n const prior = db.getLatestSession('discover');\n\n if (prior) {\n // Incremental in-place rescan (2.1): the delta is computed and returned by\n // runLocalDiscovery itself, so no second diff pass is needed.\n const r = await runLocalDiscovery(db, prior.id, {\n hint: cfg.entryPoints.join(','),\n plugins: cfg.plugins,\n mode: 'update',\n onProgress: (line) => logInfo(`scan: ${line}`),\n });\n const delta = r.delta ?? diffTopology({ nodes: [], edges: [] }, { nodes: [], edges: [] });\n logInfo('scheduled run complete', { sessionId: prior.id, base: prior.id, ...delta.summary });\n return { sessionId: prior.id, baseSessionId: prior.id, delta, nodes: r.nodes, edges: r.edges, scanners: r.scanners };\n }\n\n // First run: no prior topology — create a session, scan it, diff against empty.\n const sessionId = db.createSession('discover', cfg);\n try {\n const r = await runLocalDiscovery(db, sessionId, {\n hint: cfg.entryPoints.join(','),\n plugins: cfg.plugins,\n mode: 'replace',\n onProgress: (line) => logInfo(`scan: ${line}`),\n });\n const current = { nodes: db.getNodes(sessionId), edges: db.getEdges(sessionId) };\n const delta = diffTopology({ nodes: [], edges: [] }, current);\n logInfo('scheduled run complete', { sessionId, base: null, ...delta.summary });\n return { sessionId, baseSessionId: undefined, delta, nodes: r.nodes, edges: r.edges, scanners: r.scanners };\n } finally {\n db.endSession(sessionId);\n }\n}\n","import { mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { CartographyDB, GraphSummary } from './db.js';\nimport type { ComplianceReport } from './compliance/types.js';\nimport type { NodeRow, EdgeRow, TopologyDiff } from './types.js';\nimport { NODE_TYPE_GROUPS } 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 for (const [layer, types] of Object.entries(NODE_TYPE_GROUPS)) {\n if ((types as readonly string[]).includes(type)) return layer;\n }\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\n// ── Diff / Drift Mermaid ───────────────────────────────────────────────────────\n\nconst DIFF_CLASSES: Record<'added' | 'removed' | 'changed' | 'context', string> = {\n added: 'fill:#0d3d0d,stroke:#22c55e,color:#86efac',\n removed: 'fill:#3d0d0d,stroke:#ef4444,color:#fca5a5',\n changed: 'fill:#3d2f0d,stroke:#f59e0b,color:#fcd34d',\n context: 'fill:#1e1e1e,stroke:#555555,color:#999999',\n};\n\nfunction diffNodeLabel(node: NodeRow, suffix?: string): string {\n const icon = MERMAID_ICONS[node.type] ?? '?';\n const extra = suffix ? `<br/><small>Δ ${suffix}</small>` : '';\n return `[\"${icon} <b>${node.name}</b><br/><small>${node.type}</small>${extra}\"]`;\n}\n\n/**\n * Render a topology diff as a Mermaid graph: added nodes/edges in green, removed\n * in red, changed in amber. Endpoints of added/removed edges that are otherwise\n * unchanged are drawn as neutral \"context\" nodes so every edge has both ends.\n */\nexport function generateDiffMermaid(diff: TopologyDiff): string {\n const total =\n diff.summary.nodesAdded + diff.summary.nodesRemoved + diff.summary.nodesChanged +\n diff.summary.edgesAdded + diff.summary.edgesRemoved;\n if (total === 0) return 'graph TB\\n nodrift[\"✓ No drift between the two sessions\"]';\n\n const lines: string[] = ['graph TB'];\n for (const [k, style] of Object.entries(DIFF_CLASSES)) lines.push(` classDef ${k} ${style}`);\n lines.push('');\n\n type Cls = 'added' | 'removed' | 'changed' | 'context';\n const rank: Record<Cls, number> = { added: 3, removed: 3, changed: 3, context: 0 };\n const entries = new Map<string, { node: NodeRow; cls: Cls; suffix?: string }>();\n const place = (node: NodeRow, cls: Cls, suffix?: string) => {\n const prev = entries.get(node.id);\n if (prev && rank[prev.cls] >= rank[cls]) return;\n entries.set(node.id, { node, cls, suffix });\n };\n\n for (const n of diff.nodes.added) place(n, 'added');\n for (const n of diff.nodes.removed) place(n, 'removed');\n for (const c of diff.nodes.changed) place(c.after, 'changed', c.changedFields.join(', '));\n\n // Stub a neutral context node for an edge endpoint we have no full record for.\n const contextNode = (id: string): NodeRow => ({\n id, type: 'unknown', name: id, discoveredVia: 'diff',\n confidence: 1, metadata: {}, tags: [], sessionId: '', discoveredAt: '', depth: 0,\n });\n const ensureEndpoint = (id: string) => { if (!entries.has(id)) place(contextNode(id), 'context'); };\n for (const e of [...diff.edges.added, ...diff.edges.removed]) { ensureEndpoint(e.sourceId); ensureEndpoint(e.targetId); }\n\n for (const { node, cls, suffix } of entries.values()) {\n lines.push(` ${sanitize(node.id)}${diffNodeLabel(node, suffix)}:::${cls}`);\n }\n lines.push('');\n\n for (const e of diff.edges.added) {\n const label = EDGE_LABELS[e.relationship] ?? e.relationship;\n lines.push(` ${sanitize(e.sourceId)} ==>|\"+ ${label}\"| ${sanitize(e.targetId)}`);\n }\n for (const e of diff.edges.removed) {\n const label = EDGE_LABELS[e.relationship] ?? e.relationship;\n lines.push(` ${sanitize(e.sourceId)} -.->|\"- ${label}\"| ${sanitize(e.targetId)}`);\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: ${node.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 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 }, 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// ── 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-product{font-size:15px;font-weight:600;color:var(--text-muted)}\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 <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 <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// ── Cost (FinOps) export (3.3) ────────────────────────────────────────────────\n\n/**\n * CSV-escape a field: quote when it contains a comma/quote/newline, and neutralize\n * spreadsheet formula injection by prefixing a leading `=`/`+`/`-`/`@` with a quote\n * (a hostile `owner`/`domain` from an imported CSV must not execute in Excel/Sheets).\n */\nfunction csvField(v: string | number): string {\n let s = String(v);\n if (/^[=+\\-@]/.test(s)) s = `'${s}`;\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n}\n\n/**\n * Cost rolled up by domain and owner as CSV — the FinOps export. One block per\n * scope; rows are bucketed by `(currency, period)` so mixed currencies are never\n * summed into one figure.\n */\nexport function exportCostCSV(summary: GraphSummary): string {\n const rows = ['scope,key,currency,period,total,nodes'];\n for (const c of summary.costByDomain) {\n rows.push(['domain', c.domain, c.currency, c.period, c.total, c.nodes].map(csvField).join(','));\n }\n for (const c of summary.costByOwner) {\n rows.push(['owner', c.owner, c.currency, c.period, c.total, c.nodes].map(csvField).join(','));\n }\n return rows.join('\\n') + '\\n';\n}\n\n/** The same cost rollup as JSON for programmatic consumers. */\nexport function exportCostSummary(summary: GraphSummary): string {\n return JSON.stringify({\n costByDomain: summary.costByDomain,\n costByOwner: summary.costByOwner,\n costCoverage: summary.costCoverage,\n }, null, 2);\n}\n\n// ── Compliance report export (3.4) ────────────────────────────────────────────\n\nconst SEVERITY_RANK: Record<string, number> = { critical: 0, high: 1, medium: 2, low: 3 };\n\n/**\n * Render a `ComplianceReport` as `json`, `markdown`, or `mermaid`. The Mermaid form\n * reuses the diff exporter's severity-coloured `classDef` pattern: one node per gap\n * coloured by severity, with a `\"✓ No compliance gaps\"` empty state.\n */\nexport function exportComplianceReport(report: ComplianceReport, format: 'json' | 'markdown' | 'mermaid'): string {\n if (format === 'json') return JSON.stringify(report, null, 2);\n\n if (format === 'markdown') {\n const scoreStr = report.score === null ? 'n/a' : `${report.score}/100`;\n const out: string[] = [\n `# Compliance — ${report.rulesetName} v${report.rulesetVersion}`,\n ``,\n `**Status:** ${report.status.toUpperCase()} · **Score:** ${scoreStr}`,\n ``,\n `| Controls | Count |`, `|----------|-------|`,\n `| Passed | ${report.totals.passed} |`,\n `| Failed | ${report.totals.failed} |`,\n `| Not applicable | ${report.totals.notApplicable} |`,\n `| Total | ${report.totals.rules} |`,\n ``,\n `| Severity | Failed | Passed |`, `|----------|--------|--------|`,\n ...(['critical', 'high', 'medium', 'low'] as const).map(\n (s) => `| ${s} | ${report.bySeverity[s].failed} | ${report.bySeverity[s].passed} |`,\n ),\n ];\n if (report.gaps.length === 0) {\n out.push(``, `✓ No compliance gaps.`);\n } else {\n out.push(``, `## Gaps`);\n for (const g of [...report.gaps].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity])) {\n out.push(``, `### [${g.severity}] ${g.control} — ${g.title}`, ...g.nodeIds.map((id) => `- \\`${id}\\``));\n }\n }\n return out.join('\\n');\n }\n\n // mermaid\n const lines: string[] = [\n 'graph TB',\n ' classDef critical fill:#7f1d1d,stroke:#ef4444,color:#fff;',\n ' classDef high fill:#7c2d12,stroke:#f97316,color:#fff;',\n ' classDef medium fill:#713f12,stroke:#eab308,color:#fff;',\n ' classDef low fill:#1e3a5f,stroke:#3b82f6,color:#fff;',\n ];\n if (report.gaps.length === 0) {\n lines.push(' ok[\"✓ No compliance gaps\"]');\n return lines.join('\\n');\n }\n // Strip Mermaid-breaking chars (quote/bracket/newline) defensively, even though\n // titles come from trusted bundled rulesets. Order by severity for parity with md.\n const mmSafe = (s: string): string => s.replace(/[\"\\]\\r\\n]/g, \"'\");\n let i = 0;\n for (const g of [...report.gaps].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity])) {\n const gid = `g${i++}`;\n lines.push(` ${gid}[\"${mmSafe(g.control)}: ${mmSafe(g.title)} (${g.nodeIds.length})\"]:::${g.severity}`);\n }\n return lines.join('\\n');\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'],\n): void {\n mkdirSync(outputDir, { 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\n if (formats.includes('mermaid')) {\n writeFileSync(join(outputDir, 'topology.mermaid'), generateTopologyMermaid(nodes, edges));\n writeFileSync(join(outputDir, 'dependencies.mermaid'), generateDependencyMermaid(nodes, edges));\n }\n\n if (formats.includes('json')) {\n writeFileSync(join(outputDir, 'catalog.json'), exportJSON(db, sessionId));\n }\n\n if (formats.includes('yaml')) {\n writeFileSync(join(outputDir, 'catalog-info.yaml'), exportBackstageYAML(nodes, edges));\n }\n\n if (formats.includes('html') || formats.includes('map') || formats.includes('discovery')) {\n writeFileSync(join(outputDir, 'discovery.html'), exportDiscoveryApp(nodes, edges));\n }\n\n if (formats.includes('cost')) {\n const summary = db.getGraphSummary(sessionId);\n writeFileSync(join(outputDir, 'cost-by-domain.csv'), exportCostCSV(summary));\n writeFileSync(join(outputDir, 'cost-summary.json'), exportCostSummary(summary));\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 // Pre-parse occupied coordinates to avoid repeated string splitting\n const parsedOccupied: AxialCoord[] = [];\n for (const oKey of occupied) {\n const [oq, or] = oKey.split(',').map(Number);\n parsedOccupied.push({ q: oq, r: or });\n }\n\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 if (occupied.has(key(tp.q, tp.r))) { fits = false; break; }\n\n for (const oc of parsedOccupied) {\n if (hexDistance(tp, oc) < gap) {\n fits = false;\n break;\n }\n }\n if (!fits) break;\n }\n\n if (fits) return candidate;\n }\n }\n\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-to-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","/**\n * Plain-text compliance report formatter (3.4). Colour-free so it lives outside\n * `cli.ts` (which owns the colour helpers and is excluded from coverage).\n */\n\nimport type { ComplianceReport } from './types.js';\n\nconst NODE_CAP = 50;\n\n/** Render a `ComplianceReport` as a plain-text summary with `✓`/`✗` markers. */\nexport function formatComplianceText(report: ComplianceReport): string {\n const scoreStr = report.score === null ? 'n/a' : `${report.score}/100`;\n const lines: string[] = [\n `Compliance: ${report.rulesetName} v${report.rulesetVersion} — ${report.status.toUpperCase()} (score ${scoreStr})`,\n `Controls: ${report.totals.passed} passed, ${report.totals.failed} failed, ${report.totals.notApplicable} n/a (of ${report.totals.rules})`,\n '',\n 'By severity (failed/passed):',\n ...(['critical', 'high', 'medium', 'low'] as const).map(\n (s) => ` - ${s}: ${report.bySeverity[s].failed} failed / ${report.bySeverity[s].passed} passed`,\n ),\n ];\n\n if (report.gaps.length === 0) {\n lines.push('', '✓ No compliance gaps.');\n return lines.join('\\n');\n }\n\n lines.push('', `Gaps (${report.gaps.length}):`);\n for (const g of report.gaps) {\n lines.push(` ✗ [${g.severity}] ${g.control} — ${g.title}`);\n const shown = g.nodeIds.slice(0, NODE_CAP);\n for (const id of shown) lines.push(` ${id}`);\n if (g.nodeIds.length > NODE_CAP) lines.push(` … +${g.nodeIds.length - NODE_CAP} more`);\n }\n return lines.join('\\n');\n}\n","/**\n * Cost attribution (3.3) — turn the topology into a FinOps lens.\n *\n * A pluggable `CostSource` yields `{ owner, cost }` keyed by node id; `enrichCosts`\n * applies it to a session via targeted, idempotent UPDATEs (never `INSERT OR\n * REPLACE`, so other node fields are untouched). The first source is `CsvCostSource`\n * — deterministic, provider-agnostic, no new dependency. Live billing-API sources\n * (AWS Cost Explorer, GCP/Azure) are future implementations of the same interface.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { CartographyDB } from './db.js';\nimport { CostEntrySchema } from './types.js';\nimport type { CostEntry } from './types.js';\nimport { logWarn } from './logger.js';\n\n/** One attribution line keyed to a node. `cost`/`owner` may be partially present. */\nexport interface CostRecord {\n nodeId: string;\n owner?: string;\n cost?: CostEntry;\n}\n\n/** A pluggable provider of cost/owner attribution, keyed by node id. */\nexport interface CostSource {\n readonly id: string;\n /** Attribution keyed by node id. An absent/unauthorized source resolves to an empty map (degrade). */\n fetch(): Promise<Map<string, CostRecord>>;\n}\n\nexport interface EnrichResult {\n source: string;\n total: number;\n matched: number;\n unmatched: number;\n unmatchedIds: string[];\n}\n\n/** How a CSV row is resolved to a node id when no explicit `nodeId` column is given. */\nexport type MatchStrategy = 'nodeId' | 'name' | 'tag';\n\nexport interface CsvCostSourceOptions {\n filePath: string;\n match?: MatchStrategy;\n db?: CartographyDB;\n sessionId?: string;\n}\n\n/** Split one CSV line into fields, honoring double-quoted fields with escaped quotes. */\nfunction splitCsvLine(line: string): string[] {\n const out: string[] = [];\n let cur = '';\n let inQuotes = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (inQuotes) {\n if (ch === '\"') {\n if (line[i + 1] === '\"') { cur += '\"'; i++; } else { inQuotes = false; }\n } else cur += ch;\n } else if (ch === '\"') {\n inQuotes = true;\n } else if (ch === ',') {\n out.push(cur); cur = '';\n } else cur += ch;\n }\n out.push(cur);\n return out.map((s) => s.trim());\n}\n\n/**\n * Parse a cost CSV (`nodeId,owner,amount,currency,period[,source]`, header required)\n * into validated `CostRecord[]`. Each row's cost fields are validated via\n * `CostEntrySchema`; a malformed row is skipped with a `logWarn` (counts only, no\n * owner PII) — the batch never aborts.\n */\nexport function parseCostCsv(text: string): CostRecord[] {\n const lines = text.split(/\\r?\\n/).filter((l) => l.trim().length > 0);\n if (lines.length === 0) return [];\n const header = splitCsvLine(lines[0]).map((h) => h.toLowerCase());\n const col = (name: string): number => header.indexOf(name);\n const iNode = col('nodeid');\n const iOwner = col('owner');\n const iAmount = col('amount');\n const iCurrency = col('currency');\n const iPeriod = col('period');\n const iSource = col('source');\n if (iNode < 0) {\n logWarn('cost csv: missing required \"nodeId\" header column');\n return [];\n }\n\n const records: CostRecord[] = [];\n for (let r = 1; r < lines.length; r++) {\n const f = splitCsvLine(lines[r]);\n const nodeId = f[iNode];\n if (!nodeId) { logWarn(`cost csv: row ${r + 1} skipped (empty nodeId)`); continue; }\n\n const rec: CostRecord = { nodeId };\n if (iOwner >= 0 && f[iOwner]) rec.owner = f[iOwner];\n\n const amountRaw = iAmount >= 0 ? f[iAmount] : '';\n if (amountRaw) {\n const parsed = CostEntrySchema.safeParse({\n amount: Number(amountRaw),\n currency: iCurrency >= 0 ? f[iCurrency] : undefined,\n period: iPeriod >= 0 ? f[iPeriod] : undefined,\n ...(iSource >= 0 && f[iSource] ? { source: f[iSource] } : {}),\n });\n if (!parsed.success) {\n logWarn(`cost csv: row ${r + 1} skipped (invalid cost fields)`);\n // Keep an owner-only record if owner is present; otherwise drop the row.\n if (!rec.owner) continue;\n } else {\n rec.cost = parsed.data;\n }\n }\n if (rec.owner || rec.cost) records.push(rec);\n }\n return records;\n}\n\n/** CSV-backed cost source. Resolves row→node ids per the chosen `MatchStrategy`. */\nexport class CsvCostSource implements CostSource {\n readonly id: string;\n constructor(private readonly opts: CsvCostSourceOptions) {\n const base = opts.filePath.split(/[\\\\/]/).pop() ?? opts.filePath;\n this.id = `csv:${base}`;\n }\n\n async fetch(): Promise<Map<string, CostRecord>> {\n const text = readFileSync(resolve(this.opts.filePath), 'utf-8');\n const records = parseCostCsv(text);\n const match = this.opts.match ?? 'nodeId';\n const out = new Map<string, CostRecord>();\n\n if (match === 'nodeId') {\n for (const rec of records) out.set(rec.nodeId, rec);\n return out;\n }\n\n // name / tag resolution needs the session's nodes to build a lookup index.\n if (!this.opts.db || !this.opts.sessionId) {\n logWarn(`cost csv: match '${match}' requires db + sessionId; falling back to nodeId`);\n for (const rec of records) out.set(rec.nodeId, rec);\n return out;\n }\n const nodes = this.opts.db.getNodes(this.opts.sessionId);\n const index = new Map<string, string>(); // key (name or tag) → node id\n for (const n of nodes) {\n if (match === 'name') index.set(n.name, n.id);\n else for (const t of n.tags) index.set(t, n.id);\n }\n for (const rec of records) {\n const resolved = index.get(rec.nodeId);\n out.set(resolved ?? rec.nodeId, { ...rec, nodeId: resolved ?? rec.nodeId });\n }\n return out;\n }\n}\n\n/**\n * Idempotent post-pass: apply a source's attribution to a session via targeted\n * UPDATEs. Re-running with the same source yields the same DB state. Unmatched\n * rows (no such node id) are reported, never written.\n */\nexport async function enrichCosts(db: CartographyDB, sessionId: string, source: CostSource): Promise<EnrichResult> {\n const records = await source.fetch();\n let matched = 0;\n const unmatchedIds: string[] = [];\n for (const [nodeId, rec] of records) {\n const ok = db.enrichNodeAttribution(sessionId, nodeId, {\n owner: rec.owner ?? undefined,\n cost: rec.cost ?? undefined,\n });\n if (ok) matched++; else unmatchedIds.push(nodeId);\n }\n return { source: source.id, total: records.size, matched, unmatched: unmatchedIds.length, unmatchedIds };\n}\n","/**\n * Sharing classifier + pre-send preview (2.10).\n *\n * Resolves the effective sharing level for each node (a PERSONAL-host hard floor\n * over the persisted policy), applies that level (`none` drops, `anonymized`\n * pseudonymizes, `full` keeps raw), and builds {@link previewShare} — the exact,\n * topology-shape-preserving payload that *would* leave the machine. This is the\n * privacy gate 2.11/2.12 build on; nothing here performs any network I/O.\n */\n\nimport type { DiscoveryNode, NodeRow, EdgeRow, SharingLevel, SharingPolicy } from './types.js';\nimport type { CartographyDB } from './db.js';\nimport { pseudonymizeString, pseudonymize } from './anonymize.js';\nimport { isPersonalHost } from './scanners/bookmarks.js';\n\n/** Count of `*`/`**` wildcards in a pattern (fewer ⇒ more specific). */\nfunction wildcardCount(pattern: string): number {\n return (pattern.match(/\\*/g) ?? []).length;\n}\n\n/**\n * Glob match over a node id. `*` matches any run of non-`:` chars within a\n * segment; `**` matches any chars including `:`. Anchored full match.\n */\nfunction globMatch(pattern: string, id: string): boolean {\n // Build a regex: ** → .*, * → [^:]*, everything else escaped literally.\n let re = '^';\n for (let i = 0; i < pattern.length; i++) {\n const c = pattern[i];\n if (c === '*') {\n if (pattern[i + 1] === '*') { re += '.*'; i++; }\n else re += '[^:]*';\n } else {\n re += c.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n }\n re += '$';\n return new RegExp(re).test(id);\n}\n\n/**\n * Resolve the policy-level for a node id: the most-specific matching override\n * wins (fewest wildcards, then longest pattern); ties and no-match fall through\n * to `defaultLevel`. The `'*'`/`'**'` rows are the global default and always lose\n * to any narrower match. Pure and deterministic.\n */\nexport function resolveSharingLevel(nodeId: string, policy: SharingPolicy): SharingLevel {\n const matches = policy.overrides\n .filter((o) => o.pattern !== '*' && o.pattern !== '**' && globMatch(o.pattern, nodeId))\n .sort((a, b) =>\n wildcardCount(a.pattern) - wildcardCount(b.pattern) ||\n b.pattern.length - a.pattern.length,\n );\n return matches.length ? matches[0].level : policy.defaultLevel;\n}\n\n/** Hostnames embedded in a node (id, name, and host/url metadata) for the PERSONAL floor. */\nfunction nodeHosts(node: DiscoveryNode): string[] {\n const out: string[] = [node.id, node.name];\n const meta = node.metadata ?? {};\n for (const k of ['host', 'url', 'domain'] as const) {\n const v = (meta as Record<string, unknown>)[k];\n if (typeof v === 'string') out.push(v);\n }\n if (node.domain) out.push(node.domain);\n return out;\n}\n\n/**\n * The effective level for a node: a PERSONAL-host hard floor (single-sourced from\n * the bookmarks never-share list) forces `'none'` regardless of policy; otherwise\n * the policy's resolved level for the node id applies.\n */\nexport function resolveEffectiveLevel(node: DiscoveryNode, policy: SharingPolicy): SharingLevel {\n if (nodeHosts(node).some((h) => isPersonalHost(h))) return 'none';\n return resolveSharingLevel(node.id, policy);\n}\n\n/**\n * Apply a sharing level to one node:\n * - `none` → `null` (the node is dropped, never leaves).\n * - `anonymized` → a clone with `id`/`name` pseudonymized and `metadata` walked\n * (structure-preserving); identifying fragments tokenized.\n * - `full` → a structural clone, verbatim.\n *\n * The same deterministic `pseudonymizeString` is applied to the id here and by\n * {@link previewShare} when remapping edges, so endpoints always resolve.\n */\nexport function applySharingLevel(\n node: DiscoveryNode,\n level: SharingLevel,\n orgKey: Buffer,\n db?: CartographyDB,\n): DiscoveryNode | null {\n if (level === 'none') return null;\n if (level === 'full') return { ...node, metadata: { ...(node.metadata ?? {}) }, tags: [...(node.tags ?? [])] };\n return {\n ...node,\n id: pseudonymizeString(node.id, orgKey, db),\n name: pseudonymizeString(node.name, orgKey, db),\n metadata: pseudonymize(node.metadata ?? {}, orgKey, db) as Record<string, unknown>,\n tags: (node.tags ?? []).map((t) => pseudonymizeString(t, orgKey, db)),\n };\n}\n\nexport interface SharePreviewEntry {\n /** The original (un-anonymized) node, for the operator's side-by-side disclosure. */\n node: DiscoveryNode;\n level: SharingLevel;\n /** What would actually leave: the transformed node, or `null` when dropped. */\n payload: DiscoveryNode | null;\n}\n\nexport interface SharePreview {\n nodes: SharePreviewEntry[];\n /** Edges that would leave, with endpoints remapped to surviving (transformed) ids. */\n edges: { sourceId: string; targetId: string; relationship: string }[];\n /** Original ids of nodes dropped at level `none`. */\n droppedNodeIds: string[];\n}\n\n/**\n * Build the exact payload that would leave for a session: resolve each node's\n * effective level, apply it, and remap edge endpoints through the same id\n * transform. An edge survives only when both endpoints survive — so when no node\n * is dropped the node count, edge count, and relationship multiset are identical\n * before/after; when some are dropped the result is a well-defined subgraph.\n *\n * `opts.persistReversal` (default false) controls whether anonymized fragments are\n * written to the reversal map — pure preview need not persist; an actual\n * pre-send transform should, so an admin can later invert.\n */\nexport function previewShare(\n db: CartographyDB,\n sessionId: string,\n orgKey: Buffer,\n policy: SharingPolicy,\n opts: { persistReversal?: boolean } = {},\n): SharePreview {\n const persist = opts.persistReversal ? db : undefined;\n const nodes: NodeRow[] = db.getNodes(sessionId);\n const edges: EdgeRow[] = db.getEdges(sessionId);\n\n const entries: SharePreviewEntry[] = [];\n const idMap = new Map<string, string | null>(); // original id → new id (null = dropped)\n const droppedNodeIds: string[] = [];\n\n for (const node of nodes) {\n const level = resolveEffectiveLevel(node, policy);\n const payload = applySharingLevel(node, level, orgKey, persist);\n entries.push({ node, level, payload });\n if (payload === null) {\n idMap.set(node.id, null);\n droppedNodeIds.push(node.id);\n } else {\n idMap.set(node.id, payload.id);\n }\n }\n\n const outEdges: { sourceId: string; targetId: string; relationship: string }[] = [];\n for (const e of edges) {\n const src = idMap.get(e.sourceId);\n const tgt = idMap.get(e.targetId);\n // Drop an edge if either endpoint is dropped or unknown (not in this session's nodes).\n if (src == null || tgt == null) continue;\n outEdges.push({ sourceId: src, targetId: tgt, relationship: e.relationship });\n }\n\n return { nodes: entries, edges: outEdges, droppedNodeIds };\n}\n\n/**\n * The seam 2.11 will use to suppress `ask_user` re-prompts: true when the node id\n * is already covered by the persisted policy — either a matching override or a\n * non-`none` default — so a matched item is never re-prompted.\n */\nexport function isRemembered(policy: SharingPolicy, nodeId: string): boolean {\n const matched = policy.overrides.some((o) => o.pattern !== '*' && o.pattern !== '**' && globMatch(o.pattern, nodeId));\n return matched || policy.defaultLevel !== 'none';\n}\n","/**\n * Stable content hashing for the central-DB share queue (2.11).\n *\n * The hash is computed over the *policy-transformed* payload (what `previewShare`\n * produces — already anonymized/dropped), so two scans that yield the same outgoing\n * bytes map to the same `pending_shares` row (idempotent enqueue) and the same\n * server-side dedup key. It is deterministic via `stableStringify` (recursively\n * key-sorted JSON), so key ordering never perturbs the hash.\n *\n * Zero new dependencies — `node:crypto` + the existing `stableStringify`.\n */\n\nimport { createHash } from 'node:crypto';\nimport { stableStringify } from '../diff.js';\n\n/** A node or edge projected to its outgoing (already-transformed) shape. */\nexport type ShareKind = 'node' | 'edge';\n\n/**\n * sha256 (hex) over the canonical JSON of `{ kind, payload }`. `payload` is the\n * transformed projection — for `anonymized`/`none` items it is the anonymized form,\n * never the raw record — so the hash is stable across scans and identical to the\n * value the central side can dedup on.\n */\nexport function shareHash(kind: ShareKind, payload: unknown): string {\n return createHash('sha256').update(stableStringify({ kind, payload })).digest('hex');\n}\n","/**\n * Share classifier (2.11) — pure, DB-agnostic.\n *\n * Buckets the items of a {@link SharePreview} (the 2.10 policy-transformed payload)\n * into `share` / `withhold` / `pending` against the persisted {@link SharingPolicy}\n * and the set of already-shared content hashes:\n *\n * - A node whose transformed payload is `null` (effective level `none`, incl. the\n * PERSONAL-host hard floor) is **withheld** — it never leaves.\n * - A node already pushed (its `shareHash` ∈ `sharedHashes`) is dropped (no bucket).\n * - A surviving node covered by the policy ({@link isRemembered}) is **share** — the\n * employee's policy is the standing consent, so it auto-approves with no re-prompt.\n * - A surviving node *not* covered by any policy rule/default is **pending** — new /\n * unmatched, queued for explicit review. Nothing in this bucket ever leaves until\n * the employee approves it (the load-bearing privacy invariant).\n *\n * Edges follow their endpoints: an edge is shareable only when both endpoints\n * survive (already guaranteed by `previewShare`, which drops dangling edges) and\n * both endpoint nodes land in `share`. Otherwise the edge is withheld.\n *\n * The transform/anonymization itself is **not** reimplemented here — it is consumed\n * from `previewShare`. This module only routes the already-transformed items.\n */\n\nimport type { SharingPolicy } from '../types.js';\nimport type { SharePreview } from '../sharing.js';\nimport { isRemembered } from '../sharing.js';\nimport { shareHash } from './hash.js';\n\n/** One classified, ready-to-queue item carrying its outgoing (transformed) payload. */\nexport interface ClassifiedItem {\n contentHash: string;\n kind: 'node' | 'edge';\n /** Original node id (for `node` items); the remapped source id for `edge` items. */\n nodeId?: string;\n /** The exact transformed bytes that would leave the machine. */\n payload: unknown;\n}\n\nexport interface ClassifyResult {\n /** Covered by policy → auto-approve (no re-prompt). */\n share: ClassifiedItem[];\n /** Effective level `none` / PERSONAL floor → suppressed; never leaves. */\n withhold: ClassifiedItem[];\n /** New / unmatched → queued for explicit human review. */\n pending: ClassifiedItem[];\n}\n\nexport interface ClassifyInput {\n preview: SharePreview;\n policy: SharingPolicy;\n /** `content_hash` values already pushed (status `shared`) — suppress re-share. */\n sharedHashes: ReadonlySet<string>;\n}\n\n/**\n * Route a {@link SharePreview} into share / withhold / pending buckets. Pure and\n * deterministic: same inputs always yield the same buckets. The payloads carried\n * here are the transformed ones from the preview, so anything routed to `share` is\n * already anonymized per policy.\n */\nexport function classify(input: ClassifyInput): ClassifyResult {\n const { preview, policy, sharedHashes } = input;\n const result: ClassifyResult = { share: [], withhold: [], pending: [] };\n\n // Track which original node ids are cleared to share, so edges can follow.\n const sharedNodeIds = new Set<string>();\n\n for (const entry of preview.nodes) {\n if (entry.payload === null) {\n // Dropped at level `none` (or PERSONAL floor): nothing leaves.\n result.withhold.push({ contentHash: '', kind: 'node', nodeId: entry.node.id, payload: null });\n continue;\n }\n const contentHash = shareHash('node', entry.payload);\n if (sharedHashes.has(contentHash)) continue; // already shared — no bucket\n\n const item: ClassifiedItem = { contentHash, kind: 'node', nodeId: entry.node.id, payload: entry.payload };\n if (isRemembered(policy, entry.node.id)) {\n result.share.push(item);\n sharedNodeIds.add(entry.node.id);\n } else {\n result.pending.push(item);\n }\n }\n\n // Edges already have endpoints remapped (and dangling ones dropped) by previewShare.\n // An edge is shareable only when both of its (original) endpoint nodes are cleared.\n // previewShare exposes only remapped ids on edges, so we re-derive surviving status\n // from the share bucket via the transformed payload: edges whose remapped endpoints\n // correspond to shared nodes go to `share`; the rest are withheld.\n const sharedRemappedIds = new Set<string>();\n for (const entry of preview.nodes) {\n if (entry.payload !== null && sharedNodeIds.has(entry.node.id)) {\n sharedRemappedIds.add(entry.payload.id);\n }\n }\n for (const e of preview.edges) {\n const payload = { sourceId: e.sourceId, targetId: e.targetId, relationship: e.relationship };\n const contentHash = shareHash('edge', payload);\n const bothShared = sharedRemappedIds.has(e.sourceId) && sharedRemappedIds.has(e.targetId);\n if (!bothShared) {\n result.withhold.push({ contentHash: '', kind: 'edge', payload });\n continue;\n }\n if (sharedHashes.has(contentHash)) continue;\n result.share.push({ contentHash, kind: 'edge', payload });\n }\n\n return result;\n}\n","/**\n * Outbound push client (2.11) — Cartograph's first egress path.\n *\n * Pushes consented, policy-transformed deltas to the central ingest endpoint over\n * bearer-auth HTTPS. It is the inverse of the inbound MCP auth in\n * `src/mcp/transports.ts`: instead of validating a bearer token, it *sends* one.\n *\n * Load-bearing privacy invariant: this function only ever sends the items it is\n * handed, which the caller (`sync push`) draws exclusively from `getApprovedShares()`\n * (status `approved`). The hard guards below additionally make a no-config / insecure\n * / empty send impossible. The bearer token is NEVER logged and NEVER placed in the\n * payload; error logging routes the URL through `stripSensitive`.\n *\n * Network is Node 20+ global `fetch`, injectable via `fetchImpl` so tests never hit\n * the network. Zero new dependencies.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { CartographyConfig } from '../types.js';\nimport { stripSensitive, redactValue } from '../tools.js';\nimport { stableStringify } from '../diff.js';\n\n/** One item to push. `payload` is the already-policy-transformed (anonymized) projection. */\nexport interface PushItem {\n contentHash: string;\n kind: 'node' | 'edge';\n payload: unknown;\n}\n\nexport interface PushResult {\n /** Items the server acknowledged. */\n sent: number;\n /** Batches POSTed. */\n batches: number;\n /** Items in batches that ultimately failed (left for a later retry). */\n failed: number;\n /** content hashes the server acknowledged (caller flips these to `shared`). */\n sentHashes: string[];\n}\n\nexport interface PushOptions {\n /** Injectable fetch (tests). Defaults to Node global `fetch`. */\n fetchImpl?: typeof fetch;\n /** Items per batch. Defaults to `config.centralDb.batchSize ?? 100`. */\n batchSize?: number;\n /** Max retries per batch on 5xx / network errors. Default 4. */\n maxRetries?: number;\n /** Per-request timeout (ms). Default 15000. */\n timeoutMs?: number;\n /** Preview only — never networks. */\n dryRun?: boolean;\n /** Log sink (stderr by default); the token never reaches it. */\n log?: (line: string) => void;\n /** Sleep hook (tests inject a no-op to skip real backoff). */\n sleep?: (ms: number) => Promise<void>;\n}\n\n/** Wire-format version of the push envelope (the contract WS 2.12 ingests). */\nexport const PUSH_SCHEMA_VERSION = 1 as const;\n\nconst DEFAULT_BATCH = 100;\nconst DEFAULT_RETRIES = 4;\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\nfunction defaultLog(line: string): void {\n process.stderr.write(`[cartography-sync] ${line}\\n`);\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/** Deterministic per-batch idempotency key: sha256 over the sorted content hashes. */\nfunction batchKey(items: PushItem[]): string {\n const hashes = items.map((i) => i.contentHash).sort();\n return createHash('sha256').update(stableStringify(hashes)).digest('hex');\n}\n\n/**\n * Push approved deltas. Returns counts and the acknowledged content hashes.\n *\n * Hard guards (the no-leak guarantee):\n * - missing `centralDb.url`/`token` → throws (nothing sent, fetch never called).\n * - non-`https:` URL → throws, unless `CARTOGRAPHY_ALLOW_INSECURE_SYNC === '1'`\n * (test-only, documented as unsafe; never the default).\n * - empty `items` → returns zeros without any network call.\n */\nexport async function pushDeltas(\n config: CartographyConfig,\n items: PushItem[],\n opts: PushOptions = {},\n): Promise<PushResult> {\n const central = config.centralDb;\n if (!central?.url || !central.token) {\n throw new Error('sync push: centralDb not configured (set centralDb.url + token)');\n }\n\n let parsed: URL;\n try {\n parsed = new URL(central.url);\n } catch {\n throw new Error('sync push: centralDb.url is not a valid URL');\n }\n const insecureAllowed = process.env.CARTOGRAPHY_ALLOW_INSECURE_SYNC === '1';\n if (parsed.protocol !== 'https:' && !insecureAllowed) {\n throw new Error(\n `sync push: refusing to send over insecure ${parsed.protocol}// — use https:// ` +\n `(or set CARTOGRAPHY_ALLOW_INSECURE_SYNC=1 for local testing only)`,\n );\n }\n\n const log = opts.log ?? defaultLog;\n const sleep = opts.sleep ?? defaultSleep;\n const fetchImpl = opts.fetchImpl ?? fetch;\n const batchSize = Math.max(1, opts.batchSize ?? central.batchSize ?? DEFAULT_BATCH);\n const maxRetries = Math.max(0, opts.maxRetries ?? DEFAULT_RETRIES);\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const safeUrl = stripSensitive(central.url);\n\n if (items.length === 0) {\n log('nothing to push (0 approved items)');\n return { sent: 0, batches: 0, failed: 0, sentHashes: [] };\n }\n\n // Defensive: strip any lingering credentials from the (already-anonymized) payload.\n const batches: PushItem[][] = [];\n for (let i = 0; i < items.length; i += batchSize) {\n batches.push(items.slice(i, i + batchSize).map((it) => ({ ...it, payload: redactValue(it.payload) })));\n }\n\n let sent = 0;\n let failed = 0;\n const sentHashes: string[] = [];\n\n for (const batch of batches) {\n const key = batchKey(batch);\n const body = JSON.stringify({\n schemaVersion: PUSH_SCHEMA_VERSION,\n ...(central.org ? { org: central.org } : {}),\n items: batch.map((b) => ({ contentHash: b.contentHash, kind: b.kind, payload: b.payload })),\n });\n\n if (opts.dryRun) {\n log(`dry-run: would POST ${batch.length} item(s) to ${safeUrl} (idempotency ${key.slice(0, 12)}…)`);\n sent += batch.length;\n sentHashes.push(...batch.map((b) => b.contentHash));\n continue;\n }\n\n let ok = false;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const startedAt = Date.now();\n try {\n const res = await fetchImpl(central.url, {\n method: 'POST',\n headers: {\n 'authorization': `Bearer ${central.token}`,\n 'content-type': 'application/json',\n 'x-idempotency-key': key,\n },\n body,\n signal: controller.signal,\n });\n const elapsed = Date.now() - startedAt;\n if (res.ok) {\n log(`pushed ${batch.length} item(s) → ${safeUrl} [${res.status}] ${elapsed}ms (attempt ${attempt + 1})`);\n ok = true;\n break;\n }\n // 4xx is a client error — do not retry; surface and stop this batch.\n if (res.status >= 400 && res.status < 500) {\n log(`batch rejected → ${safeUrl} [${res.status}] (no retry)`);\n break;\n }\n // 5xx — retryable.\n log(`batch failed → ${safeUrl} [${res.status}] (attempt ${attempt + 1}/${maxRetries + 1})`);\n } catch (err) {\n // Network/timeout — retryable. Never echo headers or token.\n const msg = err instanceof Error ? err.message : String(err);\n log(`batch error → ${safeUrl}: ${msg.replace(/https?:\\/\\/[^\\s]+/g, (u) => stripSensitive(u))} (attempt ${attempt + 1}/${maxRetries + 1})`);\n } finally {\n clearTimeout(timer);\n }\n if (attempt < maxRetries) {\n // Capped exponential backoff with jitter.\n const base = Math.min(2 ** attempt * 250, 4000);\n await sleep(base + Math.floor(Math.random() * 100));\n }\n }\n\n if (ok) {\n sent += batch.length;\n sentHashes.push(...batch.map((b) => b.contentHash));\n } else {\n failed += batch.length;\n }\n }\n\n return { sent, batches: batches.length, failed, sentHashes };\n}\n","/**\n * Central-DB sync orchestration (2.11) — the post-scan enqueue glue.\n *\n * After a (manual or scheduled) scan, {@link runSyncClassify} resolves the\n * employee's persisted sharing policy (2.10), builds the policy-transformed payload\n * via `previewShare` (already anonymized/dropped — nothing raw for `anonymized`/\n * `none`), classifies it against the already-shared set, and enqueues the result\n * into `pending_shares`:\n *\n * - `share` → enqueued `approved` with `decided_by='rule'` (the policy *is* the\n * standing consent; never re-prompted).\n * - `pending` → enqueued `pending` for explicit review (nothing leaves until approved).\n * - `withhold`→ recorded `withheld` for the audit/`sync status` suppression count.\n *\n * Short-circuits to a no-op when `centralDb` is unconfigured, so an install without\n * sync configured never writes to the queue. All writes run in one transaction.\n */\n\nimport type { CartographyConfig } from '../types.js';\nimport type { CartographyDB } from '../db.js';\nimport { loadOrgKey } from '../orgkey.js';\nimport { previewShare } from '../sharing.js';\nimport { classify } from './classify.js';\n\nexport interface SyncClassifyResult {\n enqueued: number;\n autoShared: number;\n withheld: number;\n}\n\nexport interface SyncClassifyOptions {\n /** Override the org-key path / namespace (tests). Defaults to `config.organization`. */\n orgKey?: Buffer;\n}\n\n/**\n * Classify a session's topology against the persisted policy and enqueue the\n * result. Returns counts for `pending` (`enqueued`), auto-approved (`autoShared`),\n * and suppressed (`withheld`) items. No-op (all zeros) when sync is unconfigured.\n */\nexport function runSyncClassify(\n db: CartographyDB,\n sessionId: string,\n config: CartographyConfig,\n opts: SyncClassifyOptions = {},\n): SyncClassifyResult {\n if (!config.centralDb?.url) return { enqueued: 0, autoShared: 0, withheld: 0 };\n\n const orgKey = opts.orgKey ?? loadOrgKey({ organization: config.organization });\n const policy = db.getSharingPolicy();\n // persistReversal: an actual pre-send transform should persist the reversal map so\n // an admin can later invert anonymized tokens.\n const preview = previewShare(db, sessionId, orgKey, policy, { persistReversal: true });\n const sharedHashes = db.getSharedHashes();\n\n const { share, pending, withhold } = classify({ preview, policy, sharedHashes });\n\n const writeAll = db.rawConnection().transaction(() => {\n for (const item of share) {\n db.enqueuePending({\n contentHash: item.contentHash,\n sessionId,\n nodeId: item.nodeId,\n kind: item.kind,\n payload: item.payload,\n status: 'approved',\n decidedBy: 'rule',\n });\n }\n for (const item of pending) {\n db.enqueuePending({\n contentHash: item.contentHash,\n sessionId,\n nodeId: item.nodeId,\n kind: item.kind,\n payload: item.payload,\n status: 'pending',\n });\n }\n // Withheld items carry no stable content hash (payload may be null); record them\n // only when they have a real hash to key on (edges suppressed for unshared\n // endpoints). Dropped `none` nodes are intentionally not queued — they leave no\n // trace beyond `previewShare.droppedNodeIds`.\n for (const item of withhold) {\n if (!item.contentHash) continue;\n db.enqueuePending({\n contentHash: item.contentHash,\n sessionId,\n nodeId: item.nodeId,\n kind: item.kind,\n payload: item.payload,\n status: 'withheld',\n decidedBy: 'rule',\n });\n }\n });\n writeAll();\n\n return { enqueued: pending.length, autoShared: share.length, withheld: withhold.length };\n}\n\nexport type { ClassifiedItem, ClassifyResult, ClassifyInput } from './classify.js';\nexport { classify } from './classify.js';\nexport { shareHash } from './hash.js';\nexport { pushDeltas } from './push.js';\nexport type { PushItem, PushResult, PushOptions } from './push.js';\n","/**\n * Format-agnostic (de)serialization for host config files. JSON keeps 2-space\n * indentation; TOML uses smol-toml; YAML uses the `yaml` package. Empty or\n * whitespace-only input parses to an empty object so a fresh install starts clean.\n */\n\nimport { parse as parseToml, stringify as stringifyToml } from 'smol-toml';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { ConfigFormat } from './types.js';\n\nexport function parseConfig(text: string, format: ConfigFormat): Record<string, unknown> {\n if (!text.trim()) return {};\n try {\n switch (format) {\n case 'json':\n return JSON.parse(text) as Record<string, unknown>;\n case 'toml':\n return parseToml(text) as Record<string, unknown>;\n case 'yaml':\n return (parseYaml(text) as Record<string, unknown>) ?? {};\n }\n } catch (err) {\n // Fail loudly with an actionable message rather than crashing with a raw\n // parser stack or silently discarding (and then clobbering) the user's config.\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse existing ${format.toUpperCase()} config: ${detail}`);\n }\n}\n\nexport function serializeConfig(obj: Record<string, unknown>, format: ConfigFormat): string {\n switch (format) {\n case 'json':\n return JSON.stringify(obj, null, 2) + '\\n';\n case 'toml':\n return stringifyToml(obj) + '\\n';\n case 'yaml':\n return stringifyYaml(obj);\n }\n}\n","/**\n * Idempotent deep-merge of plain objects. Used by client specs to splice a server\n * entry into an existing config without clobbering unrelated keys. Arrays and\n * scalars from `source` replace those in `target`; nested plain objects merge\n * recursively. `source` is never mutated; `target` is cloned.\n */\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nexport function deepMerge<T extends Record<string, unknown>>(target: T, source: Record<string, unknown>): T {\n const out: Record<string, unknown> = { ...target };\n for (const [key, value] of Object.entries(source)) {\n const existing = out[key];\n if (isPlainObject(existing) && isPlainObject(value)) {\n out[key] = deepMerge(existing, value);\n } else {\n out[key] = value;\n }\n }\n return out as T;\n}\n","/**\n * Shared entry-shape helpers. Most hosts accept the Claude-Desktop-style object\n * (`command`/`args`/`env` for stdio, `url`/`type` for HTTP); specs that diverge\n * (VS Code `type`, Zed `source`, Codex TOML, …) compose or override these.\n */\n\nimport type { ServerEntry } from './types.js';\n\n/** The common `{ command, args, env }` | `{ type:'http', url }` server object. */\nexport function mcpServerObject(entry: ServerEntry): Record<string, unknown> {\n if (entry.url) {\n return { type: 'http', url: entry.url, ...(entry.env ? { env: entry.env } : {}) };\n }\n return {\n command: entry.command,\n args: entry.args ?? [],\n ...(entry.env ? { env: entry.env } : {}),\n };\n}\n","/**\n * The canonical Cartography MCP server entry every client spec receives. Kept in\n * one place so the npx invocation (and any future packaging change) is defined\n * exactly once and reused across all hosts.\n */\n\nimport type { ServerEntry } from './types.js';\n\nexport const PACKAGE_NAME = '@datasynx/agentic-ai-cartography';\nexport const MCP_BIN = 'cartography-mcp';\nexport const DEFAULT_SERVER_NAME = 'cartography';\n\nexport interface EntryOptions {\n /** `http` produces a `url` entry; otherwise stdio via npx. */\n transport?: 'stdio' | 'http';\n /** HTTP endpoint (used when transport === 'http'). */\n url?: string;\n /** Extra environment variables to inject. */\n env?: Record<string, string>;\n /** Extra package arguments appended after the bin name (e.g. `--db`, `--session`). */\n packageArgs?: string[];\n}\n\n/** Build the default server entry (stdio via `npx` unless an HTTP url is given). */\nexport function defaultServerEntry(opts: EntryOptions = {}): ServerEntry {\n if (opts.transport === 'http') {\n return { url: opts.url ?? 'http://127.0.0.1:3737/mcp', ...(opts.env ? { env: opts.env } : {}) };\n }\n const args = ['-y', '--package', PACKAGE_NAME, MCP_BIN, ...(opts.packageArgs ?? [])];\n return { command: 'npx', args, ...(opts.env ? { env: opts.env } : {}) };\n}\n","/**\n * Generic install planning/applying. `planInstall` is pure relative to a provided\n * context (reads the existing file, computes the merged result, never writes) so\n * it powers both `--dry-run` and the real write in `applyInstall`.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { homedir } from 'node:os';\nimport { parseConfig, serializeConfig } from './format.js';\nimport { DEFAULT_SERVER_NAME } from './entry.js';\nimport type { ClientSpec, ConfigFormat, OsKind, ResolveContext, Scope, ServerEntry } from './types.js';\n\nexport interface InstallPlan {\n client: string;\n label: string;\n path: string;\n format: ConfigFormat;\n /** Existing file contents ('' when the file does not exist). */\n before: string;\n /** Contents that would be written. */\n after: string;\n fileExists: boolean;\n changed: boolean;\n note?: string;\n}\n\n/** Detect the current OS kind. */\nexport function currentOs(): OsKind {\n if (process.platform === 'win32') return 'win';\n if (process.platform === 'darwin') return 'mac';\n return 'linux';\n}\n\n/** Build a real resolve context from the running environment. */\nexport function defaultContext(scope: Scope): ResolveContext {\n return { scope, os: currentOs(), home: homedir(), cwd: process.cwd(), env: process.env };\n}\n\nexport interface PlanOptions {\n serverName?: string;\n entry: ServerEntry;\n}\n\n/** Compute what installing `entry` into `spec` would change. Reads the config file but never writes. */\nexport function planInstall(spec: ClientSpec, ctx: ResolveContext, opts: PlanOptions): InstallPlan {\n const path = spec.path(ctx);\n if (!path) {\n throw new Error(`${spec.label} does not support the \"${ctx.scope}\" scope.`);\n }\n const fileExists = existsSync(path);\n const before = fileExists ? readFileSync(path, 'utf8') : '';\n const existing = parseConfig(before, spec.format);\n const merged = spec.apply(existing, opts.serverName ?? DEFAULT_SERVER_NAME, opts.entry);\n const after = serializeConfig(merged, spec.format);\n return {\n client: spec.id,\n label: spec.label,\n path,\n format: spec.format,\n before,\n after,\n fileExists,\n changed: after !== before,\n ...(spec.note ? { note: spec.note } : {}),\n };\n}\n\n/** Write a plan's result to disk, creating parent directories as needed. */\nexport function applyInstall(plan: InstallPlan): void {\n mkdirSync(dirname(plan.path), { recursive: true });\n writeFileSync(plan.path, plan.after, 'utf8');\n}\n\n/** A minimal line-oriented diff for `--dry-run` output. */\nexport function renderDiff(before: string, after: string): string {\n if (before === after) return ' (no changes)';\n const b = before.length ? before.split('\\n') : [];\n const a = after.split('\\n');\n const out: string[] = [];\n const max = Math.max(b.length, a.length);\n for (let i = 0; i < max; i++) {\n if (b[i] === a[i]) {\n if (a[i] !== undefined) out.push(` ${a[i]}`);\n } else {\n if (b[i] !== undefined) out.push(`- ${b[i]}`);\n if (a[i] !== undefined) out.push(`+ ${a[i]}`);\n }\n }\n return out.join('\\n');\n}\n","/**\n * Registry of supported MCP hosts. The merge engine and CLI are generic; every\n * host-specific detail (config path, format, schema key) lives in a `ClientSpec`\n * here. New hosts are added by appending a spec — no engine changes required.\n */\n\nimport { join } from 'node:path';\nimport { deepMerge } from './merge.js';\nimport { mcpServerObject } from './shapes.js';\nimport type { ClientSpec, ResolveContext, ServerEntry } from './types.js';\n\n/** Helper: a host that stores stdio/http servers under a top-level JSON key. */\nfunction jsonKeyedClient(args: {\n id: string;\n label: string;\n key: string;\n globalPath: ClientSpec['path'];\n projectPath?: ClientSpec['path'];\n note?: string;\n}): ClientSpec {\n return {\n id: args.id,\n label: args.label,\n format: 'json',\n note: args.note,\n path: (ctx) => (ctx.scope === 'project' ? args.projectPath?.(ctx) : args.globalPath(ctx)),\n apply: (existing, name, entry) =>\n deepMerge(existing, { [args.key]: { [name]: mcpServerObject(entry) } }),\n };\n}\n\n// ── Claude Code (reference JSON host) ────────────────────────────────────────\nconst claudeCode = jsonKeyedClient({\n id: 'claude-code',\n label: 'Claude Code',\n key: 'mcpServers',\n globalPath: (ctx) => join(ctx.home, '.claude.json'),\n projectPath: (ctx) => join(ctx.cwd, '.mcp.json'),\n});\n\n// ── Cursor ───────────────────────────────────────────────────────────────────\nconst cursor = jsonKeyedClient({\n id: 'cursor',\n label: 'Cursor',\n key: 'mcpServers',\n globalPath: (ctx) => join(ctx.home, '.cursor', 'mcp.json'),\n projectPath: (ctx) => join(ctx.cwd, '.cursor', 'mcp.json'),\n});\n\n// ── VS Code / GitHub Copilot ─────────────────────────────────────────────────\n// Diverges from the norm: the key is `servers` (not `mcpServers`) and stdio\n// entries carry an explicit `type: \"stdio\"`.\nfunction vscodeServerObject(entry: ServerEntry): Record<string, unknown> {\n if (entry.url) return { type: 'http', url: entry.url, ...(entry.env ? { env: entry.env } : {}) };\n return { type: 'stdio', command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n}\n\n/** VS Code user-profile directory per OS. */\nfunction vscodeUserDir(ctx: ResolveContext): string {\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Code', 'User');\n if (ctx.os === 'mac') return join(ctx.home, 'Library', 'Application Support', 'Code', 'User');\n return join(ctx.home, '.config', 'Code', 'User');\n}\n\nconst vscode: ClientSpec = {\n id: 'vscode',\n label: 'VS Code (Copilot)',\n format: 'json',\n note: 'Uses the `servers` key (not `mcpServers`) — the most common copy-paste mistake.',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.vscode', 'mcp.json') : join(vscodeUserDir(ctx), 'mcp.json')),\n apply: (existing, name, entry) => deepMerge(existing, { servers: { [name]: vscodeServerObject(entry) } }),\n};\n\n// ── OpenAI Codex CLI ─────────────────────────────────────────────────────────\n// TOML, table form `[mcp_servers.<name>]` (NOT `[mcp.servers.\"name\"]`).\nconst codex: ClientSpec = {\n id: 'codex',\n label: 'Codex CLI',\n format: 'toml',\n note: 'Project scope only loads in \"trusted\" projects.',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.codex', 'config.toml') : join(ctx.home, '.codex', 'config.toml')),\n apply: (existing, name, entry) => deepMerge(existing, { mcp_servers: { [name]: mcpServerObject(entry) } }),\n};\n\n// ── Windsurf (Codeium) ───────────────────────────────────────────────────────\n// Global-only; path is identical across OSes (USERPROFILE === home on Windows).\nconst windsurf = jsonKeyedClient({\n id: 'windsurf',\n label: 'Windsurf',\n key: 'mcpServers',\n globalPath: (ctx) => join(ctx.home, '.codeium', 'windsurf', 'mcp_config.json'),\n});\n\n// ── Cline & Roo Code (VS Code globalStorage hosts) ───────────────────────────\n/** `<VS Code user dir>/globalStorage/<extensionId>/settings/cline_mcp_settings.json`. */\nfunction codeGlobalStorage(ctx: ResolveContext, extensionId: string): string {\n return join(vscodeUserDir(ctx), 'globalStorage', extensionId, 'settings', 'cline_mcp_settings.json');\n}\n\nconst cline: ClientSpec = {\n id: 'cline',\n label: 'Cline',\n format: 'json',\n path: (ctx) => (ctx.scope === 'project' ? undefined : codeGlobalStorage(ctx, 'saoudrizwan.claude-dev')),\n // Cline augments the standard object with its own auto-approve/disable flags.\n apply: (existing, name, entry) =>\n deepMerge(existing, { mcpServers: { [name]: { ...mcpServerObject(entry), alwaysAllow: [], disabled: false } } }),\n};\n\nconst roo: ClientSpec = {\n id: 'roo',\n label: 'Roo Code',\n format: 'json',\n note: 'Project .roo/mcp.json takes precedence over the global settings.',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.roo', 'mcp.json') : codeGlobalStorage(ctx, 'rooveterinaryinc.roo-cline')),\n apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } }),\n};\n\n// ── Zed ──────────────────────────────────────────────────────────────────────\n// Key is `context_servers`; manual entries require `\"source\": \"custom\"`.\nconst zed: ClientSpec = {\n id: 'zed',\n label: 'Zed',\n format: 'json',\n note: 'Manual servers need \"source\": \"custom\"; remote uses an mcp-remote bridge.',\n path: (ctx) => {\n if (ctx.scope === 'project') return join(ctx.cwd, '.zed', 'settings.json');\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Zed', 'settings.json');\n return join(ctx.home, '.config', 'zed', 'settings.json');\n },\n apply: (existing, name, entry) => {\n const inner = entry.url\n ? { source: 'custom', url: entry.url }\n : { source: 'custom', command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n return deepMerge(existing, { context_servers: { [name]: inner } });\n },\n};\n\n// ── JetBrains AI Assistant / Junie ───────────────────────────────────────────\nconst junie: ClientSpec = {\n id: 'junie',\n label: 'JetBrains / Junie',\n format: 'json',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.junie', 'mcp', 'mcp.json') : join(ctx.home, '.junie', 'mcp', 'mcp.json')),\n apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } }),\n};\n\n// ── Gemini CLI ───────────────────────────────────────────────────────────────\n// stdio: command/args/env; HTTP uses the `httpUrl` key (not `url`).\nconst gemini: ClientSpec = {\n id: 'gemini',\n label: 'Gemini CLI',\n format: 'json',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.gemini', 'settings.json') : join(ctx.home, '.gemini', 'settings.json')),\n apply: (existing, name, entry) => {\n const inner = entry.url\n ? { httpUrl: entry.url, ...(entry.env ? { env: entry.env } : {}) }\n : mcpServerObject(entry);\n return deepMerge(existing, { mcpServers: { [name]: inner } });\n },\n};\n\n// ── Goose (Block) ────────────────────────────────────────────────────────────\n// YAML under `extensions:`. Built-ins (`type: builtin`) are preserved by the merge.\nconst goose: ClientSpec = {\n id: 'goose',\n label: 'Goose',\n format: 'yaml',\n note: 'Verify the extension shape against current Goose docs; built-ins are left untouched.',\n path: (ctx) => {\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Block', 'goose', 'config', 'config.yaml');\n return join(ctx.home, '.config', 'goose', 'config.yaml');\n },\n apply: (existing, name, entry) => {\n const inner = entry.url\n ? { name, type: 'streamable_http', enabled: true, uri: entry.url, ...(entry.env ? { env: entry.env } : {}) }\n : { name, type: 'stdio', enabled: true, command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n return deepMerge(existing, { extensions: { [name]: inner } });\n },\n};\n\n// ── OpenHands (All-Hands-AI) ─────────────────────────────────────────────────\n// `[mcp]` holds arrays (stdio_servers / shttp_servers / sse_servers), not a keyed\n// object — so entries are appended/replaced by identity, not deep-merged.\nfunction isObj(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\nconst openhands: ClientSpec = {\n id: 'openhands',\n label: 'OpenHands',\n format: 'toml',\n note: 'SHTTP is preferred; SSE is legacy. Only api_key is supported (no arbitrary headers).',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, 'config.toml') : join(ctx.home, '.openhands', 'config.toml')),\n apply: (existing, name, entry) => {\n const mcp = isObj(existing.mcp) ? { ...existing.mcp } : {};\n const key = entry.url ? 'shttp_servers' : 'stdio_servers';\n const arr = Array.isArray(mcp[key]) ? [...(mcp[key] as Record<string, unknown>[])] : [];\n const item: Record<string, unknown> = entry.url\n ? { url: entry.url, ...(entry.env ? { env: entry.env } : {}) }\n : { name, command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n const matches = (s: Record<string, unknown>) => (entry.url ? s.url === entry.url : s.name === name);\n const idx = arr.findIndex(matches);\n if (idx >= 0) arr[idx] = item;\n else arr.push(item);\n mcp[key] = arr;\n return { ...existing, mcp };\n },\n};\n\n// ── Claude Desktop ───────────────────────────────────────────────────────────\n// JSON `mcpServers`. (One-click installs are also available via the .mcpb bundle.)\nconst claudeDesktop: ClientSpec = {\n id: 'claude-desktop',\n label: 'Claude Desktop',\n format: 'json',\n note: 'One-click install is also available via the .mcpb bundle (npm run build:mcpb).',\n path: (ctx) => {\n if (ctx.scope === 'project') return undefined;\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');\n if (ctx.os === 'mac') return join(ctx.home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');\n return join(ctx.home, '.config', 'Claude', 'claude_desktop_config.json');\n },\n apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } }),\n};\n\n/** All registered clients, in display order. Extended by later milestones. */\nexport const CLIENTS: ClientSpec[] = [\n claudeCode, cursor, vscode, codex, windsurf,\n cline, roo, zed, junie, gemini,\n goose, openhands, claudeDesktop,\n];\n\nexport function getClient(id: string): ClientSpec | undefined {\n return CLIENTS.find((c) => c.id === id);\n}\n\nexport function listClients(): ReadonlyArray<Pick<ClientSpec, 'id' | 'label' | 'format' | 'note'>> {\n return CLIENTS.map(({ id, label, format, note }) => ({ id, label, format, note }));\n}\n","/**\n * One-click install deeplinks for hosts that support them. Cursor expects a\n * **Base64**-encoded server config; VS Code expects **URL-encoded** JSON — mixing\n * the two encodings is a classic mistake, so each has its own helper.\n */\n\nimport { mcpServerObject } from './shapes.js';\nimport type { ServerEntry } from './types.js';\n\n/** `cursor://…/mcp/install?name=<name>&config=<base64 JSON of the server config>`. */\nexport function cursorDeeplink(name: string, entry: ServerEntry): string {\n const config = Buffer.from(JSON.stringify(mcpServerObject(entry))).toString('base64');\n const params = new URLSearchParams({ name, config });\n return `cursor://anysphere.cursor-deeplink/mcp/install?${params.toString()}`;\n}\n\nexport interface VscodeDeeplinkOptions {\n /** Target VS Code Insiders (`vscode-insiders://`). */\n insiders?: boolean;\n}\n\n/** `vscode://mcp/install?<URL-encoded JSON>` where the JSON is `{ name, ...serverConfig }`. */\nexport function vscodeDeeplink(name: string, entry: ServerEntry, opts: VscodeDeeplinkOptions = {}): string {\n const scheme = opts.insiders ? 'vscode-insiders' : 'vscode';\n const payload = encodeURIComponent(JSON.stringify({ name, ...mcpServerObject(entry) }));\n return `${scheme}://mcp/install?${payload}`;\n}\n\n/** A `code --add-mcp '<json>'` CLI one-liner (alternative to the deeplink). */\nexport function codeAddMcpCommand(name: string, entry: ServerEntry): string {\n return `code --add-mcp '${JSON.stringify({ name, ...mcpServerObject(entry) })}'`;\n}\n"],"mappings":"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AA,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;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;AAA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","names":["z","z","readFileSync","readFileSync","cursor","join","HEX_SIZE","join","readFileSync","readFileSync","readFileSync","existsSync","writeFileSync","resolve","dirname","matches","createHash","createHash","mkdirSync","readFileSync","writeFileSync","existsSync","existsSync","readFileSync","mkdirSync","writeFileSync","join","join","matches","dirname","readFileSync","resolve","existsSync","writeFileSync","saved","host","execSync","join","dim","o"]}
1
+ {"version":3,"sources":["../src/cli.ts","../src/preflight.ts","../src/auth/types.ts","../src/providers/types.ts","../src/safety.ts","../src/audit.ts","../src/providers/claude.ts","../src/providers/shell.ts","../src/providers/zod-schema.ts","../src/providers/audit.ts","../src/providers/loop.ts","../src/providers/openai.ts","../src/providers/ollama.ts","../src/providers/registry.ts","../src/agent.ts","../src/config.ts","../src/schedule.ts","../src/exporter.ts","../src/hex.ts","../src/cluster.ts","../src/mapper.ts","../src/compliance/report.ts","../src/cost.ts","../src/sharing.ts","../src/sync/hash.ts","../src/sync/classify.ts","../src/sync/push.ts","../src/sync/index.ts","../src/installer/format.ts","../src/installer/merge.ts","../src/installer/shapes.ts","../src/installer/entry.ts","../src/installer/install.ts","../src/installer/registry.ts","../src/installer/deeplinks.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { Command } from 'commander';\nimport { checkPrerequisites } from './preflight.js';\nimport { CartographyDB, deriveSessionName, normalizeTenant } from './db.js';\nimport { hashToken } from './auth/identity.js';\nimport { RoleSchema } from './auth/types.js';\nimport { defaultConfig } from './types.js';\nimport type { TopologyDiff } from './types.js';\nimport { runDiscovery } from './agent.js';\nimport type { DiscoveryEvent } from './agent.js';\nimport { runLocalDiscovery } from './discovery/local.js';\nimport { loadConfig, ConfigError } from './config.js';\nimport { runOnce, nextRun } from './schedule.js';\nimport type { ScheduledRunResult } from './schedule.js';\nimport { defaultProviderRegistry } from './providers/registry.js';\nimport type { ProviderName } from './types.js';\nimport { exportAll, generateDiffMermaid, exportComplianceReport } from './exporter.js';\nimport { getRuleset, listRulesets } from './compliance/rulesets/registry.js';\nimport { formatComplianceText } from './compliance/report.js';\nimport { DriftConfigSchema } from './types.js';\nimport { runDrift } from './drift.js';\nimport { enrichCosts, CsvCostSource } from './cost.js';\nimport { readFileSync, existsSync, writeFileSync } from 'fs';\nimport { resolve, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport { createInterface } from 'readline';\nimport { IS_WIN, IS_MAC, PLATFORM, commandExists } from './platform.js';\nimport { logInfo, logError, logWarn, setVerbose } from './logger.js';\nimport { cleanupTempFiles } from './bookmarks.js';\nimport { stripSensitive } from './tools.js';\nimport { startMcp } from './mcp/start.js';\nimport { startApi } from './api/start.js';\nimport { SharingLevelSchema } from './types.js';\nimport { loadOrgKey, rotateOrgKey } from './orgkey.js';\nimport { reversePseudonym } from './anonymize.js';\nimport { previewShare } from './sharing.js';\nimport { runSyncClassify, pushDeltas } from './sync/index.js';\nimport type { PushItem } from './sync/push.js';\nimport type { CartographyConfig, PendingShareRow } from './types.js';\nimport { getClient, listClients, planInstall, applyInstall, renderDiff, defaultContext, defaultServerEntry, DEFAULT_SERVER_NAME, cursorDeeplink, vscodeDeeplink, codeAddMcpCommand } from './installer/index.js';\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\n/**\n * Render a {@link TopologyDiff} as human-readable text. Module-scope so both the\n * `diff` command and the `discover --update` (incremental rescan) path can reuse it.\n */\nfunction renderDiffText(d: TopologyDiff): string {\n const out: string[] = [];\n out.push(`${bold('Topology diff')} ${dim(d.base.sessionId.slice(0, 8))} → ${dim(d.current.sessionId.slice(0, 8))}`);\n out.push(` base: ${d.base.nodeCount} nodes, ${d.base.edgeCount} edges ${dim(d.base.startedAt)}`);\n out.push(` current: ${d.current.nodeCount} nodes, ${d.current.edgeCount} edges ${dim(d.current.startedAt)}`);\n out.push('');\n out.push(` nodes: ${green('+' + d.summary.nodesAdded)} ${red('-' + d.summary.nodesRemoved)} ${yellow('~' + d.summary.nodesChanged)} edges: ${green('+' + d.summary.edgesAdded)} ${red('-' + d.summary.edgesRemoved)}`);\n if (d.summary.nodesAdded + d.summary.nodesRemoved + d.summary.nodesChanged + d.summary.edgesAdded + d.summary.edgesRemoved === 0) {\n out.push('');\n out.push(` ${green('✓')} No drift between the two sessions.`);\n return out.join('\\n');\n }\n out.push('');\n for (const n of d.nodes.added) out.push(` ${green('+')} ${n.id} ${dim('(' + n.type + ')')}`);\n for (const n of d.nodes.removed) out.push(` ${red('-')} ${n.id} ${dim('(' + n.type + ')')}`);\n for (const c of d.nodes.changed) out.push(` ${yellow('~')} ${c.id} ${dim('[' + c.changedFields.join(', ') + ']')}`);\n for (const e of d.edges.added) out.push(` ${green('+')} edge ${e.sourceId} ${dim('─' + e.relationship + '→')} ${e.targetId}`);\n for (const e of d.edges.removed) out.push(` ${red('-')} edge ${e.sourceId} ${dim('─' + e.relationship + '→')} ${e.targetId}`);\n return out.join('\\n');\n}\n\n/** One-line human-readable summary of a scheduled drift run (`text` output format). */\nfunction renderDriftSummaryText(r: ScheduledRunResult): string {\n const s = r.delta.summary;\n const base = r.baseSessionId ? r.baseSessionId.slice(0, 8) : '∅';\n return (\n `${green('+' + s.nodesAdded)}/${red('-' + s.nodesRemoved)}/${yellow('~' + s.nodesChanged)} nodes, ` +\n `${green('+' + s.edgesAdded)}/${red('-' + s.edgesRemoved)} edges ` +\n `${dim('(session ' + r.sessionId.slice(0, 8) + ', base ' + base + ')')}`\n );\n}\n\n/**\n * Post-scan central-DB sync hook (2.11). Classifies + enqueues; prints a one-line\n * stderr summary. No-op (and silent) when `centralDb` is unconfigured. Never throws\n * out — a sync failure must not fail the scan that produced the data.\n */\nfunction maybeQueueForSync(\n db: CartographyDB,\n sessionId: string,\n config: CartographyConfig,\n w: (s: string) => void,\n): void {\n if (!config.centralDb?.url) return;\n try {\n const r = runSyncClassify(db, sessionId, config);\n if (r.enqueued > 0) {\n w(` ${cyan('⇪')} ${bold(String(r.enqueued))} item(s) queued for review — run ${bold(\"'datasynx-cartography sync review'\")}\\n`);\n } else if (r.autoShared > 0) {\n w(` ${cyan('⇪')} ${bold(String(r.autoShared))} item(s) auto-approved by policy — run ${bold(\"'datasynx-cartography sync push'\")}\\n`);\n }\n } catch (err) {\n logWarn(`central-DB sync classify skipped: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\nmain();\n\nfunction main(): void {\n // ── Graceful shutdown ──\n let activeDb: CartographyDB | null = null;\n const shutdown = (signal: NodeJS.Signals) => {\n logWarn(`Received ${signal}, shutting down gracefully…`);\n if (activeDb) {\n try { activeDb.close(); } catch { /* already closed */ }\n activeDb = null;\n }\n // Re-raise with the default disposition so the process terminates with the\n // correct signal exit status (e.g. 130 for SIGINT) without process.exit().\n process.removeListener('SIGTERM', shutdown);\n process.removeListener('SIGINT', shutdown);\n process.kill(process.pid, signal);\n };\n process.on('SIGTERM', shutdown);\n process.on('SIGINT', shutdown);\n\n // Clean up orphaned temp files from previous bookmark/history scans\n cleanupTempFiles();\n\n const program = new Command();\n\n const CMD = 'datasynx-cartography';\n const __dirname = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));\n let VERSION = '0.0.0';\n try {\n VERSION = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf-8')).version ?? VERSION;\n } catch {\n logWarn('Could not read package.json version; falling back to 0.0.0');\n }\n\n program\n .name(CMD)\n .description('AI-powered Infrastructure Discovery & Agentic AI Cartography')\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('--provider <name>', 'Agent provider: claude, openai, ollama (or CARTOGRAPHY_PROVIDER)')\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('--name <name>', 'Custom session name (default: auto-derived from the topology)')\n .option('--update [sessionId]', 'Re-scan an existing session in place (deterministic local scan; default: latest discover session)')\n .option('--output-format <fmt>', 'Progress/result format: text, json, stream-json', 'text')\n .option('-v, --verbose', 'Show agent reasoning', false)\n .action(async (opts) => {\n // ── Provider selection (--provider, then CARTOGRAPHY_PROVIDER, default claude) ──\n const providerName = (opts.provider ?? process.env.CARTOGRAPHY_PROVIDER ?? 'claude') as string;\n if (!defaultProviderRegistry.has(providerName)) {\n process.stderr.write(\n `❌ Unknown provider \"${providerName}\" (valid: ${defaultProviderRegistry.names().join(', ')})\\n`,\n );\n process.exitCode = 2;\n return;\n }\n const provider = providerName as ProviderName;\n\n checkPrerequisites(provider);\n\n // ── Input validation ──\n const parsedDepth = parseInt(opts.depth, 10);\n const parsedMaxTurns = parseInt(opts.maxTurns, 10);\n if (Number.isNaN(parsedDepth) || parsedDepth < 1 || parsedDepth > 50) {\n process.stderr.write(`❌ Invalid --depth: \"${opts.depth}\" (must be 1–50)\\n`);\n process.exitCode = 2;\n return;\n }\n if (Number.isNaN(parsedMaxTurns) || parsedMaxTurns < 1 || parsedMaxTurns > 500) {\n process.stderr.write(`❌ Invalid --max-turns: \"${opts.maxTurns}\" (must be 1–500)\\n`);\n process.exitCode = 2;\n return;\n }\n\n const fmt = (opts.outputFormat ?? 'text') as 'text' | 'json' | 'stream-json';\n if (!['text', 'json', 'stream-json'].includes(fmt)) {\n process.stderr.write(`❌ Invalid --output-format: \"${opts.outputFormat}\" (must be text, json, or stream-json)\\n`);\n process.exitCode = 2;\n return;\n }\n // Headless modes keep stdout machine-readable: human progress (spinner, banners)\n // goes to stderr only, and interactive review/follow-up are skipped entirely.\n const isText = fmt === 'text';\n\n setVerbose(opts.verbose);\n\n const config = defaultConfig({\n entryPoints: opts.entry,\n maxDepth: parsedDepth,\n maxTurns: parsedMaxTurns,\n provider,\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 logInfo('Discovery started', {\n entryPoints: config.entryPoints,\n provider: config.provider,\n model: config.agentModel,\n maxTurns: config.maxTurns,\n maxDepth: config.maxDepth,\n });\n\n const db = new CartographyDB(config.dbPath);\n activeDb = db;\n\n const w = process.stderr.write.bind(process.stderr);\n\n // ── Incremental rescan (2.1): `--update` re-scans an existing session in\n // place via the deterministic local scanner registry, prints the delta,\n // and exits — it never invokes the agent loop or creates a new session. ──\n if (opts.update) {\n const tenantId = normalizeTenant(opts.org);\n const targetId = typeof opts.update === 'string'\n ? opts.update\n : db.getLatestSession('discover', tenantId)?.id;\n const targetSession = targetId ? db.getSession(targetId) : undefined;\n if (!targetId || !targetSession) {\n process.stderr.write(\n `❌ No discover session to update${typeof opts.update === 'string' ? ` (id \"${opts.update}\")` : ''}; run \\`discover\\` first.\\n`,\n );\n process.exitCode = 2;\n db.close();\n activeDb = null;\n return;\n }\n const baseNodeCount = db.getNodes(targetId).length;\n const baseEdgeCount = db.getEdges(targetId).length;\n if (isText) {\n w('\\n');\n w(` ${bold('CARTOGRAPHY')} ${dim('incremental rescan · ' + targetId.slice(0, 8))}\\n`);\n w(dim(' ────────────────────────────────────────────────\\n\\n'));\n }\n try {\n const r = await runLocalDiscovery(db, targetId, { mode: 'update' });\n const updated = db.getSession(targetId);\n const diff: TopologyDiff = {\n base: { sessionId: targetId, startedAt: targetSession.startedAt, nodeCount: baseNodeCount, edgeCount: baseEdgeCount },\n current: { sessionId: targetId, startedAt: updated?.lastScannedAt ?? new Date().toISOString(), nodeCount: r.nodes, edgeCount: r.edges },\n nodes: r.delta?.nodes ?? { added: [], removed: [], changed: [], unchanged: 0 },\n edges: r.delta?.edges ?? { added: [], removed: [], unchanged: 0 },\n summary: r.delta?.summary ?? { nodesAdded: 0, nodesRemoved: 0, nodesChanged: 0, edgesAdded: 0, edgesRemoved: 0 },\n anomalies: { base: [], current: [], added: [] },\n };\n if (fmt === 'text') w(renderDiffText(diff) + '\\n\\n');\n else process.stdout.write(JSON.stringify(diff, null, 2) + '\\n');\n logInfo('Incremental rescan complete', { sessionId: targetId, ...diff.summary });\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logError('Incremental rescan failed', { sessionId: targetId, error: errMsg });\n w(`\\n ${bold(red('✗'))} Rescan failed: ${errMsg}\\n`);\n process.exitCode = 1;\n }\n db.close();\n activeDb = null;\n return;\n }\n\n const sessionId = db.createSession('discover', config, opts.org);\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 if (isText) {\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\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 if (!isText) {\n // stream-json emits one JSON object per line on stdout; json buffers (final catalog below).\n if (fmt === 'stream-json') process.stdout.write(JSON.stringify(event) + '\\n');\n return;\n }\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__cartography__', '');\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 if (!isText) return '(Non-interactive mode — please continue without this information)';\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 const rawMsg = err instanceof Error ? err.message : String(err);\n const errMsg = rawMsg.replace(/https?:\\/\\/[^\\s]+/g, u => stripSensitive(u));\n logError('Discovery failed', { sessionId, error: errMsg });\n w(`\\n ${bold('\\x1b[31m✗\\x1b[0m')} Discovery failed: ${errMsg}\\n`);\n db.close();\n activeDb = null;\n process.exitCode = 1;\n return;\n }\n\n stopSpinner();\n db.endSession(sessionId);\n // 2.11 central-DB sync: classify the freshly-scanned topology against the\n // persisted policy and enqueue new/unmatched items for review. No-op unless\n // `centralDb` is configured (config.json / CARTOGRAPHY_CENTRAL_*). This is the\n // manual caller; the scheduled engine (`schedule`) wires the same helper.\n maybeQueueForSync(db, sessionId, config, w);\n // Deterministic, human-friendly session label (override with --name).\n const sessionName = (opts.name as string | undefined)?.trim()\n || deriveSessionName(db.getGraphSummary(sessionId), db.getSession(sessionId)?.startedAt ?? new Date().toISOString());\n db.setSessionName(sessionId, sessionName);\n const stats = db.getStats(sessionId);\n const totalSec = ((Date.now() - startTime) / 1000).toFixed(1);\n\n logInfo('Discovery completed', {\n sessionId,\n nodes: stats.nodes,\n edges: stats.edges,\n durationSec: parseFloat(totalSec),\n });\n\n if (!isText) {\n // Headless: emit machine-readable result on stdout, write artifacts, finish.\n const durationMs = Date.now() - startTime;\n if (fmt === 'stream-json') {\n process.stdout.write(JSON.stringify({ kind: 'result', sessionId, nodes: stats.nodes, edges: stats.edges, durationMs }) + '\\n');\n } else {\n process.stdout.write(JSON.stringify(\n { sessionId, stats, nodes: db.getNodes(sessionId), edges: db.getEdges(sessionId), durationMs },\n null, 2,\n ) + '\\n');\n }\n exportAll(db, sessionId, config.outputDir, ['discovery']);\n db.close();\n activeDb = null;\n return;\n }\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, ['discovery']);\n\n const discoveryPath = resolve(config.outputDir, 'discovery.html');\n w('\\n');\n if (existsSync(discoveryPath)) {\n w(` ${green('✓')} ${bold('discovery.html')} ${dim('← Enterprise Map')}\\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, ['discovery']);\n if (existsSync(discoveryPath)) {\n w(` ${green('✓')} ${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,cost')\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'];\n exportAll(db, session.id, opts.output, formats);\n process.stderr.write(`✓ Exported to: ${opts.output}\\n`);\n\n db.close();\n });\n\n // ── DIFF / DRIFT ───────────────────────────────────────────────────────────\n\n program\n .command('diff [base] [current]')\n .description('Compare two discovery sessions (drift detection). Defaults to the two most recent.')\n .option('--format <fmt>', 'Output format: text, json, mermaid', 'text')\n .option('-o, --output <file>', 'Write to a file instead of stdout')\n .option('--db <path>', 'DB path')\n .action((base: string | undefined, current: string | undefined, opts) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n activeDb = db;\n try {\n // getSessions() is newest-first (rowid DESC): [0] = latest, [1] = previous.\n const sessions = db.getSessions();\n const currentId = current ?? sessions[0]?.id;\n const baseId = base ?? sessions[1]?.id;\n if (!baseId || !currentId) {\n process.stderr.write('❌ Need at least two discovery sessions to diff\\n');\n process.exitCode = 1;\n return;\n }\n if (baseId === currentId) {\n process.stderr.write('❌ Base and current session are the same\\n');\n process.exitCode = 1;\n return;\n }\n const d = db.diffSessions(baseId, currentId);\n const out = opts.format === 'json' ? JSON.stringify(d, null, 2)\n : opts.format === 'mermaid' ? generateDiffMermaid(d)\n : renderDiffText(d);\n if (opts.output) {\n writeFileSync(opts.output, out + '\\n');\n process.stderr.write(`✓ Wrote diff to: ${opts.output}\\n`);\n } else {\n process.stdout.write(out + '\\n');\n }\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n } finally {\n db.close();\n activeDb = null;\n }\n });\n\n // ── COMPLIANCE SCORING (3.4) ─────────────────────────────────────────────────\n // Grade a session against a declarative ruleset (CIS/SOC2/ISO 27001 starter sets).\n // Deterministic, read-only; degrades cleanly (unknown ruleset → exit 1, no throw).\n\n program\n .command('compliance [session-id]')\n .description('Score a session against a compliance ruleset (CIS/SOC2/ISO 27001 starter sets)')\n .option('--ruleset <name>', 'Ruleset: baseline, cis, soc2, iso27001', 'baseline')\n .option('--format <fmt>', 'Output format: text, json, markdown, mermaid', 'text')\n .option('-o, --output <file>', 'Write to a file instead of stdout')\n .option('--db <path>', 'DB path')\n .action((sessionId: string | undefined, opts) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n activeDb = db;\n try {\n const ruleset = getRuleset(opts.ruleset);\n if (!ruleset) {\n process.stderr.write(`❌ Unknown ruleset: \"${opts.ruleset}\" (available: ${listRulesets().map((r) => r.name).join(', ')})\\n`);\n process.exitCode = 1;\n return;\n }\n const sid = sessionId ?? db.getLatestSession()?.id;\n if (!sid) {\n process.stderr.write('❌ No session to score (run discovery first or pass a session id)\\n');\n process.exitCode = 1;\n return;\n }\n const report = db.scoreSession(sid, ruleset);\n const out = opts.format === 'json' || opts.format === 'markdown' || opts.format === 'mermaid'\n ? exportComplianceReport(report, opts.format)\n : formatComplianceText(report);\n if (opts.output) {\n writeFileSync(opts.output, out + '\\n');\n process.stderr.write(`✓ Wrote compliance report to: ${opts.output}\\n`);\n } else {\n process.stdout.write(out + '\\n');\n }\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n } finally {\n db.close();\n activeDb = null;\n }\n });\n\n // ── DRIFT DETECTION + ALERTING (3.1) ─────────────────────────────────────────\n // Classify drift between two sessions into a severity-ranked alert and emit it\n // to configured sinks (stdout default; opt-in outbound webhook). Passive monitor:\n // a single-session catalog is a clean no-op (exit 0), not an error.\n\n program\n .command('drift [base] [current]')\n .description('Classify drift between two sessions and emit to configured sinks (default: stdout). Defaults to the two most recent.')\n .option('--min-severity <s>', 'Minimum severity to emit: info|warning|critical', 'info')\n .option('--webhook <url>', 'Outbound webhook URL (overrides config; token via CARTOGRAPHY_DRIFT_TOKEN)')\n .option('--db <path>', 'DB path')\n .action(async (base: string | undefined, current: string | undefined, opts) => {\n let drift;\n try {\n drift = DriftConfigSchema.parse({\n minSeverity: opts.minSeverity,\n sinks: opts.webhook ? [{ type: 'webhook', url: opts.webhook }] : [{ type: 'stdout' }],\n });\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n return;\n }\n const config = defaultConfig({ ...(opts.db ? { dbPath: opts.db } : {}), drift });\n const db = new CartographyDB(config.dbPath);\n activeDb = db;\n try {\n const alert = await runDrift(db, config, { base, current, minSeverity: drift.minSeverity });\n if (!alert) {\n process.stderr.write('ℹ Need at least two discovery sessions for drift; nothing to do.\\n');\n return;\n }\n // The stdout sink (if configured) already wrote the payload; summary to stderr.\n process.stderr.write(`✓ drift severity=${alert.severity} items=${alert.items.length}\\n`);\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n } finally {\n db.close();\n activeDb = null;\n }\n });\n\n // ── SCHEDULED DISCOVERY (2.5) ────────────────────────────────────────────────\n // Recurring, headless, read-only rescans that record per-run topology drift.\n // Reuses the deterministic local discovery path (no agent loop, no API key) and\n // the incremental `mode:'update'` rescan, persisting one `drift_runs` row per pass.\n\n program\n .command('schedule')\n .description('Run discovery recurringly and record per-run topology drift')\n .requiredOption('--config <file>', 'Path to a JSON config file with a schedule block')\n .option('--once', 'Run a single pass and exit (cron-driver friendly; default)', false)\n .option('--watch', 'Run continuously on the configured cron schedule', false)\n .option('--output-format <fmt>', 'Result format: text, json, stream-json (overrides config)')\n .option('--db <path>', 'DB path (overrides config)')\n .action(async (opts) => {\n let cfg;\n try {\n cfg = loadConfig(opts.config);\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof ConfigError ? err.message : String(err)}\\n`);\n process.exitCode = 2;\n return;\n }\n if (opts.db) cfg = defaultConfig({ ...cfg, dbPath: opts.db });\n\n const fmt = (opts.outputFormat ?? cfg.schedule?.outputFormat ?? 'json') as 'text' | 'json' | 'stream-json';\n if (!['text', 'json', 'stream-json'].includes(fmt)) {\n process.stderr.write(`❌ Invalid --output-format: \"${fmt}\" (must be text, json, or stream-json)\\n`);\n process.exitCode = 2;\n return;\n }\n if (opts.once && opts.watch) {\n process.stderr.write('❌ --once and --watch are mutually exclusive\\n');\n process.exitCode = 2;\n return;\n }\n\n const cron = cfg.schedule?.cron;\n if (opts.watch && !cron) {\n process.stderr.write('❌ --watch requires a `schedule.cron` in the config file\\n');\n process.exitCode = 2;\n return;\n }\n // Validate cron up-front so a bad expression fails before any scan.\n if (cron) {\n try {\n nextRun(cron, new Date());\n } catch (err) {\n process.stderr.write(`❌ Invalid cron \"${cron}\": ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 2;\n return;\n }\n }\n\n const db = new CartographyDB(cfg.dbPath);\n activeDb = db;\n\n const emit = (r: ScheduledRunResult): void => {\n if (fmt === 'text') {\n process.stdout.write(renderDriftSummaryText(r) + '\\n');\n } else {\n const payload = { sessionId: r.sessionId, baseSessionId: r.baseSessionId ?? null, summary: r.delta.summary };\n process.stdout.write(JSON.stringify(payload) + '\\n');\n }\n };\n\n const doRun = async (): Promise<void> => {\n const r = await runOnce(cfg, db);\n db.recordDriftRun(r.sessionId, r.baseSessionId, r.delta);\n // 2.11: enqueue this run's topology for central-DB review (no-op unless\n // `centralDb` is configured in the loaded config file).\n maybeQueueForSync(db, r.sessionId, cfg, (s) => process.stderr.write(s));\n emit(r);\n };\n\n if (opts.watch) {\n let stopped = false;\n let timer: ReturnType<typeof setTimeout> | null = null;\n // setTimeout delays are 32-bit ms (~24.8 days). For a far-future cron\n // instant we sleep in capped chunks and re-check, so the timer never\n // overflows (which would fire immediately) — see TimeoutOverflowWarning.\n const MAX_DELAY_MS = 24 * 60 * 60 * 1000; // 1 day per chunk\n let nextAnnounced: string | null = null;\n const schedule = (): void => {\n if (stopped) return;\n const next = nextRun(cron!, new Date());\n const targetMs = next.getTime();\n if (next.toISOString() !== nextAnnounced) {\n logInfo(`next scheduled run at ${next.toISOString()}`);\n nextAnnounced = next.toISOString();\n }\n const remaining = targetMs - Date.now();\n if (remaining > MAX_DELAY_MS) {\n // Not due yet — sleep a bounded chunk, then re-evaluate.\n timer = setTimeout(schedule, MAX_DELAY_MS);\n return;\n }\n timer = setTimeout(() => {\n void (async () => {\n try {\n await doRun();\n } catch (err) {\n // One bad run never kills the schedule — log and continue to the next tick.\n logError(`scheduled run failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n nextAnnounced = null; // force re-announce of the following instant\n schedule();\n })();\n }, Math.max(0, remaining));\n };\n // Cooperate with the module-level SIGINT/SIGTERM shutdown: stop the loop and\n // clear the pending timer so the event loop drains after `shutdown` closes the DB.\n const stop = (): void => {\n stopped = true;\n if (timer) clearTimeout(timer);\n };\n process.once('SIGINT', stop);\n process.once('SIGTERM', stop);\n schedule();\n return; // event loop kept alive by the pending timer\n }\n\n // Default: a single pass (--once or no flag).\n try {\n await doRun();\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n } finally {\n db.close();\n activeDb = null;\n }\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 if (session.name) process.stdout.write(` Name: ${session.name}\\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 const events = db.getEvents(session.id);\n if (events.length > 0) {\n process.stdout.write('\\n Recent activity:\\n');\n for (const e of events.slice(-15)) {\n const kb = e.resultBytes != null ? ` (${(e.resultBytes / 1024).toFixed(1)} KB)` : '';\n process.stdout.write(` ${e.timestamp} ${e.process} ${(e.command ?? '').slice(0, 60)}${kb}\\n`);\n }\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 `${session.name ? ` ${session.name}` : ''}\\n`\n );\n }\n\n db.close();\n });\n\n // ── OVERVIEW ──────────────────────────────────────────────────────────────\n\n program\n .command('overview')\n .description('Overview of all cartography sessions')\n .option('--db <path>', 'DB path')\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;\n for (const s of sessions) {\n const st = db.getStats(s.id);\n totalNodes += st.nodes; totalEdges += st.edges;\n }\n\n w(` ${b(String(sessions.length))} Sessions · ${b(String(totalNodes))} Nodes · `);\n w(`${b(String(totalEdges))} Edges\\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 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)}${session.name ? ` ${d(session.name)}` : ''}\\n`);\n w(` ${d('Nodes: ' + stats.nodes + ' Edges: ' + stats.edges)}\\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 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 path')\n .option('--model <m>', 'Model (defaults to the fast helper model)')\n .action(async (sessionIdArg: string | undefined, opts) => {\n const config = defaultConfig();\n const model = (opts as { model?: string }).model ?? config.models.fast;\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\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')}\\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 });\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 analyze dependencies, identify risks, suggest optimizations.\n\nINFRASTRUCTURE SNAPSHOT (${nodes.length} nodes, ${edges.length} edges):\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,\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 const platformName = IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux';\n\n out('\\n');\n out(b(' DATASYNX CARTOGRAPHY') + ' ' + dim('v' + VERSION) + '\\n');\n out(dim(' AI-powered Infrastructure Discovery & Agentic AI Cartography\\n'));\n out(dim(` Platform: ${platformName}\\n`));\n out('\\n');\n line();\n\n // ── PLATFORM SUPPORT\n out(b(cyan(' CROSS-PLATFORM SUPPORT\\n')));\n out('\\n');\n out(` ${green('Linux')} ss, ps, dpkg/snap/flatpak, find, /bin/sh\\n`);\n out(` ${green('macOS')} lsof, ps, /Applications, Homebrew, Spotlight, /bin/sh\\n`);\n out(` ${green('Windows')} Get-NetTCPConnection, Get-Process, Get-Service, Registry,\\n`);\n out(` winget, choco, scoop, PowerShell\\n`);\n out('\\n');\n out(dim(' Network scanning:\\n'));\n out(dim(' Linux: ss -tlnp\\n'));\n out(dim(' macOS: lsof -iTCP -sTCP:LISTEN -n -P\\n'));\n out(dim(' Windows: Get-NetTCPConnection -State Listen\\n'));\n out('\\n');\n out(dim(' Installed apps:\\n'));\n out(dim(' Linux: dpkg, rpm, snap, flatpak, .desktop files\\n'));\n out(dim(' macOS: /Applications, brew list, Spotlight (mdfind)\\n'));\n out(dim(' Windows: winget list, Registry scan, choco, scoop\\n'));\n out('\\n');\n out(dim(' Browser bookmarks & history:\\n'));\n out(dim(' Linux: ~/.config/google-chrome, Snap/Flatpak variants, ~/.mozilla\\n'));\n out(dim(' macOS: ~/Library/Application Support/Google/Chrome, ~/Library/.../Firefox\\n'));\n out(dim(' Windows: %LOCALAPPDATA%\\\\Google\\\\Chrome\\\\User Data, %APPDATA%\\\\Mozilla\\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 (provider-agnostic: claude, openai, ollama).\\n`);\n out(` The agent autonomously runs ${IS_WIN ? 'Get-NetTCPConnection, Get-Process' : IS_MAC ? 'lsof, ps' : '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(' discovery.html Enterprise Map\\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, map, cost (default: all but cost)\\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\\n'));\n out(dim(' └── Platform Detection (platform.ts)\\n'));\n out(dim(' └── Shell: /bin/sh (Unix) | PowerShell (Windows)\\n'));\n out(dim(' └── Agent Orchestrator (agent.ts)\\n'));\n out(dim(' └── runDiscovery() → AgentProvider (claude|openai|ollama) + 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(' scan_k8s, scan_aws, scan_gcp, scan_azure\\n'));\n out(dim(' └── CartographyDB (SQLite WAL)\\n'));\n out('\\n');\n line();\n\n // ── SAFETY\n out(b(cyan(' SAFETY\\n')));\n out('\\n');\n out(dim(' PreToolUse hook blocks ALL destructive commands:\\n'));\n out(dim(' Unix: rm, mv, dd, chmod, kill, docker rm/run, kubectl delete, >\\n'));\n out(dim(' PowerShell: Remove-Item, Stop-Process, Stop-Service, Out-File, etc.\\n'));\n out(dim(' Claude only reads — never writes, never deletes.\\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 if (IS_WIN) {\n out(' $env:ANTHROPIC_API_KEY=\"sk-ant-...\"\\n');\n } else {\n out(' export ANTHROPIC_API_KEY=sk-ant-...\\n');\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('cost')\n .description('Import cost/owner attribution from a CSV and enrich a session (FinOps)')\n .requiredOption('--file <path>', 'CSV: nodeId,owner,amount,currency,period[,source]')\n .option('--session <id>', 'Session to enrich (default: latest)')\n .option('--match <strategy>', 'Row→node match: nodeId | name | tag', 'nodeId')\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 activeDb = db;\n try {\n const sessionId = opts.session ?? db.getLatestSession('discover')?.id;\n if (!sessionId) {\n process.stderr.write('❌ No session to enrich (run discovery first or pass --session)\\n');\n process.exitCode = 1;\n return;\n }\n const match = opts.match as 'nodeId' | 'name' | 'tag';\n if (!['nodeId', 'name', 'tag'].includes(match)) {\n process.stderr.write(`❌ Invalid --match: \"${match}\" (nodeId | name | tag)\\n`);\n process.exitCode = 1;\n return;\n }\n const source = new CsvCostSource({ filePath: opts.file, match, db, sessionId });\n const r = await enrichCosts(db, sessionId, source);\n process.stderr.write(`✓ cost: ${r.matched} matched, ${r.unmatched} unmatched (of ${r.total}) from ${r.source}\\n`);\n if (r.unmatchedIds.length > 0) {\n process.stderr.write(` unmatched ids: ${r.unmatchedIds.slice(0, 20).join(', ')}${r.unmatchedIds.length > 20 ? ' …' : ''}\\n`);\n }\n if (r.matched === 0 && r.total > 0) process.exitCode = 1;\n } catch (err) {\n process.stderr.write(`❌ ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n } finally {\n db.close();\n activeDb = null;\n }\n });\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('--org <name>', 'Tenant/organization to scope the session to (default: local)')\n .option('--db <path>', 'DB path')\n .action(async (opts) => {\n const config = defaultConfig({ ...(opts.db ? { dbPath: opts.db } : {}), ...(opts.org ? { organization: opts.org } : {}) });\n const db = new CartographyDB(config.dbPath);\n const sessionId = opts.session ?? db.createSession('discover', config, opts.org);\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) >= 20) {\n ok(`Node.js ${nodeVer}`);\n } else {\n err(`Node.js ${nodeVer} — requires >=20`);\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 (platform-aware)\n const localTools: Array<[string, string, string]> = [\n ['docker', 'docker --version', 'all'],\n ];\n // Network scanning tool varies by platform\n if (IS_WIN) {\n localTools.push(['PowerShell (Get-NetTCPConnection)', 'powershell -Command \"Get-NetTCPConnection -State Listen | Select-Object -First 1\"', 'win32']);\n } else if (IS_MAC) {\n localTools.push(['lsof', 'lsof -v 2>&1 | head -1', 'darwin']);\n } else {\n localTools.push(['ss', 'ss --version', 'linux']);\n }\n for (const [name, cmd] of localTools) {\n try {\n execSync(cmd, { stdio: 'pipe', timeout: 10_000 });\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 // ── Prune ──────────────────────────────────────────────────────────────────\n\n program\n .command('prune')\n .description('Delete old sessions and their data')\n .option('--older-than <days>', 'Delete sessions older than N days', '30')\n .option('--events-older-than <days>', 'Compact the audit trail: delete activity events older than N days (4.7 retention)')\n .option('--db <path>', 'DB path')\n .option('--dry-run', 'Show what would be deleted without actually deleting', false)\n .action((opts) => {\n const days = parseInt(opts.olderThan, 10);\n if (Number.isNaN(days) || days < 1) {\n process.stderr.write(`Invalid --older-than: \"${opts.olderThan}\" (must be >= 1)\\n`);\n process.exitCode = 2;\n return;\n }\n\n const config = defaultConfig({ ...(opts.db ? { dbPath: opts.db } : {}) });\n const db = new CartographyDB(config.dbPath);\n const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();\n\n // Audit-trail retention/compaction (4.7) — independent of session pruning.\n if (opts.eventsOlderThan !== undefined) {\n const eventDays = parseInt(opts.eventsOlderThan, 10);\n if (Number.isNaN(eventDays) || eventDays < 1) {\n process.stderr.write(`Invalid --events-older-than: \"${opts.eventsOlderThan}\" (must be >= 1)\\n`);\n process.exitCode = 2;\n db.close();\n return;\n }\n const eventCutoff = new Date(Date.now() - eventDays * 24 * 60 * 60 * 1000).toISOString();\n if (opts.dryRun) {\n process.stderr.write(`Would compact the audit trail (delete events older than ${eventDays} days).\\n`);\n } else {\n const removed = db.pruneEventsOlderThan(eventCutoff);\n logInfo('Audit events pruned', { removed, olderThanDays: eventDays });\n process.stderr.write(`Deleted ${removed} audit event(s) older than ${eventDays} days.\\n`);\n }\n }\n\n const sessions = db.getSessions().filter(s => s.startedAt < cutoff);\n\n if (sessions.length === 0) {\n process.stderr.write(`No sessions older than ${days} days.\\n`);\n db.close();\n return;\n }\n\n if (opts.dryRun) {\n process.stderr.write(`Would delete ${sessions.length} session(s) older than ${days} days:\\n`);\n for (const s of sessions) {\n const stats = db.getStats(s.id);\n process.stderr.write(` ${s.id.substring(0, 8)} ${s.startedAt.substring(0, 19)} nodes:${stats.nodes} edges:${stats.edges}\\n`);\n }\n } else {\n const deleted = db.pruneSessions(cutoff);\n logInfo('Sessions pruned', { deleted, olderThanDays: days });\n process.stderr.write(`Deleted ${deleted} session(s) older than ${days} days.\\n`);\n }\n\n db.close();\n });\n\n // ── MCP server ────────────────────────────────────────────────────────────\n\n program\n .command('list-clients')\n .description('List the AI hosts the installer can configure')\n .action(() => {\n o('\\n' + bold(' Supported MCP hosts:') + '\\n\\n');\n for (const c of listClients()) {\n o(` ${green(c.id.padEnd(16))} ${bold(c.label.padEnd(20))} ${dim(c.format)}\\n`);\n if (c.note) o(` ${' '.repeat(16)} ${dim('↳ ' + c.note)}\\n`);\n }\n o('\\n' + dim(` Install: ${CMD} install --client <id> [--project] [--dry-run]`) + '\\n\\n');\n });\n\n program\n .command('install')\n .description('Register the Cartography MCP server into an AI host\\'s config (parse-merge, never clobber)')\n .requiredOption('--client <id>', 'Target host id (see `list-clients`)')\n .option('--global', 'Write the global/user config (default)', false)\n .option('--project', 'Write the project-local config instead', false)\n .option('--dry-run', 'Show the merge diff without writing', false)\n .option('--deeplink', 'Print a one-click install deeplink instead of writing (Cursor / VS Code)', false)\n .option('--name <name>', 'Server name to register', DEFAULT_SERVER_NAME)\n .option('--http', 'Register the Streamable HTTP endpoint instead of stdio', false)\n .option('--url <url>', 'HTTP endpoint (with --http)')\n .option('--db <path>', 'Pass --db <path> to the server')\n .option('--session <id>', 'Pass --session <id> to the server')\n .action((opts) => {\n const spec = getClient(opts.client);\n if (!spec) {\n logError(`Unknown client \"${opts.client}\". Run \\`${CMD} list-clients\\` to see options.`);\n process.exitCode = 1;\n return;\n }\n const scope = opts.project ? 'project' : 'global';\n const packageArgs: string[] = [];\n if (opts.db) packageArgs.push('--db', opts.db);\n if (opts.session) packageArgs.push('--session', opts.session);\n const entry = defaultServerEntry({\n transport: opts.http ? 'http' : 'stdio',\n ...(opts.url ? { url: opts.url } : {}),\n ...(packageArgs.length ? { packageArgs } : {}),\n });\n if (opts.deeplink) {\n if (opts.client === 'cursor') {\n o('\\n' + bold(' Cursor one-click:') + '\\n ' + cyan(cursorDeeplink(opts.name, entry)) + '\\n\\n');\n } else if (opts.client === 'vscode') {\n o('\\n' + bold(' VS Code one-click:') + '\\n ' + cyan(vscodeDeeplink(opts.name, entry)) + '\\n');\n o(' ' + dim('or: ') + codeAddMcpCommand(opts.name, entry) + '\\n\\n');\n } else {\n logWarn(`No deeplink available for \"${opts.client}\". Deeplinks exist for: cursor, vscode.`);\n }\n return;\n }\n try {\n const plan = planInstall(spec, defaultContext(scope), { serverName: opts.name, entry });\n o('\\n' + bold(` ${plan.label}`) + dim(` (${plan.format}, ${scope})`) + '\\n');\n o(dim(` ${plan.path}`) + '\\n');\n if (plan.note) o(yellow(` ⚠ ${plan.note}`) + '\\n');\n o('\\n' + renderDiff(plan.before, plan.after) + '\\n\\n');\n if (!plan.changed) {\n o(green(' ✓ Already up to date — nothing to write.') + '\\n\\n');\n return;\n }\n if (opts.dryRun) {\n o(yellow(' Dry run — no file written.') + '\\n\\n');\n return;\n }\n applyInstall(plan);\n o(green(` ✓ Wrote ${plan.fileExists ? 'updated' : 'new'} config.`) + ' ' + dim('Restart the host to pick it up.') + '\\n\\n');\n } catch (err) {\n logError(err instanceof Error ? err.message : String(err));\n process.exitCode = 1;\n }\n });\n\n program\n // ── consent: persistent sharing policy + admin anonymization (2.10) ─────────\n const consent = program\n .command('consent')\n .description('Manage the per-employee data-sharing policy (none|anonymized|full) + admin anonymization');\n\n consent\n .command('default <level>')\n .description('Set the global default sharing level (none|anonymized|full)')\n .option('--db <path>', 'DB path')\n .action((level: string, opts: { db?: string }) => {\n const parsed = SharingLevelSchema.safeParse(level);\n if (!parsed.success) {\n logError(`Invalid level \"${level}\" — expected one of: none, anonymized, full`);\n process.exitCode = 1;\n return;\n }\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n db.setSharingLevel('*', parsed.data);\n logInfo(`default sharing level set to \"${parsed.data}\"`);\n } finally {\n db.close();\n }\n });\n\n consent\n .command('set <pattern> <level>')\n .description('Set a pattern override (glob over the node id; * = within-segment, ** = any)')\n .option('--db <path>', 'DB path')\n .action((pattern: string, level: string, opts: { db?: string }) => {\n const parsed = SharingLevelSchema.safeParse(level);\n if (!parsed.success) {\n logError(`Invalid level \"${level}\" — expected one of: none, anonymized, full`);\n process.exitCode = 1;\n return;\n }\n if (pattern === '*' || pattern === '**') {\n logError('Use `consent default <level>` to set the global default; `set` is for narrower overrides');\n process.exitCode = 1;\n return;\n }\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n db.setSharingLevel(pattern, parsed.data);\n logInfo(`override \"${pattern}\" → \"${parsed.data}\"`);\n } finally {\n db.close();\n }\n });\n\n consent\n .command('clear <pattern>')\n .description('Remove a pattern override (the global default cannot be cleared)')\n .option('--db <path>', 'DB path')\n .action((pattern: string, opts: { db?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n db.clearSharingOverride(pattern);\n logInfo(`override \"${pattern}\" cleared`);\n } finally {\n db.close();\n }\n });\n\n consent\n .command('list')\n .description('Show the global default + every pattern override')\n .option('--db <path>', 'DB path')\n .action((opts: { db?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n const policy = db.getSharingPolicy();\n process.stdout.write(JSON.stringify(policy, null, 2) + '\\n');\n } finally {\n db.close();\n }\n });\n\n consent\n .command('preview [session]')\n .description('Show exactly what would leave the machine for a session (default: latest)')\n .option('--db <path>', 'DB path')\n .option('--org <name>', 'Organization namespace for the org key')\n .action((session: string | undefined, opts: { db?: string; org?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n const sid = session && session !== 'latest' ? session : db.getLatestSession('discover')?.id;\n if (!sid) {\n logError('No session found to preview');\n process.exitCode = 1;\n return;\n }\n const orgKey = loadOrgKey({ organization: opts.org });\n const policy = db.getSharingPolicy();\n const preview = previewShare(db, sid, orgKey, policy);\n process.stdout.write(JSON.stringify(preview, null, 2) + '\\n');\n } finally {\n db.close();\n }\n });\n\n const consentKey = consent.command('key').description('Org-key administration');\n consentKey\n .command('rotate')\n .description('Rotate the org key (prior reversal entries become unrecoverable)')\n .option('--org <name>', 'Organization namespace for the org key')\n .action((opts: { org?: string }) => {\n rotateOrgKey({ organization: opts.org });\n logInfo('org key rotated');\n });\n\n consent\n .command('reverse <token>')\n .description('Admin: recover the original plaintext behind a pseudonym token')\n .option('--db <path>', 'DB path')\n .option('--org <name>', 'Organization namespace for the org key')\n .action((token: string, opts: { db?: string; org?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n const orgKey = loadOrgKey({ organization: opts.org });\n const plaintext = reversePseudonym(token, orgKey, db);\n if (plaintext === undefined) {\n logError(`Could not reverse \"${token}\" (unknown token or wrong/rotated org key)`);\n process.exitCode = 1;\n return;\n }\n process.stdout.write(plaintext + '\\n');\n } finally {\n db.close();\n }\n });\n\n // ── sync: central-DB pending-review queue + consent-gated push (2.11) ────────\n const sync = program\n .command('sync')\n .description('Central-DB outbound sync: review queued items and push approved deltas (opt-in)');\n\n sync\n .command('status')\n .description('Show the pending-review queue (counts by status + pending items)')\n .option('--db <path>', 'DB path')\n .action((opts: { db?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n if (!config.centralDb?.url) {\n logWarn('centralDb is not configured — sync is inert (set centralDb in ~/.cartography/config.json or CARTOGRAPHY_CENTRAL_URL/TOKEN)');\n }\n const counts = db.countPendingByStatus();\n process.stdout.write(JSON.stringify(counts, null, 2) + '\\n');\n const pending = db.getPendingShares({ status: 'pending' });\n for (const p of pending.slice(0, 50)) {\n process.stdout.write(` ${p.kind === 'node' ? '●' : '→'} ${p.nodeId ?? p.contentHash.slice(0, 12)} ${dim('(' + p.kind + ')')}\\n`);\n }\n if (pending.length > 50) process.stdout.write(` ${dim('… and ' + (pending.length - 50) + ' more')}\\n`);\n } finally {\n db.close();\n }\n });\n\n sync\n .command('review')\n .description('Interactively approve/withhold each pending item (decisions are remembered)')\n .option('--db <path>', 'DB path')\n .action(async (opts: { db?: string }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n const db = new CartographyDB(config.dbPath);\n try {\n const pending = db.getPendingShares({ status: 'pending' });\n if (pending.length === 0) {\n logInfo('no pending items to review');\n return;\n }\n if (!process.stdin.isTTY) {\n logWarn(`${pending.length} pending item(s); run \\`sync review\\` in an interactive terminal to decide them`);\n return;\n }\n const w = process.stderr.write.bind(process.stderr);\n // Pattern a decision should remember: the node id (so the same node never\n // re-prompts). Edges have no id — they are decided per-item without a rule.\n const patternFor = (p: PendingShareRow): string | undefined => p.nodeId;\n for (const p of pending) {\n w('\\n');\n w(` ${yellow(bold('?'))} Share ${p.kind} ${bold(p.nodeId ?? p.contentHash.slice(0, 12))}?\\n`);\n w(` ${dim(JSON.stringify(p.payload))}\\n`);\n const rl = createInterface({ input: process.stdin, output: process.stderr });\n const ans = (await new Promise<string>((res) => rl.question(` ${cyan('→')} [s]hare / [w]ithhold / [a]lways / [n]ever / [q]uit: `, res))).trim().toLowerCase();\n rl.close();\n const pat = patternFor(p);\n if (ans === 'q') break;\n if (ans === 's') {\n db.setPendingStatus(p.contentHash, 'approved', 'user');\n } else if (ans === 'w') {\n db.setPendingStatus(p.contentHash, 'withheld', 'user');\n } else if (ans === 'a') {\n if (pat) db.setSharingLevel(pat, 'full');\n db.setPendingStatus(p.contentHash, 'approved', 'user');\n } else if (ans === 'n') {\n if (pat) db.setSharingLevel(pat, 'none');\n db.setPendingStatus(p.contentHash, 'withheld', 'user');\n } else {\n w(` ${dim('skipped (left pending)')}\\n`);\n }\n }\n const counts = db.countPendingByStatus();\n logInfo(`review done — approved ${counts.approved}, withheld ${counts.withheld}, pending ${counts.pending}`);\n } finally {\n db.close();\n }\n });\n\n sync\n .command('push')\n .description('Push approved deltas to the central ingest endpoint (bearer-auth HTTPS)')\n .option('--db <path>', 'DB path')\n .option('--dry-run', 'Preview the batches without sending', false)\n .action(async (opts: { db?: string; dryRun?: boolean }) => {\n const config = defaultConfig(opts.db ? { dbPath: opts.db } : {});\n if (!config.centralDb?.url) {\n logError('centralDb is not configured — nothing to push (set centralDb.url + token)');\n process.exitCode = 1;\n return;\n }\n const db = new CartographyDB(config.dbPath);\n try {\n const approved = db.getApprovedShares();\n const items: PushItem[] = approved.map((p) => ({ contentHash: p.contentHash, kind: p.kind, payload: p.payload }));\n const result = await pushDeltas(config, items, { dryRun: opts.dryRun });\n if (!opts.dryRun) {\n for (const hash of result.sentHashes) db.setPendingStatus(hash, 'shared');\n }\n logInfo(`sync push: sent ${result.sent}, batches ${result.batches}, failed ${result.failed}${opts.dryRun ? ' (dry-run)' : ''}`);\n } catch (err) {\n logError(`sync push failed: ${err instanceof Error ? err.message : String(err)}`);\n process.exitCode = 1;\n } finally {\n db.close();\n }\n });\n\n program\n .command('mcp')\n .description('Run the Model Context Protocol server (stdio by default) — the primary interface for AI agents')\n .option('--http', 'Use Streamable HTTP transport instead of stdio', false)\n .option('--port <n>', 'HTTP port', '3737')\n .option('--host <h>', 'HTTP host', '127.0.0.1')\n .option('--allowed-hosts <list>', 'Comma-separated Host allowlist (required for non-loopback --host)')\n .option('--token <secret>', 'Bearer token required on HTTP requests (or CARTOGRAPHY_HTTP_TOKEN); mandatory for non-loopback --host')\n .option('--db <path>', 'DB path')\n .option('--session <id>', 'Session to serve (id or \"latest\")', 'latest')\n .option('--tenant <id>', 'Tenant/organization whose topology to serve (alias: --org; default: local)')\n .option('--org <id>', 'Alias for --tenant')\n .option('--no-semantic', 'Disable semantic (vector) search')\n .option('--plugins <list>', 'Comma-separated scanner plugin package names to load (opt-in; or CARTOGRAPHY_PLUGINS)')\n .option('--server-mode', 'Run as a central collector: enable the authenticated POST /ingest write route + org-wide summary (implies --http; opt-in)', false)\n .option('--anon-mode <mode>', 'On ingest, reject|strip un-anonymized identifying fragments (server-mode)', 'reject')\n .option('--auth-required', 'Reject unauthenticated requests even on loopback (RBAC required mode)', false)\n .action(async (opts) => {\n try {\n const anonMode = opts.anonMode;\n if (anonMode !== 'reject' && anonMode !== 'strip') {\n process.stderr.write(`\\nerror: --anon-mode must be 'reject' or 'strip' (got '${anonMode}')\\n`);\n process.exitCode = 1;\n return;\n }\n await startMcp({\n transport: opts.http ? 'http' : 'stdio',\n port: parseInt(opts.port, 10),\n host: opts.host,\n allowedHosts: opts.allowedHosts ? String(opts.allowedHosts).split(',').map((h: string) => h.trim()).filter(Boolean) : undefined,\n token: opts.token,\n dbPath: opts.db,\n session: opts.session,\n tenant: opts.tenant ?? opts.org,\n semantic: opts.semantic,\n plugins: opts.plugins ? String(opts.plugins).split(',').map((p: string) => p.trim()).filter(Boolean) : undefined,\n serverMode: opts.serverMode === true,\n anonMode,\n authRequired: opts.authRequired === true,\n });\n } catch (err) {\n process.stderr.write(`\\nerror: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n }\n });\n\n program\n .command('api')\n .description('Run the read-only REST/GraphQL API server over the topology store (4.2)')\n .option('--http', 'Use HTTP transport (default; kept for symmetry with mcp)', true)\n .option('--port <n>', 'HTTP port', '3737')\n .option('--host <h>', 'HTTP host', '127.0.0.1')\n .option('--allowed-hosts <list>', 'Comma-separated Host allowlist (required for non-loopback --host)')\n .option('--allowed-origins <list>', 'Comma-separated CORS Origin allowlist (default: same-origin only)')\n .option('--token <secret>', 'Bearer token required on requests (or CARTOGRAPHY_HTTP_TOKEN); mandatory for non-loopback --host')\n .option('--db <path>', 'DB path')\n .option('--session <id>', 'Session to serve (id or \"latest\")', 'latest')\n .option('--tenant <id>', 'Default tenant whose topology to serve (alias: --org; default: local)')\n .option('--org <id>', 'Alias for --tenant')\n .option('--no-graphql', 'Disable the /graphql endpoint (REST only)')\n .option('--auth-required', 'Reject unauthenticated requests even on loopback (RBAC required mode)', false)\n .action(async (opts) => {\n try {\n await startApi({\n authRequired: opts.authRequired === true,\n port: parseInt(opts.port, 10),\n host: opts.host,\n allowedHosts: opts.allowedHosts ? String(opts.allowedHosts).split(',').map((h: string) => h.trim()).filter(Boolean) : undefined,\n allowedOrigins: opts.allowedOrigins ? String(opts.allowedOrigins).split(',').map((o: string) => o.trim()).filter(Boolean) : undefined,\n token: opts.token,\n dbPath: opts.db,\n session: opts.session,\n tenant: opts.tenant ?? opts.org,\n graphql: opts.graphql,\n });\n } catch (err) {\n process.stderr.write(`\\nerror: ${err instanceof Error ? err.message : String(err)}\\n`);\n process.exitCode = 1;\n }\n });\n\n const authCmd = program\n .command('auth')\n .description('Manage RBAC credentials for the HTTP surfaces (MCP transport + API server, 4.5)');\n\n authCmd\n .command('add <subject>')\n .description('Create a credential and print its bearer token ONCE (only the hash is stored)')\n .option('--role <role>', 'viewer | operator | admin', 'viewer')\n .option('--tenant <id>', 'Tenant the credential is scoped to (default: local)')\n .option('--token <secret>', 'Use this token instead of generating one')\n .option('--db <path>', 'DB path')\n .action((subject: string, opts: { role: string; tenant?: string; token?: string; db?: string }) => {\n const role = RoleSchema.safeParse(opts.role);\n if (!role.success) {\n process.stderr.write(`\\nerror: --role must be one of viewer|operator|admin (got '${opts.role}')\\n`);\n process.exitCode = 1;\n return;\n }\n const db = new CartographyDB(opts.db ?? defaultConfig().dbPath);\n try {\n const token = opts.token ?? randomBytes(24).toString('base64url');\n const tenant = normalizeTenant(opts.tenant);\n db.addCredential({ tokenHash: hashToken(token), subject, tenant, role: role.data });\n process.stderr.write(`\\n✓ credential added: subject=${subject} role=${role.data} tenant=${tenant}\\n`);\n process.stderr.write(` Bearer token (shown once — store it now):\\n\\n`);\n process.stdout.write(token + '\\n');\n } finally {\n db.close();\n }\n });\n\n authCmd\n .command('list')\n .description('List credentials (subjects/roles/tenants — token hashes only, never the raw token)')\n .option('--db <path>', 'DB path')\n .action((opts: { db?: string }) => {\n const db = new CartographyDB(opts.db ?? defaultConfig().dbPath);\n try {\n const creds = db.listCredentials();\n if (creds.length === 0) { process.stderr.write('No credentials configured (open/shared-token mode).\\n'); return; }\n for (const c of creds) process.stdout.write(`${c.subject}\\t${c.role}\\t${c.tenant}\\t${c.createdAt}\\n`);\n } finally {\n db.close();\n }\n });\n\n authCmd\n .command('revoke <subject>')\n .description('Revoke all credentials for a subject')\n .option('--db <path>', 'DB path')\n .action((subject: string, opts: { db?: string }) => {\n const db = new CartographyDB(opts.db ?? defaultConfig().dbPath);\n try {\n const n = db.revokeCredentialsBySubject(subject);\n process.stderr.write(n > 0 ? `✓ revoked ${n} credential(s) for ${subject}\\n` : `no credentials found for ${subject}\\n`);\n } finally {\n db.close();\n }\n });\n\n // ── Banner (always show) ──────────────────────────────────────────────────\n\n const o = (s: string) => process.stderr.write(s);\n\n o('\\n');\n o(cyan(' ____ _ ____ ') + '\\n');\n o(cyan(' | _ \\\\ __ _| |_ __ _/ ___| _ _ _ __ __ __') + '\\n');\n o(cyan(' | | | |/ _` | __/ _` \\\\___ \\\\| | | | \\'_ \\\\\\\\ \\\\/ /') + '\\n');\n o(cyan(' | |_| | (_| | || (_| |___) | |_| | | | |> < ') + '\\n');\n o(cyan(' |____/ \\\\__,_|\\\\__\\\\__,_|____/ \\\\__, |_| |_/_/\\\\_\\\\') + '\\n');\n o(cyan(' |___/ ') + '\\n');\n o('\\n');\n o(bold(' Cartography') + ' ' + dim('v' + VERSION) + '\\n');\n o(dim(' AI-powered Infrastructure Discovery & Agentic AI Cartography\\n'));\n o(dim(' Autonomous infrastructure discovery — zero-config, provider-agnostic\\n'));\n o('\\n');\n\n // ── Welcome Screen (no args → command overview) ─────────────────────────\n\n if (process.argv.length <= 2) {\n o(dim(' ────────────────────────────────────────────────\\n'));\n o('\\n');\n o(bold(' Commands:\\n'));\n o('\\n');\n o(` ${green('discover')} ${dim('Scan infrastructure (provider: claude|openai|ollama)')}\\n`);\n o(` ${green('seed')} ${dim('Manually add known tools/DBs/APIs')}\\n`);\n o(` ${green('bookmarks')} ${dim('View browser bookmarks')}\\n`);\n o(` ${green('export')} ${dim('[session]')} ${dim('Export Mermaid, JSON, YAML, HTML')}\\n`);\n o(` ${green('show')} ${dim('[session]')} ${dim('Show session details')}\\n`);\n o(` ${green('sessions')} ${dim('List all sessions')}\\n`);\n o(` ${green('doctor')} ${dim('Check requirements (kubectl, aws, gcloud, az)')}\\n`);\n o(` ${green('docs')} ${dim('Full feature reference')}\\n`);\n o('\\n');\n o(dim(' ────────────────────────────────────────────────\\n'));\n o('\\n');\n o(bold(' Quick Start:\\n'));\n o('\\n');\n o(` ${magenta('$')} ${bold('datasynx-cartography doctor')} ${dim('Check requirements')}\\n`);\n o(` ${magenta('$')} ${bold('datasynx-cartography seed')} ${dim('Add known infrastructure')}\\n`);\n o(` ${magenta('$')} ${bold('datasynx-cartography discover')} ${dim('One-time scan')}\\n`);\n o('\\n');\n o(dim(' Docs: datasynx-cartography docs\\n'));\n o(dim(' Help: datasynx-cartography --help\\n'));\n o(dim(' npm: @datasynx/agentic-ai-cartography\\n'));\n o('\\n');\n return;\n }\n\n o(dim(' ────────────────────────────────────────────────\\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';\nimport type { ProviderName } from './types.js';\n\nfunction isOAuthLoggedIn(): boolean {\n // Claude CLI stores 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\n/**\n * Provider-aware preflight. Defaults to `'claude'` so existing zero-arg callers are\n * unaffected. For non-Claude providers, checks only what each backend needs from the\n * environment; deeper reachability (e.g. Ollama host) is deferred to the provider's\n * `ensureAvailable`, which degrades at run with an actionable message.\n */\nexport function checkPrerequisites(provider: ProviderName = 'claude'): void {\n if (provider === 'openai') {\n checkOpenAIPrerequisites();\n return;\n }\n if (provider === 'ollama') {\n // No key/CLI required at preflight; the provider checks host reachability at run.\n process.stderr.write(\n `✓ Ollama provider selected (host: ${process.env.OLLAMA_HOST ?? 'http://127.0.0.1:11434'})\\n`,\n );\n return;\n }\n checkClaudePrerequisites();\n}\n\nfunction checkOpenAIPrerequisites(): void {\n if (!process.env.OPENAI_API_KEY) {\n process.stderr.write(\n '\\n❌ OpenAI provider selected but OPENAI_API_KEY is not set.\\n\\n' +\n ' Set your key:\\n' +\n ' export OPENAI_API_KEY=sk-...\\n\\n' +\n ' Install the SDK if needed:\\n' +\n ' npm install openai\\n\\n' +\n ' Tip: pass a non-Claude model, e.g. --model gpt-4.1\\n\\n'\n );\n process.exitCode = 1;\n throw new Error('OPENAI_API_KEY not set');\n }\n}\n\nfunction checkClaudePrerequisites(): void {\n // Claude CLI present?\n try {\n execSync('claude --version', { stdio: 'pipe' });\n } catch {\n process.stderr.write(\n '\\n❌ Claude CLI not found.\\n' +\n ' Datasynx Cartography requires the Claude CLI as a runtime dependency.\\n\\n' +\n ' Install:\\n' +\n ' npm install -g @anthropic-ai/claude-code\\n' +\n ' # or\\n' +\n ' curl -fsSL https://claude.ai/install.sh | bash\\n\\n' +\n ' Then: claude login\\n\\n'\n );\n process.exitCode = 1;\n throw new Error('Claude CLI not found');\n }\n\n // Check auth: API Key OR 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 '⚠ No authentication found. Please choose one of the following options:\\n\\n' +\n ' Option A — claude.ai Subscription (recommended):\\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('✓ Logged in via claude login (Subscription)\\n');\n }\n}\n","/**\n * RBAC identity types (4.5). A bearer credential resolves to a {@link Principal}\n * `{ subject, tenant, role }`; the HTTP surfaces (MCP transport + REST/GraphQL API)\n * enforce a deny-by-default `can(role, action)` matrix and pin every read to the\n * principal's tenant. Kept dependency-light and free of any import from `db.ts`/\n * `server.ts` so it can be reused by both transports without a cycle.\n */\n\nimport { z } from 'zod';\n\n/** Roles, least → most privileged. `admin ⊇ operator ⊇ viewer` (rank-ordered). */\nexport const ROLES = ['viewer', 'operator', 'admin'] as const;\nexport type Role = typeof ROLES[number];\nexport const RoleSchema = z.enum(ROLES);\n\n/**\n * Gated action classes:\n * - `read` — any read-only query/resource (viewer+).\n * - `discovery` — trigger a scan that mutates the catalog, e.g. `run_discovery` (operator+).\n * - `admin` — manage credentials / admin-only surfaces (admin only).\n */\nexport const ACTIONS = ['read', 'discovery', 'admin'] as const;\nexport type Action = typeof ACTIONS[number];\nexport const ActionSchema = z.enum(ACTIONS);\n\n/** The authenticated caller, bound to exactly one tenant. */\nexport interface Principal {\n /** Stable identity (token label / username / OIDC `sub`). */\n subject: string;\n /** Org-scope this principal may read/act within. */\n tenant: string;\n role: Role;\n}\nexport const PrincipalSchema = z.object({\n subject: z.string().min(1),\n tenant: z.string().min(1),\n role: RoleSchema,\n});\n\n/** A seeded credential (config-supplied). The token is hashed before storage; never persisted raw. */\nexport const CredentialConfigSchema = z.object({\n token: z.string().min(1),\n subject: z.string().min(1),\n tenant: z.string().optional(),\n role: RoleSchema.default('viewer'),\n});\nexport type CredentialConfig = z.infer<typeof CredentialConfigSchema>;\n\n/**\n * Opt-in auth block on {@link CartographyConfig}. Absent → today's behavior exactly\n * (loopback no-token → implicit admin; a configured shared token → one implicit admin).\n * When `credentials` are present (here or in the SQLite store), the server runs in RBAC\n * mode: only a known token resolves to a principal, everything else is 401.\n */\nexport const AuthConfigSchema = z.object({\n /** Seed credentials (merged into the SQLite store on startup). */\n credentials: z.array(CredentialConfigSchema).optional(),\n /** Reject unauthenticated requests even on loopback (default: loopback dev stays open). */\n required: z.boolean().optional(),\n});\nexport type AuthConfig = z.infer<typeof AuthConfigSchema>;\n\n/** A stored credential record (token already hashed). */\nexport interface CredentialRecord {\n tokenHash: string;\n subject: string;\n tenant: string;\n role: Role;\n createdAt: string;\n}\n\n/** Resolves a stored credential by its token hash. Implemented over SQLite (and, later, OIDC). */\nexport interface CredentialStore {\n /** Number of stored credentials — `0` means \"no RBAC configured\" (fall back to shared/loopback). */\n count(): number;\n findByHash(tokenHash: string): CredentialRecord | undefined;\n}\n","// Provider-neutral agent layer — the seam that decouples the discovery loop from\n// any single LLM SDK. Each provider yields the existing DiscoveryEvent stream, so\n// runDiscovery() and every caller of it stay backend-agnostic.\n\nimport type { CartographyDB } from '../db.js';\nimport type { CartographyConfig, ProviderName } from '../types.js';\nimport type { DiscoveryEvent, AskUserFn } from '../agent.js';\n\nexport type { ProviderName } from '../types.js';\n\n/** Inputs a provider needs to run one discovery session. */\nexport interface AgentRunContext {\n config: CartographyConfig;\n db: CartographyDB;\n sessionId: string;\n systemPrompt: string;\n initialPrompt: string;\n onAskUser?: AskUserFn;\n /** Wall-clock deadline (epoch ms). Providers MUST stop and emit `done` past this. */\n deadlineMs: number;\n}\n\n/**\n * A provider-neutral agent backend. Yields the existing `DiscoveryEvent` stream.\n * Implementations MUST: enforce `config.maxTurns`, honor `ctx.deadlineMs`, route\n * every shell command through `checkReadOnly` + `run()`, and write an audit row per\n * executed tool so the audit trail is identical across providers.\n */\nexport interface AgentProvider {\n readonly name: ProviderName;\n /** Throw a clear Error if the optional dep / API key / CLI is missing. */\n ensureAvailable(config: CartographyConfig): Promise<void>;\n run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent>;\n}\n\nexport type ProviderFactory = () => AgentProvider;\n\n/** Registry of provider factories; the single source of truth for valid provider names. */\nexport class ProviderRegistry {\n private readonly factories = new Map<ProviderName, ProviderFactory>();\n\n register(name: ProviderName, factory: ProviderFactory): void {\n this.factories.set(name, factory);\n }\n\n has(name: string): name is ProviderName {\n return this.factories.has(name as ProviderName);\n }\n\n resolve(name: ProviderName): AgentProvider {\n const f = this.factories.get(name);\n if (!f) throw new Error(`Unknown provider \"${name}\". Available: ${this.names().join(', ')}`);\n return f();\n }\n\n names(): ProviderName[] {\n return [...this.factories.keys()];\n }\n}\n","// PreToolUse Safety Hook — enforces the read-only allowlist on all Bash calls.\n//\n// This is the Claude-Code-specific adapter. The authoritative policy lives in\n// ./allowlist.ts and is shared with the MCP server, so safety is identical no\n// matter which agent or model drives discovery.\n\nimport type { HookCallback } from '@anthropic-ai/claude-agent-sdk';\nimport { checkReadOnly } from './allowlist.js';\n\nexport type { HookCallback };\n\nexport const safetyHook: HookCallback = async (input, _toolUseID, _options) => {\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 ?? '').trim();\n\n // An empty command runs nothing — allow it (matches Claude Code's no-op behavior).\n if (!cmd) {\n return { hookSpecificOutput: { hookEventName: 'PreToolUse', permissionDecision: 'allow' } };\n }\n\n const decision = checkReadOnly(cmd);\n if (!decision.allowed) {\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `BLOCKED: ${decision.reason} — read-only allowlist policy`,\n },\n };\n }\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n },\n };\n};\n","// PostToolUse Audit Hook — records every executed tool call into activity_events.\n//\n// Governance/audit trail: for each tool the agent runs during discovery we persist\n// { tool, command, result bytes, timestamp } so an operator can review what ran.\n// Audit failures must never break discovery, so the write is best-effort.\n\nimport type { HookCallback } from '@anthropic-ai/claude-agent-sdk';\nimport type { CartographyDB } from './db.js';\nimport { logDebug } from './logger.js';\n\n/** Build a PostToolUse hook bound to a session that logs executed tools to the catalog. */\nexport function createAuditHook(db: CartographyDB, sessionId: string): HookCallback {\n return async (input) => {\n try {\n if (!('tool_name' in input)) return {};\n const i = input as { tool_name: string; tool_input?: { command?: string }; tool_response?: unknown };\n const command = i.tool_input?.command ?? JSON.stringify(i.tool_input ?? {}).slice(0, 2000);\n const response = typeof i.tool_response === 'string' ? i.tool_response : JSON.stringify(i.tool_response ?? '');\n db.insertEvent(sessionId, {\n eventType: 'tool_executed',\n process: i.tool_name,\n pid: process.pid,\n command,\n resultBytes: Buffer.byteLength(response),\n });\n } catch (err) {\n logDebug(`audit hook failed to record event: ${String(err)}`);\n }\n return {};\n };\n}\n","// Claude provider — the Anthropic Claude Agent SDK backend. This is the verbatim\n// move of the original runDiscovery() loop (src/agent.ts) behind the AgentProvider\n// seam, so the Claude path is behavior-preserved. The SDK is dynamically imported\n// so its absence degrades gracefully (ProviderUnavailableError).\n\nimport { createCartographyTools } from '../tools.js';\nimport { safetyHook } from '../safety.js';\nimport { createAuditHook } from '../audit.js';\nimport type { CartographyConfig } from '../types.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentProvider, AgentRunContext } from './types.js';\n\nfunction createClaudeProvider(): AgentProvider {\n return {\n name: 'claude',\n\n async ensureAvailable(_config: CartographyConfig): Promise<void> {\n try {\n await import('@anthropic-ai/claude-agent-sdk');\n } catch {\n throw new Error(\n 'Claude provider unavailable: the @anthropic-ai/claude-agent-sdk package is not installed.\\n' +\n ' Install: npm install @anthropic-ai/claude-agent-sdk',\n );\n }\n },\n\n async *run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent> {\n const { config, db, sessionId, systemPrompt, initialPrompt, onAskUser, deadlineMs } = ctx;\n const { query } = await import('@anthropic-ai/claude-agent-sdk');\n const tools = await createCartographyTools(db, sessionId, {\n onAskUser,\n maxResponseBytes: config.maxToolResponseBytes,\n });\n\n let turnCount = 0;\n\n for await (const msg of query({\n prompt: initialPrompt,\n options: {\n model: config.models.lead,\n maxTurns: config.maxTurns,\n systemPrompt,\n mcpServers: { cartography: tools },\n allowedTools: [\n 'Bash',\n 'mcp__cartography__save_node',\n 'mcp__cartography__save_edge',\n 'mcp__cartography__get_catalog',\n 'mcp__cartography__scan_bookmarks',\n 'mcp__cartography__scan_browser_history',\n 'mcp__cartography__scan_installed_apps',\n 'mcp__cartography__scan_local_databases',\n 'mcp__cartography__scan_k8s_resources',\n 'mcp__cartography__scan_aws_resources',\n 'mcp__cartography__scan_gcp_resources',\n 'mcp__cartography__scan_azure_resources',\n 'mcp__cartography__ask_user',\n ],\n hooks: {\n PreToolUse: [{ matcher: 'Bash', hooks: [safetyHook] }],\n PostToolUse: [{ hooks: [createAuditHook(db, sessionId)] }],\n },\n permissionMode: 'bypassPermissions',\n },\n })) {\n // Wall-clock timeout guard\n if (Date.now() > deadlineMs) {\n yield { kind: 'error', text: 'Discovery timeout — wall-clock limit reached' };\n yield { kind: 'done' };\n return;\n }\n\n if (msg.type === 'assistant') {\n turnCount++;\n yield { kind: 'turn', turn: turnCount };\n\n for (const block of msg.message.content) {\n if (block.type === 'text') {\n yield { kind: 'thinking', text: block.text };\n }\n if (block.type === 'tool_use') {\n yield {\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 (\n typeof block === 'object' &&\n block !== null &&\n 'type' in block &&\n (block as { type: string }).type === 'tool_result'\n ) {\n const tb = block as { tool_use_id?: string; content?: unknown };\n const text = typeof tb.content === 'string' ? tb.content : '';\n yield { kind: 'tool_result', tool: tb.tool_use_id ?? '', output: text };\n }\n }\n }\n }\n\n if (msg.type === 'result') {\n yield { kind: 'done' };\n return;\n }\n }\n },\n };\n}\n\nexport { createClaudeProvider };\n","// The neutral Bash tool for non-SDK providers (OpenAI, Ollama).\n//\n// The Claude Agent SDK ships a built-in `Bash` tool gated by `safetyHook`. OpenAI\n// and Ollama have no such built-in, so this exposes an in-process `Bash` tool whose\n// handler enforces the read-only allowlist via `checkReadOnly` BEFORE delegating to\n// `run()` (which re-checks `checkReadOnly` again — defense in depth). This is the\n// load-bearing safety parity: every provider's shell calls pass through the same\n// authoritative policy in src/allowlist.ts.\n\nimport { z } from 'zod';\nimport { checkReadOnly } from '../allowlist.js';\nimport { run, IS_WIN } from '../platform.js';\nimport type { AgentTool } from '../tools.js';\n\nexport function createBashTool(): AgentTool {\n const shell = IS_WIN ? 'powershell' : 'posix';\n return {\n name: 'Bash',\n description:\n 'Run a read-only shell command (inspect ports, processes, config). ' +\n 'Mutating or destructive commands are blocked by the read-only allowlist.',\n inputShape: { command: z.string().describe('The read-only shell command to run') },\n annotations: { readOnlyHint: true, openWorldHint: true },\n handler: async (args) => {\n const command = String(args['command'] ?? '').trim();\n if (!command) return { content: [{ type: 'text', text: '' }] };\n const decision = checkReadOnly(command, { shell });\n if (!decision.allowed) {\n return {\n content: [\n { type: 'text', text: `BLOCKED: ${decision.reason ?? 'not read-only'} — read-only allowlist policy` },\n ],\n };\n }\n const output = run(command) || '(no output)';\n return { content: [{ type: 'text', text: output }] };\n },\n };\n}\n","// Minimal zod → JSON Schema converter for the discovery tools' input shapes.\n//\n// OpenAI and Ollama function-calling both expect JSON Schema for tool parameters.\n// Rather than add the `zod-to-json-schema` runtime dependency, this implements a\n// small, sufficient converter covering exactly the zod constructs the discovery\n// tools use (string, number with min/max, boolean, enum, array, record, optional,\n// default, describe). An unsupported construct throws a clear Error naming the\n// field, so a future tool can't be silently mis-converted.\n\nimport { z } from 'zod';\n\nexport interface JsonSchema {\n type: 'object';\n properties: Record<string, unknown>;\n required: string[];\n additionalProperties: boolean;\n}\n\n/** Unwrap optional/default/describe wrappers, tracking whether the field is required. */\ninterface Unwrapped {\n schema: z.ZodTypeAny;\n required: boolean;\n description?: string;\n}\n\nfunction unwrap(schema: z.ZodTypeAny): Unwrapped {\n let current = schema;\n let required = true;\n let description: string | undefined = current.description;\n\n // Peel wrapper types until we reach the concrete inner type.\n // zod v4 exposes the inner type via `.def.innerType`.\n for (;;) {\n const def = (current as unknown as { def?: { type?: string; innerType?: z.ZodTypeAny } }).def;\n const typeName = def?.type;\n if (typeName === 'optional' || typeName === 'default') {\n required = false;\n const inner = def?.innerType;\n if (!inner) break;\n current = inner;\n description = description ?? current.description;\n continue;\n }\n if (typeName === 'nullable') {\n const inner = def?.innerType;\n if (!inner) break;\n current = inner;\n description = description ?? current.description;\n continue;\n }\n break;\n }\n return { schema: current, required, description };\n}\n\nfunction convert(schema: z.ZodTypeAny, field: string): Record<string, unknown> {\n const def = (schema as unknown as { def?: Record<string, unknown> }).def;\n const typeName = def?.['type'] as string | undefined;\n\n switch (typeName) {\n case 'string':\n return { type: 'string' };\n case 'number': {\n const out: Record<string, unknown> = { type: 'number' };\n // zod v4 stores checks under def.checks\n const checks = (def?.['checks'] as { _zod?: { def?: { check?: string; value?: number } } }[]) ?? [];\n for (const c of checks) {\n const cd = c?._zod?.def;\n if (cd?.check === 'greater_than') out['minimum'] = cd.value;\n if (cd?.check === 'less_than') out['maximum'] = cd.value;\n }\n return out;\n }\n case 'boolean':\n return { type: 'boolean' };\n case 'enum': {\n const entries = def?.['entries'] as Record<string, string> | undefined;\n const values = entries ? Object.values(entries) : [];\n return { type: 'string', enum: values };\n }\n case 'array': {\n const element = def?.['element'] as z.ZodTypeAny | undefined;\n return { type: 'array', items: element ? convert(unwrap(element).schema, field) : {} };\n }\n case 'record':\n return { type: 'object', additionalProperties: true };\n default:\n throw new Error(\n `zod-schema: unsupported zod construct \"${typeName ?? 'unknown'}\" on field \"${field}\". ` +\n 'Extend src/providers/zod-schema.ts to support it.',\n );\n }\n}\n\n/** Convert a flat ZodRawShape used by the discovery tools to a JSON Schema object. */\nexport function shapeToJsonSchema(shape: z.ZodRawShape): JsonSchema {\n const properties: Record<string, unknown> = {};\n const required: string[] = [];\n\n for (const [key, raw] of Object.entries(shape)) {\n const { schema, required: isRequired, description } = unwrap(raw as z.ZodTypeAny);\n const prop = convert(schema, key);\n if (description) prop['description'] = description;\n properties[key] = prop;\n if (isRequired) required.push(key);\n }\n\n return { type: 'object', properties, required, additionalProperties: false };\n}\n","// Provider-neutral audit writer — extracted from the Claude PostToolUse hook\n// (src/audit.ts) so non-SDK providers (OpenAI, Ollama) share the exact same audit\n// trail. Every executed tool writes one `tool_executed` row into `activity_events`.\n// Audit failures must never break discovery, so the write is best-effort.\n\nimport type { CartographyDB } from '../db.js';\nimport { logDebug } from '../logger.js';\n\n/** Record one executed tool call into the catalog's audit trail (best-effort). */\nexport function recordToolEvent(\n db: CartographyDB,\n sessionId: string,\n evt: { tool: string; command: string; response: string },\n): void {\n try {\n db.insertEvent(sessionId, {\n eventType: 'tool_executed',\n process: evt.tool,\n pid: process.pid,\n command: evt.command,\n resultBytes: Buffer.byteLength(evt.response),\n });\n } catch (err) {\n logDebug(`audit writer failed to record event: ${String(err)}`);\n }\n}\n","// Shared tool-calling loop for non-SDK providers (OpenAI, Ollama).\n//\n// Both providers delegate here, parameterized by a `chat()` callback that performs\n// one model round-trip. This loop owns: turn counting + maxTurns enforcement,\n// deadlineMs (wall-clock) checks, dispatch of tool calls to AgentTool handlers\n// (matched by name), the neutral Bash tool, the audit write (db.insertEvent via\n// recordToolEvent), and emission of turn/thinking/tool_call/tool_result/error/done.\n// This guarantees OpenAI and Ollama emit identical DiscoveryEvent kinds.\n\nimport type { CartographyDB } from '../db.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentTool } from '../tools.js';\nimport { recordToolEvent } from './audit.js';\n\n/** A model's request to call one tool. */\nexport interface ToolCall {\n /** Opaque id correlating the call to its result (provider-specific). */\n id: string;\n name: string;\n args: Record<string, unknown>;\n}\n\n/** The neutral result of one model round-trip. */\nexport interface ChatTurn {\n /** Assistant free text for this turn (may be empty). */\n text: string;\n /** Tool calls the model requested this turn (empty ⇒ the model is done). */\n toolCalls: ToolCall[];\n}\n\n/** One executed tool call's result, fed back to the model on the next round-trip. */\nexport interface ToolOutcome {\n id: string;\n name: string;\n output: string;\n}\n\n/**\n * Performs one model round-trip given the prior tool outcomes. On the first call\n * `outcomes` is empty. Returns the assistant turn (text + any tool calls). The\n * callback owns building/threading the provider-specific message history.\n */\nexport type ChatFn = (outcomes: ToolOutcome[]) => Promise<ChatTurn>;\n\nexport interface LoopOptions {\n db: CartographyDB;\n sessionId: string;\n tools: AgentTool[];\n maxTurns: number;\n deadlineMs: number;\n}\n\n/** Dispatch a single tool call to its handler, recording the audit row. */\nasync function dispatchTool(\n call: ToolCall,\n tools: AgentTool[],\n db: CartographyDB,\n sessionId: string,\n): Promise<string> {\n const tool = tools.find((t) => t.name === call.name);\n if (!tool) {\n const text = `ERROR: unknown tool \"${call.name}\"`;\n recordToolEvent(db, sessionId, { tool: call.name, command: JSON.stringify(call.args).slice(0, 2000), response: text });\n return text;\n }\n let output: string;\n try {\n const result = await tool.handler(call.args);\n output = result.content.map((c) => c.text).join('\\n');\n } catch (err) {\n output = `ERROR: ${err instanceof Error ? err.message : String(err)}`;\n }\n // Audit: the Bash command for Bash, else the (clamped) JSON args — mirrors src/audit.ts.\n const command =\n call.name === 'Bash'\n ? String(call.args['command'] ?? '')\n : JSON.stringify(call.args).slice(0, 2000);\n recordToolEvent(db, sessionId, { tool: call.name, command, response: output });\n return output;\n}\n\n/**\n * Drive a provider's tool-calling loop, yielding the neutral DiscoveryEvent stream.\n * Honors maxTurns and the wall-clock deadline; always terminates with `done` (or an\n * `error` + `done` pair on failure).\n */\nexport async function* runToolLoop(opts: LoopOptions, chat: ChatFn): AsyncIterable<DiscoveryEvent> {\n const { db, sessionId, tools, maxTurns, deadlineMs } = opts;\n let outcomes: ToolOutcome[] = [];\n let turn = 0;\n\n try {\n while (turn < maxTurns) {\n if (Date.now() > deadlineMs) {\n yield { kind: 'error', text: 'Discovery timeout — wall-clock limit reached' };\n yield { kind: 'done' };\n return;\n }\n\n const result = await chat(outcomes);\n turn++;\n yield { kind: 'turn', turn };\n\n if (result.text) yield { kind: 'thinking', text: result.text };\n\n if (result.toolCalls.length === 0) {\n // No tool calls ⇒ the model has finished.\n yield { kind: 'done' };\n return;\n }\n\n const nextOutcomes: ToolOutcome[] = [];\n for (const call of result.toolCalls) {\n yield { kind: 'tool_call', tool: call.name, input: call.args };\n const output = await dispatchTool(call, tools, db, sessionId);\n yield { kind: 'tool_result', tool: call.name, output };\n nextOutcomes.push({ id: call.id, name: call.name, output });\n }\n outcomes = nextOutcomes;\n }\n\n // maxTurns exhausted without a natural stop — terminate cleanly.\n yield { kind: 'done' };\n } catch (err) {\n yield { kind: 'error', text: `Discovery error: ${err instanceof Error ? err.message : String(err)}` };\n yield { kind: 'done' };\n }\n}\n","// OpenAI provider — drives discovery via the chat-completions function-calling\n// loop over the SAME neutral tool handlers the Claude path uses. The `openai`\n// package is an optional dependency, lazily imported; its absence (or a missing\n// OPENAI_API_KEY) degrades with a clear Error, never a crash.\n\nimport type { CartographyConfig } from '../types.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentProvider, AgentRunContext } from './types.js';\nimport { buildCartographyToolHandlers, type AgentTool } from '../tools.js';\nimport { createBashTool } from './shell.js';\nimport { shapeToJsonSchema } from './zod-schema.js';\nimport { runToolLoop, type ChatFn, type ToolOutcome } from './loop.js';\n\n/** Minimal structural types for the slice of the OpenAI SDK we use (no `any`). */\ninterface OpenAIToolCall {\n id: string;\n type: 'function';\n function: { name: string; arguments: string };\n}\ninterface OpenAIMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content: string | null;\n tool_calls?: OpenAIToolCall[];\n tool_call_id?: string;\n}\ninterface OpenAIChoice {\n message: { role: 'assistant'; content: string | null; tool_calls?: OpenAIToolCall[] };\n}\ninterface OpenAICompletion {\n choices: OpenAIChoice[];\n}\ninterface OpenAIClient {\n chat: {\n completions: {\n create(req: {\n model: string;\n messages: OpenAIMessage[];\n tools: { type: 'function'; function: { name: string; description: string; parameters: unknown } }[];\n tool_choice: 'auto';\n }): Promise<OpenAICompletion>;\n };\n };\n}\ntype OpenAIModule = { default: new (opts: { apiKey: string; baseURL?: string }) => OpenAIClient };\n\nfunction toOpenAITools(tools: AgentTool[]) {\n return tools.map((t) => ({\n type: 'function' as const,\n function: { name: t.name, description: t.description, parameters: shapeToJsonSchema(t.inputShape) },\n }));\n}\n\nfunction createOpenAIProvider(): AgentProvider {\n return {\n name: 'openai',\n\n async ensureAvailable(_config: CartographyConfig): Promise<void> {\n try {\n await import('openai' as string);\n } catch {\n throw new Error(\n 'OpenAI provider unavailable: the `openai` package is not installed.\\n' +\n ' Install: npm install openai',\n );\n }\n if (!process.env['OPENAI_API_KEY']) {\n throw new Error(\n 'OpenAI provider unavailable: OPENAI_API_KEY is not set.\\n' +\n ' Set it: export OPENAI_API_KEY=sk-...',\n );\n }\n },\n\n async *run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent> {\n const { config, db, sessionId, systemPrompt, initialPrompt, onAskUser, deadlineMs } = ctx;\n const mod = (await import('openai' as string)) as unknown as OpenAIModule;\n const apiKey = process.env['OPENAI_API_KEY'] ?? '';\n const baseURL = process.env['OPENAI_BASE_URL'];\n const client = new mod.default({ apiKey, ...(baseURL ? { baseURL } : {}) });\n\n const handlers = await buildCartographyToolHandlers(db, sessionId, {\n onAskUser,\n maxResponseBytes: config.maxToolResponseBytes,\n });\n const tools: AgentTool[] = [...handlers, createBashTool()];\n const openaiTools = toOpenAITools(tools);\n\n const messages: OpenAIMessage[] = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: initialPrompt },\n ];\n\n const chat: ChatFn = async (outcomes: ToolOutcome[]) => {\n // Append the previous turn's tool results before the next round-trip.\n for (const oc of outcomes) {\n messages.push({ role: 'tool', tool_call_id: oc.id, content: oc.output });\n }\n\n const completion = await client.chat.completions.create({\n model: config.models.lead,\n messages,\n tools: openaiTools,\n tool_choice: 'auto',\n });\n const choice = completion.choices[0]?.message;\n const text = choice?.content ?? '';\n const toolCalls = choice?.tool_calls ?? [];\n\n // Record the assistant turn so the next round-trip has full context.\n messages.push({ role: 'assistant', content: text || null, ...(toolCalls.length ? { tool_calls: toolCalls } : {}) });\n\n return {\n text,\n toolCalls: toolCalls.map((tc) => ({\n id: tc.id,\n name: tc.function.name,\n args: parseArgs(tc.function.arguments),\n })),\n };\n };\n\n yield* runToolLoop({ db, sessionId, tools, maxTurns: config.maxTurns, deadlineMs }, chat);\n },\n };\n}\n\nfunction parseArgs(raw: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(raw || '{}') as unknown;\n return typeof parsed === 'object' && parsed !== null ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\nexport { createOpenAIProvider };\n","// Ollama provider — drives discovery via Ollama's native /api/chat endpoint using\n// built-in `fetch` (no SDK dependency). Reuses the SAME neutral tool handlers and\n// the shared tool-calling loop as OpenAI, so it emits identical DiscoveryEvent\n// kinds and shares the read-only/audit safety path. Resolves the host from\n// OLLAMA_HOST (default http://127.0.0.1:11434); an unreachable host degrades with a\n// clear `ollama serve` hint rather than crashing.\n\nimport type { CartographyConfig } from '../types.js';\nimport type { DiscoveryEvent } from '../agent.js';\nimport type { AgentProvider, AgentRunContext } from './types.js';\nimport { buildCartographyToolHandlers, type AgentTool } from '../tools.js';\nimport { createBashTool } from './shell.js';\nimport { shapeToJsonSchema } from './zod-schema.js';\nimport { runToolLoop, type ChatFn, type ToolOutcome } from './loop.js';\n\nconst DEFAULT_HOST = 'http://127.0.0.1:11434';\n\nfunction host(): string {\n return (process.env['OLLAMA_HOST'] || DEFAULT_HOST).replace(/\\/+$/, '');\n}\n\n/** Minimal structural types for the slice of the Ollama /api/chat API we use. */\ninterface OllamaToolCall {\n function: { name: string; arguments: Record<string, unknown> };\n}\ninterface OllamaMessage {\n role: 'system' | 'user' | 'assistant' | 'tool';\n content: string;\n tool_calls?: OllamaToolCall[];\n}\ninterface OllamaChatResponse {\n message?: { role: 'assistant'; content?: string; tool_calls?: OllamaToolCall[] };\n}\n\nfunction toOllamaTools(tools: AgentTool[]) {\n return tools.map((t) => ({\n type: 'function' as const,\n function: { name: t.name, description: t.description, parameters: shapeToJsonSchema(t.inputShape) },\n }));\n}\n\nfunction createOllamaProvider(): AgentProvider {\n return {\n name: 'ollama',\n\n async ensureAvailable(_config: CartographyConfig): Promise<void> {\n const base = host();\n try {\n const res = await fetch(`${base}/api/tags`, { method: 'GET' });\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n } catch {\n throw new Error(\n `Ollama provider unavailable: not reachable at ${base}.\\n` +\n ' Start it: ollama serve (or set OLLAMA_HOST=<url>)',\n );\n }\n },\n\n async *run(ctx: AgentRunContext): AsyncIterable<DiscoveryEvent> {\n const { config, db, sessionId, systemPrompt, initialPrompt, onAskUser, deadlineMs } = ctx;\n const base = host();\n\n const handlers = await buildCartographyToolHandlers(db, sessionId, {\n onAskUser,\n maxResponseBytes: config.maxToolResponseBytes,\n });\n const tools: AgentTool[] = [...handlers, createBashTool()];\n const ollamaTools = toOllamaTools(tools);\n\n const messages: OllamaMessage[] = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: initialPrompt },\n ];\n\n const chat: ChatFn = async (outcomes: ToolOutcome[]) => {\n // Ollama tool results are matched positionally (no per-call id).\n for (const oc of outcomes) {\n messages.push({ role: 'tool', content: oc.output });\n }\n\n const res = await fetch(`${base}/api/chat`, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ model: config.models.lead, messages, tools: ollamaTools, stream: false }),\n });\n if (!res.ok) {\n throw new Error(`Ollama /api/chat returned HTTP ${res.status}`);\n }\n const data = (await res.json()) as OllamaChatResponse;\n const text = data.message?.content ?? '';\n const toolCalls = data.message?.tool_calls ?? [];\n\n messages.push({\n role: 'assistant',\n content: text,\n ...(toolCalls.length ? { tool_calls: toolCalls } : {}),\n });\n\n return {\n text,\n toolCalls: toolCalls.map((tc, i) => ({\n id: `${tc.function.name}:${i}`,\n name: tc.function.name,\n args: tc.function.arguments ?? {},\n })),\n };\n };\n\n yield* runToolLoop({ db, sessionId, tools, maxTurns: config.maxTurns, deadlineMs }, chat);\n },\n };\n}\n\nexport { createOllamaProvider };\n","// The default provider registry: the single source of truth for valid provider\n// names. Factories lazily build providers; each provider lazily imports its own\n// optional dependency, so importing this module never pulls an optional SDK.\n\nimport { ProviderRegistry } from './types.js';\nimport { createClaudeProvider } from './claude.js';\nimport { createOpenAIProvider } from './openai.js';\nimport { createOllamaProvider } from './ollama.js';\n\nexport function createDefaultRegistry(): ProviderRegistry {\n const r = new ProviderRegistry();\n r.register('claude', createClaudeProvider);\n r.register('openai', createOpenAIProvider);\n r.register('ollama', createOllamaProvider);\n return r;\n}\n\nexport const defaultProviderRegistry = createDefaultRegistry();\n","import type { CartographyDB } from './db.js';\nimport type { CartographyConfig } from './types.js';\nimport { IS_WIN, IS_MAC, PLATFORM } from './platform.js';\nimport { defaultProviderRegistry } from './providers/registry.js';\nimport type { AgentRunContext } from './providers/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: 'error'; text: string }\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 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 // Platform-specific instructions for the agent\n const platformName = IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux';\n const networkScanCmd = IS_WIN\n ? 'Get-NetTCPConnection -State Listen (PowerShell) → identify all listening ports/processes'\n : IS_MAC\n ? 'lsof -iTCP -sTCP:LISTEN -n -P && ps aux → identify all listening ports/processes'\n : 'ss -tlnp && ps aux → identify all listening ports/processes';\n const readOnlyTools = IS_WIN\n ? 'Get-NetTCPConnection, Get-Process, Get-Service, Get-ChildItem, curl, docker inspect, kubectl get'\n : IS_MAC\n ? 'lsof, ps, cat, head, curl -s, docker inspect, kubectl get'\n : 'ss, ps, cat, head, curl -s, docker inspect, kubectl get';\n const processCmd = IS_WIN ? 'Get-Process' : 'ps aux';\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.\nPLATFORM: ${platformName} (${PLATFORM})\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: ${networkScanCmd}\n Also run: ${processCmd} → identify running services\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\nPLATFORM-SPECIFIC NOTES (${platformName}):\n${IS_WIN ? `• Use PowerShell commands: Get-NetTCPConnection, Get-Process, Get-Service, Get-ChildItem\n• Do NOT use Unix commands (ss, ps aux, find, which, head, grep) — they won't work on Windows\n• Use $env:LOCALAPPDATA, $env:APPDATA for app data paths\n• Registry scan for installed programs is handled by scan_installed_apps` : IS_MAC ? `• Use lsof -iTCP -sTCP:LISTEN -n -P for port scanning (ss is NOT available on macOS)\n• Use ps aux for process listing\n• Applications are in /Applications and ~/Applications\n• Homebrew (brew) for package management` : `• Use ss -tlnp for port scanning\n• Use ps aux for process listing\n• Check dpkg, snap, flatpak for installed packages\n• Check Snap/Flatpak browser variants for bookmarks`}\n\nRULES:\n• Read-only only (${readOnlyTools})\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• Max crawl depth: ${config.maxDepth} hops from an entry point — do not chase leads deeper than this\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 const MAX_DISCOVERY_MS = 30 * 60 * 1000; // 30-minute wall-clock timeout\n const startTime = Date.now();\n const deadlineMs = startTime + MAX_DISCOVERY_MS;\n\n // Resolve the agent backend from the registry. The Claude path is byte-for-byte\n // the original loop, moved verbatim into src/providers/claude.ts; OpenAI/Ollama\n // run the shared tool-calling loop over the same neutral tool handlers.\n const provider = defaultProviderRegistry.resolve(config.provider ?? 'claude');\n await provider.ensureAvailable(config);\n\n const ctx: AgentRunContext = {\n config,\n db,\n sessionId,\n systemPrompt,\n initialPrompt,\n onAskUser,\n deadlineMs,\n };\n\n try {\n for await (const event of provider.run(ctx)) {\n onEvent?.(event);\n if (event.kind === 'done') return;\n // Fallback wall-clock guard in addition to the provider's own check.\n if (Date.now() > deadlineMs) {\n onEvent?.({ kind: 'error', text: `Discovery timeout after ${MAX_DISCOVERY_MS / 60000} minutes` });\n onEvent?.({ kind: 'done' });\n return;\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n onEvent?.({ kind: 'error', text: `Discovery error: ${message}` });\n throw err;\n }\n}\n\n","/**\n * Config-file layer (2.5).\n *\n * Reads and validates a JSON `cartography.config.json` file and resolves it into\n * a fully-populated {@link CartographyConfig}. The reader is deliberately general:\n * WS 2.11 (central-org sync) consumes this same module and extends the file schema\n * (`ConfigFileSchema`) with its own block, so all file → config merging logic lives\n * here in one place.\n *\n * Security: untrusted file input is parsed with `JSON.parse` (no `eval`, no dynamic\n * `require`) and validated with a `.strict()` Zod schema that rejects unknown keys.\n * File contents are never executed; values flow only into the existing sanitized\n * config/discovery sinks.\n */\n\nimport { readFileSync } from 'node:fs';\nimport type { CartographyConfig, CentralDbConfig } from './types.js';\nimport { ConfigFileSchema, centralDbFromEnv, defaultConfig } from './types.js';\n\n/** Raised when a config file cannot be read, parsed, or validated. */\nexport class ConfigError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ConfigError';\n }\n}\n\n/**\n * Read and validate a JSON config file at `path`, returning a fully-resolved\n * {@link CartographyConfig}. Merges the file's `schedule`/`entryPoints`/`dbPath`/\n * `organization` into `defaultConfig` so every existing config invariant (e.g.\n * `agentModel === models.lead`) is preserved.\n *\n * Precedence for the shared `entryPoints`/`dbPath`: a value inside the `schedule`\n * block wins over the same file-level key (the schedule block is the more specific\n * intent for a scheduled run).\n *\n * @throws {ConfigError} when the file is missing/unreadable, the JSON is malformed,\n * or the content fails schema validation (including unknown keys via `.strict()`).\n */\nexport function loadConfig(path: string): CartographyConfig {\n const file = readConfigFile(path);\n\n const overrides: Partial<CartographyConfig> = {};\n if (file.organization) overrides.organization = file.organization;\n\n const entryPoints = file.schedule?.entryPoints ?? file.entryPoints;\n if (entryPoints) overrides.entryPoints = [...entryPoints];\n\n const dbPath = file.schedule?.dbPath ?? file.dbPath;\n if (dbPath) overrides.dbPath = dbPath;\n\n if (file.schedule) overrides.schedule = file.schedule;\n\n // 2.11 central-DB sync. Precedence (low→high): file `centralDb` < env\n // (`CARTOGRAPHY_CENTRAL_*`) < anything `defaultConfig` later layers. We merge the\n // file block under env here (env wins per field) and pass the result as the\n // explicit `centralDb` override; `defaultConfig` then validates it once. A file\n // `url` + env `token` compose into a valid block; an invalid one is dropped there.\n if (file.centralDb) {\n // `defaultConfig` re-validates the assembled block; pass the (possibly partial)\n // merge as a centralDb override. The cast is sound because resolveCentralDb\n // safeParses before it lands on the resolved config.\n const merged: Partial<CentralDbConfig> = { ...file.centralDb, ...centralDbFromEnv() };\n overrides.centralDb = merged as CentralDbConfig;\n }\n\n return defaultConfig(overrides);\n}\n\n/**\n * Lower-level reader: parse + validate a config file into its typed shape without\n * resolving it against `defaultConfig`. Useful for callers (e.g. WS 2.11) that need\n * the raw file shape rather than a merged runtime config.\n *\n * @throws {ConfigError} on missing file, malformed JSON, or schema-validation failure.\n */\nexport function readConfigFile(path: string): import('./types.js').ConfigFile {\n let raw: string;\n try {\n raw = readFileSync(path, 'utf-8');\n } catch (err) {\n throw new ConfigError(\n `Cannot read config file ${path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n let json: unknown;\n try {\n json = JSON.parse(raw);\n } catch (err) {\n throw new ConfigError(\n `Invalid JSON in ${path}: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const parsed = ConfigFileSchema.safeParse(json);\n if (!parsed.success) {\n const detail = parsed.error.issues\n .map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`)\n .join('; ');\n throw new ConfigError(`Invalid config in ${path}: ${detail}`);\n }\n return parsed.data;\n}\n","/**\n * Scheduled discovery (2.5).\n *\n * A thin, dependency-free cron driver over the existing read-only discovery and\n * drift machinery. Two pure pieces — {@link parseCron} and {@link nextRun} — own\n * a minimal 5-field cron grammar (UTC, no NPM dependency); {@link runOnce} runs a\n * single deterministic local scan and returns the topology drift relative to the\n * prior run. `runOnce` never invokes the Claude/agent loop and needs no API key.\n *\n * Reuse, not reimplementation: discovery is `runLocalDiscovery` and diffing is the\n * pure `diffTopology` engine (surfaced here via the 2.1 incremental `mode:'update'`\n * rescan, which already returns the delta). This module adds scheduling + per-run\n * persistence around them.\n */\n\nimport type { CartographyConfig } from './types.js';\nimport type { TopologyDelta } from './diff.js';\nimport type { CartographyDB } from './db.js';\nimport { runLocalDiscovery } from './discovery/local.js';\nimport { diffTopology } from './diff.js';\nimport { logInfo } from './logger.js';\n\n/** Parsed allowed-value sets for each cron field (all UTC). */\nexport interface CronFields {\n /** 0–59 */\n minute: Set<number>;\n /** 0–23 */\n hour: Set<number>;\n /** 1–31 */\n dom: Set<number>;\n /** 1–12 */\n month: Set<number>;\n /** 0–6 (Sunday = 0) */\n dow: Set<number>;\n}\n\ninterface FieldSpec {\n name: keyof CronFields;\n min: number;\n max: number;\n}\n\nconst FIELD_SPECS: readonly FieldSpec[] = [\n { name: 'minute', min: 0, max: 59 },\n { name: 'hour', min: 0, max: 23 },\n { name: 'dom', min: 1, max: 31 },\n { name: 'month', min: 1, max: 12 },\n { name: 'dow', min: 0, max: 7 }, // 7 and 0 both mean Sunday; normalized to 0 below\n];\n\n/** Parse one cron field into the set of integers it matches within [min, max]. */\nfunction parseField(raw: string, spec: FieldSpec): Set<number> {\n const out = new Set<number>();\n const add = (n: number): void => {\n if (!Number.isInteger(n) || n < spec.min || n > spec.max) {\n throw new RangeError(`Invalid value \"${n}\" in cron field \"${spec.name}\" (allowed ${spec.min}-${spec.max})`);\n }\n // Day-of-week 7 normalizes to 0 (both Sunday).\n out.add(spec.name === 'dow' && n === 7 ? 0 : n);\n };\n\n for (const part of raw.split(',')) {\n if (part === '') {\n throw new RangeError(`Empty term in cron field \"${spec.name}\"`);\n }\n // Split an optional step: \"<range>/<step>\".\n const [rangePart, stepPart, ...rest] = part.split('/');\n if (rest.length > 0) {\n throw new RangeError(`Malformed step in cron field \"${spec.name}\": \"${part}\"`);\n }\n let step = 1;\n if (stepPart !== undefined) {\n step = Number(stepPart);\n if (!Number.isInteger(step) || step < 1) {\n throw new RangeError(`Invalid step \"${stepPart}\" in cron field \"${spec.name}\"`);\n }\n }\n\n let lo: number;\n let hi: number;\n if (rangePart === '*') {\n lo = spec.min;\n hi = spec.max;\n } else if (rangePart.includes('-')) {\n const [a, b, ...extra] = rangePart.split('-');\n if (extra.length > 0) {\n throw new RangeError(`Malformed range in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n lo = Number(a);\n hi = Number(b);\n if (!Number.isInteger(lo) || !Number.isInteger(hi)) {\n throw new RangeError(`Non-numeric range in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n if (lo > hi) {\n throw new RangeError(`Descending range in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n } else {\n const n = Number(rangePart);\n if (!Number.isInteger(n)) {\n throw new RangeError(`Non-numeric value in cron field \"${spec.name}\": \"${rangePart}\"`);\n }\n // A bare value with a step (\"5/10\") steps from the value to the field max.\n lo = n;\n hi = stepPart !== undefined ? spec.max : n;\n }\n\n for (let v = lo; v <= hi; v += step) add(v);\n }\n\n if (out.size === 0) {\n throw new RangeError(`Cron field \"${spec.name}\" matched no values`);\n }\n return out;\n}\n\n/**\n * Parse a 5-field cron expression (`minute hour dom month dow`, UTC) into its\n * matching value sets. Grammar per field: star, star-slash-n, `a`, `a-b`,\n * `a-b`-slash-n, `a`-slash-n, and comma lists of those. Day-of-week `7`\n * normalizes to `0` (Sunday).\n *\n * @throws {RangeError} when the expression is not exactly 5 fields, or any field\n * is out of range / non-numeric / malformed.\n */\nexport function parseCron(expr: string): CronFields {\n const fields = expr.trim().split(/\\s+/);\n if (fields.length !== 5) {\n throw new RangeError(`Cron expression must have 5 fields (got ${fields.length}): \"${expr}\"`);\n }\n const [minute, hour, dom, month, dow] = FIELD_SPECS.map((spec, i) => parseField(fields[i]!, spec));\n return { minute: minute!, hour: hour!, dom: dom!, month: month!, dow: dow! };\n}\n\n/** True when `date` (UTC) satisfies all cron fields, honoring dom/dow OR-semantics. */\nfunction matches(fields: CronFields, date: Date): boolean {\n if (!fields.minute.has(date.getUTCMinutes())) return false;\n if (!fields.hour.has(date.getUTCHours())) return false;\n if (!fields.month.has(date.getUTCMonth() + 1)) return false;\n\n const domRestricted = fields.dom.size !== 31;\n const dowRestricted = fields.dow.size !== 7;\n const domOk = fields.dom.has(date.getUTCDate());\n const dowOk = fields.dow.has(date.getUTCDay());\n\n // Standard cron OR-semantics: when both dom and dow are restricted, either may\n // match; when only one is restricted, only that one constrains.\n if (domRestricted && dowRestricted) return domOk || dowOk;\n if (domRestricted) return domOk;\n if (dowRestricted) return dowOk;\n return true;\n}\n\n/** Upper bound on the forward search: ~4 years of minutes (covers Feb-29 schedules). */\nconst MAX_SEARCH_MINUTES = 4 * 366 * 24 * 60;\n\n/**\n * The earliest scheduled instant strictly after `after` (UTC, second/ms truncated).\n * Deterministic and pure: same `expr` + `after` always yields the same Date.\n *\n * @throws {RangeError} when `expr` is invalid, or no match is found within ~4 years.\n */\nexport function nextRun(expr: string, after: Date): Date {\n const fields = parseCron(expr);\n // Truncate to the minute and step forward at least one minute.\n const cursor = new Date(after.getTime());\n cursor.setUTCSeconds(0, 0);\n cursor.setUTCMinutes(cursor.getUTCMinutes() + 1);\n\n for (let i = 0; i < MAX_SEARCH_MINUTES; i++) {\n if (matches(fields, cursor)) return new Date(cursor.getTime());\n cursor.setUTCMinutes(cursor.getUTCMinutes() + 1);\n }\n throw new RangeError(`No cron match for \"${expr}\" within ~4 years after ${after.toISOString()}`);\n}\n\nexport interface ScheduledRunResult {\n /** The session this run scanned (reused in place across runs). */\n sessionId: string;\n /** The prior session used as the diff base, or `undefined` on the first run. */\n baseSessionId?: string;\n /** Topology drift this run observed. */\n delta: TopologyDelta;\n /** Node count after the scan. */\n nodes: number;\n /** Edge count after the scan. */\n edges: number;\n /** Ids of the scanners that ran. */\n scanners: string[];\n}\n\n/**\n * Run one deterministic local-discovery pass and return its topology drift.\n *\n * Reuses the most recent prior `discover` session for this config's tenant and\n * rescans it in place via the 2.1 incremental `mode:'update'` path (same session\n * id, prunes vanished entities, stamps `last_scanned_at`), which already returns\n * the delta from the pure `diffTopology` engine. When no prior session exists, it\n * creates a fresh session, replace-scans it, and reports the whole topology as\n * `added` (diffed against an empty base).\n *\n * Read-only and API-key-free. Does **not** persist the drift run — the caller does\n * (so tests can inspect the delta first). All progress goes to stderr.\n */\nexport async function runOnce(cfg: CartographyConfig, db: CartographyDB): Promise<ScheduledRunResult> {\n const prior = db.getLatestSession('discover');\n\n if (prior) {\n // Incremental in-place rescan (2.1): the delta is computed and returned by\n // runLocalDiscovery itself, so no second diff pass is needed.\n const r = await runLocalDiscovery(db, prior.id, {\n hint: cfg.entryPoints.join(','),\n plugins: cfg.plugins,\n mode: 'update',\n onProgress: (line) => logInfo(`scan: ${line}`),\n });\n const delta = r.delta ?? diffTopology({ nodes: [], edges: [] }, { nodes: [], edges: [] });\n logInfo('scheduled run complete', { sessionId: prior.id, base: prior.id, ...delta.summary });\n return { sessionId: prior.id, baseSessionId: prior.id, delta, nodes: r.nodes, edges: r.edges, scanners: r.scanners };\n }\n\n // First run: no prior topology — create a session, scan it, diff against empty.\n const sessionId = db.createSession('discover', cfg);\n try {\n const r = await runLocalDiscovery(db, sessionId, {\n hint: cfg.entryPoints.join(','),\n plugins: cfg.plugins,\n mode: 'replace',\n onProgress: (line) => logInfo(`scan: ${line}`),\n });\n const current = { nodes: db.getNodes(sessionId), edges: db.getEdges(sessionId) };\n const delta = diffTopology({ nodes: [], edges: [] }, current);\n logInfo('scheduled run complete', { sessionId, base: null, ...delta.summary });\n return { sessionId, baseSessionId: undefined, delta, nodes: r.nodes, edges: r.edges, scanners: r.scanners };\n } finally {\n db.endSession(sessionId);\n }\n}\n","import { mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { CartographyDB, GraphSummary } from './db.js';\nimport type { ComplianceReport } from './compliance/types.js';\nimport type { NodeRow, EdgeRow, TopologyDiff } from './types.js';\nimport { NODE_TYPE_GROUPS } from './types.js';\nimport { toBackstageEntities, entitiesToYaml } from './backstage.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 for (const [layer, types] of Object.entries(NODE_TYPE_GROUPS)) {\n if ((types as readonly string[]).includes(type)) return layer;\n }\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\n// ── Diff / Drift Mermaid ───────────────────────────────────────────────────────\n\nconst DIFF_CLASSES: Record<'added' | 'removed' | 'changed' | 'context', string> = {\n added: 'fill:#0d3d0d,stroke:#22c55e,color:#86efac',\n removed: 'fill:#3d0d0d,stroke:#ef4444,color:#fca5a5',\n changed: 'fill:#3d2f0d,stroke:#f59e0b,color:#fcd34d',\n context: 'fill:#1e1e1e,stroke:#555555,color:#999999',\n};\n\nfunction diffNodeLabel(node: NodeRow, suffix?: string): string {\n const icon = MERMAID_ICONS[node.type] ?? '?';\n const extra = suffix ? `<br/><small>Δ ${suffix}</small>` : '';\n return `[\"${icon} <b>${node.name}</b><br/><small>${node.type}</small>${extra}\"]`;\n}\n\n/**\n * Render a topology diff as a Mermaid graph: added nodes/edges in green, removed\n * in red, changed in amber. Endpoints of added/removed edges that are otherwise\n * unchanged are drawn as neutral \"context\" nodes so every edge has both ends.\n */\nexport function generateDiffMermaid(diff: TopologyDiff): string {\n const total =\n diff.summary.nodesAdded + diff.summary.nodesRemoved + diff.summary.nodesChanged +\n diff.summary.edgesAdded + diff.summary.edgesRemoved;\n if (total === 0) return 'graph TB\\n nodrift[\"✓ No drift between the two sessions\"]';\n\n const lines: string[] = ['graph TB'];\n for (const [k, style] of Object.entries(DIFF_CLASSES)) lines.push(` classDef ${k} ${style}`);\n lines.push('');\n\n type Cls = 'added' | 'removed' | 'changed' | 'context';\n const rank: Record<Cls, number> = { added: 3, removed: 3, changed: 3, context: 0 };\n const entries = new Map<string, { node: NodeRow; cls: Cls; suffix?: string }>();\n const place = (node: NodeRow, cls: Cls, suffix?: string) => {\n const prev = entries.get(node.id);\n if (prev && rank[prev.cls] >= rank[cls]) return;\n entries.set(node.id, { node, cls, suffix });\n };\n\n for (const n of diff.nodes.added) place(n, 'added');\n for (const n of diff.nodes.removed) place(n, 'removed');\n for (const c of diff.nodes.changed) place(c.after, 'changed', c.changedFields.join(', '));\n\n // Stub a neutral context node for an edge endpoint we have no full record for.\n const contextNode = (id: string): NodeRow => ({\n id, type: 'unknown', name: id, discoveredVia: 'diff',\n confidence: 1, metadata: {}, tags: [], sessionId: '', discoveredAt: '', depth: 0,\n });\n const ensureEndpoint = (id: string) => { if (!entries.has(id)) place(contextNode(id), 'context'); };\n for (const e of [...diff.edges.added, ...diff.edges.removed]) { ensureEndpoint(e.sourceId); ensureEndpoint(e.targetId); }\n\n for (const { node, cls, suffix } of entries.values()) {\n lines.push(` ${sanitize(node.id)}${diffNodeLabel(node, suffix)}:::${cls}`);\n }\n lines.push('');\n\n for (const e of diff.edges.added) {\n const label = EDGE_LABELS[e.relationship] ?? e.relationship;\n lines.push(` ${sanitize(e.sourceId)} ==>|\"+ ${label}\"| ${sanitize(e.targetId)}`);\n }\n for (const e of diff.edges.removed) {\n const label = EDGE_LABELS[e.relationship] ?? e.relationship;\n lines.push(` ${sanitize(e.sourceId)} -.->|\"- ${label}\"| ${sanitize(e.targetId)}`);\n }\n\n return lines.join('\\n');\n}\n\n// ── Backstage YAML ───────────────────────────────────────────────────────────\n\nexport function exportBackstageYAML(nodes: NodeRow[], edges: EdgeRow[], org?: string): string {\n // Thin serializer over the extracted, transport-agnostic mapper (4.6). Kept\n // byte-identical to the previous inline implementation by a snapshot test.\n return entitiesToYaml(toBackstageEntities(nodes, edges, org !== undefined ? { org } : {}));\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 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 }, 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// ── 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-product{font-size:15px;font-weight:600;color:var(--text-muted)}\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 <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 <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// ── Cost (FinOps) export (3.3) ────────────────────────────────────────────────\n\n/**\n * CSV-escape a field: quote when it contains a comma/quote/newline, and neutralize\n * spreadsheet formula injection by prefixing a leading `=`/`+`/`-`/`@` with a quote\n * (a hostile `owner`/`domain` from an imported CSV must not execute in Excel/Sheets).\n */\nfunction csvField(v: string | number): string {\n let s = String(v);\n if (/^[=+\\-@]/.test(s)) s = `'${s}`;\n return /[\",\\n]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n}\n\n/**\n * Cost rolled up by domain and owner as CSV — the FinOps export. One block per\n * scope; rows are bucketed by `(currency, period)` so mixed currencies are never\n * summed into one figure.\n */\nexport function exportCostCSV(summary: GraphSummary): string {\n const rows = ['scope,key,currency,period,total,nodes'];\n for (const c of summary.costByDomain) {\n rows.push(['domain', c.domain, c.currency, c.period, c.total, c.nodes].map(csvField).join(','));\n }\n for (const c of summary.costByOwner) {\n rows.push(['owner', c.owner, c.currency, c.period, c.total, c.nodes].map(csvField).join(','));\n }\n return rows.join('\\n') + '\\n';\n}\n\n/** The same cost rollup as JSON for programmatic consumers. */\nexport function exportCostSummary(summary: GraphSummary): string {\n return JSON.stringify({\n costByDomain: summary.costByDomain,\n costByOwner: summary.costByOwner,\n costCoverage: summary.costCoverage,\n }, null, 2);\n}\n\n// ── Compliance report export (3.4) ────────────────────────────────────────────\n\nconst SEVERITY_RANK: Record<string, number> = { critical: 0, high: 1, medium: 2, low: 3 };\n\n/**\n * Render a `ComplianceReport` as `json`, `markdown`, or `mermaid`. The Mermaid form\n * reuses the diff exporter's severity-coloured `classDef` pattern: one node per gap\n * coloured by severity, with a `\"✓ No compliance gaps\"` empty state.\n */\nexport function exportComplianceReport(report: ComplianceReport, format: 'json' | 'markdown' | 'mermaid'): string {\n if (format === 'json') return JSON.stringify(report, null, 2);\n\n if (format === 'markdown') {\n const scoreStr = report.score === null ? 'n/a' : `${report.score}/100`;\n const out: string[] = [\n `# Compliance — ${report.rulesetName} v${report.rulesetVersion}`,\n ``,\n `**Status:** ${report.status.toUpperCase()} · **Score:** ${scoreStr}`,\n ``,\n `| Controls | Count |`, `|----------|-------|`,\n `| Passed | ${report.totals.passed} |`,\n `| Failed | ${report.totals.failed} |`,\n `| Not applicable | ${report.totals.notApplicable} |`,\n `| Total | ${report.totals.rules} |`,\n ``,\n `| Severity | Failed | Passed |`, `|----------|--------|--------|`,\n ...(['critical', 'high', 'medium', 'low'] as const).map(\n (s) => `| ${s} | ${report.bySeverity[s].failed} | ${report.bySeverity[s].passed} |`,\n ),\n ];\n if (report.gaps.length === 0) {\n out.push(``, `✓ No compliance gaps.`);\n } else {\n out.push(``, `## Gaps`);\n for (const g of [...report.gaps].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity])) {\n out.push(``, `### [${g.severity}] ${g.control} — ${g.title}`, ...g.nodeIds.map((id) => `- \\`${id}\\``));\n }\n }\n return out.join('\\n');\n }\n\n // mermaid\n const lines: string[] = [\n 'graph TB',\n ' classDef critical fill:#7f1d1d,stroke:#ef4444,color:#fff;',\n ' classDef high fill:#7c2d12,stroke:#f97316,color:#fff;',\n ' classDef medium fill:#713f12,stroke:#eab308,color:#fff;',\n ' classDef low fill:#1e3a5f,stroke:#3b82f6,color:#fff;',\n ];\n if (report.gaps.length === 0) {\n lines.push(' ok[\"✓ No compliance gaps\"]');\n return lines.join('\\n');\n }\n // Strip Mermaid-breaking chars (quote/bracket/newline) defensively, even though\n // titles come from trusted bundled rulesets. Order by severity for parity with md.\n const mmSafe = (s: string): string => s.replace(/[\"\\]\\r\\n]/g, \"'\");\n let i = 0;\n for (const g of [...report.gaps].sort((a, b) => SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity])) {\n const gid = `g${i++}`;\n lines.push(` ${gid}[\"${mmSafe(g.control)}: ${mmSafe(g.title)} (${g.nodeIds.length})\"]:::${g.severity}`);\n }\n return lines.join('\\n');\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'],\n): void {\n mkdirSync(outputDir, { 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\n if (formats.includes('mermaid')) {\n writeFileSync(join(outputDir, 'topology.mermaid'), generateTopologyMermaid(nodes, edges));\n writeFileSync(join(outputDir, 'dependencies.mermaid'), generateDependencyMermaid(nodes, edges));\n }\n\n if (formats.includes('json')) {\n writeFileSync(join(outputDir, 'catalog.json'), exportJSON(db, sessionId));\n }\n\n if (formats.includes('yaml')) {\n writeFileSync(join(outputDir, 'catalog-info.yaml'), exportBackstageYAML(nodes, edges));\n }\n\n if (formats.includes('html') || formats.includes('map') || formats.includes('discovery')) {\n writeFileSync(join(outputDir, 'discovery.html'), exportDiscoveryApp(nodes, edges));\n }\n\n if (formats.includes('cost')) {\n const summary = db.getGraphSummary(sessionId);\n writeFileSync(join(outputDir, 'cost-by-domain.csv'), exportCostCSV(summary));\n writeFileSync(join(outputDir, 'cost-summary.json'), exportCostSummary(summary));\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 // Pre-parse occupied coordinates to avoid repeated string splitting\n const parsedOccupied: AxialCoord[] = [];\n for (const oKey of occupied) {\n const [oq, or] = oKey.split(',').map(Number);\n parsedOccupied.push({ q: oq, r: or });\n }\n\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 if (occupied.has(key(tp.q, tp.r))) { fits = false; break; }\n\n for (const oc of parsedOccupied) {\n if (hexDistance(tp, oc) < gap) {\n fits = false;\n break;\n }\n }\n if (!fits) break;\n }\n\n if (fits) return candidate;\n }\n }\n\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-to-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","/**\n * Plain-text compliance report formatter (3.4). Colour-free so it lives outside\n * `cli.ts` (which owns the colour helpers and is excluded from coverage).\n */\n\nimport type { ComplianceReport } from './types.js';\n\nconst NODE_CAP = 50;\n\n/** Render a `ComplianceReport` as a plain-text summary with `✓`/`✗` markers. */\nexport function formatComplianceText(report: ComplianceReport): string {\n const scoreStr = report.score === null ? 'n/a' : `${report.score}/100`;\n const lines: string[] = [\n `Compliance: ${report.rulesetName} v${report.rulesetVersion} — ${report.status.toUpperCase()} (score ${scoreStr})`,\n `Controls: ${report.totals.passed} passed, ${report.totals.failed} failed, ${report.totals.notApplicable} n/a (of ${report.totals.rules})`,\n '',\n 'By severity (failed/passed):',\n ...(['critical', 'high', 'medium', 'low'] as const).map(\n (s) => ` - ${s}: ${report.bySeverity[s].failed} failed / ${report.bySeverity[s].passed} passed`,\n ),\n ];\n\n if (report.gaps.length === 0) {\n lines.push('', '✓ No compliance gaps.');\n return lines.join('\\n');\n }\n\n lines.push('', `Gaps (${report.gaps.length}):`);\n for (const g of report.gaps) {\n lines.push(` ✗ [${g.severity}] ${g.control} — ${g.title}`);\n const shown = g.nodeIds.slice(0, NODE_CAP);\n for (const id of shown) lines.push(` ${id}`);\n if (g.nodeIds.length > NODE_CAP) lines.push(` … +${g.nodeIds.length - NODE_CAP} more`);\n }\n return lines.join('\\n');\n}\n","/**\n * Cost attribution (3.3) — turn the topology into a FinOps lens.\n *\n * A pluggable `CostSource` yields `{ owner, cost }` keyed by node id; `enrichCosts`\n * applies it to a session via targeted, idempotent UPDATEs (never `INSERT OR\n * REPLACE`, so other node fields are untouched). The first source is `CsvCostSource`\n * — deterministic, provider-agnostic, no new dependency. Live billing-API sources\n * (AWS Cost Explorer, GCP/Azure) are future implementations of the same interface.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport type { CartographyDB } from './db.js';\nimport { CostEntrySchema } from './types.js';\nimport type { CostEntry } from './types.js';\nimport { logWarn } from './logger.js';\n\n/** One attribution line keyed to a node. `cost`/`owner` may be partially present. */\nexport interface CostRecord {\n nodeId: string;\n owner?: string;\n cost?: CostEntry;\n}\n\n/** A pluggable provider of cost/owner attribution, keyed by node id. */\nexport interface CostSource {\n readonly id: string;\n /** Attribution keyed by node id. An absent/unauthorized source resolves to an empty map (degrade). */\n fetch(): Promise<Map<string, CostRecord>>;\n}\n\nexport interface EnrichResult {\n source: string;\n total: number;\n matched: number;\n unmatched: number;\n unmatchedIds: string[];\n}\n\n/** How a CSV row is resolved to a node id when no explicit `nodeId` column is given. */\nexport type MatchStrategy = 'nodeId' | 'name' | 'tag';\n\nexport interface CsvCostSourceOptions {\n filePath: string;\n match?: MatchStrategy;\n db?: CartographyDB;\n sessionId?: string;\n}\n\n/** Split one CSV line into fields, honoring double-quoted fields with escaped quotes. */\nfunction splitCsvLine(line: string): string[] {\n const out: string[] = [];\n let cur = '';\n let inQuotes = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (inQuotes) {\n if (ch === '\"') {\n if (line[i + 1] === '\"') { cur += '\"'; i++; } else { inQuotes = false; }\n } else cur += ch;\n } else if (ch === '\"') {\n inQuotes = true;\n } else if (ch === ',') {\n out.push(cur); cur = '';\n } else cur += ch;\n }\n out.push(cur);\n return out.map((s) => s.trim());\n}\n\n/**\n * Parse a cost CSV (`nodeId,owner,amount,currency,period[,source]`, header required)\n * into validated `CostRecord[]`. Each row's cost fields are validated via\n * `CostEntrySchema`; a malformed row is skipped with a `logWarn` (counts only, no\n * owner PII) — the batch never aborts.\n */\nexport function parseCostCsv(text: string): CostRecord[] {\n const lines = text.split(/\\r?\\n/).filter((l) => l.trim().length > 0);\n if (lines.length === 0) return [];\n const header = splitCsvLine(lines[0]).map((h) => h.toLowerCase());\n const col = (name: string): number => header.indexOf(name);\n const iNode = col('nodeid');\n const iOwner = col('owner');\n const iAmount = col('amount');\n const iCurrency = col('currency');\n const iPeriod = col('period');\n const iSource = col('source');\n if (iNode < 0) {\n logWarn('cost csv: missing required \"nodeId\" header column');\n return [];\n }\n\n const records: CostRecord[] = [];\n for (let r = 1; r < lines.length; r++) {\n const f = splitCsvLine(lines[r]);\n const nodeId = f[iNode];\n if (!nodeId) { logWarn(`cost csv: row ${r + 1} skipped (empty nodeId)`); continue; }\n\n const rec: CostRecord = { nodeId };\n if (iOwner >= 0 && f[iOwner]) rec.owner = f[iOwner];\n\n const amountRaw = iAmount >= 0 ? f[iAmount] : '';\n if (amountRaw) {\n const parsed = CostEntrySchema.safeParse({\n amount: Number(amountRaw),\n currency: iCurrency >= 0 ? f[iCurrency] : undefined,\n period: iPeriod >= 0 ? f[iPeriod] : undefined,\n ...(iSource >= 0 && f[iSource] ? { source: f[iSource] } : {}),\n });\n if (!parsed.success) {\n logWarn(`cost csv: row ${r + 1} skipped (invalid cost fields)`);\n // Keep an owner-only record if owner is present; otherwise drop the row.\n if (!rec.owner) continue;\n } else {\n rec.cost = parsed.data;\n }\n }\n if (rec.owner || rec.cost) records.push(rec);\n }\n return records;\n}\n\n/** CSV-backed cost source. Resolves row→node ids per the chosen `MatchStrategy`. */\nexport class CsvCostSource implements CostSource {\n readonly id: string;\n constructor(private readonly opts: CsvCostSourceOptions) {\n const base = opts.filePath.split(/[\\\\/]/).pop() ?? opts.filePath;\n this.id = `csv:${base}`;\n }\n\n async fetch(): Promise<Map<string, CostRecord>> {\n const text = readFileSync(resolve(this.opts.filePath), 'utf-8');\n const records = parseCostCsv(text);\n const match = this.opts.match ?? 'nodeId';\n const out = new Map<string, CostRecord>();\n\n if (match === 'nodeId') {\n for (const rec of records) out.set(rec.nodeId, rec);\n return out;\n }\n\n // name / tag resolution needs the session's nodes to build a lookup index.\n if (!this.opts.db || !this.opts.sessionId) {\n logWarn(`cost csv: match '${match}' requires db + sessionId; falling back to nodeId`);\n for (const rec of records) out.set(rec.nodeId, rec);\n return out;\n }\n const nodes = this.opts.db.getNodes(this.opts.sessionId);\n const index = new Map<string, string>(); // key (name or tag) → node id\n for (const n of nodes) {\n if (match === 'name') index.set(n.name, n.id);\n else for (const t of n.tags) index.set(t, n.id);\n }\n for (const rec of records) {\n const resolved = index.get(rec.nodeId);\n out.set(resolved ?? rec.nodeId, { ...rec, nodeId: resolved ?? rec.nodeId });\n }\n return out;\n }\n}\n\n/**\n * Idempotent post-pass: apply a source's attribution to a session via targeted\n * UPDATEs. Re-running with the same source yields the same DB state. Unmatched\n * rows (no such node id) are reported, never written.\n */\nexport async function enrichCosts(db: CartographyDB, sessionId: string, source: CostSource): Promise<EnrichResult> {\n const records = await source.fetch();\n let matched = 0;\n const unmatchedIds: string[] = [];\n for (const [nodeId, rec] of records) {\n const ok = db.enrichNodeAttribution(sessionId, nodeId, {\n owner: rec.owner ?? undefined,\n cost: rec.cost ?? undefined,\n });\n if (ok) matched++; else unmatchedIds.push(nodeId);\n }\n return { source: source.id, total: records.size, matched, unmatched: unmatchedIds.length, unmatchedIds };\n}\n","/**\n * Sharing classifier + pre-send preview (2.10).\n *\n * Resolves the effective sharing level for each node (a PERSONAL-host hard floor\n * over the persisted policy), applies that level (`none` drops, `anonymized`\n * pseudonymizes, `full` keeps raw), and builds {@link previewShare} — the exact,\n * topology-shape-preserving payload that *would* leave the machine. This is the\n * privacy gate 2.11/2.12 build on; nothing here performs any network I/O.\n */\n\nimport type { DiscoveryNode, NodeRow, EdgeRow, SharingLevel, SharingPolicy } from './types.js';\nimport type { CartographyDB } from './db.js';\nimport { pseudonymizeString, pseudonymize } from './anonymize.js';\nimport { isPersonalHost } from './scanners/bookmarks.js';\n\n/** Count of `*`/`**` wildcards in a pattern (fewer ⇒ more specific). */\nfunction wildcardCount(pattern: string): number {\n return (pattern.match(/\\*/g) ?? []).length;\n}\n\n/**\n * Glob match over a node id. `*` matches any run of non-`:` chars within a\n * segment; `**` matches any chars including `:`. Anchored full match.\n */\nfunction globMatch(pattern: string, id: string): boolean {\n // Build a regex: ** → .*, * → [^:]*, everything else escaped literally.\n let re = '^';\n for (let i = 0; i < pattern.length; i++) {\n const c = pattern[i];\n if (c === '*') {\n if (pattern[i + 1] === '*') { re += '.*'; i++; }\n else re += '[^:]*';\n } else {\n re += c.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&');\n }\n }\n re += '$';\n return new RegExp(re).test(id);\n}\n\n/**\n * Resolve the policy-level for a node id: the most-specific matching override\n * wins (fewest wildcards, then longest pattern); ties and no-match fall through\n * to `defaultLevel`. The `'*'`/`'**'` rows are the global default and always lose\n * to any narrower match. Pure and deterministic.\n */\nexport function resolveSharingLevel(nodeId: string, policy: SharingPolicy): SharingLevel {\n const matches = policy.overrides\n .filter((o) => o.pattern !== '*' && o.pattern !== '**' && globMatch(o.pattern, nodeId))\n .sort((a, b) =>\n wildcardCount(a.pattern) - wildcardCount(b.pattern) ||\n b.pattern.length - a.pattern.length,\n );\n return matches.length ? matches[0].level : policy.defaultLevel;\n}\n\n/** Hostnames embedded in a node (id, name, and host/url metadata) for the PERSONAL floor. */\nfunction nodeHosts(node: DiscoveryNode): string[] {\n const out: string[] = [node.id, node.name];\n const meta = node.metadata ?? {};\n for (const k of ['host', 'url', 'domain'] as const) {\n const v = (meta as Record<string, unknown>)[k];\n if (typeof v === 'string') out.push(v);\n }\n if (node.domain) out.push(node.domain);\n return out;\n}\n\n/**\n * The effective level for a node: a PERSONAL-host hard floor (single-sourced from\n * the bookmarks never-share list) forces `'none'` regardless of policy; otherwise\n * the policy's resolved level for the node id applies.\n */\nexport function resolveEffectiveLevel(node: DiscoveryNode, policy: SharingPolicy): SharingLevel {\n if (nodeHosts(node).some((h) => isPersonalHost(h))) return 'none';\n return resolveSharingLevel(node.id, policy);\n}\n\n/**\n * Apply a sharing level to one node:\n * - `none` → `null` (the node is dropped, never leaves).\n * - `anonymized` → a clone with `id`/`name` pseudonymized and `metadata` walked\n * (structure-preserving); identifying fragments tokenized.\n * - `full` → a structural clone, verbatim.\n *\n * The same deterministic `pseudonymizeString` is applied to the id here and by\n * {@link previewShare} when remapping edges, so endpoints always resolve.\n */\nexport function applySharingLevel(\n node: DiscoveryNode,\n level: SharingLevel,\n orgKey: Buffer,\n db?: CartographyDB,\n): DiscoveryNode | null {\n if (level === 'none') return null;\n if (level === 'full') return { ...node, metadata: { ...(node.metadata ?? {}) }, tags: [...(node.tags ?? [])] };\n return {\n ...node,\n id: pseudonymizeString(node.id, orgKey, db),\n name: pseudonymizeString(node.name, orgKey, db),\n metadata: pseudonymize(node.metadata ?? {}, orgKey, db) as Record<string, unknown>,\n tags: (node.tags ?? []).map((t) => pseudonymizeString(t, orgKey, db)),\n };\n}\n\nexport interface SharePreviewEntry {\n /** The original (un-anonymized) node, for the operator's side-by-side disclosure. */\n node: DiscoveryNode;\n level: SharingLevel;\n /** What would actually leave: the transformed node, or `null` when dropped. */\n payload: DiscoveryNode | null;\n}\n\nexport interface SharePreview {\n nodes: SharePreviewEntry[];\n /** Edges that would leave, with endpoints remapped to surviving (transformed) ids. */\n edges: { sourceId: string; targetId: string; relationship: string }[];\n /** Original ids of nodes dropped at level `none`. */\n droppedNodeIds: string[];\n}\n\n/**\n * Build the exact payload that would leave for a session: resolve each node's\n * effective level, apply it, and remap edge endpoints through the same id\n * transform. An edge survives only when both endpoints survive — so when no node\n * is dropped the node count, edge count, and relationship multiset are identical\n * before/after; when some are dropped the result is a well-defined subgraph.\n *\n * `opts.persistReversal` (default false) controls whether anonymized fragments are\n * written to the reversal map — pure preview need not persist; an actual\n * pre-send transform should, so an admin can later invert.\n */\nexport function previewShare(\n db: CartographyDB,\n sessionId: string,\n orgKey: Buffer,\n policy: SharingPolicy,\n opts: { persistReversal?: boolean } = {},\n): SharePreview {\n const persist = opts.persistReversal ? db : undefined;\n const nodes: NodeRow[] = db.getNodes(sessionId);\n const edges: EdgeRow[] = db.getEdges(sessionId);\n\n const entries: SharePreviewEntry[] = [];\n const idMap = new Map<string, string | null>(); // original id → new id (null = dropped)\n const droppedNodeIds: string[] = [];\n\n for (const node of nodes) {\n const level = resolveEffectiveLevel(node, policy);\n const payload = applySharingLevel(node, level, orgKey, persist);\n entries.push({ node, level, payload });\n if (payload === null) {\n idMap.set(node.id, null);\n droppedNodeIds.push(node.id);\n } else {\n idMap.set(node.id, payload.id);\n }\n }\n\n const outEdges: { sourceId: string; targetId: string; relationship: string }[] = [];\n for (const e of edges) {\n const src = idMap.get(e.sourceId);\n const tgt = idMap.get(e.targetId);\n // Drop an edge if either endpoint is dropped or unknown (not in this session's nodes).\n if (src == null || tgt == null) continue;\n outEdges.push({ sourceId: src, targetId: tgt, relationship: e.relationship });\n }\n\n return { nodes: entries, edges: outEdges, droppedNodeIds };\n}\n\n/**\n * The seam 2.11 will use to suppress `ask_user` re-prompts: true when the node id\n * is already covered by the persisted policy — either a matching override or a\n * non-`none` default — so a matched item is never re-prompted.\n */\nexport function isRemembered(policy: SharingPolicy, nodeId: string): boolean {\n const matched = policy.overrides.some((o) => o.pattern !== '*' && o.pattern !== '**' && globMatch(o.pattern, nodeId));\n return matched || policy.defaultLevel !== 'none';\n}\n","/**\n * Stable content hashing for the central-DB share queue (2.11).\n *\n * The hash is computed over the *policy-transformed* payload (what `previewShare`\n * produces — already anonymized/dropped), so two scans that yield the same outgoing\n * bytes map to the same `pending_shares` row (idempotent enqueue) and the same\n * server-side dedup key. It is deterministic via `stableStringify` (recursively\n * key-sorted JSON), so key ordering never perturbs the hash.\n *\n * Zero new dependencies — `node:crypto` + the existing `stableStringify`.\n */\n\nimport { createHash } from 'node:crypto';\nimport { stableStringify } from '../diff.js';\n\n/** A node or edge projected to its outgoing (already-transformed) shape. */\nexport type ShareKind = 'node' | 'edge';\n\n/**\n * sha256 (hex) over the canonical JSON of `{ kind, payload }`. `payload` is the\n * transformed projection — for `anonymized`/`none` items it is the anonymized form,\n * never the raw record — so the hash is stable across scans and identical to the\n * value the central side can dedup on.\n */\nexport function shareHash(kind: ShareKind, payload: unknown): string {\n return createHash('sha256').update(stableStringify({ kind, payload })).digest('hex');\n}\n","/**\n * Share classifier (2.11) — pure, DB-agnostic.\n *\n * Buckets the items of a {@link SharePreview} (the 2.10 policy-transformed payload)\n * into `share` / `withhold` / `pending` against the persisted {@link SharingPolicy}\n * and the set of already-shared content hashes:\n *\n * - A node whose transformed payload is `null` (effective level `none`, incl. the\n * PERSONAL-host hard floor) is **withheld** — it never leaves.\n * - A node already pushed (its `shareHash` ∈ `sharedHashes`) is dropped (no bucket).\n * - A surviving node covered by the policy ({@link isRemembered}) is **share** — the\n * employee's policy is the standing consent, so it auto-approves with no re-prompt.\n * - A surviving node *not* covered by any policy rule/default is **pending** — new /\n * unmatched, queued for explicit review. Nothing in this bucket ever leaves until\n * the employee approves it (the load-bearing privacy invariant).\n *\n * Edges follow their endpoints: an edge is shareable only when both endpoints\n * survive (already guaranteed by `previewShare`, which drops dangling edges) and\n * both endpoint nodes land in `share`. Otherwise the edge is withheld.\n *\n * The transform/anonymization itself is **not** reimplemented here — it is consumed\n * from `previewShare`. This module only routes the already-transformed items.\n */\n\nimport type { SharingPolicy } from '../types.js';\nimport type { SharePreview } from '../sharing.js';\nimport { isRemembered } from '../sharing.js';\nimport { shareHash } from './hash.js';\n\n/** One classified, ready-to-queue item carrying its outgoing (transformed) payload. */\nexport interface ClassifiedItem {\n contentHash: string;\n kind: 'node' | 'edge';\n /** Original node id (for `node` items); the remapped source id for `edge` items. */\n nodeId?: string;\n /** The exact transformed bytes that would leave the machine. */\n payload: unknown;\n}\n\nexport interface ClassifyResult {\n /** Covered by policy → auto-approve (no re-prompt). */\n share: ClassifiedItem[];\n /** Effective level `none` / PERSONAL floor → suppressed; never leaves. */\n withhold: ClassifiedItem[];\n /** New / unmatched → queued for explicit human review. */\n pending: ClassifiedItem[];\n}\n\nexport interface ClassifyInput {\n preview: SharePreview;\n policy: SharingPolicy;\n /** `content_hash` values already pushed (status `shared`) — suppress re-share. */\n sharedHashes: ReadonlySet<string>;\n}\n\n/**\n * Route a {@link SharePreview} into share / withhold / pending buckets. Pure and\n * deterministic: same inputs always yield the same buckets. The payloads carried\n * here are the transformed ones from the preview, so anything routed to `share` is\n * already anonymized per policy.\n */\nexport function classify(input: ClassifyInput): ClassifyResult {\n const { preview, policy, sharedHashes } = input;\n const result: ClassifyResult = { share: [], withhold: [], pending: [] };\n\n // Track which original node ids are cleared to share, so edges can follow.\n const sharedNodeIds = new Set<string>();\n\n for (const entry of preview.nodes) {\n if (entry.payload === null) {\n // Dropped at level `none` (or PERSONAL floor): nothing leaves.\n result.withhold.push({ contentHash: '', kind: 'node', nodeId: entry.node.id, payload: null });\n continue;\n }\n const contentHash = shareHash('node', entry.payload);\n if (sharedHashes.has(contentHash)) continue; // already shared — no bucket\n\n const item: ClassifiedItem = { contentHash, kind: 'node', nodeId: entry.node.id, payload: entry.payload };\n if (isRemembered(policy, entry.node.id)) {\n result.share.push(item);\n sharedNodeIds.add(entry.node.id);\n } else {\n result.pending.push(item);\n }\n }\n\n // Edges already have endpoints remapped (and dangling ones dropped) by previewShare.\n // An edge is shareable only when both of its (original) endpoint nodes are cleared.\n // previewShare exposes only remapped ids on edges, so we re-derive surviving status\n // from the share bucket via the transformed payload: edges whose remapped endpoints\n // correspond to shared nodes go to `share`; the rest are withheld.\n const sharedRemappedIds = new Set<string>();\n for (const entry of preview.nodes) {\n if (entry.payload !== null && sharedNodeIds.has(entry.node.id)) {\n sharedRemappedIds.add(entry.payload.id);\n }\n }\n for (const e of preview.edges) {\n const payload = { sourceId: e.sourceId, targetId: e.targetId, relationship: e.relationship };\n const contentHash = shareHash('edge', payload);\n const bothShared = sharedRemappedIds.has(e.sourceId) && sharedRemappedIds.has(e.targetId);\n if (!bothShared) {\n result.withhold.push({ contentHash: '', kind: 'edge', payload });\n continue;\n }\n if (sharedHashes.has(contentHash)) continue;\n result.share.push({ contentHash, kind: 'edge', payload });\n }\n\n return result;\n}\n","/**\n * Outbound push client (2.11) — Cartograph's first egress path.\n *\n * Pushes consented, policy-transformed deltas to the central ingest endpoint over\n * bearer-auth HTTPS. It is the inverse of the inbound MCP auth in\n * `src/mcp/transports.ts`: instead of validating a bearer token, it *sends* one.\n *\n * Load-bearing privacy invariant: this function only ever sends the items it is\n * handed, which the caller (`sync push`) draws exclusively from `getApprovedShares()`\n * (status `approved`). The hard guards below additionally make a no-config / insecure\n * / empty send impossible. The bearer token is NEVER logged and NEVER placed in the\n * payload; error logging routes the URL through `stripSensitive`.\n *\n * Network is Node 20+ global `fetch`, injectable via `fetchImpl` so tests never hit\n * the network. Zero new dependencies.\n */\n\nimport { createHash } from 'node:crypto';\nimport type { CartographyConfig } from '../types.js';\nimport { stripSensitive, redactValue } from '../tools.js';\nimport { stableStringify } from '../diff.js';\n\n/** One item to push. `payload` is the already-policy-transformed (anonymized) projection. */\nexport interface PushItem {\n contentHash: string;\n kind: 'node' | 'edge';\n payload: unknown;\n}\n\nexport interface PushResult {\n /** Items the server acknowledged. */\n sent: number;\n /** Batches POSTed. */\n batches: number;\n /** Items in batches that ultimately failed (left for a later retry). */\n failed: number;\n /** content hashes the server acknowledged (caller flips these to `shared`). */\n sentHashes: string[];\n}\n\nexport interface PushOptions {\n /** Injectable fetch (tests). Defaults to Node global `fetch`. */\n fetchImpl?: typeof fetch;\n /** Items per batch. Defaults to `config.centralDb.batchSize ?? 100`. */\n batchSize?: number;\n /** Max retries per batch on 5xx / network errors. Default 4. */\n maxRetries?: number;\n /** Per-request timeout (ms). Default 15000. */\n timeoutMs?: number;\n /** Preview only — never networks. */\n dryRun?: boolean;\n /** Log sink (stderr by default); the token never reaches it. */\n log?: (line: string) => void;\n /** Sleep hook (tests inject a no-op to skip real backoff). */\n sleep?: (ms: number) => Promise<void>;\n}\n\n/** Wire-format version of the push envelope (the contract WS 2.12 ingests). */\nexport const PUSH_SCHEMA_VERSION = 1 as const;\n\nconst DEFAULT_BATCH = 100;\nconst DEFAULT_RETRIES = 4;\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\nfunction defaultLog(line: string): void {\n process.stderr.write(`[cartography-sync] ${line}\\n`);\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise((r) => setTimeout(r, ms));\n}\n\n/** Deterministic per-batch idempotency key: sha256 over the sorted content hashes. */\nfunction batchKey(items: PushItem[]): string {\n const hashes = items.map((i) => i.contentHash).sort();\n return createHash('sha256').update(stableStringify(hashes)).digest('hex');\n}\n\n/**\n * Push approved deltas. Returns counts and the acknowledged content hashes.\n *\n * Hard guards (the no-leak guarantee):\n * - missing `centralDb.url`/`token` → throws (nothing sent, fetch never called).\n * - non-`https:` URL → throws, unless `CARTOGRAPHY_ALLOW_INSECURE_SYNC === '1'`\n * (test-only, documented as unsafe; never the default).\n * - empty `items` → returns zeros without any network call.\n */\nexport async function pushDeltas(\n config: CartographyConfig,\n items: PushItem[],\n opts: PushOptions = {},\n): Promise<PushResult> {\n const central = config.centralDb;\n if (!central?.url || !central.token) {\n throw new Error('sync push: centralDb not configured (set centralDb.url + token)');\n }\n\n let parsed: URL;\n try {\n parsed = new URL(central.url);\n } catch {\n throw new Error('sync push: centralDb.url is not a valid URL');\n }\n const insecureAllowed = process.env.CARTOGRAPHY_ALLOW_INSECURE_SYNC === '1';\n if (parsed.protocol !== 'https:' && !insecureAllowed) {\n throw new Error(\n `sync push: refusing to send over insecure ${parsed.protocol}// — use https:// ` +\n `(or set CARTOGRAPHY_ALLOW_INSECURE_SYNC=1 for local testing only)`,\n );\n }\n\n const log = opts.log ?? defaultLog;\n const sleep = opts.sleep ?? defaultSleep;\n const fetchImpl = opts.fetchImpl ?? fetch;\n const batchSize = Math.max(1, opts.batchSize ?? central.batchSize ?? DEFAULT_BATCH);\n const maxRetries = Math.max(0, opts.maxRetries ?? DEFAULT_RETRIES);\n const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const safeUrl = stripSensitive(central.url);\n\n if (items.length === 0) {\n log('nothing to push (0 approved items)');\n return { sent: 0, batches: 0, failed: 0, sentHashes: [] };\n }\n\n // Defensive: strip any lingering credentials from the (already-anonymized) payload.\n const batches: PushItem[][] = [];\n for (let i = 0; i < items.length; i += batchSize) {\n batches.push(items.slice(i, i + batchSize).map((it) => ({ ...it, payload: redactValue(it.payload) })));\n }\n\n let sent = 0;\n let failed = 0;\n const sentHashes: string[] = [];\n\n for (const batch of batches) {\n const key = batchKey(batch);\n const body = JSON.stringify({\n schemaVersion: PUSH_SCHEMA_VERSION,\n ...(central.org ? { org: central.org } : {}),\n items: batch.map((b) => ({ contentHash: b.contentHash, kind: b.kind, payload: b.payload })),\n });\n\n if (opts.dryRun) {\n log(`dry-run: would POST ${batch.length} item(s) to ${safeUrl} (idempotency ${key.slice(0, 12)}…)`);\n sent += batch.length;\n sentHashes.push(...batch.map((b) => b.contentHash));\n continue;\n }\n\n let ok = false;\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const startedAt = Date.now();\n try {\n const res = await fetchImpl(central.url, {\n method: 'POST',\n headers: {\n 'authorization': `Bearer ${central.token}`,\n 'content-type': 'application/json',\n 'x-idempotency-key': key,\n },\n body,\n signal: controller.signal,\n });\n const elapsed = Date.now() - startedAt;\n if (res.ok) {\n log(`pushed ${batch.length} item(s) → ${safeUrl} [${res.status}] ${elapsed}ms (attempt ${attempt + 1})`);\n ok = true;\n break;\n }\n // 4xx is a client error — do not retry; surface and stop this batch.\n if (res.status >= 400 && res.status < 500) {\n log(`batch rejected → ${safeUrl} [${res.status}] (no retry)`);\n break;\n }\n // 5xx — retryable.\n log(`batch failed → ${safeUrl} [${res.status}] (attempt ${attempt + 1}/${maxRetries + 1})`);\n } catch (err) {\n // Network/timeout — retryable. Never echo headers or token.\n const msg = err instanceof Error ? err.message : String(err);\n log(`batch error → ${safeUrl}: ${msg.replace(/https?:\\/\\/[^\\s]+/g, (u) => stripSensitive(u))} (attempt ${attempt + 1}/${maxRetries + 1})`);\n } finally {\n clearTimeout(timer);\n }\n if (attempt < maxRetries) {\n // Capped exponential backoff with jitter.\n const base = Math.min(2 ** attempt * 250, 4000);\n await sleep(base + Math.floor(Math.random() * 100));\n }\n }\n\n if (ok) {\n sent += batch.length;\n sentHashes.push(...batch.map((b) => b.contentHash));\n } else {\n failed += batch.length;\n }\n }\n\n return { sent, batches: batches.length, failed, sentHashes };\n}\n","/**\n * Central-DB sync orchestration (2.11) — the post-scan enqueue glue.\n *\n * After a (manual or scheduled) scan, {@link runSyncClassify} resolves the\n * employee's persisted sharing policy (2.10), builds the policy-transformed payload\n * via `previewShare` (already anonymized/dropped — nothing raw for `anonymized`/\n * `none`), classifies it against the already-shared set, and enqueues the result\n * into `pending_shares`:\n *\n * - `share` → enqueued `approved` with `decided_by='rule'` (the policy *is* the\n * standing consent; never re-prompted).\n * - `pending` → enqueued `pending` for explicit review (nothing leaves until approved).\n * - `withhold`→ recorded `withheld` for the audit/`sync status` suppression count.\n *\n * Short-circuits to a no-op when `centralDb` is unconfigured, so an install without\n * sync configured never writes to the queue. All writes run in one transaction.\n */\n\nimport type { CartographyConfig } from '../types.js';\nimport type { CartographyDB } from '../db.js';\nimport { loadOrgKey } from '../orgkey.js';\nimport { previewShare } from '../sharing.js';\nimport { classify } from './classify.js';\n\nexport interface SyncClassifyResult {\n enqueued: number;\n autoShared: number;\n withheld: number;\n}\n\nexport interface SyncClassifyOptions {\n /** Override the org-key path / namespace (tests). Defaults to `config.organization`. */\n orgKey?: Buffer;\n}\n\n/**\n * Classify a session's topology against the persisted policy and enqueue the\n * result. Returns counts for `pending` (`enqueued`), auto-approved (`autoShared`),\n * and suppressed (`withheld`) items. No-op (all zeros) when sync is unconfigured.\n */\nexport function runSyncClassify(\n db: CartographyDB,\n sessionId: string,\n config: CartographyConfig,\n opts: SyncClassifyOptions = {},\n): SyncClassifyResult {\n if (!config.centralDb?.url) return { enqueued: 0, autoShared: 0, withheld: 0 };\n\n const orgKey = opts.orgKey ?? loadOrgKey({ organization: config.organization });\n const policy = db.getSharingPolicy();\n // persistReversal: an actual pre-send transform should persist the reversal map so\n // an admin can later invert anonymized tokens.\n const preview = previewShare(db, sessionId, orgKey, policy, { persistReversal: true });\n const sharedHashes = db.getSharedHashes();\n\n const { share, pending, withhold } = classify({ preview, policy, sharedHashes });\n\n const writeAll = db.rawConnection().transaction(() => {\n for (const item of share) {\n db.enqueuePending({\n contentHash: item.contentHash,\n sessionId,\n nodeId: item.nodeId,\n kind: item.kind,\n payload: item.payload,\n status: 'approved',\n decidedBy: 'rule',\n });\n }\n for (const item of pending) {\n db.enqueuePending({\n contentHash: item.contentHash,\n sessionId,\n nodeId: item.nodeId,\n kind: item.kind,\n payload: item.payload,\n status: 'pending',\n });\n }\n // Withheld items carry no stable content hash (payload may be null); record them\n // only when they have a real hash to key on (edges suppressed for unshared\n // endpoints). Dropped `none` nodes are intentionally not queued — they leave no\n // trace beyond `previewShare.droppedNodeIds`.\n for (const item of withhold) {\n if (!item.contentHash) continue;\n db.enqueuePending({\n contentHash: item.contentHash,\n sessionId,\n nodeId: item.nodeId,\n kind: item.kind,\n payload: item.payload,\n status: 'withheld',\n decidedBy: 'rule',\n });\n }\n });\n writeAll();\n\n return { enqueued: pending.length, autoShared: share.length, withheld: withhold.length };\n}\n\nexport type { ClassifiedItem, ClassifyResult, ClassifyInput } from './classify.js';\nexport { classify } from './classify.js';\nexport { shareHash } from './hash.js';\nexport { pushDeltas } from './push.js';\nexport type { PushItem, PushResult, PushOptions } from './push.js';\n","/**\n * Format-agnostic (de)serialization for host config files. JSON keeps 2-space\n * indentation; TOML uses smol-toml; YAML uses the `yaml` package. Empty or\n * whitespace-only input parses to an empty object so a fresh install starts clean.\n */\n\nimport { parse as parseToml, stringify as stringifyToml } from 'smol-toml';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { ConfigFormat } from './types.js';\n\nexport function parseConfig(text: string, format: ConfigFormat): Record<string, unknown> {\n if (!text.trim()) return {};\n try {\n switch (format) {\n case 'json':\n return JSON.parse(text) as Record<string, unknown>;\n case 'toml':\n return parseToml(text) as Record<string, unknown>;\n case 'yaml':\n return (parseYaml(text) as Record<string, unknown>) ?? {};\n }\n } catch (err) {\n // Fail loudly with an actionable message rather than crashing with a raw\n // parser stack or silently discarding (and then clobbering) the user's config.\n const detail = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse existing ${format.toUpperCase()} config: ${detail}`);\n }\n}\n\nexport function serializeConfig(obj: Record<string, unknown>, format: ConfigFormat): string {\n switch (format) {\n case 'json':\n return JSON.stringify(obj, null, 2) + '\\n';\n case 'toml':\n return stringifyToml(obj) + '\\n';\n case 'yaml':\n return stringifyYaml(obj);\n }\n}\n","/**\n * Idempotent deep-merge of plain objects. Used by client specs to splice a server\n * entry into an existing config without clobbering unrelated keys. Arrays and\n * scalars from `source` replace those in `target`; nested plain objects merge\n * recursively. `source` is never mutated; `target` is cloned.\n */\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\n\nexport function deepMerge<T extends Record<string, unknown>>(target: T, source: Record<string, unknown>): T {\n const out: Record<string, unknown> = { ...target };\n for (const [key, value] of Object.entries(source)) {\n const existing = out[key];\n if (isPlainObject(existing) && isPlainObject(value)) {\n out[key] = deepMerge(existing, value);\n } else {\n out[key] = value;\n }\n }\n return out as T;\n}\n","/**\n * Shared entry-shape helpers. Most hosts accept the Claude-Desktop-style object\n * (`command`/`args`/`env` for stdio, `url`/`type` for HTTP); specs that diverge\n * (VS Code `type`, Zed `source`, Codex TOML, …) compose or override these.\n */\n\nimport type { ServerEntry } from './types.js';\n\n/** The common `{ command, args, env }` | `{ type:'http', url }` server object. */\nexport function mcpServerObject(entry: ServerEntry): Record<string, unknown> {\n if (entry.url) {\n return { type: 'http', url: entry.url, ...(entry.env ? { env: entry.env } : {}) };\n }\n return {\n command: entry.command,\n args: entry.args ?? [],\n ...(entry.env ? { env: entry.env } : {}),\n };\n}\n","/**\n * The canonical Cartography MCP server entry every client spec receives. Kept in\n * one place so the npx invocation (and any future packaging change) is defined\n * exactly once and reused across all hosts.\n */\n\nimport type { ServerEntry } from './types.js';\n\nexport const PACKAGE_NAME = '@datasynx/agentic-ai-cartography';\nexport const MCP_BIN = 'cartography-mcp';\nexport const DEFAULT_SERVER_NAME = 'cartography';\n\nexport interface EntryOptions {\n /** `http` produces a `url` entry; otherwise stdio via npx. */\n transport?: 'stdio' | 'http';\n /** HTTP endpoint (used when transport === 'http'). */\n url?: string;\n /** Extra environment variables to inject. */\n env?: Record<string, string>;\n /** Extra package arguments appended after the bin name (e.g. `--db`, `--session`). */\n packageArgs?: string[];\n}\n\n/** Build the default server entry (stdio via `npx` unless an HTTP url is given). */\nexport function defaultServerEntry(opts: EntryOptions = {}): ServerEntry {\n if (opts.transport === 'http') {\n return { url: opts.url ?? 'http://127.0.0.1:3737/mcp', ...(opts.env ? { env: opts.env } : {}) };\n }\n const args = ['-y', '--package', PACKAGE_NAME, MCP_BIN, ...(opts.packageArgs ?? [])];\n return { command: 'npx', args, ...(opts.env ? { env: opts.env } : {}) };\n}\n","/**\n * Generic install planning/applying. `planInstall` is pure relative to a provided\n * context (reads the existing file, computes the merged result, never writes) so\n * it powers both `--dry-run` and the real write in `applyInstall`.\n */\n\nimport { mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { homedir } from 'node:os';\nimport { parseConfig, serializeConfig } from './format.js';\nimport { DEFAULT_SERVER_NAME } from './entry.js';\nimport type { ClientSpec, ConfigFormat, OsKind, ResolveContext, Scope, ServerEntry } from './types.js';\n\nexport interface InstallPlan {\n client: string;\n label: string;\n path: string;\n format: ConfigFormat;\n /** Existing file contents ('' when the file does not exist). */\n before: string;\n /** Contents that would be written. */\n after: string;\n fileExists: boolean;\n changed: boolean;\n note?: string;\n}\n\n/** Detect the current OS kind. */\nexport function currentOs(): OsKind {\n if (process.platform === 'win32') return 'win';\n if (process.platform === 'darwin') return 'mac';\n return 'linux';\n}\n\n/** Build a real resolve context from the running environment. */\nexport function defaultContext(scope: Scope): ResolveContext {\n return { scope, os: currentOs(), home: homedir(), cwd: process.cwd(), env: process.env };\n}\n\nexport interface PlanOptions {\n serverName?: string;\n entry: ServerEntry;\n}\n\n/** Compute what installing `entry` into `spec` would change. Reads the config file but never writes. */\nexport function planInstall(spec: ClientSpec, ctx: ResolveContext, opts: PlanOptions): InstallPlan {\n const path = spec.path(ctx);\n if (!path) {\n throw new Error(`${spec.label} does not support the \"${ctx.scope}\" scope.`);\n }\n const fileExists = existsSync(path);\n const before = fileExists ? readFileSync(path, 'utf8') : '';\n const existing = parseConfig(before, spec.format);\n const merged = spec.apply(existing, opts.serverName ?? DEFAULT_SERVER_NAME, opts.entry);\n const after = serializeConfig(merged, spec.format);\n return {\n client: spec.id,\n label: spec.label,\n path,\n format: spec.format,\n before,\n after,\n fileExists,\n changed: after !== before,\n ...(spec.note ? { note: spec.note } : {}),\n };\n}\n\n/** Write a plan's result to disk, creating parent directories as needed. */\nexport function applyInstall(plan: InstallPlan): void {\n mkdirSync(dirname(plan.path), { recursive: true });\n writeFileSync(plan.path, plan.after, 'utf8');\n}\n\n/** A minimal line-oriented diff for `--dry-run` output. */\nexport function renderDiff(before: string, after: string): string {\n if (before === after) return ' (no changes)';\n const b = before.length ? before.split('\\n') : [];\n const a = after.split('\\n');\n const out: string[] = [];\n const max = Math.max(b.length, a.length);\n for (let i = 0; i < max; i++) {\n if (b[i] === a[i]) {\n if (a[i] !== undefined) out.push(` ${a[i]}`);\n } else {\n if (b[i] !== undefined) out.push(`- ${b[i]}`);\n if (a[i] !== undefined) out.push(`+ ${a[i]}`);\n }\n }\n return out.join('\\n');\n}\n","/**\n * Registry of supported MCP hosts. The merge engine and CLI are generic; every\n * host-specific detail (config path, format, schema key) lives in a `ClientSpec`\n * here. New hosts are added by appending a spec — no engine changes required.\n */\n\nimport { join } from 'node:path';\nimport { deepMerge } from './merge.js';\nimport { mcpServerObject } from './shapes.js';\nimport type { ClientSpec, ResolveContext, ServerEntry } from './types.js';\n\n/** Helper: a host that stores stdio/http servers under a top-level JSON key. */\nfunction jsonKeyedClient(args: {\n id: string;\n label: string;\n key: string;\n globalPath: ClientSpec['path'];\n projectPath?: ClientSpec['path'];\n note?: string;\n}): ClientSpec {\n return {\n id: args.id,\n label: args.label,\n format: 'json',\n note: args.note,\n path: (ctx) => (ctx.scope === 'project' ? args.projectPath?.(ctx) : args.globalPath(ctx)),\n apply: (existing, name, entry) =>\n deepMerge(existing, { [args.key]: { [name]: mcpServerObject(entry) } }),\n };\n}\n\n// ── Claude Code (reference JSON host) ────────────────────────────────────────\nconst claudeCode = jsonKeyedClient({\n id: 'claude-code',\n label: 'Claude Code',\n key: 'mcpServers',\n globalPath: (ctx) => join(ctx.home, '.claude.json'),\n projectPath: (ctx) => join(ctx.cwd, '.mcp.json'),\n});\n\n// ── Cursor ───────────────────────────────────────────────────────────────────\nconst cursor = jsonKeyedClient({\n id: 'cursor',\n label: 'Cursor',\n key: 'mcpServers',\n globalPath: (ctx) => join(ctx.home, '.cursor', 'mcp.json'),\n projectPath: (ctx) => join(ctx.cwd, '.cursor', 'mcp.json'),\n});\n\n// ── VS Code / GitHub Copilot ─────────────────────────────────────────────────\n// Diverges from the norm: the key is `servers` (not `mcpServers`) and stdio\n// entries carry an explicit `type: \"stdio\"`.\nfunction vscodeServerObject(entry: ServerEntry): Record<string, unknown> {\n if (entry.url) return { type: 'http', url: entry.url, ...(entry.env ? { env: entry.env } : {}) };\n return { type: 'stdio', command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n}\n\n/** VS Code user-profile directory per OS. */\nfunction vscodeUserDir(ctx: ResolveContext): string {\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Code', 'User');\n if (ctx.os === 'mac') return join(ctx.home, 'Library', 'Application Support', 'Code', 'User');\n return join(ctx.home, '.config', 'Code', 'User');\n}\n\nconst vscode: ClientSpec = {\n id: 'vscode',\n label: 'VS Code (Copilot)',\n format: 'json',\n note: 'Uses the `servers` key (not `mcpServers`) — the most common copy-paste mistake.',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.vscode', 'mcp.json') : join(vscodeUserDir(ctx), 'mcp.json')),\n apply: (existing, name, entry) => deepMerge(existing, { servers: { [name]: vscodeServerObject(entry) } }),\n};\n\n// ── OpenAI Codex CLI ─────────────────────────────────────────────────────────\n// TOML, table form `[mcp_servers.<name>]` (NOT `[mcp.servers.\"name\"]`).\nconst codex: ClientSpec = {\n id: 'codex',\n label: 'Codex CLI',\n format: 'toml',\n note: 'Project scope only loads in \"trusted\" projects.',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.codex', 'config.toml') : join(ctx.home, '.codex', 'config.toml')),\n apply: (existing, name, entry) => deepMerge(existing, { mcp_servers: { [name]: mcpServerObject(entry) } }),\n};\n\n// ── Windsurf (Codeium) ───────────────────────────────────────────────────────\n// Global-only; path is identical across OSes (USERPROFILE === home on Windows).\nconst windsurf = jsonKeyedClient({\n id: 'windsurf',\n label: 'Windsurf',\n key: 'mcpServers',\n globalPath: (ctx) => join(ctx.home, '.codeium', 'windsurf', 'mcp_config.json'),\n});\n\n// ── Cline & Roo Code (VS Code globalStorage hosts) ───────────────────────────\n/** `<VS Code user dir>/globalStorage/<extensionId>/settings/cline_mcp_settings.json`. */\nfunction codeGlobalStorage(ctx: ResolveContext, extensionId: string): string {\n return join(vscodeUserDir(ctx), 'globalStorage', extensionId, 'settings', 'cline_mcp_settings.json');\n}\n\nconst cline: ClientSpec = {\n id: 'cline',\n label: 'Cline',\n format: 'json',\n path: (ctx) => (ctx.scope === 'project' ? undefined : codeGlobalStorage(ctx, 'saoudrizwan.claude-dev')),\n // Cline augments the standard object with its own auto-approve/disable flags.\n apply: (existing, name, entry) =>\n deepMerge(existing, { mcpServers: { [name]: { ...mcpServerObject(entry), alwaysAllow: [], disabled: false } } }),\n};\n\nconst roo: ClientSpec = {\n id: 'roo',\n label: 'Roo Code',\n format: 'json',\n note: 'Project .roo/mcp.json takes precedence over the global settings.',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.roo', 'mcp.json') : codeGlobalStorage(ctx, 'rooveterinaryinc.roo-cline')),\n apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } }),\n};\n\n// ── Zed ──────────────────────────────────────────────────────────────────────\n// Key is `context_servers`; manual entries require `\"source\": \"custom\"`.\nconst zed: ClientSpec = {\n id: 'zed',\n label: 'Zed',\n format: 'json',\n note: 'Manual servers need \"source\": \"custom\"; remote uses an mcp-remote bridge.',\n path: (ctx) => {\n if (ctx.scope === 'project') return join(ctx.cwd, '.zed', 'settings.json');\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Zed', 'settings.json');\n return join(ctx.home, '.config', 'zed', 'settings.json');\n },\n apply: (existing, name, entry) => {\n const inner = entry.url\n ? { source: 'custom', url: entry.url }\n : { source: 'custom', command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n return deepMerge(existing, { context_servers: { [name]: inner } });\n },\n};\n\n// ── JetBrains AI Assistant / Junie ───────────────────────────────────────────\nconst junie: ClientSpec = {\n id: 'junie',\n label: 'JetBrains / Junie',\n format: 'json',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.junie', 'mcp', 'mcp.json') : join(ctx.home, '.junie', 'mcp', 'mcp.json')),\n apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } }),\n};\n\n// ── Gemini CLI ───────────────────────────────────────────────────────────────\n// stdio: command/args/env; HTTP uses the `httpUrl` key (not `url`).\nconst gemini: ClientSpec = {\n id: 'gemini',\n label: 'Gemini CLI',\n format: 'json',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, '.gemini', 'settings.json') : join(ctx.home, '.gemini', 'settings.json')),\n apply: (existing, name, entry) => {\n const inner = entry.url\n ? { httpUrl: entry.url, ...(entry.env ? { env: entry.env } : {}) }\n : mcpServerObject(entry);\n return deepMerge(existing, { mcpServers: { [name]: inner } });\n },\n};\n\n// ── Goose (Block) ────────────────────────────────────────────────────────────\n// YAML under `extensions:`. Built-ins (`type: builtin`) are preserved by the merge.\nconst goose: ClientSpec = {\n id: 'goose',\n label: 'Goose',\n format: 'yaml',\n note: 'Verify the extension shape against current Goose docs; built-ins are left untouched.',\n path: (ctx) => {\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Block', 'goose', 'config', 'config.yaml');\n return join(ctx.home, '.config', 'goose', 'config.yaml');\n },\n apply: (existing, name, entry) => {\n const inner = entry.url\n ? { name, type: 'streamable_http', enabled: true, uri: entry.url, ...(entry.env ? { env: entry.env } : {}) }\n : { name, type: 'stdio', enabled: true, command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n return deepMerge(existing, { extensions: { [name]: inner } });\n },\n};\n\n// ── OpenHands (All-Hands-AI) ─────────────────────────────────────────────────\n// `[mcp]` holds arrays (stdio_servers / shttp_servers / sse_servers), not a keyed\n// object — so entries are appended/replaced by identity, not deep-merged.\nfunction isObj(v: unknown): v is Record<string, unknown> {\n return typeof v === 'object' && v !== null && !Array.isArray(v);\n}\nconst openhands: ClientSpec = {\n id: 'openhands',\n label: 'OpenHands',\n format: 'toml',\n note: 'SHTTP is preferred; SSE is legacy. Only api_key is supported (no arbitrary headers).',\n path: (ctx) => (ctx.scope === 'project' ? join(ctx.cwd, 'config.toml') : join(ctx.home, '.openhands', 'config.toml')),\n apply: (existing, name, entry) => {\n const mcp = isObj(existing.mcp) ? { ...existing.mcp } : {};\n const key = entry.url ? 'shttp_servers' : 'stdio_servers';\n const arr = Array.isArray(mcp[key]) ? [...(mcp[key] as Record<string, unknown>[])] : [];\n const item: Record<string, unknown> = entry.url\n ? { url: entry.url, ...(entry.env ? { env: entry.env } : {}) }\n : { name, command: entry.command, args: entry.args ?? [], ...(entry.env ? { env: entry.env } : {}) };\n const matches = (s: Record<string, unknown>) => (entry.url ? s.url === entry.url : s.name === name);\n const idx = arr.findIndex(matches);\n if (idx >= 0) arr[idx] = item;\n else arr.push(item);\n mcp[key] = arr;\n return { ...existing, mcp };\n },\n};\n\n// ── Claude Desktop ───────────────────────────────────────────────────────────\n// JSON `mcpServers`. (One-click installs are also available via the .mcpb bundle.)\nconst claudeDesktop: ClientSpec = {\n id: 'claude-desktop',\n label: 'Claude Desktop',\n format: 'json',\n note: 'One-click install is also available via the .mcpb bundle (npm run build:mcpb).',\n path: (ctx) => {\n if (ctx.scope === 'project') return undefined;\n if (ctx.os === 'win') return join(ctx.env.APPDATA ?? join(ctx.home, 'AppData', 'Roaming'), 'Claude', 'claude_desktop_config.json');\n if (ctx.os === 'mac') return join(ctx.home, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json');\n return join(ctx.home, '.config', 'Claude', 'claude_desktop_config.json');\n },\n apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } }),\n};\n\n/** All registered clients, in display order. Extended by later milestones. */\nexport const CLIENTS: ClientSpec[] = [\n claudeCode, cursor, vscode, codex, windsurf,\n cline, roo, zed, junie, gemini,\n goose, openhands, claudeDesktop,\n];\n\nexport function getClient(id: string): ClientSpec | undefined {\n return CLIENTS.find((c) => c.id === id);\n}\n\nexport function listClients(): ReadonlyArray<Pick<ClientSpec, 'id' | 'label' | 'format' | 'note'>> {\n return CLIENTS.map(({ id, label, format, note }) => ({ id, label, format, note }));\n}\n","/**\n * One-click install deeplinks for hosts that support them. Cursor expects a\n * **Base64**-encoded server config; VS Code expects **URL-encoded** JSON — mixing\n * the two encodings is a classic mistake, so each has its own helper.\n */\n\nimport { mcpServerObject } from './shapes.js';\nimport type { ServerEntry } from './types.js';\n\n/** `cursor://…/mcp/install?name=<name>&config=<base64 JSON of the server config>`. */\nexport function cursorDeeplink(name: string, entry: ServerEntry): string {\n const config = Buffer.from(JSON.stringify(mcpServerObject(entry))).toString('base64');\n const params = new URLSearchParams({ name, config });\n return `cursor://anysphere.cursor-deeplink/mcp/install?${params.toString()}`;\n}\n\nexport interface VscodeDeeplinkOptions {\n /** Target VS Code Insiders (`vscode-insiders://`). */\n insiders?: boolean;\n}\n\n/** `vscode://mcp/install?<URL-encoded JSON>` where the JSON is `{ name, ...serverConfig }`. */\nexport function vscodeDeeplink(name: string, entry: ServerEntry, opts: VscodeDeeplinkOptions = {}): string {\n const scheme = opts.insiders ? 'vscode-insiders' : 'vscode';\n const payload = encodeURIComponent(JSON.stringify({ name, ...mcpServerObject(entry) }));\n return `${scheme}://mcp/install?${payload}`;\n}\n\n/** A `code --add-mcp '<json>'` CLI one-liner (alternative to the deeplink). */\nexport function codeAddMcpCommand(name: string, entry: ServerEntry): string {\n return `code --add-mcp '${JSON.stringify({ name, ...mcpServerObject(entry) })}'`;\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,6BAqON,SAAS,mBAAmB,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQ1D,UAAU,SAAS,YAAY,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAW1C,UAAU,0MAA0M,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yCA+BnL,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAOtB,SAAS,mBAAmB,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,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;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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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,UAAU,OAAkB,OAA0B;AACpE,QAAM,MAAM;AAAA,IACV,OAAO;AAAA,MACL,UAAU;AAAA,MACV,MAAM;AAAA,MACN,OAAO;AAAA,MACP,UAAU,EAAE,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE;AAAA,MACjD,OAAO,OAAO,YAAY,MAAM,IAAI,OAAK,CAAC,EAAE,IAAI;AAAA,QAC9C,OAAO,EAAE;AAAA,QACT,UAAU;AAAA,UACR,MAAM,EAAE;AAAA,UAAM,OAAO,UAAU,EAAE,IAAI;AAAA,UACrC,YAAY,EAAE;AAAA,UAAY,eAAe,EAAE;AAAA,UAC3C,cAAc,EAAE;AAAA,UAAc,MAAM,EAAE;AAAA,UAAM,UAAU,EAAE;AAAA,QAC1D;AAAA,MACF,CAAC,CAAC,CAAC;AAAA,MACH,OAAO,MAAM,IAAI,QAAM;AAAA,QACrB,QAAQ,EAAE;AAAA,QAAU,QAAQ,EAAE;AAAA,QAC9B,UAAU,EAAE;AAAA,QACZ,UAAU,EAAE,YAAY,EAAE,YAAY,UAAU,EAAE,SAAS;AAAA,MAC7D,EAAE;AAAA,IACJ;AAAA,EACF;AACA,SAAO,KAAK,UAAU,KAAK,MAAM,CAAC;AACpC;AASA,SAAS,SAAS,GAA4B;AAC5C,MAAI,IAAI,OAAO,CAAC;AAChB,MAAI,WAAW,KAAK,CAAC,EAAG,KAAI,IAAI,CAAC;AACjC,SAAO,SAAS,KAAK,CAAC,IAAI,IAAI,EAAE,QAAQ,MAAM,IAAI,CAAC,MAAM;AAC3D;AAOO,SAAS,cAAc,SAA+B;AAC3D,QAAM,OAAO,CAAC,uCAAuC;AACrD,aAAW,KAAK,QAAQ,cAAc;AACpC,SAAK,KAAK,CAAC,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,KAAK,GAAG,CAAC;AAAA,EAChG;AACA,aAAW,KAAK,QAAQ,aAAa;AACnC,SAAK,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,QAAQ,EAAE,KAAK,GAAG,CAAC;AAAA,EAC9F;AACA,SAAO,KAAK,KAAK,IAAI,IAAI;AAC3B;AAGO,SAAS,kBAAkB,SAA+B;AAC/D,SAAO,KAAK,UAAU;AAAA,IACpB,cAAc,QAAQ;AAAA,IACtB,aAAa,QAAQ;AAAA,IACrB,cAAc,QAAQ;AAAA,EACxB,GAAG,MAAM,CAAC;AACZ;AAIA,IAAM,gBAAwC,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,EAAE;AAOjF,SAAS,uBAAuB,QAA0B,QAAiD;AAChH,MAAI,WAAW,OAAQ,QAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;AAE5D,MAAI,WAAW,YAAY;AACzB,UAAM,WAAW,OAAO,UAAU,OAAO,QAAQ,GAAG,OAAO,KAAK;AAChE,UAAM,MAAgB;AAAA,MACpB,uBAAkB,OAAO,WAAW,KAAK,OAAO,cAAc;AAAA,MAC9D;AAAA,MACA,eAAe,OAAO,OAAO,YAAY,CAAC,oBAAiB,QAAQ;AAAA,MACnE;AAAA,MACA;AAAA,MAAwB;AAAA,MACxB,cAAc,OAAO,OAAO,MAAM;AAAA,MAClC,cAAc,OAAO,OAAO,MAAM;AAAA,MAClC,sBAAsB,OAAO,OAAO,aAAa;AAAA,MACjD,aAAa,OAAO,OAAO,KAAK;AAAA,MAChC;AAAA,MACA;AAAA,MAAkC;AAAA,MAClC,GAAI,CAAC,YAAY,QAAQ,UAAU,KAAK,EAAY;AAAA,QAClD,CAAC,MAAM,KAAK,CAAC,MAAM,OAAO,WAAW,CAAC,EAAE,MAAM,MAAM,OAAO,WAAW,CAAC,EAAE,MAAM;AAAA,MACjF;AAAA,IACF;AACA,QAAI,OAAO,KAAK,WAAW,GAAG;AAC5B,UAAI,KAAK,IAAI,4BAAuB;AAAA,IACtC,OAAO;AACL,UAAI,KAAK,IAAI,SAAS;AACtB,iBAAW,KAAK,CAAC,GAAG,OAAO,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,cAAc,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,CAAC,GAAG;AACtG,YAAI,KAAK,IAAI,QAAQ,EAAE,QAAQ,KAAK,EAAE,OAAO,WAAM,EAAE,KAAK,IAAI,GAAG,EAAE,QAAQ,IAAI,CAAC,OAAO,OAAO,EAAE,IAAI,CAAC;AAAA,MACvG;AAAA,IACF;AACA,WAAO,IAAI,KAAK,IAAI;AAAA,EACtB;AAGA,QAAM,QAAkB;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,MAAI,OAAO,KAAK,WAAW,GAAG;AAC5B,UAAM,KAAK,mCAA8B;AACzC,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAGA,QAAM,SAAS,CAAC,MAAsB,EAAE,QAAQ,cAAc,GAAG;AACjE,MAAI,IAAI;AACR,aAAW,KAAK,CAAC,GAAG,OAAO,IAAI,EAAE,KAAK,CAAC,GAAG,MAAM,cAAc,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,CAAC,GAAG;AACtG,UAAM,MAAM,IAAI,GAAG;AACnB,UAAM,KAAK,KAAK,GAAG,KAAK,OAAO,EAAE,OAAO,CAAC,KAAK,OAAO,EAAE,KAAK,CAAC,KAAK,EAAE,QAAQ,MAAM,SAAS,EAAE,QAAQ,EAAE;AAAA,EACzG;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAIO,SAAS,UACd,IACA,WACA,WACA,UAAoB,CAAC,WAAW,QAAQ,QAAQ,QAAQ,OAAO,WAAW,GACpE;AACN,YAAU,WAAW,EAAE,WAAW,KAAK,CAAC;AAExC,QAAM,QAAQ,GAAG,SAAS,SAAS;AACnC,QAAM,QAAQ,GAAG,SAAS,SAAS;AAGnC,QAAM,UAAUC,MAAK,WAAW,4BAA4B;AAC5D,gBAAc,SAAS,UAAU,OAAO,KAAK,CAAC;AAE9C,MAAI,QAAQ,SAAS,SAAS,GAAG;AAC/B,kBAAcA,MAAK,WAAW,kBAAkB,GAAG,wBAAwB,OAAO,KAAK,CAAC;AACxF,kBAAcA,MAAK,WAAW,sBAAsB,GAAG,0BAA0B,OAAO,KAAK,CAAC;AAAA,EAChG;AAEA,MAAI,QAAQ,SAAS,MAAM,GAAG;AAC5B,kBAAcA,MAAK,WAAW,cAAc,GAAG,WAAW,IAAI,SAAS,CAAC;AAAA,EAC1E;AAEA,MAAI,QAAQ,SAAS,MAAM,GAAG;AAC5B,kBAAcA,MAAK,WAAW,mBAAmB,GAAG,oBAAoB,OAAO,KAAK,CAAC;AAAA,EACvF;AAEA,MAAI,QAAQ,SAAS,MAAM,KAAK,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,WAAW,GAAG;AACxF,kBAAcA,MAAK,WAAW,gBAAgB,GAAG,mBAAmB,OAAO,KAAK,CAAC;AAAA,EACnF;AAEA,MAAI,QAAQ,SAAS,MAAM,GAAG;AAC5B,UAAM,UAAU,GAAG,gBAAgB,SAAS;AAC5C,kBAAcA,MAAK,WAAW,oBAAoB,GAAG,cAAc,OAAO,CAAC;AAC3E,kBAAcA,MAAK,WAAW,mBAAmB,GAAG,kBAAkB,OAAO,CAAC;AAAA,EAChF;AACF;;;AI7mFA,IAAM,WAAW;AAGV,SAAS,qBAAqB,QAAkC;AACrE,QAAM,WAAW,OAAO,UAAU,OAAO,QAAQ,GAAG,OAAO,KAAK;AAChE,QAAM,QAAkB;AAAA,IACtB,eAAe,OAAO,WAAW,KAAK,OAAO,cAAc,WAAM,OAAO,OAAO,YAAY,CAAC,WAAW,QAAQ;AAAA,IAC/G,aAAa,OAAO,OAAO,MAAM,YAAY,OAAO,OAAO,MAAM,YAAY,OAAO,OAAO,aAAa,YAAY,OAAO,OAAO,KAAK;AAAA,IACvI;AAAA,IACA;AAAA,IACA,GAAI,CAAC,YAAY,QAAQ,UAAU,KAAK,EAAY;AAAA,MAClD,CAAC,MAAM,OAAO,CAAC,KAAK,OAAO,WAAW,CAAC,EAAE,MAAM,aAAa,OAAO,WAAW,CAAC,EAAE,MAAM;AAAA,IACzF;AAAA,EACF;AAEA,MAAI,OAAO,KAAK,WAAW,GAAG;AAC5B,UAAM,KAAK,IAAI,4BAAuB;AACtC,WAAO,MAAM,KAAK,IAAI;AAAA,EACxB;AAEA,QAAM,KAAK,IAAI,SAAS,OAAO,KAAK,MAAM,IAAI;AAC9C,aAAW,KAAK,OAAO,MAAM;AAC3B,UAAM,KAAK,aAAQ,EAAE,QAAQ,KAAK,EAAE,OAAO,WAAM,EAAE,KAAK,EAAE;AAC1D,UAAM,QAAQ,EAAE,QAAQ,MAAM,GAAG,QAAQ;AACzC,eAAW,MAAM,MAAO,OAAM,KAAK,SAAS,EAAE,EAAE;AAChD,QAAI,EAAE,QAAQ,SAAS,SAAU,OAAM,KAAK,iBAAY,EAAE,QAAQ,SAAS,QAAQ,OAAO;AAAA,EAC5F;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;;;ACzBA,SAAS,gBAAAC,qBAAoB;AAC7B,SAAS,eAAe;AAuCxB,SAAS,aAAa,MAAwB;AAC5C,QAAM,MAAgB,CAAC;AACvB,MAAI,MAAM;AACV,MAAI,WAAW;AACf,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACpC,UAAM,KAAK,KAAK,CAAC;AACjB,QAAI,UAAU;AACZ,UAAI,OAAO,KAAK;AACd,YAAI,KAAK,IAAI,CAAC,MAAM,KAAK;AAAE,iBAAO;AAAK;AAAA,QAAK,OAAO;AAAE,qBAAW;AAAA,QAAO;AAAA,MACzE,MAAO,QAAO;AAAA,IAChB,WAAW,OAAO,KAAK;AACrB,iBAAW;AAAA,IACb,WAAW,OAAO,KAAK;AACrB,UAAI,KAAK,GAAG;AAAG,YAAM;AAAA,IACvB,MAAO,QAAO;AAAA,EAChB;AACA,MAAI,KAAK,GAAG;AACZ,SAAO,IAAI,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAChC;AAQO,SAAS,aAAa,MAA4B;AACvD,QAAM,QAAQ,KAAK,MAAM,OAAO,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC;AACnE,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,QAAM,SAAS,aAAa,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC;AAChE,QAAM,MAAM,CAAC,SAAyB,OAAO,QAAQ,IAAI;AACzD,QAAM,QAAQ,IAAI,QAAQ;AAC1B,QAAM,SAAS,IAAI,OAAO;AAC1B,QAAM,UAAU,IAAI,QAAQ;AAC5B,QAAM,YAAY,IAAI,UAAU;AAChC,QAAM,UAAU,IAAI,QAAQ;AAC5B,QAAM,UAAU,IAAI,QAAQ;AAC5B,MAAI,QAAQ,GAAG;AACb,YAAQ,mDAAmD;AAC3D,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,UAAwB,CAAC;AAC/B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,IAAI,aAAa,MAAM,CAAC,CAAC;AAC/B,UAAM,SAAS,EAAE,KAAK;AACtB,QAAI,CAAC,QAAQ;AAAE,cAAQ,iBAAiB,IAAI,CAAC,yBAAyB;AAAG;AAAA,IAAU;AAEnF,UAAM,MAAkB,EAAE,OAAO;AACjC,QAAI,UAAU,KAAK,EAAE,MAAM,EAAG,KAAI,QAAQ,EAAE,MAAM;AAElD,UAAM,YAAY,WAAW,IAAI,EAAE,OAAO,IAAI;AAC9C,QAAI,WAAW;AACb,YAAM,SAAS,gBAAgB,UAAU;AAAA,QACvC,QAAQ,OAAO,SAAS;AAAA,QACxB,UAAU,aAAa,IAAI,EAAE,SAAS,IAAI;AAAA,QAC1C,QAAQ,WAAW,IAAI,EAAE,OAAO,IAAI;AAAA,QACpC,GAAI,WAAW,KAAK,EAAE,OAAO,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MAC7D,CAAC;AACD,UAAI,CAAC,OAAO,SAAS;AACnB,gBAAQ,iBAAiB,IAAI,CAAC,gCAAgC;AAE9D,YAAI,CAAC,IAAI,MAAO;AAAA,MAClB,OAAO;AACL,YAAI,OAAO,OAAO;AAAA,MACpB;AAAA,IACF;AACA,QAAI,IAAI,SAAS,IAAI,KAAM,SAAQ,KAAK,GAAG;AAAA,EAC7C;AACA,SAAO;AACT;AAGO,IAAM,gBAAN,MAA0C;AAAA,EAE/C,YAA6B,MAA4B;AAA5B;AAC3B,UAAM,OAAO,KAAK,SAAS,MAAM,OAAO,EAAE,IAAI,KAAK,KAAK;AACxD,SAAK,KAAK,OAAO,IAAI;AAAA,EACvB;AAAA,EAJS;AAAA,EAMT,MAAM,QAA0C;AAC9C,UAAM,OAAOC,cAAa,QAAQ,KAAK,KAAK,QAAQ,GAAG,OAAO;AAC9D,UAAM,UAAU,aAAa,IAAI;AACjC,UAAM,QAAQ,KAAK,KAAK,SAAS;AACjC,UAAM,MAAM,oBAAI,IAAwB;AAExC,QAAI,UAAU,UAAU;AACtB,iBAAW,OAAO,QAAS,KAAI,IAAI,IAAI,QAAQ,GAAG;AAClD,aAAO;AAAA,IACT;AAGA,QAAI,CAAC,KAAK,KAAK,MAAM,CAAC,KAAK,KAAK,WAAW;AACzC,cAAQ,oBAAoB,KAAK,mDAAmD;AACpF,iBAAW,OAAO,QAAS,KAAI,IAAI,IAAI,QAAQ,GAAG;AAClD,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,KAAK,KAAK,GAAG,SAAS,KAAK,KAAK,SAAS;AACvD,UAAM,QAAQ,oBAAI,IAAoB;AACtC,eAAW,KAAK,OAAO;AACrB,UAAI,UAAU,OAAQ,OAAM,IAAI,EAAE,MAAM,EAAE,EAAE;AAAA,UACvC,YAAW,KAAK,EAAE,KAAM,OAAM,IAAI,GAAG,EAAE,EAAE;AAAA,IAChD;AACA,eAAW,OAAO,SAAS;AACzB,YAAM,WAAW,MAAM,IAAI,IAAI,MAAM;AACrC,UAAI,IAAI,YAAY,IAAI,QAAQ,EAAE,GAAG,KAAK,QAAQ,YAAY,IAAI,OAAO,CAAC;AAAA,IAC5E;AACA,WAAO;AAAA,EACT;AACF;AAOA,eAAsB,YAAY,IAAmB,WAAmB,QAA2C;AACjH,QAAM,UAAU,MAAM,OAAO,MAAM;AACnC,MAAI,UAAU;AACd,QAAM,eAAyB,CAAC;AAChC,aAAW,CAAC,QAAQ,GAAG,KAAK,SAAS;AACnC,UAAM,KAAK,GAAG,sBAAsB,WAAW,QAAQ;AAAA,MACrD,OAAO,IAAI,SAAS;AAAA,MACpB,MAAM,IAAI,QAAQ;AAAA,IACpB,CAAC;AACD,QAAI,GAAI;AAAA,QAAgB,cAAa,KAAK,MAAM;AAAA,EAClD;AACA,SAAO,EAAE,QAAQ,OAAO,IAAI,OAAO,QAAQ,MAAM,SAAS,WAAW,aAAa,QAAQ,aAAa;AACzG;;;AtB5JA,SAAS,gBAAAC,eAAc,cAAAC,aAAY,iBAAAC,sBAAqB;AACxD,SAAS,WAAAC,UAAS,WAAAC,gBAAe;AACjC,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;;;AuBThC,SAAS,cAAc,SAAyB;AAC9C,UAAQ,QAAQ,MAAM,KAAK,KAAK,CAAC,GAAG;AACtC;AAMA,SAAS,UAAU,SAAiB,IAAqB;AAEvD,MAAI,KAAK;AACT,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,IAAI,QAAQ,CAAC;AACnB,QAAI,MAAM,KAAK;AACb,UAAI,QAAQ,IAAI,CAAC,MAAM,KAAK;AAAE,cAAM;AAAM;AAAA,MAAK,MAC1C,OAAM;AAAA,IACb,OAAO;AACL,YAAM,EAAE,QAAQ,sBAAsB,MAAM;AAAA,IAC9C;AAAA,EACF;AACA,QAAM;AACN,SAAO,IAAI,OAAO,EAAE,EAAE,KAAK,EAAE;AAC/B;AAQO,SAAS,oBAAoB,QAAgB,QAAqC;AACvF,QAAMC,WAAU,OAAO,UACpB,OAAO,CAAC,MAAM,EAAE,YAAY,OAAO,EAAE,YAAY,QAAQ,UAAU,EAAE,SAAS,MAAM,CAAC,EACrF;AAAA,IAAK,CAAC,GAAG,MACR,cAAc,EAAE,OAAO,IAAI,cAAc,EAAE,OAAO,KAClD,EAAE,QAAQ,SAAS,EAAE,QAAQ;AAAA,EAC/B;AACF,SAAOA,SAAQ,SAASA,SAAQ,CAAC,EAAE,QAAQ,OAAO;AACpD;AAGA,SAAS,UAAU,MAA+B;AAChD,QAAM,MAAgB,CAAC,KAAK,IAAI,KAAK,IAAI;AACzC,QAAM,OAAO,KAAK,YAAY,CAAC;AAC/B,aAAW,KAAK,CAAC,QAAQ,OAAO,QAAQ,GAAY;AAClD,UAAM,IAAK,KAAiC,CAAC;AAC7C,QAAI,OAAO,MAAM,SAAU,KAAI,KAAK,CAAC;AAAA,EACvC;AACA,MAAI,KAAK,OAAQ,KAAI,KAAK,KAAK,MAAM;AACrC,SAAO;AACT;AAOO,SAAS,sBAAsB,MAAqB,QAAqC;AAC9F,MAAI,UAAU,IAAI,EAAE,KAAK,CAAC,MAAM,eAAe,CAAC,CAAC,EAAG,QAAO;AAC3D,SAAO,oBAAoB,KAAK,IAAI,MAAM;AAC5C;AAYO,SAAS,kBACd,MACA,OACA,QACA,IACsB;AACtB,MAAI,UAAU,OAAQ,QAAO;AAC7B,MAAI,UAAU,OAAQ,QAAO,EAAE,GAAG,MAAM,UAAU,EAAE,GAAI,KAAK,YAAY,CAAC,EAAG,GAAG,MAAM,CAAC,GAAI,KAAK,QAAQ,CAAC,CAAE,EAAE;AAC7G,SAAO;AAAA,IACL,GAAG;AAAA,IACH,IAAI,mBAAmB,KAAK,IAAI,QAAQ,EAAE;AAAA,IAC1C,MAAM,mBAAmB,KAAK,MAAM,QAAQ,EAAE;AAAA,IAC9C,UAAU,aAAa,KAAK,YAAY,CAAC,GAAG,QAAQ,EAAE;AAAA,IACtD,OAAO,KAAK,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM,mBAAmB,GAAG,QAAQ,EAAE,CAAC;AAAA,EACtE;AACF;AA6BO,SAAS,aACd,IACA,WACA,QACA,QACA,OAAsC,CAAC,GACzB;AACd,QAAM,UAAU,KAAK,kBAAkB,KAAK;AAC5C,QAAM,QAAmB,GAAG,SAAS,SAAS;AAC9C,QAAM,QAAmB,GAAG,SAAS,SAAS;AAE9C,QAAM,UAA+B,CAAC;AACtC,QAAM,QAAQ,oBAAI,IAA2B;AAC7C,QAAM,iBAA2B,CAAC;AAElC,aAAW,QAAQ,OAAO;AACxB,UAAM,QAAQ,sBAAsB,MAAM,MAAM;AAChD,UAAM,UAAU,kBAAkB,MAAM,OAAO,QAAQ,OAAO;AAC9D,YAAQ,KAAK,EAAE,MAAM,OAAO,QAAQ,CAAC;AACrC,QAAI,YAAY,MAAM;AACpB,YAAM,IAAI,KAAK,IAAI,IAAI;AACvB,qBAAe,KAAK,KAAK,EAAE;AAAA,IAC7B,OAAO;AACL,YAAM,IAAI,KAAK,IAAI,QAAQ,EAAE;AAAA,IAC/B;AAAA,EACF;AAEA,QAAM,WAA2E,CAAC;AAClF,aAAW,KAAK,OAAO;AACrB,UAAM,MAAM,MAAM,IAAI,EAAE,QAAQ;AAChC,UAAM,MAAM,MAAM,IAAI,EAAE,QAAQ;AAEhC,QAAI,OAAO,QAAQ,OAAO,KAAM;AAChC,aAAS,KAAK,EAAE,UAAU,KAAK,UAAU,KAAK,cAAc,EAAE,aAAa,CAAC;AAAA,EAC9E;AAEA,SAAO,EAAE,OAAO,SAAS,OAAO,UAAU,eAAe;AAC3D;AAOO,SAAS,aAAa,QAAuB,QAAyB;AAC3E,QAAM,UAAU,OAAO,UAAU,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,EAAE,YAAY,QAAQ,UAAU,EAAE,SAAS,MAAM,CAAC;AACpH,SAAO,WAAW,OAAO,iBAAiB;AAC5C;;;ACvKA,SAAS,kBAAkB;AAYpB,SAAS,UAAU,MAAiB,SAA0B;AACnE,SAAO,WAAW,QAAQ,EAAE,OAAO,gBAAgB,EAAE,MAAM,QAAQ,CAAC,CAAC,EAAE,OAAO,KAAK;AACrF;;;ACmCO,SAAS,SAAS,OAAsC;AAC7D,QAAM,EAAE,SAAS,QAAQ,aAAa,IAAI;AAC1C,QAAM,SAAyB,EAAE,OAAO,CAAC,GAAG,UAAU,CAAC,GAAG,SAAS,CAAC,EAAE;AAGtE,QAAM,gBAAgB,oBAAI,IAAY;AAEtC,aAAW,SAAS,QAAQ,OAAO;AACjC,QAAI,MAAM,YAAY,MAAM;AAE1B,aAAO,SAAS,KAAK,EAAE,aAAa,IAAI,MAAM,QAAQ,QAAQ,MAAM,KAAK,IAAI,SAAS,KAAK,CAAC;AAC5F;AAAA,IACF;AACA,UAAM,cAAc,UAAU,QAAQ,MAAM,OAAO;AACnD,QAAI,aAAa,IAAI,WAAW,EAAG;AAEnC,UAAM,OAAuB,EAAE,aAAa,MAAM,QAAQ,QAAQ,MAAM,KAAK,IAAI,SAAS,MAAM,QAAQ;AACxG,QAAI,aAAa,QAAQ,MAAM,KAAK,EAAE,GAAG;AACvC,aAAO,MAAM,KAAK,IAAI;AACtB,oBAAc,IAAI,MAAM,KAAK,EAAE;AAAA,IACjC,OAAO;AACL,aAAO,QAAQ,KAAK,IAAI;AAAA,IAC1B;AAAA,EACF;AAOA,QAAM,oBAAoB,oBAAI,IAAY;AAC1C,aAAW,SAAS,QAAQ,OAAO;AACjC,QAAI,MAAM,YAAY,QAAQ,cAAc,IAAI,MAAM,KAAK,EAAE,GAAG;AAC9D,wBAAkB,IAAI,MAAM,QAAQ,EAAE;AAAA,IACxC;AAAA,EACF;AACA,aAAW,KAAK,QAAQ,OAAO;AAC7B,UAAM,UAAU,EAAE,UAAU,EAAE,UAAU,UAAU,EAAE,UAAU,cAAc,EAAE,aAAa;AAC3F,UAAM,cAAc,UAAU,QAAQ,OAAO;AAC7C,UAAM,aAAa,kBAAkB,IAAI,EAAE,QAAQ,KAAK,kBAAkB,IAAI,EAAE,QAAQ;AACxF,QAAI,CAAC,YAAY;AACf,aAAO,SAAS,KAAK,EAAE,aAAa,IAAI,MAAM,QAAQ,QAAQ,CAAC;AAC/D;AAAA,IACF;AACA,QAAI,aAAa,IAAI,WAAW,EAAG;AACnC,WAAO,MAAM,KAAK,EAAE,aAAa,MAAM,QAAQ,QAAQ,CAAC;AAAA,EAC1D;AAEA,SAAO;AACT;;;AC7FA,SAAS,cAAAC,mBAAkB;AAyCpB,IAAM,sBAAsB;AAEnC,IAAM,gBAAgB;AACtB,IAAM,kBAAkB;AACxB,IAAM,qBAAqB;AAE3B,SAAS,WAAW,MAAoB;AACtC,UAAQ,OAAO,MAAM,sBAAsB,IAAI;AAAA,CAAI;AACrD;AAEA,SAAS,aAAa,IAA2B;AAC/C,SAAO,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,EAAE,CAAC;AAC7C;AAGA,SAAS,SAAS,OAA2B;AAC3C,QAAM,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK;AACpD,SAAOC,YAAW,QAAQ,EAAE,OAAO,gBAAgB,MAAM,CAAC,EAAE,OAAO,KAAK;AAC1E;AAWA,eAAsB,WACpB,QACA,OACA,OAAoB,CAAC,GACA;AACrB,QAAM,UAAU,OAAO;AACvB,MAAI,CAAC,SAAS,OAAO,CAAC,QAAQ,OAAO;AACnC,UAAM,IAAI,MAAM,iEAAiE;AAAA,EACnF;AAEA,MAAI;AACJ,MAAI;AACF,aAAS,IAAI,IAAI,QAAQ,GAAG;AAAA,EAC9B,QAAQ;AACN,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AACA,QAAM,kBAAkB,QAAQ,IAAI,oCAAoC;AACxE,MAAI,OAAO,aAAa,YAAY,CAAC,iBAAiB;AACpD,UAAM,IAAI;AAAA,MACR,6CAA6C,OAAO,QAAQ;AAAA,IAE9D;AAAA,EACF;AAEA,QAAM,MAAM,KAAK,OAAO;AACxB,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,YAAY,KAAK,IAAI,GAAG,KAAK,aAAa,QAAQ,aAAa,aAAa;AAClF,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,cAAc,eAAe;AACjE,QAAM,YAAY,KAAK,aAAa;AACpC,QAAM,UAAU,eAAe,QAAQ,GAAG;AAE1C,MAAI,MAAM,WAAW,GAAG;AACtB,QAAI,oCAAoC;AACxC,WAAO,EAAE,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,YAAY,CAAC,EAAE;AAAA,EAC1D;AAGA,QAAM,UAAwB,CAAC;AAC/B,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK,WAAW;AAChD,YAAQ,KAAK,MAAM,MAAM,GAAG,IAAI,SAAS,EAAE,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,SAAS,YAAY,GAAG,OAAO,EAAE,EAAE,CAAC;AAAA,EACvG;AAEA,MAAI,OAAO;AACX,MAAI,SAAS;AACb,QAAM,aAAuB,CAAC;AAE9B,aAAW,SAAS,SAAS;AAC3B,UAAM,MAAM,SAAS,KAAK;AAC1B,UAAM,OAAO,KAAK,UAAU;AAAA,MAC1B,eAAe;AAAA,MACf,GAAI,QAAQ,MAAM,EAAE,KAAK,QAAQ,IAAI,IAAI,CAAC;AAAA,MAC1C,OAAO,MAAM,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,aAAa,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,EAAE;AAAA,IAC5F,CAAC;AAED,QAAI,KAAK,QAAQ;AACf,UAAI,uBAAuB,MAAM,MAAM,eAAe,OAAO,iBAAiB,IAAI,MAAM,GAAG,EAAE,CAAC,SAAI;AAClG,cAAQ,MAAM;AACd,iBAAW,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC;AAClD;AAAA,IACF;AAEA,QAAI,KAAK;AACT,aAAS,UAAU,GAAG,WAAW,YAAY,WAAW;AACtD,YAAM,aAAa,IAAI,gBAAgB;AACvC,YAAM,QAAQ,WAAW,MAAM,WAAW,MAAM,GAAG,SAAS;AAC5D,YAAM,YAAY,KAAK,IAAI;AAC3B,UAAI;AACF,cAAM,MAAM,MAAM,UAAU,QAAQ,KAAK;AAAA,UACvC,QAAQ;AAAA,UACR,SAAS;AAAA,YACP,iBAAiB,UAAU,QAAQ,KAAK;AAAA,YACxC,gBAAgB;AAAA,YAChB,qBAAqB;AAAA,UACvB;AAAA,UACA;AAAA,UACA,QAAQ,WAAW;AAAA,QACrB,CAAC;AACD,cAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,YAAI,IAAI,IAAI;AACV,cAAI,UAAU,MAAM,MAAM,mBAAc,OAAO,KAAK,IAAI,MAAM,KAAK,OAAO,eAAe,UAAU,CAAC,GAAG;AACvG,eAAK;AACL;AAAA,QACF;AAEA,YAAI,IAAI,UAAU,OAAO,IAAI,SAAS,KAAK;AACzC,cAAI,yBAAoB,OAAO,KAAK,IAAI,MAAM,cAAc;AAC5D;AAAA,QACF;AAEA,YAAI,uBAAkB,OAAO,KAAK,IAAI,MAAM,cAAc,UAAU,CAAC,IAAI,aAAa,CAAC,GAAG;AAAA,MAC5F,SAAS,KAAK;AAEZ,cAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,YAAI,sBAAiB,OAAO,KAAK,IAAI,QAAQ,sBAAsB,CAAC,MAAM,eAAe,CAAC,CAAC,CAAC,aAAa,UAAU,CAAC,IAAI,aAAa,CAAC,GAAG;AAAA,MAC3I,UAAE;AACA,qBAAa,KAAK;AAAA,MACpB;AACA,UAAI,UAAU,YAAY;AAExB,cAAM,OAAO,KAAK,IAAI,KAAK,UAAU,KAAK,GAAI;AAC9C,cAAM,MAAM,OAAO,KAAK,MAAM,KAAK,OAAO,IAAI,GAAG,CAAC;AAAA,MACpD;AAAA,IACF;AAEA,QAAI,IAAI;AACN,cAAQ,MAAM;AACd,iBAAW,KAAK,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC;AAAA,IACpD,OAAO;AACL,gBAAU,MAAM;AAAA,IAClB;AAAA,EACF;AAEA,SAAO,EAAE,MAAM,SAAS,QAAQ,QAAQ,QAAQ,WAAW;AAC7D;;;ACjKO,SAAS,gBACd,IACA,WACA,QACA,OAA4B,CAAC,GACT;AACpB,MAAI,CAAC,OAAO,WAAW,IAAK,QAAO,EAAE,UAAU,GAAG,YAAY,GAAG,UAAU,EAAE;AAE7E,QAAM,SAAS,KAAK,UAAU,WAAW,EAAE,cAAc,OAAO,aAAa,CAAC;AAC9E,QAAM,SAAS,GAAG,iBAAiB;AAGnC,QAAM,UAAU,aAAa,IAAI,WAAW,QAAQ,QAAQ,EAAE,iBAAiB,KAAK,CAAC;AACrF,QAAM,eAAe,GAAG,gBAAgB;AAExC,QAAM,EAAE,OAAO,SAAS,SAAS,IAAI,SAAS,EAAE,SAAS,QAAQ,aAAa,CAAC;AAE/E,QAAM,WAAW,GAAG,cAAc,EAAE,YAAY,MAAM;AACpD,eAAW,QAAQ,OAAO;AACxB,SAAG,eAAe;AAAA,QAChB,aAAa,KAAK;AAAA,QAClB;AAAA,QACA,QAAQ,KAAK;AAAA,QACb,MAAM,KAAK;AAAA,QACX,SAAS,KAAK;AAAA,QACd,QAAQ;AAAA,QACR,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AACA,eAAW,QAAQ,SAAS;AAC1B,SAAG,eAAe;AAAA,QAChB,aAAa,KAAK;AAAA,QAClB;AAAA,QACA,QAAQ,KAAK;AAAA,QACb,MAAM,KAAK;AAAA,QACX,SAAS,KAAK;AAAA,QACd,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAKA,eAAW,QAAQ,UAAU;AAC3B,UAAI,CAAC,KAAK,YAAa;AACvB,SAAG,eAAe;AAAA,QAChB,aAAa,KAAK;AAAA,QAClB;AAAA,QACA,QAAQ,KAAK;AAAA,QACb,MAAM,KAAK;AAAA,QACX,SAAS,KAAK;AAAA,QACd,QAAQ;AAAA,QACR,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACF,CAAC;AACD,WAAS;AAET,SAAO,EAAE,UAAU,QAAQ,QAAQ,YAAY,MAAM,QAAQ,UAAU,SAAS,OAAO;AACzF;;;AC7FA,SAAS,SAAS,WAAW,aAAa,qBAAqB;AAC/D,SAAS,SAAS,WAAW,aAAa,qBAAqB;AAGxD,SAAS,YAAY,MAAc,QAA+C;AACvF,MAAI,CAAC,KAAK,KAAK,EAAG,QAAO,CAAC;AAC1B,MAAI;AACF,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,eAAO,KAAK,MAAM,IAAI;AAAA,MACxB,KAAK;AACH,eAAO,UAAU,IAAI;AAAA,MACvB,KAAK;AACH,eAAQ,UAAU,IAAI,KAAiC,CAAC;AAAA,IAC5D;AAAA,EACF,SAAS,KAAK;AAGZ,UAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,UAAM,IAAI,MAAM,4BAA4B,OAAO,YAAY,CAAC,YAAY,MAAM,EAAE;AAAA,EACtF;AACF;AAEO,SAAS,gBAAgB,KAA8B,QAA8B;AAC1F,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,KAAK,UAAU,KAAK,MAAM,CAAC,IAAI;AAAA,IACxC,KAAK;AACH,aAAO,cAAc,GAAG,IAAI;AAAA,IAC9B,KAAK;AACH,aAAO,cAAc,GAAG;AAAA,EAC5B;AACF;;;AC/BA,SAAS,cAAc,GAA0C;AAC/D,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AAEO,SAAS,UAA6C,QAAW,QAAoC;AAC1G,QAAM,MAA+B,EAAE,GAAG,OAAO;AACjD,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACjD,UAAM,WAAW,IAAI,GAAG;AACxB,QAAI,cAAc,QAAQ,KAAK,cAAc,KAAK,GAAG;AACnD,UAAI,GAAG,IAAI,UAAU,UAAU,KAAK;AAAA,IACtC,OAAO;AACL,UAAI,GAAG,IAAI;AAAA,IACb;AAAA,EACF;AACA,SAAO;AACT;;;ACbO,SAAS,gBAAgB,OAA6C;AAC3E,MAAI,MAAM,KAAK;AACb,WAAO,EAAE,MAAM,QAAQ,KAAK,MAAM,KAAK,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,EAAG;AAAA,EAClF;AACA,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,MAAM,MAAM,QAAQ,CAAC;AAAA,IACrB,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,EACxC;AACF;;;ACVO,IAAM,eAAe;AACrB,IAAM,UAAU;AAChB,IAAM,sBAAsB;AAc5B,SAAS,mBAAmB,OAAqB,CAAC,GAAgB;AACvE,MAAI,KAAK,cAAc,QAAQ;AAC7B,WAAO,EAAE,KAAK,KAAK,OAAO,6BAA6B,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC,EAAG;AAAA,EAChG;AACA,QAAM,OAAO,CAAC,MAAM,aAAa,cAAc,SAAS,GAAI,KAAK,eAAe,CAAC,CAAE;AACnF,SAAO,EAAE,SAAS,OAAO,MAAM,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC,EAAG;AACxE;;;ACxBA,SAAS,aAAAC,YAAW,gBAAAC,eAAc,iBAAAC,gBAAe,cAAAC,mBAAkB;AACnE,SAAS,eAAe;AACxB,SAAS,eAAe;AAoBjB,SAAS,YAAoB;AAClC,MAAI,QAAQ,aAAa,QAAS,QAAO;AACzC,MAAI,QAAQ,aAAa,SAAU,QAAO;AAC1C,SAAO;AACT;AAGO,SAAS,eAAe,OAA8B;AAC3D,SAAO,EAAE,OAAO,IAAI,UAAU,GAAG,MAAM,QAAQ,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,QAAQ,IAAI;AACzF;AAQO,SAAS,YAAY,MAAkB,KAAqB,MAAgC;AACjG,QAAM,OAAO,KAAK,KAAK,GAAG;AAC1B,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,MAAM,GAAG,KAAK,KAAK,0BAA0B,IAAI,KAAK,UAAU;AAAA,EAC5E;AACA,QAAM,aAAaC,YAAW,IAAI;AAClC,QAAM,SAAS,aAAaC,cAAa,MAAM,MAAM,IAAI;AACzD,QAAM,WAAW,YAAY,QAAQ,KAAK,MAAM;AAChD,QAAM,SAAS,KAAK,MAAM,UAAU,KAAK,cAAc,qBAAqB,KAAK,KAAK;AACtF,QAAM,QAAQ,gBAAgB,QAAQ,KAAK,MAAM;AACjD,SAAO;AAAA,IACL,QAAQ,KAAK;AAAA,IACb,OAAO,KAAK;AAAA,IACZ;AAAA,IACA,QAAQ,KAAK;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,UAAU;AAAA,IACnB,GAAI,KAAK,OAAO,EAAE,MAAM,KAAK,KAAK,IAAI,CAAC;AAAA,EACzC;AACF;AAGO,SAAS,aAAa,MAAyB;AACpD,EAAAC,WAAU,QAAQ,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACjD,EAAAC,eAAc,KAAK,MAAM,KAAK,OAAO,MAAM;AAC7C;AAGO,SAAS,WAAW,QAAgB,OAAuB;AAChE,MAAI,WAAW,MAAO,QAAO;AAC7B,QAAM,IAAI,OAAO,SAAS,OAAO,MAAM,IAAI,IAAI,CAAC;AAChD,QAAM,IAAI,MAAM,MAAM,IAAI;AAC1B,QAAM,MAAgB,CAAC;AACvB,QAAM,MAAM,KAAK,IAAI,EAAE,QAAQ,EAAE,MAAM;AACvC,WAAS,IAAI,GAAG,IAAI,KAAK,KAAK;AAC5B,QAAI,EAAE,CAAC,MAAM,EAAE,CAAC,GAAG;AACjB,UAAI,EAAE,CAAC,MAAM,OAAW,KAAI,KAAK,KAAK,EAAE,CAAC,CAAC,EAAE;AAAA,IAC9C,OAAO;AACL,UAAI,EAAE,CAAC,MAAM,OAAW,KAAI,KAAK,KAAK,EAAE,CAAC,CAAC,EAAE;AAC5C,UAAI,EAAE,CAAC,MAAM,OAAW,KAAI,KAAK,KAAK,EAAE,CAAC,CAAC,EAAE;AAAA,IAC9C;AAAA,EACF;AACA,SAAO,IAAI,KAAK,IAAI;AACtB;;;ACpFA,SAAS,QAAAC,aAAY;AAMrB,SAAS,gBAAgB,MAOV;AACb,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,OAAO,KAAK;AAAA,IACZ,QAAQ;AAAA,IACR,MAAM,KAAK;AAAA,IACX,MAAM,CAAC,QAAS,IAAI,UAAU,YAAY,KAAK,cAAc,GAAG,IAAI,KAAK,WAAW,GAAG;AAAA,IACvF,OAAO,CAAC,UAAU,MAAM,UACtB,UAAU,UAAU,EAAE,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC,IAAI,GAAG,gBAAgB,KAAK,EAAE,EAAE,CAAC;AAAA,EAC1E;AACF;AAGA,IAAM,aAAa,gBAAgB;AAAA,EACjC,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,KAAK;AAAA,EACL,YAAY,CAAC,QAAQC,MAAK,IAAI,MAAM,cAAc;AAAA,EAClD,aAAa,CAAC,QAAQA,MAAK,IAAI,KAAK,WAAW;AACjD,CAAC;AAGD,IAAM,SAAS,gBAAgB;AAAA,EAC7B,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,KAAK;AAAA,EACL,YAAY,CAAC,QAAQA,MAAK,IAAI,MAAM,WAAW,UAAU;AAAA,EACzD,aAAa,CAAC,QAAQA,MAAK,IAAI,KAAK,WAAW,UAAU;AAC3D,CAAC;AAKD,SAAS,mBAAmB,OAA6C;AACvE,MAAI,MAAM,IAAK,QAAO,EAAE,MAAM,QAAQ,KAAK,MAAM,KAAK,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,EAAG;AAC/F,SAAO,EAAE,MAAM,SAAS,SAAS,MAAM,SAAS,MAAM,MAAM,QAAQ,CAAC,GAAG,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,EAAG;AACnH;AAGA,SAAS,cAAc,KAA6B;AAClD,MAAI,IAAI,OAAO,MAAO,QAAOA,MAAK,IAAI,IAAI,WAAWA,MAAK,IAAI,MAAM,WAAW,SAAS,GAAG,QAAQ,MAAM;AACzG,MAAI,IAAI,OAAO,MAAO,QAAOA,MAAK,IAAI,MAAM,WAAW,uBAAuB,QAAQ,MAAM;AAC5F,SAAOA,MAAK,IAAI,MAAM,WAAW,QAAQ,MAAM;AACjD;AAEA,IAAM,SAAqB;AAAA,EACzB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM,CAAC,QAAS,IAAI,UAAU,YAAYA,MAAK,IAAI,KAAK,WAAW,UAAU,IAAIA,MAAK,cAAc,GAAG,GAAG,UAAU;AAAA,EACpH,OAAO,CAAC,UAAU,MAAM,UAAU,UAAU,UAAU,EAAE,SAAS,EAAE,CAAC,IAAI,GAAG,mBAAmB,KAAK,EAAE,EAAE,CAAC;AAC1G;AAIA,IAAM,QAAoB;AAAA,EACxB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM,CAAC,QAAS,IAAI,UAAU,YAAYA,MAAK,IAAI,KAAK,UAAU,aAAa,IAAIA,MAAK,IAAI,MAAM,UAAU,aAAa;AAAA,EACzH,OAAO,CAAC,UAAU,MAAM,UAAU,UAAU,UAAU,EAAE,aAAa,EAAE,CAAC,IAAI,GAAG,gBAAgB,KAAK,EAAE,EAAE,CAAC;AAC3G;AAIA,IAAM,WAAW,gBAAgB;AAAA,EAC/B,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,KAAK;AAAA,EACL,YAAY,CAAC,QAAQA,MAAK,IAAI,MAAM,YAAY,YAAY,iBAAiB;AAC/E,CAAC;AAID,SAAS,kBAAkB,KAAqB,aAA6B;AAC3E,SAAOA,MAAK,cAAc,GAAG,GAAG,iBAAiB,aAAa,YAAY,yBAAyB;AACrG;AAEA,IAAM,QAAoB;AAAA,EACxB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM,CAAC,QAAS,IAAI,UAAU,YAAY,SAAY,kBAAkB,KAAK,wBAAwB;AAAA;AAAA,EAErG,OAAO,CAAC,UAAU,MAAM,UACtB,UAAU,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,GAAG,EAAE,GAAG,gBAAgB,KAAK,GAAG,aAAa,CAAC,GAAG,UAAU,MAAM,EAAE,EAAE,CAAC;AACnH;AAEA,IAAM,MAAkB;AAAA,EACtB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM,CAAC,QAAS,IAAI,UAAU,YAAYA,MAAK,IAAI,KAAK,QAAQ,UAAU,IAAI,kBAAkB,KAAK,4BAA4B;AAAA,EACjI,OAAO,CAAC,UAAU,MAAM,UAAU,UAAU,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,GAAG,gBAAgB,KAAK,EAAE,EAAE,CAAC;AAC1G;AAIA,IAAM,MAAkB;AAAA,EACtB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM,CAAC,QAAQ;AACb,QAAI,IAAI,UAAU,UAAW,QAAOA,MAAK,IAAI,KAAK,QAAQ,eAAe;AACzE,QAAI,IAAI,OAAO,MAAO,QAAOA,MAAK,IAAI,IAAI,WAAWA,MAAK,IAAI,MAAM,WAAW,SAAS,GAAG,OAAO,eAAe;AACjH,WAAOA,MAAK,IAAI,MAAM,WAAW,OAAO,eAAe;AAAA,EACzD;AAAA,EACA,OAAO,CAAC,UAAU,MAAM,UAAU;AAChC,UAAM,QAAQ,MAAM,MAChB,EAAE,QAAQ,UAAU,KAAK,MAAM,IAAI,IACnC,EAAE,QAAQ,UAAU,SAAS,MAAM,SAAS,MAAM,MAAM,QAAQ,CAAC,GAAG,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,EAAG;AACjH,WAAO,UAAU,UAAU,EAAE,iBAAiB,EAAE,CAAC,IAAI,GAAG,MAAM,EAAE,CAAC;AAAA,EACnE;AACF;AAGA,IAAM,QAAoB;AAAA,EACxB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM,CAAC,QAAS,IAAI,UAAU,YAAYA,MAAK,IAAI,KAAK,UAAU,OAAO,UAAU,IAAIA,MAAK,IAAI,MAAM,UAAU,OAAO,UAAU;AAAA,EACjI,OAAO,CAAC,UAAU,MAAM,UAAU,UAAU,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,GAAG,gBAAgB,KAAK,EAAE,EAAE,CAAC;AAC1G;AAIA,IAAM,SAAqB;AAAA,EACzB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM,CAAC,QAAS,IAAI,UAAU,YAAYA,MAAK,IAAI,KAAK,WAAW,eAAe,IAAIA,MAAK,IAAI,MAAM,WAAW,eAAe;AAAA,EAC/H,OAAO,CAAC,UAAU,MAAM,UAAU;AAChC,UAAM,QAAQ,MAAM,MAChB,EAAE,SAAS,MAAM,KAAK,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,EAAG,IAC/D,gBAAgB,KAAK;AACzB,WAAO,UAAU,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,GAAG,MAAM,EAAE,CAAC;AAAA,EAC9D;AACF;AAIA,IAAM,QAAoB;AAAA,EACxB,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM,CAAC,QAAQ;AACb,QAAI,IAAI,OAAO,MAAO,QAAOA,MAAK,IAAI,IAAI,WAAWA,MAAK,IAAI,MAAM,WAAW,SAAS,GAAG,SAAS,SAAS,UAAU,aAAa;AACpI,WAAOA,MAAK,IAAI,MAAM,WAAW,SAAS,aAAa;AAAA,EACzD;AAAA,EACA,OAAO,CAAC,UAAU,MAAM,UAAU;AAChC,UAAM,QAAQ,MAAM,MAChB,EAAE,MAAM,MAAM,mBAAmB,SAAS,MAAM,KAAK,MAAM,KAAK,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,EAAG,IACzG,EAAE,MAAM,MAAM,SAAS,SAAS,MAAM,SAAS,MAAM,SAAS,MAAM,MAAM,QAAQ,CAAC,GAAG,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,EAAG;AACnI,WAAO,UAAU,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,GAAG,MAAM,EAAE,CAAC;AAAA,EAC9D;AACF;AAKA,SAAS,MAAM,GAA0C;AACvD,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AACA,IAAM,YAAwB;AAAA,EAC5B,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM,CAAC,QAAS,IAAI,UAAU,YAAYA,MAAK,IAAI,KAAK,aAAa,IAAIA,MAAK,IAAI,MAAM,cAAc,aAAa;AAAA,EACnH,OAAO,CAAC,UAAU,MAAM,UAAU;AAChC,UAAM,MAAM,MAAM,SAAS,GAAG,IAAI,EAAE,GAAG,SAAS,IAAI,IAAI,CAAC;AACzD,UAAM,MAAM,MAAM,MAAM,kBAAkB;AAC1C,UAAM,MAAM,MAAM,QAAQ,IAAI,GAAG,CAAC,IAAI,CAAC,GAAI,IAAI,GAAG,CAA+B,IAAI,CAAC;AACtF,UAAM,OAAgC,MAAM,MACxC,EAAE,KAAK,MAAM,KAAK,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,EAAG,IAC3D,EAAE,MAAM,SAAS,MAAM,SAAS,MAAM,MAAM,QAAQ,CAAC,GAAG,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC,EAAG;AACrG,UAAMC,WAAU,CAAC,MAAgC,MAAM,MAAM,EAAE,QAAQ,MAAM,MAAM,EAAE,SAAS;AAC9F,UAAM,MAAM,IAAI,UAAUA,QAAO;AACjC,QAAI,OAAO,EAAG,KAAI,GAAG,IAAI;AAAA,QACpB,KAAI,KAAK,IAAI;AAClB,QAAI,GAAG,IAAI;AACX,WAAO,EAAE,GAAG,UAAU,IAAI;AAAA,EAC5B;AACF;AAIA,IAAM,gBAA4B;AAAA,EAChC,IAAI;AAAA,EACJ,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM,CAAC,QAAQ;AACb,QAAI,IAAI,UAAU,UAAW,QAAO;AACpC,QAAI,IAAI,OAAO,MAAO,QAAOD,MAAK,IAAI,IAAI,WAAWA,MAAK,IAAI,MAAM,WAAW,SAAS,GAAG,UAAU,4BAA4B;AACjI,QAAI,IAAI,OAAO,MAAO,QAAOA,MAAK,IAAI,MAAM,WAAW,uBAAuB,UAAU,4BAA4B;AACpH,WAAOA,MAAK,IAAI,MAAM,WAAW,UAAU,4BAA4B;AAAA,EACzE;AAAA,EACA,OAAO,CAAC,UAAU,MAAM,UAAU,UAAU,UAAU,EAAE,YAAY,EAAE,CAAC,IAAI,GAAG,gBAAgB,KAAK,EAAE,EAAE,CAAC;AAC1G;AAGO,IAAM,UAAwB;AAAA,EACnC;AAAA,EAAY;AAAA,EAAQ;AAAA,EAAQ;AAAA,EAAO;AAAA,EACnC;AAAA,EAAO;AAAA,EAAK;AAAA,EAAK;AAAA,EAAO;AAAA,EACxB;AAAA,EAAO;AAAA,EAAW;AACpB;AAEO,SAAS,UAAU,IAAoC;AAC5D,SAAO,QAAQ,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AACxC;AAEO,SAAS,cAAmF;AACjG,SAAO,QAAQ,IAAI,CAAC,EAAE,IAAI,OAAO,QAAQ,KAAK,OAAO,EAAE,IAAI,OAAO,QAAQ,KAAK,EAAE;AACnF;;;ACpOO,SAAS,eAAe,MAAc,OAA4B;AACvE,QAAM,SAAS,OAAO,KAAK,KAAK,UAAU,gBAAgB,KAAK,CAAC,CAAC,EAAE,SAAS,QAAQ;AACpF,QAAM,SAAS,IAAI,gBAAgB,EAAE,MAAM,OAAO,CAAC;AACnD,SAAO,kDAAkD,OAAO,SAAS,CAAC;AAC5E;AAQO,SAAS,eAAe,MAAc,OAAoB,OAA8B,CAAC,GAAW;AACzG,QAAM,SAAS,KAAK,WAAW,oBAAoB;AACnD,QAAM,UAAU,mBAAmB,KAAK,UAAU,EAAE,MAAM,GAAG,gBAAgB,KAAK,EAAE,CAAC,CAAC;AACtF,SAAO,GAAG,MAAM,kBAAkB,OAAO;AAC3C;AAGO,SAAS,kBAAkB,MAAc,OAA4B;AAC1E,SAAO,mBAAmB,KAAK,UAAU,EAAE,MAAM,GAAG,gBAAgB,KAAK,EAAE,CAAC,CAAC;AAC/E;;;AlCYA,IAAM,OAAU,CAAC,MAAc,UAAU,CAAC;AAC1C,IAAM,MAAU,CAAC,MAAc,UAAU,CAAC;AAC1C,IAAM,OAAU,CAAC,MAAc,WAAW,CAAC;AAC3C,IAAM,QAAU,CAAC,MAAc,WAAW,CAAC;AAC3C,IAAM,SAAU,CAAC,MAAc,WAAW,CAAC;AAC3C,IAAM,UAAU,CAAC,MAAc,WAAW,CAAC;AAC3C,IAAM,MAAU,CAAC,MAAc,WAAW,CAAC;AAM3C,SAAS,eAAe,GAAyB;AAC/C,QAAM,MAAgB,CAAC;AACvB,MAAI,KAAK,GAAG,KAAK,eAAe,CAAC,KAAK,IAAI,EAAE,KAAK,UAAU,MAAM,GAAG,CAAC,CAAC,CAAC,WAAM,IAAI,EAAE,QAAQ,UAAU,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE;AACnH,MAAI,KAAK,cAAc,EAAE,KAAK,SAAS,WAAW,EAAE,KAAK,SAAS,WAAW,IAAI,EAAE,KAAK,SAAS,CAAC,EAAE;AACpG,MAAI,KAAK,cAAc,EAAE,QAAQ,SAAS,WAAW,EAAE,QAAQ,SAAS,WAAW,IAAI,EAAE,QAAQ,SAAS,CAAC,EAAE;AAC7G,MAAI,KAAK,EAAE;AACX,MAAI,KAAK,YAAY,MAAM,MAAM,EAAE,QAAQ,UAAU,CAAC,IAAI,IAAI,MAAM,EAAE,QAAQ,YAAY,CAAC,IAAI,OAAO,MAAM,EAAE,QAAQ,YAAY,CAAC,cAAc,MAAM,MAAM,EAAE,QAAQ,UAAU,CAAC,IAAI,IAAI,MAAM,EAAE,QAAQ,YAAY,CAAC,EAAE;AACzN,MAAI,EAAE,QAAQ,aAAa,EAAE,QAAQ,eAAe,EAAE,QAAQ,eAAe,EAAE,QAAQ,aAAa,EAAE,QAAQ,iBAAiB,GAAG;AAChI,QAAI,KAAK,EAAE;AACX,QAAI,KAAK,KAAK,MAAM,QAAG,CAAC,qCAAqC;AAC7D,WAAO,IAAI,KAAK,IAAI;AAAA,EACtB;AACA,MAAI,KAAK,EAAE;AACX,aAAW,KAAK,EAAE,MAAM,MAAO,KAAI,KAAK,KAAK,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,IAAI,MAAM,EAAE,OAAO,GAAG,CAAC,EAAE;AAC5F,aAAW,KAAK,EAAE,MAAM,QAAS,KAAI,KAAK,KAAK,IAAI,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,IAAI,MAAM,EAAE,OAAO,GAAG,CAAC,EAAE;AAC5F,aAAW,KAAK,EAAE,MAAM,QAAS,KAAI,KAAK,KAAK,OAAO,GAAG,CAAC,IAAI,EAAE,EAAE,IAAI,IAAI,MAAM,EAAE,cAAc,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE;AACnH,aAAW,KAAK,EAAE,MAAM,MAAO,KAAI,KAAK,KAAK,MAAM,GAAG,CAAC,SAAS,EAAE,QAAQ,IAAI,IAAI,WAAM,EAAE,eAAe,QAAG,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC7H,aAAW,KAAK,EAAE,MAAM,QAAS,KAAI,KAAK,KAAK,IAAI,GAAG,CAAC,SAAS,EAAE,QAAQ,IAAI,IAAI,WAAM,EAAE,eAAe,QAAG,CAAC,IAAI,EAAE,QAAQ,EAAE;AAC7H,SAAO,IAAI,KAAK,IAAI;AACtB;AAGA,SAAS,uBAAuB,GAA+B;AAC7D,QAAM,IAAI,EAAE,MAAM;AAClB,QAAM,OAAO,EAAE,gBAAgB,EAAE,cAAc,MAAM,GAAG,CAAC,IAAI;AAC7D,SACE,GAAG,MAAM,MAAM,EAAE,UAAU,CAAC,IAAI,IAAI,MAAM,EAAE,YAAY,CAAC,IAAI,OAAO,MAAM,EAAE,YAAY,CAAC,WACtF,MAAM,MAAM,EAAE,UAAU,CAAC,IAAI,IAAI,MAAM,EAAE,YAAY,CAAC,UACtD,IAAI,cAAc,EAAE,UAAU,MAAM,GAAG,CAAC,IAAI,YAAY,OAAO,GAAG,CAAC;AAE1E;AAOA,SAAS,kBACP,IACA,WACA,QACA,GACM;AACN,MAAI,CAAC,OAAO,WAAW,IAAK;AAC5B,MAAI;AACF,UAAM,IAAI,gBAAgB,IAAI,WAAW,MAAM;AAC/C,QAAI,EAAE,WAAW,GAAG;AAClB,QAAE,KAAK,KAAK,QAAG,CAAC,KAAK,KAAK,OAAO,EAAE,QAAQ,CAAC,CAAC,yCAAoC,KAAK,oCAAoC,CAAC;AAAA,CAAI;AAAA,IACjI,WAAW,EAAE,aAAa,GAAG;AAC3B,QAAE,KAAK,KAAK,QAAG,CAAC,KAAK,KAAK,OAAO,EAAE,UAAU,CAAC,CAAC,+CAA0C,KAAK,kCAAkC,CAAC;AAAA,CAAI;AAAA,IACvI;AAAA,EACF,SAAS,KAAK;AACZ,YAAQ,qCAAqC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,EACjG;AACF;AAEA,KAAK;AAEL,SAAS,OAAa;AAEpB,MAAI,WAAiC;AACrC,QAAM,WAAW,CAAC,WAA2B;AAC3C,YAAQ,YAAY,MAAM,kCAA6B;AACvD,QAAI,UAAU;AACZ,UAAI;AAAE,iBAAS,MAAM;AAAA,MAAG,QAAQ;AAAA,MAAuB;AACvD,iBAAW;AAAA,IACb;AAGA,YAAQ,eAAe,WAAW,QAAQ;AAC1C,YAAQ,eAAe,UAAU,QAAQ;AACzC,YAAQ,KAAK,QAAQ,KAAK,MAAM;AAAA,EAClC;AACA,UAAQ,GAAG,WAAW,QAAQ;AAC9B,UAAQ,GAAG,UAAU,QAAQ;AAG7B,mBAAiB;AAEjB,QAAM,UAAU,IAAI,QAAQ;AAE5B,QAAM,MAAM;AACZ,QAAM,YAAY,YAAY,WAAWE,SAAQ,cAAc,YAAY,GAAG,CAAC;AAC/E,MAAI,UAAU;AACd,MAAI;AACF,cAAU,KAAK,MAAMC,cAAaC,SAAQ,WAAW,MAAM,cAAc,GAAG,OAAO,CAAC,EAAE,WAAW;AAAA,EACnG,QAAQ;AACN,YAAQ,4DAA4D;AAAA,EACtE;AAEA,UACG,KAAK,GAAG,EACR,YAAY,8DAA8D,EAC1E,QAAQ,OAAO;AAIlB,UACG,QAAQ,UAAU,EAClB,YAAY,kCAAkC,EAC9C,OAAO,sBAAsB,gBAAgB,CAAC,WAAW,CAAC,EAC1D,OAAO,eAAe,mBAAmB,GAAG,EAC5C,OAAO,mBAAmB,mBAAmB,IAAI,EACjD,OAAO,qBAAqB,kEAAkE,EAC9F,OAAO,eAAe,eAAe,4BAA4B,EACjE,OAAO,gBAAgB,mCAAmC,EAC1D,OAAO,sBAAsB,oBAAoB,mBAAmB,EACpE,OAAO,eAAe,SAAS,EAC/B,OAAO,iBAAiB,+DAA+D,EACvF,OAAO,wBAAwB,mGAAmG,EAClI,OAAO,yBAAyB,mDAAmD,MAAM,EACzF,OAAO,iBAAiB,wBAAwB,KAAK,EACrD,OAAO,OAAO,SAAS;AAEtB,UAAM,eAAgB,KAAK,YAAY,QAAQ,IAAI,wBAAwB;AAC3E,QAAI,CAAC,wBAAwB,IAAI,YAAY,GAAG;AAC9C,cAAQ,OAAO;AAAA,QACb,4BAAuB,YAAY,aAAa,wBAAwB,MAAM,EAAE,KAAK,IAAI,CAAC;AAAA;AAAA,MAC5F;AACA,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,WAAW;AAEjB,uBAAmB,QAAQ;AAG3B,UAAM,cAAc,SAAS,KAAK,OAAO,EAAE;AAC3C,UAAM,iBAAiB,SAAS,KAAK,UAAU,EAAE;AACjD,QAAI,OAAO,MAAM,WAAW,KAAK,cAAc,KAAK,cAAc,IAAI;AACpE,cAAQ,OAAO,MAAM,4BAAuB,KAAK,KAAK;AAAA,CAAoB;AAC1E,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,QAAI,OAAO,MAAM,cAAc,KAAK,iBAAiB,KAAK,iBAAiB,KAAK;AAC9E,cAAQ,OAAO,MAAM,gCAA2B,KAAK,QAAQ;AAAA,CAAqB;AAClF,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,MAAO,KAAK,gBAAgB;AAClC,QAAI,CAAC,CAAC,QAAQ,QAAQ,aAAa,EAAE,SAAS,GAAG,GAAG;AAClD,cAAQ,OAAO,MAAM,oCAA+B,KAAK,YAAY;AAAA,CAA0C;AAC/G,cAAQ,WAAW;AACnB;AAAA,IACF;AAGA,UAAM,SAAS,QAAQ;AAEvB,eAAW,KAAK,OAAO;AAEvB,UAAM,SAAS,cAAc;AAAA,MAC3B,aAAa,KAAK;AAAA,MAClB,UAAU;AAAA,MACV,UAAU;AAAA,MACV;AAAA,MACA,YAAY,KAAK;AAAA,MACjB,cAAc,KAAK;AAAA,MACnB,WAAW,KAAK;AAAA,MAChB,GAAI,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC;AAAA,MACrC,SAAS,KAAK;AAAA,IAChB,CAAC;AAED,YAAQ,qBAAqB;AAAA,MAC3B,aAAa,OAAO;AAAA,MACpB,UAAU,OAAO;AAAA,MACjB,OAAO,OAAO;AAAA,MACd,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,IACnB,CAAC;AAED,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,eAAW;AAEX,UAAM,IAAI,QAAQ,OAAO,MAAM,KAAK,QAAQ,MAAM;AAKlD,QAAI,KAAK,QAAQ;AACf,YAAM,WAAW,gBAAgB,KAAK,GAAG;AACzC,YAAM,WAAW,OAAO,KAAK,WAAW,WACpC,KAAK,SACL,GAAG,iBAAiB,YAAY,QAAQ,GAAG;AAC/C,YAAM,gBAAgB,WAAW,GAAG,WAAW,QAAQ,IAAI;AAC3D,UAAI,CAAC,YAAY,CAAC,eAAe;AAC/B,gBAAQ,OAAO;AAAA,UACb,uCAAkC,OAAO,KAAK,WAAW,WAAW,SAAS,KAAK,MAAM,OAAO,EAAE;AAAA;AAAA,QACnG;AACA,gBAAQ,WAAW;AACnB,WAAG,MAAM;AACT,mBAAW;AACX;AAAA,MACF;AACA,YAAM,gBAAgB,GAAG,SAAS,QAAQ,EAAE;AAC5C,YAAM,gBAAgB,GAAG,SAAS,QAAQ,EAAE;AAC5C,UAAI,QAAQ;AACV,UAAE,IAAI;AACN,UAAE,KAAK,KAAK,aAAa,CAAC,KAAK,IAAI,6BAA0B,SAAS,MAAM,GAAG,CAAC,CAAC,CAAC;AAAA,CAAI;AACtF,UAAE,IAAI,wSAAwD,CAAC;AAAA,MACjE;AACA,UAAI;AACF,cAAM,IAAI,MAAM,kBAAkB,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AAClE,cAAM,UAAU,GAAG,WAAW,QAAQ;AACtC,cAAM,OAAqB;AAAA,UACzB,MAAM,EAAE,WAAW,UAAU,WAAW,cAAc,WAAW,WAAW,eAAe,WAAW,cAAc;AAAA,UACpH,SAAS,EAAE,WAAW,UAAU,WAAW,SAAS,kBAAiB,oBAAI,KAAK,GAAE,YAAY,GAAG,WAAW,EAAE,OAAO,WAAW,EAAE,MAAM;AAAA,UACtI,OAAO,EAAE,OAAO,SAAS,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC,GAAG,WAAW,EAAE;AAAA,UAC7E,OAAO,EAAE,OAAO,SAAS,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,GAAG,WAAW,EAAE;AAAA,UAChE,SAAS,EAAE,OAAO,WAAW,EAAE,YAAY,GAAG,cAAc,GAAG,cAAc,GAAG,YAAY,GAAG,cAAc,EAAE;AAAA,UAC/G,WAAW,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,OAAO,CAAC,EAAE;AAAA,QAChD;AACA,YAAI,QAAQ,OAAQ,GAAE,eAAe,IAAI,IAAI,MAAM;AAAA,YAC9C,SAAQ,OAAO,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,IAAI;AAC9D,gBAAQ,+BAA+B,EAAE,WAAW,UAAU,GAAG,KAAK,QAAQ,CAAC;AAAA,MACjF,SAAS,KAAK;AACZ,cAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,iBAAS,6BAA6B,EAAE,WAAW,UAAU,OAAO,OAAO,CAAC;AAC5E,UAAE;AAAA,IAAO,KAAK,IAAI,QAAG,CAAC,CAAC,oBAAoB,MAAM;AAAA,CAAI;AACrD,gBAAQ,WAAW;AAAA,MACrB;AACA,SAAG,MAAM;AACT,iBAAW;AACX;AAAA,IACF;AAEA,UAAM,YAAY,GAAG,cAAc,YAAY,QAAQ,KAAK,GAAG;AAE/D,UAAM,UAAU,CAAC,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,UAAK,QAAG;AACjE,QAAI,UAAU;AACd,QAAI,eAAsD;AAC1D,QAAI,aAAa;AAEjB,UAAM,eAAe,CAAC,QAAgB;AACpC,mBAAa;AACb,UAAI,aAAc,eAAc,YAAY;AAC5C,qBAAe,YAAY,MAAM;AAC/B,cAAM,QAAQ,KAAK,QAAQ,UAAU,QAAQ,MAAM,KAAK,QAAG;AAC3D,UAAE,OAAO,KAAK,IAAI,UAAU,QAAQ;AACpC;AAAA,MACF,GAAG,EAAE;AAAA,IACP;AAEA,UAAM,cAAc,MAAM;AACxB,UAAI,cAAc;AAAE,sBAAc,YAAY;AAAG,uBAAe;AAAA,MAAM;AACtE,QAAE,UAAU;AAAA,IACd;AAEA,UAAM,YAAY,KAAK,IAAI;AAC3B,QAAI,UAAU;AACd,QAAI,YAAY;AAChB,QAAI,YAAY;AAEhB,QAAI,QAAQ;AACV,QAAE,IAAI;AACN,QAAE,KAAK,KAAK,aAAa,CAAC,KAAK,IAAI,OAAO,YAAY,KAAK,IAAI,CAAC,CAAC;AAAA,CAAI;AACrE,QAAE,KAAK,IAAI,YAAY,OAAO,aAAa,kBAAkB,OAAO,QAAQ,CAAC;AAAA,CAAI;AACjF,QAAE,IAAI,sSAAsD,CAAC;AAC7D,QAAE,IAAI;AAAA,IACR;AAEA,UAAM,UAAU,CAAC,MAAc,QAAgB;AAC7C,kBAAY;AACZ,YAAM,YAAY,KAAK,IAAI,IAAI,aAAa,KAAM,QAAQ,CAAC;AAC3D,QAAE,KAAK,IAAI,KAAK,GAAG,KAAK,IAAI,UAAU,GAAG,CAAC;AAAA,CAAI;AAAA,IAChD;AAEA,UAAM,cAAc,CAAC,UAA0B;AAC7C,UAAI,CAAC,QAAQ;AAEX,YAAI,QAAQ,cAAe,SAAQ,OAAO,MAAM,KAAK,UAAU,KAAK,IAAI,IAAI;AAC5E;AAAA,MACF;AACA,cAAQ,MAAM,MAAM;AAAA,QAClB,KAAK;AACH,oBAAU,MAAM;AAChB,uBAAa,QAAQ,OAAO,IAAI,OAAO,QAAQ,KAAK,IAAI,SAAS,SAAS,UAAU,SAAS,EAAE,CAAC,EAAE;AAClG;AAAA,QAEF,KAAK;AACH,cAAI,OAAO,SAAS;AAClB,wBAAY;AACZ,kBAAM,QAAQ,MAAM,KAAK,MAAM,IAAI,EAAE,MAAM,GAAG,CAAC;AAC/C,uBAAW,QAAQ,OAAO;AACxB,gBAAE,KAAK,IAAI,OAAO,KAAK,UAAU,GAAG,EAAE,CAAC,CAAC;AAAA,CAAI;AAAA,YAC9C;AAAA,UACF;AACA;AAAA,QAEF,KAAK,aAAa;AAChB,gBAAM,WAAW,MAAM,KAAK,QAAQ,sBAAsB,EAAE;AAE5D,cAAI,aAAa,QAAQ;AACvB,kBAAM,OAAO,MAAM,MAAM,SAAS,KAAe,IAAI,UAAU,GAAG,EAAE;AACpE,yBAAa,GAAG,OAAO,GAAG,CAAC,IAAI,GAAG,EAAE;AAAA,UACtC,WAAW,aAAa,aAAa;AACnC,kBAAM,KAAK,MAAM,MAAM,IAAI,KAAe;AAC1C,kBAAM,OAAO,MAAM,MAAM,MAAM,KAAe;AAC9C;AACA,oBAAQ,MAAM,GAAG,GAAG,GAAG,KAAK,MAAM,CAAC,IAAI,KAAK,EAAE,CAAC,IAAI,IAAI,MAAM,OAAO,GAAG,CAAC,EAAE;AAC1E,yBAAa,QAAQ,OAAO,IAAI,OAAO,QAAQ,KAAK,IAAI,SAAS,SAAS,UAAU,SAAS,EAAE,CAAC,EAAE;AAAA,UACpG,WAAW,aAAa,aAAa;AACnC,kBAAM,MAAM,MAAM,MAAM,UAAU,KAAe;AACjD,kBAAM,MAAM,MAAM,MAAM,UAAU,KAAe;AACjD,kBAAM,MAAM,MAAM,MAAM,cAAc,KAAe;AACrD;AACA,oBAAQ,QAAQ,GAAG,GAAG,GAAG,KAAK,MAAM,CAAC,IAAI,KAAK,GAAG,CAAC,IAAI,IAAI,WAAM,MAAM,QAAG,CAAC,IAAI,KAAK,GAAG,CAAC,EAAE;AACzF,yBAAa,QAAQ,OAAO,IAAI,OAAO,QAAQ,KAAK,IAAI,SAAS,SAAS,UAAU,SAAS,EAAE,CAAC,EAAE;AAAA,UACpG,WAAW,aAAa,eAAe;AACrC,yBAAa,iBAAiB,IAAI,uBAAuB,CAAC,EAAE;AAAA,UAC9D,WAAW,aAAa,kBAAkB;AACxC,oBAAQ,KAAK,WAAI,GAAG,kCAA6B;AACjD,yBAAa,gBAAgB;AAAA,UAC/B,WAAW,aAAa,wBAAwB;AAC9C,oBAAQ,KAAK,WAAI,GAAG,4DAAuD;AAC3E,yBAAa,sBAAsB;AAAA,UACrC,WAAW,aAAa,uBAAuB;AAC7C,kBAAM,KAAK,MAAM,MAAM,YAAY;AACnC,oBAAQ,KAAK,WAAI,GAAG,KAAK,6BAA6B,KAAK,EAAE,CAAC,KAAK,mCAA8B;AACjG,yBAAa,qBAAqB;AAAA,UACpC,WAAW,aAAa,wBAAwB;AAC9C,oBAAQ,KAAK,WAAI,GAAG,4DAAuD;AAC3E,yBAAa,sBAAsB;AAAA,UACrC,WAAW,aAAa,YAAY;AAElC,kBAAM,KAAK,MAAM,MAAM,UAAU,KAAe,IAAI,UAAU,GAAG,GAAG;AACpE,oBAAQ,OAAO,GAAG,GAAG,GAAG,KAAK,aAAa,CAAC,IAAI,CAAC,EAAE;AAAA,UACpD,OAAO;AACL,yBAAa,GAAG,QAAQ,KAAK;AAAA,UAC/B;AACA;AAAA,QACF;AAAA,QAEA,KAAK;AAEH;AAAA,QAEF,KAAK;AACH,sBAAY;AACZ;AAAA,MACJ;AAAA,IACF;AAGA,UAAM,YAAY,OAAO,UAAkB,YAAsC;AAC/E,UAAI,CAAC,OAAQ,QAAO;AACpB,kBAAY;AACZ,QAAE,IAAI;AACN,QAAE,IAAI,sSAAsD,CAAC;AAC7D,QAAE,KAAK,OAAO,KAAK,GAAG,CAAC,CAAC,KAAK,KAAK,aAAa,CAAC,IAAI,QAAQ;AAAA,CAAI;AAChE,UAAI,QAAS,GAAE,QAAQ,IAAI,OAAO,CAAC;AAAA,CAAI;AAEvC,UAAI,CAAC,QAAQ,MAAM,OAAO;AACxB,UAAE,KAAK,IAAI,8DAAyD,CAAC;AAAA;AAAA,CAAM;AAC3E,eAAO;AAAA,MACT;AAEA,YAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,YAAM,SAAS,MAAM,IAAI,QAAgB,CAAAA,aAAW,GAAG,SAAS,KAAK,KAAK,QAAG,CAAC,KAAKA,QAAO,CAAC;AAC3F,SAAG,MAAM;AACT,QAAE,IAAI;AACN,aAAO,UAAU;AAAA,IACnB;AAEA,QAAI;AACF,YAAM,aAAa,QAAQ,IAAI,WAAW,aAAa,WAAW,MAAS;AAAA,IAC7E,SAAS,KAAK;AACZ,kBAAY;AACZ,YAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC9D,YAAM,SAAS,OAAO,QAAQ,sBAAsB,OAAK,eAAe,CAAC,CAAC;AAC1E,eAAS,oBAAoB,EAAE,WAAW,OAAO,OAAO,CAAC;AACzD,QAAE;AAAA,IAAO,KAAK,uBAAkB,CAAC,uBAAuB,MAAM;AAAA,CAAI;AAClE,SAAG,MAAM;AACT,iBAAW;AACX,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,gBAAY;AACZ,OAAG,WAAW,SAAS;AAKvB,sBAAkB,IAAI,WAAW,QAAQ,CAAC;AAE1C,UAAM,cAAe,KAAK,MAA6B,KAAK,KACvD,kBAAkB,GAAG,gBAAgB,SAAS,GAAG,GAAG,WAAW,SAAS,GAAG,cAAa,oBAAI,KAAK,GAAE,YAAY,CAAC;AACrH,OAAG,eAAe,WAAW,WAAW;AACxC,UAAM,QAAQ,GAAG,SAAS,SAAS;AACnC,UAAM,aAAa,KAAK,IAAI,IAAI,aAAa,KAAM,QAAQ,CAAC;AAE5D,YAAQ,uBAAuB;AAAA,MAC7B;AAAA,MACA,OAAO,MAAM;AAAA,MACb,OAAO,MAAM;AAAA,MACb,aAAa,WAAW,QAAQ;AAAA,IAClC,CAAC;AAED,QAAI,CAAC,QAAQ;AAEX,YAAM,aAAa,KAAK,IAAI,IAAI;AAChC,UAAI,QAAQ,eAAe;AACzB,gBAAQ,OAAO,MAAM,KAAK,UAAU,EAAE,MAAM,UAAU,WAAW,OAAO,MAAM,OAAO,OAAO,MAAM,OAAO,WAAW,CAAC,IAAI,IAAI;AAAA,MAC/H,OAAO;AACL,gBAAQ,OAAO,MAAM,KAAK;AAAA,UACxB,EAAE,WAAW,OAAO,OAAO,GAAG,SAAS,SAAS,GAAG,OAAO,GAAG,SAAS,SAAS,GAAG,WAAW;AAAA,UAC7F;AAAA,UAAM;AAAA,QACR,IAAI,IAAI;AAAA,MACV;AACA,gBAAU,IAAI,WAAW,OAAO,WAAW,CAAC,WAAW,CAAC;AACxD,SAAG,MAAM;AACT,iBAAW;AACX;AAAA,IACF;AAEA,MAAE,IAAI;AACN,MAAE,IAAI,sSAAsD,CAAC;AAC7D,MAAE,KAAK,MAAM,KAAK,MAAM,CAAC,CAAC,KAAK,KAAK,OAAO,MAAM,KAAK,CAAC,CAAC,WAAW,KAAK,OAAO,MAAM,KAAK,CAAC,CAAC,WAAW,IAAI,QAAQ,WAAW,GAAG,CAAC;AAAA,CAAI;AACtI,MAAE,IAAI;AAGN,UAAM,WAAW,GAAG,SAAS,SAAS;AACtC,UAAM,WAAW,GAAG,SAAS,SAAS;AAEtC,QAAI,SAAS,SAAS,KAAK,QAAQ,MAAM,OAAO;AAC9C,QAAE,IAAI;AACN,QAAE,IAAI,sSAAsD,CAAC;AAC7D,QAAE,KAAK,KAAK,QAAQ,CAAC,KAAK,KAAK,OAAO,SAAS,MAAM,CAAC,CAAC;AAAA,CAAqB;AAC5E,QAAE,IAAI,oEAAoE,CAAC;AAC3E,QAAE,IAAI;AAEN,YAAM,SAAS;AACf,YAAM,WAAW;AACjB,eAAS,QAAQ,CAAC,GAAG,MAAM;AACzB,cAAM,MAAM,OAAO,IAAI,CAAC,EAAE,SAAS,CAAC;AACpC,cAAM,KAAK,EAAE,GAAG,OAAO,MAAM,EAAE,UAAU,GAAG,MAAM;AAClD,cAAM,OAAO,IAAI,IAAI,EAAE,IAAI,IAAI,OAAO,QAAQ,CAAC;AAC/C,cAAM,OAAO,IAAI,GAAG,KAAK,MAAM,EAAE,aAAa,GAAG,CAAC,GAAG;AACrD,cAAM,MAAM,IAAI,EAAE,kBAAkB,aAAa,eAAQ,EAAE,kBAAkB,oBAAoB,eAAQ,EAAE;AAC3G,UAAE,KAAK,IAAI,GAAG,CAAC,KAAK,KAAK,QAAG,CAAC,IAAI,EAAE,KAAK,IAAI,KAAK,IAAI,GAAG,GAAG;AAAA,CAAI;AAAA,MACjE,CAAC;AAGD,UAAI,SAAS,SAAS,GAAG;AACvB,UAAE,IAAI;AACN,UAAE,KAAK,KAAK,OAAO,SAAS,MAAM,CAAC,CAAC;AAAA,CAA2B;AAC/D,mBAAW,KAAK,SAAS,MAAM,GAAG,EAAE,GAAG;AACrC,YAAE,KAAK,IAAI,IAAI,CAAC,GAAG,KAAK,EAAE,QAAQ,CAAC,IAAI,IAAI,WAAM,EAAE,eAAe,QAAG,CAAC,IAAI,KAAK,EAAE,QAAQ,CAAC,KAAK,IAAI,KAAK,MAAM,EAAE,aAAa,GAAG,IAAI,GAAG,CAAC;AAAA,CAAI;AAAA,QAC9I;AACA,YAAI,SAAS,SAAS,GAAI,GAAE,KAAK,IAAI,mBAAc,SAAS,SAAS,MAAM,aAAa,CAAC;AAAA,CAAI;AAAA,MAC/F;AAEA,QAAE,IAAI;AACN,YAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,YAAM,SAAS,MAAM,IAAI;AAAA,QAAgB,CAAAA,aACvC,GAAG,SAAS,KAAK,OAAO,GAAG,CAAC,gDAAgDA,QAAO;AAAA,MACrF;AACA,SAAG,MAAM;AAET,YAAM,WAAW,OAAO,KAAK,EAAE,MAAM,QAAQ,EAAE,IAAI,MAAM,EAAE,OAAO,OAAK,KAAK,KAAK,KAAK,SAAS,MAAM;AACrG,UAAI,SAAS,SAAS,GAAG;AACvB,mBAAW,OAAO,UAAU;AAC1B,gBAAM,OAAO,SAAS,MAAM,CAAC;AAC7B,cAAI,KAAM,IAAG,WAAW,WAAW,KAAK,EAAE;AAAA,QAC5C;AACA,UAAE;AAAA,IAAO,MAAM,QAAG,CAAC,KAAK,KAAK,OAAO,SAAS,MAAM,CAAC,CAAC;AAAA,CAAoB;AAAA,MAC3E,OAAO;AACL,UAAE;AAAA,IAAO,MAAM,QAAG,CAAC;AAAA,CAAoB;AAAA,MACzC;AAAA,IACF;AAGA,cAAU,IAAI,WAAW,OAAO,WAAW,CAAC,WAAW,CAAC;AAExD,UAAM,gBAAgBA,SAAQ,OAAO,WAAW,gBAAgB;AAChE,MAAE,IAAI;AACN,QAAIC,YAAW,aAAa,GAAG;AAC7B,QAAE,KAAK,MAAM,QAAG,CAAC,KAAK,KAAK,gBAAgB,CAAC,KAAK,IAAI,uBAAkB,CAAC;AAAA,CAAI;AAAA,IAC9E;AACA,MAAE,IAAI;AAGN,QAAI,QAAQ,MAAM,OAAO;AACvB,QAAE,IAAI,sSAAsD,CAAC;AAC7D,QAAE,KAAK,KAAK,aAAa,CAAC,KAAK,IAAI,gCAAgC,CAAC;AAAA,CAAI;AACxE,QAAE,IAAI,mFAAmF,CAAC;AAC1F,QAAE,IAAI;AAGN,kBAAY;AACZ,kBAAY;AAEZ,UAAI,oBAAoB;AACxB,aAAO,mBAAmB;AACxB,cAAM,aAAa,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AACnF,cAAM,eAAe,MAAM,IAAI;AAAA,UAAgB,CAAAD,aAC7C,WAAW,SAAS,KAAK,OAAO,QAAG,CAAC,mCAAmCA,QAAO;AAAA,QAChF;AACA,mBAAW,MAAM;AAEjB,YAAI,CAAC,aAAa,KAAK,GAAG;AACxB,8BAAoB;AACpB;AAAA,QACF;AAEA,cAAM,sBAAsB,aAAa,KAAK;AAC9C,UAAE,IAAI;AACN,UAAE,KAAK,KAAK,KAAK,QAAG,CAAC,CAAC,oBAAoB,KAAK,mBAAmB,CAAC;AAAA,CAAI;AACvE,UAAE,IAAI;AAEN,YAAI;AACF,gBAAM,aAAa,QAAQ,IAAI,WAAW,aAAa,WAAW,mBAAmB;AAAA,QACvF,SAAS,KAAK;AACZ,sBAAY;AACZ,YAAE;AAAA,IAAO,IAAI,QAAG,CAAC,YAAY,GAAG;AAAA,CAAI;AAAA,QACtC;AAEA,oBAAY;AACZ,cAAM,gBAAgB,GAAG,SAAS,SAAS;AAC3C,UAAE,IAAI;AACN,UAAE,IAAI,sSAAsD,CAAC;AAC7D,UAAE,KAAK,MAAM,KAAK,QAAG,CAAC,CAAC,gBAAgB,KAAK,OAAO,cAAc,KAAK,CAAC,CAAC,WAAW,KAAK,OAAO,cAAc,KAAK,CAAC,CAAC;AAAA,CAAU;AAC9H,UAAE,IAAI;AAGN,kBAAU,IAAI,WAAW,OAAO,WAAW,CAAC,WAAW,CAAC;AACxD,YAAIC,YAAW,aAAa,GAAG;AAC7B,YAAE,KAAK,MAAM,QAAG,CAAC,KAAK,KAAK,wBAAwB,CAAC;AAAA,CAAI;AAAA,QAC1D;AACA,UAAE,IAAI;AAAA,MACR;AAAA,IACF;AAEA,OAAG,MAAM;AAAA,EACX,CAAC;AAIH,UACG,QAAQ,qBAAqB,EAC7B,YAAY,2BAA2B,EACvC,OAAO,sBAAsB,oBAAoB,mBAAmB,EACpE,OAAO,qBAAqB,0CAA0C,EACtE,OAAO,CAAC,WAA+B,SAAS;AAC/C,UAAM,SAAS,cAAc,EAAE,WAAW,KAAK,OAAO,CAAC;AACvD,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAE1C,UAAM,UAAU,YACZ,GAAG,WAAW,SAAS,IACvB,GAAG,iBAAiB;AAExB,QAAI,CAAC,SAAS;AACZ,cAAQ,OAAO,MAAM,2BAAsB;AAC3C,SAAG,MAAM;AACT,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,UAAU,KAAK,UAAU,CAAC,WAAW,QAAQ,QAAQ,QAAQ,KAAK;AACxE,cAAU,IAAI,QAAQ,IAAI,KAAK,QAAQ,OAAO;AAC9C,YAAQ,OAAO,MAAM,uBAAkB,KAAK,MAAM;AAAA,CAAI;AAEtD,OAAG,MAAM;AAAA,EACX,CAAC;AAIH,UACG,QAAQ,uBAAuB,EAC/B,YAAY,oFAAoF,EAChG,OAAO,kBAAkB,sCAAsC,MAAM,EACrE,OAAO,uBAAuB,mCAAmC,EACjE,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,MAA0B,SAA6B,SAAS;AACvE,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,eAAW;AACX,QAAI;AAEF,YAAM,WAAW,GAAG,YAAY;AAChC,YAAM,YAAY,WAAW,SAAS,CAAC,GAAG;AAC1C,YAAM,SAAS,QAAQ,SAAS,CAAC,GAAG;AACpC,UAAI,CAAC,UAAU,CAAC,WAAW;AACzB,gBAAQ,OAAO,MAAM,uDAAkD;AACvE,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,UAAI,WAAW,WAAW;AACxB,gBAAQ,OAAO,MAAM,gDAA2C;AAChE,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,YAAM,IAAI,GAAG,aAAa,QAAQ,SAAS;AAC3C,YAAM,MAAM,KAAK,WAAW,SAAS,KAAK,UAAU,GAAG,MAAM,CAAC,IAC1D,KAAK,WAAW,YAAY,oBAAoB,CAAC,IACjD,eAAe,CAAC;AACpB,UAAI,KAAK,QAAQ;AACf,QAAAC,eAAc,KAAK,QAAQ,MAAM,IAAI;AACrC,gBAAQ,OAAO,MAAM,yBAAoB,KAAK,MAAM;AAAA,CAAI;AAAA,MAC1D,OAAO;AACL,gBAAQ,OAAO,MAAM,MAAM,IAAI;AAAA,MACjC;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM,UAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9E,cAAQ,WAAW;AAAA,IACrB,UAAE;AACA,SAAG,MAAM;AACT,iBAAW;AAAA,IACb;AAAA,EACF,CAAC;AAMH,UACG,QAAQ,yBAAyB,EACjC,YAAY,gFAAgF,EAC5F,OAAO,oBAAoB,0CAA0C,UAAU,EAC/E,OAAO,kBAAkB,gDAAgD,MAAM,EAC/E,OAAO,uBAAuB,mCAAmC,EACjE,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,WAA+B,SAAS;AAC/C,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,eAAW;AACX,QAAI;AACF,YAAM,UAAU,WAAW,KAAK,OAAO;AACvC,UAAI,CAAC,SAAS;AACZ,gBAAQ,OAAO,MAAM,4BAAuB,KAAK,OAAO,iBAAiB,aAAa,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,CAAK;AAC1H,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,YAAM,MAAM,aAAa,GAAG,iBAAiB,GAAG;AAChD,UAAI,CAAC,KAAK;AACR,gBAAQ,OAAO,MAAM,yEAAoE;AACzF,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,YAAM,SAAS,GAAG,aAAa,KAAK,OAAO;AAC3C,YAAM,MAAM,KAAK,WAAW,UAAU,KAAK,WAAW,cAAc,KAAK,WAAW,YAChF,uBAAuB,QAAQ,KAAK,MAAM,IAC1C,qBAAqB,MAAM;AAC/B,UAAI,KAAK,QAAQ;AACf,QAAAA,eAAc,KAAK,QAAQ,MAAM,IAAI;AACrC,gBAAQ,OAAO,MAAM,sCAAiC,KAAK,MAAM;AAAA,CAAI;AAAA,MACvE,OAAO;AACL,gBAAQ,OAAO,MAAM,MAAM,IAAI;AAAA,MACjC;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM,UAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9E,cAAQ,WAAW;AAAA,IACrB,UAAE;AACA,SAAG,MAAM;AACT,iBAAW;AAAA,IACb;AAAA,EACF,CAAC;AAOH,UACG,QAAQ,wBAAwB,EAChC,YAAY,sHAAsH,EAClI,OAAO,sBAAsB,mDAAmD,MAAM,EACtF,OAAO,mBAAmB,4EAA4E,EACtG,OAAO,eAAe,SAAS,EAC/B,OAAO,OAAO,MAA0B,SAA6B,SAAS;AAC7E,QAAI;AACJ,QAAI;AACF,cAAQ,kBAAkB,MAAM;AAAA,QAC9B,aAAa,KAAK;AAAA,QAClB,OAAO,KAAK,UAAU,CAAC,EAAE,MAAM,WAAW,KAAK,KAAK,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,SAAS,CAAC;AAAA,MACtF,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM,UAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9E,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,SAAS,cAAc,EAAE,GAAI,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,GAAI,MAAM,CAAC;AAC/E,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,eAAW;AACX,QAAI;AACF,YAAM,QAAQ,MAAM,SAAS,IAAI,QAAQ,EAAE,MAAM,SAAS,aAAa,MAAM,YAAY,CAAC;AAC1F,UAAI,CAAC,OAAO;AACV,gBAAQ,OAAO,MAAM,yEAAoE;AACzF;AAAA,MACF;AAEA,cAAQ,OAAO,MAAM,yBAAoB,MAAM,QAAQ,UAAU,MAAM,MAAM,MAAM;AAAA,CAAI;AAAA,IACzF,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM,UAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9E,cAAQ,WAAW;AAAA,IACrB,UAAE;AACA,SAAG,MAAM;AACT,iBAAW;AAAA,IACb;AAAA,EACF,CAAC;AAOH,UACG,QAAQ,UAAU,EAClB,YAAY,6DAA6D,EACzE,eAAe,mBAAmB,kDAAkD,EACpF,OAAO,UAAU,8DAA8D,KAAK,EACpF,OAAO,WAAW,oDAAoD,KAAK,EAC3E,OAAO,yBAAyB,2DAA2D,EAC3F,OAAO,eAAe,4BAA4B,EAClD,OAAO,OAAO,SAAS;AACtB,QAAI;AACJ,QAAI;AACF,YAAM,WAAW,KAAK,MAAM;AAAA,IAC9B,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM,UAAK,eAAe,cAAc,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACpF,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,QAAI,KAAK,GAAI,OAAM,cAAc,EAAE,GAAG,KAAK,QAAQ,KAAK,GAAG,CAAC;AAE5D,UAAM,MAAO,KAAK,gBAAgB,IAAI,UAAU,gBAAgB;AAChE,QAAI,CAAC,CAAC,QAAQ,QAAQ,aAAa,EAAE,SAAS,GAAG,GAAG;AAClD,cAAQ,OAAO,MAAM,oCAA+B,GAAG;AAAA,CAA0C;AACjG,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,QAAI,KAAK,QAAQ,KAAK,OAAO;AAC3B,cAAQ,OAAO,MAAM,oDAA+C;AACpE,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,OAAO,IAAI,UAAU;AAC3B,QAAI,KAAK,SAAS,CAAC,MAAM;AACvB,cAAQ,OAAO,MAAM,gEAA2D;AAChF,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,QAAI,MAAM;AACR,UAAI;AACF,gBAAQ,MAAM,oBAAI,KAAK,CAAC;AAAA,MAC1B,SAAS,KAAK;AACZ,gBAAQ,OAAO,MAAM,wBAAmB,IAAI,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACtG,gBAAQ,WAAW;AACnB;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK,IAAI,cAAc,IAAI,MAAM;AACvC,eAAW;AAEX,UAAM,OAAO,CAAC,MAAgC;AAC5C,UAAI,QAAQ,QAAQ;AAClB,gBAAQ,OAAO,MAAM,uBAAuB,CAAC,IAAI,IAAI;AAAA,MACvD,OAAO;AACL,cAAM,UAAU,EAAE,WAAW,EAAE,WAAW,eAAe,EAAE,iBAAiB,MAAM,SAAS,EAAE,MAAM,QAAQ;AAC3G,gBAAQ,OAAO,MAAM,KAAK,UAAU,OAAO,IAAI,IAAI;AAAA,MACrD;AAAA,IACF;AAEA,UAAM,QAAQ,YAA2B;AACvC,YAAM,IAAI,MAAM,QAAQ,KAAK,EAAE;AAC/B,SAAG,eAAe,EAAE,WAAW,EAAE,eAAe,EAAE,KAAK;AAGvD,wBAAkB,IAAI,EAAE,WAAW,KAAK,CAAC,MAAM,QAAQ,OAAO,MAAM,CAAC,CAAC;AACtE,WAAK,CAAC;AAAA,IACR;AAEA,QAAI,KAAK,OAAO;AACd,UAAI,UAAU;AACd,UAAI,QAA8C;AAIlD,YAAM,eAAe,KAAK,KAAK,KAAK;AACpC,UAAI,gBAA+B;AACnC,YAAM,WAAW,MAAY;AAC3B,YAAI,QAAS;AACb,cAAM,OAAO,QAAQ,MAAO,oBAAI,KAAK,CAAC;AACtC,cAAM,WAAW,KAAK,QAAQ;AAC9B,YAAI,KAAK,YAAY,MAAM,eAAe;AACxC,kBAAQ,yBAAyB,KAAK,YAAY,CAAC,EAAE;AACrD,0BAAgB,KAAK,YAAY;AAAA,QACnC;AACA,cAAM,YAAY,WAAW,KAAK,IAAI;AACtC,YAAI,YAAY,cAAc;AAE5B,kBAAQ,WAAW,UAAU,YAAY;AACzC;AAAA,QACF;AACA,gBAAQ,WAAW,MAAM;AACvB,gBAAM,YAAY;AAChB,gBAAI;AACF,oBAAM,MAAM;AAAA,YACd,SAAS,KAAK;AAEZ,uBAAS,yBAAyB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAAA,YACtF;AACA,4BAAgB;AAChB,qBAAS;AAAA,UACX,GAAG;AAAA,QACL,GAAG,KAAK,IAAI,GAAG,SAAS,CAAC;AAAA,MAC3B;AAGA,YAAM,OAAO,MAAY;AACvB,kBAAU;AACV,YAAI,MAAO,cAAa,KAAK;AAAA,MAC/B;AACA,cAAQ,KAAK,UAAU,IAAI;AAC3B,cAAQ,KAAK,WAAW,IAAI;AAC5B,eAAS;AACT;AAAA,IACF;AAGA,QAAI;AACF,YAAM,MAAM;AAAA,IACd,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM,UAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9E,cAAQ,WAAW;AAAA,IACrB,UAAE;AACA,SAAG,MAAM;AACT,iBAAW;AAAA,IACb;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,mBAAmB,EAC3B,YAAY,sBAAsB,EAClC,OAAO,CAAC,cAAuB;AAC9B,UAAM,SAAS,cAAc;AAC7B,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAE1C,UAAM,UAAU,YACZ,GAAG,WAAW,SAAS,IACvB,GAAG,iBAAiB;AAExB,QAAI,CAAC,SAAS;AACZ,cAAQ,OAAO,MAAM,2BAAsB;AAC3C,SAAG,MAAM;AACT,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,QAAQ,GAAG,SAAS,QAAQ,EAAE;AACpC,UAAM,QAAQ,GAAG,SAAS,QAAQ,EAAE;AAEpC,YAAQ,OAAO,MAAM;AAAA,WAAc,QAAQ,EAAE;AAAA,CAAI;AACjD,QAAI,QAAQ,KAAM,SAAQ,OAAO,MAAM,cAAc,QAAQ,IAAI;AAAA,CAAI;AACrE,YAAQ,OAAO,MAAM,cAAc,QAAQ,IAAI;AAAA,CAAI;AACnD,YAAQ,OAAO,MAAM,cAAc,QAAQ,SAAS;AAAA,CAAI;AACxD,QAAI,QAAQ,YAAa,SAAQ,OAAO,MAAM,cAAc,QAAQ,WAAW;AAAA,CAAI;AACnF,YAAQ,OAAO,MAAM,cAAc,MAAM,KAAK;AAAA,CAAI;AAClD,YAAQ,OAAO,MAAM,cAAc,MAAM,KAAK;AAAA,CAAI;AAClD,YAAQ,OAAO,MAAM,cAAc,MAAM,MAAM;AAAA,CAAI;AACnD,YAAQ,OAAO,MAAM,cAAc,MAAM,KAAK;AAAA,CAAI;AAElD,UAAM,SAAS,GAAG,UAAU,QAAQ,EAAE;AACtC,QAAI,OAAO,SAAS,GAAG;AACrB,cAAQ,OAAO,MAAM,wBAAwB;AAC7C,iBAAW,KAAK,OAAO,MAAM,GAAG,GAAG;AACjC,cAAM,KAAK,EAAE,eAAe,OAAO,MAAM,EAAE,cAAc,MAAM,QAAQ,CAAC,CAAC,SAAS;AAClF,gBAAQ,OAAO,MAAM,OAAO,EAAE,SAAS,KAAK,EAAE,OAAO,MAAM,EAAE,WAAW,IAAI,MAAM,GAAG,EAAE,CAAC,GAAG,EAAE;AAAA,CAAI;AAAA,MACnG;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,GAAG;AACpB,cAAQ,OAAO,MAAM,yBAAyB;AAC9C,iBAAW,QAAQ,MAAM,MAAM,GAAG,EAAE,GAAG;AACrC,gBAAQ,OAAO,MAAM,OAAO,KAAK,EAAE,KAAK,KAAK,IAAI,iBAAiB,KAAK,UAAU;AAAA,CAAK;AAAA,MACxF;AACA,UAAI,MAAM,SAAS,IAAI;AACrB,gBAAQ,OAAO,MAAM,eAAe,MAAM,SAAS,EAAE;AAAA,CAAS;AAAA,MAChE;AAAA,IACF;AAEA,YAAQ,OAAO,MAAM,IAAI;AACzB,OAAG,MAAM;AAAA,EACX,CAAC;AAEH,UACG,QAAQ,UAAU,EAClB,YAAY,mBAAmB,EAC/B,OAAO,MAAM;AACZ,UAAM,SAAS,cAAc;AAC7B,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,UAAM,WAAW,GAAG,YAAY;AAEhC,QAAI,SAAS,WAAW,GAAG;AACzB,cAAQ,OAAO,MAAM,qBAAqB;AAC1C,SAAG,MAAM;AACT;AAAA,IACF;AAEA,eAAW,WAAW,UAAU;AAC9B,YAAM,QAAQ,GAAG,SAAS,QAAQ,EAAE;AACpC,YAAM,SAAS,QAAQ,cAAc,WAAM;AAC3C,cAAQ,OAAO;AAAA,QACb,GAAG,MAAM,IAAI,QAAQ,GAAG,UAAU,GAAG,CAAC,CAAC,MAAM,QAAQ,IAAI,MACtD,QAAQ,UAAU,UAAU,GAAG,EAAE,CAAC,WAC5B,MAAM,KAAK,UAAU,MAAM,KAAK,GACtC,QAAQ,OAAO,KAAK,QAAQ,IAAI,KAAK,EAAE;AAAA;AAAA,MAC5C;AAAA,IACF;AAEA,OAAG,MAAM;AAAA,EACX,CAAC;AAIH,UACG,QAAQ,UAAU,EAClB,YAAY,sCAAsC,EAClD,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,SAAS;AAChB,UAAM,SAAS,cAAc;AAC7B,UAAM,KAAK,IAAI,cAAe,KAAyB,MAAM,OAAO,MAAM;AAC1E,UAAM,WAAW,GAAG,YAAY;AAEhC,UAAM,IAAI,MAAM,IAAI;AACpB,UAAM,IAAI,CAAC,MAAc,QAAQ,OAAO,MAAM,CAAC;AAE/C,MAAE,IAAI;AACN,MAAE,KAAK,EAAE,sBAAsB,CAAC;AAAA,CAAI;AACpC,MAAE,EAAE,oUAA2D,CAAC;AAEhE,QAAI,SAAS,WAAW,GAAG;AACzB,QAAE,KAAK,EAAE,8BAA8B,CAAC,IAAI,MAAM,+BAA+B,CAAC;AAAA;AAAA,CAAM;AACxF,SAAG,MAAM;AACT;AAAA,IACF;AAGA,QAAI,aAAa,GAAG,aAAa;AACjC,eAAW,KAAK,UAAU;AACxB,YAAM,KAAK,GAAG,SAAS,EAAE,EAAE;AAC3B,oBAAc,GAAG;AAAO,oBAAc,GAAG;AAAA,IAC3C;AAEA,MAAE,KAAK,EAAE,OAAO,SAAS,MAAM,CAAC,CAAC,kBAAe,EAAE,OAAO,UAAU,CAAC,CAAC,cAAW;AAChF,MAAE,GAAG,EAAE,OAAO,UAAU,CAAC,CAAC;AAAA;AAAA,CAAY;AAEtC,eAAW,WAAW,UAAU;AAC9B,YAAM,QAAQ,GAAG,SAAS,QAAQ,EAAE;AACpC,YAAM,QAAQ,GAAG,SAAS,QAAQ,EAAE;AACpC,YAAM,SAAS,QAAQ,cAAc,MAAM,QAAG,IAAI,OAAO,QAAG;AAC5D,YAAM,MAAM,QAAQ,UAAU,UAAU,GAAG,EAAE,EAAE,QAAQ,KAAK,GAAG;AAC/D,YAAM,MAAM,KAAK,QAAQ,GAAG,UAAU,GAAG,CAAC,CAAC;AAE3C,QAAE,KAAK,MAAM,IAAI,GAAG,KAAK,EAAE,MAAM,QAAQ,OAAO,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,GAAG,QAAQ,OAAO,KAAK,EAAE,QAAQ,IAAI,CAAC,KAAK,EAAE;AAAA,CAAI;AAChH,QAAE,OAAO,EAAE,YAAY,MAAM,QAAQ,cAAc,MAAM,KAAK,CAAC;AAAA,CAAI;AAGnE,YAAM,SAAS,oBAAI,IAAoB;AACvC,iBAAW,KAAK,MAAO,QAAO,IAAI,EAAE,OAAO,OAAO,IAAI,EAAE,IAAI,KAAK,KAAK,CAAC;AACvE,UAAI,OAAO,OAAO,GAAG;AACnB,cAAM,QAAQ,CAAC,GAAG,OAAO,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,IAAI;AAC1E,UAAE,OAAO,EAAE,KAAK,CAAC;AAAA,CAAI;AAAA,MACvB;AAGA,YAAM,WAAW,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,OAAK,EAAE,EAAE,EAAE,KAAK,IAAI;AAC3D,UAAI,SAAU,GAAE,OAAO,EAAE,YAAY,YAAY,MAAM,SAAS,IAAI,YAAO,GAAG,CAAC;AAAA,CAAI;AAEnF,QAAE,IAAI;AAAA,IACR;AAEA,OAAG,MAAM;AAAA,EACX,CAAC;AAIH,UACG,QAAQ,mBAAmB,EAC3B,YAAY,mDAAmD,EAC/D,OAAO,eAAe,SAAS,EAC/B,OAAO,eAAe,2CAA2C,EACjE,OAAO,OAAO,cAAkC,SAAS;AACxD,UAAM,SAAS,cAAc;AAC7B,UAAM,QAAS,KAA4B,SAAS,OAAO,OAAO;AAClE,UAAM,KAAK,IAAI,cAAe,KAAyB,MAAM,OAAO,MAAM;AAC1E,UAAM,WAAW,GAAG,YAAY;AAEhC,UAAM,UAAU,eACZ,SAAS,KAAK,OAAK,EAAE,GAAG,WAAW,YAAY,CAAC,IAChD,SAAS,OAAO,OAAK,EAAE,WAAW,EAAE,GAAG,EAAE,KAAK,SAAS,GAAG,EAAE;AAEhE,QAAI,CAAC,SAAS;AACZ,cAAQ,OAAO,MAAM,yCAAyC;AAC9D,SAAG,MAAM;AACT;AAAA,IACF;AAEA,UAAM,QAAQ,GAAG,SAAS,QAAQ,EAAE;AACpC,UAAM,QAAQ,GAAG,SAAS,QAAQ,EAAE;AAEpC,UAAM,IAAI,CAAC,MAAc,QAAQ,OAAO,MAAM,CAAC;AAE/C,MAAE,IAAI;AACN,MAAE,IAAI;AAAA,CAA2D,CAAC;AAClE,MAAE,KAAK,KAAK,kBAAkB,CAAC,KAAK,IAAI,aAAa,QAAQ,GAAG,UAAU,GAAG,CAAC,CAAC,CAAC;AAAA,CAAI;AACpF,MAAE,KAAK,IAAI,OAAO,MAAM,MAAM,IAAI,iBAAc,MAAM,SAAS,QAAQ,CAAC;AAAA,CAAI;AAC5E,MAAE,IAAI;AAAA,CAA2D,CAAC;AAClE,MAAE,KAAK,IAAI,0DAA0D,CAAC,EAAE;AAExE,UAAM,aAAa,MAAM,OAAO,mBAAmB,GAAG;AACtD,UAAM,SAAS,IAAI,UAAU;AAG7B,UAAM,eAAe,KAAK,UAAU;AAAA,MAClC,OAAO,MAAM,IAAI,QAAM;AAAA,QACrB,IAAI,EAAE;AAAA,QAAI,MAAM,EAAE;AAAA,QAAM,MAAM,EAAE;AAAA,QAChC,YAAY,EAAE;AAAA,QACd,UAAU,EAAE;AAAA,QACZ,MAAM,EAAE;AAAA,MACV,EAAE;AAAA,MACF,OAAO,MAAM,IAAI,QAAM,EAAE,MAAM,EAAE,UAAU,IAAI,EAAE,UAAU,KAAK,EAAE,cAAc,MAAM,EAAE,WAAW,EAAE;AAAA,IACvG,CAAC;AAED,UAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA,2BAKA,MAAM,MAAM,WAAW,MAAM,MAAM;AAAA,EAC5D,aAAa,UAAU,GAAG,IAAK,CAAC;AAI5B,UAAM,UAAsB,CAAC;AAE7B,UAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAE3E,UAAM,MAAM,MAAM,IAAI,QAAgB,CAAAF,aAAW,GAAG,SAAS,KAAK,KAAK,GAAG,CAAC,KAAKA,QAAO,CAAC;AAGxF,WAAO,MAAM;AACX,UAAI;AACJ,UAAI;AAAE,oBAAY,MAAM,IAAI;AAAA,MAAG,QAAQ;AAAE;AAAA,MAAO;AAEhD,UAAI,CAAC,UAAU,KAAK,EAAG;AACvB,UAAI,CAAC,QAAQ,QAAQ,IAAI,EAAE,SAAS,UAAU,KAAK,EAAE,YAAY,CAAC,EAAG;AAErE,cAAQ,KAAK,EAAE,MAAM,QAAQ,SAAS,UAAU,CAAC;AAEjD,UAAI;AACF,cAAM,OAAO,MAAM,OAAO,SAAS,OAAO;AAAA,UACxC;AAAA,UACA,YAAY;AAAA,UACZ,QAAQ;AAAA,UACR,UAAU;AAAA,QACZ,CAAC;AAED,cAAM,QAAQ,KAAK,QAAQ,KAAK,OAAK,EAAE,SAAS,MAAM,GAAG,QAAQ;AACjE,gBAAQ,KAAK,EAAE,MAAM,aAAa,SAAS,MAAM,CAAC;AAElD,UAAE,IAAI;AAEN,mBAAW,QAAQ,MAAM,MAAM,IAAI,GAAG;AACpC,YAAE,KAAK,IAAI;AAAA,CAAI;AAAA,QACjB;AACA,UAAE,IAAI;AAAA,MACR,SAAS,KAAK;AACZ,UAAE,KAAK,IAAI,QAAG,CAAC,YAAY,GAAG;AAAA;AAAA,CAAM;AAAA,MACtC;AAAA,IACF;AAEA,OAAG,MAAM;AACT,OAAG,MAAM;AACT,MAAE;AAAA,IAAO,IAAI,aAAa,CAAC;AAAA;AAAA,CAAM;AAAA,EACnC,CAAC;AAIH,UACG,QAAQ,MAAM,EACd,YAAY,yCAAyC,EACrD,OAAO,MAAM;AACZ,UAAM,MAAM,QAAQ,OAAO,MAAM,KAAK,QAAQ,MAAM;AACpD,UAAM,IAAI;AACV,UAAM,OAAO,MAAM,IAAI,IAAI,SAAI,OAAO,EAAE,CAAC,IAAI,IAAI;AACjD,UAAM,eAAe,SAAS,YAAY,SAAS,UAAU;AAE7D,QAAI,IAAI;AACR,QAAI,EAAE,wBAAwB,IAAI,OAAO,IAAI,MAAM,OAAO,IAAI,IAAI;AAClE,QAAI,IAAI,kEAAkE,CAAC;AAC3E,QAAI,IAAI,eAAe,YAAY;AAAA,CAAI,CAAC;AACxC,QAAI,IAAI;AACR,SAAK;AAGL,QAAI,EAAE,KAAK,4BAA4B,CAAC,CAAC;AACzC,QAAI,IAAI;AACR,QAAI,KAAK,MAAM,OAAO,CAAC;AAAA,CAAiD;AACxE,QAAI,KAAK,MAAM,OAAO,CAAC;AAAA,CAA8D;AACrF,QAAI,KAAK,MAAM,SAAS,CAAC;AAAA,CAAgE;AACzF,QAAI;AAAA,CAAgD;AACpD,QAAI,IAAI;AACR,QAAI,IAAI,uBAAuB,CAAC;AAChC,QAAI,IAAI,yBAAyB,CAAC;AAClC,QAAI,IAAI,8CAA8C,CAAC;AACvD,QAAI,IAAI,mDAAmD,CAAC;AAC5D,QAAI,IAAI;AACR,QAAI,IAAI,qBAAqB,CAAC;AAC9B,QAAI,IAAI,yDAAyD,CAAC;AAClE,QAAI,IAAI,6DAA6D,CAAC;AACtE,QAAI,IAAI,yDAAyD,CAAC;AAClE,QAAI,IAAI;AACR,QAAI,IAAI,kCAAkC,CAAC;AAC3C,QAAI,IAAI,2EAA2E,CAAC;AACpF,QAAI,IAAI,mFAAmF,CAAC;AAC5F,QAAI,IAAI,8EAA8E,CAAC;AACvF,QAAI,IAAI;AACR,SAAK;AAGL,QAAI,EAAE,KAAK,iBAAiB,CAAC,CAAC;AAC9B,QAAI,IAAI;AACR,QAAI,KAAK,MAAM,+BAA+B,CAAC;AAAA,CAAI;AACnD,QAAI;AAAA,CAAoF;AACxF,QAAI,mCAAmC,SAAS,sCAAsC,SAAS,aAAa,QAAQ;AAAA,CAAuC;AAC3J,QAAI;AAAA,CAAwC;AAC5C,QAAI,IAAI;AACR,QAAI,IAAI,gBAAgB,CAAC;AACzB,QAAI,IAAI,yEAAyE,CAAC;AAClF,QAAI,IAAI,iEAAiE,CAAC;AAC1E,QAAI,IAAI,kEAAkE,CAAC;AAC3E,QAAI,IAAI,qFAAqF,CAAC;AAC9F,QAAI,IAAI,+DAA+D,CAAC;AACxE,QAAI,IAAI,iFAAiF,CAAC;AAC1F,QAAI,IAAI,oDAAoD,CAAC;AAC7D,QAAI,IAAI;AACR,QAAI,IAAI,eAAe,CAAC;AACxB,QAAI,IAAI,0BAA0B,CAAC;AACnC,QAAI,IAAI,iDAAiD,CAAC;AAC1D,QAAI,IAAI;AACR,SAAK;AAGL,QAAI,EAAE,KAAK,uBAAuB,CAAC,CAAC;AACpC,QAAI,IAAI;AACR,QAAI,KAAK,MAAM,0CAA0C,CAAC;AAAA,CAAI;AAC9D,QAAI,IAAI,yFAAyF,CAAC;AAClG,QAAI,IAAI,4CAA4C,CAAC;AACrD,QAAI,IAAI;AACR,QAAI,KAAK,MAAM,wCAAwC,CAAC,OAAO,IAAI,6BAA6B,CAAC;AAAA,CAAI;AACrG,QAAI,KAAK,MAAM,+BAA+B,CAAC,gBAAgB,IAAI,mBAAmB,CAAC;AAAA,CAAI;AAC3F,QAAI,IAAI;AACR,SAAK;AAGL,QAAI,EAAE,KAAK,oBAAoB,CAAC,CAAC;AACjC,QAAI,IAAI;AACR,QAAI,OAAO,kEAAkE,CAAC;AAC9E,QAAI,IAAI,oXAAmE,CAAC;AAC5E,QAAI;AAAA,CAAgE;AACpE,QAAI,IAAI;AACR,SAAK;AAGL,QAAI,EAAE,KAAK,kBAAkB,CAAC,CAAC;AAC/B,QAAI,IAAI;AACR,QAAI,IAAI,qBAAqB,CAAC;AAC9B,QAAI,IAAI,gEAAiD,CAAC;AAC1D,QAAI,IAAI,+DAAgD,CAAC;AACzD,QAAI,IAAI,+EAAgE,CAAC;AACzE,QAAI,IAAI,gEAAiD,CAAC;AAC1D,QAAI,IAAI,oHAAgG,CAAC;AACzG,QAAI,IAAI,sEAAuD,CAAC;AAChE,QAAI,IAAI,iDAAiD,CAAC;AAC1D,QAAI,IAAI,iEAAiE,CAAC;AAC1E,QAAI,IAAI,qEAAqE,CAAC;AAC9E,QAAI,IAAI,oEAAoE,CAAC;AAC7E,QAAI,IAAI,yEAA0D,CAAC;AACnE,QAAI,IAAI;AACR,SAAK;AAGL,QAAI,EAAE,KAAK,YAAY,CAAC,CAAC;AACzB,QAAI,IAAI;AACR,QAAI,IAAI,sDAAsD,CAAC;AAC/D,QAAI,IAAI,6EAA6E,CAAC;AACtF,QAAI,IAAI,2EAA2E,CAAC;AACpF,QAAI,IAAI,2DAAsD,CAAC;AAC/D,QAAI,IAAI;AACR,SAAK;AAGL,QAAI,EAAE,KAAK,WAAW,CAAC,CAAC;AACxB,QAAI,IAAI;AACR,QAAI,IAAI,0CAA0C,CAAC;AACnD,QAAI,8CAA8C;AAClD,QAAI,kBAAkB;AACtB,QAAI,IAAI;AACR,QAAI,IAAI,8CAA8C,CAAC;AACvD,QAAI,QAAQ;AACV,UAAI,yCAAyC;AAAA,IAC/C,OAAO;AACL,UAAI,yCAAyC;AAAA,IAC/C;AACA,QAAI,IAAI;AACR,QAAI,IAAI,aAAa,CAAC;AACtB,QAAI,mCAAmC;AACvC,QAAI,IAAI;AACR,QAAI,IAAI,yCAAyC,CAAC;AAClD,QAAI,IAAI;AAAA,EACV,CAAC;AAIH,UACG,QAAQ,WAAW,EACnB,YAAY,qFAAqF,EACjG,OAAO,YAAY;AAClB,UAAM,EAAE,iBAAiB,IAAI,MAAM,OAAO,yBAAgB;AAC1D,UAAM,MAAM,CAAC,MAAc,QAAQ,OAAO,MAAM,CAAC;AAEjD,YAAQ,OAAO,MAAM,6BAA6B;AAClD,UAAM,QAAQ,MAAM,iBAAiB;AAErC,QAAI,MAAM,WAAW,GAAG;AACtB,UAAI,iGAA4F;AAChG;AAAA,IACF;AAGA,UAAM,WAAW,oBAAI,IAA0B;AAC/C,eAAW,KAAK,OAAO;AACrB,UAAI,CAAC,SAAS,IAAI,EAAE,MAAM,EAAG,UAAS,IAAI,EAAE,QAAQ,CAAC,CAAC;AACtD,eAAS,IAAI,EAAE,MAAM,EAAG,KAAK,CAAC;AAAA,IAChC;AAEA,eAAW,CAAC,QAAQ,OAAO,KAAK,UAAU;AACxC,UAAI,KAAK,KAAK,KAAK,OAAO,YAAY,CAAC,EAAE,CAAC,IAAI,IAAI,MAAM,QAAQ,MAAM;AAAA,CAAW,CAAC;AAClF,UAAI,IAAI,4PAA+C,CAAC;AACxD,iBAAW,KAAK,SAAS;AACvB,cAAM,YAAa,EAAE,aAAa,WAAW,EAAE,SAAS,OAAS,EAAE,aAAa,UAAU,EAAE,SAAS;AACrG,cAAM,UAAU,YAAY,KAAK,IAAI,EAAE,IAAI;AAC3C,YAAI,KAAK,KAAK,EAAE,WAAW,KAAK,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,OAAO,CAAC;AAAA,CAAI;AAAA,MACnE;AACA,UAAI,IAAI;AAAA,IACV;AAEA,QAAI,IAAI,YAAY,MAAM,MAAM;AAAA;AAAA,CAAmB,CAAC;AACpD,QAAI,IAAI,SAAS,IAAI,kCAAkC,IAAI,4DAAuD,CAAC;AAAA,EACrH,CAAC;AAIH,UACG,QAAQ,MAAM,EACd,YAAY,wEAAwE,EACpF,eAAe,iBAAiB,mDAAmD,EACnF,OAAO,kBAAkB,qCAAqC,EAC9D,OAAO,sBAAsB,4CAAuC,QAAQ,EAC5E,OAAO,eAAe,SAAS,EAC/B,OAAO,OAAO,SAAS;AACtB,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,eAAW;AACX,QAAI;AACF,YAAM,YAAY,KAAK,WAAW,GAAG,iBAAiB,UAAU,GAAG;AACnE,UAAI,CAAC,WAAW;AACd,gBAAQ,OAAO,MAAM,uEAAkE;AACvF,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,YAAM,QAAQ,KAAK;AACnB,UAAI,CAAC,CAAC,UAAU,QAAQ,KAAK,EAAE,SAAS,KAAK,GAAG;AAC9C,gBAAQ,OAAO,MAAM,4BAAuB,KAAK;AAAA,CAA2B;AAC5E,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,YAAM,SAAS,IAAI,cAAc,EAAE,UAAU,KAAK,MAAM,OAAO,IAAI,UAAU,CAAC;AAC9E,YAAM,IAAI,MAAM,YAAY,IAAI,WAAW,MAAM;AACjD,cAAQ,OAAO,MAAM,gBAAW,EAAE,OAAO,aAAa,EAAE,SAAS,kBAAkB,EAAE,KAAK,UAAU,EAAE,MAAM;AAAA,CAAI;AAChH,UAAI,EAAE,aAAa,SAAS,GAAG;AAC7B,gBAAQ,OAAO,MAAM,oBAAoB,EAAE,aAAa,MAAM,GAAG,EAAE,EAAE,KAAK,IAAI,CAAC,GAAG,EAAE,aAAa,SAAS,KAAK,YAAO,EAAE;AAAA,CAAI;AAAA,MAC9H;AACA,UAAI,EAAE,YAAY,KAAK,EAAE,QAAQ,EAAG,SAAQ,WAAW;AAAA,IACzD,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM,UAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9E,cAAQ,WAAW;AAAA,IACrB,UAAE;AACA,SAAG,MAAM;AACT,iBAAW;AAAA,IACb;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,MAAM,EACd,YAAY,4DAA4D,EACxE,OAAO,iBAAiB,iCAAiC,EACzD,OAAO,kBAAkB,gDAAgD,EACzE,OAAO,gBAAgB,8DAA8D,EACrF,OAAO,eAAe,SAAS,EAC/B,OAAO,OAAO,SAAS;AACtB,UAAM,SAAS,cAAc,EAAE,GAAI,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,GAAI,GAAI,KAAK,MAAM,EAAE,cAAc,KAAK,IAAI,IAAI,CAAC,EAAG,CAAC;AACzH,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,UAAM,YAAY,KAAK,WAAW,GAAG,cAAc,YAAY,QAAQ,KAAK,GAAG;AAE/E,UAAM,MAAM,CAAC,MAAc,QAAQ,OAAO,MAAM,CAAC;AACjD,UAAM,IAAM,CAAC,MAAc,QAAQ,OAAO,MAAM,CAAC;AAGjD,QAAI,KAAK,MAAM;AACb,UAAI;AACJ,UAAI;AACF,cAAM,KAAK,MAAMD,cAAaC,SAAQ,KAAK,IAAI,GAAG,MAAM,CAAC;AAAA,MAC3D,SAAS,GAAG;AACV,UAAE,IAAI;AAAA,iCAA+B,CAAC;AAAA;AAAA,CAAM,CAAC;AAC7C,gBAAQ,WAAW;AACnB;AAAA,MACF;AAEA,UAAI,CAAC,MAAM,QAAQ,GAAG,GAAG;AACvB,UAAE,IAAI,0FAAqF,CAAC;AAC5F,gBAAQ,WAAW;AACnB;AAAA,MACF;AAEA,UAAIG,SAAQ;AACZ,iBAAW,SAAS,KAAkC;AACpD,cAAM,OAAO,MAAM,MAAM;AACzB,cAAM,OAAO,MAAM,MAAM;AACzB,cAAMC,QAAO,MAAM,MAAM;AACzB,cAAM,OAAO,MAAM,MAAM;AACzB,cAAM,OAAQ,MAAM,MAAM,KAA8B,CAAC;AACzD,cAAM,WAAY,MAAM,UAAU,KAA6C,CAAC;AAEhF,YAAI,CAAC,QAAQ,CAAC,MAAM;AAClB,YAAE,OAAO,qCAAgC,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI,CAAC;AACnE;AAAA,QACF;AAEA,cAAM,KAAKA,QACP,GAAG,IAAI,IAAIA,KAAI,GAAG,OAAO,MAAM,OAAO,EAAE,KACxC,GAAG,IAAI,IAAI,KAAK,YAAY,EAAE,QAAQ,QAAQ,GAAG,CAAC;AAEtD,WAAG,WAAW,WAAW;AAAA,UACvB;AAAA,UACA;AAAA,UACA;AAAA,UACA,eAAe;AAAA,UACf,YAAY;AAAA,UACZ,UAAU,EAAE,GAAG,UAAU,GAAIA,QAAO,EAAE,MAAAA,MAAK,IAAI,CAAC,GAAI,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC,EAAG;AAAA,UAC9E;AAAA,QACF,CAAC;AACD,YAAI,KAAK,MAAM,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC,KAAK,IAAI,MAAM,OAAO,GAAG,CAAC;AAAA,CAAI;AAC9D,QAAAD;AAAA,MACF;AAEA,SAAG,WAAW,SAAS;AACvB,QAAE;AAAA,IAAO,MAAM,KAAK,MAAM,CAAC,CAAC,KAAKA,MAAK,iBAAiB,IAAI,cAAc,SAAS,CAAC;AAAA;AAAA,CAAM;AACzF;AAAA,IACF;AAGA,UAAM,EAAE,WAAW,IAAI,MAAM,OAAO,qBAAY;AAEhD,QAAI,CAAC,QAAQ,MAAM,OAAO;AACxB,QAAE,IAAI,uFAAkF,CAAC;AACzF,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,MAAE,IAAI;AACN,MAAE,IAAI,sSAAsD,CAAC;AAC7D,MAAE,KAAK,8BAA8B,CAAC;AACtC,MAAE,IAAI,2DAA2D,CAAC;AAClE,MAAE,IAAI,sSAAsD,CAAC;AAC7D,MAAE,IAAI;AAEN,UAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,UAAM,MAAM,CAAC,MAA+B,IAAI,QAAQ,SAAO,GAAG,SAAS,GAAG,GAAG,CAAC;AAElF,QAAI,QAAQ;AAEZ,UAAM,WAAW,WAAW,IAAI,CAAC,GAAG,MAAM,GAAG,KAAK,IAAI,GAAG,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,MAAM;AAGrG,WAAO,MAAM;AACX,QAAE,IAAI;AACN,QAAE,IAAI,iBAAiB,CAAC;AACxB,QAAE,KAAK,QAAQ;AAAA;AAAA,CAAM;AAErB,YAAM,aAAa,MAAM,IAAI,KAAK,KAAK,MAAM,CAAC,IAAI,IAAI,gCAAgC,CAAC,IAAI,GAAG,KAAK;AACnG,UAAI,CAAC,UAAW;AAEhB,UAAI;AACJ,YAAM,QAAQ,SAAS,WAAW,EAAE;AACpC,UAAI,CAAC,MAAM,KAAK,KAAK,SAAS,KAAK,SAAS,WAAW,QAAQ;AAC7D,mBAAW,WAAW,QAAQ,CAAC;AAAA,MACjC,WAAW,WAAW,SAAS,SAAsC,GAAG;AACtE,mBAAW;AAAA,MACb,OAAO;AACL,UAAE,OAAO,4BAAuB,SAAS;AAAA,CAAK,CAAC;AAC/C;AAAA,MACF;AAEA,YAAM,QAAQ,MAAM,IAAI,KAAK,KAAK,MAAM,CAAC,IAAI,IAAI,0BAA0B,CAAC,IAAI,GAAG,KAAK;AACxF,UAAI,CAAC,MAAM;AAAE,UAAE,IAAI,iBAAiB,CAAC;AAAG;AAAA,MAAU;AAElD,YAAM,WAAW,MAAM,IAAI,KAAK,KAAK,WAAW,CAAC,IAAI,IAAI,wBAAwB,CAAC,IAAI,GAAG,KAAK;AAC9F,YAAM,WAAW,MAAM,IAAI,KAAK,KAAK,MAAM,CAAC,IAAI,IAAI,YAAY,CAAC,IAAI,GAAG,KAAK;AAC7E,YAAM,WAAW,MAAM,IAAI,KAAK,KAAK,MAAM,CAAC,IAAI,IAAI,6BAA6B,CAAC,IAAI,GAAG,KAAK;AAE9F,YAAMC,QAAO,WAAW;AACxB,YAAM,OAAO,UAAU,SAAS,SAAS,EAAE,IAAI;AAC/C,YAAM,OAAO,UAAU,QAAQ,MAAM,GAAG,EAAE,IAAI,OAAK,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IAAI,CAAC;AAEhF,YAAM,KAAKA,QACP,GAAG,QAAQ,IAAIA,KAAI,GAAG,OAAO,MAAM,OAAO,EAAE,KAC5C,GAAG,QAAQ,IAAI,KAAK,YAAY,EAAE,QAAQ,QAAQ,GAAG,CAAC;AAE1D,SAAG,WAAW,WAAW;AAAA,QACvB;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA,eAAe;AAAA,QACf,YAAY;AAAA,QACZ,UAAU,EAAE,GAAIA,QAAO,EAAE,MAAAA,MAAK,IAAI,CAAC,GAAI,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC,EAAG;AAAA,QACjE;AAAA,MACF,CAAC;AACD,UAAI,KAAK,MAAM,GAAG,CAAC,KAAK,KAAK,EAAE,CAAC;AAAA,CAAI;AACpC;AAEA,YAAM,SAAS,MAAM,IAAI,KAAK,IAAI,yBAAyB,CAAC,IAAI,GAAG,KAAK,EAAE,YAAY;AACtF,UAAI,UAAU,OAAO,UAAU,KAAM;AAAA,IACvC;AAEA,OAAG,MAAM;AACT,OAAG,WAAW,SAAS;AACvB,MAAE,IAAI;AACN,MAAE,IAAI,sSAAsD,CAAC;AAC7D,MAAE,KAAK,MAAM,KAAK,MAAM,CAAC,CAAC,KAAK,KAAK,QAAQ,UAAU,IAAI,MAAM,EAAE;AAAA,CAAU;AAC5E,MAAE,KAAK,IAAI,cAAc,SAAS,CAAC;AAAA,CAAI;AACvC,MAAE,KAAK,IAAI,oCAAoC,SAAS,CAAC;AAAA;AAAA,CAAM;AAAA,EACjE,CAAC;AAIH,UACG,QAAQ,QAAQ,EAChB,YAAY,uCAAuC,EACnD,OAAO,YAAY;AAClB,UAAM,EAAE,UAAAC,UAAS,IAAI,MAAM,OAAO,eAAoB;AACtD,UAAM,EAAE,YAAAJ,aAAY,cAAAF,cAAa,IAAI,MAAM,OAAO,IAAS;AAC3D,UAAM,EAAE,MAAAO,MAAK,IAAI,MAAM,OAAO,MAAW;AACzC,UAAM,MAAM,CAAC,MAAc,QAAQ,OAAO,MAAM,CAAC;AACjD,UAAM,KAAM,CAAC,QAAgB,IAAI,4BAAuB,GAAG;AAAA,CAAI;AAC/D,UAAM,MAAM,CAAC,QAAgB,IAAI,4BAAuB,GAAG;AAAA,CAAI;AAC/D,UAAM,OAAO,CAAC,QAAgB,IAAI,4BAAuB,GAAG;AAAA,CAAI;AAChE,UAAMC,OAAO,CAAC,MAAc,UAAU,CAAC;AACvC,QAAI,UAAU;AAEd,QAAI,wDAAmD;AACvD,QAAIA,KAAI,4MAAuC,CAAC;AAGhD,UAAM,UAAU,QAAQ,SAAS;AACjC,UAAM,CAAC,KAAK,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI,MAAM;AAC7C,SAAK,SAAS,MAAM,IAAI;AACtB,SAAG,WAAW,OAAO,EAAE;AAAA,IACzB,OAAO;AACL,UAAI,WAAW,OAAO,uBAAkB;AACxC,gBAAU;AAAA,IACZ;AAGA,QAAI;AACF,YAAM,IAAIF,UAAS,oBAAoB,EAAE,OAAO,OAAO,CAAC,EAAE,SAAS,EAAE,KAAK;AAC1E,SAAG,eAAeE,KAAI,CAAC,CAAC,EAAE;AAAA,IAC5B,QAAQ;AACN,UAAI,gEAA2D;AAC/D,gBAAU;AAAA,IACZ;AAGA,UAAM,YAAY,QAAQ,QAAQ,IAAI,iBAAiB;AACvD,UAAM,OAAO,QAAQ,IAAI,QAAQ,QAAQ,IAAI,eAAe;AAC5D,QAAI,WAAW;AACf,QAAI;AACF,YAAM,QAAQ,KAAK,MAAMR,cAAaO,MAAK,MAAM,WAAW,mBAAmB,GAAG,MAAM,CAAC;AACzF,YAAM,QAAQ,MAAM,eAAe;AACnC,iBAAW,OAAO,QAAQ,aAAa,MAAM,YAAY,MAAM,aAAa,EAAE,SAAS;AAAA,IACzF,QAAQ;AAAA,IAAsB;AAE9B,QAAI,WAAW;AACb,SAAG,uBAAuB;AAAA,IAC5B,WAAW,UAAU;AACnB,SAAG,6BAA6B;AAAA,IAClC,OAAO;AACL,UAAI,qFAAgF;AACpF,gBAAU;AAAA,IACZ;AAGA,QAAI;AACF,YAAM,IAAID,UAAS,4EAA4E,EAAE,OAAO,OAAO,CAAC,EAAE,SAAS,EAAE,MAAM,IAAI,EAAE,CAAC,GAAG,KAAK,KAAK;AACvJ,SAAG,YAAYE,KAAI,KAAK,aAAa,CAAC,EAAE;AAAA,IAC1C,QAAQ;AACN,WAAK,sBAAsBA,KAAI,yDAAoD,CAAC,EAAE;AAAA,IACxF;AAGA,UAAM,YAA6C;AAAA,MACjD,CAAC,OAAU,iBAAoB,4CAAuC;AAAA,MACtE,CAAC,UAAU,oBAAoB,uDAAkD;AAAA,MACjF,CAAC,MAAU,gBAAoB,wDAAmD;AAAA,IACpF;AACA,eAAW,CAAC,MAAM,KAAK,IAAI,KAAK,WAAW;AACzC,UAAI;AACF,QAAAF,UAAS,KAAK,EAAE,OAAO,OAAO,CAAC;AAC/B,WAAG,GAAG,IAAI,KAAKE,KAAI,4BAA4B,CAAC,EAAE;AAAA,MACpD,QAAQ;AACN,aAAK,GAAG,IAAI,eAAeA,KAAI,iCAA4B,IAAI,CAAC,EAAE;AAAA,MACpE;AAAA,IACF;AAGA,UAAM,aAA8C;AAAA,MAClD,CAAC,UAAU,oBAAoB,KAAK;AAAA,IACtC;AAEA,QAAI,QAAQ;AACV,iBAAW,KAAK,CAAC,qCAAqC,qFAAqF,OAAO,CAAC;AAAA,IACrJ,WAAW,QAAQ;AACjB,iBAAW,KAAK,CAAC,QAAQ,0BAA0B,QAAQ,CAAC;AAAA,IAC9D,OAAO;AACL,iBAAW,KAAK,CAAC,MAAM,gBAAgB,OAAO,CAAC;AAAA,IACjD;AACA,eAAW,CAAC,MAAM,GAAG,KAAK,YAAY;AACpC,UAAI;AACF,QAAAF,UAAS,KAAK,EAAE,OAAO,QAAQ,SAAS,IAAO,CAAC;AAChD,WAAG,GAAG,IAAI,KAAKE,KAAI,kBAAkB,CAAC,EAAE;AAAA,MAC1C,QAAQ;AACN,aAAK,GAAG,IAAI,eAAeA,KAAI,8BAAyB,OAAO,kBAAkB,CAAC,EAAE;AAAA,MACtF;AAAA,IACF;AAGA,UAAM,QAAQD,MAAK,MAAM,cAAc;AACvC,QAAIL,YAAW,KAAK,GAAG;AACrB,SAAG,mBAAmBM,KAAI,yBAAyB,CAAC,EAAE;AAAA,IACxD,OAAO;AACL,WAAK,wCAAwCA,KAAI,qCAAgC,CAAC;AAAA,IACpF;AAEA,QAAIA,KAAI,4MAAuC,CAAC;AAChD,QAAI,SAAS;AACX,UAAI,oFAA+E;AAAA,IACrF,OAAO;AACL,UAAI,8EAA8E;AAClF,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF,CAAC;AAIH,UACG,QAAQ,OAAO,EACf,YAAY,oCAAoC,EAChD,OAAO,uBAAuB,qCAAqC,IAAI,EACvE,OAAO,8BAA8B,mFAAmF,EACxH,OAAO,eAAe,SAAS,EAC/B,OAAO,aAAa,wDAAwD,KAAK,EACjF,OAAO,CAAC,SAAS;AAChB,UAAM,OAAO,SAAS,KAAK,WAAW,EAAE;AACxC,QAAI,OAAO,MAAM,IAAI,KAAK,OAAO,GAAG;AAClC,cAAQ,OAAO,MAAM,0BAA0B,KAAK,SAAS;AAAA,CAAoB;AACjF,cAAQ,WAAW;AACnB;AAAA,IACF;AAEA,UAAM,SAAS,cAAc,EAAE,GAAI,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,EAAG,CAAC;AACxE,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,UAAM,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,KAAK,GAAI,EAAE,YAAY;AAG7E,QAAI,KAAK,oBAAoB,QAAW;AACtC,YAAM,YAAY,SAAS,KAAK,iBAAiB,EAAE;AACnD,UAAI,OAAO,MAAM,SAAS,KAAK,YAAY,GAAG;AAC5C,gBAAQ,OAAO,MAAM,iCAAiC,KAAK,eAAe;AAAA,CAAoB;AAC9F,gBAAQ,WAAW;AACnB,WAAG,MAAM;AACT;AAAA,MACF;AACA,YAAM,cAAc,IAAI,KAAK,KAAK,IAAI,IAAI,YAAY,KAAK,KAAK,KAAK,GAAI,EAAE,YAAY;AACvF,UAAI,KAAK,QAAQ;AACf,gBAAQ,OAAO,MAAM,2DAA2D,SAAS;AAAA,CAAW;AAAA,MACtG,OAAO;AACL,cAAM,UAAU,GAAG,qBAAqB,WAAW;AACnD,gBAAQ,uBAAuB,EAAE,SAAS,eAAe,UAAU,CAAC;AACpE,gBAAQ,OAAO,MAAM,WAAW,OAAO,8BAA8B,SAAS;AAAA,CAAU;AAAA,MAC1F;AAAA,IACF;AAEA,UAAM,WAAW,GAAG,YAAY,EAAE,OAAO,OAAK,EAAE,YAAY,MAAM;AAElE,QAAI,SAAS,WAAW,GAAG;AACzB,cAAQ,OAAO,MAAM,0BAA0B,IAAI;AAAA,CAAU;AAC7D,SAAG,MAAM;AACT;AAAA,IACF;AAEA,QAAI,KAAK,QAAQ;AACf,cAAQ,OAAO,MAAM,gBAAgB,SAAS,MAAM,0BAA0B,IAAI;AAAA,CAAU;AAC5F,iBAAW,KAAK,UAAU;AACxB,cAAM,QAAQ,GAAG,SAAS,EAAE,EAAE;AAC9B,gBAAQ,OAAO,MAAM,KAAK,EAAE,GAAG,UAAU,GAAG,CAAC,CAAC,KAAK,EAAE,UAAU,UAAU,GAAG,EAAE,CAAC,WAAW,MAAM,KAAK,UAAU,MAAM,KAAK;AAAA,CAAI;AAAA,MAChI;AAAA,IACF,OAAO;AACL,YAAM,UAAU,GAAG,cAAc,MAAM;AACvC,cAAQ,mBAAmB,EAAE,SAAS,eAAe,KAAK,CAAC;AAC3D,cAAQ,OAAO,MAAM,WAAW,OAAO,0BAA0B,IAAI;AAAA,CAAU;AAAA,IACjF;AAEA,OAAG,MAAM;AAAA,EACX,CAAC;AAIH,UACG,QAAQ,cAAc,EACtB,YAAY,+CAA+C,EAC3D,OAAO,MAAM;AACZ,MAAE,OAAO,KAAK,wBAAwB,IAAI,MAAM;AAChD,eAAW,KAAK,YAAY,GAAG;AAC7B,QAAE,KAAK,MAAM,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC,IAAI,KAAK,EAAE,MAAM,OAAO,EAAE,CAAC,CAAC,IAAI,IAAI,EAAE,MAAM,CAAC;AAAA,CAAI;AAC9E,UAAI,EAAE,KAAM,GAAE,KAAK,IAAI,OAAO,EAAE,CAAC,IAAI,IAAI,YAAO,EAAE,IAAI,CAAC;AAAA,CAAI;AAAA,IAC7D;AACA,MAAE,OAAO,IAAI,eAAe,GAAG,iDAAiD,IAAI,MAAM;AAAA,EAC5F,CAAC;AAEH,UACG,QAAQ,SAAS,EACjB,YAAY,2FAA4F,EACxG,eAAe,iBAAiB,qCAAqC,EACrE,OAAO,YAAY,0CAA0C,KAAK,EAClE,OAAO,aAAa,0CAA0C,KAAK,EACnE,OAAO,aAAa,uCAAuC,KAAK,EAChE,OAAO,cAAc,4EAA4E,KAAK,EACtG,OAAO,iBAAiB,2BAA2B,mBAAmB,EACtE,OAAO,UAAU,0DAA0D,KAAK,EAChF,OAAO,eAAe,6BAA6B,EACnD,OAAO,eAAe,gCAAgC,EACtD,OAAO,kBAAkB,mCAAmC,EAC5D,OAAO,CAAC,SAAS;AAChB,UAAM,OAAO,UAAU,KAAK,MAAM;AAClC,QAAI,CAAC,MAAM;AACT,eAAS,mBAAmB,KAAK,MAAM,YAAY,GAAG,iCAAiC;AACvF,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,QAAQ,KAAK,UAAU,YAAY;AACzC,UAAM,cAAwB,CAAC;AAC/B,QAAI,KAAK,GAAI,aAAY,KAAK,QAAQ,KAAK,EAAE;AAC7C,QAAI,KAAK,QAAS,aAAY,KAAK,aAAa,KAAK,OAAO;AAC5D,UAAM,QAAQ,mBAAmB;AAAA,MAC/B,WAAW,KAAK,OAAO,SAAS;AAAA,MAChC,GAAI,KAAK,MAAM,EAAE,KAAK,KAAK,IAAI,IAAI,CAAC;AAAA,MACpC,GAAI,YAAY,SAAS,EAAE,YAAY,IAAI,CAAC;AAAA,IAC9C,CAAC;AACD,QAAI,KAAK,UAAU;AACjB,UAAI,KAAK,WAAW,UAAU;AAC5B,UAAE,OAAO,KAAK,qBAAqB,IAAI,SAAS,KAAK,eAAe,KAAK,MAAM,KAAK,CAAC,IAAI,MAAM;AAAA,MACjG,WAAW,KAAK,WAAW,UAAU;AACnC,UAAE,OAAO,KAAK,sBAAsB,IAAI,SAAS,KAAK,eAAe,KAAK,MAAM,KAAK,CAAC,IAAI,IAAI;AAC9F,UAAE,OAAO,IAAI,MAAM,IAAI,kBAAkB,KAAK,MAAM,KAAK,IAAI,MAAM;AAAA,MACrE,OAAO;AACL,gBAAQ,8BAA8B,KAAK,MAAM,yCAAyC;AAAA,MAC5F;AACA;AAAA,IACF;AACA,QAAI;AACF,YAAM,OAAO,YAAY,MAAM,eAAe,KAAK,GAAG,EAAE,YAAY,KAAK,MAAM,MAAM,CAAC;AACtF,QAAE,OAAO,KAAK,KAAK,KAAK,KAAK,EAAE,IAAI,IAAI,KAAK,KAAK,MAAM,KAAK,KAAK,GAAG,IAAI,IAAI;AAC5E,QAAE,IAAI,KAAK,KAAK,IAAI,EAAE,IAAI,IAAI;AAC9B,UAAI,KAAK,KAAM,GAAE,OAAO,YAAO,KAAK,IAAI,EAAE,IAAI,IAAI;AAClD,QAAE,OAAO,WAAW,KAAK,QAAQ,KAAK,KAAK,IAAI,MAAM;AACrD,UAAI,CAAC,KAAK,SAAS;AACjB,UAAE,MAAM,sDAA4C,IAAI,MAAM;AAC9D;AAAA,MACF;AACA,UAAI,KAAK,QAAQ;AACf,UAAE,OAAO,mCAA8B,IAAI,MAAM;AACjD;AAAA,MACF;AACA,mBAAa,IAAI;AACjB,QAAE,MAAM,kBAAa,KAAK,aAAa,YAAY,KAAK,UAAU,IAAI,MAAM,IAAI,iCAAiC,IAAI,MAAM;AAAA,IAC7H,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AACzD,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF,CAAC;AAEH;AAEA,QAAM,UAAU,QACb,QAAQ,SAAS,EACjB,YAAY,0FAA0F;AAEzG,UACG,QAAQ,iBAAiB,EACzB,YAAY,6DAA6D,EACzE,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,OAAe,SAA0B;AAChD,UAAM,SAAS,mBAAmB,UAAU,KAAK;AACjD,QAAI,CAAC,OAAO,SAAS;AACnB,eAAS,kBAAkB,KAAK,kDAA6C;AAC7E,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,QAAI;AACF,SAAG,gBAAgB,KAAK,OAAO,IAAI;AACnC,cAAQ,iCAAiC,OAAO,IAAI,GAAG;AAAA,IACzD,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,uBAAuB,EAC/B,YAAY,8EAA8E,EAC1F,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,SAAiB,OAAe,SAA0B;AACjE,UAAM,SAAS,mBAAmB,UAAU,KAAK;AACjD,QAAI,CAAC,OAAO,SAAS;AACnB,eAAS,kBAAkB,KAAK,kDAA6C;AAC7E,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,QAAI,YAAY,OAAO,YAAY,MAAM;AACvC,eAAS,0FAA0F;AACnG,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,QAAI;AACF,SAAG,gBAAgB,SAAS,OAAO,IAAI;AACvC,cAAQ,aAAa,OAAO,aAAQ,OAAO,IAAI,GAAG;AAAA,IACpD,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,iBAAiB,EACzB,YAAY,kEAAkE,EAC9E,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,SAAiB,SAA0B;AAClD,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,QAAI;AACF,SAAG,qBAAqB,OAAO;AAC/B,cAAQ,aAAa,OAAO,WAAW;AAAA,IACzC,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,MAAM,EACd,YAAY,kDAAkD,EAC9D,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,SAA0B;AACjC,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,QAAI;AACF,YAAM,SAAS,GAAG,iBAAiB;AACnC,cAAQ,OAAO,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,IAAI;AAAA,IAC7D,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,mBAAmB,EAC3B,YAAY,2EAA2E,EACvF,OAAO,eAAe,SAAS,EAC/B,OAAO,gBAAgB,wCAAwC,EAC/D,OAAO,CAAC,SAA6B,SAAwC;AAC5E,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,QAAI;AACF,YAAM,MAAM,WAAW,YAAY,WAAW,UAAU,GAAG,iBAAiB,UAAU,GAAG;AACzF,UAAI,CAAC,KAAK;AACR,iBAAS,6BAA6B;AACtC,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,YAAM,SAAS,WAAW,EAAE,cAAc,KAAK,IAAI,CAAC;AACpD,YAAM,SAAS,GAAG,iBAAiB;AACnC,YAAM,UAAU,aAAa,IAAI,KAAK,QAAQ,MAAM;AACpD,cAAQ,OAAO,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,IAAI,IAAI;AAAA,IAC9D,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAEH,QAAM,aAAa,QAAQ,QAAQ,KAAK,EAAE,YAAY,wBAAwB;AAC9E,aACG,QAAQ,QAAQ,EAChB,YAAY,kEAAkE,EAC9E,OAAO,gBAAgB,wCAAwC,EAC/D,OAAO,CAAC,SAA2B;AAClC,iBAAa,EAAE,cAAc,KAAK,IAAI,CAAC;AACvC,YAAQ,iBAAiB;AAAA,EAC3B,CAAC;AAEH,UACG,QAAQ,iBAAiB,EACzB,YAAY,gEAAgE,EAC5E,OAAO,eAAe,SAAS,EAC/B,OAAO,gBAAgB,wCAAwC,EAC/D,OAAO,CAAC,OAAe,SAAwC;AAC9D,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,QAAI;AACF,YAAM,SAAS,WAAW,EAAE,cAAc,KAAK,IAAI,CAAC;AACpD,YAAM,YAAY,iBAAiB,OAAO,QAAQ,EAAE;AACpD,UAAI,cAAc,QAAW;AAC3B,iBAAS,sBAAsB,KAAK,4CAA4C;AAChF,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,cAAQ,OAAO,MAAM,YAAY,IAAI;AAAA,IACvC,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAGH,QAAM,OAAO,QACV,QAAQ,MAAM,EACd,YAAY,iFAAiF;AAEhG,OACG,QAAQ,QAAQ,EAChB,YAAY,kEAAkE,EAC9E,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,SAA0B;AACjC,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,QAAI;AACF,UAAI,CAAC,OAAO,WAAW,KAAK;AAC1B,gBAAQ,iIAA4H;AAAA,MACtI;AACA,YAAM,SAAS,GAAG,qBAAqB;AACvC,cAAQ,OAAO,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,IAAI;AAC3D,YAAM,UAAU,GAAG,iBAAiB,EAAE,QAAQ,UAAU,CAAC;AACzD,iBAAW,KAAK,QAAQ,MAAM,GAAG,EAAE,GAAG;AACpC,gBAAQ,OAAO,MAAM,KAAK,EAAE,SAAS,SAAS,WAAM,QAAG,IAAI,EAAE,UAAU,EAAE,YAAY,MAAM,GAAG,EAAE,CAAC,IAAI,IAAI,MAAM,EAAE,OAAO,GAAG,CAAC;AAAA,CAAI;AAAA,MAClI;AACA,UAAI,QAAQ,SAAS,GAAI,SAAQ,OAAO,MAAM,KAAK,IAAI,iBAAY,QAAQ,SAAS,MAAM,OAAO,CAAC;AAAA,CAAI;AAAA,IACxG,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAEH,OACG,QAAQ,QAAQ,EAChB,YAAY,6EAA6E,EACzF,OAAO,eAAe,SAAS,EAC/B,OAAO,OAAO,SAA0B;AACvC,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,QAAI;AACF,YAAM,UAAU,GAAG,iBAAiB,EAAE,QAAQ,UAAU,CAAC;AACzD,UAAI,QAAQ,WAAW,GAAG;AACxB,gBAAQ,4BAA4B;AACpC;AAAA,MACF;AACA,UAAI,CAAC,QAAQ,MAAM,OAAO;AACxB,gBAAQ,GAAG,QAAQ,MAAM,iFAAiF;AAC1G;AAAA,MACF;AACA,YAAM,IAAI,QAAQ,OAAO,MAAM,KAAK,QAAQ,MAAM;AAGlD,YAAM,aAAa,CAAC,MAA2C,EAAE;AACjE,iBAAW,KAAK,SAAS;AACvB,UAAE,IAAI;AACN,UAAE,KAAK,OAAO,KAAK,GAAG,CAAC,CAAC,WAAW,EAAE,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,YAAY,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,CAAK;AAC9F,UAAE,QAAQ,IAAI,KAAK,UAAU,EAAE,OAAO,CAAC,CAAC;AAAA,CAAI;AAC5C,cAAM,KAAK,gBAAgB,EAAE,OAAO,QAAQ,OAAO,QAAQ,QAAQ,OAAO,CAAC;AAC3E,cAAM,OAAO,MAAM,IAAI,QAAgB,CAAC,QAAQ,GAAG,SAAS,KAAK,KAAK,QAAG,CAAC,yDAAyD,GAAG,CAAC,GAAG,KAAK,EAAE,YAAY;AAC7J,WAAG,MAAM;AACT,cAAM,MAAM,WAAW,CAAC;AACxB,YAAI,QAAQ,IAAK;AACjB,YAAI,QAAQ,KAAK;AACf,aAAG,iBAAiB,EAAE,aAAa,YAAY,MAAM;AAAA,QACvD,WAAW,QAAQ,KAAK;AACtB,aAAG,iBAAiB,EAAE,aAAa,YAAY,MAAM;AAAA,QACvD,WAAW,QAAQ,KAAK;AACtB,cAAI,IAAK,IAAG,gBAAgB,KAAK,MAAM;AACvC,aAAG,iBAAiB,EAAE,aAAa,YAAY,MAAM;AAAA,QACvD,WAAW,QAAQ,KAAK;AACtB,cAAI,IAAK,IAAG,gBAAgB,KAAK,MAAM;AACvC,aAAG,iBAAiB,EAAE,aAAa,YAAY,MAAM;AAAA,QACvD,OAAO;AACL,YAAE,KAAK,IAAI,wBAAwB,CAAC;AAAA,CAAI;AAAA,QAC1C;AAAA,MACF;AACA,YAAM,SAAS,GAAG,qBAAqB;AACvC,cAAQ,+BAA0B,OAAO,QAAQ,cAAc,OAAO,QAAQ,aAAa,OAAO,OAAO,EAAE;AAAA,IAC7G,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAEH,OACG,QAAQ,MAAM,EACd,YAAY,yEAAyE,EACrF,OAAO,eAAe,SAAS,EAC/B,OAAO,aAAa,uCAAuC,KAAK,EAChE,OAAO,OAAO,SAA4C;AACzD,UAAM,SAAS,cAAc,KAAK,KAAK,EAAE,QAAQ,KAAK,GAAG,IAAI,CAAC,CAAC;AAC/D,QAAI,CAAC,OAAO,WAAW,KAAK;AAC1B,eAAS,gFAA2E;AACpF,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,KAAK,IAAI,cAAc,OAAO,MAAM;AAC1C,QAAI;AACF,YAAM,WAAW,GAAG,kBAAkB;AACtC,YAAM,QAAoB,SAAS,IAAI,CAAC,OAAO,EAAE,aAAa,EAAE,aAAa,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,EAAE;AAChH,YAAM,SAAS,MAAM,WAAW,QAAQ,OAAO,EAAE,QAAQ,KAAK,OAAO,CAAC;AACtE,UAAI,CAAC,KAAK,QAAQ;AAChB,mBAAW,QAAQ,OAAO,WAAY,IAAG,iBAAiB,MAAM,QAAQ;AAAA,MAC1E;AACA,cAAQ,mBAAmB,OAAO,IAAI,aAAa,OAAO,OAAO,YAAY,OAAO,MAAM,GAAG,KAAK,SAAS,eAAe,EAAE,EAAE;AAAA,IAChI,SAAS,KAAK;AACZ,eAAS,qBAAqB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,EAAE;AAChF,cAAQ,WAAW;AAAA,IACrB,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,KAAK,EACb,YAAY,qGAAgG,EAC5G,OAAO,UAAU,kDAAkD,KAAK,EACxE,OAAO,cAAc,aAAa,MAAM,EACxC,OAAO,cAAc,aAAa,WAAW,EAC7C,OAAO,0BAA0B,mEAAmE,EACpG,OAAO,oBAAoB,uGAAuG,EAClI,OAAO,eAAe,SAAS,EAC/B,OAAO,kBAAkB,qCAAqC,QAAQ,EACtE,OAAO,iBAAiB,4EAA4E,EACpG,OAAO,cAAc,oBAAoB,EACzC,OAAO,iBAAiB,kCAAkC,EAC1D,OAAO,oBAAoB,uFAAuF,EAClH,OAAO,iBAAiB,6HAA6H,KAAK,EAC1J,OAAO,sBAAsB,6EAA6E,QAAQ,EAClH,OAAO,mBAAmB,yEAAyE,KAAK,EACxG,OAAO,OAAO,SAAS;AACtB,QAAI;AACF,YAAM,WAAW,KAAK;AACtB,UAAI,aAAa,YAAY,aAAa,SAAS;AACjD,gBAAQ,OAAO,MAAM;AAAA,uDAA0D,QAAQ;AAAA,CAAM;AAC7F,gBAAQ,WAAW;AACnB;AAAA,MACF;AACA,YAAM,SAAS;AAAA,QACb,WAAW,KAAK,OAAO,SAAS;AAAA,QAChC,MAAM,SAAS,KAAK,MAAM,EAAE;AAAA,QAC5B,MAAM,KAAK;AAAA,QACX,cAAc,KAAK,eAAe,OAAO,KAAK,YAAY,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAc,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IAAI;AAAA,QACtH,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,SAAS,KAAK;AAAA,QACd,QAAQ,KAAK,UAAU,KAAK;AAAA,QAC5B,UAAU,KAAK;AAAA,QACf,SAAS,KAAK,UAAU,OAAO,KAAK,OAAO,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAc,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IAAI;AAAA,QACvG,YAAY,KAAK,eAAe;AAAA,QAChC;AAAA,QACA,cAAc,KAAK,iBAAiB;AAAA,MACtC,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM;AAAA,SAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACrF,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,KAAK,EACb,YAAY,yEAAyE,EACrF,OAAO,UAAU,4DAA4D,IAAI,EACjF,OAAO,cAAc,aAAa,MAAM,EACxC,OAAO,cAAc,aAAa,WAAW,EAC7C,OAAO,0BAA0B,mEAAmE,EACpG,OAAO,4BAA4B,mEAAmE,EACtG,OAAO,oBAAoB,kGAAkG,EAC7H,OAAO,eAAe,SAAS,EAC/B,OAAO,kBAAkB,qCAAqC,QAAQ,EACtE,OAAO,iBAAiB,uEAAuE,EAC/F,OAAO,cAAc,oBAAoB,EACzC,OAAO,gBAAgB,2CAA2C,EAClE,OAAO,mBAAmB,yEAAyE,KAAK,EACxG,OAAO,OAAO,SAAS;AACtB,QAAI;AACF,YAAM,SAAS;AAAA,QACb,cAAc,KAAK,iBAAiB;AAAA,QACpC,MAAM,SAAS,KAAK,MAAM,EAAE;AAAA,QAC5B,MAAM,KAAK;AAAA,QACX,cAAc,KAAK,eAAe,OAAO,KAAK,YAAY,EAAE,MAAM,GAAG,EAAE,IAAI,CAAC,MAAc,EAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IAAI;AAAA,QACtH,gBAAgB,KAAK,iBAAiB,OAAO,KAAK,cAAc,EAAE,MAAM,GAAG,EAAE,IAAI,CAACC,OAAcA,GAAE,KAAK,CAAC,EAAE,OAAO,OAAO,IAAI;AAAA,QAC5H,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,SAAS,KAAK;AAAA,QACd,QAAQ,KAAK,UAAU,KAAK;AAAA,QAC5B,SAAS,KAAK;AAAA,MAChB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,cAAQ,OAAO,MAAM;AAAA,SAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,CAAI;AACrF,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF,CAAC;AAEH,QAAM,UAAU,QACb,QAAQ,MAAM,EACd,YAAY,iFAAiF;AAEhG,UACG,QAAQ,eAAe,EACvB,YAAY,+EAA+E,EAC3F,OAAO,iBAAiB,6BAA6B,QAAQ,EAC7D,OAAO,iBAAiB,qDAAqD,EAC7E,OAAO,oBAAoB,0CAA0C,EACrE,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,SAAiB,SAAyE;AACjG,UAAM,OAAO,WAAW,UAAU,KAAK,IAAI;AAC3C,QAAI,CAAC,KAAK,SAAS;AACjB,cAAQ,OAAO,MAAM;AAAA,2DAA8D,KAAK,IAAI;AAAA,CAAM;AAClG,cAAQ,WAAW;AACnB;AAAA,IACF;AACA,UAAM,KAAK,IAAI,cAAc,KAAK,MAAM,cAAc,EAAE,MAAM;AAC9D,QAAI;AACF,YAAM,QAAQ,KAAK,SAAS,YAAY,EAAE,EAAE,SAAS,WAAW;AAChE,YAAM,SAAS,gBAAgB,KAAK,MAAM;AAC1C,SAAG,cAAc,EAAE,WAAW,UAAU,KAAK,GAAG,SAAS,QAAQ,MAAM,KAAK,KAAK,CAAC;AAClF,cAAQ,OAAO,MAAM;AAAA,mCAAiC,OAAO,SAAS,KAAK,IAAI,WAAW,MAAM;AAAA,CAAI;AACpG,cAAQ,OAAO,MAAM;AAAA;AAAA,CAAiD;AACtE,cAAQ,OAAO,MAAM,QAAQ,IAAI;AAAA,IACnC,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,MAAM,EACd,YAAY,yFAAoF,EAChG,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,SAA0B;AACjC,UAAM,KAAK,IAAI,cAAc,KAAK,MAAM,cAAc,EAAE,MAAM;AAC9D,QAAI;AACF,YAAM,QAAQ,GAAG,gBAAgB;AACjC,UAAI,MAAM,WAAW,GAAG;AAAE,gBAAQ,OAAO,MAAM,uDAAuD;AAAG;AAAA,MAAQ;AACjH,iBAAW,KAAK,MAAO,SAAQ,OAAO,MAAM,GAAG,EAAE,OAAO,IAAK,EAAE,IAAI,IAAK,EAAE,MAAM,IAAK,EAAE,SAAS;AAAA,CAAI;AAAA,IACtG,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAEH,UACG,QAAQ,kBAAkB,EAC1B,YAAY,sCAAsC,EAClD,OAAO,eAAe,SAAS,EAC/B,OAAO,CAAC,SAAiB,SAA0B;AAClD,UAAM,KAAK,IAAI,cAAc,KAAK,MAAM,cAAc,EAAE,MAAM;AAC9D,QAAI;AACF,YAAM,IAAI,GAAG,2BAA2B,OAAO;AAC/C,cAAQ,OAAO,MAAM,IAAI,IAAI,kBAAa,CAAC,sBAAsB,OAAO;AAAA,IAAO,4BAA4B,OAAO;AAAA,CAAI;AAAA,IACxH,UAAE;AACA,SAAG,MAAM;AAAA,IACX;AAAA,EACF,CAAC;AAIH,QAAM,IAAI,CAAC,MAAc,QAAQ,OAAO,MAAM,CAAC;AAE/C,IAAE,IAAI;AACN,IAAE,KAAK,kDAAkD,IAAI,IAAI;AACjE,IAAE,KAAK,kDAAkD,IAAI,IAAI;AACjE,IAAE,KAAK,sDAAuD,IAAI,IAAI;AACtE,IAAE,KAAK,iDAAiD,IAAI,IAAI;AAChE,IAAE,KAAK,uDAAuD,IAAI,IAAI;AACtE,IAAE,KAAK,kDAAkD,IAAI,IAAI;AACjE,IAAE,IAAI;AACN,IAAE,KAAK,eAAe,IAAI,OAAO,IAAI,MAAM,OAAO,IAAI,IAAI;AAC1D,IAAE,IAAI,kEAAkE,CAAC;AACzE,IAAE,IAAI,+EAA0E,CAAC;AACjF,IAAE,IAAI;AAIN,MAAI,QAAQ,KAAK,UAAU,GAAG;AAC5B,MAAE,IAAI,sSAAsD,CAAC;AAC7D,MAAE,IAAI;AACN,MAAE,KAAK,eAAe,CAAC;AACvB,MAAE,IAAI;AACN,MAAE,KAAK,MAAM,UAAU,CAAC,gBAAgB,IAAI,sDAAsD,CAAC;AAAA,CAAI;AACvG,MAAE,KAAK,MAAM,MAAM,CAAC,oBAAoB,IAAI,mCAAmC,CAAC;AAAA,CAAI;AACpF,MAAE,KAAK,MAAM,WAAW,CAAC,eAAe,IAAI,wBAAwB,CAAC;AAAA,CAAI;AACzE,MAAE,KAAK,MAAM,QAAQ,CAAC,IAAI,IAAI,WAAW,CAAC,OAAO,IAAI,kCAAkC,CAAC;AAAA,CAAI;AAC5F,MAAE,KAAK,MAAM,MAAM,CAAC,IAAI,IAAI,WAAW,CAAC,SAAS,IAAI,sBAAsB,CAAC;AAAA,CAAI;AAChF,MAAE,KAAK,MAAM,UAAU,CAAC,gBAAgB,IAAI,mBAAmB,CAAC;AAAA,CAAI;AACpE,MAAE,KAAK,MAAM,QAAQ,CAAC,kBAAkB,IAAI,+CAA+C,CAAC;AAAA,CAAI;AAChG,MAAE,KAAK,MAAM,MAAM,CAAC,oBAAoB,IAAI,wBAAwB,CAAC;AAAA,CAAI;AACzE,MAAE,IAAI;AACN,MAAE,IAAI,sSAAsD,CAAC;AAC7D,MAAE,IAAI;AACN,MAAE,KAAK,kBAAkB,CAAC;AAC1B,MAAE,IAAI;AACN,MAAE,KAAK,QAAQ,GAAG,CAAC,IAAI,KAAK,6BAA6B,CAAC,YAAY,IAAI,oBAAoB,CAAC;AAAA,CAAI;AACnG,MAAE,KAAK,QAAQ,GAAG,CAAC,IAAI,KAAK,2BAA2B,CAAC,cAAc,IAAI,0BAA0B,CAAC;AAAA,CAAI;AACzG,MAAE,KAAK,QAAQ,GAAG,CAAC,IAAI,KAAK,+BAA+B,CAAC,UAAU,IAAI,eAAe,CAAC;AAAA,CAAI;AAC9F,MAAE,IAAI;AACN,MAAE,IAAI,uCAAuC,CAAC;AAC9C,MAAE,IAAI,yCAAyC,CAAC;AAChD,MAAE,IAAI,8CAA8C,CAAC;AACrD,MAAE,IAAI;AACN;AAAA,EACF;AAEA,IAAE,IAAI,sSAAsD,CAAC;AAC7D,IAAE,IAAI;AAIN,UAAQ,aAAa,CAAC,QAAQ;AAC5B,QAAI,IAAI,SAAS,2BAA2B;AAC1C,cAAQ,WAAW;AAAA,IACrB,OAAO;AACL,cAAQ,WAAW;AAAA,IACrB;AAAA,EACF,CAAC;AAED,UAAQ,MAAM,QAAQ,IAAI;AAC5B;","names":["z","z","readFileSync","readFileSync","cursor","join","HEX_SIZE","join","readFileSync","readFileSync","readFileSync","existsSync","writeFileSync","resolve","dirname","matches","createHash","createHash","mkdirSync","readFileSync","writeFileSync","existsSync","existsSync","readFileSync","mkdirSync","writeFileSync","join","join","matches","dirname","readFileSync","resolve","existsSync","writeFileSync","saved","host","execSync","join","dim","o"]}