@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/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-PWIT35Z4.js";
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
- } from "./chunk-L64CATKE.js";
73
+ upsertConnectorEntry,
74
+ validateConnectorEntry
75
+ } from "./chunk-5RIL3U5A.js";
76
76
  import {
77
77
  startOtelGrpcReceiver
78
- } from "./chunk-7QXN726V.js";
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-ZLAZ7PLC.js";
86
+ } from "./chunk-N5L3RBGP.js";
87
87
 
88
88
  // src/cli.ts
89
- import path10 from "path";
89
+ import path11 from "path";
90
90
  import os2 from "os";
91
- import { promises as fs8 } from "fs";
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(plan3) {
2954
- return plan3.dependencyEdits.length === 0 && plan3.entrypointEdits.length === 0 && plan3.envEdits.length === 0 && (plan3.generatedFiles?.length ?? 0) === 0 && plan3.nextConfigEdit === void 0;
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 / Go / Rust are out of MVP scope per ADR-047);",
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: plan3 } = section;
2994
- lines.push(`## ${installer} (${plan3.language}) \u2014 ${plan3.serviceDir}`);
3121
+ const { installer, plan: plan4 } = section;
3122
+ lines.push(`## ${installer} (${plan4.language}) \u2014 ${plan4.serviceDir}`);
2995
3123
  lines.push("");
2996
- if (plan3.libOnly) {
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 (plan3.entryFile) {
3002
- lines.push(`entry: ${plan3.entryFile}`);
3129
+ if (plan4.entryFile) {
3130
+ lines.push(`entry: ${plan4.entryFile}`);
3003
3131
  lines.push("");
3004
3132
  }
3005
- if (plan3.dependencyEdits.length > 0) {
3133
+ if (plan4.dependencyEdits.length > 0) {
3006
3134
  lines.push("### dependencies");
3007
3135
  const byFile = /* @__PURE__ */ new Map();
3008
- for (const dep of plan3.dependencyEdits) {
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 (plan3.generatedFiles && plan3.generatedFiles.length > 0) {
3155
+ if (plan4.generatedFiles && plan4.generatedFiles.length > 0) {
3028
3156
  lines.push("### generated files");
3029
- for (const gen of plan3.generatedFiles) {
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 (plan3.entrypointEdits.length > 0) {
3165
+ if (plan4.entrypointEdits.length > 0) {
3038
3166
  lines.push("### entry-point injection");
3039
- for (const e of plan3.entrypointEdits) {
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 (plan3.envEdits.length > 0) {
3174
+ if (plan4.envEdits.length > 0) {
3047
3175
  lines.push("### env (written to <package-dir>/.env.neat)");
3048
- for (const env of plan3.envEdits) {
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 (plan3.nextConfigEdit) {
3181
+ if (plan4.nextConfigEdit) {
3054
3182
  lines.push("### next.config (framework flag)");
3055
- lines.push(`--- ${plan3.nextConfigEdit.file}`);
3056
- lines.push(`+ experimental: { instrumentationHook: true }, // ${plan3.nextConfigEdit.reason}`);
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 fs6 } from "fs";
3192
+ import { promises as fs7 } from "fs";
3065
3193
  import http from "http";
3066
3194
  import net from "net";
3067
- import path7 from "path";
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, path7.join(opts.scanPath, "neat-out"));
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 plan3 = await installer.plan(svc.dir, { project });
3112
- if (isEmptyPlan(plan3) && !plan3.libOnly && plan3.runtimeKind === void 0) {
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(plan3);
3244
+ const outcome = await installer.apply(plan4);
3117
3245
  if (outcome.outcome === "instrumented") {
3118
3246
  instrumented++;
3119
- if (plan3.dependencyEdits.length > 0) {
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 = path7.basename(svc.dir);
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 = path7.basename(svc.dir);
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 = path7.basename(svc.dir);
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 = path7.basename(svc.dir);
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 = path7.basename(svc.dir);
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 = path7.basename(svc.dir);
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 = path7.basename(svc.dir);
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 = path7.join(scanPath, "neat-out", "daemon.spawn.lock");
3406
- await fs6.mkdir(path7.dirname(lockPath), { recursive: true });
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 fs6.open(lockPath, "wx");
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 fs6.unlink(lockPath).catch(() => {
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 fs6.stat(lockPath);
3548
+ const stat = await fs7.stat(lockPath);
3421
3549
  if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
3422
- await fs6.unlink(lockPath).catch(() => {
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 path7.join(projectPath2, "neat-out", "daemon.log");
3579
+ return path8.join(projectPath2, "neat-out", "daemon.log");
3452
3580
  }
3453
3581
  function spawnDaemonDetached(spec) {
3454
- const here = path7.dirname(fileURLToPath2(import.meta.url));
3582
+ const here = path8.dirname(fileURLToPath2(import.meta.url));
3455
3583
  const candidates = [
3456
- path7.join(here, "neatd.cjs"),
3457
- path7.join(here, "neatd.js")
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(path7.dirname(logPath), { recursive: true });
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 fs6.stat(opts.scanPath).catch(() => null);
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 ? path7.relative(opts.scanPath, daemonLogPath(opts.scanPath)) : null;
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 path8 from "path";
4271
+ import path9 from "path";
4142
4272
  import os from "os";
4143
- import { promises as fs7 } from "fs";
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 : path8.dirname(fileURLToPath3(import.meta.url));
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
- path8.resolve(here, "../../claude-skill", rel),
4156
- path8.resolve(here, "../../../claude-skill", rel),
4157
- path8.resolve(here, "../claude-skill", rel)
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 fs7.readFile(candidate, "utf8");
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 path8.resolve(override);
4172
- return path8.join(os.homedir(), ".neat");
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 path8.resolve(override);
4306
+ if (override && override.length > 0) return path9.resolve(override);
4177
4307
  const home = process.env.HOME ?? process.env.USERPROFILE ?? os.homedir();
4178
- return path8.join(home, ".claude", "settings.json");
4308
+ return path9.join(home, ".claude", "settings.json");
4179
4309
  }
4180
4310
  function installedHookPath() {
4181
- return path8.join(neatHome(), "hooks", HOOK_FILENAME);
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 fs7.mkdir(path8.dirname(scriptPath), { recursive: true });
4215
- await fs7.writeFile(scriptPath, hookScript, { mode: 493 });
4216
- const guidePath = path8.join(neatHome(), GUIDE_INSTALL_NAME);
4217
- await fs7.writeFile(guidePath, guide, "utf8");
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 fs7.readFile(settingsFile, "utf8"));
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 fs7.mkdir(path8.dirname(settingsFile), { recursive: true });
4244
- await fs7.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
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 path9 from "path";
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(path11) {
4472
+ async get(path12) {
4343
4473
  let res;
4344
4474
  try {
4345
- res = await fetch(`${root}${path11}`, {
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 ${path11}: ${body}`,
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(path11, body) {
4493
+ async post(path12, body) {
4364
4494
  let res;
4365
4495
  try {
4366
- res = await fetch(`${root}${path11}`, {
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 ${path11}: ${text}`,
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 path11 = projectPath(
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(path11);
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 path11 = projectPath(
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(path11);
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 path11 = projectPath(
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(path11);
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 path11 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
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(path11);
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}${path9.sep}`)) {
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 plan3 = await installer.plan(svc.dir, { project });
5301
- if (isEmptyPlan(plan3) && !plan3.libOnly && plan3.runtimeKind === void 0) continue;
5302
- sections.push({ installer: installer.name, plan: plan3 });
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 fs8.stat(opts.scanPath).catch(() => null);
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 = path10.join(opts.scanPath, "neat.patch");
5447
+ const patchPath = path11.join(opts.scanPath, "neat.patch");
5318
5448
  if (opts.dryRun) {
5319
- await fs8.writeFile(patchPath, patch, "utf8");
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 = path10.join(opts.scanPath, ".gitignore");
5323
- const gitignoreExists = await fs8.stat(gitignorePath).then(() => true).catch(() => false);
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
- path10.join(opts.scanPath, "neat-out")
5464
+ path11.join(opts.scanPath, "neat-out")
5335
5465
  );
5336
- const errorsPath = path10.join(path10.dirname(opts.outPath), path10.basename(projectPaths.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 fs8.writeFile(patchPath, patch, "utf8");
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 path10.resolve(override);
5585
+ if (override && override.length > 0) return path11.resolve(override);
5456
5586
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
5457
- return path10.join(home, ".claude.json");
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 fs8.readFile(target, "utf8"));
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 fs8.mkdir(path10.dirname(target), { recursive: true });
5482
- await fs8.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
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: apply3, dryRun, noInstall } = parsed;
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 (apply3 && dryRun) {
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 = path10.resolve(target);
5677
+ const scanPath = path11.resolve(target);
5548
5678
  const projectExplicit = parsed.project !== null;
5549
- const projectName = projectExplicit ? project : path10.basename(scanPath);
5679
+ const projectName = projectExplicit ? project : path11.basename(scanPath);
5550
5680
  const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
5551
- const fallback = pathsForProject(projectKey, path10.join(scanPath, "neat-out")).snapshotPath;
5552
- const outPath = path10.resolve(process.env.NEAT_OUT_PATH ?? fallback);
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: apply3,
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 = path10.resolve(target);
5574
- const stat = await fs8.stat(scanPath).catch(() => null);
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, path10.join(scanPath, "neat-out"));
5580
- const outPath = path10.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
5581
- const errorsPath = path10.resolve(
5582
- process.env.NEAT_ERRORS_PATH ?? path10.join(path10.dirname(outPath), path10.basename(projectPaths.errorsPath))
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 = path10.resolve(
5585
- process.env.NEAT_STALE_EVENTS_PATH ?? path10.join(path10.dirname(outPath), path10.basename(projectPaths.staleEventsPath))
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 ? path10.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
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 ? path10.resolve(process.env.NEAT_HOME) : path10.join(os2.homedir(), ".neat"),
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 = path10.resolve(cmd);
5774
- const stat = await fs8.stat(scanPath).catch(() => null);
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 : path10.basename(scanPath);
5907
+ const projectName = projectExplicit ? parsed.project : path11.basename(scanPath);
5778
5908
  const result = await runOrchestrator({
5779
5909
  scanPath,
5780
5910
  project: projectName,