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

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-CAGAHYDB.js";
12
12
  import {
13
13
  buildSearchIndex
14
14
  } from "./chunk-BC53SCT7.js";
@@ -17,6 +17,7 @@ import {
17
17
  PolicyViolationsLog,
18
18
  ProjectNameCollisionError,
19
19
  Projects,
20
+ RegistryError,
20
21
  SCHEMA_VERSION,
21
22
  addCallEdges,
22
23
  addConfigNodes,
@@ -42,9 +43,12 @@ import {
42
43
  evaluateAllPolicies,
43
44
  extractFromDirectory,
44
45
  findDaemonByProject,
46
+ findProjectByPath,
45
47
  formatExtractionBanner,
46
48
  formatPrecisionFloorBanner,
49
+ formatSkippedBanner,
47
50
  getGraph,
51
+ getProject,
48
52
  getProviderFieldSchema,
49
53
  isEnvRef,
50
54
  isPushProvider,
@@ -78,23 +82,24 @@ import {
78
82
  startStalenessLoop,
79
83
  upsertConnectorEntry,
80
84
  validateConnectorEntry
81
- } from "./chunk-CNOJ4T6I.js";
85
+ } from "./chunk-TXKOAART.js";
82
86
  import {
83
87
  startOtelGrpcReceiver
84
- } from "./chunk-ERE47MCR.js";
88
+ } from "./chunk-5SJHA5ZH.js";
85
89
  import {
86
90
  __dirname,
87
91
  __require,
88
92
  assertBindAuthority,
89
93
  buildOtelReceiver,
94
+ isLoopbackHost,
90
95
  listenSteppingOtlp,
91
96
  readAuthEnv
92
- } from "./chunk-GDGUY4T6.js";
97
+ } from "./chunk-M7CGATVC.js";
93
98
 
94
99
  // src/cli.ts
95
- import path19 from "path";
100
+ import path21 from "path";
96
101
  import os5 from "os";
97
- import { promises as fs17 } from "fs";
102
+ import { promises as fs18 } from "fs";
98
103
 
99
104
  // src/banner.ts
100
105
  import path from "path";
@@ -3936,6 +3941,20 @@ import path10 from "path";
3936
3941
  import { fileURLToPath as fileURLToPath2 } from "url";
3937
3942
  import { spawn as spawn2 } from "child_process";
3938
3943
  import readline from "readline";
3944
+ var SnapshotWriteError = class extends Error {
3945
+ constructor(scanPath, nodeCount, edgeCount, cause) {
3946
+ super(`could not write the snapshot for ${scanPath}: ${cause.message}`);
3947
+ this.scanPath = scanPath;
3948
+ this.nodeCount = nodeCount;
3949
+ this.edgeCount = edgeCount;
3950
+ this.cause = cause;
3951
+ this.name = "SnapshotWriteError";
3952
+ }
3953
+ scanPath;
3954
+ nodeCount;
3955
+ edgeCount;
3956
+ cause;
3957
+ };
3939
3958
  async function extractAndPersist(opts) {
3940
3959
  const services = await discoverServices(opts.scanPath);
3941
3960
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
@@ -3947,7 +3966,11 @@ async function extractAndPersist(opts) {
3947
3966
  errorsPath: projectPaths.errorsPath
3948
3967
  });
3949
3968
  if (!opts.dryRun) {
3950
- await saveGraphToDisk(graph, projectPaths.snapshotPath);
3969
+ try {
3970
+ await saveGraphToDisk(graph, projectPaths.snapshotPath);
3971
+ } catch (err) {
3972
+ throw new SnapshotWriteError(opts.scanPath, graph.order, graph.size, err);
3973
+ }
3951
3974
  }
3952
3975
  return {
3953
3976
  graph,
@@ -4330,6 +4353,16 @@ async function healthIsForProject(restPort, project) {
4330
4353
  function daemonLogPath(projectPath3) {
4331
4354
  return path10.join(projectPath3, "neat-out", "daemon.log");
4332
4355
  }
4356
+ var DAEMON_LOG_TAIL_LINES = 12;
4357
+ async function readDaemonLogTail(projectPath3, maxLines = DAEMON_LOG_TAIL_LINES) {
4358
+ try {
4359
+ const raw = await fs9.readFile(daemonLogPath(projectPath3), "utf8");
4360
+ const lines = raw.split(/\r?\n/).map((l) => l.trimEnd()).filter((l) => l.trim().length > 0);
4361
+ return lines.slice(-maxLines);
4362
+ } catch {
4363
+ return [];
4364
+ }
4365
+ }
4333
4366
  function spawnDaemonDetached(spec) {
4334
4367
  const here = path10.dirname(fileURLToPath2(import.meta.url));
4335
4368
  const candidates = [
@@ -4376,6 +4409,72 @@ function spawnDaemonDetached(spec) {
4376
4409
  if (logFd !== null) fsSync.closeSync(logFd);
4377
4410
  return child;
4378
4411
  }
4412
+ async function ensureProjectDaemon(opts) {
4413
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
4414
+ const bindHost = resolveHost(
4415
+ {},
4416
+ typeof process.env.NEAT_AUTH_TOKEN === "string" && process.env.NEAT_AUTH_TOKEN.length > 0
4417
+ );
4418
+ const persistedPorts = await persistedPortsFor(opts.projectPath);
4419
+ if (persistedPorts && await healthIsForProject(persistedPorts.rest, opts.project)) {
4420
+ return { status: "already-running", ports: persistedPorts };
4421
+ }
4422
+ let allocated;
4423
+ if (persistedPorts && await isPortFree(persistedPorts.rest, bindHost) && await tripleFree(persistedPorts, bindHost)) {
4424
+ allocated = persistedPorts;
4425
+ } else {
4426
+ allocated = await allocatePorts(bindHost);
4427
+ }
4428
+ if (!allocated) return { status: "no-ports" };
4429
+ const release = await acquireSpawnLock(opts.projectPath);
4430
+ if (!release) {
4431
+ const reused = await waitForPeerDaemon(allocated.rest, opts.project, timeoutMs);
4432
+ return reused ? { status: "already-running", ports: allocated } : { status: "peer-timeout" };
4433
+ }
4434
+ try {
4435
+ if (await healthIsForProject(allocated.rest, opts.project)) {
4436
+ return { status: "already-running", ports: allocated };
4437
+ }
4438
+ spawnDaemonDetached({
4439
+ project: opts.project,
4440
+ projectPath: opts.projectPath,
4441
+ ports: allocated
4442
+ });
4443
+ const ready = await waitForDaemonReady(allocated.rest, opts.project, timeoutMs);
4444
+ if (!ready.ready) {
4445
+ return {
4446
+ status: "timed-out",
4447
+ ports: allocated,
4448
+ stillBootstrapping: ready.stillBootstrapping,
4449
+ brokenProjects: ready.brokenProjects
4450
+ };
4451
+ }
4452
+ return { status: "spawned", ports: allocated, brokenProjects: ready.brokenProjects };
4453
+ } catch (err) {
4454
+ return { status: "spawn-failed", message: err.message };
4455
+ } finally {
4456
+ await release();
4457
+ }
4458
+ }
4459
+ async function describeDaemonTimeout(outcome, projectPath3, timeoutMs) {
4460
+ const lines = [`neat: daemon did not become ready within ${timeoutMs}ms`];
4461
+ if (outcome.stillBootstrapping.length > 0) {
4462
+ lines.push(`neat: still bootstrapping: ${outcome.stillBootstrapping.join(", ")}`);
4463
+ }
4464
+ if (outcome.brokenProjects.length > 0) {
4465
+ lines.push(`neat: broken projects: ${outcome.brokenProjects.join(", ")}`);
4466
+ }
4467
+ const logPath = daemonLogPath(projectPath3);
4468
+ const tail = await readDaemonLogTail(projectPath3);
4469
+ if (tail.length > 0) {
4470
+ lines.push("");
4471
+ lines.push(`neat: the daemon's last words (${path10.relative(projectPath3, logPath)}):`);
4472
+ for (const line of tail) lines.push(` ${line}`);
4473
+ } else {
4474
+ lines.push(`neat: see ${logPath}`);
4475
+ }
4476
+ return lines;
4477
+ }
4379
4478
  function openBrowser(url) {
4380
4479
  if (!process.stdout.isTTY) return "failed";
4381
4480
  const platform = process.platform;
@@ -4409,14 +4508,26 @@ async function runOrchestrator(opts) {
4409
4508
  result.exitCode = 2;
4410
4509
  return result;
4411
4510
  }
4412
- printBanner();
4511
+ if (!opts.skipBanner) printBanner();
4413
4512
  console.log(`neat: ${opts.scanPath}`);
4414
4513
  console.log("");
4415
- const persisted = await extractAndPersist({
4416
- scanPath: opts.scanPath,
4417
- project: opts.project,
4418
- projectExplicit: opts.projectExplicit
4419
- });
4514
+ let persisted;
4515
+ try {
4516
+ persisted = await extractAndPersist({
4517
+ scanPath: opts.scanPath,
4518
+ project: opts.project,
4519
+ projectExplicit: opts.projectExplicit
4520
+ });
4521
+ } catch (err) {
4522
+ if (!(err instanceof SnapshotWriteError)) throw err;
4523
+ console.error(`neat: extracted ${opts.scanPath}, but could not write the snapshot.`);
4524
+ console.error(`neat: the graph came to ${err.nodeCount} nodes and ${err.edgeCount} edges \u2014 too large to save.`);
4525
+ console.error(`neat: point neat at one project instead of a tree that holds many:`);
4526
+ console.error(`neat: npx neat.is <path-to-one-project>`);
4527
+ console.error(`neat: cause: ${err.cause.message}`);
4528
+ result.exitCode = 1;
4529
+ return result;
4530
+ }
4420
4531
  const { graph, services, languages } = persisted;
4421
4532
  result.steps.discovery = { services: services.length, languages };
4422
4533
  console.log(`discovered ${services.length} service(s) across ${languages.length} language(s)`);
@@ -4452,24 +4563,11 @@ async function runOrchestrator(opts) {
4452
4563
  } catch (err) {
4453
4564
  if (!(err instanceof ProjectNameCollisionError)) throw err;
4454
4565
  console.error(`neat: ${err.message}`);
4455
- console.error("pass --project <other-name> to register under a different name.");
4566
+ console.error(`neat: run \`neat <path> --project <other-name>\` to register it under a different name,`);
4567
+ console.error(`neat: or \`neat uninstall ${opts.project}\` if the old registration is stale.`);
4456
4568
  result.exitCode = 1;
4457
4569
  return result;
4458
4570
  }
4459
- const siblings = await listProjects();
4460
- const paused = [];
4461
- for (const p of siblings) {
4462
- if (p.name !== currentProjectName && p.status === "active") {
4463
- await setStatus(p.name, "paused");
4464
- paused.push(p.name);
4465
- }
4466
- }
4467
- if (paused.length > 0) {
4468
- const plural = paused.length === 1 ? "" : "s";
4469
- console.log(
4470
- `neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
4471
- );
4472
- }
4473
4571
  if (!runApply) {
4474
4572
  result.steps.apply.skipped = true;
4475
4573
  console.log("skipped instrumentation (--no-instrument)");
@@ -4485,75 +4583,45 @@ async function runOrchestrator(opts) {
4485
4583
  }
4486
4584
  }
4487
4585
  const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
4488
- const bindHost = resolveHost(
4489
- {},
4490
- typeof process.env.NEAT_AUTH_TOKEN === "string" && process.env.NEAT_AUTH_TOKEN.length > 0
4491
- );
4492
- const persistedPorts = await persistedPortsFor(opts.scanPath);
4493
- let allocated = null;
4494
- if (persistedPorts && await healthIsForProject(persistedPorts.rest, currentProjectName)) {
4495
- result.steps.daemon = "already-running";
4496
- allocated = persistedPorts;
4497
- } else {
4498
- if (persistedPorts && await isPortFree(persistedPorts.rest, bindHost) && await tripleFree(persistedPorts, bindHost)) {
4499
- allocated = persistedPorts;
4500
- } else {
4501
- allocated = await allocatePorts(bindHost);
4502
- }
4503
- if (!allocated) {
4586
+ const outcome = await ensureProjectDaemon({
4587
+ project: currentProjectName,
4588
+ projectPath: opts.scanPath,
4589
+ timeoutMs
4590
+ });
4591
+ const allocated = "ports" in outcome ? outcome.ports : null;
4592
+ switch (outcome.status) {
4593
+ case "already-running":
4594
+ result.steps.daemon = "already-running";
4595
+ break;
4596
+ case "spawned":
4597
+ result.steps.daemon = "spawned";
4598
+ if (outcome.brokenProjects.length > 0) {
4599
+ console.warn(
4600
+ `neat: ${outcome.brokenProjects.length} project(s) reported broken: ${outcome.brokenProjects.join(", ")}`
4601
+ );
4602
+ }
4603
+ break;
4604
+ case "no-ports":
4504
4605
  for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) {
4505
4606
  console.error(line);
4506
4607
  }
4507
4608
  result.exitCode = 3;
4508
4609
  return result;
4509
- }
4510
- const release = await acquireSpawnLock(opts.scanPath);
4511
- if (!release) {
4512
- const reused = await waitForPeerDaemon(allocated.rest, currentProjectName, timeoutMs);
4513
- if (reused) {
4514
- result.steps.daemon = "already-running";
4515
- } else {
4516
- console.error("neat: another `neat` is spawning this project but its daemon did not come up in time");
4517
- result.exitCode = 1;
4518
- return result;
4519
- }
4520
- } else {
4521
- try {
4522
- if (await healthIsForProject(allocated.rest, currentProjectName)) {
4523
- result.steps.daemon = "already-running";
4524
- } else {
4525
- spawnDaemonDetached({
4526
- project: currentProjectName,
4527
- projectPath: opts.scanPath,
4528
- ports: allocated
4529
- });
4530
- const ready = await waitForDaemonReady(allocated.rest, currentProjectName, timeoutMs);
4531
- result.steps.daemon = ready.ready ? "spawned" : "timed-out";
4532
- if (!ready.ready) {
4533
- console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
4534
- if (ready.stillBootstrapping.length > 0) {
4535
- console.error(`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`);
4536
- }
4537
- if (ready.brokenProjects.length > 0) {
4538
- console.error(`neat: broken projects: ${ready.brokenProjects.join(", ")}`);
4539
- }
4540
- result.exitCode = 1;
4541
- return result;
4542
- }
4543
- if (ready.brokenProjects.length > 0) {
4544
- console.warn(
4545
- `neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
4546
- );
4547
- }
4548
- }
4549
- } catch (err) {
4550
- console.error(`neat: daemon spawn failed \u2014 ${err.message}`);
4551
- result.exitCode = 1;
4552
- return result;
4553
- } finally {
4554
- await release();
4610
+ case "timed-out":
4611
+ result.steps.daemon = "timed-out";
4612
+ for (const line of await describeDaemonTimeout(outcome, opts.scanPath, timeoutMs)) {
4613
+ console.error(line);
4555
4614
  }
4556
- }
4615
+ result.exitCode = 1;
4616
+ return result;
4617
+ case "peer-timeout":
4618
+ console.error("neat: another `neat` is spawning this project but its daemon did not come up in time");
4619
+ result.exitCode = 1;
4620
+ return result;
4621
+ case "spawn-failed":
4622
+ console.error(`neat: daemon spawn failed \u2014 ${outcome.message}`);
4623
+ result.exitCode = 1;
4624
+ return result;
4557
4625
  }
4558
4626
  const webPort = allocated?.web ?? NEAT_PORTS[2];
4559
4627
  const dashboardUrl = opts.dashboardUrl ?? `http://localhost:${webPort}`;
@@ -5245,6 +5313,196 @@ async function clearActiveProfile(home = neatHome()) {
5245
5313
  });
5246
5314
  }
5247
5315
 
5316
+ // src/daemon-probe.ts
5317
+ var PROBE_ATTEMPT_TIMEOUT_MS = 3e4;
5318
+ var PROBE_TOTAL_BUDGET_MS = 12e4;
5319
+ var PROBE_RETRY_PAUSE_MS = 2e3;
5320
+ function isTimeoutError(err) {
5321
+ const name = err?.name;
5322
+ return name === "TimeoutError" || name === "AbortError";
5323
+ }
5324
+ async function probeOnce(fetchImpl, root, token, timeoutMs) {
5325
+ let res;
5326
+ try {
5327
+ res = await fetchImpl(`${root}/health`, {
5328
+ headers: { authorization: `Bearer ${token}` },
5329
+ signal: AbortSignal.timeout(timeoutMs)
5330
+ });
5331
+ } catch (err) {
5332
+ if (isTimeoutError(err)) return { kind: "timeout" };
5333
+ return { kind: "unreachable", detail: err.message };
5334
+ }
5335
+ if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
5336
+ if (!res.ok) return { kind: "not-neat", status: res.status };
5337
+ const contentType = res.headers.get("content-type") ?? "";
5338
+ if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
5339
+ return { kind: "ok" };
5340
+ }
5341
+ async function probeDaemon(fetchImpl, endpoint, token, hooks) {
5342
+ const root = endpoint.replace(/\/$/, "");
5343
+ const deadline = hooks.now() + PROBE_TOTAL_BUDGET_MS;
5344
+ let warned = false;
5345
+ for (; ; ) {
5346
+ const result = await probeOnce(fetchImpl, root, token, PROBE_ATTEMPT_TIMEOUT_MS);
5347
+ if (result.kind !== "timeout") return result;
5348
+ if (!warned) {
5349
+ hooks.onWaiting();
5350
+ warned = true;
5351
+ }
5352
+ if (hooks.now() >= deadline) {
5353
+ return { kind: "unreachable", detail: `no response after ${Math.round(PROBE_TOTAL_BUDGET_MS / 1e3)}s` };
5354
+ }
5355
+ await hooks.sleep(PROBE_RETRY_PAUSE_MS);
5356
+ }
5357
+ }
5358
+
5359
+ // src/login-connect.ts
5360
+ import { execFile } from "child_process";
5361
+ import { promisify } from "util";
5362
+ import { promises as fs11 } from "fs";
5363
+ import path12 from "path";
5364
+ var execFileAsync = promisify(execFile);
5365
+ async function hasLocalSnapshot(cwd) {
5366
+ try {
5367
+ await fs11.access(path12.join(cwd, "neat-out", "graph.json"));
5368
+ return true;
5369
+ } catch {
5370
+ return false;
5371
+ }
5372
+ }
5373
+ var POLL_INTERVAL_MS = 3e3;
5374
+ var POLL_BUDGET_MS = 10 * 6e4;
5375
+ var CP_TIMEOUT_MS = 1e4;
5376
+ async function detectGitHubRepo(cwd) {
5377
+ const read = async (args) => {
5378
+ try {
5379
+ const { stdout } = await execFileAsync("git", args, { cwd, timeout: 5e3 });
5380
+ return stdout.trim();
5381
+ } catch {
5382
+ return null;
5383
+ }
5384
+ };
5385
+ let url = await read(["remote", "get-url", "origin"]);
5386
+ if (!url) {
5387
+ const names = await read(["remote"]);
5388
+ const first = names?.split(/\r?\n/).find((l) => l.trim().length > 0)?.trim();
5389
+ if (!first) return null;
5390
+ url = await read(["remote", "get-url", first]);
5391
+ }
5392
+ if (!url) return null;
5393
+ return parseGitHubRemote(url);
5394
+ }
5395
+ function parseGitHubRemote(url) {
5396
+ const trimmed = url.trim().replace(/\.git$/, "");
5397
+ const m = trimmed.match(/^https?:\/\/(?:[^@/]+@)?github\.com\/([^/]+)\/([^/]+)$/) ?? trimmed.match(/^git@github\.com:([^/]+)\/([^/]+)$/) ?? trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+)$/);
5398
+ if (!m) return null;
5399
+ const owner = m[1];
5400
+ const name = m[2];
5401
+ if (!owner || !name) return null;
5402
+ return { owner, name };
5403
+ }
5404
+ async function cpGetJson(fetchImpl, cpUrl, path22, accessToken) {
5405
+ try {
5406
+ const res = await fetchImpl(`${cpUrl}${path22}`, {
5407
+ headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
5408
+ signal: AbortSignal.timeout(CP_TIMEOUT_MS)
5409
+ });
5410
+ if (!res.ok) return { ok: false };
5411
+ return { ok: true, body: await res.json() };
5412
+ } catch {
5413
+ return { ok: false };
5414
+ }
5415
+ }
5416
+ async function listBoundRepos(fetchImpl, cpUrl, accessToken, projectId) {
5417
+ const res = await cpGetJson(
5418
+ fetchImpl,
5419
+ cpUrl,
5420
+ `/me/projects/${encodeURIComponent(projectId)}/repos`,
5421
+ accessToken
5422
+ );
5423
+ if (!res.ok || !Array.isArray(res.body)) return null;
5424
+ return res.body;
5425
+ }
5426
+ async function waitForFirstProject(fetchImpl, cpUrl, accessToken, deps) {
5427
+ const pollMs = deps.pollMs ?? POLL_INTERVAL_MS;
5428
+ const deadline = deps.now() + (deps.budgetMs ?? POLL_BUDGET_MS);
5429
+ for (; ; ) {
5430
+ if (deps.signal?.aborted) return { kind: "aborted" };
5431
+ const me = await cpGetJson(fetchImpl, cpUrl, "/me", accessToken);
5432
+ if (me.ok) {
5433
+ const running = (me.body.projects ?? []).find((p) => p.status === "running");
5434
+ if (running) return { kind: "project", project: running };
5435
+ }
5436
+ if (deps.now() >= deadline) return { kind: "timeout" };
5437
+ await deps.sleep(pollMs);
5438
+ if (deps.signal?.aborted) return { kind: "aborted" };
5439
+ }
5440
+ }
5441
+ function bindUrl(webUrl, projectId) {
5442
+ return `${webUrl}/config/repos?project=${encodeURIComponent(projectId)}`;
5443
+ }
5444
+ function onboardingUrl(webUrl) {
5445
+ return `${webUrl}/onboarding`;
5446
+ }
5447
+ async function runPostLoginConnect(input) {
5448
+ const d = input.deps;
5449
+ const out = d.out ?? ((line) => console.log(line));
5450
+ const fetchImpl = d.fetchImpl ?? fetch;
5451
+ const cwd = d.cwd ?? process.cwd();
5452
+ const detect6 = d.detectRepo ?? detectGitHubRepo;
5453
+ const readLine = d.readLine;
5454
+ const repo = await detect6(cwd);
5455
+ out("");
5456
+ if (!repo) {
5457
+ out("This directory has no GitHub remote, so it can't be bound to the project.");
5458
+ out(` Push its graph instead: neat sync --to ${input.project.name}`);
5459
+ out(` Manage projects and repos: ${input.webUrl}`);
5460
+ return;
5461
+ }
5462
+ const slug = `${repo.owner}/${repo.name}`;
5463
+ const bound = await listBoundRepos(fetchImpl, input.cpUrl, input.accessToken, input.project.id);
5464
+ const match = bound?.find((r) => r.owner === repo.owner && r.name === repo.name);
5465
+ if (match) {
5466
+ const state = match.syncStatus ? ` (${match.syncStatus})` : "";
5467
+ out(`${slug} is bound to ${input.project.name}${state} \u2014 hosted NEAT extracts it for you.`);
5468
+ out(` See the graph: ${input.webUrl}`);
5469
+ return;
5470
+ }
5471
+ out(`${slug} isn't part of ${input.project.name} yet \u2014 logging in connected your account, not this repo.`);
5472
+ out("");
5473
+ const installed = input.me?.github?.installed;
5474
+ 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";
5475
+ out(bindLine);
5476
+ out(`2) Push the graph on this machine now \u2014 a snapshot, not a subscription`);
5477
+ out("3) Neither, for now");
5478
+ const answer = readLine ? (await readLine("Choose 1, 2 or 3 (Enter skips): "))?.trim() : void 0;
5479
+ if (answer === "1") {
5480
+ const url = bindUrl(input.webUrl, input.project.id);
5481
+ const opened = (d.openBrowser ?? (() => false))(url);
5482
+ out("");
5483
+ out(opened ? `Opened ${url}` : `Open this to bind the repo:
5484
+ ${url}`);
5485
+ return;
5486
+ }
5487
+ if (answer === "2") {
5488
+ const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
5489
+ out("");
5490
+ if (!hasGraph) {
5491
+ out("There is no local graph to push yet. Build one, then push it:");
5492
+ out(" neat");
5493
+ out(` neat sync --to ${input.project.name}`);
5494
+ return;
5495
+ }
5496
+ out("Push the local graph with:");
5497
+ out(` neat sync --to ${input.project.name}`);
5498
+ return;
5499
+ }
5500
+ out("");
5501
+ out("No problem \u2014 when you want this repo in the graph:");
5502
+ out(` Bind it: ${bindUrl(input.webUrl, input.project.id)}`);
5503
+ out(` Or push it: neat sync --to ${input.project.name}`);
5504
+ }
5505
+
5248
5506
  // src/login-sso.ts
5249
5507
  var DEFAULT_CP_URL = "https://api.neat.is";
5250
5508
  var DEFAULT_WEB_URL = "https://app.neat.is";
@@ -5270,6 +5528,7 @@ function pickProject(projects, want) {
5270
5528
  return {
5271
5529
  error: {
5272
5530
  code: 1,
5531
+ kind: "no-project",
5273
5532
  message: `no running project to connect to \u2014 create + provision one in the console first${listed}`
5274
5533
  }
5275
5534
  };
@@ -5320,7 +5579,7 @@ async function exchangeCredential(cpUrl, accessToken, opts, deps) {
5320
5579
  if (!cred.endpoint || !cred.authToken) {
5321
5580
  return { error: { code: 1, message: "the credential response was missing endpoint/authToken" } };
5322
5581
  }
5323
- return { project, cred };
5582
+ return { project, cred, me };
5324
5583
  }
5325
5584
  function defaultOpenBrowser(url) {
5326
5585
  const platform = process.platform;
@@ -5402,12 +5661,50 @@ async function runSsoLogin(opts, deps) {
5402
5661
  }
5403
5662
  accessToken = lb.token;
5404
5663
  }
5405
- const ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
5664
+ const fetchImpl = deps.fetchImpl ?? fetch;
5665
+ const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
5666
+ const now = deps.now ?? Date.now;
5667
+ let ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
5668
+ if ("error" in ex && ex.error.kind === "no-project" && !opts.json) {
5669
+ const url = onboardingUrl(opts.webUrl);
5670
+ out("");
5671
+ out("You don't have a project yet \u2014 let's make one.");
5672
+ const opened = (deps.openBrowser ?? defaultOpenBrowser)(url);
5673
+ out(opened ? `Opened ${url}` : `Open this to create your first project:
5674
+ ${url}`);
5675
+ out("Waiting for it to come up \u2014 Ctrl-C to stop and finish later.");
5676
+ const waited = await waitForFirstProject(fetchImpl, opts.cpUrl, accessToken, {
5677
+ sleep: sleep2,
5678
+ now,
5679
+ ...deps.signal ? { signal: deps.signal } : {}
5680
+ });
5681
+ if (waited.kind === "aborted") {
5682
+ out("Stopped. Run `neat login` again once your project is up.");
5683
+ return 130;
5684
+ }
5685
+ if (waited.kind === "timeout") {
5686
+ err("neat login: no project came up while waiting \u2014 run `neat login` again once it does");
5687
+ return 1;
5688
+ }
5689
+ out(`${waited.project.name} is up.`);
5690
+ ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
5691
+ }
5406
5692
  if ("error" in ex) {
5407
5693
  err(`neat login: ${ex.error.message}`);
5408
5694
  return ex.error.code;
5409
5695
  }
5410
- const { project, cred } = ex;
5696
+ const { project, cred, me } = ex;
5697
+ const probe = await probeDaemon(fetchImpl, cred.endpoint, cred.authToken, {
5698
+ sleep: sleep2,
5699
+ now,
5700
+ onWaiting: () => err("Waking the hosted daemon \u2014 a cold instance can take up to a minute\u2026")
5701
+ });
5702
+ if (probe.kind !== "ok") {
5703
+ 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`;
5704
+ err(`neat login: "${project.name}" is provisioned, but its daemon isn't answering \u2014 ${why}.`);
5705
+ err(`Nothing was changed. Check the project at ${opts.webUrl} and run \`neat login\` again.`);
5706
+ return probe.kind === "unreachable" ? 3 : 1;
5707
+ }
5411
5708
  await upsertProfile(
5412
5709
  { name: opts.name, endpoint: cred.endpoint, authToken: cred.authToken },
5413
5710
  { makeActive: true, ...deps.home ? { home: deps.home } : {} }
@@ -5435,10 +5732,20 @@ async function runSsoLogin(opts, deps) {
5435
5732
  out(` OTEL_EXPORTER_OTLP_ENDPOINT=${cred.ingestEndpoint}`);
5436
5733
  out(` OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${cred.otelToken}`);
5437
5734
  }
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}`);
5735
+ await runPostLoginConnect({
5736
+ cpUrl: opts.cpUrl,
5737
+ webUrl: opts.webUrl,
5738
+ accessToken,
5739
+ project: { id: project.id, name: project.name },
5740
+ me,
5741
+ deps: {
5742
+ fetchImpl,
5743
+ out,
5744
+ ...deps.readLine ? { readLine: deps.readLine } : {},
5745
+ ...deps.openBrowser ? { openBrowser: deps.openBrowser } : { openBrowser: defaultOpenBrowser },
5746
+ ...deps.connect ?? {}
5747
+ }
5748
+ });
5442
5749
  out("");
5443
5750
  out("Run `neat logout` to switch back to your local daemon.");
5444
5751
  }
@@ -5545,8 +5852,8 @@ async function runConnectCommand(rawArgs, deps = {}) {
5545
5852
  }
5546
5853
 
5547
5854
  // src/doctor-cli.ts
5548
- import { promises as fs11 } from "fs";
5549
- import path12 from "path";
5855
+ import { promises as fs12 } from "fs";
5856
+ import path13 from "path";
5550
5857
 
5551
5858
  // src/cli-client.ts
5552
5859
  import { Provenance as Provenance3 } from "@neat.is/types";
@@ -5574,10 +5881,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
5574
5881
  const root = baseUrl.replace(/\/$/, "");
5575
5882
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
5576
5883
  return {
5577
- async get(path20) {
5884
+ async get(path22) {
5578
5885
  let res;
5579
5886
  try {
5580
- res = await fetchImpl(`${root}${path20}`, {
5887
+ res = await fetchImpl(`${root}${path22}`, {
5581
5888
  headers: { ...authHeader }
5582
5889
  });
5583
5890
  } catch (err) {
@@ -5589,16 +5896,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
5589
5896
  const body = await res.text().catch(() => "");
5590
5897
  throw new HttpError(
5591
5898
  res.status,
5592
- `${res.status} ${res.statusText} on GET ${path20}: ${body}`,
5899
+ `${res.status} ${res.statusText} on GET ${path22}: ${body}`,
5593
5900
  body
5594
5901
  );
5595
5902
  }
5596
5903
  return await res.json();
5597
5904
  },
5598
- async post(path20, body) {
5905
+ async post(path22, body) {
5599
5906
  let res;
5600
5907
  try {
5601
- res = await fetchImpl(`${root}${path20}`, {
5908
+ res = await fetchImpl(`${root}${path22}`, {
5602
5909
  method: "POST",
5603
5910
  headers: { "content-type": "application/json", ...authHeader },
5604
5911
  body: JSON.stringify(body)
@@ -5612,7 +5919,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
5612
5919
  const text = await res.text().catch(() => "");
5613
5920
  throw new HttpError(
5614
5921
  res.status,
5615
- `${res.status} ${res.statusText} on POST ${path20}: ${text}`,
5922
+ `${res.status} ${res.statusText} on POST ${path22}: ${text}`,
5616
5923
  text
5617
5924
  );
5618
5925
  }
@@ -5626,12 +5933,12 @@ function projectPath(project, suffix) {
5626
5933
  }
5627
5934
  async function runRootCause(client, input) {
5628
5935
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
5629
- const path20 = projectPath(
5936
+ const path22 = projectPath(
5630
5937
  input.project,
5631
5938
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
5632
5939
  );
5633
5940
  try {
5634
- const result = await client.get(path20);
5941
+ const result = await client.get(path22);
5635
5942
  const arrowPath = result.traversalPath.join(" \u2190 ");
5636
5943
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
5637
5944
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -5657,12 +5964,12 @@ async function runRootCause(client, input) {
5657
5964
  }
5658
5965
  async function runBlastRadius(client, input) {
5659
5966
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
5660
- const path20 = projectPath(
5967
+ const path22 = projectPath(
5661
5968
  input.project,
5662
5969
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
5663
5970
  );
5664
5971
  try {
5665
- const result = await client.get(path20);
5972
+ const result = await client.get(path22);
5666
5973
  if (result.totalAffected === 0) {
5667
5974
  return {
5668
5975
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -5696,12 +6003,12 @@ function formatBlastEntry(n) {
5696
6003
  }
5697
6004
  async function runDependencies(client, input) {
5698
6005
  const depth = input.depth ?? 3;
5699
- const path20 = projectPath(
6006
+ const path22 = projectPath(
5700
6007
  input.project,
5701
6008
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
5702
6009
  );
5703
6010
  try {
5704
- const result = await client.get(path20);
6011
+ const result = await client.get(path22);
5705
6012
  if (result.total === 0) {
5706
6013
  return {
5707
6014
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -5793,9 +6100,9 @@ function formatDuration(ms) {
5793
6100
  return `${Math.round(h / 24)}d`;
5794
6101
  }
5795
6102
  async function runIncidents(client, input) {
5796
- const path20 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
6103
+ const path22 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
5797
6104
  try {
5798
- const body = await client.get(path20);
6105
+ const body = await client.get(path22);
5799
6106
  const events = body.events;
5800
6107
  if (events.length === 0) {
5801
6108
  return {
@@ -6159,7 +6466,7 @@ function checkNode(nodeVersion) {
6159
6466
  }
6160
6467
  async function neatOutExists(cwd) {
6161
6468
  try {
6162
- const st = await fs11.stat(path12.join(cwd, "neat-out"));
6469
+ const st = await fs12.stat(path13.join(cwd, "neat-out"));
6163
6470
  return st.isDirectory();
6164
6471
  } catch {
6165
6472
  return false;
@@ -6276,11 +6583,118 @@ async function runDoctorCommand(argv, deps = {}) {
6276
6583
  return checks.every((c) => c.ok) ? 0 : 1;
6277
6584
  }
6278
6585
 
6586
+ // src/up-cli.ts
6587
+ import path14 from "path";
6588
+ function parseUpArgs(argv, env) {
6589
+ const args = { json: false, help: false };
6590
+ for (let i = 0; i < argv.length; i++) {
6591
+ const a = argv[i];
6592
+ if (a === "--json") args.json = true;
6593
+ else if (a === "-h" || a === "--help") args.help = true;
6594
+ else if (a === "--project") args.project = argv[++i];
6595
+ else if (a.startsWith("--project=")) args.project = a.slice("--project=".length);
6596
+ else if (a === "--profile") args.profile = argv[++i];
6597
+ else if (a.startsWith("--profile=")) args.profile = a.slice("--profile=".length);
6598
+ else if (args.unknown === void 0) args.unknown = a;
6599
+ }
6600
+ if (!args.project && env.NEAT_PROJECT && env.NEAT_PROJECT.length > 0) args.project = env.NEAT_PROJECT;
6601
+ return args;
6602
+ }
6603
+ var USAGE = [
6604
+ "usage: neat up [--project <name>] [--json]",
6605
+ "",
6606
+ "Start this project's daemon, or recover it if it died. Does nothing when it is",
6607
+ "already running. Extracts and instruments nothing \u2014 run `neat` for that."
6608
+ ];
6609
+ async function runUpCommand(argv, deps) {
6610
+ const cwd = deps.cwd ?? process.cwd();
6611
+ const env = deps.env ?? process.env;
6612
+ const out = deps.out ?? ((line) => console.log(line));
6613
+ const err = deps.err ?? ((line) => console.error(line));
6614
+ const ensure = deps.ensureDaemon ?? ensureProjectDaemon;
6615
+ const args = parseUpArgs(argv, env);
6616
+ if (args.help) {
6617
+ for (const line of USAGE) out(line);
6618
+ return 0;
6619
+ }
6620
+ if (args.unknown !== void 0) {
6621
+ err(`neat up: unexpected argument \`${args.unknown}\``);
6622
+ for (const line of USAGE) err(line);
6623
+ return 2;
6624
+ }
6625
+ let entry2;
6626
+ try {
6627
+ entry2 = args.project ? await getProject(args.project) : await findProjectByPath(cwd);
6628
+ } catch {
6629
+ entry2 = void 0;
6630
+ }
6631
+ if (!entry2) {
6632
+ err(
6633
+ args.project ? `neat up: no project named "${args.project}" is registered on this machine \u2014 \`neat list\` shows what is.` : "neat up: This directory isn't a NEAT project yet \u2014 run `npx neat.is` here first, or pass --project <name>."
6634
+ );
6635
+ return 2;
6636
+ }
6637
+ let target;
6638
+ try {
6639
+ target = await deps.resolveTarget({ project: entry2.name, profile: args.profile });
6640
+ } catch (e) {
6641
+ err(`neat up: ${e.message}`);
6642
+ return 2;
6643
+ }
6644
+ if (!target.local) {
6645
+ err(`neat up: queries from here go to ${target.endpoint}, through ${target.via}.`);
6646
+ err("neat up: that daemon doesn't run on this machine, so it isn't yours to start.");
6647
+ return 2;
6648
+ }
6649
+ const outcome = await ensure({ project: entry2.name, projectPath: entry2.path });
6650
+ const log = daemonLogPath(entry2.path);
6651
+ if (args.json) {
6652
+ const ports = "ports" in outcome ? outcome.ports : void 0;
6653
+ out(
6654
+ JSON.stringify({
6655
+ project: entry2.name,
6656
+ path: entry2.path,
6657
+ status: outcome.status,
6658
+ ...ports ? { endpoint: `http://localhost:${ports.rest}`, ports } : {},
6659
+ ...outcome.status === "spawn-failed" ? { error: outcome.message } : {},
6660
+ log
6661
+ })
6662
+ );
6663
+ }
6664
+ switch (outcome.status) {
6665
+ case "already-running":
6666
+ if (!args.json) {
6667
+ out(`neat up: ${entry2.name} is already running \u2014 http://localhost:${outcome.ports.rest}`);
6668
+ }
6669
+ return 0;
6670
+ case "spawned":
6671
+ if (!args.json) {
6672
+ out(`neat up: started ${entry2.name} \u2014 http://localhost:${outcome.ports.rest}`);
6673
+ out(`neat up: traces on :${outcome.ports.otlp}, log at ${path14.relative(cwd, log) || log}`);
6674
+ }
6675
+ return 0;
6676
+ case "timed-out":
6677
+ if (!args.json) {
6678
+ for (const line of await describeDaemonTimeout(outcome, entry2.path, DEFAULT_DAEMON_READY_TIMEOUT_MS)) err(line);
6679
+ }
6680
+ return 1;
6681
+ case "peer-timeout":
6682
+ if (!args.json) {
6683
+ err("neat up: another `neat` is starting this project, and its daemon did not come up in time.");
6684
+ err(`neat up: see ${log}`);
6685
+ }
6686
+ return 1;
6687
+ case "spawn-failed":
6688
+ if (!args.json) err(`neat up: could not start the daemon \u2014 ${outcome.message}`);
6689
+ return 1;
6690
+ case "no-ports":
6691
+ if (!args.json) for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) err(line);
6692
+ return 3;
6693
+ }
6694
+ }
6695
+
6279
6696
  // src/login-cli.ts
6280
6697
  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
6698
  var DEFAULT_PROFILE_NAME = "hosted";
6285
6699
  function readFlagValue(argv, i) {
6286
6700
  const arg = argv[i];
@@ -6345,44 +6759,6 @@ function printLoginHelp(out) {
6345
6759
  out(' the profile (default "hosted"); the paste token can also come from NEAT_LOGIN_TOKEN.');
6346
6760
  out(" Exit 0 on success, 1 rejected/needs-action, 2 misuse, 3 unreachable.");
6347
6761
  }
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
6762
  async function runLoginCommand(argv, deps = {}) {
6387
6763
  const out = deps.out ?? ((line) => console.log(line));
6388
6764
  const err = deps.err ?? ((line) => console.error(line));
@@ -6415,8 +6791,13 @@ async function runLoginCommand(argv, deps = {}) {
6415
6791
  fetchImpl,
6416
6792
  out,
6417
6793
  err,
6794
+ readLine,
6795
+ sleep: sleep2,
6796
+ now,
6418
6797
  ...deps.openBrowser ? { openBrowser: deps.openBrowser } : {},
6419
- ...deps.home ? { home: deps.home } : {}
6798
+ ...deps.home ? { home: deps.home } : {},
6799
+ ...deps.signal ? { signal: deps.signal } : {},
6800
+ ...deps.connect ? { connect: deps.connect } : {}
6420
6801
  }
6421
6802
  );
6422
6803
  }
@@ -6572,8 +6953,8 @@ async function defaultReadSecret(prompt) {
6572
6953
  }
6573
6954
 
6574
6955
  // src/welcome.ts
6575
- import { promises as fs12 } from "fs";
6576
- import path13 from "path";
6956
+ import { promises as fs13 } from "fs";
6957
+ import path15 from "path";
6577
6958
  import readline4 from "readline/promises";
6578
6959
  var WORDMARK = [
6579
6960
  "\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 +7013,17 @@ async function runFrontDoor(deps, out) {
6632
7013
  const login = deps.login ?? (() => Promise.resolve(0));
6633
7014
  const orchestrator = deps.orchestrator ?? (() => Promise.resolve(0));
6634
7015
  const cwd = deps.cwd ?? process.cwd();
7016
+ const flagGiven = deps.instrumentFlagGiven ?? false;
7017
+ const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
6635
7018
  printHeader(out);
6636
7019
  out("Welcome to NEAT. Let's get you a graph of this system.");
6637
7020
  out("");
6638
7021
  const keyReader = deps.readKey !== void 0 ? { read: deps.readKey, done: () => {
6639
7022
  } } : createKeyReader();
6640
7023
  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);
7024
+ if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
6642
7025
  if (picked === 0) return login(["--browser"]);
6643
- return runSelfHosted(out, readLine, orchestrator, cwd);
7026
+ return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
6644
7027
  }
6645
7028
  var MENU_OPTIONS = [
6646
7029
  "Log me into Hosted Neat",
@@ -6743,7 +7126,7 @@ function decodeKey(buf) {
6743
7126
  function defaultMoveCursorUp(rows) {
6744
7127
  if (rows > 0) process.stdout.write(`\x1B[${rows}A`);
6745
7128
  }
6746
- async function runSelfHosted(out, readLine, orchestrator, cwd) {
7129
+ async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, instrumentFlagGiven) {
6747
7130
  const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
6748
7131
  if (wantsPrompt !== "n" && wantsPrompt !== "no") {
6749
7132
  out("");
@@ -6754,9 +7137,70 @@ async function runSelfHosted(out, readLine, orchestrator, cwd) {
6754
7137
  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
7138
  out("");
6756
7139
  }
7140
+ const project = await resolveProjectName(out, readLine, cwd, readRegistry2);
7141
+ const declined = instrumentFlagGiven ? void 0 : await askToInstrument(out, readLine);
6757
7142
  out("Building your local graph now\u2026");
6758
7143
  out("");
6759
- return orchestrator(cwd);
7144
+ const overrides = {
7145
+ headerShown: true,
7146
+ ...project !== void 0 ? { project } : {},
7147
+ ...declined === void 0 ? {} : declined ? { noInstrument: true } : { yes: true }
7148
+ };
7149
+ return orchestrator(cwd, overrides);
7150
+ }
7151
+ async function askToInstrument(out, readLine) {
7152
+ out("Instrument the services for OpenTelemetry now?");
7153
+ out("This edits package.json / requirements.txt / go.mod and runs the package manager.");
7154
+ const answer = (await readLine("[Y/n]: "))?.trim().toLowerCase();
7155
+ const declined = answer === "n" || answer === "no";
7156
+ if (declined) {
7157
+ out("");
7158
+ out("Skipping instrumentation \u2014 the graph will hold the declared side only.");
7159
+ out("Run `neat init . --apply` when you want the runtime half.");
7160
+ }
7161
+ out("");
7162
+ return declined;
7163
+ }
7164
+ async function resolveProjectName(out, readLine, cwd, readRegistry2) {
7165
+ const here = path15.resolve(cwd);
7166
+ const base = path15.basename(here);
7167
+ let taken;
7168
+ try {
7169
+ const { projects } = await readRegistry2();
7170
+ taken = new Map(projects.map((p) => [p.name, p.path]));
7171
+ } catch {
7172
+ return void 0;
7173
+ }
7174
+ const clash = taken.get(base);
7175
+ if (clash === void 0 || path15.resolve(clash) === here) return void 0;
7176
+ out("");
7177
+ out(`A project named \`${base}\` is already registered (${clash}).`);
7178
+ const suggested = firstFreeName(base, taken);
7179
+ for (; ; ) {
7180
+ const answer = (await readLine(`Name this one: [${suggested}] `))?.trim();
7181
+ if (answer === void 0) return suggested;
7182
+ const chosen = answer.length === 0 ? suggested : answer;
7183
+ if (!isUsableProjectName(chosen)) {
7184
+ out(`"${chosen}" won't work as a project name \u2014 letters, digits, dot, dash and underscore.`);
7185
+ continue;
7186
+ }
7187
+ const other = taken.get(chosen);
7188
+ if (other !== void 0 && path15.resolve(other) !== here) {
7189
+ out(`\`${chosen}\` is registered too (${other}).`);
7190
+ continue;
7191
+ }
7192
+ out("");
7193
+ return chosen;
7194
+ }
7195
+ }
7196
+ function firstFreeName(base, taken) {
7197
+ for (let n = 2; ; n++) {
7198
+ const candidate = `${base}-${n}`;
7199
+ if (!taken.has(candidate)) return candidate;
7200
+ }
7201
+ }
7202
+ function isUsableProjectName(name) {
7203
+ return name.length > 0 && name !== "." && name !== ".." && /^[A-Za-z0-9._-]+$/.test(name);
6760
7204
  }
6761
7205
  async function shouldShowWelcome(deps = {}) {
6762
7206
  const stdinTTY = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
@@ -6766,9 +7210,9 @@ async function shouldShowWelcome(deps = {}) {
6766
7210
  const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
6767
7211
  const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
6768
7212
  try {
6769
- const here = path13.resolve(cwd);
7213
+ const here = path15.resolve(cwd);
6770
7214
  const { projects } = await readRegistry2();
6771
- if (projects.some((p) => path13.resolve(p.path) === here)) return false;
7215
+ if (projects.some((p) => path15.resolve(p.path) === here)) return false;
6772
7216
  if (await hasSnapshot(here)) return false;
6773
7217
  return true;
6774
7218
  } catch {
@@ -6777,7 +7221,7 @@ async function shouldShowWelcome(deps = {}) {
6777
7221
  }
6778
7222
  async function defaultHasSnapshot(cwd) {
6779
7223
  try {
6780
- const entries = await fs12.readdir(path13.join(cwd, "neat-out"));
7224
+ const entries = await fs13.readdir(path15.join(cwd, "neat-out"));
6781
7225
  return entries.some((e) => e.endsWith(".json"));
6782
7226
  } catch {
6783
7227
  return false;
@@ -6794,27 +7238,27 @@ async function defaultReadLine2(prompt) {
6794
7238
  }
6795
7239
 
6796
7240
  // src/hooks-cli.ts
6797
- import path14 from "path";
7241
+ import path16 from "path";
6798
7242
  import os2 from "os";
6799
- import { promises as fs13 } from "fs";
7243
+ import { promises as fs14 } from "fs";
6800
7244
  import { fileURLToPath as fileURLToPath3 } from "url";
6801
7245
  var HOOK_FILENAME = "neat-search-nudge.mjs";
6802
7246
  var GUIDE_FILENAME = "GRAPH_FIRST.md";
6803
7247
  var GUIDE_INSTALL_NAME = "neat-graph-first.md";
6804
7248
  var HOOK_MATCHER = "Grep|Glob|Bash";
6805
7249
  function moduleDir() {
6806
- return typeof __dirname !== "undefined" ? __dirname : path14.dirname(fileURLToPath3(import.meta.url));
7250
+ return typeof __dirname !== "undefined" ? __dirname : path16.dirname(fileURLToPath3(import.meta.url));
6807
7251
  }
6808
7252
  async function readSkillAsset(rel) {
6809
7253
  const here = moduleDir();
6810
7254
  const candidates = [
6811
- path14.resolve(here, "../../claude-skill", rel),
6812
- path14.resolve(here, "../../../claude-skill", rel),
6813
- path14.resolve(here, "../claude-skill", rel)
7255
+ path16.resolve(here, "../../claude-skill", rel),
7256
+ path16.resolve(here, "../../../claude-skill", rel),
7257
+ path16.resolve(here, "../claude-skill", rel)
6814
7258
  ];
6815
7259
  for (const candidate of candidates) {
6816
7260
  try {
6817
- return await fs13.readFile(candidate, "utf8");
7261
+ return await fs14.readFile(candidate, "utf8");
6818
7262
  } catch {
6819
7263
  }
6820
7264
  }
@@ -6824,20 +7268,20 @@ async function readSkillAsset(rel) {
6824
7268
  }
6825
7269
  function neatHome2() {
6826
7270
  const override = process.env.NEAT_HOME;
6827
- if (override && override.length > 0) return path14.resolve(override);
6828
- return path14.join(os2.homedir(), ".neat");
7271
+ if (override && override.length > 0) return path16.resolve(override);
7272
+ return path16.join(os2.homedir(), ".neat");
6829
7273
  }
6830
7274
  function claudeSettingsPath() {
6831
7275
  const override = process.env.NEAT_CLAUDE_SETTINGS;
6832
- if (override && override.length > 0) return path14.resolve(override);
7276
+ if (override && override.length > 0) return path16.resolve(override);
6833
7277
  const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
6834
- return path14.join(home, ".claude", "settings.json");
7278
+ return path16.join(home, ".claude", "settings.json");
6835
7279
  }
6836
7280
  function installedHookPath() {
6837
- return path14.join(neatHome2(), "hooks", HOOK_FILENAME);
7281
+ return path16.join(neatHome2(), "hooks", HOOK_FILENAME);
6838
7282
  }
6839
7283
  function gateFlagPath() {
6840
- return path14.join(neatHome2(), "hooks", "gate-enabled");
7284
+ return path16.join(neatHome2(), "hooks", "gate-enabled");
6841
7285
  }
6842
7286
  function isNeatSearchEntry(entry2) {
6843
7287
  return (entry2.hooks ?? []).some(
@@ -6870,14 +7314,14 @@ async function runHooks(opts) {
6870
7314
  const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
6871
7315
  const guide = await readSkillAsset(GUIDE_FILENAME);
6872
7316
  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");
7317
+ await fs14.mkdir(path16.dirname(scriptPath), { recursive: true });
7318
+ await fs14.writeFile(scriptPath, hookScript, { mode: 493 });
7319
+ const guidePath = path16.join(neatHome2(), GUIDE_INSTALL_NAME);
7320
+ await fs14.writeFile(guidePath, guide, "utf8");
6877
7321
  const settingsFile = claudeSettingsPath();
6878
7322
  let settings = {};
6879
7323
  try {
6880
- settings = JSON.parse(await fs13.readFile(settingsFile, "utf8"));
7324
+ settings = JSON.parse(await fs14.readFile(settingsFile, "utf8"));
6881
7325
  } catch (err) {
6882
7326
  if (err.code !== "ENOENT") {
6883
7327
  console.error(
@@ -6899,14 +7343,14 @@ async function runHooks(opts) {
6899
7343
  ...settings,
6900
7344
  hooks: { ...hooks, PreToolUse: preToolUse }
6901
7345
  };
6902
- await fs13.mkdir(path14.dirname(settingsFile), { recursive: true });
6903
- await fs13.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
7346
+ await fs14.mkdir(path16.dirname(settingsFile), { recursive: true });
7347
+ await fs14.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
6904
7348
  const flag = gateFlagPath();
6905
7349
  if (opts.gate) {
6906
- await fs13.mkdir(path14.dirname(flag), { recursive: true });
6907
- await fs13.writeFile(flag, "1\n", "utf8");
7350
+ await fs14.mkdir(path16.dirname(flag), { recursive: true });
7351
+ await fs14.writeFile(flag, "1\n", "utf8");
6908
7352
  } else {
6909
- await fs13.rm(flag, { force: true });
7353
+ await fs14.rm(flag, { force: true });
6910
7354
  }
6911
7355
  const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
6912
7356
  console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
@@ -6995,8 +7439,8 @@ async function runHooksCommand(args) {
6995
7439
  }
6996
7440
 
6997
7441
  // src/claude-cli.ts
6998
- import path15 from "path";
6999
- import { promises as fs14 } from "fs";
7442
+ import path17 from "path";
7443
+ import { promises as fs15 } from "fs";
7000
7444
  var NEAT_SECTION_HEADING = "## neat";
7001
7445
  var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
7002
7446
  code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
@@ -7030,8 +7474,8 @@ ${NEAT_DIRECTIVE_BODY}
7030
7474
  }
7031
7475
  function claudeMdPath() {
7032
7476
  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");
7477
+ if (override && override.length > 0) return path17.resolve(override);
7478
+ return path17.join(process.cwd(), "CLAUDE.md");
7035
7479
  }
7036
7480
  function splitAroundSection(raw) {
7037
7481
  const lines = raw.split("\n");
@@ -7059,7 +7503,7 @@ function compose(before, after) {
7059
7503
  }
7060
7504
  async function readIfExists(file) {
7061
7505
  try {
7062
- return await fs14.readFile(file, "utf8");
7506
+ return await fs15.readFile(file, "utf8");
7063
7507
  } catch (err) {
7064
7508
  if (err.code === "ENOENT") return null;
7065
7509
  throw err;
@@ -7070,8 +7514,8 @@ async function runInstall() {
7070
7514
  const raw = await readIfExists(file) ?? "";
7071
7515
  const { before, after, found } = splitAroundSection(raw);
7072
7516
  const next = compose(before, after);
7073
- await fs14.mkdir(path15.dirname(file), { recursive: true });
7074
- await fs14.writeFile(file, next, "utf8");
7517
+ await fs15.mkdir(path17.dirname(file), { recursive: true });
7518
+ await fs15.writeFile(file, next, "utf8");
7075
7519
  const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
7076
7520
  console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
7077
7521
  console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
@@ -7092,7 +7536,7 @@ async function runUninstall() {
7092
7536
  }
7093
7537
  const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
7094
7538
  const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
7095
- await fs14.writeFile(file, next, "utf8");
7539
+ await fs15.writeFile(file, next, "utf8");
7096
7540
  console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
7097
7541
  return { exitCode: 0 };
7098
7542
  }
@@ -7134,9 +7578,9 @@ async function runClaudeCommand(args) {
7134
7578
  }
7135
7579
 
7136
7580
  // src/codex-cli.ts
7137
- import path16 from "path";
7581
+ import path18 from "path";
7138
7582
  import os3 from "os";
7139
- import { promises as fs15 } from "fs";
7583
+ import { promises as fs16 } from "fs";
7140
7584
  import { isDeepStrictEqual } from "util";
7141
7585
  import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
7142
7586
  var CODEX_MCP_SERVER = {
@@ -7154,14 +7598,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
7154
7598
  var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
7155
7599
  function codexConfigPath() {
7156
7600
  const override = process.env.NEAT_CODEX_CONFIG;
7157
- if (override && override.length > 0) return path16.resolve(override);
7601
+ if (override && override.length > 0) return path18.resolve(override);
7158
7602
  const home = process.env.HOME ?? process.env.USERPROFILE ?? os3.homedir();
7159
- return path16.join(home, ".codex", "config.toml");
7603
+ return path18.join(home, ".codex", "config.toml");
7160
7604
  }
7161
7605
  function agentsFilePath() {
7162
7606
  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");
7607
+ if (override && override.length > 0) return path18.resolve(override);
7608
+ return path18.join(process.cwd(), "AGENTS.md");
7165
7609
  }
7166
7610
  function isTableHeader(line) {
7167
7611
  return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
@@ -7295,7 +7739,7 @@ async function runCodex(opts) {
7295
7739
  const agentsPath = agentsFilePath();
7296
7740
  let configRaw = "";
7297
7741
  try {
7298
- configRaw = await fs15.readFile(configPath, "utf8");
7742
+ configRaw = await fs16.readFile(configPath, "utf8");
7299
7743
  } catch (err) {
7300
7744
  if (err.code !== "ENOENT") {
7301
7745
  console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
@@ -7304,7 +7748,7 @@ async function runCodex(opts) {
7304
7748
  }
7305
7749
  let agentsRaw = "";
7306
7750
  try {
7307
- agentsRaw = await fs15.readFile(agentsPath, "utf8");
7751
+ agentsRaw = await fs16.readFile(agentsPath, "utf8");
7308
7752
  } catch (err) {
7309
7753
  if (err.code !== "ENOENT") {
7310
7754
  console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
@@ -7344,15 +7788,15 @@ async function runCodex(opts) {
7344
7788
  return { exitCode: 0 };
7345
7789
  }
7346
7790
  if (config.changed) {
7347
- await fs15.mkdir(path16.dirname(configPath), { recursive: true });
7348
- await fs15.writeFile(configPath, config.text, "utf8");
7791
+ await fs16.mkdir(path18.dirname(configPath), { recursive: true });
7792
+ await fs16.writeFile(configPath, config.text, "utf8");
7349
7793
  console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
7350
7794
  } else {
7351
7795
  console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
7352
7796
  }
7353
7797
  if (agents.changed) {
7354
- await fs15.mkdir(path16.dirname(agentsPath), { recursive: true });
7355
- await fs15.writeFile(agentsPath, agents.text, "utf8");
7798
+ await fs16.mkdir(path18.dirname(agentsPath), { recursive: true });
7799
+ await fs16.writeFile(agentsPath, agents.text, "utf8");
7356
7800
  console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
7357
7801
  } else {
7358
7802
  console.log(`neat codex: ${agentsPath} already has the graph-first block`);
@@ -7408,9 +7852,9 @@ async function runCodexCommand(args) {
7408
7852
  }
7409
7853
 
7410
7854
  // src/editors-cli.ts
7411
- import path17 from "path";
7855
+ import path19 from "path";
7412
7856
  import os4 from "os";
7413
- import { promises as fs16 } from "fs";
7857
+ import { promises as fs17 } from "fs";
7414
7858
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
7415
7859
  import * as jsonc from "jsonc-parser";
7416
7860
  var NEAT_MCP_SERVER = {
@@ -7434,17 +7878,17 @@ function homeDir() {
7434
7878
  }
7435
7879
  function xdgConfigDir() {
7436
7880
  const xdg = process.env.XDG_CONFIG_HOME;
7437
- return xdg && xdg.length > 0 ? path17.resolve(xdg) : path17.join(homeDir(), ".config");
7881
+ return xdg && xdg.length > 0 ? path19.resolve(xdg) : path19.join(homeDir(), ".config");
7438
7882
  }
7439
7883
  function envOverride(name) {
7440
7884
  const v = process.env[name];
7441
- return v && v.length > 0 ? path17.resolve(v) : void 0;
7885
+ return v && v.length > 0 ? path19.resolve(v) : void 0;
7442
7886
  }
7443
7887
  var CURSOR_CLIENT = {
7444
7888
  id: "cursor",
7445
7889
  label: "Cursor",
7446
7890
  docsUrl: "https://docs.cursor.com/context/mcp",
7447
- mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path17.join(homeDir(), ".cursor", "mcp.json"),
7891
+ mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path19.join(homeDir(), ".cursor", "mcp.json"),
7448
7892
  mcpContainerKey: "mcpServers",
7449
7893
  format: "json",
7450
7894
  // Cursor still reads a single `.cursorrules` at the project root (the modern
@@ -7456,7 +7900,7 @@ var DEVIN_CLIENT = {
7456
7900
  id: "devin",
7457
7901
  label: "Devin Desktop (Cascade)",
7458
7902
  docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
7459
- mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path17.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
7903
+ mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path19.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
7460
7904
  mcpContainerKey: "mcpServers",
7461
7905
  format: "json",
7462
7906
  rulesFileName: ".windsurfrules"
@@ -7465,7 +7909,7 @@ var GEMINI_CLIENT = {
7465
7909
  id: "gemini",
7466
7910
  label: "Gemini CLI",
7467
7911
  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"),
7912
+ mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path19.join(homeDir(), ".gemini", "settings.json"),
7469
7913
  mcpContainerKey: "mcpServers",
7470
7914
  format: "json",
7471
7915
  rulesFileName: "GEMINI.md"
@@ -7474,7 +7918,7 @@ var QWEN_CLIENT = {
7474
7918
  id: "qwen",
7475
7919
  label: "Qwen Code",
7476
7920
  docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
7477
- mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path17.join(homeDir(), ".qwen", "settings.json"),
7921
+ mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path19.join(homeDir(), ".qwen", "settings.json"),
7478
7922
  mcpContainerKey: "mcpServers",
7479
7923
  format: "json",
7480
7924
  rulesFileName: "QWEN.md"
@@ -7483,7 +7927,7 @@ var AMAZONQ_CLIENT = {
7483
7927
  id: "amazonq",
7484
7928
  label: "Amazon Q Developer CLI",
7485
7929
  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"),
7930
+ mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path19.join(homeDir(), ".aws", "amazonq", "mcp.json"),
7487
7931
  mcpContainerKey: "mcpServers",
7488
7932
  format: "json"
7489
7933
  };
@@ -7491,7 +7935,7 @@ var ROOCODE_CLIENT = {
7491
7935
  id: "roocode",
7492
7936
  label: "Roo Code",
7493
7937
  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"),
7938
+ mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path19.join(process.cwd(), ".roo", "mcp.json"),
7495
7939
  mcpContainerKey: "mcpServers",
7496
7940
  format: "json"
7497
7941
  };
@@ -7504,9 +7948,9 @@ var ZED_CLIENT = {
7504
7948
  if (override) return override;
7505
7949
  if (process.platform === "win32") {
7506
7950
  const appData = process.env.APPDATA;
7507
- if (appData && appData.length > 0) return path17.join(appData, "Zed", "settings.json");
7951
+ if (appData && appData.length > 0) return path19.join(appData, "Zed", "settings.json");
7508
7952
  }
7509
- return path17.join(homeDir(), ".config", "zed", "settings.json");
7953
+ return path19.join(homeDir(), ".config", "zed", "settings.json");
7510
7954
  },
7511
7955
  mcpContainerKey: "context_servers",
7512
7956
  format: "jsonc",
@@ -7516,7 +7960,7 @@ var OPENCODE_CLIENT = {
7516
7960
  id: "opencode",
7517
7961
  label: "OpenCode",
7518
7962
  docsUrl: "https://opencode.ai/docs/mcp-servers/",
7519
- mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path17.join(xdgConfigDir(), "opencode", "opencode.json"),
7963
+ mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path19.join(xdgConfigDir(), "opencode", "opencode.json"),
7520
7964
  mcpContainerKey: "mcp",
7521
7965
  format: "json",
7522
7966
  serverEntry: NEAT_OPENCODE_SERVER,
@@ -7526,7 +7970,7 @@ var CRUSH_CLIENT = {
7526
7970
  id: "crush",
7527
7971
  label: "Crush",
7528
7972
  docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
7529
- mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path17.join(xdgConfigDir(), "crush", "crush.json"),
7973
+ mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path19.join(xdgConfigDir(), "crush", "crush.json"),
7530
7974
  mcpContainerKey: "mcp",
7531
7975
  format: "json",
7532
7976
  serverEntry: NEAT_CRUSH_SERVER,
@@ -7589,7 +8033,7 @@ async function planMcp(client, mcpPath) {
7589
8033
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
7590
8034
  let raw = "";
7591
8035
  try {
7592
- raw = await fs16.readFile(mcpPath, "utf8");
8036
+ raw = await fs17.readFile(mcpPath, "utf8");
7593
8037
  } catch (err) {
7594
8038
  const e = err;
7595
8039
  if (e.code === "ENOENT") {
@@ -7631,7 +8075,7 @@ async function runEditorInstall(client, opts) {
7631
8075
  const mcpPath = client.mcpConfigPath();
7632
8076
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
7633
8077
  const hasRules = typeof client.rulesFileName === "string";
7634
- const rulesPath = hasRules ? path17.join(opts.projectDir, client.rulesFileName) : "";
8078
+ const rulesPath = hasRules ? path19.join(opts.projectDir, client.rulesFileName) : "";
7635
8079
  const mcp = await planMcp(client, mcpPath);
7636
8080
  if (mcp === null) return { exitCode: 1 };
7637
8081
  let existingRules = "";
@@ -7640,7 +8084,7 @@ async function runEditorInstall(client, opts) {
7640
8084
  let block = "";
7641
8085
  if (hasRules) {
7642
8086
  try {
7643
- existingRules = await fs16.readFile(rulesPath, "utf8");
8087
+ existingRules = await fs17.readFile(rulesPath, "utf8");
7644
8088
  } catch (err) {
7645
8089
  if (err.code !== "ENOENT") {
7646
8090
  console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
@@ -7674,11 +8118,11 @@ async function runEditorInstall(client, opts) {
7674
8118
  );
7675
8119
  return { exitCode: 0 };
7676
8120
  }
7677
- await fs16.mkdir(path17.dirname(mcpPath), { recursive: true });
7678
- await fs16.writeFile(mcpPath, mcp.text, "utf8");
8121
+ await fs17.mkdir(path19.dirname(mcpPath), { recursive: true });
8122
+ await fs17.writeFile(mcpPath, mcp.text, "utf8");
7679
8123
  if (hasRules) {
7680
- await fs16.mkdir(path17.dirname(rulesPath), { recursive: true });
7681
- await fs16.writeFile(rulesPath, newRules, "utf8");
8124
+ await fs17.mkdir(path19.dirname(rulesPath), { recursive: true });
8125
+ await fs17.writeFile(rulesPath, newRules, "utf8");
7682
8126
  }
7683
8127
  console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
7684
8128
  console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
@@ -8178,7 +8622,7 @@ function sleep(ms, signal) {
8178
8622
  }
8179
8623
 
8180
8624
  // src/cli-verbs.ts
8181
- import path18 from "path";
8625
+ import path20 from "path";
8182
8626
  async function resolveProjectEntry(opts) {
8183
8627
  const entries = await listProjects();
8184
8628
  if (opts.project) {
@@ -8188,7 +8632,7 @@ async function resolveProjectEntry(opts) {
8188
8632
  const cwd = opts.cwd ?? process.cwd();
8189
8633
  const resolvedCwd = await normalizeProjectPath(cwd);
8190
8634
  for (const entry2 of entries) {
8191
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path18.sep}`)) {
8635
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path20.sep}`)) {
8192
8636
  return entry2;
8193
8637
  }
8194
8638
  }
@@ -8507,6 +8951,8 @@ function usage5() {
8507
8951
  console.log(" test <id> re-check an existing connector's credential");
8508
8952
  console.log(" Credentials default to an env-var reference ($VAR) resolved at");
8509
8953
  console.log(" run time; the config file is written owner-only (0600).");
8954
+ console.log(" up Start this project's daemon, or recover it if it died. Does");
8955
+ console.log(" nothing when it is already running. Flags: --project, --json.");
8510
8956
  console.log(" doctor Preflight this directory's setup \u2014 Node version, project,");
8511
8957
  console.log(" and daemon reachability \u2014 and print a fix for anything down.");
8512
8958
  console.log(" Flags: --json. Exits 0 when all pass, 1 when a check fails.");
@@ -8765,7 +9211,7 @@ async function buildPatchSections(services, project) {
8765
9211
  }
8766
9212
  async function runInit(opts) {
8767
9213
  const written = [];
8768
- const stat = await fs17.stat(opts.scanPath).catch(() => null);
9214
+ const stat = await fs18.stat(opts.scanPath).catch(() => null);
8769
9215
  if (!stat || !stat.isDirectory()) {
8770
9216
  console.error(`neat init: ${opts.scanPath} is not a directory`);
8771
9217
  return { exitCode: 2, writtenFiles: written };
@@ -8774,13 +9220,13 @@ async function runInit(opts) {
8774
9220
  printDiscoveryReport(opts, services);
8775
9221
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
8776
9222
  const patch = renderPatch(sections);
8777
- const patchPath = path19.join(opts.scanPath, "neat.patch");
9223
+ const patchPath = path21.join(opts.scanPath, "neat.patch");
8778
9224
  if (opts.dryRun) {
8779
- await fs17.writeFile(patchPath, patch, "utf8");
9225
+ await fs18.writeFile(patchPath, patch, "utf8");
8780
9226
  written.push(patchPath);
8781
9227
  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);
9228
+ const gitignorePath = path21.join(opts.scanPath, ".gitignore");
9229
+ const gitignoreExists = await fs18.stat(gitignorePath).then(() => true).catch(() => false);
8784
9230
  const verb = gitignoreExists ? "append" : "create";
8785
9231
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
8786
9232
  console.log("rerun without --dry-run to register and snapshot.");
@@ -8791,9 +9237,9 @@ async function runInit(opts) {
8791
9237
  const graph = getGraph(graphKey);
8792
9238
  const projectPaths = pathsForProject(
8793
9239
  graphKey,
8794
- path19.join(opts.scanPath, "neat-out")
9240
+ path21.join(opts.scanPath, "neat-out")
8795
9241
  );
8796
- const errorsPath = path19.join(path19.dirname(opts.outPath), path19.basename(projectPaths.errorsPath));
9242
+ const errorsPath = path21.join(path21.dirname(opts.outPath), path21.basename(projectPaths.errorsPath));
8797
9243
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
8798
9244
  await saveGraphToDisk(graph, opts.outPath);
8799
9245
  written.push(opts.outPath);
@@ -8802,15 +9248,13 @@ async function runInit(opts) {
8802
9248
  written.push(gitignoreResult.file);
8803
9249
  }
8804
9250
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
8805
- let currentProjectName = opts.project;
8806
9251
  try {
8807
- const entry2 = await addProject({
9252
+ await addProject({
8808
9253
  name: opts.project,
8809
9254
  path: opts.scanPath,
8810
9255
  languages,
8811
9256
  status: "active"
8812
9257
  });
8813
- currentProjectName = entry2.name;
8814
9258
  } catch (err) {
8815
9259
  if (err instanceof ProjectNameCollisionError) {
8816
9260
  console.error(`neat init: ${err.message}`);
@@ -8819,20 +9263,6 @@ async function runInit(opts) {
8819
9263
  }
8820
9264
  throw err;
8821
9265
  }
8822
- const siblings = await listProjects();
8823
- const paused = [];
8824
- for (const p of siblings) {
8825
- if (p.name !== currentProjectName && p.status === "active") {
8826
- await setStatus(p.name, "paused");
8827
- paused.push(p.name);
8828
- }
8829
- }
8830
- if (paused.length > 0) {
8831
- const plural = paused.length === 1 ? "" : "s";
8832
- console.log(
8833
- `neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
8834
- );
8835
- }
8836
9266
  if (!opts.noInstall) {
8837
9267
  if (opts.apply) {
8838
9268
  let instrumented = 0;
@@ -8872,7 +9302,7 @@ async function runInit(opts) {
8872
9302
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
8873
9303
  }
8874
9304
  } else {
8875
- await fs17.writeFile(patchPath, patch, "utf8");
9305
+ await fs18.writeFile(patchPath, patch, "utf8");
8876
9306
  written.push(patchPath);
8877
9307
  }
8878
9308
  }
@@ -8893,6 +9323,8 @@ async function runInit(opts) {
8893
9323
  if (result.extractionErrors > 0) {
8894
9324
  console.log(`errors: ${errorsPath}`);
8895
9325
  }
9326
+ const skippedBanner = formatSkippedBanner(result.skippedFiles);
9327
+ if (skippedBanner) console.log(skippedBanner);
8896
9328
  console.log(formatPrecisionFloorBanner(result.extractedDropped));
8897
9329
  if (result.extractionErrors > 0 && isStrictExtractionEnabled()) {
8898
9330
  return { exitCode: 4, writtenFiles: written };
@@ -8913,9 +9345,9 @@ var CLAUDE_SKILL_CONFIG = {
8913
9345
  };
8914
9346
  function claudeConfigPath() {
8915
9347
  const override = process.env.NEAT_CLAUDE_CONFIG;
8916
- if (override && override.length > 0) return path19.resolve(override);
9348
+ if (override && override.length > 0) return path21.resolve(override);
8917
9349
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
8918
- return path19.join(home, ".claude.json");
9350
+ return path21.join(home, ".claude.json");
8919
9351
  }
8920
9352
  async function runSkill(opts) {
8921
9353
  const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -8927,7 +9359,7 @@ async function runSkill(opts) {
8927
9359
  const target = claudeConfigPath();
8928
9360
  let existing = {};
8929
9361
  try {
8930
- existing = JSON.parse(await fs17.readFile(target, "utf8"));
9362
+ existing = JSON.parse(await fs18.readFile(target, "utf8"));
8931
9363
  } catch (err) {
8932
9364
  if (err.code !== "ENOENT") {
8933
9365
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -8939,8 +9371,8 @@ async function runSkill(opts) {
8939
9371
  ...existing,
8940
9372
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
8941
9373
  };
8942
- await fs17.mkdir(path19.dirname(target), { recursive: true });
8943
- await fs17.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
9374
+ await fs18.mkdir(path21.dirname(target), { recursive: true });
9375
+ await fs18.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
8944
9376
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
8945
9377
  console.log("restart Claude Code to pick up the new MCP server.");
8946
9378
  console.log("");
@@ -8989,6 +9421,21 @@ async function main() {
8989
9421
  if (code !== 0) process.exit(code);
8990
9422
  return;
8991
9423
  }
9424
+ if (cmd0 === "up") {
9425
+ const code = await runUpCommand(argv.slice(1), {
9426
+ resolveTarget: async (opts) => {
9427
+ const target = await resolveClientTarget(opts);
9428
+ const named = opts.profile ?? process.env.NEAT_PROFILE;
9429
+ return {
9430
+ endpoint: target.endpoint,
9431
+ local: isLoopbackEndpoint(target.endpoint),
9432
+ via: target.source === "profile" ? `the \`${named}\` profile` : target.source === "active" ? "your active profile" : "your environment"
9433
+ };
9434
+ }
9435
+ });
9436
+ if (code !== 0) process.exit(code);
9437
+ return;
9438
+ }
8992
9439
  if (cmd0 === "login") {
8993
9440
  const code = await runLoginCommand(argv.slice(1));
8994
9441
  if (code !== 0) process.exit(code);
@@ -9061,12 +9508,12 @@ async function main() {
9061
9508
  console.error("neat init: --apply and --dry-run are mutually exclusive");
9062
9509
  process.exit(2);
9063
9510
  }
9064
- const scanPath = path19.resolve(target);
9511
+ const scanPath = path21.resolve(target);
9065
9512
  const projectExplicit = parsed.project !== null;
9066
- const projectName = projectExplicit ? project : path19.basename(scanPath);
9513
+ const projectName = projectExplicit ? project : path21.basename(scanPath);
9067
9514
  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);
9515
+ const fallback = pathsForProject(projectKey, path21.join(scanPath, "neat-out")).snapshotPath;
9516
+ const outPath = path21.resolve(process.env.NEAT_OUT_PATH ?? fallback);
9070
9517
  const result = await runInit({
9071
9518
  scanPath,
9072
9519
  outPath,
@@ -9087,21 +9534,21 @@ async function main() {
9087
9534
  usage5();
9088
9535
  process.exit(2);
9089
9536
  }
9090
- const scanPath = path19.resolve(target);
9091
- const stat = await fs17.stat(scanPath).catch(() => null);
9537
+ const scanPath = path21.resolve(target);
9538
+ const stat = await fs18.stat(scanPath).catch(() => null);
9092
9539
  if (!stat || !stat.isDirectory()) {
9093
9540
  console.error(`neat watch: ${scanPath} is not a directory`);
9094
9541
  process.exit(2);
9095
9542
  }
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))
9543
+ const projectPaths = pathsForProject(project, path21.join(scanPath, "neat-out"));
9544
+ const outPath = path21.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
9545
+ const errorsPath = path21.resolve(
9546
+ process.env.NEAT_ERRORS_PATH ?? path21.join(path21.dirname(outPath), path21.basename(projectPaths.errorsPath))
9100
9547
  );
9101
- const staleEventsPath = path19.resolve(
9102
- process.env.NEAT_STALE_EVENTS_PATH ?? path19.join(path19.dirname(outPath), path19.basename(projectPaths.staleEventsPath))
9548
+ const staleEventsPath = path21.resolve(
9549
+ process.env.NEAT_STALE_EVENTS_PATH ?? path21.join(path21.dirname(outPath), path21.basename(projectPaths.staleEventsPath))
9103
9550
  );
9104
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path19.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
9551
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path21.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
9105
9552
  const handle = await startWatch(getGraph(project), {
9106
9553
  scanPath,
9107
9554
  outPath,
@@ -9110,7 +9557,7 @@ async function main() {
9110
9557
  project,
9111
9558
  // Resolve NEAT_HOME so a `neat watch` picks up connectors added to
9112
9559
  // ~/.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"),
9560
+ neatHome: process.env.NEAT_HOME ? path21.resolve(process.env.NEAT_HOME) : path21.join(os5.homedir(), ".neat"),
9114
9561
  ...embeddingsCachePath ? { embeddingsCachePath } : {},
9115
9562
  host: process.env.HOST ?? "0.0.0.0",
9116
9563
  port: Number(process.env.PORT ?? 8080),
@@ -9291,12 +9738,12 @@ async function main() {
9291
9738
  usage5();
9292
9739
  process.exit(1);
9293
9740
  }
9294
- async function tryOrchestrator(cmd, parsed) {
9295
- const scanPath = path19.resolve(cmd);
9296
- const stat = await fs17.stat(scanPath).catch(() => null);
9741
+ async function tryOrchestrator(cmd, parsed, overrides = {}) {
9742
+ const scanPath = path21.resolve(cmd);
9743
+ const stat = await fs18.stat(scanPath).catch(() => null);
9297
9744
  if (!stat || !stat.isDirectory()) return null;
9298
9745
  const projectExplicit = parsed.project !== null;
9299
- const projectName = projectExplicit ? parsed.project : path19.basename(scanPath);
9746
+ const projectName = projectExplicit ? parsed.project : path21.basename(scanPath);
9300
9747
  const result = await runOrchestrator({
9301
9748
  scanPath,
9302
9749
  project: projectName,
@@ -9304,14 +9751,31 @@ async function tryOrchestrator(cmd, parsed) {
9304
9751
  noInstrument: parsed.noInstrument,
9305
9752
  open: parsed.open,
9306
9753
  noOpen: parsed.noOpen,
9307
- yes: parsed.yes
9754
+ yes: parsed.yes,
9755
+ ...overrides.skipBanner ? { skipBanner: true } : {}
9308
9756
  });
9309
9757
  return result.exitCode;
9310
9758
  }
9311
9759
  async function runWelcomeFlow(parsed) {
9312
9760
  return runWelcome({
9313
9761
  login: (loginArgv) => runLoginCommand(loginArgv),
9314
- orchestrator: async (cwd) => await tryOrchestrator(cwd, parsed) ?? 0
9762
+ // What the person chose at the door reaches the orchestrator as the flags they
9763
+ // stand for: the name as `--project`, the instrument answer as `--no-instrument`
9764
+ // or as `yes` (which keeps the orchestrator's own prompt quiet, since the door
9765
+ // already asked). `headerShown` isn't a flag anyone can type — it says the
9766
+ // door's wordmark already opened this run, so the orchestrator's banner is skipped.
9767
+ orchestrator: async (cwd, opts) => await tryOrchestrator(
9768
+ cwd,
9769
+ {
9770
+ ...parsed,
9771
+ ...opts?.project !== void 0 ? { project: opts.project } : {},
9772
+ ...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
9773
+ ...opts?.yes !== void 0 ? { yes: opts.yes } : {}
9774
+ },
9775
+ { skipBanner: opts?.headerShown === true }
9776
+ ) ?? 0,
9777
+ // An explicit flag is an answer; don't ask again.
9778
+ instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
9315
9779
  });
9316
9780
  }
9317
9781
  var QUERY_VERBS = /* @__PURE__ */ new Set([
@@ -9344,9 +9808,17 @@ var ProjectResolutionError = class extends Error {
9344
9808
  }
9345
9809
  exitCode;
9346
9810
  };
9347
- async function resolveProjectForVerb(client, parsed) {
9811
+ function isLoopbackEndpoint(endpoint) {
9812
+ try {
9813
+ return isLoopbackHost(new URL(endpoint).hostname.replace(/^\[|\]$/g, ""));
9814
+ } catch {
9815
+ return false;
9816
+ }
9817
+ }
9818
+ async function resolveProjectForVerb(client, parsed, cwdProject) {
9348
9819
  const explicit = resolveProjectFlag(parsed);
9349
9820
  if (explicit) return explicit;
9821
+ if (cwdProject) return cwdProject;
9350
9822
  const projects = await client.get("/projects");
9351
9823
  if (projects.some((p) => p.name === DEFAULT_PROJECT)) {
9352
9824
  return void 0;
@@ -9393,14 +9865,28 @@ async function resolveClientTarget(opts = {}) {
9393
9865
  }
9394
9866
  return { endpoint: "http://localhost:8080", source: "default" };
9395
9867
  }
9868
+ async function projectForCwd(cwd = process.cwd()) {
9869
+ try {
9870
+ return (await findProjectByPath(cwd))?.name;
9871
+ } catch {
9872
+ return void 0;
9873
+ }
9874
+ }
9875
+ function cwdProjectApplies(source) {
9876
+ return source === "daemon-record" || source === "default";
9877
+ }
9396
9878
  async function resolveDaemonUrl(project, profile) {
9397
9879
  return (await resolveClientTarget({ project, profile })).endpoint;
9398
9880
  }
9399
9881
  async function runQueryVerb(cmd, parsed) {
9400
9882
  const requestedProject = resolveProjectFlag(parsed);
9883
+ const cwdProject = requestedProject ? void 0 : await projectForCwd();
9401
9884
  let target;
9402
9885
  try {
9403
- target = await resolveClientTarget({ project: requestedProject, profile: parsed.profile ?? void 0 });
9886
+ target = await resolveClientTarget({
9887
+ project: requestedProject ?? cwdProject,
9888
+ profile: parsed.profile ?? void 0
9889
+ });
9404
9890
  } catch (err) {
9405
9891
  if (err instanceof UnknownProfileError) {
9406
9892
  process.stderr.write(`${err.message}
@@ -9559,7 +10045,11 @@ async function runQueryVerb(cmd, parsed) {
9559
10045
  return 2;
9560
10046
  }
9561
10047
  try {
9562
- const project = await resolveProjectForVerb(client, parsed);
10048
+ const project = await resolveProjectForVerb(
10049
+ client,
10050
+ parsed,
10051
+ cwdProjectApplies(target.source) ? cwdProject : void 0
10052
+ );
9563
10053
  const result = await makeWork(project);
9564
10054
  if (parsed.json) process.stdout.write(formatJson(result) + "\n");
9565
10055
  else process.stdout.write(formatHuman(result) + "\n");
@@ -9574,6 +10064,10 @@ async function runQueryVerb(cmd, parsed) {
9574
10064
  console.error(`neat ${cmd}: ${detail.trim()}`);
9575
10065
  } else if (err instanceof TransportError) {
9576
10066
  console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${target.endpoint})`);
10067
+ if (isLoopbackEndpoint(target.endpoint)) {
10068
+ const upCommand = requestedProject ? `neat up --project ${requestedProject}` : "neat up";
10069
+ console.error(`neat ${cmd}: run \`${upCommand}\` to start it.`);
10070
+ }
9577
10071
  } else {
9578
10072
  console.error(`neat ${cmd}: ${err.message}`);
9579
10073
  }
@@ -9582,9 +10076,13 @@ async function runQueryVerb(cmd, parsed) {
9582
10076
  }
9583
10077
  async function runMonitorVerb(parsed) {
9584
10078
  const requestedProject = resolveProjectFlag(parsed);
10079
+ const cwdProject = requestedProject ? void 0 : await projectForCwd();
9585
10080
  let target;
9586
10081
  try {
9587
- target = await resolveClientTarget({ project: requestedProject, profile: parsed.profile ?? void 0 });
10082
+ target = await resolveClientTarget({
10083
+ project: requestedProject ?? cwdProject,
10084
+ profile: parsed.profile ?? void 0
10085
+ });
9588
10086
  } catch (err) {
9589
10087
  if (err instanceof UnknownProfileError) return 0;
9590
10088
  throw err;
@@ -9592,7 +10090,11 @@ async function runMonitorVerb(parsed) {
9592
10090
  const client = createHttpClient(target.endpoint, target.authToken);
9593
10091
  let project;
9594
10092
  try {
9595
- project = await resolveProjectForVerb(client, parsed);
10093
+ project = await resolveProjectForVerb(
10094
+ client,
10095
+ parsed,
10096
+ cwdProjectApplies(target.source) ? cwdProject : void 0
10097
+ );
9596
10098
  } catch (err) {
9597
10099
  if (err instanceof ProjectResolutionError) {
9598
10100
  console.error(`neat monitor: ${err.message}`);
@@ -9616,11 +10118,18 @@ async function runMonitorVerb(parsed) {
9616
10118
  process.off("SIGTERM", onSignal);
9617
10119
  }
9618
10120
  }
10121
+ function reportFatal(err, log = console.error) {
10122
+ if (err instanceof RegistryError) {
10123
+ log(`neat: ${err.message}`);
10124
+ return 1;
10125
+ }
10126
+ log(err);
10127
+ return 1;
10128
+ }
9619
10129
  var entry = process.argv[1] ?? "";
9620
10130
  if (/[\\/](?:cli\.(?:cjs|js)|cli|neat|neat\.is)$/.test(entry)) {
9621
10131
  main().catch((err) => {
9622
- console.error(err);
9623
- process.exit(1);
10132
+ process.exit(reportFatal(err));
9624
10133
  });
9625
10134
  }
9626
10135
  export {
@@ -9629,11 +10138,15 @@ export {
9629
10138
  QUERY_VERBS,
9630
10139
  UnknownProfileError,
9631
10140
  commandPrefix,
10141
+ cwdProjectApplies,
10142
+ isLoopbackEndpoint,
9632
10143
  isNpxInvocation,
9633
10144
  main,
9634
10145
  parseArgs,
9635
10146
  printBanner,
10147
+ projectForCwd,
9636
10148
  readPackageVersion,
10149
+ reportFatal,
9637
10150
  resolveClientTarget,
9638
10151
  resolveDaemonUrl,
9639
10152
  resolveProjectForVerb,