@neat.is/core 0.9.15-dev.20260907 → 0.9.15-dev.20260909
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +694 -200
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +16 -2
- package/dist/cli.d.ts +16 -2
- package/dist/cli.js +625 -135
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -91,9 +91,9 @@ import {
|
|
|
91
91
|
} from "./chunk-GDGUY4T6.js";
|
|
92
92
|
|
|
93
93
|
// src/cli.ts
|
|
94
|
-
import
|
|
95
|
-
import
|
|
96
|
-
import { promises as
|
|
94
|
+
import path18 from "path";
|
|
95
|
+
import os5 from "os";
|
|
96
|
+
import { promises as fs16 } from "fs";
|
|
97
97
|
|
|
98
98
|
// src/banner.ts
|
|
99
99
|
import path from "path";
|
|
@@ -4871,10 +4871,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4871
4871
|
const root = baseUrl.replace(/\/$/, "");
|
|
4872
4872
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
4873
4873
|
return {
|
|
4874
|
-
async get(
|
|
4874
|
+
async get(path19) {
|
|
4875
4875
|
let res;
|
|
4876
4876
|
try {
|
|
4877
|
-
res = await fetch(`${root}${
|
|
4877
|
+
res = await fetch(`${root}${path19}`, {
|
|
4878
4878
|
headers: { ...authHeader }
|
|
4879
4879
|
});
|
|
4880
4880
|
} catch (err) {
|
|
@@ -4886,16 +4886,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4886
4886
|
const body = await res.text().catch(() => "");
|
|
4887
4887
|
throw new HttpError(
|
|
4888
4888
|
res.status,
|
|
4889
|
-
`${res.status} ${res.statusText} on GET ${
|
|
4889
|
+
`${res.status} ${res.statusText} on GET ${path19}: ${body}`,
|
|
4890
4890
|
body
|
|
4891
4891
|
);
|
|
4892
4892
|
}
|
|
4893
4893
|
return await res.json();
|
|
4894
4894
|
},
|
|
4895
|
-
async post(
|
|
4895
|
+
async post(path19, body) {
|
|
4896
4896
|
let res;
|
|
4897
4897
|
try {
|
|
4898
|
-
res = await fetch(`${root}${
|
|
4898
|
+
res = await fetch(`${root}${path19}`, {
|
|
4899
4899
|
method: "POST",
|
|
4900
4900
|
headers: { "content-type": "application/json", ...authHeader },
|
|
4901
4901
|
body: JSON.stringify(body)
|
|
@@ -4909,7 +4909,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4909
4909
|
const text = await res.text().catch(() => "");
|
|
4910
4910
|
throw new HttpError(
|
|
4911
4911
|
res.status,
|
|
4912
|
-
`${res.status} ${res.statusText} on POST ${
|
|
4912
|
+
`${res.status} ${res.statusText} on POST ${path19}: ${text}`,
|
|
4913
4913
|
text
|
|
4914
4914
|
);
|
|
4915
4915
|
}
|
|
@@ -4923,12 +4923,12 @@ function projectPath(project, suffix) {
|
|
|
4923
4923
|
}
|
|
4924
4924
|
async function runRootCause(client, input) {
|
|
4925
4925
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
4926
|
-
const
|
|
4926
|
+
const path19 = projectPath(
|
|
4927
4927
|
input.project,
|
|
4928
4928
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
4929
4929
|
);
|
|
4930
4930
|
try {
|
|
4931
|
-
const result = await client.get(
|
|
4931
|
+
const result = await client.get(path19);
|
|
4932
4932
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
4933
4933
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
4934
4934
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -4954,12 +4954,12 @@ async function runRootCause(client, input) {
|
|
|
4954
4954
|
}
|
|
4955
4955
|
async function runBlastRadius(client, input) {
|
|
4956
4956
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
4957
|
-
const
|
|
4957
|
+
const path19 = projectPath(
|
|
4958
4958
|
input.project,
|
|
4959
4959
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
4960
4960
|
);
|
|
4961
4961
|
try {
|
|
4962
|
-
const result = await client.get(
|
|
4962
|
+
const result = await client.get(path19);
|
|
4963
4963
|
if (result.totalAffected === 0) {
|
|
4964
4964
|
return {
|
|
4965
4965
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -4993,12 +4993,12 @@ function formatBlastEntry(n) {
|
|
|
4993
4993
|
}
|
|
4994
4994
|
async function runDependencies(client, input) {
|
|
4995
4995
|
const depth = input.depth ?? 3;
|
|
4996
|
-
const
|
|
4996
|
+
const path19 = projectPath(
|
|
4997
4997
|
input.project,
|
|
4998
4998
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
4999
4999
|
);
|
|
5000
5000
|
try {
|
|
5001
|
-
const result = await client.get(
|
|
5001
|
+
const result = await client.get(path19);
|
|
5002
5002
|
if (result.total === 0) {
|
|
5003
5003
|
return {
|
|
5004
5004
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -5090,9 +5090,9 @@ function formatDuration(ms) {
|
|
|
5090
5090
|
return `${Math.round(h / 24)}d`;
|
|
5091
5091
|
}
|
|
5092
5092
|
async function runIncidents(client, input) {
|
|
5093
|
-
const
|
|
5093
|
+
const path19 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
5094
5094
|
try {
|
|
5095
|
-
const body = await client.get(
|
|
5095
|
+
const body = await client.get(path19);
|
|
5096
5096
|
const events = body.events;
|
|
5097
5097
|
if (events.length === 0) {
|
|
5098
5098
|
return {
|
|
@@ -5563,28 +5563,462 @@ async function runDoctorCommand(argv, deps = {}) {
|
|
|
5563
5563
|
return checks.every((c) => c.ok) ? 0 : 1;
|
|
5564
5564
|
}
|
|
5565
5565
|
|
|
5566
|
-
// src/
|
|
5567
|
-
import
|
|
5568
|
-
|
|
5566
|
+
// src/login-cli.ts
|
|
5567
|
+
import readline3 from "readline/promises";
|
|
5568
|
+
|
|
5569
|
+
// src/profiles.ts
|
|
5569
5570
|
import { promises as fs11 } from "fs";
|
|
5571
|
+
import os from "os";
|
|
5572
|
+
import path12 from "path";
|
|
5573
|
+
var PROFILES_CONFIG_VERSION = 1;
|
|
5574
|
+
function neatHome() {
|
|
5575
|
+
const override = process.env.NEAT_HOME;
|
|
5576
|
+
if (override && override.length > 0) return path12.resolve(override);
|
|
5577
|
+
return path12.join(os.homedir(), ".neat");
|
|
5578
|
+
}
|
|
5579
|
+
function profilesConfigPath(home = neatHome()) {
|
|
5580
|
+
return path12.join(home, "profiles.json");
|
|
5581
|
+
}
|
|
5582
|
+
function profilesConfigLockPath(home = neatHome()) {
|
|
5583
|
+
return path12.join(home, "profiles.json.lock");
|
|
5584
|
+
}
|
|
5585
|
+
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
5586
|
+
async function warnIfModeLooserThan0600(file) {
|
|
5587
|
+
if (process.platform === "win32") return;
|
|
5588
|
+
try {
|
|
5589
|
+
const stat = await fs11.stat(file);
|
|
5590
|
+
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
5591
|
+
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
5592
|
+
console.warn(
|
|
5593
|
+
`[neat] ${file} is mode 0${mode}, looser than the 0600 this file's token calls for \u2014 run \`chmod 600 ${file}\``
|
|
5594
|
+
);
|
|
5595
|
+
}
|
|
5596
|
+
} catch {
|
|
5597
|
+
}
|
|
5598
|
+
}
|
|
5599
|
+
async function readProfilesConfig(home = neatHome()) {
|
|
5600
|
+
const file = profilesConfigPath(home);
|
|
5601
|
+
let raw;
|
|
5602
|
+
try {
|
|
5603
|
+
raw = await fs11.readFile(file, "utf8");
|
|
5604
|
+
} catch (err) {
|
|
5605
|
+
if (err.code === "ENOENT") {
|
|
5606
|
+
return { version: PROFILES_CONFIG_VERSION, profiles: [] };
|
|
5607
|
+
}
|
|
5608
|
+
throw err;
|
|
5609
|
+
}
|
|
5610
|
+
await warnIfModeLooserThan0600(file);
|
|
5611
|
+
let parsed;
|
|
5612
|
+
try {
|
|
5613
|
+
parsed = JSON.parse(raw);
|
|
5614
|
+
} catch (err) {
|
|
5615
|
+
throw new Error(`${file} is not valid JSON: ${err.message}`);
|
|
5616
|
+
}
|
|
5617
|
+
return validateConfig(parsed, file);
|
|
5618
|
+
}
|
|
5619
|
+
function validateConfig(parsed, file) {
|
|
5620
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
5621
|
+
throw new Error(`${file} must be a JSON object with a "profiles" array`);
|
|
5622
|
+
}
|
|
5623
|
+
const obj = parsed;
|
|
5624
|
+
const version = obj.version === void 0 ? PROFILES_CONFIG_VERSION : obj.version;
|
|
5625
|
+
if (typeof version !== "number" || !Number.isInteger(version)) {
|
|
5626
|
+
throw new Error(`${file}: "version" must be an integer`);
|
|
5627
|
+
}
|
|
5628
|
+
const rawProfiles = obj.profiles;
|
|
5629
|
+
if (!Array.isArray(rawProfiles)) {
|
|
5630
|
+
throw new Error(`${file}: "profiles" must be an array`);
|
|
5631
|
+
}
|
|
5632
|
+
const profiles = rawProfiles.map((entry2, i) => validateEntry(entry2, i, file));
|
|
5633
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5634
|
+
for (const p of profiles) {
|
|
5635
|
+
if (seen.has(p.name)) throw new Error(`${file}: duplicate profile name "${p.name}"`);
|
|
5636
|
+
seen.add(p.name);
|
|
5637
|
+
}
|
|
5638
|
+
let active;
|
|
5639
|
+
if (obj.active !== void 0) {
|
|
5640
|
+
if (typeof obj.active !== "string" || obj.active.length === 0) {
|
|
5641
|
+
throw new Error(`${file}: "active" must be a non-empty string when present`);
|
|
5642
|
+
}
|
|
5643
|
+
active = seen.has(obj.active) ? obj.active : void 0;
|
|
5644
|
+
}
|
|
5645
|
+
return { version, ...active ? { active } : {}, profiles };
|
|
5646
|
+
}
|
|
5647
|
+
function validateEntry(entry2, index, file) {
|
|
5648
|
+
const where = `${file}: profiles[${index}]`;
|
|
5649
|
+
if (typeof entry2 !== "object" || entry2 === null || Array.isArray(entry2)) {
|
|
5650
|
+
throw new Error(`${where} must be an object`);
|
|
5651
|
+
}
|
|
5652
|
+
const e = entry2;
|
|
5653
|
+
const name = e.name;
|
|
5654
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
5655
|
+
throw new Error(`${where}.name must be a non-empty string`);
|
|
5656
|
+
}
|
|
5657
|
+
const endpoint = e.endpoint;
|
|
5658
|
+
if (typeof endpoint !== "string" || endpoint.length === 0) {
|
|
5659
|
+
throw new Error(`${where}.endpoint must be a non-empty string`);
|
|
5660
|
+
}
|
|
5661
|
+
let parsedUrl;
|
|
5662
|
+
try {
|
|
5663
|
+
parsedUrl = new URL(endpoint);
|
|
5664
|
+
} catch {
|
|
5665
|
+
throw new Error(`${where}.endpoint must be an absolute URL (got "${endpoint}")`);
|
|
5666
|
+
}
|
|
5667
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
5668
|
+
throw new Error(`${where}.endpoint must be an http(s) URL (got "${parsedUrl.protocol}")`);
|
|
5669
|
+
}
|
|
5670
|
+
if (e.authToken !== void 0 && (typeof e.authToken !== "string" || e.authToken.length === 0)) {
|
|
5671
|
+
throw new Error(`${where}.authToken must be a non-empty string when present`);
|
|
5672
|
+
}
|
|
5673
|
+
return {
|
|
5674
|
+
name,
|
|
5675
|
+
endpoint,
|
|
5676
|
+
...typeof e.authToken === "string" ? { authToken: e.authToken } : {}
|
|
5677
|
+
};
|
|
5678
|
+
}
|
|
5679
|
+
async function resolveProfile(name, home = neatHome()) {
|
|
5680
|
+
const { profiles } = await readProfilesConfig(home);
|
|
5681
|
+
return profiles.find((p) => p.name === name);
|
|
5682
|
+
}
|
|
5683
|
+
async function getActiveProfile(home = neatHome()) {
|
|
5684
|
+
const { active, profiles } = await readProfilesConfig(home);
|
|
5685
|
+
if (!active) return void 0;
|
|
5686
|
+
return profiles.find((p) => p.name === active);
|
|
5687
|
+
}
|
|
5688
|
+
function serialize(config) {
|
|
5689
|
+
const names = new Set(config.profiles.map((p) => p.name));
|
|
5690
|
+
const active = config.active && names.has(config.active) ? config.active : void 0;
|
|
5691
|
+
const out = {
|
|
5692
|
+
version: config.version ?? PROFILES_CONFIG_VERSION,
|
|
5693
|
+
...active ? { active } : {},
|
|
5694
|
+
profiles: config.profiles
|
|
5695
|
+
};
|
|
5696
|
+
return `${JSON.stringify(out, null, 2)}
|
|
5697
|
+
`;
|
|
5698
|
+
}
|
|
5699
|
+
async function writeConfigAtomic(config, home) {
|
|
5700
|
+
const file = profilesConfigPath(home);
|
|
5701
|
+
await fs11.mkdir(path12.dirname(file), { recursive: true });
|
|
5702
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
5703
|
+
const fd = await fs11.open(tmp, "w", 384);
|
|
5704
|
+
try {
|
|
5705
|
+
await fd.writeFile(serialize(config), "utf8");
|
|
5706
|
+
await fd.sync();
|
|
5707
|
+
} finally {
|
|
5708
|
+
await fd.close();
|
|
5709
|
+
}
|
|
5710
|
+
await fs11.rename(tmp, file);
|
|
5711
|
+
}
|
|
5712
|
+
var LOCK_RETRY_MS = 50;
|
|
5713
|
+
var LOCK_TIMEOUT_MS = 5e3;
|
|
5714
|
+
async function acquireLock(lockPath) {
|
|
5715
|
+
await fs11.mkdir(path12.dirname(lockPath), { recursive: true });
|
|
5716
|
+
const deadline = Date.now() + LOCK_TIMEOUT_MS;
|
|
5717
|
+
for (; ; ) {
|
|
5718
|
+
try {
|
|
5719
|
+
const fd = await fs11.open(lockPath, "wx");
|
|
5720
|
+
await fd.writeFile(`${process.pid}
|
|
5721
|
+
`, "utf8");
|
|
5722
|
+
await fd.close();
|
|
5723
|
+
return;
|
|
5724
|
+
} catch (err) {
|
|
5725
|
+
if (err.code !== "EEXIST") throw err;
|
|
5726
|
+
if (Date.now() >= deadline) {
|
|
5727
|
+
throw new Error(
|
|
5728
|
+
`timed out acquiring ${lockPath} after ${LOCK_TIMEOUT_MS}ms \u2014 if no other neat process is running, remove the stale lock file`
|
|
5729
|
+
);
|
|
5730
|
+
}
|
|
5731
|
+
await new Promise((resolve) => setTimeout(resolve, LOCK_RETRY_MS));
|
|
5732
|
+
}
|
|
5733
|
+
}
|
|
5734
|
+
}
|
|
5735
|
+
async function releaseLock(lockPath) {
|
|
5736
|
+
await fs11.rm(lockPath, { force: true });
|
|
5737
|
+
}
|
|
5738
|
+
async function withProfilesLock(home, fn) {
|
|
5739
|
+
const lockPath = profilesConfigLockPath(home);
|
|
5740
|
+
await acquireLock(lockPath);
|
|
5741
|
+
try {
|
|
5742
|
+
return await fn();
|
|
5743
|
+
} finally {
|
|
5744
|
+
await releaseLock(lockPath);
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
async function upsertProfile(profile, opts = {}) {
|
|
5748
|
+
const home = opts.home ?? neatHome();
|
|
5749
|
+
const validated = validateEntry(profile, 0, profilesConfigPath(home));
|
|
5750
|
+
await withProfilesLock(home, async () => {
|
|
5751
|
+
const config = await readProfilesConfig(home);
|
|
5752
|
+
const others = config.profiles.filter((p) => p.name !== validated.name);
|
|
5753
|
+
const profiles = [...others, validated];
|
|
5754
|
+
const makeActive = opts.makeActive ?? config.profiles.length === 0;
|
|
5755
|
+
const active = makeActive ? validated.name : config.active;
|
|
5756
|
+
await writeConfigAtomic({ version: config.version, ...active ? { active } : {}, profiles }, home);
|
|
5757
|
+
});
|
|
5758
|
+
}
|
|
5759
|
+
async function removeProfile(name, home = neatHome()) {
|
|
5760
|
+
return withProfilesLock(home, async () => {
|
|
5761
|
+
const config = await readProfilesConfig(home);
|
|
5762
|
+
const profiles = config.profiles.filter((p) => p.name !== name);
|
|
5763
|
+
if (profiles.length === config.profiles.length) return false;
|
|
5764
|
+
const active = config.active === name ? void 0 : config.active;
|
|
5765
|
+
await writeConfigAtomic({ version: config.version, ...active ? { active } : {}, profiles }, home);
|
|
5766
|
+
return true;
|
|
5767
|
+
});
|
|
5768
|
+
}
|
|
5769
|
+
async function clearActiveProfile(home = neatHome()) {
|
|
5770
|
+
return withProfilesLock(home, async () => {
|
|
5771
|
+
const config = await readProfilesConfig(home);
|
|
5772
|
+
if (!config.active) return false;
|
|
5773
|
+
await writeConfigAtomic({ version: config.version, profiles: config.profiles }, home);
|
|
5774
|
+
return true;
|
|
5775
|
+
});
|
|
5776
|
+
}
|
|
5777
|
+
|
|
5778
|
+
// src/login-cli.ts
|
|
5779
|
+
var HEALTH_TIMEOUT_MS2 = 5e3;
|
|
5780
|
+
var DEFAULT_PROFILE_NAME = "hosted";
|
|
5781
|
+
function readFlagValue(argv, i) {
|
|
5782
|
+
const arg = argv[i];
|
|
5783
|
+
const eq = arg.indexOf("=");
|
|
5784
|
+
if (eq !== -1) return { value: arg.slice(eq + 1), next: i };
|
|
5785
|
+
return { value: argv[i + 1], next: i + 1 };
|
|
5786
|
+
}
|
|
5787
|
+
function parseLoginArgs(argv) {
|
|
5788
|
+
const parsed = { name: DEFAULT_PROFILE_NAME, json: false, help: false };
|
|
5789
|
+
for (let i = 0; i < argv.length; i++) {
|
|
5790
|
+
const arg = argv[i];
|
|
5791
|
+
if (arg === "-h" || arg === "--help") parsed.help = true;
|
|
5792
|
+
else if (arg === "--json") parsed.json = true;
|
|
5793
|
+
else if (arg === "--endpoint" || arg.startsWith("--endpoint=")) {
|
|
5794
|
+
const { value, next } = readFlagValue(argv, i);
|
|
5795
|
+
parsed.endpoint = value;
|
|
5796
|
+
i = next;
|
|
5797
|
+
} else if (arg === "--token" || arg.startsWith("--token=")) {
|
|
5798
|
+
const { value, next } = readFlagValue(argv, i);
|
|
5799
|
+
parsed.token = value;
|
|
5800
|
+
i = next;
|
|
5801
|
+
} else if (arg === "--name" || arg.startsWith("--name=")) {
|
|
5802
|
+
const { value, next } = readFlagValue(argv, i);
|
|
5803
|
+
if (value && value.length > 0) parsed.name = value;
|
|
5804
|
+
i = next;
|
|
5805
|
+
} else {
|
|
5806
|
+
parsed.error = `unknown argument "${arg}"`;
|
|
5807
|
+
break;
|
|
5808
|
+
}
|
|
5809
|
+
}
|
|
5810
|
+
return parsed;
|
|
5811
|
+
}
|
|
5812
|
+
function printLoginHelp(out) {
|
|
5813
|
+
out("usage: neat login [--endpoint <url>] [--token <token>] [--name <name>] [--json]");
|
|
5814
|
+
out(" Connect this machine to a hosted NEAT and make it the default for the");
|
|
5815
|
+
out(" neat CLI and the MCP server. Omit --endpoint / --token to be prompted");
|
|
5816
|
+
out(" (the token is read without echo). --name labels the profile (default");
|
|
5817
|
+
out(' "hosted"). The token can also come from NEAT_LOGIN_TOKEN.');
|
|
5818
|
+
out(" Exit 0 on success, 1 rejected token/endpoint, 2 misuse, 3 unreachable.");
|
|
5819
|
+
}
|
|
5820
|
+
async function probeDaemon(fetchImpl, endpoint, token) {
|
|
5821
|
+
const root = endpoint.replace(/\/$/, "");
|
|
5822
|
+
let res;
|
|
5823
|
+
try {
|
|
5824
|
+
res = await fetchImpl(`${root}/health`, {
|
|
5825
|
+
headers: { authorization: `Bearer ${token}` },
|
|
5826
|
+
signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS2)
|
|
5827
|
+
});
|
|
5828
|
+
} catch (err) {
|
|
5829
|
+
return { kind: "unreachable", detail: err.message };
|
|
5830
|
+
}
|
|
5831
|
+
if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
|
|
5832
|
+
if (!res.ok) return { kind: "not-neat", status: res.status };
|
|
5833
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
5834
|
+
if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
|
|
5835
|
+
return { kind: "ok" };
|
|
5836
|
+
}
|
|
5837
|
+
async function runLoginCommand(argv, deps = {}) {
|
|
5838
|
+
const out = deps.out ?? ((line) => console.log(line));
|
|
5839
|
+
const err = deps.err ?? ((line) => console.error(line));
|
|
5840
|
+
const env = deps.env ?? process.env;
|
|
5841
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
5842
|
+
const readLine = deps.readLine ?? defaultReadLine;
|
|
5843
|
+
const readSecret = deps.readSecret ?? defaultReadSecret;
|
|
5844
|
+
const args = parseLoginArgs(argv);
|
|
5845
|
+
if (args.help) {
|
|
5846
|
+
printLoginHelp(out);
|
|
5847
|
+
return 0;
|
|
5848
|
+
}
|
|
5849
|
+
if (args.error) {
|
|
5850
|
+
err(`neat login: ${args.error}`);
|
|
5851
|
+
return 2;
|
|
5852
|
+
}
|
|
5853
|
+
let endpoint = args.endpoint;
|
|
5854
|
+
if (!endpoint) endpoint = (await readLine("Hosted NEAT endpoint (https://\u2026): "))?.trim();
|
|
5855
|
+
if (!endpoint) {
|
|
5856
|
+
err("neat login: an endpoint is required \u2014 pass --endpoint <url> or run interactively");
|
|
5857
|
+
return 2;
|
|
5858
|
+
}
|
|
5859
|
+
let url;
|
|
5860
|
+
try {
|
|
5861
|
+
url = new URL(endpoint);
|
|
5862
|
+
} catch {
|
|
5863
|
+
err(`neat login: --endpoint must be an absolute URL (got "${endpoint}")`);
|
|
5864
|
+
return 2;
|
|
5865
|
+
}
|
|
5866
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
5867
|
+
err(`neat login: --endpoint must be an http(s) URL (got "${url.protocol}")`);
|
|
5868
|
+
return 2;
|
|
5869
|
+
}
|
|
5870
|
+
const envToken = env.NEAT_LOGIN_TOKEN;
|
|
5871
|
+
let token = args.token ?? (envToken && envToken.length > 0 ? envToken : void 0);
|
|
5872
|
+
if (!token) token = (await readSecret("Daemon token: "))?.trim();
|
|
5873
|
+
if (!token) {
|
|
5874
|
+
err("neat login: a token is required \u2014 pass --token, set NEAT_LOGIN_TOKEN, or run interactively");
|
|
5875
|
+
return 2;
|
|
5876
|
+
}
|
|
5877
|
+
const probe = await probeDaemon(fetchImpl, endpoint, token);
|
|
5878
|
+
if (probe.kind === "unreachable") {
|
|
5879
|
+
err(`neat login: can't reach ${endpoint} \u2014 ${probe.detail}`);
|
|
5880
|
+
return 3;
|
|
5881
|
+
}
|
|
5882
|
+
if (probe.kind === "unauthorized") {
|
|
5883
|
+
err(`neat login: ${endpoint} rejected the token (HTTP ${probe.status}). Check the token and try again.`);
|
|
5884
|
+
return 1;
|
|
5885
|
+
}
|
|
5886
|
+
if (probe.kind === "not-neat") {
|
|
5887
|
+
err(`neat login: ${endpoint} answered but does not look like a NEAT daemon (HTTP ${probe.status}).`);
|
|
5888
|
+
return 1;
|
|
5889
|
+
}
|
|
5890
|
+
await upsertProfile(
|
|
5891
|
+
{ name: args.name, endpoint, authToken: token },
|
|
5892
|
+
{ makeActive: true, ...deps.home ? { home: deps.home } : {} }
|
|
5893
|
+
);
|
|
5894
|
+
if (args.json) {
|
|
5895
|
+
out(JSON.stringify({ status: "logged-in", profile: args.name, endpoint }, null, 2));
|
|
5896
|
+
} else {
|
|
5897
|
+
out(`Logged in \u2014 profile "${args.name}" \u2192 ${endpoint}`);
|
|
5898
|
+
out("The neat CLI and the MCP server now read this hosted graph by default.");
|
|
5899
|
+
out("Run `neat logout` to switch back to your local daemon.");
|
|
5900
|
+
}
|
|
5901
|
+
return 0;
|
|
5902
|
+
}
|
|
5903
|
+
function parseLogoutArgs(argv) {
|
|
5904
|
+
const parsed = { help: false };
|
|
5905
|
+
for (let i = 0; i < argv.length; i++) {
|
|
5906
|
+
const arg = argv[i];
|
|
5907
|
+
if (arg === "-h" || arg === "--help") parsed.help = true;
|
|
5908
|
+
else if (arg === "--name" || arg.startsWith("--name=")) {
|
|
5909
|
+
const { value, next } = readFlagValue(argv, i);
|
|
5910
|
+
parsed.name = value;
|
|
5911
|
+
i = next;
|
|
5912
|
+
} else {
|
|
5913
|
+
parsed.error = `unknown argument "${arg}"`;
|
|
5914
|
+
break;
|
|
5915
|
+
}
|
|
5916
|
+
}
|
|
5917
|
+
return parsed;
|
|
5918
|
+
}
|
|
5919
|
+
async function runLogoutCommand(argv, deps = {}) {
|
|
5920
|
+
const out = deps.out ?? ((line) => console.log(line));
|
|
5921
|
+
const err = deps.err ?? ((line) => console.error(line));
|
|
5922
|
+
const home = deps.home;
|
|
5923
|
+
const args = parseLogoutArgs(argv);
|
|
5924
|
+
if (args.help) {
|
|
5925
|
+
out("usage: neat logout [--name <name>]");
|
|
5926
|
+
out(" With no argument, clears the active hosted profile so the CLI and MCP");
|
|
5927
|
+
out(" server go back to your local daemon (the stored profile is kept).");
|
|
5928
|
+
out(" --name <name> removes that profile from ~/.neat/profiles.json entirely.");
|
|
5929
|
+
return 0;
|
|
5930
|
+
}
|
|
5931
|
+
if (args.error) {
|
|
5932
|
+
err(`neat logout: ${args.error}`);
|
|
5933
|
+
return 2;
|
|
5934
|
+
}
|
|
5935
|
+
if (args.name !== void 0) {
|
|
5936
|
+
if (args.name.length === 0) {
|
|
5937
|
+
err("neat logout: --name needs a profile name");
|
|
5938
|
+
return 2;
|
|
5939
|
+
}
|
|
5940
|
+
const removed = await removeProfile(args.name, home ?? void 0);
|
|
5941
|
+
if (!removed) {
|
|
5942
|
+
err(`neat logout: no profile named "${args.name}"`);
|
|
5943
|
+
return 1;
|
|
5944
|
+
}
|
|
5945
|
+
out(`Removed profile "${args.name}".`);
|
|
5946
|
+
return 0;
|
|
5947
|
+
}
|
|
5948
|
+
const active = await getActiveProfile(home ?? void 0);
|
|
5949
|
+
if (!active) {
|
|
5950
|
+
out("Not logged in to a hosted NEAT \u2014 the CLI is already using your local daemon.");
|
|
5951
|
+
return 0;
|
|
5952
|
+
}
|
|
5953
|
+
await clearActiveProfile(home ?? void 0);
|
|
5954
|
+
out(`Logged out of "${active.name}" (${active.endpoint}). The CLI is back on your local daemon.`);
|
|
5955
|
+
return 0;
|
|
5956
|
+
}
|
|
5957
|
+
async function defaultReadLine(prompt) {
|
|
5958
|
+
if (!process.stdin.isTTY) return void 0;
|
|
5959
|
+
const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
|
|
5960
|
+
try {
|
|
5961
|
+
return await rl.question(prompt);
|
|
5962
|
+
} finally {
|
|
5963
|
+
rl.close();
|
|
5964
|
+
}
|
|
5965
|
+
}
|
|
5966
|
+
async function defaultReadSecret(prompt) {
|
|
5967
|
+
const stdin = process.stdin;
|
|
5968
|
+
if (!stdin.isTTY) return void 0;
|
|
5969
|
+
process.stdout.write(prompt);
|
|
5970
|
+
return new Promise((resolve) => {
|
|
5971
|
+
const chars = [];
|
|
5972
|
+
stdin.setRawMode(true);
|
|
5973
|
+
stdin.resume();
|
|
5974
|
+
stdin.setEncoding("utf8");
|
|
5975
|
+
const cleanup = () => {
|
|
5976
|
+
stdin.setRawMode(false);
|
|
5977
|
+
stdin.pause();
|
|
5978
|
+
stdin.off("data", onData);
|
|
5979
|
+
};
|
|
5980
|
+
const onData = (ch) => {
|
|
5981
|
+
const code = ch.charCodeAt(0);
|
|
5982
|
+
if (ch === "\n" || ch === "\r" || code === 4) {
|
|
5983
|
+
cleanup();
|
|
5984
|
+
process.stdout.write("\n");
|
|
5985
|
+
resolve(chars.join(""));
|
|
5986
|
+
} else if (code === 3) {
|
|
5987
|
+
cleanup();
|
|
5988
|
+
process.stdout.write("\n");
|
|
5989
|
+
process.exit(130);
|
|
5990
|
+
} else if (code === 127 || ch === "\b") {
|
|
5991
|
+
chars.pop();
|
|
5992
|
+
} else {
|
|
5993
|
+
chars.push(ch);
|
|
5994
|
+
}
|
|
5995
|
+
};
|
|
5996
|
+
stdin.on("data", onData);
|
|
5997
|
+
});
|
|
5998
|
+
}
|
|
5999
|
+
|
|
6000
|
+
// src/hooks-cli.ts
|
|
6001
|
+
import path13 from "path";
|
|
6002
|
+
import os2 from "os";
|
|
6003
|
+
import { promises as fs12 } from "fs";
|
|
5570
6004
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
5571
6005
|
var HOOK_FILENAME = "neat-search-nudge.mjs";
|
|
5572
6006
|
var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
5573
6007
|
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
5574
6008
|
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
5575
6009
|
function moduleDir() {
|
|
5576
|
-
return typeof __dirname !== "undefined" ? __dirname :
|
|
6010
|
+
return typeof __dirname !== "undefined" ? __dirname : path13.dirname(fileURLToPath3(import.meta.url));
|
|
5577
6011
|
}
|
|
5578
6012
|
async function readSkillAsset(rel) {
|
|
5579
6013
|
const here = moduleDir();
|
|
5580
6014
|
const candidates = [
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
6015
|
+
path13.resolve(here, "../../claude-skill", rel),
|
|
6016
|
+
path13.resolve(here, "../../../claude-skill", rel),
|
|
6017
|
+
path13.resolve(here, "../claude-skill", rel)
|
|
5584
6018
|
];
|
|
5585
6019
|
for (const candidate of candidates) {
|
|
5586
6020
|
try {
|
|
5587
|
-
return await
|
|
6021
|
+
return await fs12.readFile(candidate, "utf8");
|
|
5588
6022
|
} catch {
|
|
5589
6023
|
}
|
|
5590
6024
|
}
|
|
@@ -5592,22 +6026,22 @@ async function readSkillAsset(rel) {
|
|
|
5592
6026
|
`neat hooks: could not find @neat.is/claude-skill/${rel} \u2014 is the package installed?`
|
|
5593
6027
|
);
|
|
5594
6028
|
}
|
|
5595
|
-
function
|
|
6029
|
+
function neatHome2() {
|
|
5596
6030
|
const override = process.env.NEAT_HOME;
|
|
5597
|
-
if (override && override.length > 0) return
|
|
5598
|
-
return
|
|
6031
|
+
if (override && override.length > 0) return path13.resolve(override);
|
|
6032
|
+
return path13.join(os2.homedir(), ".neat");
|
|
5599
6033
|
}
|
|
5600
6034
|
function claudeSettingsPath() {
|
|
5601
6035
|
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
5602
|
-
if (override && override.length > 0) return
|
|
5603
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ??
|
|
5604
|
-
return
|
|
6036
|
+
if (override && override.length > 0) return path13.resolve(override);
|
|
6037
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
|
|
6038
|
+
return path13.join(home, ".claude", "settings.json");
|
|
5605
6039
|
}
|
|
5606
6040
|
function installedHookPath() {
|
|
5607
|
-
return
|
|
6041
|
+
return path13.join(neatHome2(), "hooks", HOOK_FILENAME);
|
|
5608
6042
|
}
|
|
5609
6043
|
function gateFlagPath() {
|
|
5610
|
-
return
|
|
6044
|
+
return path13.join(neatHome2(), "hooks", "gate-enabled");
|
|
5611
6045
|
}
|
|
5612
6046
|
function isNeatSearchEntry(entry2) {
|
|
5613
6047
|
return (entry2.hooks ?? []).some(
|
|
@@ -5640,14 +6074,14 @@ async function runHooks(opts) {
|
|
|
5640
6074
|
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
5641
6075
|
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
5642
6076
|
const scriptPath = installedHookPath();
|
|
5643
|
-
await
|
|
5644
|
-
await
|
|
5645
|
-
const guidePath =
|
|
5646
|
-
await
|
|
6077
|
+
await fs12.mkdir(path13.dirname(scriptPath), { recursive: true });
|
|
6078
|
+
await fs12.writeFile(scriptPath, hookScript, { mode: 493 });
|
|
6079
|
+
const guidePath = path13.join(neatHome2(), GUIDE_INSTALL_NAME);
|
|
6080
|
+
await fs12.writeFile(guidePath, guide, "utf8");
|
|
5647
6081
|
const settingsFile = claudeSettingsPath();
|
|
5648
6082
|
let settings = {};
|
|
5649
6083
|
try {
|
|
5650
|
-
settings = JSON.parse(await
|
|
6084
|
+
settings = JSON.parse(await fs12.readFile(settingsFile, "utf8"));
|
|
5651
6085
|
} catch (err) {
|
|
5652
6086
|
if (err.code !== "ENOENT") {
|
|
5653
6087
|
console.error(
|
|
@@ -5669,14 +6103,14 @@ async function runHooks(opts) {
|
|
|
5669
6103
|
...settings,
|
|
5670
6104
|
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
5671
6105
|
};
|
|
5672
|
-
await
|
|
5673
|
-
await
|
|
6106
|
+
await fs12.mkdir(path13.dirname(settingsFile), { recursive: true });
|
|
6107
|
+
await fs12.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
5674
6108
|
const flag = gateFlagPath();
|
|
5675
6109
|
if (opts.gate) {
|
|
5676
|
-
await
|
|
5677
|
-
await
|
|
6110
|
+
await fs12.mkdir(path13.dirname(flag), { recursive: true });
|
|
6111
|
+
await fs12.writeFile(flag, "1\n", "utf8");
|
|
5678
6112
|
} else {
|
|
5679
|
-
await
|
|
6113
|
+
await fs12.rm(flag, { force: true });
|
|
5680
6114
|
}
|
|
5681
6115
|
const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
|
|
5682
6116
|
console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
|
|
@@ -5765,8 +6199,8 @@ async function runHooksCommand(args) {
|
|
|
5765
6199
|
}
|
|
5766
6200
|
|
|
5767
6201
|
// src/claude-cli.ts
|
|
5768
|
-
import
|
|
5769
|
-
import { promises as
|
|
6202
|
+
import path14 from "path";
|
|
6203
|
+
import { promises as fs13 } from "fs";
|
|
5770
6204
|
var NEAT_SECTION_HEADING = "## neat";
|
|
5771
6205
|
var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
|
|
5772
6206
|
code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
|
|
@@ -5800,8 +6234,8 @@ ${NEAT_DIRECTIVE_BODY}
|
|
|
5800
6234
|
}
|
|
5801
6235
|
function claudeMdPath() {
|
|
5802
6236
|
const override = process.env.NEAT_CLAUDE_MD;
|
|
5803
|
-
if (override && override.length > 0) return
|
|
5804
|
-
return
|
|
6237
|
+
if (override && override.length > 0) return path14.resolve(override);
|
|
6238
|
+
return path14.join(process.cwd(), "CLAUDE.md");
|
|
5805
6239
|
}
|
|
5806
6240
|
function splitAroundSection(raw) {
|
|
5807
6241
|
const lines = raw.split("\n");
|
|
@@ -5829,7 +6263,7 @@ function compose(before, after) {
|
|
|
5829
6263
|
}
|
|
5830
6264
|
async function readIfExists(file) {
|
|
5831
6265
|
try {
|
|
5832
|
-
return await
|
|
6266
|
+
return await fs13.readFile(file, "utf8");
|
|
5833
6267
|
} catch (err) {
|
|
5834
6268
|
if (err.code === "ENOENT") return null;
|
|
5835
6269
|
throw err;
|
|
@@ -5840,8 +6274,8 @@ async function runInstall() {
|
|
|
5840
6274
|
const raw = await readIfExists(file) ?? "";
|
|
5841
6275
|
const { before, after, found } = splitAroundSection(raw);
|
|
5842
6276
|
const next = compose(before, after);
|
|
5843
|
-
await
|
|
5844
|
-
await
|
|
6277
|
+
await fs13.mkdir(path14.dirname(file), { recursive: true });
|
|
6278
|
+
await fs13.writeFile(file, next, "utf8");
|
|
5845
6279
|
const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
|
|
5846
6280
|
console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
|
|
5847
6281
|
console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
|
|
@@ -5862,7 +6296,7 @@ async function runUninstall() {
|
|
|
5862
6296
|
}
|
|
5863
6297
|
const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
|
|
5864
6298
|
const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
|
|
5865
|
-
await
|
|
6299
|
+
await fs13.writeFile(file, next, "utf8");
|
|
5866
6300
|
console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
|
|
5867
6301
|
return { exitCode: 0 };
|
|
5868
6302
|
}
|
|
@@ -5904,9 +6338,9 @@ async function runClaudeCommand(args) {
|
|
|
5904
6338
|
}
|
|
5905
6339
|
|
|
5906
6340
|
// src/codex-cli.ts
|
|
5907
|
-
import
|
|
5908
|
-
import
|
|
5909
|
-
import { promises as
|
|
6341
|
+
import path15 from "path";
|
|
6342
|
+
import os3 from "os";
|
|
6343
|
+
import { promises as fs14 } from "fs";
|
|
5910
6344
|
import { isDeepStrictEqual } from "util";
|
|
5911
6345
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
5912
6346
|
var CODEX_MCP_SERVER = {
|
|
@@ -5924,14 +6358,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
5924
6358
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
5925
6359
|
function codexConfigPath() {
|
|
5926
6360
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
5927
|
-
if (override && override.length > 0) return
|
|
5928
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ??
|
|
5929
|
-
return
|
|
6361
|
+
if (override && override.length > 0) return path15.resolve(override);
|
|
6362
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? os3.homedir();
|
|
6363
|
+
return path15.join(home, ".codex", "config.toml");
|
|
5930
6364
|
}
|
|
5931
6365
|
function agentsFilePath() {
|
|
5932
6366
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
5933
|
-
if (override && override.length > 0) return
|
|
5934
|
-
return
|
|
6367
|
+
if (override && override.length > 0) return path15.resolve(override);
|
|
6368
|
+
return path15.join(process.cwd(), "AGENTS.md");
|
|
5935
6369
|
}
|
|
5936
6370
|
function isTableHeader(line) {
|
|
5937
6371
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -6065,7 +6499,7 @@ async function runCodex(opts) {
|
|
|
6065
6499
|
const agentsPath = agentsFilePath();
|
|
6066
6500
|
let configRaw = "";
|
|
6067
6501
|
try {
|
|
6068
|
-
configRaw = await
|
|
6502
|
+
configRaw = await fs14.readFile(configPath, "utf8");
|
|
6069
6503
|
} catch (err) {
|
|
6070
6504
|
if (err.code !== "ENOENT") {
|
|
6071
6505
|
console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
|
|
@@ -6074,7 +6508,7 @@ async function runCodex(opts) {
|
|
|
6074
6508
|
}
|
|
6075
6509
|
let agentsRaw = "";
|
|
6076
6510
|
try {
|
|
6077
|
-
agentsRaw = await
|
|
6511
|
+
agentsRaw = await fs14.readFile(agentsPath, "utf8");
|
|
6078
6512
|
} catch (err) {
|
|
6079
6513
|
if (err.code !== "ENOENT") {
|
|
6080
6514
|
console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
|
|
@@ -6114,15 +6548,15 @@ async function runCodex(opts) {
|
|
|
6114
6548
|
return { exitCode: 0 };
|
|
6115
6549
|
}
|
|
6116
6550
|
if (config.changed) {
|
|
6117
|
-
await
|
|
6118
|
-
await
|
|
6551
|
+
await fs14.mkdir(path15.dirname(configPath), { recursive: true });
|
|
6552
|
+
await fs14.writeFile(configPath, config.text, "utf8");
|
|
6119
6553
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
6120
6554
|
} else {
|
|
6121
6555
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
6122
6556
|
}
|
|
6123
6557
|
if (agents.changed) {
|
|
6124
|
-
await
|
|
6125
|
-
await
|
|
6558
|
+
await fs14.mkdir(path15.dirname(agentsPath), { recursive: true });
|
|
6559
|
+
await fs14.writeFile(agentsPath, agents.text, "utf8");
|
|
6126
6560
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
6127
6561
|
} else {
|
|
6128
6562
|
console.log(`neat codex: ${agentsPath} already has the graph-first block`);
|
|
@@ -6178,9 +6612,9 @@ async function runCodexCommand(args) {
|
|
|
6178
6612
|
}
|
|
6179
6613
|
|
|
6180
6614
|
// src/editors-cli.ts
|
|
6181
|
-
import
|
|
6182
|
-
import
|
|
6183
|
-
import { promises as
|
|
6615
|
+
import path16 from "path";
|
|
6616
|
+
import os4 from "os";
|
|
6617
|
+
import { promises as fs15 } from "fs";
|
|
6184
6618
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
6185
6619
|
import * as jsonc from "jsonc-parser";
|
|
6186
6620
|
var NEAT_MCP_SERVER = {
|
|
@@ -6200,21 +6634,21 @@ var NEAT_CRUSH_SERVER = {
|
|
|
6200
6634
|
var GRAPH_FIRST_MARKER_OPEN = "<!-- neat:graph-first -->";
|
|
6201
6635
|
var GRAPH_FIRST_MARKER_CLOSE = "<!-- /neat:graph-first -->";
|
|
6202
6636
|
function homeDir() {
|
|
6203
|
-
return process.env.HOME ?? process.env.USERPROFILE ??
|
|
6637
|
+
return process.env.HOME ?? process.env.USERPROFILE ?? os4.homedir();
|
|
6204
6638
|
}
|
|
6205
6639
|
function xdgConfigDir() {
|
|
6206
6640
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
6207
|
-
return xdg && xdg.length > 0 ?
|
|
6641
|
+
return xdg && xdg.length > 0 ? path16.resolve(xdg) : path16.join(homeDir(), ".config");
|
|
6208
6642
|
}
|
|
6209
6643
|
function envOverride(name) {
|
|
6210
6644
|
const v = process.env[name];
|
|
6211
|
-
return v && v.length > 0 ?
|
|
6645
|
+
return v && v.length > 0 ? path16.resolve(v) : void 0;
|
|
6212
6646
|
}
|
|
6213
6647
|
var CURSOR_CLIENT = {
|
|
6214
6648
|
id: "cursor",
|
|
6215
6649
|
label: "Cursor",
|
|
6216
6650
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
6217
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
6651
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path16.join(homeDir(), ".cursor", "mcp.json"),
|
|
6218
6652
|
mcpContainerKey: "mcpServers",
|
|
6219
6653
|
format: "json",
|
|
6220
6654
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -6226,7 +6660,7 @@ var DEVIN_CLIENT = {
|
|
|
6226
6660
|
id: "devin",
|
|
6227
6661
|
label: "Devin Desktop (Cascade)",
|
|
6228
6662
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
6229
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
6663
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path16.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
6230
6664
|
mcpContainerKey: "mcpServers",
|
|
6231
6665
|
format: "json",
|
|
6232
6666
|
rulesFileName: ".windsurfrules"
|
|
@@ -6235,7 +6669,7 @@ var GEMINI_CLIENT = {
|
|
|
6235
6669
|
id: "gemini",
|
|
6236
6670
|
label: "Gemini CLI",
|
|
6237
6671
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
6238
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
6672
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path16.join(homeDir(), ".gemini", "settings.json"),
|
|
6239
6673
|
mcpContainerKey: "mcpServers",
|
|
6240
6674
|
format: "json",
|
|
6241
6675
|
rulesFileName: "GEMINI.md"
|
|
@@ -6244,7 +6678,7 @@ var QWEN_CLIENT = {
|
|
|
6244
6678
|
id: "qwen",
|
|
6245
6679
|
label: "Qwen Code",
|
|
6246
6680
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
6247
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
6681
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path16.join(homeDir(), ".qwen", "settings.json"),
|
|
6248
6682
|
mcpContainerKey: "mcpServers",
|
|
6249
6683
|
format: "json",
|
|
6250
6684
|
rulesFileName: "QWEN.md"
|
|
@@ -6253,7 +6687,7 @@ var AMAZONQ_CLIENT = {
|
|
|
6253
6687
|
id: "amazonq",
|
|
6254
6688
|
label: "Amazon Q Developer CLI",
|
|
6255
6689
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
6256
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
6690
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path16.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
6257
6691
|
mcpContainerKey: "mcpServers",
|
|
6258
6692
|
format: "json"
|
|
6259
6693
|
};
|
|
@@ -6261,7 +6695,7 @@ var ROOCODE_CLIENT = {
|
|
|
6261
6695
|
id: "roocode",
|
|
6262
6696
|
label: "Roo Code",
|
|
6263
6697
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
6264
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
6698
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path16.join(process.cwd(), ".roo", "mcp.json"),
|
|
6265
6699
|
mcpContainerKey: "mcpServers",
|
|
6266
6700
|
format: "json"
|
|
6267
6701
|
};
|
|
@@ -6274,9 +6708,9 @@ var ZED_CLIENT = {
|
|
|
6274
6708
|
if (override) return override;
|
|
6275
6709
|
if (process.platform === "win32") {
|
|
6276
6710
|
const appData = process.env.APPDATA;
|
|
6277
|
-
if (appData && appData.length > 0) return
|
|
6711
|
+
if (appData && appData.length > 0) return path16.join(appData, "Zed", "settings.json");
|
|
6278
6712
|
}
|
|
6279
|
-
return
|
|
6713
|
+
return path16.join(homeDir(), ".config", "zed", "settings.json");
|
|
6280
6714
|
},
|
|
6281
6715
|
mcpContainerKey: "context_servers",
|
|
6282
6716
|
format: "jsonc",
|
|
@@ -6286,7 +6720,7 @@ var OPENCODE_CLIENT = {
|
|
|
6286
6720
|
id: "opencode",
|
|
6287
6721
|
label: "OpenCode",
|
|
6288
6722
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
6289
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
6723
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path16.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
6290
6724
|
mcpContainerKey: "mcp",
|
|
6291
6725
|
format: "json",
|
|
6292
6726
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -6296,7 +6730,7 @@ var CRUSH_CLIENT = {
|
|
|
6296
6730
|
id: "crush",
|
|
6297
6731
|
label: "Crush",
|
|
6298
6732
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
6299
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
6733
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path16.join(xdgConfigDir(), "crush", "crush.json"),
|
|
6300
6734
|
mcpContainerKey: "mcp",
|
|
6301
6735
|
format: "json",
|
|
6302
6736
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -6359,7 +6793,7 @@ async function planMcp(client, mcpPath) {
|
|
|
6359
6793
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
6360
6794
|
let raw = "";
|
|
6361
6795
|
try {
|
|
6362
|
-
raw = await
|
|
6796
|
+
raw = await fs15.readFile(mcpPath, "utf8");
|
|
6363
6797
|
} catch (err) {
|
|
6364
6798
|
const e = err;
|
|
6365
6799
|
if (e.code === "ENOENT") {
|
|
@@ -6401,7 +6835,7 @@ async function runEditorInstall(client, opts) {
|
|
|
6401
6835
|
const mcpPath = client.mcpConfigPath();
|
|
6402
6836
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
6403
6837
|
const hasRules = typeof client.rulesFileName === "string";
|
|
6404
|
-
const rulesPath = hasRules ?
|
|
6838
|
+
const rulesPath = hasRules ? path16.join(opts.projectDir, client.rulesFileName) : "";
|
|
6405
6839
|
const mcp = await planMcp(client, mcpPath);
|
|
6406
6840
|
if (mcp === null) return { exitCode: 1 };
|
|
6407
6841
|
let existingRules = "";
|
|
@@ -6410,7 +6844,7 @@ async function runEditorInstall(client, opts) {
|
|
|
6410
6844
|
let block = "";
|
|
6411
6845
|
if (hasRules) {
|
|
6412
6846
|
try {
|
|
6413
|
-
existingRules = await
|
|
6847
|
+
existingRules = await fs15.readFile(rulesPath, "utf8");
|
|
6414
6848
|
} catch (err) {
|
|
6415
6849
|
if (err.code !== "ENOENT") {
|
|
6416
6850
|
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
@@ -6444,11 +6878,11 @@ async function runEditorInstall(client, opts) {
|
|
|
6444
6878
|
);
|
|
6445
6879
|
return { exitCode: 0 };
|
|
6446
6880
|
}
|
|
6447
|
-
await
|
|
6448
|
-
await
|
|
6881
|
+
await fs15.mkdir(path16.dirname(mcpPath), { recursive: true });
|
|
6882
|
+
await fs15.writeFile(mcpPath, mcp.text, "utf8");
|
|
6449
6883
|
if (hasRules) {
|
|
6450
|
-
await
|
|
6451
|
-
await
|
|
6884
|
+
await fs15.mkdir(path16.dirname(rulesPath), { recursive: true });
|
|
6885
|
+
await fs15.writeFile(rulesPath, newRules, "utf8");
|
|
6452
6886
|
}
|
|
6453
6887
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
6454
6888
|
console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
|
|
@@ -6939,7 +7373,7 @@ function sleep(ms, signal) {
|
|
|
6939
7373
|
}
|
|
6940
7374
|
|
|
6941
7375
|
// src/cli-verbs.ts
|
|
6942
|
-
import
|
|
7376
|
+
import path17 from "path";
|
|
6943
7377
|
async function resolveProjectEntry(opts) {
|
|
6944
7378
|
const entries = await listProjects();
|
|
6945
7379
|
if (opts.project) {
|
|
@@ -6949,7 +7383,7 @@ async function resolveProjectEntry(opts) {
|
|
|
6949
7383
|
const cwd = opts.cwd ?? process.cwd();
|
|
6950
7384
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
6951
7385
|
for (const entry2 of entries) {
|
|
6952
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
7386
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path17.sep}`)) {
|
|
6953
7387
|
return entry2;
|
|
6954
7388
|
}
|
|
6955
7389
|
}
|
|
@@ -7254,6 +7688,12 @@ function usage5() {
|
|
|
7254
7688
|
console.log(" doctor Preflight this directory's setup \u2014 Node version, project,");
|
|
7255
7689
|
console.log(" and daemon reachability \u2014 and print a fix for anything down.");
|
|
7256
7690
|
console.log(" Flags: --json. Exits 0 when all pass, 1 when a check fails.");
|
|
7691
|
+
console.log(" login Connect this machine to a hosted NEAT and make it the default");
|
|
7692
|
+
console.log(" for the CLI and the MCP server. Paste the daemon endpoint +");
|
|
7693
|
+
console.log(" token, or run interactively (the token is read without echo).");
|
|
7694
|
+
console.log(" Flags: --endpoint <url>, --token <token>, --name <name>, --json.");
|
|
7695
|
+
console.log(" logout Clear the active hosted profile (back to your local daemon);");
|
|
7696
|
+
console.log(" --name <name> removes that profile entirely.");
|
|
7257
7697
|
console.log("");
|
|
7258
7698
|
console.log("query commands (mirror the MCP tools, ADR-050):");
|
|
7259
7699
|
console.log(" ask <question> Plain-language door: resolves the question to");
|
|
@@ -7304,6 +7744,7 @@ function usage5() {
|
|
|
7304
7744
|
}
|
|
7305
7745
|
var STRING_FLAGS = [
|
|
7306
7746
|
["--project", "project"],
|
|
7747
|
+
["--profile", "profile"],
|
|
7307
7748
|
["--depth", "depth"],
|
|
7308
7749
|
["--limit", "limit"],
|
|
7309
7750
|
["--edge-type", "edgeType"],
|
|
@@ -7321,6 +7762,7 @@ function parseArgs(rest) {
|
|
|
7321
7762
|
const positional = [];
|
|
7322
7763
|
const out = {
|
|
7323
7764
|
project: null,
|
|
7765
|
+
profile: null,
|
|
7324
7766
|
apply: false,
|
|
7325
7767
|
dryRun: false,
|
|
7326
7768
|
noInstall: false,
|
|
@@ -7479,7 +7921,7 @@ async function buildPatchSections(services, project) {
|
|
|
7479
7921
|
}
|
|
7480
7922
|
async function runInit(opts) {
|
|
7481
7923
|
const written = [];
|
|
7482
|
-
const stat = await
|
|
7924
|
+
const stat = await fs16.stat(opts.scanPath).catch(() => null);
|
|
7483
7925
|
if (!stat || !stat.isDirectory()) {
|
|
7484
7926
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
7485
7927
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -7488,13 +7930,13 @@ async function runInit(opts) {
|
|
|
7488
7930
|
printDiscoveryReport(opts, services);
|
|
7489
7931
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
7490
7932
|
const patch = renderPatch(sections);
|
|
7491
|
-
const patchPath =
|
|
7933
|
+
const patchPath = path18.join(opts.scanPath, "neat.patch");
|
|
7492
7934
|
if (opts.dryRun) {
|
|
7493
|
-
await
|
|
7935
|
+
await fs16.writeFile(patchPath, patch, "utf8");
|
|
7494
7936
|
written.push(patchPath);
|
|
7495
7937
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
7496
|
-
const gitignorePath =
|
|
7497
|
-
const gitignoreExists = await
|
|
7938
|
+
const gitignorePath = path18.join(opts.scanPath, ".gitignore");
|
|
7939
|
+
const gitignoreExists = await fs16.stat(gitignorePath).then(() => true).catch(() => false);
|
|
7498
7940
|
const verb = gitignoreExists ? "append" : "create";
|
|
7499
7941
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
7500
7942
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -7505,9 +7947,9 @@ async function runInit(opts) {
|
|
|
7505
7947
|
const graph = getGraph(graphKey);
|
|
7506
7948
|
const projectPaths = pathsForProject(
|
|
7507
7949
|
graphKey,
|
|
7508
|
-
|
|
7950
|
+
path18.join(opts.scanPath, "neat-out")
|
|
7509
7951
|
);
|
|
7510
|
-
const errorsPath =
|
|
7952
|
+
const errorsPath = path18.join(path18.dirname(opts.outPath), path18.basename(projectPaths.errorsPath));
|
|
7511
7953
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
7512
7954
|
await saveGraphToDisk(graph, opts.outPath);
|
|
7513
7955
|
written.push(opts.outPath);
|
|
@@ -7586,7 +8028,7 @@ async function runInit(opts) {
|
|
|
7586
8028
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
7587
8029
|
}
|
|
7588
8030
|
} else {
|
|
7589
|
-
await
|
|
8031
|
+
await fs16.writeFile(patchPath, patch, "utf8");
|
|
7590
8032
|
written.push(patchPath);
|
|
7591
8033
|
}
|
|
7592
8034
|
}
|
|
@@ -7627,9 +8069,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
7627
8069
|
};
|
|
7628
8070
|
function claudeConfigPath() {
|
|
7629
8071
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
7630
|
-
if (override && override.length > 0) return
|
|
8072
|
+
if (override && override.length > 0) return path18.resolve(override);
|
|
7631
8073
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
7632
|
-
return
|
|
8074
|
+
return path18.join(home, ".claude.json");
|
|
7633
8075
|
}
|
|
7634
8076
|
async function runSkill(opts) {
|
|
7635
8077
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -7641,7 +8083,7 @@ async function runSkill(opts) {
|
|
|
7641
8083
|
const target = claudeConfigPath();
|
|
7642
8084
|
let existing = {};
|
|
7643
8085
|
try {
|
|
7644
|
-
existing = JSON.parse(await
|
|
8086
|
+
existing = JSON.parse(await fs16.readFile(target, "utf8"));
|
|
7645
8087
|
} catch (err) {
|
|
7646
8088
|
if (err.code !== "ENOENT") {
|
|
7647
8089
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -7653,8 +8095,8 @@ async function runSkill(opts) {
|
|
|
7653
8095
|
...existing,
|
|
7654
8096
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
7655
8097
|
};
|
|
7656
|
-
await
|
|
7657
|
-
await
|
|
8098
|
+
await fs16.mkdir(path18.dirname(target), { recursive: true });
|
|
8099
|
+
await fs16.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
7658
8100
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
7659
8101
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
7660
8102
|
console.log("");
|
|
@@ -7698,6 +8140,16 @@ async function main() {
|
|
|
7698
8140
|
if (code !== 0) process.exit(code);
|
|
7699
8141
|
return;
|
|
7700
8142
|
}
|
|
8143
|
+
if (cmd0 === "login") {
|
|
8144
|
+
const code = await runLoginCommand(argv.slice(1));
|
|
8145
|
+
if (code !== 0) process.exit(code);
|
|
8146
|
+
return;
|
|
8147
|
+
}
|
|
8148
|
+
if (cmd0 === "logout") {
|
|
8149
|
+
const code = await runLogoutCommand(argv.slice(1));
|
|
8150
|
+
if (code !== 0) process.exit(code);
|
|
8151
|
+
return;
|
|
8152
|
+
}
|
|
7701
8153
|
if (cmd0 === "hooks") {
|
|
7702
8154
|
const code = await runHooksCommand(argv.slice(1));
|
|
7703
8155
|
if (code !== 0) process.exit(code);
|
|
@@ -7750,12 +8202,12 @@ async function main() {
|
|
|
7750
8202
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
7751
8203
|
process.exit(2);
|
|
7752
8204
|
}
|
|
7753
|
-
const scanPath =
|
|
8205
|
+
const scanPath = path18.resolve(target);
|
|
7754
8206
|
const projectExplicit = parsed.project !== null;
|
|
7755
|
-
const projectName = projectExplicit ? project :
|
|
8207
|
+
const projectName = projectExplicit ? project : path18.basename(scanPath);
|
|
7756
8208
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
7757
|
-
const fallback = pathsForProject(projectKey,
|
|
7758
|
-
const outPath =
|
|
8209
|
+
const fallback = pathsForProject(projectKey, path18.join(scanPath, "neat-out")).snapshotPath;
|
|
8210
|
+
const outPath = path18.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
7759
8211
|
const result = await runInit({
|
|
7760
8212
|
scanPath,
|
|
7761
8213
|
outPath,
|
|
@@ -7776,21 +8228,21 @@ async function main() {
|
|
|
7776
8228
|
usage5();
|
|
7777
8229
|
process.exit(2);
|
|
7778
8230
|
}
|
|
7779
|
-
const scanPath =
|
|
7780
|
-
const stat = await
|
|
8231
|
+
const scanPath = path18.resolve(target);
|
|
8232
|
+
const stat = await fs16.stat(scanPath).catch(() => null);
|
|
7781
8233
|
if (!stat || !stat.isDirectory()) {
|
|
7782
8234
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
7783
8235
|
process.exit(2);
|
|
7784
8236
|
}
|
|
7785
|
-
const projectPaths = pathsForProject(project,
|
|
7786
|
-
const outPath =
|
|
7787
|
-
const errorsPath =
|
|
7788
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
8237
|
+
const projectPaths = pathsForProject(project, path18.join(scanPath, "neat-out"));
|
|
8238
|
+
const outPath = path18.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
8239
|
+
const errorsPath = path18.resolve(
|
|
8240
|
+
process.env.NEAT_ERRORS_PATH ?? path18.join(path18.dirname(outPath), path18.basename(projectPaths.errorsPath))
|
|
7789
8241
|
);
|
|
7790
|
-
const staleEventsPath =
|
|
7791
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
8242
|
+
const staleEventsPath = path18.resolve(
|
|
8243
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path18.join(path18.dirname(outPath), path18.basename(projectPaths.staleEventsPath))
|
|
7792
8244
|
);
|
|
7793
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
8245
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path18.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
7794
8246
|
const handle = await startWatch(getGraph(project), {
|
|
7795
8247
|
scanPath,
|
|
7796
8248
|
outPath,
|
|
@@ -7799,7 +8251,7 @@ async function main() {
|
|
|
7799
8251
|
project,
|
|
7800
8252
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
7801
8253
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
7802
|
-
neatHome: process.env.NEAT_HOME ?
|
|
8254
|
+
neatHome: process.env.NEAT_HOME ? path18.resolve(process.env.NEAT_HOME) : path18.join(os5.homedir(), ".neat"),
|
|
7803
8255
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
7804
8256
|
host: process.env.HOST ?? "0.0.0.0",
|
|
7805
8257
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -7981,11 +8433,11 @@ async function main() {
|
|
|
7981
8433
|
process.exit(1);
|
|
7982
8434
|
}
|
|
7983
8435
|
async function tryOrchestrator(cmd, parsed) {
|
|
7984
|
-
const scanPath =
|
|
7985
|
-
const stat = await
|
|
8436
|
+
const scanPath = path18.resolve(cmd);
|
|
8437
|
+
const stat = await fs16.stat(scanPath).catch(() => null);
|
|
7986
8438
|
if (!stat || !stat.isDirectory()) return null;
|
|
7987
8439
|
const projectExplicit = parsed.project !== null;
|
|
7988
|
-
const projectName = projectExplicit ? parsed.project :
|
|
8440
|
+
const projectName = projectExplicit ? parsed.project : path18.basename(scanPath);
|
|
7989
8441
|
const result = await runOrchestrator({
|
|
7990
8442
|
scanPath,
|
|
7991
8443
|
project: projectName,
|
|
@@ -8048,19 +8500,50 @@ Pass --project <name> to choose:
|
|
|
8048
8500
|
${names}`
|
|
8049
8501
|
);
|
|
8050
8502
|
}
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
|
|
8055
|
-
|
|
8056
|
-
|
|
8503
|
+
var UnknownProfileError = class extends Error {
|
|
8504
|
+
constructor(name) {
|
|
8505
|
+
super(
|
|
8506
|
+
`no profile named "${name}" in ~/.neat/profiles.json \u2014 run \`neat login\` first, or drop --profile / NEAT_PROFILE`
|
|
8507
|
+
);
|
|
8508
|
+
this.name = "UnknownProfileError";
|
|
8057
8509
|
}
|
|
8058
|
-
|
|
8510
|
+
};
|
|
8511
|
+
async function resolveClientTarget(opts = {}) {
|
|
8512
|
+
const named = opts.profile ?? process.env.NEAT_PROFILE;
|
|
8513
|
+
if (named && named.length > 0) {
|
|
8514
|
+
const profile = await resolveProfile(named);
|
|
8515
|
+
if (!profile) throw new UnknownProfileError(named);
|
|
8516
|
+
return { endpoint: profile.endpoint, authToken: profile.authToken, source: "profile" };
|
|
8517
|
+
}
|
|
8518
|
+
const pin = process.env.NEAT_API_URL ?? process.env.NEAT_CORE_URL;
|
|
8519
|
+
if (pin) return { endpoint: pin, authToken: resolveAuthToken(), source: "env" };
|
|
8520
|
+
const active = await getActiveProfile().catch(() => void 0);
|
|
8521
|
+
if (active) return { endpoint: active.endpoint, authToken: active.authToken, source: "active" };
|
|
8522
|
+
if (opts.project) {
|
|
8523
|
+
const daemon = await findDaemonByProject(opts.project);
|
|
8524
|
+
if (daemon) {
|
|
8525
|
+
return { endpoint: `http://localhost:${daemon.record.ports.rest}`, source: "daemon-record" };
|
|
8526
|
+
}
|
|
8527
|
+
}
|
|
8528
|
+
return { endpoint: "http://localhost:8080", source: "default" };
|
|
8529
|
+
}
|
|
8530
|
+
async function resolveDaemonUrl(project, profile) {
|
|
8531
|
+
return (await resolveClientTarget({ project, profile })).endpoint;
|
|
8059
8532
|
}
|
|
8060
8533
|
async function runQueryVerb(cmd, parsed) {
|
|
8061
8534
|
const requestedProject = resolveProjectFlag(parsed);
|
|
8062
|
-
|
|
8063
|
-
|
|
8535
|
+
let target;
|
|
8536
|
+
try {
|
|
8537
|
+
target = await resolveClientTarget({ project: requestedProject, profile: parsed.profile ?? void 0 });
|
|
8538
|
+
} catch (err) {
|
|
8539
|
+
if (err instanceof UnknownProfileError) {
|
|
8540
|
+
process.stderr.write(`${err.message}
|
|
8541
|
+
`);
|
|
8542
|
+
return 2;
|
|
8543
|
+
}
|
|
8544
|
+
throw err;
|
|
8545
|
+
}
|
|
8546
|
+
const client = createHttpClient(target.endpoint, target.authToken);
|
|
8064
8547
|
const positional = parsed.positional;
|
|
8065
8548
|
let makeWork;
|
|
8066
8549
|
switch (cmd) {
|
|
@@ -8224,7 +8707,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
8224
8707
|
const detail = err.responseBody.length > 0 ? err.responseBody : err.message;
|
|
8225
8708
|
console.error(`neat ${cmd}: ${detail.trim()}`);
|
|
8226
8709
|
} else if (err instanceof TransportError) {
|
|
8227
|
-
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${
|
|
8710
|
+
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${target.endpoint})`);
|
|
8228
8711
|
} else {
|
|
8229
8712
|
console.error(`neat ${cmd}: ${err.message}`);
|
|
8230
8713
|
}
|
|
@@ -8233,9 +8716,14 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
8233
8716
|
}
|
|
8234
8717
|
async function runMonitorVerb(parsed) {
|
|
8235
8718
|
const requestedProject = resolveProjectFlag(parsed);
|
|
8236
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8719
|
+
let target;
|
|
8720
|
+
try {
|
|
8721
|
+
target = await resolveClientTarget({ project: requestedProject, profile: parsed.profile ?? void 0 });
|
|
8722
|
+
} catch (err) {
|
|
8723
|
+
if (err instanceof UnknownProfileError) return 0;
|
|
8724
|
+
throw err;
|
|
8725
|
+
}
|
|
8726
|
+
const client = createHttpClient(target.endpoint, target.authToken);
|
|
8239
8727
|
let project;
|
|
8240
8728
|
try {
|
|
8241
8729
|
project = await resolveProjectForVerb(client, parsed);
|
|
@@ -8251,10 +8739,10 @@ async function runMonitorVerb(parsed) {
|
|
|
8251
8739
|
process.once("SIGTERM", onSignal);
|
|
8252
8740
|
try {
|
|
8253
8741
|
return await runMonitor({
|
|
8254
|
-
baseUrl,
|
|
8742
|
+
baseUrl: target.endpoint,
|
|
8255
8743
|
project,
|
|
8256
8744
|
json: parsed.json,
|
|
8257
|
-
authToken:
|
|
8745
|
+
authToken: target.authToken,
|
|
8258
8746
|
signal: controller.signal
|
|
8259
8747
|
});
|
|
8260
8748
|
} finally {
|
|
@@ -8273,12 +8761,14 @@ export {
|
|
|
8273
8761
|
CLAUDE_SKILL_CONFIG,
|
|
8274
8762
|
ProjectResolutionError,
|
|
8275
8763
|
QUERY_VERBS,
|
|
8764
|
+
UnknownProfileError,
|
|
8276
8765
|
commandPrefix,
|
|
8277
8766
|
isNpxInvocation,
|
|
8278
8767
|
main,
|
|
8279
8768
|
parseArgs,
|
|
8280
8769
|
printBanner,
|
|
8281
8770
|
readPackageVersion,
|
|
8771
|
+
resolveClientTarget,
|
|
8282
8772
|
resolveDaemonUrl,
|
|
8283
8773
|
resolveProjectForVerb,
|
|
8284
8774
|
runInit,
|