@datasynx/agentic-ai-cartography 1.1.1 → 2.0.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 +197 -33
- package/dist/bookmarks-VS56KVCO.js +25 -0
- package/dist/chunk-CJ2PITFA.js +785 -0
- package/dist/chunk-CJ2PITFA.js.map +1 -0
- package/dist/chunk-D6SRSLBF.js +48 -0
- package/dist/{chunk-WJR63RWY.js → chunk-J6FDZ6HZ.js} +11 -2
- package/dist/chunk-J6FDZ6HZ.js.map +1 -0
- package/dist/chunk-UGSNG3QJ.js +49 -0
- package/dist/chunk-UGSNG3QJ.js.map +1 -0
- package/dist/chunk-W7YE6AAH.js +1516 -0
- package/dist/chunk-W7YE6AAH.js.map +1 -0
- package/dist/cli.js +133 -664
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +60115 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +734 -0
- package/dist/index.d.ts +363 -7
- package/dist/index.js +1462 -161
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +33 -0
- package/dist/mcp-bin.js.map +1 -0
- package/dist/onnxruntime_binding-6Q6HXASN.node +0 -0
- package/dist/onnxruntime_binding-EKZT2NRK.node +0 -0
- package/dist/onnxruntime_binding-P6S7V3CI.node +0 -0
- package/dist/onnxruntime_binding-PJNNIIUO.node +0 -0
- package/dist/onnxruntime_binding-UN6SPTQK.node +0 -0
- package/dist/sdk-A6NLO3DJ.js +12294 -0
- package/dist/sdk-A6NLO3DJ.js.map +1 -0
- package/dist/sdk-G5D4WQZ4.js +12293 -0
- package/dist/sdk-G5D4WQZ4.js.map +1 -0
- package/dist/sdk-QSTAREST.js +4869 -0
- package/dist/sdk-QSTAREST.js.map +1 -0
- package/dist/sqlite-vec-EZN67B2V.js +40 -0
- package/dist/sqlite-vec-EZN67B2V.js.map +1 -0
- package/dist/sqlite-vec-UK5YYE5T.js +39 -0
- package/dist/sqlite-vec-UK5YYE5T.js.map +1 -0
- package/dist/transformers.node-BTYUTJK5.js +42884 -0
- package/dist/transformers.node-BTYUTJK5.js.map +1 -0
- package/dist/transformers.node-J6PRTTOX.js +42883 -0
- package/dist/transformers.node-J6PRTTOX.js.map +1 -0
- package/dist/{types-54623ALF.js → types-JG27FR3E.js} +5 -2
- package/dist/types-JG27FR3E.js.map +1 -0
- package/package.json +51 -16
- package/server.json +28 -0
- package/dist/bookmarks-BWNVQGPG.js +0 -14
- package/dist/chunk-QKNYI3SU.js +0 -459
- package/dist/chunk-QKNYI3SU.js.map +0 -1
- package/dist/chunk-WJR63RWY.js.map +0 -1
- /package/dist/{bookmarks-BWNVQGPG.js.map → bookmarks-VS56KVCO.js.map} +0 -0
- /package/dist/{types-54623ALF.js.map → chunk-D6SRSLBF.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CartographyDB,
|
|
4
|
+
startMcp
|
|
5
|
+
} from "./chunk-W7YE6AAH.js";
|
|
2
6
|
import {
|
|
3
7
|
DOMAIN_COLORS,
|
|
4
8
|
DOMAIN_PALETTE,
|
|
5
9
|
EDGE_RELATIONSHIPS,
|
|
6
10
|
NODE_TYPES,
|
|
11
|
+
NODE_TYPE_GROUPS,
|
|
7
12
|
defaultConfig
|
|
8
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-J6FDZ6HZ.js";
|
|
9
14
|
import {
|
|
10
15
|
HOME,
|
|
11
16
|
IS_LINUX,
|
|
12
17
|
IS_MAC,
|
|
13
18
|
IS_WIN,
|
|
14
19
|
PLATFORM,
|
|
20
|
+
checkReadOnly,
|
|
15
21
|
cleanupTempFiles,
|
|
16
22
|
commandExists,
|
|
17
23
|
dbScanDirs,
|
|
18
24
|
findFiles,
|
|
25
|
+
logDebug,
|
|
26
|
+
logError,
|
|
27
|
+
logInfo,
|
|
28
|
+
logWarn,
|
|
19
29
|
run,
|
|
20
30
|
scanAllBookmarks,
|
|
21
31
|
scanAllHistory,
|
|
22
32
|
scanWindowsDbServices,
|
|
23
|
-
scanWindowsPrograms
|
|
24
|
-
|
|
33
|
+
scanWindowsPrograms,
|
|
34
|
+
setVerbose
|
|
35
|
+
} from "./chunk-CJ2PITFA.js";
|
|
36
|
+
import "./chunk-UGSNG3QJ.js";
|
|
25
37
|
|
|
26
38
|
// src/cli.ts
|
|
27
39
|
import { Command } from "commander";
|
|
@@ -47,7 +59,7 @@ function checkPrerequisites() {
|
|
|
47
59
|
execSync("claude --version", { stdio: "pipe" });
|
|
48
60
|
} catch {
|
|
49
61
|
process.stderr.write(
|
|
50
|
-
"\n\u274C Claude CLI
|
|
62
|
+
"\n\u274C Claude CLI not found.\n Datasynx Cartography requires the Claude CLI as a runtime dependency.\n\n Install:\n npm install -g @anthropic-ai/claude-code\n # or\n curl -fsSL https://claude.ai/install.sh | bash\n\n Then: claude login\n\n"
|
|
51
63
|
);
|
|
52
64
|
process.exitCode = 1;
|
|
53
65
|
throw new Error("Claude CLI not found");
|
|
@@ -56,562 +68,31 @@ function checkPrerequisites() {
|
|
|
56
68
|
const hasOAuth = isOAuthLoggedIn();
|
|
57
69
|
if (!hasApiKey && !hasOAuth) {
|
|
58
70
|
process.stderr.write(
|
|
59
|
-
"\u26A0
|
|
71
|
+
"\u26A0 No authentication found. Please choose one of the following options:\n\n Option A \u2014 claude.ai Subscription (recommended):\n claude login\n\n Option B \u2014 API Key:\n export ANTHROPIC_API_KEY=sk-ant-...\n\n"
|
|
60
72
|
);
|
|
61
73
|
} else if (hasOAuth && !hasApiKey) {
|
|
62
|
-
process.stderr.write("\u2713
|
|
74
|
+
process.stderr.write("\u2713 Logged in via claude login (Subscription)\n");
|
|
63
75
|
}
|
|
64
76
|
}
|
|
65
77
|
|
|
66
|
-
// src/db.ts
|
|
67
|
-
import Database from "better-sqlite3";
|
|
68
|
-
import { mkdirSync } from "fs";
|
|
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
|
-
});
|
|
147
|
-
var SCHEMA = `
|
|
148
|
-
PRAGMA journal_mode = WAL;
|
|
149
|
-
PRAGMA foreign_keys = ON;
|
|
150
|
-
PRAGMA busy_timeout = 5000;
|
|
151
|
-
|
|
152
|
-
CREATE TABLE IF NOT EXISTS sessions (
|
|
153
|
-
id TEXT PRIMARY KEY,
|
|
154
|
-
mode TEXT NOT NULL CHECK (mode IN ('discover')),
|
|
155
|
-
started_at TEXT NOT NULL,
|
|
156
|
-
completed_at TEXT,
|
|
157
|
-
config TEXT NOT NULL DEFAULT '{}'
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
CREATE TABLE IF NOT EXISTS nodes (
|
|
161
|
-
id TEXT NOT NULL,
|
|
162
|
-
session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
163
|
-
type TEXT NOT NULL,
|
|
164
|
-
name TEXT NOT NULL,
|
|
165
|
-
discovered_via TEXT,
|
|
166
|
-
discovered_at TEXT NOT NULL,
|
|
167
|
-
path_id TEXT,
|
|
168
|
-
depth INTEGER DEFAULT 0,
|
|
169
|
-
confidence REAL DEFAULT 0.5,
|
|
170
|
-
metadata TEXT NOT NULL DEFAULT '{}',
|
|
171
|
-
tags TEXT NOT NULL DEFAULT '[]',
|
|
172
|
-
domain TEXT,
|
|
173
|
-
sub_domain TEXT,
|
|
174
|
-
quality_score REAL,
|
|
175
|
-
PRIMARY KEY (id, session_id)
|
|
176
|
-
);
|
|
177
|
-
|
|
178
|
-
CREATE TABLE IF NOT EXISTS connections (
|
|
179
|
-
id TEXT PRIMARY KEY,
|
|
180
|
-
session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
181
|
-
source_asset_id TEXT NOT NULL,
|
|
182
|
-
target_asset_id TEXT NOT NULL,
|
|
183
|
-
type TEXT,
|
|
184
|
-
created_at TEXT NOT NULL
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
CREATE TABLE IF NOT EXISTS edges (
|
|
188
|
-
id TEXT PRIMARY KEY,
|
|
189
|
-
session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
190
|
-
source_id TEXT NOT NULL,
|
|
191
|
-
target_id TEXT NOT NULL,
|
|
192
|
-
relationship TEXT NOT NULL,
|
|
193
|
-
evidence TEXT,
|
|
194
|
-
confidence REAL DEFAULT 0.5,
|
|
195
|
-
discovered_at TEXT NOT NULL
|
|
196
|
-
);
|
|
197
|
-
|
|
198
|
-
CREATE TABLE IF NOT EXISTS activity_events (
|
|
199
|
-
id TEXT PRIMARY KEY,
|
|
200
|
-
session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
201
|
-
task_id TEXT,
|
|
202
|
-
timestamp TEXT NOT NULL,
|
|
203
|
-
event_type TEXT NOT NULL,
|
|
204
|
-
process TEXT NOT NULL,
|
|
205
|
-
pid INTEGER NOT NULL,
|
|
206
|
-
target TEXT,
|
|
207
|
-
target_type TEXT,
|
|
208
|
-
port INTEGER,
|
|
209
|
-
duration_ms INTEGER
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
213
|
-
id TEXT PRIMARY KEY,
|
|
214
|
-
session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
215
|
-
description TEXT,
|
|
216
|
-
started_at TEXT NOT NULL,
|
|
217
|
-
completed_at TEXT,
|
|
218
|
-
steps TEXT NOT NULL DEFAULT '[]',
|
|
219
|
-
involved_services TEXT NOT NULL DEFAULT '[]',
|
|
220
|
-
status TEXT DEFAULT 'active' CHECK (status IN ('active','completed','cancelled'))
|
|
221
|
-
);
|
|
222
|
-
|
|
223
|
-
CREATE TABLE IF NOT EXISTS workflows (
|
|
224
|
-
id TEXT PRIMARY KEY,
|
|
225
|
-
session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
226
|
-
name TEXT,
|
|
227
|
-
pattern TEXT NOT NULL,
|
|
228
|
-
task_ids TEXT NOT NULL DEFAULT '[]',
|
|
229
|
-
occurrences INTEGER DEFAULT 1,
|
|
230
|
-
first_seen TEXT NOT NULL,
|
|
231
|
-
last_seen TEXT NOT NULL,
|
|
232
|
-
avg_duration_ms INTEGER,
|
|
233
|
-
involved_services TEXT NOT NULL DEFAULT '[]'
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
CREATE TABLE IF NOT EXISTS node_approvals (
|
|
237
|
-
pattern TEXT PRIMARY KEY,
|
|
238
|
-
action TEXT NOT NULL CHECK (action IN ('save','ignore','auto')),
|
|
239
|
-
created_at TEXT NOT NULL
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
CREATE INDEX IF NOT EXISTS idx_nodes_session ON nodes(session_id);
|
|
243
|
-
CREATE INDEX IF NOT EXISTS idx_edges_session ON edges(session_id);
|
|
244
|
-
CREATE INDEX IF NOT EXISTS idx_events_session ON activity_events(session_id);
|
|
245
|
-
CREATE INDEX IF NOT EXISTS idx_events_task ON activity_events(task_id);
|
|
246
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_session ON tasks(session_id);
|
|
247
|
-
CREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);
|
|
248
|
-
`;
|
|
249
|
-
var CartographyDB = class {
|
|
250
|
-
db;
|
|
251
|
-
constructor(dbPath) {
|
|
252
|
-
mkdirSync(dirname(dbPath), { recursive: true });
|
|
253
|
-
this.db = new Database(dbPath);
|
|
254
|
-
this.db.pragma("journal_mode = WAL");
|
|
255
|
-
this.db.pragma("foreign_keys = ON");
|
|
256
|
-
this.db.pragma("busy_timeout = 5000");
|
|
257
|
-
this.migrate();
|
|
258
|
-
}
|
|
259
|
-
migrate() {
|
|
260
|
-
const version = this.db.pragma("user_version", { simple: true });
|
|
261
|
-
if (version === 0) {
|
|
262
|
-
this.db.exec(SCHEMA);
|
|
263
|
-
this.db.pragma("user_version = 2");
|
|
264
|
-
} else if (version === 1) {
|
|
265
|
-
const cols = this.db.prepare("PRAGMA table_info(nodes)").all().map((c) => c.name);
|
|
266
|
-
if (!cols.includes("domain")) this.db.exec("ALTER TABLE nodes ADD COLUMN domain TEXT");
|
|
267
|
-
if (!cols.includes("sub_domain")) this.db.exec("ALTER TABLE nodes ADD COLUMN sub_domain TEXT");
|
|
268
|
-
if (!cols.includes("quality_score")) this.db.exec("ALTER TABLE nodes ADD COLUMN quality_score REAL");
|
|
269
|
-
this.db.exec(`
|
|
270
|
-
CREATE TABLE IF NOT EXISTS connections (
|
|
271
|
-
id TEXT PRIMARY KEY,
|
|
272
|
-
session_id TEXT NOT NULL REFERENCES sessions(id),
|
|
273
|
-
source_asset_id TEXT NOT NULL,
|
|
274
|
-
target_asset_id TEXT NOT NULL,
|
|
275
|
-
type TEXT,
|
|
276
|
-
created_at TEXT NOT NULL
|
|
277
|
-
);
|
|
278
|
-
CREATE INDEX IF NOT EXISTS idx_connections_session ON connections(session_id);
|
|
279
|
-
`);
|
|
280
|
-
this.db.pragma("user_version = 2");
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
close() {
|
|
284
|
-
this.db.pragma("optimize");
|
|
285
|
-
this.db.close();
|
|
286
|
-
}
|
|
287
|
-
// ── Sessions ────────────────────────────
|
|
288
|
-
createSession(mode, config) {
|
|
289
|
-
const id = crypto.randomUUID();
|
|
290
|
-
this.db.prepare(
|
|
291
|
-
"INSERT INTO sessions (id, mode, started_at, config) VALUES (?, ?, ?, ?)"
|
|
292
|
-
).run(id, mode, (/* @__PURE__ */ new Date()).toISOString(), JSON.stringify(config));
|
|
293
|
-
return id;
|
|
294
|
-
}
|
|
295
|
-
endSession(id) {
|
|
296
|
-
this.db.prepare("UPDATE sessions SET completed_at = ? WHERE id = ?").run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
297
|
-
}
|
|
298
|
-
getSession(id) {
|
|
299
|
-
const row = this.db.prepare("SELECT * FROM sessions WHERE id = ?").get(id);
|
|
300
|
-
return row ? this.mapSession(row) : void 0;
|
|
301
|
-
}
|
|
302
|
-
getLatestSession(mode) {
|
|
303
|
-
const row = mode ? this.db.prepare("SELECT * FROM sessions WHERE mode = ? ORDER BY rowid DESC LIMIT 1").get(mode) : this.db.prepare("SELECT * FROM sessions ORDER BY rowid DESC LIMIT 1").get();
|
|
304
|
-
return row ? this.mapSession(row) : void 0;
|
|
305
|
-
}
|
|
306
|
-
getSessions() {
|
|
307
|
-
const rows = this.db.prepare("SELECT * FROM sessions ORDER BY rowid DESC").all();
|
|
308
|
-
return rows.map((r) => this.mapSession(r));
|
|
309
|
-
}
|
|
310
|
-
mapSession(r) {
|
|
311
|
-
const v = SessionRowSchema.parse(r);
|
|
312
|
-
return {
|
|
313
|
-
id: v.id,
|
|
314
|
-
mode: v.mode,
|
|
315
|
-
startedAt: v.started_at,
|
|
316
|
-
completedAt: v.completed_at ?? void 0,
|
|
317
|
-
config: v.config
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
// ── Nodes ───────────────────────────────
|
|
321
|
-
upsertNode(sessionId, node, depth = 0) {
|
|
322
|
-
this.db.prepare(`
|
|
323
|
-
INSERT OR REPLACE INTO nodes
|
|
324
|
-
(id, session_id, type, name, discovered_via, discovered_at, depth, confidence, metadata, tags,
|
|
325
|
-
domain, sub_domain, quality_score)
|
|
326
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
327
|
-
`).run(
|
|
328
|
-
node.id,
|
|
329
|
-
sessionId,
|
|
330
|
-
node.type,
|
|
331
|
-
node.name,
|
|
332
|
-
node.discoveredVia,
|
|
333
|
-
(/* @__PURE__ */ new Date()).toISOString(),
|
|
334
|
-
depth,
|
|
335
|
-
node.confidence,
|
|
336
|
-
JSON.stringify(node.metadata ?? {}),
|
|
337
|
-
JSON.stringify(node.tags ?? []),
|
|
338
|
-
node.domain ?? null,
|
|
339
|
-
node.subDomain ?? null,
|
|
340
|
-
node.qualityScore ?? null
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
getNodes(sessionId) {
|
|
344
|
-
const rows = this.db.prepare("SELECT * FROM nodes WHERE session_id = ?").all(sessionId);
|
|
345
|
-
return rows.map((r) => this.mapNode(r));
|
|
346
|
-
}
|
|
347
|
-
mapNode(r) {
|
|
348
|
-
const v = NodeRowSchema.parse(r);
|
|
349
|
-
return {
|
|
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
|
|
364
|
-
};
|
|
365
|
-
}
|
|
366
|
-
deleteNode(sessionId, nodeId) {
|
|
367
|
-
this.db.prepare("DELETE FROM nodes WHERE session_id = ? AND id = ?").run(sessionId, nodeId);
|
|
368
|
-
this.db.prepare(
|
|
369
|
-
"DELETE FROM edges WHERE session_id = ? AND (source_id = ? OR target_id = ?)"
|
|
370
|
-
).run(sessionId, nodeId, nodeId);
|
|
371
|
-
}
|
|
372
|
-
// ── Edges ───────────────────────────────
|
|
373
|
-
insertEdge(sessionId, edge) {
|
|
374
|
-
const id = crypto.randomUUID();
|
|
375
|
-
this.db.prepare(`
|
|
376
|
-
INSERT OR IGNORE INTO edges
|
|
377
|
-
(id, session_id, source_id, target_id, relationship, evidence, confidence, discovered_at)
|
|
378
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
379
|
-
`).run(
|
|
380
|
-
id,
|
|
381
|
-
sessionId,
|
|
382
|
-
edge.sourceId,
|
|
383
|
-
edge.targetId,
|
|
384
|
-
edge.relationship,
|
|
385
|
-
edge.evidence,
|
|
386
|
-
edge.confidence,
|
|
387
|
-
(/* @__PURE__ */ new Date()).toISOString()
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
getEdges(sessionId) {
|
|
391
|
-
const rows = this.db.prepare("SELECT * FROM edges WHERE session_id = ?").all(sessionId);
|
|
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
|
-
});
|
|
405
|
-
}
|
|
406
|
-
// ── Events ──────────────────────────────
|
|
407
|
-
insertEvent(sessionId, event, taskId) {
|
|
408
|
-
const id = crypto.randomUUID();
|
|
409
|
-
this.db.prepare(`
|
|
410
|
-
INSERT INTO activity_events
|
|
411
|
-
(id, session_id, task_id, timestamp, event_type, process, pid, target, target_type, port)
|
|
412
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
413
|
-
`).run(
|
|
414
|
-
id,
|
|
415
|
-
sessionId,
|
|
416
|
-
taskId ?? null,
|
|
417
|
-
(/* @__PURE__ */ new Date()).toISOString(),
|
|
418
|
-
event.eventType,
|
|
419
|
-
event.process,
|
|
420
|
-
event.pid,
|
|
421
|
-
event.target ?? null,
|
|
422
|
-
event.targetType ?? null,
|
|
423
|
-
event.port ?? null
|
|
424
|
-
);
|
|
425
|
-
}
|
|
426
|
-
getEvents(sessionId, since) {
|
|
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);
|
|
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
|
-
});
|
|
444
|
-
}
|
|
445
|
-
// ── Tasks ───────────────────────────────
|
|
446
|
-
startTask(sessionId, description) {
|
|
447
|
-
const id = crypto.randomUUID();
|
|
448
|
-
this.db.prepare(`
|
|
449
|
-
INSERT INTO tasks (id, session_id, description, started_at, steps, involved_services, status)
|
|
450
|
-
VALUES (?, ?, ?, ?, '[]', '[]', 'active')
|
|
451
|
-
`).run(id, sessionId, description ?? null, (/* @__PURE__ */ new Date()).toISOString());
|
|
452
|
-
return id;
|
|
453
|
-
}
|
|
454
|
-
endCurrentTask(sessionId) {
|
|
455
|
-
this.db.prepare(`
|
|
456
|
-
UPDATE tasks SET status = 'completed', completed_at = ?
|
|
457
|
-
WHERE session_id = ? AND status = 'active'
|
|
458
|
-
`).run((/* @__PURE__ */ new Date()).toISOString(), sessionId);
|
|
459
|
-
}
|
|
460
|
-
updateTaskDescription(sessionId, description) {
|
|
461
|
-
this.db.prepare(`
|
|
462
|
-
UPDATE tasks SET description = ?
|
|
463
|
-
WHERE session_id = ? AND status = 'active'
|
|
464
|
-
`).run(description, sessionId);
|
|
465
|
-
}
|
|
466
|
-
getActiveTask(sessionId) {
|
|
467
|
-
const row = this.db.prepare(
|
|
468
|
-
"SELECT * FROM tasks WHERE session_id = ? AND status = 'active' LIMIT 1"
|
|
469
|
-
).get(sessionId);
|
|
470
|
-
return row ? this.mapTask(row) : void 0;
|
|
471
|
-
}
|
|
472
|
-
getTasks(sessionId) {
|
|
473
|
-
const rows = this.db.prepare("SELECT * FROM tasks WHERE session_id = ? ORDER BY started_at").all(sessionId);
|
|
474
|
-
return rows.map((r) => this.mapTask(r));
|
|
475
|
-
}
|
|
476
|
-
mapTask(r) {
|
|
477
|
-
const v = TaskRowSchema.parse(r);
|
|
478
|
-
return {
|
|
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
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
// ── Workflows ───────────────────────────
|
|
490
|
-
insertWorkflow(sessionId, data) {
|
|
491
|
-
const id = crypto.randomUUID();
|
|
492
|
-
this.db.prepare(`
|
|
493
|
-
INSERT INTO workflows
|
|
494
|
-
(id, session_id, name, pattern, task_ids, occurrences,
|
|
495
|
-
first_seen, last_seen, avg_duration_ms, involved_services)
|
|
496
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
497
|
-
`).run(
|
|
498
|
-
id,
|
|
499
|
-
sessionId,
|
|
500
|
-
data.name ?? null,
|
|
501
|
-
data.pattern,
|
|
502
|
-
data.taskIds,
|
|
503
|
-
data.occurrences,
|
|
504
|
-
data.firstSeen,
|
|
505
|
-
data.lastSeen,
|
|
506
|
-
data.avgDurationMs,
|
|
507
|
-
data.involvedServices
|
|
508
|
-
);
|
|
509
|
-
}
|
|
510
|
-
getWorkflows(sessionId) {
|
|
511
|
-
const rows = this.db.prepare("SELECT * FROM workflows WHERE session_id = ?").all(sessionId);
|
|
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
|
-
});
|
|
527
|
-
}
|
|
528
|
-
// ── Connections (user-created hex map links) ─────────────────────────────
|
|
529
|
-
upsertConnection(sessionId, conn) {
|
|
530
|
-
const existing = this.db.prepare(
|
|
531
|
-
"SELECT id FROM connections WHERE session_id = ? AND source_asset_id = ? AND target_asset_id = ?"
|
|
532
|
-
).get(sessionId, conn.sourceAssetId, conn.targetAssetId);
|
|
533
|
-
if (existing) return existing.id;
|
|
534
|
-
const id = crypto.randomUUID();
|
|
535
|
-
this.db.prepare(`
|
|
536
|
-
INSERT INTO connections (id, session_id, source_asset_id, target_asset_id, type, created_at)
|
|
537
|
-
VALUES (?, ?, ?, ?, ?, ?)
|
|
538
|
-
`).run(id, sessionId, conn.sourceAssetId, conn.targetAssetId, conn.type ?? null, (/* @__PURE__ */ new Date()).toISOString());
|
|
539
|
-
return id;
|
|
540
|
-
}
|
|
541
|
-
getConnections(sessionId) {
|
|
542
|
-
const rows = this.db.prepare("SELECT * FROM connections WHERE session_id = ?").all(sessionId);
|
|
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
|
-
});
|
|
554
|
-
}
|
|
555
|
-
deleteConnection(sessionId, connectionId) {
|
|
556
|
-
this.db.prepare("DELETE FROM connections WHERE session_id = ? AND id = ?").run(sessionId, connectionId);
|
|
557
|
-
}
|
|
558
|
-
// ── Approvals ───────────────────────────
|
|
559
|
-
setApproval(pattern, action) {
|
|
560
|
-
this.db.prepare(`
|
|
561
|
-
INSERT OR REPLACE INTO node_approvals (pattern, action, created_at) VALUES (?, ?, ?)
|
|
562
|
-
`).run(pattern, action, (/* @__PURE__ */ new Date()).toISOString());
|
|
563
|
-
}
|
|
564
|
-
getApproval(pattern) {
|
|
565
|
-
const row = this.db.prepare("SELECT action FROM node_approvals WHERE pattern = ?").get(pattern);
|
|
566
|
-
return row?.action;
|
|
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
|
-
}
|
|
593
|
-
// ── Stats ───────────────────────────────
|
|
594
|
-
getStats(sessionId) {
|
|
595
|
-
const nodes = this.db.prepare("SELECT COUNT(*) as c FROM nodes WHERE session_id = ?").get(sessionId).c;
|
|
596
|
-
const edges = this.db.prepare("SELECT COUNT(*) as c FROM edges WHERE session_id = ?").get(sessionId).c;
|
|
597
|
-
const events = this.db.prepare("SELECT COUNT(*) as c FROM activity_events WHERE session_id = ?").get(sessionId).c;
|
|
598
|
-
const tasks = this.db.prepare("SELECT COUNT(*) as c FROM tasks WHERE session_id = ?").get(sessionId).c;
|
|
599
|
-
return { nodes, edges, events, tasks };
|
|
600
|
-
}
|
|
601
|
-
};
|
|
602
|
-
|
|
603
78
|
// src/tools.ts
|
|
604
|
-
import { z
|
|
79
|
+
import { z } from "zod";
|
|
605
80
|
function createScanRunner(runFn, opts = {}) {
|
|
606
81
|
const threshold = opts.threshold ?? 3;
|
|
607
82
|
let consecutiveFailures = 0;
|
|
608
83
|
let tripped = false;
|
|
609
84
|
return (cmd) => {
|
|
610
|
-
if (tripped)
|
|
85
|
+
if (tripped) {
|
|
86
|
+
logDebug(`Circuit breaker: skipping "${cmd}" (${consecutiveFailures} consecutive failures)`);
|
|
87
|
+
return "(skipped \u2014 circuit breaker: too many consecutive failures)";
|
|
88
|
+
}
|
|
611
89
|
const result = runFn(cmd, { timeout: opts.timeout ?? 2e4, env: opts.env });
|
|
612
90
|
if (!result) {
|
|
613
91
|
consecutiveFailures++;
|
|
614
|
-
if (consecutiveFailures >= threshold)
|
|
92
|
+
if (consecutiveFailures >= threshold) {
|
|
93
|
+
tripped = true;
|
|
94
|
+
logDebug(`Circuit breaker tripped after ${threshold} failures, last command: "${cmd}"`);
|
|
95
|
+
}
|
|
615
96
|
return "(error or not available)";
|
|
616
97
|
}
|
|
617
98
|
consecutiveFailures = 0;
|
|
@@ -619,27 +100,31 @@ function createScanRunner(runFn, opts = {}) {
|
|
|
619
100
|
};
|
|
620
101
|
}
|
|
621
102
|
function stripSensitive(target) {
|
|
103
|
+
const raw = target.trim();
|
|
104
|
+
if (!raw) return raw;
|
|
622
105
|
try {
|
|
623
|
-
const url = new URL(
|
|
624
|
-
|
|
106
|
+
const url = new URL(raw.startsWith("http") ? raw : `tcp://${raw}`);
|
|
107
|
+
const stripped = `${url.hostname}${url.port ? ":" + url.port : ""}`;
|
|
108
|
+
return stripped || raw;
|
|
625
109
|
} catch {
|
|
626
|
-
|
|
110
|
+
const stripped = raw.replace(/\/.*$/, "").replace(/\?.*$/, "").replace(/@.*:/, ":");
|
|
111
|
+
return stripped || raw;
|
|
627
112
|
}
|
|
628
113
|
}
|
|
629
114
|
async function createCartographyTools(db, sessionId, opts = {}) {
|
|
630
|
-
const { tool, createSdkMcpServer } = await import("
|
|
115
|
+
const { tool, createSdkMcpServer } = await import("./sdk-A6NLO3DJ.js");
|
|
631
116
|
const tools = [
|
|
632
117
|
tool("save_node", "Save an infrastructure node to the catalog", {
|
|
633
|
-
id:
|
|
634
|
-
type:
|
|
635
|
-
name:
|
|
636
|
-
discoveredVia:
|
|
637
|
-
confidence:
|
|
638
|
-
metadata:
|
|
639
|
-
tags:
|
|
640
|
-
domain:
|
|
641
|
-
subDomain:
|
|
642
|
-
qualityScore:
|
|
118
|
+
id: z.string(),
|
|
119
|
+
type: z.enum(NODE_TYPES),
|
|
120
|
+
name: z.string(),
|
|
121
|
+
discoveredVia: z.string(),
|
|
122
|
+
confidence: z.number().min(0).max(1),
|
|
123
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
124
|
+
tags: z.array(z.string()).optional(),
|
|
125
|
+
domain: z.string().optional().describe('Business domain, e.g. "Marketing", "Finance"'),
|
|
126
|
+
subDomain: z.string().optional().describe('Sub-domain, e.g. "Forecast client orders"'),
|
|
127
|
+
qualityScore: z.number().min(0).max(100).optional().describe("Data quality score 0\u2013100")
|
|
643
128
|
}, async (args) => {
|
|
644
129
|
const node = {
|
|
645
130
|
id: stripSensitive(args["id"]),
|
|
@@ -657,11 +142,11 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
657
142
|
return { content: [{ type: "text", text: `\u2713 Node: ${node.id}` }] };
|
|
658
143
|
}),
|
|
659
144
|
tool("save_edge", "Save a relationship (edge) between two nodes \u2014 ALWAYS save edges when connections are clear", {
|
|
660
|
-
sourceId:
|
|
661
|
-
targetId:
|
|
662
|
-
relationship:
|
|
663
|
-
evidence:
|
|
664
|
-
confidence:
|
|
145
|
+
sourceId: z.string(),
|
|
146
|
+
targetId: z.string(),
|
|
147
|
+
relationship: z.enum(EDGE_RELATIONSHIPS),
|
|
148
|
+
evidence: z.string(),
|
|
149
|
+
confidence: z.number().min(0).max(1)
|
|
665
150
|
}, async (args) => {
|
|
666
151
|
db.insertEdge(sessionId, {
|
|
667
152
|
sourceId: args["sourceId"],
|
|
@@ -673,7 +158,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
673
158
|
return { content: [{ type: "text", text: `\u2713 ${args["sourceId"]}\u2192${args["targetId"]}` }] };
|
|
674
159
|
}),
|
|
675
160
|
tool("get_catalog", "Get the current catalog \u2014 use before save_node to avoid duplicates", {
|
|
676
|
-
includeEdges:
|
|
161
|
+
includeEdges: z.boolean().default(true)
|
|
677
162
|
}, async (args) => {
|
|
678
163
|
const nodes = db.getNodes(sessionId);
|
|
679
164
|
const edges = args["includeEdges"] ? db.getEdges(sessionId) : [];
|
|
@@ -688,8 +173,8 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
688
173
|
};
|
|
689
174
|
}),
|
|
690
175
|
tool("ask_user", "Ask the user a question \u2014 for clarifications, missing context, or consent (e.g. before scanning browser history)", {
|
|
691
|
-
question:
|
|
692
|
-
context:
|
|
176
|
+
question: z.string().describe("The question for the user (clear and specific)"),
|
|
177
|
+
context: z.string().optional().describe("Optional context explaining why this is relevant")
|
|
693
178
|
}, async (args) => {
|
|
694
179
|
const question = args["question"];
|
|
695
180
|
const context = args["context"];
|
|
@@ -702,7 +187,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
702
187
|
};
|
|
703
188
|
}),
|
|
704
189
|
tool("scan_bookmarks", "Scan all browser bookmarks \u2014 hostnames only, no personal data (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)", {
|
|
705
|
-
minConfidence:
|
|
190
|
+
minConfidence: z.number().min(0).max(1).default(0.5).optional()
|
|
706
191
|
}, async () => {
|
|
707
192
|
const hosts = await scanAllBookmarks();
|
|
708
193
|
return {
|
|
@@ -722,7 +207,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
722
207
|
};
|
|
723
208
|
}),
|
|
724
209
|
tool("scan_browser_history", "Scan browser history \u2014 anonymized hostnames + visit frequency. ALWAYS call ask_user for consent before using this tool.", {
|
|
725
|
-
minVisits:
|
|
210
|
+
minVisits: z.number().min(1).default(3).optional().describe("Minimum visit count to include a host (filters rarely-visited sites)")
|
|
726
211
|
}, async (args) => {
|
|
727
212
|
const minVisits = args["minVisits"] ?? 3;
|
|
728
213
|
const hosts = await scanAllHistory();
|
|
@@ -744,7 +229,7 @@ async function createCartographyTools(db, sessionId, opts = {}) {
|
|
|
744
229
|
};
|
|
745
230
|
}),
|
|
746
231
|
tool("scan_local_databases", "Scan for local database files and running DB servers \u2014 PostgreSQL databases, MySQL, SQLite files from installed apps", {
|
|
747
|
-
deep:
|
|
232
|
+
deep: z.boolean().default(false).optional().describe("Also search home directory recursively for SQLite/DB files (slower)")
|
|
748
233
|
}, async (args) => {
|
|
749
234
|
const deep = args["deep"] ?? false;
|
|
750
235
|
const results = {};
|
|
@@ -816,7 +301,7 @@ ${v}`).join("\n\n");
|
|
|
816
301
|
return { content: [{ type: "text", text: out }] };
|
|
817
302
|
}),
|
|
818
303
|
tool("scan_k8s_resources", "Scan Kubernetes cluster via kubectl \u2014 100% readonly (get, describe)", {
|
|
819
|
-
namespace:
|
|
304
|
+
namespace: z.string().optional().describe("Filter by namespace \u2014 empty = all namespaces")
|
|
820
305
|
}, async (args) => {
|
|
821
306
|
const ns = args["namespace"];
|
|
822
307
|
const nsFlag = ns ? `-n ${ns}` : "--all-namespaces";
|
|
@@ -847,8 +332,8 @@ ${runK(c)}`).join("\n\n");
|
|
|
847
332
|
return { content: [{ type: "text", text: out }] };
|
|
848
333
|
}),
|
|
849
334
|
tool("scan_aws_resources", "Scan AWS infrastructure via AWS CLI \u2014 100% readonly (describe, list)", {
|
|
850
|
-
region:
|
|
851
|
-
profile:
|
|
335
|
+
region: z.string().optional().describe("AWS Region \u2014 default: AWS_DEFAULT_REGION or profile"),
|
|
336
|
+
profile: z.string().optional().describe("AWS CLI profile")
|
|
852
337
|
}, async (args) => {
|
|
853
338
|
const region = args["region"];
|
|
854
339
|
const profile = args["profile"];
|
|
@@ -871,7 +356,7 @@ ${runAws(c)}`).join("\n\n");
|
|
|
871
356
|
return { content: [{ type: "text", text: out }] };
|
|
872
357
|
}),
|
|
873
358
|
tool("scan_gcp_resources", "Scan Google Cloud Platform via gcloud CLI \u2014 100% readonly (list, describe)", {
|
|
874
|
-
project:
|
|
359
|
+
project: z.string().optional().describe("GCP Project ID \u2014 default: current gcloud project")
|
|
875
360
|
}, async (args) => {
|
|
876
361
|
const project = args["project"];
|
|
877
362
|
const pf = project ? `--project ${project}` : "";
|
|
@@ -892,8 +377,8 @@ ${runGcp(c)}`).join("\n\n");
|
|
|
892
377
|
return { content: [{ type: "text", text: out }] };
|
|
893
378
|
}),
|
|
894
379
|
tool("scan_azure_resources", "Scan Azure infrastructure via az CLI \u2014 100% readonly (list, show)", {
|
|
895
|
-
subscription:
|
|
896
|
-
resourceGroup:
|
|
380
|
+
subscription: z.string().optional().describe("Azure Subscription ID"),
|
|
381
|
+
resourceGroup: z.string().optional().describe("Filter by resource group")
|
|
897
382
|
}, async (args) => {
|
|
898
383
|
const sub = args["subscription"];
|
|
899
384
|
const rg = args["resourceGroup"];
|
|
@@ -916,7 +401,7 @@ ${runAz(c)}`).join("\n\n");
|
|
|
916
401
|
return { content: [{ type: "text", text: out }] };
|
|
917
402
|
}),
|
|
918
403
|
tool("scan_installed_apps", "Scan all installed apps and tools \u2014 IDEs, office, dev tools, business apps, databases", {
|
|
919
|
-
searchHint:
|
|
404
|
+
searchHint: z.string().optional().describe('Optional search term to find specific tools (e.g. "hubspot windsurf cursor")')
|
|
920
405
|
}, async (args) => {
|
|
921
406
|
const hint = args["searchHint"];
|
|
922
407
|
const results = {};
|
|
@@ -1101,18 +586,20 @@ ${v}`).join("\n\n");
|
|
|
1101
586
|
}
|
|
1102
587
|
|
|
1103
588
|
// src/safety.ts
|
|
1104
|
-
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;
|
|
1105
|
-
var BLOCKED_REDIRECTS = />>|>[^>]|Out-File|Set-Content|Add-Content/;
|
|
1106
589
|
var safetyHook = async (input, _toolUseID, _options) => {
|
|
1107
590
|
if (!("tool_name" in input)) return {};
|
|
1108
591
|
if (input.tool_name !== "Bash") return {};
|
|
1109
|
-
const cmd = input.tool_input?.command ?? "";
|
|
1110
|
-
if (
|
|
592
|
+
const cmd = (input.tool_input?.command ?? "").trim();
|
|
593
|
+
if (!cmd) {
|
|
594
|
+
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow" } };
|
|
595
|
+
}
|
|
596
|
+
const decision = checkReadOnly(cmd);
|
|
597
|
+
if (!decision.allowed) {
|
|
1111
598
|
return {
|
|
1112
599
|
hookSpecificOutput: {
|
|
1113
600
|
hookEventName: "PreToolUse",
|
|
1114
601
|
permissionDecision: "deny",
|
|
1115
|
-
permissionDecisionReason: `BLOCKED:
|
|
602
|
+
permissionDecisionReason: `BLOCKED: ${decision.reason} \u2014 read-only allowlist policy`
|
|
1116
603
|
}
|
|
1117
604
|
};
|
|
1118
605
|
}
|
|
@@ -1126,7 +613,7 @@ var safetyHook = async (input, _toolUseID, _options) => {
|
|
|
1126
613
|
|
|
1127
614
|
// src/agent.ts
|
|
1128
615
|
async function runDiscovery(config, db, sessionId, onEvent, onAskUser, hint) {
|
|
1129
|
-
const { query } = await import("
|
|
616
|
+
const { query } = await import("./sdk-A6NLO3DJ.js");
|
|
1130
617
|
const tools = await createCartographyTools(db, sessionId, { onAskUser });
|
|
1131
618
|
const hintSection = hint ? `
|
|
1132
619
|
\u26A1 USER HINT (HIGH PRIORITY): The user wants to find these specific tools: "${hint}"
|
|
@@ -1319,7 +806,7 @@ Use ask_user when you need context from the user.`;
|
|
|
1319
806
|
}
|
|
1320
807
|
|
|
1321
808
|
// src/exporter.ts
|
|
1322
|
-
import { mkdirSync
|
|
809
|
+
import { mkdirSync, writeFileSync } from "fs";
|
|
1323
810
|
import { join as join2 } from "path";
|
|
1324
811
|
|
|
1325
812
|
// src/hex.ts
|
|
@@ -1427,6 +914,11 @@ function layoutClusters(groups, hexSize) {
|
|
|
1427
914
|
}
|
|
1428
915
|
function findFreeOrigin(occupied, count, gap) {
|
|
1429
916
|
const key = (q, r) => `${q},${r}`;
|
|
917
|
+
const parsedOccupied = [];
|
|
918
|
+
for (const oKey of occupied) {
|
|
919
|
+
const [oq, or] = oKey.split(",").map(Number);
|
|
920
|
+
parsedOccupied.push({ q: oq, r: or });
|
|
921
|
+
}
|
|
1430
922
|
for (let searchRadius = 1; searchRadius < 100; searchRadius++) {
|
|
1431
923
|
const candidates = hexSpiral({ q: 0, r: 0 }, 1 + 6 * searchRadius * (searchRadius + 1) / 2);
|
|
1432
924
|
for (const candidate of candidates) {
|
|
@@ -1437,9 +929,8 @@ function findFreeOrigin(occupied, count, gap) {
|
|
|
1437
929
|
fits = false;
|
|
1438
930
|
break;
|
|
1439
931
|
}
|
|
1440
|
-
for (const
|
|
1441
|
-
|
|
1442
|
-
if (hexDistance(tp, { q: oq, r: or }) < gap) {
|
|
932
|
+
for (const oc of parsedOccupied) {
|
|
933
|
+
if (hexDistance(tp, oc) < gap) {
|
|
1443
934
|
fits = false;
|
|
1444
935
|
break;
|
|
1445
936
|
}
|
|
@@ -1537,12 +1028,9 @@ function buildMapData(nodes, edges, options) {
|
|
|
1537
1028
|
|
|
1538
1029
|
// src/exporter.ts
|
|
1539
1030
|
function nodeLayer(type) {
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
if (["message_broker", "queue", "topic"].includes(type)) return "messaging";
|
|
1544
|
-
if (["host", "container", "pod", "k8s_cluster"].includes(type)) return "infra";
|
|
1545
|
-
if (type === "config_file") return "config";
|
|
1031
|
+
for (const [layer, types] of Object.entries(NODE_TYPE_GROUPS)) {
|
|
1032
|
+
if (types.includes(type)) return layer;
|
|
1033
|
+
}
|
|
1546
1034
|
return "other";
|
|
1547
1035
|
}
|
|
1548
1036
|
var LAYER_LABELS = {
|
|
@@ -2824,7 +2312,7 @@ function exportJGF(nodes, edges) {
|
|
|
2824
2312
|
return JSON.stringify(jgf, null, 2);
|
|
2825
2313
|
}
|
|
2826
2314
|
function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml", "html", "map", "discovery"]) {
|
|
2827
|
-
|
|
2315
|
+
mkdirSync(outputDir, { recursive: true });
|
|
2828
2316
|
const nodes = db.getNodes(sessionId);
|
|
2829
2317
|
const edges = db.getEdges(sessionId);
|
|
2830
2318
|
const jgfPath = join2(outputDir, "cartography-graph.jgf.json");
|
|
@@ -2846,35 +2334,9 @@ function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml
|
|
|
2846
2334
|
|
|
2847
2335
|
// src/cli.ts
|
|
2848
2336
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
2849
|
-
import { resolve } from "path";
|
|
2337
|
+
import { resolve, dirname } from "path";
|
|
2338
|
+
import { fileURLToPath } from "url";
|
|
2850
2339
|
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
|
|
2878
2340
|
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
2879
2341
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
2880
2342
|
var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
@@ -2901,7 +2363,8 @@ function main() {
|
|
|
2901
2363
|
cleanupTempFiles();
|
|
2902
2364
|
const program = new Command();
|
|
2903
2365
|
const CMD = "datasynx-cartography";
|
|
2904
|
-
const
|
|
2366
|
+
const __dirname = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url));
|
|
2367
|
+
const { version: VERSION } = JSON.parse(readFileSync2(resolve(__dirname, "..", "package.json"), "utf-8"));
|
|
2905
2368
|
program.name(CMD).description("AI-powered Infrastructure Discovery & Agentic AI Cartography").version(VERSION);
|
|
2906
2369
|
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) => {
|
|
2907
2370
|
checkPrerequisites();
|
|
@@ -3065,7 +2528,8 @@ function main() {
|
|
|
3065
2528
|
await runDiscovery(config, db, sessionId, handleEvent, onAskUser, void 0);
|
|
3066
2529
|
} catch (err) {
|
|
3067
2530
|
stopSpinner();
|
|
3068
|
-
const
|
|
2531
|
+
const rawMsg = err instanceof Error ? err.message : String(err);
|
|
2532
|
+
const errMsg = rawMsg.replace(/https?:\/\/[^\s]+/g, (u) => stripSensitive(u));
|
|
3069
2533
|
logError("Discovery failed", { sessionId, error: errMsg });
|
|
3070
2534
|
w(`
|
|
3071
2535
|
${bold("\x1B[31m\u2717\x1B[0m")} Discovery failed: ${errMsg}
|
|
@@ -3277,7 +2741,7 @@ Session: ${session.id}
|
|
|
3277
2741
|
}
|
|
3278
2742
|
db.close();
|
|
3279
2743
|
});
|
|
3280
|
-
program.command("overview").description("Overview of all cartography sessions").option("--db <path>", "DB
|
|
2744
|
+
program.command("overview").description("Overview of all cartography sessions").option("--db <path>", "DB path").action((opts) => {
|
|
3281
2745
|
const config = defaultConfig();
|
|
3282
2746
|
const db = new CartographyDB(opts.db ?? config.dbPath);
|
|
3283
2747
|
const sessions = db.getSessions();
|
|
@@ -3328,7 +2792,7 @@ Session: ${session.id}
|
|
|
3328
2792
|
}
|
|
3329
2793
|
db.close();
|
|
3330
2794
|
});
|
|
3331
|
-
program.command("chat [session-id]").description("Interactive chat about your mapped infrastructure").option("--db <path>", "DB
|
|
2795
|
+
program.command("chat [session-id]").description("Interactive chat about your mapped infrastructure").option("--db <path>", "DB path").option("--model <m>", "Model", "claude-sonnet-4-5-20250929").action(async (sessionIdArg, opts) => {
|
|
3332
2796
|
const config = defaultConfig();
|
|
3333
2797
|
const db = new CartographyDB(opts.db ?? config.dbPath);
|
|
3334
2798
|
const sessions = db.getSessions();
|
|
@@ -3351,7 +2815,7 @@ Session: ${session.id}
|
|
|
3351
2815
|
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\u2500\u2500\u2500\u2500\u2500
|
|
3352
2816
|
`));
|
|
3353
2817
|
w(` ${dim("Ask anything about your infrastructure. exit = quit.\n\n")}`);
|
|
3354
|
-
const Anthropic = (await import("
|
|
2818
|
+
const Anthropic = (await import("./sdk-QSTAREST.js")).default;
|
|
3355
2819
|
const client = new Anthropic();
|
|
3356
2820
|
const infraSummary = JSON.stringify({
|
|
3357
2821
|
nodes: nodes.map((n) => ({
|
|
@@ -3541,7 +3005,7 @@ ${infraSummary.substring(0, 12e3)}`;
|
|
|
3541
3005
|
out("\n");
|
|
3542
3006
|
});
|
|
3543
3007
|
program.command("bookmarks").description("View all browser bookmarks (Chrome, Chromium, Edge, Brave, Vivaldi, Opera, Firefox)").action(async () => {
|
|
3544
|
-
const { scanAllBookmarks: scanAllBookmarks2 } = await import("./bookmarks-
|
|
3008
|
+
const { scanAllBookmarks: scanAllBookmarks2 } = await import("./bookmarks-VS56KVCO.js");
|
|
3545
3009
|
const out = (s) => process.stdout.write(s);
|
|
3546
3010
|
process.stderr.write(" Scanning bookmarks...\n\n");
|
|
3547
3011
|
const hosts = await scanAllBookmarks2();
|
|
@@ -3628,7 +3092,7 @@ ${infraSummary.substring(0, 12e3)}`;
|
|
|
3628
3092
|
`);
|
|
3629
3093
|
return;
|
|
3630
3094
|
}
|
|
3631
|
-
const { NODE_TYPES: NODE_TYPES2 } = await import("./types-
|
|
3095
|
+
const { NODE_TYPES: NODE_TYPES2 } = await import("./types-JG27FR3E.js");
|
|
3632
3096
|
if (!process.stdin.isTTY) {
|
|
3633
3097
|
w(red("\n \u2717 Interactive mode requires a terminal (use --file for non-interactive)\n\n"));
|
|
3634
3098
|
process.exitCode = 1;
|
|
@@ -3722,7 +3186,7 @@ ${infraSummary.substring(0, 12e3)}`;
|
|
|
3722
3186
|
if ((major ?? 0) >= 18) {
|
|
3723
3187
|
ok(`Node.js ${nodeVer}`);
|
|
3724
3188
|
} else {
|
|
3725
|
-
err(`Node.js ${nodeVer} \u2014
|
|
3189
|
+
err(`Node.js ${nodeVer} \u2014 requires >=18`);
|
|
3726
3190
|
allGood = false;
|
|
3727
3191
|
}
|
|
3728
3192
|
try {
|
|
@@ -3834,64 +3298,69 @@ ${infraSummary.substring(0, 12e3)}`;
|
|
|
3834
3298
|
}
|
|
3835
3299
|
db.close();
|
|
3836
3300
|
});
|
|
3301
|
+
program.command("mcp").description("Run the Model Context Protocol server (stdio by default) \u2014 the primary interface for AI agents").option("--http", "Use Streamable HTTP transport instead of stdio", false).option("--port <n>", "HTTP port", "3737").option("--host <h>", "HTTP host", "127.0.0.1").option("--db <path>", "DB path").option("--session <id>", 'Session to serve (id or "latest")', "latest").option("--no-semantic", "Disable semantic (vector) search").action(async (opts) => {
|
|
3302
|
+
await startMcp({
|
|
3303
|
+
transport: opts.http ? "http" : "stdio",
|
|
3304
|
+
port: parseInt(opts.port, 10),
|
|
3305
|
+
host: opts.host,
|
|
3306
|
+
dbPath: opts.db,
|
|
3307
|
+
session: opts.session,
|
|
3308
|
+
semantic: opts.semantic
|
|
3309
|
+
});
|
|
3310
|
+
});
|
|
3837
3311
|
const o = (s) => process.stderr.write(s);
|
|
3838
|
-
const _b = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
3839
|
-
const _d = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
3840
|
-
const _c = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
3841
|
-
const _g = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
3842
|
-
const _m = (s) => `\x1B[35m${s}\x1B[0m`;
|
|
3843
3312
|
o("\n");
|
|
3844
|
-
o(
|
|
3845
|
-
o(
|
|
3846
|
-
o(
|
|
3847
|
-
o(
|
|
3848
|
-
o(
|
|
3849
|
-
o(
|
|
3313
|
+
o(cyan(" ____ _ ____ ") + "\n");
|
|
3314
|
+
o(cyan(" | _ \\ __ _| |_ __ _/ ___| _ _ _ __ __ __") + "\n");
|
|
3315
|
+
o(cyan(" | | | |/ _` | __/ _` \\___ \\| | | | '_ \\\\ \\/ /") + "\n");
|
|
3316
|
+
o(cyan(" | |_| | (_| | || (_| |___) | |_| | | | |> < ") + "\n");
|
|
3317
|
+
o(cyan(" |____/ \\__,_|\\__\\__,_|____/ \\__, |_| |_/_/\\_\\") + "\n");
|
|
3318
|
+
o(cyan(" |___/ ") + "\n");
|
|
3850
3319
|
o("\n");
|
|
3851
|
-
o(
|
|
3852
|
-
o(
|
|
3853
|
-
o(
|
|
3320
|
+
o(bold(" Cartography") + " " + dim("v" + VERSION) + "\n");
|
|
3321
|
+
o(dim(" AI-powered Infrastructure Discovery & Agentic AI Cartography\n"));
|
|
3322
|
+
o(dim(" Autonomous infrastructure discovery \u2014 zero-config, provider-agnostic\n"));
|
|
3854
3323
|
o("\n");
|
|
3855
3324
|
if (process.argv.length <= 2) {
|
|
3856
|
-
o(
|
|
3325
|
+
o(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"));
|
|
3857
3326
|
o("\n");
|
|
3858
|
-
o(
|
|
3327
|
+
o(bold(" Commands:\n"));
|
|
3859
3328
|
o("\n");
|
|
3860
|
-
o(` ${
|
|
3329
|
+
o(` ${green("discover")} ${dim("Scan infrastructure (Claude Sonnet)")}
|
|
3861
3330
|
`);
|
|
3862
|
-
o(` ${
|
|
3331
|
+
o(` ${green("seed")} ${dim("Manually add known tools/DBs/APIs")}
|
|
3863
3332
|
`);
|
|
3864
|
-
o(` ${
|
|
3333
|
+
o(` ${green("bookmarks")} ${dim("View browser bookmarks")}
|
|
3865
3334
|
`);
|
|
3866
|
-
o(` ${
|
|
3335
|
+
o(` ${green("export")} ${dim("[session]")} ${dim("Export Mermaid, JSON, YAML, HTML")}
|
|
3867
3336
|
`);
|
|
3868
|
-
o(` ${
|
|
3337
|
+
o(` ${green("show")} ${dim("[session]")} ${dim("Show session details")}
|
|
3869
3338
|
`);
|
|
3870
|
-
o(` ${
|
|
3339
|
+
o(` ${green("sessions")} ${dim("List all sessions")}
|
|
3871
3340
|
`);
|
|
3872
|
-
o(` ${
|
|
3341
|
+
o(` ${green("doctor")} ${dim("Check requirements (kubectl, aws, gcloud, az)")}
|
|
3873
3342
|
`);
|
|
3874
|
-
o(` ${
|
|
3343
|
+
o(` ${green("docs")} ${dim("Full feature reference")}
|
|
3875
3344
|
`);
|
|
3876
3345
|
o("\n");
|
|
3877
|
-
o(
|
|
3346
|
+
o(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"));
|
|
3878
3347
|
o("\n");
|
|
3879
|
-
o(
|
|
3348
|
+
o(bold(" Quick Start:\n"));
|
|
3880
3349
|
o("\n");
|
|
3881
|
-
o(` ${
|
|
3350
|
+
o(` ${magenta("$")} ${bold("datasynx-cartography doctor")} ${dim("Check requirements")}
|
|
3882
3351
|
`);
|
|
3883
|
-
o(` ${
|
|
3352
|
+
o(` ${magenta("$")} ${bold("datasynx-cartography seed")} ${dim("Add known infrastructure")}
|
|
3884
3353
|
`);
|
|
3885
|
-
o(` ${
|
|
3354
|
+
o(` ${magenta("$")} ${bold("datasynx-cartography discover")} ${dim("One-time scan")}
|
|
3886
3355
|
`);
|
|
3887
3356
|
o("\n");
|
|
3888
|
-
o(
|
|
3889
|
-
o(
|
|
3890
|
-
o(
|
|
3357
|
+
o(dim(" Docs: datasynx-cartography docs\n"));
|
|
3358
|
+
o(dim(" Help: datasynx-cartography --help\n"));
|
|
3359
|
+
o(dim(" npm: @datasynx/agentic-ai-cartography\n"));
|
|
3891
3360
|
o("\n");
|
|
3892
3361
|
return;
|
|
3893
3362
|
}
|
|
3894
|
-
o(
|
|
3363
|
+
o(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"));
|
|
3895
3364
|
o("\n");
|
|
3896
3365
|
program.exitOverride((err) => {
|
|
3897
3366
|
if (err.code === "commander.helpDisplayed") {
|