@neat.is/core 0.7.4 → 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-UU4XKSTO.js → chunk-KB4HRB6N.js} +872 -119
- package/dist/chunk-KB4HRB6N.js.map +1 -0
- package/dist/{chunk-KN6O7KMK.js → chunk-MYLUXVXE.js} +2 -2
- package/dist/cli.cjs +1632 -328
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +647 -104
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +892 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +892 -133
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +860 -101
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-UU4XKSTO.js.map +0 -1
- /package/dist/{chunk-KN6O7KMK.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";
|
|
@@ -74,7 +74,7 @@ import {
|
|
|
74
74
|
startStalenessLoop,
|
|
75
75
|
upsertConnectorEntry,
|
|
76
76
|
validateConnectorEntry
|
|
77
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-KB4HRB6N.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,453 @@ 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
|
+
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
|
+
|
|
4474
4921
|
// src/monitor.ts
|
|
4475
4922
|
import { EdgeType, parseEdgeId, Provenance as Provenance3 } from "@neat.is/types";
|
|
4476
4923
|
|
|
@@ -4500,10 +4947,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4500
4947
|
const root = baseUrl.replace(/\/$/, "");
|
|
4501
4948
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
4502
4949
|
return {
|
|
4503
|
-
async get(
|
|
4950
|
+
async get(path14) {
|
|
4504
4951
|
let res;
|
|
4505
4952
|
try {
|
|
4506
|
-
res = await fetch(`${root}${
|
|
4953
|
+
res = await fetch(`${root}${path14}`, {
|
|
4507
4954
|
headers: { ...authHeader }
|
|
4508
4955
|
});
|
|
4509
4956
|
} catch (err) {
|
|
@@ -4515,16 +4962,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4515
4962
|
const body = await res.text().catch(() => "");
|
|
4516
4963
|
throw new HttpError(
|
|
4517
4964
|
res.status,
|
|
4518
|
-
`${res.status} ${res.statusText} on GET ${
|
|
4965
|
+
`${res.status} ${res.statusText} on GET ${path14}: ${body}`,
|
|
4519
4966
|
body
|
|
4520
4967
|
);
|
|
4521
4968
|
}
|
|
4522
4969
|
return await res.json();
|
|
4523
4970
|
},
|
|
4524
|
-
async post(
|
|
4971
|
+
async post(path14, body) {
|
|
4525
4972
|
let res;
|
|
4526
4973
|
try {
|
|
4527
|
-
res = await fetch(`${root}${
|
|
4974
|
+
res = await fetch(`${root}${path14}`, {
|
|
4528
4975
|
method: "POST",
|
|
4529
4976
|
headers: { "content-type": "application/json", ...authHeader },
|
|
4530
4977
|
body: JSON.stringify(body)
|
|
@@ -4538,7 +4985,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4538
4985
|
const text = await res.text().catch(() => "");
|
|
4539
4986
|
throw new HttpError(
|
|
4540
4987
|
res.status,
|
|
4541
|
-
`${res.status} ${res.statusText} on POST ${
|
|
4988
|
+
`${res.status} ${res.statusText} on POST ${path14}: ${text}`,
|
|
4542
4989
|
text
|
|
4543
4990
|
);
|
|
4544
4991
|
}
|
|
@@ -4552,12 +4999,12 @@ function projectPath(project, suffix) {
|
|
|
4552
4999
|
}
|
|
4553
5000
|
async function runRootCause(client, input) {
|
|
4554
5001
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
4555
|
-
const
|
|
5002
|
+
const path14 = projectPath(
|
|
4556
5003
|
input.project,
|
|
4557
5004
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
4558
5005
|
);
|
|
4559
5006
|
try {
|
|
4560
|
-
const result = await client.get(
|
|
5007
|
+
const result = await client.get(path14);
|
|
4561
5008
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
4562
5009
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
4563
5010
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -4583,12 +5030,12 @@ async function runRootCause(client, input) {
|
|
|
4583
5030
|
}
|
|
4584
5031
|
async function runBlastRadius(client, input) {
|
|
4585
5032
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
4586
|
-
const
|
|
5033
|
+
const path14 = projectPath(
|
|
4587
5034
|
input.project,
|
|
4588
5035
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
4589
5036
|
);
|
|
4590
5037
|
try {
|
|
4591
|
-
const result = await client.get(
|
|
5038
|
+
const result = await client.get(path14);
|
|
4592
5039
|
if (result.totalAffected === 0) {
|
|
4593
5040
|
return {
|
|
4594
5041
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -4622,12 +5069,12 @@ function formatBlastEntry(n) {
|
|
|
4622
5069
|
}
|
|
4623
5070
|
async function runDependencies(client, input) {
|
|
4624
5071
|
const depth = input.depth ?? 3;
|
|
4625
|
-
const
|
|
5072
|
+
const path14 = projectPath(
|
|
4626
5073
|
input.project,
|
|
4627
5074
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
4628
5075
|
);
|
|
4629
5076
|
try {
|
|
4630
|
-
const result = await client.get(
|
|
5077
|
+
const result = await client.get(path14);
|
|
4631
5078
|
if (result.total === 0) {
|
|
4632
5079
|
return {
|
|
4633
5080
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -4719,9 +5166,9 @@ function formatDuration(ms) {
|
|
|
4719
5166
|
return `${Math.round(h / 24)}d`;
|
|
4720
5167
|
}
|
|
4721
5168
|
async function runIncidents(client, input) {
|
|
4722
|
-
const
|
|
5169
|
+
const path14 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
4723
5170
|
try {
|
|
4724
|
-
const body = await client.get(
|
|
5171
|
+
const body = await client.get(path14);
|
|
4725
5172
|
const events = body.events;
|
|
4726
5173
|
if (events.length === 0) {
|
|
4727
5174
|
return {
|
|
@@ -5084,6 +5531,24 @@ function observedEdgeJson(edge) {
|
|
|
5084
5531
|
provenance: edge.provenance
|
|
5085
5532
|
});
|
|
5086
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
|
+
}
|
|
5087
5552
|
var MonitorEmitter = class {
|
|
5088
5553
|
constructor(opts) {
|
|
5089
5554
|
this.opts = opts;
|
|
@@ -5127,6 +5592,20 @@ var MonitorEmitter = class {
|
|
|
5127
5592
|
this.out(this.opts.json ? observedEdgeJson(edge) : formatObservedEdgeLine(edge));
|
|
5128
5593
|
return true;
|
|
5129
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
|
+
}
|
|
5130
5609
|
};
|
|
5131
5610
|
function parseFrame(raw) {
|
|
5132
5611
|
let event = "message";
|
|
@@ -5181,54 +5660,83 @@ function projectPath2(project, suffix) {
|
|
|
5181
5660
|
function backoffDelay(attempt, capMs) {
|
|
5182
5661
|
return Math.min(capMs, 500 * 2 ** Math.min(attempt, 6));
|
|
5183
5662
|
}
|
|
5184
|
-
async
|
|
5185
|
-
const
|
|
5186
|
-
|
|
5187
|
-
|
|
5188
|
-
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
|
|
5192
|
-
const
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
|
|
5197
|
-
|
|
5198
|
-
|
|
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;
|
|
5199
5680
|
return;
|
|
5200
5681
|
}
|
|
5201
|
-
|
|
5682
|
+
running = true;
|
|
5202
5683
|
try {
|
|
5203
|
-
|
|
5204
|
-
emitter.emitDivergences(result);
|
|
5684
|
+
await read();
|
|
5205
5685
|
} catch {
|
|
5206
5686
|
} finally {
|
|
5207
|
-
|
|
5208
|
-
if (
|
|
5209
|
-
|
|
5210
|
-
|
|
5687
|
+
running = false;
|
|
5688
|
+
if (pending) {
|
|
5689
|
+
pending = false;
|
|
5690
|
+
schedule();
|
|
5211
5691
|
}
|
|
5212
5692
|
}
|
|
5213
5693
|
};
|
|
5214
|
-
const
|
|
5215
|
-
|
|
5216
|
-
|
|
5217
|
-
|
|
5218
|
-
void
|
|
5694
|
+
const schedule = () => {
|
|
5695
|
+
cancel();
|
|
5696
|
+
timer = setTimeout(() => {
|
|
5697
|
+
timer = null;
|
|
5698
|
+
void run();
|
|
5219
5699
|
}, debounceMs);
|
|
5220
|
-
if (typeof
|
|
5700
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
5221
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);
|
|
5222
5730
|
const onFrame = (frame) => {
|
|
5223
5731
|
switch (frame.event) {
|
|
5224
5732
|
case "extraction-complete":
|
|
5225
|
-
|
|
5733
|
+
divergences.schedule();
|
|
5226
5734
|
break;
|
|
5227
5735
|
case "stale-transition": {
|
|
5228
5736
|
const payload = safeParse(frame.data);
|
|
5229
5737
|
const edgeId = payload && typeof payload.edgeId === "string" ? payload.edgeId : void 0;
|
|
5230
5738
|
if (edgeId) emitter.emitStale(edgeId);
|
|
5231
|
-
|
|
5739
|
+
divergences.schedule();
|
|
5232
5740
|
break;
|
|
5233
5741
|
}
|
|
5234
5742
|
case "edge-added": {
|
|
@@ -5236,10 +5744,13 @@ async function runMonitor(opts) {
|
|
|
5236
5744
|
const edge = payload?.edge;
|
|
5237
5745
|
if (edge && edge.provenance === Provenance3.OBSERVED) {
|
|
5238
5746
|
emitter.emitObservedEdge(edge);
|
|
5239
|
-
|
|
5747
|
+
divergences.schedule();
|
|
5240
5748
|
}
|
|
5241
5749
|
break;
|
|
5242
5750
|
}
|
|
5751
|
+
case "policy-violation":
|
|
5752
|
+
policies.schedule();
|
|
5753
|
+
break;
|
|
5243
5754
|
default:
|
|
5244
5755
|
break;
|
|
5245
5756
|
}
|
|
@@ -5252,17 +5763,17 @@ async function runMonitor(opts) {
|
|
|
5252
5763
|
let firstConnect = true;
|
|
5253
5764
|
for (let attempt = 0; ; attempt++) {
|
|
5254
5765
|
if (opts.signal?.aborted) break;
|
|
5255
|
-
let
|
|
5766
|
+
let conn;
|
|
5256
5767
|
try {
|
|
5257
|
-
|
|
5768
|
+
conn = await openEvents(eventsUrl, headers, opts.signal);
|
|
5258
5769
|
} catch (err) {
|
|
5259
5770
|
if (err.name === "AbortError") break;
|
|
5260
5771
|
if (!connectedOnce || attempt >= maxReconnects) break;
|
|
5261
5772
|
await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
|
|
5262
5773
|
continue;
|
|
5263
5774
|
}
|
|
5264
|
-
if (!
|
|
5265
|
-
await
|
|
5775
|
+
if (!conn.ok || !conn.body) {
|
|
5776
|
+
await conn.body?.cancel().catch(() => {
|
|
5266
5777
|
});
|
|
5267
5778
|
if (!connectedOnce || attempt >= maxReconnects) break;
|
|
5268
5779
|
await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
|
|
@@ -5272,19 +5783,22 @@ async function runMonitor(opts) {
|
|
|
5272
5783
|
attempt = 0;
|
|
5273
5784
|
if (firstConnect) {
|
|
5274
5785
|
firstConnect = false;
|
|
5275
|
-
await
|
|
5786
|
+
await divergences.runNow();
|
|
5787
|
+
await policies.runNow();
|
|
5276
5788
|
} else {
|
|
5277
|
-
|
|
5789
|
+
divergences.schedule();
|
|
5790
|
+
policies.schedule();
|
|
5278
5791
|
}
|
|
5279
5792
|
try {
|
|
5280
|
-
await drainSse(
|
|
5793
|
+
await drainSse(conn.body, onFrame);
|
|
5281
5794
|
} catch {
|
|
5282
5795
|
}
|
|
5283
5796
|
if (opts.signal?.aborted) break;
|
|
5284
5797
|
if (attempt >= maxReconnects) break;
|
|
5285
5798
|
await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
|
|
5286
5799
|
}
|
|
5287
|
-
|
|
5800
|
+
divergences.cancel();
|
|
5801
|
+
policies.cancel();
|
|
5288
5802
|
return 0;
|
|
5289
5803
|
}
|
|
5290
5804
|
function sleep(ms, signal) {
|
|
@@ -5307,7 +5821,7 @@ function sleep(ms, signal) {
|
|
|
5307
5821
|
}
|
|
5308
5822
|
|
|
5309
5823
|
// src/cli-verbs.ts
|
|
5310
|
-
import
|
|
5824
|
+
import path12 from "path";
|
|
5311
5825
|
async function resolveProjectEntry(opts) {
|
|
5312
5826
|
const entries = await listProjects();
|
|
5313
5827
|
if (opts.project) {
|
|
@@ -5317,7 +5831,7 @@ async function resolveProjectEntry(opts) {
|
|
|
5317
5831
|
const cwd = opts.cwd ?? process.cwd();
|
|
5318
5832
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
5319
5833
|
for (const entry2 of entries) {
|
|
5320
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
5834
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path12.sep}`)) {
|
|
5321
5835
|
return entry2;
|
|
5322
5836
|
}
|
|
5323
5837
|
}
|
|
@@ -5482,7 +5996,7 @@ function isNpxInvocation() {
|
|
|
5482
5996
|
function commandPrefix() {
|
|
5483
5997
|
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
5484
5998
|
}
|
|
5485
|
-
function
|
|
5999
|
+
function usage4() {
|
|
5486
6000
|
const neat = commandPrefix();
|
|
5487
6001
|
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
5488
6002
|
console.log("");
|
|
@@ -5502,10 +6016,12 @@ function usage2() {
|
|
|
5502
6016
|
console.log(" PORT (default 8080), OTEL_PORT (4318), HOST (0.0.0.0)");
|
|
5503
6017
|
console.log(" control listeners. NEAT_OTLP_GRPC=true also opens 4317.");
|
|
5504
6018
|
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("
|
|
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.");
|
|
5509
6025
|
console.log(" Flags:");
|
|
5510
6026
|
console.log(" --project <name> watch a registered project by name");
|
|
5511
6027
|
console.log(" --json emit one JSON object per line");
|
|
@@ -5535,6 +6051,22 @@ function usage2() {
|
|
|
5535
6051
|
console.log(" --print-hook print the hook script");
|
|
5536
6052
|
console.log(" --print-guide print the graph-first guidance");
|
|
5537
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)");
|
|
5538
6070
|
console.log(" deploy Detect the deploy substrate, generate NEAT_AUTH_TOKEN,");
|
|
5539
6071
|
console.log(" emit a docker-compose / systemd / docker run artifact, and");
|
|
5540
6072
|
console.log(" print the OTel env-vars block to paste into your platform.");
|
|
@@ -5548,7 +6080,8 @@ function usage2() {
|
|
|
5548
6080
|
console.log(" --no-instrument skip the SDK install apply step");
|
|
5549
6081
|
console.log(" --json emit the delta summary as JSON");
|
|
5550
6082
|
console.log(" connector Configure OBSERVED connectors \u2014 pull (supabase, railway,");
|
|
5551
|
-
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).");
|
|
5552
6085
|
console.log(" Subcommands:");
|
|
5553
6086
|
console.log(" add <provider> add a connector; validates the credential");
|
|
5554
6087
|
console.log(" against the provider first (--skip-validate to skip)");
|
|
@@ -5780,7 +6313,7 @@ async function buildPatchSections(services, project) {
|
|
|
5780
6313
|
}
|
|
5781
6314
|
async function runInit(opts) {
|
|
5782
6315
|
const written = [];
|
|
5783
|
-
const stat = await
|
|
6316
|
+
const stat = await fs11.stat(opts.scanPath).catch(() => null);
|
|
5784
6317
|
if (!stat || !stat.isDirectory()) {
|
|
5785
6318
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
5786
6319
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -5789,13 +6322,13 @@ async function runInit(opts) {
|
|
|
5789
6322
|
printDiscoveryReport(opts, services);
|
|
5790
6323
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
5791
6324
|
const patch = renderPatch(sections);
|
|
5792
|
-
const patchPath =
|
|
6325
|
+
const patchPath = path13.join(opts.scanPath, "neat.patch");
|
|
5793
6326
|
if (opts.dryRun) {
|
|
5794
|
-
await
|
|
6327
|
+
await fs11.writeFile(patchPath, patch, "utf8");
|
|
5795
6328
|
written.push(patchPath);
|
|
5796
6329
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
5797
|
-
const gitignorePath =
|
|
5798
|
-
const gitignoreExists = await
|
|
6330
|
+
const gitignorePath = path13.join(opts.scanPath, ".gitignore");
|
|
6331
|
+
const gitignoreExists = await fs11.stat(gitignorePath).then(() => true).catch(() => false);
|
|
5799
6332
|
const verb = gitignoreExists ? "append" : "create";
|
|
5800
6333
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
5801
6334
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -5806,9 +6339,9 @@ async function runInit(opts) {
|
|
|
5806
6339
|
const graph = getGraph(graphKey);
|
|
5807
6340
|
const projectPaths = pathsForProject(
|
|
5808
6341
|
graphKey,
|
|
5809
|
-
|
|
6342
|
+
path13.join(opts.scanPath, "neat-out")
|
|
5810
6343
|
);
|
|
5811
|
-
const errorsPath =
|
|
6344
|
+
const errorsPath = path13.join(path13.dirname(opts.outPath), path13.basename(projectPaths.errorsPath));
|
|
5812
6345
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
5813
6346
|
await saveGraphToDisk(graph, opts.outPath);
|
|
5814
6347
|
written.push(opts.outPath);
|
|
@@ -5887,7 +6420,7 @@ async function runInit(opts) {
|
|
|
5887
6420
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
5888
6421
|
}
|
|
5889
6422
|
} else {
|
|
5890
|
-
await
|
|
6423
|
+
await fs11.writeFile(patchPath, patch, "utf8");
|
|
5891
6424
|
written.push(patchPath);
|
|
5892
6425
|
}
|
|
5893
6426
|
}
|
|
@@ -5927,9 +6460,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
5927
6460
|
};
|
|
5928
6461
|
function claudeConfigPath() {
|
|
5929
6462
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
5930
|
-
if (override && override.length > 0) return
|
|
6463
|
+
if (override && override.length > 0) return path13.resolve(override);
|
|
5931
6464
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5932
|
-
return
|
|
6465
|
+
return path13.join(home, ".claude.json");
|
|
5933
6466
|
}
|
|
5934
6467
|
async function runSkill(opts) {
|
|
5935
6468
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -5941,7 +6474,7 @@ async function runSkill(opts) {
|
|
|
5941
6474
|
const target = claudeConfigPath();
|
|
5942
6475
|
let existing = {};
|
|
5943
6476
|
try {
|
|
5944
|
-
existing = JSON.parse(await
|
|
6477
|
+
existing = JSON.parse(await fs11.readFile(target, "utf8"));
|
|
5945
6478
|
} catch (err) {
|
|
5946
6479
|
if (err.code !== "ENOENT") {
|
|
5947
6480
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -5953,8 +6486,8 @@ async function runSkill(opts) {
|
|
|
5953
6486
|
...existing,
|
|
5954
6487
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
5955
6488
|
};
|
|
5956
|
-
await
|
|
5957
|
-
await
|
|
6489
|
+
await fs11.mkdir(path13.dirname(target), { recursive: true });
|
|
6490
|
+
await fs11.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
5958
6491
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
5959
6492
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
5960
6493
|
console.log("");
|
|
@@ -5981,7 +6514,7 @@ async function main() {
|
|
|
5981
6514
|
const argv = process.argv.slice(2);
|
|
5982
6515
|
const cmd0 = argv[0];
|
|
5983
6516
|
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
5984
|
-
|
|
6517
|
+
usage4();
|
|
5985
6518
|
process.exit(0);
|
|
5986
6519
|
}
|
|
5987
6520
|
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
@@ -5998,6 +6531,16 @@ async function main() {
|
|
|
5998
6531
|
if (code !== 0) process.exit(code);
|
|
5999
6532
|
return;
|
|
6000
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
|
+
}
|
|
6001
6544
|
const argvParsed = parseArgs(argv);
|
|
6002
6545
|
if (argvParsed.positional.length === 0) {
|
|
6003
6546
|
const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
|
|
@@ -6012,19 +6555,19 @@ async function main() {
|
|
|
6012
6555
|
const target = positional[0];
|
|
6013
6556
|
if (!target) {
|
|
6014
6557
|
console.error("neat init: missing <path>");
|
|
6015
|
-
|
|
6558
|
+
usage4();
|
|
6016
6559
|
process.exit(2);
|
|
6017
6560
|
}
|
|
6018
6561
|
if (apply4 && dryRun) {
|
|
6019
6562
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
6020
6563
|
process.exit(2);
|
|
6021
6564
|
}
|
|
6022
|
-
const scanPath =
|
|
6565
|
+
const scanPath = path13.resolve(target);
|
|
6023
6566
|
const projectExplicit = parsed.project !== null;
|
|
6024
|
-
const projectName = projectExplicit ? project :
|
|
6567
|
+
const projectName = projectExplicit ? project : path13.basename(scanPath);
|
|
6025
6568
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
6026
|
-
const fallback = pathsForProject(projectKey,
|
|
6027
|
-
const outPath =
|
|
6569
|
+
const fallback = pathsForProject(projectKey, path13.join(scanPath, "neat-out")).snapshotPath;
|
|
6570
|
+
const outPath = path13.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
6028
6571
|
const result = await runInit({
|
|
6029
6572
|
scanPath,
|
|
6030
6573
|
outPath,
|
|
@@ -6042,24 +6585,24 @@ async function main() {
|
|
|
6042
6585
|
const target = positional[0];
|
|
6043
6586
|
if (!target) {
|
|
6044
6587
|
console.error("neat watch: missing <path>");
|
|
6045
|
-
|
|
6588
|
+
usage4();
|
|
6046
6589
|
process.exit(2);
|
|
6047
6590
|
}
|
|
6048
|
-
const scanPath =
|
|
6049
|
-
const stat = await
|
|
6591
|
+
const scanPath = path13.resolve(target);
|
|
6592
|
+
const stat = await fs11.stat(scanPath).catch(() => null);
|
|
6050
6593
|
if (!stat || !stat.isDirectory()) {
|
|
6051
6594
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
6052
6595
|
process.exit(2);
|
|
6053
6596
|
}
|
|
6054
|
-
const projectPaths = pathsForProject(project,
|
|
6055
|
-
const outPath =
|
|
6056
|
-
const errorsPath =
|
|
6057
|
-
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))
|
|
6058
6601
|
);
|
|
6059
|
-
const staleEventsPath =
|
|
6060
|
-
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))
|
|
6061
6604
|
);
|
|
6062
|
-
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;
|
|
6063
6606
|
const handle = await startWatch(getGraph(project), {
|
|
6064
6607
|
scanPath,
|
|
6065
6608
|
outPath,
|
|
@@ -6068,7 +6611,7 @@ async function main() {
|
|
|
6068
6611
|
project,
|
|
6069
6612
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
6070
6613
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
6071
|
-
neatHome: process.env.NEAT_HOME ?
|
|
6614
|
+
neatHome: process.env.NEAT_HOME ? path13.resolve(process.env.NEAT_HOME) : path13.join(os4.homedir(), ".neat"),
|
|
6072
6615
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
6073
6616
|
host: process.env.HOST ?? "0.0.0.0",
|
|
6074
6617
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -6104,7 +6647,7 @@ async function main() {
|
|
|
6104
6647
|
const name = positional[0];
|
|
6105
6648
|
if (!name) {
|
|
6106
6649
|
console.error("neat pause: missing <name>");
|
|
6107
|
-
|
|
6650
|
+
usage4();
|
|
6108
6651
|
process.exit(2);
|
|
6109
6652
|
}
|
|
6110
6653
|
const daemon = await findDaemonByProject(name);
|
|
@@ -6129,7 +6672,7 @@ async function main() {
|
|
|
6129
6672
|
const name = positional[0];
|
|
6130
6673
|
if (!name) {
|
|
6131
6674
|
console.error("neat resume: missing <name>");
|
|
6132
|
-
|
|
6675
|
+
usage4();
|
|
6133
6676
|
process.exit(2);
|
|
6134
6677
|
}
|
|
6135
6678
|
const daemon = await findDaemonByProject(name);
|
|
@@ -6159,7 +6702,7 @@ async function main() {
|
|
|
6159
6702
|
const name = positional[0];
|
|
6160
6703
|
if (!name) {
|
|
6161
6704
|
console.error("neat uninstall: missing <name>");
|
|
6162
|
-
|
|
6705
|
+
usage4();
|
|
6163
6706
|
process.exit(2);
|
|
6164
6707
|
}
|
|
6165
6708
|
const daemon = await findDaemonByProject(name);
|
|
@@ -6246,15 +6789,15 @@ async function main() {
|
|
|
6246
6789
|
return;
|
|
6247
6790
|
}
|
|
6248
6791
|
console.error(`neat: unknown command "${cmd}"`);
|
|
6249
|
-
|
|
6792
|
+
usage4();
|
|
6250
6793
|
process.exit(1);
|
|
6251
6794
|
}
|
|
6252
6795
|
async function tryOrchestrator(cmd, parsed) {
|
|
6253
|
-
const scanPath =
|
|
6254
|
-
const stat = await
|
|
6796
|
+
const scanPath = path13.resolve(cmd);
|
|
6797
|
+
const stat = await fs11.stat(scanPath).catch(() => null);
|
|
6255
6798
|
if (!stat || !stat.isDirectory()) return null;
|
|
6256
6799
|
const projectExplicit = parsed.project !== null;
|
|
6257
|
-
const projectName = projectExplicit ? parsed.project :
|
|
6800
|
+
const projectName = projectExplicit ? parsed.project : path13.basename(scanPath);
|
|
6258
6801
|
const result = await runOrchestrator({
|
|
6259
6802
|
scanPath,
|
|
6260
6803
|
project: projectName,
|
|
@@ -6542,6 +7085,6 @@ export {
|
|
|
6542
7085
|
runMonitorVerb,
|
|
6543
7086
|
runQueryVerb,
|
|
6544
7087
|
runSkill,
|
|
6545
|
-
|
|
7088
|
+
usage4 as usage
|
|
6546
7089
|
};
|
|
6547
7090
|
//# sourceMappingURL=cli.js.map
|