@neat.is/core 0.10.1-dev.20260926 → 0.10.1-dev.20260928

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
61
61
  ]);
62
62
  const publicRead = opts.publicRead === true;
63
63
  app.addHook("preHandler", (req, reply, done) => {
64
- const path98 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path98) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path98)) {
64
+ const path99 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path99) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path99)) {
66
66
  done();
67
67
  return;
68
68
  }
@@ -415,8 +415,8 @@ function websocketChannelPathOf(attrs) {
415
415
  const v = attrs[key];
416
416
  if (typeof v === "string" && v.length > 0) {
417
417
  const q = v.indexOf("?");
418
- const path98 = q === -1 ? v : v.slice(0, q);
419
- if (path98.length > 0) return path98;
418
+ const path99 = q === -1 ? v : v.slice(0, q);
419
+ if (path99.length > 0) return path99;
420
420
  }
421
421
  }
422
422
  return void 0;
@@ -805,9 +805,9 @@ __export(cli_exports, {
805
805
  });
806
806
  module.exports = __toCommonJS(cli_exports);
807
807
  init_cjs_shims();
808
- var import_node_path98 = __toESM(require("path"), 1);
808
+ var import_node_path99 = __toESM(require("path"), 1);
809
809
  var import_node_os11 = __toESM(require("os"), 1);
810
- var import_node_fs59 = require("fs");
810
+ var import_node_fs60 = require("fs");
811
811
 
812
812
  // src/banner.ts
813
813
  init_cjs_shims();
@@ -1490,19 +1490,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1490
1490
  function longestIncomingWalk(graph, start, maxDepth) {
1491
1491
  let best = { path: [start], edges: [] };
1492
1492
  const visited = /* @__PURE__ */ new Set([start]);
1493
- function step(node, path98, edges) {
1494
- if (path98.length > best.path.length) {
1495
- best = { path: [...path98], edges: [...edges] };
1493
+ function step(node, path99, edges) {
1494
+ if (path99.length > best.path.length) {
1495
+ best = { path: [...path99], edges: [...edges] };
1496
1496
  }
1497
- if (path98.length - 1 >= maxDepth) return;
1497
+ if (path99.length - 1 >= maxDepth) return;
1498
1498
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1499
1499
  for (const [srcId, edge] of incoming) {
1500
1500
  if (visited.has(srcId)) continue;
1501
1501
  visited.add(srcId);
1502
- path98.push(srcId);
1502
+ path99.push(srcId);
1503
1503
  edges.push(edge);
1504
- step(srcId, path98, edges);
1505
- path98.pop();
1504
+ step(srcId, path99, edges);
1505
+ path99.pop();
1506
1506
  edges.pop();
1507
1507
  visited.delete(srcId);
1508
1508
  }
@@ -1717,20 +1717,20 @@ function dominantFailingCall(graph, serviceId17, visited) {
1717
1717
  return best;
1718
1718
  }
1719
1719
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1720
- const path98 = [originServiceId];
1720
+ const path99 = [originServiceId];
1721
1721
  const edges = [];
1722
1722
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1723
1723
  let current = originServiceId;
1724
1724
  for (let depth = 0; depth < maxDepth; depth++) {
1725
1725
  const hop = dominantFailingCall(graph, current, visited);
1726
1726
  if (!hop) break;
1727
- path98.push(hop.nextService);
1727
+ path99.push(hop.nextService);
1728
1728
  edges.push(hop.edge);
1729
1729
  visited.add(hop.nextService);
1730
1730
  current = hop.nextService;
1731
1731
  }
1732
1732
  if (edges.length === 0) return null;
1733
- return { path: path98, edges, culprit: current };
1733
+ return { path: path99, edges, culprit: current };
1734
1734
  }
1735
1735
  function isStaleCallEdge(e) {
1736
1736
  return e.type === import_types2.EdgeType.CALLS && e.provenance === import_types2.Provenance.STALE;
@@ -1766,26 +1766,26 @@ function dominantStaleCall(graph, serviceId17, visited) {
1766
1766
  return best;
1767
1767
  }
1768
1768
  function followStaleCallChain(graph, originServiceId, maxDepth) {
1769
- const path98 = [originServiceId];
1769
+ const path99 = [originServiceId];
1770
1770
  const edges = [];
1771
1771
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1772
1772
  let current = originServiceId;
1773
1773
  for (let depth = 0; depth < maxDepth; depth++) {
1774
1774
  const hop = dominantStaleCall(graph, current, visited);
1775
1775
  if (!hop) break;
1776
- path98.push(hop.nextService);
1776
+ path99.push(hop.nextService);
1777
1777
  edges.push(hop.edge);
1778
1778
  visited.add(hop.nextService);
1779
1779
  current = hop.nextService;
1780
1780
  }
1781
1781
  if (edges.length === 0) return null;
1782
- return { path: path98, edges, culprit: current };
1782
+ return { path: path99, edges, culprit: current };
1783
1783
  }
1784
1784
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1785
1785
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1786
1786
  if (!chain) return null;
1787
1787
  const culprit = chain.culprit;
1788
- const path98 = [...chain.path];
1788
+ const path99 = [...chain.path];
1789
1789
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1790
1790
  const baseConfidence = confidenceFromMix(chain.edges);
1791
1791
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1793,14 +1793,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1793
1793
  if (loc) {
1794
1794
  let rootCauseNode = culprit;
1795
1795
  if (loc.fileNode) {
1796
- path98.push(loc.fileNode);
1796
+ path99.push(loc.fileNode);
1797
1797
  edgeProvenances.push(import_types2.Provenance.OBSERVED);
1798
1798
  rootCauseNode = loc.fileNode;
1799
1799
  }
1800
1800
  return import_types2.RootCauseResultSchema.parse({
1801
1801
  rootCauseNode,
1802
1802
  rootCauseReason: loc.rootCauseReason,
1803
- traversalPath: path98,
1803
+ traversalPath: path99,
1804
1804
  edgeProvenances,
1805
1805
  confidence,
1806
1806
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1812,7 +1812,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1812
1812
  return import_types2.RootCauseResultSchema.parse({
1813
1813
  rootCauseNode: culprit,
1814
1814
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1815
- traversalPath: path98,
1815
+ traversalPath: path99,
1816
1816
  edgeProvenances,
1817
1817
  confidence,
1818
1818
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2465,10 +2465,10 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
2465
2465
  traversalPath = staleChain.path;
2466
2466
  edgeProvenances = staleChain.edges.map((e) => e.provenance);
2467
2467
  } else if (top.node !== seedNode) {
2468
- const path98 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
2469
- if (path98) {
2470
- traversalPath = path98.nodes;
2471
- edgeProvenances = path98.edges.map((e) => e.provenance);
2468
+ const path99 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
2469
+ if (path99) {
2470
+ traversalPath = path99.nodes;
2471
+ edgeProvenances = path99.edges.map((e) => e.provenance);
2472
2472
  } else {
2473
2473
  traversalPath = [errorNodeId, top.node];
2474
2474
  edgeProvenances = [top.provenance ?? import_types2.Provenance.OBSERVED];
@@ -3919,8 +3919,8 @@ function chiRoutesFromSource(source, parser) {
3919
3919
  chiWalk(tree.rootNode, "", out);
3920
3920
  return out;
3921
3921
  }
3922
- function stripChiRegex(path98) {
3923
- return path98.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3922
+ function stripChiRegex(path99) {
3923
+ return path99.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3924
3924
  }
3925
3925
  function chiWalk(node, prefix, out) {
3926
3926
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -4592,9 +4592,9 @@ function rubyRocketRoute(args) {
4592
4592
  if (!pair || pair.type !== "pair") continue;
4593
4593
  const k = pair.childForFieldName("key");
4594
4594
  if (k?.type !== "string") continue;
4595
- const path98 = rubyLiteral(k);
4596
- if (path98 === null) continue;
4597
- return { path: path98, target: rubyLiteral(pair.childForFieldName("value")) };
4595
+ const path99 = rubyLiteral(k);
4596
+ if (path99 === null) continue;
4597
+ return { path: path99, target: rubyLiteral(pair.childForFieldName("value")) };
4598
4598
  }
4599
4599
  return null;
4600
4600
  }
@@ -12002,13 +12002,16 @@ function objectKeys(obj) {
12002
12002
  var MODULAR_WRITES = /* @__PURE__ */ new Set(["addDoc", "setDoc", "updateDoc"]);
12003
12003
  var NAMESPACED_WRITES = /* @__PURE__ */ new Set(["add", "set", "update"]);
12004
12004
  var READ_CLAUSES = /* @__PURE__ */ new Set(["where", "orderBy"]);
12005
- function firestoreEndpointsFromFile(file, serviceDir) {
12005
+ function firestoreEndpointsFromFile(file, serviceDir, imported) {
12006
12006
  const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(file.content);
12007
12007
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
12008
- if (!hasClient && !hasAdmin) return [];
12009
- const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
12008
+ const importedNames = imported?.names ?? /* @__PURE__ */ new Set();
12009
+ if (!hasClient && !hasAdmin && importedNames.size === 0) return [];
12010
+ const ownSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
12011
+ const fileSdk = !hasClient && !hasAdmin ? imported?.sdk ?? null : ownSdk;
12010
12012
  const tree = parseSource3(parserForExt2(import_node_path45.default.extname(file.path)), file.content);
12011
12013
  const clientVars = firestoreClientVars(tree.rootNode);
12014
+ for (const n of importedNames) clientVars.add(n);
12012
12015
  const collLine = /* @__PURE__ */ new Map();
12013
12016
  const writes = /* @__PURE__ */ new Map();
12014
12017
  const reads = /* @__PURE__ */ new Map();
@@ -12119,6 +12122,99 @@ function firestoreEndpointsFromFile(file, serviceDir) {
12119
12122
  }
12120
12123
  return out;
12121
12124
  }
12125
+ function exportedFirestoreClients(root) {
12126
+ const out = /* @__PURE__ */ new Set();
12127
+ const collect = (node) => {
12128
+ if (node.type === "variable_declarator") {
12129
+ const name = node.childForFieldName("name");
12130
+ let value = node.childForFieldName("value");
12131
+ if (value?.type === "await_expression") value = namedChildren(value)[0] ?? null;
12132
+ if (name?.type === "identifier" && isFirestoreClientFactory(value)) out.add(name.text);
12133
+ }
12134
+ for (const c of namedChildren(node)) collect(c);
12135
+ };
12136
+ const walk11 = (node) => {
12137
+ if (node.type === "export_statement") collect(node);
12138
+ else for (const c of namedChildren(node)) walk11(c);
12139
+ };
12140
+ walk11(root);
12141
+ return out;
12142
+ }
12143
+ function namedImportBindings(root) {
12144
+ const out = [];
12145
+ const walk11 = (node) => {
12146
+ if (node.type === "import_statement") {
12147
+ const src = node.childForFieldName("source");
12148
+ const specifier = src ? src.text.slice(1, -1) : "";
12149
+ if (specifier.length > 0) {
12150
+ const stack = [node];
12151
+ while (stack.length > 0) {
12152
+ const n = stack.pop();
12153
+ if (n.type === "import_specifier") {
12154
+ const nameNode = n.childForFieldName("name");
12155
+ const aliasNode = n.childForFieldName("alias");
12156
+ const importedName = nameNode?.text;
12157
+ if (importedName) {
12158
+ out.push({ local: aliasNode?.text ?? importedName, imported: importedName, specifier });
12159
+ }
12160
+ continue;
12161
+ }
12162
+ for (const c of namedChildren(n)) stack.push(c);
12163
+ }
12164
+ }
12165
+ }
12166
+ for (const c of namedChildren(node)) walk11(c);
12167
+ };
12168
+ walk11(root);
12169
+ return out;
12170
+ }
12171
+ function sdkOf(content) {
12172
+ const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(content);
12173
+ const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(content);
12174
+ return hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
12175
+ }
12176
+ function parseOrNull(file) {
12177
+ const ext = import_node_path45.default.extname(file.path);
12178
+ if (!(ext in GRAMMAR_BY_EXT)) return null;
12179
+ try {
12180
+ return parseSource3(parserForExt2(ext), file.content).rootNode;
12181
+ } catch {
12182
+ return null;
12183
+ }
12184
+ }
12185
+ async function firestoreCrossFileEndpoints(files, serviceDir) {
12186
+ const registry = /* @__PURE__ */ new Map();
12187
+ for (const f of files) {
12188
+ if (sdkOf(f.content) === null && !FIRESTORE_CLIENT_IMPORT_RE.test(f.content)) continue;
12189
+ const root = parseOrNull(f);
12190
+ if (!root) continue;
12191
+ const names = exportedFirestoreClients(root);
12192
+ if (names.size === 0) continue;
12193
+ registry.set(toPosix(import_node_path45.default.relative(serviceDir, f.path)), { names, sdk: sdkOf(f.content) });
12194
+ }
12195
+ if (registry.size === 0) return [];
12196
+ const out = [];
12197
+ for (const f of files) {
12198
+ const root = parseOrNull(f);
12199
+ if (!root) continue;
12200
+ if (firestoreClientVars(root).size > 0) continue;
12201
+ const bindings = namedImportBindings(root);
12202
+ if (bindings.length === 0) continue;
12203
+ const names = /* @__PURE__ */ new Set();
12204
+ let sdk = null;
12205
+ for (const b of bindings) {
12206
+ const rel = await resolveJsImport(b.specifier, import_node_path45.default.dirname(f.path), serviceDir, null);
12207
+ if (!rel) continue;
12208
+ const entry2 = registry.get(rel);
12209
+ if (!entry2 || !entry2.names.has(b.imported)) continue;
12210
+ names.add(b.local);
12211
+ sdk ??= entry2.sdk;
12212
+ }
12213
+ if (names.size === 0) continue;
12214
+ out.push(...firestoreEndpointsFromFile(f, serviceDir, { names, sdk }));
12215
+ }
12216
+ return out;
12217
+ }
12122
12218
 
12123
12219
  // src/extract/calls/mongoose.ts
12124
12220
  init_cjs_shims();
@@ -14928,6 +15024,7 @@ async function addExternalEndpointEdges(graph, services) {
14928
15024
  recordExtractionError("python socket call extraction", file.path, err);
14929
15025
  }
14930
15026
  }
15027
+ endpoints.push(...await firestoreCrossFileEndpoints(maskedFiles, service.dir));
14931
15028
  endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
14932
15029
  endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
14933
15030
  endpoints.push(...await prismaColumnEndpoints(service.dir));
@@ -16223,6 +16320,9 @@ function stripComments(src) {
16223
16320
  function segmentsFromMatchPath(pathToken) {
16224
16321
  return pathToken.split("/").filter((s) => s.length > 0);
16225
16322
  }
16323
+ function isRecursiveWildcard(seg) {
16324
+ return /^\{[^}]*=\s*\*\*\}$/.test(seg);
16325
+ }
16226
16326
  function collectionKeyFromSegments(segments) {
16227
16327
  let segs = segments;
16228
16328
  if (segs.length >= 3 && segs[0] === "databases" && /^\{.*\}$/.test(segs[1]) && segs[2] === "documents") {
@@ -16235,10 +16335,11 @@ function collectionKeyFromName(name) {
16235
16335
  }
16236
16336
  function fieldsFromCondition(cond) {
16237
16337
  const out = [];
16238
- const callRe = /\b(?:hasAll|hasOnly|hasAny)\s*\(\s*\[([^\]]*)\]\s*\)/g;
16338
+ const callRe = /(!\s*)?\b(?:hasAll|hasOnly|hasAny)\s*\(\s*\[([^\]]*)\]\s*\)/g;
16239
16339
  let m;
16240
16340
  while ((m = callRe.exec(cond)) !== null) {
16241
- const inner = m[1];
16341
+ if (m[1] !== void 0 || isNegatedAt(cond, m.index)) continue;
16342
+ const inner = m[2];
16242
16343
  const litRe = /'([^']*)'|"([^"]*)"/g;
16243
16344
  let lm;
16244
16345
  while ((lm = litRe.exec(inner)) !== null) {
@@ -16248,6 +16349,28 @@ function fieldsFromCondition(cond) {
16248
16349
  }
16249
16350
  return out;
16250
16351
  }
16352
+ function isNegatedAt(cond, idx) {
16353
+ let i = idx - 1;
16354
+ while (i >= 0) {
16355
+ const ch = cond[i];
16356
+ if (ch === ")") {
16357
+ let depth = 1;
16358
+ i--;
16359
+ while (i >= 0 && depth > 0) {
16360
+ if (cond[i] === ")") depth++;
16361
+ else if (cond[i] === "(") depth--;
16362
+ i--;
16363
+ }
16364
+ continue;
16365
+ }
16366
+ if (/[A-Za-z0-9_$.\]['"]/.test(ch) || ch === " ") {
16367
+ i--;
16368
+ continue;
16369
+ }
16370
+ return ch === "!";
16371
+ }
16372
+ return false;
16373
+ }
16251
16374
  function parseFirestoreRules(content) {
16252
16375
  const src = stripComments(content);
16253
16376
  const tokenRe = /match\s+((?:\/(?:\{[^}]*\}|[^\s/{}]+))+)\s*\{|allow\b([^;{}]*);|(\{)|(\})/g;
@@ -16260,7 +16383,8 @@ function parseFirestoreRules(content) {
16260
16383
  while ((m = tokenRe.exec(src)) !== null) {
16261
16384
  if (m[1] !== void 0) {
16262
16385
  depth++;
16263
- matchStack.push({ depth, segs: segmentsFromMatchPath(m[1]) });
16386
+ const segs = segmentsFromMatchPath(m[1]);
16387
+ matchStack.push({ depth, segs, recursive: segs.some(isRecursiveWildcard) });
16264
16388
  continue;
16265
16389
  }
16266
16390
  if (m[2] !== void 0) {
@@ -16271,6 +16395,10 @@ function parseFirestoreRules(content) {
16271
16395
  if (!methods.some((mm) => WRITE_METHODS.has(mm))) continue;
16272
16396
  const key = currentKey();
16273
16397
  if (key.length === 0) continue;
16398
+ if (matchStack.some((f) => f.recursive)) {
16399
+ indeterminate.add(key);
16400
+ continue;
16401
+ }
16274
16402
  const cond = colon === -1 ? "" : body.slice(colon + 1);
16275
16403
  const fields = fieldsFromCondition(cond);
16276
16404
  if (fields.length > 0) {
@@ -20325,10 +20453,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
20325
20453
  // src/connectors/supabase/map.ts
20326
20454
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
20327
20455
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
20328
- function targetFromRestPath(path98) {
20329
- const rpcMatch = REST_RPC_PATH_RE.exec(path98);
20456
+ function targetFromRestPath(path99) {
20457
+ const rpcMatch = REST_RPC_PATH_RE.exec(path99);
20330
20458
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
20331
- const tableMatch = REST_TABLE_PATH_RE.exec(path98);
20459
+ const tableMatch = REST_TABLE_PATH_RE.exec(path99);
20332
20460
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
20333
20461
  return null;
20334
20462
  }
@@ -20932,9 +21060,9 @@ function parseFirebaseTargetName(targetName) {
20932
21060
  const secondSep = rest.indexOf(FIELD_SEP);
20933
21061
  if (secondSep === -1) return null;
20934
21062
  const method = rest.slice(0, secondSep);
20935
- const path98 = rest.slice(secondSep + 1);
20936
- if (!resourceName || !method || !path98) return null;
20937
- return { resourceName, method, path: path98 };
21063
+ const path99 = rest.slice(secondSep + 1);
21064
+ if (!resourceName || !method || !path99) return null;
21065
+ return { resourceName, method, path: path99 };
20938
21066
  }
20939
21067
  function resourceNameFor(type, labels) {
20940
21068
  if (!labels) return null;
@@ -20972,14 +21100,14 @@ function mapLogEntryToSignal(entry2) {
20972
21100
  if (!req) return null;
20973
21101
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
20974
21102
  const method = req.requestMethod.toUpperCase();
20975
- const path98 = pathFromRequestUrl(req.requestUrl);
20976
- if (path98 === null) return null;
21103
+ const path99 = pathFromRequestUrl(req.requestUrl);
21104
+ if (path99 === null) return null;
20977
21105
  const timestamp = entry2.timestamp;
20978
21106
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
20979
21107
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
20980
21108
  return {
20981
21109
  targetKind: resourceType,
20982
- targetName: packFirebaseTargetName({ resourceName, method, path: path98 }),
21110
+ targetName: packFirebaseTargetName({ resourceName, method, path: path99 }),
20983
21111
  callCount: 1,
20984
21112
  errorCount: isError ? 1 : 0,
20985
21113
  lastObservedIso: timestamp
@@ -21004,9 +21132,9 @@ var import_types73 = require("@neat.is/types");
21004
21132
  function normalizeName(name) {
21005
21133
  return name.toLowerCase().replace(/[^a-z0-9]/g, "");
21006
21134
  }
21007
- function inferServiceName(graph, resourceName, method, path98) {
21135
+ function inferServiceName(graph, resourceName, method, path99) {
21008
21136
  const wantedMethod = method.toUpperCase();
21009
- const wantedPath = normalizePathTemplate(path98);
21137
+ const wantedPath = normalizePathTemplate(path99);
21010
21138
  const services = [];
21011
21139
  const owners = /* @__PURE__ */ new Set();
21012
21140
  graph.forEachNode((_id, attrs) => {
@@ -21223,7 +21351,7 @@ function mapEventToSignal(event) {
21223
21351
  if (Number.isNaN(observedAt.getTime())) return null;
21224
21352
  const statusCode = metadata?.statusCode;
21225
21353
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
21226
- const path98 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
21354
+ const path99 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
21227
21355
  return {
21228
21356
  targetKind: CLOUDFLARE_TARGET_KIND,
21229
21357
  targetName: scriptName,
@@ -21231,7 +21359,7 @@ function mapEventToSignal(event) {
21231
21359
  errorCount: isError ? 1 : 0,
21232
21360
  lastObservedIso: observedAt.toISOString(),
21233
21361
  method,
21234
- ...path98 ? { path: path98 } : {},
21362
+ ...path99 ? { path: path99 } : {},
21235
21363
  ...typeof statusCode === "number" ? { statusCode } : {},
21236
21364
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
21237
21365
  };
@@ -21277,8 +21405,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
21277
21405
  });
21278
21406
  return found;
21279
21407
  }
21280
- function findMatchingRouteNode(graph, serviceName, method, path98) {
21281
- const normalizedPath = normalizePathTemplate(path98);
21408
+ function findMatchingRouteNode(graph, serviceName, method, path99) {
21409
+ const normalizedPath = normalizePathTemplate(path99);
21282
21410
  let found = null;
21283
21411
  graph.forEachNode((id, attrs) => {
21284
21412
  if (found) return;
@@ -21295,10 +21423,10 @@ function createCloudflareResolveTarget(config, graph) {
21295
21423
  return (signal) => {
21296
21424
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
21297
21425
  const scriptName = signal.targetName;
21298
- const { method, path: path98 } = signal;
21426
+ const { method, path: path99 } = signal;
21299
21427
  const resolveRouteGrain = (serviceName, wholeFileId) => {
21300
- if (!method || !path98) return wholeFileId;
21301
- return findMatchingRouteNode(graph, serviceName, method, path98) ?? wholeFileId;
21428
+ if (!method || !path99) return wholeFileId;
21429
+ return findMatchingRouteNode(graph, serviceName, method, path99) ?? wholeFileId;
21302
21430
  };
21303
21431
  const mapping = config.workers?.[scriptName];
21304
21432
  if (mapping) {
@@ -21650,9 +21778,9 @@ function parseCloudRunTargetName(targetName) {
21650
21778
  const secondSep = rest.indexOf(FIELD_SEP2);
21651
21779
  if (secondSep === -1) return null;
21652
21780
  const method = rest.slice(0, secondSep);
21653
- const path98 = rest.slice(secondSep + 1);
21654
- if (!serviceName || !method || !path98) return null;
21655
- return { serviceName, method, path: path98 };
21781
+ const path99 = rest.slice(secondSep + 1);
21782
+ if (!serviceName || !method || !path99) return null;
21783
+ return { serviceName, method, path: path99 };
21656
21784
  }
21657
21785
 
21658
21786
  // src/connectors/cloud-run/map.ts
@@ -21681,14 +21809,14 @@ function mapLogEntryToSignal2(entry2) {
21681
21809
  if (!req) return null;
21682
21810
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
21683
21811
  const method = req.requestMethod.toUpperCase();
21684
- const path98 = pathFromRequestUrl2(req.requestUrl);
21685
- if (path98 === null) return null;
21812
+ const path99 = pathFromRequestUrl2(req.requestUrl);
21813
+ if (path99 === null) return null;
21686
21814
  const timestamp = entry2.timestamp;
21687
21815
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
21688
21816
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
21689
21817
  return {
21690
21818
  targetKind: CLOUD_RUN_TARGET_KIND,
21691
- targetName: packCloudRunTargetName({ serviceName, method, path: path98 }),
21819
+ targetName: packCloudRunTargetName({ serviceName, method, path: path99 }),
21692
21820
  callCount: 1,
21693
21821
  errorCount: isError ? 1 : 0,
21694
21822
  lastObservedIso: timestamp
@@ -21727,14 +21855,14 @@ function createCloudRunResolveTarget(graph, config) {
21727
21855
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
21728
21856
  const identity = parseCloudRunTargetName(signal.targetName);
21729
21857
  if (!identity) return null;
21730
- const { serviceName: gcpServiceName, method, path: path98 } = identity;
21731
- const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path98) ?? void 0 : void 0);
21858
+ const { serviceName: gcpServiceName, method, path: path99 } = identity;
21859
+ const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path99) ?? void 0 : void 0);
21732
21860
  if (mappedService) {
21733
21861
  const routeNodeId = findMatchingRouteNode2(
21734
21862
  graph,
21735
21863
  mappedService,
21736
21864
  method,
21737
- normalizePathTemplate(path98)
21865
+ normalizePathTemplate(path99)
21738
21866
  );
21739
21867
  if (routeNodeId) {
21740
21868
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types84.EdgeType.CALLS };
@@ -21870,9 +21998,9 @@ function parseGcpLbTargetName(targetName) {
21870
21998
  const secondSep = rest.indexOf(FIELD_SEP3);
21871
21999
  if (secondSep === -1) return null;
21872
22000
  const method = rest.slice(0, secondSep);
21873
- const path98 = rest.slice(secondSep + 1);
21874
- if (!backendServiceName || !method || !path98) return null;
21875
- return { backendServiceName, method, path: path98 };
22001
+ const path99 = rest.slice(secondSep + 1);
22002
+ if (!backendServiceName || !method || !path99) return null;
22003
+ return { backendServiceName, method, path: path99 };
21876
22004
  }
21877
22005
 
21878
22006
  // src/connectors/gcp-lb/map.ts
@@ -21901,14 +22029,14 @@ function mapLogEntryToSignal3(entry2) {
21901
22029
  if (!req) return null;
21902
22030
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
21903
22031
  const method = req.requestMethod.toUpperCase();
21904
- const path98 = pathFromRequestUrl3(req.requestUrl);
21905
- if (path98 === null) return null;
22032
+ const path99 = pathFromRequestUrl3(req.requestUrl);
22033
+ if (path99 === null) return null;
21906
22034
  const timestamp = entry2.timestamp;
21907
22035
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
21908
22036
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD5;
21909
22037
  return {
21910
22038
  targetKind: GCP_LB_TARGET_KIND,
21911
- targetName: packGcpLbTargetName({ backendServiceName, method, path: path98 }),
22039
+ targetName: packGcpLbTargetName({ backendServiceName, method, path: path99 }),
21912
22040
  callCount: 1,
21913
22041
  errorCount: isError ? 1 : 0,
21914
22042
  lastObservedIso: timestamp
@@ -21947,14 +22075,14 @@ function createGcpLbResolveTarget(graph, config) {
21947
22075
  if (signal.targetKind !== GCP_LB_TARGET_KIND) return null;
21948
22076
  const identity = parseGcpLbTargetName(signal.targetName);
21949
22077
  if (!identity) return null;
21950
- const { backendServiceName, method, path: path98 } = identity;
21951
- const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path98) ?? void 0 : void 0);
22078
+ const { backendServiceName, method, path: path99 } = identity;
22079
+ const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path99) ?? void 0 : void 0);
21952
22080
  if (mappedService) {
21953
22081
  const routeNodeId = findMatchingRouteNode3(
21954
22082
  graph,
21955
22083
  mappedService,
21956
22084
  method,
21957
- normalizePathTemplate(path98)
22085
+ normalizePathTemplate(path99)
21958
22086
  );
21959
22087
  if (routeNodeId) {
21960
22088
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types88.EdgeType.CALLS };
@@ -24346,9 +24474,9 @@ function makeK8sFetchImpl(transport) {
24346
24474
  req.end();
24347
24475
  }));
24348
24476
  }
24349
- async function listResource(transport, namespace, path98, opts = {}) {
24477
+ async function listResource(transport, namespace, path99, opts = {}) {
24350
24478
  const base = opts.apiUrl ?? transport.server;
24351
- const url = `${base.replace(/\/$/, "")}${path98}`;
24479
+ const url = `${base.replace(/\/$/, "")}${path99}`;
24352
24480
  const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
24353
24481
  const res = await junctionFetch(
24354
24482
  url,
@@ -24358,7 +24486,7 @@ async function listResource(transport, namespace, path98, opts = {}) {
24358
24486
  { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
24359
24487
  );
24360
24488
  if (!res.ok) {
24361
- throw new Error(`kubernetes ${path98} failed: ${res.status} ${res.statusText}`);
24489
+ throw new Error(`kubernetes ${path99} failed: ${res.status} ${res.statusText}`);
24362
24490
  }
24363
24491
  const json = await res.json();
24364
24492
  return Array.isArray(json.items) ? json.items : [];
@@ -30047,7 +30175,7 @@ async function clearActiveProfile(home = neatHome3()) {
30047
30175
  }
30048
30176
 
30049
30177
  // src/login-sso.ts
30050
- var DEFAULT_CP_URL = "https://neat-control-plane-bg5yqctn2q-nw.a.run.app";
30178
+ var DEFAULT_CP_URL = "https://api.neat.is";
30051
30179
  var DEFAULT_WEB_URL = "https://app.neat.is";
30052
30180
  var CALLBACK_TIMEOUT_MS = 5 * 6e4;
30053
30181
  function resolveCpUrl(env = process.env, override) {
@@ -30236,6 +30364,11 @@ async function runSsoLogin(opts, deps) {
30236
30364
  out(` OTEL_EXPORTER_OTLP_ENDPOINT=${cred.ingestEndpoint}`);
30237
30365
  out(` OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${cred.otelToken}`);
30238
30366
  }
30367
+ out("");
30368
+ out("This directory isn't part of that graph yet \u2014 logging in connected your account,");
30369
+ out("not this repo, and nothing here has been uploaded.");
30370
+ out(` Manage projects and repos: ${opts.webUrl}`);
30371
+ out("");
30239
30372
  out("Run `neat logout` to switch back to your local daemon.");
30240
30373
  }
30241
30374
  return 0;
@@ -30372,10 +30505,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
30372
30505
  const root = baseUrl.replace(/\/$/, "");
30373
30506
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
30374
30507
  return {
30375
- async get(path98) {
30508
+ async get(path99) {
30376
30509
  let res;
30377
30510
  try {
30378
- res = await fetchImpl(`${root}${path98}`, {
30511
+ res = await fetchImpl(`${root}${path99}`, {
30379
30512
  headers: { ...authHeader }
30380
30513
  });
30381
30514
  } catch (err) {
@@ -30387,16 +30520,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
30387
30520
  const body = await res.text().catch(() => "");
30388
30521
  throw new HttpError(
30389
30522
  res.status,
30390
- `${res.status} ${res.statusText} on GET ${path98}: ${body}`,
30523
+ `${res.status} ${res.statusText} on GET ${path99}: ${body}`,
30391
30524
  body
30392
30525
  );
30393
30526
  }
30394
30527
  return await res.json();
30395
30528
  },
30396
- async post(path98, body) {
30529
+ async post(path99, body) {
30397
30530
  let res;
30398
30531
  try {
30399
- res = await fetchImpl(`${root}${path98}`, {
30532
+ res = await fetchImpl(`${root}${path99}`, {
30400
30533
  method: "POST",
30401
30534
  headers: { "content-type": "application/json", ...authHeader },
30402
30535
  body: JSON.stringify(body)
@@ -30410,7 +30543,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
30410
30543
  const text = await res.text().catch(() => "");
30411
30544
  throw new HttpError(
30412
30545
  res.status,
30413
- `${res.status} ${res.statusText} on POST ${path98}: ${text}`,
30546
+ `${res.status} ${res.statusText} on POST ${path99}: ${text}`,
30414
30547
  text
30415
30548
  );
30416
30549
  }
@@ -30424,12 +30557,12 @@ function projectPath(project, suffix) {
30424
30557
  }
30425
30558
  async function runRootCause(client, input) {
30426
30559
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
30427
- const path98 = projectPath(
30560
+ const path99 = projectPath(
30428
30561
  input.project,
30429
30562
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
30430
30563
  );
30431
30564
  try {
30432
- const result = await client.get(path98);
30565
+ const result = await client.get(path99);
30433
30566
  const arrowPath = result.traversalPath.join(" \u2190 ");
30434
30567
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
30435
30568
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -30455,12 +30588,12 @@ async function runRootCause(client, input) {
30455
30588
  }
30456
30589
  async function runBlastRadius(client, input) {
30457
30590
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
30458
- const path98 = projectPath(
30591
+ const path99 = projectPath(
30459
30592
  input.project,
30460
30593
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
30461
30594
  );
30462
30595
  try {
30463
- const result = await client.get(path98);
30596
+ const result = await client.get(path99);
30464
30597
  if (result.totalAffected === 0) {
30465
30598
  return {
30466
30599
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -30494,12 +30627,12 @@ function formatBlastEntry(n) {
30494
30627
  }
30495
30628
  async function runDependencies(client, input) {
30496
30629
  const depth = input.depth ?? 3;
30497
- const path98 = projectPath(
30630
+ const path99 = projectPath(
30498
30631
  input.project,
30499
30632
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
30500
30633
  );
30501
30634
  try {
30502
- const result = await client.get(path98);
30635
+ const result = await client.get(path99);
30503
30636
  if (result.total === 0) {
30504
30637
  return {
30505
30638
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -30591,9 +30724,9 @@ function formatDuration(ms) {
30591
30724
  return `${Math.round(h / 24)}d`;
30592
30725
  }
30593
30726
  async function runIncidents(client, input) {
30594
- const path98 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
30727
+ const path99 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
30595
30728
  try {
30596
- const body = await client.get(path98);
30729
+ const body = await client.get(path99);
30597
30730
  const events = body.events;
30598
30731
  if (events.length === 0) {
30599
30732
  return {
@@ -31372,6 +31505,8 @@ async function defaultReadSecret(prompt) {
31372
31505
 
31373
31506
  // src/welcome.ts
31374
31507
  init_cjs_shims();
31508
+ var import_node_fs55 = require("fs");
31509
+ var import_node_path93 = __toESM(require("path"), 1);
31375
31510
  var import_promises5 = __toESM(require("readline/promises"), 1);
31376
31511
  var WORDMARK = [
31377
31512
  "\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
@@ -31404,6 +31539,28 @@ function printHeader(out) {
31404
31539
  }
31405
31540
  async function runWelcome(deps = {}) {
31406
31541
  const out = deps.out ?? ((line) => console.log(line));
31542
+ try {
31543
+ return await runFrontDoor(deps, out);
31544
+ } catch (err) {
31545
+ if (isPromptCancelled(err)) {
31546
+ out("");
31547
+ return 130;
31548
+ }
31549
+ throw err;
31550
+ }
31551
+ }
31552
+ var PromptCancelled = class extends Error {
31553
+ constructor() {
31554
+ super("prompt cancelled");
31555
+ this.name = "PromptCancelled";
31556
+ }
31557
+ };
31558
+ function isPromptCancelled(err) {
31559
+ if (err instanceof PromptCancelled) return true;
31560
+ const e = err;
31561
+ return e?.code === "ABORT_ERR" || e?.name === "AbortError";
31562
+ }
31563
+ async function runFrontDoor(deps, out) {
31407
31564
  const readLine = deps.readLine ?? defaultReadLine2;
31408
31565
  const login = deps.login ?? (() => Promise.resolve(0));
31409
31566
  const orchestrator = deps.orchestrator ?? (() => Promise.resolve(0));
@@ -31411,21 +31568,114 @@ async function runWelcome(deps = {}) {
31411
31568
  printHeader(out);
31412
31569
  out("Welcome to NEAT. Let's get you a graph of this system.");
31413
31570
  out("");
31414
- out(" 1) I have a NEAT account \u2014 log in");
31415
- out(" 2) Self-hosted \u2014 set up NEAT on this machine");
31571
+ const keyReader = deps.readKey !== void 0 ? { read: deps.readKey, done: () => {
31572
+ } } : createKeyReader();
31573
+ const picked = keyReader !== null ? await selectOption(out, keyReader, deps.moveCursorUp ?? defaultMoveCursorUp) : await selectByNumber(out, readLine);
31574
+ if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd);
31575
+ if (picked === 0) return login(["--browser"]);
31576
+ return runSelfHosted(out, readLine, orchestrator, cwd);
31577
+ }
31578
+ var MENU_OPTIONS = [
31579
+ "Log me into Hosted Neat",
31580
+ "I'd like to self-host or use it locally (copy a prompt)"
31581
+ ];
31582
+ async function selectOption(out, reader, moveCursorUp) {
31583
+ let cursor = 0;
31584
+ const render = () => {
31585
+ for (const [i, label] of MENU_OPTIONS.entries()) {
31586
+ out(i === cursor ? ` \u276F ${i + 1}) ${label}` : ` ${i + 1}) ${label}`);
31587
+ }
31588
+ };
31589
+ try {
31590
+ render();
31591
+ out("");
31592
+ out(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 1 or 2 to jump");
31593
+ for (; ; ) {
31594
+ const key = await reader.read();
31595
+ if (key === void 0) return void 0;
31596
+ if (key === "cancel") throw new PromptCancelled();
31597
+ if (key === "enter") return cursor;
31598
+ if (key === "select-1") return 0;
31599
+ if (key === "select-2") return 1;
31600
+ if (key === "up") cursor = (cursor + MENU_OPTIONS.length - 1) % MENU_OPTIONS.length;
31601
+ else if (key === "down") cursor = (cursor + 1) % MENU_OPTIONS.length;
31602
+ else continue;
31603
+ moveCursorUp(MENU_OPTIONS.length + 2);
31604
+ render();
31605
+ out("");
31606
+ out(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 1 or 2 to jump");
31607
+ }
31608
+ } finally {
31609
+ reader.done();
31610
+ }
31611
+ }
31612
+ async function selectByNumber(out, readLine) {
31613
+ for (const [i, label] of MENU_OPTIONS.entries()) out(` ${i + 1}) ${label}`);
31416
31614
  out("");
31417
31615
  for (; ; ) {
31418
31616
  const choice = (await readLine("Choose 1 or 2 (default 2): "))?.trim();
31419
- if (choice === void 0) return runSelfHosted(out, readLine, orchestrator, cwd);
31420
- if (choice === "1") {
31421
- return login(["--browser"]);
31422
- }
31423
- if (choice === "2" || choice === "") {
31424
- return runSelfHosted(out, readLine, orchestrator, cwd);
31425
- }
31426
- out(`"${choice}" isn't 1 or 2 \u2014 try again, or press Enter for self-hosted.`);
31617
+ if (choice === void 0) return void 0;
31618
+ if (choice === "1") return 0;
31619
+ if (choice === "2" || choice === "") return 1;
31620
+ out(`"${choice}" isn't 1 or 2 \u2014 try again, or press Enter for the local path.`);
31427
31621
  }
31428
31622
  }
31623
+ function createKeyReader() {
31624
+ const stdin = process.stdin;
31625
+ if (!stdin.isTTY || typeof stdin.setRawMode !== "function") return null;
31626
+ stdin.setRawMode(true);
31627
+ stdin.resume();
31628
+ let closed = false;
31629
+ let ended = false;
31630
+ const markEnded = () => {
31631
+ ended = true;
31632
+ };
31633
+ stdin.once("end", markEnded);
31634
+ stdin.once("close", markEnded);
31635
+ return {
31636
+ read: () => new Promise((resolve) => {
31637
+ if (ended) {
31638
+ resolve(void 0);
31639
+ return;
31640
+ }
31641
+ const finish = (value) => {
31642
+ stdin.removeListener("data", onData);
31643
+ stdin.removeListener("end", onClosed);
31644
+ stdin.removeListener("close", onClosed);
31645
+ resolve(value);
31646
+ };
31647
+ const onData = (buf) => finish(decodeKey(buf));
31648
+ const onClosed = () => {
31649
+ ended = true;
31650
+ finish(void 0);
31651
+ };
31652
+ stdin.on("data", onData);
31653
+ stdin.once("end", onClosed);
31654
+ stdin.once("close", onClosed);
31655
+ }),
31656
+ done: () => {
31657
+ if (closed) return;
31658
+ closed = true;
31659
+ stdin.removeListener("end", markEnded);
31660
+ stdin.removeListener("close", markEnded);
31661
+ stdin.setRawMode(false);
31662
+ stdin.pause();
31663
+ }
31664
+ };
31665
+ }
31666
+ function decodeKey(buf) {
31667
+ const s = buf.toString("utf8");
31668
+ if (s === "\x1B[A" || s === "k") return "up";
31669
+ if (s === "\x1B[B" || s === "j") return "down";
31670
+ if (s === "\r" || s === "\n") return "enter";
31671
+ if (s === "1") return "select-1";
31672
+ if (s === "2") return "select-2";
31673
+ if (s === "" || s === "\x1B") return "cancel";
31674
+ return "ignore";
31675
+ }
31676
+ function defaultMoveCursorUp(rows) {
31677
+ if (rows > 0) process.stdout.write(`\x1B[${rows}A`);
31678
+ }
31429
31679
  async function runSelfHosted(out, readLine, orchestrator, cwd) {
31430
31680
  const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
31431
31681
  if (wantsPrompt !== "n" && wantsPrompt !== "no") {
@@ -31445,10 +31695,23 @@ async function shouldShowWelcome(deps = {}) {
31445
31695
  const stdinTTY = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
31446
31696
  const stdoutTTY = deps.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
31447
31697
  if (!stdinTTY || !stdoutTTY) return false;
31448
- const readProfiles = deps.readProfiles ?? (() => readProfilesConfig());
31698
+ const cwd = deps.cwd ?? process.cwd();
31699
+ const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
31700
+ const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
31701
+ try {
31702
+ const here = import_node_path93.default.resolve(cwd);
31703
+ const { projects } = await readRegistry2();
31704
+ if (projects.some((p) => import_node_path93.default.resolve(p.path) === here)) return false;
31705
+ if (await hasSnapshot(here)) return false;
31706
+ return true;
31707
+ } catch {
31708
+ return false;
31709
+ }
31710
+ }
31711
+ async function defaultHasSnapshot(cwd) {
31449
31712
  try {
31450
- const { profiles } = await readProfiles();
31451
- return profiles.length === 0;
31713
+ const entries = await import_node_fs55.promises.readdir(import_node_path93.default.join(cwd, "neat-out"));
31714
+ return entries.some((e) => e.endsWith(".json"));
31452
31715
  } catch {
31453
31716
  return false;
31454
31717
  }
@@ -31465,27 +31728,27 @@ async function defaultReadLine2(prompt) {
31465
31728
 
31466
31729
  // src/hooks-cli.ts
31467
31730
  init_cjs_shims();
31468
- var import_node_path93 = __toESM(require("path"), 1);
31731
+ var import_node_path94 = __toESM(require("path"), 1);
31469
31732
  var import_node_os8 = __toESM(require("os"), 1);
31470
- var import_node_fs55 = require("fs");
31733
+ var import_node_fs56 = require("fs");
31471
31734
  var import_node_url5 = require("url");
31472
31735
  var HOOK_FILENAME = "neat-search-nudge.mjs";
31473
31736
  var GUIDE_FILENAME = "GRAPH_FIRST.md";
31474
31737
  var GUIDE_INSTALL_NAME = "neat-graph-first.md";
31475
31738
  var HOOK_MATCHER = "Grep|Glob|Bash";
31476
31739
  function moduleDir() {
31477
- return typeof __dirname !== "undefined" ? __dirname : import_node_path93.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
31740
+ return typeof __dirname !== "undefined" ? __dirname : import_node_path94.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
31478
31741
  }
31479
31742
  async function readSkillAsset(rel) {
31480
31743
  const here = moduleDir();
31481
31744
  const candidates = [
31482
- import_node_path93.default.resolve(here, "../../claude-skill", rel),
31483
- import_node_path93.default.resolve(here, "../../../claude-skill", rel),
31484
- import_node_path93.default.resolve(here, "../claude-skill", rel)
31745
+ import_node_path94.default.resolve(here, "../../claude-skill", rel),
31746
+ import_node_path94.default.resolve(here, "../../../claude-skill", rel),
31747
+ import_node_path94.default.resolve(here, "../claude-skill", rel)
31485
31748
  ];
31486
31749
  for (const candidate of candidates) {
31487
31750
  try {
31488
- return await import_node_fs55.promises.readFile(candidate, "utf8");
31751
+ return await import_node_fs56.promises.readFile(candidate, "utf8");
31489
31752
  } catch {
31490
31753
  }
31491
31754
  }
@@ -31495,20 +31758,20 @@ async function readSkillAsset(rel) {
31495
31758
  }
31496
31759
  function neatHome4() {
31497
31760
  const override = process.env.NEAT_HOME;
31498
- if (override && override.length > 0) return import_node_path93.default.resolve(override);
31499
- return import_node_path93.default.join(import_node_os8.default.homedir(), ".neat");
31761
+ if (override && override.length > 0) return import_node_path94.default.resolve(override);
31762
+ return import_node_path94.default.join(import_node_os8.default.homedir(), ".neat");
31500
31763
  }
31501
31764
  function claudeSettingsPath() {
31502
31765
  const override = process.env.NEAT_CLAUDE_SETTINGS;
31503
- if (override && override.length > 0) return import_node_path93.default.resolve(override);
31766
+ if (override && override.length > 0) return import_node_path94.default.resolve(override);
31504
31767
  const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os8.default.homedir();
31505
- return import_node_path93.default.join(home, ".claude", "settings.json");
31768
+ return import_node_path94.default.join(home, ".claude", "settings.json");
31506
31769
  }
31507
31770
  function installedHookPath() {
31508
- return import_node_path93.default.join(neatHome4(), "hooks", HOOK_FILENAME);
31771
+ return import_node_path94.default.join(neatHome4(), "hooks", HOOK_FILENAME);
31509
31772
  }
31510
31773
  function gateFlagPath() {
31511
- return import_node_path93.default.join(neatHome4(), "hooks", "gate-enabled");
31774
+ return import_node_path94.default.join(neatHome4(), "hooks", "gate-enabled");
31512
31775
  }
31513
31776
  function isNeatSearchEntry(entry2) {
31514
31777
  return (entry2.hooks ?? []).some(
@@ -31541,14 +31804,14 @@ async function runHooks(opts) {
31541
31804
  const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
31542
31805
  const guide = await readSkillAsset(GUIDE_FILENAME);
31543
31806
  const scriptPath = installedHookPath();
31544
- await import_node_fs55.promises.mkdir(import_node_path93.default.dirname(scriptPath), { recursive: true });
31545
- await import_node_fs55.promises.writeFile(scriptPath, hookScript, { mode: 493 });
31546
- const guidePath = import_node_path93.default.join(neatHome4(), GUIDE_INSTALL_NAME);
31547
- await import_node_fs55.promises.writeFile(guidePath, guide, "utf8");
31807
+ await import_node_fs56.promises.mkdir(import_node_path94.default.dirname(scriptPath), { recursive: true });
31808
+ await import_node_fs56.promises.writeFile(scriptPath, hookScript, { mode: 493 });
31809
+ const guidePath = import_node_path94.default.join(neatHome4(), GUIDE_INSTALL_NAME);
31810
+ await import_node_fs56.promises.writeFile(guidePath, guide, "utf8");
31548
31811
  const settingsFile = claudeSettingsPath();
31549
31812
  let settings = {};
31550
31813
  try {
31551
- settings = JSON.parse(await import_node_fs55.promises.readFile(settingsFile, "utf8"));
31814
+ settings = JSON.parse(await import_node_fs56.promises.readFile(settingsFile, "utf8"));
31552
31815
  } catch (err) {
31553
31816
  if (err.code !== "ENOENT") {
31554
31817
  console.error(
@@ -31570,14 +31833,14 @@ async function runHooks(opts) {
31570
31833
  ...settings,
31571
31834
  hooks: { ...hooks, PreToolUse: preToolUse }
31572
31835
  };
31573
- await import_node_fs55.promises.mkdir(import_node_path93.default.dirname(settingsFile), { recursive: true });
31574
- await import_node_fs55.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
31836
+ await import_node_fs56.promises.mkdir(import_node_path94.default.dirname(settingsFile), { recursive: true });
31837
+ await import_node_fs56.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
31575
31838
  const flag = gateFlagPath();
31576
31839
  if (opts.gate) {
31577
- await import_node_fs55.promises.mkdir(import_node_path93.default.dirname(flag), { recursive: true });
31578
- await import_node_fs55.promises.writeFile(flag, "1\n", "utf8");
31840
+ await import_node_fs56.promises.mkdir(import_node_path94.default.dirname(flag), { recursive: true });
31841
+ await import_node_fs56.promises.writeFile(flag, "1\n", "utf8");
31579
31842
  } else {
31580
- await import_node_fs55.promises.rm(flag, { force: true });
31843
+ await import_node_fs56.promises.rm(flag, { force: true });
31581
31844
  }
31582
31845
  const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
31583
31846
  console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
@@ -31667,8 +31930,8 @@ async function runHooksCommand(args) {
31667
31930
 
31668
31931
  // src/claude-cli.ts
31669
31932
  init_cjs_shims();
31670
- var import_node_path94 = __toESM(require("path"), 1);
31671
- var import_node_fs56 = require("fs");
31933
+ var import_node_path95 = __toESM(require("path"), 1);
31934
+ var import_node_fs57 = require("fs");
31672
31935
  var NEAT_SECTION_HEADING = "## neat";
31673
31936
  var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
31674
31937
  code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
@@ -31702,8 +31965,8 @@ ${NEAT_DIRECTIVE_BODY}
31702
31965
  }
31703
31966
  function claudeMdPath() {
31704
31967
  const override = process.env.NEAT_CLAUDE_MD;
31705
- if (override && override.length > 0) return import_node_path94.default.resolve(override);
31706
- return import_node_path94.default.join(process.cwd(), "CLAUDE.md");
31968
+ if (override && override.length > 0) return import_node_path95.default.resolve(override);
31969
+ return import_node_path95.default.join(process.cwd(), "CLAUDE.md");
31707
31970
  }
31708
31971
  function splitAroundSection(raw) {
31709
31972
  const lines = raw.split("\n");
@@ -31731,7 +31994,7 @@ function compose(before, after) {
31731
31994
  }
31732
31995
  async function readIfExists2(file) {
31733
31996
  try {
31734
- return await import_node_fs56.promises.readFile(file, "utf8");
31997
+ return await import_node_fs57.promises.readFile(file, "utf8");
31735
31998
  } catch (err) {
31736
31999
  if (err.code === "ENOENT") return null;
31737
32000
  throw err;
@@ -31742,8 +32005,8 @@ async function runInstall() {
31742
32005
  const raw = await readIfExists2(file) ?? "";
31743
32006
  const { before, after, found } = splitAroundSection(raw);
31744
32007
  const next = compose(before, after);
31745
- await import_node_fs56.promises.mkdir(import_node_path94.default.dirname(file), { recursive: true });
31746
- await import_node_fs56.promises.writeFile(file, next, "utf8");
32008
+ await import_node_fs57.promises.mkdir(import_node_path95.default.dirname(file), { recursive: true });
32009
+ await import_node_fs57.promises.writeFile(file, next, "utf8");
31747
32010
  const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
31748
32011
  console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
31749
32012
  console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
@@ -31764,7 +32027,7 @@ async function runUninstall() {
31764
32027
  }
31765
32028
  const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
31766
32029
  const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
31767
- await import_node_fs56.promises.writeFile(file, next, "utf8");
32030
+ await import_node_fs57.promises.writeFile(file, next, "utf8");
31768
32031
  console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
31769
32032
  return { exitCode: 0 };
31770
32033
  }
@@ -31807,9 +32070,9 @@ async function runClaudeCommand(args) {
31807
32070
 
31808
32071
  // src/codex-cli.ts
31809
32072
  init_cjs_shims();
31810
- var import_node_path95 = __toESM(require("path"), 1);
32073
+ var import_node_path96 = __toESM(require("path"), 1);
31811
32074
  var import_node_os9 = __toESM(require("os"), 1);
31812
- var import_node_fs57 = require("fs");
32075
+ var import_node_fs58 = require("fs");
31813
32076
  var import_node_util = require("util");
31814
32077
  var import_smol_toml6 = require("smol-toml");
31815
32078
  var CODEX_MCP_SERVER = {
@@ -31827,14 +32090,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
31827
32090
  var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
31828
32091
  function codexConfigPath() {
31829
32092
  const override = process.env.NEAT_CODEX_CONFIG;
31830
- if (override && override.length > 0) return import_node_path95.default.resolve(override);
32093
+ if (override && override.length > 0) return import_node_path96.default.resolve(override);
31831
32094
  const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os9.default.homedir();
31832
- return import_node_path95.default.join(home, ".codex", "config.toml");
32095
+ return import_node_path96.default.join(home, ".codex", "config.toml");
31833
32096
  }
31834
32097
  function agentsFilePath() {
31835
32098
  const override = process.env.NEAT_CODEX_AGENTS;
31836
- if (override && override.length > 0) return import_node_path95.default.resolve(override);
31837
- return import_node_path95.default.join(process.cwd(), "AGENTS.md");
32099
+ if (override && override.length > 0) return import_node_path96.default.resolve(override);
32100
+ return import_node_path96.default.join(process.cwd(), "AGENTS.md");
31838
32101
  }
31839
32102
  function isTableHeader(line) {
31840
32103
  return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
@@ -31968,7 +32231,7 @@ async function runCodex(opts) {
31968
32231
  const agentsPath = agentsFilePath();
31969
32232
  let configRaw = "";
31970
32233
  try {
31971
- configRaw = await import_node_fs57.promises.readFile(configPath, "utf8");
32234
+ configRaw = await import_node_fs58.promises.readFile(configPath, "utf8");
31972
32235
  } catch (err) {
31973
32236
  if (err.code !== "ENOENT") {
31974
32237
  console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
@@ -31977,7 +32240,7 @@ async function runCodex(opts) {
31977
32240
  }
31978
32241
  let agentsRaw = "";
31979
32242
  try {
31980
- agentsRaw = await import_node_fs57.promises.readFile(agentsPath, "utf8");
32243
+ agentsRaw = await import_node_fs58.promises.readFile(agentsPath, "utf8");
31981
32244
  } catch (err) {
31982
32245
  if (err.code !== "ENOENT") {
31983
32246
  console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
@@ -32017,15 +32280,15 @@ async function runCodex(opts) {
32017
32280
  return { exitCode: 0 };
32018
32281
  }
32019
32282
  if (config.changed) {
32020
- await import_node_fs57.promises.mkdir(import_node_path95.default.dirname(configPath), { recursive: true });
32021
- await import_node_fs57.promises.writeFile(configPath, config.text, "utf8");
32283
+ await import_node_fs58.promises.mkdir(import_node_path96.default.dirname(configPath), { recursive: true });
32284
+ await import_node_fs58.promises.writeFile(configPath, config.text, "utf8");
32022
32285
  console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
32023
32286
  } else {
32024
32287
  console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
32025
32288
  }
32026
32289
  if (agents.changed) {
32027
- await import_node_fs57.promises.mkdir(import_node_path95.default.dirname(agentsPath), { recursive: true });
32028
- await import_node_fs57.promises.writeFile(agentsPath, agents.text, "utf8");
32290
+ await import_node_fs58.promises.mkdir(import_node_path96.default.dirname(agentsPath), { recursive: true });
32291
+ await import_node_fs58.promises.writeFile(agentsPath, agents.text, "utf8");
32029
32292
  console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
32030
32293
  } else {
32031
32294
  console.log(`neat codex: ${agentsPath} already has the graph-first block`);
@@ -32082,9 +32345,9 @@ async function runCodexCommand(args) {
32082
32345
 
32083
32346
  // src/editors-cli.ts
32084
32347
  init_cjs_shims();
32085
- var import_node_path96 = __toESM(require("path"), 1);
32348
+ var import_node_path97 = __toESM(require("path"), 1);
32086
32349
  var import_node_os10 = __toESM(require("os"), 1);
32087
- var import_node_fs58 = require("fs");
32350
+ var import_node_fs59 = require("fs");
32088
32351
  var import_node_util2 = require("util");
32089
32352
  var jsonc = __toESM(require("jsonc-parser"), 1);
32090
32353
  var NEAT_MCP_SERVER = {
@@ -32108,17 +32371,17 @@ function homeDir() {
32108
32371
  }
32109
32372
  function xdgConfigDir() {
32110
32373
  const xdg = process.env.XDG_CONFIG_HOME;
32111
- return xdg && xdg.length > 0 ? import_node_path96.default.resolve(xdg) : import_node_path96.default.join(homeDir(), ".config");
32374
+ return xdg && xdg.length > 0 ? import_node_path97.default.resolve(xdg) : import_node_path97.default.join(homeDir(), ".config");
32112
32375
  }
32113
32376
  function envOverride(name) {
32114
32377
  const v = process.env[name];
32115
- return v && v.length > 0 ? import_node_path96.default.resolve(v) : void 0;
32378
+ return v && v.length > 0 ? import_node_path97.default.resolve(v) : void 0;
32116
32379
  }
32117
32380
  var CURSOR_CLIENT = {
32118
32381
  id: "cursor",
32119
32382
  label: "Cursor",
32120
32383
  docsUrl: "https://docs.cursor.com/context/mcp",
32121
- mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path96.default.join(homeDir(), ".cursor", "mcp.json"),
32384
+ mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path97.default.join(homeDir(), ".cursor", "mcp.json"),
32122
32385
  mcpContainerKey: "mcpServers",
32123
32386
  format: "json",
32124
32387
  // Cursor still reads a single `.cursorrules` at the project root (the modern
@@ -32130,7 +32393,7 @@ var DEVIN_CLIENT = {
32130
32393
  id: "devin",
32131
32394
  label: "Devin Desktop (Cascade)",
32132
32395
  docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
32133
- mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path96.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
32396
+ mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path97.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
32134
32397
  mcpContainerKey: "mcpServers",
32135
32398
  format: "json",
32136
32399
  rulesFileName: ".windsurfrules"
@@ -32139,7 +32402,7 @@ var GEMINI_CLIENT = {
32139
32402
  id: "gemini",
32140
32403
  label: "Gemini CLI",
32141
32404
  docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
32142
- mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path96.default.join(homeDir(), ".gemini", "settings.json"),
32405
+ mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path97.default.join(homeDir(), ".gemini", "settings.json"),
32143
32406
  mcpContainerKey: "mcpServers",
32144
32407
  format: "json",
32145
32408
  rulesFileName: "GEMINI.md"
@@ -32148,7 +32411,7 @@ var QWEN_CLIENT = {
32148
32411
  id: "qwen",
32149
32412
  label: "Qwen Code",
32150
32413
  docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
32151
- mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path96.default.join(homeDir(), ".qwen", "settings.json"),
32414
+ mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path97.default.join(homeDir(), ".qwen", "settings.json"),
32152
32415
  mcpContainerKey: "mcpServers",
32153
32416
  format: "json",
32154
32417
  rulesFileName: "QWEN.md"
@@ -32157,7 +32420,7 @@ var AMAZONQ_CLIENT = {
32157
32420
  id: "amazonq",
32158
32421
  label: "Amazon Q Developer CLI",
32159
32422
  docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
32160
- mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path96.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
32423
+ mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path97.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
32161
32424
  mcpContainerKey: "mcpServers",
32162
32425
  format: "json"
32163
32426
  };
@@ -32165,7 +32428,7 @@ var ROOCODE_CLIENT = {
32165
32428
  id: "roocode",
32166
32429
  label: "Roo Code",
32167
32430
  docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
32168
- mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path96.default.join(process.cwd(), ".roo", "mcp.json"),
32431
+ mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path97.default.join(process.cwd(), ".roo", "mcp.json"),
32169
32432
  mcpContainerKey: "mcpServers",
32170
32433
  format: "json"
32171
32434
  };
@@ -32178,9 +32441,9 @@ var ZED_CLIENT = {
32178
32441
  if (override) return override;
32179
32442
  if (process.platform === "win32") {
32180
32443
  const appData = process.env.APPDATA;
32181
- if (appData && appData.length > 0) return import_node_path96.default.join(appData, "Zed", "settings.json");
32444
+ if (appData && appData.length > 0) return import_node_path97.default.join(appData, "Zed", "settings.json");
32182
32445
  }
32183
- return import_node_path96.default.join(homeDir(), ".config", "zed", "settings.json");
32446
+ return import_node_path97.default.join(homeDir(), ".config", "zed", "settings.json");
32184
32447
  },
32185
32448
  mcpContainerKey: "context_servers",
32186
32449
  format: "jsonc",
@@ -32190,7 +32453,7 @@ var OPENCODE_CLIENT = {
32190
32453
  id: "opencode",
32191
32454
  label: "OpenCode",
32192
32455
  docsUrl: "https://opencode.ai/docs/mcp-servers/",
32193
- mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path96.default.join(xdgConfigDir(), "opencode", "opencode.json"),
32456
+ mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path97.default.join(xdgConfigDir(), "opencode", "opencode.json"),
32194
32457
  mcpContainerKey: "mcp",
32195
32458
  format: "json",
32196
32459
  serverEntry: NEAT_OPENCODE_SERVER,
@@ -32200,7 +32463,7 @@ var CRUSH_CLIENT = {
32200
32463
  id: "crush",
32201
32464
  label: "Crush",
32202
32465
  docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
32203
- mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path96.default.join(xdgConfigDir(), "crush", "crush.json"),
32466
+ mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path97.default.join(xdgConfigDir(), "crush", "crush.json"),
32204
32467
  mcpContainerKey: "mcp",
32205
32468
  format: "json",
32206
32469
  serverEntry: NEAT_CRUSH_SERVER,
@@ -32263,7 +32526,7 @@ async function planMcp(client, mcpPath) {
32263
32526
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
32264
32527
  let raw = "";
32265
32528
  try {
32266
- raw = await import_node_fs58.promises.readFile(mcpPath, "utf8");
32529
+ raw = await import_node_fs59.promises.readFile(mcpPath, "utf8");
32267
32530
  } catch (err) {
32268
32531
  const e = err;
32269
32532
  if (e.code === "ENOENT") {
@@ -32305,7 +32568,7 @@ async function runEditorInstall(client, opts) {
32305
32568
  const mcpPath = client.mcpConfigPath();
32306
32569
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
32307
32570
  const hasRules = typeof client.rulesFileName === "string";
32308
- const rulesPath = hasRules ? import_node_path96.default.join(opts.projectDir, client.rulesFileName) : "";
32571
+ const rulesPath = hasRules ? import_node_path97.default.join(opts.projectDir, client.rulesFileName) : "";
32309
32572
  const mcp = await planMcp(client, mcpPath);
32310
32573
  if (mcp === null) return { exitCode: 1 };
32311
32574
  let existingRules = "";
@@ -32314,7 +32577,7 @@ async function runEditorInstall(client, opts) {
32314
32577
  let block = "";
32315
32578
  if (hasRules) {
32316
32579
  try {
32317
- existingRules = await import_node_fs58.promises.readFile(rulesPath, "utf8");
32580
+ existingRules = await import_node_fs59.promises.readFile(rulesPath, "utf8");
32318
32581
  } catch (err) {
32319
32582
  if (err.code !== "ENOENT") {
32320
32583
  console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
@@ -32348,11 +32611,11 @@ async function runEditorInstall(client, opts) {
32348
32611
  );
32349
32612
  return { exitCode: 0 };
32350
32613
  }
32351
- await import_node_fs58.promises.mkdir(import_node_path96.default.dirname(mcpPath), { recursive: true });
32352
- await import_node_fs58.promises.writeFile(mcpPath, mcp.text, "utf8");
32614
+ await import_node_fs59.promises.mkdir(import_node_path97.default.dirname(mcpPath), { recursive: true });
32615
+ await import_node_fs59.promises.writeFile(mcpPath, mcp.text, "utf8");
32353
32616
  if (hasRules) {
32354
- await import_node_fs58.promises.mkdir(import_node_path96.default.dirname(rulesPath), { recursive: true });
32355
- await import_node_fs58.promises.writeFile(rulesPath, newRules, "utf8");
32617
+ await import_node_fs59.promises.mkdir(import_node_path97.default.dirname(rulesPath), { recursive: true });
32618
+ await import_node_fs59.promises.writeFile(rulesPath, newRules, "utf8");
32356
32619
  }
32357
32620
  console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
32358
32621
  console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
@@ -32854,7 +33117,7 @@ function sleep(ms, signal) {
32854
33117
 
32855
33118
  // src/cli-verbs.ts
32856
33119
  init_cjs_shims();
32857
- var import_node_path97 = __toESM(require("path"), 1);
33120
+ var import_node_path98 = __toESM(require("path"), 1);
32858
33121
  async function resolveProjectEntry(opts) {
32859
33122
  const entries = await listProjects();
32860
33123
  if (opts.project) {
@@ -32864,7 +33127,7 @@ async function resolveProjectEntry(opts) {
32864
33127
  const cwd = opts.cwd ?? process.cwd();
32865
33128
  const resolvedCwd = await normalizeProjectPath(cwd);
32866
33129
  for (const entry2 of entries) {
32867
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path97.default.sep}`)) {
33130
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path98.default.sep}`)) {
32868
33131
  return entry2;
32869
33132
  }
32870
33133
  }
@@ -33441,7 +33704,7 @@ async function buildPatchSections(services, project) {
33441
33704
  }
33442
33705
  async function runInit(opts) {
33443
33706
  const written = [];
33444
- const stat = await import_node_fs59.promises.stat(opts.scanPath).catch(() => null);
33707
+ const stat = await import_node_fs60.promises.stat(opts.scanPath).catch(() => null);
33445
33708
  if (!stat || !stat.isDirectory()) {
33446
33709
  console.error(`neat init: ${opts.scanPath} is not a directory`);
33447
33710
  return { exitCode: 2, writtenFiles: written };
@@ -33450,13 +33713,13 @@ async function runInit(opts) {
33450
33713
  printDiscoveryReport(opts, services);
33451
33714
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
33452
33715
  const patch = renderPatch(sections);
33453
- const patchPath = import_node_path98.default.join(opts.scanPath, "neat.patch");
33716
+ const patchPath = import_node_path99.default.join(opts.scanPath, "neat.patch");
33454
33717
  if (opts.dryRun) {
33455
- await import_node_fs59.promises.writeFile(patchPath, patch, "utf8");
33718
+ await import_node_fs60.promises.writeFile(patchPath, patch, "utf8");
33456
33719
  written.push(patchPath);
33457
33720
  console.log(`dry-run: patch written to ${patchPath}`);
33458
- const gitignorePath = import_node_path98.default.join(opts.scanPath, ".gitignore");
33459
- const gitignoreExists = await import_node_fs59.promises.stat(gitignorePath).then(() => true).catch(() => false);
33721
+ const gitignorePath = import_node_path99.default.join(opts.scanPath, ".gitignore");
33722
+ const gitignoreExists = await import_node_fs60.promises.stat(gitignorePath).then(() => true).catch(() => false);
33460
33723
  const verb = gitignoreExists ? "append" : "create";
33461
33724
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
33462
33725
  console.log("rerun without --dry-run to register and snapshot.");
@@ -33467,9 +33730,9 @@ async function runInit(opts) {
33467
33730
  const graph = getGraph(graphKey);
33468
33731
  const projectPaths = pathsForProject(
33469
33732
  graphKey,
33470
- import_node_path98.default.join(opts.scanPath, "neat-out")
33733
+ import_node_path99.default.join(opts.scanPath, "neat-out")
33471
33734
  );
33472
- const errorsPath = import_node_path98.default.join(import_node_path98.default.dirname(opts.outPath), import_node_path98.default.basename(projectPaths.errorsPath));
33735
+ const errorsPath = import_node_path99.default.join(import_node_path99.default.dirname(opts.outPath), import_node_path99.default.basename(projectPaths.errorsPath));
33473
33736
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
33474
33737
  await saveGraphToDisk(graph, opts.outPath);
33475
33738
  written.push(opts.outPath);
@@ -33548,7 +33811,7 @@ async function runInit(opts) {
33548
33811
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
33549
33812
  }
33550
33813
  } else {
33551
- await import_node_fs59.promises.writeFile(patchPath, patch, "utf8");
33814
+ await import_node_fs60.promises.writeFile(patchPath, patch, "utf8");
33552
33815
  written.push(patchPath);
33553
33816
  }
33554
33817
  }
@@ -33589,9 +33852,9 @@ var CLAUDE_SKILL_CONFIG = {
33589
33852
  };
33590
33853
  function claudeConfigPath() {
33591
33854
  const override = process.env.NEAT_CLAUDE_CONFIG;
33592
- if (override && override.length > 0) return import_node_path98.default.resolve(override);
33855
+ if (override && override.length > 0) return import_node_path99.default.resolve(override);
33593
33856
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
33594
- return import_node_path98.default.join(home, ".claude.json");
33857
+ return import_node_path99.default.join(home, ".claude.json");
33595
33858
  }
33596
33859
  async function runSkill(opts) {
33597
33860
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -33603,7 +33866,7 @@ async function runSkill(opts) {
33603
33866
  const target = claudeConfigPath();
33604
33867
  let existing = {};
33605
33868
  try {
33606
- existing = JSON.parse(await import_node_fs59.promises.readFile(target, "utf8"));
33869
+ existing = JSON.parse(await import_node_fs60.promises.readFile(target, "utf8"));
33607
33870
  } catch (err) {
33608
33871
  if (err.code !== "ENOENT") {
33609
33872
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -33615,8 +33878,8 @@ async function runSkill(opts) {
33615
33878
  ...existing,
33616
33879
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
33617
33880
  };
33618
- await import_node_fs59.promises.mkdir(import_node_path98.default.dirname(target), { recursive: true });
33619
- await import_node_fs59.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
33881
+ await import_node_fs60.promises.mkdir(import_node_path99.default.dirname(target), { recursive: true });
33882
+ await import_node_fs60.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
33620
33883
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
33621
33884
  console.log("restart Claude Code to pick up the new MCP server.");
33622
33885
  console.log("");
@@ -33642,7 +33905,7 @@ async function runSkill(opts) {
33642
33905
  async function main() {
33643
33906
  const argv = process.argv.slice(2);
33644
33907
  const cmd0 = argv[0];
33645
- if (cmd0 === "-h" || cmd0 === "--help") {
33908
+ if (argv.some((a) => a === "-h" || a === "--help")) {
33646
33909
  usage5();
33647
33910
  process.exit(0);
33648
33911
  }
@@ -33737,12 +34000,12 @@ async function main() {
33737
34000
  console.error("neat init: --apply and --dry-run are mutually exclusive");
33738
34001
  process.exit(2);
33739
34002
  }
33740
- const scanPath = import_node_path98.default.resolve(target);
34003
+ const scanPath = import_node_path99.default.resolve(target);
33741
34004
  const projectExplicit = parsed.project !== null;
33742
- const projectName = projectExplicit ? project : import_node_path98.default.basename(scanPath);
34005
+ const projectName = projectExplicit ? project : import_node_path99.default.basename(scanPath);
33743
34006
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
33744
- const fallback = pathsForProject(projectKey, import_node_path98.default.join(scanPath, "neat-out")).snapshotPath;
33745
- const outPath = import_node_path98.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
34007
+ const fallback = pathsForProject(projectKey, import_node_path99.default.join(scanPath, "neat-out")).snapshotPath;
34008
+ const outPath = import_node_path99.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
33746
34009
  const result = await runInit({
33747
34010
  scanPath,
33748
34011
  outPath,
@@ -33763,21 +34026,21 @@ async function main() {
33763
34026
  usage5();
33764
34027
  process.exit(2);
33765
34028
  }
33766
- const scanPath = import_node_path98.default.resolve(target);
33767
- const stat = await import_node_fs59.promises.stat(scanPath).catch(() => null);
34029
+ const scanPath = import_node_path99.default.resolve(target);
34030
+ const stat = await import_node_fs60.promises.stat(scanPath).catch(() => null);
33768
34031
  if (!stat || !stat.isDirectory()) {
33769
34032
  console.error(`neat watch: ${scanPath} is not a directory`);
33770
34033
  process.exit(2);
33771
34034
  }
33772
- const projectPaths = pathsForProject(project, import_node_path98.default.join(scanPath, "neat-out"));
33773
- const outPath = import_node_path98.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
33774
- const errorsPath = import_node_path98.default.resolve(
33775
- process.env.NEAT_ERRORS_PATH ?? import_node_path98.default.join(import_node_path98.default.dirname(outPath), import_node_path98.default.basename(projectPaths.errorsPath))
34035
+ const projectPaths = pathsForProject(project, import_node_path99.default.join(scanPath, "neat-out"));
34036
+ const outPath = import_node_path99.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
34037
+ const errorsPath = import_node_path99.default.resolve(
34038
+ process.env.NEAT_ERRORS_PATH ?? import_node_path99.default.join(import_node_path99.default.dirname(outPath), import_node_path99.default.basename(projectPaths.errorsPath))
33776
34039
  );
33777
- const staleEventsPath = import_node_path98.default.resolve(
33778
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path98.default.join(import_node_path98.default.dirname(outPath), import_node_path98.default.basename(projectPaths.staleEventsPath))
34040
+ const staleEventsPath = import_node_path99.default.resolve(
34041
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path99.default.join(import_node_path99.default.dirname(outPath), import_node_path99.default.basename(projectPaths.staleEventsPath))
33779
34042
  );
33780
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path98.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
34043
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path99.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
33781
34044
  const handle = await startWatch(getGraph(project), {
33782
34045
  scanPath,
33783
34046
  outPath,
@@ -33786,7 +34049,7 @@ async function main() {
33786
34049
  project,
33787
34050
  // Resolve NEAT_HOME so a `neat watch` picks up connectors added to
33788
34051
  // ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
33789
- neatHome: process.env.NEAT_HOME ? import_node_path98.default.resolve(process.env.NEAT_HOME) : import_node_path98.default.join(import_node_os11.default.homedir(), ".neat"),
34052
+ neatHome: process.env.NEAT_HOME ? import_node_path99.default.resolve(process.env.NEAT_HOME) : import_node_path99.default.join(import_node_os11.default.homedir(), ".neat"),
33790
34053
  ...embeddingsCachePath ? { embeddingsCachePath } : {},
33791
34054
  host: process.env.HOST ?? "0.0.0.0",
33792
34055
  port: Number(process.env.PORT ?? 8080),
@@ -33968,11 +34231,11 @@ async function main() {
33968
34231
  process.exit(1);
33969
34232
  }
33970
34233
  async function tryOrchestrator(cmd, parsed) {
33971
- const scanPath = import_node_path98.default.resolve(cmd);
33972
- const stat = await import_node_fs59.promises.stat(scanPath).catch(() => null);
34234
+ const scanPath = import_node_path99.default.resolve(cmd);
34235
+ const stat = await import_node_fs60.promises.stat(scanPath).catch(() => null);
33973
34236
  if (!stat || !stat.isDirectory()) return null;
33974
34237
  const projectExplicit = parsed.project !== null;
33975
- const projectName = projectExplicit ? parsed.project : import_node_path98.default.basename(scanPath);
34238
+ const projectName = projectExplicit ? parsed.project : import_node_path99.default.basename(scanPath);
33976
34239
  const result = await runOrchestrator({
33977
34240
  scanPath,
33978
34241
  project: projectName,