@neat.is/core 0.9.15-dev.20260906 → 0.9.15-dev.20260908

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 path96 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
- if (exactUnauthPaths.has(path96) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path96)) {
64
+ const path97 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
65
+ if (exactUnauthPaths.has(path97) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path97)) {
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 path96 = q === -1 ? v : v.slice(0, q);
419
- if (path96.length > 0) return path96;
418
+ const path97 = q === -1 ? v : v.slice(0, q);
419
+ if (path97.length > 0) return path97;
420
420
  }
421
421
  }
422
422
  return void 0;
@@ -787,12 +787,14 @@ __export(cli_exports, {
787
787
  CLAUDE_SKILL_CONFIG: () => CLAUDE_SKILL_CONFIG,
788
788
  ProjectResolutionError: () => ProjectResolutionError,
789
789
  QUERY_VERBS: () => QUERY_VERBS,
790
+ UnknownProfileError: () => UnknownProfileError,
790
791
  commandPrefix: () => commandPrefix,
791
792
  isNpxInvocation: () => isNpxInvocation,
792
793
  main: () => main,
793
794
  parseArgs: () => parseArgs,
794
795
  printBanner: () => printBanner,
795
796
  readPackageVersion: () => readPackageVersion,
797
+ resolveClientTarget: () => resolveClientTarget,
796
798
  resolveDaemonUrl: () => resolveDaemonUrl,
797
799
  resolveProjectForVerb: () => resolveProjectForVerb,
798
800
  runInit: () => runInit,
@@ -803,9 +805,9 @@ __export(cli_exports, {
803
805
  });
804
806
  module.exports = __toCommonJS(cli_exports);
805
807
  init_cjs_shims();
806
- var import_node_path95 = __toESM(require("path"), 1);
807
- var import_node_os9 = __toESM(require("os"), 1);
808
- var import_node_fs58 = require("fs");
808
+ var import_node_path96 = __toESM(require("path"), 1);
809
+ var import_node_os10 = __toESM(require("os"), 1);
810
+ var import_node_fs59 = require("fs");
809
811
 
810
812
  // src/banner.ts
811
813
  init_cjs_shims();
@@ -1382,19 +1384,19 @@ function confidenceFromMix(edges, now = Date.now()) {
1382
1384
  function longestIncomingWalk(graph, start, maxDepth) {
1383
1385
  let best = { path: [start], edges: [] };
1384
1386
  const visited = /* @__PURE__ */ new Set([start]);
1385
- function step(node, path96, edges) {
1386
- if (path96.length > best.path.length) {
1387
- best = { path: [...path96], edges: [...edges] };
1387
+ function step(node, path97, edges) {
1388
+ if (path97.length > best.path.length) {
1389
+ best = { path: [...path97], edges: [...edges] };
1388
1390
  }
1389
- if (path96.length - 1 >= maxDepth) return;
1391
+ if (path97.length - 1 >= maxDepth) return;
1390
1392
  const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
1391
1393
  for (const [srcId, edge] of incoming) {
1392
1394
  if (visited.has(srcId)) continue;
1393
1395
  visited.add(srcId);
1394
- path96.push(srcId);
1396
+ path97.push(srcId);
1395
1397
  edges.push(edge);
1396
- step(srcId, path96, edges);
1397
- path96.pop();
1398
+ step(srcId, path97, edges);
1399
+ path97.pop();
1398
1400
  edges.pop();
1399
1401
  visited.delete(srcId);
1400
1402
  }
@@ -1609,20 +1611,20 @@ function dominantFailingCall(graph, serviceId17, visited) {
1609
1611
  return best;
1610
1612
  }
1611
1613
  function followFailingCallChain(graph, originServiceId, maxDepth) {
1612
- const path96 = [originServiceId];
1614
+ const path97 = [originServiceId];
1613
1615
  const edges = [];
1614
1616
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1615
1617
  let current = originServiceId;
1616
1618
  for (let depth = 0; depth < maxDepth; depth++) {
1617
1619
  const hop = dominantFailingCall(graph, current, visited);
1618
1620
  if (!hop) break;
1619
- path96.push(hop.nextService);
1621
+ path97.push(hop.nextService);
1620
1622
  edges.push(hop.edge);
1621
1623
  visited.add(hop.nextService);
1622
1624
  current = hop.nextService;
1623
1625
  }
1624
1626
  if (edges.length === 0) return null;
1625
- return { path: path96, edges, culprit: current };
1627
+ return { path: path97, edges, culprit: current };
1626
1628
  }
1627
1629
  function isStaleCallEdge(e) {
1628
1630
  return e.type === import_types.EdgeType.CALLS && e.provenance === import_types.Provenance.STALE;
@@ -1658,26 +1660,26 @@ function dominantStaleCall(graph, serviceId17, visited) {
1658
1660
  return best;
1659
1661
  }
1660
1662
  function followStaleCallChain(graph, originServiceId, maxDepth) {
1661
- const path96 = [originServiceId];
1663
+ const path97 = [originServiceId];
1662
1664
  const edges = [];
1663
1665
  const visited = /* @__PURE__ */ new Set([originServiceId]);
1664
1666
  let current = originServiceId;
1665
1667
  for (let depth = 0; depth < maxDepth; depth++) {
1666
1668
  const hop = dominantStaleCall(graph, current, visited);
1667
1669
  if (!hop) break;
1668
- path96.push(hop.nextService);
1670
+ path97.push(hop.nextService);
1669
1671
  edges.push(hop.edge);
1670
1672
  visited.add(hop.nextService);
1671
1673
  current = hop.nextService;
1672
1674
  }
1673
1675
  if (edges.length === 0) return null;
1674
- return { path: path96, edges, culprit: current };
1676
+ return { path: path97, edges, culprit: current };
1675
1677
  }
1676
1678
  function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1677
1679
  const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
1678
1680
  if (!chain) return null;
1679
1681
  const culprit = chain.culprit;
1680
- const path96 = [...chain.path];
1682
+ const path97 = [...chain.path];
1681
1683
  const edgeProvenances = chain.edges.map((e) => e.provenance);
1682
1684
  const baseConfidence = confidenceFromMix(chain.edges);
1683
1685
  const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
@@ -1685,14 +1687,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1685
1687
  if (loc) {
1686
1688
  let rootCauseNode = culprit;
1687
1689
  if (loc.fileNode) {
1688
- path96.push(loc.fileNode);
1690
+ path97.push(loc.fileNode);
1689
1691
  edgeProvenances.push(import_types.Provenance.OBSERVED);
1690
1692
  rootCauseNode = loc.fileNode;
1691
1693
  }
1692
1694
  return import_types.RootCauseResultSchema.parse({
1693
1695
  rootCauseNode,
1694
1696
  rootCauseReason: loc.rootCauseReason,
1695
- traversalPath: path96,
1697
+ traversalPath: path97,
1696
1698
  edgeProvenances,
1697
1699
  confidence,
1698
1700
  ...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
@@ -1704,7 +1706,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
1704
1706
  return import_types.RootCauseResultSchema.parse({
1705
1707
  rootCauseNode: culprit,
1706
1708
  rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
1707
- traversalPath: path96,
1709
+ traversalPath: path97,
1708
1710
  edgeProvenances,
1709
1711
  confidence,
1710
1712
  fixRecommendation: `Inspect ${culpritName}'s failing handler`
@@ -2357,10 +2359,10 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
2357
2359
  traversalPath = staleChain.path;
2358
2360
  edgeProvenances = staleChain.edges.map((e) => e.provenance);
2359
2361
  } else if (top.node !== seedNode) {
2360
- const path96 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
2361
- if (path96) {
2362
- traversalPath = path96.nodes;
2363
- edgeProvenances = path96.edges.map((e) => e.provenance);
2362
+ const path97 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
2363
+ if (path97) {
2364
+ traversalPath = path97.nodes;
2365
+ edgeProvenances = path97.edges.map((e) => e.provenance);
2364
2366
  } else {
2365
2367
  traversalPath = [errorNodeId, top.node];
2366
2368
  edgeProvenances = [top.provenance ?? import_types.Provenance.OBSERVED];
@@ -3811,8 +3813,8 @@ function chiRoutesFromSource(source, parser) {
3811
3813
  chiWalk(tree.rootNode, "", out);
3812
3814
  return out;
3813
3815
  }
3814
- function stripChiRegex(path96) {
3815
- return path96.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3816
+ function stripChiRegex(path97) {
3817
+ return path97.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
3816
3818
  }
3817
3819
  function chiWalk(node, prefix, out) {
3818
3820
  for (let i = 0; i < node.namedChildCount; i++) {
@@ -4484,9 +4486,9 @@ function rubyRocketRoute(args) {
4484
4486
  if (!pair || pair.type !== "pair") continue;
4485
4487
  const k = pair.childForFieldName("key");
4486
4488
  if (k?.type !== "string") continue;
4487
- const path96 = rubyLiteral(k);
4488
- if (path96 === null) continue;
4489
- return { path: path96, target: rubyLiteral(pair.childForFieldName("value")) };
4489
+ const path97 = rubyLiteral(k);
4490
+ if (path97 === null) continue;
4491
+ return { path: path97, target: rubyLiteral(pair.childForFieldName("value")) };
4490
4492
  }
4491
4493
  return null;
4492
4494
  }
@@ -19899,10 +19901,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
19899
19901
  // src/connectors/supabase/map.ts
19900
19902
  var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
19901
19903
  var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
19902
- function targetFromRestPath(path96) {
19903
- const rpcMatch = REST_RPC_PATH_RE.exec(path96);
19904
+ function targetFromRestPath(path97) {
19905
+ const rpcMatch = REST_RPC_PATH_RE.exec(path97);
19904
19906
  if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
19905
- const tableMatch = REST_TABLE_PATH_RE.exec(path96);
19907
+ const tableMatch = REST_TABLE_PATH_RE.exec(path97);
19906
19908
  if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
19907
19909
  return null;
19908
19910
  }
@@ -20506,9 +20508,9 @@ function parseFirebaseTargetName(targetName) {
20506
20508
  const secondSep = rest.indexOf(FIELD_SEP);
20507
20509
  if (secondSep === -1) return null;
20508
20510
  const method = rest.slice(0, secondSep);
20509
- const path96 = rest.slice(secondSep + 1);
20510
- if (!resourceName || !method || !path96) return null;
20511
- return { resourceName, method, path: path96 };
20511
+ const path97 = rest.slice(secondSep + 1);
20512
+ if (!resourceName || !method || !path97) return null;
20513
+ return { resourceName, method, path: path97 };
20512
20514
  }
20513
20515
  function resourceNameFor(type, labels) {
20514
20516
  if (!labels) return null;
@@ -20546,14 +20548,14 @@ function mapLogEntryToSignal(entry2) {
20546
20548
  if (!req) return null;
20547
20549
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
20548
20550
  const method = req.requestMethod.toUpperCase();
20549
- const path96 = pathFromRequestUrl(req.requestUrl);
20550
- if (path96 === null) return null;
20551
+ const path97 = pathFromRequestUrl(req.requestUrl);
20552
+ if (path97 === null) return null;
20551
20553
  const timestamp = entry2.timestamp;
20552
20554
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
20553
20555
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
20554
20556
  return {
20555
20557
  targetKind: resourceType,
20556
- targetName: packFirebaseTargetName({ resourceName, method, path: path96 }),
20558
+ targetName: packFirebaseTargetName({ resourceName, method, path: path97 }),
20557
20559
  callCount: 1,
20558
20560
  errorCount: isError ? 1 : 0,
20559
20561
  lastObservedIso: timestamp
@@ -20760,7 +20762,7 @@ function mapEventToSignal(event) {
20760
20762
  if (Number.isNaN(observedAt.getTime())) return null;
20761
20763
  const statusCode = metadata?.statusCode;
20762
20764
  const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
20763
- const path96 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
20765
+ const path97 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
20764
20766
  return {
20765
20767
  targetKind: CLOUDFLARE_TARGET_KIND,
20766
20768
  targetName: scriptName,
@@ -20768,7 +20770,7 @@ function mapEventToSignal(event) {
20768
20770
  errorCount: isError ? 1 : 0,
20769
20771
  lastObservedIso: observedAt.toISOString(),
20770
20772
  method,
20771
- ...path96 ? { path: path96 } : {},
20773
+ ...path97 ? { path: path97 } : {},
20772
20774
  ...typeof statusCode === "number" ? { statusCode } : {},
20773
20775
  ...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
20774
20776
  };
@@ -20814,8 +20816,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
20814
20816
  });
20815
20817
  return found;
20816
20818
  }
20817
- function findMatchingRouteNode(graph, serviceName, method, path96) {
20818
- const normalizedPath = normalizePathTemplate(path96);
20819
+ function findMatchingRouteNode(graph, serviceName, method, path97) {
20820
+ const normalizedPath = normalizePathTemplate(path97);
20819
20821
  let found = null;
20820
20822
  graph.forEachNode((id, attrs) => {
20821
20823
  if (found) return;
@@ -20832,10 +20834,10 @@ function createCloudflareResolveTarget(config, graph) {
20832
20834
  return (signal) => {
20833
20835
  if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
20834
20836
  const scriptName = signal.targetName;
20835
- const { method, path: path96 } = signal;
20837
+ const { method, path: path97 } = signal;
20836
20838
  const resolveRouteGrain = (serviceName, wholeFileId) => {
20837
- if (!method || !path96) return wholeFileId;
20838
- return findMatchingRouteNode(graph, serviceName, method, path96) ?? wholeFileId;
20839
+ if (!method || !path97) return wholeFileId;
20840
+ return findMatchingRouteNode(graph, serviceName, method, path97) ?? wholeFileId;
20839
20841
  };
20840
20842
  const mapping = config.workers?.[scriptName];
20841
20843
  if (mapping) {
@@ -21187,9 +21189,9 @@ function parseCloudRunTargetName(targetName) {
21187
21189
  const secondSep = rest.indexOf(FIELD_SEP2);
21188
21190
  if (secondSep === -1) return null;
21189
21191
  const method = rest.slice(0, secondSep);
21190
- const path96 = rest.slice(secondSep + 1);
21191
- if (!serviceName || !method || !path96) return null;
21192
- return { serviceName, method, path: path96 };
21192
+ const path97 = rest.slice(secondSep + 1);
21193
+ if (!serviceName || !method || !path97) return null;
21194
+ return { serviceName, method, path: path97 };
21193
21195
  }
21194
21196
 
21195
21197
  // src/connectors/cloud-run/map.ts
@@ -21218,14 +21220,14 @@ function mapLogEntryToSignal2(entry2) {
21218
21220
  if (!req) return null;
21219
21221
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
21220
21222
  const method = req.requestMethod.toUpperCase();
21221
- const path96 = pathFromRequestUrl2(req.requestUrl);
21222
- if (path96 === null) return null;
21223
+ const path97 = pathFromRequestUrl2(req.requestUrl);
21224
+ if (path97 === null) return null;
21223
21225
  const timestamp = entry2.timestamp;
21224
21226
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
21225
21227
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
21226
21228
  return {
21227
21229
  targetKind: CLOUD_RUN_TARGET_KIND,
21228
- targetName: packCloudRunTargetName({ serviceName, method, path: path96 }),
21230
+ targetName: packCloudRunTargetName({ serviceName, method, path: path97 }),
21229
21231
  callCount: 1,
21230
21232
  errorCount: isError ? 1 : 0,
21231
21233
  lastObservedIso: timestamp
@@ -21264,14 +21266,14 @@ function createCloudRunResolveTarget(graph, config) {
21264
21266
  if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
21265
21267
  const identity = parseCloudRunTargetName(signal.targetName);
21266
21268
  if (!identity) return null;
21267
- const { serviceName: gcpServiceName, method, path: path96 } = identity;
21269
+ const { serviceName: gcpServiceName, method, path: path97 } = identity;
21268
21270
  const mappedService = config.serviceMap?.[gcpServiceName];
21269
21271
  if (mappedService) {
21270
21272
  const routeNodeId = findMatchingRouteNode2(
21271
21273
  graph,
21272
21274
  mappedService,
21273
21275
  method,
21274
- normalizePathTemplate(path96)
21276
+ normalizePathTemplate(path97)
21275
21277
  );
21276
21278
  if (routeNodeId) {
21277
21279
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types82.EdgeType.CALLS };
@@ -21407,9 +21409,9 @@ function parseGcpLbTargetName(targetName) {
21407
21409
  const secondSep = rest.indexOf(FIELD_SEP3);
21408
21410
  if (secondSep === -1) return null;
21409
21411
  const method = rest.slice(0, secondSep);
21410
- const path96 = rest.slice(secondSep + 1);
21411
- if (!backendServiceName || !method || !path96) return null;
21412
- return { backendServiceName, method, path: path96 };
21412
+ const path97 = rest.slice(secondSep + 1);
21413
+ if (!backendServiceName || !method || !path97) return null;
21414
+ return { backendServiceName, method, path: path97 };
21413
21415
  }
21414
21416
 
21415
21417
  // src/connectors/gcp-lb/map.ts
@@ -21438,14 +21440,14 @@ function mapLogEntryToSignal3(entry2) {
21438
21440
  if (!req) return null;
21439
21441
  if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
21440
21442
  const method = req.requestMethod.toUpperCase();
21441
- const path96 = pathFromRequestUrl3(req.requestUrl);
21442
- if (path96 === null) return null;
21443
+ const path97 = pathFromRequestUrl3(req.requestUrl);
21444
+ if (path97 === null) return null;
21443
21445
  const timestamp = entry2.timestamp;
21444
21446
  if (typeof timestamp !== "string" || timestamp.length === 0) return null;
21445
21447
  const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD5;
21446
21448
  return {
21447
21449
  targetKind: GCP_LB_TARGET_KIND,
21448
- targetName: packGcpLbTargetName({ backendServiceName, method, path: path96 }),
21450
+ targetName: packGcpLbTargetName({ backendServiceName, method, path: path97 }),
21449
21451
  callCount: 1,
21450
21452
  errorCount: isError ? 1 : 0,
21451
21453
  lastObservedIso: timestamp
@@ -21484,14 +21486,14 @@ function createGcpLbResolveTarget(graph, config) {
21484
21486
  if (signal.targetKind !== GCP_LB_TARGET_KIND) return null;
21485
21487
  const identity = parseGcpLbTargetName(signal.targetName);
21486
21488
  if (!identity) return null;
21487
- const { backendServiceName, method, path: path96 } = identity;
21489
+ const { backendServiceName, method, path: path97 } = identity;
21488
21490
  const mappedService = config.backendServiceMap?.[backendServiceName];
21489
21491
  if (mappedService) {
21490
21492
  const routeNodeId = findMatchingRouteNode3(
21491
21493
  graph,
21492
21494
  mappedService,
21493
21495
  method,
21494
- normalizePathTemplate(path96)
21496
+ normalizePathTemplate(path97)
21495
21497
  );
21496
21498
  if (routeNodeId) {
21497
21499
  return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types86.EdgeType.CALLS };
@@ -23856,9 +23858,9 @@ function makeK8sFetchImpl(transport) {
23856
23858
  req.end();
23857
23859
  }));
23858
23860
  }
23859
- async function listResource(transport, namespace, path96, opts = {}) {
23861
+ async function listResource(transport, namespace, path97, opts = {}) {
23860
23862
  const base = opts.apiUrl ?? transport.server;
23861
- const url = `${base.replace(/\/$/, "")}${path96}`;
23863
+ const url = `${base.replace(/\/$/, "")}${path97}`;
23862
23864
  const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
23863
23865
  const res = await junctionFetch(
23864
23866
  url,
@@ -23868,7 +23870,7 @@ async function listResource(transport, namespace, path96, opts = {}) {
23868
23870
  { provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
23869
23871
  );
23870
23872
  if (!res.ok) {
23871
- throw new Error(`kubernetes ${path96} failed: ${res.status} ${res.statusText}`);
23873
+ throw new Error(`kubernetes ${path97} failed: ${res.status} ${res.statusText}`);
23872
23874
  }
23873
23875
  const json = await res.json();
23874
23876
  return Array.isArray(json.items) ? json.items : [];
@@ -29341,10 +29343,10 @@ function createHttpClient(baseUrl, bearerToken) {
29341
29343
  const root = baseUrl.replace(/\/$/, "");
29342
29344
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
29343
29345
  return {
29344
- async get(path96) {
29346
+ async get(path97) {
29345
29347
  let res;
29346
29348
  try {
29347
- res = await fetch(`${root}${path96}`, {
29349
+ res = await fetch(`${root}${path97}`, {
29348
29350
  headers: { ...authHeader }
29349
29351
  });
29350
29352
  } catch (err) {
@@ -29356,16 +29358,16 @@ function createHttpClient(baseUrl, bearerToken) {
29356
29358
  const body = await res.text().catch(() => "");
29357
29359
  throw new HttpError(
29358
29360
  res.status,
29359
- `${res.status} ${res.statusText} on GET ${path96}: ${body}`,
29361
+ `${res.status} ${res.statusText} on GET ${path97}: ${body}`,
29360
29362
  body
29361
29363
  );
29362
29364
  }
29363
29365
  return await res.json();
29364
29366
  },
29365
- async post(path96, body) {
29367
+ async post(path97, body) {
29366
29368
  let res;
29367
29369
  try {
29368
- res = await fetch(`${root}${path96}`, {
29370
+ res = await fetch(`${root}${path97}`, {
29369
29371
  method: "POST",
29370
29372
  headers: { "content-type": "application/json", ...authHeader },
29371
29373
  body: JSON.stringify(body)
@@ -29379,7 +29381,7 @@ function createHttpClient(baseUrl, bearerToken) {
29379
29381
  const text = await res.text().catch(() => "");
29380
29382
  throw new HttpError(
29381
29383
  res.status,
29382
- `${res.status} ${res.statusText} on POST ${path96}: ${text}`,
29384
+ `${res.status} ${res.statusText} on POST ${path97}: ${text}`,
29383
29385
  text
29384
29386
  );
29385
29387
  }
@@ -29393,12 +29395,12 @@ function projectPath(project, suffix) {
29393
29395
  }
29394
29396
  async function runRootCause(client, input) {
29395
29397
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
29396
- const path96 = projectPath(
29398
+ const path97 = projectPath(
29397
29399
  input.project,
29398
29400
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
29399
29401
  );
29400
29402
  try {
29401
- const result = await client.get(path96);
29403
+ const result = await client.get(path97);
29402
29404
  const arrowPath = result.traversalPath.join(" \u2190 ");
29403
29405
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
29404
29406
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -29424,12 +29426,12 @@ async function runRootCause(client, input) {
29424
29426
  }
29425
29427
  async function runBlastRadius(client, input) {
29426
29428
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
29427
- const path96 = projectPath(
29429
+ const path97 = projectPath(
29428
29430
  input.project,
29429
29431
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
29430
29432
  );
29431
29433
  try {
29432
- const result = await client.get(path96);
29434
+ const result = await client.get(path97);
29433
29435
  if (result.totalAffected === 0) {
29434
29436
  return {
29435
29437
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -29463,12 +29465,12 @@ function formatBlastEntry(n) {
29463
29465
  }
29464
29466
  async function runDependencies(client, input) {
29465
29467
  const depth = input.depth ?? 3;
29466
- const path96 = projectPath(
29468
+ const path97 = projectPath(
29467
29469
  input.project,
29468
29470
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
29469
29471
  );
29470
29472
  try {
29471
- const result = await client.get(path96);
29473
+ const result = await client.get(path97);
29472
29474
  if (result.total === 0) {
29473
29475
  return {
29474
29476
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -29560,9 +29562,9 @@ function formatDuration(ms) {
29560
29562
  return `${Math.round(h / 24)}d`;
29561
29563
  }
29562
29564
  async function runIncidents(client, input) {
29563
- const path96 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
29565
+ const path97 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
29564
29566
  try {
29565
- const body = await client.get(path96);
29567
+ const body = await client.get(path97);
29566
29568
  const events = body.events;
29567
29569
  if (events.length === 0) {
29568
29570
  return {
@@ -30033,29 +30035,465 @@ async function runDoctorCommand(argv, deps = {}) {
30033
30035
  return checks.every((c) => c.ok) ? 0 : 1;
30034
30036
  }
30035
30037
 
30036
- // src/hooks-cli.ts
30038
+ // src/login-cli.ts
30039
+ init_cjs_shims();
30040
+ var import_promises2 = __toESM(require("readline/promises"), 1);
30041
+
30042
+ // src/profiles.ts
30037
30043
  init_cjs_shims();
30038
- var import_node_path90 = __toESM(require("path"), 1);
30039
- var import_node_os6 = __toESM(require("os"), 1);
30040
30044
  var import_node_fs54 = require("fs");
30045
+ var import_node_os6 = __toESM(require("os"), 1);
30046
+ var import_node_path90 = __toESM(require("path"), 1);
30047
+ var PROFILES_CONFIG_VERSION = 1;
30048
+ function neatHome3() {
30049
+ const override = process.env.NEAT_HOME;
30050
+ if (override && override.length > 0) return import_node_path90.default.resolve(override);
30051
+ return import_node_path90.default.join(import_node_os6.default.homedir(), ".neat");
30052
+ }
30053
+ function profilesConfigPath(home = neatHome3()) {
30054
+ return import_node_path90.default.join(home, "profiles.json");
30055
+ }
30056
+ function profilesConfigLockPath(home = neatHome3()) {
30057
+ return import_node_path90.default.join(home, "profiles.json.lock");
30058
+ }
30059
+ var MODE_MASK_LOOSER_THAN_06002 = 63;
30060
+ async function warnIfModeLooserThan06002(file) {
30061
+ if (process.platform === "win32") return;
30062
+ try {
30063
+ const stat = await import_node_fs54.promises.stat(file);
30064
+ if ((stat.mode & MODE_MASK_LOOSER_THAN_06002) !== 0) {
30065
+ const mode = (stat.mode & 511).toString(8).padStart(3, "0");
30066
+ console.warn(
30067
+ `[neat] ${file} is mode 0${mode}, looser than the 0600 this file's token calls for \u2014 run \`chmod 600 ${file}\``
30068
+ );
30069
+ }
30070
+ } catch {
30071
+ }
30072
+ }
30073
+ async function readProfilesConfig(home = neatHome3()) {
30074
+ const file = profilesConfigPath(home);
30075
+ let raw;
30076
+ try {
30077
+ raw = await import_node_fs54.promises.readFile(file, "utf8");
30078
+ } catch (err) {
30079
+ if (err.code === "ENOENT") {
30080
+ return { version: PROFILES_CONFIG_VERSION, profiles: [] };
30081
+ }
30082
+ throw err;
30083
+ }
30084
+ await warnIfModeLooserThan06002(file);
30085
+ let parsed;
30086
+ try {
30087
+ parsed = JSON.parse(raw);
30088
+ } catch (err) {
30089
+ throw new Error(`${file} is not valid JSON: ${err.message}`);
30090
+ }
30091
+ return validateConfig2(parsed, file);
30092
+ }
30093
+ function validateConfig2(parsed, file) {
30094
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
30095
+ throw new Error(`${file} must be a JSON object with a "profiles" array`);
30096
+ }
30097
+ const obj = parsed;
30098
+ const version = obj.version === void 0 ? PROFILES_CONFIG_VERSION : obj.version;
30099
+ if (typeof version !== "number" || !Number.isInteger(version)) {
30100
+ throw new Error(`${file}: "version" must be an integer`);
30101
+ }
30102
+ const rawProfiles = obj.profiles;
30103
+ if (!Array.isArray(rawProfiles)) {
30104
+ throw new Error(`${file}: "profiles" must be an array`);
30105
+ }
30106
+ const profiles = rawProfiles.map((entry2, i) => validateEntry2(entry2, i, file));
30107
+ const seen = /* @__PURE__ */ new Set();
30108
+ for (const p of profiles) {
30109
+ if (seen.has(p.name)) throw new Error(`${file}: duplicate profile name "${p.name}"`);
30110
+ seen.add(p.name);
30111
+ }
30112
+ let active;
30113
+ if (obj.active !== void 0) {
30114
+ if (typeof obj.active !== "string" || obj.active.length === 0) {
30115
+ throw new Error(`${file}: "active" must be a non-empty string when present`);
30116
+ }
30117
+ active = seen.has(obj.active) ? obj.active : void 0;
30118
+ }
30119
+ return { version, ...active ? { active } : {}, profiles };
30120
+ }
30121
+ function validateEntry2(entry2, index, file) {
30122
+ const where = `${file}: profiles[${index}]`;
30123
+ if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
30124
+ throw new Error(`${where} must be an object`);
30125
+ }
30126
+ const e = entry2;
30127
+ const name = e.name;
30128
+ if (typeof name !== "string" || name.length === 0) {
30129
+ throw new Error(`${where}.name must be a non-empty string`);
30130
+ }
30131
+ const endpoint2 = e.endpoint;
30132
+ if (typeof endpoint2 !== "string" || endpoint2.length === 0) {
30133
+ throw new Error(`${where}.endpoint must be a non-empty string`);
30134
+ }
30135
+ let parsedUrl;
30136
+ try {
30137
+ parsedUrl = new URL(endpoint2);
30138
+ } catch {
30139
+ throw new Error(`${where}.endpoint must be an absolute URL (got "${endpoint2}")`);
30140
+ }
30141
+ if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
30142
+ throw new Error(`${where}.endpoint must be an http(s) URL (got "${parsedUrl.protocol}")`);
30143
+ }
30144
+ if (e.authToken !== void 0 && (typeof e.authToken !== "string" || e.authToken.length === 0)) {
30145
+ throw new Error(`${where}.authToken must be a non-empty string when present`);
30146
+ }
30147
+ return {
30148
+ name,
30149
+ endpoint: endpoint2,
30150
+ ...typeof e.authToken === "string" ? { authToken: e.authToken } : {}
30151
+ };
30152
+ }
30153
+ async function resolveProfile(name, home = neatHome3()) {
30154
+ const { profiles } = await readProfilesConfig(home);
30155
+ return profiles.find((p) => p.name === name);
30156
+ }
30157
+ async function getActiveProfile(home = neatHome3()) {
30158
+ const { active, profiles } = await readProfilesConfig(home);
30159
+ if (!active) return void 0;
30160
+ return profiles.find((p) => p.name === active);
30161
+ }
30162
+ function serialize(config) {
30163
+ const names = new Set(config.profiles.map((p) => p.name));
30164
+ const active = config.active && names.has(config.active) ? config.active : void 0;
30165
+ const out = {
30166
+ version: config.version ?? PROFILES_CONFIG_VERSION,
30167
+ ...active ? { active } : {},
30168
+ profiles: config.profiles
30169
+ };
30170
+ return `${JSON.stringify(out, null, 2)}
30171
+ `;
30172
+ }
30173
+ async function writeConfigAtomic(config, home) {
30174
+ const file = profilesConfigPath(home);
30175
+ await import_node_fs54.promises.mkdir(import_node_path90.default.dirname(file), { recursive: true });
30176
+ const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
30177
+ const fd = await import_node_fs54.promises.open(tmp, "w", 384);
30178
+ try {
30179
+ await fd.writeFile(serialize(config), "utf8");
30180
+ await fd.sync();
30181
+ } finally {
30182
+ await fd.close();
30183
+ }
30184
+ await import_node_fs54.promises.rename(tmp, file);
30185
+ }
30186
+ var LOCK_RETRY_MS2 = 50;
30187
+ var LOCK_TIMEOUT_MS2 = 5e3;
30188
+ async function acquireLock2(lockPath) {
30189
+ await import_node_fs54.promises.mkdir(import_node_path90.default.dirname(lockPath), { recursive: true });
30190
+ const deadline = Date.now() + LOCK_TIMEOUT_MS2;
30191
+ for (; ; ) {
30192
+ try {
30193
+ const fd = await import_node_fs54.promises.open(lockPath, "wx");
30194
+ await fd.writeFile(`${process.pid}
30195
+ `, "utf8");
30196
+ await fd.close();
30197
+ return;
30198
+ } catch (err) {
30199
+ if (err.code !== "EEXIST") throw err;
30200
+ if (Date.now() >= deadline) {
30201
+ throw new Error(
30202
+ `timed out acquiring ${lockPath} after ${LOCK_TIMEOUT_MS2}ms \u2014 if no other neat process is running, remove the stale lock file`
30203
+ );
30204
+ }
30205
+ await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_MS2));
30206
+ }
30207
+ }
30208
+ }
30209
+ async function releaseLock2(lockPath) {
30210
+ await import_node_fs54.promises.rm(lockPath, { force: true });
30211
+ }
30212
+ async function withProfilesLock(home, fn) {
30213
+ const lockPath = profilesConfigLockPath(home);
30214
+ await acquireLock2(lockPath);
30215
+ try {
30216
+ return await fn();
30217
+ } finally {
30218
+ await releaseLock2(lockPath);
30219
+ }
30220
+ }
30221
+ async function upsertProfile(profile, opts = {}) {
30222
+ const home = opts.home ?? neatHome3();
30223
+ const validated = validateEntry2(profile, 0, profilesConfigPath(home));
30224
+ await withProfilesLock(home, async () => {
30225
+ const config = await readProfilesConfig(home);
30226
+ const others = config.profiles.filter((p) => p.name !== validated.name);
30227
+ const profiles = [...others, validated];
30228
+ const makeActive = opts.makeActive ?? config.profiles.length === 0;
30229
+ const active = makeActive ? validated.name : config.active;
30230
+ await writeConfigAtomic({ version: config.version, ...active ? { active } : {}, profiles }, home);
30231
+ });
30232
+ }
30233
+ async function removeProfile(name, home = neatHome3()) {
30234
+ return withProfilesLock(home, async () => {
30235
+ const config = await readProfilesConfig(home);
30236
+ const profiles = config.profiles.filter((p) => p.name !== name);
30237
+ if (profiles.length === config.profiles.length) return false;
30238
+ const active = config.active === name ? void 0 : config.active;
30239
+ await writeConfigAtomic({ version: config.version, ...active ? { active } : {}, profiles }, home);
30240
+ return true;
30241
+ });
30242
+ }
30243
+ async function clearActiveProfile(home = neatHome3()) {
30244
+ return withProfilesLock(home, async () => {
30245
+ const config = await readProfilesConfig(home);
30246
+ if (!config.active) return false;
30247
+ await writeConfigAtomic({ version: config.version, profiles: config.profiles }, home);
30248
+ return true;
30249
+ });
30250
+ }
30251
+
30252
+ // src/login-cli.ts
30253
+ var HEALTH_TIMEOUT_MS2 = 5e3;
30254
+ var DEFAULT_PROFILE_NAME = "hosted";
30255
+ function readFlagValue(argv, i) {
30256
+ const arg = argv[i];
30257
+ const eq = arg.indexOf("=");
30258
+ if (eq !== -1) return { value: arg.slice(eq + 1), next: i };
30259
+ return { value: argv[i + 1], next: i + 1 };
30260
+ }
30261
+ function parseLoginArgs(argv) {
30262
+ const parsed = { name: DEFAULT_PROFILE_NAME, json: false, help: false };
30263
+ for (let i = 0; i < argv.length; i++) {
30264
+ const arg = argv[i];
30265
+ if (arg === "-h" || arg === "--help") parsed.help = true;
30266
+ else if (arg === "--json") parsed.json = true;
30267
+ else if (arg === "--endpoint" || arg.startsWith("--endpoint=")) {
30268
+ const { value, next } = readFlagValue(argv, i);
30269
+ parsed.endpoint = value;
30270
+ i = next;
30271
+ } else if (arg === "--token" || arg.startsWith("--token=")) {
30272
+ const { value, next } = readFlagValue(argv, i);
30273
+ parsed.token = value;
30274
+ i = next;
30275
+ } else if (arg === "--name" || arg.startsWith("--name=")) {
30276
+ const { value, next } = readFlagValue(argv, i);
30277
+ if (value && value.length > 0) parsed.name = value;
30278
+ i = next;
30279
+ } else {
30280
+ parsed.error = `unknown argument "${arg}"`;
30281
+ break;
30282
+ }
30283
+ }
30284
+ return parsed;
30285
+ }
30286
+ function printLoginHelp(out) {
30287
+ out("usage: neat login [--endpoint <url>] [--token <token>] [--name <name>] [--json]");
30288
+ out(" Connect this machine to a hosted NEAT and make it the default for the");
30289
+ out(" neat CLI and the MCP server. Omit --endpoint / --token to be prompted");
30290
+ out(" (the token is read without echo). --name labels the profile (default");
30291
+ out(' "hosted"). The token can also come from NEAT_LOGIN_TOKEN.');
30292
+ out(" Exit 0 on success, 1 rejected token/endpoint, 2 misuse, 3 unreachable.");
30293
+ }
30294
+ async function probeDaemon(fetchImpl, endpoint2, token) {
30295
+ const root = endpoint2.replace(/\/$/, "");
30296
+ let res;
30297
+ try {
30298
+ res = await fetchImpl(`${root}/health`, {
30299
+ headers: { authorization: `Bearer ${token}` },
30300
+ signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS2)
30301
+ });
30302
+ } catch (err) {
30303
+ return { kind: "unreachable", detail: err.message };
30304
+ }
30305
+ if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
30306
+ if (!res.ok) return { kind: "not-neat", status: res.status };
30307
+ const contentType = res.headers.get("content-type") ?? "";
30308
+ if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
30309
+ return { kind: "ok" };
30310
+ }
30311
+ async function runLoginCommand(argv, deps = {}) {
30312
+ const out = deps.out ?? ((line) => console.log(line));
30313
+ const err = deps.err ?? ((line) => console.error(line));
30314
+ const env = deps.env ?? process.env;
30315
+ const fetchImpl = deps.fetchImpl ?? fetch;
30316
+ const readLine = deps.readLine ?? defaultReadLine;
30317
+ const readSecret = deps.readSecret ?? defaultReadSecret;
30318
+ const args = parseLoginArgs(argv);
30319
+ if (args.help) {
30320
+ printLoginHelp(out);
30321
+ return 0;
30322
+ }
30323
+ if (args.error) {
30324
+ err(`neat login: ${args.error}`);
30325
+ return 2;
30326
+ }
30327
+ let endpoint2 = args.endpoint;
30328
+ if (!endpoint2) endpoint2 = (await readLine("Hosted NEAT endpoint (https://\u2026): "))?.trim();
30329
+ if (!endpoint2) {
30330
+ err("neat login: an endpoint is required \u2014 pass --endpoint <url> or run interactively");
30331
+ return 2;
30332
+ }
30333
+ let url;
30334
+ try {
30335
+ url = new URL(endpoint2);
30336
+ } catch {
30337
+ err(`neat login: --endpoint must be an absolute URL (got "${endpoint2}")`);
30338
+ return 2;
30339
+ }
30340
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
30341
+ err(`neat login: --endpoint must be an http(s) URL (got "${url.protocol}")`);
30342
+ return 2;
30343
+ }
30344
+ const envToken = env.NEAT_LOGIN_TOKEN;
30345
+ let token = args.token ?? (envToken && envToken.length > 0 ? envToken : void 0);
30346
+ if (!token) token = (await readSecret("Daemon token: "))?.trim();
30347
+ if (!token) {
30348
+ err("neat login: a token is required \u2014 pass --token, set NEAT_LOGIN_TOKEN, or run interactively");
30349
+ return 2;
30350
+ }
30351
+ const probe = await probeDaemon(fetchImpl, endpoint2, token);
30352
+ if (probe.kind === "unreachable") {
30353
+ err(`neat login: can't reach ${endpoint2} \u2014 ${probe.detail}`);
30354
+ return 3;
30355
+ }
30356
+ if (probe.kind === "unauthorized") {
30357
+ err(`neat login: ${endpoint2} rejected the token (HTTP ${probe.status}). Check the token and try again.`);
30358
+ return 1;
30359
+ }
30360
+ if (probe.kind === "not-neat") {
30361
+ err(`neat login: ${endpoint2} answered but does not look like a NEAT daemon (HTTP ${probe.status}).`);
30362
+ return 1;
30363
+ }
30364
+ await upsertProfile(
30365
+ { name: args.name, endpoint: endpoint2, authToken: token },
30366
+ { makeActive: true, ...deps.home ? { home: deps.home } : {} }
30367
+ );
30368
+ if (args.json) {
30369
+ out(JSON.stringify({ status: "logged-in", profile: args.name, endpoint: endpoint2 }, null, 2));
30370
+ } else {
30371
+ out(`Logged in \u2014 profile "${args.name}" \u2192 ${endpoint2}`);
30372
+ out("The neat CLI and the MCP server now read this hosted graph by default.");
30373
+ out("Run `neat logout` to switch back to your local daemon.");
30374
+ }
30375
+ return 0;
30376
+ }
30377
+ function parseLogoutArgs(argv) {
30378
+ const parsed = { help: false };
30379
+ for (let i = 0; i < argv.length; i++) {
30380
+ const arg = argv[i];
30381
+ if (arg === "-h" || arg === "--help") parsed.help = true;
30382
+ else if (arg === "--name" || arg.startsWith("--name=")) {
30383
+ const { value, next } = readFlagValue(argv, i);
30384
+ parsed.name = value;
30385
+ i = next;
30386
+ } else {
30387
+ parsed.error = `unknown argument "${arg}"`;
30388
+ break;
30389
+ }
30390
+ }
30391
+ return parsed;
30392
+ }
30393
+ async function runLogoutCommand(argv, deps = {}) {
30394
+ const out = deps.out ?? ((line) => console.log(line));
30395
+ const err = deps.err ?? ((line) => console.error(line));
30396
+ const home = deps.home;
30397
+ const args = parseLogoutArgs(argv);
30398
+ if (args.help) {
30399
+ out("usage: neat logout [--name <name>]");
30400
+ out(" With no argument, clears the active hosted profile so the CLI and MCP");
30401
+ out(" server go back to your local daemon (the stored profile is kept).");
30402
+ out(" --name <name> removes that profile from ~/.neat/profiles.json entirely.");
30403
+ return 0;
30404
+ }
30405
+ if (args.error) {
30406
+ err(`neat logout: ${args.error}`);
30407
+ return 2;
30408
+ }
30409
+ if (args.name !== void 0) {
30410
+ if (args.name.length === 0) {
30411
+ err("neat logout: --name needs a profile name");
30412
+ return 2;
30413
+ }
30414
+ const removed = await removeProfile(args.name, home ?? void 0);
30415
+ if (!removed) {
30416
+ err(`neat logout: no profile named "${args.name}"`);
30417
+ return 1;
30418
+ }
30419
+ out(`Removed profile "${args.name}".`);
30420
+ return 0;
30421
+ }
30422
+ const active = await getActiveProfile(home ?? void 0);
30423
+ if (!active) {
30424
+ out("Not logged in to a hosted NEAT \u2014 the CLI is already using your local daemon.");
30425
+ return 0;
30426
+ }
30427
+ await clearActiveProfile(home ?? void 0);
30428
+ out(`Logged out of "${active.name}" (${active.endpoint}). The CLI is back on your local daemon.`);
30429
+ return 0;
30430
+ }
30431
+ async function defaultReadLine(prompt) {
30432
+ if (!process.stdin.isTTY) return void 0;
30433
+ const rl = import_promises2.default.createInterface({ input: process.stdin, output: process.stdout });
30434
+ try {
30435
+ return await rl.question(prompt);
30436
+ } finally {
30437
+ rl.close();
30438
+ }
30439
+ }
30440
+ async function defaultReadSecret(prompt) {
30441
+ const stdin = process.stdin;
30442
+ if (!stdin.isTTY) return void 0;
30443
+ process.stdout.write(prompt);
30444
+ return new Promise((resolve) => {
30445
+ const chars = [];
30446
+ stdin.setRawMode(true);
30447
+ stdin.resume();
30448
+ stdin.setEncoding("utf8");
30449
+ const cleanup = () => {
30450
+ stdin.setRawMode(false);
30451
+ stdin.pause();
30452
+ stdin.off("data", onData);
30453
+ };
30454
+ const onData = (ch) => {
30455
+ const code = ch.charCodeAt(0);
30456
+ if (ch === "\n" || ch === "\r" || code === 4) {
30457
+ cleanup();
30458
+ process.stdout.write("\n");
30459
+ resolve(chars.join(""));
30460
+ } else if (code === 3) {
30461
+ cleanup();
30462
+ process.stdout.write("\n");
30463
+ process.exit(130);
30464
+ } else if (code === 127 || ch === "\b") {
30465
+ chars.pop();
30466
+ } else {
30467
+ chars.push(ch);
30468
+ }
30469
+ };
30470
+ stdin.on("data", onData);
30471
+ });
30472
+ }
30473
+
30474
+ // src/hooks-cli.ts
30475
+ init_cjs_shims();
30476
+ var import_node_path91 = __toESM(require("path"), 1);
30477
+ var import_node_os7 = __toESM(require("os"), 1);
30478
+ var import_node_fs55 = require("fs");
30041
30479
  var import_node_url5 = require("url");
30042
30480
  var HOOK_FILENAME = "neat-search-nudge.mjs";
30043
30481
  var GUIDE_FILENAME = "GRAPH_FIRST.md";
30044
30482
  var GUIDE_INSTALL_NAME = "neat-graph-first.md";
30045
30483
  var HOOK_MATCHER = "Grep|Glob|Bash";
30046
30484
  function moduleDir() {
30047
- return typeof __dirname !== "undefined" ? __dirname : import_node_path90.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
30485
+ return typeof __dirname !== "undefined" ? __dirname : import_node_path91.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
30048
30486
  }
30049
30487
  async function readSkillAsset(rel) {
30050
30488
  const here = moduleDir();
30051
30489
  const candidates = [
30052
- import_node_path90.default.resolve(here, "../../claude-skill", rel),
30053
- import_node_path90.default.resolve(here, "../../../claude-skill", rel),
30054
- import_node_path90.default.resolve(here, "../claude-skill", rel)
30490
+ import_node_path91.default.resolve(here, "../../claude-skill", rel),
30491
+ import_node_path91.default.resolve(here, "../../../claude-skill", rel),
30492
+ import_node_path91.default.resolve(here, "../claude-skill", rel)
30055
30493
  ];
30056
30494
  for (const candidate of candidates) {
30057
30495
  try {
30058
- return await import_node_fs54.promises.readFile(candidate, "utf8");
30496
+ return await import_node_fs55.promises.readFile(candidate, "utf8");
30059
30497
  } catch {
30060
30498
  }
30061
30499
  }
@@ -30063,22 +30501,22 @@ async function readSkillAsset(rel) {
30063
30501
  `neat hooks: could not find @neat.is/claude-skill/${rel} \u2014 is the package installed?`
30064
30502
  );
30065
30503
  }
30066
- function neatHome3() {
30504
+ function neatHome4() {
30067
30505
  const override = process.env.NEAT_HOME;
30068
- if (override && override.length > 0) return import_node_path90.default.resolve(override);
30069
- return import_node_path90.default.join(import_node_os6.default.homedir(), ".neat");
30506
+ if (override && override.length > 0) return import_node_path91.default.resolve(override);
30507
+ return import_node_path91.default.join(import_node_os7.default.homedir(), ".neat");
30070
30508
  }
30071
30509
  function claudeSettingsPath() {
30072
30510
  const override = process.env.NEAT_CLAUDE_SETTINGS;
30073
- if (override && override.length > 0) return import_node_path90.default.resolve(override);
30074
- const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os6.default.homedir();
30075
- return import_node_path90.default.join(home, ".claude", "settings.json");
30511
+ if (override && override.length > 0) return import_node_path91.default.resolve(override);
30512
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os7.default.homedir();
30513
+ return import_node_path91.default.join(home, ".claude", "settings.json");
30076
30514
  }
30077
30515
  function installedHookPath() {
30078
- return import_node_path90.default.join(neatHome3(), "hooks", HOOK_FILENAME);
30516
+ return import_node_path91.default.join(neatHome4(), "hooks", HOOK_FILENAME);
30079
30517
  }
30080
30518
  function gateFlagPath() {
30081
- return import_node_path90.default.join(neatHome3(), "hooks", "gate-enabled");
30519
+ return import_node_path91.default.join(neatHome4(), "hooks", "gate-enabled");
30082
30520
  }
30083
30521
  function isNeatSearchEntry(entry2) {
30084
30522
  return (entry2.hooks ?? []).some(
@@ -30111,14 +30549,14 @@ async function runHooks(opts) {
30111
30549
  const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
30112
30550
  const guide = await readSkillAsset(GUIDE_FILENAME);
30113
30551
  const scriptPath = installedHookPath();
30114
- await import_node_fs54.promises.mkdir(import_node_path90.default.dirname(scriptPath), { recursive: true });
30115
- await import_node_fs54.promises.writeFile(scriptPath, hookScript, { mode: 493 });
30116
- const guidePath = import_node_path90.default.join(neatHome3(), GUIDE_INSTALL_NAME);
30117
- await import_node_fs54.promises.writeFile(guidePath, guide, "utf8");
30552
+ await import_node_fs55.promises.mkdir(import_node_path91.default.dirname(scriptPath), { recursive: true });
30553
+ await import_node_fs55.promises.writeFile(scriptPath, hookScript, { mode: 493 });
30554
+ const guidePath = import_node_path91.default.join(neatHome4(), GUIDE_INSTALL_NAME);
30555
+ await import_node_fs55.promises.writeFile(guidePath, guide, "utf8");
30118
30556
  const settingsFile = claudeSettingsPath();
30119
30557
  let settings = {};
30120
30558
  try {
30121
- settings = JSON.parse(await import_node_fs54.promises.readFile(settingsFile, "utf8"));
30559
+ settings = JSON.parse(await import_node_fs55.promises.readFile(settingsFile, "utf8"));
30122
30560
  } catch (err) {
30123
30561
  if (err.code !== "ENOENT") {
30124
30562
  console.error(
@@ -30140,14 +30578,14 @@ async function runHooks(opts) {
30140
30578
  ...settings,
30141
30579
  hooks: { ...hooks, PreToolUse: preToolUse }
30142
30580
  };
30143
- await import_node_fs54.promises.mkdir(import_node_path90.default.dirname(settingsFile), { recursive: true });
30144
- await import_node_fs54.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
30581
+ await import_node_fs55.promises.mkdir(import_node_path91.default.dirname(settingsFile), { recursive: true });
30582
+ await import_node_fs55.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
30145
30583
  const flag = gateFlagPath();
30146
30584
  if (opts.gate) {
30147
- await import_node_fs54.promises.mkdir(import_node_path90.default.dirname(flag), { recursive: true });
30148
- await import_node_fs54.promises.writeFile(flag, "1\n", "utf8");
30585
+ await import_node_fs55.promises.mkdir(import_node_path91.default.dirname(flag), { recursive: true });
30586
+ await import_node_fs55.promises.writeFile(flag, "1\n", "utf8");
30149
30587
  } else {
30150
- await import_node_fs54.promises.rm(flag, { force: true });
30588
+ await import_node_fs55.promises.rm(flag, { force: true });
30151
30589
  }
30152
30590
  const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
30153
30591
  console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
@@ -30237,8 +30675,8 @@ async function runHooksCommand(args) {
30237
30675
 
30238
30676
  // src/claude-cli.ts
30239
30677
  init_cjs_shims();
30240
- var import_node_path91 = __toESM(require("path"), 1);
30241
- var import_node_fs55 = require("fs");
30678
+ var import_node_path92 = __toESM(require("path"), 1);
30679
+ var import_node_fs56 = require("fs");
30242
30680
  var NEAT_SECTION_HEADING = "## neat";
30243
30681
  var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
30244
30682
  code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
@@ -30272,8 +30710,8 @@ ${NEAT_DIRECTIVE_BODY}
30272
30710
  }
30273
30711
  function claudeMdPath() {
30274
30712
  const override = process.env.NEAT_CLAUDE_MD;
30275
- if (override && override.length > 0) return import_node_path91.default.resolve(override);
30276
- return import_node_path91.default.join(process.cwd(), "CLAUDE.md");
30713
+ if (override && override.length > 0) return import_node_path92.default.resolve(override);
30714
+ return import_node_path92.default.join(process.cwd(), "CLAUDE.md");
30277
30715
  }
30278
30716
  function splitAroundSection(raw) {
30279
30717
  const lines = raw.split("\n");
@@ -30301,7 +30739,7 @@ function compose(before, after) {
30301
30739
  }
30302
30740
  async function readIfExists2(file) {
30303
30741
  try {
30304
- return await import_node_fs55.promises.readFile(file, "utf8");
30742
+ return await import_node_fs56.promises.readFile(file, "utf8");
30305
30743
  } catch (err) {
30306
30744
  if (err.code === "ENOENT") return null;
30307
30745
  throw err;
@@ -30312,8 +30750,8 @@ async function runInstall() {
30312
30750
  const raw = await readIfExists2(file) ?? "";
30313
30751
  const { before, after, found } = splitAroundSection(raw);
30314
30752
  const next = compose(before, after);
30315
- await import_node_fs55.promises.mkdir(import_node_path91.default.dirname(file), { recursive: true });
30316
- await import_node_fs55.promises.writeFile(file, next, "utf8");
30753
+ await import_node_fs56.promises.mkdir(import_node_path92.default.dirname(file), { recursive: true });
30754
+ await import_node_fs56.promises.writeFile(file, next, "utf8");
30317
30755
  const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
30318
30756
  console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
30319
30757
  console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
@@ -30334,7 +30772,7 @@ async function runUninstall() {
30334
30772
  }
30335
30773
  const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
30336
30774
  const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
30337
- await import_node_fs55.promises.writeFile(file, next, "utf8");
30775
+ await import_node_fs56.promises.writeFile(file, next, "utf8");
30338
30776
  console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
30339
30777
  return { exitCode: 0 };
30340
30778
  }
@@ -30377,9 +30815,9 @@ async function runClaudeCommand(args) {
30377
30815
 
30378
30816
  // src/codex-cli.ts
30379
30817
  init_cjs_shims();
30380
- var import_node_path92 = __toESM(require("path"), 1);
30381
- var import_node_os7 = __toESM(require("os"), 1);
30382
- var import_node_fs56 = require("fs");
30818
+ var import_node_path93 = __toESM(require("path"), 1);
30819
+ var import_node_os8 = __toESM(require("os"), 1);
30820
+ var import_node_fs57 = require("fs");
30383
30821
  var import_node_util = require("util");
30384
30822
  var import_smol_toml6 = require("smol-toml");
30385
30823
  var CODEX_MCP_SERVER = {
@@ -30397,14 +30835,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
30397
30835
  var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
30398
30836
  function codexConfigPath() {
30399
30837
  const override = process.env.NEAT_CODEX_CONFIG;
30400
- if (override && override.length > 0) return import_node_path92.default.resolve(override);
30401
- const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os7.default.homedir();
30402
- return import_node_path92.default.join(home, ".codex", "config.toml");
30838
+ if (override && override.length > 0) return import_node_path93.default.resolve(override);
30839
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os8.default.homedir();
30840
+ return import_node_path93.default.join(home, ".codex", "config.toml");
30403
30841
  }
30404
30842
  function agentsFilePath() {
30405
30843
  const override = process.env.NEAT_CODEX_AGENTS;
30406
- if (override && override.length > 0) return import_node_path92.default.resolve(override);
30407
- return import_node_path92.default.join(process.cwd(), "AGENTS.md");
30844
+ if (override && override.length > 0) return import_node_path93.default.resolve(override);
30845
+ return import_node_path93.default.join(process.cwd(), "AGENTS.md");
30408
30846
  }
30409
30847
  function isTableHeader(line) {
30410
30848
  return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
@@ -30538,7 +30976,7 @@ async function runCodex(opts) {
30538
30976
  const agentsPath = agentsFilePath();
30539
30977
  let configRaw = "";
30540
30978
  try {
30541
- configRaw = await import_node_fs56.promises.readFile(configPath, "utf8");
30979
+ configRaw = await import_node_fs57.promises.readFile(configPath, "utf8");
30542
30980
  } catch (err) {
30543
30981
  if (err.code !== "ENOENT") {
30544
30982
  console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
@@ -30547,7 +30985,7 @@ async function runCodex(opts) {
30547
30985
  }
30548
30986
  let agentsRaw = "";
30549
30987
  try {
30550
- agentsRaw = await import_node_fs56.promises.readFile(agentsPath, "utf8");
30988
+ agentsRaw = await import_node_fs57.promises.readFile(agentsPath, "utf8");
30551
30989
  } catch (err) {
30552
30990
  if (err.code !== "ENOENT") {
30553
30991
  console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
@@ -30587,15 +31025,15 @@ async function runCodex(opts) {
30587
31025
  return { exitCode: 0 };
30588
31026
  }
30589
31027
  if (config.changed) {
30590
- await import_node_fs56.promises.mkdir(import_node_path92.default.dirname(configPath), { recursive: true });
30591
- await import_node_fs56.promises.writeFile(configPath, config.text, "utf8");
31028
+ await import_node_fs57.promises.mkdir(import_node_path93.default.dirname(configPath), { recursive: true });
31029
+ await import_node_fs57.promises.writeFile(configPath, config.text, "utf8");
30592
31030
  console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
30593
31031
  } else {
30594
31032
  console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
30595
31033
  }
30596
31034
  if (agents.changed) {
30597
- await import_node_fs56.promises.mkdir(import_node_path92.default.dirname(agentsPath), { recursive: true });
30598
- await import_node_fs56.promises.writeFile(agentsPath, agents.text, "utf8");
31035
+ await import_node_fs57.promises.mkdir(import_node_path93.default.dirname(agentsPath), { recursive: true });
31036
+ await import_node_fs57.promises.writeFile(agentsPath, agents.text, "utf8");
30599
31037
  console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
30600
31038
  } else {
30601
31039
  console.log(`neat codex: ${agentsPath} already has the graph-first block`);
@@ -30652,9 +31090,9 @@ async function runCodexCommand(args) {
30652
31090
 
30653
31091
  // src/editors-cli.ts
30654
31092
  init_cjs_shims();
30655
- var import_node_path93 = __toESM(require("path"), 1);
30656
- var import_node_os8 = __toESM(require("os"), 1);
30657
- var import_node_fs57 = require("fs");
31093
+ var import_node_path94 = __toESM(require("path"), 1);
31094
+ var import_node_os9 = __toESM(require("os"), 1);
31095
+ var import_node_fs58 = require("fs");
30658
31096
  var import_node_util2 = require("util");
30659
31097
  var jsonc = __toESM(require("jsonc-parser"), 1);
30660
31098
  var NEAT_MCP_SERVER = {
@@ -30674,21 +31112,21 @@ var NEAT_CRUSH_SERVER = {
30674
31112
  var GRAPH_FIRST_MARKER_OPEN = "<!-- neat:graph-first -->";
30675
31113
  var GRAPH_FIRST_MARKER_CLOSE = "<!-- /neat:graph-first -->";
30676
31114
  function homeDir() {
30677
- return process.env.HOME ?? process.env.USERPROFILE ?? import_node_os8.default.homedir();
31115
+ return process.env.HOME ?? process.env.USERPROFILE ?? import_node_os9.default.homedir();
30678
31116
  }
30679
31117
  function xdgConfigDir() {
30680
31118
  const xdg = process.env.XDG_CONFIG_HOME;
30681
- return xdg && xdg.length > 0 ? import_node_path93.default.resolve(xdg) : import_node_path93.default.join(homeDir(), ".config");
31119
+ return xdg && xdg.length > 0 ? import_node_path94.default.resolve(xdg) : import_node_path94.default.join(homeDir(), ".config");
30682
31120
  }
30683
31121
  function envOverride(name) {
30684
31122
  const v = process.env[name];
30685
- return v && v.length > 0 ? import_node_path93.default.resolve(v) : void 0;
31123
+ return v && v.length > 0 ? import_node_path94.default.resolve(v) : void 0;
30686
31124
  }
30687
31125
  var CURSOR_CLIENT = {
30688
31126
  id: "cursor",
30689
31127
  label: "Cursor",
30690
31128
  docsUrl: "https://docs.cursor.com/context/mcp",
30691
- mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path93.default.join(homeDir(), ".cursor", "mcp.json"),
31129
+ mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path94.default.join(homeDir(), ".cursor", "mcp.json"),
30692
31130
  mcpContainerKey: "mcpServers",
30693
31131
  format: "json",
30694
31132
  // Cursor still reads a single `.cursorrules` at the project root (the modern
@@ -30700,7 +31138,7 @@ var DEVIN_CLIENT = {
30700
31138
  id: "devin",
30701
31139
  label: "Devin Desktop (Cascade)",
30702
31140
  docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
30703
- mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path93.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
31141
+ mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path94.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
30704
31142
  mcpContainerKey: "mcpServers",
30705
31143
  format: "json",
30706
31144
  rulesFileName: ".windsurfrules"
@@ -30709,7 +31147,7 @@ var GEMINI_CLIENT = {
30709
31147
  id: "gemini",
30710
31148
  label: "Gemini CLI",
30711
31149
  docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
30712
- mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path93.default.join(homeDir(), ".gemini", "settings.json"),
31150
+ mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path94.default.join(homeDir(), ".gemini", "settings.json"),
30713
31151
  mcpContainerKey: "mcpServers",
30714
31152
  format: "json",
30715
31153
  rulesFileName: "GEMINI.md"
@@ -30718,7 +31156,7 @@ var QWEN_CLIENT = {
30718
31156
  id: "qwen",
30719
31157
  label: "Qwen Code",
30720
31158
  docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
30721
- mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path93.default.join(homeDir(), ".qwen", "settings.json"),
31159
+ mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path94.default.join(homeDir(), ".qwen", "settings.json"),
30722
31160
  mcpContainerKey: "mcpServers",
30723
31161
  format: "json",
30724
31162
  rulesFileName: "QWEN.md"
@@ -30727,7 +31165,7 @@ var AMAZONQ_CLIENT = {
30727
31165
  id: "amazonq",
30728
31166
  label: "Amazon Q Developer CLI",
30729
31167
  docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
30730
- mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path93.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
31168
+ mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path94.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
30731
31169
  mcpContainerKey: "mcpServers",
30732
31170
  format: "json"
30733
31171
  };
@@ -30735,7 +31173,7 @@ var ROOCODE_CLIENT = {
30735
31173
  id: "roocode",
30736
31174
  label: "Roo Code",
30737
31175
  docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
30738
- mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path93.default.join(process.cwd(), ".roo", "mcp.json"),
31176
+ mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path94.default.join(process.cwd(), ".roo", "mcp.json"),
30739
31177
  mcpContainerKey: "mcpServers",
30740
31178
  format: "json"
30741
31179
  };
@@ -30748,9 +31186,9 @@ var ZED_CLIENT = {
30748
31186
  if (override) return override;
30749
31187
  if (process.platform === "win32") {
30750
31188
  const appData = process.env.APPDATA;
30751
- if (appData && appData.length > 0) return import_node_path93.default.join(appData, "Zed", "settings.json");
31189
+ if (appData && appData.length > 0) return import_node_path94.default.join(appData, "Zed", "settings.json");
30752
31190
  }
30753
- return import_node_path93.default.join(homeDir(), ".config", "zed", "settings.json");
31191
+ return import_node_path94.default.join(homeDir(), ".config", "zed", "settings.json");
30754
31192
  },
30755
31193
  mcpContainerKey: "context_servers",
30756
31194
  format: "jsonc",
@@ -30760,7 +31198,7 @@ var OPENCODE_CLIENT = {
30760
31198
  id: "opencode",
30761
31199
  label: "OpenCode",
30762
31200
  docsUrl: "https://opencode.ai/docs/mcp-servers/",
30763
- mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path93.default.join(xdgConfigDir(), "opencode", "opencode.json"),
31201
+ mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path94.default.join(xdgConfigDir(), "opencode", "opencode.json"),
30764
31202
  mcpContainerKey: "mcp",
30765
31203
  format: "json",
30766
31204
  serverEntry: NEAT_OPENCODE_SERVER,
@@ -30770,7 +31208,7 @@ var CRUSH_CLIENT = {
30770
31208
  id: "crush",
30771
31209
  label: "Crush",
30772
31210
  docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
30773
- mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path93.default.join(xdgConfigDir(), "crush", "crush.json"),
31211
+ mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path94.default.join(xdgConfigDir(), "crush", "crush.json"),
30774
31212
  mcpContainerKey: "mcp",
30775
31213
  format: "json",
30776
31214
  serverEntry: NEAT_CRUSH_SERVER,
@@ -30833,7 +31271,7 @@ async function planMcp(client, mcpPath) {
30833
31271
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
30834
31272
  let raw = "";
30835
31273
  try {
30836
- raw = await import_node_fs57.promises.readFile(mcpPath, "utf8");
31274
+ raw = await import_node_fs58.promises.readFile(mcpPath, "utf8");
30837
31275
  } catch (err) {
30838
31276
  const e = err;
30839
31277
  if (e.code === "ENOENT") {
@@ -30875,7 +31313,7 @@ async function runEditorInstall(client, opts) {
30875
31313
  const mcpPath = client.mcpConfigPath();
30876
31314
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
30877
31315
  const hasRules = typeof client.rulesFileName === "string";
30878
- const rulesPath = hasRules ? import_node_path93.default.join(opts.projectDir, client.rulesFileName) : "";
31316
+ const rulesPath = hasRules ? import_node_path94.default.join(opts.projectDir, client.rulesFileName) : "";
30879
31317
  const mcp = await planMcp(client, mcpPath);
30880
31318
  if (mcp === null) return { exitCode: 1 };
30881
31319
  let existingRules = "";
@@ -30884,7 +31322,7 @@ async function runEditorInstall(client, opts) {
30884
31322
  let block = "";
30885
31323
  if (hasRules) {
30886
31324
  try {
30887
- existingRules = await import_node_fs57.promises.readFile(rulesPath, "utf8");
31325
+ existingRules = await import_node_fs58.promises.readFile(rulesPath, "utf8");
30888
31326
  } catch (err) {
30889
31327
  if (err.code !== "ENOENT") {
30890
31328
  console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
@@ -30918,11 +31356,11 @@ async function runEditorInstall(client, opts) {
30918
31356
  );
30919
31357
  return { exitCode: 0 };
30920
31358
  }
30921
- await import_node_fs57.promises.mkdir(import_node_path93.default.dirname(mcpPath), { recursive: true });
30922
- await import_node_fs57.promises.writeFile(mcpPath, mcp.text, "utf8");
31359
+ await import_node_fs58.promises.mkdir(import_node_path94.default.dirname(mcpPath), { recursive: true });
31360
+ await import_node_fs58.promises.writeFile(mcpPath, mcp.text, "utf8");
30923
31361
  if (hasRules) {
30924
- await import_node_fs57.promises.mkdir(import_node_path93.default.dirname(rulesPath), { recursive: true });
30925
- await import_node_fs57.promises.writeFile(rulesPath, newRules, "utf8");
31362
+ await import_node_fs58.promises.mkdir(import_node_path94.default.dirname(rulesPath), { recursive: true });
31363
+ await import_node_fs58.promises.writeFile(rulesPath, newRules, "utf8");
30926
31364
  }
30927
31365
  console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
30928
31366
  console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
@@ -31415,7 +31853,7 @@ function sleep(ms, signal) {
31415
31853
 
31416
31854
  // src/cli-verbs.ts
31417
31855
  init_cjs_shims();
31418
- var import_node_path94 = __toESM(require("path"), 1);
31856
+ var import_node_path95 = __toESM(require("path"), 1);
31419
31857
  async function resolveProjectEntry(opts) {
31420
31858
  const entries = await listProjects();
31421
31859
  if (opts.project) {
@@ -31425,7 +31863,7 @@ async function resolveProjectEntry(opts) {
31425
31863
  const cwd = opts.cwd ?? process.cwd();
31426
31864
  const resolvedCwd = await normalizeProjectPath(cwd);
31427
31865
  for (const entry2 of entries) {
31428
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path94.default.sep}`)) {
31866
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path95.default.sep}`)) {
31429
31867
  return entry2;
31430
31868
  }
31431
31869
  }
@@ -31730,6 +32168,12 @@ function usage5() {
31730
32168
  console.log(" doctor Preflight this directory's setup \u2014 Node version, project,");
31731
32169
  console.log(" and daemon reachability \u2014 and print a fix for anything down.");
31732
32170
  console.log(" Flags: --json. Exits 0 when all pass, 1 when a check fails.");
32171
+ console.log(" login Connect this machine to a hosted NEAT and make it the default");
32172
+ console.log(" for the CLI and the MCP server. Paste the daemon endpoint +");
32173
+ console.log(" token, or run interactively (the token is read without echo).");
32174
+ console.log(" Flags: --endpoint <url>, --token <token>, --name <name>, --json.");
32175
+ console.log(" logout Clear the active hosted profile (back to your local daemon);");
32176
+ console.log(" --name <name> removes that profile entirely.");
31733
32177
  console.log("");
31734
32178
  console.log("query commands (mirror the MCP tools, ADR-050):");
31735
32179
  console.log(" ask <question> Plain-language door: resolves the question to");
@@ -31780,6 +32224,7 @@ function usage5() {
31780
32224
  }
31781
32225
  var STRING_FLAGS = [
31782
32226
  ["--project", "project"],
32227
+ ["--profile", "profile"],
31783
32228
  ["--depth", "depth"],
31784
32229
  ["--limit", "limit"],
31785
32230
  ["--edge-type", "edgeType"],
@@ -31797,6 +32242,7 @@ function parseArgs(rest) {
31797
32242
  const positional = [];
31798
32243
  const out = {
31799
32244
  project: null,
32245
+ profile: null,
31800
32246
  apply: false,
31801
32247
  dryRun: false,
31802
32248
  noInstall: false,
@@ -31955,7 +32401,7 @@ async function buildPatchSections(services, project) {
31955
32401
  }
31956
32402
  async function runInit(opts) {
31957
32403
  const written = [];
31958
- const stat = await import_node_fs58.promises.stat(opts.scanPath).catch(() => null);
32404
+ const stat = await import_node_fs59.promises.stat(opts.scanPath).catch(() => null);
31959
32405
  if (!stat || !stat.isDirectory()) {
31960
32406
  console.error(`neat init: ${opts.scanPath} is not a directory`);
31961
32407
  return { exitCode: 2, writtenFiles: written };
@@ -31964,13 +32410,13 @@ async function runInit(opts) {
31964
32410
  printDiscoveryReport(opts, services);
31965
32411
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
31966
32412
  const patch = renderPatch(sections);
31967
- const patchPath = import_node_path95.default.join(opts.scanPath, "neat.patch");
32413
+ const patchPath = import_node_path96.default.join(opts.scanPath, "neat.patch");
31968
32414
  if (opts.dryRun) {
31969
- await import_node_fs58.promises.writeFile(patchPath, patch, "utf8");
32415
+ await import_node_fs59.promises.writeFile(patchPath, patch, "utf8");
31970
32416
  written.push(patchPath);
31971
32417
  console.log(`dry-run: patch written to ${patchPath}`);
31972
- const gitignorePath = import_node_path95.default.join(opts.scanPath, ".gitignore");
31973
- const gitignoreExists = await import_node_fs58.promises.stat(gitignorePath).then(() => true).catch(() => false);
32418
+ const gitignorePath = import_node_path96.default.join(opts.scanPath, ".gitignore");
32419
+ const gitignoreExists = await import_node_fs59.promises.stat(gitignorePath).then(() => true).catch(() => false);
31974
32420
  const verb = gitignoreExists ? "append" : "create";
31975
32421
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
31976
32422
  console.log("rerun without --dry-run to register and snapshot.");
@@ -31981,9 +32427,9 @@ async function runInit(opts) {
31981
32427
  const graph = getGraph(graphKey);
31982
32428
  const projectPaths = pathsForProject(
31983
32429
  graphKey,
31984
- import_node_path95.default.join(opts.scanPath, "neat-out")
32430
+ import_node_path96.default.join(opts.scanPath, "neat-out")
31985
32431
  );
31986
- const errorsPath = import_node_path95.default.join(import_node_path95.default.dirname(opts.outPath), import_node_path95.default.basename(projectPaths.errorsPath));
32432
+ const errorsPath = import_node_path96.default.join(import_node_path96.default.dirname(opts.outPath), import_node_path96.default.basename(projectPaths.errorsPath));
31987
32433
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
31988
32434
  await saveGraphToDisk(graph, opts.outPath);
31989
32435
  written.push(opts.outPath);
@@ -32062,7 +32508,7 @@ async function runInit(opts) {
32062
32508
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
32063
32509
  }
32064
32510
  } else {
32065
- await import_node_fs58.promises.writeFile(patchPath, patch, "utf8");
32511
+ await import_node_fs59.promises.writeFile(patchPath, patch, "utf8");
32066
32512
  written.push(patchPath);
32067
32513
  }
32068
32514
  }
@@ -32103,9 +32549,9 @@ var CLAUDE_SKILL_CONFIG = {
32103
32549
  };
32104
32550
  function claudeConfigPath() {
32105
32551
  const override = process.env.NEAT_CLAUDE_CONFIG;
32106
- if (override && override.length > 0) return import_node_path95.default.resolve(override);
32552
+ if (override && override.length > 0) return import_node_path96.default.resolve(override);
32107
32553
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
32108
- return import_node_path95.default.join(home, ".claude.json");
32554
+ return import_node_path96.default.join(home, ".claude.json");
32109
32555
  }
32110
32556
  async function runSkill(opts) {
32111
32557
  const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -32117,7 +32563,7 @@ async function runSkill(opts) {
32117
32563
  const target = claudeConfigPath();
32118
32564
  let existing = {};
32119
32565
  try {
32120
- existing = JSON.parse(await import_node_fs58.promises.readFile(target, "utf8"));
32566
+ existing = JSON.parse(await import_node_fs59.promises.readFile(target, "utf8"));
32121
32567
  } catch (err) {
32122
32568
  if (err.code !== "ENOENT") {
32123
32569
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -32129,8 +32575,8 @@ async function runSkill(opts) {
32129
32575
  ...existing,
32130
32576
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
32131
32577
  };
32132
- await import_node_fs58.promises.mkdir(import_node_path95.default.dirname(target), { recursive: true });
32133
- await import_node_fs58.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
32578
+ await import_node_fs59.promises.mkdir(import_node_path96.default.dirname(target), { recursive: true });
32579
+ await import_node_fs59.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
32134
32580
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
32135
32581
  console.log("restart Claude Code to pick up the new MCP server.");
32136
32582
  console.log("");
@@ -32174,6 +32620,16 @@ async function main() {
32174
32620
  if (code !== 0) process.exit(code);
32175
32621
  return;
32176
32622
  }
32623
+ if (cmd0 === "login") {
32624
+ const code = await runLoginCommand(argv.slice(1));
32625
+ if (code !== 0) process.exit(code);
32626
+ return;
32627
+ }
32628
+ if (cmd0 === "logout") {
32629
+ const code = await runLogoutCommand(argv.slice(1));
32630
+ if (code !== 0) process.exit(code);
32631
+ return;
32632
+ }
32177
32633
  if (cmd0 === "hooks") {
32178
32634
  const code = await runHooksCommand(argv.slice(1));
32179
32635
  if (code !== 0) process.exit(code);
@@ -32226,12 +32682,12 @@ async function main() {
32226
32682
  console.error("neat init: --apply and --dry-run are mutually exclusive");
32227
32683
  process.exit(2);
32228
32684
  }
32229
- const scanPath = import_node_path95.default.resolve(target);
32685
+ const scanPath = import_node_path96.default.resolve(target);
32230
32686
  const projectExplicit = parsed.project !== null;
32231
- const projectName = projectExplicit ? project : import_node_path95.default.basename(scanPath);
32687
+ const projectName = projectExplicit ? project : import_node_path96.default.basename(scanPath);
32232
32688
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
32233
- const fallback = pathsForProject(projectKey, import_node_path95.default.join(scanPath, "neat-out")).snapshotPath;
32234
- const outPath = import_node_path95.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
32689
+ const fallback = pathsForProject(projectKey, import_node_path96.default.join(scanPath, "neat-out")).snapshotPath;
32690
+ const outPath = import_node_path96.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
32235
32691
  const result = await runInit({
32236
32692
  scanPath,
32237
32693
  outPath,
@@ -32252,21 +32708,21 @@ async function main() {
32252
32708
  usage5();
32253
32709
  process.exit(2);
32254
32710
  }
32255
- const scanPath = import_node_path95.default.resolve(target);
32256
- const stat = await import_node_fs58.promises.stat(scanPath).catch(() => null);
32711
+ const scanPath = import_node_path96.default.resolve(target);
32712
+ const stat = await import_node_fs59.promises.stat(scanPath).catch(() => null);
32257
32713
  if (!stat || !stat.isDirectory()) {
32258
32714
  console.error(`neat watch: ${scanPath} is not a directory`);
32259
32715
  process.exit(2);
32260
32716
  }
32261
- const projectPaths = pathsForProject(project, import_node_path95.default.join(scanPath, "neat-out"));
32262
- const outPath = import_node_path95.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
32263
- const errorsPath = import_node_path95.default.resolve(
32264
- process.env.NEAT_ERRORS_PATH ?? import_node_path95.default.join(import_node_path95.default.dirname(outPath), import_node_path95.default.basename(projectPaths.errorsPath))
32717
+ const projectPaths = pathsForProject(project, import_node_path96.default.join(scanPath, "neat-out"));
32718
+ const outPath = import_node_path96.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
32719
+ const errorsPath = import_node_path96.default.resolve(
32720
+ process.env.NEAT_ERRORS_PATH ?? import_node_path96.default.join(import_node_path96.default.dirname(outPath), import_node_path96.default.basename(projectPaths.errorsPath))
32265
32721
  );
32266
- const staleEventsPath = import_node_path95.default.resolve(
32267
- process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path95.default.join(import_node_path95.default.dirname(outPath), import_node_path95.default.basename(projectPaths.staleEventsPath))
32722
+ const staleEventsPath = import_node_path96.default.resolve(
32723
+ process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path96.default.join(import_node_path96.default.dirname(outPath), import_node_path96.default.basename(projectPaths.staleEventsPath))
32268
32724
  );
32269
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path95.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
32725
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path96.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
32270
32726
  const handle = await startWatch(getGraph(project), {
32271
32727
  scanPath,
32272
32728
  outPath,
@@ -32275,7 +32731,7 @@ async function main() {
32275
32731
  project,
32276
32732
  // Resolve NEAT_HOME so a `neat watch` picks up connectors added to
32277
32733
  // ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
32278
- neatHome: process.env.NEAT_HOME ? import_node_path95.default.resolve(process.env.NEAT_HOME) : import_node_path95.default.join(import_node_os9.default.homedir(), ".neat"),
32734
+ neatHome: process.env.NEAT_HOME ? import_node_path96.default.resolve(process.env.NEAT_HOME) : import_node_path96.default.join(import_node_os10.default.homedir(), ".neat"),
32279
32735
  ...embeddingsCachePath ? { embeddingsCachePath } : {},
32280
32736
  host: process.env.HOST ?? "0.0.0.0",
32281
32737
  port: Number(process.env.PORT ?? 8080),
@@ -32457,11 +32913,11 @@ async function main() {
32457
32913
  process.exit(1);
32458
32914
  }
32459
32915
  async function tryOrchestrator(cmd, parsed) {
32460
- const scanPath = import_node_path95.default.resolve(cmd);
32461
- const stat = await import_node_fs58.promises.stat(scanPath).catch(() => null);
32916
+ const scanPath = import_node_path96.default.resolve(cmd);
32917
+ const stat = await import_node_fs59.promises.stat(scanPath).catch(() => null);
32462
32918
  if (!stat || !stat.isDirectory()) return null;
32463
32919
  const projectExplicit = parsed.project !== null;
32464
- const projectName = projectExplicit ? parsed.project : import_node_path95.default.basename(scanPath);
32920
+ const projectName = projectExplicit ? parsed.project : import_node_path96.default.basename(scanPath);
32465
32921
  const result = await runOrchestrator({
32466
32922
  scanPath,
32467
32923
  project: projectName,
@@ -32524,19 +32980,50 @@ Pass --project <name> to choose:
32524
32980
  ${names}`
32525
32981
  );
32526
32982
  }
32527
- async function resolveDaemonUrl(project) {
32528
- const explicit = process.env.NEAT_API_URL ?? process.env.NEAT_CORE_URL;
32529
- if (explicit) return explicit;
32530
- if (project) {
32531
- const daemon = await findDaemonByProject(project);
32532
- if (daemon) return `http://localhost:${daemon.record.ports.rest}`;
32983
+ var UnknownProfileError = class extends Error {
32984
+ constructor(name) {
32985
+ super(
32986
+ `no profile named "${name}" in ~/.neat/profiles.json \u2014 run \`neat login\` first, or drop --profile / NEAT_PROFILE`
32987
+ );
32988
+ this.name = "UnknownProfileError";
32533
32989
  }
32534
- return "http://localhost:8080";
32990
+ };
32991
+ async function resolveClientTarget(opts = {}) {
32992
+ const named2 = opts.profile ?? process.env.NEAT_PROFILE;
32993
+ if (named2 && named2.length > 0) {
32994
+ const profile = await resolveProfile(named2);
32995
+ if (!profile) throw new UnknownProfileError(named2);
32996
+ return { endpoint: profile.endpoint, authToken: profile.authToken, source: "profile" };
32997
+ }
32998
+ const pin = process.env.NEAT_API_URL ?? process.env.NEAT_CORE_URL;
32999
+ if (pin) return { endpoint: pin, authToken: resolveAuthToken(), source: "env" };
33000
+ const active = await getActiveProfile().catch(() => void 0);
33001
+ if (active) return { endpoint: active.endpoint, authToken: active.authToken, source: "active" };
33002
+ if (opts.project) {
33003
+ const daemon = await findDaemonByProject(opts.project);
33004
+ if (daemon) {
33005
+ return { endpoint: `http://localhost:${daemon.record.ports.rest}`, source: "daemon-record" };
33006
+ }
33007
+ }
33008
+ return { endpoint: "http://localhost:8080", source: "default" };
33009
+ }
33010
+ async function resolveDaemonUrl(project, profile) {
33011
+ return (await resolveClientTarget({ project, profile })).endpoint;
32535
33012
  }
32536
33013
  async function runQueryVerb(cmd, parsed) {
32537
33014
  const requestedProject = resolveProjectFlag(parsed);
32538
- const baseUrl = await resolveDaemonUrl(requestedProject);
32539
- const client = createHttpClient(baseUrl, resolveAuthToken());
33015
+ let target;
33016
+ try {
33017
+ target = await resolveClientTarget({ project: requestedProject, profile: parsed.profile ?? void 0 });
33018
+ } catch (err) {
33019
+ if (err instanceof UnknownProfileError) {
33020
+ process.stderr.write(`${err.message}
33021
+ `);
33022
+ return 2;
33023
+ }
33024
+ throw err;
33025
+ }
33026
+ const client = createHttpClient(target.endpoint, target.authToken);
32540
33027
  const positional = parsed.positional;
32541
33028
  let makeWork;
32542
33029
  switch (cmd) {
@@ -32700,7 +33187,7 @@ async function runQueryVerb(cmd, parsed) {
32700
33187
  const detail = err.responseBody.length > 0 ? err.responseBody : err.message;
32701
33188
  console.error(`neat ${cmd}: ${detail.trim()}`);
32702
33189
  } else if (err instanceof TransportError) {
32703
- console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${baseUrl})`);
33190
+ console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${target.endpoint})`);
32704
33191
  } else {
32705
33192
  console.error(`neat ${cmd}: ${err.message}`);
32706
33193
  }
@@ -32709,9 +33196,14 @@ async function runQueryVerb(cmd, parsed) {
32709
33196
  }
32710
33197
  async function runMonitorVerb(parsed) {
32711
33198
  const requestedProject = resolveProjectFlag(parsed);
32712
- const baseUrl = await resolveDaemonUrl(requestedProject);
32713
- const token = resolveAuthToken();
32714
- const client = createHttpClient(baseUrl, token);
33199
+ let target;
33200
+ try {
33201
+ target = await resolveClientTarget({ project: requestedProject, profile: parsed.profile ?? void 0 });
33202
+ } catch (err) {
33203
+ if (err instanceof UnknownProfileError) return 0;
33204
+ throw err;
33205
+ }
33206
+ const client = createHttpClient(target.endpoint, target.authToken);
32715
33207
  let project;
32716
33208
  try {
32717
33209
  project = await resolveProjectForVerb(client, parsed);
@@ -32727,10 +33219,10 @@ async function runMonitorVerb(parsed) {
32727
33219
  process.once("SIGTERM", onSignal);
32728
33220
  try {
32729
33221
  return await runMonitor({
32730
- baseUrl,
33222
+ baseUrl: target.endpoint,
32731
33223
  project,
32732
33224
  json: parsed.json,
32733
- authToken: token,
33225
+ authToken: target.authToken,
32734
33226
  signal: controller.signal
32735
33227
  });
32736
33228
  } finally {
@@ -32750,12 +33242,14 @@ if (/[\\/](?:cli\.(?:cjs|js)|cli|neat|neat\.is)$/.test(entry)) {
32750
33242
  CLAUDE_SKILL_CONFIG,
32751
33243
  ProjectResolutionError,
32752
33244
  QUERY_VERBS,
33245
+ UnknownProfileError,
32753
33246
  commandPrefix,
32754
33247
  isNpxInvocation,
32755
33248
  main,
32756
33249
  parseArgs,
32757
33250
  printBanner,
32758
33251
  readPackageVersion,
33252
+ resolveClientTarget,
32759
33253
  resolveDaemonUrl,
32760
33254
  resolveProjectForVerb,
32761
33255
  runInit,