@neat.is/core 0.10.1-dev.20260927 → 0.10.1

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 path100 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path100) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path100)) {
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 path100 = q === -1 ? v : v.slice(0, q);
419
+ if (path100.length > 0) return path100;
420
420
  }
421
421
  }
422
422
  return void 0;
@@ -789,6 +789,7 @@ __export(cli_exports, {
789
789
  QUERY_VERBS: () => QUERY_VERBS,
790
790
  UnknownProfileError: () => UnknownProfileError,
791
791
  commandPrefix: () => commandPrefix,
792
+ isLoopbackEndpoint: () => isLoopbackEndpoint,
792
793
  isNpxInvocation: () => isNpxInvocation,
793
794
  main: () => main,
794
795
  parseArgs: () => parseArgs,
@@ -805,9 +806,9 @@ __export(cli_exports, {
805
806
  });
806
807
  module.exports = __toCommonJS(cli_exports);
807
808
  init_cjs_shims();
808
- var import_node_path98 = __toESM(require("path"), 1);
809
+ var import_node_path100 = __toESM(require("path"), 1);
809
810
  var import_node_os11 = __toESM(require("os"), 1);
810
- var import_node_fs59 = require("fs");
811
+ var import_node_fs61 = require("fs");
811
812
 
812
813
  // src/banner.ts
813
814
  init_cjs_shims();
@@ -1490,19 +1491,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1490
1491
  function longestIncomingWalk(graph, start, maxDepth) {
1491
1492
  let best = { path: [start], edges: [] };
1492
1493
  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] };
1494
+ function step(node, path100, edges) {
1495
+ if (path100.length > best.path.length) {
1496
+ best = { path: [...path100], edges: [...edges] };
1496
1497
  }
1497
- if (path98.length - 1 >= maxDepth) return;
1498
+ if (path100.length - 1 >= maxDepth) return;
1498
1499
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1499
1500
  for (const [srcId, edge] of incoming) {
1500
1501
  if (visited.has(srcId)) continue;
1501
1502
  visited.add(srcId);
1502
- path98.push(srcId);
1503
+ path100.push(srcId);
1503
1504
  edges.push(edge);
1504
- step(srcId, path98, edges);
1505
- path98.pop();
1505
+ step(srcId, path100, edges);
1506
+ path100.pop();
1506
1507
  edges.pop();
1507
1508
  visited.delete(srcId);
1508
1509
  }
@@ -1717,20 +1718,20 @@ function dominantFailingCall(graph, serviceId17, visited) {
1717
1718
  return best;
1718
1719
  }
1719
1720
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1720
- const path98 = [originServiceId];
1721
+ const path100 = [originServiceId];
1721
1722
  const edges = [];
1722
1723
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1723
1724
  let current = originServiceId;
1724
1725
  for (let depth = 0; depth < maxDepth; depth++) {
1725
1726
  const hop = dominantFailingCall(graph, current, visited);
1726
1727
  if (!hop) break;
1727
- path98.push(hop.nextService);
1728
+ path100.push(hop.nextService);
1728
1729
  edges.push(hop.edge);
1729
1730
  visited.add(hop.nextService);
1730
1731
  current = hop.nextService;
1731
1732
  }
1732
1733
  if (edges.length === 0) return null;
1733
- return { path: path98, edges, culprit: current };
1734
+ return { path: path100, edges, culprit: current };
1734
1735
  }
1735
1736
  function isStaleCallEdge(e) {
1736
1737
  return e.type === import_types2.EdgeType.CALLS && e.provenance === import_types2.Provenance.STALE;
@@ -1766,26 +1767,26 @@ function dominantStaleCall(graph, serviceId17, visited) {
1766
1767
  return best;
1767
1768
  }
1768
1769
  function followStaleCallChain(graph, originServiceId, maxDepth) {
1769
- const path98 = [originServiceId];
1770
+ const path100 = [originServiceId];
1770
1771
  const edges = [];
1771
1772
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1772
1773
  let current = originServiceId;
1773
1774
  for (let depth = 0; depth < maxDepth; depth++) {
1774
1775
  const hop = dominantStaleCall(graph, current, visited);
1775
1776
  if (!hop) break;
1776
- path98.push(hop.nextService);
1777
+ path100.push(hop.nextService);
1777
1778
  edges.push(hop.edge);
1778
1779
  visited.add(hop.nextService);
1779
1780
  current = hop.nextService;
1780
1781
  }
1781
1782
  if (edges.length === 0) return null;
1782
- return { path: path98, edges, culprit: current };
1783
+ return { path: path100, edges, culprit: current };
1783
1784
  }
1784
1785
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1785
1786
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1786
1787
  if (!chain) return null;
1787
1788
  const culprit = chain.culprit;
1788
- const path98 = [...chain.path];
1789
+ const path100 = [...chain.path];
1789
1790
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1790
1791
  const baseConfidence = confidenceFromMix(chain.edges);
1791
1792
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1793,14 +1794,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1793
1794
  if (loc) {
1794
1795
  let rootCauseNode = culprit;
1795
1796
  if (loc.fileNode) {
1796
- path98.push(loc.fileNode);
1797
+ path100.push(loc.fileNode);
1797
1798
  edgeProvenances.push(import_types2.Provenance.OBSERVED);
1798
1799
  rootCauseNode = loc.fileNode;
1799
1800
  }
1800
1801
  return import_types2.RootCauseResultSchema.parse({
1801
1802
  rootCauseNode,
1802
1803
  rootCauseReason: loc.rootCauseReason,
1803
- traversalPath: path98,
1804
+ traversalPath: path100,
1804
1805
  edgeProvenances,
1805
1806
  confidence,
1806
1807
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1812,7 +1813,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1812
1813
  return import_types2.RootCauseResultSchema.parse({
1813
1814
  rootCauseNode: culprit,
1814
1815
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1815
- traversalPath: path98,
1816
+ traversalPath: path100,
1816
1817
  edgeProvenances,
1817
1818
  confidence,
1818
1819
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2465,10 +2466,10 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
2465
2466
  traversalPath = staleChain.path;
2466
2467
  edgeProvenances = staleChain.edges.map((e) => e.provenance);
2467
2468
  } 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);
2469
+ const path100 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
2470
+ if (path100) {
2471
+ traversalPath = path100.nodes;
2472
+ edgeProvenances = path100.edges.map((e) => e.provenance);
2472
2473
  } else {
2473
2474
  traversalPath = [errorNodeId, top.node];
2474
2475
  edgeProvenances = [top.provenance ?? import_types2.Provenance.OBSERVED];
@@ -3919,8 +3920,8 @@ function chiRoutesFromSource(source, parser) {
3919
3920
  chiWalk(tree.rootNode, "", out);
3920
3921
  return out;
3921
3922
  }
3922
- function stripChiRegex(path98) {
3923
- return path98.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3923
+ function stripChiRegex(path100) {
3924
+ return path100.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3924
3925
  }
3925
3926
  function chiWalk(node, prefix, out) {
3926
3927
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -4592,9 +4593,9 @@ function rubyRocketRoute(args) {
4592
4593
  if (!pair || pair.type !== "pair") continue;
4593
4594
  const k = pair.childForFieldName("key");
4594
4595
  if (k?.type !== "string") continue;
4595
- const path98 = rubyLiteral(k);
4596
- if (path98 === null) continue;
4597
- return { path: path98, target: rubyLiteral(pair.childForFieldName("value")) };
4596
+ const path100 = rubyLiteral(k);
4597
+ if (path100 === null) continue;
4598
+ return { path: path100, target: rubyLiteral(pair.childForFieldName("value")) };
4598
4599
  }
4599
4600
  return null;
4600
4601
  }
@@ -12002,13 +12003,16 @@ function objectKeys(obj) {
12002
12003
  var MODULAR_WRITES = /* @__PURE__ */ new Set(["addDoc", "setDoc", "updateDoc"]);
12003
12004
  var NAMESPACED_WRITES = /* @__PURE__ */ new Set(["add", "set", "update"]);
12004
12005
  var READ_CLAUSES = /* @__PURE__ */ new Set(["where", "orderBy"]);
12005
- function firestoreEndpointsFromFile(file, serviceDir) {
12006
+ function firestoreEndpointsFromFile(file, serviceDir, imported) {
12006
12007
  const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(file.content);
12007
12008
  const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
12008
- if (!hasClient && !hasAdmin) return [];
12009
- const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
12009
+ const importedNames = imported?.names ?? /* @__PURE__ */ new Set();
12010
+ if (!hasClient && !hasAdmin && importedNames.size === 0) return [];
12011
+ const ownSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
12012
+ const fileSdk = !hasClient && !hasAdmin ? imported?.sdk ?? null : ownSdk;
12010
12013
  const tree = parseSource3(parserForExt2(import_node_path45.default.extname(file.path)), file.content);
12011
12014
  const clientVars = firestoreClientVars(tree.rootNode);
12015
+ for (const n of importedNames) clientVars.add(n);
12012
12016
  const collLine = /* @__PURE__ */ new Map();
12013
12017
  const writes = /* @__PURE__ */ new Map();
12014
12018
  const reads = /* @__PURE__ */ new Map();
@@ -12119,6 +12123,99 @@ function firestoreEndpointsFromFile(file, serviceDir) {
12119
12123
  }
12120
12124
  return out;
12121
12125
  }
12126
+ function exportedFirestoreClients(root) {
12127
+ const out = /* @__PURE__ */ new Set();
12128
+ const collect = (node) => {
12129
+ if (node.type === "variable_declarator") {
12130
+ const name = node.childForFieldName("name");
12131
+ let value = node.childForFieldName("value");
12132
+ if (value?.type === "await_expression") value = namedChildren(value)[0] ?? null;
12133
+ if (name?.type === "identifier" && isFirestoreClientFactory(value)) out.add(name.text);
12134
+ }
12135
+ for (const c of namedChildren(node)) collect(c);
12136
+ };
12137
+ const walk11 = (node) => {
12138
+ if (node.type === "export_statement") collect(node);
12139
+ else for (const c of namedChildren(node)) walk11(c);
12140
+ };
12141
+ walk11(root);
12142
+ return out;
12143
+ }
12144
+ function namedImportBindings(root) {
12145
+ const out = [];
12146
+ const walk11 = (node) => {
12147
+ if (node.type === "import_statement") {
12148
+ const src = node.childForFieldName("source");
12149
+ const specifier = src ? src.text.slice(1, -1) : "";
12150
+ if (specifier.length > 0) {
12151
+ const stack = [node];
12152
+ while (stack.length > 0) {
12153
+ const n = stack.pop();
12154
+ if (n.type === "import_specifier") {
12155
+ const nameNode = n.childForFieldName("name");
12156
+ const aliasNode = n.childForFieldName("alias");
12157
+ const importedName = nameNode?.text;
12158
+ if (importedName) {
12159
+ out.push({ local: aliasNode?.text ?? importedName, imported: importedName, specifier });
12160
+ }
12161
+ continue;
12162
+ }
12163
+ for (const c of namedChildren(n)) stack.push(c);
12164
+ }
12165
+ }
12166
+ }
12167
+ for (const c of namedChildren(node)) walk11(c);
12168
+ };
12169
+ walk11(root);
12170
+ return out;
12171
+ }
12172
+ function sdkOf(content) {
12173
+ const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(content);
12174
+ const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(content);
12175
+ return hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
12176
+ }
12177
+ function parseOrNull(file) {
12178
+ const ext = import_node_path45.default.extname(file.path);
12179
+ if (!(ext in GRAMMAR_BY_EXT)) return null;
12180
+ try {
12181
+ return parseSource3(parserForExt2(ext), file.content).rootNode;
12182
+ } catch {
12183
+ return null;
12184
+ }
12185
+ }
12186
+ async function firestoreCrossFileEndpoints(files, serviceDir) {
12187
+ const registry = /* @__PURE__ */ new Map();
12188
+ for (const f of files) {
12189
+ if (sdkOf(f.content) === null && !FIRESTORE_CLIENT_IMPORT_RE.test(f.content)) continue;
12190
+ const root = parseOrNull(f);
12191
+ if (!root) continue;
12192
+ const names = exportedFirestoreClients(root);
12193
+ if (names.size === 0) continue;
12194
+ registry.set(toPosix(import_node_path45.default.relative(serviceDir, f.path)), { names, sdk: sdkOf(f.content) });
12195
+ }
12196
+ if (registry.size === 0) return [];
12197
+ const out = [];
12198
+ for (const f of files) {
12199
+ const root = parseOrNull(f);
12200
+ if (!root) continue;
12201
+ if (firestoreClientVars(root).size > 0) continue;
12202
+ const bindings = namedImportBindings(root);
12203
+ if (bindings.length === 0) continue;
12204
+ const names = /* @__PURE__ */ new Set();
12205
+ let sdk = null;
12206
+ for (const b of bindings) {
12207
+ const rel = await resolveJsImport(b.specifier, import_node_path45.default.dirname(f.path), serviceDir, null);
12208
+ if (!rel) continue;
12209
+ const entry2 = registry.get(rel);
12210
+ if (!entry2 || !entry2.names.has(b.imported)) continue;
12211
+ names.add(b.local);
12212
+ sdk ??= entry2.sdk;
12213
+ }
12214
+ if (names.size === 0) continue;
12215
+ out.push(...firestoreEndpointsFromFile(f, serviceDir, { names, sdk }));
12216
+ }
12217
+ return out;
12218
+ }
12122
12219
 
12123
12220
  // src/extract/calls/mongoose.ts
12124
12221
  init_cjs_shims();
@@ -14928,6 +15025,7 @@ async function addExternalEndpointEdges(graph, services) {
14928
15025
  recordExtractionError("python socket call extraction", file.path, err);
14929
15026
  }
14930
15027
  }
15028
+ endpoints.push(...await firestoreCrossFileEndpoints(maskedFiles, service.dir));
14931
15029
  endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
14932
15030
  endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
14933
15031
  endpoints.push(...await prismaColumnEndpoints(service.dir));
@@ -16223,6 +16321,9 @@ function stripComments(src) {
16223
16321
  function segmentsFromMatchPath(pathToken) {
16224
16322
  return pathToken.split("/").filter((s) => s.length > 0);
16225
16323
  }
16324
+ function isRecursiveWildcard(seg) {
16325
+ return /^\{[^}]*=\s*\*\*\}$/.test(seg);
16326
+ }
16226
16327
  function collectionKeyFromSegments(segments) {
16227
16328
  let segs = segments;
16228
16329
  if (segs.length >= 3 && segs[0] === "databases" && /^\{.*\}$/.test(segs[1]) && segs[2] === "documents") {
@@ -16235,10 +16336,11 @@ function collectionKeyFromName(name) {
16235
16336
  }
16236
16337
  function fieldsFromCondition(cond) {
16237
16338
  const out = [];
16238
- const callRe = /\b(?:hasAll|hasOnly|hasAny)\s*\(\s*\[([^\]]*)\]\s*\)/g;
16339
+ const callRe = /(!\s*)?\b(?:hasAll|hasOnly|hasAny)\s*\(\s*\[([^\]]*)\]\s*\)/g;
16239
16340
  let m;
16240
16341
  while ((m = callRe.exec(cond)) !== null) {
16241
- const inner = m[1];
16342
+ if (m[1] !== void 0 || isNegatedAt(cond, m.index)) continue;
16343
+ const inner = m[2];
16242
16344
  const litRe = /'([^']*)'|"([^"]*)"/g;
16243
16345
  let lm;
16244
16346
  while ((lm = litRe.exec(inner)) !== null) {
@@ -16248,6 +16350,28 @@ function fieldsFromCondition(cond) {
16248
16350
  }
16249
16351
  return out;
16250
16352
  }
16353
+ function isNegatedAt(cond, idx) {
16354
+ let i = idx - 1;
16355
+ while (i >= 0) {
16356
+ const ch = cond[i];
16357
+ if (ch === ")") {
16358
+ let depth = 1;
16359
+ i--;
16360
+ while (i >= 0 && depth > 0) {
16361
+ if (cond[i] === ")") depth++;
16362
+ else if (cond[i] === "(") depth--;
16363
+ i--;
16364
+ }
16365
+ continue;
16366
+ }
16367
+ if (/[A-Za-z0-9_$.\]['"]/.test(ch) || ch === " ") {
16368
+ i--;
16369
+ continue;
16370
+ }
16371
+ return ch === "!";
16372
+ }
16373
+ return false;
16374
+ }
16251
16375
  function parseFirestoreRules(content) {
16252
16376
  const src = stripComments(content);
16253
16377
  const tokenRe = /match\s+((?:\/(?:\{[^}]*\}|[^\s/{}]+))+)\s*\{|allow\b([^;{}]*);|(\{)|(\})/g;
@@ -16260,7 +16384,8 @@ function parseFirestoreRules(content) {
16260
16384
  while ((m = tokenRe.exec(src)) !== null) {
16261
16385
  if (m[1] !== void 0) {
16262
16386
  depth++;
16263
- matchStack.push({ depth, segs: segmentsFromMatchPath(m[1]) });
16387
+ const segs = segmentsFromMatchPath(m[1]);
16388
+ matchStack.push({ depth, segs, recursive: segs.some(isRecursiveWildcard) });
16264
16389
  continue;
16265
16390
  }
16266
16391
  if (m[2] !== void 0) {
@@ -16271,6 +16396,10 @@ function parseFirestoreRules(content) {
16271
16396
  if (!methods.some((mm) => WRITE_METHODS.has(mm))) continue;
16272
16397
  const key = currentKey();
16273
16398
  if (key.length === 0) continue;
16399
+ if (matchStack.some((f) => f.recursive)) {
16400
+ indeterminate.add(key);
16401
+ continue;
16402
+ }
16274
16403
  const cond = colon === -1 ? "" : body.slice(colon + 1);
16275
16404
  const fields = fieldsFromCondition(cond);
16276
16405
  if (fields.length > 0) {
@@ -20325,10 +20454,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
20325
20454
  // src/connectors/supabase/map.ts
20326
20455
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
20327
20456
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
20328
- function targetFromRestPath(path98) {
20329
- const rpcMatch = REST_RPC_PATH_RE.exec(path98);
20457
+ function targetFromRestPath(path100) {
20458
+ const rpcMatch = REST_RPC_PATH_RE.exec(path100);
20330
20459
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
20331
- const tableMatch = REST_TABLE_PATH_RE.exec(path98);
20460
+ const tableMatch = REST_TABLE_PATH_RE.exec(path100);
20332
20461
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
20333
20462
  return null;
20334
20463
  }
@@ -20932,9 +21061,9 @@ function parseFirebaseTargetName(targetName) {
20932
21061
  const secondSep = rest.indexOf(FIELD_SEP);
20933
21062
  if (secondSep === -1) return null;
20934
21063
  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 };
21064
+ const path100 = rest.slice(secondSep + 1);
21065
+ if (!resourceName || !method || !path100) return null;
21066
+ return { resourceName, method, path: path100 };
20938
21067
  }
20939
21068
  function resourceNameFor(type, labels) {
20940
21069
  if (!labels) return null;
@@ -20972,14 +21101,14 @@ function mapLogEntryToSignal(entry2) {
20972
21101
  if (!req) return null;
20973
21102
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
20974
21103
  const method = req.requestMethod.toUpperCase();
20975
- const path98 = pathFromRequestUrl(req.requestUrl);
20976
- if (path98 === null) return null;
21104
+ const path100 = pathFromRequestUrl(req.requestUrl);
21105
+ if (path100 === null) return null;
20977
21106
  const timestamp = entry2.timestamp;
20978
21107
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
20979
21108
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
20980
21109
  return {
20981
21110
  targetKind: resourceType,
20982
- targetName: packFirebaseTargetName({ resourceName, method, path: path98 }),
21111
+ targetName: packFirebaseTargetName({ resourceName, method, path: path100 }),
20983
21112
  callCount: 1,
20984
21113
  errorCount: isError ? 1 : 0,
20985
21114
  lastObservedIso: timestamp
@@ -21004,9 +21133,9 @@ var import_types73 = require("@neat.is/types");
21004
21133
  function normalizeName(name) {
21005
21134
  return name.toLowerCase().replace(/[^a-z0-9]/g, "");
21006
21135
  }
21007
- function inferServiceName(graph, resourceName, method, path98) {
21136
+ function inferServiceName(graph, resourceName, method, path100) {
21008
21137
  const wantedMethod = method.toUpperCase();
21009
- const wantedPath = normalizePathTemplate(path98);
21138
+ const wantedPath = normalizePathTemplate(path100);
21010
21139
  const services = [];
21011
21140
  const owners = /* @__PURE__ */ new Set();
21012
21141
  graph.forEachNode((_id, attrs) => {
@@ -21223,7 +21352,7 @@ function mapEventToSignal(event) {
21223
21352
  if (Number.isNaN(observedAt.getTime())) return null;
21224
21353
  const statusCode = metadata?.statusCode;
21225
21354
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
21226
- const path98 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
21355
+ const path100 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
21227
21356
  return {
21228
21357
  targetKind: CLOUDFLARE_TARGET_KIND,
21229
21358
  targetName: scriptName,
@@ -21231,7 +21360,7 @@ function mapEventToSignal(event) {
21231
21360
  errorCount: isError ? 1 : 0,
21232
21361
  lastObservedIso: observedAt.toISOString(),
21233
21362
  method,
21234
- ...path98 ? { path: path98 } : {},
21363
+ ...path100 ? { path: path100 } : {},
21235
21364
  ...typeof statusCode === "number" ? { statusCode } : {},
21236
21365
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
21237
21366
  };
@@ -21277,8 +21406,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
21277
21406
  });
21278
21407
  return found;
21279
21408
  }
21280
- function findMatchingRouteNode(graph, serviceName, method, path98) {
21281
- const normalizedPath = normalizePathTemplate(path98);
21409
+ function findMatchingRouteNode(graph, serviceName, method, path100) {
21410
+ const normalizedPath = normalizePathTemplate(path100);
21282
21411
  let found = null;
21283
21412
  graph.forEachNode((id, attrs) => {
21284
21413
  if (found) return;
@@ -21295,10 +21424,10 @@ function createCloudflareResolveTarget(config, graph) {
21295
21424
  return (signal) => {
21296
21425
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
21297
21426
  const scriptName = signal.targetName;
21298
- const { method, path: path98 } = signal;
21427
+ const { method, path: path100 } = signal;
21299
21428
  const resolveRouteGrain = (serviceName, wholeFileId) => {
21300
- if (!method || !path98) return wholeFileId;
21301
- return findMatchingRouteNode(graph, serviceName, method, path98) ?? wholeFileId;
21429
+ if (!method || !path100) return wholeFileId;
21430
+ return findMatchingRouteNode(graph, serviceName, method, path100) ?? wholeFileId;
21302
21431
  };
21303
21432
  const mapping = config.workers?.[scriptName];
21304
21433
  if (mapping) {
@@ -21650,9 +21779,9 @@ function parseCloudRunTargetName(targetName) {
21650
21779
  const secondSep = rest.indexOf(FIELD_SEP2);
21651
21780
  if (secondSep === -1) return null;
21652
21781
  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 };
21782
+ const path100 = rest.slice(secondSep + 1);
21783
+ if (!serviceName || !method || !path100) return null;
21784
+ return { serviceName, method, path: path100 };
21656
21785
  }
21657
21786
 
21658
21787
  // src/connectors/cloud-run/map.ts
@@ -21681,14 +21810,14 @@ function mapLogEntryToSignal2(entry2) {
21681
21810
  if (!req) return null;
21682
21811
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
21683
21812
  const method = req.requestMethod.toUpperCase();
21684
- const path98 = pathFromRequestUrl2(req.requestUrl);
21685
- if (path98 === null) return null;
21813
+ const path100 = pathFromRequestUrl2(req.requestUrl);
21814
+ if (path100 === null) return null;
21686
21815
  const timestamp = entry2.timestamp;
21687
21816
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
21688
21817
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
21689
21818
  return {
21690
21819
  targetKind: CLOUD_RUN_TARGET_KIND,
21691
- targetName: packCloudRunTargetName({ serviceName, method, path: path98 }),
21820
+ targetName: packCloudRunTargetName({ serviceName, method, path: path100 }),
21692
21821
  callCount: 1,
21693
21822
  errorCount: isError ? 1 : 0,
21694
21823
  lastObservedIso: timestamp
@@ -21727,14 +21856,14 @@ function createCloudRunResolveTarget(graph, config) {
21727
21856
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
21728
21857
  const identity = parseCloudRunTargetName(signal.targetName);
21729
21858
  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);
21859
+ const { serviceName: gcpServiceName, method, path: path100 } = identity;
21860
+ const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path100) ?? void 0 : void 0);
21732
21861
  if (mappedService) {
21733
21862
  const routeNodeId = findMatchingRouteNode2(
21734
21863
  graph,
21735
21864
  mappedService,
21736
21865
  method,
21737
- normalizePathTemplate(path98)
21866
+ normalizePathTemplate(path100)
21738
21867
  );
21739
21868
  if (routeNodeId) {
21740
21869
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types84.EdgeType.CALLS };
@@ -21870,9 +21999,9 @@ function parseGcpLbTargetName(targetName) {
21870
21999
  const secondSep = rest.indexOf(FIELD_SEP3);
21871
22000
  if (secondSep === -1) return null;
21872
22001
  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 };
22002
+ const path100 = rest.slice(secondSep + 1);
22003
+ if (!backendServiceName || !method || !path100) return null;
22004
+ return { backendServiceName, method, path: path100 };
21876
22005
  }
21877
22006
 
21878
22007
  // src/connectors/gcp-lb/map.ts
@@ -21901,14 +22030,14 @@ function mapLogEntryToSignal3(entry2) {
21901
22030
  if (!req) return null;
21902
22031
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
21903
22032
  const method = req.requestMethod.toUpperCase();
21904
- const path98 = pathFromRequestUrl3(req.requestUrl);
21905
- if (path98 === null) return null;
22033
+ const path100 = pathFromRequestUrl3(req.requestUrl);
22034
+ if (path100 === null) return null;
21906
22035
  const timestamp = entry2.timestamp;
21907
22036
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
21908
22037
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD5;
21909
22038
  return {
21910
22039
  targetKind: GCP_LB_TARGET_KIND,
21911
- targetName: packGcpLbTargetName({ backendServiceName, method, path: path98 }),
22040
+ targetName: packGcpLbTargetName({ backendServiceName, method, path: path100 }),
21912
22041
  callCount: 1,
21913
22042
  errorCount: isError ? 1 : 0,
21914
22043
  lastObservedIso: timestamp
@@ -21947,14 +22076,14 @@ function createGcpLbResolveTarget(graph, config) {
21947
22076
  if (signal.targetKind !== GCP_LB_TARGET_KIND) return null;
21948
22077
  const identity = parseGcpLbTargetName(signal.targetName);
21949
22078
  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);
22079
+ const { backendServiceName, method, path: path100 } = identity;
22080
+ const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path100) ?? void 0 : void 0);
21952
22081
  if (mappedService) {
21953
22082
  const routeNodeId = findMatchingRouteNode3(
21954
22083
  graph,
21955
22084
  mappedService,
21956
22085
  method,
21957
- normalizePathTemplate(path98)
22086
+ normalizePathTemplate(path100)
21958
22087
  );
21959
22088
  if (routeNodeId) {
21960
22089
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types88.EdgeType.CALLS };
@@ -24346,9 +24475,9 @@ function makeK8sFetchImpl(transport) {
24346
24475
  req.end();
24347
24476
  }));
24348
24477
  }
24349
- async function listResource(transport, namespace, path98, opts = {}) {
24478
+ async function listResource(transport, namespace, path100, opts = {}) {
24350
24479
  const base = opts.apiUrl ?? transport.server;
24351
- const url = `${base.replace(/\/$/, "")}${path98}`;
24480
+ const url = `${base.replace(/\/$/, "")}${path100}`;
24352
24481
  const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
24353
24482
  const res = await junctionFetch(
24354
24483
  url,
@@ -24358,7 +24487,7 @@ async function listResource(transport, namespace, path98, opts = {}) {
24358
24487
  { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
24359
24488
  );
24360
24489
  if (!res.ok) {
24361
- throw new Error(`kubernetes ${path98} failed: ${res.status} ${res.statusText}`);
24490
+ throw new Error(`kubernetes ${path100} failed: ${res.status} ${res.statusText}`);
24362
24491
  }
24363
24492
  const json = await res.json();
24364
24493
  return Array.isArray(json.items) ? json.items : [];
@@ -29249,7 +29378,8 @@ async function runOrchestrator(opts) {
29249
29378
  } catch (err) {
29250
29379
  if (!(err instanceof ProjectNameCollisionError)) throw err;
29251
29380
  console.error(`neat: ${err.message}`);
29252
- console.error("pass --project <other-name> to register under a different name.");
29381
+ console.error(`neat: run \`neat <path> --project <other-name>\` to register it under a different name,`);
29382
+ console.error(`neat: or \`neat uninstall ${opts.project}\` if the old registration is stale.`);
29253
29383
  result.exitCode = 1;
29254
29384
  return result;
29255
29385
  }
@@ -29828,12 +29958,12 @@ async function runConnectorCommand(rawArgs, deps = {}) {
29828
29958
 
29829
29959
  // src/hosted-connect-cli.ts
29830
29960
  init_cjs_shims();
29831
- var import_node_child_process5 = require("child_process");
29961
+ var import_node_child_process6 = require("child_process");
29832
29962
 
29833
29963
  // src/login-sso.ts
29834
29964
  init_cjs_shims();
29835
29965
  var import_node_http2 = require("http");
29836
- var import_node_child_process4 = require("child_process");
29966
+ var import_node_child_process5 = require("child_process");
29837
29967
  var import_node_crypto7 = require("crypto");
29838
29968
 
29839
29969
  // src/profiles.ts
@@ -30046,8 +30176,200 @@ async function clearActiveProfile(home = neatHome3()) {
30046
30176
  });
30047
30177
  }
30048
30178
 
30179
+ // src/daemon-probe.ts
30180
+ init_cjs_shims();
30181
+ var PROBE_ATTEMPT_TIMEOUT_MS = 3e4;
30182
+ var PROBE_TOTAL_BUDGET_MS = 12e4;
30183
+ var PROBE_RETRY_PAUSE_MS = 2e3;
30184
+ function isTimeoutError(err) {
30185
+ const name = err?.name;
30186
+ return name === "TimeoutError" || name === "AbortError";
30187
+ }
30188
+ async function probeOnce(fetchImpl, root, token, timeoutMs) {
30189
+ let res;
30190
+ try {
30191
+ res = await fetchImpl(`${root}/health`, {
30192
+ headers: { authorization: `Bearer ${token}` },
30193
+ signal: AbortSignal.timeout(timeoutMs)
30194
+ });
30195
+ } catch (err) {
30196
+ if (isTimeoutError(err)) return { kind: "timeout" };
30197
+ return { kind: "unreachable", detail: err.message };
30198
+ }
30199
+ if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
30200
+ if (!res.ok) return { kind: "not-neat", status: res.status };
30201
+ const contentType = res.headers.get("content-type") ?? "";
30202
+ if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
30203
+ return { kind: "ok" };
30204
+ }
30205
+ async function probeDaemon(fetchImpl, endpoint2, token, hooks) {
30206
+ const root = endpoint2.replace(/\/$/, "");
30207
+ const deadline = hooks.now() + PROBE_TOTAL_BUDGET_MS;
30208
+ let warned = false;
30209
+ for (; ; ) {
30210
+ const result = await probeOnce(fetchImpl, root, token, PROBE_ATTEMPT_TIMEOUT_MS);
30211
+ if (result.kind !== "timeout") return result;
30212
+ if (!warned) {
30213
+ hooks.onWaiting();
30214
+ warned = true;
30215
+ }
30216
+ if (hooks.now() >= deadline) {
30217
+ return { kind: "unreachable", detail: `no response after ${Math.round(PROBE_TOTAL_BUDGET_MS / 1e3)}s` };
30218
+ }
30219
+ await hooks.sleep(PROBE_RETRY_PAUSE_MS);
30220
+ }
30221
+ }
30222
+
30223
+ // src/login-connect.ts
30224
+ init_cjs_shims();
30225
+ var import_node_child_process4 = require("child_process");
30226
+ var import_node_util = require("util");
30227
+ var import_node_fs54 = require("fs");
30228
+ var import_node_path92 = __toESM(require("path"), 1);
30229
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process4.execFile);
30230
+ async function hasLocalSnapshot(cwd) {
30231
+ try {
30232
+ await import_node_fs54.promises.access(import_node_path92.default.join(cwd, "neat-out", "graph.json"));
30233
+ return true;
30234
+ } catch {
30235
+ return false;
30236
+ }
30237
+ }
30238
+ var POLL_INTERVAL_MS = 3e3;
30239
+ var POLL_BUDGET_MS = 10 * 6e4;
30240
+ var CP_TIMEOUT_MS = 1e4;
30241
+ async function detectGitHubRepo(cwd) {
30242
+ const read = async (args) => {
30243
+ try {
30244
+ const { stdout } = await execFileAsync("git", args, { cwd, timeout: 5e3 });
30245
+ return stdout.trim();
30246
+ } catch {
30247
+ return null;
30248
+ }
30249
+ };
30250
+ let url = await read(["remote", "get-url", "origin"]);
30251
+ if (!url) {
30252
+ const names = await read(["remote"]);
30253
+ const first = names?.split(/\r?\n/).find((l) => l.trim().length > 0)?.trim();
30254
+ if (!first) return null;
30255
+ url = await read(["remote", "get-url", first]);
30256
+ }
30257
+ if (!url) return null;
30258
+ return parseGitHubRemote(url);
30259
+ }
30260
+ function parseGitHubRemote(url) {
30261
+ const trimmed = url.trim().replace(/\.git$/, "");
30262
+ const m = trimmed.match(/^https?:\/\/(?:[^@/]+@)?github\.com\/([^/]+)\/([^/]+)$/) ?? trimmed.match(/^git@github\.com:([^/]+)\/([^/]+)$/) ?? trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+)$/);
30263
+ if (!m) return null;
30264
+ const owner = m[1];
30265
+ const name = m[2];
30266
+ if (!owner || !name) return null;
30267
+ return { owner, name };
30268
+ }
30269
+ async function cpGetJson(fetchImpl, cpUrl, path100, accessToken) {
30270
+ try {
30271
+ const res = await fetchImpl(`${cpUrl}${path100}`, {
30272
+ headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
30273
+ signal: AbortSignal.timeout(CP_TIMEOUT_MS)
30274
+ });
30275
+ if (!res.ok) return { ok: false };
30276
+ return { ok: true, body: await res.json() };
30277
+ } catch {
30278
+ return { ok: false };
30279
+ }
30280
+ }
30281
+ async function listBoundRepos(fetchImpl, cpUrl, accessToken, projectId) {
30282
+ const res = await cpGetJson(
30283
+ fetchImpl,
30284
+ cpUrl,
30285
+ `/me/projects/${encodeURIComponent(projectId)}/repos`,
30286
+ accessToken
30287
+ );
30288
+ if (!res.ok || !Array.isArray(res.body)) return null;
30289
+ return res.body;
30290
+ }
30291
+ async function waitForFirstProject(fetchImpl, cpUrl, accessToken, deps) {
30292
+ const pollMs = deps.pollMs ?? POLL_INTERVAL_MS;
30293
+ const deadline = deps.now() + (deps.budgetMs ?? POLL_BUDGET_MS);
30294
+ for (; ; ) {
30295
+ if (deps.signal?.aborted) return { kind: "aborted" };
30296
+ const me = await cpGetJson(fetchImpl, cpUrl, "/me", accessToken);
30297
+ if (me.ok) {
30298
+ const running = (me.body.projects ?? []).find((p) => p.status === "running");
30299
+ if (running) return { kind: "project", project: running };
30300
+ }
30301
+ if (deps.now() >= deadline) return { kind: "timeout" };
30302
+ await deps.sleep(pollMs);
30303
+ if (deps.signal?.aborted) return { kind: "aborted" };
30304
+ }
30305
+ }
30306
+ function bindUrl(webUrl, projectId) {
30307
+ return `${webUrl}/config/repos?project=${encodeURIComponent(projectId)}`;
30308
+ }
30309
+ function onboardingUrl(webUrl) {
30310
+ return `${webUrl}/onboarding`;
30311
+ }
30312
+ async function runPostLoginConnect(input) {
30313
+ const d = input.deps;
30314
+ const out = d.out ?? ((line) => console.log(line));
30315
+ const fetchImpl = d.fetchImpl ?? fetch;
30316
+ const cwd = d.cwd ?? process.cwd();
30317
+ const detect6 = d.detectRepo ?? detectGitHubRepo;
30318
+ const readLine = d.readLine;
30319
+ const repo = await detect6(cwd);
30320
+ out("");
30321
+ if (!repo) {
30322
+ out("This directory has no GitHub remote, so it can't be bound to the project.");
30323
+ out(` Push its graph instead: neat sync --to ${input.project.name}`);
30324
+ out(` Manage projects and repos: ${input.webUrl}`);
30325
+ return;
30326
+ }
30327
+ const slug = `${repo.owner}/${repo.name}`;
30328
+ const bound = await listBoundRepos(fetchImpl, input.cpUrl, input.accessToken, input.project.id);
30329
+ const match = bound?.find((r) => r.owner === repo.owner && r.name === repo.name);
30330
+ if (match) {
30331
+ const state = match.syncStatus ? ` (${match.syncStatus})` : "";
30332
+ out(`${slug} is bound to ${input.project.name}${state} \u2014 hosted NEAT extracts it for you.`);
30333
+ out(` See the graph: ${input.webUrl}`);
30334
+ return;
30335
+ }
30336
+ out(`${slug} isn't part of ${input.project.name} yet \u2014 logging in connected your account, not this repo.`);
30337
+ out("");
30338
+ const installed = input.me?.github?.installed;
30339
+ const bindLine = installed === false ? "1) Install the GitHub App and bind this repo \u2014 hosted NEAT keeps it in sync" : installed === true ? "1) Bind this repo \u2014 hosted NEAT clones and extracts it, and keeps it in sync" : "1) Bind this repo through the console \u2014 hosted NEAT keeps it in sync";
30340
+ out(bindLine);
30341
+ out(`2) Push the graph on this machine now \u2014 a snapshot, not a subscription`);
30342
+ out("3) Neither, for now");
30343
+ const answer = readLine ? (await readLine("Choose 1, 2 or 3 (Enter skips): "))?.trim() : void 0;
30344
+ if (answer === "1") {
30345
+ const url = bindUrl(input.webUrl, input.project.id);
30346
+ const opened = (d.openBrowser ?? (() => false))(url);
30347
+ out("");
30348
+ out(opened ? `Opened ${url}` : `Open this to bind the repo:
30349
+ ${url}`);
30350
+ return;
30351
+ }
30352
+ if (answer === "2") {
30353
+ const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
30354
+ out("");
30355
+ if (!hasGraph) {
30356
+ out("There is no local graph to push yet. Build one, then push it:");
30357
+ out(" neat");
30358
+ out(` neat sync --to ${input.project.name}`);
30359
+ return;
30360
+ }
30361
+ out("Push the local graph with:");
30362
+ out(` neat sync --to ${input.project.name}`);
30363
+ return;
30364
+ }
30365
+ out("");
30366
+ out("No problem \u2014 when you want this repo in the graph:");
30367
+ out(` Bind it: ${bindUrl(input.webUrl, input.project.id)}`);
30368
+ out(` Or push it: neat sync --to ${input.project.name}`);
30369
+ }
30370
+
30049
30371
  // src/login-sso.ts
30050
- var DEFAULT_CP_URL = "https://neat-control-plane-bg5yqctn2q-nw.a.run.app";
30372
+ var DEFAULT_CP_URL = "https://api.neat.is";
30051
30373
  var DEFAULT_WEB_URL = "https://app.neat.is";
30052
30374
  var CALLBACK_TIMEOUT_MS = 5 * 6e4;
30053
30375
  function resolveCpUrl(env = process.env, override) {
@@ -30071,6 +30393,7 @@ function pickProject(projects, want) {
30071
30393
  return {
30072
30394
  error: {
30073
30395
  code: 1,
30396
+ kind: "no-project",
30074
30397
  message: `no running project to connect to \u2014 create + provision one in the console first${listed}`
30075
30398
  }
30076
30399
  };
@@ -30121,14 +30444,14 @@ async function exchangeCredential(cpUrl, accessToken, opts, deps) {
30121
30444
  if (!cred.endpoint || !cred.authToken) {
30122
30445
  return { error: { code: 1, message: "the credential response was missing endpoint/authToken" } };
30123
30446
  }
30124
- return { project, cred };
30447
+ return { project, cred, me };
30125
30448
  }
30126
30449
  function defaultOpenBrowser(url) {
30127
30450
  const platform = process.platform;
30128
30451
  const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
30129
30452
  const args = platform === "win32" ? ["/c", "start", "", url] : [url];
30130
30453
  try {
30131
- const child = (0, import_node_child_process4.spawn)(cmd, args, { detached: true, stdio: "ignore" });
30454
+ const child = (0, import_node_child_process5.spawn)(cmd, args, { detached: true, stdio: "ignore" });
30132
30455
  child.on("error", () => {
30133
30456
  });
30134
30457
  child.unref();
@@ -30203,12 +30526,50 @@ async function runSsoLogin(opts, deps) {
30203
30526
  }
30204
30527
  accessToken = lb.token;
30205
30528
  }
30206
- const ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
30529
+ const fetchImpl = deps.fetchImpl ?? fetch;
30530
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
30531
+ const now = deps.now ?? Date.now;
30532
+ let ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
30533
+ if ("error" in ex && ex.error.kind === "no-project" && !opts.json) {
30534
+ const url = onboardingUrl(opts.webUrl);
30535
+ out("");
30536
+ out("You don't have a project yet \u2014 let's make one.");
30537
+ const opened = (deps.openBrowser ?? defaultOpenBrowser)(url);
30538
+ out(opened ? `Opened ${url}` : `Open this to create your first project:
30539
+ ${url}`);
30540
+ out("Waiting for it to come up \u2014 Ctrl-C to stop and finish later.");
30541
+ const waited = await waitForFirstProject(fetchImpl, opts.cpUrl, accessToken, {
30542
+ sleep: sleep2,
30543
+ now,
30544
+ ...deps.signal ? { signal: deps.signal } : {}
30545
+ });
30546
+ if (waited.kind === "aborted") {
30547
+ out("Stopped. Run `neat login` again once your project is up.");
30548
+ return 130;
30549
+ }
30550
+ if (waited.kind === "timeout") {
30551
+ err("neat login: no project came up while waiting \u2014 run `neat login` again once it does");
30552
+ return 1;
30553
+ }
30554
+ out(`${waited.project.name} is up.`);
30555
+ ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
30556
+ }
30207
30557
  if ("error" in ex) {
30208
30558
  err(`neat login: ${ex.error.message}`);
30209
30559
  return ex.error.code;
30210
30560
  }
30211
- const { project, cred } = ex;
30561
+ const { project, cred, me } = ex;
30562
+ const probe = await probeDaemon(fetchImpl, cred.endpoint, cred.authToken, {
30563
+ sleep: sleep2,
30564
+ now,
30565
+ onWaiting: () => err("Waking the hosted daemon \u2014 a cold instance can take up to a minute\u2026")
30566
+ });
30567
+ if (probe.kind !== "ok") {
30568
+ const why = probe.kind === "unreachable" ? probe.detail : probe.kind === "unauthorized" ? `it rejected the credential (HTTP ${probe.status})` : `it answered HTTP ${probe.status} and doesn't look like NEAT`;
30569
+ err(`neat login: "${project.name}" is provisioned, but its daemon isn't answering \u2014 ${why}.`);
30570
+ err(`Nothing was changed. Check the project at ${opts.webUrl} and run \`neat login\` again.`);
30571
+ return probe.kind === "unreachable" ? 3 : 1;
30572
+ }
30212
30573
  await upsertProfile(
30213
30574
  { name: opts.name, endpoint: cred.endpoint, authToken: cred.authToken },
30214
30575
  { makeActive: true, ...deps.home ? { home: deps.home } : {} }
@@ -30236,6 +30597,21 @@ async function runSsoLogin(opts, deps) {
30236
30597
  out(` OTEL_EXPORTER_OTLP_ENDPOINT=${cred.ingestEndpoint}`);
30237
30598
  out(` OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${cred.otelToken}`);
30238
30599
  }
30600
+ await runPostLoginConnect({
30601
+ cpUrl: opts.cpUrl,
30602
+ webUrl: opts.webUrl,
30603
+ accessToken,
30604
+ project: { id: project.id, name: project.name },
30605
+ me,
30606
+ deps: {
30607
+ fetchImpl,
30608
+ out,
30609
+ ...deps.readLine ? { readLine: deps.readLine } : {},
30610
+ ...deps.openBrowser ? { openBrowser: deps.openBrowser } : { openBrowser: defaultOpenBrowser },
30611
+ ...deps.connect ?? {}
30612
+ }
30613
+ });
30614
+ out("");
30239
30615
  out("Run `neat logout` to switch back to your local daemon.");
30240
30616
  }
30241
30617
  return 0;
@@ -30247,7 +30623,7 @@ function defaultOpenBrowser2(url) {
30247
30623
  const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
30248
30624
  const args = platform === "win32" ? ["/c", "start", "", url] : [url];
30249
30625
  try {
30250
- const child = (0, import_node_child_process5.spawn)(cmd, args, { detached: true, stdio: "ignore" });
30626
+ const child = (0, import_node_child_process6.spawn)(cmd, args, { detached: true, stdio: "ignore" });
30251
30627
  child.on("error", () => {
30252
30628
  });
30253
30629
  child.unref();
@@ -30342,8 +30718,8 @@ async function runConnectCommand(rawArgs, deps = {}) {
30342
30718
 
30343
30719
  // src/doctor-cli.ts
30344
30720
  init_cjs_shims();
30345
- var import_node_fs54 = require("fs");
30346
- var import_node_path92 = __toESM(require("path"), 1);
30721
+ var import_node_fs55 = require("fs");
30722
+ var import_node_path93 = __toESM(require("path"), 1);
30347
30723
 
30348
30724
  // src/cli-client.ts
30349
30725
  init_cjs_shims();
@@ -30372,10 +30748,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
30372
30748
  const root = baseUrl.replace(/\/$/, "");
30373
30749
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
30374
30750
  return {
30375
- async get(path98) {
30751
+ async get(path100) {
30376
30752
  let res;
30377
30753
  try {
30378
- res = await fetchImpl(`${root}${path98}`, {
30754
+ res = await fetchImpl(`${root}${path100}`, {
30379
30755
  headers: { ...authHeader }
30380
30756
  });
30381
30757
  } catch (err) {
@@ -30387,16 +30763,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
30387
30763
  const body = await res.text().catch(() => "");
30388
30764
  throw new HttpError(
30389
30765
  res.status,
30390
- `${res.status} ${res.statusText} on GET ${path98}: ${body}`,
30766
+ `${res.status} ${res.statusText} on GET ${path100}: ${body}`,
30391
30767
  body
30392
30768
  );
30393
30769
  }
30394
30770
  return await res.json();
30395
30771
  },
30396
- async post(path98, body) {
30772
+ async post(path100, body) {
30397
30773
  let res;
30398
30774
  try {
30399
- res = await fetchImpl(`${root}${path98}`, {
30775
+ res = await fetchImpl(`${root}${path100}`, {
30400
30776
  method: "POST",
30401
30777
  headers: { "content-type": "application/json", ...authHeader },
30402
30778
  body: JSON.stringify(body)
@@ -30410,7 +30786,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
30410
30786
  const text = await res.text().catch(() => "");
30411
30787
  throw new HttpError(
30412
30788
  res.status,
30413
- `${res.status} ${res.statusText} on POST ${path98}: ${text}`,
30789
+ `${res.status} ${res.statusText} on POST ${path100}: ${text}`,
30414
30790
  text
30415
30791
  );
30416
30792
  }
@@ -30424,12 +30800,12 @@ function projectPath(project, suffix) {
30424
30800
  }
30425
30801
  async function runRootCause(client, input) {
30426
30802
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
30427
- const path98 = projectPath(
30803
+ const path100 = projectPath(
30428
30804
  input.project,
30429
30805
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
30430
30806
  );
30431
30807
  try {
30432
- const result = await client.get(path98);
30808
+ const result = await client.get(path100);
30433
30809
  const arrowPath = result.traversalPath.join(" \u2190 ");
30434
30810
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
30435
30811
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -30455,12 +30831,12 @@ async function runRootCause(client, input) {
30455
30831
  }
30456
30832
  async function runBlastRadius(client, input) {
30457
30833
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
30458
- const path98 = projectPath(
30834
+ const path100 = projectPath(
30459
30835
  input.project,
30460
30836
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
30461
30837
  );
30462
30838
  try {
30463
- const result = await client.get(path98);
30839
+ const result = await client.get(path100);
30464
30840
  if (result.totalAffected === 0) {
30465
30841
  return {
30466
30842
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -30494,12 +30870,12 @@ function formatBlastEntry(n) {
30494
30870
  }
30495
30871
  async function runDependencies(client, input) {
30496
30872
  const depth = input.depth ?? 3;
30497
- const path98 = projectPath(
30873
+ const path100 = projectPath(
30498
30874
  input.project,
30499
30875
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
30500
30876
  );
30501
30877
  try {
30502
- const result = await client.get(path98);
30878
+ const result = await client.get(path100);
30503
30879
  if (result.total === 0) {
30504
30880
  return {
30505
30881
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -30591,9 +30967,9 @@ function formatDuration(ms) {
30591
30967
  return `${Math.round(h / 24)}d`;
30592
30968
  }
30593
30969
  async function runIncidents(client, input) {
30594
- const path98 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
30970
+ const path100 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
30595
30971
  try {
30596
- const body = await client.get(path98);
30972
+ const body = await client.get(path100);
30597
30973
  const events = body.events;
30598
30974
  if (events.length === 0) {
30599
30975
  return {
@@ -30957,7 +31333,7 @@ function checkNode(nodeVersion) {
30957
31333
  }
30958
31334
  async function neatOutExists(cwd) {
30959
31335
  try {
30960
- const st = await import_node_fs54.promises.stat(import_node_path92.default.join(cwd, "neat-out"));
31336
+ const st = await import_node_fs55.promises.stat(import_node_path93.default.join(cwd, "neat-out"));
30961
31337
  return st.isDirectory();
30962
31338
  } catch {
30963
31339
  return false;
@@ -31077,9 +31453,6 @@ async function runDoctorCommand(argv, deps = {}) {
31077
31453
  // src/login-cli.ts
31078
31454
  init_cjs_shims();
31079
31455
  var import_promises4 = __toESM(require("readline/promises"), 1);
31080
- var PROBE_ATTEMPT_TIMEOUT_MS = 3e4;
31081
- var PROBE_TOTAL_BUDGET_MS = 12e4;
31082
- var PROBE_RETRY_PAUSE_MS = 2e3;
31083
31456
  var DEFAULT_PROFILE_NAME = "hosted";
31084
31457
  function readFlagValue(argv, i) {
31085
31458
  const arg = argv[i];
@@ -31144,44 +31517,6 @@ function printLoginHelp(out) {
31144
31517
  out(' the profile (default "hosted"); the paste token can also come from NEAT_LOGIN_TOKEN.');
31145
31518
  out(" Exit 0 on success, 1 rejected/needs-action, 2 misuse, 3 unreachable.");
31146
31519
  }
31147
- function isTimeoutError(err) {
31148
- const name = err?.name;
31149
- return name === "TimeoutError" || name === "AbortError";
31150
- }
31151
- async function probeOnce(fetchImpl, root, token, timeoutMs) {
31152
- let res;
31153
- try {
31154
- res = await fetchImpl(`${root}/health`, {
31155
- headers: { authorization: `Bearer ${token}` },
31156
- signal: AbortSignal.timeout(timeoutMs)
31157
- });
31158
- } catch (err) {
31159
- if (isTimeoutError(err)) return { kind: "timeout" };
31160
- return { kind: "unreachable", detail: err.message };
31161
- }
31162
- if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
31163
- if (!res.ok) return { kind: "not-neat", status: res.status };
31164
- const contentType = res.headers.get("content-type") ?? "";
31165
- if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
31166
- return { kind: "ok" };
31167
- }
31168
- async function probeDaemon(fetchImpl, endpoint2, token, hooks) {
31169
- const root = endpoint2.replace(/\/$/, "");
31170
- const deadline = hooks.now() + PROBE_TOTAL_BUDGET_MS;
31171
- let warned = false;
31172
- for (; ; ) {
31173
- const result = await probeOnce(fetchImpl, root, token, PROBE_ATTEMPT_TIMEOUT_MS);
31174
- if (result.kind !== "timeout") return result;
31175
- if (!warned) {
31176
- hooks.onWaiting();
31177
- warned = true;
31178
- }
31179
- if (hooks.now() >= deadline) {
31180
- return { kind: "unreachable", detail: `no response after ${Math.round(PROBE_TOTAL_BUDGET_MS / 1e3)}s` };
31181
- }
31182
- await hooks.sleep(PROBE_RETRY_PAUSE_MS);
31183
- }
31184
- }
31185
31520
  async function runLoginCommand(argv, deps = {}) {
31186
31521
  const out = deps.out ?? ((line) => console.log(line));
31187
31522
  const err = deps.err ?? ((line) => console.error(line));
@@ -31214,8 +31549,13 @@ async function runLoginCommand(argv, deps = {}) {
31214
31549
  fetchImpl,
31215
31550
  out,
31216
31551
  err,
31552
+ readLine,
31553
+ sleep: sleep2,
31554
+ now,
31217
31555
  ...deps.openBrowser ? { openBrowser: deps.openBrowser } : {},
31218
- ...deps.home ? { home: deps.home } : {}
31556
+ ...deps.home ? { home: deps.home } : {},
31557
+ ...deps.signal ? { signal: deps.signal } : {},
31558
+ ...deps.connect ? { connect: deps.connect } : {}
31219
31559
  }
31220
31560
  );
31221
31561
  }
@@ -31372,6 +31712,8 @@ async function defaultReadSecret(prompt) {
31372
31712
 
31373
31713
  // src/welcome.ts
31374
31714
  init_cjs_shims();
31715
+ var import_node_fs56 = require("fs");
31716
+ var import_node_path94 = __toESM(require("path"), 1);
31375
31717
  var import_promises5 = __toESM(require("readline/promises"), 1);
31376
31718
  var WORDMARK = [
31377
31719
  "\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,29 +31746,146 @@ function printHeader(out) {
31404
31746
  }
31405
31747
  async function runWelcome(deps = {}) {
31406
31748
  const out = deps.out ?? ((line) => console.log(line));
31749
+ try {
31750
+ return await runFrontDoor(deps, out);
31751
+ } catch (err) {
31752
+ if (isPromptCancelled(err)) {
31753
+ out("");
31754
+ return 130;
31755
+ }
31756
+ throw err;
31757
+ }
31758
+ }
31759
+ var PromptCancelled = class extends Error {
31760
+ constructor() {
31761
+ super("prompt cancelled");
31762
+ this.name = "PromptCancelled";
31763
+ }
31764
+ };
31765
+ function isPromptCancelled(err) {
31766
+ if (err instanceof PromptCancelled) return true;
31767
+ const e = err;
31768
+ return e?.code === "ABORT_ERR" || e?.name === "AbortError";
31769
+ }
31770
+ async function runFrontDoor(deps, out) {
31407
31771
  const readLine = deps.readLine ?? defaultReadLine2;
31408
31772
  const login = deps.login ?? (() => Promise.resolve(0));
31409
31773
  const orchestrator = deps.orchestrator ?? (() => Promise.resolve(0));
31410
31774
  const cwd = deps.cwd ?? process.cwd();
31775
+ const flagGiven = deps.instrumentFlagGiven ?? false;
31776
+ const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
31411
31777
  printHeader(out);
31412
31778
  out("Welcome to NEAT. Let's get you a graph of this system.");
31413
31779
  out("");
31414
- out(" 1) I have a NEAT account \u2014 log in");
31415
- out(" 2) Self-hosted \u2014 set up NEAT on this machine");
31780
+ const keyReader = deps.readKey !== void 0 ? { read: deps.readKey, done: () => {
31781
+ } } : createKeyReader();
31782
+ const picked = keyReader !== null ? await selectOption(out, keyReader, deps.moveCursorUp ?? defaultMoveCursorUp) : await selectByNumber(out, readLine);
31783
+ if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
31784
+ if (picked === 0) return login(["--browser"]);
31785
+ return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
31786
+ }
31787
+ var MENU_OPTIONS = [
31788
+ "Log me into Hosted Neat",
31789
+ "I'd like to self-host or use it locally (copy a prompt)"
31790
+ ];
31791
+ async function selectOption(out, reader, moveCursorUp) {
31792
+ let cursor = 0;
31793
+ const render = () => {
31794
+ for (const [i, label] of MENU_OPTIONS.entries()) {
31795
+ out(i === cursor ? ` \u276F ${i + 1}) ${label}` : ` ${i + 1}) ${label}`);
31796
+ }
31797
+ };
31798
+ try {
31799
+ render();
31800
+ out("");
31801
+ out(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 1 or 2 to jump");
31802
+ for (; ; ) {
31803
+ const key = await reader.read();
31804
+ if (key === void 0) return void 0;
31805
+ if (key === "cancel") throw new PromptCancelled();
31806
+ if (key === "enter") return cursor;
31807
+ if (key === "select-1") return 0;
31808
+ if (key === "select-2") return 1;
31809
+ if (key === "up") cursor = (cursor + MENU_OPTIONS.length - 1) % MENU_OPTIONS.length;
31810
+ else if (key === "down") cursor = (cursor + 1) % MENU_OPTIONS.length;
31811
+ else continue;
31812
+ moveCursorUp(MENU_OPTIONS.length + 2);
31813
+ render();
31814
+ out("");
31815
+ out(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 1 or 2 to jump");
31816
+ }
31817
+ } finally {
31818
+ reader.done();
31819
+ }
31820
+ }
31821
+ async function selectByNumber(out, readLine) {
31822
+ for (const [i, label] of MENU_OPTIONS.entries()) out(` ${i + 1}) ${label}`);
31416
31823
  out("");
31417
31824
  for (; ; ) {
31418
31825
  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.`);
31826
+ if (choice === void 0) return void 0;
31827
+ if (choice === "1") return 0;
31828
+ if (choice === "2" || choice === "") return 1;
31829
+ out(`"${choice}" isn't 1 or 2 \u2014 try again, or press Enter for the local path.`);
31427
31830
  }
31428
31831
  }
31429
- async function runSelfHosted(out, readLine, orchestrator, cwd) {
31832
+ function createKeyReader() {
31833
+ const stdin = process.stdin;
31834
+ if (!stdin.isTTY || typeof stdin.setRawMode !== "function") return null;
31835
+ stdin.setRawMode(true);
31836
+ stdin.resume();
31837
+ let closed = false;
31838
+ let ended = false;
31839
+ const markEnded = () => {
31840
+ ended = true;
31841
+ };
31842
+ stdin.once("end", markEnded);
31843
+ stdin.once("close", markEnded);
31844
+ return {
31845
+ read: () => new Promise((resolve) => {
31846
+ if (ended) {
31847
+ resolve(void 0);
31848
+ return;
31849
+ }
31850
+ const finish = (value) => {
31851
+ stdin.removeListener("data", onData);
31852
+ stdin.removeListener("end", onClosed);
31853
+ stdin.removeListener("close", onClosed);
31854
+ resolve(value);
31855
+ };
31856
+ const onData = (buf) => finish(decodeKey(buf));
31857
+ const onClosed = () => {
31858
+ ended = true;
31859
+ finish(void 0);
31860
+ };
31861
+ stdin.on("data", onData);
31862
+ stdin.once("end", onClosed);
31863
+ stdin.once("close", onClosed);
31864
+ }),
31865
+ done: () => {
31866
+ if (closed) return;
31867
+ closed = true;
31868
+ stdin.removeListener("end", markEnded);
31869
+ stdin.removeListener("close", markEnded);
31870
+ stdin.setRawMode(false);
31871
+ stdin.pause();
31872
+ }
31873
+ };
31874
+ }
31875
+ function decodeKey(buf) {
31876
+ const s = buf.toString("utf8");
31877
+ if (s === "\x1B[A" || s === "k") return "up";
31878
+ if (s === "\x1B[B" || s === "j") return "down";
31879
+ if (s === "\r" || s === "\n") return "enter";
31880
+ if (s === "1") return "select-1";
31881
+ if (s === "2") return "select-2";
31882
+ if (s === "" || s === "\x1B") return "cancel";
31883
+ return "ignore";
31884
+ }
31885
+ function defaultMoveCursorUp(rows) {
31886
+ if (rows > 0) process.stdout.write(`\x1B[${rows}A`);
31887
+ }
31888
+ async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, instrumentFlagGiven) {
31430
31889
  const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
31431
31890
  if (wantsPrompt !== "n" && wantsPrompt !== "no") {
31432
31891
  out("");
@@ -31437,18 +31896,91 @@ async function runSelfHosted(out, readLine, orchestrator, cwd) {
31437
31896
  out("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
31438
31897
  out("");
31439
31898
  }
31899
+ const project = await resolveProjectName(out, readLine, cwd, readRegistry2);
31900
+ const declined = instrumentFlagGiven ? void 0 : await askToInstrument(out, readLine);
31440
31901
  out("Building your local graph now\u2026");
31441
31902
  out("");
31442
- return orchestrator(cwd);
31903
+ const overrides = {
31904
+ ...project !== void 0 ? { project } : {},
31905
+ ...declined === void 0 ? {} : declined ? { noInstrument: true } : { yes: true }
31906
+ };
31907
+ return orchestrator(cwd, Object.keys(overrides).length > 0 ? overrides : void 0);
31908
+ }
31909
+ async function askToInstrument(out, readLine) {
31910
+ out("Instrument the services for OpenTelemetry now?");
31911
+ out("This edits package.json / requirements.txt / go.mod and runs the package manager.");
31912
+ const answer = (await readLine("[Y/n]: "))?.trim().toLowerCase();
31913
+ const declined = answer === "n" || answer === "no";
31914
+ if (declined) {
31915
+ out("");
31916
+ out("Skipping instrumentation \u2014 the graph will hold the declared side only.");
31917
+ out("Run `neat init . --apply` when you want the runtime half.");
31918
+ }
31919
+ out("");
31920
+ return declined;
31921
+ }
31922
+ async function resolveProjectName(out, readLine, cwd, readRegistry2) {
31923
+ const here = import_node_path94.default.resolve(cwd);
31924
+ const base = import_node_path94.default.basename(here);
31925
+ let taken;
31926
+ try {
31927
+ const { projects } = await readRegistry2();
31928
+ taken = new Map(projects.map((p) => [p.name, p.path]));
31929
+ } catch {
31930
+ return void 0;
31931
+ }
31932
+ const clash = taken.get(base);
31933
+ if (clash === void 0 || import_node_path94.default.resolve(clash) === here) return void 0;
31934
+ out("");
31935
+ out(`A project named \`${base}\` is already registered (${clash}).`);
31936
+ const suggested = firstFreeName(base, taken);
31937
+ for (; ; ) {
31938
+ const answer = (await readLine(`Name this one: [${suggested}] `))?.trim();
31939
+ if (answer === void 0) return suggested;
31940
+ const chosen = answer.length === 0 ? suggested : answer;
31941
+ if (!isUsableProjectName(chosen)) {
31942
+ out(`"${chosen}" won't work as a project name \u2014 letters, digits, dot, dash and underscore.`);
31943
+ continue;
31944
+ }
31945
+ const other = taken.get(chosen);
31946
+ if (other !== void 0 && import_node_path94.default.resolve(other) !== here) {
31947
+ out(`\`${chosen}\` is registered too (${other}).`);
31948
+ continue;
31949
+ }
31950
+ out("");
31951
+ return chosen;
31952
+ }
31953
+ }
31954
+ function firstFreeName(base, taken) {
31955
+ for (let n = 2; ; n++) {
31956
+ const candidate = `${base}-${n}`;
31957
+ if (!taken.has(candidate)) return candidate;
31958
+ }
31959
+ }
31960
+ function isUsableProjectName(name) {
31961
+ return name.length > 0 && name !== "." && name !== ".." && /^[A-Za-z0-9._-]+$/.test(name);
31443
31962
  }
31444
31963
  async function shouldShowWelcome(deps = {}) {
31445
31964
  const stdinTTY = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
31446
31965
  const stdoutTTY = deps.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
31447
31966
  if (!stdinTTY || !stdoutTTY) return false;
31448
- const readProfiles = deps.readProfiles ?? (() => readProfilesConfig());
31967
+ const cwd = deps.cwd ?? process.cwd();
31968
+ const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
31969
+ const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
31970
+ try {
31971
+ const here = import_node_path94.default.resolve(cwd);
31972
+ const { projects } = await readRegistry2();
31973
+ if (projects.some((p) => import_node_path94.default.resolve(p.path) === here)) return false;
31974
+ if (await hasSnapshot(here)) return false;
31975
+ return true;
31976
+ } catch {
31977
+ return false;
31978
+ }
31979
+ }
31980
+ async function defaultHasSnapshot(cwd) {
31449
31981
  try {
31450
- const { profiles } = await readProfiles();
31451
- return profiles.length === 0;
31982
+ const entries = await import_node_fs56.promises.readdir(import_node_path94.default.join(cwd, "neat-out"));
31983
+ return entries.some((e) => e.endsWith(".json"));
31452
31984
  } catch {
31453
31985
  return false;
31454
31986
  }
@@ -31465,27 +31997,27 @@ async function defaultReadLine2(prompt) {
31465
31997
 
31466
31998
  // src/hooks-cli.ts
31467
31999
  init_cjs_shims();
31468
- var import_node_path93 = __toESM(require("path"), 1);
32000
+ var import_node_path95 = __toESM(require("path"), 1);
31469
32001
  var import_node_os8 = __toESM(require("os"), 1);
31470
- var import_node_fs55 = require("fs");
32002
+ var import_node_fs57 = require("fs");
31471
32003
  var import_node_url5 = require("url");
31472
32004
  var HOOK_FILENAME = "neat-search-nudge.mjs";
31473
32005
  var GUIDE_FILENAME = "GRAPH_FIRST.md";
31474
32006
  var GUIDE_INSTALL_NAME = "neat-graph-first.md";
31475
32007
  var HOOK_MATCHER = "Grep|Glob|Bash";
31476
32008
  function moduleDir() {
31477
- return typeof __dirname !== "undefined" ? __dirname : import_node_path93.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
32009
+ return typeof __dirname !== "undefined" ? __dirname : import_node_path95.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
31478
32010
  }
31479
32011
  async function readSkillAsset(rel) {
31480
32012
  const here = moduleDir();
31481
32013
  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)
32014
+ import_node_path95.default.resolve(here, "../../claude-skill", rel),
32015
+ import_node_path95.default.resolve(here, "../../../claude-skill", rel),
32016
+ import_node_path95.default.resolve(here, "../claude-skill", rel)
31485
32017
  ];
31486
32018
  for (const candidate of candidates) {
31487
32019
  try {
31488
- return await import_node_fs55.promises.readFile(candidate, "utf8");
32020
+ return await import_node_fs57.promises.readFile(candidate, "utf8");
31489
32021
  } catch {
31490
32022
  }
31491
32023
  }
@@ -31495,20 +32027,20 @@ async function readSkillAsset(rel) {
31495
32027
  }
31496
32028
  function neatHome4() {
31497
32029
  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");
32030
+ if (override && override.length > 0) return import_node_path95.default.resolve(override);
32031
+ return import_node_path95.default.join(import_node_os8.default.homedir(), ".neat");
31500
32032
  }
31501
32033
  function claudeSettingsPath() {
31502
32034
  const override = process.env.NEAT_CLAUDE_SETTINGS;
31503
- if (override && override.length > 0) return import_node_path93.default.resolve(override);
32035
+ if (override && override.length > 0) return import_node_path95.default.resolve(override);
31504
32036
  const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os8.default.homedir();
31505
- return import_node_path93.default.join(home, ".claude", "settings.json");
32037
+ return import_node_path95.default.join(home, ".claude", "settings.json");
31506
32038
  }
31507
32039
  function installedHookPath() {
31508
- return import_node_path93.default.join(neatHome4(), "hooks", HOOK_FILENAME);
32040
+ return import_node_path95.default.join(neatHome4(), "hooks", HOOK_FILENAME);
31509
32041
  }
31510
32042
  function gateFlagPath() {
31511
- return import_node_path93.default.join(neatHome4(), "hooks", "gate-enabled");
32043
+ return import_node_path95.default.join(neatHome4(), "hooks", "gate-enabled");
31512
32044
  }
31513
32045
  function isNeatSearchEntry(entry2) {
31514
32046
  return (entry2.hooks ?? []).some(
@@ -31541,14 +32073,14 @@ async function runHooks(opts) {
31541
32073
  const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
31542
32074
  const guide = await readSkillAsset(GUIDE_FILENAME);
31543
32075
  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");
32076
+ await import_node_fs57.promises.mkdir(import_node_path95.default.dirname(scriptPath), { recursive: true });
32077
+ await import_node_fs57.promises.writeFile(scriptPath, hookScript, { mode: 493 });
32078
+ const guidePath = import_node_path95.default.join(neatHome4(), GUIDE_INSTALL_NAME);
32079
+ await import_node_fs57.promises.writeFile(guidePath, guide, "utf8");
31548
32080
  const settingsFile = claudeSettingsPath();
31549
32081
  let settings = {};
31550
32082
  try {
31551
- settings = JSON.parse(await import_node_fs55.promises.readFile(settingsFile, "utf8"));
32083
+ settings = JSON.parse(await import_node_fs57.promises.readFile(settingsFile, "utf8"));
31552
32084
  } catch (err) {
31553
32085
  if (err.code !== "ENOENT") {
31554
32086
  console.error(
@@ -31570,14 +32102,14 @@ async function runHooks(opts) {
31570
32102
  ...settings,
31571
32103
  hooks: { ...hooks, PreToolUse: preToolUse }
31572
32104
  };
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");
32105
+ await import_node_fs57.promises.mkdir(import_node_path95.default.dirname(settingsFile), { recursive: true });
32106
+ await import_node_fs57.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
31575
32107
  const flag = gateFlagPath();
31576
32108
  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");
32109
+ await import_node_fs57.promises.mkdir(import_node_path95.default.dirname(flag), { recursive: true });
32110
+ await import_node_fs57.promises.writeFile(flag, "1\n", "utf8");
31579
32111
  } else {
31580
- await import_node_fs55.promises.rm(flag, { force: true });
32112
+ await import_node_fs57.promises.rm(flag, { force: true });
31581
32113
  }
31582
32114
  const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
31583
32115
  console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
@@ -31667,8 +32199,8 @@ async function runHooksCommand(args) {
31667
32199
 
31668
32200
  // src/claude-cli.ts
31669
32201
  init_cjs_shims();
31670
- var import_node_path94 = __toESM(require("path"), 1);
31671
- var import_node_fs56 = require("fs");
32202
+ var import_node_path96 = __toESM(require("path"), 1);
32203
+ var import_node_fs58 = require("fs");
31672
32204
  var NEAT_SECTION_HEADING = "## neat";
31673
32205
  var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
31674
32206
  code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
@@ -31702,8 +32234,8 @@ ${NEAT_DIRECTIVE_BODY}
31702
32234
  }
31703
32235
  function claudeMdPath() {
31704
32236
  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");
32237
+ if (override && override.length > 0) return import_node_path96.default.resolve(override);
32238
+ return import_node_path96.default.join(process.cwd(), "CLAUDE.md");
31707
32239
  }
31708
32240
  function splitAroundSection(raw) {
31709
32241
  const lines = raw.split("\n");
@@ -31731,7 +32263,7 @@ function compose(before, after) {
31731
32263
  }
31732
32264
  async function readIfExists2(file) {
31733
32265
  try {
31734
- return await import_node_fs56.promises.readFile(file, "utf8");
32266
+ return await import_node_fs58.promises.readFile(file, "utf8");
31735
32267
  } catch (err) {
31736
32268
  if (err.code === "ENOENT") return null;
31737
32269
  throw err;
@@ -31742,8 +32274,8 @@ async function runInstall() {
31742
32274
  const raw = await readIfExists2(file) ?? "";
31743
32275
  const { before, after, found } = splitAroundSection(raw);
31744
32276
  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");
32277
+ await import_node_fs58.promises.mkdir(import_node_path96.default.dirname(file), { recursive: true });
32278
+ await import_node_fs58.promises.writeFile(file, next, "utf8");
31747
32279
  const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
31748
32280
  console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
31749
32281
  console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
@@ -31764,7 +32296,7 @@ async function runUninstall() {
31764
32296
  }
31765
32297
  const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
31766
32298
  const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
31767
- await import_node_fs56.promises.writeFile(file, next, "utf8");
32299
+ await import_node_fs58.promises.writeFile(file, next, "utf8");
31768
32300
  console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
31769
32301
  return { exitCode: 0 };
31770
32302
  }
@@ -31807,10 +32339,10 @@ async function runClaudeCommand(args) {
31807
32339
 
31808
32340
  // src/codex-cli.ts
31809
32341
  init_cjs_shims();
31810
- var import_node_path95 = __toESM(require("path"), 1);
32342
+ var import_node_path97 = __toESM(require("path"), 1);
31811
32343
  var import_node_os9 = __toESM(require("os"), 1);
31812
- var import_node_fs57 = require("fs");
31813
- var import_node_util = require("util");
32344
+ var import_node_fs59 = require("fs");
32345
+ var import_node_util2 = require("util");
31814
32346
  var import_smol_toml6 = require("smol-toml");
31815
32347
  var CODEX_MCP_SERVER = {
31816
32348
  command: "npx",
@@ -31827,14 +32359,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
31827
32359
  var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
31828
32360
  function codexConfigPath() {
31829
32361
  const override = process.env.NEAT_CODEX_CONFIG;
31830
- if (override && override.length > 0) return import_node_path95.default.resolve(override);
32362
+ if (override && override.length > 0) return import_node_path97.default.resolve(override);
31831
32363
  const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os9.default.homedir();
31832
- return import_node_path95.default.join(home, ".codex", "config.toml");
32364
+ return import_node_path97.default.join(home, ".codex", "config.toml");
31833
32365
  }
31834
32366
  function agentsFilePath() {
31835
32367
  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");
32368
+ if (override && override.length > 0) return import_node_path97.default.resolve(override);
32369
+ return import_node_path97.default.join(process.cwd(), "AGENTS.md");
31838
32370
  }
31839
32371
  function isTableHeader(line) {
31840
32372
  return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
@@ -31911,17 +32443,17 @@ function verifyPreserved(raw, spliced, original) {
31911
32443
  }
31912
32444
  const origServers = original.mcp_servers ?? {};
31913
32445
  const nextServers = next.mcp_servers ?? {};
31914
- if (!(0, import_node_util.isDeepStrictEqual)(nextServers.neat, CODEX_MCP_SERVER)) return false;
32446
+ if (!(0, import_node_util2.isDeepStrictEqual)(nextServers.neat, CODEX_MCP_SERVER)) return false;
31915
32447
  for (const name of Object.keys(origServers)) {
31916
32448
  if (name === "neat") continue;
31917
- if (!(0, import_node_util.isDeepStrictEqual)(nextServers[name], origServers[name])) return false;
32449
+ if (!(0, import_node_util2.isDeepStrictEqual)(nextServers[name], origServers[name])) return false;
31918
32450
  }
31919
32451
  for (const name of Object.keys(nextServers)) {
31920
32452
  if (name !== "neat" && !(name in origServers)) return false;
31921
32453
  }
31922
32454
  for (const key of Object.keys(original)) {
31923
32455
  if (key === "mcp_servers") continue;
31924
- if (!(0, import_node_util.isDeepStrictEqual)(next[key], original[key])) return false;
32456
+ if (!(0, import_node_util2.isDeepStrictEqual)(next[key], original[key])) return false;
31925
32457
  }
31926
32458
  for (const key of Object.keys(next)) {
31927
32459
  if (key !== "mcp_servers" && !(key in original)) return false;
@@ -31968,7 +32500,7 @@ async function runCodex(opts) {
31968
32500
  const agentsPath = agentsFilePath();
31969
32501
  let configRaw = "";
31970
32502
  try {
31971
- configRaw = await import_node_fs57.promises.readFile(configPath, "utf8");
32503
+ configRaw = await import_node_fs59.promises.readFile(configPath, "utf8");
31972
32504
  } catch (err) {
31973
32505
  if (err.code !== "ENOENT") {
31974
32506
  console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
@@ -31977,7 +32509,7 @@ async function runCodex(opts) {
31977
32509
  }
31978
32510
  let agentsRaw = "";
31979
32511
  try {
31980
- agentsRaw = await import_node_fs57.promises.readFile(agentsPath, "utf8");
32512
+ agentsRaw = await import_node_fs59.promises.readFile(agentsPath, "utf8");
31981
32513
  } catch (err) {
31982
32514
  if (err.code !== "ENOENT") {
31983
32515
  console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
@@ -32017,15 +32549,15 @@ async function runCodex(opts) {
32017
32549
  return { exitCode: 0 };
32018
32550
  }
32019
32551
  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");
32552
+ await import_node_fs59.promises.mkdir(import_node_path97.default.dirname(configPath), { recursive: true });
32553
+ await import_node_fs59.promises.writeFile(configPath, config.text, "utf8");
32022
32554
  console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
32023
32555
  } else {
32024
32556
  console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
32025
32557
  }
32026
32558
  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");
32559
+ await import_node_fs59.promises.mkdir(import_node_path97.default.dirname(agentsPath), { recursive: true });
32560
+ await import_node_fs59.promises.writeFile(agentsPath, agents.text, "utf8");
32029
32561
  console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
32030
32562
  } else {
32031
32563
  console.log(`neat codex: ${agentsPath} already has the graph-first block`);
@@ -32082,10 +32614,10 @@ async function runCodexCommand(args) {
32082
32614
 
32083
32615
  // src/editors-cli.ts
32084
32616
  init_cjs_shims();
32085
- var import_node_path96 = __toESM(require("path"), 1);
32617
+ var import_node_path98 = __toESM(require("path"), 1);
32086
32618
  var import_node_os10 = __toESM(require("os"), 1);
32087
- var import_node_fs58 = require("fs");
32088
- var import_node_util2 = require("util");
32619
+ var import_node_fs60 = require("fs");
32620
+ var import_node_util3 = require("util");
32089
32621
  var jsonc = __toESM(require("jsonc-parser"), 1);
32090
32622
  var NEAT_MCP_SERVER = {
32091
32623
  command: "npx",
@@ -32108,17 +32640,17 @@ function homeDir() {
32108
32640
  }
32109
32641
  function xdgConfigDir() {
32110
32642
  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");
32643
+ return xdg && xdg.length > 0 ? import_node_path98.default.resolve(xdg) : import_node_path98.default.join(homeDir(), ".config");
32112
32644
  }
32113
32645
  function envOverride(name) {
32114
32646
  const v = process.env[name];
32115
- return v && v.length > 0 ? import_node_path96.default.resolve(v) : void 0;
32647
+ return v && v.length > 0 ? import_node_path98.default.resolve(v) : void 0;
32116
32648
  }
32117
32649
  var CURSOR_CLIENT = {
32118
32650
  id: "cursor",
32119
32651
  label: "Cursor",
32120
32652
  docsUrl: "https://docs.cursor.com/context/mcp",
32121
- mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path96.default.join(homeDir(), ".cursor", "mcp.json"),
32653
+ mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path98.default.join(homeDir(), ".cursor", "mcp.json"),
32122
32654
  mcpContainerKey: "mcpServers",
32123
32655
  format: "json",
32124
32656
  // Cursor still reads a single `.cursorrules` at the project root (the modern
@@ -32130,7 +32662,7 @@ var DEVIN_CLIENT = {
32130
32662
  id: "devin",
32131
32663
  label: "Devin Desktop (Cascade)",
32132
32664
  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"),
32665
+ mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path98.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
32134
32666
  mcpContainerKey: "mcpServers",
32135
32667
  format: "json",
32136
32668
  rulesFileName: ".windsurfrules"
@@ -32139,7 +32671,7 @@ var GEMINI_CLIENT = {
32139
32671
  id: "gemini",
32140
32672
  label: "Gemini CLI",
32141
32673
  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"),
32674
+ mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path98.default.join(homeDir(), ".gemini", "settings.json"),
32143
32675
  mcpContainerKey: "mcpServers",
32144
32676
  format: "json",
32145
32677
  rulesFileName: "GEMINI.md"
@@ -32148,7 +32680,7 @@ var QWEN_CLIENT = {
32148
32680
  id: "qwen",
32149
32681
  label: "Qwen Code",
32150
32682
  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"),
32683
+ mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path98.default.join(homeDir(), ".qwen", "settings.json"),
32152
32684
  mcpContainerKey: "mcpServers",
32153
32685
  format: "json",
32154
32686
  rulesFileName: "QWEN.md"
@@ -32157,7 +32689,7 @@ var AMAZONQ_CLIENT = {
32157
32689
  id: "amazonq",
32158
32690
  label: "Amazon Q Developer CLI",
32159
32691
  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"),
32692
+ mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path98.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
32161
32693
  mcpContainerKey: "mcpServers",
32162
32694
  format: "json"
32163
32695
  };
@@ -32165,7 +32697,7 @@ var ROOCODE_CLIENT = {
32165
32697
  id: "roocode",
32166
32698
  label: "Roo Code",
32167
32699
  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"),
32700
+ mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path98.default.join(process.cwd(), ".roo", "mcp.json"),
32169
32701
  mcpContainerKey: "mcpServers",
32170
32702
  format: "json"
32171
32703
  };
@@ -32178,9 +32710,9 @@ var ZED_CLIENT = {
32178
32710
  if (override) return override;
32179
32711
  if (process.platform === "win32") {
32180
32712
  const appData = process.env.APPDATA;
32181
- if (appData && appData.length > 0) return import_node_path96.default.join(appData, "Zed", "settings.json");
32713
+ if (appData && appData.length > 0) return import_node_path98.default.join(appData, "Zed", "settings.json");
32182
32714
  }
32183
- return import_node_path96.default.join(homeDir(), ".config", "zed", "settings.json");
32715
+ return import_node_path98.default.join(homeDir(), ".config", "zed", "settings.json");
32184
32716
  },
32185
32717
  mcpContainerKey: "context_servers",
32186
32718
  format: "jsonc",
@@ -32190,7 +32722,7 @@ var OPENCODE_CLIENT = {
32190
32722
  id: "opencode",
32191
32723
  label: "OpenCode",
32192
32724
  docsUrl: "https://opencode.ai/docs/mcp-servers/",
32193
- mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path96.default.join(xdgConfigDir(), "opencode", "opencode.json"),
32725
+ mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path98.default.join(xdgConfigDir(), "opencode", "opencode.json"),
32194
32726
  mcpContainerKey: "mcp",
32195
32727
  format: "json",
32196
32728
  serverEntry: NEAT_OPENCODE_SERVER,
@@ -32200,7 +32732,7 @@ var CRUSH_CLIENT = {
32200
32732
  id: "crush",
32201
32733
  label: "Crush",
32202
32734
  docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
32203
- mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path96.default.join(xdgConfigDir(), "crush", "crush.json"),
32735
+ mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path98.default.join(xdgConfigDir(), "crush", "crush.json"),
32204
32736
  mcpContainerKey: "mcp",
32205
32737
  format: "json",
32206
32738
  serverEntry: NEAT_CRUSH_SERVER,
@@ -32219,7 +32751,7 @@ var CLIENTS = {
32219
32751
  };
32220
32752
  function mergeJsonMcp(existing, containerKey, serverEntry) {
32221
32753
  const servers = existing[containerKey] ?? {};
32222
- const already = (0, import_node_util2.isDeepStrictEqual)(servers.neat, serverEntry);
32754
+ const already = (0, import_node_util3.isDeepStrictEqual)(servers.neat, serverEntry);
32223
32755
  const merged = {
32224
32756
  ...existing,
32225
32757
  [containerKey]: { ...servers, neat: serverEntry }
@@ -32230,7 +32762,7 @@ function mergeJsoncMcp(raw, containerKey, serverEntry) {
32230
32762
  const base = raw.trim().length > 0 ? raw : "{}";
32231
32763
  const parsed = jsonc.parse(base) ?? {};
32232
32764
  const servers = parsed[containerKey] ?? {};
32233
- if ((0, import_node_util2.isDeepStrictEqual)(servers.neat, serverEntry)) {
32765
+ if ((0, import_node_util3.isDeepStrictEqual)(servers.neat, serverEntry)) {
32234
32766
  return { text: raw, changed: false };
32235
32767
  }
32236
32768
  const edits = jsonc.modify(base, [containerKey, "neat"], serverEntry, {
@@ -32263,7 +32795,7 @@ async function planMcp(client, mcpPath) {
32263
32795
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
32264
32796
  let raw = "";
32265
32797
  try {
32266
- raw = await import_node_fs58.promises.readFile(mcpPath, "utf8");
32798
+ raw = await import_node_fs60.promises.readFile(mcpPath, "utf8");
32267
32799
  } catch (err) {
32268
32800
  const e = err;
32269
32801
  if (e.code === "ENOENT") {
@@ -32305,7 +32837,7 @@ async function runEditorInstall(client, opts) {
32305
32837
  const mcpPath = client.mcpConfigPath();
32306
32838
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
32307
32839
  const hasRules = typeof client.rulesFileName === "string";
32308
- const rulesPath = hasRules ? import_node_path96.default.join(opts.projectDir, client.rulesFileName) : "";
32840
+ const rulesPath = hasRules ? import_node_path98.default.join(opts.projectDir, client.rulesFileName) : "";
32309
32841
  const mcp = await planMcp(client, mcpPath);
32310
32842
  if (mcp === null) return { exitCode: 1 };
32311
32843
  let existingRules = "";
@@ -32314,7 +32846,7 @@ async function runEditorInstall(client, opts) {
32314
32846
  let block = "";
32315
32847
  if (hasRules) {
32316
32848
  try {
32317
- existingRules = await import_node_fs58.promises.readFile(rulesPath, "utf8");
32849
+ existingRules = await import_node_fs60.promises.readFile(rulesPath, "utf8");
32318
32850
  } catch (err) {
32319
32851
  if (err.code !== "ENOENT") {
32320
32852
  console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
@@ -32348,11 +32880,11 @@ async function runEditorInstall(client, opts) {
32348
32880
  );
32349
32881
  return { exitCode: 0 };
32350
32882
  }
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");
32883
+ await import_node_fs60.promises.mkdir(import_node_path98.default.dirname(mcpPath), { recursive: true });
32884
+ await import_node_fs60.promises.writeFile(mcpPath, mcp.text, "utf8");
32353
32885
  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");
32886
+ await import_node_fs60.promises.mkdir(import_node_path98.default.dirname(rulesPath), { recursive: true });
32887
+ await import_node_fs60.promises.writeFile(rulesPath, newRules, "utf8");
32356
32888
  }
32357
32889
  console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
32358
32890
  console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
@@ -32854,7 +33386,7 @@ function sleep(ms, signal) {
32854
33386
 
32855
33387
  // src/cli-verbs.ts
32856
33388
  init_cjs_shims();
32857
- var import_node_path97 = __toESM(require("path"), 1);
33389
+ var import_node_path99 = __toESM(require("path"), 1);
32858
33390
  async function resolveProjectEntry(opts) {
32859
33391
  const entries = await listProjects();
32860
33392
  if (opts.project) {
@@ -32864,7 +33396,7 @@ async function resolveProjectEntry(opts) {
32864
33396
  const cwd = opts.cwd ?? process.cwd();
32865
33397
  const resolvedCwd = await normalizeProjectPath(cwd);
32866
33398
  for (const entry2 of entries) {
32867
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path97.default.sep}`)) {
33399
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path99.default.sep}`)) {
32868
33400
  return entry2;
32869
33401
  }
32870
33402
  }
@@ -33039,6 +33571,7 @@ async function runSync(opts) {
33039
33571
 
33040
33572
  // src/cli.ts
33041
33573
  var import_types112 = require("@neat.is/types");
33574
+ init_auth();
33042
33575
  function usage5() {
33043
33576
  const neat = commandPrefix();
33044
33577
  console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
@@ -33441,7 +33974,7 @@ async function buildPatchSections(services, project) {
33441
33974
  }
33442
33975
  async function runInit(opts) {
33443
33976
  const written = [];
33444
- const stat = await import_node_fs59.promises.stat(opts.scanPath).catch(() => null);
33977
+ const stat = await import_node_fs61.promises.stat(opts.scanPath).catch(() => null);
33445
33978
  if (!stat || !stat.isDirectory()) {
33446
33979
  console.error(`neat init: ${opts.scanPath} is not a directory`);
33447
33980
  return { exitCode: 2, writtenFiles: written };
@@ -33450,13 +33983,13 @@ async function runInit(opts) {
33450
33983
  printDiscoveryReport(opts, services);
33451
33984
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
33452
33985
  const patch = renderPatch(sections);
33453
- const patchPath = import_node_path98.default.join(opts.scanPath, "neat.patch");
33986
+ const patchPath = import_node_path100.default.join(opts.scanPath, "neat.patch");
33454
33987
  if (opts.dryRun) {
33455
- await import_node_fs59.promises.writeFile(patchPath, patch, "utf8");
33988
+ await import_node_fs61.promises.writeFile(patchPath, patch, "utf8");
33456
33989
  written.push(patchPath);
33457
33990
  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);
33991
+ const gitignorePath = import_node_path100.default.join(opts.scanPath, ".gitignore");
33992
+ const gitignoreExists = await import_node_fs61.promises.stat(gitignorePath).then(() => true).catch(() => false);
33460
33993
  const verb = gitignoreExists ? "append" : "create";
33461
33994
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
33462
33995
  console.log("rerun without --dry-run to register and snapshot.");
@@ -33467,9 +34000,9 @@ async function runInit(opts) {
33467
34000
  const graph = getGraph(graphKey);
33468
34001
  const projectPaths = pathsForProject(
33469
34002
  graphKey,
33470
- import_node_path98.default.join(opts.scanPath, "neat-out")
34003
+ import_node_path100.default.join(opts.scanPath, "neat-out")
33471
34004
  );
33472
- const errorsPath = import_node_path98.default.join(import_node_path98.default.dirname(opts.outPath), import_node_path98.default.basename(projectPaths.errorsPath));
34005
+ const errorsPath = import_node_path100.default.join(import_node_path100.default.dirname(opts.outPath), import_node_path100.default.basename(projectPaths.errorsPath));
33473
34006
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
33474
34007
  await saveGraphToDisk(graph, opts.outPath);
33475
34008
  written.push(opts.outPath);
@@ -33548,7 +34081,7 @@ async function runInit(opts) {
33548
34081
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
33549
34082
  }
33550
34083
  } else {
33551
- await import_node_fs59.promises.writeFile(patchPath, patch, "utf8");
34084
+ await import_node_fs61.promises.writeFile(patchPath, patch, "utf8");
33552
34085
  written.push(patchPath);
33553
34086
  }
33554
34087
  }
@@ -33589,9 +34122,9 @@ var CLAUDE_SKILL_CONFIG = {
33589
34122
  };
33590
34123
  function claudeConfigPath() {
33591
34124
  const override = process.env.NEAT_CLAUDE_CONFIG;
33592
- if (override && override.length > 0) return import_node_path98.default.resolve(override);
34125
+ if (override && override.length > 0) return import_node_path100.default.resolve(override);
33593
34126
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
33594
- return import_node_path98.default.join(home, ".claude.json");
34127
+ return import_node_path100.default.join(home, ".claude.json");
33595
34128
  }
33596
34129
  async function runSkill(opts) {
33597
34130
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -33603,7 +34136,7 @@ async function runSkill(opts) {
33603
34136
  const target = claudeConfigPath();
33604
34137
  let existing = {};
33605
34138
  try {
33606
- existing = JSON.parse(await import_node_fs59.promises.readFile(target, "utf8"));
34139
+ existing = JSON.parse(await import_node_fs61.promises.readFile(target, "utf8"));
33607
34140
  } catch (err) {
33608
34141
  if (err.code !== "ENOENT") {
33609
34142
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -33615,8 +34148,8 @@ async function runSkill(opts) {
33615
34148
  ...existing,
33616
34149
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
33617
34150
  };
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");
34151
+ await import_node_fs61.promises.mkdir(import_node_path100.default.dirname(target), { recursive: true });
34152
+ await import_node_fs61.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
33620
34153
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
33621
34154
  console.log("restart Claude Code to pick up the new MCP server.");
33622
34155
  console.log("");
@@ -33642,7 +34175,7 @@ async function runSkill(opts) {
33642
34175
  async function main() {
33643
34176
  const argv = process.argv.slice(2);
33644
34177
  const cmd0 = argv[0];
33645
- if (cmd0 === "-h" || cmd0 === "--help") {
34178
+ if (argv.some((a) => a === "-h" || a === "--help")) {
33646
34179
  usage5();
33647
34180
  process.exit(0);
33648
34181
  }
@@ -33737,12 +34270,12 @@ async function main() {
33737
34270
  console.error("neat init: --apply and --dry-run are mutually exclusive");
33738
34271
  process.exit(2);
33739
34272
  }
33740
- const scanPath = import_node_path98.default.resolve(target);
34273
+ const scanPath = import_node_path100.default.resolve(target);
33741
34274
  const projectExplicit = parsed.project !== null;
33742
- const projectName = projectExplicit ? project : import_node_path98.default.basename(scanPath);
34275
+ const projectName = projectExplicit ? project : import_node_path100.default.basename(scanPath);
33743
34276
  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);
34277
+ const fallback = pathsForProject(projectKey, import_node_path100.default.join(scanPath, "neat-out")).snapshotPath;
34278
+ const outPath = import_node_path100.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
33746
34279
  const result = await runInit({
33747
34280
  scanPath,
33748
34281
  outPath,
@@ -33763,21 +34296,21 @@ async function main() {
33763
34296
  usage5();
33764
34297
  process.exit(2);
33765
34298
  }
33766
- const scanPath = import_node_path98.default.resolve(target);
33767
- const stat = await import_node_fs59.promises.stat(scanPath).catch(() => null);
34299
+ const scanPath = import_node_path100.default.resolve(target);
34300
+ const stat = await import_node_fs61.promises.stat(scanPath).catch(() => null);
33768
34301
  if (!stat || !stat.isDirectory()) {
33769
34302
  console.error(`neat watch: ${scanPath} is not a directory`);
33770
34303
  process.exit(2);
33771
34304
  }
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))
34305
+ const projectPaths = pathsForProject(project, import_node_path100.default.join(scanPath, "neat-out"));
34306
+ const outPath = import_node_path100.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
34307
+ const errorsPath = import_node_path100.default.resolve(
34308
+ process.env.NEAT_ERRORS_PATH ?? import_node_path100.default.join(import_node_path100.default.dirname(outPath), import_node_path100.default.basename(projectPaths.errorsPath))
33776
34309
  );
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))
34310
+ const staleEventsPath = import_node_path100.default.resolve(
34311
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path100.default.join(import_node_path100.default.dirname(outPath), import_node_path100.default.basename(projectPaths.staleEventsPath))
33779
34312
  );
33780
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path98.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
34313
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path100.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
33781
34314
  const handle = await startWatch(getGraph(project), {
33782
34315
  scanPath,
33783
34316
  outPath,
@@ -33786,7 +34319,7 @@ async function main() {
33786
34319
  project,
33787
34320
  // Resolve NEAT_HOME so a `neat watch` picks up connectors added to
33788
34321
  // ~/.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"),
34322
+ neatHome: process.env.NEAT_HOME ? import_node_path100.default.resolve(process.env.NEAT_HOME) : import_node_path100.default.join(import_node_os11.default.homedir(), ".neat"),
33790
34323
  ...embeddingsCachePath ? { embeddingsCachePath } : {},
33791
34324
  host: process.env.HOST ?? "0.0.0.0",
33792
34325
  port: Number(process.env.PORT ?? 8080),
@@ -33968,11 +34501,11 @@ async function main() {
33968
34501
  process.exit(1);
33969
34502
  }
33970
34503
  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);
34504
+ const scanPath = import_node_path100.default.resolve(cmd);
34505
+ const stat = await import_node_fs61.promises.stat(scanPath).catch(() => null);
33973
34506
  if (!stat || !stat.isDirectory()) return null;
33974
34507
  const projectExplicit = parsed.project !== null;
33975
- const projectName = projectExplicit ? parsed.project : import_node_path98.default.basename(scanPath);
34508
+ const projectName = projectExplicit ? parsed.project : import_node_path100.default.basename(scanPath);
33976
34509
  const result = await runOrchestrator({
33977
34510
  scanPath,
33978
34511
  project: projectName,
@@ -33987,7 +34520,18 @@ async function tryOrchestrator(cmd, parsed) {
33987
34520
  async function runWelcomeFlow(parsed) {
33988
34521
  return runWelcome({
33989
34522
  login: (loginArgv) => runLoginCommand(loginArgv),
33990
- orchestrator: async (cwd) => await tryOrchestrator(cwd, parsed) ?? 0
34523
+ // What the person chose at the door reaches the orchestrator as the flags they
34524
+ // stand for: the name as `--project`, the instrument answer as `--no-instrument`
34525
+ // or as `yes` (which keeps the orchestrator's own prompt quiet, since the door
34526
+ // already asked).
34527
+ orchestrator: async (cwd, opts) => await tryOrchestrator(cwd, {
34528
+ ...parsed,
34529
+ ...opts?.project !== void 0 ? { project: opts.project } : {},
34530
+ ...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
34531
+ ...opts?.yes !== void 0 ? { yes: opts.yes } : {}
34532
+ }) ?? 0,
34533
+ // An explicit flag is an answer; don't ask again.
34534
+ instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
33991
34535
  });
33992
34536
  }
33993
34537
  var QUERY_VERBS = /* @__PURE__ */ new Set([
@@ -34020,6 +34564,13 @@ var ProjectResolutionError = class extends Error {
34020
34564
  }
34021
34565
  exitCode;
34022
34566
  };
34567
+ function isLoopbackEndpoint(endpoint2) {
34568
+ try {
34569
+ return isLoopbackHost2(new URL(endpoint2).hostname.replace(/^\[|\]$/g, ""));
34570
+ } catch {
34571
+ return false;
34572
+ }
34573
+ }
34023
34574
  async function resolveProjectForVerb(client, parsed) {
34024
34575
  const explicit = resolveProjectFlag(parsed);
34025
34576
  if (explicit) return explicit;
@@ -34250,6 +34801,9 @@ async function runQueryVerb(cmd, parsed) {
34250
34801
  console.error(`neat ${cmd}: ${detail.trim()}`);
34251
34802
  } else if (err instanceof TransportError) {
34252
34803
  console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${target.endpoint})`);
34804
+ if (isLoopbackEndpoint(target.endpoint)) {
34805
+ console.error(`neat ${cmd}: start one with \`neat watch .\`, or \`neat\` to extract and start in one step.`);
34806
+ }
34253
34807
  } else {
34254
34808
  console.error(`neat ${cmd}: ${err.message}`);
34255
34809
  }
@@ -34306,6 +34860,7 @@ if (/[\\/](?:cli\.(?:cjs|js)|cli|neat|neat\.is)$/.test(entry)) {
34306
34860
  QUERY_VERBS,
34307
34861
  UnknownProfileError,
34308
34862
  commandPrefix,
34863
+ isLoopbackEndpoint,
34309
34864
  isNpxInvocation,
34310
34865
  main,
34311
34866
  parseArgs,