@datasynx/agentic-ai-cartography 0.9.2 → 1.1.0

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
@@ -2,6 +2,203 @@
2
2
  import Database from "better-sqlite3";
3
3
  import { mkdirSync } from "fs";
4
4
  import { dirname } from "path";
5
+ import { z as z2 } from "zod";
6
+
7
+ // src/types.ts
8
+ import { z } from "zod";
9
+ var NODE_TYPES = [
10
+ "host",
11
+ "database_server",
12
+ "database",
13
+ "table",
14
+ "web_service",
15
+ "api_endpoint",
16
+ "cache_server",
17
+ "message_broker",
18
+ "queue",
19
+ "topic",
20
+ "container",
21
+ "pod",
22
+ "k8s_cluster",
23
+ "config_file",
24
+ "saas_tool",
25
+ "unknown"
26
+ ];
27
+ var EDGE_RELATIONSHIPS = [
28
+ "connects_to",
29
+ "reads_from",
30
+ "writes_to",
31
+ "calls",
32
+ "contains",
33
+ "depends_on"
34
+ ];
35
+ var NodeSchema = z.object({
36
+ id: z.string().describe('Format: "{type}:{host}:{port}" oder "{type}:{name}"'),
37
+ type: z.enum(NODE_TYPES),
38
+ name: z.string(),
39
+ discoveredVia: z.string(),
40
+ confidence: z.number().min(0).max(1).default(0.5),
41
+ metadata: z.record(z.string(), z.unknown()).default({}),
42
+ tags: z.array(z.string()).default([]),
43
+ domain: z.string().optional().describe('Business domain, e.g. "Marketing", "Finance"'),
44
+ subDomain: z.string().optional().describe('Sub-domain, e.g. "Forecast client orders"'),
45
+ qualityScore: z.number().min(0).max(100).optional().describe("Data quality score 0\u2013100")
46
+ });
47
+ var EdgeSchema = z.object({
48
+ sourceId: z.string(),
49
+ targetId: z.string(),
50
+ relationship: z.enum(EDGE_RELATIONSHIPS),
51
+ evidence: z.string(),
52
+ confidence: z.number().min(0).max(1).default(0.5)
53
+ });
54
+ var DataAssetSchema = z.object({
55
+ id: z.string(),
56
+ name: z.string(),
57
+ domain: z.string(),
58
+ subDomain: z.string().optional(),
59
+ qualityScore: z.number().min(0).max(100).optional(),
60
+ metadata: z.record(z.string(), z.unknown()).default({}),
61
+ position: z.object({ q: z.number(), r: z.number() })
62
+ });
63
+ var ClusterSchema = z.object({
64
+ id: z.string(),
65
+ label: z.string(),
66
+ domain: z.string(),
67
+ color: z.string(),
68
+ assetIds: z.array(z.string()),
69
+ centroid: z.object({ x: z.number(), y: z.number() })
70
+ });
71
+ var ConnectionSchema = z.object({
72
+ id: z.string(),
73
+ sourceAssetId: z.string(),
74
+ targetAssetId: z.string(),
75
+ type: z.string().optional()
76
+ });
77
+ var DOMAIN_COLORS = {
78
+ "Quality Control": "#1a2744",
79
+ "Supply Chain": "#1e3a6e",
80
+ "Marketing": "#6a7fb5",
81
+ "Finance": "#3a8a8a",
82
+ "HR": "#2a5a9a",
83
+ "Logistics": "#0e7490",
84
+ "Sales": "#1d4ed8",
85
+ "Engineering": "#4338ca",
86
+ "Operations": "#0891b2",
87
+ "Data Layer": "#1e3352",
88
+ "Web / API": "#1a3a1a",
89
+ "Messaging": "#2a1a3a",
90
+ "Infrastructure": "#0f2a40",
91
+ "Other": "#374151"
92
+ };
93
+ var DOMAIN_PALETTE = [
94
+ "#1a2e5a",
95
+ "#1e3a8a",
96
+ "#1d4ed8",
97
+ "#2563eb",
98
+ "#3b82f6",
99
+ "#6366f1",
100
+ "#818cf8",
101
+ "#7c9fc3",
102
+ "#0e7490",
103
+ "#0891b2",
104
+ "#06b6d4",
105
+ "#22d3ee",
106
+ "#0d9488",
107
+ "#14b8a6",
108
+ "#2dd4bf",
109
+ "#5eead4"
110
+ ];
111
+ function defaultConfig(overrides = {}) {
112
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
113
+ return {
114
+ maxDepth: 8,
115
+ maxTurns: 50,
116
+ entryPoints: ["localhost"],
117
+ agentModel: "claude-sonnet-4-5-20250929",
118
+ outputDir: "./cartography-output",
119
+ dbPath: `${home}/.cartography/cartography.db`,
120
+ verbose: false,
121
+ ...overrides
122
+ };
123
+ }
124
+
125
+ // src/db.ts
126
+ var SessionRowSchema = z2.object({
127
+ id: z2.string(),
128
+ mode: z2.literal("discover"),
129
+ started_at: z2.string(),
130
+ completed_at: z2.string().nullable().optional(),
131
+ config: z2.string()
132
+ });
133
+ var NodeRowSchema = z2.object({
134
+ id: z2.string(),
135
+ session_id: z2.string(),
136
+ type: z2.enum(NODE_TYPES),
137
+ name: z2.string(),
138
+ discovered_via: z2.string().nullable().optional(),
139
+ discovered_at: z2.string(),
140
+ path_id: z2.string().nullable().optional(),
141
+ depth: z2.number().default(0),
142
+ confidence: z2.number().default(0.5),
143
+ metadata: z2.string().default("{}"),
144
+ tags: z2.string().default("[]"),
145
+ domain: z2.string().nullable().optional(),
146
+ sub_domain: z2.string().nullable().optional(),
147
+ quality_score: z2.number().nullable().optional()
148
+ });
149
+ var EdgeRowSchema = z2.object({
150
+ id: z2.string(),
151
+ session_id: z2.string(),
152
+ source_id: z2.string(),
153
+ target_id: z2.string(),
154
+ relationship: z2.enum(EDGE_RELATIONSHIPS),
155
+ evidence: z2.string().nullable().optional(),
156
+ confidence: z2.number().default(0.5),
157
+ discovered_at: z2.string()
158
+ });
159
+ var EventRowSchema = z2.object({
160
+ id: z2.string(),
161
+ session_id: z2.string(),
162
+ task_id: z2.string().nullable().optional(),
163
+ timestamp: z2.string(),
164
+ event_type: z2.string(),
165
+ process: z2.string(),
166
+ pid: z2.number(),
167
+ target: z2.string().nullable().optional(),
168
+ target_type: z2.string().nullable().optional(),
169
+ port: z2.number().nullable().optional(),
170
+ duration_ms: z2.number().nullable().optional()
171
+ });
172
+ var TaskRowSchema = z2.object({
173
+ id: z2.string(),
174
+ session_id: z2.string(),
175
+ description: z2.string().nullable().optional(),
176
+ started_at: z2.string(),
177
+ completed_at: z2.string().nullable().optional(),
178
+ steps: z2.string().default("[]"),
179
+ involved_services: z2.string().default("[]"),
180
+ status: z2.enum(["active", "completed", "cancelled"])
181
+ });
182
+ var WorkflowRowSchema = z2.object({
183
+ id: z2.string(),
184
+ session_id: z2.string(),
185
+ name: z2.string().nullable().optional(),
186
+ pattern: z2.string(),
187
+ task_ids: z2.string().default("[]"),
188
+ occurrences: z2.number().default(1),
189
+ first_seen: z2.string(),
190
+ last_seen: z2.string(),
191
+ avg_duration_ms: z2.number().nullable().optional(),
192
+ involved_services: z2.string().default("[]")
193
+ });
194
+ var ConnectionRowSchema = z2.object({
195
+ id: z2.string(),
196
+ session_id: z2.string(),
197
+ source_asset_id: z2.string(),
198
+ target_asset_id: z2.string(),
199
+ type: z2.string().nullable().optional(),
200
+ created_at: z2.string()
201
+ });
5
202
  var SCHEMA = `
6
203
  PRAGMA journal_mode = WAL;
7
204
  PRAGMA foreign_keys = ON;
@@ -166,12 +363,13 @@ var CartographyDB = class {
166
363
  return rows.map((r) => this.mapSession(r));
167
364
  }
168
365
  mapSession(r) {
366
+ const v = SessionRowSchema.parse(r);
169
367
  return {
170
- id: r["id"],
171
- mode: r["mode"],
172
- startedAt: r["started_at"],
173
- completedAt: r["completed_at"] ?? void 0,
174
- config: r["config"]
368
+ id: v.id,
369
+ mode: v.mode,
370
+ startedAt: v.started_at,
371
+ completedAt: v.completed_at ?? void 0,
372
+ config: v.config
175
373
  };
176
374
  }
177
375
  // ── Nodes ───────────────────────────────
@@ -202,21 +400,22 @@ var CartographyDB = class {
202
400
  return rows.map((r) => this.mapNode(r));
203
401
  }
204
402
  mapNode(r) {
403
+ const v = NodeRowSchema.parse(r);
205
404
  return {
206
- id: r["id"],
207
- sessionId: r["session_id"],
208
- type: r["type"],
209
- name: r["name"],
210
- discoveredVia: r["discovered_via"],
211
- discoveredAt: r["discovered_at"],
212
- depth: r["depth"],
213
- confidence: r["confidence"],
214
- metadata: JSON.parse(r["metadata"]),
215
- tags: JSON.parse(r["tags"]),
216
- pathId: r["path_id"],
217
- domain: r["domain"] ?? void 0,
218
- subDomain: r["sub_domain"] ?? void 0,
219
- qualityScore: r["quality_score"] ?? void 0
405
+ id: v.id,
406
+ sessionId: v.session_id,
407
+ type: v.type,
408
+ name: v.name,
409
+ discoveredVia: v.discovered_via ?? "",
410
+ discoveredAt: v.discovered_at,
411
+ depth: v.depth,
412
+ confidence: v.confidence,
413
+ metadata: JSON.parse(v.metadata),
414
+ tags: JSON.parse(v.tags),
415
+ pathId: v.path_id ?? void 0,
416
+ domain: v.domain ?? void 0,
417
+ subDomain: v.sub_domain ?? void 0,
418
+ qualityScore: v.quality_score ?? void 0
220
419
  };
221
420
  }
222
421
  deleteNode(sessionId, nodeId) {
@@ -245,16 +444,19 @@ var CartographyDB = class {
245
444
  }
246
445
  getEdges(sessionId) {
247
446
  const rows = this.db.prepare("SELECT * FROM edges WHERE session_id = ?").all(sessionId);
248
- return rows.map((r) => ({
249
- id: r["id"],
250
- sessionId: r["session_id"],
251
- sourceId: r["source_id"],
252
- targetId: r["target_id"],
253
- relationship: r["relationship"],
254
- evidence: r["evidence"],
255
- confidence: r["confidence"],
256
- discoveredAt: r["discovered_at"]
257
- }));
447
+ return rows.map((r) => {
448
+ const v = EdgeRowSchema.parse(r);
449
+ return {
450
+ id: v.id,
451
+ sessionId: v.session_id,
452
+ sourceId: v.source_id,
453
+ targetId: v.target_id,
454
+ relationship: v.relationship,
455
+ evidence: v.evidence ?? "",
456
+ confidence: v.confidence,
457
+ discoveredAt: v.discovered_at
458
+ };
459
+ });
258
460
  }
259
461
  // ── Events ──────────────────────────────
260
462
  insertEvent(sessionId, event, taskId) {
@@ -278,19 +480,22 @@ var CartographyDB = class {
278
480
  }
279
481
  getEvents(sessionId, since) {
280
482
  const rows = since ? this.db.prepare("SELECT * FROM activity_events WHERE session_id = ? AND timestamp > ? ORDER BY timestamp").all(sessionId, since) : this.db.prepare("SELECT * FROM activity_events WHERE session_id = ? ORDER BY timestamp").all(sessionId);
281
- return rows.map((r) => ({
282
- id: r["id"],
283
- sessionId: r["session_id"],
284
- taskId: r["task_id"],
285
- timestamp: r["timestamp"],
286
- eventType: r["event_type"],
287
- process: r["process"],
288
- pid: r["pid"],
289
- target: r["target"],
290
- targetType: r["target_type"],
291
- port: r["port"],
292
- durationMs: r["duration_ms"]
293
- }));
483
+ return rows.map((r) => {
484
+ const v = EventRowSchema.parse(r);
485
+ return {
486
+ id: v.id,
487
+ sessionId: v.session_id,
488
+ taskId: v.task_id ?? void 0,
489
+ timestamp: v.timestamp,
490
+ eventType: v.event_type,
491
+ process: v.process,
492
+ pid: v.pid,
493
+ target: v.target ?? void 0,
494
+ targetType: v.target_type ?? void 0,
495
+ port: v.port ?? void 0,
496
+ durationMs: v.duration_ms ?? void 0
497
+ };
498
+ });
294
499
  }
295
500
  // ── Tasks ───────────────────────────────
296
501
  startTask(sessionId, description) {
@@ -324,15 +529,16 @@ var CartographyDB = class {
324
529
  return rows.map((r) => this.mapTask(r));
325
530
  }
326
531
  mapTask(r) {
532
+ const v = TaskRowSchema.parse(r);
327
533
  return {
328
- id: r["id"],
329
- sessionId: r["session_id"],
330
- description: r["description"],
331
- startedAt: r["started_at"],
332
- completedAt: r["completed_at"],
333
- steps: r["steps"],
334
- involvedServices: r["involved_services"],
335
- status: r["status"]
534
+ id: v.id,
535
+ sessionId: v.session_id,
536
+ description: v.description ?? void 0,
537
+ startedAt: v.started_at,
538
+ completedAt: v.completed_at ?? void 0,
539
+ steps: v.steps,
540
+ involvedServices: v.involved_services,
541
+ status: v.status
336
542
  };
337
543
  }
338
544
  // ── Workflows ───────────────────────────
@@ -358,18 +564,21 @@ var CartographyDB = class {
358
564
  }
359
565
  getWorkflows(sessionId) {
360
566
  const rows = this.db.prepare("SELECT * FROM workflows WHERE session_id = ?").all(sessionId);
361
- return rows.map((r) => ({
362
- id: r["id"],
363
- sessionId: r["session_id"],
364
- name: r["name"],
365
- pattern: r["pattern"],
366
- taskIds: r["task_ids"],
367
- occurrences: r["occurrences"],
368
- firstSeen: r["first_seen"],
369
- lastSeen: r["last_seen"],
370
- avgDurationMs: r["avg_duration_ms"],
371
- involvedServices: r["involved_services"]
372
- }));
567
+ return rows.map((r) => {
568
+ const v = WorkflowRowSchema.parse(r);
569
+ return {
570
+ id: v.id,
571
+ sessionId: v.session_id,
572
+ name: v.name ?? void 0,
573
+ pattern: v.pattern,
574
+ taskIds: v.task_ids,
575
+ occurrences: v.occurrences,
576
+ firstSeen: v.first_seen,
577
+ lastSeen: v.last_seen,
578
+ avgDurationMs: v.avg_duration_ms ?? 0,
579
+ involvedServices: v.involved_services
580
+ };
581
+ });
373
582
  }
374
583
  // ── Connections (user-created hex map links) ─────────────────────────────
375
584
  upsertConnection(sessionId, conn) {
@@ -386,14 +595,17 @@ var CartographyDB = class {
386
595
  }
387
596
  getConnections(sessionId) {
388
597
  const rows = this.db.prepare("SELECT * FROM connections WHERE session_id = ?").all(sessionId);
389
- return rows.map((r) => ({
390
- id: r["id"],
391
- sessionId: r["session_id"],
392
- sourceAssetId: r["source_asset_id"],
393
- targetAssetId: r["target_asset_id"],
394
- type: r["type"] ?? void 0,
395
- createdAt: r["created_at"]
396
- }));
598
+ return rows.map((r) => {
599
+ const v = ConnectionRowSchema.parse(r);
600
+ return {
601
+ id: v.id,
602
+ sessionId: v.session_id,
603
+ sourceAssetId: v.source_asset_id,
604
+ targetAssetId: v.target_asset_id,
605
+ type: v.type ?? void 0,
606
+ createdAt: v.created_at
607
+ };
608
+ });
397
609
  }
398
610
  deleteConnection(sessionId, connectionId) {
399
611
  this.db.prepare("DELETE FROM connections WHERE session_id = ? AND id = ?").run(sessionId, connectionId);
@@ -408,6 +620,31 @@ var CartographyDB = class {
408
620
  const row = this.db.prepare("SELECT action FROM node_approvals WHERE pattern = ?").get(pattern);
409
621
  return row?.action;
410
622
  }
623
+ // ── Pruning ──────────────────────────────
624
+ /**
625
+ * Delete a session and all its associated data (nodes, edges, events, tasks, workflows, connections).
626
+ */
627
+ deleteSession(sessionId) {
628
+ this.db.prepare("DELETE FROM connections WHERE session_id = ?").run(sessionId);
629
+ this.db.prepare("DELETE FROM workflows WHERE session_id = ?").run(sessionId);
630
+ this.db.prepare("DELETE FROM activity_events WHERE session_id = ?").run(sessionId);
631
+ this.db.prepare("DELETE FROM tasks WHERE session_id = ?").run(sessionId);
632
+ this.db.prepare("DELETE FROM edges WHERE session_id = ?").run(sessionId);
633
+ this.db.prepare("DELETE FROM nodes WHERE session_id = ?").run(sessionId);
634
+ this.db.prepare("DELETE FROM sessions WHERE id = ?").run(sessionId);
635
+ }
636
+ /**
637
+ * Prune sessions older than the given ISO date string. Returns count of deleted sessions.
638
+ */
639
+ pruneSessions(olderThan) {
640
+ const rows = this.db.prepare(
641
+ "SELECT id FROM sessions WHERE started_at < ?"
642
+ ).all(olderThan);
643
+ for (const row of rows) {
644
+ this.deleteSession(row.id);
645
+ }
646
+ return rows.length;
647
+ }
411
648
  // ── Stats ───────────────────────────────
412
649
  getStats(sessionId) {
413
650
  const nodes = this.db.prepare("SELECT COUNT(*) as c FROM nodes WHERE session_id = ?").get(sessionId).c;
@@ -419,129 +656,11 @@ var CartographyDB = class {
419
656
  };
420
657
 
421
658
  // src/tools.ts
422
- import { z as z2 } from "zod";
423
-
424
- // src/types.ts
425
- import { z } from "zod";
426
- var NODE_TYPES = [
427
- "host",
428
- "database_server",
429
- "database",
430
- "table",
431
- "web_service",
432
- "api_endpoint",
433
- "cache_server",
434
- "message_broker",
435
- "queue",
436
- "topic",
437
- "container",
438
- "pod",
439
- "k8s_cluster",
440
- "config_file",
441
- "saas_tool",
442
- "unknown"
443
- ];
444
- var EDGE_RELATIONSHIPS = [
445
- "connects_to",
446
- "reads_from",
447
- "writes_to",
448
- "calls",
449
- "contains",
450
- "depends_on"
451
- ];
452
- var NodeSchema = z.object({
453
- id: z.string().describe('Format: "{type}:{host}:{port}" oder "{type}:{name}"'),
454
- type: z.enum(NODE_TYPES),
455
- name: z.string(),
456
- discoveredVia: z.string(),
457
- confidence: z.number().min(0).max(1).default(0.5),
458
- metadata: z.record(z.string(), z.unknown()).default({}),
459
- tags: z.array(z.string()).default([]),
460
- domain: z.string().optional().describe('Business domain, e.g. "Marketing", "Finance"'),
461
- subDomain: z.string().optional().describe('Sub-domain, e.g. "Forecast client orders"'),
462
- qualityScore: z.number().min(0).max(100).optional().describe("Data quality score 0\u2013100")
463
- });
464
- var EdgeSchema = z.object({
465
- sourceId: z.string(),
466
- targetId: z.string(),
467
- relationship: z.enum(EDGE_RELATIONSHIPS),
468
- evidence: z.string(),
469
- confidence: z.number().min(0).max(1).default(0.5)
470
- });
471
- var DataAssetSchema = z.object({
472
- id: z.string(),
473
- name: z.string(),
474
- domain: z.string(),
475
- subDomain: z.string().optional(),
476
- qualityScore: z.number().min(0).max(100).optional(),
477
- metadata: z.record(z.string(), z.unknown()).default({}),
478
- position: z.object({ q: z.number(), r: z.number() })
479
- });
480
- var ClusterSchema = z.object({
481
- id: z.string(),
482
- label: z.string(),
483
- domain: z.string(),
484
- color: z.string(),
485
- assetIds: z.array(z.string()),
486
- centroid: z.object({ x: z.number(), y: z.number() })
487
- });
488
- var ConnectionSchema = z.object({
489
- id: z.string(),
490
- sourceAssetId: z.string(),
491
- targetAssetId: z.string(),
492
- type: z.string().optional()
493
- });
494
- var DOMAIN_COLORS = {
495
- "Quality Control": "#1a2744",
496
- "Supply Chain": "#1e3a6e",
497
- "Marketing": "#6a7fb5",
498
- "Finance": "#3a8a8a",
499
- "HR": "#2a5a9a",
500
- "Logistics": "#0e7490",
501
- "Sales": "#1d4ed8",
502
- "Engineering": "#4338ca",
503
- "Operations": "#0891b2",
504
- "Data Layer": "#1e3352",
505
- "Web / API": "#1a3a1a",
506
- "Messaging": "#2a1a3a",
507
- "Infrastructure": "#0f2a40",
508
- "Other": "#374151"
509
- };
510
- var DOMAIN_PALETTE = [
511
- "#1a2e5a",
512
- "#1e3a8a",
513
- "#1d4ed8",
514
- "#2563eb",
515
- "#3b82f6",
516
- "#6366f1",
517
- "#818cf8",
518
- "#7c9fc3",
519
- "#0e7490",
520
- "#0891b2",
521
- "#06b6d4",
522
- "#22d3ee",
523
- "#0d9488",
524
- "#14b8a6",
525
- "#2dd4bf",
526
- "#5eead4"
527
- ];
528
- function defaultConfig(overrides = {}) {
529
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
530
- return {
531
- maxDepth: 8,
532
- maxTurns: 50,
533
- entryPoints: ["localhost"],
534
- agentModel: "claude-sonnet-4-5-20250929",
535
- outputDir: "./cartography-output",
536
- dbPath: `${home}/.cartography/cartography.db`,
537
- verbose: false,
538
- ...overrides
539
- };
540
- }
659
+ import { z as z3 } from "zod";
541
660
 
542
661
  // src/bookmarks.ts
543
662
  import { tmpdir } from "os";
544
- import { existsSync as existsSync2, readFileSync, readdirSync, copyFileSync, statSync } from "fs";
663
+ import { existsSync as existsSync2, readFileSync, readdirSync, copyFileSync, statSync, unlinkSync } from "fs";
545
664
  import { join as join2 } from "path";
546
665
 
547
666
  // src/platform.ts
@@ -568,13 +687,42 @@ function getShell() {
568
687
  if (!_shell) _shell = platformShell();
569
688
  return _shell;
570
689
  }
690
+ var SAFE_ENV_KEYS = [
691
+ "PATH",
692
+ "HOME",
693
+ "USER",
694
+ "LANG",
695
+ "LC_ALL",
696
+ "TERM",
697
+ "SHELL",
698
+ "USERPROFILE",
699
+ "LOCALAPPDATA",
700
+ "APPDATA",
701
+ "PROGRAMFILES",
702
+ "XDG_CONFIG_HOME",
703
+ "XDG_DATA_HOME",
704
+ "XDG_RUNTIME_DIR",
705
+ "AWS_DEFAULT_REGION",
706
+ "AWS_PROFILE",
707
+ "AWS_CONFIG_FILE",
708
+ "KUBECONFIG",
709
+ "GOOGLE_APPLICATION_CREDENTIALS",
710
+ "AZURE_CONFIG_DIR"
711
+ ];
712
+ function safeEnv() {
713
+ const env = {};
714
+ for (const key of SAFE_ENV_KEYS) {
715
+ if (process.env[key]) env[key] = process.env[key];
716
+ }
717
+ return env;
718
+ }
571
719
  function run(cmd, opts = {}) {
572
720
  try {
573
721
  return execSync(cmd, {
574
722
  stdio: "pipe",
575
723
  timeout: opts.timeout ?? 1e4,
576
724
  shell: getShell(),
577
- env: opts.env
725
+ env: opts.env ?? safeEnv()
578
726
  }).toString().trim();
579
727
  } catch {
580
728
  return "";
@@ -688,6 +836,23 @@ function scanWindowsDbServices() {
688
836
  }
689
837
 
690
838
  // src/bookmarks.ts
839
+ function cleanupTempFiles() {
840
+ let cleaned = 0;
841
+ const tmp = tmpdir();
842
+ try {
843
+ for (const f of readdirSync(tmp)) {
844
+ if (f.startsWith("cartograph_") && f.endsWith(".sqlite")) {
845
+ try {
846
+ unlinkSync(join2(tmp, f));
847
+ cleaned++;
848
+ } catch {
849
+ }
850
+ }
851
+ }
852
+ } catch {
853
+ }
854
+ return cleaned;
855
+ }
691
856
  function extractHost(rawUrl, source) {
692
857
  try {
693
858
  const u = new URL(rawUrl);
@@ -933,6 +1098,22 @@ async function scanAllHistory() {
933
1098
  }
934
1099
 
935
1100
  // src/tools.ts
1101
+ function createScanRunner(runFn, opts = {}) {
1102
+ const threshold = opts.threshold ?? 3;
1103
+ let consecutiveFailures = 0;
1104
+ let tripped = false;
1105
+ return (cmd) => {
1106
+ if (tripped) return "(skipped \u2014 circuit breaker: too many consecutive failures)";
1107
+ const result = runFn(cmd, { timeout: opts.timeout ?? 2e4, env: opts.env });
1108
+ if (!result) {
1109
+ consecutiveFailures++;
1110
+ if (consecutiveFailures >= threshold) tripped = true;
1111
+ return "(error or not available)";
1112
+ }
1113
+ consecutiveFailures = 0;
1114
+ return result;
1115
+ };
1116
+ }
936
1117
  function stripSensitive(target) {
937
1118
  try {
938
1119
  const url = new URL(target.startsWith("http") ? target : `tcp://${target}`);
@@ -945,16 +1126,16 @@ async function createCartographyTools(db, sessionId, opts = {}) {
945
1126
  const { tool, createSdkMcpServer } = await import("@anthropic-ai/claude-agent-sdk");
946
1127
  const tools = [
947
1128
  tool("save_node", "Save an infrastructure node to the catalog", {
948
- id: z2.string(),
949
- type: z2.enum(NODE_TYPES),
950
- name: z2.string(),
951
- discoveredVia: z2.string(),
952
- confidence: z2.number().min(0).max(1),
953
- metadata: z2.record(z2.string(), z2.unknown()).optional(),
954
- tags: z2.array(z2.string()).optional(),
955
- domain: z2.string().optional().describe('Business domain, e.g. "Marketing", "Finance"'),
956
- subDomain: z2.string().optional().describe('Sub-domain, e.g. "Forecast client orders"'),
957
- qualityScore: z2.number().min(0).max(100).optional().describe("Data quality score 0\u2013100")
1129
+ id: z3.string(),
1130
+ type: z3.enum(NODE_TYPES),
1131
+ name: z3.string(),
1132
+ discoveredVia: z3.string(),
1133
+ confidence: z3.number().min(0).max(1),
1134
+ metadata: z3.record(z3.string(), z3.unknown()).optional(),
1135
+ tags: z3.array(z3.string()).optional(),
1136
+ domain: z3.string().optional().describe('Business domain, e.g. "Marketing", "Finance"'),
1137
+ subDomain: z3.string().optional().describe('Sub-domain, e.g. "Forecast client orders"'),
1138
+ qualityScore: z3.number().min(0).max(100).optional().describe("Data quality score 0\u2013100")
958
1139
  }, async (args) => {
959
1140
  const node = {
960
1141
  id: stripSensitive(args["id"]),
@@ -972,11 +1153,11 @@ async function createCartographyTools(db, sessionId, opts = {}) {
972
1153
  return { content: [{ type: "text", text: `\u2713 Node: ${node.id}` }] };
973
1154
  }),
974
1155
  tool("save_edge", "Save a relationship (edge) between two nodes \u2014 ALWAYS save edges when connections are clear", {
975
- sourceId: z2.string(),
976
- targetId: z2.string(),
977
- relationship: z2.enum(EDGE_RELATIONSHIPS),
978
- evidence: z2.string(),
979
- confidence: z2.number().min(0).max(1)
1156
+ sourceId: z3.string(),
1157
+ targetId: z3.string(),
1158
+ relationship: z3.enum(EDGE_RELATIONSHIPS),
1159
+ evidence: z3.string(),
1160
+ confidence: z3.number().min(0).max(1)
980
1161
  }, async (args) => {
981
1162
  db.insertEdge(sessionId, {
982
1163
  sourceId: args["sourceId"],
@@ -988,7 +1169,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
988
1169
  return { content: [{ type: "text", text: `\u2713 ${args["sourceId"]}\u2192${args["targetId"]}` }] };
989
1170
  }),
990
1171
  tool("get_catalog", "Get the current catalog \u2014 use before save_node to avoid duplicates", {
991
- includeEdges: z2.boolean().default(true)
1172
+ includeEdges: z3.boolean().default(true)
992
1173
  }, async (args) => {
993
1174
  const nodes = db.getNodes(sessionId);
994
1175
  const edges = args["includeEdges"] ? db.getEdges(sessionId) : [];
@@ -1003,8 +1184,8 @@ async function createCartographyTools(db, sessionId, opts = {}) {
1003
1184
  };
1004
1185
  }),
1005
1186
  tool("ask_user", "Ask the user a question \u2014 for clarifications, missing context, or consent (e.g. before scanning browser history)", {
1006
- question: z2.string().describe("The question for the user (clear and specific)"),
1007
- context: z2.string().optional().describe("Optional context explaining why this is relevant")
1187
+ question: z3.string().describe("The question for the user (clear and specific)"),
1188
+ context: z3.string().optional().describe("Optional context explaining why this is relevant")
1008
1189
  }, async (args) => {
1009
1190
  const question = args["question"];
1010
1191
  const context = args["context"];
@@ -1017,7 +1198,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
1017
1198
  };
1018
1199
  }),
1019
1200
  tool("scan_bookmarks", "Scan all browser bookmarks \u2014 hostnames only, no personal data (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)", {
1020
- minConfidence: z2.number().min(0).max(1).default(0.5).optional()
1201
+ minConfidence: z3.number().min(0).max(1).default(0.5).optional()
1021
1202
  }, async () => {
1022
1203
  const hosts = await scanAllBookmarks();
1023
1204
  return {
@@ -1037,7 +1218,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
1037
1218
  };
1038
1219
  }),
1039
1220
  tool("scan_browser_history", "Scan browser history \u2014 anonymized hostnames + visit frequency. ALWAYS call ask_user for consent before using this tool.", {
1040
- minVisits: z2.number().min(1).default(3).optional().describe("Minimum visit count to include a host (filters rarely-visited sites)")
1221
+ minVisits: z3.number().min(1).default(3).optional().describe("Minimum visit count to include a host (filters rarely-visited sites)")
1041
1222
  }, async (args) => {
1042
1223
  const minVisits = args["minVisits"] ?? 3;
1043
1224
  const hosts = await scanAllHistory();
@@ -1059,7 +1240,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
1059
1240
  };
1060
1241
  }),
1061
1242
  tool("scan_local_databases", "Scan for local database files and running DB servers \u2014 PostgreSQL databases, MySQL, SQLite files from installed apps", {
1062
- deep: z2.boolean().default(false).optional().describe("Also search home directory recursively for SQLite/DB files (slower)")
1243
+ deep: z3.boolean().default(false).optional().describe("Also search home directory recursively for SQLite/DB files (slower)")
1063
1244
  }, async (args) => {
1064
1245
  const deep = args["deep"] ?? false;
1065
1246
  const results = {};
@@ -1131,14 +1312,11 @@ ${v}`).join("\n\n");
1131
1312
  return { content: [{ type: "text", text: out }] };
1132
1313
  }),
1133
1314
  tool("scan_k8s_resources", "Scan Kubernetes cluster via kubectl \u2014 100% readonly (get, describe)", {
1134
- namespace: z2.string().optional().describe("Filter by namespace \u2014 empty = all namespaces")
1315
+ namespace: z3.string().optional().describe("Filter by namespace \u2014 empty = all namespaces")
1135
1316
  }, async (args) => {
1136
1317
  const ns = args["namespace"];
1137
1318
  const nsFlag = ns ? `-n ${ns}` : "--all-namespaces";
1138
- const runK = (cmd) => {
1139
- const r = run(cmd, { timeout: 15e3 });
1140
- return r || `(error or not available)`;
1141
- };
1319
+ const runK = createScanRunner(run, { timeout: 15e3, threshold: 3 });
1142
1320
  const sections = IS_WIN ? [
1143
1321
  ["CONTEXT", "kubectl config current-context"],
1144
1322
  ["NODES", "kubectl get nodes -o wide"],
@@ -1165,15 +1343,15 @@ ${runK(c)}`).join("\n\n");
1165
1343
  return { content: [{ type: "text", text: out }] };
1166
1344
  }),
1167
1345
  tool("scan_aws_resources", "Scan AWS infrastructure via AWS CLI \u2014 100% readonly (describe, list)", {
1168
- region: z2.string().optional().describe("AWS Region \u2014 default: AWS_DEFAULT_REGION or profile"),
1169
- profile: z2.string().optional().describe("AWS CLI profile")
1346
+ region: z3.string().optional().describe("AWS Region \u2014 default: AWS_DEFAULT_REGION or profile"),
1347
+ profile: z3.string().optional().describe("AWS CLI profile")
1170
1348
  }, async (args) => {
1171
1349
  const region = args["region"];
1172
1350
  const profile = args["profile"];
1173
1351
  const env = { ...process.env };
1174
1352
  if (region) env["AWS_DEFAULT_REGION"] = region;
1175
1353
  const pf = profile ? `--profile ${profile}` : "";
1176
- const runAws = (cmd) => run(cmd, { timeout: 2e4, env }) || "(error or not available)";
1354
+ const runAws = createScanRunner(run, { timeout: 2e4, env, threshold: 3 });
1177
1355
  const sections = [
1178
1356
  ["IDENTITY", `aws sts get-caller-identity ${pf} --output json`],
1179
1357
  ["EC2", `aws ec2 describe-instances ${pf} --query "Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,PublicIpAddress,PrivateIpAddress]" --output table`],
@@ -1189,11 +1367,11 @@ ${runAws(c)}`).join("\n\n");
1189
1367
  return { content: [{ type: "text", text: out }] };
1190
1368
  }),
1191
1369
  tool("scan_gcp_resources", "Scan Google Cloud Platform via gcloud CLI \u2014 100% readonly (list, describe)", {
1192
- project: z2.string().optional().describe("GCP Project ID \u2014 default: current gcloud project")
1370
+ project: z3.string().optional().describe("GCP Project ID \u2014 default: current gcloud project")
1193
1371
  }, async (args) => {
1194
1372
  const project = args["project"];
1195
1373
  const pf = project ? `--project ${project}` : "";
1196
- const runGcp = (cmd) => run(cmd, { timeout: 2e4 }) || "(error or not available)";
1374
+ const runGcp = createScanRunner(run, { timeout: 2e4, threshold: 3 });
1197
1375
  const sections = [
1198
1376
  ["IDENTITY", `gcloud config list account --format="value(core.account)"`],
1199
1377
  ["COMPUTE_INSTANCES", `gcloud compute instances list ${pf}`],
@@ -1210,14 +1388,14 @@ ${runGcp(c)}`).join("\n\n");
1210
1388
  return { content: [{ type: "text", text: out }] };
1211
1389
  }),
1212
1390
  tool("scan_azure_resources", "Scan Azure infrastructure via az CLI \u2014 100% readonly (list, show)", {
1213
- subscription: z2.string().optional().describe("Azure Subscription ID"),
1214
- resourceGroup: z2.string().optional().describe("Filter by resource group")
1391
+ subscription: z3.string().optional().describe("Azure Subscription ID"),
1392
+ resourceGroup: z3.string().optional().describe("Filter by resource group")
1215
1393
  }, async (args) => {
1216
1394
  const sub = args["subscription"];
1217
1395
  const rg = args["resourceGroup"];
1218
1396
  const sf = sub ? `--subscription ${sub}` : "";
1219
1397
  const rf = rg ? `--resource-group ${rg}` : "";
1220
- const runAz = (cmd) => run(cmd, { timeout: 2e4 }) || "(error or not available)";
1398
+ const runAz = createScanRunner(run, { timeout: 2e4, threshold: 3 });
1221
1399
  const sections = [
1222
1400
  ["IDENTITY", `az account show --output json ${sf}`],
1223
1401
  ["VMS", `az vm list ${sf} ${rf} --output table`],
@@ -1234,7 +1412,7 @@ ${runAz(c)}`).join("\n\n");
1234
1412
  return { content: [{ type: "text", text: out }] };
1235
1413
  }),
1236
1414
  tool("scan_installed_apps", "Scan all installed apps and tools \u2014 IDEs, office, dev tools, business apps, databases", {
1237
- searchHint: z2.string().optional().describe('Optional search term to find specific tools (e.g. "hubspot windsurf cursor")')
1415
+ searchHint: z3.string().optional().describe('Optional search term to find specific tools (e.g. "hubspot windsurf cursor")')
1238
1416
  }, async (args) => {
1239
1417
  const hint = args["searchHint"];
1240
1418
  const results = {};
@@ -1558,68 +1736,81 @@ Then scan_local_databases() for database servers and SQLite files.
1558
1736
  Then systematically scan local services, then config files.
1559
1737
  Finally, map all edges (Step 8 \u2014 critical!) before finishing.
1560
1738
  Use ask_user when you need context from the user.`;
1739
+ const MAX_DISCOVERY_MS = 30 * 60 * 1e3;
1561
1740
  let turnCount = 0;
1562
- for await (const msg of query({
1563
- prompt: initialPrompt,
1564
- options: {
1565
- model: config.agentModel,
1566
- maxTurns: config.maxTurns,
1567
- systemPrompt,
1568
- mcpServers: { cartography: tools },
1569
- allowedTools: [
1570
- "Bash",
1571
- "mcp__cartograph__save_node",
1572
- "mcp__cartograph__save_edge",
1573
- "mcp__cartograph__get_catalog",
1574
- "mcp__cartograph__scan_bookmarks",
1575
- "mcp__cartograph__scan_browser_history",
1576
- "mcp__cartograph__scan_installed_apps",
1577
- "mcp__cartograph__scan_local_databases",
1578
- "mcp__cartograph__scan_k8s_resources",
1579
- "mcp__cartograph__scan_aws_resources",
1580
- "mcp__cartograph__scan_gcp_resources",
1581
- "mcp__cartograph__scan_azure_resources",
1582
- "mcp__cartograph__ask_user"
1583
- ],
1584
- hooks: {
1585
- PreToolUse: [{ matcher: "Bash", hooks: [safetyHook] }]
1586
- },
1587
- permissionMode: "bypassPermissions"
1588
- }
1589
- })) {
1590
- if (!onEvent) continue;
1591
- if (msg.type === "assistant") {
1592
- turnCount++;
1593
- onEvent({ kind: "turn", turn: turnCount });
1594
- for (const block of msg.message.content) {
1595
- if (block.type === "text") {
1596
- onEvent({ kind: "thinking", text: block.text });
1597
- }
1598
- if (block.type === "tool_use") {
1599
- onEvent({
1600
- kind: "tool_call",
1601
- tool: block.name,
1602
- input: block.input
1603
- });
1741
+ try {
1742
+ const startTime = Date.now();
1743
+ for await (const msg of query({
1744
+ prompt: initialPrompt,
1745
+ options: {
1746
+ model: config.agentModel,
1747
+ maxTurns: config.maxTurns,
1748
+ systemPrompt,
1749
+ mcpServers: { cartography: tools },
1750
+ allowedTools: [
1751
+ "Bash",
1752
+ "mcp__cartograph__save_node",
1753
+ "mcp__cartograph__save_edge",
1754
+ "mcp__cartograph__get_catalog",
1755
+ "mcp__cartograph__scan_bookmarks",
1756
+ "mcp__cartograph__scan_browser_history",
1757
+ "mcp__cartograph__scan_installed_apps",
1758
+ "mcp__cartograph__scan_local_databases",
1759
+ "mcp__cartograph__scan_k8s_resources",
1760
+ "mcp__cartograph__scan_aws_resources",
1761
+ "mcp__cartograph__scan_gcp_resources",
1762
+ "mcp__cartograph__scan_azure_resources",
1763
+ "mcp__cartograph__ask_user"
1764
+ ],
1765
+ hooks: {
1766
+ PreToolUse: [{ matcher: "Bash", hooks: [safetyHook] }]
1767
+ },
1768
+ permissionMode: "bypassPermissions"
1769
+ }
1770
+ })) {
1771
+ if (Date.now() - startTime > MAX_DISCOVERY_MS) {
1772
+ onEvent?.({ kind: "error", text: `Discovery timeout after ${MAX_DISCOVERY_MS / 6e4} minutes` });
1773
+ onEvent?.({ kind: "done" });
1774
+ return;
1775
+ }
1776
+ if (!onEvent) continue;
1777
+ if (msg.type === "assistant") {
1778
+ turnCount++;
1779
+ onEvent({ kind: "turn", turn: turnCount });
1780
+ for (const block of msg.message.content) {
1781
+ if (block.type === "text") {
1782
+ onEvent({ kind: "thinking", text: block.text });
1783
+ }
1784
+ if (block.type === "tool_use") {
1785
+ onEvent({
1786
+ kind: "tool_call",
1787
+ tool: block.name,
1788
+ input: block.input
1789
+ });
1790
+ }
1604
1791
  }
1605
1792
  }
1606
- }
1607
- if (msg.type === "user") {
1608
- const content = msg.message?.content;
1609
- if (Array.isArray(content)) {
1610
- for (const block of content) {
1611
- if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_result") {
1612
- const tb = block;
1613
- const text = typeof tb.content === "string" ? tb.content : "";
1614
- onEvent({ kind: "tool_result", tool: tb.tool_use_id ?? "", output: text });
1793
+ if (msg.type === "user") {
1794
+ const content = msg.message?.content;
1795
+ if (Array.isArray(content)) {
1796
+ for (const block of content) {
1797
+ if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_result") {
1798
+ const tb = block;
1799
+ const text = typeof tb.content === "string" ? tb.content : "";
1800
+ onEvent({ kind: "tool_result", tool: tb.tool_use_id ?? "", output: text });
1801
+ }
1615
1802
  }
1616
1803
  }
1617
1804
  }
1805
+ if (msg.type === "result") {
1806
+ onEvent({ kind: "done" });
1807
+ return;
1808
+ }
1618
1809
  }
1619
- if (msg.type === "result") {
1620
- onEvent({ kind: "done" });
1621
- return;
1622
- }
1810
+ } catch (err) {
1811
+ const message = err instanceof Error ? err.message : String(err);
1812
+ onEvent?.({ kind: "error", text: `Discovery error: ${message}` });
1813
+ throw err;
1623
1814
  }
1624
1815
  }
1625
1816
 
@@ -3238,11 +3429,40 @@ function checkPrerequisites() {
3238
3429
  process.stderr.write("\u2713 Eingeloggt via claude login (Subscription)\n");
3239
3430
  }
3240
3431
  }
3432
+
3433
+ // src/logger.ts
3434
+ var verboseMode = false;
3435
+ function setVerbose(v) {
3436
+ verboseMode = v;
3437
+ }
3438
+ function log(level, message, context) {
3439
+ if (level === "DEBUG" && !verboseMode) return;
3440
+ const entry = {
3441
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
3442
+ level,
3443
+ message,
3444
+ ...context && Object.keys(context).length > 0 ? { context } : {}
3445
+ };
3446
+ process.stderr.write(JSON.stringify(entry) + "\n");
3447
+ }
3448
+ function logDebug(message, context) {
3449
+ log("DEBUG", message, context);
3450
+ }
3451
+ function logInfo(message, context) {
3452
+ log("INFO", message, context);
3453
+ }
3454
+ function logWarn(message, context) {
3455
+ log("WARN", message, context);
3456
+ }
3457
+ function logError(message, context) {
3458
+ log("ERROR", message, context);
3459
+ }
3241
3460
  export {
3242
3461
  CartographyDB,
3243
3462
  assignColors,
3244
3463
  buildMapData,
3245
3464
  checkPrerequisites,
3465
+ cleanupTempFiles,
3246
3466
  computeCentroid,
3247
3467
  computeClusterBounds,
3248
3468
  createCartographyTools,
@@ -3264,10 +3484,16 @@ export {
3264
3484
  hexSpiral,
3265
3485
  hexToPixel,
3266
3486
  layoutClusters,
3487
+ log,
3488
+ logDebug,
3489
+ logError,
3490
+ logInfo,
3491
+ logWarn,
3267
3492
  nodesToAssets,
3268
3493
  pixelToHex,
3269
3494
  runDiscovery,
3270
3495
  safetyHook,
3496
+ setVerbose,
3271
3497
  shadeVariant,
3272
3498
  stripSensitive
3273
3499
  };