@neat.is/core 0.7.4 → 0.7.6
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/{chunk-KN6O7KMK.js → chunk-XHSK4BGL.js} +2 -2
- package/dist/{chunk-UU4XKSTO.js → chunk-XT4NNFH6.js} +7204 -3877
- package/dist/chunk-XT4NNFH6.js.map +1 -0
- package/dist/cli.cjs +9340 -5256
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +850 -104
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +7562 -4226
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +7532 -4196
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +6656 -3320
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +5 -2
- package/dist/chunk-UU4XKSTO.js.map +0 -1
- /package/dist/{chunk-KN6O7KMK.js.map → chunk-XHSK4BGL.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
resolveHost,
|
|
7
7
|
resolveNeatVersion,
|
|
8
8
|
writeDaemonRecord
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-XHSK4BGL.js";
|
|
10
10
|
import {
|
|
11
11
|
buildSearchIndex
|
|
12
12
|
} from "./chunk-BIY46Q6U.js";
|
|
@@ -74,7 +74,7 @@ import {
|
|
|
74
74
|
startStalenessLoop,
|
|
75
75
|
upsertConnectorEntry,
|
|
76
76
|
validateConnectorEntry
|
|
77
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-XT4NNFH6.js";
|
|
78
78
|
import {
|
|
79
79
|
startOtelGrpcReceiver
|
|
80
80
|
} from "./chunk-6H757ZNM.js";
|
|
@@ -88,9 +88,9 @@ import {
|
|
|
88
88
|
} from "./chunk-P2ZEKJ35.js";
|
|
89
89
|
|
|
90
90
|
// src/cli.ts
|
|
91
|
-
import
|
|
92
|
-
import
|
|
93
|
-
import { promises as
|
|
91
|
+
import path13 from "path";
|
|
92
|
+
import os4 from "os";
|
|
93
|
+
import { promises as fs11 } from "fs";
|
|
94
94
|
|
|
95
95
|
// src/banner.ts
|
|
96
96
|
import path from "path";
|
|
@@ -4471,6 +4471,612 @@ async function runHooksCommand(args) {
|
|
|
4471
4471
|
}
|
|
4472
4472
|
}
|
|
4473
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
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
4753
|
+
import * as jsonc from "jsonc-parser";
|
|
4754
|
+
var NEAT_MCP_SERVER = {
|
|
4755
|
+
command: "npx",
|
|
4756
|
+
args: ["-y", "@neat.is/mcp"]
|
|
4757
|
+
};
|
|
4758
|
+
var NEAT_OPENCODE_SERVER = {
|
|
4759
|
+
type: "local",
|
|
4760
|
+
command: ["npx", "-y", "@neat.is/mcp"],
|
|
4761
|
+
enabled: true
|
|
4762
|
+
};
|
|
4763
|
+
var NEAT_CRUSH_SERVER = {
|
|
4764
|
+
type: "stdio",
|
|
4765
|
+
command: "npx",
|
|
4766
|
+
args: ["-y", "@neat.is/mcp"]
|
|
4767
|
+
};
|
|
4768
|
+
var GRAPH_FIRST_MARKER_OPEN = "<!-- neat:graph-first -->";
|
|
4769
|
+
var GRAPH_FIRST_MARKER_CLOSE = "<!-- /neat:graph-first -->";
|
|
4770
|
+
function homeDir() {
|
|
4771
|
+
return process.env.HOME ?? process.env.USERPROFILE ?? os3.homedir();
|
|
4772
|
+
}
|
|
4773
|
+
function xdgConfigDir() {
|
|
4774
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
4775
|
+
return xdg && xdg.length > 0 ? path11.resolve(xdg) : path11.join(homeDir(), ".config");
|
|
4776
|
+
}
|
|
4777
|
+
function envOverride(name) {
|
|
4778
|
+
const v = process.env[name];
|
|
4779
|
+
return v && v.length > 0 ? path11.resolve(v) : void 0;
|
|
4780
|
+
}
|
|
4781
|
+
var CURSOR_CLIENT = {
|
|
4782
|
+
id: "cursor",
|
|
4783
|
+
label: "Cursor",
|
|
4784
|
+
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
4785
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path11.join(homeDir(), ".cursor", "mcp.json"),
|
|
4786
|
+
mcpContainerKey: "mcpServers",
|
|
4787
|
+
format: "json",
|
|
4788
|
+
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
4789
|
+
// `.cursor/rules/*.mdc` split is one-rule-per-file with frontmatter — a worse
|
|
4790
|
+
// fit for a marker-fenced block). GRAPH_FIRST.md names this file directly.
|
|
4791
|
+
rulesFileName: ".cursorrules"
|
|
4792
|
+
};
|
|
4793
|
+
var DEVIN_CLIENT = {
|
|
4794
|
+
id: "devin",
|
|
4795
|
+
label: "Devin Desktop (Cascade)",
|
|
4796
|
+
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
4797
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path11.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
4798
|
+
mcpContainerKey: "mcpServers",
|
|
4799
|
+
format: "json",
|
|
4800
|
+
rulesFileName: ".windsurfrules"
|
|
4801
|
+
};
|
|
4802
|
+
var GEMINI_CLIENT = {
|
|
4803
|
+
id: "gemini",
|
|
4804
|
+
label: "Gemini CLI",
|
|
4805
|
+
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
4806
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path11.join(homeDir(), ".gemini", "settings.json"),
|
|
4807
|
+
mcpContainerKey: "mcpServers",
|
|
4808
|
+
format: "json",
|
|
4809
|
+
rulesFileName: "GEMINI.md"
|
|
4810
|
+
};
|
|
4811
|
+
var QWEN_CLIENT = {
|
|
4812
|
+
id: "qwen",
|
|
4813
|
+
label: "Qwen Code",
|
|
4814
|
+
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
4815
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path11.join(homeDir(), ".qwen", "settings.json"),
|
|
4816
|
+
mcpContainerKey: "mcpServers",
|
|
4817
|
+
format: "json",
|
|
4818
|
+
rulesFileName: "QWEN.md"
|
|
4819
|
+
};
|
|
4820
|
+
var AMAZONQ_CLIENT = {
|
|
4821
|
+
id: "amazonq",
|
|
4822
|
+
label: "Amazon Q Developer CLI",
|
|
4823
|
+
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
4824
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path11.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
4825
|
+
mcpContainerKey: "mcpServers",
|
|
4826
|
+
format: "json"
|
|
4827
|
+
};
|
|
4828
|
+
var ROOCODE_CLIENT = {
|
|
4829
|
+
id: "roocode",
|
|
4830
|
+
label: "Roo Code",
|
|
4831
|
+
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
4832
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path11.join(process.cwd(), ".roo", "mcp.json"),
|
|
4833
|
+
mcpContainerKey: "mcpServers",
|
|
4834
|
+
format: "json"
|
|
4835
|
+
};
|
|
4836
|
+
var ZED_CLIENT = {
|
|
4837
|
+
id: "zed",
|
|
4838
|
+
label: "Zed",
|
|
4839
|
+
docsUrl: "https://zed.dev/docs/ai/mcp",
|
|
4840
|
+
mcpConfigPath: () => {
|
|
4841
|
+
const override = envOverride("NEAT_ZED_CONFIG");
|
|
4842
|
+
if (override) return override;
|
|
4843
|
+
if (process.platform === "win32") {
|
|
4844
|
+
const appData = process.env.APPDATA;
|
|
4845
|
+
if (appData && appData.length > 0) return path11.join(appData, "Zed", "settings.json");
|
|
4846
|
+
}
|
|
4847
|
+
return path11.join(homeDir(), ".config", "zed", "settings.json");
|
|
4848
|
+
},
|
|
4849
|
+
mcpContainerKey: "context_servers",
|
|
4850
|
+
format: "jsonc",
|
|
4851
|
+
rulesFileName: ".rules"
|
|
4852
|
+
};
|
|
4853
|
+
var OPENCODE_CLIENT = {
|
|
4854
|
+
id: "opencode",
|
|
4855
|
+
label: "OpenCode",
|
|
4856
|
+
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
4857
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path11.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
4858
|
+
mcpContainerKey: "mcp",
|
|
4859
|
+
format: "json",
|
|
4860
|
+
serverEntry: NEAT_OPENCODE_SERVER,
|
|
4861
|
+
rulesFileName: "AGENTS.md"
|
|
4862
|
+
};
|
|
4863
|
+
var CRUSH_CLIENT = {
|
|
4864
|
+
id: "crush",
|
|
4865
|
+
label: "Crush",
|
|
4866
|
+
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
4867
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path11.join(xdgConfigDir(), "crush", "crush.json"),
|
|
4868
|
+
mcpContainerKey: "mcp",
|
|
4869
|
+
format: "json",
|
|
4870
|
+
serverEntry: NEAT_CRUSH_SERVER,
|
|
4871
|
+
rulesFileName: "AGENTS.md"
|
|
4872
|
+
};
|
|
4873
|
+
var CLIENTS = {
|
|
4874
|
+
cursor: CURSOR_CLIENT,
|
|
4875
|
+
devin: DEVIN_CLIENT,
|
|
4876
|
+
gemini: GEMINI_CLIENT,
|
|
4877
|
+
qwen: QWEN_CLIENT,
|
|
4878
|
+
amazonq: AMAZONQ_CLIENT,
|
|
4879
|
+
roocode: ROOCODE_CLIENT,
|
|
4880
|
+
zed: ZED_CLIENT,
|
|
4881
|
+
opencode: OPENCODE_CLIENT,
|
|
4882
|
+
crush: CRUSH_CLIENT
|
|
4883
|
+
};
|
|
4884
|
+
function mergeJsonMcp(existing, containerKey, serverEntry) {
|
|
4885
|
+
const servers = existing[containerKey] ?? {};
|
|
4886
|
+
const already = isDeepStrictEqual2(servers.neat, serverEntry);
|
|
4887
|
+
const merged = {
|
|
4888
|
+
...existing,
|
|
4889
|
+
[containerKey]: { ...servers, neat: serverEntry }
|
|
4890
|
+
};
|
|
4891
|
+
return { merged, changed: !already };
|
|
4892
|
+
}
|
|
4893
|
+
function mergeJsoncMcp(raw, containerKey, serverEntry) {
|
|
4894
|
+
const base = raw.trim().length > 0 ? raw : "{}";
|
|
4895
|
+
const parsed = jsonc.parse(base) ?? {};
|
|
4896
|
+
const servers = parsed[containerKey] ?? {};
|
|
4897
|
+
if (isDeepStrictEqual2(servers.neat, serverEntry)) {
|
|
4898
|
+
return { text: raw, changed: false };
|
|
4899
|
+
}
|
|
4900
|
+
const edits = jsonc.modify(base, [containerKey, "neat"], serverEntry, {
|
|
4901
|
+
formattingOptions: { tabSize: 2, insertSpaces: true }
|
|
4902
|
+
});
|
|
4903
|
+
let text = jsonc.applyEdits(base, edits);
|
|
4904
|
+
if (!text.endsWith("\n")) text += "\n";
|
|
4905
|
+
return { text, changed: text !== raw };
|
|
4906
|
+
}
|
|
4907
|
+
function escapeRegExp(s) {
|
|
4908
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4909
|
+
}
|
|
4910
|
+
function buildGuidanceBlock(guide) {
|
|
4911
|
+
return `${GRAPH_FIRST_MARKER_OPEN}
|
|
4912
|
+
${guide.trim()}
|
|
4913
|
+
${GRAPH_FIRST_MARKER_CLOSE}
|
|
4914
|
+
`;
|
|
4915
|
+
}
|
|
4916
|
+
function mergeRulesFile(existing, block) {
|
|
4917
|
+
const region = new RegExp(
|
|
4918
|
+
`${escapeRegExp(GRAPH_FIRST_MARKER_OPEN)}[\\s\\S]*?${escapeRegExp(GRAPH_FIRST_MARKER_CLOSE)}\\n?`
|
|
4919
|
+
);
|
|
4920
|
+
if (region.test(existing)) return existing.replace(region, block);
|
|
4921
|
+
if (existing.trim().length === 0) return block;
|
|
4922
|
+
return `${existing.replace(/\s+$/, "")}
|
|
4923
|
+
|
|
4924
|
+
${block}`;
|
|
4925
|
+
}
|
|
4926
|
+
async function planMcp(client, mcpPath) {
|
|
4927
|
+
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
4928
|
+
let raw = "";
|
|
4929
|
+
try {
|
|
4930
|
+
raw = await fs10.readFile(mcpPath, "utf8");
|
|
4931
|
+
} catch (err) {
|
|
4932
|
+
const e = err;
|
|
4933
|
+
if (e.code === "ENOENT") {
|
|
4934
|
+
raw = "";
|
|
4935
|
+
} else {
|
|
4936
|
+
console.error(`neat ${client.id}: failed to read ${mcpPath} \u2014 ${e.message}`);
|
|
4937
|
+
return null;
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
if (client.format === "jsonc") {
|
|
4941
|
+
if (raw.trim().length > 0) {
|
|
4942
|
+
const errors = [];
|
|
4943
|
+
jsonc.parse(raw, errors, { allowTrailingComma: true });
|
|
4944
|
+
if (errors.length > 0) {
|
|
4945
|
+
const first = errors[0];
|
|
4946
|
+
console.error(
|
|
4947
|
+
`neat ${client.id}: ${mcpPath} is not valid JSONC \u2014 ${jsonc.printParseErrorCode(first.error)} at offset ${first.offset}. Fix it (or move it aside) and re-run; nothing was written.`
|
|
4948
|
+
);
|
|
4949
|
+
return null;
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
return mergeJsoncMcp(raw, client.mcpContainerKey, serverEntry);
|
|
4953
|
+
}
|
|
4954
|
+
let existing = {};
|
|
4955
|
+
if (raw.trim().length > 0) {
|
|
4956
|
+
try {
|
|
4957
|
+
existing = JSON.parse(raw);
|
|
4958
|
+
} catch (err) {
|
|
4959
|
+
console.error(
|
|
4960
|
+
`neat ${client.id}: ${mcpPath} is not valid JSON \u2014 ${err.message}. Fix it (or move it aside) and re-run; nothing was written.`
|
|
4961
|
+
);
|
|
4962
|
+
return null;
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
const { merged, changed } = mergeJsonMcp(existing, client.mcpContainerKey, serverEntry);
|
|
4966
|
+
return { text: JSON.stringify(merged, null, 2) + "\n", changed };
|
|
4967
|
+
}
|
|
4968
|
+
async function runEditorInstall(client, opts) {
|
|
4969
|
+
const mcpPath = client.mcpConfigPath();
|
|
4970
|
+
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
4971
|
+
const hasRules = typeof client.rulesFileName === "string";
|
|
4972
|
+
const rulesPath = hasRules ? path11.join(opts.projectDir, client.rulesFileName) : "";
|
|
4973
|
+
const mcp = await planMcp(client, mcpPath);
|
|
4974
|
+
if (mcp === null) return { exitCode: 1 };
|
|
4975
|
+
let existingRules = "";
|
|
4976
|
+
let newRules = "";
|
|
4977
|
+
let rulesChanged = false;
|
|
4978
|
+
let block = "";
|
|
4979
|
+
if (hasRules) {
|
|
4980
|
+
try {
|
|
4981
|
+
existingRules = await fs10.readFile(rulesPath, "utf8");
|
|
4982
|
+
} catch (err) {
|
|
4983
|
+
if (err.code !== "ENOENT") {
|
|
4984
|
+
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
4985
|
+
return { exitCode: 1 };
|
|
4986
|
+
}
|
|
4987
|
+
}
|
|
4988
|
+
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
4989
|
+
block = buildGuidanceBlock(guide);
|
|
4990
|
+
newRules = mergeRulesFile(existingRules, block);
|
|
4991
|
+
rulesChanged = newRules !== existingRules;
|
|
4992
|
+
}
|
|
4993
|
+
if (!opts.apply) {
|
|
4994
|
+
console.log(`neat ${client.id} \u2014 wire NEAT into ${client.label} (plan; nothing written)`);
|
|
4995
|
+
console.log("");
|
|
4996
|
+
console.log(`MCP server \u2192 ${mcpPath}`);
|
|
4997
|
+
console.log(
|
|
4998
|
+
mcp.changed ? ` would add ${client.mcpContainerKey}.neat:` : ` ${client.mcpContainerKey}.neat already present and current \u2014 no change:`
|
|
4999
|
+
);
|
|
5000
|
+
console.log(indent(JSON.stringify({ neat: serverEntry }, null, 2)));
|
|
5001
|
+
if (hasRules) {
|
|
5002
|
+
console.log("");
|
|
5003
|
+
console.log(`Graph-first guidance \u2192 ${rulesPath}`);
|
|
5004
|
+
console.log(
|
|
5005
|
+
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."
|
|
5006
|
+
);
|
|
5007
|
+
if (rulesChanged) console.log(indent(block.trimEnd()));
|
|
5008
|
+
}
|
|
5009
|
+
console.log("");
|
|
5010
|
+
console.log(
|
|
5011
|
+
hasRules ? `Re-run with --apply to write both files. Existing servers and rules are kept.` : `Re-run with --apply to write the config. Existing servers are kept.`
|
|
5012
|
+
);
|
|
5013
|
+
return { exitCode: 0 };
|
|
5014
|
+
}
|
|
5015
|
+
await fs10.mkdir(path11.dirname(mcpPath), { recursive: true });
|
|
5016
|
+
await fs10.writeFile(mcpPath, mcp.text, "utf8");
|
|
5017
|
+
if (hasRules) {
|
|
5018
|
+
await fs10.mkdir(path11.dirname(rulesPath), { recursive: true });
|
|
5019
|
+
await fs10.writeFile(rulesPath, newRules, "utf8");
|
|
5020
|
+
}
|
|
5021
|
+
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
5022
|
+
console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
|
|
5023
|
+
if (hasRules) console.log(` guidance: ${rulesPath} (neat:graph-first block)`);
|
|
5024
|
+
console.log("");
|
|
5025
|
+
console.log(`restart ${client.label} to pick up the MCP server. Point it at a non-default`);
|
|
5026
|
+
console.log(`daemon by setting NEAT_CORE_URL in the neat server's env in that config.`);
|
|
5027
|
+
return { exitCode: 0 };
|
|
5028
|
+
}
|
|
5029
|
+
function indent(text) {
|
|
5030
|
+
return text.split("\n").map((line) => line.length > 0 ? ` ${line}` : line).join("\n");
|
|
5031
|
+
}
|
|
5032
|
+
function usage3(client) {
|
|
5033
|
+
const hasRules = typeof client.rulesFileName === "string";
|
|
5034
|
+
console.log(
|
|
5035
|
+
hasRules ? `neat ${client.id} \u2014 install NEAT's MCP server + graph-first guidance into ${client.label}` : `neat ${client.id} \u2014 install NEAT's MCP server into ${client.label}`
|
|
5036
|
+
);
|
|
5037
|
+
console.log("");
|
|
5038
|
+
console.log(
|
|
5039
|
+
hasRules ? " --apply write the MCP config and the rules file (default: plan only)" : " --apply write the MCP config (default: plan only)"
|
|
5040
|
+
);
|
|
5041
|
+
console.log("");
|
|
5042
|
+
console.log("Writes NEAT's stdio MCP server (npx -y @neat.is/mcp) into");
|
|
5043
|
+
console.log(` ${client.mcpConfigPath()}`);
|
|
5044
|
+
if (hasRules) {
|
|
5045
|
+
console.log(`and the graph-first guidance block into ./${client.rulesFileName}, both`);
|
|
5046
|
+
console.log("additively \u2014 existing servers and rules are preserved, a re-run is a no-op.");
|
|
5047
|
+
} else {
|
|
5048
|
+
console.log("additively \u2014 existing servers are preserved, a re-run is a no-op.");
|
|
5049
|
+
}
|
|
5050
|
+
console.log("");
|
|
5051
|
+
console.log(`See ${client.docsUrl} for ${client.label}'s MCP config format.`);
|
|
5052
|
+
}
|
|
5053
|
+
async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
5054
|
+
const client = CLIENTS[clientId];
|
|
5055
|
+
let apply4 = false;
|
|
5056
|
+
for (const arg of args) {
|
|
5057
|
+
switch (arg) {
|
|
5058
|
+
case "--apply":
|
|
5059
|
+
apply4 = true;
|
|
5060
|
+
break;
|
|
5061
|
+
case "-h":
|
|
5062
|
+
case "--help":
|
|
5063
|
+
usage3(client);
|
|
5064
|
+
return 0;
|
|
5065
|
+
default:
|
|
5066
|
+
console.error(`neat ${client.id}: unknown flag "${arg}"`);
|
|
5067
|
+
usage3(client);
|
|
5068
|
+
return 2;
|
|
5069
|
+
}
|
|
5070
|
+
}
|
|
5071
|
+
try {
|
|
5072
|
+
const { exitCode } = await runEditorInstall(client, { apply: apply4, projectDir });
|
|
5073
|
+
return exitCode;
|
|
5074
|
+
} catch (err) {
|
|
5075
|
+
console.error(err.message);
|
|
5076
|
+
return 1;
|
|
5077
|
+
}
|
|
5078
|
+
}
|
|
5079
|
+
|
|
4474
5080
|
// src/monitor.ts
|
|
4475
5081
|
import { EdgeType, parseEdgeId, Provenance as Provenance3 } from "@neat.is/types";
|
|
4476
5082
|
|
|
@@ -4500,10 +5106,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4500
5106
|
const root = baseUrl.replace(/\/$/, "");
|
|
4501
5107
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
4502
5108
|
return {
|
|
4503
|
-
async get(
|
|
5109
|
+
async get(path14) {
|
|
4504
5110
|
let res;
|
|
4505
5111
|
try {
|
|
4506
|
-
res = await fetch(`${root}${
|
|
5112
|
+
res = await fetch(`${root}${path14}`, {
|
|
4507
5113
|
headers: { ...authHeader }
|
|
4508
5114
|
});
|
|
4509
5115
|
} catch (err) {
|
|
@@ -4515,16 +5121,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4515
5121
|
const body = await res.text().catch(() => "");
|
|
4516
5122
|
throw new HttpError(
|
|
4517
5123
|
res.status,
|
|
4518
|
-
`${res.status} ${res.statusText} on GET ${
|
|
5124
|
+
`${res.status} ${res.statusText} on GET ${path14}: ${body}`,
|
|
4519
5125
|
body
|
|
4520
5126
|
);
|
|
4521
5127
|
}
|
|
4522
5128
|
return await res.json();
|
|
4523
5129
|
},
|
|
4524
|
-
async post(
|
|
5130
|
+
async post(path14, body) {
|
|
4525
5131
|
let res;
|
|
4526
5132
|
try {
|
|
4527
|
-
res = await fetch(`${root}${
|
|
5133
|
+
res = await fetch(`${root}${path14}`, {
|
|
4528
5134
|
method: "POST",
|
|
4529
5135
|
headers: { "content-type": "application/json", ...authHeader },
|
|
4530
5136
|
body: JSON.stringify(body)
|
|
@@ -4538,7 +5144,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4538
5144
|
const text = await res.text().catch(() => "");
|
|
4539
5145
|
throw new HttpError(
|
|
4540
5146
|
res.status,
|
|
4541
|
-
`${res.status} ${res.statusText} on POST ${
|
|
5147
|
+
`${res.status} ${res.statusText} on POST ${path14}: ${text}`,
|
|
4542
5148
|
text
|
|
4543
5149
|
);
|
|
4544
5150
|
}
|
|
@@ -4552,12 +5158,12 @@ function projectPath(project, suffix) {
|
|
|
4552
5158
|
}
|
|
4553
5159
|
async function runRootCause(client, input) {
|
|
4554
5160
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
4555
|
-
const
|
|
5161
|
+
const path14 = projectPath(
|
|
4556
5162
|
input.project,
|
|
4557
5163
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
4558
5164
|
);
|
|
4559
5165
|
try {
|
|
4560
|
-
const result = await client.get(
|
|
5166
|
+
const result = await client.get(path14);
|
|
4561
5167
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
4562
5168
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
4563
5169
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -4583,12 +5189,12 @@ async function runRootCause(client, input) {
|
|
|
4583
5189
|
}
|
|
4584
5190
|
async function runBlastRadius(client, input) {
|
|
4585
5191
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
4586
|
-
const
|
|
5192
|
+
const path14 = projectPath(
|
|
4587
5193
|
input.project,
|
|
4588
5194
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
4589
5195
|
);
|
|
4590
5196
|
try {
|
|
4591
|
-
const result = await client.get(
|
|
5197
|
+
const result = await client.get(path14);
|
|
4592
5198
|
if (result.totalAffected === 0) {
|
|
4593
5199
|
return {
|
|
4594
5200
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -4622,12 +5228,12 @@ function formatBlastEntry(n) {
|
|
|
4622
5228
|
}
|
|
4623
5229
|
async function runDependencies(client, input) {
|
|
4624
5230
|
const depth = input.depth ?? 3;
|
|
4625
|
-
const
|
|
5231
|
+
const path14 = projectPath(
|
|
4626
5232
|
input.project,
|
|
4627
5233
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
4628
5234
|
);
|
|
4629
5235
|
try {
|
|
4630
|
-
const result = await client.get(
|
|
5236
|
+
const result = await client.get(path14);
|
|
4631
5237
|
if (result.total === 0) {
|
|
4632
5238
|
return {
|
|
4633
5239
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -4719,9 +5325,9 @@ function formatDuration(ms) {
|
|
|
4719
5325
|
return `${Math.round(h / 24)}d`;
|
|
4720
5326
|
}
|
|
4721
5327
|
async function runIncidents(client, input) {
|
|
4722
|
-
const
|
|
5328
|
+
const path14 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
4723
5329
|
try {
|
|
4724
|
-
const body = await client.get(
|
|
5330
|
+
const body = await client.get(path14);
|
|
4725
5331
|
const events = body.events;
|
|
4726
5332
|
if (events.length === 0) {
|
|
4727
5333
|
return {
|
|
@@ -5084,6 +5690,24 @@ function observedEdgeJson(edge) {
|
|
|
5084
5690
|
provenance: edge.provenance
|
|
5085
5691
|
});
|
|
5086
5692
|
}
|
|
5693
|
+
function policyKey(v) {
|
|
5694
|
+
return `policy|${v.id}`;
|
|
5695
|
+
}
|
|
5696
|
+
function policySubject(v) {
|
|
5697
|
+
const s = v.subject;
|
|
5698
|
+
if (s.nodeId) return s.nodeId;
|
|
5699
|
+
if (s.edgeId) return s.edgeId;
|
|
5700
|
+
if (s.path && s.path.length > 0) return s.path.join(" \u2192 ");
|
|
5701
|
+
return "";
|
|
5702
|
+
}
|
|
5703
|
+
function formatPolicyLine(v) {
|
|
5704
|
+
const subject = policySubject(v);
|
|
5705
|
+
const where = subject ? ` (${subject})` : "";
|
|
5706
|
+
return `\u26A0 policy [${v.severity}] ${v.policyName} \u2014 ${v.message}${where}`;
|
|
5707
|
+
}
|
|
5708
|
+
function policyJson(v) {
|
|
5709
|
+
return JSON.stringify({ kind: "policy", ...v });
|
|
5710
|
+
}
|
|
5087
5711
|
var MonitorEmitter = class {
|
|
5088
5712
|
constructor(opts) {
|
|
5089
5713
|
this.opts = opts;
|
|
@@ -5127,6 +5751,20 @@ var MonitorEmitter = class {
|
|
|
5127
5751
|
this.out(this.opts.json ? observedEdgeJson(edge) : formatObservedEdgeLine(edge));
|
|
5128
5752
|
return true;
|
|
5129
5753
|
}
|
|
5754
|
+
// Emit every not-yet-seen policy violation in a fresh /policies/violations
|
|
5755
|
+
// read. Returns the count newly emitted (0 → nothing printed, the silent
|
|
5756
|
+
// path). Idempotent across re-reads: the seen-set keys off the violation id.
|
|
5757
|
+
emitPolicies(response) {
|
|
5758
|
+
let emitted = 0;
|
|
5759
|
+
for (const v of response.violations) {
|
|
5760
|
+
const key = policyKey(v);
|
|
5761
|
+
if (this.seen.has(key)) continue;
|
|
5762
|
+
this.seen.add(key);
|
|
5763
|
+
this.out(this.opts.json ? policyJson(v) : formatPolicyLine(v));
|
|
5764
|
+
emitted++;
|
|
5765
|
+
}
|
|
5766
|
+
return emitted;
|
|
5767
|
+
}
|
|
5130
5768
|
};
|
|
5131
5769
|
function parseFrame(raw) {
|
|
5132
5770
|
let event = "message";
|
|
@@ -5181,54 +5819,83 @@ function projectPath2(project, suffix) {
|
|
|
5181
5819
|
function backoffDelay(attempt, capMs) {
|
|
5182
5820
|
return Math.min(capMs, 500 * 2 ** Math.min(attempt, 6));
|
|
5183
5821
|
}
|
|
5184
|
-
async
|
|
5185
|
-
const
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
const
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
5822
|
+
var defaultOpenEvents = async (url, headers, signal) => {
|
|
5823
|
+
const res = await fetch(url, { headers, signal });
|
|
5824
|
+
return { ok: res.ok, status: res.status, body: res.body };
|
|
5825
|
+
};
|
|
5826
|
+
function makeDebouncedReader(read, debounceMs) {
|
|
5827
|
+
let timer = null;
|
|
5828
|
+
let running = false;
|
|
5829
|
+
let pending = false;
|
|
5830
|
+
const cancel = () => {
|
|
5831
|
+
if (timer) {
|
|
5832
|
+
clearTimeout(timer);
|
|
5833
|
+
timer = null;
|
|
5834
|
+
}
|
|
5835
|
+
};
|
|
5836
|
+
const run = async () => {
|
|
5837
|
+
if (running) {
|
|
5838
|
+
pending = true;
|
|
5199
5839
|
return;
|
|
5200
5840
|
}
|
|
5201
|
-
|
|
5841
|
+
running = true;
|
|
5202
5842
|
try {
|
|
5203
|
-
|
|
5204
|
-
emitter.emitDivergences(result);
|
|
5843
|
+
await read();
|
|
5205
5844
|
} catch {
|
|
5206
5845
|
} finally {
|
|
5207
|
-
|
|
5208
|
-
if (
|
|
5209
|
-
|
|
5210
|
-
|
|
5846
|
+
running = false;
|
|
5847
|
+
if (pending) {
|
|
5848
|
+
pending = false;
|
|
5849
|
+
schedule();
|
|
5211
5850
|
}
|
|
5212
5851
|
}
|
|
5213
5852
|
};
|
|
5214
|
-
const
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
void
|
|
5853
|
+
const schedule = () => {
|
|
5854
|
+
cancel();
|
|
5855
|
+
timer = setTimeout(() => {
|
|
5856
|
+
timer = null;
|
|
5857
|
+
void run();
|
|
5219
5858
|
}, debounceMs);
|
|
5220
|
-
if (typeof
|
|
5859
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
5221
5860
|
};
|
|
5861
|
+
return {
|
|
5862
|
+
schedule,
|
|
5863
|
+
runNow: async () => {
|
|
5864
|
+
cancel();
|
|
5865
|
+
await run();
|
|
5866
|
+
},
|
|
5867
|
+
cancel
|
|
5868
|
+
};
|
|
5869
|
+
}
|
|
5870
|
+
async function runMonitor(opts) {
|
|
5871
|
+
const write = opts.write ?? ((line) => process.stdout.write(line));
|
|
5872
|
+
const debounceMs = opts.debounceMs ?? 400;
|
|
5873
|
+
const backoffCapMs = opts.backoffCapMs ?? 1e4;
|
|
5874
|
+
const maxReconnects = opts.maxReconnects ?? Number.POSITIVE_INFINITY;
|
|
5875
|
+
const client = opts.httpClient ?? createHttpClient(opts.baseUrl, opts.authToken);
|
|
5876
|
+
const openEvents = opts.openEvents ?? defaultOpenEvents;
|
|
5877
|
+
const emitter = new MonitorEmitter({ json: opts.json, write });
|
|
5878
|
+
const divergencesPath = projectPath2(opts.project, "/graph/divergences");
|
|
5879
|
+
const policiesPath = projectPath2(opts.project, "/policies/violations");
|
|
5880
|
+
const eventsUrl = `${opts.baseUrl.replace(/\/$/, "")}${projectPath2(opts.project, "/events")}`;
|
|
5881
|
+
const divergences = makeDebouncedReader(async () => {
|
|
5882
|
+
const result = await client.get(divergencesPath);
|
|
5883
|
+
emitter.emitDivergences(result);
|
|
5884
|
+
}, debounceMs);
|
|
5885
|
+
const policies = makeDebouncedReader(async () => {
|
|
5886
|
+
const result = await client.get(policiesPath);
|
|
5887
|
+
emitter.emitPolicies(result);
|
|
5888
|
+
}, debounceMs);
|
|
5222
5889
|
const onFrame = (frame) => {
|
|
5223
5890
|
switch (frame.event) {
|
|
5224
5891
|
case "extraction-complete":
|
|
5225
|
-
|
|
5892
|
+
divergences.schedule();
|
|
5226
5893
|
break;
|
|
5227
5894
|
case "stale-transition": {
|
|
5228
5895
|
const payload = safeParse(frame.data);
|
|
5229
5896
|
const edgeId = payload && typeof payload.edgeId === "string" ? payload.edgeId : void 0;
|
|
5230
5897
|
if (edgeId) emitter.emitStale(edgeId);
|
|
5231
|
-
|
|
5898
|
+
divergences.schedule();
|
|
5232
5899
|
break;
|
|
5233
5900
|
}
|
|
5234
5901
|
case "edge-added": {
|
|
@@ -5236,10 +5903,13 @@ async function runMonitor(opts) {
|
|
|
5236
5903
|
const edge = payload?.edge;
|
|
5237
5904
|
if (edge && edge.provenance === Provenance3.OBSERVED) {
|
|
5238
5905
|
emitter.emitObservedEdge(edge);
|
|
5239
|
-
|
|
5906
|
+
divergences.schedule();
|
|
5240
5907
|
}
|
|
5241
5908
|
break;
|
|
5242
5909
|
}
|
|
5910
|
+
case "policy-violation":
|
|
5911
|
+
policies.schedule();
|
|
5912
|
+
break;
|
|
5243
5913
|
default:
|
|
5244
5914
|
break;
|
|
5245
5915
|
}
|
|
@@ -5252,17 +5922,17 @@ async function runMonitor(opts) {
|
|
|
5252
5922
|
let firstConnect = true;
|
|
5253
5923
|
for (let attempt = 0; ; attempt++) {
|
|
5254
5924
|
if (opts.signal?.aborted) break;
|
|
5255
|
-
let
|
|
5925
|
+
let conn;
|
|
5256
5926
|
try {
|
|
5257
|
-
|
|
5927
|
+
conn = await openEvents(eventsUrl, headers, opts.signal);
|
|
5258
5928
|
} catch (err) {
|
|
5259
5929
|
if (err.name === "AbortError") break;
|
|
5260
5930
|
if (!connectedOnce || attempt >= maxReconnects) break;
|
|
5261
5931
|
await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
|
|
5262
5932
|
continue;
|
|
5263
5933
|
}
|
|
5264
|
-
if (!
|
|
5265
|
-
await
|
|
5934
|
+
if (!conn.ok || !conn.body) {
|
|
5935
|
+
await conn.body?.cancel().catch(() => {
|
|
5266
5936
|
});
|
|
5267
5937
|
if (!connectedOnce || attempt >= maxReconnects) break;
|
|
5268
5938
|
await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
|
|
@@ -5272,19 +5942,22 @@ async function runMonitor(opts) {
|
|
|
5272
5942
|
attempt = 0;
|
|
5273
5943
|
if (firstConnect) {
|
|
5274
5944
|
firstConnect = false;
|
|
5275
|
-
await
|
|
5945
|
+
await divergences.runNow();
|
|
5946
|
+
await policies.runNow();
|
|
5276
5947
|
} else {
|
|
5277
|
-
|
|
5948
|
+
divergences.schedule();
|
|
5949
|
+
policies.schedule();
|
|
5278
5950
|
}
|
|
5279
5951
|
try {
|
|
5280
|
-
await drainSse(
|
|
5952
|
+
await drainSse(conn.body, onFrame);
|
|
5281
5953
|
} catch {
|
|
5282
5954
|
}
|
|
5283
5955
|
if (opts.signal?.aborted) break;
|
|
5284
5956
|
if (attempt >= maxReconnects) break;
|
|
5285
5957
|
await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
|
|
5286
5958
|
}
|
|
5287
|
-
|
|
5959
|
+
divergences.cancel();
|
|
5960
|
+
policies.cancel();
|
|
5288
5961
|
return 0;
|
|
5289
5962
|
}
|
|
5290
5963
|
function sleep(ms, signal) {
|
|
@@ -5307,7 +5980,7 @@ function sleep(ms, signal) {
|
|
|
5307
5980
|
}
|
|
5308
5981
|
|
|
5309
5982
|
// src/cli-verbs.ts
|
|
5310
|
-
import
|
|
5983
|
+
import path12 from "path";
|
|
5311
5984
|
async function resolveProjectEntry(opts) {
|
|
5312
5985
|
const entries = await listProjects();
|
|
5313
5986
|
if (opts.project) {
|
|
@@ -5317,7 +5990,7 @@ async function resolveProjectEntry(opts) {
|
|
|
5317
5990
|
const cwd = opts.cwd ?? process.cwd();
|
|
5318
5991
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
5319
5992
|
for (const entry2 of entries) {
|
|
5320
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
5993
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path12.sep}`)) {
|
|
5321
5994
|
return entry2;
|
|
5322
5995
|
}
|
|
5323
5996
|
}
|
|
@@ -5482,7 +6155,7 @@ function isNpxInvocation() {
|
|
|
5482
6155
|
function commandPrefix() {
|
|
5483
6156
|
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
5484
6157
|
}
|
|
5485
|
-
function
|
|
6158
|
+
function usage4() {
|
|
5486
6159
|
const neat = commandPrefix();
|
|
5487
6160
|
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
5488
6161
|
console.log("");
|
|
@@ -5502,10 +6175,12 @@ function usage2() {
|
|
|
5502
6175
|
console.log(" PORT (default 8080), OTEL_PORT (4318), HOST (0.0.0.0)");
|
|
5503
6176
|
console.log(" control listeners. NEAT_OTLP_GRPC=true also opens 4317.");
|
|
5504
6177
|
console.log(" monitor Stream live graph facts to stdout \u2014 one line per new");
|
|
5505
|
-
console.log(" fact \u2014 as the daemon learns them: fresh divergences
|
|
5506
|
-
console.log("
|
|
5507
|
-
console.log("
|
|
5508
|
-
console.log("
|
|
6178
|
+
console.log(" fact \u2014 as the daemon learns them: fresh divergences");
|
|
6179
|
+
console.log(" (down to the drifting column), integrations that just");
|
|
6180
|
+
console.log(" went stale, new observed runtime dependencies, and");
|
|
6181
|
+
console.log(" freshly-tripped policy violations. Silent when nothing");
|
|
6182
|
+
console.log(" is new; exits clean with no output when no daemon is");
|
|
6183
|
+
console.log(" reachable.");
|
|
5509
6184
|
console.log(" Flags:");
|
|
5510
6185
|
console.log(" --project <name> watch a registered project by name");
|
|
5511
6186
|
console.log(" --json emit one JSON object per line");
|
|
@@ -5535,6 +6210,55 @@ function usage2() {
|
|
|
5535
6210
|
console.log(" --print-hook print the hook script");
|
|
5536
6211
|
console.log(" --print-guide print the graph-first guidance");
|
|
5537
6212
|
console.log(" --print-settings print the settings.json block --apply adds");
|
|
6213
|
+
console.log(" codex Install NEAT into the OpenAI Codex CLI: add [mcp_servers.neat]");
|
|
6214
|
+
console.log(" to ~/.codex/config.toml and the graph-first block to ./AGENTS.md.");
|
|
6215
|
+
console.log(" Plan by default; --apply to write.");
|
|
6216
|
+
console.log(" Flags:");
|
|
6217
|
+
console.log(" --apply add the MCP server + AGENTS.md block");
|
|
6218
|
+
console.log(" --print-config print the config.toml table");
|
|
6219
|
+
console.log(" --print-guide print the AGENTS.md block");
|
|
6220
|
+
console.log(" cursor Wire NEAT into Cursor: add the MCP server to ~/.cursor/mcp.json");
|
|
6221
|
+
console.log(" and the graph-first guidance to ./.cursorrules. Plan by default.");
|
|
6222
|
+
console.log(" Flags:");
|
|
6223
|
+
console.log(" --apply write the MCP config + rules file (default: plan)");
|
|
6224
|
+
console.log(" devin Wire NEAT into Devin Desktop (Cascade): add the MCP server to");
|
|
6225
|
+
console.log(" ~/.codeium/windsurf/mcp_config.json and the graph-first guidance");
|
|
6226
|
+
console.log(" to ./.windsurfrules. Plan by default.");
|
|
6227
|
+
console.log(" Flags:");
|
|
6228
|
+
console.log(" --apply write the MCP config + rules file (default: plan)");
|
|
6229
|
+
console.log(" gemini Wire NEAT into the Gemini CLI: add the MCP server to");
|
|
6230
|
+
console.log(" ~/.gemini/settings.json and the graph-first guidance to");
|
|
6231
|
+
console.log(" ./GEMINI.md. Plan by default.");
|
|
6232
|
+
console.log(" Flags:");
|
|
6233
|
+
console.log(" --apply write the MCP config + rules file (default: plan)");
|
|
6234
|
+
console.log(" qwen Wire NEAT into Qwen Code: add the MCP server to");
|
|
6235
|
+
console.log(" ~/.qwen/settings.json and the graph-first guidance to");
|
|
6236
|
+
console.log(" ./QWEN.md. Plan by default.");
|
|
6237
|
+
console.log(" Flags:");
|
|
6238
|
+
console.log(" --apply write the MCP config + rules file (default: plan)");
|
|
6239
|
+
console.log(" amazonq Wire NEAT into the Amazon Q Developer CLI: add the MCP server to");
|
|
6240
|
+
console.log(" ~/.aws/amazonq/mcp.json. Plan by default.");
|
|
6241
|
+
console.log(" Flags:");
|
|
6242
|
+
console.log(" --apply write the MCP config (default: plan)");
|
|
6243
|
+
console.log(" roocode Wire NEAT into Roo Code: add the MCP server to the project's");
|
|
6244
|
+
console.log(" ./.roo/mcp.json. Plan by default.");
|
|
6245
|
+
console.log(" Flags:");
|
|
6246
|
+
console.log(" --apply write the MCP config (default: plan)");
|
|
6247
|
+
console.log(" zed Wire NEAT into Zed: add the MCP server under context_servers in");
|
|
6248
|
+
console.log(" ~/.config/zed/settings.json (comments preserved) and the");
|
|
6249
|
+
console.log(" graph-first guidance to ./.rules. Plan by default.");
|
|
6250
|
+
console.log(" Flags:");
|
|
6251
|
+
console.log(" --apply write the MCP config + rules file (default: plan)");
|
|
6252
|
+
console.log(" opencode Wire NEAT into OpenCode: add the MCP server under mcp in");
|
|
6253
|
+
console.log(" ~/.config/opencode/opencode.json and the graph-first guidance");
|
|
6254
|
+
console.log(" to ./AGENTS.md. Plan by default.");
|
|
6255
|
+
console.log(" Flags:");
|
|
6256
|
+
console.log(" --apply write the MCP config + rules file (default: plan)");
|
|
6257
|
+
console.log(" crush Wire NEAT into Crush: add the MCP server under mcp in");
|
|
6258
|
+
console.log(" ~/.config/crush/crush.json and the graph-first guidance");
|
|
6259
|
+
console.log(" to ./AGENTS.md. Plan by default.");
|
|
6260
|
+
console.log(" Flags:");
|
|
6261
|
+
console.log(" --apply write the MCP config + rules file (default: plan)");
|
|
5538
6262
|
console.log(" deploy Detect the deploy substrate, generate NEAT_AUTH_TOKEN,");
|
|
5539
6263
|
console.log(" emit a docker-compose / systemd / docker run artifact, and");
|
|
5540
6264
|
console.log(" print the OTel env-vars block to paste into your platform.");
|
|
@@ -5548,7 +6272,8 @@ function usage2() {
|
|
|
5548
6272
|
console.log(" --no-instrument skip the SDK install apply step");
|
|
5549
6273
|
console.log(" --json emit the delta summary as JSON");
|
|
5550
6274
|
console.log(" connector Configure OBSERVED connectors \u2014 pull (supabase, railway,");
|
|
5551
|
-
console.log(" firebase, cloudflare) and push (vercel,
|
|
6275
|
+
console.log(" firebase, cloudflare, neon, cloud-run) and push (vercel,");
|
|
6276
|
+
console.log(" via a trace Drain).");
|
|
5552
6277
|
console.log(" Subcommands:");
|
|
5553
6278
|
console.log(" add <provider> add a connector; validates the credential");
|
|
5554
6279
|
console.log(" against the provider first (--skip-validate to skip)");
|
|
@@ -5780,7 +6505,7 @@ async function buildPatchSections(services, project) {
|
|
|
5780
6505
|
}
|
|
5781
6506
|
async function runInit(opts) {
|
|
5782
6507
|
const written = [];
|
|
5783
|
-
const stat = await
|
|
6508
|
+
const stat = await fs11.stat(opts.scanPath).catch(() => null);
|
|
5784
6509
|
if (!stat || !stat.isDirectory()) {
|
|
5785
6510
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
5786
6511
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -5789,13 +6514,13 @@ async function runInit(opts) {
|
|
|
5789
6514
|
printDiscoveryReport(opts, services);
|
|
5790
6515
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
5791
6516
|
const patch = renderPatch(sections);
|
|
5792
|
-
const patchPath =
|
|
6517
|
+
const patchPath = path13.join(opts.scanPath, "neat.patch");
|
|
5793
6518
|
if (opts.dryRun) {
|
|
5794
|
-
await
|
|
6519
|
+
await fs11.writeFile(patchPath, patch, "utf8");
|
|
5795
6520
|
written.push(patchPath);
|
|
5796
6521
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
5797
|
-
const gitignorePath =
|
|
5798
|
-
const gitignoreExists = await
|
|
6522
|
+
const gitignorePath = path13.join(opts.scanPath, ".gitignore");
|
|
6523
|
+
const gitignoreExists = await fs11.stat(gitignorePath).then(() => true).catch(() => false);
|
|
5799
6524
|
const verb = gitignoreExists ? "append" : "create";
|
|
5800
6525
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
5801
6526
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -5806,9 +6531,9 @@ async function runInit(opts) {
|
|
|
5806
6531
|
const graph = getGraph(graphKey);
|
|
5807
6532
|
const projectPaths = pathsForProject(
|
|
5808
6533
|
graphKey,
|
|
5809
|
-
|
|
6534
|
+
path13.join(opts.scanPath, "neat-out")
|
|
5810
6535
|
);
|
|
5811
|
-
const errorsPath =
|
|
6536
|
+
const errorsPath = path13.join(path13.dirname(opts.outPath), path13.basename(projectPaths.errorsPath));
|
|
5812
6537
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
5813
6538
|
await saveGraphToDisk(graph, opts.outPath);
|
|
5814
6539
|
written.push(opts.outPath);
|
|
@@ -5887,7 +6612,7 @@ async function runInit(opts) {
|
|
|
5887
6612
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
5888
6613
|
}
|
|
5889
6614
|
} else {
|
|
5890
|
-
await
|
|
6615
|
+
await fs11.writeFile(patchPath, patch, "utf8");
|
|
5891
6616
|
written.push(patchPath);
|
|
5892
6617
|
}
|
|
5893
6618
|
}
|
|
@@ -5927,9 +6652,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
5927
6652
|
};
|
|
5928
6653
|
function claudeConfigPath() {
|
|
5929
6654
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
5930
|
-
if (override && override.length > 0) return
|
|
6655
|
+
if (override && override.length > 0) return path13.resolve(override);
|
|
5931
6656
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5932
|
-
return
|
|
6657
|
+
return path13.join(home, ".claude.json");
|
|
5933
6658
|
}
|
|
5934
6659
|
async function runSkill(opts) {
|
|
5935
6660
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -5941,7 +6666,7 @@ async function runSkill(opts) {
|
|
|
5941
6666
|
const target = claudeConfigPath();
|
|
5942
6667
|
let existing = {};
|
|
5943
6668
|
try {
|
|
5944
|
-
existing = JSON.parse(await
|
|
6669
|
+
existing = JSON.parse(await fs11.readFile(target, "utf8"));
|
|
5945
6670
|
} catch (err) {
|
|
5946
6671
|
if (err.code !== "ENOENT") {
|
|
5947
6672
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -5953,8 +6678,8 @@ async function runSkill(opts) {
|
|
|
5953
6678
|
...existing,
|
|
5954
6679
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
5955
6680
|
};
|
|
5956
|
-
await
|
|
5957
|
-
await
|
|
6681
|
+
await fs11.mkdir(path13.dirname(target), { recursive: true });
|
|
6682
|
+
await fs11.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
5958
6683
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
5959
6684
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
5960
6685
|
console.log("");
|
|
@@ -5981,7 +6706,7 @@ async function main() {
|
|
|
5981
6706
|
const argv = process.argv.slice(2);
|
|
5982
6707
|
const cmd0 = argv[0];
|
|
5983
6708
|
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
5984
|
-
|
|
6709
|
+
usage4();
|
|
5985
6710
|
process.exit(0);
|
|
5986
6711
|
}
|
|
5987
6712
|
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
@@ -5998,6 +6723,27 @@ async function main() {
|
|
|
5998
6723
|
if (code !== 0) process.exit(code);
|
|
5999
6724
|
return;
|
|
6000
6725
|
}
|
|
6726
|
+
if (cmd0 === "codex") {
|
|
6727
|
+
const code = await runCodexCommand(argv.slice(1));
|
|
6728
|
+
if (code !== 0) process.exit(code);
|
|
6729
|
+
return;
|
|
6730
|
+
}
|
|
6731
|
+
const EDITOR_VERBS = [
|
|
6732
|
+
"cursor",
|
|
6733
|
+
"devin",
|
|
6734
|
+
"gemini",
|
|
6735
|
+
"qwen",
|
|
6736
|
+
"amazonq",
|
|
6737
|
+
"roocode",
|
|
6738
|
+
"zed",
|
|
6739
|
+
"opencode",
|
|
6740
|
+
"crush"
|
|
6741
|
+
];
|
|
6742
|
+
if (typeof cmd0 === "string" && EDITOR_VERBS.includes(cmd0)) {
|
|
6743
|
+
const code = await runEditorCommand(cmd0, argv.slice(1));
|
|
6744
|
+
if (code !== 0) process.exit(code);
|
|
6745
|
+
return;
|
|
6746
|
+
}
|
|
6001
6747
|
const argvParsed = parseArgs(argv);
|
|
6002
6748
|
if (argvParsed.positional.length === 0) {
|
|
6003
6749
|
const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
|
|
@@ -6012,19 +6758,19 @@ async function main() {
|
|
|
6012
6758
|
const target = positional[0];
|
|
6013
6759
|
if (!target) {
|
|
6014
6760
|
console.error("neat init: missing <path>");
|
|
6015
|
-
|
|
6761
|
+
usage4();
|
|
6016
6762
|
process.exit(2);
|
|
6017
6763
|
}
|
|
6018
6764
|
if (apply4 && dryRun) {
|
|
6019
6765
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
6020
6766
|
process.exit(2);
|
|
6021
6767
|
}
|
|
6022
|
-
const scanPath =
|
|
6768
|
+
const scanPath = path13.resolve(target);
|
|
6023
6769
|
const projectExplicit = parsed.project !== null;
|
|
6024
|
-
const projectName = projectExplicit ? project :
|
|
6770
|
+
const projectName = projectExplicit ? project : path13.basename(scanPath);
|
|
6025
6771
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
6026
|
-
const fallback = pathsForProject(projectKey,
|
|
6027
|
-
const outPath =
|
|
6772
|
+
const fallback = pathsForProject(projectKey, path13.join(scanPath, "neat-out")).snapshotPath;
|
|
6773
|
+
const outPath = path13.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
6028
6774
|
const result = await runInit({
|
|
6029
6775
|
scanPath,
|
|
6030
6776
|
outPath,
|
|
@@ -6042,24 +6788,24 @@ async function main() {
|
|
|
6042
6788
|
const target = positional[0];
|
|
6043
6789
|
if (!target) {
|
|
6044
6790
|
console.error("neat watch: missing <path>");
|
|
6045
|
-
|
|
6791
|
+
usage4();
|
|
6046
6792
|
process.exit(2);
|
|
6047
6793
|
}
|
|
6048
|
-
const scanPath =
|
|
6049
|
-
const stat = await
|
|
6794
|
+
const scanPath = path13.resolve(target);
|
|
6795
|
+
const stat = await fs11.stat(scanPath).catch(() => null);
|
|
6050
6796
|
if (!stat || !stat.isDirectory()) {
|
|
6051
6797
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
6052
6798
|
process.exit(2);
|
|
6053
6799
|
}
|
|
6054
|
-
const projectPaths = pathsForProject(project,
|
|
6055
|
-
const outPath =
|
|
6056
|
-
const errorsPath =
|
|
6057
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
6800
|
+
const projectPaths = pathsForProject(project, path13.join(scanPath, "neat-out"));
|
|
6801
|
+
const outPath = path13.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
6802
|
+
const errorsPath = path13.resolve(
|
|
6803
|
+
process.env.NEAT_ERRORS_PATH ?? path13.join(path13.dirname(outPath), path13.basename(projectPaths.errorsPath))
|
|
6058
6804
|
);
|
|
6059
|
-
const staleEventsPath =
|
|
6060
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
6805
|
+
const staleEventsPath = path13.resolve(
|
|
6806
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path13.join(path13.dirname(outPath), path13.basename(projectPaths.staleEventsPath))
|
|
6061
6807
|
);
|
|
6062
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
6808
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path13.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
6063
6809
|
const handle = await startWatch(getGraph(project), {
|
|
6064
6810
|
scanPath,
|
|
6065
6811
|
outPath,
|
|
@@ -6068,7 +6814,7 @@ async function main() {
|
|
|
6068
6814
|
project,
|
|
6069
6815
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
6070
6816
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
6071
|
-
neatHome: process.env.NEAT_HOME ?
|
|
6817
|
+
neatHome: process.env.NEAT_HOME ? path13.resolve(process.env.NEAT_HOME) : path13.join(os4.homedir(), ".neat"),
|
|
6072
6818
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
6073
6819
|
host: process.env.HOST ?? "0.0.0.0",
|
|
6074
6820
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -6104,7 +6850,7 @@ async function main() {
|
|
|
6104
6850
|
const name = positional[0];
|
|
6105
6851
|
if (!name) {
|
|
6106
6852
|
console.error("neat pause: missing <name>");
|
|
6107
|
-
|
|
6853
|
+
usage4();
|
|
6108
6854
|
process.exit(2);
|
|
6109
6855
|
}
|
|
6110
6856
|
const daemon = await findDaemonByProject(name);
|
|
@@ -6129,7 +6875,7 @@ async function main() {
|
|
|
6129
6875
|
const name = positional[0];
|
|
6130
6876
|
if (!name) {
|
|
6131
6877
|
console.error("neat resume: missing <name>");
|
|
6132
|
-
|
|
6878
|
+
usage4();
|
|
6133
6879
|
process.exit(2);
|
|
6134
6880
|
}
|
|
6135
6881
|
const daemon = await findDaemonByProject(name);
|
|
@@ -6159,7 +6905,7 @@ async function main() {
|
|
|
6159
6905
|
const name = positional[0];
|
|
6160
6906
|
if (!name) {
|
|
6161
6907
|
console.error("neat uninstall: missing <name>");
|
|
6162
|
-
|
|
6908
|
+
usage4();
|
|
6163
6909
|
process.exit(2);
|
|
6164
6910
|
}
|
|
6165
6911
|
const daemon = await findDaemonByProject(name);
|
|
@@ -6246,15 +6992,15 @@ async function main() {
|
|
|
6246
6992
|
return;
|
|
6247
6993
|
}
|
|
6248
6994
|
console.error(`neat: unknown command "${cmd}"`);
|
|
6249
|
-
|
|
6995
|
+
usage4();
|
|
6250
6996
|
process.exit(1);
|
|
6251
6997
|
}
|
|
6252
6998
|
async function tryOrchestrator(cmd, parsed) {
|
|
6253
|
-
const scanPath =
|
|
6254
|
-
const stat = await
|
|
6999
|
+
const scanPath = path13.resolve(cmd);
|
|
7000
|
+
const stat = await fs11.stat(scanPath).catch(() => null);
|
|
6255
7001
|
if (!stat || !stat.isDirectory()) return null;
|
|
6256
7002
|
const projectExplicit = parsed.project !== null;
|
|
6257
|
-
const projectName = projectExplicit ? parsed.project :
|
|
7003
|
+
const projectName = projectExplicit ? parsed.project : path13.basename(scanPath);
|
|
6258
7004
|
const result = await runOrchestrator({
|
|
6259
7005
|
scanPath,
|
|
6260
7006
|
project: projectName,
|
|
@@ -6542,6 +7288,6 @@ export {
|
|
|
6542
7288
|
runMonitorVerb,
|
|
6543
7289
|
runQueryVerb,
|
|
6544
7290
|
runSkill,
|
|
6545
|
-
|
|
7291
|
+
usage4 as usage
|
|
6546
7292
|
};
|
|
6547
7293
|
//# sourceMappingURL=cli.js.map
|