@neat.is/core 0.4.28-dev.20260712 → 0.4.28-dev.20260713

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/cli.cjs CHANGED
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
61
61
  ]);
62
62
  const publicRead = opts.publicRead === true;
63
63
  app.addHook("preHandler", (req, reply, done) => {
64
- const path58 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path58) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path58)) {
64
+ const path61 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path61) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path61)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -194,8 +194,8 @@ function reshapeGrpcRequest(req) {
194
194
  };
195
195
  }
196
196
  function resolveProtoRoot() {
197
- const here = import_node_path45.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
198
- return import_node_path45.default.resolve(here, "..", "proto");
197
+ const here = import_node_path49.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
198
+ return import_node_path49.default.resolve(here, "..", "proto");
199
199
  }
200
200
  function loadTraceService() {
201
201
  const protoRoot = resolveProtoRoot();
@@ -263,13 +263,13 @@ async function startOtelGrpcReceiver(opts) {
263
263
  })
264
264
  };
265
265
  }
266
- var import_node_url2, import_node_path45, import_node_crypto2, grpc, protoLoader;
266
+ var import_node_url2, import_node_path49, import_node_crypto2, grpc, protoLoader;
267
267
  var init_otel_grpc = __esm({
268
268
  "src/otel-grpc.ts"() {
269
269
  "use strict";
270
270
  init_cjs_shims();
271
271
  import_node_url2 = require("url");
272
- import_node_path45 = __toESM(require("path"), 1);
272
+ import_node_path49 = __toESM(require("path"), 1);
273
273
  import_node_crypto2 = require("crypto");
274
274
  grpc = __toESM(require("@grpc/grpc-js"), 1);
275
275
  protoLoader = __toESM(require("@grpc/proto-loader"), 1);
@@ -362,8 +362,8 @@ function websocketChannelPathOf(attrs) {
362
362
  const v = attrs[key];
363
363
  if (typeof v === "string" && v.length > 0) {
364
364
  const q = v.indexOf("?");
365
- const path58 = q === -1 ? v : v.slice(0, q);
366
- if (path58.length > 0) return path58;
365
+ const path61 = q === -1 ? v : v.slice(0, q);
366
+ if (path61.length > 0) return path61;
367
367
  }
368
368
  }
369
369
  return void 0;
@@ -417,10 +417,10 @@ function parseOtlpRequest(body) {
417
417
  return out;
418
418
  }
419
419
  function loadProtoRoot() {
420
- const here = import_node_path46.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
421
- const protoRoot = import_node_path46.default.resolve(here, "..", "proto");
420
+ const here = import_node_path50.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
421
+ const protoRoot = import_node_path50.default.resolve(here, "..", "proto");
422
422
  const root = new import_protobufjs.default.Root();
423
- root.resolvePath = (_origin, target) => import_node_path46.default.resolve(protoRoot, target);
423
+ root.resolvePath = (_origin, target) => import_node_path50.default.resolve(protoRoot, target);
424
424
  root.loadSync(
425
425
  "opentelemetry/proto/collector/trace/v1/trace_service.proto",
426
426
  { keepCase: true }
@@ -648,12 +648,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
648
648
  }
649
649
  }
650
650
  }
651
- var import_node_path46, import_node_url3, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
651
+ var import_node_path50, import_node_url3, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
652
652
  var init_otel = __esm({
653
653
  "src/otel.ts"() {
654
654
  "use strict";
655
655
  init_cjs_shims();
656
- import_node_path46 = __toESM(require("path"), 1);
656
+ import_node_path50 = __toESM(require("path"), 1);
657
657
  import_node_url3 = require("url");
658
658
  import_fastify2 = __toESM(require("fastify"), 1);
659
659
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -690,8 +690,8 @@ __export(cli_exports, {
690
690
  });
691
691
  module.exports = __toCommonJS(cli_exports);
692
692
  init_cjs_shims();
693
- var import_node_path57 = __toESM(require("path"), 1);
694
- var import_node_fs37 = require("fs");
693
+ var import_node_path60 = __toESM(require("path"), 1);
694
+ var import_node_fs40 = require("fs");
695
695
 
696
696
  // src/banner.ts
697
697
  init_cjs_shims();
@@ -1256,19 +1256,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1256
1256
  function longestIncomingWalk(graph, start, maxDepth) {
1257
1257
  let best = { path: [start], edges: [] };
1258
1258
  const visited = /* @__PURE__ */ new Set([start]);
1259
- function step(node, path58, edges) {
1260
- if (path58.length > best.path.length) {
1261
- best = { path: [...path58], edges: [...edges] };
1259
+ function step(node, path61, edges) {
1260
+ if (path61.length > best.path.length) {
1261
+ best = { path: [...path61], edges: [...edges] };
1262
1262
  }
1263
- if (path58.length - 1 >= maxDepth) return;
1263
+ if (path61.length - 1 >= maxDepth) return;
1264
1264
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1265
1265
  for (const [srcId, edge] of incoming) {
1266
1266
  if (visited.has(srcId)) continue;
1267
1267
  visited.add(srcId);
1268
- path58.push(srcId);
1268
+ path61.push(srcId);
1269
1269
  edges.push(edge);
1270
- step(srcId, path58, edges);
1271
- path58.pop();
1270
+ step(srcId, path61, edges);
1271
+ path61.pop();
1272
1272
  edges.pop();
1273
1273
  visited.delete(srcId);
1274
1274
  }
@@ -1462,26 +1462,26 @@ function dominantFailingCall(graph, serviceId5, visited) {
1462
1462
  return best;
1463
1463
  }
1464
1464
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1465
- const path58 = [originServiceId];
1465
+ const path61 = [originServiceId];
1466
1466
  const edges = [];
1467
1467
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1468
1468
  let current = originServiceId;
1469
1469
  for (let depth = 0; depth < maxDepth; depth++) {
1470
1470
  const hop = dominantFailingCall(graph, current, visited);
1471
1471
  if (!hop) break;
1472
- path58.push(hop.nextService);
1472
+ path61.push(hop.nextService);
1473
1473
  edges.push(hop.edge);
1474
1474
  visited.add(hop.nextService);
1475
1475
  current = hop.nextService;
1476
1476
  }
1477
1477
  if (edges.length === 0) return null;
1478
- return { path: path58, edges, culprit: current };
1478
+ return { path: path61, edges, culprit: current };
1479
1479
  }
1480
1480
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1481
1481
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1482
1482
  if (!chain) return null;
1483
1483
  const culprit = chain.culprit;
1484
- const path58 = [...chain.path];
1484
+ const path61 = [...chain.path];
1485
1485
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1486
1486
  const baseConfidence = confidenceFromMix(chain.edges);
1487
1487
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1489,14 +1489,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1489
1489
  if (loc) {
1490
1490
  let rootCauseNode = culprit;
1491
1491
  if (loc.fileNode) {
1492
- path58.push(loc.fileNode);
1492
+ path61.push(loc.fileNode);
1493
1493
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1494
1494
  rootCauseNode = loc.fileNode;
1495
1495
  }
1496
1496
  return import_types.RootCauseResultSchema.parse({
1497
1497
  rootCauseNode,
1498
1498
  rootCauseReason: loc.rootCauseReason,
1499
- traversalPath: path58,
1499
+ traversalPath: path61,
1500
1500
  edgeProvenances,
1501
1501
  confidence,
1502
1502
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1508,7 +1508,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1508
1508
  return import_types.RootCauseResultSchema.parse({
1509
1509
  rootCauseNode: culprit,
1510
1510
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1511
- traversalPath: path58,
1511
+ traversalPath: path61,
1512
1512
  edgeProvenances,
1513
1513
  confidence,
1514
1514
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -6503,6 +6503,39 @@ function classifyImage(image) {
6503
6503
  if (last.startsWith("memcached")) return "memcached";
6504
6504
  return "container";
6505
6505
  }
6506
+ function lineContaining(raw, needle) {
6507
+ if (!needle) return void 0;
6508
+ const idx = raw.indexOf(needle);
6509
+ if (idx === -1) return void 0;
6510
+ let line = 1;
6511
+ for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
6512
+ return line;
6513
+ }
6514
+ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provider, evidenceFile, line) {
6515
+ let nodesAdded = 0;
6516
+ let edgesAdded = 0;
6517
+ const node = makeInfraNode(kind, name, provider);
6518
+ if (!graph.hasNode(node.id)) {
6519
+ graph.addNode(node.id, node);
6520
+ nodesAdded++;
6521
+ }
6522
+ if (node.id === anchorId) return { nodesAdded, edgesAdded };
6523
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, node.id, edgeType);
6524
+ if (!graph.hasEdge(edgeId)) {
6525
+ const edge = {
6526
+ id: edgeId,
6527
+ source: anchorId,
6528
+ target: node.id,
6529
+ type: edgeType,
6530
+ provenance: import_types21.Provenance.EXTRACTED,
6531
+ confidence: (0, import_types21.confidenceForExtracted)("structural"),
6532
+ evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6533
+ };
6534
+ graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
6535
+ edgesAdded++;
6536
+ }
6537
+ return { nodesAdded, edgesAdded };
6538
+ }
6506
6539
 
6507
6540
  // src/extract/infra/docker-compose.ts
6508
6541
  function dependsOnList(value) {
@@ -6862,7 +6895,7 @@ async function readWranglerConfig(dir) {
6862
6895
  }
6863
6896
  return null;
6864
6897
  }
6865
- function lineContaining(raw, needle) {
6898
+ function lineContaining2(raw, needle) {
6866
6899
  if (!needle) return void 0;
6867
6900
  const idx = raw.indexOf(needle);
6868
6901
  if (idx === -1) return void 0;
@@ -6986,7 +7019,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
6986
7019
  "cloudflare-route",
6987
7020
  route,
6988
7021
  evidenceFile,
6989
- lineContaining(raw, route)
7022
+ lineContaining2(raw, route)
6990
7023
  );
6991
7024
  nodesAdded += result.nodesAdded;
6992
7025
  edgesAdded += result.edgesAdded;
@@ -7010,7 +7043,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7010
7043
  group.kind,
7011
7044
  name,
7012
7045
  evidenceFile,
7013
- lineContaining(raw, name)
7046
+ lineContaining2(raw, name)
7014
7047
  );
7015
7048
  nodesAdded += result.nodesAdded;
7016
7049
  edgesAdded += result.edgesAdded;
@@ -7024,7 +7057,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7024
7057
  "cloudflare-cron",
7025
7058
  cron,
7026
7059
  evidenceFile,
7027
- lineContaining(raw, cron)
7060
+ lineContaining2(raw, cron)
7028
7061
  );
7029
7062
  nodesAdded += result.nodesAdded;
7030
7063
  edgesAdded += result.edgesAdded;
@@ -7037,7 +7070,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7037
7070
  "cloudflare-env-var",
7038
7071
  varName,
7039
7072
  evidenceFile,
7040
- lineContaining(raw, varName)
7073
+ lineContaining2(raw, varName)
7041
7074
  );
7042
7075
  nodesAdded += result.nodesAdded;
7043
7076
  edgesAdded += result.edgesAdded;
@@ -7055,7 +7088,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7055
7088
  type: import_types25.EdgeType.CALLS,
7056
7089
  provenance: import_types25.Provenance.EXTRACTED,
7057
7090
  confidence: (0, import_types25.confidenceForExtracted)("structural"),
7058
- evidence: { file: evidenceFile, line: lineContaining(raw, svc.service) }
7091
+ evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7059
7092
  };
7060
7093
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
7061
7094
  edgesAdded++;
@@ -7069,11 +7102,208 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7069
7102
  "cloudflare-service-binding",
7070
7103
  svc.service,
7071
7104
  evidenceFile,
7072
- lineContaining(raw, svc.service)
7105
+ lineContaining2(raw, svc.service)
7106
+ );
7107
+ nodesAdded += result.nodesAdded;
7108
+ edgesAdded += result.edgesAdded;
7109
+ }
7110
+ }
7111
+ return { nodesAdded, edgesAdded };
7112
+ }
7113
+
7114
+ // src/extract/infra/vercel.ts
7115
+ init_cjs_shims();
7116
+ var import_node_fs21 = require("fs");
7117
+ var import_node_path37 = __toESM(require("path"), 1);
7118
+ var import_types26 = require("@neat.is/types");
7119
+ var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7120
+ async function readVercelConfig(dir) {
7121
+ for (const filename of VERCEL_CONFIG_FILENAMES) {
7122
+ const abs = import_node_path37.default.join(dir, filename);
7123
+ if (!await exists(abs)) continue;
7124
+ const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7125
+ const config = JSON.parse(maskCommentsInSource(raw));
7126
+ return { config, relFile: filename, raw };
7127
+ }
7128
+ return null;
7129
+ }
7130
+ async function readLinkedProjectName(dir) {
7131
+ const abs = import_node_path37.default.join(dir, ".vercel", "project.json");
7132
+ if (!await exists(abs)) return void 0;
7133
+ const parsed = JSON.parse(await import_node_fs21.promises.readFile(abs, "utf8"));
7134
+ return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
7135
+ }
7136
+ function routeSource(route) {
7137
+ return route.source ?? route.src;
7138
+ }
7139
+ async function addVercelServices(graph, services, scanPath) {
7140
+ let nodesAdded = 0;
7141
+ let edgesAdded = 0;
7142
+ for (const service of services) {
7143
+ let read = null;
7144
+ let projectName;
7145
+ try {
7146
+ read = await readVercelConfig(service.dir);
7147
+ projectName = await readLinkedProjectName(service.dir);
7148
+ } catch (err) {
7149
+ recordExtractionError("infra vercel", import_node_path37.default.relative(scanPath, service.dir), err);
7150
+ continue;
7151
+ }
7152
+ if (!read && !projectName) continue;
7153
+ const serviceNode = graph.getNodeAttributes(service.node.id);
7154
+ if (serviceNode.platform !== "vercel" || projectName && serviceNode.platformName !== projectName) {
7155
+ const updated = {
7156
+ ...serviceNode,
7157
+ platform: "vercel",
7158
+ ...projectName ? { platformName: projectName } : {}
7159
+ };
7160
+ graph.replaceNodeAttributes(service.node.id, updated);
7161
+ }
7162
+ const anchorId = service.node.id;
7163
+ if (!read) continue;
7164
+ const { config, relFile, raw } = read;
7165
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7166
+ const add = (edgeType, kind, name) => {
7167
+ if (!name) return;
7168
+ const result = emitPlatformResourceEdge(
7169
+ graph,
7170
+ anchorId,
7171
+ edgeType,
7172
+ kind,
7173
+ name,
7174
+ "vercel",
7175
+ evidenceFile,
7176
+ lineContaining(raw, name)
7177
+ );
7178
+ nodesAdded += result.nodesAdded;
7179
+ edgesAdded += result.edgesAdded;
7180
+ };
7181
+ add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
7182
+ for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7183
+ for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7184
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7185
+ for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7186
+ add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7187
+ }
7188
+ }
7189
+ return { nodesAdded, edgesAdded };
7190
+ }
7191
+
7192
+ // src/extract/infra/railway.ts
7193
+ init_cjs_shims();
7194
+ var import_node_fs22 = require("fs");
7195
+ var import_node_path38 = __toESM(require("path"), 1);
7196
+ var import_smol_toml3 = require("smol-toml");
7197
+ var import_types27 = require("@neat.is/types");
7198
+ var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7199
+ async function readRailwayConfig(dir) {
7200
+ for (const filename of RAILWAY_FILENAMES) {
7201
+ const abs = import_node_path38.default.join(dir, filename);
7202
+ if (!await exists(abs)) continue;
7203
+ const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7204
+ const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
7205
+ return { config, relFile: filename, raw };
7206
+ }
7207
+ return null;
7208
+ }
7209
+ async function addRailwayServices(graph, services, scanPath) {
7210
+ let nodesAdded = 0;
7211
+ let edgesAdded = 0;
7212
+ for (const service of services) {
7213
+ let read = null;
7214
+ try {
7215
+ read = await readRailwayConfig(service.dir);
7216
+ } catch (err) {
7217
+ recordExtractionError("infra railway", import_node_path38.default.relative(scanPath, service.dir), err);
7218
+ continue;
7219
+ }
7220
+ if (!read) continue;
7221
+ const serviceNode = graph.getNodeAttributes(service.node.id);
7222
+ if (serviceNode.platform !== "railway") {
7223
+ graph.replaceNodeAttributes(service.node.id, { ...serviceNode, platform: "railway" });
7224
+ }
7225
+ const anchorId = service.node.id;
7226
+ const { config, relFile, raw } = read;
7227
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7228
+ const add = (edgeType, kind, name) => {
7229
+ if (!name) return;
7230
+ const result = emitPlatformResourceEdge(
7231
+ graph,
7232
+ anchorId,
7233
+ edgeType,
7234
+ kind,
7235
+ name,
7236
+ "railway",
7237
+ evidenceFile,
7238
+ lineContaining(raw, name)
7073
7239
  );
7074
7240
  nodesAdded += result.nodesAdded;
7075
7241
  edgesAdded += result.edgesAdded;
7242
+ };
7243
+ add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
7244
+ add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7245
+ add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7246
+ }
7247
+ return { nodesAdded, edgesAdded };
7248
+ }
7249
+
7250
+ // src/extract/infra/supabase.ts
7251
+ init_cjs_shims();
7252
+ var import_node_fs23 = require("fs");
7253
+ var import_node_path39 = __toESM(require("path"), 1);
7254
+ var import_smol_toml4 = require("smol-toml");
7255
+ var import_types28 = require("@neat.is/types");
7256
+ async function readSupabaseConfig(dir) {
7257
+ const relFile = import_node_path39.default.join("supabase", "config.toml");
7258
+ const abs = import_node_path39.default.join(dir, relFile);
7259
+ if (!await exists(abs)) return null;
7260
+ const raw = await import_node_fs23.promises.readFile(abs, "utf8");
7261
+ const config = (0, import_smol_toml4.parse)(raw);
7262
+ return { config, relFile, raw };
7263
+ }
7264
+ async function addSupabaseProjects(graph, services, scanPath) {
7265
+ let nodesAdded = 0;
7266
+ let edgesAdded = 0;
7267
+ for (const service of services) {
7268
+ let read = null;
7269
+ try {
7270
+ read = await readSupabaseConfig(service.dir);
7271
+ } catch (err) {
7272
+ recordExtractionError("infra supabase", import_node_path39.default.relative(scanPath, service.dir), err);
7273
+ continue;
7076
7274
  }
7275
+ if (!read) continue;
7276
+ const { config, relFile, raw } = read;
7277
+ const projectId = typeof config.project_id === "string" ? config.project_id : void 0;
7278
+ const serviceNode = graph.getNodeAttributes(service.node.id);
7279
+ if (serviceNode.platform !== "supabase" || projectId && serviceNode.platformName !== projectId) {
7280
+ graph.replaceNodeAttributes(service.node.id, {
7281
+ ...serviceNode,
7282
+ platform: "supabase",
7283
+ ...projectId ? { platformName: projectId } : {}
7284
+ });
7285
+ }
7286
+ const anchorId = service.node.id;
7287
+ const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
7288
+ const add = (edgeType, kind, name) => {
7289
+ if (!name) return;
7290
+ const result = emitPlatformResourceEdge(
7291
+ graph,
7292
+ anchorId,
7293
+ edgeType,
7294
+ kind,
7295
+ name,
7296
+ "supabase",
7297
+ evidenceFile,
7298
+ lineContaining(raw, name)
7299
+ );
7300
+ nodesAdded += result.nodesAdded;
7301
+ edgesAdded += result.edgesAdded;
7302
+ };
7303
+ add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
7304
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
7305
+ if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7306
+ if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7077
7307
  }
7078
7308
  return { nodesAdded, edgesAdded };
7079
7309
  }
@@ -7085,24 +7315,27 @@ async function addInfra(graph, scanPath, services) {
7085
7315
  const terraform = await addTerraformResources(graph, scanPath);
7086
7316
  const k8s = await addK8sResources(graph, scanPath);
7087
7317
  const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
7318
+ const vercel = await addVercelServices(graph, services, scanPath);
7319
+ const railway = await addRailwayServices(graph, services, scanPath);
7320
+ const supabase = await addSupabaseProjects(graph, services, scanPath);
7088
7321
  return {
7089
- nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
7090
- edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
7322
+ nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded + vercel.nodesAdded + railway.nodesAdded + supabase.nodesAdded,
7323
+ edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded + vercel.edgesAdded + railway.edgesAdded + supabase.edgesAdded
7091
7324
  };
7092
7325
  }
7093
7326
 
7094
7327
  // src/extract/index.ts
7095
- var import_node_path38 = __toESM(require("path"), 1);
7328
+ var import_node_path41 = __toESM(require("path"), 1);
7096
7329
 
7097
7330
  // src/extract/retire.ts
7098
7331
  init_cjs_shims();
7099
- var import_node_fs21 = require("fs");
7100
- var import_node_path37 = __toESM(require("path"), 1);
7101
- var import_types26 = require("@neat.is/types");
7332
+ var import_node_fs24 = require("fs");
7333
+ var import_node_path40 = __toESM(require("path"), 1);
7334
+ var import_types29 = require("@neat.is/types");
7102
7335
  function dropOrphanedFileNodes(graph) {
7103
7336
  const orphans = [];
7104
7337
  graph.forEachNode((id, attrs) => {
7105
- if (attrs.type !== import_types26.NodeType.FileNode) return;
7338
+ if (attrs.type !== import_types29.NodeType.FileNode) return;
7106
7339
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7107
7340
  orphans.push(id);
7108
7341
  }
@@ -7115,7 +7348,7 @@ function retireEdgesByFile(graph, file) {
7115
7348
  const toDrop = [];
7116
7349
  graph.forEachEdge((id, attrs) => {
7117
7350
  const edge = attrs;
7118
- if (edge.provenance !== import_types26.Provenance.EXTRACTED) return;
7351
+ if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7119
7352
  if (!edge.evidence?.file) return;
7120
7353
  if (edge.evidence.file === normalized) toDrop.push(id);
7121
7354
  });
@@ -7128,14 +7361,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7128
7361
  const bases = [scanPath, ...serviceDirs];
7129
7362
  graph.forEachEdge((id, attrs) => {
7130
7363
  const edge = attrs;
7131
- if (edge.provenance !== import_types26.Provenance.EXTRACTED) return;
7364
+ if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7132
7365
  const evidenceFile = edge.evidence?.file;
7133
7366
  if (!evidenceFile) return;
7134
- if (import_node_path37.default.isAbsolute(evidenceFile)) {
7135
- if (!(0, import_node_fs21.existsSync)(evidenceFile)) toDrop.push(id);
7367
+ if (import_node_path40.default.isAbsolute(evidenceFile)) {
7368
+ if (!(0, import_node_fs24.existsSync)(evidenceFile)) toDrop.push(id);
7136
7369
  return;
7137
7370
  }
7138
- const found = bases.some((base) => (0, import_node_fs21.existsSync)(import_node_path37.default.join(base, evidenceFile)));
7371
+ const found = bases.some((base) => (0, import_node_fs24.existsSync)(import_node_path40.default.join(base, evidenceFile)));
7139
7372
  if (!found) toDrop.push(id);
7140
7373
  });
7141
7374
  for (const id of toDrop) graph.dropEdge(id);
@@ -7177,7 +7410,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7177
7410
  }
7178
7411
  const droppedEntries = drainDroppedExtracted();
7179
7412
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7180
- const rejectedPath = import_node_path38.default.join(import_node_path38.default.dirname(opts.errorsPath), "rejected.ndjson");
7413
+ const rejectedPath = import_node_path41.default.join(import_node_path41.default.dirname(opts.errorsPath), "rejected.ndjson");
7181
7414
  try {
7182
7415
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7183
7416
  } catch (err) {
@@ -7211,7 +7444,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7211
7444
 
7212
7445
  // src/divergences.ts
7213
7446
  init_cjs_shims();
7214
- var import_types27 = require("@neat.is/types");
7447
+ var import_types30 = require("@neat.is/types");
7215
7448
  function bucketKey(source, target, type) {
7216
7449
  return `${type}|${source}|${target}`;
7217
7450
  }
@@ -7219,22 +7452,22 @@ function bucketEdges(graph) {
7219
7452
  const buckets2 = /* @__PURE__ */ new Map();
7220
7453
  graph.forEachEdge((id, attrs) => {
7221
7454
  const e = attrs;
7222
- const parsed = (0, import_types27.parseEdgeId)(id);
7455
+ const parsed = (0, import_types30.parseEdgeId)(id);
7223
7456
  const provenance = parsed?.provenance ?? e.provenance;
7224
7457
  const key = bucketKey(e.source, e.target, e.type);
7225
7458
  const cur = buckets2.get(key) ?? { source: e.source, target: e.target, type: e.type };
7226
7459
  switch (provenance) {
7227
- case import_types27.Provenance.EXTRACTED:
7460
+ case import_types30.Provenance.EXTRACTED:
7228
7461
  cur.extracted = e;
7229
7462
  break;
7230
- case import_types27.Provenance.OBSERVED:
7463
+ case import_types30.Provenance.OBSERVED:
7231
7464
  cur.observed = e;
7232
7465
  break;
7233
- case import_types27.Provenance.INFERRED:
7466
+ case import_types30.Provenance.INFERRED:
7234
7467
  cur.inferred = e;
7235
7468
  break;
7236
7469
  default:
7237
- if (e.provenance === import_types27.Provenance.STALE) cur.stale = e;
7470
+ if (e.provenance === import_types30.Provenance.STALE) cur.stale = e;
7238
7471
  }
7239
7472
  buckets2.set(key, cur);
7240
7473
  });
@@ -7243,12 +7476,12 @@ function bucketEdges(graph) {
7243
7476
  function nodeIsFrontier(graph, nodeId) {
7244
7477
  if (!graph.hasNode(nodeId)) return false;
7245
7478
  const attrs = graph.getNodeAttributes(nodeId);
7246
- return attrs.type === import_types27.NodeType.FrontierNode;
7479
+ return attrs.type === import_types30.NodeType.FrontierNode;
7247
7480
  }
7248
7481
  function nodeIsWebsocketChannel(graph, nodeId) {
7249
7482
  if (!graph.hasNode(nodeId)) return false;
7250
7483
  const attrs = graph.getNodeAttributes(nodeId);
7251
- return attrs.type === import_types27.NodeType.WebSocketChannelNode;
7484
+ return attrs.type === import_types30.NodeType.WebSocketChannelNode;
7252
7485
  }
7253
7486
  function clampConfidence(n) {
7254
7487
  if (!Number.isFinite(n)) return 0;
@@ -7268,14 +7501,14 @@ function gradedConfidence(edge) {
7268
7501
  return clampConfidence(confidenceForEdge(edge));
7269
7502
  }
7270
7503
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
7271
- import_types27.EdgeType.CALLS,
7272
- import_types27.EdgeType.CONNECTS_TO,
7273
- import_types27.EdgeType.PUBLISHES_TO,
7274
- import_types27.EdgeType.CONSUMES_FROM
7504
+ import_types30.EdgeType.CALLS,
7505
+ import_types30.EdgeType.CONNECTS_TO,
7506
+ import_types30.EdgeType.PUBLISHES_TO,
7507
+ import_types30.EdgeType.CONSUMES_FROM
7275
7508
  ]);
7276
7509
  function detectMissingDivergences(graph, bucket) {
7277
7510
  const out = [];
7278
- if (bucket.type === import_types27.EdgeType.CONTAINS) return out;
7511
+ if (bucket.type === import_types30.EdgeType.CONTAINS) return out;
7279
7512
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
7280
7513
  if (!nodeIsFrontier(graph, bucket.target)) {
7281
7514
  out.push({
@@ -7316,7 +7549,7 @@ function declaredHostFor(svc) {
7316
7549
  function hasExtractedConfiguredBy(graph, svcId) {
7317
7550
  for (const edgeId of graph.outboundEdges(svcId)) {
7318
7551
  const e = graph.getEdgeAttributes(edgeId);
7319
- if (e.type === import_types27.EdgeType.CONFIGURED_BY && e.provenance === import_types27.Provenance.EXTRACTED) {
7552
+ if (e.type === import_types30.EdgeType.CONFIGURED_BY && e.provenance === import_types30.Provenance.EXTRACTED) {
7320
7553
  return true;
7321
7554
  }
7322
7555
  }
@@ -7329,10 +7562,10 @@ function detectHostMismatch(graph, svcId, svc) {
7329
7562
  const out = [];
7330
7563
  for (const edgeId of graph.outboundEdges(svcId)) {
7331
7564
  const edge = graph.getEdgeAttributes(edgeId);
7332
- if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
7333
- if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
7565
+ if (edge.type !== import_types30.EdgeType.CONNECTS_TO) continue;
7566
+ if (edge.provenance !== import_types30.Provenance.OBSERVED) continue;
7334
7567
  const target = graph.getNodeAttributes(edge.target);
7335
- if (target.type !== import_types27.NodeType.DatabaseNode) continue;
7568
+ if (target.type !== import_types30.NodeType.DatabaseNode) continue;
7336
7569
  const observedHost = target.host?.trim();
7337
7570
  if (!observedHost) continue;
7338
7571
  if (observedHost === declaredHost) continue;
@@ -7354,10 +7587,10 @@ function detectCompatDivergences(graph, svcId, svc) {
7354
7587
  const deps = svc.dependencies ?? {};
7355
7588
  for (const edgeId of graph.outboundEdges(svcId)) {
7356
7589
  const edge = graph.getEdgeAttributes(edgeId);
7357
- if (edge.type !== import_types27.EdgeType.CONNECTS_TO) continue;
7358
- if (edge.provenance !== import_types27.Provenance.OBSERVED) continue;
7590
+ if (edge.type !== import_types30.EdgeType.CONNECTS_TO) continue;
7591
+ if (edge.provenance !== import_types30.Provenance.OBSERVED) continue;
7359
7592
  const target = graph.getNodeAttributes(edge.target);
7360
- if (target.type !== import_types27.NodeType.DatabaseNode) continue;
7593
+ if (target.type !== import_types30.NodeType.DatabaseNode) continue;
7361
7594
  for (const pair of compatPairs()) {
7362
7595
  if (pair.engine !== target.engine) continue;
7363
7596
  const declared = deps[pair.driver];
@@ -7416,7 +7649,7 @@ function suppressHostMismatchHalves(all) {
7416
7649
  for (const d of all) {
7417
7650
  if (d.type !== "host-mismatch") continue;
7418
7651
  observedHalf.add(`${d.source}->${d.target}`);
7419
- declaredHalf.add((0, import_types27.databaseId)(d.extractedHost));
7652
+ declaredHalf.add((0, import_types30.databaseId)(d.extractedHost));
7420
7653
  }
7421
7654
  if (observedHalf.size === 0) return all;
7422
7655
  return all.filter((d) => {
@@ -7435,7 +7668,7 @@ function computeDivergences(graph, opts = {}) {
7435
7668
  }
7436
7669
  graph.forEachNode((nodeId, attrs) => {
7437
7670
  const n = attrs;
7438
- if (n.type !== import_types27.NodeType.ServiceNode) return;
7671
+ if (n.type !== import_types30.NodeType.ServiceNode) return;
7439
7672
  const svc = n;
7440
7673
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
7441
7674
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -7469,7 +7702,7 @@ function computeDivergences(graph, opts = {}) {
7469
7702
  if (a.source !== b.source) return a.source.localeCompare(b.source);
7470
7703
  return a.target.localeCompare(b.target);
7471
7704
  });
7472
- return import_types27.DivergenceResultSchema.parse({
7705
+ return import_types30.DivergenceResultSchema.parse({
7473
7706
  divergences: filtered,
7474
7707
  totalAffected: filtered.length,
7475
7708
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7478,9 +7711,9 @@ function computeDivergences(graph, opts = {}) {
7478
7711
 
7479
7712
  // src/persist.ts
7480
7713
  init_cjs_shims();
7481
- var import_node_fs22 = require("fs");
7482
- var import_node_path39 = __toESM(require("path"), 1);
7483
- var import_types28 = require("@neat.is/types");
7714
+ var import_node_fs25 = require("fs");
7715
+ var import_node_path42 = __toESM(require("path"), 1);
7716
+ var import_types31 = require("@neat.is/types");
7484
7717
  var SCHEMA_VERSION = 4;
7485
7718
  function migrateV1ToV2(payload) {
7486
7719
  const nodes = payload.graph.nodes;
@@ -7502,12 +7735,12 @@ function migrateV2ToV3(payload) {
7502
7735
  for (const edge of edges) {
7503
7736
  const attrs = edge.attributes;
7504
7737
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7505
- attrs.provenance = import_types28.Provenance.OBSERVED;
7738
+ attrs.provenance = import_types31.Provenance.OBSERVED;
7506
7739
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7507
7740
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7508
7741
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7509
7742
  if (type && source && target) {
7510
- const newId = (0, import_types28.observedEdgeId)(source, target, type);
7743
+ const newId = (0, import_types31.observedEdgeId)(source, target, type);
7511
7744
  attrs.id = newId;
7512
7745
  if (edge.key) edge.key = newId;
7513
7746
  }
@@ -7516,7 +7749,7 @@ function migrateV2ToV3(payload) {
7516
7749
  return { ...payload, schemaVersion: 3 };
7517
7750
  }
7518
7751
  async function ensureDir(filePath) {
7519
- await import_node_fs22.promises.mkdir(import_node_path39.default.dirname(filePath), { recursive: true });
7752
+ await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(filePath), { recursive: true });
7520
7753
  }
7521
7754
  async function saveGraphToDisk(graph, outPath) {
7522
7755
  await ensureDir(outPath);
@@ -7526,13 +7759,13 @@ async function saveGraphToDisk(graph, outPath) {
7526
7759
  graph: graph.export()
7527
7760
  };
7528
7761
  const tmp = `${outPath}.tmp`;
7529
- await import_node_fs22.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7530
- await import_node_fs22.promises.rename(tmp, outPath);
7762
+ await import_node_fs25.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7763
+ await import_node_fs25.promises.rename(tmp, outPath);
7531
7764
  }
7532
7765
  async function loadGraphFromDisk(graph, outPath) {
7533
7766
  let raw;
7534
7767
  try {
7535
- raw = await import_node_fs22.promises.readFile(outPath, "utf8");
7768
+ raw = await import_node_fs25.promises.readFile(outPath, "utf8");
7536
7769
  } catch (err) {
7537
7770
  if (err.code === "ENOENT") return;
7538
7771
  throw err;
@@ -7597,8 +7830,8 @@ function startPersistLoop(graph, outPath, opts = {}) {
7597
7830
 
7598
7831
  // src/gitignore.ts
7599
7832
  init_cjs_shims();
7600
- var import_node_fs23 = require("fs");
7601
- var import_node_path40 = __toESM(require("path"), 1);
7833
+ var import_node_fs26 = require("fs");
7834
+ var import_node_path43 = __toESM(require("path"), 1);
7602
7835
  var NEAT_OUT_LINE = "neat-out/";
7603
7836
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
7604
7837
  function isNeatOutLine(line) {
@@ -7606,15 +7839,15 @@ function isNeatOutLine(line) {
7606
7839
  return trimmed === "neat-out/" || trimmed === "neat-out";
7607
7840
  }
7608
7841
  async function ensureNeatOutIgnored(projectDir) {
7609
- const file = import_node_path40.default.join(projectDir, ".gitignore");
7842
+ const file = import_node_path43.default.join(projectDir, ".gitignore");
7610
7843
  let existing = null;
7611
7844
  try {
7612
- existing = await import_node_fs23.promises.readFile(file, "utf8");
7845
+ existing = await import_node_fs26.promises.readFile(file, "utf8");
7613
7846
  } catch (err) {
7614
7847
  if (err.code !== "ENOENT") throw err;
7615
7848
  }
7616
7849
  if (existing === null) {
7617
- await import_node_fs23.promises.writeFile(file, `${NEAT_HEADER}
7850
+ await import_node_fs26.promises.writeFile(file, `${NEAT_HEADER}
7618
7851
  ${NEAT_OUT_LINE}
7619
7852
  `, "utf8");
7620
7853
  return { action: "created", file };
@@ -7627,13 +7860,13 @@ ${NEAT_OUT_LINE}
7627
7860
  ${NEAT_HEADER}
7628
7861
  ${NEAT_OUT_LINE}
7629
7862
  `;
7630
- await import_node_fs23.promises.writeFile(file, existing + appended, "utf8");
7863
+ await import_node_fs26.promises.writeFile(file, existing + appended, "utf8");
7631
7864
  return { action: "added", file };
7632
7865
  }
7633
7866
 
7634
7867
  // src/summary.ts
7635
7868
  init_cjs_shims();
7636
- var import_types29 = require("@neat.is/types");
7869
+ var import_types32 = require("@neat.is/types");
7637
7870
  function renderOtelEnvBlock() {
7638
7871
  return [
7639
7872
  "for prod OTel routing, set these in your deploy platform's env:",
@@ -7643,19 +7876,19 @@ function renderOtelEnvBlock() {
7643
7876
  }
7644
7877
  function findIncompatServices(nodes) {
7645
7878
  return nodes.filter(
7646
- (n) => n.type === import_types29.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
7879
+ (n) => n.type === import_types32.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
7647
7880
  );
7648
7881
  }
7649
7882
  function servicesWithoutObserved(nodes, edges) {
7650
7883
  const seen = /* @__PURE__ */ new Set();
7651
7884
  for (const e of edges) {
7652
- if (e.provenance === import_types29.Provenance.OBSERVED) {
7885
+ if (e.provenance === import_types32.Provenance.OBSERVED) {
7653
7886
  seen.add(e.source);
7654
7887
  seen.add(e.target);
7655
7888
  }
7656
7889
  }
7657
7890
  return nodes.filter(
7658
- (n) => n.type === import_types29.NodeType.ServiceNode && !seen.has(n.id)
7891
+ (n) => n.type === import_types32.NodeType.ServiceNode && !seen.has(n.id)
7659
7892
  );
7660
7893
  }
7661
7894
  function formatDivergence(d) {
@@ -7729,27 +7962,27 @@ function formatIncompat(inc) {
7729
7962
 
7730
7963
  // src/watch.ts
7731
7964
  init_cjs_shims();
7732
- var import_node_fs32 = __toESM(require("fs"), 1);
7733
- var import_node_path51 = __toESM(require("path"), 1);
7965
+ var import_node_fs35 = __toESM(require("fs"), 1);
7966
+ var import_node_path54 = __toESM(require("path"), 1);
7734
7967
  var import_chokidar = __toESM(require("chokidar"), 1);
7735
7968
 
7736
7969
  // src/api.ts
7737
7970
  init_cjs_shims();
7738
7971
  var import_fastify = __toESM(require("fastify"), 1);
7739
7972
  var import_cors = __toESM(require("@fastify/cors"), 1);
7740
- var import_types31 = require("@neat.is/types");
7973
+ var import_types34 = require("@neat.is/types");
7741
7974
 
7742
7975
  // src/extend/index.ts
7743
7976
  init_cjs_shims();
7744
- var import_node_fs25 = require("fs");
7745
- var import_node_path42 = __toESM(require("path"), 1);
7977
+ var import_node_fs28 = require("fs");
7978
+ var import_node_path45 = __toESM(require("path"), 1);
7746
7979
  var import_node_os2 = __toESM(require("os"), 1);
7747
7980
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
7748
7981
 
7749
7982
  // src/installers/package-manager.ts
7750
7983
  init_cjs_shims();
7751
- var import_node_fs24 = require("fs");
7752
- var import_node_path41 = __toESM(require("path"), 1);
7984
+ var import_node_fs27 = require("fs");
7985
+ var import_node_path44 = __toESM(require("path"), 1);
7753
7986
  var import_node_child_process = require("child_process");
7754
7987
  var LOCKFILE_PRIORITY = [
7755
7988
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7764,29 +7997,29 @@ var LOCKFILE_PRIORITY = [
7764
7997
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
7765
7998
  async function exists2(p) {
7766
7999
  try {
7767
- await import_node_fs24.promises.access(p);
8000
+ await import_node_fs27.promises.access(p);
7768
8001
  return true;
7769
8002
  } catch {
7770
8003
  return false;
7771
8004
  }
7772
8005
  }
7773
8006
  async function detectPackageManager(serviceDir) {
7774
- let dir = import_node_path41.default.resolve(serviceDir);
8007
+ let dir = import_node_path44.default.resolve(serviceDir);
7775
8008
  const stops = /* @__PURE__ */ new Set();
7776
8009
  for (let i = 0; i < 64; i++) {
7777
8010
  if (stops.has(dir)) break;
7778
8011
  stops.add(dir);
7779
8012
  for (const candidate of LOCKFILE_PRIORITY) {
7780
- const lockPath = import_node_path41.default.join(dir, candidate.lockfile);
8013
+ const lockPath = import_node_path44.default.join(dir, candidate.lockfile);
7781
8014
  if (await exists2(lockPath)) {
7782
8015
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7783
8016
  }
7784
8017
  }
7785
- const parent = import_node_path41.default.dirname(dir);
8018
+ const parent = import_node_path44.default.dirname(dir);
7786
8019
  if (parent === dir) break;
7787
8020
  dir = parent;
7788
8021
  }
7789
- return { pm: "npm", cwd: import_node_path41.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
8022
+ return { pm: "npm", cwd: import_node_path44.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7790
8023
  }
7791
8024
  async function runPackageManagerInstall(cmd) {
7792
8025
  return new Promise((resolve) => {
@@ -7828,30 +8061,30 @@ ${err.message}`
7828
8061
  // src/extend/index.ts
7829
8062
  async function fileExists2(p) {
7830
8063
  try {
7831
- await import_node_fs25.promises.access(p);
8064
+ await import_node_fs28.promises.access(p);
7832
8065
  return true;
7833
8066
  } catch {
7834
8067
  return false;
7835
8068
  }
7836
8069
  }
7837
8070
  async function readPackageJson(scanPath) {
7838
- const pkgPath = import_node_path42.default.join(scanPath, "package.json");
7839
- const raw = await import_node_fs25.promises.readFile(pkgPath, "utf8");
8071
+ const pkgPath = import_node_path45.default.join(scanPath, "package.json");
8072
+ const raw = await import_node_fs28.promises.readFile(pkgPath, "utf8");
7840
8073
  return JSON.parse(raw);
7841
8074
  }
7842
8075
  async function findHookFiles(scanPath) {
7843
- const entries = await import_node_fs25.promises.readdir(scanPath);
8076
+ const entries = await import_node_fs28.promises.readdir(scanPath);
7844
8077
  return entries.filter(
7845
8078
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
7846
8079
  ).sort();
7847
8080
  }
7848
8081
  function extendLogPath() {
7849
- return process.env.NEAT_EXTEND_LOG ?? import_node_path42.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
8082
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path45.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7850
8083
  }
7851
8084
  async function appendExtendLog(entry2) {
7852
8085
  const logPath = extendLogPath();
7853
- await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(logPath), { recursive: true });
7854
- await import_node_fs25.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
8086
+ await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(logPath), { recursive: true });
8087
+ await import_node_fs28.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7855
8088
  }
7856
8089
  function splicedContent(fileContent, snippet2) {
7857
8090
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -7909,7 +8142,7 @@ function lookupInstrumentation(library, installedVersion) {
7909
8142
  }
7910
8143
  async function describeProjectInstrumentation(ctx) {
7911
8144
  const hookFiles = await findHookFiles(ctx.scanPath);
7912
- const envNeat = await fileExists2(import_node_path42.default.join(ctx.scanPath, ".env.neat"));
8145
+ const envNeat = await fileExists2(import_node_path45.default.join(ctx.scanPath, ".env.neat"));
7913
8146
  const registryInstrPackages = new Set(
7914
8147
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7915
8148
  );
@@ -7931,31 +8164,31 @@ async function applyExtension(ctx, args, options) {
7931
8164
  );
7932
8165
  }
7933
8166
  for (const file of hookFiles) {
7934
- const content = await import_node_fs25.promises.readFile(import_node_path42.default.join(ctx.scanPath, file), "utf8");
8167
+ const content = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
7935
8168
  if (content.includes(args.registration_snippet)) {
7936
8169
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7937
8170
  }
7938
8171
  }
7939
8172
  const primaryFile = hookFiles[0];
7940
- const primaryPath = import_node_path42.default.join(ctx.scanPath, primaryFile);
8173
+ const primaryPath = import_node_path45.default.join(ctx.scanPath, primaryFile);
7941
8174
  const filesTouched = [];
7942
8175
  const depsAdded = [];
7943
- const pkgPath = import_node_path42.default.join(ctx.scanPath, "package.json");
8176
+ const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
7944
8177
  const pkg = await readPackageJson(ctx.scanPath);
7945
8178
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7946
8179
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
7947
- await import_node_fs25.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
8180
+ await import_node_fs28.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7948
8181
  filesTouched.push("package.json");
7949
8182
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
7950
8183
  }
7951
- const hookContent = await import_node_fs25.promises.readFile(primaryPath, "utf8");
8184
+ const hookContent = await import_node_fs28.promises.readFile(primaryPath, "utf8");
7952
8185
  const patched = splicedContent(hookContent, args.registration_snippet);
7953
8186
  if (!patched) {
7954
8187
  throw new Error(
7955
8188
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
7956
8189
  );
7957
8190
  }
7958
- await import_node_fs25.promises.writeFile(primaryPath, patched, "utf8");
8191
+ await import_node_fs28.promises.writeFile(primaryPath, patched, "utf8");
7959
8192
  filesTouched.push(primaryFile);
7960
8193
  const cmd = await detectPackageManager(ctx.scanPath);
7961
8194
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -7986,7 +8219,7 @@ async function dryRunExtension(ctx, args) {
7986
8219
  };
7987
8220
  }
7988
8221
  for (const file of hookFiles) {
7989
- const content = await import_node_fs25.promises.readFile(import_node_path42.default.join(ctx.scanPath, file), "utf8");
8222
+ const content = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, file), "utf8");
7990
8223
  if (content.includes(args.registration_snippet)) {
7991
8224
  return {
7992
8225
  library: args.library,
@@ -8008,7 +8241,7 @@ async function dryRunExtension(ctx, args) {
8008
8241
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
8009
8242
  filesTouched.push("package.json");
8010
8243
  }
8011
- const hookContent = await import_node_fs25.promises.readFile(import_node_path42.default.join(ctx.scanPath, primaryFile), "utf8");
8244
+ const hookContent = await import_node_fs28.promises.readFile(import_node_path45.default.join(ctx.scanPath, primaryFile), "utf8");
8012
8245
  const patched = splicedContent(hookContent, args.registration_snippet);
8013
8246
  if (patched) {
8014
8247
  filesTouched.push(primaryFile);
@@ -8023,28 +8256,28 @@ async function rollbackExtension(ctx, args) {
8023
8256
  if (!await fileExists2(logPath)) {
8024
8257
  return { undone: false, message: "no apply found for library" };
8025
8258
  }
8026
- const raw = await import_node_fs25.promises.readFile(logPath, "utf8");
8259
+ const raw = await import_node_fs28.promises.readFile(logPath, "utf8");
8027
8260
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
8028
8261
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
8029
8262
  if (!match) {
8030
8263
  return { undone: false, message: "no apply found for library" };
8031
8264
  }
8032
- const pkgPath = import_node_path42.default.join(ctx.scanPath, "package.json");
8265
+ const pkgPath = import_node_path45.default.join(ctx.scanPath, "package.json");
8033
8266
  if (await fileExists2(pkgPath)) {
8034
8267
  const pkg = await readPackageJson(ctx.scanPath);
8035
8268
  if (pkg.dependencies?.[match.instrumentation_package]) {
8036
8269
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
8037
8270
  pkg.dependencies = rest;
8038
- await import_node_fs25.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
8271
+ await import_node_fs28.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
8039
8272
  }
8040
8273
  }
8041
8274
  const hookFiles = await findHookFiles(ctx.scanPath);
8042
8275
  for (const file of hookFiles) {
8043
- const filePath = import_node_path42.default.join(ctx.scanPath, file);
8044
- const content = await import_node_fs25.promises.readFile(filePath, "utf8");
8276
+ const filePath = import_node_path45.default.join(ctx.scanPath, file);
8277
+ const content = await import_node_fs28.promises.readFile(filePath, "utf8");
8045
8278
  if (content.includes(match.registration_snippet)) {
8046
8279
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
8047
- await import_node_fs25.promises.writeFile(filePath, filtered, "utf8");
8280
+ await import_node_fs28.promises.writeFile(filePath, filtered, "utf8");
8048
8281
  break;
8049
8282
  }
8050
8283
  }
@@ -8101,7 +8334,7 @@ function queryLogEntries(opts) {
8101
8334
 
8102
8335
  // src/diff.ts
8103
8336
  init_cjs_shims();
8104
- var import_node_fs26 = require("fs");
8337
+ var import_node_fs29 = require("fs");
8105
8338
  async function loadSnapshotForDiff(target) {
8106
8339
  if (/^https?:\/\//i.test(target)) {
8107
8340
  const res = await fetch(target);
@@ -8110,7 +8343,7 @@ async function loadSnapshotForDiff(target) {
8110
8343
  }
8111
8344
  return await res.json();
8112
8345
  }
8113
- const raw = await import_node_fs26.promises.readFile(target, "utf8");
8346
+ const raw = await import_node_fs29.promises.readFile(target, "utf8");
8114
8347
  return JSON.parse(raw);
8115
8348
  }
8116
8349
  function indexEntries(entries) {
@@ -8178,23 +8411,23 @@ function canonicalJson(value) {
8178
8411
 
8179
8412
  // src/projects.ts
8180
8413
  init_cjs_shims();
8181
- var import_node_path43 = __toESM(require("path"), 1);
8414
+ var import_node_path46 = __toESM(require("path"), 1);
8182
8415
  function pathsForProject(project, baseDir) {
8183
8416
  if (project === DEFAULT_PROJECT) {
8184
8417
  return {
8185
- snapshotPath: import_node_path43.default.join(baseDir, "graph.json"),
8186
- errorsPath: import_node_path43.default.join(baseDir, "errors.ndjson"),
8187
- staleEventsPath: import_node_path43.default.join(baseDir, "stale-events.ndjson"),
8188
- embeddingsCachePath: import_node_path43.default.join(baseDir, "embeddings.json"),
8189
- policyViolationsPath: import_node_path43.default.join(baseDir, "policy-violations.ndjson")
8418
+ snapshotPath: import_node_path46.default.join(baseDir, "graph.json"),
8419
+ errorsPath: import_node_path46.default.join(baseDir, "errors.ndjson"),
8420
+ staleEventsPath: import_node_path46.default.join(baseDir, "stale-events.ndjson"),
8421
+ embeddingsCachePath: import_node_path46.default.join(baseDir, "embeddings.json"),
8422
+ policyViolationsPath: import_node_path46.default.join(baseDir, "policy-violations.ndjson")
8190
8423
  };
8191
8424
  }
8192
8425
  return {
8193
- snapshotPath: import_node_path43.default.join(baseDir, `${project}.json`),
8194
- errorsPath: import_node_path43.default.join(baseDir, `errors.${project}.ndjson`),
8195
- staleEventsPath: import_node_path43.default.join(baseDir, `stale-events.${project}.ndjson`),
8196
- embeddingsCachePath: import_node_path43.default.join(baseDir, `embeddings.${project}.json`),
8197
- policyViolationsPath: import_node_path43.default.join(baseDir, `policy-violations.${project}.ndjson`)
8426
+ snapshotPath: import_node_path46.default.join(baseDir, `${project}.json`),
8427
+ errorsPath: import_node_path46.default.join(baseDir, `errors.${project}.ndjson`),
8428
+ staleEventsPath: import_node_path46.default.join(baseDir, `stale-events.${project}.ndjson`),
8429
+ embeddingsCachePath: import_node_path46.default.join(baseDir, `embeddings.${project}.json`),
8430
+ policyViolationsPath: import_node_path46.default.join(baseDir, `policy-violations.${project}.ndjson`)
8198
8431
  };
8199
8432
  }
8200
8433
  var Projects = class {
@@ -8230,28 +8463,28 @@ var Projects = class {
8230
8463
 
8231
8464
  // src/registry.ts
8232
8465
  init_cjs_shims();
8233
- var import_node_fs27 = require("fs");
8466
+ var import_node_fs30 = require("fs");
8234
8467
  var import_node_os3 = __toESM(require("os"), 1);
8235
- var import_node_path44 = __toESM(require("path"), 1);
8236
- var import_types30 = require("@neat.is/types");
8468
+ var import_node_path47 = __toESM(require("path"), 1);
8469
+ var import_types33 = require("@neat.is/types");
8237
8470
  var LOCK_TIMEOUT_MS = 5e3;
8238
8471
  var LOCK_RETRY_MS = 50;
8239
8472
  function neatHome() {
8240
8473
  const override = process.env.NEAT_HOME;
8241
- if (override && override.length > 0) return import_node_path44.default.resolve(override);
8242
- return import_node_path44.default.join(import_node_os3.default.homedir(), ".neat");
8474
+ if (override && override.length > 0) return import_node_path47.default.resolve(override);
8475
+ return import_node_path47.default.join(import_node_os3.default.homedir(), ".neat");
8243
8476
  }
8244
8477
  function registryPath() {
8245
- return import_node_path44.default.join(neatHome(), "projects.json");
8478
+ return import_node_path47.default.join(neatHome(), "projects.json");
8246
8479
  }
8247
8480
  function registryLockPath() {
8248
- return import_node_path44.default.join(neatHome(), "projects.json.lock");
8481
+ return import_node_path47.default.join(neatHome(), "projects.json.lock");
8249
8482
  }
8250
8483
  function daemonPidPath() {
8251
- return import_node_path44.default.join(neatHome(), "neatd.pid");
8484
+ return import_node_path47.default.join(neatHome(), "neatd.pid");
8252
8485
  }
8253
8486
  function daemonsDir() {
8254
- return import_node_path44.default.join(neatHome(), "daemons");
8487
+ return import_node_path47.default.join(neatHome(), "daemons");
8255
8488
  }
8256
8489
  function isFiniteInt(v) {
8257
8490
  return typeof v === "number" && Number.isFinite(v);
@@ -8284,7 +8517,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
8284
8517
  const dir = daemonsDir();
8285
8518
  let names;
8286
8519
  try {
8287
- names = await import_node_fs27.promises.readdir(dir);
8520
+ names = await import_node_fs30.promises.readdir(dir);
8288
8521
  } catch (err) {
8289
8522
  if (err.code === "ENOENT") return [];
8290
8523
  throw err;
@@ -8292,10 +8525,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
8292
8525
  const out = [];
8293
8526
  for (const name of names) {
8294
8527
  if (!name.endsWith(".json")) continue;
8295
- const file = import_node_path44.default.join(dir, name);
8528
+ const file = import_node_path47.default.join(dir, name);
8296
8529
  let raw;
8297
8530
  try {
8298
- raw = await import_node_fs27.promises.readFile(file, "utf8");
8531
+ raw = await import_node_fs30.promises.readFile(file, "utf8");
8299
8532
  } catch {
8300
8533
  continue;
8301
8534
  }
@@ -8308,7 +8541,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
8308
8541
  return out;
8309
8542
  }
8310
8543
  async function removeDaemonRecord(source) {
8311
- await import_node_fs27.promises.unlink(source).catch(() => {
8544
+ await import_node_fs30.promises.unlink(source).catch(() => {
8312
8545
  });
8313
8546
  }
8314
8547
  async function listMachineProjects(probe = defaultDiscoveryProbe) {
@@ -8365,7 +8598,7 @@ function isPidAliveDefault(pid) {
8365
8598
  }
8366
8599
  async function readPidFile(file) {
8367
8600
  try {
8368
- const raw = await import_node_fs27.promises.readFile(file, "utf8");
8601
+ const raw = await import_node_fs30.promises.readFile(file, "utf8");
8369
8602
  const pid = Number.parseInt(raw.trim(), 10);
8370
8603
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
8371
8604
  } catch {
@@ -8413,32 +8646,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8413
8646
  }
8414
8647
  }
8415
8648
  async function normalizeProjectPath(input) {
8416
- const resolved = import_node_path44.default.resolve(input);
8649
+ const resolved = import_node_path47.default.resolve(input);
8417
8650
  try {
8418
- return await import_node_fs27.promises.realpath(resolved);
8651
+ return await import_node_fs30.promises.realpath(resolved);
8419
8652
  } catch {
8420
8653
  return resolved;
8421
8654
  }
8422
8655
  }
8423
8656
  async function writeAtomically(target, contents) {
8424
- await import_node_fs27.promises.mkdir(import_node_path44.default.dirname(target), { recursive: true });
8657
+ await import_node_fs30.promises.mkdir(import_node_path47.default.dirname(target), { recursive: true });
8425
8658
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8426
- const fd = await import_node_fs27.promises.open(tmp, "w");
8659
+ const fd = await import_node_fs30.promises.open(tmp, "w");
8427
8660
  try {
8428
8661
  await fd.writeFile(contents, "utf8");
8429
8662
  await fd.sync();
8430
8663
  } finally {
8431
8664
  await fd.close();
8432
8665
  }
8433
- await import_node_fs27.promises.rename(tmp, target);
8666
+ await import_node_fs30.promises.rename(tmp, target);
8434
8667
  }
8435
8668
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8436
8669
  const deadline = Date.now() + timeoutMs;
8437
- await import_node_fs27.promises.mkdir(import_node_path44.default.dirname(lockPath), { recursive: true });
8670
+ await import_node_fs30.promises.mkdir(import_node_path47.default.dirname(lockPath), { recursive: true });
8438
8671
  let probedHolder = false;
8439
8672
  while (true) {
8440
8673
  try {
8441
- const fd = await import_node_fs27.promises.open(lockPath, "wx");
8674
+ const fd = await import_node_fs30.promises.open(lockPath, "wx");
8442
8675
  try {
8443
8676
  await fd.writeFile(`${process.pid}
8444
8677
  `, "utf8");
@@ -8463,7 +8696,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
8463
8696
  }
8464
8697
  }
8465
8698
  async function releaseLock(lockPath) {
8466
- await import_node_fs27.promises.unlink(lockPath).catch(() => {
8699
+ await import_node_fs30.promises.unlink(lockPath).catch(() => {
8467
8700
  });
8468
8701
  }
8469
8702
  async function withLock(fn) {
@@ -8479,7 +8712,7 @@ async function readRegistry() {
8479
8712
  const file = registryPath();
8480
8713
  let raw;
8481
8714
  try {
8482
- raw = await import_node_fs27.promises.readFile(file, "utf8");
8715
+ raw = await import_node_fs30.promises.readFile(file, "utf8");
8483
8716
  } catch (err) {
8484
8717
  if (err.code === "ENOENT") {
8485
8718
  return { version: 1, projects: [] };
@@ -8487,10 +8720,10 @@ async function readRegistry() {
8487
8720
  throw err;
8488
8721
  }
8489
8722
  const parsed = JSON.parse(raw);
8490
- return import_types30.RegistryFileSchema.parse(parsed);
8723
+ return import_types33.RegistryFileSchema.parse(parsed);
8491
8724
  }
8492
8725
  async function writeRegistry(reg) {
8493
- const validated = import_types30.RegistryFileSchema.parse(reg);
8726
+ const validated = import_types33.RegistryFileSchema.parse(reg);
8494
8727
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8495
8728
  }
8496
8729
  var ProjectNameCollisionError = class extends Error {
@@ -8575,7 +8808,7 @@ function pruneTtlMs() {
8575
8808
  }
8576
8809
  async function statPathStatus(p) {
8577
8810
  try {
8578
- const stat = await import_node_fs27.promises.stat(p);
8811
+ const stat = await import_node_fs30.promises.stat(p);
8579
8812
  return stat.isDirectory() ? "present" : "unknown";
8580
8813
  } catch (err) {
8581
8814
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -8672,54 +8905,368 @@ data: ${JSON.stringify(envelope.payload)}
8672
8905
 
8673
8906
  // src/api.ts
8674
8907
  init_auth();
8675
- function serializeGraph(graph) {
8676
- const nodes = [];
8677
- graph.forEachNode((_id, attrs) => {
8678
- nodes.push(attrs);
8679
- });
8680
- const edges = [];
8681
- graph.forEachEdge((_id, attrs) => {
8682
- edges.push(attrs);
8683
- });
8684
- return { nodes, edges };
8685
- }
8686
- function projectFromReq(req, singleProject) {
8687
- const params = req.params;
8688
- const named = params.project;
8689
- if (singleProject) {
8690
- return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
8908
+
8909
+ // src/connectors-config.ts
8910
+ init_cjs_shims();
8911
+ var import_node_os4 = __toESM(require("os"), 1);
8912
+ var import_node_path48 = __toESM(require("path"), 1);
8913
+ var import_node_fs31 = require("fs");
8914
+ var CONNECTORS_CONFIG_VERSION = 1;
8915
+ var EnvRefUnsetError = class extends Error {
8916
+ ref;
8917
+ varName;
8918
+ constructor(ref, varName) {
8919
+ super(`${ref} is unset`);
8920
+ this.name = "EnvRefUnsetError";
8921
+ this.ref = ref;
8922
+ this.varName = varName;
8691
8923
  }
8692
- return named ?? DEFAULT_PROJECT;
8924
+ };
8925
+ function neatHome2() {
8926
+ const override = process.env.NEAT_HOME;
8927
+ if (override && override.length > 0) return import_node_path48.default.resolve(override);
8928
+ return import_node_path48.default.join(import_node_os4.default.homedir(), ".neat");
8693
8929
  }
8694
- function resolveProject(registry, req, reply, bootstrap, singleProject) {
8695
- const name = projectFromReq(req, singleProject);
8696
- const ctx = registry.get(name);
8697
- if (!ctx) {
8698
- const phase = bootstrap?.status(name);
8699
- if (phase === "bootstrapping") {
8700
- void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
8701
- return null;
8930
+ function connectorsConfigPath(home = neatHome2()) {
8931
+ return import_node_path48.default.join(home, "connectors.json");
8932
+ }
8933
+ var MODE_MASK_LOOSER_THAN_0600 = 63;
8934
+ async function warnIfModeLooserThan0600(file) {
8935
+ if (process.platform === "win32") return;
8936
+ try {
8937
+ const stat = await import_node_fs31.promises.stat(file);
8938
+ if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
8939
+ const mode = (stat.mode & 511).toString(8).padStart(3, "0");
8940
+ console.warn(
8941
+ `[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
8942
+ );
8702
8943
  }
8703
- if (phase === "broken") {
8704
- void reply.code(503).send({ ready: false, project: name, status: "broken" });
8705
- return null;
8944
+ } catch {
8945
+ }
8946
+ }
8947
+ async function readConnectorsConfig(home = neatHome2()) {
8948
+ const file = connectorsConfigPath(home);
8949
+ let raw;
8950
+ try {
8951
+ raw = await import_node_fs31.promises.readFile(file, "utf8");
8952
+ } catch (err) {
8953
+ if (err.code === "ENOENT") {
8954
+ return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
8706
8955
  }
8707
- void reply.code(404).send({ error: "project not found", project: name });
8708
- return null;
8956
+ throw err;
8709
8957
  }
8710
- return ctx;
8958
+ await warnIfModeLooserThan0600(file);
8959
+ let parsed;
8960
+ try {
8961
+ parsed = JSON.parse(raw);
8962
+ } catch (err) {
8963
+ throw new Error(`${file} is not valid JSON: ${err.message}`);
8964
+ }
8965
+ return validateConfig(parsed, file);
8711
8966
  }
8712
- function buildLegacyRegistry(opts) {
8713
- if (opts.projects) return opts.projects;
8714
- if (!opts.graph) {
8715
- throw new Error("buildApi: either `projects` or `graph` must be provided");
8967
+ function validateConfig(parsed, file) {
8968
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
8969
+ throw new Error(`${file} must be a JSON object with a "connectors" array`);
8716
8970
  }
8717
- const registry = new Projects();
8718
- const paths = pathsForProject(DEFAULT_PROJECT, "");
8719
- registry.set(DEFAULT_PROJECT, {
8720
- graph: opts.graph,
8721
- scanPath: opts.scanPath,
8722
- paths: {
8971
+ const obj = parsed;
8972
+ const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
8973
+ if (typeof version !== "number" || !Number.isInteger(version)) {
8974
+ throw new Error(`${file}: "version" must be an integer`);
8975
+ }
8976
+ const rawConnectors = obj.connectors;
8977
+ if (!Array.isArray(rawConnectors)) {
8978
+ throw new Error(`${file}: "connectors" must be an array`);
8979
+ }
8980
+ const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
8981
+ return { version, connectors };
8982
+ }
8983
+ function validateEntry(entry2, index, file) {
8984
+ const where = `${file}: connectors[${index}]`;
8985
+ if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
8986
+ throw new Error(`${where} must be an object`);
8987
+ }
8988
+ const e = entry2;
8989
+ const id = e.id;
8990
+ if (typeof id !== "string" || id.length === 0) {
8991
+ throw new Error(`${where}.id must be a non-empty string`);
8992
+ }
8993
+ const provider = e.provider;
8994
+ if (typeof provider !== "string" || provider.length === 0) {
8995
+ throw new Error(`${where}.provider must be a non-empty string`);
8996
+ }
8997
+ if (e.project !== void 0 && typeof e.project !== "string") {
8998
+ throw new Error(`${where}.project must be a string when present`);
8999
+ }
9000
+ const credential = validateCredentialRef(e.credential, `${where}.credential`);
9001
+ let options;
9002
+ if (e.options !== void 0) {
9003
+ if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
9004
+ throw new Error(`${where}.options must be an object when present`);
9005
+ }
9006
+ options = e.options;
9007
+ }
9008
+ return {
9009
+ id,
9010
+ provider,
9011
+ ...typeof e.project === "string" ? { project: e.project } : {},
9012
+ credential,
9013
+ ...options ? { options } : {}
9014
+ };
9015
+ }
9016
+ function validateCredentialRef(raw, where) {
9017
+ if (typeof raw === "string") {
9018
+ if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
9019
+ return raw;
9020
+ }
9021
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
9022
+ const fields = raw;
9023
+ const keys = Object.keys(fields);
9024
+ if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
9025
+ for (const key of keys) {
9026
+ const v = fields[key];
9027
+ if (typeof v !== "string" || v.length === 0) {
9028
+ throw new Error(`${where}.${key} must be a non-empty string`);
9029
+ }
9030
+ }
9031
+ return fields;
9032
+ }
9033
+ throw new Error(`${where} must be a string or an object of strings`);
9034
+ }
9035
+ function resolveCredential(ref, env = process.env) {
9036
+ if (typeof ref === "string") {
9037
+ return { kind: "single", value: resolveRef(ref, env) };
9038
+ }
9039
+ const fields = {};
9040
+ for (const [key, value] of Object.entries(ref)) {
9041
+ fields[key] = resolveRef(value, env);
9042
+ }
9043
+ return { kind: "fields", fields };
9044
+ }
9045
+ function resolveRef(value, env) {
9046
+ if (value.length > 1 && value.startsWith("$")) {
9047
+ const varName = value.slice(1);
9048
+ const resolved = env[varName];
9049
+ if (resolved === void 0 || resolved.length === 0) {
9050
+ throw new EnvRefUnsetError(value, varName);
9051
+ }
9052
+ return resolved;
9053
+ }
9054
+ return value;
9055
+ }
9056
+ function connectorMatchesProject(entry2, project) {
9057
+ return entry2.project === void 0 || entry2.project === project;
9058
+ }
9059
+ var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
9060
+ var CONNECTORS_LOCK_RETRY_MS = 50;
9061
+ function connectorsConfigLockPath(home = neatHome2()) {
9062
+ return import_node_path48.default.join(home, "connectors.json.lock");
9063
+ }
9064
+ function isEnvRef(value) {
9065
+ return value.length > 1 && value.startsWith("$");
9066
+ }
9067
+ function redactCredentialRef(ref) {
9068
+ const one = (value) => isEnvRef(value) ? value : "****";
9069
+ if (typeof ref === "string") return one(ref);
9070
+ const out = {};
9071
+ for (const [key, value] of Object.entries(ref)) out[key] = one(value);
9072
+ return out;
9073
+ }
9074
+ async function writeConfigAtomically0600(file, contents) {
9075
+ await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(file), { recursive: true });
9076
+ const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
9077
+ const fd = await import_node_fs31.promises.open(tmp, "w", 384);
9078
+ try {
9079
+ await fd.writeFile(contents, "utf8");
9080
+ await fd.chmod(384);
9081
+ await fd.sync();
9082
+ } finally {
9083
+ await fd.close();
9084
+ }
9085
+ await import_node_fs31.promises.rename(tmp, file);
9086
+ }
9087
+ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
9088
+ const deadline = Date.now() + timeoutMs;
9089
+ await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(lockPath), { recursive: true });
9090
+ for (; ; ) {
9091
+ try {
9092
+ const fd = await import_node_fs31.promises.open(lockPath, "wx");
9093
+ try {
9094
+ await fd.writeFile(`${process.pid}
9095
+ `, "utf8");
9096
+ } finally {
9097
+ await fd.close();
9098
+ }
9099
+ return;
9100
+ } catch (err) {
9101
+ if (err.code !== "EEXIST") throw err;
9102
+ if (Date.now() >= deadline) {
9103
+ throw new Error(
9104
+ `neat connector: timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process may be writing the connector config; if none is, remove that file by hand.`
9105
+ );
9106
+ }
9107
+ await new Promise((r) => setTimeout(r, CONNECTORS_LOCK_RETRY_MS));
9108
+ }
9109
+ }
9110
+ }
9111
+ async function releaseConnectorsLock(lockPath) {
9112
+ await import_node_fs31.promises.unlink(lockPath).catch(() => {
9113
+ });
9114
+ }
9115
+ async function withConnectorsLock(home, fn) {
9116
+ const lock = connectorsConfigLockPath(home);
9117
+ await acquireConnectorsLock(lock);
9118
+ try {
9119
+ return await fn();
9120
+ } finally {
9121
+ await releaseConnectorsLock(lock);
9122
+ }
9123
+ }
9124
+ function serializeConfig(config) {
9125
+ return JSON.stringify(
9126
+ { version: config.version ?? CONNECTORS_CONFIG_VERSION, connectors: config.connectors },
9127
+ null,
9128
+ 2
9129
+ ) + "\n";
9130
+ }
9131
+ async function upsertConnectorEntry(entry2, home = neatHome2()) {
9132
+ const file = connectorsConfigPath(home);
9133
+ return withConnectorsLock(home, async () => {
9134
+ const config = await readConnectorsConfig(home);
9135
+ validateEntry(entry2, config.connectors.length, file);
9136
+ const idx = config.connectors.findIndex((c) => c.id === entry2.id);
9137
+ const replaced = idx >= 0;
9138
+ if (replaced) config.connectors[idx] = entry2;
9139
+ else config.connectors.push(entry2);
9140
+ await writeConfigAtomically0600(file, serializeConfig(config));
9141
+ return { replaced };
9142
+ });
9143
+ }
9144
+ async function removeConnectorEntry(id, home = neatHome2()) {
9145
+ const file = connectorsConfigPath(home);
9146
+ return withConnectorsLock(home, async () => {
9147
+ const config = await readConnectorsConfig(home);
9148
+ const idx = config.connectors.findIndex((c) => c.id === id);
9149
+ if (idx < 0) return void 0;
9150
+ const [removed] = config.connectors.splice(idx, 1);
9151
+ await writeConfigAtomically0600(file, serializeConfig(config));
9152
+ return removed;
9153
+ });
9154
+ }
9155
+ function slugFragment(value) {
9156
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9157
+ }
9158
+ function autoSlugConnectorId(provider, project, existingIds) {
9159
+ const base = slugFragment(provider) || "connector";
9160
+ if (!existingIds.has(base)) return base;
9161
+ const projectFrag = project ? slugFragment(project) : "";
9162
+ if (projectFrag) {
9163
+ const withProject = `${base}-${projectFrag}`;
9164
+ if (!existingIds.has(withProject)) return withProject;
9165
+ }
9166
+ const stem = projectFrag ? `${base}-${projectFrag}` : base;
9167
+ for (let n = 2; ; n++) {
9168
+ const candidate = `${stem}-${n}`;
9169
+ if (!existingIds.has(candidate)) return candidate;
9170
+ }
9171
+ }
9172
+ function describeCredential(ref, env = process.env) {
9173
+ const one = (value, field) => {
9174
+ if (isEnvRef(value)) {
9175
+ const varName = value.slice(1);
9176
+ const resolved = env[varName];
9177
+ const set = typeof resolved === "string" && resolved.length > 0;
9178
+ return {
9179
+ ...field ? { field } : {},
9180
+ kind: "env-ref",
9181
+ display: value,
9182
+ status: set ? "set" : "unset"
9183
+ };
9184
+ }
9185
+ return { ...field ? { field } : {}, kind: "plaintext", display: "****" };
9186
+ };
9187
+ if (typeof ref === "string") return [one(ref)];
9188
+ return Object.entries(ref).map(([k, v]) => one(v, k));
9189
+ }
9190
+
9191
+ // src/connectors/status.ts
9192
+ init_cjs_shims();
9193
+ var CONNECTOR_STALE_THRESHOLD_MS = 5 * 6e4;
9194
+ var records = /* @__PURE__ */ new Map();
9195
+ function deriveState(rec, now, thresholdMs) {
9196
+ if (rec.lastOutcome === "error") return "error";
9197
+ const okAt = rec.lastOkAt ? Date.parse(rec.lastOkAt) : NaN;
9198
+ if (!Number.isNaN(okAt) && now - okAt > thresholdMs) return "stale";
9199
+ return "healthy";
9200
+ }
9201
+ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_THRESHOLD_MS) {
9202
+ const rec = records.get(id);
9203
+ if (!rec) {
9204
+ return {
9205
+ state: "idle",
9206
+ lastPollAt: null,
9207
+ lastOutcome: null,
9208
+ lastError: null,
9209
+ signalsLastPoll: 0
9210
+ };
9211
+ }
9212
+ return {
9213
+ state: deriveState(rec, now, thresholdMs),
9214
+ lastPollAt: rec.lastPollAt,
9215
+ lastOutcome: rec.lastOutcome,
9216
+ lastError: rec.lastError,
9217
+ signalsLastPoll: rec.signalsLastPoll
9218
+ };
9219
+ }
9220
+
9221
+ // src/api.ts
9222
+ function serializeGraph(graph) {
9223
+ const nodes = [];
9224
+ graph.forEachNode((_id, attrs) => {
9225
+ nodes.push(attrs);
9226
+ });
9227
+ const edges = [];
9228
+ graph.forEachEdge((_id, attrs) => {
9229
+ edges.push(attrs);
9230
+ });
9231
+ return { nodes, edges };
9232
+ }
9233
+ function projectFromReq(req, singleProject) {
9234
+ const params = req.params;
9235
+ const named = params.project;
9236
+ if (singleProject) {
9237
+ return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
9238
+ }
9239
+ return named ?? DEFAULT_PROJECT;
9240
+ }
9241
+ function resolveProject(registry, req, reply, bootstrap, singleProject) {
9242
+ const name = projectFromReq(req, singleProject);
9243
+ const ctx = registry.get(name);
9244
+ if (!ctx) {
9245
+ const phase = bootstrap?.status(name);
9246
+ if (phase === "bootstrapping") {
9247
+ void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
9248
+ return null;
9249
+ }
9250
+ if (phase === "broken") {
9251
+ void reply.code(503).send({ ready: false, project: name, status: "broken" });
9252
+ return null;
9253
+ }
9254
+ void reply.code(404).send({ error: "project not found", project: name });
9255
+ return null;
9256
+ }
9257
+ return ctx;
9258
+ }
9259
+ function buildLegacyRegistry(opts) {
9260
+ if (opts.projects) return opts.projects;
9261
+ if (!opts.graph) {
9262
+ throw new Error("buildApi: either `projects` or `graph` must be provided");
9263
+ }
9264
+ const registry = new Projects();
9265
+ const paths = pathsForProject(DEFAULT_PROJECT, "");
9266
+ registry.set(DEFAULT_PROJECT, {
9267
+ graph: opts.graph,
9268
+ scanPath: opts.scanPath,
9269
+ paths: {
8723
9270
  snapshotPath: paths.snapshotPath,
8724
9271
  errorsPath: opts.errorsPath ?? paths.errorsPath,
8725
9272
  staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,
@@ -8822,11 +9369,11 @@ function registerRoutes(scope, ctx) {
8822
9369
  const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
8823
9370
  const parsed = [];
8824
9371
  for (const c of candidates) {
8825
- const r = import_types31.DivergenceTypeSchema.safeParse(c);
9372
+ const r = import_types34.DivergenceTypeSchema.safeParse(c);
8826
9373
  if (!r.success) {
8827
9374
  return reply.code(400).send({
8828
9375
  error: `unknown divergence type "${c}"`,
8829
- allowed: import_types31.DivergenceTypeSchema.options
9376
+ allowed: import_types34.DivergenceTypeSchema.options
8830
9377
  });
8831
9378
  }
8832
9379
  parsed.push(r.data);
@@ -8891,6 +9438,26 @@ function registerRoutes(scope, ctx) {
8891
9438
  const sliced = filtered.slice(0, safeLimit);
8892
9439
  return { count: sliced.length, total, logs: sliced };
8893
9440
  });
9441
+ scope.get("/connectors", async (req, reply) => {
9442
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9443
+ if (!proj) return;
9444
+ let entries;
9445
+ try {
9446
+ entries = (await readConnectorsConfig(ctx.connectorsHome)).connectors;
9447
+ } catch (err) {
9448
+ return reply.code(500).send({
9449
+ error: "connectors.json failed to read",
9450
+ details: err.message
9451
+ });
9452
+ }
9453
+ const connectors = entries.filter((entry2) => connectorMatchesProject(entry2, proj.name)).map((entry2) => ({
9454
+ id: entry2.id,
9455
+ provider: entry2.provider,
9456
+ credentialRef: redactCredentialRef(entry2.credential),
9457
+ status: getConnectorStatus(entry2.id)
9458
+ }));
9459
+ return { connectors };
9460
+ });
8894
9461
  const incidentHistoryHandler = async (req, reply) => {
8895
9462
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
8896
9463
  if (!proj) return;
@@ -9077,7 +9644,7 @@ function registerRoutes(scope, ctx) {
9077
9644
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
9078
9645
  let violations = await log.readAll();
9079
9646
  if (req.query.severity) {
9080
- const sev = import_types31.PolicySeveritySchema.safeParse(req.query.severity);
9647
+ const sev = import_types34.PolicySeveritySchema.safeParse(req.query.severity);
9081
9648
  if (!sev.success) {
9082
9649
  return reply.code(400).send({
9083
9650
  error: "invalid severity",
@@ -9116,7 +9683,7 @@ function registerRoutes(scope, ctx) {
9116
9683
  scope.post("/policies/check", async (req, reply) => {
9117
9684
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
9118
9685
  if (!proj) return;
9119
- const parsed = import_types31.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9686
+ const parsed = import_types34.PoliciesCheckBodySchema.safeParse(req.body ?? {});
9120
9687
  if (!parsed.success) {
9121
9688
  return reply.code(400).send({
9122
9689
  error: "invalid /policies/check body",
@@ -9293,7 +9860,8 @@ async function buildApi(opts) {
9293
9860
  staleEventsPathFor,
9294
9861
  policyFilePathFor,
9295
9862
  bootstrap: opts.bootstrap,
9296
- singleProject: opts.singleProject?.name
9863
+ singleProject: opts.singleProject?.name,
9864
+ connectorsHome: opts.connectorsHome
9297
9865
  };
9298
9866
  app.get("/health", async () => {
9299
9867
  const uptimeMs = Date.now() - startedAt;
@@ -9378,8 +9946,8 @@ init_otel();
9378
9946
 
9379
9947
  // src/daemon.ts
9380
9948
  init_cjs_shims();
9381
- var import_node_fs30 = require("fs");
9382
- var import_node_path49 = __toESM(require("path"), 1);
9949
+ var import_node_fs33 = require("fs");
9950
+ var import_node_path52 = __toESM(require("path"), 1);
9383
9951
  var import_node_module = require("module");
9384
9952
  init_otel();
9385
9953
 
@@ -9673,6 +10241,30 @@ function buildEdgeLogsQuery(limit) {
9673
10241
  `limit ${safeLimit}`
9674
10242
  ].join("\n");
9675
10243
  }
10244
+ function logsAllHttpFailureMessage(status2) {
10245
+ if (status2 === 401 || status2 === 403) {
10246
+ return `supabase connector: logs.all request rejected (HTTP ${status2}). Check the Management API token, its analytics read scope, and --api-project-ref.`;
10247
+ }
10248
+ if (status2 === 404) {
10249
+ return "supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.";
10250
+ }
10251
+ if (status2 === 429) {
10252
+ return "supabase connector: logs.all rate-limited (HTTP 429). The connector will retry on the next poll; reduce poll cadence if this persists.";
10253
+ }
10254
+ if (status2 === 400) {
10255
+ return "supabase connector: logs.all query rejected (HTTP 400). Provider details redacted; confirm the live log-query dialect before shipping this connector.";
10256
+ }
10257
+ return `supabase connector: logs.all request failed (HTTP ${status2}); provider details redacted.`;
10258
+ }
10259
+ function providerErrorDetails(error) {
10260
+ if (!error || typeof error === "string") return "";
10261
+ const parts = [];
10262
+ if (typeof error.code === "number") parts.push(`code ${error.code}`);
10263
+ if (typeof error.status === "string" && /^[A-Z0-9_.-]+$/.test(error.status)) {
10264
+ parts.push(`status ${error.status}`);
10265
+ }
10266
+ return parts.length > 0 ? ` (${parts.join(", ")})` : "";
10267
+ }
9676
10268
  async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
9677
10269
  const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
9678
10270
  const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
@@ -9687,12 +10279,18 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
9687
10279
  { provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
9688
10280
  );
9689
10281
  if (!res.ok) {
9690
- throw new Error(`supabase connector: logs.all request failed (${res.status} ${res.statusText})`);
10282
+ throw new Error(logsAllHttpFailureMessage(res.status));
10283
+ }
10284
+ let body;
10285
+ try {
10286
+ body = await res.json();
10287
+ } catch {
10288
+ throw new Error("supabase connector: logs.all returned invalid JSON; provider details redacted.");
9691
10289
  }
9692
- const body = await res.json();
9693
10290
  if (body.error) {
9694
- const message = typeof body.error === "string" ? body.error : body.error.message;
9695
- throw new Error(`supabase connector: logs.all returned an error (${message})`);
10291
+ throw new Error(
10292
+ `supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`
10293
+ );
9696
10294
  }
9697
10295
  return body.result ?? [];
9698
10296
  }
@@ -9724,10 +10322,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
9724
10322
  // src/connectors/supabase/map.ts
9725
10323
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
9726
10324
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
9727
- function targetFromRestPath(path58) {
9728
- const rpcMatch = REST_RPC_PATH_RE.exec(path58);
10325
+ function targetFromRestPath(path61) {
10326
+ const rpcMatch = REST_RPC_PATH_RE.exec(path61);
9729
10327
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
9730
- const tableMatch = REST_TABLE_PATH_RE.exec(path58);
10328
+ const tableMatch = REST_TABLE_PATH_RE.exec(path61);
9731
10329
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
9732
10330
  return null;
9733
10331
  }
@@ -9830,19 +10428,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
9830
10428
 
9831
10429
  // src/connectors/supabase/resolve.ts
9832
10430
  init_cjs_shims();
9833
- var import_types33 = require("@neat.is/types");
10431
+ var import_types36 = require("@neat.is/types");
9834
10432
  function createSupabaseResolveTarget(graph, config) {
9835
10433
  return (signal, _ctx) => {
9836
10434
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
9837
10435
  return null;
9838
10436
  }
9839
- const subResourceId = (0, import_types33.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10437
+ const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
9840
10438
  if (graph.hasNode(subResourceId)) {
9841
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types33.EdgeType.CALLS };
10439
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
9842
10440
  }
9843
- const projectLevelId = (0, import_types33.infraId)("supabase", config.nodeRef);
10441
+ const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
9844
10442
  if (graph.hasNode(projectLevelId)) {
9845
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types33.EdgeType.CALLS };
10443
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
9846
10444
  }
9847
10445
  return null;
9848
10446
  };
@@ -9850,6 +10448,35 @@ function createSupabaseResolveTarget(graph, config) {
9850
10448
 
9851
10449
  // src/connectors/supabase/index.ts
9852
10450
  var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
10451
+ function errorCode(err) {
10452
+ const code = err?.code;
10453
+ return typeof code === "string" && code.length > 0 ? code : void 0;
10454
+ }
10455
+ function describeSupabasePostgresSurfaceFailure(projectRef, err) {
10456
+ const code = errorCode(err);
10457
+ let reason;
10458
+ switch (code) {
10459
+ case "42501":
10460
+ reason = "permission denied; grant pg_read_all_stats to the configured Postgres role";
10461
+ break;
10462
+ case "42P01":
10463
+ case "42704":
10464
+ reason = "pg_stat_statements is not enabled or visible to the configured Postgres role";
10465
+ break;
10466
+ case "28P01":
10467
+ case "28000":
10468
+ reason = "Postgres credential rejected";
10469
+ break;
10470
+ case "3D000":
10471
+ reason = "database not found";
10472
+ break;
10473
+ default: {
10474
+ const name = err instanceof Error && err.name ? err.name : "Error";
10475
+ reason = code ? `${name} ${code}` : name;
10476
+ }
10477
+ }
10478
+ return `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} (${reason}); continuing with Management API log surface.`;
10479
+ }
9853
10480
  var SupabaseConnector = class {
9854
10481
  // `deps.fetchPgStatStatements` defaults to the real Postgres-backed
9855
10482
  // implementation; tests override it to exercise the "both surfaces
@@ -9881,12 +10508,18 @@ var SupabaseConnector = class {
9881
10508
  const signals = mapEdgeLogRowsToSignals(logRows);
9882
10509
  if (creds.postgresConnectionString) {
9883
10510
  const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements;
9884
- const statementRows = await fetchStatements(
9885
- creds.postgresConnectionString,
9886
- this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
9887
- this.config.apiProjectRef
9888
- );
9889
- signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
10511
+ try {
10512
+ const statementRows = await fetchStatements(
10513
+ creds.postgresConnectionString,
10514
+ this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
10515
+ this.config.apiProjectRef
10516
+ );
10517
+ signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
10518
+ } catch (err) {
10519
+ const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err);
10520
+ if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary);
10521
+ else console.warn(summary);
10522
+ }
9890
10523
  }
9891
10524
  return signals;
9892
10525
  }
@@ -9900,7 +10533,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
9900
10533
 
9901
10534
  // src/connectors/railway/index.ts
9902
10535
  init_cjs_shims();
9903
- var import_types37 = require("@neat.is/types");
10536
+ var import_types40 = require("@neat.is/types");
9904
10537
 
9905
10538
  // src/connectors/railway/client.ts
9906
10539
  init_cjs_shims();
@@ -10045,7 +10678,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10045
10678
  const out = [];
10046
10679
  graph.forEachNode((_id, attrs) => {
10047
10680
  const node = attrs;
10048
- if (node.type !== import_types37.NodeType.RouteNode) return;
10681
+ if (node.type !== import_types40.NodeType.RouteNode) return;
10049
10682
  const route = attrs;
10050
10683
  if (route.service !== serviceName) return;
10051
10684
  out.push({
@@ -10141,12 +10774,12 @@ function createRailwayResolveTarget(config) {
10141
10774
  const serviceName = config.serviceNameById[config.serviceId];
10142
10775
  if (!serviceName) return null;
10143
10776
  if (signal.targetKind === ROUTE_TARGET_KIND) {
10144
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types37.EdgeType.CALLS };
10777
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
10145
10778
  }
10146
10779
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
10147
10780
  const peerName = config.serviceNameById[signal.targetName];
10148
10781
  if (!peerName) return null;
10149
- return { targetNodeId: (0, import_types37.serviceId)(peerName), serviceName, edgeType: import_types37.EdgeType.CONNECTS_TO };
10782
+ return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
10150
10783
  }
10151
10784
  return null;
10152
10785
  };
@@ -10261,9 +10894,9 @@ function parseFirebaseTargetName(targetName) {
10261
10894
  const secondSep = rest.indexOf(FIELD_SEP);
10262
10895
  if (secondSep === -1) return null;
10263
10896
  const method = rest.slice(0, secondSep);
10264
- const path58 = rest.slice(secondSep + 1);
10265
- if (!resourceName || !method || !path58) return null;
10266
- return { resourceName, method, path: path58 };
10897
+ const path61 = rest.slice(secondSep + 1);
10898
+ if (!resourceName || !method || !path61) return null;
10899
+ return { resourceName, method, path: path61 };
10267
10900
  }
10268
10901
  function resourceNameFor(type, labels) {
10269
10902
  if (!labels) return null;
@@ -10300,14 +10933,14 @@ function mapLogEntryToSignal(entry2) {
10300
10933
  if (!req) return null;
10301
10934
  if (!req.requestMethod) return null;
10302
10935
  const method = req.requestMethod.toUpperCase();
10303
- const path58 = pathFromRequestUrl(req.requestUrl);
10304
- if (path58 === null) return null;
10936
+ const path61 = pathFromRequestUrl(req.requestUrl);
10937
+ if (path61 === null) return null;
10305
10938
  const timestamp = entry2.timestamp;
10306
10939
  if (!timestamp) return null;
10307
10940
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
10308
10941
  return {
10309
10942
  targetKind: resourceType,
10310
- targetName: packFirebaseTargetName({ resourceName, method, path: path58 }),
10943
+ targetName: packFirebaseTargetName({ resourceName, method, path: path61 }),
10311
10944
  callCount: 1,
10312
10945
  errorCount: isError ? 1 : 0,
10313
10946
  lastObservedIso: timestamp
@@ -10324,7 +10957,7 @@ function mapLogEntriesToSignals(entries) {
10324
10957
 
10325
10958
  // src/connectors/firebase/resolve.ts
10326
10959
  init_cjs_shims();
10327
- var import_types38 = require("@neat.is/types");
10960
+ var import_types41 = require("@neat.is/types");
10328
10961
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
10329
10962
  switch (resourceType) {
10330
10963
  case "cloud_function":
@@ -10339,7 +10972,7 @@ function routeEntriesFor(graph, serviceName) {
10339
10972
  const entries = [];
10340
10973
  graph.forEachNode((_id, attrs) => {
10341
10974
  const node = attrs;
10342
- if (node.type !== import_types38.NodeType.RouteNode) return;
10975
+ if (node.type !== import_types41.NodeType.RouteNode) return;
10343
10976
  const route = attrs;
10344
10977
  if (route.service !== serviceName) return;
10345
10978
  entries.push({
@@ -10371,7 +11004,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
10371
11004
  return {
10372
11005
  targetNodeId: match.routeNodeId,
10373
11006
  serviceName,
10374
- edgeType: import_types38.EdgeType.CALLS
11007
+ edgeType: import_types41.EdgeType.CALLS
10375
11008
  };
10376
11009
  };
10377
11010
  }
@@ -10398,7 +11031,7 @@ init_cjs_shims();
10398
11031
 
10399
11032
  // src/connectors/cloudflare/connector.ts
10400
11033
  init_cjs_shims();
10401
- var import_types40 = require("@neat.is/types");
11034
+ var import_types43 = require("@neat.is/types");
10402
11035
 
10403
11036
  // src/connectors/cloudflare/client.ts
10404
11037
  init_cjs_shims();
@@ -10489,396 +11122,124 @@ function parseHttpMethodFromTrigger(trigger) {
10489
11122
  function parsePathFromTrigger(trigger) {
10490
11123
  const trimmed = trigger.trim();
10491
11124
  const spaceIdx = trimmed.indexOf(" ");
10492
- if (spaceIdx === -1) return void 0;
10493
- const rest = trimmed.slice(spaceIdx + 1).trim();
10494
- return rest.length > 0 ? rest : void 0;
10495
- }
10496
- var ERROR_STATUS_THRESHOLD3 = 500;
10497
- function mapEventToSignal(event) {
10498
- const metadata = event.$metadata;
10499
- const workers = event.$workers;
10500
- const method = parseHttpMethodFromTrigger(metadata?.trigger);
10501
- if (!method) return null;
10502
- const scriptName = workers?.scriptName ?? metadata?.service;
10503
- if (!scriptName) return null;
10504
- const timestampMs = event.timestamp ?? metadata?.startTime;
10505
- if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
10506
- const statusCode = metadata?.statusCode;
10507
- const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
10508
- const path58 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10509
- return {
10510
- targetKind: CLOUDFLARE_TARGET_KIND,
10511
- targetName: scriptName,
10512
- callCount: 1,
10513
- errorCount: isError ? 1 : 0,
10514
- lastObservedIso: new Date(timestampMs).toISOString(),
10515
- method,
10516
- ...path58 ? { path: path58 } : {},
10517
- ...typeof statusCode === "number" ? { statusCode } : {},
10518
- ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
10519
- };
10520
- }
10521
-
10522
- // src/connectors/cloudflare/connector.ts
10523
- var DEFAULT_MAX_LOOKBACK_MS3 = 60 * 60 * 1e3;
10524
- function resolveFromMs(since, maxLookbackMs) {
10525
- const cap = maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS3;
10526
- const now = Date.now();
10527
- const floor = now - cap;
10528
- if (!since) return floor;
10529
- const parsed = Date.parse(since);
10530
- if (Number.isNaN(parsed)) return floor;
10531
- return Math.max(parsed, floor);
10532
- }
10533
- var CloudflareConnector = class {
10534
- constructor(config) {
10535
- this.config = config;
10536
- }
10537
- config;
10538
- provider = "cloudflare";
10539
- async poll(ctx) {
10540
- const toMs = Date.now();
10541
- const fromMs = resolveFromMs(ctx.since, this.config.maxLookbackMs);
10542
- const events = await queryWorkerInvocations(ctx, this.config, { fromMs, toMs });
10543
- const signals = [];
10544
- for (const event of events) {
10545
- const signal = mapEventToSignal(event);
10546
- if (signal) signals.push(signal);
10547
- }
10548
- return signals;
10549
- }
10550
- };
10551
- function findTaggedWorkerFileNode(graph, workerName) {
10552
- let found = null;
10553
- graph.forEachNode((id, attrs) => {
10554
- if (found) return;
10555
- const a = attrs;
10556
- if (a.type === import_types40.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
10557
- found = id;
10558
- }
10559
- });
10560
- return found;
10561
- }
10562
- function findMatchingRouteNode(graph, serviceName, method, path58) {
10563
- const normalizedPath = normalizePathTemplate(path58);
10564
- let found = null;
10565
- graph.forEachNode((id, attrs) => {
10566
- if (found) return;
10567
- const a = attrs;
10568
- if (a.type !== import_types40.NodeType.RouteNode || a.service !== serviceName) return;
10569
- if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
10570
- const routeMethod = (a.method ?? "").toUpperCase();
10571
- if (routeMethod !== "ALL" && routeMethod !== method) return;
10572
- found = id;
10573
- });
10574
- return found;
10575
- }
10576
- function createCloudflareResolveTarget(config, graph) {
10577
- return (signal) => {
10578
- if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
10579
- const scriptName = signal.targetName;
10580
- const { method, path: path58 } = signal;
10581
- const resolveRouteGrain = (serviceName, wholeFileId) => {
10582
- if (!method || !path58) return wholeFileId;
10583
- return findMatchingRouteNode(graph, serviceName, method, path58) ?? wholeFileId;
10584
- };
10585
- const mapping = config.workers?.[scriptName];
10586
- if (mapping) {
10587
- const wholeFileId = (0, import_types40.fileId)(mapping.service, mapping.entryFile);
10588
- return {
10589
- targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
10590
- serviceName: mapping.service,
10591
- edgeType: import_types40.EdgeType.CALLS
10592
- };
10593
- }
10594
- const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
10595
- if (taggedFileId) {
10596
- const fileNode = graph.getNodeAttributes(taggedFileId);
10597
- return {
10598
- targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
10599
- serviceName: fileNode.service,
10600
- edgeType: import_types40.EdgeType.CALLS
10601
- };
10602
- }
10603
- return {
10604
- targetNodeId: (0, import_types40.infraId)("cloudflare-worker", scriptName),
10605
- serviceName: scriptName,
10606
- edgeType: import_types40.EdgeType.CALLS,
10607
- ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
10608
- };
10609
- };
10610
- }
10611
-
10612
- // src/connectors-config.ts
10613
- init_cjs_shims();
10614
- var import_node_os4 = __toESM(require("os"), 1);
10615
- var import_node_path47 = __toESM(require("path"), 1);
10616
- var import_node_fs28 = require("fs");
10617
- var CONNECTORS_CONFIG_VERSION = 1;
10618
- var EnvRefUnsetError = class extends Error {
10619
- ref;
10620
- varName;
10621
- constructor(ref, varName) {
10622
- super(`${ref} is unset`);
10623
- this.name = "EnvRefUnsetError";
10624
- this.ref = ref;
10625
- this.varName = varName;
10626
- }
10627
- };
10628
- function neatHome2() {
10629
- const override = process.env.NEAT_HOME;
10630
- if (override && override.length > 0) return import_node_path47.default.resolve(override);
10631
- return import_node_path47.default.join(import_node_os4.default.homedir(), ".neat");
10632
- }
10633
- function connectorsConfigPath(home = neatHome2()) {
10634
- return import_node_path47.default.join(home, "connectors.json");
10635
- }
10636
- var MODE_MASK_LOOSER_THAN_0600 = 63;
10637
- async function warnIfModeLooserThan0600(file) {
10638
- if (process.platform === "win32") return;
10639
- try {
10640
- const stat = await import_node_fs28.promises.stat(file);
10641
- if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
10642
- const mode = (stat.mode & 511).toString(8).padStart(3, "0");
10643
- console.warn(
10644
- `[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
10645
- );
10646
- }
10647
- } catch {
10648
- }
10649
- }
10650
- async function readConnectorsConfig(home = neatHome2()) {
10651
- const file = connectorsConfigPath(home);
10652
- let raw;
10653
- try {
10654
- raw = await import_node_fs28.promises.readFile(file, "utf8");
10655
- } catch (err) {
10656
- if (err.code === "ENOENT") {
10657
- return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
10658
- }
10659
- throw err;
10660
- }
10661
- await warnIfModeLooserThan0600(file);
10662
- let parsed;
10663
- try {
10664
- parsed = JSON.parse(raw);
10665
- } catch (err) {
10666
- throw new Error(`${file} is not valid JSON: ${err.message}`);
10667
- }
10668
- return validateConfig(parsed, file);
10669
- }
10670
- function validateConfig(parsed, file) {
10671
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
10672
- throw new Error(`${file} must be a JSON object with a "connectors" array`);
10673
- }
10674
- const obj = parsed;
10675
- const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
10676
- if (typeof version !== "number" || !Number.isInteger(version)) {
10677
- throw new Error(`${file}: "version" must be an integer`);
10678
- }
10679
- const rawConnectors = obj.connectors;
10680
- if (!Array.isArray(rawConnectors)) {
10681
- throw new Error(`${file}: "connectors" must be an array`);
10682
- }
10683
- const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
10684
- return { version, connectors };
10685
- }
10686
- function validateEntry(entry2, index, file) {
10687
- const where = `${file}: connectors[${index}]`;
10688
- if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
10689
- throw new Error(`${where} must be an object`);
10690
- }
10691
- const e = entry2;
10692
- const id = e.id;
10693
- if (typeof id !== "string" || id.length === 0) {
10694
- throw new Error(`${where}.id must be a non-empty string`);
10695
- }
10696
- const provider = e.provider;
10697
- if (typeof provider !== "string" || provider.length === 0) {
10698
- throw new Error(`${where}.provider must be a non-empty string`);
10699
- }
10700
- if (e.project !== void 0 && typeof e.project !== "string") {
10701
- throw new Error(`${where}.project must be a string when present`);
10702
- }
10703
- const credential = validateCredentialRef(e.credential, `${where}.credential`);
10704
- let options;
10705
- if (e.options !== void 0) {
10706
- if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
10707
- throw new Error(`${where}.options must be an object when present`);
10708
- }
10709
- options = e.options;
10710
- }
11125
+ if (spaceIdx === -1) return void 0;
11126
+ const rest = trimmed.slice(spaceIdx + 1).trim();
11127
+ return rest.length > 0 ? rest : void 0;
11128
+ }
11129
+ var ERROR_STATUS_THRESHOLD3 = 500;
11130
+ function mapEventToSignal(event) {
11131
+ const metadata = event.$metadata;
11132
+ const workers = event.$workers;
11133
+ const method = parseHttpMethodFromTrigger(metadata?.trigger);
11134
+ if (!method) return null;
11135
+ const scriptName = workers?.scriptName ?? metadata?.service;
11136
+ if (!scriptName) return null;
11137
+ const timestampMs = event.timestamp ?? metadata?.startTime;
11138
+ if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
11139
+ const statusCode = metadata?.statusCode;
11140
+ const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
11141
+ const path61 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10711
11142
  return {
10712
- id,
10713
- provider,
10714
- ...typeof e.project === "string" ? { project: e.project } : {},
10715
- credential,
10716
- ...options ? { options } : {}
11143
+ targetKind: CLOUDFLARE_TARGET_KIND,
11144
+ targetName: scriptName,
11145
+ callCount: 1,
11146
+ errorCount: isError ? 1 : 0,
11147
+ lastObservedIso: new Date(timestampMs).toISOString(),
11148
+ method,
11149
+ ...path61 ? { path: path61 } : {},
11150
+ ...typeof statusCode === "number" ? { statusCode } : {},
11151
+ ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
10717
11152
  };
10718
11153
  }
10719
- function validateCredentialRef(raw, where) {
10720
- if (typeof raw === "string") {
10721
- if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
10722
- return raw;
10723
- }
10724
- if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
10725
- const fields = raw;
10726
- const keys = Object.keys(fields);
10727
- if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
10728
- for (const key of keys) {
10729
- const v = fields[key];
10730
- if (typeof v !== "string" || v.length === 0) {
10731
- throw new Error(`${where}.${key} must be a non-empty string`);
10732
- }
10733
- }
10734
- return fields;
10735
- }
10736
- throw new Error(`${where} must be a string or an object of strings`);
11154
+
11155
+ // src/connectors/cloudflare/connector.ts
11156
+ var DEFAULT_MAX_LOOKBACK_MS3 = 60 * 60 * 1e3;
11157
+ function resolveFromMs(since, maxLookbackMs) {
11158
+ const cap = maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS3;
11159
+ const now = Date.now();
11160
+ const floor = now - cap;
11161
+ if (!since) return floor;
11162
+ const parsed = Date.parse(since);
11163
+ if (Number.isNaN(parsed)) return floor;
11164
+ return Math.max(parsed, floor);
10737
11165
  }
10738
- function resolveCredential(ref, env = process.env) {
10739
- if (typeof ref === "string") {
10740
- return { kind: "single", value: resolveRef(ref, env) };
10741
- }
10742
- const fields = {};
10743
- for (const [key, value] of Object.entries(ref)) {
10744
- fields[key] = resolveRef(value, env);
11166
+ var CloudflareConnector = class {
11167
+ constructor(config) {
11168
+ this.config = config;
10745
11169
  }
10746
- return { kind: "fields", fields };
10747
- }
10748
- function resolveRef(value, env) {
10749
- if (value.length > 1 && value.startsWith("$")) {
10750
- const varName = value.slice(1);
10751
- const resolved = env[varName];
10752
- if (resolved === void 0 || resolved.length === 0) {
10753
- throw new EnvRefUnsetError(value, varName);
11170
+ config;
11171
+ provider = "cloudflare";
11172
+ async poll(ctx) {
11173
+ const toMs = Date.now();
11174
+ const fromMs = resolveFromMs(ctx.since, this.config.maxLookbackMs);
11175
+ const events = await queryWorkerInvocations(ctx, this.config, { fromMs, toMs });
11176
+ const signals = [];
11177
+ for (const event of events) {
11178
+ const signal = mapEventToSignal(event);
11179
+ if (signal) signals.push(signal);
10754
11180
  }
10755
- return resolved;
10756
- }
10757
- return value;
10758
- }
10759
- var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
10760
- var CONNECTORS_LOCK_RETRY_MS = 50;
10761
- function connectorsConfigLockPath(home = neatHome2()) {
10762
- return import_node_path47.default.join(home, "connectors.json.lock");
10763
- }
10764
- function isEnvRef(value) {
10765
- return value.length > 1 && value.startsWith("$");
10766
- }
10767
- async function writeConfigAtomically0600(file, contents) {
10768
- await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(file), { recursive: true });
10769
- const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
10770
- const fd = await import_node_fs28.promises.open(tmp, "w", 384);
10771
- try {
10772
- await fd.writeFile(contents, "utf8");
10773
- await fd.chmod(384);
10774
- await fd.sync();
10775
- } finally {
10776
- await fd.close();
11181
+ return signals;
10777
11182
  }
10778
- await import_node_fs28.promises.rename(tmp, file);
10779
- }
10780
- async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
10781
- const deadline = Date.now() + timeoutMs;
10782
- await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(lockPath), { recursive: true });
10783
- for (; ; ) {
10784
- try {
10785
- const fd = await import_node_fs28.promises.open(lockPath, "wx");
10786
- try {
10787
- await fd.writeFile(`${process.pid}
10788
- `, "utf8");
10789
- } finally {
10790
- await fd.close();
10791
- }
10792
- return;
10793
- } catch (err) {
10794
- if (err.code !== "EEXIST") throw err;
10795
- if (Date.now() >= deadline) {
10796
- throw new Error(
10797
- `neat connector: timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process may be writing the connector config; if none is, remove that file by hand.`
10798
- );
10799
- }
10800
- await new Promise((r) => setTimeout(r, CONNECTORS_LOCK_RETRY_MS));
11183
+ };
11184
+ function findTaggedWorkerFileNode(graph, workerName) {
11185
+ let found = null;
11186
+ graph.forEachNode((id, attrs) => {
11187
+ if (found) return;
11188
+ const a = attrs;
11189
+ if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11190
+ found = id;
10801
11191
  }
10802
- }
10803
- }
10804
- async function releaseConnectorsLock(lockPath) {
10805
- await import_node_fs28.promises.unlink(lockPath).catch(() => {
10806
- });
10807
- }
10808
- async function withConnectorsLock(home, fn) {
10809
- const lock = connectorsConfigLockPath(home);
10810
- await acquireConnectorsLock(lock);
10811
- try {
10812
- return await fn();
10813
- } finally {
10814
- await releaseConnectorsLock(lock);
10815
- }
10816
- }
10817
- function serializeConfig(config) {
10818
- return JSON.stringify(
10819
- { version: config.version ?? CONNECTORS_CONFIG_VERSION, connectors: config.connectors },
10820
- null,
10821
- 2
10822
- ) + "\n";
10823
- }
10824
- async function upsertConnectorEntry(entry2, home = neatHome2()) {
10825
- const file = connectorsConfigPath(home);
10826
- return withConnectorsLock(home, async () => {
10827
- const config = await readConnectorsConfig(home);
10828
- validateEntry(entry2, config.connectors.length, file);
10829
- const idx = config.connectors.findIndex((c) => c.id === entry2.id);
10830
- const replaced = idx >= 0;
10831
- if (replaced) config.connectors[idx] = entry2;
10832
- else config.connectors.push(entry2);
10833
- await writeConfigAtomically0600(file, serializeConfig(config));
10834
- return { replaced };
10835
11192
  });
11193
+ return found;
10836
11194
  }
10837
- async function removeConnectorEntry(id, home = neatHome2()) {
10838
- const file = connectorsConfigPath(home);
10839
- return withConnectorsLock(home, async () => {
10840
- const config = await readConnectorsConfig(home);
10841
- const idx = config.connectors.findIndex((c) => c.id === id);
10842
- if (idx < 0) return void 0;
10843
- const [removed] = config.connectors.splice(idx, 1);
10844
- await writeConfigAtomically0600(file, serializeConfig(config));
10845
- return removed;
11195
+ function findMatchingRouteNode(graph, serviceName, method, path61) {
11196
+ const normalizedPath = normalizePathTemplate(path61);
11197
+ let found = null;
11198
+ graph.forEachNode((id, attrs) => {
11199
+ if (found) return;
11200
+ const a = attrs;
11201
+ if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
11202
+ if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11203
+ const routeMethod = (a.method ?? "").toUpperCase();
11204
+ if (routeMethod !== "ALL" && routeMethod !== method) return;
11205
+ found = id;
10846
11206
  });
11207
+ return found;
10847
11208
  }
10848
- function slugFragment(value) {
10849
- return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
10850
- }
10851
- function autoSlugConnectorId(provider, project, existingIds) {
10852
- const base = slugFragment(provider) || "connector";
10853
- if (!existingIds.has(base)) return base;
10854
- const projectFrag = project ? slugFragment(project) : "";
10855
- if (projectFrag) {
10856
- const withProject = `${base}-${projectFrag}`;
10857
- if (!existingIds.has(withProject)) return withProject;
10858
- }
10859
- const stem = projectFrag ? `${base}-${projectFrag}` : base;
10860
- for (let n = 2; ; n++) {
10861
- const candidate = `${stem}-${n}`;
10862
- if (!existingIds.has(candidate)) return candidate;
10863
- }
10864
- }
10865
- function describeCredential(ref, env = process.env) {
10866
- const one = (value, field) => {
10867
- if (isEnvRef(value)) {
10868
- const varName = value.slice(1);
10869
- const resolved = env[varName];
10870
- const set = typeof resolved === "string" && resolved.length > 0;
11209
+ function createCloudflareResolveTarget(config, graph) {
11210
+ return (signal) => {
11211
+ if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11212
+ const scriptName = signal.targetName;
11213
+ const { method, path: path61 } = signal;
11214
+ const resolveRouteGrain = (serviceName, wholeFileId) => {
11215
+ if (!method || !path61) return wholeFileId;
11216
+ return findMatchingRouteNode(graph, serviceName, method, path61) ?? wholeFileId;
11217
+ };
11218
+ const mapping = config.workers?.[scriptName];
11219
+ if (mapping) {
11220
+ const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
10871
11221
  return {
10872
- ...field ? { field } : {},
10873
- kind: "env-ref",
10874
- display: value,
10875
- status: set ? "set" : "unset"
11222
+ targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11223
+ serviceName: mapping.service,
11224
+ edgeType: import_types43.EdgeType.CALLS
10876
11225
  };
10877
11226
  }
10878
- return { ...field ? { field } : {}, kind: "plaintext", display: "****" };
11227
+ const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
11228
+ if (taggedFileId) {
11229
+ const fileNode = graph.getNodeAttributes(taggedFileId);
11230
+ return {
11231
+ targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11232
+ serviceName: fileNode.service,
11233
+ edgeType: import_types43.EdgeType.CALLS
11234
+ };
11235
+ }
11236
+ return {
11237
+ targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
11238
+ serviceName: scriptName,
11239
+ edgeType: import_types43.EdgeType.CALLS,
11240
+ ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11241
+ };
10879
11242
  };
10880
- if (typeof ref === "string") return [one(ref)];
10881
- return Object.entries(ref).map(([k, v]) => one(v, k));
10882
11243
  }
10883
11244
 
10884
11245
  // src/connectors/registry.ts
@@ -11074,33 +11435,33 @@ init_auth();
11074
11435
 
11075
11436
  // src/unrouted.ts
11076
11437
  init_cjs_shims();
11077
- var import_node_fs29 = require("fs");
11078
- var import_node_path48 = __toESM(require("path"), 1);
11438
+ var import_node_fs32 = require("fs");
11439
+ var import_node_path51 = __toESM(require("path"), 1);
11079
11440
 
11080
11441
  // src/daemon.ts
11081
- var import_types43 = require("@neat.is/types");
11442
+ var import_types46 = require("@neat.is/types");
11082
11443
  function daemonJsonPath(scanPath) {
11083
- return import_node_path49.default.join(scanPath, "neat-out", "daemon.json");
11444
+ return import_node_path52.default.join(scanPath, "neat-out", "daemon.json");
11084
11445
  }
11085
11446
  function daemonsDiscoveryDir(home) {
11086
11447
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
11087
- return import_node_path49.default.join(base, "daemons");
11448
+ return import_node_path52.default.join(base, "daemons");
11088
11449
  }
11089
11450
  function daemonDiscoveryPath(project, home) {
11090
- return import_node_path49.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11451
+ return import_node_path52.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11091
11452
  }
11092
11453
  function sanitizeDiscoveryName(project) {
11093
11454
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
11094
11455
  }
11095
11456
  function neatHomeFromEnv() {
11096
11457
  const env = process.env.NEAT_HOME;
11097
- if (env && env.length > 0) return import_node_path49.default.resolve(env);
11458
+ if (env && env.length > 0) return import_node_path52.default.resolve(env);
11098
11459
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11099
- return import_node_path49.default.join(home, ".neat");
11460
+ return import_node_path52.default.join(home, ".neat");
11100
11461
  }
11101
11462
  async function readDaemonRecord(scanPath) {
11102
11463
  try {
11103
- const raw = await import_node_fs30.promises.readFile(daemonJsonPath(scanPath), "utf8");
11464
+ const raw = await import_node_fs33.promises.readFile(daemonJsonPath(scanPath), "utf8");
11104
11465
  const parsed = JSON.parse(raw);
11105
11466
  if (typeof parsed.project === "string" && parsed.ports && typeof parsed.ports.rest === "number" && typeof parsed.ports.otlp === "number" && typeof parsed.ports.web === "number") {
11106
11467
  return parsed;
@@ -11140,7 +11501,7 @@ async function clearDaemonRecord(record, home) {
11140
11501
  } catch {
11141
11502
  }
11142
11503
  try {
11143
- await import_node_fs30.promises.unlink(daemonDiscoveryPath(record.project, home));
11504
+ await import_node_fs33.promises.unlink(daemonDiscoveryPath(record.project, home));
11144
11505
  } catch {
11145
11506
  }
11146
11507
  }
@@ -11166,8 +11527,8 @@ init_otel_grpc();
11166
11527
 
11167
11528
  // src/search.ts
11168
11529
  init_cjs_shims();
11169
- var import_node_fs31 = require("fs");
11170
- var import_node_path50 = __toESM(require("path"), 1);
11530
+ var import_node_fs34 = require("fs");
11531
+ var import_node_path53 = __toESM(require("path"), 1);
11171
11532
  var import_node_crypto4 = require("crypto");
11172
11533
  var DEFAULT_LIMIT = 10;
11173
11534
  var NOMIC_DIM = 768;
@@ -11321,7 +11682,7 @@ async function pickEmbedder() {
11321
11682
  }
11322
11683
  async function readCache(cachePath) {
11323
11684
  try {
11324
- const raw = await import_node_fs31.promises.readFile(cachePath, "utf8");
11685
+ const raw = await import_node_fs34.promises.readFile(cachePath, "utf8");
11325
11686
  const parsed = JSON.parse(raw);
11326
11687
  if (parsed.version !== 1) return null;
11327
11688
  return parsed;
@@ -11330,8 +11691,8 @@ async function readCache(cachePath) {
11330
11691
  }
11331
11692
  }
11332
11693
  async function writeCache(cachePath, cache) {
11333
- await import_node_fs31.promises.mkdir(import_node_path50.default.dirname(cachePath), { recursive: true });
11334
- await import_node_fs31.promises.writeFile(cachePath, JSON.stringify(cache));
11694
+ await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(cachePath), { recursive: true });
11695
+ await import_node_fs34.promises.writeFile(cachePath, JSON.stringify(cache));
11335
11696
  }
11336
11697
  var VectorIndex = class {
11337
11698
  constructor(embedder, cachePath) {
@@ -11488,8 +11849,8 @@ var ALL_PHASES = [
11488
11849
  ];
11489
11850
  function classifyChange(relPath) {
11490
11851
  const phases = /* @__PURE__ */ new Set();
11491
- const base = import_node_path51.default.basename(relPath).toLowerCase();
11492
- const segments = relPath.split(import_node_path51.default.sep).map((s) => s.toLowerCase());
11852
+ const base = import_node_path54.default.basename(relPath).toLowerCase();
11853
+ const segments = relPath.split(import_node_path54.default.sep).map((s) => s.toLowerCase());
11493
11854
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
11494
11855
  phases.add("services");
11495
11856
  phases.add("aliases");
@@ -11610,16 +11971,16 @@ function countWatchableDirs(scanPath, limit) {
11610
11971
  if (count >= limit) return;
11611
11972
  let entries;
11612
11973
  try {
11613
- entries = import_node_fs32.default.readdirSync(dir, { withFileTypes: true });
11974
+ entries = import_node_fs35.default.readdirSync(dir, { withFileTypes: true });
11614
11975
  } catch {
11615
11976
  return;
11616
11977
  }
11617
11978
  for (const e of entries) {
11618
11979
  if (count >= limit) return;
11619
11980
  if (!e.isDirectory()) continue;
11620
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path51.default.join(dir, e.name) + import_node_path51.default.sep))) continue;
11981
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path54.default.join(dir, e.name) + import_node_path54.default.sep))) continue;
11621
11982
  count++;
11622
- if (depth < 2) visit(import_node_path51.default.join(dir, e.name), depth + 1);
11983
+ if (depth < 2) visit(import_node_path54.default.join(dir, e.name), depth + 1);
11623
11984
  }
11624
11985
  };
11625
11986
  visit(scanPath, 0);
@@ -11637,8 +11998,8 @@ async function startWatch(graph, opts) {
11637
11998
  const projectName = opts.project ?? DEFAULT_PROJECT;
11638
11999
  await loadGraphFromDisk(graph, opts.outPath);
11639
12000
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
11640
- const policyFilePath = import_node_path51.default.join(opts.scanPath, "policy.json");
11641
- const policyViolationsPath = import_node_path51.default.join(import_node_path51.default.dirname(opts.outPath), "policy-violations.ndjson");
12001
+ const policyFilePath = import_node_path54.default.join(opts.scanPath, "policy.json");
12002
+ const policyViolationsPath = import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "policy-violations.ndjson");
11642
12003
  let policies = [];
11643
12004
  try {
11644
12005
  policies = await loadPolicyFile(policyFilePath);
@@ -11689,7 +12050,7 @@ async function startWatch(graph, opts) {
11689
12050
  assertBindAuthority(host, auth.authToken);
11690
12051
  const port = opts.port ?? 8080;
11691
12052
  const otelPort = opts.otelPort ?? 4318;
11692
- const cachePath = opts.embeddingsCachePath ?? import_node_path51.default.join(import_node_path51.default.dirname(opts.outPath), "embeddings.json");
12053
+ const cachePath = opts.embeddingsCachePath ?? import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "embeddings.json");
11693
12054
  let searchIndex;
11694
12055
  try {
11695
12056
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -11707,7 +12068,7 @@ async function startWatch(graph, opts) {
11707
12068
  // Paths are derived from the explicit options the watch caller passes
11708
12069
  // — pathsForProject is only used to fill in the embeddings/snapshot
11709
12070
  // fields so the registry shape is complete.
11710
- ...pathsForProject(projectName, import_node_path51.default.dirname(opts.outPath)),
12071
+ ...pathsForProject(projectName, import_node_path54.default.dirname(opts.outPath)),
11711
12072
  snapshotPath: opts.outPath,
11712
12073
  errorsPath: opts.errorsPath,
11713
12074
  staleEventsPath: opts.staleEventsPath
@@ -11825,9 +12186,9 @@ async function startWatch(graph, opts) {
11825
12186
  };
11826
12187
  const onPath = (absPath) => {
11827
12188
  if (shouldIgnore(absPath)) return;
11828
- const rel = import_node_path51.default.relative(opts.scanPath, absPath);
12189
+ const rel = import_node_path54.default.relative(opts.scanPath, absPath);
11829
12190
  if (!rel || rel.startsWith("..")) return;
11830
- pendingPaths.add(rel.split(import_node_path51.default.sep).join("/"));
12191
+ pendingPaths.add(rel.split(import_node_path54.default.sep).join("/"));
11831
12192
  const phases = classifyChange(rel);
11832
12193
  if (phases.size === 0) {
11833
12194
  for (const p of ALL_PHASES) pending.add(p);
@@ -11883,8 +12244,8 @@ async function startWatch(graph, opts) {
11883
12244
 
11884
12245
  // src/deploy/detect.ts
11885
12246
  init_cjs_shims();
11886
- var import_node_fs33 = require("fs");
11887
- var import_node_path52 = __toESM(require("path"), 1);
12247
+ var import_node_fs36 = require("fs");
12248
+ var import_node_path55 = __toESM(require("path"), 1);
11888
12249
  var import_node_child_process2 = require("child_process");
11889
12250
  var import_node_crypto5 = require("crypto");
11890
12251
  function generateToken() {
@@ -11984,21 +12345,21 @@ async function runDeploy(opts = {}) {
11984
12345
  const token = generateToken();
11985
12346
  switch (substrate) {
11986
12347
  case "docker-compose": {
11987
- const artifactPath = import_node_path52.default.join(cwd, "docker-compose.neat.yml");
12348
+ const artifactPath = import_node_path55.default.join(cwd, "docker-compose.neat.yml");
11988
12349
  const contents = emitDockerCompose(cwd);
11989
- await import_node_fs33.promises.writeFile(artifactPath, contents, "utf8");
12350
+ await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
11990
12351
  return {
11991
12352
  substrate,
11992
12353
  artifactPath,
11993
12354
  token,
11994
12355
  contents,
11995
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path52.default.basename(artifactPath)} up -d`
12356
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path55.default.basename(artifactPath)} up -d`
11996
12357
  };
11997
12358
  }
11998
12359
  case "systemd": {
11999
- const artifactPath = import_node_path52.default.join(cwd, "neat.service");
12360
+ const artifactPath = import_node_path55.default.join(cwd, "neat.service");
12000
12361
  const contents = emitSystemdUnit(cwd);
12001
- await import_node_fs33.promises.writeFile(artifactPath, contents, "utf8");
12362
+ await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
12002
12363
  return {
12003
12364
  substrate,
12004
12365
  artifactPath,
@@ -12031,8 +12392,8 @@ init_cjs_shims();
12031
12392
 
12032
12393
  // src/installers/javascript.ts
12033
12394
  init_cjs_shims();
12034
- var import_node_fs34 = require("fs");
12035
- var import_node_path53 = __toESM(require("path"), 1);
12395
+ var import_node_fs37 = require("fs");
12396
+ var import_node_path56 = __toESM(require("path"), 1);
12036
12397
  var import_semver2 = __toESM(require("semver"), 1);
12037
12398
 
12038
12399
  // src/installers/templates.ts
@@ -12647,15 +13008,15 @@ var OTEL_ENV = {
12647
13008
  value: "http://localhost:4318/projects/<project>/v1/traces"
12648
13009
  };
12649
13010
  function serviceNodeName(pkg, serviceDir) {
12650
- return pkg.name ?? import_node_path53.default.basename(serviceDir);
13011
+ return pkg.name ?? import_node_path56.default.basename(serviceDir);
12651
13012
  }
12652
13013
  function projectToken(pkg, serviceDir, project) {
12653
13014
  if (project && project.length > 0) return project;
12654
- return pkg.name ?? import_node_path53.default.basename(serviceDir);
13015
+ return pkg.name ?? import_node_path56.default.basename(serviceDir);
12655
13016
  }
12656
13017
  async function readJsonFile(p) {
12657
13018
  try {
12658
- const raw = await import_node_fs34.promises.readFile(p, "utf8");
13019
+ const raw = await import_node_fs37.promises.readFile(p, "utf8");
12659
13020
  return JSON.parse(raw);
12660
13021
  } catch {
12661
13022
  return null;
@@ -12664,16 +13025,16 @@ async function readJsonFile(p) {
12664
13025
  async function detectRuntimeKind(pkgRoot, pkg) {
12665
13026
  const deps = allDeps(pkg);
12666
13027
  if ("react-native" in deps || "expo" in deps) return "react-native";
12667
- const appJson = await readJsonFile(import_node_path53.default.join(pkgRoot, "app.json"));
13028
+ const appJson = await readJsonFile(import_node_path56.default.join(pkgRoot, "app.json"));
12668
13029
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
12669
13030
  return "react-native";
12670
13031
  }
12671
- if (await exists3(import_node_path53.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path53.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path53.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
13032
+ if (await exists3(import_node_path56.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path56.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path56.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
12672
13033
  return "browser-bundle";
12673
13034
  }
12674
- if (await exists3(import_node_path53.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
12675
- if (await exists3(import_node_path53.default.join(pkgRoot, "bun.lockb"))) return "bun";
12676
- if (await exists3(import_node_path53.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path53.default.join(pkgRoot, "deno.lock"))) {
13035
+ if (await exists3(import_node_path56.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
13036
+ if (await exists3(import_node_path56.default.join(pkgRoot, "bun.lockb"))) return "bun";
13037
+ if (await exists3(import_node_path56.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path56.default.join(pkgRoot, "deno.lock"))) {
12677
13038
  return "deno";
12678
13039
  }
12679
13040
  const engines = pkg.engines ?? {};
@@ -12682,7 +13043,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
12682
13043
  }
12683
13044
  async function readPackageJson2(serviceDir) {
12684
13045
  try {
12685
- const raw = await import_node_fs34.promises.readFile(import_node_path53.default.join(serviceDir, "package.json"), "utf8");
13046
+ const raw = await import_node_fs37.promises.readFile(import_node_path56.default.join(serviceDir, "package.json"), "utf8");
12686
13047
  return JSON.parse(raw);
12687
13048
  } catch {
12688
13049
  return null;
@@ -12690,7 +13051,7 @@ async function readPackageJson2(serviceDir) {
12690
13051
  }
12691
13052
  async function exists3(p) {
12692
13053
  try {
12693
- await import_node_fs34.promises.stat(p);
13054
+ await import_node_fs37.promises.stat(p);
12694
13055
  return true;
12695
13056
  } catch {
12696
13057
  return false;
@@ -12698,7 +13059,7 @@ async function exists3(p) {
12698
13059
  }
12699
13060
  async function readFileMaybe(p) {
12700
13061
  try {
12701
- return await import_node_fs34.promises.readFile(p, "utf8");
13062
+ return await import_node_fs37.promises.readFile(p, "utf8");
12702
13063
  } catch {
12703
13064
  return null;
12704
13065
  }
@@ -12726,7 +13087,7 @@ function needsVersionUpgrade(installed, expected) {
12726
13087
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
12727
13088
  async function findNextConfig(serviceDir) {
12728
13089
  for (const name of NEXT_CONFIG_CANDIDATES) {
12729
- const candidate = import_node_path53.default.join(serviceDir, name);
13090
+ const candidate = import_node_path56.default.join(serviceDir, name);
12730
13091
  if (await exists3(candidate)) return candidate;
12731
13092
  }
12732
13093
  return null;
@@ -12795,7 +13156,7 @@ function hasRemixDependency(pkg) {
12795
13156
  }
12796
13157
  async function findRemixEntry(serviceDir) {
12797
13158
  for (const rel of REMIX_ENTRY_CANDIDATES) {
12798
- const candidate = import_node_path53.default.join(serviceDir, rel);
13159
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12799
13160
  if (await exists3(candidate)) return candidate;
12800
13161
  }
12801
13162
  return null;
@@ -12807,14 +13168,14 @@ function hasSvelteKitDependency(pkg) {
12807
13168
  }
12808
13169
  async function findSvelteKitHooks(serviceDir) {
12809
13170
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
12810
- const candidate = import_node_path53.default.join(serviceDir, rel);
13171
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12811
13172
  if (await exists3(candidate)) return candidate;
12812
13173
  }
12813
13174
  return null;
12814
13175
  }
12815
13176
  async function findSvelteKitConfig(serviceDir) {
12816
13177
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
12817
- const candidate = import_node_path53.default.join(serviceDir, rel);
13178
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12818
13179
  if (await exists3(candidate)) return candidate;
12819
13180
  }
12820
13181
  return null;
@@ -12825,7 +13186,7 @@ function hasNuxtDependency(pkg) {
12825
13186
  }
12826
13187
  async function findNuxtConfig(serviceDir) {
12827
13188
  for (const name of NUXT_CONFIG_CANDIDATES) {
12828
- const candidate = import_node_path53.default.join(serviceDir, name);
13189
+ const candidate = import_node_path56.default.join(serviceDir, name);
12829
13190
  if (await exists3(candidate)) return candidate;
12830
13191
  }
12831
13192
  return null;
@@ -12836,7 +13197,7 @@ function hasAstroDependency(pkg) {
12836
13197
  }
12837
13198
  async function findAstroConfig(serviceDir) {
12838
13199
  for (const name of ASTRO_CONFIG_CANDIDATES) {
12839
- const candidate = import_node_path53.default.join(serviceDir, name);
13200
+ const candidate = import_node_path56.default.join(serviceDir, name);
12840
13201
  if (await exists3(candidate)) return candidate;
12841
13202
  }
12842
13203
  return null;
@@ -12850,7 +13211,7 @@ function parseNextMajor(range) {
12850
13211
  return Number.isFinite(n) ? n : null;
12851
13212
  }
12852
13213
  async function isTypeScriptProject(serviceDir) {
12853
- return exists3(import_node_path53.default.join(serviceDir, "tsconfig.json"));
13214
+ return exists3(import_node_path56.default.join(serviceDir, "tsconfig.json"));
12854
13215
  }
12855
13216
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
12856
13217
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -12899,7 +13260,7 @@ function entryFromScript(script) {
12899
13260
  }
12900
13261
  async function resolveEntry(serviceDir, pkg) {
12901
13262
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
12902
- const candidate = import_node_path53.default.resolve(serviceDir, pkg.main);
13263
+ const candidate = import_node_path56.default.resolve(serviceDir, pkg.main);
12903
13264
  if (await exists3(candidate)) return candidate;
12904
13265
  }
12905
13266
  if (pkg.bin) {
@@ -12913,40 +13274,40 @@ async function resolveEntry(serviceDir, pkg) {
12913
13274
  if (typeof first === "string") binEntry = first;
12914
13275
  }
12915
13276
  if (binEntry) {
12916
- const candidate = import_node_path53.default.resolve(serviceDir, binEntry);
13277
+ const candidate = import_node_path56.default.resolve(serviceDir, binEntry);
12917
13278
  if (await exists3(candidate)) return candidate;
12918
13279
  }
12919
13280
  }
12920
13281
  const startEntry = entryFromScript(pkg.scripts?.start);
12921
13282
  if (startEntry) {
12922
- const candidate = import_node_path53.default.resolve(serviceDir, startEntry);
13283
+ const candidate = import_node_path56.default.resolve(serviceDir, startEntry);
12923
13284
  if (await exists3(candidate)) return candidate;
12924
13285
  }
12925
13286
  const devEntry = entryFromScript(pkg.scripts?.dev);
12926
13287
  if (devEntry) {
12927
- const candidate = import_node_path53.default.resolve(serviceDir, devEntry);
13288
+ const candidate = import_node_path56.default.resolve(serviceDir, devEntry);
12928
13289
  if (await exists3(candidate)) return candidate;
12929
13290
  }
12930
13291
  for (const rel of SRC_INDEX_CANDIDATES) {
12931
- const candidate = import_node_path53.default.join(serviceDir, rel);
13292
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12932
13293
  if (await exists3(candidate)) return candidate;
12933
13294
  }
12934
13295
  for (const rel of SRC_NAMED_CANDIDATES) {
12935
- const candidate = import_node_path53.default.join(serviceDir, rel);
13296
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12936
13297
  if (await exists3(candidate)) return candidate;
12937
13298
  }
12938
13299
  for (const rel of ROOT_NAMED_CANDIDATES) {
12939
- const candidate = import_node_path53.default.join(serviceDir, rel);
13300
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12940
13301
  if (await exists3(candidate)) return candidate;
12941
13302
  }
12942
13303
  for (const name of INDEX_CANDIDATES) {
12943
- const candidate = import_node_path53.default.join(serviceDir, name);
13304
+ const candidate = import_node_path56.default.join(serviceDir, name);
12944
13305
  if (await exists3(candidate)) return candidate;
12945
13306
  }
12946
13307
  return null;
12947
13308
  }
12948
13309
  function dispatchEntry(entryFile, pkg) {
12949
- const ext = import_node_path53.default.extname(entryFile).toLowerCase();
13310
+ const ext = import_node_path56.default.extname(entryFile).toLowerCase();
12950
13311
  if (ext === ".ts" || ext === ".tsx") return "ts";
12951
13312
  if (ext === ".mjs") return "esm";
12952
13313
  if (ext === ".cjs") return "cjs";
@@ -12963,9 +13324,9 @@ function otelInitContents(flavor) {
12963
13324
  return OTEL_INIT_CJS;
12964
13325
  }
12965
13326
  function injectionLine(flavor, entryFile, otelInitFile) {
12966
- let rel = import_node_path53.default.relative(import_node_path53.default.dirname(entryFile), otelInitFile);
13327
+ let rel = import_node_path56.default.relative(import_node_path56.default.dirname(entryFile), otelInitFile);
12967
13328
  if (!rel.startsWith(".")) rel = `./${rel}`;
12968
- rel = rel.split(import_node_path53.default.sep).join("/");
13329
+ rel = rel.split(import_node_path56.default.sep).join("/");
12969
13330
  if (flavor === "cjs") return `require('${rel}')`;
12970
13331
  if (flavor === "esm") return `import '${rel}'`;
12971
13332
  const tsRel = rel.replace(/\.ts$/, "");
@@ -12978,27 +13339,27 @@ function lineIsOtelInjection(line) {
12978
13339
  }
12979
13340
  async function detectsSrcLayout(serviceDir) {
12980
13341
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
12981
- exists3(import_node_path53.default.join(serviceDir, "src", "app")),
12982
- exists3(import_node_path53.default.join(serviceDir, "src", "pages")),
12983
- exists3(import_node_path53.default.join(serviceDir, "app")),
12984
- exists3(import_node_path53.default.join(serviceDir, "pages"))
13342
+ exists3(import_node_path56.default.join(serviceDir, "src", "app")),
13343
+ exists3(import_node_path56.default.join(serviceDir, "src", "pages")),
13344
+ exists3(import_node_path56.default.join(serviceDir, "app")),
13345
+ exists3(import_node_path56.default.join(serviceDir, "pages"))
12985
13346
  ]);
12986
13347
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
12987
13348
  }
12988
13349
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
12989
13350
  const useTs = await isTypeScriptProject(serviceDir);
12990
13351
  const srcLayout = await detectsSrcLayout(serviceDir);
12991
- const baseDir = srcLayout ? import_node_path53.default.join(serviceDir, "src") : serviceDir;
12992
- const instrumentationFile = import_node_path53.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
12993
- const instrumentationNodeFile = import_node_path53.default.join(
13352
+ const baseDir = srcLayout ? import_node_path56.default.join(serviceDir, "src") : serviceDir;
13353
+ const instrumentationFile = import_node_path56.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
13354
+ const instrumentationNodeFile = import_node_path56.default.join(
12994
13355
  baseDir,
12995
13356
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
12996
13357
  );
12997
- const instrumentationEdgeFile = import_node_path53.default.join(
13358
+ const instrumentationEdgeFile = import_node_path56.default.join(
12998
13359
  baseDir,
12999
13360
  useTs ? "instrumentation.edge.ts" : "instrumentation.edge.js"
13000
13361
  );
13001
- const envNeatFile = import_node_path53.default.join(baseDir, ".env.neat");
13362
+ const envNeatFile = import_node_path56.default.join(baseDir, ".env.neat");
13002
13363
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
13003
13364
  const dependencyEdits = [];
13004
13365
  for (const sdk of SDK_PACKAGES) {
@@ -13075,7 +13436,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
13075
13436
  const nextMajor = parseNextMajor(nextRange);
13076
13437
  if (nextMajor !== null && nextMajor < 15) {
13077
13438
  try {
13078
- const raw = await import_node_fs34.promises.readFile(nextConfigPath, "utf8");
13439
+ const raw = await import_node_fs37.promises.readFile(nextConfigPath, "utf8");
13079
13440
  if (!raw.includes("instrumentationHook")) {
13080
13441
  nextConfigEdit = {
13081
13442
  file: nextConfigPath,
@@ -13123,7 +13484,7 @@ function buildDependencyEdits(pkg, manifestPath) {
13123
13484
  return edits;
13124
13485
  }
13125
13486
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
13126
- const envNeatFile = import_node_path53.default.join(serviceDir, ".env.neat");
13487
+ const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
13127
13488
  if (!await exists3(envNeatFile)) {
13128
13489
  generatedFiles.push({
13129
13490
  file: envNeatFile,
@@ -13158,7 +13519,7 @@ function fileImportsOtelHook(raw, specifiers) {
13158
13519
  }
13159
13520
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
13160
13521
  const useTs = await isTypeScriptProject(serviceDir);
13161
- const otelServerFile = import_node_path53.default.join(
13522
+ const otelServerFile = import_node_path56.default.join(
13162
13523
  serviceDir,
13163
13524
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
13164
13525
  );
@@ -13179,7 +13540,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
13179
13540
  await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
13180
13541
  const entrypointEdits = [];
13181
13542
  try {
13182
- const raw = await import_node_fs34.promises.readFile(entryFile, "utf8");
13543
+ const raw = await import_node_fs37.promises.readFile(entryFile, "utf8");
13183
13544
  if (!fileImportsOtelHook(raw, ["./otel.server"])) {
13184
13545
  const lines = raw.split(/\r?\n/);
13185
13546
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -13215,11 +13576,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
13215
13576
  }
13216
13577
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
13217
13578
  const useTs = await isTypeScriptProject(serviceDir);
13218
- const otelInitFile = import_node_path53.default.join(
13579
+ const otelInitFile = import_node_path56.default.join(
13219
13580
  serviceDir,
13220
13581
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
13221
13582
  );
13222
- const resolvedHooksFile = hooksFile ?? import_node_path53.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
13583
+ const resolvedHooksFile = hooksFile ?? import_node_path56.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
13223
13584
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
13224
13585
  const generatedFiles = [];
13225
13586
  const entrypointEdits = [];
@@ -13244,7 +13605,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
13244
13605
  });
13245
13606
  } else {
13246
13607
  try {
13247
- const raw = await import_node_fs34.promises.readFile(hooksFile, "utf8");
13608
+ const raw = await import_node_fs37.promises.readFile(hooksFile, "utf8");
13248
13609
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
13249
13610
  const lines = raw.split(/\r?\n/);
13250
13611
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -13281,11 +13642,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
13281
13642
  }
13282
13643
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
13283
13644
  const useTs = await isTypeScriptProject(serviceDir);
13284
- const otelPluginFile = import_node_path53.default.join(
13645
+ const otelPluginFile = import_node_path56.default.join(
13285
13646
  serviceDir,
13286
13647
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
13287
13648
  );
13288
- const otelInitFile = import_node_path53.default.join(
13649
+ const otelInitFile = import_node_path56.default.join(
13289
13650
  serviceDir,
13290
13651
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
13291
13652
  );
@@ -13336,19 +13697,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
13336
13697
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
13337
13698
  async function findAstroMiddleware(serviceDir) {
13338
13699
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
13339
- const candidate = import_node_path53.default.join(serviceDir, rel);
13700
+ const candidate = import_node_path56.default.join(serviceDir, rel);
13340
13701
  if (await exists3(candidate)) return candidate;
13341
13702
  }
13342
13703
  return null;
13343
13704
  }
13344
13705
  async function planAstro(serviceDir, pkg, manifestPath, project) {
13345
13706
  const useTs = await isTypeScriptProject(serviceDir);
13346
- const otelInitFile = import_node_path53.default.join(
13707
+ const otelInitFile = import_node_path56.default.join(
13347
13708
  serviceDir,
13348
13709
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
13349
13710
  );
13350
13711
  const existingMiddleware = await findAstroMiddleware(serviceDir);
13351
- const middlewareFile = existingMiddleware ?? import_node_path53.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
13712
+ const middlewareFile = existingMiddleware ?? import_node_path56.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
13352
13713
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
13353
13714
  const generatedFiles = [];
13354
13715
  const entrypointEdits = [];
@@ -13373,7 +13734,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
13373
13734
  });
13374
13735
  } else {
13375
13736
  try {
13376
- const raw = await import_node_fs34.promises.readFile(existingMiddleware, "utf8");
13737
+ const raw = await import_node_fs37.promises.readFile(existingMiddleware, "utf8");
13377
13738
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
13378
13739
  const lines = raw.split(/\r?\n/);
13379
13740
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -13444,7 +13805,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
13444
13805
  }
13445
13806
  async function plan(serviceDir, opts) {
13446
13807
  const pkg = await readPackageJson2(serviceDir);
13447
- const manifestPath = import_node_path53.default.join(serviceDir, "package.json");
13808
+ const manifestPath = import_node_path56.default.join(serviceDir, "package.json");
13448
13809
  const project = opts?.project;
13449
13810
  const empty = {
13450
13811
  language: "javascript",
@@ -13479,8 +13840,8 @@ async function plan(serviceDir, opts) {
13479
13840
  return { ...empty, libOnly: true };
13480
13841
  }
13481
13842
  const flavor = dispatchEntry(entryFile, pkg);
13482
- const otelInitFile = import_node_path53.default.join(import_node_path53.default.dirname(entryFile), otelInitFilename(flavor));
13483
- const envNeatFile = import_node_path53.default.join(serviceDir, ".env.neat");
13843
+ const otelInitFile = import_node_path56.default.join(import_node_path56.default.dirname(entryFile), otelInitFilename(flavor));
13844
+ const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
13484
13845
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
13485
13846
  const dependencyEdits = [];
13486
13847
  for (const sdk of SDK_PACKAGES) {
@@ -13504,7 +13865,7 @@ async function plan(serviceDir, opts) {
13504
13865
  }
13505
13866
  const entrypointEdits = [];
13506
13867
  try {
13507
- const raw = await import_node_fs34.promises.readFile(entryFile, "utf8");
13868
+ const raw = await import_node_fs37.promises.readFile(entryFile, "utf8");
13508
13869
  const lines = raw.split(/\r?\n/);
13509
13870
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
13510
13871
  if (!lineIsOtelInjection(firstReal)) {
@@ -13549,13 +13910,13 @@ async function plan(serviceDir, opts) {
13549
13910
  };
13550
13911
  }
13551
13912
  function isAllowedWritePath(serviceDir, target) {
13552
- const rel = import_node_path53.default.relative(serviceDir, target);
13913
+ const rel = import_node_path56.default.relative(serviceDir, target);
13553
13914
  if (rel.startsWith("..")) return false;
13554
- const base = import_node_path53.default.basename(target);
13915
+ const base = import_node_path56.default.basename(target);
13555
13916
  if (base === "package.json") return true;
13556
13917
  if (base === ".env.neat") return true;
13557
13918
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
13558
- const relPosix = rel.split(import_node_path53.default.sep).join("/");
13919
+ const relPosix = rel.split(import_node_path56.default.sep).join("/");
13559
13920
  if (/^instrumentation(?:\.(?:node|edge))?\.(?:js|cjs|mjs|ts)$/.test(base)) {
13560
13921
  if (relPosix === base) return true;
13561
13922
  if (relPosix === `src/${base}`) return true;
@@ -13572,10 +13933,10 @@ function isAllowedWritePath(serviceDir, target) {
13572
13933
  return false;
13573
13934
  }
13574
13935
  async function writeAtomic(file, contents) {
13575
- await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(file), { recursive: true });
13936
+ await import_node_fs37.promises.mkdir(import_node_path56.default.dirname(file), { recursive: true });
13576
13937
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
13577
- await import_node_fs34.promises.writeFile(tmp, contents, "utf8");
13578
- await import_node_fs34.promises.rename(tmp, file);
13938
+ await import_node_fs37.promises.writeFile(tmp, contents, "utf8");
13939
+ await import_node_fs37.promises.rename(tmp, file);
13579
13940
  }
13580
13941
  async function apply(installPlan) {
13581
13942
  const { serviceDir } = installPlan;
@@ -13651,7 +14012,7 @@ async function apply(installPlan) {
13651
14012
  for (const target of allTargets) {
13652
14013
  if (await exists3(target)) {
13653
14014
  try {
13654
- originals.set(target, await import_node_fs34.promises.readFile(target, "utf8"));
14015
+ originals.set(target, await import_node_fs37.promises.readFile(target, "utf8"));
13655
14016
  } catch {
13656
14017
  }
13657
14018
  }
@@ -13734,14 +14095,14 @@ async function rollback(installPlan, originals, createdFiles) {
13734
14095
  const removed = [];
13735
14096
  for (const [file, raw] of originals.entries()) {
13736
14097
  try {
13737
- await import_node_fs34.promises.writeFile(file, raw, "utf8");
14098
+ await import_node_fs37.promises.writeFile(file, raw, "utf8");
13738
14099
  restored.push(file);
13739
14100
  } catch {
13740
14101
  }
13741
14102
  }
13742
14103
  for (const file of createdFiles) {
13743
14104
  try {
13744
- await import_node_fs34.promises.unlink(file);
14105
+ await import_node_fs37.promises.unlink(file);
13745
14106
  removed.push(file);
13746
14107
  } catch {
13747
14108
  }
@@ -13756,8 +14117,8 @@ async function rollback(installPlan, originals, createdFiles) {
13756
14117
  ...removed.map((f) => `removed: ${f}`),
13757
14118
  ""
13758
14119
  ];
13759
- const rollbackPath = import_node_path53.default.join(installPlan.serviceDir, "neat-rollback.patch");
13760
- await import_node_fs34.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
14120
+ const rollbackPath = import_node_path56.default.join(installPlan.serviceDir, "neat-rollback.patch");
14121
+ await import_node_fs37.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
13761
14122
  }
13762
14123
  function injectInstrumentationHook(raw) {
13763
14124
  if (raw.includes("instrumentationHook")) return raw;
@@ -13786,8 +14147,8 @@ var javascriptInstaller = {
13786
14147
 
13787
14148
  // src/installers/python.ts
13788
14149
  init_cjs_shims();
13789
- var import_node_fs35 = require("fs");
13790
- var import_node_path54 = __toESM(require("path"), 1);
14150
+ var import_node_fs38 = require("fs");
14151
+ var import_node_path57 = __toESM(require("path"), 1);
13791
14152
  var SDK_PACKAGES2 = [
13792
14153
  { name: "opentelemetry-distro", version: ">=0.49b0" },
13793
14154
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -13799,7 +14160,7 @@ var OTEL_ENV2 = {
13799
14160
  };
13800
14161
  async function exists4(p) {
13801
14162
  try {
13802
- await import_node_fs35.promises.stat(p);
14163
+ await import_node_fs38.promises.stat(p);
13803
14164
  return true;
13804
14165
  } catch {
13805
14166
  return false;
@@ -13808,7 +14169,7 @@ async function exists4(p) {
13808
14169
  async function detect2(serviceDir) {
13809
14170
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
13810
14171
  for (const m of markers) {
13811
- if (await exists4(import_node_path54.default.join(serviceDir, m))) return true;
14172
+ if (await exists4(import_node_path57.default.join(serviceDir, m))) return true;
13812
14173
  }
13813
14174
  return false;
13814
14175
  }
@@ -13818,9 +14179,9 @@ function reqPackageName(line) {
13818
14179
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
13819
14180
  }
13820
14181
  async function planRequirementsTxtEdits(serviceDir) {
13821
- const file = import_node_path54.default.join(serviceDir, "requirements.txt");
14182
+ const file = import_node_path57.default.join(serviceDir, "requirements.txt");
13822
14183
  if (!await exists4(file)) return null;
13823
- const raw = await import_node_fs35.promises.readFile(file, "utf8");
14184
+ const raw = await import_node_fs38.promises.readFile(file, "utf8");
13824
14185
  const presentNames = new Set(
13825
14186
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
13826
14187
  );
@@ -13828,9 +14189,9 @@ async function planRequirementsTxtEdits(serviceDir) {
13828
14189
  return { manifest: file, missing: [...missing] };
13829
14190
  }
13830
14191
  async function planProcfileEdits(serviceDir) {
13831
- const procfile = import_node_path54.default.join(serviceDir, "Procfile");
14192
+ const procfile = import_node_path57.default.join(serviceDir, "Procfile");
13832
14193
  if (!await exists4(procfile)) return [];
13833
- const raw = await import_node_fs35.promises.readFile(procfile, "utf8");
14194
+ const raw = await import_node_fs38.promises.readFile(procfile, "utf8");
13834
14195
  const edits = [];
13835
14196
  for (const line of raw.split(/\r?\n/)) {
13836
14197
  if (line.length === 0) continue;
@@ -13882,8 +14243,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
13882
14243
  const next = `${original}${trailing}${newlines.join("\n")}
13883
14244
  `;
13884
14245
  const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
13885
- await import_node_fs35.promises.writeFile(tmp, next, "utf8");
13886
- await import_node_fs35.promises.rename(tmp, manifest);
14246
+ await import_node_fs38.promises.writeFile(tmp, next, "utf8");
14247
+ await import_node_fs38.promises.rename(tmp, manifest);
13887
14248
  }
13888
14249
  async function applyProcfile(procfile, edits, original) {
13889
14250
  let next = original;
@@ -13892,8 +14253,8 @@ async function applyProcfile(procfile, edits, original) {
13892
14253
  next = next.replace(e.before, e.after);
13893
14254
  }
13894
14255
  const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
13895
- await import_node_fs35.promises.writeFile(tmp, next, "utf8");
13896
- await import_node_fs35.promises.rename(tmp, procfile);
14256
+ await import_node_fs38.promises.writeFile(tmp, next, "utf8");
14257
+ await import_node_fs38.promises.rename(tmp, procfile);
13897
14258
  }
13898
14259
  async function apply2(installPlan) {
13899
14260
  const { serviceDir } = installPlan;
@@ -13906,7 +14267,7 @@ async function apply2(installPlan) {
13906
14267
  const originals = /* @__PURE__ */ new Map();
13907
14268
  for (const file of touched) {
13908
14269
  try {
13909
- originals.set(file, await import_node_fs35.promises.readFile(file, "utf8"));
14270
+ originals.set(file, await import_node_fs38.promises.readFile(file, "utf8"));
13910
14271
  } catch {
13911
14272
  }
13912
14273
  }
@@ -13917,7 +14278,7 @@ async function apply2(installPlan) {
13917
14278
  if (raw === void 0) {
13918
14279
  throw new Error(`python installer: cannot read ${file} during apply`);
13919
14280
  }
13920
- const base = import_node_path54.default.basename(file);
14281
+ const base = import_node_path57.default.basename(file);
13921
14282
  if (base === "requirements.txt") {
13922
14283
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
13923
14284
  if (edits.length > 0) {
@@ -13942,7 +14303,7 @@ async function rollback2(installPlan, originals) {
13942
14303
  const restored = [];
13943
14304
  for (const [file, raw] of originals.entries()) {
13944
14305
  try {
13945
- await import_node_fs35.promises.writeFile(file, raw, "utf8");
14306
+ await import_node_fs38.promises.writeFile(file, raw, "utf8");
13946
14307
  restored.push(file);
13947
14308
  } catch {
13948
14309
  }
@@ -13956,8 +14317,8 @@ async function rollback2(installPlan, originals) {
13956
14317
  ...restored.map((f) => `restored: ${f}`),
13957
14318
  ""
13958
14319
  ];
13959
- const rollbackPath = import_node_path54.default.join(installPlan.serviceDir, "neat-rollback.patch");
13960
- await import_node_fs35.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
14320
+ const rollbackPath = import_node_path57.default.join(installPlan.serviceDir, "neat-rollback.patch");
14321
+ await import_node_fs38.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
13961
14322
  }
13962
14323
  var pythonInstaller = {
13963
14324
  name: "python",
@@ -14080,10 +14441,10 @@ function renderPatch(sections) {
14080
14441
 
14081
14442
  // src/orchestrator.ts
14082
14443
  init_cjs_shims();
14083
- var import_node_fs36 = require("fs");
14444
+ var import_node_fs39 = require("fs");
14084
14445
  var import_node_http = __toESM(require("http"), 1);
14085
14446
  var import_node_net = __toESM(require("net"), 1);
14086
- var import_node_path55 = __toESM(require("path"), 1);
14447
+ var import_node_path58 = __toESM(require("path"), 1);
14087
14448
  var import_node_child_process3 = require("child_process");
14088
14449
  var import_node_readline = __toESM(require("readline"), 1);
14089
14450
  async function extractAndPersist(opts) {
@@ -14092,7 +14453,7 @@ async function extractAndPersist(opts) {
14092
14453
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
14093
14454
  resetGraph(graphKey);
14094
14455
  const graph = getGraph(graphKey);
14095
- const projectPaths = pathsForProject(graphKey, import_node_path55.default.join(opts.scanPath, "neat-out"));
14456
+ const projectPaths = pathsForProject(graphKey, import_node_path58.default.join(opts.scanPath, "neat-out"));
14096
14457
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
14097
14458
  errorsPath: projectPaths.errorsPath
14098
14459
  });
@@ -14144,7 +14505,7 @@ async function applyInstallersOver(services, project, options = {}) {
14144
14505
  libOnly++;
14145
14506
  const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
14146
14507
  if (appDeps.length > 0) {
14147
- const svcName = import_node_path55.default.basename(svc.dir);
14508
+ const svcName = import_node_path58.default.basename(svc.dir);
14148
14509
  const list = appDeps.join(", ");
14149
14510
  console.warn(
14150
14511
  `neat: runtime layer won't engage for ${svcName}: no entry point found.
@@ -14157,7 +14518,7 @@ async function applyInstallersOver(services, project, options = {}) {
14157
14518
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
14158
14519
  } else if (outcome.outcome === "react-native") {
14159
14520
  reactNative++;
14160
- const svcName = import_node_path55.default.basename(svc.dir);
14521
+ const svcName = import_node_path58.default.basename(svc.dir);
14161
14522
  console.log(
14162
14523
  `neat: ${svc.dir} detected as React Native / Expo
14163
14524
  The installer doesn't cover this runtime deterministically.
@@ -14168,7 +14529,7 @@ async function applyInstallersOver(services, project, options = {}) {
14168
14529
  );
14169
14530
  } else if (outcome.outcome === "bun") {
14170
14531
  bun++;
14171
- const svcName = import_node_path55.default.basename(svc.dir);
14532
+ const svcName = import_node_path58.default.basename(svc.dir);
14172
14533
  console.log(
14173
14534
  `neat: ${svc.dir} detected as Bun
14174
14535
  The installer doesn't cover this runtime deterministically.
@@ -14179,7 +14540,7 @@ async function applyInstallersOver(services, project, options = {}) {
14179
14540
  );
14180
14541
  } else if (outcome.outcome === "deno") {
14181
14542
  deno++;
14182
- const svcName = import_node_path55.default.basename(svc.dir);
14543
+ const svcName = import_node_path58.default.basename(svc.dir);
14183
14544
  console.log(
14184
14545
  `neat: ${svc.dir} detected as Deno
14185
14546
  The installer doesn't cover this runtime deterministically.
@@ -14190,7 +14551,7 @@ async function applyInstallersOver(services, project, options = {}) {
14190
14551
  );
14191
14552
  } else if (outcome.outcome === "cloudflare-workers") {
14192
14553
  cloudflareWorkers++;
14193
- const svcName = import_node_path55.default.basename(svc.dir);
14554
+ const svcName = import_node_path58.default.basename(svc.dir);
14194
14555
  console.log(
14195
14556
  `neat: ${svc.dir} detected as Cloudflare Workers
14196
14557
  The installer doesn't cover this runtime deterministically.
@@ -14201,7 +14562,7 @@ async function applyInstallersOver(services, project, options = {}) {
14201
14562
  );
14202
14563
  } else if (outcome.outcome === "electron") {
14203
14564
  electron++;
14204
- const svcName = import_node_path55.default.basename(svc.dir);
14565
+ const svcName = import_node_path58.default.basename(svc.dir);
14205
14566
  console.log(
14206
14567
  `neat: ${svc.dir} detected as Electron
14207
14568
  The installer doesn't cover this runtime deterministically.
@@ -14214,7 +14575,7 @@ async function applyInstallersOver(services, project, options = {}) {
14214
14575
  if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
14215
14576
  const gaps = uninstrumentedLibraries(svc.pkg);
14216
14577
  if (gaps.length > 0) {
14217
- const svcName = import_node_path55.default.basename(svc.dir);
14578
+ const svcName = import_node_path58.default.basename(svc.dir);
14218
14579
  const list = gaps.join(", ");
14219
14580
  const subject = gaps.length === 1 ? "this library" : "these libraries";
14220
14581
  const aux = gaps.length === 1 ? "isn't" : "aren't";
@@ -14420,24 +14781,24 @@ async function persistedPortsFor(scanPath) {
14420
14781
  return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
14421
14782
  }
14422
14783
  async function acquireSpawnLock(scanPath) {
14423
- const lockPath = import_node_path55.default.join(scanPath, "neat-out", "daemon.spawn.lock");
14424
- await import_node_fs36.promises.mkdir(import_node_path55.default.dirname(lockPath), { recursive: true });
14784
+ const lockPath = import_node_path58.default.join(scanPath, "neat-out", "daemon.spawn.lock");
14785
+ await import_node_fs39.promises.mkdir(import_node_path58.default.dirname(lockPath), { recursive: true });
14425
14786
  const STALE_LOCK_MS = 6e4;
14426
14787
  try {
14427
- const fd = await import_node_fs36.promises.open(lockPath, "wx");
14788
+ const fd = await import_node_fs39.promises.open(lockPath, "wx");
14428
14789
  await fd.writeFile(`${process.pid}
14429
14790
  `, "utf8");
14430
14791
  await fd.close();
14431
14792
  return async () => {
14432
- await import_node_fs36.promises.unlink(lockPath).catch(() => {
14793
+ await import_node_fs39.promises.unlink(lockPath).catch(() => {
14433
14794
  });
14434
14795
  };
14435
14796
  } catch (err) {
14436
14797
  if (err.code !== "EEXIST") return null;
14437
14798
  try {
14438
- const stat = await import_node_fs36.promises.stat(lockPath);
14799
+ const stat = await import_node_fs39.promises.stat(lockPath);
14439
14800
  if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
14440
- await import_node_fs36.promises.unlink(lockPath).catch(() => {
14801
+ await import_node_fs39.promises.unlink(lockPath).catch(() => {
14441
14802
  });
14442
14803
  return acquireSpawnLock(scanPath);
14443
14804
  }
@@ -14466,13 +14827,13 @@ async function healthIsForProject(restPort, project) {
14466
14827
  return false;
14467
14828
  }
14468
14829
  function daemonLogPath(projectPath2) {
14469
- return import_node_path55.default.join(projectPath2, "neat-out", "daemon.log");
14830
+ return import_node_path58.default.join(projectPath2, "neat-out", "daemon.log");
14470
14831
  }
14471
14832
  function spawnDaemonDetached(spec) {
14472
- const here = import_node_path55.default.dirname(new URL(importMetaUrl).pathname);
14833
+ const here = import_node_path58.default.dirname(new URL(importMetaUrl).pathname);
14473
14834
  const candidates = [
14474
- import_node_path55.default.join(here, "neatd.cjs"),
14475
- import_node_path55.default.join(here, "neatd.js")
14835
+ import_node_path58.default.join(here, "neatd.cjs"),
14836
+ import_node_path58.default.join(here, "neatd.js")
14476
14837
  ];
14477
14838
  let entry2 = null;
14478
14839
  const fsSync = require("fs");
@@ -14502,7 +14863,7 @@ function spawnDaemonDetached(spec) {
14502
14863
  let logFd = null;
14503
14864
  if (spec) {
14504
14865
  const logPath = daemonLogPath(spec.projectPath);
14505
- fsSync.mkdirSync(import_node_path55.default.dirname(logPath), { recursive: true });
14866
+ fsSync.mkdirSync(import_node_path58.default.dirname(logPath), { recursive: true });
14506
14867
  logFd = fsSync.openSync(logPath, "a");
14507
14868
  }
14508
14869
  const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
@@ -14541,7 +14902,7 @@ async function runOrchestrator(opts) {
14541
14902
  browser: "skipped"
14542
14903
  }
14543
14904
  };
14544
- const stat = await import_node_fs36.promises.stat(opts.scanPath).catch(() => null);
14905
+ const stat = await import_node_fs39.promises.stat(opts.scanPath).catch(() => null);
14545
14906
  if (!stat || !stat.isDirectory()) {
14546
14907
  console.error(`neat: ${opts.scanPath} is not a directory`);
14547
14908
  result.exitCode = 2;
@@ -14701,7 +15062,7 @@ async function runOrchestrator(opts) {
14701
15062
  result.steps.browser = openBrowser(dashboardUrl);
14702
15063
  }
14703
15064
  const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
14704
- const daemonLog = daemonRunning ? import_node_path55.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
15065
+ const daemonLog = daemonRunning ? import_node_path58.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
14705
15066
  printSummary(result, graph, dashboardUrl, daemonLog);
14706
15067
  return result;
14707
15068
  }
@@ -15069,11 +15430,11 @@ async function runConnectorCommand(rawArgs, deps = {}) {
15069
15430
 
15070
15431
  // src/cli-verbs.ts
15071
15432
  init_cjs_shims();
15072
- var import_node_path56 = __toESM(require("path"), 1);
15433
+ var import_node_path59 = __toESM(require("path"), 1);
15073
15434
 
15074
15435
  // src/cli-client.ts
15075
15436
  init_cjs_shims();
15076
- var import_types44 = require("@neat.is/types");
15437
+ var import_types47 = require("@neat.is/types");
15077
15438
  var HttpError = class extends Error {
15078
15439
  constructor(status2, message, responseBody = "") {
15079
15440
  super(message);
@@ -15098,10 +15459,10 @@ function createHttpClient(baseUrl, bearerToken) {
15098
15459
  const root = baseUrl.replace(/\/$/, "");
15099
15460
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
15100
15461
  return {
15101
- async get(path58) {
15462
+ async get(path61) {
15102
15463
  let res;
15103
15464
  try {
15104
- res = await fetch(`${root}${path58}`, {
15465
+ res = await fetch(`${root}${path61}`, {
15105
15466
  headers: { ...authHeader }
15106
15467
  });
15107
15468
  } catch (err) {
@@ -15113,16 +15474,16 @@ function createHttpClient(baseUrl, bearerToken) {
15113
15474
  const body = await res.text().catch(() => "");
15114
15475
  throw new HttpError(
15115
15476
  res.status,
15116
- `${res.status} ${res.statusText} on GET ${path58}: ${body}`,
15477
+ `${res.status} ${res.statusText} on GET ${path61}: ${body}`,
15117
15478
  body
15118
15479
  );
15119
15480
  }
15120
15481
  return await res.json();
15121
15482
  },
15122
- async post(path58, body) {
15483
+ async post(path61, body) {
15123
15484
  let res;
15124
15485
  try {
15125
- res = await fetch(`${root}${path58}`, {
15486
+ res = await fetch(`${root}${path61}`, {
15126
15487
  method: "POST",
15127
15488
  headers: { "content-type": "application/json", ...authHeader },
15128
15489
  body: JSON.stringify(body)
@@ -15136,7 +15497,7 @@ function createHttpClient(baseUrl, bearerToken) {
15136
15497
  const text = await res.text().catch(() => "");
15137
15498
  throw new HttpError(
15138
15499
  res.status,
15139
- `${res.status} ${res.statusText} on POST ${path58}: ${text}`,
15500
+ `${res.status} ${res.statusText} on POST ${path61}: ${text}`,
15140
15501
  text
15141
15502
  );
15142
15503
  }
@@ -15150,12 +15511,12 @@ function projectPath(project, suffix) {
15150
15511
  }
15151
15512
  async function runRootCause(client, input) {
15152
15513
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
15153
- const path58 = projectPath(
15514
+ const path61 = projectPath(
15154
15515
  input.project,
15155
15516
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
15156
15517
  );
15157
15518
  try {
15158
- const result = await client.get(path58);
15519
+ const result = await client.get(path61);
15159
15520
  const arrowPath = result.traversalPath.join(" \u2190 ");
15160
15521
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
15161
15522
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -15181,12 +15542,12 @@ async function runRootCause(client, input) {
15181
15542
  }
15182
15543
  async function runBlastRadius(client, input) {
15183
15544
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
15184
- const path58 = projectPath(
15545
+ const path61 = projectPath(
15185
15546
  input.project,
15186
15547
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
15187
15548
  );
15188
15549
  try {
15189
- const result = await client.get(path58);
15550
+ const result = await client.get(path61);
15190
15551
  if (result.totalAffected === 0) {
15191
15552
  return {
15192
15553
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -15215,17 +15576,17 @@ async function runBlastRadius(client, input) {
15215
15576
  }
15216
15577
  }
15217
15578
  function formatBlastEntry(n) {
15218
- const tag = n.edgeProvenance === import_types44.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
15579
+ const tag = n.edgeProvenance === import_types47.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
15219
15580
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
15220
15581
  }
15221
15582
  async function runDependencies(client, input) {
15222
15583
  const depth = input.depth ?? 3;
15223
- const path58 = projectPath(
15584
+ const path61 = projectPath(
15224
15585
  input.project,
15225
15586
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
15226
15587
  );
15227
15588
  try {
15228
- const result = await client.get(path58);
15589
+ const result = await client.get(path61);
15229
15590
  if (result.total === 0) {
15230
15591
  return {
15231
15592
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -15272,7 +15633,7 @@ async function runObservedDependencies(client, input) {
15272
15633
  if (result.observed) {
15273
15634
  return {
15274
15635
  summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
15275
- provenance: import_types44.Provenance.OBSERVED
15636
+ provenance: import_types47.Provenance.OBSERVED
15276
15637
  };
15277
15638
  }
15278
15639
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -15282,7 +15643,7 @@ async function runObservedDependencies(client, input) {
15282
15643
  return {
15283
15644
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
15284
15645
  block: blockLines.join("\n"),
15285
- provenance: import_types44.Provenance.OBSERVED
15646
+ provenance: import_types47.Provenance.OBSERVED
15286
15647
  };
15287
15648
  } catch (err) {
15288
15649
  if (err instanceof HttpError && err.status === 404) {
@@ -15317,9 +15678,9 @@ function formatDuration(ms) {
15317
15678
  return `${Math.round(h / 24)}d`;
15318
15679
  }
15319
15680
  async function runIncidents(client, input) {
15320
- const path58 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
15681
+ const path61 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
15321
15682
  try {
15322
- const body = await client.get(path58);
15683
+ const body = await client.get(path61);
15323
15684
  const events = body.events;
15324
15685
  if (events.length === 0) {
15325
15686
  return {
@@ -15336,7 +15697,7 @@ async function runIncidents(client, input) {
15336
15697
  return {
15337
15698
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
15338
15699
  block: blockLines.join("\n"),
15339
- provenance: import_types44.Provenance.OBSERVED
15700
+ provenance: import_types47.Provenance.OBSERVED
15340
15701
  };
15341
15702
  } catch (err) {
15342
15703
  if (err instanceof HttpError && err.status === 404) {
@@ -15445,7 +15806,7 @@ async function runStaleEdges(client, input) {
15445
15806
  return {
15446
15807
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
15447
15808
  block: blockLines.join("\n"),
15448
- provenance: import_types44.Provenance.STALE
15809
+ provenance: import_types47.Provenance.STALE
15449
15810
  };
15450
15811
  }
15451
15812
  async function runPolicies(client, input) {
@@ -15609,7 +15970,7 @@ async function resolveProjectEntry(opts) {
15609
15970
  const cwd = opts.cwd ?? process.cwd();
15610
15971
  const resolvedCwd = await normalizeProjectPath(cwd);
15611
15972
  for (const entry2 of entries) {
15612
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path56.default.sep}`)) {
15973
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path59.default.sep}`)) {
15613
15974
  return entry2;
15614
15975
  }
15615
15976
  }
@@ -15762,7 +16123,7 @@ async function runSync(opts) {
15762
16123
  }
15763
16124
 
15764
16125
  // src/cli.ts
15765
- var import_types45 = require("@neat.is/types");
16126
+ var import_types48 = require("@neat.is/types");
15766
16127
  function isNpxInvocation() {
15767
16128
  if (process.env.npm_command === "exec") return true;
15768
16129
  const execpath = process.env.npm_execpath ?? "";
@@ -16055,7 +16416,7 @@ async function buildPatchSections(services, project) {
16055
16416
  }
16056
16417
  async function runInit(opts) {
16057
16418
  const written = [];
16058
- const stat = await import_node_fs37.promises.stat(opts.scanPath).catch(() => null);
16419
+ const stat = await import_node_fs40.promises.stat(opts.scanPath).catch(() => null);
16059
16420
  if (!stat || !stat.isDirectory()) {
16060
16421
  console.error(`neat init: ${opts.scanPath} is not a directory`);
16061
16422
  return { exitCode: 2, writtenFiles: written };
@@ -16064,13 +16425,13 @@ async function runInit(opts) {
16064
16425
  printDiscoveryReport(opts, services);
16065
16426
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
16066
16427
  const patch = renderPatch(sections);
16067
- const patchPath = import_node_path57.default.join(opts.scanPath, "neat.patch");
16428
+ const patchPath = import_node_path60.default.join(opts.scanPath, "neat.patch");
16068
16429
  if (opts.dryRun) {
16069
- await import_node_fs37.promises.writeFile(patchPath, patch, "utf8");
16430
+ await import_node_fs40.promises.writeFile(patchPath, patch, "utf8");
16070
16431
  written.push(patchPath);
16071
16432
  console.log(`dry-run: patch written to ${patchPath}`);
16072
- const gitignorePath = import_node_path57.default.join(opts.scanPath, ".gitignore");
16073
- const gitignoreExists = await import_node_fs37.promises.stat(gitignorePath).then(() => true).catch(() => false);
16433
+ const gitignorePath = import_node_path60.default.join(opts.scanPath, ".gitignore");
16434
+ const gitignoreExists = await import_node_fs40.promises.stat(gitignorePath).then(() => true).catch(() => false);
16074
16435
  const verb = gitignoreExists ? "append" : "create";
16075
16436
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
16076
16437
  console.log("rerun without --dry-run to register and snapshot.");
@@ -16081,9 +16442,9 @@ async function runInit(opts) {
16081
16442
  const graph = getGraph(graphKey);
16082
16443
  const projectPaths = pathsForProject(
16083
16444
  graphKey,
16084
- import_node_path57.default.join(opts.scanPath, "neat-out")
16445
+ import_node_path60.default.join(opts.scanPath, "neat-out")
16085
16446
  );
16086
- const errorsPath = import_node_path57.default.join(import_node_path57.default.dirname(opts.outPath), import_node_path57.default.basename(projectPaths.errorsPath));
16447
+ const errorsPath = import_node_path60.default.join(import_node_path60.default.dirname(opts.outPath), import_node_path60.default.basename(projectPaths.errorsPath));
16087
16448
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
16088
16449
  await saveGraphToDisk(graph, opts.outPath);
16089
16450
  written.push(opts.outPath);
@@ -16162,7 +16523,7 @@ async function runInit(opts) {
16162
16523
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
16163
16524
  }
16164
16525
  } else {
16165
- await import_node_fs37.promises.writeFile(patchPath, patch, "utf8");
16526
+ await import_node_fs40.promises.writeFile(patchPath, patch, "utf8");
16166
16527
  written.push(patchPath);
16167
16528
  }
16168
16529
  }
@@ -16202,9 +16563,9 @@ var CLAUDE_SKILL_CONFIG = {
16202
16563
  };
16203
16564
  function claudeConfigPath() {
16204
16565
  const override = process.env.NEAT_CLAUDE_CONFIG;
16205
- if (override && override.length > 0) return import_node_path57.default.resolve(override);
16566
+ if (override && override.length > 0) return import_node_path60.default.resolve(override);
16206
16567
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
16207
- return import_node_path57.default.join(home, ".claude.json");
16568
+ return import_node_path60.default.join(home, ".claude.json");
16208
16569
  }
16209
16570
  async function runSkill(opts) {
16210
16571
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -16216,7 +16577,7 @@ async function runSkill(opts) {
16216
16577
  const target = claudeConfigPath();
16217
16578
  let existing = {};
16218
16579
  try {
16219
- existing = JSON.parse(await import_node_fs37.promises.readFile(target, "utf8"));
16580
+ existing = JSON.parse(await import_node_fs40.promises.readFile(target, "utf8"));
16220
16581
  } catch (err) {
16221
16582
  if (err.code !== "ENOENT") {
16222
16583
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -16228,8 +16589,8 @@ async function runSkill(opts) {
16228
16589
  ...existing,
16229
16590
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
16230
16591
  };
16231
- await import_node_fs37.promises.mkdir(import_node_path57.default.dirname(target), { recursive: true });
16232
- await import_node_fs37.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
16592
+ await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(target), { recursive: true });
16593
+ await import_node_fs40.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
16233
16594
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
16234
16595
  console.log("restart Claude Code to pick up the new MCP server.");
16235
16596
  return { exitCode: 0 };
@@ -16283,12 +16644,12 @@ async function main() {
16283
16644
  console.error("neat init: --apply and --dry-run are mutually exclusive");
16284
16645
  process.exit(2);
16285
16646
  }
16286
- const scanPath = import_node_path57.default.resolve(target);
16647
+ const scanPath = import_node_path60.default.resolve(target);
16287
16648
  const projectExplicit = parsed.project !== null;
16288
- const projectName = projectExplicit ? project : import_node_path57.default.basename(scanPath);
16649
+ const projectName = projectExplicit ? project : import_node_path60.default.basename(scanPath);
16289
16650
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
16290
- const fallback = pathsForProject(projectKey, import_node_path57.default.join(scanPath, "neat-out")).snapshotPath;
16291
- const outPath = import_node_path57.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
16651
+ const fallback = pathsForProject(projectKey, import_node_path60.default.join(scanPath, "neat-out")).snapshotPath;
16652
+ const outPath = import_node_path60.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
16292
16653
  const result = await runInit({
16293
16654
  scanPath,
16294
16655
  outPath,
@@ -16309,21 +16670,21 @@ async function main() {
16309
16670
  usage();
16310
16671
  process.exit(2);
16311
16672
  }
16312
- const scanPath = import_node_path57.default.resolve(target);
16313
- const stat = await import_node_fs37.promises.stat(scanPath).catch(() => null);
16673
+ const scanPath = import_node_path60.default.resolve(target);
16674
+ const stat = await import_node_fs40.promises.stat(scanPath).catch(() => null);
16314
16675
  if (!stat || !stat.isDirectory()) {
16315
16676
  console.error(`neat watch: ${scanPath} is not a directory`);
16316
16677
  process.exit(2);
16317
16678
  }
16318
- const projectPaths = pathsForProject(project, import_node_path57.default.join(scanPath, "neat-out"));
16319
- const outPath = import_node_path57.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
16320
- const errorsPath = import_node_path57.default.resolve(
16321
- process.env.NEAT_ERRORS_PATH ?? import_node_path57.default.join(import_node_path57.default.dirname(outPath), import_node_path57.default.basename(projectPaths.errorsPath))
16679
+ const projectPaths = pathsForProject(project, import_node_path60.default.join(scanPath, "neat-out"));
16680
+ const outPath = import_node_path60.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
16681
+ const errorsPath = import_node_path60.default.resolve(
16682
+ process.env.NEAT_ERRORS_PATH ?? import_node_path60.default.join(import_node_path60.default.dirname(outPath), import_node_path60.default.basename(projectPaths.errorsPath))
16322
16683
  );
16323
- const staleEventsPath = import_node_path57.default.resolve(
16324
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path57.default.join(import_node_path57.default.dirname(outPath), import_node_path57.default.basename(projectPaths.staleEventsPath))
16684
+ const staleEventsPath = import_node_path60.default.resolve(
16685
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path60.default.join(import_node_path60.default.dirname(outPath), import_node_path60.default.basename(projectPaths.staleEventsPath))
16325
16686
  );
16326
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path57.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
16687
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path60.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
16327
16688
  const handle = await startWatch(getGraph(project), {
16328
16689
  scanPath,
16329
16690
  outPath,
@@ -16506,11 +16867,11 @@ async function main() {
16506
16867
  process.exit(1);
16507
16868
  }
16508
16869
  async function tryOrchestrator(cmd, parsed) {
16509
- const scanPath = import_node_path57.default.resolve(cmd);
16510
- const stat = await import_node_fs37.promises.stat(scanPath).catch(() => null);
16870
+ const scanPath = import_node_path60.default.resolve(cmd);
16871
+ const stat = await import_node_fs40.promises.stat(scanPath).catch(() => null);
16511
16872
  if (!stat || !stat.isDirectory()) return null;
16512
16873
  const projectExplicit = parsed.project !== null;
16513
- const projectName = projectExplicit ? parsed.project : import_node_path57.default.basename(scanPath);
16874
+ const projectName = projectExplicit ? parsed.project : import_node_path60.default.basename(scanPath);
16514
16875
  const result = await runOrchestrator({
16515
16876
  scanPath,
16516
16877
  project: projectName,
@@ -16699,10 +17060,10 @@ async function runQueryVerb(cmd, parsed) {
16699
17060
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
16700
17061
  const out = [];
16701
17062
  for (const p of parts) {
16702
- const r = import_types45.DivergenceTypeSchema.safeParse(p);
17063
+ const r = import_types48.DivergenceTypeSchema.safeParse(p);
16703
17064
  if (!r.success) {
16704
17065
  console.error(
16705
- `neat divergences: unknown --type "${p}". allowed: ${import_types45.DivergenceTypeSchema.options.join(", ")}`
17066
+ `neat divergences: unknown --type "${p}". allowed: ${import_types48.DivergenceTypeSchema.options.join(", ")}`
16706
17067
  );
16707
17068
  return 2;
16708
17069
  }