@datasynx/agentic-ai-cartography 0.2.3 → 0.2.5
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 +154 -66
- package/dist/bookmarks-B2LUZQGG.js +12 -0
- package/dist/chunk-MZUF7KGX.js +232 -0
- package/dist/chunk-MZUF7KGX.js.map +1 -0
- package/dist/{chunk-GUZXO6PM.js → chunk-NEB52VYQ.js} +258 -67
- package/dist/chunk-NEB52VYQ.js.map +1 -0
- package/dist/cli.js +414 -310
- package/dist/cli.js.map +1 -1
- package/dist/{exporter-BDVDYA3K.js → exporter-LRHXMSKN.js} +2 -2
- package/dist/index.js +602 -194
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/bookmarks-O7KNR7D3.js +0 -8
- package/dist/chunk-GUZXO6PM.js.map +0 -1
- package/dist/chunk-JAFRT2R6.js +0 -97
- package/dist/chunk-JAFRT2R6.js.map +0 -1
- /package/dist/{bookmarks-O7KNR7D3.js.map → bookmarks-B2LUZQGG.js.map} +0 -0
- /package/dist/{exporter-BDVDYA3K.js.map → exporter-LRHXMSKN.js.map} +0 -0
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/exporter.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { CartographyDB } from './db.js';\nimport type { NodeRow, EdgeRow, SOP } from './types.js';\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: '❓ Sonstige',\n};\n\nconst LAYER_ORDER = ['saas', 'web', 'data', 'messaging', 'infra', 'config', 'other'];\n\n// ── Icons & Labels ───────────────────────────────────────────────────────────\n\nconst MERMAID_ICONS: Record<string, string> = {\n host: '🖥',\n database_server: '🗄',\n database: '🗄',\n table: '📋',\n web_service: '🌐',\n api_endpoint: '🔌',\n cache_server: '⚡',\n message_broker: '📨',\n queue: '📬',\n topic: '📢',\n container: '📦',\n pod: '☸',\n k8s_cluster: '☸',\n config_file: '📄',\n saas_tool: '☁',\n unknown: '❓',\n};\n\nconst EDGE_LABELS: Record<string, string> = {\n connects_to: '→',\n reads_from: 'reads',\n writes_to: 'writes',\n calls: 'calls',\n contains: 'contains',\n depends_on: 'depends on',\n};\n\n// Class colors per type (dark-theme friendly)\nconst MERMAID_CLASSES: Record<string, string> = {\n host: 'fill:#1e3352,stroke:#4a82c4,color:#cce',\n database_server:'fill:#1e3352,stroke:#4a82c4,color:#cce',\n database: 'fill:#163352,stroke:#3a8ad4,color:#bdf',\n table: 'fill:#0f2a40,stroke:#2a6090,color:#9bd',\n web_service: 'fill:#1a3a1a,stroke:#3a9a3a,color:#bfb',\n api_endpoint: 'fill:#0f2a0f,stroke:#2a7a2a,color:#9d9',\n cache_server: 'fill:#3a2a0a,stroke:#ca8a0a,color:#fda',\n message_broker: 'fill:#2a1a3a,stroke:#7a3aaa,color:#daf',\n queue: 'fill:#1f1030,stroke:#5a2a8a,color:#caf',\n topic: 'fill:#1f1030,stroke:#5a2a8a,color:#caf',\n container: 'fill:#1a2a3a,stroke:#3a6a9a,color:#acd',\n pod: 'fill:#0f1f2f,stroke:#2a5a8a,color:#8bc',\n k8s_cluster: 'fill:#0a1520,stroke:#1a4a7a,color:#7ab',\n config_file: 'fill:#2a2a1a,stroke:#7a7a2a,color:#ddc',\n saas_tool: 'fill:#2a1a2a,stroke:#9a3a9a,color:#daf',\n unknown: 'fill:#2a2a2a,stroke:#5a5a5a,color:#aaa',\n};\n\n// ── Mermaid ──────────────────────────────────────────────────────────────────\n\nfunction sanitize(id: string): string {\n return id.replace(/[^a-zA-Z0-9_]/g, '_');\n}\n\nfunction nodeLabel(node: NodeRow): string {\n const icon = MERMAID_ICONS[node.type] ?? '?';\n const parts = node.id.split(':');\n const location = parts.length >= 3 ? `${parts[1]}:${parts[2]}` : parts[1] ?? '';\n const conf = `${Math.round(node.confidence * 100)}%`;\n\n // Pull 1-2 key metadata fields (no credentials)\n const meta = node.metadata as Record<string, unknown>;\n const extras: string[] = [];\n for (const key of ['category', 'version', 'description']) {\n const v = meta[key];\n if (typeof v === 'string' && v.length > 0) {\n extras.push(v.substring(0, 28));\n break; // max 1 extra line for readability\n }\n }\n\n const locLine = location ? `<br/><small>${location}</small>` : '';\n const extraLine = extras.length ? `<br/><small>${extras[0]}</small>` : '';\n return `[\"${icon} <b>${node.name}</b>${locLine}${extraLine}<br/><small>${node.type} · ${conf}</small>\"]`;\n}\n\nexport function generateTopologyMermaid(nodes: NodeRow[], edges: EdgeRow[]): string {\n if (nodes.length === 0) return 'graph TB\\n empty[\"No nodes discovered yet\"]';\n\n const lines: string[] = ['graph TB'];\n\n // classDef per used type\n const usedTypes = new Set(nodes.map(n => n.type));\n for (const type of usedTypes) {\n const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES['unknown']!;\n lines.push(` classDef ${type.replace(/_/g, '')} ${style}`);\n }\n lines.push('');\n\n // Group by semantic layer (ordered top→bottom)\n const layerMap = new Map<string, NodeRow[]>();\n for (const node of nodes) {\n const layer = nodeLayer(node.type);\n if (!layerMap.has(layer)) layerMap.set(layer, []);\n layerMap.get(layer)!.push(node);\n }\n\n for (const layerKey of LAYER_ORDER) {\n const layerNodes = layerMap.get(layerKey);\n if (!layerNodes || layerNodes.length === 0) continue;\n const label = LAYER_LABELS[layerKey] ?? layerKey;\n lines.push(` subgraph ${layerKey}[\"${label}\"]`);\n for (const node of layerNodes) {\n lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, '')}`);\n }\n lines.push(' end');\n lines.push('');\n }\n\n // Edges: dashed for low-confidence (<0.6), solid otherwise\n for (const edge of edges) {\n const src = sanitize(edge.sourceId);\n const tgt = sanitize(edge.targetId);\n const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;\n const arrow = edge.confidence < 0.6 ? `-. \"${label}\" .->` : `-->|\"${label}\"|`;\n lines.push(` ${src} ${arrow} ${tgt}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function generateDependencyMermaid(nodes: NodeRow[], edges: EdgeRow[]): string {\n const depEdges = edges.filter(e =>\n ['calls', 'reads_from', 'writes_to', 'depends_on'].includes(e.relationship)\n );\n\n if (depEdges.length === 0) return 'graph LR\\n empty[\"No dependency edges found\"]';\n\n const lines: string[] = ['graph LR'];\n\n const usedIds = new Set<string>();\n for (const edge of depEdges) {\n usedIds.add(edge.sourceId);\n usedIds.add(edge.targetId);\n }\n\n const usedNodes = nodes.filter(n => usedIds.has(n.id));\n const usedTypes = new Set(usedNodes.map(n => n.type));\n for (const type of usedTypes) {\n const style = MERMAID_CLASSES[type] ?? MERMAID_CLASSES['unknown']!;\n lines.push(` classDef ${type.replace(/_/g, '')} ${style}`);\n }\n lines.push('');\n\n for (const node of usedNodes) {\n lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, '')}`);\n }\n lines.push('');\n\n for (const edge of depEdges) {\n const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;\n lines.push(` ${sanitize(edge.sourceId)} -->|\"${label}\"| ${sanitize(edge.targetId)}`);\n }\n\n return lines.join('\\n');\n}\n\nexport function generateWorkflowMermaid(sop: SOP): string {\n const lines: string[] = ['flowchart TD'];\n\n for (const step of sop.steps) {\n const nodeId = `S${step.order}`;\n const label = `${step.order}. ${step.instruction.substring(0, 60)}`;\n lines.push(` ${nodeId}[\"${label}\"]`);\n\n if (step.order > 1) {\n lines.push(` S${step.order - 1} --> ${nodeId}`);\n }\n }\n\n return lines.join('\\n');\n}\n\n// ── Backstage YAML ───────────────────────────────────────────────────────────\n\nexport function exportBackstageYAML(nodes: NodeRow[], edges: EdgeRow[], org?: string): string {\n const owner = org ?? 'unknown';\n const docs: string[] = [];\n\n for (const node of nodes) {\n const isComponent = ['web_service', 'container', 'pod'].includes(node.type);\n const isAPI = node.type === 'api_endpoint';\n const kind = isComponent ? 'Component' : isAPI ? 'API' : 'Resource';\n\n const deps = edges\n .filter(e => e.sourceId === node.id)\n .map(e => ` - resource:default/${sanitize(e.targetId)}`);\n\n const doc = [\n `apiVersion: backstage.io/v1alpha1`,\n `kind: ${kind}`,\n `metadata:`,\n ` name: ${sanitize(node.id)}`,\n ` annotations:`,\n ` cartography/discovered-at: \"${node.discoveredAt}\"`,\n ` cartography/confidence: \"${node.confidence}\"`,\n `spec:`,\n ` type: ${node.type}`,\n ` lifecycle: production`,\n ` owner: ${owner}`,\n ...(deps.length > 0 ? [' dependsOn:', ...deps] : []),\n ].join('\\n');\n\n docs.push(doc);\n }\n\n return docs.join('\\n---\\n');\n}\n\n// ── JSON ─────────────────────────────────────────────────────────────────────\n\nexport function exportJSON(db: CartographyDB, sessionId: string): string {\n const nodes = db.getNodes(sessionId);\n const edges = db.getEdges(sessionId);\n const events = db.getEvents(sessionId);\n const tasks = db.getTasks(sessionId);\n const sops = db.getSOPs(sessionId);\n const stats = db.getStats(sessionId);\n\n return JSON.stringify({\n sessionId,\n exportedAt: new Date().toISOString(),\n stats,\n nodes,\n edges,\n events,\n tasks,\n sops,\n }, null, 2);\n}\n\n// ── HTML (D3.js Force-Graph) ──────────────────────────────────────────────────\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 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=\"de\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Cartography — Topology</title>\n <script src=\"https://d3js.org/d3.v7.min.js\"></script>\n <style>\n * { box-sizing: border-box; }\n body { margin: 0; background: #0d1117; color: #e6edf3; font-family: 'SF Mono', 'Fira Code', monospace; display: flex; }\n #graph { flex: 1; height: 100vh; }\n svg { width: 100%; height: 100%; }\n .link { stroke-opacity: 0.5; }\n .link-label { font-size: 9px; fill: #8b949e; }\n .node circle { stroke-width: 2px; cursor: pointer; transition: r 0.15s; }\n .node circle:hover { r: 14; }\n .node text { font-size: 11px; fill: #c9d1d9; pointer-events: none; }\n /* ── Sidebar ── */\n #sidebar {\n width: 300px; min-width: 300px; height: 100vh; overflow-y: auto;\n background: #161b22; border-left: 1px solid #30363d;\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 #21262d; vertical-align: top; }\n #sidebar .meta-table td:first-child { color: #8b949e; white-space: nowrap; width: 90px; }\n #sidebar .tag { display: inline-block; background: #21262d; border-radius: 3px; padding: 1px 5px; margin: 1px; }\n #sidebar .conf-bar { height: 6px; border-radius: 3px; background: #21262d; 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 #21262d; color: #8b949e; }\n #sidebar .edge-item span { color: #c9d1d9; }\n .hint { color: #484f58; font-size: 11px; margin-top: 8px; }\n #header { position: fixed; top: 10px; left: 10px; background: rgba(13,17,23,0.85);\n padding: 8px 12px; border-radius: 6px; font-size: 12px; border: 1px solid #30363d; }\n #header strong { color: #58a6ff; }\n </style>\n</head>\n<body>\n<div id=\"graph\">\n <div id=\"header\">\n <strong>Cartography</strong> \n <span style=\"color:#8b949e\">${nodes.length} Nodes · ${edges.length} Edges</span><br>\n <span style=\"color:#484f58;font-size:10px\">Scroll=zoom · Drag=pan · Click=details</span>\n </div>\n <svg></svg>\n</div>\n<div id=\"sidebar\">\n <h2>Infrastructure Map</h2>\n <p class=\"hint\">Klicke einen Node um Details anzuzeigen.</p>\n</div>\n<script>\nconst data = ${graphData};\n\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: '#da8bff', unknown: '#6c757d',\n};\n\nconst NODE_RADIUS = { saas_tool: 10, host: 11, database_server: 11, k8s_cluster: 13, default: 8 };\nconst radius = d => NODE_RADIUS[d.type] || NODE_RADIUS.default;\n\nconst sidebar = document.getElementById('sidebar');\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>Typ</td><td><span style=\"color:\\${c}\">\\${d.type}</span></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>Entdeckt via</td><td>\\${d.discoveredVia || '—'}</td></tr>\n <tr><td>Zeitpunkt</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>Verbindungen:</strong>'+edgeItems+'</div>' : ''}\n \\`;\n}\n\nconst svgEl = d3.select('svg');\nconst graphDiv = document.getElementById('graph');\nconst width = () => graphDiv.clientWidth;\nconst height = () => graphDiv.clientHeight;\nconst g = svgEl.append('g');\n\nsvgEl.call(d3.zoom().scaleExtent([0.1, 4]).on('zoom', e => g.attr('transform', e.transform)));\n\nconst sim = d3.forceSimulation(data.nodes)\n .force('link', d3.forceLink(data.links).id(d => d.id).distance(d => d.relationship === 'contains' ? 60 : 120))\n .force('charge', d3.forceManyBody().strength(-320))\n .force('center', d3.forceCenter(width() / 2, height() / 2))\n .force('collision', d3.forceCollide().radius(d => radius(d) + 20));\n\nconst link = g.append('g')\n .selectAll('line').data(data.links).join('line')\n .attr('class', 'link')\n .attr('stroke', d => d.confidence < 0.6 ? '#444' : '#555')\n .attr('stroke-dasharray', d => d.confidence < 0.6 ? '4 3' : null)\n .attr('stroke-width', d => d.confidence < 0.6 ? 1 : 1.5);\n\nlink.append('title').text(d => \\`\\${d.relationship} (conf:\\${d.confidence})\\n\\${d.evidence||''}\\`);\n\nconst node = g.append('g')\n .selectAll('g').data(data.nodes).join('g').attr('class', 'node')\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(); showNode(d); });\n\nnode.append('circle')\n .attr('r', radius)\n .attr('fill', d => TYPE_COLORS[d.type] || '#aaa')\n .attr('stroke', d => d3.color(TYPE_COLORS[d.type] || '#aaa').brighter(1).formatHex())\n .append('title').text(d => \\`\\${d.id}\\nconf:\\${d.confidence}\\`);\n\nnode.append('text').attr('dx', d => radius(d) + 4).attr('dy', '.35em').text(d => d.name);\n\nsim.on('tick', () => {\n link.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 node.attr('transform', d => \\`translate(\\${d.x},\\${d.y})\\`);\n});\n\nsvgEl.on('click', () => {\n sidebar.innerHTML = '<h2>Infrastructure Map</h2><p class=\"hint\">Klicke einen Node um Details anzuzeigen.</p>';\n});\n</script>\n</body>\n</html>`;\n}\n\n// ── SOP Markdown ─────────────────────────────────────────────────────────────\n\nexport function exportSOPMarkdown(sop: SOP): string {\n const lines: string[] = [\n `# ${sop.title}`,\n '',\n `**Beschreibung:** ${sop.description}`,\n `**Systeme:** ${sop.involvedSystems.join(', ')}`,\n `**Dauer:** ${sop.estimatedDuration}`,\n `**Häufigkeit:** ${sop.frequency}`,\n `**Confidence:** ${sop.confidence.toFixed(2)}`,\n '',\n '## Schritte',\n '',\n ];\n\n for (const step of sop.steps) {\n lines.push(`${step.order}. **${step.tool}**${step.target ? ` → \\`${step.target}\\`` : ''}`);\n lines.push(` ${step.instruction}`);\n if (step.notes) lines.push(` _${step.notes}_`);\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n// ── SOP Dashboard HTML ───────────────────────────────────────────────────────\n\nexport function exportSOPDashboard(sops: Array<SOP & { id: string; workflowId: string; generatedAt?: string }>): string {\n const sopsJson = JSON.stringify(sops.map(s => ({\n id: s.id,\n title: s.title,\n description: s.description,\n steps: s.steps,\n systems: s.involvedSystems,\n duration: s.estimatedDuration,\n frequency: s.frequency,\n confidence: s.confidence,\n generatedAt: s.generatedAt ?? new Date().toISOString(),\n })));\n\n // System frequency: how many SOPs reference each system\n const systemCount: Record<string, number> = {};\n for (const sop of sops) {\n for (const sys of sop.involvedSystems) {\n systemCount[sys] = (systemCount[sys] ?? 0) + 1;\n }\n }\n const systemsJson = JSON.stringify(\n Object.entries(systemCount).sort((a, b) => b[1] - a[1])\n );\n\n return `<!DOCTYPE html>\n<html lang=\"de\">\n<head>\n <meta charset=\"UTF-8\">\n <title>Cartography — SOP Dashboard</title>\n <style>\n * { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n background: #0d1117; color: #e6edf3;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, monospace;\n padding: 0; line-height: 1.6;\n }\n .header {\n background: linear-gradient(135deg, #161b22 0%, #1a1f2e 100%);\n border-bottom: 1px solid #30363d; padding: 32px 40px;\n }\n .header h1 { font-size: 24px; color: #58a6ff; margin-bottom: 8px; }\n .header .subtitle { color: #8b949e; font-size: 14px; }\n .stats-row {\n display: flex; gap: 24px; margin-top: 16px; flex-wrap: wrap;\n }\n .stat-card {\n background: #21262d; border: 1px solid #30363d; border-radius: 8px;\n padding: 12px 20px; min-width: 140px;\n }\n .stat-card .value { font-size: 28px; font-weight: 700; color: #58a6ff; }\n .stat-card .label { font-size: 11px; color: #8b949e; text-transform: uppercase; letter-spacing: 0.5px; }\n .container { max-width: 1200px; margin: 0 auto; padding: 24px 40px; }\n .section-title { font-size: 18px; color: #c9d1d9; margin: 32px 0 16px; border-bottom: 1px solid #21262d; padding-bottom: 8px; }\n /* Systems bar chart */\n .systems-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 24px; }\n .sys-tag {\n background: #21262d; border: 1px solid #30363d; border-radius: 6px;\n padding: 6px 12px; font-size: 12px; cursor: default;\n }\n .sys-tag .count { color: #58a6ff; font-weight: 600; margin-left: 4px; }\n /* SOP cards */\n .sop-card {\n background: #161b22; border: 1px solid #30363d; border-radius: 8px;\n margin-bottom: 16px; overflow: hidden; transition: border-color 0.2s;\n }\n .sop-card:hover { border-color: #58a6ff; }\n .sop-header {\n padding: 16px 20px; cursor: pointer; display: flex;\n justify-content: space-between; align-items: center;\n }\n .sop-header h3 { font-size: 16px; color: #e6edf3; }\n .sop-meta { display: flex; gap: 16px; align-items: center; font-size: 12px; color: #8b949e; }\n .sop-meta .freq { color: #3fb950; font-weight: 600; }\n .sop-meta .dur { color: #d29922; }\n .sop-meta .conf {\n display: inline-flex; align-items: center; gap: 4px;\n }\n .conf-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }\n .sop-body { display: none; padding: 0 20px 20px; border-top: 1px solid #21262d; }\n .sop-body.open { display: block; padding-top: 16px; }\n .sop-desc { color: #8b949e; font-size: 13px; margin-bottom: 12px; }\n .sop-systems { margin-bottom: 12px; }\n .sop-systems span { background: #0d419d33; color: #58a6ff; border-radius: 4px; padding: 2px 8px; font-size: 11px; margin-right: 4px; }\n .steps-list { list-style: none; counter-reset: step; }\n .steps-list li {\n counter-increment: step; position: relative;\n padding: 10px 12px 10px 44px; border-left: 2px solid #30363d;\n margin-left: 14px; font-size: 13px;\n }\n .steps-list li:last-child { border-left-color: transparent; }\n .steps-list li::before {\n content: counter(step);\n position: absolute; left: -14px; top: 8px;\n width: 26px; height: 26px; border-radius: 50%;\n background: #21262d; border: 2px solid #30363d;\n display: flex; align-items: center; justify-content: center;\n font-size: 12px; font-weight: 600; color: #58a6ff;\n }\n .step-tool { color: #d2a8ff; font-weight: 600; }\n .step-target { color: #7ee787; font-size: 12px; }\n .step-notes { color: #8b949e; font-style: italic; font-size: 12px; margin-top: 2px; }\n .step-instr { color: #c9d1d9; }\n .toggle-icon { color: #8b949e; font-size: 18px; transition: transform 0.2s; }\n .toggle-icon.open { transform: rotate(90deg); }\n .empty { color: #484f58; font-size: 14px; padding: 40px; text-align: center; }\n .gen-time { color: #484f58; font-size: 11px; margin-top: 8px; }\n </style>\n</head>\n<body>\n<div class=\"header\">\n <h1>SOP Dashboard</h1>\n <div class=\"subtitle\">Datasynx Cartography — Standard Operating Procedures</div>\n <div class=\"stats-row\">\n <div class=\"stat-card\"><div class=\"value\" id=\"sop-count\">0</div><div class=\"label\">SOPs</div></div>\n <div class=\"stat-card\"><div class=\"value\" id=\"step-count\">0</div><div class=\"label\">Total Steps</div></div>\n <div class=\"stat-card\"><div class=\"value\" id=\"sys-count\">0</div><div class=\"label\">Systems</div></div>\n <div class=\"stat-card\"><div class=\"value\" id=\"avg-conf\">—</div><div class=\"label\">Avg Confidence</div></div>\n </div>\n</div>\n<div class=\"container\">\n <h2 class=\"section-title\">Beteiligte Systeme</h2>\n <div class=\"systems-grid\" id=\"systems\"></div>\n\n <h2 class=\"section-title\">SOPs</h2>\n <div id=\"sop-list\"></div>\n</div>\n<script>\nconst sops = ${sopsJson};\nconst systems = ${systemsJson};\n\ndocument.getElementById('sop-count').textContent = sops.length;\ndocument.getElementById('step-count').textContent = sops.reduce((a, s) => a + s.steps.length, 0);\ndocument.getElementById('sys-count').textContent = systems.length;\nconst avgConf = sops.length > 0\n ? (sops.reduce((a, s) => a + s.confidence, 0) / sops.length * 100).toFixed(0) + '%'\n : '—';\ndocument.getElementById('avg-conf').textContent = avgConf;\n\nconst sysDiv = document.getElementById('systems');\nsystems.forEach(([name, count]) => {\n const el = document.createElement('div');\n el.className = 'sys-tag';\n el.innerHTML = name + '<span class=\"count\">x' + count + '</span>';\n sysDiv.appendChild(el);\n});\n\nconst listDiv = document.getElementById('sop-list');\nif (sops.length === 0) {\n listDiv.innerHTML = '<div class=\"empty\">Keine SOPs vorhanden. Shadow-Daemon starten und Workflows beobachten.</div>';\n}\n\nsops.forEach((sop, i) => {\n const confColor = sop.confidence >= 0.8 ? '#3fb950' : sop.confidence >= 0.5 ? '#d29922' : '#f85149';\n const card = document.createElement('div');\n card.className = 'sop-card';\n card.innerHTML = \\`\n <div class=\"sop-header\" onclick=\"toggle(\\${i})\">\n <h3>\\${sop.title}</h3>\n <div class=\"sop-meta\">\n <span class=\"freq\">\\${sop.frequency}</span>\n <span class=\"dur\">\\${sop.duration}</span>\n <span class=\"conf\"><span class=\"conf-dot\" style=\"background:\\${confColor}\"></span>\\${Math.round(sop.confidence*100)}%</span>\n <span class=\"toggle-icon\" id=\"icon-\\${i}\">▸</span>\n </div>\n </div>\n <div class=\"sop-body\" id=\"body-\\${i}\">\n <div class=\"sop-desc\">\\${sop.description}</div>\n <div class=\"sop-systems\">\\${sop.systems.map(s => '<span>'+s+'</span>').join('')}</div>\n <ol class=\"steps-list\">\n \\${sop.steps.map(st => \\`\n <li>\n <span class=\"step-tool\">\\${st.tool}</span>\n \\${st.target ? '<span class=\"step-target\"> → '+st.target+'</span>' : ''}\n <div class=\"step-instr\">\\${st.instruction}</div>\n \\${st.notes ? '<div class=\"step-notes\">'+st.notes+'</div>' : ''}\n </li>\n \\`).join('')}\n </ol>\n <div class=\"gen-time\">Generiert: \\${sop.generatedAt ? sop.generatedAt.substring(0,19).replace('T',' ') : '—'}</div>\n </div>\n \\`;\n listDiv.appendChild(card);\n});\n\nfunction toggle(i) {\n const body = document.getElementById('body-'+i);\n const icon = document.getElementById('icon-'+i);\n body.classList.toggle('open');\n icon.classList.toggle('open');\n}\n</script>\n</body>\n</html>`;\n}\n\n// ── exportAll ─────────────────────────────────────────────────────────────────\n\nexport function exportAll(\n db: CartographyDB,\n sessionId: string,\n outputDir: string,\n formats: string[] = ['mermaid', 'json', 'yaml', 'html', 'sops'],\n): void {\n mkdirSync(outputDir, { recursive: true });\n mkdirSync(join(outputDir, 'sops'), { recursive: true });\n mkdirSync(join(outputDir, 'workflows'), { recursive: true });\n\n const nodes = db.getNodes(sessionId);\n const edges = db.getEdges(sessionId);\n\n if (formats.includes('mermaid')) {\n writeFileSync(join(outputDir, 'topology.mermaid'), generateTopologyMermaid(nodes, edges));\n writeFileSync(join(outputDir, 'dependencies.mermaid'), generateDependencyMermaid(nodes, edges));\n process.stderr.write('✓ topology.mermaid, dependencies.mermaid\\n');\n }\n\n if (formats.includes('json')) {\n writeFileSync(join(outputDir, 'catalog.json'), exportJSON(db, sessionId));\n process.stderr.write('✓ catalog.json\\n');\n }\n\n if (formats.includes('yaml')) {\n writeFileSync(join(outputDir, 'catalog-info.yaml'), exportBackstageYAML(nodes, edges));\n process.stderr.write('✓ catalog-info.yaml\\n');\n }\n\n if (formats.includes('html')) {\n writeFileSync(join(outputDir, 'topology.html'), exportHTML(nodes, edges));\n process.stderr.write('✓ topology.html\\n');\n }\n\n if (formats.includes('sops')) {\n const sops = db.getSOPs(sessionId);\n for (const sop of sops) {\n const filename = sop.title.toLowerCase().replace(/[^a-z0-9]+/g, '-') + '.md';\n writeFileSync(join(outputDir, 'sops', filename), exportSOPMarkdown(sop));\n\n const wfFilename = `workflow-${sop.workflowId.substring(0, 8)}.mermaid`;\n writeFileSync(join(outputDir, 'workflows', wfFilename), generateWorkflowMermaid(sop));\n }\n if (sops.length > 0) {\n process.stderr.write(`✓ ${sops.length} SOPs + workflow diagrams\\n`);\n }\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/chunk-JAFRT2R6.js
DELETED
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/bookmarks.ts
|
|
4
|
-
import { homedir, tmpdir } from "os";
|
|
5
|
-
import { existsSync, readFileSync, readdirSync, copyFileSync } from "fs";
|
|
6
|
-
import { join } from "path";
|
|
7
|
-
function extractHost(rawUrl, source) {
|
|
8
|
-
try {
|
|
9
|
-
const u = new URL(rawUrl);
|
|
10
|
-
if (u.protocol !== "http:" && u.protocol !== "https:") return null;
|
|
11
|
-
const protocol = u.protocol === "https:" ? "https" : "http";
|
|
12
|
-
const port = u.port ? parseInt(u.port, 10) : protocol === "https" ? 443 : 80;
|
|
13
|
-
const hostname = u.hostname.toLowerCase();
|
|
14
|
-
if (!hostname || hostname === "localhost" || hostname === "127.0.0.1") return null;
|
|
15
|
-
return { hostname, port, protocol, source };
|
|
16
|
-
} catch {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
function walkChrome(node, source, out) {
|
|
21
|
-
if (node.type === "url" && node.url) {
|
|
22
|
-
const h = extractHost(node.url, source);
|
|
23
|
-
if (h) out.push(h);
|
|
24
|
-
}
|
|
25
|
-
if (node.children) {
|
|
26
|
-
for (const child of node.children) walkChrome(child, source, out);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
function readChromeLike(filePath, source) {
|
|
30
|
-
if (!existsSync(filePath)) return [];
|
|
31
|
-
try {
|
|
32
|
-
const raw = JSON.parse(readFileSync(filePath, "utf8"));
|
|
33
|
-
const out = [];
|
|
34
|
-
for (const root of Object.values(raw.roots)) {
|
|
35
|
-
if (root) walkChrome(root, source, out);
|
|
36
|
-
}
|
|
37
|
-
return out;
|
|
38
|
-
} catch {
|
|
39
|
-
return [];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
async function readFirefox(profileDir) {
|
|
43
|
-
const src = join(profileDir, "places.sqlite");
|
|
44
|
-
if (!existsSync(src)) return [];
|
|
45
|
-
const tmp = join(tmpdir(), `cartograph_ff_${Date.now()}.sqlite`);
|
|
46
|
-
try {
|
|
47
|
-
copyFileSync(src, tmp);
|
|
48
|
-
const { default: Database } = await import("better-sqlite3");
|
|
49
|
-
const db = new Database(tmp, { readonly: true, fileMustExist: true });
|
|
50
|
-
const rows = db.prepare(`
|
|
51
|
-
SELECT DISTINCT p.url
|
|
52
|
-
FROM moz_places p
|
|
53
|
-
JOIN moz_bookmarks b ON b.fk = p.id
|
|
54
|
-
WHERE b.type = 1 AND p.url NOT LIKE 'place:%'
|
|
55
|
-
LIMIT 3000
|
|
56
|
-
`).all();
|
|
57
|
-
db.close();
|
|
58
|
-
return rows.map((r) => extractHost(r.url, "firefox")).filter((h) => h !== null);
|
|
59
|
-
} catch {
|
|
60
|
-
return [];
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
var HOME = homedir();
|
|
64
|
-
var IS_MAC = process.platform === "darwin";
|
|
65
|
-
var CHROME_PATHS = IS_MAC ? [`${HOME}/Library/Application Support/Google/Chrome/Default/Bookmarks`] : [`${HOME}/.config/google-chrome/Default/Bookmarks`];
|
|
66
|
-
var EDGE_PATHS = IS_MAC ? [`${HOME}/Library/Application Support/Microsoft Edge/Default/Bookmarks`] : [`${HOME}/.config/microsoft-edge/Default/Bookmarks`];
|
|
67
|
-
var BRAVE_PATHS = IS_MAC ? [`${HOME}/Library/Application Support/BraveSoftware/Brave-Browser/Default/Bookmarks`] : [`${HOME}/.config/BraveSoftware/Brave-Browser/Default/Bookmarks`];
|
|
68
|
-
function firefoxProfileDirs() {
|
|
69
|
-
const base = IS_MAC ? `${HOME}/Library/Application Support/Firefox/Profiles` : `${HOME}/.mozilla/firefox`;
|
|
70
|
-
if (!existsSync(base)) return [];
|
|
71
|
-
try {
|
|
72
|
-
return readdirSync(base).filter((d) => d.includes(".default") || d.includes("-release")).map((d) => join(base, d));
|
|
73
|
-
} catch {
|
|
74
|
-
return [];
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
async function scanAllBookmarks() {
|
|
78
|
-
const all = [];
|
|
79
|
-
for (const p of CHROME_PATHS) all.push(...readChromeLike(p, "chrome"));
|
|
80
|
-
for (const p of EDGE_PATHS) all.push(...readChromeLike(p, "edge"));
|
|
81
|
-
for (const p of BRAVE_PATHS) all.push(...readChromeLike(p, "brave"));
|
|
82
|
-
for (const dir of firefoxProfileDirs()) {
|
|
83
|
-
all.push(...await readFirefox(dir));
|
|
84
|
-
}
|
|
85
|
-
const seen = /* @__PURE__ */ new Set();
|
|
86
|
-
return all.filter((h) => {
|
|
87
|
-
const key = h.hostname;
|
|
88
|
-
if (seen.has(key)) return false;
|
|
89
|
-
seen.add(key);
|
|
90
|
-
return true;
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export {
|
|
95
|
-
scanAllBookmarks
|
|
96
|
-
};
|
|
97
|
-
//# sourceMappingURL=chunk-JAFRT2R6.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bookmarks.ts"],"sourcesContent":["import { homedir, tmpdir } from 'node:os';\nimport { existsSync, readFileSync, readdirSync, copyFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nexport interface BookmarkHost {\n hostname: string;\n port: number;\n protocol: 'http' | 'https';\n source: string;\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 readFirefox(profileDir: string): Promise<BookmarkHost[]> {\n const src = join(profileDir, 'places.sqlite');\n if (!existsSync(src)) return [];\n const tmp = join(tmpdir(), `cartograph_ff_${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 }\n}\n\n// ── Platform paths ────────────────────────────────────────────────────────────\n\nconst HOME = homedir();\nconst IS_MAC = process.platform === 'darwin';\n\nconst CHROME_PATHS = IS_MAC\n ? [`${HOME}/Library/Application Support/Google/Chrome/Default/Bookmarks`]\n : [`${HOME}/.config/google-chrome/Default/Bookmarks`];\n\nconst EDGE_PATHS = IS_MAC\n ? [`${HOME}/Library/Application Support/Microsoft Edge/Default/Bookmarks`]\n : [`${HOME}/.config/microsoft-edge/Default/Bookmarks`];\n\nconst BRAVE_PATHS = IS_MAC\n ? [`${HOME}/Library/Application Support/BraveSoftware/Brave-Browser/Default/Bookmarks`]\n : [`${HOME}/.config/BraveSoftware/Brave-Browser/Default/Bookmarks`];\n\nfunction firefoxProfileDirs(): string[] {\n const base = IS_MAC\n ? `${HOME}/Library/Application Support/Firefox/Profiles`\n : `${HOME}/.mozilla/firefox`;\n if (!existsSync(base)) return [];\n try {\n return readdirSync(base)\n .filter(d => d.includes('.default') || d.includes('-release'))\n .map(d => join(base, d));\n } catch {\n return [];\n }\n}\n\n// ── Public API ────────────────────────────────────────────────────────────────\n\nexport async function scanAllBookmarks(): Promise<BookmarkHost[]> {\n const all: BookmarkHost[] = [];\n\n for (const p of CHROME_PATHS) all.push(...readChromeLike(p, 'chrome'));\n for (const p of EDGE_PATHS) all.push(...readChromeLike(p, 'edge'));\n for (const p of BRAVE_PATHS) all.push(...readChromeLike(p, 'brave'));\n\n for (const dir of firefoxProfileDirs()) {\n all.push(...await readFirefox(dir));\n }\n\n // Deduplicate by hostname (port not included — same host on 80+443 = same service)\n const seen = new Set<string>();\n return all.filter(h => {\n const key = h.hostname;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n"],"mappings":"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","names":[]}
|
|
File without changes
|
|
File without changes
|