@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/{chunk-BY6ZP5WA.js → chunk-KB4HRB6N.js} +4624 -3555
- package/dist/chunk-KB4HRB6N.js.map +1 -0
- package/dist/{chunk-3SBEY67S.js → chunk-MYLUXVXE.js} +2 -2
- package/dist/cli.cjs +4871 -3239
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +663 -104
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +3221 -2148
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +3221 -2148
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +2984 -1911
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-BY6ZP5WA.js.map +0 -1
- /package/dist/{chunk-3SBEY67S.js.map → chunk-MYLUXVXE.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-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-
|
|
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
|
|
90
|
-
import
|
|
91
|
-
import { promises as
|
|
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(
|
|
4950
|
+
async get(path14) {
|
|
4488
4951
|
let res;
|
|
4489
4952
|
try {
|
|
4490
|
-
res = await fetch(`${root}${
|
|
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 ${
|
|
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(
|
|
4971
|
+
async post(path14, body) {
|
|
4509
4972
|
let res;
|
|
4510
4973
|
try {
|
|
4511
|
-
res = await fetch(`${root}${
|
|
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 ${
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
5169
|
-
const
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
const
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
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
|
-
|
|
5682
|
+
running = true;
|
|
5186
5683
|
try {
|
|
5187
|
-
|
|
5188
|
-
emitter.emitDivergences(result);
|
|
5684
|
+
await read();
|
|
5189
5685
|
} catch {
|
|
5190
5686
|
} finally {
|
|
5191
|
-
|
|
5192
|
-
if (
|
|
5193
|
-
|
|
5194
|
-
|
|
5687
|
+
running = false;
|
|
5688
|
+
if (pending) {
|
|
5689
|
+
pending = false;
|
|
5690
|
+
schedule();
|
|
5195
5691
|
}
|
|
5196
5692
|
}
|
|
5197
5693
|
};
|
|
5198
|
-
const
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
void
|
|
5694
|
+
const schedule = () => {
|
|
5695
|
+
cancel();
|
|
5696
|
+
timer = setTimeout(() => {
|
|
5697
|
+
timer = null;
|
|
5698
|
+
void run();
|
|
5203
5699
|
}, debounceMs);
|
|
5204
|
-
if (typeof
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5766
|
+
let conn;
|
|
5240
5767
|
try {
|
|
5241
|
-
|
|
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 (!
|
|
5249
|
-
await
|
|
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
|
|
5786
|
+
await divergences.runNow();
|
|
5787
|
+
await policies.runNow();
|
|
5260
5788
|
} else {
|
|
5261
|
-
|
|
5789
|
+
divergences.schedule();
|
|
5790
|
+
policies.schedule();
|
|
5262
5791
|
}
|
|
5263
5792
|
try {
|
|
5264
|
-
await drainSse(
|
|
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
|
-
|
|
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
|
|
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}${
|
|
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
|
|
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("
|
|
5491
|
-
console.log("
|
|
5492
|
-
console.log("
|
|
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,
|
|
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
|
|
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 =
|
|
6325
|
+
const patchPath = path13.join(opts.scanPath, "neat.patch");
|
|
5777
6326
|
if (opts.dryRun) {
|
|
5778
|
-
await
|
|
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 =
|
|
5782
|
-
const gitignoreExists = await
|
|
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
|
-
|
|
6342
|
+
path13.join(opts.scanPath, "neat-out")
|
|
5794
6343
|
);
|
|
5795
|
-
const 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
|
|
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
|
|
6463
|
+
if (override && override.length > 0) return path13.resolve(override);
|
|
5915
6464
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5916
|
-
return
|
|
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
|
|
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
|
|
5941
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
6565
|
+
const scanPath = path13.resolve(target);
|
|
6007
6566
|
const projectExplicit = parsed.project !== null;
|
|
6008
|
-
const projectName = projectExplicit ? project :
|
|
6567
|
+
const projectName = projectExplicit ? project : path13.basename(scanPath);
|
|
6009
6568
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
6010
|
-
const fallback = pathsForProject(projectKey,
|
|
6011
|
-
const outPath =
|
|
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
|
-
|
|
6588
|
+
usage4();
|
|
6030
6589
|
process.exit(2);
|
|
6031
6590
|
}
|
|
6032
|
-
const scanPath =
|
|
6033
|
-
const stat = await
|
|
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,
|
|
6039
|
-
const outPath =
|
|
6040
|
-
const errorsPath =
|
|
6041
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
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 =
|
|
6044
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
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 ?
|
|
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 ?
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
6792
|
+
usage4();
|
|
6234
6793
|
process.exit(1);
|
|
6235
6794
|
}
|
|
6236
6795
|
async function tryOrchestrator(cmd, parsed) {
|
|
6237
|
-
const scanPath =
|
|
6238
|
-
const stat = await
|
|
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 :
|
|
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
|
-
|
|
7088
|
+
usage4 as usage
|
|
6530
7089
|
};
|
|
6531
7090
|
//# sourceMappingURL=cli.js.map
|