@neat.is/core 0.4.27 → 0.4.28-dev.20260708

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -55,15 +55,16 @@ function mountBearerAuth(app, opts) {
55
55
  if (!opts.token || opts.token.length === 0) return;
56
56
  if (opts.trustProxy) return;
57
57
  const expected = Buffer.from(opts.token, "utf8");
58
- const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
58
+ const exactUnauthPaths = /* @__PURE__ */ new Set([
59
+ ...DEFAULT_UNAUTH_PATHS,
60
+ ...opts.extraUnauthenticatedSuffixes ?? []
61
+ ]);
59
62
  const publicRead = opts.publicRead === true;
60
63
  app.addHook("preHandler", (req, reply, done) => {
61
- const path56 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
62
- for (const suffix of suffixes) {
63
- if (path56 === suffix || path56.endsWith(suffix)) {
64
- done();
65
- return;
66
- }
64
+ const path57 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path57) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path57)) {
66
+ done();
67
+ return;
67
68
  }
68
69
  if (publicRead && PUBLIC_READ_METHODS.has(req.method)) {
69
70
  done();
@@ -98,7 +99,7 @@ function readAuthEnv(env = process.env) {
98
99
  publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
99
100
  };
100
101
  }
101
- var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_SUFFIXES;
102
+ var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_PATHS, PROJECT_SCOPED_UNAUTH_PATTERN;
102
103
  var init_auth = __esm({
103
104
  "src/auth.ts"() {
104
105
  "use strict";
@@ -119,12 +120,15 @@ var init_auth = __esm({
119
120
  }
120
121
  };
121
122
  PUBLIC_READ_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
122
- DEFAULT_UNAUTH_SUFFIXES = [
123
+ DEFAULT_UNAUTH_PATHS = [
123
124
  "/health",
124
125
  "/healthz",
125
126
  "/readyz",
126
127
  "/api/config"
127
128
  ];
129
+ PROJECT_SCOPED_UNAUTH_PATTERN = new RegExp(
130
+ `^/projects/[^/]+/(?:${DEFAULT_UNAUTH_PATHS.map((p) => p.slice(1)).join("|")})$`
131
+ );
128
132
  }
129
133
  });
130
134
 
@@ -358,8 +362,8 @@ function websocketChannelPathOf(attrs) {
358
362
  const v = attrs[key];
359
363
  if (typeof v === "string" && v.length > 0) {
360
364
  const q = v.indexOf("?");
361
- const path56 = q === -1 ? v : v.slice(0, q);
362
- if (path56.length > 0) return path56;
365
+ const path57 = q === -1 ? v : v.slice(0, q);
366
+ if (path57.length > 0) return path57;
363
367
  }
364
368
  }
365
369
  return void 0;
@@ -686,8 +690,8 @@ __export(cli_exports, {
686
690
  });
687
691
  module.exports = __toCommonJS(cli_exports);
688
692
  init_cjs_shims();
689
- var import_node_path55 = __toESM(require("path"), 1);
690
- var import_node_fs35 = require("fs");
693
+ var import_node_path56 = __toESM(require("path"), 1);
694
+ var import_node_fs36 = require("fs");
691
695
 
692
696
  // src/banner.ts
693
697
  init_cjs_shims();
@@ -1252,19 +1256,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1252
1256
  function longestIncomingWalk(graph, start, maxDepth) {
1253
1257
  let best = { path: [start], edges: [] };
1254
1258
  const visited = /* @__PURE__ */ new Set([start]);
1255
- function step(node, path56, edges) {
1256
- if (path56.length > best.path.length) {
1257
- best = { path: [...path56], edges: [...edges] };
1259
+ function step(node, path57, edges) {
1260
+ if (path57.length > best.path.length) {
1261
+ best = { path: [...path57], edges: [...edges] };
1258
1262
  }
1259
- if (path56.length - 1 >= maxDepth) return;
1263
+ if (path57.length - 1 >= maxDepth) return;
1260
1264
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1261
1265
  for (const [srcId, edge] of incoming) {
1262
1266
  if (visited.has(srcId)) continue;
1263
1267
  visited.add(srcId);
1264
- path56.push(srcId);
1268
+ path57.push(srcId);
1265
1269
  edges.push(edge);
1266
- step(srcId, path56, edges);
1267
- path56.pop();
1270
+ step(srcId, path57, edges);
1271
+ path57.pop();
1268
1272
  edges.pop();
1269
1273
  visited.delete(srcId);
1270
1274
  }
@@ -1279,7 +1283,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
1279
1283
  for (const id of walk3.path) {
1280
1284
  const owner = resolveOwningService(graph, id);
1281
1285
  if (!owner) continue;
1282
- const { id: serviceId4, svc } = owner;
1286
+ const { id: serviceId5, svc } = owner;
1283
1287
  const deps = svc.dependencies ?? {};
1284
1288
  for (const pair of candidatePairs) {
1285
1289
  const declared = deps[pair.driver];
@@ -1292,7 +1296,7 @@ function databaseRootCauseShape(graph, origin, walk3) {
1292
1296
  );
1293
1297
  if (!result.compatible) {
1294
1298
  return {
1295
- rootCauseNode: serviceId4,
1299
+ rootCauseNode: serviceId5,
1296
1300
  rootCauseReason: result.reason ?? "incompatible driver",
1297
1301
  ...result.minDriverVersion ? {
1298
1302
  fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
@@ -1307,7 +1311,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1307
1311
  for (const id of walk3.path) {
1308
1312
  const owner = resolveOwningService(graph, id);
1309
1313
  if (!owner) continue;
1310
- const { id: serviceId4, svc } = owner;
1314
+ const { id: serviceId5, svc } = owner;
1311
1315
  const deps = svc.dependencies ?? {};
1312
1316
  const serviceNodeEngine = svc.nodeEngine;
1313
1317
  for (const constraint of nodeEngineConstraints()) {
@@ -1316,7 +1320,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1316
1320
  const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
1317
1321
  if (!result.compatible && result.reason) {
1318
1322
  return {
1319
- rootCauseNode: serviceId4,
1323
+ rootCauseNode: serviceId5,
1320
1324
  rootCauseReason: result.reason,
1321
1325
  ...result.requiredNodeVersion ? {
1322
1326
  fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
@@ -1331,7 +1335,7 @@ function serviceRootCauseShape(graph, _origin, walk3) {
1331
1335
  const result = checkPackageConflict(conflict, declared, requiredDeclared);
1332
1336
  if (!result.compatible && result.reason) {
1333
1337
  return {
1334
- rootCauseNode: serviceId4,
1338
+ rootCauseNode: serviceId5,
1335
1339
  rootCauseReason: result.reason,
1336
1340
  fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
1337
1341
  };
@@ -1422,9 +1426,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
1422
1426
  function isFailingCallEdge(e) {
1423
1427
  return e.type === import_types.EdgeType.CALLS && (e.signal?.errorCount ?? 0) > 0;
1424
1428
  }
1425
- function callSourcesForService(graph, serviceId4) {
1426
- const ids = [serviceId4];
1427
- for (const edgeId of graph.outboundEdges(serviceId4)) {
1429
+ function callSourcesForService(graph, serviceId5) {
1430
+ const ids = [serviceId5];
1431
+ for (const edgeId of graph.outboundEdges(serviceId5)) {
1428
1432
  const e = graph.getEdgeAttributes(edgeId);
1429
1433
  if (e.type !== import_types.EdgeType.CONTAINS) continue;
1430
1434
  const tgt = graph.getNodeAttributes(e.target);
@@ -1441,9 +1445,9 @@ function failingCallDominates(e, id, curEdge, curId) {
1441
1445
  }
1442
1446
  return id < curId;
1443
1447
  }
1444
- function dominantFailingCall(graph, serviceId4, visited) {
1448
+ function dominantFailingCall(graph, serviceId5, visited) {
1445
1449
  let best = null;
1446
- for (const src of callSourcesForService(graph, serviceId4)) {
1450
+ for (const src of callSourcesForService(graph, serviceId5)) {
1447
1451
  for (const edgeId of graph.outboundEdges(src)) {
1448
1452
  const e = graph.getEdgeAttributes(edgeId);
1449
1453
  if (!isFailingCallEdge(e)) continue;
@@ -1458,26 +1462,26 @@ function dominantFailingCall(graph, serviceId4, visited) {
1458
1462
  return best;
1459
1463
  }
1460
1464
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1461
- const path56 = [originServiceId];
1465
+ const path57 = [originServiceId];
1462
1466
  const edges = [];
1463
1467
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1464
1468
  let current = originServiceId;
1465
1469
  for (let depth = 0; depth < maxDepth; depth++) {
1466
1470
  const hop = dominantFailingCall(graph, current, visited);
1467
1471
  if (!hop) break;
1468
- path56.push(hop.nextService);
1472
+ path57.push(hop.nextService);
1469
1473
  edges.push(hop.edge);
1470
1474
  visited.add(hop.nextService);
1471
1475
  current = hop.nextService;
1472
1476
  }
1473
1477
  if (edges.length === 0) return null;
1474
- return { path: path56, edges, culprit: current };
1478
+ return { path: path57, edges, culprit: current };
1475
1479
  }
1476
1480
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1477
1481
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1478
1482
  if (!chain) return null;
1479
1483
  const culprit = chain.culprit;
1480
- const path56 = [...chain.path];
1484
+ const path57 = [...chain.path];
1481
1485
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1482
1486
  const baseConfidence = confidenceFromMix(chain.edges);
1483
1487
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1485,14 +1489,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1485
1489
  if (loc) {
1486
1490
  let rootCauseNode = culprit;
1487
1491
  if (loc.fileNode) {
1488
- path56.push(loc.fileNode);
1492
+ path57.push(loc.fileNode);
1489
1493
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1490
1494
  rootCauseNode = loc.fileNode;
1491
1495
  }
1492
1496
  return import_types.RootCauseResultSchema.parse({
1493
1497
  rootCauseNode,
1494
1498
  rootCauseReason: loc.rootCauseReason,
1495
- traversalPath: path56,
1499
+ traversalPath: path57,
1496
1500
  edgeProvenances,
1497
1501
  confidence,
1498
1502
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1504,7 +1508,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1504
1508
  return import_types.RootCauseResultSchema.parse({
1505
1509
  rootCauseNode: culprit,
1506
1510
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1507
- traversalPath: path56,
1511
+ traversalPath: path57,
1508
1512
  edgeProvenances,
1509
1513
  confidence,
1510
1514
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -3060,29 +3064,29 @@ function promoteFrontierNodes(graph, opts = {}) {
3060
3064
  toPromote.push({ frontierId: id, serviceId: target });
3061
3065
  });
3062
3066
  let promoted = 0;
3063
- for (const { frontierId: frontierId2, serviceId: serviceId4 } of toPromote) {
3067
+ for (const { frontierId: frontierId2, serviceId: serviceId5 } of toPromote) {
3064
3068
  if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
3065
3069
  const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
3066
3070
  if (!gate.allowed) {
3067
3071
  continue;
3068
3072
  }
3069
3073
  }
3070
- rewireFrontierEdges(graph, frontierId2, serviceId4);
3074
+ rewireFrontierEdges(graph, frontierId2, serviceId5);
3071
3075
  graph.dropNode(frontierId2);
3072
3076
  promoted++;
3073
3077
  }
3074
3078
  return promoted;
3075
3079
  }
3076
- function rewireFrontierEdges(graph, frontierId2, serviceId4) {
3080
+ function rewireFrontierEdges(graph, frontierId2, serviceId5) {
3077
3081
  const inbound = [...graph.inboundEdges(frontierId2)];
3078
3082
  const outbound = [...graph.outboundEdges(frontierId2)];
3079
3083
  for (const edgeId of inbound) {
3080
3084
  const edge = graph.getEdgeAttributes(edgeId);
3081
- rebuildEdge(graph, edge, edge.source, serviceId4, edgeId);
3085
+ rebuildEdge(graph, edge, edge.source, serviceId5, edgeId);
3082
3086
  }
3083
3087
  for (const edgeId of outbound) {
3084
3088
  const edge = graph.getEdgeAttributes(edgeId);
3085
- rebuildEdge(graph, edge, serviceId4, edge.target, edgeId);
3089
+ rebuildEdge(graph, edge, serviceId5, edge.target, edgeId);
3086
3090
  }
3087
3091
  }
3088
3092
  function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
@@ -3233,24 +3237,58 @@ function dedupeIncidents(events) {
3233
3237
  return !hasRealFailure.has(groupKey(ev));
3234
3238
  });
3235
3239
  }
3240
+ var SnapshotValidationError = class extends Error {
3241
+ constructor(issues) {
3242
+ super(`snapshot failed validation (${issues.length} invalid ${issues.length === 1 ? "entry" : "entries"})`);
3243
+ this.issues = issues;
3244
+ this.name = "SnapshotValidationError";
3245
+ }
3246
+ issues;
3247
+ };
3248
+ function describeZodIssues(error) {
3249
+ return error.issues.map((issue) => issue.path.length > 0 ? `${issue.path.join(".")}: ${issue.message}` : issue.message).join("; ");
3250
+ }
3236
3251
  function mergeSnapshot(graph, snapshot) {
3237
3252
  const exported = snapshot.graph;
3253
+ const incomingNodes = Array.isArray(exported.nodes) ? exported.nodes : [];
3254
+ const incomingEdges = Array.isArray(exported.edges) ? exported.edges : [];
3255
+ const issues = [];
3256
+ const validNodes = [];
3257
+ const validEdges = [];
3258
+ for (const node of incomingNodes) {
3259
+ if (node.attributes === void 0) continue;
3260
+ const parsed = import_types3.GraphNodeSchema.safeParse(node.attributes);
3261
+ if (!parsed.success) {
3262
+ issues.push(`node "${node.key}": ${describeZodIssues(parsed.error)}`);
3263
+ continue;
3264
+ }
3265
+ validNodes.push({ key: node.key, attributes: parsed.data });
3266
+ }
3267
+ for (const edge of incomingEdges) {
3268
+ if (edge.attributes === void 0) continue;
3269
+ const parsed = import_types3.GraphEdgeSchema.safeParse(edge.attributes);
3270
+ if (!parsed.success) {
3271
+ const label = edge.key ?? `${edge.source}->${edge.target}`;
3272
+ issues.push(`edge "${label}": ${describeZodIssues(parsed.error)}`);
3273
+ continue;
3274
+ }
3275
+ const id = edge.key ?? parsed.data.id;
3276
+ validEdges.push({ key: id, source: edge.source, target: edge.target, attributes: parsed.data });
3277
+ }
3278
+ if (issues.length > 0) {
3279
+ throw new SnapshotValidationError(issues);
3280
+ }
3238
3281
  let nodesAdded = 0;
3239
3282
  let edgesAdded = 0;
3240
- for (const node of exported.nodes ?? []) {
3283
+ for (const node of validNodes) {
3241
3284
  if (graph.hasNode(node.key)) continue;
3242
- if (!node.attributes) continue;
3243
3285
  graph.addNode(node.key, node.attributes);
3244
3286
  nodesAdded++;
3245
3287
  }
3246
- for (const edge of exported.edges ?? []) {
3247
- const attrs = edge.attributes;
3248
- if (!attrs) continue;
3249
- const id = edge.key ?? attrs.id;
3250
- if (!id) continue;
3251
- if (graph.hasEdge(id)) continue;
3288
+ for (const edge of validEdges) {
3289
+ if (graph.hasEdge(edge.key)) continue;
3252
3290
  if (!graph.hasNode(edge.source) || !graph.hasNode(edge.target)) continue;
3253
- graph.addEdgeWithKey(id, edge.source, edge.target, attrs);
3291
+ graph.addEdgeWithKey(edge.key, edge.source, edge.target, edge.attributes);
3254
3292
  edgesAdded++;
3255
3293
  }
3256
3294
  return { nodesAdded, edgesAdded };
@@ -3856,9 +3894,9 @@ var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
3856
3894
  "StatefulSet",
3857
3895
  "DaemonSet"
3858
3896
  ]);
3859
- function addAliases(graph, serviceId4, candidates) {
3860
- if (!graph.hasNode(serviceId4)) return;
3861
- const node = graph.getNodeAttributes(serviceId4);
3897
+ function addAliases(graph, serviceId5, candidates) {
3898
+ if (!graph.hasNode(serviceId5)) return;
3899
+ const node = graph.getNodeAttributes(serviceId5);
3862
3900
  if (node.type !== import_types6.NodeType.ServiceNode) return;
3863
3901
  const set = new Set(node.aliases ?? []);
3864
3902
  for (const c of candidates) {
@@ -3868,7 +3906,7 @@ function addAliases(graph, serviceId4, candidates) {
3868
3906
  }
3869
3907
  if (set.size === 0) return;
3870
3908
  const updated = { ...node, aliases: [...set].sort() };
3871
- graph.replaceNodeAttributes(serviceId4, updated);
3909
+ graph.replaceNodeAttributes(serviceId5, updated);
3872
3910
  }
3873
3911
  function indexServicesByName(services) {
3874
3912
  const map = /* @__PURE__ */ new Map();
@@ -3901,12 +3939,12 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
3901
3939
  }
3902
3940
  if (!compose?.services) return;
3903
3941
  for (const [composeName, svc] of Object.entries(compose.services)) {
3904
- const serviceId4 = serviceIndex.get(composeName);
3905
- if (!serviceId4) continue;
3942
+ const serviceId5 = serviceIndex.get(composeName);
3943
+ if (!serviceId5) continue;
3906
3944
  const aliases = /* @__PURE__ */ new Set([composeName]);
3907
3945
  if (svc.container_name) aliases.add(svc.container_name);
3908
3946
  if (svc.hostname) aliases.add(svc.hostname);
3909
- addAliases(graph, serviceId4, aliases);
3947
+ addAliases(graph, serviceId5, aliases);
3910
3948
  }
3911
3949
  }
3912
3950
  var LABEL_KEYS = /* @__PURE__ */ new Set([
@@ -6943,13 +6981,13 @@ function bucketKey(source, target, type) {
6943
6981
  return `${type}|${source}|${target}`;
6944
6982
  }
6945
6983
  function bucketEdges(graph) {
6946
- const buckets = /* @__PURE__ */ new Map();
6984
+ const buckets2 = /* @__PURE__ */ new Map();
6947
6985
  graph.forEachEdge((id, attrs) => {
6948
6986
  const e = attrs;
6949
6987
  const parsed = (0, import_types26.parseEdgeId)(id);
6950
6988
  const provenance = parsed?.provenance ?? e.provenance;
6951
6989
  const key = bucketKey(e.source, e.target, e.type);
6952
- const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
6990
+ const cur = buckets2.get(key) ?? { source: e.source, target: e.target, type: e.type };
6953
6991
  switch (provenance) {
6954
6992
  case import_types26.Provenance.EXTRACTED:
6955
6993
  cur.extracted = e;
@@ -6963,9 +7001,9 @@ function bucketEdges(graph) {
6963
7001
  default:
6964
7002
  if (e.provenance === import_types26.Provenance.STALE) cur.stale = e;
6965
7003
  }
6966
- buckets.set(key, cur);
7004
+ buckets2.set(key, cur);
6967
7005
  });
6968
- return buckets;
7006
+ return buckets2;
6969
7007
  }
6970
7008
  function nodeIsFrontier(graph, nodeId) {
6971
7009
  if (!graph.hasNode(nodeId)) return false;
@@ -7156,8 +7194,8 @@ function suppressHostMismatchHalves(all) {
7156
7194
  }
7157
7195
  function computeDivergences(graph, opts = {}) {
7158
7196
  const all = [];
7159
- const buckets = bucketEdges(graph);
7160
- for (const bucket of buckets.values()) {
7197
+ const buckets2 = bucketEdges(graph);
7198
+ for (const bucket of buckets2.values()) {
7161
7199
  for (const d of detectMissingDivergences(graph, bucket)) all.push(d);
7162
7200
  }
7163
7201
  graph.forEachNode((nodeId, attrs) => {
@@ -7456,8 +7494,8 @@ function formatIncompat(inc) {
7456
7494
 
7457
7495
  // src/watch.ts
7458
7496
  init_cjs_shims();
7459
- var import_node_fs30 = __toESM(require("fs"), 1);
7460
- var import_node_path49 = __toESM(require("path"), 1);
7497
+ var import_node_fs31 = __toESM(require("fs"), 1);
7498
+ var import_node_path50 = __toESM(require("path"), 1);
7461
7499
  var import_chokidar = __toESM(require("chokidar"), 1);
7462
7500
 
7463
7501
  // src/api.ts
@@ -7781,6 +7819,51 @@ async function rollbackExtension(ctx, args) {
7781
7819
  };
7782
7820
  }
7783
7821
 
7822
+ // src/logs-store.ts
7823
+ init_cjs_shims();
7824
+ var LOGS_STORE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
7825
+ var LOGS_QUERY_DEFAULT_LIMIT = 100;
7826
+ var LOGS_QUERY_MAX_LIMIT = 1e3;
7827
+ var buffers = /* @__PURE__ */ new Map();
7828
+ var KEY_SEP = "::";
7829
+ function bufferKey(projectName, source) {
7830
+ return `${projectName}${KEY_SEP}${source}`;
7831
+ }
7832
+ function sourcesForProject(projectName) {
7833
+ const prefix = `${projectName}${KEY_SEP}`;
7834
+ const sources = [];
7835
+ for (const key of buffers.keys()) {
7836
+ if (key.startsWith(prefix)) sources.push(key.slice(prefix.length));
7837
+ }
7838
+ return sources;
7839
+ }
7840
+ function queryLogEntries(opts) {
7841
+ const { projectName, source, service, since, limit } = opts;
7842
+ const sources = source && source.length > 0 ? source : sourcesForProject(projectName);
7843
+ let merged = [];
7844
+ for (const src of sources) {
7845
+ const buf = buffers.get(bufferKey(projectName, src));
7846
+ if (buf) merged = merged.concat(buf);
7847
+ }
7848
+ if (service) {
7849
+ merged = merged.filter((e) => e.serviceName === service);
7850
+ }
7851
+ if (since) {
7852
+ const sinceMs = Date.parse(since);
7853
+ if (!Number.isNaN(sinceMs)) {
7854
+ merged = merged.filter((e) => {
7855
+ const t = Date.parse(e.timestamp);
7856
+ return Number.isNaN(t) || t >= sinceMs;
7857
+ });
7858
+ }
7859
+ }
7860
+ merged.sort((a, b) => Date.parse(b.timestamp) - Date.parse(a.timestamp));
7861
+ if (typeof limit === "number" && Number.isFinite(limit) && limit >= 0) {
7862
+ return merged.slice(0, limit);
7863
+ }
7864
+ return merged;
7865
+ }
7866
+
7784
7867
  // src/diff.ts
7785
7868
  init_cjs_shims();
7786
7869
  var import_node_fs25 = require("fs");
@@ -8556,6 +8639,23 @@ function registerRoutes(scope, ctx) {
8556
8639
  const sliced = ordered.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 50);
8557
8640
  return { count: sliced.length, total, events: sliced };
8558
8641
  });
8642
+ scope.get("/logs", async (req, reply) => {
8643
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
8644
+ if (!proj) return;
8645
+ const rawSource = req.query.source;
8646
+ const sources = rawSource === void 0 ? void 0 : Array.isArray(rawSource) ? rawSource : [rawSource];
8647
+ const filtered = queryLogEntries({
8648
+ projectName: proj.name,
8649
+ ...sources && sources.length > 0 ? { source: sources } : {},
8650
+ ...req.query.service ? { service: req.query.service } : {},
8651
+ ...req.query.since ? { since: req.query.since } : {}
8652
+ });
8653
+ const total = filtered.length;
8654
+ const limit = req.query.limit ? Number(req.query.limit) : LOGS_QUERY_DEFAULT_LIMIT;
8655
+ const safeLimit = Number.isFinite(limit) && limit > 0 ? Math.min(limit, LOGS_QUERY_MAX_LIMIT) : LOGS_QUERY_DEFAULT_LIMIT;
8656
+ const sliced = filtered.slice(0, safeLimit);
8657
+ return { count: sliced.length, total, logs: sliced };
8658
+ });
8559
8659
  const incidentHistoryHandler = async (req, reply) => {
8560
8660
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
8561
8661
  if (!proj) return;
@@ -8651,8 +8751,16 @@ function registerRoutes(scope, ctx) {
8651
8751
  if (!against) {
8652
8752
  return reply.code(400).send({ error: "query parameter `against` is required" });
8653
8753
  }
8754
+ const targetProjectName = against === "self" ? proj.name : against;
8755
+ const targetProject = registry.get(targetProjectName);
8756
+ if (!targetProject) {
8757
+ return reply.code(400).send({
8758
+ error: "unknown snapshot id \u2014 `against` must be `self` or the name of a project this server has a snapshot for",
8759
+ against
8760
+ });
8761
+ }
8654
8762
  try {
8655
- const snapshot = await loadSnapshotForDiff(against);
8763
+ const snapshot = await loadSnapshotForDiff(targetProject.paths.snapshotPath);
8656
8764
  return computeGraphDiff(proj.graph, snapshot);
8657
8765
  } catch (err) {
8658
8766
  return reply.code(400).send({ error: "failed to load snapshot", against, detail: err.message });
@@ -8682,6 +8790,13 @@ function registerRoutes(scope, ctx) {
8682
8790
  edgeCount: proj.graph.size
8683
8791
  };
8684
8792
  } catch (err) {
8793
+ if (err instanceof SnapshotValidationError) {
8794
+ return reply.code(400).send({
8795
+ error: "snapshot merge failed",
8796
+ details: err.message,
8797
+ issues: err.issues
8798
+ });
8799
+ }
8685
8800
  return reply.code(400).send({
8686
8801
  error: "snapshot merge failed",
8687
8802
  details: err.message
@@ -9028,164 +9143,1779 @@ init_otel();
9028
9143
 
9029
9144
  // src/daemon.ts
9030
9145
  init_cjs_shims();
9031
- var import_node_fs28 = require("fs");
9032
- var import_node_path47 = __toESM(require("path"), 1);
9146
+ var import_node_fs29 = require("fs");
9147
+ var import_node_path48 = __toESM(require("path"), 1);
9033
9148
  var import_node_module = require("module");
9034
9149
  init_otel();
9035
9150
 
9036
9151
  // src/connectors/index.ts
9037
9152
  init_cjs_shims();
9038
9153
 
9039
- // src/daemon.ts
9040
- init_auth();
9041
-
9042
- // src/unrouted.ts
9154
+ // src/connectors/registry.ts
9043
9155
  init_cjs_shims();
9044
- var import_node_fs27 = require("fs");
9045
- var import_node_path46 = __toESM(require("path"), 1);
9046
9156
 
9047
- // src/daemon.ts
9048
- var import_types31 = require("@neat.is/types");
9049
- function daemonJsonPath(scanPath) {
9050
- return import_node_path47.default.join(scanPath, "neat-out", "daemon.json");
9051
- }
9052
- function daemonsDiscoveryDir(home) {
9053
- const base = home && home.length > 0 ? home : neatHomeFromEnv();
9054
- return import_node_path47.default.join(base, "daemons");
9157
+ // src/connectors/junction.ts
9158
+ init_cjs_shims();
9159
+ var buckets = /* @__PURE__ */ new Map();
9160
+ function bucketMapKey(provider, accountKey) {
9161
+ return `${provider}\0${accountKey}`;
9162
+ }
9163
+ function getBucket(provider, accountKey, config) {
9164
+ const key = bucketMapKey(provider, accountKey);
9165
+ const existing = buckets.get(key);
9166
+ if (existing && existing.capacity === config.capacity && existing.refillMs === config.refillMs) {
9167
+ return existing;
9168
+ }
9169
+ const fresh = { ...config, tokens: config.capacity, updatedAt: Date.now() };
9170
+ buckets.set(key, fresh);
9171
+ return fresh;
9172
+ }
9173
+ function refillBucket(bucket, now) {
9174
+ if (now <= bucket.updatedAt) return;
9175
+ const elapsed = now - bucket.updatedAt;
9176
+ const grant = elapsed / bucket.refillMs;
9177
+ if (grant <= 0) return;
9178
+ bucket.tokens = Math.min(bucket.capacity, bucket.tokens + grant);
9179
+ bucket.updatedAt = now;
9180
+ }
9181
+ var RateLimitExceededError = class extends Error {
9182
+ constructor(provider, accountKey) {
9183
+ super(
9184
+ `junction: rate limit exceeded for ${provider}:${accountKey} \u2014 waiting for the next token would exceed this call's wall-clock budget`
9185
+ );
9186
+ this.name = "RateLimitExceededError";
9187
+ }
9188
+ };
9189
+ function delay(ms) {
9190
+ if (ms <= 0) return Promise.resolve();
9191
+ return new Promise((resolve) => {
9192
+ const timer = setTimeout(resolve, ms);
9193
+ if (typeof timer.unref === "function") timer.unref();
9194
+ });
9055
9195
  }
9056
- function daemonDiscoveryPath(project, home) {
9057
- return import_node_path47.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
9196
+ async function acquireToken(provider, accountKey, config, remainingBudgetMs) {
9197
+ const bucket = getBucket(provider, accountKey, config);
9198
+ refillBucket(bucket, Date.now());
9199
+ if (bucket.tokens >= 1) {
9200
+ bucket.tokens -= 1;
9201
+ return 0;
9202
+ }
9203
+ const waitMs = Math.ceil((1 - bucket.tokens) * bucket.refillMs);
9204
+ if (waitMs > remainingBudgetMs) {
9205
+ throw new RateLimitExceededError(provider, accountKey);
9206
+ }
9207
+ await delay(waitMs);
9208
+ refillBucket(bucket, Date.now());
9209
+ bucket.tokens = Math.max(0, bucket.tokens - 1);
9210
+ return waitMs;
9211
+ }
9212
+ var JUNCTION_DEFAULT_RATE_LIMITS = {
9213
+ // ~300 requests / 5 minutes (ADR-131). Burst capacity holds a third of
9214
+ // that ceiling; steady-state refill (1 token / 3s = 20/min = 100/5min)
9215
+ // stays well clear of the documented limit even under sustained polling.
9216
+ cloudflare: { capacity: 100, refillMs: 3e3 },
9217
+ // Placeholder pending a live project confirming the real cap
9218
+ // (docs/connectors/railway.md: "does not appear to publish one as of this
9219
+ // writing").
9220
+ railway: { capacity: 30, refillMs: 1e4 },
9221
+ // Placeholder pending a live rate-limit check (docs/connectors/
9222
+ // firebase.md: "needs-endpoint-testing against entries.list's live rate
9223
+ // limits").
9224
+ firebase: { capacity: 30, refillMs: 1e4 },
9225
+ // Placeholder pending a live rate-limit check (docs/connectors/supabase.md:
9226
+ // "the documented rate limit for this specific endpoint is unconfirmed").
9227
+ supabase: { capacity: 30, refillMs: 1e4 },
9228
+ // Not a documented API limit at all — a self-imposed ceiling on the raw
9229
+ // pg_stat_statements connection (see module header above).
9230
+ "supabase-postgres": { capacity: 20, refillMs: 3e3 }
9231
+ };
9232
+ var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
9233
+ function defaultRateLimitFor(provider) {
9234
+ return JUNCTION_DEFAULT_RATE_LIMITS[provider] ?? JUNCTION_GENERIC_RATE_LIMIT;
9235
+ }
9236
+ var JUNCTION_DEFAULT_TIMEOUT_MS = 1e4;
9237
+ var JUNCTION_DEFAULT_MAX_ATTEMPTS = 3;
9238
+ var JUNCTION_DEFAULT_INITIAL_BACKOFF_MS = 200;
9239
+ var JUNCTION_DEFAULT_BACKOFF_MULTIPLIER = 4;
9240
+ var JUNCTION_DEFAULT_MAX_ELAPSED_MS = 3e4;
9241
+ var JUNCTION_DEFAULT_DB_TIMEOUT_MS = 1e4;
9242
+ async function backoff(attempt, initialBackoffMs, backoffMultiplier, remainingBudgetMs) {
9243
+ const raw = initialBackoffMs * backoffMultiplier ** (attempt - 1);
9244
+ const capped = Math.max(0, Math.min(raw, remainingBudgetMs));
9245
+ await delay(capped);
9246
+ }
9247
+ function safeUrlLabel(url) {
9248
+ try {
9249
+ const u = typeof url === "string" ? new URL(url) : url;
9250
+ return `${u.origin}${u.pathname}`;
9251
+ } catch {
9252
+ return String(url);
9253
+ }
9058
9254
  }
9059
- function sanitizeDiscoveryName(project) {
9060
- return project.replace(/[^A-Za-z0-9._-]/g, "_");
9255
+ function logOutcome(provider, accountKey, outcome, method, label, attempt, startedAt) {
9256
+ const elapsedMs = Date.now() - startedAt;
9257
+ const line = `[neat connector] ${provider}:${accountKey} ${method} ${label} \u2014 ${outcome} (attempt ${attempt}, ${elapsedMs}ms)`;
9258
+ if (outcome === "success" || outcome === "retried-then-succeeded") {
9259
+ console.log(line);
9260
+ } else if (outcome === "rate-limited") {
9261
+ console.warn(line);
9262
+ } else {
9263
+ console.error(line);
9264
+ }
9061
9265
  }
9062
- function neatHomeFromEnv() {
9063
- const env = process.env.NEAT_HOME;
9064
- if (env && env.length > 0) return import_node_path47.default.resolve(env);
9065
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9066
- return import_node_path47.default.join(home, ".neat");
9266
+ function bearerAuthHeader(token) {
9267
+ return { Authorization: `Bearer ${token}` };
9067
9268
  }
9068
- async function readDaemonRecord(scanPath) {
9069
- try {
9070
- const raw = await import_node_fs28.promises.readFile(daemonJsonPath(scanPath), "utf8");
9071
- const parsed = JSON.parse(raw);
9072
- if (typeof parsed.project === "string" && parsed.ports && typeof parsed.ports.rest === "number" && typeof parsed.ports.otlp === "number" && typeof parsed.ports.web === "number") {
9073
- return parsed;
9269
+ async function junctionFetch(url, init = {}, policy) {
9270
+ const {
9271
+ provider,
9272
+ accountKey,
9273
+ timeoutMs = JUNCTION_DEFAULT_TIMEOUT_MS,
9274
+ maxAttempts = JUNCTION_DEFAULT_MAX_ATTEMPTS,
9275
+ maxElapsedMs = JUNCTION_DEFAULT_MAX_ELAPSED_MS,
9276
+ initialBackoffMs = JUNCTION_DEFAULT_INITIAL_BACKOFF_MS,
9277
+ backoffMultiplier = JUNCTION_DEFAULT_BACKOFF_MULTIPLIER,
9278
+ rateLimit = defaultRateLimitFor(provider),
9279
+ fetchImpl = fetch
9280
+ } = policy;
9281
+ const method = (init.method ?? "GET").toUpperCase();
9282
+ const label = safeUrlLabel(url);
9283
+ const startedAt = Date.now();
9284
+ let attempt = 0;
9285
+ let sawRetry = false;
9286
+ for (; ; ) {
9287
+ attempt++;
9288
+ const remainingBudget = maxElapsedMs - (Date.now() - startedAt);
9289
+ if (remainingBudget <= 0) {
9290
+ logOutcome(provider, accountKey, "retried-then-failed", method, label, attempt - 1, startedAt);
9291
+ throw new Error(
9292
+ `junction: ${provider}:${accountKey} ${method} ${label} exceeded its wall-clock budget (${maxElapsedMs}ms) after ${attempt - 1} attempt(s)`
9293
+ );
9294
+ }
9295
+ try {
9296
+ await acquireToken(provider, accountKey, rateLimit, remainingBudget);
9297
+ } catch (err) {
9298
+ if (err instanceof RateLimitExceededError) {
9299
+ logOutcome(provider, accountKey, "rate-limited", method, label, attempt, startedAt);
9300
+ }
9301
+ throw err;
9302
+ }
9303
+ const controller = new AbortController();
9304
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
9305
+ if (typeof timer.unref === "function") timer.unref();
9306
+ try {
9307
+ const res = await fetchImpl(url, { ...init, signal: controller.signal });
9308
+ clearTimeout(timer);
9309
+ if (res.ok || res.status < 500) {
9310
+ logOutcome(provider, accountKey, sawRetry ? "retried-then-succeeded" : "success", method, label, attempt, startedAt);
9311
+ return res;
9312
+ }
9313
+ if (attempt >= maxAttempts) {
9314
+ logOutcome(provider, accountKey, sawRetry ? "retried-then-failed" : "failed", method, label, attempt, startedAt);
9315
+ return res;
9316
+ }
9317
+ sawRetry = true;
9318
+ await backoff(attempt, initialBackoffMs, backoffMultiplier, maxElapsedMs - (Date.now() - startedAt));
9319
+ } catch (err) {
9320
+ clearTimeout(timer);
9321
+ if (attempt >= maxAttempts) {
9322
+ logOutcome(provider, accountKey, sawRetry ? "retried-then-failed" : "failed", method, label, attempt, startedAt);
9323
+ throw err;
9324
+ }
9325
+ sawRetry = true;
9326
+ await backoff(attempt, initialBackoffMs, backoffMultiplier, maxElapsedMs - (Date.now() - startedAt));
9074
9327
  }
9075
- return null;
9076
- } catch {
9077
- return null;
9078
9328
  }
9079
9329
  }
9080
- function resolveNeatVersion() {
9081
- if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
9082
- return process.env.NEAT_LOCAL_VERSION;
9330
+ var DbJunctionTimeoutError = class extends Error {
9331
+ constructor(ms) {
9332
+ super(`junction: db query exceeded its ${ms}ms timeout`);
9333
+ this.name = "DbJunctionTimeoutError";
9083
9334
  }
9084
- try {
9085
- const req = (0, import_node_module.createRequire)(importMetaUrl);
9086
- const pkg = req("../package.json");
9087
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
9088
- } catch {
9089
- return "0.0.0";
9335
+ };
9336
+ function withTimeout(run, timeoutMs) {
9337
+ return new Promise((resolve, reject) => {
9338
+ const timer = setTimeout(() => reject(new DbJunctionTimeoutError(timeoutMs)), timeoutMs);
9339
+ if (typeof timer.unref === "function") timer.unref();
9340
+ run().then(
9341
+ (value) => {
9342
+ clearTimeout(timer);
9343
+ resolve(value);
9344
+ },
9345
+ (err) => {
9346
+ clearTimeout(timer);
9347
+ reject(err);
9348
+ }
9349
+ );
9350
+ });
9351
+ }
9352
+ var RETRYABLE_NODE_ERROR_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "ECONNRESET", "ETIMEDOUT", "EHOSTUNREACH", "EAI_AGAIN", "EPIPE"]);
9353
+ function isRetryableDbError(err) {
9354
+ if (err instanceof DbJunctionTimeoutError) return true;
9355
+ const code = err?.code;
9356
+ if (typeof code !== "string") return false;
9357
+ if (code.startsWith("08") || code === "57P03") return true;
9358
+ return RETRYABLE_NODE_ERROR_CODES.has(code);
9359
+ }
9360
+ async function dbJunction(run, policy) {
9361
+ const {
9362
+ provider,
9363
+ accountKey,
9364
+ timeoutMs = JUNCTION_DEFAULT_DB_TIMEOUT_MS,
9365
+ maxAttempts = JUNCTION_DEFAULT_MAX_ATTEMPTS,
9366
+ maxElapsedMs = JUNCTION_DEFAULT_MAX_ELAPSED_MS,
9367
+ initialBackoffMs = JUNCTION_DEFAULT_INITIAL_BACKOFF_MS,
9368
+ backoffMultiplier = JUNCTION_DEFAULT_BACKOFF_MULTIPLIER,
9369
+ rateLimit = defaultRateLimitFor(provider)
9370
+ } = policy;
9371
+ const startedAt = Date.now();
9372
+ let attempt = 0;
9373
+ let sawRetry = false;
9374
+ for (; ; ) {
9375
+ attempt++;
9376
+ const remainingBudget = maxElapsedMs - (Date.now() - startedAt);
9377
+ if (remainingBudget <= 0) {
9378
+ logOutcome(provider, accountKey, "retried-then-failed", "QUERY", "db", attempt - 1, startedAt);
9379
+ throw new Error(
9380
+ `junction: ${provider}:${accountKey} db query exceeded its wall-clock budget (${maxElapsedMs}ms) after ${attempt - 1} attempt(s)`
9381
+ );
9382
+ }
9383
+ try {
9384
+ await acquireToken(provider, accountKey, rateLimit, remainingBudget);
9385
+ } catch (err) {
9386
+ if (err instanceof RateLimitExceededError) {
9387
+ logOutcome(provider, accountKey, "rate-limited", "QUERY", "db", attempt, startedAt);
9388
+ }
9389
+ throw err;
9390
+ }
9391
+ try {
9392
+ const result = await withTimeout(run, timeoutMs);
9393
+ logOutcome(provider, accountKey, sawRetry ? "retried-then-succeeded" : "success", "QUERY", "db", attempt, startedAt);
9394
+ return result;
9395
+ } catch (err) {
9396
+ if (!isRetryableDbError(err) || attempt >= maxAttempts) {
9397
+ logOutcome(provider, accountKey, sawRetry ? "retried-then-failed" : "failed", "QUERY", "db", attempt, startedAt);
9398
+ throw err;
9399
+ }
9400
+ sawRetry = true;
9401
+ await backoff(attempt, initialBackoffMs, backoffMultiplier, maxElapsedMs - (Date.now() - startedAt));
9402
+ }
9090
9403
  }
9091
9404
  }
9092
- async function writeDaemonRecord(record, home) {
9093
- const body = JSON.stringify(record, null, 2) + "\n";
9094
- await writeAtomically(daemonJsonPath(record.projectPath), body);
9095
- try {
9096
- await writeAtomically(daemonDiscoveryPath(record.project, home), body);
9097
- } catch (err) {
9098
- console.warn(
9099
- `neatd: could not write discovery copy for "${record.project}" \u2014 ${err.message}`
9100
- );
9405
+
9406
+ // src/connectors/supabase/index.ts
9407
+ init_cjs_shims();
9408
+
9409
+ // src/connectors/supabase/client.ts
9410
+ init_cjs_shims();
9411
+ var DEFAULT_SUPABASE_MANAGEMENT_API_URL = "https://api.supabase.com";
9412
+ var DEFAULT_LOG_LIMIT = 1e3;
9413
+ var SUPABASE_LOG_QUERY_MAX_WINDOW_MS = 24 * 60 * 60 * 1e3;
9414
+ function boundedSupabaseLogWindow(since, now, maxLookbackMs) {
9415
+ const window = Math.min(maxLookbackMs, SUPABASE_LOG_QUERY_MAX_WINDOW_MS);
9416
+ const floor = new Date(now.getTime() - window);
9417
+ const endIso = now.toISOString();
9418
+ if (!since) return { startIso: floor.toISOString(), endIso, truncated: false };
9419
+ const sinceMs = new Date(since).getTime();
9420
+ if (Number.isNaN(sinceMs)) return { startIso: floor.toISOString(), endIso, truncated: false };
9421
+ if (sinceMs < floor.getTime()) return { startIso: floor.toISOString(), endIso, truncated: true };
9422
+ return { startIso: new Date(sinceMs).toISOString(), endIso, truncated: false };
9423
+ }
9424
+ function buildEdgeLogsQuery(limit) {
9425
+ const safeLimit = Math.max(1, Math.trunc(limit) || DEFAULT_LOG_LIMIT);
9426
+ return [
9427
+ "select",
9428
+ " format_timestamp('%Y-%m-%dT%H:%M:%E6SZ', timestamp) as timestamp,",
9429
+ " request.method as method,",
9430
+ " request.path as path,",
9431
+ " response.status_code as status_code",
9432
+ "from edge_logs",
9433
+ "cross join unnest(metadata) as metadata",
9434
+ "cross join unnest(metadata.request) as request",
9435
+ "cross join unnest(metadata.response) as response",
9436
+ "where regexp_contains(request.path, '^/rest/v1/')",
9437
+ "order by timestamp asc",
9438
+ `limit ${safeLimit}`
9439
+ ].join("\n");
9440
+ }
9441
+ async function fetchSupabaseEdgeLogs(config, token, startIso, endIso, fetchImpl = fetch) {
9442
+ const baseUrl = config.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
9443
+ const url = new URL(`${baseUrl}/v1/projects/${config.apiProjectRef}/analytics/endpoints/logs.all`);
9444
+ url.searchParams.set("sql", buildEdgeLogsQuery(config.logLimit ?? DEFAULT_LOG_LIMIT));
9445
+ url.searchParams.set("iso_timestamp_start", startIso);
9446
+ url.searchParams.set("iso_timestamp_end", endIso);
9447
+ const res = await junctionFetch(
9448
+ url,
9449
+ { method: "GET", headers: bearerAuthHeader(token) },
9450
+ // accountKey: the Supabase project ref (ADR-131's own worked example) —
9451
+ // the Management API's rate limit is enforced per project.
9452
+ { provider: "supabase", accountKey: config.apiProjectRef, fetchImpl }
9453
+ );
9454
+ if (!res.ok) {
9455
+ throw new Error(`supabase connector: logs.all request failed (${res.status} ${res.statusText})`);
9456
+ }
9457
+ const body = await res.json();
9458
+ if (body.error) {
9459
+ const message = typeof body.error === "string" ? body.error : body.error.message;
9460
+ throw new Error(`supabase connector: logs.all returned an error (${message})`);
9101
9461
  }
9462
+ return body.result ?? [];
9102
9463
  }
9103
- async function clearDaemonRecord(record, home) {
9104
- try {
9105
- const stopped = { ...record, status: "stopped" };
9106
- await writeAtomically(daemonJsonPath(record.projectPath), JSON.stringify(stopped, null, 2) + "\n");
9107
- } catch {
9464
+
9465
+ // src/connectors/supabase/map.ts
9466
+ init_cjs_shims();
9467
+
9468
+ // src/connectors/supabase/types.ts
9469
+ init_cjs_shims();
9470
+ function readSupabaseCredentials(raw) {
9471
+ const managementToken = raw["managementToken"];
9472
+ if (typeof managementToken !== "string" || managementToken.length === 0) {
9473
+ throw new Error("supabase connector: credentials.managementToken must be a non-empty string");
9108
9474
  }
9109
- try {
9110
- await import_node_fs28.promises.unlink(daemonDiscoveryPath(record.project, home));
9111
- } catch {
9475
+ const postgresConnectionString = raw["postgresConnectionString"];
9476
+ if (postgresConnectionString !== void 0 && (typeof postgresConnectionString !== "string" || postgresConnectionString.length === 0)) {
9477
+ throw new Error(
9478
+ "supabase connector: credentials.postgresConnectionString must be a non-empty string when present"
9479
+ );
9112
9480
  }
9481
+ return {
9482
+ managementToken,
9483
+ ...postgresConnectionString ? { postgresConnectionString } : {}
9484
+ };
9113
9485
  }
9114
- function portFromListenAddress(address, fallback) {
9115
- try {
9116
- const port = new URL(address).port;
9117
- const n = Number.parseInt(port, 10);
9118
- if (Number.isFinite(n) && n > 0) return n;
9119
- } catch {
9486
+ var SUPABASE_TABLE_TARGET_KIND = "supabase-table";
9487
+ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
9488
+
9489
+ // src/connectors/supabase/map.ts
9490
+ var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
9491
+ var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
9492
+ function targetFromRestPath(path57) {
9493
+ const rpcMatch = REST_RPC_PATH_RE.exec(path57);
9494
+ if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
9495
+ const tableMatch = REST_TABLE_PATH_RE.exec(path57);
9496
+ if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
9497
+ return null;
9498
+ }
9499
+ var ERROR_STATUS_THRESHOLD = 500;
9500
+ function mapEdgeLogRowsToSignals(rows) {
9501
+ const buckets2 = /* @__PURE__ */ new Map();
9502
+ for (const row of rows) {
9503
+ const target = targetFromRestPath(row.path);
9504
+ if (!target) continue;
9505
+ const key = `${target.targetKind}:${target.name}`;
9506
+ const isError = row.status_code >= ERROR_STATUS_THRESHOLD;
9507
+ const existing = buckets2.get(key);
9508
+ if (existing) {
9509
+ existing.callCount += 1;
9510
+ if (isError) existing.errorCount += 1;
9511
+ if (row.timestamp > existing.lastObservedIso) existing.lastObservedIso = row.timestamp;
9512
+ } else {
9513
+ buckets2.set(key, {
9514
+ targetKind: target.targetKind,
9515
+ targetName: target.name,
9516
+ callCount: 1,
9517
+ errorCount: isError ? 1 : 0,
9518
+ lastObservedIso: row.timestamp
9519
+ });
9520
+ }
9120
9521
  }
9121
- return fallback;
9522
+ return [...buckets2.values()].map((b) => ({
9523
+ targetKind: b.targetKind,
9524
+ targetName: b.targetName,
9525
+ callCount: b.callCount,
9526
+ errorCount: b.errorCount,
9527
+ lastObservedIso: b.lastObservedIso
9528
+ }));
9122
9529
  }
9123
- function resolveHost(opts, authTokenSet) {
9124
- if (opts.host && opts.host.length > 0) return opts.host;
9125
- const env = process.env.HOST;
9126
- if (env && env.length > 0) return env;
9127
- if (!authTokenSet) return "127.0.0.1";
9128
- return "0.0.0.0";
9530
+ var FROM_TABLE_RE = /\bfrom\s+"?(?:[a-z_][a-z0-9_]*"?\.)?"?([a-z_][a-z0-9_]*)"?/i;
9531
+ var SYSTEM_SCHEMA_PREFIXES = ["pg_", "information_schema"];
9532
+ function tableNameFromQueryText(query) {
9533
+ const match = FROM_TABLE_RE.exec(query);
9534
+ if (!match) return null;
9535
+ const name = match[1];
9536
+ const lower = name.toLowerCase();
9537
+ if (SYSTEM_SCHEMA_PREFIXES.some((prefix) => lower.startsWith(prefix))) return null;
9538
+ return name;
9539
+ }
9540
+ function diffPgStatStatementsToSignals(rows, previous, nowIso2) {
9541
+ const signals = [];
9542
+ const seen = /* @__PURE__ */ new Set();
9543
+ for (const row of rows) {
9544
+ seen.add(row.queryid);
9545
+ const calls = Number(row.calls);
9546
+ const prior = previous.get(row.queryid);
9547
+ previous.set(row.queryid, { calls });
9548
+ if (!prior || calls < prior.calls) continue;
9549
+ const delta = calls - prior.calls;
9550
+ if (delta <= 0) continue;
9551
+ const table = tableNameFromQueryText(row.query);
9552
+ if (!table) continue;
9553
+ signals.push({
9554
+ targetKind: SUPABASE_TABLE_TARGET_KIND,
9555
+ targetName: table,
9556
+ callCount: delta,
9557
+ errorCount: 0,
9558
+ lastObservedIso: nowIso2
9559
+ });
9560
+ }
9561
+ for (const queryid of [...previous.keys()]) {
9562
+ if (!seen.has(queryid)) previous.delete(queryid);
9563
+ }
9564
+ return signals;
9129
9565
  }
9130
9566
 
9131
- // src/watch.ts
9132
- init_otel_grpc();
9133
-
9134
- // src/search.ts
9567
+ // src/connectors/supabase/postgres-client.ts
9135
9568
  init_cjs_shims();
9136
- var import_node_fs29 = require("fs");
9137
- var import_node_path48 = __toESM(require("path"), 1);
9138
- var import_node_crypto3 = require("crypto");
9139
- var DEFAULT_LIMIT = 10;
9140
- var NOMIC_DIM = 768;
9141
- var MINI_LM_DIM = 384;
9142
- function shouldEmbed(node) {
9143
- return node.type !== "FrontierNode";
9569
+ var import_pg = __toESM(require("pg"), 1);
9570
+ var { Client } = import_pg.default;
9571
+ var DEFAULT_STATEMENT_LIMIT = 500;
9572
+ var STATEMENTS_QUERY = `
9573
+ select queryid, query, calls, total_exec_time, rows
9574
+ from pg_stat_statements
9575
+ where query ~* '^\\s*select\\b'
9576
+ order by calls desc
9577
+ limit $1
9578
+ `;
9579
+ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT_LIMIT, accountKey = "unknown", clientFactory = (cs) => new Client({ connectionString: cs })) {
9580
+ return dbJunction(
9581
+ async () => {
9582
+ const client = clientFactory(connectionString);
9583
+ await client.connect();
9584
+ try {
9585
+ await client.query("SET default_transaction_read_only = on");
9586
+ const result = await client.query(STATEMENTS_QUERY, [limit]);
9587
+ return result.rows;
9588
+ } finally {
9589
+ await client.end();
9590
+ }
9591
+ },
9592
+ { provider: "supabase-postgres", accountKey }
9593
+ );
9144
9594
  }
9145
- function embedText(node) {
9146
- const parts = [node.id];
9147
- const name = node.name;
9148
- if (name) parts.push(name);
9149
- switch (node.type) {
9150
- case "ServiceNode": {
9151
- const lang = node.language;
9152
- if (lang) parts.push(`language=${lang}`);
9153
- break;
9154
- }
9155
- case "DatabaseNode": {
9156
- const eng = node.engine;
9157
- const ver = node.engineVersion;
9158
- if (eng) parts.push(`engine=${eng}`);
9159
- if (ver) parts.push(`engineVersion=${ver}`);
9160
- break;
9161
- }
9162
- case "InfraNode": {
9163
- const kind = node.kind;
9164
- if (kind) parts.push(`kind=${kind}`);
9165
- break;
9595
+
9596
+ // src/connectors/supabase/resolve.ts
9597
+ init_cjs_shims();
9598
+ var import_types32 = require("@neat.is/types");
9599
+ function createSupabaseResolveTarget(graph, config) {
9600
+ return (signal, _ctx) => {
9601
+ if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
9602
+ return null;
9166
9603
  }
9167
- case "ConfigNode": {
9168
- const filePath = node.path;
9169
- if (filePath) parts.push(`path=${filePath}`);
9170
- break;
9604
+ const subResourceId = (0, import_types32.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
9605
+ if (graph.hasNode(subResourceId)) {
9606
+ return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types32.EdgeType.CALLS };
9171
9607
  }
9172
- case "RouteNode": {
9173
- const method = node.method;
9174
- const tmpl = node.pathTemplate;
9175
- if (method) parts.push(`method=${method}`);
9176
- if (tmpl) parts.push(`path=${tmpl}`);
9177
- break;
9608
+ const projectLevelId = (0, import_types32.infraId)("supabase", config.nodeRef);
9609
+ if (graph.hasNode(projectLevelId)) {
9610
+ return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types32.EdgeType.CALLS };
9178
9611
  }
9179
- case "GraphQLOperationNode": {
9180
- const opType = node.operationType;
9181
- if (opType) parts.push(`operationType=${opType}`);
9182
- break;
9612
+ return null;
9613
+ };
9614
+ }
9615
+
9616
+ // src/connectors/supabase/index.ts
9617
+ var DEFAULT_MAX_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
9618
+ var SupabaseConnector = class {
9619
+ // `deps.fetchPgStatStatements` defaults to the real Postgres-backed
9620
+ // implementation; tests override it to exercise the "both surfaces
9621
+ // combine" and "surface 2 only runs when a connection string is present"
9622
+ // behavior without a live database — the same dependency-injection seam
9623
+ // `fetchImpl` gives cloudflare/client.ts's tests for `fetch`.
9624
+ constructor(config, deps = {}) {
9625
+ this.config = config;
9626
+ this.deps = deps;
9627
+ }
9628
+ config;
9629
+ deps;
9630
+ provider = "supabase";
9631
+ // pg_stat_statements.calls is cumulative, not per-window (map.ts's
9632
+ // diffPgStatStatementsToSignals doc comment) — this Map carries the
9633
+ // previous poll's counts across ticks, the same way
9634
+ // `startConnectorPollLoop` (connectors/index.ts) carries `since` across
9635
+ // ticks for every connector. Lives on the instance, not `ConnectorContext`,
9636
+ // because `ConnectorContext` is rebuilt fresh per tick (connectors/index.ts's
9637
+ // `{ ...ctx, since }`) while this connector object is the one thing every
9638
+ // tick shares.
9639
+ statementBaselines = /* @__PURE__ */ new Map();
9640
+ async poll(ctx) {
9641
+ const creds = readSupabaseCredentials(ctx.credentials);
9642
+ const now = /* @__PURE__ */ new Date();
9643
+ const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS;
9644
+ const { startIso, endIso } = boundedSupabaseLogWindow(ctx.since, now, maxLookbackMs);
9645
+ const logRows = await fetchSupabaseEdgeLogs(this.config, creds.managementToken, startIso, endIso);
9646
+ const signals = mapEdgeLogRowsToSignals(logRows);
9647
+ if (creds.postgresConnectionString) {
9648
+ const fetchStatements = this.deps.fetchPgStatStatements ?? fetchPgStatStatements;
9649
+ const statementRows = await fetchStatements(
9650
+ creds.postgresConnectionString,
9651
+ this.config.statementLimit ?? DEFAULT_STATEMENT_LIMIT,
9652
+ this.config.apiProjectRef
9653
+ );
9654
+ signals.push(...diffPgStatStatementsToSignals(statementRows, this.statementBaselines, now.toISOString()));
9183
9655
  }
9184
- case "GrpcMethodNode": {
9185
- const rpcService = node.rpcService;
9186
- const rpcMethod = node.rpcMethod;
9187
- if (rpcService) parts.push(`rpcService=${rpcService}`);
9188
- if (rpcMethod) parts.push(`rpcMethod=${rpcMethod}`);
9656
+ return signals;
9657
+ }
9658
+ };
9659
+ function createSupabaseConnector(graph, config, deps = {}) {
9660
+ return {
9661
+ connector: new SupabaseConnector(config, deps),
9662
+ resolveTarget: createSupabaseResolveTarget(graph, config)
9663
+ };
9664
+ }
9665
+
9666
+ // src/connectors/railway/index.ts
9667
+ init_cjs_shims();
9668
+ var import_types36 = require("@neat.is/types");
9669
+
9670
+ // src/connectors/railway/client.ts
9671
+ init_cjs_shims();
9672
+ var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
9673
+ var DEFAULT_MAX_LOOKBACK_MS2 = 24 * 60 * 60 * 1e3;
9674
+ var DEFAULT_LOG_LIMIT2 = 1e3;
9675
+ function readRailwayToken(credentials) {
9676
+ const token = credentials.token;
9677
+ if (typeof token !== "string" || token.length === 0) {
9678
+ throw new Error(
9679
+ "Railway connector requires ctx.credentials.token (a Project-Access-Token or account Bearer token)"
9680
+ );
9681
+ }
9682
+ return token;
9683
+ }
9684
+ async function railwayGraphQL(apiUrl, token, query, variables, accountKey) {
9685
+ const res = await junctionFetch(
9686
+ apiUrl,
9687
+ {
9688
+ method: "POST",
9689
+ headers: {
9690
+ "Content-Type": "application/json",
9691
+ // docs.railway.com/integrations/api/graphql-overview confirms
9692
+ // `Authorization: Bearer <token>` for an account/workspace token;
9693
+ // Railway also documents a dedicated `Project-Access-Token: <token>`
9694
+ // header for the project-scoped token ADR-127 targets specifically.
9695
+ // This connector sends the Bearer form — needs-endpoint-testing
9696
+ // whether a live Project-Access-Token requires the dedicated header
9697
+ // instead once this poller runs against a real project.
9698
+ ...bearerAuthHeader(token)
9699
+ },
9700
+ body: JSON.stringify({ query, variables })
9701
+ },
9702
+ { provider: "railway", accountKey }
9703
+ );
9704
+ if (!res.ok) {
9705
+ throw new Error(`Railway GraphQL request failed: ${res.status} ${res.statusText}`);
9706
+ }
9707
+ const body = await res.json();
9708
+ if (body.errors && body.errors.length > 0) {
9709
+ throw new Error(`Railway GraphQL errors: ${body.errors.map((e) => e.message).join("; ")}`);
9710
+ }
9711
+ if (!body.data) throw new Error("Railway GraphQL response carried no data");
9712
+ return body.data;
9713
+ }
9714
+ var HTTP_LOGS_QUERY = `
9715
+ query HttpLogs(
9716
+ $environmentId: String!
9717
+ $serviceId: String!
9718
+ $startDate: String!
9719
+ $endDate: String!
9720
+ $limit: Int
9721
+ ) {
9722
+ httpLogs(
9723
+ environmentId: $environmentId
9724
+ serviceId: $serviceId
9725
+ startDate: $startDate
9726
+ endDate: $endDate
9727
+ limit: $limit
9728
+ ) {
9729
+ timestamp
9730
+ method
9731
+ path
9732
+ httpStatus
9733
+ totalDuration
9734
+ requestId
9735
+ deploymentId
9736
+ edgeRegion
9737
+ }
9738
+ }
9739
+ `;
9740
+ var NETWORK_FLOW_LOGS_QUERY = `
9741
+ query NetworkFlowLogs(
9742
+ $environmentId: String!
9743
+ $serviceId: String!
9744
+ $startDate: String!
9745
+ $endDate: String!
9746
+ $limit: Int
9747
+ ) {
9748
+ networkFlowLogs(
9749
+ environmentId: $environmentId
9750
+ serviceId: $serviceId
9751
+ startDate: $startDate
9752
+ endDate: $endDate
9753
+ limit: $limit
9754
+ ) {
9755
+ timestamp
9756
+ peerServiceId
9757
+ peerKind
9758
+ direction
9759
+ byteCount
9760
+ packetCount
9761
+ dropCause
9762
+ }
9763
+ }
9764
+ `;
9765
+ async function fetchRailwayHttpLogs(config, token, startDate, endDate) {
9766
+ const data = await railwayGraphQL(
9767
+ config.apiUrl ?? DEFAULT_RAILWAY_API_URL,
9768
+ token,
9769
+ HTTP_LOGS_QUERY,
9770
+ {
9771
+ environmentId: config.environmentId,
9772
+ serviceId: config.serviceId,
9773
+ startDate,
9774
+ endDate,
9775
+ limit: config.limit ?? DEFAULT_LOG_LIMIT2
9776
+ },
9777
+ config.environmentId
9778
+ );
9779
+ return data.httpLogs;
9780
+ }
9781
+ async function fetchRailwayNetworkFlowLogs(config, token, startDate, endDate) {
9782
+ const data = await railwayGraphQL(
9783
+ config.apiUrl ?? DEFAULT_RAILWAY_API_URL,
9784
+ token,
9785
+ NETWORK_FLOW_LOGS_QUERY,
9786
+ {
9787
+ environmentId: config.environmentId,
9788
+ serviceId: config.serviceId,
9789
+ startDate,
9790
+ endDate,
9791
+ limit: config.limit ?? DEFAULT_LOG_LIMIT2
9792
+ },
9793
+ config.environmentId
9794
+ );
9795
+ return data.networkFlowLogs;
9796
+ }
9797
+ function boundedRailwayStartDate(since, now, maxLookbackMs) {
9798
+ const floor = new Date(now.getTime() - maxLookbackMs);
9799
+ if (!since) return floor.toISOString();
9800
+ const sinceMs = new Date(since).getTime();
9801
+ if (Number.isNaN(sinceMs)) return floor.toISOString();
9802
+ return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
9803
+ }
9804
+
9805
+ // src/connectors/railway/index.ts
9806
+ var ROUTE_TARGET_KIND = "route";
9807
+ var UNMATCHED_ROUTE_TARGET_KIND = "unmatched-route";
9808
+ var PEER_SERVICE_TARGET_KIND = "peer-service";
9809
+ function buildRailwayRouteIndex(graph, serviceName) {
9810
+ const out = [];
9811
+ graph.forEachNode((_id, attrs) => {
9812
+ const node = attrs;
9813
+ if (node.type !== import_types36.NodeType.RouteNode) return;
9814
+ const route = attrs;
9815
+ if (route.service !== serviceName) return;
9816
+ out.push({
9817
+ method: route.method.toUpperCase(),
9818
+ normalizedPath: normalizePathTemplate(route.pathTemplate),
9819
+ routeNodeId: route.id,
9820
+ path: route.path,
9821
+ line: route.line
9822
+ });
9823
+ });
9824
+ return out;
9825
+ }
9826
+ function findRailwayRoute(entries, method, normalizedPath) {
9827
+ return entries.find(
9828
+ (e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || e.method === method)
9829
+ );
9830
+ }
9831
+ function bucketKey2(method, normalizedPath) {
9832
+ return `${method} ${normalizedPath}`;
9833
+ }
9834
+ function isHttpErrorStatus(status2) {
9835
+ return status2 >= 400;
9836
+ }
9837
+ function upsertBucket(buckets2, key, isError, timestamp, build) {
9838
+ const existing = buckets2.get(key);
9839
+ if (existing) {
9840
+ existing.callCount += 1;
9841
+ if (isError) existing.errorCount += 1;
9842
+ if (timestamp > existing.lastObservedIso) existing.lastObservedIso = timestamp;
9843
+ return;
9844
+ }
9845
+ buckets2.set(key, { callCount: 1, errorCount: isError ? 1 : 0, lastObservedIso: timestamp, ...build() });
9846
+ }
9847
+ function mapRailwayHttpLogsToSignals(entries, routeIndex) {
9848
+ const buckets2 = /* @__PURE__ */ new Map();
9849
+ for (const entry2 of entries) {
9850
+ const method = entry2.method.toUpperCase();
9851
+ const normalizedPath = normalizePathTemplate(entry2.path);
9852
+ const match = findRailwayRoute(routeIndex, method, normalizedPath);
9853
+ const isError = isHttpErrorStatus(entry2.httpStatus);
9854
+ if (match) {
9855
+ upsertBucket(buckets2, `route:${match.routeNodeId}`, isError, entry2.timestamp, () => ({
9856
+ targetKind: ROUTE_TARGET_KIND,
9857
+ targetName: match.routeNodeId,
9858
+ // RouteNode.line is optional in the schema (packages/types/src/
9859
+ // nodes.ts) even though routes.ts always sets it today — skip the
9860
+ // callSite rather than fabricate a line when it's ever absent
9861
+ // (file-awareness.md §6).
9862
+ ...match.line !== void 0 ? { callSite: { file: match.path, line: match.line } } : {}
9863
+ }));
9864
+ } else {
9865
+ upsertBucket(
9866
+ buckets2,
9867
+ `unmatched:${bucketKey2(method, normalizedPath)}`,
9868
+ isError,
9869
+ entry2.timestamp,
9870
+ () => ({
9871
+ targetKind: UNMATCHED_ROUTE_TARGET_KIND,
9872
+ targetName: bucketKey2(method, normalizedPath)
9873
+ })
9874
+ );
9875
+ }
9876
+ }
9877
+ return [...buckets2.values()].map((b) => ({
9878
+ targetKind: b.targetKind,
9879
+ targetName: b.targetName,
9880
+ callCount: b.callCount,
9881
+ errorCount: b.errorCount,
9882
+ lastObservedIso: b.lastObservedIso,
9883
+ ...b.callSite ? { callSite: b.callSite } : {}
9884
+ }));
9885
+ }
9886
+ function mapRailwayNetworkFlowLogsToSignals(entries) {
9887
+ const buckets2 = /* @__PURE__ */ new Map();
9888
+ for (const entry2 of entries) {
9889
+ if (!entry2.peerServiceId) continue;
9890
+ const isError = entry2.dropCause !== null && entry2.dropCause !== "";
9891
+ upsertBucket(buckets2, entry2.peerServiceId, isError, entry2.timestamp, () => ({
9892
+ targetKind: PEER_SERVICE_TARGET_KIND,
9893
+ targetName: entry2.peerServiceId
9894
+ }));
9895
+ }
9896
+ return [...buckets2.values()].map((b) => ({
9897
+ targetKind: b.targetKind,
9898
+ targetName: b.targetName,
9899
+ callCount: b.callCount,
9900
+ errorCount: b.errorCount,
9901
+ lastObservedIso: b.lastObservedIso
9902
+ }));
9903
+ }
9904
+ function createRailwayResolveTarget(config) {
9905
+ return (signal) => {
9906
+ const serviceName = config.serviceNameById[config.serviceId];
9907
+ if (!serviceName) return null;
9908
+ if (signal.targetKind === ROUTE_TARGET_KIND) {
9909
+ return { targetNodeId: signal.targetName, serviceName, edgeType: import_types36.EdgeType.CALLS };
9910
+ }
9911
+ if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
9912
+ const peerName = config.serviceNameById[signal.targetName];
9913
+ if (!peerName) return null;
9914
+ return { targetNodeId: (0, import_types36.serviceId)(peerName), serviceName, edgeType: import_types36.EdgeType.CONNECTS_TO };
9915
+ }
9916
+ return null;
9917
+ };
9918
+ }
9919
+ function createRailwayConnector(graph, config) {
9920
+ return {
9921
+ provider: "railway",
9922
+ async poll(ctx) {
9923
+ const token = readRailwayToken(ctx.credentials);
9924
+ const now = /* @__PURE__ */ new Date();
9925
+ const maxLookbackMs = config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS2;
9926
+ const startDate = boundedRailwayStartDate(ctx.since, now, maxLookbackMs);
9927
+ const endDate = now.toISOString();
9928
+ const [httpLogs, flowLogs] = await Promise.all([
9929
+ fetchRailwayHttpLogs(config, token, startDate, endDate),
9930
+ fetchRailwayNetworkFlowLogs(config, token, startDate, endDate)
9931
+ ]);
9932
+ const serviceName = config.serviceNameById[config.serviceId];
9933
+ const routeIndex = serviceName ? buildRailwayRouteIndex(graph, serviceName) : [];
9934
+ return [
9935
+ ...mapRailwayHttpLogsToSignals(httpLogs, routeIndex),
9936
+ ...mapRailwayNetworkFlowLogsToSignals(flowLogs)
9937
+ ];
9938
+ }
9939
+ };
9940
+ }
9941
+
9942
+ // src/connectors/firebase/index.ts
9943
+ init_cjs_shims();
9944
+
9945
+ // src/connectors/firebase/logging-api.ts
9946
+ init_cjs_shims();
9947
+ function readFirebaseCredentials(raw) {
9948
+ const projectId = raw["projectId"];
9949
+ const accessToken = raw["accessToken"];
9950
+ if (typeof projectId !== "string" || projectId.length === 0) {
9951
+ throw new Error("firebase connector: credentials.projectId must be a non-empty string");
9952
+ }
9953
+ if (typeof accessToken !== "string" || accessToken.length === 0) {
9954
+ throw new Error("firebase connector: credentials.accessToken must be a non-empty string");
9955
+ }
9956
+ return { projectId, accessToken };
9957
+ }
9958
+ var RESOURCE_TYPES = [
9959
+ "cloud_function",
9960
+ "cloud_run_revision",
9961
+ "firebase_domain"
9962
+ ];
9963
+ function isFirebaseResourceType(value) {
9964
+ return RESOURCE_TYPES.includes(value);
9965
+ }
9966
+ function buildEntriesFilter(sinceIso) {
9967
+ return [
9968
+ 'resource.type = ("cloud_function" OR "cloud_run_revision" OR "firebase_domain")',
9969
+ "httpRequest:*",
9970
+ `timestamp >= "${sinceIso}"`
9971
+ ].join(" AND ");
9972
+ }
9973
+ var DEFAULT_LOOKBACK_MS = 24 * 60 * 60 * 1e3;
9974
+ var ENTRIES_LIST_URL = "https://logging.googleapis.com/v2/entries:list";
9975
+ var PAGE_SIZE = 1e3;
9976
+ var MAX_PAGES = 20;
9977
+ async function fetchHttpRequestLogEntries(creds, sinceIso) {
9978
+ const filter = buildEntriesFilter(sinceIso);
9979
+ const out = [];
9980
+ let pageToken;
9981
+ for (let page = 0; page < MAX_PAGES; page++) {
9982
+ const body = {
9983
+ resourceNames: [`projects/${creds.projectId}`],
9984
+ filter,
9985
+ orderBy: "timestamp asc",
9986
+ pageSize: PAGE_SIZE,
9987
+ ...pageToken ? { pageToken } : {}
9988
+ };
9989
+ const res = await junctionFetch(
9990
+ ENTRIES_LIST_URL,
9991
+ {
9992
+ method: "POST",
9993
+ headers: {
9994
+ ...bearerAuthHeader(creds.accessToken),
9995
+ "Content-Type": "application/json"
9996
+ },
9997
+ body: JSON.stringify(body)
9998
+ },
9999
+ // accountKey: the GCP project id (ADR-131's own worked example for
10000
+ // Firebase) — one customer's Cloud Logging quota is scoped per GCP
10001
+ // project, not per Firebase site/function.
10002
+ { provider: "firebase", accountKey: creds.projectId }
10003
+ );
10004
+ if (!res.ok) {
10005
+ throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`);
10006
+ }
10007
+ const json = await res.json();
10008
+ out.push(...json.entries ?? []);
10009
+ if (!json.nextPageToken) break;
10010
+ pageToken = json.nextPageToken;
10011
+ }
10012
+ return out;
10013
+ }
10014
+
10015
+ // src/connectors/firebase/map.ts
10016
+ init_cjs_shims();
10017
+ var FIELD_SEP = "\0";
10018
+ function packFirebaseTargetName(identity) {
10019
+ return [identity.resourceName, identity.method, identity.path].join(FIELD_SEP);
10020
+ }
10021
+ function parseFirebaseTargetName(targetName) {
10022
+ const firstSep = targetName.indexOf(FIELD_SEP);
10023
+ if (firstSep === -1) return null;
10024
+ const resourceName = targetName.slice(0, firstSep);
10025
+ const rest = targetName.slice(firstSep + 1);
10026
+ const secondSep = rest.indexOf(FIELD_SEP);
10027
+ if (secondSep === -1) return null;
10028
+ const method = rest.slice(0, secondSep);
10029
+ const path57 = rest.slice(secondSep + 1);
10030
+ if (!resourceName || !method || !path57) return null;
10031
+ return { resourceName, method, path: path57 };
10032
+ }
10033
+ function resourceNameFor(type, labels) {
10034
+ if (!labels) return null;
10035
+ switch (type) {
10036
+ case "cloud_function":
10037
+ return labels["function_name"] ?? null;
10038
+ case "cloud_run_revision":
10039
+ return labels["service_name"] ?? null;
10040
+ case "firebase_domain":
10041
+ return labels["site_name"] ?? null;
10042
+ }
10043
+ }
10044
+ function pathFromRequestUrl(requestUrl) {
10045
+ if (!requestUrl) return null;
10046
+ if (requestUrl.startsWith("/")) {
10047
+ const withoutQuery = requestUrl.split("?")[0];
10048
+ return withoutQuery && withoutQuery.length > 0 ? withoutQuery : "/";
10049
+ }
10050
+ try {
10051
+ const candidate = requestUrl.startsWith("//") ? `https:${requestUrl}` : requestUrl;
10052
+ const parsed = new URL(candidate);
10053
+ return parsed.pathname || "/";
10054
+ } catch {
10055
+ return null;
10056
+ }
10057
+ }
10058
+ var ERROR_STATUS_THRESHOLD2 = 500;
10059
+ function mapLogEntryToSignal(entry2) {
10060
+ const resourceType = entry2.resource?.type;
10061
+ if (!resourceType || !isFirebaseResourceType(resourceType)) return null;
10062
+ const resourceName = resourceNameFor(resourceType, entry2.resource?.labels);
10063
+ if (!resourceName) return null;
10064
+ const req = entry2.httpRequest;
10065
+ if (!req) return null;
10066
+ if (!req.requestMethod) return null;
10067
+ const method = req.requestMethod.toUpperCase();
10068
+ const path57 = pathFromRequestUrl(req.requestUrl);
10069
+ if (path57 === null) return null;
10070
+ const timestamp = entry2.timestamp;
10071
+ if (!timestamp) return null;
10072
+ const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
10073
+ return {
10074
+ targetKind: resourceType,
10075
+ targetName: packFirebaseTargetName({ resourceName, method, path: path57 }),
10076
+ callCount: 1,
10077
+ errorCount: isError ? 1 : 0,
10078
+ lastObservedIso: timestamp
10079
+ };
10080
+ }
10081
+ function mapLogEntriesToSignals(entries) {
10082
+ const out = [];
10083
+ for (const entry2 of entries) {
10084
+ const signal = mapLogEntryToSignal(entry2);
10085
+ if (signal) out.push(signal);
10086
+ }
10087
+ return out;
10088
+ }
10089
+
10090
+ // src/connectors/firebase/resolve.ts
10091
+ init_cjs_shims();
10092
+ var import_types37 = require("@neat.is/types");
10093
+ function neatServiceNameFor(resourceType, resourceName, serviceMap) {
10094
+ switch (resourceType) {
10095
+ case "cloud_function":
10096
+ return serviceMap.functions?.[resourceName] ?? null;
10097
+ case "cloud_run_revision":
10098
+ return serviceMap.cloudRun?.[resourceName] ?? null;
10099
+ case "firebase_domain":
10100
+ return serviceMap.hosting?.[resourceName] ?? null;
10101
+ }
10102
+ }
10103
+ function routeEntriesFor(graph, serviceName) {
10104
+ const entries = [];
10105
+ graph.forEachNode((_id, attrs) => {
10106
+ const node = attrs;
10107
+ if (node.type !== import_types37.NodeType.RouteNode) return;
10108
+ const route = attrs;
10109
+ if (route.service !== serviceName) return;
10110
+ entries.push({
10111
+ method: route.method.toUpperCase(),
10112
+ normalizedPath: normalizePathTemplate(route.pathTemplate),
10113
+ routeNodeId: route.id
10114
+ });
10115
+ });
10116
+ return entries;
10117
+ }
10118
+ function findRoute2(entries, method, normalizedPath) {
10119
+ return entries.find(
10120
+ (e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || e.method === method)
10121
+ );
10122
+ }
10123
+ function createFirebaseResolveTarget(graph, serviceMap) {
10124
+ return (signal, _ctx) => {
10125
+ const resourceType = signal.targetKind;
10126
+ if (resourceType !== "cloud_function" && resourceType !== "cloud_run_revision" && resourceType !== "firebase_domain") {
10127
+ return null;
10128
+ }
10129
+ const identity = parseFirebaseTargetName(signal.targetName);
10130
+ if (!identity) return null;
10131
+ const serviceName = neatServiceNameFor(resourceType, identity.resourceName, serviceMap);
10132
+ if (!serviceName) return null;
10133
+ const normalizedPath = normalizePathTemplate(identity.path);
10134
+ const match = findRoute2(routeEntriesFor(graph, serviceName), identity.method, normalizedPath);
10135
+ if (!match) return null;
10136
+ return {
10137
+ targetNodeId: match.routeNodeId,
10138
+ serviceName,
10139
+ edgeType: import_types37.EdgeType.CALLS
10140
+ };
10141
+ };
10142
+ }
10143
+
10144
+ // src/connectors/firebase/index.ts
10145
+ var FirebaseConnector = class {
10146
+ provider = "firebase";
10147
+ async poll(ctx) {
10148
+ const creds = readFirebaseCredentials(ctx.credentials);
10149
+ const sinceIso = ctx.since ?? new Date(Date.now() - DEFAULT_LOOKBACK_MS).toISOString();
10150
+ const entries = await fetchHttpRequestLogEntries(creds, sinceIso);
10151
+ return mapLogEntriesToSignals(entries);
10152
+ }
10153
+ };
10154
+ function createFirebaseConnector(graph, serviceMap) {
10155
+ return {
10156
+ connector: new FirebaseConnector(),
10157
+ resolveTarget: createFirebaseResolveTarget(graph, serviceMap)
10158
+ };
10159
+ }
10160
+
10161
+ // src/connectors/cloudflare/index.ts
10162
+ init_cjs_shims();
10163
+
10164
+ // src/connectors/cloudflare/connector.ts
10165
+ init_cjs_shims();
10166
+ var import_types39 = require("@neat.is/types");
10167
+
10168
+ // src/connectors/cloudflare/client.ts
10169
+ init_cjs_shims();
10170
+ var import_node_crypto3 = require("crypto");
10171
+ var DEFAULT_BASE_URL = "https://api.cloudflare.com/client/v4";
10172
+ var DEFAULT_EVENT_LIMIT = 1e3;
10173
+ async function queryWorkerInvocations(ctx, config, window, fetchImpl = fetch) {
10174
+ const token = ctx.credentials.apiToken;
10175
+ if (typeof token !== "string" || token.length === 0) {
10176
+ throw new Error("cloudflare connector: ctx.credentials.apiToken must be a non-empty string");
10177
+ }
10178
+ const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
10179
+ const url = `${baseUrl}/accounts/${config.accountId}/workers/observability/telemetry/query`;
10180
+ const body = {
10181
+ // Cloudflare's schema requires an identifier per query even for an
10182
+ // ad-hoc, unsaved one — a fresh id per tick, never reused.
10183
+ queryId: `neat-connector-${(0, import_node_crypto3.randomUUID)()}`,
10184
+ timeframe: { from: window.fromMs, to: window.toMs },
10185
+ view: "events",
10186
+ limit: config.eventLimit ?? DEFAULT_EVENT_LIMIT,
10187
+ // Execute without persisting — this is a read, not a saved query
10188
+ // (connectors.md §2's "never writes on the read path" applies to
10189
+ // Cloudflare's own query-history state too).
10190
+ dry: true
10191
+ };
10192
+ const res = await junctionFetch(
10193
+ url,
10194
+ {
10195
+ method: "POST",
10196
+ headers: {
10197
+ "Content-Type": "application/json",
10198
+ ...bearerAuthHeader(token)
10199
+ },
10200
+ body: JSON.stringify(body)
10201
+ },
10202
+ // accountKey: the Cloudflare account id (ADR-131's own worked example) —
10203
+ // the Telemetry Query API's ~300/5min limit is enforced per account.
10204
+ { provider: "cloudflare", accountKey: config.accountId, fetchImpl }
10205
+ );
10206
+ if (!res.ok) {
10207
+ throw new Error(
10208
+ `cloudflare connector: telemetry query failed (${res.status} ${res.statusText})`
10209
+ );
10210
+ }
10211
+ const payload = await res.json();
10212
+ if (!payload.success) {
10213
+ const message = payload.errors?.map((e) => e.message).join("; ") || "unknown error";
10214
+ throw new Error(`cloudflare connector: telemetry query returned an error (${message})`);
10215
+ }
10216
+ return payload.result?.events?.events ?? [];
10217
+ }
10218
+
10219
+ // src/connectors/cloudflare/map.ts
10220
+ init_cjs_shims();
10221
+
10222
+ // src/connectors/cloudflare/types.ts
10223
+ init_cjs_shims();
10224
+ var CLOUDFLARE_TARGET_KIND = "cloudflare-worker-invocation";
10225
+
10226
+ // src/connectors/cloudflare/map.ts
10227
+ var HTTP_METHODS = /* @__PURE__ */ new Set([
10228
+ "GET",
10229
+ "POST",
10230
+ "PUT",
10231
+ "PATCH",
10232
+ "DELETE",
10233
+ "HEAD",
10234
+ "OPTIONS",
10235
+ "TRACE",
10236
+ "CONNECT"
10237
+ ]);
10238
+ var LEADING_TOKEN_RE = /^(\S+)\s+\S/;
10239
+ function parseHttpMethodFromTrigger(trigger) {
10240
+ if (!trigger) return null;
10241
+ const match = LEADING_TOKEN_RE.exec(trigger.trim());
10242
+ const token = match?.[1];
10243
+ if (!token) return null;
10244
+ const method = token.toUpperCase();
10245
+ return HTTP_METHODS.has(method) ? method : null;
10246
+ }
10247
+ var ERROR_STATUS_THRESHOLD3 = 500;
10248
+ function mapEventToSignal(event) {
10249
+ const metadata = event.$metadata;
10250
+ const workers = event.$workers;
10251
+ const method = parseHttpMethodFromTrigger(metadata?.trigger);
10252
+ if (!method) return null;
10253
+ const scriptName = workers?.scriptName ?? metadata?.service;
10254
+ if (!scriptName) return null;
10255
+ const timestampMs = event.timestamp ?? metadata?.startTime;
10256
+ if (typeof timestampMs !== "number" || !Number.isFinite(timestampMs)) return null;
10257
+ const statusCode = metadata?.statusCode;
10258
+ const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
10259
+ return {
10260
+ targetKind: CLOUDFLARE_TARGET_KIND,
10261
+ targetName: scriptName,
10262
+ callCount: 1,
10263
+ errorCount: isError ? 1 : 0,
10264
+ lastObservedIso: new Date(timestampMs).toISOString(),
10265
+ method,
10266
+ ...typeof statusCode === "number" ? { statusCode } : {},
10267
+ ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
10268
+ };
10269
+ }
10270
+
10271
+ // src/connectors/cloudflare/connector.ts
10272
+ var DEFAULT_MAX_LOOKBACK_MS3 = 60 * 60 * 1e3;
10273
+ function resolveFromMs(since, maxLookbackMs) {
10274
+ const cap = maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS3;
10275
+ const now = Date.now();
10276
+ const floor = now - cap;
10277
+ if (!since) return floor;
10278
+ const parsed = Date.parse(since);
10279
+ if (Number.isNaN(parsed)) return floor;
10280
+ return Math.max(parsed, floor);
10281
+ }
10282
+ var CloudflareConnector = class {
10283
+ constructor(config) {
10284
+ this.config = config;
10285
+ }
10286
+ config;
10287
+ provider = "cloudflare";
10288
+ async poll(ctx) {
10289
+ const toMs = Date.now();
10290
+ const fromMs = resolveFromMs(ctx.since, this.config.maxLookbackMs);
10291
+ const events = await queryWorkerInvocations(ctx, this.config, { fromMs, toMs });
10292
+ const signals = [];
10293
+ for (const event of events) {
10294
+ const signal = mapEventToSignal(event);
10295
+ if (signal) signals.push(signal);
10296
+ }
10297
+ return signals;
10298
+ }
10299
+ };
10300
+ function createCloudflareResolveTarget(config) {
10301
+ return (signal) => {
10302
+ if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
10303
+ const mapping = config.workers[signal.targetName];
10304
+ if (!mapping) return null;
10305
+ return {
10306
+ targetNodeId: (0, import_types39.fileId)(mapping.service, mapping.entryFile),
10307
+ serviceName: mapping.service,
10308
+ edgeType: import_types39.EdgeType.CALLS
10309
+ };
10310
+ };
10311
+ }
10312
+
10313
+ // src/connectors-config.ts
10314
+ init_cjs_shims();
10315
+ var import_node_os4 = __toESM(require("os"), 1);
10316
+ var import_node_path46 = __toESM(require("path"), 1);
10317
+ var import_node_fs27 = require("fs");
10318
+ var CONNECTORS_CONFIG_VERSION = 1;
10319
+ var EnvRefUnsetError = class extends Error {
10320
+ ref;
10321
+ varName;
10322
+ constructor(ref, varName) {
10323
+ super(`${ref} is unset`);
10324
+ this.name = "EnvRefUnsetError";
10325
+ this.ref = ref;
10326
+ this.varName = varName;
10327
+ }
10328
+ };
10329
+ function neatHome2() {
10330
+ const override = process.env.NEAT_HOME;
10331
+ if (override && override.length > 0) return import_node_path46.default.resolve(override);
10332
+ return import_node_path46.default.join(import_node_os4.default.homedir(), ".neat");
10333
+ }
10334
+ function connectorsConfigPath(home = neatHome2()) {
10335
+ return import_node_path46.default.join(home, "connectors.json");
10336
+ }
10337
+ var MODE_MASK_LOOSER_THAN_0600 = 63;
10338
+ async function warnIfModeLooserThan0600(file) {
10339
+ if (process.platform === "win32") return;
10340
+ try {
10341
+ const stat = await import_node_fs27.promises.stat(file);
10342
+ if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
10343
+ const mode = (stat.mode & 511).toString(8).padStart(3, "0");
10344
+ console.warn(
10345
+ `[neat] ${file} is mode 0${mode}, looser than the 0600 this file's secrets call for \u2014 run \`chmod 600 ${file}\``
10346
+ );
10347
+ }
10348
+ } catch {
10349
+ }
10350
+ }
10351
+ async function readConnectorsConfig(home = neatHome2()) {
10352
+ const file = connectorsConfigPath(home);
10353
+ let raw;
10354
+ try {
10355
+ raw = await import_node_fs27.promises.readFile(file, "utf8");
10356
+ } catch (err) {
10357
+ if (err.code === "ENOENT") {
10358
+ return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
10359
+ }
10360
+ throw err;
10361
+ }
10362
+ await warnIfModeLooserThan0600(file);
10363
+ let parsed;
10364
+ try {
10365
+ parsed = JSON.parse(raw);
10366
+ } catch (err) {
10367
+ throw new Error(`${file} is not valid JSON: ${err.message}`);
10368
+ }
10369
+ return validateConfig(parsed, file);
10370
+ }
10371
+ function validateConfig(parsed, file) {
10372
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
10373
+ throw new Error(`${file} must be a JSON object with a "connectors" array`);
10374
+ }
10375
+ const obj = parsed;
10376
+ const version = obj.version === void 0 ? CONNECTORS_CONFIG_VERSION : obj.version;
10377
+ if (typeof version !== "number" || !Number.isInteger(version)) {
10378
+ throw new Error(`${file}: "version" must be an integer`);
10379
+ }
10380
+ const rawConnectors = obj.connectors;
10381
+ if (!Array.isArray(rawConnectors)) {
10382
+ throw new Error(`${file}: "connectors" must be an array`);
10383
+ }
10384
+ const connectors = rawConnectors.map((entry2, i) => validateEntry(entry2, i, file));
10385
+ return { version, connectors };
10386
+ }
10387
+ function validateEntry(entry2, index, file) {
10388
+ const where = `${file}: connectors[${index}]`;
10389
+ if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
10390
+ throw new Error(`${where} must be an object`);
10391
+ }
10392
+ const e = entry2;
10393
+ const id = e.id;
10394
+ if (typeof id !== "string" || id.length === 0) {
10395
+ throw new Error(`${where}.id must be a non-empty string`);
10396
+ }
10397
+ const provider = e.provider;
10398
+ if (typeof provider !== "string" || provider.length === 0) {
10399
+ throw new Error(`${where}.provider must be a non-empty string`);
10400
+ }
10401
+ if (e.project !== void 0 && typeof e.project !== "string") {
10402
+ throw new Error(`${where}.project must be a string when present`);
10403
+ }
10404
+ const credential = validateCredentialRef(e.credential, `${where}.credential`);
10405
+ let options;
10406
+ if (e.options !== void 0) {
10407
+ if (typeof e.options !== "object" || e.options === null || Array.isArray(e.options)) {
10408
+ throw new Error(`${where}.options must be an object when present`);
10409
+ }
10410
+ options = e.options;
10411
+ }
10412
+ return {
10413
+ id,
10414
+ provider,
10415
+ ...typeof e.project === "string" ? { project: e.project } : {},
10416
+ credential,
10417
+ ...options ? { options } : {}
10418
+ };
10419
+ }
10420
+ function validateCredentialRef(raw, where) {
10421
+ if (typeof raw === "string") {
10422
+ if (raw.length === 0) throw new Error(`${where} must be a non-empty string`);
10423
+ return raw;
10424
+ }
10425
+ if (typeof raw === "object" && raw !== null && !Array.isArray(raw)) {
10426
+ const fields = raw;
10427
+ const keys = Object.keys(fields);
10428
+ if (keys.length === 0) throw new Error(`${where} object must have at least one field`);
10429
+ for (const key of keys) {
10430
+ const v = fields[key];
10431
+ if (typeof v !== "string" || v.length === 0) {
10432
+ throw new Error(`${where}.${key} must be a non-empty string`);
10433
+ }
10434
+ }
10435
+ return fields;
10436
+ }
10437
+ throw new Error(`${where} must be a string or an object of strings`);
10438
+ }
10439
+ function resolveCredential(ref, env = process.env) {
10440
+ if (typeof ref === "string") {
10441
+ return { kind: "single", value: resolveRef(ref, env) };
10442
+ }
10443
+ const fields = {};
10444
+ for (const [key, value] of Object.entries(ref)) {
10445
+ fields[key] = resolveRef(value, env);
10446
+ }
10447
+ return { kind: "fields", fields };
10448
+ }
10449
+ function resolveRef(value, env) {
10450
+ if (value.length > 1 && value.startsWith("$")) {
10451
+ const varName = value.slice(1);
10452
+ const resolved = env[varName];
10453
+ if (resolved === void 0 || resolved.length === 0) {
10454
+ throw new EnvRefUnsetError(value, varName);
10455
+ }
10456
+ return resolved;
10457
+ }
10458
+ return value;
10459
+ }
10460
+ var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
10461
+ var CONNECTORS_LOCK_RETRY_MS = 50;
10462
+ function connectorsConfigLockPath(home = neatHome2()) {
10463
+ return import_node_path46.default.join(home, "connectors.json.lock");
10464
+ }
10465
+ function isEnvRef(value) {
10466
+ return value.length > 1 && value.startsWith("$");
10467
+ }
10468
+ async function writeConfigAtomically0600(file, contents) {
10469
+ await import_node_fs27.promises.mkdir(import_node_path46.default.dirname(file), { recursive: true });
10470
+ const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
10471
+ const fd = await import_node_fs27.promises.open(tmp, "w", 384);
10472
+ try {
10473
+ await fd.writeFile(contents, "utf8");
10474
+ await fd.chmod(384);
10475
+ await fd.sync();
10476
+ } finally {
10477
+ await fd.close();
10478
+ }
10479
+ await import_node_fs27.promises.rename(tmp, file);
10480
+ }
10481
+ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
10482
+ const deadline = Date.now() + timeoutMs;
10483
+ await import_node_fs27.promises.mkdir(import_node_path46.default.dirname(lockPath), { recursive: true });
10484
+ for (; ; ) {
10485
+ try {
10486
+ const fd = await import_node_fs27.promises.open(lockPath, "wx");
10487
+ try {
10488
+ await fd.writeFile(`${process.pid}
10489
+ `, "utf8");
10490
+ } finally {
10491
+ await fd.close();
10492
+ }
10493
+ return;
10494
+ } catch (err) {
10495
+ if (err.code !== "EEXIST") throw err;
10496
+ if (Date.now() >= deadline) {
10497
+ throw new Error(
10498
+ `neat connector: timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process may be writing the connector config; if none is, remove that file by hand.`
10499
+ );
10500
+ }
10501
+ await new Promise((r) => setTimeout(r, CONNECTORS_LOCK_RETRY_MS));
10502
+ }
10503
+ }
10504
+ }
10505
+ async function releaseConnectorsLock(lockPath) {
10506
+ await import_node_fs27.promises.unlink(lockPath).catch(() => {
10507
+ });
10508
+ }
10509
+ async function withConnectorsLock(home, fn) {
10510
+ const lock = connectorsConfigLockPath(home);
10511
+ await acquireConnectorsLock(lock);
10512
+ try {
10513
+ return await fn();
10514
+ } finally {
10515
+ await releaseConnectorsLock(lock);
10516
+ }
10517
+ }
10518
+ function serializeConfig(config) {
10519
+ return JSON.stringify(
10520
+ { version: config.version ?? CONNECTORS_CONFIG_VERSION, connectors: config.connectors },
10521
+ null,
10522
+ 2
10523
+ ) + "\n";
10524
+ }
10525
+ async function upsertConnectorEntry(entry2, home = neatHome2()) {
10526
+ const file = connectorsConfigPath(home);
10527
+ return withConnectorsLock(home, async () => {
10528
+ const config = await readConnectorsConfig(home);
10529
+ validateEntry(entry2, config.connectors.length, file);
10530
+ const idx = config.connectors.findIndex((c) => c.id === entry2.id);
10531
+ const replaced = idx >= 0;
10532
+ if (replaced) config.connectors[idx] = entry2;
10533
+ else config.connectors.push(entry2);
10534
+ await writeConfigAtomically0600(file, serializeConfig(config));
10535
+ return { replaced };
10536
+ });
10537
+ }
10538
+ async function removeConnectorEntry(id, home = neatHome2()) {
10539
+ const file = connectorsConfigPath(home);
10540
+ return withConnectorsLock(home, async () => {
10541
+ const config = await readConnectorsConfig(home);
10542
+ const idx = config.connectors.findIndex((c) => c.id === id);
10543
+ if (idx < 0) return void 0;
10544
+ const [removed] = config.connectors.splice(idx, 1);
10545
+ await writeConfigAtomically0600(file, serializeConfig(config));
10546
+ return removed;
10547
+ });
10548
+ }
10549
+ function slugFragment(value) {
10550
+ return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
10551
+ }
10552
+ function autoSlugConnectorId(provider, project, existingIds) {
10553
+ const base = slugFragment(provider) || "connector";
10554
+ if (!existingIds.has(base)) return base;
10555
+ const projectFrag = project ? slugFragment(project) : "";
10556
+ if (projectFrag) {
10557
+ const withProject = `${base}-${projectFrag}`;
10558
+ if (!existingIds.has(withProject)) return withProject;
10559
+ }
10560
+ const stem = projectFrag ? `${base}-${projectFrag}` : base;
10561
+ for (let n = 2; ; n++) {
10562
+ const candidate = `${stem}-${n}`;
10563
+ if (!existingIds.has(candidate)) return candidate;
10564
+ }
10565
+ }
10566
+ function describeCredential(ref, env = process.env) {
10567
+ const one = (value, field) => {
10568
+ if (isEnvRef(value)) {
10569
+ const varName = value.slice(1);
10570
+ const resolved = env[varName];
10571
+ const set = typeof resolved === "string" && resolved.length > 0;
10572
+ return {
10573
+ ...field ? { field } : {},
10574
+ kind: "env-ref",
10575
+ display: value,
10576
+ status: set ? "set" : "unset"
10577
+ };
10578
+ }
10579
+ return { ...field ? { field } : {}, kind: "plaintext", display: "****" };
10580
+ };
10581
+ if (typeof ref === "string") return [one(ref)];
10582
+ return Object.entries(ref).map(([k, v]) => one(v, k));
10583
+ }
10584
+
10585
+ // src/connectors/registry.ts
10586
+ var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
10587
+ async function authProbe(input) {
10588
+ const { provider, accountKey, url, token, init, fetchImpl } = input;
10589
+ try {
10590
+ const res = await junctionFetch(
10591
+ url,
10592
+ {
10593
+ ...init ?? {},
10594
+ headers: { ...bearerAuthHeader(token), ...init?.headers ?? {} }
10595
+ },
10596
+ { provider, accountKey, ...fetchImpl ? { fetchImpl } : {} }
10597
+ );
10598
+ if (res.ok) return { ok: true };
10599
+ if (res.status === 401 || res.status === 403) {
10600
+ return { ok: false, reason: `${provider} rejected the credential (HTTP ${res.status})` };
10601
+ }
10602
+ return {
10603
+ ok: false,
10604
+ reason: `${provider} auth check returned HTTP ${res.status} ${res.statusText} \u2014 could not confirm the credential`
10605
+ };
10606
+ } catch (err) {
10607
+ return {
10608
+ ok: false,
10609
+ reason: `${provider} auth check could not reach the provider: ${err.message}`
10610
+ };
10611
+ }
10612
+ }
10613
+ var PROVIDER_DISPATCH = {
10614
+ supabase: {
10615
+ provider: "supabase",
10616
+ primaryCredentialKey: "managementToken",
10617
+ requiredCredentialFields: ["managementToken"],
10618
+ requiredOptionFields: ["apiProjectRef", "nodeRef", "serviceName"],
10619
+ build(graph, options) {
10620
+ return createSupabaseConnector(graph, options);
10621
+ },
10622
+ // GET /v1/projects — the Management API's own auth-gated list endpoint, the
10623
+ // cheapest confirmation the management token is live (the same surface
10624
+ // client.ts polls, minus the heavy log query).
10625
+ validate({ credentials, options, fetchImpl }) {
10626
+ const cfg = options;
10627
+ const baseUrl = cfg.managementApiUrl ?? DEFAULT_SUPABASE_MANAGEMENT_API_URL;
10628
+ return authProbe({
10629
+ provider: "supabase",
10630
+ accountKey: cfg.apiProjectRef ?? "validate",
10631
+ url: `${baseUrl}/v1/projects`,
10632
+ token: String(credentials.managementToken ?? ""),
10633
+ ...fetchImpl ? { fetchImpl } : {}
10634
+ });
10635
+ }
10636
+ },
10637
+ railway: {
10638
+ provider: "railway",
10639
+ primaryCredentialKey: "token",
10640
+ requiredCredentialFields: ["token"],
10641
+ requiredOptionFields: ["environmentId", "serviceId", "serviceNameById"],
10642
+ build(graph, options) {
10643
+ const config = options;
10644
+ return {
10645
+ connector: createRailwayConnector(graph, config),
10646
+ resolveTarget: createRailwayResolveTarget(config)
10647
+ };
10648
+ },
10649
+ // A minimal GraphQL POST — Railway's gateway 401s an unauthenticated call
10650
+ // at the HTTP layer, so a 2xx confirms the token was accepted regardless of
10651
+ // the query's own shape (the connector's real queries are still
10652
+ // needs-endpoint-testing per railway/types.ts — auth is what we check).
10653
+ validate({ credentials, options, fetchImpl }) {
10654
+ const cfg = options;
10655
+ return authProbe({
10656
+ provider: "railway",
10657
+ accountKey: cfg.environmentId ?? "validate",
10658
+ url: cfg.apiUrl ?? DEFAULT_RAILWAY_API_URL,
10659
+ token: String(credentials.token ?? ""),
10660
+ init: {
10661
+ method: "POST",
10662
+ headers: { "Content-Type": "application/json" },
10663
+ body: JSON.stringify({ query: "{ __typename }" })
10664
+ },
10665
+ ...fetchImpl ? { fetchImpl } : {}
10666
+ });
10667
+ }
10668
+ },
10669
+ firebase: {
10670
+ provider: "firebase",
10671
+ // Firebase reads both projectId and accessToken from the credential; the
10672
+ // single-string form maps to the secret, and the required-fields check
10673
+ // below catches a projectId that was never supplied.
10674
+ primaryCredentialKey: "accessToken",
10675
+ requiredCredentialFields: ["projectId", "accessToken"],
10676
+ requiredOptionFields: [],
10677
+ build(graph, options) {
10678
+ return createFirebaseConnector(graph, options);
10679
+ },
10680
+ // GET the project's Cloud Logging log-name list (pageSize 1) — within the
10681
+ // same `roles/logging.viewer` grant the connector polls under, and the
10682
+ // lightest call that still fails 401/403 on a bad or wrong-scoped token.
10683
+ validate({ credentials, fetchImpl }) {
10684
+ const projectId = String(credentials.projectId ?? "");
10685
+ return authProbe({
10686
+ provider: "firebase",
10687
+ accountKey: projectId || "validate",
10688
+ url: `https://logging.googleapis.com/v2/projects/${projectId}/logs?pageSize=1`,
10689
+ token: String(credentials.accessToken ?? ""),
10690
+ ...fetchImpl ? { fetchImpl } : {}
10691
+ });
10692
+ }
10693
+ },
10694
+ cloudflare: {
10695
+ provider: "cloudflare",
10696
+ primaryCredentialKey: "apiToken",
10697
+ requiredCredentialFields: ["apiToken"],
10698
+ requiredOptionFields: ["accountId", "workers"],
10699
+ build(_graph, options) {
10700
+ const config = options;
10701
+ return {
10702
+ connector: new CloudflareConnector(config),
10703
+ resolveTarget: createCloudflareResolveTarget(config)
10704
+ };
10705
+ },
10706
+ // GET /user/tokens/verify — Cloudflare's own purpose-built "is this API
10707
+ // token live" endpoint. 200 on a valid token, 401 on an invalid one.
10708
+ validate({ credentials, options, fetchImpl }) {
10709
+ const cfg = options;
10710
+ const baseUrl = cfg.baseUrl ?? CLOUDFLARE_API_BASE_URL;
10711
+ return authProbe({
10712
+ provider: "cloudflare",
10713
+ accountKey: cfg.accountId ?? "validate",
10714
+ url: `${baseUrl}/user/tokens/verify`,
10715
+ token: String(credentials.apiToken ?? ""),
10716
+ ...fetchImpl ? { fetchImpl } : {}
10717
+ });
10718
+ }
10719
+ }
10720
+ };
10721
+ function getProviderDispatch(provider) {
10722
+ return PROVIDER_DISPATCH[provider];
10723
+ }
10724
+ function resolveEntryCredentials(dispatch, entry2, env) {
10725
+ let credentials;
10726
+ try {
10727
+ const resolved = resolveCredential(entry2.credential, env);
10728
+ credentials = resolved.kind === "single" ? { [dispatch.primaryCredentialKey]: resolved.value } : { ...resolved.fields };
10729
+ } catch (err) {
10730
+ if (err instanceof EnvRefUnsetError) return { ok: false, kind: "unset-env", reason: err.message };
10731
+ return { ok: false, kind: "error", reason: err.message };
10732
+ }
10733
+ const missingCreds = dispatch.requiredCredentialFields.filter((k) => !credentials[k]);
10734
+ if (missingCreds.length > 0) {
10735
+ return {
10736
+ ok: false,
10737
+ kind: "missing-field",
10738
+ reason: `credential missing required field(s): ${missingCreds.join(", ")}`
10739
+ };
10740
+ }
10741
+ return { ok: true, credentials };
10742
+ }
10743
+ async function validateConnectorEntry(entry2, env = process.env, fetchImpl) {
10744
+ const dispatch = PROVIDER_DISPATCH[entry2.provider];
10745
+ if (!dispatch) {
10746
+ return { status: "unknown-provider", reason: `unknown provider "${entry2.provider}"` };
10747
+ }
10748
+ const creds = resolveEntryCredentials(dispatch, entry2, env);
10749
+ if (!creds.ok) {
10750
+ if (creds.kind === "unset-env") return { status: "unset-env", reason: creds.reason };
10751
+ return { status: "missing-field", reason: creds.reason };
10752
+ }
10753
+ const options = entry2.options ?? {};
10754
+ const missingOpts = dispatch.requiredOptionFields.filter((k) => !(k in options));
10755
+ if (missingOpts.length > 0) {
10756
+ return {
10757
+ status: "missing-field",
10758
+ reason: `options missing required field(s): ${missingOpts.join(", ")}`
10759
+ };
10760
+ }
10761
+ const result = await dispatch.validate({
10762
+ credentials: creds.credentials,
10763
+ options,
10764
+ ...fetchImpl ? { fetchImpl } : {}
10765
+ });
10766
+ return result.ok ? { status: "ok" } : { status: "rejected", reason: result.reason };
10767
+ }
10768
+
10769
+ // src/daemon.ts
10770
+ init_auth();
10771
+
10772
+ // src/unrouted.ts
10773
+ init_cjs_shims();
10774
+ var import_node_fs28 = require("fs");
10775
+ var import_node_path47 = __toESM(require("path"), 1);
10776
+
10777
+ // src/daemon.ts
10778
+ var import_types42 = require("@neat.is/types");
10779
+ function daemonJsonPath(scanPath) {
10780
+ return import_node_path48.default.join(scanPath, "neat-out", "daemon.json");
10781
+ }
10782
+ function daemonsDiscoveryDir(home) {
10783
+ const base = home && home.length > 0 ? home : neatHomeFromEnv();
10784
+ return import_node_path48.default.join(base, "daemons");
10785
+ }
10786
+ function daemonDiscoveryPath(project, home) {
10787
+ return import_node_path48.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
10788
+ }
10789
+ function sanitizeDiscoveryName(project) {
10790
+ return project.replace(/[^A-Za-z0-9._-]/g, "_");
10791
+ }
10792
+ function neatHomeFromEnv() {
10793
+ const env = process.env.NEAT_HOME;
10794
+ if (env && env.length > 0) return import_node_path48.default.resolve(env);
10795
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
10796
+ return import_node_path48.default.join(home, ".neat");
10797
+ }
10798
+ async function readDaemonRecord(scanPath) {
10799
+ try {
10800
+ const raw = await import_node_fs29.promises.readFile(daemonJsonPath(scanPath), "utf8");
10801
+ const parsed = JSON.parse(raw);
10802
+ if (typeof parsed.project === "string" && parsed.ports && typeof parsed.ports.rest === "number" && typeof parsed.ports.otlp === "number" && typeof parsed.ports.web === "number") {
10803
+ return parsed;
10804
+ }
10805
+ return null;
10806
+ } catch {
10807
+ return null;
10808
+ }
10809
+ }
10810
+ function resolveNeatVersion() {
10811
+ if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
10812
+ return process.env.NEAT_LOCAL_VERSION;
10813
+ }
10814
+ try {
10815
+ const req = (0, import_node_module.createRequire)(importMetaUrl);
10816
+ const pkg = req("../package.json");
10817
+ return typeof pkg.version === "string" ? pkg.version : "0.0.0";
10818
+ } catch {
10819
+ return "0.0.0";
10820
+ }
10821
+ }
10822
+ async function writeDaemonRecord(record, home) {
10823
+ const body = JSON.stringify(record, null, 2) + "\n";
10824
+ await writeAtomically(daemonJsonPath(record.projectPath), body);
10825
+ try {
10826
+ await writeAtomically(daemonDiscoveryPath(record.project, home), body);
10827
+ } catch (err) {
10828
+ console.warn(
10829
+ `neatd: could not write discovery copy for "${record.project}" \u2014 ${err.message}`
10830
+ );
10831
+ }
10832
+ }
10833
+ async function clearDaemonRecord(record, home) {
10834
+ try {
10835
+ const stopped = { ...record, status: "stopped" };
10836
+ await writeAtomically(daemonJsonPath(record.projectPath), JSON.stringify(stopped, null, 2) + "\n");
10837
+ } catch {
10838
+ }
10839
+ try {
10840
+ await import_node_fs29.promises.unlink(daemonDiscoveryPath(record.project, home));
10841
+ } catch {
10842
+ }
10843
+ }
10844
+ function portFromListenAddress(address, fallback) {
10845
+ try {
10846
+ const port = new URL(address).port;
10847
+ const n = Number.parseInt(port, 10);
10848
+ if (Number.isFinite(n) && n > 0) return n;
10849
+ } catch {
10850
+ }
10851
+ return fallback;
10852
+ }
10853
+ function resolveHost(opts, authTokenSet) {
10854
+ if (opts.host && opts.host.length > 0) return opts.host;
10855
+ const env = process.env.HOST;
10856
+ if (env && env.length > 0) return env;
10857
+ if (!authTokenSet) return "127.0.0.1";
10858
+ return "0.0.0.0";
10859
+ }
10860
+
10861
+ // src/watch.ts
10862
+ init_otel_grpc();
10863
+
10864
+ // src/search.ts
10865
+ init_cjs_shims();
10866
+ var import_node_fs30 = require("fs");
10867
+ var import_node_path49 = __toESM(require("path"), 1);
10868
+ var import_node_crypto4 = require("crypto");
10869
+ var DEFAULT_LIMIT = 10;
10870
+ var NOMIC_DIM = 768;
10871
+ var MINI_LM_DIM = 384;
10872
+ function shouldEmbed(node) {
10873
+ return node.type !== "FrontierNode";
10874
+ }
10875
+ function embedText(node) {
10876
+ const parts = [node.id];
10877
+ const name = node.name;
10878
+ if (name) parts.push(name);
10879
+ switch (node.type) {
10880
+ case "ServiceNode": {
10881
+ const lang = node.language;
10882
+ if (lang) parts.push(`language=${lang}`);
10883
+ break;
10884
+ }
10885
+ case "DatabaseNode": {
10886
+ const eng = node.engine;
10887
+ const ver = node.engineVersion;
10888
+ if (eng) parts.push(`engine=${eng}`);
10889
+ if (ver) parts.push(`engineVersion=${ver}`);
10890
+ break;
10891
+ }
10892
+ case "InfraNode": {
10893
+ const kind = node.kind;
10894
+ if (kind) parts.push(`kind=${kind}`);
10895
+ break;
10896
+ }
10897
+ case "ConfigNode": {
10898
+ const filePath = node.path;
10899
+ if (filePath) parts.push(`path=${filePath}`);
10900
+ break;
10901
+ }
10902
+ case "RouteNode": {
10903
+ const method = node.method;
10904
+ const tmpl = node.pathTemplate;
10905
+ if (method) parts.push(`method=${method}`);
10906
+ if (tmpl) parts.push(`path=${tmpl}`);
10907
+ break;
10908
+ }
10909
+ case "GraphQLOperationNode": {
10910
+ const opType = node.operationType;
10911
+ if (opType) parts.push(`operationType=${opType}`);
10912
+ break;
10913
+ }
10914
+ case "GrpcMethodNode": {
10915
+ const rpcService = node.rpcService;
10916
+ const rpcMethod = node.rpcMethod;
10917
+ if (rpcService) parts.push(`rpcService=${rpcService}`);
10918
+ if (rpcMethod) parts.push(`rpcMethod=${rpcMethod}`);
9189
10919
  break;
9190
10920
  }
9191
10921
  case "WebSocketChannelNode": {
@@ -9199,7 +10929,7 @@ function embedText(node) {
9199
10929
  return parts.join(" ");
9200
10930
  }
9201
10931
  function attrsHash(node) {
9202
- return (0, import_node_crypto3.createHash)("sha1").update(embedText(node)).digest("hex").slice(0, 16);
10932
+ return (0, import_node_crypto4.createHash)("sha1").update(embedText(node)).digest("hex").slice(0, 16);
9203
10933
  }
9204
10934
  function cosine(a, b) {
9205
10935
  if (a.length !== b.length) return 0;
@@ -9288,7 +11018,7 @@ async function pickEmbedder() {
9288
11018
  }
9289
11019
  async function readCache(cachePath) {
9290
11020
  try {
9291
- const raw = await import_node_fs29.promises.readFile(cachePath, "utf8");
11021
+ const raw = await import_node_fs30.promises.readFile(cachePath, "utf8");
9292
11022
  const parsed = JSON.parse(raw);
9293
11023
  if (parsed.version !== 1) return null;
9294
11024
  return parsed;
@@ -9297,8 +11027,8 @@ async function readCache(cachePath) {
9297
11027
  }
9298
11028
  }
9299
11029
  async function writeCache(cachePath, cache) {
9300
- await import_node_fs29.promises.mkdir(import_node_path48.default.dirname(cachePath), { recursive: true });
9301
- await import_node_fs29.promises.writeFile(cachePath, JSON.stringify(cache));
11030
+ await import_node_fs30.promises.mkdir(import_node_path49.default.dirname(cachePath), { recursive: true });
11031
+ await import_node_fs30.promises.writeFile(cachePath, JSON.stringify(cache));
9302
11032
  }
9303
11033
  var VectorIndex = class {
9304
11034
  constructor(embedder, cachePath) {
@@ -9455,8 +11185,8 @@ var ALL_PHASES = [
9455
11185
  ];
9456
11186
  function classifyChange(relPath) {
9457
11187
  const phases = /* @__PURE__ */ new Set();
9458
- const base = import_node_path49.default.basename(relPath).toLowerCase();
9459
- const segments = relPath.split(import_node_path49.default.sep).map((s) => s.toLowerCase());
11188
+ const base = import_node_path50.default.basename(relPath).toLowerCase();
11189
+ const segments = relPath.split(import_node_path50.default.sep).map((s) => s.toLowerCase());
9460
11190
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
9461
11191
  phases.add("services");
9462
11192
  phases.add("aliases");
@@ -9577,16 +11307,16 @@ function countWatchableDirs(scanPath, limit) {
9577
11307
  if (count >= limit) return;
9578
11308
  let entries;
9579
11309
  try {
9580
- entries = import_node_fs30.default.readdirSync(dir, { withFileTypes: true });
11310
+ entries = import_node_fs31.default.readdirSync(dir, { withFileTypes: true });
9581
11311
  } catch {
9582
11312
  return;
9583
11313
  }
9584
11314
  for (const e of entries) {
9585
11315
  if (count >= limit) return;
9586
11316
  if (!e.isDirectory()) continue;
9587
- if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path49.default.join(dir, e.name) + import_node_path49.default.sep))) continue;
11317
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path50.default.join(dir, e.name) + import_node_path50.default.sep))) continue;
9588
11318
  count++;
9589
- if (depth < 2) visit(import_node_path49.default.join(dir, e.name), depth + 1);
11319
+ if (depth < 2) visit(import_node_path50.default.join(dir, e.name), depth + 1);
9590
11320
  }
9591
11321
  };
9592
11322
  visit(scanPath, 0);
@@ -9604,8 +11334,8 @@ async function startWatch(graph, opts) {
9604
11334
  const projectName = opts.project ?? DEFAULT_PROJECT;
9605
11335
  await loadGraphFromDisk(graph, opts.outPath);
9606
11336
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
9607
- const policyFilePath = import_node_path49.default.join(opts.scanPath, "policy.json");
9608
- const policyViolationsPath = import_node_path49.default.join(import_node_path49.default.dirname(opts.outPath), "policy-violations.ndjson");
11337
+ const policyFilePath = import_node_path50.default.join(opts.scanPath, "policy.json");
11338
+ const policyViolationsPath = import_node_path50.default.join(import_node_path50.default.dirname(opts.outPath), "policy-violations.ndjson");
9609
11339
  let policies = [];
9610
11340
  try {
9611
11341
  policies = await loadPolicyFile(policyFilePath);
@@ -9656,7 +11386,7 @@ async function startWatch(graph, opts) {
9656
11386
  assertBindAuthority(host, auth.authToken);
9657
11387
  const port = opts.port ?? 8080;
9658
11388
  const otelPort = opts.otelPort ?? 4318;
9659
- const cachePath = opts.embeddingsCachePath ?? import_node_path49.default.join(import_node_path49.default.dirname(opts.outPath), "embeddings.json");
11389
+ const cachePath = opts.embeddingsCachePath ?? import_node_path50.default.join(import_node_path50.default.dirname(opts.outPath), "embeddings.json");
9660
11390
  let searchIndex;
9661
11391
  try {
9662
11392
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -9674,7 +11404,7 @@ async function startWatch(graph, opts) {
9674
11404
  // Paths are derived from the explicit options the watch caller passes
9675
11405
  // — pathsForProject is only used to fill in the embeddings/snapshot
9676
11406
  // fields so the registry shape is complete.
9677
- ...pathsForProject(projectName, import_node_path49.default.dirname(opts.outPath)),
11407
+ ...pathsForProject(projectName, import_node_path50.default.dirname(opts.outPath)),
9678
11408
  snapshotPath: opts.outPath,
9679
11409
  errorsPath: opts.errorsPath,
9680
11410
  staleEventsPath: opts.staleEventsPath
@@ -9792,9 +11522,9 @@ async function startWatch(graph, opts) {
9792
11522
  };
9793
11523
  const onPath = (absPath) => {
9794
11524
  if (shouldIgnore(absPath)) return;
9795
- const rel = import_node_path49.default.relative(opts.scanPath, absPath);
11525
+ const rel = import_node_path50.default.relative(opts.scanPath, absPath);
9796
11526
  if (!rel || rel.startsWith("..")) return;
9797
- pendingPaths.add(rel.split(import_node_path49.default.sep).join("/"));
11527
+ pendingPaths.add(rel.split(import_node_path50.default.sep).join("/"));
9798
11528
  const phases = classifyChange(rel);
9799
11529
  if (phases.size === 0) {
9800
11530
  for (const p of ALL_PHASES) pending.add(p);
@@ -9850,12 +11580,12 @@ async function startWatch(graph, opts) {
9850
11580
 
9851
11581
  // src/deploy/detect.ts
9852
11582
  init_cjs_shims();
9853
- var import_node_fs31 = require("fs");
9854
- var import_node_path50 = __toESM(require("path"), 1);
11583
+ var import_node_fs32 = require("fs");
11584
+ var import_node_path51 = __toESM(require("path"), 1);
9855
11585
  var import_node_child_process2 = require("child_process");
9856
- var import_node_crypto4 = require("crypto");
11586
+ var import_node_crypto5 = require("crypto");
9857
11587
  function generateToken() {
9858
- return (0, import_node_crypto4.randomBytes)(32).toString("base64url");
11588
+ return (0, import_node_crypto5.randomBytes)(32).toString("base64url");
9859
11589
  }
9860
11590
  async function probeBinary(binary, arg) {
9861
11591
  return new Promise((resolve) => {
@@ -9951,21 +11681,21 @@ async function runDeploy(opts = {}) {
9951
11681
  const token = generateToken();
9952
11682
  switch (substrate) {
9953
11683
  case "docker-compose": {
9954
- const artifactPath = import_node_path50.default.join(cwd, "docker-compose.neat.yml");
11684
+ const artifactPath = import_node_path51.default.join(cwd, "docker-compose.neat.yml");
9955
11685
  const contents = emitDockerCompose(cwd);
9956
- await import_node_fs31.promises.writeFile(artifactPath, contents, "utf8");
11686
+ await import_node_fs32.promises.writeFile(artifactPath, contents, "utf8");
9957
11687
  return {
9958
11688
  substrate,
9959
11689
  artifactPath,
9960
11690
  token,
9961
11691
  contents,
9962
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path50.default.basename(artifactPath)} up -d`
11692
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path51.default.basename(artifactPath)} up -d`
9963
11693
  };
9964
11694
  }
9965
11695
  case "systemd": {
9966
- const artifactPath = import_node_path50.default.join(cwd, "neat.service");
11696
+ const artifactPath = import_node_path51.default.join(cwd, "neat.service");
9967
11697
  const contents = emitSystemdUnit(cwd);
9968
- await import_node_fs31.promises.writeFile(artifactPath, contents, "utf8");
11698
+ await import_node_fs32.promises.writeFile(artifactPath, contents, "utf8");
9969
11699
  return {
9970
11700
  substrate,
9971
11701
  artifactPath,
@@ -9998,8 +11728,8 @@ init_cjs_shims();
9998
11728
 
9999
11729
  // src/installers/javascript.ts
10000
11730
  init_cjs_shims();
10001
- var import_node_fs32 = require("fs");
10002
- var import_node_path51 = __toESM(require("path"), 1);
11731
+ var import_node_fs33 = require("fs");
11732
+ var import_node_path52 = __toESM(require("path"), 1);
10003
11733
  var import_semver2 = __toESM(require("semver"), 1);
10004
11734
 
10005
11735
  // src/installers/templates.ts
@@ -10614,15 +12344,15 @@ var OTEL_ENV = {
10614
12344
  value: "http://localhost:4318/projects/<project>/v1/traces"
10615
12345
  };
10616
12346
  function serviceNodeName(pkg, serviceDir) {
10617
- return pkg.name ?? import_node_path51.default.basename(serviceDir);
12347
+ return pkg.name ?? import_node_path52.default.basename(serviceDir);
10618
12348
  }
10619
12349
  function projectToken(pkg, serviceDir, project) {
10620
12350
  if (project && project.length > 0) return project;
10621
- return pkg.name ?? import_node_path51.default.basename(serviceDir);
12351
+ return pkg.name ?? import_node_path52.default.basename(serviceDir);
10622
12352
  }
10623
12353
  async function readJsonFile(p) {
10624
12354
  try {
10625
- const raw = await import_node_fs32.promises.readFile(p, "utf8");
12355
+ const raw = await import_node_fs33.promises.readFile(p, "utf8");
10626
12356
  return JSON.parse(raw);
10627
12357
  } catch {
10628
12358
  return null;
@@ -10631,16 +12361,16 @@ async function readJsonFile(p) {
10631
12361
  async function detectRuntimeKind(pkgRoot, pkg) {
10632
12362
  const deps = allDeps(pkg);
10633
12363
  if ("react-native" in deps || "expo" in deps) return "react-native";
10634
- const appJson = await readJsonFile(import_node_path51.default.join(pkgRoot, "app.json"));
12364
+ const appJson = await readJsonFile(import_node_path52.default.join(pkgRoot, "app.json"));
10635
12365
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
10636
12366
  return "react-native";
10637
12367
  }
10638
- if (await exists3(import_node_path51.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path51.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path51.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
12368
+ if (await exists3(import_node_path52.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path52.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path52.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
10639
12369
  return "browser-bundle";
10640
12370
  }
10641
- if (await exists3(import_node_path51.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
10642
- if (await exists3(import_node_path51.default.join(pkgRoot, "bun.lockb"))) return "bun";
10643
- if (await exists3(import_node_path51.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path51.default.join(pkgRoot, "deno.lock"))) {
12371
+ if (await exists3(import_node_path52.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
12372
+ if (await exists3(import_node_path52.default.join(pkgRoot, "bun.lockb"))) return "bun";
12373
+ if (await exists3(import_node_path52.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path52.default.join(pkgRoot, "deno.lock"))) {
10644
12374
  return "deno";
10645
12375
  }
10646
12376
  const engines = pkg.engines ?? {};
@@ -10649,7 +12379,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
10649
12379
  }
10650
12380
  async function readPackageJson2(serviceDir) {
10651
12381
  try {
10652
- const raw = await import_node_fs32.promises.readFile(import_node_path51.default.join(serviceDir, "package.json"), "utf8");
12382
+ const raw = await import_node_fs33.promises.readFile(import_node_path52.default.join(serviceDir, "package.json"), "utf8");
10653
12383
  return JSON.parse(raw);
10654
12384
  } catch {
10655
12385
  return null;
@@ -10657,7 +12387,7 @@ async function readPackageJson2(serviceDir) {
10657
12387
  }
10658
12388
  async function exists3(p) {
10659
12389
  try {
10660
- await import_node_fs32.promises.stat(p);
12390
+ await import_node_fs33.promises.stat(p);
10661
12391
  return true;
10662
12392
  } catch {
10663
12393
  return false;
@@ -10665,7 +12395,7 @@ async function exists3(p) {
10665
12395
  }
10666
12396
  async function readFileMaybe(p) {
10667
12397
  try {
10668
- return await import_node_fs32.promises.readFile(p, "utf8");
12398
+ return await import_node_fs33.promises.readFile(p, "utf8");
10669
12399
  } catch {
10670
12400
  return null;
10671
12401
  }
@@ -10693,7 +12423,7 @@ function needsVersionUpgrade(installed, expected) {
10693
12423
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
10694
12424
  async function findNextConfig(serviceDir) {
10695
12425
  for (const name of NEXT_CONFIG_CANDIDATES) {
10696
- const candidate = import_node_path51.default.join(serviceDir, name);
12426
+ const candidate = import_node_path52.default.join(serviceDir, name);
10697
12427
  if (await exists3(candidate)) return candidate;
10698
12428
  }
10699
12429
  return null;
@@ -10762,7 +12492,7 @@ function hasRemixDependency(pkg) {
10762
12492
  }
10763
12493
  async function findRemixEntry(serviceDir) {
10764
12494
  for (const rel of REMIX_ENTRY_CANDIDATES) {
10765
- const candidate = import_node_path51.default.join(serviceDir, rel);
12495
+ const candidate = import_node_path52.default.join(serviceDir, rel);
10766
12496
  if (await exists3(candidate)) return candidate;
10767
12497
  }
10768
12498
  return null;
@@ -10774,14 +12504,14 @@ function hasSvelteKitDependency(pkg) {
10774
12504
  }
10775
12505
  async function findSvelteKitHooks(serviceDir) {
10776
12506
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
10777
- const candidate = import_node_path51.default.join(serviceDir, rel);
12507
+ const candidate = import_node_path52.default.join(serviceDir, rel);
10778
12508
  if (await exists3(candidate)) return candidate;
10779
12509
  }
10780
12510
  return null;
10781
12511
  }
10782
12512
  async function findSvelteKitConfig(serviceDir) {
10783
12513
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
10784
- const candidate = import_node_path51.default.join(serviceDir, rel);
12514
+ const candidate = import_node_path52.default.join(serviceDir, rel);
10785
12515
  if (await exists3(candidate)) return candidate;
10786
12516
  }
10787
12517
  return null;
@@ -10792,7 +12522,7 @@ function hasNuxtDependency(pkg) {
10792
12522
  }
10793
12523
  async function findNuxtConfig(serviceDir) {
10794
12524
  for (const name of NUXT_CONFIG_CANDIDATES) {
10795
- const candidate = import_node_path51.default.join(serviceDir, name);
12525
+ const candidate = import_node_path52.default.join(serviceDir, name);
10796
12526
  if (await exists3(candidate)) return candidate;
10797
12527
  }
10798
12528
  return null;
@@ -10803,7 +12533,7 @@ function hasAstroDependency(pkg) {
10803
12533
  }
10804
12534
  async function findAstroConfig(serviceDir) {
10805
12535
  for (const name of ASTRO_CONFIG_CANDIDATES) {
10806
- const candidate = import_node_path51.default.join(serviceDir, name);
12536
+ const candidate = import_node_path52.default.join(serviceDir, name);
10807
12537
  if (await exists3(candidate)) return candidate;
10808
12538
  }
10809
12539
  return null;
@@ -10817,7 +12547,7 @@ function parseNextMajor(range) {
10817
12547
  return Number.isFinite(n) ? n : null;
10818
12548
  }
10819
12549
  async function isTypeScriptProject(serviceDir) {
10820
- return exists3(import_node_path51.default.join(serviceDir, "tsconfig.json"));
12550
+ return exists3(import_node_path52.default.join(serviceDir, "tsconfig.json"));
10821
12551
  }
10822
12552
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
10823
12553
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -10866,7 +12596,7 @@ function entryFromScript(script) {
10866
12596
  }
10867
12597
  async function resolveEntry(serviceDir, pkg) {
10868
12598
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
10869
- const candidate = import_node_path51.default.resolve(serviceDir, pkg.main);
12599
+ const candidate = import_node_path52.default.resolve(serviceDir, pkg.main);
10870
12600
  if (await exists3(candidate)) return candidate;
10871
12601
  }
10872
12602
  if (pkg.bin) {
@@ -10880,40 +12610,40 @@ async function resolveEntry(serviceDir, pkg) {
10880
12610
  if (typeof first === "string") binEntry = first;
10881
12611
  }
10882
12612
  if (binEntry) {
10883
- const candidate = import_node_path51.default.resolve(serviceDir, binEntry);
12613
+ const candidate = import_node_path52.default.resolve(serviceDir, binEntry);
10884
12614
  if (await exists3(candidate)) return candidate;
10885
12615
  }
10886
12616
  }
10887
12617
  const startEntry = entryFromScript(pkg.scripts?.start);
10888
12618
  if (startEntry) {
10889
- const candidate = import_node_path51.default.resolve(serviceDir, startEntry);
12619
+ const candidate = import_node_path52.default.resolve(serviceDir, startEntry);
10890
12620
  if (await exists3(candidate)) return candidate;
10891
12621
  }
10892
12622
  const devEntry = entryFromScript(pkg.scripts?.dev);
10893
12623
  if (devEntry) {
10894
- const candidate = import_node_path51.default.resolve(serviceDir, devEntry);
12624
+ const candidate = import_node_path52.default.resolve(serviceDir, devEntry);
10895
12625
  if (await exists3(candidate)) return candidate;
10896
12626
  }
10897
12627
  for (const rel of SRC_INDEX_CANDIDATES) {
10898
- const candidate = import_node_path51.default.join(serviceDir, rel);
12628
+ const candidate = import_node_path52.default.join(serviceDir, rel);
10899
12629
  if (await exists3(candidate)) return candidate;
10900
12630
  }
10901
12631
  for (const rel of SRC_NAMED_CANDIDATES) {
10902
- const candidate = import_node_path51.default.join(serviceDir, rel);
12632
+ const candidate = import_node_path52.default.join(serviceDir, rel);
10903
12633
  if (await exists3(candidate)) return candidate;
10904
12634
  }
10905
12635
  for (const rel of ROOT_NAMED_CANDIDATES) {
10906
- const candidate = import_node_path51.default.join(serviceDir, rel);
12636
+ const candidate = import_node_path52.default.join(serviceDir, rel);
10907
12637
  if (await exists3(candidate)) return candidate;
10908
12638
  }
10909
12639
  for (const name of INDEX_CANDIDATES) {
10910
- const candidate = import_node_path51.default.join(serviceDir, name);
12640
+ const candidate = import_node_path52.default.join(serviceDir, name);
10911
12641
  if (await exists3(candidate)) return candidate;
10912
12642
  }
10913
12643
  return null;
10914
12644
  }
10915
12645
  function dispatchEntry(entryFile, pkg) {
10916
- const ext = import_node_path51.default.extname(entryFile).toLowerCase();
12646
+ const ext = import_node_path52.default.extname(entryFile).toLowerCase();
10917
12647
  if (ext === ".ts" || ext === ".tsx") return "ts";
10918
12648
  if (ext === ".mjs") return "esm";
10919
12649
  if (ext === ".cjs") return "cjs";
@@ -10930,9 +12660,9 @@ function otelInitContents(flavor) {
10930
12660
  return OTEL_INIT_CJS;
10931
12661
  }
10932
12662
  function injectionLine(flavor, entryFile, otelInitFile) {
10933
- let rel = import_node_path51.default.relative(import_node_path51.default.dirname(entryFile), otelInitFile);
12663
+ let rel = import_node_path52.default.relative(import_node_path52.default.dirname(entryFile), otelInitFile);
10934
12664
  if (!rel.startsWith(".")) rel = `./${rel}`;
10935
- rel = rel.split(import_node_path51.default.sep).join("/");
12665
+ rel = rel.split(import_node_path52.default.sep).join("/");
10936
12666
  if (flavor === "cjs") return `require('${rel}')`;
10937
12667
  if (flavor === "esm") return `import '${rel}'`;
10938
12668
  const tsRel = rel.replace(/\.ts$/, "");
@@ -10945,27 +12675,27 @@ function lineIsOtelInjection(line) {
10945
12675
  }
10946
12676
  async function detectsSrcLayout(serviceDir) {
10947
12677
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
10948
- exists3(import_node_path51.default.join(serviceDir, "src", "app")),
10949
- exists3(import_node_path51.default.join(serviceDir, "src", "pages")),
10950
- exists3(import_node_path51.default.join(serviceDir, "app")),
10951
- exists3(import_node_path51.default.join(serviceDir, "pages"))
12678
+ exists3(import_node_path52.default.join(serviceDir, "src", "app")),
12679
+ exists3(import_node_path52.default.join(serviceDir, "src", "pages")),
12680
+ exists3(import_node_path52.default.join(serviceDir, "app")),
12681
+ exists3(import_node_path52.default.join(serviceDir, "pages"))
10952
12682
  ]);
10953
12683
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
10954
12684
  }
10955
12685
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
10956
12686
  const useTs = await isTypeScriptProject(serviceDir);
10957
12687
  const srcLayout = await detectsSrcLayout(serviceDir);
10958
- const baseDir = srcLayout ? import_node_path51.default.join(serviceDir, "src") : serviceDir;
10959
- const instrumentationFile = import_node_path51.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
10960
- const instrumentationNodeFile = import_node_path51.default.join(
12688
+ const baseDir = srcLayout ? import_node_path52.default.join(serviceDir, "src") : serviceDir;
12689
+ const instrumentationFile = import_node_path52.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
12690
+ const instrumentationNodeFile = import_node_path52.default.join(
10961
12691
  baseDir,
10962
12692
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
10963
12693
  );
10964
- const instrumentationEdgeFile = import_node_path51.default.join(
12694
+ const instrumentationEdgeFile = import_node_path52.default.join(
10965
12695
  baseDir,
10966
12696
  useTs ? "instrumentation.edge.ts" : "instrumentation.edge.js"
10967
12697
  );
10968
- const envNeatFile = import_node_path51.default.join(baseDir, ".env.neat");
12698
+ const envNeatFile = import_node_path52.default.join(baseDir, ".env.neat");
10969
12699
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
10970
12700
  const dependencyEdits = [];
10971
12701
  for (const sdk of SDK_PACKAGES) {
@@ -11042,7 +12772,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
11042
12772
  const nextMajor = parseNextMajor(nextRange);
11043
12773
  if (nextMajor !== null && nextMajor < 15) {
11044
12774
  try {
11045
- const raw = await import_node_fs32.promises.readFile(nextConfigPath, "utf8");
12775
+ const raw = await import_node_fs33.promises.readFile(nextConfigPath, "utf8");
11046
12776
  if (!raw.includes("instrumentationHook")) {
11047
12777
  nextConfigEdit = {
11048
12778
  file: nextConfigPath,
@@ -11090,7 +12820,7 @@ function buildDependencyEdits(pkg, manifestPath) {
11090
12820
  return edits;
11091
12821
  }
11092
12822
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
11093
- const envNeatFile = import_node_path51.default.join(serviceDir, ".env.neat");
12823
+ const envNeatFile = import_node_path52.default.join(serviceDir, ".env.neat");
11094
12824
  if (!await exists3(envNeatFile)) {
11095
12825
  generatedFiles.push({
11096
12826
  file: envNeatFile,
@@ -11125,7 +12855,7 @@ function fileImportsOtelHook(raw, specifiers) {
11125
12855
  }
11126
12856
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
11127
12857
  const useTs = await isTypeScriptProject(serviceDir);
11128
- const otelServerFile = import_node_path51.default.join(
12858
+ const otelServerFile = import_node_path52.default.join(
11129
12859
  serviceDir,
11130
12860
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
11131
12861
  );
@@ -11146,7 +12876,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
11146
12876
  await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
11147
12877
  const entrypointEdits = [];
11148
12878
  try {
11149
- const raw = await import_node_fs32.promises.readFile(entryFile, "utf8");
12879
+ const raw = await import_node_fs33.promises.readFile(entryFile, "utf8");
11150
12880
  if (!fileImportsOtelHook(raw, ["./otel.server"])) {
11151
12881
  const lines = raw.split(/\r?\n/);
11152
12882
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -11182,11 +12912,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
11182
12912
  }
11183
12913
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
11184
12914
  const useTs = await isTypeScriptProject(serviceDir);
11185
- const otelInitFile = import_node_path51.default.join(
12915
+ const otelInitFile = import_node_path52.default.join(
11186
12916
  serviceDir,
11187
12917
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
11188
12918
  );
11189
- const resolvedHooksFile = hooksFile ?? import_node_path51.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
12919
+ const resolvedHooksFile = hooksFile ?? import_node_path52.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
11190
12920
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
11191
12921
  const generatedFiles = [];
11192
12922
  const entrypointEdits = [];
@@ -11211,7 +12941,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
11211
12941
  });
11212
12942
  } else {
11213
12943
  try {
11214
- const raw = await import_node_fs32.promises.readFile(hooksFile, "utf8");
12944
+ const raw = await import_node_fs33.promises.readFile(hooksFile, "utf8");
11215
12945
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
11216
12946
  const lines = raw.split(/\r?\n/);
11217
12947
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -11248,11 +12978,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
11248
12978
  }
11249
12979
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
11250
12980
  const useTs = await isTypeScriptProject(serviceDir);
11251
- const otelPluginFile = import_node_path51.default.join(
12981
+ const otelPluginFile = import_node_path52.default.join(
11252
12982
  serviceDir,
11253
12983
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
11254
12984
  );
11255
- const otelInitFile = import_node_path51.default.join(
12985
+ const otelInitFile = import_node_path52.default.join(
11256
12986
  serviceDir,
11257
12987
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
11258
12988
  );
@@ -11303,19 +13033,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
11303
13033
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
11304
13034
  async function findAstroMiddleware(serviceDir) {
11305
13035
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
11306
- const candidate = import_node_path51.default.join(serviceDir, rel);
13036
+ const candidate = import_node_path52.default.join(serviceDir, rel);
11307
13037
  if (await exists3(candidate)) return candidate;
11308
13038
  }
11309
13039
  return null;
11310
13040
  }
11311
13041
  async function planAstro(serviceDir, pkg, manifestPath, project) {
11312
13042
  const useTs = await isTypeScriptProject(serviceDir);
11313
- const otelInitFile = import_node_path51.default.join(
13043
+ const otelInitFile = import_node_path52.default.join(
11314
13044
  serviceDir,
11315
13045
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
11316
13046
  );
11317
13047
  const existingMiddleware = await findAstroMiddleware(serviceDir);
11318
- const middlewareFile = existingMiddleware ?? import_node_path51.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
13048
+ const middlewareFile = existingMiddleware ?? import_node_path52.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
11319
13049
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
11320
13050
  const generatedFiles = [];
11321
13051
  const entrypointEdits = [];
@@ -11340,7 +13070,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
11340
13070
  });
11341
13071
  } else {
11342
13072
  try {
11343
- const raw = await import_node_fs32.promises.readFile(existingMiddleware, "utf8");
13073
+ const raw = await import_node_fs33.promises.readFile(existingMiddleware, "utf8");
11344
13074
  if (!fileImportsOtelHook(raw, ["./otel-init"])) {
11345
13075
  const lines = raw.split(/\r?\n/);
11346
13076
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
@@ -11411,7 +13141,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
11411
13141
  }
11412
13142
  async function plan(serviceDir, opts) {
11413
13143
  const pkg = await readPackageJson2(serviceDir);
11414
- const manifestPath = import_node_path51.default.join(serviceDir, "package.json");
13144
+ const manifestPath = import_node_path52.default.join(serviceDir, "package.json");
11415
13145
  const project = opts?.project;
11416
13146
  const empty = {
11417
13147
  language: "javascript",
@@ -11446,8 +13176,8 @@ async function plan(serviceDir, opts) {
11446
13176
  return { ...empty, libOnly: true };
11447
13177
  }
11448
13178
  const flavor = dispatchEntry(entryFile, pkg);
11449
- const otelInitFile = import_node_path51.default.join(import_node_path51.default.dirname(entryFile), otelInitFilename(flavor));
11450
- const envNeatFile = import_node_path51.default.join(serviceDir, ".env.neat");
13179
+ const otelInitFile = import_node_path52.default.join(import_node_path52.default.dirname(entryFile), otelInitFilename(flavor));
13180
+ const envNeatFile = import_node_path52.default.join(serviceDir, ".env.neat");
11451
13181
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
11452
13182
  const dependencyEdits = [];
11453
13183
  for (const sdk of SDK_PACKAGES) {
@@ -11471,7 +13201,7 @@ async function plan(serviceDir, opts) {
11471
13201
  }
11472
13202
  const entrypointEdits = [];
11473
13203
  try {
11474
- const raw = await import_node_fs32.promises.readFile(entryFile, "utf8");
13204
+ const raw = await import_node_fs33.promises.readFile(entryFile, "utf8");
11475
13205
  const lines = raw.split(/\r?\n/);
11476
13206
  const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
11477
13207
  if (!lineIsOtelInjection(firstReal)) {
@@ -11516,13 +13246,13 @@ async function plan(serviceDir, opts) {
11516
13246
  };
11517
13247
  }
11518
13248
  function isAllowedWritePath(serviceDir, target) {
11519
- const rel = import_node_path51.default.relative(serviceDir, target);
13249
+ const rel = import_node_path52.default.relative(serviceDir, target);
11520
13250
  if (rel.startsWith("..")) return false;
11521
- const base = import_node_path51.default.basename(target);
13251
+ const base = import_node_path52.default.basename(target);
11522
13252
  if (base === "package.json") return true;
11523
13253
  if (base === ".env.neat") return true;
11524
13254
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
11525
- const relPosix = rel.split(import_node_path51.default.sep).join("/");
13255
+ const relPosix = rel.split(import_node_path52.default.sep).join("/");
11526
13256
  if (/^instrumentation(?:\.(?:node|edge))?\.(?:js|cjs|mjs|ts)$/.test(base)) {
11527
13257
  if (relPosix === base) return true;
11528
13258
  if (relPosix === `src/${base}`) return true;
@@ -11539,10 +13269,10 @@ function isAllowedWritePath(serviceDir, target) {
11539
13269
  return false;
11540
13270
  }
11541
13271
  async function writeAtomic(file, contents) {
11542
- await import_node_fs32.promises.mkdir(import_node_path51.default.dirname(file), { recursive: true });
13272
+ await import_node_fs33.promises.mkdir(import_node_path52.default.dirname(file), { recursive: true });
11543
13273
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
11544
- await import_node_fs32.promises.writeFile(tmp, contents, "utf8");
11545
- await import_node_fs32.promises.rename(tmp, file);
13274
+ await import_node_fs33.promises.writeFile(tmp, contents, "utf8");
13275
+ await import_node_fs33.promises.rename(tmp, file);
11546
13276
  }
11547
13277
  async function apply(installPlan) {
11548
13278
  const { serviceDir } = installPlan;
@@ -11618,7 +13348,7 @@ async function apply(installPlan) {
11618
13348
  for (const target of allTargets) {
11619
13349
  if (await exists3(target)) {
11620
13350
  try {
11621
- originals.set(target, await import_node_fs32.promises.readFile(target, "utf8"));
13351
+ originals.set(target, await import_node_fs33.promises.readFile(target, "utf8"));
11622
13352
  } catch {
11623
13353
  }
11624
13354
  }
@@ -11701,14 +13431,14 @@ async function rollback(installPlan, originals, createdFiles) {
11701
13431
  const removed = [];
11702
13432
  for (const [file, raw] of originals.entries()) {
11703
13433
  try {
11704
- await import_node_fs32.promises.writeFile(file, raw, "utf8");
13434
+ await import_node_fs33.promises.writeFile(file, raw, "utf8");
11705
13435
  restored.push(file);
11706
13436
  } catch {
11707
13437
  }
11708
13438
  }
11709
13439
  for (const file of createdFiles) {
11710
13440
  try {
11711
- await import_node_fs32.promises.unlink(file);
13441
+ await import_node_fs33.promises.unlink(file);
11712
13442
  removed.push(file);
11713
13443
  } catch {
11714
13444
  }
@@ -11723,8 +13453,8 @@ async function rollback(installPlan, originals, createdFiles) {
11723
13453
  ...removed.map((f) => `removed: ${f}`),
11724
13454
  ""
11725
13455
  ];
11726
- const rollbackPath = import_node_path51.default.join(installPlan.serviceDir, "neat-rollback.patch");
11727
- await import_node_fs32.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
13456
+ const rollbackPath = import_node_path52.default.join(installPlan.serviceDir, "neat-rollback.patch");
13457
+ await import_node_fs33.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
11728
13458
  }
11729
13459
  function injectInstrumentationHook(raw) {
11730
13460
  if (raw.includes("instrumentationHook")) return raw;
@@ -11753,8 +13483,8 @@ var javascriptInstaller = {
11753
13483
 
11754
13484
  // src/installers/python.ts
11755
13485
  init_cjs_shims();
11756
- var import_node_fs33 = require("fs");
11757
- var import_node_path52 = __toESM(require("path"), 1);
13486
+ var import_node_fs34 = require("fs");
13487
+ var import_node_path53 = __toESM(require("path"), 1);
11758
13488
  var SDK_PACKAGES2 = [
11759
13489
  { name: "opentelemetry-distro", version: ">=0.49b0" },
11760
13490
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -11766,7 +13496,7 @@ var OTEL_ENV2 = {
11766
13496
  };
11767
13497
  async function exists4(p) {
11768
13498
  try {
11769
- await import_node_fs33.promises.stat(p);
13499
+ await import_node_fs34.promises.stat(p);
11770
13500
  return true;
11771
13501
  } catch {
11772
13502
  return false;
@@ -11775,7 +13505,7 @@ async function exists4(p) {
11775
13505
  async function detect2(serviceDir) {
11776
13506
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
11777
13507
  for (const m of markers) {
11778
- if (await exists4(import_node_path52.default.join(serviceDir, m))) return true;
13508
+ if (await exists4(import_node_path53.default.join(serviceDir, m))) return true;
11779
13509
  }
11780
13510
  return false;
11781
13511
  }
@@ -11785,9 +13515,9 @@ function reqPackageName(line) {
11785
13515
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
11786
13516
  }
11787
13517
  async function planRequirementsTxtEdits(serviceDir) {
11788
- const file = import_node_path52.default.join(serviceDir, "requirements.txt");
13518
+ const file = import_node_path53.default.join(serviceDir, "requirements.txt");
11789
13519
  if (!await exists4(file)) return null;
11790
- const raw = await import_node_fs33.promises.readFile(file, "utf8");
13520
+ const raw = await import_node_fs34.promises.readFile(file, "utf8");
11791
13521
  const presentNames = new Set(
11792
13522
  raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
11793
13523
  );
@@ -11795,9 +13525,9 @@ async function planRequirementsTxtEdits(serviceDir) {
11795
13525
  return { manifest: file, missing: [...missing] };
11796
13526
  }
11797
13527
  async function planProcfileEdits(serviceDir) {
11798
- const procfile = import_node_path52.default.join(serviceDir, "Procfile");
13528
+ const procfile = import_node_path53.default.join(serviceDir, "Procfile");
11799
13529
  if (!await exists4(procfile)) return [];
11800
- const raw = await import_node_fs33.promises.readFile(procfile, "utf8");
13530
+ const raw = await import_node_fs34.promises.readFile(procfile, "utf8");
11801
13531
  const edits = [];
11802
13532
  for (const line of raw.split(/\r?\n/)) {
11803
13533
  if (line.length === 0) continue;
@@ -11849,8 +13579,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
11849
13579
  const next = `${original}${trailing}${newlines.join("\n")}
11850
13580
  `;
11851
13581
  const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
11852
- await import_node_fs33.promises.writeFile(tmp, next, "utf8");
11853
- await import_node_fs33.promises.rename(tmp, manifest);
13582
+ await import_node_fs34.promises.writeFile(tmp, next, "utf8");
13583
+ await import_node_fs34.promises.rename(tmp, manifest);
11854
13584
  }
11855
13585
  async function applyProcfile(procfile, edits, original) {
11856
13586
  let next = original;
@@ -11859,8 +13589,8 @@ async function applyProcfile(procfile, edits, original) {
11859
13589
  next = next.replace(e.before, e.after);
11860
13590
  }
11861
13591
  const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
11862
- await import_node_fs33.promises.writeFile(tmp, next, "utf8");
11863
- await import_node_fs33.promises.rename(tmp, procfile);
13592
+ await import_node_fs34.promises.writeFile(tmp, next, "utf8");
13593
+ await import_node_fs34.promises.rename(tmp, procfile);
11864
13594
  }
11865
13595
  async function apply2(installPlan) {
11866
13596
  const { serviceDir } = installPlan;
@@ -11873,7 +13603,7 @@ async function apply2(installPlan) {
11873
13603
  const originals = /* @__PURE__ */ new Map();
11874
13604
  for (const file of touched) {
11875
13605
  try {
11876
- originals.set(file, await import_node_fs33.promises.readFile(file, "utf8"));
13606
+ originals.set(file, await import_node_fs34.promises.readFile(file, "utf8"));
11877
13607
  } catch {
11878
13608
  }
11879
13609
  }
@@ -11884,7 +13614,7 @@ async function apply2(installPlan) {
11884
13614
  if (raw === void 0) {
11885
13615
  throw new Error(`python installer: cannot read ${file} during apply`);
11886
13616
  }
11887
- const base = import_node_path52.default.basename(file);
13617
+ const base = import_node_path53.default.basename(file);
11888
13618
  if (base === "requirements.txt") {
11889
13619
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
11890
13620
  if (edits.length > 0) {
@@ -11909,7 +13639,7 @@ async function rollback2(installPlan, originals) {
11909
13639
  const restored = [];
11910
13640
  for (const [file, raw] of originals.entries()) {
11911
13641
  try {
11912
- await import_node_fs33.promises.writeFile(file, raw, "utf8");
13642
+ await import_node_fs34.promises.writeFile(file, raw, "utf8");
11913
13643
  restored.push(file);
11914
13644
  } catch {
11915
13645
  }
@@ -11923,8 +13653,8 @@ async function rollback2(installPlan, originals) {
11923
13653
  ...restored.map((f) => `restored: ${f}`),
11924
13654
  ""
11925
13655
  ];
11926
- const rollbackPath = import_node_path52.default.join(installPlan.serviceDir, "neat-rollback.patch");
11927
- await import_node_fs33.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
13656
+ const rollbackPath = import_node_path53.default.join(installPlan.serviceDir, "neat-rollback.patch");
13657
+ await import_node_fs34.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
11928
13658
  }
11929
13659
  var pythonInstaller = {
11930
13660
  name: "python",
@@ -12047,10 +13777,10 @@ function renderPatch(sections) {
12047
13777
 
12048
13778
  // src/orchestrator.ts
12049
13779
  init_cjs_shims();
12050
- var import_node_fs34 = require("fs");
13780
+ var import_node_fs35 = require("fs");
12051
13781
  var import_node_http = __toESM(require("http"), 1);
12052
13782
  var import_node_net = __toESM(require("net"), 1);
12053
- var import_node_path53 = __toESM(require("path"), 1);
13783
+ var import_node_path54 = __toESM(require("path"), 1);
12054
13784
  var import_node_child_process3 = require("child_process");
12055
13785
  var import_node_readline = __toESM(require("readline"), 1);
12056
13786
  async function extractAndPersist(opts) {
@@ -12059,7 +13789,7 @@ async function extractAndPersist(opts) {
12059
13789
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
12060
13790
  resetGraph(graphKey);
12061
13791
  const graph = getGraph(graphKey);
12062
- const projectPaths = pathsForProject(graphKey, import_node_path53.default.join(opts.scanPath, "neat-out"));
13792
+ const projectPaths = pathsForProject(graphKey, import_node_path54.default.join(opts.scanPath, "neat-out"));
12063
13793
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
12064
13794
  errorsPath: projectPaths.errorsPath
12065
13795
  });
@@ -12111,7 +13841,7 @@ async function applyInstallersOver(services, project, options = {}) {
12111
13841
  libOnly++;
12112
13842
  const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
12113
13843
  if (appDeps.length > 0) {
12114
- const svcName = import_node_path53.default.basename(svc.dir);
13844
+ const svcName = import_node_path54.default.basename(svc.dir);
12115
13845
  const list = appDeps.join(", ");
12116
13846
  console.warn(
12117
13847
  `neat: runtime layer won't engage for ${svcName}: no entry point found.
@@ -12124,7 +13854,7 @@ async function applyInstallersOver(services, project, options = {}) {
12124
13854
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
12125
13855
  } else if (outcome.outcome === "react-native") {
12126
13856
  reactNative++;
12127
- const svcName = import_node_path53.default.basename(svc.dir);
13857
+ const svcName = import_node_path54.default.basename(svc.dir);
12128
13858
  console.log(
12129
13859
  `neat: ${svc.dir} detected as React Native / Expo
12130
13860
  The installer doesn't cover this runtime deterministically.
@@ -12135,7 +13865,7 @@ async function applyInstallersOver(services, project, options = {}) {
12135
13865
  );
12136
13866
  } else if (outcome.outcome === "bun") {
12137
13867
  bun++;
12138
- const svcName = import_node_path53.default.basename(svc.dir);
13868
+ const svcName = import_node_path54.default.basename(svc.dir);
12139
13869
  console.log(
12140
13870
  `neat: ${svc.dir} detected as Bun
12141
13871
  The installer doesn't cover this runtime deterministically.
@@ -12146,7 +13876,7 @@ async function applyInstallersOver(services, project, options = {}) {
12146
13876
  );
12147
13877
  } else if (outcome.outcome === "deno") {
12148
13878
  deno++;
12149
- const svcName = import_node_path53.default.basename(svc.dir);
13879
+ const svcName = import_node_path54.default.basename(svc.dir);
12150
13880
  console.log(
12151
13881
  `neat: ${svc.dir} detected as Deno
12152
13882
  The installer doesn't cover this runtime deterministically.
@@ -12157,7 +13887,7 @@ async function applyInstallersOver(services, project, options = {}) {
12157
13887
  );
12158
13888
  } else if (outcome.outcome === "cloudflare-workers") {
12159
13889
  cloudflareWorkers++;
12160
- const svcName = import_node_path53.default.basename(svc.dir);
13890
+ const svcName = import_node_path54.default.basename(svc.dir);
12161
13891
  console.log(
12162
13892
  `neat: ${svc.dir} detected as Cloudflare Workers
12163
13893
  The installer doesn't cover this runtime deterministically.
@@ -12168,7 +13898,7 @@ async function applyInstallersOver(services, project, options = {}) {
12168
13898
  );
12169
13899
  } else if (outcome.outcome === "electron") {
12170
13900
  electron++;
12171
- const svcName = import_node_path53.default.basename(svc.dir);
13901
+ const svcName = import_node_path54.default.basename(svc.dir);
12172
13902
  console.log(
12173
13903
  `neat: ${svc.dir} detected as Electron
12174
13904
  The installer doesn't cover this runtime deterministically.
@@ -12181,7 +13911,7 @@ async function applyInstallersOver(services, project, options = {}) {
12181
13911
  if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
12182
13912
  const gaps = uninstrumentedLibraries(svc.pkg);
12183
13913
  if (gaps.length > 0) {
12184
- const svcName = import_node_path53.default.basename(svc.dir);
13914
+ const svcName = import_node_path54.default.basename(svc.dir);
12185
13915
  const list = gaps.join(", ");
12186
13916
  const subject = gaps.length === 1 ? "this library" : "these libraries";
12187
13917
  const aux = gaps.length === 1 ? "isn't" : "aren't";
@@ -12387,24 +14117,24 @@ async function persistedPortsFor(scanPath) {
12387
14117
  return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
12388
14118
  }
12389
14119
  async function acquireSpawnLock(scanPath) {
12390
- const lockPath = import_node_path53.default.join(scanPath, "neat-out", "daemon.spawn.lock");
12391
- await import_node_fs34.promises.mkdir(import_node_path53.default.dirname(lockPath), { recursive: true });
14120
+ const lockPath = import_node_path54.default.join(scanPath, "neat-out", "daemon.spawn.lock");
14121
+ await import_node_fs35.promises.mkdir(import_node_path54.default.dirname(lockPath), { recursive: true });
12392
14122
  const STALE_LOCK_MS = 6e4;
12393
14123
  try {
12394
- const fd = await import_node_fs34.promises.open(lockPath, "wx");
14124
+ const fd = await import_node_fs35.promises.open(lockPath, "wx");
12395
14125
  await fd.writeFile(`${process.pid}
12396
14126
  `, "utf8");
12397
14127
  await fd.close();
12398
14128
  return async () => {
12399
- await import_node_fs34.promises.unlink(lockPath).catch(() => {
14129
+ await import_node_fs35.promises.unlink(lockPath).catch(() => {
12400
14130
  });
12401
14131
  };
12402
14132
  } catch (err) {
12403
14133
  if (err.code !== "EEXIST") return null;
12404
14134
  try {
12405
- const stat = await import_node_fs34.promises.stat(lockPath);
14135
+ const stat = await import_node_fs35.promises.stat(lockPath);
12406
14136
  if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
12407
- await import_node_fs34.promises.unlink(lockPath).catch(() => {
14137
+ await import_node_fs35.promises.unlink(lockPath).catch(() => {
12408
14138
  });
12409
14139
  return acquireSpawnLock(scanPath);
12410
14140
  }
@@ -12433,13 +14163,13 @@ async function healthIsForProject(restPort, project) {
12433
14163
  return false;
12434
14164
  }
12435
14165
  function daemonLogPath(projectPath2) {
12436
- return import_node_path53.default.join(projectPath2, "neat-out", "daemon.log");
14166
+ return import_node_path54.default.join(projectPath2, "neat-out", "daemon.log");
12437
14167
  }
12438
14168
  function spawnDaemonDetached(spec) {
12439
- const here = import_node_path53.default.dirname(new URL(importMetaUrl).pathname);
14169
+ const here = import_node_path54.default.dirname(new URL(importMetaUrl).pathname);
12440
14170
  const candidates = [
12441
- import_node_path53.default.join(here, "neatd.cjs"),
12442
- import_node_path53.default.join(here, "neatd.js")
14171
+ import_node_path54.default.join(here, "neatd.cjs"),
14172
+ import_node_path54.default.join(here, "neatd.js")
12443
14173
  ];
12444
14174
  let entry2 = null;
12445
14175
  const fsSync = require("fs");
@@ -12469,7 +14199,7 @@ function spawnDaemonDetached(spec) {
12469
14199
  let logFd = null;
12470
14200
  if (spec) {
12471
14201
  const logPath = daemonLogPath(spec.projectPath);
12472
- fsSync.mkdirSync(import_node_path53.default.dirname(logPath), { recursive: true });
14202
+ fsSync.mkdirSync(import_node_path54.default.dirname(logPath), { recursive: true });
12473
14203
  logFd = fsSync.openSync(logPath, "a");
12474
14204
  }
12475
14205
  const child = (0, import_node_child_process3.spawn)(process.execPath, [entry2, "start"], {
@@ -12508,7 +14238,7 @@ async function runOrchestrator(opts) {
12508
14238
  browser: "skipped"
12509
14239
  }
12510
14240
  };
12511
- const stat = await import_node_fs34.promises.stat(opts.scanPath).catch(() => null);
14241
+ const stat = await import_node_fs35.promises.stat(opts.scanPath).catch(() => null);
12512
14242
  if (!stat || !stat.isDirectory()) {
12513
14243
  console.error(`neat: ${opts.scanPath} is not a directory`);
12514
14244
  result.exitCode = 2;
@@ -12668,7 +14398,7 @@ async function runOrchestrator(opts) {
12668
14398
  result.steps.browser = openBrowser(dashboardUrl);
12669
14399
  }
12670
14400
  const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
12671
- const daemonLog = daemonRunning ? import_node_path53.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
14401
+ const daemonLog = daemonRunning ? import_node_path54.default.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
12672
14402
  printSummary(result, graph, dashboardUrl, daemonLog);
12673
14403
  return result;
12674
14404
  }
@@ -12704,13 +14434,343 @@ function printSummary(result, graph, dashboardUrl, daemonLog) {
12704
14434
  }
12705
14435
  }
12706
14436
 
14437
+ // src/connector-cli.ts
14438
+ init_cjs_shims();
14439
+ var import_node_readline2 = __toESM(require("readline"), 1);
14440
+ async function defaultPrompt(question) {
14441
+ if (!process.stdin.isTTY) return "";
14442
+ const rl = import_node_readline2.default.createInterface({ input: process.stdin, output: process.stdout });
14443
+ try {
14444
+ return await new Promise((resolve) => rl.question(`${question} `, resolve));
14445
+ } finally {
14446
+ rl.close();
14447
+ }
14448
+ }
14449
+ function resolveDeps(deps) {
14450
+ return {
14451
+ home: deps.home,
14452
+ env: deps.env ?? process.env,
14453
+ ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
14454
+ prompt: deps.prompt ?? defaultPrompt,
14455
+ interactive: deps.interactive ?? Boolean(process.stdin.isTTY),
14456
+ out: deps.out ?? ((l) => console.log(l)),
14457
+ err: deps.err ?? ((l) => console.error(l))
14458
+ };
14459
+ }
14460
+ function kebabToCamel(name) {
14461
+ return name.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
14462
+ }
14463
+ function coerceFieldValue(name, raw) {
14464
+ const t = raw.trim();
14465
+ if (t.startsWith("{") || t.startsWith("[")) {
14466
+ try {
14467
+ return JSON.parse(t);
14468
+ } catch {
14469
+ return raw;
14470
+ }
14471
+ }
14472
+ if (/Ms$/.test(name) && /^\d+$/.test(t)) return Number(t);
14473
+ return raw;
14474
+ }
14475
+ function parseConnectorArgs(args) {
14476
+ const out = {
14477
+ subcommand: "",
14478
+ positional: [],
14479
+ skipValidate: false,
14480
+ plaintext: false,
14481
+ fields: {}
14482
+ };
14483
+ const positional = [];
14484
+ for (let i = 0; i < args.length; i++) {
14485
+ const arg = args[i];
14486
+ if (arg === "--skip-validate") {
14487
+ out.skipValidate = true;
14488
+ continue;
14489
+ }
14490
+ if (arg === "--plaintext") {
14491
+ out.plaintext = true;
14492
+ continue;
14493
+ }
14494
+ if (arg.startsWith("--")) {
14495
+ let flag = arg;
14496
+ let value;
14497
+ const eq = arg.indexOf("=");
14498
+ if (eq >= 0) {
14499
+ flag = arg.slice(0, eq);
14500
+ value = arg.slice(eq + 1);
14501
+ } else {
14502
+ const next = args[i + 1];
14503
+ if (next === void 0 || next.startsWith("--")) {
14504
+ return { ok: false, error: `${flag} requires a value` };
14505
+ }
14506
+ value = next;
14507
+ i++;
14508
+ }
14509
+ const name = flag.slice(2);
14510
+ if (name === "project") {
14511
+ out.project = value;
14512
+ continue;
14513
+ }
14514
+ if (name === "credential" || name === "token") {
14515
+ out.credential = value;
14516
+ continue;
14517
+ }
14518
+ if (name === "id") {
14519
+ out.id = value;
14520
+ continue;
14521
+ }
14522
+ const field = kebabToCamel(name);
14523
+ out.fields[field] = coerceFieldValue(field, value);
14524
+ continue;
14525
+ }
14526
+ positional.push(arg);
14527
+ }
14528
+ out.subcommand = positional[0] ?? "";
14529
+ out.positional = positional.slice(1);
14530
+ return { ok: true, value: out };
14531
+ }
14532
+ async function buildCredentialRef(dispatch, args, deps) {
14533
+ const hint = "an env-var reference like $PROVIDER_TOKEN is recommended (stored as a pointer, not the secret); a literal value is stored as-is";
14534
+ if (dispatch.requiredCredentialFields.length <= 1) {
14535
+ const key = dispatch.primaryCredentialKey;
14536
+ let value = args.credential;
14537
+ if (value === void 0 && typeof args.fields[key] === "string") {
14538
+ value = args.fields[key];
14539
+ }
14540
+ if (value === void 0 && deps.interactive) {
14541
+ value = await deps.prompt(`Credential for ${dispatch.provider} (${hint}):`);
14542
+ }
14543
+ return (value ?? "").trim();
14544
+ }
14545
+ const out = {};
14546
+ for (const f of dispatch.requiredCredentialFields) {
14547
+ let value;
14548
+ if (typeof args.fields[f] === "string") value = args.fields[f];
14549
+ else if (f === dispatch.primaryCredentialKey && args.credential !== void 0) value = args.credential;
14550
+ if (value === void 0 && deps.interactive) {
14551
+ value = await deps.prompt(`Credential field "${f}" for ${dispatch.provider} (${hint}):`);
14552
+ }
14553
+ out[f] = (value ?? "").trim();
14554
+ }
14555
+ return out;
14556
+ }
14557
+ function credentialComplete(dispatch, ref) {
14558
+ if (typeof ref === "string") {
14559
+ return ref.length > 0 ? [] : [dispatch.primaryCredentialKey];
14560
+ }
14561
+ return dispatch.requiredCredentialFields.filter((f) => !ref[f] || ref[f].length === 0);
14562
+ }
14563
+ function plaintextFields(ref) {
14564
+ if (typeof ref === "string") return isEnvRef(ref) ? [] : ["credential"];
14565
+ return Object.entries(ref).filter(([, v]) => !isEnvRef(v)).map(([k]) => k);
14566
+ }
14567
+ async function connectorAdd(args, deps) {
14568
+ let provider = args.positional[0];
14569
+ if (!provider && deps.interactive) {
14570
+ provider = (await deps.prompt(`Provider (${Object.keys(PROVIDER_DISPATCH).sort().join(", ")}):`)).trim();
14571
+ }
14572
+ if (!provider) {
14573
+ deps.err("neat connector add: a provider is required (e.g. `neat connector add supabase`)");
14574
+ return 2;
14575
+ }
14576
+ const dispatch = getProviderDispatch(provider);
14577
+ if (!dispatch) {
14578
+ deps.err(
14579
+ `neat connector add: unknown provider "${provider}". known providers: ${Object.keys(PROVIDER_DISPATCH).sort().join(", ")}`
14580
+ );
14581
+ return 2;
14582
+ }
14583
+ let project = args.project;
14584
+ if (project === void 0 && deps.interactive) {
14585
+ const answer = (await deps.prompt("Project (blank = the project the daemon is bootstrapping):")).trim();
14586
+ if (answer.length > 0) project = answer;
14587
+ }
14588
+ const credential = await buildCredentialRef(dispatch, args, deps);
14589
+ const options = { ...args.fields };
14590
+ for (const k of dispatch.requiredCredentialFields) delete options[k];
14591
+ for (const field of dispatch.requiredOptionFields) {
14592
+ if (field in options) continue;
14593
+ if (!deps.interactive) continue;
14594
+ const answer = (await deps.prompt(`Option "${field}" for ${provider}:`)).trim();
14595
+ if (answer.length > 0) options[field] = coerceFieldValue(field, answer);
14596
+ }
14597
+ const missingCred = credentialComplete(dispatch, credential);
14598
+ if (missingCred.length > 0) {
14599
+ deps.err(
14600
+ `neat connector add: credential is incomplete \u2014 missing ${missingCred.join(", ")}. Pass it as a flag (e.g. \`--token $PROVIDER_TOKEN\`) or run interactively.`
14601
+ );
14602
+ return 2;
14603
+ }
14604
+ const missingOpts = dispatch.requiredOptionFields.filter((f) => !(f in options));
14605
+ if (missingOpts.length > 0) {
14606
+ deps.err(
14607
+ `neat connector add: missing required option(s) for ${provider}: ${missingOpts.join(", ")}. Pass e.g. \`--${missingOpts[0].replace(/([A-Z])/g, "-$1").toLowerCase()} <value>\`.`
14608
+ );
14609
+ return 2;
14610
+ }
14611
+ const existing = await readConnectorsConfig(deps.home);
14612
+ const existingIds = new Set(existing.connectors.map((c) => c.id));
14613
+ const id = args.id ?? autoSlugConnectorId(provider, project, existingIds);
14614
+ const entry2 = {
14615
+ id,
14616
+ provider,
14617
+ ...project ? { project } : {},
14618
+ credential,
14619
+ ...Object.keys(options).length > 0 ? { options } : {}
14620
+ };
14621
+ if (!args.skipValidate) {
14622
+ const outcome = await validateConnectorEntry(entry2, deps.env, deps.fetchImpl);
14623
+ const code = reportPreWriteValidation(outcome, deps);
14624
+ if (code !== 0) return code;
14625
+ }
14626
+ const plaintext = plaintextFields(credential);
14627
+ if (plaintext.length > 0 && !args.plaintext) {
14628
+ deps.err(
14629
+ `neat connector add: storing a literal secret at rest for ${plaintext.join(", ")} \u2014 prefer an env-var reference like \`$PROVIDER_TOKEN\` (pass --plaintext to silence this).`
14630
+ );
14631
+ }
14632
+ const { replaced } = await upsertConnectorEntry(entry2, deps.home);
14633
+ const verb = replaced ? "updated" : "added";
14634
+ const where = project ? `project "${project}"` : "the bootstrapping project";
14635
+ deps.out(`${verb} connector "${id}" (${provider}) for ${where}.`);
14636
+ if (args.skipValidate) {
14637
+ deps.out("skipped validation (--skip-validate) \u2014 the credential is checked at the next daemon poll.");
14638
+ }
14639
+ deps.out("Restart the project's daemon (or start it) to begin polling this connector.");
14640
+ return 0;
14641
+ }
14642
+ function reportPreWriteValidation(outcome, deps) {
14643
+ switch (outcome.status) {
14644
+ case "ok":
14645
+ deps.out("credential validated against the provider.");
14646
+ return 0;
14647
+ case "unset-env":
14648
+ deps.err(
14649
+ `neat connector add: ${outcome.reason}. Export the variable before adding, or pass --skip-validate to add now and set it before the daemon runs.`
14650
+ );
14651
+ return 1;
14652
+ case "rejected":
14653
+ deps.err(
14654
+ `neat connector add: the provider rejected the credential \u2014 ${outcome.reason}. Nothing was written. Fix the credential, or pass --skip-validate to store it anyway.`
14655
+ );
14656
+ return 1;
14657
+ case "missing-field":
14658
+ deps.err(`neat connector add: ${outcome.reason}.`);
14659
+ return 2;
14660
+ case "unknown-provider":
14661
+ deps.err(`neat connector add: ${outcome.reason}.`);
14662
+ return 2;
14663
+ }
14664
+ }
14665
+ async function connectorList(deps, filterProject) {
14666
+ const config = await readConnectorsConfig(deps.home);
14667
+ let entries = config.connectors;
14668
+ if (filterProject) entries = entries.filter((e) => e.project === filterProject);
14669
+ if (entries.length === 0) {
14670
+ deps.out(
14671
+ filterProject ? `no connectors configured for project "${filterProject}".` : "no connectors configured. run `neat connector add <provider>` to add one."
14672
+ );
14673
+ return 0;
14674
+ }
14675
+ deps.out("id provider project credential");
14676
+ for (const e of entries) {
14677
+ const project = e.project ?? "(bootstrapping project)";
14678
+ const credential = describeCredential(e.credential, deps.env).map((c) => {
14679
+ const status2 = c.status ? ` (${c.status})` : c.kind === "plaintext" ? " (plaintext)" : "";
14680
+ return c.field ? `${c.field}=${c.display}${status2}` : `${c.display}${status2}`;
14681
+ }).join(", ");
14682
+ deps.out(`${e.id} ${e.provider} ${project} ${credential}`);
14683
+ }
14684
+ return 0;
14685
+ }
14686
+ async function connectorRemove(deps, id) {
14687
+ if (!id) {
14688
+ deps.err("neat connector remove: missing <id>. run `neat connector list` to see configured ids.");
14689
+ return 2;
14690
+ }
14691
+ const removed = await removeConnectorEntry(id, deps.home);
14692
+ if (!removed) {
14693
+ deps.err(`neat connector remove: no connector with id "${id}". run \`neat connector list\` to see configured ids.`);
14694
+ return 1;
14695
+ }
14696
+ deps.out(`removed connector "${id}" (${removed.provider}).`);
14697
+ return 0;
14698
+ }
14699
+ async function connectorTest(deps, id) {
14700
+ if (!id) {
14701
+ deps.err("neat connector test: missing <id>. run `neat connector list` to see configured ids.");
14702
+ return 2;
14703
+ }
14704
+ const config = await readConnectorsConfig(deps.home);
14705
+ const entry2 = config.connectors.find((c) => c.id === id);
14706
+ if (!entry2) {
14707
+ deps.err(`neat connector test: no connector with id "${id}". run \`neat connector list\` to see configured ids.`);
14708
+ return 1;
14709
+ }
14710
+ const outcome = await validateConnectorEntry(entry2, deps.env, deps.fetchImpl);
14711
+ switch (outcome.status) {
14712
+ case "ok":
14713
+ deps.out(`ok: "${id}" (${entry2.provider}) authenticated against the provider.`);
14714
+ return 0;
14715
+ case "unset-env":
14716
+ deps.err(`unset: "${id}" (${entry2.provider}) \u2014 ${outcome.reason}. Export the variable so the daemon can resolve it.`);
14717
+ return 1;
14718
+ case "rejected":
14719
+ deps.err(`rejected: "${id}" (${entry2.provider}) \u2014 ${outcome.reason}.`);
14720
+ return 1;
14721
+ case "missing-field":
14722
+ deps.err(`incomplete: "${id}" (${entry2.provider}) \u2014 ${outcome.reason}.`);
14723
+ return 2;
14724
+ case "unknown-provider":
14725
+ deps.err(`unknown provider: "${id}" \u2014 ${outcome.reason}.`);
14726
+ return 2;
14727
+ }
14728
+ }
14729
+ function printConnectorUsage(write) {
14730
+ write("usage: neat connector <add|list|remove|test> [args]");
14731
+ write(" add <provider> add a connector; validates the credential first (--skip-validate to skip)");
14732
+ write(" flags: --project <name> --credential/--token <$VAR|value> --id <id>");
14733
+ write(" --<option> <value> (provider-specific) --plaintext --skip-validate");
14734
+ write(" list list configured connectors (credentials shown redacted)");
14735
+ write(" flags: --project <name>");
14736
+ write(" remove <id> remove a connector by id");
14737
+ write(" test <id> re-run the credential validation for an existing connector");
14738
+ }
14739
+ async function runConnectorCommand(rawArgs, deps = {}) {
14740
+ const resolved = resolveDeps(deps);
14741
+ const parsed = parseConnectorArgs(rawArgs);
14742
+ if (!parsed.ok) {
14743
+ resolved.err(`neat connector: ${parsed.error}`);
14744
+ return 2;
14745
+ }
14746
+ const a = parsed.value;
14747
+ switch (a.subcommand) {
14748
+ case "add":
14749
+ return connectorAdd(a, resolved);
14750
+ case "list":
14751
+ return connectorList(resolved, a.project);
14752
+ case "remove":
14753
+ return connectorRemove(resolved, a.positional[0]);
14754
+ case "test":
14755
+ return connectorTest(resolved, a.positional[0]);
14756
+ case "":
14757
+ resolved.err("neat connector: missing subcommand.");
14758
+ printConnectorUsage(resolved.err);
14759
+ return 2;
14760
+ default:
14761
+ resolved.err(`neat connector: unknown subcommand "${a.subcommand}".`);
14762
+ printConnectorUsage(resolved.err);
14763
+ return 2;
14764
+ }
14765
+ }
14766
+
12707
14767
  // src/cli-verbs.ts
12708
14768
  init_cjs_shims();
12709
- var import_node_path54 = __toESM(require("path"), 1);
14769
+ var import_node_path55 = __toESM(require("path"), 1);
12710
14770
 
12711
14771
  // src/cli-client.ts
12712
14772
  init_cjs_shims();
12713
- var import_types32 = require("@neat.is/types");
14773
+ var import_types43 = require("@neat.is/types");
12714
14774
  var HttpError = class extends Error {
12715
14775
  constructor(status2, message, responseBody = "") {
12716
14776
  super(message);
@@ -12735,10 +14795,10 @@ function createHttpClient(baseUrl, bearerToken) {
12735
14795
  const root = baseUrl.replace(/\/$/, "");
12736
14796
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
12737
14797
  return {
12738
- async get(path56) {
14798
+ async get(path57) {
12739
14799
  let res;
12740
14800
  try {
12741
- res = await fetch(`${root}${path56}`, {
14801
+ res = await fetch(`${root}${path57}`, {
12742
14802
  headers: { ...authHeader }
12743
14803
  });
12744
14804
  } catch (err) {
@@ -12750,16 +14810,16 @@ function createHttpClient(baseUrl, bearerToken) {
12750
14810
  const body = await res.text().catch(() => "");
12751
14811
  throw new HttpError(
12752
14812
  res.status,
12753
- `${res.status} ${res.statusText} on GET ${path56}: ${body}`,
14813
+ `${res.status} ${res.statusText} on GET ${path57}: ${body}`,
12754
14814
  body
12755
14815
  );
12756
14816
  }
12757
14817
  return await res.json();
12758
14818
  },
12759
- async post(path56, body) {
14819
+ async post(path57, body) {
12760
14820
  let res;
12761
14821
  try {
12762
- res = await fetch(`${root}${path56}`, {
14822
+ res = await fetch(`${root}${path57}`, {
12763
14823
  method: "POST",
12764
14824
  headers: { "content-type": "application/json", ...authHeader },
12765
14825
  body: JSON.stringify(body)
@@ -12773,7 +14833,7 @@ function createHttpClient(baseUrl, bearerToken) {
12773
14833
  const text = await res.text().catch(() => "");
12774
14834
  throw new HttpError(
12775
14835
  res.status,
12776
- `${res.status} ${res.statusText} on POST ${path56}: ${text}`,
14836
+ `${res.status} ${res.statusText} on POST ${path57}: ${text}`,
12777
14837
  text
12778
14838
  );
12779
14839
  }
@@ -12787,12 +14847,12 @@ function projectPath(project, suffix) {
12787
14847
  }
12788
14848
  async function runRootCause(client, input) {
12789
14849
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
12790
- const path56 = projectPath(
14850
+ const path57 = projectPath(
12791
14851
  input.project,
12792
14852
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
12793
14853
  );
12794
14854
  try {
12795
- const result = await client.get(path56);
14855
+ const result = await client.get(path57);
12796
14856
  const arrowPath = result.traversalPath.join(" \u2190 ");
12797
14857
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
12798
14858
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -12818,12 +14878,12 @@ async function runRootCause(client, input) {
12818
14878
  }
12819
14879
  async function runBlastRadius(client, input) {
12820
14880
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
12821
- const path56 = projectPath(
14881
+ const path57 = projectPath(
12822
14882
  input.project,
12823
14883
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
12824
14884
  );
12825
14885
  try {
12826
- const result = await client.get(path56);
14886
+ const result = await client.get(path57);
12827
14887
  if (result.totalAffected === 0) {
12828
14888
  return {
12829
14889
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -12852,17 +14912,17 @@ async function runBlastRadius(client, input) {
12852
14912
  }
12853
14913
  }
12854
14914
  function formatBlastEntry(n) {
12855
- const tag = n.edgeProvenance === import_types32.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
14915
+ const tag = n.edgeProvenance === import_types43.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
12856
14916
  return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
12857
14917
  }
12858
14918
  async function runDependencies(client, input) {
12859
14919
  const depth = input.depth ?? 3;
12860
- const path56 = projectPath(
14920
+ const path57 = projectPath(
12861
14921
  input.project,
12862
14922
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
12863
14923
  );
12864
14924
  try {
12865
- const result = await client.get(path56);
14925
+ const result = await client.get(path57);
12866
14926
  if (result.total === 0) {
12867
14927
  return {
12868
14928
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -12909,7 +14969,7 @@ async function runObservedDependencies(client, input) {
12909
14969
  if (result.observed) {
12910
14970
  return {
12911
14971
  summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
12912
- provenance: import_types32.Provenance.OBSERVED
14972
+ provenance: import_types43.Provenance.OBSERVED
12913
14973
  };
12914
14974
  }
12915
14975
  const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
@@ -12919,7 +14979,7 @@ async function runObservedDependencies(client, input) {
12919
14979
  return {
12920
14980
  summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
12921
14981
  block: blockLines.join("\n"),
12922
- provenance: import_types32.Provenance.OBSERVED
14982
+ provenance: import_types43.Provenance.OBSERVED
12923
14983
  };
12924
14984
  } catch (err) {
12925
14985
  if (err instanceof HttpError && err.status === 404) {
@@ -12954,9 +15014,9 @@ function formatDuration(ms) {
12954
15014
  return `${Math.round(h / 24)}d`;
12955
15015
  }
12956
15016
  async function runIncidents(client, input) {
12957
- const path56 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
15017
+ const path57 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
12958
15018
  try {
12959
- const body = await client.get(path56);
15019
+ const body = await client.get(path57);
12960
15020
  const events = body.events;
12961
15021
  if (events.length === 0) {
12962
15022
  return {
@@ -12973,7 +15033,7 @@ async function runIncidents(client, input) {
12973
15033
  return {
12974
15034
  summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
12975
15035
  block: blockLines.join("\n"),
12976
- provenance: import_types32.Provenance.OBSERVED
15036
+ provenance: import_types43.Provenance.OBSERVED
12977
15037
  };
12978
15038
  } catch (err) {
12979
15039
  if (err instanceof HttpError && err.status === 404) {
@@ -13082,7 +15142,7 @@ async function runStaleEdges(client, input) {
13082
15142
  return {
13083
15143
  summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
13084
15144
  block: blockLines.join("\n"),
13085
- provenance: import_types32.Provenance.STALE
15145
+ provenance: import_types43.Provenance.STALE
13086
15146
  };
13087
15147
  }
13088
15148
  async function runPolicies(client, input) {
@@ -13246,7 +15306,7 @@ async function resolveProjectEntry(opts) {
13246
15306
  const cwd = opts.cwd ?? process.cwd();
13247
15307
  const resolvedCwd = await normalizeProjectPath(cwd);
13248
15308
  for (const entry2 of entries) {
13249
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path54.default.sep}`)) {
15309
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path55.default.sep}`)) {
13250
15310
  return entry2;
13251
15311
  }
13252
15312
  }
@@ -13399,7 +15459,7 @@ async function runSync(opts) {
13399
15459
  }
13400
15460
 
13401
15461
  // src/cli.ts
13402
- var import_types33 = require("@neat.is/types");
15462
+ var import_types44 = require("@neat.is/types");
13403
15463
  function isNpxInvocation() {
13404
15464
  if (process.env.npm_command === "exec") return true;
13405
15465
  const execpath = process.env.npm_execpath ?? "";
@@ -13460,6 +15520,17 @@ function usage() {
13460
15520
  console.log(" --dry-run run extraction in-memory; do not write");
13461
15521
  console.log(" --no-instrument skip the SDK install apply step");
13462
15522
  console.log(" --json emit the delta summary as JSON");
15523
+ console.log(" connector Configure pull-based OBSERVED connectors (supabase, railway,");
15524
+ console.log(" firebase, cloudflare). Subcommands:");
15525
+ console.log(" add <provider> add a connector; validates the credential");
15526
+ console.log(" against the provider first (--skip-validate to skip)");
15527
+ console.log(" flags: --project <name>, --credential/--token <$VAR|value>,");
15528
+ console.log(" --id <id>, --<option> <value>, --plaintext, --skip-validate");
15529
+ console.log(" list list configured connectors (credentials redacted)");
15530
+ console.log(" remove <id> remove a connector by id");
15531
+ console.log(" test <id> re-check an existing connector's credential");
15532
+ console.log(" Credentials default to an env-var reference ($VAR) resolved at");
15533
+ console.log(" run time; the config file is written owner-only (0600).");
13463
15534
  console.log("");
13464
15535
  console.log("query commands (mirror the MCP tools, ADR-050):");
13465
15536
  console.log(" root-cause <node-id> Walk inbound edges to find what broke first.");
@@ -13681,7 +15752,7 @@ async function buildPatchSections(services, project) {
13681
15752
  }
13682
15753
  async function runInit(opts) {
13683
15754
  const written = [];
13684
- const stat = await import_node_fs35.promises.stat(opts.scanPath).catch(() => null);
15755
+ const stat = await import_node_fs36.promises.stat(opts.scanPath).catch(() => null);
13685
15756
  if (!stat || !stat.isDirectory()) {
13686
15757
  console.error(`neat init: ${opts.scanPath} is not a directory`);
13687
15758
  return { exitCode: 2, writtenFiles: written };
@@ -13690,13 +15761,13 @@ async function runInit(opts) {
13690
15761
  printDiscoveryReport(opts, services);
13691
15762
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
13692
15763
  const patch = renderPatch(sections);
13693
- const patchPath = import_node_path55.default.join(opts.scanPath, "neat.patch");
15764
+ const patchPath = import_node_path56.default.join(opts.scanPath, "neat.patch");
13694
15765
  if (opts.dryRun) {
13695
- await import_node_fs35.promises.writeFile(patchPath, patch, "utf8");
15766
+ await import_node_fs36.promises.writeFile(patchPath, patch, "utf8");
13696
15767
  written.push(patchPath);
13697
15768
  console.log(`dry-run: patch written to ${patchPath}`);
13698
- const gitignorePath = import_node_path55.default.join(opts.scanPath, ".gitignore");
13699
- const gitignoreExists = await import_node_fs35.promises.stat(gitignorePath).then(() => true).catch(() => false);
15769
+ const gitignorePath = import_node_path56.default.join(opts.scanPath, ".gitignore");
15770
+ const gitignoreExists = await import_node_fs36.promises.stat(gitignorePath).then(() => true).catch(() => false);
13700
15771
  const verb = gitignoreExists ? "append" : "create";
13701
15772
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
13702
15773
  console.log("rerun without --dry-run to register and snapshot.");
@@ -13707,9 +15778,9 @@ async function runInit(opts) {
13707
15778
  const graph = getGraph(graphKey);
13708
15779
  const projectPaths = pathsForProject(
13709
15780
  graphKey,
13710
- import_node_path55.default.join(opts.scanPath, "neat-out")
15781
+ import_node_path56.default.join(opts.scanPath, "neat-out")
13711
15782
  );
13712
- const errorsPath = import_node_path55.default.join(import_node_path55.default.dirname(opts.outPath), import_node_path55.default.basename(projectPaths.errorsPath));
15783
+ const errorsPath = import_node_path56.default.join(import_node_path56.default.dirname(opts.outPath), import_node_path56.default.basename(projectPaths.errorsPath));
13713
15784
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
13714
15785
  await saveGraphToDisk(graph, opts.outPath);
13715
15786
  written.push(opts.outPath);
@@ -13788,7 +15859,7 @@ async function runInit(opts) {
13788
15859
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
13789
15860
  }
13790
15861
  } else {
13791
- await import_node_fs35.promises.writeFile(patchPath, patch, "utf8");
15862
+ await import_node_fs36.promises.writeFile(patchPath, patch, "utf8");
13792
15863
  written.push(patchPath);
13793
15864
  }
13794
15865
  }
@@ -13828,9 +15899,9 @@ var CLAUDE_SKILL_CONFIG = {
13828
15899
  };
13829
15900
  function claudeConfigPath() {
13830
15901
  const override = process.env.NEAT_CLAUDE_CONFIG;
13831
- if (override && override.length > 0) return import_node_path55.default.resolve(override);
15902
+ if (override && override.length > 0) return import_node_path56.default.resolve(override);
13832
15903
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
13833
- return import_node_path55.default.join(home, ".claude.json");
15904
+ return import_node_path56.default.join(home, ".claude.json");
13834
15905
  }
13835
15906
  async function runSkill(opts) {
13836
15907
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -13842,7 +15913,7 @@ async function runSkill(opts) {
13842
15913
  const target = claudeConfigPath();
13843
15914
  let existing = {};
13844
15915
  try {
13845
- existing = JSON.parse(await import_node_fs35.promises.readFile(target, "utf8"));
15916
+ existing = JSON.parse(await import_node_fs36.promises.readFile(target, "utf8"));
13846
15917
  } catch (err) {
13847
15918
  if (err.code !== "ENOENT") {
13848
15919
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -13854,8 +15925,8 @@ async function runSkill(opts) {
13854
15925
  ...existing,
13855
15926
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
13856
15927
  };
13857
- await import_node_fs35.promises.mkdir(import_node_path55.default.dirname(target), { recursive: true });
13858
- await import_node_fs35.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
15928
+ await import_node_fs36.promises.mkdir(import_node_path56.default.dirname(target), { recursive: true });
15929
+ await import_node_fs36.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
13859
15930
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
13860
15931
  console.log("restart Claude Code to pick up the new MCP server.");
13861
15932
  return { exitCode: 0 };
@@ -13883,6 +15954,11 @@ async function main() {
13883
15954
  printVersion();
13884
15955
  process.exit(0);
13885
15956
  }
15957
+ if (cmd0 === "connector") {
15958
+ const code = await runConnectorCommand(argv.slice(1));
15959
+ if (code !== 0) process.exit(code);
15960
+ return;
15961
+ }
13886
15962
  const argvParsed = parseArgs(argv);
13887
15963
  if (argvParsed.positional.length === 0) {
13888
15964
  const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
@@ -13904,12 +15980,12 @@ async function main() {
13904
15980
  console.error("neat init: --apply and --dry-run are mutually exclusive");
13905
15981
  process.exit(2);
13906
15982
  }
13907
- const scanPath = import_node_path55.default.resolve(target);
15983
+ const scanPath = import_node_path56.default.resolve(target);
13908
15984
  const projectExplicit = parsed.project !== null;
13909
- const projectName = projectExplicit ? project : import_node_path55.default.basename(scanPath);
15985
+ const projectName = projectExplicit ? project : import_node_path56.default.basename(scanPath);
13910
15986
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
13911
- const fallback = pathsForProject(projectKey, import_node_path55.default.join(scanPath, "neat-out")).snapshotPath;
13912
- const outPath = import_node_path55.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
15987
+ const fallback = pathsForProject(projectKey, import_node_path56.default.join(scanPath, "neat-out")).snapshotPath;
15988
+ const outPath = import_node_path56.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
13913
15989
  const result = await runInit({
13914
15990
  scanPath,
13915
15991
  outPath,
@@ -13930,21 +16006,21 @@ async function main() {
13930
16006
  usage();
13931
16007
  process.exit(2);
13932
16008
  }
13933
- const scanPath = import_node_path55.default.resolve(target);
13934
- const stat = await import_node_fs35.promises.stat(scanPath).catch(() => null);
16009
+ const scanPath = import_node_path56.default.resolve(target);
16010
+ const stat = await import_node_fs36.promises.stat(scanPath).catch(() => null);
13935
16011
  if (!stat || !stat.isDirectory()) {
13936
16012
  console.error(`neat watch: ${scanPath} is not a directory`);
13937
16013
  process.exit(2);
13938
16014
  }
13939
- const projectPaths = pathsForProject(project, import_node_path55.default.join(scanPath, "neat-out"));
13940
- const outPath = import_node_path55.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
13941
- const errorsPath = import_node_path55.default.resolve(
13942
- process.env.NEAT_ERRORS_PATH ?? import_node_path55.default.join(import_node_path55.default.dirname(outPath), import_node_path55.default.basename(projectPaths.errorsPath))
16015
+ const projectPaths = pathsForProject(project, import_node_path56.default.join(scanPath, "neat-out"));
16016
+ const outPath = import_node_path56.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
16017
+ const errorsPath = import_node_path56.default.resolve(
16018
+ process.env.NEAT_ERRORS_PATH ?? import_node_path56.default.join(import_node_path56.default.dirname(outPath), import_node_path56.default.basename(projectPaths.errorsPath))
13943
16019
  );
13944
- const staleEventsPath = import_node_path55.default.resolve(
13945
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path55.default.join(import_node_path55.default.dirname(outPath), import_node_path55.default.basename(projectPaths.staleEventsPath))
16020
+ const staleEventsPath = import_node_path56.default.resolve(
16021
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path56.default.join(import_node_path56.default.dirname(outPath), import_node_path56.default.basename(projectPaths.staleEventsPath))
13946
16022
  );
13947
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path55.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
16023
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path56.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
13948
16024
  const handle = await startWatch(getGraph(project), {
13949
16025
  scanPath,
13950
16026
  outPath,
@@ -14127,11 +16203,11 @@ async function main() {
14127
16203
  process.exit(1);
14128
16204
  }
14129
16205
  async function tryOrchestrator(cmd, parsed) {
14130
- const scanPath = import_node_path55.default.resolve(cmd);
14131
- const stat = await import_node_fs35.promises.stat(scanPath).catch(() => null);
16206
+ const scanPath = import_node_path56.default.resolve(cmd);
16207
+ const stat = await import_node_fs36.promises.stat(scanPath).catch(() => null);
14132
16208
  if (!stat || !stat.isDirectory()) return null;
14133
16209
  const projectExplicit = parsed.project !== null;
14134
- const projectName = projectExplicit ? parsed.project : import_node_path55.default.basename(scanPath);
16210
+ const projectName = projectExplicit ? parsed.project : import_node_path56.default.basename(scanPath);
14135
16211
  const result = await runOrchestrator({
14136
16212
  scanPath,
14137
16213
  project: projectName,
@@ -14320,10 +16396,10 @@ async function runQueryVerb(cmd, parsed) {
14320
16396
  const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
14321
16397
  const out = [];
14322
16398
  for (const p of parts) {
14323
- const r = import_types33.DivergenceTypeSchema.safeParse(p);
16399
+ const r = import_types44.DivergenceTypeSchema.safeParse(p);
14324
16400
  if (!r.success) {
14325
16401
  console.error(
14326
- `neat divergences: unknown --type "${p}". allowed: ${import_types33.DivergenceTypeSchema.options.join(", ")}`
16402
+ `neat divergences: unknown --type "${p}". allowed: ${import_types44.DivergenceTypeSchema.options.join(", ")}`
14327
16403
  );
14328
16404
  return 2;
14329
16405
  }