@datasynx/agentic-ai-cartography 0.7.0 → 0.8.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/index.js CHANGED
@@ -527,7 +527,7 @@ var NodeSchema = z.object({
527
527
  name: z.string(),
528
528
  discoveredVia: z.string(),
529
529
  confidence: z.number().min(0).max(1).default(0.5),
530
- metadata: z.record(z.unknown()).default({}),
530
+ metadata: z.record(z.string(), z.unknown()).default({}),
531
531
  tags: z.array(z.string()).default([]),
532
532
  domain: z.string().optional().describe('Business domain, e.g. "Marketing", "Finance"'),
533
533
  subDomain: z.string().optional().describe('Sub-domain, e.g. "Forecast client orders"'),
@@ -562,7 +562,7 @@ var DataAssetSchema = z.object({
562
562
  domain: z.string(),
563
563
  subDomain: z.string().optional(),
564
564
  qualityScore: z.number().min(0).max(100).optional(),
565
- metadata: z.record(z.unknown()).default({}),
565
+ metadata: z.record(z.string(), z.unknown()).default({}),
566
566
  position: z.object({ q: z.number(), r: z.number() })
567
567
  });
568
568
  var ClusterSchema = z.object({
@@ -628,9 +628,154 @@ function defaultConfig(overrides = {}) {
628
628
  }
629
629
 
630
630
  // src/bookmarks.ts
631
- import { homedir, tmpdir } from "os";
632
- import { existsSync, readFileSync, readdirSync, copyFileSync, statSync } from "fs";
631
+ import { tmpdir } from "os";
632
+ import { existsSync as existsSync2, readFileSync, readdirSync, copyFileSync, statSync } from "fs";
633
+ import { join as join2 } from "path";
634
+
635
+ // src/platform.ts
636
+ import { homedir } from "os";
633
637
  import { join } from "path";
638
+ import { execSync } from "child_process";
639
+ import { existsSync } from "fs";
640
+ var PLATFORM = process.platform;
641
+ var IS_WIN = PLATFORM === "win32";
642
+ var IS_MAC = PLATFORM === "darwin";
643
+ var IS_LINUX = PLATFORM === "linux";
644
+ var HOME = homedir();
645
+ function platformShell() {
646
+ if (!IS_WIN) return "/bin/sh";
647
+ try {
648
+ execSync("pwsh -Version", { stdio: "pipe", timeout: 3e3 });
649
+ return "pwsh";
650
+ } catch {
651
+ return "powershell.exe";
652
+ }
653
+ }
654
+ var _shell;
655
+ function getShell() {
656
+ if (!_shell) _shell = platformShell();
657
+ return _shell;
658
+ }
659
+ function run(cmd, opts = {}) {
660
+ try {
661
+ return execSync(cmd, {
662
+ stdio: "pipe",
663
+ timeout: opts.timeout ?? 1e4,
664
+ shell: getShell(),
665
+ env: opts.env
666
+ }).toString().trim();
667
+ } catch {
668
+ return "";
669
+ }
670
+ }
671
+ function commandExists(cmd) {
672
+ if (IS_WIN) {
673
+ const r = run(`Get-Command ${cmd} -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source`, { timeout: 5e3 });
674
+ return r;
675
+ }
676
+ return run(`which ${cmd} 2>/dev/null`);
677
+ }
678
+ function userDataDir() {
679
+ if (IS_WIN) return process.env.APPDATA ?? join(HOME, "AppData", "Roaming");
680
+ if (IS_MAC) return join(HOME, "Library", "Application Support");
681
+ return process.env.XDG_DATA_HOME ?? join(HOME, ".local", "share");
682
+ }
683
+ function browserBasePaths() {
684
+ if (IS_WIN) {
685
+ const local = process.env.LOCALAPPDATA ?? join(HOME, "AppData", "Local");
686
+ return {
687
+ chrome: join(local, "Google", "Chrome", "User Data"),
688
+ chromium: join(local, "Chromium", "User Data"),
689
+ edge: join(local, "Microsoft", "Edge", "User Data"),
690
+ brave: join(local, "BraveSoftware", "Brave-Browser", "User Data"),
691
+ vivaldi: join(local, "Vivaldi", "User Data"),
692
+ opera: join(userDataDir(), "Opera Software", "Opera Stable")
693
+ };
694
+ }
695
+ if (IS_MAC) {
696
+ const lib = join(HOME, "Library", "Application Support");
697
+ return {
698
+ chrome: join(lib, "Google", "Chrome"),
699
+ chromium: join(lib, "Chromium"),
700
+ edge: join(lib, "Microsoft Edge"),
701
+ brave: join(lib, "BraveSoftware", "Brave-Browser"),
702
+ vivaldi: join(lib, "Vivaldi"),
703
+ opera: join(lib, "com.operasoftware.Opera")
704
+ };
705
+ }
706
+ return {
707
+ chrome: join(HOME, ".config", "google-chrome"),
708
+ chromium: join(HOME, ".config", "chromium"),
709
+ edge: join(HOME, ".config", "microsoft-edge"),
710
+ brave: join(HOME, ".config", "BraveSoftware", "Brave-Browser"),
711
+ vivaldi: join(HOME, ".config", "vivaldi"),
712
+ opera: join(HOME, ".config", "opera")
713
+ };
714
+ }
715
+ function firefoxBaseDirs() {
716
+ if (IS_WIN) {
717
+ const roaming = process.env.APPDATA ?? join(HOME, "AppData", "Roaming");
718
+ return [join(roaming, "Mozilla", "Firefox", "Profiles")];
719
+ }
720
+ if (IS_MAC) {
721
+ return [join(HOME, "Library", "Application Support", "Firefox", "Profiles")];
722
+ }
723
+ return [
724
+ join(HOME, ".mozilla", "firefox"),
725
+ join(HOME, "snap", "firefox", "common", ".mozilla", "firefox"),
726
+ join(HOME, ".var", "app", "org.mozilla.firefox", ".mozilla", "firefox")
727
+ ];
728
+ }
729
+ function dbScanDirs() {
730
+ const dirs = [];
731
+ if (IS_WIN) {
732
+ const local = process.env.LOCALAPPDATA ?? join(HOME, "AppData", "Local");
733
+ const roaming = process.env.APPDATA ?? join(HOME, "AppData", "Roaming");
734
+ dirs.push(local, roaming);
735
+ const pd = join(HOME, "AppData", "Local", "Programs");
736
+ if (existsSync(pd)) dirs.push(pd);
737
+ } else if (IS_MAC) {
738
+ dirs.push(join(HOME, "Library", "Application Support"));
739
+ if (existsSync("/var/lib")) dirs.push("/var/lib");
740
+ } else {
741
+ const configDir = join(HOME, ".config");
742
+ const dataDir = join(HOME, ".local", "share");
743
+ if (existsSync(configDir)) dirs.push(configDir);
744
+ if (existsSync(dataDir)) dirs.push(dataDir);
745
+ if (existsSync("/var/lib")) dirs.push("/var/lib");
746
+ }
747
+ return dirs.filter((d) => existsSync(d));
748
+ }
749
+ function findFiles(dirs, patterns, maxDepth, limit) {
750
+ if (dirs.length === 0) return "";
751
+ if (IS_WIN) {
752
+ const includes = patterns.map((p) => `'${p}'`).join(",");
753
+ const pathList = dirs.map((d) => `'${d}'`).join(",");
754
+ return run(
755
+ `Get-ChildItem -Path ${pathList} -Recurse -Depth ${maxDepth} -Include ${includes} -ErrorAction SilentlyContinue | Select-Object -First ${limit} -ExpandProperty FullName`,
756
+ { timeout: 15e3 }
757
+ );
758
+ }
759
+ const nameArgs = patterns.map((p) => `-name "${p}"`).join(" -o ");
760
+ const findCmds = dirs.map((d) => `find "${d}" -maxdepth ${maxDepth} \\( ${nameArgs} \\) 2>/dev/null`).join("; ");
761
+ return run(`{ ${findCmds}; } | head -${limit}`, { timeout: 15e3 });
762
+ }
763
+ function scanWindowsPrograms() {
764
+ if (!IS_WIN) return "";
765
+ return run(
766
+ `$paths = @('HKLM:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*','HKLM:\\Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*','HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\*'); Get-ItemProperty $paths -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName } | Select-Object -Property DisplayName, Publisher, DisplayVersion | Sort-Object DisplayName | Format-Table -AutoSize | Out-String -Width 300`,
767
+ { timeout: 2e4 }
768
+ );
769
+ }
770
+ function scanWindowsDbServices() {
771
+ if (!IS_WIN) return "";
772
+ return run(
773
+ `Get-Service | Where-Object { $_.Name -match 'postgres|mysql|mariadb|mongo|redis|MSSQL|elastic|clickhouse|cassandra' } | Select-Object Name, DisplayName, Status, StartType | Format-Table -AutoSize`,
774
+ { timeout: 1e4 }
775
+ );
776
+ }
777
+
778
+ // src/bookmarks.ts
634
779
  function extractHost(rawUrl, source) {
635
780
  try {
636
781
  const u = new URL(rawUrl);
@@ -654,7 +799,7 @@ function walkChrome(node, source, out) {
654
799
  }
655
800
  }
656
801
  function readChromeLike(filePath, source) {
657
- if (!existsSync(filePath)) return [];
802
+ if (!existsSync2(filePath)) return [];
658
803
  try {
659
804
  const raw = JSON.parse(readFileSync(filePath, "utf8"));
660
805
  const out = [];
@@ -667,9 +812,9 @@ function readChromeLike(filePath, source) {
667
812
  }
668
813
  }
669
814
  async function readFirefoxBookmarks(profileDir) {
670
- const src = join(profileDir, "places.sqlite");
671
- if (!existsSync(src)) return [];
672
- const tmp = join(tmpdir(), `cartograph_ff_bm_${Date.now()}.sqlite`);
815
+ const src = join2(profileDir, "places.sqlite");
816
+ if (!existsSync2(src)) return [];
817
+ const tmp = join2(tmpdir(), `cartograph_ff_bm_${Date.now()}.sqlite`);
673
818
  try {
674
819
  copyFileSync(src, tmp);
675
820
  const { default: Database2 } = await import("better-sqlite3");
@@ -693,9 +838,9 @@ async function readFirefoxBookmarks(profileDir) {
693
838
  }
694
839
  }
695
840
  async function readFirefoxHistory(profileDir) {
696
- const src = join(profileDir, "places.sqlite");
697
- if (!existsSync(src)) return [];
698
- const tmp = join(tmpdir(), `cartograph_ff_hist_${Date.now()}.sqlite`);
841
+ const src = join2(profileDir, "places.sqlite");
842
+ if (!existsSync2(src)) return [];
843
+ const tmp = join2(tmpdir(), `cartograph_ff_hist_${Date.now()}.sqlite`);
699
844
  try {
700
845
  copyFileSync(src, tmp);
701
846
  const { default: Database2 } = await import("better-sqlite3");
@@ -724,8 +869,8 @@ async function readFirefoxHistory(profileDir) {
724
869
  }
725
870
  }
726
871
  async function readChromiumHistory(historyPath, source) {
727
- if (!existsSync(historyPath)) return [];
728
- const tmp = join(tmpdir(), `cartograph_ch_hist_${Date.now()}.sqlite`);
872
+ if (!existsSync2(historyPath)) return [];
873
+ const tmp = join2(tmpdir(), `cartograph_ch_hist_${Date.now()}.sqlite`);
729
874
  try {
730
875
  copyFileSync(historyPath, tmp);
731
876
  const { default: Database2 } = await import("better-sqlite3");
@@ -753,18 +898,17 @@ async function readChromiumHistory(historyPath, source) {
753
898
  }
754
899
  }
755
900
  }
756
- var HOME = homedir();
757
- var IS_MAC = process.platform === "darwin";
901
+ var IS_LINUX2 = !IS_MAC && !IS_WIN;
758
902
  function chromeLikePaths(base) {
759
903
  const paths = [];
760
- const defaultPath = join(base, "Default", "Bookmarks");
761
- if (existsSync(defaultPath)) paths.push(defaultPath);
762
- if (existsSync(base)) {
904
+ const defaultPath = join2(base, "Default", "Bookmarks");
905
+ if (existsSync2(defaultPath)) paths.push(defaultPath);
906
+ if (existsSync2(base)) {
763
907
  try {
764
908
  for (const entry of readdirSync(base)) {
765
909
  if (entry.startsWith("Profile ")) {
766
- const p = join(base, entry, "Bookmarks");
767
- if (existsSync(p)) paths.push(p);
910
+ const p = join2(base, entry, "Bookmarks");
911
+ if (existsSync2(p)) paths.push(p);
768
912
  }
769
913
  }
770
914
  } catch {
@@ -774,14 +918,14 @@ function chromeLikePaths(base) {
774
918
  }
775
919
  function chromeLikeHistoryPaths(base) {
776
920
  const paths = [];
777
- const defaultPath = join(base, "Default", "History");
778
- if (existsSync(defaultPath)) paths.push(defaultPath);
779
- if (existsSync(base)) {
921
+ const defaultPath = join2(base, "Default", "History");
922
+ if (existsSync2(defaultPath)) paths.push(defaultPath);
923
+ if (existsSync2(base)) {
780
924
  try {
781
925
  for (const entry of readdirSync(base)) {
782
926
  if (entry.startsWith("Profile ")) {
783
- const p = join(base, entry, "History");
784
- if (existsSync(p)) paths.push(p);
927
+ const p = join2(base, entry, "History");
928
+ if (existsSync2(p)) paths.push(p);
785
929
  }
786
930
  }
787
931
  } catch {
@@ -789,29 +933,28 @@ function chromeLikeHistoryPaths(base) {
789
933
  }
790
934
  return paths;
791
935
  }
792
- var CHROME_BASE = IS_MAC ? `${HOME}/Library/Application Support/Google/Chrome` : `${HOME}/.config/google-chrome`;
793
- var CHROMIUM_BASE = IS_MAC ? `${HOME}/Library/Application Support/Chromium` : `${HOME}/.config/chromium`;
794
- var CHROMIUM_SNAP_BASE = `${HOME}/snap/chromium/common/chromium`;
795
- var CHROMIUM_FLATPAK_BASE = `${HOME}/.var/app/org.chromium.Chromium/config/chromium`;
796
- var CHROME_FLATPAK_BASE = `${HOME}/.var/app/com.google.Chrome/config/google-chrome`;
797
- var BRAVE_FLATPAK_BASE = `${HOME}/.var/app/com.brave.Browser/config/BraveSoftware/Brave-Browser`;
798
- var EDGE_FLATPAK_BASE = `${HOME}/.var/app/com.microsoft.Edge/config/microsoft-edge`;
799
- var FIREFOX_SNAP_BASE = `${HOME}/snap/firefox/common/.mozilla/firefox`;
800
- var FIREFOX_FLATPAK_BASE = `${HOME}/.var/app/org.mozilla.firefox/.mozilla/firefox`;
801
- var EDGE_BASE = IS_MAC ? `${HOME}/Library/Application Support/Microsoft Edge` : `${HOME}/.config/microsoft-edge`;
802
- var BRAVE_BASE = IS_MAC ? `${HOME}/Library/Application Support/BraveSoftware/Brave-Browser` : `${HOME}/.config/BraveSoftware/Brave-Browser`;
803
- var VIVALDI_BASE = IS_MAC ? `${HOME}/Library/Application Support/Vivaldi` : `${HOME}/.config/vivaldi`;
804
- var OPERA_BASE = IS_MAC ? `${HOME}/Library/Application Support/com.operasoftware.Opera` : `${HOME}/.config/opera`;
936
+ var BROWSER_BASES = browserBasePaths();
937
+ var CHROME_BASE = BROWSER_BASES.chrome;
938
+ var CHROMIUM_BASE = BROWSER_BASES.chromium;
939
+ var EDGE_BASE = BROWSER_BASES.edge;
940
+ var BRAVE_BASE = BROWSER_BASES.brave;
941
+ var VIVALDI_BASE = BROWSER_BASES.vivaldi;
942
+ var OPERA_BASE = BROWSER_BASES.opera;
943
+ var CHROMIUM_SNAP_BASE = join2(HOME, "snap", "chromium", "common", "chromium");
944
+ var CHROMIUM_FLATPAK_BASE = join2(HOME, ".var", "app", "org.chromium.Chromium", "config", "chromium");
945
+ var CHROME_FLATPAK_BASE = join2(HOME, ".var", "app", "com.google.Chrome", "config", "google-chrome");
946
+ var BRAVE_FLATPAK_BASE = join2(HOME, ".var", "app", "com.brave.Browser", "config", "BraveSoftware", "Brave-Browser");
947
+ var EDGE_FLATPAK_BASE = join2(HOME, ".var", "app", "com.microsoft.Edge", "config", "microsoft-edge");
805
948
  function firefoxProfileDirs() {
806
- const bases = IS_MAC ? [`${HOME}/Library/Application Support/Firefox/Profiles`] : [`${HOME}/.mozilla/firefox`, FIREFOX_SNAP_BASE, FIREFOX_FLATPAK_BASE];
949
+ const bases = firefoxBaseDirs();
807
950
  const dirs = [];
808
951
  for (const base of bases) {
809
- if (!existsSync(base)) continue;
952
+ if (!existsSync2(base)) continue;
810
953
  try {
811
954
  for (const d of readdirSync(base)) {
812
- const full = join(base, d);
955
+ const full = join2(base, d);
813
956
  try {
814
- if (statSync(full).isDirectory() && existsSync(join(full, "places.sqlite"))) {
957
+ if (statSync(full).isDirectory() && existsSync2(join2(full, "places.sqlite"))) {
815
958
  dirs.push(full);
816
959
  }
817
960
  } catch {
@@ -830,7 +973,7 @@ async function scanAllBookmarks() {
830
973
  for (const p of chromeLikePaths(BRAVE_BASE)) all.push(...readChromeLike(p, "brave"));
831
974
  for (const p of chromeLikePaths(VIVALDI_BASE)) all.push(...readChromeLike(p, "vivaldi"));
832
975
  for (const p of chromeLikePaths(OPERA_BASE)) all.push(...readChromeLike(p, "opera"));
833
- if (!IS_MAC) {
976
+ if (IS_LINUX2) {
834
977
  for (const p of chromeLikePaths(CHROMIUM_SNAP_BASE)) all.push(...readChromeLike(p, "chromium-snap"));
835
978
  for (const p of chromeLikePaths(CHROMIUM_FLATPAK_BASE)) all.push(...readChromeLike(p, "chromium-flatpak"));
836
979
  for (const p of chromeLikePaths(CHROME_FLATPAK_BASE)) all.push(...readChromeLike(p, "chrome-flatpak"));
@@ -855,7 +998,7 @@ async function scanAllHistory() {
855
998
  for (const p of chromeLikeHistoryPaths(BRAVE_BASE)) all.push(...await readChromiumHistory(p, "brave"));
856
999
  for (const p of chromeLikeHistoryPaths(VIVALDI_BASE)) all.push(...await readChromiumHistory(p, "vivaldi"));
857
1000
  for (const p of chromeLikeHistoryPaths(OPERA_BASE)) all.push(...await readChromiumHistory(p, "opera"));
858
- if (!IS_MAC) {
1001
+ if (IS_LINUX2) {
859
1002
  for (const p of chromeLikeHistoryPaths(CHROMIUM_SNAP_BASE)) all.push(...await readChromiumHistory(p, "chromium-snap"));
860
1003
  for (const p of chromeLikeHistoryPaths(CHROMIUM_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, "chromium-flatpak"));
861
1004
  for (const p of chromeLikeHistoryPaths(CHROME_FLATPAK_BASE)) all.push(...await readChromiumHistory(p, "chrome-flatpak"));
@@ -887,8 +1030,7 @@ function stripSensitive(target) {
887
1030
  }
888
1031
  }
889
1032
  async function createCartographyTools(db, sessionId, opts = {}) {
890
- const sdk = await import("@anthropic-ai/claude-code");
891
- const { tool, createSdkMcpServer } = sdk;
1033
+ const { tool, createSdkMcpServer } = await import("@anthropic-ai/claude-agent-sdk");
892
1034
  const tools = [
893
1035
  tool("save_node", "Save an infrastructure node to the catalog", {
894
1036
  id: z2.string(),
@@ -896,7 +1038,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
896
1038
  name: z2.string(),
897
1039
  discoveredVia: z2.string(),
898
1040
  confidence: z2.number().min(0).max(1),
899
- metadata: z2.record(z2.unknown()).optional(),
1041
+ metadata: z2.record(z2.string(), z2.unknown()).optional(),
900
1042
  tags: z2.array(z2.string()).optional(),
901
1043
  domain: z2.string().optional().describe('Business domain, e.g. "Marketing", "Finance"'),
902
1044
  subDomain: z2.string().optional().describe('Sub-domain, e.g. "Forecast client orders"'),
@@ -1007,33 +1149,71 @@ async function createCartographyTools(db, sessionId, opts = {}) {
1007
1149
  tool("scan_local_databases", "Scan for local database files and running DB servers \u2014 PostgreSQL databases, MySQL, SQLite files from installed apps", {
1008
1150
  deep: z2.boolean().default(false).optional().describe("Also search home directory recursively for SQLite/DB files (slower)")
1009
1151
  }, async (args) => {
1010
- const { execSync: execSync2 } = await import("child_process");
1011
- const { homedir: homedir2 } = await import("os");
1012
- const { existsSync: existsSync3 } = await import("fs");
1013
1152
  const deep = args["deep"] ?? false;
1014
- const HOME2 = homedir2();
1015
- const run = (cmd) => {
1016
- try {
1017
- return execSync2(cmd, { stdio: "pipe", timeout: 1e4, shell: "/bin/sh" }).toString().trim();
1018
- } catch {
1019
- return "";
1020
- }
1021
- };
1022
1153
  const results = {};
1023
- results["POSTGRES_DATABASES"] = run(`psql -lqt 2>/dev/null | grep -v "template0\\|template1" | awk '{print $1}' | grep -v "^$\\|^|"`) || "(psql not running or not available)";
1024
- results["POSTGRES_CLUSTERS"] = run("pg_lsclusters 2>/dev/null") || "(pg_lsclusters not available)";
1025
- results["MYSQL_DATABASES"] = run('mysql --connect-timeout=3 -e "SHOW DATABASES;" 2>/dev/null') || "(mysql not running or requires auth)";
1026
- results["MONGODB_DATABASES"] = run(`mongosh --quiet --eval "db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join('\\n')" 2>/dev/null`) || "(mongosh not available)";
1027
- results["REDIS_INFO"] = run("redis-cli info server 2>/dev/null | head -5") || "(redis-cli not available)";
1028
- const appDirs = [`${HOME2}/.config`, `${HOME2}/.local/share`, `${HOME2}/Library/Application Support`, "/var/lib"].filter((d) => existsSync3(d));
1154
+ results["PLATFORM"] = `${PLATFORM} (${IS_WIN ? "Windows" : IS_MAC ? "macOS" : "Linux"})`;
1155
+ if (IS_WIN) {
1156
+ results["DB_SERVICES"] = scanWindowsDbServices() || "(no database services found)";
1157
+ }
1158
+ if (commandExists("psql")) {
1159
+ if (IS_WIN) {
1160
+ results["POSTGRES_DATABASES"] = run("psql -lqt", { timeout: 1e4 }) || "(psql found but not running or requires auth)";
1161
+ } else {
1162
+ results["POSTGRES_DATABASES"] = run(`psql -lqt 2>/dev/null | grep -v "template0\\|template1" | awk '{print $1}' | grep -v "^$\\|^|"`) || "(psql not running or not available)";
1163
+ results["POSTGRES_CLUSTERS"] = run("pg_lsclusters 2>/dev/null") || "(pg_lsclusters not available)";
1164
+ }
1165
+ } else {
1166
+ results["POSTGRES_DATABASES"] = "(psql not installed)";
1167
+ }
1168
+ if (commandExists("mysql")) {
1169
+ if (IS_WIN) {
1170
+ results["MYSQL_DATABASES"] = run('mysql --connect-timeout=3 -e "SHOW DATABASES;"', { timeout: 1e4 }) || "(mysql not running or requires auth)";
1171
+ } else {
1172
+ results["MYSQL_DATABASES"] = run('mysql --connect-timeout=3 -e "SHOW DATABASES;" 2>/dev/null') || "(mysql not running or requires auth)";
1173
+ }
1174
+ } else {
1175
+ results["MYSQL_DATABASES"] = "(mysql not installed)";
1176
+ }
1177
+ if (commandExists("mongosh")) {
1178
+ if (IS_WIN) {
1179
+ results["MONGODB_DATABASES"] = run(`mongosh --quiet --eval "db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join('\\n')"`, { timeout: 1e4 }) || "(mongosh not available)";
1180
+ } else {
1181
+ results["MONGODB_DATABASES"] = run(`mongosh --quiet --eval "db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join('\\n')" 2>/dev/null`) || "(mongosh not available)";
1182
+ }
1183
+ } else {
1184
+ results["MONGODB_DATABASES"] = "(mongosh not installed)";
1185
+ }
1186
+ if (commandExists("redis-cli")) {
1187
+ if (IS_WIN) {
1188
+ results["REDIS_INFO"] = run("redis-cli info server", { timeout: 1e4 }).split("\n").slice(0, 5).join("\n") || "(redis-cli not available)";
1189
+ } else {
1190
+ results["REDIS_INFO"] = run("redis-cli info server 2>/dev/null | head -5") || "(redis-cli not available)";
1191
+ }
1192
+ } else {
1193
+ results["REDIS_INFO"] = "(redis-cli not installed)";
1194
+ }
1195
+ const appDirs = dbScanDirs();
1029
1196
  if (appDirs.length > 0) {
1030
- const findCmds = appDirs.map((d) => `find "${d}" -maxdepth 4 \\( -name "*.sqlite" -o -name "*.sqlite3" -o -name "*.db" \\) 2>/dev/null`).join("; ");
1031
- results["SQLITE_APP_FILES"] = run(`{ ${findCmds}; } | head -80`) || "(none found)";
1197
+ results["SQLITE_APP_FILES"] = findFiles(appDirs, ["*.sqlite", "*.sqlite3", "*.db"], 4, 80) || "(none found)";
1032
1198
  }
1033
1199
  if (deep) {
1034
- results["SQLITE_DEEP_SCAN"] = run(`find "${HOME2}" -maxdepth 6 \\( -name "*.sqlite" -o -name "*.sqlite3" -o -name "*.db" \\) -not -path "*/node_modules/*" -not -path "*/.git/*" 2>/dev/null | head -100`) || "(none found)";
1200
+ if (IS_WIN) {
1201
+ results["SQLITE_DEEP_SCAN"] = run(
1202
+ `Get-ChildItem -Path '${HOME}' -Recurse -Depth 6 -Include '*.sqlite','*.sqlite3','*.db' -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch 'node_modules|\\.git' } | Select-Object -First 100 -ExpandProperty FullName`,
1203
+ { timeout: 3e4 }
1204
+ ) || "(none found)";
1205
+ } else {
1206
+ 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)";
1207
+ }
1208
+ }
1209
+ if (IS_WIN) {
1210
+ results["DB_CONFIG_FILES"] = run(
1211
+ `Get-ChildItem -Path '${HOME}' -Recurse -Depth 4 -Include '.env','.env.local','database.yml','database.json','docker-compose.yml' -ErrorAction SilentlyContinue | Select-Object -First 20 -ExpandProperty FullName`,
1212
+ { timeout: 15e3 }
1213
+ ) || "(none found)";
1214
+ } else {
1215
+ 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)";
1035
1216
  }
1036
- results["DB_CONFIG_FILES"] = run(`find "${HOME2}" -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)";
1037
1217
  const out = Object.entries(results).map(([k, v]) => `=== ${k} ===
1038
1218
  ${v}`).join("\n\n");
1039
1219
  return { content: [{ type: "text", text: out }] };
@@ -1041,17 +1221,23 @@ ${v}`).join("\n\n");
1041
1221
  tool("scan_k8s_resources", "Scan Kubernetes cluster via kubectl \u2014 100% readonly (get, describe)", {
1042
1222
  namespace: z2.string().optional().describe("Filter by namespace \u2014 empty = all namespaces")
1043
1223
  }, async (args) => {
1044
- const { execSync: execSync2 } = await import("child_process");
1045
1224
  const ns = args["namespace"];
1046
1225
  const nsFlag = ns ? `-n ${ns}` : "--all-namespaces";
1047
- const run = (cmd) => {
1048
- try {
1049
- return execSync2(cmd, { stdio: "pipe", timeout: 15e3, shell: "/bin/sh" }).toString().trim();
1050
- } catch (e) {
1051
- return `(error: ${e instanceof Error ? e.message.split("\n")[0] : String(e)})`;
1052
- }
1226
+ const runK = (cmd) => {
1227
+ const r = run(cmd, { timeout: 15e3 });
1228
+ return r || `(error or not available)`;
1053
1229
  };
1054
- const sections = [
1230
+ const sections = IS_WIN ? [
1231
+ ["CONTEXT", "kubectl config current-context"],
1232
+ ["NODES", "kubectl get nodes -o wide"],
1233
+ ["NAMESPACES", "kubectl get namespaces"],
1234
+ ["SERVICES", `kubectl get services ${nsFlag}`],
1235
+ ["DEPLOYMENTS", `kubectl get deployments ${nsFlag}`],
1236
+ ["STATEFULSETS", `kubectl get statefulsets ${nsFlag}`],
1237
+ ["INGRESSES", `kubectl get ingress ${nsFlag}`],
1238
+ ["PODS_RUNNING", `kubectl get pods ${nsFlag} --field-selector=status.phase=Running`],
1239
+ ["CONFIGMAPS_SYSTEM", "kubectl get configmaps -n kube-system"]
1240
+ ] : [
1055
1241
  ["CONTEXT", 'kubectl config current-context 2>/dev/null || echo "(no context set)"'],
1056
1242
  ["NODES", "kubectl get nodes -o wide"],
1057
1243
  ["NAMESPACES", "kubectl get namespaces"],
@@ -1063,128 +1249,101 @@ ${v}`).join("\n\n");
1063
1249
  ["CONFIGMAPS_SYSTEM", "kubectl get configmaps -n kube-system 2>/dev/null | head -30"]
1064
1250
  ];
1065
1251
  const out = sections.map(([l, c]) => `=== ${l} ===
1066
- ${run(c)}`).join("\n\n");
1252
+ ${runK(c)}`).join("\n\n");
1067
1253
  return { content: [{ type: "text", text: out }] };
1068
1254
  }),
1069
1255
  tool("scan_aws_resources", "Scan AWS infrastructure via AWS CLI \u2014 100% readonly (describe, list)", {
1070
1256
  region: z2.string().optional().describe("AWS Region \u2014 default: AWS_DEFAULT_REGION or profile"),
1071
1257
  profile: z2.string().optional().describe("AWS CLI profile")
1072
1258
  }, async (args) => {
1073
- const { execSync: execSync2 } = await import("child_process");
1074
1259
  const region = args["region"];
1075
1260
  const profile = args["profile"];
1076
1261
  const env = { ...process.env };
1077
1262
  if (region) env["AWS_DEFAULT_REGION"] = region;
1078
1263
  const pf = profile ? `--profile ${profile}` : "";
1079
- const run = (cmd) => {
1080
- try {
1081
- return execSync2(cmd, { stdio: "pipe", timeout: 2e4, shell: "/bin/sh", env }).toString().trim();
1082
- } catch (e) {
1083
- return `(error: ${e instanceof Error ? e.message.split("\n")[0] : String(e)})`;
1084
- }
1085
- };
1264
+ const runAws = (cmd) => run(cmd, { timeout: 2e4, env }) || "(error or not available)";
1086
1265
  const sections = [
1087
1266
  ["IDENTITY", `aws sts get-caller-identity ${pf} --output json`],
1088
- ["EC2", `aws ec2 describe-instances ${pf} --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,PublicIpAddress,PrivateIpAddress,Tags[?Key==\`Name\`].Value|[0]]' --output table`],
1089
- ["RDS", `aws rds describe-db-instances ${pf} --query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus,Endpoint.Address,Endpoint.Port]' --output table`],
1090
- ["ELB_V2", `aws elbv2 describe-load-balancers ${pf} --query 'LoadBalancers[*].[LoadBalancerName,DNSName,Type,State.Code]' --output table`],
1267
+ ["EC2", `aws ec2 describe-instances ${pf} --query "Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,PublicIpAddress,PrivateIpAddress]" --output table`],
1268
+ ["RDS", `aws rds describe-db-instances ${pf} --query "DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus,Endpoint.Address,Endpoint.Port]" --output table`],
1269
+ ["ELB_V2", `aws elbv2 describe-load-balancers ${pf} --query "LoadBalancers[*].[LoadBalancerName,DNSName,Type,State.Code]" --output table`],
1091
1270
  ["EKS", `aws eks list-clusters ${pf} --output json`],
1092
- ["ELASTICACHE", `aws elasticache describe-cache-clusters ${pf} --query 'CacheClusters[*].[CacheClusterId,Engine,CacheClusterStatus]' --output table 2>/dev/null || echo "(not available)"`],
1093
- ["S3", `aws s3 ls ${pf} 2>/dev/null || echo "(not available)"`],
1094
- ["VPC", `aws ec2 describe-vpcs ${pf} --query 'Vpcs[*].[VpcId,CidrBlock,IsDefault,Tags[?Key==\`Name\`].Value|[0]]' --output table`]
1271
+ ["ELASTICACHE", `aws elasticache describe-cache-clusters ${pf} --query "CacheClusters[*].[CacheClusterId,Engine,CacheClusterStatus]" --output table`],
1272
+ ["S3", `aws s3 ls ${pf}`],
1273
+ ["VPC", `aws ec2 describe-vpcs ${pf} --query "Vpcs[*].[VpcId,CidrBlock,IsDefault]" --output table`]
1095
1274
  ];
1096
1275
  const out = sections.map(([l, c]) => `=== ${l} ===
1097
- ${run(c)}`).join("\n\n");
1276
+ ${runAws(c)}`).join("\n\n");
1098
1277
  return { content: [{ type: "text", text: out }] };
1099
1278
  }),
1100
1279
  tool("scan_gcp_resources", "Scan Google Cloud Platform via gcloud CLI \u2014 100% readonly (list, describe)", {
1101
1280
  project: z2.string().optional().describe("GCP Project ID \u2014 default: current gcloud project")
1102
1281
  }, async (args) => {
1103
- const { execSync: execSync2 } = await import("child_process");
1104
1282
  const project = args["project"];
1105
1283
  const pf = project ? `--project ${project}` : "";
1106
- const run = (cmd) => {
1107
- try {
1108
- return execSync2(cmd, { stdio: "pipe", timeout: 2e4, shell: "/bin/sh" }).toString().trim();
1109
- } catch (e) {
1110
- return `(error: ${e instanceof Error ? e.message.split("\n")[0] : String(e)})`;
1111
- }
1112
- };
1284
+ const runGcp = (cmd) => run(cmd, { timeout: 2e4 }) || "(error or not available)";
1113
1285
  const sections = [
1114
- ["IDENTITY", `gcloud config list account --format='value(core.account)' 2>/dev/null; gcloud config get-value project 2>/dev/null`],
1115
- ["COMPUTE_INSTANCES", `gcloud compute instances list ${pf} 2>/dev/null || echo "(error)"`],
1116
- ["SQL_INSTANCES", `gcloud sql instances list ${pf} 2>/dev/null || echo "(error)"`],
1117
- ["GKE_CLUSTERS", `gcloud container clusters list ${pf} 2>/dev/null || echo "(error)"`],
1118
- ["CLOUD_RUN", `gcloud run services list ${pf} --platform managed 2>/dev/null || echo "(error)"`],
1119
- ["CLOUD_FUNCTIONS", `gcloud functions list ${pf} 2>/dev/null || echo "(error)"`],
1120
- ["REDIS", `gcloud redis instances list ${pf} --regions=- 2>/dev/null || echo "(error)"`],
1121
- ["PUBSUB", `gcloud pubsub topics list ${pf} 2>/dev/null || echo "(error)"`],
1122
- ["SPANNER", `gcloud spanner instances list ${pf} 2>/dev/null || echo "(error)"`]
1286
+ ["IDENTITY", `gcloud config list account --format="value(core.account)"`],
1287
+ ["COMPUTE_INSTANCES", `gcloud compute instances list ${pf}`],
1288
+ ["SQL_INSTANCES", `gcloud sql instances list ${pf}`],
1289
+ ["GKE_CLUSTERS", `gcloud container clusters list ${pf}`],
1290
+ ["CLOUD_RUN", `gcloud run services list ${pf} --platform managed`],
1291
+ ["CLOUD_FUNCTIONS", `gcloud functions list ${pf}`],
1292
+ ["REDIS", `gcloud redis instances list ${pf} --regions=-`],
1293
+ ["PUBSUB", `gcloud pubsub topics list ${pf}`],
1294
+ ["SPANNER", `gcloud spanner instances list ${pf}`]
1123
1295
  ];
1124
1296
  const out = sections.map(([l, c]) => `=== ${l} ===
1125
- ${run(c)}`).join("\n\n");
1297
+ ${runGcp(c)}`).join("\n\n");
1126
1298
  return { content: [{ type: "text", text: out }] };
1127
1299
  }),
1128
1300
  tool("scan_azure_resources", "Scan Azure infrastructure via az CLI \u2014 100% readonly (list, show)", {
1129
1301
  subscription: z2.string().optional().describe("Azure Subscription ID"),
1130
1302
  resourceGroup: z2.string().optional().describe("Filter by resource group")
1131
1303
  }, async (args) => {
1132
- const { execSync: execSync2 } = await import("child_process");
1133
1304
  const sub = args["subscription"];
1134
1305
  const rg = args["resourceGroup"];
1135
1306
  const sf = sub ? `--subscription ${sub}` : "";
1136
1307
  const rf = rg ? `--resource-group ${rg}` : "";
1137
- const run = (cmd) => {
1138
- try {
1139
- return execSync2(cmd, { stdio: "pipe", timeout: 2e4, shell: "/bin/sh" }).toString().trim();
1140
- } catch (e) {
1141
- return `(error: ${e instanceof Error ? e.message.split("\n")[0] : String(e)})`;
1142
- }
1143
- };
1308
+ const runAz = (cmd) => run(cmd, { timeout: 2e4 }) || "(error or not available)";
1144
1309
  const sections = [
1145
- ["IDENTITY", `az account show --output json ${sf} 2>/dev/null || echo "(not logged in \u2014 az login)"`],
1146
- ["VMS", `az vm list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
1147
- ["AKS", `az aks list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
1148
- ["SQL_SERVERS", `az sql server list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
1149
- ["POSTGRES", `az postgres server list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
1150
- ["REDIS", `az redis list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
1151
- ["WEBAPPS", `az webapp list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
1152
- ["CONTAINER_APPS", `az containerapp list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
1153
- ["FUNCTIONS", `az functionapp list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`]
1310
+ ["IDENTITY", `az account show --output json ${sf}`],
1311
+ ["VMS", `az vm list ${sf} ${rf} --output table`],
1312
+ ["AKS", `az aks list ${sf} ${rf} --output table`],
1313
+ ["SQL_SERVERS", `az sql server list ${sf} ${rf} --output table`],
1314
+ ["POSTGRES", `az postgres server list ${sf} ${rf} --output table`],
1315
+ ["REDIS", `az redis list ${sf} ${rf} --output table`],
1316
+ ["WEBAPPS", `az webapp list ${sf} ${rf} --output table`],
1317
+ ["CONTAINER_APPS", `az containerapp list ${sf} ${rf} --output table`],
1318
+ ["FUNCTIONS", `az functionapp list ${sf} ${rf} --output table`]
1154
1319
  ];
1155
1320
  const out = sections.map(([l, c]) => `=== ${l} ===
1156
- ${run(c)}`).join("\n\n");
1321
+ ${runAz(c)}`).join("\n\n");
1157
1322
  return { content: [{ type: "text", text: out }] };
1158
1323
  }),
1159
1324
  tool("scan_installed_apps", "Scan all installed apps and tools \u2014 IDEs, office, dev tools, business apps, databases", {
1160
1325
  searchHint: z2.string().optional().describe('Optional search term to find specific tools (e.g. "hubspot windsurf cursor")')
1161
1326
  }, async (args) => {
1162
- const { execSync: execSync2 } = await import("child_process");
1163
1327
  const hint = args["searchHint"];
1164
- const run = (cmd) => {
1165
- try {
1166
- return execSync2(cmd, { stdio: "pipe", timeout: 15e3, shell: "/bin/sh" }).toString().trim();
1167
- } catch {
1168
- return "";
1169
- }
1170
- };
1171
- const platform = process.platform;
1172
1328
  const results = {};
1173
- if (platform === "darwin") {
1329
+ results["PLATFORM"] = `${PLATFORM} (${IS_WIN ? "Windows" : IS_MAC ? "macOS" : "Linux"})`;
1330
+ if (IS_MAC) {
1174
1331
  results["APPLICATIONS"] = run("ls /Applications/ 2>/dev/null | head -200") || "(empty)";
1175
1332
  results["USER_APPLICATIONS"] = run("ls ~/Applications/ 2>/dev/null | head -100") || "(empty)";
1176
1333
  results["BREW_CASKS"] = run("brew list --cask 2>/dev/null | head -100") || "(brew not installed)";
1177
1334
  results["BREW_FORMULAE"] = run("brew list --formula 2>/dev/null | head -150") || "(brew not installed)";
1178
1335
  results["SPOTLIGHT_APPS"] = run(`mdfind "kMDItemKind == 'Application'" 2>/dev/null | grep -v "^/System" | grep -v "^/Library/Apple" | head -100`) || "(Spotlight not available)";
1179
- } else if (platform === "linux") {
1336
+ } else if (IS_LINUX) {
1180
1337
  results["DPKG"] = run("dpkg --list 2>/dev/null | awk '{print $2}' | head -200") || "(dpkg not available)";
1181
1338
  results["SNAP"] = run("snap list 2>/dev/null | head -50") || "(snap not available)";
1182
1339
  results["FLATPAK"] = run("flatpak list 2>/dev/null | head -50") || "(flatpak not available)";
1183
1340
  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)";
1184
1341
  results["RPM"] = run("rpm -qa 2>/dev/null | head -200") || "(rpm not available)";
1185
- } else if (platform === "win32") {
1186
- results["WINGET"] = run("winget list 2>/dev/null | head -100") || "(winget not available)";
1187
- results["PROGRAMS_x64"] = run('reg query "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall" /s /v DisplayName 2>/dev/null | findstr DisplayName | head -100') || "(not available)";
1342
+ } else if (IS_WIN) {
1343
+ results["WINGET"] = run("winget list --accept-source-agreements", { timeout: 2e4 }) || "(winget not available)";
1344
+ results["INSTALLED_PROGRAMS"] = scanWindowsPrograms() || "(registry scan failed)";
1345
+ results["CHOCO"] = run("choco list --local-only", { timeout: 15e3 }) || "(chocolatey not installed)";
1346
+ results["SCOOP"] = run("scoop list", { timeout: 15e3 }) || "(scoop not installed)";
1188
1347
  }
1189
1348
  const knownTools = [
1190
1349
  // IDEs & Editors
@@ -1247,7 +1406,6 @@ ${run(c)}`).join("\n\n");
1247
1406
  "php",
1248
1407
  "composer",
1249
1408
  "dotnet",
1250
- "dotnet-sdk",
1251
1409
  // Databases
1252
1410
  "psql",
1253
1411
  "mysql",
@@ -1288,6 +1446,8 @@ ${run(c)}`).join("\n\n");
1288
1446
  "brave",
1289
1447
  "opera",
1290
1448
  "edge",
1449
+ // Windows-specific
1450
+ ...IS_WIN ? ["pwsh", "powershell", "wsl", "winget", "choco", "scoop", "notepad++"] : [],
1291
1451
  // Monitoring / Analytics
1292
1452
  "datadog-agent",
1293
1453
  "newrelic-agent",
@@ -1302,21 +1462,35 @@ ${run(c)}`).join("\n\n");
1302
1462
  const found = [];
1303
1463
  const notFound = [];
1304
1464
  for (const t of knownTools) {
1305
- const r = run(`which ${t} 2>/dev/null`);
1465
+ const r = commandExists(t);
1306
1466
  if (r) found.push(`${t}: ${r}`);
1307
1467
  else notFound.push(t);
1308
1468
  }
1309
- results["WHICH_FOUND"] = found.join("\n") || "(none found)";
1310
- results["WHICH_NOT_FOUND"] = notFound.join(", ");
1469
+ results["TOOLS_FOUND"] = found.join("\n") || "(none found)";
1470
+ results["TOOLS_NOT_FOUND"] = notFound.join(", ");
1311
1471
  if (hint) {
1312
1472
  const terms = hint.split(/[\s,]+/).filter(Boolean);
1313
1473
  const hintResults = [];
1314
1474
  for (const term of terms) {
1315
1475
  const safe = term.replace(/[^a-zA-Z0-9._-]/g, "");
1316
1476
  if (!safe) continue;
1317
- 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`);
1318
- if (r) hintResults.push(`${term}: ${r}`);
1319
- else hintResults.push(`${term}: (not found)`);
1477
+ const cmdPath = commandExists(safe);
1478
+ if (cmdPath) {
1479
+ hintResults.push(`${term}: ${cmdPath}`);
1480
+ continue;
1481
+ }
1482
+ let fallback = "";
1483
+ if (IS_WIN) {
1484
+ fallback = run(
1485
+ `Get-ChildItem -Path 'C:\\Program Files','C:\\Program Files (x86)','${HOME}\\AppData\\Local\\Programs' -Recurse -Depth 3 -Filter '*${safe}*' -ErrorAction SilentlyContinue | Select-Object -First 5 -ExpandProperty FullName`,
1486
+ { timeout: 1e4 }
1487
+ );
1488
+ } else if (IS_MAC) {
1489
+ fallback = run(`mdfind -name "${safe}" 2>/dev/null | head -5`);
1490
+ } else {
1491
+ 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`);
1492
+ }
1493
+ hintResults.push(fallback ? `${term}: ${fallback}` : `${term}: (not found)`);
1320
1494
  }
1321
1495
  results["HINT_SEARCH"] = hintResults.join("\n");
1322
1496
  }
@@ -1355,9 +1529,9 @@ ${v}`).join("\n\n");
1355
1529
  }
1356
1530
 
1357
1531
  // src/safety.ts
1358
- var BLOCKED_CMDS = /\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)\b/i;
1359
- var BLOCKED_REDIRECTS = />>|>[^>]/;
1360
- var safetyHook = async (input) => {
1532
+ var BLOCKED_CMDS = /\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;
1533
+ var BLOCKED_REDIRECTS = />>|>[^>]|Out-File|Set-Content|Add-Content/;
1534
+ var safetyHook = async (input, _toolUseID, _options) => {
1361
1535
  if (!("tool_name" in input)) return {};
1362
1536
  if (input.tool_name !== "Bash") return {};
1363
1537
  const cmd = input.tool_input?.command ?? "";
@@ -1380,13 +1554,18 @@ var safetyHook = async (input) => {
1380
1554
 
1381
1555
  // src/agent.ts
1382
1556
  async function runDiscovery(config, db, sessionId, onEvent, onAskUser, hint) {
1383
- const { query } = await import("@anthropic-ai/claude-code");
1557
+ const { query } = await import("@anthropic-ai/claude-agent-sdk");
1384
1558
  const tools = await createCartographyTools(db, sessionId, { onAskUser });
1385
1559
  const hintSection = hint ? `
1386
1560
  \u26A1 USER HINT (HIGH PRIORITY): The user wants to find these specific tools: "${hint}"
1387
1561
  \u2192 Run scan_installed_apps(searchHint: "${hint}") IMMEDIATELY and save found tools as saas_tool nodes!
1388
1562
  ` : "";
1563
+ const platformName = IS_WIN ? "Windows" : IS_MAC ? "macOS" : "Linux";
1564
+ const networkScanCmd = IS_WIN ? "Get-NetTCPConnection -State Listen (PowerShell) \u2192 identify all listening ports/processes" : IS_MAC ? "lsof -iTCP -sTCP:LISTEN -n -P && ps aux \u2192 identify all listening ports/processes" : "ss -tlnp && ps aux \u2192 identify all listening ports/processes";
1565
+ const readOnlyTools = IS_WIN ? "Get-NetTCPConnection, Get-Process, Get-Service, Get-ChildItem, curl, docker inspect, kubectl get" : IS_MAC ? "lsof, ps, cat, head, curl -s, docker inspect, kubectl get" : "ss, ps, cat, head, curl -s, docker inspect, kubectl get";
1566
+ const processCmd = IS_WIN ? "Get-Process" : "ps aux";
1389
1567
  const systemPrompt = `You are an infrastructure discovery agent. Map the complete system landscape \u2014 local services, SaaS tools, AND all installed apps/tools of the user.
1568
+ PLATFORM: ${platformName} (${PLATFORM})
1390
1569
  ${hintSection}
1391
1570
  \u2501\u2501 MANDATORY SEQUENCE \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
1392
1571
  STEP 1 \u2014 Browser Bookmarks (ALWAYS FIRST):
@@ -1417,7 +1596,8 @@ STEP 4 \u2014 Local Databases & Infrastructure:
1417
1596
  \u2022 MongoDB running \u2192 save_node as database_server
1418
1597
  \u2022 Redis running \u2192 save_node as cache_server
1419
1598
  \u2022 SQLite files in app directories \u2192 save_node as database if clearly a business app DB
1420
- Then run: ss -tlnp && ps aux \u2192 identify all listening ports/processes
1599
+ Then run: ${networkScanCmd}
1600
+ Also run: ${processCmd} \u2192 identify running services
1421
1601
  Deepen each service: DB\u2192schemas, API\u2192endpoints, Queue\u2192topics
1422
1602
 
1423
1603
  STEP 5 \u2014 Cloud & Kubernetes (if CLI available):
@@ -1454,8 +1634,20 @@ PORT MAPPING: 5432=postgres, 3306=mysql, 27017=mongodb, 6379=redis,
1454
1634
  9092=kafka, 5672=rabbitmq, 80/443/8080/3000=web_service,
1455
1635
  9090=prometheus, 8500=consul, 8200=vault, 2379=etcd
1456
1636
 
1637
+ PLATFORM-SPECIFIC NOTES (${platformName}):
1638
+ ${IS_WIN ? `\u2022 Use PowerShell commands: Get-NetTCPConnection, Get-Process, Get-Service, Get-ChildItem
1639
+ \u2022 Do NOT use Unix commands (ss, ps aux, find, which, head, grep) \u2014 they won't work on Windows
1640
+ \u2022 Use $env:LOCALAPPDATA, $env:APPDATA for app data paths
1641
+ \u2022 Registry scan for installed programs is handled by scan_installed_apps` : IS_MAC ? `\u2022 Use lsof -iTCP -sTCP:LISTEN -n -P for port scanning (ss is NOT available on macOS)
1642
+ \u2022 Use ps aux for process listing
1643
+ \u2022 Applications are in /Applications and ~/Applications
1644
+ \u2022 Homebrew (brew) for package management` : `\u2022 Use ss -tlnp for port scanning
1645
+ \u2022 Use ps aux for process listing
1646
+ \u2022 Check dpkg, snap, flatpak for installed packages
1647
+ \u2022 Check Snap/Flatpak browser variants for bookmarks`}
1648
+
1457
1649
  RULES:
1458
- \u2022 Read-only only (ss, ps, cat, head, curl -s, docker inspect, kubectl get)
1650
+ \u2022 Read-only only (${readOnlyTools})
1459
1651
  \u2022 Node IDs: "type:host:port" or "type:name" \u2014 no paths, no credentials
1460
1652
  \u2022 saas_tool IDs: "saas_tool:github.com", "saas_tool:vscode", "saas_tool:cursor"
1461
1653
  \u2022 Installed-app IDs: "saas_tool:<appname>" e.g. "saas_tool:slack", "saas_tool:docker-desktop"
@@ -1482,7 +1674,7 @@ Use ask_user when you need context from the user.`;
1482
1674
  options: {
1483
1675
  model: config.agentModel,
1484
1676
  maxTurns: config.maxTurns,
1485
- customSystemPrompt: systemPrompt,
1677
+ systemPrompt,
1486
1678
  mcpServers: { cartography: tools },
1487
1679
  allowedTools: [
1488
1680
  "Bash",
@@ -1543,7 +1735,7 @@ Use ask_user when you need context from the user.`;
1543
1735
 
1544
1736
  // src/exporter.ts
1545
1737
  import { mkdirSync as mkdirSync2, writeFileSync } from "fs";
1546
- import { join as join2 } from "path";
1738
+ import { join as join3 } from "path";
1547
1739
 
1548
1740
  // src/hex.ts
1549
1741
  function hexToPixel(q, r, size) {
@@ -3346,53 +3538,44 @@ function exportJGF(nodes, edges) {
3346
3538
  }
3347
3539
  function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml", "html", "map", "discovery", "sops"]) {
3348
3540
  mkdirSync2(outputDir, { recursive: true });
3349
- mkdirSync2(join2(outputDir, "sops"), { recursive: true });
3350
- mkdirSync2(join2(outputDir, "workflows"), { recursive: true });
3541
+ mkdirSync2(join3(outputDir, "sops"), { recursive: true });
3542
+ mkdirSync2(join3(outputDir, "workflows"), { recursive: true });
3351
3543
  const nodes = db.getNodes(sessionId);
3352
3544
  const edges = db.getEdges(sessionId);
3353
- const jgfPath = join2(outputDir, "cartography-graph.jgf.json");
3545
+ const jgfPath = join3(outputDir, "cartography-graph.jgf.json");
3354
3546
  writeFileSync(jgfPath, exportJGF(nodes, edges));
3355
- process.stderr.write("\u2713 cartography-graph.jgf.json\n");
3356
3547
  if (formats.includes("mermaid")) {
3357
- writeFileSync(join2(outputDir, "topology.mermaid"), generateTopologyMermaid(nodes, edges));
3358
- writeFileSync(join2(outputDir, "dependencies.mermaid"), generateDependencyMermaid(nodes, edges));
3359
- process.stderr.write("\u2713 topology.mermaid, dependencies.mermaid\n");
3548
+ writeFileSync(join3(outputDir, "topology.mermaid"), generateTopologyMermaid(nodes, edges));
3549
+ writeFileSync(join3(outputDir, "dependencies.mermaid"), generateDependencyMermaid(nodes, edges));
3360
3550
  }
3361
3551
  if (formats.includes("json")) {
3362
- writeFileSync(join2(outputDir, "catalog.json"), exportJSON(db, sessionId));
3363
- process.stderr.write("\u2713 catalog.json\n");
3552
+ writeFileSync(join3(outputDir, "catalog.json"), exportJSON(db, sessionId));
3364
3553
  }
3365
3554
  if (formats.includes("yaml")) {
3366
- writeFileSync(join2(outputDir, "catalog-info.yaml"), exportBackstageYAML(nodes, edges));
3367
- process.stderr.write("\u2713 catalog-info.yaml\n");
3555
+ writeFileSync(join3(outputDir, "catalog-info.yaml"), exportBackstageYAML(nodes, edges));
3368
3556
  }
3369
3557
  if (formats.includes("html") || formats.includes("map") || formats.includes("discovery")) {
3370
- writeFileSync(join2(outputDir, "discovery.html"), exportDiscoveryApp(nodes, edges));
3371
- process.stderr.write("\u2713 discovery.html\n");
3558
+ writeFileSync(join3(outputDir, "discovery.html"), exportDiscoveryApp(nodes, edges));
3372
3559
  }
3373
3560
  if (formats.includes("sops")) {
3374
3561
  const sops = db.getSOPs(sessionId);
3375
3562
  for (const sop of sops) {
3376
3563
  const filename = sop.title.toLowerCase().replace(/[^a-z0-9]+/g, "-") + ".md";
3377
- writeFileSync(join2(outputDir, "sops", filename), exportSOPMarkdown(sop));
3564
+ writeFileSync(join3(outputDir, "sops", filename), exportSOPMarkdown(sop));
3378
3565
  const wfFilename = `workflow-${sop.workflowId.substring(0, 8)}.mermaid`;
3379
- writeFileSync(join2(outputDir, "workflows", wfFilename), generateWorkflowMermaid(sop));
3380
- }
3381
- if (sops.length > 0) {
3382
- process.stderr.write(`\u2713 ${sops.length} SOPs + workflow diagrams
3383
- `);
3566
+ writeFileSync(join3(outputDir, "workflows", wfFilename), generateWorkflowMermaid(sop));
3384
3567
  }
3385
3568
  }
3386
3569
  }
3387
3570
 
3388
3571
  // src/preflight.ts
3389
- import { execSync } from "child_process";
3390
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
3391
- import { join as join3 } from "path";
3572
+ import { execSync as execSync2 } from "child_process";
3573
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
3574
+ import { join as join4 } from "path";
3392
3575
  function isOAuthLoggedIn() {
3393
3576
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
3394
- const credFile = join3(home, ".claude", ".credentials.json");
3395
- if (!existsSync2(credFile)) return false;
3577
+ const credFile = join4(home, ".claude", ".credentials.json");
3578
+ if (!existsSync3(credFile)) return false;
3396
3579
  try {
3397
3580
  const creds = JSON.parse(readFileSync2(credFile, "utf8"));
3398
3581
  const oauth = creds["claudeAiOauth"];
@@ -3403,7 +3586,7 @@ function isOAuthLoggedIn() {
3403
3586
  }
3404
3587
  function checkPrerequisites() {
3405
3588
  try {
3406
- execSync("claude --version", { stdio: "pipe" });
3589
+ execSync2("claude --version", { stdio: "pipe" });
3407
3590
  } catch {
3408
3591
  process.stderr.write(
3409
3592
  "\n\u274C Claude CLI nicht gefunden.\n Datasynx Cartography braucht die Claude CLI als Runtime-Dependency.\n\n Installieren:\n npm install -g @anthropic-ai/claude-code\n # oder\n curl -fsSL https://claude.ai/install.sh | bash\n\n Danach: claude login\n\n"