@neat.is/core 0.10.1 → 0.10.2
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-AWIK6T3G.js → chunk-CAGAHYDB.js} +2 -2
- package/dist/{chunk-E3YFFIHE.js → chunk-TXKOAART.js} +796 -562
- package/dist/chunk-TXKOAART.js.map +1 -0
- package/dist/cli.cjs +1518 -1062
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +5 -2
- package/dist/cli.d.ts +5 -2
- package/dist/cli.js +436 -212
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +838 -621
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +849 -632
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +712 -525
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-E3YFFIHE.js.map +0 -1
- /package/dist/{chunk-AWIK6T3G.js.map → chunk-CAGAHYDB.js.map} +0 -0
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-CAGAHYDB.js";
|
|
12
12
|
import {
|
|
13
13
|
buildSearchIndex
|
|
14
14
|
} from "./chunk-BC53SCT7.js";
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
PolicyViolationsLog,
|
|
18
18
|
ProjectNameCollisionError,
|
|
19
19
|
Projects,
|
|
20
|
+
RegistryError,
|
|
20
21
|
SCHEMA_VERSION,
|
|
21
22
|
addCallEdges,
|
|
22
23
|
addConfigNodes,
|
|
@@ -42,9 +43,12 @@ import {
|
|
|
42
43
|
evaluateAllPolicies,
|
|
43
44
|
extractFromDirectory,
|
|
44
45
|
findDaemonByProject,
|
|
46
|
+
findProjectByPath,
|
|
45
47
|
formatExtractionBanner,
|
|
46
48
|
formatPrecisionFloorBanner,
|
|
49
|
+
formatSkippedBanner,
|
|
47
50
|
getGraph,
|
|
51
|
+
getProject,
|
|
48
52
|
getProviderFieldSchema,
|
|
49
53
|
isEnvRef,
|
|
50
54
|
isPushProvider,
|
|
@@ -78,7 +82,7 @@ import {
|
|
|
78
82
|
startStalenessLoop,
|
|
79
83
|
upsertConnectorEntry,
|
|
80
84
|
validateConnectorEntry
|
|
81
|
-
} from "./chunk-
|
|
85
|
+
} from "./chunk-TXKOAART.js";
|
|
82
86
|
import {
|
|
83
87
|
startOtelGrpcReceiver
|
|
84
88
|
} from "./chunk-5SJHA5ZH.js";
|
|
@@ -93,7 +97,7 @@ import {
|
|
|
93
97
|
} from "./chunk-M7CGATVC.js";
|
|
94
98
|
|
|
95
99
|
// src/cli.ts
|
|
96
|
-
import
|
|
100
|
+
import path21 from "path";
|
|
97
101
|
import os5 from "os";
|
|
98
102
|
import { promises as fs18 } from "fs";
|
|
99
103
|
|
|
@@ -3937,6 +3941,20 @@ import path10 from "path";
|
|
|
3937
3941
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3938
3942
|
import { spawn as spawn2 } from "child_process";
|
|
3939
3943
|
import readline from "readline";
|
|
3944
|
+
var SnapshotWriteError = class extends Error {
|
|
3945
|
+
constructor(scanPath, nodeCount, edgeCount, cause) {
|
|
3946
|
+
super(`could not write the snapshot for ${scanPath}: ${cause.message}`);
|
|
3947
|
+
this.scanPath = scanPath;
|
|
3948
|
+
this.nodeCount = nodeCount;
|
|
3949
|
+
this.edgeCount = edgeCount;
|
|
3950
|
+
this.cause = cause;
|
|
3951
|
+
this.name = "SnapshotWriteError";
|
|
3952
|
+
}
|
|
3953
|
+
scanPath;
|
|
3954
|
+
nodeCount;
|
|
3955
|
+
edgeCount;
|
|
3956
|
+
cause;
|
|
3957
|
+
};
|
|
3940
3958
|
async function extractAndPersist(opts) {
|
|
3941
3959
|
const services = await discoverServices(opts.scanPath);
|
|
3942
3960
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
@@ -3948,7 +3966,11 @@ async function extractAndPersist(opts) {
|
|
|
3948
3966
|
errorsPath: projectPaths.errorsPath
|
|
3949
3967
|
});
|
|
3950
3968
|
if (!opts.dryRun) {
|
|
3951
|
-
|
|
3969
|
+
try {
|
|
3970
|
+
await saveGraphToDisk(graph, projectPaths.snapshotPath);
|
|
3971
|
+
} catch (err) {
|
|
3972
|
+
throw new SnapshotWriteError(opts.scanPath, graph.order, graph.size, err);
|
|
3973
|
+
}
|
|
3952
3974
|
}
|
|
3953
3975
|
return {
|
|
3954
3976
|
graph,
|
|
@@ -4331,6 +4353,16 @@ async function healthIsForProject(restPort, project) {
|
|
|
4331
4353
|
function daemonLogPath(projectPath3) {
|
|
4332
4354
|
return path10.join(projectPath3, "neat-out", "daemon.log");
|
|
4333
4355
|
}
|
|
4356
|
+
var DAEMON_LOG_TAIL_LINES = 12;
|
|
4357
|
+
async function readDaemonLogTail(projectPath3, maxLines = DAEMON_LOG_TAIL_LINES) {
|
|
4358
|
+
try {
|
|
4359
|
+
const raw = await fs9.readFile(daemonLogPath(projectPath3), "utf8");
|
|
4360
|
+
const lines = raw.split(/\r?\n/).map((l) => l.trimEnd()).filter((l) => l.trim().length > 0);
|
|
4361
|
+
return lines.slice(-maxLines);
|
|
4362
|
+
} catch {
|
|
4363
|
+
return [];
|
|
4364
|
+
}
|
|
4365
|
+
}
|
|
4334
4366
|
function spawnDaemonDetached(spec) {
|
|
4335
4367
|
const here = path10.dirname(fileURLToPath2(import.meta.url));
|
|
4336
4368
|
const candidates = [
|
|
@@ -4377,6 +4409,72 @@ function spawnDaemonDetached(spec) {
|
|
|
4377
4409
|
if (logFd !== null) fsSync.closeSync(logFd);
|
|
4378
4410
|
return child;
|
|
4379
4411
|
}
|
|
4412
|
+
async function ensureProjectDaemon(opts) {
|
|
4413
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
|
|
4414
|
+
const bindHost = resolveHost(
|
|
4415
|
+
{},
|
|
4416
|
+
typeof process.env.NEAT_AUTH_TOKEN === "string" && process.env.NEAT_AUTH_TOKEN.length > 0
|
|
4417
|
+
);
|
|
4418
|
+
const persistedPorts = await persistedPortsFor(opts.projectPath);
|
|
4419
|
+
if (persistedPorts && await healthIsForProject(persistedPorts.rest, opts.project)) {
|
|
4420
|
+
return { status: "already-running", ports: persistedPorts };
|
|
4421
|
+
}
|
|
4422
|
+
let allocated;
|
|
4423
|
+
if (persistedPorts && await isPortFree(persistedPorts.rest, bindHost) && await tripleFree(persistedPorts, bindHost)) {
|
|
4424
|
+
allocated = persistedPorts;
|
|
4425
|
+
} else {
|
|
4426
|
+
allocated = await allocatePorts(bindHost);
|
|
4427
|
+
}
|
|
4428
|
+
if (!allocated) return { status: "no-ports" };
|
|
4429
|
+
const release = await acquireSpawnLock(opts.projectPath);
|
|
4430
|
+
if (!release) {
|
|
4431
|
+
const reused = await waitForPeerDaemon(allocated.rest, opts.project, timeoutMs);
|
|
4432
|
+
return reused ? { status: "already-running", ports: allocated } : { status: "peer-timeout" };
|
|
4433
|
+
}
|
|
4434
|
+
try {
|
|
4435
|
+
if (await healthIsForProject(allocated.rest, opts.project)) {
|
|
4436
|
+
return { status: "already-running", ports: allocated };
|
|
4437
|
+
}
|
|
4438
|
+
spawnDaemonDetached({
|
|
4439
|
+
project: opts.project,
|
|
4440
|
+
projectPath: opts.projectPath,
|
|
4441
|
+
ports: allocated
|
|
4442
|
+
});
|
|
4443
|
+
const ready = await waitForDaemonReady(allocated.rest, opts.project, timeoutMs);
|
|
4444
|
+
if (!ready.ready) {
|
|
4445
|
+
return {
|
|
4446
|
+
status: "timed-out",
|
|
4447
|
+
ports: allocated,
|
|
4448
|
+
stillBootstrapping: ready.stillBootstrapping,
|
|
4449
|
+
brokenProjects: ready.brokenProjects
|
|
4450
|
+
};
|
|
4451
|
+
}
|
|
4452
|
+
return { status: "spawned", ports: allocated, brokenProjects: ready.brokenProjects };
|
|
4453
|
+
} catch (err) {
|
|
4454
|
+
return { status: "spawn-failed", message: err.message };
|
|
4455
|
+
} finally {
|
|
4456
|
+
await release();
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
async function describeDaemonTimeout(outcome, projectPath3, timeoutMs) {
|
|
4460
|
+
const lines = [`neat: daemon did not become ready within ${timeoutMs}ms`];
|
|
4461
|
+
if (outcome.stillBootstrapping.length > 0) {
|
|
4462
|
+
lines.push(`neat: still bootstrapping: ${outcome.stillBootstrapping.join(", ")}`);
|
|
4463
|
+
}
|
|
4464
|
+
if (outcome.brokenProjects.length > 0) {
|
|
4465
|
+
lines.push(`neat: broken projects: ${outcome.brokenProjects.join(", ")}`);
|
|
4466
|
+
}
|
|
4467
|
+
const logPath = daemonLogPath(projectPath3);
|
|
4468
|
+
const tail = await readDaemonLogTail(projectPath3);
|
|
4469
|
+
if (tail.length > 0) {
|
|
4470
|
+
lines.push("");
|
|
4471
|
+
lines.push(`neat: the daemon's last words (${path10.relative(projectPath3, logPath)}):`);
|
|
4472
|
+
for (const line of tail) lines.push(` ${line}`);
|
|
4473
|
+
} else {
|
|
4474
|
+
lines.push(`neat: see ${logPath}`);
|
|
4475
|
+
}
|
|
4476
|
+
return lines;
|
|
4477
|
+
}
|
|
4380
4478
|
function openBrowser(url) {
|
|
4381
4479
|
if (!process.stdout.isTTY) return "failed";
|
|
4382
4480
|
const platform = process.platform;
|
|
@@ -4410,14 +4508,26 @@ async function runOrchestrator(opts) {
|
|
|
4410
4508
|
result.exitCode = 2;
|
|
4411
4509
|
return result;
|
|
4412
4510
|
}
|
|
4413
|
-
printBanner();
|
|
4511
|
+
if (!opts.skipBanner) printBanner();
|
|
4414
4512
|
console.log(`neat: ${opts.scanPath}`);
|
|
4415
4513
|
console.log("");
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4514
|
+
let persisted;
|
|
4515
|
+
try {
|
|
4516
|
+
persisted = await extractAndPersist({
|
|
4517
|
+
scanPath: opts.scanPath,
|
|
4518
|
+
project: opts.project,
|
|
4519
|
+
projectExplicit: opts.projectExplicit
|
|
4520
|
+
});
|
|
4521
|
+
} catch (err) {
|
|
4522
|
+
if (!(err instanceof SnapshotWriteError)) throw err;
|
|
4523
|
+
console.error(`neat: extracted ${opts.scanPath}, but could not write the snapshot.`);
|
|
4524
|
+
console.error(`neat: the graph came to ${err.nodeCount} nodes and ${err.edgeCount} edges \u2014 too large to save.`);
|
|
4525
|
+
console.error(`neat: point neat at one project instead of a tree that holds many:`);
|
|
4526
|
+
console.error(`neat: npx neat.is <path-to-one-project>`);
|
|
4527
|
+
console.error(`neat: cause: ${err.cause.message}`);
|
|
4528
|
+
result.exitCode = 1;
|
|
4529
|
+
return result;
|
|
4530
|
+
}
|
|
4421
4531
|
const { graph, services, languages } = persisted;
|
|
4422
4532
|
result.steps.discovery = { services: services.length, languages };
|
|
4423
4533
|
console.log(`discovered ${services.length} service(s) across ${languages.length} language(s)`);
|
|
@@ -4458,20 +4568,6 @@ async function runOrchestrator(opts) {
|
|
|
4458
4568
|
result.exitCode = 1;
|
|
4459
4569
|
return result;
|
|
4460
4570
|
}
|
|
4461
|
-
const siblings = await listProjects();
|
|
4462
|
-
const paused = [];
|
|
4463
|
-
for (const p of siblings) {
|
|
4464
|
-
if (p.name !== currentProjectName && p.status === "active") {
|
|
4465
|
-
await setStatus(p.name, "paused");
|
|
4466
|
-
paused.push(p.name);
|
|
4467
|
-
}
|
|
4468
|
-
}
|
|
4469
|
-
if (paused.length > 0) {
|
|
4470
|
-
const plural = paused.length === 1 ? "" : "s";
|
|
4471
|
-
console.log(
|
|
4472
|
-
`neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
|
|
4473
|
-
);
|
|
4474
|
-
}
|
|
4475
4571
|
if (!runApply) {
|
|
4476
4572
|
result.steps.apply.skipped = true;
|
|
4477
4573
|
console.log("skipped instrumentation (--no-instrument)");
|
|
@@ -4487,75 +4583,45 @@ async function runOrchestrator(opts) {
|
|
|
4487
4583
|
}
|
|
4488
4584
|
}
|
|
4489
4585
|
const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
|
|
4490
|
-
const
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4586
|
+
const outcome = await ensureProjectDaemon({
|
|
4587
|
+
project: currentProjectName,
|
|
4588
|
+
projectPath: opts.scanPath,
|
|
4589
|
+
timeoutMs
|
|
4590
|
+
});
|
|
4591
|
+
const allocated = "ports" in outcome ? outcome.ports : null;
|
|
4592
|
+
switch (outcome.status) {
|
|
4593
|
+
case "already-running":
|
|
4594
|
+
result.steps.daemon = "already-running";
|
|
4595
|
+
break;
|
|
4596
|
+
case "spawned":
|
|
4597
|
+
result.steps.daemon = "spawned";
|
|
4598
|
+
if (outcome.brokenProjects.length > 0) {
|
|
4599
|
+
console.warn(
|
|
4600
|
+
`neat: ${outcome.brokenProjects.length} project(s) reported broken: ${outcome.brokenProjects.join(", ")}`
|
|
4601
|
+
);
|
|
4602
|
+
}
|
|
4603
|
+
break;
|
|
4604
|
+
case "no-ports":
|
|
4506
4605
|
for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) {
|
|
4507
4606
|
console.error(line);
|
|
4508
4607
|
}
|
|
4509
4608
|
result.exitCode = 3;
|
|
4510
4609
|
return result;
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4515
|
-
if (reused) {
|
|
4516
|
-
result.steps.daemon = "already-running";
|
|
4517
|
-
} else {
|
|
4518
|
-
console.error("neat: another `neat` is spawning this project but its daemon did not come up in time");
|
|
4519
|
-
result.exitCode = 1;
|
|
4520
|
-
return result;
|
|
4521
|
-
}
|
|
4522
|
-
} else {
|
|
4523
|
-
try {
|
|
4524
|
-
if (await healthIsForProject(allocated.rest, currentProjectName)) {
|
|
4525
|
-
result.steps.daemon = "already-running";
|
|
4526
|
-
} else {
|
|
4527
|
-
spawnDaemonDetached({
|
|
4528
|
-
project: currentProjectName,
|
|
4529
|
-
projectPath: opts.scanPath,
|
|
4530
|
-
ports: allocated
|
|
4531
|
-
});
|
|
4532
|
-
const ready = await waitForDaemonReady(allocated.rest, currentProjectName, timeoutMs);
|
|
4533
|
-
result.steps.daemon = ready.ready ? "spawned" : "timed-out";
|
|
4534
|
-
if (!ready.ready) {
|
|
4535
|
-
console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
|
|
4536
|
-
if (ready.stillBootstrapping.length > 0) {
|
|
4537
|
-
console.error(`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`);
|
|
4538
|
-
}
|
|
4539
|
-
if (ready.brokenProjects.length > 0) {
|
|
4540
|
-
console.error(`neat: broken projects: ${ready.brokenProjects.join(", ")}`);
|
|
4541
|
-
}
|
|
4542
|
-
result.exitCode = 1;
|
|
4543
|
-
return result;
|
|
4544
|
-
}
|
|
4545
|
-
if (ready.brokenProjects.length > 0) {
|
|
4546
|
-
console.warn(
|
|
4547
|
-
`neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
|
|
4548
|
-
);
|
|
4549
|
-
}
|
|
4550
|
-
}
|
|
4551
|
-
} catch (err) {
|
|
4552
|
-
console.error(`neat: daemon spawn failed \u2014 ${err.message}`);
|
|
4553
|
-
result.exitCode = 1;
|
|
4554
|
-
return result;
|
|
4555
|
-
} finally {
|
|
4556
|
-
await release();
|
|
4610
|
+
case "timed-out":
|
|
4611
|
+
result.steps.daemon = "timed-out";
|
|
4612
|
+
for (const line of await describeDaemonTimeout(outcome, opts.scanPath, timeoutMs)) {
|
|
4613
|
+
console.error(line);
|
|
4557
4614
|
}
|
|
4558
|
-
|
|
4615
|
+
result.exitCode = 1;
|
|
4616
|
+
return result;
|
|
4617
|
+
case "peer-timeout":
|
|
4618
|
+
console.error("neat: another `neat` is spawning this project but its daemon did not come up in time");
|
|
4619
|
+
result.exitCode = 1;
|
|
4620
|
+
return result;
|
|
4621
|
+
case "spawn-failed":
|
|
4622
|
+
console.error(`neat: daemon spawn failed \u2014 ${outcome.message}`);
|
|
4623
|
+
result.exitCode = 1;
|
|
4624
|
+
return result;
|
|
4559
4625
|
}
|
|
4560
4626
|
const webPort = allocated?.web ?? NEAT_PORTS[2];
|
|
4561
4627
|
const dashboardUrl = opts.dashboardUrl ?? `http://localhost:${webPort}`;
|
|
@@ -5335,9 +5401,9 @@ function parseGitHubRemote(url) {
|
|
|
5335
5401
|
if (!owner || !name) return null;
|
|
5336
5402
|
return { owner, name };
|
|
5337
5403
|
}
|
|
5338
|
-
async function cpGetJson(fetchImpl, cpUrl,
|
|
5404
|
+
async function cpGetJson(fetchImpl, cpUrl, path22, accessToken) {
|
|
5339
5405
|
try {
|
|
5340
|
-
const res = await fetchImpl(`${cpUrl}${
|
|
5406
|
+
const res = await fetchImpl(`${cpUrl}${path22}`, {
|
|
5341
5407
|
headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
|
|
5342
5408
|
signal: AbortSignal.timeout(CP_TIMEOUT_MS)
|
|
5343
5409
|
});
|
|
@@ -5815,10 +5881,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5815
5881
|
const root = baseUrl.replace(/\/$/, "");
|
|
5816
5882
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
5817
5883
|
return {
|
|
5818
|
-
async get(
|
|
5884
|
+
async get(path22) {
|
|
5819
5885
|
let res;
|
|
5820
5886
|
try {
|
|
5821
|
-
res = await fetchImpl(`${root}${
|
|
5887
|
+
res = await fetchImpl(`${root}${path22}`, {
|
|
5822
5888
|
headers: { ...authHeader }
|
|
5823
5889
|
});
|
|
5824
5890
|
} catch (err) {
|
|
@@ -5830,16 +5896,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5830
5896
|
const body = await res.text().catch(() => "");
|
|
5831
5897
|
throw new HttpError(
|
|
5832
5898
|
res.status,
|
|
5833
|
-
`${res.status} ${res.statusText} on GET ${
|
|
5899
|
+
`${res.status} ${res.statusText} on GET ${path22}: ${body}`,
|
|
5834
5900
|
body
|
|
5835
5901
|
);
|
|
5836
5902
|
}
|
|
5837
5903
|
return await res.json();
|
|
5838
5904
|
},
|
|
5839
|
-
async post(
|
|
5905
|
+
async post(path22, body) {
|
|
5840
5906
|
let res;
|
|
5841
5907
|
try {
|
|
5842
|
-
res = await fetchImpl(`${root}${
|
|
5908
|
+
res = await fetchImpl(`${root}${path22}`, {
|
|
5843
5909
|
method: "POST",
|
|
5844
5910
|
headers: { "content-type": "application/json", ...authHeader },
|
|
5845
5911
|
body: JSON.stringify(body)
|
|
@@ -5853,7 +5919,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5853
5919
|
const text = await res.text().catch(() => "");
|
|
5854
5920
|
throw new HttpError(
|
|
5855
5921
|
res.status,
|
|
5856
|
-
`${res.status} ${res.statusText} on POST ${
|
|
5922
|
+
`${res.status} ${res.statusText} on POST ${path22}: ${text}`,
|
|
5857
5923
|
text
|
|
5858
5924
|
);
|
|
5859
5925
|
}
|
|
@@ -5867,12 +5933,12 @@ function projectPath(project, suffix) {
|
|
|
5867
5933
|
}
|
|
5868
5934
|
async function runRootCause(client, input) {
|
|
5869
5935
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
5870
|
-
const
|
|
5936
|
+
const path22 = projectPath(
|
|
5871
5937
|
input.project,
|
|
5872
5938
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
5873
5939
|
);
|
|
5874
5940
|
try {
|
|
5875
|
-
const result = await client.get(
|
|
5941
|
+
const result = await client.get(path22);
|
|
5876
5942
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
5877
5943
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
5878
5944
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -5898,12 +5964,12 @@ async function runRootCause(client, input) {
|
|
|
5898
5964
|
}
|
|
5899
5965
|
async function runBlastRadius(client, input) {
|
|
5900
5966
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
5901
|
-
const
|
|
5967
|
+
const path22 = projectPath(
|
|
5902
5968
|
input.project,
|
|
5903
5969
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
5904
5970
|
);
|
|
5905
5971
|
try {
|
|
5906
|
-
const result = await client.get(
|
|
5972
|
+
const result = await client.get(path22);
|
|
5907
5973
|
if (result.totalAffected === 0) {
|
|
5908
5974
|
return {
|
|
5909
5975
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -5937,12 +6003,12 @@ function formatBlastEntry(n) {
|
|
|
5937
6003
|
}
|
|
5938
6004
|
async function runDependencies(client, input) {
|
|
5939
6005
|
const depth = input.depth ?? 3;
|
|
5940
|
-
const
|
|
6006
|
+
const path22 = projectPath(
|
|
5941
6007
|
input.project,
|
|
5942
6008
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
5943
6009
|
);
|
|
5944
6010
|
try {
|
|
5945
|
-
const result = await client.get(
|
|
6011
|
+
const result = await client.get(path22);
|
|
5946
6012
|
if (result.total === 0) {
|
|
5947
6013
|
return {
|
|
5948
6014
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -6034,9 +6100,9 @@ function formatDuration(ms) {
|
|
|
6034
6100
|
return `${Math.round(h / 24)}d`;
|
|
6035
6101
|
}
|
|
6036
6102
|
async function runIncidents(client, input) {
|
|
6037
|
-
const
|
|
6103
|
+
const path22 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
6038
6104
|
try {
|
|
6039
|
-
const body = await client.get(
|
|
6105
|
+
const body = await client.get(path22);
|
|
6040
6106
|
const events = body.events;
|
|
6041
6107
|
if (events.length === 0) {
|
|
6042
6108
|
return {
|
|
@@ -6517,6 +6583,116 @@ async function runDoctorCommand(argv, deps = {}) {
|
|
|
6517
6583
|
return checks.every((c) => c.ok) ? 0 : 1;
|
|
6518
6584
|
}
|
|
6519
6585
|
|
|
6586
|
+
// src/up-cli.ts
|
|
6587
|
+
import path14 from "path";
|
|
6588
|
+
function parseUpArgs(argv, env) {
|
|
6589
|
+
const args = { json: false, help: false };
|
|
6590
|
+
for (let i = 0; i < argv.length; i++) {
|
|
6591
|
+
const a = argv[i];
|
|
6592
|
+
if (a === "--json") args.json = true;
|
|
6593
|
+
else if (a === "-h" || a === "--help") args.help = true;
|
|
6594
|
+
else if (a === "--project") args.project = argv[++i];
|
|
6595
|
+
else if (a.startsWith("--project=")) args.project = a.slice("--project=".length);
|
|
6596
|
+
else if (a === "--profile") args.profile = argv[++i];
|
|
6597
|
+
else if (a.startsWith("--profile=")) args.profile = a.slice("--profile=".length);
|
|
6598
|
+
else if (args.unknown === void 0) args.unknown = a;
|
|
6599
|
+
}
|
|
6600
|
+
if (!args.project && env.NEAT_PROJECT && env.NEAT_PROJECT.length > 0) args.project = env.NEAT_PROJECT;
|
|
6601
|
+
return args;
|
|
6602
|
+
}
|
|
6603
|
+
var USAGE = [
|
|
6604
|
+
"usage: neat up [--project <name>] [--json]",
|
|
6605
|
+
"",
|
|
6606
|
+
"Start this project's daemon, or recover it if it died. Does nothing when it is",
|
|
6607
|
+
"already running. Extracts and instruments nothing \u2014 run `neat` for that."
|
|
6608
|
+
];
|
|
6609
|
+
async function runUpCommand(argv, deps) {
|
|
6610
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
6611
|
+
const env = deps.env ?? process.env;
|
|
6612
|
+
const out = deps.out ?? ((line) => console.log(line));
|
|
6613
|
+
const err = deps.err ?? ((line) => console.error(line));
|
|
6614
|
+
const ensure = deps.ensureDaemon ?? ensureProjectDaemon;
|
|
6615
|
+
const args = parseUpArgs(argv, env);
|
|
6616
|
+
if (args.help) {
|
|
6617
|
+
for (const line of USAGE) out(line);
|
|
6618
|
+
return 0;
|
|
6619
|
+
}
|
|
6620
|
+
if (args.unknown !== void 0) {
|
|
6621
|
+
err(`neat up: unexpected argument \`${args.unknown}\``);
|
|
6622
|
+
for (const line of USAGE) err(line);
|
|
6623
|
+
return 2;
|
|
6624
|
+
}
|
|
6625
|
+
let entry2;
|
|
6626
|
+
try {
|
|
6627
|
+
entry2 = args.project ? await getProject(args.project) : await findProjectByPath(cwd);
|
|
6628
|
+
} catch {
|
|
6629
|
+
entry2 = void 0;
|
|
6630
|
+
}
|
|
6631
|
+
if (!entry2) {
|
|
6632
|
+
err(
|
|
6633
|
+
args.project ? `neat up: no project named "${args.project}" is registered on this machine \u2014 \`neat list\` shows what is.` : "neat up: This directory isn't a NEAT project yet \u2014 run `npx neat.is` here first, or pass --project <name>."
|
|
6634
|
+
);
|
|
6635
|
+
return 2;
|
|
6636
|
+
}
|
|
6637
|
+
let target;
|
|
6638
|
+
try {
|
|
6639
|
+
target = await deps.resolveTarget({ project: entry2.name, profile: args.profile });
|
|
6640
|
+
} catch (e) {
|
|
6641
|
+
err(`neat up: ${e.message}`);
|
|
6642
|
+
return 2;
|
|
6643
|
+
}
|
|
6644
|
+
if (!target.local) {
|
|
6645
|
+
err(`neat up: queries from here go to ${target.endpoint}, through ${target.via}.`);
|
|
6646
|
+
err("neat up: that daemon doesn't run on this machine, so it isn't yours to start.");
|
|
6647
|
+
return 2;
|
|
6648
|
+
}
|
|
6649
|
+
const outcome = await ensure({ project: entry2.name, projectPath: entry2.path });
|
|
6650
|
+
const log = daemonLogPath(entry2.path);
|
|
6651
|
+
if (args.json) {
|
|
6652
|
+
const ports = "ports" in outcome ? outcome.ports : void 0;
|
|
6653
|
+
out(
|
|
6654
|
+
JSON.stringify({
|
|
6655
|
+
project: entry2.name,
|
|
6656
|
+
path: entry2.path,
|
|
6657
|
+
status: outcome.status,
|
|
6658
|
+
...ports ? { endpoint: `http://localhost:${ports.rest}`, ports } : {},
|
|
6659
|
+
...outcome.status === "spawn-failed" ? { error: outcome.message } : {},
|
|
6660
|
+
log
|
|
6661
|
+
})
|
|
6662
|
+
);
|
|
6663
|
+
}
|
|
6664
|
+
switch (outcome.status) {
|
|
6665
|
+
case "already-running":
|
|
6666
|
+
if (!args.json) {
|
|
6667
|
+
out(`neat up: ${entry2.name} is already running \u2014 http://localhost:${outcome.ports.rest}`);
|
|
6668
|
+
}
|
|
6669
|
+
return 0;
|
|
6670
|
+
case "spawned":
|
|
6671
|
+
if (!args.json) {
|
|
6672
|
+
out(`neat up: started ${entry2.name} \u2014 http://localhost:${outcome.ports.rest}`);
|
|
6673
|
+
out(`neat up: traces on :${outcome.ports.otlp}, log at ${path14.relative(cwd, log) || log}`);
|
|
6674
|
+
}
|
|
6675
|
+
return 0;
|
|
6676
|
+
case "timed-out":
|
|
6677
|
+
if (!args.json) {
|
|
6678
|
+
for (const line of await describeDaemonTimeout(outcome, entry2.path, DEFAULT_DAEMON_READY_TIMEOUT_MS)) err(line);
|
|
6679
|
+
}
|
|
6680
|
+
return 1;
|
|
6681
|
+
case "peer-timeout":
|
|
6682
|
+
if (!args.json) {
|
|
6683
|
+
err("neat up: another `neat` is starting this project, and its daemon did not come up in time.");
|
|
6684
|
+
err(`neat up: see ${log}`);
|
|
6685
|
+
}
|
|
6686
|
+
return 1;
|
|
6687
|
+
case "spawn-failed":
|
|
6688
|
+
if (!args.json) err(`neat up: could not start the daemon \u2014 ${outcome.message}`);
|
|
6689
|
+
return 1;
|
|
6690
|
+
case "no-ports":
|
|
6691
|
+
if (!args.json) for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) err(line);
|
|
6692
|
+
return 3;
|
|
6693
|
+
}
|
|
6694
|
+
}
|
|
6695
|
+
|
|
6520
6696
|
// src/login-cli.ts
|
|
6521
6697
|
import readline3 from "readline/promises";
|
|
6522
6698
|
var DEFAULT_PROFILE_NAME = "hosted";
|
|
@@ -6778,7 +6954,7 @@ async function defaultReadSecret(prompt) {
|
|
|
6778
6954
|
|
|
6779
6955
|
// src/welcome.ts
|
|
6780
6956
|
import { promises as fs13 } from "fs";
|
|
6781
|
-
import
|
|
6957
|
+
import path15 from "path";
|
|
6782
6958
|
import readline4 from "readline/promises";
|
|
6783
6959
|
var WORDMARK = [
|
|
6784
6960
|
"\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",
|
|
@@ -6966,10 +7142,11 @@ async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, in
|
|
|
6966
7142
|
out("Building your local graph now\u2026");
|
|
6967
7143
|
out("");
|
|
6968
7144
|
const overrides = {
|
|
7145
|
+
headerShown: true,
|
|
6969
7146
|
...project !== void 0 ? { project } : {},
|
|
6970
7147
|
...declined === void 0 ? {} : declined ? { noInstrument: true } : { yes: true }
|
|
6971
7148
|
};
|
|
6972
|
-
return orchestrator(cwd,
|
|
7149
|
+
return orchestrator(cwd, overrides);
|
|
6973
7150
|
}
|
|
6974
7151
|
async function askToInstrument(out, readLine) {
|
|
6975
7152
|
out("Instrument the services for OpenTelemetry now?");
|
|
@@ -6985,8 +7162,8 @@ async function askToInstrument(out, readLine) {
|
|
|
6985
7162
|
return declined;
|
|
6986
7163
|
}
|
|
6987
7164
|
async function resolveProjectName(out, readLine, cwd, readRegistry2) {
|
|
6988
|
-
const here =
|
|
6989
|
-
const base =
|
|
7165
|
+
const here = path15.resolve(cwd);
|
|
7166
|
+
const base = path15.basename(here);
|
|
6990
7167
|
let taken;
|
|
6991
7168
|
try {
|
|
6992
7169
|
const { projects } = await readRegistry2();
|
|
@@ -6995,7 +7172,7 @@ async function resolveProjectName(out, readLine, cwd, readRegistry2) {
|
|
|
6995
7172
|
return void 0;
|
|
6996
7173
|
}
|
|
6997
7174
|
const clash = taken.get(base);
|
|
6998
|
-
if (clash === void 0 ||
|
|
7175
|
+
if (clash === void 0 || path15.resolve(clash) === here) return void 0;
|
|
6999
7176
|
out("");
|
|
7000
7177
|
out(`A project named \`${base}\` is already registered (${clash}).`);
|
|
7001
7178
|
const suggested = firstFreeName(base, taken);
|
|
@@ -7008,7 +7185,7 @@ async function resolveProjectName(out, readLine, cwd, readRegistry2) {
|
|
|
7008
7185
|
continue;
|
|
7009
7186
|
}
|
|
7010
7187
|
const other = taken.get(chosen);
|
|
7011
|
-
if (other !== void 0 &&
|
|
7188
|
+
if (other !== void 0 && path15.resolve(other) !== here) {
|
|
7012
7189
|
out(`\`${chosen}\` is registered too (${other}).`);
|
|
7013
7190
|
continue;
|
|
7014
7191
|
}
|
|
@@ -7033,9 +7210,9 @@ async function shouldShowWelcome(deps = {}) {
|
|
|
7033
7210
|
const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
|
|
7034
7211
|
const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
|
|
7035
7212
|
try {
|
|
7036
|
-
const here =
|
|
7213
|
+
const here = path15.resolve(cwd);
|
|
7037
7214
|
const { projects } = await readRegistry2();
|
|
7038
|
-
if (projects.some((p) =>
|
|
7215
|
+
if (projects.some((p) => path15.resolve(p.path) === here)) return false;
|
|
7039
7216
|
if (await hasSnapshot(here)) return false;
|
|
7040
7217
|
return true;
|
|
7041
7218
|
} catch {
|
|
@@ -7044,7 +7221,7 @@ async function shouldShowWelcome(deps = {}) {
|
|
|
7044
7221
|
}
|
|
7045
7222
|
async function defaultHasSnapshot(cwd) {
|
|
7046
7223
|
try {
|
|
7047
|
-
const entries = await fs13.readdir(
|
|
7224
|
+
const entries = await fs13.readdir(path15.join(cwd, "neat-out"));
|
|
7048
7225
|
return entries.some((e) => e.endsWith(".json"));
|
|
7049
7226
|
} catch {
|
|
7050
7227
|
return false;
|
|
@@ -7061,7 +7238,7 @@ async function defaultReadLine2(prompt) {
|
|
|
7061
7238
|
}
|
|
7062
7239
|
|
|
7063
7240
|
// src/hooks-cli.ts
|
|
7064
|
-
import
|
|
7241
|
+
import path16 from "path";
|
|
7065
7242
|
import os2 from "os";
|
|
7066
7243
|
import { promises as fs14 } from "fs";
|
|
7067
7244
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -7070,14 +7247,14 @@ var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
|
7070
7247
|
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
7071
7248
|
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
7072
7249
|
function moduleDir() {
|
|
7073
|
-
return typeof __dirname !== "undefined" ? __dirname :
|
|
7250
|
+
return typeof __dirname !== "undefined" ? __dirname : path16.dirname(fileURLToPath3(import.meta.url));
|
|
7074
7251
|
}
|
|
7075
7252
|
async function readSkillAsset(rel) {
|
|
7076
7253
|
const here = moduleDir();
|
|
7077
7254
|
const candidates = [
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7255
|
+
path16.resolve(here, "../../claude-skill", rel),
|
|
7256
|
+
path16.resolve(here, "../../../claude-skill", rel),
|
|
7257
|
+
path16.resolve(here, "../claude-skill", rel)
|
|
7081
7258
|
];
|
|
7082
7259
|
for (const candidate of candidates) {
|
|
7083
7260
|
try {
|
|
@@ -7091,20 +7268,20 @@ async function readSkillAsset(rel) {
|
|
|
7091
7268
|
}
|
|
7092
7269
|
function neatHome2() {
|
|
7093
7270
|
const override = process.env.NEAT_HOME;
|
|
7094
|
-
if (override && override.length > 0) return
|
|
7095
|
-
return
|
|
7271
|
+
if (override && override.length > 0) return path16.resolve(override);
|
|
7272
|
+
return path16.join(os2.homedir(), ".neat");
|
|
7096
7273
|
}
|
|
7097
7274
|
function claudeSettingsPath() {
|
|
7098
7275
|
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
7099
|
-
if (override && override.length > 0) return
|
|
7276
|
+
if (override && override.length > 0) return path16.resolve(override);
|
|
7100
7277
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
|
|
7101
|
-
return
|
|
7278
|
+
return path16.join(home, ".claude", "settings.json");
|
|
7102
7279
|
}
|
|
7103
7280
|
function installedHookPath() {
|
|
7104
|
-
return
|
|
7281
|
+
return path16.join(neatHome2(), "hooks", HOOK_FILENAME);
|
|
7105
7282
|
}
|
|
7106
7283
|
function gateFlagPath() {
|
|
7107
|
-
return
|
|
7284
|
+
return path16.join(neatHome2(), "hooks", "gate-enabled");
|
|
7108
7285
|
}
|
|
7109
7286
|
function isNeatSearchEntry(entry2) {
|
|
7110
7287
|
return (entry2.hooks ?? []).some(
|
|
@@ -7137,9 +7314,9 @@ async function runHooks(opts) {
|
|
|
7137
7314
|
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
7138
7315
|
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
7139
7316
|
const scriptPath = installedHookPath();
|
|
7140
|
-
await fs14.mkdir(
|
|
7317
|
+
await fs14.mkdir(path16.dirname(scriptPath), { recursive: true });
|
|
7141
7318
|
await fs14.writeFile(scriptPath, hookScript, { mode: 493 });
|
|
7142
|
-
const guidePath =
|
|
7319
|
+
const guidePath = path16.join(neatHome2(), GUIDE_INSTALL_NAME);
|
|
7143
7320
|
await fs14.writeFile(guidePath, guide, "utf8");
|
|
7144
7321
|
const settingsFile = claudeSettingsPath();
|
|
7145
7322
|
let settings = {};
|
|
@@ -7166,11 +7343,11 @@ async function runHooks(opts) {
|
|
|
7166
7343
|
...settings,
|
|
7167
7344
|
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
7168
7345
|
};
|
|
7169
|
-
await fs14.mkdir(
|
|
7346
|
+
await fs14.mkdir(path16.dirname(settingsFile), { recursive: true });
|
|
7170
7347
|
await fs14.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
7171
7348
|
const flag = gateFlagPath();
|
|
7172
7349
|
if (opts.gate) {
|
|
7173
|
-
await fs14.mkdir(
|
|
7350
|
+
await fs14.mkdir(path16.dirname(flag), { recursive: true });
|
|
7174
7351
|
await fs14.writeFile(flag, "1\n", "utf8");
|
|
7175
7352
|
} else {
|
|
7176
7353
|
await fs14.rm(flag, { force: true });
|
|
@@ -7262,7 +7439,7 @@ async function runHooksCommand(args) {
|
|
|
7262
7439
|
}
|
|
7263
7440
|
|
|
7264
7441
|
// src/claude-cli.ts
|
|
7265
|
-
import
|
|
7442
|
+
import path17 from "path";
|
|
7266
7443
|
import { promises as fs15 } from "fs";
|
|
7267
7444
|
var NEAT_SECTION_HEADING = "## neat";
|
|
7268
7445
|
var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
|
|
@@ -7297,8 +7474,8 @@ ${NEAT_DIRECTIVE_BODY}
|
|
|
7297
7474
|
}
|
|
7298
7475
|
function claudeMdPath() {
|
|
7299
7476
|
const override = process.env.NEAT_CLAUDE_MD;
|
|
7300
|
-
if (override && override.length > 0) return
|
|
7301
|
-
return
|
|
7477
|
+
if (override && override.length > 0) return path17.resolve(override);
|
|
7478
|
+
return path17.join(process.cwd(), "CLAUDE.md");
|
|
7302
7479
|
}
|
|
7303
7480
|
function splitAroundSection(raw) {
|
|
7304
7481
|
const lines = raw.split("\n");
|
|
@@ -7337,7 +7514,7 @@ async function runInstall() {
|
|
|
7337
7514
|
const raw = await readIfExists(file) ?? "";
|
|
7338
7515
|
const { before, after, found } = splitAroundSection(raw);
|
|
7339
7516
|
const next = compose(before, after);
|
|
7340
|
-
await fs15.mkdir(
|
|
7517
|
+
await fs15.mkdir(path17.dirname(file), { recursive: true });
|
|
7341
7518
|
await fs15.writeFile(file, next, "utf8");
|
|
7342
7519
|
const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
|
|
7343
7520
|
console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
|
|
@@ -7401,7 +7578,7 @@ async function runClaudeCommand(args) {
|
|
|
7401
7578
|
}
|
|
7402
7579
|
|
|
7403
7580
|
// src/codex-cli.ts
|
|
7404
|
-
import
|
|
7581
|
+
import path18 from "path";
|
|
7405
7582
|
import os3 from "os";
|
|
7406
7583
|
import { promises as fs16 } from "fs";
|
|
7407
7584
|
import { isDeepStrictEqual } from "util";
|
|
@@ -7421,14 +7598,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
7421
7598
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
7422
7599
|
function codexConfigPath() {
|
|
7423
7600
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
7424
|
-
if (override && override.length > 0) return
|
|
7601
|
+
if (override && override.length > 0) return path18.resolve(override);
|
|
7425
7602
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? os3.homedir();
|
|
7426
|
-
return
|
|
7603
|
+
return path18.join(home, ".codex", "config.toml");
|
|
7427
7604
|
}
|
|
7428
7605
|
function agentsFilePath() {
|
|
7429
7606
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
7430
|
-
if (override && override.length > 0) return
|
|
7431
|
-
return
|
|
7607
|
+
if (override && override.length > 0) return path18.resolve(override);
|
|
7608
|
+
return path18.join(process.cwd(), "AGENTS.md");
|
|
7432
7609
|
}
|
|
7433
7610
|
function isTableHeader(line) {
|
|
7434
7611
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -7611,14 +7788,14 @@ async function runCodex(opts) {
|
|
|
7611
7788
|
return { exitCode: 0 };
|
|
7612
7789
|
}
|
|
7613
7790
|
if (config.changed) {
|
|
7614
|
-
await fs16.mkdir(
|
|
7791
|
+
await fs16.mkdir(path18.dirname(configPath), { recursive: true });
|
|
7615
7792
|
await fs16.writeFile(configPath, config.text, "utf8");
|
|
7616
7793
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
7617
7794
|
} else {
|
|
7618
7795
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
7619
7796
|
}
|
|
7620
7797
|
if (agents.changed) {
|
|
7621
|
-
await fs16.mkdir(
|
|
7798
|
+
await fs16.mkdir(path18.dirname(agentsPath), { recursive: true });
|
|
7622
7799
|
await fs16.writeFile(agentsPath, agents.text, "utf8");
|
|
7623
7800
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
7624
7801
|
} else {
|
|
@@ -7675,7 +7852,7 @@ async function runCodexCommand(args) {
|
|
|
7675
7852
|
}
|
|
7676
7853
|
|
|
7677
7854
|
// src/editors-cli.ts
|
|
7678
|
-
import
|
|
7855
|
+
import path19 from "path";
|
|
7679
7856
|
import os4 from "os";
|
|
7680
7857
|
import { promises as fs17 } from "fs";
|
|
7681
7858
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
@@ -7701,17 +7878,17 @@ function homeDir() {
|
|
|
7701
7878
|
}
|
|
7702
7879
|
function xdgConfigDir() {
|
|
7703
7880
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
7704
|
-
return xdg && xdg.length > 0 ?
|
|
7881
|
+
return xdg && xdg.length > 0 ? path19.resolve(xdg) : path19.join(homeDir(), ".config");
|
|
7705
7882
|
}
|
|
7706
7883
|
function envOverride(name) {
|
|
7707
7884
|
const v = process.env[name];
|
|
7708
|
-
return v && v.length > 0 ?
|
|
7885
|
+
return v && v.length > 0 ? path19.resolve(v) : void 0;
|
|
7709
7886
|
}
|
|
7710
7887
|
var CURSOR_CLIENT = {
|
|
7711
7888
|
id: "cursor",
|
|
7712
7889
|
label: "Cursor",
|
|
7713
7890
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
7714
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
7891
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path19.join(homeDir(), ".cursor", "mcp.json"),
|
|
7715
7892
|
mcpContainerKey: "mcpServers",
|
|
7716
7893
|
format: "json",
|
|
7717
7894
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -7723,7 +7900,7 @@ var DEVIN_CLIENT = {
|
|
|
7723
7900
|
id: "devin",
|
|
7724
7901
|
label: "Devin Desktop (Cascade)",
|
|
7725
7902
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
7726
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
7903
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path19.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
7727
7904
|
mcpContainerKey: "mcpServers",
|
|
7728
7905
|
format: "json",
|
|
7729
7906
|
rulesFileName: ".windsurfrules"
|
|
@@ -7732,7 +7909,7 @@ var GEMINI_CLIENT = {
|
|
|
7732
7909
|
id: "gemini",
|
|
7733
7910
|
label: "Gemini CLI",
|
|
7734
7911
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
7735
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
7912
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path19.join(homeDir(), ".gemini", "settings.json"),
|
|
7736
7913
|
mcpContainerKey: "mcpServers",
|
|
7737
7914
|
format: "json",
|
|
7738
7915
|
rulesFileName: "GEMINI.md"
|
|
@@ -7741,7 +7918,7 @@ var QWEN_CLIENT = {
|
|
|
7741
7918
|
id: "qwen",
|
|
7742
7919
|
label: "Qwen Code",
|
|
7743
7920
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
7744
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
7921
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path19.join(homeDir(), ".qwen", "settings.json"),
|
|
7745
7922
|
mcpContainerKey: "mcpServers",
|
|
7746
7923
|
format: "json",
|
|
7747
7924
|
rulesFileName: "QWEN.md"
|
|
@@ -7750,7 +7927,7 @@ var AMAZONQ_CLIENT = {
|
|
|
7750
7927
|
id: "amazonq",
|
|
7751
7928
|
label: "Amazon Q Developer CLI",
|
|
7752
7929
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
7753
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
7930
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path19.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
7754
7931
|
mcpContainerKey: "mcpServers",
|
|
7755
7932
|
format: "json"
|
|
7756
7933
|
};
|
|
@@ -7758,7 +7935,7 @@ var ROOCODE_CLIENT = {
|
|
|
7758
7935
|
id: "roocode",
|
|
7759
7936
|
label: "Roo Code",
|
|
7760
7937
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
7761
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
7938
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path19.join(process.cwd(), ".roo", "mcp.json"),
|
|
7762
7939
|
mcpContainerKey: "mcpServers",
|
|
7763
7940
|
format: "json"
|
|
7764
7941
|
};
|
|
@@ -7771,9 +7948,9 @@ var ZED_CLIENT = {
|
|
|
7771
7948
|
if (override) return override;
|
|
7772
7949
|
if (process.platform === "win32") {
|
|
7773
7950
|
const appData = process.env.APPDATA;
|
|
7774
|
-
if (appData && appData.length > 0) return
|
|
7951
|
+
if (appData && appData.length > 0) return path19.join(appData, "Zed", "settings.json");
|
|
7775
7952
|
}
|
|
7776
|
-
return
|
|
7953
|
+
return path19.join(homeDir(), ".config", "zed", "settings.json");
|
|
7777
7954
|
},
|
|
7778
7955
|
mcpContainerKey: "context_servers",
|
|
7779
7956
|
format: "jsonc",
|
|
@@ -7783,7 +7960,7 @@ var OPENCODE_CLIENT = {
|
|
|
7783
7960
|
id: "opencode",
|
|
7784
7961
|
label: "OpenCode",
|
|
7785
7962
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
7786
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
7963
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path19.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
7787
7964
|
mcpContainerKey: "mcp",
|
|
7788
7965
|
format: "json",
|
|
7789
7966
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -7793,7 +7970,7 @@ var CRUSH_CLIENT = {
|
|
|
7793
7970
|
id: "crush",
|
|
7794
7971
|
label: "Crush",
|
|
7795
7972
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
7796
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
7973
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path19.join(xdgConfigDir(), "crush", "crush.json"),
|
|
7797
7974
|
mcpContainerKey: "mcp",
|
|
7798
7975
|
format: "json",
|
|
7799
7976
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -7898,7 +8075,7 @@ async function runEditorInstall(client, opts) {
|
|
|
7898
8075
|
const mcpPath = client.mcpConfigPath();
|
|
7899
8076
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
7900
8077
|
const hasRules = typeof client.rulesFileName === "string";
|
|
7901
|
-
const rulesPath = hasRules ?
|
|
8078
|
+
const rulesPath = hasRules ? path19.join(opts.projectDir, client.rulesFileName) : "";
|
|
7902
8079
|
const mcp = await planMcp(client, mcpPath);
|
|
7903
8080
|
if (mcp === null) return { exitCode: 1 };
|
|
7904
8081
|
let existingRules = "";
|
|
@@ -7941,10 +8118,10 @@ async function runEditorInstall(client, opts) {
|
|
|
7941
8118
|
);
|
|
7942
8119
|
return { exitCode: 0 };
|
|
7943
8120
|
}
|
|
7944
|
-
await fs17.mkdir(
|
|
8121
|
+
await fs17.mkdir(path19.dirname(mcpPath), { recursive: true });
|
|
7945
8122
|
await fs17.writeFile(mcpPath, mcp.text, "utf8");
|
|
7946
8123
|
if (hasRules) {
|
|
7947
|
-
await fs17.mkdir(
|
|
8124
|
+
await fs17.mkdir(path19.dirname(rulesPath), { recursive: true });
|
|
7948
8125
|
await fs17.writeFile(rulesPath, newRules, "utf8");
|
|
7949
8126
|
}
|
|
7950
8127
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
@@ -8445,7 +8622,7 @@ function sleep(ms, signal) {
|
|
|
8445
8622
|
}
|
|
8446
8623
|
|
|
8447
8624
|
// src/cli-verbs.ts
|
|
8448
|
-
import
|
|
8625
|
+
import path20 from "path";
|
|
8449
8626
|
async function resolveProjectEntry(opts) {
|
|
8450
8627
|
const entries = await listProjects();
|
|
8451
8628
|
if (opts.project) {
|
|
@@ -8455,7 +8632,7 @@ async function resolveProjectEntry(opts) {
|
|
|
8455
8632
|
const cwd = opts.cwd ?? process.cwd();
|
|
8456
8633
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
8457
8634
|
for (const entry2 of entries) {
|
|
8458
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
8635
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path20.sep}`)) {
|
|
8459
8636
|
return entry2;
|
|
8460
8637
|
}
|
|
8461
8638
|
}
|
|
@@ -8774,6 +8951,8 @@ function usage5() {
|
|
|
8774
8951
|
console.log(" test <id> re-check an existing connector's credential");
|
|
8775
8952
|
console.log(" Credentials default to an env-var reference ($VAR) resolved at");
|
|
8776
8953
|
console.log(" run time; the config file is written owner-only (0600).");
|
|
8954
|
+
console.log(" up Start this project's daemon, or recover it if it died. Does");
|
|
8955
|
+
console.log(" nothing when it is already running. Flags: --project, --json.");
|
|
8777
8956
|
console.log(" doctor Preflight this directory's setup \u2014 Node version, project,");
|
|
8778
8957
|
console.log(" and daemon reachability \u2014 and print a fix for anything down.");
|
|
8779
8958
|
console.log(" Flags: --json. Exits 0 when all pass, 1 when a check fails.");
|
|
@@ -9041,12 +9220,12 @@ async function runInit(opts) {
|
|
|
9041
9220
|
printDiscoveryReport(opts, services);
|
|
9042
9221
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
9043
9222
|
const patch = renderPatch(sections);
|
|
9044
|
-
const patchPath =
|
|
9223
|
+
const patchPath = path21.join(opts.scanPath, "neat.patch");
|
|
9045
9224
|
if (opts.dryRun) {
|
|
9046
9225
|
await fs18.writeFile(patchPath, patch, "utf8");
|
|
9047
9226
|
written.push(patchPath);
|
|
9048
9227
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
9049
|
-
const gitignorePath =
|
|
9228
|
+
const gitignorePath = path21.join(opts.scanPath, ".gitignore");
|
|
9050
9229
|
const gitignoreExists = await fs18.stat(gitignorePath).then(() => true).catch(() => false);
|
|
9051
9230
|
const verb = gitignoreExists ? "append" : "create";
|
|
9052
9231
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
@@ -9058,9 +9237,9 @@ async function runInit(opts) {
|
|
|
9058
9237
|
const graph = getGraph(graphKey);
|
|
9059
9238
|
const projectPaths = pathsForProject(
|
|
9060
9239
|
graphKey,
|
|
9061
|
-
|
|
9240
|
+
path21.join(opts.scanPath, "neat-out")
|
|
9062
9241
|
);
|
|
9063
|
-
const errorsPath =
|
|
9242
|
+
const errorsPath = path21.join(path21.dirname(opts.outPath), path21.basename(projectPaths.errorsPath));
|
|
9064
9243
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
9065
9244
|
await saveGraphToDisk(graph, opts.outPath);
|
|
9066
9245
|
written.push(opts.outPath);
|
|
@@ -9069,15 +9248,13 @@ async function runInit(opts) {
|
|
|
9069
9248
|
written.push(gitignoreResult.file);
|
|
9070
9249
|
}
|
|
9071
9250
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
9072
|
-
let currentProjectName = opts.project;
|
|
9073
9251
|
try {
|
|
9074
|
-
|
|
9252
|
+
await addProject({
|
|
9075
9253
|
name: opts.project,
|
|
9076
9254
|
path: opts.scanPath,
|
|
9077
9255
|
languages,
|
|
9078
9256
|
status: "active"
|
|
9079
9257
|
});
|
|
9080
|
-
currentProjectName = entry2.name;
|
|
9081
9258
|
} catch (err) {
|
|
9082
9259
|
if (err instanceof ProjectNameCollisionError) {
|
|
9083
9260
|
console.error(`neat init: ${err.message}`);
|
|
@@ -9086,20 +9263,6 @@ async function runInit(opts) {
|
|
|
9086
9263
|
}
|
|
9087
9264
|
throw err;
|
|
9088
9265
|
}
|
|
9089
|
-
const siblings = await listProjects();
|
|
9090
|
-
const paused = [];
|
|
9091
|
-
for (const p of siblings) {
|
|
9092
|
-
if (p.name !== currentProjectName && p.status === "active") {
|
|
9093
|
-
await setStatus(p.name, "paused");
|
|
9094
|
-
paused.push(p.name);
|
|
9095
|
-
}
|
|
9096
|
-
}
|
|
9097
|
-
if (paused.length > 0) {
|
|
9098
|
-
const plural = paused.length === 1 ? "" : "s";
|
|
9099
|
-
console.log(
|
|
9100
|
-
`neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
|
|
9101
|
-
);
|
|
9102
|
-
}
|
|
9103
9266
|
if (!opts.noInstall) {
|
|
9104
9267
|
if (opts.apply) {
|
|
9105
9268
|
let instrumented = 0;
|
|
@@ -9160,6 +9323,8 @@ async function runInit(opts) {
|
|
|
9160
9323
|
if (result.extractionErrors > 0) {
|
|
9161
9324
|
console.log(`errors: ${errorsPath}`);
|
|
9162
9325
|
}
|
|
9326
|
+
const skippedBanner = formatSkippedBanner(result.skippedFiles);
|
|
9327
|
+
if (skippedBanner) console.log(skippedBanner);
|
|
9163
9328
|
console.log(formatPrecisionFloorBanner(result.extractedDropped));
|
|
9164
9329
|
if (result.extractionErrors > 0 && isStrictExtractionEnabled()) {
|
|
9165
9330
|
return { exitCode: 4, writtenFiles: written };
|
|
@@ -9180,9 +9345,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
9180
9345
|
};
|
|
9181
9346
|
function claudeConfigPath() {
|
|
9182
9347
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
9183
|
-
if (override && override.length > 0) return
|
|
9348
|
+
if (override && override.length > 0) return path21.resolve(override);
|
|
9184
9349
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
9185
|
-
return
|
|
9350
|
+
return path21.join(home, ".claude.json");
|
|
9186
9351
|
}
|
|
9187
9352
|
async function runSkill(opts) {
|
|
9188
9353
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -9206,7 +9371,7 @@ async function runSkill(opts) {
|
|
|
9206
9371
|
...existing,
|
|
9207
9372
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
9208
9373
|
};
|
|
9209
|
-
await fs18.mkdir(
|
|
9374
|
+
await fs18.mkdir(path21.dirname(target), { recursive: true });
|
|
9210
9375
|
await fs18.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9211
9376
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
9212
9377
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
@@ -9256,6 +9421,21 @@ async function main() {
|
|
|
9256
9421
|
if (code !== 0) process.exit(code);
|
|
9257
9422
|
return;
|
|
9258
9423
|
}
|
|
9424
|
+
if (cmd0 === "up") {
|
|
9425
|
+
const code = await runUpCommand(argv.slice(1), {
|
|
9426
|
+
resolveTarget: async (opts) => {
|
|
9427
|
+
const target = await resolveClientTarget(opts);
|
|
9428
|
+
const named = opts.profile ?? process.env.NEAT_PROFILE;
|
|
9429
|
+
return {
|
|
9430
|
+
endpoint: target.endpoint,
|
|
9431
|
+
local: isLoopbackEndpoint(target.endpoint),
|
|
9432
|
+
via: target.source === "profile" ? `the \`${named}\` profile` : target.source === "active" ? "your active profile" : "your environment"
|
|
9433
|
+
};
|
|
9434
|
+
}
|
|
9435
|
+
});
|
|
9436
|
+
if (code !== 0) process.exit(code);
|
|
9437
|
+
return;
|
|
9438
|
+
}
|
|
9259
9439
|
if (cmd0 === "login") {
|
|
9260
9440
|
const code = await runLoginCommand(argv.slice(1));
|
|
9261
9441
|
if (code !== 0) process.exit(code);
|
|
@@ -9328,12 +9508,12 @@ async function main() {
|
|
|
9328
9508
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
9329
9509
|
process.exit(2);
|
|
9330
9510
|
}
|
|
9331
|
-
const scanPath =
|
|
9511
|
+
const scanPath = path21.resolve(target);
|
|
9332
9512
|
const projectExplicit = parsed.project !== null;
|
|
9333
|
-
const projectName = projectExplicit ? project :
|
|
9513
|
+
const projectName = projectExplicit ? project : path21.basename(scanPath);
|
|
9334
9514
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
9335
|
-
const fallback = pathsForProject(projectKey,
|
|
9336
|
-
const outPath =
|
|
9515
|
+
const fallback = pathsForProject(projectKey, path21.join(scanPath, "neat-out")).snapshotPath;
|
|
9516
|
+
const outPath = path21.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
9337
9517
|
const result = await runInit({
|
|
9338
9518
|
scanPath,
|
|
9339
9519
|
outPath,
|
|
@@ -9354,21 +9534,21 @@ async function main() {
|
|
|
9354
9534
|
usage5();
|
|
9355
9535
|
process.exit(2);
|
|
9356
9536
|
}
|
|
9357
|
-
const scanPath =
|
|
9537
|
+
const scanPath = path21.resolve(target);
|
|
9358
9538
|
const stat = await fs18.stat(scanPath).catch(() => null);
|
|
9359
9539
|
if (!stat || !stat.isDirectory()) {
|
|
9360
9540
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
9361
9541
|
process.exit(2);
|
|
9362
9542
|
}
|
|
9363
|
-
const projectPaths = pathsForProject(project,
|
|
9364
|
-
const outPath =
|
|
9365
|
-
const errorsPath =
|
|
9366
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
9543
|
+
const projectPaths = pathsForProject(project, path21.join(scanPath, "neat-out"));
|
|
9544
|
+
const outPath = path21.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
9545
|
+
const errorsPath = path21.resolve(
|
|
9546
|
+
process.env.NEAT_ERRORS_PATH ?? path21.join(path21.dirname(outPath), path21.basename(projectPaths.errorsPath))
|
|
9367
9547
|
);
|
|
9368
|
-
const staleEventsPath =
|
|
9369
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
9548
|
+
const staleEventsPath = path21.resolve(
|
|
9549
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path21.join(path21.dirname(outPath), path21.basename(projectPaths.staleEventsPath))
|
|
9370
9550
|
);
|
|
9371
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
9551
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path21.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
9372
9552
|
const handle = await startWatch(getGraph(project), {
|
|
9373
9553
|
scanPath,
|
|
9374
9554
|
outPath,
|
|
@@ -9377,7 +9557,7 @@ async function main() {
|
|
|
9377
9557
|
project,
|
|
9378
9558
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
9379
9559
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
9380
|
-
neatHome: process.env.NEAT_HOME ?
|
|
9560
|
+
neatHome: process.env.NEAT_HOME ? path21.resolve(process.env.NEAT_HOME) : path21.join(os5.homedir(), ".neat"),
|
|
9381
9561
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
9382
9562
|
host: process.env.HOST ?? "0.0.0.0",
|
|
9383
9563
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -9558,12 +9738,12 @@ async function main() {
|
|
|
9558
9738
|
usage5();
|
|
9559
9739
|
process.exit(1);
|
|
9560
9740
|
}
|
|
9561
|
-
async function tryOrchestrator(cmd, parsed) {
|
|
9562
|
-
const scanPath =
|
|
9741
|
+
async function tryOrchestrator(cmd, parsed, overrides = {}) {
|
|
9742
|
+
const scanPath = path21.resolve(cmd);
|
|
9563
9743
|
const stat = await fs18.stat(scanPath).catch(() => null);
|
|
9564
9744
|
if (!stat || !stat.isDirectory()) return null;
|
|
9565
9745
|
const projectExplicit = parsed.project !== null;
|
|
9566
|
-
const projectName = projectExplicit ? parsed.project :
|
|
9746
|
+
const projectName = projectExplicit ? parsed.project : path21.basename(scanPath);
|
|
9567
9747
|
const result = await runOrchestrator({
|
|
9568
9748
|
scanPath,
|
|
9569
9749
|
project: projectName,
|
|
@@ -9571,7 +9751,8 @@ async function tryOrchestrator(cmd, parsed) {
|
|
|
9571
9751
|
noInstrument: parsed.noInstrument,
|
|
9572
9752
|
open: parsed.open,
|
|
9573
9753
|
noOpen: parsed.noOpen,
|
|
9574
|
-
yes: parsed.yes
|
|
9754
|
+
yes: parsed.yes,
|
|
9755
|
+
...overrides.skipBanner ? { skipBanner: true } : {}
|
|
9575
9756
|
});
|
|
9576
9757
|
return result.exitCode;
|
|
9577
9758
|
}
|
|
@@ -9581,13 +9762,18 @@ async function runWelcomeFlow(parsed) {
|
|
|
9581
9762
|
// What the person chose at the door reaches the orchestrator as the flags they
|
|
9582
9763
|
// stand for: the name as `--project`, the instrument answer as `--no-instrument`
|
|
9583
9764
|
// or as `yes` (which keeps the orchestrator's own prompt quiet, since the door
|
|
9584
|
-
// already asked).
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9765
|
+
// already asked). `headerShown` isn't a flag anyone can type — it says the
|
|
9766
|
+
// door's wordmark already opened this run, so the orchestrator's banner is skipped.
|
|
9767
|
+
orchestrator: async (cwd, opts) => await tryOrchestrator(
|
|
9768
|
+
cwd,
|
|
9769
|
+
{
|
|
9770
|
+
...parsed,
|
|
9771
|
+
...opts?.project !== void 0 ? { project: opts.project } : {},
|
|
9772
|
+
...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
|
|
9773
|
+
...opts?.yes !== void 0 ? { yes: opts.yes } : {}
|
|
9774
|
+
},
|
|
9775
|
+
{ skipBanner: opts?.headerShown === true }
|
|
9776
|
+
) ?? 0,
|
|
9591
9777
|
// An explicit flag is an answer; don't ask again.
|
|
9592
9778
|
instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
|
|
9593
9779
|
});
|
|
@@ -9629,9 +9815,10 @@ function isLoopbackEndpoint(endpoint) {
|
|
|
9629
9815
|
return false;
|
|
9630
9816
|
}
|
|
9631
9817
|
}
|
|
9632
|
-
async function resolveProjectForVerb(client, parsed) {
|
|
9818
|
+
async function resolveProjectForVerb(client, parsed, cwdProject) {
|
|
9633
9819
|
const explicit = resolveProjectFlag(parsed);
|
|
9634
9820
|
if (explicit) return explicit;
|
|
9821
|
+
if (cwdProject) return cwdProject;
|
|
9635
9822
|
const projects = await client.get("/projects");
|
|
9636
9823
|
if (projects.some((p) => p.name === DEFAULT_PROJECT)) {
|
|
9637
9824
|
return void 0;
|
|
@@ -9678,14 +9865,28 @@ async function resolveClientTarget(opts = {}) {
|
|
|
9678
9865
|
}
|
|
9679
9866
|
return { endpoint: "http://localhost:8080", source: "default" };
|
|
9680
9867
|
}
|
|
9868
|
+
async function projectForCwd(cwd = process.cwd()) {
|
|
9869
|
+
try {
|
|
9870
|
+
return (await findProjectByPath(cwd))?.name;
|
|
9871
|
+
} catch {
|
|
9872
|
+
return void 0;
|
|
9873
|
+
}
|
|
9874
|
+
}
|
|
9875
|
+
function cwdProjectApplies(source) {
|
|
9876
|
+
return source === "daemon-record" || source === "default";
|
|
9877
|
+
}
|
|
9681
9878
|
async function resolveDaemonUrl(project, profile) {
|
|
9682
9879
|
return (await resolveClientTarget({ project, profile })).endpoint;
|
|
9683
9880
|
}
|
|
9684
9881
|
async function runQueryVerb(cmd, parsed) {
|
|
9685
9882
|
const requestedProject = resolveProjectFlag(parsed);
|
|
9883
|
+
const cwdProject = requestedProject ? void 0 : await projectForCwd();
|
|
9686
9884
|
let target;
|
|
9687
9885
|
try {
|
|
9688
|
-
target = await resolveClientTarget({
|
|
9886
|
+
target = await resolveClientTarget({
|
|
9887
|
+
project: requestedProject ?? cwdProject,
|
|
9888
|
+
profile: parsed.profile ?? void 0
|
|
9889
|
+
});
|
|
9689
9890
|
} catch (err) {
|
|
9690
9891
|
if (err instanceof UnknownProfileError) {
|
|
9691
9892
|
process.stderr.write(`${err.message}
|
|
@@ -9844,7 +10045,11 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
9844
10045
|
return 2;
|
|
9845
10046
|
}
|
|
9846
10047
|
try {
|
|
9847
|
-
const project = await resolveProjectForVerb(
|
|
10048
|
+
const project = await resolveProjectForVerb(
|
|
10049
|
+
client,
|
|
10050
|
+
parsed,
|
|
10051
|
+
cwdProjectApplies(target.source) ? cwdProject : void 0
|
|
10052
|
+
);
|
|
9848
10053
|
const result = await makeWork(project);
|
|
9849
10054
|
if (parsed.json) process.stdout.write(formatJson(result) + "\n");
|
|
9850
10055
|
else process.stdout.write(formatHuman(result) + "\n");
|
|
@@ -9860,7 +10065,8 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
9860
10065
|
} else if (err instanceof TransportError) {
|
|
9861
10066
|
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${target.endpoint})`);
|
|
9862
10067
|
if (isLoopbackEndpoint(target.endpoint)) {
|
|
9863
|
-
|
|
10068
|
+
const upCommand = requestedProject ? `neat up --project ${requestedProject}` : "neat up";
|
|
10069
|
+
console.error(`neat ${cmd}: run \`${upCommand}\` to start it.`);
|
|
9864
10070
|
}
|
|
9865
10071
|
} else {
|
|
9866
10072
|
console.error(`neat ${cmd}: ${err.message}`);
|
|
@@ -9870,9 +10076,13 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
9870
10076
|
}
|
|
9871
10077
|
async function runMonitorVerb(parsed) {
|
|
9872
10078
|
const requestedProject = resolveProjectFlag(parsed);
|
|
10079
|
+
const cwdProject = requestedProject ? void 0 : await projectForCwd();
|
|
9873
10080
|
let target;
|
|
9874
10081
|
try {
|
|
9875
|
-
target = await resolveClientTarget({
|
|
10082
|
+
target = await resolveClientTarget({
|
|
10083
|
+
project: requestedProject ?? cwdProject,
|
|
10084
|
+
profile: parsed.profile ?? void 0
|
|
10085
|
+
});
|
|
9876
10086
|
} catch (err) {
|
|
9877
10087
|
if (err instanceof UnknownProfileError) return 0;
|
|
9878
10088
|
throw err;
|
|
@@ -9880,7 +10090,11 @@ async function runMonitorVerb(parsed) {
|
|
|
9880
10090
|
const client = createHttpClient(target.endpoint, target.authToken);
|
|
9881
10091
|
let project;
|
|
9882
10092
|
try {
|
|
9883
|
-
project = await resolveProjectForVerb(
|
|
10093
|
+
project = await resolveProjectForVerb(
|
|
10094
|
+
client,
|
|
10095
|
+
parsed,
|
|
10096
|
+
cwdProjectApplies(target.source) ? cwdProject : void 0
|
|
10097
|
+
);
|
|
9884
10098
|
} catch (err) {
|
|
9885
10099
|
if (err instanceof ProjectResolutionError) {
|
|
9886
10100
|
console.error(`neat monitor: ${err.message}`);
|
|
@@ -9904,11 +10118,18 @@ async function runMonitorVerb(parsed) {
|
|
|
9904
10118
|
process.off("SIGTERM", onSignal);
|
|
9905
10119
|
}
|
|
9906
10120
|
}
|
|
10121
|
+
function reportFatal(err, log = console.error) {
|
|
10122
|
+
if (err instanceof RegistryError) {
|
|
10123
|
+
log(`neat: ${err.message}`);
|
|
10124
|
+
return 1;
|
|
10125
|
+
}
|
|
10126
|
+
log(err);
|
|
10127
|
+
return 1;
|
|
10128
|
+
}
|
|
9907
10129
|
var entry = process.argv[1] ?? "";
|
|
9908
10130
|
if (/[\\/](?:cli\.(?:cjs|js)|cli|neat|neat\.is)$/.test(entry)) {
|
|
9909
10131
|
main().catch((err) => {
|
|
9910
|
-
|
|
9911
|
-
process.exit(1);
|
|
10132
|
+
process.exit(reportFatal(err));
|
|
9912
10133
|
});
|
|
9913
10134
|
}
|
|
9914
10135
|
export {
|
|
@@ -9917,12 +10138,15 @@ export {
|
|
|
9917
10138
|
QUERY_VERBS,
|
|
9918
10139
|
UnknownProfileError,
|
|
9919
10140
|
commandPrefix,
|
|
10141
|
+
cwdProjectApplies,
|
|
9920
10142
|
isLoopbackEndpoint,
|
|
9921
10143
|
isNpxInvocation,
|
|
9922
10144
|
main,
|
|
9923
10145
|
parseArgs,
|
|
9924
10146
|
printBanner,
|
|
10147
|
+
projectForCwd,
|
|
9925
10148
|
readPackageVersion,
|
|
10149
|
+
reportFatal,
|
|
9926
10150
|
resolveClientTarget,
|
|
9927
10151
|
resolveDaemonUrl,
|
|
9928
10152
|
resolveProjectForVerb,
|