@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/README.md +3 -2
- package/dist/{bookmarks-72CDYAHD.js → bookmarks-BWNVQGPG.js} +4 -2
- package/dist/{chunk-3NVQ3ND6.js → chunk-QKNYI3SU.js} +50 -3
- package/dist/chunk-QKNYI3SU.js.map +1 -0
- package/dist/cli.js +418 -167
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +36 -1
- package/dist/index.js +507 -281
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
- package/dist/chunk-3NVQ3ND6.js.map +0 -1
- /package/dist/{bookmarks-72CDYAHD.js.map → bookmarks-BWNVQGPG.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
IS_MAC,
|
|
13
13
|
IS_WIN,
|
|
14
14
|
PLATFORM,
|
|
15
|
+
cleanupTempFiles,
|
|
15
16
|
commandExists,
|
|
16
17
|
dbScanDirs,
|
|
17
18
|
findFiles,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
scanAllHistory,
|
|
21
22
|
scanWindowsDbServices,
|
|
22
23
|
scanWindowsPrograms
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-QKNYI3SU.js";
|
|
24
25
|
|
|
25
26
|
// src/cli.ts
|
|
26
27
|
import { Command } from "commander";
|
|
@@ -66,6 +67,83 @@ function checkPrerequisites() {
|
|
|
66
67
|
import Database from "better-sqlite3";
|
|
67
68
|
import { mkdirSync } from "fs";
|
|
68
69
|
import { dirname } from "path";
|
|
70
|
+
import { z } from "zod";
|
|
71
|
+
var SessionRowSchema = z.object({
|
|
72
|
+
id: z.string(),
|
|
73
|
+
mode: z.literal("discover"),
|
|
74
|
+
started_at: z.string(),
|
|
75
|
+
completed_at: z.string().nullable().optional(),
|
|
76
|
+
config: z.string()
|
|
77
|
+
});
|
|
78
|
+
var NodeRowSchema = z.object({
|
|
79
|
+
id: z.string(),
|
|
80
|
+
session_id: z.string(),
|
|
81
|
+
type: z.enum(NODE_TYPES),
|
|
82
|
+
name: z.string(),
|
|
83
|
+
discovered_via: z.string().nullable().optional(),
|
|
84
|
+
discovered_at: z.string(),
|
|
85
|
+
path_id: z.string().nullable().optional(),
|
|
86
|
+
depth: z.number().default(0),
|
|
87
|
+
confidence: z.number().default(0.5),
|
|
88
|
+
metadata: z.string().default("{}"),
|
|
89
|
+
tags: z.string().default("[]"),
|
|
90
|
+
domain: z.string().nullable().optional(),
|
|
91
|
+
sub_domain: z.string().nullable().optional(),
|
|
92
|
+
quality_score: z.number().nullable().optional()
|
|
93
|
+
});
|
|
94
|
+
var EdgeRowSchema = z.object({
|
|
95
|
+
id: z.string(),
|
|
96
|
+
session_id: z.string(),
|
|
97
|
+
source_id: z.string(),
|
|
98
|
+
target_id: z.string(),
|
|
99
|
+
relationship: z.enum(EDGE_RELATIONSHIPS),
|
|
100
|
+
evidence: z.string().nullable().optional(),
|
|
101
|
+
confidence: z.number().default(0.5),
|
|
102
|
+
discovered_at: z.string()
|
|
103
|
+
});
|
|
104
|
+
var EventRowSchema = z.object({
|
|
105
|
+
id: z.string(),
|
|
106
|
+
session_id: z.string(),
|
|
107
|
+
task_id: z.string().nullable().optional(),
|
|
108
|
+
timestamp: z.string(),
|
|
109
|
+
event_type: z.string(),
|
|
110
|
+
process: z.string(),
|
|
111
|
+
pid: z.number(),
|
|
112
|
+
target: z.string().nullable().optional(),
|
|
113
|
+
target_type: z.string().nullable().optional(),
|
|
114
|
+
port: z.number().nullable().optional(),
|
|
115
|
+
duration_ms: z.number().nullable().optional()
|
|
116
|
+
});
|
|
117
|
+
var TaskRowSchema = z.object({
|
|
118
|
+
id: z.string(),
|
|
119
|
+
session_id: z.string(),
|
|
120
|
+
description: z.string().nullable().optional(),
|
|
121
|
+
started_at: z.string(),
|
|
122
|
+
completed_at: z.string().nullable().optional(),
|
|
123
|
+
steps: z.string().default("[]"),
|
|
124
|
+
involved_services: z.string().default("[]"),
|
|
125
|
+
status: z.enum(["active", "completed", "cancelled"])
|
|
126
|
+
});
|
|
127
|
+
var WorkflowRowSchema = z.object({
|
|
128
|
+
id: z.string(),
|
|
129
|
+
session_id: z.string(),
|
|
130
|
+
name: z.string().nullable().optional(),
|
|
131
|
+
pattern: z.string(),
|
|
132
|
+
task_ids: z.string().default("[]"),
|
|
133
|
+
occurrences: z.number().default(1),
|
|
134
|
+
first_seen: z.string(),
|
|
135
|
+
last_seen: z.string(),
|
|
136
|
+
avg_duration_ms: z.number().nullable().optional(),
|
|
137
|
+
involved_services: z.string().default("[]")
|
|
138
|
+
});
|
|
139
|
+
var ConnectionRowSchema = z.object({
|
|
140
|
+
id: z.string(),
|
|
141
|
+
session_id: z.string(),
|
|
142
|
+
source_asset_id: z.string(),
|
|
143
|
+
target_asset_id: z.string(),
|
|
144
|
+
type: z.string().nullable().optional(),
|
|
145
|
+
created_at: z.string()
|
|
146
|
+
});
|
|
69
147
|
var SCHEMA = `
|
|
70
148
|
PRAGMA journal_mode = WAL;
|
|
71
149
|
PRAGMA foreign_keys = ON;
|
|
@@ -230,12 +308,13 @@ var CartographyDB = class {
|
|
|
230
308
|
return rows.map((r) => this.mapSession(r));
|
|
231
309
|
}
|
|
232
310
|
mapSession(r) {
|
|
311
|
+
const v = SessionRowSchema.parse(r);
|
|
233
312
|
return {
|
|
234
|
-
id:
|
|
235
|
-
mode:
|
|
236
|
-
startedAt:
|
|
237
|
-
completedAt:
|
|
238
|
-
config:
|
|
313
|
+
id: v.id,
|
|
314
|
+
mode: v.mode,
|
|
315
|
+
startedAt: v.started_at,
|
|
316
|
+
completedAt: v.completed_at ?? void 0,
|
|
317
|
+
config: v.config
|
|
239
318
|
};
|
|
240
319
|
}
|
|
241
320
|
// ── Nodes ───────────────────────────────
|
|
@@ -266,21 +345,22 @@ var CartographyDB = class {
|
|
|
266
345
|
return rows.map((r) => this.mapNode(r));
|
|
267
346
|
}
|
|
268
347
|
mapNode(r) {
|
|
348
|
+
const v = NodeRowSchema.parse(r);
|
|
269
349
|
return {
|
|
270
|
-
id:
|
|
271
|
-
sessionId:
|
|
272
|
-
type:
|
|
273
|
-
name:
|
|
274
|
-
discoveredVia:
|
|
275
|
-
discoveredAt:
|
|
276
|
-
depth:
|
|
277
|
-
confidence:
|
|
278
|
-
metadata: JSON.parse(
|
|
279
|
-
tags: JSON.parse(
|
|
280
|
-
pathId:
|
|
281
|
-
domain:
|
|
282
|
-
subDomain:
|
|
283
|
-
qualityScore:
|
|
350
|
+
id: v.id,
|
|
351
|
+
sessionId: v.session_id,
|
|
352
|
+
type: v.type,
|
|
353
|
+
name: v.name,
|
|
354
|
+
discoveredVia: v.discovered_via ?? "",
|
|
355
|
+
discoveredAt: v.discovered_at,
|
|
356
|
+
depth: v.depth,
|
|
357
|
+
confidence: v.confidence,
|
|
358
|
+
metadata: JSON.parse(v.metadata),
|
|
359
|
+
tags: JSON.parse(v.tags),
|
|
360
|
+
pathId: v.path_id ?? void 0,
|
|
361
|
+
domain: v.domain ?? void 0,
|
|
362
|
+
subDomain: v.sub_domain ?? void 0,
|
|
363
|
+
qualityScore: v.quality_score ?? void 0
|
|
284
364
|
};
|
|
285
365
|
}
|
|
286
366
|
deleteNode(sessionId, nodeId) {
|
|
@@ -309,16 +389,19 @@ var CartographyDB = class {
|
|
|
309
389
|
}
|
|
310
390
|
getEdges(sessionId) {
|
|
311
391
|
const rows = this.db.prepare("SELECT * FROM edges WHERE session_id = ?").all(sessionId);
|
|
312
|
-
return rows.map((r) =>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
392
|
+
return rows.map((r) => {
|
|
393
|
+
const v = EdgeRowSchema.parse(r);
|
|
394
|
+
return {
|
|
395
|
+
id: v.id,
|
|
396
|
+
sessionId: v.session_id,
|
|
397
|
+
sourceId: v.source_id,
|
|
398
|
+
targetId: v.target_id,
|
|
399
|
+
relationship: v.relationship,
|
|
400
|
+
evidence: v.evidence ?? "",
|
|
401
|
+
confidence: v.confidence,
|
|
402
|
+
discoveredAt: v.discovered_at
|
|
403
|
+
};
|
|
404
|
+
});
|
|
322
405
|
}
|
|
323
406
|
// ── Events ──────────────────────────────
|
|
324
407
|
insertEvent(sessionId, event, taskId) {
|
|
@@ -342,19 +425,22 @@ var CartographyDB = class {
|
|
|
342
425
|
}
|
|
343
426
|
getEvents(sessionId, since) {
|
|
344
427
|
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);
|
|
345
|
-
return rows.map((r) =>
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
428
|
+
return rows.map((r) => {
|
|
429
|
+
const v = EventRowSchema.parse(r);
|
|
430
|
+
return {
|
|
431
|
+
id: v.id,
|
|
432
|
+
sessionId: v.session_id,
|
|
433
|
+
taskId: v.task_id ?? void 0,
|
|
434
|
+
timestamp: v.timestamp,
|
|
435
|
+
eventType: v.event_type,
|
|
436
|
+
process: v.process,
|
|
437
|
+
pid: v.pid,
|
|
438
|
+
target: v.target ?? void 0,
|
|
439
|
+
targetType: v.target_type ?? void 0,
|
|
440
|
+
port: v.port ?? void 0,
|
|
441
|
+
durationMs: v.duration_ms ?? void 0
|
|
442
|
+
};
|
|
443
|
+
});
|
|
358
444
|
}
|
|
359
445
|
// ── Tasks ───────────────────────────────
|
|
360
446
|
startTask(sessionId, description) {
|
|
@@ -388,15 +474,16 @@ var CartographyDB = class {
|
|
|
388
474
|
return rows.map((r) => this.mapTask(r));
|
|
389
475
|
}
|
|
390
476
|
mapTask(r) {
|
|
477
|
+
const v = TaskRowSchema.parse(r);
|
|
391
478
|
return {
|
|
392
|
-
id:
|
|
393
|
-
sessionId:
|
|
394
|
-
description:
|
|
395
|
-
startedAt:
|
|
396
|
-
completedAt:
|
|
397
|
-
steps:
|
|
398
|
-
involvedServices:
|
|
399
|
-
status:
|
|
479
|
+
id: v.id,
|
|
480
|
+
sessionId: v.session_id,
|
|
481
|
+
description: v.description ?? void 0,
|
|
482
|
+
startedAt: v.started_at,
|
|
483
|
+
completedAt: v.completed_at ?? void 0,
|
|
484
|
+
steps: v.steps,
|
|
485
|
+
involvedServices: v.involved_services,
|
|
486
|
+
status: v.status
|
|
400
487
|
};
|
|
401
488
|
}
|
|
402
489
|
// ── Workflows ───────────────────────────
|
|
@@ -422,18 +509,21 @@ var CartographyDB = class {
|
|
|
422
509
|
}
|
|
423
510
|
getWorkflows(sessionId) {
|
|
424
511
|
const rows = this.db.prepare("SELECT * FROM workflows WHERE session_id = ?").all(sessionId);
|
|
425
|
-
return rows.map((r) =>
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
512
|
+
return rows.map((r) => {
|
|
513
|
+
const v = WorkflowRowSchema.parse(r);
|
|
514
|
+
return {
|
|
515
|
+
id: v.id,
|
|
516
|
+
sessionId: v.session_id,
|
|
517
|
+
name: v.name ?? void 0,
|
|
518
|
+
pattern: v.pattern,
|
|
519
|
+
taskIds: v.task_ids,
|
|
520
|
+
occurrences: v.occurrences,
|
|
521
|
+
firstSeen: v.first_seen,
|
|
522
|
+
lastSeen: v.last_seen,
|
|
523
|
+
avgDurationMs: v.avg_duration_ms ?? 0,
|
|
524
|
+
involvedServices: v.involved_services
|
|
525
|
+
};
|
|
526
|
+
});
|
|
437
527
|
}
|
|
438
528
|
// ── Connections (user-created hex map links) ─────────────────────────────
|
|
439
529
|
upsertConnection(sessionId, conn) {
|
|
@@ -450,14 +540,17 @@ var CartographyDB = class {
|
|
|
450
540
|
}
|
|
451
541
|
getConnections(sessionId) {
|
|
452
542
|
const rows = this.db.prepare("SELECT * FROM connections WHERE session_id = ?").all(sessionId);
|
|
453
|
-
return rows.map((r) =>
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
543
|
+
return rows.map((r) => {
|
|
544
|
+
const v = ConnectionRowSchema.parse(r);
|
|
545
|
+
return {
|
|
546
|
+
id: v.id,
|
|
547
|
+
sessionId: v.session_id,
|
|
548
|
+
sourceAssetId: v.source_asset_id,
|
|
549
|
+
targetAssetId: v.target_asset_id,
|
|
550
|
+
type: v.type ?? void 0,
|
|
551
|
+
createdAt: v.created_at
|
|
552
|
+
};
|
|
553
|
+
});
|
|
461
554
|
}
|
|
462
555
|
deleteConnection(sessionId, connectionId) {
|
|
463
556
|
this.db.prepare("DELETE FROM connections WHERE session_id = ? AND id = ?").run(sessionId, connectionId);
|
|
@@ -472,6 +565,31 @@ var CartographyDB = class {
|
|
|
472
565
|
const row = this.db.prepare("SELECT action FROM node_approvals WHERE pattern = ?").get(pattern);
|
|
473
566
|
return row?.action;
|
|
474
567
|
}
|
|
568
|
+
// ── Pruning ──────────────────────────────
|
|
569
|
+
/**
|
|
570
|
+
* Delete a session and all its associated data (nodes, edges, events, tasks, workflows, connections).
|
|
571
|
+
*/
|
|
572
|
+
deleteSession(sessionId) {
|
|
573
|
+
this.db.prepare("DELETE FROM connections WHERE session_id = ?").run(sessionId);
|
|
574
|
+
this.db.prepare("DELETE FROM workflows WHERE session_id = ?").run(sessionId);
|
|
575
|
+
this.db.prepare("DELETE FROM activity_events WHERE session_id = ?").run(sessionId);
|
|
576
|
+
this.db.prepare("DELETE FROM tasks WHERE session_id = ?").run(sessionId);
|
|
577
|
+
this.db.prepare("DELETE FROM edges WHERE session_id = ?").run(sessionId);
|
|
578
|
+
this.db.prepare("DELETE FROM nodes WHERE session_id = ?").run(sessionId);
|
|
579
|
+
this.db.prepare("DELETE FROM sessions WHERE id = ?").run(sessionId);
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Prune sessions older than the given ISO date string. Returns count of deleted sessions.
|
|
583
|
+
*/
|
|
584
|
+
pruneSessions(olderThan) {
|
|
585
|
+
const rows = this.db.prepare(
|
|
586
|
+
"SELECT id FROM sessions WHERE started_at < ?"
|
|
587
|
+
).all(olderThan);
|
|
588
|
+
for (const row of rows) {
|
|
589
|
+
this.deleteSession(row.id);
|
|
590
|
+
}
|
|
591
|
+
return rows.length;
|
|
592
|
+
}
|
|
475
593
|
// ── Stats ───────────────────────────────
|
|
476
594
|
getStats(sessionId) {
|
|
477
595
|
const nodes = this.db.prepare("SELECT COUNT(*) as c FROM nodes WHERE session_id = ?").get(sessionId).c;
|
|
@@ -483,7 +601,23 @@ var CartographyDB = class {
|
|
|
483
601
|
};
|
|
484
602
|
|
|
485
603
|
// src/tools.ts
|
|
486
|
-
import { z } from "zod";
|
|
604
|
+
import { z as z2 } from "zod";
|
|
605
|
+
function createScanRunner(runFn, opts = {}) {
|
|
606
|
+
const threshold = opts.threshold ?? 3;
|
|
607
|
+
let consecutiveFailures = 0;
|
|
608
|
+
let tripped = false;
|
|
609
|
+
return (cmd) => {
|
|
610
|
+
if (tripped) return "(skipped \u2014 circuit breaker: too many consecutive failures)";
|
|
611
|
+
const result = runFn(cmd, { timeout: opts.timeout ?? 2e4, env: opts.env });
|
|
612
|
+
if (!result) {
|
|
613
|
+
consecutiveFailures++;
|
|
614
|
+
if (consecutiveFailures >= threshold) tripped = true;
|
|
615
|
+
return "(error or not available)";
|
|
616
|
+
}
|
|
617
|
+
consecutiveFailures = 0;
|
|
618
|
+
return result;
|
|
619
|
+
};
|
|
620
|
+
}
|
|
487
621
|
function stripSensitive(target) {
|
|
488
622
|
try {
|
|
489
623
|
const url = new URL(target.startsWith("http") ? target : `tcp://${target}`);
|
|
@@ -496,16 +630,16 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
496
630
|
const { tool, createSdkMcpServer } = await import("@anthropic-ai/claude-agent-sdk");
|
|
497
631
|
const tools = [
|
|
498
632
|
tool("save_node", "Save an infrastructure node to the catalog", {
|
|
499
|
-
id:
|
|
500
|
-
type:
|
|
501
|
-
name:
|
|
502
|
-
discoveredVia:
|
|
503
|
-
confidence:
|
|
504
|
-
metadata:
|
|
505
|
-
tags:
|
|
506
|
-
domain:
|
|
507
|
-
subDomain:
|
|
508
|
-
qualityScore:
|
|
633
|
+
id: z2.string(),
|
|
634
|
+
type: z2.enum(NODE_TYPES),
|
|
635
|
+
name: z2.string(),
|
|
636
|
+
discoveredVia: z2.string(),
|
|
637
|
+
confidence: z2.number().min(0).max(1),
|
|
638
|
+
metadata: z2.record(z2.string(), z2.unknown()).optional(),
|
|
639
|
+
tags: z2.array(z2.string()).optional(),
|
|
640
|
+
domain: z2.string().optional().describe('Business domain, e.g. "Marketing", "Finance"'),
|
|
641
|
+
subDomain: z2.string().optional().describe('Sub-domain, e.g. "Forecast client orders"'),
|
|
642
|
+
qualityScore: z2.number().min(0).max(100).optional().describe("Data quality score 0\u2013100")
|
|
509
643
|
}, async (args) => {
|
|
510
644
|
const node = {
|
|
511
645
|
id: stripSensitive(args["id"]),
|
|
@@ -523,11 +657,11 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
523
657
|
return { content: [{ type: "text", text: `\u2713 Node: ${node.id}` }] };
|
|
524
658
|
}),
|
|
525
659
|
tool("save_edge", "Save a relationship (edge) between two nodes \u2014 ALWAYS save edges when connections are clear", {
|
|
526
|
-
sourceId:
|
|
527
|
-
targetId:
|
|
528
|
-
relationship:
|
|
529
|
-
evidence:
|
|
530
|
-
confidence:
|
|
660
|
+
sourceId: z2.string(),
|
|
661
|
+
targetId: z2.string(),
|
|
662
|
+
relationship: z2.enum(EDGE_RELATIONSHIPS),
|
|
663
|
+
evidence: z2.string(),
|
|
664
|
+
confidence: z2.number().min(0).max(1)
|
|
531
665
|
}, async (args) => {
|
|
532
666
|
db.insertEdge(sessionId, {
|
|
533
667
|
sourceId: args["sourceId"],
|
|
@@ -539,7 +673,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
539
673
|
return { content: [{ type: "text", text: `\u2713 ${args["sourceId"]}\u2192${args["targetId"]}` }] };
|
|
540
674
|
}),
|
|
541
675
|
tool("get_catalog", "Get the current catalog \u2014 use before save_node to avoid duplicates", {
|
|
542
|
-
includeEdges:
|
|
676
|
+
includeEdges: z2.boolean().default(true)
|
|
543
677
|
}, async (args) => {
|
|
544
678
|
const nodes = db.getNodes(sessionId);
|
|
545
679
|
const edges = args["includeEdges"] ? db.getEdges(sessionId) : [];
|
|
@@ -554,8 +688,8 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
554
688
|
};
|
|
555
689
|
}),
|
|
556
690
|
tool("ask_user", "Ask the user a question \u2014 for clarifications, missing context, or consent (e.g. before scanning browser history)", {
|
|
557
|
-
question:
|
|
558
|
-
context:
|
|
691
|
+
question: z2.string().describe("The question for the user (clear and specific)"),
|
|
692
|
+
context: z2.string().optional().describe("Optional context explaining why this is relevant")
|
|
559
693
|
}, async (args) => {
|
|
560
694
|
const question = args["question"];
|
|
561
695
|
const context = args["context"];
|
|
@@ -568,7 +702,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
568
702
|
};
|
|
569
703
|
}),
|
|
570
704
|
tool("scan_bookmarks", "Scan all browser bookmarks \u2014 hostnames only, no personal data (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)", {
|
|
571
|
-
minConfidence:
|
|
705
|
+
minConfidence: z2.number().min(0).max(1).default(0.5).optional()
|
|
572
706
|
}, async () => {
|
|
573
707
|
const hosts = await scanAllBookmarks();
|
|
574
708
|
return {
|
|
@@ -588,7 +722,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
588
722
|
};
|
|
589
723
|
}),
|
|
590
724
|
tool("scan_browser_history", "Scan browser history \u2014 anonymized hostnames + visit frequency. ALWAYS call ask_user for consent before using this tool.", {
|
|
591
|
-
minVisits:
|
|
725
|
+
minVisits: z2.number().min(1).default(3).optional().describe("Minimum visit count to include a host (filters rarely-visited sites)")
|
|
592
726
|
}, async (args) => {
|
|
593
727
|
const minVisits = args["minVisits"] ?? 3;
|
|
594
728
|
const hosts = await scanAllHistory();
|
|
@@ -610,7 +744,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
610
744
|
};
|
|
611
745
|
}),
|
|
612
746
|
tool("scan_local_databases", "Scan for local database files and running DB servers \u2014 PostgreSQL databases, MySQL, SQLite files from installed apps", {
|
|
613
|
-
deep:
|
|
747
|
+
deep: z2.boolean().default(false).optional().describe("Also search home directory recursively for SQLite/DB files (slower)")
|
|
614
748
|
}, async (args) => {
|
|
615
749
|
const deep = args["deep"] ?? false;
|
|
616
750
|
const results = {};
|
|
@@ -682,14 +816,11 @@ ${v}`).join("\n\n");
|
|
|
682
816
|
return { content: [{ type: "text", text: out }] };
|
|
683
817
|
}),
|
|
684
818
|
tool("scan_k8s_resources", "Scan Kubernetes cluster via kubectl \u2014 100% readonly (get, describe)", {
|
|
685
|
-
namespace:
|
|
819
|
+
namespace: z2.string().optional().describe("Filter by namespace \u2014 empty = all namespaces")
|
|
686
820
|
}, async (args) => {
|
|
687
821
|
const ns = args["namespace"];
|
|
688
822
|
const nsFlag = ns ? `-n ${ns}` : "--all-namespaces";
|
|
689
|
-
const runK = (
|
|
690
|
-
const r = run(cmd, { timeout: 15e3 });
|
|
691
|
-
return r || `(error or not available)`;
|
|
692
|
-
};
|
|
823
|
+
const runK = createScanRunner(run, { timeout: 15e3, threshold: 3 });
|
|
693
824
|
const sections = IS_WIN ? [
|
|
694
825
|
["CONTEXT", "kubectl config current-context"],
|
|
695
826
|
["NODES", "kubectl get nodes -o wide"],
|
|
@@ -716,15 +847,15 @@ ${runK(c)}`).join("\n\n");
|
|
|
716
847
|
return { content: [{ type: "text", text: out }] };
|
|
717
848
|
}),
|
|
718
849
|
tool("scan_aws_resources", "Scan AWS infrastructure via AWS CLI \u2014 100% readonly (describe, list)", {
|
|
719
|
-
region:
|
|
720
|
-
profile:
|
|
850
|
+
region: z2.string().optional().describe("AWS Region \u2014 default: AWS_DEFAULT_REGION or profile"),
|
|
851
|
+
profile: z2.string().optional().describe("AWS CLI profile")
|
|
721
852
|
}, async (args) => {
|
|
722
853
|
const region = args["region"];
|
|
723
854
|
const profile = args["profile"];
|
|
724
855
|
const env = { ...process.env };
|
|
725
856
|
if (region) env["AWS_DEFAULT_REGION"] = region;
|
|
726
857
|
const pf = profile ? `--profile ${profile}` : "";
|
|
727
|
-
const runAws = (
|
|
858
|
+
const runAws = createScanRunner(run, { timeout: 2e4, env, threshold: 3 });
|
|
728
859
|
const sections = [
|
|
729
860
|
["IDENTITY", `aws sts get-caller-identity ${pf} --output json`],
|
|
730
861
|
["EC2", `aws ec2 describe-instances ${pf} --query "Reservations[*].Instances[*].[InstanceId,InstanceType,State.Name,PublicIpAddress,PrivateIpAddress]" --output table`],
|
|
@@ -740,11 +871,11 @@ ${runAws(c)}`).join("\n\n");
|
|
|
740
871
|
return { content: [{ type: "text", text: out }] };
|
|
741
872
|
}),
|
|
742
873
|
tool("scan_gcp_resources", "Scan Google Cloud Platform via gcloud CLI \u2014 100% readonly (list, describe)", {
|
|
743
|
-
project:
|
|
874
|
+
project: z2.string().optional().describe("GCP Project ID \u2014 default: current gcloud project")
|
|
744
875
|
}, async (args) => {
|
|
745
876
|
const project = args["project"];
|
|
746
877
|
const pf = project ? `--project ${project}` : "";
|
|
747
|
-
const runGcp = (
|
|
878
|
+
const runGcp = createScanRunner(run, { timeout: 2e4, threshold: 3 });
|
|
748
879
|
const sections = [
|
|
749
880
|
["IDENTITY", `gcloud config list account --format="value(core.account)"`],
|
|
750
881
|
["COMPUTE_INSTANCES", `gcloud compute instances list ${pf}`],
|
|
@@ -761,14 +892,14 @@ ${runGcp(c)}`).join("\n\n");
|
|
|
761
892
|
return { content: [{ type: "text", text: out }] };
|
|
762
893
|
}),
|
|
763
894
|
tool("scan_azure_resources", "Scan Azure infrastructure via az CLI \u2014 100% readonly (list, show)", {
|
|
764
|
-
subscription:
|
|
765
|
-
resourceGroup:
|
|
895
|
+
subscription: z2.string().optional().describe("Azure Subscription ID"),
|
|
896
|
+
resourceGroup: z2.string().optional().describe("Filter by resource group")
|
|
766
897
|
}, async (args) => {
|
|
767
898
|
const sub = args["subscription"];
|
|
768
899
|
const rg = args["resourceGroup"];
|
|
769
900
|
const sf = sub ? `--subscription ${sub}` : "";
|
|
770
901
|
const rf = rg ? `--resource-group ${rg}` : "";
|
|
771
|
-
const runAz = (
|
|
902
|
+
const runAz = createScanRunner(run, { timeout: 2e4, threshold: 3 });
|
|
772
903
|
const sections = [
|
|
773
904
|
["IDENTITY", `az account show --output json ${sf}`],
|
|
774
905
|
["VMS", `az vm list ${sf} ${rf} --output table`],
|
|
@@ -785,7 +916,7 @@ ${runAz(c)}`).join("\n\n");
|
|
|
785
916
|
return { content: [{ type: "text", text: out }] };
|
|
786
917
|
}),
|
|
787
918
|
tool("scan_installed_apps", "Scan all installed apps and tools \u2014 IDEs, office, dev tools, business apps, databases", {
|
|
788
|
-
searchHint:
|
|
919
|
+
searchHint: z2.string().optional().describe('Optional search term to find specific tools (e.g. "hubspot windsurf cursor")')
|
|
789
920
|
}, async (args) => {
|
|
790
921
|
const hint = args["searchHint"];
|
|
791
922
|
const results = {};
|
|
@@ -1109,68 +1240,81 @@ Then scan_local_databases() for database servers and SQLite files.
|
|
|
1109
1240
|
Then systematically scan local services, then config files.
|
|
1110
1241
|
Finally, map all edges (Step 8 \u2014 critical!) before finishing.
|
|
1111
1242
|
Use ask_user when you need context from the user.`;
|
|
1243
|
+
const MAX_DISCOVERY_MS = 30 * 60 * 1e3;
|
|
1112
1244
|
let turnCount = 0;
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1245
|
+
try {
|
|
1246
|
+
const startTime = Date.now();
|
|
1247
|
+
for await (const msg of query({
|
|
1248
|
+
prompt: initialPrompt,
|
|
1249
|
+
options: {
|
|
1250
|
+
model: config.agentModel,
|
|
1251
|
+
maxTurns: config.maxTurns,
|
|
1252
|
+
systemPrompt,
|
|
1253
|
+
mcpServers: { cartography: tools },
|
|
1254
|
+
allowedTools: [
|
|
1255
|
+
"Bash",
|
|
1256
|
+
"mcp__cartograph__save_node",
|
|
1257
|
+
"mcp__cartograph__save_edge",
|
|
1258
|
+
"mcp__cartograph__get_catalog",
|
|
1259
|
+
"mcp__cartograph__scan_bookmarks",
|
|
1260
|
+
"mcp__cartograph__scan_browser_history",
|
|
1261
|
+
"mcp__cartograph__scan_installed_apps",
|
|
1262
|
+
"mcp__cartograph__scan_local_databases",
|
|
1263
|
+
"mcp__cartograph__scan_k8s_resources",
|
|
1264
|
+
"mcp__cartograph__scan_aws_resources",
|
|
1265
|
+
"mcp__cartograph__scan_gcp_resources",
|
|
1266
|
+
"mcp__cartograph__scan_azure_resources",
|
|
1267
|
+
"mcp__cartograph__ask_user"
|
|
1268
|
+
],
|
|
1269
|
+
hooks: {
|
|
1270
|
+
PreToolUse: [{ matcher: "Bash", hooks: [safetyHook] }]
|
|
1271
|
+
},
|
|
1272
|
+
permissionMode: "bypassPermissions"
|
|
1273
|
+
}
|
|
1274
|
+
})) {
|
|
1275
|
+
if (Date.now() - startTime > MAX_DISCOVERY_MS) {
|
|
1276
|
+
onEvent?.({ kind: "error", text: `Discovery timeout after ${MAX_DISCOVERY_MS / 6e4} minutes` });
|
|
1277
|
+
onEvent?.({ kind: "done" });
|
|
1278
|
+
return;
|
|
1279
|
+
}
|
|
1280
|
+
if (!onEvent) continue;
|
|
1281
|
+
if (msg.type === "assistant") {
|
|
1282
|
+
turnCount++;
|
|
1283
|
+
onEvent({ kind: "turn", turn: turnCount });
|
|
1284
|
+
for (const block of msg.message.content) {
|
|
1285
|
+
if (block.type === "text") {
|
|
1286
|
+
onEvent({ kind: "thinking", text: block.text });
|
|
1287
|
+
}
|
|
1288
|
+
if (block.type === "tool_use") {
|
|
1289
|
+
onEvent({
|
|
1290
|
+
kind: "tool_call",
|
|
1291
|
+
tool: block.name,
|
|
1292
|
+
input: block.input
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1155
1295
|
}
|
|
1156
1296
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1297
|
+
if (msg.type === "user") {
|
|
1298
|
+
const content = msg.message?.content;
|
|
1299
|
+
if (Array.isArray(content)) {
|
|
1300
|
+
for (const block of content) {
|
|
1301
|
+
if (typeof block === "object" && block !== null && "type" in block && block.type === "tool_result") {
|
|
1302
|
+
const tb = block;
|
|
1303
|
+
const text = typeof tb.content === "string" ? tb.content : "";
|
|
1304
|
+
onEvent({ kind: "tool_result", tool: tb.tool_use_id ?? "", output: text });
|
|
1305
|
+
}
|
|
1166
1306
|
}
|
|
1167
1307
|
}
|
|
1168
1308
|
}
|
|
1309
|
+
if (msg.type === "result") {
|
|
1310
|
+
onEvent({ kind: "done" });
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1169
1313
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1314
|
+
} catch (err) {
|
|
1315
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1316
|
+
onEvent?.({ kind: "error", text: `Discovery error: ${message}` });
|
|
1317
|
+
throw err;
|
|
1174
1318
|
}
|
|
1175
1319
|
}
|
|
1176
1320
|
|
|
@@ -2704,6 +2848,33 @@ function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml
|
|
|
2704
2848
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
2705
2849
|
import { resolve } from "path";
|
|
2706
2850
|
import { createInterface } from "readline";
|
|
2851
|
+
|
|
2852
|
+
// src/logger.ts
|
|
2853
|
+
var verboseMode = false;
|
|
2854
|
+
function setVerbose(v) {
|
|
2855
|
+
verboseMode = v;
|
|
2856
|
+
}
|
|
2857
|
+
function log(level, message, context) {
|
|
2858
|
+
if (level === "DEBUG" && !verboseMode) return;
|
|
2859
|
+
const entry = {
|
|
2860
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2861
|
+
level,
|
|
2862
|
+
message,
|
|
2863
|
+
...context && Object.keys(context).length > 0 ? { context } : {}
|
|
2864
|
+
};
|
|
2865
|
+
process.stderr.write(JSON.stringify(entry) + "\n");
|
|
2866
|
+
}
|
|
2867
|
+
function logInfo(message, context) {
|
|
2868
|
+
log("INFO", message, context);
|
|
2869
|
+
}
|
|
2870
|
+
function logWarn(message, context) {
|
|
2871
|
+
log("WARN", message, context);
|
|
2872
|
+
}
|
|
2873
|
+
function logError(message, context) {
|
|
2874
|
+
log("ERROR", message, context);
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
// src/cli.ts
|
|
2707
2878
|
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
2708
2879
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
2709
2880
|
var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
@@ -2713,23 +2884,60 @@ var magenta = (s) => `\x1B[35m${s}\x1B[0m`;
|
|
|
2713
2884
|
var red = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
2714
2885
|
main();
|
|
2715
2886
|
function main() {
|
|
2887
|
+
let activeDb = null;
|
|
2888
|
+
const shutdown = (signal) => {
|
|
2889
|
+
logWarn(`Received ${signal}, shutting down gracefully\u2026`);
|
|
2890
|
+
if (activeDb) {
|
|
2891
|
+
try {
|
|
2892
|
+
activeDb.close();
|
|
2893
|
+
} catch {
|
|
2894
|
+
}
|
|
2895
|
+
activeDb = null;
|
|
2896
|
+
}
|
|
2897
|
+
process.exit(signal === "SIGINT" ? 130 : 0);
|
|
2898
|
+
};
|
|
2899
|
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
|
|
2900
|
+
process.on("SIGINT", () => shutdown("SIGINT"));
|
|
2901
|
+
cleanupTempFiles();
|
|
2716
2902
|
const program = new Command();
|
|
2717
2903
|
const CMD = "datasynx-cartography";
|
|
2718
|
-
const VERSION = "0.
|
|
2904
|
+
const VERSION = "1.0.1";
|
|
2719
2905
|
program.name(CMD).description("AI-powered Infrastructure Discovery & Agentic AI Cartography").version(VERSION);
|
|
2720
2906
|
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) => {
|
|
2721
2907
|
checkPrerequisites();
|
|
2908
|
+
const parsedDepth = parseInt(opts.depth, 10);
|
|
2909
|
+
const parsedMaxTurns = parseInt(opts.maxTurns, 10);
|
|
2910
|
+
if (Number.isNaN(parsedDepth) || parsedDepth < 1 || parsedDepth > 50) {
|
|
2911
|
+
process.stderr.write(`\u274C Invalid --depth: "${opts.depth}" (must be 1\u201350)
|
|
2912
|
+
`);
|
|
2913
|
+
process.exitCode = 2;
|
|
2914
|
+
return;
|
|
2915
|
+
}
|
|
2916
|
+
if (Number.isNaN(parsedMaxTurns) || parsedMaxTurns < 1 || parsedMaxTurns > 500) {
|
|
2917
|
+
process.stderr.write(`\u274C Invalid --max-turns: "${opts.maxTurns}" (must be 1\u2013500)
|
|
2918
|
+
`);
|
|
2919
|
+
process.exitCode = 2;
|
|
2920
|
+
return;
|
|
2921
|
+
}
|
|
2922
|
+
setVerbose(opts.verbose);
|
|
2722
2923
|
const config = defaultConfig({
|
|
2723
2924
|
entryPoints: opts.entry,
|
|
2724
|
-
maxDepth:
|
|
2725
|
-
maxTurns:
|
|
2925
|
+
maxDepth: parsedDepth,
|
|
2926
|
+
maxTurns: parsedMaxTurns,
|
|
2726
2927
|
agentModel: opts.model,
|
|
2727
2928
|
organization: opts.org,
|
|
2728
2929
|
outputDir: opts.output,
|
|
2729
2930
|
...opts.db ? { dbPath: opts.db } : {},
|
|
2730
2931
|
verbose: opts.verbose
|
|
2731
2932
|
});
|
|
2933
|
+
logInfo("Discovery started", {
|
|
2934
|
+
entryPoints: config.entryPoints,
|
|
2935
|
+
model: config.agentModel,
|
|
2936
|
+
maxTurns: config.maxTurns,
|
|
2937
|
+
maxDepth: config.maxDepth
|
|
2938
|
+
});
|
|
2732
2939
|
const db = new CartographyDB(config.dbPath);
|
|
2940
|
+
activeDb = db;
|
|
2733
2941
|
const sessionId = db.createSession("discover", config);
|
|
2734
2942
|
const w = process.stderr.write.bind(process.stderr);
|
|
2735
2943
|
const SPINNER = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
@@ -2857,10 +3065,13 @@ function main() {
|
|
|
2857
3065
|
await runDiscovery(config, db, sessionId, handleEvent, onAskUser, void 0);
|
|
2858
3066
|
} catch (err) {
|
|
2859
3067
|
stopSpinner();
|
|
3068
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
3069
|
+
logError("Discovery failed", { sessionId, error: errMsg });
|
|
2860
3070
|
w(`
|
|
2861
|
-
${bold("\x1B[31m\u2717\x1B[0m")} Discovery failed: ${
|
|
3071
|
+
${bold("\x1B[31m\u2717\x1B[0m")} Discovery failed: ${errMsg}
|
|
2862
3072
|
`);
|
|
2863
3073
|
db.close();
|
|
3074
|
+
activeDb = null;
|
|
2864
3075
|
process.exitCode = 1;
|
|
2865
3076
|
return;
|
|
2866
3077
|
}
|
|
@@ -2868,6 +3079,12 @@ function main() {
|
|
|
2868
3079
|
db.endSession(sessionId);
|
|
2869
3080
|
const stats = db.getStats(sessionId);
|
|
2870
3081
|
const totalSec = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
3082
|
+
logInfo("Discovery completed", {
|
|
3083
|
+
sessionId,
|
|
3084
|
+
nodes: stats.nodes,
|
|
3085
|
+
edges: stats.edges,
|
|
3086
|
+
durationSec: parseFloat(totalSec)
|
|
3087
|
+
});
|
|
2871
3088
|
w("\n");
|
|
2872
3089
|
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"));
|
|
2873
3090
|
w(` ${green(bold("DONE"))} ${bold(String(stats.nodes))} nodes, ${bold(String(stats.edges))} edges ${dim("in " + totalSec + "s")}
|
|
@@ -3324,7 +3541,7 @@ ${infraSummary.substring(0, 12e3)}`;
|
|
|
3324
3541
|
out("\n");
|
|
3325
3542
|
});
|
|
3326
3543
|
program.command("bookmarks").description("View all browser bookmarks (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)").action(async () => {
|
|
3327
|
-
const { scanAllBookmarks: scanAllBookmarks2 } = await import("./bookmarks-
|
|
3544
|
+
const { scanAllBookmarks: scanAllBookmarks2 } = await import("./bookmarks-BWNVQGPG.js");
|
|
3328
3545
|
const out = (s) => process.stdout.write(s);
|
|
3329
3546
|
process.stderr.write(" Scanning bookmarks...\n\n");
|
|
3330
3547
|
const hosts = await scanAllBookmarks2();
|
|
@@ -3583,6 +3800,40 @@ ${infraSummary.substring(0, 12e3)}`;
|
|
|
3583
3800
|
process.exitCode = 1;
|
|
3584
3801
|
}
|
|
3585
3802
|
});
|
|
3803
|
+
program.command("prune").description("Delete old sessions and their data").option("--older-than <days>", "Delete sessions older than N days", "30").option("--db <path>", "DB path").option("--dry-run", "Show what would be deleted without actually deleting", false).action((opts) => {
|
|
3804
|
+
const days = parseInt(opts.olderThan, 10);
|
|
3805
|
+
if (Number.isNaN(days) || days < 1) {
|
|
3806
|
+
process.stderr.write(`Invalid --older-than: "${opts.olderThan}" (must be >= 1)
|
|
3807
|
+
`);
|
|
3808
|
+
process.exitCode = 2;
|
|
3809
|
+
return;
|
|
3810
|
+
}
|
|
3811
|
+
const config = defaultConfig({ ...opts.db ? { dbPath: opts.db } : {} });
|
|
3812
|
+
const db = new CartographyDB(config.dbPath);
|
|
3813
|
+
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1e3).toISOString();
|
|
3814
|
+
const sessions = db.getSessions().filter((s) => s.startedAt < cutoff);
|
|
3815
|
+
if (sessions.length === 0) {
|
|
3816
|
+
process.stderr.write(`No sessions older than ${days} days.
|
|
3817
|
+
`);
|
|
3818
|
+
db.close();
|
|
3819
|
+
return;
|
|
3820
|
+
}
|
|
3821
|
+
if (opts.dryRun) {
|
|
3822
|
+
process.stderr.write(`Would delete ${sessions.length} session(s) older than ${days} days:
|
|
3823
|
+
`);
|
|
3824
|
+
for (const s of sessions) {
|
|
3825
|
+
const stats = db.getStats(s.id);
|
|
3826
|
+
process.stderr.write(` ${s.id.substring(0, 8)} ${s.startedAt.substring(0, 19)} nodes:${stats.nodes} edges:${stats.edges}
|
|
3827
|
+
`);
|
|
3828
|
+
}
|
|
3829
|
+
} else {
|
|
3830
|
+
const deleted = db.pruneSessions(cutoff);
|
|
3831
|
+
logInfo("Sessions pruned", { deleted, olderThanDays: days });
|
|
3832
|
+
process.stderr.write(`Deleted ${deleted} session(s) older than ${days} days.
|
|
3833
|
+
`);
|
|
3834
|
+
}
|
|
3835
|
+
db.close();
|
|
3836
|
+
});
|
|
3586
3837
|
const o = (s) => process.stderr.write(s);
|
|
3587
3838
|
const _b = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
3588
3839
|
const _d = (s) => `\x1B[2m${s}\x1B[0m`;
|