@neat.is/core 0.10.1 → 0.10.3-dev.20260930
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-DLQNMU3R.js} +2 -2
- package/dist/{chunk-E3YFFIHE.js → chunk-LNIVRE4L.js} +801 -569
- package/dist/chunk-LNIVRE4L.js.map +1 -0
- package/dist/cli.cjs +1544 -1081
- 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 +457 -224
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +843 -628
- 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 +854 -639
- 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-DLQNMU3R.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-DLQNMU3R.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-LNIVRE4L.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,
|
|
@@ -4186,9 +4208,15 @@ async function snapshotProjectStatus(restPort, project, body) {
|
|
|
4186
4208
|
}
|
|
4187
4209
|
return [{ name: project, status: await probeProjectHealth(restPort, project) }];
|
|
4188
4210
|
}
|
|
4189
|
-
|
|
4190
|
-
|
|
4191
|
-
|
|
4211
|
+
var STILL_WAITING_INTERVAL_MS = 1e4;
|
|
4212
|
+
function daemonProgressLine(project, event) {
|
|
4213
|
+
if (event.kind === "starting") return `starting the daemon for ${project}\u2026`;
|
|
4214
|
+
return `still waiting for ${project}'s daemon (${Math.round(event.elapsedMs / 1e3)}s)`;
|
|
4215
|
+
}
|
|
4216
|
+
async function waitForDaemonReady(restPort, project, timeoutMs, onProgress, reminderIntervalMs = STILL_WAITING_INTERVAL_MS) {
|
|
4217
|
+
const startedAt = Date.now();
|
|
4218
|
+
const deadline = startedAt + timeoutMs;
|
|
4219
|
+
let nextReminderAt = startedAt + reminderIntervalMs;
|
|
4192
4220
|
while (Date.now() < deadline) {
|
|
4193
4221
|
const body = await fetchDaemonHealth(restPort);
|
|
4194
4222
|
if (body !== null) {
|
|
@@ -4198,15 +4226,10 @@ async function waitForDaemonReady(restPort, project, timeoutMs) {
|
|
|
4198
4226
|
if (bootstrapping.length === 0) {
|
|
4199
4227
|
return { ready: true, brokenProjects: broken, stillBootstrapping: [] };
|
|
4200
4228
|
}
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
console.log(
|
|
4206
|
-
`neat: waiting on ${bootstrapping.length} project${plural}: ${bootstrapping.join(", ")}`
|
|
4207
|
-
);
|
|
4208
|
-
lastBootstrapping = bootstrapping;
|
|
4209
|
-
}
|
|
4229
|
+
}
|
|
4230
|
+
if (Date.now() >= nextReminderAt) {
|
|
4231
|
+
onProgress?.({ kind: "waiting", elapsedMs: Date.now() - startedAt });
|
|
4232
|
+
nextReminderAt += reminderIntervalMs;
|
|
4210
4233
|
}
|
|
4211
4234
|
await new Promise((r) => setTimeout(r, PROBE_INTERVAL_MS));
|
|
4212
4235
|
}
|
|
@@ -4331,6 +4354,16 @@ async function healthIsForProject(restPort, project) {
|
|
|
4331
4354
|
function daemonLogPath(projectPath3) {
|
|
4332
4355
|
return path10.join(projectPath3, "neat-out", "daemon.log");
|
|
4333
4356
|
}
|
|
4357
|
+
var DAEMON_LOG_TAIL_LINES = 12;
|
|
4358
|
+
async function readDaemonLogTail(projectPath3, maxLines = DAEMON_LOG_TAIL_LINES) {
|
|
4359
|
+
try {
|
|
4360
|
+
const raw = await fs9.readFile(daemonLogPath(projectPath3), "utf8");
|
|
4361
|
+
const lines = raw.split(/\r?\n/).map((l) => l.trimEnd()).filter((l) => l.trim().length > 0);
|
|
4362
|
+
return lines.slice(-maxLines);
|
|
4363
|
+
} catch {
|
|
4364
|
+
return [];
|
|
4365
|
+
}
|
|
4366
|
+
}
|
|
4334
4367
|
function spawnDaemonDetached(spec) {
|
|
4335
4368
|
const here = path10.dirname(fileURLToPath2(import.meta.url));
|
|
4336
4369
|
const candidates = [
|
|
@@ -4377,6 +4410,73 @@ function spawnDaemonDetached(spec) {
|
|
|
4377
4410
|
if (logFd !== null) fsSync.closeSync(logFd);
|
|
4378
4411
|
return child;
|
|
4379
4412
|
}
|
|
4413
|
+
async function ensureProjectDaemon(opts) {
|
|
4414
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
|
|
4415
|
+
const bindHost = resolveHost(
|
|
4416
|
+
{},
|
|
4417
|
+
typeof process.env.NEAT_AUTH_TOKEN === "string" && process.env.NEAT_AUTH_TOKEN.length > 0
|
|
4418
|
+
);
|
|
4419
|
+
const persistedPorts = await persistedPortsFor(opts.projectPath);
|
|
4420
|
+
if (persistedPorts && await healthIsForProject(persistedPorts.rest, opts.project)) {
|
|
4421
|
+
return { status: "already-running", ports: persistedPorts };
|
|
4422
|
+
}
|
|
4423
|
+
let allocated;
|
|
4424
|
+
if (persistedPorts && await isPortFree(persistedPorts.rest, bindHost) && await tripleFree(persistedPorts, bindHost)) {
|
|
4425
|
+
allocated = persistedPorts;
|
|
4426
|
+
} else {
|
|
4427
|
+
allocated = await allocatePorts(bindHost);
|
|
4428
|
+
}
|
|
4429
|
+
if (!allocated) return { status: "no-ports" };
|
|
4430
|
+
const release = await acquireSpawnLock(opts.projectPath);
|
|
4431
|
+
if (!release) {
|
|
4432
|
+
const reused = await waitForPeerDaemon(allocated.rest, opts.project, timeoutMs);
|
|
4433
|
+
return reused ? { status: "already-running", ports: allocated } : { status: "peer-timeout" };
|
|
4434
|
+
}
|
|
4435
|
+
try {
|
|
4436
|
+
if (await healthIsForProject(allocated.rest, opts.project)) {
|
|
4437
|
+
return { status: "already-running", ports: allocated };
|
|
4438
|
+
}
|
|
4439
|
+
spawnDaemonDetached({
|
|
4440
|
+
project: opts.project,
|
|
4441
|
+
projectPath: opts.projectPath,
|
|
4442
|
+
ports: allocated
|
|
4443
|
+
});
|
|
4444
|
+
opts.onProgress?.({ kind: "starting" });
|
|
4445
|
+
const ready = await waitForDaemonReady(allocated.rest, opts.project, timeoutMs, opts.onProgress);
|
|
4446
|
+
if (!ready.ready) {
|
|
4447
|
+
return {
|
|
4448
|
+
status: "timed-out",
|
|
4449
|
+
ports: allocated,
|
|
4450
|
+
stillBootstrapping: ready.stillBootstrapping,
|
|
4451
|
+
brokenProjects: ready.brokenProjects
|
|
4452
|
+
};
|
|
4453
|
+
}
|
|
4454
|
+
return { status: "spawned", ports: allocated, brokenProjects: ready.brokenProjects };
|
|
4455
|
+
} catch (err) {
|
|
4456
|
+
return { status: "spawn-failed", message: err.message };
|
|
4457
|
+
} finally {
|
|
4458
|
+
await release();
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
async function describeDaemonTimeout(outcome, projectPath3, timeoutMs) {
|
|
4462
|
+
const lines = [`neat: daemon did not become ready within ${timeoutMs}ms`];
|
|
4463
|
+
if (outcome.stillBootstrapping.length > 0) {
|
|
4464
|
+
lines.push(`neat: still bootstrapping: ${outcome.stillBootstrapping.join(", ")}`);
|
|
4465
|
+
}
|
|
4466
|
+
if (outcome.brokenProjects.length > 0) {
|
|
4467
|
+
lines.push(`neat: broken projects: ${outcome.brokenProjects.join(", ")}`);
|
|
4468
|
+
}
|
|
4469
|
+
const logPath = daemonLogPath(projectPath3);
|
|
4470
|
+
const tail = await readDaemonLogTail(projectPath3);
|
|
4471
|
+
if (tail.length > 0) {
|
|
4472
|
+
lines.push("");
|
|
4473
|
+
lines.push(`neat: the daemon's last words (${path10.relative(projectPath3, logPath)}):`);
|
|
4474
|
+
for (const line of tail) lines.push(` ${line}`);
|
|
4475
|
+
} else {
|
|
4476
|
+
lines.push(`neat: see ${logPath}`);
|
|
4477
|
+
}
|
|
4478
|
+
return lines;
|
|
4479
|
+
}
|
|
4380
4480
|
function openBrowser(url) {
|
|
4381
4481
|
if (!process.stdout.isTTY) return "failed";
|
|
4382
4482
|
const platform = process.platform;
|
|
@@ -4410,14 +4510,26 @@ async function runOrchestrator(opts) {
|
|
|
4410
4510
|
result.exitCode = 2;
|
|
4411
4511
|
return result;
|
|
4412
4512
|
}
|
|
4413
|
-
printBanner();
|
|
4513
|
+
if (!opts.skipBanner) printBanner();
|
|
4414
4514
|
console.log(`neat: ${opts.scanPath}`);
|
|
4415
4515
|
console.log("");
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4516
|
+
let persisted;
|
|
4517
|
+
try {
|
|
4518
|
+
persisted = await extractAndPersist({
|
|
4519
|
+
scanPath: opts.scanPath,
|
|
4520
|
+
project: opts.project,
|
|
4521
|
+
projectExplicit: opts.projectExplicit
|
|
4522
|
+
});
|
|
4523
|
+
} catch (err) {
|
|
4524
|
+
if (!(err instanceof SnapshotWriteError)) throw err;
|
|
4525
|
+
console.error(`neat: extracted ${opts.scanPath}, but could not write the snapshot.`);
|
|
4526
|
+
console.error(`neat: the graph came to ${err.nodeCount} nodes and ${err.edgeCount} edges \u2014 too large to save.`);
|
|
4527
|
+
console.error(`neat: point neat at one project instead of a tree that holds many:`);
|
|
4528
|
+
console.error(`neat: npx neat.is <path-to-one-project>`);
|
|
4529
|
+
console.error(`neat: cause: ${err.cause.message}`);
|
|
4530
|
+
result.exitCode = 1;
|
|
4531
|
+
return result;
|
|
4532
|
+
}
|
|
4421
4533
|
const { graph, services, languages } = persisted;
|
|
4422
4534
|
result.steps.discovery = { services: services.length, languages };
|
|
4423
4535
|
console.log(`discovered ${services.length} service(s) across ${languages.length} language(s)`);
|
|
@@ -4458,20 +4570,6 @@ async function runOrchestrator(opts) {
|
|
|
4458
4570
|
result.exitCode = 1;
|
|
4459
4571
|
return result;
|
|
4460
4572
|
}
|
|
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
4573
|
if (!runApply) {
|
|
4476
4574
|
result.steps.apply.skipped = true;
|
|
4477
4575
|
console.log("skipped instrumentation (--no-instrument)");
|
|
@@ -4487,75 +4585,46 @@ async function runOrchestrator(opts) {
|
|
|
4487
4585
|
}
|
|
4488
4586
|
}
|
|
4489
4587
|
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
|
-
|
|
4588
|
+
const outcome = await ensureProjectDaemon({
|
|
4589
|
+
project: currentProjectName,
|
|
4590
|
+
projectPath: opts.scanPath,
|
|
4591
|
+
timeoutMs,
|
|
4592
|
+
onProgress: (event) => console.log(`neat: ${daemonProgressLine(currentProjectName, event)}`)
|
|
4593
|
+
});
|
|
4594
|
+
const allocated = "ports" in outcome ? outcome.ports : null;
|
|
4595
|
+
switch (outcome.status) {
|
|
4596
|
+
case "already-running":
|
|
4597
|
+
result.steps.daemon = "already-running";
|
|
4598
|
+
break;
|
|
4599
|
+
case "spawned":
|
|
4600
|
+
result.steps.daemon = "spawned";
|
|
4601
|
+
if (outcome.brokenProjects.length > 0) {
|
|
4602
|
+
console.warn(
|
|
4603
|
+
`neat: ${outcome.brokenProjects.length} project(s) reported broken: ${outcome.brokenProjects.join(", ")}`
|
|
4604
|
+
);
|
|
4605
|
+
}
|
|
4606
|
+
break;
|
|
4607
|
+
case "no-ports":
|
|
4506
4608
|
for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) {
|
|
4507
4609
|
console.error(line);
|
|
4508
4610
|
}
|
|
4509
4611
|
result.exitCode = 3;
|
|
4510
4612
|
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();
|
|
4613
|
+
case "timed-out":
|
|
4614
|
+
result.steps.daemon = "timed-out";
|
|
4615
|
+
for (const line of await describeDaemonTimeout(outcome, opts.scanPath, timeoutMs)) {
|
|
4616
|
+
console.error(line);
|
|
4557
4617
|
}
|
|
4558
|
-
|
|
4618
|
+
result.exitCode = 1;
|
|
4619
|
+
return result;
|
|
4620
|
+
case "peer-timeout":
|
|
4621
|
+
console.error("neat: another `neat` is spawning this project but its daemon did not come up in time");
|
|
4622
|
+
result.exitCode = 1;
|
|
4623
|
+
return result;
|
|
4624
|
+
case "spawn-failed":
|
|
4625
|
+
console.error(`neat: daemon spawn failed \u2014 ${outcome.message}`);
|
|
4626
|
+
result.exitCode = 1;
|
|
4627
|
+
return result;
|
|
4559
4628
|
}
|
|
4560
4629
|
const webPort = allocated?.web ?? NEAT_PORTS[2];
|
|
4561
4630
|
const dashboardUrl = opts.dashboardUrl ?? `http://localhost:${webPort}`;
|
|
@@ -5335,9 +5404,9 @@ function parseGitHubRemote(url) {
|
|
|
5335
5404
|
if (!owner || !name) return null;
|
|
5336
5405
|
return { owner, name };
|
|
5337
5406
|
}
|
|
5338
|
-
async function cpGetJson(fetchImpl, cpUrl,
|
|
5407
|
+
async function cpGetJson(fetchImpl, cpUrl, path22, accessToken) {
|
|
5339
5408
|
try {
|
|
5340
|
-
const res = await fetchImpl(`${cpUrl}${
|
|
5409
|
+
const res = await fetchImpl(`${cpUrl}${path22}`, {
|
|
5341
5410
|
headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
|
|
5342
5411
|
signal: AbortSignal.timeout(CP_TIMEOUT_MS)
|
|
5343
5412
|
});
|
|
@@ -5815,10 +5884,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5815
5884
|
const root = baseUrl.replace(/\/$/, "");
|
|
5816
5885
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
5817
5886
|
return {
|
|
5818
|
-
async get(
|
|
5887
|
+
async get(path22) {
|
|
5819
5888
|
let res;
|
|
5820
5889
|
try {
|
|
5821
|
-
res = await fetchImpl(`${root}${
|
|
5890
|
+
res = await fetchImpl(`${root}${path22}`, {
|
|
5822
5891
|
headers: { ...authHeader }
|
|
5823
5892
|
});
|
|
5824
5893
|
} catch (err) {
|
|
@@ -5830,16 +5899,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5830
5899
|
const body = await res.text().catch(() => "");
|
|
5831
5900
|
throw new HttpError(
|
|
5832
5901
|
res.status,
|
|
5833
|
-
`${res.status} ${res.statusText} on GET ${
|
|
5902
|
+
`${res.status} ${res.statusText} on GET ${path22}: ${body}`,
|
|
5834
5903
|
body
|
|
5835
5904
|
);
|
|
5836
5905
|
}
|
|
5837
5906
|
return await res.json();
|
|
5838
5907
|
},
|
|
5839
|
-
async post(
|
|
5908
|
+
async post(path22, body) {
|
|
5840
5909
|
let res;
|
|
5841
5910
|
try {
|
|
5842
|
-
res = await fetchImpl(`${root}${
|
|
5911
|
+
res = await fetchImpl(`${root}${path22}`, {
|
|
5843
5912
|
method: "POST",
|
|
5844
5913
|
headers: { "content-type": "application/json", ...authHeader },
|
|
5845
5914
|
body: JSON.stringify(body)
|
|
@@ -5853,7 +5922,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5853
5922
|
const text = await res.text().catch(() => "");
|
|
5854
5923
|
throw new HttpError(
|
|
5855
5924
|
res.status,
|
|
5856
|
-
`${res.status} ${res.statusText} on POST ${
|
|
5925
|
+
`${res.status} ${res.statusText} on POST ${path22}: ${text}`,
|
|
5857
5926
|
text
|
|
5858
5927
|
);
|
|
5859
5928
|
}
|
|
@@ -5867,12 +5936,12 @@ function projectPath(project, suffix) {
|
|
|
5867
5936
|
}
|
|
5868
5937
|
async function runRootCause(client, input) {
|
|
5869
5938
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
5870
|
-
const
|
|
5939
|
+
const path22 = projectPath(
|
|
5871
5940
|
input.project,
|
|
5872
5941
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
5873
5942
|
);
|
|
5874
5943
|
try {
|
|
5875
|
-
const result = await client.get(
|
|
5944
|
+
const result = await client.get(path22);
|
|
5876
5945
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
5877
5946
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
5878
5947
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -5898,12 +5967,12 @@ async function runRootCause(client, input) {
|
|
|
5898
5967
|
}
|
|
5899
5968
|
async function runBlastRadius(client, input) {
|
|
5900
5969
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
5901
|
-
const
|
|
5970
|
+
const path22 = projectPath(
|
|
5902
5971
|
input.project,
|
|
5903
5972
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
5904
5973
|
);
|
|
5905
5974
|
try {
|
|
5906
|
-
const result = await client.get(
|
|
5975
|
+
const result = await client.get(path22);
|
|
5907
5976
|
if (result.totalAffected === 0) {
|
|
5908
5977
|
return {
|
|
5909
5978
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -5937,12 +6006,12 @@ function formatBlastEntry(n) {
|
|
|
5937
6006
|
}
|
|
5938
6007
|
async function runDependencies(client, input) {
|
|
5939
6008
|
const depth = input.depth ?? 3;
|
|
5940
|
-
const
|
|
6009
|
+
const path22 = projectPath(
|
|
5941
6010
|
input.project,
|
|
5942
6011
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
5943
6012
|
);
|
|
5944
6013
|
try {
|
|
5945
|
-
const result = await client.get(
|
|
6014
|
+
const result = await client.get(path22);
|
|
5946
6015
|
if (result.total === 0) {
|
|
5947
6016
|
return {
|
|
5948
6017
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -6034,9 +6103,9 @@ function formatDuration(ms) {
|
|
|
6034
6103
|
return `${Math.round(h / 24)}d`;
|
|
6035
6104
|
}
|
|
6036
6105
|
async function runIncidents(client, input) {
|
|
6037
|
-
const
|
|
6106
|
+
const path22 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
6038
6107
|
try {
|
|
6039
|
-
const body = await client.get(
|
|
6108
|
+
const body = await client.get(path22);
|
|
6040
6109
|
const events = body.events;
|
|
6041
6110
|
if (events.length === 0) {
|
|
6042
6111
|
return {
|
|
@@ -6517,6 +6586,122 @@ async function runDoctorCommand(argv, deps = {}) {
|
|
|
6517
6586
|
return checks.every((c) => c.ok) ? 0 : 1;
|
|
6518
6587
|
}
|
|
6519
6588
|
|
|
6589
|
+
// src/up-cli.ts
|
|
6590
|
+
import path14 from "path";
|
|
6591
|
+
function parseUpArgs(argv, env) {
|
|
6592
|
+
const args = { json: false, help: false };
|
|
6593
|
+
for (let i = 0; i < argv.length; i++) {
|
|
6594
|
+
const a = argv[i];
|
|
6595
|
+
if (a === "--json") args.json = true;
|
|
6596
|
+
else if (a === "-h" || a === "--help") args.help = true;
|
|
6597
|
+
else if (a === "--project") args.project = argv[++i];
|
|
6598
|
+
else if (a.startsWith("--project=")) args.project = a.slice("--project=".length);
|
|
6599
|
+
else if (a === "--profile") args.profile = argv[++i];
|
|
6600
|
+
else if (a.startsWith("--profile=")) args.profile = a.slice("--profile=".length);
|
|
6601
|
+
else if (args.unknown === void 0) args.unknown = a;
|
|
6602
|
+
}
|
|
6603
|
+
if (!args.project && env.NEAT_PROJECT && env.NEAT_PROJECT.length > 0) args.project = env.NEAT_PROJECT;
|
|
6604
|
+
return args;
|
|
6605
|
+
}
|
|
6606
|
+
var USAGE = [
|
|
6607
|
+
"usage: neat up [--project <name>] [--json]",
|
|
6608
|
+
"",
|
|
6609
|
+
"Start this project's daemon, or recover it if it died. Does nothing when it is",
|
|
6610
|
+
"already running. Extracts and instruments nothing \u2014 run `neat` for that."
|
|
6611
|
+
];
|
|
6612
|
+
async function runUpCommand(argv, deps) {
|
|
6613
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
6614
|
+
const env = deps.env ?? process.env;
|
|
6615
|
+
const out = deps.out ?? ((line) => console.log(line));
|
|
6616
|
+
const err = deps.err ?? ((line) => console.error(line));
|
|
6617
|
+
const ensure = deps.ensureDaemon ?? ensureProjectDaemon;
|
|
6618
|
+
const args = parseUpArgs(argv, env);
|
|
6619
|
+
if (args.help) {
|
|
6620
|
+
for (const line of USAGE) out(line);
|
|
6621
|
+
return 0;
|
|
6622
|
+
}
|
|
6623
|
+
if (args.unknown !== void 0) {
|
|
6624
|
+
err(`neat up: unexpected argument \`${args.unknown}\``);
|
|
6625
|
+
for (const line of USAGE) err(line);
|
|
6626
|
+
return 2;
|
|
6627
|
+
}
|
|
6628
|
+
let entry2;
|
|
6629
|
+
try {
|
|
6630
|
+
entry2 = args.project ? await getProject(args.project) : await findProjectByPath(cwd);
|
|
6631
|
+
} catch {
|
|
6632
|
+
entry2 = void 0;
|
|
6633
|
+
}
|
|
6634
|
+
if (!entry2) {
|
|
6635
|
+
err(
|
|
6636
|
+
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>."
|
|
6637
|
+
);
|
|
6638
|
+
return 2;
|
|
6639
|
+
}
|
|
6640
|
+
let target;
|
|
6641
|
+
try {
|
|
6642
|
+
target = await deps.resolveTarget({ project: entry2.name, profile: args.profile });
|
|
6643
|
+
} catch (e) {
|
|
6644
|
+
err(`neat up: ${e.message}`);
|
|
6645
|
+
return 2;
|
|
6646
|
+
}
|
|
6647
|
+
if (!target.local) {
|
|
6648
|
+
err(`neat up: queries from here go to ${target.endpoint}, through ${target.via}.`);
|
|
6649
|
+
err("neat up: that daemon doesn't run on this machine, so it isn't yours to start.");
|
|
6650
|
+
return 2;
|
|
6651
|
+
}
|
|
6652
|
+
const project = entry2.name;
|
|
6653
|
+
const outcome = await ensure({
|
|
6654
|
+
project,
|
|
6655
|
+
projectPath: entry2.path,
|
|
6656
|
+
// `--json` prints one object and nothing else, so progress stays quiet there.
|
|
6657
|
+
...args.json ? {} : { onProgress: (event) => out(`neat up: ${daemonProgressLine(project, event)}`) }
|
|
6658
|
+
});
|
|
6659
|
+
const log = daemonLogPath(entry2.path);
|
|
6660
|
+
if (args.json) {
|
|
6661
|
+
const ports = "ports" in outcome ? outcome.ports : void 0;
|
|
6662
|
+
out(
|
|
6663
|
+
JSON.stringify({
|
|
6664
|
+
project: entry2.name,
|
|
6665
|
+
path: entry2.path,
|
|
6666
|
+
status: outcome.status,
|
|
6667
|
+
...ports ? { endpoint: `http://localhost:${ports.rest}`, ports } : {},
|
|
6668
|
+
...outcome.status === "spawn-failed" ? { error: outcome.message } : {},
|
|
6669
|
+
log
|
|
6670
|
+
})
|
|
6671
|
+
);
|
|
6672
|
+
}
|
|
6673
|
+
switch (outcome.status) {
|
|
6674
|
+
case "already-running":
|
|
6675
|
+
if (!args.json) {
|
|
6676
|
+
out(`neat up: ${entry2.name} is already running \u2014 http://localhost:${outcome.ports.rest}`);
|
|
6677
|
+
}
|
|
6678
|
+
return 0;
|
|
6679
|
+
case "spawned":
|
|
6680
|
+
if (!args.json) {
|
|
6681
|
+
out(`neat up: started ${entry2.name} \u2014 http://localhost:${outcome.ports.rest}`);
|
|
6682
|
+
out(`neat up: traces on :${outcome.ports.otlp}, log at ${path14.relative(cwd, log) || log}`);
|
|
6683
|
+
}
|
|
6684
|
+
return 0;
|
|
6685
|
+
case "timed-out":
|
|
6686
|
+
if (!args.json) {
|
|
6687
|
+
for (const line of await describeDaemonTimeout(outcome, entry2.path, DEFAULT_DAEMON_READY_TIMEOUT_MS)) err(line);
|
|
6688
|
+
}
|
|
6689
|
+
return 1;
|
|
6690
|
+
case "peer-timeout":
|
|
6691
|
+
if (!args.json) {
|
|
6692
|
+
err("neat up: another `neat` is starting this project, and its daemon did not come up in time.");
|
|
6693
|
+
err(`neat up: see ${log}`);
|
|
6694
|
+
}
|
|
6695
|
+
return 1;
|
|
6696
|
+
case "spawn-failed":
|
|
6697
|
+
if (!args.json) err(`neat up: could not start the daemon \u2014 ${outcome.message}`);
|
|
6698
|
+
return 1;
|
|
6699
|
+
case "no-ports":
|
|
6700
|
+
if (!args.json) for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) err(line);
|
|
6701
|
+
return 3;
|
|
6702
|
+
}
|
|
6703
|
+
}
|
|
6704
|
+
|
|
6520
6705
|
// src/login-cli.ts
|
|
6521
6706
|
import readline3 from "readline/promises";
|
|
6522
6707
|
var DEFAULT_PROFILE_NAME = "hosted";
|
|
@@ -6778,7 +6963,7 @@ async function defaultReadSecret(prompt) {
|
|
|
6778
6963
|
|
|
6779
6964
|
// src/welcome.ts
|
|
6780
6965
|
import { promises as fs13 } from "fs";
|
|
6781
|
-
import
|
|
6966
|
+
import path15 from "path";
|
|
6782
6967
|
import readline4 from "readline/promises";
|
|
6783
6968
|
var WORDMARK = [
|
|
6784
6969
|
"\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 +7151,11 @@ async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, in
|
|
|
6966
7151
|
out("Building your local graph now\u2026");
|
|
6967
7152
|
out("");
|
|
6968
7153
|
const overrides = {
|
|
7154
|
+
headerShown: true,
|
|
6969
7155
|
...project !== void 0 ? { project } : {},
|
|
6970
7156
|
...declined === void 0 ? {} : declined ? { noInstrument: true } : { yes: true }
|
|
6971
7157
|
};
|
|
6972
|
-
return orchestrator(cwd,
|
|
7158
|
+
return orchestrator(cwd, overrides);
|
|
6973
7159
|
}
|
|
6974
7160
|
async function askToInstrument(out, readLine) {
|
|
6975
7161
|
out("Instrument the services for OpenTelemetry now?");
|
|
@@ -6985,8 +7171,8 @@ async function askToInstrument(out, readLine) {
|
|
|
6985
7171
|
return declined;
|
|
6986
7172
|
}
|
|
6987
7173
|
async function resolveProjectName(out, readLine, cwd, readRegistry2) {
|
|
6988
|
-
const here =
|
|
6989
|
-
const base =
|
|
7174
|
+
const here = path15.resolve(cwd);
|
|
7175
|
+
const base = path15.basename(here);
|
|
6990
7176
|
let taken;
|
|
6991
7177
|
try {
|
|
6992
7178
|
const { projects } = await readRegistry2();
|
|
@@ -6995,7 +7181,7 @@ async function resolveProjectName(out, readLine, cwd, readRegistry2) {
|
|
|
6995
7181
|
return void 0;
|
|
6996
7182
|
}
|
|
6997
7183
|
const clash = taken.get(base);
|
|
6998
|
-
if (clash === void 0 ||
|
|
7184
|
+
if (clash === void 0 || path15.resolve(clash) === here) return void 0;
|
|
6999
7185
|
out("");
|
|
7000
7186
|
out(`A project named \`${base}\` is already registered (${clash}).`);
|
|
7001
7187
|
const suggested = firstFreeName(base, taken);
|
|
@@ -7008,7 +7194,7 @@ async function resolveProjectName(out, readLine, cwd, readRegistry2) {
|
|
|
7008
7194
|
continue;
|
|
7009
7195
|
}
|
|
7010
7196
|
const other = taken.get(chosen);
|
|
7011
|
-
if (other !== void 0 &&
|
|
7197
|
+
if (other !== void 0 && path15.resolve(other) !== here) {
|
|
7012
7198
|
out(`\`${chosen}\` is registered too (${other}).`);
|
|
7013
7199
|
continue;
|
|
7014
7200
|
}
|
|
@@ -7033,9 +7219,9 @@ async function shouldShowWelcome(deps = {}) {
|
|
|
7033
7219
|
const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
|
|
7034
7220
|
const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
|
|
7035
7221
|
try {
|
|
7036
|
-
const here =
|
|
7222
|
+
const here = path15.resolve(cwd);
|
|
7037
7223
|
const { projects } = await readRegistry2();
|
|
7038
|
-
if (projects.some((p) =>
|
|
7224
|
+
if (projects.some((p) => path15.resolve(p.path) === here)) return false;
|
|
7039
7225
|
if (await hasSnapshot(here)) return false;
|
|
7040
7226
|
return true;
|
|
7041
7227
|
} catch {
|
|
@@ -7044,7 +7230,7 @@ async function shouldShowWelcome(deps = {}) {
|
|
|
7044
7230
|
}
|
|
7045
7231
|
async function defaultHasSnapshot(cwd) {
|
|
7046
7232
|
try {
|
|
7047
|
-
const entries = await fs13.readdir(
|
|
7233
|
+
const entries = await fs13.readdir(path15.join(cwd, "neat-out"));
|
|
7048
7234
|
return entries.some((e) => e.endsWith(".json"));
|
|
7049
7235
|
} catch {
|
|
7050
7236
|
return false;
|
|
@@ -7061,7 +7247,7 @@ async function defaultReadLine2(prompt) {
|
|
|
7061
7247
|
}
|
|
7062
7248
|
|
|
7063
7249
|
// src/hooks-cli.ts
|
|
7064
|
-
import
|
|
7250
|
+
import path16 from "path";
|
|
7065
7251
|
import os2 from "os";
|
|
7066
7252
|
import { promises as fs14 } from "fs";
|
|
7067
7253
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
@@ -7070,14 +7256,14 @@ var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
|
7070
7256
|
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
7071
7257
|
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
7072
7258
|
function moduleDir() {
|
|
7073
|
-
return typeof __dirname !== "undefined" ? __dirname :
|
|
7259
|
+
return typeof __dirname !== "undefined" ? __dirname : path16.dirname(fileURLToPath3(import.meta.url));
|
|
7074
7260
|
}
|
|
7075
7261
|
async function readSkillAsset(rel) {
|
|
7076
7262
|
const here = moduleDir();
|
|
7077
7263
|
const candidates = [
|
|
7078
|
-
|
|
7079
|
-
|
|
7080
|
-
|
|
7264
|
+
path16.resolve(here, "../../claude-skill", rel),
|
|
7265
|
+
path16.resolve(here, "../../../claude-skill", rel),
|
|
7266
|
+
path16.resolve(here, "../claude-skill", rel)
|
|
7081
7267
|
];
|
|
7082
7268
|
for (const candidate of candidates) {
|
|
7083
7269
|
try {
|
|
@@ -7091,20 +7277,20 @@ async function readSkillAsset(rel) {
|
|
|
7091
7277
|
}
|
|
7092
7278
|
function neatHome2() {
|
|
7093
7279
|
const override = process.env.NEAT_HOME;
|
|
7094
|
-
if (override && override.length > 0) return
|
|
7095
|
-
return
|
|
7280
|
+
if (override && override.length > 0) return path16.resolve(override);
|
|
7281
|
+
return path16.join(os2.homedir(), ".neat");
|
|
7096
7282
|
}
|
|
7097
7283
|
function claudeSettingsPath() {
|
|
7098
7284
|
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
7099
|
-
if (override && override.length > 0) return
|
|
7285
|
+
if (override && override.length > 0) return path16.resolve(override);
|
|
7100
7286
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
|
|
7101
|
-
return
|
|
7287
|
+
return path16.join(home, ".claude", "settings.json");
|
|
7102
7288
|
}
|
|
7103
7289
|
function installedHookPath() {
|
|
7104
|
-
return
|
|
7290
|
+
return path16.join(neatHome2(), "hooks", HOOK_FILENAME);
|
|
7105
7291
|
}
|
|
7106
7292
|
function gateFlagPath() {
|
|
7107
|
-
return
|
|
7293
|
+
return path16.join(neatHome2(), "hooks", "gate-enabled");
|
|
7108
7294
|
}
|
|
7109
7295
|
function isNeatSearchEntry(entry2) {
|
|
7110
7296
|
return (entry2.hooks ?? []).some(
|
|
@@ -7137,9 +7323,9 @@ async function runHooks(opts) {
|
|
|
7137
7323
|
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
7138
7324
|
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
7139
7325
|
const scriptPath = installedHookPath();
|
|
7140
|
-
await fs14.mkdir(
|
|
7326
|
+
await fs14.mkdir(path16.dirname(scriptPath), { recursive: true });
|
|
7141
7327
|
await fs14.writeFile(scriptPath, hookScript, { mode: 493 });
|
|
7142
|
-
const guidePath =
|
|
7328
|
+
const guidePath = path16.join(neatHome2(), GUIDE_INSTALL_NAME);
|
|
7143
7329
|
await fs14.writeFile(guidePath, guide, "utf8");
|
|
7144
7330
|
const settingsFile = claudeSettingsPath();
|
|
7145
7331
|
let settings = {};
|
|
@@ -7166,11 +7352,11 @@ async function runHooks(opts) {
|
|
|
7166
7352
|
...settings,
|
|
7167
7353
|
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
7168
7354
|
};
|
|
7169
|
-
await fs14.mkdir(
|
|
7355
|
+
await fs14.mkdir(path16.dirname(settingsFile), { recursive: true });
|
|
7170
7356
|
await fs14.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
7171
7357
|
const flag = gateFlagPath();
|
|
7172
7358
|
if (opts.gate) {
|
|
7173
|
-
await fs14.mkdir(
|
|
7359
|
+
await fs14.mkdir(path16.dirname(flag), { recursive: true });
|
|
7174
7360
|
await fs14.writeFile(flag, "1\n", "utf8");
|
|
7175
7361
|
} else {
|
|
7176
7362
|
await fs14.rm(flag, { force: true });
|
|
@@ -7262,7 +7448,7 @@ async function runHooksCommand(args) {
|
|
|
7262
7448
|
}
|
|
7263
7449
|
|
|
7264
7450
|
// src/claude-cli.ts
|
|
7265
|
-
import
|
|
7451
|
+
import path17 from "path";
|
|
7266
7452
|
import { promises as fs15 } from "fs";
|
|
7267
7453
|
var NEAT_SECTION_HEADING = "## neat";
|
|
7268
7454
|
var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
|
|
@@ -7297,8 +7483,8 @@ ${NEAT_DIRECTIVE_BODY}
|
|
|
7297
7483
|
}
|
|
7298
7484
|
function claudeMdPath() {
|
|
7299
7485
|
const override = process.env.NEAT_CLAUDE_MD;
|
|
7300
|
-
if (override && override.length > 0) return
|
|
7301
|
-
return
|
|
7486
|
+
if (override && override.length > 0) return path17.resolve(override);
|
|
7487
|
+
return path17.join(process.cwd(), "CLAUDE.md");
|
|
7302
7488
|
}
|
|
7303
7489
|
function splitAroundSection(raw) {
|
|
7304
7490
|
const lines = raw.split("\n");
|
|
@@ -7337,7 +7523,7 @@ async function runInstall() {
|
|
|
7337
7523
|
const raw = await readIfExists(file) ?? "";
|
|
7338
7524
|
const { before, after, found } = splitAroundSection(raw);
|
|
7339
7525
|
const next = compose(before, after);
|
|
7340
|
-
await fs15.mkdir(
|
|
7526
|
+
await fs15.mkdir(path17.dirname(file), { recursive: true });
|
|
7341
7527
|
await fs15.writeFile(file, next, "utf8");
|
|
7342
7528
|
const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
|
|
7343
7529
|
console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
|
|
@@ -7401,7 +7587,7 @@ async function runClaudeCommand(args) {
|
|
|
7401
7587
|
}
|
|
7402
7588
|
|
|
7403
7589
|
// src/codex-cli.ts
|
|
7404
|
-
import
|
|
7590
|
+
import path18 from "path";
|
|
7405
7591
|
import os3 from "os";
|
|
7406
7592
|
import { promises as fs16 } from "fs";
|
|
7407
7593
|
import { isDeepStrictEqual } from "util";
|
|
@@ -7421,14 +7607,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
7421
7607
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
7422
7608
|
function codexConfigPath() {
|
|
7423
7609
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
7424
|
-
if (override && override.length > 0) return
|
|
7610
|
+
if (override && override.length > 0) return path18.resolve(override);
|
|
7425
7611
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? os3.homedir();
|
|
7426
|
-
return
|
|
7612
|
+
return path18.join(home, ".codex", "config.toml");
|
|
7427
7613
|
}
|
|
7428
7614
|
function agentsFilePath() {
|
|
7429
7615
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
7430
|
-
if (override && override.length > 0) return
|
|
7431
|
-
return
|
|
7616
|
+
if (override && override.length > 0) return path18.resolve(override);
|
|
7617
|
+
return path18.join(process.cwd(), "AGENTS.md");
|
|
7432
7618
|
}
|
|
7433
7619
|
function isTableHeader(line) {
|
|
7434
7620
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -7611,14 +7797,14 @@ async function runCodex(opts) {
|
|
|
7611
7797
|
return { exitCode: 0 };
|
|
7612
7798
|
}
|
|
7613
7799
|
if (config.changed) {
|
|
7614
|
-
await fs16.mkdir(
|
|
7800
|
+
await fs16.mkdir(path18.dirname(configPath), { recursive: true });
|
|
7615
7801
|
await fs16.writeFile(configPath, config.text, "utf8");
|
|
7616
7802
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
7617
7803
|
} else {
|
|
7618
7804
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
7619
7805
|
}
|
|
7620
7806
|
if (agents.changed) {
|
|
7621
|
-
await fs16.mkdir(
|
|
7807
|
+
await fs16.mkdir(path18.dirname(agentsPath), { recursive: true });
|
|
7622
7808
|
await fs16.writeFile(agentsPath, agents.text, "utf8");
|
|
7623
7809
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
7624
7810
|
} else {
|
|
@@ -7675,7 +7861,7 @@ async function runCodexCommand(args) {
|
|
|
7675
7861
|
}
|
|
7676
7862
|
|
|
7677
7863
|
// src/editors-cli.ts
|
|
7678
|
-
import
|
|
7864
|
+
import path19 from "path";
|
|
7679
7865
|
import os4 from "os";
|
|
7680
7866
|
import { promises as fs17 } from "fs";
|
|
7681
7867
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
@@ -7701,17 +7887,17 @@ function homeDir() {
|
|
|
7701
7887
|
}
|
|
7702
7888
|
function xdgConfigDir() {
|
|
7703
7889
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
7704
|
-
return xdg && xdg.length > 0 ?
|
|
7890
|
+
return xdg && xdg.length > 0 ? path19.resolve(xdg) : path19.join(homeDir(), ".config");
|
|
7705
7891
|
}
|
|
7706
7892
|
function envOverride(name) {
|
|
7707
7893
|
const v = process.env[name];
|
|
7708
|
-
return v && v.length > 0 ?
|
|
7894
|
+
return v && v.length > 0 ? path19.resolve(v) : void 0;
|
|
7709
7895
|
}
|
|
7710
7896
|
var CURSOR_CLIENT = {
|
|
7711
7897
|
id: "cursor",
|
|
7712
7898
|
label: "Cursor",
|
|
7713
7899
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
7714
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
7900
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path19.join(homeDir(), ".cursor", "mcp.json"),
|
|
7715
7901
|
mcpContainerKey: "mcpServers",
|
|
7716
7902
|
format: "json",
|
|
7717
7903
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -7723,7 +7909,7 @@ var DEVIN_CLIENT = {
|
|
|
7723
7909
|
id: "devin",
|
|
7724
7910
|
label: "Devin Desktop (Cascade)",
|
|
7725
7911
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
7726
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
7912
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path19.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
7727
7913
|
mcpContainerKey: "mcpServers",
|
|
7728
7914
|
format: "json",
|
|
7729
7915
|
rulesFileName: ".windsurfrules"
|
|
@@ -7732,7 +7918,7 @@ var GEMINI_CLIENT = {
|
|
|
7732
7918
|
id: "gemini",
|
|
7733
7919
|
label: "Gemini CLI",
|
|
7734
7920
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
7735
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
7921
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path19.join(homeDir(), ".gemini", "settings.json"),
|
|
7736
7922
|
mcpContainerKey: "mcpServers",
|
|
7737
7923
|
format: "json",
|
|
7738
7924
|
rulesFileName: "GEMINI.md"
|
|
@@ -7741,7 +7927,7 @@ var QWEN_CLIENT = {
|
|
|
7741
7927
|
id: "qwen",
|
|
7742
7928
|
label: "Qwen Code",
|
|
7743
7929
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
7744
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
7930
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path19.join(homeDir(), ".qwen", "settings.json"),
|
|
7745
7931
|
mcpContainerKey: "mcpServers",
|
|
7746
7932
|
format: "json",
|
|
7747
7933
|
rulesFileName: "QWEN.md"
|
|
@@ -7750,7 +7936,7 @@ var AMAZONQ_CLIENT = {
|
|
|
7750
7936
|
id: "amazonq",
|
|
7751
7937
|
label: "Amazon Q Developer CLI",
|
|
7752
7938
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
7753
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
7939
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path19.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
7754
7940
|
mcpContainerKey: "mcpServers",
|
|
7755
7941
|
format: "json"
|
|
7756
7942
|
};
|
|
@@ -7758,7 +7944,7 @@ var ROOCODE_CLIENT = {
|
|
|
7758
7944
|
id: "roocode",
|
|
7759
7945
|
label: "Roo Code",
|
|
7760
7946
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
7761
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
7947
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path19.join(process.cwd(), ".roo", "mcp.json"),
|
|
7762
7948
|
mcpContainerKey: "mcpServers",
|
|
7763
7949
|
format: "json"
|
|
7764
7950
|
};
|
|
@@ -7771,9 +7957,9 @@ var ZED_CLIENT = {
|
|
|
7771
7957
|
if (override) return override;
|
|
7772
7958
|
if (process.platform === "win32") {
|
|
7773
7959
|
const appData = process.env.APPDATA;
|
|
7774
|
-
if (appData && appData.length > 0) return
|
|
7960
|
+
if (appData && appData.length > 0) return path19.join(appData, "Zed", "settings.json");
|
|
7775
7961
|
}
|
|
7776
|
-
return
|
|
7962
|
+
return path19.join(homeDir(), ".config", "zed", "settings.json");
|
|
7777
7963
|
},
|
|
7778
7964
|
mcpContainerKey: "context_servers",
|
|
7779
7965
|
format: "jsonc",
|
|
@@ -7783,7 +7969,7 @@ var OPENCODE_CLIENT = {
|
|
|
7783
7969
|
id: "opencode",
|
|
7784
7970
|
label: "OpenCode",
|
|
7785
7971
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
7786
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
7972
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path19.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
7787
7973
|
mcpContainerKey: "mcp",
|
|
7788
7974
|
format: "json",
|
|
7789
7975
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -7793,7 +7979,7 @@ var CRUSH_CLIENT = {
|
|
|
7793
7979
|
id: "crush",
|
|
7794
7980
|
label: "Crush",
|
|
7795
7981
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
7796
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
7982
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path19.join(xdgConfigDir(), "crush", "crush.json"),
|
|
7797
7983
|
mcpContainerKey: "mcp",
|
|
7798
7984
|
format: "json",
|
|
7799
7985
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -7898,7 +8084,7 @@ async function runEditorInstall(client, opts) {
|
|
|
7898
8084
|
const mcpPath = client.mcpConfigPath();
|
|
7899
8085
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
7900
8086
|
const hasRules = typeof client.rulesFileName === "string";
|
|
7901
|
-
const rulesPath = hasRules ?
|
|
8087
|
+
const rulesPath = hasRules ? path19.join(opts.projectDir, client.rulesFileName) : "";
|
|
7902
8088
|
const mcp = await planMcp(client, mcpPath);
|
|
7903
8089
|
if (mcp === null) return { exitCode: 1 };
|
|
7904
8090
|
let existingRules = "";
|
|
@@ -7941,10 +8127,10 @@ async function runEditorInstall(client, opts) {
|
|
|
7941
8127
|
);
|
|
7942
8128
|
return { exitCode: 0 };
|
|
7943
8129
|
}
|
|
7944
|
-
await fs17.mkdir(
|
|
8130
|
+
await fs17.mkdir(path19.dirname(mcpPath), { recursive: true });
|
|
7945
8131
|
await fs17.writeFile(mcpPath, mcp.text, "utf8");
|
|
7946
8132
|
if (hasRules) {
|
|
7947
|
-
await fs17.mkdir(
|
|
8133
|
+
await fs17.mkdir(path19.dirname(rulesPath), { recursive: true });
|
|
7948
8134
|
await fs17.writeFile(rulesPath, newRules, "utf8");
|
|
7949
8135
|
}
|
|
7950
8136
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
@@ -8445,7 +8631,7 @@ function sleep(ms, signal) {
|
|
|
8445
8631
|
}
|
|
8446
8632
|
|
|
8447
8633
|
// src/cli-verbs.ts
|
|
8448
|
-
import
|
|
8634
|
+
import path20 from "path";
|
|
8449
8635
|
async function resolveProjectEntry(opts) {
|
|
8450
8636
|
const entries = await listProjects();
|
|
8451
8637
|
if (opts.project) {
|
|
@@ -8455,7 +8641,7 @@ async function resolveProjectEntry(opts) {
|
|
|
8455
8641
|
const cwd = opts.cwd ?? process.cwd();
|
|
8456
8642
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
8457
8643
|
for (const entry2 of entries) {
|
|
8458
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
8644
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path20.sep}`)) {
|
|
8459
8645
|
return entry2;
|
|
8460
8646
|
}
|
|
8461
8647
|
}
|
|
@@ -8774,6 +8960,8 @@ function usage5() {
|
|
|
8774
8960
|
console.log(" test <id> re-check an existing connector's credential");
|
|
8775
8961
|
console.log(" Credentials default to an env-var reference ($VAR) resolved at");
|
|
8776
8962
|
console.log(" run time; the config file is written owner-only (0600).");
|
|
8963
|
+
console.log(" up Start this project's daemon, or recover it if it died. Does");
|
|
8964
|
+
console.log(" nothing when it is already running. Flags: --project, --json.");
|
|
8777
8965
|
console.log(" doctor Preflight this directory's setup \u2014 Node version, project,");
|
|
8778
8966
|
console.log(" and daemon reachability \u2014 and print a fix for anything down.");
|
|
8779
8967
|
console.log(" Flags: --json. Exits 0 when all pass, 1 when a check fails.");
|
|
@@ -9041,12 +9229,12 @@ async function runInit(opts) {
|
|
|
9041
9229
|
printDiscoveryReport(opts, services);
|
|
9042
9230
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
9043
9231
|
const patch = renderPatch(sections);
|
|
9044
|
-
const patchPath =
|
|
9232
|
+
const patchPath = path21.join(opts.scanPath, "neat.patch");
|
|
9045
9233
|
if (opts.dryRun) {
|
|
9046
9234
|
await fs18.writeFile(patchPath, patch, "utf8");
|
|
9047
9235
|
written.push(patchPath);
|
|
9048
9236
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
9049
|
-
const gitignorePath =
|
|
9237
|
+
const gitignorePath = path21.join(opts.scanPath, ".gitignore");
|
|
9050
9238
|
const gitignoreExists = await fs18.stat(gitignorePath).then(() => true).catch(() => false);
|
|
9051
9239
|
const verb = gitignoreExists ? "append" : "create";
|
|
9052
9240
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
@@ -9058,9 +9246,9 @@ async function runInit(opts) {
|
|
|
9058
9246
|
const graph = getGraph(graphKey);
|
|
9059
9247
|
const projectPaths = pathsForProject(
|
|
9060
9248
|
graphKey,
|
|
9061
|
-
|
|
9249
|
+
path21.join(opts.scanPath, "neat-out")
|
|
9062
9250
|
);
|
|
9063
|
-
const errorsPath =
|
|
9251
|
+
const errorsPath = path21.join(path21.dirname(opts.outPath), path21.basename(projectPaths.errorsPath));
|
|
9064
9252
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
9065
9253
|
await saveGraphToDisk(graph, opts.outPath);
|
|
9066
9254
|
written.push(opts.outPath);
|
|
@@ -9069,15 +9257,13 @@ async function runInit(opts) {
|
|
|
9069
9257
|
written.push(gitignoreResult.file);
|
|
9070
9258
|
}
|
|
9071
9259
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
9072
|
-
let currentProjectName = opts.project;
|
|
9073
9260
|
try {
|
|
9074
|
-
|
|
9261
|
+
await addProject({
|
|
9075
9262
|
name: opts.project,
|
|
9076
9263
|
path: opts.scanPath,
|
|
9077
9264
|
languages,
|
|
9078
9265
|
status: "active"
|
|
9079
9266
|
});
|
|
9080
|
-
currentProjectName = entry2.name;
|
|
9081
9267
|
} catch (err) {
|
|
9082
9268
|
if (err instanceof ProjectNameCollisionError) {
|
|
9083
9269
|
console.error(`neat init: ${err.message}`);
|
|
@@ -9086,20 +9272,6 @@ async function runInit(opts) {
|
|
|
9086
9272
|
}
|
|
9087
9273
|
throw err;
|
|
9088
9274
|
}
|
|
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
9275
|
if (!opts.noInstall) {
|
|
9104
9276
|
if (opts.apply) {
|
|
9105
9277
|
let instrumented = 0;
|
|
@@ -9160,6 +9332,8 @@ async function runInit(opts) {
|
|
|
9160
9332
|
if (result.extractionErrors > 0) {
|
|
9161
9333
|
console.log(`errors: ${errorsPath}`);
|
|
9162
9334
|
}
|
|
9335
|
+
const skippedBanner = formatSkippedBanner(result.skippedFiles);
|
|
9336
|
+
if (skippedBanner) console.log(skippedBanner);
|
|
9163
9337
|
console.log(formatPrecisionFloorBanner(result.extractedDropped));
|
|
9164
9338
|
if (result.extractionErrors > 0 && isStrictExtractionEnabled()) {
|
|
9165
9339
|
return { exitCode: 4, writtenFiles: written };
|
|
@@ -9180,9 +9354,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
9180
9354
|
};
|
|
9181
9355
|
function claudeConfigPath() {
|
|
9182
9356
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
9183
|
-
if (override && override.length > 0) return
|
|
9357
|
+
if (override && override.length > 0) return path21.resolve(override);
|
|
9184
9358
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
9185
|
-
return
|
|
9359
|
+
return path21.join(home, ".claude.json");
|
|
9186
9360
|
}
|
|
9187
9361
|
async function runSkill(opts) {
|
|
9188
9362
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -9206,7 +9380,7 @@ async function runSkill(opts) {
|
|
|
9206
9380
|
...existing,
|
|
9207
9381
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
9208
9382
|
};
|
|
9209
|
-
await fs18.mkdir(
|
|
9383
|
+
await fs18.mkdir(path21.dirname(target), { recursive: true });
|
|
9210
9384
|
await fs18.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
9211
9385
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
9212
9386
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
@@ -9256,6 +9430,21 @@ async function main() {
|
|
|
9256
9430
|
if (code !== 0) process.exit(code);
|
|
9257
9431
|
return;
|
|
9258
9432
|
}
|
|
9433
|
+
if (cmd0 === "up") {
|
|
9434
|
+
const code = await runUpCommand(argv.slice(1), {
|
|
9435
|
+
resolveTarget: async (opts) => {
|
|
9436
|
+
const target = await resolveClientTarget(opts);
|
|
9437
|
+
const named = opts.profile ?? process.env.NEAT_PROFILE;
|
|
9438
|
+
return {
|
|
9439
|
+
endpoint: target.endpoint,
|
|
9440
|
+
local: isLoopbackEndpoint(target.endpoint),
|
|
9441
|
+
via: target.source === "profile" ? `the \`${named}\` profile` : target.source === "active" ? "your active profile" : "your environment"
|
|
9442
|
+
};
|
|
9443
|
+
}
|
|
9444
|
+
});
|
|
9445
|
+
if (code !== 0) process.exit(code);
|
|
9446
|
+
return;
|
|
9447
|
+
}
|
|
9259
9448
|
if (cmd0 === "login") {
|
|
9260
9449
|
const code = await runLoginCommand(argv.slice(1));
|
|
9261
9450
|
if (code !== 0) process.exit(code);
|
|
@@ -9328,12 +9517,12 @@ async function main() {
|
|
|
9328
9517
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
9329
9518
|
process.exit(2);
|
|
9330
9519
|
}
|
|
9331
|
-
const scanPath =
|
|
9520
|
+
const scanPath = path21.resolve(target);
|
|
9332
9521
|
const projectExplicit = parsed.project !== null;
|
|
9333
|
-
const projectName = projectExplicit ? project :
|
|
9522
|
+
const projectName = projectExplicit ? project : path21.basename(scanPath);
|
|
9334
9523
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
9335
|
-
const fallback = pathsForProject(projectKey,
|
|
9336
|
-
const outPath =
|
|
9524
|
+
const fallback = pathsForProject(projectKey, path21.join(scanPath, "neat-out")).snapshotPath;
|
|
9525
|
+
const outPath = path21.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
9337
9526
|
const result = await runInit({
|
|
9338
9527
|
scanPath,
|
|
9339
9528
|
outPath,
|
|
@@ -9354,21 +9543,21 @@ async function main() {
|
|
|
9354
9543
|
usage5();
|
|
9355
9544
|
process.exit(2);
|
|
9356
9545
|
}
|
|
9357
|
-
const scanPath =
|
|
9546
|
+
const scanPath = path21.resolve(target);
|
|
9358
9547
|
const stat = await fs18.stat(scanPath).catch(() => null);
|
|
9359
9548
|
if (!stat || !stat.isDirectory()) {
|
|
9360
9549
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
9361
9550
|
process.exit(2);
|
|
9362
9551
|
}
|
|
9363
|
-
const projectPaths = pathsForProject(project,
|
|
9364
|
-
const outPath =
|
|
9365
|
-
const errorsPath =
|
|
9366
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
9552
|
+
const projectPaths = pathsForProject(project, path21.join(scanPath, "neat-out"));
|
|
9553
|
+
const outPath = path21.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
9554
|
+
const errorsPath = path21.resolve(
|
|
9555
|
+
process.env.NEAT_ERRORS_PATH ?? path21.join(path21.dirname(outPath), path21.basename(projectPaths.errorsPath))
|
|
9367
9556
|
);
|
|
9368
|
-
const staleEventsPath =
|
|
9369
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
9557
|
+
const staleEventsPath = path21.resolve(
|
|
9558
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path21.join(path21.dirname(outPath), path21.basename(projectPaths.staleEventsPath))
|
|
9370
9559
|
);
|
|
9371
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
9560
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path21.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
9372
9561
|
const handle = await startWatch(getGraph(project), {
|
|
9373
9562
|
scanPath,
|
|
9374
9563
|
outPath,
|
|
@@ -9377,7 +9566,7 @@ async function main() {
|
|
|
9377
9566
|
project,
|
|
9378
9567
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
9379
9568
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
9380
|
-
neatHome: process.env.NEAT_HOME ?
|
|
9569
|
+
neatHome: process.env.NEAT_HOME ? path21.resolve(process.env.NEAT_HOME) : path21.join(os5.homedir(), ".neat"),
|
|
9381
9570
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
9382
9571
|
host: process.env.HOST ?? "0.0.0.0",
|
|
9383
9572
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -9558,12 +9747,12 @@ async function main() {
|
|
|
9558
9747
|
usage5();
|
|
9559
9748
|
process.exit(1);
|
|
9560
9749
|
}
|
|
9561
|
-
async function tryOrchestrator(cmd, parsed) {
|
|
9562
|
-
const scanPath =
|
|
9750
|
+
async function tryOrchestrator(cmd, parsed, overrides = {}) {
|
|
9751
|
+
const scanPath = path21.resolve(cmd);
|
|
9563
9752
|
const stat = await fs18.stat(scanPath).catch(() => null);
|
|
9564
9753
|
if (!stat || !stat.isDirectory()) return null;
|
|
9565
9754
|
const projectExplicit = parsed.project !== null;
|
|
9566
|
-
const projectName = projectExplicit ? parsed.project :
|
|
9755
|
+
const projectName = projectExplicit ? parsed.project : path21.basename(scanPath);
|
|
9567
9756
|
const result = await runOrchestrator({
|
|
9568
9757
|
scanPath,
|
|
9569
9758
|
project: projectName,
|
|
@@ -9571,7 +9760,8 @@ async function tryOrchestrator(cmd, parsed) {
|
|
|
9571
9760
|
noInstrument: parsed.noInstrument,
|
|
9572
9761
|
open: parsed.open,
|
|
9573
9762
|
noOpen: parsed.noOpen,
|
|
9574
|
-
yes: parsed.yes
|
|
9763
|
+
yes: parsed.yes,
|
|
9764
|
+
...overrides.skipBanner ? { skipBanner: true } : {}
|
|
9575
9765
|
});
|
|
9576
9766
|
return result.exitCode;
|
|
9577
9767
|
}
|
|
@@ -9581,13 +9771,18 @@ async function runWelcomeFlow(parsed) {
|
|
|
9581
9771
|
// What the person chose at the door reaches the orchestrator as the flags they
|
|
9582
9772
|
// stand for: the name as `--project`, the instrument answer as `--no-instrument`
|
|
9583
9773
|
// or as `yes` (which keeps the orchestrator's own prompt quiet, since the door
|
|
9584
|
-
// already asked).
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9774
|
+
// already asked). `headerShown` isn't a flag anyone can type — it says the
|
|
9775
|
+
// door's wordmark already opened this run, so the orchestrator's banner is skipped.
|
|
9776
|
+
orchestrator: async (cwd, opts) => await tryOrchestrator(
|
|
9777
|
+
cwd,
|
|
9778
|
+
{
|
|
9779
|
+
...parsed,
|
|
9780
|
+
...opts?.project !== void 0 ? { project: opts.project } : {},
|
|
9781
|
+
...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
|
|
9782
|
+
...opts?.yes !== void 0 ? { yes: opts.yes } : {}
|
|
9783
|
+
},
|
|
9784
|
+
{ skipBanner: opts?.headerShown === true }
|
|
9785
|
+
) ?? 0,
|
|
9591
9786
|
// An explicit flag is an answer; don't ask again.
|
|
9592
9787
|
instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
|
|
9593
9788
|
});
|
|
@@ -9629,9 +9824,10 @@ function isLoopbackEndpoint(endpoint) {
|
|
|
9629
9824
|
return false;
|
|
9630
9825
|
}
|
|
9631
9826
|
}
|
|
9632
|
-
async function resolveProjectForVerb(client, parsed) {
|
|
9827
|
+
async function resolveProjectForVerb(client, parsed, cwdProject) {
|
|
9633
9828
|
const explicit = resolveProjectFlag(parsed);
|
|
9634
9829
|
if (explicit) return explicit;
|
|
9830
|
+
if (cwdProject) return cwdProject;
|
|
9635
9831
|
const projects = await client.get("/projects");
|
|
9636
9832
|
if (projects.some((p) => p.name === DEFAULT_PROJECT)) {
|
|
9637
9833
|
return void 0;
|
|
@@ -9678,14 +9874,28 @@ async function resolveClientTarget(opts = {}) {
|
|
|
9678
9874
|
}
|
|
9679
9875
|
return { endpoint: "http://localhost:8080", source: "default" };
|
|
9680
9876
|
}
|
|
9877
|
+
async function projectForCwd(cwd = process.cwd()) {
|
|
9878
|
+
try {
|
|
9879
|
+
return (await findProjectByPath(cwd))?.name;
|
|
9880
|
+
} catch {
|
|
9881
|
+
return void 0;
|
|
9882
|
+
}
|
|
9883
|
+
}
|
|
9884
|
+
function cwdProjectApplies(source) {
|
|
9885
|
+
return source === "daemon-record" || source === "default";
|
|
9886
|
+
}
|
|
9681
9887
|
async function resolveDaemonUrl(project, profile) {
|
|
9682
9888
|
return (await resolveClientTarget({ project, profile })).endpoint;
|
|
9683
9889
|
}
|
|
9684
9890
|
async function runQueryVerb(cmd, parsed) {
|
|
9685
9891
|
const requestedProject = resolveProjectFlag(parsed);
|
|
9892
|
+
const cwdProject = requestedProject ? void 0 : await projectForCwd();
|
|
9686
9893
|
let target;
|
|
9687
9894
|
try {
|
|
9688
|
-
target = await resolveClientTarget({
|
|
9895
|
+
target = await resolveClientTarget({
|
|
9896
|
+
project: requestedProject ?? cwdProject,
|
|
9897
|
+
profile: parsed.profile ?? void 0
|
|
9898
|
+
});
|
|
9689
9899
|
} catch (err) {
|
|
9690
9900
|
if (err instanceof UnknownProfileError) {
|
|
9691
9901
|
process.stderr.write(`${err.message}
|
|
@@ -9844,7 +10054,11 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
9844
10054
|
return 2;
|
|
9845
10055
|
}
|
|
9846
10056
|
try {
|
|
9847
|
-
const project = await resolveProjectForVerb(
|
|
10057
|
+
const project = await resolveProjectForVerb(
|
|
10058
|
+
client,
|
|
10059
|
+
parsed,
|
|
10060
|
+
cwdProjectApplies(target.source) ? cwdProject : void 0
|
|
10061
|
+
);
|
|
9848
10062
|
const result = await makeWork(project);
|
|
9849
10063
|
if (parsed.json) process.stdout.write(formatJson(result) + "\n");
|
|
9850
10064
|
else process.stdout.write(formatHuman(result) + "\n");
|
|
@@ -9860,7 +10074,8 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
9860
10074
|
} else if (err instanceof TransportError) {
|
|
9861
10075
|
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${target.endpoint})`);
|
|
9862
10076
|
if (isLoopbackEndpoint(target.endpoint)) {
|
|
9863
|
-
|
|
10077
|
+
const upCommand = requestedProject ? `neat up --project ${requestedProject}` : "neat up";
|
|
10078
|
+
console.error(`neat ${cmd}: run \`${upCommand}\` to start it.`);
|
|
9864
10079
|
}
|
|
9865
10080
|
} else {
|
|
9866
10081
|
console.error(`neat ${cmd}: ${err.message}`);
|
|
@@ -9870,9 +10085,13 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
9870
10085
|
}
|
|
9871
10086
|
async function runMonitorVerb(parsed) {
|
|
9872
10087
|
const requestedProject = resolveProjectFlag(parsed);
|
|
10088
|
+
const cwdProject = requestedProject ? void 0 : await projectForCwd();
|
|
9873
10089
|
let target;
|
|
9874
10090
|
try {
|
|
9875
|
-
target = await resolveClientTarget({
|
|
10091
|
+
target = await resolveClientTarget({
|
|
10092
|
+
project: requestedProject ?? cwdProject,
|
|
10093
|
+
profile: parsed.profile ?? void 0
|
|
10094
|
+
});
|
|
9876
10095
|
} catch (err) {
|
|
9877
10096
|
if (err instanceof UnknownProfileError) return 0;
|
|
9878
10097
|
throw err;
|
|
@@ -9880,7 +10099,11 @@ async function runMonitorVerb(parsed) {
|
|
|
9880
10099
|
const client = createHttpClient(target.endpoint, target.authToken);
|
|
9881
10100
|
let project;
|
|
9882
10101
|
try {
|
|
9883
|
-
project = await resolveProjectForVerb(
|
|
10102
|
+
project = await resolveProjectForVerb(
|
|
10103
|
+
client,
|
|
10104
|
+
parsed,
|
|
10105
|
+
cwdProjectApplies(target.source) ? cwdProject : void 0
|
|
10106
|
+
);
|
|
9884
10107
|
} catch (err) {
|
|
9885
10108
|
if (err instanceof ProjectResolutionError) {
|
|
9886
10109
|
console.error(`neat monitor: ${err.message}`);
|
|
@@ -9904,11 +10127,18 @@ async function runMonitorVerb(parsed) {
|
|
|
9904
10127
|
process.off("SIGTERM", onSignal);
|
|
9905
10128
|
}
|
|
9906
10129
|
}
|
|
10130
|
+
function reportFatal(err, log = console.error) {
|
|
10131
|
+
if (err instanceof RegistryError) {
|
|
10132
|
+
log(`neat: ${err.message}`);
|
|
10133
|
+
return 1;
|
|
10134
|
+
}
|
|
10135
|
+
log(err);
|
|
10136
|
+
return 1;
|
|
10137
|
+
}
|
|
9907
10138
|
var entry = process.argv[1] ?? "";
|
|
9908
10139
|
if (/[\\/](?:cli\.(?:cjs|js)|cli|neat|neat\.is)$/.test(entry)) {
|
|
9909
10140
|
main().catch((err) => {
|
|
9910
|
-
|
|
9911
|
-
process.exit(1);
|
|
10141
|
+
process.exit(reportFatal(err));
|
|
9912
10142
|
});
|
|
9913
10143
|
}
|
|
9914
10144
|
export {
|
|
@@ -9917,12 +10147,15 @@ export {
|
|
|
9917
10147
|
QUERY_VERBS,
|
|
9918
10148
|
UnknownProfileError,
|
|
9919
10149
|
commandPrefix,
|
|
10150
|
+
cwdProjectApplies,
|
|
9920
10151
|
isLoopbackEndpoint,
|
|
9921
10152
|
isNpxInvocation,
|
|
9922
10153
|
main,
|
|
9923
10154
|
parseArgs,
|
|
9924
10155
|
printBanner,
|
|
10156
|
+
projectForCwd,
|
|
9925
10157
|
readPackageVersion,
|
|
10158
|
+
reportFatal,
|
|
9926
10159
|
resolveClientTarget,
|
|
9927
10160
|
resolveDaemonUrl,
|
|
9928
10161
|
resolveProjectForVerb,
|