@neat.is/core 0.10.1-dev.20260927 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ERE47MCR.js → chunk-5SJHA5ZH.js} +2 -2
- package/dist/{chunk-EJPNEEJA.js → chunk-AWIK6T3G.js} +95 -49
- package/dist/chunk-AWIK6T3G.js.map +1 -0
- package/dist/{chunk-J3VVEB45.js → chunk-E3YFFIHE.js} +136 -8
- package/dist/chunk-E3YFFIHE.js.map +1 -0
- package/dist/{chunk-GDGUY4T6.js → chunk-M7CGATVC.js} +3 -2
- package/dist/cli.cjs +820 -265
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +608 -183
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +226 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +226 -52
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-Y5K4WGWO.js → otel-grpc-CU73Y5WT.js} +3 -3
- package/dist/server.cjs +134 -6
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +3 -3
- package/dist/chunk-EJPNEEJA.js.map +0 -1
- package/dist/chunk-J3VVEB45.js.map +0 -1
- /package/dist/{chunk-ERE47MCR.js.map → chunk-5SJHA5ZH.js.map} +0 -0
- /package/dist/{chunk-GDGUY4T6.js.map → chunk-M7CGATVC.js.map} +0 -0
- /package/dist/{otel-grpc-Y5K4WGWO.js.map → otel-grpc-CU73Y5WT.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
|
|
|
61
61
|
]);
|
|
62
62
|
const publicRead = opts.publicRead === true;
|
|
63
63
|
app.addHook("preHandler", (req, reply, done) => {
|
|
64
|
-
const
|
|
65
|
-
if (exactUnauthPaths.has(
|
|
64
|
+
const path100 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
65
|
+
if (exactUnauthPaths.has(path100) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path100)) {
|
|
66
66
|
done();
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
@@ -415,8 +415,8 @@ function websocketChannelPathOf(attrs) {
|
|
|
415
415
|
const v = attrs[key];
|
|
416
416
|
if (typeof v === "string" && v.length > 0) {
|
|
417
417
|
const q = v.indexOf("?");
|
|
418
|
-
const
|
|
419
|
-
if (
|
|
418
|
+
const path100 = q === -1 ? v : v.slice(0, q);
|
|
419
|
+
if (path100.length > 0) return path100;
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
return void 0;
|
|
@@ -789,6 +789,7 @@ __export(cli_exports, {
|
|
|
789
789
|
QUERY_VERBS: () => QUERY_VERBS,
|
|
790
790
|
UnknownProfileError: () => UnknownProfileError,
|
|
791
791
|
commandPrefix: () => commandPrefix,
|
|
792
|
+
isLoopbackEndpoint: () => isLoopbackEndpoint,
|
|
792
793
|
isNpxInvocation: () => isNpxInvocation,
|
|
793
794
|
main: () => main,
|
|
794
795
|
parseArgs: () => parseArgs,
|
|
@@ -805,9 +806,9 @@ __export(cli_exports, {
|
|
|
805
806
|
});
|
|
806
807
|
module.exports = __toCommonJS(cli_exports);
|
|
807
808
|
init_cjs_shims();
|
|
808
|
-
var
|
|
809
|
+
var import_node_path100 = __toESM(require("path"), 1);
|
|
809
810
|
var import_node_os11 = __toESM(require("os"), 1);
|
|
810
|
-
var
|
|
811
|
+
var import_node_fs61 = require("fs");
|
|
811
812
|
|
|
812
813
|
// src/banner.ts
|
|
813
814
|
init_cjs_shims();
|
|
@@ -1490,19 +1491,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1490
1491
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1491
1492
|
let best = { path: [start], edges: [] };
|
|
1492
1493
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1493
|
-
function step(node,
|
|
1494
|
-
if (
|
|
1495
|
-
best = { path: [...
|
|
1494
|
+
function step(node, path100, edges) {
|
|
1495
|
+
if (path100.length > best.path.length) {
|
|
1496
|
+
best = { path: [...path100], edges: [...edges] };
|
|
1496
1497
|
}
|
|
1497
|
-
if (
|
|
1498
|
+
if (path100.length - 1 >= maxDepth) return;
|
|
1498
1499
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1499
1500
|
for (const [srcId, edge] of incoming) {
|
|
1500
1501
|
if (visited.has(srcId)) continue;
|
|
1501
1502
|
visited.add(srcId);
|
|
1502
|
-
|
|
1503
|
+
path100.push(srcId);
|
|
1503
1504
|
edges.push(edge);
|
|
1504
|
-
step(srcId,
|
|
1505
|
-
|
|
1505
|
+
step(srcId, path100, edges);
|
|
1506
|
+
path100.pop();
|
|
1506
1507
|
edges.pop();
|
|
1507
1508
|
visited.delete(srcId);
|
|
1508
1509
|
}
|
|
@@ -1717,20 +1718,20 @@ function dominantFailingCall(graph, serviceId17, visited) {
|
|
|
1717
1718
|
return best;
|
|
1718
1719
|
}
|
|
1719
1720
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1720
|
-
const
|
|
1721
|
+
const path100 = [originServiceId];
|
|
1721
1722
|
const edges = [];
|
|
1722
1723
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1723
1724
|
let current = originServiceId;
|
|
1724
1725
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1725
1726
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1726
1727
|
if (!hop) break;
|
|
1727
|
-
|
|
1728
|
+
path100.push(hop.nextService);
|
|
1728
1729
|
edges.push(hop.edge);
|
|
1729
1730
|
visited.add(hop.nextService);
|
|
1730
1731
|
current = hop.nextService;
|
|
1731
1732
|
}
|
|
1732
1733
|
if (edges.length === 0) return null;
|
|
1733
|
-
return { path:
|
|
1734
|
+
return { path: path100, edges, culprit: current };
|
|
1734
1735
|
}
|
|
1735
1736
|
function isStaleCallEdge(e) {
|
|
1736
1737
|
return e.type === import_types2.EdgeType.CALLS && e.provenance === import_types2.Provenance.STALE;
|
|
@@ -1766,26 +1767,26 @@ function dominantStaleCall(graph, serviceId17, visited) {
|
|
|
1766
1767
|
return best;
|
|
1767
1768
|
}
|
|
1768
1769
|
function followStaleCallChain(graph, originServiceId, maxDepth) {
|
|
1769
|
-
const
|
|
1770
|
+
const path100 = [originServiceId];
|
|
1770
1771
|
const edges = [];
|
|
1771
1772
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1772
1773
|
let current = originServiceId;
|
|
1773
1774
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1774
1775
|
const hop = dominantStaleCall(graph, current, visited);
|
|
1775
1776
|
if (!hop) break;
|
|
1776
|
-
|
|
1777
|
+
path100.push(hop.nextService);
|
|
1777
1778
|
edges.push(hop.edge);
|
|
1778
1779
|
visited.add(hop.nextService);
|
|
1779
1780
|
current = hop.nextService;
|
|
1780
1781
|
}
|
|
1781
1782
|
if (edges.length === 0) return null;
|
|
1782
|
-
return { path:
|
|
1783
|
+
return { path: path100, edges, culprit: current };
|
|
1783
1784
|
}
|
|
1784
1785
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1785
1786
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1786
1787
|
if (!chain) return null;
|
|
1787
1788
|
const culprit = chain.culprit;
|
|
1788
|
-
const
|
|
1789
|
+
const path100 = [...chain.path];
|
|
1789
1790
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1790
1791
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1791
1792
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1793,14 +1794,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1793
1794
|
if (loc) {
|
|
1794
1795
|
let rootCauseNode = culprit;
|
|
1795
1796
|
if (loc.fileNode) {
|
|
1796
|
-
|
|
1797
|
+
path100.push(loc.fileNode);
|
|
1797
1798
|
edgeProvenances.push(import_types2.Provenance.OBSERVED);
|
|
1798
1799
|
rootCauseNode = loc.fileNode;
|
|
1799
1800
|
}
|
|
1800
1801
|
return import_types2.RootCauseResultSchema.parse({
|
|
1801
1802
|
rootCauseNode,
|
|
1802
1803
|
rootCauseReason: loc.rootCauseReason,
|
|
1803
|
-
traversalPath:
|
|
1804
|
+
traversalPath: path100,
|
|
1804
1805
|
edgeProvenances,
|
|
1805
1806
|
confidence,
|
|
1806
1807
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1812,7 +1813,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1812
1813
|
return import_types2.RootCauseResultSchema.parse({
|
|
1813
1814
|
rootCauseNode: culprit,
|
|
1814
1815
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1815
|
-
traversalPath:
|
|
1816
|
+
traversalPath: path100,
|
|
1816
1817
|
edgeProvenances,
|
|
1817
1818
|
confidence,
|
|
1818
1819
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2465,10 +2466,10 @@ function enrichWithNavigation(graph, errorNodeId, tagged, incidents, now) {
|
|
|
2465
2466
|
traversalPath = staleChain.path;
|
|
2466
2467
|
edgeProvenances = staleChain.edges.map((e) => e.provenance);
|
|
2467
2468
|
} else if (top.node !== seedNode) {
|
|
2468
|
-
const
|
|
2469
|
-
if (
|
|
2470
|
-
traversalPath =
|
|
2471
|
-
edgeProvenances =
|
|
2469
|
+
const path100 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
|
|
2470
|
+
if (path100) {
|
|
2471
|
+
traversalPath = path100.nodes;
|
|
2472
|
+
edgeProvenances = path100.edges.map((e) => e.provenance);
|
|
2472
2473
|
} else {
|
|
2473
2474
|
traversalPath = [errorNodeId, top.node];
|
|
2474
2475
|
edgeProvenances = [top.provenance ?? import_types2.Provenance.OBSERVED];
|
|
@@ -3919,8 +3920,8 @@ function chiRoutesFromSource(source, parser) {
|
|
|
3919
3920
|
chiWalk(tree.rootNode, "", out);
|
|
3920
3921
|
return out;
|
|
3921
3922
|
}
|
|
3922
|
-
function stripChiRegex(
|
|
3923
|
-
return
|
|
3923
|
+
function stripChiRegex(path100) {
|
|
3924
|
+
return path100.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
|
|
3924
3925
|
}
|
|
3925
3926
|
function chiWalk(node, prefix, out) {
|
|
3926
3927
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -4592,9 +4593,9 @@ function rubyRocketRoute(args) {
|
|
|
4592
4593
|
if (!pair || pair.type !== "pair") continue;
|
|
4593
4594
|
const k = pair.childForFieldName("key");
|
|
4594
4595
|
if (k?.type !== "string") continue;
|
|
4595
|
-
const
|
|
4596
|
-
if (
|
|
4597
|
-
return { path:
|
|
4596
|
+
const path100 = rubyLiteral(k);
|
|
4597
|
+
if (path100 === null) continue;
|
|
4598
|
+
return { path: path100, target: rubyLiteral(pair.childForFieldName("value")) };
|
|
4598
4599
|
}
|
|
4599
4600
|
return null;
|
|
4600
4601
|
}
|
|
@@ -12002,13 +12003,16 @@ function objectKeys(obj) {
|
|
|
12002
12003
|
var MODULAR_WRITES = /* @__PURE__ */ new Set(["addDoc", "setDoc", "updateDoc"]);
|
|
12003
12004
|
var NAMESPACED_WRITES = /* @__PURE__ */ new Set(["add", "set", "update"]);
|
|
12004
12005
|
var READ_CLAUSES = /* @__PURE__ */ new Set(["where", "orderBy"]);
|
|
12005
|
-
function firestoreEndpointsFromFile(file, serviceDir) {
|
|
12006
|
+
function firestoreEndpointsFromFile(file, serviceDir, imported) {
|
|
12006
12007
|
const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(file.content);
|
|
12007
12008
|
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
|
|
12008
|
-
|
|
12009
|
-
|
|
12009
|
+
const importedNames = imported?.names ?? /* @__PURE__ */ new Set();
|
|
12010
|
+
if (!hasClient && !hasAdmin && importedNames.size === 0) return [];
|
|
12011
|
+
const ownSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
12012
|
+
const fileSdk = !hasClient && !hasAdmin ? imported?.sdk ?? null : ownSdk;
|
|
12010
12013
|
const tree = parseSource3(parserForExt2(import_node_path45.default.extname(file.path)), file.content);
|
|
12011
12014
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
12015
|
+
for (const n of importedNames) clientVars.add(n);
|
|
12012
12016
|
const collLine = /* @__PURE__ */ new Map();
|
|
12013
12017
|
const writes = /* @__PURE__ */ new Map();
|
|
12014
12018
|
const reads = /* @__PURE__ */ new Map();
|
|
@@ -12119,6 +12123,99 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
12119
12123
|
}
|
|
12120
12124
|
return out;
|
|
12121
12125
|
}
|
|
12126
|
+
function exportedFirestoreClients(root) {
|
|
12127
|
+
const out = /* @__PURE__ */ new Set();
|
|
12128
|
+
const collect = (node) => {
|
|
12129
|
+
if (node.type === "variable_declarator") {
|
|
12130
|
+
const name = node.childForFieldName("name");
|
|
12131
|
+
let value = node.childForFieldName("value");
|
|
12132
|
+
if (value?.type === "await_expression") value = namedChildren(value)[0] ?? null;
|
|
12133
|
+
if (name?.type === "identifier" && isFirestoreClientFactory(value)) out.add(name.text);
|
|
12134
|
+
}
|
|
12135
|
+
for (const c of namedChildren(node)) collect(c);
|
|
12136
|
+
};
|
|
12137
|
+
const walk11 = (node) => {
|
|
12138
|
+
if (node.type === "export_statement") collect(node);
|
|
12139
|
+
else for (const c of namedChildren(node)) walk11(c);
|
|
12140
|
+
};
|
|
12141
|
+
walk11(root);
|
|
12142
|
+
return out;
|
|
12143
|
+
}
|
|
12144
|
+
function namedImportBindings(root) {
|
|
12145
|
+
const out = [];
|
|
12146
|
+
const walk11 = (node) => {
|
|
12147
|
+
if (node.type === "import_statement") {
|
|
12148
|
+
const src = node.childForFieldName("source");
|
|
12149
|
+
const specifier = src ? src.text.slice(1, -1) : "";
|
|
12150
|
+
if (specifier.length > 0) {
|
|
12151
|
+
const stack = [node];
|
|
12152
|
+
while (stack.length > 0) {
|
|
12153
|
+
const n = stack.pop();
|
|
12154
|
+
if (n.type === "import_specifier") {
|
|
12155
|
+
const nameNode = n.childForFieldName("name");
|
|
12156
|
+
const aliasNode = n.childForFieldName("alias");
|
|
12157
|
+
const importedName = nameNode?.text;
|
|
12158
|
+
if (importedName) {
|
|
12159
|
+
out.push({ local: aliasNode?.text ?? importedName, imported: importedName, specifier });
|
|
12160
|
+
}
|
|
12161
|
+
continue;
|
|
12162
|
+
}
|
|
12163
|
+
for (const c of namedChildren(n)) stack.push(c);
|
|
12164
|
+
}
|
|
12165
|
+
}
|
|
12166
|
+
}
|
|
12167
|
+
for (const c of namedChildren(node)) walk11(c);
|
|
12168
|
+
};
|
|
12169
|
+
walk11(root);
|
|
12170
|
+
return out;
|
|
12171
|
+
}
|
|
12172
|
+
function sdkOf(content) {
|
|
12173
|
+
const hasClient = FIRESTORE_CLIENT_IMPORT_RE.test(content);
|
|
12174
|
+
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(content);
|
|
12175
|
+
return hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
12176
|
+
}
|
|
12177
|
+
function parseOrNull(file) {
|
|
12178
|
+
const ext = import_node_path45.default.extname(file.path);
|
|
12179
|
+
if (!(ext in GRAMMAR_BY_EXT)) return null;
|
|
12180
|
+
try {
|
|
12181
|
+
return parseSource3(parserForExt2(ext), file.content).rootNode;
|
|
12182
|
+
} catch {
|
|
12183
|
+
return null;
|
|
12184
|
+
}
|
|
12185
|
+
}
|
|
12186
|
+
async function firestoreCrossFileEndpoints(files, serviceDir) {
|
|
12187
|
+
const registry = /* @__PURE__ */ new Map();
|
|
12188
|
+
for (const f of files) {
|
|
12189
|
+
if (sdkOf(f.content) === null && !FIRESTORE_CLIENT_IMPORT_RE.test(f.content)) continue;
|
|
12190
|
+
const root = parseOrNull(f);
|
|
12191
|
+
if (!root) continue;
|
|
12192
|
+
const names = exportedFirestoreClients(root);
|
|
12193
|
+
if (names.size === 0) continue;
|
|
12194
|
+
registry.set(toPosix(import_node_path45.default.relative(serviceDir, f.path)), { names, sdk: sdkOf(f.content) });
|
|
12195
|
+
}
|
|
12196
|
+
if (registry.size === 0) return [];
|
|
12197
|
+
const out = [];
|
|
12198
|
+
for (const f of files) {
|
|
12199
|
+
const root = parseOrNull(f);
|
|
12200
|
+
if (!root) continue;
|
|
12201
|
+
if (firestoreClientVars(root).size > 0) continue;
|
|
12202
|
+
const bindings = namedImportBindings(root);
|
|
12203
|
+
if (bindings.length === 0) continue;
|
|
12204
|
+
const names = /* @__PURE__ */ new Set();
|
|
12205
|
+
let sdk = null;
|
|
12206
|
+
for (const b of bindings) {
|
|
12207
|
+
const rel = await resolveJsImport(b.specifier, import_node_path45.default.dirname(f.path), serviceDir, null);
|
|
12208
|
+
if (!rel) continue;
|
|
12209
|
+
const entry2 = registry.get(rel);
|
|
12210
|
+
if (!entry2 || !entry2.names.has(b.imported)) continue;
|
|
12211
|
+
names.add(b.local);
|
|
12212
|
+
sdk ??= entry2.sdk;
|
|
12213
|
+
}
|
|
12214
|
+
if (names.size === 0) continue;
|
|
12215
|
+
out.push(...firestoreEndpointsFromFile(f, serviceDir, { names, sdk }));
|
|
12216
|
+
}
|
|
12217
|
+
return out;
|
|
12218
|
+
}
|
|
12122
12219
|
|
|
12123
12220
|
// src/extract/calls/mongoose.ts
|
|
12124
12221
|
init_cjs_shims();
|
|
@@ -14928,6 +15025,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
14928
15025
|
recordExtractionError("python socket call extraction", file.path, err);
|
|
14929
15026
|
}
|
|
14930
15027
|
}
|
|
15028
|
+
endpoints.push(...await firestoreCrossFileEndpoints(maskedFiles, service.dir));
|
|
14931
15029
|
endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
|
|
14932
15030
|
endpoints.push(...pythonOrmCrossFileEndpoints(maskedFiles, service.dir));
|
|
14933
15031
|
endpoints.push(...await prismaColumnEndpoints(service.dir));
|
|
@@ -16223,6 +16321,9 @@ function stripComments(src) {
|
|
|
16223
16321
|
function segmentsFromMatchPath(pathToken) {
|
|
16224
16322
|
return pathToken.split("/").filter((s) => s.length > 0);
|
|
16225
16323
|
}
|
|
16324
|
+
function isRecursiveWildcard(seg) {
|
|
16325
|
+
return /^\{[^}]*=\s*\*\*\}$/.test(seg);
|
|
16326
|
+
}
|
|
16226
16327
|
function collectionKeyFromSegments(segments) {
|
|
16227
16328
|
let segs = segments;
|
|
16228
16329
|
if (segs.length >= 3 && segs[0] === "databases" && /^\{.*\}$/.test(segs[1]) && segs[2] === "documents") {
|
|
@@ -16235,10 +16336,11 @@ function collectionKeyFromName(name) {
|
|
|
16235
16336
|
}
|
|
16236
16337
|
function fieldsFromCondition(cond) {
|
|
16237
16338
|
const out = [];
|
|
16238
|
-
const callRe =
|
|
16339
|
+
const callRe = /(!\s*)?\b(?:hasAll|hasOnly|hasAny)\s*\(\s*\[([^\]]*)\]\s*\)/g;
|
|
16239
16340
|
let m;
|
|
16240
16341
|
while ((m = callRe.exec(cond)) !== null) {
|
|
16241
|
-
|
|
16342
|
+
if (m[1] !== void 0 || isNegatedAt(cond, m.index)) continue;
|
|
16343
|
+
const inner = m[2];
|
|
16242
16344
|
const litRe = /'([^']*)'|"([^"]*)"/g;
|
|
16243
16345
|
let lm;
|
|
16244
16346
|
while ((lm = litRe.exec(inner)) !== null) {
|
|
@@ -16248,6 +16350,28 @@ function fieldsFromCondition(cond) {
|
|
|
16248
16350
|
}
|
|
16249
16351
|
return out;
|
|
16250
16352
|
}
|
|
16353
|
+
function isNegatedAt(cond, idx) {
|
|
16354
|
+
let i = idx - 1;
|
|
16355
|
+
while (i >= 0) {
|
|
16356
|
+
const ch = cond[i];
|
|
16357
|
+
if (ch === ")") {
|
|
16358
|
+
let depth = 1;
|
|
16359
|
+
i--;
|
|
16360
|
+
while (i >= 0 && depth > 0) {
|
|
16361
|
+
if (cond[i] === ")") depth++;
|
|
16362
|
+
else if (cond[i] === "(") depth--;
|
|
16363
|
+
i--;
|
|
16364
|
+
}
|
|
16365
|
+
continue;
|
|
16366
|
+
}
|
|
16367
|
+
if (/[A-Za-z0-9_$.\]['"]/.test(ch) || ch === " ") {
|
|
16368
|
+
i--;
|
|
16369
|
+
continue;
|
|
16370
|
+
}
|
|
16371
|
+
return ch === "!";
|
|
16372
|
+
}
|
|
16373
|
+
return false;
|
|
16374
|
+
}
|
|
16251
16375
|
function parseFirestoreRules(content) {
|
|
16252
16376
|
const src = stripComments(content);
|
|
16253
16377
|
const tokenRe = /match\s+((?:\/(?:\{[^}]*\}|[^\s/{}]+))+)\s*\{|allow\b([^;{}]*);|(\{)|(\})/g;
|
|
@@ -16260,7 +16384,8 @@ function parseFirestoreRules(content) {
|
|
|
16260
16384
|
while ((m = tokenRe.exec(src)) !== null) {
|
|
16261
16385
|
if (m[1] !== void 0) {
|
|
16262
16386
|
depth++;
|
|
16263
|
-
|
|
16387
|
+
const segs = segmentsFromMatchPath(m[1]);
|
|
16388
|
+
matchStack.push({ depth, segs, recursive: segs.some(isRecursiveWildcard) });
|
|
16264
16389
|
continue;
|
|
16265
16390
|
}
|
|
16266
16391
|
if (m[2] !== void 0) {
|
|
@@ -16271,6 +16396,10 @@ function parseFirestoreRules(content) {
|
|
|
16271
16396
|
if (!methods.some((mm) => WRITE_METHODS.has(mm))) continue;
|
|
16272
16397
|
const key = currentKey();
|
|
16273
16398
|
if (key.length === 0) continue;
|
|
16399
|
+
if (matchStack.some((f) => f.recursive)) {
|
|
16400
|
+
indeterminate.add(key);
|
|
16401
|
+
continue;
|
|
16402
|
+
}
|
|
16274
16403
|
const cond = colon === -1 ? "" : body.slice(colon + 1);
|
|
16275
16404
|
const fields = fieldsFromCondition(cond);
|
|
16276
16405
|
if (fields.length > 0) {
|
|
@@ -20325,10 +20454,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
20325
20454
|
// src/connectors/supabase/map.ts
|
|
20326
20455
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
20327
20456
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
20328
|
-
function targetFromRestPath(
|
|
20329
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
20457
|
+
function targetFromRestPath(path100) {
|
|
20458
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path100);
|
|
20330
20459
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
20331
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
20460
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path100);
|
|
20332
20461
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
20333
20462
|
return null;
|
|
20334
20463
|
}
|
|
@@ -20932,9 +21061,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
20932
21061
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
20933
21062
|
if (secondSep === -1) return null;
|
|
20934
21063
|
const method = rest.slice(0, secondSep);
|
|
20935
|
-
const
|
|
20936
|
-
if (!resourceName || !method || !
|
|
20937
|
-
return { resourceName, method, path:
|
|
21064
|
+
const path100 = rest.slice(secondSep + 1);
|
|
21065
|
+
if (!resourceName || !method || !path100) return null;
|
|
21066
|
+
return { resourceName, method, path: path100 };
|
|
20938
21067
|
}
|
|
20939
21068
|
function resourceNameFor(type, labels) {
|
|
20940
21069
|
if (!labels) return null;
|
|
@@ -20972,14 +21101,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
20972
21101
|
if (!req) return null;
|
|
20973
21102
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
20974
21103
|
const method = req.requestMethod.toUpperCase();
|
|
20975
|
-
const
|
|
20976
|
-
if (
|
|
21104
|
+
const path100 = pathFromRequestUrl(req.requestUrl);
|
|
21105
|
+
if (path100 === null) return null;
|
|
20977
21106
|
const timestamp = entry2.timestamp;
|
|
20978
21107
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
20979
21108
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
20980
21109
|
return {
|
|
20981
21110
|
targetKind: resourceType,
|
|
20982
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
21111
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path100 }),
|
|
20983
21112
|
callCount: 1,
|
|
20984
21113
|
errorCount: isError ? 1 : 0,
|
|
20985
21114
|
lastObservedIso: timestamp
|
|
@@ -21004,9 +21133,9 @@ var import_types73 = require("@neat.is/types");
|
|
|
21004
21133
|
function normalizeName(name) {
|
|
21005
21134
|
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
21006
21135
|
}
|
|
21007
|
-
function inferServiceName(graph, resourceName, method,
|
|
21136
|
+
function inferServiceName(graph, resourceName, method, path100) {
|
|
21008
21137
|
const wantedMethod = method.toUpperCase();
|
|
21009
|
-
const wantedPath = normalizePathTemplate(
|
|
21138
|
+
const wantedPath = normalizePathTemplate(path100);
|
|
21010
21139
|
const services = [];
|
|
21011
21140
|
const owners = /* @__PURE__ */ new Set();
|
|
21012
21141
|
graph.forEachNode((_id, attrs) => {
|
|
@@ -21223,7 +21352,7 @@ function mapEventToSignal(event) {
|
|
|
21223
21352
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
21224
21353
|
const statusCode = metadata?.statusCode;
|
|
21225
21354
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
21226
|
-
const
|
|
21355
|
+
const path100 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
21227
21356
|
return {
|
|
21228
21357
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
21229
21358
|
targetName: scriptName,
|
|
@@ -21231,7 +21360,7 @@ function mapEventToSignal(event) {
|
|
|
21231
21360
|
errorCount: isError ? 1 : 0,
|
|
21232
21361
|
lastObservedIso: observedAt.toISOString(),
|
|
21233
21362
|
method,
|
|
21234
|
-
...
|
|
21363
|
+
...path100 ? { path: path100 } : {},
|
|
21235
21364
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
21236
21365
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
21237
21366
|
};
|
|
@@ -21277,8 +21406,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
21277
21406
|
});
|
|
21278
21407
|
return found;
|
|
21279
21408
|
}
|
|
21280
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
21281
|
-
const normalizedPath = normalizePathTemplate(
|
|
21409
|
+
function findMatchingRouteNode(graph, serviceName, method, path100) {
|
|
21410
|
+
const normalizedPath = normalizePathTemplate(path100);
|
|
21282
21411
|
let found = null;
|
|
21283
21412
|
graph.forEachNode((id, attrs) => {
|
|
21284
21413
|
if (found) return;
|
|
@@ -21295,10 +21424,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
21295
21424
|
return (signal) => {
|
|
21296
21425
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
21297
21426
|
const scriptName = signal.targetName;
|
|
21298
|
-
const { method, path:
|
|
21427
|
+
const { method, path: path100 } = signal;
|
|
21299
21428
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
21300
|
-
if (!method || !
|
|
21301
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
21429
|
+
if (!method || !path100) return wholeFileId;
|
|
21430
|
+
return findMatchingRouteNode(graph, serviceName, method, path100) ?? wholeFileId;
|
|
21302
21431
|
};
|
|
21303
21432
|
const mapping = config.workers?.[scriptName];
|
|
21304
21433
|
if (mapping) {
|
|
@@ -21650,9 +21779,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
21650
21779
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
21651
21780
|
if (secondSep === -1) return null;
|
|
21652
21781
|
const method = rest.slice(0, secondSep);
|
|
21653
|
-
const
|
|
21654
|
-
if (!serviceName || !method || !
|
|
21655
|
-
return { serviceName, method, path:
|
|
21782
|
+
const path100 = rest.slice(secondSep + 1);
|
|
21783
|
+
if (!serviceName || !method || !path100) return null;
|
|
21784
|
+
return { serviceName, method, path: path100 };
|
|
21656
21785
|
}
|
|
21657
21786
|
|
|
21658
21787
|
// src/connectors/cloud-run/map.ts
|
|
@@ -21681,14 +21810,14 @@ function mapLogEntryToSignal2(entry2) {
|
|
|
21681
21810
|
if (!req) return null;
|
|
21682
21811
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
21683
21812
|
const method = req.requestMethod.toUpperCase();
|
|
21684
|
-
const
|
|
21685
|
-
if (
|
|
21813
|
+
const path100 = pathFromRequestUrl2(req.requestUrl);
|
|
21814
|
+
if (path100 === null) return null;
|
|
21686
21815
|
const timestamp = entry2.timestamp;
|
|
21687
21816
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
21688
21817
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
21689
21818
|
return {
|
|
21690
21819
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
21691
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
21820
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path100 }),
|
|
21692
21821
|
callCount: 1,
|
|
21693
21822
|
errorCount: isError ? 1 : 0,
|
|
21694
21823
|
lastObservedIso: timestamp
|
|
@@ -21727,14 +21856,14 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
21727
21856
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
21728
21857
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
21729
21858
|
if (!identity) return null;
|
|
21730
|
-
const { serviceName: gcpServiceName, method, path:
|
|
21731
|
-
const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method,
|
|
21859
|
+
const { serviceName: gcpServiceName, method, path: path100 } = identity;
|
|
21860
|
+
const mappedService = config.serviceMap?.[gcpServiceName] ?? (config.inferServices ? inferServiceName(graph, gcpServiceName, method, path100) ?? void 0 : void 0);
|
|
21732
21861
|
if (mappedService) {
|
|
21733
21862
|
const routeNodeId = findMatchingRouteNode2(
|
|
21734
21863
|
graph,
|
|
21735
21864
|
mappedService,
|
|
21736
21865
|
method,
|
|
21737
|
-
normalizePathTemplate(
|
|
21866
|
+
normalizePathTemplate(path100)
|
|
21738
21867
|
);
|
|
21739
21868
|
if (routeNodeId) {
|
|
21740
21869
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types84.EdgeType.CALLS };
|
|
@@ -21870,9 +21999,9 @@ function parseGcpLbTargetName(targetName) {
|
|
|
21870
21999
|
const secondSep = rest.indexOf(FIELD_SEP3);
|
|
21871
22000
|
if (secondSep === -1) return null;
|
|
21872
22001
|
const method = rest.slice(0, secondSep);
|
|
21873
|
-
const
|
|
21874
|
-
if (!backendServiceName || !method || !
|
|
21875
|
-
return { backendServiceName, method, path:
|
|
22002
|
+
const path100 = rest.slice(secondSep + 1);
|
|
22003
|
+
if (!backendServiceName || !method || !path100) return null;
|
|
22004
|
+
return { backendServiceName, method, path: path100 };
|
|
21876
22005
|
}
|
|
21877
22006
|
|
|
21878
22007
|
// src/connectors/gcp-lb/map.ts
|
|
@@ -21901,14 +22030,14 @@ function mapLogEntryToSignal3(entry2) {
|
|
|
21901
22030
|
if (!req) return null;
|
|
21902
22031
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
21903
22032
|
const method = req.requestMethod.toUpperCase();
|
|
21904
|
-
const
|
|
21905
|
-
if (
|
|
22033
|
+
const path100 = pathFromRequestUrl3(req.requestUrl);
|
|
22034
|
+
if (path100 === null) return null;
|
|
21906
22035
|
const timestamp = entry2.timestamp;
|
|
21907
22036
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
21908
22037
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD5;
|
|
21909
22038
|
return {
|
|
21910
22039
|
targetKind: GCP_LB_TARGET_KIND,
|
|
21911
|
-
targetName: packGcpLbTargetName({ backendServiceName, method, path:
|
|
22040
|
+
targetName: packGcpLbTargetName({ backendServiceName, method, path: path100 }),
|
|
21912
22041
|
callCount: 1,
|
|
21913
22042
|
errorCount: isError ? 1 : 0,
|
|
21914
22043
|
lastObservedIso: timestamp
|
|
@@ -21947,14 +22076,14 @@ function createGcpLbResolveTarget(graph, config) {
|
|
|
21947
22076
|
if (signal.targetKind !== GCP_LB_TARGET_KIND) return null;
|
|
21948
22077
|
const identity = parseGcpLbTargetName(signal.targetName);
|
|
21949
22078
|
if (!identity) return null;
|
|
21950
|
-
const { backendServiceName, method, path:
|
|
21951
|
-
const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method,
|
|
22079
|
+
const { backendServiceName, method, path: path100 } = identity;
|
|
22080
|
+
const mappedService = config.backendServiceMap?.[backendServiceName] ?? (config.inferServices ? inferServiceName(graph, backendServiceName, method, path100) ?? void 0 : void 0);
|
|
21952
22081
|
if (mappedService) {
|
|
21953
22082
|
const routeNodeId = findMatchingRouteNode3(
|
|
21954
22083
|
graph,
|
|
21955
22084
|
mappedService,
|
|
21956
22085
|
method,
|
|
21957
|
-
normalizePathTemplate(
|
|
22086
|
+
normalizePathTemplate(path100)
|
|
21958
22087
|
);
|
|
21959
22088
|
if (routeNodeId) {
|
|
21960
22089
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types88.EdgeType.CALLS };
|
|
@@ -24346,9 +24475,9 @@ function makeK8sFetchImpl(transport) {
|
|
|
24346
24475
|
req.end();
|
|
24347
24476
|
}));
|
|
24348
24477
|
}
|
|
24349
|
-
async function listResource(transport, namespace,
|
|
24478
|
+
async function listResource(transport, namespace, path100, opts = {}) {
|
|
24350
24479
|
const base = opts.apiUrl ?? transport.server;
|
|
24351
|
-
const url = `${base.replace(/\/$/, "")}${
|
|
24480
|
+
const url = `${base.replace(/\/$/, "")}${path100}`;
|
|
24352
24481
|
const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
|
|
24353
24482
|
const res = await junctionFetch(
|
|
24354
24483
|
url,
|
|
@@ -24358,7 +24487,7 @@ async function listResource(transport, namespace, path98, opts = {}) {
|
|
|
24358
24487
|
{ provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
|
|
24359
24488
|
);
|
|
24360
24489
|
if (!res.ok) {
|
|
24361
|
-
throw new Error(`kubernetes ${
|
|
24490
|
+
throw new Error(`kubernetes ${path100} failed: ${res.status} ${res.statusText}`);
|
|
24362
24491
|
}
|
|
24363
24492
|
const json = await res.json();
|
|
24364
24493
|
return Array.isArray(json.items) ? json.items : [];
|
|
@@ -29249,7 +29378,8 @@ async function runOrchestrator(opts) {
|
|
|
29249
29378
|
} catch (err) {
|
|
29250
29379
|
if (!(err instanceof ProjectNameCollisionError)) throw err;
|
|
29251
29380
|
console.error(`neat: ${err.message}`);
|
|
29252
|
-
console.error(
|
|
29381
|
+
console.error(`neat: run \`neat <path> --project <other-name>\` to register it under a different name,`);
|
|
29382
|
+
console.error(`neat: or \`neat uninstall ${opts.project}\` if the old registration is stale.`);
|
|
29253
29383
|
result.exitCode = 1;
|
|
29254
29384
|
return result;
|
|
29255
29385
|
}
|
|
@@ -29828,12 +29958,12 @@ async function runConnectorCommand(rawArgs, deps = {}) {
|
|
|
29828
29958
|
|
|
29829
29959
|
// src/hosted-connect-cli.ts
|
|
29830
29960
|
init_cjs_shims();
|
|
29831
|
-
var
|
|
29961
|
+
var import_node_child_process6 = require("child_process");
|
|
29832
29962
|
|
|
29833
29963
|
// src/login-sso.ts
|
|
29834
29964
|
init_cjs_shims();
|
|
29835
29965
|
var import_node_http2 = require("http");
|
|
29836
|
-
var
|
|
29966
|
+
var import_node_child_process5 = require("child_process");
|
|
29837
29967
|
var import_node_crypto7 = require("crypto");
|
|
29838
29968
|
|
|
29839
29969
|
// src/profiles.ts
|
|
@@ -30046,8 +30176,200 @@ async function clearActiveProfile(home = neatHome3()) {
|
|
|
30046
30176
|
});
|
|
30047
30177
|
}
|
|
30048
30178
|
|
|
30179
|
+
// src/daemon-probe.ts
|
|
30180
|
+
init_cjs_shims();
|
|
30181
|
+
var PROBE_ATTEMPT_TIMEOUT_MS = 3e4;
|
|
30182
|
+
var PROBE_TOTAL_BUDGET_MS = 12e4;
|
|
30183
|
+
var PROBE_RETRY_PAUSE_MS = 2e3;
|
|
30184
|
+
function isTimeoutError(err) {
|
|
30185
|
+
const name = err?.name;
|
|
30186
|
+
return name === "TimeoutError" || name === "AbortError";
|
|
30187
|
+
}
|
|
30188
|
+
async function probeOnce(fetchImpl, root, token, timeoutMs) {
|
|
30189
|
+
let res;
|
|
30190
|
+
try {
|
|
30191
|
+
res = await fetchImpl(`${root}/health`, {
|
|
30192
|
+
headers: { authorization: `Bearer ${token}` },
|
|
30193
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
30194
|
+
});
|
|
30195
|
+
} catch (err) {
|
|
30196
|
+
if (isTimeoutError(err)) return { kind: "timeout" };
|
|
30197
|
+
return { kind: "unreachable", detail: err.message };
|
|
30198
|
+
}
|
|
30199
|
+
if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
|
|
30200
|
+
if (!res.ok) return { kind: "not-neat", status: res.status };
|
|
30201
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
30202
|
+
if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
|
|
30203
|
+
return { kind: "ok" };
|
|
30204
|
+
}
|
|
30205
|
+
async function probeDaemon(fetchImpl, endpoint2, token, hooks) {
|
|
30206
|
+
const root = endpoint2.replace(/\/$/, "");
|
|
30207
|
+
const deadline = hooks.now() + PROBE_TOTAL_BUDGET_MS;
|
|
30208
|
+
let warned = false;
|
|
30209
|
+
for (; ; ) {
|
|
30210
|
+
const result = await probeOnce(fetchImpl, root, token, PROBE_ATTEMPT_TIMEOUT_MS);
|
|
30211
|
+
if (result.kind !== "timeout") return result;
|
|
30212
|
+
if (!warned) {
|
|
30213
|
+
hooks.onWaiting();
|
|
30214
|
+
warned = true;
|
|
30215
|
+
}
|
|
30216
|
+
if (hooks.now() >= deadline) {
|
|
30217
|
+
return { kind: "unreachable", detail: `no response after ${Math.round(PROBE_TOTAL_BUDGET_MS / 1e3)}s` };
|
|
30218
|
+
}
|
|
30219
|
+
await hooks.sleep(PROBE_RETRY_PAUSE_MS);
|
|
30220
|
+
}
|
|
30221
|
+
}
|
|
30222
|
+
|
|
30223
|
+
// src/login-connect.ts
|
|
30224
|
+
init_cjs_shims();
|
|
30225
|
+
var import_node_child_process4 = require("child_process");
|
|
30226
|
+
var import_node_util = require("util");
|
|
30227
|
+
var import_node_fs54 = require("fs");
|
|
30228
|
+
var import_node_path92 = __toESM(require("path"), 1);
|
|
30229
|
+
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process4.execFile);
|
|
30230
|
+
async function hasLocalSnapshot(cwd) {
|
|
30231
|
+
try {
|
|
30232
|
+
await import_node_fs54.promises.access(import_node_path92.default.join(cwd, "neat-out", "graph.json"));
|
|
30233
|
+
return true;
|
|
30234
|
+
} catch {
|
|
30235
|
+
return false;
|
|
30236
|
+
}
|
|
30237
|
+
}
|
|
30238
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
30239
|
+
var POLL_BUDGET_MS = 10 * 6e4;
|
|
30240
|
+
var CP_TIMEOUT_MS = 1e4;
|
|
30241
|
+
async function detectGitHubRepo(cwd) {
|
|
30242
|
+
const read = async (args) => {
|
|
30243
|
+
try {
|
|
30244
|
+
const { stdout } = await execFileAsync("git", args, { cwd, timeout: 5e3 });
|
|
30245
|
+
return stdout.trim();
|
|
30246
|
+
} catch {
|
|
30247
|
+
return null;
|
|
30248
|
+
}
|
|
30249
|
+
};
|
|
30250
|
+
let url = await read(["remote", "get-url", "origin"]);
|
|
30251
|
+
if (!url) {
|
|
30252
|
+
const names = await read(["remote"]);
|
|
30253
|
+
const first = names?.split(/\r?\n/).find((l) => l.trim().length > 0)?.trim();
|
|
30254
|
+
if (!first) return null;
|
|
30255
|
+
url = await read(["remote", "get-url", first]);
|
|
30256
|
+
}
|
|
30257
|
+
if (!url) return null;
|
|
30258
|
+
return parseGitHubRemote(url);
|
|
30259
|
+
}
|
|
30260
|
+
function parseGitHubRemote(url) {
|
|
30261
|
+
const trimmed = url.trim().replace(/\.git$/, "");
|
|
30262
|
+
const m = trimmed.match(/^https?:\/\/(?:[^@/]+@)?github\.com\/([^/]+)\/([^/]+)$/) ?? trimmed.match(/^git@github\.com:([^/]+)\/([^/]+)$/) ?? trimmed.match(/^ssh:\/\/git@github\.com\/([^/]+)\/([^/]+)$/);
|
|
30263
|
+
if (!m) return null;
|
|
30264
|
+
const owner = m[1];
|
|
30265
|
+
const name = m[2];
|
|
30266
|
+
if (!owner || !name) return null;
|
|
30267
|
+
return { owner, name };
|
|
30268
|
+
}
|
|
30269
|
+
async function cpGetJson(fetchImpl, cpUrl, path100, accessToken) {
|
|
30270
|
+
try {
|
|
30271
|
+
const res = await fetchImpl(`${cpUrl}${path100}`, {
|
|
30272
|
+
headers: { authorization: `Bearer ${accessToken}`, accept: "application/json" },
|
|
30273
|
+
signal: AbortSignal.timeout(CP_TIMEOUT_MS)
|
|
30274
|
+
});
|
|
30275
|
+
if (!res.ok) return { ok: false };
|
|
30276
|
+
return { ok: true, body: await res.json() };
|
|
30277
|
+
} catch {
|
|
30278
|
+
return { ok: false };
|
|
30279
|
+
}
|
|
30280
|
+
}
|
|
30281
|
+
async function listBoundRepos(fetchImpl, cpUrl, accessToken, projectId) {
|
|
30282
|
+
const res = await cpGetJson(
|
|
30283
|
+
fetchImpl,
|
|
30284
|
+
cpUrl,
|
|
30285
|
+
`/me/projects/${encodeURIComponent(projectId)}/repos`,
|
|
30286
|
+
accessToken
|
|
30287
|
+
);
|
|
30288
|
+
if (!res.ok || !Array.isArray(res.body)) return null;
|
|
30289
|
+
return res.body;
|
|
30290
|
+
}
|
|
30291
|
+
async function waitForFirstProject(fetchImpl, cpUrl, accessToken, deps) {
|
|
30292
|
+
const pollMs = deps.pollMs ?? POLL_INTERVAL_MS;
|
|
30293
|
+
const deadline = deps.now() + (deps.budgetMs ?? POLL_BUDGET_MS);
|
|
30294
|
+
for (; ; ) {
|
|
30295
|
+
if (deps.signal?.aborted) return { kind: "aborted" };
|
|
30296
|
+
const me = await cpGetJson(fetchImpl, cpUrl, "/me", accessToken);
|
|
30297
|
+
if (me.ok) {
|
|
30298
|
+
const running = (me.body.projects ?? []).find((p) => p.status === "running");
|
|
30299
|
+
if (running) return { kind: "project", project: running };
|
|
30300
|
+
}
|
|
30301
|
+
if (deps.now() >= deadline) return { kind: "timeout" };
|
|
30302
|
+
await deps.sleep(pollMs);
|
|
30303
|
+
if (deps.signal?.aborted) return { kind: "aborted" };
|
|
30304
|
+
}
|
|
30305
|
+
}
|
|
30306
|
+
function bindUrl(webUrl, projectId) {
|
|
30307
|
+
return `${webUrl}/config/repos?project=${encodeURIComponent(projectId)}`;
|
|
30308
|
+
}
|
|
30309
|
+
function onboardingUrl(webUrl) {
|
|
30310
|
+
return `${webUrl}/onboarding`;
|
|
30311
|
+
}
|
|
30312
|
+
async function runPostLoginConnect(input) {
|
|
30313
|
+
const d = input.deps;
|
|
30314
|
+
const out = d.out ?? ((line) => console.log(line));
|
|
30315
|
+
const fetchImpl = d.fetchImpl ?? fetch;
|
|
30316
|
+
const cwd = d.cwd ?? process.cwd();
|
|
30317
|
+
const detect6 = d.detectRepo ?? detectGitHubRepo;
|
|
30318
|
+
const readLine = d.readLine;
|
|
30319
|
+
const repo = await detect6(cwd);
|
|
30320
|
+
out("");
|
|
30321
|
+
if (!repo) {
|
|
30322
|
+
out("This directory has no GitHub remote, so it can't be bound to the project.");
|
|
30323
|
+
out(` Push its graph instead: neat sync --to ${input.project.name}`);
|
|
30324
|
+
out(` Manage projects and repos: ${input.webUrl}`);
|
|
30325
|
+
return;
|
|
30326
|
+
}
|
|
30327
|
+
const slug = `${repo.owner}/${repo.name}`;
|
|
30328
|
+
const bound = await listBoundRepos(fetchImpl, input.cpUrl, input.accessToken, input.project.id);
|
|
30329
|
+
const match = bound?.find((r) => r.owner === repo.owner && r.name === repo.name);
|
|
30330
|
+
if (match) {
|
|
30331
|
+
const state = match.syncStatus ? ` (${match.syncStatus})` : "";
|
|
30332
|
+
out(`${slug} is bound to ${input.project.name}${state} \u2014 hosted NEAT extracts it for you.`);
|
|
30333
|
+
out(` See the graph: ${input.webUrl}`);
|
|
30334
|
+
return;
|
|
30335
|
+
}
|
|
30336
|
+
out(`${slug} isn't part of ${input.project.name} yet \u2014 logging in connected your account, not this repo.`);
|
|
30337
|
+
out("");
|
|
30338
|
+
const installed = input.me?.github?.installed;
|
|
30339
|
+
const bindLine = installed === false ? "1) Install the GitHub App and bind this repo \u2014 hosted NEAT keeps it in sync" : installed === true ? "1) Bind this repo \u2014 hosted NEAT clones and extracts it, and keeps it in sync" : "1) Bind this repo through the console \u2014 hosted NEAT keeps it in sync";
|
|
30340
|
+
out(bindLine);
|
|
30341
|
+
out(`2) Push the graph on this machine now \u2014 a snapshot, not a subscription`);
|
|
30342
|
+
out("3) Neither, for now");
|
|
30343
|
+
const answer = readLine ? (await readLine("Choose 1, 2 or 3 (Enter skips): "))?.trim() : void 0;
|
|
30344
|
+
if (answer === "1") {
|
|
30345
|
+
const url = bindUrl(input.webUrl, input.project.id);
|
|
30346
|
+
const opened = (d.openBrowser ?? (() => false))(url);
|
|
30347
|
+
out("");
|
|
30348
|
+
out(opened ? `Opened ${url}` : `Open this to bind the repo:
|
|
30349
|
+
${url}`);
|
|
30350
|
+
return;
|
|
30351
|
+
}
|
|
30352
|
+
if (answer === "2") {
|
|
30353
|
+
const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
|
|
30354
|
+
out("");
|
|
30355
|
+
if (!hasGraph) {
|
|
30356
|
+
out("There is no local graph to push yet. Build one, then push it:");
|
|
30357
|
+
out(" neat");
|
|
30358
|
+
out(` neat sync --to ${input.project.name}`);
|
|
30359
|
+
return;
|
|
30360
|
+
}
|
|
30361
|
+
out("Push the local graph with:");
|
|
30362
|
+
out(` neat sync --to ${input.project.name}`);
|
|
30363
|
+
return;
|
|
30364
|
+
}
|
|
30365
|
+
out("");
|
|
30366
|
+
out("No problem \u2014 when you want this repo in the graph:");
|
|
30367
|
+
out(` Bind it: ${bindUrl(input.webUrl, input.project.id)}`);
|
|
30368
|
+
out(` Or push it: neat sync --to ${input.project.name}`);
|
|
30369
|
+
}
|
|
30370
|
+
|
|
30049
30371
|
// src/login-sso.ts
|
|
30050
|
-
var DEFAULT_CP_URL = "https://neat
|
|
30372
|
+
var DEFAULT_CP_URL = "https://api.neat.is";
|
|
30051
30373
|
var DEFAULT_WEB_URL = "https://app.neat.is";
|
|
30052
30374
|
var CALLBACK_TIMEOUT_MS = 5 * 6e4;
|
|
30053
30375
|
function resolveCpUrl(env = process.env, override) {
|
|
@@ -30071,6 +30393,7 @@ function pickProject(projects, want) {
|
|
|
30071
30393
|
return {
|
|
30072
30394
|
error: {
|
|
30073
30395
|
code: 1,
|
|
30396
|
+
kind: "no-project",
|
|
30074
30397
|
message: `no running project to connect to \u2014 create + provision one in the console first${listed}`
|
|
30075
30398
|
}
|
|
30076
30399
|
};
|
|
@@ -30121,14 +30444,14 @@ async function exchangeCredential(cpUrl, accessToken, opts, deps) {
|
|
|
30121
30444
|
if (!cred.endpoint || !cred.authToken) {
|
|
30122
30445
|
return { error: { code: 1, message: "the credential response was missing endpoint/authToken" } };
|
|
30123
30446
|
}
|
|
30124
|
-
return { project, cred };
|
|
30447
|
+
return { project, cred, me };
|
|
30125
30448
|
}
|
|
30126
30449
|
function defaultOpenBrowser(url) {
|
|
30127
30450
|
const platform = process.platform;
|
|
30128
30451
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
30129
30452
|
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
30130
30453
|
try {
|
|
30131
|
-
const child = (0,
|
|
30454
|
+
const child = (0, import_node_child_process5.spawn)(cmd, args, { detached: true, stdio: "ignore" });
|
|
30132
30455
|
child.on("error", () => {
|
|
30133
30456
|
});
|
|
30134
30457
|
child.unref();
|
|
@@ -30203,12 +30526,50 @@ async function runSsoLogin(opts, deps) {
|
|
|
30203
30526
|
}
|
|
30204
30527
|
accessToken = lb.token;
|
|
30205
30528
|
}
|
|
30206
|
-
const
|
|
30529
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
30530
|
+
const sleep2 = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
30531
|
+
const now = deps.now ?? Date.now;
|
|
30532
|
+
let ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
|
|
30533
|
+
if ("error" in ex && ex.error.kind === "no-project" && !opts.json) {
|
|
30534
|
+
const url = onboardingUrl(opts.webUrl);
|
|
30535
|
+
out("");
|
|
30536
|
+
out("You don't have a project yet \u2014 let's make one.");
|
|
30537
|
+
const opened = (deps.openBrowser ?? defaultOpenBrowser)(url);
|
|
30538
|
+
out(opened ? `Opened ${url}` : `Open this to create your first project:
|
|
30539
|
+
${url}`);
|
|
30540
|
+
out("Waiting for it to come up \u2014 Ctrl-C to stop and finish later.");
|
|
30541
|
+
const waited = await waitForFirstProject(fetchImpl, opts.cpUrl, accessToken, {
|
|
30542
|
+
sleep: sleep2,
|
|
30543
|
+
now,
|
|
30544
|
+
...deps.signal ? { signal: deps.signal } : {}
|
|
30545
|
+
});
|
|
30546
|
+
if (waited.kind === "aborted") {
|
|
30547
|
+
out("Stopped. Run `neat login` again once your project is up.");
|
|
30548
|
+
return 130;
|
|
30549
|
+
}
|
|
30550
|
+
if (waited.kind === "timeout") {
|
|
30551
|
+
err("neat login: no project came up while waiting \u2014 run `neat login` again once it does");
|
|
30552
|
+
return 1;
|
|
30553
|
+
}
|
|
30554
|
+
out(`${waited.project.name} is up.`);
|
|
30555
|
+
ex = await exchangeCredential(opts.cpUrl, accessToken, { project: opts.project }, deps);
|
|
30556
|
+
}
|
|
30207
30557
|
if ("error" in ex) {
|
|
30208
30558
|
err(`neat login: ${ex.error.message}`);
|
|
30209
30559
|
return ex.error.code;
|
|
30210
30560
|
}
|
|
30211
|
-
const { project, cred } = ex;
|
|
30561
|
+
const { project, cred, me } = ex;
|
|
30562
|
+
const probe = await probeDaemon(fetchImpl, cred.endpoint, cred.authToken, {
|
|
30563
|
+
sleep: sleep2,
|
|
30564
|
+
now,
|
|
30565
|
+
onWaiting: () => err("Waking the hosted daemon \u2014 a cold instance can take up to a minute\u2026")
|
|
30566
|
+
});
|
|
30567
|
+
if (probe.kind !== "ok") {
|
|
30568
|
+
const why = probe.kind === "unreachable" ? probe.detail : probe.kind === "unauthorized" ? `it rejected the credential (HTTP ${probe.status})` : `it answered HTTP ${probe.status} and doesn't look like NEAT`;
|
|
30569
|
+
err(`neat login: "${project.name}" is provisioned, but its daemon isn't answering \u2014 ${why}.`);
|
|
30570
|
+
err(`Nothing was changed. Check the project at ${opts.webUrl} and run \`neat login\` again.`);
|
|
30571
|
+
return probe.kind === "unreachable" ? 3 : 1;
|
|
30572
|
+
}
|
|
30212
30573
|
await upsertProfile(
|
|
30213
30574
|
{ name: opts.name, endpoint: cred.endpoint, authToken: cred.authToken },
|
|
30214
30575
|
{ makeActive: true, ...deps.home ? { home: deps.home } : {} }
|
|
@@ -30236,6 +30597,21 @@ async function runSsoLogin(opts, deps) {
|
|
|
30236
30597
|
out(` OTEL_EXPORTER_OTLP_ENDPOINT=${cred.ingestEndpoint}`);
|
|
30237
30598
|
out(` OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${cred.otelToken}`);
|
|
30238
30599
|
}
|
|
30600
|
+
await runPostLoginConnect({
|
|
30601
|
+
cpUrl: opts.cpUrl,
|
|
30602
|
+
webUrl: opts.webUrl,
|
|
30603
|
+
accessToken,
|
|
30604
|
+
project: { id: project.id, name: project.name },
|
|
30605
|
+
me,
|
|
30606
|
+
deps: {
|
|
30607
|
+
fetchImpl,
|
|
30608
|
+
out,
|
|
30609
|
+
...deps.readLine ? { readLine: deps.readLine } : {},
|
|
30610
|
+
...deps.openBrowser ? { openBrowser: deps.openBrowser } : { openBrowser: defaultOpenBrowser },
|
|
30611
|
+
...deps.connect ?? {}
|
|
30612
|
+
}
|
|
30613
|
+
});
|
|
30614
|
+
out("");
|
|
30239
30615
|
out("Run `neat logout` to switch back to your local daemon.");
|
|
30240
30616
|
}
|
|
30241
30617
|
return 0;
|
|
@@ -30247,7 +30623,7 @@ function defaultOpenBrowser2(url) {
|
|
|
30247
30623
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
30248
30624
|
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
30249
30625
|
try {
|
|
30250
|
-
const child = (0,
|
|
30626
|
+
const child = (0, import_node_child_process6.spawn)(cmd, args, { detached: true, stdio: "ignore" });
|
|
30251
30627
|
child.on("error", () => {
|
|
30252
30628
|
});
|
|
30253
30629
|
child.unref();
|
|
@@ -30342,8 +30718,8 @@ async function runConnectCommand(rawArgs, deps = {}) {
|
|
|
30342
30718
|
|
|
30343
30719
|
// src/doctor-cli.ts
|
|
30344
30720
|
init_cjs_shims();
|
|
30345
|
-
var
|
|
30346
|
-
var
|
|
30721
|
+
var import_node_fs55 = require("fs");
|
|
30722
|
+
var import_node_path93 = __toESM(require("path"), 1);
|
|
30347
30723
|
|
|
30348
30724
|
// src/cli-client.ts
|
|
30349
30725
|
init_cjs_shims();
|
|
@@ -30372,10 +30748,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
30372
30748
|
const root = baseUrl.replace(/\/$/, "");
|
|
30373
30749
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
30374
30750
|
return {
|
|
30375
|
-
async get(
|
|
30751
|
+
async get(path100) {
|
|
30376
30752
|
let res;
|
|
30377
30753
|
try {
|
|
30378
|
-
res = await fetchImpl(`${root}${
|
|
30754
|
+
res = await fetchImpl(`${root}${path100}`, {
|
|
30379
30755
|
headers: { ...authHeader }
|
|
30380
30756
|
});
|
|
30381
30757
|
} catch (err) {
|
|
@@ -30387,16 +30763,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
30387
30763
|
const body = await res.text().catch(() => "");
|
|
30388
30764
|
throw new HttpError(
|
|
30389
30765
|
res.status,
|
|
30390
|
-
`${res.status} ${res.statusText} on GET ${
|
|
30766
|
+
`${res.status} ${res.statusText} on GET ${path100}: ${body}`,
|
|
30391
30767
|
body
|
|
30392
30768
|
);
|
|
30393
30769
|
}
|
|
30394
30770
|
return await res.json();
|
|
30395
30771
|
},
|
|
30396
|
-
async post(
|
|
30772
|
+
async post(path100, body) {
|
|
30397
30773
|
let res;
|
|
30398
30774
|
try {
|
|
30399
|
-
res = await fetchImpl(`${root}${
|
|
30775
|
+
res = await fetchImpl(`${root}${path100}`, {
|
|
30400
30776
|
method: "POST",
|
|
30401
30777
|
headers: { "content-type": "application/json", ...authHeader },
|
|
30402
30778
|
body: JSON.stringify(body)
|
|
@@ -30410,7 +30786,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
30410
30786
|
const text = await res.text().catch(() => "");
|
|
30411
30787
|
throw new HttpError(
|
|
30412
30788
|
res.status,
|
|
30413
|
-
`${res.status} ${res.statusText} on POST ${
|
|
30789
|
+
`${res.status} ${res.statusText} on POST ${path100}: ${text}`,
|
|
30414
30790
|
text
|
|
30415
30791
|
);
|
|
30416
30792
|
}
|
|
@@ -30424,12 +30800,12 @@ function projectPath(project, suffix) {
|
|
|
30424
30800
|
}
|
|
30425
30801
|
async function runRootCause(client, input) {
|
|
30426
30802
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
30427
|
-
const
|
|
30803
|
+
const path100 = projectPath(
|
|
30428
30804
|
input.project,
|
|
30429
30805
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
30430
30806
|
);
|
|
30431
30807
|
try {
|
|
30432
|
-
const result = await client.get(
|
|
30808
|
+
const result = await client.get(path100);
|
|
30433
30809
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
30434
30810
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
30435
30811
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -30455,12 +30831,12 @@ async function runRootCause(client, input) {
|
|
|
30455
30831
|
}
|
|
30456
30832
|
async function runBlastRadius(client, input) {
|
|
30457
30833
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
30458
|
-
const
|
|
30834
|
+
const path100 = projectPath(
|
|
30459
30835
|
input.project,
|
|
30460
30836
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
30461
30837
|
);
|
|
30462
30838
|
try {
|
|
30463
|
-
const result = await client.get(
|
|
30839
|
+
const result = await client.get(path100);
|
|
30464
30840
|
if (result.totalAffected === 0) {
|
|
30465
30841
|
return {
|
|
30466
30842
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -30494,12 +30870,12 @@ function formatBlastEntry(n) {
|
|
|
30494
30870
|
}
|
|
30495
30871
|
async function runDependencies(client, input) {
|
|
30496
30872
|
const depth = input.depth ?? 3;
|
|
30497
|
-
const
|
|
30873
|
+
const path100 = projectPath(
|
|
30498
30874
|
input.project,
|
|
30499
30875
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
30500
30876
|
);
|
|
30501
30877
|
try {
|
|
30502
|
-
const result = await client.get(
|
|
30878
|
+
const result = await client.get(path100);
|
|
30503
30879
|
if (result.total === 0) {
|
|
30504
30880
|
return {
|
|
30505
30881
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -30591,9 +30967,9 @@ function formatDuration(ms) {
|
|
|
30591
30967
|
return `${Math.round(h / 24)}d`;
|
|
30592
30968
|
}
|
|
30593
30969
|
async function runIncidents(client, input) {
|
|
30594
|
-
const
|
|
30970
|
+
const path100 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
30595
30971
|
try {
|
|
30596
|
-
const body = await client.get(
|
|
30972
|
+
const body = await client.get(path100);
|
|
30597
30973
|
const events = body.events;
|
|
30598
30974
|
if (events.length === 0) {
|
|
30599
30975
|
return {
|
|
@@ -30957,7 +31333,7 @@ function checkNode(nodeVersion) {
|
|
|
30957
31333
|
}
|
|
30958
31334
|
async function neatOutExists(cwd) {
|
|
30959
31335
|
try {
|
|
30960
|
-
const st = await
|
|
31336
|
+
const st = await import_node_fs55.promises.stat(import_node_path93.default.join(cwd, "neat-out"));
|
|
30961
31337
|
return st.isDirectory();
|
|
30962
31338
|
} catch {
|
|
30963
31339
|
return false;
|
|
@@ -31077,9 +31453,6 @@ async function runDoctorCommand(argv, deps = {}) {
|
|
|
31077
31453
|
// src/login-cli.ts
|
|
31078
31454
|
init_cjs_shims();
|
|
31079
31455
|
var import_promises4 = __toESM(require("readline/promises"), 1);
|
|
31080
|
-
var PROBE_ATTEMPT_TIMEOUT_MS = 3e4;
|
|
31081
|
-
var PROBE_TOTAL_BUDGET_MS = 12e4;
|
|
31082
|
-
var PROBE_RETRY_PAUSE_MS = 2e3;
|
|
31083
31456
|
var DEFAULT_PROFILE_NAME = "hosted";
|
|
31084
31457
|
function readFlagValue(argv, i) {
|
|
31085
31458
|
const arg = argv[i];
|
|
@@ -31144,44 +31517,6 @@ function printLoginHelp(out) {
|
|
|
31144
31517
|
out(' the profile (default "hosted"); the paste token can also come from NEAT_LOGIN_TOKEN.');
|
|
31145
31518
|
out(" Exit 0 on success, 1 rejected/needs-action, 2 misuse, 3 unreachable.");
|
|
31146
31519
|
}
|
|
31147
|
-
function isTimeoutError(err) {
|
|
31148
|
-
const name = err?.name;
|
|
31149
|
-
return name === "TimeoutError" || name === "AbortError";
|
|
31150
|
-
}
|
|
31151
|
-
async function probeOnce(fetchImpl, root, token, timeoutMs) {
|
|
31152
|
-
let res;
|
|
31153
|
-
try {
|
|
31154
|
-
res = await fetchImpl(`${root}/health`, {
|
|
31155
|
-
headers: { authorization: `Bearer ${token}` },
|
|
31156
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
31157
|
-
});
|
|
31158
|
-
} catch (err) {
|
|
31159
|
-
if (isTimeoutError(err)) return { kind: "timeout" };
|
|
31160
|
-
return { kind: "unreachable", detail: err.message };
|
|
31161
|
-
}
|
|
31162
|
-
if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
|
|
31163
|
-
if (!res.ok) return { kind: "not-neat", status: res.status };
|
|
31164
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
31165
|
-
if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
|
|
31166
|
-
return { kind: "ok" };
|
|
31167
|
-
}
|
|
31168
|
-
async function probeDaemon(fetchImpl, endpoint2, token, hooks) {
|
|
31169
|
-
const root = endpoint2.replace(/\/$/, "");
|
|
31170
|
-
const deadline = hooks.now() + PROBE_TOTAL_BUDGET_MS;
|
|
31171
|
-
let warned = false;
|
|
31172
|
-
for (; ; ) {
|
|
31173
|
-
const result = await probeOnce(fetchImpl, root, token, PROBE_ATTEMPT_TIMEOUT_MS);
|
|
31174
|
-
if (result.kind !== "timeout") return result;
|
|
31175
|
-
if (!warned) {
|
|
31176
|
-
hooks.onWaiting();
|
|
31177
|
-
warned = true;
|
|
31178
|
-
}
|
|
31179
|
-
if (hooks.now() >= deadline) {
|
|
31180
|
-
return { kind: "unreachable", detail: `no response after ${Math.round(PROBE_TOTAL_BUDGET_MS / 1e3)}s` };
|
|
31181
|
-
}
|
|
31182
|
-
await hooks.sleep(PROBE_RETRY_PAUSE_MS);
|
|
31183
|
-
}
|
|
31184
|
-
}
|
|
31185
31520
|
async function runLoginCommand(argv, deps = {}) {
|
|
31186
31521
|
const out = deps.out ?? ((line) => console.log(line));
|
|
31187
31522
|
const err = deps.err ?? ((line) => console.error(line));
|
|
@@ -31214,8 +31549,13 @@ async function runLoginCommand(argv, deps = {}) {
|
|
|
31214
31549
|
fetchImpl,
|
|
31215
31550
|
out,
|
|
31216
31551
|
err,
|
|
31552
|
+
readLine,
|
|
31553
|
+
sleep: sleep2,
|
|
31554
|
+
now,
|
|
31217
31555
|
...deps.openBrowser ? { openBrowser: deps.openBrowser } : {},
|
|
31218
|
-
...deps.home ? { home: deps.home } : {}
|
|
31556
|
+
...deps.home ? { home: deps.home } : {},
|
|
31557
|
+
...deps.signal ? { signal: deps.signal } : {},
|
|
31558
|
+
...deps.connect ? { connect: deps.connect } : {}
|
|
31219
31559
|
}
|
|
31220
31560
|
);
|
|
31221
31561
|
}
|
|
@@ -31372,6 +31712,8 @@ async function defaultReadSecret(prompt) {
|
|
|
31372
31712
|
|
|
31373
31713
|
// src/welcome.ts
|
|
31374
31714
|
init_cjs_shims();
|
|
31715
|
+
var import_node_fs56 = require("fs");
|
|
31716
|
+
var import_node_path94 = __toESM(require("path"), 1);
|
|
31375
31717
|
var import_promises5 = __toESM(require("readline/promises"), 1);
|
|
31376
31718
|
var WORDMARK = [
|
|
31377
31719
|
"\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557",
|
|
@@ -31404,29 +31746,146 @@ function printHeader(out) {
|
|
|
31404
31746
|
}
|
|
31405
31747
|
async function runWelcome(deps = {}) {
|
|
31406
31748
|
const out = deps.out ?? ((line) => console.log(line));
|
|
31749
|
+
try {
|
|
31750
|
+
return await runFrontDoor(deps, out);
|
|
31751
|
+
} catch (err) {
|
|
31752
|
+
if (isPromptCancelled(err)) {
|
|
31753
|
+
out("");
|
|
31754
|
+
return 130;
|
|
31755
|
+
}
|
|
31756
|
+
throw err;
|
|
31757
|
+
}
|
|
31758
|
+
}
|
|
31759
|
+
var PromptCancelled = class extends Error {
|
|
31760
|
+
constructor() {
|
|
31761
|
+
super("prompt cancelled");
|
|
31762
|
+
this.name = "PromptCancelled";
|
|
31763
|
+
}
|
|
31764
|
+
};
|
|
31765
|
+
function isPromptCancelled(err) {
|
|
31766
|
+
if (err instanceof PromptCancelled) return true;
|
|
31767
|
+
const e = err;
|
|
31768
|
+
return e?.code === "ABORT_ERR" || e?.name === "AbortError";
|
|
31769
|
+
}
|
|
31770
|
+
async function runFrontDoor(deps, out) {
|
|
31407
31771
|
const readLine = deps.readLine ?? defaultReadLine2;
|
|
31408
31772
|
const login = deps.login ?? (() => Promise.resolve(0));
|
|
31409
31773
|
const orchestrator = deps.orchestrator ?? (() => Promise.resolve(0));
|
|
31410
31774
|
const cwd = deps.cwd ?? process.cwd();
|
|
31775
|
+
const flagGiven = deps.instrumentFlagGiven ?? false;
|
|
31776
|
+
const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
|
|
31411
31777
|
printHeader(out);
|
|
31412
31778
|
out("Welcome to NEAT. Let's get you a graph of this system.");
|
|
31413
31779
|
out("");
|
|
31414
|
-
|
|
31415
|
-
|
|
31780
|
+
const keyReader = deps.readKey !== void 0 ? { read: deps.readKey, done: () => {
|
|
31781
|
+
} } : createKeyReader();
|
|
31782
|
+
const picked = keyReader !== null ? await selectOption(out, keyReader, deps.moveCursorUp ?? defaultMoveCursorUp) : await selectByNumber(out, readLine);
|
|
31783
|
+
if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
|
|
31784
|
+
if (picked === 0) return login(["--browser"]);
|
|
31785
|
+
return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
|
|
31786
|
+
}
|
|
31787
|
+
var MENU_OPTIONS = [
|
|
31788
|
+
"Log me into Hosted Neat",
|
|
31789
|
+
"I'd like to self-host or use it locally (copy a prompt)"
|
|
31790
|
+
];
|
|
31791
|
+
async function selectOption(out, reader, moveCursorUp) {
|
|
31792
|
+
let cursor = 0;
|
|
31793
|
+
const render = () => {
|
|
31794
|
+
for (const [i, label] of MENU_OPTIONS.entries()) {
|
|
31795
|
+
out(i === cursor ? ` \u276F ${i + 1}) ${label}` : ` ${i + 1}) ${label}`);
|
|
31796
|
+
}
|
|
31797
|
+
};
|
|
31798
|
+
try {
|
|
31799
|
+
render();
|
|
31800
|
+
out("");
|
|
31801
|
+
out(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 1 or 2 to jump");
|
|
31802
|
+
for (; ; ) {
|
|
31803
|
+
const key = await reader.read();
|
|
31804
|
+
if (key === void 0) return void 0;
|
|
31805
|
+
if (key === "cancel") throw new PromptCancelled();
|
|
31806
|
+
if (key === "enter") return cursor;
|
|
31807
|
+
if (key === "select-1") return 0;
|
|
31808
|
+
if (key === "select-2") return 1;
|
|
31809
|
+
if (key === "up") cursor = (cursor + MENU_OPTIONS.length - 1) % MENU_OPTIONS.length;
|
|
31810
|
+
else if (key === "down") cursor = (cursor + 1) % MENU_OPTIONS.length;
|
|
31811
|
+
else continue;
|
|
31812
|
+
moveCursorUp(MENU_OPTIONS.length + 2);
|
|
31813
|
+
render();
|
|
31814
|
+
out("");
|
|
31815
|
+
out(" \u2191/\u2193 to move \xB7 Enter to choose \xB7 1 or 2 to jump");
|
|
31816
|
+
}
|
|
31817
|
+
} finally {
|
|
31818
|
+
reader.done();
|
|
31819
|
+
}
|
|
31820
|
+
}
|
|
31821
|
+
async function selectByNumber(out, readLine) {
|
|
31822
|
+
for (const [i, label] of MENU_OPTIONS.entries()) out(` ${i + 1}) ${label}`);
|
|
31416
31823
|
out("");
|
|
31417
31824
|
for (; ; ) {
|
|
31418
31825
|
const choice = (await readLine("Choose 1 or 2 (default 2): "))?.trim();
|
|
31419
|
-
if (choice === void 0) return
|
|
31420
|
-
if (choice === "1")
|
|
31421
|
-
|
|
31422
|
-
}
|
|
31423
|
-
if (choice === "2" || choice === "") {
|
|
31424
|
-
return runSelfHosted(out, readLine, orchestrator, cwd);
|
|
31425
|
-
}
|
|
31426
|
-
out(`"${choice}" isn't 1 or 2 \u2014 try again, or press Enter for self-hosted.`);
|
|
31826
|
+
if (choice === void 0) return void 0;
|
|
31827
|
+
if (choice === "1") return 0;
|
|
31828
|
+
if (choice === "2" || choice === "") return 1;
|
|
31829
|
+
out(`"${choice}" isn't 1 or 2 \u2014 try again, or press Enter for the local path.`);
|
|
31427
31830
|
}
|
|
31428
31831
|
}
|
|
31429
|
-
|
|
31832
|
+
function createKeyReader() {
|
|
31833
|
+
const stdin = process.stdin;
|
|
31834
|
+
if (!stdin.isTTY || typeof stdin.setRawMode !== "function") return null;
|
|
31835
|
+
stdin.setRawMode(true);
|
|
31836
|
+
stdin.resume();
|
|
31837
|
+
let closed = false;
|
|
31838
|
+
let ended = false;
|
|
31839
|
+
const markEnded = () => {
|
|
31840
|
+
ended = true;
|
|
31841
|
+
};
|
|
31842
|
+
stdin.once("end", markEnded);
|
|
31843
|
+
stdin.once("close", markEnded);
|
|
31844
|
+
return {
|
|
31845
|
+
read: () => new Promise((resolve) => {
|
|
31846
|
+
if (ended) {
|
|
31847
|
+
resolve(void 0);
|
|
31848
|
+
return;
|
|
31849
|
+
}
|
|
31850
|
+
const finish = (value) => {
|
|
31851
|
+
stdin.removeListener("data", onData);
|
|
31852
|
+
stdin.removeListener("end", onClosed);
|
|
31853
|
+
stdin.removeListener("close", onClosed);
|
|
31854
|
+
resolve(value);
|
|
31855
|
+
};
|
|
31856
|
+
const onData = (buf) => finish(decodeKey(buf));
|
|
31857
|
+
const onClosed = () => {
|
|
31858
|
+
ended = true;
|
|
31859
|
+
finish(void 0);
|
|
31860
|
+
};
|
|
31861
|
+
stdin.on("data", onData);
|
|
31862
|
+
stdin.once("end", onClosed);
|
|
31863
|
+
stdin.once("close", onClosed);
|
|
31864
|
+
}),
|
|
31865
|
+
done: () => {
|
|
31866
|
+
if (closed) return;
|
|
31867
|
+
closed = true;
|
|
31868
|
+
stdin.removeListener("end", markEnded);
|
|
31869
|
+
stdin.removeListener("close", markEnded);
|
|
31870
|
+
stdin.setRawMode(false);
|
|
31871
|
+
stdin.pause();
|
|
31872
|
+
}
|
|
31873
|
+
};
|
|
31874
|
+
}
|
|
31875
|
+
function decodeKey(buf) {
|
|
31876
|
+
const s = buf.toString("utf8");
|
|
31877
|
+
if (s === "\x1B[A" || s === "k") return "up";
|
|
31878
|
+
if (s === "\x1B[B" || s === "j") return "down";
|
|
31879
|
+
if (s === "\r" || s === "\n") return "enter";
|
|
31880
|
+
if (s === "1") return "select-1";
|
|
31881
|
+
if (s === "2") return "select-2";
|
|
31882
|
+
if (s === "" || s === "\x1B") return "cancel";
|
|
31883
|
+
return "ignore";
|
|
31884
|
+
}
|
|
31885
|
+
function defaultMoveCursorUp(rows) {
|
|
31886
|
+
if (rows > 0) process.stdout.write(`\x1B[${rows}A`);
|
|
31887
|
+
}
|
|
31888
|
+
async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, instrumentFlagGiven) {
|
|
31430
31889
|
const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
|
|
31431
31890
|
if (wantsPrompt !== "n" && wantsPrompt !== "no") {
|
|
31432
31891
|
out("");
|
|
@@ -31437,18 +31896,91 @@ async function runSelfHosted(out, readLine, orchestrator, cwd) {
|
|
|
31437
31896
|
out("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
31438
31897
|
out("");
|
|
31439
31898
|
}
|
|
31899
|
+
const project = await resolveProjectName(out, readLine, cwd, readRegistry2);
|
|
31900
|
+
const declined = instrumentFlagGiven ? void 0 : await askToInstrument(out, readLine);
|
|
31440
31901
|
out("Building your local graph now\u2026");
|
|
31441
31902
|
out("");
|
|
31442
|
-
|
|
31903
|
+
const overrides = {
|
|
31904
|
+
...project !== void 0 ? { project } : {},
|
|
31905
|
+
...declined === void 0 ? {} : declined ? { noInstrument: true } : { yes: true }
|
|
31906
|
+
};
|
|
31907
|
+
return orchestrator(cwd, Object.keys(overrides).length > 0 ? overrides : void 0);
|
|
31908
|
+
}
|
|
31909
|
+
async function askToInstrument(out, readLine) {
|
|
31910
|
+
out("Instrument the services for OpenTelemetry now?");
|
|
31911
|
+
out("This edits package.json / requirements.txt / go.mod and runs the package manager.");
|
|
31912
|
+
const answer = (await readLine("[Y/n]: "))?.trim().toLowerCase();
|
|
31913
|
+
const declined = answer === "n" || answer === "no";
|
|
31914
|
+
if (declined) {
|
|
31915
|
+
out("");
|
|
31916
|
+
out("Skipping instrumentation \u2014 the graph will hold the declared side only.");
|
|
31917
|
+
out("Run `neat init . --apply` when you want the runtime half.");
|
|
31918
|
+
}
|
|
31919
|
+
out("");
|
|
31920
|
+
return declined;
|
|
31921
|
+
}
|
|
31922
|
+
async function resolveProjectName(out, readLine, cwd, readRegistry2) {
|
|
31923
|
+
const here = import_node_path94.default.resolve(cwd);
|
|
31924
|
+
const base = import_node_path94.default.basename(here);
|
|
31925
|
+
let taken;
|
|
31926
|
+
try {
|
|
31927
|
+
const { projects } = await readRegistry2();
|
|
31928
|
+
taken = new Map(projects.map((p) => [p.name, p.path]));
|
|
31929
|
+
} catch {
|
|
31930
|
+
return void 0;
|
|
31931
|
+
}
|
|
31932
|
+
const clash = taken.get(base);
|
|
31933
|
+
if (clash === void 0 || import_node_path94.default.resolve(clash) === here) return void 0;
|
|
31934
|
+
out("");
|
|
31935
|
+
out(`A project named \`${base}\` is already registered (${clash}).`);
|
|
31936
|
+
const suggested = firstFreeName(base, taken);
|
|
31937
|
+
for (; ; ) {
|
|
31938
|
+
const answer = (await readLine(`Name this one: [${suggested}] `))?.trim();
|
|
31939
|
+
if (answer === void 0) return suggested;
|
|
31940
|
+
const chosen = answer.length === 0 ? suggested : answer;
|
|
31941
|
+
if (!isUsableProjectName(chosen)) {
|
|
31942
|
+
out(`"${chosen}" won't work as a project name \u2014 letters, digits, dot, dash and underscore.`);
|
|
31943
|
+
continue;
|
|
31944
|
+
}
|
|
31945
|
+
const other = taken.get(chosen);
|
|
31946
|
+
if (other !== void 0 && import_node_path94.default.resolve(other) !== here) {
|
|
31947
|
+
out(`\`${chosen}\` is registered too (${other}).`);
|
|
31948
|
+
continue;
|
|
31949
|
+
}
|
|
31950
|
+
out("");
|
|
31951
|
+
return chosen;
|
|
31952
|
+
}
|
|
31953
|
+
}
|
|
31954
|
+
function firstFreeName(base, taken) {
|
|
31955
|
+
for (let n = 2; ; n++) {
|
|
31956
|
+
const candidate = `${base}-${n}`;
|
|
31957
|
+
if (!taken.has(candidate)) return candidate;
|
|
31958
|
+
}
|
|
31959
|
+
}
|
|
31960
|
+
function isUsableProjectName(name) {
|
|
31961
|
+
return name.length > 0 && name !== "." && name !== ".." && /^[A-Za-z0-9._-]+$/.test(name);
|
|
31443
31962
|
}
|
|
31444
31963
|
async function shouldShowWelcome(deps = {}) {
|
|
31445
31964
|
const stdinTTY = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
31446
31965
|
const stdoutTTY = deps.stdoutIsTTY ?? Boolean(process.stdout.isTTY);
|
|
31447
31966
|
if (!stdinTTY || !stdoutTTY) return false;
|
|
31448
|
-
const
|
|
31967
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
31968
|
+
const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
|
|
31969
|
+
const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
|
|
31970
|
+
try {
|
|
31971
|
+
const here = import_node_path94.default.resolve(cwd);
|
|
31972
|
+
const { projects } = await readRegistry2();
|
|
31973
|
+
if (projects.some((p) => import_node_path94.default.resolve(p.path) === here)) return false;
|
|
31974
|
+
if (await hasSnapshot(here)) return false;
|
|
31975
|
+
return true;
|
|
31976
|
+
} catch {
|
|
31977
|
+
return false;
|
|
31978
|
+
}
|
|
31979
|
+
}
|
|
31980
|
+
async function defaultHasSnapshot(cwd) {
|
|
31449
31981
|
try {
|
|
31450
|
-
const
|
|
31451
|
-
return
|
|
31982
|
+
const entries = await import_node_fs56.promises.readdir(import_node_path94.default.join(cwd, "neat-out"));
|
|
31983
|
+
return entries.some((e) => e.endsWith(".json"));
|
|
31452
31984
|
} catch {
|
|
31453
31985
|
return false;
|
|
31454
31986
|
}
|
|
@@ -31465,27 +31997,27 @@ async function defaultReadLine2(prompt) {
|
|
|
31465
31997
|
|
|
31466
31998
|
// src/hooks-cli.ts
|
|
31467
31999
|
init_cjs_shims();
|
|
31468
|
-
var
|
|
32000
|
+
var import_node_path95 = __toESM(require("path"), 1);
|
|
31469
32001
|
var import_node_os8 = __toESM(require("os"), 1);
|
|
31470
|
-
var
|
|
32002
|
+
var import_node_fs57 = require("fs");
|
|
31471
32003
|
var import_node_url5 = require("url");
|
|
31472
32004
|
var HOOK_FILENAME = "neat-search-nudge.mjs";
|
|
31473
32005
|
var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
31474
32006
|
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
31475
32007
|
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
31476
32008
|
function moduleDir() {
|
|
31477
|
-
return typeof __dirname !== "undefined" ? __dirname :
|
|
32009
|
+
return typeof __dirname !== "undefined" ? __dirname : import_node_path95.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
|
|
31478
32010
|
}
|
|
31479
32011
|
async function readSkillAsset(rel) {
|
|
31480
32012
|
const here = moduleDir();
|
|
31481
32013
|
const candidates = [
|
|
31482
|
-
|
|
31483
|
-
|
|
31484
|
-
|
|
32014
|
+
import_node_path95.default.resolve(here, "../../claude-skill", rel),
|
|
32015
|
+
import_node_path95.default.resolve(here, "../../../claude-skill", rel),
|
|
32016
|
+
import_node_path95.default.resolve(here, "../claude-skill", rel)
|
|
31485
32017
|
];
|
|
31486
32018
|
for (const candidate of candidates) {
|
|
31487
32019
|
try {
|
|
31488
|
-
return await
|
|
32020
|
+
return await import_node_fs57.promises.readFile(candidate, "utf8");
|
|
31489
32021
|
} catch {
|
|
31490
32022
|
}
|
|
31491
32023
|
}
|
|
@@ -31495,20 +32027,20 @@ async function readSkillAsset(rel) {
|
|
|
31495
32027
|
}
|
|
31496
32028
|
function neatHome4() {
|
|
31497
32029
|
const override = process.env.NEAT_HOME;
|
|
31498
|
-
if (override && override.length > 0) return
|
|
31499
|
-
return
|
|
32030
|
+
if (override && override.length > 0) return import_node_path95.default.resolve(override);
|
|
32031
|
+
return import_node_path95.default.join(import_node_os8.default.homedir(), ".neat");
|
|
31500
32032
|
}
|
|
31501
32033
|
function claudeSettingsPath() {
|
|
31502
32034
|
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
31503
|
-
if (override && override.length > 0) return
|
|
32035
|
+
if (override && override.length > 0) return import_node_path95.default.resolve(override);
|
|
31504
32036
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os8.default.homedir();
|
|
31505
|
-
return
|
|
32037
|
+
return import_node_path95.default.join(home, ".claude", "settings.json");
|
|
31506
32038
|
}
|
|
31507
32039
|
function installedHookPath() {
|
|
31508
|
-
return
|
|
32040
|
+
return import_node_path95.default.join(neatHome4(), "hooks", HOOK_FILENAME);
|
|
31509
32041
|
}
|
|
31510
32042
|
function gateFlagPath() {
|
|
31511
|
-
return
|
|
32043
|
+
return import_node_path95.default.join(neatHome4(), "hooks", "gate-enabled");
|
|
31512
32044
|
}
|
|
31513
32045
|
function isNeatSearchEntry(entry2) {
|
|
31514
32046
|
return (entry2.hooks ?? []).some(
|
|
@@ -31541,14 +32073,14 @@ async function runHooks(opts) {
|
|
|
31541
32073
|
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
31542
32074
|
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
31543
32075
|
const scriptPath = installedHookPath();
|
|
31544
|
-
await
|
|
31545
|
-
await
|
|
31546
|
-
const guidePath =
|
|
31547
|
-
await
|
|
32076
|
+
await import_node_fs57.promises.mkdir(import_node_path95.default.dirname(scriptPath), { recursive: true });
|
|
32077
|
+
await import_node_fs57.promises.writeFile(scriptPath, hookScript, { mode: 493 });
|
|
32078
|
+
const guidePath = import_node_path95.default.join(neatHome4(), GUIDE_INSTALL_NAME);
|
|
32079
|
+
await import_node_fs57.promises.writeFile(guidePath, guide, "utf8");
|
|
31548
32080
|
const settingsFile = claudeSettingsPath();
|
|
31549
32081
|
let settings = {};
|
|
31550
32082
|
try {
|
|
31551
|
-
settings = JSON.parse(await
|
|
32083
|
+
settings = JSON.parse(await import_node_fs57.promises.readFile(settingsFile, "utf8"));
|
|
31552
32084
|
} catch (err) {
|
|
31553
32085
|
if (err.code !== "ENOENT") {
|
|
31554
32086
|
console.error(
|
|
@@ -31570,14 +32102,14 @@ async function runHooks(opts) {
|
|
|
31570
32102
|
...settings,
|
|
31571
32103
|
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
31572
32104
|
};
|
|
31573
|
-
await
|
|
31574
|
-
await
|
|
32105
|
+
await import_node_fs57.promises.mkdir(import_node_path95.default.dirname(settingsFile), { recursive: true });
|
|
32106
|
+
await import_node_fs57.promises.writeFile(settingsFile, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
31575
32107
|
const flag = gateFlagPath();
|
|
31576
32108
|
if (opts.gate) {
|
|
31577
|
-
await
|
|
31578
|
-
await
|
|
32109
|
+
await import_node_fs57.promises.mkdir(import_node_path95.default.dirname(flag), { recursive: true });
|
|
32110
|
+
await import_node_fs57.promises.writeFile(flag, "1\n", "utf8");
|
|
31579
32111
|
} else {
|
|
31580
|
-
await
|
|
32112
|
+
await import_node_fs57.promises.rm(flag, { force: true });
|
|
31581
32113
|
}
|
|
31582
32114
|
const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
|
|
31583
32115
|
console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
|
|
@@ -31667,8 +32199,8 @@ async function runHooksCommand(args) {
|
|
|
31667
32199
|
|
|
31668
32200
|
// src/claude-cli.ts
|
|
31669
32201
|
init_cjs_shims();
|
|
31670
|
-
var
|
|
31671
|
-
var
|
|
32202
|
+
var import_node_path96 = __toESM(require("path"), 1);
|
|
32203
|
+
var import_node_fs58 = require("fs");
|
|
31672
32204
|
var NEAT_SECTION_HEADING = "## neat";
|
|
31673
32205
|
var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
|
|
31674
32206
|
code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
|
|
@@ -31702,8 +32234,8 @@ ${NEAT_DIRECTIVE_BODY}
|
|
|
31702
32234
|
}
|
|
31703
32235
|
function claudeMdPath() {
|
|
31704
32236
|
const override = process.env.NEAT_CLAUDE_MD;
|
|
31705
|
-
if (override && override.length > 0) return
|
|
31706
|
-
return
|
|
32237
|
+
if (override && override.length > 0) return import_node_path96.default.resolve(override);
|
|
32238
|
+
return import_node_path96.default.join(process.cwd(), "CLAUDE.md");
|
|
31707
32239
|
}
|
|
31708
32240
|
function splitAroundSection(raw) {
|
|
31709
32241
|
const lines = raw.split("\n");
|
|
@@ -31731,7 +32263,7 @@ function compose(before, after) {
|
|
|
31731
32263
|
}
|
|
31732
32264
|
async function readIfExists2(file) {
|
|
31733
32265
|
try {
|
|
31734
|
-
return await
|
|
32266
|
+
return await import_node_fs58.promises.readFile(file, "utf8");
|
|
31735
32267
|
} catch (err) {
|
|
31736
32268
|
if (err.code === "ENOENT") return null;
|
|
31737
32269
|
throw err;
|
|
@@ -31742,8 +32274,8 @@ async function runInstall() {
|
|
|
31742
32274
|
const raw = await readIfExists2(file) ?? "";
|
|
31743
32275
|
const { before, after, found } = splitAroundSection(raw);
|
|
31744
32276
|
const next = compose(before, after);
|
|
31745
|
-
await
|
|
31746
|
-
await
|
|
32277
|
+
await import_node_fs58.promises.mkdir(import_node_path96.default.dirname(file), { recursive: true });
|
|
32278
|
+
await import_node_fs58.promises.writeFile(file, next, "utf8");
|
|
31747
32279
|
const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
|
|
31748
32280
|
console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
|
|
31749
32281
|
console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
|
|
@@ -31764,7 +32296,7 @@ async function runUninstall() {
|
|
|
31764
32296
|
}
|
|
31765
32297
|
const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
|
|
31766
32298
|
const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
|
|
31767
|
-
await
|
|
32299
|
+
await import_node_fs58.promises.writeFile(file, next, "utf8");
|
|
31768
32300
|
console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
|
|
31769
32301
|
return { exitCode: 0 };
|
|
31770
32302
|
}
|
|
@@ -31807,10 +32339,10 @@ async function runClaudeCommand(args) {
|
|
|
31807
32339
|
|
|
31808
32340
|
// src/codex-cli.ts
|
|
31809
32341
|
init_cjs_shims();
|
|
31810
|
-
var
|
|
32342
|
+
var import_node_path97 = __toESM(require("path"), 1);
|
|
31811
32343
|
var import_node_os9 = __toESM(require("os"), 1);
|
|
31812
|
-
var
|
|
31813
|
-
var
|
|
32344
|
+
var import_node_fs59 = require("fs");
|
|
32345
|
+
var import_node_util2 = require("util");
|
|
31814
32346
|
var import_smol_toml6 = require("smol-toml");
|
|
31815
32347
|
var CODEX_MCP_SERVER = {
|
|
31816
32348
|
command: "npx",
|
|
@@ -31827,14 +32359,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
31827
32359
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
31828
32360
|
function codexConfigPath() {
|
|
31829
32361
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
31830
|
-
if (override && override.length > 0) return
|
|
32362
|
+
if (override && override.length > 0) return import_node_path97.default.resolve(override);
|
|
31831
32363
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os9.default.homedir();
|
|
31832
|
-
return
|
|
32364
|
+
return import_node_path97.default.join(home, ".codex", "config.toml");
|
|
31833
32365
|
}
|
|
31834
32366
|
function agentsFilePath() {
|
|
31835
32367
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
31836
|
-
if (override && override.length > 0) return
|
|
31837
|
-
return
|
|
32368
|
+
if (override && override.length > 0) return import_node_path97.default.resolve(override);
|
|
32369
|
+
return import_node_path97.default.join(process.cwd(), "AGENTS.md");
|
|
31838
32370
|
}
|
|
31839
32371
|
function isTableHeader(line) {
|
|
31840
32372
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -31911,17 +32443,17 @@ function verifyPreserved(raw, spliced, original) {
|
|
|
31911
32443
|
}
|
|
31912
32444
|
const origServers = original.mcp_servers ?? {};
|
|
31913
32445
|
const nextServers = next.mcp_servers ?? {};
|
|
31914
|
-
if (!(0,
|
|
32446
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(nextServers.neat, CODEX_MCP_SERVER)) return false;
|
|
31915
32447
|
for (const name of Object.keys(origServers)) {
|
|
31916
32448
|
if (name === "neat") continue;
|
|
31917
|
-
if (!(0,
|
|
32449
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(nextServers[name], origServers[name])) return false;
|
|
31918
32450
|
}
|
|
31919
32451
|
for (const name of Object.keys(nextServers)) {
|
|
31920
32452
|
if (name !== "neat" && !(name in origServers)) return false;
|
|
31921
32453
|
}
|
|
31922
32454
|
for (const key of Object.keys(original)) {
|
|
31923
32455
|
if (key === "mcp_servers") continue;
|
|
31924
|
-
if (!(0,
|
|
32456
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(next[key], original[key])) return false;
|
|
31925
32457
|
}
|
|
31926
32458
|
for (const key of Object.keys(next)) {
|
|
31927
32459
|
if (key !== "mcp_servers" && !(key in original)) return false;
|
|
@@ -31968,7 +32500,7 @@ async function runCodex(opts) {
|
|
|
31968
32500
|
const agentsPath = agentsFilePath();
|
|
31969
32501
|
let configRaw = "";
|
|
31970
32502
|
try {
|
|
31971
|
-
configRaw = await
|
|
32503
|
+
configRaw = await import_node_fs59.promises.readFile(configPath, "utf8");
|
|
31972
32504
|
} catch (err) {
|
|
31973
32505
|
if (err.code !== "ENOENT") {
|
|
31974
32506
|
console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
|
|
@@ -31977,7 +32509,7 @@ async function runCodex(opts) {
|
|
|
31977
32509
|
}
|
|
31978
32510
|
let agentsRaw = "";
|
|
31979
32511
|
try {
|
|
31980
|
-
agentsRaw = await
|
|
32512
|
+
agentsRaw = await import_node_fs59.promises.readFile(agentsPath, "utf8");
|
|
31981
32513
|
} catch (err) {
|
|
31982
32514
|
if (err.code !== "ENOENT") {
|
|
31983
32515
|
console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
|
|
@@ -32017,15 +32549,15 @@ async function runCodex(opts) {
|
|
|
32017
32549
|
return { exitCode: 0 };
|
|
32018
32550
|
}
|
|
32019
32551
|
if (config.changed) {
|
|
32020
|
-
await
|
|
32021
|
-
await
|
|
32552
|
+
await import_node_fs59.promises.mkdir(import_node_path97.default.dirname(configPath), { recursive: true });
|
|
32553
|
+
await import_node_fs59.promises.writeFile(configPath, config.text, "utf8");
|
|
32022
32554
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
32023
32555
|
} else {
|
|
32024
32556
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
32025
32557
|
}
|
|
32026
32558
|
if (agents.changed) {
|
|
32027
|
-
await
|
|
32028
|
-
await
|
|
32559
|
+
await import_node_fs59.promises.mkdir(import_node_path97.default.dirname(agentsPath), { recursive: true });
|
|
32560
|
+
await import_node_fs59.promises.writeFile(agentsPath, agents.text, "utf8");
|
|
32029
32561
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
32030
32562
|
} else {
|
|
32031
32563
|
console.log(`neat codex: ${agentsPath} already has the graph-first block`);
|
|
@@ -32082,10 +32614,10 @@ async function runCodexCommand(args) {
|
|
|
32082
32614
|
|
|
32083
32615
|
// src/editors-cli.ts
|
|
32084
32616
|
init_cjs_shims();
|
|
32085
|
-
var
|
|
32617
|
+
var import_node_path98 = __toESM(require("path"), 1);
|
|
32086
32618
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
32087
|
-
var
|
|
32088
|
-
var
|
|
32619
|
+
var import_node_fs60 = require("fs");
|
|
32620
|
+
var import_node_util3 = require("util");
|
|
32089
32621
|
var jsonc = __toESM(require("jsonc-parser"), 1);
|
|
32090
32622
|
var NEAT_MCP_SERVER = {
|
|
32091
32623
|
command: "npx",
|
|
@@ -32108,17 +32640,17 @@ function homeDir() {
|
|
|
32108
32640
|
}
|
|
32109
32641
|
function xdgConfigDir() {
|
|
32110
32642
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
32111
|
-
return xdg && xdg.length > 0 ?
|
|
32643
|
+
return xdg && xdg.length > 0 ? import_node_path98.default.resolve(xdg) : import_node_path98.default.join(homeDir(), ".config");
|
|
32112
32644
|
}
|
|
32113
32645
|
function envOverride(name) {
|
|
32114
32646
|
const v = process.env[name];
|
|
32115
|
-
return v && v.length > 0 ?
|
|
32647
|
+
return v && v.length > 0 ? import_node_path98.default.resolve(v) : void 0;
|
|
32116
32648
|
}
|
|
32117
32649
|
var CURSOR_CLIENT = {
|
|
32118
32650
|
id: "cursor",
|
|
32119
32651
|
label: "Cursor",
|
|
32120
32652
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
32121
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
32653
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path98.default.join(homeDir(), ".cursor", "mcp.json"),
|
|
32122
32654
|
mcpContainerKey: "mcpServers",
|
|
32123
32655
|
format: "json",
|
|
32124
32656
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -32130,7 +32662,7 @@ var DEVIN_CLIENT = {
|
|
|
32130
32662
|
id: "devin",
|
|
32131
32663
|
label: "Devin Desktop (Cascade)",
|
|
32132
32664
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
32133
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
32665
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path98.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
32134
32666
|
mcpContainerKey: "mcpServers",
|
|
32135
32667
|
format: "json",
|
|
32136
32668
|
rulesFileName: ".windsurfrules"
|
|
@@ -32139,7 +32671,7 @@ var GEMINI_CLIENT = {
|
|
|
32139
32671
|
id: "gemini",
|
|
32140
32672
|
label: "Gemini CLI",
|
|
32141
32673
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
32142
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
32674
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path98.default.join(homeDir(), ".gemini", "settings.json"),
|
|
32143
32675
|
mcpContainerKey: "mcpServers",
|
|
32144
32676
|
format: "json",
|
|
32145
32677
|
rulesFileName: "GEMINI.md"
|
|
@@ -32148,7 +32680,7 @@ var QWEN_CLIENT = {
|
|
|
32148
32680
|
id: "qwen",
|
|
32149
32681
|
label: "Qwen Code",
|
|
32150
32682
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
32151
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
32683
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path98.default.join(homeDir(), ".qwen", "settings.json"),
|
|
32152
32684
|
mcpContainerKey: "mcpServers",
|
|
32153
32685
|
format: "json",
|
|
32154
32686
|
rulesFileName: "QWEN.md"
|
|
@@ -32157,7 +32689,7 @@ var AMAZONQ_CLIENT = {
|
|
|
32157
32689
|
id: "amazonq",
|
|
32158
32690
|
label: "Amazon Q Developer CLI",
|
|
32159
32691
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
32160
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
32692
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path98.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
32161
32693
|
mcpContainerKey: "mcpServers",
|
|
32162
32694
|
format: "json"
|
|
32163
32695
|
};
|
|
@@ -32165,7 +32697,7 @@ var ROOCODE_CLIENT = {
|
|
|
32165
32697
|
id: "roocode",
|
|
32166
32698
|
label: "Roo Code",
|
|
32167
32699
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
32168
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
32700
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path98.default.join(process.cwd(), ".roo", "mcp.json"),
|
|
32169
32701
|
mcpContainerKey: "mcpServers",
|
|
32170
32702
|
format: "json"
|
|
32171
32703
|
};
|
|
@@ -32178,9 +32710,9 @@ var ZED_CLIENT = {
|
|
|
32178
32710
|
if (override) return override;
|
|
32179
32711
|
if (process.platform === "win32") {
|
|
32180
32712
|
const appData = process.env.APPDATA;
|
|
32181
|
-
if (appData && appData.length > 0) return
|
|
32713
|
+
if (appData && appData.length > 0) return import_node_path98.default.join(appData, "Zed", "settings.json");
|
|
32182
32714
|
}
|
|
32183
|
-
return
|
|
32715
|
+
return import_node_path98.default.join(homeDir(), ".config", "zed", "settings.json");
|
|
32184
32716
|
},
|
|
32185
32717
|
mcpContainerKey: "context_servers",
|
|
32186
32718
|
format: "jsonc",
|
|
@@ -32190,7 +32722,7 @@ var OPENCODE_CLIENT = {
|
|
|
32190
32722
|
id: "opencode",
|
|
32191
32723
|
label: "OpenCode",
|
|
32192
32724
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
32193
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
32725
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path98.default.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
32194
32726
|
mcpContainerKey: "mcp",
|
|
32195
32727
|
format: "json",
|
|
32196
32728
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -32200,7 +32732,7 @@ var CRUSH_CLIENT = {
|
|
|
32200
32732
|
id: "crush",
|
|
32201
32733
|
label: "Crush",
|
|
32202
32734
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
32203
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
32735
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path98.default.join(xdgConfigDir(), "crush", "crush.json"),
|
|
32204
32736
|
mcpContainerKey: "mcp",
|
|
32205
32737
|
format: "json",
|
|
32206
32738
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -32219,7 +32751,7 @@ var CLIENTS = {
|
|
|
32219
32751
|
};
|
|
32220
32752
|
function mergeJsonMcp(existing, containerKey, serverEntry) {
|
|
32221
32753
|
const servers = existing[containerKey] ?? {};
|
|
32222
|
-
const already = (0,
|
|
32754
|
+
const already = (0, import_node_util3.isDeepStrictEqual)(servers.neat, serverEntry);
|
|
32223
32755
|
const merged = {
|
|
32224
32756
|
...existing,
|
|
32225
32757
|
[containerKey]: { ...servers, neat: serverEntry }
|
|
@@ -32230,7 +32762,7 @@ function mergeJsoncMcp(raw, containerKey, serverEntry) {
|
|
|
32230
32762
|
const base = raw.trim().length > 0 ? raw : "{}";
|
|
32231
32763
|
const parsed = jsonc.parse(base) ?? {};
|
|
32232
32764
|
const servers = parsed[containerKey] ?? {};
|
|
32233
|
-
if ((0,
|
|
32765
|
+
if ((0, import_node_util3.isDeepStrictEqual)(servers.neat, serverEntry)) {
|
|
32234
32766
|
return { text: raw, changed: false };
|
|
32235
32767
|
}
|
|
32236
32768
|
const edits = jsonc.modify(base, [containerKey, "neat"], serverEntry, {
|
|
@@ -32263,7 +32795,7 @@ async function planMcp(client, mcpPath) {
|
|
|
32263
32795
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
32264
32796
|
let raw = "";
|
|
32265
32797
|
try {
|
|
32266
|
-
raw = await
|
|
32798
|
+
raw = await import_node_fs60.promises.readFile(mcpPath, "utf8");
|
|
32267
32799
|
} catch (err) {
|
|
32268
32800
|
const e = err;
|
|
32269
32801
|
if (e.code === "ENOENT") {
|
|
@@ -32305,7 +32837,7 @@ async function runEditorInstall(client, opts) {
|
|
|
32305
32837
|
const mcpPath = client.mcpConfigPath();
|
|
32306
32838
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
32307
32839
|
const hasRules = typeof client.rulesFileName === "string";
|
|
32308
|
-
const rulesPath = hasRules ?
|
|
32840
|
+
const rulesPath = hasRules ? import_node_path98.default.join(opts.projectDir, client.rulesFileName) : "";
|
|
32309
32841
|
const mcp = await planMcp(client, mcpPath);
|
|
32310
32842
|
if (mcp === null) return { exitCode: 1 };
|
|
32311
32843
|
let existingRules = "";
|
|
@@ -32314,7 +32846,7 @@ async function runEditorInstall(client, opts) {
|
|
|
32314
32846
|
let block = "";
|
|
32315
32847
|
if (hasRules) {
|
|
32316
32848
|
try {
|
|
32317
|
-
existingRules = await
|
|
32849
|
+
existingRules = await import_node_fs60.promises.readFile(rulesPath, "utf8");
|
|
32318
32850
|
} catch (err) {
|
|
32319
32851
|
if (err.code !== "ENOENT") {
|
|
32320
32852
|
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
@@ -32348,11 +32880,11 @@ async function runEditorInstall(client, opts) {
|
|
|
32348
32880
|
);
|
|
32349
32881
|
return { exitCode: 0 };
|
|
32350
32882
|
}
|
|
32351
|
-
await
|
|
32352
|
-
await
|
|
32883
|
+
await import_node_fs60.promises.mkdir(import_node_path98.default.dirname(mcpPath), { recursive: true });
|
|
32884
|
+
await import_node_fs60.promises.writeFile(mcpPath, mcp.text, "utf8");
|
|
32353
32885
|
if (hasRules) {
|
|
32354
|
-
await
|
|
32355
|
-
await
|
|
32886
|
+
await import_node_fs60.promises.mkdir(import_node_path98.default.dirname(rulesPath), { recursive: true });
|
|
32887
|
+
await import_node_fs60.promises.writeFile(rulesPath, newRules, "utf8");
|
|
32356
32888
|
}
|
|
32357
32889
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
32358
32890
|
console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
|
|
@@ -32854,7 +33386,7 @@ function sleep(ms, signal) {
|
|
|
32854
33386
|
|
|
32855
33387
|
// src/cli-verbs.ts
|
|
32856
33388
|
init_cjs_shims();
|
|
32857
|
-
var
|
|
33389
|
+
var import_node_path99 = __toESM(require("path"), 1);
|
|
32858
33390
|
async function resolveProjectEntry(opts) {
|
|
32859
33391
|
const entries = await listProjects();
|
|
32860
33392
|
if (opts.project) {
|
|
@@ -32864,7 +33396,7 @@ async function resolveProjectEntry(opts) {
|
|
|
32864
33396
|
const cwd = opts.cwd ?? process.cwd();
|
|
32865
33397
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
32866
33398
|
for (const entry2 of entries) {
|
|
32867
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
33399
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path99.default.sep}`)) {
|
|
32868
33400
|
return entry2;
|
|
32869
33401
|
}
|
|
32870
33402
|
}
|
|
@@ -33039,6 +33571,7 @@ async function runSync(opts) {
|
|
|
33039
33571
|
|
|
33040
33572
|
// src/cli.ts
|
|
33041
33573
|
var import_types112 = require("@neat.is/types");
|
|
33574
|
+
init_auth();
|
|
33042
33575
|
function usage5() {
|
|
33043
33576
|
const neat = commandPrefix();
|
|
33044
33577
|
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
@@ -33441,7 +33974,7 @@ async function buildPatchSections(services, project) {
|
|
|
33441
33974
|
}
|
|
33442
33975
|
async function runInit(opts) {
|
|
33443
33976
|
const written = [];
|
|
33444
|
-
const stat = await
|
|
33977
|
+
const stat = await import_node_fs61.promises.stat(opts.scanPath).catch(() => null);
|
|
33445
33978
|
if (!stat || !stat.isDirectory()) {
|
|
33446
33979
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
33447
33980
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -33450,13 +33983,13 @@ async function runInit(opts) {
|
|
|
33450
33983
|
printDiscoveryReport(opts, services);
|
|
33451
33984
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
33452
33985
|
const patch = renderPatch(sections);
|
|
33453
|
-
const patchPath =
|
|
33986
|
+
const patchPath = import_node_path100.default.join(opts.scanPath, "neat.patch");
|
|
33454
33987
|
if (opts.dryRun) {
|
|
33455
|
-
await
|
|
33988
|
+
await import_node_fs61.promises.writeFile(patchPath, patch, "utf8");
|
|
33456
33989
|
written.push(patchPath);
|
|
33457
33990
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
33458
|
-
const gitignorePath =
|
|
33459
|
-
const gitignoreExists = await
|
|
33991
|
+
const gitignorePath = import_node_path100.default.join(opts.scanPath, ".gitignore");
|
|
33992
|
+
const gitignoreExists = await import_node_fs61.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
33460
33993
|
const verb = gitignoreExists ? "append" : "create";
|
|
33461
33994
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
33462
33995
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -33467,9 +34000,9 @@ async function runInit(opts) {
|
|
|
33467
34000
|
const graph = getGraph(graphKey);
|
|
33468
34001
|
const projectPaths = pathsForProject(
|
|
33469
34002
|
graphKey,
|
|
33470
|
-
|
|
34003
|
+
import_node_path100.default.join(opts.scanPath, "neat-out")
|
|
33471
34004
|
);
|
|
33472
|
-
const errorsPath =
|
|
34005
|
+
const errorsPath = import_node_path100.default.join(import_node_path100.default.dirname(opts.outPath), import_node_path100.default.basename(projectPaths.errorsPath));
|
|
33473
34006
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
33474
34007
|
await saveGraphToDisk(graph, opts.outPath);
|
|
33475
34008
|
written.push(opts.outPath);
|
|
@@ -33548,7 +34081,7 @@ async function runInit(opts) {
|
|
|
33548
34081
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
33549
34082
|
}
|
|
33550
34083
|
} else {
|
|
33551
|
-
await
|
|
34084
|
+
await import_node_fs61.promises.writeFile(patchPath, patch, "utf8");
|
|
33552
34085
|
written.push(patchPath);
|
|
33553
34086
|
}
|
|
33554
34087
|
}
|
|
@@ -33589,9 +34122,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
33589
34122
|
};
|
|
33590
34123
|
function claudeConfigPath() {
|
|
33591
34124
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
33592
|
-
if (override && override.length > 0) return
|
|
34125
|
+
if (override && override.length > 0) return import_node_path100.default.resolve(override);
|
|
33593
34126
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
33594
|
-
return
|
|
34127
|
+
return import_node_path100.default.join(home, ".claude.json");
|
|
33595
34128
|
}
|
|
33596
34129
|
async function runSkill(opts) {
|
|
33597
34130
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -33603,7 +34136,7 @@ async function runSkill(opts) {
|
|
|
33603
34136
|
const target = claudeConfigPath();
|
|
33604
34137
|
let existing = {};
|
|
33605
34138
|
try {
|
|
33606
|
-
existing = JSON.parse(await
|
|
34139
|
+
existing = JSON.parse(await import_node_fs61.promises.readFile(target, "utf8"));
|
|
33607
34140
|
} catch (err) {
|
|
33608
34141
|
if (err.code !== "ENOENT") {
|
|
33609
34142
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -33615,8 +34148,8 @@ async function runSkill(opts) {
|
|
|
33615
34148
|
...existing,
|
|
33616
34149
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
33617
34150
|
};
|
|
33618
|
-
await
|
|
33619
|
-
await
|
|
34151
|
+
await import_node_fs61.promises.mkdir(import_node_path100.default.dirname(target), { recursive: true });
|
|
34152
|
+
await import_node_fs61.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
33620
34153
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
33621
34154
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
33622
34155
|
console.log("");
|
|
@@ -33642,7 +34175,7 @@ async function runSkill(opts) {
|
|
|
33642
34175
|
async function main() {
|
|
33643
34176
|
const argv = process.argv.slice(2);
|
|
33644
34177
|
const cmd0 = argv[0];
|
|
33645
|
-
if (
|
|
34178
|
+
if (argv.some((a) => a === "-h" || a === "--help")) {
|
|
33646
34179
|
usage5();
|
|
33647
34180
|
process.exit(0);
|
|
33648
34181
|
}
|
|
@@ -33737,12 +34270,12 @@ async function main() {
|
|
|
33737
34270
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
33738
34271
|
process.exit(2);
|
|
33739
34272
|
}
|
|
33740
|
-
const scanPath =
|
|
34273
|
+
const scanPath = import_node_path100.default.resolve(target);
|
|
33741
34274
|
const projectExplicit = parsed.project !== null;
|
|
33742
|
-
const projectName = projectExplicit ? project :
|
|
34275
|
+
const projectName = projectExplicit ? project : import_node_path100.default.basename(scanPath);
|
|
33743
34276
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
33744
|
-
const fallback = pathsForProject(projectKey,
|
|
33745
|
-
const outPath =
|
|
34277
|
+
const fallback = pathsForProject(projectKey, import_node_path100.default.join(scanPath, "neat-out")).snapshotPath;
|
|
34278
|
+
const outPath = import_node_path100.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
33746
34279
|
const result = await runInit({
|
|
33747
34280
|
scanPath,
|
|
33748
34281
|
outPath,
|
|
@@ -33763,21 +34296,21 @@ async function main() {
|
|
|
33763
34296
|
usage5();
|
|
33764
34297
|
process.exit(2);
|
|
33765
34298
|
}
|
|
33766
|
-
const scanPath =
|
|
33767
|
-
const stat = await
|
|
34299
|
+
const scanPath = import_node_path100.default.resolve(target);
|
|
34300
|
+
const stat = await import_node_fs61.promises.stat(scanPath).catch(() => null);
|
|
33768
34301
|
if (!stat || !stat.isDirectory()) {
|
|
33769
34302
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
33770
34303
|
process.exit(2);
|
|
33771
34304
|
}
|
|
33772
|
-
const projectPaths = pathsForProject(project,
|
|
33773
|
-
const outPath =
|
|
33774
|
-
const errorsPath =
|
|
33775
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
34305
|
+
const projectPaths = pathsForProject(project, import_node_path100.default.join(scanPath, "neat-out"));
|
|
34306
|
+
const outPath = import_node_path100.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
34307
|
+
const errorsPath = import_node_path100.default.resolve(
|
|
34308
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path100.default.join(import_node_path100.default.dirname(outPath), import_node_path100.default.basename(projectPaths.errorsPath))
|
|
33776
34309
|
);
|
|
33777
|
-
const staleEventsPath =
|
|
33778
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
34310
|
+
const staleEventsPath = import_node_path100.default.resolve(
|
|
34311
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path100.default.join(import_node_path100.default.dirname(outPath), import_node_path100.default.basename(projectPaths.staleEventsPath))
|
|
33779
34312
|
);
|
|
33780
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
34313
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path100.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
33781
34314
|
const handle = await startWatch(getGraph(project), {
|
|
33782
34315
|
scanPath,
|
|
33783
34316
|
outPath,
|
|
@@ -33786,7 +34319,7 @@ async function main() {
|
|
|
33786
34319
|
project,
|
|
33787
34320
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
33788
34321
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
33789
|
-
neatHome: process.env.NEAT_HOME ?
|
|
34322
|
+
neatHome: process.env.NEAT_HOME ? import_node_path100.default.resolve(process.env.NEAT_HOME) : import_node_path100.default.join(import_node_os11.default.homedir(), ".neat"),
|
|
33790
34323
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
33791
34324
|
host: process.env.HOST ?? "0.0.0.0",
|
|
33792
34325
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -33968,11 +34501,11 @@ async function main() {
|
|
|
33968
34501
|
process.exit(1);
|
|
33969
34502
|
}
|
|
33970
34503
|
async function tryOrchestrator(cmd, parsed) {
|
|
33971
|
-
const scanPath =
|
|
33972
|
-
const stat = await
|
|
34504
|
+
const scanPath = import_node_path100.default.resolve(cmd);
|
|
34505
|
+
const stat = await import_node_fs61.promises.stat(scanPath).catch(() => null);
|
|
33973
34506
|
if (!stat || !stat.isDirectory()) return null;
|
|
33974
34507
|
const projectExplicit = parsed.project !== null;
|
|
33975
|
-
const projectName = projectExplicit ? parsed.project :
|
|
34508
|
+
const projectName = projectExplicit ? parsed.project : import_node_path100.default.basename(scanPath);
|
|
33976
34509
|
const result = await runOrchestrator({
|
|
33977
34510
|
scanPath,
|
|
33978
34511
|
project: projectName,
|
|
@@ -33987,7 +34520,18 @@ async function tryOrchestrator(cmd, parsed) {
|
|
|
33987
34520
|
async function runWelcomeFlow(parsed) {
|
|
33988
34521
|
return runWelcome({
|
|
33989
34522
|
login: (loginArgv) => runLoginCommand(loginArgv),
|
|
33990
|
-
|
|
34523
|
+
// What the person chose at the door reaches the orchestrator as the flags they
|
|
34524
|
+
// stand for: the name as `--project`, the instrument answer as `--no-instrument`
|
|
34525
|
+
// or as `yes` (which keeps the orchestrator's own prompt quiet, since the door
|
|
34526
|
+
// already asked).
|
|
34527
|
+
orchestrator: async (cwd, opts) => await tryOrchestrator(cwd, {
|
|
34528
|
+
...parsed,
|
|
34529
|
+
...opts?.project !== void 0 ? { project: opts.project } : {},
|
|
34530
|
+
...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
|
|
34531
|
+
...opts?.yes !== void 0 ? { yes: opts.yes } : {}
|
|
34532
|
+
}) ?? 0,
|
|
34533
|
+
// An explicit flag is an answer; don't ask again.
|
|
34534
|
+
instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
|
|
33991
34535
|
});
|
|
33992
34536
|
}
|
|
33993
34537
|
var QUERY_VERBS = /* @__PURE__ */ new Set([
|
|
@@ -34020,6 +34564,13 @@ var ProjectResolutionError = class extends Error {
|
|
|
34020
34564
|
}
|
|
34021
34565
|
exitCode;
|
|
34022
34566
|
};
|
|
34567
|
+
function isLoopbackEndpoint(endpoint2) {
|
|
34568
|
+
try {
|
|
34569
|
+
return isLoopbackHost2(new URL(endpoint2).hostname.replace(/^\[|\]$/g, ""));
|
|
34570
|
+
} catch {
|
|
34571
|
+
return false;
|
|
34572
|
+
}
|
|
34573
|
+
}
|
|
34023
34574
|
async function resolveProjectForVerb(client, parsed) {
|
|
34024
34575
|
const explicit = resolveProjectFlag(parsed);
|
|
34025
34576
|
if (explicit) return explicit;
|
|
@@ -34250,6 +34801,9 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
34250
34801
|
console.error(`neat ${cmd}: ${detail.trim()}`);
|
|
34251
34802
|
} else if (err instanceof TransportError) {
|
|
34252
34803
|
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (endpoint=${target.endpoint})`);
|
|
34804
|
+
if (isLoopbackEndpoint(target.endpoint)) {
|
|
34805
|
+
console.error(`neat ${cmd}: start one with \`neat watch .\`, or \`neat\` to extract and start in one step.`);
|
|
34806
|
+
}
|
|
34253
34807
|
} else {
|
|
34254
34808
|
console.error(`neat ${cmd}: ${err.message}`);
|
|
34255
34809
|
}
|
|
@@ -34306,6 +34860,7 @@ if (/[\\/](?:cli\.(?:cjs|js)|cli|neat|neat\.is)$/.test(entry)) {
|
|
|
34306
34860
|
QUERY_VERBS,
|
|
34307
34861
|
UnknownProfileError,
|
|
34308
34862
|
commandPrefix,
|
|
34863
|
+
isLoopbackEndpoint,
|
|
34309
34864
|
isNpxInvocation,
|
|
34310
34865
|
main,
|
|
34311
34866
|
parseArgs,
|