@datasynx/agentic-ai-cartography 2.2.0 → 2.4.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/api-bin.js +24 -0
- package/dist/api-bin.js.map +1 -0
- package/dist/chunk-B4QWX7CP.js +2618 -0
- package/dist/chunk-B4QWX7CP.js.map +1 -0
- package/dist/chunk-L4OSL7I6.js +1134 -0
- package/dist/chunk-L4OSL7I6.js.map +1 -0
- package/dist/{chunk-WCR47QA2.js → chunk-QQOQBE2A.js} +16 -5
- package/dist/chunk-QQOQBE2A.js.map +1 -0
- package/dist/{chunk-BNDCY2RI.js → chunk-X5JA2UDT.js} +60 -2445
- package/dist/chunk-X5JA2UDT.js.map +1 -0
- package/dist/cli.js +36 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1522 -156
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +450 -10
- package/dist/index.d.ts +450 -10
- package/dist/index.js +1449 -114
- package/dist/index.js.map +1 -1
- package/dist/mcp-bin.js +3 -2
- package/dist/mcp-bin.js.map +1 -1
- package/dist/{types-TJWXAQ2L.js → types-5L3AGZLG.js} +2 -2
- package/package.json +8 -5
- package/scripts/gen-api-schemas.ts +29 -0
- package/scripts/sync-version.mjs +51 -0
- package/server.json +2 -2
- package/dist/chunk-BNDCY2RI.js.map +0 -1
- package/dist/chunk-WCR47QA2.js.map +0 -1
- /package/dist/{types-TJWXAQ2L.js.map → types-5L3AGZLG.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -45,37 +45,51 @@ __export(src_exports, {
|
|
|
45
45
|
DriftConfigSchema: () => DriftConfigSchema,
|
|
46
46
|
INGEST_SCHEMA_VERSION: () => INGEST_SCHEMA_VERSION,
|
|
47
47
|
IngestEnvelopeSchema: () => IngestEnvelopeSchema,
|
|
48
|
+
InvalidTenantError: () => InvalidTenantError,
|
|
49
|
+
JiraSink: () => JiraSink,
|
|
50
|
+
LOOPBACK_HOSTS: () => LOOPBACK_HOSTS2,
|
|
48
51
|
MCP_BIN: () => MCP_BIN,
|
|
52
|
+
NotFoundError: () => NotFoundError,
|
|
49
53
|
PACKAGE_NAME: () => PACKAGE_NAME,
|
|
54
|
+
PAGERDUTY_ENQUEUE_URL: () => PAGERDUTY_ENQUEUE_URL,
|
|
50
55
|
PERSONAL: () => PERSONAL,
|
|
51
56
|
PORT_MAP: () => PORT_MAP,
|
|
52
57
|
PRIVATE_IP: () => PRIVATE_IP,
|
|
53
58
|
PUSH_SCHEMA_VERSION: () => PUSH_SCHEMA_VERSION,
|
|
59
|
+
PagerDutySink: () => PagerDutySink,
|
|
54
60
|
ProviderRegistry: () => ProviderRegistry,
|
|
55
61
|
RELATION_TO_DIRECTION: () => RELATION_TO_DIRECTION,
|
|
56
62
|
RuleCheckSchema: () => RuleCheckSchema,
|
|
57
63
|
RulesetSchema: () => RulesetSchema,
|
|
58
64
|
SCAN_ARG_PATTERNS: () => SCAN_ARG_PATTERNS,
|
|
65
|
+
SDL: () => SDL,
|
|
59
66
|
SEVERITY_WEIGHT: () => SEVERITY_WEIGHT,
|
|
60
67
|
SHARING_LEVELS: () => SHARING_LEVELS,
|
|
61
68
|
ScannerRegistry: () => ScannerRegistry,
|
|
62
69
|
ScannerShape: () => ScannerShape,
|
|
63
70
|
SharingLevelSchema: () => SharingLevelSchema,
|
|
71
|
+
SlackSink: () => SlackSink,
|
|
72
|
+
SqliteQueryBackend: () => SqliteQueryBackend,
|
|
64
73
|
SqliteStoreBackend: () => SqliteStoreBackend,
|
|
65
74
|
StdoutSink: () => StdoutSink,
|
|
75
|
+
TENANT_HEADER: () => TENANT_HEADER,
|
|
66
76
|
VectorStore: () => VectorStore,
|
|
67
77
|
WebhookSink: () => WebhookSink,
|
|
68
78
|
applyInstall: () => applyInstall,
|
|
69
79
|
applySharingLevel: () => applySharingLevel,
|
|
70
80
|
assertReadOnly: () => assertReadOnly,
|
|
81
|
+
assertSafeBind: () => assertSafeBind,
|
|
71
82
|
assertSafeScanArg: () => assertSafeScanArg,
|
|
72
83
|
assignColors: () => assignColors,
|
|
84
|
+
bearerToken: () => bearerToken,
|
|
73
85
|
bookmarksScanner: () => bookmarksScanner,
|
|
74
86
|
buildCartographyToolHandlers: () => buildCartographyToolHandlers,
|
|
75
87
|
buildMapData: () => buildMapData,
|
|
88
|
+
buildOpenApiDocument: () => buildOpenApiDocument,
|
|
76
89
|
buildReport: () => buildReport,
|
|
77
90
|
buildSinks: () => buildSinks,
|
|
78
91
|
centralDbFromEnv: () => centralDbFromEnv,
|
|
92
|
+
checkBearer: () => checkBearer,
|
|
79
93
|
checkPrerequisites: () => checkPrerequisites,
|
|
80
94
|
checkReadOnly: () => checkReadOnly,
|
|
81
95
|
clampText: () => clampText,
|
|
@@ -103,10 +117,12 @@ __export(src_exports, {
|
|
|
103
117
|
createOpenAIProvider: () => createOpenAIProvider,
|
|
104
118
|
createScanRunner: () => createScanRunner,
|
|
105
119
|
createSemanticSearch: () => createSemanticSearch,
|
|
120
|
+
createSqliteQueryBackend: () => createSqliteQueryBackend,
|
|
106
121
|
currentOs: () => currentOs,
|
|
107
122
|
cursorDeeplink: () => cursorDeeplink,
|
|
108
123
|
databasesScanner: () => databasesScanner,
|
|
109
124
|
deepMerge: () => deepMerge,
|
|
125
|
+
defaultAllowedHosts: () => defaultAllowedHosts,
|
|
110
126
|
defaultConfig: () => defaultConfig,
|
|
111
127
|
defaultContext: () => defaultContext,
|
|
112
128
|
defaultProviderRegistry: () => defaultProviderRegistry,
|
|
@@ -123,6 +139,7 @@ __export(src_exports, {
|
|
|
123
139
|
evaluateCheck: () => evaluateCheck,
|
|
124
140
|
evaluateRule: () => evaluateRule,
|
|
125
141
|
evidenceLine: () => evidenceLine,
|
|
142
|
+
executeGraphql: () => executeGraphql,
|
|
126
143
|
executeNlQuery: () => executeNlQuery,
|
|
127
144
|
exportAll: () => exportAll,
|
|
128
145
|
exportBackstageYAML: () => exportBackstageYAML,
|
|
@@ -136,6 +153,9 @@ __export(src_exports, {
|
|
|
136
153
|
filterBySeverity: () => filterBySeverity,
|
|
137
154
|
findAnonViolations: () => findAnonViolations,
|
|
138
155
|
formatComplianceText: () => formatComplianceText,
|
|
156
|
+
formatJira: () => formatJira,
|
|
157
|
+
formatPagerDuty: () => formatPagerDuty,
|
|
158
|
+
formatSlack: () => formatSlack,
|
|
139
159
|
generateDependencyMermaid: () => generateDependencyMermaid,
|
|
140
160
|
generateDiffMermaid: () => generateDiffMermaid,
|
|
141
161
|
generateTopologyMermaid: () => generateTopologyMermaid,
|
|
@@ -143,6 +163,7 @@ __export(src_exports, {
|
|
|
143
163
|
getRuleset: () => getRuleset,
|
|
144
164
|
globalId: () => globalId,
|
|
145
165
|
groupByDomain: () => groupByDomain,
|
|
166
|
+
handleGraphqlGet: () => handleGraphqlGet,
|
|
146
167
|
hexCorners: () => hexCorners,
|
|
147
168
|
hexDistance: () => hexDistance,
|
|
148
169
|
hexNeighbors: () => hexNeighbors,
|
|
@@ -153,9 +174,11 @@ __export(src_exports, {
|
|
|
153
174
|
hostname: () => hostname,
|
|
154
175
|
ingestEnvelope: () => ingestEnvelope,
|
|
155
176
|
installedAppsScanner: () => installedAppsScanner,
|
|
177
|
+
isLoopbackHost: () => isLoopbackHost,
|
|
156
178
|
isPersonalHost: () => isPersonalHost,
|
|
157
179
|
isReadOnlyCommand: () => isReadOnlyCommand,
|
|
158
180
|
isRemembered: () => isRemembered,
|
|
181
|
+
isSecureWebhookUrl: () => isSecureWebhookUrl,
|
|
159
182
|
k8sScanner: () => k8sScanner,
|
|
160
183
|
keyMetaOf: () => keyMetaOf,
|
|
161
184
|
layoutClusters: () => layoutClusters,
|
|
@@ -181,6 +204,7 @@ __export(src_exports, {
|
|
|
181
204
|
normalizeTenant: () => normalizeTenant,
|
|
182
205
|
orgKeyPath: () => orgKeyPath,
|
|
183
206
|
osUser: () => osUser,
|
|
207
|
+
parseApiArgs: () => parseApiArgs,
|
|
184
208
|
parseComposeDeps: () => parseComposeDeps,
|
|
185
209
|
parseConfig: () => parseConfig,
|
|
186
210
|
parseConnectionString: () => parseConnectionString,
|
|
@@ -193,6 +217,7 @@ __export(src_exports, {
|
|
|
193
217
|
pixelToHex: () => pixelToHex,
|
|
194
218
|
planInstall: () => planInstall,
|
|
195
219
|
portsScanner: () => portsScanner,
|
|
220
|
+
postJson: () => postJson,
|
|
196
221
|
previewShare: () => previewShare,
|
|
197
222
|
pseudonymize: () => pseudonymize,
|
|
198
223
|
pseudonymizeFragment: () => pseudonymizeFragment,
|
|
@@ -206,10 +231,12 @@ __export(src_exports, {
|
|
|
206
231
|
resolveEffectiveLevel: () => resolveEffectiveLevel,
|
|
207
232
|
resolveNlQuery: () => resolveNlQuery,
|
|
208
233
|
resolveSharingLevel: () => resolveSharingLevel,
|
|
234
|
+
resolveTenant: () => resolveTenant,
|
|
209
235
|
revalidateAnonymized: () => revalidateAnonymized,
|
|
210
236
|
reversalKey: () => reversalKey,
|
|
211
237
|
reversePseudonym: () => reversePseudonym,
|
|
212
238
|
rotateOrgKey: () => rotateOrgKey,
|
|
239
|
+
runApi: () => runApi,
|
|
213
240
|
runDiscovery: () => runDiscovery,
|
|
214
241
|
runDrift: () => runDrift,
|
|
215
242
|
runHttp: () => runHttp,
|
|
@@ -232,9 +259,12 @@ __export(src_exports, {
|
|
|
232
259
|
shareHash: () => shareHash,
|
|
233
260
|
splitSegments: () => splitSegments,
|
|
234
261
|
stableStringify: () => stableStringify,
|
|
262
|
+
startApi: () => startApi,
|
|
235
263
|
stripSensitive: () => stripSensitive,
|
|
264
|
+
timingSafeEqual: () => timingSafeEqual,
|
|
236
265
|
validateScanner: () => validateScanner,
|
|
237
|
-
vscodeDeeplink: () => vscodeDeeplink
|
|
266
|
+
vscodeDeeplink: () => vscodeDeeplink,
|
|
267
|
+
zodToJsonSchema: () => zodToJsonSchema
|
|
238
268
|
});
|
|
239
269
|
module.exports = __toCommonJS(src_exports);
|
|
240
270
|
|
|
@@ -370,6 +400,8 @@ var DOMAIN_PALETTE = [
|
|
|
370
400
|
"#5eead4"
|
|
371
401
|
];
|
|
372
402
|
var DRIFT_FIELDS = ["type", "name", "domain", "subDomain", "qualityScore", "metadata", "tags", "owner", "cost"];
|
|
403
|
+
var ANOMALY_KINDS = ["orphan", "shadow-it"];
|
|
404
|
+
var ANOMALY_SEVERITIES = ["low", "medium", "high"];
|
|
373
405
|
var DEFAULT_ANOMALY_THRESHOLDS = {
|
|
374
406
|
orphanWeakDegree: 1,
|
|
375
407
|
shadowConfidence: 0.4,
|
|
@@ -394,15 +426,26 @@ var SECURITY_METADATA_KEYS = [
|
|
|
394
426
|
var DriftConfigSchema = import_zod.z.object({
|
|
395
427
|
minSeverity: import_zod.z.enum(SEVERITIES).default("info"),
|
|
396
428
|
sinks: import_zod.z.array(import_zod.z.object({
|
|
397
|
-
type: import_zod.z.enum(["stdout", "webhook"]),
|
|
429
|
+
type: import_zod.z.enum(["stdout", "webhook", "slack", "pagerduty", "jira"]),
|
|
398
430
|
url: import_zod.z.string().url().optional(),
|
|
399
431
|
token: import_zod.z.string().optional(),
|
|
400
|
-
timeoutMs: import_zod.z.number().int().positive().optional()
|
|
432
|
+
timeoutMs: import_zod.z.number().int().positive().optional(),
|
|
433
|
+
routingKey: import_zod.z.string().optional(),
|
|
434
|
+
email: import_zod.z.string().optional(),
|
|
435
|
+
project: import_zod.z.string().optional(),
|
|
436
|
+
issueType: import_zod.z.string().optional()
|
|
401
437
|
})).default([{ type: "stdout" }])
|
|
402
438
|
}).superRefine((cfg, ctx) => {
|
|
403
439
|
for (const [i, s] of cfg.sinks.entries()) {
|
|
404
|
-
|
|
405
|
-
ctx.addIssue({ code: "custom", path: ["sinks", i, "url"], message:
|
|
440
|
+
const requireUrl = (msg) => {
|
|
441
|
+
if (!s.url) ctx.addIssue({ code: "custom", path: ["sinks", i, "url"], message: msg });
|
|
442
|
+
};
|
|
443
|
+
if (s.type === "webhook") requireUrl("webhook sink requires a url");
|
|
444
|
+
if (s.type === "slack") requireUrl("slack sink requires a webhook url");
|
|
445
|
+
if (s.type === "jira") {
|
|
446
|
+
requireUrl("jira sink requires a base url");
|
|
447
|
+
if (!s.email) ctx.addIssue({ code: "custom", path: ["sinks", i, "email"], message: "jira sink requires an email" });
|
|
448
|
+
if (!s.project) ctx.addIssue({ code: "custom", path: ["sinks", i, "project"], message: "jira sink requires a project key" });
|
|
406
449
|
}
|
|
407
450
|
}
|
|
408
451
|
});
|
|
@@ -1515,8 +1558,8 @@ var cloudGcpScanner = {
|
|
|
1515
1558
|
allowedCommands: ["gcloud"],
|
|
1516
1559
|
detect: (ctx) => Boolean((ctx.commandExists ?? commandExists)("gcloud")),
|
|
1517
1560
|
async scan(ctx) {
|
|
1518
|
-
const { project } = parseScanHint(ctx.hint);
|
|
1519
|
-
const pf =
|
|
1561
|
+
const { project: project2 } = parseScanHint(ctx.hint);
|
|
1562
|
+
const pf = project2 ? ` --project ${project2}` : "";
|
|
1520
1563
|
const runG = createScanRunner((c) => ctx.run(c, { timeout: 2e4 }), { threshold: 3 });
|
|
1521
1564
|
const nodes = [];
|
|
1522
1565
|
const edges = [];
|
|
@@ -2016,8 +2059,17 @@ function stripSensitive(target) {
|
|
|
2016
2059
|
const stripped = `${url.hostname}${url.port ? ":" + url.port : ""}`;
|
|
2017
2060
|
return stripped || raw;
|
|
2018
2061
|
} catch {
|
|
2019
|
-
|
|
2020
|
-
|
|
2062
|
+
let s = raw;
|
|
2063
|
+
const slash = s.indexOf("/");
|
|
2064
|
+
if (slash >= 0) s = s.slice(0, slash);
|
|
2065
|
+
const q = s.indexOf("?");
|
|
2066
|
+
if (q >= 0) s = s.slice(0, q);
|
|
2067
|
+
const at = s.indexOf("@");
|
|
2068
|
+
if (at >= 0) {
|
|
2069
|
+
const colon = s.lastIndexOf(":");
|
|
2070
|
+
if (colon > at) s = s.slice(0, at) + ":" + s.slice(colon + 1);
|
|
2071
|
+
}
|
|
2072
|
+
return s || raw;
|
|
2021
2073
|
}
|
|
2022
2074
|
}
|
|
2023
2075
|
var SCAN_ARG_PATTERNS = {
|
|
@@ -2035,7 +2087,7 @@ function assertSafeScanArg(kind, value) {
|
|
|
2035
2087
|
return value;
|
|
2036
2088
|
}
|
|
2037
2089
|
function redactSecrets(value) {
|
|
2038
|
-
return value.replace(/([a-z][a-z0-9+.-]
|
|
2090
|
+
return value.replace(/([a-z][a-z0-9+.-]{0,63}:\/\/[^:@/\s]{1,256}):[^@/\s]{1,256}@/gi, "$1:***@");
|
|
2039
2091
|
}
|
|
2040
2092
|
function redactValue(value) {
|
|
2041
2093
|
if (typeof value === "string") return redactSecrets(value);
|
|
@@ -2204,9 +2256,9 @@ async function buildCartographyToolHandlers(db, sessionId, opts = {}) {
|
|
|
2204
2256
|
tool("scan_gcp_resources", "Scan Google Cloud Platform via gcloud CLI \u2014 100% readonly (list, describe)", {
|
|
2205
2257
|
project: import_zod2.z.string().regex(SCAN_ARG_PATTERNS["gcp-project"], "invalid GCP project id").optional().describe("GCP Project ID \u2014 default: current gcloud project")
|
|
2206
2258
|
}, async (args) => {
|
|
2207
|
-
const
|
|
2208
|
-
if (
|
|
2209
|
-
return runScannerTool(cloudGcpScanner,
|
|
2259
|
+
const project2 = args["project"];
|
|
2260
|
+
if (project2) assertSafeScanArg("gcp-project", project2);
|
|
2261
|
+
return runScannerTool(cloudGcpScanner, project2 ? `project=${project2}` : "");
|
|
2210
2262
|
}, { annotations: READ_SCAN }),
|
|
2211
2263
|
tool("scan_azure_resources", "Scan Azure infrastructure via az CLI \u2014 100% readonly (list, show)", {
|
|
2212
2264
|
subscription: import_zod2.z.string().regex(SCAN_ARG_PATTERNS["azure-subscription"], "invalid Azure subscription id").optional().describe("Azure Subscription ID"),
|
|
@@ -2358,14 +2410,14 @@ async function buildCartographyToolHandlers(db, sessionId, opts = {}) {
|
|
|
2358
2410
|
"neon"
|
|
2359
2411
|
];
|
|
2360
2412
|
const found = [];
|
|
2361
|
-
const
|
|
2413
|
+
const notFound2 = [];
|
|
2362
2414
|
for (const t of knownTools) {
|
|
2363
2415
|
const r = commandExists(t);
|
|
2364
2416
|
if (r) found.push(`${t}: ${r}`);
|
|
2365
|
-
else
|
|
2417
|
+
else notFound2.push(t);
|
|
2366
2418
|
}
|
|
2367
2419
|
results["TOOLS_FOUND"] = found.join("\n") || "(none found)";
|
|
2368
|
-
results["TOOLS_NOT_FOUND"] =
|
|
2420
|
+
results["TOOLS_NOT_FOUND"] = notFound2.join(", ");
|
|
2369
2421
|
if (hint) {
|
|
2370
2422
|
const terms = hint.split(/[\s,]+/).filter(Boolean);
|
|
2371
2423
|
const hintResults = [];
|
|
@@ -4502,6 +4554,86 @@ var SqliteStoreBackend = class {
|
|
|
4502
4554
|
}
|
|
4503
4555
|
};
|
|
4504
4556
|
|
|
4557
|
+
// src/store/query.ts
|
|
4558
|
+
var NotFoundError = class extends Error {
|
|
4559
|
+
constructor(message) {
|
|
4560
|
+
super(message);
|
|
4561
|
+
this.name = "NotFoundError";
|
|
4562
|
+
}
|
|
4563
|
+
};
|
|
4564
|
+
var MAX_NODE_LIMIT = 1e3;
|
|
4565
|
+
var MAX_DEPTH = 64;
|
|
4566
|
+
function clamp(value, min, max) {
|
|
4567
|
+
return Math.floor(Math.max(min, Math.min(value, max)));
|
|
4568
|
+
}
|
|
4569
|
+
var SqliteQueryBackend = class {
|
|
4570
|
+
constructor(db, defaultSession = "latest") {
|
|
4571
|
+
this.db = db;
|
|
4572
|
+
this.defaultSession = defaultSession;
|
|
4573
|
+
}
|
|
4574
|
+
/**
|
|
4575
|
+
* Resolve the session id for a request, scoped to `ctx.tenant`. An explicit id must
|
|
4576
|
+
* belong to the tenant or it resolves to undefined (cross-tenant isolation); else the
|
|
4577
|
+
* newest `discover` session for the tenant. Mirrors `resolveSession` in the MCP server.
|
|
4578
|
+
*/
|
|
4579
|
+
resolveSession(ctx, sessionId) {
|
|
4580
|
+
const requested = sessionId ?? (this.defaultSession === "latest" ? void 0 : this.defaultSession);
|
|
4581
|
+
if (requested) {
|
|
4582
|
+
const s = this.db.getSession(requested);
|
|
4583
|
+
if (s && s.tenant === ctx.tenant) return s.id;
|
|
4584
|
+
throw new NotFoundError(`session not found`);
|
|
4585
|
+
}
|
|
4586
|
+
const latest = this.db.getLatestSession("discover", ctx.tenant) ?? this.db.getLatestSession(void 0, ctx.tenant);
|
|
4587
|
+
if (!latest) throw new NotFoundError(`no session available`);
|
|
4588
|
+
return latest.id;
|
|
4589
|
+
}
|
|
4590
|
+
summary(ctx, sessionId) {
|
|
4591
|
+
return this.db.getGraphSummary(this.resolveSession(ctx, sessionId));
|
|
4592
|
+
}
|
|
4593
|
+
nodes(ctx, q, sessionId) {
|
|
4594
|
+
const sid = this.resolveSession(ctx, sessionId);
|
|
4595
|
+
const limit = clamp(q.limit ?? 100, 1, MAX_NODE_LIMIT);
|
|
4596
|
+
const offset = Math.floor(Math.max(0, q.offset ?? 0));
|
|
4597
|
+
const total = this.db.getNodeCount(sid);
|
|
4598
|
+
if (q.search) {
|
|
4599
|
+
const nodes2 = this.db.searchNodes(sid, q.search, { ...q.types ? { types: q.types } : {}, limit });
|
|
4600
|
+
return { nodes: nodes2, total: nodes2.length, limit, offset: 0 };
|
|
4601
|
+
}
|
|
4602
|
+
const nodes = this.db.getNodes(sid, { limit, offset });
|
|
4603
|
+
return { nodes, total, limit, offset };
|
|
4604
|
+
}
|
|
4605
|
+
node(ctx, id, sessionId) {
|
|
4606
|
+
return this.db.getNode(this.resolveSession(ctx, sessionId), id);
|
|
4607
|
+
}
|
|
4608
|
+
dependencies(ctx, id, q, sessionId) {
|
|
4609
|
+
const sid = this.resolveSession(ctx, sessionId);
|
|
4610
|
+
return this.db.getDependencies(sid, id, {
|
|
4611
|
+
direction: q.direction ?? "downstream",
|
|
4612
|
+
maxDepth: clamp(q.maxDepth ?? 8, 1, MAX_DEPTH)
|
|
4613
|
+
});
|
|
4614
|
+
}
|
|
4615
|
+
diff(ctx, base, current) {
|
|
4616
|
+
for (const id of [base, current]) {
|
|
4617
|
+
const s = this.db.getSession(id);
|
|
4618
|
+
if (!s || s.tenant !== ctx.tenant) throw new NotFoundError(`session not found`);
|
|
4619
|
+
}
|
|
4620
|
+
try {
|
|
4621
|
+
return this.db.diffSessions(base, current);
|
|
4622
|
+
} catch (err) {
|
|
4623
|
+
throw new NotFoundError(err instanceof Error ? err.message : "diff failed");
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
sessions(ctx) {
|
|
4627
|
+
return this.db.getSessions(ctx.tenant);
|
|
4628
|
+
}
|
|
4629
|
+
health(ctx) {
|
|
4630
|
+
return { store: "sqlite", sessions: this.db.getSessions(ctx.tenant).length };
|
|
4631
|
+
}
|
|
4632
|
+
};
|
|
4633
|
+
function createSqliteQueryBackend(db, defaultSession = "latest") {
|
|
4634
|
+
return new SqliteQueryBackend(db, defaultSession);
|
|
4635
|
+
}
|
|
4636
|
+
|
|
4505
4637
|
// src/central/merge.ts
|
|
4506
4638
|
function computeIdentity(org, node) {
|
|
4507
4639
|
return {
|
|
@@ -5053,6 +5185,41 @@ var StdoutSink = class {
|
|
|
5053
5185
|
|
|
5054
5186
|
// src/sinks/webhook.ts
|
|
5055
5187
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
5188
|
+
async function postJson(opts) {
|
|
5189
|
+
const doFetch = opts.fetchImpl ?? (typeof fetch === "function" ? fetch : void 0);
|
|
5190
|
+
if (!doFetch) {
|
|
5191
|
+
logWarn("sink unavailable: global fetch missing", { sink: opts.sinkName });
|
|
5192
|
+
return;
|
|
5193
|
+
}
|
|
5194
|
+
if (!opts.url) {
|
|
5195
|
+
logWarn("sink unavailable: no url configured", { sink: opts.sinkName });
|
|
5196
|
+
return;
|
|
5197
|
+
}
|
|
5198
|
+
if (!isSecureWebhookUrl(opts.url)) {
|
|
5199
|
+
logWarn("sink refused: insecure scheme (use https:// or a loopback host)", {
|
|
5200
|
+
sink: opts.sinkName,
|
|
5201
|
+
host: stripSensitive(opts.url)
|
|
5202
|
+
});
|
|
5203
|
+
return;
|
|
5204
|
+
}
|
|
5205
|
+
try {
|
|
5206
|
+
const res = await doFetch(opts.url, {
|
|
5207
|
+
method: "POST",
|
|
5208
|
+
headers: { "content-type": "application/json", ...opts.headers ?? {} },
|
|
5209
|
+
body: JSON.stringify(opts.body),
|
|
5210
|
+
signal: AbortSignal.timeout(opts.timeoutMs ?? 1e4)
|
|
5211
|
+
});
|
|
5212
|
+
if (!res.ok) {
|
|
5213
|
+
logError("sink delivery failed", { sink: opts.sinkName, host: stripSensitive(opts.url), status: res.status });
|
|
5214
|
+
}
|
|
5215
|
+
} catch (err) {
|
|
5216
|
+
logError("sink delivery failed", {
|
|
5217
|
+
sink: opts.sinkName,
|
|
5218
|
+
host: stripSensitive(opts.url),
|
|
5219
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
5220
|
+
});
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5056
5223
|
function isSecureWebhookUrl(url, env = process.env) {
|
|
5057
5224
|
if (env.CARTOGRAPHY_ALLOW_INSECURE_SYNC === "1") return true;
|
|
5058
5225
|
let parsed;
|
|
@@ -5071,59 +5238,177 @@ var WebhookSink = class {
|
|
|
5071
5238
|
}
|
|
5072
5239
|
name = "webhook";
|
|
5073
5240
|
async emit(alert) {
|
|
5074
|
-
if (typeof fetch !== "function") {
|
|
5075
|
-
logWarn("webhook sink unavailable: global fetch missing", { sink: this.name });
|
|
5076
|
-
return;
|
|
5077
|
-
}
|
|
5078
5241
|
const { url, token, timeoutMs } = this.opts;
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5242
|
+
await postJson({
|
|
5243
|
+
url,
|
|
5244
|
+
body: redactValue(alert),
|
|
5245
|
+
...token ? { headers: { authorization: `Bearer ${token}` } } : {},
|
|
5246
|
+
...timeoutMs !== void 0 ? { timeoutMs } : {},
|
|
5247
|
+
sinkName: this.name
|
|
5248
|
+
});
|
|
5249
|
+
}
|
|
5250
|
+
};
|
|
5251
|
+
|
|
5252
|
+
// src/sinks/providers.ts
|
|
5253
|
+
var MAX_ITEMS2 = 20;
|
|
5254
|
+
var SEVERITY_EMOJI = { info: "\u{1F7E2}", warning: "\u{1F7E1}", critical: "\u{1F534}" };
|
|
5255
|
+
function headline(alert) {
|
|
5256
|
+
const s = alert.summary;
|
|
5257
|
+
return `${s.nodesAdded}+ / ${s.nodesRemoved}- / ${s.nodesChanged}~ nodes, ${s.edgesAdded}+ / ${s.edgesRemoved}- edges`;
|
|
5258
|
+
}
|
|
5259
|
+
function itemLine(it) {
|
|
5260
|
+
const sec = it.securityFields?.length ? ` [security: ${it.securityFields.join(", ")}]` : "";
|
|
5261
|
+
const fields = it.changedFields?.length ? ` (${it.changedFields.join(", ")})` : "";
|
|
5262
|
+
return `${it.severity.toUpperCase()} \xB7 ${it.kind} \xB7 ${it.label}${fields}${sec}`;
|
|
5263
|
+
}
|
|
5264
|
+
function bodyText(alert) {
|
|
5265
|
+
const lines = alert.items.slice(0, MAX_ITEMS2).map(itemLine);
|
|
5266
|
+
const more = alert.items.length > MAX_ITEMS2 ? [`\u2026and ${alert.items.length - MAX_ITEMS2} more`] : [];
|
|
5267
|
+
return [headline(alert), "", ...lines, ...more].join("\n");
|
|
5268
|
+
}
|
|
5269
|
+
function formatSlack(alert) {
|
|
5270
|
+
const title = `${SEVERITY_EMOJI[alert.severity]} Topology drift \u2014 ${alert.severity}`;
|
|
5271
|
+
return {
|
|
5272
|
+
text: `${title}: ${headline(alert)}`,
|
|
5273
|
+
blocks: [
|
|
5274
|
+
{ type: "header", text: { type: "plain_text", text: title, emoji: true } },
|
|
5275
|
+
{ type: "section", text: { type: "mrkdwn", text: "```" + bodyText(alert) + "```" } },
|
|
5276
|
+
{ type: "context", elements: [{ type: "mrkdwn", text: `base ${alert.base.sessionId} \u2192 current ${alert.current.sessionId} \xB7 ${alert.generatedAt}` }] }
|
|
5277
|
+
]
|
|
5278
|
+
};
|
|
5279
|
+
}
|
|
5280
|
+
var PD_SEVERITY = {
|
|
5281
|
+
info: "info",
|
|
5282
|
+
warning: "warning",
|
|
5283
|
+
critical: "critical"
|
|
5284
|
+
};
|
|
5285
|
+
function formatPagerDuty(alert, routingKey) {
|
|
5286
|
+
return {
|
|
5287
|
+
routing_key: routingKey,
|
|
5288
|
+
event_action: "trigger",
|
|
5289
|
+
// Stable per base→current pair so repeated alerts for the same delta de-duplicate.
|
|
5290
|
+
dedup_key: `cartograph-drift:${alert.base.sessionId}:${alert.current.sessionId}`,
|
|
5291
|
+
payload: {
|
|
5292
|
+
summary: `Cartograph topology drift (${alert.severity}): ${headline(alert)}`,
|
|
5293
|
+
source: "cartograph",
|
|
5294
|
+
severity: PD_SEVERITY[alert.severity],
|
|
5295
|
+
timestamp: alert.generatedAt,
|
|
5296
|
+
custom_details: {
|
|
5297
|
+
summary: alert.summary,
|
|
5298
|
+
items: alert.items.slice(0, MAX_ITEMS2).map((it) => ({
|
|
5299
|
+
kind: it.kind,
|
|
5300
|
+
ref: it.ref,
|
|
5301
|
+
severity: it.severity,
|
|
5302
|
+
...it.changedFields ? { changedFields: it.changedFields } : {},
|
|
5303
|
+
...it.securityFields ? { securityFields: it.securityFields } : {}
|
|
5304
|
+
}))
|
|
5102
5305
|
}
|
|
5103
|
-
} catch (err) {
|
|
5104
|
-
logError("webhook sink failed", {
|
|
5105
|
-
sink: this.name,
|
|
5106
|
-
host: stripSensitive(url),
|
|
5107
|
-
reason: err instanceof Error ? err.message : String(err)
|
|
5108
|
-
});
|
|
5109
5306
|
}
|
|
5307
|
+
};
|
|
5308
|
+
}
|
|
5309
|
+
function formatJira(alert, opts) {
|
|
5310
|
+
return {
|
|
5311
|
+
fields: {
|
|
5312
|
+
project: { key: opts.project },
|
|
5313
|
+
issuetype: { name: opts.issueType ?? "Task" },
|
|
5314
|
+
summary: `Cartograph topology drift (${alert.severity}): ${headline(alert)}`,
|
|
5315
|
+
description: bodyText(alert) + `
|
|
5316
|
+
|
|
5317
|
+
base ${alert.base.sessionId} \u2192 current ${alert.current.sessionId}
|
|
5318
|
+
generated ${alert.generatedAt}`
|
|
5319
|
+
}
|
|
5320
|
+
};
|
|
5321
|
+
}
|
|
5322
|
+
|
|
5323
|
+
// src/sinks/provider-sink.ts
|
|
5324
|
+
var PAGERDUTY_ENQUEUE_URL = "https://events.pagerduty.com/v2/enqueue";
|
|
5325
|
+
function deliver(name, url, body, opts, headers) {
|
|
5326
|
+
return postJson({
|
|
5327
|
+
url,
|
|
5328
|
+
body,
|
|
5329
|
+
sinkName: name,
|
|
5330
|
+
...headers ? { headers } : {},
|
|
5331
|
+
...opts.timeoutMs !== void 0 ? { timeoutMs: opts.timeoutMs } : {},
|
|
5332
|
+
...opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}
|
|
5333
|
+
});
|
|
5334
|
+
}
|
|
5335
|
+
var SlackSink = class {
|
|
5336
|
+
constructor(opts) {
|
|
5337
|
+
this.opts = opts;
|
|
5338
|
+
}
|
|
5339
|
+
name = "slack";
|
|
5340
|
+
async emit(alert) {
|
|
5341
|
+
await deliver(this.name, this.opts.url, formatSlack(redactValue(alert)), this.opts);
|
|
5342
|
+
}
|
|
5343
|
+
};
|
|
5344
|
+
var PagerDutySink = class {
|
|
5345
|
+
constructor(opts) {
|
|
5346
|
+
this.opts = opts;
|
|
5347
|
+
}
|
|
5348
|
+
name = "pagerduty";
|
|
5349
|
+
async emit(alert) {
|
|
5350
|
+
const body = formatPagerDuty(redactValue(alert), this.opts.routingKey);
|
|
5351
|
+
await deliver(this.name, this.opts.url || PAGERDUTY_ENQUEUE_URL, body, this.opts);
|
|
5352
|
+
}
|
|
5353
|
+
};
|
|
5354
|
+
var JiraSink = class {
|
|
5355
|
+
constructor(opts) {
|
|
5356
|
+
this.opts = opts;
|
|
5357
|
+
}
|
|
5358
|
+
name = "jira";
|
|
5359
|
+
async emit(alert) {
|
|
5360
|
+
const body = formatJira(redactValue(alert), {
|
|
5361
|
+
project: this.opts.project,
|
|
5362
|
+
...this.opts.issueType ? { issueType: this.opts.issueType } : {}
|
|
5363
|
+
});
|
|
5364
|
+
const auth = Buffer.from(`${this.opts.email}:${this.opts.token}`).toString("base64");
|
|
5365
|
+
const base = this.opts.url.replace(/\/+$/, "");
|
|
5366
|
+
await deliver(this.name, `${base}/rest/api/2/issue`, body, this.opts, { authorization: `Basic ${auth}` });
|
|
5110
5367
|
}
|
|
5111
5368
|
};
|
|
5112
5369
|
|
|
5113
5370
|
// src/sinks/index.ts
|
|
5114
5371
|
function buildSinks(drift) {
|
|
5115
5372
|
const configs = drift?.sinks && drift.sinks.length > 0 ? drift.sinks : [{ type: "stdout" }];
|
|
5373
|
+
const envSecret = process.env.CARTOGRAPHY_DRIFT_TOKEN;
|
|
5116
5374
|
const sinks = [];
|
|
5117
5375
|
for (const s of configs) {
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5376
|
+
const timeoutMs = s.timeoutMs;
|
|
5377
|
+
switch (s.type) {
|
|
5378
|
+
case "webhook":
|
|
5379
|
+
if (!s.url) {
|
|
5380
|
+
logWarn("drift sink skipped: webhook requires a url", { sink: s.type });
|
|
5381
|
+
break;
|
|
5382
|
+
}
|
|
5383
|
+
sinks.push(new WebhookSink({ url: s.url, token: s.token ?? envSecret, timeoutMs }));
|
|
5384
|
+
break;
|
|
5385
|
+
case "slack":
|
|
5386
|
+
if (!s.url) {
|
|
5387
|
+
logWarn("drift sink skipped: slack requires a webhook url", { sink: s.type });
|
|
5388
|
+
break;
|
|
5389
|
+
}
|
|
5390
|
+
sinks.push(new SlackSink({ url: s.url, timeoutMs }));
|
|
5391
|
+
break;
|
|
5392
|
+
case "pagerduty": {
|
|
5393
|
+
const routingKey = s.routingKey ?? s.token ?? envSecret;
|
|
5394
|
+
if (!routingKey) {
|
|
5395
|
+
logWarn("drift sink skipped: pagerduty requires a routingKey (or CARTOGRAPHY_DRIFT_TOKEN)", { sink: s.type });
|
|
5396
|
+
break;
|
|
5397
|
+
}
|
|
5398
|
+
sinks.push(new PagerDutySink({ url: s.url ?? PAGERDUTY_ENQUEUE_URL, routingKey, timeoutMs }));
|
|
5399
|
+
break;
|
|
5400
|
+
}
|
|
5401
|
+
case "jira": {
|
|
5402
|
+
const token = s.token ?? envSecret;
|
|
5403
|
+
if (!s.url || !s.email || !s.project || !token) {
|
|
5404
|
+
logWarn("drift sink skipped: jira requires url, email, project and a token", { sink: s.type });
|
|
5405
|
+
break;
|
|
5406
|
+
}
|
|
5407
|
+
sinks.push(new JiraSink({ url: s.url, email: s.email, token, project: s.project, issueType: s.issueType, timeoutMs }));
|
|
5408
|
+
break;
|
|
5409
|
+
}
|
|
5410
|
+
default:
|
|
5411
|
+
sinks.push(new StdoutSink());
|
|
5127
5412
|
}
|
|
5128
5413
|
}
|
|
5129
5414
|
return sinks.length > 0 ? sinks : [new StdoutSink()];
|
|
@@ -5532,7 +5817,7 @@ async function resolveNlQuery(db, sessionId, search, raw, opts) {
|
|
|
5532
5817
|
|
|
5533
5818
|
// src/mcp/server.ts
|
|
5534
5819
|
var SERVER_NAME = "cartography";
|
|
5535
|
-
var SERVER_VERSION = "2.
|
|
5820
|
+
var SERVER_VERSION = "2.4.0";
|
|
5536
5821
|
var SERVICE_TYPES = NODE_TYPE_GROUPS.web;
|
|
5537
5822
|
var DATA_TYPES = NODE_TYPE_GROUPS.data;
|
|
5538
5823
|
var lexicalSearch = async (db, sessionId, query, opts) => db.searchNodes(sessionId, query, { types: opts.types, limit: opts.limit }).map((node) => ({ node }));
|
|
@@ -6033,6 +6318,50 @@ var import_node_crypto5 = require("crypto");
|
|
|
6033
6318
|
var import_node_http = __toESM(require("http"), 1);
|
|
6034
6319
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
6035
6320
|
var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
6321
|
+
|
|
6322
|
+
// src/api/auth.ts
|
|
6323
|
+
var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
6324
|
+
function isLoopbackHost(host2) {
|
|
6325
|
+
return LOOPBACK_HOSTS2.has(host2);
|
|
6326
|
+
}
|
|
6327
|
+
function timingSafeEqual(a, b) {
|
|
6328
|
+
if (a.length !== b.length) return false;
|
|
6329
|
+
let diff = 0;
|
|
6330
|
+
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
6331
|
+
return diff === 0;
|
|
6332
|
+
}
|
|
6333
|
+
function bearerToken(header) {
|
|
6334
|
+
if (!header) return void 0;
|
|
6335
|
+
const trimmed = header.trim();
|
|
6336
|
+
if (trimmed.length < 7 || trimmed.slice(0, 6).toLowerCase() !== "bearer") return void 0;
|
|
6337
|
+
const rest = trimmed.slice(6);
|
|
6338
|
+
if (!/^\s/.test(rest)) return void 0;
|
|
6339
|
+
const token = rest.trimStart();
|
|
6340
|
+
return token.length > 0 ? token : void 0;
|
|
6341
|
+
}
|
|
6342
|
+
function checkBearer(authorizationHeader, token) {
|
|
6343
|
+
if (!token) return true;
|
|
6344
|
+
const provided = bearerToken(authorizationHeader);
|
|
6345
|
+
return provided !== void 0 && timingSafeEqual(provided, token);
|
|
6346
|
+
}
|
|
6347
|
+
function assertSafeBind(opts) {
|
|
6348
|
+
if (isLoopbackHost(opts.host)) return;
|
|
6349
|
+
if (opts.allowedHosts === void 0) {
|
|
6350
|
+
throw new Error(
|
|
6351
|
+
`Refusing to bind a non-loopback host (${opts.host}) without an explicit allowedHosts allowlist. Pass { allowedHosts: ['your.public.host:port'] } to opt in, or bind 127.0.0.1 for local-only use.`
|
|
6352
|
+
);
|
|
6353
|
+
}
|
|
6354
|
+
if (!opts.token) {
|
|
6355
|
+
throw new Error(
|
|
6356
|
+
`Refusing to bind a non-loopback host (${opts.host}) without an auth token. Pass { token } (or --token / CARTOGRAPHY_HTTP_TOKEN) so requests must carry 'Authorization: Bearer <token>'.`
|
|
6357
|
+
);
|
|
6358
|
+
}
|
|
6359
|
+
}
|
|
6360
|
+
function defaultAllowedHosts(host2, port) {
|
|
6361
|
+
return [`${host2}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];
|
|
6362
|
+
}
|
|
6363
|
+
|
|
6364
|
+
// src/mcp/transports.ts
|
|
6036
6365
|
async function runStdio(server) {
|
|
6037
6366
|
const transport = new import_stdio.StdioServerTransport();
|
|
6038
6367
|
await server.connect(transport);
|
|
@@ -6061,17 +6390,6 @@ async function readCappedBody(req, cap) {
|
|
|
6061
6390
|
return { overflow: false, value: void 0 };
|
|
6062
6391
|
}
|
|
6063
6392
|
}
|
|
6064
|
-
function timingSafeEqual(a, b) {
|
|
6065
|
-
if (a.length !== b.length) return false;
|
|
6066
|
-
let diff = 0;
|
|
6067
|
-
for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
6068
|
-
return diff === 0;
|
|
6069
|
-
}
|
|
6070
|
-
function bearerToken(header) {
|
|
6071
|
-
if (!header) return void 0;
|
|
6072
|
-
const m = /^Bearer\s+(.+)$/i.exec(header.trim());
|
|
6073
|
-
return m ? m[1] : void 0;
|
|
6074
|
-
}
|
|
6075
6393
|
async function readJsonBody(req) {
|
|
6076
6394
|
const chunks = [];
|
|
6077
6395
|
for await (const chunk of req) chunks.push(chunk);
|
|
@@ -6082,22 +6400,11 @@ async function readJsonBody(req) {
|
|
|
6082
6400
|
return void 0;
|
|
6083
6401
|
}
|
|
6084
6402
|
}
|
|
6085
|
-
var LOOPBACK_HOSTS2 = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "[::1]"]);
|
|
6086
6403
|
async function runHttp(factory, opts = {}) {
|
|
6087
6404
|
const host2 = opts.host ?? "127.0.0.1";
|
|
6088
6405
|
const port = opts.port ?? 3737;
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
throw new Error(
|
|
6092
|
-
`Refusing to bind a non-loopback host (${host2}) without an explicit allowedHosts allowlist. Pass { allowedHosts: ['your.public.host:port'] } to opt in, or bind 127.0.0.1 for local-only use.`
|
|
6093
|
-
);
|
|
6094
|
-
}
|
|
6095
|
-
if (!isLoopback && !opts.token) {
|
|
6096
|
-
throw new Error(
|
|
6097
|
-
`Refusing to bind a non-loopback host (${host2}) without an auth token. Pass { token } (or --token / CARTOGRAPHY_HTTP_TOKEN) so requests must carry 'Authorization: Bearer <token>'.`
|
|
6098
|
-
);
|
|
6099
|
-
}
|
|
6100
|
-
const allowedHosts = opts.allowedHosts ?? [`${host2}:${port}`, `localhost:${port}`, `127.0.0.1:${port}`];
|
|
6406
|
+
assertSafeBind({ host: host2, port, ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}, ...opts.token ? { token: opts.token } : {} });
|
|
6407
|
+
const allowedHosts = opts.allowedHosts ?? defaultAllowedHosts(host2, port);
|
|
6101
6408
|
const token = opts.token;
|
|
6102
6409
|
const transports = /* @__PURE__ */ new Map();
|
|
6103
6410
|
const httpServer = import_node_http.default.createServer(async (req, res) => {
|
|
@@ -6108,12 +6415,9 @@ async function runHttp(factory, opts = {}) {
|
|
|
6108
6415
|
res.writeHead(404, { "content-type": "application/json" }).end('{"error":"not found"}');
|
|
6109
6416
|
return;
|
|
6110
6417
|
}
|
|
6111
|
-
if (token) {
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" }).end('{"error":"unauthorized"}');
|
|
6115
|
-
return;
|
|
6116
|
-
}
|
|
6418
|
+
if (!checkBearer(req.headers["authorization"], token)) {
|
|
6419
|
+
res.writeHead(401, { "content-type": "application/json", "www-authenticate": "Bearer" }).end('{"error":"unauthorized"}');
|
|
6420
|
+
return;
|
|
6117
6421
|
}
|
|
6118
6422
|
if (isIngest) {
|
|
6119
6423
|
const hostHeader = (req.headers["host"] ?? "").toLowerCase();
|
|
@@ -6167,7 +6471,7 @@ async function runHttp(factory, opts = {}) {
|
|
|
6167
6471
|
if (!res.headersSent) res.writeHead(500, { "content-type": "application/json" }).end('{"error":"internal error"}');
|
|
6168
6472
|
}
|
|
6169
6473
|
});
|
|
6170
|
-
await new Promise((
|
|
6474
|
+
await new Promise((resolve3) => httpServer.listen(port, host2, resolve3));
|
|
6171
6475
|
return httpServer;
|
|
6172
6476
|
}
|
|
6173
6477
|
|
|
@@ -6349,8 +6653,8 @@ async function createSemanticSearch(db, embedder, opts = {}) {
|
|
|
6349
6653
|
return lexicalSearch2();
|
|
6350
6654
|
}
|
|
6351
6655
|
const store = new VectorStore(db, provider);
|
|
6352
|
-
const
|
|
6353
|
-
if (!
|
|
6656
|
+
const ok3 = await store.init();
|
|
6657
|
+
if (!ok3) {
|
|
6354
6658
|
log2?.("semantic search: vector store unavailable (sqlite-vec not installed or failed to load) \u2014 using lexical search");
|
|
6355
6659
|
return lexicalSearch2();
|
|
6356
6660
|
}
|
|
@@ -6959,6 +7263,1038 @@ function localDiscoveryFn(registry, plugins) {
|
|
|
6959
7263
|
};
|
|
6960
7264
|
}
|
|
6961
7265
|
|
|
7266
|
+
// src/api/server.ts
|
|
7267
|
+
var import_node_http2 = __toESM(require("http"), 1);
|
|
7268
|
+
|
|
7269
|
+
// src/api/tenant.ts
|
|
7270
|
+
var TENANT_HEADER = "x-cartograph-tenant";
|
|
7271
|
+
var InvalidTenantError = class extends Error {
|
|
7272
|
+
constructor() {
|
|
7273
|
+
super("invalid tenant");
|
|
7274
|
+
this.name = "InvalidTenantError";
|
|
7275
|
+
}
|
|
7276
|
+
};
|
|
7277
|
+
function resolveTenant(req, url, opts = {}) {
|
|
7278
|
+
const headerName = (opts.header ?? TENANT_HEADER).toLowerCase();
|
|
7279
|
+
const raw = headerValue(req, headerName) ?? url.searchParams.get("tenant") ?? void 0;
|
|
7280
|
+
if (raw === void 0 || raw === "") {
|
|
7281
|
+
return { tenant: opts.defaultTenant ?? DEFAULT_TENANT };
|
|
7282
|
+
}
|
|
7283
|
+
if (raw.trim().length > 128) {
|
|
7284
|
+
throw new InvalidTenantError();
|
|
7285
|
+
}
|
|
7286
|
+
const normalized = normalizeTenant(raw);
|
|
7287
|
+
if (normalized === DEFAULT_TENANT && raw.trim() !== DEFAULT_TENANT) {
|
|
7288
|
+
throw new InvalidTenantError();
|
|
7289
|
+
}
|
|
7290
|
+
return { tenant: normalized };
|
|
7291
|
+
}
|
|
7292
|
+
function headerValue(req, name) {
|
|
7293
|
+
const v = req.headers[name];
|
|
7294
|
+
if (Array.isArray(v)) return v[0];
|
|
7295
|
+
return v;
|
|
7296
|
+
}
|
|
7297
|
+
|
|
7298
|
+
// src/api/schemas.ts
|
|
7299
|
+
var import_zod8 = require("zod");
|
|
7300
|
+
var DIRECTIONS = ["downstream", "upstream", "both"];
|
|
7301
|
+
var CostSchema = import_zod8.z.object({
|
|
7302
|
+
amount: import_zod8.z.number(),
|
|
7303
|
+
currency: import_zod8.z.string(),
|
|
7304
|
+
period: import_zod8.z.enum(COST_PERIODS),
|
|
7305
|
+
source: import_zod8.z.string().optional()
|
|
7306
|
+
});
|
|
7307
|
+
var NodeSchema2 = import_zod8.z.object({
|
|
7308
|
+
id: import_zod8.z.string(),
|
|
7309
|
+
type: import_zod8.z.string(),
|
|
7310
|
+
name: import_zod8.z.string(),
|
|
7311
|
+
confidence: import_zod8.z.number(),
|
|
7312
|
+
domain: import_zod8.z.string().optional(),
|
|
7313
|
+
subDomain: import_zod8.z.string().optional(),
|
|
7314
|
+
qualityScore: import_zod8.z.number().optional(),
|
|
7315
|
+
owner: import_zod8.z.string().optional(),
|
|
7316
|
+
cost: CostSchema.optional(),
|
|
7317
|
+
tags: import_zod8.z.array(import_zod8.z.string())
|
|
7318
|
+
});
|
|
7319
|
+
var EdgeSchema2 = import_zod8.z.object({
|
|
7320
|
+
sourceId: import_zod8.z.string(),
|
|
7321
|
+
targetId: import_zod8.z.string(),
|
|
7322
|
+
relationship: import_zod8.z.string(),
|
|
7323
|
+
confidence: import_zod8.z.number(),
|
|
7324
|
+
evidence: import_zod8.z.string()
|
|
7325
|
+
});
|
|
7326
|
+
var AnomalySchema = import_zod8.z.object({
|
|
7327
|
+
nodeId: import_zod8.z.string(),
|
|
7328
|
+
kind: import_zod8.z.enum(ANOMALY_KINDS),
|
|
7329
|
+
severity: import_zod8.z.enum(ANOMALY_SEVERITIES),
|
|
7330
|
+
reason: import_zod8.z.string()
|
|
7331
|
+
});
|
|
7332
|
+
var TopConnectedSchema = import_zod8.z.object({
|
|
7333
|
+
id: import_zod8.z.string(),
|
|
7334
|
+
name: import_zod8.z.string(),
|
|
7335
|
+
type: import_zod8.z.string(),
|
|
7336
|
+
degree: import_zod8.z.number().int()
|
|
7337
|
+
});
|
|
7338
|
+
var CostByDomainSchema = import_zod8.z.object({
|
|
7339
|
+
domain: import_zod8.z.string(),
|
|
7340
|
+
currency: import_zod8.z.string(),
|
|
7341
|
+
period: import_zod8.z.string(),
|
|
7342
|
+
total: import_zod8.z.number(),
|
|
7343
|
+
nodes: import_zod8.z.number().int()
|
|
7344
|
+
});
|
|
7345
|
+
var CostByOwnerSchema = import_zod8.z.object({
|
|
7346
|
+
owner: import_zod8.z.string(),
|
|
7347
|
+
currency: import_zod8.z.string(),
|
|
7348
|
+
period: import_zod8.z.string(),
|
|
7349
|
+
total: import_zod8.z.number(),
|
|
7350
|
+
nodes: import_zod8.z.number().int()
|
|
7351
|
+
});
|
|
7352
|
+
var SummaryResponse = import_zod8.z.object({
|
|
7353
|
+
sessionId: import_zod8.z.string(),
|
|
7354
|
+
totals: import_zod8.z.object({ nodes: import_zod8.z.number().int(), edges: import_zod8.z.number().int() }),
|
|
7355
|
+
nodesByType: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.number().int()),
|
|
7356
|
+
nodesByDomain: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.number().int()),
|
|
7357
|
+
edgesByRelationship: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.number().int()),
|
|
7358
|
+
topConnected: import_zod8.z.array(TopConnectedSchema),
|
|
7359
|
+
anomalies: import_zod8.z.array(AnomalySchema),
|
|
7360
|
+
contributors: import_zod8.z.number().int(),
|
|
7361
|
+
costByDomain: import_zod8.z.array(CostByDomainSchema),
|
|
7362
|
+
costByOwner: import_zod8.z.array(CostByOwnerSchema),
|
|
7363
|
+
costCoverage: import_zod8.z.object({ withCost: import_zod8.z.number().int(), total: import_zod8.z.number().int() })
|
|
7364
|
+
});
|
|
7365
|
+
var NodesResponse = import_zod8.z.object({
|
|
7366
|
+
nodes: import_zod8.z.array(NodeSchema2),
|
|
7367
|
+
total: import_zod8.z.number().int(),
|
|
7368
|
+
limit: import_zod8.z.number().int(),
|
|
7369
|
+
offset: import_zod8.z.number().int()
|
|
7370
|
+
});
|
|
7371
|
+
var DependencyNodeSchema = NodeSchema2.extend({ depth: import_zod8.z.number().int() });
|
|
7372
|
+
var DependenciesResponse = import_zod8.z.object({
|
|
7373
|
+
root: NodeSchema2.optional(),
|
|
7374
|
+
direction: import_zod8.z.enum(DIRECTIONS),
|
|
7375
|
+
maxDepth: import_zod8.z.number().int(),
|
|
7376
|
+
nodes: import_zod8.z.array(DependencyNodeSchema),
|
|
7377
|
+
edges: import_zod8.z.array(EdgeSchema2)
|
|
7378
|
+
});
|
|
7379
|
+
var SessionEndpointSchema = import_zod8.z.object({
|
|
7380
|
+
sessionId: import_zod8.z.string(),
|
|
7381
|
+
startedAt: import_zod8.z.string(),
|
|
7382
|
+
nodeCount: import_zod8.z.number().int(),
|
|
7383
|
+
edgeCount: import_zod8.z.number().int()
|
|
7384
|
+
});
|
|
7385
|
+
var NodeChangeSchema = import_zod8.z.object({
|
|
7386
|
+
id: import_zod8.z.string(),
|
|
7387
|
+
changedFields: import_zod8.z.array(import_zod8.z.string()),
|
|
7388
|
+
confidenceDelta: import_zod8.z.number()
|
|
7389
|
+
});
|
|
7390
|
+
var DiffResponse = import_zod8.z.object({
|
|
7391
|
+
base: SessionEndpointSchema,
|
|
7392
|
+
current: SessionEndpointSchema,
|
|
7393
|
+
summary: import_zod8.z.object({
|
|
7394
|
+
nodesAdded: import_zod8.z.number().int(),
|
|
7395
|
+
nodesRemoved: import_zod8.z.number().int(),
|
|
7396
|
+
nodesChanged: import_zod8.z.number().int(),
|
|
7397
|
+
edgesAdded: import_zod8.z.number().int(),
|
|
7398
|
+
edgesRemoved: import_zod8.z.number().int()
|
|
7399
|
+
}),
|
|
7400
|
+
nodes: import_zod8.z.object({
|
|
7401
|
+
added: import_zod8.z.array(NodeSchema2),
|
|
7402
|
+
removed: import_zod8.z.array(NodeSchema2),
|
|
7403
|
+
changed: import_zod8.z.array(NodeChangeSchema),
|
|
7404
|
+
unchanged: import_zod8.z.number().int()
|
|
7405
|
+
}),
|
|
7406
|
+
edges: import_zod8.z.object({
|
|
7407
|
+
added: import_zod8.z.array(EdgeSchema2),
|
|
7408
|
+
removed: import_zod8.z.array(EdgeSchema2),
|
|
7409
|
+
unchanged: import_zod8.z.number().int()
|
|
7410
|
+
}),
|
|
7411
|
+
anomalies: import_zod8.z.object({ added: import_zod8.z.array(AnomalySchema) })
|
|
7412
|
+
});
|
|
7413
|
+
var SessionSchema = import_zod8.z.object({
|
|
7414
|
+
id: import_zod8.z.string(),
|
|
7415
|
+
mode: import_zod8.z.literal("discover"),
|
|
7416
|
+
startedAt: import_zod8.z.string(),
|
|
7417
|
+
completedAt: import_zod8.z.string().optional(),
|
|
7418
|
+
name: import_zod8.z.string().optional(),
|
|
7419
|
+
tenant: import_zod8.z.string(),
|
|
7420
|
+
lastScannedAt: import_zod8.z.string().optional()
|
|
7421
|
+
});
|
|
7422
|
+
var SessionsResponse = import_zod8.z.object({ sessions: import_zod8.z.array(SessionSchema) });
|
|
7423
|
+
var HealthResponse = import_zod8.z.object({
|
|
7424
|
+
status: import_zod8.z.literal("ok"),
|
|
7425
|
+
version: import_zod8.z.string(),
|
|
7426
|
+
store: import_zod8.z.literal("sqlite"),
|
|
7427
|
+
sessions: import_zod8.z.number().int()
|
|
7428
|
+
});
|
|
7429
|
+
var ErrorResponse = import_zod8.z.object({
|
|
7430
|
+
error: import_zod8.z.string(),
|
|
7431
|
+
code: import_zod8.z.string().optional()
|
|
7432
|
+
});
|
|
7433
|
+
var API_SCHEMAS = {
|
|
7434
|
+
Node: NodeSchema2,
|
|
7435
|
+
Edge: EdgeSchema2,
|
|
7436
|
+
Anomaly: AnomalySchema,
|
|
7437
|
+
Summary: SummaryResponse,
|
|
7438
|
+
Nodes: NodesResponse,
|
|
7439
|
+
Dependencies: DependenciesResponse,
|
|
7440
|
+
Diff: DiffResponse,
|
|
7441
|
+
Session: SessionSchema,
|
|
7442
|
+
Sessions: SessionsResponse,
|
|
7443
|
+
Health: HealthResponse,
|
|
7444
|
+
Error: ErrorResponse
|
|
7445
|
+
};
|
|
7446
|
+
|
|
7447
|
+
// src/api/rest.ts
|
|
7448
|
+
function toApiNode(n) {
|
|
7449
|
+
const out = { id: n.id, type: n.type, name: n.name, confidence: n.confidence, tags: n.tags };
|
|
7450
|
+
if (n.domain !== void 0) out["domain"] = n.domain;
|
|
7451
|
+
if (n.subDomain !== void 0) out["subDomain"] = n.subDomain;
|
|
7452
|
+
if (n.qualityScore !== void 0) out["qualityScore"] = n.qualityScore;
|
|
7453
|
+
if (n.owner !== void 0) out["owner"] = n.owner;
|
|
7454
|
+
if (n.cost !== void 0) out["cost"] = n.cost;
|
|
7455
|
+
return out;
|
|
7456
|
+
}
|
|
7457
|
+
function toApiEdge(e) {
|
|
7458
|
+
return { sourceId: e.sourceId, targetId: e.targetId, relationship: e.relationship, confidence: e.confidence, evidence: e.evidence };
|
|
7459
|
+
}
|
|
7460
|
+
function toApiSession(s) {
|
|
7461
|
+
const out = { id: s.id, mode: s.mode, startedAt: s.startedAt, tenant: s.tenant };
|
|
7462
|
+
if (s.completedAt !== void 0) out["completedAt"] = s.completedAt;
|
|
7463
|
+
if (s.name !== void 0) out["name"] = s.name;
|
|
7464
|
+
if (s.lastScannedAt !== void 0) out["lastScannedAt"] = s.lastScannedAt;
|
|
7465
|
+
return out;
|
|
7466
|
+
}
|
|
7467
|
+
function toApiAnomaly(a) {
|
|
7468
|
+
return { nodeId: a.nodeId, kind: a.kind, severity: a.severity, reason: a.reason };
|
|
7469
|
+
}
|
|
7470
|
+
function projectDependencies(r) {
|
|
7471
|
+
return {
|
|
7472
|
+
...r.root ? { root: toApiNode(r.root) } : {},
|
|
7473
|
+
direction: r.direction,
|
|
7474
|
+
maxDepth: r.maxDepth,
|
|
7475
|
+
nodes: r.nodes.map((n) => ({ ...toApiNode(n), depth: n.depth })),
|
|
7476
|
+
edges: r.edges.map(toApiEdge)
|
|
7477
|
+
};
|
|
7478
|
+
}
|
|
7479
|
+
function projectDiff(diff) {
|
|
7480
|
+
return {
|
|
7481
|
+
base: { sessionId: diff.base.sessionId, startedAt: diff.base.startedAt, nodeCount: diff.base.nodeCount, edgeCount: diff.base.edgeCount },
|
|
7482
|
+
current: { sessionId: diff.current.sessionId, startedAt: diff.current.startedAt, nodeCount: diff.current.nodeCount, edgeCount: diff.current.edgeCount },
|
|
7483
|
+
summary: diff.summary,
|
|
7484
|
+
nodes: {
|
|
7485
|
+
added: diff.nodes.added.map(toApiNode),
|
|
7486
|
+
removed: diff.nodes.removed.map(toApiNode),
|
|
7487
|
+
changed: diff.nodes.changed.map((c) => ({ id: c.id, changedFields: c.changedFields, confidenceDelta: c.confidenceDelta })),
|
|
7488
|
+
unchanged: diff.nodes.unchanged
|
|
7489
|
+
},
|
|
7490
|
+
edges: {
|
|
7491
|
+
added: diff.edges.added.map(toApiEdge),
|
|
7492
|
+
removed: diff.edges.removed.map(toApiEdge),
|
|
7493
|
+
unchanged: diff.edges.unchanged
|
|
7494
|
+
},
|
|
7495
|
+
anomalies: { added: diff.anomalies.added.map(toApiAnomaly) }
|
|
7496
|
+
};
|
|
7497
|
+
}
|
|
7498
|
+
function ok(body) {
|
|
7499
|
+
return { status: 200, body };
|
|
7500
|
+
}
|
|
7501
|
+
function badRequest(error) {
|
|
7502
|
+
return { status: 400, body: { error } };
|
|
7503
|
+
}
|
|
7504
|
+
function notFound(error = "not found") {
|
|
7505
|
+
return { status: 404, body: { error } };
|
|
7506
|
+
}
|
|
7507
|
+
function guard(fn) {
|
|
7508
|
+
try {
|
|
7509
|
+
return fn();
|
|
7510
|
+
} catch (err) {
|
|
7511
|
+
if (err instanceof NotFoundError) return notFound(err.message);
|
|
7512
|
+
throw err;
|
|
7513
|
+
}
|
|
7514
|
+
}
|
|
7515
|
+
function validateOut(schema, body) {
|
|
7516
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
7517
|
+
const r = schema.safeParse(body);
|
|
7518
|
+
if (!r.success) throw new Error(`API response failed its own schema contract: ${r.error.message}`);
|
|
7519
|
+
}
|
|
7520
|
+
return body;
|
|
7521
|
+
}
|
|
7522
|
+
function intParam(url, name) {
|
|
7523
|
+
const raw = url.searchParams.get(name);
|
|
7524
|
+
if (raw === null || raw.trim() === "") return void 0;
|
|
7525
|
+
const n = Number(raw);
|
|
7526
|
+
return Number.isInteger(n) ? n : void 0;
|
|
7527
|
+
}
|
|
7528
|
+
function sessionParam(url) {
|
|
7529
|
+
return url.searchParams.get("session") ?? void 0;
|
|
7530
|
+
}
|
|
7531
|
+
function handleSummary(ctx, url, d) {
|
|
7532
|
+
return guard(() => ok(validateOut(SummaryResponse, d.backend.summary(ctx, sessionParam(url)))));
|
|
7533
|
+
}
|
|
7534
|
+
function handleNodes(ctx, url, d) {
|
|
7535
|
+
return guard(() => {
|
|
7536
|
+
const search = url.searchParams.get("search") ?? void 0;
|
|
7537
|
+
const typesRaw = url.searchParams.get("types");
|
|
7538
|
+
const types = typesRaw ? typesRaw.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
7539
|
+
const limit = intParam(url, "limit");
|
|
7540
|
+
const offset = intParam(url, "offset");
|
|
7541
|
+
const r = d.backend.nodes(
|
|
7542
|
+
ctx,
|
|
7543
|
+
{ ...search ? { search } : {}, ...types ? { types } : {}, ...limit !== void 0 ? { limit } : {}, ...offset !== void 0 ? { offset } : {} },
|
|
7544
|
+
sessionParam(url)
|
|
7545
|
+
);
|
|
7546
|
+
return ok(validateOut(NodesResponse, { nodes: r.nodes.map(toApiNode), total: r.total, limit: r.limit, offset: r.offset }));
|
|
7547
|
+
});
|
|
7548
|
+
}
|
|
7549
|
+
function handleDependencies(ctx, id, url, d) {
|
|
7550
|
+
const directionRaw = url.searchParams.get("direction");
|
|
7551
|
+
if (directionRaw !== null && !DIRECTIONS.includes(directionRaw)) {
|
|
7552
|
+
return badRequest(`direction must be one of ${DIRECTIONS.join(", ")}`);
|
|
7553
|
+
}
|
|
7554
|
+
return guard(() => {
|
|
7555
|
+
const direction = directionRaw ?? void 0;
|
|
7556
|
+
const maxDepth = intParam(url, "maxDepth");
|
|
7557
|
+
const r = d.backend.dependencies(
|
|
7558
|
+
ctx,
|
|
7559
|
+
id,
|
|
7560
|
+
{ ...direction ? { direction } : {}, ...maxDepth !== void 0 ? { maxDepth } : {} },
|
|
7561
|
+
sessionParam(url)
|
|
7562
|
+
);
|
|
7563
|
+
return ok(validateOut(DependenciesResponse, projectDependencies(r)));
|
|
7564
|
+
});
|
|
7565
|
+
}
|
|
7566
|
+
function handleDiff(ctx, url, d) {
|
|
7567
|
+
const base = url.searchParams.get("base");
|
|
7568
|
+
const current = url.searchParams.get("current");
|
|
7569
|
+
if (!base || !current) return badRequest("both `base` and `current` query params are required");
|
|
7570
|
+
return guard(() => {
|
|
7571
|
+
const diff = d.backend.diff(ctx, base, current);
|
|
7572
|
+
return ok(validateOut(DiffResponse, projectDiff(diff)));
|
|
7573
|
+
});
|
|
7574
|
+
}
|
|
7575
|
+
function handleSessions(ctx, d) {
|
|
7576
|
+
return guard(() => ok(validateOut(SessionsResponse, { sessions: d.backend.sessions(ctx).map(toApiSession) })));
|
|
7577
|
+
}
|
|
7578
|
+
function handleHealth(ctx, d) {
|
|
7579
|
+
const h = d.backend.health(ctx);
|
|
7580
|
+
return ok(validateOut(HealthResponse, { status: "ok", version: d.version, store: h.store, sessions: h.sessions }));
|
|
7581
|
+
}
|
|
7582
|
+
|
|
7583
|
+
// src/api/openapi.ts
|
|
7584
|
+
function defOf(schema) {
|
|
7585
|
+
return schema.def ?? {};
|
|
7586
|
+
}
|
|
7587
|
+
function unwrapOptional(schema) {
|
|
7588
|
+
const def = defOf(schema);
|
|
7589
|
+
if ((def.type === "optional" || def.type === "nullable") && def.innerType) {
|
|
7590
|
+
return { inner: def.innerType, optional: true };
|
|
7591
|
+
}
|
|
7592
|
+
return { inner: schema, optional: false };
|
|
7593
|
+
}
|
|
7594
|
+
function zodToJsonSchema(schema) {
|
|
7595
|
+
const def = defOf(schema);
|
|
7596
|
+
switch (def.type) {
|
|
7597
|
+
case "string":
|
|
7598
|
+
return { type: "string" };
|
|
7599
|
+
case "number": {
|
|
7600
|
+
const isInt = (def.checks ?? []).some((c) => c._zod?.def?.check === "number_format");
|
|
7601
|
+
return { type: isInt ? "integer" : "number" };
|
|
7602
|
+
}
|
|
7603
|
+
case "boolean":
|
|
7604
|
+
return { type: "boolean" };
|
|
7605
|
+
case "literal": {
|
|
7606
|
+
const values = def.values ?? [];
|
|
7607
|
+
return values.length === 1 ? { const: values[0] } : { enum: values };
|
|
7608
|
+
}
|
|
7609
|
+
case "enum":
|
|
7610
|
+
return { type: "string", enum: Object.values(def.entries ?? {}) };
|
|
7611
|
+
case "array":
|
|
7612
|
+
return { type: "array", items: def.element ? zodToJsonSchema(def.element) : {} };
|
|
7613
|
+
case "record":
|
|
7614
|
+
return { type: "object", additionalProperties: def.valueType ? zodToJsonSchema(def.valueType) : true };
|
|
7615
|
+
case "optional":
|
|
7616
|
+
case "nullable":
|
|
7617
|
+
return def.innerType ? zodToJsonSchema(def.innerType) : {};
|
|
7618
|
+
case "object": {
|
|
7619
|
+
const shape = def.shape ?? {};
|
|
7620
|
+
const properties = {};
|
|
7621
|
+
const required = [];
|
|
7622
|
+
for (const key of Object.keys(shape)) {
|
|
7623
|
+
const { inner, optional } = unwrapOptional(shape[key]);
|
|
7624
|
+
properties[key] = zodToJsonSchema(inner);
|
|
7625
|
+
if (!optional) required.push(key);
|
|
7626
|
+
}
|
|
7627
|
+
return { type: "object", properties, required, additionalProperties: false };
|
|
7628
|
+
}
|
|
7629
|
+
default:
|
|
7630
|
+
throw new Error(`zodToJsonSchema: unsupported zod construct "${def.type ?? "unknown"}". Extend src/api/openapi.ts.`);
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
var TENANT_PARAM = {
|
|
7634
|
+
name: "tenant",
|
|
7635
|
+
in: "query",
|
|
7636
|
+
required: false,
|
|
7637
|
+
description: 'Tenant/org scope (also accepted via the X-Cartograph-Tenant header). Defaults to "local".',
|
|
7638
|
+
schema: { type: "string" }
|
|
7639
|
+
};
|
|
7640
|
+
var SESSION_PARAM = {
|
|
7641
|
+
name: "session",
|
|
7642
|
+
in: "query",
|
|
7643
|
+
required: false,
|
|
7644
|
+
description: "Session id to query, or omit for the latest discovery session.",
|
|
7645
|
+
schema: { type: "string" }
|
|
7646
|
+
};
|
|
7647
|
+
function errorResponses() {
|
|
7648
|
+
const err = { description: "Error", content: { "application/json": { schema: { $ref: "#/components/schemas/Error" } } } };
|
|
7649
|
+
return { "400": { ...err, description: "Bad request" }, "401": { ...err, description: "Unauthorized" }, "404": { ...err, description: "Not found" } };
|
|
7650
|
+
}
|
|
7651
|
+
function ok2(ref, description) {
|
|
7652
|
+
return { description, content: { "application/json": { schema: { $ref: `#/components/schemas/${ref}` } } } };
|
|
7653
|
+
}
|
|
7654
|
+
function buildOpenApiDocument(opts) {
|
|
7655
|
+
const schemas = {};
|
|
7656
|
+
for (const [name, schema] of Object.entries(API_SCHEMAS)) {
|
|
7657
|
+
schemas[name] = zodToJsonSchema(schema);
|
|
7658
|
+
}
|
|
7659
|
+
return {
|
|
7660
|
+
openapi: "3.1.0",
|
|
7661
|
+
info: {
|
|
7662
|
+
title: "Cartograph API",
|
|
7663
|
+
version: opts.version,
|
|
7664
|
+
description: "Read-only REST API over the discovered infrastructure/agentic-AI topology. Every endpoint is tenant-scoped and bearer-authenticated."
|
|
7665
|
+
},
|
|
7666
|
+
servers: [{ url: "/" }],
|
|
7667
|
+
security: [{ bearerAuth: [] }],
|
|
7668
|
+
components: {
|
|
7669
|
+
securitySchemes: { bearerAuth: { type: "http", scheme: "bearer" } },
|
|
7670
|
+
schemas
|
|
7671
|
+
},
|
|
7672
|
+
paths: {
|
|
7673
|
+
"/v1/health": {
|
|
7674
|
+
get: {
|
|
7675
|
+
summary: "Liveness + store/coverage probe",
|
|
7676
|
+
security: [],
|
|
7677
|
+
responses: { "200": ok2("Health", "Service health") }
|
|
7678
|
+
}
|
|
7679
|
+
},
|
|
7680
|
+
"/v1/openapi.json": {
|
|
7681
|
+
get: {
|
|
7682
|
+
summary: "This OpenAPI document",
|
|
7683
|
+
security: [],
|
|
7684
|
+
responses: { "200": { description: "OpenAPI 3.1 document", content: { "application/json": { schema: { type: "object" } } } } }
|
|
7685
|
+
}
|
|
7686
|
+
},
|
|
7687
|
+
"/v1/summary": {
|
|
7688
|
+
get: {
|
|
7689
|
+
summary: "Low-token topology aggregate for the resolved session",
|
|
7690
|
+
parameters: [SESSION_PARAM, TENANT_PARAM],
|
|
7691
|
+
responses: { "200": ok2("Summary", "Topology summary"), ...errorResponses() }
|
|
7692
|
+
}
|
|
7693
|
+
},
|
|
7694
|
+
"/v1/nodes": {
|
|
7695
|
+
get: {
|
|
7696
|
+
summary: "List/search/paginate nodes",
|
|
7697
|
+
parameters: [
|
|
7698
|
+
{ name: "search", in: "query", required: false, description: "Lexical/semantic search anchor.", schema: { type: "string" } },
|
|
7699
|
+
{ name: "types", in: "query", required: false, description: "Comma-separated node-type filter.", schema: { type: "string" } },
|
|
7700
|
+
{ name: "limit", in: "query", required: false, description: "Page size (default 100, max 1000).", schema: { type: "integer" } },
|
|
7701
|
+
{ name: "offset", in: "query", required: false, description: "Page offset (ignored for search).", schema: { type: "integer" } },
|
|
7702
|
+
SESSION_PARAM,
|
|
7703
|
+
TENANT_PARAM
|
|
7704
|
+
],
|
|
7705
|
+
responses: { "200": ok2("Nodes", "A page of nodes"), ...errorResponses() }
|
|
7706
|
+
}
|
|
7707
|
+
},
|
|
7708
|
+
"/v1/nodes/{id}/dependencies": {
|
|
7709
|
+
get: {
|
|
7710
|
+
summary: "Dependency traversal from a node",
|
|
7711
|
+
parameters: [
|
|
7712
|
+
{ name: "id", in: "path", required: true, description: 'Node id ("{type}:{id}").', schema: { type: "string" } },
|
|
7713
|
+
{ name: "direction", in: "query", required: false, description: "downstream | upstream | both (default downstream).", schema: { type: "string", enum: ["downstream", "upstream", "both"] } },
|
|
7714
|
+
{ name: "maxDepth", in: "query", required: false, description: "Traversal depth (default 8, max 64).", schema: { type: "integer" } },
|
|
7715
|
+
SESSION_PARAM,
|
|
7716
|
+
TENANT_PARAM
|
|
7717
|
+
],
|
|
7718
|
+
responses: { "200": ok2("Dependencies", "Traversal result"), ...errorResponses() }
|
|
7719
|
+
}
|
|
7720
|
+
},
|
|
7721
|
+
"/v1/diff": {
|
|
7722
|
+
get: {
|
|
7723
|
+
summary: "Compare two sessions (drift)",
|
|
7724
|
+
parameters: [
|
|
7725
|
+
{ name: "base", in: "query", required: true, description: "Base session id.", schema: { type: "string" } },
|
|
7726
|
+
{ name: "current", in: "query", required: true, description: "Current session id.", schema: { type: "string" } },
|
|
7727
|
+
TENANT_PARAM
|
|
7728
|
+
],
|
|
7729
|
+
responses: { "200": ok2("Diff", "Topology delta"), ...errorResponses() }
|
|
7730
|
+
}
|
|
7731
|
+
},
|
|
7732
|
+
"/v1/sessions": {
|
|
7733
|
+
get: {
|
|
7734
|
+
summary: "List discovery sessions for the tenant",
|
|
7735
|
+
parameters: [TENANT_PARAM],
|
|
7736
|
+
responses: { "200": ok2("Sessions", "Sessions"), ...errorResponses() }
|
|
7737
|
+
}
|
|
7738
|
+
}
|
|
7739
|
+
}
|
|
7740
|
+
};
|
|
7741
|
+
}
|
|
7742
|
+
|
|
7743
|
+
// src/api/graphql.ts
|
|
7744
|
+
var SDL = `# Cartograph read-only GraphQL API (4.2). Mirrors the REST surface.
|
|
7745
|
+
schema { query: Query }
|
|
7746
|
+
|
|
7747
|
+
type Query {
|
|
7748
|
+
summary(session: String): Summary
|
|
7749
|
+
nodes(search: String, types: [String!], limit: Int, offset: Int, session: String): NodeConnection
|
|
7750
|
+
node(id: String!, session: String): Node
|
|
7751
|
+
dependencies(id: String!, direction: Direction, maxDepth: Int, session: String): Dependencies
|
|
7752
|
+
diff(base: String!, current: String!): Diff
|
|
7753
|
+
sessions: [Session!]!
|
|
7754
|
+
}
|
|
7755
|
+
|
|
7756
|
+
enum Direction { downstream upstream both }
|
|
7757
|
+
|
|
7758
|
+
type Totals { nodes: Int! edges: Int! }
|
|
7759
|
+
type Count { key: String! value: Int! }
|
|
7760
|
+
type TopConnected { id: String! name: String! type: String! degree: Int! }
|
|
7761
|
+
type Anomaly { nodeId: String! kind: String! severity: String! reason: String! }
|
|
7762
|
+
type Cost { amount: Float! currency: String! period: String! source: String }
|
|
7763
|
+
type CostRollup { key: String! currency: String! period: String! total: Float! nodes: Int! }
|
|
7764
|
+
type CostCoverage { withCost: Int! total: Int! }
|
|
7765
|
+
|
|
7766
|
+
type Node {
|
|
7767
|
+
id: String! type: String! name: String! confidence: Float!
|
|
7768
|
+
domain: String subDomain: String qualityScore: Float owner: String cost: Cost tags: [String!]!
|
|
7769
|
+
}
|
|
7770
|
+
type DependencyNode {
|
|
7771
|
+
id: String! type: String! name: String! confidence: Float!
|
|
7772
|
+
domain: String subDomain: String qualityScore: Float owner: String cost: Cost tags: [String!]! depth: Int!
|
|
7773
|
+
}
|
|
7774
|
+
type Edge { sourceId: String! targetId: String! relationship: String! confidence: Float! evidence: String! }
|
|
7775
|
+
|
|
7776
|
+
type Summary {
|
|
7777
|
+
sessionId: String!
|
|
7778
|
+
totals: Totals!
|
|
7779
|
+
topConnected: [TopConnected!]!
|
|
7780
|
+
anomalies: [Anomaly!]!
|
|
7781
|
+
contributors: Int!
|
|
7782
|
+
costByDomain: [CostRollup!]!
|
|
7783
|
+
costByOwner: [CostRollup!]!
|
|
7784
|
+
costCoverage: CostCoverage!
|
|
7785
|
+
}
|
|
7786
|
+
|
|
7787
|
+
type NodeConnection { nodes: [Node!]! total: Int! limit: Int! offset: Int! }
|
|
7788
|
+
type Dependencies { root: Node direction: Direction! maxDepth: Int! nodes: [DependencyNode!]! edges: [Edge!]! }
|
|
7789
|
+
|
|
7790
|
+
type SessionEndpoint { sessionId: String! startedAt: String! nodeCount: Int! edgeCount: Int! }
|
|
7791
|
+
type DiffSummary { nodesAdded: Int! nodesRemoved: Int! nodesChanged: Int! edgesAdded: Int! edgesRemoved: Int! }
|
|
7792
|
+
type NodeChange { id: String! changedFields: [String!]! confidenceDelta: Float! }
|
|
7793
|
+
type DiffNodes { added: [Node!]! removed: [Node!]! changed: [NodeChange!]! unchanged: Int! }
|
|
7794
|
+
type DiffEdges { added: [Edge!]! removed: [Edge!]! unchanged: Int! }
|
|
7795
|
+
type DiffAnomalies { added: [Anomaly!]! }
|
|
7796
|
+
type Diff {
|
|
7797
|
+
base: SessionEndpoint! current: SessionEndpoint! summary: DiffSummary!
|
|
7798
|
+
nodes: DiffNodes! edges: DiffEdges! anomalies: DiffAnomalies!
|
|
7799
|
+
}
|
|
7800
|
+
|
|
7801
|
+
type Session { id: String! mode: String! startedAt: String! completedAt: String name: String tenant: String! lastScannedAt: String }
|
|
7802
|
+
`;
|
|
7803
|
+
var resolvers = {
|
|
7804
|
+
summary: (ctx, args, backend) => backend.summary(ctx, str(args["session"])),
|
|
7805
|
+
nodes: (ctx, args, backend) => {
|
|
7806
|
+
const r = backend.nodes(
|
|
7807
|
+
ctx,
|
|
7808
|
+
{
|
|
7809
|
+
...str(args["search"]) ? { search: str(args["search"]) } : {},
|
|
7810
|
+
...Array.isArray(args["types"]) ? { types: args["types"].map(String) } : {},
|
|
7811
|
+
...num(args["limit"]) !== void 0 ? { limit: num(args["limit"]) } : {},
|
|
7812
|
+
...num(args["offset"]) !== void 0 ? { offset: num(args["offset"]) } : {}
|
|
7813
|
+
},
|
|
7814
|
+
str(args["session"])
|
|
7815
|
+
);
|
|
7816
|
+
return { nodes: r.nodes.map(toApiNode), total: r.total, limit: r.limit, offset: r.offset };
|
|
7817
|
+
},
|
|
7818
|
+
node: (ctx, args, backend) => {
|
|
7819
|
+
const n = backend.node(ctx, String(args["id"]), str(args["session"]));
|
|
7820
|
+
return n ? toApiNode(n) : null;
|
|
7821
|
+
},
|
|
7822
|
+
dependencies: (ctx, args, backend) => {
|
|
7823
|
+
const r = backend.dependencies(
|
|
7824
|
+
ctx,
|
|
7825
|
+
String(args["id"]),
|
|
7826
|
+
{
|
|
7827
|
+
...str(args["direction"]) ? { direction: str(args["direction"]) } : {},
|
|
7828
|
+
...num(args["maxDepth"]) !== void 0 ? { maxDepth: num(args["maxDepth"]) } : {}
|
|
7829
|
+
},
|
|
7830
|
+
str(args["session"])
|
|
7831
|
+
);
|
|
7832
|
+
return projectDependencies(r);
|
|
7833
|
+
},
|
|
7834
|
+
diff: (ctx, args, backend) => projectDiff(backend.diff(ctx, String(args["base"]), String(args["current"]))),
|
|
7835
|
+
sessions: (ctx, _args, backend) => backend.sessions(ctx).map(toApiSession)
|
|
7836
|
+
};
|
|
7837
|
+
function str(v) {
|
|
7838
|
+
return typeof v === "string" ? v : void 0;
|
|
7839
|
+
}
|
|
7840
|
+
function num(v) {
|
|
7841
|
+
return typeof v === "number" && Number.isInteger(v) ? v : void 0;
|
|
7842
|
+
}
|
|
7843
|
+
var NAME_RE = /[_A-Za-z][_0-9A-Za-z]*/y;
|
|
7844
|
+
function tokenize2(src) {
|
|
7845
|
+
const tokens = [];
|
|
7846
|
+
let i = 0;
|
|
7847
|
+
while (i < src.length) {
|
|
7848
|
+
const c = src[i];
|
|
7849
|
+
if (/\s|,/.test(c)) {
|
|
7850
|
+
i++;
|
|
7851
|
+
continue;
|
|
7852
|
+
}
|
|
7853
|
+
if (c === "#") {
|
|
7854
|
+
while (i < src.length && src[i] !== "\n") i++;
|
|
7855
|
+
continue;
|
|
7856
|
+
}
|
|
7857
|
+
if ("{}()[]:!$".includes(c)) {
|
|
7858
|
+
tokens.push(c);
|
|
7859
|
+
i++;
|
|
7860
|
+
continue;
|
|
7861
|
+
}
|
|
7862
|
+
if (c === '"') {
|
|
7863
|
+
let j = i + 1;
|
|
7864
|
+
let s = "";
|
|
7865
|
+
while (j < src.length && src[j] !== '"') {
|
|
7866
|
+
s += src[j];
|
|
7867
|
+
j++;
|
|
7868
|
+
}
|
|
7869
|
+
tokens.push(JSON.stringify(s));
|
|
7870
|
+
i = j + 1;
|
|
7871
|
+
continue;
|
|
7872
|
+
}
|
|
7873
|
+
NAME_RE.lastIndex = i;
|
|
7874
|
+
const m = NAME_RE.exec(src);
|
|
7875
|
+
if (m && m.index === i) {
|
|
7876
|
+
tokens.push(m[0]);
|
|
7877
|
+
i = NAME_RE.lastIndex;
|
|
7878
|
+
continue;
|
|
7879
|
+
}
|
|
7880
|
+
const numMatch = /-?\d+(\.\d+)?/y;
|
|
7881
|
+
numMatch.lastIndex = i;
|
|
7882
|
+
const nm = numMatch.exec(src);
|
|
7883
|
+
if (nm && nm.index === i) {
|
|
7884
|
+
tokens.push(nm[0]);
|
|
7885
|
+
i = numMatch.lastIndex;
|
|
7886
|
+
continue;
|
|
7887
|
+
}
|
|
7888
|
+
throw new Error(`unexpected character '${c}'`);
|
|
7889
|
+
}
|
|
7890
|
+
return tokens;
|
|
7891
|
+
}
|
|
7892
|
+
var MAX_SELECTION_DEPTH = 32;
|
|
7893
|
+
var Parser = class {
|
|
7894
|
+
constructor(tokens, variables) {
|
|
7895
|
+
this.tokens = tokens;
|
|
7896
|
+
this.variables = variables;
|
|
7897
|
+
}
|
|
7898
|
+
pos = 0;
|
|
7899
|
+
depth = 0;
|
|
7900
|
+
peek() {
|
|
7901
|
+
return this.tokens[this.pos];
|
|
7902
|
+
}
|
|
7903
|
+
next() {
|
|
7904
|
+
return this.tokens[this.pos++];
|
|
7905
|
+
}
|
|
7906
|
+
expect(tok) {
|
|
7907
|
+
if (this.tokens[this.pos] !== tok) throw new Error(`expected '${tok}', got '${this.tokens[this.pos] ?? "<eof>"}'`);
|
|
7908
|
+
this.pos++;
|
|
7909
|
+
}
|
|
7910
|
+
parseDocument() {
|
|
7911
|
+
if (this.peek() === "mutation" || this.peek() === "subscription") {
|
|
7912
|
+
throw new Error("only query operations are supported (read-only API)");
|
|
7913
|
+
}
|
|
7914
|
+
if (this.peek() === "query") {
|
|
7915
|
+
this.next();
|
|
7916
|
+
if (this.peek() && this.peek() !== "{" && this.peek() !== "(") this.next();
|
|
7917
|
+
if (this.peek() === "(") this.skipBalanced("(", ")");
|
|
7918
|
+
}
|
|
7919
|
+
this.expect("{");
|
|
7920
|
+
const selections = this.parseSelectionSet();
|
|
7921
|
+
return selections;
|
|
7922
|
+
}
|
|
7923
|
+
skipBalanced(open, close) {
|
|
7924
|
+
this.expect(open);
|
|
7925
|
+
let depth = 1;
|
|
7926
|
+
while (depth > 0) {
|
|
7927
|
+
const t = this.next();
|
|
7928
|
+
if (t === void 0) throw new Error("unbalanced");
|
|
7929
|
+
if (t === open) depth++;
|
|
7930
|
+
else if (t === close) depth--;
|
|
7931
|
+
}
|
|
7932
|
+
}
|
|
7933
|
+
parseSelectionSet() {
|
|
7934
|
+
if (++this.depth > MAX_SELECTION_DEPTH) throw new Error(`selection set nested deeper than ${MAX_SELECTION_DEPTH}`);
|
|
7935
|
+
const out = [];
|
|
7936
|
+
while (this.peek() !== "}") {
|
|
7937
|
+
if (this.peek() === void 0) throw new Error("unexpected end of selection set");
|
|
7938
|
+
out.push(this.parseSelection());
|
|
7939
|
+
}
|
|
7940
|
+
this.expect("}");
|
|
7941
|
+
this.depth--;
|
|
7942
|
+
return out;
|
|
7943
|
+
}
|
|
7944
|
+
parseSelection() {
|
|
7945
|
+
let name = this.next();
|
|
7946
|
+
const alias = name;
|
|
7947
|
+
if (this.peek() === ":") {
|
|
7948
|
+
this.next();
|
|
7949
|
+
name = this.next();
|
|
7950
|
+
}
|
|
7951
|
+
const args = {};
|
|
7952
|
+
if (this.peek() === "(") {
|
|
7953
|
+
this.next();
|
|
7954
|
+
while (this.peek() !== ")") {
|
|
7955
|
+
const argName = this.next();
|
|
7956
|
+
this.expect(":");
|
|
7957
|
+
args[argName] = this.parseValue();
|
|
7958
|
+
}
|
|
7959
|
+
this.expect(")");
|
|
7960
|
+
}
|
|
7961
|
+
let selections = [];
|
|
7962
|
+
if (this.peek() === "{") {
|
|
7963
|
+
this.next();
|
|
7964
|
+
selections = this.parseSelectionSet();
|
|
7965
|
+
}
|
|
7966
|
+
return { name, alias, args, selections };
|
|
7967
|
+
}
|
|
7968
|
+
parseValue() {
|
|
7969
|
+
const t = this.next();
|
|
7970
|
+
if (t === "$") {
|
|
7971
|
+
const v = this.next();
|
|
7972
|
+
return this.variables[v];
|
|
7973
|
+
}
|
|
7974
|
+
if (t === "[") {
|
|
7975
|
+
const arr = [];
|
|
7976
|
+
while (this.peek() !== "]") arr.push(this.parseValue());
|
|
7977
|
+
this.expect("]");
|
|
7978
|
+
return arr;
|
|
7979
|
+
}
|
|
7980
|
+
if (t.startsWith('"')) return JSON.parse(t);
|
|
7981
|
+
if (t === "true") return true;
|
|
7982
|
+
if (t === "false") return false;
|
|
7983
|
+
if (t === "null") return null;
|
|
7984
|
+
if (/^-?\d+(\.\d+)?$/.test(t)) return Number(t);
|
|
7985
|
+
return t;
|
|
7986
|
+
}
|
|
7987
|
+
};
|
|
7988
|
+
function project(value, selections) {
|
|
7989
|
+
if (value === null || value === void 0) return null;
|
|
7990
|
+
if (selections.length === 0) return value;
|
|
7991
|
+
if (Array.isArray(value)) return value.map((v) => project(v, selections));
|
|
7992
|
+
if (typeof value !== "object") return value;
|
|
7993
|
+
const obj = value;
|
|
7994
|
+
const out = {};
|
|
7995
|
+
for (const sel of selections) {
|
|
7996
|
+
if (sel.name === "__typename") {
|
|
7997
|
+
out[sel.alias] = void 0;
|
|
7998
|
+
continue;
|
|
7999
|
+
}
|
|
8000
|
+
out[sel.alias] = project(obj[sel.name], sel.selections);
|
|
8001
|
+
}
|
|
8002
|
+
return out;
|
|
8003
|
+
}
|
|
8004
|
+
function introspectionSchema() {
|
|
8005
|
+
const names = [...SDL.matchAll(/^(?:type|enum)\s+([_A-Za-z][_0-9A-Za-z]*)/gm)].map((m) => m[1]);
|
|
8006
|
+
const types = names.map((name) => ({ name, kind: /^[A-Z]/.test(name) ? "OBJECT" : "SCALAR" }));
|
|
8007
|
+
return {
|
|
8008
|
+
__schema: {
|
|
8009
|
+
queryType: { name: "Query" },
|
|
8010
|
+
mutationType: null,
|
|
8011
|
+
subscriptionType: null,
|
|
8012
|
+
types,
|
|
8013
|
+
directives: []
|
|
8014
|
+
}
|
|
8015
|
+
};
|
|
8016
|
+
}
|
|
8017
|
+
async function executeGraphql(ctx, body, deps) {
|
|
8018
|
+
const req = body ?? {};
|
|
8019
|
+
if (typeof req.query !== "string" || req.query.trim() === "") {
|
|
8020
|
+
return { errors: [{ message: "missing query" }] };
|
|
8021
|
+
}
|
|
8022
|
+
const variables = typeof req.variables === "object" && req.variables !== null ? req.variables : {};
|
|
8023
|
+
let selections;
|
|
8024
|
+
try {
|
|
8025
|
+
selections = new Parser(tokenize2(req.query), variables).parseDocument();
|
|
8026
|
+
} catch (err) {
|
|
8027
|
+
return { errors: [{ message: `syntax error: ${err instanceof Error ? err.message : String(err)}` }] };
|
|
8028
|
+
}
|
|
8029
|
+
const data = {};
|
|
8030
|
+
const errors = [];
|
|
8031
|
+
for (const sel of selections) {
|
|
8032
|
+
try {
|
|
8033
|
+
if (sel.name === "__schema") {
|
|
8034
|
+
data[sel.alias] = project(introspectionSchema()["__schema"], sel.selections);
|
|
8035
|
+
continue;
|
|
8036
|
+
}
|
|
8037
|
+
if (sel.name === "__typename") {
|
|
8038
|
+
data[sel.alias] = "Query";
|
|
8039
|
+
continue;
|
|
8040
|
+
}
|
|
8041
|
+
const resolver = resolvers[sel.name];
|
|
8042
|
+
if (!resolver) {
|
|
8043
|
+
errors.push({ message: `Cannot query field "${sel.name}" on type "Query"` });
|
|
8044
|
+
continue;
|
|
8045
|
+
}
|
|
8046
|
+
const resolved = resolver(ctx, sel.args, deps.backend);
|
|
8047
|
+
data[sel.alias] = project(resolved, sel.selections);
|
|
8048
|
+
} catch (err) {
|
|
8049
|
+
errors.push({ message: err instanceof Error ? err.message : String(err) });
|
|
8050
|
+
}
|
|
8051
|
+
}
|
|
8052
|
+
return errors.length > 0 ? { data, errors } : { data };
|
|
8053
|
+
}
|
|
8054
|
+
function handleGraphqlGet() {
|
|
8055
|
+
return { status: 200, body: SDL };
|
|
8056
|
+
}
|
|
8057
|
+
|
|
8058
|
+
// src/api/server.ts
|
|
8059
|
+
var DEPENDENCIES_RE = /^\/v1\/nodes\/(.+)\/dependencies$/;
|
|
8060
|
+
var MAX_GRAPHQL_BYTES = 1024 * 1024;
|
|
8061
|
+
function send(res, status, body, headers = {}) {
|
|
8062
|
+
res.writeHead(status, { "content-type": "application/json", ...headers }).end(JSON.stringify(body));
|
|
8063
|
+
}
|
|
8064
|
+
async function readBody(req, cap) {
|
|
8065
|
+
const chunks = [];
|
|
8066
|
+
let total = 0;
|
|
8067
|
+
let overflow = false;
|
|
8068
|
+
for await (const chunk of req) {
|
|
8069
|
+
if (overflow) continue;
|
|
8070
|
+
const buf = chunk;
|
|
8071
|
+
total += buf.length;
|
|
8072
|
+
if (total > cap) {
|
|
8073
|
+
overflow = true;
|
|
8074
|
+
chunks.length = 0;
|
|
8075
|
+
continue;
|
|
8076
|
+
}
|
|
8077
|
+
chunks.push(buf);
|
|
8078
|
+
}
|
|
8079
|
+
if (overflow) return { overflow: true, value: void 0 };
|
|
8080
|
+
if (chunks.length === 0) return { overflow: false, value: void 0 };
|
|
8081
|
+
try {
|
|
8082
|
+
return { overflow: false, value: JSON.parse(Buffer.concat(chunks).toString("utf8")) };
|
|
8083
|
+
} catch {
|
|
8084
|
+
return { overflow: false, value: void 0 };
|
|
8085
|
+
}
|
|
8086
|
+
}
|
|
8087
|
+
async function runApi(opts) {
|
|
8088
|
+
const host2 = opts.host ?? "127.0.0.1";
|
|
8089
|
+
const requestedPort = opts.port ?? 3737;
|
|
8090
|
+
const token = opts.token;
|
|
8091
|
+
const graphqlEnabled = opts.graphql !== false;
|
|
8092
|
+
const defaultTenant = opts.tenant?.defaultTenant ?? DEFAULT_TENANT;
|
|
8093
|
+
const log2 = opts.log ?? (() => {
|
|
8094
|
+
});
|
|
8095
|
+
const restDeps = { backend: opts.backend, version: opts.version };
|
|
8096
|
+
const openApiDoc = buildOpenApiDocument({ version: opts.version });
|
|
8097
|
+
const allowedOrigins = opts.allowedOrigins ?? [];
|
|
8098
|
+
assertSafeBind({ host: host2, port: requestedPort, ...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {}, ...token ? { token } : {} });
|
|
8099
|
+
let allowedHosts = opts.allowedHosts ?? [];
|
|
8100
|
+
const corsHeaders = (req) => {
|
|
8101
|
+
const origin = req.headers["origin"];
|
|
8102
|
+
if (typeof origin === "string" && allowedOrigins.includes(origin)) {
|
|
8103
|
+
return {
|
|
8104
|
+
"access-control-allow-origin": origin,
|
|
8105
|
+
"vary": "Origin",
|
|
8106
|
+
"access-control-allow-methods": "GET, POST, OPTIONS",
|
|
8107
|
+
"access-control-allow-headers": "authorization, content-type, x-cartograph-tenant"
|
|
8108
|
+
};
|
|
8109
|
+
}
|
|
8110
|
+
return {};
|
|
8111
|
+
};
|
|
8112
|
+
const server = import_node_http2.default.createServer((req, res) => {
|
|
8113
|
+
const started = Date.now();
|
|
8114
|
+
let tenantLabel = "-";
|
|
8115
|
+
const finish = (status) => {
|
|
8116
|
+
log2(`[cartography-api] ${req.method ?? "-"} ${req.url ?? "-"} ${status} ${Date.now() - started}ms tenant=${tenantLabel}`);
|
|
8117
|
+
};
|
|
8118
|
+
void (async () => {
|
|
8119
|
+
try {
|
|
8120
|
+
const url = new URL(req.url ?? "/", `http://${req.headers["host"] ?? host2}`);
|
|
8121
|
+
const path = url.pathname;
|
|
8122
|
+
const cors = corsHeaders(req);
|
|
8123
|
+
if (req.method === "OPTIONS") {
|
|
8124
|
+
res.writeHead(204, cors).end();
|
|
8125
|
+
finish(204);
|
|
8126
|
+
return;
|
|
8127
|
+
}
|
|
8128
|
+
const hostHeader = (req.headers["host"] ?? "").toLowerCase();
|
|
8129
|
+
if (!allowedHosts.some((h) => h.toLowerCase() === hostHeader)) {
|
|
8130
|
+
send(res, 403, { error: "host not allowed" }, cors);
|
|
8131
|
+
finish(403);
|
|
8132
|
+
return;
|
|
8133
|
+
}
|
|
8134
|
+
if (path === "/v1/openapi.json" && req.method === "GET") {
|
|
8135
|
+
send(res, 200, openApiDoc, cors);
|
|
8136
|
+
finish(200);
|
|
8137
|
+
return;
|
|
8138
|
+
}
|
|
8139
|
+
if (path === "/v1/health") {
|
|
8140
|
+
if (req.method !== "GET") {
|
|
8141
|
+
send(res, 405, { error: "method not allowed" }, { allow: "GET", ...cors });
|
|
8142
|
+
finish(405);
|
|
8143
|
+
return;
|
|
8144
|
+
}
|
|
8145
|
+
tenantLabel = defaultTenant;
|
|
8146
|
+
const r = handleHealth({ tenant: defaultTenant }, restDeps);
|
|
8147
|
+
send(res, r.status, r.body, cors);
|
|
8148
|
+
finish(r.status);
|
|
8149
|
+
return;
|
|
8150
|
+
}
|
|
8151
|
+
if (!checkBearer(req.headers["authorization"], token)) {
|
|
8152
|
+
send(res, 401, { error: "unauthorized" }, { "www-authenticate": "Bearer", ...cors });
|
|
8153
|
+
finish(401);
|
|
8154
|
+
return;
|
|
8155
|
+
}
|
|
8156
|
+
let ctx;
|
|
8157
|
+
try {
|
|
8158
|
+
ctx = resolveTenant(req, url, opts.tenant ?? {});
|
|
8159
|
+
tenantLabel = ctx.tenant;
|
|
8160
|
+
} catch (err) {
|
|
8161
|
+
if (err instanceof InvalidTenantError) {
|
|
8162
|
+
send(res, 400, { error: "invalid tenant" }, cors);
|
|
8163
|
+
finish(400);
|
|
8164
|
+
return;
|
|
8165
|
+
}
|
|
8166
|
+
throw err;
|
|
8167
|
+
}
|
|
8168
|
+
if (graphqlEnabled && path === "/graphql") {
|
|
8169
|
+
if (req.method === "GET") {
|
|
8170
|
+
const g = handleGraphqlGet();
|
|
8171
|
+
res.writeHead(g.status, { "content-type": "text/plain; charset=utf-8", ...cors }).end(g.body);
|
|
8172
|
+
finish(g.status);
|
|
8173
|
+
return;
|
|
8174
|
+
}
|
|
8175
|
+
if (req.method === "POST") {
|
|
8176
|
+
const { overflow, value } = await readBody(req, MAX_GRAPHQL_BYTES);
|
|
8177
|
+
if (overflow) {
|
|
8178
|
+
send(res, 413, { error: "payload too large" }, cors);
|
|
8179
|
+
finish(413);
|
|
8180
|
+
return;
|
|
8181
|
+
}
|
|
8182
|
+
const result = await executeGraphql(ctx, value, { backend: opts.backend });
|
|
8183
|
+
send(res, 200, result, cors);
|
|
8184
|
+
finish(200);
|
|
8185
|
+
return;
|
|
8186
|
+
}
|
|
8187
|
+
send(res, 405, { error: "method not allowed" }, { allow: "GET, POST", ...cors });
|
|
8188
|
+
finish(405);
|
|
8189
|
+
return;
|
|
8190
|
+
}
|
|
8191
|
+
if (path.startsWith("/v1/")) {
|
|
8192
|
+
if (req.method !== "GET") {
|
|
8193
|
+
send(res, 405, { error: "method not allowed" }, { allow: "GET", ...cors });
|
|
8194
|
+
finish(405);
|
|
8195
|
+
return;
|
|
8196
|
+
}
|
|
8197
|
+
const result = dispatchRest(ctx, path, url, restDeps);
|
|
8198
|
+
if (result) {
|
|
8199
|
+
send(res, result.status, result.body, cors);
|
|
8200
|
+
finish(result.status);
|
|
8201
|
+
return;
|
|
8202
|
+
}
|
|
8203
|
+
}
|
|
8204
|
+
send(res, 404, { error: "not found" }, cors);
|
|
8205
|
+
finish(404);
|
|
8206
|
+
} catch (err) {
|
|
8207
|
+
process.stderr.write(`[cartography-api] request failed: ${err instanceof Error ? err.message : String(err)}
|
|
8208
|
+
`);
|
|
8209
|
+
if (!res.headersSent) send(res, 500, { error: "internal error" });
|
|
8210
|
+
finish(500);
|
|
8211
|
+
}
|
|
8212
|
+
})();
|
|
8213
|
+
});
|
|
8214
|
+
await new Promise((resolve3) => server.listen(requestedPort, host2, resolve3));
|
|
8215
|
+
const actualPort = server.address().port;
|
|
8216
|
+
if (allowedHosts.length === 0) allowedHosts = defaultAllowedHosts(host2, actualPort);
|
|
8217
|
+
return server;
|
|
8218
|
+
}
|
|
8219
|
+
function dispatchRest(ctx, path, url, deps) {
|
|
8220
|
+
switch (path) {
|
|
8221
|
+
case "/v1/summary":
|
|
8222
|
+
return handleSummary(ctx, url, deps);
|
|
8223
|
+
case "/v1/nodes":
|
|
8224
|
+
return handleNodes(ctx, url, deps);
|
|
8225
|
+
case "/v1/diff":
|
|
8226
|
+
return handleDiff(ctx, url, deps);
|
|
8227
|
+
case "/v1/sessions":
|
|
8228
|
+
return handleSessions(ctx, deps);
|
|
8229
|
+
default: {
|
|
8230
|
+
const m = DEPENDENCIES_RE.exec(path);
|
|
8231
|
+
if (m) return handleDependencies(ctx, decodeURIComponent(m[1]), url, deps);
|
|
8232
|
+
return void 0;
|
|
8233
|
+
}
|
|
8234
|
+
}
|
|
8235
|
+
}
|
|
8236
|
+
|
|
8237
|
+
// src/api/start.ts
|
|
8238
|
+
var import_node_fs5 = require("fs");
|
|
8239
|
+
var import_node_path5 = require("path");
|
|
8240
|
+
var import_node_url = require("url");
|
|
8241
|
+
var import_meta = {};
|
|
8242
|
+
function readVersion() {
|
|
8243
|
+
try {
|
|
8244
|
+
const dir = import_meta.dirname ?? (0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(import_meta.url));
|
|
8245
|
+
return JSON.parse((0, import_node_fs5.readFileSync)((0, import_node_path5.resolve)(dir, "..", "package.json"), "utf-8")).version ?? "0.0.0";
|
|
8246
|
+
} catch {
|
|
8247
|
+
return "0.0.0";
|
|
8248
|
+
}
|
|
8249
|
+
}
|
|
8250
|
+
function parseApiArgs(argv) {
|
|
8251
|
+
const opts = {};
|
|
8252
|
+
for (let i = 0; i < argv.length; i++) {
|
|
8253
|
+
const a = argv[i];
|
|
8254
|
+
if (a === "--http") continue;
|
|
8255
|
+
else if (a === "--no-graphql") opts.graphql = false;
|
|
8256
|
+
else if (a === "--port") opts.port = Number(argv[++i]);
|
|
8257
|
+
else if (a === "--host") opts.host = argv[++i];
|
|
8258
|
+
else if (a === "--allowed-hosts") opts.allowedHosts = splitList(argv[++i]);
|
|
8259
|
+
else if (a === "--allowed-origins") opts.allowedOrigins = splitList(argv[++i]);
|
|
8260
|
+
else if (a === "--token") opts.token = argv[++i];
|
|
8261
|
+
else if (a === "--db") opts.dbPath = argv[++i];
|
|
8262
|
+
else if (a === "--session") opts.session = argv[++i];
|
|
8263
|
+
else if (a === "--tenant" || a === "--org") opts.tenant = argv[++i];
|
|
8264
|
+
else if (a === "--help" || a === "-h") opts.help = true;
|
|
8265
|
+
}
|
|
8266
|
+
return opts;
|
|
8267
|
+
}
|
|
8268
|
+
function splitList(raw) {
|
|
8269
|
+
return (raw ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
8270
|
+
}
|
|
8271
|
+
async function startApi(opts = {}) {
|
|
8272
|
+
const log2 = opts.log ?? ((m) => process.stderr.write(m + "\n"));
|
|
8273
|
+
const db = new CartographyDB(opts.dbPath ?? defaultConfig().dbPath);
|
|
8274
|
+
const backend = createSqliteQueryBackend(db, opts.session ?? "latest");
|
|
8275
|
+
const token = opts.token ?? process.env["CARTOGRAPHY_HTTP_TOKEN"];
|
|
8276
|
+
const host2 = opts.host ?? "127.0.0.1";
|
|
8277
|
+
const port = opts.port ?? 3737;
|
|
8278
|
+
const version = readVersion();
|
|
8279
|
+
const server = await runApi({
|
|
8280
|
+
host: host2,
|
|
8281
|
+
port,
|
|
8282
|
+
backend,
|
|
8283
|
+
version,
|
|
8284
|
+
...opts.allowedHosts ? { allowedHosts: opts.allowedHosts } : {},
|
|
8285
|
+
...opts.allowedOrigins ? { allowedOrigins: opts.allowedOrigins } : {},
|
|
8286
|
+
...token ? { token } : {},
|
|
8287
|
+
...opts.graphql === false ? { graphql: false } : {},
|
|
8288
|
+
...opts.tenant ? { tenant: { defaultTenant: normalizeTenant(opts.tenant) } } : {},
|
|
8289
|
+
log: log2
|
|
8290
|
+
});
|
|
8291
|
+
const graphqlNote = opts.graphql === false ? " [REST only]" : " + /graphql";
|
|
8292
|
+
log2(
|
|
8293
|
+
`Cartograph API (REST${graphqlNote}) on http://${host2}:${port}/v1${token ? " (auth: bearer token required)" : ""} (tenant: ${normalizeTenant(opts.tenant)})`
|
|
8294
|
+
);
|
|
8295
|
+
return server;
|
|
8296
|
+
}
|
|
8297
|
+
|
|
6962
8298
|
// src/installer/format.ts
|
|
6963
8299
|
var import_smol_toml = require("smol-toml");
|
|
6964
8300
|
var import_yaml = require("yaml");
|
|
@@ -7031,8 +8367,8 @@ function defaultServerEntry(opts = {}) {
|
|
|
7031
8367
|
}
|
|
7032
8368
|
|
|
7033
8369
|
// src/installer/install.ts
|
|
7034
|
-
var
|
|
7035
|
-
var
|
|
8370
|
+
var import_node_fs6 = require("fs");
|
|
8371
|
+
var import_node_path6 = require("path");
|
|
7036
8372
|
var import_node_os4 = require("os");
|
|
7037
8373
|
function currentOs() {
|
|
7038
8374
|
if (process.platform === "win32") return "win";
|
|
@@ -7047,8 +8383,8 @@ function planInstall(spec, ctx, opts) {
|
|
|
7047
8383
|
if (!path) {
|
|
7048
8384
|
throw new Error(`${spec.label} does not support the "${ctx.scope}" scope.`);
|
|
7049
8385
|
}
|
|
7050
|
-
const fileExists = (0,
|
|
7051
|
-
const before = fileExists ? (0,
|
|
8386
|
+
const fileExists = (0, import_node_fs6.existsSync)(path);
|
|
8387
|
+
const before = fileExists ? (0, import_node_fs6.readFileSync)(path, "utf8") : "";
|
|
7052
8388
|
const existing = parseConfig(before, spec.format);
|
|
7053
8389
|
const merged = spec.apply(existing, opts.serverName ?? DEFAULT_SERVER_NAME, opts.entry);
|
|
7054
8390
|
const after = serializeConfig(merged, spec.format);
|
|
@@ -7065,8 +8401,8 @@ function planInstall(spec, ctx, opts) {
|
|
|
7065
8401
|
};
|
|
7066
8402
|
}
|
|
7067
8403
|
function applyInstall(plan) {
|
|
7068
|
-
(0,
|
|
7069
|
-
(0,
|
|
8404
|
+
(0, import_node_fs6.mkdirSync)((0, import_node_path6.dirname)(plan.path), { recursive: true });
|
|
8405
|
+
(0, import_node_fs6.writeFileSync)(plan.path, plan.after, "utf8");
|
|
7070
8406
|
}
|
|
7071
8407
|
function renderDiff(before, after) {
|
|
7072
8408
|
if (before === after) return " (no changes)";
|
|
@@ -7086,7 +8422,7 @@ function renderDiff(before, after) {
|
|
|
7086
8422
|
}
|
|
7087
8423
|
|
|
7088
8424
|
// src/installer/registry.ts
|
|
7089
|
-
var
|
|
8425
|
+
var import_node_path7 = require("path");
|
|
7090
8426
|
function jsonKeyedClient(args) {
|
|
7091
8427
|
return {
|
|
7092
8428
|
id: args.id,
|
|
@@ -7101,31 +8437,31 @@ var claudeCode = jsonKeyedClient({
|
|
|
7101
8437
|
id: "claude-code",
|
|
7102
8438
|
label: "Claude Code",
|
|
7103
8439
|
key: "mcpServers",
|
|
7104
|
-
globalPath: (ctx) => (0,
|
|
7105
|
-
projectPath: (ctx) => (0,
|
|
8440
|
+
globalPath: (ctx) => (0, import_node_path7.join)(ctx.home, ".claude.json"),
|
|
8441
|
+
projectPath: (ctx) => (0, import_node_path7.join)(ctx.cwd, ".mcp.json")
|
|
7106
8442
|
});
|
|
7107
8443
|
var cursor = jsonKeyedClient({
|
|
7108
8444
|
id: "cursor",
|
|
7109
8445
|
label: "Cursor",
|
|
7110
8446
|
key: "mcpServers",
|
|
7111
|
-
globalPath: (ctx) => (0,
|
|
7112
|
-
projectPath: (ctx) => (0,
|
|
8447
|
+
globalPath: (ctx) => (0, import_node_path7.join)(ctx.home, ".cursor", "mcp.json"),
|
|
8448
|
+
projectPath: (ctx) => (0, import_node_path7.join)(ctx.cwd, ".cursor", "mcp.json")
|
|
7113
8449
|
});
|
|
7114
8450
|
function vscodeServerObject(entry) {
|
|
7115
8451
|
if (entry.url) return { type: "http", url: entry.url, ...entry.env ? { env: entry.env } : {} };
|
|
7116
8452
|
return { type: "stdio", command: entry.command, args: entry.args ?? [], ...entry.env ? { env: entry.env } : {} };
|
|
7117
8453
|
}
|
|
7118
8454
|
function vscodeUserDir(ctx) {
|
|
7119
|
-
if (ctx.os === "win") return (0,
|
|
7120
|
-
if (ctx.os === "mac") return (0,
|
|
7121
|
-
return (0,
|
|
8455
|
+
if (ctx.os === "win") return (0, import_node_path7.join)(ctx.env.APPDATA ?? (0, import_node_path7.join)(ctx.home, "AppData", "Roaming"), "Code", "User");
|
|
8456
|
+
if (ctx.os === "mac") return (0, import_node_path7.join)(ctx.home, "Library", "Application Support", "Code", "User");
|
|
8457
|
+
return (0, import_node_path7.join)(ctx.home, ".config", "Code", "User");
|
|
7122
8458
|
}
|
|
7123
8459
|
var vscode = {
|
|
7124
8460
|
id: "vscode",
|
|
7125
8461
|
label: "VS Code (Copilot)",
|
|
7126
8462
|
format: "json",
|
|
7127
8463
|
note: "Uses the `servers` key (not `mcpServers`) \u2014 the most common copy-paste mistake.",
|
|
7128
|
-
path: (ctx) => ctx.scope === "project" ? (0,
|
|
8464
|
+
path: (ctx) => ctx.scope === "project" ? (0, import_node_path7.join)(ctx.cwd, ".vscode", "mcp.json") : (0, import_node_path7.join)(vscodeUserDir(ctx), "mcp.json"),
|
|
7129
8465
|
apply: (existing, name, entry) => deepMerge(existing, { servers: { [name]: vscodeServerObject(entry) } })
|
|
7130
8466
|
};
|
|
7131
8467
|
var codex = {
|
|
@@ -7133,17 +8469,17 @@ var codex = {
|
|
|
7133
8469
|
label: "Codex CLI",
|
|
7134
8470
|
format: "toml",
|
|
7135
8471
|
note: 'Project scope only loads in "trusted" projects.',
|
|
7136
|
-
path: (ctx) => ctx.scope === "project" ? (0,
|
|
8472
|
+
path: (ctx) => ctx.scope === "project" ? (0, import_node_path7.join)(ctx.cwd, ".codex", "config.toml") : (0, import_node_path7.join)(ctx.home, ".codex", "config.toml"),
|
|
7137
8473
|
apply: (existing, name, entry) => deepMerge(existing, { mcp_servers: { [name]: mcpServerObject(entry) } })
|
|
7138
8474
|
};
|
|
7139
8475
|
var windsurf = jsonKeyedClient({
|
|
7140
8476
|
id: "windsurf",
|
|
7141
8477
|
label: "Windsurf",
|
|
7142
8478
|
key: "mcpServers",
|
|
7143
|
-
globalPath: (ctx) => (0,
|
|
8479
|
+
globalPath: (ctx) => (0, import_node_path7.join)(ctx.home, ".codeium", "windsurf", "mcp_config.json")
|
|
7144
8480
|
});
|
|
7145
8481
|
function codeGlobalStorage(ctx, extensionId) {
|
|
7146
|
-
return (0,
|
|
8482
|
+
return (0, import_node_path7.join)(vscodeUserDir(ctx), "globalStorage", extensionId, "settings", "cline_mcp_settings.json");
|
|
7147
8483
|
}
|
|
7148
8484
|
var cline = {
|
|
7149
8485
|
id: "cline",
|
|
@@ -7158,7 +8494,7 @@ var roo = {
|
|
|
7158
8494
|
label: "Roo Code",
|
|
7159
8495
|
format: "json",
|
|
7160
8496
|
note: "Project .roo/mcp.json takes precedence over the global settings.",
|
|
7161
|
-
path: (ctx) => ctx.scope === "project" ? (0,
|
|
8497
|
+
path: (ctx) => ctx.scope === "project" ? (0, import_node_path7.join)(ctx.cwd, ".roo", "mcp.json") : codeGlobalStorage(ctx, "rooveterinaryinc.roo-cline"),
|
|
7162
8498
|
apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } })
|
|
7163
8499
|
};
|
|
7164
8500
|
var zed = {
|
|
@@ -7167,9 +8503,9 @@ var zed = {
|
|
|
7167
8503
|
format: "json",
|
|
7168
8504
|
note: 'Manual servers need "source": "custom"; remote uses an mcp-remote bridge.',
|
|
7169
8505
|
path: (ctx) => {
|
|
7170
|
-
if (ctx.scope === "project") return (0,
|
|
7171
|
-
if (ctx.os === "win") return (0,
|
|
7172
|
-
return (0,
|
|
8506
|
+
if (ctx.scope === "project") return (0, import_node_path7.join)(ctx.cwd, ".zed", "settings.json");
|
|
8507
|
+
if (ctx.os === "win") return (0, import_node_path7.join)(ctx.env.APPDATA ?? (0, import_node_path7.join)(ctx.home, "AppData", "Roaming"), "Zed", "settings.json");
|
|
8508
|
+
return (0, import_node_path7.join)(ctx.home, ".config", "zed", "settings.json");
|
|
7173
8509
|
},
|
|
7174
8510
|
apply: (existing, name, entry) => {
|
|
7175
8511
|
const inner = entry.url ? { source: "custom", url: entry.url } : { source: "custom", command: entry.command, args: entry.args ?? [], ...entry.env ? { env: entry.env } : {} };
|
|
@@ -7180,14 +8516,14 @@ var junie = {
|
|
|
7180
8516
|
id: "junie",
|
|
7181
8517
|
label: "JetBrains / Junie",
|
|
7182
8518
|
format: "json",
|
|
7183
|
-
path: (ctx) => ctx.scope === "project" ? (0,
|
|
8519
|
+
path: (ctx) => ctx.scope === "project" ? (0, import_node_path7.join)(ctx.cwd, ".junie", "mcp", "mcp.json") : (0, import_node_path7.join)(ctx.home, ".junie", "mcp", "mcp.json"),
|
|
7184
8520
|
apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } })
|
|
7185
8521
|
};
|
|
7186
8522
|
var gemini = {
|
|
7187
8523
|
id: "gemini",
|
|
7188
8524
|
label: "Gemini CLI",
|
|
7189
8525
|
format: "json",
|
|
7190
|
-
path: (ctx) => ctx.scope === "project" ? (0,
|
|
8526
|
+
path: (ctx) => ctx.scope === "project" ? (0, import_node_path7.join)(ctx.cwd, ".gemini", "settings.json") : (0, import_node_path7.join)(ctx.home, ".gemini", "settings.json"),
|
|
7191
8527
|
apply: (existing, name, entry) => {
|
|
7192
8528
|
const inner = entry.url ? { httpUrl: entry.url, ...entry.env ? { env: entry.env } : {} } : mcpServerObject(entry);
|
|
7193
8529
|
return deepMerge(existing, { mcpServers: { [name]: inner } });
|
|
@@ -7199,8 +8535,8 @@ var goose = {
|
|
|
7199
8535
|
format: "yaml",
|
|
7200
8536
|
note: "Verify the extension shape against current Goose docs; built-ins are left untouched.",
|
|
7201
8537
|
path: (ctx) => {
|
|
7202
|
-
if (ctx.os === "win") return (0,
|
|
7203
|
-
return (0,
|
|
8538
|
+
if (ctx.os === "win") return (0, import_node_path7.join)(ctx.env.APPDATA ?? (0, import_node_path7.join)(ctx.home, "AppData", "Roaming"), "Block", "goose", "config", "config.yaml");
|
|
8539
|
+
return (0, import_node_path7.join)(ctx.home, ".config", "goose", "config.yaml");
|
|
7204
8540
|
},
|
|
7205
8541
|
apply: (existing, name, entry) => {
|
|
7206
8542
|
const inner = entry.url ? { name, type: "streamable_http", enabled: true, uri: entry.url, ...entry.env ? { env: entry.env } : {} } : { name, type: "stdio", enabled: true, command: entry.command, args: entry.args ?? [], ...entry.env ? { env: entry.env } : {} };
|
|
@@ -7215,7 +8551,7 @@ var openhands = {
|
|
|
7215
8551
|
label: "OpenHands",
|
|
7216
8552
|
format: "toml",
|
|
7217
8553
|
note: "SHTTP is preferred; SSE is legacy. Only api_key is supported (no arbitrary headers).",
|
|
7218
|
-
path: (ctx) => ctx.scope === "project" ? (0,
|
|
8554
|
+
path: (ctx) => ctx.scope === "project" ? (0, import_node_path7.join)(ctx.cwd, "config.toml") : (0, import_node_path7.join)(ctx.home, ".openhands", "config.toml"),
|
|
7219
8555
|
apply: (existing, name, entry) => {
|
|
7220
8556
|
const mcp = isObj(existing.mcp) ? { ...existing.mcp } : {};
|
|
7221
8557
|
const key = entry.url ? "shttp_servers" : "stdio_servers";
|
|
@@ -7236,9 +8572,9 @@ var claudeDesktop = {
|
|
|
7236
8572
|
note: "One-click install is also available via the .mcpb bundle (npm run build:mcpb).",
|
|
7237
8573
|
path: (ctx) => {
|
|
7238
8574
|
if (ctx.scope === "project") return void 0;
|
|
7239
|
-
if (ctx.os === "win") return (0,
|
|
7240
|
-
if (ctx.os === "mac") return (0,
|
|
7241
|
-
return (0,
|
|
8575
|
+
if (ctx.os === "win") return (0, import_node_path7.join)(ctx.env.APPDATA ?? (0, import_node_path7.join)(ctx.home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
8576
|
+
if (ctx.os === "mac") return (0, import_node_path7.join)(ctx.home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
8577
|
+
return (0, import_node_path7.join)(ctx.home, ".config", "Claude", "claude_desktop_config.json");
|
|
7242
8578
|
},
|
|
7243
8579
|
apply: (existing, name, entry) => deepMerge(existing, { mcpServers: { [name]: mcpServerObject(entry) } })
|
|
7244
8580
|
};
|
|
@@ -7439,13 +8775,13 @@ function createClaudeProvider() {
|
|
|
7439
8775
|
}
|
|
7440
8776
|
|
|
7441
8777
|
// src/providers/shell.ts
|
|
7442
|
-
var
|
|
8778
|
+
var import_zod9 = require("zod");
|
|
7443
8779
|
function createBashTool() {
|
|
7444
8780
|
const shell = IS_WIN ? "powershell" : "posix";
|
|
7445
8781
|
return {
|
|
7446
8782
|
name: "Bash",
|
|
7447
8783
|
description: "Run a read-only shell command (inspect ports, processes, config). Mutating or destructive commands are blocked by the read-only allowlist.",
|
|
7448
|
-
inputShape: { command:
|
|
8784
|
+
inputShape: { command: import_zod9.z.string().describe("The read-only shell command to run") },
|
|
7449
8785
|
annotations: { readOnlyHint: true, openWorldHint: true },
|
|
7450
8786
|
handler: async (args) => {
|
|
7451
8787
|
const command = String(args["command"] ?? "").trim();
|
|
@@ -7914,8 +9250,8 @@ Use ask_user when you need context from the user.`;
|
|
|
7914
9250
|
}
|
|
7915
9251
|
|
|
7916
9252
|
// src/cost.ts
|
|
7917
|
-
var
|
|
7918
|
-
var
|
|
9253
|
+
var import_node_fs7 = require("fs");
|
|
9254
|
+
var import_node_path8 = require("path");
|
|
7919
9255
|
function splitCsvLine(line) {
|
|
7920
9256
|
const out = [];
|
|
7921
9257
|
let cur = "";
|
|
@@ -7993,7 +9329,7 @@ var CsvCostSource = class {
|
|
|
7993
9329
|
}
|
|
7994
9330
|
id;
|
|
7995
9331
|
async fetch() {
|
|
7996
|
-
const text = (0,
|
|
9332
|
+
const text = (0, import_node_fs7.readFileSync)((0, import_node_path8.resolve)(this.opts.filePath), "utf-8");
|
|
7997
9333
|
const records = parseCostCsv(text);
|
|
7998
9334
|
const match = this.opts.match ?? "nodeId";
|
|
7999
9335
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -8024,19 +9360,19 @@ async function enrichCosts(db, sessionId, source) {
|
|
|
8024
9360
|
let matched = 0;
|
|
8025
9361
|
const unmatchedIds = [];
|
|
8026
9362
|
for (const [nodeId, rec] of records) {
|
|
8027
|
-
const
|
|
9363
|
+
const ok3 = db.enrichNodeAttribution(sessionId, nodeId, {
|
|
8028
9364
|
owner: rec.owner ?? void 0,
|
|
8029
9365
|
cost: rec.cost ?? void 0
|
|
8030
9366
|
});
|
|
8031
|
-
if (
|
|
9367
|
+
if (ok3) matched++;
|
|
8032
9368
|
else unmatchedIds.push(nodeId);
|
|
8033
9369
|
}
|
|
8034
9370
|
return { source: source.id, total: records.size, matched, unmatched: unmatchedIds.length, unmatchedIds };
|
|
8035
9371
|
}
|
|
8036
9372
|
|
|
8037
9373
|
// src/exporter.ts
|
|
8038
|
-
var
|
|
8039
|
-
var
|
|
9374
|
+
var import_node_fs8 = require("fs");
|
|
9375
|
+
var import_node_path9 = require("path");
|
|
8040
9376
|
|
|
8041
9377
|
// src/hex.ts
|
|
8042
9378
|
function hexToPixel(q, r, size) {
|
|
@@ -8135,10 +9471,10 @@ function assignColors(domains) {
|
|
|
8135
9471
|
return result;
|
|
8136
9472
|
}
|
|
8137
9473
|
function shadeVariant(hex, amount) {
|
|
8138
|
-
const
|
|
8139
|
-
const r = Math.min(255, (
|
|
8140
|
-
const g = Math.min(255, (
|
|
8141
|
-
const b = Math.min(255, (
|
|
9474
|
+
const num2 = parseInt(hex.replace("#", ""), 16);
|
|
9475
|
+
const r = Math.min(255, (num2 >> 16) + amount);
|
|
9476
|
+
const g = Math.min(255, (num2 >> 8 & 255) + amount);
|
|
9477
|
+
const b = Math.min(255, (num2 & 255) + amount);
|
|
8142
9478
|
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
8143
9479
|
}
|
|
8144
9480
|
function groupByDomain(assets) {
|
|
@@ -9728,28 +11064,28 @@ function exportComplianceReport(report, format) {
|
|
|
9728
11064
|
return lines.join("\n");
|
|
9729
11065
|
}
|
|
9730
11066
|
function exportAll(db, sessionId, outputDir, formats = ["mermaid", "json", "yaml", "html", "map", "discovery"]) {
|
|
9731
|
-
(0,
|
|
11067
|
+
(0, import_node_fs8.mkdirSync)(outputDir, { recursive: true });
|
|
9732
11068
|
const nodes = db.getNodes(sessionId);
|
|
9733
11069
|
const edges = db.getEdges(sessionId);
|
|
9734
|
-
const jgfPath = (0,
|
|
9735
|
-
(0,
|
|
11070
|
+
const jgfPath = (0, import_node_path9.join)(outputDir, "cartography-graph.jgf.json");
|
|
11071
|
+
(0, import_node_fs8.writeFileSync)(jgfPath, exportJGF(nodes, edges));
|
|
9736
11072
|
if (formats.includes("mermaid")) {
|
|
9737
|
-
(0,
|
|
9738
|
-
(0,
|
|
11073
|
+
(0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "topology.mermaid"), generateTopologyMermaid(nodes, edges));
|
|
11074
|
+
(0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "dependencies.mermaid"), generateDependencyMermaid(nodes, edges));
|
|
9739
11075
|
}
|
|
9740
11076
|
if (formats.includes("json")) {
|
|
9741
|
-
(0,
|
|
11077
|
+
(0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "catalog.json"), exportJSON(db, sessionId));
|
|
9742
11078
|
}
|
|
9743
11079
|
if (formats.includes("yaml")) {
|
|
9744
|
-
(0,
|
|
11080
|
+
(0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "catalog-info.yaml"), exportBackstageYAML(nodes, edges));
|
|
9745
11081
|
}
|
|
9746
11082
|
if (formats.includes("html") || formats.includes("map") || formats.includes("discovery")) {
|
|
9747
|
-
(0,
|
|
11083
|
+
(0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "discovery.html"), exportDiscoveryApp(nodes, edges));
|
|
9748
11084
|
}
|
|
9749
11085
|
if (formats.includes("cost")) {
|
|
9750
11086
|
const summary = db.getGraphSummary(sessionId);
|
|
9751
|
-
(0,
|
|
9752
|
-
(0,
|
|
11087
|
+
(0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "cost-by-domain.csv"), exportCostCSV(summary));
|
|
11088
|
+
(0, import_node_fs8.writeFileSync)((0, import_node_path9.join)(outputDir, "cost-summary.json"), exportCostSummary(summary));
|
|
9753
11089
|
}
|
|
9754
11090
|
}
|
|
9755
11091
|
|
|
@@ -9781,7 +11117,7 @@ function formatComplianceText(report) {
|
|
|
9781
11117
|
}
|
|
9782
11118
|
|
|
9783
11119
|
// src/config.ts
|
|
9784
|
-
var
|
|
11120
|
+
var import_node_fs9 = require("fs");
|
|
9785
11121
|
var ConfigError = class extends Error {
|
|
9786
11122
|
constructor(message) {
|
|
9787
11123
|
super(message);
|
|
@@ -9806,7 +11142,7 @@ function loadConfig(path) {
|
|
|
9806
11142
|
function readConfigFile(path) {
|
|
9807
11143
|
let raw;
|
|
9808
11144
|
try {
|
|
9809
|
-
raw = (0,
|
|
11145
|
+
raw = (0, import_node_fs9.readFileSync)(path, "utf-8");
|
|
9810
11146
|
} catch (err) {
|
|
9811
11147
|
throw new ConfigError(
|
|
9812
11148
|
`Cannot read config file ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
@@ -10067,7 +11403,7 @@ async function pushDeltas(config, items, opts = {}) {
|
|
|
10067
11403
|
sentHashes.push(...batch.map((b) => b.contentHash));
|
|
10068
11404
|
continue;
|
|
10069
11405
|
}
|
|
10070
|
-
let
|
|
11406
|
+
let ok3 = false;
|
|
10071
11407
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
10072
11408
|
const controller = new AbortController();
|
|
10073
11409
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -10086,7 +11422,7 @@ async function pushDeltas(config, items, opts = {}) {
|
|
|
10086
11422
|
const elapsed = Date.now() - startedAt;
|
|
10087
11423
|
if (res.ok) {
|
|
10088
11424
|
log2(`pushed ${batch.length} item(s) \u2192 ${safeUrl} [${res.status}] ${elapsed}ms (attempt ${attempt + 1})`);
|
|
10089
|
-
|
|
11425
|
+
ok3 = true;
|
|
10090
11426
|
break;
|
|
10091
11427
|
}
|
|
10092
11428
|
if (res.status >= 400 && res.status < 500) {
|
|
@@ -10105,7 +11441,7 @@ async function pushDeltas(config, items, opts = {}) {
|
|
|
10105
11441
|
await sleep(base + Math.floor(Math.random() * 100));
|
|
10106
11442
|
}
|
|
10107
11443
|
}
|
|
10108
|
-
if (
|
|
11444
|
+
if (ok3) {
|
|
10109
11445
|
sent += batch.length;
|
|
10110
11446
|
sentHashes.push(...batch.map((b) => b.contentHash));
|
|
10111
11447
|
} else {
|
|
@@ -10164,14 +11500,14 @@ function runSyncClassify(db, sessionId, config, opts = {}) {
|
|
|
10164
11500
|
|
|
10165
11501
|
// src/preflight.ts
|
|
10166
11502
|
var import_node_child_process2 = require("child_process");
|
|
10167
|
-
var
|
|
10168
|
-
var
|
|
11503
|
+
var import_node_fs10 = require("fs");
|
|
11504
|
+
var import_node_path10 = require("path");
|
|
10169
11505
|
function isOAuthLoggedIn() {
|
|
10170
11506
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
|
|
10171
|
-
const credFile = (0,
|
|
10172
|
-
if (!(0,
|
|
11507
|
+
const credFile = (0, import_node_path10.join)(home, ".claude", ".credentials.json");
|
|
11508
|
+
if (!(0, import_node_fs10.existsSync)(credFile)) return false;
|
|
10173
11509
|
try {
|
|
10174
|
-
const creds = JSON.parse((0,
|
|
11510
|
+
const creds = JSON.parse((0, import_node_fs10.readFileSync)(credFile, "utf8"));
|
|
10175
11511
|
const oauth = creds["claudeAiOauth"];
|
|
10176
11512
|
return typeof oauth?.["accessToken"] === "string" && oauth["accessToken"].length > 0;
|
|
10177
11513
|
} catch {
|
|
@@ -10238,37 +11574,51 @@ function checkClaudePrerequisites() {
|
|
|
10238
11574
|
DriftConfigSchema,
|
|
10239
11575
|
INGEST_SCHEMA_VERSION,
|
|
10240
11576
|
IngestEnvelopeSchema,
|
|
11577
|
+
InvalidTenantError,
|
|
11578
|
+
JiraSink,
|
|
11579
|
+
LOOPBACK_HOSTS,
|
|
10241
11580
|
MCP_BIN,
|
|
11581
|
+
NotFoundError,
|
|
10242
11582
|
PACKAGE_NAME,
|
|
11583
|
+
PAGERDUTY_ENQUEUE_URL,
|
|
10243
11584
|
PERSONAL,
|
|
10244
11585
|
PORT_MAP,
|
|
10245
11586
|
PRIVATE_IP,
|
|
10246
11587
|
PUSH_SCHEMA_VERSION,
|
|
11588
|
+
PagerDutySink,
|
|
10247
11589
|
ProviderRegistry,
|
|
10248
11590
|
RELATION_TO_DIRECTION,
|
|
10249
11591
|
RuleCheckSchema,
|
|
10250
11592
|
RulesetSchema,
|
|
10251
11593
|
SCAN_ARG_PATTERNS,
|
|
11594
|
+
SDL,
|
|
10252
11595
|
SEVERITY_WEIGHT,
|
|
10253
11596
|
SHARING_LEVELS,
|
|
10254
11597
|
ScannerRegistry,
|
|
10255
11598
|
ScannerShape,
|
|
10256
11599
|
SharingLevelSchema,
|
|
11600
|
+
SlackSink,
|
|
11601
|
+
SqliteQueryBackend,
|
|
10257
11602
|
SqliteStoreBackend,
|
|
10258
11603
|
StdoutSink,
|
|
11604
|
+
TENANT_HEADER,
|
|
10259
11605
|
VectorStore,
|
|
10260
11606
|
WebhookSink,
|
|
10261
11607
|
applyInstall,
|
|
10262
11608
|
applySharingLevel,
|
|
10263
11609
|
assertReadOnly,
|
|
11610
|
+
assertSafeBind,
|
|
10264
11611
|
assertSafeScanArg,
|
|
10265
11612
|
assignColors,
|
|
11613
|
+
bearerToken,
|
|
10266
11614
|
bookmarksScanner,
|
|
10267
11615
|
buildCartographyToolHandlers,
|
|
10268
11616
|
buildMapData,
|
|
11617
|
+
buildOpenApiDocument,
|
|
10269
11618
|
buildReport,
|
|
10270
11619
|
buildSinks,
|
|
10271
11620
|
centralDbFromEnv,
|
|
11621
|
+
checkBearer,
|
|
10272
11622
|
checkPrerequisites,
|
|
10273
11623
|
checkReadOnly,
|
|
10274
11624
|
clampText,
|
|
@@ -10296,10 +11646,12 @@ function checkClaudePrerequisites() {
|
|
|
10296
11646
|
createOpenAIProvider,
|
|
10297
11647
|
createScanRunner,
|
|
10298
11648
|
createSemanticSearch,
|
|
11649
|
+
createSqliteQueryBackend,
|
|
10299
11650
|
currentOs,
|
|
10300
11651
|
cursorDeeplink,
|
|
10301
11652
|
databasesScanner,
|
|
10302
11653
|
deepMerge,
|
|
11654
|
+
defaultAllowedHosts,
|
|
10303
11655
|
defaultConfig,
|
|
10304
11656
|
defaultContext,
|
|
10305
11657
|
defaultProviderRegistry,
|
|
@@ -10316,6 +11668,7 @@ function checkClaudePrerequisites() {
|
|
|
10316
11668
|
evaluateCheck,
|
|
10317
11669
|
evaluateRule,
|
|
10318
11670
|
evidenceLine,
|
|
11671
|
+
executeGraphql,
|
|
10319
11672
|
executeNlQuery,
|
|
10320
11673
|
exportAll,
|
|
10321
11674
|
exportBackstageYAML,
|
|
@@ -10329,6 +11682,9 @@ function checkClaudePrerequisites() {
|
|
|
10329
11682
|
filterBySeverity,
|
|
10330
11683
|
findAnonViolations,
|
|
10331
11684
|
formatComplianceText,
|
|
11685
|
+
formatJira,
|
|
11686
|
+
formatPagerDuty,
|
|
11687
|
+
formatSlack,
|
|
10332
11688
|
generateDependencyMermaid,
|
|
10333
11689
|
generateDiffMermaid,
|
|
10334
11690
|
generateTopologyMermaid,
|
|
@@ -10336,6 +11692,7 @@ function checkClaudePrerequisites() {
|
|
|
10336
11692
|
getRuleset,
|
|
10337
11693
|
globalId,
|
|
10338
11694
|
groupByDomain,
|
|
11695
|
+
handleGraphqlGet,
|
|
10339
11696
|
hexCorners,
|
|
10340
11697
|
hexDistance,
|
|
10341
11698
|
hexNeighbors,
|
|
@@ -10346,9 +11703,11 @@ function checkClaudePrerequisites() {
|
|
|
10346
11703
|
hostname,
|
|
10347
11704
|
ingestEnvelope,
|
|
10348
11705
|
installedAppsScanner,
|
|
11706
|
+
isLoopbackHost,
|
|
10349
11707
|
isPersonalHost,
|
|
10350
11708
|
isReadOnlyCommand,
|
|
10351
11709
|
isRemembered,
|
|
11710
|
+
isSecureWebhookUrl,
|
|
10352
11711
|
k8sScanner,
|
|
10353
11712
|
keyMetaOf,
|
|
10354
11713
|
layoutClusters,
|
|
@@ -10374,6 +11733,7 @@ function checkClaudePrerequisites() {
|
|
|
10374
11733
|
normalizeTenant,
|
|
10375
11734
|
orgKeyPath,
|
|
10376
11735
|
osUser,
|
|
11736
|
+
parseApiArgs,
|
|
10377
11737
|
parseComposeDeps,
|
|
10378
11738
|
parseConfig,
|
|
10379
11739
|
parseConnectionString,
|
|
@@ -10386,6 +11746,7 @@ function checkClaudePrerequisites() {
|
|
|
10386
11746
|
pixelToHex,
|
|
10387
11747
|
planInstall,
|
|
10388
11748
|
portsScanner,
|
|
11749
|
+
postJson,
|
|
10389
11750
|
previewShare,
|
|
10390
11751
|
pseudonymize,
|
|
10391
11752
|
pseudonymizeFragment,
|
|
@@ -10399,10 +11760,12 @@ function checkClaudePrerequisites() {
|
|
|
10399
11760
|
resolveEffectiveLevel,
|
|
10400
11761
|
resolveNlQuery,
|
|
10401
11762
|
resolveSharingLevel,
|
|
11763
|
+
resolveTenant,
|
|
10402
11764
|
revalidateAnonymized,
|
|
10403
11765
|
reversalKey,
|
|
10404
11766
|
reversePseudonym,
|
|
10405
11767
|
rotateOrgKey,
|
|
11768
|
+
runApi,
|
|
10406
11769
|
runDiscovery,
|
|
10407
11770
|
runDrift,
|
|
10408
11771
|
runHttp,
|
|
@@ -10425,8 +11788,11 @@ function checkClaudePrerequisites() {
|
|
|
10425
11788
|
shareHash,
|
|
10426
11789
|
splitSegments,
|
|
10427
11790
|
stableStringify,
|
|
11791
|
+
startApi,
|
|
10428
11792
|
stripSensitive,
|
|
11793
|
+
timingSafeEqual,
|
|
10429
11794
|
validateScanner,
|
|
10430
|
-
vscodeDeeplink
|
|
11795
|
+
vscodeDeeplink,
|
|
11796
|
+
zodToJsonSchema
|
|
10431
11797
|
});
|
|
10432
11798
|
//# sourceMappingURL=index.cjs.map
|