@neat.is/core 0.6.3 → 0.7.0
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-L64CATKE.js → chunk-5RIL3U5A.js} +3754 -716
- package/dist/chunk-5RIL3U5A.js.map +1 -0
- package/dist/{chunk-ZLAZ7PLC.js → chunk-N5L3RBGP.js} +3 -2
- package/dist/{chunk-7QXN726V.js → chunk-Q6DPK3RA.js} +2 -2
- package/dist/chunk-UBQ4ZZT3.js +840 -0
- package/dist/chunk-UBQ4ZZT3.js.map +1 -0
- package/dist/cli.cjs +4632 -3412
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +263 -133
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +4245 -3097
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +119 -106
- package/dist/index.d.ts +119 -106
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +4248 -3100
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-WXKM7YBW.js → otel-grpc-ZNC2QED2.js} +3 -3
- package/dist/server.cjs +3680 -649
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +3 -2
- package/dist/chunk-L64CATKE.js.map +0 -1
- package/dist/chunk-PWIT35Z4.js +0 -2810
- package/dist/chunk-PWIT35Z4.js.map +0 -1
- /package/dist/{chunk-ZLAZ7PLC.js.map → chunk-N5L3RBGP.js.map} +0 -0
- /package/dist/{chunk-7QXN726V.js.map → chunk-Q6DPK3RA.js.map} +0 -0
- /package/dist/{otel-grpc-WXKM7YBW.js.map → otel-grpc-ZNC2QED2.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
clearDaemonRecord,
|
|
4
|
-
deprovisionConnector,
|
|
5
|
-
getProviderFieldSchema,
|
|
6
|
-
isPushProvider,
|
|
7
|
-
knownProviderNames,
|
|
8
4
|
portFromListenAddress,
|
|
9
|
-
provisionConnector,
|
|
10
5
|
readDaemonRecord,
|
|
11
6
|
resolveHost,
|
|
12
7
|
resolveNeatVersion,
|
|
13
|
-
startConnectorPolling,
|
|
14
|
-
validateConnectorEntry,
|
|
15
8
|
writeDaemonRecord
|
|
16
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-UBQ4ZZT3.js";
|
|
17
10
|
import {
|
|
18
11
|
buildSearchIndex
|
|
19
12
|
} from "./chunk-BIY46Q6U.js";
|
|
@@ -36,6 +29,7 @@ import {
|
|
|
36
29
|
autoSlugConnectorId,
|
|
37
30
|
buildApi,
|
|
38
31
|
computeDivergences,
|
|
32
|
+
deprovisionConnector,
|
|
39
33
|
describeCredential,
|
|
40
34
|
detectPackageManager,
|
|
41
35
|
discoverServices,
|
|
@@ -47,8 +41,11 @@ import {
|
|
|
47
41
|
formatExtractionBanner,
|
|
48
42
|
formatPrecisionFloorBanner,
|
|
49
43
|
getGraph,
|
|
44
|
+
getProviderFieldSchema,
|
|
50
45
|
isEnvRef,
|
|
46
|
+
isPushProvider,
|
|
51
47
|
isStrictExtractionEnabled,
|
|
48
|
+
knownProviderNames,
|
|
52
49
|
listMachineProjects,
|
|
53
50
|
listProjects,
|
|
54
51
|
loadGraphFromDisk,
|
|
@@ -58,6 +55,7 @@ import {
|
|
|
58
55
|
normalizeProjectPath,
|
|
59
56
|
pathsForProject,
|
|
60
57
|
promoteFrontierNodes,
|
|
58
|
+
provisionConnector,
|
|
61
59
|
pruneRegistry,
|
|
62
60
|
readConnectorsConfig,
|
|
63
61
|
removeConnectorEntry,
|
|
@@ -69,13 +67,15 @@ import {
|
|
|
69
67
|
saveGraphToDisk,
|
|
70
68
|
setStatus,
|
|
71
69
|
signalDaemonStop,
|
|
70
|
+
startConnectorPolling,
|
|
72
71
|
startPersistLoop,
|
|
73
72
|
startStalenessLoop,
|
|
74
|
-
upsertConnectorEntry
|
|
75
|
-
|
|
73
|
+
upsertConnectorEntry,
|
|
74
|
+
validateConnectorEntry
|
|
75
|
+
} from "./chunk-5RIL3U5A.js";
|
|
76
76
|
import {
|
|
77
77
|
startOtelGrpcReceiver
|
|
78
|
-
} from "./chunk-
|
|
78
|
+
} from "./chunk-Q6DPK3RA.js";
|
|
79
79
|
import {
|
|
80
80
|
__dirname,
|
|
81
81
|
__require,
|
|
@@ -83,12 +83,12 @@ import {
|
|
|
83
83
|
buildOtelReceiver,
|
|
84
84
|
listenSteppingOtlp,
|
|
85
85
|
readAuthEnv
|
|
86
|
-
} from "./chunk-
|
|
86
|
+
} from "./chunk-N5L3RBGP.js";
|
|
87
87
|
|
|
88
88
|
// src/cli.ts
|
|
89
|
-
import
|
|
89
|
+
import path11 from "path";
|
|
90
90
|
import os2 from "os";
|
|
91
|
-
import { promises as
|
|
91
|
+
import { promises as fs9 } from "fs";
|
|
92
92
|
|
|
93
93
|
// src/banner.ts
|
|
94
94
|
import path from "path";
|
|
@@ -2949,9 +2949,137 @@ var pythonInstaller = {
|
|
|
2949
2949
|
apply: apply2
|
|
2950
2950
|
};
|
|
2951
2951
|
|
|
2952
|
+
// src/installers/go.ts
|
|
2953
|
+
import { promises as fs6 } from "fs";
|
|
2954
|
+
import path7 from "path";
|
|
2955
|
+
var GO_DEPS = [
|
|
2956
|
+
{ name: "go.opentelemetry.io/otel", version: "v1.38.0" },
|
|
2957
|
+
{ name: "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp", version: "v1.38.0" },
|
|
2958
|
+
{ name: "go.opentelemetry.io/otel/sdk", version: "v1.38.0" }
|
|
2959
|
+
];
|
|
2960
|
+
async function exists3(file) {
|
|
2961
|
+
return fs6.stat(file).then(() => true, () => false);
|
|
2962
|
+
}
|
|
2963
|
+
async function findMain(serviceDir) {
|
|
2964
|
+
const root = path7.join(serviceDir, "main.go");
|
|
2965
|
+
if (await exists3(root)) return root;
|
|
2966
|
+
const cmd = path7.join(serviceDir, "cmd");
|
|
2967
|
+
const entries = await fs6.readdir(cmd, { withFileTypes: true }).catch(() => []);
|
|
2968
|
+
for (const entry2 of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2969
|
+
if (!entry2.isDirectory()) continue;
|
|
2970
|
+
const candidate = path7.join(cmd, entry2.name, "main.go");
|
|
2971
|
+
if (await exists3(candidate)) return candidate;
|
|
2972
|
+
}
|
|
2973
|
+
return null;
|
|
2974
|
+
}
|
|
2975
|
+
function neatOtelGo(packageName = "main") {
|
|
2976
|
+
return `// neat-otel-init v1 \u2014 generated by NEAT. Safe to re-generate; do not edit.
|
|
2977
|
+
package ${packageName}
|
|
2978
|
+
|
|
2979
|
+
import (
|
|
2980
|
+
"context"
|
|
2981
|
+
"os"
|
|
2982
|
+
"path/filepath"
|
|
2983
|
+
"runtime"
|
|
2984
|
+
"strings"
|
|
2985
|
+
|
|
2986
|
+
"go.opentelemetry.io/otel"
|
|
2987
|
+
"go.opentelemetry.io/otel/attribute"
|
|
2988
|
+
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
|
|
2989
|
+
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
|
2990
|
+
"go.opentelemetry.io/otel/trace"
|
|
2991
|
+
)
|
|
2992
|
+
|
|
2993
|
+
type neatCallSiteSpanProcessor struct { root string }
|
|
2994
|
+
|
|
2995
|
+
func (p neatCallSiteSpanProcessor) OnStart(_ context.Context, span sdktrace.ReadWriteSpan) {
|
|
2996
|
+
if span.SpanKind() != trace.SpanKindClient && span.SpanKind() != trace.SpanKindProducer { return }
|
|
2997
|
+
pcs := make([]uintptr, 32)
|
|
2998
|
+
n := runtime.Callers(2, pcs)
|
|
2999
|
+
frames := runtime.CallersFrames(pcs[:n])
|
|
3000
|
+
for {
|
|
3001
|
+
frame, more := frames.Next()
|
|
3002
|
+
clean := filepath.Clean(frame.File)
|
|
3003
|
+
slash := filepath.ToSlash(clean)
|
|
3004
|
+
if clean != "" && strings.HasPrefix(clean, p.root+string(os.PathSeparator)) && !strings.Contains(slash, "/vendor/") && !strings.HasSuffix(slash, "/neat_otel.go") {
|
|
3005
|
+
span.SetAttributes(
|
|
3006
|
+
attribute.String("code.file.path", clean),
|
|
3007
|
+
attribute.Int("code.line.number", frame.Line),
|
|
3008
|
+
attribute.String("code.function.name", frame.Function),
|
|
3009
|
+
)
|
|
3010
|
+
return
|
|
3011
|
+
}
|
|
3012
|
+
if !more { return }
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
func (neatCallSiteSpanProcessor) OnEnd(sdktrace.ReadOnlySpan) {}
|
|
3016
|
+
func (neatCallSiteSpanProcessor) Shutdown(context.Context) error { return nil }
|
|
3017
|
+
func (neatCallSiteSpanProcessor) ForceFlush(context.Context) error { return nil }
|
|
3018
|
+
|
|
3019
|
+
func init() {
|
|
3020
|
+
root := os.Getenv("NEAT_SERVICE_ROOT")
|
|
3021
|
+
if root == "" {
|
|
3022
|
+
_, self, _, ok := runtime.Caller(0)
|
|
3023
|
+
if !ok { return }
|
|
3024
|
+
root = filepath.Dir(self)
|
|
3025
|
+
for {
|
|
3026
|
+
if _, err := os.Stat(filepath.Join(root, "go.mod")); err == nil { break }
|
|
3027
|
+
parent := filepath.Dir(root)
|
|
3028
|
+
if parent == root { return }
|
|
3029
|
+
root = parent
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
root, _ = filepath.Abs(root)
|
|
3033
|
+
if canonical, err := filepath.EvalSymlinks(root); err == nil { root = canonical }
|
|
3034
|
+
exporter, err := otlptracehttp.New(context.Background())
|
|
3035
|
+
if err != nil { return }
|
|
3036
|
+
provider := sdktrace.NewTracerProvider(
|
|
3037
|
+
sdktrace.WithBatcher(exporter),
|
|
3038
|
+
sdktrace.WithSpanProcessor(neatCallSiteSpanProcessor{root: filepath.Clean(root)}),
|
|
3039
|
+
)
|
|
3040
|
+
otel.SetTracerProvider(provider)
|
|
3041
|
+
}
|
|
3042
|
+
`;
|
|
3043
|
+
}
|
|
3044
|
+
async function detect3(serviceDir) {
|
|
3045
|
+
return exists3(path7.join(serviceDir, "go.mod"));
|
|
3046
|
+
}
|
|
3047
|
+
async function plan3(serviceDir) {
|
|
3048
|
+
const manifest = path7.join(serviceDir, "go.mod");
|
|
3049
|
+
const raw = await fs6.readFile(manifest, "utf8");
|
|
3050
|
+
const main2 = await findMain(serviceDir);
|
|
3051
|
+
const dependencyEdits = GO_DEPS.filter((dep) => !raw.includes(dep.name)).map((dep) => ({ file: manifest, kind: "add", ...dep }));
|
|
3052
|
+
if (!main2) return { language: "go", serviceDir, dependencyEdits: [], entrypointEdits: [], envEdits: [], libOnly: true };
|
|
3053
|
+
const source = await fs6.readFile(main2, "utf8");
|
|
3054
|
+
const packageName = source.match(/^\s*package\s+(\w+)\s*$/m)?.[1] ?? "main";
|
|
3055
|
+
const generated = path7.join(path7.dirname(main2), "neat_otel.go");
|
|
3056
|
+
const generatedFiles = await exists3(generated) ? [] : [{ file: generated, contents: neatOtelGo(packageName) }];
|
|
3057
|
+
return { language: "go", serviceDir, dependencyEdits, entrypointEdits: [], envEdits: [], generatedFiles, entryFile: main2 };
|
|
3058
|
+
}
|
|
3059
|
+
async function apply3(installPlan) {
|
|
3060
|
+
if (installPlan.libOnly) return { serviceDir: installPlan.serviceDir, outcome: "lib-only", writtenFiles: [] };
|
|
3061
|
+
const writtenFiles = [];
|
|
3062
|
+
const byManifest = /* @__PURE__ */ new Map();
|
|
3063
|
+
for (const edit of installPlan.dependencyEdits) byManifest.set(edit.file, [...byManifest.get(edit.file) ?? [], edit]);
|
|
3064
|
+
for (const [manifest, edits] of byManifest) {
|
|
3065
|
+
const raw = await fs6.readFile(manifest, "utf8");
|
|
3066
|
+
const suffix = raw.endsWith("\n") ? "" : "\n";
|
|
3067
|
+
await fs6.writeFile(manifest, `${raw}${suffix}${edits.map((e) => `require ${e.name} ${e.version}`).join("\n")}
|
|
3068
|
+
`, "utf8");
|
|
3069
|
+
writtenFiles.push(manifest);
|
|
3070
|
+
}
|
|
3071
|
+
for (const generated of installPlan.generatedFiles ?? []) {
|
|
3072
|
+
if (await exists3(generated.file)) continue;
|
|
3073
|
+
await fs6.writeFile(generated.file, generated.contents, "utf8");
|
|
3074
|
+
writtenFiles.push(generated.file);
|
|
3075
|
+
}
|
|
3076
|
+
return { serviceDir: installPlan.serviceDir, outcome: writtenFiles.length ? "instrumented" : "already-instrumented", writtenFiles };
|
|
3077
|
+
}
|
|
3078
|
+
var goInstaller = { name: "go", detect: detect3, plan: plan3, apply: apply3 };
|
|
3079
|
+
|
|
2952
3080
|
// src/installers/shared.ts
|
|
2953
|
-
function isEmptyPlan(
|
|
2954
|
-
return
|
|
3081
|
+
function isEmptyPlan(plan4) {
|
|
3082
|
+
return plan4.dependencyEdits.length === 0 && plan4.entrypointEdits.length === 0 && plan4.envEdits.length === 0 && (plan4.generatedFiles?.length ?? 0) === 0 && plan4.nextConfigEdit === void 0;
|
|
2955
3083
|
}
|
|
2956
3084
|
|
|
2957
3085
|
// src/installers/index.ts
|
|
@@ -2965,7 +3093,7 @@ var FORBIDDEN_LOCKFILES = /* @__PURE__ */ new Set([
|
|
|
2965
3093
|
"Cargo.lock",
|
|
2966
3094
|
"go.sum"
|
|
2967
3095
|
]);
|
|
2968
|
-
var INSTALLERS = [javascriptInstaller, pythonInstaller];
|
|
3096
|
+
var INSTALLERS = [javascriptInstaller, pythonInstaller, goInstaller];
|
|
2969
3097
|
async function pickInstaller(serviceDir) {
|
|
2970
3098
|
for (const inst of INSTALLERS) {
|
|
2971
3099
|
if (await inst.detect(serviceDir)) return inst;
|
|
@@ -2980,7 +3108,7 @@ function renderPatch(sections) {
|
|
|
2980
3108
|
"No SDK installers matched the discovered services. Two reasons this",
|
|
2981
3109
|
"normally happens:",
|
|
2982
3110
|
" - the project uses a language NEAT does not yet instrument",
|
|
2983
|
-
" (Java / Ruby / .NET /
|
|
3111
|
+
" (Java / Ruby / .NET / Rust are out of MVP scope per ADR-047);",
|
|
2984
3112
|
" - the SDK is already installed, so the installer returned an empty",
|
|
2985
3113
|
" plan.",
|
|
2986
3114
|
"",
|
|
@@ -2990,22 +3118,22 @@ function renderPatch(sections) {
|
|
|
2990
3118
|
}
|
|
2991
3119
|
const lines = ["# neat install plan", ""];
|
|
2992
3120
|
for (const section of sections) {
|
|
2993
|
-
const { installer, plan:
|
|
2994
|
-
lines.push(`## ${installer} (${
|
|
3121
|
+
const { installer, plan: plan4 } = section;
|
|
3122
|
+
lines.push(`## ${installer} (${plan4.language}) \u2014 ${plan4.serviceDir}`);
|
|
2995
3123
|
lines.push("");
|
|
2996
|
-
if (
|
|
3124
|
+
if (plan4.libOnly) {
|
|
2997
3125
|
lines.push("### skipped \u2014 no resolvable entry point (lib-only)");
|
|
2998
3126
|
lines.push("");
|
|
2999
3127
|
continue;
|
|
3000
3128
|
}
|
|
3001
|
-
if (
|
|
3002
|
-
lines.push(`entry: ${
|
|
3129
|
+
if (plan4.entryFile) {
|
|
3130
|
+
lines.push(`entry: ${plan4.entryFile}`);
|
|
3003
3131
|
lines.push("");
|
|
3004
3132
|
}
|
|
3005
|
-
if (
|
|
3133
|
+
if (plan4.dependencyEdits.length > 0) {
|
|
3006
3134
|
lines.push("### dependencies");
|
|
3007
3135
|
const byFile = /* @__PURE__ */ new Map();
|
|
3008
|
-
for (const dep of
|
|
3136
|
+
for (const dep of plan4.dependencyEdits) {
|
|
3009
3137
|
const base = dep.file.split(/[\\/]/).pop() ?? dep.file;
|
|
3010
3138
|
if (FORBIDDEN_LOCKFILES.has(base)) {
|
|
3011
3139
|
throw new Error(
|
|
@@ -3024,9 +3152,9 @@ function renderPatch(sections) {
|
|
|
3024
3152
|
}
|
|
3025
3153
|
lines.push("");
|
|
3026
3154
|
}
|
|
3027
|
-
if (
|
|
3155
|
+
if (plan4.generatedFiles && plan4.generatedFiles.length > 0) {
|
|
3028
3156
|
lines.push("### generated files");
|
|
3029
|
-
for (const gen of
|
|
3157
|
+
for (const gen of plan4.generatedFiles) {
|
|
3030
3158
|
lines.push(`--- (new file) ${gen.file}`);
|
|
3031
3159
|
for (const ln of gen.contents.split(/\r?\n/)) {
|
|
3032
3160
|
lines.push(`+ ${ln}`);
|
|
@@ -3034,26 +3162,26 @@ function renderPatch(sections) {
|
|
|
3034
3162
|
}
|
|
3035
3163
|
lines.push("");
|
|
3036
3164
|
}
|
|
3037
|
-
if (
|
|
3165
|
+
if (plan4.entrypointEdits.length > 0) {
|
|
3038
3166
|
lines.push("### entry-point injection");
|
|
3039
|
-
for (const e of
|
|
3167
|
+
for (const e of plan4.entrypointEdits) {
|
|
3040
3168
|
lines.push(`--- ${e.file}`);
|
|
3041
3169
|
lines.push(`+ ${e.after}`);
|
|
3042
3170
|
lines.push(` ${e.before}`);
|
|
3043
3171
|
}
|
|
3044
3172
|
lines.push("");
|
|
3045
3173
|
}
|
|
3046
|
-
if (
|
|
3174
|
+
if (plan4.envEdits.length > 0) {
|
|
3047
3175
|
lines.push("### env (written to <package-dir>/.env.neat)");
|
|
3048
|
-
for (const env of
|
|
3176
|
+
for (const env of plan4.envEdits) {
|
|
3049
3177
|
lines.push(`- ${env.key}=${env.value}`);
|
|
3050
3178
|
}
|
|
3051
3179
|
lines.push("");
|
|
3052
3180
|
}
|
|
3053
|
-
if (
|
|
3181
|
+
if (plan4.nextConfigEdit) {
|
|
3054
3182
|
lines.push("### next.config (framework flag)");
|
|
3055
|
-
lines.push(`--- ${
|
|
3056
|
-
lines.push(`+ experimental: { instrumentationHook: true }, // ${
|
|
3183
|
+
lines.push(`--- ${plan4.nextConfigEdit.file}`);
|
|
3184
|
+
lines.push(`+ experimental: { instrumentationHook: true }, // ${plan4.nextConfigEdit.reason}`);
|
|
3057
3185
|
lines.push("");
|
|
3058
3186
|
}
|
|
3059
3187
|
}
|
|
@@ -3061,10 +3189,10 @@ function renderPatch(sections) {
|
|
|
3061
3189
|
}
|
|
3062
3190
|
|
|
3063
3191
|
// src/orchestrator.ts
|
|
3064
|
-
import { promises as
|
|
3192
|
+
import { promises as fs7 } from "fs";
|
|
3065
3193
|
import http from "http";
|
|
3066
3194
|
import net from "net";
|
|
3067
|
-
import
|
|
3195
|
+
import path8 from "path";
|
|
3068
3196
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
3069
3197
|
import { spawn as spawn2 } from "child_process";
|
|
3070
3198
|
import readline from "readline";
|
|
@@ -3074,7 +3202,7 @@ async function extractAndPersist(opts) {
|
|
|
3074
3202
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
3075
3203
|
resetGraph(graphKey);
|
|
3076
3204
|
const graph = getGraph(graphKey);
|
|
3077
|
-
const projectPaths = pathsForProject(graphKey,
|
|
3205
|
+
const projectPaths = pathsForProject(graphKey, path8.join(opts.scanPath, "neat-out"));
|
|
3078
3206
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
3079
3207
|
errorsPath: projectPaths.errorsPath
|
|
3080
3208
|
});
|
|
@@ -3108,15 +3236,15 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
3108
3236
|
for (const svc of services) {
|
|
3109
3237
|
const installer = await pickInstaller(svc.dir);
|
|
3110
3238
|
if (!installer) continue;
|
|
3111
|
-
const
|
|
3112
|
-
if (isEmptyPlan(
|
|
3239
|
+
const plan4 = await installer.plan(svc.dir, { project });
|
|
3240
|
+
if (isEmptyPlan(plan4) && !plan4.libOnly && plan4.runtimeKind === void 0) {
|
|
3113
3241
|
already++;
|
|
3114
3242
|
continue;
|
|
3115
3243
|
}
|
|
3116
|
-
const outcome = await installer.apply(
|
|
3244
|
+
const outcome = await installer.apply(plan4);
|
|
3117
3245
|
if (outcome.outcome === "instrumented") {
|
|
3118
3246
|
instrumented++;
|
|
3119
|
-
if (
|
|
3247
|
+
if (plan4.dependencyEdits.length > 0) {
|
|
3120
3248
|
const cmd = await resolveManager(svc.dir);
|
|
3121
3249
|
const key = `${cmd.pm}:${cmd.cwd}`;
|
|
3122
3250
|
if (!installPlans.has(key)) installPlans.set(key, cmd);
|
|
@@ -3126,7 +3254,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
3126
3254
|
libOnly++;
|
|
3127
3255
|
const appDeps = svc.pkg ? appFrameworkDependencies(svc.pkg) : [];
|
|
3128
3256
|
if (appDeps.length > 0) {
|
|
3129
|
-
const svcName =
|
|
3257
|
+
const svcName = path8.basename(svc.dir);
|
|
3130
3258
|
const list = appDeps.join(", ");
|
|
3131
3259
|
console.warn(
|
|
3132
3260
|
`neat: runtime layer won't engage for ${svcName}: no entry point found.
|
|
@@ -3139,7 +3267,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
3139
3267
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
3140
3268
|
} else if (outcome.outcome === "react-native") {
|
|
3141
3269
|
reactNative++;
|
|
3142
|
-
const svcName =
|
|
3270
|
+
const svcName = path8.basename(svc.dir);
|
|
3143
3271
|
console.log(
|
|
3144
3272
|
`neat: ${svc.dir} detected as React Native / Expo
|
|
3145
3273
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -3150,7 +3278,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
3150
3278
|
);
|
|
3151
3279
|
} else if (outcome.outcome === "bun") {
|
|
3152
3280
|
bun++;
|
|
3153
|
-
const svcName =
|
|
3281
|
+
const svcName = path8.basename(svc.dir);
|
|
3154
3282
|
console.log(
|
|
3155
3283
|
`neat: ${svc.dir} detected as Bun
|
|
3156
3284
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -3161,7 +3289,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
3161
3289
|
);
|
|
3162
3290
|
} else if (outcome.outcome === "deno") {
|
|
3163
3291
|
deno++;
|
|
3164
|
-
const svcName =
|
|
3292
|
+
const svcName = path8.basename(svc.dir);
|
|
3165
3293
|
console.log(
|
|
3166
3294
|
`neat: ${svc.dir} detected as Deno
|
|
3167
3295
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -3172,7 +3300,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
3172
3300
|
);
|
|
3173
3301
|
} else if (outcome.outcome === "cloudflare-workers") {
|
|
3174
3302
|
cloudflareWorkers++;
|
|
3175
|
-
const svcName =
|
|
3303
|
+
const svcName = path8.basename(svc.dir);
|
|
3176
3304
|
console.log(
|
|
3177
3305
|
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
3178
3306
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -3183,7 +3311,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
3183
3311
|
);
|
|
3184
3312
|
} else if (outcome.outcome === "electron") {
|
|
3185
3313
|
electron++;
|
|
3186
|
-
const svcName =
|
|
3314
|
+
const svcName = path8.basename(svc.dir);
|
|
3187
3315
|
console.log(
|
|
3188
3316
|
`neat: ${svc.dir} detected as Electron
|
|
3189
3317
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -3196,7 +3324,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
3196
3324
|
if (svc.pkg && (outcome.outcome === "instrumented" || outcome.outcome === "already-instrumented")) {
|
|
3197
3325
|
const gaps = uninstrumentedLibraries(svc.pkg);
|
|
3198
3326
|
if (gaps.length > 0) {
|
|
3199
|
-
const svcName =
|
|
3327
|
+
const svcName = path8.basename(svc.dir);
|
|
3200
3328
|
const list = gaps.join(", ");
|
|
3201
3329
|
const subject = gaps.length === 1 ? "this library" : "these libraries";
|
|
3202
3330
|
const aux = gaps.length === 1 ? "isn't" : "aren't";
|
|
@@ -3402,24 +3530,24 @@ async function persistedPortsFor(scanPath) {
|
|
|
3402
3530
|
return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
|
|
3403
3531
|
}
|
|
3404
3532
|
async function acquireSpawnLock(scanPath) {
|
|
3405
|
-
const lockPath =
|
|
3406
|
-
await
|
|
3533
|
+
const lockPath = path8.join(scanPath, "neat-out", "daemon.spawn.lock");
|
|
3534
|
+
await fs7.mkdir(path8.dirname(lockPath), { recursive: true });
|
|
3407
3535
|
const STALE_LOCK_MS = 6e4;
|
|
3408
3536
|
try {
|
|
3409
|
-
const fd = await
|
|
3537
|
+
const fd = await fs7.open(lockPath, "wx");
|
|
3410
3538
|
await fd.writeFile(`${process.pid}
|
|
3411
3539
|
`, "utf8");
|
|
3412
3540
|
await fd.close();
|
|
3413
3541
|
return async () => {
|
|
3414
|
-
await
|
|
3542
|
+
await fs7.unlink(lockPath).catch(() => {
|
|
3415
3543
|
});
|
|
3416
3544
|
};
|
|
3417
3545
|
} catch (err) {
|
|
3418
3546
|
if (err.code !== "EEXIST") return null;
|
|
3419
3547
|
try {
|
|
3420
|
-
const stat = await
|
|
3548
|
+
const stat = await fs7.stat(lockPath);
|
|
3421
3549
|
if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
|
|
3422
|
-
await
|
|
3550
|
+
await fs7.unlink(lockPath).catch(() => {
|
|
3423
3551
|
});
|
|
3424
3552
|
return acquireSpawnLock(scanPath);
|
|
3425
3553
|
}
|
|
@@ -3448,13 +3576,13 @@ async function healthIsForProject(restPort, project) {
|
|
|
3448
3576
|
return false;
|
|
3449
3577
|
}
|
|
3450
3578
|
function daemonLogPath(projectPath2) {
|
|
3451
|
-
return
|
|
3579
|
+
return path8.join(projectPath2, "neat-out", "daemon.log");
|
|
3452
3580
|
}
|
|
3453
3581
|
function spawnDaemonDetached(spec) {
|
|
3454
|
-
const here =
|
|
3582
|
+
const here = path8.dirname(fileURLToPath2(import.meta.url));
|
|
3455
3583
|
const candidates = [
|
|
3456
|
-
|
|
3457
|
-
|
|
3584
|
+
path8.join(here, "neatd.cjs"),
|
|
3585
|
+
path8.join(here, "neatd.js")
|
|
3458
3586
|
];
|
|
3459
3587
|
let entry2 = null;
|
|
3460
3588
|
const fsSync = __require("fs");
|
|
@@ -3484,7 +3612,7 @@ function spawnDaemonDetached(spec) {
|
|
|
3484
3612
|
let logFd = null;
|
|
3485
3613
|
if (spec) {
|
|
3486
3614
|
const logPath = daemonLogPath(spec.projectPath);
|
|
3487
|
-
fsSync.mkdirSync(
|
|
3615
|
+
fsSync.mkdirSync(path8.dirname(logPath), { recursive: true });
|
|
3488
3616
|
logFd = fsSync.openSync(logPath, "a");
|
|
3489
3617
|
}
|
|
3490
3618
|
const child = spawn2(process.execPath, [entry2, "start"], {
|
|
@@ -3523,7 +3651,7 @@ async function runOrchestrator(opts) {
|
|
|
3523
3651
|
browser: "skipped"
|
|
3524
3652
|
}
|
|
3525
3653
|
};
|
|
3526
|
-
const stat = await
|
|
3654
|
+
const stat = await fs7.stat(opts.scanPath).catch(() => null);
|
|
3527
3655
|
if (!stat || !stat.isDirectory()) {
|
|
3528
3656
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
3529
3657
|
result.exitCode = 2;
|
|
@@ -3683,7 +3811,7 @@ async function runOrchestrator(opts) {
|
|
|
3683
3811
|
result.steps.browser = openBrowser(dashboardUrl);
|
|
3684
3812
|
}
|
|
3685
3813
|
const daemonRunning = result.steps.daemon === "spawned" || result.steps.daemon === "already-running";
|
|
3686
|
-
const daemonLog = daemonRunning ?
|
|
3814
|
+
const daemonLog = daemonRunning ? path8.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
|
|
3687
3815
|
printSummary(result, graph, dashboardUrl, daemonLog);
|
|
3688
3816
|
return result;
|
|
3689
3817
|
}
|
|
@@ -4091,7 +4219,7 @@ async function connectorTest(deps, id) {
|
|
|
4091
4219
|
function printConnectorUsage(write) {
|
|
4092
4220
|
write("usage: neat connector <add|list|remove|test> [args]");
|
|
4093
4221
|
write(` providers: ${knownProviderNames().join(", ")}`);
|
|
4094
|
-
write(" pull (polled): supabase, railway, firebase, cloudflare");
|
|
4222
|
+
write(" pull (polled): supabase, railway, firebase, cloudflare, neon");
|
|
4095
4223
|
write(" push (drains): vercel \u2014 provisions a Vercel trace Drain that forwards");
|
|
4096
4224
|
write(" traces to the daemon's OTLP receiver; no app instrumentation");
|
|
4097
4225
|
write(" add <provider> add a connector; validates the credential first (--skip-validate to skip)");
|
|
@@ -4100,6 +4228,8 @@ function printConnectorUsage(write) {
|
|
|
4100
4228
|
write(" vercel: neat connector add vercel --token $VERCEL_TOKEN \\");
|
|
4101
4229
|
write(" --otel-token $NEAT_OTEL_TOKEN --team-id <teamId> \\");
|
|
4102
4230
|
write(" --endpoint https://<public-host>/v1/traces [--project-ids <id,id>]");
|
|
4231
|
+
write(" neon: neat connector add neon --credential $NEON_OBSERVER_URL \\");
|
|
4232
|
+
write(" --project-id <projectId> --service-name <serviceName>");
|
|
4103
4233
|
write(" list list configured connectors (credentials shown redacted)");
|
|
4104
4234
|
write(" flags: --project <name>");
|
|
4105
4235
|
write(" remove <id> remove a connector by id (a push provider also has its drain deleted)");
|
|
@@ -4138,27 +4268,27 @@ async function runConnectorCommand(rawArgs, deps = {}) {
|
|
|
4138
4268
|
}
|
|
4139
4269
|
|
|
4140
4270
|
// src/hooks-cli.ts
|
|
4141
|
-
import
|
|
4271
|
+
import path9 from "path";
|
|
4142
4272
|
import os from "os";
|
|
4143
|
-
import { promises as
|
|
4273
|
+
import { promises as fs8 } from "fs";
|
|
4144
4274
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
4145
4275
|
var HOOK_FILENAME = "neat-search-nudge.mjs";
|
|
4146
4276
|
var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
4147
4277
|
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
4148
4278
|
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
4149
4279
|
function moduleDir() {
|
|
4150
|
-
return typeof __dirname !== "undefined" ? __dirname :
|
|
4280
|
+
return typeof __dirname !== "undefined" ? __dirname : path9.dirname(fileURLToPath3(import.meta.url));
|
|
4151
4281
|
}
|
|
4152
4282
|
async function readSkillAsset(rel) {
|
|
4153
4283
|
const here = moduleDir();
|
|
4154
4284
|
const candidates = [
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4285
|
+
path9.resolve(here, "../../claude-skill", rel),
|
|
4286
|
+
path9.resolve(here, "../../../claude-skill", rel),
|
|
4287
|
+
path9.resolve(here, "../claude-skill", rel)
|
|
4158
4288
|
];
|
|
4159
4289
|
for (const candidate of candidates) {
|
|
4160
4290
|
try {
|
|
4161
|
-
return await
|
|
4291
|
+
return await fs8.readFile(candidate, "utf8");
|
|
4162
4292
|
} catch {
|
|
4163
4293
|
}
|
|
4164
4294
|
}
|
|
@@ -4168,17 +4298,17 @@ async function readSkillAsset(rel) {
|
|
|
4168
4298
|
}
|
|
4169
4299
|
function neatHome() {
|
|
4170
4300
|
const override = process.env.NEAT_HOME;
|
|
4171
|
-
if (override && override.length > 0) return
|
|
4172
|
-
return
|
|
4301
|
+
if (override && override.length > 0) return path9.resolve(override);
|
|
4302
|
+
return path9.join(os.homedir(), ".neat");
|
|
4173
4303
|
}
|
|
4174
4304
|
function claudeSettingsPath() {
|
|
4175
4305
|
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
4176
|
-
if (override && override.length > 0) return
|
|
4306
|
+
if (override && override.length > 0) return path9.resolve(override);
|
|
4177
4307
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();
|
|
4178
|
-
return
|
|
4308
|
+
return path9.join(home, ".claude", "settings.json");
|
|
4179
4309
|
}
|
|
4180
4310
|
function installedHookPath() {
|
|
4181
|
-
return
|
|
4311
|
+
return path9.join(neatHome(), "hooks", HOOK_FILENAME);
|
|
4182
4312
|
}
|
|
4183
4313
|
function isNeatSearchEntry(entry2) {
|
|
4184
4314
|
return (entry2.hooks ?? []).some(
|
|
@@ -4211,14 +4341,14 @@ async function runHooks(opts) {
|
|
|
4211
4341
|
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
4212
4342
|
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
4213
4343
|
const scriptPath = installedHookPath();
|
|
4214
|
-
await
|
|
4215
|
-
await
|
|
4216
|
-
const guidePath =
|
|
4217
|
-
await
|
|
4344
|
+
await fs8.mkdir(path9.dirname(scriptPath), { recursive: true });
|
|
4345
|
+
await fs8.writeFile(scriptPath, hookScript, { mode: 493 });
|
|
4346
|
+
const guidePath = path9.join(neatHome(), GUIDE_INSTALL_NAME);
|
|
4347
|
+
await fs8.writeFile(guidePath, guide, "utf8");
|
|
4218
4348
|
const settingsFile = claudeSettingsPath();
|
|
4219
4349
|
let settings = {};
|
|
4220
4350
|
try {
|
|
4221
|
-
settings = JSON.parse(await
|
|
4351
|
+
settings = JSON.parse(await fs8.readFile(settingsFile, "utf8"));
|
|
4222
4352
|
} catch (err) {
|
|
4223
4353
|
if (err.code !== "ENOENT") {
|
|
4224
4354
|
console.error(
|
|
@@ -4240,8 +4370,8 @@ async function runHooks(opts) {
|
|
|
4240
4370
|
...settings,
|
|
4241
4371
|
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
4242
4372
|
};
|
|
4243
|
-
await
|
|
4244
|
-
await
|
|
4373
|
+
await fs8.mkdir(path9.dirname(settingsFile), { recursive: true });
|
|
4374
|
+
await fs8.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
4245
4375
|
console.log(`neat hooks: installed the search-nudge hook`);
|
|
4246
4376
|
console.log(` script: ${scriptPath}`);
|
|
4247
4377
|
console.log(` settings: ${settingsFile} (PreToolUse \u2192 ${HOOK_MATCHER})`);
|
|
@@ -4311,7 +4441,7 @@ async function runHooksCommand(args) {
|
|
|
4311
4441
|
}
|
|
4312
4442
|
|
|
4313
4443
|
// src/cli-verbs.ts
|
|
4314
|
-
import
|
|
4444
|
+
import path10 from "path";
|
|
4315
4445
|
|
|
4316
4446
|
// src/cli-client.ts
|
|
4317
4447
|
import { Provenance as Provenance2 } from "@neat.is/types";
|
|
@@ -4339,10 +4469,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4339
4469
|
const root = baseUrl.replace(/\/$/, "");
|
|
4340
4470
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
4341
4471
|
return {
|
|
4342
|
-
async get(
|
|
4472
|
+
async get(path12) {
|
|
4343
4473
|
let res;
|
|
4344
4474
|
try {
|
|
4345
|
-
res = await fetch(`${root}${
|
|
4475
|
+
res = await fetch(`${root}${path12}`, {
|
|
4346
4476
|
headers: { ...authHeader }
|
|
4347
4477
|
});
|
|
4348
4478
|
} catch (err) {
|
|
@@ -4354,16 +4484,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4354
4484
|
const body = await res.text().catch(() => "");
|
|
4355
4485
|
throw new HttpError(
|
|
4356
4486
|
res.status,
|
|
4357
|
-
`${res.status} ${res.statusText} on GET ${
|
|
4487
|
+
`${res.status} ${res.statusText} on GET ${path12}: ${body}`,
|
|
4358
4488
|
body
|
|
4359
4489
|
);
|
|
4360
4490
|
}
|
|
4361
4491
|
return await res.json();
|
|
4362
4492
|
},
|
|
4363
|
-
async post(
|
|
4493
|
+
async post(path12, body) {
|
|
4364
4494
|
let res;
|
|
4365
4495
|
try {
|
|
4366
|
-
res = await fetch(`${root}${
|
|
4496
|
+
res = await fetch(`${root}${path12}`, {
|
|
4367
4497
|
method: "POST",
|
|
4368
4498
|
headers: { "content-type": "application/json", ...authHeader },
|
|
4369
4499
|
body: JSON.stringify(body)
|
|
@@ -4377,7 +4507,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
4377
4507
|
const text = await res.text().catch(() => "");
|
|
4378
4508
|
throw new HttpError(
|
|
4379
4509
|
res.status,
|
|
4380
|
-
`${res.status} ${res.statusText} on POST ${
|
|
4510
|
+
`${res.status} ${res.statusText} on POST ${path12}: ${text}`,
|
|
4381
4511
|
text
|
|
4382
4512
|
);
|
|
4383
4513
|
}
|
|
@@ -4391,12 +4521,12 @@ function projectPath(project, suffix) {
|
|
|
4391
4521
|
}
|
|
4392
4522
|
async function runRootCause(client, input) {
|
|
4393
4523
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
4394
|
-
const
|
|
4524
|
+
const path12 = projectPath(
|
|
4395
4525
|
input.project,
|
|
4396
4526
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
4397
4527
|
);
|
|
4398
4528
|
try {
|
|
4399
|
-
const result = await client.get(
|
|
4529
|
+
const result = await client.get(path12);
|
|
4400
4530
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
4401
4531
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
4402
4532
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -4422,12 +4552,12 @@ async function runRootCause(client, input) {
|
|
|
4422
4552
|
}
|
|
4423
4553
|
async function runBlastRadius(client, input) {
|
|
4424
4554
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
4425
|
-
const
|
|
4555
|
+
const path12 = projectPath(
|
|
4426
4556
|
input.project,
|
|
4427
4557
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
4428
4558
|
);
|
|
4429
4559
|
try {
|
|
4430
|
-
const result = await client.get(
|
|
4560
|
+
const result = await client.get(path12);
|
|
4431
4561
|
if (result.totalAffected === 0) {
|
|
4432
4562
|
return {
|
|
4433
4563
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -4461,12 +4591,12 @@ function formatBlastEntry(n) {
|
|
|
4461
4591
|
}
|
|
4462
4592
|
async function runDependencies(client, input) {
|
|
4463
4593
|
const depth = input.depth ?? 3;
|
|
4464
|
-
const
|
|
4594
|
+
const path12 = projectPath(
|
|
4465
4595
|
input.project,
|
|
4466
4596
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
4467
4597
|
);
|
|
4468
4598
|
try {
|
|
4469
|
-
const result = await client.get(
|
|
4599
|
+
const result = await client.get(path12);
|
|
4470
4600
|
if (result.total === 0) {
|
|
4471
4601
|
return {
|
|
4472
4602
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -4558,9 +4688,9 @@ function formatDuration(ms) {
|
|
|
4558
4688
|
return `${Math.round(h / 24)}d`;
|
|
4559
4689
|
}
|
|
4560
4690
|
async function runIncidents(client, input) {
|
|
4561
|
-
const
|
|
4691
|
+
const path12 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
4562
4692
|
try {
|
|
4563
|
-
const body = await client.get(
|
|
4693
|
+
const body = await client.get(path12);
|
|
4564
4694
|
const events = body.events;
|
|
4565
4695
|
if (events.length === 0) {
|
|
4566
4696
|
return {
|
|
@@ -4850,7 +4980,7 @@ async function resolveProjectEntry(opts) {
|
|
|
4850
4980
|
const cwd = opts.cwd ?? process.cwd();
|
|
4851
4981
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
4852
4982
|
for (const entry2 of entries) {
|
|
4853
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
4983
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path10.sep}`)) {
|
|
4854
4984
|
return entry2;
|
|
4855
4985
|
}
|
|
4856
4986
|
}
|
|
@@ -5297,15 +5427,15 @@ async function buildPatchSections(services, project) {
|
|
|
5297
5427
|
for (const svc of services) {
|
|
5298
5428
|
const installer = await pickInstaller(svc.dir);
|
|
5299
5429
|
if (!installer) continue;
|
|
5300
|
-
const
|
|
5301
|
-
if (isEmptyPlan(
|
|
5302
|
-
sections.push({ installer: installer.name, plan:
|
|
5430
|
+
const plan4 = await installer.plan(svc.dir, { project });
|
|
5431
|
+
if (isEmptyPlan(plan4) && !plan4.libOnly && plan4.runtimeKind === void 0) continue;
|
|
5432
|
+
sections.push({ installer: installer.name, plan: plan4 });
|
|
5303
5433
|
}
|
|
5304
5434
|
return sections;
|
|
5305
5435
|
}
|
|
5306
5436
|
async function runInit(opts) {
|
|
5307
5437
|
const written = [];
|
|
5308
|
-
const stat = await
|
|
5438
|
+
const stat = await fs9.stat(opts.scanPath).catch(() => null);
|
|
5309
5439
|
if (!stat || !stat.isDirectory()) {
|
|
5310
5440
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
5311
5441
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -5314,13 +5444,13 @@ async function runInit(opts) {
|
|
|
5314
5444
|
printDiscoveryReport(opts, services);
|
|
5315
5445
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
5316
5446
|
const patch = renderPatch(sections);
|
|
5317
|
-
const patchPath =
|
|
5447
|
+
const patchPath = path11.join(opts.scanPath, "neat.patch");
|
|
5318
5448
|
if (opts.dryRun) {
|
|
5319
|
-
await
|
|
5449
|
+
await fs9.writeFile(patchPath, patch, "utf8");
|
|
5320
5450
|
written.push(patchPath);
|
|
5321
5451
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
5322
|
-
const gitignorePath =
|
|
5323
|
-
const gitignoreExists = await
|
|
5452
|
+
const gitignorePath = path11.join(opts.scanPath, ".gitignore");
|
|
5453
|
+
const gitignoreExists = await fs9.stat(gitignorePath).then(() => true).catch(() => false);
|
|
5324
5454
|
const verb = gitignoreExists ? "append" : "create";
|
|
5325
5455
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
5326
5456
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -5331,9 +5461,9 @@ async function runInit(opts) {
|
|
|
5331
5461
|
const graph = getGraph(graphKey);
|
|
5332
5462
|
const projectPaths = pathsForProject(
|
|
5333
5463
|
graphKey,
|
|
5334
|
-
|
|
5464
|
+
path11.join(opts.scanPath, "neat-out")
|
|
5335
5465
|
);
|
|
5336
|
-
const errorsPath =
|
|
5466
|
+
const errorsPath = path11.join(path11.dirname(opts.outPath), path11.basename(projectPaths.errorsPath));
|
|
5337
5467
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
5338
5468
|
await saveGraphToDisk(graph, opts.outPath);
|
|
5339
5469
|
written.push(opts.outPath);
|
|
@@ -5412,7 +5542,7 @@ async function runInit(opts) {
|
|
|
5412
5542
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
5413
5543
|
}
|
|
5414
5544
|
} else {
|
|
5415
|
-
await
|
|
5545
|
+
await fs9.writeFile(patchPath, patch, "utf8");
|
|
5416
5546
|
written.push(patchPath);
|
|
5417
5547
|
}
|
|
5418
5548
|
}
|
|
@@ -5452,9 +5582,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
5452
5582
|
};
|
|
5453
5583
|
function claudeConfigPath() {
|
|
5454
5584
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
5455
|
-
if (override && override.length > 0) return
|
|
5585
|
+
if (override && override.length > 0) return path11.resolve(override);
|
|
5456
5586
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5457
|
-
return
|
|
5587
|
+
return path11.join(home, ".claude.json");
|
|
5458
5588
|
}
|
|
5459
5589
|
async function runSkill(opts) {
|
|
5460
5590
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -5466,7 +5596,7 @@ async function runSkill(opts) {
|
|
|
5466
5596
|
const target = claudeConfigPath();
|
|
5467
5597
|
let existing = {};
|
|
5468
5598
|
try {
|
|
5469
|
-
existing = JSON.parse(await
|
|
5599
|
+
existing = JSON.parse(await fs9.readFile(target, "utf8"));
|
|
5470
5600
|
} catch (err) {
|
|
5471
5601
|
if (err.code !== "ENOENT") {
|
|
5472
5602
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -5478,8 +5608,8 @@ async function runSkill(opts) {
|
|
|
5478
5608
|
...existing,
|
|
5479
5609
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
5480
5610
|
};
|
|
5481
|
-
await
|
|
5482
|
-
await
|
|
5611
|
+
await fs9.mkdir(path11.dirname(target), { recursive: true });
|
|
5612
|
+
await fs9.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
5483
5613
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
5484
5614
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
5485
5615
|
console.log("");
|
|
@@ -5531,7 +5661,7 @@ async function main() {
|
|
|
5531
5661
|
}
|
|
5532
5662
|
const cmd = argvParsed.positional[0];
|
|
5533
5663
|
const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
|
|
5534
|
-
const { positional, apply:
|
|
5664
|
+
const { positional, apply: apply4, dryRun, noInstall } = parsed;
|
|
5535
5665
|
const project = parsed.project ?? DEFAULT_PROJECT;
|
|
5536
5666
|
if (cmd === "init") {
|
|
5537
5667
|
const target = positional[0];
|
|
@@ -5540,22 +5670,22 @@ async function main() {
|
|
|
5540
5670
|
usage2();
|
|
5541
5671
|
process.exit(2);
|
|
5542
5672
|
}
|
|
5543
|
-
if (
|
|
5673
|
+
if (apply4 && dryRun) {
|
|
5544
5674
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
5545
5675
|
process.exit(2);
|
|
5546
5676
|
}
|
|
5547
|
-
const scanPath =
|
|
5677
|
+
const scanPath = path11.resolve(target);
|
|
5548
5678
|
const projectExplicit = parsed.project !== null;
|
|
5549
|
-
const projectName = projectExplicit ? project :
|
|
5679
|
+
const projectName = projectExplicit ? project : path11.basename(scanPath);
|
|
5550
5680
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
5551
|
-
const fallback = pathsForProject(projectKey,
|
|
5552
|
-
const outPath =
|
|
5681
|
+
const fallback = pathsForProject(projectKey, path11.join(scanPath, "neat-out")).snapshotPath;
|
|
5682
|
+
const outPath = path11.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
5553
5683
|
const result = await runInit({
|
|
5554
5684
|
scanPath,
|
|
5555
5685
|
outPath,
|
|
5556
5686
|
project: projectName,
|
|
5557
5687
|
projectExplicit,
|
|
5558
|
-
apply:
|
|
5688
|
+
apply: apply4,
|
|
5559
5689
|
dryRun,
|
|
5560
5690
|
noInstall,
|
|
5561
5691
|
verbose: parsed.verbose
|
|
@@ -5570,21 +5700,21 @@ async function main() {
|
|
|
5570
5700
|
usage2();
|
|
5571
5701
|
process.exit(2);
|
|
5572
5702
|
}
|
|
5573
|
-
const scanPath =
|
|
5574
|
-
const stat = await
|
|
5703
|
+
const scanPath = path11.resolve(target);
|
|
5704
|
+
const stat = await fs9.stat(scanPath).catch(() => null);
|
|
5575
5705
|
if (!stat || !stat.isDirectory()) {
|
|
5576
5706
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
5577
5707
|
process.exit(2);
|
|
5578
5708
|
}
|
|
5579
|
-
const projectPaths = pathsForProject(project,
|
|
5580
|
-
const outPath =
|
|
5581
|
-
const errorsPath =
|
|
5582
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
5709
|
+
const projectPaths = pathsForProject(project, path11.join(scanPath, "neat-out"));
|
|
5710
|
+
const outPath = path11.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
5711
|
+
const errorsPath = path11.resolve(
|
|
5712
|
+
process.env.NEAT_ERRORS_PATH ?? path11.join(path11.dirname(outPath), path11.basename(projectPaths.errorsPath))
|
|
5583
5713
|
);
|
|
5584
|
-
const staleEventsPath =
|
|
5585
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
5714
|
+
const staleEventsPath = path11.resolve(
|
|
5715
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path11.join(path11.dirname(outPath), path11.basename(projectPaths.staleEventsPath))
|
|
5586
5716
|
);
|
|
5587
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
5717
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path11.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
5588
5718
|
const handle = await startWatch(getGraph(project), {
|
|
5589
5719
|
scanPath,
|
|
5590
5720
|
outPath,
|
|
@@ -5593,7 +5723,7 @@ async function main() {
|
|
|
5593
5723
|
project,
|
|
5594
5724
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
5595
5725
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
5596
|
-
neatHome: process.env.NEAT_HOME ?
|
|
5726
|
+
neatHome: process.env.NEAT_HOME ? path11.resolve(process.env.NEAT_HOME) : path11.join(os2.homedir(), ".neat"),
|
|
5597
5727
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
5598
5728
|
host: process.env.HOST ?? "0.0.0.0",
|
|
5599
5729
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -5770,11 +5900,11 @@ async function main() {
|
|
|
5770
5900
|
process.exit(1);
|
|
5771
5901
|
}
|
|
5772
5902
|
async function tryOrchestrator(cmd, parsed) {
|
|
5773
|
-
const scanPath =
|
|
5774
|
-
const stat = await
|
|
5903
|
+
const scanPath = path11.resolve(cmd);
|
|
5904
|
+
const stat = await fs9.stat(scanPath).catch(() => null);
|
|
5775
5905
|
if (!stat || !stat.isDirectory()) return null;
|
|
5776
5906
|
const projectExplicit = parsed.project !== null;
|
|
5777
|
-
const projectName = projectExplicit ? parsed.project :
|
|
5907
|
+
const projectName = projectExplicit ? parsed.project : path11.basename(scanPath);
|
|
5778
5908
|
const result = await runOrchestrator({
|
|
5779
5909
|
scanPath,
|
|
5780
5910
|
project: projectName,
|