@neat.is/core 0.4.16 → 0.4.18
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-6CO7C4IU.js → chunk-BGPWBRLU.js} +4 -2
- package/dist/{chunk-6CO7C4IU.js.map → chunk-BGPWBRLU.js.map} +1 -1
- package/dist/{chunk-VMLWUK7W.js → chunk-GXWBMJDJ.js} +210 -11
- package/dist/chunk-GXWBMJDJ.js.map +1 -0
- package/dist/{chunk-GHPHVXYM.js → chunk-MTXF77TN.js} +2 -2
- package/dist/{chunk-XS4CGNRO.js → chunk-T3X6XJPU.js} +403 -72
- package/dist/chunk-T3X6XJPU.js.map +1 -0
- package/dist/cli.cjs +964 -364
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +12 -2
- package/dist/cli.d.ts +12 -2
- package/dist/cli.js +331 -86
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +559 -150
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +51 -13
- package/dist/index.d.ts +51 -13
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +726 -193
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +161 -37
- package/dist/neatd.js.map +1 -1
- package/dist/{otel-grpc-QAISVAY5.js → otel-grpc-I67ONCRM.js} +3 -3
- package/dist/server.cjs +271 -97
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-VMLWUK7W.js.map +0 -1
- package/dist/chunk-XS4CGNRO.js.map +0 -1
- /package/dist/{chunk-GHPHVXYM.js.map → chunk-MTXF77TN.js.map} +0 -0
- /package/dist/{otel-grpc-QAISVAY5.js.map → otel-grpc-I67ONCRM.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readDaemonRecord
|
|
4
|
+
} from "./chunk-GXWBMJDJ.js";
|
|
2
5
|
import {
|
|
3
6
|
buildSearchIndex
|
|
4
7
|
} from "./chunk-XOOCA5T7.js";
|
|
@@ -24,10 +27,12 @@ import {
|
|
|
24
27
|
ensureCompatLoaded,
|
|
25
28
|
evaluateAllPolicies,
|
|
26
29
|
extractFromDirectory,
|
|
30
|
+
findDaemonByProject,
|
|
27
31
|
formatExtractionBanner,
|
|
28
32
|
formatPrecisionFloorBanner,
|
|
29
33
|
getGraph,
|
|
30
34
|
isStrictExtractionEnabled,
|
|
35
|
+
listMachineProjects,
|
|
31
36
|
listProjects,
|
|
32
37
|
loadGraphFromDisk,
|
|
33
38
|
loadPolicyFile,
|
|
@@ -36,25 +41,28 @@ import {
|
|
|
36
41
|
normalizeProjectPath,
|
|
37
42
|
pathsForProject,
|
|
38
43
|
promoteFrontierNodes,
|
|
44
|
+
pruneRegistry,
|
|
45
|
+
removeDaemonRecord,
|
|
39
46
|
removeProject,
|
|
40
47
|
resetGraph,
|
|
41
48
|
retireEdgesByFile,
|
|
42
49
|
runPackageManagerInstall,
|
|
43
50
|
saveGraphToDisk,
|
|
44
51
|
setStatus,
|
|
52
|
+
signalDaemonStop,
|
|
45
53
|
startPersistLoop,
|
|
46
54
|
startStalenessLoop
|
|
47
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-T3X6XJPU.js";
|
|
48
56
|
import {
|
|
49
57
|
startOtelGrpcReceiver
|
|
50
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-MTXF77TN.js";
|
|
51
59
|
import {
|
|
52
60
|
__dirname,
|
|
53
61
|
__require,
|
|
54
62
|
assertBindAuthority,
|
|
55
63
|
buildOtelReceiver,
|
|
56
64
|
readAuthEnv
|
|
57
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-BGPWBRLU.js";
|
|
58
66
|
|
|
59
67
|
// src/cli.ts
|
|
60
68
|
import path9 from "path";
|
|
@@ -744,9 +752,50 @@ import semver from "semver";
|
|
|
744
752
|
|
|
745
753
|
// src/installers/templates.ts
|
|
746
754
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
747
|
-
var OTEL_INIT_STAMP = "// neat-template-version:
|
|
755
|
+
var OTEL_INIT_STAMP = "// neat-template-version: 6 \u2014 daemon.json endpoint resolution (ADR-096) + layered file-first capture (ADR-090).";
|
|
748
756
|
var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
|
|
749
757
|
var OTEL_OTLP_PROTOCOL_JS = "process.env.OTEL_EXPORTER_OTLP_PROTOCOL ||= 'http/json'";
|
|
758
|
+
var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
|
|
759
|
+
try {
|
|
760
|
+
if (process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return
|
|
761
|
+
const __neatFs = require('node:fs')
|
|
762
|
+
const __neatPath = require('node:path')
|
|
763
|
+
let __neatDir = process.cwd()
|
|
764
|
+
for (let __i = 0; __i < 8; __i++) {
|
|
765
|
+
try {
|
|
766
|
+
const __rec = JSON.parse(__neatFs.readFileSync(__neatPath.join(__neatDir, 'neat-out', 'daemon.json'), 'utf8'))
|
|
767
|
+
if (__rec && __rec.ports && typeof __rec.ports.otlp === 'number') {
|
|
768
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/v1/traces'
|
|
769
|
+
break
|
|
770
|
+
}
|
|
771
|
+
} catch (_e) {}
|
|
772
|
+
const __parent = __neatPath.dirname(__neatDir)
|
|
773
|
+
if (__parent === __neatDir) break
|
|
774
|
+
__neatDir = __parent
|
|
775
|
+
}
|
|
776
|
+
} catch (_e) {}
|
|
777
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
|
|
778
|
+
})()`;
|
|
779
|
+
var OTEL_ESM_NODE_IMPORTS = "import { readFileSync as __neatReadFileSync } from 'node:fs'\nimport { join as __neatJoin, dirname as __neatDirname } from 'node:path'";
|
|
780
|
+
var OTEL_ENDPOINT_RESOLVER_ESM = `;(function () {
|
|
781
|
+
try {
|
|
782
|
+
if (process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return
|
|
783
|
+
let __neatDir = process.cwd()
|
|
784
|
+
for (let __i = 0; __i < 8; __i++) {
|
|
785
|
+
try {
|
|
786
|
+
const __rec = JSON.parse(__neatReadFileSync(__neatJoin(__neatDir, 'neat-out', 'daemon.json'), 'utf8'))
|
|
787
|
+
if (__rec && __rec.ports && typeof __rec.ports.otlp === 'number') {
|
|
788
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/v1/traces'
|
|
789
|
+
break
|
|
790
|
+
}
|
|
791
|
+
} catch (_e) {}
|
|
792
|
+
const __parent = __neatDirname(__neatDir)
|
|
793
|
+
if (__parent === __neatDir) break
|
|
794
|
+
__neatDir = __parent
|
|
795
|
+
}
|
|
796
|
+
} catch (_e) {}
|
|
797
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
|
|
798
|
+
})()`;
|
|
750
799
|
function neatCaptureSource(ts) {
|
|
751
800
|
const spanT = ts ? ": any" : "";
|
|
752
801
|
const strOpt = ts ? ": string | undefined" : "";
|
|
@@ -1035,7 +1084,7 @@ try {
|
|
|
1035
1084
|
var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
|
|
1036
1085
|
${OTEL_INIT_STAMP}
|
|
1037
1086
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1038
|
-
|
|
1087
|
+
${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
1039
1088
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1040
1089
|
${OTEL_OTLP_HEADERS_JS}
|
|
1041
1090
|
|
|
@@ -1053,8 +1102,9 @@ ${neatWireCaptureSource(false)}
|
|
|
1053
1102
|
`;
|
|
1054
1103
|
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
1055
1104
|
${OTEL_INIT_STAMP}
|
|
1105
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
1056
1106
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1057
|
-
|
|
1107
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
1058
1108
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1059
1109
|
${OTEL_OTLP_HEADERS_JS}
|
|
1060
1110
|
|
|
@@ -1077,8 +1127,9 @@ ${OTEL_INIT_STAMP}
|
|
|
1077
1127
|
// strict user tsconfig would reject; suppressing type-checking here keeps the
|
|
1078
1128
|
// generated file from breaking the host project's \`tsc\` gate (#427) without
|
|
1079
1129
|
// constraining the runtime logic. The file is regenerated, never hand-edited.
|
|
1130
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
1080
1131
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1081
|
-
|
|
1132
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
1082
1133
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1083
1134
|
${OTEL_OTLP_HEADERS_JS}
|
|
1084
1135
|
|
|
@@ -1100,11 +1151,14 @@ ${registrations.join("\n")}
|
|
|
1100
1151
|
`;
|
|
1101
1152
|
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
|
|
1102
1153
|
}
|
|
1103
|
-
function renderEnvNeat(serviceName,
|
|
1154
|
+
function renderEnvNeat(serviceName, _projectName) {
|
|
1104
1155
|
return [
|
|
1105
1156
|
"# Generated by `neat init --apply` (ADR-069).",
|
|
1106
1157
|
`OTEL_SERVICE_NAME=${serviceName}`,
|
|
1107
|
-
|
|
1158
|
+
"# Advisory only \u2014 the generated otel-init resolves the live endpoint from",
|
|
1159
|
+
"# <project>/neat-out/daemon.json (ports.otlp) at boot (ADR-096). This is the",
|
|
1160
|
+
"# canonical default the daemon takes when its first-choice OTLP port is free.",
|
|
1161
|
+
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces",
|
|
1108
1162
|
"OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
|
|
1109
1163
|
"# Set NEAT_OTEL_TOKEN to the daemon's OTLP secret to authenticate exported spans (#410).",
|
|
1110
1164
|
"# NEAT_OTEL_TOKEN=",
|
|
@@ -1127,8 +1181,9 @@ export async function register() {
|
|
|
1127
1181
|
}
|
|
1128
1182
|
`;
|
|
1129
1183
|
var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
1184
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
1130
1185
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1131
|
-
|
|
1186
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
1132
1187
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1133
1188
|
${OTEL_OTLP_HEADERS_JS}
|
|
1134
1189
|
|
|
@@ -1141,7 +1196,7 @@ new NodeSDK({ instrumentations }).start()
|
|
|
1141
1196
|
`;
|
|
1142
1197
|
var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
1143
1198
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1144
|
-
|
|
1199
|
+
${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
1145
1200
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1146
1201
|
${OTEL_OTLP_HEADERS_JS}
|
|
1147
1202
|
|
|
@@ -1160,8 +1215,9 @@ ${registrations.join("\n")}
|
|
|
1160
1215
|
}
|
|
1161
1216
|
var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
|
|
1162
1217
|
var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
1218
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
1163
1219
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1164
|
-
|
|
1220
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
1165
1221
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1166
1222
|
${OTEL_OTLP_HEADERS_JS}
|
|
1167
1223
|
|
|
@@ -1176,7 +1232,7 @@ sdk.start()
|
|
|
1176
1232
|
`;
|
|
1177
1233
|
var FRAMEWORK_OTEL_INIT_JS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
1178
1234
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1179
|
-
|
|
1235
|
+
${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
1180
1236
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1181
1237
|
${OTEL_OTLP_HEADERS_JS}
|
|
1182
1238
|
|
|
@@ -2833,10 +2889,6 @@ async function fetchDaemonHealth(restPort) {
|
|
|
2833
2889
|
});
|
|
2834
2890
|
});
|
|
2835
2891
|
}
|
|
2836
|
-
async function checkDaemonHealth(restPort) {
|
|
2837
|
-
const body = await fetchDaemonHealth(restPort);
|
|
2838
|
-
return body !== null;
|
|
2839
|
-
}
|
|
2840
2892
|
async function probeProjectHealth(restPort, name) {
|
|
2841
2893
|
return new Promise((resolve) => {
|
|
2842
2894
|
const req = http.get(
|
|
@@ -2912,12 +2964,6 @@ async function isPortFree(port) {
|
|
|
2912
2964
|
server.listen(port, "127.0.0.1");
|
|
2913
2965
|
});
|
|
2914
2966
|
}
|
|
2915
|
-
async function probePortsFree() {
|
|
2916
|
-
for (const port of NEAT_PORTS) {
|
|
2917
|
-
if (!await isPortFree(port)) return { free: false, held: port };
|
|
2918
|
-
}
|
|
2919
|
-
return { free: true };
|
|
2920
|
-
}
|
|
2921
2967
|
function formatPortCollisionMessage(port) {
|
|
2922
2968
|
return [
|
|
2923
2969
|
`neat: port ${port} is in use; the NEAT daemon needs it.`,
|
|
@@ -2925,7 +2971,78 @@ function formatPortCollisionMessage(port) {
|
|
|
2925
2971
|
` \`lsof -i :${port}\` to find the holding process.`
|
|
2926
2972
|
];
|
|
2927
2973
|
}
|
|
2928
|
-
|
|
2974
|
+
var PORT_ALLOCATION_ATTEMPTS = 8;
|
|
2975
|
+
var PORT_STRIDE = 1;
|
|
2976
|
+
async function tripleFree(ports) {
|
|
2977
|
+
for (const p of [ports.rest, ports.otlp, ports.web]) {
|
|
2978
|
+
if (!await isPortFree(p)) return false;
|
|
2979
|
+
}
|
|
2980
|
+
return true;
|
|
2981
|
+
}
|
|
2982
|
+
async function allocatePorts() {
|
|
2983
|
+
const [baseRest, baseOtlp, baseWeb] = NEAT_PORTS;
|
|
2984
|
+
for (let i = 0; i < PORT_ALLOCATION_ATTEMPTS; i++) {
|
|
2985
|
+
const candidate = {
|
|
2986
|
+
rest: baseRest + i * PORT_STRIDE,
|
|
2987
|
+
otlp: baseOtlp + i * PORT_STRIDE,
|
|
2988
|
+
web: baseWeb + i * PORT_STRIDE
|
|
2989
|
+
};
|
|
2990
|
+
if (await tripleFree(candidate)) return candidate;
|
|
2991
|
+
}
|
|
2992
|
+
return null;
|
|
2993
|
+
}
|
|
2994
|
+
async function persistedPortsFor(scanPath) {
|
|
2995
|
+
const record = await readDaemonRecord(scanPath);
|
|
2996
|
+
if (!record) return null;
|
|
2997
|
+
return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
|
|
2998
|
+
}
|
|
2999
|
+
async function acquireSpawnLock(scanPath) {
|
|
3000
|
+
const lockPath = path7.join(scanPath, "neat-out", "daemon.spawn.lock");
|
|
3001
|
+
await fs6.mkdir(path7.dirname(lockPath), { recursive: true });
|
|
3002
|
+
const STALE_LOCK_MS = 6e4;
|
|
3003
|
+
try {
|
|
3004
|
+
const fd = await fs6.open(lockPath, "wx");
|
|
3005
|
+
await fd.writeFile(`${process.pid}
|
|
3006
|
+
`, "utf8");
|
|
3007
|
+
await fd.close();
|
|
3008
|
+
return async () => {
|
|
3009
|
+
await fs6.unlink(lockPath).catch(() => {
|
|
3010
|
+
});
|
|
3011
|
+
};
|
|
3012
|
+
} catch (err) {
|
|
3013
|
+
if (err.code !== "EEXIST") return null;
|
|
3014
|
+
try {
|
|
3015
|
+
const stat = await fs6.stat(lockPath);
|
|
3016
|
+
if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
|
|
3017
|
+
await fs6.unlink(lockPath).catch(() => {
|
|
3018
|
+
});
|
|
3019
|
+
return acquireSpawnLock(scanPath);
|
|
3020
|
+
}
|
|
3021
|
+
} catch {
|
|
3022
|
+
return acquireSpawnLock(scanPath);
|
|
3023
|
+
}
|
|
3024
|
+
return null;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
async function waitForPeerDaemon(restPort, project, timeoutMs) {
|
|
3028
|
+
const deadline = Date.now() + timeoutMs;
|
|
3029
|
+
while (Date.now() < deadline) {
|
|
3030
|
+
if (await healthIsForProject(restPort, project)) return true;
|
|
3031
|
+
await new Promise((r) => setTimeout(r, PROBE_INTERVAL_MS));
|
|
3032
|
+
}
|
|
3033
|
+
return healthIsForProject(restPort, project);
|
|
3034
|
+
}
|
|
3035
|
+
async function healthIsForProject(restPort, project) {
|
|
3036
|
+
const body = await fetchDaemonHealth(restPort);
|
|
3037
|
+
if (body === null) return false;
|
|
3038
|
+
const named = body.project;
|
|
3039
|
+
if (typeof named === "string") return named === project;
|
|
3040
|
+
if (Array.isArray(body.projects)) {
|
|
3041
|
+
return body.projects.some((p) => p.name === project);
|
|
3042
|
+
}
|
|
3043
|
+
return false;
|
|
3044
|
+
}
|
|
3045
|
+
function spawnDaemonDetached(spec) {
|
|
2929
3046
|
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2930
3047
|
const candidates = [
|
|
2931
3048
|
path7.join(here, "neatd.cjs"),
|
|
@@ -2949,6 +3066,13 @@ function spawnDaemonDetached() {
|
|
|
2949
3066
|
if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
|
|
2950
3067
|
env.HOST = "127.0.0.1";
|
|
2951
3068
|
}
|
|
3069
|
+
if (spec) {
|
|
3070
|
+
env.NEAT_PROJECT = spec.project;
|
|
3071
|
+
env.NEAT_PROJECT_PATH = spec.projectPath;
|
|
3072
|
+
env.PORT = String(spec.ports.rest);
|
|
3073
|
+
env.OTEL_PORT = String(spec.ports.otlp);
|
|
3074
|
+
env.NEAT_WEB_PORT = String(spec.ports.web);
|
|
3075
|
+
}
|
|
2952
3076
|
const child = spawn2(process.execPath, [entry2, "start"], {
|
|
2953
3077
|
detached: true,
|
|
2954
3078
|
// stderr inherits the orchestrator's fd so the daemon's
|
|
@@ -3068,48 +3192,75 @@ async function runOrchestrator(opts) {
|
|
|
3068
3192
|
result.exitCode = 1;
|
|
3069
3193
|
}
|
|
3070
3194
|
}
|
|
3071
|
-
const restPort = Number(process.env.PORT ?? 8080);
|
|
3072
3195
|
const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
|
|
3073
|
-
|
|
3196
|
+
const persistedPorts = await persistedPortsFor(opts.scanPath);
|
|
3197
|
+
let allocated = null;
|
|
3198
|
+
if (persistedPorts && await healthIsForProject(persistedPorts.rest, currentProjectName)) {
|
|
3074
3199
|
result.steps.daemon = "already-running";
|
|
3200
|
+
allocated = persistedPorts;
|
|
3075
3201
|
} else {
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3202
|
+
if (persistedPorts && await isPortFree(persistedPorts.rest) && await tripleFree(persistedPorts)) {
|
|
3203
|
+
allocated = persistedPorts;
|
|
3204
|
+
} else {
|
|
3205
|
+
allocated = await allocatePorts();
|
|
3206
|
+
}
|
|
3207
|
+
if (!allocated) {
|
|
3208
|
+
for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) {
|
|
3079
3209
|
console.error(line);
|
|
3080
3210
|
}
|
|
3081
3211
|
result.exitCode = 3;
|
|
3082
3212
|
return result;
|
|
3083
3213
|
}
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
if (!ready.ready) {
|
|
3094
|
-
console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
|
|
3095
|
-
if (ready.stillBootstrapping.length > 0) {
|
|
3096
|
-
console.error(
|
|
3097
|
-
`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`
|
|
3098
|
-
);
|
|
3214
|
+
const release = await acquireSpawnLock(opts.scanPath);
|
|
3215
|
+
if (!release) {
|
|
3216
|
+
const reused = await waitForPeerDaemon(allocated.rest, currentProjectName, timeoutMs);
|
|
3217
|
+
if (reused) {
|
|
3218
|
+
result.steps.daemon = "already-running";
|
|
3219
|
+
} else {
|
|
3220
|
+
console.error("neat: another `neat` is spawning this project but its daemon did not come up in time");
|
|
3221
|
+
result.exitCode = 1;
|
|
3222
|
+
return result;
|
|
3099
3223
|
}
|
|
3100
|
-
|
|
3101
|
-
|
|
3224
|
+
} else {
|
|
3225
|
+
try {
|
|
3226
|
+
if (await healthIsForProject(allocated.rest, currentProjectName)) {
|
|
3227
|
+
result.steps.daemon = "already-running";
|
|
3228
|
+
} else {
|
|
3229
|
+
spawnDaemonDetached({
|
|
3230
|
+
project: currentProjectName,
|
|
3231
|
+
projectPath: opts.scanPath,
|
|
3232
|
+
ports: allocated
|
|
3233
|
+
});
|
|
3234
|
+
const ready = await waitForDaemonReady(allocated.rest, timeoutMs);
|
|
3235
|
+
result.steps.daemon = ready.ready ? "spawned" : "timed-out";
|
|
3236
|
+
if (!ready.ready) {
|
|
3237
|
+
console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
|
|
3238
|
+
if (ready.stillBootstrapping.length > 0) {
|
|
3239
|
+
console.error(`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`);
|
|
3240
|
+
}
|
|
3241
|
+
if (ready.brokenProjects.length > 0) {
|
|
3242
|
+
console.error(`neat: broken projects: ${ready.brokenProjects.join(", ")}`);
|
|
3243
|
+
}
|
|
3244
|
+
result.exitCode = 1;
|
|
3245
|
+
return result;
|
|
3246
|
+
}
|
|
3247
|
+
if (ready.brokenProjects.length > 0) {
|
|
3248
|
+
console.warn(
|
|
3249
|
+
`neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
} catch (err) {
|
|
3254
|
+
console.error(`neat: daemon spawn failed \u2014 ${err.message}`);
|
|
3255
|
+
result.exitCode = 1;
|
|
3256
|
+
return result;
|
|
3257
|
+
} finally {
|
|
3258
|
+
await release();
|
|
3102
3259
|
}
|
|
3103
|
-
result.exitCode = 1;
|
|
3104
|
-
return result;
|
|
3105
|
-
}
|
|
3106
|
-
if (ready.brokenProjects.length > 0) {
|
|
3107
|
-
console.warn(
|
|
3108
|
-
`neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
|
|
3109
|
-
);
|
|
3110
3260
|
}
|
|
3111
3261
|
}
|
|
3112
|
-
const
|
|
3262
|
+
const webPort = allocated?.web ?? NEAT_PORTS[2];
|
|
3263
|
+
const dashboardUrl = opts.dashboardUrl ?? `http://localhost:${webPort}`;
|
|
3113
3264
|
if (opts.noOpen || !process.stdout.isTTY) {
|
|
3114
3265
|
result.steps.browser = "skipped";
|
|
3115
3266
|
} else {
|
|
@@ -3677,7 +3828,7 @@ async function resolveProjectEntry(opts) {
|
|
|
3677
3828
|
}
|
|
3678
3829
|
return null;
|
|
3679
3830
|
}
|
|
3680
|
-
async function
|
|
3831
|
+
async function checkDaemonHealth(baseUrl) {
|
|
3681
3832
|
try {
|
|
3682
3833
|
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/health`, {
|
|
3683
3834
|
signal: AbortSignal.timeout(1500)
|
|
@@ -3778,7 +3929,7 @@ async function runSync(opts) {
|
|
|
3778
3929
|
}
|
|
3779
3930
|
} else {
|
|
3780
3931
|
const daemonUrl = opts.daemonUrl ?? process.env.NEAT_API_URL ?? "http://localhost:8080";
|
|
3781
|
-
const healthy = await
|
|
3932
|
+
const healthy = await checkDaemonHealth(daemonUrl);
|
|
3782
3933
|
if (healthy) {
|
|
3783
3934
|
try {
|
|
3784
3935
|
await pushSnapshotToRemote({
|
|
@@ -3852,12 +4003,18 @@ function usage() {
|
|
|
3852
4003
|
console.log(" watch <path> Start neat-core, watch <path>, re-extract on changes.");
|
|
3853
4004
|
console.log(" PORT (default 8080), OTEL_PORT (4318), HOST (0.0.0.0)");
|
|
3854
4005
|
console.log(" control listeners. NEAT_OTLP_GRPC=true also opens 4317.");
|
|
3855
|
-
console.log(" list
|
|
3856
|
-
console.log("
|
|
3857
|
-
console.log("
|
|
4006
|
+
console.log(" list Report the daemons running on this machine (alias: ps).");
|
|
4007
|
+
console.log(" Reads ~/.neat/daemons/ and folds in any registered");
|
|
4008
|
+
console.log(" project no daemon has self-described yet.");
|
|
4009
|
+
console.log(" ps Alias of list.");
|
|
4010
|
+
console.log(" pause <name> Stop a project's daemon until it is started again.");
|
|
4011
|
+
console.log(" resume <name> Bring a paused project back; for a stopped daemon, re-run");
|
|
4012
|
+
console.log(" `neat <path>` to start it.");
|
|
3858
4013
|
console.log(" uninstall <name>");
|
|
3859
|
-
console.log("
|
|
4014
|
+
console.log(" Stop a project's daemon and retire it. Does not touch");
|
|
3860
4015
|
console.log(" neat-out/, policy.json, or any user file.");
|
|
4016
|
+
console.log(" prune Drop registry entries whose path is gone from disk.");
|
|
4017
|
+
console.log(" Flags: --json emit the removed list as JSON");
|
|
3861
4018
|
console.log(" version Print the installed @neat.is/core version and exit.");
|
|
3862
4019
|
console.log(" Aliases: --version, -v.");
|
|
3863
4020
|
console.log(" skill Install or print the Claude Code MCP drop-in.");
|
|
@@ -3917,6 +4074,7 @@ function usage() {
|
|
|
3917
4074
|
console.log("");
|
|
3918
4075
|
console.log("environment:");
|
|
3919
4076
|
console.log(" NEAT_API_URL base URL for the core REST API (default http://localhost:8080)");
|
|
4077
|
+
console.log(" alias: NEAT_CORE_URL (the name the MCP server reads)");
|
|
3920
4078
|
console.log(" NEAT_PROJECT project name when --project isn't passed");
|
|
3921
4079
|
}
|
|
3922
4080
|
var STRING_FLAGS = [
|
|
@@ -4050,6 +4208,14 @@ function printVersion() {
|
|
|
4050
4208
|
process.stdout.write(`${readPackageVersion()}
|
|
4051
4209
|
`);
|
|
4052
4210
|
}
|
|
4211
|
+
function formatMachineProjectRow(r) {
|
|
4212
|
+
if (r.ports) {
|
|
4213
|
+
const where = r.pid !== void 0 ? ` pid=${r.pid}` : "";
|
|
4214
|
+
return `${r.project} ${r.state} rest=${r.ports.rest} otlp=${r.ports.otlp} web=${r.ports.web} ${r.projectPath}${where}`;
|
|
4215
|
+
}
|
|
4216
|
+
const status = r.registryStatus ? ` (${r.registryStatus})` : "";
|
|
4217
|
+
return `${r.project} ${r.state}${status} ${r.projectPath}`;
|
|
4218
|
+
}
|
|
4053
4219
|
function printDiscoveryReport(opts, services) {
|
|
4054
4220
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
4055
4221
|
const mode = opts.dryRun ? "dry-run" : opts.apply ? "apply" : "patch-only";
|
|
@@ -4228,7 +4394,7 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
4228
4394
|
command: "npx",
|
|
4229
4395
|
args: ["-y", "@neat.is/mcp"],
|
|
4230
4396
|
env: {
|
|
4231
|
-
|
|
4397
|
+
NEAT_CORE_URL: "http://localhost:8080"
|
|
4232
4398
|
}
|
|
4233
4399
|
}
|
|
4234
4400
|
}
|
|
@@ -4274,6 +4440,9 @@ async function runSkill(opts) {
|
|
|
4274
4440
|
console.log("");
|
|
4275
4441
|
console.log("Manual install: copy mcpServers.neat from --print-config into ~/.claude.json,");
|
|
4276
4442
|
console.log("then restart Claude Code. See packages/claude-skill/SKILL.md for the tool list.");
|
|
4443
|
+
console.log("");
|
|
4444
|
+
console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 point it at a");
|
|
4445
|
+
console.log("non-default daemon by editing that value in the generated config.");
|
|
4277
4446
|
return { exitCode: 0 };
|
|
4278
4447
|
}
|
|
4279
4448
|
async function main() {
|
|
@@ -4375,16 +4544,14 @@ async function main() {
|
|
|
4375
4544
|
process.on("SIGINT", shutdown);
|
|
4376
4545
|
return;
|
|
4377
4546
|
}
|
|
4378
|
-
if (cmd === "list") {
|
|
4379
|
-
const
|
|
4380
|
-
if (
|
|
4381
|
-
console.log("no projects registered. run `neat init <path>` to register one.");
|
|
4547
|
+
if (cmd === "list" || cmd === "ps") {
|
|
4548
|
+
const rows = await listMachineProjects();
|
|
4549
|
+
if (rows.length === 0) {
|
|
4550
|
+
console.log("no daemons running and no projects registered. run `neat init <path>` to register one.");
|
|
4382
4551
|
return;
|
|
4383
4552
|
}
|
|
4384
|
-
for (const
|
|
4385
|
-
|
|
4386
|
-
const langs = p.languages.length > 0 ? p.languages.join(",") : "-";
|
|
4387
|
-
console.log(`${p.name} ${p.status} ${langs} ${p.path} last-seen=${seen}`);
|
|
4553
|
+
for (const r of rows) {
|
|
4554
|
+
console.log(formatMachineProjectRow(r));
|
|
4388
4555
|
}
|
|
4389
4556
|
return;
|
|
4390
4557
|
}
|
|
@@ -4395,6 +4562,15 @@ async function main() {
|
|
|
4395
4562
|
usage();
|
|
4396
4563
|
process.exit(2);
|
|
4397
4564
|
}
|
|
4565
|
+
const daemon = await findDaemonByProject(name);
|
|
4566
|
+
if (daemon) {
|
|
4567
|
+
if (daemon.live && signalDaemonStop(daemon.record.pid)) {
|
|
4568
|
+
console.log(`paused: ${name} (${daemon.record.projectPath}) \u2014 stopped daemon pid ${daemon.record.pid}`);
|
|
4569
|
+
} else {
|
|
4570
|
+
console.log(`paused: ${name} (${daemon.record.projectPath}) \u2014 daemon was not running`);
|
|
4571
|
+
}
|
|
4572
|
+
return;
|
|
4573
|
+
}
|
|
4398
4574
|
try {
|
|
4399
4575
|
const entry2 = await setStatus(name, "paused");
|
|
4400
4576
|
console.log(`paused: ${entry2.name} (${entry2.path})`);
|
|
@@ -4411,6 +4587,15 @@ async function main() {
|
|
|
4411
4587
|
usage();
|
|
4412
4588
|
process.exit(2);
|
|
4413
4589
|
}
|
|
4590
|
+
const daemon = await findDaemonByProject(name);
|
|
4591
|
+
if (daemon) {
|
|
4592
|
+
if (daemon.live) {
|
|
4593
|
+
console.log(`resume: ${name} (${daemon.record.projectPath}) \u2014 daemon already running`);
|
|
4594
|
+
} else {
|
|
4595
|
+
console.log(`resume: ${name} (${daemon.record.projectPath}) \u2014 run \`neat ${daemon.record.projectPath}\` to start its daemon again`);
|
|
4596
|
+
}
|
|
4597
|
+
return;
|
|
4598
|
+
}
|
|
4414
4599
|
try {
|
|
4415
4600
|
const entry2 = await setStatus(name, "active");
|
|
4416
4601
|
console.log(`resumed: ${entry2.name} (${entry2.path})`);
|
|
@@ -4432,15 +4617,36 @@ async function main() {
|
|
|
4432
4617
|
usage();
|
|
4433
4618
|
process.exit(2);
|
|
4434
4619
|
}
|
|
4620
|
+
const daemon = await findDaemonByProject(name);
|
|
4435
4621
|
const removed = await removeProject(name);
|
|
4436
|
-
if (!removed) {
|
|
4622
|
+
if (!daemon && !removed) {
|
|
4437
4623
|
console.error(`neat uninstall: no project named "${name}"`);
|
|
4438
4624
|
process.exit(1);
|
|
4439
4625
|
}
|
|
4440
|
-
|
|
4626
|
+
const projectPath2 = daemon?.record.projectPath ?? removed?.path ?? "(unknown path)";
|
|
4627
|
+
if (daemon) {
|
|
4628
|
+
if (daemon.live && signalDaemonStop(daemon.record.pid)) {
|
|
4629
|
+
console.log(`uninstall: ${name} \u2014 stopped daemon pid ${daemon.record.pid}`);
|
|
4630
|
+
}
|
|
4631
|
+
await removeDaemonRecord(daemon.source);
|
|
4632
|
+
}
|
|
4633
|
+
console.log(`unregistered: ${name} (${projectPath2})`);
|
|
4441
4634
|
console.log("note: neat-out/, policy.json, and other files at the project path were left in place.");
|
|
4442
4635
|
return;
|
|
4443
4636
|
}
|
|
4637
|
+
if (cmd === "prune") {
|
|
4638
|
+
const removed = await pruneRegistry({ ttlMs: 0 });
|
|
4639
|
+
if (parsed.json) {
|
|
4640
|
+
console.log(JSON.stringify(removed.map((p) => ({ name: p.name, path: p.path })), null, 2));
|
|
4641
|
+
return;
|
|
4642
|
+
}
|
|
4643
|
+
if (removed.length === 0) {
|
|
4644
|
+
console.log("nothing to prune \u2014 every registered project path still exists.");
|
|
4645
|
+
return;
|
|
4646
|
+
}
|
|
4647
|
+
console.log(`pruned ${removed.length} project${removed.length === 1 ? "" : "s"}: ${removed.map((p) => p.name).join(", ")}`);
|
|
4648
|
+
return;
|
|
4649
|
+
}
|
|
4444
4650
|
if (cmd === "deploy") {
|
|
4445
4651
|
const artifact = await runDeploy();
|
|
4446
4652
|
const block = renderOtelEnvBlock2(artifact.token);
|
|
@@ -4528,12 +4734,44 @@ function resolveProjectFlag(parsed) {
|
|
|
4528
4734
|
if (env && env.length > 0 && env !== DEFAULT_PROJECT) return env;
|
|
4529
4735
|
return void 0;
|
|
4530
4736
|
}
|
|
4737
|
+
var ProjectResolutionError = class extends Error {
|
|
4738
|
+
constructor(message, exitCode = 2) {
|
|
4739
|
+
super(message);
|
|
4740
|
+
this.exitCode = exitCode;
|
|
4741
|
+
this.name = "ProjectResolutionError";
|
|
4742
|
+
}
|
|
4743
|
+
exitCode;
|
|
4744
|
+
};
|
|
4745
|
+
async function resolveProjectForVerb(client, parsed) {
|
|
4746
|
+
const explicit = resolveProjectFlag(parsed);
|
|
4747
|
+
if (explicit) return explicit;
|
|
4748
|
+
const projects = await client.get("/projects");
|
|
4749
|
+
if (projects.some((p) => p.name === DEFAULT_PROJECT)) {
|
|
4750
|
+
return void 0;
|
|
4751
|
+
}
|
|
4752
|
+
if (projects.length === 1) {
|
|
4753
|
+
return projects[0].name;
|
|
4754
|
+
}
|
|
4755
|
+
if (projects.length === 0) {
|
|
4756
|
+
throw new ProjectResolutionError(
|
|
4757
|
+
"No projects are registered with the daemon yet. Run `npx neat.is` in a repo to build a graph first, then re-run this command."
|
|
4758
|
+
);
|
|
4759
|
+
}
|
|
4760
|
+
const names = projects.map((p) => p.name).sort().map((n) => ` ${n}`).join("\n");
|
|
4761
|
+
throw new ProjectResolutionError(
|
|
4762
|
+
`Several projects are registered and none is named "default", so I can't pick one for you.
|
|
4763
|
+
Pass --project <name> to choose:
|
|
4764
|
+
${names}`
|
|
4765
|
+
);
|
|
4766
|
+
}
|
|
4767
|
+
function resolveDaemonUrl() {
|
|
4768
|
+
return process.env.NEAT_API_URL ?? process.env.NEAT_CORE_URL ?? "http://localhost:8080";
|
|
4769
|
+
}
|
|
4531
4770
|
async function runQueryVerb(cmd, parsed) {
|
|
4532
|
-
const baseUrl =
|
|
4771
|
+
const baseUrl = resolveDaemonUrl();
|
|
4533
4772
|
const client = createHttpClient(baseUrl, resolveAuthToken());
|
|
4534
|
-
const project = resolveProjectFlag(parsed);
|
|
4535
4773
|
const positional = parsed.positional;
|
|
4536
|
-
let
|
|
4774
|
+
let makeWork;
|
|
4537
4775
|
switch (cmd) {
|
|
4538
4776
|
case "root-cause": {
|
|
4539
4777
|
const node = positional[0];
|
|
@@ -4541,7 +4779,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4541
4779
|
console.error("neat root-cause: missing <node-id>");
|
|
4542
4780
|
return 2;
|
|
4543
4781
|
}
|
|
4544
|
-
|
|
4782
|
+
makeWork = (project) => runRootCause(client, {
|
|
4545
4783
|
errorNode: node,
|
|
4546
4784
|
...parsed.errorId ? { errorId: parsed.errorId } : {},
|
|
4547
4785
|
...project ? { project } : {}
|
|
@@ -4554,7 +4792,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4554
4792
|
console.error("neat blast-radius: missing <node-id>");
|
|
4555
4793
|
return 2;
|
|
4556
4794
|
}
|
|
4557
|
-
|
|
4795
|
+
makeWork = (project) => runBlastRadius(client, {
|
|
4558
4796
|
nodeId: node,
|
|
4559
4797
|
...parsed.depth !== null ? { depth: parsed.depth } : {},
|
|
4560
4798
|
...project ? { project } : {}
|
|
@@ -4567,7 +4805,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4567
4805
|
console.error("neat dependencies: missing <node-id>");
|
|
4568
4806
|
return 2;
|
|
4569
4807
|
}
|
|
4570
|
-
|
|
4808
|
+
makeWork = (project) => runDependencies(client, {
|
|
4571
4809
|
nodeId: node,
|
|
4572
4810
|
...parsed.depth !== null ? { depth: parsed.depth } : {},
|
|
4573
4811
|
...project ? { project } : {}
|
|
@@ -4580,14 +4818,14 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4580
4818
|
console.error("neat observed-dependencies: missing <node-id>");
|
|
4581
4819
|
return 2;
|
|
4582
4820
|
}
|
|
4583
|
-
|
|
4821
|
+
makeWork = (project) => runObservedDependencies(client, {
|
|
4584
4822
|
nodeId: node,
|
|
4585
4823
|
...project ? { project } : {}
|
|
4586
4824
|
});
|
|
4587
4825
|
break;
|
|
4588
4826
|
}
|
|
4589
4827
|
case "incidents": {
|
|
4590
|
-
|
|
4828
|
+
makeWork = (project) => runIncidents(client, {
|
|
4591
4829
|
...positional[0] ? { nodeId: positional[0] } : {},
|
|
4592
4830
|
...parsed.limit !== null ? { limit: parsed.limit } : {},
|
|
4593
4831
|
...project ? { project } : {}
|
|
@@ -4600,7 +4838,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4600
4838
|
console.error("neat search: missing <query>");
|
|
4601
4839
|
return 2;
|
|
4602
4840
|
}
|
|
4603
|
-
|
|
4841
|
+
makeWork = (project) => runSearch(client, { query: q, ...project ? { project } : {} });
|
|
4604
4842
|
break;
|
|
4605
4843
|
}
|
|
4606
4844
|
case "diff": {
|
|
@@ -4609,14 +4847,14 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4609
4847
|
console.error("neat diff: --against <snapshot-path> is required");
|
|
4610
4848
|
return 2;
|
|
4611
4849
|
}
|
|
4612
|
-
|
|
4850
|
+
makeWork = (project) => runDiff(client, {
|
|
4613
4851
|
againstSnapshot: against,
|
|
4614
4852
|
...project ? { project } : {}
|
|
4615
4853
|
});
|
|
4616
4854
|
break;
|
|
4617
4855
|
}
|
|
4618
4856
|
case "stale-edges": {
|
|
4619
|
-
|
|
4857
|
+
makeWork = (project) => runStaleEdges(client, {
|
|
4620
4858
|
...parsed.limit !== null ? { limit: parsed.limit } : {},
|
|
4621
4859
|
...parsed.edgeType ? { edgeType: parsed.edgeType } : {},
|
|
4622
4860
|
...project ? { project } : {}
|
|
@@ -4635,7 +4873,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4635
4873
|
return 2;
|
|
4636
4874
|
}
|
|
4637
4875
|
}
|
|
4638
|
-
|
|
4876
|
+
makeWork = (project) => runPolicies(client, {
|
|
4639
4877
|
...parsed.node ? { nodeId: parsed.node } : {},
|
|
4640
4878
|
...hypothetical ? { hypotheticalAction: hypothetical } : {},
|
|
4641
4879
|
...project ? { project } : {}
|
|
@@ -4659,7 +4897,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4659
4897
|
}
|
|
4660
4898
|
typeFilter = out;
|
|
4661
4899
|
}
|
|
4662
|
-
|
|
4900
|
+
makeWork = (project) => runDivergences(client, {
|
|
4663
4901
|
...typeFilter ? { type: typeFilter } : {},
|
|
4664
4902
|
...parsed.minConfidence !== null ? { minConfidence: parsed.minConfidence } : {},
|
|
4665
4903
|
...parsed.node ? { node: parsed.node } : {},
|
|
@@ -4672,16 +4910,21 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4672
4910
|
return 2;
|
|
4673
4911
|
}
|
|
4674
4912
|
try {
|
|
4675
|
-
const
|
|
4913
|
+
const project = await resolveProjectForVerb(client, parsed);
|
|
4914
|
+
const result = await makeWork(project);
|
|
4676
4915
|
if (parsed.json) process.stdout.write(formatJson(result) + "\n");
|
|
4677
4916
|
else process.stdout.write(formatHuman(result) + "\n");
|
|
4678
4917
|
return 0;
|
|
4679
4918
|
} catch (err) {
|
|
4919
|
+
if (err instanceof ProjectResolutionError) {
|
|
4920
|
+
console.error(`neat ${cmd}: ${err.message}`);
|
|
4921
|
+
return err.exitCode;
|
|
4922
|
+
}
|
|
4680
4923
|
if (err instanceof HttpError) {
|
|
4681
4924
|
const detail = err.responseBody.length > 0 ? err.responseBody : err.message;
|
|
4682
4925
|
console.error(`neat ${cmd}: ${detail.trim()}`);
|
|
4683
4926
|
} else if (err instanceof TransportError) {
|
|
4684
|
-
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (NEAT_API_URL=${
|
|
4927
|
+
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (NEAT_API_URL=${resolveDaemonUrl()})`);
|
|
4685
4928
|
} else {
|
|
4686
4929
|
console.error(`neat ${cmd}: ${err.message}`);
|
|
4687
4930
|
}
|
|
@@ -4697,6 +4940,7 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
|
|
|
4697
4940
|
}
|
|
4698
4941
|
export {
|
|
4699
4942
|
CLAUDE_SKILL_CONFIG,
|
|
4943
|
+
ProjectResolutionError,
|
|
4700
4944
|
QUERY_VERBS,
|
|
4701
4945
|
commandPrefix,
|
|
4702
4946
|
isNpxInvocation,
|
|
@@ -4704,6 +4948,7 @@ export {
|
|
|
4704
4948
|
parseArgs,
|
|
4705
4949
|
printBanner,
|
|
4706
4950
|
readPackageVersion,
|
|
4951
|
+
resolveProjectForVerb,
|
|
4707
4952
|
runInit,
|
|
4708
4953
|
runQueryVerb,
|
|
4709
4954
|
runSkill,
|