@datasynx/agentic-ai-cartography 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -710,7 +710,7 @@ var safetyHook = async (input) => {
710
710
  };
711
711
 
712
712
  // src/agent.ts
713
- async function runDiscovery(config, db, sessionId, onOutput) {
713
+ async function runDiscovery(config, db, sessionId, onEvent) {
714
714
  const { query } = await import("@anthropic-ai/claude-code");
715
715
  const tools = await createCartographyTools(db, sessionId);
716
716
  const systemPrompt = `Du bist ein Infrastruktur-Discovery-Agent.
@@ -735,6 +735,7 @@ REGELN:
735
735
  - KEINE Credentials speichern
736
736
 
737
737
  Entrypoints: ${config.entryPoints.join(", ")}`;
738
+ let turnCount = 0;
738
739
  for await (const msg of query({
739
740
  prompt: systemPrompt,
740
741
  options: {
@@ -754,14 +755,39 @@ Entrypoints: ${config.entryPoints.join(", ")}`;
754
755
  permissionMode: "bypassPermissions"
755
756
  }
756
757
  })) {
757
- if (msg.type === "assistant" && onOutput) {
758
+ if (!onEvent) continue;
759
+ if (msg.type === "assistant") {
760
+ turnCount++;
761
+ onEvent({ kind: "turn", turn: turnCount });
758
762
  for (const block of msg.message.content) {
759
763
  if (block.type === "text") {
760
- onOutput(block.text);
764
+ onEvent({ kind: "thinking", text: block.text });
765
+ }
766
+ if (block.type === "tool_use") {
767
+ onEvent({
768
+ kind: "tool_call",
769
+ tool: block.name,
770
+ input: block.input
771
+ });
761
772
  }
762
773
  }
763
774
  }
764
- if (msg.type === "result") return;
775
+ if (msg.type === "user") {
776
+ const content = msg.message?.content;
777
+ if (Array.isArray(content)) {
778
+ for (const block of content) {
779
+ if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_result") {
780
+ const tb = block;
781
+ const text = typeof tb.content === "string" ? tb.content : "";
782
+ onEvent({ kind: "tool_result", tool: tb.tool_use_id ?? "", output: text });
783
+ }
784
+ }
785
+ }
786
+ }
787
+ if (msg.type === "result") {
788
+ onEvent({ kind: "done" });
789
+ return;
790
+ }
765
791
  }
766
792
  }
767
793
  async function runShadowCycle(config, db, sessionId, prevSnapshot, currSnapshot, onOutput) {
@@ -1594,7 +1620,7 @@ if (process.env.CARTOGRAPHYY_DAEMON === "1") {
1594
1620
  function main() {
1595
1621
  const program = new Command();
1596
1622
  const CMD = "datasynx-cartography";
1597
- const VERSION = "0.1.3";
1623
+ const VERSION = "0.1.5";
1598
1624
  program.name(CMD).description("AI-powered Infrastructure Cartography & SOP Generation").version(VERSION);
1599
1625
  program.command("discover").description("Infrastruktur scannen und kartographieren").option("--entry <hosts...>", "Startpunkte", ["localhost"]).option("--depth <n>", "Max Tiefe", "8").option("--max-turns <n>", "Max Agent-Turns", "50").option("--model <m>", "Agent-Model", "claude-sonnet-4-5-20250929").option("--org <name>", "Organisation (f\xFCr Backstage)").option("-o, --output <dir>", "Output-Dir", "./datasynx-output").option("--db <path>", "DB-Pfad").option("-v, --verbose", "Agent-Reasoning anzeigen", false).action(async (opts) => {
1600
1626
  checkPrerequisites();
@@ -1611,30 +1637,135 @@ function main() {
1611
1637
  });
1612
1638
  const db = new CartographyDB(config.dbPath);
1613
1639
  const sessionId = db.createSession("discover", config);
1614
- process.stderr.write(`\u{1F50D} Scanning ${config.entryPoints.join(", ")}...
1640
+ const w = process.stderr.write.bind(process.stderr);
1641
+ const bold = (s) => `\x1B[1m${s}\x1B[0m`;
1642
+ const dim = (s) => `\x1B[2m${s}\x1B[0m`;
1643
+ const cyan = (s) => `\x1B[36m${s}\x1B[0m`;
1644
+ const green = (s) => `\x1B[32m${s}\x1B[0m`;
1645
+ const yellow = (s) => `\x1B[33m${s}\x1B[0m`;
1646
+ const magenta = (s) => `\x1B[35m${s}\x1B[0m`;
1647
+ const SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
1648
+ let spinIdx = 0;
1649
+ let spinnerTimer = null;
1650
+ let spinnerMsg = "";
1651
+ const startSpinner = (msg) => {
1652
+ spinnerMsg = msg;
1653
+ if (spinnerTimer) clearInterval(spinnerTimer);
1654
+ spinnerTimer = setInterval(() => {
1655
+ const frame = cyan(SPINNER[spinIdx % SPINNER.length] ?? "\u280B");
1656
+ w(`\r ${frame} ${spinnerMsg}\x1B[K`);
1657
+ spinIdx++;
1658
+ }, 80);
1659
+ };
1660
+ const stopSpinner = () => {
1661
+ if (spinnerTimer) {
1662
+ clearInterval(spinnerTimer);
1663
+ spinnerTimer = null;
1664
+ }
1665
+ w(`\r\x1B[K`);
1666
+ };
1667
+ const startTime = Date.now();
1668
+ let turnNum = 0;
1669
+ let nodeCount = 0;
1670
+ let edgeCount = 0;
1671
+ w("\n");
1672
+ w(` ${bold("DISCOVERY")} ${dim(config.entryPoints.join(", "))}
1673
+ `);
1674
+ w(` ${dim("Model: " + config.agentModel + " | MaxTurns: " + config.maxTurns)}
1675
+ `);
1676
+ w(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
1677
+ w("\n");
1678
+ const logLine = (icon, msg) => {
1679
+ stopSpinner();
1680
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
1681
+ w(` ${icon} ${msg} ${dim(elapsed + "s")}
1615
1682
  `);
1616
- process.stderr.write(` Model: ${config.agentModel} | MaxTurns: ${config.maxTurns}
1617
-
1683
+ };
1684
+ const handleEvent = (event) => {
1685
+ switch (event.kind) {
1686
+ case "turn":
1687
+ turnNum = event.turn;
1688
+ startSpinner(`Turn ${turnNum}/${config.maxTurns} ${dim(`nodes:${nodeCount} edges:${edgeCount}`)}`);
1689
+ break;
1690
+ case "thinking":
1691
+ if (config.verbose) {
1692
+ stopSpinner();
1693
+ const lines = event.text.split("\n").slice(0, 3);
1694
+ for (const line of lines) {
1695
+ w(` ${dim(" " + line.substring(0, 80))}
1618
1696
  `);
1697
+ }
1698
+ }
1699
+ break;
1700
+ case "tool_call": {
1701
+ const toolName = event.tool.replace("mcp__cartograph__", "");
1702
+ if (toolName === "Bash") {
1703
+ const cmd = (event.input["command"] ?? "").substring(0, 70);
1704
+ startSpinner(`${yellow("$")} ${cmd}`);
1705
+ } else if (toolName === "save_node") {
1706
+ const id = event.input["id"] ?? "?";
1707
+ const type = event.input["type"] ?? "?";
1708
+ nodeCount++;
1709
+ logLine(green("+"), `${bold("Node")} ${cyan(id)} ${dim("(" + type + ")")}`);
1710
+ startSpinner(`Turn ${turnNum}/${config.maxTurns} ${dim(`nodes:${nodeCount} edges:${edgeCount}`)}`);
1711
+ } else if (toolName === "save_edge") {
1712
+ const src = event.input["sourceId"] ?? "?";
1713
+ const tgt = event.input["targetId"] ?? "?";
1714
+ const rel = event.input["relationship"] ?? "\u2192";
1715
+ edgeCount++;
1716
+ logLine(magenta("~"), `${bold("Edge")} ${src} ${dim(rel)} ${cyan(tgt)}`);
1717
+ startSpinner(`Turn ${turnNum}/${config.maxTurns} ${dim(`nodes:${nodeCount} edges:${edgeCount}`)}`);
1718
+ } else if (toolName === "get_catalog") {
1719
+ startSpinner(`Catalog-Check ${dim("(Duplikate vermeiden)")}`);
1720
+ } else {
1721
+ startSpinner(`${toolName}...`);
1722
+ }
1723
+ break;
1724
+ }
1725
+ case "tool_result":
1726
+ break;
1727
+ case "done":
1728
+ stopSpinner();
1729
+ break;
1730
+ }
1731
+ };
1619
1732
  try {
1620
- await runDiscovery(config, db, sessionId, (text) => {
1621
- if (config.verbose) process.stdout.write(text + "\n");
1622
- });
1733
+ await runDiscovery(config, db, sessionId, handleEvent);
1623
1734
  } catch (err) {
1624
- process.stderr.write(`\u274C Discovery fehlgeschlagen: ${err}
1735
+ stopSpinner();
1736
+ w(`
1737
+ ${bold("\x1B[31m\u2717\x1B[0m")} Discovery fehlgeschlagen: ${err}
1625
1738
  `);
1626
1739
  db.close();
1627
1740
  process.exitCode = 1;
1628
1741
  return;
1629
1742
  }
1743
+ stopSpinner();
1630
1744
  db.endSession(sessionId);
1631
1745
  const stats = db.getStats(sessionId);
1632
- process.stderr.write(`
1633
- \u2713 ${stats.nodes} nodes, ${stats.edges} edges discovered
1746
+ const totalSec = ((Date.now() - startTime) / 1e3).toFixed(1);
1747
+ w("\n");
1748
+ w(dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
1749
+ w(` ${green(bold("DONE"))} ${bold(String(stats.nodes))} nodes, ${bold(String(stats.edges))} edges ${dim("in " + totalSec + "s")}
1634
1750
  `);
1751
+ w("\n");
1635
1752
  exportAll(db, sessionId, config.outputDir);
1636
- process.stderr.write(`\u2713 Exported to: ${config.outputDir}
1753
+ w(` ${green("\u2713")} Exported to ${bold(config.outputDir)}
1754
+ `);
1755
+ const nodeList = db.getNodes(sessionId).slice(0, 8);
1756
+ if (nodeList.length > 0) {
1757
+ w("\n");
1758
+ w(` ${bold("Discovered:")}
1637
1759
  `);
1760
+ for (const n of nodeList) {
1761
+ const tag = dim(`[${n.type}]`);
1762
+ w(` ${cyan("\u25CF")} ${n.id} ${tag}
1763
+ `);
1764
+ }
1765
+ if (stats.nodes > 8) w(dim(` ... +${stats.nodes - 8} more
1766
+ `));
1767
+ }
1768
+ w("\n");
1638
1769
  db.close();
1639
1770
  });
1640
1771
  const shadow = program.command("shadow").description("Shadow-Daemon verwalten");