@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/cli.js CHANGED
@@ -6,11 +6,22 @@ import {
6
6
  NODE_TYPES,
7
7
  SOPStepSchema,
8
8
  defaultConfig
9
- } from "./chunk-A7FTULDM.js";
9
+ } from "./chunk-VZO6XBKX.js";
10
10
  import {
11
+ HOME,
12
+ IS_LINUX,
13
+ IS_MAC,
14
+ IS_WIN,
15
+ PLATFORM,
16
+ commandExists,
17
+ dbScanDirs,
18
+ findFiles,
19
+ run,
11
20
  scanAllBookmarks,
12
- scanAllHistory
13
- } from "./chunk-2VIAXA5T.js";
21
+ scanAllHistory,
22
+ scanWindowsDbServices,
23
+ scanWindowsPrograms
24
+ } from "./chunk-3NVQ3ND6.js";
14
25
 
15
26
  // src/cli.ts
16
27
  import { Command } from "commander";
@@ -555,8 +566,7 @@ function stripSensitive(target) {
555
566
  }
556
567
  }
557
568
  async function createCartographyTools(db, sessionId, opts = {}) {
558
- const sdk = await import("@anthropic-ai/claude-code");
559
- const { tool, createSdkMcpServer } = sdk;
569
+ const { tool, createSdkMcpServer } = await import("@anthropic-ai/claude-agent-sdk");
560
570
  const tools = [
561
571
  tool("save_node", "Save an infrastructure node to the catalog", {
562
572
  id: z.string(),
@@ -564,7 +574,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
564
574
  name: z.string(),
565
575
  discoveredVia: z.string(),
566
576
  confidence: z.number().min(0).max(1),
567
- metadata: z.record(z.unknown()).optional(),
577
+ metadata: z.record(z.string(), z.unknown()).optional(),
568
578
  tags: z.array(z.string()).optional(),
569
579
  domain: z.string().optional().describe('Business domain, e.g. "Marketing", "Finance"'),
570
580
  subDomain: z.string().optional().describe('Sub-domain, e.g. "Forecast client orders"'),
@@ -675,33 +685,71 @@ async function createCartographyTools(db, sessionId, opts = {}) {
675
685
  tool("scan_local_databases", "Scan for local database files and running DB servers \u2014 PostgreSQL databases, MySQL, SQLite files from installed apps", {
676
686
  deep: z.boolean().default(false).optional().describe("Also search home directory recursively for SQLite/DB files (slower)")
677
687
  }, async (args) => {
678
- const { execSync: execSync2 } = await import("child_process");
679
- const { homedir } = await import("os");
680
- const { existsSync: existsSync3 } = await import("fs");
681
688
  const deep = args["deep"] ?? false;
682
- const HOME = homedir();
683
- const run = (cmd) => {
684
- try {
685
- return execSync2(cmd, { stdio: "pipe", timeout: 1e4, shell: "/bin/sh" }).toString().trim();
686
- } catch {
687
- return "";
688
- }
689
- };
690
689
  const results = {};
691
- results["POSTGRES_DATABASES"] = run(`psql -lqt 2>/dev/null | grep -v "template0\\|template1" | awk '{print $1}' | grep -v "^$\\|^|"`) || "(psql not running or not available)";
692
- results["POSTGRES_CLUSTERS"] = run("pg_lsclusters 2>/dev/null") || "(pg_lsclusters not available)";
693
- results["MYSQL_DATABASES"] = run('mysql --connect-timeout=3 -e "SHOW DATABASES;" 2>/dev/null') || "(mysql not running or requires auth)";
694
- results["MONGODB_DATABASES"] = run(`mongosh --quiet --eval "db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join('\\n')" 2>/dev/null`) || "(mongosh not available)";
695
- results["REDIS_INFO"] = run("redis-cli info server 2>/dev/null | head -5") || "(redis-cli not available)";
696
- const appDirs = [`${HOME}/.config`, `${HOME}/.local/share`, `${HOME}/Library/Application Support`, "/var/lib"].filter((d) => existsSync3(d));
690
+ results["PLATFORM"] = `${PLATFORM} (${IS_WIN ? "Windows" : IS_MAC ? "macOS" : "Linux"})`;
691
+ if (IS_WIN) {
692
+ results["DB_SERVICES"] = scanWindowsDbServices() || "(no database services found)";
693
+ }
694
+ if (commandExists("psql")) {
695
+ if (IS_WIN) {
696
+ results["POSTGRES_DATABASES"] = run("psql -lqt", { timeout: 1e4 }) || "(psql found but not running or requires auth)";
697
+ } else {
698
+ results["POSTGRES_DATABASES"] = run(`psql -lqt 2>/dev/null | grep -v "template0\\|template1" | awk '{print $1}' | grep -v "^$\\|^|"`) || "(psql not running or not available)";
699
+ results["POSTGRES_CLUSTERS"] = run("pg_lsclusters 2>/dev/null") || "(pg_lsclusters not available)";
700
+ }
701
+ } else {
702
+ results["POSTGRES_DATABASES"] = "(psql not installed)";
703
+ }
704
+ if (commandExists("mysql")) {
705
+ if (IS_WIN) {
706
+ results["MYSQL_DATABASES"] = run('mysql --connect-timeout=3 -e "SHOW DATABASES;"', { timeout: 1e4 }) || "(mysql not running or requires auth)";
707
+ } else {
708
+ results["MYSQL_DATABASES"] = run('mysql --connect-timeout=3 -e "SHOW DATABASES;" 2>/dev/null') || "(mysql not running or requires auth)";
709
+ }
710
+ } else {
711
+ results["MYSQL_DATABASES"] = "(mysql not installed)";
712
+ }
713
+ if (commandExists("mongosh")) {
714
+ if (IS_WIN) {
715
+ results["MONGODB_DATABASES"] = run(`mongosh --quiet --eval "db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join('\\n')"`, { timeout: 1e4 }) || "(mongosh not available)";
716
+ } else {
717
+ results["MONGODB_DATABASES"] = run(`mongosh --quiet --eval "db.adminCommand({listDatabases:1}).databases.map(d=>d.name).join('\\n')" 2>/dev/null`) || "(mongosh not available)";
718
+ }
719
+ } else {
720
+ results["MONGODB_DATABASES"] = "(mongosh not installed)";
721
+ }
722
+ if (commandExists("redis-cli")) {
723
+ if (IS_WIN) {
724
+ results["REDIS_INFO"] = run("redis-cli info server", { timeout: 1e4 }).split("\n").slice(0, 5).join("\n") || "(redis-cli not available)";
725
+ } else {
726
+ results["REDIS_INFO"] = run("redis-cli info server 2>/dev/null | head -5") || "(redis-cli not available)";
727
+ }
728
+ } else {
729
+ results["REDIS_INFO"] = "(redis-cli not installed)";
730
+ }
731
+ const appDirs = dbScanDirs();
697
732
  if (appDirs.length > 0) {
698
- const findCmds = appDirs.map((d) => `find "${d}" -maxdepth 4 \\( -name "*.sqlite" -o -name "*.sqlite3" -o -name "*.db" \\) 2>/dev/null`).join("; ");
699
- results["SQLITE_APP_FILES"] = run(`{ ${findCmds}; } | head -80`) || "(none found)";
733
+ results["SQLITE_APP_FILES"] = findFiles(appDirs, ["*.sqlite", "*.sqlite3", "*.db"], 4, 80) || "(none found)";
700
734
  }
701
735
  if (deep) {
702
- 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)";
736
+ if (IS_WIN) {
737
+ results["SQLITE_DEEP_SCAN"] = run(
738
+ `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`,
739
+ { timeout: 3e4 }
740
+ ) || "(none found)";
741
+ } else {
742
+ 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)";
743
+ }
744
+ }
745
+ if (IS_WIN) {
746
+ results["DB_CONFIG_FILES"] = run(
747
+ `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`,
748
+ { timeout: 15e3 }
749
+ ) || "(none found)";
750
+ } else {
751
+ 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)";
703
752
  }
704
- 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)";
705
753
  const out = Object.entries(results).map(([k, v]) => `=== ${k} ===
706
754
  ${v}`).join("\n\n");
707
755
  return { content: [{ type: "text", text: out }] };
@@ -709,17 +757,23 @@ ${v}`).join("\n\n");
709
757
  tool("scan_k8s_resources", "Scan Kubernetes cluster via kubectl \u2014 100% readonly (get, describe)", {
710
758
  namespace: z.string().optional().describe("Filter by namespace \u2014 empty = all namespaces")
711
759
  }, async (args) => {
712
- const { execSync: execSync2 } = await import("child_process");
713
760
  const ns = args["namespace"];
714
761
  const nsFlag = ns ? `-n ${ns}` : "--all-namespaces";
715
- const run = (cmd) => {
716
- try {
717
- return execSync2(cmd, { stdio: "pipe", timeout: 15e3, shell: "/bin/sh" }).toString().trim();
718
- } catch (e) {
719
- return `(error: ${e instanceof Error ? e.message.split("\n")[0] : String(e)})`;
720
- }
762
+ const runK = (cmd) => {
763
+ const r = run(cmd, { timeout: 15e3 });
764
+ return r || `(error or not available)`;
721
765
  };
722
- const sections = [
766
+ const sections = IS_WIN ? [
767
+ ["CONTEXT", "kubectl config current-context"],
768
+ ["NODES", "kubectl get nodes -o wide"],
769
+ ["NAMESPACES", "kubectl get namespaces"],
770
+ ["SERVICES", `kubectl get services ${nsFlag}`],
771
+ ["DEPLOYMENTS", `kubectl get deployments ${nsFlag}`],
772
+ ["STATEFULSETS", `kubectl get statefulsets ${nsFlag}`],
773
+ ["INGRESSES", `kubectl get ingress ${nsFlag}`],
774
+ ["PODS_RUNNING", `kubectl get pods ${nsFlag} --field-selector=status.phase=Running`],
775
+ ["CONFIGMAPS_SYSTEM", "kubectl get configmaps -n kube-system"]
776
+ ] : [
723
777
  ["CONTEXT", 'kubectl config current-context 2>/dev/null || echo "(no context set)"'],
724
778
  ["NODES", "kubectl get nodes -o wide"],
725
779
  ["NAMESPACES", "kubectl get namespaces"],
@@ -731,128 +785,101 @@ ${v}`).join("\n\n");
731
785
  ["CONFIGMAPS_SYSTEM", "kubectl get configmaps -n kube-system 2>/dev/null | head -30"]
732
786
  ];
733
787
  const out = sections.map(([l, c]) => `=== ${l} ===
734
- ${run(c)}`).join("\n\n");
788
+ ${runK(c)}`).join("\n\n");
735
789
  return { content: [{ type: "text", text: out }] };
736
790
  }),
737
791
  tool("scan_aws_resources", "Scan AWS infrastructure via AWS CLI \u2014 100% readonly (describe, list)", {
738
792
  region: z.string().optional().describe("AWS Region \u2014 default: AWS_DEFAULT_REGION or profile"),
739
793
  profile: z.string().optional().describe("AWS CLI profile")
740
794
  }, async (args) => {
741
- const { execSync: execSync2 } = await import("child_process");
742
795
  const region = args["region"];
743
796
  const profile = args["profile"];
744
797
  const env = { ...process.env };
745
798
  if (region) env["AWS_DEFAULT_REGION"] = region;
746
799
  const pf = profile ? `--profile ${profile}` : "";
747
- const run = (cmd) => {
748
- try {
749
- return execSync2(cmd, { stdio: "pipe", timeout: 2e4, shell: "/bin/sh", env }).toString().trim();
750
- } catch (e) {
751
- return `(error: ${e instanceof Error ? e.message.split("\n")[0] : String(e)})`;
752
- }
753
- };
800
+ const runAws = (cmd) => run(cmd, { timeout: 2e4, env }) || "(error or not available)";
754
801
  const sections = [
755
802
  ["IDENTITY", `aws sts get-caller-identity ${pf} --output json`],
756
- ["EC2", `aws ec2 describe-instances ${pf} --query 'Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,PublicIpAddress,PrivateIpAddress,Tags[?Key==\`Name\`].Value|[0]]' --output table`],
757
- ["RDS", `aws rds describe-db-instances ${pf} --query 'DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus,Endpoint.Address,Endpoint.Port]' --output table`],
758
- ["ELB_V2", `aws elbv2 describe-load-balancers ${pf} --query 'LoadBalancers[*].[LoadBalancerName,DNSName,Type,State.Code]' --output table`],
803
+ ["EC2", `aws ec2 describe-instances ${pf} --query "Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,PublicIpAddress,PrivateIpAddress]" --output table`],
804
+ ["RDS", `aws rds describe-db-instances ${pf} --query "DBInstances[*].[DBInstanceIdentifier,Engine,DBInstanceStatus,Endpoint.Address,Endpoint.Port]" --output table`],
805
+ ["ELB_V2", `aws elbv2 describe-load-balancers ${pf} --query "LoadBalancers[*].[LoadBalancerName,DNSName,Type,State.Code]" --output table`],
759
806
  ["EKS", `aws eks list-clusters ${pf} --output json`],
760
- ["ELASTICACHE", `aws elasticache describe-cache-clusters ${pf} --query 'CacheClusters[*].[CacheClusterId,Engine,CacheClusterStatus]' --output table 2>/dev/null || echo "(not available)"`],
761
- ["S3", `aws s3 ls ${pf} 2>/dev/null || echo "(not available)"`],
762
- ["VPC", `aws ec2 describe-vpcs ${pf} --query 'Vpcs[*].[VpcId,CidrBlock,IsDefault,Tags[?Key==\`Name\`].Value|[0]]' --output table`]
807
+ ["ELASTICACHE", `aws elasticache describe-cache-clusters ${pf} --query "CacheClusters[*].[CacheClusterId,Engine,CacheClusterStatus]" --output table`],
808
+ ["S3", `aws s3 ls ${pf}`],
809
+ ["VPC", `aws ec2 describe-vpcs ${pf} --query "Vpcs[*].[VpcId,CidrBlock,IsDefault]" --output table`]
763
810
  ];
764
811
  const out = sections.map(([l, c]) => `=== ${l} ===
765
- ${run(c)}`).join("\n\n");
812
+ ${runAws(c)}`).join("\n\n");
766
813
  return { content: [{ type: "text", text: out }] };
767
814
  }),
768
815
  tool("scan_gcp_resources", "Scan Google Cloud Platform via gcloud CLI \u2014 100% readonly (list, describe)", {
769
816
  project: z.string().optional().describe("GCP Project ID \u2014 default: current gcloud project")
770
817
  }, async (args) => {
771
- const { execSync: execSync2 } = await import("child_process");
772
818
  const project = args["project"];
773
819
  const pf = project ? `--project ${project}` : "";
774
- const run = (cmd) => {
775
- try {
776
- return execSync2(cmd, { stdio: "pipe", timeout: 2e4, shell: "/bin/sh" }).toString().trim();
777
- } catch (e) {
778
- return `(error: ${e instanceof Error ? e.message.split("\n")[0] : String(e)})`;
779
- }
780
- };
820
+ const runGcp = (cmd) => run(cmd, { timeout: 2e4 }) || "(error or not available)";
781
821
  const sections = [
782
- ["IDENTITY", `gcloud config list account --format='value(core.account)' 2>/dev/null; gcloud config get-value project 2>/dev/null`],
783
- ["COMPUTE_INSTANCES", `gcloud compute instances list ${pf} 2>/dev/null || echo "(error)"`],
784
- ["SQL_INSTANCES", `gcloud sql instances list ${pf} 2>/dev/null || echo "(error)"`],
785
- ["GKE_CLUSTERS", `gcloud container clusters list ${pf} 2>/dev/null || echo "(error)"`],
786
- ["CLOUD_RUN", `gcloud run services list ${pf} --platform managed 2>/dev/null || echo "(error)"`],
787
- ["CLOUD_FUNCTIONS", `gcloud functions list ${pf} 2>/dev/null || echo "(error)"`],
788
- ["REDIS", `gcloud redis instances list ${pf} --regions=- 2>/dev/null || echo "(error)"`],
789
- ["PUBSUB", `gcloud pubsub topics list ${pf} 2>/dev/null || echo "(error)"`],
790
- ["SPANNER", `gcloud spanner instances list ${pf} 2>/dev/null || echo "(error)"`]
822
+ ["IDENTITY", `gcloud config list account --format="value(core.account)"`],
823
+ ["COMPUTE_INSTANCES", `gcloud compute instances list ${pf}`],
824
+ ["SQL_INSTANCES", `gcloud sql instances list ${pf}`],
825
+ ["GKE_CLUSTERS", `gcloud container clusters list ${pf}`],
826
+ ["CLOUD_RUN", `gcloud run services list ${pf} --platform managed`],
827
+ ["CLOUD_FUNCTIONS", `gcloud functions list ${pf}`],
828
+ ["REDIS", `gcloud redis instances list ${pf} --regions=-`],
829
+ ["PUBSUB", `gcloud pubsub topics list ${pf}`],
830
+ ["SPANNER", `gcloud spanner instances list ${pf}`]
791
831
  ];
792
832
  const out = sections.map(([l, c]) => `=== ${l} ===
793
- ${run(c)}`).join("\n\n");
833
+ ${runGcp(c)}`).join("\n\n");
794
834
  return { content: [{ type: "text", text: out }] };
795
835
  }),
796
836
  tool("scan_azure_resources", "Scan Azure infrastructure via az CLI \u2014 100% readonly (list, show)", {
797
837
  subscription: z.string().optional().describe("Azure Subscription ID"),
798
838
  resourceGroup: z.string().optional().describe("Filter by resource group")
799
839
  }, async (args) => {
800
- const { execSync: execSync2 } = await import("child_process");
801
840
  const sub = args["subscription"];
802
841
  const rg = args["resourceGroup"];
803
842
  const sf = sub ? `--subscription ${sub}` : "";
804
843
  const rf = rg ? `--resource-group ${rg}` : "";
805
- const run = (cmd) => {
806
- try {
807
- return execSync2(cmd, { stdio: "pipe", timeout: 2e4, shell: "/bin/sh" }).toString().trim();
808
- } catch (e) {
809
- return `(error: ${e instanceof Error ? e.message.split("\n")[0] : String(e)})`;
810
- }
811
- };
844
+ const runAz = (cmd) => run(cmd, { timeout: 2e4 }) || "(error or not available)";
812
845
  const sections = [
813
- ["IDENTITY", `az account show --output json ${sf} 2>/dev/null || echo "(not logged in \u2014 az login)"`],
814
- ["VMS", `az vm list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
815
- ["AKS", `az aks list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
816
- ["SQL_SERVERS", `az sql server list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
817
- ["POSTGRES", `az postgres server list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
818
- ["REDIS", `az redis list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
819
- ["WEBAPPS", `az webapp list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
820
- ["CONTAINER_APPS", `az containerapp list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`],
821
- ["FUNCTIONS", `az functionapp list ${sf} ${rf} --output table 2>/dev/null || echo "(error)"`]
846
+ ["IDENTITY", `az account show --output json ${sf}`],
847
+ ["VMS", `az vm list ${sf} ${rf} --output table`],
848
+ ["AKS", `az aks list ${sf} ${rf} --output table`],
849
+ ["SQL_SERVERS", `az sql server list ${sf} ${rf} --output table`],
850
+ ["POSTGRES", `az postgres server list ${sf} ${rf} --output table`],
851
+ ["REDIS", `az redis list ${sf} ${rf} --output table`],
852
+ ["WEBAPPS", `az webapp list ${sf} ${rf} --output table`],
853
+ ["CONTAINER_APPS", `az containerapp list ${sf} ${rf} --output table`],
854
+ ["FUNCTIONS", `az functionapp list ${sf} ${rf} --output table`]
822
855
  ];
823
856
  const out = sections.map(([l, c]) => `=== ${l} ===
824
- ${run(c)}`).join("\n\n");
857
+ ${runAz(c)}`).join("\n\n");
825
858
  return { content: [{ type: "text", text: out }] };
826
859
  }),
827
860
  tool("scan_installed_apps", "Scan all installed apps and tools \u2014 IDEs, office, dev tools, business apps, databases", {
828
861
  searchHint: z.string().optional().describe('Optional search term to find specific tools (e.g. "hubspot windsurf cursor")')
829
862
  }, async (args) => {
830
- const { execSync: execSync2 } = await import("child_process");
831
863
  const hint = args["searchHint"];
832
- const run = (cmd) => {
833
- try {
834
- return execSync2(cmd, { stdio: "pipe", timeout: 15e3, shell: "/bin/sh" }).toString().trim();
835
- } catch {
836
- return "";
837
- }
838
- };
839
- const platform = process.platform;
840
864
  const results = {};
841
- if (platform === "darwin") {
865
+ results["PLATFORM"] = `${PLATFORM} (${IS_WIN ? "Windows" : IS_MAC ? "macOS" : "Linux"})`;
866
+ if (IS_MAC) {
842
867
  results["APPLICATIONS"] = run("ls /Applications/ 2>/dev/null | head -200") || "(empty)";
843
868
  results["USER_APPLICATIONS"] = run("ls ~/Applications/ 2>/dev/null | head -100") || "(empty)";
844
869
  results["BREW_CASKS"] = run("brew list --cask 2>/dev/null | head -100") || "(brew not installed)";
845
870
  results["BREW_FORMULAE"] = run("brew list --formula 2>/dev/null | head -150") || "(brew not installed)";
846
871
  results["SPOTLIGHT_APPS"] = run(`mdfind "kMDItemKind == 'Application'" 2>/dev/null | grep -v "^/System" | grep -v "^/Library/Apple" | head -100`) || "(Spotlight not available)";
847
- } else if (platform === "linux") {
872
+ } else if (IS_LINUX) {
848
873
  results["DPKG"] = run("dpkg --list 2>/dev/null | awk '{print $2}' | head -200") || "(dpkg not available)";
849
874
  results["SNAP"] = run("snap list 2>/dev/null | head -50") || "(snap not available)";
850
875
  results["FLATPAK"] = run("flatpak list 2>/dev/null | head -50") || "(flatpak not available)";
851
876
  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)";
852
877
  results["RPM"] = run("rpm -qa 2>/dev/null | head -200") || "(rpm not available)";
853
- } else if (platform === "win32") {
854
- results["WINGET"] = run("winget list 2>/dev/null | head -100") || "(winget not available)";
855
- results["PROGRAMS_x64"] = run('reg query "HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall" /s /v DisplayName 2>/dev/null | findstr DisplayName | head -100') || "(not available)";
878
+ } else if (IS_WIN) {
879
+ results["WINGET"] = run("winget list --accept-source-agreements", { timeout: 2e4 }) || "(winget not available)";
880
+ results["INSTALLED_PROGRAMS"] = scanWindowsPrograms() || "(registry scan failed)";
881
+ results["CHOCO"] = run("choco list --local-only", { timeout: 15e3 }) || "(chocolatey not installed)";
882
+ results["SCOOP"] = run("scoop list", { timeout: 15e3 }) || "(scoop not installed)";
856
883
  }
857
884
  const knownTools = [
858
885
  // IDEs & Editors
@@ -915,7 +942,6 @@ ${run(c)}`).join("\n\n");
915
942
  "php",
916
943
  "composer",
917
944
  "dotnet",
918
- "dotnet-sdk",
919
945
  // Databases
920
946
  "psql",
921
947
  "mysql",
@@ -956,6 +982,8 @@ ${run(c)}`).join("\n\n");
956
982
  "brave",
957
983
  "opera",
958
984
  "edge",
985
+ // Windows-specific
986
+ ...IS_WIN ? ["pwsh", "powershell", "wsl", "winget", "choco", "scoop", "notepad++"] : [],
959
987
  // Monitoring / Analytics
960
988
  "datadog-agent",
961
989
  "newrelic-agent",
@@ -970,21 +998,35 @@ ${run(c)}`).join("\n\n");
970
998
  const found = [];
971
999
  const notFound = [];
972
1000
  for (const t of knownTools) {
973
- const r = run(`which ${t} 2>/dev/null`);
1001
+ const r = commandExists(t);
974
1002
  if (r) found.push(`${t}: ${r}`);
975
1003
  else notFound.push(t);
976
1004
  }
977
- results["WHICH_FOUND"] = found.join("\n") || "(none found)";
978
- results["WHICH_NOT_FOUND"] = notFound.join(", ");
1005
+ results["TOOLS_FOUND"] = found.join("\n") || "(none found)";
1006
+ results["TOOLS_NOT_FOUND"] = notFound.join(", ");
979
1007
  if (hint) {
980
1008
  const terms = hint.split(/[\s,]+/).filter(Boolean);
981
1009
  const hintResults = [];
982
1010
  for (const term of terms) {
983
1011
  const safe = term.replace(/[^a-zA-Z0-9._-]/g, "");
984
1012
  if (!safe) continue;
985
- 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`);
986
- if (r) hintResults.push(`${term}: ${r}`);
987
- else hintResults.push(`${term}: (not found)`);
1013
+ const cmdPath = commandExists(safe);
1014
+ if (cmdPath) {
1015
+ hintResults.push(`${term}: ${cmdPath}`);
1016
+ continue;
1017
+ }
1018
+ let fallback = "";
1019
+ if (IS_WIN) {
1020
+ fallback = run(
1021
+ `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`,
1022
+ { timeout: 1e4 }
1023
+ );
1024
+ } else if (IS_MAC) {
1025
+ fallback = run(`mdfind -name "${safe}" 2>/dev/null | head -5`);
1026
+ } else {
1027
+ 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`);
1028
+ }
1029
+ hintResults.push(fallback ? `${term}: ${fallback}` : `${term}: (not found)`);
988
1030
  }
989
1031
  results["HINT_SEARCH"] = hintResults.join("\n");
990
1032
  }
@@ -1023,9 +1065,9 @@ ${v}`).join("\n\n");
1023
1065
  }
1024
1066
 
1025
1067
  // src/safety.ts
1026
- 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;
1027
- var BLOCKED_REDIRECTS = />>|>[^>]/;
1028
- var safetyHook = async (input) => {
1068
+ 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;
1069
+ var BLOCKED_REDIRECTS = />>|>[^>]|Out-File|Set-Content|Add-Content/;
1070
+ var safetyHook = async (input, _toolUseID, _options) => {
1029
1071
  if (!("tool_name" in input)) return {};
1030
1072
  if (input.tool_name !== "Bash") return {};
1031
1073
  const cmd = input.tool_input?.command ?? "";
@@ -1048,13 +1090,18 @@ var safetyHook = async (input) => {
1048
1090
 
1049
1091
  // src/agent.ts
1050
1092
  async function runDiscovery(config, db, sessionId, onEvent, onAskUser, hint) {
1051
- const { query } = await import("@anthropic-ai/claude-code");
1093
+ const { query } = await import("@anthropic-ai/claude-agent-sdk");
1052
1094
  const tools = await createCartographyTools(db, sessionId, { onAskUser });
1053
1095
  const hintSection = hint ? `
1054
1096
  \u26A1 USER HINT (HIGH PRIORITY): The user wants to find these specific tools: "${hint}"
1055
1097
  \u2192 Run scan_installed_apps(searchHint: "${hint}") IMMEDIATELY and save found tools as saas_tool nodes!
1056
1098
  ` : "";
1099
+ const platformName = IS_WIN ? "Windows" : IS_MAC ? "macOS" : "Linux";
1100
+ 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";
1101
+ 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";
1102
+ const processCmd = IS_WIN ? "Get-Process" : "ps aux";
1057
1103
  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.
1104
+ PLATFORM: ${platformName} (${PLATFORM})
1058
1105
  ${hintSection}
1059
1106
  \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
1060
1107
  STEP 1 \u2014 Browser Bookmarks (ALWAYS FIRST):
@@ -1085,7 +1132,8 @@ STEP 4 \u2014 Local Databases & Infrastructure:
1085
1132
  \u2022 MongoDB running \u2192 save_node as database_server
1086
1133
  \u2022 Redis running \u2192 save_node as cache_server
1087
1134
  \u2022 SQLite files in app directories \u2192 save_node as database if clearly a business app DB
1088
- Then run: ss -tlnp && ps aux \u2192 identify all listening ports/processes
1135
+ Then run: ${networkScanCmd}
1136
+ Also run: ${processCmd} \u2192 identify running services
1089
1137
  Deepen each service: DB\u2192schemas, API\u2192endpoints, Queue\u2192topics
1090
1138
 
1091
1139
  STEP 5 \u2014 Cloud & Kubernetes (if CLI available):
@@ -1122,8 +1170,20 @@ PORT MAPPING: 5432=postgres, 3306=mysql, 27017=mongodb, 6379=redis,
1122
1170
  9092=kafka, 5672=rabbitmq, 80/443/8080/3000=web_service,
1123
1171
  9090=prometheus, 8500=consul, 8200=vault, 2379=etcd
1124
1172
 
1173
+ PLATFORM-SPECIFIC NOTES (${platformName}):
1174
+ ${IS_WIN ? `\u2022 Use PowerShell commands: Get-NetTCPConnection, Get-Process, Get-Service, Get-ChildItem
1175
+ \u2022 Do NOT use Unix commands (ss, ps aux, find, which, head, grep) \u2014 they won't work on Windows
1176
+ \u2022 Use $env:LOCALAPPDATA, $env:APPDATA for app data paths
1177
+ \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)
1178
+ \u2022 Use ps aux for process listing
1179
+ \u2022 Applications are in /Applications and ~/Applications
1180
+ \u2022 Homebrew (brew) for package management` : `\u2022 Use ss -tlnp for port scanning
1181
+ \u2022 Use ps aux for process listing
1182
+ \u2022 Check dpkg, snap, flatpak for installed packages
1183
+ \u2022 Check Snap/Flatpak browser variants for bookmarks`}
1184
+
1125
1185
  RULES:
1126
- \u2022 Read-only only (ss, ps, cat, head, curl -s, docker inspect, kubectl get)
1186
+ \u2022 Read-only only (${readOnlyTools})
1127
1187
  \u2022 Node IDs: "type:host:port" or "type:name" \u2014 no paths, no credentials
1128
1188
  \u2022 saas_tool IDs: "saas_tool:github.com", "saas_tool:vscode", "saas_tool:cursor"
1129
1189
  \u2022 Installed-app IDs: "saas_tool:<appname>" e.g. "saas_tool:slack", "saas_tool:docker-desktop"
@@ -1150,7 +1210,7 @@ Use ask_user when you need context from the user.`;
1150
1210
  options: {
1151
1211
  model: config.agentModel,
1152
1212
  maxTurns: config.maxTurns,
1153
- customSystemPrompt: systemPrompt,
1213
+ systemPrompt,
1154
1214
  mcpServers: { cartography: tools },
1155
1215
  allowedTools: [
1156
1216
  "Bash",
@@ -2776,23 +2836,18 @@ function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml
2776
2836
  const edges = db.getEdges(sessionId);
2777
2837
  const jgfPath = join2(outputDir, "cartography-graph.jgf.json");
2778
2838
  writeFileSync(jgfPath, exportJGF(nodes, edges));
2779
- process.stderr.write("\u2713 cartography-graph.jgf.json\n");
2780
2839
  if (formats.includes("mermaid")) {
2781
2840
  writeFileSync(join2(outputDir, "topology.mermaid"), generateTopologyMermaid(nodes, edges));
2782
2841
  writeFileSync(join2(outputDir, "dependencies.mermaid"), generateDependencyMermaid(nodes, edges));
2783
- process.stderr.write("\u2713 topology.mermaid, dependencies.mermaid\n");
2784
2842
  }
2785
2843
  if (formats.includes("json")) {
2786
2844
  writeFileSync(join2(outputDir, "catalog.json"), exportJSON(db, sessionId));
2787
- process.stderr.write("\u2713 catalog.json\n");
2788
2845
  }
2789
2846
  if (formats.includes("yaml")) {
2790
2847
  writeFileSync(join2(outputDir, "catalog-info.yaml"), exportBackstageYAML(nodes, edges));
2791
- process.stderr.write("\u2713 catalog-info.yaml\n");
2792
2848
  }
2793
2849
  if (formats.includes("html") || formats.includes("map") || formats.includes("discovery")) {
2794
2850
  writeFileSync(join2(outputDir, "discovery.html"), exportDiscoveryApp(nodes, edges));
2795
- process.stderr.write("\u2713 discovery.html\n");
2796
2851
  }
2797
2852
  if (formats.includes("sops")) {
2798
2853
  const sops = db.getSOPs(sessionId);
@@ -2802,10 +2857,6 @@ function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml
2802
2857
  const wfFilename = `workflow-${sop.workflowId.substring(0, 8)}.mermaid`;
2803
2858
  writeFileSync(join2(outputDir, "workflows", wfFilename), generateWorkflowMermaid(sop));
2804
2859
  }
2805
- if (sops.length > 0) {
2806
- process.stderr.write(`\u2713 ${sops.length} SOPs + workflow diagrams
2807
- `);
2808
- }
2809
2860
  }
2810
2861
  }
2811
2862
 
@@ -2824,8 +2875,8 @@ main();
2824
2875
  function main() {
2825
2876
  const program = new Command();
2826
2877
  const CMD = "datasynx-cartography";
2827
- const VERSION = "0.2.3";
2828
- program.name(CMD).description("AI-powered Infrastructure Cartography & SOP Generation").version(VERSION);
2878
+ const VERSION = "0.7.0";
2879
+ program.name(CMD).description("AI-powered Infrastructure Discovery & Agentic AI Cartography").version(VERSION);
2829
2880
  program.command("discover").description("Scan and map your infrastructure").option("--entry <hosts...>", "Entry points", ["localhost"]).option("--depth <n>", "Max crawl depth", "8").option("--max-turns <n>", "Max agent turns", "50").option("--model <m>", "Agent model", "claude-sonnet-4-5-20250929").option("--org <name>", "Organization name (for Backstage)").option("-o, --output <dir>", "Output directory", "./datasynx-output").option("--db <path>", "DB path").option("-v, --verbose", "Show agent reasoning", false).action(async (opts) => {
2830
2881
  checkPrerequisites();
2831
2882
  const config = defaultConfig({
@@ -3034,12 +3085,11 @@ function main() {
3034
3085
  `);
3035
3086
  }
3036
3087
  }
3037
- exportAll(db, sessionId, config.outputDir);
3038
- const osc8 = (url, label) => `\x1B]8;;${url}\x1B\\${label}\x1B]8;;\x1B\\`;
3088
+ exportAll(db, sessionId, config.outputDir, ["discovery"]);
3039
3089
  const discoveryPath = resolve(config.outputDir, "discovery.html");
3040
3090
  w("\n");
3041
3091
  if (existsSync2(discoveryPath)) {
3042
- w(` ${green("\u2192")} ${osc8(`file://${discoveryPath}`, bold("Open discovery.html"))} ${dim("\u2190 Map + Topology")}
3092
+ w(` ${green("\u2713")} ${bold("discovery.html")} ${dim("\u2190 Enterprise Map")}
3043
3093
  `);
3044
3094
  }
3045
3095
  w("\n");
@@ -3082,9 +3132,9 @@ function main() {
3082
3132
  w(` ${green(bold("\u2713"))} Total now: ${bold(String(followupStats.nodes))} nodes, ${bold(String(followupStats.edges))} edges
3083
3133
  `);
3084
3134
  w("\n");
3085
- exportAll(db, sessionId, config.outputDir);
3135
+ exportAll(db, sessionId, config.outputDir, ["discovery"]);
3086
3136
  if (existsSync2(discoveryPath)) {
3087
- w(` ${green("\u2192")} ${osc8(`file://${discoveryPath}`, bold("discovery.html updated"))}
3137
+ w(` ${green("\u2713")} ${bold("discovery.html updated")}
3088
3138
  `);
3089
3139
  }
3090
3140
  w("\n");
@@ -3319,9 +3369,39 @@ ${infraSummary.substring(0, 12e3)}`;
3319
3369
  const out = process.stdout.write.bind(process.stdout);
3320
3370
  const b = bold;
3321
3371
  const line = () => out(dim("\u2500".repeat(60)) + "\n");
3372
+ const platformName = IS_WIN ? "Windows" : IS_MAC ? "macOS" : "Linux";
3322
3373
  out("\n");
3323
3374
  out(b(" DATASYNX CARTOGRAPHY") + " " + dim("v" + VERSION) + "\n");
3324
- out(dim(" AI-powered Infrastructure Cartography & SOP Generation\n"));
3375
+ out(dim(" AI-powered Infrastructure Discovery & Agentic AI Cartography\n"));
3376
+ out(dim(` Platform: ${platformName}
3377
+ `));
3378
+ out("\n");
3379
+ line();
3380
+ out(b(cyan(" CROSS-PLATFORM SUPPORT\n")));
3381
+ out("\n");
3382
+ out(` ${green("Linux")} ss, ps, dpkg/snap/flatpak, find, /bin/sh
3383
+ `);
3384
+ out(` ${green("macOS")} lsof, ps, /Applications, Homebrew, Spotlight, /bin/sh
3385
+ `);
3386
+ out(` ${green("Windows")} Get-NetTCPConnection, Get-Process, Get-Service, Registry,
3387
+ `);
3388
+ out(` winget, choco, scoop, PowerShell
3389
+ `);
3390
+ out("\n");
3391
+ out(dim(" Network scanning:\n"));
3392
+ out(dim(" Linux: ss -tlnp\n"));
3393
+ out(dim(" macOS: lsof -iTCP -sTCP:LISTEN -n -P\n"));
3394
+ out(dim(" Windows: Get-NetTCPConnection -State Listen\n"));
3395
+ out("\n");
3396
+ out(dim(" Installed apps:\n"));
3397
+ out(dim(" Linux: dpkg, rpm, snap, flatpak, .desktop files\n"));
3398
+ out(dim(" macOS: /Applications, brew list, Spotlight (mdfind)\n"));
3399
+ out(dim(" Windows: winget list, Registry scan, choco, scoop\n"));
3400
+ out("\n");
3401
+ out(dim(" Browser bookmarks & history:\n"));
3402
+ out(dim(" Linux: ~/.config/google-chrome, Snap/Flatpak variants, ~/.mozilla\n"));
3403
+ out(dim(" macOS: ~/Library/Application Support/Google/Chrome, ~/Library/.../Firefox\n"));
3404
+ out(dim(" Windows: %LOCALAPPDATA%\\Google\\Chrome\\User Data, %APPDATA%\\Mozilla\n"));
3325
3405
  out("\n");
3326
3406
  line();
3327
3407
  out(b(cyan(" CARTOGRAPHY\n")));
@@ -3330,7 +3410,7 @@ ${infraSummary.substring(0, 12e3)}`;
3330
3410
  `);
3331
3411
  out(` Scans your local infrastructure (Claude Sonnet).
3332
3412
  `);
3333
- out(` Claude autonomously runs ss, ps, curl, docker inspect, kubectl get
3413
+ out(` Claude autonomously runs ${IS_WIN ? "Get-NetTCPConnection, Get-Process" : IS_MAC ? "lsof, ps" : "ss, ps"}, curl, docker inspect, kubectl get
3334
3414
  `);
3335
3415
  out(` and stores everything in SQLite.
3336
3416
  `);
@@ -3346,13 +3426,7 @@ ${infraSummary.substring(0, 12e3)}`;
3346
3426
  out("\n");
3347
3427
  out(dim(" Output:\n"));
3348
3428
  out(dim(" datasynx-output/\n"));
3349
- out(dim(" catalog.json Machine-readable full dump\n"));
3350
- out(dim(" catalog-info.yaml Backstage service catalog\n"));
3351
- out(dim(" topology.mermaid Infrastructure topology (graph TB)\n"));
3352
- out(dim(" dependencies.mermaid Service dependencies (graph LR)\n"));
3353
- out(dim(" discovery.html Enterprise discovery frontend (Map + Topology)\n"));
3354
- out(dim(" sops/ Generated SOPs as Markdown\n"));
3355
- out(dim(" workflows/ Workflow flowcharts as Mermaid\n"));
3429
+ out(dim(" discovery.html Enterprise Map\n"));
3356
3430
  out("\n");
3357
3431
  line();
3358
3432
  out(b(cyan(" ANALYSIS & EXPORT\n")));
@@ -3379,14 +3453,25 @@ ${infraSummary.substring(0, 12e3)}`;
3379
3453
  out(b(cyan(" ARCHITECTURE\n")));
3380
3454
  out("\n");
3381
3455
  out(dim(" CLI (Commander)\n"));
3382
- out(dim(" \u2514\u2500\u2500 Preflight: Claude CLI check + API key + interval validation\n"));
3383
- out(dim(" \u2514\u2500\u2500 Agent Orchestrator (agent.ts)\n"));
3384
- out(dim(" \u2514\u2500\u2500 runDiscovery() \u2192 Claude Sonnet + Bash + MCP Tools\n"));
3385
- out(dim(" \u2514\u2500\u2500 Custom MCP Tools (tools.ts)\n"));
3386
- out(dim(" save_node, save_edge,\n"));
3387
- out(dim(" scan_bookmarks, scan_browser_history,\n"));
3388
- out(dim(" scan_installed_apps, scan_local_databases\n"));
3389
- out(dim(" \u2514\u2500\u2500 CartographyDB (SQLite WAL)\n"));
3456
+ out(dim(" \u2514\u2500\u2500 Preflight: Claude CLI check + API key\n"));
3457
+ out(dim(" \u2514\u2500\u2500 Platform Detection (platform.ts)\n"));
3458
+ out(dim(" \u2514\u2500\u2500 Shell: /bin/sh (Unix) | PowerShell (Windows)\n"));
3459
+ out(dim(" \u2514\u2500\u2500 Agent Orchestrator (agent.ts)\n"));
3460
+ out(dim(" \u2514\u2500\u2500 runDiscovery() \u2192 Claude Sonnet + Bash + MCP Tools\n"));
3461
+ out(dim(" \u2514\u2500\u2500 Custom MCP Tools (tools.ts)\n"));
3462
+ out(dim(" save_node, save_edge,\n"));
3463
+ out(dim(" scan_bookmarks, scan_browser_history,\n"));
3464
+ out(dim(" scan_installed_apps, scan_local_databases\n"));
3465
+ out(dim(" scan_k8s, scan_aws, scan_gcp, scan_azure\n"));
3466
+ out(dim(" \u2514\u2500\u2500 CartographyDB (SQLite WAL)\n"));
3467
+ out("\n");
3468
+ line();
3469
+ out(b(cyan(" SAFETY\n")));
3470
+ out("\n");
3471
+ out(dim(" PreToolUse hook blocks ALL destructive commands:\n"));
3472
+ out(dim(" Unix: rm, mv, dd, chmod, kill, docker rm/run, kubectl delete, >\n"));
3473
+ out(dim(" PowerShell: Remove-Item, Stop-Process, Stop-Service, Out-File, etc.\n"));
3474
+ out(dim(" Claude only reads \u2014 never writes, never deletes.\n"));
3390
3475
  out("\n");
3391
3476
  line();
3392
3477
  out(b(cyan(" SETUP\n")));
@@ -3396,7 +3481,11 @@ ${infraSummary.substring(0, 12e3)}`;
3396
3481
  out(" claude login\n");
3397
3482
  out("\n");
3398
3483
  out(dim(" # 2. API Key (if not using claude login)\n"));
3399
- out(" export ANTHROPIC_API_KEY=sk-ant-...\n");
3484
+ if (IS_WIN) {
3485
+ out(' $env:ANTHROPIC_API_KEY="sk-ant-..."\n');
3486
+ } else {
3487
+ out(" export ANTHROPIC_API_KEY=sk-ant-...\n");
3488
+ }
3400
3489
  out("\n");
3401
3490
  out(dim(" # 3. Go\n"));
3402
3491
  out(" datasynx-cartography discover\n");
@@ -3405,7 +3494,7 @@ ${infraSummary.substring(0, 12e3)}`;
3405
3494
  out("\n");
3406
3495
  });
3407
3496
  program.command("bookmarks").description("View all browser bookmarks (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)").action(async () => {
3408
- const { scanAllBookmarks: scanAllBookmarks2 } = await import("./bookmarks-ITLW7U5D.js");
3497
+ const { scanAllBookmarks: scanAllBookmarks2 } = await import("./bookmarks-72CDYAHD.js");
3409
3498
  const out = (s) => process.stdout.write(s);
3410
3499
  process.stderr.write(" Scanning bookmarks...\n\n");
3411
3500
  const hosts = await scanAllBookmarks2();
@@ -3492,7 +3581,7 @@ ${infraSummary.substring(0, 12e3)}`;
3492
3581
  `);
3493
3582
  return;
3494
3583
  }
3495
- const { NODE_TYPES: NODE_TYPES2 } = await import("./types-ZD6G5JKR.js");
3584
+ const { NODE_TYPES: NODE_TYPES2 } = await import("./types-RHMJ6EGX.js");
3496
3585
  if (!process.stdin.isTTY) {
3497
3586
  w(red("\n \u2717 Interactive mode requires a terminal (use --file for non-interactive)\n\n"));
3498
3587
  process.exitCode = 1;
@@ -3633,12 +3722,18 @@ ${infraSummary.substring(0, 12e3)}`;
3633
3722
  }
3634
3723
  }
3635
3724
  const localTools = [
3636
- ["docker", "docker --version"],
3637
- ["ss", "ss --version"]
3725
+ ["docker", "docker --version", "all"]
3638
3726
  ];
3727
+ if (IS_WIN) {
3728
+ localTools.push(["PowerShell (Get-NetTCPConnection)", 'powershell -Command "Get-NetTCPConnection -State Listen | Select-Object -First 1"', "win32"]);
3729
+ } else if (IS_MAC) {
3730
+ localTools.push(["lsof", "lsof -v 2>&1 | head -1", "darwin"]);
3731
+ } else {
3732
+ localTools.push(["ss", "ss --version", "linux"]);
3733
+ }
3639
3734
  for (const [name, cmd] of localTools) {
3640
3735
  try {
3641
- execSync2(cmd, { stdio: "pipe" });
3736
+ execSync2(cmd, { stdio: "pipe", timeout: 1e4 });
3642
3737
  ok(`${name} ${dim2("(discovery tool)")}`);
3643
3738
  } catch {
3644
3739
  warn(`${name} not found ${dim2("\u2014 discovery without " + name + " will be limited")}`);
@@ -3673,7 +3768,7 @@ ${infraSummary.substring(0, 12e3)}`;
3673
3768
  o(_c(" |___/ ") + "\n");
3674
3769
  o("\n");
3675
3770
  o(_b(" Cartography") + " " + _d("v" + VERSION) + "\n");
3676
- o(_d(" AI-powered Infrastructure Discovery & SOP Generation\n"));
3771
+ o(_d(" AI-powered Infrastructure Discovery & Agentic AI Cartography\n"));
3677
3772
  o(_d(" Built on Claude Agent SDK\n"));
3678
3773
  o("\n");
3679
3774
  if (process.argv.length <= 2) {