@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/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
- if (s.type === "webhook" && !s.url) {
405
- ctx.addIssue({ code: "custom", path: ["sinks", i, "url"], message: "webhook sink requires a url" });
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 = project ? ` --project ${project}` : "";
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
- const stripped = raw.replace(/\/.*$/, "").replace(/\?.*$/, "").replace(/@.*:/, ":");
2020
- return stripped || raw;
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+.-]*:\/\/[^:@/\s]+):[^@/\s]+@/gi, "$1:***@");
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 project = args["project"];
2208
- if (project) assertSafeScanArg("gcp-project", project);
2209
- return runScannerTool(cloudGcpScanner, project ? `project=${project}` : "");
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 notFound = [];
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 notFound.push(t);
2417
+ else notFound2.push(t);
2366
2418
  }
2367
2419
  results["TOOLS_FOUND"] = found.join("\n") || "(none found)";
2368
- results["TOOLS_NOT_FOUND"] = notFound.join(", ");
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
- if (!url) {
5080
- logWarn("webhook sink unavailable: no url configured", { sink: this.name });
5081
- return;
5082
- }
5083
- if (!isSecureWebhookUrl(url)) {
5084
- logWarn("webhook sink refused: insecure scheme (use https:// or a loopback host)", {
5085
- sink: this.name,
5086
- host: stripSensitive(url)
5087
- });
5088
- return;
5089
- }
5090
- try {
5091
- const res = await fetch(url, {
5092
- method: "POST",
5093
- headers: {
5094
- "content-type": "application/json",
5095
- ...token ? { authorization: `Bearer ${token}` } : {}
5096
- },
5097
- body: JSON.stringify(redactValue(alert)),
5098
- signal: AbortSignal.timeout(timeoutMs ?? 1e4)
5099
- });
5100
- if (!res.ok) {
5101
- logError("webhook sink failed", { sink: this.name, host: stripSensitive(url), status: res.status });
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
- if (s.type === "webhook") {
5119
- if (!s.url) continue;
5120
- sinks.push(new WebhookSink({
5121
- url: s.url,
5122
- token: s.token ?? process.env.CARTOGRAPHY_DRIFT_TOKEN,
5123
- timeoutMs: s.timeoutMs
5124
- }));
5125
- } else {
5126
- sinks.push(new StdoutSink());
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.2.0";
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
- const isLoopback = LOOPBACK_HOSTS2.has(host2);
6090
- if (!isLoopback && opts.allowedHosts === void 0) {
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
- const provided = bearerToken(req.headers["authorization"]);
6113
- if (!provided || !timingSafeEqual(provided, token)) {
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((resolve2) => httpServer.listen(port, host2, resolve2));
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 ok = await store.init();
6353
- if (!ok) {
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 import_node_fs5 = require("fs");
7035
- var import_node_path5 = require("path");
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, import_node_fs5.existsSync)(path);
7051
- const before = fileExists ? (0, import_node_fs5.readFileSync)(path, "utf8") : "";
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, import_node_fs5.mkdirSync)((0, import_node_path5.dirname)(plan.path), { recursive: true });
7069
- (0, import_node_fs5.writeFileSync)(plan.path, plan.after, "utf8");
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 import_node_path6 = require("path");
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, import_node_path6.join)(ctx.home, ".claude.json"),
7105
- projectPath: (ctx) => (0, import_node_path6.join)(ctx.cwd, ".mcp.json")
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, import_node_path6.join)(ctx.home, ".cursor", "mcp.json"),
7112
- projectPath: (ctx) => (0, import_node_path6.join)(ctx.cwd, ".cursor", "mcp.json")
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, import_node_path6.join)(ctx.env.APPDATA ?? (0, import_node_path6.join)(ctx.home, "AppData", "Roaming"), "Code", "User");
7120
- if (ctx.os === "mac") return (0, import_node_path6.join)(ctx.home, "Library", "Application Support", "Code", "User");
7121
- return (0, import_node_path6.join)(ctx.home, ".config", "Code", "User");
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, import_node_path6.join)(ctx.cwd, ".vscode", "mcp.json") : (0, import_node_path6.join)(vscodeUserDir(ctx), "mcp.json"),
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, import_node_path6.join)(ctx.cwd, ".codex", "config.toml") : (0, import_node_path6.join)(ctx.home, ".codex", "config.toml"),
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, import_node_path6.join)(ctx.home, ".codeium", "windsurf", "mcp_config.json")
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, import_node_path6.join)(vscodeUserDir(ctx), "globalStorage", extensionId, "settings", "cline_mcp_settings.json");
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, import_node_path6.join)(ctx.cwd, ".roo", "mcp.json") : codeGlobalStorage(ctx, "rooveterinaryinc.roo-cline"),
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, import_node_path6.join)(ctx.cwd, ".zed", "settings.json");
7171
- if (ctx.os === "win") return (0, import_node_path6.join)(ctx.env.APPDATA ?? (0, import_node_path6.join)(ctx.home, "AppData", "Roaming"), "Zed", "settings.json");
7172
- return (0, import_node_path6.join)(ctx.home, ".config", "zed", "settings.json");
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, import_node_path6.join)(ctx.cwd, ".junie", "mcp", "mcp.json") : (0, import_node_path6.join)(ctx.home, ".junie", "mcp", "mcp.json"),
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, import_node_path6.join)(ctx.cwd, ".gemini", "settings.json") : (0, import_node_path6.join)(ctx.home, ".gemini", "settings.json"),
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, import_node_path6.join)(ctx.env.APPDATA ?? (0, import_node_path6.join)(ctx.home, "AppData", "Roaming"), "Block", "goose", "config", "config.yaml");
7203
- return (0, import_node_path6.join)(ctx.home, ".config", "goose", "config.yaml");
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, import_node_path6.join)(ctx.cwd, "config.toml") : (0, import_node_path6.join)(ctx.home, ".openhands", "config.toml"),
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, import_node_path6.join)(ctx.env.APPDATA ?? (0, import_node_path6.join)(ctx.home, "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
7240
- if (ctx.os === "mac") return (0, import_node_path6.join)(ctx.home, "Library", "Application Support", "Claude", "claude_desktop_config.json");
7241
- return (0, import_node_path6.join)(ctx.home, ".config", "Claude", "claude_desktop_config.json");
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 import_zod8 = require("zod");
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: import_zod8.z.string().describe("The read-only shell command to run") },
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 import_node_fs6 = require("fs");
7918
- var import_node_path7 = require("path");
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, import_node_fs6.readFileSync)((0, import_node_path7.resolve)(this.opts.filePath), "utf-8");
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 ok = db.enrichNodeAttribution(sessionId, nodeId, {
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 (ok) matched++;
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 import_node_fs7 = require("fs");
8039
- var import_node_path8 = require("path");
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 num = parseInt(hex.replace("#", ""), 16);
8139
- const r = Math.min(255, (num >> 16) + amount);
8140
- const g = Math.min(255, (num >> 8 & 255) + amount);
8141
- const b = Math.min(255, (num & 255) + amount);
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, import_node_fs7.mkdirSync)(outputDir, { recursive: true });
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, import_node_path8.join)(outputDir, "cartography-graph.jgf.json");
9735
- (0, import_node_fs7.writeFileSync)(jgfPath, exportJGF(nodes, edges));
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, import_node_fs7.writeFileSync)((0, import_node_path8.join)(outputDir, "topology.mermaid"), generateTopologyMermaid(nodes, edges));
9738
- (0, import_node_fs7.writeFileSync)((0, import_node_path8.join)(outputDir, "dependencies.mermaid"), generateDependencyMermaid(nodes, edges));
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, import_node_fs7.writeFileSync)((0, import_node_path8.join)(outputDir, "catalog.json"), exportJSON(db, sessionId));
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, import_node_fs7.writeFileSync)((0, import_node_path8.join)(outputDir, "catalog-info.yaml"), exportBackstageYAML(nodes, edges));
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, import_node_fs7.writeFileSync)((0, import_node_path8.join)(outputDir, "discovery.html"), exportDiscoveryApp(nodes, edges));
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, import_node_fs7.writeFileSync)((0, import_node_path8.join)(outputDir, "cost-by-domain.csv"), exportCostCSV(summary));
9752
- (0, import_node_fs7.writeFileSync)((0, import_node_path8.join)(outputDir, "cost-summary.json"), exportCostSummary(summary));
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 import_node_fs8 = require("fs");
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, import_node_fs8.readFileSync)(path, "utf-8");
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 ok = false;
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
- ok = true;
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 (ok) {
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 import_node_fs9 = require("fs");
10168
- var import_node_path9 = require("path");
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, import_node_path9.join)(home, ".claude", ".credentials.json");
10172
- if (!(0, import_node_fs9.existsSync)(credFile)) return false;
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, import_node_fs9.readFileSync)(credFile, "utf8"));
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