@neat.is/core 0.10.1-dev.20260928 → 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.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  resolveNeatVersion,
9
9
  startK8sSubstratePolling,
10
10
  writeDaemonRecord
11
- } from "./chunk-HVBD4ULO.js";
11
+ } from "./chunk-AWIK6T3G.js";
12
12
  import {
13
13
  buildSearchIndex
14
14
  } from "./chunk-BC53SCT7.js";
@@ -78,23 +78,24 @@ import {
78
78
  startStalenessLoop,
79
79
  upsertConnectorEntry,
80
80
  validateConnectorEntry
81
- } from "./chunk-CNOJ4T6I.js";
81
+ } from "./chunk-E3YFFIHE.js";
82
82
  import {
83
83
  startOtelGrpcReceiver
84
- } from "./chunk-ERE47MCR.js";
84
+ } from "./chunk-5SJHA5ZH.js";
85
85
  import {
86
86
  __dirname,
87
87
  __require,
88
88
  assertBindAuthority,
89
89
  buildOtelReceiver,
90
+ isLoopbackHost,
90
91
  listenSteppingOtlp,
91
92
  readAuthEnv
92
- } from "./chunk-GDGUY4T6.js";
93
+ } from "./chunk-M7CGATVC.js";
93
94
 
94
95
  // src/cli.ts
95
- import path19 from "path";
96
+ import path20 from "path";
96
97
  import os5 from "os";
97
- import { promises as fs17 } from "fs";
98
+ import { promises as fs18 } from "fs";
98
99
 
99
100
  // src/banner.ts
100
101
  import path from "path";
@@ -4452,7 +4453,8 @@ async function runOrchestrator(opts) {
4452
4453
  } catch (err) {
4453
4454
  if (!(err instanceof ProjectNameCollisionError)) throw err;
4454
4455
  console.error(`neat: ${err.message}`);
4455
- console.error("pass --project <other-name> to register under a different name.");
4456
+ console.error(`neat: run \`neat <path> --project <other-name>\` to register it under a different name,`);
4457
+ console.error(`neat: or \`neat uninstall ${opts.project}\` if the old registration is stale.`);
4456
4458
  result.exitCode = 1;
4457
4459
  return result;
4458
4460
  }
@@ -5245,6 +5247,196 @@ async function clearActiveProfile(home = neatHome()) {
5245
5247
  });
5246
5248
  }
5247
5249
 
5250
+ // src/daemon-probe.ts
5251
+ var PROBE_ATTEMPT_TIMEOUT_MS = 3e4;
5252
+ var PROBE_TOTAL_BUDGET_MS = 12e4;
5253
+ var PROBE_RETRY_PAUSE_MS = 2e3;
5254
+ function isTimeoutError(err) {
5255
+ const name = err?.name;
5256
+ return name === "TimeoutError" || name === "AbortError";
5257
+ }
5258
+ async function probeOnce(fetchImpl, root, token, timeoutMs) {
5259
+ let res;
5260
+ try {
5261
+ res = await fetchImpl(`${root}/health`, {
5262
+ headers: { authorization: `Bearer ${token}` },
5263
+ signal: AbortSignal.timeout(timeoutMs)
5264
+ });
5265
+ } catch (err) {
5266
+ if (isTimeoutError(err)) return { kind: "timeout" };
5267
+ return { kind: "unreachable", detail: err.message };
5268
+ }
5269
+ if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
5270
+ if (!res.ok) return { kind: "not-neat", status: res.status };
5271
+ const contentType = res.headers.get("content-type") ?? "";
5272
+ if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
5273
+ return { kind: "ok" };
5274
+ }
5275
+ async function probeDaemon(fetchImpl, endpoint, token, hooks) {
5276
+ const root = endpoint.replace(/\/$/, "");
5277
+ const deadline = hooks.now() + PROBE_TOTAL_BUDGET_MS;
5278
+ let warned = false;
5279
+ for (; ; ) {
5280
+ const result = await probeOnce(fetchImpl, root, token, PROBE_ATTEMPT_TIMEOUT_MS);
5281
+ if (result.kind !== "timeout") return result;
5282
+ if (!warned) {
5283
+ hooks.onWaiting();
5284
+ warned = true;
5285
+ }
5286
+ if (hooks.now() >= deadline) {
5287
+ return { kind: "unreachable", detail: `no response after ${Math.round(PROBE_TOTAL_BUDGET_MS / 1e3)}s` };
5288
+ }
5289
+ await hooks.sleep(PROBE_RETRY_PAUSE_MS);
5290
+ }
5291
+ }
5292
+
5293
+ // src/login-connect.ts
5294
+ import { execFile } from "child_process";
5295
+ import { promisify } from "util";
5296
+ import { promises as fs11 } from "fs";
5297
+ import path12 from "path";
5298
+ var execFileAsync = promisify(execFile);
5299
+ async function hasLocalSnapshot(cwd) {
5300
+ try {
5301
+ await fs11.access(path12.join(cwd, "neat-out", "graph.json"));
5302
+ return true;
5303
+ } catch {
5304
+ return false;
5305
+ }
5306
+ }
5307
+ var POLL_INTERVAL_MS = 3e3;
5308
+ var POLL_BUDGET_MS = 10 * 6e4;
5309
+ var CP_TIMEOUT_MS = 1e4;
5310
+ async function detectGitHubRepo(cwd) {
5311
+ const read = async (args) => {
5312
+ try {
5313
+ const { stdout } = await execFileAsync("git", args, { cwd, timeout: 5e3 });
5314
+ return stdout.trim();
5315
+ } catch {
5316
+ return null;
5317
+ }
5318
+ };
5319
+ let url = await read(["remote", "get-url", "origin"]);
5320
+ if (!url) {
5321
+ const names = await read(["remote"]);
5322
+ const first = names?.split(/\r?\n/).find((l) => l.trim().length > 0)?.trim();
5323
+ if (!first) return null;
5324
+ url = await read(["remote", "get-url", first]);
5325
+ }
5326
+ if (!url) return null;
5327
+ return parseGitHubRemote(url);
5328
+ }
5329
+ function parseGitHubRemote(url) {
5330
+ const trimmed = url.trim().replace(/\.git$/, "");
5331
+ const m = trimmed.match(/^https?:\/\/(?:[^@/]+@)?github\.com\/([^/]+)\/([^/]+)$/) ?? trimmed.match(/^git@github\.com:([^/]+)\/([^/]+)$/) ?? trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+)$/);
5332
+ if (!m) return null;
5333
+ const owner = m[1];
5334
+ const name = m[2];
5335
+ if (!owner || !name) return null;
5336
+ return { owner, name };
5337
+ }
5338
+ async function cpGetJson(fetchImpl, cpUrl, path21, accessToken) {
5339
+ try {
5340
+ const res = await fetchImpl(`${cpUrl}${path21}`, {
5341
+ headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
5342
+ signal: AbortSignal.timeout(CP_TIMEOUT_MS)
5343
+ });
5344
+ if (!res.ok) return { ok: false };
5345
+ return { ok: true, body: await res.json() };
5346
+ } catch {
5347
+ return { ok: false };
5348
+ }
5349
+ }
5350
+ async function listBoundRepos(fetchImpl, cpUrl, accessToken, projectId) {
5351
+ const res = await cpGetJson(
5352
+ fetchImpl,
5353
+ cpUrl,
5354
+ `/me/projects/${encodeURIComponent(projectId)}/repos`,
5355
+ accessToken
5356
+ );
5357
+ if (!res.ok || !Array.isArray(res.body)) return null;
5358
+ return res.body;
5359
+ }
5360
+ async function waitForFirstProject(fetchImpl, cpUrl, accessToken, deps) {
5361
+ const pollMs = deps.pollMs ?? POLL_INTERVAL_MS;
5362
+ const deadline = deps.now() + (deps.budgetMs ?? POLL_BUDGET_MS);
5363
+ for (; ; ) {
5364
+ if (deps.signal?.aborted) return { kind: "aborted" };
5365
+ const me = await cpGetJson(fetchImpl, cpUrl, "/me", accessToken);
5366
+ if (me.ok) {
5367
+ const running = (me.body.projects ?? []).find((p) => p.status === "running");
5368
+ if (running) return { kind: "project", project: running };
5369
+ }
5370
+ if (deps.now() >= deadline) return { kind: "timeout" };
5371
+ await deps.sleep(pollMs);
5372
+ if (deps.signal?.aborted) return { kind: "aborted" };
5373
+ }
5374
+ }
5375
+ function bindUrl(webUrl, projectId) {
5376
+ return `${webUrl}/config/repos?project=${encodeURIComponent(projectId)}`;
5377
+ }
5378
+ function onboardingUrl(webUrl) {
5379
+ return `${webUrl}/onboarding`;
5380
+ }
5381
+ async function runPostLoginConnect(input) {
5382
+ const d = input.deps;
5383
+ const out = d.out ?? ((line) => console.log(line));
5384
+ const fetchImpl = d.fetchImpl ?? fetch;
5385
+ const cwd = d.cwd ?? process.cwd();
5386
+ const detect6 = d.detectRepo ?? detectGitHubRepo;
5387
+ const readLine = d.readLine;
5388
+ const repo = await detect6(cwd);
5389
+ out("");
5390
+ if (!repo) {
5391
+ out("This directory has no GitHub remote, so it can't be bound to the project.");
5392
+ out(` Push its graph instead: neat sync --to ${input.project.name}`);
5393
+ out(` Manage projects and repos: ${input.webUrl}`);
5394
+ return;
5395
+ }
5396
+ const slug = `${repo.owner}/${repo.name}`;
5397
+ const bound = await listBoundRepos(fetchImpl, input.cpUrl, input.accessToken, input.project.id);
5398
+ const match = bound?.find((r) => r.owner === repo.owner && r.name === repo.name);
5399
+ if (match) {
5400
+ const state = match.syncStatus ? ` (${match.syncStatus})` : "";
5401
+ out(`${slug} is bound to ${input.project.name}${state} \u2014 hosted NEAT extracts it for you.`);
5402
+ out(` See the graph: ${input.webUrl}`);
5403
+ return;
5404
+ }
5405
+ out(`${slug} isn't part of ${input.project.name} yet \u2014 logging in connected your account, not this repo.`);
5406
+ out("");
5407
+ const installed = input.me?.github?.installed;
5408
+ 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";
5409
+ out(bindLine);
5410
+ out(`2) Push the graph on this machine now \u2014 a snapshot, not a subscription`);
5411
+ out("3) Neither, for now");
5412
+ const answer = readLine ? (await readLine("Choose 1, 2 or 3 (Enter skips): "))?.trim() : void 0;
5413
+ if (answer === "1") {
5414
+ const url = bindUrl(input.webUrl, input.project.id);
5415
+ const opened = (d.openBrowser ?? (() => false))(url);
5416
+ out("");
5417
+ out(opened ? `Opened ${url}` : `Open this to bind the repo:
5418
+ ${url}`);
5419
+ return;
5420
+ }
5421
+ if (answer === "2") {
5422
+ const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
5423
+ out("");
5424
+ if (!hasGraph) {
5425
+ out("There is no local graph to push yet. Build one, then push it:");
5426
+ out(" neat");
5427
+ out(` neat sync --to ${input.project.name}`);
5428
+ return;
5429
+ }
5430
+ out("Push the local graph with:");
5431
+ out(` neat sync --to ${input.project.name}`);
5432
+ return;
5433
+ }
5434
+ out("");
5435
+ out("No problem \u2014 when you want this repo in the graph:");
5436
+ out(` Bind it: ${bindUrl(input.webUrl, input.project.id)}`);
5437
+ out(` Or push it: neat sync --to ${input.project.name}`);
5438
+ }
5439
+
5248
5440
  // src/login-sso.ts
5249
5441
  var DEFAULT_CP_URL = "https://api.neat.is";
5250
5442
  var DEFAULT_WEB_URL = "https://app.neat.is";
@@ -5270,6 +5462,7 @@ function pickProject(projects, want) {
5270
5462
  return {
5271
5463
  error: {
5272
5464
  code: 1,
5465
+ kind: "no-project",
5273
5466
  message: `no running project to connect to \u2014 create + provision one in the console first${listed}`
5274
5467
  }
5275
5468
  };
@@ -5320,7 +5513,7 @@ async function exchangeCredential(cpUrl, accessToken, opts, deps) {
5320
5513
  if (!cred.endpoint || !cred.authToken) {
5321
5514
  return { error: { code: 1, message: "the credential response was missing endpoint/authToken" } };
5322
5515
  }
5323
- return { project, cred };
5516
+ return { project, cred, me };
5324
5517
  }
5325
5518
  function defaultOpenBrowser(url) {
5326
5519
  const platform = process.platform;
@@ -5402,12 +5595,50 @@ async function runSsoLogin(opts, deps) {
5402
5595
  }
5403
5596
  accessToken = lb.token;
5404
5597
  }
5405
- const ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
5598
+ const fetchImpl = deps.fetchImpl ?? fetch;
5599
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
5600
+ const now = deps.now ?? Date.now;
5601
+ let ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
5602
+ if ("error" in ex && ex.error.kind === "no-project" && !opts.json) {
5603
+ const url = onboardingUrl(opts.webUrl);
5604
+ out("");
5605
+ out("You don't have a project yet \u2014 let's make one.");
5606
+ const opened = (deps.openBrowser ?? defaultOpenBrowser)(url);
5607
+ out(opened ? `Opened ${url}` : `Open this to create your first project:
5608
+ ${url}`);
5609
+ out("Waiting for it to come up \u2014 Ctrl-C to stop and finish later.");
5610
+ const waited = await waitForFirstProject(fetchImpl, opts.cpUrl, accessToken, {
5611
+ sleep: sleep2,
5612
+ now,
5613
+ ...deps.signal ? { signal: deps.signal } : {}
5614
+ });
5615
+ if (waited.kind === "aborted") {
5616
+ out("Stopped. Run `neat login` again once your project is up.");
5617
+ return 130;
5618
+ }
5619
+ if (waited.kind === "timeout") {
5620
+ err("neat login: no project came up while waiting \u2014 run `neat login` again once it does");
5621
+ return 1;
5622
+ }
5623
+ out(`${waited.project.name} is up.`);
5624
+ ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
5625
+ }
5406
5626
  if ("error" in ex) {
5407
5627
  err(`neat login: ${ex.error.message}`);
5408
5628
  return ex.error.code;
5409
5629
  }
5410
- const { project, cred } = ex;
5630
+ const { project, cred, me } = ex;
5631
+ const probe = await probeDaemon(fetchImpl, cred.endpoint, cred.authToken, {
5632
+ sleep: sleep2,
5633
+ now,
5634
+ onWaiting: () => err("Waking the hosted daemon \u2014 a cold instance can take up to a minute\u2026")
5635
+ });
5636
+ if (probe.kind !== "ok") {
5637
+ 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`;
5638
+ err(`neat login: "${project.name}" is provisioned, but its daemon isn't answering \u2014 ${why}.`);
5639
+ err(`Nothing was changed. Check the project at ${opts.webUrl} and run \`neat login\` again.`);
5640
+ return probe.kind === "unreachable" ? 3 : 1;
5641
+ }
5411
5642
  await upsertProfile(
5412
5643
  { name: opts.name, endpoint: cred.endpoint, authToken: cred.authToken },
5413
5644
  { makeActive: true, ...deps.home ? { home: deps.home } : {} }
@@ -5435,10 +5666,20 @@ async function runSsoLogin(opts, deps) {
5435
5666
  out(` OTEL_EXPORTER_OTLP_ENDPOINT=${cred.ingestEndpoint}`);
5436
5667
  out(` OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${cred.otelToken}`);
5437
5668
  }
5438
- out("");
5439
- out("This directory isn't part of that graph yet \u2014 logging in connected your account,");
5440
- out("not this repo, and nothing here has been uploaded.");
5441
- out(` Manage projects and repos: ${opts.webUrl}`);
5669
+ await runPostLoginConnect({
5670
+ cpUrl: opts.cpUrl,
5671
+ webUrl: opts.webUrl,
5672
+ accessToken,
5673
+ project: { id: project.id, name: project.name },
5674
+ me,
5675
+ deps: {
5676
+ fetchImpl,
5677
+ out,
5678
+ ...deps.readLine ? { readLine: deps.readLine } : {},
5679
+ ...deps.openBrowser ? { openBrowser: deps.openBrowser } : { openBrowser: defaultOpenBrowser },
5680
+ ...deps.connect ?? {}
5681
+ }
5682
+ });
5442
5683
  out("");
5443
5684
  out("Run `neat logout` to switch back to your local daemon.");
5444
5685
  }
@@ -5545,8 +5786,8 @@ async function runConnectCommand(rawArgs, deps = {}) {
5545
5786
  }
5546
5787
 
5547
5788
  // src/doctor-cli.ts
5548
- import { promises as fs11 } from "fs";
5549
- import path12 from "path";
5789
+ import { promises as fs12 } from "fs";
5790
+ import path13 from "path";
5550
5791
 
5551
5792
  // src/cli-client.ts
5552
5793
  import { Provenance as Provenance3 } from "@neat.is/types";
@@ -5574,10 +5815,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
5574
5815
  const root = baseUrl.replace(/\/$/, "");
5575
5816
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
5576
5817
  return {
5577
- async get(path20) {
5818
+ async get(path21) {
5578
5819
  let res;
5579
5820
  try {
5580
- res = await fetchImpl(`${root}${path20}`, {
5821
+ res = await fetchImpl(`${root}${path21}`, {
5581
5822
  headers: { ...authHeader }
5582
5823
  });
5583
5824
  } catch (err) {
@@ -5589,16 +5830,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
5589
5830
  const body = await res.text().catch(() => "");
5590
5831
  throw new HttpError(
5591
5832
  res.status,
5592
- `${res.status} ${res.statusText} on GET ${path20}: ${body}`,
5833
+ `${res.status} ${res.statusText} on GET ${path21}: ${body}`,
5593
5834
  body
5594
5835
  );
5595
5836
  }
5596
5837
  return await res.json();
5597
5838
  },
5598
- async post(path20, body) {
5839
+ async post(path21, body) {
5599
5840
  let res;
5600
5841
  try {
5601
- res = await fetchImpl(`${root}${path20}`, {
5842
+ res = await fetchImpl(`${root}${path21}`, {
5602
5843
  method: "POST",
5603
5844
  headers: { "content-type": "application/json", ...authHeader },
5604
5845
  body: JSON.stringify(body)
@@ -5612,7 +5853,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
5612
5853
  const text = await res.text().catch(() => "");
5613
5854
  throw new HttpError(
5614
5855
  res.status,
5615
- `${res.status} ${res.statusText} on POST ${path20}: ${text}`,
5856
+ `${res.status} ${res.statusText} on POST ${path21}: ${text}`,
5616
5857
  text
5617
5858
  );
5618
5859
  }
@@ -5626,12 +5867,12 @@ function projectPath(project, suffix) {
5626
5867
  }
5627
5868
  async function runRootCause(client, input) {
5628
5869
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
5629
- const path20 = projectPath(
5870
+ const path21 = projectPath(
5630
5871
  input.project,
5631
5872
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
5632
5873
  );
5633
5874
  try {
5634
- const result = await client.get(path20);
5875
+ const result = await client.get(path21);
5635
5876
  const arrowPath = result.traversalPath.join(" \u2190 ");
5636
5877
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
5637
5878
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -5657,12 +5898,12 @@ async function runRootCause(client, input) {
5657
5898
  }
5658
5899
  async function runBlastRadius(client, input) {
5659
5900
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
5660
- const path20 = projectPath(
5901
+ const path21 = projectPath(
5661
5902
  input.project,
5662
5903
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
5663
5904
  );
5664
5905
  try {
5665
- const result = await client.get(path20);
5906
+ const result = await client.get(path21);
5666
5907
  if (result.totalAffected === 0) {
5667
5908
  return {
5668
5909
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -5696,12 +5937,12 @@ function formatBlastEntry(n) {
5696
5937
  }
5697
5938
  async function runDependencies(client, input) {
5698
5939
  const depth = input.depth ?? 3;
5699
- const path20 = projectPath(
5940
+ const path21 = projectPath(
5700
5941
  input.project,
5701
5942
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
5702
5943
  );
5703
5944
  try {
5704
- const result = await client.get(path20);
5945
+ const result = await client.get(path21);
5705
5946
  if (result.total === 0) {
5706
5947
  return {
5707
5948
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -5793,9 +6034,9 @@ function formatDuration(ms) {
5793
6034
  return `${Math.round(h / 24)}d`;
5794
6035
  }
5795
6036
  async function runIncidents(client, input) {
5796
- const path20 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
6037
+ const path21 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
5797
6038
  try {
5798
- const body = await client.get(path20);
6039
+ const body = await client.get(path21);
5799
6040
  const events = body.events;
5800
6041
  if (events.length === 0) {
5801
6042
  return {
@@ -6159,7 +6400,7 @@ function checkNode(nodeVersion) {
6159
6400
  }
6160
6401
  async function neatOutExists(cwd) {
6161
6402
  try {
6162
- const st = await fs11.stat(path12.join(cwd, "neat-out"));
6403
+ const st = await fs12.stat(path13.join(cwd, "neat-out"));
6163
6404
  return st.isDirectory();
6164
6405
  } catch {
6165
6406
  return false;
@@ -6278,9 +6519,6 @@ async function runDoctorCommand(argv, deps = {}) {
6278
6519
 
6279
6520
  // src/login-cli.ts
6280
6521
  import readline3 from "readline/promises";
6281
- var PROBE_ATTEMPT_TIMEOUT_MS = 3e4;
6282
- var PROBE_TOTAL_BUDGET_MS = 12e4;
6283
- var PROBE_RETRY_PAUSE_MS = 2e3;
6284
6522
  var DEFAULT_PROFILE_NAME = "hosted";
6285
6523
  function readFlagValue(argv, i) {
6286
6524
  const arg = argv[i];
@@ -6345,44 +6583,6 @@ function printLoginHelp(out) {
6345
6583
  out(' the profile (default "hosted"); the paste token can also come from NEAT_LOGIN_TOKEN.');
6346
6584
  out(" Exit 0 on success, 1 rejected/needs-action, 2 misuse, 3 unreachable.");
6347
6585
  }
6348
- function isTimeoutError(err) {
6349
- const name = err?.name;
6350
- return name === "TimeoutError" || name === "AbortError";
6351
- }
6352
- async function probeOnce(fetchImpl, root, token, timeoutMs) {
6353
- let res;
6354
- try {
6355
- res = await fetchImpl(`${root}/health`, {
6356
- headers: { authorization: `Bearer ${token}` },
6357
- signal: AbortSignal.timeout(timeoutMs)
6358
- });
6359
- } catch (err) {
6360
- if (isTimeoutError(err)) return { kind: "timeout" };
6361
- return { kind: "unreachable", detail: err.message };
6362
- }
6363
- if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
6364
- if (!res.ok) return { kind: "not-neat", status: res.status };
6365
- const contentType = res.headers.get("content-type") ?? "";
6366
- if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
6367
- return { kind: "ok" };
6368
- }
6369
- async function probeDaemon(fetchImpl, endpoint, token, hooks) {
6370
- const root = endpoint.replace(/\/$/, "");
6371
- const deadline = hooks.now() + PROBE_TOTAL_BUDGET_MS;
6372
- let warned = false;
6373
- for (; ; ) {
6374
- const result = await probeOnce(fetchImpl, root, token, PROBE_ATTEMPT_TIMEOUT_MS);
6375
- if (result.kind !== "timeout") return result;
6376
- if (!warned) {
6377
- hooks.onWaiting();
6378
- warned = true;
6379
- }
6380
- if (hooks.now() >= deadline) {
6381
- return { kind: "unreachable", detail: `no response after ${Math.round(PROBE_TOTAL_BUDGET_MS / 1e3)}s` };
6382
- }
6383
- await hooks.sleep(PROBE_RETRY_PAUSE_MS);
6384
- }
6385
- }
6386
6586
  async function runLoginCommand(argv, deps = {}) {
6387
6587
  const out = deps.out ?? ((line) => console.log(line));
6388
6588
  const err = deps.err ?? ((line) => console.error(line));
@@ -6415,8 +6615,13 @@ async function runLoginCommand(argv, deps = {}) {
6415
6615
  fetchImpl,
6416
6616
  out,
6417
6617
  err,
6618
+ readLine,
6619
+ sleep: sleep2,
6620
+ now,
6418
6621
  ...deps.openBrowser ? { openBrowser: deps.openBrowser } : {},
6419
- ...deps.home ? { home: deps.home } : {}
6622
+ ...deps.home ? { home: deps.home } : {},
6623
+ ...deps.signal ? { signal: deps.signal } : {},
6624
+ ...deps.connect ? { connect: deps.connect } : {}
6420
6625
  }
6421
6626
  );
6422
6627
  }
@@ -6572,8 +6777,8 @@ async function defaultReadSecret(prompt) {
6572
6777
  }
6573
6778
 
6574
6779
  // src/welcome.ts
6575
- import { promises as fs12 } from "fs";
6576
- import path13 from "path";
6780
+ import { promises as fs13 } from "fs";
6781
+ import path14 from "path";
6577
6782
  import readline4 from "readline/promises";
6578
6783
  var WORDMARK = [
6579
6784
  "\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",
@@ -6632,15 +6837,17 @@ async function runFrontDoor(deps, out) {
6632
6837
  const login = deps.login ?? (() => Promise.resolve(0));
6633
6838
  const orchestrator = deps.orchestrator ?? (() => Promise.resolve(0));
6634
6839
  const cwd = deps.cwd ?? process.cwd();
6840
+ const flagGiven = deps.instrumentFlagGiven ?? false;
6841
+ const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
6635
6842
  printHeader(out);
6636
6843
  out("Welcome to NEAT. Let's get you a graph of this system.");
6637
6844
  out("");
6638
6845
  const keyReader = deps.readKey !== void 0 ? { read: deps.readKey, done: () => {
6639
6846
  } } : createKeyReader();
6640
6847
  const picked = keyReader !== null ? await selectOption(out, keyReader, deps.moveCursorUp ?? defaultMoveCursorUp) : await selectByNumber(out, readLine);
6641
- if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd);
6848
+ if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
6642
6849
  if (picked === 0) return login(["--browser"]);
6643
- return runSelfHosted(out, readLine, orchestrator, cwd);
6850
+ return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
6644
6851
  }
6645
6852
  var MENU_OPTIONS = [
6646
6853
  "Log me into Hosted Neat",
@@ -6743,7 +6950,7 @@ function decodeKey(buf) {
6743
6950
  function defaultMoveCursorUp(rows) {
6744
6951
  if (rows > 0) process.stdout.write(`\x1B[${rows}A`);
6745
6952
  }
6746
- async function runSelfHosted(out, readLine, orchestrator, cwd) {
6953
+ async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, instrumentFlagGiven) {
6747
6954
  const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
6748
6955
  if (wantsPrompt !== "n" && wantsPrompt !== "no") {
6749
6956
  out("");
@@ -6754,9 +6961,69 @@ async function runSelfHosted(out, readLine, orchestrator, cwd) {
6754
6961
  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");
6755
6962
  out("");
6756
6963
  }
6964
+ const project = await resolveProjectName(out, readLine, cwd, readRegistry2);
6965
+ const declined = instrumentFlagGiven ? void 0 : await askToInstrument(out, readLine);
6757
6966
  out("Building your local graph now\u2026");
6758
6967
  out("");
6759
- return orchestrator(cwd);
6968
+ const overrides = {
6969
+ ...project !== void 0 ? { project } : {},
6970
+ ...declined === void 0 ? {} : declined ? { noInstrument: true } : { yes: true }
6971
+ };
6972
+ return orchestrator(cwd, Object.keys(overrides).length > 0 ? overrides : void 0);
6973
+ }
6974
+ async function askToInstrument(out, readLine) {
6975
+ out("Instrument the services for OpenTelemetry now?");
6976
+ out("This edits package.json / requirements.txt / go.mod and runs the package manager.");
6977
+ const answer = (await readLine("[Y/n]: "))?.trim().toLowerCase();
6978
+ const declined = answer === "n" || answer === "no";
6979
+ if (declined) {
6980
+ out("");
6981
+ out("Skipping instrumentation \u2014 the graph will hold the declared side only.");
6982
+ out("Run `neat init . --apply` when you want the runtime half.");
6983
+ }
6984
+ out("");
6985
+ return declined;
6986
+ }
6987
+ async function resolveProjectName(out, readLine, cwd, readRegistry2) {
6988
+ const here = path14.resolve(cwd);
6989
+ const base = path14.basename(here);
6990
+ let taken;
6991
+ try {
6992
+ const { projects } = await readRegistry2();
6993
+ taken = new Map(projects.map((p) => [p.name, p.path]));
6994
+ } catch {
6995
+ return void 0;
6996
+ }
6997
+ const clash = taken.get(base);
6998
+ if (clash === void 0 || path14.resolve(clash) === here) return void 0;
6999
+ out("");
7000
+ out(`A project named \`${base}\` is already registered (${clash}).`);
7001
+ const suggested = firstFreeName(base, taken);
7002
+ for (; ; ) {
7003
+ const answer = (await readLine(`Name this one: [${suggested}] `))?.trim();
7004
+ if (answer === void 0) return suggested;
7005
+ const chosen = answer.length === 0 ? suggested : answer;
7006
+ if (!isUsableProjectName(chosen)) {
7007
+ out(`"${chosen}" won't work as a project name \u2014 letters, digits, dot, dash and underscore.`);
7008
+ continue;
7009
+ }
7010
+ const other = taken.get(chosen);
7011
+ if (other !== void 0 && path14.resolve(other) !== here) {
7012
+ out(`\`${chosen}\` is registered too (${other}).`);
7013
+ continue;
7014
+ }
7015
+ out("");
7016
+ return chosen;
7017
+ }
7018
+ }
7019
+ function firstFreeName(base, taken) {
7020
+ for (let n = 2; ; n++) {
7021
+ const candidate = `${base}-${n}`;
7022
+ if (!taken.has(candidate)) return candidate;
7023
+ }
7024
+ }
7025
+ function isUsableProjectName(name) {
7026
+ return name.length > 0 && name !== "." && name !== ".." && /^[A-Za-z0-9._-]+$/.test(name);
6760
7027
  }
6761
7028
  async function shouldShowWelcome(deps = {}) {
6762
7029
  const stdinTTY = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
@@ -6766,9 +7033,9 @@ async function shouldShowWelcome(deps = {}) {
6766
7033
  const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
6767
7034
  const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
6768
7035
  try {
6769
- const here = path13.resolve(cwd);
7036
+ const here = path14.resolve(cwd);
6770
7037
  const { projects } = await readRegistry2();
6771
- if (projects.some((p) => path13.resolve(p.path) === here)) return false;
7038
+ if (projects.some((p) => path14.resolve(p.path) === here)) return false;
6772
7039
  if (await hasSnapshot(here)) return false;
6773
7040
  return true;
6774
7041
  } catch {
@@ -6777,7 +7044,7 @@ async function shouldShowWelcome(deps = {}) {
6777
7044
  }
6778
7045
  async function defaultHasSnapshot(cwd) {
6779
7046
  try {
6780
- const entries = await fs12.readdir(path13.join(cwd, "neat-out"));
7047
+ const entries = await fs13.readdir(path14.join(cwd, "neat-out"));
6781
7048
  return entries.some((e) => e.endsWith(".json"));
6782
7049
  } catch {
6783
7050
  return false;
@@ -6794,27 +7061,27 @@ async function defaultReadLine2(prompt) {
6794
7061
  }
6795
7062
 
6796
7063
  // src/hooks-cli.ts
6797
- import path14 from "path";
7064
+ import path15 from "path";
6798
7065
  import os2 from "os";
6799
- import { promises as fs13 } from "fs";
7066
+ import { promises as fs14 } from "fs";
6800
7067
  import { fileURLToPath as fileURLToPath3 } from "url";
6801
7068
  var HOOK_FILENAME = "neat-search-nudge.mjs";
6802
7069
  var GUIDE_FILENAME = "GRAPH_FIRST.md";
6803
7070
  var GUIDE_INSTALL_NAME = "neat-graph-first.md";
6804
7071
  var HOOK_MATCHER = "Grep|Glob|Bash";
6805
7072
  function moduleDir() {
6806
- return typeof __dirname !== "undefined" ? __dirname : path14.dirname(fileURLToPath3(import.meta.url));
7073
+ return typeof __dirname !== "undefined" ? __dirname : path15.dirname(fileURLToPath3(import.meta.url));
6807
7074
  }
6808
7075
  async function readSkillAsset(rel) {
6809
7076
  const here = moduleDir();
6810
7077
  const candidates = [
6811
- path14.resolve(here, "../../claude-skill", rel),
6812
- path14.resolve(here, "../../../claude-skill", rel),
6813
- path14.resolve(here, "../claude-skill", rel)
7078
+ path15.resolve(here, "../../claude-skill", rel),
7079
+ path15.resolve(here, "../../../claude-skill", rel),
7080
+ path15.resolve(here, "../claude-skill", rel)
6814
7081
  ];
6815
7082
  for (const candidate of candidates) {
6816
7083
  try {
6817
- return await fs13.readFile(candidate, "utf8");
7084
+ return await fs14.readFile(candidate, "utf8");
6818
7085
  } catch {
6819
7086
  }
6820
7087
  }
@@ -6824,20 +7091,20 @@ async function readSkillAsset(rel) {
6824
7091
  }
6825
7092
  function neatHome2() {
6826
7093
  const override = process.env.NEAT_HOME;
6827
- if (override && override.length > 0) return path14.resolve(override);
6828
- return path14.join(os2.homedir(), ".neat");
7094
+ if (override && override.length > 0) return path15.resolve(override);
7095
+ return path15.join(os2.homedir(), ".neat");
6829
7096
  }
6830
7097
  function claudeSettingsPath() {
6831
7098
  const override = process.env.NEAT_CLAUDE_SETTINGS;
6832
- if (override && override.length > 0) return path14.resolve(override);
7099
+ if (override && override.length > 0) return path15.resolve(override);
6833
7100
  const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
6834
- return path14.join(home, ".claude", "settings.json");
7101
+ return path15.join(home, ".claude", "settings.json");
6835
7102
  }
6836
7103
  function installedHookPath() {
6837
- return path14.join(neatHome2(), "hooks", HOOK_FILENAME);
7104
+ return path15.join(neatHome2(), "hooks", HOOK_FILENAME);
6838
7105
  }
6839
7106
  function gateFlagPath() {
6840
- return path14.join(neatHome2(), "hooks", "gate-enabled");
7107
+ return path15.join(neatHome2(), "hooks", "gate-enabled");
6841
7108
  }
6842
7109
  function isNeatSearchEntry(entry2) {
6843
7110
  return (entry2.hooks ?? []).some(
@@ -6870,14 +7137,14 @@ async function runHooks(opts) {
6870
7137
  const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
6871
7138
  const guide = await readSkillAsset(GUIDE_FILENAME);
6872
7139
  const scriptPath = installedHookPath();
6873
- await fs13.mkdir(path14.dirname(scriptPath), { recursive: true });
6874
- await fs13.writeFile(scriptPath, hookScript, { mode: 493 });
6875
- const guidePath = path14.join(neatHome2(), GUIDE_INSTALL_NAME);
6876
- await fs13.writeFile(guidePath, guide, "utf8");
7140
+ await fs14.mkdir(path15.dirname(scriptPath), { recursive: true });
7141
+ await fs14.writeFile(scriptPath, hookScript, { mode: 493 });
7142
+ const guidePath = path15.join(neatHome2(), GUIDE_INSTALL_NAME);
7143
+ await fs14.writeFile(guidePath, guide, "utf8");
6877
7144
  const settingsFile = claudeSettingsPath();
6878
7145
  let settings = {};
6879
7146
  try {
6880
- settings = JSON.parse(await fs13.readFile(settingsFile, "utf8"));
7147
+ settings = JSON.parse(await fs14.readFile(settingsFile, "utf8"));
6881
7148
  } catch (err) {
6882
7149
  if (err.code !== "ENOENT") {
6883
7150
  console.error(
@@ -6899,14 +7166,14 @@ async function runHooks(opts) {
6899
7166
  ...settings,
6900
7167
  hooks: { ...hooks, PreToolUse: preToolUse }
6901
7168
  };
6902
- await fs13.mkdir(path14.dirname(settingsFile), { recursive: true });
6903
- await fs13.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
7169
+ await fs14.mkdir(path15.dirname(settingsFile), { recursive: true });
7170
+ await fs14.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
6904
7171
  const flag = gateFlagPath();
6905
7172
  if (opts.gate) {
6906
- await fs13.mkdir(path14.dirname(flag), { recursive: true });
6907
- await fs13.writeFile(flag, "1\n", "utf8");
7173
+ await fs14.mkdir(path15.dirname(flag), { recursive: true });
7174
+ await fs14.writeFile(flag, "1\n", "utf8");
6908
7175
  } else {
6909
- await fs13.rm(flag, { force: true });
7176
+ await fs14.rm(flag, { force: true });
6910
7177
  }
6911
7178
  const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
6912
7179
  console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
@@ -6995,8 +7262,8 @@ async function runHooksCommand(args) {
6995
7262
  }
6996
7263
 
6997
7264
  // src/claude-cli.ts
6998
- import path15 from "path";
6999
- import { promises as fs14 } from "fs";
7265
+ import path16 from "path";
7266
+ import { promises as fs15 } from "fs";
7000
7267
  var NEAT_SECTION_HEADING = "## neat";
7001
7268
  var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
7002
7269
  code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
@@ -7030,8 +7297,8 @@ ${NEAT_DIRECTIVE_BODY}
7030
7297
  }
7031
7298
  function claudeMdPath() {
7032
7299
  const override = process.env.NEAT_CLAUDE_MD;
7033
- if (override && override.length > 0) return path15.resolve(override);
7034
- return path15.join(process.cwd(), "CLAUDE.md");
7300
+ if (override && override.length > 0) return path16.resolve(override);
7301
+ return path16.join(process.cwd(), "CLAUDE.md");
7035
7302
  }
7036
7303
  function splitAroundSection(raw) {
7037
7304
  const lines = raw.split("\n");
@@ -7059,7 +7326,7 @@ function compose(before, after) {
7059
7326
  }
7060
7327
  async function readIfExists(file) {
7061
7328
  try {
7062
- return await fs14.readFile(file, "utf8");
7329
+ return await fs15.readFile(file, "utf8");
7063
7330
  } catch (err) {
7064
7331
  if (err.code === "ENOENT") return null;
7065
7332
  throw err;
@@ -7070,8 +7337,8 @@ async function runInstall() {
7070
7337
  const raw = await readIfExists(file) ?? "";
7071
7338
  const { before, after, found } = splitAroundSection(raw);
7072
7339
  const next = compose(before, after);
7073
- await fs14.mkdir(path15.dirname(file), { recursive: true });
7074
- await fs14.writeFile(file, next, "utf8");
7340
+ await fs15.mkdir(path16.dirname(file), { recursive: true });
7341
+ await fs15.writeFile(file, next, "utf8");
7075
7342
  const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
7076
7343
  console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
7077
7344
  console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
@@ -7092,7 +7359,7 @@ async function runUninstall() {
7092
7359
  }
7093
7360
  const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
7094
7361
  const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
7095
- await fs14.writeFile(file, next, "utf8");
7362
+ await fs15.writeFile(file, next, "utf8");
7096
7363
  console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
7097
7364
  return { exitCode: 0 };
7098
7365
  }
@@ -7134,9 +7401,9 @@ async function runClaudeCommand(args) {
7134
7401
  }
7135
7402
 
7136
7403
  // src/codex-cli.ts
7137
- import path16 from "path";
7404
+ import path17 from "path";
7138
7405
  import os3 from "os";
7139
- import { promises as fs15 } from "fs";
7406
+ import { promises as fs16 } from "fs";
7140
7407
  import { isDeepStrictEqual } from "util";
7141
7408
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
7142
7409
  var CODEX_MCP_SERVER = {
@@ -7154,14 +7421,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
7154
7421
  var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
7155
7422
  function codexConfigPath() {
7156
7423
  const override = process.env.NEAT_CODEX_CONFIG;
7157
- if (override && override.length > 0) return path16.resolve(override);
7424
+ if (override && override.length > 0) return path17.resolve(override);
7158
7425
  const home = process.env.HOME ?? process.env.USERPROFILE ?? os3.homedir();
7159
- return path16.join(home, ".codex", "config.toml");
7426
+ return path17.join(home, ".codex", "config.toml");
7160
7427
  }
7161
7428
  function agentsFilePath() {
7162
7429
  const override = process.env.NEAT_CODEX_AGENTS;
7163
- if (override && override.length > 0) return path16.resolve(override);
7164
- return path16.join(process.cwd(), "AGENTS.md");
7430
+ if (override && override.length > 0) return path17.resolve(override);
7431
+ return path17.join(process.cwd(), "AGENTS.md");
7165
7432
  }
7166
7433
  function isTableHeader(line) {
7167
7434
  return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
@@ -7295,7 +7562,7 @@ async function runCodex(opts) {
7295
7562
  const agentsPath = agentsFilePath();
7296
7563
  let configRaw = "";
7297
7564
  try {
7298
- configRaw = await fs15.readFile(configPath, "utf8");
7565
+ configRaw = await fs16.readFile(configPath, "utf8");
7299
7566
  } catch (err) {
7300
7567
  if (err.code !== "ENOENT") {
7301
7568
  console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
@@ -7304,7 +7571,7 @@ async function runCodex(opts) {
7304
7571
  }
7305
7572
  let agentsRaw = "";
7306
7573
  try {
7307
- agentsRaw = await fs15.readFile(agentsPath, "utf8");
7574
+ agentsRaw = await fs16.readFile(agentsPath, "utf8");
7308
7575
  } catch (err) {
7309
7576
  if (err.code !== "ENOENT") {
7310
7577
  console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
@@ -7344,15 +7611,15 @@ async function runCodex(opts) {
7344
7611
  return { exitCode: 0 };
7345
7612
  }
7346
7613
  if (config.changed) {
7347
- await fs15.mkdir(path16.dirname(configPath), { recursive: true });
7348
- await fs15.writeFile(configPath, config.text, "utf8");
7614
+ await fs16.mkdir(path17.dirname(configPath), { recursive: true });
7615
+ await fs16.writeFile(configPath, config.text, "utf8");
7349
7616
  console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
7350
7617
  } else {
7351
7618
  console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
7352
7619
  }
7353
7620
  if (agents.changed) {
7354
- await fs15.mkdir(path16.dirname(agentsPath), { recursive: true });
7355
- await fs15.writeFile(agentsPath, agents.text, "utf8");
7621
+ await fs16.mkdir(path17.dirname(agentsPath), { recursive: true });
7622
+ await fs16.writeFile(agentsPath, agents.text, "utf8");
7356
7623
  console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
7357
7624
  } else {
7358
7625
  console.log(`neat codex: ${agentsPath} already has the graph-first block`);
@@ -7408,9 +7675,9 @@ async function runCodexCommand(args) {
7408
7675
  }
7409
7676
 
7410
7677
  // src/editors-cli.ts
7411
- import path17 from "path";
7678
+ import path18 from "path";
7412
7679
  import os4 from "os";
7413
- import { promises as fs16 } from "fs";
7680
+ import { promises as fs17 } from "fs";
7414
7681
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
7415
7682
  import * as jsonc from "jsonc-parser";
7416
7683
  var NEAT_MCP_SERVER = {
@@ -7434,17 +7701,17 @@ function homeDir() {
7434
7701
  }
7435
7702
  function xdgConfigDir() {
7436
7703
  const xdg = process.env.XDG_CONFIG_HOME;
7437
- return xdg && xdg.length > 0 ? path17.resolve(xdg) : path17.join(homeDir(), ".config");
7704
+ return xdg && xdg.length > 0 ? path18.resolve(xdg) : path18.join(homeDir(), ".config");
7438
7705
  }
7439
7706
  function envOverride(name) {
7440
7707
  const v = process.env[name];
7441
- return v && v.length > 0 ? path17.resolve(v) : void 0;
7708
+ return v && v.length > 0 ? path18.resolve(v) : void 0;
7442
7709
  }
7443
7710
  var CURSOR_CLIENT = {
7444
7711
  id: "cursor",
7445
7712
  label: "Cursor",
7446
7713
  docsUrl: "https://docs.cursor.com/context/mcp",
7447
- mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path17.join(homeDir(), ".cursor", "mcp.json"),
7714
+ mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path18.join(homeDir(), ".cursor", "mcp.json"),
7448
7715
  mcpContainerKey: "mcpServers",
7449
7716
  format: "json",
7450
7717
  // Cursor still reads a single `.cursorrules` at the project root (the modern
@@ -7456,7 +7723,7 @@ var DEVIN_CLIENT = {
7456
7723
  id: "devin",
7457
7724
  label: "Devin Desktop (Cascade)",
7458
7725
  docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
7459
- mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path17.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
7726
+ mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path18.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
7460
7727
  mcpContainerKey: "mcpServers",
7461
7728
  format: "json",
7462
7729
  rulesFileName: ".windsurfrules"
@@ -7465,7 +7732,7 @@ var GEMINI_CLIENT = {
7465
7732
  id: "gemini",
7466
7733
  label: "Gemini CLI",
7467
7734
  docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
7468
- mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path17.join(homeDir(), ".gemini", "settings.json"),
7735
+ mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path18.join(homeDir(), ".gemini", "settings.json"),
7469
7736
  mcpContainerKey: "mcpServers",
7470
7737
  format: "json",
7471
7738
  rulesFileName: "GEMINI.md"
@@ -7474,7 +7741,7 @@ var QWEN_CLIENT = {
7474
7741
  id: "qwen",
7475
7742
  label: "Qwen Code",
7476
7743
  docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
7477
- mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path17.join(homeDir(), ".qwen", "settings.json"),
7744
+ mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path18.join(homeDir(), ".qwen", "settings.json"),
7478
7745
  mcpContainerKey: "mcpServers",
7479
7746
  format: "json",
7480
7747
  rulesFileName: "QWEN.md"
@@ -7483,7 +7750,7 @@ var AMAZONQ_CLIENT = {
7483
7750
  id: "amazonq",
7484
7751
  label: "Amazon Q Developer CLI",
7485
7752
  docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
7486
- mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path17.join(homeDir(), ".aws", "amazonq", "mcp.json"),
7753
+ mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path18.join(homeDir(), ".aws", "amazonq", "mcp.json"),
7487
7754
  mcpContainerKey: "mcpServers",
7488
7755
  format: "json"
7489
7756
  };
@@ -7491,7 +7758,7 @@ var ROOCODE_CLIENT = {
7491
7758
  id: "roocode",
7492
7759
  label: "Roo Code",
7493
7760
  docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
7494
- mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path17.join(process.cwd(), ".roo", "mcp.json"),
7761
+ mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path18.join(process.cwd(), ".roo", "mcp.json"),
7495
7762
  mcpContainerKey: "mcpServers",
7496
7763
  format: "json"
7497
7764
  };
@@ -7504,9 +7771,9 @@ var ZED_CLIENT = {
7504
7771
  if (override) return override;
7505
7772
  if (process.platform === "win32") {
7506
7773
  const appData = process.env.APPDATA;
7507
- if (appData && appData.length > 0) return path17.join(appData, "Zed", "settings.json");
7774
+ if (appData && appData.length > 0) return path18.join(appData, "Zed", "settings.json");
7508
7775
  }
7509
- return path17.join(homeDir(), ".config", "zed", "settings.json");
7776
+ return path18.join(homeDir(), ".config", "zed", "settings.json");
7510
7777
  },
7511
7778
  mcpContainerKey: "context_servers",
7512
7779
  format: "jsonc",
@@ -7516,7 +7783,7 @@ var OPENCODE_CLIENT = {
7516
7783
  id: "opencode",
7517
7784
  label: "OpenCode",
7518
7785
  docsUrl: "https://opencode.ai/docs/mcp-servers/",
7519
- mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path17.join(xdgConfigDir(), "opencode", "opencode.json"),
7786
+ mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path18.join(xdgConfigDir(), "opencode", "opencode.json"),
7520
7787
  mcpContainerKey: "mcp",
7521
7788
  format: "json",
7522
7789
  serverEntry: NEAT_OPENCODE_SERVER,
@@ -7526,7 +7793,7 @@ var CRUSH_CLIENT = {
7526
7793
  id: "crush",
7527
7794
  label: "Crush",
7528
7795
  docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
7529
- mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path17.join(xdgConfigDir(), "crush", "crush.json"),
7796
+ mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path18.join(xdgConfigDir(), "crush", "crush.json"),
7530
7797
  mcpContainerKey: "mcp",
7531
7798
  format: "json",
7532
7799
  serverEntry: NEAT_CRUSH_SERVER,
@@ -7589,7 +7856,7 @@ async function planMcp(client, mcpPath) {
7589
7856
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
7590
7857
  let raw = "";
7591
7858
  try {
7592
- raw = await fs16.readFile(mcpPath, "utf8");
7859
+ raw = await fs17.readFile(mcpPath, "utf8");
7593
7860
  } catch (err) {
7594
7861
  const e = err;
7595
7862
  if (e.code === "ENOENT") {
@@ -7631,7 +7898,7 @@ async function runEditorInstall(client, opts) {
7631
7898
  const mcpPath = client.mcpConfigPath();
7632
7899
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
7633
7900
  const hasRules = typeof client.rulesFileName === "string";
7634
- const rulesPath = hasRules ? path17.join(opts.projectDir, client.rulesFileName) : "";
7901
+ const rulesPath = hasRules ? path18.join(opts.projectDir, client.rulesFileName) : "";
7635
7902
  const mcp = await planMcp(client, mcpPath);
7636
7903
  if (mcp === null) return { exitCode: 1 };
7637
7904
  let existingRules = "";
@@ -7640,7 +7907,7 @@ async function runEditorInstall(client, opts) {
7640
7907
  let block = "";
7641
7908
  if (hasRules) {
7642
7909
  try {
7643
- existingRules = await fs16.readFile(rulesPath, "utf8");
7910
+ existingRules = await fs17.readFile(rulesPath, "utf8");
7644
7911
  } catch (err) {
7645
7912
  if (err.code !== "ENOENT") {
7646
7913
  console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
@@ -7674,11 +7941,11 @@ async function runEditorInstall(client, opts) {
7674
7941
  );
7675
7942
  return { exitCode: 0 };
7676
7943
  }
7677
- await fs16.mkdir(path17.dirname(mcpPath), { recursive: true });
7678
- await fs16.writeFile(mcpPath, mcp.text, "utf8");
7944
+ await fs17.mkdir(path18.dirname(mcpPath), { recursive: true });
7945
+ await fs17.writeFile(mcpPath, mcp.text, "utf8");
7679
7946
  if (hasRules) {
7680
- await fs16.mkdir(path17.dirname(rulesPath), { recursive: true });
7681
- await fs16.writeFile(rulesPath, newRules, "utf8");
7947
+ await fs17.mkdir(path18.dirname(rulesPath), { recursive: true });
7948
+ await fs17.writeFile(rulesPath, newRules, "utf8");
7682
7949
  }
7683
7950
  console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
7684
7951
  console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
@@ -8178,7 +8445,7 @@ function sleep(ms, signal) {
8178
8445
  }
8179
8446
 
8180
8447
  // src/cli-verbs.ts
8181
- import path18 from "path";
8448
+ import path19 from "path";
8182
8449
  async function resolveProjectEntry(opts) {
8183
8450
  const entries = await listProjects();
8184
8451
  if (opts.project) {
@@ -8188,7 +8455,7 @@ async function resolveProjectEntry(opts) {
8188
8455
  const cwd = opts.cwd ?? process.cwd();
8189
8456
  const resolvedCwd = await normalizeProjectPath(cwd);
8190
8457
  for (const entry2 of entries) {
8191
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path18.sep}`)) {
8458
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path19.sep}`)) {
8192
8459
  return entry2;
8193
8460
  }
8194
8461
  }
@@ -8765,7 +9032,7 @@ async function buildPatchSections(services, project) {
8765
9032
  }
8766
9033
  async function runInit(opts) {
8767
9034
  const written = [];
8768
- const stat = await fs17.stat(opts.scanPath).catch(() => null);
9035
+ const stat = await fs18.stat(opts.scanPath).catch(() => null);
8769
9036
  if (!stat || !stat.isDirectory()) {
8770
9037
  console.error(`neat init: ${opts.scanPath} is not a directory`);
8771
9038
  return { exitCode: 2, writtenFiles: written };
@@ -8774,13 +9041,13 @@ async function runInit(opts) {
8774
9041
  printDiscoveryReport(opts, services);
8775
9042
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
8776
9043
  const patch = renderPatch(sections);
8777
- const patchPath = path19.join(opts.scanPath, "neat.patch");
9044
+ const patchPath = path20.join(opts.scanPath, "neat.patch");
8778
9045
  if (opts.dryRun) {
8779
- await fs17.writeFile(patchPath, patch, "utf8");
9046
+ await fs18.writeFile(patchPath, patch, "utf8");
8780
9047
  written.push(patchPath);
8781
9048
  console.log(`dry-run: patch written to ${patchPath}`);
8782
- const gitignorePath = path19.join(opts.scanPath, ".gitignore");
8783
- const gitignoreExists = await fs17.stat(gitignorePath).then(() => true).catch(() => false);
9049
+ const gitignorePath = path20.join(opts.scanPath, ".gitignore");
9050
+ const gitignoreExists = await fs18.stat(gitignorePath).then(() => true).catch(() => false);
8784
9051
  const verb = gitignoreExists ? "append" : "create";
8785
9052
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
8786
9053
  console.log("rerun without --dry-run to register and snapshot.");
@@ -8791,9 +9058,9 @@ async function runInit(opts) {
8791
9058
  const graph = getGraph(graphKey);
8792
9059
  const projectPaths = pathsForProject(
8793
9060
  graphKey,
8794
- path19.join(opts.scanPath, "neat-out")
9061
+ path20.join(opts.scanPath, "neat-out")
8795
9062
  );
8796
- const errorsPath = path19.join(path19.dirname(opts.outPath), path19.basename(projectPaths.errorsPath));
9063
+ const errorsPath = path20.join(path20.dirname(opts.outPath), path20.basename(projectPaths.errorsPath));
8797
9064
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
8798
9065
  await saveGraphToDisk(graph, opts.outPath);
8799
9066
  written.push(opts.outPath);
@@ -8872,7 +9139,7 @@ async function runInit(opts) {
8872
9139
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
8873
9140
  }
8874
9141
  } else {
8875
- await fs17.writeFile(patchPath, patch, "utf8");
9142
+ await fs18.writeFile(patchPath, patch, "utf8");
8876
9143
  written.push(patchPath);
8877
9144
  }
8878
9145
  }
@@ -8913,9 +9180,9 @@ var CLAUDE_SKILL_CONFIG = {
8913
9180
  };
8914
9181
  function claudeConfigPath() {
8915
9182
  const override = process.env.NEAT_CLAUDE_CONFIG;
8916
- if (override && override.length > 0) return path19.resolve(override);
9183
+ if (override && override.length > 0) return path20.resolve(override);
8917
9184
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
8918
- return path19.join(home, ".claude.json");
9185
+ return path20.join(home, ".claude.json");
8919
9186
  }
8920
9187
  async function runSkill(opts) {
8921
9188
  const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -8927,7 +9194,7 @@ async function runSkill(opts) {
8927
9194
  const target = claudeConfigPath();
8928
9195
  let existing = {};
8929
9196
  try {
8930
- existing = JSON.parse(await fs17.readFile(target, "utf8"));
9197
+ existing = JSON.parse(await fs18.readFile(target, "utf8"));
8931
9198
  } catch (err) {
8932
9199
  if (err.code !== "ENOENT") {
8933
9200
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -8939,8 +9206,8 @@ async function runSkill(opts) {
8939
9206
  ...existing,
8940
9207
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
8941
9208
  };
8942
- await fs17.mkdir(path19.dirname(target), { recursive: true });
8943
- await fs17.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
9209
+ await fs18.mkdir(path20.dirname(target), { recursive: true });
9210
+ await fs18.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
8944
9211
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
8945
9212
  console.log("restart Claude Code to pick up the new MCP server.");
8946
9213
  console.log("");
@@ -9061,12 +9328,12 @@ async function main() {
9061
9328
  console.error("neat init: --apply and --dry-run are mutually exclusive");
9062
9329
  process.exit(2);
9063
9330
  }
9064
- const scanPath = path19.resolve(target);
9331
+ const scanPath = path20.resolve(target);
9065
9332
  const projectExplicit = parsed.project !== null;
9066
- const projectName = projectExplicit ? project : path19.basename(scanPath);
9333
+ const projectName = projectExplicit ? project : path20.basename(scanPath);
9067
9334
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
9068
- const fallback = pathsForProject(projectKey, path19.join(scanPath, "neat-out")).snapshotPath;
9069
- const outPath = path19.resolve(process.env.NEAT_OUT_PATH ?? fallback);
9335
+ const fallback = pathsForProject(projectKey, path20.join(scanPath, "neat-out")).snapshotPath;
9336
+ const outPath = path20.resolve(process.env.NEAT_OUT_PATH ?? fallback);
9070
9337
  const result = await runInit({
9071
9338
  scanPath,
9072
9339
  outPath,
@@ -9087,21 +9354,21 @@ async function main() {
9087
9354
  usage5();
9088
9355
  process.exit(2);
9089
9356
  }
9090
- const scanPath = path19.resolve(target);
9091
- const stat = await fs17.stat(scanPath).catch(() => null);
9357
+ const scanPath = path20.resolve(target);
9358
+ const stat = await fs18.stat(scanPath).catch(() => null);
9092
9359
  if (!stat || !stat.isDirectory()) {
9093
9360
  console.error(`neat watch: ${scanPath} is not a directory`);
9094
9361
  process.exit(2);
9095
9362
  }
9096
- const projectPaths = pathsForProject(project, path19.join(scanPath, "neat-out"));
9097
- const outPath = path19.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
9098
- const errorsPath = path19.resolve(
9099
- process.env.NEAT_ERRORS_PATH ?? path19.join(path19.dirname(outPath), path19.basename(projectPaths.errorsPath))
9363
+ const projectPaths = pathsForProject(project, path20.join(scanPath, "neat-out"));
9364
+ const outPath = path20.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
9365
+ const errorsPath = path20.resolve(
9366
+ process.env.NEAT_ERRORS_PATH ?? path20.join(path20.dirname(outPath), path20.basename(projectPaths.errorsPath))
9100
9367
  );
9101
- const staleEventsPath = path19.resolve(
9102
- process.env.NEAT_STALE_EVENTS_PATH ?? path19.join(path19.dirname(outPath), path19.basename(projectPaths.staleEventsPath))
9368
+ const staleEventsPath = path20.resolve(
9369
+ process.env.NEAT_STALE_EVENTS_PATH ?? path20.join(path20.dirname(outPath), path20.basename(projectPaths.staleEventsPath))
9103
9370
  );
9104
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path19.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
9371
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path20.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
9105
9372
  const handle = await startWatch(getGraph(project), {
9106
9373
  scanPath,
9107
9374
  outPath,
@@ -9110,7 +9377,7 @@ async function main() {
9110
9377
  project,
9111
9378
  // Resolve NEAT_HOME so a `neat watch` picks up connectors added to
9112
9379
  // ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
9113
- neatHome: process.env.NEAT_HOME ? path19.resolve(process.env.NEAT_HOME) : path19.join(os5.homedir(), ".neat"),
9380
+ neatHome: process.env.NEAT_HOME ? path20.resolve(process.env.NEAT_HOME) : path20.join(os5.homedir(), ".neat"),
9114
9381
  ...embeddingsCachePath ? { embeddingsCachePath } : {},
9115
9382
  host: process.env.HOST ?? "0.0.0.0",
9116
9383
  port: Number(process.env.PORT ?? 8080),
@@ -9292,11 +9559,11 @@ async function main() {
9292
9559
  process.exit(1);
9293
9560
  }
9294
9561
  async function tryOrchestrator(cmd, parsed) {
9295
- const scanPath = path19.resolve(cmd);
9296
- const stat = await fs17.stat(scanPath).catch(() => null);
9562
+ const scanPath = path20.resolve(cmd);
9563
+ const stat = await fs18.stat(scanPath).catch(() => null);
9297
9564
  if (!stat || !stat.isDirectory()) return null;
9298
9565
  const projectExplicit = parsed.project !== null;
9299
- const projectName = projectExplicit ? parsed.project : path19.basename(scanPath);
9566
+ const projectName = projectExplicit ? parsed.project : path20.basename(scanPath);
9300
9567
  const result = await runOrchestrator({
9301
9568
  scanPath,
9302
9569
  project: projectName,
@@ -9311,7 +9578,18 @@ async function tryOrchestrator(cmd, parsed) {
9311
9578
  async function runWelcomeFlow(parsed) {
9312
9579
  return runWelcome({
9313
9580
  login: (loginArgv) => runLoginCommand(loginArgv),
9314
- orchestrator: async (cwd) => await tryOrchestrator(cwd, parsed) ?? 0
9581
+ // What the person chose at the door reaches the orchestrator as the flags they
9582
+ // stand for: the name as `--project`, the instrument answer as `--no-instrument`
9583
+ // or as `yes` (which keeps the orchestrator's own prompt quiet, since the door
9584
+ // already asked).
9585
+ orchestrator: async (cwd, opts) => await tryOrchestrator(cwd, {
9586
+ ...parsed,
9587
+ ...opts?.project !== void 0 ? { project: opts.project } : {},
9588
+ ...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
9589
+ ...opts?.yes !== void 0 ? { yes: opts.yes } : {}
9590
+ }) ?? 0,
9591
+ // An explicit flag is an answer; don't ask again.
9592
+ instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
9315
9593
  });
9316
9594
  }
9317
9595
  var QUERY_VERBS = /* @__PURE__ */ new Set([
@@ -9344,6 +9622,13 @@ var ProjectResolutionError = class extends Error {
9344
9622
  }
9345
9623
  exitCode;
9346
9624
  };
9625
+ function isLoopbackEndpoint(endpoint) {
9626
+ try {
9627
+ return isLoopbackHost(new URL(endpoint).hostname.replace(/^\[|\]$/g, ""));
9628
+ } catch {
9629
+ return false;
9630
+ }
9631
+ }
9347
9632
  async function resolveProjectForVerb(client, parsed) {
9348
9633
  const explicit = resolveProjectFlag(parsed);
9349
9634
  if (explicit) return explicit;
@@ -9574,6 +9859,9 @@ async function runQueryVerb(cmd, parsed) {
9574
9859
  console.error(`neat ${cmd}: ${detail.trim()}`);
9575
9860
  } else if (err instanceof TransportError) {
9576
9861
  console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${target.endpoint})`);
9862
+ if (isLoopbackEndpoint(target.endpoint)) {
9863
+ console.error(`neat ${cmd}: start one with \`neat watch .\`, or \`neat\` to extract and start in one step.`);
9864
+ }
9577
9865
  } else {
9578
9866
  console.error(`neat ${cmd}: ${err.message}`);
9579
9867
  }
@@ -9629,6 +9917,7 @@ export {
9629
9917
  QUERY_VERBS,
9630
9918
  UnknownProfileError,
9631
9919
  commandPrefix,
9920
+ isLoopbackEndpoint,
9632
9921
  isNpxInvocation,
9633
9922
  main,
9634
9923
  parseArgs,