@datasynx/agentic-ai-cartography 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +421 -99
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.js +232 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -665,7 +665,7 @@ function stripSensitive(target) {
|
|
|
665
665
|
return target.replace(/\/.*$/, "").replace(/\?.*$/, "").replace(/@.*:/, ":");
|
|
666
666
|
}
|
|
667
667
|
}
|
|
668
|
-
async function createCartographyTools(db, sessionId) {
|
|
668
|
+
async function createCartographyTools(db, sessionId, opts = {}) {
|
|
669
669
|
const sdk = await import("@anthropic-ai/claude-code");
|
|
670
670
|
const { tool, createSdkMcpServer } = sdk;
|
|
671
671
|
const tools = [
|
|
@@ -755,6 +755,20 @@ async function createCartographyTools(db, sessionId) {
|
|
|
755
755
|
db.updateTaskDescription(sessionId, args["description"]);
|
|
756
756
|
return { content: [{ type: "text", text: "\u2713 Beschreibung aktualisiert" }] };
|
|
757
757
|
}),
|
|
758
|
+
tool("ask_user", "R\xFCckfrage an den User stellen \u2014 bei Unklarheiten, fehlenden Credentials-Hinweisen oder wenn Kontext fehlt", {
|
|
759
|
+
question: z2.string().describe("Die Frage an den User (klar und konkret)"),
|
|
760
|
+
context: z2.string().optional().describe("Optionaler Zusatzkontext warum die Frage relevant ist")
|
|
761
|
+
}, async (args) => {
|
|
762
|
+
const question = args["question"];
|
|
763
|
+
const context = args["context"];
|
|
764
|
+
if (opts.onAskUser) {
|
|
765
|
+
const answer = await opts.onAskUser(question, context);
|
|
766
|
+
return { content: [{ type: "text", text: answer }] };
|
|
767
|
+
}
|
|
768
|
+
return {
|
|
769
|
+
content: [{ type: "text", text: "(Kein interaktiver Modus \u2014 bitte ohne diese Information fortfahren)" }]
|
|
770
|
+
};
|
|
771
|
+
}),
|
|
758
772
|
tool("scan_bookmarks", "Alle Browser-Lesezeichen scannen \u2014 nur Hostnamen, keine pers\xF6nlichen Daten", {
|
|
759
773
|
minConfidence: z2.number().min(0).max(1).default(0.5).optional()
|
|
760
774
|
}, async () => {
|
|
@@ -830,40 +844,54 @@ var safetyHook = async (input) => {
|
|
|
830
844
|
};
|
|
831
845
|
|
|
832
846
|
// src/agent.ts
|
|
833
|
-
async function runDiscovery(config, db, sessionId, onEvent) {
|
|
847
|
+
async function runDiscovery(config, db, sessionId, onEvent, onAskUser) {
|
|
834
848
|
const { query } = await import("@anthropic-ai/claude-code");
|
|
835
|
-
const tools = await createCartographyTools(db, sessionId);
|
|
836
|
-
const systemPrompt = `Du bist ein Infrastruktur-Discovery-Agent.
|
|
837
|
-
|
|
849
|
+
const tools = await createCartographyTools(db, sessionId, { onAskUser });
|
|
850
|
+
const systemPrompt = `Du bist ein Infrastruktur-Discovery-Agent. Kartographiere die gesamte Systemlandschaft \u2014 lokale Services UND SaaS-Tools des Users.
|
|
851
|
+
|
|
852
|
+
\u2501\u2501 PFLICHT-REIHENFOLGE \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
853
|
+
SCHRITT 1 \u2014 Browser-Lesezeichen (IMMER ZUERST):
|
|
854
|
+
scan_bookmarks() aufrufen \u2192 jede zur\xFCckgegebene Domain klassifizieren:
|
|
855
|
+
\u2022 Business-Tools (GitHub, Notion, Jira, Linear, Vercel, AWS, Datadog, etc.) \u2192 save_node als saas_tool
|
|
856
|
+
\u2022 Interne Hosts (IPs, custom.company.com:PORT) \u2192 save_node als web_service
|
|
857
|
+
\u2022 Pers\xF6nliches (Social Media, News, Streaming, Shopping) \u2192 IGNORIEREN, NICHT speichern
|
|
838
858
|
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
859
|
+
SCHRITT 2 \u2014 Lokale Infrastruktur:
|
|
860
|
+
ss -tlnp && ps aux \u2192 alle lauschenden Ports/Prozesse identifizieren
|
|
861
|
+
Jeden Service vertiefen: DB\u2192Schemas, API\u2192Endpoints, Queue\u2192Topics
|
|
862
|
+
|
|
863
|
+
SCHRITT 3 \u2014 Config-Files:
|
|
864
|
+
.env, docker-compose.yml, application.yml, kubernetes/*.yml
|
|
865
|
+
Nur Host:Port extrahieren \u2014 KEINE Credentials
|
|
866
|
+
|
|
867
|
+
SCHRITT 4 \u2014 R\xFCckfragen bei Unklarheit:
|
|
868
|
+
ask_user() nutzen wenn: Dienst unklar ist, Kontext fehlt, oder User Input sinnvoll w\xE4re
|
|
869
|
+
Beispiele: "Welche Umgebung ist das (dev/staging/prod)?", "Ist <host> ein internes Tool?"
|
|
870
|
+
|
|
871
|
+
SCHRITT 5 \u2014 Fertig wenn alle Spuren ersch\xF6pft.
|
|
872
|
+
\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
|
|
849
873
|
|
|
850
874
|
PORT-MAPPING: 5432=postgres, 3306=mysql, 27017=mongodb, 6379=redis,
|
|
851
875
|
9092=kafka, 5672=rabbitmq, 80/443/8080/3000=web_service,
|
|
852
876
|
9090=prometheus, 8500=consul, 8200=vault, 2379=etcd
|
|
853
877
|
|
|
854
878
|
REGELN:
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
879
|
+
\u2022 Nur read-only (ss, ps, cat, head, curl -s, docker inspect, kubectl get)
|
|
880
|
+
\u2022 Node IDs: "type:host:port" oder "type:name" \u2014 keine Pfade, keine Credentials
|
|
881
|
+
\u2022 saas_tool IDs: "saas_tool:github.com", "saas_tool:notion.so"
|
|
882
|
+
\u2022 Confidence: 0.9 direkt gesehen, 0.7 aus Config/Bookmarks, 0.5 Vermutung
|
|
883
|
+
\u2022 metadata erlaubt: { description, category, port, version } \u2014 keine Passw\xF6rter
|
|
884
|
+
\u2022 get_catalog vor save_node \u2192 Duplikate vermeiden
|
|
885
|
+
\u2022 Edges speichern wenn Verbindungen klar erkennbar sind
|
|
862
886
|
|
|
863
887
|
Entrypoints: ${config.entryPoints.join(", ")}`;
|
|
888
|
+
const initialPrompt = `Starte Discovery jetzt.
|
|
889
|
+
F\xFChre SOFORT als erstes scan_bookmarks aus \u2014 noch bevor du ss oder ps verwendest.
|
|
890
|
+
Dann systematisch lokale Services, dann Config-Files.
|
|
891
|
+
Nutze ask_user wenn du Kontext vom User brauchst.`;
|
|
864
892
|
let turnCount = 0;
|
|
865
893
|
for await (const msg of query({
|
|
866
|
-
prompt:
|
|
894
|
+
prompt: initialPrompt,
|
|
867
895
|
options: {
|
|
868
896
|
model: config.agentModel,
|
|
869
897
|
maxTurns: config.maxTurns,
|
|
@@ -874,7 +902,8 @@ Entrypoints: ${config.entryPoints.join(", ")}`;
|
|
|
874
902
|
"mcp__cartograph__save_node",
|
|
875
903
|
"mcp__cartograph__save_edge",
|
|
876
904
|
"mcp__cartograph__get_catalog",
|
|
877
|
-
"mcp__cartograph__scan_bookmarks"
|
|
905
|
+
"mcp__cartograph__scan_bookmarks",
|
|
906
|
+
"mcp__cartograph__ask_user"
|
|
878
907
|
],
|
|
879
908
|
hooks: {
|
|
880
909
|
PreToolUse: [{ matcher: "Bash", hooks: [safetyHook] }]
|
|
@@ -1024,6 +1053,25 @@ function clusterTasks(tasks) {
|
|
|
1024
1053
|
// src/exporter.ts
|
|
1025
1054
|
import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
1026
1055
|
import { join as join3 } from "path";
|
|
1056
|
+
function nodeLayer(type) {
|
|
1057
|
+
if (type === "saas_tool") return "saas";
|
|
1058
|
+
if (["web_service", "api_endpoint"].includes(type)) return "web";
|
|
1059
|
+
if (["database_server", "database", "table", "cache_server"].includes(type)) return "data";
|
|
1060
|
+
if (["message_broker", "queue", "topic"].includes(type)) return "messaging";
|
|
1061
|
+
if (["host", "container", "pod", "k8s_cluster"].includes(type)) return "infra";
|
|
1062
|
+
if (type === "config_file") return "config";
|
|
1063
|
+
return "other";
|
|
1064
|
+
}
|
|
1065
|
+
var LAYER_LABELS = {
|
|
1066
|
+
saas: "\u2601 SaaS Tools",
|
|
1067
|
+
web: "\u{1F310} Web / API",
|
|
1068
|
+
data: "\u{1F5C4} Data Layer",
|
|
1069
|
+
messaging: "\u{1F4E8} Messaging",
|
|
1070
|
+
infra: "\u{1F5A5} Infrastructure",
|
|
1071
|
+
config: "\u{1F4C4} Config",
|
|
1072
|
+
other: "\u2753 Sonstige"
|
|
1073
|
+
};
|
|
1074
|
+
var LAYER_ORDER = ["saas", "web", "data", "messaging", "infra", "config", "other"];
|
|
1027
1075
|
var MERMAID_ICONS = {
|
|
1028
1076
|
host: "\u{1F5A5}",
|
|
1029
1077
|
database_server: "\u{1F5C4}",
|
|
@@ -1076,18 +1124,18 @@ function nodeLabel(node) {
|
|
|
1076
1124
|
const parts = node.id.split(":");
|
|
1077
1125
|
const location = parts.length >= 3 ? `${parts[1]}:${parts[2]}` : parts[1] ?? "";
|
|
1078
1126
|
const conf = `${Math.round(node.confidence * 100)}%`;
|
|
1079
|
-
const
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
if (!groups.has(group)) groups.set(group, []);
|
|
1088
|
-
groups.get(group).push(node);
|
|
1127
|
+
const meta = node.metadata;
|
|
1128
|
+
const extras = [];
|
|
1129
|
+
for (const key of ["category", "version", "description"]) {
|
|
1130
|
+
const v = meta[key];
|
|
1131
|
+
if (typeof v === "string" && v.length > 0) {
|
|
1132
|
+
extras.push(v.substring(0, 28));
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1089
1135
|
}
|
|
1090
|
-
|
|
1136
|
+
const locLine = location ? `<br/><small>${location}</small>` : "";
|
|
1137
|
+
const extraLine = extras.length ? `<br/><small>${extras[0]}</small>` : "";
|
|
1138
|
+
return `["${icon} <b>${node.name}</b>${locLine}${extraLine}<br/><small>${node.type} \xB7 ${conf}</small>"]`;
|
|
1091
1139
|
}
|
|
1092
1140
|
function generateTopologyMermaid(nodes, edges) {
|
|
1093
1141
|
if (nodes.length === 0) return 'graph TB\n empty["No nodes discovered yet"]';
|
|
@@ -1098,25 +1146,29 @@ function generateTopologyMermaid(nodes, edges) {
|
|
|
1098
1146
|
lines.push(` classDef ${type.replace(/_/g, "")} ${style}`);
|
|
1099
1147
|
}
|
|
1100
1148
|
lines.push("");
|
|
1101
|
-
const
|
|
1102
|
-
for (const
|
|
1103
|
-
const
|
|
1104
|
-
if (
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1149
|
+
const layerMap = /* @__PURE__ */ new Map();
|
|
1150
|
+
for (const node of nodes) {
|
|
1151
|
+
const layer = nodeLayer(node.type);
|
|
1152
|
+
if (!layerMap.has(layer)) layerMap.set(layer, []);
|
|
1153
|
+
layerMap.get(layer).push(node);
|
|
1154
|
+
}
|
|
1155
|
+
for (const layerKey of LAYER_ORDER) {
|
|
1156
|
+
const layerNodes = layerMap.get(layerKey);
|
|
1157
|
+
if (!layerNodes || layerNodes.length === 0) continue;
|
|
1158
|
+
const label = LAYER_LABELS[layerKey] ?? layerKey;
|
|
1159
|
+
lines.push(` subgraph ${layerKey}["${label}"]`);
|
|
1160
|
+
for (const node of layerNodes) {
|
|
1161
|
+
lines.push(` ${sanitize(node.id)}${nodeLabel(node)}:::${node.type.replace(/_/g, "")}`);
|
|
1110
1162
|
}
|
|
1111
|
-
|
|
1163
|
+
lines.push(" end");
|
|
1112
1164
|
lines.push("");
|
|
1113
1165
|
}
|
|
1114
1166
|
for (const edge of edges) {
|
|
1115
1167
|
const src = sanitize(edge.sourceId);
|
|
1116
1168
|
const tgt = sanitize(edge.targetId);
|
|
1117
1169
|
const label = EDGE_LABELS[edge.relationship] ?? edge.relationship;
|
|
1118
|
-
const
|
|
1119
|
-
lines.push(` ${src}
|
|
1170
|
+
const arrow = edge.confidence < 0.6 ? `-. "${label}" .->` : `-->|"${label}"|`;
|
|
1171
|
+
lines.push(` ${src} ${arrow} ${tgt}`);
|
|
1120
1172
|
}
|
|
1121
1173
|
return lines.join("\n");
|
|
1122
1174
|
}
|
|
@@ -1206,8 +1258,23 @@ function exportJSON(db, sessionId) {
|
|
|
1206
1258
|
}
|
|
1207
1259
|
function exportHTML(nodes, edges) {
|
|
1208
1260
|
const graphData = JSON.stringify({
|
|
1209
|
-
nodes: nodes.map((n) => ({
|
|
1210
|
-
|
|
1261
|
+
nodes: nodes.map((n) => ({
|
|
1262
|
+
id: n.id,
|
|
1263
|
+
name: n.name,
|
|
1264
|
+
type: n.type,
|
|
1265
|
+
confidence: n.confidence,
|
|
1266
|
+
discoveredVia: n.discoveredVia,
|
|
1267
|
+
discoveredAt: n.discoveredAt,
|
|
1268
|
+
tags: n.tags,
|
|
1269
|
+
metadata: n.metadata
|
|
1270
|
+
})),
|
|
1271
|
+
links: edges.map((e) => ({
|
|
1272
|
+
source: e.sourceId,
|
|
1273
|
+
target: e.targetId,
|
|
1274
|
+
relationship: e.relationship,
|
|
1275
|
+
confidence: e.confidence,
|
|
1276
|
+
evidence: e.evidence
|
|
1277
|
+
}))
|
|
1211
1278
|
});
|
|
1212
1279
|
return `<!DOCTYPE html>
|
|
1213
1280
|
<html lang="de">
|
|
@@ -1216,22 +1283,50 @@ function exportHTML(nodes, edges) {
|
|
|
1216
1283
|
<title>Cartography \u2014 Topology</title>
|
|
1217
1284
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
1218
1285
|
<style>
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
.link { stroke
|
|
1224
|
-
|
|
1225
|
-
|
|
1286
|
+
* { box-sizing: border-box; }
|
|
1287
|
+
body { margin: 0; background: #0d1117; color: #e6edf3; font-family: 'SF Mono', 'Fira Code', monospace; display: flex; }
|
|
1288
|
+
#graph { flex: 1; height: 100vh; }
|
|
1289
|
+
svg { width: 100%; height: 100%; }
|
|
1290
|
+
.link { stroke-opacity: 0.5; }
|
|
1291
|
+
.link-label { font-size: 9px; fill: #8b949e; }
|
|
1292
|
+
.node circle { stroke-width: 2px; cursor: pointer; transition: r 0.15s; }
|
|
1293
|
+
.node circle:hover { r: 14; }
|
|
1294
|
+
.node text { font-size: 11px; fill: #c9d1d9; pointer-events: none; }
|
|
1295
|
+
/* \u2500\u2500 Sidebar \u2500\u2500 */
|
|
1296
|
+
#sidebar {
|
|
1297
|
+
width: 300px; min-width: 300px; height: 100vh; overflow-y: auto;
|
|
1298
|
+
background: #161b22; border-left: 1px solid #30363d;
|
|
1299
|
+
padding: 16px; font-size: 12px; line-height: 1.6;
|
|
1300
|
+
}
|
|
1301
|
+
#sidebar h2 { margin: 0 0 8px; font-size: 14px; color: #58a6ff; }
|
|
1302
|
+
#sidebar .meta-table { width: 100%; border-collapse: collapse; }
|
|
1303
|
+
#sidebar .meta-table td { padding: 3px 6px; border-bottom: 1px solid #21262d; vertical-align: top; }
|
|
1304
|
+
#sidebar .meta-table td:first-child { color: #8b949e; white-space: nowrap; width: 90px; }
|
|
1305
|
+
#sidebar .tag { display: inline-block; background: #21262d; border-radius: 3px; padding: 1px 5px; margin: 1px; }
|
|
1306
|
+
#sidebar .conf-bar { height: 6px; border-radius: 3px; background: #21262d; margin-top: 3px; }
|
|
1307
|
+
#sidebar .conf-fill { height: 100%; border-radius: 3px; }
|
|
1308
|
+
#sidebar .edges-list { margin-top: 12px; }
|
|
1309
|
+
#sidebar .edge-item { padding: 4px 0; border-bottom: 1px solid #21262d; color: #8b949e; }
|
|
1310
|
+
#sidebar .edge-item span { color: #c9d1d9; }
|
|
1311
|
+
.hint { color: #484f58; font-size: 11px; margin-top: 8px; }
|
|
1312
|
+
#header { position: fixed; top: 10px; left: 10px; background: rgba(13,17,23,0.85);
|
|
1313
|
+
padding: 8px 12px; border-radius: 6px; font-size: 12px; border: 1px solid #30363d; }
|
|
1314
|
+
#header strong { color: #58a6ff; }
|
|
1226
1315
|
</style>
|
|
1227
1316
|
</head>
|
|
1228
1317
|
<body>
|
|
1229
|
-
<div id="
|
|
1230
|
-
<
|
|
1231
|
-
|
|
1232
|
-
|
|
1318
|
+
<div id="graph">
|
|
1319
|
+
<div id="header">
|
|
1320
|
+
<strong>Cartography</strong>
|
|
1321
|
+
<span style="color:#8b949e">${nodes.length} Nodes \xB7 ${edges.length} Edges</span><br>
|
|
1322
|
+
<span style="color:#484f58;font-size:10px">Scroll=zoom \xB7 Drag=pan \xB7 Click=details</span>
|
|
1323
|
+
</div>
|
|
1324
|
+
<svg></svg>
|
|
1325
|
+
</div>
|
|
1326
|
+
<div id="sidebar">
|
|
1327
|
+
<h2>Infrastructure Map</h2>
|
|
1328
|
+
<p class="hint">Klicke einen Node um Details anzuzeigen.</p>
|
|
1233
1329
|
</div>
|
|
1234
|
-
<svg></svg>
|
|
1235
1330
|
<script>
|
|
1236
1331
|
const data = ${graphData};
|
|
1237
1332
|
|
|
@@ -1240,41 +1335,100 @@ const TYPE_COLORS = {
|
|
|
1240
1335
|
web_service: '#6bcb77', api_endpoint: '#4d96ff', cache_server: '#ffd93d',
|
|
1241
1336
|
message_broker: '#c77dff', queue: '#e0aaff', topic: '#9d4edd',
|
|
1242
1337
|
container: '#48cae4', pod: '#00b4d8', k8s_cluster: '#0077b6',
|
|
1243
|
-
config_file: '#adb5bd', unknown: '#6c757d',
|
|
1338
|
+
config_file: '#adb5bd', saas_tool: '#da8bff', unknown: '#6c757d',
|
|
1244
1339
|
};
|
|
1245
1340
|
|
|
1246
|
-
const
|
|
1247
|
-
const
|
|
1248
|
-
|
|
1341
|
+
const NODE_RADIUS = { saas_tool: 10, host: 11, database_server: 11, k8s_cluster: 13, default: 8 };
|
|
1342
|
+
const radius = d => NODE_RADIUS[d.type] || NODE_RADIUS.default;
|
|
1343
|
+
|
|
1344
|
+
const sidebar = document.getElementById('sidebar');
|
|
1345
|
+
|
|
1346
|
+
function showNode(d) {
|
|
1347
|
+
const c = TYPE_COLORS[d.type] || '#aaa';
|
|
1348
|
+
const confPct = Math.round(d.confidence * 100);
|
|
1349
|
+
const tags = (d.tags || []).map(t => \`<span class="tag">\${t}</span>\`).join('');
|
|
1350
|
+
const metaRows = Object.entries(d.metadata || {})
|
|
1351
|
+
.filter(([,v]) => v !== null && v !== undefined && String(v).length > 0)
|
|
1352
|
+
.map(([k,v]) => \`<tr><td>\${k}</td><td>\${JSON.stringify(v)}</td></tr>\`)
|
|
1353
|
+
.join('');
|
|
1354
|
+
const related = data.links.filter(l =>
|
|
1355
|
+
(l.source.id||l.source) === d.id || (l.target.id||l.target) === d.id
|
|
1356
|
+
);
|
|
1357
|
+
const edgeItems = related.map(l => {
|
|
1358
|
+
const isOut = (l.source.id||l.source) === d.id;
|
|
1359
|
+
const other = isOut ? (l.target.id||l.target) : (l.source.id||l.source);
|
|
1360
|
+
return \`<div class="edge-item">\${isOut ? '\u2192' : '\u2190'} <span>\${other}</span> <small>[\${l.relationship}]</small></div>\`;
|
|
1361
|
+
}).join('');
|
|
1362
|
+
|
|
1363
|
+
sidebar.innerHTML = \`
|
|
1364
|
+
<h2>\${d.name}</h2>
|
|
1365
|
+
<table class="meta-table">
|
|
1366
|
+
<tr><td>ID</td><td style="font-size:10px;word-break:break-all">\${d.id}</td></tr>
|
|
1367
|
+
<tr><td>Typ</td><td><span style="color:\${c}">\${d.type}</span></td></tr>
|
|
1368
|
+
<tr><td>Confidence</td><td>
|
|
1369
|
+
\${confPct}%
|
|
1370
|
+
<div class="conf-bar"><div class="conf-fill" style="width:\${confPct}%;background:\${c}"></div></div>
|
|
1371
|
+
</td></tr>
|
|
1372
|
+
<tr><td>Entdeckt via</td><td>\${d.discoveredVia || '\u2014'}</td></tr>
|
|
1373
|
+
<tr><td>Zeitpunkt</td><td>\${d.discoveredAt ? d.discoveredAt.substring(0,19).replace('T',' ') : '\u2014'}</td></tr>
|
|
1374
|
+
\${tags ? '<tr><td>Tags</td><td>'+tags+'</td></tr>' : ''}
|
|
1375
|
+
\${metaRows}
|
|
1376
|
+
</table>
|
|
1377
|
+
\${related.length > 0 ? '<div class="edges-list"><strong>Verbindungen:</strong>'+edgeItems+'</div>' : ''}
|
|
1378
|
+
\`;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
const svgEl = d3.select('svg');
|
|
1382
|
+
const graphDiv = document.getElementById('graph');
|
|
1383
|
+
const width = () => graphDiv.clientWidth;
|
|
1384
|
+
const height = () => graphDiv.clientHeight;
|
|
1385
|
+
const g = svgEl.append('g');
|
|
1249
1386
|
|
|
1250
|
-
|
|
1387
|
+
svgEl.call(d3.zoom().scaleExtent([0.1, 4]).on('zoom', e => g.attr('transform', e.transform)));
|
|
1251
1388
|
|
|
1252
1389
|
const sim = d3.forceSimulation(data.nodes)
|
|
1253
|
-
.force('link', d3.forceLink(data.links).id(d => d.id).distance(
|
|
1254
|
-
.force('charge', d3.forceManyBody().strength(-
|
|
1255
|
-
.force('center', d3.forceCenter(width / 2, height / 2))
|
|
1390
|
+
.force('link', d3.forceLink(data.links).id(d => d.id).distance(d => d.relationship === 'contains' ? 60 : 120))
|
|
1391
|
+
.force('charge', d3.forceManyBody().strength(-320))
|
|
1392
|
+
.force('center', d3.forceCenter(width() / 2, height() / 2))
|
|
1393
|
+
.force('collision', d3.forceCollide().radius(d => radius(d) + 20));
|
|
1256
1394
|
|
|
1257
|
-
const link = g.append('g')
|
|
1258
|
-
.data(data.links).join('line')
|
|
1395
|
+
const link = g.append('g')
|
|
1396
|
+
.selectAll('line').data(data.links).join('line')
|
|
1397
|
+
.attr('class', 'link')
|
|
1398
|
+
.attr('stroke', d => d.confidence < 0.6 ? '#444' : '#555')
|
|
1399
|
+
.attr('stroke-dasharray', d => d.confidence < 0.6 ? '4 3' : null)
|
|
1400
|
+
.attr('stroke-width', d => d.confidence < 0.6 ? 1 : 1.5);
|
|
1259
1401
|
|
|
1260
|
-
|
|
1261
|
-
|
|
1402
|
+
link.append('title').text(d => \`\${d.relationship} (conf:\${d.confidence})
|
|
1403
|
+
\${d.evidence||''}\`);
|
|
1404
|
+
|
|
1405
|
+
const node = g.append('g')
|
|
1406
|
+
.selectAll('g').data(data.nodes).join('g').attr('class', 'node')
|
|
1262
1407
|
.call(d3.drag()
|
|
1263
1408
|
.on('start', (e, d) => { if (!e.active) sim.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
|
|
1264
1409
|
.on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
|
|
1265
1410
|
.on('end', (e, d) => { if (!e.active) sim.alphaTarget(0); d.fx = null; d.fy = null; })
|
|
1266
|
-
)
|
|
1411
|
+
)
|
|
1412
|
+
.on('click', (e, d) => { e.stopPropagation(); showNode(d); });
|
|
1413
|
+
|
|
1414
|
+
node.append('circle')
|
|
1415
|
+
.attr('r', radius)
|
|
1416
|
+
.attr('fill', d => TYPE_COLORS[d.type] || '#aaa')
|
|
1417
|
+
.attr('stroke', d => d3.color(TYPE_COLORS[d.type] || '#aaa').brighter(1).formatHex())
|
|
1418
|
+
.append('title').text(d => \`\${d.id}
|
|
1419
|
+
conf:\${d.confidence}\`);
|
|
1267
1420
|
|
|
1268
|
-
node.append('
|
|
1269
|
-
node.append('text').attr('dx', 12).attr('dy', '.35em').text(d => d.name);
|
|
1270
|
-
node.append('title').text(d => \`\${d.type}: \${d.id}
|
|
1271
|
-
Confidence: \${d.confidence}\`);
|
|
1421
|
+
node.append('text').attr('dx', d => radius(d) + 4).attr('dy', '.35em').text(d => d.name);
|
|
1272
1422
|
|
|
1273
1423
|
sim.on('tick', () => {
|
|
1274
1424
|
link.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
|
|
1275
1425
|
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
|
|
1276
1426
|
node.attr('transform', d => \`translate(\${d.x},\${d.y})\`);
|
|
1277
1427
|
});
|
|
1428
|
+
|
|
1429
|
+
svgEl.on('click', () => {
|
|
1430
|
+
sidebar.innerHTML = '<h2>Infrastructure Map</h2><p class="hint">Klicke einen Node um Details anzuzeigen.</p>';
|
|
1431
|
+
});
|
|
1278
1432
|
</script>
|
|
1279
1433
|
</body>
|
|
1280
1434
|
</html>`;
|
|
@@ -1792,10 +1946,17 @@ if (process.env.CARTOGRAPHYY_DAEMON === "1") {
|
|
|
1792
1946
|
} else {
|
|
1793
1947
|
main();
|
|
1794
1948
|
}
|
|
1949
|
+
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
1950
|
+
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
1951
|
+
var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
1952
|
+
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
1953
|
+
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
1954
|
+
var magenta = (s) => `\x1B[35m${s}\x1B[0m`;
|
|
1955
|
+
var red = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
1795
1956
|
function main() {
|
|
1796
1957
|
const program = new Command();
|
|
1797
1958
|
const CMD = "datasynx-cartography";
|
|
1798
|
-
const VERSION = "0.1.
|
|
1959
|
+
const VERSION = "0.1.8";
|
|
1799
1960
|
program.name(CMD).description("AI-powered Infrastructure Cartography & SOP Generation").version(VERSION);
|
|
1800
1961
|
program.command("discover").description("Infrastruktur scannen und kartographieren").option("--entry <hosts...>", "Startpunkte", ["localhost"]).option("--depth <n>", "Max Tiefe", "8").option("--max-turns <n>", "Max Agent-Turns", "50").option("--model <m>", "Agent-Model", "claude-sonnet-4-5-20250929").option("--org <name>", "Organisation (f\xFCr Backstage)").option("-o, --output <dir>", "Output-Dir", "./datasynx-output").option("--db <path>", "DB-Pfad").option("-v, --verbose", "Agent-Reasoning anzeigen", false).action(async (opts) => {
|
|
1801
1962
|
checkPrerequisites();
|
|
@@ -1813,12 +1974,6 @@ function main() {
|
|
|
1813
1974
|
const db = new CartographyDB(config.dbPath);
|
|
1814
1975
|
const sessionId = db.createSession("discover", config);
|
|
1815
1976
|
const w = process.stderr.write.bind(process.stderr);
|
|
1816
|
-
const bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
1817
|
-
const dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
1818
|
-
const cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
1819
|
-
const green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
1820
|
-
const yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
1821
|
-
const magenta = (s) => `\x1B[35m${s}\x1B[0m`;
|
|
1822
1977
|
const SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1823
1978
|
let spinIdx = 0;
|
|
1824
1979
|
let spinnerTimer = null;
|
|
@@ -1892,6 +2047,12 @@ function main() {
|
|
|
1892
2047
|
startSpinner(`Turn ${turnNum}/${config.maxTurns} ${dim(`nodes:${nodeCount} edges:${edgeCount}`)}`);
|
|
1893
2048
|
} else if (toolName === "get_catalog") {
|
|
1894
2049
|
startSpinner(`Catalog-Check ${dim("(Duplikate vermeiden)")}`);
|
|
2050
|
+
} else if (toolName === "scan_bookmarks") {
|
|
2051
|
+
logLine(cyan("\u{1F516}"), `Browser-Lesezeichen werden gescannt\u2026`);
|
|
2052
|
+
startSpinner(`scan_bookmarks`);
|
|
2053
|
+
} else if (toolName === "ask_user") {
|
|
2054
|
+
const q = (event.input["question"] ?? "").substring(0, 100);
|
|
2055
|
+
logLine(yellow("?"), `${bold("Agent fragt:")} ${q}`);
|
|
1895
2056
|
} else {
|
|
1896
2057
|
startSpinner(`${toolName}...`);
|
|
1897
2058
|
}
|
|
@@ -1904,8 +2065,28 @@ function main() {
|
|
|
1904
2065
|
break;
|
|
1905
2066
|
}
|
|
1906
2067
|
};
|
|
2068
|
+
const onAskUser = async (question, context) => {
|
|
2069
|
+
stopSpinner();
|
|
2070
|
+
w("\n");
|
|
2071
|
+
w(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
2072
|
+
w(` ${yellow(bold("?"))} ${bold("Agent fragt:")} ${question}
|
|
2073
|
+
`);
|
|
2074
|
+
if (context) w(` ${dim(context)}
|
|
2075
|
+
`);
|
|
2076
|
+
if (!process.stdin.isTTY) {
|
|
2077
|
+
w(` ${dim("(Kein Terminal \u2014 Agent f\xE4hrt ohne Antwort fort)")}
|
|
2078
|
+
|
|
2079
|
+
`);
|
|
2080
|
+
return "(Kein interaktiver Modus \u2014 bitte ohne diese Information fortfahren)";
|
|
2081
|
+
}
|
|
2082
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
2083
|
+
const answer = await new Promise((resolve2) => rl.question(` ${cyan("\u2192")} `, resolve2));
|
|
2084
|
+
rl.close();
|
|
2085
|
+
w("\n");
|
|
2086
|
+
return answer || "(Keine Antwort \u2014 bitte fortfahren)";
|
|
2087
|
+
};
|
|
1907
2088
|
try {
|
|
1908
|
-
await runDiscovery(config, db, sessionId, handleEvent);
|
|
2089
|
+
await runDiscovery(config, db, sessionId, handleEvent, onAskUser);
|
|
1909
2090
|
} catch (err) {
|
|
1910
2091
|
stopSpinner();
|
|
1911
2092
|
w(`
|
|
@@ -2141,13 +2322,154 @@ Session: ${session.id}
|
|
|
2141
2322
|
}
|
|
2142
2323
|
db.close();
|
|
2143
2324
|
});
|
|
2325
|
+
program.command("overview").description("\xDCbersicht aller Cartographies + SOPs").option("--db <path>", "DB-Pfad").action((opts) => {
|
|
2326
|
+
const config = defaultConfig();
|
|
2327
|
+
const db = new CartographyDB(opts.db ?? config.dbPath);
|
|
2328
|
+
const sessions = db.getSessions();
|
|
2329
|
+
const b = bold, d = dim;
|
|
2330
|
+
const w = (s) => process.stdout.write(s);
|
|
2331
|
+
w("\n");
|
|
2332
|
+
w(` ${b("CARTOGRAPHY OVERVIEW")}
|
|
2333
|
+
`);
|
|
2334
|
+
w(d(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
2335
|
+
if (sessions.length === 0) {
|
|
2336
|
+
w(` ${d("Noch keine Sessions. Starte mit:")} ${green("datasynx-cartography discover")}
|
|
2337
|
+
|
|
2338
|
+
`);
|
|
2339
|
+
db.close();
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
let totalNodes = 0, totalEdges = 0, totalSops = 0;
|
|
2343
|
+
for (const s of sessions) {
|
|
2344
|
+
const st = db.getStats(s.id);
|
|
2345
|
+
totalNodes += st.nodes;
|
|
2346
|
+
totalEdges += st.edges;
|
|
2347
|
+
totalSops += db.getSOPs(s.id).length;
|
|
2348
|
+
}
|
|
2349
|
+
w(` ${b(String(sessions.length))} Sessions \xB7 ${b(String(totalNodes))} Nodes \xB7 `);
|
|
2350
|
+
w(`${b(String(totalEdges))} Edges \xB7 ${b(String(totalSops))} SOPs
|
|
2351
|
+
|
|
2352
|
+
`);
|
|
2353
|
+
for (const session of sessions) {
|
|
2354
|
+
const stats = db.getStats(session.id);
|
|
2355
|
+
const nodes = db.getNodes(session.id);
|
|
2356
|
+
const sops = db.getSOPs(session.id);
|
|
2357
|
+
const status = session.completedAt ? green("\u2713") : yellow("\u25CF");
|
|
2358
|
+
const age = session.startedAt.substring(0, 16).replace("T", " ");
|
|
2359
|
+
const sid = cyan(session.id.substring(0, 8));
|
|
2360
|
+
w(` ${status} ${sid} ${b("[" + session.mode + "]")} ${d(age)}
|
|
2361
|
+
`);
|
|
2362
|
+
w(` ${d("Nodes: " + stats.nodes + " Edges: " + stats.edges + " SOPs: " + sops.length)}
|
|
2363
|
+
`);
|
|
2364
|
+
const byType = /* @__PURE__ */ new Map();
|
|
2365
|
+
for (const n of nodes) byType.set(n.type, (byType.get(n.type) ?? 0) + 1);
|
|
2366
|
+
if (byType.size > 0) {
|
|
2367
|
+
const parts = [...byType.entries()].map(([t, c]) => `${t}:${c}`).join(" ");
|
|
2368
|
+
w(` ${d(parts)}
|
|
2369
|
+
`);
|
|
2370
|
+
}
|
|
2371
|
+
const topNodes = nodes.slice(0, 5).map((n) => n.id).join(", ");
|
|
2372
|
+
if (topNodes) w(` ${d("Nodes: " + topNodes + (nodes.length > 5 ? " \u2026" : ""))}
|
|
2373
|
+
`);
|
|
2374
|
+
for (const sop of sops.slice(0, 3)) {
|
|
2375
|
+
w(` ${green("\u25BA")} ${sop.title} ${d("(" + sop.estimatedDuration + ")")}
|
|
2376
|
+
`);
|
|
2377
|
+
}
|
|
2378
|
+
if (sops.length > 3) w(` ${d("\u2026 +" + (sops.length - 3) + " weitere SOPs")}
|
|
2379
|
+
`);
|
|
2380
|
+
w("\n");
|
|
2381
|
+
}
|
|
2382
|
+
db.close();
|
|
2383
|
+
});
|
|
2384
|
+
program.command("chat [session-id]").description("Interaktiver Chat \xFCber die kartographierte Infrastruktur").option("--db <path>", "DB-Pfad").option("--model <m>", "Model", "claude-sonnet-4-5-20250929").action(async (sessionIdArg, opts) => {
|
|
2385
|
+
const config = defaultConfig();
|
|
2386
|
+
const db = new CartographyDB(opts.db ?? config.dbPath);
|
|
2387
|
+
const sessions = db.getSessions();
|
|
2388
|
+
const session = sessionIdArg ? sessions.find((s) => s.id.startsWith(sessionIdArg)) : sessions.filter((s) => s.completedAt).at(-1) ?? sessions.at(-1);
|
|
2389
|
+
if (!session) {
|
|
2390
|
+
process.stderr.write("Keine Session gefunden. F\xFChre zuerst discover aus.\n");
|
|
2391
|
+
db.close();
|
|
2392
|
+
return;
|
|
2393
|
+
}
|
|
2394
|
+
const nodes = db.getNodes(session.id);
|
|
2395
|
+
const edges = db.getEdges(session.id);
|
|
2396
|
+
const sops = db.getSOPs(session.id);
|
|
2397
|
+
const w = (s) => process.stdout.write(s);
|
|
2398
|
+
w("\n");
|
|
2399
|
+
w(dim(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2400
|
+
`));
|
|
2401
|
+
w(` ${bold("CARTOGRAPHY CHAT")} ${dim("Session " + session.id.substring(0, 8))}
|
|
2402
|
+
`);
|
|
2403
|
+
w(` ${dim(String(nodes.length) + " Nodes \xB7 " + edges.length + " Edges \xB7 " + sops.length + " SOPs")}
|
|
2404
|
+
`);
|
|
2405
|
+
w(dim(` \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2406
|
+
`));
|
|
2407
|
+
w(` ${dim("Frage alles \xFCber deine Infrastruktur. exit = beenden.\n\n")}`);
|
|
2408
|
+
const Anthropic = (await import("@anthropic-ai/sdk")).default;
|
|
2409
|
+
const client = new Anthropic();
|
|
2410
|
+
const infraSummary = JSON.stringify({
|
|
2411
|
+
nodes: nodes.map((n) => ({
|
|
2412
|
+
id: n.id,
|
|
2413
|
+
name: n.name,
|
|
2414
|
+
type: n.type,
|
|
2415
|
+
confidence: n.confidence,
|
|
2416
|
+
metadata: n.metadata,
|
|
2417
|
+
tags: n.tags
|
|
2418
|
+
})),
|
|
2419
|
+
edges: edges.map((e) => ({ from: e.sourceId, to: e.targetId, rel: e.relationship, conf: e.confidence })),
|
|
2420
|
+
sops: sops.map((s) => ({ title: s.title, description: s.description, steps: s.steps.length, duration: s.estimatedDuration }))
|
|
2421
|
+
});
|
|
2422
|
+
const systemPrompt = `Du bist ein Infrastruktur-Analyst f\xFCr Cartography.
|
|
2423
|
+
Du hast Zugriff auf die vollst\xE4ndig kartographierte Infrastruktur dieser Session.
|
|
2424
|
+
Beantworte Fragen pr\xE4zise und hilfreich. Nutze die Daten konkret.
|
|
2425
|
+
Du kannst SOPs erkl\xE4ren, Abh\xE4ngigkeiten analysieren, Risiken benennen, Optimierungen vorschlagen.
|
|
2426
|
+
|
|
2427
|
+
INFRASTRUKTUR-SNAPSHOT (${nodes.length} Nodes, ${edges.length} Edges, ${sops.length} SOPs):
|
|
2428
|
+
${infraSummary.substring(0, 12e3)}`;
|
|
2429
|
+
const history = [];
|
|
2430
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2431
|
+
const ask = () => new Promise((resolve2) => rl.question(` ${cyan(">")} `, resolve2));
|
|
2432
|
+
while (true) {
|
|
2433
|
+
let userInput;
|
|
2434
|
+
try {
|
|
2435
|
+
userInput = await ask();
|
|
2436
|
+
} catch {
|
|
2437
|
+
break;
|
|
2438
|
+
}
|
|
2439
|
+
if (!userInput.trim()) continue;
|
|
2440
|
+
if (["exit", "quit", ":q"].includes(userInput.trim().toLowerCase())) break;
|
|
2441
|
+
history.push({ role: "user", content: userInput });
|
|
2442
|
+
try {
|
|
2443
|
+
const resp = await client.messages.create({
|
|
2444
|
+
model: opts.model,
|
|
2445
|
+
max_tokens: 1024,
|
|
2446
|
+
system: systemPrompt,
|
|
2447
|
+
messages: history
|
|
2448
|
+
});
|
|
2449
|
+
const reply = resp.content.find((b) => b.type === "text")?.text ?? "";
|
|
2450
|
+
history.push({ role: "assistant", content: reply });
|
|
2451
|
+
w("\n");
|
|
2452
|
+
for (const line of reply.split("\n")) {
|
|
2453
|
+
w(` ${line}
|
|
2454
|
+
`);
|
|
2455
|
+
}
|
|
2456
|
+
w("\n");
|
|
2457
|
+
} catch (err) {
|
|
2458
|
+
w(` ${red("\u2717")} Fehler: ${err}
|
|
2459
|
+
|
|
2460
|
+
`);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
rl.close();
|
|
2464
|
+
db.close();
|
|
2465
|
+
w(`
|
|
2466
|
+
${dim("Chat beendet.")}
|
|
2467
|
+
|
|
2468
|
+
`);
|
|
2469
|
+
});
|
|
2144
2470
|
program.command("docs").description("Alle Features und Befehle auf einen Blick").action(() => {
|
|
2145
2471
|
const out = process.stdout.write.bind(process.stdout);
|
|
2146
|
-
const b =
|
|
2147
|
-
const dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
2148
|
-
const cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
2149
|
-
const green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
2150
|
-
const yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
2472
|
+
const b = bold;
|
|
2151
2473
|
const line = () => out(dim("\u2500".repeat(60)) + "\n");
|
|
2152
2474
|
out("\n");
|
|
2153
2475
|
out(b(" DATASYNX CARTOGRAPHY") + " " + dim("v" + VERSION) + "\n");
|
|
@@ -2303,10 +2625,10 @@ Session: ${session.id}
|
|
|
2303
2625
|
`);
|
|
2304
2626
|
const warn = (msg) => out(` \x1B[33m\u26A0\x1B[0m ${msg}
|
|
2305
2627
|
`);
|
|
2306
|
-
const
|
|
2628
|
+
const dim2 = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
2307
2629
|
let allGood = true;
|
|
2308
2630
|
out("\n \x1B[1mDatasynx Cartography \u2014 Doctor\x1B[0m\n");
|
|
2309
|
-
out(
|
|
2631
|
+
out(dim2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
2310
2632
|
const nodeVer = process.versions.node;
|
|
2311
2633
|
const [major] = nodeVer.split(".").map(Number);
|
|
2312
2634
|
if ((major ?? 0) >= 18) {
|
|
@@ -2317,7 +2639,7 @@ Session: ${session.id}
|
|
|
2317
2639
|
}
|
|
2318
2640
|
try {
|
|
2319
2641
|
const v = execSync3("claude --version", { stdio: "pipe" }).toString().trim();
|
|
2320
|
-
ok(`Claude CLI ${
|
|
2642
|
+
ok(`Claude CLI ${dim2(v)}`);
|
|
2321
2643
|
} catch {
|
|
2322
2644
|
err("Claude CLI nicht gefunden \u2014 npm i -g @anthropic-ai/claude-code");
|
|
2323
2645
|
allGood = false;
|
|
@@ -2347,18 +2669,18 @@ Session: ${session.id}
|
|
|
2347
2669
|
for (const [name, cmd] of optional) {
|
|
2348
2670
|
try {
|
|
2349
2671
|
execSync3(cmd, { stdio: "pipe" });
|
|
2350
|
-
ok(`${name} ${
|
|
2672
|
+
ok(`${name} ${dim2("(Discovery-Tool)")}`);
|
|
2351
2673
|
} catch {
|
|
2352
|
-
warn(`${name} nicht gefunden ${
|
|
2674
|
+
warn(`${name} nicht gefunden ${dim2("\u2014 Discovery ohne " + name + " eingeschr\xE4nkt")}`);
|
|
2353
2675
|
}
|
|
2354
2676
|
}
|
|
2355
2677
|
const dbDir = join4(home, ".cartography");
|
|
2356
2678
|
if (existsSync6(dbDir)) {
|
|
2357
|
-
ok(`~/.cartography ${
|
|
2679
|
+
ok(`~/.cartography ${dim2("(Daten-Verzeichnis vorhanden)")}`);
|
|
2358
2680
|
} else {
|
|
2359
|
-
warn("~/.cartography existiert noch nicht " +
|
|
2681
|
+
warn("~/.cartography existiert noch nicht " + dim2("\u2014 wird beim ersten Start angelegt"));
|
|
2360
2682
|
}
|
|
2361
|
-
out(
|
|
2683
|
+
out(dim2(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
|
|
2362
2684
|
if (allGood) {
|
|
2363
2685
|
out(" \x1B[32m\x1B[1mAlle Checks bestanden \u2014 datasynx-cartography discover\x1B[0m\n\n");
|
|
2364
2686
|
} else {
|