@datasynx/agentic-ai-cartography 0.2.0 → 0.2.1

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 CHANGED
@@ -741,6 +741,174 @@ ${run(c)}`).join("\n\n");
741
741
  ${run(c)}`).join("\n\n");
742
742
  return { content: [{ type: "text", text: out }] };
743
743
  }),
744
+ tool("scan_installed_apps", "Alle installierten Apps und Tools auf dem PC scannen \u2014 IDEs, Office, Dev-Tools, Business-Apps", {
745
+ searchHint: z.string().optional().describe('Optionaler Suchbegriff um gezielt nach bestimmten Tools zu suchen (z.B. "hubspot windsurf cursor")')
746
+ }, async (args) => {
747
+ const { execSync: execSync3 } = await import("child_process");
748
+ const hint = args["searchHint"];
749
+ const run = (cmd) => {
750
+ try {
751
+ return execSync3(cmd, { stdio: "pipe", timeout: 15e3, shell: "/bin/sh" }).toString().trim();
752
+ } catch {
753
+ return "";
754
+ }
755
+ };
756
+ const platform = process.platform;
757
+ const results = {};
758
+ if (platform === "darwin") {
759
+ results["APPLICATIONS"] = run("ls /Applications/ 2>/dev/null | head -200") || "(leer)";
760
+ results["USER_APPLICATIONS"] = run("ls ~/Applications/ 2>/dev/null | head -100") || "(leer)";
761
+ results["BREW_CASKS"] = run("brew list --cask 2>/dev/null | head -100") || "(brew nicht installiert)";
762
+ results["BREW_FORMULAE"] = run("brew list --formula 2>/dev/null | head -150") || "(brew nicht installiert)";
763
+ results["SPOTLIGHT_APPS"] = run(`mdfind "kMDItemKind == 'Application'" 2>/dev/null | grep -v "^/System" | grep -v "^/Library/Apple" | head -100`) || "(Spotlight nicht verf\xFCgbar)";
764
+ } else if (platform === "linux") {
765
+ results["DPKG"] = run("dpkg --list 2>/dev/null | awk '{print $2}' | head -200") || "(dpkg nicht verf\xFCgbar)";
766
+ results["SNAP"] = run("snap list 2>/dev/null | head -50") || "(snap nicht verf\xFCgbar)";
767
+ results["FLATPAK"] = run("flatpak list 2>/dev/null | head -50") || "(flatpak nicht verf\xFCgbar)";
768
+ 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") || "(keine .desktop files)";
769
+ results["RPM"] = run("rpm -qa 2>/dev/null | head -200") || "(rpm nicht verf\xFCgbar)";
770
+ } else if (platform === "win32") {
771
+ results["WINGET"] = run("winget list 2>/dev/null | head -100") || "(winget nicht verf\xFCgbar)";
772
+ results["PROGRAMS_x64"] = run('reg query "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall" /s /v DisplayName 2>/dev/null | findstr DisplayName | head -100') || "(nicht verf\xFCgbar)";
773
+ }
774
+ const knownTools = [
775
+ // IDEs & Editors
776
+ "code",
777
+ "code-insiders",
778
+ "cursor",
779
+ "windsurf",
780
+ "zed",
781
+ "vim",
782
+ "nvim",
783
+ "emacs",
784
+ "nano",
785
+ "sublime_text",
786
+ "atom",
787
+ "idea",
788
+ "webstorm",
789
+ "pycharm",
790
+ "goland",
791
+ "datagrip",
792
+ "clion",
793
+ "rider",
794
+ "phpstorm",
795
+ "rubymine",
796
+ "appcode",
797
+ // Dev Tools
798
+ "git",
799
+ "gh",
800
+ "docker",
801
+ "docker-compose",
802
+ "podman",
803
+ "kubectl",
804
+ "helm",
805
+ "terraform",
806
+ "ansible",
807
+ "node",
808
+ "npm",
809
+ "npx",
810
+ "yarn",
811
+ "pnpm",
812
+ "bun",
813
+ "deno",
814
+ "python",
815
+ "python3",
816
+ "pip",
817
+ "pip3",
818
+ "pipenv",
819
+ "poetry",
820
+ "conda",
821
+ "ruby",
822
+ "gem",
823
+ "bundler",
824
+ "rails",
825
+ "java",
826
+ "mvn",
827
+ "gradle",
828
+ "kotlin",
829
+ "go",
830
+ "cargo",
831
+ "rustc",
832
+ "php",
833
+ "composer",
834
+ "dotnet",
835
+ "dotnet-sdk",
836
+ // Databases
837
+ "psql",
838
+ "mysql",
839
+ "mysqladmin",
840
+ "mongo",
841
+ "mongosh",
842
+ "redis-cli",
843
+ "sqlite3",
844
+ "clickhouse-client",
845
+ // Cloud CLIs
846
+ "aws",
847
+ "gcloud",
848
+ "az",
849
+ "heroku",
850
+ "fly",
851
+ "vercel",
852
+ "netlify",
853
+ "wrangler",
854
+ // Infra
855
+ "vagrant",
856
+ "packer",
857
+ "consul",
858
+ "vault",
859
+ "nomad",
860
+ // Communication / SaaS
861
+ "slack",
862
+ "discord",
863
+ "zoom",
864
+ "teams",
865
+ "skype",
866
+ "telegram",
867
+ "signal",
868
+ // Browsers
869
+ "google-chrome",
870
+ "chromium",
871
+ "firefox",
872
+ "safari",
873
+ "brave",
874
+ "opera",
875
+ "edge",
876
+ // Monitoring / Analytics
877
+ "datadog-agent",
878
+ "newrelic-agent",
879
+ "prometheus",
880
+ "grafana-cli",
881
+ // Other tools
882
+ "ngrok",
883
+ "stripe",
884
+ "supabase",
885
+ "neon"
886
+ ];
887
+ const found = [];
888
+ const notFound = [];
889
+ for (const t of knownTools) {
890
+ const r = run(`which ${t} 2>/dev/null`);
891
+ if (r) found.push(`${t}: ${r}`);
892
+ else notFound.push(t);
893
+ }
894
+ results["WHICH_FOUND"] = found.join("\n") || "(nichts gefunden)";
895
+ results["WHICH_NOT_FOUND"] = notFound.join(", ");
896
+ if (hint) {
897
+ const terms = hint.split(/[\s,]+/).filter(Boolean);
898
+ const hintResults = [];
899
+ for (const term of terms) {
900
+ const safe = term.replace(/[^a-zA-Z0-9._-]/g, "");
901
+ if (!safe) continue;
902
+ const r = run(`which ${safe} 2>/dev/null || find /Applications ~/Applications /usr/bin /usr/local/bin /opt/homebrew/bin ~/.local/bin 2>/dev/null -iname "*${safe}*" -maxdepth 3 2>/dev/null | head -5`);
903
+ if (r) hintResults.push(`${term}: ${r}`);
904
+ else hintResults.push(`${term}: (nicht gefunden)`);
905
+ }
906
+ results["HINT_SEARCH"] = hintResults.join("\n");
907
+ }
908
+ const out = Object.entries(results).map(([k, v]) => `=== ${k} ===
909
+ ${v}`).join("\n\n");
910
+ return { content: [{ type: "text", text: out }] };
911
+ }),
744
912
  tool("save_sop", "Standard Operating Procedure speichern", {
745
913
  workflowId: z.string(),
746
914
  title: z.string(),
@@ -796,11 +964,15 @@ var safetyHook = async (input) => {
796
964
  };
797
965
 
798
966
  // src/agent.ts
799
- async function runDiscovery(config, db, sessionId, onEvent, onAskUser) {
967
+ async function runDiscovery(config, db, sessionId, onEvent, onAskUser, hint) {
800
968
  const { query } = await import("@anthropic-ai/claude-code");
801
969
  const tools = await createCartographyTools(db, sessionId, { onAskUser });
802
- const systemPrompt = `Du bist ein Infrastruktur-Discovery-Agent. Kartographiere die gesamte Systemlandschaft \u2014 lokale Services UND SaaS-Tools des Users.
803
-
970
+ const hintSection = hint ? `
971
+ \u26A1 USER-HINT (PRIORIT\xC4T): Der User m\xF6chte gezielt nach folgenden Tools suchen: "${hint}"
972
+ \u2192 scan_installed_apps(searchHint: "${hint}") SOFORT ausf\xFChren und diese Tools als saas_tool oder config_file speichern!
973
+ ` : "";
974
+ const systemPrompt = `Du bist ein Infrastruktur-Discovery-Agent. Kartographiere die gesamte Systemlandschaft \u2014 lokale Services, SaaS-Tools UND alle installierten Apps/Tools des Users.
975
+ ${hintSection}
804
976
  \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
805
977
  SCHRITT 1 \u2014 Browser-Lesezeichen (IMMER ZUERST):
806
978
  scan_bookmarks() aufrufen \u2192 jede zur\xFCckgegebene Domain klassifizieren:
@@ -808,26 +980,36 @@ SCHRITT 1 \u2014 Browser-Lesezeichen (IMMER ZUERST):
808
980
  \u2022 Interne Hosts (IPs, custom.company.com:PORT) \u2192 save_node als web_service
809
981
  \u2022 Pers\xF6nliches (Social Media, News, Streaming, Shopping) \u2192 IGNORIEREN, NICHT speichern
810
982
 
811
- SCHRITT 2 \u2014 Lokale Infrastruktur:
983
+ SCHRITT 2 \u2014 Installierte Apps & Tools (SEHR WICHTIG):
984
+ scan_installed_apps() aufrufen \u2192 ALLE gefundenen Apps/Tools klassifizieren:
985
+ \u2022 IDEs (VS Code, Cursor, Windsurf, JetBrains, etc.) \u2192 save_node als saas_tool mit category="ide"
986
+ \u2022 Office & Produktivit\xE4t (Word, Excel, Notion, Obsidian, etc.) \u2192 save_node als saas_tool mit category="productivity"
987
+ \u2022 Dev-Tools (Docker, kubectl, git, Node, Python, etc.) \u2192 save_node als saas_tool mit category="dev-tool"
988
+ \u2022 Business-Apps (Slack, Zoom, HubSpot, Salesforce, etc.) \u2192 save_node als saas_tool mit category="business"
989
+ \u2022 Browser (Chrome, Firefox, Safari, etc.) \u2192 save_node als saas_tool mit category="browser"
990
+ \u2022 Design-Tools (Figma, Sketch, Adobe, etc.) \u2192 save_node als saas_tool mit category="design"
991
+ ALLE relevanten Tools speichern \u2014 auch wenn offline/lokal!
992
+
993
+ SCHRITT 3 \u2014 Lokale Infrastruktur:
812
994
  ss -tlnp && ps aux \u2192 alle lauschenden Ports/Prozesse identifizieren
813
995
  Jeden Service vertiefen: DB\u2192Schemas, API\u2192Endpoints, Queue\u2192Topics
814
996
 
815
- SCHRITT 3 \u2014 Cloud & Kubernetes (falls CLI vorhanden):
997
+ SCHRITT 4 \u2014 Cloud & Kubernetes (falls CLI vorhanden):
816
998
  scan_k8s_resources() \u2192 Nodes, Services, Pods, Deployments, Ingresses
817
999
  scan_aws_resources() \u2192 EC2, RDS, ELB, EKS, ElastiCache, S3 (falls AWS CLI + Credentials)
818
1000
  scan_gcp_resources() \u2192 Compute, SQL, GKE, Cloud Run, Functions (falls gcloud + Auth)
819
1001
  scan_azure_resources() \u2192 VMs, AKS, SQL, Redis, WebApps (falls az CLI + Login)
820
1002
  Fehler / "nicht verf\xFCgbar" \u2192 ignorieren, weiter mit n\xE4chstem Tool
821
1003
 
822
- SCHRITT 4 \u2014 Config-Files:
1004
+ SCHRITT 5 \u2014 Config-Files:
823
1005
  .env, docker-compose.yml, application.yml, kubernetes/*.yml
824
1006
  Nur Host:Port extrahieren \u2014 KEINE Credentials
825
1007
 
826
- SCHRITT 5 \u2014 R\xFCckfragen bei Unklarheit:
1008
+ SCHRITT 6 \u2014 R\xFCckfragen bei Unklarheit:
827
1009
  ask_user() nutzen wenn: Dienst unklar ist, Kontext fehlt, oder User Input sinnvoll w\xE4re
828
1010
  Beispiele: "Welche Umgebung ist das (dev/staging/prod)?", "Ist <host> ein internes Tool?"
829
1011
 
830
- SCHRITT 6 \u2014 Fertig wenn alle Spuren ersch\xF6pft.
1012
+ SCHRITT 7 \u2014 Fertig wenn alle Spuren ersch\xF6pft.
831
1013
  \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
832
1014
 
833
1015
  PORT-MAPPING: 5432=postgres, 3306=mysql, 27017=mongodb, 6379=redis,
@@ -837,15 +1019,20 @@ PORT-MAPPING: 5432=postgres, 3306=mysql, 27017=mongodb, 6379=redis,
837
1019
  REGELN:
838
1020
  \u2022 Nur read-only (ss, ps, cat, head, curl -s, docker inspect, kubectl get)
839
1021
  \u2022 Node IDs: "type:host:port" oder "type:name" \u2014 keine Pfade, keine Credentials
840
- \u2022 saas_tool IDs: "saas_tool:github.com", "saas_tool:notion.so"
841
- \u2022 Confidence: 0.9 direkt gesehen, 0.7 aus Config/Bookmarks, 0.5 Vermutung
842
- \u2022 metadata erlaubt: { description, category, port, version } \u2014 keine Passw\xF6rter
1022
+ \u2022 saas_tool IDs: "saas_tool:github.com", "saas_tool:vscode", "saas_tool:cursor"
1023
+ \u2022 Installed-App IDs: "saas_tool:<appname>" z.B. "saas_tool:slack", "saas_tool:docker-desktop"
1024
+ \u2022 Confidence: 0.9 direkt gesehen, 0.7 aus Config/Bookmarks/Apps, 0.5 Vermutung
1025
+ \u2022 metadata erlaubt: { description, category, port, version, path } \u2014 keine Passw\xF6rter
843
1026
  \u2022 get_catalog vor save_node \u2192 Duplikate vermeiden
844
1027
  \u2022 Edges speichern wenn Verbindungen klar erkennbar sind
845
1028
 
846
1029
  Entrypoints: ${config.entryPoints.join(", ")}`;
847
- const initialPrompt = `Starte Discovery jetzt.
1030
+ const initialPrompt = hint ? `Starte Discovery mit USER-HINT: "${hint}".
1031
+ F\xFChre SOFORT scan_installed_apps(searchHint: "${hint}") aus um nach diesen Tools zu suchen.
1032
+ Dann scan_bookmarks, dann lokale Services.
1033
+ Nutze ask_user wenn du Kontext vom User brauchst.` : `Starte Discovery jetzt.
848
1034
  F\xFChre SOFORT als erstes scan_bookmarks aus \u2014 noch bevor du ss oder ps verwendest.
1035
+ Danach scan_installed_apps() f\xFCr alle installierten Apps und Tools.
849
1036
  Dann systematisch lokale Services, dann Config-Files.
850
1037
  Nutze ask_user wenn du Kontext vom User brauchst.`;
851
1038
  let turnCount = 0;
@@ -862,6 +1049,7 @@ Nutze ask_user wenn du Kontext vom User brauchst.`;
862
1049
  "mcp__cartograph__save_edge",
863
1050
  "mcp__cartograph__get_catalog",
864
1051
  "mcp__cartograph__scan_bookmarks",
1052
+ "mcp__cartograph__scan_installed_apps",
865
1053
  "mcp__cartograph__scan_k8s_resources",
866
1054
  "mcp__cartograph__scan_aws_resources",
867
1055
  "mcp__cartograph__scan_gcp_resources",
@@ -1613,7 +1801,7 @@ if (process.env.CARTOGRAPHYY_DAEMON === "1") {
1613
1801
  function main() {
1614
1802
  const program = new Command();
1615
1803
  const CMD = "datasynx-cartography";
1616
- const VERSION = "0.2.0";
1804
+ const VERSION = "0.2.1";
1617
1805
  program.name(CMD).description("AI-powered Infrastructure Cartography & SOP Generation").version(VERSION);
1618
1806
  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) => {
1619
1807
  checkPrerequisites();
@@ -1707,6 +1895,10 @@ function main() {
1707
1895
  } else if (toolName === "scan_bookmarks") {
1708
1896
  logLine(cyan("\u{1F516}"), `Browser-Lesezeichen werden gescannt\u2026`);
1709
1897
  startSpinner(`scan_bookmarks`);
1898
+ } else if (toolName === "scan_installed_apps") {
1899
+ const sh = event.input["searchHint"];
1900
+ logLine(cyan("\u{1F5A5}"), sh ? `Installierte Apps gesucht: ${bold(sh)}` : `Alle installierten Apps werden gescannt\u2026`);
1901
+ startSpinner(`scan_installed_apps`);
1710
1902
  } else if (toolName === "ask_user") {
1711
1903
  const q = (event.input["question"] ?? "").substring(0, 100);
1712
1904
  logLine(yellow("?"), `${bold("Agent fragt:")} ${q}`);
@@ -1743,7 +1935,7 @@ function main() {
1743
1935
  return answer || "(Keine Antwort \u2014 bitte fortfahren)";
1744
1936
  };
1745
1937
  try {
1746
- await runDiscovery(config, db, sessionId, handleEvent, onAskUser);
1938
+ await runDiscovery(config, db, sessionId, handleEvent, onAskUser, void 0);
1747
1939
  } catch (err) {
1748
1940
  stopSpinner();
1749
1941
  w(`
@@ -1821,6 +2013,53 @@ function main() {
1821
2013
  }
1822
2014
  }
1823
2015
  w("\n");
2016
+ if (process.stdin.isTTY) {
2017
+ 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"));
2018
+ w(` ${bold("WEITERSUCHEN")} ${dim("Discovery interaktiv verfeinern")}
2019
+ `);
2020
+ w(dim(' Gib Suchbegriffe ein (z.B. "hubspot windsurf cursor") oder Enter zum Beenden.\n'));
2021
+ w("\n");
2022
+ nodeCount = 0;
2023
+ edgeCount = 0;
2024
+ let continueDiscovery = true;
2025
+ while (continueDiscovery) {
2026
+ const rlFollowup = createInterface({ input: process.stdin, output: process.stderr });
2027
+ const followupHint = await new Promise(
2028
+ (resolve2) => rlFollowup.question(` ${yellow("\u2192")} Suche nach (Enter = Beenden): `, resolve2)
2029
+ );
2030
+ rlFollowup.close();
2031
+ if (!followupHint.trim()) {
2032
+ continueDiscovery = false;
2033
+ break;
2034
+ }
2035
+ const followupHintTrimmed = followupHint.trim();
2036
+ w("\n");
2037
+ w(` ${cyan(bold("\u27F3"))} Suche nach: ${bold(followupHintTrimmed)}
2038
+ `);
2039
+ w("\n");
2040
+ try {
2041
+ await runDiscovery(config, db, sessionId, handleEvent, onAskUser, followupHintTrimmed);
2042
+ } catch (err) {
2043
+ stopSpinner();
2044
+ w(`
2045
+ ${red("\u2717")} Fehler: ${err}
2046
+ `);
2047
+ }
2048
+ stopSpinner();
2049
+ const followupStats = db.getStats(sessionId);
2050
+ w("\n");
2051
+ 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"));
2052
+ w(` ${green(bold("\u2713"))} Gesamt jetzt: ${bold(String(followupStats.nodes))} nodes, ${bold(String(followupStats.edges))} edges
2053
+ `);
2054
+ w("\n");
2055
+ exportAll(db, sessionId, config.outputDir);
2056
+ if (existsSync4(htmlPath)) {
2057
+ w(` ${green("\u2192")} ${osc8(`file://${htmlPath}`, bold("topology.html aktualisiert"))}
2058
+ `);
2059
+ }
2060
+ w("\n");
2061
+ }
2062
+ }
1824
2063
  db.close();
1825
2064
  });
1826
2065
  const shadow = program.command("shadow").description("Shadow-Daemon verwalten");