@neat.is/core 0.10.1-dev.20260928 → 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-HVBD4ULO.js → chunk-AWIK6T3G.js} +3 -3
- package/dist/{chunk-CNOJ4T6I.js → chunk-E3YFFIHE.js} +2 -2
- package/dist/{chunk-GDGUY4T6.js → chunk-M7CGATVC.js} +3 -2
- package/dist/cli.cjs +547 -255
- 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 +468 -179
- package/dist/cli.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-Y5K4WGWO.js → otel-grpc-CU73Y5WT.js} +3 -3
- package/dist/server.js +3 -3
- package/package.json +3 -3
- /package/dist/{chunk-ERE47MCR.js.map → chunk-5SJHA5ZH.js.map} +0 -0
- /package/dist/{chunk-HVBD4ULO.js.map → chunk-AWIK6T3G.js.map} +0 -0
- /package/dist/{chunk-CNOJ4T6I.js.map → chunk-E3YFFIHE.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
|
}
|
|
@@ -20453,10 +20454,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
20453
20454
|
// src/connectors/supabase/map.ts
|
|
20454
20455
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
20455
20456
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
20456
|
-
function targetFromRestPath(
|
|
20457
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
20457
|
+
function targetFromRestPath(path100) {
|
|
20458
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path100);
|
|
20458
20459
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
20459
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
20460
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path100);
|
|
20460
20461
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
20461
20462
|
return null;
|
|
20462
20463
|
}
|
|
@@ -21060,9 +21061,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
21060
21061
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
21061
21062
|
if (secondSep === -1) return null;
|
|
21062
21063
|
const method = rest.slice(0, secondSep);
|
|
21063
|
-
const
|
|
21064
|
-
if (!resourceName || !method || !
|
|
21065
|
-
return { resourceName, method, path:
|
|
21064
|
+
const path100 = rest.slice(secondSep + 1);
|
|
21065
|
+
if (!resourceName || !method || !path100) return null;
|
|
21066
|
+
return { resourceName, method, path: path100 };
|
|
21066
21067
|
}
|
|
21067
21068
|
function resourceNameFor(type, labels) {
|
|
21068
21069
|
if (!labels) return null;
|
|
@@ -21100,14 +21101,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
21100
21101
|
if (!req) return null;
|
|
21101
21102
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
21102
21103
|
const method = req.requestMethod.toUpperCase();
|
|
21103
|
-
const
|
|
21104
|
-
if (
|
|
21104
|
+
const path100 = pathFromRequestUrl(req.requestUrl);
|
|
21105
|
+
if (path100 === null) return null;
|
|
21105
21106
|
const timestamp = entry2.timestamp;
|
|
21106
21107
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
21107
21108
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
21108
21109
|
return {
|
|
21109
21110
|
targetKind: resourceType,
|
|
21110
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
21111
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path100 }),
|
|
21111
21112
|
callCount: 1,
|
|
21112
21113
|
errorCount: isError ? 1 : 0,
|
|
21113
21114
|
lastObservedIso: timestamp
|
|
@@ -21132,9 +21133,9 @@ var import_types73 = require("@neat.is/types");
|
|
|
21132
21133
|
function normalizeName(name) {
|
|
21133
21134
|
return name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
21134
21135
|
}
|
|
21135
|
-
function inferServiceName(graph, resourceName, method,
|
|
21136
|
+
function inferServiceName(graph, resourceName, method, path100) {
|
|
21136
21137
|
const wantedMethod = method.toUpperCase();
|
|
21137
|
-
const wantedPath = normalizePathTemplate(
|
|
21138
|
+
const wantedPath = normalizePathTemplate(path100);
|
|
21138
21139
|
const services = [];
|
|
21139
21140
|
const owners = /* @__PURE__ */ new Set();
|
|
21140
21141
|
graph.forEachNode((_id, attrs) => {
|
|
@@ -21351,7 +21352,7 @@ function mapEventToSignal(event) {
|
|
|
21351
21352
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
21352
21353
|
const statusCode = metadata?.statusCode;
|
|
21353
21354
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
21354
|
-
const
|
|
21355
|
+
const path100 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
21355
21356
|
return {
|
|
21356
21357
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
21357
21358
|
targetName: scriptName,
|
|
@@ -21359,7 +21360,7 @@ function mapEventToSignal(event) {
|
|
|
21359
21360
|
errorCount: isError ? 1 : 0,
|
|
21360
21361
|
lastObservedIso: observedAt.toISOString(),
|
|
21361
21362
|
method,
|
|
21362
|
-
...
|
|
21363
|
+
...path100 ? { path: path100 } : {},
|
|
21363
21364
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
21364
21365
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
21365
21366
|
};
|
|
@@ -21405,8 +21406,8 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
21405
21406
|
});
|
|
21406
21407
|
return found;
|
|
21407
21408
|
}
|
|
21408
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
21409
|
-
const normalizedPath = normalizePathTemplate(
|
|
21409
|
+
function findMatchingRouteNode(graph, serviceName, method, path100) {
|
|
21410
|
+
const normalizedPath = normalizePathTemplate(path100);
|
|
21410
21411
|
let found = null;
|
|
21411
21412
|
graph.forEachNode((id, attrs) => {
|
|
21412
21413
|
if (found) return;
|
|
@@ -21423,10 +21424,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
21423
21424
|
return (signal) => {
|
|
21424
21425
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
21425
21426
|
const scriptName = signal.targetName;
|
|
21426
|
-
const { method, path:
|
|
21427
|
+
const { method, path: path100 } = signal;
|
|
21427
21428
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
21428
|
-
if (!method || !
|
|
21429
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
21429
|
+
if (!method || !path100) return wholeFileId;
|
|
21430
|
+
return findMatchingRouteNode(graph, serviceName, method, path100) ?? wholeFileId;
|
|
21430
21431
|
};
|
|
21431
21432
|
const mapping = config.workers?.[scriptName];
|
|
21432
21433
|
if (mapping) {
|
|
@@ -21778,9 +21779,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
21778
21779
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
21779
21780
|
if (secondSep === -1) return null;
|
|
21780
21781
|
const method = rest.slice(0, secondSep);
|
|
21781
|
-
const
|
|
21782
|
-
if (!serviceName || !method || !
|
|
21783
|
-
return { serviceName, method, path:
|
|
21782
|
+
const path100 = rest.slice(secondSep + 1);
|
|
21783
|
+
if (!serviceName || !method || !path100) return null;
|
|
21784
|
+
return { serviceName, method, path: path100 };
|
|
21784
21785
|
}
|
|
21785
21786
|
|
|
21786
21787
|
// src/connectors/cloud-run/map.ts
|
|
@@ -21809,14 +21810,14 @@ function mapLogEntryToSignal2(entry2) {
|
|
|
21809
21810
|
if (!req) return null;
|
|
21810
21811
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
21811
21812
|
const method = req.requestMethod.toUpperCase();
|
|
21812
|
-
const
|
|
21813
|
-
if (
|
|
21813
|
+
const path100 = pathFromRequestUrl2(req.requestUrl);
|
|
21814
|
+
if (path100 === null) return null;
|
|
21814
21815
|
const timestamp = entry2.timestamp;
|
|
21815
21816
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
21816
21817
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
21817
21818
|
return {
|
|
21818
21819
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
21819
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
21820
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path100 }),
|
|
21820
21821
|
callCount: 1,
|
|
21821
21822
|
errorCount: isError ? 1 : 0,
|
|
21822
21823
|
lastObservedIso: timestamp
|
|
@@ -21855,14 +21856,14 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
21855
21856
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
21856
21857
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
21857
21858
|
if (!identity) return null;
|
|
21858
|
-
const { serviceName: gcpServiceName, method, path:
|
|
21859
|
-
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);
|
|
21860
21861
|
if (mappedService) {
|
|
21861
21862
|
const routeNodeId = findMatchingRouteNode2(
|
|
21862
21863
|
graph,
|
|
21863
21864
|
mappedService,
|
|
21864
21865
|
method,
|
|
21865
|
-
normalizePathTemplate(
|
|
21866
|
+
normalizePathTemplate(path100)
|
|
21866
21867
|
);
|
|
21867
21868
|
if (routeNodeId) {
|
|
21868
21869
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types84.EdgeType.CALLS };
|
|
@@ -21998,9 +21999,9 @@ function parseGcpLbTargetName(targetName) {
|
|
|
21998
21999
|
const secondSep = rest.indexOf(FIELD_SEP3);
|
|
21999
22000
|
if (secondSep === -1) return null;
|
|
22000
22001
|
const method = rest.slice(0, secondSep);
|
|
22001
|
-
const
|
|
22002
|
-
if (!backendServiceName || !method || !
|
|
22003
|
-
return { backendServiceName, method, path:
|
|
22002
|
+
const path100 = rest.slice(secondSep + 1);
|
|
22003
|
+
if (!backendServiceName || !method || !path100) return null;
|
|
22004
|
+
return { backendServiceName, method, path: path100 };
|
|
22004
22005
|
}
|
|
22005
22006
|
|
|
22006
22007
|
// src/connectors/gcp-lb/map.ts
|
|
@@ -22029,14 +22030,14 @@ function mapLogEntryToSignal3(entry2) {
|
|
|
22029
22030
|
if (!req) return null;
|
|
22030
22031
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
22031
22032
|
const method = req.requestMethod.toUpperCase();
|
|
22032
|
-
const
|
|
22033
|
-
if (
|
|
22033
|
+
const path100 = pathFromRequestUrl3(req.requestUrl);
|
|
22034
|
+
if (path100 === null) return null;
|
|
22034
22035
|
const timestamp = entry2.timestamp;
|
|
22035
22036
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
22036
22037
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD5;
|
|
22037
22038
|
return {
|
|
22038
22039
|
targetKind: GCP_LB_TARGET_KIND,
|
|
22039
|
-
targetName: packGcpLbTargetName({ backendServiceName, method, path:
|
|
22040
|
+
targetName: packGcpLbTargetName({ backendServiceName, method, path: path100 }),
|
|
22040
22041
|
callCount: 1,
|
|
22041
22042
|
errorCount: isError ? 1 : 0,
|
|
22042
22043
|
lastObservedIso: timestamp
|
|
@@ -22075,14 +22076,14 @@ function createGcpLbResolveTarget(graph, config) {
|
|
|
22075
22076
|
if (signal.targetKind !== GCP_LB_TARGET_KIND) return null;
|
|
22076
22077
|
const identity = parseGcpLbTargetName(signal.targetName);
|
|
22077
22078
|
if (!identity) return null;
|
|
22078
|
-
const { backendServiceName, method, path:
|
|
22079
|
-
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);
|
|
22080
22081
|
if (mappedService) {
|
|
22081
22082
|
const routeNodeId = findMatchingRouteNode3(
|
|
22082
22083
|
graph,
|
|
22083
22084
|
mappedService,
|
|
22084
22085
|
method,
|
|
22085
|
-
normalizePathTemplate(
|
|
22086
|
+
normalizePathTemplate(path100)
|
|
22086
22087
|
);
|
|
22087
22088
|
if (routeNodeId) {
|
|
22088
22089
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types88.EdgeType.CALLS };
|
|
@@ -24474,9 +24475,9 @@ function makeK8sFetchImpl(transport) {
|
|
|
24474
24475
|
req.end();
|
|
24475
24476
|
}));
|
|
24476
24477
|
}
|
|
24477
|
-
async function listResource(transport, namespace,
|
|
24478
|
+
async function listResource(transport, namespace, path100, opts = {}) {
|
|
24478
24479
|
const base = opts.apiUrl ?? transport.server;
|
|
24479
|
-
const url = `${base.replace(/\/$/, "")}${
|
|
24480
|
+
const url = `${base.replace(/\/$/, "")}${path100}`;
|
|
24480
24481
|
const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
|
|
24481
24482
|
const res = await junctionFetch(
|
|
24482
24483
|
url,
|
|
@@ -24486,7 +24487,7 @@ async function listResource(transport, namespace, path99, opts = {}) {
|
|
|
24486
24487
|
{ provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
|
|
24487
24488
|
);
|
|
24488
24489
|
if (!res.ok) {
|
|
24489
|
-
throw new Error(`kubernetes ${
|
|
24490
|
+
throw new Error(`kubernetes ${path100} failed: ${res.status} ${res.statusText}`);
|
|
24490
24491
|
}
|
|
24491
24492
|
const json = await res.json();
|
|
24492
24493
|
return Array.isArray(json.items) ? json.items : [];
|
|
@@ -29377,7 +29378,8 @@ async function runOrchestrator(opts) {
|
|
|
29377
29378
|
} catch (err) {
|
|
29378
29379
|
if (!(err instanceof ProjectNameCollisionError)) throw err;
|
|
29379
29380
|
console.error(`neat: ${err.message}`);
|
|
29380
|
-
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.`);
|
|
29381
29383
|
result.exitCode = 1;
|
|
29382
29384
|
return result;
|
|
29383
29385
|
}
|
|
@@ -29956,12 +29958,12 @@ async function runConnectorCommand(rawArgs, deps = {}) {
|
|
|
29956
29958
|
|
|
29957
29959
|
// src/hosted-connect-cli.ts
|
|
29958
29960
|
init_cjs_shims();
|
|
29959
|
-
var
|
|
29961
|
+
var import_node_child_process6 = require("child_process");
|
|
29960
29962
|
|
|
29961
29963
|
// src/login-sso.ts
|
|
29962
29964
|
init_cjs_shims();
|
|
29963
29965
|
var import_node_http2 = require("http");
|
|
29964
|
-
var
|
|
29966
|
+
var import_node_child_process5 = require("child_process");
|
|
29965
29967
|
var import_node_crypto7 = require("crypto");
|
|
29966
29968
|
|
|
29967
29969
|
// src/profiles.ts
|
|
@@ -30174,6 +30176,198 @@ async function clearActiveProfile(home = neatHome3()) {
|
|
|
30174
30176
|
});
|
|
30175
30177
|
}
|
|
30176
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
|
+
|
|
30177
30371
|
// src/login-sso.ts
|
|
30178
30372
|
var DEFAULT_CP_URL = "https://api.neat.is";
|
|
30179
30373
|
var DEFAULT_WEB_URL = "https://app.neat.is";
|
|
@@ -30199,6 +30393,7 @@ function pickProject(projects, want) {
|
|
|
30199
30393
|
return {
|
|
30200
30394
|
error: {
|
|
30201
30395
|
code: 1,
|
|
30396
|
+
kind: "no-project",
|
|
30202
30397
|
message: `no running project to connect to \u2014 create + provision one in the console first${listed}`
|
|
30203
30398
|
}
|
|
30204
30399
|
};
|
|
@@ -30249,14 +30444,14 @@ async function exchangeCredential(cpUrl, accessToken, opts, deps) {
|
|
|
30249
30444
|
if (!cred.endpoint || !cred.authToken) {
|
|
30250
30445
|
return { error: { code: 1, message: "the credential response was missing endpoint/authToken" } };
|
|
30251
30446
|
}
|
|
30252
|
-
return { project, cred };
|
|
30447
|
+
return { project, cred, me };
|
|
30253
30448
|
}
|
|
30254
30449
|
function defaultOpenBrowser(url) {
|
|
30255
30450
|
const platform = process.platform;
|
|
30256
30451
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
30257
30452
|
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
30258
30453
|
try {
|
|
30259
|
-
const child = (0,
|
|
30454
|
+
const child = (0, import_node_child_process5.spawn)(cmd, args, { detached: true, stdio: "ignore" });
|
|
30260
30455
|
child.on("error", () => {
|
|
30261
30456
|
});
|
|
30262
30457
|
child.unref();
|
|
@@ -30331,12 +30526,50 @@ async function runSsoLogin(opts, deps) {
|
|
|
30331
30526
|
}
|
|
30332
30527
|
accessToken = lb.token;
|
|
30333
30528
|
}
|
|
30334
|
-
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
|
+
}
|
|
30335
30557
|
if ("error" in ex) {
|
|
30336
30558
|
err(`neat login: ${ex.error.message}`);
|
|
30337
30559
|
return ex.error.code;
|
|
30338
30560
|
}
|
|
30339
|
-
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
|
+
}
|
|
30340
30573
|
await upsertProfile(
|
|
30341
30574
|
{ name: opts.name, endpoint: cred.endpoint, authToken: cred.authToken },
|
|
30342
30575
|
{ makeActive: true, ...deps.home ? { home: deps.home } : {} }
|
|
@@ -30364,10 +30597,20 @@ async function runSsoLogin(opts, deps) {
|
|
|
30364
30597
|
out(` OTEL_EXPORTER_OTLP_ENDPOINT=${cred.ingestEndpoint}`);
|
|
30365
30598
|
out(` OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer ${cred.otelToken}`);
|
|
30366
30599
|
}
|
|
30367
|
-
|
|
30368
|
-
|
|
30369
|
-
|
|
30370
|
-
|
|
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
|
+
});
|
|
30371
30614
|
out("");
|
|
30372
30615
|
out("Run `neat logout` to switch back to your local daemon.");
|
|
30373
30616
|
}
|
|
@@ -30380,7 +30623,7 @@ function defaultOpenBrowser2(url) {
|
|
|
30380
30623
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
30381
30624
|
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
30382
30625
|
try {
|
|
30383
|
-
const child = (0,
|
|
30626
|
+
const child = (0, import_node_child_process6.spawn)(cmd, args, { detached: true, stdio: "ignore" });
|
|
30384
30627
|
child.on("error", () => {
|
|
30385
30628
|
});
|
|
30386
30629
|
child.unref();
|
|
@@ -30475,8 +30718,8 @@ async function runConnectCommand(rawArgs, deps = {}) {
|
|
|
30475
30718
|
|
|
30476
30719
|
// src/doctor-cli.ts
|
|
30477
30720
|
init_cjs_shims();
|
|
30478
|
-
var
|
|
30479
|
-
var
|
|
30721
|
+
var import_node_fs55 = require("fs");
|
|
30722
|
+
var import_node_path93 = __toESM(require("path"), 1);
|
|
30480
30723
|
|
|
30481
30724
|
// src/cli-client.ts
|
|
30482
30725
|
init_cjs_shims();
|
|
@@ -30505,10 +30748,10 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
30505
30748
|
const root = baseUrl.replace(/\/$/, "");
|
|
30506
30749
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
30507
30750
|
return {
|
|
30508
|
-
async get(
|
|
30751
|
+
async get(path100) {
|
|
30509
30752
|
let res;
|
|
30510
30753
|
try {
|
|
30511
|
-
res = await fetchImpl(`${root}${
|
|
30754
|
+
res = await fetchImpl(`${root}${path100}`, {
|
|
30512
30755
|
headers: { ...authHeader }
|
|
30513
30756
|
});
|
|
30514
30757
|
} catch (err) {
|
|
@@ -30520,16 +30763,16 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
30520
30763
|
const body = await res.text().catch(() => "");
|
|
30521
30764
|
throw new HttpError(
|
|
30522
30765
|
res.status,
|
|
30523
|
-
`${res.status} ${res.statusText} on GET ${
|
|
30766
|
+
`${res.status} ${res.statusText} on GET ${path100}: ${body}`,
|
|
30524
30767
|
body
|
|
30525
30768
|
);
|
|
30526
30769
|
}
|
|
30527
30770
|
return await res.json();
|
|
30528
30771
|
},
|
|
30529
|
-
async post(
|
|
30772
|
+
async post(path100, body) {
|
|
30530
30773
|
let res;
|
|
30531
30774
|
try {
|
|
30532
|
-
res = await fetchImpl(`${root}${
|
|
30775
|
+
res = await fetchImpl(`${root}${path100}`, {
|
|
30533
30776
|
method: "POST",
|
|
30534
30777
|
headers: { "content-type": "application/json", ...authHeader },
|
|
30535
30778
|
body: JSON.stringify(body)
|
|
@@ -30543,7 +30786,7 @@ function createHttpClient(baseUrl, bearerToken, fetchImpl = fetch) {
|
|
|
30543
30786
|
const text = await res.text().catch(() => "");
|
|
30544
30787
|
throw new HttpError(
|
|
30545
30788
|
res.status,
|
|
30546
|
-
`${res.status} ${res.statusText} on POST ${
|
|
30789
|
+
`${res.status} ${res.statusText} on POST ${path100}: ${text}`,
|
|
30547
30790
|
text
|
|
30548
30791
|
);
|
|
30549
30792
|
}
|
|
@@ -30557,12 +30800,12 @@ function projectPath(project, suffix) {
|
|
|
30557
30800
|
}
|
|
30558
30801
|
async function runRootCause(client, input) {
|
|
30559
30802
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
30560
|
-
const
|
|
30803
|
+
const path100 = projectPath(
|
|
30561
30804
|
input.project,
|
|
30562
30805
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
30563
30806
|
);
|
|
30564
30807
|
try {
|
|
30565
|
-
const result = await client.get(
|
|
30808
|
+
const result = await client.get(path100);
|
|
30566
30809
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
30567
30810
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
30568
30811
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -30588,12 +30831,12 @@ async function runRootCause(client, input) {
|
|
|
30588
30831
|
}
|
|
30589
30832
|
async function runBlastRadius(client, input) {
|
|
30590
30833
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
30591
|
-
const
|
|
30834
|
+
const path100 = projectPath(
|
|
30592
30835
|
input.project,
|
|
30593
30836
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
30594
30837
|
);
|
|
30595
30838
|
try {
|
|
30596
|
-
const result = await client.get(
|
|
30839
|
+
const result = await client.get(path100);
|
|
30597
30840
|
if (result.totalAffected === 0) {
|
|
30598
30841
|
return {
|
|
30599
30842
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -30627,12 +30870,12 @@ function formatBlastEntry(n) {
|
|
|
30627
30870
|
}
|
|
30628
30871
|
async function runDependencies(client, input) {
|
|
30629
30872
|
const depth = input.depth ?? 3;
|
|
30630
|
-
const
|
|
30873
|
+
const path100 = projectPath(
|
|
30631
30874
|
input.project,
|
|
30632
30875
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
30633
30876
|
);
|
|
30634
30877
|
try {
|
|
30635
|
-
const result = await client.get(
|
|
30878
|
+
const result = await client.get(path100);
|
|
30636
30879
|
if (result.total === 0) {
|
|
30637
30880
|
return {
|
|
30638
30881
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -30724,9 +30967,9 @@ function formatDuration(ms) {
|
|
|
30724
30967
|
return `${Math.round(h / 24)}d`;
|
|
30725
30968
|
}
|
|
30726
30969
|
async function runIncidents(client, input) {
|
|
30727
|
-
const
|
|
30970
|
+
const path100 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
30728
30971
|
try {
|
|
30729
|
-
const body = await client.get(
|
|
30972
|
+
const body = await client.get(path100);
|
|
30730
30973
|
const events = body.events;
|
|
30731
30974
|
if (events.length === 0) {
|
|
30732
30975
|
return {
|
|
@@ -31090,7 +31333,7 @@ function checkNode(nodeVersion) {
|
|
|
31090
31333
|
}
|
|
31091
31334
|
async function neatOutExists(cwd) {
|
|
31092
31335
|
try {
|
|
31093
|
-
const st = await
|
|
31336
|
+
const st = await import_node_fs55.promises.stat(import_node_path93.default.join(cwd, "neat-out"));
|
|
31094
31337
|
return st.isDirectory();
|
|
31095
31338
|
} catch {
|
|
31096
31339
|
return false;
|
|
@@ -31210,9 +31453,6 @@ async function runDoctorCommand(argv, deps = {}) {
|
|
|
31210
31453
|
// src/login-cli.ts
|
|
31211
31454
|
init_cjs_shims();
|
|
31212
31455
|
var import_promises4 = __toESM(require("readline/promises"), 1);
|
|
31213
|
-
var PROBE_ATTEMPT_TIMEOUT_MS = 3e4;
|
|
31214
|
-
var PROBE_TOTAL_BUDGET_MS = 12e4;
|
|
31215
|
-
var PROBE_RETRY_PAUSE_MS = 2e3;
|
|
31216
31456
|
var DEFAULT_PROFILE_NAME = "hosted";
|
|
31217
31457
|
function readFlagValue(argv, i) {
|
|
31218
31458
|
const arg = argv[i];
|
|
@@ -31277,44 +31517,6 @@ function printLoginHelp(out) {
|
|
|
31277
31517
|
out(' the profile (default "hosted"); the paste token can also come from NEAT_LOGIN_TOKEN.');
|
|
31278
31518
|
out(" Exit 0 on success, 1 rejected/needs-action, 2 misuse, 3 unreachable.");
|
|
31279
31519
|
}
|
|
31280
|
-
function isTimeoutError(err) {
|
|
31281
|
-
const name = err?.name;
|
|
31282
|
-
return name === "TimeoutError" || name === "AbortError";
|
|
31283
|
-
}
|
|
31284
|
-
async function probeOnce(fetchImpl, root, token, timeoutMs) {
|
|
31285
|
-
let res;
|
|
31286
|
-
try {
|
|
31287
|
-
res = await fetchImpl(`${root}/health`, {
|
|
31288
|
-
headers: { authorization: `Bearer ${token}` },
|
|
31289
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
31290
|
-
});
|
|
31291
|
-
} catch (err) {
|
|
31292
|
-
if (isTimeoutError(err)) return { kind: "timeout" };
|
|
31293
|
-
return { kind: "unreachable", detail: err.message };
|
|
31294
|
-
}
|
|
31295
|
-
if (res.status === 401 || res.status === 403) return { kind: "unauthorized", status: res.status };
|
|
31296
|
-
if (!res.ok) return { kind: "not-neat", status: res.status };
|
|
31297
|
-
const contentType = res.headers.get("content-type") ?? "";
|
|
31298
|
-
if (!contentType.includes("json")) return { kind: "not-neat", status: res.status };
|
|
31299
|
-
return { kind: "ok" };
|
|
31300
|
-
}
|
|
31301
|
-
async function probeDaemon(fetchImpl, endpoint2, token, hooks) {
|
|
31302
|
-
const root = endpoint2.replace(/\/$/, "");
|
|
31303
|
-
const deadline = hooks.now() + PROBE_TOTAL_BUDGET_MS;
|
|
31304
|
-
let warned = false;
|
|
31305
|
-
for (; ; ) {
|
|
31306
|
-
const result = await probeOnce(fetchImpl, root, token, PROBE_ATTEMPT_TIMEOUT_MS);
|
|
31307
|
-
if (result.kind !== "timeout") return result;
|
|
31308
|
-
if (!warned) {
|
|
31309
|
-
hooks.onWaiting();
|
|
31310
|
-
warned = true;
|
|
31311
|
-
}
|
|
31312
|
-
if (hooks.now() >= deadline) {
|
|
31313
|
-
return { kind: "unreachable", detail: `no response after ${Math.round(PROBE_TOTAL_BUDGET_MS / 1e3)}s` };
|
|
31314
|
-
}
|
|
31315
|
-
await hooks.sleep(PROBE_RETRY_PAUSE_MS);
|
|
31316
|
-
}
|
|
31317
|
-
}
|
|
31318
31520
|
async function runLoginCommand(argv, deps = {}) {
|
|
31319
31521
|
const out = deps.out ?? ((line) => console.log(line));
|
|
31320
31522
|
const err = deps.err ?? ((line) => console.error(line));
|
|
@@ -31347,8 +31549,13 @@ async function runLoginCommand(argv, deps = {}) {
|
|
|
31347
31549
|
fetchImpl,
|
|
31348
31550
|
out,
|
|
31349
31551
|
err,
|
|
31552
|
+
readLine,
|
|
31553
|
+
sleep: sleep2,
|
|
31554
|
+
now,
|
|
31350
31555
|
...deps.openBrowser ? { openBrowser: deps.openBrowser } : {},
|
|
31351
|
-
...deps.home ? { home: deps.home } : {}
|
|
31556
|
+
...deps.home ? { home: deps.home } : {},
|
|
31557
|
+
...deps.signal ? { signal: deps.signal } : {},
|
|
31558
|
+
...deps.connect ? { connect: deps.connect } : {}
|
|
31352
31559
|
}
|
|
31353
31560
|
);
|
|
31354
31561
|
}
|
|
@@ -31505,8 +31712,8 @@ async function defaultReadSecret(prompt) {
|
|
|
31505
31712
|
|
|
31506
31713
|
// src/welcome.ts
|
|
31507
31714
|
init_cjs_shims();
|
|
31508
|
-
var
|
|
31509
|
-
var
|
|
31715
|
+
var import_node_fs56 = require("fs");
|
|
31716
|
+
var import_node_path94 = __toESM(require("path"), 1);
|
|
31510
31717
|
var import_promises5 = __toESM(require("readline/promises"), 1);
|
|
31511
31718
|
var WORDMARK = [
|
|
31512
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",
|
|
@@ -31565,15 +31772,17 @@ async function runFrontDoor(deps, out) {
|
|
|
31565
31772
|
const login = deps.login ?? (() => Promise.resolve(0));
|
|
31566
31773
|
const orchestrator = deps.orchestrator ?? (() => Promise.resolve(0));
|
|
31567
31774
|
const cwd = deps.cwd ?? process.cwd();
|
|
31775
|
+
const flagGiven = deps.instrumentFlagGiven ?? false;
|
|
31776
|
+
const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
|
|
31568
31777
|
printHeader(out);
|
|
31569
31778
|
out("Welcome to NEAT. Let's get you a graph of this system.");
|
|
31570
31779
|
out("");
|
|
31571
31780
|
const keyReader = deps.readKey !== void 0 ? { read: deps.readKey, done: () => {
|
|
31572
31781
|
} } : createKeyReader();
|
|
31573
31782
|
const picked = keyReader !== null ? await selectOption(out, keyReader, deps.moveCursorUp ?? defaultMoveCursorUp) : await selectByNumber(out, readLine);
|
|
31574
|
-
if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd);
|
|
31783
|
+
if (picked === void 0) return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
|
|
31575
31784
|
if (picked === 0) return login(["--browser"]);
|
|
31576
|
-
return runSelfHosted(out, readLine, orchestrator, cwd);
|
|
31785
|
+
return runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, flagGiven);
|
|
31577
31786
|
}
|
|
31578
31787
|
var MENU_OPTIONS = [
|
|
31579
31788
|
"Log me into Hosted Neat",
|
|
@@ -31676,7 +31885,7 @@ function decodeKey(buf) {
|
|
|
31676
31885
|
function defaultMoveCursorUp(rows) {
|
|
31677
31886
|
if (rows > 0) process.stdout.write(`\x1B[${rows}A`);
|
|
31678
31887
|
}
|
|
31679
|
-
async function runSelfHosted(out, readLine, orchestrator, cwd) {
|
|
31888
|
+
async function runSelfHosted(out, readLine, orchestrator, cwd, readRegistry2, instrumentFlagGiven) {
|
|
31680
31889
|
const wantsPrompt = (await readLine("Print a copy-paste setup prompt for your coding agent? [Y/n]: "))?.trim().toLowerCase();
|
|
31681
31890
|
if (wantsPrompt !== "n" && wantsPrompt !== "no") {
|
|
31682
31891
|
out("");
|
|
@@ -31687,9 +31896,69 @@ async function runSelfHosted(out, readLine, orchestrator, cwd) {
|
|
|
31687
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");
|
|
31688
31897
|
out("");
|
|
31689
31898
|
}
|
|
31899
|
+
const project = await resolveProjectName(out, readLine, cwd, readRegistry2);
|
|
31900
|
+
const declined = instrumentFlagGiven ? void 0 : await askToInstrument(out, readLine);
|
|
31690
31901
|
out("Building your local graph now\u2026");
|
|
31691
31902
|
out("");
|
|
31692
|
-
|
|
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);
|
|
31693
31962
|
}
|
|
31694
31963
|
async function shouldShowWelcome(deps = {}) {
|
|
31695
31964
|
const stdinTTY = deps.stdinIsTTY ?? Boolean(process.stdin.isTTY);
|
|
@@ -31699,9 +31968,9 @@ async function shouldShowWelcome(deps = {}) {
|
|
|
31699
31968
|
const readRegistry2 = deps.readRegistry ?? (() => readRegistry());
|
|
31700
31969
|
const hasSnapshot = deps.hasSnapshot ?? defaultHasSnapshot;
|
|
31701
31970
|
try {
|
|
31702
|
-
const here =
|
|
31971
|
+
const here = import_node_path94.default.resolve(cwd);
|
|
31703
31972
|
const { projects } = await readRegistry2();
|
|
31704
|
-
if (projects.some((p) =>
|
|
31973
|
+
if (projects.some((p) => import_node_path94.default.resolve(p.path) === here)) return false;
|
|
31705
31974
|
if (await hasSnapshot(here)) return false;
|
|
31706
31975
|
return true;
|
|
31707
31976
|
} catch {
|
|
@@ -31710,7 +31979,7 @@ async function shouldShowWelcome(deps = {}) {
|
|
|
31710
31979
|
}
|
|
31711
31980
|
async function defaultHasSnapshot(cwd) {
|
|
31712
31981
|
try {
|
|
31713
|
-
const entries = await
|
|
31982
|
+
const entries = await import_node_fs56.promises.readdir(import_node_path94.default.join(cwd, "neat-out"));
|
|
31714
31983
|
return entries.some((e) => e.endsWith(".json"));
|
|
31715
31984
|
} catch {
|
|
31716
31985
|
return false;
|
|
@@ -31728,27 +31997,27 @@ async function defaultReadLine2(prompt) {
|
|
|
31728
31997
|
|
|
31729
31998
|
// src/hooks-cli.ts
|
|
31730
31999
|
init_cjs_shims();
|
|
31731
|
-
var
|
|
32000
|
+
var import_node_path95 = __toESM(require("path"), 1);
|
|
31732
32001
|
var import_node_os8 = __toESM(require("os"), 1);
|
|
31733
|
-
var
|
|
32002
|
+
var import_node_fs57 = require("fs");
|
|
31734
32003
|
var import_node_url5 = require("url");
|
|
31735
32004
|
var HOOK_FILENAME = "neat-search-nudge.mjs";
|
|
31736
32005
|
var GUIDE_FILENAME = "GRAPH_FIRST.md";
|
|
31737
32006
|
var GUIDE_INSTALL_NAME = "neat-graph-first.md";
|
|
31738
32007
|
var HOOK_MATCHER = "Grep|Glob|Bash";
|
|
31739
32008
|
function moduleDir() {
|
|
31740
|
-
return typeof __dirname !== "undefined" ? __dirname :
|
|
32009
|
+
return typeof __dirname !== "undefined" ? __dirname : import_node_path95.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
|
|
31741
32010
|
}
|
|
31742
32011
|
async function readSkillAsset(rel) {
|
|
31743
32012
|
const here = moduleDir();
|
|
31744
32013
|
const candidates = [
|
|
31745
|
-
|
|
31746
|
-
|
|
31747
|
-
|
|
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)
|
|
31748
32017
|
];
|
|
31749
32018
|
for (const candidate of candidates) {
|
|
31750
32019
|
try {
|
|
31751
|
-
return await
|
|
32020
|
+
return await import_node_fs57.promises.readFile(candidate, "utf8");
|
|
31752
32021
|
} catch {
|
|
31753
32022
|
}
|
|
31754
32023
|
}
|
|
@@ -31758,20 +32027,20 @@ async function readSkillAsset(rel) {
|
|
|
31758
32027
|
}
|
|
31759
32028
|
function neatHome4() {
|
|
31760
32029
|
const override = process.env.NEAT_HOME;
|
|
31761
|
-
if (override && override.length > 0) return
|
|
31762
|
-
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");
|
|
31763
32032
|
}
|
|
31764
32033
|
function claudeSettingsPath() {
|
|
31765
32034
|
const override = process.env.NEAT_CLAUDE_SETTINGS;
|
|
31766
|
-
if (override && override.length > 0) return
|
|
32035
|
+
if (override && override.length > 0) return import_node_path95.default.resolve(override);
|
|
31767
32036
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os8.default.homedir();
|
|
31768
|
-
return
|
|
32037
|
+
return import_node_path95.default.join(home, ".claude", "settings.json");
|
|
31769
32038
|
}
|
|
31770
32039
|
function installedHookPath() {
|
|
31771
|
-
return
|
|
32040
|
+
return import_node_path95.default.join(neatHome4(), "hooks", HOOK_FILENAME);
|
|
31772
32041
|
}
|
|
31773
32042
|
function gateFlagPath() {
|
|
31774
|
-
return
|
|
32043
|
+
return import_node_path95.default.join(neatHome4(), "hooks", "gate-enabled");
|
|
31775
32044
|
}
|
|
31776
32045
|
function isNeatSearchEntry(entry2) {
|
|
31777
32046
|
return (entry2.hooks ?? []).some(
|
|
@@ -31804,14 +32073,14 @@ async function runHooks(opts) {
|
|
|
31804
32073
|
const hookScript = await readSkillAsset(`hooks/${HOOK_FILENAME}`);
|
|
31805
32074
|
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
31806
32075
|
const scriptPath = installedHookPath();
|
|
31807
|
-
await
|
|
31808
|
-
await
|
|
31809
|
-
const guidePath =
|
|
31810
|
-
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");
|
|
31811
32080
|
const settingsFile = claudeSettingsPath();
|
|
31812
32081
|
let settings = {};
|
|
31813
32082
|
try {
|
|
31814
|
-
settings = JSON.parse(await
|
|
32083
|
+
settings = JSON.parse(await import_node_fs57.promises.readFile(settingsFile, "utf8"));
|
|
31815
32084
|
} catch (err) {
|
|
31816
32085
|
if (err.code !== "ENOENT") {
|
|
31817
32086
|
console.error(
|
|
@@ -31833,14 +32102,14 @@ async function runHooks(opts) {
|
|
|
31833
32102
|
...settings,
|
|
31834
32103
|
hooks: { ...hooks, PreToolUse: preToolUse }
|
|
31835
32104
|
};
|
|
31836
|
-
await
|
|
31837
|
-
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");
|
|
31838
32107
|
const flag = gateFlagPath();
|
|
31839
32108
|
if (opts.gate) {
|
|
31840
|
-
await
|
|
31841
|
-
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");
|
|
31842
32111
|
} else {
|
|
31843
|
-
await
|
|
32112
|
+
await import_node_fs57.promises.rm(flag, { force: true });
|
|
31844
32113
|
}
|
|
31845
32114
|
const mode = opts.gate ? "GATE (deny search until you ask the graph)" : "nudge (search still runs)";
|
|
31846
32115
|
console.log(`neat hooks: installed the search hook in ${opts.gate ? "gate" : "nudge"} mode`);
|
|
@@ -31930,8 +32199,8 @@ async function runHooksCommand(args) {
|
|
|
31930
32199
|
|
|
31931
32200
|
// src/claude-cli.ts
|
|
31932
32201
|
init_cjs_shims();
|
|
31933
|
-
var
|
|
31934
|
-
var
|
|
32202
|
+
var import_node_path96 = __toESM(require("path"), 1);
|
|
32203
|
+
var import_node_fs58 = require("fs");
|
|
31935
32204
|
var NEAT_SECTION_HEADING = "## neat";
|
|
31936
32205
|
var NEAT_DIRECTIVE_BODY = `This project has NEAT wired in: a live, fused semantic graph of the system \u2014
|
|
31937
32206
|
code and runtime behaviour (OpenTelemetry) in one model, every fact tagged with
|
|
@@ -31965,8 +32234,8 @@ ${NEAT_DIRECTIVE_BODY}
|
|
|
31965
32234
|
}
|
|
31966
32235
|
function claudeMdPath() {
|
|
31967
32236
|
const override = process.env.NEAT_CLAUDE_MD;
|
|
31968
|
-
if (override && override.length > 0) return
|
|
31969
|
-
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");
|
|
31970
32239
|
}
|
|
31971
32240
|
function splitAroundSection(raw) {
|
|
31972
32241
|
const lines = raw.split("\n");
|
|
@@ -31994,7 +32263,7 @@ function compose(before, after) {
|
|
|
31994
32263
|
}
|
|
31995
32264
|
async function readIfExists2(file) {
|
|
31996
32265
|
try {
|
|
31997
|
-
return await
|
|
32266
|
+
return await import_node_fs58.promises.readFile(file, "utf8");
|
|
31998
32267
|
} catch (err) {
|
|
31999
32268
|
if (err.code === "ENOENT") return null;
|
|
32000
32269
|
throw err;
|
|
@@ -32005,8 +32274,8 @@ async function runInstall() {
|
|
|
32005
32274
|
const raw = await readIfExists2(file) ?? "";
|
|
32006
32275
|
const { before, after, found } = splitAroundSection(raw);
|
|
32007
32276
|
const next = compose(before, after);
|
|
32008
|
-
await
|
|
32009
|
-
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");
|
|
32010
32279
|
const verb = raw.length === 0 ? "created" : found ? "refreshed" : "added";
|
|
32011
32280
|
console.log(`neat claude: ${verb} the \`${NEAT_SECTION_HEADING}\` section in ${file}`);
|
|
32012
32281
|
console.log("Your agent will now reach for `neat ask` before Read/Grep/Bash. Restart the");
|
|
@@ -32027,7 +32296,7 @@ async function runUninstall() {
|
|
|
32027
32296
|
}
|
|
32028
32297
|
const remaining = [before, after].filter((s) => s.length > 0).join("\n\n");
|
|
32029
32298
|
const next = remaining.length > 0 ? remaining.replace(/\n*$/, "") + "\n" : "";
|
|
32030
|
-
await
|
|
32299
|
+
await import_node_fs58.promises.writeFile(file, next, "utf8");
|
|
32031
32300
|
console.log(`neat claude: removed the \`${NEAT_SECTION_HEADING}\` section from ${file}.`);
|
|
32032
32301
|
return { exitCode: 0 };
|
|
32033
32302
|
}
|
|
@@ -32070,10 +32339,10 @@ async function runClaudeCommand(args) {
|
|
|
32070
32339
|
|
|
32071
32340
|
// src/codex-cli.ts
|
|
32072
32341
|
init_cjs_shims();
|
|
32073
|
-
var
|
|
32342
|
+
var import_node_path97 = __toESM(require("path"), 1);
|
|
32074
32343
|
var import_node_os9 = __toESM(require("os"), 1);
|
|
32075
|
-
var
|
|
32076
|
-
var
|
|
32344
|
+
var import_node_fs59 = require("fs");
|
|
32345
|
+
var import_node_util2 = require("util");
|
|
32077
32346
|
var import_smol_toml6 = require("smol-toml");
|
|
32078
32347
|
var CODEX_MCP_SERVER = {
|
|
32079
32348
|
command: "npx",
|
|
@@ -32090,14 +32359,14 @@ var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
|
32090
32359
|
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
32091
32360
|
function codexConfigPath() {
|
|
32092
32361
|
const override = process.env.NEAT_CODEX_CONFIG;
|
|
32093
|
-
if (override && override.length > 0) return
|
|
32362
|
+
if (override && override.length > 0) return import_node_path97.default.resolve(override);
|
|
32094
32363
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os9.default.homedir();
|
|
32095
|
-
return
|
|
32364
|
+
return import_node_path97.default.join(home, ".codex", "config.toml");
|
|
32096
32365
|
}
|
|
32097
32366
|
function agentsFilePath() {
|
|
32098
32367
|
const override = process.env.NEAT_CODEX_AGENTS;
|
|
32099
|
-
if (override && override.length > 0) return
|
|
32100
|
-
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");
|
|
32101
32370
|
}
|
|
32102
32371
|
function isTableHeader(line) {
|
|
32103
32372
|
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
@@ -32174,17 +32443,17 @@ function verifyPreserved(raw, spliced, original) {
|
|
|
32174
32443
|
}
|
|
32175
32444
|
const origServers = original.mcp_servers ?? {};
|
|
32176
32445
|
const nextServers = next.mcp_servers ?? {};
|
|
32177
|
-
if (!(0,
|
|
32446
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(nextServers.neat, CODEX_MCP_SERVER)) return false;
|
|
32178
32447
|
for (const name of Object.keys(origServers)) {
|
|
32179
32448
|
if (name === "neat") continue;
|
|
32180
|
-
if (!(0,
|
|
32449
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(nextServers[name], origServers[name])) return false;
|
|
32181
32450
|
}
|
|
32182
32451
|
for (const name of Object.keys(nextServers)) {
|
|
32183
32452
|
if (name !== "neat" && !(name in origServers)) return false;
|
|
32184
32453
|
}
|
|
32185
32454
|
for (const key of Object.keys(original)) {
|
|
32186
32455
|
if (key === "mcp_servers") continue;
|
|
32187
|
-
if (!(0,
|
|
32456
|
+
if (!(0, import_node_util2.isDeepStrictEqual)(next[key], original[key])) return false;
|
|
32188
32457
|
}
|
|
32189
32458
|
for (const key of Object.keys(next)) {
|
|
32190
32459
|
if (key !== "mcp_servers" && !(key in original)) return false;
|
|
@@ -32231,7 +32500,7 @@ async function runCodex(opts) {
|
|
|
32231
32500
|
const agentsPath = agentsFilePath();
|
|
32232
32501
|
let configRaw = "";
|
|
32233
32502
|
try {
|
|
32234
|
-
configRaw = await
|
|
32503
|
+
configRaw = await import_node_fs59.promises.readFile(configPath, "utf8");
|
|
32235
32504
|
} catch (err) {
|
|
32236
32505
|
if (err.code !== "ENOENT") {
|
|
32237
32506
|
console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
|
|
@@ -32240,7 +32509,7 @@ async function runCodex(opts) {
|
|
|
32240
32509
|
}
|
|
32241
32510
|
let agentsRaw = "";
|
|
32242
32511
|
try {
|
|
32243
|
-
agentsRaw = await
|
|
32512
|
+
agentsRaw = await import_node_fs59.promises.readFile(agentsPath, "utf8");
|
|
32244
32513
|
} catch (err) {
|
|
32245
32514
|
if (err.code !== "ENOENT") {
|
|
32246
32515
|
console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
|
|
@@ -32280,15 +32549,15 @@ async function runCodex(opts) {
|
|
|
32280
32549
|
return { exitCode: 0 };
|
|
32281
32550
|
}
|
|
32282
32551
|
if (config.changed) {
|
|
32283
|
-
await
|
|
32284
|
-
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");
|
|
32285
32554
|
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
32286
32555
|
} else {
|
|
32287
32556
|
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
32288
32557
|
}
|
|
32289
32558
|
if (agents.changed) {
|
|
32290
|
-
await
|
|
32291
|
-
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");
|
|
32292
32561
|
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
32293
32562
|
} else {
|
|
32294
32563
|
console.log(`neat codex: ${agentsPath} already has the graph-first block`);
|
|
@@ -32345,10 +32614,10 @@ async function runCodexCommand(args) {
|
|
|
32345
32614
|
|
|
32346
32615
|
// src/editors-cli.ts
|
|
32347
32616
|
init_cjs_shims();
|
|
32348
|
-
var
|
|
32617
|
+
var import_node_path98 = __toESM(require("path"), 1);
|
|
32349
32618
|
var import_node_os10 = __toESM(require("os"), 1);
|
|
32350
|
-
var
|
|
32351
|
-
var
|
|
32619
|
+
var import_node_fs60 = require("fs");
|
|
32620
|
+
var import_node_util3 = require("util");
|
|
32352
32621
|
var jsonc = __toESM(require("jsonc-parser"), 1);
|
|
32353
32622
|
var NEAT_MCP_SERVER = {
|
|
32354
32623
|
command: "npx",
|
|
@@ -32371,17 +32640,17 @@ function homeDir() {
|
|
|
32371
32640
|
}
|
|
32372
32641
|
function xdgConfigDir() {
|
|
32373
32642
|
const xdg = process.env.XDG_CONFIG_HOME;
|
|
32374
|
-
return xdg && xdg.length > 0 ?
|
|
32643
|
+
return xdg && xdg.length > 0 ? import_node_path98.default.resolve(xdg) : import_node_path98.default.join(homeDir(), ".config");
|
|
32375
32644
|
}
|
|
32376
32645
|
function envOverride(name) {
|
|
32377
32646
|
const v = process.env[name];
|
|
32378
|
-
return v && v.length > 0 ?
|
|
32647
|
+
return v && v.length > 0 ? import_node_path98.default.resolve(v) : void 0;
|
|
32379
32648
|
}
|
|
32380
32649
|
var CURSOR_CLIENT = {
|
|
32381
32650
|
id: "cursor",
|
|
32382
32651
|
label: "Cursor",
|
|
32383
32652
|
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
32384
|
-
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ??
|
|
32653
|
+
mcpConfigPath: () => envOverride("NEAT_CURSOR_CONFIG") ?? import_node_path98.default.join(homeDir(), ".cursor", "mcp.json"),
|
|
32385
32654
|
mcpContainerKey: "mcpServers",
|
|
32386
32655
|
format: "json",
|
|
32387
32656
|
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
@@ -32393,7 +32662,7 @@ var DEVIN_CLIENT = {
|
|
|
32393
32662
|
id: "devin",
|
|
32394
32663
|
label: "Devin Desktop (Cascade)",
|
|
32395
32664
|
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
32396
|
-
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ??
|
|
32665
|
+
mcpConfigPath: () => envOverride("NEAT_DEVIN_CONFIG") ?? import_node_path98.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json"),
|
|
32397
32666
|
mcpContainerKey: "mcpServers",
|
|
32398
32667
|
format: "json",
|
|
32399
32668
|
rulesFileName: ".windsurfrules"
|
|
@@ -32402,7 +32671,7 @@ var GEMINI_CLIENT = {
|
|
|
32402
32671
|
id: "gemini",
|
|
32403
32672
|
label: "Gemini CLI",
|
|
32404
32673
|
docsUrl: "https://github.com/google-gemini/gemini-cli/blob/main/docs/tools/mcp-server.md",
|
|
32405
|
-
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ??
|
|
32674
|
+
mcpConfigPath: () => envOverride("NEAT_GEMINI_CONFIG") ?? import_node_path98.default.join(homeDir(), ".gemini", "settings.json"),
|
|
32406
32675
|
mcpContainerKey: "mcpServers",
|
|
32407
32676
|
format: "json",
|
|
32408
32677
|
rulesFileName: "GEMINI.md"
|
|
@@ -32411,7 +32680,7 @@ var QWEN_CLIENT = {
|
|
|
32411
32680
|
id: "qwen",
|
|
32412
32681
|
label: "Qwen Code",
|
|
32413
32682
|
docsUrl: "https://qwenlm.github.io/qwen-code-docs/en/users/features/mcp/",
|
|
32414
|
-
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ??
|
|
32683
|
+
mcpConfigPath: () => envOverride("NEAT_QWEN_CONFIG") ?? import_node_path98.default.join(homeDir(), ".qwen", "settings.json"),
|
|
32415
32684
|
mcpContainerKey: "mcpServers",
|
|
32416
32685
|
format: "json",
|
|
32417
32686
|
rulesFileName: "QWEN.md"
|
|
@@ -32420,7 +32689,7 @@ var AMAZONQ_CLIENT = {
|
|
|
32420
32689
|
id: "amazonq",
|
|
32421
32690
|
label: "Amazon Q Developer CLI",
|
|
32422
32691
|
docsUrl: "https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-mcp-configuration.html",
|
|
32423
|
-
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ??
|
|
32692
|
+
mcpConfigPath: () => envOverride("NEAT_AMAZONQ_CONFIG") ?? import_node_path98.default.join(homeDir(), ".aws", "amazonq", "mcp.json"),
|
|
32424
32693
|
mcpContainerKey: "mcpServers",
|
|
32425
32694
|
format: "json"
|
|
32426
32695
|
};
|
|
@@ -32428,7 +32697,7 @@ var ROOCODE_CLIENT = {
|
|
|
32428
32697
|
id: "roocode",
|
|
32429
32698
|
label: "Roo Code",
|
|
32430
32699
|
docsUrl: "https://roocodeinc.github.io/Roo-Code/features/mcp/using-mcp-in-roo",
|
|
32431
|
-
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ??
|
|
32700
|
+
mcpConfigPath: () => envOverride("NEAT_ROOCODE_CONFIG") ?? import_node_path98.default.join(process.cwd(), ".roo", "mcp.json"),
|
|
32432
32701
|
mcpContainerKey: "mcpServers",
|
|
32433
32702
|
format: "json"
|
|
32434
32703
|
};
|
|
@@ -32441,9 +32710,9 @@ var ZED_CLIENT = {
|
|
|
32441
32710
|
if (override) return override;
|
|
32442
32711
|
if (process.platform === "win32") {
|
|
32443
32712
|
const appData = process.env.APPDATA;
|
|
32444
|
-
if (appData && appData.length > 0) return
|
|
32713
|
+
if (appData && appData.length > 0) return import_node_path98.default.join(appData, "Zed", "settings.json");
|
|
32445
32714
|
}
|
|
32446
|
-
return
|
|
32715
|
+
return import_node_path98.default.join(homeDir(), ".config", "zed", "settings.json");
|
|
32447
32716
|
},
|
|
32448
32717
|
mcpContainerKey: "context_servers",
|
|
32449
32718
|
format: "jsonc",
|
|
@@ -32453,7 +32722,7 @@ var OPENCODE_CLIENT = {
|
|
|
32453
32722
|
id: "opencode",
|
|
32454
32723
|
label: "OpenCode",
|
|
32455
32724
|
docsUrl: "https://opencode.ai/docs/mcp-servers/",
|
|
32456
|
-
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ??
|
|
32725
|
+
mcpConfigPath: () => envOverride("NEAT_OPENCODE_CONFIG") ?? import_node_path98.default.join(xdgConfigDir(), "opencode", "opencode.json"),
|
|
32457
32726
|
mcpContainerKey: "mcp",
|
|
32458
32727
|
format: "json",
|
|
32459
32728
|
serverEntry: NEAT_OPENCODE_SERVER,
|
|
@@ -32463,7 +32732,7 @@ var CRUSH_CLIENT = {
|
|
|
32463
32732
|
id: "crush",
|
|
32464
32733
|
label: "Crush",
|
|
32465
32734
|
docsUrl: "https://charmbracelet-crush.mintlify.app/configuration/mcp",
|
|
32466
|
-
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ??
|
|
32735
|
+
mcpConfigPath: () => envOverride("NEAT_CRUSH_CONFIG") ?? import_node_path98.default.join(xdgConfigDir(), "crush", "crush.json"),
|
|
32467
32736
|
mcpContainerKey: "mcp",
|
|
32468
32737
|
format: "json",
|
|
32469
32738
|
serverEntry: NEAT_CRUSH_SERVER,
|
|
@@ -32482,7 +32751,7 @@ var CLIENTS = {
|
|
|
32482
32751
|
};
|
|
32483
32752
|
function mergeJsonMcp(existing, containerKey, serverEntry) {
|
|
32484
32753
|
const servers = existing[containerKey] ?? {};
|
|
32485
|
-
const already = (0,
|
|
32754
|
+
const already = (0, import_node_util3.isDeepStrictEqual)(servers.neat, serverEntry);
|
|
32486
32755
|
const merged = {
|
|
32487
32756
|
...existing,
|
|
32488
32757
|
[containerKey]: { ...servers, neat: serverEntry }
|
|
@@ -32493,7 +32762,7 @@ function mergeJsoncMcp(raw, containerKey, serverEntry) {
|
|
|
32493
32762
|
const base = raw.trim().length > 0 ? raw : "{}";
|
|
32494
32763
|
const parsed = jsonc.parse(base) ?? {};
|
|
32495
32764
|
const servers = parsed[containerKey] ?? {};
|
|
32496
|
-
if ((0,
|
|
32765
|
+
if ((0, import_node_util3.isDeepStrictEqual)(servers.neat, serverEntry)) {
|
|
32497
32766
|
return { text: raw, changed: false };
|
|
32498
32767
|
}
|
|
32499
32768
|
const edits = jsonc.modify(base, [containerKey, "neat"], serverEntry, {
|
|
@@ -32526,7 +32795,7 @@ async function planMcp(client, mcpPath) {
|
|
|
32526
32795
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
32527
32796
|
let raw = "";
|
|
32528
32797
|
try {
|
|
32529
|
-
raw = await
|
|
32798
|
+
raw = await import_node_fs60.promises.readFile(mcpPath, "utf8");
|
|
32530
32799
|
} catch (err) {
|
|
32531
32800
|
const e = err;
|
|
32532
32801
|
if (e.code === "ENOENT") {
|
|
@@ -32568,7 +32837,7 @@ async function runEditorInstall(client, opts) {
|
|
|
32568
32837
|
const mcpPath = client.mcpConfigPath();
|
|
32569
32838
|
const serverEntry = client.serverEntry ?? NEAT_MCP_SERVER;
|
|
32570
32839
|
const hasRules = typeof client.rulesFileName === "string";
|
|
32571
|
-
const rulesPath = hasRules ?
|
|
32840
|
+
const rulesPath = hasRules ? import_node_path98.default.join(opts.projectDir, client.rulesFileName) : "";
|
|
32572
32841
|
const mcp = await planMcp(client, mcpPath);
|
|
32573
32842
|
if (mcp === null) return { exitCode: 1 };
|
|
32574
32843
|
let existingRules = "";
|
|
@@ -32577,7 +32846,7 @@ async function runEditorInstall(client, opts) {
|
|
|
32577
32846
|
let block = "";
|
|
32578
32847
|
if (hasRules) {
|
|
32579
32848
|
try {
|
|
32580
|
-
existingRules = await
|
|
32849
|
+
existingRules = await import_node_fs60.promises.readFile(rulesPath, "utf8");
|
|
32581
32850
|
} catch (err) {
|
|
32582
32851
|
if (err.code !== "ENOENT") {
|
|
32583
32852
|
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
@@ -32611,11 +32880,11 @@ async function runEditorInstall(client, opts) {
|
|
|
32611
32880
|
);
|
|
32612
32881
|
return { exitCode: 0 };
|
|
32613
32882
|
}
|
|
32614
|
-
await
|
|
32615
|
-
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");
|
|
32616
32885
|
if (hasRules) {
|
|
32617
|
-
await
|
|
32618
|
-
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");
|
|
32619
32888
|
}
|
|
32620
32889
|
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
32621
32890
|
console.log(` MCP server: ${mcpPath} (${client.mcpContainerKey}.neat \u2192 npx -y @neat.is/mcp)`);
|
|
@@ -33117,7 +33386,7 @@ function sleep(ms, signal) {
|
|
|
33117
33386
|
|
|
33118
33387
|
// src/cli-verbs.ts
|
|
33119
33388
|
init_cjs_shims();
|
|
33120
|
-
var
|
|
33389
|
+
var import_node_path99 = __toESM(require("path"), 1);
|
|
33121
33390
|
async function resolveProjectEntry(opts) {
|
|
33122
33391
|
const entries = await listProjects();
|
|
33123
33392
|
if (opts.project) {
|
|
@@ -33127,7 +33396,7 @@ async function resolveProjectEntry(opts) {
|
|
|
33127
33396
|
const cwd = opts.cwd ?? process.cwd();
|
|
33128
33397
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
33129
33398
|
for (const entry2 of entries) {
|
|
33130
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
33399
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path99.default.sep}`)) {
|
|
33131
33400
|
return entry2;
|
|
33132
33401
|
}
|
|
33133
33402
|
}
|
|
@@ -33302,6 +33571,7 @@ async function runSync(opts) {
|
|
|
33302
33571
|
|
|
33303
33572
|
// src/cli.ts
|
|
33304
33573
|
var import_types112 = require("@neat.is/types");
|
|
33574
|
+
init_auth();
|
|
33305
33575
|
function usage5() {
|
|
33306
33576
|
const neat = commandPrefix();
|
|
33307
33577
|
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
@@ -33704,7 +33974,7 @@ async function buildPatchSections(services, project) {
|
|
|
33704
33974
|
}
|
|
33705
33975
|
async function runInit(opts) {
|
|
33706
33976
|
const written = [];
|
|
33707
|
-
const stat = await
|
|
33977
|
+
const stat = await import_node_fs61.promises.stat(opts.scanPath).catch(() => null);
|
|
33708
33978
|
if (!stat || !stat.isDirectory()) {
|
|
33709
33979
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
33710
33980
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -33713,13 +33983,13 @@ async function runInit(opts) {
|
|
|
33713
33983
|
printDiscoveryReport(opts, services);
|
|
33714
33984
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
33715
33985
|
const patch = renderPatch(sections);
|
|
33716
|
-
const patchPath =
|
|
33986
|
+
const patchPath = import_node_path100.default.join(opts.scanPath, "neat.patch");
|
|
33717
33987
|
if (opts.dryRun) {
|
|
33718
|
-
await
|
|
33988
|
+
await import_node_fs61.promises.writeFile(patchPath, patch, "utf8");
|
|
33719
33989
|
written.push(patchPath);
|
|
33720
33990
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
33721
|
-
const gitignorePath =
|
|
33722
|
-
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);
|
|
33723
33993
|
const verb = gitignoreExists ? "append" : "create";
|
|
33724
33994
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
33725
33995
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -33730,9 +34000,9 @@ async function runInit(opts) {
|
|
|
33730
34000
|
const graph = getGraph(graphKey);
|
|
33731
34001
|
const projectPaths = pathsForProject(
|
|
33732
34002
|
graphKey,
|
|
33733
|
-
|
|
34003
|
+
import_node_path100.default.join(opts.scanPath, "neat-out")
|
|
33734
34004
|
);
|
|
33735
|
-
const errorsPath =
|
|
34005
|
+
const errorsPath = import_node_path100.default.join(import_node_path100.default.dirname(opts.outPath), import_node_path100.default.basename(projectPaths.errorsPath));
|
|
33736
34006
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
33737
34007
|
await saveGraphToDisk(graph, opts.outPath);
|
|
33738
34008
|
written.push(opts.outPath);
|
|
@@ -33811,7 +34081,7 @@ async function runInit(opts) {
|
|
|
33811
34081
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
33812
34082
|
}
|
|
33813
34083
|
} else {
|
|
33814
|
-
await
|
|
34084
|
+
await import_node_fs61.promises.writeFile(patchPath, patch, "utf8");
|
|
33815
34085
|
written.push(patchPath);
|
|
33816
34086
|
}
|
|
33817
34087
|
}
|
|
@@ -33852,9 +34122,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
33852
34122
|
};
|
|
33853
34123
|
function claudeConfigPath() {
|
|
33854
34124
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
33855
|
-
if (override && override.length > 0) return
|
|
34125
|
+
if (override && override.length > 0) return import_node_path100.default.resolve(override);
|
|
33856
34126
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
33857
|
-
return
|
|
34127
|
+
return import_node_path100.default.join(home, ".claude.json");
|
|
33858
34128
|
}
|
|
33859
34129
|
async function runSkill(opts) {
|
|
33860
34130
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -33866,7 +34136,7 @@ async function runSkill(opts) {
|
|
|
33866
34136
|
const target = claudeConfigPath();
|
|
33867
34137
|
let existing = {};
|
|
33868
34138
|
try {
|
|
33869
|
-
existing = JSON.parse(await
|
|
34139
|
+
existing = JSON.parse(await import_node_fs61.promises.readFile(target, "utf8"));
|
|
33870
34140
|
} catch (err) {
|
|
33871
34141
|
if (err.code !== "ENOENT") {
|
|
33872
34142
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -33878,8 +34148,8 @@ async function runSkill(opts) {
|
|
|
33878
34148
|
...existing,
|
|
33879
34149
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
33880
34150
|
};
|
|
33881
|
-
await
|
|
33882
|
-
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");
|
|
33883
34153
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
33884
34154
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
33885
34155
|
console.log("");
|
|
@@ -34000,12 +34270,12 @@ async function main() {
|
|
|
34000
34270
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
34001
34271
|
process.exit(2);
|
|
34002
34272
|
}
|
|
34003
|
-
const scanPath =
|
|
34273
|
+
const scanPath = import_node_path100.default.resolve(target);
|
|
34004
34274
|
const projectExplicit = parsed.project !== null;
|
|
34005
|
-
const projectName = projectExplicit ? project :
|
|
34275
|
+
const projectName = projectExplicit ? project : import_node_path100.default.basename(scanPath);
|
|
34006
34276
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
34007
|
-
const fallback = pathsForProject(projectKey,
|
|
34008
|
-
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);
|
|
34009
34279
|
const result = await runInit({
|
|
34010
34280
|
scanPath,
|
|
34011
34281
|
outPath,
|
|
@@ -34026,21 +34296,21 @@ async function main() {
|
|
|
34026
34296
|
usage5();
|
|
34027
34297
|
process.exit(2);
|
|
34028
34298
|
}
|
|
34029
|
-
const scanPath =
|
|
34030
|
-
const stat = await
|
|
34299
|
+
const scanPath = import_node_path100.default.resolve(target);
|
|
34300
|
+
const stat = await import_node_fs61.promises.stat(scanPath).catch(() => null);
|
|
34031
34301
|
if (!stat || !stat.isDirectory()) {
|
|
34032
34302
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
34033
34303
|
process.exit(2);
|
|
34034
34304
|
}
|
|
34035
|
-
const projectPaths = pathsForProject(project,
|
|
34036
|
-
const outPath =
|
|
34037
|
-
const errorsPath =
|
|
34038
|
-
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))
|
|
34039
34309
|
);
|
|
34040
|
-
const staleEventsPath =
|
|
34041
|
-
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))
|
|
34042
34312
|
);
|
|
34043
|
-
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;
|
|
34044
34314
|
const handle = await startWatch(getGraph(project), {
|
|
34045
34315
|
scanPath,
|
|
34046
34316
|
outPath,
|
|
@@ -34049,7 +34319,7 @@ async function main() {
|
|
|
34049
34319
|
project,
|
|
34050
34320
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
34051
34321
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
34052
|
-
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"),
|
|
34053
34323
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
34054
34324
|
host: process.env.HOST ?? "0.0.0.0",
|
|
34055
34325
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -34231,11 +34501,11 @@ async function main() {
|
|
|
34231
34501
|
process.exit(1);
|
|
34232
34502
|
}
|
|
34233
34503
|
async function tryOrchestrator(cmd, parsed) {
|
|
34234
|
-
const scanPath =
|
|
34235
|
-
const stat = await
|
|
34504
|
+
const scanPath = import_node_path100.default.resolve(cmd);
|
|
34505
|
+
const stat = await import_node_fs61.promises.stat(scanPath).catch(() => null);
|
|
34236
34506
|
if (!stat || !stat.isDirectory()) return null;
|
|
34237
34507
|
const projectExplicit = parsed.project !== null;
|
|
34238
|
-
const projectName = projectExplicit ? parsed.project :
|
|
34508
|
+
const projectName = projectExplicit ? parsed.project : import_node_path100.default.basename(scanPath);
|
|
34239
34509
|
const result = await runOrchestrator({
|
|
34240
34510
|
scanPath,
|
|
34241
34511
|
project: projectName,
|
|
@@ -34250,7 +34520,18 @@ async function tryOrchestrator(cmd, parsed) {
|
|
|
34250
34520
|
async function runWelcomeFlow(parsed) {
|
|
34251
34521
|
return runWelcome({
|
|
34252
34522
|
login: (loginArgv) => runLoginCommand(loginArgv),
|
|
34253
|
-
|
|
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
|
|
34254
34535
|
});
|
|
34255
34536
|
}
|
|
34256
34537
|
var QUERY_VERBS = /* @__PURE__ */ new Set([
|
|
@@ -34283,6 +34564,13 @@ var ProjectResolutionError = class extends Error {
|
|
|
34283
34564
|
}
|
|
34284
34565
|
exitCode;
|
|
34285
34566
|
};
|
|
34567
|
+
function isLoopbackEndpoint(endpoint2) {
|
|
34568
|
+
try {
|
|
34569
|
+
return isLoopbackHost2(new URL(endpoint2).hostname.replace(/^\[|\]$/g, ""));
|
|
34570
|
+
} catch {
|
|
34571
|
+
return false;
|
|
34572
|
+
}
|
|
34573
|
+
}
|
|
34286
34574
|
async function resolveProjectForVerb(client, parsed) {
|
|
34287
34575
|
const explicit = resolveProjectFlag(parsed);
|
|
34288
34576
|
if (explicit) return explicit;
|
|
@@ -34513,6 +34801,9 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
34513
34801
|
console.error(`neat ${cmd}: ${detail.trim()}`);
|
|
34514
34802
|
} else if (err instanceof TransportError) {
|
|
34515
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
|
+
}
|
|
34516
34807
|
} else {
|
|
34517
34808
|
console.error(`neat ${cmd}: ${err.message}`);
|
|
34518
34809
|
}
|
|
@@ -34569,6 +34860,7 @@ if (/[\\/](?:cli\.(?:cjs|js)|cli|neat|neat\.is)$/.test(entry)) {
|
|
|
34569
34860
|
QUERY_VERBS,
|
|
34570
34861
|
UnknownProfileError,
|
|
34571
34862
|
commandPrefix,
|
|
34863
|
+
isLoopbackEndpoint,
|
|
34572
34864
|
isNpxInvocation,
|
|
34573
34865
|
main,
|
|
34574
34866
|
parseArgs,
|