@neat.is/core 0.10.1-dev.20260926 → 0.10.1-dev.20260928
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-J3VVEB45.js → chunk-CNOJ4T6I.js} +135 -7
- package/dist/chunk-CNOJ4T6I.js.map +1 -0
- package/dist/{chunk-EJPNEEJA.js → chunk-HVBD4ULO.js} +94 -48
- package/dist/chunk-HVBD4ULO.js.map +1 -0
- package/dist/cli.cjs +465 -202
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +265 -129
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +226 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +226 -52
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +134 -6
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-EJPNEEJA.js.map +0 -1
- package/dist/chunk-J3VVEB45.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
resolveNeatVersion,
|
|
9
9
|
startK8sSubstratePolling,
|
|
10
10
|
writeDaemonRecord
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-HVBD4ULO.js";
|
|
12
12
|
import {
|
|
13
13
|
buildSearchIndex
|
|
14
14
|
} from "./chunk-BC53SCT7.js";
|
|
@@ -63,6 +63,7 @@ import {
|
|
|
63
63
|
pruneRegistry,
|
|
64
64
|
readConnectorsConfig,
|
|
65
65
|
readErrorEvents,
|
|
66
|
+
readRegistry,
|
|
66
67
|
removeConnectorEntry,
|
|
67
68
|
removeDaemonRecord,
|
|
68
69
|
removeProject,
|
|
@@ -77,7 +78,7 @@ import {
|
|
|
77
78
|
startStalenessLoop,
|
|
78
79
|
upsertConnectorEntry,
|
|
79
80
|
validateConnectorEntry
|
|
80
|
-
} from "./chunk-
|
|
81
|
+
} from "./chunk-CNOJ4T6I.js";
|
|
81
82
|
import {
|
|
82
83
|
startOtelGrpcReceiver
|
|
83
84
|
} from "./chunk-ERE47MCR.js";
|
|
@@ -91,9 +92,9 @@ import {
|
|
|
91
92
|
} from "./chunk-GDGUY4T6.js";
|
|
92
93
|
|
|
93
94
|
// src/cli.ts
|
|
94
|
-
import
|
|
95
|
+
import path19 from "path";
|
|
95
96
|
import os5 from "os";
|
|
96
|
-
import { promises as
|
|
97
|
+
import { promises as fs17 } from "fs";
|
|
97
98
|
|
|
98
99
|
// src/banner.ts
|
|
99
100
|
import path from "path";
|
|
@@ -5245,7 +5246,7 @@ async function clearActiveProfile(home = neatHome()) {
|
|
|
5245
5246
|
}
|
|
5246
5247
|
|
|
5247
5248
|
// src/login-sso.ts
|
|
5248
|
-
var DEFAULT_CP_URL = "https://neat
|
|
5249
|
+
var DEFAULT_CP_URL = "https://api.neat.is";
|
|
5249
5250
|
var DEFAULT_WEB_URL = "https://app.neat.is";
|
|
5250
5251
|
var CALLBACK_TIMEOUT_MS = 5 * 6e4;
|
|
5251
5252
|
function resolveCpUrl(env = process.env, override) {
|
|
@@ -5434,6 +5435,11 @@ async function runSsoLogin(opts, deps) {
|
|
|
5434
5435
|
out(` OTEL_EXPORTER_OTLP_ENDPOINT=${cred.ingestEndpoint}`);
|
|
5435
5436
|
out(` OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${cred.otelToken}`);
|
|
5436
5437
|
}
|
|
5438
|
+
out("");
|
|
5439
|
+
out("This directory isn't part of that graph yet \u2014 logging in connected your account,");
|
|
5440
|
+
out("not this repo, and nothing here has been uploaded.");
|
|
5441
|
+
out(` Manage projects and repos: ${opts.webUrl}`);
|
|
5442
|
+
out("");
|
|
5437
5443
|
out("Run `neat logout` to switch back to your local daemon.");
|
|
5438
5444
|
}
|
|
5439
5445
|
return 0;
|
|
@@ -5568,10 +5574,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5568
5574
|
const root = baseUrl.replace(/\/$/, "");
|
|
5569
5575
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
5570
5576
|
return {
|
|
5571
|
-
async get(
|
|
5577
|
+
async get(path20) {
|
|
5572
5578
|
let res;
|
|
5573
5579
|
try {
|
|
5574
|
-
res = await fetchImpl(`${root}${
|
|
5580
|
+
res = await fetchImpl(`${root}${path20}`, {
|
|
5575
5581
|
headers: { ...authHeader }
|
|
5576
5582
|
});
|
|
5577
5583
|
} catch (err) {
|
|
@@ -5583,16 +5589,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5583
5589
|
const body = await res.text().catch(() => "");
|
|
5584
5590
|
throw new HttpError(
|
|
5585
5591
|
res.status,
|
|
5586
|
-
`${res.status} ${res.statusText} on GET ${
|
|
5592
|
+
`${res.status} ${res.statusText} on GET ${path20}: ${body}`,
|
|
5587
5593
|
body
|
|
5588
5594
|
);
|
|
5589
5595
|
}
|
|
5590
5596
|
return await res.json();
|
|
5591
5597
|
},
|
|
5592
|
-
async post(
|
|
5598
|
+
async post(path20, body) {
|
|
5593
5599
|
let res;
|
|
5594
5600
|
try {
|
|
5595
|
-
res = await fetchImpl(`${root}${
|
|
5601
|
+
res = await fetchImpl(`${root}${path20}`, {
|
|
5596
5602
|
method: "POST",
|
|
5597
5603
|
headers: { "content-type": "application/json", ...authHeader },
|
|
5598
5604
|
body: JSON.stringify(body)
|
|
@@ -5606,7 +5612,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5606
5612
|
const text = await res.text().catch(() => "");
|
|
5607
5613
|
throw new HttpError(
|
|
5608
5614
|
res.status,
|
|
5609
|
-
`${res.status} ${res.statusText} on POST ${
|
|
5615
|
+
`${res.status} ${res.statusText} on POST ${path20}: ${text}`,
|
|
5610
5616
|
text
|
|
5611
5617
|
);
|
|
5612
5618
|
}
|
|
@@ -5620,12 +5626,12 @@ function projectPath(project, suffix) {
|
|
|
5620
5626
|
}
|
|
5621
5627
|
async function runRootCause(client, input) {
|
|
5622
5628
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
5623
|
-
const
|
|
5629
|
+
const path20 = projectPath(
|
|
5624
5630
|
input.project,
|
|
5625
5631
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
5626
5632
|
);
|
|
5627
5633
|
try {
|
|
5628
|
-
const result = await client.get(
|
|
5634
|
+
const result = await client.get(path20);
|
|
5629
5635
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
5630
5636
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
5631
5637
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -5651,12 +5657,12 @@ async function runRootCause(client, input) {
|
|
|
5651
5657
|
}
|
|
5652
5658
|
async function runBlastRadius(client, input) {
|
|
5653
5659
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
5654
|
-
const
|
|
5660
|
+
const path20 = projectPath(
|
|
5655
5661
|
input.project,
|
|
5656
5662
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
5657
5663
|
);
|
|
5658
5664
|
try {
|
|
5659
|
-
const result = await client.get(
|
|
5665
|
+
const result = await client.get(path20);
|
|
5660
5666
|
if (result.totalAffected === 0) {
|
|
5661
5667
|
return {
|
|
5662
5668
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -5690,12 +5696,12 @@ function formatBlastEntry(n) {
|
|
|
5690
5696
|
}
|
|
5691
5697
|
async function runDependencies(client, input) {
|
|
5692
5698
|
const depth = input.depth ?? 3;
|
|
5693
|
-
const
|
|
5699
|
+
const path20 = projectPath(
|
|
5694
5700
|
input.project,
|
|
5695
5701
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
5696
5702
|
);
|
|
5697
5703
|
try {
|
|
5698
|
-
const result = await client.get(
|
|
5704
|
+
const result = await client.get(path20);
|
|
5699
5705
|
if (result.total === 0) {
|
|
5700
5706
|
return {
|
|
5701
5707
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -5787,9 +5793,9 @@ function formatDuration(ms) {
|
|
|
5787
5793
|
return `${Math.round(h / 24)}d`;
|
|
5788
5794
|
}
|
|
5789
5795
|
async function runIncidents(client, input) {
|
|
5790
|
-
const
|
|
5796
|
+
const path20 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
5791
5797
|
try {
|
|
5792
|
-
const body = await client.get(
|
|
5798
|
+
const body = await client.get(path20);
|
|
5793
5799
|
const events = body.events;
|
|
5794
5800
|
if (events.length === 0) {
|
|
5795
5801
|
return {
|
|
@@ -6566,6 +6572,8 @@ async function defaultReadSecret(prompt) {
|
|
|
6566
6572
|
}
|
|
6567
6573
|
|
|
6568
6574
|
// src/welcome.ts
|
|
6575
|
+
import { promises as fs12 } from "fs";
|
|
6576
|
+
import path13 from "path";
|
|
6569
6577
|
import readline4 from "readline/promises";
|
|
6570
6578
|
var WORDMARK = [
|
|
6571
6579
|
"\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
@@ -6598,6 +6606,28 @@ function printHeader(out) {
|
|
|
6598
6606
|
}
|
|
6599
6607
|
async function runWelcome(deps = {}) {
|
|
6600
6608
|
const out = deps.out ?? ((line) => console.log(line));
|
|
6609
|
+
try {
|
|
6610
|
+
return await runFrontDoor(deps, out);
|
|
6611
|
+
} catch (err) {
|
|
6612
|
+
if (isPromptCancelled(err)) {
|
|
6613
|
+
out("");
|
|
6614
|
+
return 130;
|
|
6615
|
+
}
|
|
6616
|
+
throw err;
|
|
6617
|
+
}
|
|
6618
|
+
}
|
|
6619
|
+
var PromptCancelled = class extends Error {
|
|
6620
|
+
constructor() {
|
|
6621
|
+
super("prompt cancelled");
|
|
6622
|
+
this.name = "PromptCancelled";
|
|
6623
|
+
}
|
|
6624
|
+
};
|
|
6625
|
+
function isPromptCancelled(err) {
|
|
6626
|
+
if (err instanceof PromptCancelled) return true;
|
|
6627
|
+
const e = err;
|
|
6628
|
+
return e?.code === "ABORT_ERR" || e?.name === "AbortError";
|
|
6629
|
+
}
|
|
6630
|
+
async function runFrontDoor(deps, out) {
|
|
6601
6631
|
const readLine = deps.readLine ?? defaultReadLine2;
|
|
6602
6632
|
const login = deps.login ?? (() => Promise.resolve(0));
|
|
6603
6633
|
const orchestrator = deps.orchestrator ?? (() => Promise.resolve(0));
|
|
@@ -6605,21 +6635,114 @@ async function runWelcome(deps = {}) {
|
|
|
6605
6635
|
printHeader(out);
|
|
6606
6636
|
out("Welcome to NEAT. Let's get you a graph of this system.");
|
|
6607
6637
|
out("");
|
|
6608
|
-
|
|
6609
|
-
|
|
6638
|
+
const keyReader = deps.readKey !== void 0 ? { read: deps.readKey, done: () => {
|
|
6639
|
+
} } : createKeyReader();
|
|
6640
|
+
const picked = keyReader !== null ? await selectOption(out, keyReader, deps.moveCursorUp ?? defaultMoveCursorUp) : await selectByNumber(out, readLine);
|
|
6641
|
+
if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd);
|
|
6642
|
+
if (picked === 0) return login(["--browser"]);
|
|
6643
|
+
return runSelfHosted(out, readLine, orchestrator, cwd);
|
|
6644
|
+
}
|
|
6645
|
+
var MENU_OPTIONS = [
|
|
6646
|
+
"Log me into Hosted Neat",
|
|
6647
|
+
"I'd like to self-host or use it locally (copy a prompt)"
|
|
6648
|
+
];
|
|
6649
|
+
async function selectOption(out, reader, moveCursorUp) {
|
|
6650
|
+
let cursor = 0;
|
|
6651
|
+
const render = () => {
|
|
6652
|
+
for (const [i, label] of MENU_OPTIONS.entries()) {
|
|
6653
|
+
out(i === cursor ? ` \u276F ${i + 1}) ${label}` : ` ${i + 1}) ${label}`);
|
|
6654
|
+
}
|
|
6655
|
+
};
|
|
6656
|
+
try {
|
|
6657
|
+
render();
|
|
6658
|
+
out("");
|
|
6659
|
+
out(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 1 or 2 to jump");
|
|
6660
|
+
for (; ; ) {
|
|
6661
|
+
const key = await reader.read();
|
|
6662
|
+
if (key === void 0) return void 0;
|
|
6663
|
+
if (key === "cancel") throw new PromptCancelled();
|
|
6664
|
+
if (key === "enter") return cursor;
|
|
6665
|
+
if (key === "select-1") return 0;
|
|
6666
|
+
if (key === "select-2") return 1;
|
|
6667
|
+
if (key === "up") cursor = (cursor + MENU_OPTIONS.length - 1) % MENU_OPTIONS.length;
|
|
6668
|
+
else if (key === "down") cursor = (cursor + 1) % MENU_OPTIONS.length;
|
|
6669
|
+
else continue;
|
|
6670
|
+
moveCursorUp(MENU_OPTIONS.length + 2);
|
|
6671
|
+
render();
|
|
6672
|
+
out("");
|
|
6673
|
+
out(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 1 or 2 to jump");
|
|
6674
|
+
}
|
|
6675
|
+
} finally {
|
|
6676
|
+
reader.done();
|
|
6677
|
+
}
|
|
6678
|
+
}
|
|
6679
|
+
async function selectByNumber(out, readLine) {
|
|
6680
|
+
for (const [i, label] of MENU_OPTIONS.entries()) out(` ${i + 1}) ${label}`);
|
|
6610
6681
|
out("");
|
|
6611
6682
|
for (; ; ) {
|
|
6612
6683
|
const choice = (await readLine("Choose 1 or 2 (default 2): "))?.trim();
|
|
6613
|
-
if (choice === void 0) return
|
|
6614
|
-
if (choice === "1")
|
|
6615
|
-
|
|
6616
|
-
}
|
|
6617
|
-
if (choice === "2" || choice === "") {
|
|
6618
|
-
return runSelfHosted(out, readLine, orchestrator, cwd);
|
|
6619
|
-
}
|
|
6620
|
-
out(`"${choice}" isn't 1 or 2 \u2014 try again, or press Enter for self-hosted.`);
|
|
6684
|
+
if (choice === void 0) return void 0;
|
|
6685
|
+
if (choice === "1") return 0;
|
|
6686
|
+
if (choice === "2" || choice === "") return 1;
|
|
6687
|
+
out(`"${choice}" isn't 1 or 2 \u2014 try again, or press Enter for the local path.`);
|
|
6621
6688
|
}
|
|
6622
6689
|
}
|
|
6690
|
+
function createKeyReader() {
|
|
6691
|
+
const stdin = process.stdin;
|
|
6692
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") return null;
|
|
6693
|
+
stdin.setRawMode(true);
|
|
6694
|
+
stdin.resume();
|
|
6695
|
+
let closed = false;
|
|
6696
|
+
let ended = false;
|
|
6697
|
+
const markEnded = () => {
|
|
6698
|
+
ended = true;
|
|
6699
|
+
};
|
|
6700
|
+
stdin.once("end", markEnded);
|
|
6701
|
+
stdin.once("close", markEnded);
|
|
6702
|
+
return {
|
|
6703
|
+
read: () => new Promise((resolve) => {
|
|
6704
|
+
if (ended) {
|
|
6705
|
+
resolve(void 0);
|
|
6706
|
+
return;
|
|
6707
|
+
}
|
|
6708
|
+
const finish = (value) => {
|
|
6709
|
+
stdin.removeListener("data", onData);
|
|
6710
|
+
stdin.removeListener("end", onClosed);
|
|
6711
|
+
stdin.removeListener("close", onClosed);
|
|
6712
|
+
resolve(value);
|
|
6713
|
+
};
|
|
6714
|
+
const onData = (buf) => finish(decodeKey(buf));
|
|
6715
|
+
const onClosed = () => {
|
|
6716
|
+
ended = true;
|
|
6717
|
+
finish(void 0);
|
|
6718
|
+
};
|
|
6719
|
+
stdin.on("data", onData);
|
|
6720
|
+
stdin.once("end", onClosed);
|
|
6721
|
+
stdin.once("close", onClosed);
|
|
6722
|
+
}),
|
|
6723
|
+
done: () => {
|
|
6724
|
+
if (closed) return;
|
|
6725
|
+
closed = true;
|
|
6726
|
+
stdin.removeListener("end", markEnded);
|
|
6727
|
+
stdin.removeListener("close", markEnded);
|
|
6728
|
+
stdin.setRawMode(false);
|
|
6729
|
+
stdin.pause();
|
|
6730
|
+
}
|
|
6731
|
+
};
|
|
6732
|
+
}
|
|
6733
|
+
function decodeKey(buf) {
|
|
6734
|
+
const s = buf.toString("utf8");
|
|
6735
|
+
if (s === "\x1B[A" || s === "k") return "up";
|
|
6736
|
+
if (s === "\x1B[B" || s === "j") return "down";
|
|
6737
|
+
if (s === "\r" || s === "\n") return "enter";
|
|
6738
|
+
if (s === "1") return "select-1";
|
|
6739
|
+
if (s === "2") return "select-2";
|
|
6740
|
+
if (s === "" || s === "\x1B") return "cancel";
|
|
6741
|
+
return "ignore";
|
|
6742
|
+
}
|
|
6743
|
+
function defaultMoveCursorUp(rows) {
|
|
6744
|
+
if (rows > 0) process.stdout.write(`\x1B[${rows}A`);
|
|
6745
|
+
}
|
|
6623
6746
|
async function runSelfHosted(out, readLine, orchestrator, cwd) {
|
|
6624
6747
|
const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
|
|
6625
6748
|
if (wantsPrompt !== "n" && wantsPrompt !== "no") {
|
|
@@ -6639,10 +6762,23 @@ async function shouldShowWelcome(deps = {}) {
|
|
|
6639
6762
|
const stdinTTY = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
6640
6763
|
const stdoutTTY = deps.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
|
|
6641
6764
|
if (!stdinTTY || !stdoutTTY) return false;
|
|
6642
|
-
const
|
|
6765
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
6766
|
+
const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
|
|
6767
|
+
const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
|
|
6643
6768
|
try {
|
|
6644
|
-
const
|
|
6645
|
-
|
|
6769
|
+
const here = path13.resolve(cwd);
|
|
6770
|
+
const { projects } = await readRegistry2();
|
|
6771
|
+
if (projects.some((p) => path13.resolve(p.path) === here)) return false;
|
|
6772
|
+
if (await hasSnapshot(here)) return false;
|
|
6773
|
+
return true;
|
|
6774
|
+
} catch {
|
|
6775
|
+
return false;
|
|
6776
|
+
}
|
|
6777
|
+
}
|
|
6778
|
+
async function defaultHasSnapshot(cwd) {
|
|
6779
|
+
try {
|
|
6780
|
+
const entries = await fs12.readdir(path13.join(cwd, "neat-out"));
|
|
6781
|
+
return entries.some((e) => e.endsWith(".json"));
|
|
6646
6782
|
} catch {
|
|
6647
6783
|
return false;
|
|
6648
6784
|
}
|
|
@@ -6658,27 +6794,27 @@ async function defaultReadLine2(prompt) {
|
|
|
6658
6794
|
}
|
|
6659
6795
|
|
|
6660
6796
|
// src/hooks-cli.ts
|
|
6661
|
-
import
|
|
6797
|
+
import path14 from "path";
|
|
6662
6798
|
import os2 from "os";
|
|
6663
|
-
import { promises as
|
|
6799
|
+
import { promises as fs13 } from "fs";
|
|
6664
6800
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6665
6801
|
var HOOK_FILENAME = "neat-search-nudge.mjs";
|
|
6666
6802
|
var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
6667
6803
|
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
6668
6804
|
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
6669
6805
|
function moduleDir() {
|
|
6670
|
-
return typeof __dirname !== "undefined" ? __dirname :
|
|
6806
|
+
return typeof __dirname !== "undefined" ? __dirname : path14.dirname(fileURLToPath3(import.meta.url));
|
|
6671
6807
|
}
|
|
6672
6808
|
async function readSkillAsset(rel) {
|
|
6673
6809
|
const here = moduleDir();
|
|
6674
6810
|
const candidates = [
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6811
|
+
path14.resolve(here, "../../claude-skill", rel),
|
|
6812
|
+
path14.resolve(here, "../../../claude-skill", rel),
|
|
6813
|
+
path14.resolve(here, "../claude-skill", rel)
|
|
6678
6814
|
];
|
|
6679
6815
|
for (const candidate of candidates) {
|
|
6680
6816
|
try {
|
|
6681
|
-
return await
|
|
6817
|
+
return await fs13.readFile(candidate, "utf8");
|
|
6682
6818
|
} catch {
|
|
6683
6819
|
}
|
|
6684
6820
|
}
|
|
@@ -6688,20 +6824,20 @@ async function readSkillAsset(rel) {
|
|
|
6688
6824
|
}
|
|
6689
6825
|
function neatHome2() {
|
|
6690
6826
|
const override = process.env.NEAT_HOME;
|
|
6691
|
-
if (override && override.length > 0) return
|
|
6692
|
-
return
|
|
6827
|
+
if (override && override.length > 0) return path14.resolve(override);
|
|
6828
|
+
return path14.join(os2.homedir(), ".neat");
|
|
6693
6829
|
}
|
|
6694
6830
|
function claudeSettingsPath() {
|
|
6695
6831
|
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
6696
|
-
if (override && override.length > 0) return
|
|
6832
|
+
if (override && override.length > 0) return path14.resolve(override);
|
|
6697
6833
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
|
|
6698
|
-
return
|
|
6834
|
+
return path14.join(home, ".claude", "settings.json");
|
|
6699
6835
|
}
|
|
6700
6836
|
function installedHookPath() {
|
|
6701
|
-
return
|
|
6837
|
+
return path14.join(neatHome2(), "hooks", HOOK_FILENAME);
|
|
6702
6838
|
}
|
|
6703
6839
|
function gateFlagPath() {
|
|
6704
|
-
return
|
|
6840
|
+
return path14.join(neatHome2(), "hooks", "gate-enabled");
|
|
6705
6841
|
}
|
|
6706
6842
|
function isNeatSearchEntry(entry2) {
|
|
6707
6843
|
return (entry2.hooks ?? []).some(
|
|
@@ -6734,14 +6870,14 @@ async function runHooks(opts) {
|
|
|
6734
6870
|
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
6735
6871
|
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
6736
6872
|
const scriptPath = installedHookPath();
|
|
6737
|
-
await
|
|
6738
|
-
await
|
|
6739
|
-
const guidePath =
|
|
6740
|
-
await
|
|
6873
|
+
await fs13.mkdir(path14.dirname(scriptPath), { recursive: true });
|
|
6874
|
+
await fs13.writeFile(scriptPath, hookScript, { mode: 493 });
|
|
6875
|
+
const guidePath = path14.join(neatHome2(), GUIDE_INSTALL_NAME);
|
|
6876
|
+
await fs13.writeFile(guidePath, guide, "utf8");
|
|
6741
6877
|
const settingsFile = claudeSettingsPath();
|
|
6742
6878
|
let settings = {};
|
|
6743
6879
|
try {
|
|
6744
|
-
settings = JSON.parse(await
|
|
6880
|
+
settings = JSON.parse(await fs13.readFile(settingsFile, "utf8"));
|
|
6745
6881
|
} catch (err) {
|
|
6746
6882
|
if (err.code !== "ENOENT") {
|
|
6747
6883
|
console.error(
|
|
@@ -6763,14 +6899,14 @@ async function runHooks(opts) {
|
|
|
6763
6899
|
...settings,
|
|
6764
6900
|
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
6765
6901
|
};
|
|
6766
|
-
await
|
|
6767
|
-
await
|
|
6902
|
+
await fs13.mkdir(path14.dirname(settingsFile), { recursive: true });
|
|
6903
|
+
await fs13.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
6768
6904
|
const flag = gateFlagPath();
|
|
6769
6905
|
if (opts.gate) {
|
|
6770
|
-
await
|
|
6771
|
-
await
|
|
6906
|
+
await fs13.mkdir(path14.dirname(flag), { recursive: true });
|
|
6907
|
+
await fs13.writeFile(flag, "1\n", "utf8");
|
|
6772
6908
|
} else {
|
|
6773
|
-
await
|
|
6909
|
+
await fs13.rm(flag, { force: true });
|
|
6774
6910
|
}
|
|
6775
6911
|
const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
|
|
6776
6912
|
console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
|
|
@@ -6859,8 +6995,8 @@ async function runHooksCommand(args) {
|
|
|
6859
6995
|
}
|
|
6860
6996
|
|
|
6861
6997
|
// src/claude-cli.ts
|
|
6862
|
-
import
|
|
6863
|
-
import { promises as
|
|
6998
|
+
import path15 from "path";
|
|
6999
|
+
import { promises as fs14 } from "fs";
|
|
6864
7000
|
var NEAT_SECTION_HEADING = "## neat";
|
|
6865
7001
|
var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
|
|
6866
7002
|
code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
|
|
@@ -6894,8 +7030,8 @@ ${NEAT_DIRECTIVE_BODY}
|
|
|
6894
7030
|
}
|
|
6895
7031
|
function claudeMdPath() {
|
|
6896
7032
|
const override = process.env.NEAT_CLAUDE_MD;
|
|
6897
|
-
if (override && override.length > 0) return
|
|
6898
|
-
return
|
|
7033
|
+
if (override && override.length > 0) return path15.resolve(override);
|
|
7034
|
+
return path15.join(process.cwd(), "CLAUDE.md");
|
|
6899
7035
|
}
|
|
6900
7036
|
function splitAroundSection(raw) {
|
|
6901
7037
|
const lines = raw.split("\n");
|
|
@@ -6923,7 +7059,7 @@ function compose(before, after) {
|
|
|
6923
7059
|
}
|
|
6924
7060
|
async function readIfExists(file) {
|
|
6925
7061
|
try {
|
|
6926
|
-
return await
|
|
7062
|
+
return await fs14.readFile(file, "utf8");
|
|
6927
7063
|
} catch (err) {
|
|
6928
7064
|
if (err.code === "ENOENT") return null;
|
|
6929
7065
|
throw err;
|
|
@@ -6934,8 +7070,8 @@ async function runInstall() {
|
|
|
6934
7070
|
const raw = await readIfExists(file) ?? "";
|
|
6935
7071
|
const { before, after, found } = splitAroundSection(raw);
|
|
6936
7072
|
const next = compose(before, after);
|
|
6937
|
-
await
|
|
6938
|
-
await
|
|
7073
|
+
await fs14.mkdir(path15.dirname(file), { recursive: true });
|
|
7074
|
+
await fs14.writeFile(file, next, "utf8");
|
|
6939
7075
|
const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
|
|
6940
7076
|
console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
|
|
6941
7077
|
console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
|
|
@@ -6956,7 +7092,7 @@ async function runUninstall() {
|
|
|
6956
7092
|
}
|
|
6957
7093
|
const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
|
|
6958
7094
|
const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
|
|
6959
|
-
await
|
|
7095
|
+
await fs14.writeFile(file, next, "utf8");
|
|
6960
7096
|
console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
|
|
6961
7097
|
return { exitCode: 0 };
|
|
6962
7098
|
}
|
|
@@ -6998,9 +7134,9 @@ async function runClaudeCommand(args) {
|
|
|
6998
7134
|
}
|
|
6999
7135
|
|
|
7000
7136
|
// src/codex-cli.ts
|
|
7001
|
-
import
|
|
7137
|
+
import path16 from "path";
|
|
7002
7138
|
import os3 from "os";
|
|
7003
|
-
import { promises as
|
|
7139
|
+
import { promises as fs15 } from "fs";
|
|
7004
7140
|
import { isDeepStrictEqual } from "util";
|
|
7005
7141
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
7006
7142
|
var CODEX_MCP_SERVER = {
|
|
@@ -7018,14 +7154,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
7018
7154
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
7019
7155
|
function codexConfigPath() {
|
|
7020
7156
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
7021
|
-
if (override && override.length > 0) return
|
|
7157
|
+
if (override && override.length > 0) return path16.resolve(override);
|
|
7022
7158
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? os3.homedir();
|
|
7023
|
-
return
|
|
7159
|
+
return path16.join(home, ".codex", "config.toml");
|
|
7024
7160
|
}
|
|
7025
7161
|
function agentsFilePath() {
|
|
7026
7162
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
7027
|
-
if (override && override.length > 0) return
|
|
7028
|
-
return
|
|
7163
|
+
if (override && override.length > 0) return path16.resolve(override);
|
|
7164
|
+
return path16.join(process.cwd(), "AGENTS.md");
|
|
7029
7165
|
}
|
|
7030
7166
|
function isTableHeader(line) {
|
|
7031
7167
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -7159,7 +7295,7 @@ async function runCodex(opts) {
|
|
|
7159
7295
|
const agentsPath = agentsFilePath();
|
|
7160
7296
|
let configRaw = "";
|
|
7161
7297
|
try {
|
|
7162
|
-
configRaw = await
|
|
7298
|
+
configRaw = await fs15.readFile(configPath, "utf8");
|
|
7163
7299
|
} catch (err) {
|
|
7164
7300
|
if (err.code !== "ENOENT") {
|
|
7165
7301
|
console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
|
|
@@ -7168,7 +7304,7 @@ async function runCodex(opts) {
|
|
|
7168
7304
|
}
|
|
7169
7305
|
let agentsRaw = "";
|
|
7170
7306
|
try {
|
|
7171
|
-
agentsRaw = await
|
|
7307
|
+
agentsRaw = await fs15.readFile(agentsPath, "utf8");
|
|
7172
7308
|
} catch (err) {
|
|
7173
7309
|
if (err.code !== "ENOENT") {
|
|
7174
7310
|
console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
|
|
@@ -7208,15 +7344,15 @@ async function runCodex(opts) {
|
|
|
7208
7344
|
return { exitCode: 0 };
|
|
7209
7345
|
}
|
|
7210
7346
|
if (config.changed) {
|
|
7211
|
-
await
|
|
7212
|
-
await
|
|
7347
|
+
await fs15.mkdir(path16.dirname(configPath), { recursive: true });
|
|
7348
|
+
await fs15.writeFile(configPath, config.text, "utf8");
|
|
7213
7349
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
7214
7350
|
} else {
|
|
7215
7351
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
7216
7352
|
}
|
|
7217
7353
|
if (agents.changed) {
|
|
7218
|
-
await
|
|
7219
|
-
await
|
|
7354
|
+
await fs15.mkdir(path16.dirname(agentsPath), { recursive: true });
|
|
7355
|
+
await fs15.writeFile(agentsPath, agents.text, "utf8");
|
|
7220
7356
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
7221
7357
|
} else {
|
|
7222
7358
|
console.log(`neat codex: ${agentsPath} already has the graph-first block`);
|
|
@@ -7272,9 +7408,9 @@ async function runCodexCommand(args) {
|
|
|
7272
7408
|
}
|
|
7273
7409
|
|
|
7274
7410
|
// src/editors-cli.ts
|
|
7275
|
-
import
|
|
7411
|
+
import path17 from "path";
|
|
7276
7412
|
import os4 from "os";
|
|
7277
|
-
import { promises as
|
|
7413
|
+
import { promises as fs16 } from "fs";
|
|
7278
7414
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
7279
7415
|
import * as jsonc from "jsonc-parser";
|
|
7280
7416
|
var NEAT_MCP_SERVER = {
|
|
@@ -7298,17 +7434,17 @@ function homeDir() {
|
|
|
7298
7434
|
}
|
|
7299
7435
|
function xdgConfigDir() {
|
|
7300
7436
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
7301
|
-
return xdg && xdg.length > 0 ?
|
|
7437
|
+
return xdg && xdg.length > 0 ? path17.resolve(xdg) : path17.join(homeDir(), ".config");
|
|
7302
7438
|
}
|
|
7303
7439
|
function envOverride(name) {
|
|
7304
7440
|
const v = process.env[name];
|
|
7305
|
-
return v && v.length > 0 ?
|
|
7441
|
+
return v && v.length > 0 ? path17.resolve(v) : void 0;
|
|
7306
7442
|
}
|
|
7307
7443
|
var CURSOR_CLIENT = {
|
|
7308
7444
|
id: "cursor",
|
|
7309
7445
|
label: "Cursor",
|
|
7310
7446
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
7311
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
7447
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path17.join(homeDir(), ".cursor", "mcp.json"),
|
|
7312
7448
|
mcpContainerKey: "mcpServers",
|
|
7313
7449
|
format: "json",
|
|
7314
7450
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -7320,7 +7456,7 @@ var DEVIN_CLIENT = {
|
|
|
7320
7456
|
id: "devin",
|
|
7321
7457
|
label: "Devin Desktop (Cascade)",
|
|
7322
7458
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
7323
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
7459
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path17.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
7324
7460
|
mcpContainerKey: "mcpServers",
|
|
7325
7461
|
format: "json",
|
|
7326
7462
|
rulesFileName: ".windsurfrules"
|
|
@@ -7329,7 +7465,7 @@ var GEMINI_CLIENT = {
|
|
|
7329
7465
|
id: "gemini",
|
|
7330
7466
|
label: "Gemini CLI",
|
|
7331
7467
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
7332
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
7468
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path17.join(homeDir(), ".gemini", "settings.json"),
|
|
7333
7469
|
mcpContainerKey: "mcpServers",
|
|
7334
7470
|
format: "json",
|
|
7335
7471
|
rulesFileName: "GEMINI.md"
|
|
@@ -7338,7 +7474,7 @@ var QWEN_CLIENT = {
|
|
|
7338
7474
|
id: "qwen",
|
|
7339
7475
|
label: "Qwen Code",
|
|
7340
7476
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
7341
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
7477
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path17.join(homeDir(), ".qwen", "settings.json"),
|
|
7342
7478
|
mcpContainerKey: "mcpServers",
|
|
7343
7479
|
format: "json",
|
|
7344
7480
|
rulesFileName: "QWEN.md"
|
|
@@ -7347,7 +7483,7 @@ var AMAZONQ_CLIENT = {
|
|
|
7347
7483
|
id: "amazonq",
|
|
7348
7484
|
label: "Amazon Q Developer CLI",
|
|
7349
7485
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
7350
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
7486
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path17.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
7351
7487
|
mcpContainerKey: "mcpServers",
|
|
7352
7488
|
format: "json"
|
|
7353
7489
|
};
|
|
@@ -7355,7 +7491,7 @@ var ROOCODE_CLIENT = {
|
|
|
7355
7491
|
id: "roocode",
|
|
7356
7492
|
label: "Roo Code",
|
|
7357
7493
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
7358
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
7494
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path17.join(process.cwd(), ".roo", "mcp.json"),
|
|
7359
7495
|
mcpContainerKey: "mcpServers",
|
|
7360
7496
|
format: "json"
|
|
7361
7497
|
};
|
|
@@ -7368,9 +7504,9 @@ var ZED_CLIENT = {
|
|
|
7368
7504
|
if (override) return override;
|
|
7369
7505
|
if (process.platform === "win32") {
|
|
7370
7506
|
const appData = process.env.APPDATA;
|
|
7371
|
-
if (appData && appData.length > 0) return
|
|
7507
|
+
if (appData && appData.length > 0) return path17.join(appData, "Zed", "settings.json");
|
|
7372
7508
|
}
|
|
7373
|
-
return
|
|
7509
|
+
return path17.join(homeDir(), ".config", "zed", "settings.json");
|
|
7374
7510
|
},
|
|
7375
7511
|
mcpContainerKey: "context_servers",
|
|
7376
7512
|
format: "jsonc",
|
|
@@ -7380,7 +7516,7 @@ var OPENCODE_CLIENT = {
|
|
|
7380
7516
|
id: "opencode",
|
|
7381
7517
|
label: "OpenCode",
|
|
7382
7518
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
7383
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
7519
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path17.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
7384
7520
|
mcpContainerKey: "mcp",
|
|
7385
7521
|
format: "json",
|
|
7386
7522
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -7390,7 +7526,7 @@ var CRUSH_CLIENT = {
|
|
|
7390
7526
|
id: "crush",
|
|
7391
7527
|
label: "Crush",
|
|
7392
7528
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
7393
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
7529
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path17.join(xdgConfigDir(), "crush", "crush.json"),
|
|
7394
7530
|
mcpContainerKey: "mcp",
|
|
7395
7531
|
format: "json",
|
|
7396
7532
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -7453,7 +7589,7 @@ async function planMcp(client, mcpPath) {
|
|
|
7453
7589
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
7454
7590
|
let raw = "";
|
|
7455
7591
|
try {
|
|
7456
|
-
raw = await
|
|
7592
|
+
raw = await fs16.readFile(mcpPath, "utf8");
|
|
7457
7593
|
} catch (err) {
|
|
7458
7594
|
const e = err;
|
|
7459
7595
|
if (e.code === "ENOENT") {
|
|
@@ -7495,7 +7631,7 @@ async function runEditorInstall(client, opts) {
|
|
|
7495
7631
|
const mcpPath = client.mcpConfigPath();
|
|
7496
7632
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
7497
7633
|
const hasRules = typeof client.rulesFileName === "string";
|
|
7498
|
-
const rulesPath = hasRules ?
|
|
7634
|
+
const rulesPath = hasRules ? path17.join(opts.projectDir, client.rulesFileName) : "";
|
|
7499
7635
|
const mcp = await planMcp(client, mcpPath);
|
|
7500
7636
|
if (mcp === null) return { exitCode: 1 };
|
|
7501
7637
|
let existingRules = "";
|
|
@@ -7504,7 +7640,7 @@ async function runEditorInstall(client, opts) {
|
|
|
7504
7640
|
let block = "";
|
|
7505
7641
|
if (hasRules) {
|
|
7506
7642
|
try {
|
|
7507
|
-
existingRules = await
|
|
7643
|
+
existingRules = await fs16.readFile(rulesPath, "utf8");
|
|
7508
7644
|
} catch (err) {
|
|
7509
7645
|
if (err.code !== "ENOENT") {
|
|
7510
7646
|
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
@@ -7538,11 +7674,11 @@ async function runEditorInstall(client, opts) {
|
|
|
7538
7674
|
);
|
|
7539
7675
|
return { exitCode: 0 };
|
|
7540
7676
|
}
|
|
7541
|
-
await
|
|
7542
|
-
await
|
|
7677
|
+
await fs16.mkdir(path17.dirname(mcpPath), { recursive: true });
|
|
7678
|
+
await fs16.writeFile(mcpPath, mcp.text, "utf8");
|
|
7543
7679
|
if (hasRules) {
|
|
7544
|
-
await
|
|
7545
|
-
await
|
|
7680
|
+
await fs16.mkdir(path17.dirname(rulesPath), { recursive: true });
|
|
7681
|
+
await fs16.writeFile(rulesPath, newRules, "utf8");
|
|
7546
7682
|
}
|
|
7547
7683
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
7548
7684
|
console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
|
|
@@ -8042,7 +8178,7 @@ function sleep(ms, signal) {
|
|
|
8042
8178
|
}
|
|
8043
8179
|
|
|
8044
8180
|
// src/cli-verbs.ts
|
|
8045
|
-
import
|
|
8181
|
+
import path18 from "path";
|
|
8046
8182
|
async function resolveProjectEntry(opts) {
|
|
8047
8183
|
const entries = await listProjects();
|
|
8048
8184
|
if (opts.project) {
|
|
@@ -8052,7 +8188,7 @@ async function resolveProjectEntry(opts) {
|
|
|
8052
8188
|
const cwd = opts.cwd ?? process.cwd();
|
|
8053
8189
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
8054
8190
|
for (const entry2 of entries) {
|
|
8055
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
8191
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path18.sep}`)) {
|
|
8056
8192
|
return entry2;
|
|
8057
8193
|
}
|
|
8058
8194
|
}
|
|
@@ -8629,7 +8765,7 @@ async function buildPatchSections(services, project) {
|
|
|
8629
8765
|
}
|
|
8630
8766
|
async function runInit(opts) {
|
|
8631
8767
|
const written = [];
|
|
8632
|
-
const stat = await
|
|
8768
|
+
const stat = await fs17.stat(opts.scanPath).catch(() => null);
|
|
8633
8769
|
if (!stat || !stat.isDirectory()) {
|
|
8634
8770
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
8635
8771
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -8638,13 +8774,13 @@ async function runInit(opts) {
|
|
|
8638
8774
|
printDiscoveryReport(opts, services);
|
|
8639
8775
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
8640
8776
|
const patch = renderPatch(sections);
|
|
8641
|
-
const patchPath =
|
|
8777
|
+
const patchPath = path19.join(opts.scanPath, "neat.patch");
|
|
8642
8778
|
if (opts.dryRun) {
|
|
8643
|
-
await
|
|
8779
|
+
await fs17.writeFile(patchPath, patch, "utf8");
|
|
8644
8780
|
written.push(patchPath);
|
|
8645
8781
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
8646
|
-
const gitignorePath =
|
|
8647
|
-
const gitignoreExists = await
|
|
8782
|
+
const gitignorePath = path19.join(opts.scanPath, ".gitignore");
|
|
8783
|
+
const gitignoreExists = await fs17.stat(gitignorePath).then(() => true).catch(() => false);
|
|
8648
8784
|
const verb = gitignoreExists ? "append" : "create";
|
|
8649
8785
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
8650
8786
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -8655,9 +8791,9 @@ async function runInit(opts) {
|
|
|
8655
8791
|
const graph = getGraph(graphKey);
|
|
8656
8792
|
const projectPaths = pathsForProject(
|
|
8657
8793
|
graphKey,
|
|
8658
|
-
|
|
8794
|
+
path19.join(opts.scanPath, "neat-out")
|
|
8659
8795
|
);
|
|
8660
|
-
const errorsPath =
|
|
8796
|
+
const errorsPath = path19.join(path19.dirname(opts.outPath), path19.basename(projectPaths.errorsPath));
|
|
8661
8797
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
8662
8798
|
await saveGraphToDisk(graph, opts.outPath);
|
|
8663
8799
|
written.push(opts.outPath);
|
|
@@ -8736,7 +8872,7 @@ async function runInit(opts) {
|
|
|
8736
8872
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
8737
8873
|
}
|
|
8738
8874
|
} else {
|
|
8739
|
-
await
|
|
8875
|
+
await fs17.writeFile(patchPath, patch, "utf8");
|
|
8740
8876
|
written.push(patchPath);
|
|
8741
8877
|
}
|
|
8742
8878
|
}
|
|
@@ -8777,9 +8913,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
8777
8913
|
};
|
|
8778
8914
|
function claudeConfigPath() {
|
|
8779
8915
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
8780
|
-
if (override && override.length > 0) return
|
|
8916
|
+
if (override && override.length > 0) return path19.resolve(override);
|
|
8781
8917
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
8782
|
-
return
|
|
8918
|
+
return path19.join(home, ".claude.json");
|
|
8783
8919
|
}
|
|
8784
8920
|
async function runSkill(opts) {
|
|
8785
8921
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -8791,7 +8927,7 @@ async function runSkill(opts) {
|
|
|
8791
8927
|
const target = claudeConfigPath();
|
|
8792
8928
|
let existing = {};
|
|
8793
8929
|
try {
|
|
8794
|
-
existing = JSON.parse(await
|
|
8930
|
+
existing = JSON.parse(await fs17.readFile(target, "utf8"));
|
|
8795
8931
|
} catch (err) {
|
|
8796
8932
|
if (err.code !== "ENOENT") {
|
|
8797
8933
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -8803,8 +8939,8 @@ async function runSkill(opts) {
|
|
|
8803
8939
|
...existing,
|
|
8804
8940
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
8805
8941
|
};
|
|
8806
|
-
await
|
|
8807
|
-
await
|
|
8942
|
+
await fs17.mkdir(path19.dirname(target), { recursive: true });
|
|
8943
|
+
await fs17.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
8808
8944
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
8809
8945
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
8810
8946
|
console.log("");
|
|
@@ -8830,7 +8966,7 @@ async function runSkill(opts) {
|
|
|
8830
8966
|
async function main() {
|
|
8831
8967
|
const argv = process.argv.slice(2);
|
|
8832
8968
|
const cmd0 = argv[0];
|
|
8833
|
-
if (
|
|
8969
|
+
if (argv.some((a) => a === "-h" || a === "--help")) {
|
|
8834
8970
|
usage5();
|
|
8835
8971
|
process.exit(0);
|
|
8836
8972
|
}
|
|
@@ -8925,12 +9061,12 @@ async function main() {
|
|
|
8925
9061
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
8926
9062
|
process.exit(2);
|
|
8927
9063
|
}
|
|
8928
|
-
const scanPath =
|
|
9064
|
+
const scanPath = path19.resolve(target);
|
|
8929
9065
|
const projectExplicit = parsed.project !== null;
|
|
8930
|
-
const projectName = projectExplicit ? project :
|
|
9066
|
+
const projectName = projectExplicit ? project : path19.basename(scanPath);
|
|
8931
9067
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
8932
|
-
const fallback = pathsForProject(projectKey,
|
|
8933
|
-
const outPath =
|
|
9068
|
+
const fallback = pathsForProject(projectKey, path19.join(scanPath, "neat-out")).snapshotPath;
|
|
9069
|
+
const outPath = path19.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
8934
9070
|
const result = await runInit({
|
|
8935
9071
|
scanPath,
|
|
8936
9072
|
outPath,
|
|
@@ -8951,21 +9087,21 @@ async function main() {
|
|
|
8951
9087
|
usage5();
|
|
8952
9088
|
process.exit(2);
|
|
8953
9089
|
}
|
|
8954
|
-
const scanPath =
|
|
8955
|
-
const stat = await
|
|
9090
|
+
const scanPath = path19.resolve(target);
|
|
9091
|
+
const stat = await fs17.stat(scanPath).catch(() => null);
|
|
8956
9092
|
if (!stat || !stat.isDirectory()) {
|
|
8957
9093
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
8958
9094
|
process.exit(2);
|
|
8959
9095
|
}
|
|
8960
|
-
const projectPaths = pathsForProject(project,
|
|
8961
|
-
const outPath =
|
|
8962
|
-
const errorsPath =
|
|
8963
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
9096
|
+
const projectPaths = pathsForProject(project, path19.join(scanPath, "neat-out"));
|
|
9097
|
+
const outPath = path19.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
9098
|
+
const errorsPath = path19.resolve(
|
|
9099
|
+
process.env.NEAT_ERRORS_PATH ?? path19.join(path19.dirname(outPath), path19.basename(projectPaths.errorsPath))
|
|
8964
9100
|
);
|
|
8965
|
-
const staleEventsPath =
|
|
8966
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
9101
|
+
const staleEventsPath = path19.resolve(
|
|
9102
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path19.join(path19.dirname(outPath), path19.basename(projectPaths.staleEventsPath))
|
|
8967
9103
|
);
|
|
8968
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
9104
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path19.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
8969
9105
|
const handle = await startWatch(getGraph(project), {
|
|
8970
9106
|
scanPath,
|
|
8971
9107
|
outPath,
|
|
@@ -8974,7 +9110,7 @@ async function main() {
|
|
|
8974
9110
|
project,
|
|
8975
9111
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
8976
9112
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
8977
|
-
neatHome: process.env.NEAT_HOME ?
|
|
9113
|
+
neatHome: process.env.NEAT_HOME ? path19.resolve(process.env.NEAT_HOME) : path19.join(os5.homedir(), ".neat"),
|
|
8978
9114
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
8979
9115
|
host: process.env.HOST ?? "0.0.0.0",
|
|
8980
9116
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -9156,11 +9292,11 @@ async function main() {
|
|
|
9156
9292
|
process.exit(1);
|
|
9157
9293
|
}
|
|
9158
9294
|
async function tryOrchestrator(cmd, parsed) {
|
|
9159
|
-
const scanPath =
|
|
9160
|
-
const stat = await
|
|
9295
|
+
const scanPath = path19.resolve(cmd);
|
|
9296
|
+
const stat = await fs17.stat(scanPath).catch(() => null);
|
|
9161
9297
|
if (!stat || !stat.isDirectory()) return null;
|
|
9162
9298
|
const projectExplicit = parsed.project !== null;
|
|
9163
|
-
const projectName = projectExplicit ? parsed.project :
|
|
9299
|
+
const projectName = projectExplicit ? parsed.project : path19.basename(scanPath);
|
|
9164
9300
|
const result = await runOrchestrator({
|
|
9165
9301
|
scanPath,
|
|
9166
9302
|
project: projectName,
|