@datasynx/agentic-ai-cartography 1.1.0 → 2.0.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/README.md +197 -33
- package/dist/bookmarks-VS56KVCO.js +25 -0
- package/dist/chunk-CJ2PITFA.js +785 -0
- package/dist/chunk-CJ2PITFA.js.map +1 -0
- package/dist/chunk-D6SRSLBF.js +48 -0
- package/dist/{chunk-WJR63RWY.js → chunk-J6FDZ6HZ.js} +11 -2
- package/dist/chunk-J6FDZ6HZ.js.map +1 -0
- package/dist/chunk-UGSNG3QJ.js +49 -0
- package/dist/chunk-UGSNG3QJ.js.map +1 -0
- package/dist/chunk-W7YE6AAH.js +1516 -0
- package/dist/chunk-W7YE6AAH.js.map +1 -0
- package/dist/cli.js +133 -664
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +60115 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +734 -0
- package/dist/index.d.ts +363 -7
- package/dist/index.js +1462 -161
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +33 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/onnxruntime_binding-6Q6HXASN.node +0 -0
- package/dist/onnxruntime_binding-EKZT2NRK.node +0 -0
- package/dist/onnxruntime_binding-P6S7V3CI.node +0 -0
- package/dist/onnxruntime_binding-PJNNIIUO.node +0 -0
- package/dist/onnxruntime_binding-UN6SPTQK.node +0 -0
- package/dist/sdk-A6NLO3DJ.js +12294 -0
- package/dist/sdk-A6NLO3DJ.js.map +1 -0
- package/dist/sdk-G5D4WQZ4.js +12293 -0
- package/dist/sdk-G5D4WQZ4.js.map +1 -0
- package/dist/sdk-QSTAREST.js +4869 -0
- package/dist/sdk-QSTAREST.js.map +1 -0
- package/dist/sqlite-vec-EZN67B2V.js +40 -0
- package/dist/sqlite-vec-EZN67B2V.js.map +1 -0
- package/dist/sqlite-vec-UK5YYE5T.js +39 -0
- package/dist/sqlite-vec-UK5YYE5T.js.map +1 -0
- package/dist/transformers.node-BTYUTJK5.js +42884 -0
- package/dist/transformers.node-BTYUTJK5.js.map +1 -0
- package/dist/transformers.node-J6PRTTOX.js +42883 -0
- package/dist/transformers.node-J6PRTTOX.js.map +1 -0
- package/dist/{types-54623ALF.js → types-JG27FR3E.js} +5 -2
- package/dist/types-JG27FR3E.js.map +1 -0
- package/package.json +53 -17
- package/scripts/postinstall.mjs +7 -0
- package/server.json +28 -0
- package/dist/bookmarks-BWNVQGPG.js +0 -14
- package/dist/chunk-QKNYI3SU.js +0 -459
- package/dist/chunk-QKNYI3SU.js.map +0 -1
- package/dist/chunk-WJR63RWY.js.map +0 -1
- /package/dist/{bookmarks-BWNVQGPG.js.map → bookmarks-VS56KVCO.js.map} +0 -0
- /package/dist/{types-54623ALF.js.map → chunk-D6SRSLBF.js.map} +0 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/db.ts","../src/types.ts","../src/tools.ts","../src/bookmarks.ts","../src/platform.ts","../src/safety.ts","../src/agent.ts","../src/exporter.ts","../src/hex.ts","../src/cluster.ts","../src/mapper.ts","../src/preflight.ts","../src/logger.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { z } from 'zod';\nimport { NODE_TYPES, EDGE_RELATIONSHIPS } from './types.js';\nimport type {\n CartographyConfig, DiscoveryNode, DiscoveryEdge,\n NodeRow, EdgeRow, SessionRow, Connection,\n} from './types.js';\n\n// ── Row validation schemas ──────────────────────────────────────────────────\n\nconst SessionRowSchema = z.object({\n id: z.string(),\n mode: z.literal('discover'),\n started_at: z.string(),\n completed_at: z.string().nullable().optional(),\n config: z.string(),\n});\n\nconst NodeRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discovered_via: z.string().nullable().optional(),\n discovered_at: z.string(),\n path_id: z.string().nullable().optional(),\n depth: z.number().default(0),\n confidence: z.number().default(0.5),\n metadata: z.string().default('{}'),\n tags: z.string().default('[]'),\n domain: z.string().nullable().optional(),\n sub_domain: z.string().nullable().optional(),\n quality_score: z.number().nullable().optional(),\n});\n\nconst EdgeRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n source_id: z.string(),\n target_id: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string().nullable().optional(),\n confidence: z.number().default(0.5),\n discovered_at: z.string(),\n});\n\nconst EventRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n task_id: z.string().nullable().optional(),\n timestamp: z.string(),\n event_type: z.string(),\n process: z.string(),\n pid: z.number(),\n target: z.string().nullable().optional(),\n target_type: z.string().nullable().optional(),\n port: z.number().nullable().optional(),\n duration_ms: z.number().nullable().optional(),\n});\n\nconst TaskRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n description: z.string().nullable().optional(),\n started_at: z.string(),\n completed_at: z.string().nullable().optional(),\n steps: z.string().default('[]'),\n involved_services: z.string().default('[]'),\n status: z.enum(['active', 'completed', 'cancelled']),\n});\n\nconst WorkflowRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n name: z.string().nullable().optional(),\n pattern: z.string(),\n task_ids: z.string().default('[]'),\n occurrences: z.number().default(1),\n first_seen: z.string(),\n last_seen: z.string(),\n avg_duration_ms: z.number().nullable().optional(),\n involved_services: z.string().default('[]'),\n});\n\nconst ConnectionRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n source_asset_id: z.string(),\n target_asset_id: z.string(),\n type: z.string().nullable().optional(),\n created_at: z.string(),\n});\n\nexport interface ConnectionRow extends Connection {\n sessionId: string;\n createdAt: string;\n}\n\n// ── DB Row Types ──\n\nexport interface EventRow {\n id: string;\n sessionId: string;\n taskId?: string;\n timestamp: string;\n eventType: string;\n process: string;\n pid: number;\n target?: string;\n targetType?: string;\n port?: number;\n durationMs?: number;\n}\n\nexport interface TaskRow {\n id: string;\n sessionId: string;\n description?: string;\n startedAt: string;\n completedAt?: string;\n steps: string;\n involvedServices: string;\n status: 'active' | 'completed' | 'cancelled';\n}\n\nexport interface WorkflowRow {\n id: string;\n sessionId: string;\n name?: string;\n pattern: string;\n taskIds: string;\n occurrences: number;\n firstSeen: string;\n lastSeen: string;\n avgDurationMs: number;\n involvedServices: string;\n}\n\nconst SCHEMA = `\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\nPRAGMA busy_timeout = 5000;\n\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n mode TEXT NOT NULL CHECK (mode IN ('discover')),\n started_at TEXT NOT NULL,\n completed_at TEXT,\n config TEXT NOT NULL DEFAULT '{}'\n);\n\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT NOT NULL,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n discovered_via TEXT,\n discovered_at TEXT NOT NULL,\n path_id TEXT,\n depth INTEGER DEFAULT 0,\n confidence REAL DEFAULT 0.5,\n metadata TEXT NOT NULL DEFAULT '{}',\n tags TEXT NOT NULL DEFAULT '[]',\n domain TEXT,\n sub_domain TEXT,\n quality_score REAL,\n PRIMARY KEY (id, session_id)\n);\n\nCREATE TABLE IF NOT EXISTS connections (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_asset_id TEXT NOT NULL,\n target_asset_id TEXT NOT NULL,\n type TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_id TEXT NOT NULL,\n target_id TEXT NOT NULL,\n relationship TEXT NOT NULL,\n evidence TEXT,\n confidence REAL DEFAULT 0.5,\n discovered_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS activity_events (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n task_id TEXT,\n timestamp TEXT NOT NULL,\n event_type TEXT NOT NULL,\n process TEXT NOT NULL,\n pid INTEGER NOT NULL,\n target TEXT,\n target_type TEXT,\n port INTEGER,\n duration_ms INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS tasks (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n description TEXT,\n started_at TEXT NOT NULL,\n completed_at TEXT,\n steps TEXT NOT NULL DEFAULT '[]',\n involved_services TEXT NOT NULL DEFAULT '[]',\n status TEXT DEFAULT 'active' CHECK (status IN ('active','completed','cancelled'))\n);\n\nCREATE TABLE IF NOT EXISTS workflows (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n name TEXT,\n pattern TEXT NOT NULL,\n task_ids TEXT NOT NULL DEFAULT '[]',\n occurrences INTEGER DEFAULT 1,\n first_seen TEXT NOT NULL,\n last_seen TEXT NOT NULL,\n avg_duration_ms INTEGER,\n involved_services TEXT NOT NULL DEFAULT '[]'\n);\n\nCREATE TABLE IF NOT EXISTS node_approvals (\n pattern TEXT PRIMARY KEY,\n action TEXT NOT NULL CHECK (action IN ('save','ignore','auto')),\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_nodes_session ON nodes(session_id);\nCREATE INDEX IF NOT EXISTS idx_edges_session ON edges(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_session ON activity_events(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_task ON activity_events(task_id);\nCREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);\nCREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);\n`;\n\nexport class CartographyDB {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n mkdirSync(dirname(dbPath), { recursive: true });\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('busy_timeout = 5000');\n this.migrate();\n }\n\n private migrate(): void {\n const version = (this.db.pragma('user_version', { simple: true }) as number);\n if (version === 0) {\n this.db.exec(SCHEMA);\n this.db.pragma('user_version = 2');\n } else if (version === 1) {\n // v1 → v2: add hex map columns to nodes + connections table\n const cols = (this.db.prepare(\"PRAGMA table_info(nodes)\").all() as Array<{ name: string }>).map(c => c.name);\n if (!cols.includes('domain')) this.db.exec('ALTER TABLE nodes ADD COLUMN domain TEXT');\n if (!cols.includes('sub_domain')) this.db.exec('ALTER TABLE nodes ADD COLUMN sub_domain TEXT');\n if (!cols.includes('quality_score')) this.db.exec('ALTER TABLE nodes ADD COLUMN quality_score REAL');\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS connections (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_asset_id TEXT NOT NULL,\n target_asset_id TEXT NOT NULL,\n type TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);\n `);\n this.db.pragma('user_version = 2');\n }\n }\n\n close(): void {\n this.db.pragma('optimize');\n this.db.close();\n }\n\n // ── Sessions ────────────────────────────\n\n createSession(mode: 'discover', config: CartographyConfig): string {\n const id = crypto.randomUUID();\n this.db.prepare(\n 'INSERT INTO sessions (id, mode, started_at, config) VALUES (?, ?, ?, ?)'\n ).run(id, mode, new Date().toISOString(), JSON.stringify(config));\n return id;\n }\n\n endSession(id: string): void {\n this.db.prepare('UPDATE sessions SET completed_at = ? WHERE id = ?')\n .run(new Date().toISOString(), id);\n }\n\n getSession(id: string): SessionRow | undefined {\n const row = this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n getLatestSession(mode?: string): SessionRow | undefined {\n const row = mode\n ? this.db.prepare('SELECT * FROM sessions WHERE mode = ? ORDER BY rowid DESC LIMIT 1').get(mode) as Record<string, unknown> | undefined\n : this.db.prepare('SELECT * FROM sessions ORDER BY rowid DESC LIMIT 1').get() as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n getSessions(): SessionRow[] {\n const rows = this.db.prepare('SELECT * FROM sessions ORDER BY rowid DESC').all() as Record<string, unknown>[];\n return rows.map(r => this.mapSession(r));\n }\n\n private mapSession(r: Record<string, unknown>): SessionRow {\n const v = SessionRowSchema.parse(r);\n return {\n id: v.id,\n mode: v.mode,\n startedAt: v.started_at,\n completedAt: v.completed_at ?? undefined,\n config: v.config,\n };\n }\n\n // ── Nodes ───────────────────────────────\n\n upsertNode(sessionId: string, node: DiscoveryNode, depth = 0): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO nodes\n (id, session_id, type, name, discovered_via, discovered_at, depth, confidence, metadata, tags,\n domain, sub_domain, quality_score)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n node.id, sessionId, node.type, node.name, node.discoveredVia,\n new Date().toISOString(), depth, node.confidence,\n JSON.stringify(node.metadata ?? {}),\n JSON.stringify(node.tags ?? []),\n node.domain ?? null,\n node.subDomain ?? null,\n node.qualityScore ?? null,\n );\n }\n\n getNodes(sessionId: string): NodeRow[] {\n const rows = this.db.prepare('SELECT * FROM nodes WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => this.mapNode(r));\n }\n\n private mapNode(r: Record<string, unknown>): NodeRow {\n const v = NodeRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n type: v.type,\n name: v.name,\n discoveredVia: v.discovered_via ?? '',\n discoveredAt: v.discovered_at,\n depth: v.depth,\n confidence: v.confidence,\n metadata: JSON.parse(v.metadata) as Record<string, unknown>,\n tags: JSON.parse(v.tags) as string[],\n pathId: v.path_id ?? undefined,\n domain: v.domain ?? undefined,\n subDomain: v.sub_domain ?? undefined,\n qualityScore: v.quality_score ?? undefined,\n };\n }\n\n deleteNode(sessionId: string, nodeId: string): void {\n this.db.prepare('DELETE FROM nodes WHERE session_id = ? AND id = ?').run(sessionId, nodeId);\n // Remove orphaned edges\n this.db.prepare(\n 'DELETE FROM edges WHERE session_id = ? AND (source_id = ? OR target_id = ?)'\n ).run(sessionId, nodeId, nodeId);\n }\n\n // ── Edges ───────────────────────────────\n\n insertEdge(sessionId: string, edge: DiscoveryEdge): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT OR IGNORE INTO edges\n (id, session_id, source_id, target_id, relationship, evidence, confidence, discovered_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, edge.sourceId, edge.targetId,\n edge.relationship, edge.evidence, edge.confidence,\n new Date().toISOString(),\n );\n }\n\n getEdges(sessionId: string): EdgeRow[] {\n const rows = this.db.prepare('SELECT * FROM edges WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = EdgeRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n sourceId: v.source_id,\n targetId: v.target_id,\n relationship: v.relationship,\n evidence: v.evidence ?? '',\n confidence: v.confidence,\n discoveredAt: v.discovered_at,\n };\n });\n }\n\n // ── Events ──────────────────────────────\n\n insertEvent(sessionId: string, event: Pick<EventRow, 'eventType' | 'process' | 'pid' | 'target' | 'targetType' | 'port'>, taskId?: string): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO activity_events\n (id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, taskId ?? null, new Date().toISOString(),\n event.eventType, event.process, event.pid,\n event.target ?? null, event.targetType ?? null, event.port ?? null,\n );\n }\n\n getEvents(sessionId: string, since?: string): EventRow[] {\n const rows = since\n ? this.db.prepare('SELECT * FROM activity_events WHERE session_id = ? AND timestamp > ? ORDER BY timestamp').all(sessionId, since) as Record<string, unknown>[]\n : this.db.prepare('SELECT * FROM activity_events WHERE session_id = ? ORDER BY timestamp').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = EventRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n taskId: v.task_id ?? undefined,\n timestamp: v.timestamp,\n eventType: v.event_type,\n process: v.process,\n pid: v.pid,\n target: v.target ?? undefined,\n targetType: v.target_type ?? undefined,\n port: v.port ?? undefined,\n durationMs: v.duration_ms ?? undefined,\n };\n });\n }\n\n // ── Tasks ───────────────────────────────\n\n startTask(sessionId: string, description?: string): string {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO tasks (id, session_id, description, started_at, steps, involved_services, status)\n VALUES (?, ?, ?, ?, '[]', '[]', 'active')\n `).run(id, sessionId, description ?? null, new Date().toISOString());\n return id;\n }\n\n endCurrentTask(sessionId: string): void {\n this.db.prepare(`\n UPDATE tasks SET status = 'completed', completed_at = ?\n WHERE session_id = ? AND status = 'active'\n `).run(new Date().toISOString(), sessionId);\n }\n\n updateTaskDescription(sessionId: string, description: string): void {\n this.db.prepare(`\n UPDATE tasks SET description = ?\n WHERE session_id = ? AND status = 'active'\n `).run(description, sessionId);\n }\n\n getActiveTask(sessionId: string): TaskRow | undefined {\n const row = this.db.prepare(\n \"SELECT * FROM tasks WHERE session_id = ? AND status = 'active' LIMIT 1\"\n ).get(sessionId) as Record<string, unknown> | undefined;\n return row ? this.mapTask(row) : undefined;\n }\n\n getTasks(sessionId: string): TaskRow[] {\n const rows = this.db.prepare('SELECT * FROM tasks WHERE session_id = ? ORDER BY started_at').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => this.mapTask(r));\n }\n\n private mapTask(r: Record<string, unknown>): TaskRow {\n const v = TaskRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n description: v.description ?? undefined,\n startedAt: v.started_at,\n completedAt: v.completed_at ?? undefined,\n steps: v.steps,\n involvedServices: v.involved_services,\n status: v.status,\n };\n }\n\n // ── Workflows ───────────────────────────\n\n insertWorkflow(sessionId: string, data: Omit<WorkflowRow, 'id'>): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO workflows\n (id, session_id, name, pattern, task_ids, occurrences,\n first_seen, last_seen, avg_duration_ms, involved_services)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, data.name ?? null, data.pattern,\n data.taskIds, data.occurrences,\n data.firstSeen, data.lastSeen, data.avgDurationMs,\n data.involvedServices,\n );\n }\n\n getWorkflows(sessionId: string): WorkflowRow[] {\n const rows = this.db.prepare('SELECT * FROM workflows WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = WorkflowRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n name: v.name ?? undefined,\n pattern: v.pattern,\n taskIds: v.task_ids,\n occurrences: v.occurrences,\n firstSeen: v.first_seen,\n lastSeen: v.last_seen,\n avgDurationMs: v.avg_duration_ms ?? 0,\n involvedServices: v.involved_services,\n };\n });\n }\n\n // ── Connections (user-created hex map links) ─────────────────────────────\n\n upsertConnection(sessionId: string, conn: Omit<Connection, 'id'>): string {\n // Idempotent: same source+target+type = same connection\n const existing = this.db.prepare(\n 'SELECT id FROM connections WHERE session_id = ? AND source_asset_id = ? AND target_asset_id = ?'\n ).get(sessionId, conn.sourceAssetId, conn.targetAssetId) as { id: string } | undefined;\n if (existing) return existing.id;\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO connections (id, session_id, source_asset_id, target_asset_id, type, created_at)\n VALUES (?, ?, ?, ?, ?, ?)\n `).run(id, sessionId, conn.sourceAssetId, conn.targetAssetId, conn.type ?? null, new Date().toISOString());\n return id;\n }\n\n getConnections(sessionId: string): ConnectionRow[] {\n const rows = this.db.prepare('SELECT * FROM connections WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = ConnectionRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n sourceAssetId: v.source_asset_id,\n targetAssetId: v.target_asset_id,\n type: v.type ?? undefined,\n createdAt: v.created_at,\n };\n });\n }\n\n deleteConnection(sessionId: string, connectionId: string): void {\n this.db.prepare('DELETE FROM connections WHERE session_id = ? AND id = ?').run(sessionId, connectionId);\n }\n\n // ── Approvals ───────────────────────────\n\n setApproval(pattern: string, action: 'save' | 'ignore' | 'auto'): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO node_approvals (pattern, action, created_at) VALUES (?, ?, ?)\n `).run(pattern, action, new Date().toISOString());\n }\n\n getApproval(pattern: string): string | undefined {\n const row = this.db.prepare('SELECT action FROM node_approvals WHERE pattern = ?').get(pattern) as { action: string } | undefined;\n return row?.action;\n }\n\n // ── Pruning ──────────────────────────────\n\n /**\n * Delete a session and all its associated data (nodes, edges, events, tasks, workflows, connections).\n */\n deleteSession(sessionId: string): void {\n this.db.prepare('DELETE FROM connections WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM workflows WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM activity_events WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM tasks WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM edges WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM nodes WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM sessions WHERE id = ?').run(sessionId);\n }\n\n /**\n * Prune sessions older than the given ISO date string. Returns count of deleted sessions.\n */\n pruneSessions(olderThan: string): number {\n const rows = this.db.prepare(\n 'SELECT id FROM sessions WHERE started_at < ?'\n ).all(olderThan) as { id: string }[];\n for (const row of rows) {\n this.deleteSession(row.id);\n }\n return rows.length;\n }\n\n // ── Stats ───────────────────────────────\n\n getStats(sessionId: string): { nodes: number; edges: number; events: number; tasks: number } {\n const nodes = (this.db.prepare('SELECT COUNT(*) as c FROM nodes WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const edges = (this.db.prepare('SELECT COUNT(*) as c FROM edges WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const events = (this.db.prepare('SELECT COUNT(*) as c FROM activity_events WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const tasks = (this.db.prepare('SELECT COUNT(*) as c FROM tasks WHERE session_id = ?').get(sessionId) as { c: number }).c;\n return { nodes, edges, events, tasks };\n }\n}\n","import { z } from 'zod';\n\n// ── Enums ────────────────────────────────\n\nexport const NODE_TYPES = [\n 'host', 'database_server', 'database', 'table',\n 'web_service', 'api_endpoint', 'cache_server',\n 'message_broker', 'queue', 'topic',\n 'container', 'pod', 'k8s_cluster',\n 'config_file', 'saas_tool', 'unknown',\n] as const;\nexport type NodeType = typeof NODE_TYPES[number];\n\nexport const EDGE_RELATIONSHIPS = [\n 'connects_to', 'reads_from', 'writes_to',\n 'calls', 'contains', 'depends_on',\n] as const;\nexport type EdgeRelationship = typeof EDGE_RELATIONSHIPS[number];\n\n// ── Zod Schemas ──────────────────────────\n\nexport const NodeSchema = z.object({\n id: z.string().describe('Format: \"{type}:{host}:{port}\" oder \"{type}:{name}\"'),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discoveredVia: z.string(),\n confidence: z.number().min(0).max(1).default(0.5),\n metadata: z.record(z.string(), z.unknown()).default({}),\n tags: z.array(z.string()).default([]),\n domain: z.string().optional().describe('Business domain, e.g. \"Marketing\", \"Finance\"'),\n subDomain: z.string().optional().describe('Sub-domain, e.g. \"Forecast client orders\"'),\n qualityScore: z.number().min(0).max(100).optional().describe('Data quality score 0–100'),\n});\nexport type DiscoveryNode = z.infer<typeof NodeSchema>;\n\nexport const EdgeSchema = z.object({\n sourceId: z.string(),\n targetId: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string(),\n confidence: z.number().min(0).max(1).default(0.5),\n});\nexport type DiscoveryEdge = z.infer<typeof EdgeSchema>;\n\n// ── Cartography Map Types ────────────────\n\nexport const DataAssetSchema = z.object({\n id: z.string(),\n name: z.string(),\n domain: z.string(),\n subDomain: z.string().optional(),\n qualityScore: z.number().min(0).max(100).optional(),\n metadata: z.record(z.string(), z.unknown()).default({}),\n position: z.object({ q: z.number(), r: z.number() }),\n});\nexport type DataAsset = z.infer<typeof DataAssetSchema>;\n\nexport const ClusterSchema = z.object({\n id: z.string(),\n label: z.string(),\n domain: z.string(),\n color: z.string(),\n assetIds: z.array(z.string()),\n centroid: z.object({ x: z.number(), y: z.number() }),\n});\nexport type Cluster = z.infer<typeof ClusterSchema>;\n\nexport const ConnectionSchema = z.object({\n id: z.string(),\n sourceAssetId: z.string(),\n targetAssetId: z.string(),\n type: z.string().optional(),\n});\nexport type Connection = z.infer<typeof ConnectionSchema>;\n\nexport interface CartographyMapData {\n assets: DataAsset[];\n clusters: Cluster[];\n connections: Connection[];\n meta: { exportedAt: string; theme: 'light' | 'dark' };\n}\n\n/** Navy → medium blue → periwinkle → teal/cyan palette */\nexport const DOMAIN_COLORS: Record<string, string> = {\n 'Quality Control': '#1a2744',\n 'Supply Chain': '#1e3a6e',\n 'Marketing': '#6a7fb5',\n 'Finance': '#3a8a8a',\n 'HR': '#2a5a9a',\n 'Logistics': '#0e7490',\n 'Sales': '#1d4ed8',\n 'Engineering': '#4338ca',\n 'Operations': '#0891b2',\n 'Data Layer': '#1e3352',\n 'Web / API': '#1a3a1a',\n 'Messaging': '#2a1a3a',\n 'Infrastructure': '#0f2a40',\n 'Other': '#374151',\n};\n\n/** Ordered palette for dynamic domain assignment */\nexport const DOMAIN_PALETTE = [\n '#1a2e5a', '#1e3a8a', '#1d4ed8', '#2563eb', '#3b82f6',\n '#6366f1', '#818cf8', '#7c9fc3', '#0e7490', '#0891b2',\n '#06b6d4', '#22d3ee', '#0d9488', '#14b8a6', '#2dd4bf', '#5eead4',\n] as const;\n\n// ── DB Row Types ─────────────────────────\n\nexport interface NodeRow extends DiscoveryNode {\n sessionId: string;\n discoveredAt: string;\n depth: number;\n pathId?: string;\n}\n\nexport interface EdgeRow extends DiscoveryEdge {\n id: string;\n sessionId: string;\n discoveredAt: string;\n pathId?: string;\n}\n\nexport interface SessionRow {\n id: string;\n mode: 'discover';\n startedAt: string;\n completedAt?: string;\n config: string;\n}\n\n// ── Config ───────────────────────────────\n\nexport interface CartographyConfig {\n maxDepth: number;\n maxTurns: number;\n entryPoints: string[];\n agentModel: string;\n organization?: string;\n outputDir: string;\n dbPath: string;\n verbose: boolean;\n}\n\nexport function defaultConfig(overrides: Partial<CartographyConfig> = {}): CartographyConfig {\n const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';\n return {\n maxDepth: 8,\n maxTurns: 50,\n entryPoints: ['localhost'],\n agentModel: 'claude-sonnet-4-5-20250929',\n outputDir: './cartography-output',\n dbPath: `${home}/.cartography/cartography.db`,\n verbose: false,\n ...overrides,\n };\n}\n","import { z } from 'zod';\nimport type { CartographyDB } from './db.js';\nimport { NODE_TYPES, EDGE_RELATIONSHIPS } from './types.js';\nimport { scanAllBookmarks, scanAllHistory } from './bookmarks.js';\nimport {\n IS_WIN, IS_MAC, IS_LINUX, HOME, PLATFORM,\n run, commandExists, findFiles, dbScanDirs,\n scanListeningPorts, scanProcesses,\n scanWindowsPrograms, scanWindowsDbServices,\n} from './platform.js';\n\n// Lazy import to avoid hard-wiring SDK at module parse time\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype McpServer = any;\n\n/**\n * Circuit breaker for sequential CLI scans.\n * After `threshold` consecutive failures, remaining commands are skipped.\n */\nfunction createScanRunner(\n runFn: (cmd: string, opts?: { timeout?: number; env?: NodeJS.ProcessEnv }) => string,\n opts: { timeout?: number; env?: NodeJS.ProcessEnv; threshold?: number } = {},\n) {\n const threshold = opts.threshold ?? 3;\n let consecutiveFailures = 0;\n let tripped = false;\n\n return (cmd: string): string => {\n if (tripped) return '(skipped — circuit breaker: too many consecutive failures)';\n const result = runFn(cmd, { timeout: opts.timeout ?? 20_000, env: opts.env });\n if (!result) {\n consecutiveFailures++;\n if (consecutiveFailures >= threshold) tripped = true;\n return '(error or not available)';\n }\n consecutiveFailures = 0;\n return result;\n };\n}\n\nexport interface CartographyToolsOptions {\n /** Called when the agent needs a human answer. Return the user's response. */\n onAskUser?: (question: string, context?: string) => Promise<string>;\n}\n\nexport function stripSensitive(target: string): string {\n try {\n const url = new URL(target.startsWith('http') ? target : `tcp://${target}`);\n return `${url.hostname}${url.port ? ':' + url.port : ''}`;\n } catch {\n return target\n .replace(/\\/.*$/, '')\n .replace(/\\?.*$/, '')\n .replace(/@.*:/, ':');\n }\n}\n\nexport async function createCartographyTools(\n db: CartographyDB,\n sessionId: string,\n opts: CartographyToolsOptions = {},\n): Promise<McpServer> {\n // Dynamically import the SDK so missing package doesn't crash at load time\n const { tool, createSdkMcpServer } = await import('@anthropic-ai/claude-agent-sdk');\n\n const tools = [\n tool('save_node', 'Save an infrastructure node to the catalog', {\n id: z.string(),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discoveredVia: z.string(),\n confidence: z.number().min(0).max(1),\n metadata: z.record(z.string(), z.unknown()).optional(),\n tags: z.array(z.string()).optional(),\n domain: z.string().optional().describe('Business domain, e.g. \"Marketing\", \"Finance\"'),\n subDomain: z.string().optional().describe('Sub-domain, e.g. \"Forecast client orders\"'),\n qualityScore: z.number().min(0).max(100).optional().describe('Data quality score 0–100'),\n }, async (args) => {\n const node = {\n id: stripSensitive(args['id'] as string),\n type: args['type'] as typeof NODE_TYPES[number],\n name: args['name'] as string,\n discoveredVia: args['discoveredVia'] as string,\n confidence: args['confidence'] as number,\n metadata: (args['metadata'] as Record<string, unknown>) ?? {},\n tags: (args['tags'] as string[]) ?? [],\n domain: args['domain'] as string | undefined,\n subDomain: args['subDomain'] as string | undefined,\n qualityScore: args['qualityScore'] as number | undefined,\n };\n db.upsertNode(sessionId, node);\n return { content: [{ type: 'text', text: `✓ Node: ${node.id}` }] };\n }),\n\n tool('save_edge', 'Save a relationship (edge) between two nodes — ALWAYS save edges when connections are clear', {\n sourceId: z.string(),\n targetId: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string(),\n confidence: z.number().min(0).max(1),\n }, async (args) => {\n db.insertEdge(sessionId, {\n sourceId: args['sourceId'] as string,\n targetId: args['targetId'] as string,\n relationship: args['relationship'] as typeof EDGE_RELATIONSHIPS[number],\n evidence: args['evidence'] as string,\n confidence: args['confidence'] as number,\n });\n return { content: [{ type: 'text', text: `✓ ${args['sourceId']}→${args['targetId']}` }] };\n }),\n\n tool('get_catalog', 'Get the current catalog — use before save_node to avoid duplicates', {\n includeEdges: z.boolean().default(true),\n }, async (args) => {\n const nodes = db.getNodes(sessionId);\n const edges = (args['includeEdges'] as boolean) ? db.getEdges(sessionId) : [];\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: { nodes: nodes.length, edges: edges.length },\n nodeIds: nodes.map(n => n.id),\n }),\n }],\n };\n }),\n\n tool('ask_user', 'Ask the user a question — for clarifications, missing context, or consent (e.g. before scanning browser history)', {\n question: z.string().describe('The question for the user (clear and specific)'),\n context: z.string().optional().describe('Optional context explaining why this is relevant'),\n }, async (args) => {\n const question = args['question'] as string;\n const context = args['context'] as string | undefined;\n\n if (opts.onAskUser) {\n const answer = await opts.onAskUser(question, context);\n return { content: [{ type: 'text', text: answer }] };\n }\n\n // Fallback when not interactive (piped input, daemon, etc.)\n return {\n content: [{ type: 'text', text: '(Non-interactive mode — please continue without this information)' }],\n };\n }),\n\n tool('scan_bookmarks', 'Scan all browser bookmarks — hostnames only, no personal data (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)', {\n minConfidence: z.number().min(0).max(1).default(0.5).optional(),\n }, async () => {\n const hosts = await scanAllBookmarks();\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: hosts.length,\n hosts: hosts.map(h => ({\n hostname: h.hostname,\n port: h.port,\n protocol: h.protocol,\n source: h.source,\n })),\n note: 'Hostnames only — no paths, no personal data. Classify each as a business tool (save_node) or ignore (social media, news, shopping).',\n }),\n }],\n };\n }),\n\n tool('scan_browser_history', 'Scan browser history — anonymized hostnames + visit frequency. ALWAYS call ask_user for consent before using this tool.', {\n minVisits: z.number().min(1).default(3).optional().describe('Minimum visit count to include a host (filters rarely-visited sites)'),\n }, async (args) => {\n const minVisits = (args['minVisits'] as number | undefined) ?? 3;\n const hosts = await scanAllHistory();\n const filtered = hosts.filter(h => h.visitCount >= minVisits);\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: filtered.length,\n note: 'Anonymized — hostnames only, no URLs, no paths, no personal data. Classify business tools as saas_tool nodes.',\n hosts: filtered.map(h => ({\n hostname: h.hostname,\n visitCount: h.visitCount,\n protocol: h.protocol,\n source: h.source,\n })),\n }),\n }],\n };\n }),\n\n tool('scan_local_databases', 'Scan for local database files and running DB servers — PostgreSQL databases, MySQL, SQLite files from installed apps', {\n deep: z.boolean().default(false).optional().describe('Also search home directory recursively for SQLite/DB files (slower)'),\n }, async (args) => {\n const deep = (args['deep'] as boolean | undefined) ?? false;\n const results: Record<string, string> = {};\n\n results['PLATFORM'] = `${PLATFORM} (${IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux'})`;\n\n // ── Windows: detect DB services via Get-Service / Get-NetTCPConnection ──\n if (IS_WIN) {\n results['DB_SERVICES'] = scanWindowsDbServices() || '(no database services found)';\n }\n\n // ── PostgreSQL (cross-platform) ──\n if (commandExists('psql')) {\n if (IS_WIN) {\n results['POSTGRES_DATABASES'] = run('psql -lqt', { timeout: 10_000 }) || '(psql found but not running or requires auth)';\n } else {\n results['POSTGRES_DATABASES'] = run('psql -lqt 2>/dev/null | grep -v \"template0\\\\|template1\" | awk \\'{print $1}\\' | grep -v \"^$\\\\|^|\"') || '(psql not running or not available)';\n results['POSTGRES_CLUSTERS'] = run('pg_lsclusters 2>/dev/null') || '(pg_lsclusters not available)';\n }\n } else {\n results['POSTGRES_DATABASES'] = '(psql not installed)';\n }\n\n // ── MySQL / MariaDB (cross-platform) ──\n if (commandExists('mysql')) {\n if (IS_WIN) {\n results['MYSQL_DATABASES'] = run('mysql --connect-timeout=3 -e \"SHOW DATABASES;\"', { timeout: 10_000 }) || '(mysql not running or requires auth)';\n } else {\n results['MYSQL_DATABASES'] = run('mysql --connect-timeout=3 -e \"SHOW DATABASES;\" 2>/dev/null') || '(mysql not running or requires auth)';\n }\n } else {\n results['MYSQL_DATABASES'] = '(mysql not installed)';\n }\n\n // ── MongoDB (cross-platform) ──\n if (commandExists('mongosh')) {\n if (IS_WIN) {\n results['MONGODB_DATABASES'] = run('mongosh --quiet --eval \"db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join(\\'\\\\n\\')\"', { timeout: 10_000 }) || '(mongosh not available)';\n } else {\n results['MONGODB_DATABASES'] = run('mongosh --quiet --eval \"db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join(\\'\\\\n\\')\" 2>/dev/null') || '(mongosh not available)';\n }\n } else {\n results['MONGODB_DATABASES'] = '(mongosh not installed)';\n }\n\n // ── Redis (cross-platform) ──\n if (commandExists('redis-cli')) {\n if (IS_WIN) {\n results['REDIS_INFO'] = run('redis-cli info server', { timeout: 10_000 }).split('\\n').slice(0, 5).join('\\n') || '(redis-cli not available)';\n } else {\n results['REDIS_INFO'] = run('redis-cli info server 2>/dev/null | head -5') || '(redis-cli not available)';\n }\n } else {\n results['REDIS_INFO'] = '(redis-cli not installed)';\n }\n\n // ── SQLite files in app data directories (cross-platform) ──\n const appDirs = dbScanDirs();\n if (appDirs.length > 0) {\n results['SQLITE_APP_FILES'] = findFiles(appDirs, ['*.sqlite', '*.sqlite3', '*.db'], 4, 80) || '(none found)';\n }\n\n // ── Deep home scan (cross-platform) ──\n if (deep) {\n if (IS_WIN) {\n results['SQLITE_DEEP_SCAN'] = run(\n `Get-ChildItem -Path '${HOME}' -Recurse -Depth 6 -Include '*.sqlite','*.sqlite3','*.db' -ErrorAction SilentlyContinue | ` +\n `Where-Object { $_.FullName -notmatch 'node_modules|\\\\.git' } | ` +\n `Select-Object -First 100 -ExpandProperty FullName`,\n { timeout: 30_000 },\n ) || '(none found)';\n } else {\n results['SQLITE_DEEP_SCAN'] = run(`find \"${HOME}\" -maxdepth 6 \\\\( -name \"*.sqlite\" -o -name \"*.sqlite3\" -o -name \"*.db\" \\\\) -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" 2>/dev/null | head -100`) || '(none found)';\n }\n }\n\n // ── DB config files (cross-platform, no credentials extracted) ──\n if (IS_WIN) {\n results['DB_CONFIG_FILES'] = run(\n `Get-ChildItem -Path '${HOME}' -Recurse -Depth 4 -Include '.env','.env.local','database.yml','database.json','docker-compose.yml' -ErrorAction SilentlyContinue | ` +\n `Select-Object -First 20 -ExpandProperty FullName`,\n { timeout: 15_000 },\n ) || '(none found)';\n } else {\n results['DB_CONFIG_FILES'] = run(`find \"${HOME}\" -maxdepth 4 \\\\( -name \".env\" -o -name \".env.local\" -o -name \"database.yml\" -o -name \"database.json\" -o -name \"docker-compose.yml\" \\\\) 2>/dev/null | head -20`) || '(none found)';\n }\n\n const out = Object.entries(results).map(([k, v]) => `=== ${k} ===\\n${v}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_k8s_resources', 'Scan Kubernetes cluster via kubectl — 100% readonly (get, describe)', {\n namespace: z.string().optional().describe('Filter by namespace — empty = all namespaces'),\n }, async (args) => {\n const ns = args['namespace'] as string | undefined;\n const nsFlag = ns ? `-n ${ns}` : '--all-namespaces';\n const runK = createScanRunner(run, { timeout: 15_000, threshold: 3 });\n const sections: [string, string][] = IS_WIN\n ? [\n ['CONTEXT', 'kubectl config current-context'],\n ['NODES', 'kubectl get nodes -o wide'],\n ['NAMESPACES', 'kubectl get namespaces'],\n ['SERVICES', `kubectl get services ${nsFlag}`],\n ['DEPLOYMENTS', `kubectl get deployments ${nsFlag}`],\n ['STATEFULSETS', `kubectl get statefulsets ${nsFlag}`],\n ['INGRESSES', `kubectl get ingress ${nsFlag}`],\n ['PODS_RUNNING', `kubectl get pods ${nsFlag} --field-selector=status.phase=Running`],\n ['CONFIGMAPS_SYSTEM', 'kubectl get configmaps -n kube-system'],\n ]\n : [\n ['CONTEXT', 'kubectl config current-context 2>/dev/null || echo \"(no context set)\"'],\n ['NODES', 'kubectl get nodes -o wide'],\n ['NAMESPACES', 'kubectl get namespaces'],\n ['SERVICES', `kubectl get services ${nsFlag}`],\n ['DEPLOYMENTS', `kubectl get deployments ${nsFlag}`],\n ['STATEFULSETS', `kubectl get statefulsets ${nsFlag}`],\n ['INGRESSES', `kubectl get ingress ${nsFlag} 2>/dev/null || echo \"(none)\"`],\n ['PODS_RUNNING', `kubectl get pods ${nsFlag} --field-selector=status.phase=Running 2>/dev/null | head -60`],\n ['CONFIGMAPS_SYSTEM', 'kubectl get configmaps -n kube-system 2>/dev/null | head -30'],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${runK(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_aws_resources', 'Scan AWS infrastructure via AWS CLI — 100% readonly (describe, list)', {\n region: z.string().optional().describe('AWS Region — default: AWS_DEFAULT_REGION or profile'),\n profile: z.string().optional().describe('AWS CLI profile'),\n }, async (args) => {\n const region = args['region'] as string | undefined;\n const profile = args['profile'] as string | undefined;\n const env: NodeJS.ProcessEnv = { ...process.env };\n if (region) env['AWS_DEFAULT_REGION'] = region;\n const pf = profile ? `--profile ${profile}` : '';\n const runAws = createScanRunner(run, { timeout: 20_000, env, threshold: 3 });\n // aws CLI commands work the same on all platforms (aws is cross-platform)\n const sections: [string, string][] = [\n ['IDENTITY', `aws sts get-caller-identity ${pf} --output json`],\n ['EC2', `aws ec2 describe-instances ${pf} --query \"Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,PublicIpAddress,PrivateIpAddress]\" --output table`],\n ['RDS', `aws rds describe-db-instances ${pf} --query \"DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus,Endpoint.Address,Endpoint.Port]\" --output table`],\n ['ELB_V2', `aws elbv2 describe-load-balancers ${pf} --query \"LoadBalancers[*].[LoadBalancerName,DNSName,Type,State.Code]\" --output table`],\n ['EKS', `aws eks list-clusters ${pf} --output json`],\n ['ELASTICACHE', `aws elasticache describe-cache-clusters ${pf} --query \"CacheClusters[*].[CacheClusterId,Engine,CacheClusterStatus]\" --output table`],\n ['S3', `aws s3 ls ${pf}`],\n ['VPC', `aws ec2 describe-vpcs ${pf} --query \"Vpcs[*].[VpcId,CidrBlock,IsDefault]\" --output table`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${runAws(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_gcp_resources', 'Scan Google Cloud Platform via gcloud CLI — 100% readonly (list, describe)', {\n project: z.string().optional().describe('GCP Project ID — default: current gcloud project'),\n }, async (args) => {\n const project = args['project'] as string | undefined;\n const pf = project ? `--project ${project}` : '';\n const runGcp = createScanRunner(run, { timeout: 20_000, threshold: 3 });\n // gcloud CLI is cross-platform\n const sections: [string, string][] = [\n ['IDENTITY', `gcloud config list account --format=\"value(core.account)\"`],\n ['COMPUTE_INSTANCES', `gcloud compute instances list ${pf}`],\n ['SQL_INSTANCES', `gcloud sql instances list ${pf}`],\n ['GKE_CLUSTERS', `gcloud container clusters list ${pf}`],\n ['CLOUD_RUN', `gcloud run services list ${pf} --platform managed`],\n ['CLOUD_FUNCTIONS', `gcloud functions list ${pf}`],\n ['REDIS', `gcloud redis instances list ${pf} --regions=-`],\n ['PUBSUB', `gcloud pubsub topics list ${pf}`],\n ['SPANNER', `gcloud spanner instances list ${pf}`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${runGcp(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_azure_resources', 'Scan Azure infrastructure via az CLI — 100% readonly (list, show)', {\n subscription: z.string().optional().describe('Azure Subscription ID'),\n resourceGroup: z.string().optional().describe('Filter by resource group'),\n }, async (args) => {\n const sub = args['subscription'] as string | undefined;\n const rg = args['resourceGroup'] as string | undefined;\n const sf = sub ? `--subscription ${sub}` : '';\n const rf = rg ? `--resource-group ${rg}` : '';\n const runAz = createScanRunner(run, { timeout: 20_000, threshold: 3 });\n // az CLI is cross-platform\n const sections: [string, string][] = [\n ['IDENTITY', `az account show --output json ${sf}`],\n ['VMS', `az vm list ${sf} ${rf} --output table`],\n ['AKS', `az aks list ${sf} ${rf} --output table`],\n ['SQL_SERVERS', `az sql server list ${sf} ${rf} --output table`],\n ['POSTGRES', `az postgres server list ${sf} ${rf} --output table`],\n ['REDIS', `az redis list ${sf} ${rf} --output table`],\n ['WEBAPPS', `az webapp list ${sf} ${rf} --output table`],\n ['CONTAINER_APPS', `az containerapp list ${sf} ${rf} --output table`],\n ['FUNCTIONS', `az functionapp list ${sf} ${rf} --output table`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${runAz(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_installed_apps', 'Scan all installed apps and tools — IDEs, office, dev tools, business apps, databases', {\n searchHint: z.string().optional().describe('Optional search term to find specific tools (e.g. \"hubspot windsurf cursor\")'),\n }, async (args) => {\n const hint = args['searchHint'] as string | undefined;\n const results: Record<string, string> = {};\n results['PLATFORM'] = `${PLATFORM} (${IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux'})`;\n\n if (IS_MAC) {\n // macOS: scan /Applications\n results['APPLICATIONS'] = run('ls /Applications/ 2>/dev/null | head -200') || '(empty)';\n results['USER_APPLICATIONS'] = run('ls ~/Applications/ 2>/dev/null | head -100') || '(empty)';\n // Homebrew\n results['BREW_CASKS'] = run('brew list --cask 2>/dev/null | head -100') || '(brew not installed)';\n results['BREW_FORMULAE'] = run('brew list --formula 2>/dev/null | head -150') || '(brew not installed)';\n // Spotlight — find .app bundles\n results['SPOTLIGHT_APPS'] = run('mdfind \"kMDItemKind == \\'Application\\'\" 2>/dev/null | grep -v \"^/System\" | grep -v \"^/Library/Apple\" | head -100') || '(Spotlight not available)';\n } else if (IS_LINUX) {\n // Linux: dpkg, snap, flatpak, .desktop files\n results['DPKG'] = run('dpkg --list 2>/dev/null | awk \\'{print $2}\\' | head -200') || '(dpkg not available)';\n results['SNAP'] = run('snap list 2>/dev/null | head -50') || '(snap not available)';\n results['FLATPAK'] = run('flatpak list 2>/dev/null | head -50') || '(flatpak not available)';\n results['DESKTOP_FILES'] = run('ls /usr/share/applications/*.desktop ~/.local/share/applications/*.desktop 2>/dev/null | xargs -I{} basename {} .desktop 2>/dev/null | head -100') || '(no .desktop files)';\n results['RPM'] = run('rpm -qa 2>/dev/null | head -200') || '(rpm not available)';\n } else if (IS_WIN) {\n // Windows: winget, registry, Get-Package\n results['WINGET'] = run('winget list --accept-source-agreements', { timeout: 20_000 }) || '(winget not available)';\n results['INSTALLED_PROGRAMS'] = scanWindowsPrograms() || '(registry scan failed)';\n // Chocolatey\n results['CHOCO'] = run('choco list --local-only', { timeout: 15_000 }) || '(chocolatey not installed)';\n // Scoop\n results['SCOOP'] = run('scoop list', { timeout: 15_000 }) || '(scoop not installed)';\n }\n\n // ── Check known dev/business tools via cross-platform commandExists ──\n const knownTools = [\n // IDEs & Editors\n 'code', 'code-insiders', 'cursor', 'windsurf', 'zed', 'vim', 'nvim', 'emacs', 'nano', 'sublime_text', 'atom',\n 'idea', 'webstorm', 'pycharm', 'goland', 'datagrip', 'clion', 'rider', 'phpstorm', 'rubymine', 'appcode',\n // Dev Tools\n 'git', 'gh', 'docker', 'docker-compose', 'podman', 'kubectl', 'helm', 'terraform', 'ansible',\n 'node', 'npm', 'npx', 'yarn', 'pnpm', 'bun', 'deno',\n 'python', 'python3', 'pip', 'pip3', 'pipenv', 'poetry', 'conda',\n 'ruby', 'gem', 'bundler', 'rails',\n 'java', 'mvn', 'gradle', 'kotlin',\n 'go', 'cargo', 'rustc',\n 'php', 'composer',\n 'dotnet',\n // Databases\n 'psql', 'mysql', 'mysqladmin', 'mongo', 'mongosh', 'redis-cli', 'sqlite3', 'clickhouse-client',\n // Cloud CLIs\n 'aws', 'gcloud', 'az', 'heroku', 'fly', 'vercel', 'netlify', 'wrangler',\n // Infra\n 'vagrant', 'packer', 'consul', 'vault', 'nomad',\n // Communication / SaaS\n 'slack', 'discord', 'zoom', 'teams', 'skype', 'telegram', 'signal',\n // Browsers\n 'google-chrome', 'chromium', 'firefox', 'safari', 'brave', 'opera', 'edge',\n // Windows-specific\n ...(IS_WIN ? ['pwsh', 'powershell', 'wsl', 'winget', 'choco', 'scoop', 'notepad++'] : []),\n // Monitoring / Analytics\n 'datadog-agent', 'newrelic-agent', 'prometheus', 'grafana-cli',\n // Other tools\n 'ngrok', 'stripe', 'supabase', 'neon',\n ];\n\n const found: string[] = [];\n const notFound: string[] = [];\n for (const t of knownTools) {\n const r = commandExists(t);\n if (r) found.push(`${t}: ${r}`);\n else notFound.push(t);\n }\n results['TOOLS_FOUND'] = found.join('\\n') || '(none found)';\n results['TOOLS_NOT_FOUND'] = notFound.join(', ');\n\n // Hint-based search: targeted lookup for user-specified tools\n if (hint) {\n const terms = hint.split(/[\\s,]+/).filter(Boolean);\n const hintResults: string[] = [];\n for (const term of terms) {\n const safe = term.replace(/[^a-zA-Z0-9._-]/g, '');\n if (!safe) continue;\n // First try commandExists\n const cmdPath = commandExists(safe);\n if (cmdPath) {\n hintResults.push(`${term}: ${cmdPath}`);\n continue;\n }\n // Platform-specific fallback search\n let fallback = '';\n if (IS_WIN) {\n fallback = run(\n `Get-ChildItem -Path 'C:\\\\Program Files','C:\\\\Program Files (x86)','${HOME}\\\\AppData\\\\Local\\\\Programs' ` +\n `-Recurse -Depth 3 -Filter '*${safe}*' -ErrorAction SilentlyContinue | ` +\n `Select-Object -First 5 -ExpandProperty FullName`,\n { timeout: 10_000 },\n );\n } else if (IS_MAC) {\n fallback = run(`mdfind -name \"${safe}\" 2>/dev/null | head -5`);\n } else {\n fallback = run(`find /usr/bin /usr/local/bin /opt/homebrew/bin ~/.local/bin /Applications ~/Applications 2>/dev/null -iname \"*${safe}*\" -maxdepth 3 2>/dev/null | head -5`);\n }\n hintResults.push(fallback ? `${term}: ${fallback}` : `${term}: (not found)`);\n }\n results['HINT_SEARCH'] = hintResults.join('\\n');\n }\n\n const out = Object.entries(results)\n .map(([k, v]) => `=== ${k} ===\\n${v}`)\n .join('\\n\\n');\n\n return { content: [{ type: 'text', text: out }] };\n }),\n ];\n\n return createSdkMcpServer({\n name: 'cartography',\n version: '0.1.0',\n tools,\n });\n}\n","import { tmpdir } from 'node:os';\nimport { existsSync, readFileSync, readdirSync, copyFileSync, statSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { HOME, IS_WIN, IS_MAC, browserBasePaths, firefoxBaseDirs } from './platform.js';\n\n/**\n * Remove orphaned temp files from previous bookmark/history scans.\n * Call at startup to prevent /tmp accumulation after crashes.\n */\nexport function cleanupTempFiles(): number {\n let cleaned = 0;\n const tmp = tmpdir();\n try {\n for (const f of readdirSync(tmp)) {\n if (f.startsWith('cartograph_') && f.endsWith('.sqlite')) {\n try {\n unlinkSync(join(tmp, f));\n cleaned++;\n } catch { /* file in use or already gone */ }\n }\n }\n } catch { /* tmpdir not readable */ }\n return cleaned;\n}\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface BookmarkHost {\n hostname: string;\n port: number;\n protocol: 'http' | 'https';\n source: string;\n}\n\nexport interface HistoryHost extends BookmarkHost {\n visitCount: number;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction extractHost(rawUrl: string, source: string): BookmarkHost | null {\n try {\n const u = new URL(rawUrl);\n if (u.protocol !== 'http:' && u.protocol !== 'https:') return null;\n const protocol = u.protocol === 'https:' ? 'https' as const : 'http' as const;\n // Strip: no paths, no params, no credentials — hostname only\n const port = u.port ? parseInt(u.port, 10) : (protocol === 'https' ? 443 : 80);\n const hostname = u.hostname.toLowerCase();\n if (!hostname || hostname === 'localhost' || hostname === '127.0.0.1') return null;\n return { hostname, port, protocol, source };\n } catch {\n return null;\n }\n}\n\n// Chrome/Edge/Brave JSON format\ninterface ChromeNode {\n type?: string;\n url?: string;\n children?: ChromeNode[];\n}\n\nfunction walkChrome(node: ChromeNode, source: string, out: BookmarkHost[]): void {\n if (node.type === 'url' && node.url) {\n const h = extractHost(node.url, source);\n if (h) out.push(h);\n }\n if (node.children) {\n for (const child of node.children) walkChrome(child, source, out);\n }\n}\n\nfunction readChromeLike(filePath: string, source: string): BookmarkHost[] {\n if (!existsSync(filePath)) return [];\n try {\n const raw = JSON.parse(readFileSync(filePath, 'utf8')) as {\n roots: Record<string, ChromeNode>;\n };\n const out: BookmarkHost[] = [];\n for (const root of Object.values(raw.roots)) {\n if (root) walkChrome(root, source, out);\n }\n return out;\n } catch {\n return [];\n }\n}\n\nasync function readFirefoxBookmarks(profileDir: string): Promise<BookmarkHost[]> {\n const src = join(profileDir, 'places.sqlite');\n if (!existsSync(src)) return [];\n const tmp = join(tmpdir(), `cartograph_ff_bm_${Date.now()}.sqlite`);\n try {\n copyFileSync(src, tmp);\n const { default: Database } = await import('better-sqlite3');\n const db = new Database(tmp, { readonly: true, fileMustExist: true });\n const rows = db.prepare(`\n SELECT DISTINCT p.url\n FROM moz_places p\n JOIN moz_bookmarks b ON b.fk = p.id\n WHERE b.type = 1 AND p.url NOT LIKE 'place:%'\n LIMIT 3000\n `).all() as { url: string }[];\n db.close();\n return rows.map(r => extractHost(r.url, 'firefox')).filter((h): h is BookmarkHost => h !== null);\n } catch {\n return [];\n } finally {\n try { (await import('node:fs')).unlinkSync(tmp); } catch { /* ignore */ }\n }\n}\n\nexport async function readFirefoxHistory(profileDir: string): Promise<HistoryHost[]> {\n const src = join(profileDir, 'places.sqlite');\n if (!existsSync(src)) return [];\n const tmp = join(tmpdir(), `cartograph_ff_hist_${Date.now()}.sqlite`);\n try {\n copyFileSync(src, tmp);\n const { default: Database } = await import('better-sqlite3');\n const db = new Database(tmp, { readonly: true, fileMustExist: true });\n const rows = db.prepare(`\n SELECT url, visit_count\n FROM moz_places\n WHERE url NOT LIKE 'place:%'\n AND visit_count > 0\n ORDER BY visit_count DESC\n LIMIT 5000\n `).all() as { url: string; visit_count: number }[];\n db.close();\n return rows\n .map(r => {\n const h = extractHost(r.url, 'firefox');\n if (!h) return null;\n return { ...h, visitCount: r.visit_count };\n })\n .filter((h): h is HistoryHost => h !== null);\n } catch {\n return [];\n } finally {\n try { (await import('node:fs')).unlinkSync(tmp); } catch { /* ignore */ }\n }\n}\n\nasync function readChromiumHistory(historyPath: string, source: string): Promise<HistoryHost[]> {\n if (!existsSync(historyPath)) return [];\n const tmp = join(tmpdir(), `cartograph_ch_hist_${Date.now()}.sqlite`);\n try {\n copyFileSync(historyPath, tmp);\n const { default: Database } = await import('better-sqlite3');\n const db = new Database(tmp, { readonly: true, fileMustExist: true });\n const rows = db.prepare(`\n SELECT url, visit_count\n FROM urls\n WHERE hidden = 0\n AND visit_count > 0\n ORDER BY visit_count DESC\n LIMIT 5000\n `).all() as { url: string; visit_count: number }[];\n db.close();\n return rows\n .map(r => {\n const h = extractHost(r.url, source);\n if (!h) return null;\n return { ...h, visitCount: r.visit_count };\n })\n .filter((h): h is HistoryHost => h !== null);\n } catch {\n return [];\n } finally {\n try { (await import('node:fs')).unlinkSync(tmp); } catch { /* ignore */ }\n }\n}\n\n// ── Platform paths ────────────────────────────────────────────────────────────\n// Uses centralized platform.ts for Linux/macOS/Windows browser base paths.\n\nconst IS_LINUX = !IS_MAC && !IS_WIN;\n\n// Browser bookmark file paths (multiple profiles supported)\nfunction chromeLikePaths(base: string): string[] {\n const paths: string[] = [];\n const defaultPath = join(base, 'Default', 'Bookmarks');\n if (existsSync(defaultPath)) paths.push(defaultPath);\n // Also check Profile 1, Profile 2, etc.\n if (existsSync(base)) {\n try {\n for (const entry of readdirSync(base)) {\n if (entry.startsWith('Profile ')) {\n const p = join(base, entry, 'Bookmarks');\n if (existsSync(p)) paths.push(p);\n }\n }\n } catch { /* ignore */ }\n }\n return paths;\n}\n\nfunction chromeLikeHistoryPaths(base: string): string[] {\n const paths: string[] = [];\n const defaultPath = join(base, 'Default', 'History');\n if (existsSync(defaultPath)) paths.push(defaultPath);\n if (existsSync(base)) {\n try {\n for (const entry of readdirSync(base)) {\n if (entry.startsWith('Profile ')) {\n const p = join(base, entry, 'History');\n if (existsSync(p)) paths.push(p);\n }\n }\n } catch { /* ignore */ }\n }\n return paths;\n}\n\n// Get browser bases from centralized platform module\nconst BROWSER_BASES = browserBasePaths();\n\nconst CHROME_BASE = BROWSER_BASES.chrome;\nconst CHROMIUM_BASE = BROWSER_BASES.chromium;\nconst EDGE_BASE = BROWSER_BASES.edge;\nconst BRAVE_BASE = BROWSER_BASES.brave;\nconst VIVALDI_BASE = BROWSER_BASES.vivaldi;\nconst OPERA_BASE = BROWSER_BASES.opera;\n\n// Snap / Flatpak variants (Linux only)\nconst CHROMIUM_SNAP_BASE = join(HOME, 'snap', 'chromium', 'common', 'chromium');\nconst CHROMIUM_FLATPAK_BASE = join(HOME, '.var', 'app', 'org.chromium.Chromium', 'config', 'chromium');\nconst CHROME_FLATPAK_BASE = join(HOME, '.var', 'app', 'com.google.Chrome', 'config', 'google-chrome');\nconst BRAVE_FLATPAK_BASE = join(HOME, '.var', 'app', 'com.brave.Browser', 'config', 'BraveSoftware', 'Brave-Browser');\nconst EDGE_FLATPAK_BASE = join(HOME, '.var', 'app', 'com.microsoft.Edge', 'config', 'microsoft-edge');\n\nfunction firefoxProfileDirs(): string[] {\n const bases = firefoxBaseDirs();\n const dirs: string[] = [];\n for (const base of bases) {\n if (!existsSync(base)) continue;\n try {\n for (const d of readdirSync(base)) {\n const full = join(base, d);\n try {\n if (statSync(full).isDirectory() && existsSync(join(full, 'places.sqlite'))) {\n dirs.push(full);\n }\n } catch { /* ignore */ }\n }\n } catch { /* ignore */ }\n }\n return dirs;\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\nexport async function scanAllBookmarks(): Promise<BookmarkHost[]> {\n const all: BookmarkHost[] = [];\n\n // Standard browser paths\n for (const p of chromeLikePaths(CHROME_BASE)) all.push(...readChromeLike(p, 'chrome'));\n for (const p of chromeLikePaths(CHROMIUM_BASE)) all.push(...readChromeLike(p, 'chromium'));\n for (const p of chromeLikePaths(EDGE_BASE)) all.push(...readChromeLike(p, 'edge'));\n for (const p of chromeLikePaths(BRAVE_BASE)) all.push(...readChromeLike(p, 'brave'));\n for (const p of chromeLikePaths(VIVALDI_BASE)) all.push(...readChromeLike(p, 'vivaldi'));\n for (const p of chromeLikePaths(OPERA_BASE)) all.push(...readChromeLike(p, 'opera'));\n\n // Snap / Flatpak paths (Linux only — not macOS, not Windows)\n if (IS_LINUX) {\n for (const p of chromeLikePaths(CHROMIUM_SNAP_BASE)) all.push(...readChromeLike(p, 'chromium-snap'));\n for (const p of chromeLikePaths(CHROMIUM_FLATPAK_BASE)) all.push(...readChromeLike(p, 'chromium-flatpak'));\n for (const p of chromeLikePaths(CHROME_FLATPAK_BASE)) all.push(...readChromeLike(p, 'chrome-flatpak'));\n for (const p of chromeLikePaths(BRAVE_FLATPAK_BASE)) all.push(...readChromeLike(p, 'brave-flatpak'));\n for (const p of chromeLikePaths(EDGE_FLATPAK_BASE)) all.push(...readChromeLike(p, 'edge-flatpak'));\n }\n\n // Firefox: standard + snap + flatpak\n for (const dir of firefoxProfileDirs()) {\n all.push(...await readFirefoxBookmarks(dir));\n }\n\n // Deduplicate by hostname\n const seen = new Set<string>();\n return all.filter(h => {\n if (seen.has(h.hostname)) return false;\n seen.add(h.hostname);\n return true;\n });\n}\n\nexport async function scanAllHistory(): Promise<HistoryHost[]> {\n const all: HistoryHost[] = [];\n\n // Standard browser paths\n for (const p of chromeLikeHistoryPaths(CHROME_BASE)) all.push(...await readChromiumHistory(p, 'chrome'));\n for (const p of chromeLikeHistoryPaths(CHROMIUM_BASE)) all.push(...await readChromiumHistory(p, 'chromium'));\n for (const p of chromeLikeHistoryPaths(EDGE_BASE)) all.push(...await readChromiumHistory(p, 'edge'));\n for (const p of chromeLikeHistoryPaths(BRAVE_BASE)) all.push(...await readChromiumHistory(p, 'brave'));\n for (const p of chromeLikeHistoryPaths(VIVALDI_BASE)) all.push(...await readChromiumHistory(p, 'vivaldi'));\n for (const p of chromeLikeHistoryPaths(OPERA_BASE)) all.push(...await readChromiumHistory(p, 'opera'));\n\n // Snap / Flatpak paths (Linux only — not macOS, not Windows)\n if (IS_LINUX) {\n for (const p of chromeLikeHistoryPaths(CHROMIUM_SNAP_BASE)) all.push(...await readChromiumHistory(p, 'chromium-snap'));\n for (const p of chromeLikeHistoryPaths(CHROMIUM_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'chromium-flatpak'));\n for (const p of chromeLikeHistoryPaths(CHROME_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'chrome-flatpak'));\n for (const p of chromeLikeHistoryPaths(BRAVE_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'brave-flatpak'));\n for (const p of chromeLikeHistoryPaths(EDGE_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'edge-flatpak'));\n }\n\n // Firefox: standard + snap + flatpak\n for (const dir of firefoxProfileDirs()) {\n all.push(...await readFirefoxHistory(dir));\n }\n\n // Deduplicate by hostname, summing visit counts\n const byHost = new Map<string, HistoryHost>();\n for (const h of all) {\n const existing = byHost.get(h.hostname);\n if (existing) {\n existing.visitCount += h.visitCount;\n } else {\n byHost.set(h.hostname, { ...h });\n }\n }\n\n // Sort by visit count descending\n return [...byHost.values()].sort((a, b) => b.visitCount - a.visitCount);\n}\n","/**\n * Cross-platform utilities for Linux, macOS, and Windows.\n * Centralizes all OS-specific logic so scanning tools work everywhere.\n */\n\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\n\n// ── Platform detection ───────────────────────────────────────────────────────\n\nexport type Platform = 'linux' | 'darwin' | 'win32';\n\nexport const PLATFORM: Platform = process.platform as Platform;\nexport const IS_WIN = PLATFORM === 'win32';\nexport const IS_MAC = PLATFORM === 'darwin';\nexport const IS_LINUX = PLATFORM === 'linux';\nexport const HOME = homedir();\n\n// ── Shell selection ──────────────────────────────────────────────────────────\n\n/**\n * Returns the correct shell for execSync on each platform.\n * - Windows: PowerShell (pwsh if available, otherwise powershell.exe)\n * - macOS/Linux: /bin/sh\n */\nexport function platformShell(): string {\n if (!IS_WIN) return '/bin/sh';\n // Prefer pwsh (PowerShell 7+) over powershell.exe (5.1)\n try {\n execSync('pwsh -Version', { stdio: 'pipe', timeout: 3000 });\n return 'pwsh';\n } catch {\n return 'powershell.exe';\n }\n}\n\n/** Cached shell value (computed once) */\nlet _shell: string | undefined;\nexport function getShell(): string {\n if (!_shell) _shell = platformShell();\n return _shell;\n}\n\n// ── Cross-platform command runner ────────────────────────────────────────────\n\nexport interface RunOptions {\n timeout?: number;\n env?: NodeJS.ProcessEnv;\n}\n\n/**\n * Run a shell command, returning stdout as string. Returns '' on error.\n * Automatically uses the correct shell for the platform.\n */\n/** Safe environment variables — excludes secrets from child processes */\nconst SAFE_ENV_KEYS = [\n 'PATH', 'HOME', 'USER', 'LANG', 'LC_ALL', 'TERM', 'SHELL',\n 'USERPROFILE', 'LOCALAPPDATA', 'APPDATA', 'PROGRAMFILES',\n 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_RUNTIME_DIR',\n 'AWS_DEFAULT_REGION', 'AWS_PROFILE', 'AWS_CONFIG_FILE',\n 'KUBECONFIG', 'GOOGLE_APPLICATION_CREDENTIALS',\n 'AZURE_CONFIG_DIR',\n];\n\nexport function safeEnv(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n for (const key of SAFE_ENV_KEYS) {\n if (process.env[key]) env[key] = process.env[key];\n }\n return env;\n}\n\nexport function run(cmd: string, opts: RunOptions = {}): string {\n try {\n return execSync(cmd, {\n stdio: 'pipe',\n timeout: opts.timeout ?? 10_000,\n shell: getShell(),\n env: opts.env ?? safeEnv(),\n }).toString().trim();\n } catch {\n return '';\n }\n}\n\n// ── Command existence check (cross-platform `which`) ─────────────────────────\n\n/**\n * Check if a command exists. Returns its path or '' if not found.\n * - Unix: `which <cmd>`\n * - Windows: `Get-Command <cmd>` via PowerShell\n */\nexport function commandExists(cmd: string): string {\n if (IS_WIN) {\n const r = run(`Get-Command ${cmd} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source`, { timeout: 5000 });\n return r;\n }\n return run(`which ${cmd} 2>/dev/null`);\n}\n\n// ── Null device ──────────────────────────────────────────────────────────────\n\nexport const NULL_DEV = IS_WIN ? 'NUL' : '/dev/null';\n\n// ── App data directories ─────────────────────────────────────────────────────\n\n/** Returns the platform-specific user app data directory */\nexport function appDataDir(): string {\n if (IS_WIN) return process.env.LOCALAPPDATA ?? join(HOME, 'AppData', 'Local');\n if (IS_MAC) return join(HOME, 'Library', 'Application Support');\n return process.env.XDG_CONFIG_HOME ?? join(HOME, '.config');\n}\n\n/** Returns the platform-specific user data directory (broader than config) */\nexport function userDataDir(): string {\n if (IS_WIN) return process.env.APPDATA ?? join(HOME, 'AppData', 'Roaming');\n if (IS_MAC) return join(HOME, 'Library', 'Application Support');\n return process.env.XDG_DATA_HOME ?? join(HOME, '.local', 'share');\n}\n\n// ── Browser profile base paths (Chromium-based) ──────────────────────────────\n\nexport interface BrowserPaths {\n chrome: string;\n chromium: string;\n edge: string;\n brave: string;\n vivaldi: string;\n opera: string;\n}\n\nexport function browserBasePaths(): BrowserPaths {\n if (IS_WIN) {\n const local = process.env.LOCALAPPDATA ?? join(HOME, 'AppData', 'Local');\n return {\n chrome: join(local, 'Google', 'Chrome', 'User Data'),\n chromium: join(local, 'Chromium', 'User Data'),\n edge: join(local, 'Microsoft', 'Edge', 'User Data'),\n brave: join(local, 'BraveSoftware', 'Brave-Browser', 'User Data'),\n vivaldi: join(local, 'Vivaldi', 'User Data'),\n opera: join(userDataDir(), 'Opera Software', 'Opera Stable'),\n };\n }\n if (IS_MAC) {\n const lib = join(HOME, 'Library', 'Application Support');\n return {\n chrome: join(lib, 'Google', 'Chrome'),\n chromium: join(lib, 'Chromium'),\n edge: join(lib, 'Microsoft Edge'),\n brave: join(lib, 'BraveSoftware', 'Brave-Browser'),\n vivaldi: join(lib, 'Vivaldi'),\n opera: join(lib, 'com.operasoftware.Opera'),\n };\n }\n // Linux\n return {\n chrome: join(HOME, '.config', 'google-chrome'),\n chromium: join(HOME, '.config', 'chromium'),\n edge: join(HOME, '.config', 'microsoft-edge'),\n brave: join(HOME, '.config', 'BraveSoftware', 'Brave-Browser'),\n vivaldi: join(HOME, '.config', 'vivaldi'),\n opera: join(HOME, '.config', 'opera'),\n };\n}\n\n/** Firefox profile parent directories per platform */\nexport function firefoxBaseDirs(): string[] {\n if (IS_WIN) {\n const roaming = process.env.APPDATA ?? join(HOME, 'AppData', 'Roaming');\n return [join(roaming, 'Mozilla', 'Firefox', 'Profiles')];\n }\n if (IS_MAC) {\n return [join(HOME, 'Library', 'Application Support', 'Firefox', 'Profiles')];\n }\n // Linux: standard + snap + flatpak\n return [\n join(HOME, '.mozilla', 'firefox'),\n join(HOME, 'snap', 'firefox', 'common', '.mozilla', 'firefox'),\n join(HOME, '.var', 'app', 'org.mozilla.firefox', '.mozilla', 'firefox'),\n ];\n}\n\n// ── Database scan directories ────────────────────────────────────────────────\n\n/** Returns directories to search for SQLite/DB files per platform */\nexport function dbScanDirs(): string[] {\n const dirs: string[] = [];\n if (IS_WIN) {\n const local = process.env.LOCALAPPDATA ?? join(HOME, 'AppData', 'Local');\n const roaming = process.env.APPDATA ?? join(HOME, 'AppData', 'Roaming');\n dirs.push(local, roaming);\n const pd = join(HOME, 'AppData', 'Local', 'Programs');\n if (existsSync(pd)) dirs.push(pd);\n } else if (IS_MAC) {\n dirs.push(join(HOME, 'Library', 'Application Support'));\n if (existsSync('/var/lib')) dirs.push('/var/lib');\n } else {\n const configDir = join(HOME, '.config');\n const dataDir = join(HOME, '.local', 'share');\n if (existsSync(configDir)) dirs.push(configDir);\n if (existsSync(dataDir)) dirs.push(dataDir);\n if (existsSync('/var/lib')) dirs.push('/var/lib');\n }\n return dirs.filter(d => existsSync(d));\n}\n\n// ── File search (cross-platform find) ────────────────────────────────────────\n\n/**\n * Search for files matching glob patterns in given directories.\n * - Unix: `find` command\n * - Windows: PowerShell `Get-ChildItem`\n */\nexport function findFiles(dirs: string[], patterns: string[], maxDepth: number, limit: number): string {\n if (dirs.length === 0) return '';\n if (IS_WIN) {\n const includes = patterns.map(p => `'${p}'`).join(',');\n const pathList = dirs.map(d => `'${d}'`).join(',');\n return run(\n `Get-ChildItem -Path ${pathList} -Recurse -Depth ${maxDepth} -Include ${includes} -ErrorAction SilentlyContinue | Select-Object -First ${limit} -ExpandProperty FullName`,\n { timeout: 15_000 },\n );\n }\n const nameArgs = patterns.map(p => `-name \"${p}\"`).join(' -o ');\n const findCmds = dirs.map(d => `find \"${d}\" -maxdepth ${maxDepth} \\\\( ${nameArgs} \\\\) 2>/dev/null`).join('; ');\n return run(`{ ${findCmds}; } | head -${limit}`, { timeout: 15_000 });\n}\n\n// ── Network scanning ─────────────────────────────────────────────────────────\n\n/** Get all listening TCP ports and the processes behind them */\nexport function scanListeningPorts(): string {\n if (IS_WIN) {\n // PowerShell: Get-NetTCPConnection for listening ports + owning process\n return run(\n `Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | ` +\n `ForEach-Object { $p = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue; ` +\n `\"$($_.LocalAddress):$($_.LocalPort) PID=$($_.OwningProcess) $($p.ProcessName)\" } | ` +\n `Sort-Object -Unique`,\n { timeout: 15_000 },\n );\n }\n if (IS_MAC) {\n // macOS: lsof is the most reliable way (ss not available)\n return run('sudo lsof -iTCP -sTCP:LISTEN -n -P 2>/dev/null || lsof -iTCP -sTCP:LISTEN -n -P 2>/dev/null', { timeout: 15_000 });\n }\n // Linux: ss is the standard tool\n return run('ss -tlnp 2>/dev/null', { timeout: 10_000 });\n}\n\n/** Get running processes (cross-platform) */\nexport function scanProcesses(): string {\n if (IS_WIN) {\n return run(\n `Get-Process | Select-Object -Property Id, ProcessName, Path | Format-Table -AutoSize | Out-String -Width 200`,\n { timeout: 15_000 },\n );\n }\n return run('ps aux 2>/dev/null', { timeout: 10_000 });\n}\n\n// ── Windows-specific: installed programs ─────────────────────────────────────\n\n/** Scan Windows registry for installed programs */\nexport function scanWindowsPrograms(): string {\n if (!IS_WIN) return '';\n return run(\n `$paths = @(` +\n `'HKLM:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*',` +\n `'HKLM:\\\\Software\\\\Wow6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*',` +\n `'HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*'` +\n `); Get-ItemProperty $paths -ErrorAction SilentlyContinue | ` +\n `Where-Object { $_.DisplayName } | ` +\n `Select-Object -Property DisplayName, Publisher, DisplayVersion | ` +\n `Sort-Object DisplayName | ` +\n `Format-Table -AutoSize | Out-String -Width 300`,\n { timeout: 20_000 },\n );\n}\n\n/** Scan Windows services for database engines */\nexport function scanWindowsDbServices(): string {\n if (!IS_WIN) return '';\n return run(\n `Get-Service | Where-Object { ` +\n `$_.Name -match 'postgres|mysql|mariadb|mongo|redis|MSSQL|elastic|clickhouse|cassandra' ` +\n `} | Select-Object Name, DisplayName, Status, StartType | Format-Table -AutoSize`,\n { timeout: 10_000 },\n );\n}\n\n// ── file:// URL helper ───────────────────────────────────────────────────────\n\n/** Generate a correct file:// URL for the current platform */\nexport function fileUrl(absPath: string): string {\n if (IS_WIN) {\n // Windows: file:///C:/Users/... (forward slashes, triple slash)\n const normalized = absPath.replace(/\\\\/g, '/');\n return `file:///${normalized}`;\n }\n return `file://${absPath}`;\n}\n","// PreToolUse Safety Hook — enforces read-only policy on all Bash calls\n\nimport type { HookCallback } from '@anthropic-ai/claude-agent-sdk';\n\n// Word-boundary matched dangerous commands (Unix + Windows/PowerShell)\nconst BLOCKED_CMDS =\n /\\b(rm|mv|cp|dd|mkfs|chmod|chown|chgrp|kill|killall|pkill|reboot|shutdown|poweroff|halt|systemctl\\s+(start|stop|restart|enable|disable)|service\\s+(start|stop|restart)|docker\\s+(rm|rmi|stop|kill|exec|run|build|push)|kubectl\\s+(delete|apply|edit|exec|run|create|patch)|apt|yum|dnf|pacman|pip\\s+install|npm\\s+(install|uninstall)|curl\\s+.*-X\\s*(POST|PUT|DELETE|PATCH)|wget\\s+-O|tee\\s|Remove-Item|Move-Item|Copy-Item|Stop-Process|Stop-Service|Restart-Service|Start-Service|Set-Service|Invoke-WebRequest\\s+.*-Method\\s+(POST|PUT|DELETE|PATCH)|del\\s|rmdir\\s|Format-Volume|Clear-Disk|Stop-Computer|Restart-Computer|Uninstall-Package|Install-Package|Install-Module)\\b/i;\n// Redirect operators (no word boundary needed) — both Unix and PowerShell\nconst BLOCKED_REDIRECTS = />>|>[^>]|Out-File|Set-Content|Add-Content/;\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 ?? '';\n\n if (BLOCKED_CMDS.test(cmd) || BLOCKED_REDIRECTS.test(cmd)) {\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'deny',\n permissionDecisionReason: `BLOCKED: \"${cmd}\" — read-only policy`,\n },\n };\n }\n\n return {\n hookSpecificOutput: {\n hookEventName: 'PreToolUse',\n permissionDecision: 'allow',\n },\n };\n};\n","import type { CartographyDB } from './db.js';\nimport { createCartographyTools } from './tools.js';\nimport { safetyHook } from './safety.js';\nimport type { CartographyConfig } from './types.js';\nimport { IS_WIN, IS_MAC, PLATFORM } from './platform.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 { query } = await import('@anthropic-ai/claude-agent-sdk');\n const tools = await createCartographyTools(db, sessionId, { onAskUser });\n\n const hintSection = hint\n ? `\\n⚡ USER HINT (HIGH PRIORITY): The user wants to find these specific tools: \"${hint}\"\\n → Run scan_installed_apps(searchHint: \"${hint}\") IMMEDIATELY and save found tools as saas_tool nodes!\\n`\n : '';\n\n // 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\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 let turnCount = 0;\n\n try {\n const startTime = Date.now();\n\n for await (const msg of query({\n prompt: initialPrompt,\n options: {\n model: config.agentModel,\n maxTurns: config.maxTurns,\n systemPrompt,\n mcpServers: { cartography: tools },\n allowedTools: [\n 'Bash',\n 'mcp__cartograph__save_node',\n 'mcp__cartograph__save_edge',\n 'mcp__cartograph__get_catalog',\n 'mcp__cartograph__scan_bookmarks',\n 'mcp__cartograph__scan_browser_history',\n 'mcp__cartograph__scan_installed_apps',\n 'mcp__cartograph__scan_local_databases',\n 'mcp__cartograph__scan_k8s_resources',\n 'mcp__cartograph__scan_aws_resources',\n 'mcp__cartograph__scan_gcp_resources',\n 'mcp__cartograph__scan_azure_resources',\n 'mcp__cartograph__ask_user',\n ],\n hooks: {\n PreToolUse: [{ matcher: 'Bash', hooks: [safetyHook] }],\n },\n permissionMode: 'bypassPermissions',\n },\n })) {\n // Wall-clock timeout guard\n if (Date.now() - startTime > MAX_DISCOVERY_MS) {\n onEvent?.({ kind: 'error', text: `Discovery timeout after ${MAX_DISCOVERY_MS / 60000} minutes` });\n onEvent?.({ kind: 'done' });\n return;\n }\n\n if (!onEvent) continue;\n\n if (msg.type === 'assistant') {\n turnCount++;\n onEvent({ kind: 'turn', turn: turnCount });\n\n for (const block of msg.message.content) {\n if (block.type === 'text') {\n onEvent({ kind: 'thinking', text: block.text });\n }\n if (block.type === 'tool_use') {\n onEvent({\n kind: 'tool_call',\n tool: block.name as string,\n input: block.input as Record<string, unknown>,\n });\n }\n }\n }\n\n if (msg.type === 'user') {\n const content = msg.message?.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (typeof block === 'object' && block !== null && 'type' in block && (block as { type: string }).type === 'tool_result') {\n const tb = block as { tool_use_id?: string; content?: unknown };\n const text = typeof tb.content === 'string' ? tb.content : '';\n onEvent({ kind: 'tool_result', tool: tb.tool_use_id ?? '', output: text });\n }\n }\n }\n }\n\n if (msg.type === 'result') {\n onEvent({ kind: 'done' });\n return;\n }\n }\n } 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","import { mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { CartographyDB } from './db.js';\nimport type { NodeRow, EdgeRow } from './types.js';\nimport { buildMapData } from './mapper.js';\nimport { shadeVariant } from './cluster.js';\nimport { hexToPixel } from './hex.js';\n\n// ── Layer assignment ─────────────────────────────────────────────────────────\n\nfunction nodeLayer(type: string): string {\n if (type === 'saas_tool') return 'saas';\n if (['web_service', 'api_endpoint'].includes(type)) return 'web';\n if (['database_server', 'database', 'table', 'cache_server'].includes(type)) return 'data';\n if (['message_broker', 'queue', 'topic'].includes(type)) return 'messaging';\n if (['host', 'container', 'pod', 'k8s_cluster'].includes(type)) return 'infra';\n if (type === 'config_file') return 'config';\n return 'other';\n}\n\nconst LAYER_LABELS: Record<string, string> = {\n saas: '☁ SaaS Tools',\n web: '🌐 Web / API',\n data: '🗄 Data Layer',\n messaging: '📨 Messaging',\n infra: '🖥 Infrastructure',\n config: '📄 Config',\n other: '❓ Other',\n};\n\nconst LAYER_ORDER = ['saas', 'web', 'data', 'messaging', 'infra', 'config', 'other'];\n\n// ── Icons & Labels ───────────────────────────────────────────────────────────\n\nconst MERMAID_ICONS: Record<string, string> = {\n host: '🖥',\n database_server: '🗄',\n database: '🗄',\n table: '📋',\n web_service: '🌐',\n api_endpoint: '🔌',\n cache_server: '⚡',\n message_broker: '📨',\n queue: '📬',\n topic: '📢',\n container: '📦',\n pod: '☸',\n k8s_cluster: '☸',\n config_file: '📄',\n saas_tool: '☁',\n unknown: '❓',\n};\n\nconst EDGE_LABELS: Record<string, string> = {\n connects_to: '→',\n reads_from: 'reads',\n writes_to: 'writes',\n calls: 'calls',\n contains: 'contains',\n depends_on: 'depends on',\n};\n\n// Class colors per type (dark-theme friendly)\nconst MERMAID_CLASSES: Record<string, string> = {\n host: 'fill:#1e3352,stroke:#4a82c4,color:#cce',\n database_server:'fill:#1e3352,stroke:#4a82c4,color:#cce',\n database: 'fill:#163352,stroke:#3a8ad4,color:#bdf',\n table: 'fill:#0f2a40,stroke:#2a6090,color:#9bd',\n web_service: 'fill:#1a3a1a,stroke:#3a9a3a,color:#bfb',\n api_endpoint: 'fill:#0f2a0f,stroke:#2a7a2a,color:#9d9',\n cache_server: 'fill:#3a2a0a,stroke:#ca8a0a,color:#fda',\n message_broker: 'fill:#2a1a3a,stroke:#7a3aaa,color:#daf',\n queue: 'fill:#1f1030,stroke:#5a2a8a,color:#caf',\n topic: 'fill:#1f1030,stroke:#5a2a8a,color:#caf',\n container: 'fill:#1a2a3a,stroke:#3a6a9a,color:#acd',\n pod: 'fill:#0f1f2f,stroke:#2a5a8a,color:#8bc',\n k8s_cluster: 'fill:#0a1520,stroke:#1a4a7a,color:#7ab',\n config_file: 'fill:#2a2a1a,stroke:#7a7a2a,color:#ddc',\n saas_tool: 'fill:#2a1a2a,stroke:#9a3a9a,color:#daf',\n unknown: 'fill:#2a2a2a,stroke:#5a5a5a,color:#aaa',\n};\n\n// ── Mermaid ──────────────────────────────────────────────────────────────────\n\nfunction sanitize(id: string): string {\n return id.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n\nfunction nodeLabel(node: NodeRow): string {\n const icon = MERMAID_ICONS[node.type] ?? '?';\n const parts = node.id.split(':');\n const location = parts.length >= 3 ? `${parts[1]}:${parts[2]}` : parts[1] ?? '';\n const conf = `${Math.round(node.confidence * 100)}%`;\n\n // Pull 1-2 key metadata fields (no credentials)\n const meta = node.metadata as Record<string, unknown>;\n const extras: string[] = [];\n for (const key of ['category', 'version', 'description']) {\n const v = meta[key];\n if (typeof v === 'string' && v.length > 0) {\n extras.push(v.substring(0, 28));\n break; // max 1 extra line for readability\n }\n }\n\n const locLine = location ? `<br/><small>${location}</small>` : '';\n const extraLine = extras.length ? `<br/><small>${extras[0]}</small>` : '';\n return `[\"${icon} <b>${node.name}</b>${locLine}${extraLine}<br/><small>${node.type} · ${conf}</small>\"]`;\n}\n\nexport function generateTopologyMermaid(nodes: NodeRow[], edges: EdgeRow[]): string {\n if (nodes.length === 0) return 'graph TB\\n empty[\"No nodes discovered yet\"]';\n\n const lines: string[] = ['graph TB'];\n\n // classDef per used type\n const usedTypes = new Set(nodes.map(n => n.type));\n for (const type of usedTypes) {\n const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES['unknown']!;\n lines.push(` classDef ${type.replace(/_/g, '')} ${style}`);\n }\n lines.push('');\n\n // Group by semantic layer (ordered top→bottom)\n const layerMap = new Map<string, NodeRow[]>();\n for (const node of nodes) {\n const layer = nodeLayer(node.type);\n if (!layerMap.has(layer)) layerMap.set(layer, []);\n layerMap.get(layer)!.push(node);\n }\n\n for (const layerKey of LAYER_ORDER) {\n const layerNodes = layerMap.get(layerKey);\n if (!layerNodes || layerNodes.length === 0) continue;\n const label = LAYER_LABELS[layerKey] ?? layerKey;\n lines.push(` subgraph ${layerKey}[\"${label}\"]`);\n for (const node of layerNodes) {\n lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, '')}`);\n }\n lines.push(' end');\n lines.push('');\n }\n\n // Edges: dashed for low-confidence (<0.6), solid otherwise\n for (const edge of edges) {\n const src = sanitize(edge.sourceId);\n const tgt = sanitize(edge.targetId);\n const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;\n const arrow = edge.confidence < 0.6 ? `-. \"${label}\" .->` : `-->|\"${label}\"|`;\n lines.push(` ${src} ${arrow} ${tgt}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function generateDependencyMermaid(nodes: NodeRow[], edges: EdgeRow[]): string {\n const depEdges = edges.filter(e =>\n ['calls', 'reads_from', 'writes_to', 'depends_on'].includes(e.relationship)\n );\n\n if (depEdges.length === 0) return 'graph LR\\n empty[\"No dependency edges found\"]';\n\n const lines: string[] = ['graph LR'];\n\n const usedIds = new Set<string>();\n for (const edge of depEdges) {\n usedIds.add(edge.sourceId);\n usedIds.add(edge.targetId);\n }\n\n const usedNodes = nodes.filter(n => usedIds.has(n.id));\n const usedTypes = new Set(usedNodes.map(n => n.type));\n for (const type of usedTypes) {\n const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES['unknown']!;\n lines.push(` classDef ${type.replace(/_/g, '')} ${style}`);\n }\n lines.push('');\n\n for (const node of usedNodes) {\n lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, '')}`);\n }\n lines.push('');\n\n for (const edge of depEdges) {\n const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;\n lines.push(` ${sanitize(edge.sourceId)} -->|\"${label}\"| ${sanitize(edge.targetId)}`);\n }\n\n return lines.join('\\n');\n}\n\n// ── Backstage YAML ───────────────────────────────────────────────────────────\n\nexport function exportBackstageYAML(nodes: NodeRow[], edges: EdgeRow[], org?: string): string {\n const owner = org ?? 'unknown';\n const docs: string[] = [];\n\n for (const node of nodes) {\n const isComponent = ['web_service', 'container', 'pod'].includes(node.type);\n const isAPI = node.type === 'api_endpoint';\n const kind = isComponent ? 'Component' : isAPI ? 'API' : 'Resource';\n\n const deps = edges\n .filter(e => e.sourceId === node.id)\n .map(e => ` - resource:default/${sanitize(e.targetId)}`);\n\n const doc = [\n `apiVersion: backstage.io/v1alpha1`,\n `kind: ${kind}`,\n `metadata:`,\n ` name: ${sanitize(node.id)}`,\n ` annotations:`,\n ` cartography/discovered-at: \"${node.discoveredAt}\"`,\n ` cartography/confidence: \"${node.confidence}\"`,\n `spec:`,\n ` type: ${node.type}`,\n ` lifecycle: production`,\n ` owner: ${owner}`,\n ...(deps.length > 0 ? [' dependsOn:', ...deps] : []),\n ].join('\\n');\n\n docs.push(doc);\n }\n\n return docs.join('\\n---\\n');\n}\n\n// ── JSON ─────────────────────────────────────────────────────────────────────\n\nexport function exportJSON(db: CartographyDB, sessionId: string): string {\n const nodes = db.getNodes(sessionId);\n const edges = db.getEdges(sessionId);\n const events = db.getEvents(sessionId);\n const tasks = db.getTasks(sessionId);\n const 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> \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\">⌕</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' ? '☼' : '☾'}</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\">🗺</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\">—</h3>\n <button class=\"close-btn\" id=\"dp-close\" aria-label=\"Close panel\">✕</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\">🏷</button>\n <button class=\"tb-btn\" id=\"btn-quality\" title=\"Quality layer\" aria-pressed=\"false\" aria-label=\"Toggle quality layer\">👁</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\">−</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\">🔗</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,'&').replace(/</g,'<').replace(/>/g,'>');}\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?'☼':'☾';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 · ${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' ? '☼' : '☾'}\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\">🗺</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\">—</h3>\n <button class=\"close-btn\" id=\"md-close\" aria-label=\"Close\">✕</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\">🏷</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\">👁</button>\n <button class=\"tb-tool\" id=\"btn-connect\" title=\"Connection tool\">🔗</button>\n </div>\n <div id=\"map-tb-right\">\n <div class=\"map-zoom\">\n <button class=\"zoom-btn\" id=\"mz-out\">−</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> \n <span class=\"stats\">${nodeCount} nodes · ${edgeCount} edges</span><br/>\n <span class=\"zoom-level\">Scroll = zoom · Drag = pan · 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, '&').replace(/</g, '<').replace(/>/g, '>'); }\nfunction mRenderQ(s) {\n var c = s >= 70 ? '#22c55e' : s >= 40 ? '#f97316' : '#ef4444';\n return s + '/100 <div class=\"quality-bar\"><div class=\"quality-fill\" style=\"width:' + s + '%;background:' + c + '\"></div></div>';\n}\nfunction mShowDetail(asset) {\n document.getElementById('md-name').textContent = asset.name;\n var body = document.getElementById('md-body');\n var rows = [['Domain', asset.domain], ['Sub-domain', asset.subDomain],\n ['Quality', asset.qualityScore !== null ? mRenderQ(asset.qualityScore) : null]\n ].concat(Object.entries(asset.metadata || {}).slice(0, 8).map(function(kv) { return [kv[0], String(kv[1])]; }))\n .filter(function(r) { return r[1] !== null && r[1] !== undefined && r[1] !== ''; });\n body.innerHTML = rows.map(function(r) {\n return '<div class=\"meta-row\"><div class=\"meta-label\">' + mEsc(String(r[0])) + '</div><div class=\"meta-value\">' + r[1] + '</div></div>';\n }).join('');\n var related = mLocalConns.filter(function(cn) { return cn.sourceAssetId === asset.id || cn.targetAssetId === asset.id; });\n if (related.length > 0) {\n body.innerHTML += '<div class=\"meta-row\"><div class=\"meta-label\">Connections (' + related.length + ')</div><div>' +\n related.map(function(cn) {\n var oid = cn.sourceAssetId === asset.id ? cn.targetAssetId : cn.sourceAssetId;\n var o = mapAssetIndex.get(oid);\n return '<div class=\"meta-value\" style=\"margin-top:4px;font-size:12px\">' + (o ? mEsc(o.name) : oid) + '</div>';\n }).join('') + '</div></div>';\n }\n document.getElementById('map-detail').classList.add('open');\n}\ndocument.getElementById('md-close').addEventListener('click', function() {\n document.getElementById('map-detail').classList.remove('open'); mSelectedId = null; drawMap();\n});\n\n// ── Map toolbar ──────────────────────────────────────────────────────────────\ndocument.getElementById('btn-labels').addEventListener('click', function() {\n mShowLabels = !mShowLabels; this.classList.toggle('active', mShowLabels); drawMap();\n});\ndocument.getElementById('btn-all-labels').addEventListener('click', function() {\n mShowAllLabels = !mShowAllLabels; this.classList.toggle('active', mShowAllLabels);\n if (mShowAllLabels && !mShowLabels) { mShowLabels = true; document.getElementById('btn-labels').classList.add('active'); }\n drawMap();\n});\ndocument.getElementById('btn-quality').addEventListener('click', function() {\n mShowQuality = !mShowQuality; this.classList.toggle('active', mShowQuality); drawMap();\n});\ndocument.getElementById('btn-connect').addEventListener('click', function() {\n mConnectMode = !mConnectMode; mConnectFirst = null;\n this.classList.toggle('active', mConnectMode);\n mapWrap.classList.toggle('connecting', mConnectMode);\n document.getElementById('map-connect-hint').style.display = mConnectMode ? 'block' : 'none'; drawMap();\n});\n\n// Map keyboard\nmapWrap.addEventListener('keydown', function(e) {\n if (e.key === 'ArrowLeft') { mvx += 40; drawMap(); }\n else if (e.key === 'ArrowRight') { mvx -= 40; drawMap(); }\n else if (e.key === 'ArrowUp') { mvy += 40; drawMap(); }\n else if (e.key === 'ArrowDown') { mvy -= 40; drawMap(); }\n else if (e.key === '+' || e.key === '=') mApplyZoom(1.2, mW / 2, mH / 2);\n else if (e.key === '-') mApplyZoom(1 / 1.2, mW / 2, mH / 2);\n else if (e.key === 'Escape') {\n mSelectedId = null; document.getElementById('map-detail').classList.remove('open');\n if (mConnectMode) { mConnectMode = false; mConnectFirst = null; mapWrap.classList.remove('connecting'); document.getElementById('map-connect-hint').style.display = 'none'; document.getElementById('btn-connect').classList.remove('active'); }\n drawMap();\n }\n});\n\n// Map init\nresizeMap(); mapFitToView();\ndocument.getElementById('map-zoom-pct').textContent = Math.round(mScale * 100) + '%';\ndrawMap();\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// TOPOLOGY VIEW (lazy init)\n// ═══════════════════════════════════════════════════════════════════════════════\nvar TOPO = ${graphData};\n\nvar TYPE_COLORS = {\n host:'#4a9eff',database_server:'#ff6b6b',database:'#ff8c42',\n web_service:'#6bcb77',api_endpoint:'#4d96ff',cache_server:'#ffd93d',\n message_broker:'#c77dff',queue:'#e0aaff',topic:'#9d4edd',\n container:'#48cae4',pod:'#00b4d8',k8s_cluster:'#0077b6',\n config_file:'#adb5bd',saas_tool:'#c084fc',table:'#f97316',unknown:'#6c757d'\n};\nvar LAYER_COLORS = { saas:'#c084fc',web:'#6bcb77',data:'#ff6b6b',messaging:'#c77dff',infra:'#4a9eff',config:'#adb5bd',other:'#6c757d' };\nvar LAYER_NAMES = { saas:'SaaS Tools',web:'Web / API',data:'Data Layer',messaging:'Messaging',infra:'Infrastructure',config:'Config',other:'Other' };\n\nvar topoSelectedId = null;\n\nfunction setTopoSearch(q) {\n var el = document.getElementById('topo-search');\n if (el) { el.value = q; buildTopoList(q); }\n}\n\nfunction buildTopoList(filter) {\n var listEl = document.getElementById('topo-list');\n var q = (filter || '').toLowerCase();\n listEl.innerHTML = '';\n var sorted = TOPO.nodes.slice().sort(function(a, b) { return a.name.localeCompare(b.name); });\n for (var i = 0; i < sorted.length; i++) {\n var d = sorted[i];\n if (q && !d.name.toLowerCase().includes(q) && !d.type.includes(q) && !d.id.toLowerCase().includes(q)) continue;\n var item = document.createElement('div');\n item.className = 'topo-item' + (d.id === topoSelectedId ? ' active' : '');\n item.dataset.id = d.id;\n var color = TYPE_COLORS[d.type] || '#aaa';\n item.innerHTML = '<span class=\"topo-dot\" style=\"background:' + color + '\"></span>' +\n '<span class=\"topo-name\" title=\"' + d.id + '\">' + d.name + '</span>' +\n '<span class=\"topo-type\">' + d.type.replace(/_/g, ' ') + '</span>';\n (function(dd) { item.onclick = function() { selectTopoNode(dd); focusTopoNode(dd); }; })(d);\n listEl.appendChild(item);\n }\n}\n\ndocument.getElementById('topo-search').addEventListener('input', function(e) { buildTopoList(e.target.value); });\n\nvar topoSidebar = document.getElementById('topo-sidebar');\n\nfunction selectTopoNode(d) {\n topoSelectedId = d.id;\n buildTopoList(document.getElementById('topo-search').value);\n showTopoNode(d);\n if (typeof d3 !== 'undefined') d3.selectAll('.node-hex').classed('selected', function(nd) { return nd.id === d.id; });\n}\n\nfunction showTopoNode(d) {\n var c = TYPE_COLORS[d.type] || '#aaa';\n var confPct = Math.round(d.confidence * 100);\n var tags = (d.tags || []).map(function(t) { return '<span class=\"tag\">' + t + '</span>'; }).join('');\n var metaRows = Object.entries(d.metadata || {})\n .filter(function(kv) { return kv[1] !== null && kv[1] !== undefined && String(kv[1]).length > 0; })\n .map(function(kv) { return '<tr><td>' + kv[0] + '</td><td>' + JSON.stringify(kv[1]) + '</td></tr>'; }).join('');\n var related = TOPO.links.filter(function(l) {\n return (l.source.id || l.source) === d.id || (l.target.id || l.target) === d.id;\n });\n var edgeItems = related.map(function(l) {\n var isOut = (l.source.id || l.source) === d.id;\n var other = isOut ? (l.target.id || l.target) : (l.source.id || l.source);\n return '<div class=\"edge-item\">' + (isOut ? '\\\\u2192' : '\\\\u2190') + ' <span>' + other + '</span> <small>[' + l.relationship + ']</small></div>';\n }).join('');\n\n topoSidebar.innerHTML =\n '<h2>' + d.name + '</h2>' +\n '<table class=\"meta-table\">' +\n '<tr><td>ID</td><td style=\"font-size:10px;word-break:break-all\">' + d.id + '</td></tr>' +\n '<tr><td>Type</td><td><span style=\"color:' + c + '\">' + d.type + '</span></td></tr>' +\n '<tr><td>Layer</td><td>' + d.layer + '</td></tr>' +\n '<tr><td>Confidence</td><td>' + confPct + '% <div class=\"conf-bar\"><div class=\"conf-fill\" style=\"width:' + confPct + '%;background:' + c + '\"></div></div></td></tr>' +\n '<tr><td>Via</td><td>' + (d.discoveredVia || '\\\\u2014') + '</td></tr>' +\n '<tr><td>Timestamp</td><td>' + (d.discoveredAt ? d.discoveredAt.substring(0, 19).replace('T', ' ') : '\\\\u2014') + '</td></tr>' +\n (tags ? '<tr><td>Tags</td><td>' + tags + '</td></tr>' : '') +\n metaRows + '</table>' +\n (related.length > 0 ? '<div class=\"edges-list\"><strong>Connections (' + related.length + '):</strong>' + edgeItems + '</div>' : '') +\n '<div style=\"margin-top:14px\"><button class=\"export-btn\" style=\"width:100%\" onclick=\"deleteTopoNode(\\\\'' + d.id.replace(/'/g, \"\\\\\\\\'\") + '\\\\')\">Delete node</button></div>';\n}\n\nfunction deleteTopoNode(id) {\n var idx = TOPO.nodes.findIndex(function(n) { return n.id === id; });\n if (idx === -1) return;\n TOPO.nodes.splice(idx, 1);\n TOPO.links = TOPO.links.filter(function(l) {\n return (l.source.id || l.source) !== id && (l.target.id || l.target) !== id;\n });\n topoSelectedId = null;\n topoSidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Node deleted.</p>';\n if (typeof rebuildTopoGraph === 'function') rebuildTopoGraph();\n buildTopoList(document.getElementById('topo-search').value);\n}\n\nfunction initTopology() {\n if (typeof d3 === 'undefined') return;\n\n var svgEl = d3.select('#topo-graph svg');\n var graphDiv = document.getElementById('topo-graph');\n var gW = function() { return graphDiv.clientWidth; };\n var gH = function() { return graphDiv.clientHeight; };\n var g = svgEl.append('g');\n\n svgEl.append('defs').append('marker')\n .attr('id', 'arrow').attr('viewBox', '0 0 10 6')\n .attr('refX', 10).attr('refY', 3)\n .attr('markerWidth', 8).attr('markerHeight', 6)\n .attr('orient', 'auto')\n .append('path').attr('d', 'M0,0 L10,3 L0,6 Z').attr('fill', '#555');\n\n var currentZoom = 1;\n var zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', function(e) {\n g.attr('transform', e.transform); currentZoom = e.transform.k; updateTopoLOD(currentZoom);\n });\n svgEl.call(zoomBehavior);\n\n // Layer filters\n var layers = Array.from(new Set(TOPO.nodes.map(function(d) { return d.layer; })));\n var layerVisible = {};\n layers.forEach(function(l) { layerVisible[l] = true; });\n\n var toolbarEl = document.getElementById('topo-toolbar');\n layers.forEach(function(layer) {\n var btn = document.createElement('button');\n btn.className = 'filter-btn';\n btn.innerHTML = '<span class=\"filter-dot\" style=\"background:' + (LAYER_COLORS[layer] || '#666') + '\"></span>' + (LAYER_NAMES[layer] || layer);\n btn.onclick = function() { layerVisible[layer] = !layerVisible[layer]; btn.classList.toggle('off', !layerVisible[layer]); updateTopoVisibility(); };\n toolbarEl.appendChild(btn);\n });\n\n // JGF export button\n var jgfBtn = document.createElement('button');\n jgfBtn.className = 'export-btn'; jgfBtn.textContent = '\\\\u2193 JGF'; jgfBtn.title = 'Export JSON Graph Format';\n jgfBtn.onclick = function() {\n var jgf = { graph: { directed: true, type: 'cartography', label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(TOPO.nodes.map(function(n) { return [n.id, { label: n.name, metadata: { type: n.type, layer: n.layer, confidence: n.confidence, discoveredVia: n.discoveredVia, discoveredAt: n.discoveredAt, tags: n.tags } }]; })),\n edges: TOPO.links.map(function(l) { return { source: l.source.id || l.source, target: l.target.id || l.target, relation: l.relationship, metadata: { confidence: l.confidence, evidence: l.evidence } }; })\n }};\n var blob = new Blob([JSON.stringify(jgf, null, 2)], { type: 'application/json' });\n var url = URL.createObjectURL(blob);\n var a = document.createElement('a'); a.href = url; a.download = 'cartography-graph.jgf.json'; a.click();\n URL.revokeObjectURL(url);\n };\n toolbarEl.appendChild(jgfBtn);\n\n // Hex helpers\n var T_HEX = { saas_tool: 16, host: 18, database_server: 18, k8s_cluster: 20, default: 14 };\n function tHexSize(d) { return T_HEX[d.type] || T_HEX.default; }\n function tHexPath(size) {\n var pts = [];\n for (var i = 0; i < 6; i++) {\n var angle = (Math.PI / 3) * i - Math.PI / 6;\n pts.push([size * Math.cos(angle), size * Math.sin(angle)]);\n }\n return 'M' + pts.map(function(p) { return p.join(','); }).join('L') + 'Z';\n }\n\n // Cluster force\n function clusterForce(alpha) {\n var centroids = {}, counts = {};\n TOPO.nodes.forEach(function(d) {\n if (!centroids[d.layer]) { centroids[d.layer] = { x: 0, y: 0 }; counts[d.layer] = 0; }\n centroids[d.layer].x += d.x || 0; centroids[d.layer].y += d.y || 0; counts[d.layer]++;\n });\n for (var l in centroids) { centroids[l].x /= counts[l]; centroids[l].y /= counts[l]; }\n var strength = alpha * 0.15;\n TOPO.nodes.forEach(function(d) {\n var cn = centroids[d.layer];\n if (cn) { d.vx += (cn.x - d.x) * strength; d.vy += (cn.y - d.y) * strength; }\n });\n }\n\n // Hulls\n var hullGroup = g.append('g').attr('class', 'hulls');\n var hullPaths = {}, hullLabels = {};\n layers.forEach(function(layer) {\n hullPaths[layer] = hullGroup.append('path').attr('class', 'hull')\n .attr('fill', LAYER_COLORS[layer] || '#666').attr('stroke', LAYER_COLORS[layer] || '#666');\n hullLabels[layer] = hullGroup.append('text').attr('class', 'hull-label')\n .attr('fill', LAYER_COLORS[layer] || '#666').text(LAYER_NAMES[layer] || layer);\n });\n\n function updateHulls() {\n layers.forEach(function(layer) {\n if (!layerVisible[layer]) { hullPaths[layer].attr('d', null); hullLabels[layer].attr('x', -9999); return; }\n var pts = TOPO.nodes.filter(function(d) { return d.layer === layer && layerVisible[d.layer]; }).map(function(d) { return [d.x, d.y]; });\n if (pts.length < 3) {\n hullPaths[layer].attr('d', null);\n if (pts.length > 0) hullLabels[layer].attr('x', pts[0][0]).attr('y', pts[0][1] - 30);\n else hullLabels[layer].attr('x', -9999);\n return;\n }\n var hull = d3.polygonHull(pts);\n if (!hull) { hullPaths[layer].attr('d', null); return; }\n var cx = d3.mean(hull, function(p) { return p[0]; });\n var cy = d3.mean(hull, function(p) { return p[1]; });\n var padded = hull.map(function(p) {\n var dx = p[0] - cx, dy = p[1] - cy;\n var len = Math.sqrt(dx * dx + dy * dy) || 1;\n return [p[0] + dx / len * 40, p[1] + dy / len * 40];\n });\n hullPaths[layer].attr('d', 'M' + padded.join('L') + 'Z');\n hullLabels[layer].attr('x', cx).attr('y', cy - d3.max(hull, function(p) { return Math.abs(p[1] - cy); }) - 30);\n });\n }\n\n // Graph\n var linkSel, linkLabelSel, nodeSel, nodeLabelSel, sim;\n var linkGroup = g.append('g');\n var nodeGroup = g.append('g');\n\n function focusTopoNode(d) {\n if (!d.x || !d.y) return;\n var w = gW(), h = gH();\n svgEl.transition().duration(500).call(\n zoomBehavior.transform,\n d3.zoomIdentity.translate(w / 2, h / 2).scale(Math.min(3, currentZoom < 1 ? 1.5 : currentZoom)).translate(-d.x, -d.y)\n );\n }\n window.focusTopoNode = focusTopoNode;\n\n function rebuildTopoGraph() {\n if (sim) sim.stop();\n\n linkSel = linkGroup.selectAll('line').data(TOPO.links, function(d) { return (d.source.id || d.source) + '>' + (d.target.id || d.target); });\n linkSel.exit().remove();\n var linkEnter = linkSel.enter().append('line').attr('class', 'link');\n linkSel = linkEnter.merge(linkSel)\n .attr('stroke', function(d) { return d.confidence < 0.6 ? '#2a2e35' : '#3d434b'; })\n .attr('stroke-dasharray', function(d) { return d.confidence < 0.6 ? '4 3' : null; })\n .attr('stroke-width', function(d) { return d.confidence < 0.6 ? 0.8 : 1.2; })\n .attr('marker-end', 'url(#arrow)');\n linkSel.select('title').remove();\n linkSel.append('title').text(function(d) { return d.relationship + ' (' + Math.round(d.confidence * 100) + '%)\\\\n' + (d.evidence || ''); });\n\n linkLabelSel = linkGroup.selectAll('text').data(TOPO.links, function(d) { return (d.source.id || d.source) + '>' + (d.target.id || d.target); });\n linkLabelSel.exit().remove();\n linkLabelSel = linkLabelSel.enter().append('text').attr('class', 'link-label').merge(linkLabelSel).text(function(d) { return d.relationship; });\n\n nodeSel = nodeGroup.selectAll('g').data(TOPO.nodes, function(d) { return d.id; });\n nodeSel.exit().remove();\n var nodeEnter = nodeSel.enter().append('g')\n .call(d3.drag()\n .on('start', function(e, d) { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })\n .on('drag', function(e, d) { d.fx = e.x; d.fy = e.y; })\n .on('end', function(e, d) { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })\n )\n .on('click', function(e, d) { e.stopPropagation(); selectTopoNode(d); });\n nodeEnter.append('path').attr('class', 'node-hex');\n nodeEnter.append('title');\n nodeEnter.append('text').attr('class', 'node-label').attr('text-anchor', 'middle');\n\n nodeSel = nodeEnter.merge(nodeSel);\n nodeSel.select('.node-hex')\n .attr('d', function(d) { return tHexPath(tHexSize(d)); })\n .attr('fill', function(d) { return TYPE_COLORS[d.type] || '#aaa'; })\n .attr('stroke', function(d) { var c = d3.color(TYPE_COLORS[d.type] || '#aaa'); return c ? c.brighter(0.8).formatHex() : '#ccc'; })\n .attr('fill-opacity', function(d) { return 0.6 + d.confidence * 0.4; })\n .classed('selected', function(d) { return d.id === topoSelectedId; });\n nodeSel.select('title').text(function(d) { return d.name + ' (' + d.type + ')\\\\nconf: ' + Math.round(d.confidence * 100) + '%'; });\n nodeLabelSel = nodeSel.select('.node-label')\n .attr('dy', function(d) { return tHexSize(d) + 13; })\n .text(function(d) { return d.name.length > 20 ? d.name.substring(0, 18) + '\\\\u2026' : d.name; });\n\n sim = d3.forceSimulation(TOPO.nodes)\n .force('link', d3.forceLink(TOPO.links).id(function(d) { return d.id; }).distance(function(d) { return d.relationship === 'contains' ? 50 : 100; }).strength(0.4))\n .force('charge', d3.forceManyBody().strength(-280))\n .force('center', d3.forceCenter(gW() / 2, gH() / 2))\n .force('collision', d3.forceCollide().radius(function(d) { return tHexSize(d) + 10; }))\n .force('cluster', clusterForce)\n .on('tick', function() {\n updateHulls();\n linkSel.attr('x1', function(d) { return d.source.x; }).attr('y1', function(d) { return d.source.y; })\n .attr('x2', function(d) { return d.target.x; }).attr('y2', function(d) { return d.target.y; });\n linkLabelSel.attr('x', function(d) { return (d.source.x + d.target.x) / 2; })\n .attr('y', function(d) { return (d.source.y + d.target.y) / 2 - 4; });\n nodeSel.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });\n });\n }\n window.rebuildTopoGraph = rebuildTopoGraph;\n\n function updateTopoLOD(k) {\n if (nodeLabelSel) nodeLabelSel.style('opacity', k > 0.5 ? Math.min(1, (k - 0.5) * 2) : 0);\n if (linkLabelSel) linkLabelSel.style('opacity', k > 1.2 ? Math.min(1, (k - 1.2) * 3) : 0);\n d3.selectAll('.hull-label').style('font-size', k < 0.4 ? '18px' : '13px');\n }\n\n function updateTopoVisibility() {\n if (!nodeSel) return;\n nodeSel.style('display', function(d) { return layerVisible[d.layer] ? null : 'none'; });\n linkSel.style('display', function(d) {\n var s = TOPO.nodes.find(function(n) { return n.id === (d.source.id || d.source); });\n var t = TOPO.nodes.find(function(n) { return n.id === (d.target.id || d.target); });\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n linkLabelSel.style('display', function(d) {\n var s = TOPO.nodes.find(function(n) { return n.id === (d.source.id || d.source); });\n var t = TOPO.nodes.find(function(n) { return n.id === (d.target.id || d.target); });\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n }\n\n rebuildTopoGraph();\n buildTopoList();\n updateTopoLOD(1);\n\n svgEl.on('click', function() {\n topoSelectedId = null;\n d3.selectAll('.node-hex').classed('selected', false);\n buildTopoList(document.getElementById('topo-search').value);\n topoSidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Click a node to view details.</p>';\n });\n}\n\n// Init topology node list (non-D3 part)\nbuildTopoList();\n<\\/script>\n</body>\n</html>`;\n}\n\n// ── JGF Export ────────────────────────────────────────────────────────────────\n\nexport function exportJGF(nodes: NodeRow[], edges: EdgeRow[]): string {\n const jgf = {\n graph: {\n directed: true,\n type: 'cartography',\n label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(nodes.map(n => [n.id, {\n label: n.name,\n metadata: {\n type: n.type, layer: nodeLayer(n.type),\n confidence: n.confidence, discoveredVia: n.discoveredVia,\n discoveredAt: n.discoveredAt, tags: n.tags, metadata: n.metadata,\n },\n }])),\n edges: edges.map(e => ({\n source: e.sourceId, target: e.targetId,\n relation: e.relationship,\n metadata: { confidence: e.confidence, evidence: e.evidence },\n })),\n },\n };\n return JSON.stringify(jgf, null, 2);\n}\n\n// ── exportAll ─────────────────────────────────────────────────────────────────\n\nexport function exportAll(\n db: CartographyDB,\n sessionId: string,\n outputDir: string,\n formats: string[] = ['mermaid', 'json', 'yaml', 'html', 'map', 'discovery'],\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}\n","/**\n * Hex Grid Engine — flat-top axial coordinate system.\n * Reference: https://www.redblobgames.com/grids/hexagons/\n */\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface AxialCoord {\n q: number;\n r: number;\n}\n\nexport interface PixelCoord {\n x: number;\n y: number;\n}\n\n// ── Geometry ─────────────────────────────────────────────────────────────────\n\n/**\n * Convert axial hex coordinates to pixel coordinates (flat-top).\n */\nexport function hexToPixel(q: number, r: number, size: number): PixelCoord {\n const x = size * (3 / 2 * q);\n const y = size * (Math.sqrt(3) / 2 * q + Math.sqrt(3) * r);\n return { x, y };\n}\n\n/**\n * Convert pixel coordinates to nearest axial hex (flat-top).\n */\nexport function pixelToHex(x: number, y: number, size: number): AxialCoord {\n const q = (2 / 3 * x) / size;\n const r = (-1 / 3 * x + Math.sqrt(3) / 3 * y) / size;\n return hexRound(q, r);\n}\n\n/**\n * Round floating-point axial coordinates to the nearest hex.\n */\nexport function hexRound(q: number, r: number): AxialCoord {\n const s = -q - r;\n let rq = Math.round(q);\n let rr = Math.round(r);\n const rs = Math.round(s);\n const dq = Math.abs(rq - q);\n const dr = Math.abs(rr - r);\n const ds = Math.abs(rs - s);\n if (dq > dr && dq > ds) {\n rq = -rr - rs;\n } else if (dr > ds) {\n rr = -rq - rs;\n }\n // Normalize -0 to 0\n return { q: rq || 0, r: rr || 0 };\n}\n\n/**\n * Return the 6 pixel corners of a flat-top hexagon.\n */\nexport function hexCorners(cx: number, cy: number, size: number): PixelCoord[] {\n const corners: PixelCoord[] = [];\n for (let i = 0; i < 6; i++) {\n const angle = (Math.PI / 180) * (60 * i); // flat-top: start at 0°\n corners.push({\n x: cx + size * Math.cos(angle),\n y: cy + size * Math.sin(angle),\n });\n }\n return corners;\n}\n\n// ── Neighbors & Distance ─────────────────────────────────────────────────────\n\nconst HEX_DIRECTIONS: AxialCoord[] = [\n { q: 1, r: 0 }, { q: 1, r: -1 }, { q: 0, r: -1 },\n { q: -1, r: 0 }, { q: -1, r: 1 }, { q: 0, r: 1 },\n];\n\nexport function hexNeighbors(q: number, r: number): AxialCoord[] {\n return HEX_DIRECTIONS.map(d => ({ q: q + d.q, r: r + d.r }));\n}\n\nexport function hexDistance(a: AxialCoord, b: AxialCoord): number {\n return (Math.abs(a.q - b.q) + Math.abs(a.q + a.r - b.q - b.r) + Math.abs(a.r - b.r)) / 2;\n}\n\n/**\n * Generate all hex coordinates on a given ring around center.\n */\nexport function hexRing(center: AxialCoord, radius: number): AxialCoord[] {\n if (radius === 0) return [{ q: center.q, r: center.r }];\n const results: AxialCoord[] = [];\n let hex = {\n q: center.q + HEX_DIRECTIONS[4].q * radius,\n r: center.r + HEX_DIRECTIONS[4].r * radius,\n };\n for (let side = 0; side < 6; side++) {\n for (let step = 0; step < radius; step++) {\n results.push({ q: hex.q, r: hex.r });\n hex = {\n q: hex.q + HEX_DIRECTIONS[side].q,\n r: hex.r + HEX_DIRECTIONS[side].r,\n };\n }\n }\n return results;\n}\n\n/**\n * Generate a spiral sequence of hex positions (ring 0, ring 1, ring 2, …).\n * Returns exactly `count` positions.\n */\nexport function hexSpiral(center: AxialCoord, count: number): AxialCoord[] {\n const positions: AxialCoord[] = [];\n let ring = 0;\n while (positions.length < count) {\n const ringPositions = hexRing(center, ring);\n for (const pos of ringPositions) {\n if (positions.length >= count) break;\n positions.push(pos);\n }\n ring++;\n }\n return positions;\n}\n","/**\n * Domain-based clustering and hex grid positioning.\n * Groups data assets by domain, assigns organic hex positions via spiral fill,\n * computes cluster centroids and assigns colors from the domain palette.\n */\n\nimport { hexSpiral, hexToPixel, hexDistance, type AxialCoord } from './hex.js';\nimport type { DataAsset, Cluster } from './types.js';\nimport { DOMAIN_COLORS, DOMAIN_PALETTE } from './types.js';\n\n// ── Color Assignment ──────────────────────────────────────────────────────────\n\n/**\n * Assign a deterministic color from the palette to a domain name.\n * Uses the predefined DOMAIN_COLORS map first, then falls back to the palette.\n */\nexport function assignColor(domain: string, allDomains: string[]): string {\n if (DOMAIN_COLORS[domain]) return DOMAIN_COLORS[domain];\n const idx = allDomains.indexOf(domain);\n return DOMAIN_PALETTE[idx % DOMAIN_PALETTE.length];\n}\n\n/**\n * Assign colors to all domains in the dataset.\n */\nexport function assignColors(domains: string[]): Record<string, string> {\n const result: Record<string, string> = {};\n for (const d of domains) {\n result[d] = assignColor(d, domains);\n }\n return result;\n}\n\n/**\n * Generate a slightly lighter shade of a hex color string.\n */\nexport function shadeVariant(hex: string, amount: number): string {\n const num = parseInt(hex.replace('#', ''), 16);\n const r = Math.min(255, (num >> 16) + amount);\n const g = Math.min(255, ((num >> 8) & 0xff) + amount);\n const b = Math.min(255, (num & 0xff) + amount);\n return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;\n}\n\n// ── Grouping ──────────────────────────────────────────────────────────────────\n\n/**\n * Group assets by their `domain` field.\n */\nexport function groupByDomain(assets: DataAsset[]): Map<string, DataAsset[]> {\n const map = new Map<string, DataAsset[]>();\n for (const a of assets) {\n const d = a.domain || 'Other';\n if (!map.has(d)) map.set(d, []);\n map.get(d)!.push(a);\n }\n return map;\n}\n\n// ── Cluster Layout ────────────────────────────────────────────────────────────\n\nconst CLUSTER_GAP = 3; // min hex distance between cluster borders\n\n/**\n * Arrange domain clusters on the hex grid without overlap.\n * Places largest clusters first at the origin, subsequent clusters spiral outward.\n */\nexport function layoutClusters(\n groups: Map<string, DataAsset[]>,\n hexSize: number,\n): { clusters: Cluster[]; assets: DataAsset[] } {\n const allDomains = Array.from(groups.keys());\n const colors = assignColors(allDomains);\n\n // Sort domains by size descending (largest first → center)\n const sorted = Array.from(groups.entries())\n .sort((a, b) => b[1].length - a[1].length);\n\n const occupied = new Set<string>();\n const key = (q: number, r: number) => `${q},${r}`;\n\n const clusters: Cluster[] = [];\n const allAssets: DataAsset[] = [];\n\n for (const [domain, domainAssets] of sorted) {\n // Find a free origin for this cluster\n const origin = clusters.length === 0\n ? { q: 0, r: 0 }\n : findFreeOrigin(occupied, domainAssets.length, CLUSTER_GAP);\n\n // Pack assets in a spiral around the origin\n const positions = hexSpiral(origin, domainAssets.length);\n\n const assetIds: string[] = [];\n for (let i = 0; i < domainAssets.length; i++) {\n const asset = domainAssets[i];\n asset.position = positions[i];\n assetIds.push(asset.id);\n occupied.add(key(positions[i].q, positions[i].r));\n allAssets.push(asset);\n }\n\n const centroid = computeCentroid(positions, hexSize);\n\n clusters.push({\n id: `cluster:${domain}`,\n label: domain,\n domain,\n color: colors[domain],\n assetIds,\n centroid,\n });\n }\n\n return { clusters, assets: allAssets };\n}\n\n/**\n * Find a cluster origin that doesn't overlap any occupied hexes.\n */\nfunction findFreeOrigin(\n occupied: Set<string>,\n count: number,\n gap: number,\n): AxialCoord {\n const key = (q: number, r: number) => `${q},${r}`;\n\n // Search in expanding rings around the global origin\n for (let searchRadius = 1; searchRadius < 100; searchRadius++) {\n const candidates = hexSpiral({ q: 0, r: 0 }, 1 + 6 * searchRadius * (searchRadius + 1) / 2);\n\n for (const candidate of candidates) {\n const testPositions = hexSpiral(candidate, count);\n let fits = true;\n\n for (const tp of testPositions) {\n // Check that the cell itself and its gap neighbors are free\n if (occupied.has(key(tp.q, tp.r))) { fits = false; break; }\n\n for (const oKey of occupied) {\n const [oq, or] = oKey.split(',').map(Number);\n if (hexDistance(tp, { q: oq, r: or }) < gap) {\n fits = false;\n break;\n }\n }\n if (!fits) break;\n }\n\n if (fits) return candidate;\n }\n }\n\n // Fallback\n return { q: occupied.size * 5, r: 0 };\n}\n\n// ── Centroid ──────────────────────────────────────────────────────────────────\n\nexport function computeCentroid(positions: AxialCoord[], hexSize: number): { x: number; y: number } {\n if (positions.length === 0) return { x: 0, y: 0 };\n let sx = 0, sy = 0;\n for (const { q, r } of positions) {\n const { x, y } = hexToPixel(q, r, hexSize);\n sx += x;\n sy += y;\n }\n return { x: sx / positions.length, y: sy / positions.length };\n}\n\n// ── Cluster Bounds ────────────────────────────────────────────────────────────\n\nexport function computeClusterBounds(\n assets: DataAsset[],\n hexSize: number,\n): { minX: number; minY: number; maxX: number; maxY: number } {\n if (assets.length === 0) return { minX: 0, minY: 0, maxX: 0, maxY: 0 };\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const a of assets) {\n const { x, y } = hexToPixel(a.position.q, a.position.r, hexSize);\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n }\n return { minX, minY, maxX, maxY };\n}\n","/**\n * Node-zu-Asset Mapping.\n * Converts existing DiscoveryNode/Edge data into the CartographyMap data model.\n */\n\nimport type { NodeRow, EdgeRow, DataAsset, Connection, CartographyMapData } from './types.js';\nimport { layoutClusters, groupByDomain } from './cluster.js';\n\n// ── Domain Mapping ───────────────────────────────────────────────────────────\n\nconst TYPE_TO_DOMAIN: Record<string, string> = {\n database_server: 'Data Layer',\n database: 'Data Layer',\n table: 'Data Layer',\n cache_server: 'Data Layer',\n web_service: 'Web / API',\n api_endpoint: 'Web / API',\n message_broker: 'Messaging',\n queue: 'Messaging',\n topic: 'Messaging',\n host: 'Infrastructure',\n container: 'Infrastructure',\n pod: 'Infrastructure',\n k8s_cluster: 'Infrastructure',\n config_file: 'Infrastructure',\n saas_tool: 'SaaS Tools',\n};\n\n/**\n * Determine the domain for a node.\n * Priority: explicit node.domain > metadata.category > tag-based > type-based > \"Other\"\n */\nfunction resolveDomain(node: NodeRow): string {\n // 1. Explicit domain field\n if (node.domain) return node.domain;\n\n // 2. Metadata category\n const meta = node.metadata as Record<string, unknown>;\n if (typeof meta['category'] === 'string' && meta['category'].length > 0) {\n return meta['category'];\n }\n\n // 3. Tags — use first tag that looks like a domain\n for (const tag of node.tags ?? []) {\n if (tag.length > 2 && tag[0] === tag[0].toUpperCase()) {\n return tag;\n }\n }\n\n // 4. Type-based mapping\n return TYPE_TO_DOMAIN[node.type] ?? 'Other';\n}\n\n// ── Conversion ───────────────────────────────────────────────────────────────\n\n/**\n * Convert NodeRow[] to DataAsset[].\n */\nexport function nodesToAssets(nodes: NodeRow[]): DataAsset[] {\n return nodes.map(n => ({\n id: n.id,\n name: n.name,\n domain: resolveDomain(n),\n subDomain: n.subDomain,\n qualityScore: n.qualityScore ?? Math.round(n.confidence * 100),\n metadata: n.metadata ?? {},\n position: { q: 0, r: 0 }, // will be assigned by layoutClusters\n }));\n}\n\n/**\n * Convert EdgeRow[] to Connection[].\n */\nexport function edgesToConnections(edges: EdgeRow[]): Connection[] {\n return edges.map(e => ({\n id: e.id,\n sourceAssetId: e.sourceId,\n targetAssetId: e.targetId,\n type: e.relationship,\n }));\n}\n\n// ── Full Pipeline ─────────────────────────────────────────────────────────────\n\nconst HEX_SIZE = 24;\n\n/**\n * Build a complete CartographyMapData from raw nodes and edges.\n */\nexport function buildMapData(\n nodes: NodeRow[],\n edges: EdgeRow[],\n options?: { theme?: 'light' | 'dark' },\n): CartographyMapData {\n const rawAssets = nodesToAssets(nodes);\n const connections = edgesToConnections(edges);\n\n if (rawAssets.length === 0) {\n return {\n assets: [],\n clusters: [],\n connections,\n meta: { exportedAt: new Date().toISOString(), theme: options?.theme ?? 'light' },\n };\n }\n\n const groups = groupByDomain(rawAssets);\n const { clusters, assets } = layoutClusters(groups, HEX_SIZE);\n\n return {\n assets,\n clusters,\n connections,\n meta: { exportedAt: new Date().toISOString(), theme: options?.theme ?? 'light' },\n };\n}\n","import { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nfunction isOAuthLoggedIn(): boolean {\n // Claude CLI speichert OAuth-Tokens in ~/.claude/.credentials.json\n const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';\n const credFile = join(home, '.claude', '.credentials.json');\n if (!existsSync(credFile)) return false;\n try {\n const creds = JSON.parse(readFileSync(credFile, 'utf8')) as Record<string, unknown>;\n const oauth = creds['claudeAiOauth'] as Record<string, unknown> | undefined;\n return typeof oauth?.['accessToken'] === 'string' && oauth['accessToken'].length > 0;\n } catch {\n return false;\n }\n}\n\nexport function checkPrerequisites(): void {\n // Claude CLI vorhanden?\n try {\n execSync('claude --version', { stdio: 'pipe' });\n } catch {\n process.stderr.write(\n '\\n❌ Claude CLI nicht gefunden.\\n' +\n ' Datasynx Cartography braucht die Claude CLI als Runtime-Dependency.\\n\\n' +\n ' Installieren:\\n' +\n ' npm install -g @anthropic-ai/claude-code\\n' +\n ' # oder\\n' +\n ' curl -fsSL https://claude.ai/install.sh | bash\\n\\n' +\n ' Danach: claude login\\n\\n'\n );\n process.exitCode = 1;\n throw new Error('Claude CLI not found');\n }\n\n // Auth prüfen: API Key ODER OAuth-Login (claude.ai Subscription)\n const hasApiKey = Boolean(process.env.ANTHROPIC_API_KEY);\n const hasOAuth = isOAuthLoggedIn();\n\n if (!hasApiKey && !hasOAuth) {\n process.stderr.write(\n '⚠ Keine Authentifizierung gefunden. Bitte eine der folgenden Optionen:\\n\\n' +\n ' Option A — claude.ai Subscription (empfohlen):\\n' +\n ' claude login\\n\\n' +\n ' Option B — API Key:\\n' +\n ' export ANTHROPIC_API_KEY=sk-ant-...\\n\\n'\n );\n } else if (hasOAuth && !hasApiKey) {\n process.stderr.write('✓ Eingeloggt via claude login (Subscription)\\n');\n }\n}\n","/**\n * Structured logging for enterprise observability.\n * Outputs JSON to stderr for compatibility with ELK, Datadog, Splunk, CloudWatch.\n */\n\nexport type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';\n\nexport interface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n context?: Record<string, unknown>;\n}\n\nlet verboseMode = false;\n\nexport function setVerbose(v: boolean): void {\n verboseMode = v;\n}\n\nexport function log(level: LogLevel, message: string, context?: Record<string, unknown>): void {\n if (level === 'DEBUG' && !verboseMode) return;\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n ...(context && Object.keys(context).length > 0 ? { context } : {}),\n };\n process.stderr.write(JSON.stringify(entry) + '\\n');\n}\n\nexport function logDebug(message: string, context?: Record<string, unknown>): void {\n log('DEBUG', message, context);\n}\n\nexport function logInfo(message: string, context?: Record<string, unknown>): void {\n log('INFO', message, context);\n}\n\nexport function logWarn(message: string, context?: Record<string, unknown>): void {\n log('WARN', message, context);\n}\n\nexport function logError(message: string, context?: Record<string, unknown>): void {\n log('ERROR', message, context);\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,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;AA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","names":["z","z","z","existsSync","join","join","existsSync","Database","IS_LINUX","z","mkdirSync","join","HEX_SIZE","mkdirSync","join","execSync","existsSync","readFileSync","join"]}
|
|
1
|
+
{"version":3,"sources":["../src/db.ts","../src/types.ts","../src/mcp/server.ts","../src/mcp/transports.ts","../src/scanners/types.ts","../src/bookmarks.ts","../src/platform.ts","../src/allowlist.ts","../src/logger.ts","../src/scanners/bookmarks.ts","../src/scanners/installed-apps.ts","../src/scanners/ports.ts","../src/scanners/registry.ts","../src/discovery/local.ts","../src/semantic/hash.ts","../src/semantic/embeddings.ts","../src/semantic/store.ts","../src/semantic/search.ts","../src/tools.ts","../src/safety.ts","../src/agent.ts","../src/exporter.ts","../src/hex.ts","../src/cluster.ts","../src/mapper.ts","../src/preflight.ts"],"sourcesContent":["import Database from 'better-sqlite3';\nimport { mkdirSync } from 'node:fs';\nimport { dirname } from 'node:path';\nimport { z } from 'zod';\nimport { NODE_TYPES, EDGE_RELATIONSHIPS } from './types.js';\nimport type {\n CartographyConfig, DiscoveryNode, DiscoveryEdge,\n NodeRow, EdgeRow, SessionRow, Connection,\n} from './types.js';\n\n// ── Row validation schemas ──────────────────────────────────────────────────\n\nconst SessionRowSchema = z.object({\n id: z.string(),\n mode: z.literal('discover'),\n started_at: z.string(),\n completed_at: z.string().nullable().optional(),\n config: z.string(),\n});\n\nconst NodeRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discovered_via: z.string().nullable().optional(),\n discovered_at: z.string(),\n path_id: z.string().nullable().optional(),\n depth: z.number().default(0),\n confidence: z.number().default(0.5),\n metadata: z.string().default('{}'),\n tags: z.string().default('[]'),\n domain: z.string().nullable().optional(),\n sub_domain: z.string().nullable().optional(),\n quality_score: z.number().nullable().optional(),\n});\n\nconst EdgeRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n source_id: z.string(),\n target_id: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string().nullable().optional(),\n confidence: z.number().default(0.5),\n discovered_at: z.string(),\n});\n\nconst EventRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n task_id: z.string().nullable().optional(),\n timestamp: z.string(),\n event_type: z.string(),\n process: z.string(),\n pid: z.number(),\n target: z.string().nullable().optional(),\n target_type: z.string().nullable().optional(),\n port: z.number().nullable().optional(),\n duration_ms: z.number().nullable().optional(),\n});\n\nconst TaskRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n description: z.string().nullable().optional(),\n started_at: z.string(),\n completed_at: z.string().nullable().optional(),\n steps: z.string().default('[]'),\n involved_services: z.string().default('[]'),\n status: z.enum(['active', 'completed', 'cancelled']),\n});\n\nconst WorkflowRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n name: z.string().nullable().optional(),\n pattern: z.string(),\n task_ids: z.string().default('[]'),\n occurrences: z.number().default(1),\n first_seen: z.string(),\n last_seen: z.string(),\n avg_duration_ms: z.number().nullable().optional(),\n involved_services: z.string().default('[]'),\n});\n\nconst ConnectionRowSchema = z.object({\n id: z.string(),\n session_id: z.string(),\n source_asset_id: z.string(),\n target_asset_id: z.string(),\n type: z.string().nullable().optional(),\n created_at: z.string(),\n});\n\nexport interface ConnectionRow extends Connection {\n sessionId: string;\n createdAt: string;\n}\n\n/** Aggregate, low-token index of a topology — used for progressive disclosure. */\nexport interface GraphSummary {\n sessionId: string;\n totals: { nodes: number; edges: number };\n nodesByType: Record<string, number>;\n nodesByDomain: Record<string, number>;\n edgesByRelationship: Record<string, number>;\n topConnected: Array<{ id: string; name: string; type: string; degree: number }>;\n}\n\n/** Result of a recursive dependency traversal. */\nexport interface TraversalResult {\n root?: NodeRow;\n direction: 'downstream' | 'upstream' | 'both';\n maxDepth: number;\n nodes: Array<NodeRow & { depth: number }>;\n edges: EdgeRow[];\n}\n\n// ── DB Row Types ──\n\nexport interface EventRow {\n id: string;\n sessionId: string;\n taskId?: string;\n timestamp: string;\n eventType: string;\n process: string;\n pid: number;\n target?: string;\n targetType?: string;\n port?: number;\n durationMs?: number;\n}\n\nexport interface TaskRow {\n id: string;\n sessionId: string;\n description?: string;\n startedAt: string;\n completedAt?: string;\n steps: string;\n involvedServices: string;\n status: 'active' | 'completed' | 'cancelled';\n}\n\nexport interface WorkflowRow {\n id: string;\n sessionId: string;\n name?: string;\n pattern: string;\n taskIds: string;\n occurrences: number;\n firstSeen: string;\n lastSeen: string;\n avgDurationMs: number;\n involvedServices: string;\n}\n\nconst SCHEMA = `\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\nPRAGMA busy_timeout = 5000;\n\nCREATE TABLE IF NOT EXISTS sessions (\n id TEXT PRIMARY KEY,\n mode TEXT NOT NULL CHECK (mode IN ('discover')),\n started_at TEXT NOT NULL,\n completed_at TEXT,\n config TEXT NOT NULL DEFAULT '{}'\n);\n\nCREATE TABLE IF NOT EXISTS nodes (\n id TEXT NOT NULL,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n discovered_via TEXT,\n discovered_at TEXT NOT NULL,\n path_id TEXT,\n depth INTEGER DEFAULT 0,\n confidence REAL DEFAULT 0.5,\n metadata TEXT NOT NULL DEFAULT '{}',\n tags TEXT NOT NULL DEFAULT '[]',\n domain TEXT,\n sub_domain TEXT,\n quality_score REAL,\n PRIMARY KEY (id, session_id)\n);\n\nCREATE TABLE IF NOT EXISTS connections (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_asset_id TEXT NOT NULL,\n target_asset_id TEXT NOT NULL,\n type TEXT,\n created_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS edges (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_id TEXT NOT NULL,\n target_id TEXT NOT NULL,\n relationship TEXT NOT NULL,\n evidence TEXT,\n confidence REAL DEFAULT 0.5,\n discovered_at TEXT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS activity_events (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n task_id TEXT,\n timestamp TEXT NOT NULL,\n event_type TEXT NOT NULL,\n process TEXT NOT NULL,\n pid INTEGER NOT NULL,\n target TEXT,\n target_type TEXT,\n port INTEGER,\n duration_ms INTEGER\n);\n\nCREATE TABLE IF NOT EXISTS tasks (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n description TEXT,\n started_at TEXT NOT NULL,\n completed_at TEXT,\n steps TEXT NOT NULL DEFAULT '[]',\n involved_services TEXT NOT NULL DEFAULT '[]',\n status TEXT DEFAULT 'active' CHECK (status IN ('active','completed','cancelled'))\n);\n\nCREATE TABLE IF NOT EXISTS workflows (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n name TEXT,\n pattern TEXT NOT NULL,\n task_ids TEXT NOT NULL DEFAULT '[]',\n occurrences INTEGER DEFAULT 1,\n first_seen TEXT NOT NULL,\n last_seen TEXT NOT NULL,\n avg_duration_ms INTEGER,\n involved_services TEXT NOT NULL DEFAULT '[]'\n);\n\nCREATE TABLE IF NOT EXISTS node_approvals (\n pattern TEXT PRIMARY KEY,\n action TEXT NOT NULL CHECK (action IN ('save','ignore','auto')),\n created_at TEXT NOT NULL\n);\n\nCREATE INDEX IF NOT EXISTS idx_nodes_session ON nodes(session_id);\nCREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(session_id, type);\nCREATE INDEX IF NOT EXISTS idx_edges_session ON edges(session_id);\nCREATE INDEX IF NOT EXISTS idx_edges_source ON edges(session_id, source_id);\nCREATE INDEX IF NOT EXISTS idx_edges_target ON edges(session_id, target_id);\nCREATE INDEX IF NOT EXISTS idx_events_session ON activity_events(session_id);\nCREATE INDEX IF NOT EXISTS idx_events_task ON activity_events(task_id);\nCREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);\nCREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);\nCREATE INDEX IF NOT EXISTS idx_connections_lookup ON connections(session_id, source_asset_id, target_asset_id);\n`;\n\nexport class CartographyDB {\n private db: Database.Database;\n\n constructor(dbPath: string) {\n mkdirSync(dirname(dbPath), { recursive: true });\n this.db = new Database(dbPath);\n this.db.pragma('journal_mode = WAL');\n this.db.pragma('foreign_keys = ON');\n this.db.pragma('busy_timeout = 5000');\n this.migrate();\n }\n\n private migrate(): void {\n const version = (this.db.pragma('user_version', { simple: true }) as number);\n if (version === 0) {\n this.db.exec(SCHEMA);\n this.db.pragma('user_version = 4');\n return;\n } else if (version === 1) {\n // v1 → v2: add hex map columns to nodes + connections table\n const cols = (this.db.prepare(\"PRAGMA table_info(nodes)\").all() as Array<{ name: string }>).map(c => c.name);\n if (!cols.includes('domain')) this.db.exec('ALTER TABLE nodes ADD COLUMN domain TEXT');\n if (!cols.includes('sub_domain')) this.db.exec('ALTER TABLE nodes ADD COLUMN sub_domain TEXT');\n if (!cols.includes('quality_score')) this.db.exec('ALTER TABLE nodes ADD COLUMN quality_score REAL');\n this.db.exec(`\n CREATE TABLE IF NOT EXISTS connections (\n id TEXT PRIMARY KEY,\n session_id TEXT NOT NULL REFERENCES sessions(id),\n source_asset_id TEXT NOT NULL,\n target_asset_id TEXT NOT NULL,\n type TEXT,\n created_at TEXT NOT NULL\n );\n CREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);\n CREATE INDEX IF NOT EXISTS idx_connections_lookup ON connections(session_id, source_asset_id, target_asset_id);\n `);\n this.db.pragma('user_version = 3');\n }\n if (version === 2) {\n // v2 → v3: add composite index for connection upsert lookups\n this.db.exec('CREATE INDEX IF NOT EXISTS idx_connections_lookup ON connections(session_id, source_asset_id, target_asset_id)');\n this.db.pragma('user_version = 3');\n }\n // v3 → v4: add graph-traversal indexes (idempotent for any pre-v4 DB)\n const current = this.db.pragma('user_version', { simple: true }) as number;\n if (current < 4) {\n this.db.exec(`\n CREATE INDEX IF NOT EXISTS idx_nodes_type ON nodes(session_id, type);\n CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(session_id, source_id);\n CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(session_id, target_id);\n `);\n this.db.pragma('user_version = 4');\n }\n }\n\n close(): void {\n this.db.pragma('optimize');\n this.db.close();\n }\n\n /**\n * Advanced: the underlying better-sqlite3 connection. Used by the optional\n * semantic-search layer to load the `sqlite-vec` extension and manage its\n * virtual table. Prefer the typed methods above for everything else.\n */\n rawConnection(): Database.Database {\n return this.db;\n }\n\n // ── Sessions ────────────────────────────\n\n createSession(mode: 'discover', config: CartographyConfig): string {\n const id = crypto.randomUUID();\n this.db.prepare(\n 'INSERT INTO sessions (id, mode, started_at, config) VALUES (?, ?, ?, ?)'\n ).run(id, mode, new Date().toISOString(), JSON.stringify(config));\n return id;\n }\n\n endSession(id: string): void {\n this.db.prepare('UPDATE sessions SET completed_at = ? WHERE id = ?')\n .run(new Date().toISOString(), id);\n }\n\n getSession(id: string): SessionRow | undefined {\n const row = this.db.prepare('SELECT * FROM sessions WHERE id = ?').get(id) as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n getLatestSession(mode?: string): SessionRow | undefined {\n const row = mode\n ? this.db.prepare('SELECT * FROM sessions WHERE mode = ? ORDER BY rowid DESC LIMIT 1').get(mode) as Record<string, unknown> | undefined\n : this.db.prepare('SELECT * FROM sessions ORDER BY rowid DESC LIMIT 1').get() as Record<string, unknown> | undefined;\n return row ? this.mapSession(row) : undefined;\n }\n\n getSessions(): SessionRow[] {\n const rows = this.db.prepare('SELECT * FROM sessions ORDER BY rowid DESC').all() as Record<string, unknown>[];\n return rows.map(r => this.mapSession(r));\n }\n\n private mapSession(r: Record<string, unknown>): SessionRow {\n const v = SessionRowSchema.parse(r);\n return {\n id: v.id,\n mode: v.mode,\n startedAt: v.started_at,\n completedAt: v.completed_at ?? undefined,\n config: v.config,\n };\n }\n\n // ── Nodes ───────────────────────────────\n\n upsertNode(sessionId: string, node: DiscoveryNode, depth = 0): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO nodes\n (id, session_id, type, name, discovered_via, discovered_at, depth, confidence, metadata, tags,\n domain, sub_domain, quality_score)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n node.id, sessionId, node.type, node.name, node.discoveredVia,\n new Date().toISOString(), depth, node.confidence,\n JSON.stringify(node.metadata ?? {}),\n JSON.stringify(node.tags ?? []),\n node.domain ?? null,\n node.subDomain ?? null,\n node.qualityScore ?? null,\n );\n }\n\n getNodes(sessionId: string, opts?: { limit?: number; offset?: number }): NodeRow[] {\n let sql = 'SELECT * FROM nodes WHERE session_id = ?';\n if (opts?.limit) {\n sql += ` LIMIT ${opts.limit}`;\n if (opts.offset) sql += ` OFFSET ${opts.offset}`;\n }\n const rows = this.db.prepare(sql).all(sessionId) as Record<string, unknown>[];\n return rows.map(r => this.mapNode(r));\n }\n\n getNodeCount(sessionId: string): number {\n const row = this.db.prepare('SELECT COUNT(*) as cnt FROM nodes WHERE session_id = ?').get(sessionId) as { cnt: number };\n return row.cnt;\n }\n\n private mapNode(r: Record<string, unknown>): NodeRow {\n const v = NodeRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n type: v.type,\n name: v.name,\n discoveredVia: v.discovered_via ?? '',\n discoveredAt: v.discovered_at,\n depth: v.depth,\n confidence: v.confidence,\n metadata: JSON.parse(v.metadata) as Record<string, unknown>,\n tags: JSON.parse(v.tags) as string[],\n pathId: v.path_id ?? undefined,\n domain: v.domain ?? undefined,\n subDomain: v.sub_domain ?? undefined,\n qualityScore: v.quality_score ?? undefined,\n };\n }\n\n deleteNode(sessionId: string, nodeId: string): void {\n this.db.prepare('DELETE FROM nodes WHERE session_id = ? AND id = ?').run(sessionId, nodeId);\n // Remove orphaned edges\n this.db.prepare(\n 'DELETE FROM edges WHERE session_id = ? AND (source_id = ? OR target_id = ?)'\n ).run(sessionId, nodeId, nodeId);\n }\n\n // ── Edges ───────────────────────────────\n\n insertEdge(sessionId: string, edge: DiscoveryEdge): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT OR IGNORE INTO edges\n (id, session_id, source_id, target_id, relationship, evidence, confidence, discovered_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, edge.sourceId, edge.targetId,\n edge.relationship, edge.evidence, edge.confidence,\n new Date().toISOString(),\n );\n }\n\n getEdges(sessionId: string, opts?: { limit?: number; offset?: number }): EdgeRow[] {\n let sql = 'SELECT * FROM edges WHERE session_id = ?';\n if (opts?.limit) {\n sql += ` LIMIT ${opts.limit}`;\n if (opts.offset) sql += ` OFFSET ${opts.offset}`;\n }\n const rows = this.db.prepare(sql).all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = EdgeRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n sourceId: v.source_id,\n targetId: v.target_id,\n relationship: v.relationship,\n evidence: v.evidence ?? '',\n confidence: v.confidence,\n discoveredAt: v.discovered_at,\n };\n });\n }\n\n // ── Events ──────────────────────────────\n\n insertEvent(sessionId: string, event: Pick<EventRow, 'eventType' | 'process' | 'pid' | 'target' | 'targetType' | 'port'>, taskId?: string): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO activity_events\n (id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, taskId ?? null, new Date().toISOString(),\n event.eventType, event.process, event.pid,\n event.target ?? null, event.targetType ?? null, event.port ?? null,\n );\n }\n\n getEvents(sessionId: string, since?: string): EventRow[] {\n const rows = since\n ? this.db.prepare('SELECT * FROM activity_events WHERE session_id = ? AND timestamp > ? ORDER BY timestamp').all(sessionId, since) as Record<string, unknown>[]\n : this.db.prepare('SELECT * FROM activity_events WHERE session_id = ? ORDER BY timestamp').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = EventRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n taskId: v.task_id ?? undefined,\n timestamp: v.timestamp,\n eventType: v.event_type,\n process: v.process,\n pid: v.pid,\n target: v.target ?? undefined,\n targetType: v.target_type ?? undefined,\n port: v.port ?? undefined,\n durationMs: v.duration_ms ?? undefined,\n };\n });\n }\n\n // ── Tasks ───────────────────────────────\n\n startTask(sessionId: string, description?: string): string {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO tasks (id, session_id, description, started_at, steps, involved_services, status)\n VALUES (?, ?, ?, ?, '[]', '[]', 'active')\n `).run(id, sessionId, description ?? null, new Date().toISOString());\n return id;\n }\n\n endCurrentTask(sessionId: string): void {\n this.db.prepare(`\n UPDATE tasks SET status = 'completed', completed_at = ?\n WHERE session_id = ? AND status = 'active'\n `).run(new Date().toISOString(), sessionId);\n }\n\n updateTaskDescription(sessionId: string, description: string): void {\n this.db.prepare(`\n UPDATE tasks SET description = ?\n WHERE session_id = ? AND status = 'active'\n `).run(description, sessionId);\n }\n\n getActiveTask(sessionId: string): TaskRow | undefined {\n const row = this.db.prepare(\n \"SELECT * FROM tasks WHERE session_id = ? AND status = 'active' LIMIT 1\"\n ).get(sessionId) as Record<string, unknown> | undefined;\n return row ? this.mapTask(row) : undefined;\n }\n\n getTasks(sessionId: string): TaskRow[] {\n const rows = this.db.prepare('SELECT * FROM tasks WHERE session_id = ? ORDER BY started_at').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => this.mapTask(r));\n }\n\n private mapTask(r: Record<string, unknown>): TaskRow {\n const v = TaskRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n description: v.description ?? undefined,\n startedAt: v.started_at,\n completedAt: v.completed_at ?? undefined,\n steps: v.steps,\n involvedServices: v.involved_services,\n status: v.status,\n };\n }\n\n // ── Workflows ───────────────────────────\n\n insertWorkflow(sessionId: string, data: Omit<WorkflowRow, 'id'>): void {\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO workflows\n (id, session_id, name, pattern, task_ids, occurrences,\n first_seen, last_seen, avg_duration_ms, involved_services)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `).run(\n id, sessionId, data.name ?? null, data.pattern,\n data.taskIds, data.occurrences,\n data.firstSeen, data.lastSeen, data.avgDurationMs,\n data.involvedServices,\n );\n }\n\n getWorkflows(sessionId: string): WorkflowRow[] {\n const rows = this.db.prepare('SELECT * FROM workflows WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = WorkflowRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n name: v.name ?? undefined,\n pattern: v.pattern,\n taskIds: v.task_ids,\n occurrences: v.occurrences,\n firstSeen: v.first_seen,\n lastSeen: v.last_seen,\n avgDurationMs: v.avg_duration_ms ?? 0,\n involvedServices: v.involved_services,\n };\n });\n }\n\n // ── Connections (user-created hex map links) ─────────────────────────────\n\n upsertConnection(sessionId: string, conn: Omit<Connection, 'id'>): string {\n // Idempotent: same source+target+type = same connection\n const existing = this.db.prepare(\n 'SELECT id FROM connections WHERE session_id = ? AND source_asset_id = ? AND target_asset_id = ?'\n ).get(sessionId, conn.sourceAssetId, conn.targetAssetId) as { id: string } | undefined;\n if (existing) return existing.id;\n const id = crypto.randomUUID();\n this.db.prepare(`\n INSERT INTO connections (id, session_id, source_asset_id, target_asset_id, type, created_at)\n VALUES (?, ?, ?, ?, ?, ?)\n `).run(id, sessionId, conn.sourceAssetId, conn.targetAssetId, conn.type ?? null, new Date().toISOString());\n return id;\n }\n\n getConnections(sessionId: string): ConnectionRow[] {\n const rows = this.db.prepare('SELECT * FROM connections WHERE session_id = ?').all(sessionId) as Record<string, unknown>[];\n return rows.map(r => {\n const v = ConnectionRowSchema.parse(r);\n return {\n id: v.id,\n sessionId: v.session_id,\n sourceAssetId: v.source_asset_id,\n targetAssetId: v.target_asset_id,\n type: v.type ?? undefined,\n createdAt: v.created_at,\n };\n });\n }\n\n deleteConnection(sessionId: string, connectionId: string): void {\n this.db.prepare('DELETE FROM connections WHERE session_id = ? AND id = ?').run(sessionId, connectionId);\n }\n\n // ── Approvals ───────────────────────────\n\n setApproval(pattern: string, action: 'save' | 'ignore' | 'auto'): void {\n this.db.prepare(`\n INSERT OR REPLACE INTO node_approvals (pattern, action, created_at) VALUES (?, ?, ?)\n `).run(pattern, action, new Date().toISOString());\n }\n\n getApproval(pattern: string): string | undefined {\n const row = this.db.prepare('SELECT action FROM node_approvals WHERE pattern = ?').get(pattern) as { action: string } | undefined;\n return row?.action;\n }\n\n // ── Pruning ──────────────────────────────\n\n /**\n * Delete a session and all its associated data (nodes, edges, events, tasks, workflows, connections).\n */\n deleteSession(sessionId: string): void {\n this.db.prepare('DELETE FROM connections WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM workflows WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM activity_events WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM tasks WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM edges WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM nodes WHERE session_id = ?').run(sessionId);\n this.db.prepare('DELETE FROM sessions WHERE id = ?').run(sessionId);\n }\n\n /**\n * Prune sessions older than the given ISO date string. Returns count of deleted sessions.\n */\n pruneSessions(olderThan: string): number {\n const rows = this.db.prepare(\n 'SELECT id FROM sessions WHERE started_at < ?'\n ).all(olderThan) as { id: string }[];\n for (const row of rows) {\n this.deleteSession(row.id);\n }\n return rows.length;\n }\n\n // ── Graph queries (read-only context layer) ─────────────────────────────────\n\n /** Fetch a single node by id within a session. */\n getNode(sessionId: string, nodeId: string): NodeRow | undefined {\n const row = this.db.prepare('SELECT * FROM nodes WHERE session_id = ? AND id = ?')\n .get(sessionId, nodeId) as Record<string, unknown> | undefined;\n return row ? this.mapNode(row) : undefined;\n }\n\n /** Batch-fetch nodes by id, keyed for O(1) lookup. Chunked to stay under SQLite's bind-variable limit. */\n getNodesByIds(sessionId: string, ids: readonly string[]): Map<string, NodeRow> {\n const out = new Map<string, NodeRow>();\n for (let i = 0; i < ids.length; i += 900) {\n const chunk = ids.slice(i, i + 900);\n const placeholders = chunk.map(() => '?').join(',');\n const rows = this.db.prepare(\n `SELECT * FROM nodes WHERE session_id = ? AND id IN (${placeholders})`,\n ).all(sessionId, ...chunk) as Record<string, unknown>[];\n for (const r of rows) { const n = this.mapNode(r); out.set(n.id, n); }\n }\n return out;\n }\n\n /** Fetch all nodes of one or more types. */\n getNodesByType(sessionId: string, types: readonly string[]): NodeRow[] {\n if (types.length === 0) return [];\n const placeholders = types.map(() => '?').join(',');\n const rows = this.db.prepare(\n `SELECT * FROM nodes WHERE session_id = ? AND type IN (${placeholders})`,\n ).all(sessionId, ...types) as Record<string, unknown>[];\n return rows.map(r => this.mapNode(r));\n }\n\n /**\n * Lexical search over node id, name, domain, sub-domain and tags.\n * Case-insensitive substring match — the deterministic fallback for semantic search.\n */\n searchNodes(sessionId: string, query: string, opts?: { types?: readonly string[]; limit?: number }): NodeRow[] {\n const q = `%${query.trim().toLowerCase()}%`;\n const params: unknown[] = [sessionId, q, q, q, q, q];\n let sql = `\n SELECT * FROM nodes\n WHERE session_id = ?\n AND (\n lower(id) LIKE ? OR lower(name) LIKE ?\n OR lower(COALESCE(domain, '')) LIKE ?\n OR lower(COALESCE(sub_domain, '')) LIKE ?\n OR lower(tags) LIKE ?\n )`;\n if (opts?.types && opts.types.length > 0) {\n sql += ` AND type IN (${opts.types.map(() => '?').join(',')})`;\n params.push(...opts.types);\n }\n sql += ' ORDER BY confidence DESC';\n if (opts?.limit) sql += ` LIMIT ${Math.max(1, Math.floor(opts.limit))}`;\n const rows = this.db.prepare(sql).all(...params) as Record<string, unknown>[];\n return rows.map(r => this.mapNode(r));\n }\n\n /**\n * Traverse the dependency graph from a node using a recursive CTE with a\n * path-based cycle guard. `downstream` follows source→target (what the node\n * depends on / points to); `upstream` follows target→source (what depends on it).\n */\n getDependencies(\n sessionId: string,\n nodeId: string,\n opts: { direction?: 'downstream' | 'upstream' | 'both'; maxDepth?: number } = {},\n ): TraversalResult {\n const direction = opts.direction ?? 'downstream';\n const maxDepth = Math.max(1, Math.min(opts.maxDepth ?? 8, 64));\n const root = this.getNode(sessionId, nodeId);\n\n const depthById = new Map<string, number>();\n const collect = (dir: 'downstream' | 'upstream'): void => {\n // SEP = newline; node ids never contain newlines, so the path guard is collision-free.\n const [from, to] = dir === 'downstream' ? ['source_id', 'target_id'] : ['target_id', 'source_id'];\n const sql = `\n WITH RECURSIVE walk(node_id, depth, path) AS (\n SELECT ?, 0, char(10) || ? || char(10)\n UNION ALL\n SELECT e.${to}, w.depth + 1, w.path || e.${to} || char(10)\n FROM edges e JOIN walk w ON e.${from} = w.node_id\n WHERE e.session_id = ?\n AND w.depth < ?\n AND instr(w.path, char(10) || e.${to} || char(10)) = 0\n )\n SELECT node_id, MIN(depth) AS depth FROM walk WHERE node_id != ? GROUP BY node_id`;\n const rows = this.db.prepare(sql).all(nodeId, nodeId, sessionId, maxDepth, nodeId) as Array<{ node_id: string; depth: number }>;\n for (const r of rows) {\n const prev = depthById.get(r.node_id);\n if (prev === undefined || r.depth < prev) depthById.set(r.node_id, r.depth);\n }\n };\n\n if (direction === 'both') { collect('downstream'); collect('upstream'); }\n else collect(direction);\n\n const byId = this.getNodesByIds(sessionId, [...depthById.keys()]);\n const nodes = [...depthById.entries()]\n .map(([id, depth]) => { const n = byId.get(id); return n ? { ...n, depth } : undefined; })\n .filter((n): n is NodeRow & { depth: number } => n !== undefined)\n .sort((a, b) => a.depth - b.depth);\n\n // Edges that lie within the reached subgraph (including the root).\n const reachable = new Set<string>([nodeId, ...depthById.keys()]);\n const edges = this.getEdges(sessionId).filter(e => reachable.has(e.sourceId) && reachable.has(e.targetId));\n\n return { root, direction, maxDepth, nodes, edges };\n }\n\n /** Lightweight aggregate index of the whole topology — the progressive-disclosure summary. */\n getGraphSummary(sessionId: string): GraphSummary {\n const totals = {\n nodes: (this.db.prepare('SELECT COUNT(*) c FROM nodes WHERE session_id = ?').get(sessionId) as { c: number }).c,\n edges: (this.db.prepare('SELECT COUNT(*) c FROM edges WHERE session_id = ?').get(sessionId) as { c: number }).c,\n };\n const byType: Record<string, number> = {};\n for (const r of this.db.prepare('SELECT type, COUNT(*) c FROM nodes WHERE session_id = ? GROUP BY type').all(sessionId) as Array<{ type: string; c: number }>) {\n byType[r.type] = r.c;\n }\n const byDomain: Record<string, number> = {};\n for (const r of this.db.prepare(\"SELECT COALESCE(domain, '(none)') d, COUNT(*) c FROM nodes WHERE session_id = ? GROUP BY d\").all(sessionId) as Array<{ d: string; c: number }>) {\n byDomain[r.d] = r.c;\n }\n const byRelationship: Record<string, number> = {};\n for (const r of this.db.prepare('SELECT relationship rel, COUNT(*) c FROM edges WHERE session_id = ? GROUP BY rel').all(sessionId) as Array<{ rel: string; c: number }>) {\n byRelationship[r.rel] = r.c;\n }\n const topConnected = (this.db.prepare(`\n SELECT n.id, n.name, n.type, COUNT(e.id) AS degree\n FROM nodes n\n LEFT JOIN edges e ON e.session_id = n.session_id AND (e.source_id = n.id OR e.target_id = n.id)\n WHERE n.session_id = ?\n GROUP BY n.id, n.name, n.type\n ORDER BY degree DESC, n.confidence DESC\n LIMIT 10\n `).all(sessionId) as Array<{ id: string; name: string; type: string; degree: number }>);\n\n return { sessionId, totals, nodesByType: byType, nodesByDomain: byDomain, edgesByRelationship: byRelationship, topConnected };\n }\n\n // ── Stats ───────────────────────────────\n\n getStats(sessionId: string): { nodes: number; edges: number; events: number; tasks: number } {\n const nodes = (this.db.prepare('SELECT COUNT(*) as c FROM nodes WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const edges = (this.db.prepare('SELECT COUNT(*) as c FROM edges WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const events = (this.db.prepare('SELECT COUNT(*) as c FROM activity_events WHERE session_id = ?').get(sessionId) as { c: number }).c;\n const tasks = (this.db.prepare('SELECT COUNT(*) as c FROM tasks WHERE session_id = ?').get(sessionId) as { c: number }).c;\n return { nodes, edges, events, tasks };\n }\n}\n","import { z } from 'zod';\n\n// ── Enums ────────────────────────────────\n\nexport const NODE_TYPES = [\n 'host', 'database_server', 'database', 'table',\n 'web_service', 'api_endpoint', 'cache_server',\n 'message_broker', 'queue', 'topic',\n 'container', 'pod', 'k8s_cluster',\n 'config_file', 'saas_tool', 'unknown',\n] as const;\nexport type NodeType = typeof NODE_TYPES[number];\n\n/**\n * Semantic groupings of node types — the single source of truth shared by the MCP\n * resource layer (services/databases) and the exporters (layer assignment). Each\n * node type belongs to at most one group; anything ungrouped is treated as \"other\".\n */\nexport const NODE_TYPE_GROUPS = {\n saas: ['saas_tool'],\n web: ['web_service', 'api_endpoint'],\n data: ['database_server', 'database', 'table', 'cache_server'],\n messaging: ['message_broker', 'queue', 'topic'],\n infra: ['host', 'container', 'pod', 'k8s_cluster'],\n config: ['config_file'],\n} as const satisfies Record<string, readonly NodeType[]>;\n\nexport const EDGE_RELATIONSHIPS = [\n 'connects_to', 'reads_from', 'writes_to',\n 'calls', 'contains', 'depends_on',\n] as const;\nexport type EdgeRelationship = typeof EDGE_RELATIONSHIPS[number];\n\n// ── Zod Schemas ──────────────────────────\n\nexport const NodeSchema = z.object({\n id: z.string().describe('Format: \"{type}:{host}:{port}\" or \"{type}:{name}\"'),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discoveredVia: z.string(),\n confidence: z.number().min(0).max(1).default(0.5),\n metadata: z.record(z.string(), z.unknown()).default({}),\n tags: z.array(z.string()).default([]),\n domain: z.string().optional().describe('Business domain, e.g. \"Marketing\", \"Finance\"'),\n subDomain: z.string().optional().describe('Sub-domain, e.g. \"Forecast client orders\"'),\n qualityScore: z.number().min(0).max(100).optional().describe('Data quality score 0–100'),\n});\nexport type DiscoveryNode = z.infer<typeof NodeSchema>;\n\nexport const EdgeSchema = z.object({\n sourceId: z.string(),\n targetId: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string(),\n confidence: z.number().min(0).max(1).default(0.5),\n});\nexport type DiscoveryEdge = z.infer<typeof EdgeSchema>;\n\n// ── Cartography Map Types ────────────────\n\nexport const DataAssetSchema = z.object({\n id: z.string(),\n name: z.string(),\n domain: z.string(),\n subDomain: z.string().optional(),\n qualityScore: z.number().min(0).max(100).optional(),\n metadata: z.record(z.string(), z.unknown()).default({}),\n position: z.object({ q: z.number(), r: z.number() }),\n});\nexport type DataAsset = z.infer<typeof DataAssetSchema>;\n\nexport const ClusterSchema = z.object({\n id: z.string(),\n label: z.string(),\n domain: z.string(),\n color: z.string(),\n assetIds: z.array(z.string()),\n centroid: z.object({ x: z.number(), y: z.number() }),\n});\nexport type Cluster = z.infer<typeof ClusterSchema>;\n\nexport const ConnectionSchema = z.object({\n id: z.string(),\n sourceAssetId: z.string(),\n targetAssetId: z.string(),\n type: z.string().optional(),\n});\nexport type Connection = z.infer<typeof ConnectionSchema>;\n\nexport interface CartographyMapData {\n assets: DataAsset[];\n clusters: Cluster[];\n connections: Connection[];\n meta: { exportedAt: string; theme: 'light' | 'dark' };\n}\n\n/** Navy → medium blue → periwinkle → teal/cyan palette */\nexport const DOMAIN_COLORS: Record<string, string> = {\n 'Quality Control': '#1a2744',\n 'Supply Chain': '#1e3a6e',\n 'Marketing': '#6a7fb5',\n 'Finance': '#3a8a8a',\n 'HR': '#2a5a9a',\n 'Logistics': '#0e7490',\n 'Sales': '#1d4ed8',\n 'Engineering': '#4338ca',\n 'Operations': '#0891b2',\n 'Data Layer': '#1e3352',\n 'Web / API': '#1a3a1a',\n 'Messaging': '#2a1a3a',\n 'Infrastructure': '#0f2a40',\n 'Other': '#374151',\n};\n\n/** Ordered palette for dynamic domain assignment */\nexport const DOMAIN_PALETTE = [\n '#1a2e5a', '#1e3a8a', '#1d4ed8', '#2563eb', '#3b82f6',\n '#6366f1', '#818cf8', '#7c9fc3', '#0e7490', '#0891b2',\n '#06b6d4', '#22d3ee', '#0d9488', '#14b8a6', '#2dd4bf', '#5eead4',\n] as const;\n\n// ── DB Row Types ─────────────────────────\n\nexport interface NodeRow extends DiscoveryNode {\n sessionId: string;\n discoveredAt: string;\n depth: number;\n pathId?: string;\n}\n\nexport interface EdgeRow extends DiscoveryEdge {\n id: string;\n sessionId: string;\n discoveredAt: string;\n pathId?: string;\n}\n\nexport interface SessionRow {\n id: string;\n mode: 'discover';\n startedAt: string;\n completedAt?: string;\n config: string;\n}\n\n// ── Config ───────────────────────────────\n\nexport interface CartographyConfig {\n maxDepth: number;\n maxTurns: number;\n entryPoints: string[];\n agentModel: string;\n organization?: string;\n outputDir: string;\n dbPath: string;\n verbose: boolean;\n}\n\nexport function defaultConfig(overrides: Partial<CartographyConfig> = {}): CartographyConfig {\n const home = process.env.HOME ?? process.env.USERPROFILE ?? '/tmp';\n return {\n maxDepth: 8,\n maxTurns: 50,\n entryPoints: ['localhost'],\n agentModel: 'claude-sonnet-4-5-20250929',\n outputDir: './cartography-output',\n dbPath: `${home}/.cartography/cartography.db`,\n verbose: false,\n ...overrides,\n };\n}\n","/**\n * The Cartography MCP server — the package's primary, LLM-agnostic interface.\n *\n * It exposes the discovered infrastructure topology as Model Context Protocol\n * **Resources** (read-only context, progressive disclosure), a small set of query\n * **Tools** (parameterized lookups), and reusable **Prompts**. Any MCP host —\n * Claude Code, Cursor, Cline, Windsurf, the Vercel AI SDK, LangGraph — can drive\n * it; the package never needs to know which model is in use.\n */\n\nimport { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { CartographyDB } from '../db.js';\nimport type { GraphSummary } from '../db.js';\nimport { defaultConfig, NODE_TYPES, NODE_TYPE_GROUPS } from '../types.js';\nimport type { NodeRow } from '../types.js';\n\nconst SERVER_NAME = 'cartography';\nconst SERVER_VERSION = '2.0.0';\n\nconst SERVICE_TYPES = NODE_TYPE_GROUPS.web;\nconst DATA_TYPES = NODE_TYPE_GROUPS.data;\n\n/** A pluggable search backend; defaults to lexical search, can be upgraded to semantic. */\nexport type SearchFn = (\n db: CartographyDB,\n sessionId: string,\n query: string,\n opts: { types?: readonly string[]; limit: number },\n) => Promise<Array<{ node: NodeRow; score?: number }>>;\n\n/** A pluggable discovery backend invoked by the `run_discovery` tool. */\nexport type DiscoveryFn = (\n db: CartographyDB,\n sessionId: string,\n opts: { hint?: string },\n) => Promise<{ nodes: number; edges: number }>;\n\nexport interface CreateMcpServerOptions {\n /** Database instance. If omitted, one is opened at `config.dbPath`. */\n db?: CartographyDB;\n /** Path to the SQLite catalog (used when `db` is not provided). */\n dbPath?: string;\n /** Session to serve: a session id, or `'latest'` (default) for the newest discovery. */\n session?: string | 'latest';\n /** Semantic/lexical search backend. Defaults to lexical `searchNodes`. */\n search?: SearchFn;\n /** Discovery backend for `run_discovery`/`refresh`. Optional. */\n discovery?: DiscoveryFn;\n}\n\nconst lexicalSearch: SearchFn = async (db, sessionId, query, opts) =>\n db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));\n\n/** Compact projection of a node for tool results (token-economical). */\nfunction compactNode(n: NodeRow): Record<string, unknown> {\n return {\n id: n.id,\n type: n.type,\n name: n.name,\n confidence: n.confidence,\n ...(n.domain ? { domain: n.domain } : {}),\n ...(n.tags.length ? { tags: n.tags } : {}),\n };\n}\n\nfunction json(data: unknown) {\n return { content: [{ type: 'text' as const, text: JSON.stringify(data, null, 2) }] };\n}\n\nfunction summaryText(s: GraphSummary): string {\n const lines = [\n `# Infrastructure topology — session ${s.sessionId}`,\n ``,\n `Totals: ${s.totals.nodes} nodes, ${s.totals.edges} edges`,\n ``,\n `Nodes by type:`,\n ...Object.entries(s.nodesByType).sort((a, b) => b[1] - a[1]).map(([t, c]) => ` - ${t}: ${c}`),\n ``,\n `Nodes by domain:`,\n ...Object.entries(s.nodesByDomain).sort((a, b) => b[1] - a[1]).map(([d, c]) => ` - ${d}: ${c}`),\n ``,\n `Edges by relationship:`,\n ...Object.entries(s.edgesByRelationship).sort((a, b) => b[1] - a[1]).map(([r, c]) => ` - ${r}: ${c}`),\n ``,\n `Most connected:`,\n ...s.topConnected.map((n) => ` - ${n.id} (${n.type}) — degree ${n.degree}`),\n ``,\n `Read cartography://nodes/{id} or cartography://dependencies/{id} for detail.`,\n ];\n return lines.join('\\n');\n}\n\n/**\n * Build a fully-configured Cartography MCP server. Call `.connect(transport)` to run it.\n */\nexport function createMcpServer(opts: CreateMcpServerOptions = {}): McpServer {\n const db = opts.db ?? new CartographyDB(opts.dbPath ?? defaultConfig().dbPath);\n const search = opts.search ?? lexicalSearch;\n\n /** Resolve the served session id at call time (so late discoveries are picked up). */\n const resolveSession = (): string | undefined => {\n if (opts.session && opts.session !== 'latest') return opts.session;\n return db.getLatestSession('discover')?.id ?? db.getLatestSession()?.id;\n };\n\n const server = new McpServer(\n { name: SERVER_NAME, version: SERVER_VERSION },\n {\n capabilities: { resources: { subscribe: true, listChanged: true }, tools: {}, prompts: {}, logging: {} },\n instructions:\n 'Cartography exposes a discovered infrastructure/SaaS topology. Start by reading ' +\n 'cartography://graph/summary for a low-token overview, then drill into specific nodes ' +\n 'via cartography://nodes/{id} or query with the query_infrastructure / get_dependencies tools.',\n },\n );\n\n // ── Resources (read-only context, progressive disclosure) ──────────────────\n\n server.registerResource(\n 'graph-summary',\n 'cartography://graph/summary',\n { title: 'Topology summary', description: 'Low-token aggregate index of the whole landscape — read this first.', mimeType: 'text/markdown' },\n (uri) => {\n const sid = resolveSession();\n if (!sid) return { contents: [{ uri: uri.href, mimeType: 'text/markdown', text: 'No discovery session found. Run discovery first.' }] };\n return { contents: [{ uri: uri.href, mimeType: 'text/markdown', text: summaryText(db.getGraphSummary(sid)) }] };\n },\n );\n\n server.registerResource(\n 'nodes-index',\n 'cartography://nodes',\n { title: 'Node index', description: 'Lightweight list of all nodes (id, type, name only).', mimeType: 'application/json' },\n (uri) => {\n const sid = resolveSession();\n const nodes = sid ? db.getNodes(sid) : [];\n return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ count: nodes.length, nodes: nodes.map((n) => ({ id: n.id, type: n.type, name: n.name })) }, null, 2) }] };\n },\n );\n\n server.registerResource(\n 'node-detail',\n new ResourceTemplate('cartography://nodes/{id}', { list: undefined }),\n { title: 'Node detail', description: 'Full node record plus its incident edges.', mimeType: 'application/json' },\n (uri, variables) => {\n const sid = resolveSession();\n const id = decodeURIComponent(String(variables['id']));\n const node = sid ? db.getNode(sid, id) : undefined;\n if (!node) return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ error: `node not found: ${id}` }) }] };\n const edges = db.getEdges(sid!).filter((e) => e.sourceId === id || e.targetId === id);\n return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ node, edges }, null, 2) }] };\n },\n );\n\n const typedListResource = (name: string, uri: string, title: string, types: readonly string[]) =>\n server.registerResource(name, uri, { title, description: `Nodes of type: ${types.join(', ')}.`, mimeType: 'application/json' }, (u) => {\n const sid = resolveSession();\n const nodes = sid ? db.getNodesByType(sid, types) : [];\n return { contents: [{ uri: u.href, mimeType: 'application/json', text: JSON.stringify({ count: nodes.length, nodes: nodes.map(compactNode) }, null, 2) }] };\n });\n\n typedListResource('services', 'cartography://services', 'Services', SERVICE_TYPES);\n typedListResource('databases', 'cartography://databases', 'Data stores', DATA_TYPES);\n\n server.registerResource(\n 'dependencies',\n new ResourceTemplate('cartography://dependencies/{id}', { list: undefined }),\n { title: 'Dependencies', description: 'Transitive downstream dependencies of a node.', mimeType: 'application/json' },\n (uri, variables) => {\n const sid = resolveSession();\n const id = decodeURIComponent(String(variables['id']));\n if (!sid) return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ error: 'no session' }) }] };\n const r = db.getDependencies(sid, id, { direction: 'downstream', maxDepth: 8 });\n return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify({ root: id, count: r.nodes.length, nodes: r.nodes.map((n) => ({ ...compactNode(n), depth: n.depth })) }, null, 2) }] };\n },\n );\n\n server.registerResource(\n 'sessions',\n 'cartography://sessions',\n { title: 'Discovery sessions', description: 'All discovery sessions in the catalog.', mimeType: 'application/json' },\n (uri) => ({ contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(db.getSessions(), null, 2) }] }),\n );\n\n // ── Tools (model-controlled queries) ───────────────────────────────────────\n\n server.registerTool(\n 'get_summary',\n { title: 'Get topology summary', description: 'Low-token overview of the whole landscape (counts, types, domains, most-connected).', inputSchema: {} },\n () => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n return json(db.getGraphSummary(sid));\n },\n );\n\n server.registerTool(\n 'query_infrastructure',\n {\n title: 'Query infrastructure',\n description: 'Search the topology by name/id/domain (optionally filtered by node type). Returns compact node records.',\n inputSchema: {\n query: z.string().describe('Free-text query, e.g. \"postgres\", \"auth\", \"github\"'),\n types: z.array(z.enum(NODE_TYPES)).optional().describe('Restrict to these node types'),\n limit: z.number().int().min(1).max(200).default(25).optional(),\n },\n },\n async (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const results = await search(db, sid, args.query, { types: args.types, limit: args.limit ?? 25 });\n return json({ count: results.length, results: results.map((r) => ({ ...compactNode(r.node), ...(r.score !== undefined ? { score: r.score } : {}) })) });\n },\n );\n\n server.registerTool(\n 'search_topology',\n {\n title: 'Search topology (semantic)',\n description: 'Find nodes related to a concept by meaning (semantic search when available, lexical otherwise).',\n inputSchema: { query: z.string(), limit: z.number().int().min(1).max(100).default(10).optional() },\n },\n async (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const results = await search(db, sid, args.query, { limit: args.limit ?? 10 });\n return json({ count: results.length, results: results.map((r) => ({ ...compactNode(r.node), ...(r.score !== undefined ? { score: r.score } : {}) })) });\n },\n );\n\n server.registerTool(\n 'list_services',\n {\n title: 'List services',\n description: 'List discovered services or data stores.',\n inputSchema: { kind: z.enum(['services', 'databases', 'all']).default('all').optional() },\n },\n (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const kind = args.kind ?? 'all';\n const types = kind === 'services' ? SERVICE_TYPES : kind === 'databases' ? DATA_TYPES : [...SERVICE_TYPES, ...DATA_TYPES];\n return json(db.getNodesByType(sid, types).map(compactNode));\n },\n );\n\n server.registerTool(\n 'get_node',\n { title: 'Get node', description: 'Fetch a single node with its incident edges.', inputSchema: { id: z.string() } },\n (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const node = db.getNode(sid, args.id);\n if (!node) return json({ error: `node not found: ${args.id}` });\n const edges = db.getEdges(sid).filter((e) => e.sourceId === args.id || e.targetId === args.id);\n return json({ node, edges });\n },\n );\n\n server.registerTool(\n 'get_dependencies',\n {\n title: 'Get dependencies',\n description: 'Traverse the dependency graph from a node (downstream/upstream/both) with a depth limit.',\n inputSchema: {\n id: z.string(),\n direction: z.enum(['downstream', 'upstream', 'both']).default('downstream').optional(),\n maxDepth: z.number().int().min(1).max(64).default(8).optional(),\n },\n },\n (args) => {\n const sid = resolveSession();\n if (!sid) return json({ error: 'No discovery session found.' });\n const r = db.getDependencies(sid, args.id, { direction: args.direction ?? 'downstream', maxDepth: args.maxDepth ?? 8 });\n return json({\n root: r.root ? compactNode(r.root) : null,\n direction: r.direction,\n count: r.nodes.length,\n nodes: r.nodes.map((n) => ({ ...compactNode(n), depth: n.depth })),\n edges: r.edges.map((e) => ({ from: e.sourceId, to: e.targetId, rel: e.relationship })),\n });\n },\n );\n\n if (opts.discovery) {\n const discovery = opts.discovery;\n server.registerTool(\n 'run_discovery',\n {\n title: 'Run discovery',\n description: 'Scan the local system (read-only) and update the catalog. Returns counts of nodes/edges found.',\n inputSchema: { hint: z.string().optional().describe('Optional focus, e.g. tool names to look for') },\n },\n async (args) => {\n let sid = resolveSession();\n if (!sid) sid = db.createSession('discover', defaultConfig());\n const result = await discovery(db, sid, { hint: args.hint });\n server.server.sendResourceUpdated({ uri: 'cartography://graph/summary' }).catch(() => {});\n server.server.sendResourceListChanged?.();\n return json({ session: sid, ...result });\n },\n );\n }\n\n // ── Prompts (user-controlled templates) ────────────────────────────────────\n\n server.registerPrompt(\n 'audit-attack-surface',\n { title: 'Audit attack surface', description: 'Review the discovered topology for externally-reachable services and risky dependencies.' },\n () => ({\n messages: [{\n role: 'user', content: { type: 'text', text:\n 'Read cartography://graph/summary and cartography://services. Identify externally-reachable ' +\n 'services, data stores with broad inbound dependencies, and any node with low confidence that ' +\n 'warrants verification. Use get_dependencies to assess blast radius. Summarize the attack surface ' +\n 'and concrete hardening recommendations.' } }],\n }),\n );\n\n server.registerPrompt(\n 'map-service-dependencies',\n {\n title: 'Map service dependencies',\n description: 'Produce a dependency map for a given service.',\n argsSchema: { service: z.string().describe('Service node id or name') },\n },\n (args) => ({\n messages: [{\n role: 'user', content: { type: 'text', text:\n `Use query_infrastructure to locate \"${args.service}\", then get_dependencies (direction=both) to ` +\n `map everything it depends on and everything that depends on it. Present the result as a clear ` +\n `dependency tree and call out single points of failure.` } }],\n }),\n );\n\n server.registerPrompt(\n 'onboard-to-system',\n { title: 'Onboard to system', description: 'Explain the system landscape to a new engineer.' },\n () => ({\n messages: [{\n role: 'user', content: { type: 'text', text:\n 'Read cartography://graph/summary, then cartography://services and cartography://databases. ' +\n 'Write a concise onboarding briefing for a new engineer: what the major systems are, how they ' +\n 'connect, which data stores are central, and where to look first.' } }],\n }),\n );\n\n return server;\n}\n","/**\n * Transport bindings for the Cartography MCP server.\n *\n * - **stdio**: the local-first default — zero network, every client supports it.\n * - **Streamable HTTP**: a single `/mcp` endpoint for team/remote use, bound to\n * localhost with DNS-rebinding protection. The deprecated SSE transport is not used.\n */\n\nimport { randomUUID } from 'node:crypto';\nimport http from 'node:http';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n/** Connect a server over stdio (resolves when the transport closes). */\nexport async function runStdio(server: McpServer): Promise<void> {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n\nexport interface HttpOptions {\n port?: number;\n host?: string;\n /** Extra allowed Host headers (defaults to localhost:port variants). */\n allowedHosts?: string[];\n /** Allowed Origin headers (defaults to none → same-origin only). */\n allowedOrigins?: string[];\n}\n\nasync function readJsonBody(req: http.IncomingMessage): Promise<unknown> {\n const chunks: Buffer[] = [];\n for await (const chunk of req) chunks.push(chunk as Buffer);\n if (chunks.length === 0) return undefined;\n try { return JSON.parse(Buffer.concat(chunks).toString('utf8')); } catch { return undefined; }\n}\n\n/**\n * Start a Streamable HTTP server. A fresh MCP server instance is created per\n * session via `factory`, so multiple clients can connect concurrently.\n */\nexport async function runHttp(factory: () => McpServer, opts: HttpOptions = {}): Promise<http.Server> {\n const host = opts.host ?? '127.0.0.1';\n const port = opts.port ?? 3737;\n const allowedHosts = opts.allowedHosts ?? [`${host}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];\n const transports = new Map<string, StreamableHTTPServerTransport>();\n\n const httpServer = http.createServer(async (req, res) => {\n try {\n const url = req.url ?? '';\n if (!url.startsWith('/mcp')) { res.writeHead(404, { 'content-type': 'application/json' }).end('{\"error\":\"not found\"}'); return; }\n\n const sessionId = req.headers['mcp-session-id'] as string | undefined;\n const existing = sessionId ? transports.get(sessionId) : undefined;\n\n if (existing) {\n const body = req.method === 'POST' ? await readJsonBody(req) : undefined;\n await existing.handleRequest(req, res, body);\n return;\n }\n\n if (req.method !== 'POST') {\n res.writeHead(400, { 'content-type': 'application/json' }).end('{\"error\":\"missing or unknown mcp-session-id\"}');\n return;\n }\n\n // New session: initialize a transport + server instance.\n const body = await readJsonBody(req);\n const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n enableDnsRebindingProtection: true,\n allowedHosts,\n ...(opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {}),\n onsessioninitialized: (id: string) => { transports.set(id, transport); },\n });\n transport.onclose = () => { if (transport.sessionId) transports.delete(transport.sessionId); };\n await factory().connect(transport);\n await transport.handleRequest(req, res, body);\n } catch {\n if (!res.headersSent) res.writeHead(500, { 'content-type': 'application/json' }).end('{\"error\":\"internal error\"}');\n }\n });\n\n await new Promise<void>((resolve) => httpServer.listen(port, host, resolve));\n return httpServer;\n}\n","/**\n * Scanner plugin contract.\n *\n * A scanner detects whether it applies to the current machine, then produces a\n * read-only {@link ScanResult}: deterministic nodes/edges where the data is\n * structured enough, plus an optional raw report for an LLM to classify further.\n *\n * Modeled on Steampipe plugins / Backstage processors: new sources can be added\n * (in-tree or via `@datasynx/scanner-*` packages) and registered without forking\n * the core. Each scanner declares the commands it needs, feeding the safety layer.\n */\n\nimport type { Platform } from '../platform.js';\nimport type { DiscoveryNode, DiscoveryEdge } from '../types.js';\n\nexport interface ScanContext {\n /** Optional focus hint from the caller (e.g. tool names to look for). */\n hint?: string;\n /** The current platform. */\n platform: Platform;\n /** Allowlist-gated command runner (returns '' on error/blocked). */\n run: (cmd: string, opts?: { timeout?: number; env?: NodeJS.ProcessEnv }) => string;\n}\n\nexport interface ScanResult {\n /** Deterministically classified nodes. */\n nodes: DiscoveryNode[];\n /** Deterministically classified edges. */\n edges: DiscoveryEdge[];\n /** Optional raw text report for LLM-driven classification. */\n report?: string;\n}\n\nexport interface Scanner {\n /** Stable id, e.g. \"bookmarks\", \"installed-apps\", \"cloud-aws\". */\n id: string;\n /** Human-readable title. */\n title: string;\n /** Platforms this scanner supports, or 'all'. */\n platforms: Platform[] | 'all';\n /** Read-only commands this scanner may run (declared for the safety layer/docs). */\n allowedCommands?: string[];\n /** Cheap check whether the scanner applies here (e.g. a CLI is installed). */\n detect(ctx: ScanContext): boolean | Promise<boolean>;\n /** Perform the read-only scan. */\n scan(ctx: ScanContext): Promise<ScanResult>;\n}\n\n/** A typed registry of scanners with lazy, platform-aware selection. */\nexport class ScannerRegistry {\n private scanners = new Map<string, Scanner>();\n\n register(scanner: Scanner): this {\n if (this.scanners.has(scanner.id)) throw new Error(`scanner already registered: ${scanner.id}`);\n this.scanners.set(scanner.id, scanner);\n return this;\n }\n\n get(id: string): Scanner | undefined {\n return this.scanners.get(id);\n }\n\n list(): Scanner[] {\n return [...this.scanners.values()];\n }\n\n /** Scanners whose `platforms` include the given platform. */\n forPlatform(platform: Platform): Scanner[] {\n return this.list().filter((s) => s.platforms === 'all' || s.platforms.includes(platform));\n }\n}\n","import { tmpdir } from 'node:os';\nimport { existsSync, readFileSync, readdirSync, copyFileSync, statSync, unlinkSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { HOME, IS_WIN, IS_MAC, browserBasePaths, firefoxBaseDirs } from './platform.js';\n\n/**\n * Remove orphaned temp files from previous bookmark/history scans.\n * Call at startup to prevent /tmp accumulation after crashes.\n */\nexport function cleanupTempFiles(): number {\n let cleaned = 0;\n const tmp = tmpdir();\n try {\n for (const f of readdirSync(tmp)) {\n if (f.startsWith('cartograph_') && f.endsWith('.sqlite')) {\n try {\n unlinkSync(join(tmp, f));\n cleaned++;\n } catch { /* file in use or already gone */ }\n }\n }\n } catch { /* tmpdir not readable */ }\n return cleaned;\n}\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface BookmarkHost {\n hostname: string;\n port: number;\n protocol: 'http' | 'https';\n source: string;\n}\n\nexport interface HistoryHost extends BookmarkHost {\n visitCount: number;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nexport function extractHost(rawUrl: string, source: string): BookmarkHost | null {\n try {\n const u = new URL(rawUrl);\n if (u.protocol !== 'http:' && u.protocol !== 'https:') return null;\n const protocol = u.protocol === 'https:' ? 'https' as const : 'http' as const;\n // Strip: no paths, no params, no credentials — hostname only\n const port = u.port ? parseInt(u.port, 10) : (protocol === 'https' ? 443 : 80);\n const hostname = u.hostname.toLowerCase();\n if (!hostname || hostname === 'localhost' || hostname === '127.0.0.1') return null;\n return { hostname, port, protocol, source };\n } catch {\n return null;\n }\n}\n\n// Chrome/Edge/Brave JSON format\nexport interface ChromeNode {\n type?: string;\n url?: string;\n children?: ChromeNode[];\n}\n\nexport function walkChrome(node: ChromeNode, source: string, out: BookmarkHost[]): void {\n if (node.type === 'url' && node.url) {\n const h = extractHost(node.url, source);\n if (h) out.push(h);\n }\n if (node.children) {\n for (const child of node.children) walkChrome(child, source, out);\n }\n}\n\nexport function readChromeLike(filePath: string, source: string): BookmarkHost[] {\n if (!existsSync(filePath)) return [];\n try {\n const raw = JSON.parse(readFileSync(filePath, 'utf8')) as {\n roots: Record<string, ChromeNode>;\n };\n const out: BookmarkHost[] = [];\n for (const root of Object.values(raw.roots)) {\n if (root) walkChrome(root, source, out);\n }\n return out;\n } catch {\n return [];\n }\n}\n\nasync function queryBrowserDb<T>(srcPath: string, tmpPrefix: string, query: string): Promise<T[]> {\n if (!existsSync(srcPath)) return [];\n const tmp = join(tmpdir(), `cartograph_${tmpPrefix}_${Date.now()}.sqlite`);\n try {\n copyFileSync(srcPath, tmp);\n const { default: Database } = await import('better-sqlite3');\n const db = new Database(tmp, { readonly: true, fileMustExist: true });\n try {\n return db.prepare(query).all() as T[];\n } finally {\n db.close();\n }\n } catch {\n return [];\n } finally {\n try { unlinkSync(tmp); } catch { /* ignore */ }\n }\n}\n\nasync function readFirefoxBookmarks(profileDir: string): Promise<BookmarkHost[]> {\n const rows = await queryBrowserDb<{ url: string }>(\n join(profileDir, 'places.sqlite'), 'ff_bm',\n `SELECT DISTINCT p.url FROM moz_places p\n JOIN moz_bookmarks b ON b.fk = p.id\n WHERE b.type = 1 AND p.url NOT LIKE 'place:%' LIMIT 3000`,\n );\n return rows.map(r => extractHost(r.url, 'firefox')).filter((h): h is BookmarkHost => h !== null);\n}\n\nexport async function readFirefoxHistory(profileDir: string): Promise<HistoryHost[]> {\n const rows = await queryBrowserDb<{ url: string; visit_count: number }>(\n join(profileDir, 'places.sqlite'), 'ff_hist',\n `SELECT url, visit_count FROM moz_places\n WHERE url NOT LIKE 'place:%' AND visit_count > 0\n ORDER BY visit_count DESC LIMIT 5000`,\n );\n return rows\n .map(r => {\n const h = extractHost(r.url, 'firefox');\n return h ? { ...h, visitCount: r.visit_count } : null;\n })\n .filter((h): h is HistoryHost => h !== null);\n}\n\nasync function readChromiumHistory(historyPath: string, source: string): Promise<HistoryHost[]> {\n const rows = await queryBrowserDb<{ url: string; visit_count: number }>(\n historyPath, 'ch_hist',\n `SELECT url, visit_count FROM urls\n WHERE hidden = 0 AND visit_count > 0\n ORDER BY visit_count DESC LIMIT 5000`,\n );\n return rows\n .map(r => {\n const h = extractHost(r.url, source);\n return h ? { ...h, visitCount: r.visit_count } : null;\n })\n .filter((h): h is HistoryHost => h !== null);\n}\n\n// ── Platform paths ────────────────────────────────────────────────────────────\n// Uses centralized platform.ts for Linux/macOS/Windows browser base paths.\n\nconst IS_LINUX = !IS_MAC && !IS_WIN;\n\n// Browser bookmark file paths (multiple profiles supported)\nexport function chromeLikePaths(base: string): string[] {\n const paths: string[] = [];\n const defaultPath = join(base, 'Default', 'Bookmarks');\n if (existsSync(defaultPath)) paths.push(defaultPath);\n // Also check Profile 1, Profile 2, etc.\n if (existsSync(base)) {\n try {\n for (const entry of readdirSync(base)) {\n if (entry.startsWith('Profile ')) {\n const p = join(base, entry, 'Bookmarks');\n if (existsSync(p)) paths.push(p);\n }\n }\n } catch { /* ignore */ }\n }\n return paths;\n}\n\nexport function chromeLikeHistoryPaths(base: string): string[] {\n const paths: string[] = [];\n const defaultPath = join(base, 'Default', 'History');\n if (existsSync(defaultPath)) paths.push(defaultPath);\n if (existsSync(base)) {\n try {\n for (const entry of readdirSync(base)) {\n if (entry.startsWith('Profile ')) {\n const p = join(base, entry, 'History');\n if (existsSync(p)) paths.push(p);\n }\n }\n } catch { /* ignore */ }\n }\n return paths;\n}\n\n// Get browser bases from centralized platform module\nconst BROWSER_BASES = browserBasePaths();\n\nconst CHROME_BASE = BROWSER_BASES.chrome;\nconst CHROMIUM_BASE = BROWSER_BASES.chromium;\nconst EDGE_BASE = BROWSER_BASES.edge;\nconst BRAVE_BASE = BROWSER_BASES.brave;\nconst VIVALDI_BASE = BROWSER_BASES.vivaldi;\nconst OPERA_BASE = BROWSER_BASES.opera;\n\n// Snap / Flatpak variants (Linux only)\nconst CHROMIUM_SNAP_BASE = join(HOME, 'snap', 'chromium', 'common', 'chromium');\nconst CHROMIUM_FLATPAK_BASE = join(HOME, '.var', 'app', 'org.chromium.Chromium', 'config', 'chromium');\nconst CHROME_FLATPAK_BASE = join(HOME, '.var', 'app', 'com.google.Chrome', 'config', 'google-chrome');\nconst BRAVE_FLATPAK_BASE = join(HOME, '.var', 'app', 'com.brave.Browser', 'config', 'BraveSoftware', 'Brave-Browser');\nconst EDGE_FLATPAK_BASE = join(HOME, '.var', 'app', 'com.microsoft.Edge', 'config', 'microsoft-edge');\n\nfunction firefoxProfileDirs(): string[] {\n const bases = firefoxBaseDirs();\n const dirs: string[] = [];\n for (const base of bases) {\n if (!existsSync(base)) continue;\n try {\n for (const d of readdirSync(base)) {\n const full = join(base, d);\n try {\n if (statSync(full).isDirectory() && existsSync(join(full, 'places.sqlite'))) {\n dirs.push(full);\n }\n } catch { /* ignore */ }\n }\n } catch { /* ignore */ }\n }\n return dirs;\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\nexport async function scanAllBookmarks(): Promise<BookmarkHost[]> {\n const all: BookmarkHost[] = [];\n\n // Standard browser paths\n for (const p of chromeLikePaths(CHROME_BASE)) all.push(...readChromeLike(p, 'chrome'));\n for (const p of chromeLikePaths(CHROMIUM_BASE)) all.push(...readChromeLike(p, 'chromium'));\n for (const p of chromeLikePaths(EDGE_BASE)) all.push(...readChromeLike(p, 'edge'));\n for (const p of chromeLikePaths(BRAVE_BASE)) all.push(...readChromeLike(p, 'brave'));\n for (const p of chromeLikePaths(VIVALDI_BASE)) all.push(...readChromeLike(p, 'vivaldi'));\n for (const p of chromeLikePaths(OPERA_BASE)) all.push(...readChromeLike(p, 'opera'));\n\n // Snap / Flatpak paths (Linux only — not macOS, not Windows)\n if (IS_LINUX) {\n for (const p of chromeLikePaths(CHROMIUM_SNAP_BASE)) all.push(...readChromeLike(p, 'chromium-snap'));\n for (const p of chromeLikePaths(CHROMIUM_FLATPAK_BASE)) all.push(...readChromeLike(p, 'chromium-flatpak'));\n for (const p of chromeLikePaths(CHROME_FLATPAK_BASE)) all.push(...readChromeLike(p, 'chrome-flatpak'));\n for (const p of chromeLikePaths(BRAVE_FLATPAK_BASE)) all.push(...readChromeLike(p, 'brave-flatpak'));\n for (const p of chromeLikePaths(EDGE_FLATPAK_BASE)) all.push(...readChromeLike(p, 'edge-flatpak'));\n }\n\n // Firefox: standard + snap + flatpak\n for (const dir of firefoxProfileDirs()) {\n all.push(...await readFirefoxBookmarks(dir));\n }\n\n // Deduplicate by hostname\n const seen = new Set<string>();\n return all.filter(h => {\n if (seen.has(h.hostname)) return false;\n seen.add(h.hostname);\n return true;\n });\n}\n\nexport async function scanAllHistory(): Promise<HistoryHost[]> {\n const all: HistoryHost[] = [];\n\n // Standard browser paths\n for (const p of chromeLikeHistoryPaths(CHROME_BASE)) all.push(...await readChromiumHistory(p, 'chrome'));\n for (const p of chromeLikeHistoryPaths(CHROMIUM_BASE)) all.push(...await readChromiumHistory(p, 'chromium'));\n for (const p of chromeLikeHistoryPaths(EDGE_BASE)) all.push(...await readChromiumHistory(p, 'edge'));\n for (const p of chromeLikeHistoryPaths(BRAVE_BASE)) all.push(...await readChromiumHistory(p, 'brave'));\n for (const p of chromeLikeHistoryPaths(VIVALDI_BASE)) all.push(...await readChromiumHistory(p, 'vivaldi'));\n for (const p of chromeLikeHistoryPaths(OPERA_BASE)) all.push(...await readChromiumHistory(p, 'opera'));\n\n // Snap / Flatpak paths (Linux only — not macOS, not Windows)\n if (IS_LINUX) {\n for (const p of chromeLikeHistoryPaths(CHROMIUM_SNAP_BASE)) all.push(...await readChromiumHistory(p, 'chromium-snap'));\n for (const p of chromeLikeHistoryPaths(CHROMIUM_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'chromium-flatpak'));\n for (const p of chromeLikeHistoryPaths(CHROME_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'chrome-flatpak'));\n for (const p of chromeLikeHistoryPaths(BRAVE_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'brave-flatpak'));\n for (const p of chromeLikeHistoryPaths(EDGE_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, 'edge-flatpak'));\n }\n\n // Firefox: standard + snap + flatpak\n for (const dir of firefoxProfileDirs()) {\n all.push(...await readFirefoxHistory(dir));\n }\n\n // Deduplicate by hostname, summing visit counts\n const byHost = new Map<string, HistoryHost>();\n for (const h of all) {\n const existing = byHost.get(h.hostname);\n if (existing) {\n existing.visitCount += h.visitCount;\n } else {\n byHost.set(h.hostname, { ...h });\n }\n }\n\n // Sort by visit count descending\n return [...byHost.values()].sort((a, b) => b.visitCount - a.visitCount);\n}\n","/**\n * Cross-platform utilities for Linux, macOS, and Windows.\n * Centralizes all OS-specific logic so scanning tools work everywhere.\n */\n\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { execSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { checkReadOnly } from './allowlist.js';\nimport { logWarn } from './logger.js';\n\n// ── Platform detection ───────────────────────────────────────────────────────\n\nexport type Platform = 'linux' | 'darwin' | 'win32';\n\nexport const PLATFORM: Platform = process.platform as Platform;\nexport const IS_WIN = PLATFORM === 'win32';\nexport const IS_MAC = PLATFORM === 'darwin';\nexport const IS_LINUX = PLATFORM === 'linux';\nexport const HOME = homedir();\n\n// ── Shell selection ──────────────────────────────────────────────────────────\n\n/**\n * Returns the correct shell for execSync on each platform.\n * - Windows: PowerShell (pwsh if available, otherwise powershell.exe)\n * - macOS/Linux: /bin/sh\n */\nexport function platformShell(): string {\n if (!IS_WIN) return '/bin/sh';\n // Prefer pwsh (PowerShell 7+) over powershell.exe (5.1)\n try {\n execSync('pwsh -Version', { stdio: 'pipe', timeout: 3000 });\n return 'pwsh';\n } catch {\n return 'powershell.exe';\n }\n}\n\n/** Cached shell value (computed once) */\nlet _shell: string | undefined;\nexport function getShell(): string {\n if (!_shell) _shell = platformShell();\n return _shell;\n}\n\n// ── Cross-platform command runner ────────────────────────────────────────────\n\nexport interface RunOptions {\n timeout?: number;\n env?: NodeJS.ProcessEnv;\n}\n\n/**\n * Run a shell command, returning stdout as string. Returns '' on error.\n * Automatically uses the correct shell for the platform.\n */\n/** Safe environment variables — excludes secrets from child processes */\nconst SAFE_ENV_KEYS = [\n 'PATH', 'HOME', 'USER', 'LANG', 'LC_ALL', 'TERM', 'SHELL',\n 'USERPROFILE', 'LOCALAPPDATA', 'APPDATA', 'PROGRAMFILES',\n 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'XDG_RUNTIME_DIR',\n 'AWS_DEFAULT_REGION', 'AWS_PROFILE', 'AWS_CONFIG_FILE',\n 'KUBECONFIG', 'GOOGLE_APPLICATION_CREDENTIALS',\n 'AZURE_CONFIG_DIR',\n];\n\nexport function safeEnv(): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n for (const key of SAFE_ENV_KEYS) {\n if (process.env[key]) env[key] = process.env[key];\n }\n return env;\n}\n\nexport function run(cmd: string, opts: RunOptions = {}): string {\n // Defense in depth: never spawn a command that is not on the read-only allowlist,\n // regardless of where it originated (scanner template, agent, or MCP tool).\n const policy = checkReadOnly(cmd, { shell: IS_WIN ? 'powershell' : 'posix' });\n if (!policy.allowed) {\n logWarn(`Blocked non-read-only command: ${policy.reason}`);\n return '';\n }\n try {\n return execSync(cmd, {\n stdio: 'pipe',\n timeout: opts.timeout ?? 10_000,\n shell: getShell(),\n env: opts.env ?? safeEnv(),\n }).toString().trim();\n } catch {\n return '';\n }\n}\n\n// ── Command existence check (cross-platform `which`) ─────────────────────────\n\n/**\n * Check if a command exists. Returns its path or '' if not found.\n * - Unix: `which <cmd>`\n * - Windows: `Get-Command <cmd>` via PowerShell\n */\nexport function commandExists(cmd: string): string {\n if (IS_WIN) {\n const r = run(`Get-Command ${cmd} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source`, { timeout: 5000 });\n return r;\n }\n return run(`which ${cmd} 2>/dev/null`);\n}\n\n// ── Null device ──────────────────────────────────────────────────────────────\n\nexport const NULL_DEV = IS_WIN ? 'NUL' : '/dev/null';\n\n// ── App data directories ─────────────────────────────────────────────────────\n\n/** Returns the platform-specific user app data directory */\nexport function appDataDir(): string {\n if (IS_WIN) return process.env.LOCALAPPDATA ?? join(HOME, 'AppData', 'Local');\n if (IS_MAC) return join(HOME, 'Library', 'Application Support');\n return process.env.XDG_CONFIG_HOME ?? join(HOME, '.config');\n}\n\n/** Returns the platform-specific user data directory (broader than config) */\nexport function userDataDir(): string {\n if (IS_WIN) return process.env.APPDATA ?? join(HOME, 'AppData', 'Roaming');\n if (IS_MAC) return join(HOME, 'Library', 'Application Support');\n return process.env.XDG_DATA_HOME ?? join(HOME, '.local', 'share');\n}\n\n// ── Browser profile base paths (Chromium-based) ──────────────────────────────\n\nexport interface BrowserPaths {\n chrome: string;\n chromium: string;\n edge: string;\n brave: string;\n vivaldi: string;\n opera: string;\n}\n\nexport function browserBasePaths(): BrowserPaths {\n if (IS_WIN) {\n const local = process.env.LOCALAPPDATA ?? join(HOME, 'AppData', 'Local');\n return {\n chrome: join(local, 'Google', 'Chrome', 'User Data'),\n chromium: join(local, 'Chromium', 'User Data'),\n edge: join(local, 'Microsoft', 'Edge', 'User Data'),\n brave: join(local, 'BraveSoftware', 'Brave-Browser', 'User Data'),\n vivaldi: join(local, 'Vivaldi', 'User Data'),\n opera: join(userDataDir(), 'Opera Software', 'Opera Stable'),\n };\n }\n if (IS_MAC) {\n const lib = join(HOME, 'Library', 'Application Support');\n return {\n chrome: join(lib, 'Google', 'Chrome'),\n chromium: join(lib, 'Chromium'),\n edge: join(lib, 'Microsoft Edge'),\n brave: join(lib, 'BraveSoftware', 'Brave-Browser'),\n vivaldi: join(lib, 'Vivaldi'),\n opera: join(lib, 'com.operasoftware.Opera'),\n };\n }\n // Linux\n return {\n chrome: join(HOME, '.config', 'google-chrome'),\n chromium: join(HOME, '.config', 'chromium'),\n edge: join(HOME, '.config', 'microsoft-edge'),\n brave: join(HOME, '.config', 'BraveSoftware', 'Brave-Browser'),\n vivaldi: join(HOME, '.config', 'vivaldi'),\n opera: join(HOME, '.config', 'opera'),\n };\n}\n\n/** Firefox profile parent directories per platform */\nexport function firefoxBaseDirs(): string[] {\n if (IS_WIN) {\n const roaming = process.env.APPDATA ?? join(HOME, 'AppData', 'Roaming');\n return [join(roaming, 'Mozilla', 'Firefox', 'Profiles')];\n }\n if (IS_MAC) {\n return [join(HOME, 'Library', 'Application Support', 'Firefox', 'Profiles')];\n }\n // Linux: standard + snap + flatpak\n return [\n join(HOME, '.mozilla', 'firefox'),\n join(HOME, 'snap', 'firefox', 'common', '.mozilla', 'firefox'),\n join(HOME, '.var', 'app', 'org.mozilla.firefox', '.mozilla', 'firefox'),\n ];\n}\n\n// ── Database scan directories ────────────────────────────────────────────────\n\n/** Returns directories to search for SQLite/DB files per platform */\nexport function dbScanDirs(): string[] {\n const dirs: string[] = [];\n if (IS_WIN) {\n const local = process.env.LOCALAPPDATA ?? join(HOME, 'AppData', 'Local');\n const roaming = process.env.APPDATA ?? join(HOME, 'AppData', 'Roaming');\n dirs.push(local, roaming);\n const pd = join(HOME, 'AppData', 'Local', 'Programs');\n if (existsSync(pd)) dirs.push(pd);\n } else if (IS_MAC) {\n dirs.push(join(HOME, 'Library', 'Application Support'));\n if (existsSync('/var/lib')) dirs.push('/var/lib');\n } else {\n const configDir = join(HOME, '.config');\n const dataDir = join(HOME, '.local', 'share');\n if (existsSync(configDir)) dirs.push(configDir);\n if (existsSync(dataDir)) dirs.push(dataDir);\n if (existsSync('/var/lib')) dirs.push('/var/lib');\n }\n return dirs.filter(d => existsSync(d));\n}\n\n// ── File search (cross-platform find) ────────────────────────────────────────\n\n/**\n * Search for files matching glob patterns in given directories.\n * - Unix: `find` command\n * - Windows: PowerShell `Get-ChildItem`\n */\nexport function findFiles(dirs: string[], patterns: string[], maxDepth: number, limit: number): string {\n if (dirs.length === 0) return '';\n if (IS_WIN) {\n const includes = patterns.map(p => `'${p}'`).join(',');\n const pathList = dirs.map(d => `'${d}'`).join(',');\n return run(\n `Get-ChildItem -Path ${pathList} -Recurse -Depth ${maxDepth} -Include ${includes} -ErrorAction SilentlyContinue | Select-Object -First ${limit} -ExpandProperty FullName`,\n { timeout: 15_000 },\n );\n }\n const nameArgs = patterns.map(p => `-name \"${p}\"`).join(' -o ');\n const findCmds = dirs.map(d => `find \"${d}\" -maxdepth ${maxDepth} \\\\( ${nameArgs} \\\\) 2>/dev/null`).join('; ');\n return run(`{ ${findCmds}; } | head -${limit}`, { timeout: 15_000 });\n}\n\n// ── Network scanning ─────────────────────────────────────────────────────────\n\n/** Get all listening TCP ports and the processes behind them */\nexport function scanListeningPorts(): string {\n if (IS_WIN) {\n // PowerShell: Get-NetTCPConnection for listening ports + owning process\n return run(\n `Get-NetTCPConnection -State Listen -ErrorAction SilentlyContinue | ` +\n `ForEach-Object { $p = Get-Process -Id $_.OwningProcess -ErrorAction SilentlyContinue; ` +\n `\"$($_.LocalAddress):$($_.LocalPort) PID=$($_.OwningProcess) $($p.ProcessName)\" } | ` +\n `Sort-Object -Unique`,\n { timeout: 15_000 },\n );\n }\n if (IS_MAC) {\n // macOS: lsof is the most reliable way (ss not available)\n return run('sudo lsof -iTCP -sTCP:LISTEN -n -P 2>/dev/null || lsof -iTCP -sTCP:LISTEN -n -P 2>/dev/null', { timeout: 15_000 });\n }\n // Linux: ss is the standard tool\n return run('ss -tlnp 2>/dev/null', { timeout: 10_000 });\n}\n\n/** Get running processes (cross-platform) */\nexport function scanProcesses(): string {\n if (IS_WIN) {\n return run(\n `Get-Process | Select-Object -Property Id, ProcessName, Path | Format-Table -AutoSize | Out-String -Width 200`,\n { timeout: 15_000 },\n );\n }\n return run('ps aux 2>/dev/null', { timeout: 10_000 });\n}\n\n// ── Windows-specific: installed programs ─────────────────────────────────────\n\n/** Scan Windows registry for installed programs */\nexport function scanWindowsPrograms(): string {\n if (!IS_WIN) return '';\n return run(\n `$paths = @(` +\n `'HKLM:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*',` +\n `'HKLM:\\\\Software\\\\Wow6432Node\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*',` +\n `'HKCU:\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\*'` +\n `); Get-ItemProperty $paths -ErrorAction SilentlyContinue | ` +\n `Where-Object { $_.DisplayName } | ` +\n `Select-Object -Property DisplayName, Publisher, DisplayVersion | ` +\n `Sort-Object DisplayName | ` +\n `Format-Table -AutoSize | Out-String -Width 300`,\n { timeout: 20_000 },\n );\n}\n\n/** Scan Windows services for database engines */\nexport function scanWindowsDbServices(): string {\n if (!IS_WIN) return '';\n return run(\n `Get-Service | Where-Object { ` +\n `$_.Name -match 'postgres|mysql|mariadb|mongo|redis|MSSQL|elastic|clickhouse|cassandra' ` +\n `} | Select-Object Name, DisplayName, Status, StartType | Format-Table -AutoSize`,\n { timeout: 10_000 },\n );\n}\n\n// ── file:// URL helper ───────────────────────────────────────────────────────\n\n/** Generate a correct file:// URL for the current platform */\nexport function fileUrl(absPath: string): string {\n if (IS_WIN) {\n // Windows: file:///C:/Users/... (forward slashes, triple slash)\n const normalized = absPath.replace(/\\\\/g, '/');\n return `file:///${normalized}`;\n }\n return `file://${absPath}`;\n}\n","/**\n * Read-only command policy — a strict allowlist.\n *\n * Unlike a denylist (which is inherently leaky — novel destructive commands slip\n * through), this module permits only commands that are known to be read-only and\n * rejects everything else. It is the authoritative safety boundary for every\n * command the package spawns, independent of which agent or LLM is driving.\n *\n * The check is shell-aware: it splits a command line into segments on the control\n * operators `|`, `&&`, `||`, `;` (respecting single/double quotes), then validates\n * the leading executable of each segment plus its sub-command/arguments.\n */\n\n/** Plain read-only executables — no sub-command restriction needed. */\nconst READONLY_BINARIES = new Set<string>([\n // shell & text utilities\n 'echo', 'printf', 'true', 'false', 'test', 'cat', 'head', 'tail', 'grep', 'egrep',\n 'fgrep', 'awk', 'sed', 'cut', 'sort', 'uniq', 'wc', 'tr', 'xargs', 'tee',\n 'ls', 'find', 'which', 'command', 'type', 'basename', 'dirname', 'realpath',\n 'readlink', 'stat', 'file', 'printenv', 'date', 'hostname', 'uname',\n 'whoami', 'id', 'pwd', 'expr', 'seq', 'tac', 'rev', 'column', 'paste',\n // network & process inspection (read-only)\n 'ss', 'netstat', 'lsof', 'ps', 'ip', 'ifconfig', 'arp', 'dig', 'nslookup', 'host',\n // database clients (read-only usage is enforced separately for risky verbs)\n 'psql', 'mysql', 'mysqladmin', 'mongosh', 'redis-cli', 'sqlite3', 'pg_lsclusters',\n 'clickhouse-client',\n // macOS\n 'mdfind',\n]);\n\n/** `tee` is read-only only when writing to the bit bucket; otherwise it writes files. */\nconst CONDITIONAL_BINARIES = new Set<string>(['tee']);\n\n/** Package managers: list/query only — reject install/remove/upgrade-style verbs and flags. */\nconst PKG_MANAGERS = new Set<string>(['dpkg', 'rpm', 'snap', 'flatpak', 'brew', 'winget', 'choco', 'scoop', 'apt-cache']);\nconst MUTATING_PKG = /^(install|uninstall|reinstall|remove|purge|erase|upgrade|update|add|delete|pin|enable|disable|-i|--install|-r|--remove|-P|--purge|-e|--erase|-U|--upgrade|-F|--freshen)$/i;\n\n/** Executables that run another command supplied as an argument — must be validated recursively. */\nconst COMMAND_RUNNERS = new Set<string>(['xargs', 'env', 'nice', 'nohup', 'timeout', 'time', 'stdbuf', 'watch', 'sudo']);\n\n/** Mutating PowerShell cmdlets and Windows commands — rejected in PowerShell mode. */\nconst DANGEROUS_PS = /\\b(Remove-Item|Remove-ItemProperty|Move-Item|Copy-Item|Rename-Item|New-Item|New-Service|Set-Content|Add-Content|Clear-Content|Out-File|Set-ItemProperty|Set-Service|Stop-Process|Stop-Service|Start-Service|Restart-Service|Stop-Computer|Restart-Computer|Format-Volume|Clear-Disk|Remove-\\w+|Uninstall-\\w+|Install-\\w+|Set-\\w+|New-\\w+|Start-\\w+|Stop-\\w+|Restart-\\w+|Invoke-Expression|iex|Invoke-WebRequest|Invoke-RestMethod|Invoke-Command|Start-Process|Register-\\w+|Unregister-\\w+|Disable-\\w+|Enable-\\w+|Reset-\\w+|del|rmdir|rd)\\b/i;\n\n/** Coarse Unix destructive denylist — defense-in-depth backstop. */\nconst DANGEROUS_POSIX = /\\b(rm|rmdir|mv|dd|mkfs|chmod|chown|chgrp|kill|killall|pkill|reboot|shutdown|poweroff|halt|truncate|shred|fdisk|parted)\\b/i;\n\n/**\n * Multi-verb tools: the first non-flag token after the binary (and, for some, the\n * whole token list) must satisfy a read-only predicate.\n */\nconst SUBCOMMAND_RULES: Record<string, (tokens: string[]) => boolean> = {\n kubectl: (t) => allowFirstVerb(t, ['get', 'describe', 'top', 'logs', 'explain', 'config', 'version', 'cluster-info', 'api-resources', 'api-versions', 'auth']),\n docker: (t) => allowFirstVerb(t, ['ps', 'images', 'inspect', 'version', 'info', 'logs', 'stats', 'top', 'port', 'history', 'diff', 'system', 'context', 'volume', 'network', 'image', 'container']) && !hasMutatingDockerVerb(t),\n podman: (t) => SUBCOMMAND_RULES['docker']!(t),\n helm: (t) => allowFirstVerb(t, ['list', 'ls', 'status', 'get', 'show', 'history', 'version', 'repo', 'search', 'env']),\n systemctl: (t) => allowFirstVerb(t, ['status', 'show', 'list-units', 'list-unit-files', 'list-sockets', 'list-timers', 'list-dependencies', 'is-active', 'is-enabled', 'is-failed', 'cat', 'get-default', 'show-environment']),\n service: (t) => t.some((x) => /^status$/i.test(x)),\n // cloud CLIs: read-only actions only — must contain a read verb, never a mutating one\n aws: (t) => containsAwsReadAction(t) && !hasMutatingCloudVerb(t),\n gcloud: (t) => (hasToken(t, ['list', 'describe']) || isInfoOnly(t)) && !hasMutatingCloudVerb(t),\n az: (t) => (hasToken(t, ['list', 'show']) || isInfoOnly(t)) && !hasMutatingCloudVerb(t),\n // version control (read-only verbs only)\n git: (t) => allowFirstVerb(t, ['status', 'log', 'show', 'diff', 'branch', 'remote', 'config', 'rev-parse', 'ls-files', 'ls-remote', 'describe', 'tag', 'shortlog', 'cat-file', 'symbolic-ref']),\n gh: (t) => allowFirstVerb(t, ['repo', 'pr', 'issue', 'release', 'api', 'auth', 'status']) && hasToken(t, ['list', 'view', 'status', 'get']),\n};\n\n/** curl/wget: GET-only, no file writes, no request methods or bodies. */\nconst FETCH_RULES: Record<string, (tokens: string[]) => boolean> = {\n curl: (t) => !t.some((x) => /^-X$/i.test(x) || /^--request$/i.test(x) || /^-[dF]$/.test(x) || /^--data/i.test(x) || /^--form$/i.test(x) || /^-[oO]$/.test(x) || /^--output$/i.test(x) || /^--upload-file$/i.test(x)),\n wget: (t) => !t.some((x) => /^-O$/.test(x) || /^--output-document/i.test(x) || /^--post-data/i.test(x) || /^--method/i.test(x) || /^-i$/.test(x)),\n};\n\n/** Read-only PowerShell verbs (cmdlets are `Verb-Noun`). */\nconst READONLY_PS_VERBS = new Set<string>([\n 'get', 'select', 'where', 'measure', 'sort', 'format', 'out', 'convertto',\n 'convertfrom', 'compare', 'test', 'resolve', 'split', 'join', 'group', 'foreach',\n 'write', 'read', 'show', 'find', 'search', 'tee',\n]);\n\n/** Bare PowerShell helpers / aliases that are read-only. */\nconst READONLY_PS_BARE = new Set<string>(['where', 'select', 'sort', 'foreach', 'ft', 'fl', 'gci', 'gc', 'gm', 'gps', 'gsv', 'echo', 'write-host', 'write-output']);\n\nexport interface PolicyResult {\n allowed: boolean;\n reason?: string;\n}\n\nexport type ShellKind = 'posix' | 'powershell';\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction allowFirstVerb(tokens: string[], verbs: string[]): boolean {\n const verb = tokens.find((t) => !t.startsWith('-'));\n return verb !== undefined && verbs.includes(verb.toLowerCase());\n}\n\nfunction hasToken(tokens: string[], any: string[]): boolean {\n const lower = tokens.map((t) => t.toLowerCase());\n return any.some((a) => lower.includes(a));\n}\n\nfunction isInfoOnly(tokens: string[]): boolean {\n // e.g. `gcloud config list ...`, `az account show`\n return hasToken(tokens, ['config', 'account', 'version', 'info']);\n}\n\nconst MUTATING_CLOUD = /^(create|delete|update|put|set|add|remove|deploy|run|start|stop|restart|reboot|terminate|modify|attach|detach|associate|disassociate|enable|disable|invoke|exec|apply|destroy|scale|patch|register|deregister|import|copy|move|rename|reset|rotate|revoke|grant)([-_].*)?$/i;\n\nfunction hasMutatingCloudVerb(tokens: string[]): boolean {\n return tokens.some((t) => !t.startsWith('-') && MUTATING_CLOUD.test(t));\n}\n\nfunction containsAwsReadAction(tokens: string[]): boolean {\n // aws <service> <action> — action must be read-only\n return tokens.some((t) => /^(describe|list|get|lookup|search|scan|view|ls)[-_a-z0-9]*$/i.test(t) || t.toLowerCase() === 'ls');\n}\n\nfunction hasMutatingDockerVerb(tokens: string[]): boolean {\n return tokens.some((t) => /^(run|rm|rmi|exec|build|push|pull|start|stop|kill|create|commit|cp|save|load|tag|login|logout|prune|kill|restart|pause|unpause|rename|update|export|import)$/i.test(t));\n}\n\n/** Split a command line on shell control operators, honoring single/double quotes. */\nexport function splitSegments(cmd: string): string[] {\n const segments: string[] = [];\n let buf = '';\n let quote: '\"' | \"'\" | null = null;\n for (let i = 0; i < cmd.length; i++) {\n const c = cmd[i]!;\n const next = cmd[i + 1];\n if (quote) {\n buf += c;\n if (c === quote) quote = null;\n continue;\n }\n if (c === '\"' || c === \"'\") { quote = c; buf += c; continue; }\n // command substitution is rejected wholesale elsewhere; treat `$(` as a break too\n if ((c === '|' && next === '|') || (c === '&' && next === '&')) { segments.push(buf); buf = ''; i++; continue; }\n if (c === '|' || c === ';' || c === '\\n') { segments.push(buf); buf = ''; continue; }\n buf += c;\n }\n segments.push(buf);\n return segments.map((s) => s.trim()).filter(Boolean);\n}\n\n/** Tokenize one segment into words, honoring quotes and stripping them. */\nfunction tokenize(segment: string): string[] {\n const tokens: string[] = [];\n let buf = '';\n let quote: '\"' | \"'\" | null = null;\n let started = false;\n const push = () => { if (started) { tokens.push(buf); buf = ''; started = false; } };\n for (let i = 0; i < segment.length; i++) {\n const c = segment[i]!;\n if (quote) { if (c === quote) quote = null; else buf += c; started = true; continue; }\n if (c === '\"' || c === \"'\") { quote = c; started = true; continue; }\n if (c === ' ' || c === '\\t') { push(); continue; }\n buf += c; started = true;\n }\n push();\n return tokens;\n}\n\nfunction baseName(executable: string): string {\n const noPath = executable.split(/[\\\\/]/).pop() ?? executable;\n return noPath.toLowerCase();\n}\n\n/** `find` is read-only unless it is asked to execute or delete. */\nfunction findIsReadOnly(rest: string[]): boolean {\n return !rest.some((t) => /^-(exec|execdir|ok|okdir|delete|fprintf|fprint|fls)$/i.test(t));\n}\n\n/** Guard `awk`/`sed` programs against shelling out. */\nfunction awkSedIsReadOnly(exe: string, rest: string[]): boolean {\n const program = rest.join(' ');\n if (exe === 'awk') return !/\\bsystem\\s*\\(/.test(program) && !/\\|\\s*[\"']/.test(program) && !/print\\s*>/.test(program);\n // sed: reject the `e` (execute) command and the s///e flag and `w` (write file)\n return !/(^|;|\\{|\\s)e\\b/.test(program) && !/s[^\\s]*\\/[a-z]*e[a-z]*\\b/i.test(program) && !/\\bw\\s+\\S/.test(program);\n}\n\nfunction isWriteRedirect(segment: string): boolean {\n // Allow only redirects to the bit bucket / stderr merge: 2>/dev/null, >/dev/null, 2>&1, *> $null, Out-Null\n // Reject any other `>` or `>>` (file writes).\n const stripped = segment\n .replace(/\\d?>>?\\s*\\/dev\\/null/g, '')\n .replace(/\\d?>\\s*&\\s*\\d/g, '')\n .replace(/\\d?>\\s*\\$null/gi, '');\n return /(^|[^0-9&])>>?/.test(stripped);\n}\n\n// ── Public API ─────────────────────────────────────────────────────────────────\n\n/**\n * Decide whether a command line is read-only and therefore safe to execute.\n * Returns `{ allowed: false, reason }` for anything not explicitly permitted.\n */\nexport function checkReadOnly(command: string, opts: { shell?: ShellKind } = {}): PolicyResult {\n const cmd = command.trim();\n if (!cmd) return { allowed: false, reason: 'empty command' };\n\n // PowerShell uses `$(...)`, `;`-in-blocks and `{}` legitimately, so a POSIX parser\n // would mis-fire. In PowerShell mode we reject file writes and mutating cmdlets instead.\n if (opts.shell === 'powershell') {\n if (isWriteRedirect(cmd)) return { allowed: false, reason: 'file-writing redirect is not allowed' };\n if (DANGEROUS_PS.test(cmd)) return { allowed: false, reason: 'mutating PowerShell cmdlet is not allowed' };\n if (DANGEROUS_POSIX.test(cmd)) return { allowed: false, reason: 'destructive command is not allowed' };\n return { allowed: true };\n }\n\n // Reject command substitution and backticks — they hide arbitrary execution.\n if (/\\$\\(|`/.test(cmd)) return { allowed: false, reason: 'command substitution is not allowed' };\n\n // Reject file-writing redirects (anything other than /dev/null or stderr merge).\n if (isWriteRedirect(cmd)) return { allowed: false, reason: 'file-writing redirect is not allowed' };\n\n for (const segment of splitSegments(cmd)) {\n const r = checkSegment(segment);\n if (!r.allowed) return r;\n }\n\n return { allowed: true };\n}\n\n/** Validate a single pipeline segment's leading executable and its arguments. */\nfunction checkSegment(segment: string): PolicyResult {\n // Drop leading inline env assignments (`FOO=bar cmd`) and shell grouping tokens (`{ } ( )`).\n let tokens = tokenize(segment)\n .filter((t) => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(t))\n .filter((t) => t !== '{' && t !== '}' && t !== '(' && t !== ')');\n if (tokens.length === 0) return { allowed: true };\n\n let exe = baseName(tokens[0]!);\n let rest = tokens.slice(1);\n\n // Command runners (xargs, timeout, nice, env, ...): unwrap to the inner command and validate it.\n while (COMMAND_RUNNERS.has(exe)) {\n // skip the runner's own flags and their values, plus xargs replace-string (-I {})\n const inner: string[] = [];\n let i = 0;\n for (; i < rest.length; i++) {\n const t = rest[i]!;\n if (t.startsWith('-')) { if (/^-(I|n|L|P|d|s|E|u|g)$/.test(t)) i++; continue; }\n inner.push(...rest.slice(i));\n break;\n }\n if (inner.length === 0) return { allowed: true }; // runner with no inner command (e.g. `env`, `xargs echo`-less)\n exe = baseName(inner[0]!);\n rest = inner.slice(1);\n }\n\n if (exe === 'find') {\n if (!findIsReadOnly(rest)) return { allowed: false, reason: 'find: -exec/-delete is not allowed' };\n return { allowed: true };\n }\n if (exe === 'awk' || exe === 'sed') {\n if (!awkSedIsReadOnly(exe, rest)) return { allowed: false, reason: `${exe}: program may not shell out or write files` };\n return { allowed: true };\n }\n if (PKG_MANAGERS.has(exe)) {\n if (rest.some((t) => MUTATING_PKG.test(t))) return { allowed: false, reason: `${exe}: only list/query sub-commands are allowed` };\n return { allowed: true };\n }\n if (FETCH_RULES[exe]) {\n if (!FETCH_RULES[exe]!(rest)) return { allowed: false, reason: `${exe}: only read-only GET requests are allowed` };\n return { allowed: true };\n }\n if (SUBCOMMAND_RULES[exe]) {\n if (!SUBCOMMAND_RULES[exe]!(rest)) return { allowed: false, reason: `${exe}: sub-command is not read-only` };\n return { allowed: true };\n }\n if (CONDITIONAL_BINARIES.has(exe)) {\n if (rest.some((t) => !t.startsWith('-') && t !== '/dev/null')) return { allowed: false, reason: 'tee may only write to /dev/null' };\n return { allowed: true };\n }\n if (READONLY_BINARIES.has(exe)) return { allowed: true };\n if (READONLY_PS_BARE.has(exe)) return { allowed: true };\n\n // PowerShell cmdlet fallback: Verb-Noun where the verb must be read-only.\n if (exe.includes('-') && /^[a-z]+-[a-z]/.test(exe)) {\n const verb = exe.split('-')[0]!;\n if (READONLY_PS_VERBS.has(verb)) return { allowed: true };\n return { allowed: false, reason: `PowerShell cmdlet not read-only: ${exe}` };\n }\n\n return { allowed: false, reason: `command not on read-only allowlist: ${exe}` };\n}\n\n/** Convenience boolean form. */\nexport function isReadOnlyCommand(command: string): boolean {\n return checkReadOnly(command).allowed;\n}\n\n/** Throwing form for guard sites that prefer exceptions. */\nexport function assertReadOnly(command: string): void {\n const r = checkReadOnly(command);\n if (!r.allowed) throw new Error(`Blocked by read-only allowlist: ${r.reason}`);\n}\n","/**\n * Structured logging for enterprise observability.\n * Outputs JSON to stderr for compatibility with ELK, Datadog, Splunk, CloudWatch.\n */\n\nexport type LogLevel = 'DEBUG' | 'INFO' | 'WARN' | 'ERROR';\n\nexport interface LogEntry {\n timestamp: string;\n level: LogLevel;\n message: string;\n context?: Record<string, unknown>;\n}\n\nlet verboseMode = false;\n\nexport function setVerbose(v: boolean): void {\n verboseMode = v;\n}\n\nexport function log(level: LogLevel, message: string, context?: Record<string, unknown>): void {\n if (level === 'DEBUG' && !verboseMode) return;\n\n const entry: LogEntry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n ...(context && Object.keys(context).length > 0 ? { context } : {}),\n };\n process.stderr.write(JSON.stringify(entry) + '\\n');\n}\n\nexport function logDebug(message: string, context?: Record<string, unknown>): void {\n log('DEBUG', message, context);\n}\n\nexport function logInfo(message: string, context?: Record<string, unknown>): void {\n log('INFO', message, context);\n}\n\nexport function logWarn(message: string, context?: Record<string, unknown>): void {\n log('WARN', message, context);\n}\n\nexport function logError(message: string, context?: Record<string, unknown>): void {\n log('ERROR', message, context);\n}\n","import type { Scanner, ScanResult } from './types.js';\nimport type { DiscoveryNode } from '../types.js';\nimport { scanAllBookmarks } from '../bookmarks.js';\n\n/** Hostname substrings that indicate a personal site — never catalogued. */\nconst PERSONAL = [\n 'facebook.', 'instagram.', 'twitter.', 'x.com', 'tiktok.', 'reddit.', 'youtube.', 'netflix.',\n 'spotify.', 'twitch.', 'pinterest.', 'snapchat.', 'whatsapp.', 'amazon.', 'ebay.', 'aliexpress.',\n 'cnn.', 'bbc.', 'nytimes.', 'espn.', 'booking.', 'airbnb.', 'tripadvisor.', 'wikipedia.',\n];\n\n/** Well-known business/SaaS hostnames → catalogued as saas_tool. */\nconst BUSINESS = [\n 'github.', 'gitlab.', 'bitbucket.', 'atlassian.', 'jira.', 'confluence.', 'notion.', 'linear.',\n 'slack.', 'zoom.', 'figma.', 'miro.', 'vercel.', 'netlify.', 'heroku.', 'datadog', 'sentry.',\n 'grafana.', 'pagerduty.', 'aws.amazon.', 'console.cloud.google', 'portal.azure', 'cloudflare.',\n 'hubspot.', 'salesforce.', 'stripe.', 'twilio.', 'sendgrid.', 'mailchimp.', 'segment.', 'mixpanel.',\n 'amplitude.', 'looker.', 'tableau.', 'snowflake.', 'databricks.', 'mongodb.', 'redis.', 'elastic.',\n 'openai.', 'anthropic.', 'huggingface.', 'docker.', 'npmjs.', 'pypi.', 'circleci.', 'travis-ci.',\n 'jenkins.', 'terraform.', 'hashicorp.', 'okta.', 'auth0.', '1password.', 'asana.', 'trello.', 'monday.',\n];\n\nfunction classify(hostname: string): { type: DiscoveryNode['type']; confidence: number } | null {\n const h = hostname.toLowerCase();\n if (PERSONAL.some((p) => h.includes(p))) return null;\n if (BUSINESS.some((b) => h.includes(b))) return { type: 'saas_tool', confidence: 0.7 };\n // Internal/custom hosts (IPs or *.company.tld with non-standard ports) → web_service\n if (/^\\d+\\.\\d+\\.\\d+\\.\\d+$/.test(h) || /\\.(internal|local|corp|lan)\\b/.test(h)) {\n return { type: 'web_service', confidence: 0.6 };\n }\n return null; // unknown public host — leave for LLM-driven classification\n}\n\nexport const bookmarksScanner: Scanner = {\n id: 'bookmarks',\n title: 'Browser bookmarks',\n platforms: 'all',\n detect: () => true,\n async scan(): Promise<ScanResult> {\n const hosts = await scanAllBookmarks();\n const seen = new Set<string>();\n const nodes: DiscoveryNode[] = [];\n for (const host of hosts) {\n const klass = classify(host.hostname);\n if (!klass) continue;\n const id = `${klass.type}:${host.hostname}`;\n if (seen.has(id)) continue;\n seen.add(id);\n nodes.push({\n id, type: klass.type, name: host.hostname, discoveredVia: 'bookmark',\n confidence: klass.confidence, tags: ['bookmark'],\n metadata: { protocol: host.protocol, ...(host.port ? { port: host.port } : {}) },\n });\n }\n return { nodes, edges: [] };\n },\n};\n","import type { Scanner, ScanResult } from './types.js';\nimport type { DiscoveryNode } from '../types.js';\nimport { commandExists } from '../platform.js';\n\n/** Known CLIs/tools grouped by category — detected deterministically via `commandExists`. */\nconst KNOWN_TOOLS: Record<string, string[]> = {\n ide: ['code', 'code-insiders', 'cursor', 'windsurf', 'zed', 'nvim', 'vim', 'emacs', 'idea', 'webstorm', 'pycharm', 'goland', 'datagrip', 'clion', 'rider', 'phpstorm'],\n 'dev-tool': ['git', 'gh', 'docker', 'docker-compose', 'podman', 'kubectl', 'helm', 'terraform', 'ansible', 'vagrant', 'packer', 'consul', 'vault', 'nomad'],\n runtime: ['node', 'npm', 'pnpm', 'yarn', 'bun', 'deno', 'python', 'python3', 'pip', 'poetry', 'ruby', 'rails', 'java', 'mvn', 'gradle', 'go', 'cargo', 'rustc', 'php', 'composer', 'dotnet'],\n database: ['psql', 'mysql', 'mongosh', 'redis-cli', 'sqlite3', 'clickhouse-client'],\n cloud: ['aws', 'gcloud', 'az', 'heroku', 'fly', 'vercel', 'netlify', 'wrangler', 'supabase'],\n browser: ['google-chrome', 'chromium', 'firefox', 'brave', 'opera'],\n observability: ['prometheus', 'grafana-cli', 'datadog-agent', 'newrelic-agent'],\n};\n\nexport const installedAppsScanner: Scanner = {\n id: 'installed-apps',\n title: 'Installed apps & developer tools',\n platforms: 'all',\n allowedCommands: ['which', 'command', 'Get-Command'],\n detect: () => true,\n async scan(ctx): Promise<ScanResult> {\n const nodes: DiscoveryNode[] = [];\n const hintTerms = (ctx.hint ?? '').toLowerCase().split(/[\\s,]+/).filter(Boolean);\n for (const [category, tools] of Object.entries(KNOWN_TOOLS)) {\n for (const tool of tools) {\n const path = commandExists(tool);\n if (!path) continue;\n const boosted = hintTerms.some((t) => tool.includes(t));\n nodes.push({\n id: `saas_tool:${tool}`,\n type: 'saas_tool',\n name: tool,\n discoveredVia: 'installed-app',\n confidence: boosted ? 0.95 : 0.9,\n tags: [category],\n metadata: { category, path },\n });\n }\n }\n return { nodes, edges: [] };\n },\n};\n","import type { Scanner, ScanResult } from './types.js';\nimport type { DiscoveryNode, NodeType } from '../types.js';\nimport { scanListeningPorts } from '../platform.js';\n\n/** Well-known listening ports → node type + service name. */\nconst PORT_MAP: Record<number, { type: NodeType; service: string }> = {\n 5432: { type: 'database_server', service: 'postgresql' },\n 3306: { type: 'database_server', service: 'mysql' },\n 1433: { type: 'database_server', service: 'sqlserver' },\n 27017: { type: 'database_server', service: 'mongodb' },\n 9200: { type: 'database_server', service: 'elasticsearch' },\n 6379: { type: 'cache_server', service: 'redis' },\n 11211: { type: 'cache_server', service: 'memcached' },\n 9092: { type: 'message_broker', service: 'kafka' },\n 5672: { type: 'message_broker', service: 'rabbitmq' },\n 4222: { type: 'message_broker', service: 'nats' },\n 9090: { type: 'web_service', service: 'prometheus' },\n 3000: { type: 'web_service', service: 'http-app' },\n 8080: { type: 'web_service', service: 'http-app' },\n 8000: { type: 'web_service', service: 'http-app' },\n 80: { type: 'web_service', service: 'http' },\n 443: { type: 'web_service', service: 'https' },\n 8200: { type: 'web_service', service: 'vault' },\n 8500: { type: 'web_service', service: 'consul' },\n 2379: { type: 'web_service', service: 'etcd' },\n 5601: { type: 'web_service', service: 'kibana' },\n 15672: { type: 'web_service', service: 'rabbitmq-management' },\n};\n\n/** Extract distinct listening port numbers from ss/lsof/PowerShell output. */\nexport function extractListeningPorts(raw: string): number[] {\n const ports = new Set<number>();\n for (const m of raw.matchAll(/[:.](\\d{2,5})\\b/g)) {\n const p = Number(m[1]);\n if (p in PORT_MAP) ports.add(p);\n }\n return [...ports];\n}\n\nexport const portsScanner: Scanner = {\n id: 'local-ports',\n title: 'Local listening ports',\n platforms: 'all',\n allowedCommands: ['ss', 'lsof', 'Get-NetTCPConnection'],\n detect: () => true,\n async scan(): Promise<ScanResult> {\n const raw = scanListeningPorts();\n const nodes: DiscoveryNode[] = [];\n for (const port of extractListeningPorts(raw)) {\n const { type, service } = PORT_MAP[port]!;\n nodes.push({\n id: `${type}:localhost:${port}`,\n type,\n name: `${service} (:${port})`,\n discoveredVia: 'listening-port',\n confidence: 0.9,\n tags: ['local', service],\n metadata: { port, service, host: 'localhost' },\n });\n }\n return { nodes, edges: [] };\n },\n};\n","import { ScannerRegistry } from './types.js';\nimport { bookmarksScanner } from './bookmarks.js';\nimport { installedAppsScanner } from './installed-apps.js';\nimport { portsScanner } from './ports.js';\n\nexport { ScannerRegistry } from './types.js';\nexport type { Scanner, ScanContext, ScanResult } from './types.js';\nexport { bookmarksScanner } from './bookmarks.js';\nexport { installedAppsScanner } from './installed-apps.js';\nexport { portsScanner, extractListeningPorts } from './ports.js';\n\n/** A registry pre-loaded with the built-in deterministic scanners. */\nexport function defaultRegistry(): ScannerRegistry {\n return new ScannerRegistry()\n .register(bookmarksScanner)\n .register(installedAppsScanner)\n .register(portsScanner);\n}\n","/**\n * Deterministic, LLM-free local discovery.\n *\n * Runs every applicable scanner from a {@link ScannerRegistry}, deduplicates and\n * persists the resulting nodes/edges, and returns counts. This is what powers the\n * MCP `run_discovery` tool without requiring any model — the host LLM can then\n * enrich the catalog via the read tools. (The Claude-driven loop in agent.ts\n * remains available as the optional, richer turnkey path.)\n */\n\nimport type { CartographyDB } from '../db.js';\nimport type { DiscoveryNode, DiscoveryEdge } from '../types.js';\nimport { PLATFORM, run } from '../platform.js';\nimport { defaultRegistry, ScannerRegistry } from '../scanners/registry.js';\nimport type { ScanContext } from '../scanners/types.js';\n\nexport interface LocalDiscoveryOptions {\n hint?: string;\n registry?: ScannerRegistry;\n /** Called after each scanner with a short progress line. */\n onProgress?: (line: string) => void;\n}\n\nexport async function runLocalDiscovery(\n db: CartographyDB,\n sessionId: string,\n opts: LocalDiscoveryOptions = {},\n): Promise<{ nodes: number; edges: number; scanners: string[] }> {\n const registry = opts.registry ?? defaultRegistry();\n const ctx: ScanContext = { hint: opts.hint, platform: PLATFORM, run };\n\n const nodes = new Map<string, DiscoveryNode>();\n const edges: DiscoveryEdge[] = [];\n const ran: string[] = [];\n\n for (const scanner of registry.forPlatform(PLATFORM)) {\n try {\n if (!(await scanner.detect(ctx))) continue;\n const result = await scanner.scan(ctx);\n ran.push(scanner.id);\n for (const node of result.nodes) {\n // Keep the highest-confidence record on id collision.\n const prev = nodes.get(node.id);\n if (!prev || node.confidence > prev.confidence) nodes.set(node.id, node);\n }\n edges.push(...result.edges);\n opts.onProgress?.(`${scanner.title}: +${result.nodes.length} nodes`);\n } catch (err) {\n opts.onProgress?.(`${scanner.title}: failed (${err instanceof Error ? err.message : String(err)})`);\n }\n }\n\n for (const node of nodes.values()) db.upsertNode(sessionId, node);\n // Only persist edges whose endpoints exist.\n for (const edge of edges) {\n if (nodes.has(edge.sourceId) && nodes.has(edge.targetId)) db.insertEdge(sessionId, edge);\n }\n\n return { nodes: nodes.size, edges: edges.length, scanners: ran };\n}\n\n/** Adapter matching the MCP `DiscoveryFn` signature. */\nexport function localDiscoveryFn(registry?: ScannerRegistry) {\n return async (db: CartographyDB, sessionId: string, opts: { hint?: string }) => {\n const r = await runLocalDiscovery(db, sessionId, { hint: opts.hint, registry });\n return { nodes: r.nodes, edges: r.edges };\n };\n}\n","/** FNV-1a (32-bit) — the shared content hash used by the embeddings and vector store. */\nexport function fnv1a(s: string): number {\n let h = 0x811c9dc5;\n for (let i = 0; i < s.length; i++) {\n h ^= s.charCodeAt(i);\n h = Math.imul(h, 0x01000193);\n }\n return h >>> 0;\n}\n","/**\n * Embedding providers for semantic search.\n *\n * The default provider runs a small sentence-transformer locally via\n * `@huggingface/transformers` (no API key, offline after first download), keeping\n * the package LLM-agnostic. Everything is a lazy import so installs that never use\n * semantic search pay no cost and need no native model.\n */\n\nimport { fnv1a } from './hash.js';\n\n/** Produces fixed-dimension embeddings for a batch of texts. */\nexport interface EmbeddingProvider {\n readonly id: string;\n readonly dimensions: number;\n embed(texts: string[]): Promise<Float32Array[]>;\n}\n\n/**\n * Local sentence-transformer embedder (Xenova/all-MiniLM-L6-v2, 384 dims).\n * Returns `undefined` if `@huggingface/transformers` is not installed or the\n * model cannot be loaded, so callers can fall back to lexical search.\n */\nexport async function createLocalEmbedder(\n model = 'Xenova/all-MiniLM-L6-v2',\n): Promise<EmbeddingProvider | undefined> {\n try {\n // Cast the specifier to defeat static module resolution: the type build must\n // not require this optional native dep to be installed. We assert the minimal\n // surface we use instead — the runtime import is unchanged.\n const tf = (await import('@huggingface/transformers' as string)) as {\n pipeline(\n task: string,\n model: string,\n ): Promise<(text: string, opts: { pooling: string; normalize: boolean }) => Promise<{ data: unknown }>>;\n };\n const extractor = await tf.pipeline('feature-extraction', model);\n return {\n id: `local:${model}`,\n dimensions: 384,\n async embed(texts: string[]): Promise<Float32Array[]> {\n const out: Float32Array[] = [];\n for (const text of texts) {\n const tensor = await extractor(text, { pooling: 'mean', normalize: true });\n out.push(Float32Array.from(tensor.data as Iterable<number>));\n }\n return out;\n },\n };\n } catch {\n return undefined;\n }\n}\n\n/**\n * A deterministic, dependency-free hashing embedder (bag-of-character-ngrams).\n * Not as good as a transformer, but offline, instant, and useful as a fallback\n * and for tests. Produces L2-normalized vectors.\n */\nexport function createHashEmbedder(dimensions = 256): EmbeddingProvider {\n return {\n id: `hash:${dimensions}`,\n dimensions,\n async embed(texts: string[]): Promise<Float32Array[]> {\n return texts.map((text) => hashEmbed(text, dimensions));\n },\n };\n}\n\nfunction hashEmbed(text: string, dim: number): Float32Array {\n const v = new Float32Array(dim);\n const tokens = text.toLowerCase().match(/[a-z0-9]+/g) ?? [];\n for (const tok of tokens) {\n // hash the token and its char trigrams into buckets\n for (const gram of [tok, ...trigrams(tok)]) {\n const h = fnv1a(gram);\n v[h % dim] += 1;\n }\n }\n let norm = 0;\n for (const x of v) norm += x * x;\n norm = Math.sqrt(norm) || 1;\n for (let i = 0; i < dim; i++) v[i] = v[i]! / norm;\n return v;\n}\n\nfunction trigrams(s: string): string[] {\n if (s.length < 3) return [];\n const out: string[] = [];\n for (let i = 0; i <= s.length - 3; i++) out.push(s.slice(i, i + 3));\n return out;\n}\n","/**\n * Vector store backed by `sqlite-vec`. Stores one embedding per node in a `vec0`\n * virtual table living inside the same SQLite catalog, with incremental indexing\n * (content-hashed) so re-runs only embed what changed.\n */\n\nimport type { CartographyDB } from '../db.js';\nimport type { NodeRow } from '../types.js';\nimport type { EmbeddingProvider } from './embeddings.js';\nimport { fnv1a } from './hash.js';\n\n/** Text used to represent a node for embedding. */\nexport function nodeText(n: NodeRow): string {\n const desc = typeof n.metadata?.['description'] === 'string' ? (n.metadata['description'] as string) : '';\n const category = typeof n.metadata?.['category'] === 'string' ? (n.metadata['category'] as string) : '';\n return [n.name, n.id.replace(/[:_]/g, ' '), `type ${n.type}`, n.domain ?? '', n.subDomain ?? '', category, n.tags.join(' '), desc]\n .filter(Boolean).join(' — ');\n}\n\nfunction hash(s: string): string {\n return fnv1a(s).toString(16);\n}\n\nfunction toBuffer(v: Float32Array): Buffer {\n return Buffer.from(v.buffer, v.byteOffset, v.byteLength);\n}\n\nexport class VectorStore {\n private loaded = false;\n\n constructor(private db: CartographyDB, private embedder: EmbeddingProvider) {}\n\n /** Load sqlite-vec and ensure the schema exists. Returns false if unavailable. */\n async init(): Promise<boolean> {\n if (this.loaded) return true;\n try {\n const conn = this.db.rawConnection();\n // Cast the specifier so the type build doesn't require this optional native\n // dep to be installed; runtime import is unchanged.\n const sqliteVec = (await import('sqlite-vec' as string)) as { load(db: unknown): void };\n sqliteVec.load(conn);\n conn.exec(`\n CREATE TABLE IF NOT EXISTS vec_index (\n rowid INTEGER PRIMARY KEY AUTOINCREMENT,\n session_id TEXT NOT NULL,\n node_id TEXT NOT NULL,\n hash TEXT NOT NULL,\n UNIQUE(session_id, node_id)\n );\n CREATE TABLE IF NOT EXISTS vec_meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);\n `);\n // Recreate the vector table if the embedding dimensions changed.\n const dimRow = conn.prepare(\"SELECT value FROM vec_meta WHERE key = 'dims'\").get() as { value: string } | undefined;\n const dims = this.embedder.dimensions;\n if (dimRow && Number(dimRow.value) !== dims) {\n conn.exec('DROP TABLE IF EXISTS vec_nodes; DELETE FROM vec_index;');\n }\n conn.exec(`CREATE VIRTUAL TABLE IF NOT EXISTS vec_nodes USING vec0(embedding float[${dims}])`);\n conn.prepare('INSERT OR REPLACE INTO vec_meta(key, value) VALUES (?, ?)').run('dims', String(dims));\n conn.prepare('INSERT OR REPLACE INTO vec_meta(key, value) VALUES (?, ?)').run('embedder', this.embedder.id);\n this.loaded = true;\n return true;\n } catch {\n return false;\n }\n }\n\n /** Incrementally embed and index any new/changed nodes for a session. */\n async index(sessionId: string): Promise<{ embedded: number; total: number }> {\n if (!(await this.init())) return { embedded: 0, total: 0 };\n const conn = this.db.rawConnection();\n const nodes = this.db.getNodes(sessionId);\n\n const getRow = conn.prepare('SELECT rowid, hash FROM vec_index WHERE session_id = ? AND node_id = ?');\n const insIndex = conn.prepare('INSERT INTO vec_index (session_id, node_id, hash) VALUES (?, ?, ?)');\n const updHash = conn.prepare('UPDATE vec_index SET hash = ? WHERE rowid = ?');\n const delVec = conn.prepare('DELETE FROM vec_nodes WHERE rowid = ?');\n const insVec = conn.prepare('INSERT INTO vec_nodes (rowid, embedding) VALUES (?, ?)');\n\n const pending: Array<{ rowid: bigint; text: string }> = [];\n for (const n of nodes) {\n const text = nodeText(n);\n const h = hash(`${this.embedder.id}:${text}`);\n const existing = getRow.get(sessionId, n.id) as { rowid: number; hash: string } | undefined;\n if (existing) {\n if (existing.hash === h) continue;\n updHash.run(h, existing.rowid);\n delVec.run(BigInt(existing.rowid));\n pending.push({ rowid: BigInt(existing.rowid), text });\n } else {\n const info = insIndex.run(sessionId, n.id, h);\n pending.push({ rowid: BigInt(info.lastInsertRowid as number), text });\n }\n }\n\n if (pending.length > 0) {\n const vectors = await this.embedder.embed(pending.map((p) => p.text));\n const tx = conn.transaction(() => {\n pending.forEach((p, i) => insVec.run(p.rowid, toBuffer(vectors[i]!)));\n });\n tx();\n }\n return { embedded: pending.length, total: nodes.length };\n }\n\n /** k-nearest-neighbour search within a session. Returns node ids + distances. */\n async search(sessionId: string, query: string, k: number): Promise<Array<{ nodeId: string; distance: number }>> {\n if (!(await this.init())) return [];\n await this.index(sessionId);\n const conn = this.db.rawConnection();\n const [qv] = await this.embedder.embed([query]);\n if (!qv) return [];\n\n // Pure-vec KNN scan (most compatible form), then map rowids and filter by\n // session in JS. Over-fetch so cross-session neighbours don't crowd out hits.\n const overfetch = Math.max(k * 5, k);\n const knn = conn.prepare(\n 'SELECT rowid, distance FROM vec_nodes WHERE embedding MATCH ? ORDER BY distance LIMIT ?',\n ).all(toBuffer(qv), overfetch) as Array<{ rowid: number; distance: number }>;\n\n const meta = conn.prepare('SELECT node_id AS nodeId, session_id AS sessionId FROM vec_index WHERE rowid = ?');\n const out: Array<{ nodeId: string; distance: number }> = [];\n for (const row of knn) {\n const m = meta.get(row.rowid) as { nodeId: string; sessionId: string } | undefined;\n if (m && m.sessionId === sessionId) out.push({ nodeId: m.nodeId, distance: row.distance });\n if (out.length >= k) break;\n }\n return out;\n }\n}\n","/**\n * Semantic search backend for the MCP server. Wraps a {@link VectorStore} and\n * degrades gracefully to lexical search when embeddings/sqlite-vec are unavailable\n * or return nothing.\n */\n\nimport type { CartographyDB } from '../db.js';\nimport type { NodeRow } from '../types.js';\nimport type { SearchFn } from '../mcp/server.js';\nimport type { EmbeddingProvider } from './embeddings.js';\nimport { createLocalEmbedder } from './embeddings.js';\nimport { VectorStore } from './store.js';\n\nconst lexical = (db: CartographyDB, sessionId: string, query: string, opts: { types?: readonly string[]; limit: number }) =>\n db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));\n\n/** A lexical-only {@link SearchFn} — the fallback when no embedder/vector store is available. */\nconst lexicalSearch = (): SearchFn => async (d, sid, q, opts) => lexical(d, sid, q, opts);\n\n/**\n * Build a {@link SearchFn} that prefers semantic (vector) search and falls back to\n * lexical. Pass an explicit embedder, or let it lazily load the local transformer\n * (returns a lexical-only function if none is available).\n */\nexport async function createSemanticSearch(\n db: CartographyDB,\n embedder?: EmbeddingProvider,\n): Promise<SearchFn> {\n const provider = embedder ?? (await createLocalEmbedder());\n if (!provider) return lexicalSearch();\n const store = new VectorStore(db, provider);\n const ok = await store.init();\n if (!ok) return lexicalSearch();\n\n return async (d, sid, query, opts): Promise<Array<{ node: NodeRow; score?: number }>> => {\n const hits = await store.search(sid, query, opts.limit);\n if (hits.length === 0) return lexical(d, sid, query, opts);\n // Materialize only the hit nodes, not the whole session.\n const byId = d.getNodesByIds(sid, hits.map((h) => h.nodeId));\n const results: Array<{ node: NodeRow; score?: number }> = [];\n for (const h of hits) {\n const node = byId.get(h.nodeId);\n if (!node) continue; // vector outlived its node (deleted since last index)\n if (opts.types && opts.types.length > 0 && !opts.types.includes(node.type)) continue;\n // cosine distance → similarity score in [0,1]\n results.push({ node, score: Math.max(0, 1 - h.distance / 2) });\n }\n return results.length > 0 ? results : lexical(d, sid, query, opts);\n };\n}\n\nexport { VectorStore } from './store.js';\nexport { createLocalEmbedder, createHashEmbedder } from './embeddings.js';\nexport type { EmbeddingProvider } from './embeddings.js';\n","import { z } from 'zod';\nimport type { CartographyDB } from './db.js';\nimport { NODE_TYPES, EDGE_RELATIONSHIPS } from './types.js';\nimport { scanAllBookmarks, scanAllHistory } from './bookmarks.js';\nimport { logDebug } from './logger.js';\nimport {\n IS_WIN, IS_MAC, IS_LINUX, HOME, PLATFORM,\n run, commandExists, findFiles, dbScanDirs,\n scanListeningPorts, scanProcesses,\n scanWindowsPrograms, scanWindowsDbServices,\n} from './platform.js';\n\n// Type-only import — no runtime dependency on SDK at module parse time\nimport type { McpServerConfig } from '@anthropic-ai/claude-agent-sdk';\n\n/**\n * Circuit breaker for sequential CLI scans.\n * After `threshold` consecutive failures, remaining commands are skipped.\n */\nexport function createScanRunner(\n runFn: (cmd: string, opts?: { timeout?: number; env?: NodeJS.ProcessEnv }) => string,\n opts: { timeout?: number; env?: NodeJS.ProcessEnv; threshold?: number } = {},\n) {\n const threshold = opts.threshold ?? 3;\n let consecutiveFailures = 0;\n let tripped = false;\n\n return (cmd: string): string => {\n if (tripped) {\n logDebug(`Circuit breaker: skipping \"${cmd}\" (${consecutiveFailures} consecutive failures)`);\n return '(skipped — circuit breaker: too many consecutive failures)';\n }\n const result = runFn(cmd, { timeout: opts.timeout ?? 20_000, env: opts.env });\n if (!result) {\n consecutiveFailures++;\n if (consecutiveFailures >= threshold) {\n tripped = true;\n logDebug(`Circuit breaker tripped after ${threshold} failures, last command: \"${cmd}\"`);\n }\n return '(error or not available)';\n }\n consecutiveFailures = 0;\n return result;\n };\n}\n\nexport interface CartographyToolsOptions {\n /** Called when the agent needs a human answer. Return the user's response. */\n onAskUser?: (question: string, context?: string) => Promise<string>;\n}\n\nexport function stripSensitive(target: string): string {\n const raw = target.trim();\n if (!raw) return raw;\n try {\n const url = new URL(raw.startsWith('http') ? raw : `tcp://${raw}`);\n const stripped = `${url.hostname}${url.port ? ':' + url.port : ''}`;\n return stripped || raw;\n } catch {\n const stripped = raw\n .replace(/\\/.*$/, '')\n .replace(/\\?.*$/, '')\n .replace(/@.*:/, ':');\n return stripped || raw;\n }\n}\n\nexport async function createCartographyTools(\n db: CartographyDB,\n sessionId: string,\n opts: CartographyToolsOptions = {},\n): Promise<McpServerConfig> {\n // Dynamically import the SDK so missing package doesn't crash at load time\n const { tool, createSdkMcpServer } = await import('@anthropic-ai/claude-agent-sdk');\n\n const tools = [\n tool('save_node', 'Save an infrastructure node to the catalog', {\n id: z.string(),\n type: z.enum(NODE_TYPES),\n name: z.string(),\n discoveredVia: z.string(),\n confidence: z.number().min(0).max(1),\n metadata: z.record(z.string(), z.unknown()).optional(),\n tags: z.array(z.string()).optional(),\n domain: z.string().optional().describe('Business domain, e.g. \"Marketing\", \"Finance\"'),\n subDomain: z.string().optional().describe('Sub-domain, e.g. \"Forecast client orders\"'),\n qualityScore: z.number().min(0).max(100).optional().describe('Data quality score 0–100'),\n }, async (args) => {\n const node = {\n id: stripSensitive(args['id'] as string),\n type: args['type'] as typeof NODE_TYPES[number],\n name: args['name'] as string,\n discoveredVia: args['discoveredVia'] as string,\n confidence: args['confidence'] as number,\n metadata: (args['metadata'] as Record<string, unknown>) ?? {},\n tags: (args['tags'] as string[]) ?? [],\n domain: args['domain'] as string | undefined,\n subDomain: args['subDomain'] as string | undefined,\n qualityScore: args['qualityScore'] as number | undefined,\n };\n db.upsertNode(sessionId, node);\n return { content: [{ type: 'text', text: `✓ Node: ${node.id}` }] };\n }),\n\n tool('save_edge', 'Save a relationship (edge) between two nodes — ALWAYS save edges when connections are clear', {\n sourceId: z.string(),\n targetId: z.string(),\n relationship: z.enum(EDGE_RELATIONSHIPS),\n evidence: z.string(),\n confidence: z.number().min(0).max(1),\n }, async (args) => {\n db.insertEdge(sessionId, {\n sourceId: args['sourceId'] as string,\n targetId: args['targetId'] as string,\n relationship: args['relationship'] as typeof EDGE_RELATIONSHIPS[number],\n evidence: args['evidence'] as string,\n confidence: args['confidence'] as number,\n });\n return { content: [{ type: 'text', text: `✓ ${args['sourceId']}→${args['targetId']}` }] };\n }),\n\n tool('get_catalog', 'Get the current catalog — use before save_node to avoid duplicates', {\n includeEdges: z.boolean().default(true),\n }, async (args) => {\n const nodes = db.getNodes(sessionId);\n const edges = (args['includeEdges'] as boolean) ? db.getEdges(sessionId) : [];\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: { nodes: nodes.length, edges: edges.length },\n nodeIds: nodes.map(n => n.id),\n }),\n }],\n };\n }),\n\n tool('ask_user', 'Ask the user a question — for clarifications, missing context, or consent (e.g. before scanning browser history)', {\n question: z.string().describe('The question for the user (clear and specific)'),\n context: z.string().optional().describe('Optional context explaining why this is relevant'),\n }, async (args) => {\n const question = args['question'] as string;\n const context = args['context'] as string | undefined;\n\n if (opts.onAskUser) {\n const answer = await opts.onAskUser(question, context);\n return { content: [{ type: 'text', text: answer }] };\n }\n\n // Fallback when not interactive (piped input, daemon, etc.)\n return {\n content: [{ type: 'text', text: '(Non-interactive mode — please continue without this information)' }],\n };\n }),\n\n tool('scan_bookmarks', 'Scan all browser bookmarks — hostnames only, no personal data (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)', {\n minConfidence: z.number().min(0).max(1).default(0.5).optional(),\n }, async () => {\n const hosts = await scanAllBookmarks();\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: hosts.length,\n hosts: hosts.map(h => ({\n hostname: h.hostname,\n port: h.port,\n protocol: h.protocol,\n source: h.source,\n })),\n note: 'Hostnames only — no paths, no personal data. Classify each as a business tool (save_node) or ignore (social media, news, shopping).',\n }),\n }],\n };\n }),\n\n tool('scan_browser_history', 'Scan browser history — anonymized hostnames + visit frequency. ALWAYS call ask_user for consent before using this tool.', {\n minVisits: z.number().min(1).default(3).optional().describe('Minimum visit count to include a host (filters rarely-visited sites)'),\n }, async (args) => {\n const minVisits = (args['minVisits'] as number | undefined) ?? 3;\n const hosts = await scanAllHistory();\n const filtered = hosts.filter(h => h.visitCount >= minVisits);\n return {\n content: [{\n type: 'text',\n text: JSON.stringify({\n count: filtered.length,\n note: 'Anonymized — hostnames only, no URLs, no paths, no personal data. Classify business tools as saas_tool nodes.',\n hosts: filtered.map(h => ({\n hostname: h.hostname,\n visitCount: h.visitCount,\n protocol: h.protocol,\n source: h.source,\n })),\n }),\n }],\n };\n }),\n\n tool('scan_local_databases', 'Scan for local database files and running DB servers — PostgreSQL databases, MySQL, SQLite files from installed apps', {\n deep: z.boolean().default(false).optional().describe('Also search home directory recursively for SQLite/DB files (slower)'),\n }, async (args) => {\n const deep = (args['deep'] as boolean | undefined) ?? false;\n const results: Record<string, string> = {};\n\n results['PLATFORM'] = `${PLATFORM} (${IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux'})`;\n\n // ── Windows: detect DB services via Get-Service / Get-NetTCPConnection ──\n if (IS_WIN) {\n results['DB_SERVICES'] = scanWindowsDbServices() || '(no database services found)';\n }\n\n // ── PostgreSQL (cross-platform) ──\n if (commandExists('psql')) {\n if (IS_WIN) {\n results['POSTGRES_DATABASES'] = run('psql -lqt', { timeout: 10_000 }) || '(psql found but not running or requires auth)';\n } else {\n results['POSTGRES_DATABASES'] = run('psql -lqt 2>/dev/null | grep -v \"template0\\\\|template1\" | awk \\'{print $1}\\' | grep -v \"^$\\\\|^|\"') || '(psql not running or not available)';\n results['POSTGRES_CLUSTERS'] = run('pg_lsclusters 2>/dev/null') || '(pg_lsclusters not available)';\n }\n } else {\n results['POSTGRES_DATABASES'] = '(psql not installed)';\n }\n\n // ── MySQL / MariaDB (cross-platform) ──\n if (commandExists('mysql')) {\n if (IS_WIN) {\n results['MYSQL_DATABASES'] = run('mysql --connect-timeout=3 -e \"SHOW DATABASES;\"', { timeout: 10_000 }) || '(mysql not running or requires auth)';\n } else {\n results['MYSQL_DATABASES'] = run('mysql --connect-timeout=3 -e \"SHOW DATABASES;\" 2>/dev/null') || '(mysql not running or requires auth)';\n }\n } else {\n results['MYSQL_DATABASES'] = '(mysql not installed)';\n }\n\n // ── MongoDB (cross-platform) ──\n if (commandExists('mongosh')) {\n if (IS_WIN) {\n results['MONGODB_DATABASES'] = run('mongosh --quiet --eval \"db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join(\\'\\\\n\\')\"', { timeout: 10_000 }) || '(mongosh not available)';\n } else {\n results['MONGODB_DATABASES'] = run('mongosh --quiet --eval \"db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join(\\'\\\\n\\')\" 2>/dev/null') || '(mongosh not available)';\n }\n } else {\n results['MONGODB_DATABASES'] = '(mongosh not installed)';\n }\n\n // ── Redis (cross-platform) ──\n if (commandExists('redis-cli')) {\n if (IS_WIN) {\n results['REDIS_INFO'] = run('redis-cli info server', { timeout: 10_000 }).split('\\n').slice(0, 5).join('\\n') || '(redis-cli not available)';\n } else {\n results['REDIS_INFO'] = run('redis-cli info server 2>/dev/null | head -5') || '(redis-cli not available)';\n }\n } else {\n results['REDIS_INFO'] = '(redis-cli not installed)';\n }\n\n // ── SQLite files in app data directories (cross-platform) ──\n const appDirs = dbScanDirs();\n if (appDirs.length > 0) {\n results['SQLITE_APP_FILES'] = findFiles(appDirs, ['*.sqlite', '*.sqlite3', '*.db'], 4, 80) || '(none found)';\n }\n\n // ── Deep home scan (cross-platform) ──\n if (deep) {\n if (IS_WIN) {\n results['SQLITE_DEEP_SCAN'] = run(\n `Get-ChildItem -Path '${HOME}' -Recurse -Depth 6 -Include '*.sqlite','*.sqlite3','*.db' -ErrorAction SilentlyContinue | ` +\n `Where-Object { $_.FullName -notmatch 'node_modules|\\\\.git' } | ` +\n `Select-Object -First 100 -ExpandProperty FullName`,\n { timeout: 30_000 },\n ) || '(none found)';\n } else {\n results['SQLITE_DEEP_SCAN'] = run(`find \"${HOME}\" -maxdepth 6 \\\\( -name \"*.sqlite\" -o -name \"*.sqlite3\" -o -name \"*.db\" \\\\) -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" 2>/dev/null | head -100`) || '(none found)';\n }\n }\n\n // ── DB config files (cross-platform, no credentials extracted) ──\n if (IS_WIN) {\n results['DB_CONFIG_FILES'] = run(\n `Get-ChildItem -Path '${HOME}' -Recurse -Depth 4 -Include '.env','.env.local','database.yml','database.json','docker-compose.yml' -ErrorAction SilentlyContinue | ` +\n `Select-Object -First 20 -ExpandProperty FullName`,\n { timeout: 15_000 },\n ) || '(none found)';\n } else {\n results['DB_CONFIG_FILES'] = run(`find \"${HOME}\" -maxdepth 4 \\\\( -name \".env\" -o -name \".env.local\" -o -name \"database.yml\" -o -name \"database.json\" -o -name \"docker-compose.yml\" \\\\) 2>/dev/null | head -20`) || '(none found)';\n }\n\n const out = Object.entries(results).map(([k, v]) => `=== ${k} ===\\n${v}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_k8s_resources', 'Scan Kubernetes cluster via kubectl — 100% readonly (get, describe)', {\n namespace: z.string().optional().describe('Filter by namespace — empty = all namespaces'),\n }, async (args) => {\n const ns = args['namespace'] as string | undefined;\n const nsFlag = ns ? `-n ${ns}` : '--all-namespaces';\n const runK = createScanRunner(run, { timeout: 15_000, threshold: 3 });\n const sections: [string, string][] = IS_WIN\n ? [\n ['CONTEXT', 'kubectl config current-context'],\n ['NODES', 'kubectl get nodes -o wide'],\n ['NAMESPACES', 'kubectl get namespaces'],\n ['SERVICES', `kubectl get services ${nsFlag}`],\n ['DEPLOYMENTS', `kubectl get deployments ${nsFlag}`],\n ['STATEFULSETS', `kubectl get statefulsets ${nsFlag}`],\n ['INGRESSES', `kubectl get ingress ${nsFlag}`],\n ['PODS_RUNNING', `kubectl get pods ${nsFlag} --field-selector=status.phase=Running`],\n ['CONFIGMAPS_SYSTEM', 'kubectl get configmaps -n kube-system'],\n ]\n : [\n ['CONTEXT', 'kubectl config current-context 2>/dev/null || echo \"(no context set)\"'],\n ['NODES', 'kubectl get nodes -o wide'],\n ['NAMESPACES', 'kubectl get namespaces'],\n ['SERVICES', `kubectl get services ${nsFlag}`],\n ['DEPLOYMENTS', `kubectl get deployments ${nsFlag}`],\n ['STATEFULSETS', `kubectl get statefulsets ${nsFlag}`],\n ['INGRESSES', `kubectl get ingress ${nsFlag} 2>/dev/null || echo \"(none)\"`],\n ['PODS_RUNNING', `kubectl get pods ${nsFlag} --field-selector=status.phase=Running 2>/dev/null | head -60`],\n ['CONFIGMAPS_SYSTEM', 'kubectl get configmaps -n kube-system 2>/dev/null | head -30'],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${runK(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_aws_resources', 'Scan AWS infrastructure via AWS CLI — 100% readonly (describe, list)', {\n region: z.string().optional().describe('AWS Region — default: AWS_DEFAULT_REGION or profile'),\n profile: z.string().optional().describe('AWS CLI profile'),\n }, async (args) => {\n const region = args['region'] as string | undefined;\n const profile = args['profile'] as string | undefined;\n const env: NodeJS.ProcessEnv = { ...process.env };\n if (region) env['AWS_DEFAULT_REGION'] = region;\n const pf = profile ? `--profile ${profile}` : '';\n const runAws = createScanRunner(run, { timeout: 20_000, env, threshold: 3 });\n // aws CLI commands work the same on all platforms (aws is cross-platform)\n const sections: [string, string][] = [\n ['IDENTITY', `aws sts get-caller-identity ${pf} --output json`],\n ['EC2', `aws ec2 describe-instances ${pf} --query \"Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,PublicIpAddress,PrivateIpAddress]\" --output table`],\n ['RDS', `aws rds describe-db-instances ${pf} --query \"DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus,Endpoint.Address,Endpoint.Port]\" --output table`],\n ['ELB_V2', `aws elbv2 describe-load-balancers ${pf} --query \"LoadBalancers[*].[LoadBalancerName,DNSName,Type,State.Code]\" --output table`],\n ['EKS', `aws eks list-clusters ${pf} --output json`],\n ['ELASTICACHE', `aws elasticache describe-cache-clusters ${pf} --query \"CacheClusters[*].[CacheClusterId,Engine,CacheClusterStatus]\" --output table`],\n ['S3', `aws s3 ls ${pf}`],\n ['VPC', `aws ec2 describe-vpcs ${pf} --query \"Vpcs[*].[VpcId,CidrBlock,IsDefault]\" --output table`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${runAws(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_gcp_resources', 'Scan Google Cloud Platform via gcloud CLI — 100% readonly (list, describe)', {\n project: z.string().optional().describe('GCP Project ID — default: current gcloud project'),\n }, async (args) => {\n const project = args['project'] as string | undefined;\n const pf = project ? `--project ${project}` : '';\n const runGcp = createScanRunner(run, { timeout: 20_000, threshold: 3 });\n // gcloud CLI is cross-platform\n const sections: [string, string][] = [\n ['IDENTITY', `gcloud config list account --format=\"value(core.account)\"`],\n ['COMPUTE_INSTANCES', `gcloud compute instances list ${pf}`],\n ['SQL_INSTANCES', `gcloud sql instances list ${pf}`],\n ['GKE_CLUSTERS', `gcloud container clusters list ${pf}`],\n ['CLOUD_RUN', `gcloud run services list ${pf} --platform managed`],\n ['CLOUD_FUNCTIONS', `gcloud functions list ${pf}`],\n ['REDIS', `gcloud redis instances list ${pf} --regions=-`],\n ['PUBSUB', `gcloud pubsub topics list ${pf}`],\n ['SPANNER', `gcloud spanner instances list ${pf}`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${runGcp(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_azure_resources', 'Scan Azure infrastructure via az CLI — 100% readonly (list, show)', {\n subscription: z.string().optional().describe('Azure Subscription ID'),\n resourceGroup: z.string().optional().describe('Filter by resource group'),\n }, async (args) => {\n const sub = args['subscription'] as string | undefined;\n const rg = args['resourceGroup'] as string | undefined;\n const sf = sub ? `--subscription ${sub}` : '';\n const rf = rg ? `--resource-group ${rg}` : '';\n const runAz = createScanRunner(run, { timeout: 20_000, threshold: 3 });\n // az CLI is cross-platform\n const sections: [string, string][] = [\n ['IDENTITY', `az account show --output json ${sf}`],\n ['VMS', `az vm list ${sf} ${rf} --output table`],\n ['AKS', `az aks list ${sf} ${rf} --output table`],\n ['SQL_SERVERS', `az sql server list ${sf} ${rf} --output table`],\n ['POSTGRES', `az postgres server list ${sf} ${rf} --output table`],\n ['REDIS', `az redis list ${sf} ${rf} --output table`],\n ['WEBAPPS', `az webapp list ${sf} ${rf} --output table`],\n ['CONTAINER_APPS', `az containerapp list ${sf} ${rf} --output table`],\n ['FUNCTIONS', `az functionapp list ${sf} ${rf} --output table`],\n ];\n const out = sections.map(([l, c]) => `=== ${l} ===\\n${runAz(c)}`).join('\\n\\n');\n return { content: [{ type: 'text', text: out }] };\n }),\n\n tool('scan_installed_apps', 'Scan all installed apps and tools — IDEs, office, dev tools, business apps, databases', {\n searchHint: z.string().optional().describe('Optional search term to find specific tools (e.g. \"hubspot windsurf cursor\")'),\n }, async (args) => {\n const hint = args['searchHint'] as string | undefined;\n const results: Record<string, string> = {};\n results['PLATFORM'] = `${PLATFORM} (${IS_WIN ? 'Windows' : IS_MAC ? 'macOS' : 'Linux'})`;\n\n if (IS_MAC) {\n // macOS: scan /Applications\n results['APPLICATIONS'] = run('ls /Applications/ 2>/dev/null | head -200') || '(empty)';\n results['USER_APPLICATIONS'] = run('ls ~/Applications/ 2>/dev/null | head -100') || '(empty)';\n // Homebrew\n results['BREW_CASKS'] = run('brew list --cask 2>/dev/null | head -100') || '(brew not installed)';\n results['BREW_FORMULAE'] = run('brew list --formula 2>/dev/null | head -150') || '(brew not installed)';\n // Spotlight — find .app bundles\n results['SPOTLIGHT_APPS'] = run('mdfind \"kMDItemKind == \\'Application\\'\" 2>/dev/null | grep -v \"^/System\" | grep -v \"^/Library/Apple\" | head -100') || '(Spotlight not available)';\n } else if (IS_LINUX) {\n // Linux: dpkg, snap, flatpak, .desktop files\n results['DPKG'] = run('dpkg --list 2>/dev/null | awk \\'{print $2}\\' | head -200') || '(dpkg not available)';\n results['SNAP'] = run('snap list 2>/dev/null | head -50') || '(snap not available)';\n results['FLATPAK'] = run('flatpak list 2>/dev/null | head -50') || '(flatpak not available)';\n results['DESKTOP_FILES'] = run('ls /usr/share/applications/*.desktop ~/.local/share/applications/*.desktop 2>/dev/null | xargs -I{} basename {} .desktop 2>/dev/null | head -100') || '(no .desktop files)';\n results['RPM'] = run('rpm -qa 2>/dev/null | head -200') || '(rpm not available)';\n } else if (IS_WIN) {\n // Windows: winget, registry, Get-Package\n results['WINGET'] = run('winget list --accept-source-agreements', { timeout: 20_000 }) || '(winget not available)';\n results['INSTALLED_PROGRAMS'] = scanWindowsPrograms() || '(registry scan failed)';\n // Chocolatey\n results['CHOCO'] = run('choco list --local-only', { timeout: 15_000 }) || '(chocolatey not installed)';\n // Scoop\n results['SCOOP'] = run('scoop list', { timeout: 15_000 }) || '(scoop not installed)';\n }\n\n // ── Check known dev/business tools via cross-platform commandExists ──\n const knownTools = [\n // IDEs & Editors\n 'code', 'code-insiders', 'cursor', 'windsurf', 'zed', 'vim', 'nvim', 'emacs', 'nano', 'sublime_text', 'atom',\n 'idea', 'webstorm', 'pycharm', 'goland', 'datagrip', 'clion', 'rider', 'phpstorm', 'rubymine', 'appcode',\n // Dev Tools\n 'git', 'gh', 'docker', 'docker-compose', 'podman', 'kubectl', 'helm', 'terraform', 'ansible',\n 'node', 'npm', 'npx', 'yarn', 'pnpm', 'bun', 'deno',\n 'python', 'python3', 'pip', 'pip3', 'pipenv', 'poetry', 'conda',\n 'ruby', 'gem', 'bundler', 'rails',\n 'java', 'mvn', 'gradle', 'kotlin',\n 'go', 'cargo', 'rustc',\n 'php', 'composer',\n 'dotnet',\n // Databases\n 'psql', 'mysql', 'mysqladmin', 'mongo', 'mongosh', 'redis-cli', 'sqlite3', 'clickhouse-client',\n // Cloud CLIs\n 'aws', 'gcloud', 'az', 'heroku', 'fly', 'vercel', 'netlify', 'wrangler',\n // Infra\n 'vagrant', 'packer', 'consul', 'vault', 'nomad',\n // Communication / SaaS\n 'slack', 'discord', 'zoom', 'teams', 'skype', 'telegram', 'signal',\n // Browsers\n 'google-chrome', 'chromium', 'firefox', 'safari', 'brave', 'opera', 'edge',\n // Windows-specific\n ...(IS_WIN ? ['pwsh', 'powershell', 'wsl', 'winget', 'choco', 'scoop', 'notepad++'] : []),\n // Monitoring / Analytics\n 'datadog-agent', 'newrelic-agent', 'prometheus', 'grafana-cli',\n // Other tools\n 'ngrok', 'stripe', 'supabase', 'neon',\n ];\n\n const found: string[] = [];\n const notFound: string[] = [];\n for (const t of knownTools) {\n const r = commandExists(t);\n if (r) found.push(`${t}: ${r}`);\n else notFound.push(t);\n }\n results['TOOLS_FOUND'] = found.join('\\n') || '(none found)';\n results['TOOLS_NOT_FOUND'] = notFound.join(', ');\n\n // Hint-based search: targeted lookup for user-specified tools\n if (hint) {\n const terms = hint.split(/[\\s,]+/).filter(Boolean);\n const hintResults: string[] = [];\n for (const term of terms) {\n const safe = term.replace(/[^a-zA-Z0-9._-]/g, '');\n if (!safe) continue;\n // First try commandExists\n const cmdPath = commandExists(safe);\n if (cmdPath) {\n hintResults.push(`${term}: ${cmdPath}`);\n continue;\n }\n // Platform-specific fallback search\n let fallback = '';\n if (IS_WIN) {\n fallback = run(\n `Get-ChildItem -Path 'C:\\\\Program Files','C:\\\\Program Files (x86)','${HOME}\\\\AppData\\\\Local\\\\Programs' ` +\n `-Recurse -Depth 3 -Filter '*${safe}*' -ErrorAction SilentlyContinue | ` +\n `Select-Object -First 5 -ExpandProperty FullName`,\n { timeout: 10_000 },\n );\n } else if (IS_MAC) {\n fallback = run(`mdfind -name \"${safe}\" 2>/dev/null | head -5`);\n } else {\n fallback = run(`find /usr/bin /usr/local/bin /opt/homebrew/bin ~/.local/bin /Applications ~/Applications 2>/dev/null -iname \"*${safe}*\" -maxdepth 3 2>/dev/null | head -5`);\n }\n hintResults.push(fallback ? `${term}: ${fallback}` : `${term}: (not found)`);\n }\n results['HINT_SEARCH'] = hintResults.join('\\n');\n }\n\n const out = Object.entries(results)\n .map(([k, v]) => `=== ${k} ===\\n${v}`)\n .join('\\n\\n');\n\n return { content: [{ type: 'text', text: out }] };\n }),\n ];\n\n return createSdkMcpServer({\n name: 'cartography',\n version: '0.1.0',\n tools,\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","import type { CartographyDB } from './db.js';\nimport { createCartographyTools } from './tools.js';\nimport { safetyHook } from './safety.js';\nimport type { CartographyConfig } from './types.js';\nimport { IS_WIN, IS_MAC, PLATFORM } from './platform.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 { query } = await import('@anthropic-ai/claude-agent-sdk');\n const tools = await createCartographyTools(db, sessionId, { onAskUser });\n\n const hintSection = hint\n ? `\\n⚡ USER HINT (HIGH PRIORITY): The user wants to find these specific tools: \"${hint}\"\\n → Run scan_installed_apps(searchHint: \"${hint}\") IMMEDIATELY and save found tools as saas_tool nodes!\\n`\n : '';\n\n // 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\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 let turnCount = 0;\n\n try {\n const startTime = Date.now();\n\n for await (const msg of query({\n prompt: initialPrompt,\n options: {\n model: config.agentModel,\n maxTurns: config.maxTurns,\n systemPrompt,\n mcpServers: { cartography: tools },\n allowedTools: [\n 'Bash',\n 'mcp__cartograph__save_node',\n 'mcp__cartograph__save_edge',\n 'mcp__cartograph__get_catalog',\n 'mcp__cartograph__scan_bookmarks',\n 'mcp__cartograph__scan_browser_history',\n 'mcp__cartograph__scan_installed_apps',\n 'mcp__cartograph__scan_local_databases',\n 'mcp__cartograph__scan_k8s_resources',\n 'mcp__cartograph__scan_aws_resources',\n 'mcp__cartograph__scan_gcp_resources',\n 'mcp__cartograph__scan_azure_resources',\n 'mcp__cartograph__ask_user',\n ],\n hooks: {\n PreToolUse: [{ matcher: 'Bash', hooks: [safetyHook] }],\n },\n permissionMode: 'bypassPermissions',\n },\n })) {\n // Wall-clock timeout guard\n if (Date.now() - startTime > MAX_DISCOVERY_MS) {\n onEvent?.({ kind: 'error', text: `Discovery timeout after ${MAX_DISCOVERY_MS / 60000} minutes` });\n onEvent?.({ kind: 'done' });\n return;\n }\n\n if (!onEvent) continue;\n\n if (msg.type === 'assistant') {\n turnCount++;\n onEvent({ kind: 'turn', turn: turnCount });\n\n for (const block of msg.message.content) {\n if (block.type === 'text') {\n onEvent({ kind: 'thinking', text: block.text });\n }\n if (block.type === 'tool_use') {\n onEvent({\n kind: 'tool_call',\n tool: block.name as string,\n input: block.input as Record<string, unknown>,\n });\n }\n }\n }\n\n if (msg.type === 'user') {\n const content = msg.message?.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (typeof block === 'object' && block !== null && 'type' in block && (block as { type: string }).type === 'tool_result') {\n const tb = block as { tool_use_id?: string; content?: unknown };\n const text = typeof tb.content === 'string' ? tb.content : '';\n onEvent({ kind: 'tool_result', tool: tb.tool_use_id ?? '', output: text });\n }\n }\n }\n }\n\n if (msg.type === 'result') {\n onEvent({ kind: 'done' });\n return;\n }\n }\n } 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","import { mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { CartographyDB } from './db.js';\nimport type { NodeRow, EdgeRow } 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// ── Backstage YAML ───────────────────────────────────────────────────────────\n\nexport function exportBackstageYAML(nodes: NodeRow[], edges: EdgeRow[], org?: string): string {\n const owner = org ?? 'unknown';\n const docs: string[] = [];\n\n for (const node of nodes) {\n const isComponent = ['web_service', 'container', 'pod'].includes(node.type);\n const isAPI = node.type === 'api_endpoint';\n const kind = isComponent ? 'Component' : isAPI ? 'API' : 'Resource';\n\n const deps = edges\n .filter(e => e.sourceId === node.id)\n .map(e => ` - resource:default/${sanitize(e.targetId)}`);\n\n const doc = [\n `apiVersion: backstage.io/v1alpha1`,\n `kind: ${kind}`,\n `metadata:`,\n ` name: ${sanitize(node.id)}`,\n ` annotations:`,\n ` cartography/discovered-at: \"${node.discoveredAt}\"`,\n ` cartography/confidence: \"${node.confidence}\"`,\n `spec:`,\n ` type: ${node.type}`,\n ` lifecycle: production`,\n ` owner: ${owner}`,\n ...(deps.length > 0 ? [' dependsOn:', ...deps] : []),\n ].join('\\n');\n\n docs.push(doc);\n }\n\n return docs.join('\\n---\\n');\n}\n\n// ── JSON ─────────────────────────────────────────────────────────────────────\n\nexport function exportJSON(db: CartographyDB, sessionId: string): string {\n const nodes = db.getNodes(sessionId);\n const edges = db.getEdges(sessionId);\n const events = db.getEvents(sessionId);\n const tasks = db.getTasks(sessionId);\n const 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> \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\">⌕</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' ? '☼' : '☾'}</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\">🗺</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\">—</h3>\n <button class=\"close-btn\" id=\"dp-close\" aria-label=\"Close panel\">✕</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\">🏷</button>\n <button class=\"tb-btn\" id=\"btn-quality\" title=\"Quality layer\" aria-pressed=\"false\" aria-label=\"Toggle quality layer\">👁</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\">−</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\">🔗</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,'&').replace(/</g,'<').replace(/>/g,'>');}\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?'☼':'☾';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 · ${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' ? '☼' : '☾'}\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\">🗺</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\">—</h3>\n <button class=\"close-btn\" id=\"md-close\" aria-label=\"Close\">✕</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\">🏷</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\">👁</button>\n <button class=\"tb-tool\" id=\"btn-connect\" title=\"Connection tool\">🔗</button>\n </div>\n <div id=\"map-tb-right\">\n <div class=\"map-zoom\">\n <button class=\"zoom-btn\" id=\"mz-out\">−</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> \n <span class=\"stats\">${nodeCount} nodes · ${edgeCount} edges</span><br/>\n <span class=\"zoom-level\">Scroll = zoom · Drag = pan · 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, '&').replace(/</g, '<').replace(/>/g, '>'); }\nfunction mRenderQ(s) {\n var c = s >= 70 ? '#22c55e' : s >= 40 ? '#f97316' : '#ef4444';\n return s + '/100 <div class=\"quality-bar\"><div class=\"quality-fill\" style=\"width:' + s + '%;background:' + c + '\"></div></div>';\n}\nfunction mShowDetail(asset) {\n document.getElementById('md-name').textContent = asset.name;\n var body = document.getElementById('md-body');\n var rows = [['Domain', asset.domain], ['Sub-domain', asset.subDomain],\n ['Quality', asset.qualityScore !== null ? mRenderQ(asset.qualityScore) : null]\n ].concat(Object.entries(asset.metadata || {}).slice(0, 8).map(function(kv) { return [kv[0], String(kv[1])]; }))\n .filter(function(r) { return r[1] !== null && r[1] !== undefined && r[1] !== ''; });\n body.innerHTML = rows.map(function(r) {\n return '<div class=\"meta-row\"><div class=\"meta-label\">' + mEsc(String(r[0])) + '</div><div class=\"meta-value\">' + r[1] + '</div></div>';\n }).join('');\n var related = mLocalConns.filter(function(cn) { return cn.sourceAssetId === asset.id || cn.targetAssetId === asset.id; });\n if (related.length > 0) {\n body.innerHTML += '<div class=\"meta-row\"><div class=\"meta-label\">Connections (' + related.length + ')</div><div>' +\n related.map(function(cn) {\n var oid = cn.sourceAssetId === asset.id ? cn.targetAssetId : cn.sourceAssetId;\n var o = mapAssetIndex.get(oid);\n return '<div class=\"meta-value\" style=\"margin-top:4px;font-size:12px\">' + (o ? mEsc(o.name) : oid) + '</div>';\n }).join('') + '</div></div>';\n }\n document.getElementById('map-detail').classList.add('open');\n}\ndocument.getElementById('md-close').addEventListener('click', function() {\n document.getElementById('map-detail').classList.remove('open'); mSelectedId = null; drawMap();\n});\n\n// ── Map toolbar ──────────────────────────────────────────────────────────────\ndocument.getElementById('btn-labels').addEventListener('click', function() {\n mShowLabels = !mShowLabels; this.classList.toggle('active', mShowLabels); drawMap();\n});\ndocument.getElementById('btn-all-labels').addEventListener('click', function() {\n mShowAllLabels = !mShowAllLabels; this.classList.toggle('active', mShowAllLabels);\n if (mShowAllLabels && !mShowLabels) { mShowLabels = true; document.getElementById('btn-labels').classList.add('active'); }\n drawMap();\n});\ndocument.getElementById('btn-quality').addEventListener('click', function() {\n mShowQuality = !mShowQuality; this.classList.toggle('active', mShowQuality); drawMap();\n});\ndocument.getElementById('btn-connect').addEventListener('click', function() {\n mConnectMode = !mConnectMode; mConnectFirst = null;\n this.classList.toggle('active', mConnectMode);\n mapWrap.classList.toggle('connecting', mConnectMode);\n document.getElementById('map-connect-hint').style.display = mConnectMode ? 'block' : 'none'; drawMap();\n});\n\n// Map keyboard\nmapWrap.addEventListener('keydown', function(e) {\n if (e.key === 'ArrowLeft') { mvx += 40; drawMap(); }\n else if (e.key === 'ArrowRight') { mvx -= 40; drawMap(); }\n else if (e.key === 'ArrowUp') { mvy += 40; drawMap(); }\n else if (e.key === 'ArrowDown') { mvy -= 40; drawMap(); }\n else if (e.key === '+' || e.key === '=') mApplyZoom(1.2, mW / 2, mH / 2);\n else if (e.key === '-') mApplyZoom(1 / 1.2, mW / 2, mH / 2);\n else if (e.key === 'Escape') {\n mSelectedId = null; document.getElementById('map-detail').classList.remove('open');\n if (mConnectMode) { mConnectMode = false; mConnectFirst = null; mapWrap.classList.remove('connecting'); document.getElementById('map-connect-hint').style.display = 'none'; document.getElementById('btn-connect').classList.remove('active'); }\n drawMap();\n }\n});\n\n// Map init\nresizeMap(); mapFitToView();\ndocument.getElementById('map-zoom-pct').textContent = Math.round(mScale * 100) + '%';\ndrawMap();\n\n// ═══════════════════════════════════════════════════════════════════════════════\n// TOPOLOGY VIEW (lazy init)\n// ═══════════════════════════════════════════════════════════════════════════════\nvar TOPO = ${graphData};\n\nvar TYPE_COLORS = {\n host:'#4a9eff',database_server:'#ff6b6b',database:'#ff8c42',\n web_service:'#6bcb77',api_endpoint:'#4d96ff',cache_server:'#ffd93d',\n message_broker:'#c77dff',queue:'#e0aaff',topic:'#9d4edd',\n container:'#48cae4',pod:'#00b4d8',k8s_cluster:'#0077b6',\n config_file:'#adb5bd',saas_tool:'#c084fc',table:'#f97316',unknown:'#6c757d'\n};\nvar LAYER_COLORS = { saas:'#c084fc',web:'#6bcb77',data:'#ff6b6b',messaging:'#c77dff',infra:'#4a9eff',config:'#adb5bd',other:'#6c757d' };\nvar LAYER_NAMES = { saas:'SaaS Tools',web:'Web / API',data:'Data Layer',messaging:'Messaging',infra:'Infrastructure',config:'Config',other:'Other' };\n\nvar topoSelectedId = null;\n\nfunction setTopoSearch(q) {\n var el = document.getElementById('topo-search');\n if (el) { el.value = q; buildTopoList(q); }\n}\n\nfunction buildTopoList(filter) {\n var listEl = document.getElementById('topo-list');\n var q = (filter || '').toLowerCase();\n listEl.innerHTML = '';\n var sorted = TOPO.nodes.slice().sort(function(a, b) { return a.name.localeCompare(b.name); });\n for (var i = 0; i < sorted.length; i++) {\n var d = sorted[i];\n if (q && !d.name.toLowerCase().includes(q) && !d.type.includes(q) && !d.id.toLowerCase().includes(q)) continue;\n var item = document.createElement('div');\n item.className = 'topo-item' + (d.id === topoSelectedId ? ' active' : '');\n item.dataset.id = d.id;\n var color = TYPE_COLORS[d.type] || '#aaa';\n item.innerHTML = '<span class=\"topo-dot\" style=\"background:' + color + '\"></span>' +\n '<span class=\"topo-name\" title=\"' + d.id + '\">' + d.name + '</span>' +\n '<span class=\"topo-type\">' + d.type.replace(/_/g, ' ') + '</span>';\n (function(dd) { item.onclick = function() { selectTopoNode(dd); focusTopoNode(dd); }; })(d);\n listEl.appendChild(item);\n }\n}\n\ndocument.getElementById('topo-search').addEventListener('input', function(e) { buildTopoList(e.target.value); });\n\nvar topoSidebar = document.getElementById('topo-sidebar');\n\nfunction selectTopoNode(d) {\n topoSelectedId = d.id;\n buildTopoList(document.getElementById('topo-search').value);\n showTopoNode(d);\n if (typeof d3 !== 'undefined') d3.selectAll('.node-hex').classed('selected', function(nd) { return nd.id === d.id; });\n}\n\nfunction showTopoNode(d) {\n var c = TYPE_COLORS[d.type] || '#aaa';\n var confPct = Math.round(d.confidence * 100);\n var tags = (d.tags || []).map(function(t) { return '<span class=\"tag\">' + t + '</span>'; }).join('');\n var metaRows = Object.entries(d.metadata || {})\n .filter(function(kv) { return kv[1] !== null && kv[1] !== undefined && String(kv[1]).length > 0; })\n .map(function(kv) { return '<tr><td>' + kv[0] + '</td><td>' + JSON.stringify(kv[1]) + '</td></tr>'; }).join('');\n var related = TOPO.links.filter(function(l) {\n return (l.source.id || l.source) === d.id || (l.target.id || l.target) === d.id;\n });\n var edgeItems = related.map(function(l) {\n var isOut = (l.source.id || l.source) === d.id;\n var other = isOut ? (l.target.id || l.target) : (l.source.id || l.source);\n return '<div class=\"edge-item\">' + (isOut ? '\\\\u2192' : '\\\\u2190') + ' <span>' + other + '</span> <small>[' + l.relationship + ']</small></div>';\n }).join('');\n\n topoSidebar.innerHTML =\n '<h2>' + d.name + '</h2>' +\n '<table class=\"meta-table\">' +\n '<tr><td>ID</td><td style=\"font-size:10px;word-break:break-all\">' + d.id + '</td></tr>' +\n '<tr><td>Type</td><td><span style=\"color:' + c + '\">' + d.type + '</span></td></tr>' +\n '<tr><td>Layer</td><td>' + d.layer + '</td></tr>' +\n '<tr><td>Confidence</td><td>' + confPct + '% <div class=\"conf-bar\"><div class=\"conf-fill\" style=\"width:' + confPct + '%;background:' + c + '\"></div></div></td></tr>' +\n '<tr><td>Via</td><td>' + (d.discoveredVia || '\\\\u2014') + '</td></tr>' +\n '<tr><td>Timestamp</td><td>' + (d.discoveredAt ? d.discoveredAt.substring(0, 19).replace('T', ' ') : '\\\\u2014') + '</td></tr>' +\n (tags ? '<tr><td>Tags</td><td>' + tags + '</td></tr>' : '') +\n metaRows + '</table>' +\n (related.length > 0 ? '<div class=\"edges-list\"><strong>Connections (' + related.length + '):</strong>' + edgeItems + '</div>' : '') +\n '<div style=\"margin-top:14px\"><button class=\"export-btn\" style=\"width:100%\" onclick=\"deleteTopoNode(\\\\'' + d.id.replace(/'/g, \"\\\\\\\\'\") + '\\\\')\">Delete node</button></div>';\n}\n\nfunction deleteTopoNode(id) {\n var idx = TOPO.nodes.findIndex(function(n) { return n.id === id; });\n if (idx === -1) return;\n TOPO.nodes.splice(idx, 1);\n TOPO.links = TOPO.links.filter(function(l) {\n return (l.source.id || l.source) !== id && (l.target.id || l.target) !== id;\n });\n topoSelectedId = null;\n topoSidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Node deleted.</p>';\n if (typeof rebuildTopoGraph === 'function') rebuildTopoGraph();\n buildTopoList(document.getElementById('topo-search').value);\n}\n\nfunction initTopology() {\n if (typeof d3 === 'undefined') return;\n\n var svgEl = d3.select('#topo-graph svg');\n var graphDiv = document.getElementById('topo-graph');\n var gW = function() { return graphDiv.clientWidth; };\n var gH = function() { return graphDiv.clientHeight; };\n var g = svgEl.append('g');\n\n svgEl.append('defs').append('marker')\n .attr('id', 'arrow').attr('viewBox', '0 0 10 6')\n .attr('refX', 10).attr('refY', 3)\n .attr('markerWidth', 8).attr('markerHeight', 6)\n .attr('orient', 'auto')\n .append('path').attr('d', 'M0,0 L10,3 L0,6 Z').attr('fill', '#555');\n\n var currentZoom = 1;\n var zoomBehavior = d3.zoom().scaleExtent([0.08, 6]).on('zoom', function(e) {\n g.attr('transform', e.transform); currentZoom = e.transform.k; updateTopoLOD(currentZoom);\n });\n svgEl.call(zoomBehavior);\n\n // Layer filters\n var layers = Array.from(new Set(TOPO.nodes.map(function(d) { return d.layer; })));\n var layerVisible = {};\n layers.forEach(function(l) { layerVisible[l] = true; });\n\n var toolbarEl = document.getElementById('topo-toolbar');\n layers.forEach(function(layer) {\n var btn = document.createElement('button');\n btn.className = 'filter-btn';\n btn.innerHTML = '<span class=\"filter-dot\" style=\"background:' + (LAYER_COLORS[layer] || '#666') + '\"></span>' + (LAYER_NAMES[layer] || layer);\n btn.onclick = function() { layerVisible[layer] = !layerVisible[layer]; btn.classList.toggle('off', !layerVisible[layer]); updateTopoVisibility(); };\n toolbarEl.appendChild(btn);\n });\n\n // JGF export button\n var jgfBtn = document.createElement('button');\n jgfBtn.className = 'export-btn'; jgfBtn.textContent = '\\\\u2193 JGF'; jgfBtn.title = 'Export JSON Graph Format';\n jgfBtn.onclick = function() {\n var jgf = { graph: { directed: true, type: 'cartography', label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(TOPO.nodes.map(function(n) { return [n.id, { label: n.name, metadata: { type: n.type, layer: n.layer, confidence: n.confidence, discoveredVia: n.discoveredVia, discoveredAt: n.discoveredAt, tags: n.tags } }]; })),\n edges: TOPO.links.map(function(l) { return { source: l.source.id || l.source, target: l.target.id || l.target, relation: l.relationship, metadata: { confidence: l.confidence, evidence: l.evidence } }; })\n }};\n var blob = new Blob([JSON.stringify(jgf, null, 2)], { type: 'application/json' });\n var url = URL.createObjectURL(blob);\n var a = document.createElement('a'); a.href = url; a.download = 'cartography-graph.jgf.json'; a.click();\n URL.revokeObjectURL(url);\n };\n toolbarEl.appendChild(jgfBtn);\n\n // Hex helpers\n var T_HEX = { saas_tool: 16, host: 18, database_server: 18, k8s_cluster: 20, default: 14 };\n function tHexSize(d) { return T_HEX[d.type] || T_HEX.default; }\n function tHexPath(size) {\n var pts = [];\n for (var i = 0; i < 6; i++) {\n var angle = (Math.PI / 3) * i - Math.PI / 6;\n pts.push([size * Math.cos(angle), size * Math.sin(angle)]);\n }\n return 'M' + pts.map(function(p) { return p.join(','); }).join('L') + 'Z';\n }\n\n // Cluster force\n function clusterForce(alpha) {\n var centroids = {}, counts = {};\n TOPO.nodes.forEach(function(d) {\n if (!centroids[d.layer]) { centroids[d.layer] = { x: 0, y: 0 }; counts[d.layer] = 0; }\n centroids[d.layer].x += d.x || 0; centroids[d.layer].y += d.y || 0; counts[d.layer]++;\n });\n for (var l in centroids) { centroids[l].x /= counts[l]; centroids[l].y /= counts[l]; }\n var strength = alpha * 0.15;\n TOPO.nodes.forEach(function(d) {\n var cn = centroids[d.layer];\n if (cn) { d.vx += (cn.x - d.x) * strength; d.vy += (cn.y - d.y) * strength; }\n });\n }\n\n // Hulls\n var hullGroup = g.append('g').attr('class', 'hulls');\n var hullPaths = {}, hullLabels = {};\n layers.forEach(function(layer) {\n hullPaths[layer] = hullGroup.append('path').attr('class', 'hull')\n .attr('fill', LAYER_COLORS[layer] || '#666').attr('stroke', LAYER_COLORS[layer] || '#666');\n hullLabels[layer] = hullGroup.append('text').attr('class', 'hull-label')\n .attr('fill', LAYER_COLORS[layer] || '#666').text(LAYER_NAMES[layer] || layer);\n });\n\n function updateHulls() {\n layers.forEach(function(layer) {\n if (!layerVisible[layer]) { hullPaths[layer].attr('d', null); hullLabels[layer].attr('x', -9999); return; }\n var pts = TOPO.nodes.filter(function(d) { return d.layer === layer && layerVisible[d.layer]; }).map(function(d) { return [d.x, d.y]; });\n if (pts.length < 3) {\n hullPaths[layer].attr('d', null);\n if (pts.length > 0) hullLabels[layer].attr('x', pts[0][0]).attr('y', pts[0][1] - 30);\n else hullLabels[layer].attr('x', -9999);\n return;\n }\n var hull = d3.polygonHull(pts);\n if (!hull) { hullPaths[layer].attr('d', null); return; }\n var cx = d3.mean(hull, function(p) { return p[0]; });\n var cy = d3.mean(hull, function(p) { return p[1]; });\n var padded = hull.map(function(p) {\n var dx = p[0] - cx, dy = p[1] - cy;\n var len = Math.sqrt(dx * dx + dy * dy) || 1;\n return [p[0] + dx / len * 40, p[1] + dy / len * 40];\n });\n hullPaths[layer].attr('d', 'M' + padded.join('L') + 'Z');\n hullLabels[layer].attr('x', cx).attr('y', cy - d3.max(hull, function(p) { return Math.abs(p[1] - cy); }) - 30);\n });\n }\n\n // Graph\n var linkSel, linkLabelSel, nodeSel, nodeLabelSel, sim;\n var linkGroup = g.append('g');\n var nodeGroup = g.append('g');\n\n function focusTopoNode(d) {\n if (!d.x || !d.y) return;\n var w = gW(), h = gH();\n svgEl.transition().duration(500).call(\n zoomBehavior.transform,\n d3.zoomIdentity.translate(w / 2, h / 2).scale(Math.min(3, currentZoom < 1 ? 1.5 : currentZoom)).translate(-d.x, -d.y)\n );\n }\n window.focusTopoNode = focusTopoNode;\n\n function rebuildTopoGraph() {\n if (sim) sim.stop();\n\n linkSel = linkGroup.selectAll('line').data(TOPO.links, function(d) { return (d.source.id || d.source) + '>' + (d.target.id || d.target); });\n linkSel.exit().remove();\n var linkEnter = linkSel.enter().append('line').attr('class', 'link');\n linkSel = linkEnter.merge(linkSel)\n .attr('stroke', function(d) { return d.confidence < 0.6 ? '#2a2e35' : '#3d434b'; })\n .attr('stroke-dasharray', function(d) { return d.confidence < 0.6 ? '4 3' : null; })\n .attr('stroke-width', function(d) { return d.confidence < 0.6 ? 0.8 : 1.2; })\n .attr('marker-end', 'url(#arrow)');\n linkSel.select('title').remove();\n linkSel.append('title').text(function(d) { return d.relationship + ' (' + Math.round(d.confidence * 100) + '%)\\\\n' + (d.evidence || ''); });\n\n linkLabelSel = linkGroup.selectAll('text').data(TOPO.links, function(d) { return (d.source.id || d.source) + '>' + (d.target.id || d.target); });\n linkLabelSel.exit().remove();\n linkLabelSel = linkLabelSel.enter().append('text').attr('class', 'link-label').merge(linkLabelSel).text(function(d) { return d.relationship; });\n\n nodeSel = nodeGroup.selectAll('g').data(TOPO.nodes, function(d) { return d.id; });\n nodeSel.exit().remove();\n var nodeEnter = nodeSel.enter().append('g')\n .call(d3.drag()\n .on('start', function(e, d) { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })\n .on('drag', function(e, d) { d.fx = e.x; d.fy = e.y; })\n .on('end', function(e, d) { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })\n )\n .on('click', function(e, d) { e.stopPropagation(); selectTopoNode(d); });\n nodeEnter.append('path').attr('class', 'node-hex');\n nodeEnter.append('title');\n nodeEnter.append('text').attr('class', 'node-label').attr('text-anchor', 'middle');\n\n nodeSel = nodeEnter.merge(nodeSel);\n nodeSel.select('.node-hex')\n .attr('d', function(d) { return tHexPath(tHexSize(d)); })\n .attr('fill', function(d) { return TYPE_COLORS[d.type] || '#aaa'; })\n .attr('stroke', function(d) { var c = d3.color(TYPE_COLORS[d.type] || '#aaa'); return c ? c.brighter(0.8).formatHex() : '#ccc'; })\n .attr('fill-opacity', function(d) { return 0.6 + d.confidence * 0.4; })\n .classed('selected', function(d) { return d.id === topoSelectedId; });\n nodeSel.select('title').text(function(d) { return d.name + ' (' + d.type + ')\\\\nconf: ' + Math.round(d.confidence * 100) + '%'; });\n nodeLabelSel = nodeSel.select('.node-label')\n .attr('dy', function(d) { return tHexSize(d) + 13; })\n .text(function(d) { return d.name.length > 20 ? d.name.substring(0, 18) + '\\\\u2026' : d.name; });\n\n sim = d3.forceSimulation(TOPO.nodes)\n .force('link', d3.forceLink(TOPO.links).id(function(d) { return d.id; }).distance(function(d) { return d.relationship === 'contains' ? 50 : 100; }).strength(0.4))\n .force('charge', d3.forceManyBody().strength(-280))\n .force('center', d3.forceCenter(gW() / 2, gH() / 2))\n .force('collision', d3.forceCollide().radius(function(d) { return tHexSize(d) + 10; }))\n .force('cluster', clusterForce)\n .on('tick', function() {\n updateHulls();\n linkSel.attr('x1', function(d) { return d.source.x; }).attr('y1', function(d) { return d.source.y; })\n .attr('x2', function(d) { return d.target.x; }).attr('y2', function(d) { return d.target.y; });\n linkLabelSel.attr('x', function(d) { return (d.source.x + d.target.x) / 2; })\n .attr('y', function(d) { return (d.source.y + d.target.y) / 2 - 4; });\n nodeSel.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + ')'; });\n });\n }\n window.rebuildTopoGraph = rebuildTopoGraph;\n\n function updateTopoLOD(k) {\n if (nodeLabelSel) nodeLabelSel.style('opacity', k > 0.5 ? Math.min(1, (k - 0.5) * 2) : 0);\n if (linkLabelSel) linkLabelSel.style('opacity', k > 1.2 ? Math.min(1, (k - 1.2) * 3) : 0);\n d3.selectAll('.hull-label').style('font-size', k < 0.4 ? '18px' : '13px');\n }\n\n function updateTopoVisibility() {\n if (!nodeSel) return;\n nodeSel.style('display', function(d) { return layerVisible[d.layer] ? null : 'none'; });\n linkSel.style('display', function(d) {\n var s = TOPO.nodes.find(function(n) { return n.id === (d.source.id || d.source); });\n var t = TOPO.nodes.find(function(n) { return n.id === (d.target.id || d.target); });\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n linkLabelSel.style('display', function(d) {\n var s = TOPO.nodes.find(function(n) { return n.id === (d.source.id || d.source); });\n var t = TOPO.nodes.find(function(n) { return n.id === (d.target.id || d.target); });\n return (s && layerVisible[s.layer]) && (t && layerVisible[t.layer]) ? null : 'none';\n });\n }\n\n rebuildTopoGraph();\n buildTopoList();\n updateTopoLOD(1);\n\n svgEl.on('click', function() {\n topoSelectedId = null;\n d3.selectAll('.node-hex').classed('selected', false);\n buildTopoList(document.getElementById('topo-search').value);\n topoSidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Click a node to view details.</p>';\n });\n}\n\n// Init topology node list (non-D3 part)\nbuildTopoList();\n<\\/script>\n</body>\n</html>`;\n}\n\n// ── JGF Export ────────────────────────────────────────────────────────────────\n\nexport function exportJGF(nodes: NodeRow[], edges: EdgeRow[]): string {\n const jgf = {\n graph: {\n directed: true,\n type: 'cartography',\n label: 'Infrastructure Map',\n metadata: { exportedAt: new Date().toISOString() },\n nodes: Object.fromEntries(nodes.map(n => [n.id, {\n label: n.name,\n metadata: {\n type: n.type, layer: nodeLayer(n.type),\n confidence: n.confidence, discoveredVia: n.discoveredVia,\n discoveredAt: n.discoveredAt, tags: n.tags, metadata: n.metadata,\n },\n }])),\n edges: edges.map(e => ({\n source: e.sourceId, target: e.targetId,\n relation: e.relationship,\n metadata: { confidence: e.confidence, evidence: e.evidence },\n })),\n },\n };\n return JSON.stringify(jgf, null, 2);\n}\n\n// ── exportAll ─────────────────────────────────────────────────────────────────\n\nexport function exportAll(\n db: CartographyDB,\n sessionId: string,\n outputDir: string,\n formats: string[] = ['mermaid', 'json', 'yaml', 'html', 'map', 'discovery'],\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}\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","import { execSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\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\nexport function checkPrerequisites(): 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"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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;AAA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","names":["z","z","z","z","body","existsSync","join","join","existsSync","Database","IS_LINUX","lexicalSearch","z","z","mkdirSync","join","HEX_SIZE","mkdirSync","join","execSync","existsSync","readFileSync","join"]}
|