@neat.is/core 0.7.3 → 0.7.5

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
@@ -6,7 +6,7 @@ import {
6
6
  resolveHost,
7
7
  resolveNeatVersion,
8
8
  writeDaemonRecord
9
- } from "./chunk-3SBEY67S.js";
9
+ } from "./chunk-MYLUXVXE.js";
10
10
  import {
11
11
  buildSearchIndex
12
12
  } from "./chunk-BIY46Q6U.js";
@@ -23,8 +23,10 @@ import {
23
23
  addImports,
24
24
  addInfra,
25
25
  addProject,
26
+ addRoutes,
26
27
  addServiceAliases,
27
28
  addServiceNodes,
29
+ addSymbols,
28
30
  attachGraphToEventBus,
29
31
  autoSlugConnectorId,
30
32
  buildApi,
@@ -72,7 +74,7 @@ import {
72
74
  startStalenessLoop,
73
75
  upsertConnectorEntry,
74
76
  validateConnectorEntry
75
- } from "./chunk-BY6ZP5WA.js";
77
+ } from "./chunk-KB4HRB6N.js";
76
78
  import {
77
79
  startOtelGrpcReceiver
78
80
  } from "./chunk-6H757ZNM.js";
@@ -86,9 +88,9 @@ import {
86
88
  } from "./chunk-P2ZEKJ35.js";
87
89
 
88
90
  // src/cli.ts
89
- import path11 from "path";
90
- import os2 from "os";
91
- import { promises as fs9 } from "fs";
91
+ import path13 from "path";
92
+ import os4 from "os";
93
+ import { promises as fs11 } from "fs";
92
94
 
93
95
  // src/banner.ts
94
96
  import path from "path";
@@ -263,9 +265,11 @@ var ALL_PHASES = [
263
265
  "services",
264
266
  "aliases",
265
267
  "files",
268
+ "symbols",
266
269
  "imports",
267
270
  "databases",
268
271
  "configs",
272
+ "routes",
269
273
  "calls",
270
274
  "infra"
271
275
  ];
@@ -288,7 +292,9 @@ function classifyChange(relPath) {
288
292
  }
289
293
  if (/\.(?:js|jsx|mjs|cjs|ts|tsx|py)$/.test(base)) {
290
294
  phases.add("files");
295
+ phases.add("symbols");
291
296
  phases.add("imports");
297
+ phases.add("routes");
292
298
  phases.add("calls");
293
299
  }
294
300
  if (/\.ya?ml$/.test(base) && !/^docker-compose.*\.ya?ml$/.test(base)) {
@@ -315,6 +321,11 @@ async function runExtractPhases(graph, scanPath, phases, project = DEFAULT_PROJE
315
321
  nodesAdded += r.nodesAdded;
316
322
  edgesAdded += r.edgesAdded;
317
323
  }
324
+ if (phases.has("symbols")) {
325
+ const r = await addSymbols(graph, services);
326
+ nodesAdded += r.nodesAdded;
327
+ edgesAdded += r.edgesAdded;
328
+ }
318
329
  if (phases.has("imports")) {
319
330
  const r = await addImports(graph, services);
320
331
  nodesAdded += r.nodesAdded;
@@ -330,6 +341,11 @@ async function runExtractPhases(graph, scanPath, phases, project = DEFAULT_PROJE
330
341
  nodesAdded += r.nodesAdded;
331
342
  edgesAdded += r.edgesAdded;
332
343
  }
344
+ if (phases.has("routes")) {
345
+ const r = await addRoutes(graph, services);
346
+ nodesAdded += r.nodesAdded;
347
+ edgesAdded += r.edgesAdded;
348
+ }
333
349
  if (phases.has("calls")) {
334
350
  const r = await addCallEdges(graph, services);
335
351
  nodesAdded += r.nodesAdded;
@@ -4455,6 +4471,453 @@ async function runHooksCommand(args) {
4455
4471
  }
4456
4472
  }
4457
4473
 
4474
+ // src/codex-cli.ts
4475
+ import path10 from "path";
4476
+ import os2 from "os";
4477
+ import { promises as fs9 } from "fs";
4478
+ import { isDeepStrictEqual } from "util";
4479
+ import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
4480
+ var CODEX_MCP_SERVER = {
4481
+ command: "npx",
4482
+ args: ["-y", "@neat.is/mcp"],
4483
+ env: { NEAT_CORE_URL: "http://localhost:8080" }
4484
+ };
4485
+ var CODEX_NEAT_BLOCK = [
4486
+ "[mcp_servers.neat]",
4487
+ 'command = "npx"',
4488
+ 'args = ["-y", "@neat.is/mcp"]',
4489
+ 'env = { NEAT_CORE_URL = "http://localhost:8080" }'
4490
+ ].join("\n");
4491
+ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
4492
+ var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
4493
+ function codexConfigPath() {
4494
+ const override = process.env.NEAT_CODEX_CONFIG;
4495
+ if (override && override.length > 0) return path10.resolve(override);
4496
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
4497
+ return path10.join(home, ".codex", "config.toml");
4498
+ }
4499
+ function agentsFilePath() {
4500
+ const override = process.env.NEAT_CODEX_AGENTS;
4501
+ if (override && override.length > 0) return path10.resolve(override);
4502
+ return path10.join(process.cwd(), "AGENTS.md");
4503
+ }
4504
+ function isTableHeader(line) {
4505
+ return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
4506
+ }
4507
+ function tableName(line) {
4508
+ return line.trim().replace(/^\[\[?/, "").replace(/\]\]?$/, "").trim();
4509
+ }
4510
+ function isNeatHeader(line) {
4511
+ return isTableHeader(line) && tableName(line) === "mcp_servers.neat";
4512
+ }
4513
+ function isNeatChildHeader(line) {
4514
+ if (!isTableHeader(line)) return false;
4515
+ const name = tableName(line);
4516
+ return name === "mcp_servers.neat" || name.startsWith("mcp_servers.neat.");
4517
+ }
4518
+ function upsertCodexConfig(raw) {
4519
+ const trimmed = raw.trim();
4520
+ const parsed = trimmed.length > 0 ? parseToml(raw) : {};
4521
+ const existingServers = parsed.mcp_servers ?? {};
4522
+ const spliced = spliceNeatBlock(raw);
4523
+ let text;
4524
+ if (verifyPreserved(raw, spliced, parsed)) {
4525
+ text = spliced;
4526
+ } else {
4527
+ const merged = {
4528
+ ...parsed,
4529
+ mcp_servers: { ...existingServers, neat: CODEX_MCP_SERVER }
4530
+ };
4531
+ text = stringifyToml(merged);
4532
+ if (!text.endsWith("\n")) text += "\n";
4533
+ }
4534
+ return { text, changed: text !== raw };
4535
+ }
4536
+ function spliceNeatBlock(raw) {
4537
+ const block = CODEX_NEAT_BLOCK;
4538
+ const lines = raw.length > 0 ? raw.split("\n") : [];
4539
+ const start = lines.findIndex(isNeatHeader);
4540
+ if (start === -1) {
4541
+ const base = raw.replace(/\n+$/, "");
4542
+ return base.length > 0 ? `${base}
4543
+
4544
+ ${block}
4545
+ ` : `${block}
4546
+ `;
4547
+ }
4548
+ let end = start + 1;
4549
+ for (; ; ) {
4550
+ while (end < lines.length && !isTableHeader(lines[end])) end++;
4551
+ if (end < lines.length && isNeatChildHeader(lines[end])) {
4552
+ end++;
4553
+ continue;
4554
+ }
4555
+ break;
4556
+ }
4557
+ const before = lines.slice(0, start).join("\n").replace(/\n*$/, "");
4558
+ const after = lines.slice(end).join("\n").replace(/^\n*/, "");
4559
+ let text = "";
4560
+ if (before.length > 0) text += `${before}
4561
+
4562
+ `;
4563
+ text += `${block}
4564
+ `;
4565
+ if (after.length > 0) text += `
4566
+ ${after}`;
4567
+ return `${text.replace(/\n*$/, "")}
4568
+ `;
4569
+ }
4570
+ function verifyPreserved(raw, spliced, original) {
4571
+ let next;
4572
+ try {
4573
+ next = parseToml(spliced);
4574
+ } catch {
4575
+ return false;
4576
+ }
4577
+ const origServers = original.mcp_servers ?? {};
4578
+ const nextServers = next.mcp_servers ?? {};
4579
+ if (!isDeepStrictEqual(nextServers.neat, CODEX_MCP_SERVER)) return false;
4580
+ for (const name of Object.keys(origServers)) {
4581
+ if (name === "neat") continue;
4582
+ if (!isDeepStrictEqual(nextServers[name], origServers[name])) return false;
4583
+ }
4584
+ for (const name of Object.keys(nextServers)) {
4585
+ if (name !== "neat" && !(name in origServers)) return false;
4586
+ }
4587
+ for (const key of Object.keys(original)) {
4588
+ if (key === "mcp_servers") continue;
4589
+ if (!isDeepStrictEqual(next[key], original[key])) return false;
4590
+ }
4591
+ for (const key of Object.keys(next)) {
4592
+ if (key !== "mcp_servers" && !(key in original)) return false;
4593
+ }
4594
+ return true;
4595
+ }
4596
+ function agentsBlock(guide) {
4597
+ return `${NEAT_GRAPH_FIRST_START}
4598
+ ${guide.replace(/\s+$/, "")}
4599
+ ${NEAT_GRAPH_FIRST_END}
4600
+ `;
4601
+ }
4602
+ function upsertAgents(raw, guide) {
4603
+ const block = agentsBlock(guide);
4604
+ if (raw.length === 0) return { text: block, changed: true };
4605
+ const startIdx = raw.indexOf(NEAT_GRAPH_FIRST_START);
4606
+ const endIdx = raw.indexOf(NEAT_GRAPH_FIRST_END);
4607
+ if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
4608
+ const before = raw.slice(0, startIdx);
4609
+ const after = raw.slice(endIdx + NEAT_GRAPH_FIRST_END.length);
4610
+ const text2 = `${before}${block.replace(/\n+$/, "")}${after}`;
4611
+ return { text: text2, changed: text2 !== raw };
4612
+ }
4613
+ const base = raw.replace(/\n+$/, "");
4614
+ const text = base.length > 0 ? `${base}
4615
+
4616
+ ${block}` : block;
4617
+ return { text, changed: text !== raw };
4618
+ }
4619
+ async function readGuide() {
4620
+ return readSkillAsset(GUIDE_FILENAME);
4621
+ }
4622
+ async function runCodex(opts) {
4623
+ if (opts.printConfig) {
4624
+ process.stdout.write(`${CODEX_NEAT_BLOCK}
4625
+ `);
4626
+ return { exitCode: 0 };
4627
+ }
4628
+ if (opts.printGuide) {
4629
+ process.stdout.write(agentsBlock(await readGuide()));
4630
+ return { exitCode: 0 };
4631
+ }
4632
+ const configPath = codexConfigPath();
4633
+ const agentsPath = agentsFilePath();
4634
+ let configRaw = "";
4635
+ try {
4636
+ configRaw = await fs9.readFile(configPath, "utf8");
4637
+ } catch (err) {
4638
+ if (err.code !== "ENOENT") {
4639
+ console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
4640
+ return { exitCode: 1 };
4641
+ }
4642
+ }
4643
+ let agentsRaw = "";
4644
+ try {
4645
+ agentsRaw = await fs9.readFile(agentsPath, "utf8");
4646
+ } catch (err) {
4647
+ if (err.code !== "ENOENT") {
4648
+ console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
4649
+ return { exitCode: 1 };
4650
+ }
4651
+ }
4652
+ let config;
4653
+ try {
4654
+ config = upsertCodexConfig(configRaw);
4655
+ } catch (err) {
4656
+ console.error(
4657
+ `neat codex: ${configPath} is not valid TOML \u2014 ${err.message}`
4658
+ );
4659
+ console.error("neat codex: fix the file and re-run; nothing was written.");
4660
+ return { exitCode: 1 };
4661
+ }
4662
+ const guide = await readGuide();
4663
+ const agents = upsertAgents(agentsRaw, guide);
4664
+ if (!opts.apply) {
4665
+ console.log("neat codex \u2014 plan (nothing written; re-run with --apply to write)");
4666
+ console.log("");
4667
+ console.log(` Codex MCP config: ${configPath}`);
4668
+ console.log(
4669
+ config.changed ? ` ${configRaw ? "update" : "create"} the [mcp_servers.neat] table:` : " already up to date \u2014 [mcp_servers.neat] matches"
4670
+ );
4671
+ if (config.changed) {
4672
+ for (const line of CODEX_NEAT_BLOCK.split("\n")) console.log(` ${line}`);
4673
+ }
4674
+ console.log("");
4675
+ console.log(` Project instructions: ${agentsPath}`);
4676
+ console.log(
4677
+ agents.changed ? ` ${agentsRaw ? "update" : "create"} the graph-first block (between ${NEAT_GRAPH_FIRST_START} markers)` : " already up to date \u2014 graph-first block matches"
4678
+ );
4679
+ console.log("");
4680
+ console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 edit that value in");
4681
+ console.log("the generated table to point Codex at a non-default daemon.");
4682
+ return { exitCode: 0 };
4683
+ }
4684
+ if (config.changed) {
4685
+ await fs9.mkdir(path10.dirname(configPath), { recursive: true });
4686
+ await fs9.writeFile(configPath, config.text, "utf8");
4687
+ console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
4688
+ } else {
4689
+ console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
4690
+ }
4691
+ if (agents.changed) {
4692
+ await fs9.mkdir(path10.dirname(agentsPath), { recursive: true });
4693
+ await fs9.writeFile(agentsPath, agents.text, "utf8");
4694
+ console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
4695
+ } else {
4696
+ console.log(`neat codex: ${agentsPath} already has the graph-first block`);
4697
+ }
4698
+ console.log("");
4699
+ console.log("restart Codex to pick up the new MCP server. NEAT_CORE_URL in the table");
4700
+ console.log("points the server at the local daemon \u2014 edit it for a non-default one.");
4701
+ return { exitCode: 0 };
4702
+ }
4703
+ function usage2() {
4704
+ console.log("neat codex \u2014 install NEAT into the OpenAI Codex CLI (MCP server + AGENTS.md)");
4705
+ console.log("");
4706
+ console.log(" (no flag) plan: print what would change, write nothing");
4707
+ console.log(" --apply add [mcp_servers.neat] to ~/.codex/config.toml and write");
4708
+ console.log(" the graph-first block into ./AGENTS.md, merging into both");
4709
+ console.log(" without touching your other servers or instructions");
4710
+ console.log(" --print-config print the [mcp_servers.neat] TOML block to stdout");
4711
+ console.log(" --print-guide print the AGENTS.md graph-first block to stdout");
4712
+ console.log("");
4713
+ console.log("Existing config is preserved and a re-run is a no-op. A malformed");
4714
+ console.log("config.toml is a clear error with no partial write.");
4715
+ }
4716
+ async function runCodexCommand(args) {
4717
+ const opts = { apply: false, printConfig: false, printGuide: false };
4718
+ for (const arg of args) {
4719
+ switch (arg) {
4720
+ case "--apply":
4721
+ opts.apply = true;
4722
+ break;
4723
+ case "--print-config":
4724
+ opts.printConfig = true;
4725
+ break;
4726
+ case "--print-guide":
4727
+ opts.printGuide = true;
4728
+ break;
4729
+ case "-h":
4730
+ case "--help":
4731
+ usage2();
4732
+ return 0;
4733
+ default:
4734
+ console.error(`neat codex: unknown flag "${arg}"`);
4735
+ usage2();
4736
+ return 2;
4737
+ }
4738
+ }
4739
+ try {
4740
+ const { exitCode } = await runCodex(opts);
4741
+ return exitCode;
4742
+ } catch (err) {
4743
+ console.error(err.message);
4744
+ return 1;
4745
+ }
4746
+ }
4747
+
4748
+ // src/editors-cli.ts
4749
+ import path11 from "path";
4750
+ import os3 from "os";
4751
+ import { promises as fs10 } from "fs";
4752
+ var NEAT_MCP_SERVER = {
4753
+ command: "npx",
4754
+ args: ["-y", "@neat.is/mcp"]
4755
+ };
4756
+ var GRAPH_FIRST_MARKER_OPEN = "<!-- neat:graph-first -->";
4757
+ var GRAPH_FIRST_MARKER_CLOSE = "<!-- /neat:graph-first -->";
4758
+ function homeDir() {
4759
+ return process.env.HOME ?? process.env.USERPROFILE ?? os3.homedir();
4760
+ }
4761
+ var CURSOR_CLIENT = {
4762
+ id: "cursor",
4763
+ label: "Cursor",
4764
+ docsUrl: "https://docs.cursor.com/context/mcp",
4765
+ mcpConfigPath: () => {
4766
+ const override = process.env.NEAT_CURSOR_CONFIG;
4767
+ if (override && override.length > 0) return path11.resolve(override);
4768
+ return path11.join(homeDir(), ".cursor", "mcp.json");
4769
+ },
4770
+ // Cursor still reads a single `.cursorrules` at the project root (the modern
4771
+ // `.cursor/rules/*.mdc` split is one-rule-per-file with frontmatter — a worse
4772
+ // fit for a marker-fenced block). GRAPH_FIRST.md names this file directly.
4773
+ rulesFileName: ".cursorrules"
4774
+ };
4775
+ var DEVIN_CLIENT = {
4776
+ id: "devin",
4777
+ label: "Devin Desktop (Cascade)",
4778
+ docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
4779
+ mcpConfigPath: () => {
4780
+ const override = process.env.NEAT_DEVIN_CONFIG;
4781
+ if (override && override.length > 0) return path11.resolve(override);
4782
+ return path11.join(homeDir(), ".codeium", "windsurf", "mcp_config.json");
4783
+ },
4784
+ rulesFileName: ".windsurfrules"
4785
+ };
4786
+ function mergeMcpConfig(existing) {
4787
+ const servers = existing.mcpServers ?? {};
4788
+ const already = JSON.stringify(servers.neat) === JSON.stringify(NEAT_MCP_SERVER);
4789
+ const merged = {
4790
+ ...existing,
4791
+ mcpServers: { ...servers, neat: NEAT_MCP_SERVER }
4792
+ };
4793
+ return { merged, changed: !already };
4794
+ }
4795
+ function escapeRegExp(s) {
4796
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4797
+ }
4798
+ function buildGuidanceBlock(guide) {
4799
+ return `${GRAPH_FIRST_MARKER_OPEN}
4800
+ ${guide.trim()}
4801
+ ${GRAPH_FIRST_MARKER_CLOSE}
4802
+ `;
4803
+ }
4804
+ function mergeRulesFile(existing, block) {
4805
+ const region = new RegExp(
4806
+ `${escapeRegExp(GRAPH_FIRST_MARKER_OPEN)}[\\s\\S]*?${escapeRegExp(GRAPH_FIRST_MARKER_CLOSE)}\\n?`
4807
+ );
4808
+ if (region.test(existing)) return existing.replace(region, block);
4809
+ if (existing.trim().length === 0) return block;
4810
+ return `${existing.replace(/\s+$/, "")}
4811
+
4812
+ ${block}`;
4813
+ }
4814
+ async function runEditorInstall(client, opts) {
4815
+ const mcpPath = client.mcpConfigPath();
4816
+ const rulesPath = path11.join(opts.projectDir, client.rulesFileName);
4817
+ let existingMcp = {};
4818
+ try {
4819
+ existingMcp = JSON.parse(await fs10.readFile(mcpPath, "utf8"));
4820
+ } catch (err) {
4821
+ const e = err;
4822
+ if (e.code === "ENOENT") {
4823
+ existingMcp = {};
4824
+ } else if (err instanceof SyntaxError) {
4825
+ console.error(
4826
+ `neat ${client.id}: ${mcpPath} is not valid JSON \u2014 ${e.message}. Fix it (or move it aside) and re-run; nothing was written.`
4827
+ );
4828
+ return { exitCode: 1 };
4829
+ } else {
4830
+ console.error(`neat ${client.id}: failed to read ${mcpPath} \u2014 ${e.message}`);
4831
+ return { exitCode: 1 };
4832
+ }
4833
+ }
4834
+ let existingRules = "";
4835
+ try {
4836
+ existingRules = await fs10.readFile(rulesPath, "utf8");
4837
+ } catch (err) {
4838
+ if (err.code !== "ENOENT") {
4839
+ console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
4840
+ return { exitCode: 1 };
4841
+ }
4842
+ }
4843
+ const guide = await readSkillAsset(GUIDE_FILENAME);
4844
+ const block = buildGuidanceBlock(guide);
4845
+ const { merged, changed: mcpChanged } = mergeMcpConfig(existingMcp);
4846
+ const mcpJson = JSON.stringify(merged, null, 2) + "\n";
4847
+ const newRules = mergeRulesFile(existingRules, block);
4848
+ const rulesChanged = newRules !== existingRules;
4849
+ if (!opts.apply) {
4850
+ console.log(`neat ${client.id} \u2014 wire NEAT into ${client.label} (plan; nothing written)`);
4851
+ console.log("");
4852
+ console.log(`MCP server \u2192 ${mcpPath}`);
4853
+ console.log(
4854
+ mcpChanged ? " would add mcpServers.neat:" : " mcpServers.neat already present and current \u2014 no change:"
4855
+ );
4856
+ console.log(indent(JSON.stringify({ neat: NEAT_MCP_SERVER }, null, 2)));
4857
+ console.log("");
4858
+ console.log(`Graph-first guidance \u2192 ${rulesPath}`);
4859
+ console.log(
4860
+ rulesChanged ? existingRules.includes(GRAPH_FIRST_MARKER_OPEN) ? " would refresh the neat:graph-first block:" : " would add the neat:graph-first block:" : " neat:graph-first block already present and current \u2014 no change."
4861
+ );
4862
+ if (rulesChanged) console.log(indent(block.trimEnd()));
4863
+ console.log("");
4864
+ console.log(`Re-run with --apply to write both files. Existing servers and rules are kept.`);
4865
+ return { exitCode: 0 };
4866
+ }
4867
+ await fs10.mkdir(path11.dirname(mcpPath), { recursive: true });
4868
+ await fs10.writeFile(mcpPath, mcpJson, "utf8");
4869
+ await fs10.mkdir(path11.dirname(rulesPath), { recursive: true });
4870
+ await fs10.writeFile(rulesPath, newRules, "utf8");
4871
+ console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
4872
+ console.log(` MCP server: ${mcpPath} (mcpServers.neat \u2192 npx -y @neat.is/mcp)`);
4873
+ console.log(` guidance: ${rulesPath} (neat:graph-first block)`);
4874
+ console.log("");
4875
+ console.log(`restart ${client.label} to pick up the MCP server. Point it at a non-default`);
4876
+ console.log(`daemon by setting NEAT_CORE_URL in the neat server's env in that config.`);
4877
+ return { exitCode: 0 };
4878
+ }
4879
+ function indent(text) {
4880
+ return text.split("\n").map((line) => line.length > 0 ? ` ${line}` : line).join("\n");
4881
+ }
4882
+ function usage3(client) {
4883
+ console.log(`neat ${client.id} \u2014 install NEAT's MCP server + graph-first guidance into ${client.label}`);
4884
+ console.log("");
4885
+ console.log(" --apply write the MCP config and the rules file (default: plan only)");
4886
+ console.log("");
4887
+ console.log("Writes NEAT's stdio MCP server (npx -y @neat.is/mcp) into");
4888
+ console.log(` ${client.mcpConfigPath()}`);
4889
+ console.log(`and the graph-first guidance block into ./${client.rulesFileName}, both`);
4890
+ console.log("additively \u2014 existing servers and rules are preserved, a re-run is a no-op.");
4891
+ console.log("");
4892
+ console.log(`See ${client.docsUrl} for ${client.label}'s MCP config format.`);
4893
+ }
4894
+ async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
4895
+ const client = clientId === "cursor" ? CURSOR_CLIENT : DEVIN_CLIENT;
4896
+ let apply4 = false;
4897
+ for (const arg of args) {
4898
+ switch (arg) {
4899
+ case "--apply":
4900
+ apply4 = true;
4901
+ break;
4902
+ case "-h":
4903
+ case "--help":
4904
+ usage3(client);
4905
+ return 0;
4906
+ default:
4907
+ console.error(`neat ${client.id}: unknown flag "${arg}"`);
4908
+ usage3(client);
4909
+ return 2;
4910
+ }
4911
+ }
4912
+ try {
4913
+ const { exitCode } = await runEditorInstall(client, { apply: apply4, projectDir });
4914
+ return exitCode;
4915
+ } catch (err) {
4916
+ console.error(err.message);
4917
+ return 1;
4918
+ }
4919
+ }
4920
+
4458
4921
  // src/monitor.ts
4459
4922
  import { EdgeType, parseEdgeId, Provenance as Provenance3 } from "@neat.is/types";
4460
4923
 
@@ -4484,10 +4947,10 @@ function createHttpClient(baseUrl, bearerToken) {
4484
4947
  const root = baseUrl.replace(/\/$/, "");
4485
4948
  const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
4486
4949
  return {
4487
- async get(path12) {
4950
+ async get(path14) {
4488
4951
  let res;
4489
4952
  try {
4490
- res = await fetch(`${root}${path12}`, {
4953
+ res = await fetch(`${root}${path14}`, {
4491
4954
  headers: { ...authHeader }
4492
4955
  });
4493
4956
  } catch (err) {
@@ -4499,16 +4962,16 @@ function createHttpClient(baseUrl, bearerToken) {
4499
4962
  const body = await res.text().catch(() => "");
4500
4963
  throw new HttpError(
4501
4964
  res.status,
4502
- `${res.status} ${res.statusText} on GET ${path12}: ${body}`,
4965
+ `${res.status} ${res.statusText} on GET ${path14}: ${body}`,
4503
4966
  body
4504
4967
  );
4505
4968
  }
4506
4969
  return await res.json();
4507
4970
  },
4508
- async post(path12, body) {
4971
+ async post(path14, body) {
4509
4972
  let res;
4510
4973
  try {
4511
- res = await fetch(`${root}${path12}`, {
4974
+ res = await fetch(`${root}${path14}`, {
4512
4975
  method: "POST",
4513
4976
  headers: { "content-type": "application/json", ...authHeader },
4514
4977
  body: JSON.stringify(body)
@@ -4522,7 +4985,7 @@ function createHttpClient(baseUrl, bearerToken) {
4522
4985
  const text = await res.text().catch(() => "");
4523
4986
  throw new HttpError(
4524
4987
  res.status,
4525
- `${res.status} ${res.statusText} on POST ${path12}: ${text}`,
4988
+ `${res.status} ${res.statusText} on POST ${path14}: ${text}`,
4526
4989
  text
4527
4990
  );
4528
4991
  }
@@ -4536,12 +4999,12 @@ function projectPath(project, suffix) {
4536
4999
  }
4537
5000
  async function runRootCause(client, input) {
4538
5001
  const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
4539
- const path12 = projectPath(
5002
+ const path14 = projectPath(
4540
5003
  input.project,
4541
5004
  `/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
4542
5005
  );
4543
5006
  try {
4544
- const result = await client.get(path12);
5007
+ const result = await client.get(path14);
4545
5008
  const arrowPath = result.traversalPath.join(" \u2190 ");
4546
5009
  const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
4547
5010
  const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
@@ -4567,12 +5030,12 @@ async function runRootCause(client, input) {
4567
5030
  }
4568
5031
  async function runBlastRadius(client, input) {
4569
5032
  const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
4570
- const path12 = projectPath(
5033
+ const path14 = projectPath(
4571
5034
  input.project,
4572
5035
  `/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
4573
5036
  );
4574
5037
  try {
4575
- const result = await client.get(path12);
5038
+ const result = await client.get(path14);
4576
5039
  if (result.totalAffected === 0) {
4577
5040
  return {
4578
5041
  summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
@@ -4606,12 +5069,12 @@ function formatBlastEntry(n) {
4606
5069
  }
4607
5070
  async function runDependencies(client, input) {
4608
5071
  const depth = input.depth ?? 3;
4609
- const path12 = projectPath(
5072
+ const path14 = projectPath(
4610
5073
  input.project,
4611
5074
  `/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
4612
5075
  );
4613
5076
  try {
4614
- const result = await client.get(path12);
5077
+ const result = await client.get(path14);
4615
5078
  if (result.total === 0) {
4616
5079
  return {
4617
5080
  summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
@@ -4703,9 +5166,9 @@ function formatDuration(ms) {
4703
5166
  return `${Math.round(h / 24)}d`;
4704
5167
  }
4705
5168
  async function runIncidents(client, input) {
4706
- const path12 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
5169
+ const path14 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
4707
5170
  try {
4708
- const body = await client.get(path12);
5171
+ const body = await client.get(path14);
4709
5172
  const events = body.events;
4710
5173
  if (events.length === 0) {
4711
5174
  return {
@@ -5068,6 +5531,24 @@ function observedEdgeJson(edge) {
5068
5531
  provenance: edge.provenance
5069
5532
  });
5070
5533
  }
5534
+ function policyKey(v) {
5535
+ return `policy|${v.id}`;
5536
+ }
5537
+ function policySubject(v) {
5538
+ const s = v.subject;
5539
+ if (s.nodeId) return s.nodeId;
5540
+ if (s.edgeId) return s.edgeId;
5541
+ if (s.path && s.path.length > 0) return s.path.join(" \u2192 ");
5542
+ return "";
5543
+ }
5544
+ function formatPolicyLine(v) {
5545
+ const subject = policySubject(v);
5546
+ const where = subject ? ` (${subject})` : "";
5547
+ return `\u26A0 policy [${v.severity}] ${v.policyName} \u2014 ${v.message}${where}`;
5548
+ }
5549
+ function policyJson(v) {
5550
+ return JSON.stringify({ kind: "policy", ...v });
5551
+ }
5071
5552
  var MonitorEmitter = class {
5072
5553
  constructor(opts) {
5073
5554
  this.opts = opts;
@@ -5111,6 +5592,20 @@ var MonitorEmitter = class {
5111
5592
  this.out(this.opts.json ? observedEdgeJson(edge) : formatObservedEdgeLine(edge));
5112
5593
  return true;
5113
5594
  }
5595
+ // Emit every not-yet-seen policy violation in a fresh /policies/violations
5596
+ // read. Returns the count newly emitted (0 → nothing printed, the silent
5597
+ // path). Idempotent across re-reads: the seen-set keys off the violation id.
5598
+ emitPolicies(response) {
5599
+ let emitted = 0;
5600
+ for (const v of response.violations) {
5601
+ const key = policyKey(v);
5602
+ if (this.seen.has(key)) continue;
5603
+ this.seen.add(key);
5604
+ this.out(this.opts.json ? policyJson(v) : formatPolicyLine(v));
5605
+ emitted++;
5606
+ }
5607
+ return emitted;
5608
+ }
5114
5609
  };
5115
5610
  function parseFrame(raw) {
5116
5611
  let event = "message";
@@ -5165,54 +5660,83 @@ function projectPath2(project, suffix) {
5165
5660
  function backoffDelay(attempt, capMs) {
5166
5661
  return Math.min(capMs, 500 * 2 ** Math.min(attempt, 6));
5167
5662
  }
5168
- async function runMonitor(opts) {
5169
- const write = opts.write ?? ((line) => process.stdout.write(line));
5170
- const debounceMs = opts.debounceMs ?? 400;
5171
- const backoffCapMs = opts.backoffCapMs ?? 1e4;
5172
- const maxReconnects = opts.maxReconnects ?? Number.POSITIVE_INFINITY;
5173
- const client = createHttpClient(opts.baseUrl, opts.authToken);
5174
- const emitter = new MonitorEmitter({ json: opts.json, write });
5175
- const divergencesPath = projectPath2(opts.project, "/graph/divergences");
5176
- const eventsUrl = `${opts.baseUrl.replace(/\/$/, "")}${projectPath2(opts.project, "/events")}`;
5177
- let readTimer = null;
5178
- let reading = false;
5179
- let readPending = false;
5180
- const doRead = async () => {
5181
- if (reading) {
5182
- readPending = true;
5663
+ var defaultOpenEvents = async (url, headers, signal) => {
5664
+ const res = await fetch(url, { headers, signal });
5665
+ return { ok: res.ok, status: res.status, body: res.body };
5666
+ };
5667
+ function makeDebouncedReader(read, debounceMs) {
5668
+ let timer = null;
5669
+ let running = false;
5670
+ let pending = false;
5671
+ const cancel = () => {
5672
+ if (timer) {
5673
+ clearTimeout(timer);
5674
+ timer = null;
5675
+ }
5676
+ };
5677
+ const run = async () => {
5678
+ if (running) {
5679
+ pending = true;
5183
5680
  return;
5184
5681
  }
5185
- reading = true;
5682
+ running = true;
5186
5683
  try {
5187
- const result = await client.get(divergencesPath);
5188
- emitter.emitDivergences(result);
5684
+ await read();
5189
5685
  } catch {
5190
5686
  } finally {
5191
- reading = false;
5192
- if (readPending) {
5193
- readPending = false;
5194
- scheduleRead();
5687
+ running = false;
5688
+ if (pending) {
5689
+ pending = false;
5690
+ schedule();
5195
5691
  }
5196
5692
  }
5197
5693
  };
5198
- const scheduleRead = () => {
5199
- if (readTimer) clearTimeout(readTimer);
5200
- readTimer = setTimeout(() => {
5201
- readTimer = null;
5202
- void doRead();
5694
+ const schedule = () => {
5695
+ cancel();
5696
+ timer = setTimeout(() => {
5697
+ timer = null;
5698
+ void run();
5203
5699
  }, debounceMs);
5204
- if (typeof readTimer.unref === "function") readTimer.unref();
5700
+ if (typeof timer.unref === "function") timer.unref();
5205
5701
  };
5702
+ return {
5703
+ schedule,
5704
+ runNow: async () => {
5705
+ cancel();
5706
+ await run();
5707
+ },
5708
+ cancel
5709
+ };
5710
+ }
5711
+ async function runMonitor(opts) {
5712
+ const write = opts.write ?? ((line) => process.stdout.write(line));
5713
+ const debounceMs = opts.debounceMs ?? 400;
5714
+ const backoffCapMs = opts.backoffCapMs ?? 1e4;
5715
+ const maxReconnects = opts.maxReconnects ?? Number.POSITIVE_INFINITY;
5716
+ const client = opts.httpClient ?? createHttpClient(opts.baseUrl, opts.authToken);
5717
+ const openEvents = opts.openEvents ?? defaultOpenEvents;
5718
+ const emitter = new MonitorEmitter({ json: opts.json, write });
5719
+ const divergencesPath = projectPath2(opts.project, "/graph/divergences");
5720
+ const policiesPath = projectPath2(opts.project, "/policies/violations");
5721
+ const eventsUrl = `${opts.baseUrl.replace(/\/$/, "")}${projectPath2(opts.project, "/events")}`;
5722
+ const divergences = makeDebouncedReader(async () => {
5723
+ const result = await client.get(divergencesPath);
5724
+ emitter.emitDivergences(result);
5725
+ }, debounceMs);
5726
+ const policies = makeDebouncedReader(async () => {
5727
+ const result = await client.get(policiesPath);
5728
+ emitter.emitPolicies(result);
5729
+ }, debounceMs);
5206
5730
  const onFrame = (frame) => {
5207
5731
  switch (frame.event) {
5208
5732
  case "extraction-complete":
5209
- scheduleRead();
5733
+ divergences.schedule();
5210
5734
  break;
5211
5735
  case "stale-transition": {
5212
5736
  const payload = safeParse(frame.data);
5213
5737
  const edgeId = payload && typeof payload.edgeId === "string" ? payload.edgeId : void 0;
5214
5738
  if (edgeId) emitter.emitStale(edgeId);
5215
- scheduleRead();
5739
+ divergences.schedule();
5216
5740
  break;
5217
5741
  }
5218
5742
  case "edge-added": {
@@ -5220,10 +5744,13 @@ async function runMonitor(opts) {
5220
5744
  const edge = payload?.edge;
5221
5745
  if (edge && edge.provenance === Provenance3.OBSERVED) {
5222
5746
  emitter.emitObservedEdge(edge);
5223
- scheduleRead();
5747
+ divergences.schedule();
5224
5748
  }
5225
5749
  break;
5226
5750
  }
5751
+ case "policy-violation":
5752
+ policies.schedule();
5753
+ break;
5227
5754
  default:
5228
5755
  break;
5229
5756
  }
@@ -5236,17 +5763,17 @@ async function runMonitor(opts) {
5236
5763
  let firstConnect = true;
5237
5764
  for (let attempt = 0; ; attempt++) {
5238
5765
  if (opts.signal?.aborted) break;
5239
- let res;
5766
+ let conn;
5240
5767
  try {
5241
- res = await fetch(eventsUrl, { headers, signal: opts.signal });
5768
+ conn = await openEvents(eventsUrl, headers, opts.signal);
5242
5769
  } catch (err) {
5243
5770
  if (err.name === "AbortError") break;
5244
5771
  if (!connectedOnce || attempt >= maxReconnects) break;
5245
5772
  await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
5246
5773
  continue;
5247
5774
  }
5248
- if (!res.ok || !res.body) {
5249
- await res.body?.cancel().catch(() => {
5775
+ if (!conn.ok || !conn.body) {
5776
+ await conn.body?.cancel().catch(() => {
5250
5777
  });
5251
5778
  if (!connectedOnce || attempt >= maxReconnects) break;
5252
5779
  await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
@@ -5256,19 +5783,22 @@ async function runMonitor(opts) {
5256
5783
  attempt = 0;
5257
5784
  if (firstConnect) {
5258
5785
  firstConnect = false;
5259
- await doRead();
5786
+ await divergences.runNow();
5787
+ await policies.runNow();
5260
5788
  } else {
5261
- scheduleRead();
5789
+ divergences.schedule();
5790
+ policies.schedule();
5262
5791
  }
5263
5792
  try {
5264
- await drainSse(res.body, onFrame);
5793
+ await drainSse(conn.body, onFrame);
5265
5794
  } catch {
5266
5795
  }
5267
5796
  if (opts.signal?.aborted) break;
5268
5797
  if (attempt >= maxReconnects) break;
5269
5798
  await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
5270
5799
  }
5271
- if (readTimer) clearTimeout(readTimer);
5800
+ divergences.cancel();
5801
+ policies.cancel();
5272
5802
  return 0;
5273
5803
  }
5274
5804
  function sleep(ms, signal) {
@@ -5291,7 +5821,7 @@ function sleep(ms, signal) {
5291
5821
  }
5292
5822
 
5293
5823
  // src/cli-verbs.ts
5294
- import path10 from "path";
5824
+ import path12 from "path";
5295
5825
  async function resolveProjectEntry(opts) {
5296
5826
  const entries = await listProjects();
5297
5827
  if (opts.project) {
@@ -5301,7 +5831,7 @@ async function resolveProjectEntry(opts) {
5301
5831
  const cwd = opts.cwd ?? process.cwd();
5302
5832
  const resolvedCwd = await normalizeProjectPath(cwd);
5303
5833
  for (const entry2 of entries) {
5304
- if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path10.sep}`)) {
5834
+ if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path12.sep}`)) {
5305
5835
  return entry2;
5306
5836
  }
5307
5837
  }
@@ -5466,7 +5996,7 @@ function isNpxInvocation() {
5466
5996
  function commandPrefix() {
5467
5997
  return isNpxInvocation() ? "npx neat.is" : "neat";
5468
5998
  }
5469
- function usage2() {
5999
+ function usage4() {
5470
6000
  const neat = commandPrefix();
5471
6001
  console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
5472
6002
  console.log("");
@@ -5486,10 +6016,12 @@ function usage2() {
5486
6016
  console.log(" PORT (default 8080), OTEL_PORT (4318), HOST (0.0.0.0)");
5487
6017
  console.log(" control listeners. NEAT_OTLP_GRPC=true also opens 4317.");
5488
6018
  console.log(" monitor Stream live graph facts to stdout \u2014 one line per new");
5489
- console.log(" fact \u2014 as the daemon learns them: fresh divergences,");
5490
- console.log(" integrations that just went stale, and new observed");
5491
- console.log(" runtime dependencies. Silent when nothing is new; exits");
5492
- console.log(" clean with no output when no daemon is reachable.");
6019
+ console.log(" fact \u2014 as the daemon learns them: fresh divergences");
6020
+ console.log(" (down to the drifting column), integrations that just");
6021
+ console.log(" went stale, new observed runtime dependencies, and");
6022
+ console.log(" freshly-tripped policy violations. Silent when nothing");
6023
+ console.log(" is new; exits clean with no output when no daemon is");
6024
+ console.log(" reachable.");
5493
6025
  console.log(" Flags:");
5494
6026
  console.log(" --project <name> watch a registered project by name");
5495
6027
  console.log(" --json emit one JSON object per line");
@@ -5519,6 +6051,22 @@ function usage2() {
5519
6051
  console.log(" --print-hook print the hook script");
5520
6052
  console.log(" --print-guide print the graph-first guidance");
5521
6053
  console.log(" --print-settings print the settings.json block --apply adds");
6054
+ console.log(" codex Install NEAT into the OpenAI Codex CLI: add [mcp_servers.neat]");
6055
+ console.log(" to ~/.codex/config.toml and the graph-first block to ./AGENTS.md.");
6056
+ console.log(" Plan by default; --apply to write.");
6057
+ console.log(" Flags:");
6058
+ console.log(" --apply add the MCP server + AGENTS.md block");
6059
+ console.log(" --print-config print the config.toml table");
6060
+ console.log(" --print-guide print the AGENTS.md block");
6061
+ console.log(" cursor Wire NEAT into Cursor: add the MCP server to ~/.cursor/mcp.json");
6062
+ console.log(" and the graph-first guidance to ./.cursorrules. Plan by default.");
6063
+ console.log(" Flags:");
6064
+ console.log(" --apply write the MCP config + rules file (default: plan)");
6065
+ console.log(" devin Wire NEAT into Devin Desktop (Cascade): add the MCP server to");
6066
+ console.log(" ~/.codeium/windsurf/mcp_config.json and the graph-first guidance");
6067
+ console.log(" to ./.windsurfrules. Plan by default.");
6068
+ console.log(" Flags:");
6069
+ console.log(" --apply write the MCP config + rules file (default: plan)");
5522
6070
  console.log(" deploy Detect the deploy substrate, generate NEAT_AUTH_TOKEN,");
5523
6071
  console.log(" emit a docker-compose / systemd / docker run artifact, and");
5524
6072
  console.log(" print the OTel env-vars block to paste into your platform.");
@@ -5532,7 +6080,8 @@ function usage2() {
5532
6080
  console.log(" --no-instrument skip the SDK install apply step");
5533
6081
  console.log(" --json emit the delta summary as JSON");
5534
6082
  console.log(" connector Configure OBSERVED connectors \u2014 pull (supabase, railway,");
5535
- console.log(" firebase, cloudflare) and push (vercel, via a trace Drain).");
6083
+ console.log(" firebase, cloudflare, neon, cloud-run) and push (vercel,");
6084
+ console.log(" via a trace Drain).");
5536
6085
  console.log(" Subcommands:");
5537
6086
  console.log(" add <provider> add a connector; validates the credential");
5538
6087
  console.log(" against the provider first (--skip-validate to skip)");
@@ -5764,7 +6313,7 @@ async function buildPatchSections(services, project) {
5764
6313
  }
5765
6314
  async function runInit(opts) {
5766
6315
  const written = [];
5767
- const stat = await fs9.stat(opts.scanPath).catch(() => null);
6316
+ const stat = await fs11.stat(opts.scanPath).catch(() => null);
5768
6317
  if (!stat || !stat.isDirectory()) {
5769
6318
  console.error(`neat init: ${opts.scanPath} is not a directory`);
5770
6319
  return { exitCode: 2, writtenFiles: written };
@@ -5773,13 +6322,13 @@ async function runInit(opts) {
5773
6322
  printDiscoveryReport(opts, services);
5774
6323
  const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
5775
6324
  const patch = renderPatch(sections);
5776
- const patchPath = path11.join(opts.scanPath, "neat.patch");
6325
+ const patchPath = path13.join(opts.scanPath, "neat.patch");
5777
6326
  if (opts.dryRun) {
5778
- await fs9.writeFile(patchPath, patch, "utf8");
6327
+ await fs11.writeFile(patchPath, patch, "utf8");
5779
6328
  written.push(patchPath);
5780
6329
  console.log(`dry-run: patch written to ${patchPath}`);
5781
- const gitignorePath = path11.join(opts.scanPath, ".gitignore");
5782
- const gitignoreExists = await fs9.stat(gitignorePath).then(() => true).catch(() => false);
6330
+ const gitignorePath = path13.join(opts.scanPath, ".gitignore");
6331
+ const gitignoreExists = await fs11.stat(gitignorePath).then(() => true).catch(() => false);
5783
6332
  const verb = gitignoreExists ? "append" : "create";
5784
6333
  console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
5785
6334
  console.log("rerun without --dry-run to register and snapshot.");
@@ -5790,9 +6339,9 @@ async function runInit(opts) {
5790
6339
  const graph = getGraph(graphKey);
5791
6340
  const projectPaths = pathsForProject(
5792
6341
  graphKey,
5793
- path11.join(opts.scanPath, "neat-out")
6342
+ path13.join(opts.scanPath, "neat-out")
5794
6343
  );
5795
- const errorsPath = path11.join(path11.dirname(opts.outPath), path11.basename(projectPaths.errorsPath));
6344
+ const errorsPath = path13.join(path13.dirname(opts.outPath), path13.basename(projectPaths.errorsPath));
5796
6345
  const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
5797
6346
  await saveGraphToDisk(graph, opts.outPath);
5798
6347
  written.push(opts.outPath);
@@ -5871,7 +6420,7 @@ async function runInit(opts) {
5871
6420
  console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
5872
6421
  }
5873
6422
  } else {
5874
- await fs9.writeFile(patchPath, patch, "utf8");
6423
+ await fs11.writeFile(patchPath, patch, "utf8");
5875
6424
  written.push(patchPath);
5876
6425
  }
5877
6426
  }
@@ -5911,9 +6460,9 @@ var CLAUDE_SKILL_CONFIG = {
5911
6460
  };
5912
6461
  function claudeConfigPath() {
5913
6462
  const override = process.env.NEAT_CLAUDE_CONFIG;
5914
- if (override && override.length > 0) return path11.resolve(override);
6463
+ if (override && override.length > 0) return path13.resolve(override);
5915
6464
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
5916
- return path11.join(home, ".claude.json");
6465
+ return path13.join(home, ".claude.json");
5917
6466
  }
5918
6467
  async function runSkill(opts) {
5919
6468
  const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
@@ -5925,7 +6474,7 @@ async function runSkill(opts) {
5925
6474
  const target = claudeConfigPath();
5926
6475
  let existing = {};
5927
6476
  try {
5928
- existing = JSON.parse(await fs9.readFile(target, "utf8"));
6477
+ existing = JSON.parse(await fs11.readFile(target, "utf8"));
5929
6478
  } catch (err) {
5930
6479
  if (err.code !== "ENOENT") {
5931
6480
  console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
@@ -5937,8 +6486,8 @@ async function runSkill(opts) {
5937
6486
  ...existing,
5938
6487
  mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
5939
6488
  };
5940
- await fs9.mkdir(path11.dirname(target), { recursive: true });
5941
- await fs9.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
6489
+ await fs11.mkdir(path13.dirname(target), { recursive: true });
6490
+ await fs11.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
5942
6491
  console.log(`neat skill: wrote mcpServers.neat to ${target}`);
5943
6492
  console.log("restart Claude Code to pick up the new MCP server.");
5944
6493
  console.log("");
@@ -5965,7 +6514,7 @@ async function main() {
5965
6514
  const argv = process.argv.slice(2);
5966
6515
  const cmd0 = argv[0];
5967
6516
  if (cmd0 === "-h" || cmd0 === "--help") {
5968
- usage2();
6517
+ usage4();
5969
6518
  process.exit(0);
5970
6519
  }
5971
6520
  if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
@@ -5982,6 +6531,16 @@ async function main() {
5982
6531
  if (code !== 0) process.exit(code);
5983
6532
  return;
5984
6533
  }
6534
+ if (cmd0 === "codex") {
6535
+ const code = await runCodexCommand(argv.slice(1));
6536
+ if (code !== 0) process.exit(code);
6537
+ return;
6538
+ }
6539
+ if (cmd0 === "cursor" || cmd0 === "devin") {
6540
+ const code = await runEditorCommand(cmd0, argv.slice(1));
6541
+ if (code !== 0) process.exit(code);
6542
+ return;
6543
+ }
5985
6544
  const argvParsed = parseArgs(argv);
5986
6545
  if (argvParsed.positional.length === 0) {
5987
6546
  const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
@@ -5996,19 +6555,19 @@ async function main() {
5996
6555
  const target = positional[0];
5997
6556
  if (!target) {
5998
6557
  console.error("neat init: missing <path>");
5999
- usage2();
6558
+ usage4();
6000
6559
  process.exit(2);
6001
6560
  }
6002
6561
  if (apply4 && dryRun) {
6003
6562
  console.error("neat init: --apply and --dry-run are mutually exclusive");
6004
6563
  process.exit(2);
6005
6564
  }
6006
- const scanPath = path11.resolve(target);
6565
+ const scanPath = path13.resolve(target);
6007
6566
  const projectExplicit = parsed.project !== null;
6008
- const projectName = projectExplicit ? project : path11.basename(scanPath);
6567
+ const projectName = projectExplicit ? project : path13.basename(scanPath);
6009
6568
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
6010
- const fallback = pathsForProject(projectKey, path11.join(scanPath, "neat-out")).snapshotPath;
6011
- const outPath = path11.resolve(process.env.NEAT_OUT_PATH ?? fallback);
6569
+ const fallback = pathsForProject(projectKey, path13.join(scanPath, "neat-out")).snapshotPath;
6570
+ const outPath = path13.resolve(process.env.NEAT_OUT_PATH ?? fallback);
6012
6571
  const result = await runInit({
6013
6572
  scanPath,
6014
6573
  outPath,
@@ -6026,24 +6585,24 @@ async function main() {
6026
6585
  const target = positional[0];
6027
6586
  if (!target) {
6028
6587
  console.error("neat watch: missing <path>");
6029
- usage2();
6588
+ usage4();
6030
6589
  process.exit(2);
6031
6590
  }
6032
- const scanPath = path11.resolve(target);
6033
- const stat = await fs9.stat(scanPath).catch(() => null);
6591
+ const scanPath = path13.resolve(target);
6592
+ const stat = await fs11.stat(scanPath).catch(() => null);
6034
6593
  if (!stat || !stat.isDirectory()) {
6035
6594
  console.error(`neat watch: ${scanPath} is not a directory`);
6036
6595
  process.exit(2);
6037
6596
  }
6038
- const projectPaths = pathsForProject(project, path11.join(scanPath, "neat-out"));
6039
- const outPath = path11.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
6040
- const errorsPath = path11.resolve(
6041
- process.env.NEAT_ERRORS_PATH ?? path11.join(path11.dirname(outPath), path11.basename(projectPaths.errorsPath))
6597
+ const projectPaths = pathsForProject(project, path13.join(scanPath, "neat-out"));
6598
+ const outPath = path13.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
6599
+ const errorsPath = path13.resolve(
6600
+ process.env.NEAT_ERRORS_PATH ?? path13.join(path13.dirname(outPath), path13.basename(projectPaths.errorsPath))
6042
6601
  );
6043
- const staleEventsPath = path11.resolve(
6044
- process.env.NEAT_STALE_EVENTS_PATH ?? path11.join(path11.dirname(outPath), path11.basename(projectPaths.staleEventsPath))
6602
+ const staleEventsPath = path13.resolve(
6603
+ process.env.NEAT_STALE_EVENTS_PATH ?? path13.join(path13.dirname(outPath), path13.basename(projectPaths.staleEventsPath))
6045
6604
  );
6046
- const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path11.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
6605
+ const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path13.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
6047
6606
  const handle = await startWatch(getGraph(project), {
6048
6607
  scanPath,
6049
6608
  outPath,
@@ -6052,7 +6611,7 @@ async function main() {
6052
6611
  project,
6053
6612
  // Resolve NEAT_HOME so a `neat watch` picks up connectors added to
6054
6613
  // ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
6055
- neatHome: process.env.NEAT_HOME ? path11.resolve(process.env.NEAT_HOME) : path11.join(os2.homedir(), ".neat"),
6614
+ neatHome: process.env.NEAT_HOME ? path13.resolve(process.env.NEAT_HOME) : path13.join(os4.homedir(), ".neat"),
6056
6615
  ...embeddingsCachePath ? { embeddingsCachePath } : {},
6057
6616
  host: process.env.HOST ?? "0.0.0.0",
6058
6617
  port: Number(process.env.PORT ?? 8080),
@@ -6088,7 +6647,7 @@ async function main() {
6088
6647
  const name = positional[0];
6089
6648
  if (!name) {
6090
6649
  console.error("neat pause: missing <name>");
6091
- usage2();
6650
+ usage4();
6092
6651
  process.exit(2);
6093
6652
  }
6094
6653
  const daemon = await findDaemonByProject(name);
@@ -6113,7 +6672,7 @@ async function main() {
6113
6672
  const name = positional[0];
6114
6673
  if (!name) {
6115
6674
  console.error("neat resume: missing <name>");
6116
- usage2();
6675
+ usage4();
6117
6676
  process.exit(2);
6118
6677
  }
6119
6678
  const daemon = await findDaemonByProject(name);
@@ -6143,7 +6702,7 @@ async function main() {
6143
6702
  const name = positional[0];
6144
6703
  if (!name) {
6145
6704
  console.error("neat uninstall: missing <name>");
6146
- usage2();
6705
+ usage4();
6147
6706
  process.exit(2);
6148
6707
  }
6149
6708
  const daemon = await findDaemonByProject(name);
@@ -6230,15 +6789,15 @@ async function main() {
6230
6789
  return;
6231
6790
  }
6232
6791
  console.error(`neat: unknown command "${cmd}"`);
6233
- usage2();
6792
+ usage4();
6234
6793
  process.exit(1);
6235
6794
  }
6236
6795
  async function tryOrchestrator(cmd, parsed) {
6237
- const scanPath = path11.resolve(cmd);
6238
- const stat = await fs9.stat(scanPath).catch(() => null);
6796
+ const scanPath = path13.resolve(cmd);
6797
+ const stat = await fs11.stat(scanPath).catch(() => null);
6239
6798
  if (!stat || !stat.isDirectory()) return null;
6240
6799
  const projectExplicit = parsed.project !== null;
6241
- const projectName = projectExplicit ? parsed.project : path11.basename(scanPath);
6800
+ const projectName = projectExplicit ? parsed.project : path13.basename(scanPath);
6242
6801
  const result = await runOrchestrator({
6243
6802
  scanPath,
6244
6803
  project: projectName,
@@ -6526,6 +7085,6 @@ export {
6526
7085
  runMonitorVerb,
6527
7086
  runQueryVerb,
6528
7087
  runSkill,
6529
- usage2 as usage
7088
+ usage4 as usage
6530
7089
  };
6531
7090
  //# sourceMappingURL=cli.js.map