@neat.is/core 0.4.15 → 0.4.17

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
@@ -36,6 +36,7 @@ import {
36
36
  normalizeProjectPath,
37
37
  pathsForProject,
38
38
  promoteFrontierNodes,
39
+ pruneRegistry,
39
40
  removeProject,
40
41
  resetGraph,
41
42
  retireEdgesByFile,
@@ -44,7 +45,7 @@ import {
44
45
  setStatus,
45
46
  startPersistLoop,
46
47
  startStalenessLoop
47
- } from "./chunk-XS4CGNRO.js";
48
+ } from "./chunk-LUDSPX5N.js";
48
49
  import {
49
50
  startOtelGrpcReceiver
50
51
  } from "./chunk-GHPHVXYM.js";
@@ -57,13 +58,47 @@ import {
57
58
  } from "./chunk-6CO7C4IU.js";
58
59
 
59
60
  // src/cli.ts
60
- import path8 from "path";
61
- import { promises as fs7, readFileSync } from "fs";
61
+ import path9 from "path";
62
+ import { promises as fs7 } from "fs";
63
+
64
+ // src/banner.ts
65
+ import path from "path";
66
+ import { readFileSync } from "fs";
62
67
  import { fileURLToPath } from "url";
68
+ function readPackageVersion() {
69
+ const here = typeof __dirname !== "undefined" ? __dirname : path.dirname(fileURLToPath(import.meta.url));
70
+ const candidates = [
71
+ path.resolve(here, "../package.json"),
72
+ path.resolve(here, "../../package.json")
73
+ ];
74
+ for (const candidate of candidates) {
75
+ try {
76
+ const raw = readFileSync(candidate, "utf8");
77
+ const parsed = JSON.parse(raw);
78
+ if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
79
+ return parsed.version;
80
+ }
81
+ } catch {
82
+ }
83
+ }
84
+ return "unknown";
85
+ }
86
+ function printBanner() {
87
+ console.log("\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");
88
+ console.log("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D");
89
+ console.log("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ");
90
+ console.log("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ");
91
+ console.log("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ");
92
+ console.log("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ");
93
+ console.log("");
94
+ console.log(" Network Expressive Architecting Tool");
95
+ console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
96
+ console.log("");
97
+ }
63
98
 
64
99
  // src/gitignore.ts
65
100
  import { promises as fs } from "fs";
66
- import path from "path";
101
+ import path2 from "path";
67
102
  var NEAT_OUT_LINE = "neat-out/";
68
103
  var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
69
104
  function isNeatOutLine(line) {
@@ -71,7 +106,7 @@ function isNeatOutLine(line) {
71
106
  return trimmed === "neat-out/" || trimmed === "neat-out";
72
107
  }
73
108
  async function ensureNeatOutIgnored(projectDir) {
74
- const file = path.join(projectDir, ".gitignore");
109
+ const file = path2.join(projectDir, ".gitignore");
75
110
  let existing = null;
76
111
  try {
77
112
  existing = await fs.readFile(file, "utf8");
@@ -193,7 +228,7 @@ function formatIncompat(inc) {
193
228
 
194
229
  // src/watch.ts
195
230
  import fs2 from "fs";
196
- import path2 from "path";
231
+ import path3 from "path";
197
232
  import chokidar from "chokidar";
198
233
  var ALL_PHASES = [
199
234
  "services",
@@ -206,8 +241,8 @@ var ALL_PHASES = [
206
241
  ];
207
242
  function classifyChange(relPath) {
208
243
  const phases = /* @__PURE__ */ new Set();
209
- const base = path2.basename(relPath).toLowerCase();
210
- const segments = relPath.split(path2.sep).map((s) => s.toLowerCase());
244
+ const base = path3.basename(relPath).toLowerCase();
245
+ const segments = relPath.split(path3.sep).map((s) => s.toLowerCase());
211
246
  if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
212
247
  phases.add("services");
213
248
  phases.add("aliases");
@@ -328,9 +363,9 @@ function countWatchableDirs(scanPath, limit) {
328
363
  for (const e of entries) {
329
364
  if (count >= limit) return;
330
365
  if (!e.isDirectory()) continue;
331
- if (IGNORED_WATCH_PATHS.some((re) => re.test(path2.join(dir, e.name) + path2.sep))) continue;
366
+ if (IGNORED_WATCH_PATHS.some((re) => re.test(path3.join(dir, e.name) + path3.sep))) continue;
332
367
  count++;
333
- if (depth < 2) visit(path2.join(dir, e.name), depth + 1);
368
+ if (depth < 2) visit(path3.join(dir, e.name), depth + 1);
334
369
  }
335
370
  };
336
371
  visit(scanPath, 0);
@@ -348,8 +383,8 @@ async function startWatch(graph, opts) {
348
383
  const projectName = opts.project ?? DEFAULT_PROJECT;
349
384
  await loadGraphFromDisk(graph, opts.outPath);
350
385
  const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
351
- const policyFilePath = path2.join(opts.scanPath, "policy.json");
352
- const policyViolationsPath = path2.join(path2.dirname(opts.outPath), "policy-violations.ndjson");
386
+ const policyFilePath = path3.join(opts.scanPath, "policy.json");
387
+ const policyViolationsPath = path3.join(path3.dirname(opts.outPath), "policy-violations.ndjson");
353
388
  let policies = [];
354
389
  try {
355
390
  policies = await loadPolicyFile(policyFilePath);
@@ -400,7 +435,7 @@ async function startWatch(graph, opts) {
400
435
  assertBindAuthority(host, auth.authToken);
401
436
  const port = opts.port ?? 8080;
402
437
  const otelPort = opts.otelPort ?? 4318;
403
- const cachePath = opts.embeddingsCachePath ?? path2.join(path2.dirname(opts.outPath), "embeddings.json");
438
+ const cachePath = opts.embeddingsCachePath ?? path3.join(path3.dirname(opts.outPath), "embeddings.json");
404
439
  let searchIndex;
405
440
  try {
406
441
  searchIndex = await buildSearchIndex(graph, { cachePath });
@@ -418,7 +453,7 @@ async function startWatch(graph, opts) {
418
453
  // Paths are derived from the explicit options the watch caller passes
419
454
  // — pathsForProject is only used to fill in the embeddings/snapshot
420
455
  // fields so the registry shape is complete.
421
- ...pathsForProject(projectName, path2.dirname(opts.outPath)),
456
+ ...pathsForProject(projectName, path3.dirname(opts.outPath)),
422
457
  snapshotPath: opts.outPath,
423
458
  errorsPath: opts.errorsPath,
424
459
  staleEventsPath: opts.staleEventsPath
@@ -505,9 +540,9 @@ async function startWatch(graph, opts) {
505
540
  };
506
541
  const onPath = (absPath) => {
507
542
  if (shouldIgnore(absPath)) return;
508
- const rel = path2.relative(opts.scanPath, absPath);
543
+ const rel = path3.relative(opts.scanPath, absPath);
509
544
  if (!rel || rel.startsWith("..")) return;
510
- pendingPaths.add(rel.split(path2.sep).join("/"));
545
+ pendingPaths.add(rel.split(path3.sep).join("/"));
511
546
  const phases = classifyChange(rel);
512
547
  if (phases.size === 0) {
513
548
  for (const p of ALL_PHASES) pending.add(p);
@@ -561,7 +596,7 @@ async function startWatch(graph, opts) {
561
596
 
562
597
  // src/deploy/detect.ts
563
598
  import { promises as fs3 } from "fs";
564
- import path3 from "path";
599
+ import path4 from "path";
565
600
  import { spawn } from "child_process";
566
601
  import { randomBytes } from "crypto";
567
602
  function generateToken() {
@@ -661,7 +696,7 @@ async function runDeploy(opts = {}) {
661
696
  const token = generateToken();
662
697
  switch (substrate) {
663
698
  case "docker-compose": {
664
- const artifactPath = path3.join(cwd, "docker-compose.neat.yml");
699
+ const artifactPath = path4.join(cwd, "docker-compose.neat.yml");
665
700
  const contents = emitDockerCompose(cwd);
666
701
  await fs3.writeFile(artifactPath, contents, "utf8");
667
702
  return {
@@ -669,11 +704,11 @@ async function runDeploy(opts = {}) {
669
704
  artifactPath,
670
705
  token,
671
706
  contents,
672
- startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${path3.basename(artifactPath)} up -d`
707
+ startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${path4.basename(artifactPath)} up -d`
673
708
  };
674
709
  }
675
710
  case "systemd": {
676
- const artifactPath = path3.join(cwd, "neat.service");
711
+ const artifactPath = path4.join(cwd, "neat.service");
677
712
  const contents = emitSystemdUnit(cwd);
678
713
  await fs3.writeFile(artifactPath, contents, "utf8");
679
714
  return {
@@ -705,7 +740,7 @@ ${contents}EOF
705
740
 
706
741
  // src/installers/javascript.ts
707
742
  import { promises as fs4 } from "fs";
708
- import path4 from "path";
743
+ import path5 from "path";
709
744
  import semver from "semver";
710
745
 
711
746
  // src/installers/templates.ts
@@ -1251,11 +1286,11 @@ var OTEL_ENV = {
1251
1286
  value: "http://localhost:4318/projects/<project>/v1/traces"
1252
1287
  };
1253
1288
  function serviceNodeName(pkg, serviceDir) {
1254
- return pkg.name ?? path4.basename(serviceDir);
1289
+ return pkg.name ?? path5.basename(serviceDir);
1255
1290
  }
1256
1291
  function projectToken(pkg, serviceDir, project) {
1257
1292
  if (project && project.length > 0) return project;
1258
- return pkg.name ?? path4.basename(serviceDir);
1293
+ return pkg.name ?? path5.basename(serviceDir);
1259
1294
  }
1260
1295
  async function readJsonFile(p) {
1261
1296
  try {
@@ -1268,16 +1303,16 @@ async function readJsonFile(p) {
1268
1303
  async function detectRuntimeKind(pkgRoot, pkg) {
1269
1304
  const deps = allDeps(pkg);
1270
1305
  if ("react-native" in deps || "expo" in deps) return "react-native";
1271
- const appJson = await readJsonFile(path4.join(pkgRoot, "app.json"));
1306
+ const appJson = await readJsonFile(path5.join(pkgRoot, "app.json"));
1272
1307
  if (appJson && typeof appJson === "object" && "expo" in appJson) {
1273
1308
  return "react-native";
1274
1309
  }
1275
- if (await exists(path4.join(pkgRoot, "vite.config.js")) || await exists(path4.join(pkgRoot, "vite.config.ts")) || await exists(path4.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
1310
+ if (await exists(path5.join(pkgRoot, "vite.config.js")) || await exists(path5.join(pkgRoot, "vite.config.ts")) || await exists(path5.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
1276
1311
  return "browser-bundle";
1277
1312
  }
1278
- if (await exists(path4.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
1279
- if (await exists(path4.join(pkgRoot, "bun.lockb"))) return "bun";
1280
- if (await exists(path4.join(pkgRoot, "deno.json")) || await exists(path4.join(pkgRoot, "deno.lock"))) {
1313
+ if (await exists(path5.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
1314
+ if (await exists(path5.join(pkgRoot, "bun.lockb"))) return "bun";
1315
+ if (await exists(path5.join(pkgRoot, "deno.json")) || await exists(path5.join(pkgRoot, "deno.lock"))) {
1281
1316
  return "deno";
1282
1317
  }
1283
1318
  const engines = pkg.engines ?? {};
@@ -1286,7 +1321,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
1286
1321
  }
1287
1322
  async function readPackageJson(serviceDir) {
1288
1323
  try {
1289
- const raw = await fs4.readFile(path4.join(serviceDir, "package.json"), "utf8");
1324
+ const raw = await fs4.readFile(path5.join(serviceDir, "package.json"), "utf8");
1290
1325
  return JSON.parse(raw);
1291
1326
  } catch {
1292
1327
  return null;
@@ -1330,7 +1365,7 @@ function needsVersionUpgrade(installed, expected) {
1330
1365
  var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
1331
1366
  async function findNextConfig(serviceDir) {
1332
1367
  for (const name of NEXT_CONFIG_CANDIDATES) {
1333
- const candidate = path4.join(serviceDir, name);
1368
+ const candidate = path5.join(serviceDir, name);
1334
1369
  if (await exists(candidate)) return candidate;
1335
1370
  }
1336
1371
  return null;
@@ -1357,7 +1392,7 @@ function hasRemixDependency(pkg) {
1357
1392
  }
1358
1393
  async function findRemixEntry(serviceDir) {
1359
1394
  for (const rel of REMIX_ENTRY_CANDIDATES) {
1360
- const candidate = path4.join(serviceDir, rel);
1395
+ const candidate = path5.join(serviceDir, rel);
1361
1396
  if (await exists(candidate)) return candidate;
1362
1397
  }
1363
1398
  return null;
@@ -1369,14 +1404,14 @@ function hasSvelteKitDependency(pkg) {
1369
1404
  }
1370
1405
  async function findSvelteKitHooks(serviceDir) {
1371
1406
  for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
1372
- const candidate = path4.join(serviceDir, rel);
1407
+ const candidate = path5.join(serviceDir, rel);
1373
1408
  if (await exists(candidate)) return candidate;
1374
1409
  }
1375
1410
  return null;
1376
1411
  }
1377
1412
  async function findSvelteKitConfig(serviceDir) {
1378
1413
  for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
1379
- const candidate = path4.join(serviceDir, rel);
1414
+ const candidate = path5.join(serviceDir, rel);
1380
1415
  if (await exists(candidate)) return candidate;
1381
1416
  }
1382
1417
  return null;
@@ -1387,7 +1422,7 @@ function hasNuxtDependency(pkg) {
1387
1422
  }
1388
1423
  async function findNuxtConfig(serviceDir) {
1389
1424
  for (const name of NUXT_CONFIG_CANDIDATES) {
1390
- const candidate = path4.join(serviceDir, name);
1425
+ const candidate = path5.join(serviceDir, name);
1391
1426
  if (await exists(candidate)) return candidate;
1392
1427
  }
1393
1428
  return null;
@@ -1398,7 +1433,7 @@ function hasAstroDependency(pkg) {
1398
1433
  }
1399
1434
  async function findAstroConfig(serviceDir) {
1400
1435
  for (const name of ASTRO_CONFIG_CANDIDATES) {
1401
- const candidate = path4.join(serviceDir, name);
1436
+ const candidate = path5.join(serviceDir, name);
1402
1437
  if (await exists(candidate)) return candidate;
1403
1438
  }
1404
1439
  return null;
@@ -1412,7 +1447,7 @@ function parseNextMajor(range) {
1412
1447
  return Number.isFinite(n) ? n : null;
1413
1448
  }
1414
1449
  async function isTypeScriptProject(serviceDir) {
1415
- return exists(path4.join(serviceDir, "tsconfig.json"));
1450
+ return exists(path5.join(serviceDir, "tsconfig.json"));
1416
1451
  }
1417
1452
  var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
1418
1453
  var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
@@ -1458,7 +1493,7 @@ function entryFromScript(script) {
1458
1493
  }
1459
1494
  async function resolveEntry(serviceDir, pkg) {
1460
1495
  if (typeof pkg.main === "string" && pkg.main.length > 0) {
1461
- const candidate = path4.resolve(serviceDir, pkg.main);
1496
+ const candidate = path5.resolve(serviceDir, pkg.main);
1462
1497
  if (await exists(candidate)) return candidate;
1463
1498
  }
1464
1499
  if (pkg.bin) {
@@ -1472,36 +1507,36 @@ async function resolveEntry(serviceDir, pkg) {
1472
1507
  if (typeof first === "string") binEntry = first;
1473
1508
  }
1474
1509
  if (binEntry) {
1475
- const candidate = path4.resolve(serviceDir, binEntry);
1510
+ const candidate = path5.resolve(serviceDir, binEntry);
1476
1511
  if (await exists(candidate)) return candidate;
1477
1512
  }
1478
1513
  }
1479
1514
  const startEntry = entryFromScript(pkg.scripts?.start);
1480
1515
  if (startEntry) {
1481
- const candidate = path4.resolve(serviceDir, startEntry);
1516
+ const candidate = path5.resolve(serviceDir, startEntry);
1482
1517
  if (await exists(candidate)) return candidate;
1483
1518
  }
1484
1519
  const devEntry = entryFromScript(pkg.scripts?.dev);
1485
1520
  if (devEntry) {
1486
- const candidate = path4.resolve(serviceDir, devEntry);
1521
+ const candidate = path5.resolve(serviceDir, devEntry);
1487
1522
  if (await exists(candidate)) return candidate;
1488
1523
  }
1489
1524
  for (const rel of SRC_INDEX_CANDIDATES) {
1490
- const candidate = path4.join(serviceDir, rel);
1525
+ const candidate = path5.join(serviceDir, rel);
1491
1526
  if (await exists(candidate)) return candidate;
1492
1527
  }
1493
1528
  for (const rel of SRC_NAMED_CANDIDATES) {
1494
- const candidate = path4.join(serviceDir, rel);
1529
+ const candidate = path5.join(serviceDir, rel);
1495
1530
  if (await exists(candidate)) return candidate;
1496
1531
  }
1497
1532
  for (const name of INDEX_CANDIDATES) {
1498
- const candidate = path4.join(serviceDir, name);
1533
+ const candidate = path5.join(serviceDir, name);
1499
1534
  if (await exists(candidate)) return candidate;
1500
1535
  }
1501
1536
  return null;
1502
1537
  }
1503
1538
  function dispatchEntry(entryFile, pkg) {
1504
- const ext = path4.extname(entryFile).toLowerCase();
1539
+ const ext = path5.extname(entryFile).toLowerCase();
1505
1540
  if (ext === ".ts" || ext === ".tsx") return "ts";
1506
1541
  if (ext === ".mjs") return "esm";
1507
1542
  if (ext === ".cjs") return "cjs";
@@ -1518,9 +1553,9 @@ function otelInitContents(flavor) {
1518
1553
  return OTEL_INIT_CJS;
1519
1554
  }
1520
1555
  function injectionLine(flavor, entryFile, otelInitFile) {
1521
- let rel = path4.relative(path4.dirname(entryFile), otelInitFile);
1556
+ let rel = path5.relative(path5.dirname(entryFile), otelInitFile);
1522
1557
  if (!rel.startsWith(".")) rel = `./${rel}`;
1523
- rel = rel.split(path4.sep).join("/");
1558
+ rel = rel.split(path5.sep).join("/");
1524
1559
  if (flavor === "cjs") return `require('${rel}')`;
1525
1560
  if (flavor === "esm") return `import '${rel}'`;
1526
1561
  const tsRel = rel.replace(/\.ts$/, "");
@@ -1533,23 +1568,23 @@ function lineIsOtelInjection(line) {
1533
1568
  }
1534
1569
  async function detectsSrcLayout(serviceDir) {
1535
1570
  const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
1536
- exists(path4.join(serviceDir, "src", "app")),
1537
- exists(path4.join(serviceDir, "src", "pages")),
1538
- exists(path4.join(serviceDir, "app")),
1539
- exists(path4.join(serviceDir, "pages"))
1571
+ exists(path5.join(serviceDir, "src", "app")),
1572
+ exists(path5.join(serviceDir, "src", "pages")),
1573
+ exists(path5.join(serviceDir, "app")),
1574
+ exists(path5.join(serviceDir, "pages"))
1540
1575
  ]);
1541
1576
  return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
1542
1577
  }
1543
1578
  async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
1544
1579
  const useTs = await isTypeScriptProject(serviceDir);
1545
1580
  const srcLayout = await detectsSrcLayout(serviceDir);
1546
- const baseDir = srcLayout ? path4.join(serviceDir, "src") : serviceDir;
1547
- const instrumentationFile = path4.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
1548
- const instrumentationNodeFile = path4.join(
1581
+ const baseDir = srcLayout ? path5.join(serviceDir, "src") : serviceDir;
1582
+ const instrumentationFile = path5.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
1583
+ const instrumentationNodeFile = path5.join(
1549
1584
  baseDir,
1550
1585
  useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
1551
1586
  );
1552
- const envNeatFile = path4.join(baseDir, ".env.neat");
1587
+ const envNeatFile = path5.join(baseDir, ".env.neat");
1553
1588
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
1554
1589
  const dependencyEdits = [];
1555
1590
  for (const sdk of SDK_PACKAGES) {
@@ -1654,7 +1689,7 @@ function buildDependencyEdits(pkg, manifestPath) {
1654
1689
  return edits;
1655
1690
  }
1656
1691
  async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
1657
- const envNeatFile = path4.join(serviceDir, ".env.neat");
1692
+ const envNeatFile = path5.join(serviceDir, ".env.neat");
1658
1693
  if (!await exists(envNeatFile)) {
1659
1694
  generatedFiles.push({
1660
1695
  file: envNeatFile,
@@ -1689,7 +1724,7 @@ function fileImportsOtelHook(raw, specifiers) {
1689
1724
  }
1690
1725
  async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
1691
1726
  const useTs = await isTypeScriptProject(serviceDir);
1692
- const otelServerFile = path4.join(
1727
+ const otelServerFile = path5.join(
1693
1728
  serviceDir,
1694
1729
  useTs ? "app/otel.server.ts" : "app/otel.server.js"
1695
1730
  );
@@ -1746,11 +1781,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
1746
1781
  }
1747
1782
  async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
1748
1783
  const useTs = await isTypeScriptProject(serviceDir);
1749
- const otelInitFile = path4.join(
1784
+ const otelInitFile = path5.join(
1750
1785
  serviceDir,
1751
1786
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
1752
1787
  );
1753
- const resolvedHooksFile = hooksFile ?? path4.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
1788
+ const resolvedHooksFile = hooksFile ?? path5.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
1754
1789
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
1755
1790
  const generatedFiles = [];
1756
1791
  const entrypointEdits = [];
@@ -1812,11 +1847,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
1812
1847
  }
1813
1848
  async function planNuxt(serviceDir, pkg, manifestPath, project) {
1814
1849
  const useTs = await isTypeScriptProject(serviceDir);
1815
- const otelPluginFile = path4.join(
1850
+ const otelPluginFile = path5.join(
1816
1851
  serviceDir,
1817
1852
  useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
1818
1853
  );
1819
- const otelInitFile = path4.join(
1854
+ const otelInitFile = path5.join(
1820
1855
  serviceDir,
1821
1856
  useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
1822
1857
  );
@@ -1867,19 +1902,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
1867
1902
  var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
1868
1903
  async function findAstroMiddleware(serviceDir) {
1869
1904
  for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
1870
- const candidate = path4.join(serviceDir, rel);
1905
+ const candidate = path5.join(serviceDir, rel);
1871
1906
  if (await exists(candidate)) return candidate;
1872
1907
  }
1873
1908
  return null;
1874
1909
  }
1875
1910
  async function planAstro(serviceDir, pkg, manifestPath, project) {
1876
1911
  const useTs = await isTypeScriptProject(serviceDir);
1877
- const otelInitFile = path4.join(
1912
+ const otelInitFile = path5.join(
1878
1913
  serviceDir,
1879
1914
  useTs ? "src/otel-init.ts" : "src/otel-init.js"
1880
1915
  );
1881
1916
  const existingMiddleware = await findAstroMiddleware(serviceDir);
1882
- const middlewareFile = existingMiddleware ?? path4.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
1917
+ const middlewareFile = existingMiddleware ?? path5.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
1883
1918
  const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
1884
1919
  const generatedFiles = [];
1885
1920
  const entrypointEdits = [];
@@ -1975,7 +2010,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
1975
2010
  }
1976
2011
  async function plan(serviceDir, opts) {
1977
2012
  const pkg = await readPackageJson(serviceDir);
1978
- const manifestPath = path4.join(serviceDir, "package.json");
2013
+ const manifestPath = path5.join(serviceDir, "package.json");
1979
2014
  const project = opts?.project;
1980
2015
  const empty = {
1981
2016
  language: "javascript",
@@ -2010,8 +2045,8 @@ async function plan(serviceDir, opts) {
2010
2045
  return { ...empty, libOnly: true };
2011
2046
  }
2012
2047
  const flavor = dispatchEntry(entryFile, pkg);
2013
- const otelInitFile = path4.join(path4.dirname(entryFile), otelInitFilename(flavor));
2014
- const envNeatFile = path4.join(serviceDir, ".env.neat");
2048
+ const otelInitFile = path5.join(path5.dirname(entryFile), otelInitFilename(flavor));
2049
+ const envNeatFile = path5.join(serviceDir, ".env.neat");
2015
2050
  const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
2016
2051
  const dependencyEdits = [];
2017
2052
  for (const sdk of SDK_PACKAGES) {
@@ -2080,13 +2115,13 @@ async function plan(serviceDir, opts) {
2080
2115
  };
2081
2116
  }
2082
2117
  function isAllowedWritePath(serviceDir, target) {
2083
- const rel = path4.relative(serviceDir, target);
2118
+ const rel = path5.relative(serviceDir, target);
2084
2119
  if (rel.startsWith("..")) return false;
2085
- const base = path4.basename(target);
2120
+ const base = path5.basename(target);
2086
2121
  if (base === "package.json") return true;
2087
2122
  if (base === ".env.neat") return true;
2088
2123
  if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
2089
- const relPosix = rel.split(path4.sep).join("/");
2124
+ const relPosix = rel.split(path5.sep).join("/");
2090
2125
  if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
2091
2126
  if (relPosix === base) return true;
2092
2127
  if (relPosix === `src/${base}`) return true;
@@ -2103,7 +2138,7 @@ function isAllowedWritePath(serviceDir, target) {
2103
2138
  return false;
2104
2139
  }
2105
2140
  async function writeAtomic(file, contents) {
2106
- await fs4.mkdir(path4.dirname(file), { recursive: true });
2141
+ await fs4.mkdir(path5.dirname(file), { recursive: true });
2107
2142
  const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
2108
2143
  await fs4.writeFile(tmp, contents, "utf8");
2109
2144
  await fs4.rename(tmp, file);
@@ -2287,7 +2322,7 @@ async function rollback(installPlan, originals, createdFiles) {
2287
2322
  ...removed.map((f) => `removed: ${f}`),
2288
2323
  ""
2289
2324
  ];
2290
- const rollbackPath = path4.join(installPlan.serviceDir, "neat-rollback.patch");
2325
+ const rollbackPath = path5.join(installPlan.serviceDir, "neat-rollback.patch");
2291
2326
  await fs4.writeFile(rollbackPath, lines.join("\n"), "utf8");
2292
2327
  }
2293
2328
  function injectInstrumentationHook(raw) {
@@ -2317,7 +2352,7 @@ var javascriptInstaller = {
2317
2352
 
2318
2353
  // src/installers/python.ts
2319
2354
  import { promises as fs5 } from "fs";
2320
- import path5 from "path";
2355
+ import path6 from "path";
2321
2356
  var SDK_PACKAGES2 = [
2322
2357
  { name: "opentelemetry-distro", version: ">=0.49b0" },
2323
2358
  { name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
@@ -2338,7 +2373,7 @@ async function exists2(p) {
2338
2373
  async function detect2(serviceDir) {
2339
2374
  const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
2340
2375
  for (const m of markers) {
2341
- if (await exists2(path5.join(serviceDir, m))) return true;
2376
+ if (await exists2(path6.join(serviceDir, m))) return true;
2342
2377
  }
2343
2378
  return false;
2344
2379
  }
@@ -2348,7 +2383,7 @@ function reqPackageName(line) {
2348
2383
  return head.replace(/[<>=!~].*$/, "").toLowerCase();
2349
2384
  }
2350
2385
  async function planRequirementsTxtEdits(serviceDir) {
2351
- const file = path5.join(serviceDir, "requirements.txt");
2386
+ const file = path6.join(serviceDir, "requirements.txt");
2352
2387
  if (!await exists2(file)) return null;
2353
2388
  const raw = await fs5.readFile(file, "utf8");
2354
2389
  const presentNames = new Set(
@@ -2358,7 +2393,7 @@ async function planRequirementsTxtEdits(serviceDir) {
2358
2393
  return { manifest: file, missing: [...missing] };
2359
2394
  }
2360
2395
  async function planProcfileEdits(serviceDir) {
2361
- const procfile = path5.join(serviceDir, "Procfile");
2396
+ const procfile = path6.join(serviceDir, "Procfile");
2362
2397
  if (!await exists2(procfile)) return [];
2363
2398
  const raw = await fs5.readFile(procfile, "utf8");
2364
2399
  const edits = [];
@@ -2447,7 +2482,7 @@ async function apply2(installPlan) {
2447
2482
  if (raw === void 0) {
2448
2483
  throw new Error(`python installer: cannot read ${file} during apply`);
2449
2484
  }
2450
- const base = path5.basename(file);
2485
+ const base = path6.basename(file);
2451
2486
  if (base === "requirements.txt") {
2452
2487
  const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
2453
2488
  if (edits.length > 0) {
@@ -2486,7 +2521,7 @@ async function rollback2(installPlan, originals) {
2486
2521
  ...restored.map((f) => `restored: ${f}`),
2487
2522
  ""
2488
2523
  ];
2489
- const rollbackPath = path5.join(installPlan.serviceDir, "neat-rollback.patch");
2524
+ const rollbackPath = path6.join(installPlan.serviceDir, "neat-rollback.patch");
2490
2525
  await fs5.writeFile(rollbackPath, lines.join("\n"), "utf8");
2491
2526
  }
2492
2527
  var pythonInstaller = {
@@ -2611,7 +2646,7 @@ function renderPatch(sections) {
2611
2646
  import { promises as fs6 } from "fs";
2612
2647
  import http from "http";
2613
2648
  import net from "net";
2614
- import path6 from "path";
2649
+ import path7 from "path";
2615
2650
  import { spawn as spawn2 } from "child_process";
2616
2651
  import readline from "readline";
2617
2652
  async function extractAndPersist(opts) {
@@ -2620,7 +2655,7 @@ async function extractAndPersist(opts) {
2620
2655
  const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
2621
2656
  resetGraph(graphKey);
2622
2657
  const graph = getGraph(graphKey);
2623
- const projectPaths = pathsForProject(graphKey, path6.join(opts.scanPath, "neat-out"));
2658
+ const projectPaths = pathsForProject(graphKey, path7.join(opts.scanPath, "neat-out"));
2624
2659
  const extraction = await extractFromDirectory(graph, opts.scanPath, {
2625
2660
  errorsPath: projectPaths.errorsPath
2626
2661
  });
@@ -2674,7 +2709,7 @@ async function applyInstallersOver(services, project, options = {}) {
2674
2709
  console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
2675
2710
  } else if (outcome.outcome === "react-native") {
2676
2711
  reactNative++;
2677
- const svcName = path6.basename(svc.dir);
2712
+ const svcName = path7.basename(svc.dir);
2678
2713
  console.log(
2679
2714
  `neat: ${svc.dir} detected as React Native / Expo
2680
2715
  The installer doesn't cover this runtime deterministically.
@@ -2685,7 +2720,7 @@ async function applyInstallersOver(services, project, options = {}) {
2685
2720
  );
2686
2721
  } else if (outcome.outcome === "bun") {
2687
2722
  bun++;
2688
- const svcName = path6.basename(svc.dir);
2723
+ const svcName = path7.basename(svc.dir);
2689
2724
  console.log(
2690
2725
  `neat: ${svc.dir} detected as Bun
2691
2726
  The installer doesn't cover this runtime deterministically.
@@ -2696,7 +2731,7 @@ async function applyInstallersOver(services, project, options = {}) {
2696
2731
  );
2697
2732
  } else if (outcome.outcome === "deno") {
2698
2733
  deno++;
2699
- const svcName = path6.basename(svc.dir);
2734
+ const svcName = path7.basename(svc.dir);
2700
2735
  console.log(
2701
2736
  `neat: ${svc.dir} detected as Deno
2702
2737
  The installer doesn't cover this runtime deterministically.
@@ -2707,7 +2742,7 @@ async function applyInstallersOver(services, project, options = {}) {
2707
2742
  );
2708
2743
  } else if (outcome.outcome === "cloudflare-workers") {
2709
2744
  cloudflareWorkers++;
2710
- const svcName = path6.basename(svc.dir);
2745
+ const svcName = path7.basename(svc.dir);
2711
2746
  console.log(
2712
2747
  `neat: ${svc.dir} detected as Cloudflare Workers
2713
2748
  The installer doesn't cover this runtime deterministically.
@@ -2718,7 +2753,7 @@ async function applyInstallersOver(services, project, options = {}) {
2718
2753
  );
2719
2754
  } else if (outcome.outcome === "electron") {
2720
2755
  electron++;
2721
- const svcName = path6.basename(svc.dir);
2756
+ const svcName = path7.basename(svc.dir);
2722
2757
  console.log(
2723
2758
  `neat: ${svc.dir} detected as Electron
2724
2759
  The installer doesn't cover this runtime deterministically.
@@ -2892,10 +2927,10 @@ function formatPortCollisionMessage(port) {
2892
2927
  ];
2893
2928
  }
2894
2929
  function spawnDaemonDetached() {
2895
- const here = path6.dirname(new URL(import.meta.url).pathname);
2930
+ const here = path7.dirname(new URL(import.meta.url).pathname);
2896
2931
  const candidates = [
2897
- path6.join(here, "neatd.cjs"),
2898
- path6.join(here, "neatd.js")
2932
+ path7.join(here, "neatd.cjs"),
2933
+ path7.join(here, "neatd.js")
2899
2934
  ];
2900
2935
  let entry2 = null;
2901
2936
  const fsSync = __require("fs");
@@ -2959,6 +2994,7 @@ async function runOrchestrator(opts) {
2959
2994
  result.exitCode = 2;
2960
2995
  return result;
2961
2996
  }
2997
+ printBanner();
2962
2998
  console.log(`neat: ${opts.scanPath}`);
2963
2999
  console.log("");
2964
3000
  const persisted = await extractAndPersist({
@@ -2969,6 +3005,13 @@ async function runOrchestrator(opts) {
2969
3005
  const { graph, services, languages } = persisted;
2970
3006
  result.steps.discovery = { services: services.length, languages };
2971
3007
  console.log(`discovered ${services.length} service(s) across ${languages.length} language(s)`);
3008
+ if (services.length === 0) {
3009
+ console.error(
3010
+ `neat: no services found in ${opts.scanPath} \u2014 run from inside your project root, or \`npx neat.is <path>\``
3011
+ );
3012
+ result.exitCode = 2;
3013
+ return result;
3014
+ }
2972
3015
  let runApply = !opts.noInstrument;
2973
3016
  if (runApply && !opts.yes && process.stdout.isTTY && process.stdin.isTTY) {
2974
3017
  runApply = await promptYesNo("instrument your services and open the dashboard?");
@@ -3092,10 +3135,16 @@ function printSummary(result, graph, dashboardUrl) {
3092
3135
  for (const [t, c] of [...byEdge.entries()].sort()) console.log(` ${t}: ${c}`);
3093
3136
  console.log("");
3094
3137
  console.log(`dashboard: ${dashboardUrl}`);
3138
+ const token = process.env.NEAT_AUTH_TOKEN;
3139
+ if (typeof token === "string" && token.length > 0) {
3140
+ console.log(`auth token: ${token}`);
3141
+ } else {
3142
+ console.log("running locally \u2014 open the dashboard, no token needed");
3143
+ }
3095
3144
  }
3096
3145
 
3097
3146
  // src/cli-verbs.ts
3098
- import path7 from "path";
3147
+ import path8 from "path";
3099
3148
 
3100
3149
  // src/cli-client.ts
3101
3150
  import { Provenance as Provenance2 } from "@neat.is/types";
@@ -3123,10 +3172,10 @@ function createHttpClient(baseUrl, bearerToken) {
3123
3172
  const root = baseUrl.replace(/\/$/, "");
3124
3173
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
3125
3174
  return {
3126
- async get(path9) {
3175
+ async get(path10) {
3127
3176
  let res;
3128
3177
  try {
3129
- res = await fetch(`${root}${path9}`, {
3178
+ res = await fetch(`${root}${path10}`, {
3130
3179
  headers: { ...authHeader }
3131
3180
  });
3132
3181
  } catch (err) {
@@ -3138,16 +3187,16 @@ function createHttpClient(baseUrl, bearerToken) {
3138
3187
  const body = await res.text().catch(() => "");
3139
3188
  throw new HttpError(
3140
3189
  res.status,
3141
- `${res.status} ${res.statusText} on GET ${path9}: ${body}`,
3190
+ `${res.status} ${res.statusText} on GET ${path10}: ${body}`,
3142
3191
  body
3143
3192
  );
3144
3193
  }
3145
3194
  return await res.json();
3146
3195
  },
3147
- async post(path9, body) {
3196
+ async post(path10, body) {
3148
3197
  let res;
3149
3198
  try {
3150
- res = await fetch(`${root}${path9}`, {
3199
+ res = await fetch(`${root}${path10}`, {
3151
3200
  method: "POST",
3152
3201
  headers: { "content-type": "application/json", ...authHeader },
3153
3202
  body: JSON.stringify(body)
@@ -3161,7 +3210,7 @@ function createHttpClient(baseUrl, bearerToken) {
3161
3210
  const text = await res.text().catch(() => "");
3162
3211
  throw new HttpError(
3163
3212
  res.status,
3164
- `${res.status} ${res.statusText} on POST ${path9}: ${text}`,
3213
+ `${res.status} ${res.statusText} on POST ${path10}: ${text}`,
3165
3214
  text
3166
3215
  );
3167
3216
  }
@@ -3175,12 +3224,12 @@ function projectPath(project, suffix) {
3175
3224
  }
3176
3225
  async function runRootCause(client, input) {
3177
3226
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
3178
- const path9 = projectPath(
3227
+ const path10 = projectPath(
3179
3228
  input.project,
3180
3229
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
3181
3230
  );
3182
3231
  try {
3183
- const result = await client.get(path9);
3232
+ const result = await client.get(path10);
3184
3233
  const arrowPath = result.traversalPath.join(" \u2190 ");
3185
3234
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
3186
3235
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -3206,12 +3255,12 @@ async function runRootCause(client, input) {
3206
3255
  }
3207
3256
  async function runBlastRadius(client, input) {
3208
3257
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
3209
- const path9 = projectPath(
3258
+ const path10 = projectPath(
3210
3259
  input.project,
3211
3260
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
3212
3261
  );
3213
3262
  try {
3214
- const result = await client.get(path9);
3263
+ const result = await client.get(path10);
3215
3264
  if (result.totalAffected === 0) {
3216
3265
  return {
3217
3266
  summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
@@ -3245,12 +3294,12 @@ function formatBlastEntry(n) {
3245
3294
  }
3246
3295
  async function runDependencies(client, input) {
3247
3296
  const depth = input.depth ?? 3;
3248
- const path9 = projectPath(
3297
+ const path10 = projectPath(
3249
3298
  input.project,
3250
3299
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
3251
3300
  );
3252
3301
  try {
3253
- const result = await client.get(path9);
3302
+ const result = await client.get(path10);
3254
3303
  if (result.total === 0) {
3255
3304
  return {
3256
3305
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -3331,9 +3380,9 @@ function formatDuration(ms) {
3331
3380
  return `${Math.round(h / 24)}d`;
3332
3381
  }
3333
3382
  async function runIncidents(client, input) {
3334
- const path9 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
3383
+ const path10 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
3335
3384
  try {
3336
- const body = await client.get(path9);
3385
+ const body = await client.get(path10);
3337
3386
  const events = body.events;
3338
3387
  if (events.length === 0) {
3339
3388
  return {
@@ -3623,7 +3672,7 @@ async function resolveProjectEntry(opts) {
3623
3672
  const cwd = opts.cwd ?? process.cwd();
3624
3673
  const resolvedCwd = await normalizeProjectPath(cwd);
3625
3674
  for (const entry2 of entries) {
3626
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path7.sep}`)) {
3675
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path8.sep}`)) {
3627
3676
  return entry2;
3628
3677
  }
3629
3678
  }
@@ -3774,8 +3823,24 @@ async function runSync(opts) {
3774
3823
 
3775
3824
  // src/cli.ts
3776
3825
  import { DivergenceTypeSchema } from "@neat.is/types";
3826
+ function isNpxInvocation() {
3827
+ if (process.env.npm_command === "exec") return true;
3828
+ const execpath = process.env.npm_execpath ?? "";
3829
+ if (execpath.includes("npx")) return true;
3830
+ const entry2 = process.argv[1] ?? "";
3831
+ if (entry2.includes("/_npx/") || entry2.includes("\\_npx\\")) return true;
3832
+ return false;
3833
+ }
3834
+ function commandPrefix() {
3835
+ return isNpxInvocation() ? "npx neat.is" : "neat";
3836
+ }
3777
3837
  function usage() {
3778
- console.log("usage: neat <command> [args] [--project <name>]");
3838
+ const neat = commandPrefix();
3839
+ console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
3840
+ console.log("");
3841
+ console.log(`usage: ${neat} <command> [args] [--project <name>]`);
3842
+ console.log("");
3843
+ console.log(`Run \`${neat}\` with no command from inside your project to go zero-to-graph in one step.`);
3779
3844
  console.log("");
3780
3845
  console.log("lifecycle commands:");
3781
3846
  console.log(" init <path> One-time install: discover, extract, register, plan SDK install.");
@@ -3794,6 +3859,8 @@ function usage() {
3794
3859
  console.log(" uninstall <name>");
3795
3860
  console.log(" Remove a project from the registry. Does not touch");
3796
3861
  console.log(" neat-out/, policy.json, or any user file.");
3862
+ console.log(" prune Drop registry entries whose path is gone from disk.");
3863
+ console.log(" Flags: --json emit the removed list as JSON");
3797
3864
  console.log(" version Print the installed @neat.is/core version and exit.");
3798
3865
  console.log(" Aliases: --version, -v.");
3799
3866
  console.log(" skill Install or print the Claude Code MCP drop-in.");
@@ -3815,30 +3882,30 @@ function usage() {
3815
3882
  console.log("");
3816
3883
  console.log("query commands (mirror the MCP tools, ADR-050):");
3817
3884
  console.log(" root-cause <node-id> Walk inbound edges to find what broke first.");
3818
- console.log(" example: neat root-cause service:<name>");
3885
+ console.log(` example: ${neat} root-cause service:<name>`);
3819
3886
  console.log(" blast-radius <node-id> BFS outbound \u2014 what would break if this dies.");
3820
- console.log(" example: neat blast-radius database:<host>");
3887
+ console.log(` example: ${neat} blast-radius database:<host>`);
3821
3888
  console.log(" dependencies <node-id> Transitive outbound dependencies.");
3822
3889
  console.log(" Flags: --depth N (default 3, max 10)");
3823
- console.log(" example: neat dependencies service:<name> --depth 2");
3890
+ console.log(` example: ${neat} dependencies service:<name> --depth 2`);
3824
3891
  console.log(" observed-dependencies <node-id> OBSERVED-only outbound edges (runtime traffic).");
3825
- console.log(" example: neat observed-dependencies service:<name>");
3892
+ console.log(` example: ${neat} observed-dependencies service:<name>`);
3826
3893
  console.log(" incidents [<node-id>] Recent error events; per-node when an id is given.");
3827
3894
  console.log(" Flags: --limit N (default 20)");
3828
- console.log(" example: neat incidents service:<name> --limit 5");
3895
+ console.log(` example: ${neat} incidents service:<name> --limit 5`);
3829
3896
  console.log(" search <query> Semantic (or substring) match on node names/ids.");
3830
- console.log(' example: neat search "checkout"');
3897
+ console.log(` example: ${neat} search "checkout"`);
3831
3898
  console.log(" diff --against <snapshot> Compare the live graph to a saved snapshot.");
3832
- console.log(" example: neat diff --against ./snapshots/baseline.json");
3899
+ console.log(` example: ${neat} diff --against ./snapshots/baseline.json`);
3833
3900
  console.log(" stale-edges Recent OBSERVED \u2192 STALE transitions.");
3834
3901
  console.log(" Flags: --limit N, --edge-type CALLS|CONNECTS_TO|...");
3835
- console.log(" example: neat stale-edges --edge-type CALLS");
3902
+ console.log(` example: ${neat} stale-edges --edge-type CALLS`);
3836
3903
  console.log(" policies Current policy violations.");
3837
3904
  console.log(" Flags: --node <id>, --hypothetical-action <json>");
3838
- console.log(" example: neat policies --node service:<name> --json");
3905
+ console.log(` example: ${neat} policies --node service:<name> --json`);
3839
3906
  console.log(" divergences Where code (EXTRACTED) and production (OBSERVED) disagree.");
3840
3907
  console.log(" Flags: --type <list>, --min-confidence <0..1>, --node <id>");
3841
- console.log(" example: neat divergences --min-confidence 0.7");
3908
+ console.log(` example: ${neat} divergences --min-confidence 0.7`);
3842
3909
  console.log("");
3843
3910
  console.log("flags:");
3844
3911
  console.log(' --project <name> Name the project this command targets. Default: "default".');
@@ -3853,6 +3920,7 @@ function usage() {
3853
3920
  console.log("");
3854
3921
  console.log("environment:");
3855
3922
  console.log(" NEAT_API_URL base URL for the core REST API (default http://localhost:8080)");
3923
+ console.log(" alias: NEAT_CORE_URL (the name the MCP server reads)");
3856
3924
  console.log(" NEAT_PROJECT project name when --project isn't passed");
3857
3925
  }
3858
3926
  var STRING_FLAGS = [
@@ -3982,40 +4050,10 @@ function assignFlag(out, field, value) {
3982
4050
  ;
3983
4051
  out[field] = value;
3984
4052
  }
3985
- function readPackageVersion() {
3986
- const here = typeof __dirname !== "undefined" ? __dirname : path8.dirname(fileURLToPath(import.meta.url));
3987
- const candidates = [
3988
- path8.resolve(here, "../package.json"),
3989
- path8.resolve(here, "../../package.json")
3990
- ];
3991
- for (const candidate of candidates) {
3992
- try {
3993
- const raw = readFileSync(candidate, "utf8");
3994
- const parsed = JSON.parse(raw);
3995
- if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
3996
- return parsed.version;
3997
- }
3998
- } catch {
3999
- }
4000
- }
4001
- return "unknown";
4002
- }
4003
4053
  function printVersion() {
4004
4054
  process.stdout.write(`${readPackageVersion()}
4005
4055
  `);
4006
4056
  }
4007
- function printBanner() {
4008
- console.log("\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");
4009
- console.log("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D");
4010
- console.log("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ");
4011
- console.log("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ");
4012
- console.log("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ");
4013
- console.log("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ");
4014
- console.log("");
4015
- console.log(" Network Expressive Architecting Tool");
4016
- console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
4017
- console.log("");
4018
- }
4019
4057
  function printDiscoveryReport(opts, services) {
4020
4058
  const languages = [...new Set(services.map((s) => s.node.language))].sort();
4021
4059
  const mode = opts.dryRun ? "dry-run" : opts.apply ? "apply" : "patch-only";
@@ -4063,12 +4101,12 @@ async function runInit(opts) {
4063
4101
  printDiscoveryReport(opts, services);
4064
4102
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
4065
4103
  const patch = renderPatch(sections);
4066
- const patchPath = path8.join(opts.scanPath, "neat.patch");
4104
+ const patchPath = path9.join(opts.scanPath, "neat.patch");
4067
4105
  if (opts.dryRun) {
4068
4106
  await fs7.writeFile(patchPath, patch, "utf8");
4069
4107
  written.push(patchPath);
4070
4108
  console.log(`dry-run: patch written to ${patchPath}`);
4071
- const gitignorePath = path8.join(opts.scanPath, ".gitignore");
4109
+ const gitignorePath = path9.join(opts.scanPath, ".gitignore");
4072
4110
  const gitignoreExists = await fs7.stat(gitignorePath).then(() => true).catch(() => false);
4073
4111
  const verb = gitignoreExists ? "append" : "create";
4074
4112
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
@@ -4080,9 +4118,9 @@ async function runInit(opts) {
4080
4118
  const graph = getGraph(graphKey);
4081
4119
  const projectPaths = pathsForProject(
4082
4120
  graphKey,
4083
- path8.join(opts.scanPath, "neat-out")
4121
+ path9.join(opts.scanPath, "neat-out")
4084
4122
  );
4085
- const errorsPath = path8.join(path8.dirname(opts.outPath), path8.basename(projectPaths.errorsPath));
4123
+ const errorsPath = path9.join(path9.dirname(opts.outPath), path9.basename(projectPaths.errorsPath));
4086
4124
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
4087
4125
  await saveGraphToDisk(graph, opts.outPath);
4088
4126
  written.push(opts.outPath);
@@ -4194,16 +4232,16 @@ var CLAUDE_SKILL_CONFIG = {
4194
4232
  command: "npx",
4195
4233
  args: ["-y", "@neat.is/mcp"],
4196
4234
  env: {
4197
- NEAT_API_URL: "http://localhost:8080"
4235
+ NEAT_CORE_URL: "http://localhost:8080"
4198
4236
  }
4199
4237
  }
4200
4238
  }
4201
4239
  };
4202
4240
  function claudeConfigPath() {
4203
4241
  const override = process.env.NEAT_CLAUDE_CONFIG;
4204
- if (override && override.length > 0) return path8.resolve(override);
4242
+ if (override && override.length > 0) return path9.resolve(override);
4205
4243
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
4206
- return path8.join(home, ".claude.json");
4244
+ return path9.join(home, ".claude.json");
4207
4245
  }
4208
4246
  async function runSkill(opts) {
4209
4247
  const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -4227,7 +4265,7 @@ async function runSkill(opts) {
4227
4265
  ...existing,
4228
4266
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
4229
4267
  };
4230
- await fs7.mkdir(path8.dirname(target), { recursive: true });
4268
+ await fs7.mkdir(path9.dirname(target), { recursive: true });
4231
4269
  await fs7.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
4232
4270
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
4233
4271
  console.log("restart Claude Code to pick up the new MCP server.");
@@ -4240,19 +4278,30 @@ async function runSkill(opts) {
4240
4278
  console.log("");
4241
4279
  console.log("Manual install: copy mcpServers.neat from --print-config into ~/.claude.json,");
4242
4280
  console.log("then restart Claude Code. See packages/claude-skill/SKILL.md for the tool list.");
4281
+ console.log("");
4282
+ console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 point it at a");
4283
+ console.log("non-default daemon by editing that value in the generated config.");
4243
4284
  return { exitCode: 0 };
4244
4285
  }
4245
4286
  async function main() {
4246
- const [, , cmd, ...rest] = process.argv;
4247
- if (!cmd || cmd === "-h" || cmd === "--help") {
4287
+ const argv = process.argv.slice(2);
4288
+ const cmd0 = argv[0];
4289
+ if (cmd0 === "-h" || cmd0 === "--help") {
4248
4290
  usage();
4249
4291
  process.exit(0);
4250
4292
  }
4251
- if (cmd === "--version" || cmd === "-v" || cmd === "version") {
4293
+ if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
4252
4294
  printVersion();
4253
4295
  process.exit(0);
4254
4296
  }
4255
- const parsed = parseArgs(rest);
4297
+ const argvParsed = parseArgs(argv);
4298
+ if (argvParsed.positional.length === 0) {
4299
+ const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
4300
+ if (orchestratorCode2 !== null && orchestratorCode2 !== 0) process.exit(orchestratorCode2);
4301
+ return;
4302
+ }
4303
+ const cmd = argvParsed.positional[0];
4304
+ const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
4256
4305
  const { positional, apply: apply3, dryRun, noInstall } = parsed;
4257
4306
  const project = parsed.project ?? DEFAULT_PROJECT;
4258
4307
  if (cmd === "init") {
@@ -4266,12 +4315,12 @@ async function main() {
4266
4315
  console.error("neat init: --apply and --dry-run are mutually exclusive");
4267
4316
  process.exit(2);
4268
4317
  }
4269
- const scanPath = path8.resolve(target);
4318
+ const scanPath = path9.resolve(target);
4270
4319
  const projectExplicit = parsed.project !== null;
4271
- const projectName = projectExplicit ? project : path8.basename(scanPath);
4320
+ const projectName = projectExplicit ? project : path9.basename(scanPath);
4272
4321
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
4273
- const fallback = pathsForProject(projectKey, path8.join(scanPath, "neat-out")).snapshotPath;
4274
- const outPath = path8.resolve(process.env.NEAT_OUT_PATH ?? fallback);
4322
+ const fallback = pathsForProject(projectKey, path9.join(scanPath, "neat-out")).snapshotPath;
4323
+ const outPath = path9.resolve(process.env.NEAT_OUT_PATH ?? fallback);
4275
4324
  const result = await runInit({
4276
4325
  scanPath,
4277
4326
  outPath,
@@ -4292,21 +4341,21 @@ async function main() {
4292
4341
  usage();
4293
4342
  process.exit(2);
4294
4343
  }
4295
- const scanPath = path8.resolve(target);
4344
+ const scanPath = path9.resolve(target);
4296
4345
  const stat = await fs7.stat(scanPath).catch(() => null);
4297
4346
  if (!stat || !stat.isDirectory()) {
4298
4347
  console.error(`neat watch: ${scanPath} is not a directory`);
4299
4348
  process.exit(2);
4300
4349
  }
4301
- const projectPaths = pathsForProject(project, path8.join(scanPath, "neat-out"));
4302
- const outPath = path8.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
4303
- const errorsPath = path8.resolve(
4304
- process.env.NEAT_ERRORS_PATH ?? path8.join(path8.dirname(outPath), path8.basename(projectPaths.errorsPath))
4350
+ const projectPaths = pathsForProject(project, path9.join(scanPath, "neat-out"));
4351
+ const outPath = path9.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
4352
+ const errorsPath = path9.resolve(
4353
+ process.env.NEAT_ERRORS_PATH ?? path9.join(path9.dirname(outPath), path9.basename(projectPaths.errorsPath))
4305
4354
  );
4306
- const staleEventsPath = path8.resolve(
4307
- process.env.NEAT_STALE_EVENTS_PATH ?? path8.join(path8.dirname(outPath), path8.basename(projectPaths.staleEventsPath))
4355
+ const staleEventsPath = path9.resolve(
4356
+ process.env.NEAT_STALE_EVENTS_PATH ?? path9.join(path9.dirname(outPath), path9.basename(projectPaths.staleEventsPath))
4308
4357
  );
4309
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path8.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
4358
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path9.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
4310
4359
  const handle = await startWatch(getGraph(project), {
4311
4360
  scanPath,
4312
4361
  outPath,
@@ -4399,6 +4448,19 @@ async function main() {
4399
4448
  console.log("note: neat-out/, policy.json, and other files at the project path were left in place.");
4400
4449
  return;
4401
4450
  }
4451
+ if (cmd === "prune") {
4452
+ const removed = await pruneRegistry({ ttlMs: 0 });
4453
+ if (parsed.json) {
4454
+ console.log(JSON.stringify(removed.map((p) => ({ name: p.name, path: p.path })), null, 2));
4455
+ return;
4456
+ }
4457
+ if (removed.length === 0) {
4458
+ console.log("nothing to prune \u2014 every registered project path still exists.");
4459
+ return;
4460
+ }
4461
+ console.log(`pruned ${removed.length} project${removed.length === 1 ? "" : "s"}: ${removed.map((p) => p.name).join(", ")}`);
4462
+ return;
4463
+ }
4402
4464
  if (cmd === "deploy") {
4403
4465
  const artifact = await runDeploy();
4404
4466
  const block = renderOtelEnvBlock2(artifact.token);
@@ -4452,11 +4514,11 @@ async function main() {
4452
4514
  process.exit(1);
4453
4515
  }
4454
4516
  async function tryOrchestrator(cmd, parsed) {
4455
- const scanPath = path8.resolve(cmd);
4517
+ const scanPath = path9.resolve(cmd);
4456
4518
  const stat = await fs7.stat(scanPath).catch(() => null);
4457
4519
  if (!stat || !stat.isDirectory()) return null;
4458
4520
  const projectExplicit = parsed.project !== null;
4459
- const projectName = projectExplicit ? parsed.project : path8.basename(scanPath);
4521
+ const projectName = projectExplicit ? parsed.project : path9.basename(scanPath);
4460
4522
  const result = await runOrchestrator({
4461
4523
  scanPath,
4462
4524
  project: projectName,
@@ -4486,12 +4548,44 @@ function resolveProjectFlag(parsed) {
4486
4548
  if (env && env.length > 0 && env !== DEFAULT_PROJECT) return env;
4487
4549
  return void 0;
4488
4550
  }
4551
+ var ProjectResolutionError = class extends Error {
4552
+ constructor(message, exitCode = 2) {
4553
+ super(message);
4554
+ this.exitCode = exitCode;
4555
+ this.name = "ProjectResolutionError";
4556
+ }
4557
+ exitCode;
4558
+ };
4559
+ async function resolveProjectForVerb(client, parsed) {
4560
+ const explicit = resolveProjectFlag(parsed);
4561
+ if (explicit) return explicit;
4562
+ const projects = await client.get("/projects");
4563
+ if (projects.some((p) => p.name === DEFAULT_PROJECT)) {
4564
+ return void 0;
4565
+ }
4566
+ if (projects.length === 1) {
4567
+ return projects[0].name;
4568
+ }
4569
+ if (projects.length === 0) {
4570
+ throw new ProjectResolutionError(
4571
+ "No projects are registered with the daemon yet. Run `npx neat.is` in a repo to build a graph first, then re-run this command."
4572
+ );
4573
+ }
4574
+ const names = projects.map((p) => p.name).sort().map((n) => ` ${n}`).join("\n");
4575
+ throw new ProjectResolutionError(
4576
+ `Several projects are registered and none is named "default", so I can't pick one for you.
4577
+ Pass --project <name> to choose:
4578
+ ${names}`
4579
+ );
4580
+ }
4581
+ function resolveDaemonUrl() {
4582
+ return process.env.NEAT_API_URL ?? process.env.NEAT_CORE_URL ?? "http://localhost:8080";
4583
+ }
4489
4584
  async function runQueryVerb(cmd, parsed) {
4490
- const baseUrl = process.env.NEAT_API_URL ?? "http://localhost:8080";
4585
+ const baseUrl = resolveDaemonUrl();
4491
4586
  const client = createHttpClient(baseUrl, resolveAuthToken());
4492
- const project = resolveProjectFlag(parsed);
4493
4587
  const positional = parsed.positional;
4494
- let work;
4588
+ let makeWork;
4495
4589
  switch (cmd) {
4496
4590
  case "root-cause": {
4497
4591
  const node = positional[0];
@@ -4499,7 +4593,7 @@ async function runQueryVerb(cmd, parsed) {
4499
4593
  console.error("neat root-cause: missing <node-id>");
4500
4594
  return 2;
4501
4595
  }
4502
- work = runRootCause(client, {
4596
+ makeWork = (project) => runRootCause(client, {
4503
4597
  errorNode: node,
4504
4598
  ...parsed.errorId ? { errorId: parsed.errorId } : {},
4505
4599
  ...project ? { project } : {}
@@ -4512,7 +4606,7 @@ async function runQueryVerb(cmd, parsed) {
4512
4606
  console.error("neat blast-radius: missing <node-id>");
4513
4607
  return 2;
4514
4608
  }
4515
- work = runBlastRadius(client, {
4609
+ makeWork = (project) => runBlastRadius(client, {
4516
4610
  nodeId: node,
4517
4611
  ...parsed.depth !== null ? { depth: parsed.depth } : {},
4518
4612
  ...project ? { project } : {}
@@ -4525,7 +4619,7 @@ async function runQueryVerb(cmd, parsed) {
4525
4619
  console.error("neat dependencies: missing <node-id>");
4526
4620
  return 2;
4527
4621
  }
4528
- work = runDependencies(client, {
4622
+ makeWork = (project) => runDependencies(client, {
4529
4623
  nodeId: node,
4530
4624
  ...parsed.depth !== null ? { depth: parsed.depth } : {},
4531
4625
  ...project ? { project } : {}
@@ -4538,14 +4632,14 @@ async function runQueryVerb(cmd, parsed) {
4538
4632
  console.error("neat observed-dependencies: missing <node-id>");
4539
4633
  return 2;
4540
4634
  }
4541
- work = runObservedDependencies(client, {
4635
+ makeWork = (project) => runObservedDependencies(client, {
4542
4636
  nodeId: node,
4543
4637
  ...project ? { project } : {}
4544
4638
  });
4545
4639
  break;
4546
4640
  }
4547
4641
  case "incidents": {
4548
- work = runIncidents(client, {
4642
+ makeWork = (project) => runIncidents(client, {
4549
4643
  ...positional[0] ? { nodeId: positional[0] } : {},
4550
4644
  ...parsed.limit !== null ? { limit: parsed.limit } : {},
4551
4645
  ...project ? { project } : {}
@@ -4558,7 +4652,7 @@ async function runQueryVerb(cmd, parsed) {
4558
4652
  console.error("neat search: missing <query>");
4559
4653
  return 2;
4560
4654
  }
4561
- work = runSearch(client, { query: q, ...project ? { project } : {} });
4655
+ makeWork = (project) => runSearch(client, { query: q, ...project ? { project } : {} });
4562
4656
  break;
4563
4657
  }
4564
4658
  case "diff": {
@@ -4567,14 +4661,14 @@ async function runQueryVerb(cmd, parsed) {
4567
4661
  console.error("neat diff: --against <snapshot-path> is required");
4568
4662
  return 2;
4569
4663
  }
4570
- work = runDiff(client, {
4664
+ makeWork = (project) => runDiff(client, {
4571
4665
  againstSnapshot: against,
4572
4666
  ...project ? { project } : {}
4573
4667
  });
4574
4668
  break;
4575
4669
  }
4576
4670
  case "stale-edges": {
4577
- work = runStaleEdges(client, {
4671
+ makeWork = (project) => runStaleEdges(client, {
4578
4672
  ...parsed.limit !== null ? { limit: parsed.limit } : {},
4579
4673
  ...parsed.edgeType ? { edgeType: parsed.edgeType } : {},
4580
4674
  ...project ? { project } : {}
@@ -4593,7 +4687,7 @@ async function runQueryVerb(cmd, parsed) {
4593
4687
  return 2;
4594
4688
  }
4595
4689
  }
4596
- work = runPolicies(client, {
4690
+ makeWork = (project) => runPolicies(client, {
4597
4691
  ...parsed.node ? { nodeId: parsed.node } : {},
4598
4692
  ...hypothetical ? { hypotheticalAction: hypothetical } : {},
4599
4693
  ...project ? { project } : {}
@@ -4617,7 +4711,7 @@ async function runQueryVerb(cmd, parsed) {
4617
4711
  }
4618
4712
  typeFilter = out;
4619
4713
  }
4620
- work = runDivergences(client, {
4714
+ makeWork = (project) => runDivergences(client, {
4621
4715
  ...typeFilter ? { type: typeFilter } : {},
4622
4716
  ...parsed.minConfidence !== null ? { minConfidence: parsed.minConfidence } : {},
4623
4717
  ...parsed.node ? { node: parsed.node } : {},
@@ -4630,16 +4724,21 @@ async function runQueryVerb(cmd, parsed) {
4630
4724
  return 2;
4631
4725
  }
4632
4726
  try {
4633
- const result = await work;
4727
+ const project = await resolveProjectForVerb(client, parsed);
4728
+ const result = await makeWork(project);
4634
4729
  if (parsed.json) process.stdout.write(formatJson(result) + "\n");
4635
4730
  else process.stdout.write(formatHuman(result) + "\n");
4636
4731
  return 0;
4637
4732
  } catch (err) {
4733
+ if (err instanceof ProjectResolutionError) {
4734
+ console.error(`neat ${cmd}: ${err.message}`);
4735
+ return err.exitCode;
4736
+ }
4638
4737
  if (err instanceof HttpError) {
4639
4738
  const detail = err.responseBody.length > 0 ? err.responseBody : err.message;
4640
4739
  console.error(`neat ${cmd}: ${detail.trim()}`);
4641
4740
  } else if (err instanceof TransportError) {
4642
- console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (NEAT_API_URL=${process.env.NEAT_API_URL ?? "http://localhost:8080"})`);
4741
+ console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (NEAT_API_URL=${resolveDaemonUrl()})`);
4643
4742
  } else {
4644
4743
  console.error(`neat ${cmd}: ${err.message}`);
4645
4744
  }
@@ -4655,12 +4754,18 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
4655
4754
  }
4656
4755
  export {
4657
4756
  CLAUDE_SKILL_CONFIG,
4757
+ ProjectResolutionError,
4658
4758
  QUERY_VERBS,
4759
+ commandPrefix,
4760
+ isNpxInvocation,
4761
+ main,
4659
4762
  parseArgs,
4660
4763
  printBanner,
4661
4764
  readPackageVersion,
4765
+ resolveProjectForVerb,
4662
4766
  runInit,
4663
4767
  runQueryVerb,
4664
- runSkill
4768
+ runSkill,
4769
+ usage
4665
4770
  };
4666
4771
  //# sourceMappingURL=cli.js.map