@neat.is/core 0.10.1 → 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-AWIK6T3G.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,7 +82,7 @@ import {
78
82
  startStalenessLoop,
79
83
  upsertConnectorEntry,
80
84
  validateConnectorEntry
81
- } from "./chunk-E3YFFIHE.js";
85
+ } from "./chunk-TXKOAART.js";
82
86
  import {
83
87
  startOtelGrpcReceiver
84
88
  } from "./chunk-5SJHA5ZH.js";
@@ -93,7 +97,7 @@ import {
93
97
  } from "./chunk-M7CGATVC.js";
94
98
 
95
99
  // src/cli.ts
96
- import path20 from "path";
100
+ import path21 from "path";
97
101
  import os5 from "os";
98
102
  import { promises as fs18 } from "fs";
99
103
 
@@ -3937,6 +3941,20 @@ import path10 from "path";
3937
3941
  import { fileURLToPath as fileURLToPath2 } from "url";
3938
3942
  import { spawn as spawn2 } from "child_process";
3939
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
+ };
3940
3958
  async function extractAndPersist(opts) {
3941
3959
  const services = await discoverServices(opts.scanPath);
3942
3960
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
@@ -3948,7 +3966,11 @@ async function extractAndPersist(opts) {
3948
3966
  errorsPath: projectPaths.errorsPath
3949
3967
  });
3950
3968
  if (!opts.dryRun) {
3951
- 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
+ }
3952
3974
  }
3953
3975
  return {
3954
3976
  graph,
@@ -4331,6 +4353,16 @@ async function healthIsForProject(restPort, project) {
4331
4353
  function daemonLogPath(projectPath3) {
4332
4354
  return path10.join(projectPath3, "neat-out", "daemon.log");
4333
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
+ }
4334
4366
  function spawnDaemonDetached(spec) {
4335
4367
  const here = path10.dirname(fileURLToPath2(import.meta.url));
4336
4368
  const candidates = [
@@ -4377,6 +4409,72 @@ function spawnDaemonDetached(spec) {
4377
4409
  if (logFd !== null) fsSync.closeSync(logFd);
4378
4410
  return child;
4379
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
+ }
4380
4478
  function openBrowser(url) {
4381
4479
  if (!process.stdout.isTTY) return "failed";
4382
4480
  const platform = process.platform;
@@ -4410,14 +4508,26 @@ async function runOrchestrator(opts) {
4410
4508
  result.exitCode = 2;
4411
4509
  return result;
4412
4510
  }
4413
- printBanner();
4511
+ if (!opts.skipBanner) printBanner();
4414
4512
  console.log(`neat: ${opts.scanPath}`);
4415
4513
  console.log("");
4416
- const persisted = await extractAndPersist({
4417
- scanPath: opts.scanPath,
4418
- project: opts.project,
4419
- projectExplicit: opts.projectExplicit
4420
- });
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
+ }
4421
4531
  const { graph, services, languages } = persisted;
4422
4532
  result.steps.discovery = { services: services.length, languages };
4423
4533
  console.log(`discovered ${services.length} service(s) across ${languages.length} language(s)`);
@@ -4458,20 +4568,6 @@ async function runOrchestrator(opts) {
4458
4568
  result.exitCode = 1;
4459
4569
  return result;
4460
4570
  }
4461
- const siblings = await listProjects();
4462
- const paused = [];
4463
- for (const p of siblings) {
4464
- if (p.name !== currentProjectName && p.status === "active") {
4465
- await setStatus(p.name, "paused");
4466
- paused.push(p.name);
4467
- }
4468
- }
4469
- if (paused.length > 0) {
4470
- const plural = paused.length === 1 ? "" : "s";
4471
- console.log(
4472
- `neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
4473
- );
4474
- }
4475
4571
  if (!runApply) {
4476
4572
  result.steps.apply.skipped = true;
4477
4573
  console.log("skipped instrumentation (--no-instrument)");
@@ -4487,75 +4583,45 @@ async function runOrchestrator(opts) {
4487
4583
  }
4488
4584
  }
4489
4585
  const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
4490
- const bindHost = resolveHost(
4491
- {},
4492
- typeof process.env.NEAT_AUTH_TOKEN === "string" && process.env.NEAT_AUTH_TOKEN.length > 0
4493
- );
4494
- const persistedPorts = await persistedPortsFor(opts.scanPath);
4495
- let allocated = null;
4496
- if (persistedPorts && await healthIsForProject(persistedPorts.rest, currentProjectName)) {
4497
- result.steps.daemon = "already-running";
4498
- allocated = persistedPorts;
4499
- } else {
4500
- if (persistedPorts && await isPortFree(persistedPorts.rest, bindHost) && await tripleFree(persistedPorts, bindHost)) {
4501
- allocated = persistedPorts;
4502
- } else {
4503
- allocated = await allocatePorts(bindHost);
4504
- }
4505
- 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":
4506
4605
  for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) {
4507
4606
  console.error(line);
4508
4607
  }
4509
4608
  result.exitCode = 3;
4510
4609
  return result;
4511
- }
4512
- const release = await acquireSpawnLock(opts.scanPath);
4513
- if (!release) {
4514
- const reused = await waitForPeerDaemon(allocated.rest, currentProjectName, timeoutMs);
4515
- if (reused) {
4516
- result.steps.daemon = "already-running";
4517
- } else {
4518
- console.error("neat: another `neat` is spawning this project but its daemon did not come up in time");
4519
- result.exitCode = 1;
4520
- return result;
4521
- }
4522
- } else {
4523
- try {
4524
- if (await healthIsForProject(allocated.rest, currentProjectName)) {
4525
- result.steps.daemon = "already-running";
4526
- } else {
4527
- spawnDaemonDetached({
4528
- project: currentProjectName,
4529
- projectPath: opts.scanPath,
4530
- ports: allocated
4531
- });
4532
- const ready = await waitForDaemonReady(allocated.rest, currentProjectName, timeoutMs);
4533
- result.steps.daemon = ready.ready ? "spawned" : "timed-out";
4534
- if (!ready.ready) {
4535
- console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
4536
- if (ready.stillBootstrapping.length > 0) {
4537
- console.error(`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`);
4538
- }
4539
- if (ready.brokenProjects.length > 0) {
4540
- console.error(`neat: broken projects: ${ready.brokenProjects.join(", ")}`);
4541
- }
4542
- result.exitCode = 1;
4543
- return result;
4544
- }
4545
- if (ready.brokenProjects.length > 0) {
4546
- console.warn(
4547
- `neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
4548
- );
4549
- }
4550
- }
4551
- } catch (err) {
4552
- console.error(`neat: daemon spawn failed \u2014 ${err.message}`);
4553
- result.exitCode = 1;
4554
- return result;
4555
- } finally {
4556
- 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);
4557
4614
  }
4558
- }
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;
4559
4625
  }
4560
4626
  const webPort = allocated?.web ?? NEAT_PORTS[2];
4561
4627
  const dashboardUrl = opts.dashboardUrl ?? `http://localhost:${webPort}`;
@@ -5335,9 +5401,9 @@ function parseGitHubRemote(url) {
5335
5401
  if (!owner || !name) return null;
5336
5402
  return { owner, name };
5337
5403
  }
5338
- async function cpGetJson(fetchImpl, cpUrl, path21, accessToken) {
5404
+ async function cpGetJson(fetchImpl, cpUrl, path22, accessToken) {
5339
5405
  try {
5340
- const res = await fetchImpl(`${cpUrl}${path21}`, {
5406
+ const res = await fetchImpl(`${cpUrl}${path22}`, {
5341
5407
  headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
5342
5408
  signal: AbortSignal.timeout(CP_TIMEOUT_MS)
5343
5409
  });
@@ -5815,10 +5881,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
5815
5881
  const root = baseUrl.replace(/\/$/, "");
5816
5882
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
5817
5883
  return {
5818
- async get(path21) {
5884
+ async get(path22) {
5819
5885
  let res;
5820
5886
  try {
5821
- res = await fetchImpl(`${root}${path21}`, {
5887
+ res = await fetchImpl(`${root}${path22}`, {
5822
5888
  headers: { ...authHeader }
5823
5889
  });
5824
5890
  } catch (err) {
@@ -5830,16 +5896,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
5830
5896
  const body = await res.text().catch(() => "");
5831
5897
  throw new HttpError(
5832
5898
  res.status,
5833
- `${res.status} ${res.statusText} on GET ${path21}: ${body}`,
5899
+ `${res.status} ${res.statusText} on GET ${path22}: ${body}`,
5834
5900
  body
5835
5901
  );
5836
5902
  }
5837
5903
  return await res.json();
5838
5904
  },
5839
- async post(path21, body) {
5905
+ async post(path22, body) {
5840
5906
  let res;
5841
5907
  try {
5842
- res = await fetchImpl(`${root}${path21}`, {
5908
+ res = await fetchImpl(`${root}${path22}`, {
5843
5909
  method: "POST",
5844
5910
  headers: { "content-type": "application/json", ...authHeader },
5845
5911
  body: JSON.stringify(body)
@@ -5853,7 +5919,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
5853
5919
  const text = await res.text().catch(() => "");
5854
5920
  throw new HttpError(
5855
5921
  res.status,
5856
- `${res.status} ${res.statusText} on POST ${path21}: ${text}`,
5922
+ `${res.status} ${res.statusText} on POST ${path22}: ${text}`,
5857
5923
  text
5858
5924
  );
5859
5925
  }
@@ -5867,12 +5933,12 @@ function projectPath(project, suffix) {
5867
5933
  }
5868
5934
  async function runRootCause(client, input) {
5869
5935
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
5870
- const path21 = projectPath(
5936
+ const path22 = projectPath(
5871
5937
  input.project,
5872
5938
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
5873
5939
  );
5874
5940
  try {
5875
- const result = await client.get(path21);
5941
+ const result = await client.get(path22);
5876
5942
  const arrowPath = result.traversalPath.join(" \u2190 ");
5877
5943
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
5878
5944
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -5898,12 +5964,12 @@ async function runRootCause(client, input) {
5898
5964
  }
5899
5965
  async function runBlastRadius(client, input) {
5900
5966
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
5901
- const path21 = projectPath(
5967
+ const path22 = projectPath(
5902
5968
  input.project,
5903
5969
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
5904
5970
  );
5905
5971
  try {
5906
- const result = await client.get(path21);
5972
+ const result = await client.get(path22);
5907
5973
  if (result.totalAffected === 0) {
5908
5974
  return {
5909
5975
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -5937,12 +6003,12 @@ function formatBlastEntry(n) {
5937
6003
  }
5938
6004
  async function runDependencies(client, input) {
5939
6005
  const depth = input.depth ?? 3;
5940
- const path21 = projectPath(
6006
+ const path22 = projectPath(
5941
6007
  input.project,
5942
6008
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
5943
6009
  );
5944
6010
  try {
5945
- const result = await client.get(path21);
6011
+ const result = await client.get(path22);
5946
6012
  if (result.total === 0) {
5947
6013
  return {
5948
6014
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -6034,9 +6100,9 @@ function formatDuration(ms) {
6034
6100
  return `${Math.round(h / 24)}d`;
6035
6101
  }
6036
6102
  async function runIncidents(client, input) {
6037
- const path21 = 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");
6038
6104
  try {
6039
- const body = await client.get(path21);
6105
+ const body = await client.get(path22);
6040
6106
  const events = body.events;
6041
6107
  if (events.length === 0) {
6042
6108
  return {
@@ -6517,6 +6583,116 @@ async function runDoctorCommand(argv, deps = {}) {
6517
6583
  return checks.every((c) => c.ok) ? 0 : 1;
6518
6584
  }
6519
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
+
6520
6696
  // src/login-cli.ts
6521
6697
  import readline3 from "readline/promises";
6522
6698
  var DEFAULT_PROFILE_NAME = "hosted";
@@ -6778,7 +6954,7 @@ async function defaultReadSecret(prompt) {
6778
6954
 
6779
6955
  // src/welcome.ts
6780
6956
  import { promises as fs13 } from "fs";
6781
- import path14 from "path";
6957
+ import path15 from "path";
6782
6958
  import readline4 from "readline/promises";
6783
6959
  var WORDMARK = [
6784
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",
@@ -6966,10 +7142,11 @@ async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, in
6966
7142
  out("Building your local graph now\u2026");
6967
7143
  out("");
6968
7144
  const overrides = {
7145
+ headerShown: true,
6969
7146
  ...project !== void 0 ? { project } : {},
6970
7147
  ...declined === void 0 ? {} : declined ? { noInstrument: true } : { yes: true }
6971
7148
  };
6972
- return orchestrator(cwd, Object.keys(overrides).length > 0 ? overrides : void 0);
7149
+ return orchestrator(cwd, overrides);
6973
7150
  }
6974
7151
  async function askToInstrument(out, readLine) {
6975
7152
  out("Instrument the services for OpenTelemetry now?");
@@ -6985,8 +7162,8 @@ async function askToInstrument(out, readLine) {
6985
7162
  return declined;
6986
7163
  }
6987
7164
  async function resolveProjectName(out, readLine, cwd, readRegistry2) {
6988
- const here = path14.resolve(cwd);
6989
- const base = path14.basename(here);
7165
+ const here = path15.resolve(cwd);
7166
+ const base = path15.basename(here);
6990
7167
  let taken;
6991
7168
  try {
6992
7169
  const { projects } = await readRegistry2();
@@ -6995,7 +7172,7 @@ async function resolveProjectName(out, readLine, cwd, readRegistry2) {
6995
7172
  return void 0;
6996
7173
  }
6997
7174
  const clash = taken.get(base);
6998
- if (clash === void 0 || path14.resolve(clash) === here) return void 0;
7175
+ if (clash === void 0 || path15.resolve(clash) === here) return void 0;
6999
7176
  out("");
7000
7177
  out(`A project named \`${base}\` is already registered (${clash}).`);
7001
7178
  const suggested = firstFreeName(base, taken);
@@ -7008,7 +7185,7 @@ async function resolveProjectName(out, readLine, cwd, readRegistry2) {
7008
7185
  continue;
7009
7186
  }
7010
7187
  const other = taken.get(chosen);
7011
- if (other !== void 0 && path14.resolve(other) !== here) {
7188
+ if (other !== void 0 && path15.resolve(other) !== here) {
7012
7189
  out(`\`${chosen}\` is registered too (${other}).`);
7013
7190
  continue;
7014
7191
  }
@@ -7033,9 +7210,9 @@ async function shouldShowWelcome(deps = {}) {
7033
7210
  const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
7034
7211
  const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
7035
7212
  try {
7036
- const here = path14.resolve(cwd);
7213
+ const here = path15.resolve(cwd);
7037
7214
  const { projects } = await readRegistry2();
7038
- if (projects.some((p) => path14.resolve(p.path) === here)) return false;
7215
+ if (projects.some((p) => path15.resolve(p.path) === here)) return false;
7039
7216
  if (await hasSnapshot(here)) return false;
7040
7217
  return true;
7041
7218
  } catch {
@@ -7044,7 +7221,7 @@ async function shouldShowWelcome(deps = {}) {
7044
7221
  }
7045
7222
  async function defaultHasSnapshot(cwd) {
7046
7223
  try {
7047
- const entries = await fs13.readdir(path14.join(cwd, "neat-out"));
7224
+ const entries = await fs13.readdir(path15.join(cwd, "neat-out"));
7048
7225
  return entries.some((e) => e.endsWith(".json"));
7049
7226
  } catch {
7050
7227
  return false;
@@ -7061,7 +7238,7 @@ async function defaultReadLine2(prompt) {
7061
7238
  }
7062
7239
 
7063
7240
  // src/hooks-cli.ts
7064
- import path15 from "path";
7241
+ import path16 from "path";
7065
7242
  import os2 from "os";
7066
7243
  import { promises as fs14 } from "fs";
7067
7244
  import { fileURLToPath as fileURLToPath3 } from "url";
@@ -7070,14 +7247,14 @@ var GUIDE_FILENAME = "GRAPH_FIRST.md";
7070
7247
  var GUIDE_INSTALL_NAME = "neat-graph-first.md";
7071
7248
  var HOOK_MATCHER = "Grep|Glob|Bash";
7072
7249
  function moduleDir() {
7073
- return typeof __dirname !== "undefined" ? __dirname : path15.dirname(fileURLToPath3(import.meta.url));
7250
+ return typeof __dirname !== "undefined" ? __dirname : path16.dirname(fileURLToPath3(import.meta.url));
7074
7251
  }
7075
7252
  async function readSkillAsset(rel) {
7076
7253
  const here = moduleDir();
7077
7254
  const candidates = [
7078
- path15.resolve(here, "../../claude-skill", rel),
7079
- path15.resolve(here, "../../../claude-skill", rel),
7080
- path15.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)
7081
7258
  ];
7082
7259
  for (const candidate of candidates) {
7083
7260
  try {
@@ -7091,20 +7268,20 @@ async function readSkillAsset(rel) {
7091
7268
  }
7092
7269
  function neatHome2() {
7093
7270
  const override = process.env.NEAT_HOME;
7094
- if (override && override.length > 0) return path15.resolve(override);
7095
- return path15.join(os2.homedir(), ".neat");
7271
+ if (override && override.length > 0) return path16.resolve(override);
7272
+ return path16.join(os2.homedir(), ".neat");
7096
7273
  }
7097
7274
  function claudeSettingsPath() {
7098
7275
  const override = process.env.NEAT_CLAUDE_SETTINGS;
7099
- if (override && override.length > 0) return path15.resolve(override);
7276
+ if (override && override.length > 0) return path16.resolve(override);
7100
7277
  const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
7101
- return path15.join(home, ".claude", "settings.json");
7278
+ return path16.join(home, ".claude", "settings.json");
7102
7279
  }
7103
7280
  function installedHookPath() {
7104
- return path15.join(neatHome2(), "hooks", HOOK_FILENAME);
7281
+ return path16.join(neatHome2(), "hooks", HOOK_FILENAME);
7105
7282
  }
7106
7283
  function gateFlagPath() {
7107
- return path15.join(neatHome2(), "hooks", "gate-enabled");
7284
+ return path16.join(neatHome2(), "hooks", "gate-enabled");
7108
7285
  }
7109
7286
  function isNeatSearchEntry(entry2) {
7110
7287
  return (entry2.hooks ?? []).some(
@@ -7137,9 +7314,9 @@ async function runHooks(opts) {
7137
7314
  const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
7138
7315
  const guide = await readSkillAsset(GUIDE_FILENAME);
7139
7316
  const scriptPath = installedHookPath();
7140
- await fs14.mkdir(path15.dirname(scriptPath), { recursive: true });
7317
+ await fs14.mkdir(path16.dirname(scriptPath), { recursive: true });
7141
7318
  await fs14.writeFile(scriptPath, hookScript, { mode: 493 });
7142
- const guidePath = path15.join(neatHome2(), GUIDE_INSTALL_NAME);
7319
+ const guidePath = path16.join(neatHome2(), GUIDE_INSTALL_NAME);
7143
7320
  await fs14.writeFile(guidePath, guide, "utf8");
7144
7321
  const settingsFile = claudeSettingsPath();
7145
7322
  let settings = {};
@@ -7166,11 +7343,11 @@ async function runHooks(opts) {
7166
7343
  ...settings,
7167
7344
  hooks: { ...hooks, PreToolUse: preToolUse }
7168
7345
  };
7169
- await fs14.mkdir(path15.dirname(settingsFile), { recursive: true });
7346
+ await fs14.mkdir(path16.dirname(settingsFile), { recursive: true });
7170
7347
  await fs14.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
7171
7348
  const flag = gateFlagPath();
7172
7349
  if (opts.gate) {
7173
- await fs14.mkdir(path15.dirname(flag), { recursive: true });
7350
+ await fs14.mkdir(path16.dirname(flag), { recursive: true });
7174
7351
  await fs14.writeFile(flag, "1\n", "utf8");
7175
7352
  } else {
7176
7353
  await fs14.rm(flag, { force: true });
@@ -7262,7 +7439,7 @@ async function runHooksCommand(args) {
7262
7439
  }
7263
7440
 
7264
7441
  // src/claude-cli.ts
7265
- import path16 from "path";
7442
+ import path17 from "path";
7266
7443
  import { promises as fs15 } from "fs";
7267
7444
  var NEAT_SECTION_HEADING = "## neat";
7268
7445
  var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
@@ -7297,8 +7474,8 @@ ${NEAT_DIRECTIVE_BODY}
7297
7474
  }
7298
7475
  function claudeMdPath() {
7299
7476
  const override = process.env.NEAT_CLAUDE_MD;
7300
- if (override && override.length > 0) return path16.resolve(override);
7301
- return path16.join(process.cwd(), "CLAUDE.md");
7477
+ if (override && override.length > 0) return path17.resolve(override);
7478
+ return path17.join(process.cwd(), "CLAUDE.md");
7302
7479
  }
7303
7480
  function splitAroundSection(raw) {
7304
7481
  const lines = raw.split("\n");
@@ -7337,7 +7514,7 @@ async function runInstall() {
7337
7514
  const raw = await readIfExists(file) ?? "";
7338
7515
  const { before, after, found } = splitAroundSection(raw);
7339
7516
  const next = compose(before, after);
7340
- await fs15.mkdir(path16.dirname(file), { recursive: true });
7517
+ await fs15.mkdir(path17.dirname(file), { recursive: true });
7341
7518
  await fs15.writeFile(file, next, "utf8");
7342
7519
  const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
7343
7520
  console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
@@ -7401,7 +7578,7 @@ async function runClaudeCommand(args) {
7401
7578
  }
7402
7579
 
7403
7580
  // src/codex-cli.ts
7404
- import path17 from "path";
7581
+ import path18 from "path";
7405
7582
  import os3 from "os";
7406
7583
  import { promises as fs16 } from "fs";
7407
7584
  import { isDeepStrictEqual } from "util";
@@ -7421,14 +7598,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
7421
7598
  var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
7422
7599
  function codexConfigPath() {
7423
7600
  const override = process.env.NEAT_CODEX_CONFIG;
7424
- if (override && override.length > 0) return path17.resolve(override);
7601
+ if (override && override.length > 0) return path18.resolve(override);
7425
7602
  const home = process.env.HOME ?? process.env.USERPROFILE ?? os3.homedir();
7426
- return path17.join(home, ".codex", "config.toml");
7603
+ return path18.join(home, ".codex", "config.toml");
7427
7604
  }
7428
7605
  function agentsFilePath() {
7429
7606
  const override = process.env.NEAT_CODEX_AGENTS;
7430
- if (override && override.length > 0) return path17.resolve(override);
7431
- return path17.join(process.cwd(), "AGENTS.md");
7607
+ if (override && override.length > 0) return path18.resolve(override);
7608
+ return path18.join(process.cwd(), "AGENTS.md");
7432
7609
  }
7433
7610
  function isTableHeader(line) {
7434
7611
  return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
@@ -7611,14 +7788,14 @@ async function runCodex(opts) {
7611
7788
  return { exitCode: 0 };
7612
7789
  }
7613
7790
  if (config.changed) {
7614
- await fs16.mkdir(path17.dirname(configPath), { recursive: true });
7791
+ await fs16.mkdir(path18.dirname(configPath), { recursive: true });
7615
7792
  await fs16.writeFile(configPath, config.text, "utf8");
7616
7793
  console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
7617
7794
  } else {
7618
7795
  console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
7619
7796
  }
7620
7797
  if (agents.changed) {
7621
- await fs16.mkdir(path17.dirname(agentsPath), { recursive: true });
7798
+ await fs16.mkdir(path18.dirname(agentsPath), { recursive: true });
7622
7799
  await fs16.writeFile(agentsPath, agents.text, "utf8");
7623
7800
  console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
7624
7801
  } else {
@@ -7675,7 +7852,7 @@ async function runCodexCommand(args) {
7675
7852
  }
7676
7853
 
7677
7854
  // src/editors-cli.ts
7678
- import path18 from "path";
7855
+ import path19 from "path";
7679
7856
  import os4 from "os";
7680
7857
  import { promises as fs17 } from "fs";
7681
7858
  import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
@@ -7701,17 +7878,17 @@ function homeDir() {
7701
7878
  }
7702
7879
  function xdgConfigDir() {
7703
7880
  const xdg = process.env.XDG_CONFIG_HOME;
7704
- return xdg && xdg.length > 0 ? path18.resolve(xdg) : path18.join(homeDir(), ".config");
7881
+ return xdg && xdg.length > 0 ? path19.resolve(xdg) : path19.join(homeDir(), ".config");
7705
7882
  }
7706
7883
  function envOverride(name) {
7707
7884
  const v = process.env[name];
7708
- return v && v.length > 0 ? path18.resolve(v) : void 0;
7885
+ return v && v.length > 0 ? path19.resolve(v) : void 0;
7709
7886
  }
7710
7887
  var CURSOR_CLIENT = {
7711
7888
  id: "cursor",
7712
7889
  label: "Cursor",
7713
7890
  docsUrl: "https://docs.cursor.com/context/mcp",
7714
- mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path18.join(homeDir(), ".cursor", "mcp.json"),
7891
+ mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path19.join(homeDir(), ".cursor", "mcp.json"),
7715
7892
  mcpContainerKey: "mcpServers",
7716
7893
  format: "json",
7717
7894
  // Cursor still reads a single `.cursorrules` at the project root (the modern
@@ -7723,7 +7900,7 @@ var DEVIN_CLIENT = {
7723
7900
  id: "devin",
7724
7901
  label: "Devin Desktop (Cascade)",
7725
7902
  docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
7726
- mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path18.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
7903
+ mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path19.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
7727
7904
  mcpContainerKey: "mcpServers",
7728
7905
  format: "json",
7729
7906
  rulesFileName: ".windsurfrules"
@@ -7732,7 +7909,7 @@ var GEMINI_CLIENT = {
7732
7909
  id: "gemini",
7733
7910
  label: "Gemini CLI",
7734
7911
  docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
7735
- mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path18.join(homeDir(), ".gemini", "settings.json"),
7912
+ mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path19.join(homeDir(), ".gemini", "settings.json"),
7736
7913
  mcpContainerKey: "mcpServers",
7737
7914
  format: "json",
7738
7915
  rulesFileName: "GEMINI.md"
@@ -7741,7 +7918,7 @@ var QWEN_CLIENT = {
7741
7918
  id: "qwen",
7742
7919
  label: "Qwen Code",
7743
7920
  docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
7744
- mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path18.join(homeDir(), ".qwen", "settings.json"),
7921
+ mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path19.join(homeDir(), ".qwen", "settings.json"),
7745
7922
  mcpContainerKey: "mcpServers",
7746
7923
  format: "json",
7747
7924
  rulesFileName: "QWEN.md"
@@ -7750,7 +7927,7 @@ var AMAZONQ_CLIENT = {
7750
7927
  id: "amazonq",
7751
7928
  label: "Amazon Q Developer CLI",
7752
7929
  docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
7753
- mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path18.join(homeDir(), ".aws", "amazonq", "mcp.json"),
7930
+ mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path19.join(homeDir(), ".aws", "amazonq", "mcp.json"),
7754
7931
  mcpContainerKey: "mcpServers",
7755
7932
  format: "json"
7756
7933
  };
@@ -7758,7 +7935,7 @@ var ROOCODE_CLIENT = {
7758
7935
  id: "roocode",
7759
7936
  label: "Roo Code",
7760
7937
  docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
7761
- mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path18.join(process.cwd(), ".roo", "mcp.json"),
7938
+ mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path19.join(process.cwd(), ".roo", "mcp.json"),
7762
7939
  mcpContainerKey: "mcpServers",
7763
7940
  format: "json"
7764
7941
  };
@@ -7771,9 +7948,9 @@ var ZED_CLIENT = {
7771
7948
  if (override) return override;
7772
7949
  if (process.platform === "win32") {
7773
7950
  const appData = process.env.APPDATA;
7774
- if (appData && appData.length > 0) return path18.join(appData, "Zed", "settings.json");
7951
+ if (appData && appData.length > 0) return path19.join(appData, "Zed", "settings.json");
7775
7952
  }
7776
- return path18.join(homeDir(), ".config", "zed", "settings.json");
7953
+ return path19.join(homeDir(), ".config", "zed", "settings.json");
7777
7954
  },
7778
7955
  mcpContainerKey: "context_servers",
7779
7956
  format: "jsonc",
@@ -7783,7 +7960,7 @@ var OPENCODE_CLIENT = {
7783
7960
  id: "opencode",
7784
7961
  label: "OpenCode",
7785
7962
  docsUrl: "https://opencode.ai/docs/mcp-servers/",
7786
- mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path18.join(xdgConfigDir(), "opencode", "opencode.json"),
7963
+ mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path19.join(xdgConfigDir(), "opencode", "opencode.json"),
7787
7964
  mcpContainerKey: "mcp",
7788
7965
  format: "json",
7789
7966
  serverEntry: NEAT_OPENCODE_SERVER,
@@ -7793,7 +7970,7 @@ var CRUSH_CLIENT = {
7793
7970
  id: "crush",
7794
7971
  label: "Crush",
7795
7972
  docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
7796
- mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path18.join(xdgConfigDir(), "crush", "crush.json"),
7973
+ mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path19.join(xdgConfigDir(), "crush", "crush.json"),
7797
7974
  mcpContainerKey: "mcp",
7798
7975
  format: "json",
7799
7976
  serverEntry: NEAT_CRUSH_SERVER,
@@ -7898,7 +8075,7 @@ async function runEditorInstall(client, opts) {
7898
8075
  const mcpPath = client.mcpConfigPath();
7899
8076
  const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
7900
8077
  const hasRules = typeof client.rulesFileName === "string";
7901
- const rulesPath = hasRules ? path18.join(opts.projectDir, client.rulesFileName) : "";
8078
+ const rulesPath = hasRules ? path19.join(opts.projectDir, client.rulesFileName) : "";
7902
8079
  const mcp = await planMcp(client, mcpPath);
7903
8080
  if (mcp === null) return { exitCode: 1 };
7904
8081
  let existingRules = "";
@@ -7941,10 +8118,10 @@ async function runEditorInstall(client, opts) {
7941
8118
  );
7942
8119
  return { exitCode: 0 };
7943
8120
  }
7944
- await fs17.mkdir(path18.dirname(mcpPath), { recursive: true });
8121
+ await fs17.mkdir(path19.dirname(mcpPath), { recursive: true });
7945
8122
  await fs17.writeFile(mcpPath, mcp.text, "utf8");
7946
8123
  if (hasRules) {
7947
- await fs17.mkdir(path18.dirname(rulesPath), { recursive: true });
8124
+ await fs17.mkdir(path19.dirname(rulesPath), { recursive: true });
7948
8125
  await fs17.writeFile(rulesPath, newRules, "utf8");
7949
8126
  }
7950
8127
  console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
@@ -8445,7 +8622,7 @@ function sleep(ms, signal) {
8445
8622
  }
8446
8623
 
8447
8624
  // src/cli-verbs.ts
8448
- import path19 from "path";
8625
+ import path20 from "path";
8449
8626
  async function resolveProjectEntry(opts) {
8450
8627
  const entries = await listProjects();
8451
8628
  if (opts.project) {
@@ -8455,7 +8632,7 @@ async function resolveProjectEntry(opts) {
8455
8632
  const cwd = opts.cwd ?? process.cwd();
8456
8633
  const resolvedCwd = await normalizeProjectPath(cwd);
8457
8634
  for (const entry2 of entries) {
8458
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path19.sep}`)) {
8635
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path20.sep}`)) {
8459
8636
  return entry2;
8460
8637
  }
8461
8638
  }
@@ -8774,6 +8951,8 @@ function usage5() {
8774
8951
  console.log(" test <id> re-check an existing connector's credential");
8775
8952
  console.log(" Credentials default to an env-var reference ($VAR) resolved at");
8776
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.");
8777
8956
  console.log(" doctor Preflight this directory's setup \u2014 Node version, project,");
8778
8957
  console.log(" and daemon reachability \u2014 and print a fix for anything down.");
8779
8958
  console.log(" Flags: --json. Exits 0 when all pass, 1 when a check fails.");
@@ -9041,12 +9220,12 @@ async function runInit(opts) {
9041
9220
  printDiscoveryReport(opts, services);
9042
9221
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
9043
9222
  const patch = renderPatch(sections);
9044
- const patchPath = path20.join(opts.scanPath, "neat.patch");
9223
+ const patchPath = path21.join(opts.scanPath, "neat.patch");
9045
9224
  if (opts.dryRun) {
9046
9225
  await fs18.writeFile(patchPath, patch, "utf8");
9047
9226
  written.push(patchPath);
9048
9227
  console.log(`dry-run: patch written to ${patchPath}`);
9049
- const gitignorePath = path20.join(opts.scanPath, ".gitignore");
9228
+ const gitignorePath = path21.join(opts.scanPath, ".gitignore");
9050
9229
  const gitignoreExists = await fs18.stat(gitignorePath).then(() => true).catch(() => false);
9051
9230
  const verb = gitignoreExists ? "append" : "create";
9052
9231
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
@@ -9058,9 +9237,9 @@ async function runInit(opts) {
9058
9237
  const graph = getGraph(graphKey);
9059
9238
  const projectPaths = pathsForProject(
9060
9239
  graphKey,
9061
- path20.join(opts.scanPath, "neat-out")
9240
+ path21.join(opts.scanPath, "neat-out")
9062
9241
  );
9063
- const errorsPath = path20.join(path20.dirname(opts.outPath), path20.basename(projectPaths.errorsPath));
9242
+ const errorsPath = path21.join(path21.dirname(opts.outPath), path21.basename(projectPaths.errorsPath));
9064
9243
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
9065
9244
  await saveGraphToDisk(graph, opts.outPath);
9066
9245
  written.push(opts.outPath);
@@ -9069,15 +9248,13 @@ async function runInit(opts) {
9069
9248
  written.push(gitignoreResult.file);
9070
9249
  }
9071
9250
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
9072
- let currentProjectName = opts.project;
9073
9251
  try {
9074
- const entry2 = await addProject({
9252
+ await addProject({
9075
9253
  name: opts.project,
9076
9254
  path: opts.scanPath,
9077
9255
  languages,
9078
9256
  status: "active"
9079
9257
  });
9080
- currentProjectName = entry2.name;
9081
9258
  } catch (err) {
9082
9259
  if (err instanceof ProjectNameCollisionError) {
9083
9260
  console.error(`neat init: ${err.message}`);
@@ -9086,20 +9263,6 @@ async function runInit(opts) {
9086
9263
  }
9087
9264
  throw err;
9088
9265
  }
9089
- const siblings = await listProjects();
9090
- const paused = [];
9091
- for (const p of siblings) {
9092
- if (p.name !== currentProjectName && p.status === "active") {
9093
- await setStatus(p.name, "paused");
9094
- paused.push(p.name);
9095
- }
9096
- }
9097
- if (paused.length > 0) {
9098
- const plural = paused.length === 1 ? "" : "s";
9099
- console.log(
9100
- `neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
9101
- );
9102
- }
9103
9266
  if (!opts.noInstall) {
9104
9267
  if (opts.apply) {
9105
9268
  let instrumented = 0;
@@ -9160,6 +9323,8 @@ async function runInit(opts) {
9160
9323
  if (result.extractionErrors > 0) {
9161
9324
  console.log(`errors: ${errorsPath}`);
9162
9325
  }
9326
+ const skippedBanner = formatSkippedBanner(result.skippedFiles);
9327
+ if (skippedBanner) console.log(skippedBanner);
9163
9328
  console.log(formatPrecisionFloorBanner(result.extractedDropped));
9164
9329
  if (result.extractionErrors > 0 && isStrictExtractionEnabled()) {
9165
9330
  return { exitCode: 4, writtenFiles: written };
@@ -9180,9 +9345,9 @@ var CLAUDE_SKILL_CONFIG = {
9180
9345
  };
9181
9346
  function claudeConfigPath() {
9182
9347
  const override = process.env.NEAT_CLAUDE_CONFIG;
9183
- if (override && override.length > 0) return path20.resolve(override);
9348
+ if (override && override.length > 0) return path21.resolve(override);
9184
9349
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
9185
- return path20.join(home, ".claude.json");
9350
+ return path21.join(home, ".claude.json");
9186
9351
  }
9187
9352
  async function runSkill(opts) {
9188
9353
  const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -9206,7 +9371,7 @@ async function runSkill(opts) {
9206
9371
  ...existing,
9207
9372
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
9208
9373
  };
9209
- await fs18.mkdir(path20.dirname(target), { recursive: true });
9374
+ await fs18.mkdir(path21.dirname(target), { recursive: true });
9210
9375
  await fs18.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
9211
9376
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
9212
9377
  console.log("restart Claude Code to pick up the new MCP server.");
@@ -9256,6 +9421,21 @@ async function main() {
9256
9421
  if (code !== 0) process.exit(code);
9257
9422
  return;
9258
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
+ }
9259
9439
  if (cmd0 === "login") {
9260
9440
  const code = await runLoginCommand(argv.slice(1));
9261
9441
  if (code !== 0) process.exit(code);
@@ -9328,12 +9508,12 @@ async function main() {
9328
9508
  console.error("neat init: --apply and --dry-run are mutually exclusive");
9329
9509
  process.exit(2);
9330
9510
  }
9331
- const scanPath = path20.resolve(target);
9511
+ const scanPath = path21.resolve(target);
9332
9512
  const projectExplicit = parsed.project !== null;
9333
- const projectName = projectExplicit ? project : path20.basename(scanPath);
9513
+ const projectName = projectExplicit ? project : path21.basename(scanPath);
9334
9514
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
9335
- const fallback = pathsForProject(projectKey, path20.join(scanPath, "neat-out")).snapshotPath;
9336
- const outPath = path20.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);
9337
9517
  const result = await runInit({
9338
9518
  scanPath,
9339
9519
  outPath,
@@ -9354,21 +9534,21 @@ async function main() {
9354
9534
  usage5();
9355
9535
  process.exit(2);
9356
9536
  }
9357
- const scanPath = path20.resolve(target);
9537
+ const scanPath = path21.resolve(target);
9358
9538
  const stat = await fs18.stat(scanPath).catch(() => null);
9359
9539
  if (!stat || !stat.isDirectory()) {
9360
9540
  console.error(`neat watch: ${scanPath} is not a directory`);
9361
9541
  process.exit(2);
9362
9542
  }
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))
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))
9367
9547
  );
9368
- const staleEventsPath = path20.resolve(
9369
- process.env.NEAT_STALE_EVENTS_PATH ?? path20.join(path20.dirname(outPath), path20.basename(projectPaths.staleEventsPath))
9548
+ const staleEventsPath = path21.resolve(
9549
+ process.env.NEAT_STALE_EVENTS_PATH ?? path21.join(path21.dirname(outPath), path21.basename(projectPaths.staleEventsPath))
9370
9550
  );
9371
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path20.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;
9372
9552
  const handle = await startWatch(getGraph(project), {
9373
9553
  scanPath,
9374
9554
  outPath,
@@ -9377,7 +9557,7 @@ async function main() {
9377
9557
  project,
9378
9558
  // Resolve NEAT_HOME so a `neat watch` picks up connectors added to
9379
9559
  // ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
9380
- neatHome: process.env.NEAT_HOME ? path20.resolve(process.env.NEAT_HOME) : path20.join(os5.homedir(), ".neat"),
9560
+ neatHome: process.env.NEAT_HOME ? path21.resolve(process.env.NEAT_HOME) : path21.join(os5.homedir(), ".neat"),
9381
9561
  ...embeddingsCachePath ? { embeddingsCachePath } : {},
9382
9562
  host: process.env.HOST ?? "0.0.0.0",
9383
9563
  port: Number(process.env.PORT ?? 8080),
@@ -9558,12 +9738,12 @@ async function main() {
9558
9738
  usage5();
9559
9739
  process.exit(1);
9560
9740
  }
9561
- async function tryOrchestrator(cmd, parsed) {
9562
- const scanPath = path20.resolve(cmd);
9741
+ async function tryOrchestrator(cmd, parsed, overrides = {}) {
9742
+ const scanPath = path21.resolve(cmd);
9563
9743
  const stat = await fs18.stat(scanPath).catch(() => null);
9564
9744
  if (!stat || !stat.isDirectory()) return null;
9565
9745
  const projectExplicit = parsed.project !== null;
9566
- const projectName = projectExplicit ? parsed.project : path20.basename(scanPath);
9746
+ const projectName = projectExplicit ? parsed.project : path21.basename(scanPath);
9567
9747
  const result = await runOrchestrator({
9568
9748
  scanPath,
9569
9749
  project: projectName,
@@ -9571,7 +9751,8 @@ async function tryOrchestrator(cmd, parsed) {
9571
9751
  noInstrument: parsed.noInstrument,
9572
9752
  open: parsed.open,
9573
9753
  noOpen: parsed.noOpen,
9574
- yes: parsed.yes
9754
+ yes: parsed.yes,
9755
+ ...overrides.skipBanner ? { skipBanner: true } : {}
9575
9756
  });
9576
9757
  return result.exitCode;
9577
9758
  }
@@ -9581,13 +9762,18 @@ async function runWelcomeFlow(parsed) {
9581
9762
  // What the person chose at the door reaches the orchestrator as the flags they
9582
9763
  // stand for: the name as `--project`, the instrument answer as `--no-instrument`
9583
9764
  // 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,
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,
9591
9777
  // An explicit flag is an answer; don't ask again.
9592
9778
  instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
9593
9779
  });
@@ -9629,9 +9815,10 @@ function isLoopbackEndpoint(endpoint) {
9629
9815
  return false;
9630
9816
  }
9631
9817
  }
9632
- async function resolveProjectForVerb(client, parsed) {
9818
+ async function resolveProjectForVerb(client, parsed, cwdProject) {
9633
9819
  const explicit = resolveProjectFlag(parsed);
9634
9820
  if (explicit) return explicit;
9821
+ if (cwdProject) return cwdProject;
9635
9822
  const projects = await client.get("/projects");
9636
9823
  if (projects.some((p) => p.name === DEFAULT_PROJECT)) {
9637
9824
  return void 0;
@@ -9678,14 +9865,28 @@ async function resolveClientTarget(opts = {}) {
9678
9865
  }
9679
9866
  return { endpoint: "http://localhost:8080", source: "default" };
9680
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
+ }
9681
9878
  async function resolveDaemonUrl(project, profile) {
9682
9879
  return (await resolveClientTarget({ project, profile })).endpoint;
9683
9880
  }
9684
9881
  async function runQueryVerb(cmd, parsed) {
9685
9882
  const requestedProject = resolveProjectFlag(parsed);
9883
+ const cwdProject = requestedProject ? void 0 : await projectForCwd();
9686
9884
  let target;
9687
9885
  try {
9688
- 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
+ });
9689
9890
  } catch (err) {
9690
9891
  if (err instanceof UnknownProfileError) {
9691
9892
  process.stderr.write(`${err.message}
@@ -9844,7 +10045,11 @@ async function runQueryVerb(cmd, parsed) {
9844
10045
  return 2;
9845
10046
  }
9846
10047
  try {
9847
- const project = await resolveProjectForVerb(client, parsed);
10048
+ const project = await resolveProjectForVerb(
10049
+ client,
10050
+ parsed,
10051
+ cwdProjectApplies(target.source) ? cwdProject : void 0
10052
+ );
9848
10053
  const result = await makeWork(project);
9849
10054
  if (parsed.json) process.stdout.write(formatJson(result) + "\n");
9850
10055
  else process.stdout.write(formatHuman(result) + "\n");
@@ -9860,7 +10065,8 @@ async function runQueryVerb(cmd, parsed) {
9860
10065
  } else if (err instanceof TransportError) {
9861
10066
  console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${target.endpoint})`);
9862
10067
  if (isLoopbackEndpoint(target.endpoint)) {
9863
- console.error(`neat ${cmd}: start one with \`neat watch .\`, or \`neat\` to extract and start in one step.`);
10068
+ const upCommand = requestedProject ? `neat up --project ${requestedProject}` : "neat up";
10069
+ console.error(`neat ${cmd}: run \`${upCommand}\` to start it.`);
9864
10070
  }
9865
10071
  } else {
9866
10072
  console.error(`neat ${cmd}: ${err.message}`);
@@ -9870,9 +10076,13 @@ async function runQueryVerb(cmd, parsed) {
9870
10076
  }
9871
10077
  async function runMonitorVerb(parsed) {
9872
10078
  const requestedProject = resolveProjectFlag(parsed);
10079
+ const cwdProject = requestedProject ? void 0 : await projectForCwd();
9873
10080
  let target;
9874
10081
  try {
9875
- 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
+ });
9876
10086
  } catch (err) {
9877
10087
  if (err instanceof UnknownProfileError) return 0;
9878
10088
  throw err;
@@ -9880,7 +10090,11 @@ async function runMonitorVerb(parsed) {
9880
10090
  const client = createHttpClient(target.endpoint, target.authToken);
9881
10091
  let project;
9882
10092
  try {
9883
- project = await resolveProjectForVerb(client, parsed);
10093
+ project = await resolveProjectForVerb(
10094
+ client,
10095
+ parsed,
10096
+ cwdProjectApplies(target.source) ? cwdProject : void 0
10097
+ );
9884
10098
  } catch (err) {
9885
10099
  if (err instanceof ProjectResolutionError) {
9886
10100
  console.error(`neat monitor: ${err.message}`);
@@ -9904,11 +10118,18 @@ async function runMonitorVerb(parsed) {
9904
10118
  process.off("SIGTERM", onSignal);
9905
10119
  }
9906
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
+ }
9907
10129
  var entry = process.argv[1] ?? "";
9908
10130
  if (/[\\/](?:cli\.(?:cjs|js)|cli|neat|neat\.is)$/.test(entry)) {
9909
10131
  main().catch((err) => {
9910
- console.error(err);
9911
- process.exit(1);
10132
+ process.exit(reportFatal(err));
9912
10133
  });
9913
10134
  }
9914
10135
  export {
@@ -9917,12 +10138,15 @@ export {
9917
10138
  QUERY_VERBS,
9918
10139
  UnknownProfileError,
9919
10140
  commandPrefix,
10141
+ cwdProjectApplies,
9920
10142
  isLoopbackEndpoint,
9921
10143
  isNpxInvocation,
9922
10144
  main,
9923
10145
  parseArgs,
9924
10146
  printBanner,
10147
+ projectForCwd,
9925
10148
  readPackageVersion,
10149
+ reportFatal,
9926
10150
  resolveClientTarget,
9927
10151
  resolveDaemonUrl,
9928
10152
  resolveProjectForVerb,