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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/neatd.cjs CHANGED
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
61
61
  ]);
62
62
  const publicRead = opts.publicRead === true;
63
63
  app.addHook("preHandler", (req2, reply, done) => {
64
- const path50 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path50) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path50)) {
64
+ const path53 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path53) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path53)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -194,8 +194,8 @@ function reshapeGrpcRequest(req2) {
194
194
  };
195
195
  }
196
196
  function resolveProtoRoot() {
197
- const here = import_node_path43.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
- return import_node_path43.default.resolve(here, "..", "proto");
197
+ const here = import_node_path47.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
198
+ return import_node_path47.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_url, import_node_path43, import_node_crypto2, grpc, protoLoader;
266
+ var import_node_url, import_node_path47, 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_url = require("url");
272
- import_node_path43 = __toESM(require("path"), 1);
272
+ import_node_path47 = __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 path50 = q === -1 ? v : v.slice(0, q);
366
- if (path50.length > 0) return path50;
365
+ const path53 = q === -1 ? v : v.slice(0, q);
366
+ if (path53.length > 0) return path53;
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_path44.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
421
- const protoRoot = import_node_path44.default.resolve(here, "..", "proto");
420
+ const here = import_node_path48.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
421
+ const protoRoot = import_node_path48.default.resolve(here, "..", "proto");
422
422
  const root = new import_protobufjs.default.Root();
423
- root.resolvePath = (_origin, target) => import_node_path44.default.resolve(protoRoot, target);
423
+ root.resolvePath = (_origin, target) => import_node_path48.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_path44, import_node_url2, 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_path48, import_node_url2, 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_path44 = __toESM(require("path"), 1);
656
+ import_node_path48 = __toESM(require("path"), 1);
657
657
  import_node_url2 = require("url");
658
658
  import_fastify2 = __toESM(require("fastify"), 1);
659
659
  import_protobufjs = __toESM(require("protobufjs"), 1);
@@ -676,14 +676,14 @@ __export(neatd_exports, {
676
676
  });
677
677
  module.exports = __toCommonJS(neatd_exports);
678
678
  init_cjs_shims();
679
- var import_node_fs30 = require("fs");
680
- var import_node_path49 = __toESM(require("path"), 1);
679
+ var import_node_fs33 = require("fs");
680
+ var import_node_path52 = __toESM(require("path"), 1);
681
681
  var import_node_module2 = require("module");
682
682
 
683
683
  // src/daemon.ts
684
684
  init_cjs_shims();
685
- var import_node_fs28 = require("fs");
686
- var import_node_path47 = __toESM(require("path"), 1);
685
+ var import_node_fs31 = require("fs");
686
+ var import_node_path50 = __toESM(require("path"), 1);
687
687
  var import_node_module = require("module");
688
688
 
689
689
  // src/graph.ts
@@ -1213,19 +1213,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1213
1213
  function longestIncomingWalk(graph, start, maxDepth) {
1214
1214
  let best = { path: [start], edges: [] };
1215
1215
  const visited = /* @__PURE__ */ new Set([start]);
1216
- function step(node, path50, edges) {
1217
- if (path50.length > best.path.length) {
1218
- best = { path: [...path50], edges: [...edges] };
1216
+ function step(node, path53, edges) {
1217
+ if (path53.length > best.path.length) {
1218
+ best = { path: [...path53], edges: [...edges] };
1219
1219
  }
1220
- if (path50.length - 1 >= maxDepth) return;
1220
+ if (path53.length - 1 >= maxDepth) return;
1221
1221
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1222
1222
  for (const [srcId, edge] of incoming) {
1223
1223
  if (visited.has(srcId)) continue;
1224
1224
  visited.add(srcId);
1225
- path50.push(srcId);
1225
+ path53.push(srcId);
1226
1226
  edges.push(edge);
1227
- step(srcId, path50, edges);
1228
- path50.pop();
1227
+ step(srcId, path53, edges);
1228
+ path53.pop();
1229
1229
  edges.pop();
1230
1230
  visited.delete(srcId);
1231
1231
  }
@@ -1419,26 +1419,26 @@ function dominantFailingCall(graph, serviceId5, visited) {
1419
1419
  return best;
1420
1420
  }
1421
1421
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1422
- const path50 = [originServiceId];
1422
+ const path53 = [originServiceId];
1423
1423
  const edges = [];
1424
1424
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1425
1425
  let current = originServiceId;
1426
1426
  for (let depth = 0; depth < maxDepth; depth++) {
1427
1427
  const hop = dominantFailingCall(graph, current, visited);
1428
1428
  if (!hop) break;
1429
- path50.push(hop.nextService);
1429
+ path53.push(hop.nextService);
1430
1430
  edges.push(hop.edge);
1431
1431
  visited.add(hop.nextService);
1432
1432
  current = hop.nextService;
1433
1433
  }
1434
1434
  if (edges.length === 0) return null;
1435
- return { path: path50, edges, culprit: current };
1435
+ return { path: path53, edges, culprit: current };
1436
1436
  }
1437
1437
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1438
1438
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1439
1439
  if (!chain) return null;
1440
1440
  const culprit = chain.culprit;
1441
- const path50 = [...chain.path];
1441
+ const path53 = [...chain.path];
1442
1442
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1443
1443
  const baseConfidence = confidenceFromMix(chain.edges);
1444
1444
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1446,14 +1446,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1446
1446
  if (loc) {
1447
1447
  let rootCauseNode = culprit;
1448
1448
  if (loc.fileNode) {
1449
- path50.push(loc.fileNode);
1449
+ path53.push(loc.fileNode);
1450
1450
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1451
1451
  rootCauseNode = loc.fileNode;
1452
1452
  }
1453
1453
  return import_types.RootCauseResultSchema.parse({
1454
1454
  rootCauseNode,
1455
1455
  rootCauseReason: loc.rootCauseReason,
1456
- traversalPath: path50,
1456
+ traversalPath: path53,
1457
1457
  edgeProvenances,
1458
1458
  confidence,
1459
1459
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1465,7 +1465,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1465
1465
  return import_types.RootCauseResultSchema.parse({
1466
1466
  rootCauseNode: culprit,
1467
1467
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1468
- traversalPath: path50,
1468
+ traversalPath: path53,
1469
1469
  edgeProvenances,
1470
1470
  confidence,
1471
1471
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -6458,6 +6458,39 @@ function classifyImage(image) {
6458
6458
  if (last.startsWith("memcached")) return "memcached";
6459
6459
  return "container";
6460
6460
  }
6461
+ function lineContaining(raw, needle) {
6462
+ if (!needle) return void 0;
6463
+ const idx = raw.indexOf(needle);
6464
+ if (idx === -1) return void 0;
6465
+ let line = 1;
6466
+ for (let i = 0; i < idx; i++) if (raw[i] === "\n") line++;
6467
+ return line;
6468
+ }
6469
+ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provider, evidenceFile, line) {
6470
+ let nodesAdded = 0;
6471
+ let edgesAdded = 0;
6472
+ const node = makeInfraNode(kind, name, provider);
6473
+ if (!graph.hasNode(node.id)) {
6474
+ graph.addNode(node.id, node);
6475
+ nodesAdded++;
6476
+ }
6477
+ if (node.id === anchorId) return { nodesAdded, edgesAdded };
6478
+ const edgeId = (0, import_types4.extractedEdgeId)(anchorId, node.id, edgeType);
6479
+ if (!graph.hasEdge(edgeId)) {
6480
+ const edge = {
6481
+ id: edgeId,
6482
+ source: anchorId,
6483
+ target: node.id,
6484
+ type: edgeType,
6485
+ provenance: import_types21.Provenance.EXTRACTED,
6486
+ confidence: (0, import_types21.confidenceForExtracted)("structural"),
6487
+ evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
6488
+ };
6489
+ graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
6490
+ edgesAdded++;
6491
+ }
6492
+ return { nodesAdded, edgesAdded };
6493
+ }
6461
6494
 
6462
6495
  // src/extract/infra/docker-compose.ts
6463
6496
  function dependsOnList(value) {
@@ -6817,7 +6850,7 @@ async function readWranglerConfig(dir) {
6817
6850
  }
6818
6851
  return null;
6819
6852
  }
6820
- function lineContaining(raw, needle) {
6853
+ function lineContaining2(raw, needle) {
6821
6854
  if (!needle) return void 0;
6822
6855
  const idx = raw.indexOf(needle);
6823
6856
  if (idx === -1) return void 0;
@@ -6941,7 +6974,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
6941
6974
  "cloudflare-route",
6942
6975
  route,
6943
6976
  evidenceFile,
6944
- lineContaining(raw, route)
6977
+ lineContaining2(raw, route)
6945
6978
  );
6946
6979
  nodesAdded += result.nodesAdded;
6947
6980
  edgesAdded += result.edgesAdded;
@@ -6965,7 +6998,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
6965
6998
  group.kind,
6966
6999
  name,
6967
7000
  evidenceFile,
6968
- lineContaining(raw, name)
7001
+ lineContaining2(raw, name)
6969
7002
  );
6970
7003
  nodesAdded += result.nodesAdded;
6971
7004
  edgesAdded += result.edgesAdded;
@@ -6979,7 +7012,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
6979
7012
  "cloudflare-cron",
6980
7013
  cron,
6981
7014
  evidenceFile,
6982
- lineContaining(raw, cron)
7015
+ lineContaining2(raw, cron)
6983
7016
  );
6984
7017
  nodesAdded += result.nodesAdded;
6985
7018
  edgesAdded += result.edgesAdded;
@@ -6992,7 +7025,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
6992
7025
  "cloudflare-env-var",
6993
7026
  varName,
6994
7027
  evidenceFile,
6995
- lineContaining(raw, varName)
7028
+ lineContaining2(raw, varName)
6996
7029
  );
6997
7030
  nodesAdded += result.nodesAdded;
6998
7031
  edgesAdded += result.edgesAdded;
@@ -7010,7 +7043,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7010
7043
  type: import_types25.EdgeType.CALLS,
7011
7044
  provenance: import_types25.Provenance.EXTRACTED,
7012
7045
  confidence: (0, import_types25.confidenceForExtracted)("structural"),
7013
- evidence: { file: evidenceFile, line: lineContaining(raw, svc.service) }
7046
+ evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
7014
7047
  };
7015
7048
  graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
7016
7049
  edgesAdded++;
@@ -7024,11 +7057,208 @@ async function addCloudflareWorkers(graph, services, scanPath) {
7024
7057
  "cloudflare-service-binding",
7025
7058
  svc.service,
7026
7059
  evidenceFile,
7027
- lineContaining(raw, svc.service)
7060
+ lineContaining2(raw, svc.service)
7061
+ );
7062
+ nodesAdded += result.nodesAdded;
7063
+ edgesAdded += result.edgesAdded;
7064
+ }
7065
+ }
7066
+ return { nodesAdded, edgesAdded };
7067
+ }
7068
+
7069
+ // src/extract/infra/vercel.ts
7070
+ init_cjs_shims();
7071
+ var import_node_fs20 = require("fs");
7072
+ var import_node_path36 = __toESM(require("path"), 1);
7073
+ var import_types26 = require("@neat.is/types");
7074
+ var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
7075
+ async function readVercelConfig(dir) {
7076
+ for (const filename of VERCEL_CONFIG_FILENAMES) {
7077
+ const abs = import_node_path36.default.join(dir, filename);
7078
+ if (!await exists(abs)) continue;
7079
+ const raw = await import_node_fs20.promises.readFile(abs, "utf8");
7080
+ const config = JSON.parse(maskCommentsInSource(raw));
7081
+ return { config, relFile: filename, raw };
7082
+ }
7083
+ return null;
7084
+ }
7085
+ async function readLinkedProjectName(dir) {
7086
+ const abs = import_node_path36.default.join(dir, ".vercel", "project.json");
7087
+ if (!await exists(abs)) return void 0;
7088
+ const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
7089
+ return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
7090
+ }
7091
+ function routeSource(route) {
7092
+ return route.source ?? route.src;
7093
+ }
7094
+ async function addVercelServices(graph, services, scanPath) {
7095
+ let nodesAdded = 0;
7096
+ let edgesAdded = 0;
7097
+ for (const service of services) {
7098
+ let read = null;
7099
+ let projectName;
7100
+ try {
7101
+ read = await readVercelConfig(service.dir);
7102
+ projectName = await readLinkedProjectName(service.dir);
7103
+ } catch (err) {
7104
+ recordExtractionError("infra vercel", import_node_path36.default.relative(scanPath, service.dir), err);
7105
+ continue;
7106
+ }
7107
+ if (!read && !projectName) continue;
7108
+ const serviceNode = graph.getNodeAttributes(service.node.id);
7109
+ if (serviceNode.platform !== "vercel" || projectName && serviceNode.platformName !== projectName) {
7110
+ const updated = {
7111
+ ...serviceNode,
7112
+ platform: "vercel",
7113
+ ...projectName ? { platformName: projectName } : {}
7114
+ };
7115
+ graph.replaceNodeAttributes(service.node.id, updated);
7116
+ }
7117
+ const anchorId = service.node.id;
7118
+ if (!read) continue;
7119
+ const { config, relFile, raw } = read;
7120
+ const evidenceFile = toPosix2(import_node_path36.default.relative(scanPath, import_node_path36.default.join(service.dir, relFile)));
7121
+ const add = (edgeType, kind, name) => {
7122
+ if (!name) return;
7123
+ const result = emitPlatformResourceEdge(
7124
+ graph,
7125
+ anchorId,
7126
+ edgeType,
7127
+ kind,
7128
+ name,
7129
+ "vercel",
7130
+ evidenceFile,
7131
+ lineContaining(raw, name)
7132
+ );
7133
+ nodesAdded += result.nodesAdded;
7134
+ edgesAdded += result.edgesAdded;
7135
+ };
7136
+ add(import_types26.EdgeType.RUNS_ON, "vercel", "vercel");
7137
+ for (const cron of config.crons ?? []) add(import_types26.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
7138
+ for (const varName of Object.keys(config.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7139
+ for (const varName of Object.keys(config.build?.env ?? {})) add(import_types26.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
7140
+ for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
7141
+ add(import_types26.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
7142
+ }
7143
+ }
7144
+ return { nodesAdded, edgesAdded };
7145
+ }
7146
+
7147
+ // src/extract/infra/railway.ts
7148
+ init_cjs_shims();
7149
+ var import_node_fs21 = require("fs");
7150
+ var import_node_path37 = __toESM(require("path"), 1);
7151
+ var import_smol_toml3 = require("smol-toml");
7152
+ var import_types27 = require("@neat.is/types");
7153
+ var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
7154
+ async function readRailwayConfig(dir) {
7155
+ for (const filename of RAILWAY_FILENAMES) {
7156
+ const abs = import_node_path37.default.join(dir, filename);
7157
+ if (!await exists(abs)) continue;
7158
+ const raw = await import_node_fs21.promises.readFile(abs, "utf8");
7159
+ const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
7160
+ return { config, relFile: filename, raw };
7161
+ }
7162
+ return null;
7163
+ }
7164
+ async function addRailwayServices(graph, services, scanPath) {
7165
+ let nodesAdded = 0;
7166
+ let edgesAdded = 0;
7167
+ for (const service of services) {
7168
+ let read = null;
7169
+ try {
7170
+ read = await readRailwayConfig(service.dir);
7171
+ } catch (err) {
7172
+ recordExtractionError("infra railway", import_node_path37.default.relative(scanPath, service.dir), err);
7173
+ continue;
7174
+ }
7175
+ if (!read) continue;
7176
+ const serviceNode = graph.getNodeAttributes(service.node.id);
7177
+ if (serviceNode.platform !== "railway") {
7178
+ graph.replaceNodeAttributes(service.node.id, { ...serviceNode, platform: "railway" });
7179
+ }
7180
+ const anchorId = service.node.id;
7181
+ const { config, relFile, raw } = read;
7182
+ const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, relFile)));
7183
+ const add = (edgeType, kind, name) => {
7184
+ if (!name) return;
7185
+ const result = emitPlatformResourceEdge(
7186
+ graph,
7187
+ anchorId,
7188
+ edgeType,
7189
+ kind,
7190
+ name,
7191
+ "railway",
7192
+ evidenceFile,
7193
+ lineContaining(raw, name)
7028
7194
  );
7029
7195
  nodesAdded += result.nodesAdded;
7030
7196
  edgesAdded += result.edgesAdded;
7197
+ };
7198
+ add(import_types27.EdgeType.RUNS_ON, "railway", "railway");
7199
+ add(import_types27.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
7200
+ add(import_types27.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
7201
+ }
7202
+ return { nodesAdded, edgesAdded };
7203
+ }
7204
+
7205
+ // src/extract/infra/supabase.ts
7206
+ init_cjs_shims();
7207
+ var import_node_fs22 = require("fs");
7208
+ var import_node_path38 = __toESM(require("path"), 1);
7209
+ var import_smol_toml4 = require("smol-toml");
7210
+ var import_types28 = require("@neat.is/types");
7211
+ async function readSupabaseConfig(dir) {
7212
+ const relFile = import_node_path38.default.join("supabase", "config.toml");
7213
+ const abs = import_node_path38.default.join(dir, relFile);
7214
+ if (!await exists(abs)) return null;
7215
+ const raw = await import_node_fs22.promises.readFile(abs, "utf8");
7216
+ const config = (0, import_smol_toml4.parse)(raw);
7217
+ return { config, relFile, raw };
7218
+ }
7219
+ async function addSupabaseProjects(graph, services, scanPath) {
7220
+ let nodesAdded = 0;
7221
+ let edgesAdded = 0;
7222
+ for (const service of services) {
7223
+ let read = null;
7224
+ try {
7225
+ read = await readSupabaseConfig(service.dir);
7226
+ } catch (err) {
7227
+ recordExtractionError("infra supabase", import_node_path38.default.relative(scanPath, service.dir), err);
7228
+ continue;
7229
+ }
7230
+ if (!read) continue;
7231
+ const { config, relFile, raw } = read;
7232
+ const projectId = typeof config.project_id === "string" ? config.project_id : void 0;
7233
+ const serviceNode = graph.getNodeAttributes(service.node.id);
7234
+ if (serviceNode.platform !== "supabase" || projectId && serviceNode.platformName !== projectId) {
7235
+ graph.replaceNodeAttributes(service.node.id, {
7236
+ ...serviceNode,
7237
+ platform: "supabase",
7238
+ ...projectId ? { platformName: projectId } : {}
7239
+ });
7031
7240
  }
7241
+ const anchorId = service.node.id;
7242
+ const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
7243
+ const add = (edgeType, kind, name) => {
7244
+ if (!name) return;
7245
+ const result = emitPlatformResourceEdge(
7246
+ graph,
7247
+ anchorId,
7248
+ edgeType,
7249
+ kind,
7250
+ name,
7251
+ "supabase",
7252
+ evidenceFile,
7253
+ lineContaining(raw, name)
7254
+ );
7255
+ nodesAdded += result.nodesAdded;
7256
+ edgesAdded += result.edgesAdded;
7257
+ };
7258
+ add(import_types28.EdgeType.RUNS_ON, "supabase", "supabase");
7259
+ for (const fn of Object.keys(config.functions ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "supabase-function", fn);
7260
+ if (config.storage) add(import_types28.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
7261
+ if (config.auth) add(import_types28.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
7032
7262
  }
7033
7263
  return { nodesAdded, edgesAdded };
7034
7264
  }
@@ -7040,24 +7270,27 @@ async function addInfra(graph, scanPath, services) {
7040
7270
  const terraform = await addTerraformResources(graph, scanPath);
7041
7271
  const k8s = await addK8sResources(graph, scanPath);
7042
7272
  const cloudflare = await addCloudflareWorkers(graph, services, scanPath);
7273
+ const vercel = await addVercelServices(graph, services, scanPath);
7274
+ const railway = await addRailwayServices(graph, services, scanPath);
7275
+ const supabase = await addSupabaseProjects(graph, services, scanPath);
7043
7276
  return {
7044
- nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded,
7045
- edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded
7277
+ nodesAdded: compose.nodesAdded + dockerfile.nodesAdded + terraform.nodesAdded + k8s.nodesAdded + cloudflare.nodesAdded + vercel.nodesAdded + railway.nodesAdded + supabase.nodesAdded,
7278
+ edgesAdded: compose.edgesAdded + dockerfile.edgesAdded + terraform.edgesAdded + k8s.edgesAdded + cloudflare.edgesAdded + vercel.edgesAdded + railway.edgesAdded + supabase.edgesAdded
7046
7279
  };
7047
7280
  }
7048
7281
 
7049
7282
  // src/extract/index.ts
7050
- var import_node_path37 = __toESM(require("path"), 1);
7283
+ var import_node_path40 = __toESM(require("path"), 1);
7051
7284
 
7052
7285
  // src/extract/retire.ts
7053
7286
  init_cjs_shims();
7054
- var import_node_fs20 = require("fs");
7055
- var import_node_path36 = __toESM(require("path"), 1);
7056
- var import_types26 = require("@neat.is/types");
7287
+ var import_node_fs23 = require("fs");
7288
+ var import_node_path39 = __toESM(require("path"), 1);
7289
+ var import_types29 = require("@neat.is/types");
7057
7290
  function dropOrphanedFileNodes(graph) {
7058
7291
  const orphans = [];
7059
7292
  graph.forEachNode((id, attrs) => {
7060
- if (attrs.type !== import_types26.NodeType.FileNode) return;
7293
+ if (attrs.type !== import_types29.NodeType.FileNode) return;
7061
7294
  if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
7062
7295
  orphans.push(id);
7063
7296
  }
@@ -7070,14 +7303,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
7070
7303
  const bases = [scanPath, ...serviceDirs];
7071
7304
  graph.forEachEdge((id, attrs) => {
7072
7305
  const edge = attrs;
7073
- if (edge.provenance !== import_types26.Provenance.EXTRACTED) return;
7306
+ if (edge.provenance !== import_types29.Provenance.EXTRACTED) return;
7074
7307
  const evidenceFile = edge.evidence?.file;
7075
7308
  if (!evidenceFile) return;
7076
- if (import_node_path36.default.isAbsolute(evidenceFile)) {
7077
- if (!(0, import_node_fs20.existsSync)(evidenceFile)) toDrop.push(id);
7309
+ if (import_node_path39.default.isAbsolute(evidenceFile)) {
7310
+ if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
7078
7311
  return;
7079
7312
  }
7080
- const found = bases.some((base) => (0, import_node_fs20.existsSync)(import_node_path36.default.join(base, evidenceFile)));
7313
+ const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path39.default.join(base, evidenceFile)));
7081
7314
  if (!found) toDrop.push(id);
7082
7315
  });
7083
7316
  for (const id of toDrop) graph.dropEdge(id);
@@ -7119,7 +7352,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7119
7352
  }
7120
7353
  const droppedEntries = drainDroppedExtracted();
7121
7354
  if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
7122
- const rejectedPath = import_node_path37.default.join(import_node_path37.default.dirname(opts.errorsPath), "rejected.ndjson");
7355
+ const rejectedPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.errorsPath), "rejected.ndjson");
7123
7356
  try {
7124
7357
  await writeRejectedExtracted(droppedEntries, rejectedPath);
7125
7358
  } catch (err) {
@@ -7153,9 +7386,9 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
7153
7386
 
7154
7387
  // src/persist.ts
7155
7388
  init_cjs_shims();
7156
- var import_node_fs21 = require("fs");
7157
- var import_node_path38 = __toESM(require("path"), 1);
7158
- var import_types27 = require("@neat.is/types");
7389
+ var import_node_fs24 = require("fs");
7390
+ var import_node_path41 = __toESM(require("path"), 1);
7391
+ var import_types30 = require("@neat.is/types");
7159
7392
  var SCHEMA_VERSION = 4;
7160
7393
  function migrateV1ToV2(payload) {
7161
7394
  const nodes = payload.graph.nodes;
@@ -7177,12 +7410,12 @@ function migrateV2ToV3(payload) {
7177
7410
  for (const edge of edges) {
7178
7411
  const attrs = edge.attributes;
7179
7412
  if (!attrs || attrs.provenance !== "FRONTIER") continue;
7180
- attrs.provenance = import_types27.Provenance.OBSERVED;
7413
+ attrs.provenance = import_types30.Provenance.OBSERVED;
7181
7414
  const type = typeof attrs.type === "string" ? attrs.type : void 0;
7182
7415
  const source = typeof attrs.source === "string" ? attrs.source : void 0;
7183
7416
  const target = typeof attrs.target === "string" ? attrs.target : void 0;
7184
7417
  if (type && source && target) {
7185
- const newId = (0, import_types27.observedEdgeId)(source, target, type);
7418
+ const newId = (0, import_types30.observedEdgeId)(source, target, type);
7186
7419
  attrs.id = newId;
7187
7420
  if (edge.key) edge.key = newId;
7188
7421
  }
@@ -7191,7 +7424,7 @@ function migrateV2ToV3(payload) {
7191
7424
  return { ...payload, schemaVersion: 3 };
7192
7425
  }
7193
7426
  async function ensureDir(filePath) {
7194
- await import_node_fs21.promises.mkdir(import_node_path38.default.dirname(filePath), { recursive: true });
7427
+ await import_node_fs24.promises.mkdir(import_node_path41.default.dirname(filePath), { recursive: true });
7195
7428
  }
7196
7429
  async function saveGraphToDisk(graph, outPath) {
7197
7430
  await ensureDir(outPath);
@@ -7201,13 +7434,13 @@ async function saveGraphToDisk(graph, outPath) {
7201
7434
  graph: graph.export()
7202
7435
  };
7203
7436
  const tmp = `${outPath}.tmp`;
7204
- await import_node_fs21.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7205
- await import_node_fs21.promises.rename(tmp, outPath);
7437
+ await import_node_fs24.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
7438
+ await import_node_fs24.promises.rename(tmp, outPath);
7206
7439
  }
7207
7440
  async function loadGraphFromDisk(graph, outPath) {
7208
7441
  let raw;
7209
7442
  try {
7210
- raw = await import_node_fs21.promises.readFile(outPath, "utf8");
7443
+ raw = await import_node_fs24.promises.readFile(outPath, "utf8");
7211
7444
  } catch (err) {
7212
7445
  if (err.code === "ENOENT") return;
7213
7446
  throw err;
@@ -7272,23 +7505,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
7272
7505
 
7273
7506
  // src/projects.ts
7274
7507
  init_cjs_shims();
7275
- var import_node_path39 = __toESM(require("path"), 1);
7508
+ var import_node_path42 = __toESM(require("path"), 1);
7276
7509
  function pathsForProject(project, baseDir) {
7277
7510
  if (project === DEFAULT_PROJECT) {
7278
7511
  return {
7279
- snapshotPath: import_node_path39.default.join(baseDir, "graph.json"),
7280
- errorsPath: import_node_path39.default.join(baseDir, "errors.ndjson"),
7281
- staleEventsPath: import_node_path39.default.join(baseDir, "stale-events.ndjson"),
7282
- embeddingsCachePath: import_node_path39.default.join(baseDir, "embeddings.json"),
7283
- policyViolationsPath: import_node_path39.default.join(baseDir, "policy-violations.ndjson")
7512
+ snapshotPath: import_node_path42.default.join(baseDir, "graph.json"),
7513
+ errorsPath: import_node_path42.default.join(baseDir, "errors.ndjson"),
7514
+ staleEventsPath: import_node_path42.default.join(baseDir, "stale-events.ndjson"),
7515
+ embeddingsCachePath: import_node_path42.default.join(baseDir, "embeddings.json"),
7516
+ policyViolationsPath: import_node_path42.default.join(baseDir, "policy-violations.ndjson")
7284
7517
  };
7285
7518
  }
7286
7519
  return {
7287
- snapshotPath: import_node_path39.default.join(baseDir, `${project}.json`),
7288
- errorsPath: import_node_path39.default.join(baseDir, `errors.${project}.ndjson`),
7289
- staleEventsPath: import_node_path39.default.join(baseDir, `stale-events.${project}.ndjson`),
7290
- embeddingsCachePath: import_node_path39.default.join(baseDir, `embeddings.${project}.json`),
7291
- policyViolationsPath: import_node_path39.default.join(baseDir, `policy-violations.${project}.ndjson`)
7520
+ snapshotPath: import_node_path42.default.join(baseDir, `${project}.json`),
7521
+ errorsPath: import_node_path42.default.join(baseDir, `errors.${project}.ndjson`),
7522
+ staleEventsPath: import_node_path42.default.join(baseDir, `stale-events.${project}.ndjson`),
7523
+ embeddingsCachePath: import_node_path42.default.join(baseDir, `embeddings.${project}.json`),
7524
+ policyViolationsPath: import_node_path42.default.join(baseDir, `policy-violations.${project}.ndjson`)
7292
7525
  };
7293
7526
  }
7294
7527
  var Projects = class {
@@ -7326,19 +7559,19 @@ var Projects = class {
7326
7559
  init_cjs_shims();
7327
7560
  var import_fastify = __toESM(require("fastify"), 1);
7328
7561
  var import_cors = __toESM(require("@fastify/cors"), 1);
7329
- var import_types30 = require("@neat.is/types");
7562
+ var import_types33 = require("@neat.is/types");
7330
7563
 
7331
7564
  // src/extend/index.ts
7332
7565
  init_cjs_shims();
7333
- var import_node_fs23 = require("fs");
7334
- var import_node_path41 = __toESM(require("path"), 1);
7566
+ var import_node_fs26 = require("fs");
7567
+ var import_node_path44 = __toESM(require("path"), 1);
7335
7568
  var import_node_os2 = __toESM(require("os"), 1);
7336
7569
  var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
7337
7570
 
7338
7571
  // src/installers/package-manager.ts
7339
7572
  init_cjs_shims();
7340
- var import_node_fs22 = require("fs");
7341
- var import_node_path40 = __toESM(require("path"), 1);
7573
+ var import_node_fs25 = require("fs");
7574
+ var import_node_path43 = __toESM(require("path"), 1);
7342
7575
  var import_node_child_process = require("child_process");
7343
7576
  var LOCKFILE_PRIORITY = [
7344
7577
  { lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
@@ -7353,29 +7586,29 @@ var LOCKFILE_PRIORITY = [
7353
7586
  var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
7354
7587
  async function exists2(p) {
7355
7588
  try {
7356
- await import_node_fs22.promises.access(p);
7589
+ await import_node_fs25.promises.access(p);
7357
7590
  return true;
7358
7591
  } catch {
7359
7592
  return false;
7360
7593
  }
7361
7594
  }
7362
7595
  async function detectPackageManager(serviceDir) {
7363
- let dir = import_node_path40.default.resolve(serviceDir);
7596
+ let dir = import_node_path43.default.resolve(serviceDir);
7364
7597
  const stops = /* @__PURE__ */ new Set();
7365
7598
  for (let i = 0; i < 64; i++) {
7366
7599
  if (stops.has(dir)) break;
7367
7600
  stops.add(dir);
7368
7601
  for (const candidate of LOCKFILE_PRIORITY) {
7369
- const lockPath = import_node_path40.default.join(dir, candidate.lockfile);
7602
+ const lockPath = import_node_path43.default.join(dir, candidate.lockfile);
7370
7603
  if (await exists2(lockPath)) {
7371
7604
  return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
7372
7605
  }
7373
7606
  }
7374
- const parent = import_node_path40.default.dirname(dir);
7607
+ const parent = import_node_path43.default.dirname(dir);
7375
7608
  if (parent === dir) break;
7376
7609
  dir = parent;
7377
7610
  }
7378
- return { pm: "npm", cwd: import_node_path40.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7611
+ return { pm: "npm", cwd: import_node_path43.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
7379
7612
  }
7380
7613
  async function runPackageManagerInstall(cmd) {
7381
7614
  return new Promise((resolve) => {
@@ -7417,30 +7650,30 @@ ${err.message}`
7417
7650
  // src/extend/index.ts
7418
7651
  async function fileExists2(p) {
7419
7652
  try {
7420
- await import_node_fs23.promises.access(p);
7653
+ await import_node_fs26.promises.access(p);
7421
7654
  return true;
7422
7655
  } catch {
7423
7656
  return false;
7424
7657
  }
7425
7658
  }
7426
7659
  async function readPackageJson(scanPath) {
7427
- const pkgPath = import_node_path41.default.join(scanPath, "package.json");
7428
- const raw = await import_node_fs23.promises.readFile(pkgPath, "utf8");
7660
+ const pkgPath = import_node_path44.default.join(scanPath, "package.json");
7661
+ const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
7429
7662
  return JSON.parse(raw);
7430
7663
  }
7431
7664
  async function findHookFiles(scanPath) {
7432
- const entries = await import_node_fs23.promises.readdir(scanPath);
7665
+ const entries = await import_node_fs26.promises.readdir(scanPath);
7433
7666
  return entries.filter(
7434
7667
  (e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
7435
7668
  ).sort();
7436
7669
  }
7437
7670
  function extendLogPath() {
7438
- return process.env.NEAT_EXTEND_LOG ?? import_node_path41.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7671
+ return process.env.NEAT_EXTEND_LOG ?? import_node_path44.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
7439
7672
  }
7440
7673
  async function appendExtendLog(entry2) {
7441
7674
  const logPath = extendLogPath();
7442
- await import_node_fs23.promises.mkdir(import_node_path41.default.dirname(logPath), { recursive: true });
7443
- await import_node_fs23.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7675
+ await import_node_fs26.promises.mkdir(import_node_path44.default.dirname(logPath), { recursive: true });
7676
+ await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
7444
7677
  }
7445
7678
  function splicedContent(fileContent, snippet2) {
7446
7679
  if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
@@ -7498,7 +7731,7 @@ function lookupInstrumentation(library, installedVersion) {
7498
7731
  }
7499
7732
  async function describeProjectInstrumentation(ctx) {
7500
7733
  const hookFiles = await findHookFiles(ctx.scanPath);
7501
- const envNeat = await fileExists2(import_node_path41.default.join(ctx.scanPath, ".env.neat"));
7734
+ const envNeat = await fileExists2(import_node_path44.default.join(ctx.scanPath, ".env.neat"));
7502
7735
  const registryInstrPackages = new Set(
7503
7736
  (0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
7504
7737
  );
@@ -7520,31 +7753,31 @@ async function applyExtension(ctx, args, options) {
7520
7753
  );
7521
7754
  }
7522
7755
  for (const file of hookFiles) {
7523
- const content = await import_node_fs23.promises.readFile(import_node_path41.default.join(ctx.scanPath, file), "utf8");
7756
+ const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
7524
7757
  if (content.includes(args.registration_snippet)) {
7525
7758
  return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
7526
7759
  }
7527
7760
  }
7528
7761
  const primaryFile = hookFiles[0];
7529
- const primaryPath = import_node_path41.default.join(ctx.scanPath, primaryFile);
7762
+ const primaryPath = import_node_path44.default.join(ctx.scanPath, primaryFile);
7530
7763
  const filesTouched = [];
7531
7764
  const depsAdded = [];
7532
- const pkgPath = import_node_path41.default.join(ctx.scanPath, "package.json");
7765
+ const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
7533
7766
  const pkg = await readPackageJson(ctx.scanPath);
7534
7767
  if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
7535
7768
  pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
7536
- await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7769
+ await import_node_fs26.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7537
7770
  filesTouched.push("package.json");
7538
7771
  depsAdded.push(`${args.instrumentation_package}@${args.version}`);
7539
7772
  }
7540
- const hookContent = await import_node_fs23.promises.readFile(primaryPath, "utf8");
7773
+ const hookContent = await import_node_fs26.promises.readFile(primaryPath, "utf8");
7541
7774
  const patched = splicedContent(hookContent, args.registration_snippet);
7542
7775
  if (!patched) {
7543
7776
  throw new Error(
7544
7777
  `Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
7545
7778
  );
7546
7779
  }
7547
- await import_node_fs23.promises.writeFile(primaryPath, patched, "utf8");
7780
+ await import_node_fs26.promises.writeFile(primaryPath, patched, "utf8");
7548
7781
  filesTouched.push(primaryFile);
7549
7782
  const cmd = await detectPackageManager(ctx.scanPath);
7550
7783
  const installer = options?.runInstall ?? runPackageManagerInstall;
@@ -7575,7 +7808,7 @@ async function dryRunExtension(ctx, args) {
7575
7808
  };
7576
7809
  }
7577
7810
  for (const file of hookFiles) {
7578
- const content = await import_node_fs23.promises.readFile(import_node_path41.default.join(ctx.scanPath, file), "utf8");
7811
+ const content = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, file), "utf8");
7579
7812
  if (content.includes(args.registration_snippet)) {
7580
7813
  return {
7581
7814
  library: args.library,
@@ -7597,7 +7830,7 @@ async function dryRunExtension(ctx, args) {
7597
7830
  depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
7598
7831
  filesTouched.push("package.json");
7599
7832
  }
7600
- const hookContent = await import_node_fs23.promises.readFile(import_node_path41.default.join(ctx.scanPath, primaryFile), "utf8");
7833
+ const hookContent = await import_node_fs26.promises.readFile(import_node_path44.default.join(ctx.scanPath, primaryFile), "utf8");
7601
7834
  const patched = splicedContent(hookContent, args.registration_snippet);
7602
7835
  if (patched) {
7603
7836
  filesTouched.push(primaryFile);
@@ -7612,28 +7845,28 @@ async function rollbackExtension(ctx, args) {
7612
7845
  if (!await fileExists2(logPath)) {
7613
7846
  return { undone: false, message: "no apply found for library" };
7614
7847
  }
7615
- const raw = await import_node_fs23.promises.readFile(logPath, "utf8");
7848
+ const raw = await import_node_fs26.promises.readFile(logPath, "utf8");
7616
7849
  const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
7617
7850
  const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
7618
7851
  if (!match) {
7619
7852
  return { undone: false, message: "no apply found for library" };
7620
7853
  }
7621
- const pkgPath = import_node_path41.default.join(ctx.scanPath, "package.json");
7854
+ const pkgPath = import_node_path44.default.join(ctx.scanPath, "package.json");
7622
7855
  if (await fileExists2(pkgPath)) {
7623
7856
  const pkg = await readPackageJson(ctx.scanPath);
7624
7857
  if (pkg.dependencies?.[match.instrumentation_package]) {
7625
7858
  const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
7626
7859
  pkg.dependencies = rest;
7627
- await import_node_fs23.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7860
+ await import_node_fs26.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
7628
7861
  }
7629
7862
  }
7630
7863
  const hookFiles = await findHookFiles(ctx.scanPath);
7631
7864
  for (const file of hookFiles) {
7632
- const filePath = import_node_path41.default.join(ctx.scanPath, file);
7633
- const content = await import_node_fs23.promises.readFile(filePath, "utf8");
7865
+ const filePath = import_node_path44.default.join(ctx.scanPath, file);
7866
+ const content = await import_node_fs26.promises.readFile(filePath, "utf8");
7634
7867
  if (content.includes(match.registration_snippet)) {
7635
7868
  const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
7636
- await import_node_fs23.promises.writeFile(filePath, filtered, "utf8");
7869
+ await import_node_fs26.promises.writeFile(filePath, filtered, "utf8");
7637
7870
  break;
7638
7871
  }
7639
7872
  }
@@ -7645,7 +7878,7 @@ async function rollbackExtension(ctx, args) {
7645
7878
 
7646
7879
  // src/divergences.ts
7647
7880
  init_cjs_shims();
7648
- var import_types28 = require("@neat.is/types");
7881
+ var import_types31 = require("@neat.is/types");
7649
7882
  function bucketKey(source, target, type) {
7650
7883
  return `${type}|${source}|${target}`;
7651
7884
  }
@@ -7653,22 +7886,22 @@ function bucketEdges(graph) {
7653
7886
  const buckets2 = /* @__PURE__ */ new Map();
7654
7887
  graph.forEachEdge((id, attrs) => {
7655
7888
  const e = attrs;
7656
- const parsed = (0, import_types28.parseEdgeId)(id);
7889
+ const parsed = (0, import_types31.parseEdgeId)(id);
7657
7890
  const provenance = parsed?.provenance ?? e.provenance;
7658
7891
  const key = bucketKey(e.source, e.target, e.type);
7659
7892
  const cur = buckets2.get(key) ?? { source: e.source, target: e.target, type: e.type };
7660
7893
  switch (provenance) {
7661
- case import_types28.Provenance.EXTRACTED:
7894
+ case import_types31.Provenance.EXTRACTED:
7662
7895
  cur.extracted = e;
7663
7896
  break;
7664
- case import_types28.Provenance.OBSERVED:
7897
+ case import_types31.Provenance.OBSERVED:
7665
7898
  cur.observed = e;
7666
7899
  break;
7667
- case import_types28.Provenance.INFERRED:
7900
+ case import_types31.Provenance.INFERRED:
7668
7901
  cur.inferred = e;
7669
7902
  break;
7670
7903
  default:
7671
- if (e.provenance === import_types28.Provenance.STALE) cur.stale = e;
7904
+ if (e.provenance === import_types31.Provenance.STALE) cur.stale = e;
7672
7905
  }
7673
7906
  buckets2.set(key, cur);
7674
7907
  });
@@ -7677,12 +7910,12 @@ function bucketEdges(graph) {
7677
7910
  function nodeIsFrontier(graph, nodeId) {
7678
7911
  if (!graph.hasNode(nodeId)) return false;
7679
7912
  const attrs = graph.getNodeAttributes(nodeId);
7680
- return attrs.type === import_types28.NodeType.FrontierNode;
7913
+ return attrs.type === import_types31.NodeType.FrontierNode;
7681
7914
  }
7682
7915
  function nodeIsWebsocketChannel(graph, nodeId) {
7683
7916
  if (!graph.hasNode(nodeId)) return false;
7684
7917
  const attrs = graph.getNodeAttributes(nodeId);
7685
- return attrs.type === import_types28.NodeType.WebSocketChannelNode;
7918
+ return attrs.type === import_types31.NodeType.WebSocketChannelNode;
7686
7919
  }
7687
7920
  function clampConfidence(n) {
7688
7921
  if (!Number.isFinite(n)) return 0;
@@ -7702,14 +7935,14 @@ function gradedConfidence(edge) {
7702
7935
  return clampConfidence(confidenceForEdge(edge));
7703
7936
  }
7704
7937
  var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
7705
- import_types28.EdgeType.CALLS,
7706
- import_types28.EdgeType.CONNECTS_TO,
7707
- import_types28.EdgeType.PUBLISHES_TO,
7708
- import_types28.EdgeType.CONSUMES_FROM
7938
+ import_types31.EdgeType.CALLS,
7939
+ import_types31.EdgeType.CONNECTS_TO,
7940
+ import_types31.EdgeType.PUBLISHES_TO,
7941
+ import_types31.EdgeType.CONSUMES_FROM
7709
7942
  ]);
7710
7943
  function detectMissingDivergences(graph, bucket) {
7711
7944
  const out = [];
7712
- if (bucket.type === import_types28.EdgeType.CONTAINS) return out;
7945
+ if (bucket.type === import_types31.EdgeType.CONTAINS) return out;
7713
7946
  if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
7714
7947
  if (!nodeIsFrontier(graph, bucket.target)) {
7715
7948
  out.push({
@@ -7750,7 +7983,7 @@ function declaredHostFor(svc) {
7750
7983
  function hasExtractedConfiguredBy(graph, svcId) {
7751
7984
  for (const edgeId of graph.outboundEdges(svcId)) {
7752
7985
  const e = graph.getEdgeAttributes(edgeId);
7753
- if (e.type === import_types28.EdgeType.CONFIGURED_BY && e.provenance === import_types28.Provenance.EXTRACTED) {
7986
+ if (e.type === import_types31.EdgeType.CONFIGURED_BY && e.provenance === import_types31.Provenance.EXTRACTED) {
7754
7987
  return true;
7755
7988
  }
7756
7989
  }
@@ -7763,10 +7996,10 @@ function detectHostMismatch(graph, svcId, svc) {
7763
7996
  const out = [];
7764
7997
  for (const edgeId of graph.outboundEdges(svcId)) {
7765
7998
  const edge = graph.getEdgeAttributes(edgeId);
7766
- if (edge.type !== import_types28.EdgeType.CONNECTS_TO) continue;
7767
- if (edge.provenance !== import_types28.Provenance.OBSERVED) continue;
7999
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8000
+ if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
7768
8001
  const target = graph.getNodeAttributes(edge.target);
7769
- if (target.type !== import_types28.NodeType.DatabaseNode) continue;
8002
+ if (target.type !== import_types31.NodeType.DatabaseNode) continue;
7770
8003
  const observedHost = target.host?.trim();
7771
8004
  if (!observedHost) continue;
7772
8005
  if (observedHost === declaredHost) continue;
@@ -7788,10 +8021,10 @@ function detectCompatDivergences(graph, svcId, svc) {
7788
8021
  const deps = svc.dependencies ?? {};
7789
8022
  for (const edgeId of graph.outboundEdges(svcId)) {
7790
8023
  const edge = graph.getEdgeAttributes(edgeId);
7791
- if (edge.type !== import_types28.EdgeType.CONNECTS_TO) continue;
7792
- if (edge.provenance !== import_types28.Provenance.OBSERVED) continue;
8024
+ if (edge.type !== import_types31.EdgeType.CONNECTS_TO) continue;
8025
+ if (edge.provenance !== import_types31.Provenance.OBSERVED) continue;
7793
8026
  const target = graph.getNodeAttributes(edge.target);
7794
- if (target.type !== import_types28.NodeType.DatabaseNode) continue;
8027
+ if (target.type !== import_types31.NodeType.DatabaseNode) continue;
7795
8028
  for (const pair of compatPairs()) {
7796
8029
  if (pair.engine !== target.engine) continue;
7797
8030
  const declared = deps[pair.driver];
@@ -7850,7 +8083,7 @@ function suppressHostMismatchHalves(all) {
7850
8083
  for (const d of all) {
7851
8084
  if (d.type !== "host-mismatch") continue;
7852
8085
  observedHalf.add(`${d.source}->${d.target}`);
7853
- declaredHalf.add((0, import_types28.databaseId)(d.extractedHost));
8086
+ declaredHalf.add((0, import_types31.databaseId)(d.extractedHost));
7854
8087
  }
7855
8088
  if (observedHalf.size === 0) return all;
7856
8089
  return all.filter((d) => {
@@ -7869,7 +8102,7 @@ function computeDivergences(graph, opts = {}) {
7869
8102
  }
7870
8103
  graph.forEachNode((nodeId, attrs) => {
7871
8104
  const n = attrs;
7872
- if (n.type !== import_types28.NodeType.ServiceNode) return;
8105
+ if (n.type !== import_types31.NodeType.ServiceNode) return;
7873
8106
  const svc = n;
7874
8107
  for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
7875
8108
  for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
@@ -7903,7 +8136,7 @@ function computeDivergences(graph, opts = {}) {
7903
8136
  if (a.source !== b.source) return a.source.localeCompare(b.source);
7904
8137
  return a.target.localeCompare(b.target);
7905
8138
  });
7906
- return import_types28.DivergenceResultSchema.parse({
8139
+ return import_types31.DivergenceResultSchema.parse({
7907
8140
  divergences: filtered,
7908
8141
  totalAffected: filtered.length,
7909
8142
  computedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -7957,7 +8190,7 @@ function queryLogEntries(opts) {
7957
8190
 
7958
8191
  // src/diff.ts
7959
8192
  init_cjs_shims();
7960
- var import_node_fs24 = require("fs");
8193
+ var import_node_fs27 = require("fs");
7961
8194
  async function loadSnapshotForDiff(target) {
7962
8195
  if (/^https?:\/\//i.test(target)) {
7963
8196
  const res = await fetch(target);
@@ -7966,7 +8199,7 @@ async function loadSnapshotForDiff(target) {
7966
8199
  }
7967
8200
  return await res.json();
7968
8201
  }
7969
- const raw = await import_node_fs24.promises.readFile(target, "utf8");
8202
+ const raw = await import_node_fs27.promises.readFile(target, "utf8");
7970
8203
  return JSON.parse(raw);
7971
8204
  }
7972
8205
  function indexEntries(entries) {
@@ -8034,25 +8267,25 @@ function canonicalJson(value) {
8034
8267
 
8035
8268
  // src/registry.ts
8036
8269
  init_cjs_shims();
8037
- var import_node_fs25 = require("fs");
8270
+ var import_node_fs28 = require("fs");
8038
8271
  var import_node_os3 = __toESM(require("os"), 1);
8039
- var import_node_path42 = __toESM(require("path"), 1);
8040
- var import_types29 = require("@neat.is/types");
8272
+ var import_node_path45 = __toESM(require("path"), 1);
8273
+ var import_types32 = require("@neat.is/types");
8041
8274
  var LOCK_TIMEOUT_MS = 5e3;
8042
8275
  var LOCK_RETRY_MS = 50;
8043
8276
  function neatHome() {
8044
8277
  const override = process.env.NEAT_HOME;
8045
- if (override && override.length > 0) return import_node_path42.default.resolve(override);
8046
- return import_node_path42.default.join(import_node_os3.default.homedir(), ".neat");
8278
+ if (override && override.length > 0) return import_node_path45.default.resolve(override);
8279
+ return import_node_path45.default.join(import_node_os3.default.homedir(), ".neat");
8047
8280
  }
8048
8281
  function registryPath() {
8049
- return import_node_path42.default.join(neatHome(), "projects.json");
8282
+ return import_node_path45.default.join(neatHome(), "projects.json");
8050
8283
  }
8051
8284
  function registryLockPath() {
8052
- return import_node_path42.default.join(neatHome(), "projects.json.lock");
8285
+ return import_node_path45.default.join(neatHome(), "projects.json.lock");
8053
8286
  }
8054
8287
  function daemonPidPath() {
8055
- return import_node_path42.default.join(neatHome(), "neatd.pid");
8288
+ return import_node_path45.default.join(neatHome(), "neatd.pid");
8056
8289
  }
8057
8290
  function isPidAliveDefault(pid) {
8058
8291
  try {
@@ -8064,7 +8297,7 @@ function isPidAliveDefault(pid) {
8064
8297
  }
8065
8298
  async function readPidFile(file) {
8066
8299
  try {
8067
- const raw = await import_node_fs25.promises.readFile(file, "utf8");
8300
+ const raw = await import_node_fs28.promises.readFile(file, "utf8");
8068
8301
  const pid = Number.parseInt(raw.trim(), 10);
8069
8302
  return Number.isInteger(pid) && pid > 0 ? pid : void 0;
8070
8303
  } catch {
@@ -8112,24 +8345,24 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
8112
8345
  }
8113
8346
  }
8114
8347
  async function writeAtomically(target, contents) {
8115
- await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(target), { recursive: true });
8348
+ await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(target), { recursive: true });
8116
8349
  const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
8117
- const fd = await import_node_fs25.promises.open(tmp, "w");
8350
+ const fd = await import_node_fs28.promises.open(tmp, "w");
8118
8351
  try {
8119
8352
  await fd.writeFile(contents, "utf8");
8120
8353
  await fd.sync();
8121
8354
  } finally {
8122
8355
  await fd.close();
8123
8356
  }
8124
- await import_node_fs25.promises.rename(tmp, target);
8357
+ await import_node_fs28.promises.rename(tmp, target);
8125
8358
  }
8126
8359
  async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
8127
8360
  const deadline = Date.now() + timeoutMs;
8128
- await import_node_fs25.promises.mkdir(import_node_path42.default.dirname(lockPath), { recursive: true });
8361
+ await import_node_fs28.promises.mkdir(import_node_path45.default.dirname(lockPath), { recursive: true });
8129
8362
  let probedHolder = false;
8130
8363
  while (true) {
8131
8364
  try {
8132
- const fd = await import_node_fs25.promises.open(lockPath, "wx");
8365
+ const fd = await import_node_fs28.promises.open(lockPath, "wx");
8133
8366
  try {
8134
8367
  await fd.writeFile(`${process.pid}
8135
8368
  `, "utf8");
@@ -8154,7 +8387,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
8154
8387
  }
8155
8388
  }
8156
8389
  async function releaseLock(lockPath) {
8157
- await import_node_fs25.promises.unlink(lockPath).catch(() => {
8390
+ await import_node_fs28.promises.unlink(lockPath).catch(() => {
8158
8391
  });
8159
8392
  }
8160
8393
  async function withLock(fn) {
@@ -8170,7 +8403,7 @@ async function readRegistry() {
8170
8403
  const file = registryPath();
8171
8404
  let raw;
8172
8405
  try {
8173
- raw = await import_node_fs25.promises.readFile(file, "utf8");
8406
+ raw = await import_node_fs28.promises.readFile(file, "utf8");
8174
8407
  } catch (err) {
8175
8408
  if (err.code === "ENOENT") {
8176
8409
  return { version: 1, projects: [] };
@@ -8178,10 +8411,10 @@ async function readRegistry() {
8178
8411
  throw err;
8179
8412
  }
8180
8413
  const parsed = JSON.parse(raw);
8181
- return import_types29.RegistryFileSchema.parse(parsed);
8414
+ return import_types32.RegistryFileSchema.parse(parsed);
8182
8415
  }
8183
8416
  async function writeRegistry(reg) {
8184
- const validated = import_types29.RegistryFileSchema.parse(reg);
8417
+ const validated = import_types32.RegistryFileSchema.parse(reg);
8185
8418
  await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
8186
8419
  }
8187
8420
  async function getProject(name) {
@@ -8225,7 +8458,7 @@ function pruneTtlMs() {
8225
8458
  }
8226
8459
  async function statPathStatus(p) {
8227
8460
  try {
8228
- const stat = await import_node_fs25.promises.stat(p);
8461
+ const stat = await import_node_fs28.promises.stat(p);
8229
8462
  return stat.isDirectory() ? "present" : "unknown";
8230
8463
  } catch (err) {
8231
8464
  return err.code === "ENOENT" ? "gone" : "unknown";
@@ -8322,65 +8555,274 @@ data: ${JSON.stringify(envelope.payload)}
8322
8555
 
8323
8556
  // src/api.ts
8324
8557
  init_auth();
8325
- function serializeGraph(graph) {
8326
- const nodes = [];
8327
- graph.forEachNode((_id, attrs) => {
8328
- nodes.push(attrs);
8329
- });
8330
- const edges = [];
8331
- graph.forEachEdge((_id, attrs) => {
8332
- edges.push(attrs);
8333
- });
8334
- return { nodes, edges };
8335
- }
8336
- function projectFromReq(req2, singleProject) {
8337
- const params = req2.params;
8338
- const named = params.project;
8339
- if (singleProject) {
8340
- return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
8558
+
8559
+ // src/connectors-config.ts
8560
+ init_cjs_shims();
8561
+ var import_node_os4 = __toESM(require("os"), 1);
8562
+ var import_node_path46 = __toESM(require("path"), 1);
8563
+ var import_node_fs29 = require("fs");
8564
+ var CONNECTORS_CONFIG_VERSION = 1;
8565
+ var EnvRefUnsetError = class extends Error {
8566
+ ref;
8567
+ varName;
8568
+ constructor(ref, varName) {
8569
+ super(`${ref} is unset`);
8570
+ this.name = "EnvRefUnsetError";
8571
+ this.ref = ref;
8572
+ this.varName = varName;
8341
8573
  }
8342
- return named ?? DEFAULT_PROJECT;
8574
+ };
8575
+ function neatHome2() {
8576
+ const override = process.env.NEAT_HOME;
8577
+ if (override && override.length > 0) return import_node_path46.default.resolve(override);
8578
+ return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
8343
8579
  }
8344
- function resolveProject(registry, req2, reply, bootstrap, singleProject) {
8345
- const name = projectFromReq(req2, singleProject);
8346
- const ctx = registry.get(name);
8347
- if (!ctx) {
8348
- const phase = bootstrap?.status(name);
8349
- if (phase === "bootstrapping") {
8350
- void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
8351
- return null;
8352
- }
8353
- if (phase === "broken") {
8354
- void reply.code(503).send({ ready: false, project: name, status: "broken" });
8355
- return null;
8580
+ function connectorsConfigPath(home = neatHome2()) {
8581
+ return import_node_path46.default.join(home, "connectors.json");
8582
+ }
8583
+ var MODE_MASK_LOOSER_THAN_0600 = 63;
8584
+ async function warnIfModeLooserThan0600(file) {
8585
+ if (process.platform === "win32") return;
8586
+ try {
8587
+ const stat = await import_node_fs29.promises.stat(file);
8588
+ if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
8589
+ const mode = (stat.mode & 511).toString(8).padStart(3, "0");
8590
+ console.warn(
8591
+ `[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
8592
+ );
8356
8593
  }
8357
- void reply.code(404).send({ error: "project not found", project: name });
8358
- return null;
8594
+ } catch {
8359
8595
  }
8360
- return ctx;
8361
8596
  }
8362
- function buildLegacyRegistry(opts) {
8363
- if (opts.projects) return opts.projects;
8364
- if (!opts.graph) {
8365
- throw new Error("buildApi: either `projects` or `graph` must be provided");
8597
+ async function readConnectorsConfig(home = neatHome2()) {
8598
+ const file = connectorsConfigPath(home);
8599
+ let raw;
8600
+ try {
8601
+ raw = await import_node_fs29.promises.readFile(file, "utf8");
8602
+ } catch (err) {
8603
+ if (err.code === "ENOENT") {
8604
+ return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
8605
+ }
8606
+ throw err;
8366
8607
  }
8367
- const registry = new Projects();
8368
- const paths = pathsForProject(DEFAULT_PROJECT, "");
8369
- registry.set(DEFAULT_PROJECT, {
8370
- graph: opts.graph,
8371
- scanPath: opts.scanPath,
8372
- paths: {
8373
- snapshotPath: paths.snapshotPath,
8374
- errorsPath: opts.errorsPath ?? paths.errorsPath,
8375
- staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,
8376
- embeddingsCachePath: paths.embeddingsCachePath,
8377
- policyViolationsPath: paths.policyViolationsPath
8378
- },
8379
- searchIndex: opts.searchIndex
8380
- });
8381
- return registry;
8608
+ await warnIfModeLooserThan0600(file);
8609
+ let parsed;
8610
+ try {
8611
+ parsed = JSON.parse(raw);
8612
+ } catch (err) {
8613
+ throw new Error(`${file} is not valid JSON: ${err.message}`);
8614
+ }
8615
+ return validateConfig(parsed, file);
8382
8616
  }
8383
- function registerRoutes(scope, ctx) {
8617
+ function validateConfig(parsed, file) {
8618
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
8619
+ throw new Error(`${file} must be a JSON object with a "connectors" array`);
8620
+ }
8621
+ const obj = parsed;
8622
+ const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
8623
+ if (typeof version !== "number" || !Number.isInteger(version)) {
8624
+ throw new Error(`${file}: "version" must be an integer`);
8625
+ }
8626
+ const rawConnectors = obj.connectors;
8627
+ if (!Array.isArray(rawConnectors)) {
8628
+ throw new Error(`${file}: "connectors" must be an array`);
8629
+ }
8630
+ const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
8631
+ return { version, connectors };
8632
+ }
8633
+ function validateEntry(entry2, index, file) {
8634
+ const where = `${file}: connectors[${index}]`;
8635
+ if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
8636
+ throw new Error(`${where} must be an object`);
8637
+ }
8638
+ const e = entry2;
8639
+ const id = e.id;
8640
+ if (typeof id !== "string" || id.length === 0) {
8641
+ throw new Error(`${where}.id must be a non-empty string`);
8642
+ }
8643
+ const provider = e.provider;
8644
+ if (typeof provider !== "string" || provider.length === 0) {
8645
+ throw new Error(`${where}.provider must be a non-empty string`);
8646
+ }
8647
+ if (e.project !== void 0 && typeof e.project !== "string") {
8648
+ throw new Error(`${where}.project must be a string when present`);
8649
+ }
8650
+ const credential = validateCredentialRef(e.credential, `${where}.credential`);
8651
+ let options;
8652
+ if (e.options !== void 0) {
8653
+ if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
8654
+ throw new Error(`${where}.options must be an object when present`);
8655
+ }
8656
+ options = e.options;
8657
+ }
8658
+ return {
8659
+ id,
8660
+ provider,
8661
+ ...typeof e.project === "string" ? { project: e.project } : {},
8662
+ credential,
8663
+ ...options ? { options } : {}
8664
+ };
8665
+ }
8666
+ function validateCredentialRef(raw, where) {
8667
+ if (typeof raw === "string") {
8668
+ if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
8669
+ return raw;
8670
+ }
8671
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
8672
+ const fields = raw;
8673
+ const keys = Object.keys(fields);
8674
+ if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
8675
+ for (const key of keys) {
8676
+ const v = fields[key];
8677
+ if (typeof v !== "string" || v.length === 0) {
8678
+ throw new Error(`${where}.${key} must be a non-empty string`);
8679
+ }
8680
+ }
8681
+ return fields;
8682
+ }
8683
+ throw new Error(`${where} must be a string or an object of strings`);
8684
+ }
8685
+ function resolveCredential(ref, env = process.env) {
8686
+ if (typeof ref === "string") {
8687
+ return { kind: "single", value: resolveRef(ref, env) };
8688
+ }
8689
+ const fields = {};
8690
+ for (const [key, value] of Object.entries(ref)) {
8691
+ fields[key] = resolveRef(value, env);
8692
+ }
8693
+ return { kind: "fields", fields };
8694
+ }
8695
+ function resolveRef(value, env) {
8696
+ if (value.length > 1 && value.startsWith("$")) {
8697
+ const varName = value.slice(1);
8698
+ const resolved = env[varName];
8699
+ if (resolved === void 0 || resolved.length === 0) {
8700
+ throw new EnvRefUnsetError(value, varName);
8701
+ }
8702
+ return resolved;
8703
+ }
8704
+ return value;
8705
+ }
8706
+ function connectorMatchesProject(entry2, project) {
8707
+ return entry2.project === void 0 || entry2.project === project;
8708
+ }
8709
+ function isEnvRef(value) {
8710
+ return value.length > 1 && value.startsWith("$");
8711
+ }
8712
+ function redactCredentialRef(ref) {
8713
+ const one = (value) => isEnvRef(value) ? value : "****";
8714
+ if (typeof ref === "string") return one(ref);
8715
+ const out = {};
8716
+ for (const [key, value] of Object.entries(ref)) out[key] = one(value);
8717
+ return out;
8718
+ }
8719
+
8720
+ // src/connectors/status.ts
8721
+ init_cjs_shims();
8722
+ var CONNECTOR_STALE_THRESHOLD_MS = 5 * 6e4;
8723
+ var records = /* @__PURE__ */ new Map();
8724
+ var MAX_ERROR_LEN = 200;
8725
+ function sanitizePollError(err) {
8726
+ const msg = err instanceof Error ? err.message : String(err);
8727
+ const collapsed = msg.replace(/\s+/g, " ").trim();
8728
+ return collapsed.length > MAX_ERROR_LEN ? `${collapsed.slice(0, MAX_ERROR_LEN - 1)}\u2026` : collapsed;
8729
+ }
8730
+ function recordConnectorPoll(id, tick) {
8731
+ const prev = records.get(id);
8732
+ records.set(id, {
8733
+ lastPollAt: tick.at,
8734
+ lastOutcome: tick.outcome,
8735
+ lastError: tick.outcome === "error" ? tick.error ?? null : null,
8736
+ signalsLastPoll: tick.signalsLastPoll ?? 0,
8737
+ lastOkAt: tick.outcome === "ok" ? tick.at : prev?.lastOkAt ?? null
8738
+ });
8739
+ }
8740
+ function deriveState(rec, now, thresholdMs) {
8741
+ if (rec.lastOutcome === "error") return "error";
8742
+ const okAt = rec.lastOkAt ? Date.parse(rec.lastOkAt) : NaN;
8743
+ if (!Number.isNaN(okAt) && now - okAt > thresholdMs) return "stale";
8744
+ return "healthy";
8745
+ }
8746
+ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_THRESHOLD_MS) {
8747
+ const rec = records.get(id);
8748
+ if (!rec) {
8749
+ return {
8750
+ state: "idle",
8751
+ lastPollAt: null,
8752
+ lastOutcome: null,
8753
+ lastError: null,
8754
+ signalsLastPoll: 0
8755
+ };
8756
+ }
8757
+ return {
8758
+ state: deriveState(rec, now, thresholdMs),
8759
+ lastPollAt: rec.lastPollAt,
8760
+ lastOutcome: rec.lastOutcome,
8761
+ lastError: rec.lastError,
8762
+ signalsLastPoll: rec.signalsLastPoll
8763
+ };
8764
+ }
8765
+
8766
+ // src/api.ts
8767
+ function serializeGraph(graph) {
8768
+ const nodes = [];
8769
+ graph.forEachNode((_id, attrs) => {
8770
+ nodes.push(attrs);
8771
+ });
8772
+ const edges = [];
8773
+ graph.forEachEdge((_id, attrs) => {
8774
+ edges.push(attrs);
8775
+ });
8776
+ return { nodes, edges };
8777
+ }
8778
+ function projectFromReq(req2, singleProject) {
8779
+ const params = req2.params;
8780
+ const named = params.project;
8781
+ if (singleProject) {
8782
+ return named === void 0 || named === DEFAULT_PROJECT ? singleProject : named;
8783
+ }
8784
+ return named ?? DEFAULT_PROJECT;
8785
+ }
8786
+ function resolveProject(registry, req2, reply, bootstrap, singleProject) {
8787
+ const name = projectFromReq(req2, singleProject);
8788
+ const ctx = registry.get(name);
8789
+ if (!ctx) {
8790
+ const phase = bootstrap?.status(name);
8791
+ if (phase === "bootstrapping") {
8792
+ void reply.code(503).send({ ready: false, project: name, status: "bootstrapping" });
8793
+ return null;
8794
+ }
8795
+ if (phase === "broken") {
8796
+ void reply.code(503).send({ ready: false, project: name, status: "broken" });
8797
+ return null;
8798
+ }
8799
+ void reply.code(404).send({ error: "project not found", project: name });
8800
+ return null;
8801
+ }
8802
+ return ctx;
8803
+ }
8804
+ function buildLegacyRegistry(opts) {
8805
+ if (opts.projects) return opts.projects;
8806
+ if (!opts.graph) {
8807
+ throw new Error("buildApi: either `projects` or `graph` must be provided");
8808
+ }
8809
+ const registry = new Projects();
8810
+ const paths = pathsForProject(DEFAULT_PROJECT, "");
8811
+ registry.set(DEFAULT_PROJECT, {
8812
+ graph: opts.graph,
8813
+ scanPath: opts.scanPath,
8814
+ paths: {
8815
+ snapshotPath: paths.snapshotPath,
8816
+ errorsPath: opts.errorsPath ?? paths.errorsPath,
8817
+ staleEventsPath: opts.staleEventsPath ?? paths.staleEventsPath,
8818
+ embeddingsCachePath: paths.embeddingsCachePath,
8819
+ policyViolationsPath: paths.policyViolationsPath
8820
+ },
8821
+ searchIndex: opts.searchIndex
8822
+ });
8823
+ return registry;
8824
+ }
8825
+ function registerRoutes(scope, ctx) {
8384
8826
  const { registry, startedAt, errorsPathFor, staleEventsPathFor } = ctx;
8385
8827
  scope.get("/events", (req2, reply) => {
8386
8828
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
@@ -8472,11 +8914,11 @@ function registerRoutes(scope, ctx) {
8472
8914
  const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
8473
8915
  const parsed = [];
8474
8916
  for (const c of candidates) {
8475
- const r = import_types30.DivergenceTypeSchema.safeParse(c);
8917
+ const r = import_types33.DivergenceTypeSchema.safeParse(c);
8476
8918
  if (!r.success) {
8477
8919
  return reply.code(400).send({
8478
8920
  error: `unknown divergence type "${c}"`,
8479
- allowed: import_types30.DivergenceTypeSchema.options
8921
+ allowed: import_types33.DivergenceTypeSchema.options
8480
8922
  });
8481
8923
  }
8482
8924
  parsed.push(r.data);
@@ -8541,6 +8983,26 @@ function registerRoutes(scope, ctx) {
8541
8983
  const sliced = filtered.slice(0, safeLimit);
8542
8984
  return { count: sliced.length, total, logs: sliced };
8543
8985
  });
8986
+ scope.get("/connectors", async (req2, reply) => {
8987
+ const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
8988
+ if (!proj) return;
8989
+ let entries;
8990
+ try {
8991
+ entries = (await readConnectorsConfig(ctx.connectorsHome)).connectors;
8992
+ } catch (err) {
8993
+ return reply.code(500).send({
8994
+ error: "connectors.json failed to read",
8995
+ details: err.message
8996
+ });
8997
+ }
8998
+ const connectors = entries.filter((entry2) => connectorMatchesProject(entry2, proj.name)).map((entry2) => ({
8999
+ id: entry2.id,
9000
+ provider: entry2.provider,
9001
+ credentialRef: redactCredentialRef(entry2.credential),
9002
+ status: getConnectorStatus(entry2.id)
9003
+ }));
9004
+ return { connectors };
9005
+ });
8544
9006
  const incidentHistoryHandler = async (req2, reply) => {
8545
9007
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
8546
9008
  if (!proj) return;
@@ -8727,7 +9189,7 @@ function registerRoutes(scope, ctx) {
8727
9189
  const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
8728
9190
  let violations = await log.readAll();
8729
9191
  if (req2.query.severity) {
8730
- const sev = import_types30.PolicySeveritySchema.safeParse(req2.query.severity);
9192
+ const sev = import_types33.PolicySeveritySchema.safeParse(req2.query.severity);
8731
9193
  if (!sev.success) {
8732
9194
  return reply.code(400).send({
8733
9195
  error: "invalid severity",
@@ -8766,7 +9228,7 @@ function registerRoutes(scope, ctx) {
8766
9228
  scope.post("/policies/check", async (req2, reply) => {
8767
9229
  const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
8768
9230
  if (!proj) return;
8769
- const parsed = import_types30.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
9231
+ const parsed = import_types33.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
8770
9232
  if (!parsed.success) {
8771
9233
  return reply.code(400).send({
8772
9234
  error: "invalid /policies/check body",
@@ -8943,7 +9405,8 @@ async function buildApi(opts) {
8943
9405
  staleEventsPathFor,
8944
9406
  policyFilePathFor,
8945
9407
  bootstrap: opts.bootstrap,
8946
- singleProject: opts.singleProject?.name
9408
+ singleProject: opts.singleProject?.name,
9409
+ connectorsHome: opts.connectorsHome
8947
9410
  };
8948
9411
  app.get("/health", async () => {
8949
9412
  const uptimeMs = Date.now() - startedAt;
@@ -9085,16 +9548,31 @@ function startConnectorPollLoop(connector, ctx, graph, resolveTarget, options =
9085
9548
  let stopped = false;
9086
9549
  let since = ctx.since;
9087
9550
  const intervalMs = options.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
9551
+ const connectorId = options.connectorId;
9088
9552
  const onError = options.onError ?? ((err) => console.error(`[neatd] connector poll failed (${connector.provider})`, err));
9089
9553
  const tick = () => {
9090
9554
  if (stopped) return;
9091
9555
  void (async () => {
9092
9556
  const tickStartedAt = (/* @__PURE__ */ new Date()).toISOString();
9093
9557
  try {
9094
- await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
9558
+ const result = await runConnectorPoll(connector, { ...ctx, since }, graph, resolveTarget);
9095
9559
  since = tickStartedAt;
9560
+ if (connectorId) {
9561
+ recordConnectorPoll(connectorId, {
9562
+ outcome: "ok",
9563
+ at: tickStartedAt,
9564
+ signalsLastPoll: result.signalCount
9565
+ });
9566
+ }
9096
9567
  } catch (err) {
9097
9568
  onError(err);
9569
+ if (connectorId) {
9570
+ recordConnectorPoll(connectorId, {
9571
+ outcome: "error",
9572
+ at: tickStartedAt,
9573
+ error: sanitizePollError(err)
9574
+ });
9575
+ }
9098
9576
  }
9099
9577
  })();
9100
9578
  };
@@ -9393,6 +9871,30 @@ function buildEdgeLogsQuery(limit) {
9393
9871
  `limit ${safeLimit}`
9394
9872
  ].join("\n");
9395
9873
  }
9874
+ function logsAllHttpFailureMessage(status2) {
9875
+ if (status2 === 401 || status2 === 403) {
9876
+ return `supabase connector: logs.all request rejected (HTTP ${status2}). Check the Management API token, its analytics read scope, and --api-project-ref.`;
9877
+ }
9878
+ if (status2 === 404) {
9879
+ return "supabase connector: logs.all project not found (HTTP 404). Check --api-project-ref.";
9880
+ }
9881
+ if (status2 === 429) {
9882
+ return "supabase connector: logs.all rate-limited (HTTP 429). The connector will retry on the next poll; reduce poll cadence if this persists.";
9883
+ }
9884
+ if (status2 === 400) {
9885
+ return "supabase connector: logs.all query rejected (HTTP 400). Provider details redacted; confirm the live log-query dialect before shipping this connector.";
9886
+ }
9887
+ return `supabase connector: logs.all request failed (HTTP ${status2}); provider details redacted.`;
9888
+ }
9889
+ function providerErrorDetails(error) {
9890
+ if (!error || typeof error === "string") return "";
9891
+ const parts = [];
9892
+ if (typeof error.code === "number") parts.push(`code ${error.code}`);
9893
+ if (typeof error.status === "string" && /^[A-Z0-9_.-]+$/.test(error.status)) {
9894
+ parts.push(`status ${error.status}`);
9895
+ }
9896
+ return parts.length > 0 ? ` (${parts.join(", ")})` : "";
9897
+ }
9396
9898
  async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
9397
9899
  const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
9398
9900
  const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
@@ -9407,12 +9909,18 @@ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl
9407
9909
  { provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
9408
9910
  );
9409
9911
  if (!res.ok) {
9410
- throw new Error(`supabase connector: logs.all request failed (${res.status} ${res.statusText})`);
9912
+ throw new Error(logsAllHttpFailureMessage(res.status));
9913
+ }
9914
+ let body;
9915
+ try {
9916
+ body = await res.json();
9917
+ } catch {
9918
+ throw new Error("supabase connector: logs.all returned invalid JSON; provider details redacted.");
9411
9919
  }
9412
- const body = await res.json();
9413
9920
  if (body.error) {
9414
- const message = typeof body.error === "string" ? body.error : body.error.message;
9415
- throw new Error(`supabase connector: logs.all returned an error (${message})`);
9921
+ throw new Error(
9922
+ `supabase connector: logs.all returned a provider error${providerErrorDetails(body.error)}; provider message redacted.`
9923
+ );
9416
9924
  }
9417
9925
  return body.result ?? [];
9418
9926
  }
@@ -9444,10 +9952,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
9444
9952
  // src/connectors/supabase/map.ts
9445
9953
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
9446
9954
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
9447
- function targetFromRestPath(path50) {
9448
- const rpcMatch = REST_RPC_PATH_RE.exec(path50);
9955
+ function targetFromRestPath(path53) {
9956
+ const rpcMatch = REST_RPC_PATH_RE.exec(path53);
9449
9957
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
9450
- const tableMatch = REST_TABLE_PATH_RE.exec(path50);
9958
+ const tableMatch = REST_TABLE_PATH_RE.exec(path53);
9451
9959
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
9452
9960
  return null;
9453
9961
  }
@@ -9550,19 +10058,19 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
9550
10058
 
9551
10059
  // src/connectors/supabase/resolve.ts
9552
10060
  init_cjs_shims();
9553
- var import_types32 = require("@neat.is/types");
10061
+ var import_types35 = require("@neat.is/types");
9554
10062
  function createSupabaseResolveTarget(graph, config) {
9555
10063
  return (signal, _ctx) => {
9556
10064
  if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
9557
10065
  return null;
9558
10066
  }
9559
- const subResourceId = (0, import_types32.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
10067
+ const subResourceId = (0, import_types35.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
9560
10068
  if (graph.hasNode(subResourceId)) {
9561
- return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types32.EdgeType.CALLS };
10069
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
9562
10070
  }
9563
- const projectLevelId = (0, import_types32.infraId)("supabase", config.nodeRef);
10071
+ const projectLevelId = (0, import_types35.infraId)("supabase", config.nodeRef);
9564
10072
  if (graph.hasNode(projectLevelId)) {
9565
- return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types32.EdgeType.CALLS };
10073
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types35.EdgeType.CALLS };
9566
10074
  }
9567
10075
  return null;
9568
10076
  };
@@ -9570,6 +10078,35 @@ function createSupabaseResolveTarget(graph, config) {
9570
10078
 
9571
10079
  // src/connectors/supabase/index.ts
9572
10080
  var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
10081
+ function errorCode(err) {
10082
+ const code = err?.code;
10083
+ return typeof code === "string" && code.length > 0 ? code : void 0;
10084
+ }
10085
+ function describeSupabasePostgresSurfaceFailure(projectRef, err) {
10086
+ const code = errorCode(err);
10087
+ let reason;
10088
+ switch (code) {
10089
+ case "42501":
10090
+ reason = "permission denied; grant pg_read_all_stats to the configured Postgres role";
10091
+ break;
10092
+ case "42P01":
10093
+ case "42704":
10094
+ reason = "pg_stat_statements is not enabled or visible to the configured Postgres role";
10095
+ break;
10096
+ case "28P01":
10097
+ case "28000":
10098
+ reason = "Postgres credential rejected";
10099
+ break;
10100
+ case "3D000":
10101
+ reason = "database not found";
10102
+ break;
10103
+ default: {
10104
+ const name = err instanceof Error && err.name ? err.name : "Error";
10105
+ reason = code ? `${name} ${code}` : name;
10106
+ }
10107
+ }
10108
+ return `supabase connector: pg_stat_statements surface unavailable for project ${projectRef} (${reason}); continuing with Management API log surface.`;
10109
+ }
9573
10110
  var SupabaseConnector = class {
9574
10111
  // `deps.fetchPgStatStatements` defaults to the real Postgres-backed
9575
10112
  // implementation; tests override it to exercise the "both surfaces
@@ -9601,12 +10138,18 @@ var SupabaseConnector = class {
9601
10138
  const signals = mapEdgeLogRowsToSignals(logRows);
9602
10139
  if (creds.postgresConnectionString) {
9603
10140
  const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements;
9604
- const statementRows = await fetchStatements(
9605
- creds.postgresConnectionString,
9606
- this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
9607
- this.config.apiProjectRef
9608
- );
9609
- signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
10141
+ try {
10142
+ const statementRows = await fetchStatements(
10143
+ creds.postgresConnectionString,
10144
+ this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
10145
+ this.config.apiProjectRef
10146
+ );
10147
+ signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
10148
+ } catch (err) {
10149
+ const summary = describeSupabasePostgresSurfaceFailure(this.config.apiProjectRef, err);
10150
+ if (this.deps.onPostgresSurfaceError) this.deps.onPostgresSurfaceError(err, summary);
10151
+ else console.warn(summary);
10152
+ }
9610
10153
  }
9611
10154
  return signals;
9612
10155
  }
@@ -9620,7 +10163,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
9620
10163
 
9621
10164
  // src/connectors/railway/index.ts
9622
10165
  init_cjs_shims();
9623
- var import_types36 = require("@neat.is/types");
10166
+ var import_types39 = require("@neat.is/types");
9624
10167
 
9625
10168
  // src/connectors/railway/client.ts
9626
10169
  init_cjs_shims();
@@ -9765,7 +10308,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
9765
10308
  const out = [];
9766
10309
  graph.forEachNode((_id, attrs) => {
9767
10310
  const node = attrs;
9768
- if (node.type !== import_types36.NodeType.RouteNode) return;
10311
+ if (node.type !== import_types39.NodeType.RouteNode) return;
9769
10312
  const route = attrs;
9770
10313
  if (route.service !== serviceName) return;
9771
10314
  out.push({
@@ -9861,12 +10404,12 @@ function createRailwayResolveTarget(config) {
9861
10404
  const serviceName = config.serviceNameById[config.serviceId];
9862
10405
  if (!serviceName) return null;
9863
10406
  if (signal.targetKind === ROUTE_TARGET_KIND) {
9864
- return { targetNodeId: signal.targetName, serviceName, edgeType: import_types36.EdgeType.CALLS };
10407
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types39.EdgeType.CALLS };
9865
10408
  }
9866
10409
  if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
9867
10410
  const peerName = config.serviceNameById[signal.targetName];
9868
10411
  if (!peerName) return null;
9869
- return { targetNodeId: (0, import_types36.serviceId)(peerName), serviceName, edgeType: import_types36.EdgeType.CONNECTS_TO };
10412
+ return { targetNodeId: (0, import_types39.serviceId)(peerName), serviceName, edgeType: import_types39.EdgeType.CONNECTS_TO };
9870
10413
  }
9871
10414
  return null;
9872
10415
  };
@@ -9981,9 +10524,9 @@ function parseFirebaseTargetName(targetName) {
9981
10524
  const secondSep = rest.indexOf(FIELD_SEP);
9982
10525
  if (secondSep === -1) return null;
9983
10526
  const method = rest.slice(0, secondSep);
9984
- const path50 = rest.slice(secondSep + 1);
9985
- if (!resourceName || !method || !path50) return null;
9986
- return { resourceName, method, path: path50 };
10527
+ const path53 = rest.slice(secondSep + 1);
10528
+ if (!resourceName || !method || !path53) return null;
10529
+ return { resourceName, method, path: path53 };
9987
10530
  }
9988
10531
  function resourceNameFor(type, labels) {
9989
10532
  if (!labels) return null;
@@ -10020,14 +10563,14 @@ function mapLogEntryToSignal(entry2) {
10020
10563
  if (!req2) return null;
10021
10564
  if (!req2.requestMethod) return null;
10022
10565
  const method = req2.requestMethod.toUpperCase();
10023
- const path50 = pathFromRequestUrl(req2.requestUrl);
10024
- if (path50 === null) return null;
10566
+ const path53 = pathFromRequestUrl(req2.requestUrl);
10567
+ if (path53 === null) return null;
10025
10568
  const timestamp = entry2.timestamp;
10026
10569
  if (!timestamp) return null;
10027
10570
  const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
10028
10571
  return {
10029
10572
  targetKind: resourceType,
10030
- targetName: packFirebaseTargetName({ resourceName, method, path: path50 }),
10573
+ targetName: packFirebaseTargetName({ resourceName, method, path: path53 }),
10031
10574
  callCount: 1,
10032
10575
  errorCount: isError ? 1 : 0,
10033
10576
  lastObservedIso: timestamp
@@ -10044,7 +10587,7 @@ function mapLogEntriesToSignals(entries) {
10044
10587
 
10045
10588
  // src/connectors/firebase/resolve.ts
10046
10589
  init_cjs_shims();
10047
- var import_types37 = require("@neat.is/types");
10590
+ var import_types40 = require("@neat.is/types");
10048
10591
  function neatServiceNameFor(resourceType, resourceName, serviceMap) {
10049
10592
  switch (resourceType) {
10050
10593
  case "cloud_function":
@@ -10059,7 +10602,7 @@ function routeEntriesFor(graph, serviceName) {
10059
10602
  const entries = [];
10060
10603
  graph.forEachNode((_id, attrs) => {
10061
10604
  const node = attrs;
10062
- if (node.type !== import_types37.NodeType.RouteNode) return;
10605
+ if (node.type !== import_types40.NodeType.RouteNode) return;
10063
10606
  const route = attrs;
10064
10607
  if (route.service !== serviceName) return;
10065
10608
  entries.push({
@@ -10091,7 +10634,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
10091
10634
  return {
10092
10635
  targetNodeId: match.routeNodeId,
10093
10636
  serviceName,
10094
- edgeType: import_types37.EdgeType.CALLS
10637
+ edgeType: import_types40.EdgeType.CALLS
10095
10638
  };
10096
10639
  };
10097
10640
  }
@@ -10118,7 +10661,7 @@ init_cjs_shims();
10118
10661
 
10119
10662
  // src/connectors/cloudflare/connector.ts
10120
10663
  init_cjs_shims();
10121
- var import_types39 = require("@neat.is/types");
10664
+ var import_types42 = require("@neat.is/types");
10122
10665
 
10123
10666
  // src/connectors/cloudflare/client.ts
10124
10667
  init_cjs_shims();
@@ -10225,7 +10768,7 @@ function mapEventToSignal(event) {
10225
10768
  if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
10226
10769
  const statusCode = metadata?.statusCode;
10227
10770
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
10228
- const path50 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10771
+ const path53 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
10229
10772
  return {
10230
10773
  targetKind: CLOUDFLARE_TARGET_KIND,
10231
10774
  targetName: scriptName,
@@ -10233,7 +10776,7 @@ function mapEventToSignal(event) {
10233
10776
  errorCount: isError ? 1 : 0,
10234
10777
  lastObservedIso: new Date(timestampMs).toISOString(),
10235
10778
  method,
10236
- ...path50 ? { path: path50 } : {},
10779
+ ...path53 ? { path: path53 } : {},
10237
10780
  ...typeof statusCode === "number" ? { statusCode } : {},
10238
10781
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
10239
10782
  };
@@ -10273,19 +10816,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
10273
10816
  graph.forEachNode((id, attrs) => {
10274
10817
  if (found) return;
10275
10818
  const a = attrs;
10276
- if (a.type === import_types39.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
10819
+ if (a.type === import_types42.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
10277
10820
  found = id;
10278
10821
  }
10279
10822
  });
10280
10823
  return found;
10281
10824
  }
10282
- function findMatchingRouteNode(graph, serviceName, method, path50) {
10283
- const normalizedPath = normalizePathTemplate(path50);
10825
+ function findMatchingRouteNode(graph, serviceName, method, path53) {
10826
+ const normalizedPath = normalizePathTemplate(path53);
10284
10827
  let found = null;
10285
10828
  graph.forEachNode((id, attrs) => {
10286
10829
  if (found) return;
10287
10830
  const a = attrs;
10288
- if (a.type !== import_types39.NodeType.RouteNode || a.service !== serviceName) return;
10831
+ if (a.type !== import_types42.NodeType.RouteNode || a.service !== serviceName) return;
10289
10832
  if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
10290
10833
  const routeMethod = (a.method ?? "").toUpperCase();
10291
10834
  if (routeMethod !== "ALL" && routeMethod !== method) return;
@@ -10297,18 +10840,18 @@ function createCloudflareResolveTarget(config, graph) {
10297
10840
  return (signal) => {
10298
10841
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
10299
10842
  const scriptName = signal.targetName;
10300
- const { method, path: path50 } = signal;
10843
+ const { method, path: path53 } = signal;
10301
10844
  const resolveRouteGrain = (serviceName, wholeFileId) => {
10302
- if (!method || !path50) return wholeFileId;
10303
- return findMatchingRouteNode(graph, serviceName, method, path50) ?? wholeFileId;
10845
+ if (!method || !path53) return wholeFileId;
10846
+ return findMatchingRouteNode(graph, serviceName, method, path53) ?? wholeFileId;
10304
10847
  };
10305
10848
  const mapping = config.workers?.[scriptName];
10306
10849
  if (mapping) {
10307
- const wholeFileId = (0, import_types39.fileId)(mapping.service, mapping.entryFile);
10850
+ const wholeFileId = (0, import_types42.fileId)(mapping.service, mapping.entryFile);
10308
10851
  return {
10309
10852
  targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
10310
10853
  serviceName: mapping.service,
10311
- edgeType: import_types39.EdgeType.CALLS
10854
+ edgeType: import_types42.EdgeType.CALLS
10312
10855
  };
10313
10856
  }
10314
10857
  const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
@@ -10317,169 +10860,18 @@ function createCloudflareResolveTarget(config, graph) {
10317
10860
  return {
10318
10861
  targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
10319
10862
  serviceName: fileNode.service,
10320
- edgeType: import_types39.EdgeType.CALLS
10863
+ edgeType: import_types42.EdgeType.CALLS
10321
10864
  };
10322
10865
  }
10323
10866
  return {
10324
- targetNodeId: (0, import_types39.infraId)("cloudflare-worker", scriptName),
10867
+ targetNodeId: (0, import_types42.infraId)("cloudflare-worker", scriptName),
10325
10868
  serviceName: scriptName,
10326
- edgeType: import_types39.EdgeType.CALLS,
10869
+ edgeType: import_types42.EdgeType.CALLS,
10327
10870
  ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
10328
10871
  };
10329
10872
  };
10330
10873
  }
10331
10874
 
10332
- // src/connectors-config.ts
10333
- init_cjs_shims();
10334
- var import_node_os4 = __toESM(require("os"), 1);
10335
- var import_node_path45 = __toESM(require("path"), 1);
10336
- var import_node_fs26 = require("fs");
10337
- var CONNECTORS_CONFIG_VERSION = 1;
10338
- var EnvRefUnsetError = class extends Error {
10339
- ref;
10340
- varName;
10341
- constructor(ref, varName) {
10342
- super(`${ref} is unset`);
10343
- this.name = "EnvRefUnsetError";
10344
- this.ref = ref;
10345
- this.varName = varName;
10346
- }
10347
- };
10348
- function neatHome2() {
10349
- const override = process.env.NEAT_HOME;
10350
- if (override && override.length > 0) return import_node_path45.default.resolve(override);
10351
- return import_node_path45.default.join(import_node_os4.default.homedir(), ".neat");
10352
- }
10353
- function connectorsConfigPath(home = neatHome2()) {
10354
- return import_node_path45.default.join(home, "connectors.json");
10355
- }
10356
- var MODE_MASK_LOOSER_THAN_0600 = 63;
10357
- async function warnIfModeLooserThan0600(file) {
10358
- if (process.platform === "win32") return;
10359
- try {
10360
- const stat = await import_node_fs26.promises.stat(file);
10361
- if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
10362
- const mode = (stat.mode & 511).toString(8).padStart(3, "0");
10363
- console.warn(
10364
- `[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
10365
- );
10366
- }
10367
- } catch {
10368
- }
10369
- }
10370
- async function readConnectorsConfig(home = neatHome2()) {
10371
- const file = connectorsConfigPath(home);
10372
- let raw;
10373
- try {
10374
- raw = await import_node_fs26.promises.readFile(file, "utf8");
10375
- } catch (err) {
10376
- if (err.code === "ENOENT") {
10377
- return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
10378
- }
10379
- throw err;
10380
- }
10381
- await warnIfModeLooserThan0600(file);
10382
- let parsed;
10383
- try {
10384
- parsed = JSON.parse(raw);
10385
- } catch (err) {
10386
- throw new Error(`${file} is not valid JSON: ${err.message}`);
10387
- }
10388
- return validateConfig(parsed, file);
10389
- }
10390
- function validateConfig(parsed, file) {
10391
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
10392
- throw new Error(`${file} must be a JSON object with a "connectors" array`);
10393
- }
10394
- const obj = parsed;
10395
- const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
10396
- if (typeof version !== "number" || !Number.isInteger(version)) {
10397
- throw new Error(`${file}: "version" must be an integer`);
10398
- }
10399
- const rawConnectors = obj.connectors;
10400
- if (!Array.isArray(rawConnectors)) {
10401
- throw new Error(`${file}: "connectors" must be an array`);
10402
- }
10403
- const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
10404
- return { version, connectors };
10405
- }
10406
- function validateEntry(entry2, index, file) {
10407
- const where = `${file}: connectors[${index}]`;
10408
- if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
10409
- throw new Error(`${where} must be an object`);
10410
- }
10411
- const e = entry2;
10412
- const id = e.id;
10413
- if (typeof id !== "string" || id.length === 0) {
10414
- throw new Error(`${where}.id must be a non-empty string`);
10415
- }
10416
- const provider = e.provider;
10417
- if (typeof provider !== "string" || provider.length === 0) {
10418
- throw new Error(`${where}.provider must be a non-empty string`);
10419
- }
10420
- if (e.project !== void 0 && typeof e.project !== "string") {
10421
- throw new Error(`${where}.project must be a string when present`);
10422
- }
10423
- const credential = validateCredentialRef(e.credential, `${where}.credential`);
10424
- let options;
10425
- if (e.options !== void 0) {
10426
- if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
10427
- throw new Error(`${where}.options must be an object when present`);
10428
- }
10429
- options = e.options;
10430
- }
10431
- return {
10432
- id,
10433
- provider,
10434
- ...typeof e.project === "string" ? { project: e.project } : {},
10435
- credential,
10436
- ...options ? { options } : {}
10437
- };
10438
- }
10439
- function validateCredentialRef(raw, where) {
10440
- if (typeof raw === "string") {
10441
- if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
10442
- return raw;
10443
- }
10444
- if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
10445
- const fields = raw;
10446
- const keys = Object.keys(fields);
10447
- if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
10448
- for (const key of keys) {
10449
- const v = fields[key];
10450
- if (typeof v !== "string" || v.length === 0) {
10451
- throw new Error(`${where}.${key} must be a non-empty string`);
10452
- }
10453
- }
10454
- return fields;
10455
- }
10456
- throw new Error(`${where} must be a string or an object of strings`);
10457
- }
10458
- function resolveCredential(ref, env = process.env) {
10459
- if (typeof ref === "string") {
10460
- return { kind: "single", value: resolveRef(ref, env) };
10461
- }
10462
- const fields = {};
10463
- for (const [key, value] of Object.entries(ref)) {
10464
- fields[key] = resolveRef(value, env);
10465
- }
10466
- return { kind: "fields", fields };
10467
- }
10468
- function resolveRef(value, env) {
10469
- if (value.length > 1 && value.startsWith("$")) {
10470
- const varName = value.slice(1);
10471
- const resolved = env[varName];
10472
- if (resolved === void 0 || resolved.length === 0) {
10473
- throw new EnvRefUnsetError(value, varName);
10474
- }
10475
- return resolved;
10476
- }
10477
- return value;
10478
- }
10479
- function connectorMatchesProject(entry2, project) {
10480
- return entry2.project === void 0 || entry2.project === project;
10481
- }
10482
-
10483
10875
  // src/connectors/registry.ts
10484
10876
  var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
10485
10877
  async function authProbe(input) {
@@ -10665,6 +11057,9 @@ function buildRegistration(entry2, graph, env = process.env) {
10665
11057
  return {
10666
11058
  ok: true,
10667
11059
  registration: {
11060
+ // Carry the entry id so the daemon can key this connector's poll-status
11061
+ // records to it (ADR-136).
11062
+ id: entry2.id,
10668
11063
  connector: built.connector,
10669
11064
  credentials,
10670
11065
  resolveTarget: built.resolveTarget,
@@ -10699,8 +11094,8 @@ init_auth();
10699
11094
 
10700
11095
  // src/unrouted.ts
10701
11096
  init_cjs_shims();
10702
- var import_node_fs27 = require("fs");
10703
- var import_node_path46 = __toESM(require("path"), 1);
11097
+ var import_node_fs30 = require("fs");
11098
+ var import_node_path49 = __toESM(require("path"), 1);
10704
11099
  function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
10705
11100
  return {
10706
11101
  timestamp: now.toISOString(),
@@ -10710,34 +11105,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
10710
11105
  };
10711
11106
  }
10712
11107
  async function appendUnroutedSpan(neatHome4, record) {
10713
- const target = import_node_path46.default.join(neatHome4, "errors.ndjson");
10714
- await import_node_fs27.promises.mkdir(neatHome4, { recursive: true });
10715
- await import_node_fs27.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
11108
+ const target = import_node_path49.default.join(neatHome4, "errors.ndjson");
11109
+ await import_node_fs30.promises.mkdir(neatHome4, { recursive: true });
11110
+ await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
10716
11111
  }
10717
11112
  function unroutedErrorsPath(neatHome4) {
10718
- return import_node_path46.default.join(neatHome4, "errors.ndjson");
11113
+ return import_node_path49.default.join(neatHome4, "errors.ndjson");
10719
11114
  }
10720
11115
 
10721
11116
  // src/daemon.ts
10722
- var import_types42 = require("@neat.is/types");
11117
+ var import_types45 = require("@neat.is/types");
10723
11118
  function daemonJsonPath(scanPath) {
10724
- return import_node_path47.default.join(scanPath, "neat-out", "daemon.json");
11119
+ return import_node_path50.default.join(scanPath, "neat-out", "daemon.json");
10725
11120
  }
10726
11121
  function daemonsDiscoveryDir(home) {
10727
11122
  const base = home && home.length > 0 ? home : neatHomeFromEnv();
10728
- return import_node_path47.default.join(base, "daemons");
11123
+ return import_node_path50.default.join(base, "daemons");
10729
11124
  }
10730
11125
  function daemonDiscoveryPath(project, home) {
10731
- return import_node_path47.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
11126
+ return import_node_path50.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
10732
11127
  }
10733
11128
  function sanitizeDiscoveryName(project) {
10734
11129
  return project.replace(/[^A-Za-z0-9._-]/g, "_");
10735
11130
  }
10736
11131
  function neatHomeFromEnv() {
10737
11132
  const env = process.env.NEAT_HOME;
10738
- if (env && env.length > 0) return import_node_path47.default.resolve(env);
11133
+ if (env && env.length > 0) return import_node_path50.default.resolve(env);
10739
11134
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10740
- return import_node_path47.default.join(home, ".neat");
11135
+ return import_node_path50.default.join(home, ".neat");
10741
11136
  }
10742
11137
  function resolveNeatVersion() {
10743
11138
  if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
@@ -10769,7 +11164,7 @@ async function clearDaemonRecord(record, home) {
10769
11164
  } catch {
10770
11165
  }
10771
11166
  try {
10772
- await import_node_fs28.promises.unlink(daemonDiscoveryPath(record.project, home));
11167
+ await import_node_fs31.promises.unlink(daemonDiscoveryPath(record.project, home));
10773
11168
  } catch {
10774
11169
  }
10775
11170
  }
@@ -10778,12 +11173,12 @@ function reconcileDaemonRecordSync(record, home) {
10778
11173
  const stopped = { ...record, status: "stopped" };
10779
11174
  const target = daemonJsonPath(record.projectPath);
10780
11175
  const tmp = `${target}.${process.pid}.tmp`;
10781
- (0, import_node_fs28.writeFileSync)(tmp, JSON.stringify(stopped, null, 2) + "\n");
10782
- (0, import_node_fs28.renameSync)(tmp, target);
11176
+ (0, import_node_fs31.writeFileSync)(tmp, JSON.stringify(stopped, null, 2) + "\n");
11177
+ (0, import_node_fs31.renameSync)(tmp, target);
10783
11178
  } catch {
10784
11179
  }
10785
11180
  try {
10786
- (0, import_node_fs28.unlinkSync)(daemonDiscoveryPath(record.project, home));
11181
+ (0, import_node_fs31.unlinkSync)(daemonDiscoveryPath(record.project, home));
10787
11182
  } catch {
10788
11183
  }
10789
11184
  }
@@ -10806,11 +11201,11 @@ function teardownSlot(slot) {
10806
11201
  }
10807
11202
  }
10808
11203
  function neatHomeFor(opts) {
10809
- if (opts.neatHome && opts.neatHome.length > 0) return import_node_path47.default.resolve(opts.neatHome);
11204
+ if (opts.neatHome && opts.neatHome.length > 0) return import_node_path50.default.resolve(opts.neatHome);
10810
11205
  const env = process.env.NEAT_HOME;
10811
- if (env && env.length > 0) return import_node_path47.default.resolve(env);
11206
+ if (env && env.length > 0) return import_node_path50.default.resolve(env);
10812
11207
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10813
- return import_node_path47.default.join(home, ".neat");
11208
+ return import_node_path50.default.join(home, ".neat");
10814
11209
  }
10815
11210
  function routeSpanToProject(serviceName, projects) {
10816
11211
  if (!serviceName) return DEFAULT_PROJECT;
@@ -10854,13 +11249,13 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
10854
11249
  if (!serviceName) return true;
10855
11250
  if (serviceNameMatchesProject(serviceName, project)) return true;
10856
11251
  return graph.someNode(
10857
- (_id, attrs) => attrs.type === import_types42.NodeType.ServiceNode && attrs.name === serviceName
11252
+ (_id, attrs) => attrs.type === import_types45.NodeType.ServiceNode && attrs.name === serviceName
10858
11253
  );
10859
11254
  }
10860
11255
  async function bootstrapProject(entry2, connectors = [], neatHome4) {
10861
- const paths = pathsForProject(entry2.name, import_node_path47.default.join(entry2.path, "neat-out"));
11256
+ const paths = pathsForProject(entry2.name, import_node_path50.default.join(entry2.path, "neat-out"));
10862
11257
  try {
10863
- const stat = await import_node_fs28.promises.stat(entry2.path);
11258
+ const stat = await import_node_fs31.promises.stat(entry2.path);
10864
11259
  if (!stat.isDirectory()) {
10865
11260
  throw new Error(`registered path ${entry2.path} is not a directory`);
10866
11261
  }
@@ -10913,7 +11308,11 @@ async function bootstrapProject(entry2, connectors = [], neatHome4) {
10913
11308
  { projectDir: entry2.path, credentials: registration.credentials },
10914
11309
  graph,
10915
11310
  registration.resolveTarget,
10916
- { intervalMs: registration.intervalMs }
11311
+ // `connectorId` is threaded through so every tick lands in the
11312
+ // in-process status tracker the connector-status endpoint reads
11313
+ // (ADR-136). Undefined for a programmatic registration, which records
11314
+ // nothing.
11315
+ { intervalMs: registration.intervalMs, connectorId: registration.id }
10917
11316
  )
10918
11317
  );
10919
11318
  const stopConnectors = () => {
@@ -10985,7 +11384,7 @@ async function startDaemon(opts = {}) {
10985
11384
  const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
10986
11385
  const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
10987
11386
  const singleProject = projectArg;
10988
- const singleProjectPath = singleProject && projectPathArg ? import_node_path47.default.resolve(projectPathArg) : null;
11387
+ const singleProjectPath = singleProject && projectPathArg ? import_node_path50.default.resolve(projectPathArg) : null;
10989
11388
  if (singleProject && !singleProjectPath) {
10990
11389
  throw new Error(
10991
11390
  `neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
@@ -10993,14 +11392,14 @@ async function startDaemon(opts = {}) {
10993
11392
  }
10994
11393
  if (!singleProject) {
10995
11394
  try {
10996
- await import_node_fs28.promises.access(regPath);
11395
+ await import_node_fs31.promises.access(regPath);
10997
11396
  } catch {
10998
11397
  throw new Error(
10999
11398
  `neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
11000
11399
  );
11001
11400
  }
11002
11401
  }
11003
- const pidPath = import_node_path47.default.join(home, "neatd.pid");
11402
+ const pidPath = import_node_path50.default.join(home, "neatd.pid");
11004
11403
  await writeAtomically(pidPath, `${process.pid}
11005
11404
  `);
11006
11405
  const slots = /* @__PURE__ */ new Map();
@@ -11182,7 +11581,12 @@ async function startDaemon(opts = {}) {
11182
11581
  // only this project (the dashboard pins to it), and the daemon-wide
11183
11582
  // `/health` carries it at the top level for the spawn-reuse identity
11184
11583
  // check. Absent for the legacy multi-project daemon.
11185
- singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0
11584
+ singleProject: singleProject && singleProjectPath ? { name: singleProject, path: singleProjectPath } : void 0,
11585
+ // ADR-136 — the connector-status endpoint reads ~/.neat/connectors.json
11586
+ // through the same resolved home the slot bootstrap read it from, so a
11587
+ // daemon given an explicit NEAT_HOME serves status for the same file it
11588
+ // polls.
11589
+ connectorsHome: home
11186
11590
  });
11187
11591
  restAddress = await restApp.listen({ port: restPort, host });
11188
11592
  console.log(
@@ -11194,7 +11598,7 @@ async function startDaemon(opts = {}) {
11194
11598
  }
11195
11599
  if (restApp) await restApp.close().catch(() => {
11196
11600
  });
11197
- await import_node_fs28.promises.unlink(pidPath).catch(() => {
11601
+ await import_node_fs31.promises.unlink(pidPath).catch(() => {
11198
11602
  });
11199
11603
  throw new Error(
11200
11604
  `neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
@@ -11320,7 +11724,7 @@ async function startDaemon(opts = {}) {
11320
11724
  });
11321
11725
  if (otlpApp) await otlpApp.close().catch(() => {
11322
11726
  });
11323
- await import_node_fs28.promises.unlink(pidPath).catch(() => {
11727
+ await import_node_fs31.promises.unlink(pidPath).catch(() => {
11324
11728
  });
11325
11729
  throw new Error(
11326
11730
  `neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
@@ -11355,7 +11759,7 @@ async function startDaemon(opts = {}) {
11355
11759
  });
11356
11760
  if (otlpApp) await otlpApp.close().catch(() => {
11357
11761
  });
11358
- await import_node_fs28.promises.unlink(pidPath).catch(() => {
11762
+ await import_node_fs31.promises.unlink(pidPath).catch(() => {
11359
11763
  });
11360
11764
  throw new Error(
11361
11765
  `neatd: failed to write daemon.json for "${singleProject}" \u2014 ${err.message}`
@@ -11402,9 +11806,9 @@ async function startDaemon(opts = {}) {
11402
11806
  let registryWatcher = null;
11403
11807
  let reloadTimer = null;
11404
11808
  if (!singleProject) try {
11405
- const regDir = import_node_path47.default.dirname(regPath);
11406
- const regBase = import_node_path47.default.basename(regPath);
11407
- registryWatcher = (0, import_node_fs28.watch)(regDir, (_eventType, filename) => {
11809
+ const regDir = import_node_path50.default.dirname(regPath);
11810
+ const regBase = import_node_path50.default.basename(regPath);
11811
+ registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
11408
11812
  if (filename !== null && filename !== regBase) return;
11409
11813
  if (reloadTimer) clearTimeout(reloadTimer);
11410
11814
  reloadTimer = setTimeout(() => {
@@ -11453,7 +11857,7 @@ async function startDaemon(opts = {}) {
11453
11857
  if (daemonRecord) {
11454
11858
  await clearDaemonRecord(daemonRecord, home);
11455
11859
  }
11456
- await import_node_fs28.promises.unlink(pidPath).catch(() => {
11860
+ await import_node_fs31.promises.unlink(pidPath).catch(() => {
11457
11861
  });
11458
11862
  };
11459
11863
  return {
@@ -11476,9 +11880,9 @@ init_auth();
11476
11880
  // src/web-spawn.ts
11477
11881
  init_cjs_shims();
11478
11882
  var import_node_child_process2 = require("child_process");
11479
- var import_node_fs29 = require("fs");
11883
+ var import_node_fs32 = require("fs");
11480
11884
  var import_node_net = __toESM(require("net"), 1);
11481
- var import_node_path48 = __toESM(require("path"), 1);
11885
+ var import_node_path51 = __toESM(require("path"), 1);
11482
11886
  var DEFAULT_WEB_PORT = 6328;
11483
11887
  var DEFAULT_REST_PORT = 8080;
11484
11888
  function asValidPort(value) {
@@ -11487,11 +11891,11 @@ function asValidPort(value) {
11487
11891
  }
11488
11892
  function projectRoot() {
11489
11893
  const fromEnv = process.env.NEAT_SCAN_PATH;
11490
- return import_node_path48.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
11894
+ return import_node_path51.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
11491
11895
  }
11492
11896
  async function readDaemonPorts(root) {
11493
11897
  try {
11494
- const raw = await import_node_fs29.promises.readFile(import_node_path48.default.join(root, "neat-out", "daemon.json"), "utf8");
11898
+ const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(root, "neat-out", "daemon.json"), "utf8");
11495
11899
  const parsed = JSON.parse(raw);
11496
11900
  const ports = parsed?.ports ?? {};
11497
11901
  return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
@@ -11536,10 +11940,10 @@ function resolveWebPackageDir() {
11536
11940
  eval("require")
11537
11941
  );
11538
11942
  const pkgJsonPath = req.resolve("@neat.is/web/package.json");
11539
- return import_node_path48.default.dirname(pkgJsonPath);
11943
+ return import_node_path51.default.dirname(pkgJsonPath);
11540
11944
  }
11541
11945
  function resolveStandaloneServerEntry(webDir) {
11542
- return import_node_path48.default.join(webDir, ".next/standalone/packages/web/server.js");
11946
+ return import_node_path51.default.join(webDir, ".next/standalone/packages/web/server.js");
11543
11947
  }
11544
11948
  async function pickInternalPort() {
11545
11949
  return new Promise((resolve, reject) => {
@@ -11592,7 +11996,7 @@ async function spawnWebUI(restPort, opts = {}) {
11592
11996
  NEAT_API_URL: apiUrl
11593
11997
  };
11594
11998
  child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
11595
- cwd: import_node_path48.default.dirname(serverEntry),
11999
+ cwd: import_node_path51.default.dirname(serverEntry),
11596
12000
  env,
11597
12001
  stdio: ["ignore", "inherit", "inherit"],
11598
12002
  detached: false
@@ -11768,14 +12172,14 @@ function localVersion() {
11768
12172
  }
11769
12173
  function neatHome3() {
11770
12174
  if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
11771
- return import_node_path49.default.resolve(process.env.NEAT_HOME);
12175
+ return import_node_path52.default.resolve(process.env.NEAT_HOME);
11772
12176
  }
11773
12177
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
11774
- return import_node_path49.default.join(home, ".neat");
12178
+ return import_node_path52.default.join(home, ".neat");
11775
12179
  }
11776
12180
  async function readPid() {
11777
12181
  try {
11778
- const raw = await import_node_fs30.promises.readFile(import_node_path49.default.join(neatHome3(), "neatd.pid"), "utf8");
12182
+ const raw = await import_node_fs33.promises.readFile(import_node_path52.default.join(neatHome3(), "neatd.pid"), "utf8");
11779
12183
  const n = Number.parseInt(raw.trim(), 10);
11780
12184
  return Number.isFinite(n) ? n : null;
11781
12185
  } catch {