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

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",
@@ -9261,9 +9828,11 @@ async function buildApi(opts) {
9261
9828
  trustProxy,
9262
9829
  publicRead
9263
9830
  });
9831
+ const requiresAuth = authToken !== void 0 && authToken.length > 0 && trustProxy !== true;
9264
9832
  app.get("/api/config", async () => ({
9265
9833
  publicRead: publicRead === true,
9266
- authProxy: trustProxy === true
9834
+ authProxy: trustProxy === true,
9835
+ requiresAuth
9267
9836
  }));
9268
9837
  const startedAt = opts.startedAt ?? Date.now();
9269
9838
  const registry = buildLegacyRegistry(opts);
@@ -9293,7 +9862,8 @@ async function buildApi(opts) {
9293
9862
  staleEventsPathFor,
9294
9863
  policyFilePathFor,
9295
9864
  bootstrap: opts.bootstrap,
9296
- singleProject: opts.singleProject?.name
9865
+ singleProject: opts.singleProject?.name,
9866
+ connectorsHome: opts.connectorsHome
9297
9867
  };
9298
9868
  app.get("/health", async () => {
9299
9869
  const uptimeMs = Date.now() - startedAt;
@@ -9378,8 +9948,8 @@ init_otel();
9378
9948
 
9379
9949
  // src/daemon.ts
9380
9950
  init_cjs_shims();
9381
- var import_node_fs30 = require("fs");
9382
- var import_node_path49 = __toESM(require("path"), 1);
9951
+ var import_node_fs33 = require("fs");
9952
+ var import_node_path52 = __toESM(require("path"), 1);
9383
9953
  var import_node_module = require("module");
9384
9954
  init_otel();
9385
9955
 
@@ -9673,6 +10243,30 @@ function buildEdgeLogsQuery(limit) {
9673
10243
  `limit ${safeLimit}`
9674
10244
  ].join("\n");
9675
10245
  }
10246
+ function logsAllHttpFailureMessage(status2) {
10247
+ if (status2 === 401 || status2 === 403) {
10248
+ return `supabase connector: logs.all request rejected (HTTP ${status2}). Check the Management API token, its analytics read scope, and --api-project-ref.`;
10249
+ }
10250
+ if (status2 === 404) {
10251
+ return "supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.";
10252
+ }
10253
+ if (status2 === 429) {
10254
+ return "supabase connector: logs.all rate-limited (HTTP 429). The connector will retry on the next poll; reduce poll cadence if this persists.";
10255
+ }
10256
+ if (status2 === 400) {
10257
+ return "supabase connector: logs.all query rejected (HTTP 400). Provider details redacted; confirm the live log-query dialect before shipping this connector.";
10258
+ }
10259
+ return `supabase connector: logs.all request failed (HTTP ${status2}); provider details redacted.`;
10260
+ }
10261
+ function providerErrorDetails(error) {
10262
+ if (!error || typeof error === "string") return "";
10263
+ const parts = [];
10264
+ if (typeof error.code === "number") parts.push(`code ${error.code}`);
10265
+ if (typeof error.status === "string" && /^[A-Z0-9_.-]+$/.test(error.status)) {
10266
+ parts.push(`status ${error.status}`);
10267
+ }
10268
+ return parts.length > 0 ? ` (${parts.join(", ")})` : "";
10269
+ }
9676
10270
  async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
9677
10271
  const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
9678
10272
  const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
@@ -9687,12 +10281,18 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
9687
10281
  { provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
9688
10282
  );
9689
10283
  if (!res.ok) {
9690
- throw new Error(`supabase connector: logs.all request failed (${res.status} ${res.statusText})`);
10284
+ throw new Error(logsAllHttpFailureMessage(res.status));
10285
+ }
10286
+ let body;
10287
+ try {
10288
+ body = await res.json();
10289
+ } catch {
10290
+ throw new Error("supabase connector: logs.all returned invalid JSON; provider details redacted.");
9691
10291
  }
9692
- const body = await res.json();
9693
10292
  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})`);
10293
+ throw new Error(
10294
+ `supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`
10295
+ );
9696
10296
  }
9697
10297
  return body.result ?? [];
9698
10298
  }
@@ -9724,10 +10324,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
9724
10324
  // src/connectors/supabase/map.ts
9725
10325
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
9726
10326
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
9727
- function targetFromRestPath(path58) {
9728
- const rpcMatch = REST_RPC_PATH_RE.exec(path58);
10327
+ function targetFromRestPath(path61) {
10328
+ const rpcMatch = REST_RPC_PATH_RE.exec(path61);
9729
10329
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
9730
- const tableMatch = REST_TABLE_PATH_RE.exec(path58);
10330
+ const tableMatch = REST_TABLE_PATH_RE.exec(path61);
9731
10331
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
9732
10332
  return null;
9733
10333
  }
@@ -9830,19 +10430,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
9830
10430
 
9831
10431
  // src/connectors/supabase/resolve.ts
9832
10432
  init_cjs_shims();
9833
- var import_types33 = require("@neat.is/types");
10433
+ var import_types36 = require("@neat.is/types");
9834
10434
  function createSupabaseResolveTarget(graph, config) {
9835
10435
  return (signal, _ctx) => {
9836
10436
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
9837
10437
  return null;
9838
10438
  }
9839
- const subResourceId = (0, import_types33.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10439
+ const subResourceId = (0, import_types36.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
9840
10440
  if (graph.hasNode(subResourceId)) {
9841
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types33.EdgeType.CALLS };
10441
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
9842
10442
  }
9843
- const projectLevelId = (0, import_types33.infraId)("supabase", config.nodeRef);
10443
+ const projectLevelId = (0, import_types36.infraId)("supabase", config.nodeRef);
9844
10444
  if (graph.hasNode(projectLevelId)) {
9845
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types33.EdgeType.CALLS };
10445
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types36.EdgeType.CALLS };
9846
10446
  }
9847
10447
  return null;
9848
10448
  };
@@ -9850,6 +10450,35 @@ function createSupabaseResolveTarget(graph, config) {
9850
10450
 
9851
10451
  // src/connectors/supabase/index.ts
9852
10452
  var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
10453
+ function errorCode(err) {
10454
+ const code = err?.code;
10455
+ return typeof code === "string" && code.length > 0 ? code : void 0;
10456
+ }
10457
+ function describeSupabasePostgresSurfaceFailure(projectRef, err) {
10458
+ const code = errorCode(err);
10459
+ let reason;
10460
+ switch (code) {
10461
+ case "42501":
10462
+ reason = "permission denied; grant pg_read_all_stats to the configured Postgres role";
10463
+ break;
10464
+ case "42P01":
10465
+ case "42704":
10466
+ reason = "pg_stat_statements is not enabled or visible to the configured Postgres role";
10467
+ break;
10468
+ case "28P01":
10469
+ case "28000":
10470
+ reason = "Postgres credential rejected";
10471
+ break;
10472
+ case "3D000":
10473
+ reason = "database not found";
10474
+ break;
10475
+ default: {
10476
+ const name = err instanceof Error && err.name ? err.name : "Error";
10477
+ reason = code ? `${name} ${code}` : name;
10478
+ }
10479
+ }
10480
+ return `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} (${reason}); continuing with Management API log surface.`;
10481
+ }
9853
10482
  var SupabaseConnector = class {
9854
10483
  // `deps.fetchPgStatStatements` defaults to the real Postgres-backed
9855
10484
  // implementation; tests override it to exercise the "both surfaces
@@ -9881,12 +10510,18 @@ var SupabaseConnector = class {
9881
10510
  const signals = mapEdgeLogRowsToSignals(logRows);
9882
10511
  if (creds.postgresConnectionString) {
9883
10512
  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()));
10513
+ try {
10514
+ const statementRows = await fetchStatements(
10515
+ creds.postgresConnectionString,
10516
+ this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
10517
+ this.config.apiProjectRef
10518
+ );
10519
+ signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
10520
+ } catch (err) {
10521
+ const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err);
10522
+ if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary);
10523
+ else console.warn(summary);
10524
+ }
9890
10525
  }
9891
10526
  return signals;
9892
10527
  }
@@ -9900,7 +10535,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
9900
10535
 
9901
10536
  // src/connectors/railway/index.ts
9902
10537
  init_cjs_shims();
9903
- var import_types37 = require("@neat.is/types");
10538
+ var import_types40 = require("@neat.is/types");
9904
10539
 
9905
10540
  // src/connectors/railway/client.ts
9906
10541
  init_cjs_shims();
@@ -10045,7 +10680,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
10045
10680
  const out = [];
10046
10681
  graph.forEachNode((_id, attrs) => {
10047
10682
  const node = attrs;
10048
- if (node.type !== import_types37.NodeType.RouteNode) return;
10683
+ if (node.type !== import_types40.NodeType.RouteNode) return;
10049
10684
  const route = attrs;
10050
10685
  if (route.service !== serviceName) return;
10051
10686
  out.push({
@@ -10141,12 +10776,12 @@ function createRailwayResolveTarget(config) {
10141
10776
  const serviceName = config.serviceNameById[config.serviceId];
10142
10777
  if (!serviceName) return null;
10143
10778
  if (signal.targetKind === ROUTE_TARGET_KIND) {
10144
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types37.EdgeType.CALLS };
10779
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types40.EdgeType.CALLS };
10145
10780
  }
10146
10781
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
10147
10782
  const peerName = config.serviceNameById[signal.targetName];
10148
10783
  if (!peerName) return null;
10149
- return { targetNodeId: (0, import_types37.serviceId)(peerName), serviceName, edgeType: import_types37.EdgeType.CONNECTS_TO };
10784
+ return { targetNodeId: (0, import_types40.serviceId)(peerName), serviceName, edgeType: import_types40.EdgeType.CONNECTS_TO };
10150
10785
  }
10151
10786
  return null;
10152
10787
  };
@@ -10261,9 +10896,9 @@ function parseFirebaseTargetName(targetName) {
10261
10896
  const secondSep = rest.indexOf(FIELD_SEP);
10262
10897
  if (secondSep === -1) return null;
10263
10898
  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 };
10899
+ const path61 = rest.slice(secondSep + 1);
10900
+ if (!resourceName || !method || !path61) return null;
10901
+ return { resourceName, method, path: path61 };
10267
10902
  }
10268
10903
  function resourceNameFor(type, labels) {
10269
10904
  if (!labels) return null;
@@ -10300,14 +10935,14 @@ function mapLogEntryToSignal(entry2) {
10300
10935
  if (!req) return null;
10301
10936
  if (!req.requestMethod) return null;
10302
10937
  const method = req.requestMethod.toUpperCase();
10303
- const path58 = pathFromRequestUrl(req.requestUrl);
10304
- if (path58 === null) return null;
10938
+ const path61 = pathFromRequestUrl(req.requestUrl);
10939
+ if (path61 === null) return null;
10305
10940
  const timestamp = entry2.timestamp;
10306
10941
  if (!timestamp) return null;
10307
10942
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
10308
10943
  return {
10309
10944
  targetKind: resourceType,
10310
- targetName: packFirebaseTargetName({ resourceName, method, path: path58 }),
10945
+ targetName: packFirebaseTargetName({ resourceName, method, path: path61 }),
10311
10946
  callCount: 1,
10312
10947
  errorCount: isError ? 1 : 0,
10313
10948
  lastObservedIso: timestamp
@@ -10324,7 +10959,7 @@ function mapLogEntriesToSignals(entries) {
10324
10959
 
10325
10960
  // src/connectors/firebase/resolve.ts
10326
10961
  init_cjs_shims();
10327
- var import_types38 = require("@neat.is/types");
10962
+ var import_types41 = require("@neat.is/types");
10328
10963
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
10329
10964
  switch (resourceType) {
10330
10965
  case "cloud_function":
@@ -10339,7 +10974,7 @@ function routeEntriesFor(graph, serviceName) {
10339
10974
  const entries = [];
10340
10975
  graph.forEachNode((_id, attrs) => {
10341
10976
  const node = attrs;
10342
- if (node.type !== import_types38.NodeType.RouteNode) return;
10977
+ if (node.type !== import_types41.NodeType.RouteNode) return;
10343
10978
  const route = attrs;
10344
10979
  if (route.service !== serviceName) return;
10345
10980
  entries.push({
@@ -10371,7 +11006,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
10371
11006
  return {
10372
11007
  targetNodeId: match.routeNodeId,
10373
11008
  serviceName,
10374
- edgeType: import_types38.EdgeType.CALLS
11009
+ edgeType: import_types41.EdgeType.CALLS
10375
11010
  };
10376
11011
  };
10377
11012
  }
@@ -10398,7 +11033,7 @@ init_cjs_shims();
10398
11033
 
10399
11034
  // src/connectors/cloudflare/connector.ts
10400
11035
  init_cjs_shims();
10401
- var import_types40 = require("@neat.is/types");
11036
+ var import_types43 = require("@neat.is/types");
10402
11037
 
10403
11038
  // src/connectors/cloudflare/client.ts
10404
11039
  init_cjs_shims();
@@ -10489,396 +11124,124 @@ function parseHttpMethodFromTrigger(trigger) {
10489
11124
  function parsePathFromTrigger(trigger) {
10490
11125
  const trimmed = trigger.trim();
10491
11126
  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
- }
11127
+ if (spaceIdx === -1) return void 0;
11128
+ const rest = trimmed.slice(spaceIdx + 1).trim();
11129
+ return rest.length > 0 ? rest : void 0;
11130
+ }
11131
+ var ERROR_STATUS_THRESHOLD3 = 500;
11132
+ function mapEventToSignal(event) {
11133
+ const metadata = event.$metadata;
11134
+ const workers = event.$workers;
11135
+ const method = parseHttpMethodFromTrigger(metadata?.trigger);
11136
+ if (!method) return null;
11137
+ const scriptName = workers?.scriptName ?? metadata?.service;
11138
+ if (!scriptName) return null;
11139
+ const timestampMs = event.timestamp ?? metadata?.startTime;
11140
+ if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
11141
+ const statusCode = metadata?.statusCode;
11142
+ const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
11143
+ const path61 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10711
11144
  return {
10712
- id,
10713
- provider,
10714
- ...typeof e.project === "string" ? { project: e.project } : {},
10715
- credential,
10716
- ...options ? { options } : {}
11145
+ targetKind: CLOUDFLARE_TARGET_KIND,
11146
+ targetName: scriptName,
11147
+ callCount: 1,
11148
+ errorCount: isError ? 1 : 0,
11149
+ lastObservedIso: new Date(timestampMs).toISOString(),
11150
+ method,
11151
+ ...path61 ? { path: path61 } : {},
11152
+ ...typeof statusCode === "number" ? { statusCode } : {},
11153
+ ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
10717
11154
  };
10718
11155
  }
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`);
11156
+
11157
+ // src/connectors/cloudflare/connector.ts
11158
+ var DEFAULT_MAX_LOOKBACK_MS3 = 60 * 60 * 1e3;
11159
+ function resolveFromMs(since, maxLookbackMs) {
11160
+ const cap = maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS3;
11161
+ const now = Date.now();
11162
+ const floor = now - cap;
11163
+ if (!since) return floor;
11164
+ const parsed = Date.parse(since);
11165
+ if (Number.isNaN(parsed)) return floor;
11166
+ return Math.max(parsed, floor);
10737
11167
  }
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);
11168
+ var CloudflareConnector = class {
11169
+ constructor(config) {
11170
+ this.config = config;
10745
11171
  }
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);
11172
+ config;
11173
+ provider = "cloudflare";
11174
+ async poll(ctx) {
11175
+ const toMs = Date.now();
11176
+ const fromMs = resolveFromMs(ctx.since, this.config.maxLookbackMs);
11177
+ const events = await queryWorkerInvocations(ctx, this.config, { fromMs, toMs });
11178
+ const signals = [];
11179
+ for (const event of events) {
11180
+ const signal = mapEventToSignal(event);
11181
+ if (signal) signals.push(signal);
10754
11182
  }
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();
11183
+ return signals;
10777
11184
  }
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));
11185
+ };
11186
+ function findTaggedWorkerFileNode(graph, workerName) {
11187
+ let found = null;
11188
+ graph.forEachNode((id, attrs) => {
11189
+ if (found) return;
11190
+ const a = attrs;
11191
+ if (a.type === import_types43.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
11192
+ found = id;
10801
11193
  }
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
11194
  });
11195
+ return found;
10836
11196
  }
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;
11197
+ function findMatchingRouteNode(graph, serviceName, method, path61) {
11198
+ const normalizedPath = normalizePathTemplate(path61);
11199
+ let found = null;
11200
+ graph.forEachNode((id, attrs) => {
11201
+ if (found) return;
11202
+ const a = attrs;
11203
+ if (a.type !== import_types43.NodeType.RouteNode || a.service !== serviceName) return;
11204
+ if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
11205
+ const routeMethod = (a.method ?? "").toUpperCase();
11206
+ if (routeMethod !== "ALL" && routeMethod !== method) return;
11207
+ found = id;
10846
11208
  });
11209
+ return found;
10847
11210
  }
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;
11211
+ function createCloudflareResolveTarget(config, graph) {
11212
+ return (signal) => {
11213
+ if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
11214
+ const scriptName = signal.targetName;
11215
+ const { method, path: path61 } = signal;
11216
+ const resolveRouteGrain = (serviceName, wholeFileId) => {
11217
+ if (!method || !path61) return wholeFileId;
11218
+ return findMatchingRouteNode(graph, serviceName, method, path61) ?? wholeFileId;
11219
+ };
11220
+ const mapping = config.workers?.[scriptName];
11221
+ if (mapping) {
11222
+ const wholeFileId = (0, import_types43.fileId)(mapping.service, mapping.entryFile);
10871
11223
  return {
10872
- ...field ? { field } : {},
10873
- kind: "env-ref",
10874
- display: value,
10875
- status: set ? "set" : "unset"
11224
+ targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
11225
+ serviceName: mapping.service,
11226
+ edgeType: import_types43.EdgeType.CALLS
10876
11227
  };
10877
11228
  }
10878
- return { ...field ? { field } : {}, kind: "plaintext", display: "****" };
11229
+ const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
11230
+ if (taggedFileId) {
11231
+ const fileNode = graph.getNodeAttributes(taggedFileId);
11232
+ return {
11233
+ targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
11234
+ serviceName: fileNode.service,
11235
+ edgeType: import_types43.EdgeType.CALLS
11236
+ };
11237
+ }
11238
+ return {
11239
+ targetNodeId: (0, import_types43.infraId)("cloudflare-worker", scriptName),
11240
+ serviceName: scriptName,
11241
+ edgeType: import_types43.EdgeType.CALLS,
11242
+ ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
11243
+ };
10879
11244
  };
10880
- if (typeof ref === "string") return [one(ref)];
10881
- return Object.entries(ref).map(([k, v]) => one(v, k));
10882
11245
  }
10883
11246
 
10884
11247
  // src/connectors/registry.ts
@@ -11074,33 +11437,33 @@ init_auth();
11074
11437
 
11075
11438
  // src/unrouted.ts
11076
11439
  init_cjs_shims();
11077
- var import_node_fs29 = require("fs");
11078
- var import_node_path48 = __toESM(require("path"), 1);
11440
+ var import_node_fs32 = require("fs");
11441
+ var import_node_path51 = __toESM(require("path"), 1);
11079
11442
 
11080
11443
  // src/daemon.ts
11081
- var import_types43 = require("@neat.is/types");
11444
+ var import_types46 = require("@neat.is/types");
11082
11445
  function daemonJsonPath(scanPath) {
11083
- return import_node_path49.default.join(scanPath, "neat-out", "daemon.json");
11446
+ return import_node_path52.default.join(scanPath, "neat-out", "daemon.json");
11084
11447
  }
11085
11448
  function daemonsDiscoveryDir(home) {
11086
11449
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
11087
- return import_node_path49.default.join(base, "daemons");
11450
+ return import_node_path52.default.join(base, "daemons");
11088
11451
  }
11089
11452
  function daemonDiscoveryPath(project, home) {
11090
- return import_node_path49.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11453
+ return import_node_path52.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11091
11454
  }
11092
11455
  function sanitizeDiscoveryName(project) {
11093
11456
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
11094
11457
  }
11095
11458
  function neatHomeFromEnv() {
11096
11459
  const env = process.env.NEAT_HOME;
11097
- if (env && env.length > 0) return import_node_path49.default.resolve(env);
11460
+ if (env && env.length > 0) return import_node_path52.default.resolve(env);
11098
11461
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11099
- return import_node_path49.default.join(home, ".neat");
11462
+ return import_node_path52.default.join(home, ".neat");
11100
11463
  }
11101
11464
  async function readDaemonRecord(scanPath) {
11102
11465
  try {
11103
- const raw = await import_node_fs30.promises.readFile(daemonJsonPath(scanPath), "utf8");
11466
+ const raw = await import_node_fs33.promises.readFile(daemonJsonPath(scanPath), "utf8");
11104
11467
  const parsed = JSON.parse(raw);
11105
11468
  if (typeof parsed.project === "string" && parsed.ports && typeof parsed.ports.rest === "number" && typeof parsed.ports.otlp === "number" && typeof parsed.ports.web === "number") {
11106
11469
  return parsed;
@@ -11140,7 +11503,7 @@ async function clearDaemonRecord(record, home) {
11140
11503
  } catch {
11141
11504
  }
11142
11505
  try {
11143
- await import_node_fs30.promises.unlink(daemonDiscoveryPath(record.project, home));
11506
+ await import_node_fs33.promises.unlink(daemonDiscoveryPath(record.project, home));
11144
11507
  } catch {
11145
11508
  }
11146
11509
  }
@@ -11166,8 +11529,8 @@ init_otel_grpc();
11166
11529
 
11167
11530
  // src/search.ts
11168
11531
  init_cjs_shims();
11169
- var import_node_fs31 = require("fs");
11170
- var import_node_path50 = __toESM(require("path"), 1);
11532
+ var import_node_fs34 = require("fs");
11533
+ var import_node_path53 = __toESM(require("path"), 1);
11171
11534
  var import_node_crypto4 = require("crypto");
11172
11535
  var DEFAULT_LIMIT = 10;
11173
11536
  var NOMIC_DIM = 768;
@@ -11321,7 +11684,7 @@ async function pickEmbedder() {
11321
11684
  }
11322
11685
  async function readCache(cachePath) {
11323
11686
  try {
11324
- const raw = await import_node_fs31.promises.readFile(cachePath, "utf8");
11687
+ const raw = await import_node_fs34.promises.readFile(cachePath, "utf8");
11325
11688
  const parsed = JSON.parse(raw);
11326
11689
  if (parsed.version !== 1) return null;
11327
11690
  return parsed;
@@ -11330,8 +11693,8 @@ async function readCache(cachePath) {
11330
11693
  }
11331
11694
  }
11332
11695
  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));
11696
+ await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(cachePath), { recursive: true });
11697
+ await import_node_fs34.promises.writeFile(cachePath, JSON.stringify(cache));
11335
11698
  }
11336
11699
  var VectorIndex = class {
11337
11700
  constructor(embedder, cachePath) {
@@ -11488,8 +11851,8 @@ var ALL_PHASES = [
11488
11851
  ];
11489
11852
  function classifyChange(relPath) {
11490
11853
  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());
11854
+ const base = import_node_path54.default.basename(relPath).toLowerCase();
11855
+ const segments = relPath.split(import_node_path54.default.sep).map((s) => s.toLowerCase());
11493
11856
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
11494
11857
  phases.add("services");
11495
11858
  phases.add("aliases");
@@ -11610,16 +11973,16 @@ function countWatchableDirs(scanPath, limit) {
11610
11973
  if (count >= limit) return;
11611
11974
  let entries;
11612
11975
  try {
11613
- entries = import_node_fs32.default.readdirSync(dir, { withFileTypes: true });
11976
+ entries = import_node_fs35.default.readdirSync(dir, { withFileTypes: true });
11614
11977
  } catch {
11615
11978
  return;
11616
11979
  }
11617
11980
  for (const e of entries) {
11618
11981
  if (count >= limit) return;
11619
11982
  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;
11983
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path54.default.join(dir, e.name) + import_node_path54.default.sep))) continue;
11621
11984
  count++;
11622
- if (depth < 2) visit(import_node_path51.default.join(dir, e.name), depth + 1);
11985
+ if (depth < 2) visit(import_node_path54.default.join(dir, e.name), depth + 1);
11623
11986
  }
11624
11987
  };
11625
11988
  visit(scanPath, 0);
@@ -11637,8 +12000,8 @@ async function startWatch(graph, opts) {
11637
12000
  const projectName = opts.project ?? DEFAULT_PROJECT;
11638
12001
  await loadGraphFromDisk(graph, opts.outPath);
11639
12002
  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");
12003
+ const policyFilePath = import_node_path54.default.join(opts.scanPath, "policy.json");
12004
+ const policyViolationsPath = import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "policy-violations.ndjson");
11642
12005
  let policies = [];
11643
12006
  try {
11644
12007
  policies = await loadPolicyFile(policyFilePath);
@@ -11689,7 +12052,7 @@ async function startWatch(graph, opts) {
11689
12052
  assertBindAuthority(host, auth.authToken);
11690
12053
  const port = opts.port ?? 8080;
11691
12054
  const otelPort = opts.otelPort ?? 4318;
11692
- const cachePath = opts.embeddingsCachePath ?? import_node_path51.default.join(import_node_path51.default.dirname(opts.outPath), "embeddings.json");
12055
+ const cachePath = opts.embeddingsCachePath ?? import_node_path54.default.join(import_node_path54.default.dirname(opts.outPath), "embeddings.json");
11693
12056
  let searchIndex;
11694
12057
  try {
11695
12058
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -11707,7 +12070,7 @@ async function startWatch(graph, opts) {
11707
12070
  // Paths are derived from the explicit options the watch caller passes
11708
12071
  // — pathsForProject is only used to fill in the embeddings/snapshot
11709
12072
  // fields so the registry shape is complete.
11710
- ...pathsForProject(projectName, import_node_path51.default.dirname(opts.outPath)),
12073
+ ...pathsForProject(projectName, import_node_path54.default.dirname(opts.outPath)),
11711
12074
  snapshotPath: opts.outPath,
11712
12075
  errorsPath: opts.errorsPath,
11713
12076
  staleEventsPath: opts.staleEventsPath
@@ -11825,9 +12188,9 @@ async function startWatch(graph, opts) {
11825
12188
  };
11826
12189
  const onPath = (absPath) => {
11827
12190
  if (shouldIgnore(absPath)) return;
11828
- const rel = import_node_path51.default.relative(opts.scanPath, absPath);
12191
+ const rel = import_node_path54.default.relative(opts.scanPath, absPath);
11829
12192
  if (!rel || rel.startsWith("..")) return;
11830
- pendingPaths.add(rel.split(import_node_path51.default.sep).join("/"));
12193
+ pendingPaths.add(rel.split(import_node_path54.default.sep).join("/"));
11831
12194
  const phases = classifyChange(rel);
11832
12195
  if (phases.size === 0) {
11833
12196
  for (const p of ALL_PHASES) pending.add(p);
@@ -11883,8 +12246,8 @@ async function startWatch(graph, opts) {
11883
12246
 
11884
12247
  // src/deploy/detect.ts
11885
12248
  init_cjs_shims();
11886
- var import_node_fs33 = require("fs");
11887
- var import_node_path52 = __toESM(require("path"), 1);
12249
+ var import_node_fs36 = require("fs");
12250
+ var import_node_path55 = __toESM(require("path"), 1);
11888
12251
  var import_node_child_process2 = require("child_process");
11889
12252
  var import_node_crypto5 = require("crypto");
11890
12253
  function generateToken() {
@@ -11984,21 +12347,21 @@ async function runDeploy(opts = {}) {
11984
12347
  const token = generateToken();
11985
12348
  switch (substrate) {
11986
12349
  case "docker-compose": {
11987
- const artifactPath = import_node_path52.default.join(cwd, "docker-compose.neat.yml");
12350
+ const artifactPath = import_node_path55.default.join(cwd, "docker-compose.neat.yml");
11988
12351
  const contents = emitDockerCompose(cwd);
11989
- await import_node_fs33.promises.writeFile(artifactPath, contents, "utf8");
12352
+ await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
11990
12353
  return {
11991
12354
  substrate,
11992
12355
  artifactPath,
11993
12356
  token,
11994
12357
  contents,
11995
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path52.default.basename(artifactPath)} up -d`
12358
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path55.default.basename(artifactPath)} up -d`
11996
12359
  };
11997
12360
  }
11998
12361
  case "systemd": {
11999
- const artifactPath = import_node_path52.default.join(cwd, "neat.service");
12362
+ const artifactPath = import_node_path55.default.join(cwd, "neat.service");
12000
12363
  const contents = emitSystemdUnit(cwd);
12001
- await import_node_fs33.promises.writeFile(artifactPath, contents, "utf8");
12364
+ await import_node_fs36.promises.writeFile(artifactPath, contents, "utf8");
12002
12365
  return {
12003
12366
  substrate,
12004
12367
  artifactPath,
@@ -12031,8 +12394,8 @@ init_cjs_shims();
12031
12394
 
12032
12395
  // src/installers/javascript.ts
12033
12396
  init_cjs_shims();
12034
- var import_node_fs34 = require("fs");
12035
- var import_node_path53 = __toESM(require("path"), 1);
12397
+ var import_node_fs37 = require("fs");
12398
+ var import_node_path56 = __toESM(require("path"), 1);
12036
12399
  var import_semver2 = __toESM(require("semver"), 1);
12037
12400
 
12038
12401
  // src/installers/templates.ts
@@ -12647,15 +13010,15 @@ var OTEL_ENV = {
12647
13010
  value: "http://localhost:4318/projects/<project>/v1/traces"
12648
13011
  };
12649
13012
  function serviceNodeName(pkg, serviceDir) {
12650
- return pkg.name ?? import_node_path53.default.basename(serviceDir);
13013
+ return pkg.name ?? import_node_path56.default.basename(serviceDir);
12651
13014
  }
12652
13015
  function projectToken(pkg, serviceDir, project) {
12653
13016
  if (project && project.length > 0) return project;
12654
- return pkg.name ?? import_node_path53.default.basename(serviceDir);
13017
+ return pkg.name ?? import_node_path56.default.basename(serviceDir);
12655
13018
  }
12656
13019
  async function readJsonFile(p) {
12657
13020
  try {
12658
- const raw = await import_node_fs34.promises.readFile(p, "utf8");
13021
+ const raw = await import_node_fs37.promises.readFile(p, "utf8");
12659
13022
  return JSON.parse(raw);
12660
13023
  } catch {
12661
13024
  return null;
@@ -12664,16 +13027,16 @@ async function readJsonFile(p) {
12664
13027
  async function detectRuntimeKind(pkgRoot, pkg) {
12665
13028
  const deps = allDeps(pkg);
12666
13029
  if ("react-native" in deps || "expo" in deps) return "react-native";
12667
- const appJson = await readJsonFile(import_node_path53.default.join(pkgRoot, "app.json"));
13030
+ const appJson = await readJsonFile(import_node_path56.default.join(pkgRoot, "app.json"));
12668
13031
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
12669
13032
  return "react-native";
12670
13033
  }
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) {
13034
+ 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
13035
  return "browser-bundle";
12673
13036
  }
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"))) {
13037
+ if (await exists3(import_node_path56.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
13038
+ if (await exists3(import_node_path56.default.join(pkgRoot, "bun.lockb"))) return "bun";
13039
+ if (await exists3(import_node_path56.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path56.default.join(pkgRoot, "deno.lock"))) {
12677
13040
  return "deno";
12678
13041
  }
12679
13042
  const engines = pkg.engines ?? {};
@@ -12682,7 +13045,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
12682
13045
  }
12683
13046
  async function readPackageJson2(serviceDir) {
12684
13047
  try {
12685
- const raw = await import_node_fs34.promises.readFile(import_node_path53.default.join(serviceDir, "package.json"), "utf8");
13048
+ const raw = await import_node_fs37.promises.readFile(import_node_path56.default.join(serviceDir, "package.json"), "utf8");
12686
13049
  return JSON.parse(raw);
12687
13050
  } catch {
12688
13051
  return null;
@@ -12690,7 +13053,7 @@ async function readPackageJson2(serviceDir) {
12690
13053
  }
12691
13054
  async function exists3(p) {
12692
13055
  try {
12693
- await import_node_fs34.promises.stat(p);
13056
+ await import_node_fs37.promises.stat(p);
12694
13057
  return true;
12695
13058
  } catch {
12696
13059
  return false;
@@ -12698,7 +13061,7 @@ async function exists3(p) {
12698
13061
  }
12699
13062
  async function readFileMaybe(p) {
12700
13063
  try {
12701
- return await import_node_fs34.promises.readFile(p, "utf8");
13064
+ return await import_node_fs37.promises.readFile(p, "utf8");
12702
13065
  } catch {
12703
13066
  return null;
12704
13067
  }
@@ -12726,7 +13089,7 @@ function needsVersionUpgrade(installed, expected) {
12726
13089
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
12727
13090
  async function findNextConfig(serviceDir) {
12728
13091
  for (const name of NEXT_CONFIG_CANDIDATES) {
12729
- const candidate = import_node_path53.default.join(serviceDir, name);
13092
+ const candidate = import_node_path56.default.join(serviceDir, name);
12730
13093
  if (await exists3(candidate)) return candidate;
12731
13094
  }
12732
13095
  return null;
@@ -12795,7 +13158,7 @@ function hasRemixDependency(pkg) {
12795
13158
  }
12796
13159
  async function findRemixEntry(serviceDir) {
12797
13160
  for (const rel of REMIX_ENTRY_CANDIDATES) {
12798
- const candidate = import_node_path53.default.join(serviceDir, rel);
13161
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12799
13162
  if (await exists3(candidate)) return candidate;
12800
13163
  }
12801
13164
  return null;
@@ -12807,14 +13170,14 @@ function hasSvelteKitDependency(pkg) {
12807
13170
  }
12808
13171
  async function findSvelteKitHooks(serviceDir) {
12809
13172
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
12810
- const candidate = import_node_path53.default.join(serviceDir, rel);
13173
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12811
13174
  if (await exists3(candidate)) return candidate;
12812
13175
  }
12813
13176
  return null;
12814
13177
  }
12815
13178
  async function findSvelteKitConfig(serviceDir) {
12816
13179
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
12817
- const candidate = import_node_path53.default.join(serviceDir, rel);
13180
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12818
13181
  if (await exists3(candidate)) return candidate;
12819
13182
  }
12820
13183
  return null;
@@ -12825,7 +13188,7 @@ function hasNuxtDependency(pkg) {
12825
13188
  }
12826
13189
  async function findNuxtConfig(serviceDir) {
12827
13190
  for (const name of NUXT_CONFIG_CANDIDATES) {
12828
- const candidate = import_node_path53.default.join(serviceDir, name);
13191
+ const candidate = import_node_path56.default.join(serviceDir, name);
12829
13192
  if (await exists3(candidate)) return candidate;
12830
13193
  }
12831
13194
  return null;
@@ -12836,7 +13199,7 @@ function hasAstroDependency(pkg) {
12836
13199
  }
12837
13200
  async function findAstroConfig(serviceDir) {
12838
13201
  for (const name of ASTRO_CONFIG_CANDIDATES) {
12839
- const candidate = import_node_path53.default.join(serviceDir, name);
13202
+ const candidate = import_node_path56.default.join(serviceDir, name);
12840
13203
  if (await exists3(candidate)) return candidate;
12841
13204
  }
12842
13205
  return null;
@@ -12850,7 +13213,7 @@ function parseNextMajor(range) {
12850
13213
  return Number.isFinite(n) ? n : null;
12851
13214
  }
12852
13215
  async function isTypeScriptProject(serviceDir) {
12853
- return exists3(import_node_path53.default.join(serviceDir, "tsconfig.json"));
13216
+ return exists3(import_node_path56.default.join(serviceDir, "tsconfig.json"));
12854
13217
  }
12855
13218
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
12856
13219
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -12899,7 +13262,7 @@ function entryFromScript(script) {
12899
13262
  }
12900
13263
  async function resolveEntry(serviceDir, pkg) {
12901
13264
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
12902
- const candidate = import_node_path53.default.resolve(serviceDir, pkg.main);
13265
+ const candidate = import_node_path56.default.resolve(serviceDir, pkg.main);
12903
13266
  if (await exists3(candidate)) return candidate;
12904
13267
  }
12905
13268
  if (pkg.bin) {
@@ -12913,40 +13276,40 @@ async function resolveEntry(serviceDir, pkg) {
12913
13276
  if (typeof first === "string") binEntry = first;
12914
13277
  }
12915
13278
  if (binEntry) {
12916
- const candidate = import_node_path53.default.resolve(serviceDir, binEntry);
13279
+ const candidate = import_node_path56.default.resolve(serviceDir, binEntry);
12917
13280
  if (await exists3(candidate)) return candidate;
12918
13281
  }
12919
13282
  }
12920
13283
  const startEntry = entryFromScript(pkg.scripts?.start);
12921
13284
  if (startEntry) {
12922
- const candidate = import_node_path53.default.resolve(serviceDir, startEntry);
13285
+ const candidate = import_node_path56.default.resolve(serviceDir, startEntry);
12923
13286
  if (await exists3(candidate)) return candidate;
12924
13287
  }
12925
13288
  const devEntry = entryFromScript(pkg.scripts?.dev);
12926
13289
  if (devEntry) {
12927
- const candidate = import_node_path53.default.resolve(serviceDir, devEntry);
13290
+ const candidate = import_node_path56.default.resolve(serviceDir, devEntry);
12928
13291
  if (await exists3(candidate)) return candidate;
12929
13292
  }
12930
13293
  for (const rel of SRC_INDEX_CANDIDATES) {
12931
- const candidate = import_node_path53.default.join(serviceDir, rel);
13294
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12932
13295
  if (await exists3(candidate)) return candidate;
12933
13296
  }
12934
13297
  for (const rel of SRC_NAMED_CANDIDATES) {
12935
- const candidate = import_node_path53.default.join(serviceDir, rel);
13298
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12936
13299
  if (await exists3(candidate)) return candidate;
12937
13300
  }
12938
13301
  for (const rel of ROOT_NAMED_CANDIDATES) {
12939
- const candidate = import_node_path53.default.join(serviceDir, rel);
13302
+ const candidate = import_node_path56.default.join(serviceDir, rel);
12940
13303
  if (await exists3(candidate)) return candidate;
12941
13304
  }
12942
13305
  for (const name of INDEX_CANDIDATES) {
12943
- const candidate = import_node_path53.default.join(serviceDir, name);
13306
+ const candidate = import_node_path56.default.join(serviceDir, name);
12944
13307
  if (await exists3(candidate)) return candidate;
12945
13308
  }
12946
13309
  return null;
12947
13310
  }
12948
13311
  function dispatchEntry(entryFile, pkg) {
12949
- const ext = import_node_path53.default.extname(entryFile).toLowerCase();
13312
+ const ext = import_node_path56.default.extname(entryFile).toLowerCase();
12950
13313
  if (ext === ".ts" || ext === ".tsx") return "ts";
12951
13314
  if (ext === ".mjs") return "esm";
12952
13315
  if (ext === ".cjs") return "cjs";
@@ -12963,9 +13326,9 @@ function otelInitContents(flavor) {
12963
13326
  return OTEL_INIT_CJS;
12964
13327
  }
12965
13328
  function injectionLine(flavor, entryFile, otelInitFile) {
12966
- let rel = import_node_path53.default.relative(import_node_path53.default.dirname(entryFile), otelInitFile);
13329
+ let rel = import_node_path56.default.relative(import_node_path56.default.dirname(entryFile), otelInitFile);
12967
13330
  if (!rel.startsWith(".")) rel = `./${rel}`;
12968
- rel = rel.split(import_node_path53.default.sep).join("/");
13331
+ rel = rel.split(import_node_path56.default.sep).join("/");
12969
13332
  if (flavor === "cjs") return `require('${rel}')`;
12970
13333
  if (flavor === "esm") return `import '${rel}'`;
12971
13334
  const tsRel = rel.replace(/\.ts$/, "");
@@ -12978,27 +13341,27 @@ function lineIsOtelInjection(line) {
12978
13341
  }
12979
13342
  async function detectsSrcLayout(serviceDir) {
12980
13343
  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"))
13344
+ exists3(import_node_path56.default.join(serviceDir, "src", "app")),
13345
+ exists3(import_node_path56.default.join(serviceDir, "src", "pages")),
13346
+ exists3(import_node_path56.default.join(serviceDir, "app")),
13347
+ exists3(import_node_path56.default.join(serviceDir, "pages"))
12985
13348
  ]);
12986
13349
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
12987
13350
  }
12988
13351
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
12989
13352
  const useTs = await isTypeScriptProject(serviceDir);
12990
13353
  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(
13354
+ const baseDir = srcLayout ? import_node_path56.default.join(serviceDir, "src") : serviceDir;
13355
+ const instrumentationFile = import_node_path56.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
13356
+ const instrumentationNodeFile = import_node_path56.default.join(
12994
13357
  baseDir,
12995
13358
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
12996
13359
  );
12997
- const instrumentationEdgeFile = import_node_path53.default.join(
13360
+ const instrumentationEdgeFile = import_node_path56.default.join(
12998
13361
  baseDir,
12999
13362
  useTs ? "instrumentation.edge.ts" : "instrumentation.edge.js"
13000
13363
  );
13001
- const envNeatFile = import_node_path53.default.join(baseDir, ".env.neat");
13364
+ const envNeatFile = import_node_path56.default.join(baseDir, ".env.neat");
13002
13365
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
13003
13366
  const dependencyEdits = [];
13004
13367
  for (const sdk of SDK_PACKAGES) {
@@ -13075,7 +13438,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
13075
13438
  const nextMajor = parseNextMajor(nextRange);
13076
13439
  if (nextMajor !== null && nextMajor < 15) {
13077
13440
  try {
13078
- const raw = await import_node_fs34.promises.readFile(nextConfigPath, "utf8");
13441
+ const raw = await import_node_fs37.promises.readFile(nextConfigPath, "utf8");
13079
13442
  if (!raw.includes("instrumentationHook")) {
13080
13443
  nextConfigEdit = {
13081
13444
  file: nextConfigPath,
@@ -13123,7 +13486,7 @@ function buildDependencyEdits(pkg, manifestPath) {
13123
13486
  return edits;
13124
13487
  }
13125
13488
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
13126
- const envNeatFile = import_node_path53.default.join(serviceDir, ".env.neat");
13489
+ const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
13127
13490
  if (!await exists3(envNeatFile)) {
13128
13491
  generatedFiles.push({
13129
13492
  file: envNeatFile,
@@ -13158,7 +13521,7 @@ function fileImportsOtelHook(raw, specifiers) {
13158
13521
  }
13159
13522
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
13160
13523
  const useTs = await isTypeScriptProject(serviceDir);
13161
- const otelServerFile = import_node_path53.default.join(
13524
+ const otelServerFile = import_node_path56.default.join(
13162
13525
  serviceDir,
13163
13526
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
13164
13527
  );
@@ -13179,7 +13542,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
13179
13542
  await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
13180
13543
  const entrypointEdits = [];
13181
13544
  try {
13182
- const raw = await import_node_fs34.promises.readFile(entryFile, "utf8");
13545
+ const raw = await import_node_fs37.promises.readFile(entryFile, "utf8");
13183
13546
  if (!fileImportsOtelHook(raw, ["./otel.server"])) {
13184
13547
  const lines = raw.split(/\r?\n/);
13185
13548
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -13215,11 +13578,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
13215
13578
  }
13216
13579
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
13217
13580
  const useTs = await isTypeScriptProject(serviceDir);
13218
- const otelInitFile = import_node_path53.default.join(
13581
+ const otelInitFile = import_node_path56.default.join(
13219
13582
  serviceDir,
13220
13583
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
13221
13584
  );
13222
- const resolvedHooksFile = hooksFile ?? import_node_path53.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
13585
+ const resolvedHooksFile = hooksFile ?? import_node_path56.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
13223
13586
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
13224
13587
  const generatedFiles = [];
13225
13588
  const entrypointEdits = [];
@@ -13244,7 +13607,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
13244
13607
  });
13245
13608
  } else {
13246
13609
  try {
13247
- const raw = await import_node_fs34.promises.readFile(hooksFile, "utf8");
13610
+ const raw = await import_node_fs37.promises.readFile(hooksFile, "utf8");
13248
13611
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
13249
13612
  const lines = raw.split(/\r?\n/);
13250
13613
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -13281,11 +13644,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
13281
13644
  }
13282
13645
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
13283
13646
  const useTs = await isTypeScriptProject(serviceDir);
13284
- const otelPluginFile = import_node_path53.default.join(
13647
+ const otelPluginFile = import_node_path56.default.join(
13285
13648
  serviceDir,
13286
13649
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
13287
13650
  );
13288
- const otelInitFile = import_node_path53.default.join(
13651
+ const otelInitFile = import_node_path56.default.join(
13289
13652
  serviceDir,
13290
13653
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
13291
13654
  );
@@ -13336,19 +13699,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
13336
13699
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
13337
13700
  async function findAstroMiddleware(serviceDir) {
13338
13701
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
13339
- const candidate = import_node_path53.default.join(serviceDir, rel);
13702
+ const candidate = import_node_path56.default.join(serviceDir, rel);
13340
13703
  if (await exists3(candidate)) return candidate;
13341
13704
  }
13342
13705
  return null;
13343
13706
  }
13344
13707
  async function planAstro(serviceDir, pkg, manifestPath, project) {
13345
13708
  const useTs = await isTypeScriptProject(serviceDir);
13346
- const otelInitFile = import_node_path53.default.join(
13709
+ const otelInitFile = import_node_path56.default.join(
13347
13710
  serviceDir,
13348
13711
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
13349
13712
  );
13350
13713
  const existingMiddleware = await findAstroMiddleware(serviceDir);
13351
- const middlewareFile = existingMiddleware ?? import_node_path53.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
13714
+ const middlewareFile = existingMiddleware ?? import_node_path56.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
13352
13715
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
13353
13716
  const generatedFiles = [];
13354
13717
  const entrypointEdits = [];
@@ -13373,7 +13736,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
13373
13736
  });
13374
13737
  } else {
13375
13738
  try {
13376
- const raw = await import_node_fs34.promises.readFile(existingMiddleware, "utf8");
13739
+ const raw = await import_node_fs37.promises.readFile(existingMiddleware, "utf8");
13377
13740
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
13378
13741
  const lines = raw.split(/\r?\n/);
13379
13742
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -13444,7 +13807,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
13444
13807
  }
13445
13808
  async function plan(serviceDir, opts) {
13446
13809
  const pkg = await readPackageJson2(serviceDir);
13447
- const manifestPath = import_node_path53.default.join(serviceDir, "package.json");
13810
+ const manifestPath = import_node_path56.default.join(serviceDir, "package.json");
13448
13811
  const project = opts?.project;
13449
13812
  const empty = {
13450
13813
  language: "javascript",
@@ -13479,8 +13842,8 @@ async function plan(serviceDir, opts) {
13479
13842
  return { ...empty, libOnly: true };
13480
13843
  }
13481
13844
  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");
13845
+ const otelInitFile = import_node_path56.default.join(import_node_path56.default.dirname(entryFile), otelInitFilename(flavor));
13846
+ const envNeatFile = import_node_path56.default.join(serviceDir, ".env.neat");
13484
13847
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
13485
13848
  const dependencyEdits = [];
13486
13849
  for (const sdk of SDK_PACKAGES) {
@@ -13504,7 +13867,7 @@ async function plan(serviceDir, opts) {
13504
13867
  }
13505
13868
  const entrypointEdits = [];
13506
13869
  try {
13507
- const raw = await import_node_fs34.promises.readFile(entryFile, "utf8");
13870
+ const raw = await import_node_fs37.promises.readFile(entryFile, "utf8");
13508
13871
  const lines = raw.split(/\r?\n/);
13509
13872
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
13510
13873
  if (!lineIsOtelInjection(firstReal)) {
@@ -13549,13 +13912,13 @@ async function plan(serviceDir, opts) {
13549
13912
  };
13550
13913
  }
13551
13914
  function isAllowedWritePath(serviceDir, target) {
13552
- const rel = import_node_path53.default.relative(serviceDir, target);
13915
+ const rel = import_node_path56.default.relative(serviceDir, target);
13553
13916
  if (rel.startsWith("..")) return false;
13554
- const base = import_node_path53.default.basename(target);
13917
+ const base = import_node_path56.default.basename(target);
13555
13918
  if (base === "package.json") return true;
13556
13919
  if (base === ".env.neat") return true;
13557
13920
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
13558
- const relPosix = rel.split(import_node_path53.default.sep).join("/");
13921
+ const relPosix = rel.split(import_node_path56.default.sep).join("/");
13559
13922
  if (/^instrumentation(?:\.(?:node|edge))?\.(?:js|cjs|mjs|ts)$/.test(base)) {
13560
13923
  if (relPosix === base) return true;
13561
13924
  if (relPosix === `src/${base}`) return true;
@@ -13572,10 +13935,10 @@ function isAllowedWritePath(serviceDir, target) {
13572
13935
  return false;
13573
13936
  }
13574
13937
  async function writeAtomic(file, contents) {
13575
- await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(file), { recursive: true });
13938
+ await import_node_fs37.promises.mkdir(import_node_path56.default.dirname(file), { recursive: true });
13576
13939
  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);
13940
+ await import_node_fs37.promises.writeFile(tmp, contents, "utf8");
13941
+ await import_node_fs37.promises.rename(tmp, file);
13579
13942
  }
13580
13943
  async function apply(installPlan) {
13581
13944
  const { serviceDir } = installPlan;
@@ -13651,7 +14014,7 @@ async function apply(installPlan) {
13651
14014
  for (const target of allTargets) {
13652
14015
  if (await exists3(target)) {
13653
14016
  try {
13654
- originals.set(target, await import_node_fs34.promises.readFile(target, "utf8"));
14017
+ originals.set(target, await import_node_fs37.promises.readFile(target, "utf8"));
13655
14018
  } catch {
13656
14019
  }
13657
14020
  }
@@ -13734,14 +14097,14 @@ async function rollback(installPlan, originals, createdFiles) {
13734
14097
  const removed = [];
13735
14098
  for (const [file, raw] of originals.entries()) {
13736
14099
  try {
13737
- await import_node_fs34.promises.writeFile(file, raw, "utf8");
14100
+ await import_node_fs37.promises.writeFile(file, raw, "utf8");
13738
14101
  restored.push(file);
13739
14102
  } catch {
13740
14103
  }
13741
14104
  }
13742
14105
  for (const file of createdFiles) {
13743
14106
  try {
13744
- await import_node_fs34.promises.unlink(file);
14107
+ await import_node_fs37.promises.unlink(file);
13745
14108
  removed.push(file);
13746
14109
  } catch {
13747
14110
  }
@@ -13756,8 +14119,8 @@ async function rollback(installPlan, originals, createdFiles) {
13756
14119
  ...removed.map((f) => `removed: ${f}`),
13757
14120
  ""
13758
14121
  ];
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");
14122
+ const rollbackPath = import_node_path56.default.join(installPlan.serviceDir, "neat-rollback.patch");
14123
+ await import_node_fs37.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
13761
14124
  }
13762
14125
  function injectInstrumentationHook(raw) {
13763
14126
  if (raw.includes("instrumentationHook")) return raw;
@@ -13786,8 +14149,8 @@ var javascriptInstaller = {
13786
14149
 
13787
14150
  // src/installers/python.ts
13788
14151
  init_cjs_shims();
13789
- var import_node_fs35 = require("fs");
13790
- var import_node_path54 = __toESM(require("path"), 1);
14152
+ var import_node_fs38 = require("fs");
14153
+ var import_node_path57 = __toESM(require("path"), 1);
13791
14154
  var SDK_PACKAGES2 = [
13792
14155
  { name: "opentelemetry-distro", version: ">=0.49b0" },
13793
14156
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -13799,7 +14162,7 @@ var OTEL_ENV2 = {
13799
14162
  };
13800
14163
  async function exists4(p) {
13801
14164
  try {
13802
- await import_node_fs35.promises.stat(p);
14165
+ await import_node_fs38.promises.stat(p);
13803
14166
  return true;
13804
14167
  } catch {
13805
14168
  return false;
@@ -13808,7 +14171,7 @@ async function exists4(p) {
13808
14171
  async function detect2(serviceDir) {
13809
14172
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
13810
14173
  for (const m of markers) {
13811
- if (await exists4(import_node_path54.default.join(serviceDir, m))) return true;
14174
+ if (await exists4(import_node_path57.default.join(serviceDir, m))) return true;
13812
14175
  }
13813
14176
  return false;
13814
14177
  }
@@ -13818,9 +14181,9 @@ function reqPackageName(line) {
13818
14181
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
13819
14182
  }
13820
14183
  async function planRequirementsTxtEdits(serviceDir) {
13821
- const file = import_node_path54.default.join(serviceDir, "requirements.txt");
14184
+ const file = import_node_path57.default.join(serviceDir, "requirements.txt");
13822
14185
  if (!await exists4(file)) return null;
13823
- const raw = await import_node_fs35.promises.readFile(file, "utf8");
14186
+ const raw = await import_node_fs38.promises.readFile(file, "utf8");
13824
14187
  const presentNames = new Set(
13825
14188
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
13826
14189
  );
@@ -13828,9 +14191,9 @@ async function planRequirementsTxtEdits(serviceDir) {
13828
14191
  return { manifest: file, missing: [...missing] };
13829
14192
  }
13830
14193
  async function planProcfileEdits(serviceDir) {
13831
- const procfile = import_node_path54.default.join(serviceDir, "Procfile");
14194
+ const procfile = import_node_path57.default.join(serviceDir, "Procfile");
13832
14195
  if (!await exists4(procfile)) return [];
13833
- const raw = await import_node_fs35.promises.readFile(procfile, "utf8");
14196
+ const raw = await import_node_fs38.promises.readFile(procfile, "utf8");
13834
14197
  const edits = [];
13835
14198
  for (const line of raw.split(/\r?\n/)) {
13836
14199
  if (line.length === 0) continue;
@@ -13882,8 +14245,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
13882
14245
  const next = `${original}${trailing}${newlines.join("\n")}
13883
14246
  `;
13884
14247
  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);
14248
+ await import_node_fs38.promises.writeFile(tmp, next, "utf8");
14249
+ await import_node_fs38.promises.rename(tmp, manifest);
13887
14250
  }
13888
14251
  async function applyProcfile(procfile, edits, original) {
13889
14252
  let next = original;
@@ -13892,8 +14255,8 @@ async function applyProcfile(procfile, edits, original) {
13892
14255
  next = next.replace(e.before, e.after);
13893
14256
  }
13894
14257
  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);
14258
+ await import_node_fs38.promises.writeFile(tmp, next, "utf8");
14259
+ await import_node_fs38.promises.rename(tmp, procfile);
13897
14260
  }
13898
14261
  async function apply2(installPlan) {
13899
14262
  const { serviceDir } = installPlan;
@@ -13906,7 +14269,7 @@ async function apply2(installPlan) {
13906
14269
  const originals = /* @__PURE__ */ new Map();
13907
14270
  for (const file of touched) {
13908
14271
  try {
13909
- originals.set(file, await import_node_fs35.promises.readFile(file, "utf8"));
14272
+ originals.set(file, await import_node_fs38.promises.readFile(file, "utf8"));
13910
14273
  } catch {
13911
14274
  }
13912
14275
  }
@@ -13917,7 +14280,7 @@ async function apply2(installPlan) {
13917
14280
  if (raw === void 0) {
13918
14281
  throw new Error(`python installer: cannot read ${file} during apply`);
13919
14282
  }
13920
- const base = import_node_path54.default.basename(file);
14283
+ const base = import_node_path57.default.basename(file);
13921
14284
  if (base === "requirements.txt") {
13922
14285
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
13923
14286
  if (edits.length > 0) {
@@ -13942,7 +14305,7 @@ async function rollback2(installPlan, originals) {
13942
14305
  const restored = [];
13943
14306
  for (const [file, raw] of originals.entries()) {
13944
14307
  try {
13945
- await import_node_fs35.promises.writeFile(file, raw, "utf8");
14308
+ await import_node_fs38.promises.writeFile(file, raw, "utf8");
13946
14309
  restored.push(file);
13947
14310
  } catch {
13948
14311
  }
@@ -13956,8 +14319,8 @@ async function rollback2(installPlan, originals) {
13956
14319
  ...restored.map((f) => `restored: ${f}`),
13957
14320
  ""
13958
14321
  ];
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");
14322
+ const rollbackPath = import_node_path57.default.join(installPlan.serviceDir, "neat-rollback.patch");
14323
+ await import_node_fs38.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
13961
14324
  }
13962
14325
  var pythonInstaller = {
13963
14326
  name: "python",
@@ -14080,10 +14443,10 @@ function renderPatch(sections) {
14080
14443
 
14081
14444
  // src/orchestrator.ts
14082
14445
  init_cjs_shims();
14083
- var import_node_fs36 = require("fs");
14446
+ var import_node_fs39 = require("fs");
14084
14447
  var import_node_http = __toESM(require("http"), 1);
14085
14448
  var import_node_net = __toESM(require("net"), 1);
14086
- var import_node_path55 = __toESM(require("path"), 1);
14449
+ var import_node_path58 = __toESM(require("path"), 1);
14087
14450
  var import_node_child_process3 = require("child_process");
14088
14451
  var import_node_readline = __toESM(require("readline"), 1);
14089
14452
  async function extractAndPersist(opts) {
@@ -14092,7 +14455,7 @@ async function extractAndPersist(opts) {
14092
14455
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
14093
14456
  resetGraph(graphKey);
14094
14457
  const graph = getGraph(graphKey);
14095
- const projectPaths = pathsForProject(graphKey, import_node_path55.default.join(opts.scanPath, "neat-out"));
14458
+ const projectPaths = pathsForProject(graphKey, import_node_path58.default.join(opts.scanPath, "neat-out"));
14096
14459
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
14097
14460
  errorsPath: projectPaths.errorsPath
14098
14461
  });
@@ -14144,7 +14507,7 @@ async function applyInstallersOver(services, project, options = {}) {
14144
14507
  libOnly++;
14145
14508
  const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
14146
14509
  if (appDeps.length > 0) {
14147
- const svcName = import_node_path55.default.basename(svc.dir);
14510
+ const svcName = import_node_path58.default.basename(svc.dir);
14148
14511
  const list = appDeps.join(", ");
14149
14512
  console.warn(
14150
14513
  `neat: runtime layer won't engage for ${svcName}: no entry point found.
@@ -14157,7 +14520,7 @@ async function applyInstallersOver(services, project, options = {}) {
14157
14520
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
14158
14521
  } else if (outcome.outcome === "react-native") {
14159
14522
  reactNative++;
14160
- const svcName = import_node_path55.default.basename(svc.dir);
14523
+ const svcName = import_node_path58.default.basename(svc.dir);
14161
14524
  console.log(
14162
14525
  `neat: ${svc.dir} detected as React Native / Expo
14163
14526
  The installer doesn't cover this runtime deterministically.
@@ -14168,7 +14531,7 @@ async function applyInstallersOver(services, project, options = {}) {
14168
14531
  );
14169
14532
  } else if (outcome.outcome === "bun") {
14170
14533
  bun++;
14171
- const svcName = import_node_path55.default.basename(svc.dir);
14534
+ const svcName = import_node_path58.default.basename(svc.dir);
14172
14535
  console.log(
14173
14536
  `neat: ${svc.dir} detected as Bun
14174
14537
  The installer doesn't cover this runtime deterministically.
@@ -14179,7 +14542,7 @@ async function applyInstallersOver(services, project, options = {}) {
14179
14542
  );
14180
14543
  } else if (outcome.outcome === "deno") {
14181
14544
  deno++;
14182
- const svcName = import_node_path55.default.basename(svc.dir);
14545
+ const svcName = import_node_path58.default.basename(svc.dir);
14183
14546
  console.log(
14184
14547
  `neat: ${svc.dir} detected as Deno
14185
14548
  The installer doesn't cover this runtime deterministically.
@@ -14190,7 +14553,7 @@ async function applyInstallersOver(services, project, options = {}) {
14190
14553
  );
14191
14554
  } else if (outcome.outcome === "cloudflare-workers") {
14192
14555
  cloudflareWorkers++;
14193
- const svcName = import_node_path55.default.basename(svc.dir);
14556
+ const svcName = import_node_path58.default.basename(svc.dir);
14194
14557
  console.log(
14195
14558
  `neat: ${svc.dir} detected as Cloudflare Workers
14196
14559
  The installer doesn't cover this runtime deterministically.
@@ -14201,7 +14564,7 @@ async function applyInstallersOver(services, project, options = {}) {
14201
14564
  );
14202
14565
  } else if (outcome.outcome === "electron") {
14203
14566
  electron++;
14204
- const svcName = import_node_path55.default.basename(svc.dir);
14567
+ const svcName = import_node_path58.default.basename(svc.dir);
14205
14568
  console.log(
14206
14569
  `neat: ${svc.dir} detected as Electron
14207
14570
  The installer doesn't cover this runtime deterministically.
@@ -14214,7 +14577,7 @@ async function applyInstallersOver(services, project, options = {}) {
14214
14577
  if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
14215
14578
  const gaps = uninstrumentedLibraries(svc.pkg);
14216
14579
  if (gaps.length > 0) {
14217
- const svcName = import_node_path55.default.basename(svc.dir);
14580
+ const svcName = import_node_path58.default.basename(svc.dir);
14218
14581
  const list = gaps.join(", ");
14219
14582
  const subject = gaps.length === 1 ? "this library" : "these libraries";
14220
14583
  const aux = gaps.length === 1 ? "isn't" : "aren't";
@@ -14420,24 +14783,24 @@ async function persistedPortsFor(scanPath) {
14420
14783
  return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
14421
14784
  }
14422
14785
  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 });
14786
+ const lockPath = import_node_path58.default.join(scanPath, "neat-out", "daemon.spawn.lock");
14787
+ await import_node_fs39.promises.mkdir(import_node_path58.default.dirname(lockPath), { recursive: true });
14425
14788
  const STALE_LOCK_MS = 6e4;
14426
14789
  try {
14427
- const fd = await import_node_fs36.promises.open(lockPath, "wx");
14790
+ const fd = await import_node_fs39.promises.open(lockPath, "wx");
14428
14791
  await fd.writeFile(`${process.pid}
14429
14792
  `, "utf8");
14430
14793
  await fd.close();
14431
14794
  return async () => {
14432
- await import_node_fs36.promises.unlink(lockPath).catch(() => {
14795
+ await import_node_fs39.promises.unlink(lockPath).catch(() => {
14433
14796
  });
14434
14797
  };
14435
14798
  } catch (err) {
14436
14799
  if (err.code !== "EEXIST") return null;
14437
14800
  try {
14438
- const stat = await import_node_fs36.promises.stat(lockPath);
14801
+ const stat = await import_node_fs39.promises.stat(lockPath);
14439
14802
  if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
14440
- await import_node_fs36.promises.unlink(lockPath).catch(() => {
14803
+ await import_node_fs39.promises.unlink(lockPath).catch(() => {
14441
14804
  });
14442
14805
  return acquireSpawnLock(scanPath);
14443
14806
  }
@@ -14466,13 +14829,13 @@ async function healthIsForProject(restPort, project) {
14466
14829
  return false;
14467
14830
  }
14468
14831
  function daemonLogPath(projectPath2) {
14469
- return import_node_path55.default.join(projectPath2, "neat-out", "daemon.log");
14832
+ return import_node_path58.default.join(projectPath2, "neat-out", "daemon.log");
14470
14833
  }
14471
14834
  function spawnDaemonDetached(spec) {
14472
- const here = import_node_path55.default.dirname(new URL(importMetaUrl).pathname);
14835
+ const here = import_node_path58.default.dirname(new URL(importMetaUrl).pathname);
14473
14836
  const candidates = [
14474
- import_node_path55.default.join(here, "neatd.cjs"),
14475
- import_node_path55.default.join(here, "neatd.js")
14837
+ import_node_path58.default.join(here, "neatd.cjs"),
14838
+ import_node_path58.default.join(here, "neatd.js")
14476
14839
  ];
14477
14840
  let entry2 = null;
14478
14841
  const fsSync = require("fs");
@@ -14502,7 +14865,7 @@ function spawnDaemonDetached(spec) {
14502
14865
  let logFd = null;
14503
14866
  if (spec) {
14504
14867
  const logPath = daemonLogPath(spec.projectPath);
14505
- fsSync.mkdirSync(import_node_path55.default.dirname(logPath), { recursive: true });
14868
+ fsSync.mkdirSync(import_node_path58.default.dirname(logPath), { recursive: true });
14506
14869
  logFd = fsSync.openSync(logPath, "a");
14507
14870
  }
14508
14871
  const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
@@ -14541,7 +14904,7 @@ async function runOrchestrator(opts) {
14541
14904
  browser: "skipped"
14542
14905
  }
14543
14906
  };
14544
- const stat = await import_node_fs36.promises.stat(opts.scanPath).catch(() => null);
14907
+ const stat = await import_node_fs39.promises.stat(opts.scanPath).catch(() => null);
14545
14908
  if (!stat || !stat.isDirectory()) {
14546
14909
  console.error(`neat: ${opts.scanPath} is not a directory`);
14547
14910
  result.exitCode = 2;
@@ -14701,7 +15064,7 @@ async function runOrchestrator(opts) {
14701
15064
  result.steps.browser = openBrowser(dashboardUrl);
14702
15065
  }
14703
15066
  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;
15067
+ const daemonLog = daemonRunning ? import_node_path58.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
14705
15068
  printSummary(result, graph, dashboardUrl, daemonLog);
14706
15069
  return result;
14707
15070
  }
@@ -15069,11 +15432,11 @@ async function runConnectorCommand(rawArgs, deps = {}) {
15069
15432
 
15070
15433
  // src/cli-verbs.ts
15071
15434
  init_cjs_shims();
15072
- var import_node_path56 = __toESM(require("path"), 1);
15435
+ var import_node_path59 = __toESM(require("path"), 1);
15073
15436
 
15074
15437
  // src/cli-client.ts
15075
15438
  init_cjs_shims();
15076
- var import_types44 = require("@neat.is/types");
15439
+ var import_types47 = require("@neat.is/types");
15077
15440
  var HttpError = class extends Error {
15078
15441
  constructor(status2, message, responseBody = "") {
15079
15442
  super(message);
@@ -15098,10 +15461,10 @@ function createHttpClient(baseUrl, bearerToken) {
15098
15461
  const root = baseUrl.replace(/\/$/, "");
15099
15462
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
15100
15463
  return {
15101
- async get(path58) {
15464
+ async get(path61) {
15102
15465
  let res;
15103
15466
  try {
15104
- res = await fetch(`${root}${path58}`, {
15467
+ res = await fetch(`${root}${path61}`, {
15105
15468
  headers: { ...authHeader }
15106
15469
  });
15107
15470
  } catch (err) {
@@ -15113,16 +15476,16 @@ function createHttpClient(baseUrl, bearerToken) {
15113
15476
  const body = await res.text().catch(() => "");
15114
15477
  throw new HttpError(
15115
15478
  res.status,
15116
- `${res.status} ${res.statusText} on GET ${path58}: ${body}`,
15479
+ `${res.status} ${res.statusText} on GET ${path61}: ${body}`,
15117
15480
  body
15118
15481
  );
15119
15482
  }
15120
15483
  return await res.json();
15121
15484
  },
15122
- async post(path58, body) {
15485
+ async post(path61, body) {
15123
15486
  let res;
15124
15487
  try {
15125
- res = await fetch(`${root}${path58}`, {
15488
+ res = await fetch(`${root}${path61}`, {
15126
15489
  method: "POST",
15127
15490
  headers: { "content-type": "application/json", ...authHeader },
15128
15491
  body: JSON.stringify(body)
@@ -15136,7 +15499,7 @@ function createHttpClient(baseUrl, bearerToken) {
15136
15499
  const text = await res.text().catch(() => "");
15137
15500
  throw new HttpError(
15138
15501
  res.status,
15139
- `${res.status} ${res.statusText} on POST ${path58}: ${text}`,
15502
+ `${res.status} ${res.statusText} on POST ${path61}: ${text}`,
15140
15503
  text
15141
15504
  );
15142
15505
  }
@@ -15150,12 +15513,12 @@ function projectPath(project, suffix) {
15150
15513
  }
15151
15514
  async function runRootCause(client, input) {
15152
15515
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
15153
- const path58 = projectPath(
15516
+ const path61 = projectPath(
15154
15517
  input.project,
15155
15518
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
15156
15519
  );
15157
15520
  try {
15158
- const result = await client.get(path58);
15521
+ const result = await client.get(path61);
15159
15522
  const arrowPath = result.traversalPath.join(" \u2190 ");
15160
15523
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
15161
15524
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -15181,12 +15544,12 @@ async function runRootCause(client, input) {
15181
15544
  }
15182
15545
  async function runBlastRadius(client, input) {
15183
15546
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
15184
- const path58 = projectPath(
15547
+ const path61 = projectPath(
15185
15548
  input.project,
15186
15549
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
15187
15550
  );
15188
15551
  try {
15189
- const result = await client.get(path58);
15552
+ const result = await client.get(path61);
15190
15553
  if (result.totalAffected === 0) {
15191
15554
  return {
15192
15555
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -15215,17 +15578,17 @@ async function runBlastRadius(client, input) {
15215
15578
  }
15216
15579
  }
15217
15580
  function formatBlastEntry(n) {
15218
- const tag = n.edgeProvenance === import_types44.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
15581
+ const tag = n.edgeProvenance === import_types47.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
15219
15582
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
15220
15583
  }
15221
15584
  async function runDependencies(client, input) {
15222
15585
  const depth = input.depth ?? 3;
15223
- const path58 = projectPath(
15586
+ const path61 = projectPath(
15224
15587
  input.project,
15225
15588
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
15226
15589
  );
15227
15590
  try {
15228
- const result = await client.get(path58);
15591
+ const result = await client.get(path61);
15229
15592
  if (result.total === 0) {
15230
15593
  return {
15231
15594
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -15272,7 +15635,7 @@ async function runObservedDependencies(client, input) {
15272
15635
  if (result.observed) {
15273
15636
  return {
15274
15637
  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
15638
+ provenance: import_types47.Provenance.OBSERVED
15276
15639
  };
15277
15640
  }
15278
15641
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -15282,7 +15645,7 @@ async function runObservedDependencies(client, input) {
15282
15645
  return {
15283
15646
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
15284
15647
  block: blockLines.join("\n"),
15285
- provenance: import_types44.Provenance.OBSERVED
15648
+ provenance: import_types47.Provenance.OBSERVED
15286
15649
  };
15287
15650
  } catch (err) {
15288
15651
  if (err instanceof HttpError && err.status === 404) {
@@ -15317,9 +15680,9 @@ function formatDuration(ms) {
15317
15680
  return `${Math.round(h / 24)}d`;
15318
15681
  }
15319
15682
  async function runIncidents(client, input) {
15320
- const path58 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
15683
+ const path61 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
15321
15684
  try {
15322
- const body = await client.get(path58);
15685
+ const body = await client.get(path61);
15323
15686
  const events = body.events;
15324
15687
  if (events.length === 0) {
15325
15688
  return {
@@ -15336,7 +15699,7 @@ async function runIncidents(client, input) {
15336
15699
  return {
15337
15700
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
15338
15701
  block: blockLines.join("\n"),
15339
- provenance: import_types44.Provenance.OBSERVED
15702
+ provenance: import_types47.Provenance.OBSERVED
15340
15703
  };
15341
15704
  } catch (err) {
15342
15705
  if (err instanceof HttpError && err.status === 404) {
@@ -15445,7 +15808,7 @@ async function runStaleEdges(client, input) {
15445
15808
  return {
15446
15809
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
15447
15810
  block: blockLines.join("\n"),
15448
- provenance: import_types44.Provenance.STALE
15811
+ provenance: import_types47.Provenance.STALE
15449
15812
  };
15450
15813
  }
15451
15814
  async function runPolicies(client, input) {
@@ -15609,7 +15972,7 @@ async function resolveProjectEntry(opts) {
15609
15972
  const cwd = opts.cwd ?? process.cwd();
15610
15973
  const resolvedCwd = await normalizeProjectPath(cwd);
15611
15974
  for (const entry2 of entries) {
15612
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path56.default.sep}`)) {
15975
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path59.default.sep}`)) {
15613
15976
  return entry2;
15614
15977
  }
15615
15978
  }
@@ -15762,7 +16125,7 @@ async function runSync(opts) {
15762
16125
  }
15763
16126
 
15764
16127
  // src/cli.ts
15765
- var import_types45 = require("@neat.is/types");
16128
+ var import_types48 = require("@neat.is/types");
15766
16129
  function isNpxInvocation() {
15767
16130
  if (process.env.npm_command === "exec") return true;
15768
16131
  const execpath = process.env.npm_execpath ?? "";
@@ -16055,7 +16418,7 @@ async function buildPatchSections(services, project) {
16055
16418
  }
16056
16419
  async function runInit(opts) {
16057
16420
  const written = [];
16058
- const stat = await import_node_fs37.promises.stat(opts.scanPath).catch(() => null);
16421
+ const stat = await import_node_fs40.promises.stat(opts.scanPath).catch(() => null);
16059
16422
  if (!stat || !stat.isDirectory()) {
16060
16423
  console.error(`neat init: ${opts.scanPath} is not a directory`);
16061
16424
  return { exitCode: 2, writtenFiles: written };
@@ -16064,13 +16427,13 @@ async function runInit(opts) {
16064
16427
  printDiscoveryReport(opts, services);
16065
16428
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
16066
16429
  const patch = renderPatch(sections);
16067
- const patchPath = import_node_path57.default.join(opts.scanPath, "neat.patch");
16430
+ const patchPath = import_node_path60.default.join(opts.scanPath, "neat.patch");
16068
16431
  if (opts.dryRun) {
16069
- await import_node_fs37.promises.writeFile(patchPath, patch, "utf8");
16432
+ await import_node_fs40.promises.writeFile(patchPath, patch, "utf8");
16070
16433
  written.push(patchPath);
16071
16434
  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);
16435
+ const gitignorePath = import_node_path60.default.join(opts.scanPath, ".gitignore");
16436
+ const gitignoreExists = await import_node_fs40.promises.stat(gitignorePath).then(() => true).catch(() => false);
16074
16437
  const verb = gitignoreExists ? "append" : "create";
16075
16438
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
16076
16439
  console.log("rerun without --dry-run to register and snapshot.");
@@ -16081,9 +16444,9 @@ async function runInit(opts) {
16081
16444
  const graph = getGraph(graphKey);
16082
16445
  const projectPaths = pathsForProject(
16083
16446
  graphKey,
16084
- import_node_path57.default.join(opts.scanPath, "neat-out")
16447
+ import_node_path60.default.join(opts.scanPath, "neat-out")
16085
16448
  );
16086
- const errorsPath = import_node_path57.default.join(import_node_path57.default.dirname(opts.outPath), import_node_path57.default.basename(projectPaths.errorsPath));
16449
+ const errorsPath = import_node_path60.default.join(import_node_path60.default.dirname(opts.outPath), import_node_path60.default.basename(projectPaths.errorsPath));
16087
16450
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
16088
16451
  await saveGraphToDisk(graph, opts.outPath);
16089
16452
  written.push(opts.outPath);
@@ -16162,7 +16525,7 @@ async function runInit(opts) {
16162
16525
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
16163
16526
  }
16164
16527
  } else {
16165
- await import_node_fs37.promises.writeFile(patchPath, patch, "utf8");
16528
+ await import_node_fs40.promises.writeFile(patchPath, patch, "utf8");
16166
16529
  written.push(patchPath);
16167
16530
  }
16168
16531
  }
@@ -16202,9 +16565,9 @@ var CLAUDE_SKILL_CONFIG = {
16202
16565
  };
16203
16566
  function claudeConfigPath() {
16204
16567
  const override = process.env.NEAT_CLAUDE_CONFIG;
16205
- if (override && override.length > 0) return import_node_path57.default.resolve(override);
16568
+ if (override && override.length > 0) return import_node_path60.default.resolve(override);
16206
16569
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
16207
- return import_node_path57.default.join(home, ".claude.json");
16570
+ return import_node_path60.default.join(home, ".claude.json");
16208
16571
  }
16209
16572
  async function runSkill(opts) {
16210
16573
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -16216,7 +16579,7 @@ async function runSkill(opts) {
16216
16579
  const target = claudeConfigPath();
16217
16580
  let existing = {};
16218
16581
  try {
16219
- existing = JSON.parse(await import_node_fs37.promises.readFile(target, "utf8"));
16582
+ existing = JSON.parse(await import_node_fs40.promises.readFile(target, "utf8"));
16220
16583
  } catch (err) {
16221
16584
  if (err.code !== "ENOENT") {
16222
16585
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -16228,8 +16591,8 @@ async function runSkill(opts) {
16228
16591
  ...existing,
16229
16592
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
16230
16593
  };
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");
16594
+ await import_node_fs40.promises.mkdir(import_node_path60.default.dirname(target), { recursive: true });
16595
+ await import_node_fs40.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
16233
16596
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
16234
16597
  console.log("restart Claude Code to pick up the new MCP server.");
16235
16598
  return { exitCode: 0 };
@@ -16283,12 +16646,12 @@ async function main() {
16283
16646
  console.error("neat init: --apply and --dry-run are mutually exclusive");
16284
16647
  process.exit(2);
16285
16648
  }
16286
- const scanPath = import_node_path57.default.resolve(target);
16649
+ const scanPath = import_node_path60.default.resolve(target);
16287
16650
  const projectExplicit = parsed.project !== null;
16288
- const projectName = projectExplicit ? project : import_node_path57.default.basename(scanPath);
16651
+ const projectName = projectExplicit ? project : import_node_path60.default.basename(scanPath);
16289
16652
  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);
16653
+ const fallback = pathsForProject(projectKey, import_node_path60.default.join(scanPath, "neat-out")).snapshotPath;
16654
+ const outPath = import_node_path60.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
16292
16655
  const result = await runInit({
16293
16656
  scanPath,
16294
16657
  outPath,
@@ -16309,21 +16672,21 @@ async function main() {
16309
16672
  usage();
16310
16673
  process.exit(2);
16311
16674
  }
16312
- const scanPath = import_node_path57.default.resolve(target);
16313
- const stat = await import_node_fs37.promises.stat(scanPath).catch(() => null);
16675
+ const scanPath = import_node_path60.default.resolve(target);
16676
+ const stat = await import_node_fs40.promises.stat(scanPath).catch(() => null);
16314
16677
  if (!stat || !stat.isDirectory()) {
16315
16678
  console.error(`neat watch: ${scanPath} is not a directory`);
16316
16679
  process.exit(2);
16317
16680
  }
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))
16681
+ const projectPaths = pathsForProject(project, import_node_path60.default.join(scanPath, "neat-out"));
16682
+ const outPath = import_node_path60.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
16683
+ const errorsPath = import_node_path60.default.resolve(
16684
+ process.env.NEAT_ERRORS_PATH ?? import_node_path60.default.join(import_node_path60.default.dirname(outPath), import_node_path60.default.basename(projectPaths.errorsPath))
16322
16685
  );
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))
16686
+ const staleEventsPath = import_node_path60.default.resolve(
16687
+ 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
16688
  );
16326
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path57.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
16689
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path60.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
16327
16690
  const handle = await startWatch(getGraph(project), {
16328
16691
  scanPath,
16329
16692
  outPath,
@@ -16506,11 +16869,11 @@ async function main() {
16506
16869
  process.exit(1);
16507
16870
  }
16508
16871
  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);
16872
+ const scanPath = import_node_path60.default.resolve(cmd);
16873
+ const stat = await import_node_fs40.promises.stat(scanPath).catch(() => null);
16511
16874
  if (!stat || !stat.isDirectory()) return null;
16512
16875
  const projectExplicit = parsed.project !== null;
16513
- const projectName = projectExplicit ? parsed.project : import_node_path57.default.basename(scanPath);
16876
+ const projectName = projectExplicit ? parsed.project : import_node_path60.default.basename(scanPath);
16514
16877
  const result = await runOrchestrator({
16515
16878
  scanPath,
16516
16879
  project: projectName,
@@ -16699,10 +17062,10 @@ async function runQueryVerb(cmd, parsed) {
16699
17062
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
16700
17063
  const out = [];
16701
17064
  for (const p of parts) {
16702
- const r = import_types45.DivergenceTypeSchema.safeParse(p);
17065
+ const r = import_types48.DivergenceTypeSchema.safeParse(p);
16703
17066
  if (!r.success) {
16704
17067
  console.error(
16705
- `neat divergences: unknown --type "${p}". allowed: ${import_types45.DivergenceTypeSchema.options.join(", ")}`
17068
+ `neat divergences: unknown --type "${p}". allowed: ${import_types48.DivergenceTypeSchema.options.join(", ")}`
16706
17069
  );
16707
17070
  return 2;
16708
17071
  }