@neat.is/core 0.10.1-dev.20260928 → 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-ERE47MCR.js → chunk-5SJHA5ZH.js} +2 -2
- package/dist/{chunk-HVBD4ULO.js → chunk-CAGAHYDB.js} +3 -3
- package/dist/{chunk-GDGUY4T6.js → chunk-M7CGATVC.js} +3 -2
- package/dist/{chunk-CNOJ4T6I.js → chunk-TXKOAART.js} +797 -563
- package/dist/chunk-TXKOAART.js.map +1 -0
- package/dist/cli.cjs +1858 -1110
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +6 -2
- package/dist/cli.d.ts +6 -2
- package/dist/cli.js +802 -289
- 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 +4 -4
- package/dist/neatd.cjs +849 -632
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-Y5K4WGWO.js → otel-grpc-CU73Y5WT.js} +3 -3
- package/dist/server.cjs +712 -525
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +3 -3
- package/dist/chunk-CNOJ4T6I.js.map +0 -1
- /package/dist/{chunk-ERE47MCR.js.map → chunk-5SJHA5ZH.js.map} +0 -0
- /package/dist/{chunk-HVBD4ULO.js.map → chunk-CAGAHYDB.js.map} +0 -0
- /package/dist/{chunk-GDGUY4T6.js.map → chunk-M7CGATVC.js.map} +0 -0
- /package/dist/{otel-grpc-Y5K4WGWO.js.map → otel-grpc-CU73Y5WT.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,23 +82,24 @@ 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
|
-
} from "./chunk-
|
|
88
|
+
} from "./chunk-5SJHA5ZH.js";
|
|
85
89
|
import {
|
|
86
90
|
__dirname,
|
|
87
91
|
__require,
|
|
88
92
|
assertBindAuthority,
|
|
89
93
|
buildOtelReceiver,
|
|
94
|
+
isLoopbackHost,
|
|
90
95
|
listenSteppingOtlp,
|
|
91
96
|
readAuthEnv
|
|
92
|
-
} from "./chunk-
|
|
97
|
+
} from "./chunk-M7CGATVC.js";
|
|
93
98
|
|
|
94
99
|
// src/cli.ts
|
|
95
|
-
import
|
|
100
|
+
import path21 from "path";
|
|
96
101
|
import os5 from "os";
|
|
97
|
-
import { promises as
|
|
102
|
+
import { promises as fs18 } from "fs";
|
|
98
103
|
|
|
99
104
|
// src/banner.ts
|
|
100
105
|
import path from "path";
|
|
@@ -3936,6 +3941,20 @@ import path10 from "path";
|
|
|
3936
3941
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3937
3942
|
import { spawn as spawn2 } from "child_process";
|
|
3938
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
|
+
};
|
|
3939
3958
|
async function extractAndPersist(opts) {
|
|
3940
3959
|
const services = await discoverServices(opts.scanPath);
|
|
3941
3960
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
@@ -3947,7 +3966,11 @@ async function extractAndPersist(opts) {
|
|
|
3947
3966
|
errorsPath: projectPaths.errorsPath
|
|
3948
3967
|
});
|
|
3949
3968
|
if (!opts.dryRun) {
|
|
3950
|
-
|
|
3969
|
+
try {
|
|
3970
|
+
await saveGraphToDisk(graph, projectPaths.snapshotPath);
|
|
3971
|
+
} catch (err) {
|
|
3972
|
+
throw new SnapshotWriteError(opts.scanPath, graph.order, graph.size, err);
|
|
3973
|
+
}
|
|
3951
3974
|
}
|
|
3952
3975
|
return {
|
|
3953
3976
|
graph,
|
|
@@ -4330,6 +4353,16 @@ async function healthIsForProject(restPort, project) {
|
|
|
4330
4353
|
function daemonLogPath(projectPath3) {
|
|
4331
4354
|
return path10.join(projectPath3, "neat-out", "daemon.log");
|
|
4332
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
|
+
}
|
|
4333
4366
|
function spawnDaemonDetached(spec) {
|
|
4334
4367
|
const here = path10.dirname(fileURLToPath2(import.meta.url));
|
|
4335
4368
|
const candidates = [
|
|
@@ -4376,6 +4409,72 @@ function spawnDaemonDetached(spec) {
|
|
|
4376
4409
|
if (logFd !== null) fsSync.closeSync(logFd);
|
|
4377
4410
|
return child;
|
|
4378
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
|
+
}
|
|
4379
4478
|
function openBrowser(url) {
|
|
4380
4479
|
if (!process.stdout.isTTY) return "failed";
|
|
4381
4480
|
const platform = process.platform;
|
|
@@ -4409,14 +4508,26 @@ async function runOrchestrator(opts) {
|
|
|
4409
4508
|
result.exitCode = 2;
|
|
4410
4509
|
return result;
|
|
4411
4510
|
}
|
|
4412
|
-
printBanner();
|
|
4511
|
+
if (!opts.skipBanner) printBanner();
|
|
4413
4512
|
console.log(`neat: ${opts.scanPath}`);
|
|
4414
4513
|
console.log("");
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
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
|
+
}
|
|
4420
4531
|
const { graph, services, languages } = persisted;
|
|
4421
4532
|
result.steps.discovery = { services: services.length, languages };
|
|
4422
4533
|
console.log(`discovered ${services.length} service(s) across ${languages.length} language(s)`);
|
|
@@ -4452,24 +4563,11 @@ async function runOrchestrator(opts) {
|
|
|
4452
4563
|
} catch (err) {
|
|
4453
4564
|
if (!(err instanceof ProjectNameCollisionError)) throw err;
|
|
4454
4565
|
console.error(`neat: ${err.message}`);
|
|
4455
|
-
console.error(
|
|
4566
|
+
console.error(`neat: run \`neat <path> --project <other-name>\` to register it under a different name,`);
|
|
4567
|
+
console.error(`neat: or \`neat uninstall ${opts.project}\` if the old registration is stale.`);
|
|
4456
4568
|
result.exitCode = 1;
|
|
4457
4569
|
return result;
|
|
4458
4570
|
}
|
|
4459
|
-
const siblings = await listProjects();
|
|
4460
|
-
const paused = [];
|
|
4461
|
-
for (const p of siblings) {
|
|
4462
|
-
if (p.name !== currentProjectName && p.status === "active") {
|
|
4463
|
-
await setStatus(p.name, "paused");
|
|
4464
|
-
paused.push(p.name);
|
|
4465
|
-
}
|
|
4466
|
-
}
|
|
4467
|
-
if (paused.length > 0) {
|
|
4468
|
-
const plural = paused.length === 1 ? "" : "s";
|
|
4469
|
-
console.log(
|
|
4470
|
-
`neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
|
|
4471
|
-
);
|
|
4472
|
-
}
|
|
4473
4571
|
if (!runApply) {
|
|
4474
4572
|
result.steps.apply.skipped = true;
|
|
4475
4573
|
console.log("skipped instrumentation (--no-instrument)");
|
|
@@ -4485,75 +4583,45 @@ async function runOrchestrator(opts) {
|
|
|
4485
4583
|
}
|
|
4486
4584
|
}
|
|
4487
4585
|
const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
|
|
4488
|
-
const
|
|
4489
|
-
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
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":
|
|
4504
4605
|
for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) {
|
|
4505
4606
|
console.error(line);
|
|
4506
4607
|
}
|
|
4507
4608
|
result.exitCode = 3;
|
|
4508
4609
|
return result;
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
if (reused) {
|
|
4514
|
-
result.steps.daemon = "already-running";
|
|
4515
|
-
} else {
|
|
4516
|
-
console.error("neat: another `neat` is spawning this project but its daemon did not come up in time");
|
|
4517
|
-
result.exitCode = 1;
|
|
4518
|
-
return result;
|
|
4519
|
-
}
|
|
4520
|
-
} else {
|
|
4521
|
-
try {
|
|
4522
|
-
if (await healthIsForProject(allocated.rest, currentProjectName)) {
|
|
4523
|
-
result.steps.daemon = "already-running";
|
|
4524
|
-
} else {
|
|
4525
|
-
spawnDaemonDetached({
|
|
4526
|
-
project: currentProjectName,
|
|
4527
|
-
projectPath: opts.scanPath,
|
|
4528
|
-
ports: allocated
|
|
4529
|
-
});
|
|
4530
|
-
const ready = await waitForDaemonReady(allocated.rest, currentProjectName, timeoutMs);
|
|
4531
|
-
result.steps.daemon = ready.ready ? "spawned" : "timed-out";
|
|
4532
|
-
if (!ready.ready) {
|
|
4533
|
-
console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
|
|
4534
|
-
if (ready.stillBootstrapping.length > 0) {
|
|
4535
|
-
console.error(`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`);
|
|
4536
|
-
}
|
|
4537
|
-
if (ready.brokenProjects.length > 0) {
|
|
4538
|
-
console.error(`neat: broken projects: ${ready.brokenProjects.join(", ")}`);
|
|
4539
|
-
}
|
|
4540
|
-
result.exitCode = 1;
|
|
4541
|
-
return result;
|
|
4542
|
-
}
|
|
4543
|
-
if (ready.brokenProjects.length > 0) {
|
|
4544
|
-
console.warn(
|
|
4545
|
-
`neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
|
|
4546
|
-
);
|
|
4547
|
-
}
|
|
4548
|
-
}
|
|
4549
|
-
} catch (err) {
|
|
4550
|
-
console.error(`neat: daemon spawn failed \u2014 ${err.message}`);
|
|
4551
|
-
result.exitCode = 1;
|
|
4552
|
-
return result;
|
|
4553
|
-
} finally {
|
|
4554
|
-
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);
|
|
4555
4614
|
}
|
|
4556
|
-
|
|
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;
|
|
4557
4625
|
}
|
|
4558
4626
|
const webPort = allocated?.web ?? NEAT_PORTS[2];
|
|
4559
4627
|
const dashboardUrl = opts.dashboardUrl ?? `http://localhost:${webPort}`;
|
|
@@ -5245,6 +5313,196 @@ async function clearActiveProfile(home = neatHome()) {
|
|
|
5245
5313
|
});
|
|
5246
5314
|
}
|
|
5247
5315
|
|
|
5316
|
+
// src/daemon-probe.ts
|
|
5317
|
+
var PROBE_ATTEMPT_TIMEOUT_MS = 3e4;
|
|
5318
|
+
var PROBE_TOTAL_BUDGET_MS = 12e4;
|
|
5319
|
+
var PROBE_RETRY_PAUSE_MS = 2e3;
|
|
5320
|
+
function isTimeoutError(err) {
|
|
5321
|
+
const name = err?.name;
|
|
5322
|
+
return name === "TimeoutError" || name === "AbortError";
|
|
5323
|
+
}
|
|
5324
|
+
async function probeOnce(fetchImpl, root, token, timeoutMs) {
|
|
5325
|
+
let res;
|
|
5326
|
+
try {
|
|
5327
|
+
res = await fetchImpl(`${root}/health`, {
|
|
5328
|
+
headers: { authorization: `Bearer ${token}` },
|
|
5329
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
5330
|
+
});
|
|
5331
|
+
} catch (err) {
|
|
5332
|
+
if (isTimeoutError(err)) return { kind: "timeout" };
|
|
5333
|
+
return { kind: "unreachable", detail: err.message };
|
|
5334
|
+
}
|
|
5335
|
+
if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
|
|
5336
|
+
if (!res.ok) return { kind: "not-neat", status: res.status };
|
|
5337
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
5338
|
+
if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
|
|
5339
|
+
return { kind: "ok" };
|
|
5340
|
+
}
|
|
5341
|
+
async function probeDaemon(fetchImpl, endpoint, token, hooks) {
|
|
5342
|
+
const root = endpoint.replace(/\/$/, "");
|
|
5343
|
+
const deadline = hooks.now() + PROBE_TOTAL_BUDGET_MS;
|
|
5344
|
+
let warned = false;
|
|
5345
|
+
for (; ; ) {
|
|
5346
|
+
const result = await probeOnce(fetchImpl, root, token, PROBE_ATTEMPT_TIMEOUT_MS);
|
|
5347
|
+
if (result.kind !== "timeout") return result;
|
|
5348
|
+
if (!warned) {
|
|
5349
|
+
hooks.onWaiting();
|
|
5350
|
+
warned = true;
|
|
5351
|
+
}
|
|
5352
|
+
if (hooks.now() >= deadline) {
|
|
5353
|
+
return { kind: "unreachable", detail: `no response after ${Math.round(PROBE_TOTAL_BUDGET_MS / 1e3)}s` };
|
|
5354
|
+
}
|
|
5355
|
+
await hooks.sleep(PROBE_RETRY_PAUSE_MS);
|
|
5356
|
+
}
|
|
5357
|
+
}
|
|
5358
|
+
|
|
5359
|
+
// src/login-connect.ts
|
|
5360
|
+
import { execFile } from "child_process";
|
|
5361
|
+
import { promisify } from "util";
|
|
5362
|
+
import { promises as fs11 } from "fs";
|
|
5363
|
+
import path12 from "path";
|
|
5364
|
+
var execFileAsync = promisify(execFile);
|
|
5365
|
+
async function hasLocalSnapshot(cwd) {
|
|
5366
|
+
try {
|
|
5367
|
+
await fs11.access(path12.join(cwd, "neat-out", "graph.json"));
|
|
5368
|
+
return true;
|
|
5369
|
+
} catch {
|
|
5370
|
+
return false;
|
|
5371
|
+
}
|
|
5372
|
+
}
|
|
5373
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
5374
|
+
var POLL_BUDGET_MS = 10 * 6e4;
|
|
5375
|
+
var CP_TIMEOUT_MS = 1e4;
|
|
5376
|
+
async function detectGitHubRepo(cwd) {
|
|
5377
|
+
const read = async (args) => {
|
|
5378
|
+
try {
|
|
5379
|
+
const { stdout } = await execFileAsync("git", args, { cwd, timeout: 5e3 });
|
|
5380
|
+
return stdout.trim();
|
|
5381
|
+
} catch {
|
|
5382
|
+
return null;
|
|
5383
|
+
}
|
|
5384
|
+
};
|
|
5385
|
+
let url = await read(["remote", "get-url", "origin"]);
|
|
5386
|
+
if (!url) {
|
|
5387
|
+
const names = await read(["remote"]);
|
|
5388
|
+
const first = names?.split(/\r?\n/).find((l) => l.trim().length > 0)?.trim();
|
|
5389
|
+
if (!first) return null;
|
|
5390
|
+
url = await read(["remote", "get-url", first]);
|
|
5391
|
+
}
|
|
5392
|
+
if (!url) return null;
|
|
5393
|
+
return parseGitHubRemote(url);
|
|
5394
|
+
}
|
|
5395
|
+
function parseGitHubRemote(url) {
|
|
5396
|
+
const trimmed = url.trim().replace(/\.git$/, "");
|
|
5397
|
+
const m = trimmed.match(/^https?:\/\/(?:[^@/]+@)?github\.com\/([^/]+)\/([^/]+)$/) ?? trimmed.match(/^git@github\.com:([^/]+)\/([^/]+)$/) ?? trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+)$/);
|
|
5398
|
+
if (!m) return null;
|
|
5399
|
+
const owner = m[1];
|
|
5400
|
+
const name = m[2];
|
|
5401
|
+
if (!owner || !name) return null;
|
|
5402
|
+
return { owner, name };
|
|
5403
|
+
}
|
|
5404
|
+
async function cpGetJson(fetchImpl, cpUrl, path22, accessToken) {
|
|
5405
|
+
try {
|
|
5406
|
+
const res = await fetchImpl(`${cpUrl}${path22}`, {
|
|
5407
|
+
headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
|
|
5408
|
+
signal: AbortSignal.timeout(CP_TIMEOUT_MS)
|
|
5409
|
+
});
|
|
5410
|
+
if (!res.ok) return { ok: false };
|
|
5411
|
+
return { ok: true, body: await res.json() };
|
|
5412
|
+
} catch {
|
|
5413
|
+
return { ok: false };
|
|
5414
|
+
}
|
|
5415
|
+
}
|
|
5416
|
+
async function listBoundRepos(fetchImpl, cpUrl, accessToken, projectId) {
|
|
5417
|
+
const res = await cpGetJson(
|
|
5418
|
+
fetchImpl,
|
|
5419
|
+
cpUrl,
|
|
5420
|
+
`/me/projects/${encodeURIComponent(projectId)}/repos`,
|
|
5421
|
+
accessToken
|
|
5422
|
+
);
|
|
5423
|
+
if (!res.ok || !Array.isArray(res.body)) return null;
|
|
5424
|
+
return res.body;
|
|
5425
|
+
}
|
|
5426
|
+
async function waitForFirstProject(fetchImpl, cpUrl, accessToken, deps) {
|
|
5427
|
+
const pollMs = deps.pollMs ?? POLL_INTERVAL_MS;
|
|
5428
|
+
const deadline = deps.now() + (deps.budgetMs ?? POLL_BUDGET_MS);
|
|
5429
|
+
for (; ; ) {
|
|
5430
|
+
if (deps.signal?.aborted) return { kind: "aborted" };
|
|
5431
|
+
const me = await cpGetJson(fetchImpl, cpUrl, "/me", accessToken);
|
|
5432
|
+
if (me.ok) {
|
|
5433
|
+
const running = (me.body.projects ?? []).find((p) => p.status === "running");
|
|
5434
|
+
if (running) return { kind: "project", project: running };
|
|
5435
|
+
}
|
|
5436
|
+
if (deps.now() >= deadline) return { kind: "timeout" };
|
|
5437
|
+
await deps.sleep(pollMs);
|
|
5438
|
+
if (deps.signal?.aborted) return { kind: "aborted" };
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
function bindUrl(webUrl, projectId) {
|
|
5442
|
+
return `${webUrl}/config/repos?project=${encodeURIComponent(projectId)}`;
|
|
5443
|
+
}
|
|
5444
|
+
function onboardingUrl(webUrl) {
|
|
5445
|
+
return `${webUrl}/onboarding`;
|
|
5446
|
+
}
|
|
5447
|
+
async function runPostLoginConnect(input) {
|
|
5448
|
+
const d = input.deps;
|
|
5449
|
+
const out = d.out ?? ((line) => console.log(line));
|
|
5450
|
+
const fetchImpl = d.fetchImpl ?? fetch;
|
|
5451
|
+
const cwd = d.cwd ?? process.cwd();
|
|
5452
|
+
const detect6 = d.detectRepo ?? detectGitHubRepo;
|
|
5453
|
+
const readLine = d.readLine;
|
|
5454
|
+
const repo = await detect6(cwd);
|
|
5455
|
+
out("");
|
|
5456
|
+
if (!repo) {
|
|
5457
|
+
out("This directory has no GitHub remote, so it can't be bound to the project.");
|
|
5458
|
+
out(` Push its graph instead: neat sync --to ${input.project.name}`);
|
|
5459
|
+
out(` Manage projects and repos: ${input.webUrl}`);
|
|
5460
|
+
return;
|
|
5461
|
+
}
|
|
5462
|
+
const slug = `${repo.owner}/${repo.name}`;
|
|
5463
|
+
const bound = await listBoundRepos(fetchImpl, input.cpUrl, input.accessToken, input.project.id);
|
|
5464
|
+
const match = bound?.find((r) => r.owner === repo.owner && r.name === repo.name);
|
|
5465
|
+
if (match) {
|
|
5466
|
+
const state = match.syncStatus ? ` (${match.syncStatus})` : "";
|
|
5467
|
+
out(`${slug} is bound to ${input.project.name}${state} \u2014 hosted NEAT extracts it for you.`);
|
|
5468
|
+
out(` See the graph: ${input.webUrl}`);
|
|
5469
|
+
return;
|
|
5470
|
+
}
|
|
5471
|
+
out(`${slug} isn't part of ${input.project.name} yet \u2014 logging in connected your account, not this repo.`);
|
|
5472
|
+
out("");
|
|
5473
|
+
const installed = input.me?.github?.installed;
|
|
5474
|
+
const bindLine = installed === false ? "1) Install the GitHub App and bind this repo \u2014 hosted NEAT keeps it in sync" : installed === true ? "1) Bind this repo \u2014 hosted NEAT clones and extracts it, and keeps it in sync" : "1) Bind this repo through the console \u2014 hosted NEAT keeps it in sync";
|
|
5475
|
+
out(bindLine);
|
|
5476
|
+
out(`2) Push the graph on this machine now \u2014 a snapshot, not a subscription`);
|
|
5477
|
+
out("3) Neither, for now");
|
|
5478
|
+
const answer = readLine ? (await readLine("Choose 1, 2 or 3 (Enter skips): "))?.trim() : void 0;
|
|
5479
|
+
if (answer === "1") {
|
|
5480
|
+
const url = bindUrl(input.webUrl, input.project.id);
|
|
5481
|
+
const opened = (d.openBrowser ?? (() => false))(url);
|
|
5482
|
+
out("");
|
|
5483
|
+
out(opened ? `Opened ${url}` : `Open this to bind the repo:
|
|
5484
|
+
${url}`);
|
|
5485
|
+
return;
|
|
5486
|
+
}
|
|
5487
|
+
if (answer === "2") {
|
|
5488
|
+
const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
|
|
5489
|
+
out("");
|
|
5490
|
+
if (!hasGraph) {
|
|
5491
|
+
out("There is no local graph to push yet. Build one, then push it:");
|
|
5492
|
+
out(" neat");
|
|
5493
|
+
out(` neat sync --to ${input.project.name}`);
|
|
5494
|
+
return;
|
|
5495
|
+
}
|
|
5496
|
+
out("Push the local graph with:");
|
|
5497
|
+
out(` neat sync --to ${input.project.name}`);
|
|
5498
|
+
return;
|
|
5499
|
+
}
|
|
5500
|
+
out("");
|
|
5501
|
+
out("No problem \u2014 when you want this repo in the graph:");
|
|
5502
|
+
out(` Bind it: ${bindUrl(input.webUrl, input.project.id)}`);
|
|
5503
|
+
out(` Or push it: neat sync --to ${input.project.name}`);
|
|
5504
|
+
}
|
|
5505
|
+
|
|
5248
5506
|
// src/login-sso.ts
|
|
5249
5507
|
var DEFAULT_CP_URL = "https://api.neat.is";
|
|
5250
5508
|
var DEFAULT_WEB_URL = "https://app.neat.is";
|
|
@@ -5270,6 +5528,7 @@ function pickProject(projects, want) {
|
|
|
5270
5528
|
return {
|
|
5271
5529
|
error: {
|
|
5272
5530
|
code: 1,
|
|
5531
|
+
kind: "no-project",
|
|
5273
5532
|
message: `no running project to connect to \u2014 create + provision one in the console first${listed}`
|
|
5274
5533
|
}
|
|
5275
5534
|
};
|
|
@@ -5320,7 +5579,7 @@ async function exchangeCredential(cpUrl, accessToken, opts, deps) {
|
|
|
5320
5579
|
if (!cred.endpoint || !cred.authToken) {
|
|
5321
5580
|
return { error: { code: 1, message: "the credential response was missing endpoint/authToken" } };
|
|
5322
5581
|
}
|
|
5323
|
-
return { project, cred };
|
|
5582
|
+
return { project, cred, me };
|
|
5324
5583
|
}
|
|
5325
5584
|
function defaultOpenBrowser(url) {
|
|
5326
5585
|
const platform = process.platform;
|
|
@@ -5402,12 +5661,50 @@ async function runSsoLogin(opts, deps) {
|
|
|
5402
5661
|
}
|
|
5403
5662
|
accessToken = lb.token;
|
|
5404
5663
|
}
|
|
5405
|
-
const
|
|
5664
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
5665
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
5666
|
+
const now = deps.now ?? Date.now;
|
|
5667
|
+
let ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
|
|
5668
|
+
if ("error" in ex && ex.error.kind === "no-project" && !opts.json) {
|
|
5669
|
+
const url = onboardingUrl(opts.webUrl);
|
|
5670
|
+
out("");
|
|
5671
|
+
out("You don't have a project yet \u2014 let's make one.");
|
|
5672
|
+
const opened = (deps.openBrowser ?? defaultOpenBrowser)(url);
|
|
5673
|
+
out(opened ? `Opened ${url}` : `Open this to create your first project:
|
|
5674
|
+
${url}`);
|
|
5675
|
+
out("Waiting for it to come up \u2014 Ctrl-C to stop and finish later.");
|
|
5676
|
+
const waited = await waitForFirstProject(fetchImpl, opts.cpUrl, accessToken, {
|
|
5677
|
+
sleep: sleep2,
|
|
5678
|
+
now,
|
|
5679
|
+
...deps.signal ? { signal: deps.signal } : {}
|
|
5680
|
+
});
|
|
5681
|
+
if (waited.kind === "aborted") {
|
|
5682
|
+
out("Stopped. Run `neat login` again once your project is up.");
|
|
5683
|
+
return 130;
|
|
5684
|
+
}
|
|
5685
|
+
if (waited.kind === "timeout") {
|
|
5686
|
+
err("neat login: no project came up while waiting \u2014 run `neat login` again once it does");
|
|
5687
|
+
return 1;
|
|
5688
|
+
}
|
|
5689
|
+
out(`${waited.project.name} is up.`);
|
|
5690
|
+
ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
|
|
5691
|
+
}
|
|
5406
5692
|
if ("error" in ex) {
|
|
5407
5693
|
err(`neat login: ${ex.error.message}`);
|
|
5408
5694
|
return ex.error.code;
|
|
5409
5695
|
}
|
|
5410
|
-
const { project, cred } = ex;
|
|
5696
|
+
const { project, cred, me } = ex;
|
|
5697
|
+
const probe = await probeDaemon(fetchImpl, cred.endpoint, cred.authToken, {
|
|
5698
|
+
sleep: sleep2,
|
|
5699
|
+
now,
|
|
5700
|
+
onWaiting: () => err("Waking the hosted daemon \u2014 a cold instance can take up to a minute\u2026")
|
|
5701
|
+
});
|
|
5702
|
+
if (probe.kind !== "ok") {
|
|
5703
|
+
const why = probe.kind === "unreachable" ? probe.detail : probe.kind === "unauthorized" ? `it rejected the credential (HTTP ${probe.status})` : `it answered HTTP ${probe.status} and doesn't look like NEAT`;
|
|
5704
|
+
err(`neat login: "${project.name}" is provisioned, but its daemon isn't answering \u2014 ${why}.`);
|
|
5705
|
+
err(`Nothing was changed. Check the project at ${opts.webUrl} and run \`neat login\` again.`);
|
|
5706
|
+
return probe.kind === "unreachable" ? 3 : 1;
|
|
5707
|
+
}
|
|
5411
5708
|
await upsertProfile(
|
|
5412
5709
|
{ name: opts.name, endpoint: cred.endpoint, authToken: cred.authToken },
|
|
5413
5710
|
{ makeActive: true, ...deps.home ? { home: deps.home } : {} }
|
|
@@ -5435,10 +5732,20 @@ async function runSsoLogin(opts, deps) {
|
|
|
5435
5732
|
out(` OTEL_EXPORTER_OTLP_ENDPOINT=${cred.ingestEndpoint}`);
|
|
5436
5733
|
out(` OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${cred.otelToken}`);
|
|
5437
5734
|
}
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5735
|
+
await runPostLoginConnect({
|
|
5736
|
+
cpUrl: opts.cpUrl,
|
|
5737
|
+
webUrl: opts.webUrl,
|
|
5738
|
+
accessToken,
|
|
5739
|
+
project: { id: project.id, name: project.name },
|
|
5740
|
+
me,
|
|
5741
|
+
deps: {
|
|
5742
|
+
fetchImpl,
|
|
5743
|
+
out,
|
|
5744
|
+
...deps.readLine ? { readLine: deps.readLine } : {},
|
|
5745
|
+
...deps.openBrowser ? { openBrowser: deps.openBrowser } : { openBrowser: defaultOpenBrowser },
|
|
5746
|
+
...deps.connect ?? {}
|
|
5747
|
+
}
|
|
5748
|
+
});
|
|
5442
5749
|
out("");
|
|
5443
5750
|
out("Run `neat logout` to switch back to your local daemon.");
|
|
5444
5751
|
}
|
|
@@ -5545,8 +5852,8 @@ async function runConnectCommand(rawArgs, deps = {}) {
|
|
|
5545
5852
|
}
|
|
5546
5853
|
|
|
5547
5854
|
// src/doctor-cli.ts
|
|
5548
|
-
import { promises as
|
|
5549
|
-
import
|
|
5855
|
+
import { promises as fs12 } from "fs";
|
|
5856
|
+
import path13 from "path";
|
|
5550
5857
|
|
|
5551
5858
|
// src/cli-client.ts
|
|
5552
5859
|
import { Provenance as Provenance3 } from "@neat.is/types";
|
|
@@ -5574,10 +5881,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5574
5881
|
const root = baseUrl.replace(/\/$/, "");
|
|
5575
5882
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
5576
5883
|
return {
|
|
5577
|
-
async get(
|
|
5884
|
+
async get(path22) {
|
|
5578
5885
|
let res;
|
|
5579
5886
|
try {
|
|
5580
|
-
res = await fetchImpl(`${root}${
|
|
5887
|
+
res = await fetchImpl(`${root}${path22}`, {
|
|
5581
5888
|
headers: { ...authHeader }
|
|
5582
5889
|
});
|
|
5583
5890
|
} catch (err) {
|
|
@@ -5589,16 +5896,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5589
5896
|
const body = await res.text().catch(() => "");
|
|
5590
5897
|
throw new HttpError(
|
|
5591
5898
|
res.status,
|
|
5592
|
-
`${res.status} ${res.statusText} on GET ${
|
|
5899
|
+
`${res.status} ${res.statusText} on GET ${path22}: ${body}`,
|
|
5593
5900
|
body
|
|
5594
5901
|
);
|
|
5595
5902
|
}
|
|
5596
5903
|
return await res.json();
|
|
5597
5904
|
},
|
|
5598
|
-
async post(
|
|
5905
|
+
async post(path22, body) {
|
|
5599
5906
|
let res;
|
|
5600
5907
|
try {
|
|
5601
|
-
res = await fetchImpl(`${root}${
|
|
5908
|
+
res = await fetchImpl(`${root}${path22}`, {
|
|
5602
5909
|
method: "POST",
|
|
5603
5910
|
headers: { "content-type": "application/json", ...authHeader },
|
|
5604
5911
|
body: JSON.stringify(body)
|
|
@@ -5612,7 +5919,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
5612
5919
|
const text = await res.text().catch(() => "");
|
|
5613
5920
|
throw new HttpError(
|
|
5614
5921
|
res.status,
|
|
5615
|
-
`${res.status} ${res.statusText} on POST ${
|
|
5922
|
+
`${res.status} ${res.statusText} on POST ${path22}: ${text}`,
|
|
5616
5923
|
text
|
|
5617
5924
|
);
|
|
5618
5925
|
}
|
|
@@ -5626,12 +5933,12 @@ function projectPath(project, suffix) {
|
|
|
5626
5933
|
}
|
|
5627
5934
|
async function runRootCause(client, input) {
|
|
5628
5935
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
5629
|
-
const
|
|
5936
|
+
const path22 = projectPath(
|
|
5630
5937
|
input.project,
|
|
5631
5938
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
5632
5939
|
);
|
|
5633
5940
|
try {
|
|
5634
|
-
const result = await client.get(
|
|
5941
|
+
const result = await client.get(path22);
|
|
5635
5942
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
5636
5943
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
5637
5944
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -5657,12 +5964,12 @@ async function runRootCause(client, input) {
|
|
|
5657
5964
|
}
|
|
5658
5965
|
async function runBlastRadius(client, input) {
|
|
5659
5966
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
5660
|
-
const
|
|
5967
|
+
const path22 = projectPath(
|
|
5661
5968
|
input.project,
|
|
5662
5969
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
5663
5970
|
);
|
|
5664
5971
|
try {
|
|
5665
|
-
const result = await client.get(
|
|
5972
|
+
const result = await client.get(path22);
|
|
5666
5973
|
if (result.totalAffected === 0) {
|
|
5667
5974
|
return {
|
|
5668
5975
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -5696,12 +6003,12 @@ function formatBlastEntry(n) {
|
|
|
5696
6003
|
}
|
|
5697
6004
|
async function runDependencies(client, input) {
|
|
5698
6005
|
const depth = input.depth ?? 3;
|
|
5699
|
-
const
|
|
6006
|
+
const path22 = projectPath(
|
|
5700
6007
|
input.project,
|
|
5701
6008
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
5702
6009
|
);
|
|
5703
6010
|
try {
|
|
5704
|
-
const result = await client.get(
|
|
6011
|
+
const result = await client.get(path22);
|
|
5705
6012
|
if (result.total === 0) {
|
|
5706
6013
|
return {
|
|
5707
6014
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -5793,9 +6100,9 @@ function formatDuration(ms) {
|
|
|
5793
6100
|
return `${Math.round(h / 24)}d`;
|
|
5794
6101
|
}
|
|
5795
6102
|
async function runIncidents(client, input) {
|
|
5796
|
-
const
|
|
6103
|
+
const path22 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
5797
6104
|
try {
|
|
5798
|
-
const body = await client.get(
|
|
6105
|
+
const body = await client.get(path22);
|
|
5799
6106
|
const events = body.events;
|
|
5800
6107
|
if (events.length === 0) {
|
|
5801
6108
|
return {
|
|
@@ -6159,7 +6466,7 @@ function checkNode(nodeVersion) {
|
|
|
6159
6466
|
}
|
|
6160
6467
|
async function neatOutExists(cwd) {
|
|
6161
6468
|
try {
|
|
6162
|
-
const st = await
|
|
6469
|
+
const st = await fs12.stat(path13.join(cwd, "neat-out"));
|
|
6163
6470
|
return st.isDirectory();
|
|
6164
6471
|
} catch {
|
|
6165
6472
|
return false;
|
|
@@ -6276,11 +6583,118 @@ async function runDoctorCommand(argv, deps = {}) {
|
|
|
6276
6583
|
return checks.every((c) => c.ok) ? 0 : 1;
|
|
6277
6584
|
}
|
|
6278
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
|
+
|
|
6279
6696
|
// src/login-cli.ts
|
|
6280
6697
|
import readline3 from "readline/promises";
|
|
6281
|
-
var PROBE_ATTEMPT_TIMEOUT_MS = 3e4;
|
|
6282
|
-
var PROBE_TOTAL_BUDGET_MS = 12e4;
|
|
6283
|
-
var PROBE_RETRY_PAUSE_MS = 2e3;
|
|
6284
6698
|
var DEFAULT_PROFILE_NAME = "hosted";
|
|
6285
6699
|
function readFlagValue(argv, i) {
|
|
6286
6700
|
const arg = argv[i];
|
|
@@ -6345,44 +6759,6 @@ function printLoginHelp(out) {
|
|
|
6345
6759
|
out(' the profile (default "hosted"); the paste token can also come from NEAT_LOGIN_TOKEN.');
|
|
6346
6760
|
out(" Exit 0 on success, 1 rejected/needs-action, 2 misuse, 3 unreachable.");
|
|
6347
6761
|
}
|
|
6348
|
-
function isTimeoutError(err) {
|
|
6349
|
-
const name = err?.name;
|
|
6350
|
-
return name === "TimeoutError" || name === "AbortError";
|
|
6351
|
-
}
|
|
6352
|
-
async function probeOnce(fetchImpl, root, token, timeoutMs) {
|
|
6353
|
-
let res;
|
|
6354
|
-
try {
|
|
6355
|
-
res = await fetchImpl(`${root}/health`, {
|
|
6356
|
-
headers: { authorization: `Bearer ${token}` },
|
|
6357
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
6358
|
-
});
|
|
6359
|
-
} catch (err) {
|
|
6360
|
-
if (isTimeoutError(err)) return { kind: "timeout" };
|
|
6361
|
-
return { kind: "unreachable", detail: err.message };
|
|
6362
|
-
}
|
|
6363
|
-
if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
|
|
6364
|
-
if (!res.ok) return { kind: "not-neat", status: res.status };
|
|
6365
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
6366
|
-
if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
|
|
6367
|
-
return { kind: "ok" };
|
|
6368
|
-
}
|
|
6369
|
-
async function probeDaemon(fetchImpl, endpoint, token, hooks) {
|
|
6370
|
-
const root = endpoint.replace(/\/$/, "");
|
|
6371
|
-
const deadline = hooks.now() + PROBE_TOTAL_BUDGET_MS;
|
|
6372
|
-
let warned = false;
|
|
6373
|
-
for (; ; ) {
|
|
6374
|
-
const result = await probeOnce(fetchImpl, root, token, PROBE_ATTEMPT_TIMEOUT_MS);
|
|
6375
|
-
if (result.kind !== "timeout") return result;
|
|
6376
|
-
if (!warned) {
|
|
6377
|
-
hooks.onWaiting();
|
|
6378
|
-
warned = true;
|
|
6379
|
-
}
|
|
6380
|
-
if (hooks.now() >= deadline) {
|
|
6381
|
-
return { kind: "unreachable", detail: `no response after ${Math.round(PROBE_TOTAL_BUDGET_MS / 1e3)}s` };
|
|
6382
|
-
}
|
|
6383
|
-
await hooks.sleep(PROBE_RETRY_PAUSE_MS);
|
|
6384
|
-
}
|
|
6385
|
-
}
|
|
6386
6762
|
async function runLoginCommand(argv, deps = {}) {
|
|
6387
6763
|
const out = deps.out ?? ((line) => console.log(line));
|
|
6388
6764
|
const err = deps.err ?? ((line) => console.error(line));
|
|
@@ -6415,8 +6791,13 @@ async function runLoginCommand(argv, deps = {}) {
|
|
|
6415
6791
|
fetchImpl,
|
|
6416
6792
|
out,
|
|
6417
6793
|
err,
|
|
6794
|
+
readLine,
|
|
6795
|
+
sleep: sleep2,
|
|
6796
|
+
now,
|
|
6418
6797
|
...deps.openBrowser ? { openBrowser: deps.openBrowser } : {},
|
|
6419
|
-
...deps.home ? { home: deps.home } : {}
|
|
6798
|
+
...deps.home ? { home: deps.home } : {},
|
|
6799
|
+
...deps.signal ? { signal: deps.signal } : {},
|
|
6800
|
+
...deps.connect ? { connect: deps.connect } : {}
|
|
6420
6801
|
}
|
|
6421
6802
|
);
|
|
6422
6803
|
}
|
|
@@ -6572,8 +6953,8 @@ async function defaultReadSecret(prompt) {
|
|
|
6572
6953
|
}
|
|
6573
6954
|
|
|
6574
6955
|
// src/welcome.ts
|
|
6575
|
-
import { promises as
|
|
6576
|
-
import
|
|
6956
|
+
import { promises as fs13 } from "fs";
|
|
6957
|
+
import path15 from "path";
|
|
6577
6958
|
import readline4 from "readline/promises";
|
|
6578
6959
|
var WORDMARK = [
|
|
6579
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",
|
|
@@ -6632,15 +7013,17 @@ async function runFrontDoor(deps, out) {
|
|
|
6632
7013
|
const login = deps.login ?? (() => Promise.resolve(0));
|
|
6633
7014
|
const orchestrator = deps.orchestrator ?? (() => Promise.resolve(0));
|
|
6634
7015
|
const cwd = deps.cwd ?? process.cwd();
|
|
7016
|
+
const flagGiven = deps.instrumentFlagGiven ?? false;
|
|
7017
|
+
const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
|
|
6635
7018
|
printHeader(out);
|
|
6636
7019
|
out("Welcome to NEAT. Let's get you a graph of this system.");
|
|
6637
7020
|
out("");
|
|
6638
7021
|
const keyReader = deps.readKey !== void 0 ? { read: deps.readKey, done: () => {
|
|
6639
7022
|
} } : createKeyReader();
|
|
6640
7023
|
const picked = keyReader !== null ? await selectOption(out, keyReader, deps.moveCursorUp ?? defaultMoveCursorUp) : await selectByNumber(out, readLine);
|
|
6641
|
-
if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd);
|
|
7024
|
+
if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
|
|
6642
7025
|
if (picked === 0) return login(["--browser"]);
|
|
6643
|
-
return runSelfHosted(out, readLine, orchestrator, cwd);
|
|
7026
|
+
return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
|
|
6644
7027
|
}
|
|
6645
7028
|
var MENU_OPTIONS = [
|
|
6646
7029
|
"Log me into Hosted Neat",
|
|
@@ -6743,7 +7126,7 @@ function decodeKey(buf) {
|
|
|
6743
7126
|
function defaultMoveCursorUp(rows) {
|
|
6744
7127
|
if (rows > 0) process.stdout.write(`\x1B[${rows}A`);
|
|
6745
7128
|
}
|
|
6746
|
-
async function runSelfHosted(out, readLine, orchestrator, cwd) {
|
|
7129
|
+
async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, instrumentFlagGiven) {
|
|
6747
7130
|
const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
|
|
6748
7131
|
if (wantsPrompt !== "n" && wantsPrompt !== "no") {
|
|
6749
7132
|
out("");
|
|
@@ -6754,9 +7137,70 @@ async function runSelfHosted(out, readLine, orchestrator, cwd) {
|
|
|
6754
7137
|
out("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
6755
7138
|
out("");
|
|
6756
7139
|
}
|
|
7140
|
+
const project = await resolveProjectName(out, readLine, cwd, readRegistry2);
|
|
7141
|
+
const declined = instrumentFlagGiven ? void 0 : await askToInstrument(out, readLine);
|
|
6757
7142
|
out("Building your local graph now\u2026");
|
|
6758
7143
|
out("");
|
|
6759
|
-
|
|
7144
|
+
const overrides = {
|
|
7145
|
+
headerShown: true,
|
|
7146
|
+
...project !== void 0 ? { project } : {},
|
|
7147
|
+
...declined === void 0 ? {} : declined ? { noInstrument: true } : { yes: true }
|
|
7148
|
+
};
|
|
7149
|
+
return orchestrator(cwd, overrides);
|
|
7150
|
+
}
|
|
7151
|
+
async function askToInstrument(out, readLine) {
|
|
7152
|
+
out("Instrument the services for OpenTelemetry now?");
|
|
7153
|
+
out("This edits package.json / requirements.txt / go.mod and runs the package manager.");
|
|
7154
|
+
const answer = (await readLine("[Y/n]: "))?.trim().toLowerCase();
|
|
7155
|
+
const declined = answer === "n" || answer === "no";
|
|
7156
|
+
if (declined) {
|
|
7157
|
+
out("");
|
|
7158
|
+
out("Skipping instrumentation \u2014 the graph will hold the declared side only.");
|
|
7159
|
+
out("Run `neat init . --apply` when you want the runtime half.");
|
|
7160
|
+
}
|
|
7161
|
+
out("");
|
|
7162
|
+
return declined;
|
|
7163
|
+
}
|
|
7164
|
+
async function resolveProjectName(out, readLine, cwd, readRegistry2) {
|
|
7165
|
+
const here = path15.resolve(cwd);
|
|
7166
|
+
const base = path15.basename(here);
|
|
7167
|
+
let taken;
|
|
7168
|
+
try {
|
|
7169
|
+
const { projects } = await readRegistry2();
|
|
7170
|
+
taken = new Map(projects.map((p) => [p.name, p.path]));
|
|
7171
|
+
} catch {
|
|
7172
|
+
return void 0;
|
|
7173
|
+
}
|
|
7174
|
+
const clash = taken.get(base);
|
|
7175
|
+
if (clash === void 0 || path15.resolve(clash) === here) return void 0;
|
|
7176
|
+
out("");
|
|
7177
|
+
out(`A project named \`${base}\` is already registered (${clash}).`);
|
|
7178
|
+
const suggested = firstFreeName(base, taken);
|
|
7179
|
+
for (; ; ) {
|
|
7180
|
+
const answer = (await readLine(`Name this one: [${suggested}] `))?.trim();
|
|
7181
|
+
if (answer === void 0) return suggested;
|
|
7182
|
+
const chosen = answer.length === 0 ? suggested : answer;
|
|
7183
|
+
if (!isUsableProjectName(chosen)) {
|
|
7184
|
+
out(`"${chosen}" won't work as a project name \u2014 letters, digits, dot, dash and underscore.`);
|
|
7185
|
+
continue;
|
|
7186
|
+
}
|
|
7187
|
+
const other = taken.get(chosen);
|
|
7188
|
+
if (other !== void 0 && path15.resolve(other) !== here) {
|
|
7189
|
+
out(`\`${chosen}\` is registered too (${other}).`);
|
|
7190
|
+
continue;
|
|
7191
|
+
}
|
|
7192
|
+
out("");
|
|
7193
|
+
return chosen;
|
|
7194
|
+
}
|
|
7195
|
+
}
|
|
7196
|
+
function firstFreeName(base, taken) {
|
|
7197
|
+
for (let n = 2; ; n++) {
|
|
7198
|
+
const candidate = `${base}-${n}`;
|
|
7199
|
+
if (!taken.has(candidate)) return candidate;
|
|
7200
|
+
}
|
|
7201
|
+
}
|
|
7202
|
+
function isUsableProjectName(name) {
|
|
7203
|
+
return name.length > 0 && name !== "." && name !== ".." && /^[A-Za-z0-9._-]+$/.test(name);
|
|
6760
7204
|
}
|
|
6761
7205
|
async function shouldShowWelcome(deps = {}) {
|
|
6762
7206
|
const stdinTTY = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
@@ -6766,9 +7210,9 @@ async function shouldShowWelcome(deps = {}) {
|
|
|
6766
7210
|
const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
|
|
6767
7211
|
const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
|
|
6768
7212
|
try {
|
|
6769
|
-
const here =
|
|
7213
|
+
const here = path15.resolve(cwd);
|
|
6770
7214
|
const { projects } = await readRegistry2();
|
|
6771
|
-
if (projects.some((p) =>
|
|
7215
|
+
if (projects.some((p) => path15.resolve(p.path) === here)) return false;
|
|
6772
7216
|
if (await hasSnapshot(here)) return false;
|
|
6773
7217
|
return true;
|
|
6774
7218
|
} catch {
|
|
@@ -6777,7 +7221,7 @@ async function shouldShowWelcome(deps = {}) {
|
|
|
6777
7221
|
}
|
|
6778
7222
|
async function defaultHasSnapshot(cwd) {
|
|
6779
7223
|
try {
|
|
6780
|
-
const entries = await
|
|
7224
|
+
const entries = await fs13.readdir(path15.join(cwd, "neat-out"));
|
|
6781
7225
|
return entries.some((e) => e.endsWith(".json"));
|
|
6782
7226
|
} catch {
|
|
6783
7227
|
return false;
|
|
@@ -6794,27 +7238,27 @@ async function defaultReadLine2(prompt) {
|
|
|
6794
7238
|
}
|
|
6795
7239
|
|
|
6796
7240
|
// src/hooks-cli.ts
|
|
6797
|
-
import
|
|
7241
|
+
import path16 from "path";
|
|
6798
7242
|
import os2 from "os";
|
|
6799
|
-
import { promises as
|
|
7243
|
+
import { promises as fs14 } from "fs";
|
|
6800
7244
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
6801
7245
|
var HOOK_FILENAME = "neat-search-nudge.mjs";
|
|
6802
7246
|
var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
6803
7247
|
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
6804
7248
|
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
6805
7249
|
function moduleDir() {
|
|
6806
|
-
return typeof __dirname !== "undefined" ? __dirname :
|
|
7250
|
+
return typeof __dirname !== "undefined" ? __dirname : path16.dirname(fileURLToPath3(import.meta.url));
|
|
6807
7251
|
}
|
|
6808
7252
|
async function readSkillAsset(rel) {
|
|
6809
7253
|
const here = moduleDir();
|
|
6810
7254
|
const candidates = [
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
7255
|
+
path16.resolve(here, "../../claude-skill", rel),
|
|
7256
|
+
path16.resolve(here, "../../../claude-skill", rel),
|
|
7257
|
+
path16.resolve(here, "../claude-skill", rel)
|
|
6814
7258
|
];
|
|
6815
7259
|
for (const candidate of candidates) {
|
|
6816
7260
|
try {
|
|
6817
|
-
return await
|
|
7261
|
+
return await fs14.readFile(candidate, "utf8");
|
|
6818
7262
|
} catch {
|
|
6819
7263
|
}
|
|
6820
7264
|
}
|
|
@@ -6824,20 +7268,20 @@ async function readSkillAsset(rel) {
|
|
|
6824
7268
|
}
|
|
6825
7269
|
function neatHome2() {
|
|
6826
7270
|
const override = process.env.NEAT_HOME;
|
|
6827
|
-
if (override && override.length > 0) return
|
|
6828
|
-
return
|
|
7271
|
+
if (override && override.length > 0) return path16.resolve(override);
|
|
7272
|
+
return path16.join(os2.homedir(), ".neat");
|
|
6829
7273
|
}
|
|
6830
7274
|
function claudeSettingsPath() {
|
|
6831
7275
|
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
6832
|
-
if (override && override.length > 0) return
|
|
7276
|
+
if (override && override.length > 0) return path16.resolve(override);
|
|
6833
7277
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? os2.homedir();
|
|
6834
|
-
return
|
|
7278
|
+
return path16.join(home, ".claude", "settings.json");
|
|
6835
7279
|
}
|
|
6836
7280
|
function installedHookPath() {
|
|
6837
|
-
return
|
|
7281
|
+
return path16.join(neatHome2(), "hooks", HOOK_FILENAME);
|
|
6838
7282
|
}
|
|
6839
7283
|
function gateFlagPath() {
|
|
6840
|
-
return
|
|
7284
|
+
return path16.join(neatHome2(), "hooks", "gate-enabled");
|
|
6841
7285
|
}
|
|
6842
7286
|
function isNeatSearchEntry(entry2) {
|
|
6843
7287
|
return (entry2.hooks ?? []).some(
|
|
@@ -6870,14 +7314,14 @@ async function runHooks(opts) {
|
|
|
6870
7314
|
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
6871
7315
|
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
6872
7316
|
const scriptPath = installedHookPath();
|
|
6873
|
-
await
|
|
6874
|
-
await
|
|
6875
|
-
const guidePath =
|
|
6876
|
-
await
|
|
7317
|
+
await fs14.mkdir(path16.dirname(scriptPath), { recursive: true });
|
|
7318
|
+
await fs14.writeFile(scriptPath, hookScript, { mode: 493 });
|
|
7319
|
+
const guidePath = path16.join(neatHome2(), GUIDE_INSTALL_NAME);
|
|
7320
|
+
await fs14.writeFile(guidePath, guide, "utf8");
|
|
6877
7321
|
const settingsFile = claudeSettingsPath();
|
|
6878
7322
|
let settings = {};
|
|
6879
7323
|
try {
|
|
6880
|
-
settings = JSON.parse(await
|
|
7324
|
+
settings = JSON.parse(await fs14.readFile(settingsFile, "utf8"));
|
|
6881
7325
|
} catch (err) {
|
|
6882
7326
|
if (err.code !== "ENOENT") {
|
|
6883
7327
|
console.error(
|
|
@@ -6899,14 +7343,14 @@ async function runHooks(opts) {
|
|
|
6899
7343
|
...settings,
|
|
6900
7344
|
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
6901
7345
|
};
|
|
6902
|
-
await
|
|
6903
|
-
await
|
|
7346
|
+
await fs14.mkdir(path16.dirname(settingsFile), { recursive: true });
|
|
7347
|
+
await fs14.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
6904
7348
|
const flag = gateFlagPath();
|
|
6905
7349
|
if (opts.gate) {
|
|
6906
|
-
await
|
|
6907
|
-
await
|
|
7350
|
+
await fs14.mkdir(path16.dirname(flag), { recursive: true });
|
|
7351
|
+
await fs14.writeFile(flag, "1\n", "utf8");
|
|
6908
7352
|
} else {
|
|
6909
|
-
await
|
|
7353
|
+
await fs14.rm(flag, { force: true });
|
|
6910
7354
|
}
|
|
6911
7355
|
const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
|
|
6912
7356
|
console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
|
|
@@ -6995,8 +7439,8 @@ async function runHooksCommand(args) {
|
|
|
6995
7439
|
}
|
|
6996
7440
|
|
|
6997
7441
|
// src/claude-cli.ts
|
|
6998
|
-
import
|
|
6999
|
-
import { promises as
|
|
7442
|
+
import path17 from "path";
|
|
7443
|
+
import { promises as fs15 } from "fs";
|
|
7000
7444
|
var NEAT_SECTION_HEADING = "## neat";
|
|
7001
7445
|
var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
|
|
7002
7446
|
code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
|
|
@@ -7030,8 +7474,8 @@ ${NEAT_DIRECTIVE_BODY}
|
|
|
7030
7474
|
}
|
|
7031
7475
|
function claudeMdPath() {
|
|
7032
7476
|
const override = process.env.NEAT_CLAUDE_MD;
|
|
7033
|
-
if (override && override.length > 0) return
|
|
7034
|
-
return
|
|
7477
|
+
if (override && override.length > 0) return path17.resolve(override);
|
|
7478
|
+
return path17.join(process.cwd(), "CLAUDE.md");
|
|
7035
7479
|
}
|
|
7036
7480
|
function splitAroundSection(raw) {
|
|
7037
7481
|
const lines = raw.split("\n");
|
|
@@ -7059,7 +7503,7 @@ function compose(before, after) {
|
|
|
7059
7503
|
}
|
|
7060
7504
|
async function readIfExists(file) {
|
|
7061
7505
|
try {
|
|
7062
|
-
return await
|
|
7506
|
+
return await fs15.readFile(file, "utf8");
|
|
7063
7507
|
} catch (err) {
|
|
7064
7508
|
if (err.code === "ENOENT") return null;
|
|
7065
7509
|
throw err;
|
|
@@ -7070,8 +7514,8 @@ async function runInstall() {
|
|
|
7070
7514
|
const raw = await readIfExists(file) ?? "";
|
|
7071
7515
|
const { before, after, found } = splitAroundSection(raw);
|
|
7072
7516
|
const next = compose(before, after);
|
|
7073
|
-
await
|
|
7074
|
-
await
|
|
7517
|
+
await fs15.mkdir(path17.dirname(file), { recursive: true });
|
|
7518
|
+
await fs15.writeFile(file, next, "utf8");
|
|
7075
7519
|
const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
|
|
7076
7520
|
console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
|
|
7077
7521
|
console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
|
|
@@ -7092,7 +7536,7 @@ async function runUninstall() {
|
|
|
7092
7536
|
}
|
|
7093
7537
|
const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
|
|
7094
7538
|
const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
|
|
7095
|
-
await
|
|
7539
|
+
await fs15.writeFile(file, next, "utf8");
|
|
7096
7540
|
console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
|
|
7097
7541
|
return { exitCode: 0 };
|
|
7098
7542
|
}
|
|
@@ -7134,9 +7578,9 @@ async function runClaudeCommand(args) {
|
|
|
7134
7578
|
}
|
|
7135
7579
|
|
|
7136
7580
|
// src/codex-cli.ts
|
|
7137
|
-
import
|
|
7581
|
+
import path18 from "path";
|
|
7138
7582
|
import os3 from "os";
|
|
7139
|
-
import { promises as
|
|
7583
|
+
import { promises as fs16 } from "fs";
|
|
7140
7584
|
import { isDeepStrictEqual } from "util";
|
|
7141
7585
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
7142
7586
|
var CODEX_MCP_SERVER = {
|
|
@@ -7154,14 +7598,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
7154
7598
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
7155
7599
|
function codexConfigPath() {
|
|
7156
7600
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
7157
|
-
if (override && override.length > 0) return
|
|
7601
|
+
if (override && override.length > 0) return path18.resolve(override);
|
|
7158
7602
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? os3.homedir();
|
|
7159
|
-
return
|
|
7603
|
+
return path18.join(home, ".codex", "config.toml");
|
|
7160
7604
|
}
|
|
7161
7605
|
function agentsFilePath() {
|
|
7162
7606
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
7163
|
-
if (override && override.length > 0) return
|
|
7164
|
-
return
|
|
7607
|
+
if (override && override.length > 0) return path18.resolve(override);
|
|
7608
|
+
return path18.join(process.cwd(), "AGENTS.md");
|
|
7165
7609
|
}
|
|
7166
7610
|
function isTableHeader(line) {
|
|
7167
7611
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -7295,7 +7739,7 @@ async function runCodex(opts) {
|
|
|
7295
7739
|
const agentsPath = agentsFilePath();
|
|
7296
7740
|
let configRaw = "";
|
|
7297
7741
|
try {
|
|
7298
|
-
configRaw = await
|
|
7742
|
+
configRaw = await fs16.readFile(configPath, "utf8");
|
|
7299
7743
|
} catch (err) {
|
|
7300
7744
|
if (err.code !== "ENOENT") {
|
|
7301
7745
|
console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
|
|
@@ -7304,7 +7748,7 @@ async function runCodex(opts) {
|
|
|
7304
7748
|
}
|
|
7305
7749
|
let agentsRaw = "";
|
|
7306
7750
|
try {
|
|
7307
|
-
agentsRaw = await
|
|
7751
|
+
agentsRaw = await fs16.readFile(agentsPath, "utf8");
|
|
7308
7752
|
} catch (err) {
|
|
7309
7753
|
if (err.code !== "ENOENT") {
|
|
7310
7754
|
console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
|
|
@@ -7344,15 +7788,15 @@ async function runCodex(opts) {
|
|
|
7344
7788
|
return { exitCode: 0 };
|
|
7345
7789
|
}
|
|
7346
7790
|
if (config.changed) {
|
|
7347
|
-
await
|
|
7348
|
-
await
|
|
7791
|
+
await fs16.mkdir(path18.dirname(configPath), { recursive: true });
|
|
7792
|
+
await fs16.writeFile(configPath, config.text, "utf8");
|
|
7349
7793
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
7350
7794
|
} else {
|
|
7351
7795
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
7352
7796
|
}
|
|
7353
7797
|
if (agents.changed) {
|
|
7354
|
-
await
|
|
7355
|
-
await
|
|
7798
|
+
await fs16.mkdir(path18.dirname(agentsPath), { recursive: true });
|
|
7799
|
+
await fs16.writeFile(agentsPath, agents.text, "utf8");
|
|
7356
7800
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
7357
7801
|
} else {
|
|
7358
7802
|
console.log(`neat codex: ${agentsPath} already has the graph-first block`);
|
|
@@ -7408,9 +7852,9 @@ async function runCodexCommand(args) {
|
|
|
7408
7852
|
}
|
|
7409
7853
|
|
|
7410
7854
|
// src/editors-cli.ts
|
|
7411
|
-
import
|
|
7855
|
+
import path19 from "path";
|
|
7412
7856
|
import os4 from "os";
|
|
7413
|
-
import { promises as
|
|
7857
|
+
import { promises as fs17 } from "fs";
|
|
7414
7858
|
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
7415
7859
|
import * as jsonc from "jsonc-parser";
|
|
7416
7860
|
var NEAT_MCP_SERVER = {
|
|
@@ -7434,17 +7878,17 @@ function homeDir() {
|
|
|
7434
7878
|
}
|
|
7435
7879
|
function xdgConfigDir() {
|
|
7436
7880
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
7437
|
-
return xdg && xdg.length > 0 ?
|
|
7881
|
+
return xdg && xdg.length > 0 ? path19.resolve(xdg) : path19.join(homeDir(), ".config");
|
|
7438
7882
|
}
|
|
7439
7883
|
function envOverride(name) {
|
|
7440
7884
|
const v = process.env[name];
|
|
7441
|
-
return v && v.length > 0 ?
|
|
7885
|
+
return v && v.length > 0 ? path19.resolve(v) : void 0;
|
|
7442
7886
|
}
|
|
7443
7887
|
var CURSOR_CLIENT = {
|
|
7444
7888
|
id: "cursor",
|
|
7445
7889
|
label: "Cursor",
|
|
7446
7890
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
7447
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
7891
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? path19.join(homeDir(), ".cursor", "mcp.json"),
|
|
7448
7892
|
mcpContainerKey: "mcpServers",
|
|
7449
7893
|
format: "json",
|
|
7450
7894
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -7456,7 +7900,7 @@ var DEVIN_CLIENT = {
|
|
|
7456
7900
|
id: "devin",
|
|
7457
7901
|
label: "Devin Desktop (Cascade)",
|
|
7458
7902
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
7459
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
7903
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? path19.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
7460
7904
|
mcpContainerKey: "mcpServers",
|
|
7461
7905
|
format: "json",
|
|
7462
7906
|
rulesFileName: ".windsurfrules"
|
|
@@ -7465,7 +7909,7 @@ var GEMINI_CLIENT = {
|
|
|
7465
7909
|
id: "gemini",
|
|
7466
7910
|
label: "Gemini CLI",
|
|
7467
7911
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
7468
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
7912
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? path19.join(homeDir(), ".gemini", "settings.json"),
|
|
7469
7913
|
mcpContainerKey: "mcpServers",
|
|
7470
7914
|
format: "json",
|
|
7471
7915
|
rulesFileName: "GEMINI.md"
|
|
@@ -7474,7 +7918,7 @@ var QWEN_CLIENT = {
|
|
|
7474
7918
|
id: "qwen",
|
|
7475
7919
|
label: "Qwen Code",
|
|
7476
7920
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
7477
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
7921
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? path19.join(homeDir(), ".qwen", "settings.json"),
|
|
7478
7922
|
mcpContainerKey: "mcpServers",
|
|
7479
7923
|
format: "json",
|
|
7480
7924
|
rulesFileName: "QWEN.md"
|
|
@@ -7483,7 +7927,7 @@ var AMAZONQ_CLIENT = {
|
|
|
7483
7927
|
id: "amazonq",
|
|
7484
7928
|
label: "Amazon Q Developer CLI",
|
|
7485
7929
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
7486
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
7930
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? path19.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
7487
7931
|
mcpContainerKey: "mcpServers",
|
|
7488
7932
|
format: "json"
|
|
7489
7933
|
};
|
|
@@ -7491,7 +7935,7 @@ var ROOCODE_CLIENT = {
|
|
|
7491
7935
|
id: "roocode",
|
|
7492
7936
|
label: "Roo Code",
|
|
7493
7937
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
7494
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
7938
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? path19.join(process.cwd(), ".roo", "mcp.json"),
|
|
7495
7939
|
mcpContainerKey: "mcpServers",
|
|
7496
7940
|
format: "json"
|
|
7497
7941
|
};
|
|
@@ -7504,9 +7948,9 @@ var ZED_CLIENT = {
|
|
|
7504
7948
|
if (override) return override;
|
|
7505
7949
|
if (process.platform === "win32") {
|
|
7506
7950
|
const appData = process.env.APPDATA;
|
|
7507
|
-
if (appData && appData.length > 0) return
|
|
7951
|
+
if (appData && appData.length > 0) return path19.join(appData, "Zed", "settings.json");
|
|
7508
7952
|
}
|
|
7509
|
-
return
|
|
7953
|
+
return path19.join(homeDir(), ".config", "zed", "settings.json");
|
|
7510
7954
|
},
|
|
7511
7955
|
mcpContainerKey: "context_servers",
|
|
7512
7956
|
format: "jsonc",
|
|
@@ -7516,7 +7960,7 @@ var OPENCODE_CLIENT = {
|
|
|
7516
7960
|
id: "opencode",
|
|
7517
7961
|
label: "OpenCode",
|
|
7518
7962
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
7519
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
7963
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? path19.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
7520
7964
|
mcpContainerKey: "mcp",
|
|
7521
7965
|
format: "json",
|
|
7522
7966
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -7526,7 +7970,7 @@ var CRUSH_CLIENT = {
|
|
|
7526
7970
|
id: "crush",
|
|
7527
7971
|
label: "Crush",
|
|
7528
7972
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
7529
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
7973
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? path19.join(xdgConfigDir(), "crush", "crush.json"),
|
|
7530
7974
|
mcpContainerKey: "mcp",
|
|
7531
7975
|
format: "json",
|
|
7532
7976
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -7589,7 +8033,7 @@ async function planMcp(client, mcpPath) {
|
|
|
7589
8033
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
7590
8034
|
let raw = "";
|
|
7591
8035
|
try {
|
|
7592
|
-
raw = await
|
|
8036
|
+
raw = await fs17.readFile(mcpPath, "utf8");
|
|
7593
8037
|
} catch (err) {
|
|
7594
8038
|
const e = err;
|
|
7595
8039
|
if (e.code === "ENOENT") {
|
|
@@ -7631,7 +8075,7 @@ async function runEditorInstall(client, opts) {
|
|
|
7631
8075
|
const mcpPath = client.mcpConfigPath();
|
|
7632
8076
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
7633
8077
|
const hasRules = typeof client.rulesFileName === "string";
|
|
7634
|
-
const rulesPath = hasRules ?
|
|
8078
|
+
const rulesPath = hasRules ? path19.join(opts.projectDir, client.rulesFileName) : "";
|
|
7635
8079
|
const mcp = await planMcp(client, mcpPath);
|
|
7636
8080
|
if (mcp === null) return { exitCode: 1 };
|
|
7637
8081
|
let existingRules = "";
|
|
@@ -7640,7 +8084,7 @@ async function runEditorInstall(client, opts) {
|
|
|
7640
8084
|
let block = "";
|
|
7641
8085
|
if (hasRules) {
|
|
7642
8086
|
try {
|
|
7643
|
-
existingRules = await
|
|
8087
|
+
existingRules = await fs17.readFile(rulesPath, "utf8");
|
|
7644
8088
|
} catch (err) {
|
|
7645
8089
|
if (err.code !== "ENOENT") {
|
|
7646
8090
|
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
@@ -7674,11 +8118,11 @@ async function runEditorInstall(client, opts) {
|
|
|
7674
8118
|
);
|
|
7675
8119
|
return { exitCode: 0 };
|
|
7676
8120
|
}
|
|
7677
|
-
await
|
|
7678
|
-
await
|
|
8121
|
+
await fs17.mkdir(path19.dirname(mcpPath), { recursive: true });
|
|
8122
|
+
await fs17.writeFile(mcpPath, mcp.text, "utf8");
|
|
7679
8123
|
if (hasRules) {
|
|
7680
|
-
await
|
|
7681
|
-
await
|
|
8124
|
+
await fs17.mkdir(path19.dirname(rulesPath), { recursive: true });
|
|
8125
|
+
await fs17.writeFile(rulesPath, newRules, "utf8");
|
|
7682
8126
|
}
|
|
7683
8127
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
7684
8128
|
console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
|
|
@@ -8178,7 +8622,7 @@ function sleep(ms, signal) {
|
|
|
8178
8622
|
}
|
|
8179
8623
|
|
|
8180
8624
|
// src/cli-verbs.ts
|
|
8181
|
-
import
|
|
8625
|
+
import path20 from "path";
|
|
8182
8626
|
async function resolveProjectEntry(opts) {
|
|
8183
8627
|
const entries = await listProjects();
|
|
8184
8628
|
if (opts.project) {
|
|
@@ -8188,7 +8632,7 @@ async function resolveProjectEntry(opts) {
|
|
|
8188
8632
|
const cwd = opts.cwd ?? process.cwd();
|
|
8189
8633
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
8190
8634
|
for (const entry2 of entries) {
|
|
8191
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
8635
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path20.sep}`)) {
|
|
8192
8636
|
return entry2;
|
|
8193
8637
|
}
|
|
8194
8638
|
}
|
|
@@ -8507,6 +8951,8 @@ function usage5() {
|
|
|
8507
8951
|
console.log(" test <id> re-check an existing connector's credential");
|
|
8508
8952
|
console.log(" Credentials default to an env-var reference ($VAR) resolved at");
|
|
8509
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.");
|
|
8510
8956
|
console.log(" doctor Preflight this directory's setup \u2014 Node version, project,");
|
|
8511
8957
|
console.log(" and daemon reachability \u2014 and print a fix for anything down.");
|
|
8512
8958
|
console.log(" Flags: --json. Exits 0 when all pass, 1 when a check fails.");
|
|
@@ -8765,7 +9211,7 @@ async function buildPatchSections(services, project) {
|
|
|
8765
9211
|
}
|
|
8766
9212
|
async function runInit(opts) {
|
|
8767
9213
|
const written = [];
|
|
8768
|
-
const stat = await
|
|
9214
|
+
const stat = await fs18.stat(opts.scanPath).catch(() => null);
|
|
8769
9215
|
if (!stat || !stat.isDirectory()) {
|
|
8770
9216
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
8771
9217
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -8774,13 +9220,13 @@ async function runInit(opts) {
|
|
|
8774
9220
|
printDiscoveryReport(opts, services);
|
|
8775
9221
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
8776
9222
|
const patch = renderPatch(sections);
|
|
8777
|
-
const patchPath =
|
|
9223
|
+
const patchPath = path21.join(opts.scanPath, "neat.patch");
|
|
8778
9224
|
if (opts.dryRun) {
|
|
8779
|
-
await
|
|
9225
|
+
await fs18.writeFile(patchPath, patch, "utf8");
|
|
8780
9226
|
written.push(patchPath);
|
|
8781
9227
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
8782
|
-
const gitignorePath =
|
|
8783
|
-
const gitignoreExists = await
|
|
9228
|
+
const gitignorePath = path21.join(opts.scanPath, ".gitignore");
|
|
9229
|
+
const gitignoreExists = await fs18.stat(gitignorePath).then(() => true).catch(() => false);
|
|
8784
9230
|
const verb = gitignoreExists ? "append" : "create";
|
|
8785
9231
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
8786
9232
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -8791,9 +9237,9 @@ async function runInit(opts) {
|
|
|
8791
9237
|
const graph = getGraph(graphKey);
|
|
8792
9238
|
const projectPaths = pathsForProject(
|
|
8793
9239
|
graphKey,
|
|
8794
|
-
|
|
9240
|
+
path21.join(opts.scanPath, "neat-out")
|
|
8795
9241
|
);
|
|
8796
|
-
const errorsPath =
|
|
9242
|
+
const errorsPath = path21.join(path21.dirname(opts.outPath), path21.basename(projectPaths.errorsPath));
|
|
8797
9243
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
8798
9244
|
await saveGraphToDisk(graph, opts.outPath);
|
|
8799
9245
|
written.push(opts.outPath);
|
|
@@ -8802,15 +9248,13 @@ async function runInit(opts) {
|
|
|
8802
9248
|
written.push(gitignoreResult.file);
|
|
8803
9249
|
}
|
|
8804
9250
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
8805
|
-
let currentProjectName = opts.project;
|
|
8806
9251
|
try {
|
|
8807
|
-
|
|
9252
|
+
await addProject({
|
|
8808
9253
|
name: opts.project,
|
|
8809
9254
|
path: opts.scanPath,
|
|
8810
9255
|
languages,
|
|
8811
9256
|
status: "active"
|
|
8812
9257
|
});
|
|
8813
|
-
currentProjectName = entry2.name;
|
|
8814
9258
|
} catch (err) {
|
|
8815
9259
|
if (err instanceof ProjectNameCollisionError) {
|
|
8816
9260
|
console.error(`neat init: ${err.message}`);
|
|
@@ -8819,20 +9263,6 @@ async function runInit(opts) {
|
|
|
8819
9263
|
}
|
|
8820
9264
|
throw err;
|
|
8821
9265
|
}
|
|
8822
|
-
const siblings = await listProjects();
|
|
8823
|
-
const paused = [];
|
|
8824
|
-
for (const p of siblings) {
|
|
8825
|
-
if (p.name !== currentProjectName && p.status === "active") {
|
|
8826
|
-
await setStatus(p.name, "paused");
|
|
8827
|
-
paused.push(p.name);
|
|
8828
|
-
}
|
|
8829
|
-
}
|
|
8830
|
-
if (paused.length > 0) {
|
|
8831
|
-
const plural = paused.length === 1 ? "" : "s";
|
|
8832
|
-
console.log(
|
|
8833
|
-
`neat: paused ${paused.length} sibling project${plural}; run \`neat resume <name>\` to bring one back active.`
|
|
8834
|
-
);
|
|
8835
|
-
}
|
|
8836
9266
|
if (!opts.noInstall) {
|
|
8837
9267
|
if (opts.apply) {
|
|
8838
9268
|
let instrumented = 0;
|
|
@@ -8872,7 +9302,7 @@ async function runInit(opts) {
|
|
|
8872
9302
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
8873
9303
|
}
|
|
8874
9304
|
} else {
|
|
8875
|
-
await
|
|
9305
|
+
await fs18.writeFile(patchPath, patch, "utf8");
|
|
8876
9306
|
written.push(patchPath);
|
|
8877
9307
|
}
|
|
8878
9308
|
}
|
|
@@ -8893,6 +9323,8 @@ async function runInit(opts) {
|
|
|
8893
9323
|
if (result.extractionErrors > 0) {
|
|
8894
9324
|
console.log(`errors: ${errorsPath}`);
|
|
8895
9325
|
}
|
|
9326
|
+
const skippedBanner = formatSkippedBanner(result.skippedFiles);
|
|
9327
|
+
if (skippedBanner) console.log(skippedBanner);
|
|
8896
9328
|
console.log(formatPrecisionFloorBanner(result.extractedDropped));
|
|
8897
9329
|
if (result.extractionErrors > 0 && isStrictExtractionEnabled()) {
|
|
8898
9330
|
return { exitCode: 4, writtenFiles: written };
|
|
@@ -8913,9 +9345,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
8913
9345
|
};
|
|
8914
9346
|
function claudeConfigPath() {
|
|
8915
9347
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
8916
|
-
if (override && override.length > 0) return
|
|
9348
|
+
if (override && override.length > 0) return path21.resolve(override);
|
|
8917
9349
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
8918
|
-
return
|
|
9350
|
+
return path21.join(home, ".claude.json");
|
|
8919
9351
|
}
|
|
8920
9352
|
async function runSkill(opts) {
|
|
8921
9353
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -8927,7 +9359,7 @@ async function runSkill(opts) {
|
|
|
8927
9359
|
const target = claudeConfigPath();
|
|
8928
9360
|
let existing = {};
|
|
8929
9361
|
try {
|
|
8930
|
-
existing = JSON.parse(await
|
|
9362
|
+
existing = JSON.parse(await fs18.readFile(target, "utf8"));
|
|
8931
9363
|
} catch (err) {
|
|
8932
9364
|
if (err.code !== "ENOENT") {
|
|
8933
9365
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -8939,8 +9371,8 @@ async function runSkill(opts) {
|
|
|
8939
9371
|
...existing,
|
|
8940
9372
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
8941
9373
|
};
|
|
8942
|
-
await
|
|
8943
|
-
await
|
|
9374
|
+
await fs18.mkdir(path21.dirname(target), { recursive: true });
|
|
9375
|
+
await fs18.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
8944
9376
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
8945
9377
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
8946
9378
|
console.log("");
|
|
@@ -8989,6 +9421,21 @@ async function main() {
|
|
|
8989
9421
|
if (code !== 0) process.exit(code);
|
|
8990
9422
|
return;
|
|
8991
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
|
+
}
|
|
8992
9439
|
if (cmd0 === "login") {
|
|
8993
9440
|
const code = await runLoginCommand(argv.slice(1));
|
|
8994
9441
|
if (code !== 0) process.exit(code);
|
|
@@ -9061,12 +9508,12 @@ async function main() {
|
|
|
9061
9508
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
9062
9509
|
process.exit(2);
|
|
9063
9510
|
}
|
|
9064
|
-
const scanPath =
|
|
9511
|
+
const scanPath = path21.resolve(target);
|
|
9065
9512
|
const projectExplicit = parsed.project !== null;
|
|
9066
|
-
const projectName = projectExplicit ? project :
|
|
9513
|
+
const projectName = projectExplicit ? project : path21.basename(scanPath);
|
|
9067
9514
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
9068
|
-
const fallback = pathsForProject(projectKey,
|
|
9069
|
-
const outPath =
|
|
9515
|
+
const fallback = pathsForProject(projectKey, path21.join(scanPath, "neat-out")).snapshotPath;
|
|
9516
|
+
const outPath = path21.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
9070
9517
|
const result = await runInit({
|
|
9071
9518
|
scanPath,
|
|
9072
9519
|
outPath,
|
|
@@ -9087,21 +9534,21 @@ async function main() {
|
|
|
9087
9534
|
usage5();
|
|
9088
9535
|
process.exit(2);
|
|
9089
9536
|
}
|
|
9090
|
-
const scanPath =
|
|
9091
|
-
const stat = await
|
|
9537
|
+
const scanPath = path21.resolve(target);
|
|
9538
|
+
const stat = await fs18.stat(scanPath).catch(() => null);
|
|
9092
9539
|
if (!stat || !stat.isDirectory()) {
|
|
9093
9540
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
9094
9541
|
process.exit(2);
|
|
9095
9542
|
}
|
|
9096
|
-
const projectPaths = pathsForProject(project,
|
|
9097
|
-
const outPath =
|
|
9098
|
-
const errorsPath =
|
|
9099
|
-
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))
|
|
9100
9547
|
);
|
|
9101
|
-
const staleEventsPath =
|
|
9102
|
-
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))
|
|
9103
9550
|
);
|
|
9104
|
-
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;
|
|
9105
9552
|
const handle = await startWatch(getGraph(project), {
|
|
9106
9553
|
scanPath,
|
|
9107
9554
|
outPath,
|
|
@@ -9110,7 +9557,7 @@ async function main() {
|
|
|
9110
9557
|
project,
|
|
9111
9558
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
9112
9559
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
9113
|
-
neatHome: process.env.NEAT_HOME ?
|
|
9560
|
+
neatHome: process.env.NEAT_HOME ? path21.resolve(process.env.NEAT_HOME) : path21.join(os5.homedir(), ".neat"),
|
|
9114
9561
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
9115
9562
|
host: process.env.HOST ?? "0.0.0.0",
|
|
9116
9563
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -9291,12 +9738,12 @@ async function main() {
|
|
|
9291
9738
|
usage5();
|
|
9292
9739
|
process.exit(1);
|
|
9293
9740
|
}
|
|
9294
|
-
async function tryOrchestrator(cmd, parsed) {
|
|
9295
|
-
const scanPath =
|
|
9296
|
-
const stat = await
|
|
9741
|
+
async function tryOrchestrator(cmd, parsed, overrides = {}) {
|
|
9742
|
+
const scanPath = path21.resolve(cmd);
|
|
9743
|
+
const stat = await fs18.stat(scanPath).catch(() => null);
|
|
9297
9744
|
if (!stat || !stat.isDirectory()) return null;
|
|
9298
9745
|
const projectExplicit = parsed.project !== null;
|
|
9299
|
-
const projectName = projectExplicit ? parsed.project :
|
|
9746
|
+
const projectName = projectExplicit ? parsed.project : path21.basename(scanPath);
|
|
9300
9747
|
const result = await runOrchestrator({
|
|
9301
9748
|
scanPath,
|
|
9302
9749
|
project: projectName,
|
|
@@ -9304,14 +9751,31 @@ async function tryOrchestrator(cmd, parsed) {
|
|
|
9304
9751
|
noInstrument: parsed.noInstrument,
|
|
9305
9752
|
open: parsed.open,
|
|
9306
9753
|
noOpen: parsed.noOpen,
|
|
9307
|
-
yes: parsed.yes
|
|
9754
|
+
yes: parsed.yes,
|
|
9755
|
+
...overrides.skipBanner ? { skipBanner: true } : {}
|
|
9308
9756
|
});
|
|
9309
9757
|
return result.exitCode;
|
|
9310
9758
|
}
|
|
9311
9759
|
async function runWelcomeFlow(parsed) {
|
|
9312
9760
|
return runWelcome({
|
|
9313
9761
|
login: (loginArgv) => runLoginCommand(loginArgv),
|
|
9314
|
-
|
|
9762
|
+
// What the person chose at the door reaches the orchestrator as the flags they
|
|
9763
|
+
// stand for: the name as `--project`, the instrument answer as `--no-instrument`
|
|
9764
|
+
// or as `yes` (which keeps the orchestrator's own prompt quiet, since the door
|
|
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,
|
|
9777
|
+
// An explicit flag is an answer; don't ask again.
|
|
9778
|
+
instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
|
|
9315
9779
|
});
|
|
9316
9780
|
}
|
|
9317
9781
|
var QUERY_VERBS = /* @__PURE__ */ new Set([
|
|
@@ -9344,9 +9808,17 @@ var ProjectResolutionError = class extends Error {
|
|
|
9344
9808
|
}
|
|
9345
9809
|
exitCode;
|
|
9346
9810
|
};
|
|
9347
|
-
|
|
9811
|
+
function isLoopbackEndpoint(endpoint) {
|
|
9812
|
+
try {
|
|
9813
|
+
return isLoopbackHost(new URL(endpoint).hostname.replace(/^\[|\]$/g, ""));
|
|
9814
|
+
} catch {
|
|
9815
|
+
return false;
|
|
9816
|
+
}
|
|
9817
|
+
}
|
|
9818
|
+
async function resolveProjectForVerb(client, parsed, cwdProject) {
|
|
9348
9819
|
const explicit = resolveProjectFlag(parsed);
|
|
9349
9820
|
if (explicit) return explicit;
|
|
9821
|
+
if (cwdProject) return cwdProject;
|
|
9350
9822
|
const projects = await client.get("/projects");
|
|
9351
9823
|
if (projects.some((p) => p.name === DEFAULT_PROJECT)) {
|
|
9352
9824
|
return void 0;
|
|
@@ -9393,14 +9865,28 @@ async function resolveClientTarget(opts = {}) {
|
|
|
9393
9865
|
}
|
|
9394
9866
|
return { endpoint: "http://localhost:8080", source: "default" };
|
|
9395
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
|
+
}
|
|
9396
9878
|
async function resolveDaemonUrl(project, profile) {
|
|
9397
9879
|
return (await resolveClientTarget({ project, profile })).endpoint;
|
|
9398
9880
|
}
|
|
9399
9881
|
async function runQueryVerb(cmd, parsed) {
|
|
9400
9882
|
const requestedProject = resolveProjectFlag(parsed);
|
|
9883
|
+
const cwdProject = requestedProject ? void 0 : await projectForCwd();
|
|
9401
9884
|
let target;
|
|
9402
9885
|
try {
|
|
9403
|
-
target = await resolveClientTarget({
|
|
9886
|
+
target = await resolveClientTarget({
|
|
9887
|
+
project: requestedProject ?? cwdProject,
|
|
9888
|
+
profile: parsed.profile ?? void 0
|
|
9889
|
+
});
|
|
9404
9890
|
} catch (err) {
|
|
9405
9891
|
if (err instanceof UnknownProfileError) {
|
|
9406
9892
|
process.stderr.write(`${err.message}
|
|
@@ -9559,7 +10045,11 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
9559
10045
|
return 2;
|
|
9560
10046
|
}
|
|
9561
10047
|
try {
|
|
9562
|
-
const project = await resolveProjectForVerb(
|
|
10048
|
+
const project = await resolveProjectForVerb(
|
|
10049
|
+
client,
|
|
10050
|
+
parsed,
|
|
10051
|
+
cwdProjectApplies(target.source) ? cwdProject : void 0
|
|
10052
|
+
);
|
|
9563
10053
|
const result = await makeWork(project);
|
|
9564
10054
|
if (parsed.json) process.stdout.write(formatJson(result) + "\n");
|
|
9565
10055
|
else process.stdout.write(formatHuman(result) + "\n");
|
|
@@ -9574,6 +10064,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
9574
10064
|
console.error(`neat ${cmd}: ${detail.trim()}`);
|
|
9575
10065
|
} else if (err instanceof TransportError) {
|
|
9576
10066
|
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${target.endpoint})`);
|
|
10067
|
+
if (isLoopbackEndpoint(target.endpoint)) {
|
|
10068
|
+
const upCommand = requestedProject ? `neat up --project ${requestedProject}` : "neat up";
|
|
10069
|
+
console.error(`neat ${cmd}: run \`${upCommand}\` to start it.`);
|
|
10070
|
+
}
|
|
9577
10071
|
} else {
|
|
9578
10072
|
console.error(`neat ${cmd}: ${err.message}`);
|
|
9579
10073
|
}
|
|
@@ -9582,9 +10076,13 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
9582
10076
|
}
|
|
9583
10077
|
async function runMonitorVerb(parsed) {
|
|
9584
10078
|
const requestedProject = resolveProjectFlag(parsed);
|
|
10079
|
+
const cwdProject = requestedProject ? void 0 : await projectForCwd();
|
|
9585
10080
|
let target;
|
|
9586
10081
|
try {
|
|
9587
|
-
target = await resolveClientTarget({
|
|
10082
|
+
target = await resolveClientTarget({
|
|
10083
|
+
project: requestedProject ?? cwdProject,
|
|
10084
|
+
profile: parsed.profile ?? void 0
|
|
10085
|
+
});
|
|
9588
10086
|
} catch (err) {
|
|
9589
10087
|
if (err instanceof UnknownProfileError) return 0;
|
|
9590
10088
|
throw err;
|
|
@@ -9592,7 +10090,11 @@ async function runMonitorVerb(parsed) {
|
|
|
9592
10090
|
const client = createHttpClient(target.endpoint, target.authToken);
|
|
9593
10091
|
let project;
|
|
9594
10092
|
try {
|
|
9595
|
-
project = await resolveProjectForVerb(
|
|
10093
|
+
project = await resolveProjectForVerb(
|
|
10094
|
+
client,
|
|
10095
|
+
parsed,
|
|
10096
|
+
cwdProjectApplies(target.source) ? cwdProject : void 0
|
|
10097
|
+
);
|
|
9596
10098
|
} catch (err) {
|
|
9597
10099
|
if (err instanceof ProjectResolutionError) {
|
|
9598
10100
|
console.error(`neat monitor: ${err.message}`);
|
|
@@ -9616,11 +10118,18 @@ async function runMonitorVerb(parsed) {
|
|
|
9616
10118
|
process.off("SIGTERM", onSignal);
|
|
9617
10119
|
}
|
|
9618
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
|
+
}
|
|
9619
10129
|
var entry = process.argv[1] ?? "";
|
|
9620
10130
|
if (/[\\/](?:cli\.(?:cjs|js)|cli|neat|neat\.is)$/.test(entry)) {
|
|
9621
10131
|
main().catch((err) => {
|
|
9622
|
-
|
|
9623
|
-
process.exit(1);
|
|
10132
|
+
process.exit(reportFatal(err));
|
|
9624
10133
|
});
|
|
9625
10134
|
}
|
|
9626
10135
|
export {
|
|
@@ -9629,11 +10138,15 @@ export {
|
|
|
9629
10138
|
QUERY_VERBS,
|
|
9630
10139
|
UnknownProfileError,
|
|
9631
10140
|
commandPrefix,
|
|
10141
|
+
cwdProjectApplies,
|
|
10142
|
+
isLoopbackEndpoint,
|
|
9632
10143
|
isNpxInvocation,
|
|
9633
10144
|
main,
|
|
9634
10145
|
parseArgs,
|
|
9635
10146
|
printBanner,
|
|
10147
|
+
projectForCwd,
|
|
9636
10148
|
readPackageVersion,
|
|
10149
|
+
reportFatal,
|
|
9637
10150
|
resolveClientTarget,
|
|
9638
10151
|
resolveDaemonUrl,
|
|
9639
10152
|
resolveProjectForVerb,
|