@neat.is/core 0.7.4 → 0.7.5
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-UU4XKSTO.js → chunk-KB4HRB6N.js} +872 -119
- package/dist/chunk-KB4HRB6N.js.map +1 -0
- package/dist/{chunk-KN6O7KMK.js → chunk-MYLUXVXE.js} +2 -2
- package/dist/cli.cjs +1632 -328
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +647 -104
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +892 -133
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +892 -133
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +860 -101
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-UU4XKSTO.js.map +0 -1
- /package/dist/{chunk-KN6O7KMK.js.map → chunk-MYLUXVXE.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 path74 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
65
|
+
if (exactUnauthPaths.has(path74) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path74)) {
|
|
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 path74 = q === -1 ? v : v.slice(0, q);
|
|
419
|
+
if (path74.length > 0) return path74;
|
|
420
420
|
}
|
|
421
421
|
}
|
|
422
422
|
return void 0;
|
|
@@ -748,13 +748,13 @@ __export(cli_exports, {
|
|
|
748
748
|
runMonitorVerb: () => runMonitorVerb,
|
|
749
749
|
runQueryVerb: () => runQueryVerb,
|
|
750
750
|
runSkill: () => runSkill,
|
|
751
|
-
usage: () =>
|
|
751
|
+
usage: () => usage4
|
|
752
752
|
});
|
|
753
753
|
module.exports = __toCommonJS(cli_exports);
|
|
754
754
|
init_cjs_shims();
|
|
755
|
-
var
|
|
756
|
-
var
|
|
757
|
-
var
|
|
755
|
+
var import_node_path73 = __toESM(require("path"), 1);
|
|
756
|
+
var import_node_os8 = __toESM(require("os"), 1);
|
|
757
|
+
var import_node_fs45 = require("fs");
|
|
758
758
|
|
|
759
759
|
// src/banner.ts
|
|
760
760
|
init_cjs_shims();
|
|
@@ -1322,19 +1322,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1322
1322
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1323
1323
|
let best = { path: [start], edges: [] };
|
|
1324
1324
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1325
|
-
function step(node,
|
|
1326
|
-
if (
|
|
1327
|
-
best = { path: [...
|
|
1325
|
+
function step(node, path74, edges) {
|
|
1326
|
+
if (path74.length > best.path.length) {
|
|
1327
|
+
best = { path: [...path74], edges: [...edges] };
|
|
1328
1328
|
}
|
|
1329
|
-
if (
|
|
1329
|
+
if (path74.length - 1 >= maxDepth) return;
|
|
1330
1330
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1331
1331
|
for (const [srcId, edge] of incoming) {
|
|
1332
1332
|
if (visited.has(srcId)) continue;
|
|
1333
1333
|
visited.add(srcId);
|
|
1334
|
-
|
|
1334
|
+
path74.push(srcId);
|
|
1335
1335
|
edges.push(edge);
|
|
1336
|
-
step(srcId,
|
|
1337
|
-
|
|
1336
|
+
step(srcId, path74, edges);
|
|
1337
|
+
path74.pop();
|
|
1338
1338
|
edges.pop();
|
|
1339
1339
|
visited.delete(srcId);
|
|
1340
1340
|
}
|
|
@@ -1541,26 +1541,26 @@ function dominantFailingCall(graph, serviceId7, visited) {
|
|
|
1541
1541
|
return best;
|
|
1542
1542
|
}
|
|
1543
1543
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1544
|
-
const
|
|
1544
|
+
const path74 = [originServiceId];
|
|
1545
1545
|
const edges = [];
|
|
1546
1546
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1547
1547
|
let current = originServiceId;
|
|
1548
1548
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1549
1549
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1550
1550
|
if (!hop) break;
|
|
1551
|
-
|
|
1551
|
+
path74.push(hop.nextService);
|
|
1552
1552
|
edges.push(hop.edge);
|
|
1553
1553
|
visited.add(hop.nextService);
|
|
1554
1554
|
current = hop.nextService;
|
|
1555
1555
|
}
|
|
1556
1556
|
if (edges.length === 0) return null;
|
|
1557
|
-
return { path:
|
|
1557
|
+
return { path: path74, edges, culprit: current };
|
|
1558
1558
|
}
|
|
1559
1559
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1560
1560
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1561
1561
|
if (!chain) return null;
|
|
1562
1562
|
const culprit = chain.culprit;
|
|
1563
|
-
const
|
|
1563
|
+
const path74 = [...chain.path];
|
|
1564
1564
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1565
1565
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1566
1566
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1568,14 +1568,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1568
1568
|
if (loc) {
|
|
1569
1569
|
let rootCauseNode = culprit;
|
|
1570
1570
|
if (loc.fileNode) {
|
|
1571
|
-
|
|
1571
|
+
path74.push(loc.fileNode);
|
|
1572
1572
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1573
1573
|
rootCauseNode = loc.fileNode;
|
|
1574
1574
|
}
|
|
1575
1575
|
return import_types.RootCauseResultSchema.parse({
|
|
1576
1576
|
rootCauseNode,
|
|
1577
1577
|
rootCauseReason: loc.rootCauseReason,
|
|
1578
|
-
traversalPath:
|
|
1578
|
+
traversalPath: path74,
|
|
1579
1579
|
edgeProvenances,
|
|
1580
1580
|
confidence,
|
|
1581
1581
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1587,7 +1587,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1587
1587
|
return import_types.RootCauseResultSchema.parse({
|
|
1588
1588
|
rootCauseNode: culprit,
|
|
1589
1589
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1590
|
-
traversalPath:
|
|
1590
|
+
traversalPath: path74,
|
|
1591
1591
|
edgeProvenances,
|
|
1592
1592
|
confidence,
|
|
1593
1593
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -8393,6 +8393,55 @@ function columnsFromClassBody(body) {
|
|
|
8393
8393
|
}
|
|
8394
8394
|
return out;
|
|
8395
8395
|
}
|
|
8396
|
+
function foreignKeyParentTable(call) {
|
|
8397
|
+
const fn = call.childForFieldName("function");
|
|
8398
|
+
const t = fn?.text;
|
|
8399
|
+
if (!t) return null;
|
|
8400
|
+
const base = t.includes(".") ? t.slice(t.lastIndexOf(".") + 1) : t;
|
|
8401
|
+
if (base !== "ForeignKey") return null;
|
|
8402
|
+
const target = firstPositionalString(call);
|
|
8403
|
+
if (!target) return null;
|
|
8404
|
+
const parts = target.split(".");
|
|
8405
|
+
if (parts.length < 2) return null;
|
|
8406
|
+
return parts[parts.length - 2];
|
|
8407
|
+
}
|
|
8408
|
+
function sqlalchemyForeignKeys(file, serviceDir) {
|
|
8409
|
+
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
8410
|
+
const tree = parseSource6(makePyParser4(), file.content);
|
|
8411
|
+
const out = [];
|
|
8412
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8413
|
+
walk3(tree.rootNode, (node) => {
|
|
8414
|
+
if (node.type !== "class_definition") return;
|
|
8415
|
+
const body = node.childForFieldName("body");
|
|
8416
|
+
const nameNode = node.childForFieldName("name");
|
|
8417
|
+
if (!body || !nameNode) return;
|
|
8418
|
+
const explicit = explicitTablename(body);
|
|
8419
|
+
if (explicit === "computed") return;
|
|
8420
|
+
let childTable = null;
|
|
8421
|
+
if (explicit) childTable = explicit.name;
|
|
8422
|
+
else if (extendsFlaskModel(node)) childTable = flaskSqlalchemyTableName(nameNode.text);
|
|
8423
|
+
if (!childTable) return;
|
|
8424
|
+
walk3(body, (n) => {
|
|
8425
|
+
if (n.type !== "call") return;
|
|
8426
|
+
const parentTable = foreignKeyParentTable(n);
|
|
8427
|
+
if (!parentTable) return;
|
|
8428
|
+
const key = `${childTable}->${parentTable}`;
|
|
8429
|
+
if (seen.has(key)) return;
|
|
8430
|
+
seen.add(key);
|
|
8431
|
+
const line = n.startPosition.row + 1;
|
|
8432
|
+
out.push({
|
|
8433
|
+
childTable,
|
|
8434
|
+
parentTable,
|
|
8435
|
+
evidence: {
|
|
8436
|
+
file: import_node_path36.default.relative(serviceDir, file.path),
|
|
8437
|
+
line,
|
|
8438
|
+
snippet: snippet(file.content, line)
|
|
8439
|
+
}
|
|
8440
|
+
});
|
|
8441
|
+
});
|
|
8442
|
+
});
|
|
8443
|
+
return out;
|
|
8444
|
+
}
|
|
8396
8445
|
function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
8397
8446
|
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
8398
8447
|
const tree = parseSource6(makePyParser4(), file.content);
|
|
@@ -8711,15 +8760,15 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
8711
8760
|
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
8712
8761
|
const args = node.childForFieldName("arguments");
|
|
8713
8762
|
const argNodes = args ? namedChildren3(args) : [];
|
|
8714
|
-
const
|
|
8763
|
+
const tableName2 = stringLiteralText2(argNodes[0] ?? null);
|
|
8715
8764
|
const obj = argNodes[1];
|
|
8716
|
-
if (
|
|
8717
|
-
seen.add(
|
|
8765
|
+
if (tableName2 && obj?.type === "object" && !seen.has(tableName2)) {
|
|
8766
|
+
seen.add(tableName2);
|
|
8718
8767
|
const columns = columnsFromObject(obj);
|
|
8719
8768
|
const line = node.startPosition.row + 1;
|
|
8720
8769
|
out.push({
|
|
8721
|
-
infraId: (0, import_types27.infraId)("sql-table",
|
|
8722
|
-
name:
|
|
8770
|
+
infraId: (0, import_types27.infraId)("sql-table", tableName2),
|
|
8771
|
+
name: tableName2,
|
|
8723
8772
|
kind: "sql-table",
|
|
8724
8773
|
edgeType: "CALLS",
|
|
8725
8774
|
confidenceKind: "structural",
|
|
@@ -8738,6 +8787,92 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
8738
8787
|
walk6(tree.rootNode);
|
|
8739
8788
|
return out;
|
|
8740
8789
|
}
|
|
8790
|
+
function enclosingVarName(call) {
|
|
8791
|
+
let node = call;
|
|
8792
|
+
while (node?.parent) {
|
|
8793
|
+
const parent = node.parent;
|
|
8794
|
+
if (parent.type === "variable_declarator") {
|
|
8795
|
+
const name = parent.childForFieldName("name");
|
|
8796
|
+
return name?.type === "identifier" ? name.text : null;
|
|
8797
|
+
}
|
|
8798
|
+
if (parent.type === "call_expression" || parent.type === "member_expression") {
|
|
8799
|
+
node = parent;
|
|
8800
|
+
continue;
|
|
8801
|
+
}
|
|
8802
|
+
return null;
|
|
8803
|
+
}
|
|
8804
|
+
return null;
|
|
8805
|
+
}
|
|
8806
|
+
function collectDrizzleTables(root) {
|
|
8807
|
+
const tables = [];
|
|
8808
|
+
const varToTable = /* @__PURE__ */ new Map();
|
|
8809
|
+
const walk6 = (node) => {
|
|
8810
|
+
if (node.type === "call_expression") {
|
|
8811
|
+
const fn = node.childForFieldName("function");
|
|
8812
|
+
if (fn?.type === "identifier" && TABLE_BUILDERS.has(fn.text)) {
|
|
8813
|
+
const args = node.childForFieldName("arguments");
|
|
8814
|
+
const argNodes = args ? namedChildren3(args) : [];
|
|
8815
|
+
const tableName2 = stringLiteralText2(argNodes[0] ?? null);
|
|
8816
|
+
const obj = argNodes[1]?.type === "object" ? argNodes[1] : null;
|
|
8817
|
+
if (tableName2) {
|
|
8818
|
+
tables.push({ tableName: tableName2, object: obj });
|
|
8819
|
+
const varName = enclosingVarName(node);
|
|
8820
|
+
if (varName) varToTable.set(varName, tableName2);
|
|
8821
|
+
}
|
|
8822
|
+
}
|
|
8823
|
+
}
|
|
8824
|
+
for (const c of namedChildren3(node)) walk6(c);
|
|
8825
|
+
};
|
|
8826
|
+
walk6(root);
|
|
8827
|
+
return { tables, varToTable };
|
|
8828
|
+
}
|
|
8829
|
+
function referencesTargetVar(call) {
|
|
8830
|
+
const fn = call.childForFieldName("function");
|
|
8831
|
+
if (fn?.type !== "member_expression") return null;
|
|
8832
|
+
if (fn.childForFieldName("property")?.text !== "references") return null;
|
|
8833
|
+
const args = call.childForFieldName("arguments");
|
|
8834
|
+
const first = args ? namedChildren3(args)[0] ?? null : null;
|
|
8835
|
+
if (first?.type !== "arrow_function") return null;
|
|
8836
|
+
const body = first.childForFieldName("body");
|
|
8837
|
+
if (body?.type !== "member_expression") return null;
|
|
8838
|
+
const obj = body.childForFieldName("object");
|
|
8839
|
+
return obj?.type === "identifier" ? obj.text : null;
|
|
8840
|
+
}
|
|
8841
|
+
function drizzleForeignKeys(file, serviceDir) {
|
|
8842
|
+
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
8843
|
+
const tree = parseSource3(parserForExt(import_node_path38.default.extname(file.path)), file.content);
|
|
8844
|
+
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
8845
|
+
const out = [];
|
|
8846
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8847
|
+
for (const table of tables) {
|
|
8848
|
+
if (!table.object) continue;
|
|
8849
|
+
const walk6 = (node) => {
|
|
8850
|
+
if (node.type === "call_expression") {
|
|
8851
|
+
const targetVar = referencesTargetVar(node);
|
|
8852
|
+
const parentTable = targetVar ? varToTable.get(targetVar) : void 0;
|
|
8853
|
+
if (parentTable) {
|
|
8854
|
+
const key = `${table.tableName}->${parentTable}`;
|
|
8855
|
+
if (!seen.has(key)) {
|
|
8856
|
+
seen.add(key);
|
|
8857
|
+
const line = node.startPosition.row + 1;
|
|
8858
|
+
out.push({
|
|
8859
|
+
childTable: table.tableName,
|
|
8860
|
+
parentTable,
|
|
8861
|
+
evidence: {
|
|
8862
|
+
file: import_node_path38.default.relative(serviceDir, file.path),
|
|
8863
|
+
line,
|
|
8864
|
+
snippet: snippet(file.content, line)
|
|
8865
|
+
}
|
|
8866
|
+
});
|
|
8867
|
+
}
|
|
8868
|
+
}
|
|
8869
|
+
}
|
|
8870
|
+
for (const c of namedChildren3(node)) walk6(c);
|
|
8871
|
+
};
|
|
8872
|
+
walk6(table.object);
|
|
8873
|
+
}
|
|
8874
|
+
return out;
|
|
8875
|
+
}
|
|
8741
8876
|
|
|
8742
8877
|
// src/extract/calls/prisma.ts
|
|
8743
8878
|
init_cjs_shims();
|
|
@@ -8866,6 +9001,96 @@ async function prismaColumnEndpoints(serviceDir) {
|
|
|
8866
9001
|
if (!content) return [];
|
|
8867
9002
|
return prismaColumnsFromSchema({ path: schemaPath, content }, serviceDir);
|
|
8868
9003
|
}
|
|
9004
|
+
function buildModelTableMap(lines) {
|
|
9005
|
+
const map = /* @__PURE__ */ new Map();
|
|
9006
|
+
let current = null;
|
|
9007
|
+
let depth = 0;
|
|
9008
|
+
for (const raw of lines) {
|
|
9009
|
+
if (current === null) {
|
|
9010
|
+
const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
|
|
9011
|
+
if (header && raw.includes("{")) {
|
|
9012
|
+
current = { model: header[1], table: header[1] };
|
|
9013
|
+
depth = netBraces(raw);
|
|
9014
|
+
if (depth <= 0) {
|
|
9015
|
+
map.set(current.model, current.table);
|
|
9016
|
+
current = null;
|
|
9017
|
+
}
|
|
9018
|
+
}
|
|
9019
|
+
continue;
|
|
9020
|
+
}
|
|
9021
|
+
depth += netBraces(raw);
|
|
9022
|
+
const trimmed = stripLineComment(raw).trim();
|
|
9023
|
+
if (trimmed.startsWith("@@")) {
|
|
9024
|
+
const m = trimmed.match(/@@map\(\s*"([^"]+)"\s*\)/);
|
|
9025
|
+
if (m) current.table = m[1];
|
|
9026
|
+
}
|
|
9027
|
+
if (depth <= 0) {
|
|
9028
|
+
map.set(current.model, current.table);
|
|
9029
|
+
current = null;
|
|
9030
|
+
}
|
|
9031
|
+
}
|
|
9032
|
+
if (current) map.set(current.model, current.table);
|
|
9033
|
+
return map;
|
|
9034
|
+
}
|
|
9035
|
+
function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
9036
|
+
const content = file.content;
|
|
9037
|
+
if (!/\bmodel\s+[A-Za-z_]\w*\s*\{/.test(content)) return [];
|
|
9038
|
+
const lines = content.split("\n");
|
|
9039
|
+
const modelToTable = buildModelTableMap(lines);
|
|
9040
|
+
const out = [];
|
|
9041
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9042
|
+
let current = null;
|
|
9043
|
+
let depth = 0;
|
|
9044
|
+
for (let i = 0; i < lines.length; i++) {
|
|
9045
|
+
const raw = lines[i];
|
|
9046
|
+
const lineNo = i + 1;
|
|
9047
|
+
if (current === null) {
|
|
9048
|
+
const header = raw.match(/^\s*model\s+([A-Za-z_]\w*)\b/);
|
|
9049
|
+
if (header && raw.includes("{")) {
|
|
9050
|
+
current = { table: modelToTable.get(header[1]) ?? header[1] };
|
|
9051
|
+
depth = netBraces(raw);
|
|
9052
|
+
if (depth <= 0) current = null;
|
|
9053
|
+
}
|
|
9054
|
+
continue;
|
|
9055
|
+
}
|
|
9056
|
+
depth += netBraces(raw);
|
|
9057
|
+
const closing = depth <= 0;
|
|
9058
|
+
const trimmed = stripLineComment(raw).trim();
|
|
9059
|
+
if (trimmed && !trimmed.startsWith("@@") && !trimmed.startsWith("}")) {
|
|
9060
|
+
const fm = trimmed.match(/^([A-Za-z_]\w*)\s+([A-Za-z_]\w*)/);
|
|
9061
|
+
if (fm && /@relation\b[^)]*\bfields\s*:/.test(trimmed)) {
|
|
9062
|
+
const parentTable = modelToTable.get(fm[2]);
|
|
9063
|
+
if (parentTable) {
|
|
9064
|
+
const key = `${current.table}->${parentTable}`;
|
|
9065
|
+
if (!seen.has(key)) {
|
|
9066
|
+
seen.add(key);
|
|
9067
|
+
out.push({
|
|
9068
|
+
childTable: current.table,
|
|
9069
|
+
parentTable,
|
|
9070
|
+
evidence: {
|
|
9071
|
+
file: import_node_path39.default.relative(serviceDir, file.path),
|
|
9072
|
+
line: lineNo,
|
|
9073
|
+
snippet: snippet(content, lineNo)
|
|
9074
|
+
}
|
|
9075
|
+
});
|
|
9076
|
+
}
|
|
9077
|
+
}
|
|
9078
|
+
}
|
|
9079
|
+
}
|
|
9080
|
+
if (closing) current = null;
|
|
9081
|
+
}
|
|
9082
|
+
return out;
|
|
9083
|
+
}
|
|
9084
|
+
async function prismaForeignKeys(serviceDir) {
|
|
9085
|
+
const schemaPath = await findFirst(serviceDir, [
|
|
9086
|
+
import_node_path39.default.join("prisma", "schema.prisma"),
|
|
9087
|
+
"schema.prisma"
|
|
9088
|
+
]);
|
|
9089
|
+
if (!schemaPath) return [];
|
|
9090
|
+
const content = await readIfExists(schemaPath);
|
|
9091
|
+
if (!content) return [];
|
|
9092
|
+
return prismaForeignKeysFromSchema({ path: schemaPath, content }, serviceDir);
|
|
9093
|
+
}
|
|
8869
9094
|
|
|
8870
9095
|
// src/extract/calls/go.ts
|
|
8871
9096
|
init_cjs_shims();
|
|
@@ -9042,21 +9267,79 @@ async function addCallEdges(graph, services) {
|
|
|
9042
9267
|
};
|
|
9043
9268
|
}
|
|
9044
9269
|
|
|
9270
|
+
// src/extract/table-edges.ts
|
|
9271
|
+
init_cjs_shims();
|
|
9272
|
+
var import_types31 = require("@neat.is/types");
|
|
9273
|
+
async function addTableEdges(graph, services) {
|
|
9274
|
+
let nodesAdded = 0;
|
|
9275
|
+
let edgesAdded = 0;
|
|
9276
|
+
for (const service of services) {
|
|
9277
|
+
const files = await loadSourceFiles(service.dir);
|
|
9278
|
+
const refs = [];
|
|
9279
|
+
for (const file of files) {
|
|
9280
|
+
try {
|
|
9281
|
+
refs.push(...drizzleForeignKeys(file, service.dir));
|
|
9282
|
+
refs.push(...sqlalchemyForeignKeys(file, service.dir));
|
|
9283
|
+
} catch (err) {
|
|
9284
|
+
recordExtractionError("foreign-key extraction", file.path, err);
|
|
9285
|
+
}
|
|
9286
|
+
}
|
|
9287
|
+
try {
|
|
9288
|
+
refs.push(...await prismaForeignKeys(service.dir));
|
|
9289
|
+
} catch (err) {
|
|
9290
|
+
recordExtractionError("prisma foreign-key extraction", service.dir, err);
|
|
9291
|
+
}
|
|
9292
|
+
for (const ref of refs) {
|
|
9293
|
+
const childId = (0, import_types31.infraId)("sql-table", ref.childTable);
|
|
9294
|
+
const parentId = (0, import_types31.infraId)("sql-table", ref.parentTable);
|
|
9295
|
+
if (childId === parentId) continue;
|
|
9296
|
+
nodesAdded += ensureTableNode(graph, childId, ref.childTable);
|
|
9297
|
+
nodesAdded += ensureTableNode(graph, parentId, ref.parentTable);
|
|
9298
|
+
const edgeId = (0, import_types31.extractedEdgeId)(childId, parentId, import_types31.EdgeType.REFERENCES);
|
|
9299
|
+
if (graph.hasEdge(edgeId)) continue;
|
|
9300
|
+
const edge = {
|
|
9301
|
+
id: edgeId,
|
|
9302
|
+
source: childId,
|
|
9303
|
+
target: parentId,
|
|
9304
|
+
type: import_types31.EdgeType.REFERENCES,
|
|
9305
|
+
provenance: import_types31.Provenance.EXTRACTED,
|
|
9306
|
+
confidence: (0, import_types31.confidenceForExtracted)("structural"),
|
|
9307
|
+
evidence: ref.evidence
|
|
9308
|
+
};
|
|
9309
|
+
graph.addEdgeWithKey(edgeId, childId, parentId, edge);
|
|
9310
|
+
edgesAdded++;
|
|
9311
|
+
}
|
|
9312
|
+
}
|
|
9313
|
+
return { nodesAdded, edgesAdded };
|
|
9314
|
+
}
|
|
9315
|
+
function ensureTableNode(graph, id, name) {
|
|
9316
|
+
if (graph.hasNode(id)) return 0;
|
|
9317
|
+
const node = {
|
|
9318
|
+
id,
|
|
9319
|
+
type: import_types31.NodeType.InfraNode,
|
|
9320
|
+
name,
|
|
9321
|
+
provider: "self",
|
|
9322
|
+
kind: "sql-table"
|
|
9323
|
+
};
|
|
9324
|
+
graph.addNode(id, node);
|
|
9325
|
+
return 1;
|
|
9326
|
+
}
|
|
9327
|
+
|
|
9045
9328
|
// src/extract/infra/index.ts
|
|
9046
9329
|
init_cjs_shims();
|
|
9047
9330
|
|
|
9048
9331
|
// src/extract/infra/docker-compose.ts
|
|
9049
9332
|
init_cjs_shims();
|
|
9050
9333
|
var import_node_path43 = __toESM(require("path"), 1);
|
|
9051
|
-
var
|
|
9334
|
+
var import_types33 = require("@neat.is/types");
|
|
9052
9335
|
|
|
9053
9336
|
// src/extract/infra/shared.ts
|
|
9054
9337
|
init_cjs_shims();
|
|
9055
|
-
var
|
|
9338
|
+
var import_types32 = require("@neat.is/types");
|
|
9056
9339
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
9057
9340
|
return {
|
|
9058
|
-
id: (0,
|
|
9059
|
-
type:
|
|
9341
|
+
id: (0, import_types32.infraId)(kind, name),
|
|
9342
|
+
type: import_types32.NodeType.InfraNode,
|
|
9060
9343
|
name,
|
|
9061
9344
|
provider,
|
|
9062
9345
|
kind,
|
|
@@ -9100,8 +9383,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
|
|
|
9100
9383
|
source: anchorId,
|
|
9101
9384
|
target: node.id,
|
|
9102
9385
|
type: edgeType,
|
|
9103
|
-
provenance:
|
|
9104
|
-
confidence: (0,
|
|
9386
|
+
provenance: import_types32.Provenance.EXTRACTED,
|
|
9387
|
+
confidence: (0, import_types32.confidenceForExtracted)("structural"),
|
|
9105
9388
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
9106
9389
|
};
|
|
9107
9390
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9168,15 +9451,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
9168
9451
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
9169
9452
|
const targetId = composeNameToNodeId.get(dep);
|
|
9170
9453
|
if (!targetId) continue;
|
|
9171
|
-
const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId,
|
|
9454
|
+
const edgeId = (0, import_types3.extractedEdgeId)(sourceId, targetId, import_types33.EdgeType.DEPENDS_ON);
|
|
9172
9455
|
if (graph.hasEdge(edgeId)) continue;
|
|
9173
9456
|
const edge = {
|
|
9174
9457
|
id: edgeId,
|
|
9175
9458
|
source: sourceId,
|
|
9176
9459
|
target: targetId,
|
|
9177
|
-
type:
|
|
9178
|
-
provenance:
|
|
9179
|
-
confidence: (0,
|
|
9460
|
+
type: import_types33.EdgeType.DEPENDS_ON,
|
|
9461
|
+
provenance: import_types33.Provenance.EXTRACTED,
|
|
9462
|
+
confidence: (0, import_types33.confidenceForExtracted)("structural"),
|
|
9180
9463
|
evidence: { file: evidenceFile }
|
|
9181
9464
|
};
|
|
9182
9465
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9190,7 +9473,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
9190
9473
|
init_cjs_shims();
|
|
9191
9474
|
var import_node_path44 = __toESM(require("path"), 1);
|
|
9192
9475
|
var import_node_fs18 = require("fs");
|
|
9193
|
-
var
|
|
9476
|
+
var import_types34 = require("@neat.is/types");
|
|
9194
9477
|
function readDockerfile(content) {
|
|
9195
9478
|
let image = null;
|
|
9196
9479
|
const ports = [];
|
|
@@ -9249,15 +9532,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9249
9532
|
);
|
|
9250
9533
|
nodesAdded += fn;
|
|
9251
9534
|
edgesAdded += fe;
|
|
9252
|
-
const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id,
|
|
9535
|
+
const edgeId = (0, import_types3.extractedEdgeId)(fileNodeId, node.id, import_types34.EdgeType.RUNS_ON);
|
|
9253
9536
|
if (!graph.hasEdge(edgeId)) {
|
|
9254
9537
|
const edge = {
|
|
9255
9538
|
id: edgeId,
|
|
9256
9539
|
source: fileNodeId,
|
|
9257
9540
|
target: node.id,
|
|
9258
|
-
type:
|
|
9259
|
-
provenance:
|
|
9260
|
-
confidence: (0,
|
|
9541
|
+
type: import_types34.EdgeType.RUNS_ON,
|
|
9542
|
+
provenance: import_types34.Provenance.EXTRACTED,
|
|
9543
|
+
confidence: (0, import_types34.confidenceForExtracted)("structural"),
|
|
9261
9544
|
evidence: {
|
|
9262
9545
|
file: evidenceFile,
|
|
9263
9546
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -9272,15 +9555,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9272
9555
|
graph.addNode(portNode.id, portNode);
|
|
9273
9556
|
nodesAdded++;
|
|
9274
9557
|
}
|
|
9275
|
-
const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id,
|
|
9558
|
+
const portEdgeId = (0, import_types3.extractedEdgeId)(fileNodeId, portNode.id, import_types34.EdgeType.CONNECTS_TO);
|
|
9276
9559
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
9277
9560
|
const portEdge = {
|
|
9278
9561
|
id: portEdgeId,
|
|
9279
9562
|
source: fileNodeId,
|
|
9280
9563
|
target: portNode.id,
|
|
9281
|
-
type:
|
|
9282
|
-
provenance:
|
|
9283
|
-
confidence: (0,
|
|
9564
|
+
type: import_types34.EdgeType.CONNECTS_TO,
|
|
9565
|
+
provenance: import_types34.Provenance.EXTRACTED,
|
|
9566
|
+
confidence: (0, import_types34.confidenceForExtracted)("structural"),
|
|
9284
9567
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
9285
9568
|
};
|
|
9286
9569
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -9294,7 +9577,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
9294
9577
|
init_cjs_shims();
|
|
9295
9578
|
var import_node_fs19 = require("fs");
|
|
9296
9579
|
var import_node_path45 = __toESM(require("path"), 1);
|
|
9297
|
-
var
|
|
9580
|
+
var import_types35 = require("@neat.is/types");
|
|
9298
9581
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
9299
9582
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
9300
9583
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -9375,16 +9658,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
9375
9658
|
if (!target) continue;
|
|
9376
9659
|
if (seen.has(target.nodeId)) continue;
|
|
9377
9660
|
seen.add(target.nodeId);
|
|
9378
|
-
const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
9661
|
+
const edgeId = (0, import_types3.extractedEdgeId)(resource.nodeId, target.nodeId, import_types35.EdgeType.DEPENDS_ON);
|
|
9379
9662
|
if (graph.hasEdge(edgeId)) continue;
|
|
9380
9663
|
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
9381
9664
|
const edge = {
|
|
9382
9665
|
id: edgeId,
|
|
9383
9666
|
source: resource.nodeId,
|
|
9384
9667
|
target: target.nodeId,
|
|
9385
|
-
type:
|
|
9386
|
-
provenance:
|
|
9387
|
-
confidence: (0,
|
|
9668
|
+
type: import_types35.EdgeType.DEPENDS_ON,
|
|
9669
|
+
provenance: import_types35.Provenance.EXTRACTED,
|
|
9670
|
+
confidence: (0, import_types35.confidenceForExtracted)("structural"),
|
|
9388
9671
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
9389
9672
|
};
|
|
9390
9673
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9456,7 +9739,7 @@ init_cjs_shims();
|
|
|
9456
9739
|
var import_node_fs21 = require("fs");
|
|
9457
9740
|
var import_node_path47 = __toESM(require("path"), 1);
|
|
9458
9741
|
var import_smol_toml2 = require("smol-toml");
|
|
9459
|
-
var
|
|
9742
|
+
var import_types36 = require("@neat.is/types");
|
|
9460
9743
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
9461
9744
|
async function readWranglerConfig(dir) {
|
|
9462
9745
|
for (const filename of WRANGLER_FILENAMES) {
|
|
@@ -9504,8 +9787,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
|
|
|
9504
9787
|
source: anchorId,
|
|
9505
9788
|
target: node.id,
|
|
9506
9789
|
type: edgeType,
|
|
9507
|
-
provenance:
|
|
9508
|
-
confidence: (0,
|
|
9790
|
+
provenance: import_types36.Provenance.EXTRACTED,
|
|
9791
|
+
confidence: (0, import_types36.confidenceForExtracted)("structural"),
|
|
9509
9792
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
9510
9793
|
};
|
|
9511
9794
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9566,15 +9849,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9566
9849
|
nodesAdded++;
|
|
9567
9850
|
}
|
|
9568
9851
|
if (runtimeNode.id !== anchorId) {
|
|
9569
|
-
const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id,
|
|
9852
|
+
const runsOnId = (0, import_types3.extractedEdgeId)(anchorId, runtimeNode.id, import_types36.EdgeType.RUNS_ON);
|
|
9570
9853
|
if (!graph.hasEdge(runsOnId)) {
|
|
9571
9854
|
const edge = {
|
|
9572
9855
|
id: runsOnId,
|
|
9573
9856
|
source: anchorId,
|
|
9574
9857
|
target: runtimeNode.id,
|
|
9575
|
-
type:
|
|
9576
|
-
provenance:
|
|
9577
|
-
confidence: (0,
|
|
9858
|
+
type: import_types36.EdgeType.RUNS_ON,
|
|
9859
|
+
provenance: import_types36.Provenance.EXTRACTED,
|
|
9860
|
+
confidence: (0, import_types36.confidenceForExtracted)("structural"),
|
|
9578
9861
|
evidence: {
|
|
9579
9862
|
file: evidenceFile,
|
|
9580
9863
|
...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
|
|
@@ -9588,7 +9871,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9588
9871
|
const result = addResourceEdge(
|
|
9589
9872
|
graph,
|
|
9590
9873
|
anchorId,
|
|
9591
|
-
|
|
9874
|
+
import_types36.EdgeType.CONNECTS_TO,
|
|
9592
9875
|
"cloudflare-route",
|
|
9593
9876
|
route,
|
|
9594
9877
|
evidenceFile,
|
|
@@ -9612,7 +9895,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9612
9895
|
const result = addResourceEdge(
|
|
9613
9896
|
graph,
|
|
9614
9897
|
anchorId,
|
|
9615
|
-
|
|
9898
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9616
9899
|
group.kind,
|
|
9617
9900
|
name,
|
|
9618
9901
|
evidenceFile,
|
|
@@ -9626,7 +9909,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9626
9909
|
const result = addResourceEdge(
|
|
9627
9910
|
graph,
|
|
9628
9911
|
anchorId,
|
|
9629
|
-
|
|
9912
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9630
9913
|
"cloudflare-cron",
|
|
9631
9914
|
cron,
|
|
9632
9915
|
evidenceFile,
|
|
@@ -9639,7 +9922,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9639
9922
|
const result = addResourceEdge(
|
|
9640
9923
|
graph,
|
|
9641
9924
|
anchorId,
|
|
9642
|
-
|
|
9925
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9643
9926
|
"cloudflare-env-var",
|
|
9644
9927
|
varName,
|
|
9645
9928
|
evidenceFile,
|
|
@@ -9652,15 +9935,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9652
9935
|
if (!svc.service) continue;
|
|
9653
9936
|
const target = workerIndex.get(svc.service);
|
|
9654
9937
|
if (target && target.anchorId !== anchorId) {
|
|
9655
|
-
const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId,
|
|
9938
|
+
const edgeId = (0, import_types3.extractedEdgeId)(anchorId, target.anchorId, import_types36.EdgeType.CALLS);
|
|
9656
9939
|
if (!graph.hasEdge(edgeId)) {
|
|
9657
9940
|
const edge = {
|
|
9658
9941
|
id: edgeId,
|
|
9659
9942
|
source: anchorId,
|
|
9660
9943
|
target: target.anchorId,
|
|
9661
|
-
type:
|
|
9662
|
-
provenance:
|
|
9663
|
-
confidence: (0,
|
|
9944
|
+
type: import_types36.EdgeType.CALLS,
|
|
9945
|
+
provenance: import_types36.Provenance.EXTRACTED,
|
|
9946
|
+
confidence: (0, import_types36.confidenceForExtracted)("structural"),
|
|
9664
9947
|
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
9665
9948
|
};
|
|
9666
9949
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -9671,7 +9954,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9671
9954
|
const result = addResourceEdge(
|
|
9672
9955
|
graph,
|
|
9673
9956
|
anchorId,
|
|
9674
|
-
|
|
9957
|
+
import_types36.EdgeType.DEPENDS_ON,
|
|
9675
9958
|
"cloudflare-service-binding",
|
|
9676
9959
|
svc.service,
|
|
9677
9960
|
evidenceFile,
|
|
@@ -9688,7 +9971,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
9688
9971
|
init_cjs_shims();
|
|
9689
9972
|
var import_node_fs22 = require("fs");
|
|
9690
9973
|
var import_node_path48 = __toESM(require("path"), 1);
|
|
9691
|
-
var
|
|
9974
|
+
var import_types37 = require("@neat.is/types");
|
|
9692
9975
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
9693
9976
|
async function readVercelConfig(dir) {
|
|
9694
9977
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
@@ -9751,12 +10034,12 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
9751
10034
|
nodesAdded += result.nodesAdded;
|
|
9752
10035
|
edgesAdded += result.edgesAdded;
|
|
9753
10036
|
};
|
|
9754
|
-
add(
|
|
9755
|
-
for (const cron of config.crons ?? []) add(
|
|
9756
|
-
for (const varName of Object.keys(config.env ?? {})) add(
|
|
9757
|
-
for (const varName of Object.keys(config.build?.env ?? {})) add(
|
|
10037
|
+
add(import_types37.EdgeType.RUNS_ON, "vercel", "vercel");
|
|
10038
|
+
for (const cron of config.crons ?? []) add(import_types37.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
|
|
10039
|
+
for (const varName of Object.keys(config.env ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
10040
|
+
for (const varName of Object.keys(config.build?.env ?? {})) add(import_types37.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
9758
10041
|
for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
|
|
9759
|
-
add(
|
|
10042
|
+
add(import_types37.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
|
|
9760
10043
|
}
|
|
9761
10044
|
}
|
|
9762
10045
|
return { nodesAdded, edgesAdded };
|
|
@@ -9767,7 +10050,7 @@ init_cjs_shims();
|
|
|
9767
10050
|
var import_node_fs23 = require("fs");
|
|
9768
10051
|
var import_node_path49 = __toESM(require("path"), 1);
|
|
9769
10052
|
var import_smol_toml3 = require("smol-toml");
|
|
9770
|
-
var
|
|
10053
|
+
var import_types38 = require("@neat.is/types");
|
|
9771
10054
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
9772
10055
|
async function readRailwayConfig(dir) {
|
|
9773
10056
|
for (const filename of RAILWAY_FILENAMES) {
|
|
@@ -9813,9 +10096,9 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
9813
10096
|
nodesAdded += result.nodesAdded;
|
|
9814
10097
|
edgesAdded += result.edgesAdded;
|
|
9815
10098
|
};
|
|
9816
|
-
add(
|
|
9817
|
-
add(
|
|
9818
|
-
add(
|
|
10099
|
+
add(import_types38.EdgeType.RUNS_ON, "railway", "railway");
|
|
10100
|
+
add(import_types38.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
|
|
10101
|
+
add(import_types38.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
|
|
9819
10102
|
}
|
|
9820
10103
|
return { nodesAdded, edgesAdded };
|
|
9821
10104
|
}
|
|
@@ -9825,7 +10108,7 @@ init_cjs_shims();
|
|
|
9825
10108
|
var import_node_fs24 = require("fs");
|
|
9826
10109
|
var import_node_path50 = __toESM(require("path"), 1);
|
|
9827
10110
|
var import_smol_toml4 = require("smol-toml");
|
|
9828
|
-
var
|
|
10111
|
+
var import_types39 = require("@neat.is/types");
|
|
9829
10112
|
async function readSupabaseConfig(dir) {
|
|
9830
10113
|
const relFile = import_node_path50.default.join("supabase", "config.toml");
|
|
9831
10114
|
const abs = import_node_path50.default.join(dir, relFile);
|
|
@@ -9873,10 +10156,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
9873
10156
|
nodesAdded += result.nodesAdded;
|
|
9874
10157
|
edgesAdded += result.edgesAdded;
|
|
9875
10158
|
};
|
|
9876
|
-
add(
|
|
9877
|
-
for (const fn of Object.keys(config.functions ?? {})) add(
|
|
9878
|
-
if (config.storage) add(
|
|
9879
|
-
if (config.auth) add(
|
|
10159
|
+
add(import_types39.EdgeType.RUNS_ON, "supabase", "supabase");
|
|
10160
|
+
for (const fn of Object.keys(config.functions ?? {})) add(import_types39.EdgeType.DEPENDS_ON, "supabase-function", fn);
|
|
10161
|
+
if (config.storage) add(import_types39.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
|
|
10162
|
+
if (config.auth) add(import_types39.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
|
|
9880
10163
|
}
|
|
9881
10164
|
return { nodesAdded, edgesAdded };
|
|
9882
10165
|
}
|
|
@@ -9904,11 +10187,11 @@ var import_node_path52 = __toESM(require("path"), 1);
|
|
|
9904
10187
|
init_cjs_shims();
|
|
9905
10188
|
var import_node_fs25 = require("fs");
|
|
9906
10189
|
var import_node_path51 = __toESM(require("path"), 1);
|
|
9907
|
-
var
|
|
10190
|
+
var import_types40 = require("@neat.is/types");
|
|
9908
10191
|
function dropOrphanedFileNodes(graph) {
|
|
9909
10192
|
const orphans = [];
|
|
9910
10193
|
graph.forEachNode((id, attrs) => {
|
|
9911
|
-
if (attrs.type !==
|
|
10194
|
+
if (attrs.type !== import_types40.NodeType.FileNode) return;
|
|
9912
10195
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
9913
10196
|
orphans.push(id);
|
|
9914
10197
|
}
|
|
@@ -9921,7 +10204,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
9921
10204
|
const toDrop = [];
|
|
9922
10205
|
graph.forEachEdge((id, attrs) => {
|
|
9923
10206
|
const edge = attrs;
|
|
9924
|
-
if (edge.provenance !==
|
|
10207
|
+
if (edge.provenance !== import_types40.Provenance.EXTRACTED) return;
|
|
9925
10208
|
if (!edge.evidence?.file) return;
|
|
9926
10209
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
9927
10210
|
});
|
|
@@ -9934,7 +10217,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
9934
10217
|
const bases = [scanPath, ...serviceDirs];
|
|
9935
10218
|
graph.forEachEdge((id, attrs) => {
|
|
9936
10219
|
const edge = attrs;
|
|
9937
|
-
if (edge.provenance !==
|
|
10220
|
+
if (edge.provenance !== import_types40.Provenance.EXTRACTED) return;
|
|
9938
10221
|
const evidenceFile = edge.evidence?.file;
|
|
9939
10222
|
if (!evidenceFile) return;
|
|
9940
10223
|
if (import_node_path51.default.isAbsolute(evidenceFile)) {
|
|
@@ -9965,6 +10248,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
9965
10248
|
const routePhase = await addRoutes(graph, services);
|
|
9966
10249
|
const grpcPhase = await addGrpcMethods(graph, services);
|
|
9967
10250
|
const phase4 = await addCallEdges(graph, services);
|
|
10251
|
+
const tableEdges = await addTableEdges(graph, services);
|
|
9968
10252
|
const phase5 = await addInfra(graph, scanPath, services);
|
|
9969
10253
|
const ghostsRetired = retireExtractedEdgesByMissingFile(
|
|
9970
10254
|
graph,
|
|
@@ -10004,8 +10288,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
10004
10288
|
}
|
|
10005
10289
|
}
|
|
10006
10290
|
const result = {
|
|
10007
|
-
nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
10008
|
-
edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
10291
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + symbolEnum.nodesAdded + importGraph.nodesAdded + symbolEdges.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + routePhase.nodesAdded + grpcPhase.nodesAdded + phase4.nodesAdded + tableEdges.nodesAdded + phase5.nodesAdded,
|
|
10292
|
+
edgesAdded: fileEnum.edgesAdded + symbolEnum.edgesAdded + importGraph.edgesAdded + symbolEdges.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + routePhase.edgesAdded + grpcPhase.edgesAdded + phase4.edgesAdded + tableEdges.edgesAdded + phase5.edgesAdded,
|
|
10009
10293
|
frontiersPromoted,
|
|
10010
10294
|
extractionErrors: errorEntries.length,
|
|
10011
10295
|
errorEntries,
|
|
@@ -10028,39 +10312,39 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
10028
10312
|
|
|
10029
10313
|
// src/divergences.ts
|
|
10030
10314
|
init_cjs_shims();
|
|
10031
|
-
var
|
|
10315
|
+
var import_types41 = require("@neat.is/types");
|
|
10032
10316
|
function bucketKey(source, target, type) {
|
|
10033
10317
|
return `${type}|${source}|${target}`;
|
|
10034
10318
|
}
|
|
10035
10319
|
function bucketSourceFor(graph, edge) {
|
|
10036
|
-
if (edge.type !==
|
|
10037
|
-
const parsed = (0,
|
|
10320
|
+
if (edge.type !== import_types41.EdgeType.CONNECTS_TO) return edge.source;
|
|
10321
|
+
const parsed = (0, import_types41.parseFileId)(edge.source);
|
|
10038
10322
|
if (!parsed || !graph.hasNode(edge.target)) return edge.source;
|
|
10039
10323
|
const target = graph.getNodeAttributes(edge.target);
|
|
10040
|
-
if (target.type !==
|
|
10041
|
-
return (0,
|
|
10324
|
+
if (target.type !== import_types41.NodeType.DatabaseNode) return edge.source;
|
|
10325
|
+
return (0, import_types41.serviceId)(parsed.service);
|
|
10042
10326
|
}
|
|
10043
10327
|
function bucketEdges(graph) {
|
|
10044
10328
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
10045
10329
|
graph.forEachEdge((id, attrs) => {
|
|
10046
10330
|
const e = attrs;
|
|
10047
|
-
const parsed = (0,
|
|
10331
|
+
const parsed = (0, import_types41.parseEdgeId)(id);
|
|
10048
10332
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
10049
10333
|
const source = bucketSourceFor(graph, e);
|
|
10050
10334
|
const key = bucketKey(source, e.target, e.type);
|
|
10051
10335
|
const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
|
|
10052
10336
|
switch (provenance) {
|
|
10053
|
-
case
|
|
10337
|
+
case import_types41.Provenance.EXTRACTED:
|
|
10054
10338
|
cur.extracted = e;
|
|
10055
10339
|
break;
|
|
10056
|
-
case
|
|
10340
|
+
case import_types41.Provenance.OBSERVED:
|
|
10057
10341
|
cur.observed = e;
|
|
10058
10342
|
break;
|
|
10059
|
-
case
|
|
10343
|
+
case import_types41.Provenance.INFERRED:
|
|
10060
10344
|
cur.inferred = e;
|
|
10061
10345
|
break;
|
|
10062
10346
|
default:
|
|
10063
|
-
if (e.provenance ===
|
|
10347
|
+
if (e.provenance === import_types41.Provenance.STALE) cur.stale = e;
|
|
10064
10348
|
}
|
|
10065
10349
|
buckets2.set(key, cur);
|
|
10066
10350
|
});
|
|
@@ -10069,17 +10353,17 @@ function bucketEdges(graph) {
|
|
|
10069
10353
|
function nodeIsFrontier(graph, nodeId) {
|
|
10070
10354
|
if (!graph.hasNode(nodeId)) return false;
|
|
10071
10355
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
10072
|
-
return attrs.type ===
|
|
10356
|
+
return attrs.type === import_types41.NodeType.FrontierNode;
|
|
10073
10357
|
}
|
|
10074
10358
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
10075
10359
|
if (!graph.hasNode(nodeId)) return false;
|
|
10076
10360
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
10077
|
-
return attrs.type ===
|
|
10361
|
+
return attrs.type === import_types41.NodeType.WebSocketChannelNode;
|
|
10078
10362
|
}
|
|
10079
10363
|
function nodeIsSymbol(graph, nodeId) {
|
|
10080
10364
|
if (!graph.hasNode(nodeId)) return false;
|
|
10081
10365
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
10082
|
-
return attrs.type ===
|
|
10366
|
+
return attrs.type === import_types41.NodeType.SymbolNode;
|
|
10083
10367
|
}
|
|
10084
10368
|
function clampConfidence(n) {
|
|
10085
10369
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -10099,14 +10383,14 @@ function gradedConfidence(edge) {
|
|
|
10099
10383
|
return clampConfidence(confidenceForEdge(edge));
|
|
10100
10384
|
}
|
|
10101
10385
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
10102
|
-
|
|
10103
|
-
|
|
10104
|
-
|
|
10105
|
-
|
|
10386
|
+
import_types41.EdgeType.CALLS,
|
|
10387
|
+
import_types41.EdgeType.CONNECTS_TO,
|
|
10388
|
+
import_types41.EdgeType.PUBLISHES_TO,
|
|
10389
|
+
import_types41.EdgeType.CONSUMES_FROM
|
|
10106
10390
|
]);
|
|
10107
10391
|
function detectMissingDivergences(graph, bucket) {
|
|
10108
10392
|
const out = [];
|
|
10109
|
-
if (bucket.type ===
|
|
10393
|
+
if (bucket.type === import_types41.EdgeType.CONTAINS) return out;
|
|
10110
10394
|
if (nodeIsSymbol(graph, bucket.source) || nodeIsSymbol(graph, bucket.target)) return out;
|
|
10111
10395
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
10112
10396
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
@@ -10148,7 +10432,7 @@ function declaredHostFor(svc) {
|
|
|
10148
10432
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
10149
10433
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
10150
10434
|
const e = graph.getEdgeAttributes(edgeId);
|
|
10151
|
-
if (e.type ===
|
|
10435
|
+
if (e.type === import_types41.EdgeType.CONFIGURED_BY && e.provenance === import_types41.Provenance.EXTRACTED) {
|
|
10152
10436
|
return true;
|
|
10153
10437
|
}
|
|
10154
10438
|
}
|
|
@@ -10161,10 +10445,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
10161
10445
|
const out = [];
|
|
10162
10446
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
10163
10447
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
10164
|
-
if (edge.type !==
|
|
10165
|
-
if (edge.provenance !==
|
|
10448
|
+
if (edge.type !== import_types41.EdgeType.CONNECTS_TO) continue;
|
|
10449
|
+
if (edge.provenance !== import_types41.Provenance.OBSERVED) continue;
|
|
10166
10450
|
const target = graph.getNodeAttributes(edge.target);
|
|
10167
|
-
if (target.type !==
|
|
10451
|
+
if (target.type !== import_types41.NodeType.DatabaseNode) continue;
|
|
10168
10452
|
const observedHost = target.host?.trim();
|
|
10169
10453
|
if (!observedHost) continue;
|
|
10170
10454
|
if (observedHost === declaredHost) continue;
|
|
@@ -10186,10 +10470,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
10186
10470
|
const deps = svc.dependencies ?? {};
|
|
10187
10471
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
10188
10472
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
10189
|
-
if (edge.type !==
|
|
10190
|
-
if (edge.provenance !==
|
|
10473
|
+
if (edge.type !== import_types41.EdgeType.CONNECTS_TO) continue;
|
|
10474
|
+
if (edge.provenance !== import_types41.Provenance.OBSERVED) continue;
|
|
10191
10475
|
const target = graph.getNodeAttributes(edge.target);
|
|
10192
|
-
if (target.type !==
|
|
10476
|
+
if (target.type !== import_types41.NodeType.DatabaseNode) continue;
|
|
10193
10477
|
for (const pair of compatPairs()) {
|
|
10194
10478
|
if (pair.engine !== target.engine) continue;
|
|
10195
10479
|
const declared = deps[pair.driver];
|
|
@@ -10286,7 +10570,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
10286
10570
|
for (const d of all) {
|
|
10287
10571
|
if (d.type !== "host-mismatch") continue;
|
|
10288
10572
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
10289
|
-
declaredHalf.add((0,
|
|
10573
|
+
declaredHalf.add((0, import_types41.databaseId)(d.extractedHost));
|
|
10290
10574
|
}
|
|
10291
10575
|
if (observedHalf.size === 0) return all;
|
|
10292
10576
|
return all.filter((d) => {
|
|
@@ -10305,13 +10589,13 @@ function computeDivergences(graph, opts = {}) {
|
|
|
10305
10589
|
}
|
|
10306
10590
|
graph.forEachNode((nodeId, attrs) => {
|
|
10307
10591
|
const n = attrs;
|
|
10308
|
-
if (n.type ===
|
|
10592
|
+
if (n.type === import_types41.NodeType.ServiceNode) {
|
|
10309
10593
|
const svc = n;
|
|
10310
10594
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
10311
10595
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
10312
10596
|
return;
|
|
10313
10597
|
}
|
|
10314
|
-
if (n.type ===
|
|
10598
|
+
if (n.type === import_types41.NodeType.InfraNode && n.kind === "sql-table") {
|
|
10315
10599
|
for (const d of detectColumnDrift(n)) all.push(d);
|
|
10316
10600
|
}
|
|
10317
10601
|
});
|
|
@@ -10347,7 +10631,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
10347
10631
|
const bc = "column" in b && b.column ? b.column : "";
|
|
10348
10632
|
return ac.localeCompare(bc);
|
|
10349
10633
|
});
|
|
10350
|
-
return
|
|
10634
|
+
return import_types41.DivergenceResultSchema.parse({
|
|
10351
10635
|
divergences: filtered,
|
|
10352
10636
|
totalAffected: filtered.length,
|
|
10353
10637
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -10358,7 +10642,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
10358
10642
|
init_cjs_shims();
|
|
10359
10643
|
var import_node_fs26 = require("fs");
|
|
10360
10644
|
var import_node_path53 = __toESM(require("path"), 1);
|
|
10361
|
-
var
|
|
10645
|
+
var import_types42 = require("@neat.is/types");
|
|
10362
10646
|
var SCHEMA_VERSION = 6;
|
|
10363
10647
|
function migrateV1ToV2(payload) {
|
|
10364
10648
|
const nodes = payload.graph.nodes;
|
|
@@ -10382,7 +10666,7 @@ function migrateV5ToV6(payload) {
|
|
|
10382
10666
|
if (Array.isArray(nodes)) {
|
|
10383
10667
|
for (const node of nodes) {
|
|
10384
10668
|
const attrs = node.attributes;
|
|
10385
|
-
if (!attrs || attrs.type !==
|
|
10669
|
+
if (!attrs || attrs.type !== import_types42.NodeType.InfraNode) continue;
|
|
10386
10670
|
if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
|
|
10387
10671
|
if (!Array.isArray(attrs.columns)) attrs.columns = [];
|
|
10388
10672
|
}
|
|
@@ -10395,12 +10679,12 @@ function migrateV2ToV3(payload) {
|
|
|
10395
10679
|
for (const edge of edges) {
|
|
10396
10680
|
const attrs = edge.attributes;
|
|
10397
10681
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
10398
|
-
attrs.provenance =
|
|
10682
|
+
attrs.provenance = import_types42.Provenance.OBSERVED;
|
|
10399
10683
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
10400
10684
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
10401
10685
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
10402
10686
|
if (type && source && target) {
|
|
10403
|
-
const newId = (0,
|
|
10687
|
+
const newId = (0, import_types42.observedEdgeId)(source, target, type);
|
|
10404
10688
|
attrs.id = newId;
|
|
10405
10689
|
if (edge.key) edge.key = newId;
|
|
10406
10690
|
}
|
|
@@ -10532,7 +10816,7 @@ ${NEAT_OUT_LINE}
|
|
|
10532
10816
|
|
|
10533
10817
|
// src/summary.ts
|
|
10534
10818
|
init_cjs_shims();
|
|
10535
|
-
var
|
|
10819
|
+
var import_types43 = require("@neat.is/types");
|
|
10536
10820
|
function renderOtelEnvBlock() {
|
|
10537
10821
|
return [
|
|
10538
10822
|
"for prod OTel routing, set these in your deploy platform's env:",
|
|
@@ -10542,19 +10826,19 @@ function renderOtelEnvBlock() {
|
|
|
10542
10826
|
}
|
|
10543
10827
|
function findIncompatServices(nodes) {
|
|
10544
10828
|
return nodes.filter(
|
|
10545
|
-
(n) => n.type ===
|
|
10829
|
+
(n) => n.type === import_types43.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
|
|
10546
10830
|
);
|
|
10547
10831
|
}
|
|
10548
10832
|
function servicesWithoutObserved(nodes, edges) {
|
|
10549
10833
|
const seen = /* @__PURE__ */ new Set();
|
|
10550
10834
|
for (const e of edges) {
|
|
10551
|
-
if (e.provenance ===
|
|
10835
|
+
if (e.provenance === import_types43.Provenance.OBSERVED) {
|
|
10552
10836
|
seen.add(e.source);
|
|
10553
10837
|
seen.add(e.target);
|
|
10554
10838
|
}
|
|
10555
10839
|
}
|
|
10556
10840
|
return nodes.filter(
|
|
10557
|
-
(n) => n.type ===
|
|
10841
|
+
(n) => n.type === import_types43.NodeType.ServiceNode && !seen.has(n.id)
|
|
10558
10842
|
);
|
|
10559
10843
|
}
|
|
10560
10844
|
function formatDivergence(d) {
|
|
@@ -10636,7 +10920,7 @@ var import_chokidar = __toESM(require("chokidar"), 1);
|
|
|
10636
10920
|
init_cjs_shims();
|
|
10637
10921
|
var import_fastify2 = __toESM(require("fastify"), 1);
|
|
10638
10922
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
10639
|
-
var
|
|
10923
|
+
var import_types67 = require("@neat.is/types");
|
|
10640
10924
|
|
|
10641
10925
|
// src/extend/index.ts
|
|
10642
10926
|
init_cjs_shims();
|
|
@@ -11160,7 +11444,7 @@ init_cjs_shims();
|
|
|
11160
11444
|
var import_node_fs31 = require("fs");
|
|
11161
11445
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
11162
11446
|
var import_node_path58 = __toESM(require("path"), 1);
|
|
11163
|
-
var
|
|
11447
|
+
var import_types44 = require("@neat.is/types");
|
|
11164
11448
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
11165
11449
|
var LOCK_RETRY_MS = 50;
|
|
11166
11450
|
function neatHome() {
|
|
@@ -11414,10 +11698,10 @@ async function readRegistry() {
|
|
|
11414
11698
|
throw err;
|
|
11415
11699
|
}
|
|
11416
11700
|
const parsed = JSON.parse(raw);
|
|
11417
|
-
return
|
|
11701
|
+
return import_types44.RegistryFileSchema.parse(parsed);
|
|
11418
11702
|
}
|
|
11419
11703
|
async function writeRegistry(reg) {
|
|
11420
|
-
const validated =
|
|
11704
|
+
const validated = import_types44.RegistryFileSchema.parse(reg);
|
|
11421
11705
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
11422
11706
|
}
|
|
11423
11707
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -11930,15 +12214,15 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
11930
12214
|
|
|
11931
12215
|
// src/connectors/index.ts
|
|
11932
12216
|
init_cjs_shims();
|
|
11933
|
-
var
|
|
12217
|
+
var import_types45 = require("@neat.is/types");
|
|
11934
12218
|
var NO_ENV = "unknown";
|
|
11935
12219
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
11936
12220
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
11937
12221
|
const sites = [];
|
|
11938
12222
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
11939
12223
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
11940
|
-
if (edge.provenance !==
|
|
11941
|
-
const parsed = (0,
|
|
12224
|
+
if (edge.provenance !== import_types45.Provenance.EXTRACTED) continue;
|
|
12225
|
+
const parsed = (0, import_types45.parseFileId)(edge.source);
|
|
11942
12226
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
11943
12227
|
const site = { relPath: edge.evidence.file };
|
|
11944
12228
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -11949,7 +12233,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
11949
12233
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
11950
12234
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
11951
12235
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
11952
|
-
if (attrs.type !==
|
|
12236
|
+
if (attrs.type !== import_types45.NodeType.RouteNode || !attrs.path) return void 0;
|
|
11953
12237
|
const site = { relPath: attrs.path };
|
|
11954
12238
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
11955
12239
|
return site;
|
|
@@ -12133,7 +12417,11 @@ var JUNCTION_DEFAULT_RATE_LIMITS = {
|
|
|
12133
12417
|
// connector add/remove/test` (provision/deprovision/validate), never a poll
|
|
12134
12418
|
// loop, so this bucket is exercised a handful of times per command. Kept
|
|
12135
12419
|
// conservative pending a documented Drains-API rate limit.
|
|
12136
|
-
vercel: { capacity: 20, refillMs: 5e3 }
|
|
12420
|
+
vercel: { capacity: 20, refillMs: 5e3 },
|
|
12421
|
+
// Render's REST API (api-docs.render.com/reference/rate-limiting) isn't
|
|
12422
|
+
// pinned here to a single confirmed number — this is a conservative
|
|
12423
|
+
// placeholder pending a live project, matching the other pull providers.
|
|
12424
|
+
render: { capacity: 30, refillMs: 1e4 }
|
|
12137
12425
|
};
|
|
12138
12426
|
var JUNCTION_GENERIC_RATE_LIMIT = { capacity: 20, refillMs: 5e3 };
|
|
12139
12427
|
function defaultRateLimitFor(provider) {
|
|
@@ -12426,10 +12714,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
12426
12714
|
// src/connectors/supabase/map.ts
|
|
12427
12715
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
12428
12716
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
12429
|
-
function targetFromRestPath(
|
|
12430
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
12717
|
+
function targetFromRestPath(path74) {
|
|
12718
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path74);
|
|
12431
12719
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
12432
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
12720
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path74);
|
|
12433
12721
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
12434
12722
|
return null;
|
|
12435
12723
|
}
|
|
@@ -12540,23 +12828,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
12540
12828
|
|
|
12541
12829
|
// src/connectors/supabase/resolve.ts
|
|
12542
12830
|
init_cjs_shims();
|
|
12543
|
-
var
|
|
12831
|
+
var import_types47 = require("@neat.is/types");
|
|
12544
12832
|
function createSupabaseResolveTarget(graph, config) {
|
|
12545
12833
|
return (signal, _ctx) => {
|
|
12546
12834
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
12547
12835
|
return null;
|
|
12548
12836
|
}
|
|
12549
|
-
const subResourceId = (0,
|
|
12837
|
+
const subResourceId = (0, import_types47.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
12550
12838
|
if (graph.hasNode(subResourceId)) {
|
|
12551
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
12839
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types47.EdgeType.CALLS };
|
|
12552
12840
|
}
|
|
12553
|
-
const bareResourceId = (0,
|
|
12841
|
+
const bareResourceId = (0, import_types47.infraId)(signal.targetKind, signal.targetName);
|
|
12554
12842
|
if (graph.hasNode(bareResourceId)) {
|
|
12555
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
12843
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types47.EdgeType.CALLS };
|
|
12556
12844
|
}
|
|
12557
|
-
const projectLevelId = (0,
|
|
12845
|
+
const projectLevelId = (0, import_types47.infraId)("supabase", config.nodeRef);
|
|
12558
12846
|
if (graph.hasNode(projectLevelId)) {
|
|
12559
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
12847
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types47.EdgeType.CALLS };
|
|
12560
12848
|
}
|
|
12561
12849
|
return null;
|
|
12562
12850
|
};
|
|
@@ -12649,7 +12937,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
12649
12937
|
|
|
12650
12938
|
// src/connectors/railway/index.ts
|
|
12651
12939
|
init_cjs_shims();
|
|
12652
|
-
var
|
|
12940
|
+
var import_types51 = require("@neat.is/types");
|
|
12653
12941
|
|
|
12654
12942
|
// src/connectors/railway/client.ts
|
|
12655
12943
|
init_cjs_shims();
|
|
@@ -12800,7 +13088,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
12800
13088
|
const out = [];
|
|
12801
13089
|
graph.forEachNode((_id, attrs) => {
|
|
12802
13090
|
const node = attrs;
|
|
12803
|
-
if (node.type !==
|
|
13091
|
+
if (node.type !== import_types51.NodeType.RouteNode) return;
|
|
12804
13092
|
const route = attrs;
|
|
12805
13093
|
if (route.service !== serviceName) return;
|
|
12806
13094
|
out.push({
|
|
@@ -12904,12 +13192,12 @@ function createRailwayResolveTarget(config) {
|
|
|
12904
13192
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
12905
13193
|
if (!serviceName) return null;
|
|
12906
13194
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
12907
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
13195
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types51.EdgeType.CALLS };
|
|
12908
13196
|
}
|
|
12909
13197
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
12910
13198
|
const peerName = config.serviceNameById[signal.targetName];
|
|
12911
13199
|
if (!peerName) return null;
|
|
12912
|
-
return { targetNodeId: (0,
|
|
13200
|
+
return { targetNodeId: (0, import_types51.serviceId)(peerName), serviceName, edgeType: import_types51.EdgeType.CONNECTS_TO };
|
|
12913
13201
|
}
|
|
12914
13202
|
return null;
|
|
12915
13203
|
};
|
|
@@ -13033,9 +13321,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
13033
13321
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
13034
13322
|
if (secondSep === -1) return null;
|
|
13035
13323
|
const method = rest.slice(0, secondSep);
|
|
13036
|
-
const
|
|
13037
|
-
if (!resourceName || !method || !
|
|
13038
|
-
return { resourceName, method, path:
|
|
13324
|
+
const path74 = rest.slice(secondSep + 1);
|
|
13325
|
+
if (!resourceName || !method || !path74) return null;
|
|
13326
|
+
return { resourceName, method, path: path74 };
|
|
13039
13327
|
}
|
|
13040
13328
|
function resourceNameFor(type, labels) {
|
|
13041
13329
|
if (!labels) return null;
|
|
@@ -13073,14 +13361,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
13073
13361
|
if (!req) return null;
|
|
13074
13362
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
13075
13363
|
const method = req.requestMethod.toUpperCase();
|
|
13076
|
-
const
|
|
13077
|
-
if (
|
|
13364
|
+
const path74 = pathFromRequestUrl(req.requestUrl);
|
|
13365
|
+
if (path74 === null) return null;
|
|
13078
13366
|
const timestamp = entry2.timestamp;
|
|
13079
13367
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
13080
13368
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
13081
13369
|
return {
|
|
13082
13370
|
targetKind: resourceType,
|
|
13083
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
13371
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path74 }),
|
|
13084
13372
|
callCount: 1,
|
|
13085
13373
|
errorCount: isError ? 1 : 0,
|
|
13086
13374
|
lastObservedIso: timestamp
|
|
@@ -13097,7 +13385,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
13097
13385
|
|
|
13098
13386
|
// src/connectors/firebase/resolve.ts
|
|
13099
13387
|
init_cjs_shims();
|
|
13100
|
-
var
|
|
13388
|
+
var import_types52 = require("@neat.is/types");
|
|
13101
13389
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
13102
13390
|
switch (resourceType) {
|
|
13103
13391
|
case "cloud_function":
|
|
@@ -13112,7 +13400,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
13112
13400
|
const entries = [];
|
|
13113
13401
|
graph.forEachNode((_id, attrs) => {
|
|
13114
13402
|
const node = attrs;
|
|
13115
|
-
if (node.type !==
|
|
13403
|
+
if (node.type !== import_types52.NodeType.RouteNode) return;
|
|
13116
13404
|
const route = attrs;
|
|
13117
13405
|
if (route.service !== serviceName) return;
|
|
13118
13406
|
entries.push({
|
|
@@ -13144,7 +13432,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
13144
13432
|
return {
|
|
13145
13433
|
targetNodeId: match.routeNodeId,
|
|
13146
13434
|
serviceName,
|
|
13147
|
-
edgeType:
|
|
13435
|
+
edgeType: import_types52.EdgeType.CALLS
|
|
13148
13436
|
};
|
|
13149
13437
|
};
|
|
13150
13438
|
}
|
|
@@ -13171,7 +13459,7 @@ init_cjs_shims();
|
|
|
13171
13459
|
|
|
13172
13460
|
// src/connectors/cloudflare/connector.ts
|
|
13173
13461
|
init_cjs_shims();
|
|
13174
|
-
var
|
|
13462
|
+
var import_types54 = require("@neat.is/types");
|
|
13175
13463
|
|
|
13176
13464
|
// src/connectors/cloudflare/client.ts
|
|
13177
13465
|
init_cjs_shims();
|
|
@@ -13287,7 +13575,7 @@ function mapEventToSignal(event) {
|
|
|
13287
13575
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
13288
13576
|
const statusCode = metadata?.statusCode;
|
|
13289
13577
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
13290
|
-
const
|
|
13578
|
+
const path74 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
13291
13579
|
return {
|
|
13292
13580
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
13293
13581
|
targetName: scriptName,
|
|
@@ -13295,7 +13583,7 @@ function mapEventToSignal(event) {
|
|
|
13295
13583
|
errorCount: isError ? 1 : 0,
|
|
13296
13584
|
lastObservedIso: observedAt.toISOString(),
|
|
13297
13585
|
method,
|
|
13298
|
-
...
|
|
13586
|
+
...path74 ? { path: path74 } : {},
|
|
13299
13587
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
13300
13588
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
13301
13589
|
};
|
|
@@ -13335,19 +13623,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
13335
13623
|
graph.forEachNode((id, attrs) => {
|
|
13336
13624
|
if (found) return;
|
|
13337
13625
|
const a = attrs;
|
|
13338
|
-
if (a.type ===
|
|
13626
|
+
if (a.type === import_types54.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
13339
13627
|
found = id;
|
|
13340
13628
|
}
|
|
13341
13629
|
});
|
|
13342
13630
|
return found;
|
|
13343
13631
|
}
|
|
13344
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
13345
|
-
const normalizedPath = normalizePathTemplate(
|
|
13632
|
+
function findMatchingRouteNode(graph, serviceName, method, path74) {
|
|
13633
|
+
const normalizedPath = normalizePathTemplate(path74);
|
|
13346
13634
|
let found = null;
|
|
13347
13635
|
graph.forEachNode((id, attrs) => {
|
|
13348
13636
|
if (found) return;
|
|
13349
13637
|
const a = attrs;
|
|
13350
|
-
if (a.type !==
|
|
13638
|
+
if (a.type !== import_types54.NodeType.RouteNode || a.service !== serviceName) return;
|
|
13351
13639
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
13352
13640
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
13353
13641
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -13359,18 +13647,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
13359
13647
|
return (signal) => {
|
|
13360
13648
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
13361
13649
|
const scriptName = signal.targetName;
|
|
13362
|
-
const { method, path:
|
|
13650
|
+
const { method, path: path74 } = signal;
|
|
13363
13651
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
13364
|
-
if (!method || !
|
|
13365
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
13652
|
+
if (!method || !path74) return wholeFileId;
|
|
13653
|
+
return findMatchingRouteNode(graph, serviceName, method, path74) ?? wholeFileId;
|
|
13366
13654
|
};
|
|
13367
13655
|
const mapping = config.workers?.[scriptName];
|
|
13368
13656
|
if (mapping) {
|
|
13369
|
-
const wholeFileId = (0,
|
|
13657
|
+
const wholeFileId = (0, import_types54.fileId)(mapping.service, mapping.entryFile);
|
|
13370
13658
|
return {
|
|
13371
13659
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
13372
13660
|
serviceName: mapping.service,
|
|
13373
|
-
edgeType:
|
|
13661
|
+
edgeType: import_types54.EdgeType.CALLS
|
|
13374
13662
|
};
|
|
13375
13663
|
}
|
|
13376
13664
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -13379,13 +13667,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
13379
13667
|
return {
|
|
13380
13668
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
13381
13669
|
serviceName: fileNode.service,
|
|
13382
|
-
edgeType:
|
|
13670
|
+
edgeType: import_types54.EdgeType.CALLS
|
|
13383
13671
|
};
|
|
13384
13672
|
}
|
|
13385
13673
|
return {
|
|
13386
|
-
targetNodeId: (0,
|
|
13674
|
+
targetNodeId: (0, import_types54.infraId)("cloudflare-worker", scriptName),
|
|
13387
13675
|
serviceName: scriptName,
|
|
13388
|
-
edgeType:
|
|
13676
|
+
edgeType: import_types54.EdgeType.CALLS,
|
|
13389
13677
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
13390
13678
|
};
|
|
13391
13679
|
};
|
|
@@ -13581,14 +13869,14 @@ function diffNeonStatementsToSignals(rows, previous, observedAtIso) {
|
|
|
13581
13869
|
|
|
13582
13870
|
// src/connectors/neon/resolve.ts
|
|
13583
13871
|
init_cjs_shims();
|
|
13584
|
-
var
|
|
13872
|
+
var import_types58 = require("@neat.is/types");
|
|
13585
13873
|
function createNeonResolveTarget(config) {
|
|
13586
13874
|
return (signal) => {
|
|
13587
13875
|
if (signal.targetKind !== NEON_SQL_TABLE_TARGET_KIND || !signal.targetName) return null;
|
|
13588
13876
|
return {
|
|
13589
|
-
targetNodeId: (0,
|
|
13877
|
+
targetNodeId: (0, import_types58.infraId)("sql-table", signal.targetName),
|
|
13590
13878
|
serviceName: config.serviceName,
|
|
13591
|
-
edgeType:
|
|
13879
|
+
edgeType: import_types58.EdgeType.CALLS,
|
|
13592
13880
|
ensureInfraNode: { kind: "sql-table", name: signal.targetName, provider: "neon" }
|
|
13593
13881
|
};
|
|
13594
13882
|
};
|
|
@@ -13626,45 +13914,451 @@ function createNeonConnector(config, deps = {}) {
|
|
|
13626
13914
|
};
|
|
13627
13915
|
}
|
|
13628
13916
|
|
|
13629
|
-
// src/connectors/
|
|
13630
|
-
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13917
|
+
// src/connectors/cloud-run/index.ts
|
|
13918
|
+
init_cjs_shims();
|
|
13919
|
+
|
|
13920
|
+
// src/connectors/cloud-run/client.ts
|
|
13921
|
+
init_cjs_shims();
|
|
13922
|
+
function cloudRunRequestLogName(projectId) {
|
|
13923
|
+
return `projects/${projectId}/logs/run.googleapis.com%2Frequests`;
|
|
13924
|
+
}
|
|
13925
|
+
function buildCloudRunEntriesFilter(projectId, sinceIso) {
|
|
13926
|
+
return [
|
|
13927
|
+
`logName = "${cloudRunRequestLogName(projectId)}"`,
|
|
13928
|
+
`resource.type = "${CLOUD_RUN_RESOURCE_TYPE}"`,
|
|
13929
|
+
'httpRequest.requestMethod != ""',
|
|
13930
|
+
`timestamp >= "${sinceIso}"`
|
|
13931
|
+
].join(" AND ");
|
|
13932
|
+
}
|
|
13933
|
+
var CLOUD_RUN_RESOURCE_TYPE = "cloud_run_revision";
|
|
13934
|
+
var DEFAULT_LOOKBACK_MS2 = 24 * 60 * 60 * 1e3;
|
|
13935
|
+
var ENTRIES_LIST_URL2 = "https://logging.googleapis.com/v2/entries:list";
|
|
13936
|
+
var PAGE_SIZE2 = 1e3;
|
|
13937
|
+
var MAX_PAGES2 = 20;
|
|
13938
|
+
async function fetchCloudRunRequestLogEntries(creds, sinceIso, apiUrl = ENTRIES_LIST_URL2) {
|
|
13939
|
+
const filter = buildCloudRunEntriesFilter(creds.projectId, sinceIso);
|
|
13940
|
+
const out = [];
|
|
13941
|
+
let pageToken;
|
|
13942
|
+
for (let page = 0; page < MAX_PAGES2; page++) {
|
|
13943
|
+
const body = {
|
|
13944
|
+
resourceNames: [`projects/${creds.projectId}`],
|
|
13945
|
+
filter,
|
|
13946
|
+
orderBy: "timestamp asc",
|
|
13947
|
+
pageSize: PAGE_SIZE2,
|
|
13948
|
+
...pageToken ? { pageToken } : {}
|
|
13949
|
+
};
|
|
13634
13950
|
const res = await junctionFetch(
|
|
13635
|
-
|
|
13951
|
+
apiUrl,
|
|
13636
13952
|
{
|
|
13637
|
-
|
|
13638
|
-
headers: {
|
|
13953
|
+
method: "POST",
|
|
13954
|
+
headers: {
|
|
13955
|
+
...bearerAuthHeader(creds.accessToken),
|
|
13956
|
+
"Content-Type": "application/json"
|
|
13957
|
+
},
|
|
13958
|
+
body: JSON.stringify(body)
|
|
13639
13959
|
},
|
|
13640
|
-
|
|
13960
|
+
// accountKey: the GCP project id — one customer's Cloud Logging quota is
|
|
13961
|
+
// scoped per GCP project (ADR-131's per-(provider, accountKey) rate-limit
|
|
13962
|
+
// bucket), the same key Firebase's connector uses.
|
|
13963
|
+
{ provider: "cloud-run", accountKey: creds.projectId }
|
|
13641
13964
|
);
|
|
13642
|
-
if (res.ok)
|
|
13643
|
-
|
|
13644
|
-
return { ok: false, reason: `${provider} rejected the credential (HTTP ${res.status})` };
|
|
13965
|
+
if (!res.ok) {
|
|
13966
|
+
throw new Error(`Cloud Logging entries.list failed: ${res.status} ${res.statusText}`);
|
|
13645
13967
|
}
|
|
13646
|
-
|
|
13647
|
-
|
|
13648
|
-
|
|
13649
|
-
|
|
13650
|
-
} catch (err) {
|
|
13651
|
-
return {
|
|
13652
|
-
ok: false,
|
|
13653
|
-
reason: `${provider} auth check could not reach the provider: ${err.message}`
|
|
13654
|
-
};
|
|
13968
|
+
const json = await res.json();
|
|
13969
|
+
if (Array.isArray(json.entries)) out.push(...json.entries);
|
|
13970
|
+
if (!json.nextPageToken) break;
|
|
13971
|
+
pageToken = json.nextPageToken;
|
|
13655
13972
|
}
|
|
13973
|
+
return out;
|
|
13656
13974
|
}
|
|
13657
|
-
|
|
13658
|
-
|
|
13659
|
-
|
|
13660
|
-
|
|
13661
|
-
|
|
13662
|
-
|
|
13663
|
-
|
|
13664
|
-
|
|
13665
|
-
|
|
13666
|
-
|
|
13667
|
-
|
|
13975
|
+
|
|
13976
|
+
// src/connectors/cloud-run/map.ts
|
|
13977
|
+
init_cjs_shims();
|
|
13978
|
+
|
|
13979
|
+
// src/connectors/cloud-run/types.ts
|
|
13980
|
+
init_cjs_shims();
|
|
13981
|
+
function readCloudRunCredentials(raw) {
|
|
13982
|
+
const projectId = raw["projectId"];
|
|
13983
|
+
const accessToken = raw["accessToken"];
|
|
13984
|
+
if (typeof projectId !== "string" || projectId.length === 0) {
|
|
13985
|
+
throw new Error("cloud-run connector: credentials.projectId must be a non-empty string");
|
|
13986
|
+
}
|
|
13987
|
+
if (typeof accessToken !== "string" || accessToken.length === 0) {
|
|
13988
|
+
throw new Error("cloud-run connector: credentials.accessToken must be a non-empty string");
|
|
13989
|
+
}
|
|
13990
|
+
return { projectId, accessToken };
|
|
13991
|
+
}
|
|
13992
|
+
var CLOUD_RUN_TARGET_KIND = "cloud_run_revision";
|
|
13993
|
+
var FIELD_SEP2 = "\0";
|
|
13994
|
+
function packCloudRunTargetName(identity) {
|
|
13995
|
+
return [identity.serviceName, identity.method, identity.path].join(FIELD_SEP2);
|
|
13996
|
+
}
|
|
13997
|
+
function parseCloudRunTargetName(targetName) {
|
|
13998
|
+
const firstSep = targetName.indexOf(FIELD_SEP2);
|
|
13999
|
+
if (firstSep === -1) return null;
|
|
14000
|
+
const serviceName = targetName.slice(0, firstSep);
|
|
14001
|
+
const rest = targetName.slice(firstSep + 1);
|
|
14002
|
+
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
14003
|
+
if (secondSep === -1) return null;
|
|
14004
|
+
const method = rest.slice(0, secondSep);
|
|
14005
|
+
const path74 = rest.slice(secondSep + 1);
|
|
14006
|
+
if (!serviceName || !method || !path74) return null;
|
|
14007
|
+
return { serviceName, method, path: path74 };
|
|
14008
|
+
}
|
|
14009
|
+
|
|
14010
|
+
// src/connectors/cloud-run/map.ts
|
|
14011
|
+
var CLOUD_RUN_RESOURCE_TYPE2 = "cloud_run_revision";
|
|
14012
|
+
function pathFromRequestUrl2(requestUrl) {
|
|
14013
|
+
if (typeof requestUrl !== "string" || requestUrl.length === 0) return null;
|
|
14014
|
+
if (requestUrl.startsWith("/")) {
|
|
14015
|
+
const withoutQuery = requestUrl.split("?")[0];
|
|
14016
|
+
return withoutQuery && withoutQuery.length > 0 ? withoutQuery : "/";
|
|
14017
|
+
}
|
|
14018
|
+
try {
|
|
14019
|
+
const candidate = requestUrl.startsWith("//") ? `https:${requestUrl}` : requestUrl;
|
|
14020
|
+
const parsed = new URL(candidate);
|
|
14021
|
+
return parsed.pathname || "/";
|
|
14022
|
+
} catch {
|
|
14023
|
+
return null;
|
|
14024
|
+
}
|
|
14025
|
+
}
|
|
14026
|
+
var ERROR_STATUS_THRESHOLD4 = 500;
|
|
14027
|
+
function mapLogEntryToSignal2(entry2) {
|
|
14028
|
+
if (!entry2 || typeof entry2 !== "object") return null;
|
|
14029
|
+
if (entry2.resource?.type !== CLOUD_RUN_RESOURCE_TYPE2) return null;
|
|
14030
|
+
const serviceName = entry2.resource?.labels?.["service_name"];
|
|
14031
|
+
if (typeof serviceName !== "string" || serviceName.length === 0) return null;
|
|
14032
|
+
const req = entry2.httpRequest;
|
|
14033
|
+
if (!req) return null;
|
|
14034
|
+
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
14035
|
+
const method = req.requestMethod.toUpperCase();
|
|
14036
|
+
const path74 = pathFromRequestUrl2(req.requestUrl);
|
|
14037
|
+
if (path74 === null) return null;
|
|
14038
|
+
const timestamp = entry2.timestamp;
|
|
14039
|
+
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
14040
|
+
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
14041
|
+
return {
|
|
14042
|
+
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
14043
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path74 }),
|
|
14044
|
+
callCount: 1,
|
|
14045
|
+
errorCount: isError ? 1 : 0,
|
|
14046
|
+
lastObservedIso: timestamp
|
|
14047
|
+
};
|
|
14048
|
+
}
|
|
14049
|
+
function mapLogEntriesToSignals2(entries) {
|
|
14050
|
+
const out = [];
|
|
14051
|
+
for (const entry2 of entries) {
|
|
14052
|
+
const signal = mapLogEntryToSignal2(entry2);
|
|
14053
|
+
if (signal) out.push(signal);
|
|
14054
|
+
}
|
|
14055
|
+
return out;
|
|
14056
|
+
}
|
|
14057
|
+
|
|
14058
|
+
// src/connectors/cloud-run/resolve.ts
|
|
14059
|
+
init_cjs_shims();
|
|
14060
|
+
var import_types62 = require("@neat.is/types");
|
|
14061
|
+
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
14062
|
+
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
14063
|
+
let found = null;
|
|
14064
|
+
graph.forEachNode((_id, attrs) => {
|
|
14065
|
+
if (found) return;
|
|
14066
|
+
const node = attrs;
|
|
14067
|
+
if (node.type !== import_types62.NodeType.RouteNode) return;
|
|
14068
|
+
const route = attrs;
|
|
14069
|
+
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
14070
|
+
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
14071
|
+
const routeMethod = route.method.toUpperCase();
|
|
14072
|
+
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
14073
|
+
found = route.id;
|
|
14074
|
+
});
|
|
14075
|
+
return found;
|
|
14076
|
+
}
|
|
14077
|
+
function createCloudRunResolveTarget(graph, config) {
|
|
14078
|
+
return (signal) => {
|
|
14079
|
+
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
14080
|
+
const identity = parseCloudRunTargetName(signal.targetName);
|
|
14081
|
+
if (!identity) return null;
|
|
14082
|
+
const { serviceName: gcpServiceName, method, path: path74 } = identity;
|
|
14083
|
+
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
14084
|
+
if (mappedService) {
|
|
14085
|
+
const routeNodeId = findMatchingRouteNode2(
|
|
14086
|
+
graph,
|
|
14087
|
+
mappedService,
|
|
14088
|
+
method,
|
|
14089
|
+
normalizePathTemplate(path74)
|
|
14090
|
+
);
|
|
14091
|
+
if (routeNodeId) {
|
|
14092
|
+
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: import_types62.EdgeType.CALLS };
|
|
14093
|
+
}
|
|
14094
|
+
}
|
|
14095
|
+
return {
|
|
14096
|
+
targetNodeId: (0, import_types62.infraId)(CLOUD_RUN_SERVICE_INFRA_KIND, gcpServiceName),
|
|
14097
|
+
serviceName: mappedService ?? gcpServiceName,
|
|
14098
|
+
edgeType: import_types62.EdgeType.CALLS,
|
|
14099
|
+
ensureInfraNode: {
|
|
14100
|
+
kind: CLOUD_RUN_SERVICE_INFRA_KIND,
|
|
14101
|
+
name: gcpServiceName,
|
|
14102
|
+
provider: "cloud-run"
|
|
14103
|
+
}
|
|
14104
|
+
};
|
|
14105
|
+
};
|
|
14106
|
+
}
|
|
14107
|
+
|
|
14108
|
+
// src/connectors/cloud-run/index.ts
|
|
14109
|
+
var CloudRunConnector = class {
|
|
14110
|
+
constructor(config = {}) {
|
|
14111
|
+
this.config = config;
|
|
14112
|
+
}
|
|
14113
|
+
config;
|
|
14114
|
+
provider = "cloud-run";
|
|
14115
|
+
async poll(ctx) {
|
|
14116
|
+
const creds = readCloudRunCredentials(ctx.credentials);
|
|
14117
|
+
const maxLookbackMs = this.config.maxLookbackMs ?? DEFAULT_LOOKBACK_MS2;
|
|
14118
|
+
const sinceIso = boundedSinceIso(ctx.since, /* @__PURE__ */ new Date(), maxLookbackMs);
|
|
14119
|
+
const entries = await fetchCloudRunRequestLogEntries(creds, sinceIso, this.config.apiUrl);
|
|
14120
|
+
return mapLogEntriesToSignals2(entries);
|
|
14121
|
+
}
|
|
14122
|
+
};
|
|
14123
|
+
function boundedSinceIso(since, now, maxLookbackMs) {
|
|
14124
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
14125
|
+
if (!since) return floor.toISOString();
|
|
14126
|
+
const sinceMs = new Date(since).getTime();
|
|
14127
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
14128
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
14129
|
+
}
|
|
14130
|
+
function createCloudRunConnector(graph, config = {}) {
|
|
14131
|
+
return {
|
|
14132
|
+
connector: new CloudRunConnector(config),
|
|
14133
|
+
resolveTarget: createCloudRunResolveTarget(graph, config)
|
|
14134
|
+
};
|
|
14135
|
+
}
|
|
14136
|
+
|
|
14137
|
+
// src/connectors/render/index.ts
|
|
14138
|
+
init_cjs_shims();
|
|
14139
|
+
var import_types65 = require("@neat.is/types");
|
|
14140
|
+
|
|
14141
|
+
// src/connectors/render/types.ts
|
|
14142
|
+
init_cjs_shims();
|
|
14143
|
+
function readRenderToken(credentials) {
|
|
14144
|
+
const token = credentials.token;
|
|
14145
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
14146
|
+
throw new Error("Render connector requires ctx.credentials.token (a Render API key)");
|
|
14147
|
+
}
|
|
14148
|
+
return token;
|
|
14149
|
+
}
|
|
14150
|
+
function renderLabelValue(entry2, name) {
|
|
14151
|
+
if (!Array.isArray(entry2.labels)) return void 0;
|
|
14152
|
+
const label = entry2.labels.find((l) => l && typeof l === "object" && l.name === name);
|
|
14153
|
+
return label && typeof label.value === "string" ? label.value : void 0;
|
|
14154
|
+
}
|
|
14155
|
+
|
|
14156
|
+
// src/connectors/render/client.ts
|
|
14157
|
+
init_cjs_shims();
|
|
14158
|
+
var DEFAULT_RENDER_API_URL = "https://api.render.com/v1";
|
|
14159
|
+
var DEFAULT_RENDER_LOG_LIMIT = 100;
|
|
14160
|
+
var RENDER_MAX_LOG_LIMIT = 100;
|
|
14161
|
+
var DEFAULT_RENDER_MAX_PAGES = 20;
|
|
14162
|
+
var DEFAULT_MAX_LOOKBACK_MS4 = 24 * 60 * 60 * 1e3;
|
|
14163
|
+
function clampLimit(limit) {
|
|
14164
|
+
const raw = Math.trunc(limit ?? DEFAULT_RENDER_LOG_LIMIT);
|
|
14165
|
+
if (!Number.isFinite(raw) || raw < 1) return DEFAULT_RENDER_LOG_LIMIT;
|
|
14166
|
+
return Math.min(raw, RENDER_MAX_LOG_LIMIT);
|
|
14167
|
+
}
|
|
14168
|
+
function boundedRenderStartTime(since, now, maxLookbackMs) {
|
|
14169
|
+
const floor = new Date(now.getTime() - maxLookbackMs);
|
|
14170
|
+
if (!since) return floor.toISOString();
|
|
14171
|
+
const sinceMs = new Date(since).getTime();
|
|
14172
|
+
if (Number.isNaN(sinceMs)) return floor.toISOString();
|
|
14173
|
+
return sinceMs < floor.getTime() ? floor.toISOString() : new Date(sinceMs).toISOString();
|
|
14174
|
+
}
|
|
14175
|
+
async function fetchRenderLogPage(config, token, startTime, endTime, limit, fetchImpl) {
|
|
14176
|
+
const url = new URL(`${config.apiUrl ?? DEFAULT_RENDER_API_URL}/logs`);
|
|
14177
|
+
url.searchParams.set("ownerId", config.ownerId);
|
|
14178
|
+
url.searchParams.set("resource", config.resourceId);
|
|
14179
|
+
url.searchParams.set("type", "request");
|
|
14180
|
+
url.searchParams.set("startTime", startTime);
|
|
14181
|
+
url.searchParams.set("endTime", endTime);
|
|
14182
|
+
url.searchParams.set("direction", "backward");
|
|
14183
|
+
url.searchParams.set("limit", String(limit));
|
|
14184
|
+
const res = await junctionFetch(
|
|
14185
|
+
url,
|
|
14186
|
+
{ method: "GET", headers: { ...bearerAuthHeader(token) } },
|
|
14187
|
+
{ provider: "render", accountKey: config.ownerId, ...fetchImpl ? { fetchImpl } : {} }
|
|
14188
|
+
);
|
|
14189
|
+
if (!res.ok) {
|
|
14190
|
+
throw new Error(`Render logs request failed: ${res.status} ${res.statusText}`);
|
|
14191
|
+
}
|
|
14192
|
+
return await res.json();
|
|
14193
|
+
}
|
|
14194
|
+
async function fetchRenderRequestLogs(config, token, startTime, endTime, fetchImpl) {
|
|
14195
|
+
const limit = clampLimit(config.limit);
|
|
14196
|
+
const maxPages = Math.max(1, Math.trunc(config.maxPages ?? DEFAULT_RENDER_MAX_PAGES));
|
|
14197
|
+
const out = [];
|
|
14198
|
+
let pageStart = startTime;
|
|
14199
|
+
let pageEnd = endTime;
|
|
14200
|
+
for (let page = 0; page < maxPages; page++) {
|
|
14201
|
+
const body = await fetchRenderLogPage(config, token, pageStart, pageEnd, limit, fetchImpl);
|
|
14202
|
+
if (Array.isArray(body.logs)) out.push(...body.logs);
|
|
14203
|
+
if (!body.hasMore || !body.nextStartTime || !body.nextEndTime) break;
|
|
14204
|
+
pageStart = body.nextStartTime;
|
|
14205
|
+
pageEnd = body.nextEndTime;
|
|
14206
|
+
}
|
|
14207
|
+
return out;
|
|
14208
|
+
}
|
|
14209
|
+
|
|
14210
|
+
// src/connectors/render/index.ts
|
|
14211
|
+
var ROUTE_TARGET_KIND2 = "route";
|
|
14212
|
+
var UNMATCHED_ROUTE_TARGET_KIND2 = "unmatched-route";
|
|
14213
|
+
function buildRenderRouteIndex(graph, serviceName) {
|
|
14214
|
+
const out = [];
|
|
14215
|
+
graph.forEachNode((_id, attrs) => {
|
|
14216
|
+
const node = attrs;
|
|
14217
|
+
if (node.type !== import_types65.NodeType.RouteNode) return;
|
|
14218
|
+
const route = attrs;
|
|
14219
|
+
if (route.service !== serviceName) return;
|
|
14220
|
+
out.push({
|
|
14221
|
+
method: route.method.toUpperCase(),
|
|
14222
|
+
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
14223
|
+
routeNodeId: route.id,
|
|
14224
|
+
path: route.path,
|
|
14225
|
+
line: route.line
|
|
14226
|
+
});
|
|
14227
|
+
});
|
|
14228
|
+
return out;
|
|
14229
|
+
}
|
|
14230
|
+
function findRenderRoute(entries, method, normalizedPath) {
|
|
14231
|
+
return entries.find(
|
|
14232
|
+
(e) => e.normalizedPath === normalizedPath && (e.method === "ALL" || e.method === method)
|
|
14233
|
+
);
|
|
14234
|
+
}
|
|
14235
|
+
function bucketKey3(method, normalizedPath) {
|
|
14236
|
+
return `${method} ${normalizedPath}`;
|
|
14237
|
+
}
|
|
14238
|
+
function isHttpErrorStatus2(status2) {
|
|
14239
|
+
return status2 >= 400;
|
|
14240
|
+
}
|
|
14241
|
+
function upsertBucket2(buckets2, key, isError, timestamp, build) {
|
|
14242
|
+
const existing = buckets2.get(key);
|
|
14243
|
+
if (existing) {
|
|
14244
|
+
existing.callCount += 1;
|
|
14245
|
+
if (isError) existing.errorCount += 1;
|
|
14246
|
+
if (timestamp > existing.lastObservedIso) existing.lastObservedIso = timestamp;
|
|
14247
|
+
return;
|
|
14248
|
+
}
|
|
14249
|
+
buckets2.set(key, { callCount: 1, errorCount: isError ? 1 : 0, lastObservedIso: timestamp, ...build() });
|
|
14250
|
+
}
|
|
14251
|
+
function mapRenderRequestLogsToSignals(entries, routeIndex) {
|
|
14252
|
+
const buckets2 = /* @__PURE__ */ new Map();
|
|
14253
|
+
if (!Array.isArray(entries)) return [];
|
|
14254
|
+
for (const entry2 of entries) {
|
|
14255
|
+
if (!entry2 || typeof entry2 !== "object") continue;
|
|
14256
|
+
if (typeof entry2.timestamp !== "string") continue;
|
|
14257
|
+
const method = renderLabelValue(entry2, "method");
|
|
14258
|
+
const rawPath = renderLabelValue(entry2, "path");
|
|
14259
|
+
if (typeof method !== "string" || method.length === 0) continue;
|
|
14260
|
+
if (typeof rawPath !== "string" || rawPath.length === 0) continue;
|
|
14261
|
+
const methodUpper = method.toUpperCase();
|
|
14262
|
+
const pathOnly = rawPath.split("?")[0];
|
|
14263
|
+
const normalizedPath = normalizePathTemplate(pathOnly);
|
|
14264
|
+
const statusCode = Number.parseInt(renderLabelValue(entry2, "statusCode") ?? "", 10);
|
|
14265
|
+
const isError = Number.isFinite(statusCode) && isHttpErrorStatus2(statusCode);
|
|
14266
|
+
const match = findRenderRoute(routeIndex, methodUpper, normalizedPath);
|
|
14267
|
+
if (match) {
|
|
14268
|
+
upsertBucket2(buckets2, `route:${match.routeNodeId}`, isError, entry2.timestamp, () => ({
|
|
14269
|
+
targetKind: ROUTE_TARGET_KIND2,
|
|
14270
|
+
targetName: match.routeNodeId,
|
|
14271
|
+
// RouteNode.line is optional in the schema (packages/types/src/
|
|
14272
|
+
// nodes.ts) even though routes.ts always sets it today — skip the
|
|
14273
|
+
// callSite rather than fabricate a line when it's ever absent
|
|
14274
|
+
// (file-awareness.md §6).
|
|
14275
|
+
...match.line !== void 0 ? { callSite: { file: match.path, line: match.line } } : {}
|
|
14276
|
+
}));
|
|
14277
|
+
} else {
|
|
14278
|
+
upsertBucket2(
|
|
14279
|
+
buckets2,
|
|
14280
|
+
`unmatched:${bucketKey3(methodUpper, normalizedPath)}`,
|
|
14281
|
+
isError,
|
|
14282
|
+
entry2.timestamp,
|
|
14283
|
+
() => ({
|
|
14284
|
+
targetKind: UNMATCHED_ROUTE_TARGET_KIND2,
|
|
14285
|
+
targetName: bucketKey3(methodUpper, normalizedPath)
|
|
14286
|
+
})
|
|
14287
|
+
);
|
|
14288
|
+
}
|
|
14289
|
+
}
|
|
14290
|
+
return [...buckets2.values()].map((b) => ({
|
|
14291
|
+
targetKind: b.targetKind,
|
|
14292
|
+
targetName: b.targetName,
|
|
14293
|
+
callCount: b.callCount,
|
|
14294
|
+
errorCount: b.errorCount,
|
|
14295
|
+
lastObservedIso: b.lastObservedIso,
|
|
14296
|
+
...b.callSite ? { callSite: b.callSite } : {}
|
|
14297
|
+
}));
|
|
14298
|
+
}
|
|
14299
|
+
function createRenderResolveTarget(config) {
|
|
14300
|
+
return (signal) => {
|
|
14301
|
+
if (signal.targetKind === ROUTE_TARGET_KIND2) {
|
|
14302
|
+
return { targetNodeId: signal.targetName, serviceName: config.serviceName, edgeType: import_types65.EdgeType.CALLS };
|
|
14303
|
+
}
|
|
14304
|
+
return null;
|
|
14305
|
+
};
|
|
14306
|
+
}
|
|
14307
|
+
function createRenderConnector(graph, config) {
|
|
14308
|
+
return {
|
|
14309
|
+
provider: "render",
|
|
14310
|
+
async poll(ctx) {
|
|
14311
|
+
const token = readRenderToken(ctx.credentials);
|
|
14312
|
+
const now = /* @__PURE__ */ new Date();
|
|
14313
|
+
const maxLookbackMs = config.maxLookbackMs ?? DEFAULT_MAX_LOOKBACK_MS4;
|
|
14314
|
+
const startTime = boundedRenderStartTime(ctx.since, now, maxLookbackMs);
|
|
14315
|
+
const endTime = now.toISOString();
|
|
14316
|
+
const logs = await fetchRenderRequestLogs(config, token, startTime, endTime);
|
|
14317
|
+
const routeIndex = buildRenderRouteIndex(graph, config.serviceName);
|
|
14318
|
+
return mapRenderRequestLogsToSignals(logs, routeIndex);
|
|
14319
|
+
}
|
|
14320
|
+
};
|
|
14321
|
+
}
|
|
14322
|
+
|
|
14323
|
+
// src/connectors/registry.ts
|
|
14324
|
+
var CLOUDFLARE_API_BASE_URL = "https://api.cloudflare.com/client/v4";
|
|
14325
|
+
async function authProbe(input) {
|
|
14326
|
+
const { provider, accountKey, url, token, init, fetchImpl } = input;
|
|
14327
|
+
try {
|
|
14328
|
+
const res = await junctionFetch(
|
|
14329
|
+
url,
|
|
14330
|
+
{
|
|
14331
|
+
...init ?? {},
|
|
14332
|
+
headers: { ...bearerAuthHeader(token), ...init?.headers ?? {} }
|
|
14333
|
+
},
|
|
14334
|
+
{ provider, accountKey, ...fetchImpl ? { fetchImpl } : {} }
|
|
14335
|
+
);
|
|
14336
|
+
if (res.ok) return { ok: true };
|
|
14337
|
+
if (res.status === 401 || res.status === 403) {
|
|
14338
|
+
return { ok: false, reason: `${provider} rejected the credential (HTTP ${res.status})` };
|
|
14339
|
+
}
|
|
14340
|
+
return {
|
|
14341
|
+
ok: false,
|
|
14342
|
+
reason: `${provider} auth check returned HTTP ${res.status} ${res.statusText} \u2014 could not confirm the credential`
|
|
14343
|
+
};
|
|
14344
|
+
} catch (err) {
|
|
14345
|
+
return {
|
|
14346
|
+
ok: false,
|
|
14347
|
+
reason: `${provider} auth check could not reach the provider: ${err.message}`
|
|
14348
|
+
};
|
|
14349
|
+
}
|
|
14350
|
+
}
|
|
14351
|
+
var PROVIDER_DISPATCH = {
|
|
14352
|
+
supabase: {
|
|
14353
|
+
provider: "supabase",
|
|
14354
|
+
primaryCredentialKey: "managementToken",
|
|
14355
|
+
requiredCredentialFields: ["managementToken"],
|
|
14356
|
+
requiredOptionFields: ["apiProjectRef", "nodeRef", "serviceName"],
|
|
14357
|
+
build(graph, options) {
|
|
14358
|
+
return createSupabaseConnector(graph, options);
|
|
14359
|
+
},
|
|
14360
|
+
// GET /v1/projects — the Management API's own auth-gated list endpoint, the
|
|
14361
|
+
// cheapest confirmation the management token is live (the same surface
|
|
13668
14362
|
// client.ts polls, minus the heavy log query).
|
|
13669
14363
|
validate({ credentials, options, fetchImpl }) {
|
|
13670
14364
|
const cfg = options;
|
|
@@ -13801,6 +14495,71 @@ var PROVIDER_DISPATCH = {
|
|
|
13801
14495
|
return { ok: false, reason: `neon telemetry read failed: ${err.message}` };
|
|
13802
14496
|
}
|
|
13803
14497
|
}
|
|
14498
|
+
},
|
|
14499
|
+
"cloud-run": {
|
|
14500
|
+
provider: "cloud-run",
|
|
14501
|
+
// Cloud Run reads both projectId and accessToken from the credential; the
|
|
14502
|
+
// single-string form maps to the secret (the token), and the required-fields
|
|
14503
|
+
// check below catches a projectId that was never supplied.
|
|
14504
|
+
primaryCredentialKey: "accessToken",
|
|
14505
|
+
requiredCredentialFields: ["projectId", "accessToken"],
|
|
14506
|
+
requiredOptionFields: [],
|
|
14507
|
+
build(graph, options) {
|
|
14508
|
+
return createCloudRunConnector(graph, options);
|
|
14509
|
+
},
|
|
14510
|
+
// POST entries:list with pageSize 1 — the exact surface poll() reads, so the
|
|
14511
|
+
// probe checks the actual `logging.logEntries.list` permission the connector
|
|
14512
|
+
// needs. A GET on the lighter logs.list endpoint (as Firebase probes) would
|
|
14513
|
+
// instead check `logging.logs.list`, falsely rejecting a correctly-scoped
|
|
14514
|
+
// custom role that carries only `logging.logEntries.list` (the narrowest
|
|
14515
|
+
// grant docs/connectors/cloud-run.md documents) — the same false-negative
|
|
14516
|
+
// trap Railway's validate avoids by probing its real query. A 2xx means the
|
|
14517
|
+
// token can list log entries; 401/403 means the provider rejected it.
|
|
14518
|
+
validate({ credentials, fetchImpl }) {
|
|
14519
|
+
const projectId = String(credentials.projectId ?? "");
|
|
14520
|
+
return authProbe({
|
|
14521
|
+
provider: "cloud-run",
|
|
14522
|
+
accountKey: projectId || "validate",
|
|
14523
|
+
url: "https://logging.googleapis.com/v2/entries:list",
|
|
14524
|
+
token: String(credentials.accessToken ?? ""),
|
|
14525
|
+
init: {
|
|
14526
|
+
method: "POST",
|
|
14527
|
+
headers: { "Content-Type": "application/json" },
|
|
14528
|
+
body: JSON.stringify({ resourceNames: [`projects/${projectId}`], pageSize: 1 })
|
|
14529
|
+
},
|
|
14530
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
14531
|
+
});
|
|
14532
|
+
}
|
|
14533
|
+
},
|
|
14534
|
+
render: {
|
|
14535
|
+
provider: "render",
|
|
14536
|
+
primaryCredentialKey: "token",
|
|
14537
|
+
requiredCredentialFields: ["token"],
|
|
14538
|
+
requiredOptionFields: ["ownerId", "resourceId", "serviceName"],
|
|
14539
|
+
build(graph, options) {
|
|
14540
|
+
const config = options;
|
|
14541
|
+
return {
|
|
14542
|
+
connector: createRenderConnector(graph, config),
|
|
14543
|
+
resolveTarget: createRenderResolveTarget(config)
|
|
14544
|
+
};
|
|
14545
|
+
},
|
|
14546
|
+
// GET /v1/services?limit=1 — the cheapest read the Render API key
|
|
14547
|
+
// authenticates against (render.com/docs/api). Unlike Railway's GraphQL
|
|
14548
|
+
// gateway, Render is a plain REST API: a live key returns 2xx, a bad one a
|
|
14549
|
+
// 401/403, so authProbe's status-code check is a true verdict here. The
|
|
14550
|
+
// logs query itself also needs an ownerId + resource; `services` needs
|
|
14551
|
+
// neither and still fails 401 on a bad token, so it's the honest probe.
|
|
14552
|
+
validate({ credentials, options, fetchImpl }) {
|
|
14553
|
+
const cfg = options;
|
|
14554
|
+
const baseUrl = cfg.apiUrl ?? DEFAULT_RENDER_API_URL;
|
|
14555
|
+
return authProbe({
|
|
14556
|
+
provider: "render",
|
|
14557
|
+
accountKey: cfg.ownerId ?? "validate",
|
|
14558
|
+
url: `${baseUrl}/services?limit=1`,
|
|
14559
|
+
token: String(credentials.token ?? ""),
|
|
14560
|
+
...fetchImpl ? { fetchImpl } : {}
|
|
14561
|
+
});
|
|
14562
|
+
}
|
|
13804
14563
|
}
|
|
13805
14564
|
};
|
|
13806
14565
|
function vercelCredsFrom(credentials) {
|
|
@@ -14231,11 +14990,11 @@ function registerRoutes(scope, ctx) {
|
|
|
14231
14990
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
14232
14991
|
const parsed = [];
|
|
14233
14992
|
for (const c of candidates) {
|
|
14234
|
-
const r =
|
|
14993
|
+
const r = import_types67.DivergenceTypeSchema.safeParse(c);
|
|
14235
14994
|
if (!r.success) {
|
|
14236
14995
|
return reply.code(400).send({
|
|
14237
14996
|
error: `unknown divergence type "${c}"`,
|
|
14238
|
-
allowed:
|
|
14997
|
+
allowed: import_types67.DivergenceTypeSchema.options
|
|
14239
14998
|
});
|
|
14240
14999
|
}
|
|
14241
15000
|
parsed.push(r.data);
|
|
@@ -14544,7 +15303,7 @@ function registerRoutes(scope, ctx) {
|
|
|
14544
15303
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
14545
15304
|
let violations = await log.readAll();
|
|
14546
15305
|
if (req.query.severity) {
|
|
14547
|
-
const sev =
|
|
15306
|
+
const sev = import_types67.PolicySeveritySchema.safeParse(req.query.severity);
|
|
14548
15307
|
if (!sev.success) {
|
|
14549
15308
|
return reply.code(400).send({
|
|
14550
15309
|
error: "invalid severity",
|
|
@@ -14583,7 +15342,7 @@ function registerRoutes(scope, ctx) {
|
|
|
14583
15342
|
scope.post("/policies/check", async (req, reply) => {
|
|
14584
15343
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
14585
15344
|
if (!proj) return;
|
|
14586
|
-
const parsed =
|
|
15345
|
+
const parsed = import_types67.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
14587
15346
|
if (!parsed.success) {
|
|
14588
15347
|
return reply.code(400).send({
|
|
14589
15348
|
error: "invalid /policies/check body",
|
|
@@ -14916,7 +15675,7 @@ var import_node_fs33 = require("fs");
|
|
|
14916
15675
|
var import_node_path60 = __toESM(require("path"), 1);
|
|
14917
15676
|
|
|
14918
15677
|
// src/daemon.ts
|
|
14919
|
-
var
|
|
15678
|
+
var import_types68 = require("@neat.is/types");
|
|
14920
15679
|
function daemonJsonPath(scanPath) {
|
|
14921
15680
|
return import_node_path61.default.join(scanPath, "neat-out", "daemon.json");
|
|
14922
15681
|
}
|
|
@@ -19536,13 +20295,462 @@ async function runHooksCommand(args) {
|
|
|
19536
20295
|
}
|
|
19537
20296
|
}
|
|
19538
20297
|
|
|
20298
|
+
// src/codex-cli.ts
|
|
20299
|
+
init_cjs_shims();
|
|
20300
|
+
var import_node_path70 = __toESM(require("path"), 1);
|
|
20301
|
+
var import_node_os6 = __toESM(require("os"), 1);
|
|
20302
|
+
var import_node_fs43 = require("fs");
|
|
20303
|
+
var import_node_util = require("util");
|
|
20304
|
+
var import_smol_toml5 = require("smol-toml");
|
|
20305
|
+
var CODEX_MCP_SERVER = {
|
|
20306
|
+
command: "npx",
|
|
20307
|
+
args: ["-y", "@neat.is/mcp"],
|
|
20308
|
+
env: { NEAT_CORE_URL: "http://localhost:8080" }
|
|
20309
|
+
};
|
|
20310
|
+
var CODEX_NEAT_BLOCK = [
|
|
20311
|
+
"[mcp_servers.neat]",
|
|
20312
|
+
'command = "npx"',
|
|
20313
|
+
'args = ["-y", "@neat.is/mcp"]',
|
|
20314
|
+
'env = { NEAT_CORE_URL = "http://localhost:8080" }'
|
|
20315
|
+
].join("\n");
|
|
20316
|
+
var NEAT_GRAPH_FIRST_START = "<!-- neat:graph-first -->";
|
|
20317
|
+
var NEAT_GRAPH_FIRST_END = "<!-- /neat:graph-first -->";
|
|
20318
|
+
function codexConfigPath() {
|
|
20319
|
+
const override = process.env.NEAT_CODEX_CONFIG;
|
|
20320
|
+
if (override && override.length > 0) return import_node_path70.default.resolve(override);
|
|
20321
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? import_node_os6.default.homedir();
|
|
20322
|
+
return import_node_path70.default.join(home, ".codex", "config.toml");
|
|
20323
|
+
}
|
|
20324
|
+
function agentsFilePath() {
|
|
20325
|
+
const override = process.env.NEAT_CODEX_AGENTS;
|
|
20326
|
+
if (override && override.length > 0) return import_node_path70.default.resolve(override);
|
|
20327
|
+
return import_node_path70.default.join(process.cwd(), "AGENTS.md");
|
|
20328
|
+
}
|
|
20329
|
+
function isTableHeader(line) {
|
|
20330
|
+
return /^\s*\[\[?[^\]]+\]\]?\s*$/.test(line);
|
|
20331
|
+
}
|
|
20332
|
+
function tableName(line) {
|
|
20333
|
+
return line.trim().replace(/^\[\[?/, "").replace(/\]\]?$/, "").trim();
|
|
20334
|
+
}
|
|
20335
|
+
function isNeatHeader(line) {
|
|
20336
|
+
return isTableHeader(line) && tableName(line) === "mcp_servers.neat";
|
|
20337
|
+
}
|
|
20338
|
+
function isNeatChildHeader(line) {
|
|
20339
|
+
if (!isTableHeader(line)) return false;
|
|
20340
|
+
const name = tableName(line);
|
|
20341
|
+
return name === "mcp_servers.neat" || name.startsWith("mcp_servers.neat.");
|
|
20342
|
+
}
|
|
20343
|
+
function upsertCodexConfig(raw) {
|
|
20344
|
+
const trimmed = raw.trim();
|
|
20345
|
+
const parsed = trimmed.length > 0 ? (0, import_smol_toml5.parse)(raw) : {};
|
|
20346
|
+
const existingServers = parsed.mcp_servers ?? {};
|
|
20347
|
+
const spliced = spliceNeatBlock(raw);
|
|
20348
|
+
let text;
|
|
20349
|
+
if (verifyPreserved(raw, spliced, parsed)) {
|
|
20350
|
+
text = spliced;
|
|
20351
|
+
} else {
|
|
20352
|
+
const merged = {
|
|
20353
|
+
...parsed,
|
|
20354
|
+
mcp_servers: { ...existingServers, neat: CODEX_MCP_SERVER }
|
|
20355
|
+
};
|
|
20356
|
+
text = (0, import_smol_toml5.stringify)(merged);
|
|
20357
|
+
if (!text.endsWith("\n")) text += "\n";
|
|
20358
|
+
}
|
|
20359
|
+
return { text, changed: text !== raw };
|
|
20360
|
+
}
|
|
20361
|
+
function spliceNeatBlock(raw) {
|
|
20362
|
+
const block = CODEX_NEAT_BLOCK;
|
|
20363
|
+
const lines = raw.length > 0 ? raw.split("\n") : [];
|
|
20364
|
+
const start = lines.findIndex(isNeatHeader);
|
|
20365
|
+
if (start === -1) {
|
|
20366
|
+
const base = raw.replace(/\n+$/, "");
|
|
20367
|
+
return base.length > 0 ? `${base}
|
|
20368
|
+
|
|
20369
|
+
${block}
|
|
20370
|
+
` : `${block}
|
|
20371
|
+
`;
|
|
20372
|
+
}
|
|
20373
|
+
let end = start + 1;
|
|
20374
|
+
for (; ; ) {
|
|
20375
|
+
while (end < lines.length && !isTableHeader(lines[end])) end++;
|
|
20376
|
+
if (end < lines.length && isNeatChildHeader(lines[end])) {
|
|
20377
|
+
end++;
|
|
20378
|
+
continue;
|
|
20379
|
+
}
|
|
20380
|
+
break;
|
|
20381
|
+
}
|
|
20382
|
+
const before = lines.slice(0, start).join("\n").replace(/\n*$/, "");
|
|
20383
|
+
const after = lines.slice(end).join("\n").replace(/^\n*/, "");
|
|
20384
|
+
let text = "";
|
|
20385
|
+
if (before.length > 0) text += `${before}
|
|
20386
|
+
|
|
20387
|
+
`;
|
|
20388
|
+
text += `${block}
|
|
20389
|
+
`;
|
|
20390
|
+
if (after.length > 0) text += `
|
|
20391
|
+
${after}`;
|
|
20392
|
+
return `${text.replace(/\n*$/, "")}
|
|
20393
|
+
`;
|
|
20394
|
+
}
|
|
20395
|
+
function verifyPreserved(raw, spliced, original) {
|
|
20396
|
+
let next;
|
|
20397
|
+
try {
|
|
20398
|
+
next = (0, import_smol_toml5.parse)(spliced);
|
|
20399
|
+
} catch {
|
|
20400
|
+
return false;
|
|
20401
|
+
}
|
|
20402
|
+
const origServers = original.mcp_servers ?? {};
|
|
20403
|
+
const nextServers = next.mcp_servers ?? {};
|
|
20404
|
+
if (!(0, import_node_util.isDeepStrictEqual)(nextServers.neat, CODEX_MCP_SERVER)) return false;
|
|
20405
|
+
for (const name of Object.keys(origServers)) {
|
|
20406
|
+
if (name === "neat") continue;
|
|
20407
|
+
if (!(0, import_node_util.isDeepStrictEqual)(nextServers[name], origServers[name])) return false;
|
|
20408
|
+
}
|
|
20409
|
+
for (const name of Object.keys(nextServers)) {
|
|
20410
|
+
if (name !== "neat" && !(name in origServers)) return false;
|
|
20411
|
+
}
|
|
20412
|
+
for (const key of Object.keys(original)) {
|
|
20413
|
+
if (key === "mcp_servers") continue;
|
|
20414
|
+
if (!(0, import_node_util.isDeepStrictEqual)(next[key], original[key])) return false;
|
|
20415
|
+
}
|
|
20416
|
+
for (const key of Object.keys(next)) {
|
|
20417
|
+
if (key !== "mcp_servers" && !(key in original)) return false;
|
|
20418
|
+
}
|
|
20419
|
+
return true;
|
|
20420
|
+
}
|
|
20421
|
+
function agentsBlock(guide) {
|
|
20422
|
+
return `${NEAT_GRAPH_FIRST_START}
|
|
20423
|
+
${guide.replace(/\s+$/, "")}
|
|
20424
|
+
${NEAT_GRAPH_FIRST_END}
|
|
20425
|
+
`;
|
|
20426
|
+
}
|
|
20427
|
+
function upsertAgents(raw, guide) {
|
|
20428
|
+
const block = agentsBlock(guide);
|
|
20429
|
+
if (raw.length === 0) return { text: block, changed: true };
|
|
20430
|
+
const startIdx = raw.indexOf(NEAT_GRAPH_FIRST_START);
|
|
20431
|
+
const endIdx = raw.indexOf(NEAT_GRAPH_FIRST_END);
|
|
20432
|
+
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
20433
|
+
const before = raw.slice(0, startIdx);
|
|
20434
|
+
const after = raw.slice(endIdx + NEAT_GRAPH_FIRST_END.length);
|
|
20435
|
+
const text2 = `${before}${block.replace(/\n+$/, "")}${after}`;
|
|
20436
|
+
return { text: text2, changed: text2 !== raw };
|
|
20437
|
+
}
|
|
20438
|
+
const base = raw.replace(/\n+$/, "");
|
|
20439
|
+
const text = base.length > 0 ? `${base}
|
|
20440
|
+
|
|
20441
|
+
${block}` : block;
|
|
20442
|
+
return { text, changed: text !== raw };
|
|
20443
|
+
}
|
|
20444
|
+
async function readGuide() {
|
|
20445
|
+
return readSkillAsset(GUIDE_FILENAME);
|
|
20446
|
+
}
|
|
20447
|
+
async function runCodex(opts) {
|
|
20448
|
+
if (opts.printConfig) {
|
|
20449
|
+
process.stdout.write(`${CODEX_NEAT_BLOCK}
|
|
20450
|
+
`);
|
|
20451
|
+
return { exitCode: 0 };
|
|
20452
|
+
}
|
|
20453
|
+
if (opts.printGuide) {
|
|
20454
|
+
process.stdout.write(agentsBlock(await readGuide()));
|
|
20455
|
+
return { exitCode: 0 };
|
|
20456
|
+
}
|
|
20457
|
+
const configPath = codexConfigPath();
|
|
20458
|
+
const agentsPath = agentsFilePath();
|
|
20459
|
+
let configRaw = "";
|
|
20460
|
+
try {
|
|
20461
|
+
configRaw = await import_node_fs43.promises.readFile(configPath, "utf8");
|
|
20462
|
+
} catch (err) {
|
|
20463
|
+
if (err.code !== "ENOENT") {
|
|
20464
|
+
console.error(`neat codex: failed to read ${configPath} \u2014 ${err.message}`);
|
|
20465
|
+
return { exitCode: 1 };
|
|
20466
|
+
}
|
|
20467
|
+
}
|
|
20468
|
+
let agentsRaw = "";
|
|
20469
|
+
try {
|
|
20470
|
+
agentsRaw = await import_node_fs43.promises.readFile(agentsPath, "utf8");
|
|
20471
|
+
} catch (err) {
|
|
20472
|
+
if (err.code !== "ENOENT") {
|
|
20473
|
+
console.error(`neat codex: failed to read ${agentsPath} \u2014 ${err.message}`);
|
|
20474
|
+
return { exitCode: 1 };
|
|
20475
|
+
}
|
|
20476
|
+
}
|
|
20477
|
+
let config;
|
|
20478
|
+
try {
|
|
20479
|
+
config = upsertCodexConfig(configRaw);
|
|
20480
|
+
} catch (err) {
|
|
20481
|
+
console.error(
|
|
20482
|
+
`neat codex: ${configPath} is not valid TOML \u2014 ${err.message}`
|
|
20483
|
+
);
|
|
20484
|
+
console.error("neat codex: fix the file and re-run; nothing was written.");
|
|
20485
|
+
return { exitCode: 1 };
|
|
20486
|
+
}
|
|
20487
|
+
const guide = await readGuide();
|
|
20488
|
+
const agents = upsertAgents(agentsRaw, guide);
|
|
20489
|
+
if (!opts.apply) {
|
|
20490
|
+
console.log("neat codex \u2014 plan (nothing written; re-run with --apply to write)");
|
|
20491
|
+
console.log("");
|
|
20492
|
+
console.log(` Codex MCP config: ${configPath}`);
|
|
20493
|
+
console.log(
|
|
20494
|
+
config.changed ? ` ${configRaw ? "update" : "create"} the [mcp_servers.neat] table:` : " already up to date \u2014 [mcp_servers.neat] matches"
|
|
20495
|
+
);
|
|
20496
|
+
if (config.changed) {
|
|
20497
|
+
for (const line of CODEX_NEAT_BLOCK.split("\n")) console.log(` ${line}`);
|
|
20498
|
+
}
|
|
20499
|
+
console.log("");
|
|
20500
|
+
console.log(` Project instructions: ${agentsPath}`);
|
|
20501
|
+
console.log(
|
|
20502
|
+
agents.changed ? ` ${agentsRaw ? "update" : "create"} the graph-first block (between ${NEAT_GRAPH_FIRST_START} markers)` : " already up to date \u2014 graph-first block matches"
|
|
20503
|
+
);
|
|
20504
|
+
console.log("");
|
|
20505
|
+
console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 edit that value in");
|
|
20506
|
+
console.log("the generated table to point Codex at a non-default daemon.");
|
|
20507
|
+
return { exitCode: 0 };
|
|
20508
|
+
}
|
|
20509
|
+
if (config.changed) {
|
|
20510
|
+
await import_node_fs43.promises.mkdir(import_node_path70.default.dirname(configPath), { recursive: true });
|
|
20511
|
+
await import_node_fs43.promises.writeFile(configPath, config.text, "utf8");
|
|
20512
|
+
console.log(`neat codex: wrote [mcp_servers.neat] to ${configPath}`);
|
|
20513
|
+
} else {
|
|
20514
|
+
console.log(`neat codex: ${configPath} already has NEAT's MCP server`);
|
|
20515
|
+
}
|
|
20516
|
+
if (agents.changed) {
|
|
20517
|
+
await import_node_fs43.promises.mkdir(import_node_path70.default.dirname(agentsPath), { recursive: true });
|
|
20518
|
+
await import_node_fs43.promises.writeFile(agentsPath, agents.text, "utf8");
|
|
20519
|
+
console.log(`neat codex: wrote the graph-first block to ${agentsPath}`);
|
|
20520
|
+
} else {
|
|
20521
|
+
console.log(`neat codex: ${agentsPath} already has the graph-first block`);
|
|
20522
|
+
}
|
|
20523
|
+
console.log("");
|
|
20524
|
+
console.log("restart Codex to pick up the new MCP server. NEAT_CORE_URL in the table");
|
|
20525
|
+
console.log("points the server at the local daemon \u2014 edit it for a non-default one.");
|
|
20526
|
+
return { exitCode: 0 };
|
|
20527
|
+
}
|
|
20528
|
+
function usage2() {
|
|
20529
|
+
console.log("neat codex \u2014 install NEAT into the OpenAI Codex CLI (MCP server + AGENTS.md)");
|
|
20530
|
+
console.log("");
|
|
20531
|
+
console.log(" (no flag) plan: print what would change, write nothing");
|
|
20532
|
+
console.log(" --apply add [mcp_servers.neat] to ~/.codex/config.toml and write");
|
|
20533
|
+
console.log(" the graph-first block into ./AGENTS.md, merging into both");
|
|
20534
|
+
console.log(" without touching your other servers or instructions");
|
|
20535
|
+
console.log(" --print-config print the [mcp_servers.neat] TOML block to stdout");
|
|
20536
|
+
console.log(" --print-guide print the AGENTS.md graph-first block to stdout");
|
|
20537
|
+
console.log("");
|
|
20538
|
+
console.log("Existing config is preserved and a re-run is a no-op. A malformed");
|
|
20539
|
+
console.log("config.toml is a clear error with no partial write.");
|
|
20540
|
+
}
|
|
20541
|
+
async function runCodexCommand(args) {
|
|
20542
|
+
const opts = { apply: false, printConfig: false, printGuide: false };
|
|
20543
|
+
for (const arg of args) {
|
|
20544
|
+
switch (arg) {
|
|
20545
|
+
case "--apply":
|
|
20546
|
+
opts.apply = true;
|
|
20547
|
+
break;
|
|
20548
|
+
case "--print-config":
|
|
20549
|
+
opts.printConfig = true;
|
|
20550
|
+
break;
|
|
20551
|
+
case "--print-guide":
|
|
20552
|
+
opts.printGuide = true;
|
|
20553
|
+
break;
|
|
20554
|
+
case "-h":
|
|
20555
|
+
case "--help":
|
|
20556
|
+
usage2();
|
|
20557
|
+
return 0;
|
|
20558
|
+
default:
|
|
20559
|
+
console.error(`neat codex: unknown flag "${arg}"`);
|
|
20560
|
+
usage2();
|
|
20561
|
+
return 2;
|
|
20562
|
+
}
|
|
20563
|
+
}
|
|
20564
|
+
try {
|
|
20565
|
+
const { exitCode } = await runCodex(opts);
|
|
20566
|
+
return exitCode;
|
|
20567
|
+
} catch (err) {
|
|
20568
|
+
console.error(err.message);
|
|
20569
|
+
return 1;
|
|
20570
|
+
}
|
|
20571
|
+
}
|
|
20572
|
+
|
|
20573
|
+
// src/editors-cli.ts
|
|
20574
|
+
init_cjs_shims();
|
|
20575
|
+
var import_node_path71 = __toESM(require("path"), 1);
|
|
20576
|
+
var import_node_os7 = __toESM(require("os"), 1);
|
|
20577
|
+
var import_node_fs44 = require("fs");
|
|
20578
|
+
var NEAT_MCP_SERVER = {
|
|
20579
|
+
command: "npx",
|
|
20580
|
+
args: ["-y", "@neat.is/mcp"]
|
|
20581
|
+
};
|
|
20582
|
+
var GRAPH_FIRST_MARKER_OPEN = "<!-- neat:graph-first -->";
|
|
20583
|
+
var GRAPH_FIRST_MARKER_CLOSE = "<!-- /neat:graph-first -->";
|
|
20584
|
+
function homeDir() {
|
|
20585
|
+
return process.env.HOME ?? process.env.USERPROFILE ?? import_node_os7.default.homedir();
|
|
20586
|
+
}
|
|
20587
|
+
var CURSOR_CLIENT = {
|
|
20588
|
+
id: "cursor",
|
|
20589
|
+
label: "Cursor",
|
|
20590
|
+
docsUrl: "https://docs.cursor.com/context/mcp",
|
|
20591
|
+
mcpConfigPath: () => {
|
|
20592
|
+
const override = process.env.NEAT_CURSOR_CONFIG;
|
|
20593
|
+
if (override && override.length > 0) return import_node_path71.default.resolve(override);
|
|
20594
|
+
return import_node_path71.default.join(homeDir(), ".cursor", "mcp.json");
|
|
20595
|
+
},
|
|
20596
|
+
// Cursor still reads a single `.cursorrules` at the project root (the modern
|
|
20597
|
+
// `.cursor/rules/*.mdc` split is one-rule-per-file with frontmatter — a worse
|
|
20598
|
+
// fit for a marker-fenced block). GRAPH_FIRST.md names this file directly.
|
|
20599
|
+
rulesFileName: ".cursorrules"
|
|
20600
|
+
};
|
|
20601
|
+
var DEVIN_CLIENT = {
|
|
20602
|
+
id: "devin",
|
|
20603
|
+
label: "Devin Desktop (Cascade)",
|
|
20604
|
+
docsUrl: "https://docs.devin.ai/desktop/cascade/mcp",
|
|
20605
|
+
mcpConfigPath: () => {
|
|
20606
|
+
const override = process.env.NEAT_DEVIN_CONFIG;
|
|
20607
|
+
if (override && override.length > 0) return import_node_path71.default.resolve(override);
|
|
20608
|
+
return import_node_path71.default.join(homeDir(), ".codeium", "windsurf", "mcp_config.json");
|
|
20609
|
+
},
|
|
20610
|
+
rulesFileName: ".windsurfrules"
|
|
20611
|
+
};
|
|
20612
|
+
function mergeMcpConfig(existing) {
|
|
20613
|
+
const servers = existing.mcpServers ?? {};
|
|
20614
|
+
const already = JSON.stringify(servers.neat) === JSON.stringify(NEAT_MCP_SERVER);
|
|
20615
|
+
const merged = {
|
|
20616
|
+
...existing,
|
|
20617
|
+
mcpServers: { ...servers, neat: NEAT_MCP_SERVER }
|
|
20618
|
+
};
|
|
20619
|
+
return { merged, changed: !already };
|
|
20620
|
+
}
|
|
20621
|
+
function escapeRegExp(s) {
|
|
20622
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20623
|
+
}
|
|
20624
|
+
function buildGuidanceBlock(guide) {
|
|
20625
|
+
return `${GRAPH_FIRST_MARKER_OPEN}
|
|
20626
|
+
${guide.trim()}
|
|
20627
|
+
${GRAPH_FIRST_MARKER_CLOSE}
|
|
20628
|
+
`;
|
|
20629
|
+
}
|
|
20630
|
+
function mergeRulesFile(existing, block) {
|
|
20631
|
+
const region = new RegExp(
|
|
20632
|
+
`${escapeRegExp(GRAPH_FIRST_MARKER_OPEN)}[\\s\\S]*?${escapeRegExp(GRAPH_FIRST_MARKER_CLOSE)}\\n?`
|
|
20633
|
+
);
|
|
20634
|
+
if (region.test(existing)) return existing.replace(region, block);
|
|
20635
|
+
if (existing.trim().length === 0) return block;
|
|
20636
|
+
return `${existing.replace(/\s+$/, "")}
|
|
20637
|
+
|
|
20638
|
+
${block}`;
|
|
20639
|
+
}
|
|
20640
|
+
async function runEditorInstall(client, opts) {
|
|
20641
|
+
const mcpPath = client.mcpConfigPath();
|
|
20642
|
+
const rulesPath = import_node_path71.default.join(opts.projectDir, client.rulesFileName);
|
|
20643
|
+
let existingMcp = {};
|
|
20644
|
+
try {
|
|
20645
|
+
existingMcp = JSON.parse(await import_node_fs44.promises.readFile(mcpPath, "utf8"));
|
|
20646
|
+
} catch (err) {
|
|
20647
|
+
const e = err;
|
|
20648
|
+
if (e.code === "ENOENT") {
|
|
20649
|
+
existingMcp = {};
|
|
20650
|
+
} else if (err instanceof SyntaxError) {
|
|
20651
|
+
console.error(
|
|
20652
|
+
`neat ${client.id}: ${mcpPath} is not valid JSON \u2014 ${e.message}. Fix it (or move it aside) and re-run; nothing was written.`
|
|
20653
|
+
);
|
|
20654
|
+
return { exitCode: 1 };
|
|
20655
|
+
} else {
|
|
20656
|
+
console.error(`neat ${client.id}: failed to read ${mcpPath} \u2014 ${e.message}`);
|
|
20657
|
+
return { exitCode: 1 };
|
|
20658
|
+
}
|
|
20659
|
+
}
|
|
20660
|
+
let existingRules = "";
|
|
20661
|
+
try {
|
|
20662
|
+
existingRules = await import_node_fs44.promises.readFile(rulesPath, "utf8");
|
|
20663
|
+
} catch (err) {
|
|
20664
|
+
if (err.code !== "ENOENT") {
|
|
20665
|
+
console.error(`neat ${client.id}: failed to read ${rulesPath} \u2014 ${err.message}`);
|
|
20666
|
+
return { exitCode: 1 };
|
|
20667
|
+
}
|
|
20668
|
+
}
|
|
20669
|
+
const guide = await readSkillAsset(GUIDE_FILENAME);
|
|
20670
|
+
const block = buildGuidanceBlock(guide);
|
|
20671
|
+
const { merged, changed: mcpChanged } = mergeMcpConfig(existingMcp);
|
|
20672
|
+
const mcpJson = JSON.stringify(merged, null, 2) + "\n";
|
|
20673
|
+
const newRules = mergeRulesFile(existingRules, block);
|
|
20674
|
+
const rulesChanged = newRules !== existingRules;
|
|
20675
|
+
if (!opts.apply) {
|
|
20676
|
+
console.log(`neat ${client.id} \u2014 wire NEAT into ${client.label} (plan; nothing written)`);
|
|
20677
|
+
console.log("");
|
|
20678
|
+
console.log(`MCP server \u2192 ${mcpPath}`);
|
|
20679
|
+
console.log(
|
|
20680
|
+
mcpChanged ? " would add mcpServers.neat:" : " mcpServers.neat already present and current \u2014 no change:"
|
|
20681
|
+
);
|
|
20682
|
+
console.log(indent(JSON.stringify({ neat: NEAT_MCP_SERVER }, null, 2)));
|
|
20683
|
+
console.log("");
|
|
20684
|
+
console.log(`Graph-first guidance \u2192 ${rulesPath}`);
|
|
20685
|
+
console.log(
|
|
20686
|
+
rulesChanged ? existingRules.includes(GRAPH_FIRST_MARKER_OPEN) ? " would refresh the neat:graph-first block:" : " would add the neat:graph-first block:" : " neat:graph-first block already present and current \u2014 no change."
|
|
20687
|
+
);
|
|
20688
|
+
if (rulesChanged) console.log(indent(block.trimEnd()));
|
|
20689
|
+
console.log("");
|
|
20690
|
+
console.log(`Re-run with --apply to write both files. Existing servers and rules are kept.`);
|
|
20691
|
+
return { exitCode: 0 };
|
|
20692
|
+
}
|
|
20693
|
+
await import_node_fs44.promises.mkdir(import_node_path71.default.dirname(mcpPath), { recursive: true });
|
|
20694
|
+
await import_node_fs44.promises.writeFile(mcpPath, mcpJson, "utf8");
|
|
20695
|
+
await import_node_fs44.promises.mkdir(import_node_path71.default.dirname(rulesPath), { recursive: true });
|
|
20696
|
+
await import_node_fs44.promises.writeFile(rulesPath, newRules, "utf8");
|
|
20697
|
+
console.log(`neat ${client.id}: wired NEAT into ${client.label}`);
|
|
20698
|
+
console.log(` MCP server: ${mcpPath} (mcpServers.neat \u2192 npx -y @neat.is/mcp)`);
|
|
20699
|
+
console.log(` guidance: ${rulesPath} (neat:graph-first block)`);
|
|
20700
|
+
console.log("");
|
|
20701
|
+
console.log(`restart ${client.label} to pick up the MCP server. Point it at a non-default`);
|
|
20702
|
+
console.log(`daemon by setting NEAT_CORE_URL in the neat server's env in that config.`);
|
|
20703
|
+
return { exitCode: 0 };
|
|
20704
|
+
}
|
|
20705
|
+
function indent(text) {
|
|
20706
|
+
return text.split("\n").map((line) => line.length > 0 ? ` ${line}` : line).join("\n");
|
|
20707
|
+
}
|
|
20708
|
+
function usage3(client) {
|
|
20709
|
+
console.log(`neat ${client.id} \u2014 install NEAT's MCP server + graph-first guidance into ${client.label}`);
|
|
20710
|
+
console.log("");
|
|
20711
|
+
console.log(" --apply write the MCP config and the rules file (default: plan only)");
|
|
20712
|
+
console.log("");
|
|
20713
|
+
console.log("Writes NEAT's stdio MCP server (npx -y @neat.is/mcp) into");
|
|
20714
|
+
console.log(` ${client.mcpConfigPath()}`);
|
|
20715
|
+
console.log(`and the graph-first guidance block into ./${client.rulesFileName}, both`);
|
|
20716
|
+
console.log("additively \u2014 existing servers and rules are preserved, a re-run is a no-op.");
|
|
20717
|
+
console.log("");
|
|
20718
|
+
console.log(`See ${client.docsUrl} for ${client.label}'s MCP config format.`);
|
|
20719
|
+
}
|
|
20720
|
+
async function runEditorCommand(clientId, args, projectDir = process.cwd()) {
|
|
20721
|
+
const client = clientId === "cursor" ? CURSOR_CLIENT : DEVIN_CLIENT;
|
|
20722
|
+
let apply4 = false;
|
|
20723
|
+
for (const arg of args) {
|
|
20724
|
+
switch (arg) {
|
|
20725
|
+
case "--apply":
|
|
20726
|
+
apply4 = true;
|
|
20727
|
+
break;
|
|
20728
|
+
case "-h":
|
|
20729
|
+
case "--help":
|
|
20730
|
+
usage3(client);
|
|
20731
|
+
return 0;
|
|
20732
|
+
default:
|
|
20733
|
+
console.error(`neat ${client.id}: unknown flag "${arg}"`);
|
|
20734
|
+
usage3(client);
|
|
20735
|
+
return 2;
|
|
20736
|
+
}
|
|
20737
|
+
}
|
|
20738
|
+
try {
|
|
20739
|
+
const { exitCode } = await runEditorInstall(client, { apply: apply4, projectDir });
|
|
20740
|
+
return exitCode;
|
|
20741
|
+
} catch (err) {
|
|
20742
|
+
console.error(err.message);
|
|
20743
|
+
return 1;
|
|
20744
|
+
}
|
|
20745
|
+
}
|
|
20746
|
+
|
|
19539
20747
|
// src/monitor.ts
|
|
19540
20748
|
init_cjs_shims();
|
|
19541
|
-
var
|
|
20749
|
+
var import_types70 = require("@neat.is/types");
|
|
19542
20750
|
|
|
19543
20751
|
// src/cli-client.ts
|
|
19544
20752
|
init_cjs_shims();
|
|
19545
|
-
var
|
|
20753
|
+
var import_types69 = require("@neat.is/types");
|
|
19546
20754
|
var HttpError = class extends Error {
|
|
19547
20755
|
constructor(status2, message, responseBody = "") {
|
|
19548
20756
|
super(message);
|
|
@@ -19567,10 +20775,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
19567
20775
|
const root = baseUrl.replace(/\/$/, "");
|
|
19568
20776
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
19569
20777
|
return {
|
|
19570
|
-
async get(
|
|
20778
|
+
async get(path74) {
|
|
19571
20779
|
let res;
|
|
19572
20780
|
try {
|
|
19573
|
-
res = await fetch(`${root}${
|
|
20781
|
+
res = await fetch(`${root}${path74}`, {
|
|
19574
20782
|
headers: { ...authHeader }
|
|
19575
20783
|
});
|
|
19576
20784
|
} catch (err) {
|
|
@@ -19582,16 +20790,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
19582
20790
|
const body = await res.text().catch(() => "");
|
|
19583
20791
|
throw new HttpError(
|
|
19584
20792
|
res.status,
|
|
19585
|
-
`${res.status} ${res.statusText} on GET ${
|
|
20793
|
+
`${res.status} ${res.statusText} on GET ${path74}: ${body}`,
|
|
19586
20794
|
body
|
|
19587
20795
|
);
|
|
19588
20796
|
}
|
|
19589
20797
|
return await res.json();
|
|
19590
20798
|
},
|
|
19591
|
-
async post(
|
|
20799
|
+
async post(path74, body) {
|
|
19592
20800
|
let res;
|
|
19593
20801
|
try {
|
|
19594
|
-
res = await fetch(`${root}${
|
|
20802
|
+
res = await fetch(`${root}${path74}`, {
|
|
19595
20803
|
method: "POST",
|
|
19596
20804
|
headers: { "content-type": "application/json", ...authHeader },
|
|
19597
20805
|
body: JSON.stringify(body)
|
|
@@ -19605,7 +20813,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
19605
20813
|
const text = await res.text().catch(() => "");
|
|
19606
20814
|
throw new HttpError(
|
|
19607
20815
|
res.status,
|
|
19608
|
-
`${res.status} ${res.statusText} on POST ${
|
|
20816
|
+
`${res.status} ${res.statusText} on POST ${path74}: ${text}`,
|
|
19609
20817
|
text
|
|
19610
20818
|
);
|
|
19611
20819
|
}
|
|
@@ -19619,12 +20827,12 @@ function projectPath(project, suffix) {
|
|
|
19619
20827
|
}
|
|
19620
20828
|
async function runRootCause(client, input) {
|
|
19621
20829
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
19622
|
-
const
|
|
20830
|
+
const path74 = projectPath(
|
|
19623
20831
|
input.project,
|
|
19624
20832
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
19625
20833
|
);
|
|
19626
20834
|
try {
|
|
19627
|
-
const result = await client.get(
|
|
20835
|
+
const result = await client.get(path74);
|
|
19628
20836
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
19629
20837
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
19630
20838
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -19650,12 +20858,12 @@ async function runRootCause(client, input) {
|
|
|
19650
20858
|
}
|
|
19651
20859
|
async function runBlastRadius(client, input) {
|
|
19652
20860
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
19653
|
-
const
|
|
20861
|
+
const path74 = projectPath(
|
|
19654
20862
|
input.project,
|
|
19655
20863
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
19656
20864
|
);
|
|
19657
20865
|
try {
|
|
19658
|
-
const result = await client.get(
|
|
20866
|
+
const result = await client.get(path74);
|
|
19659
20867
|
if (result.totalAffected === 0) {
|
|
19660
20868
|
return {
|
|
19661
20869
|
summary: `${result.origin} has no dependents. Nothing else would break if it failed.`
|
|
@@ -19684,17 +20892,17 @@ async function runBlastRadius(client, input) {
|
|
|
19684
20892
|
}
|
|
19685
20893
|
}
|
|
19686
20894
|
function formatBlastEntry(n) {
|
|
19687
|
-
const tag = n.edgeProvenance ===
|
|
20895
|
+
const tag = n.edgeProvenance === import_types69.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
19688
20896
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
19689
20897
|
}
|
|
19690
20898
|
async function runDependencies(client, input) {
|
|
19691
20899
|
const depth = input.depth ?? 3;
|
|
19692
|
-
const
|
|
20900
|
+
const path74 = projectPath(
|
|
19693
20901
|
input.project,
|
|
19694
20902
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
19695
20903
|
);
|
|
19696
20904
|
try {
|
|
19697
|
-
const result = await client.get(
|
|
20905
|
+
const result = await client.get(path74);
|
|
19698
20906
|
if (result.total === 0) {
|
|
19699
20907
|
return {
|
|
19700
20908
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -19741,7 +20949,7 @@ async function runObservedDependencies(client, input) {
|
|
|
19741
20949
|
if (result.observed) {
|
|
19742
20950
|
return {
|
|
19743
20951
|
summary: `${input.nodeId} makes no outbound runtime calls, but OTel has observed it receiving traffic on ${result.inboundObservedCount} inbound call path${result.inboundObservedCount === 1 ? "" : "s"} \u2014 it's a pure receiver.`,
|
|
19744
|
-
provenance:
|
|
20952
|
+
provenance: import_types69.Provenance.OBSERVED
|
|
19745
20953
|
};
|
|
19746
20954
|
}
|
|
19747
20955
|
const note = result.hasExtractedOutbound ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
@@ -19751,7 +20959,7 @@ async function runObservedDependencies(client, input) {
|
|
|
19751
20959
|
return {
|
|
19752
20960
|
summary: `${input.nodeId} has ${result.dependencies.length} runtime dependenc${result.dependencies.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
19753
20961
|
block: blockLines.join("\n"),
|
|
19754
|
-
provenance:
|
|
20962
|
+
provenance: import_types69.Provenance.OBSERVED
|
|
19755
20963
|
};
|
|
19756
20964
|
} catch (err) {
|
|
19757
20965
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -19786,9 +20994,9 @@ function formatDuration(ms) {
|
|
|
19786
20994
|
return `${Math.round(h / 24)}d`;
|
|
19787
20995
|
}
|
|
19788
20996
|
async function runIncidents(client, input) {
|
|
19789
|
-
const
|
|
20997
|
+
const path74 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
19790
20998
|
try {
|
|
19791
|
-
const body = await client.get(
|
|
20999
|
+
const body = await client.get(path74);
|
|
19792
21000
|
const events = body.events;
|
|
19793
21001
|
if (events.length === 0) {
|
|
19794
21002
|
return {
|
|
@@ -19805,7 +21013,7 @@ async function runIncidents(client, input) {
|
|
|
19805
21013
|
return {
|
|
19806
21014
|
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
19807
21015
|
block: blockLines.join("\n"),
|
|
19808
|
-
provenance:
|
|
21016
|
+
provenance: import_types69.Provenance.OBSERVED
|
|
19809
21017
|
};
|
|
19810
21018
|
} catch (err) {
|
|
19811
21019
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -19914,7 +21122,7 @@ async function runStaleEdges(client, input) {
|
|
|
19914
21122
|
return {
|
|
19915
21123
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
19916
21124
|
block: blockLines.join("\n"),
|
|
19917
|
-
provenance:
|
|
21125
|
+
provenance: import_types69.Provenance.STALE
|
|
19918
21126
|
};
|
|
19919
21127
|
}
|
|
19920
21128
|
async function runPolicies(client, input) {
|
|
@@ -20073,10 +21281,10 @@ async function pushSnapshotToRemote(input) {
|
|
|
20073
21281
|
|
|
20074
21282
|
// src/monitor.ts
|
|
20075
21283
|
var OBSERVED_DEP_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
20076
|
-
|
|
20077
|
-
|
|
20078
|
-
|
|
20079
|
-
|
|
21284
|
+
import_types70.EdgeType.CALLS,
|
|
21285
|
+
import_types70.EdgeType.CONNECTS_TO,
|
|
21286
|
+
import_types70.EdgeType.PUBLISHES_TO,
|
|
21287
|
+
import_types70.EdgeType.CONSUMES_FROM
|
|
20080
21288
|
]);
|
|
20081
21289
|
function divergenceKey(d) {
|
|
20082
21290
|
const column = "column" in d && d.column ? d.column : "";
|
|
@@ -20121,7 +21329,7 @@ function formatDivergenceLine2(d) {
|
|
|
20121
21329
|
}
|
|
20122
21330
|
}
|
|
20123
21331
|
function formatStaleLine(edgeId) {
|
|
20124
|
-
const parsed = (0,
|
|
21332
|
+
const parsed = (0, import_types70.parseEdgeId)(edgeId);
|
|
20125
21333
|
if (parsed) {
|
|
20126
21334
|
return `\u22EF stale ${parsed.source} \u2192 ${parsed.target} (observed edge went quiet)`;
|
|
20127
21335
|
}
|
|
@@ -20134,7 +21342,7 @@ function divergenceJson(d) {
|
|
|
20134
21342
|
return JSON.stringify({ kind: "divergence", ...d });
|
|
20135
21343
|
}
|
|
20136
21344
|
function staleJson(edgeId) {
|
|
20137
|
-
const parsed = (0,
|
|
21345
|
+
const parsed = (0, import_types70.parseEdgeId)(edgeId);
|
|
20138
21346
|
return JSON.stringify({
|
|
20139
21347
|
kind: "stale",
|
|
20140
21348
|
edgeId,
|
|
@@ -20151,6 +21359,24 @@ function observedEdgeJson(edge) {
|
|
|
20151
21359
|
provenance: edge.provenance
|
|
20152
21360
|
});
|
|
20153
21361
|
}
|
|
21362
|
+
function policyKey(v) {
|
|
21363
|
+
return `policy|${v.id}`;
|
|
21364
|
+
}
|
|
21365
|
+
function policySubject(v) {
|
|
21366
|
+
const s = v.subject;
|
|
21367
|
+
if (s.nodeId) return s.nodeId;
|
|
21368
|
+
if (s.edgeId) return s.edgeId;
|
|
21369
|
+
if (s.path && s.path.length > 0) return s.path.join(" \u2192 ");
|
|
21370
|
+
return "";
|
|
21371
|
+
}
|
|
21372
|
+
function formatPolicyLine(v) {
|
|
21373
|
+
const subject = policySubject(v);
|
|
21374
|
+
const where = subject ? ` (${subject})` : "";
|
|
21375
|
+
return `\u26A0 policy [${v.severity}] ${v.policyName} \u2014 ${v.message}${where}`;
|
|
21376
|
+
}
|
|
21377
|
+
function policyJson(v) {
|
|
21378
|
+
return JSON.stringify({ kind: "policy", ...v });
|
|
21379
|
+
}
|
|
20154
21380
|
var MonitorEmitter = class {
|
|
20155
21381
|
constructor(opts) {
|
|
20156
21382
|
this.opts = opts;
|
|
@@ -20186,7 +21412,7 @@ var MonitorEmitter = class {
|
|
|
20186
21412
|
// ignores non-OBSERVED edges and non-dependency edge types (structural
|
|
20187
21413
|
// ownership), so only real runtime dependencies reach stdout.
|
|
20188
21414
|
emitObservedEdge(edge) {
|
|
20189
|
-
if (edge.provenance !==
|
|
21415
|
+
if (edge.provenance !== import_types70.Provenance.OBSERVED) return false;
|
|
20190
21416
|
if (!OBSERVED_DEP_EDGE_TYPES.has(edge.type)) return false;
|
|
20191
21417
|
const key = `edge|${edge.id}`;
|
|
20192
21418
|
if (this.seen.has(key)) return false;
|
|
@@ -20194,6 +21420,20 @@ var MonitorEmitter = class {
|
|
|
20194
21420
|
this.out(this.opts.json ? observedEdgeJson(edge) : formatObservedEdgeLine(edge));
|
|
20195
21421
|
return true;
|
|
20196
21422
|
}
|
|
21423
|
+
// Emit every not-yet-seen policy violation in a fresh /policies/violations
|
|
21424
|
+
// read. Returns the count newly emitted (0 → nothing printed, the silent
|
|
21425
|
+
// path). Idempotent across re-reads: the seen-set keys off the violation id.
|
|
21426
|
+
emitPolicies(response) {
|
|
21427
|
+
let emitted = 0;
|
|
21428
|
+
for (const v of response.violations) {
|
|
21429
|
+
const key = policyKey(v);
|
|
21430
|
+
if (this.seen.has(key)) continue;
|
|
21431
|
+
this.seen.add(key);
|
|
21432
|
+
this.out(this.opts.json ? policyJson(v) : formatPolicyLine(v));
|
|
21433
|
+
emitted++;
|
|
21434
|
+
}
|
|
21435
|
+
return emitted;
|
|
21436
|
+
}
|
|
20197
21437
|
};
|
|
20198
21438
|
function parseFrame(raw) {
|
|
20199
21439
|
let event = "message";
|
|
@@ -20248,65 +21488,97 @@ function projectPath2(project, suffix) {
|
|
|
20248
21488
|
function backoffDelay(attempt, capMs) {
|
|
20249
21489
|
return Math.min(capMs, 500 * 2 ** Math.min(attempt, 6));
|
|
20250
21490
|
}
|
|
20251
|
-
async
|
|
20252
|
-
const
|
|
20253
|
-
|
|
20254
|
-
|
|
20255
|
-
|
|
20256
|
-
|
|
20257
|
-
|
|
20258
|
-
|
|
20259
|
-
const
|
|
20260
|
-
|
|
20261
|
-
|
|
20262
|
-
|
|
20263
|
-
|
|
20264
|
-
|
|
20265
|
-
|
|
21491
|
+
var defaultOpenEvents = async (url, headers, signal) => {
|
|
21492
|
+
const res = await fetch(url, { headers, signal });
|
|
21493
|
+
return { ok: res.ok, status: res.status, body: res.body };
|
|
21494
|
+
};
|
|
21495
|
+
function makeDebouncedReader(read, debounceMs) {
|
|
21496
|
+
let timer = null;
|
|
21497
|
+
let running = false;
|
|
21498
|
+
let pending = false;
|
|
21499
|
+
const cancel = () => {
|
|
21500
|
+
if (timer) {
|
|
21501
|
+
clearTimeout(timer);
|
|
21502
|
+
timer = null;
|
|
21503
|
+
}
|
|
21504
|
+
};
|
|
21505
|
+
const run = async () => {
|
|
21506
|
+
if (running) {
|
|
21507
|
+
pending = true;
|
|
20266
21508
|
return;
|
|
20267
21509
|
}
|
|
20268
|
-
|
|
21510
|
+
running = true;
|
|
20269
21511
|
try {
|
|
20270
|
-
|
|
20271
|
-
emitter.emitDivergences(result);
|
|
21512
|
+
await read();
|
|
20272
21513
|
} catch {
|
|
20273
21514
|
} finally {
|
|
20274
|
-
|
|
20275
|
-
if (
|
|
20276
|
-
|
|
20277
|
-
|
|
21515
|
+
running = false;
|
|
21516
|
+
if (pending) {
|
|
21517
|
+
pending = false;
|
|
21518
|
+
schedule();
|
|
20278
21519
|
}
|
|
20279
21520
|
}
|
|
20280
21521
|
};
|
|
20281
|
-
const
|
|
20282
|
-
|
|
20283
|
-
|
|
20284
|
-
|
|
20285
|
-
void
|
|
21522
|
+
const schedule = () => {
|
|
21523
|
+
cancel();
|
|
21524
|
+
timer = setTimeout(() => {
|
|
21525
|
+
timer = null;
|
|
21526
|
+
void run();
|
|
20286
21527
|
}, debounceMs);
|
|
20287
|
-
if (typeof
|
|
21528
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
21529
|
+
};
|
|
21530
|
+
return {
|
|
21531
|
+
schedule,
|
|
21532
|
+
runNow: async () => {
|
|
21533
|
+
cancel();
|
|
21534
|
+
await run();
|
|
21535
|
+
},
|
|
21536
|
+
cancel
|
|
20288
21537
|
};
|
|
21538
|
+
}
|
|
21539
|
+
async function runMonitor(opts) {
|
|
21540
|
+
const write = opts.write ?? ((line) => process.stdout.write(line));
|
|
21541
|
+
const debounceMs = opts.debounceMs ?? 400;
|
|
21542
|
+
const backoffCapMs = opts.backoffCapMs ?? 1e4;
|
|
21543
|
+
const maxReconnects = opts.maxReconnects ?? Number.POSITIVE_INFINITY;
|
|
21544
|
+
const client = opts.httpClient ?? createHttpClient(opts.baseUrl, opts.authToken);
|
|
21545
|
+
const openEvents = opts.openEvents ?? defaultOpenEvents;
|
|
21546
|
+
const emitter = new MonitorEmitter({ json: opts.json, write });
|
|
21547
|
+
const divergencesPath = projectPath2(opts.project, "/graph/divergences");
|
|
21548
|
+
const policiesPath = projectPath2(opts.project, "/policies/violations");
|
|
21549
|
+
const eventsUrl = `${opts.baseUrl.replace(/\/$/, "")}${projectPath2(opts.project, "/events")}`;
|
|
21550
|
+
const divergences = makeDebouncedReader(async () => {
|
|
21551
|
+
const result = await client.get(divergencesPath);
|
|
21552
|
+
emitter.emitDivergences(result);
|
|
21553
|
+
}, debounceMs);
|
|
21554
|
+
const policies = makeDebouncedReader(async () => {
|
|
21555
|
+
const result = await client.get(policiesPath);
|
|
21556
|
+
emitter.emitPolicies(result);
|
|
21557
|
+
}, debounceMs);
|
|
20289
21558
|
const onFrame = (frame) => {
|
|
20290
21559
|
switch (frame.event) {
|
|
20291
21560
|
case "extraction-complete":
|
|
20292
|
-
|
|
21561
|
+
divergences.schedule();
|
|
20293
21562
|
break;
|
|
20294
21563
|
case "stale-transition": {
|
|
20295
21564
|
const payload = safeParse(frame.data);
|
|
20296
21565
|
const edgeId = payload && typeof payload.edgeId === "string" ? payload.edgeId : void 0;
|
|
20297
21566
|
if (edgeId) emitter.emitStale(edgeId);
|
|
20298
|
-
|
|
21567
|
+
divergences.schedule();
|
|
20299
21568
|
break;
|
|
20300
21569
|
}
|
|
20301
21570
|
case "edge-added": {
|
|
20302
21571
|
const payload = safeParse(frame.data);
|
|
20303
21572
|
const edge = payload?.edge;
|
|
20304
|
-
if (edge && edge.provenance ===
|
|
21573
|
+
if (edge && edge.provenance === import_types70.Provenance.OBSERVED) {
|
|
20305
21574
|
emitter.emitObservedEdge(edge);
|
|
20306
|
-
|
|
21575
|
+
divergences.schedule();
|
|
20307
21576
|
}
|
|
20308
21577
|
break;
|
|
20309
21578
|
}
|
|
21579
|
+
case "policy-violation":
|
|
21580
|
+
policies.schedule();
|
|
21581
|
+
break;
|
|
20310
21582
|
default:
|
|
20311
21583
|
break;
|
|
20312
21584
|
}
|
|
@@ -20319,17 +21591,17 @@ async function runMonitor(opts) {
|
|
|
20319
21591
|
let firstConnect = true;
|
|
20320
21592
|
for (let attempt = 0; ; attempt++) {
|
|
20321
21593
|
if (opts.signal?.aborted) break;
|
|
20322
|
-
let
|
|
21594
|
+
let conn;
|
|
20323
21595
|
try {
|
|
20324
|
-
|
|
21596
|
+
conn = await openEvents(eventsUrl, headers, opts.signal);
|
|
20325
21597
|
} catch (err) {
|
|
20326
21598
|
if (err.name === "AbortError") break;
|
|
20327
21599
|
if (!connectedOnce || attempt >= maxReconnects) break;
|
|
20328
21600
|
await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
|
|
20329
21601
|
continue;
|
|
20330
21602
|
}
|
|
20331
|
-
if (!
|
|
20332
|
-
await
|
|
21603
|
+
if (!conn.ok || !conn.body) {
|
|
21604
|
+
await conn.body?.cancel().catch(() => {
|
|
20333
21605
|
});
|
|
20334
21606
|
if (!connectedOnce || attempt >= maxReconnects) break;
|
|
20335
21607
|
await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
|
|
@@ -20339,19 +21611,22 @@ async function runMonitor(opts) {
|
|
|
20339
21611
|
attempt = 0;
|
|
20340
21612
|
if (firstConnect) {
|
|
20341
21613
|
firstConnect = false;
|
|
20342
|
-
await
|
|
21614
|
+
await divergences.runNow();
|
|
21615
|
+
await policies.runNow();
|
|
20343
21616
|
} else {
|
|
20344
|
-
|
|
21617
|
+
divergences.schedule();
|
|
21618
|
+
policies.schedule();
|
|
20345
21619
|
}
|
|
20346
21620
|
try {
|
|
20347
|
-
await drainSse(
|
|
21621
|
+
await drainSse(conn.body, onFrame);
|
|
20348
21622
|
} catch {
|
|
20349
21623
|
}
|
|
20350
21624
|
if (opts.signal?.aborted) break;
|
|
20351
21625
|
if (attempt >= maxReconnects) break;
|
|
20352
21626
|
await sleep(backoffDelay(attempt, backoffCapMs), opts.signal);
|
|
20353
21627
|
}
|
|
20354
|
-
|
|
21628
|
+
divergences.cancel();
|
|
21629
|
+
policies.cancel();
|
|
20355
21630
|
return 0;
|
|
20356
21631
|
}
|
|
20357
21632
|
function sleep(ms, signal) {
|
|
@@ -20375,7 +21650,7 @@ function sleep(ms, signal) {
|
|
|
20375
21650
|
|
|
20376
21651
|
// src/cli-verbs.ts
|
|
20377
21652
|
init_cjs_shims();
|
|
20378
|
-
var
|
|
21653
|
+
var import_node_path72 = __toESM(require("path"), 1);
|
|
20379
21654
|
async function resolveProjectEntry(opts) {
|
|
20380
21655
|
const entries = await listProjects();
|
|
20381
21656
|
if (opts.project) {
|
|
@@ -20385,7 +21660,7 @@ async function resolveProjectEntry(opts) {
|
|
|
20385
21660
|
const cwd = opts.cwd ?? process.cwd();
|
|
20386
21661
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
20387
21662
|
for (const entry2 of entries) {
|
|
20388
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
21663
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path72.default.sep}`)) {
|
|
20389
21664
|
return entry2;
|
|
20390
21665
|
}
|
|
20391
21666
|
}
|
|
@@ -20538,7 +21813,7 @@ async function runSync(opts) {
|
|
|
20538
21813
|
}
|
|
20539
21814
|
|
|
20540
21815
|
// src/cli.ts
|
|
20541
|
-
var
|
|
21816
|
+
var import_types71 = require("@neat.is/types");
|
|
20542
21817
|
function isNpxInvocation() {
|
|
20543
21818
|
if (process.env.npm_command === "exec") return true;
|
|
20544
21819
|
const execpath = process.env.npm_execpath ?? "";
|
|
@@ -20550,7 +21825,7 @@ function isNpxInvocation() {
|
|
|
20550
21825
|
function commandPrefix() {
|
|
20551
21826
|
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
20552
21827
|
}
|
|
20553
|
-
function
|
|
21828
|
+
function usage4() {
|
|
20554
21829
|
const neat = commandPrefix();
|
|
20555
21830
|
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
20556
21831
|
console.log("");
|
|
@@ -20570,10 +21845,12 @@ function usage2() {
|
|
|
20570
21845
|
console.log(" PORT (default 8080), OTEL_PORT (4318), HOST (0.0.0.0)");
|
|
20571
21846
|
console.log(" control listeners. NEAT_OTLP_GRPC=true also opens 4317.");
|
|
20572
21847
|
console.log(" monitor Stream live graph facts to stdout \u2014 one line per new");
|
|
20573
|
-
console.log(" fact \u2014 as the daemon learns them: fresh divergences
|
|
20574
|
-
console.log("
|
|
20575
|
-
console.log("
|
|
20576
|
-
console.log("
|
|
21848
|
+
console.log(" fact \u2014 as the daemon learns them: fresh divergences");
|
|
21849
|
+
console.log(" (down to the drifting column), integrations that just");
|
|
21850
|
+
console.log(" went stale, new observed runtime dependencies, and");
|
|
21851
|
+
console.log(" freshly-tripped policy violations. Silent when nothing");
|
|
21852
|
+
console.log(" is new; exits clean with no output when no daemon is");
|
|
21853
|
+
console.log(" reachable.");
|
|
20577
21854
|
console.log(" Flags:");
|
|
20578
21855
|
console.log(" --project <name> watch a registered project by name");
|
|
20579
21856
|
console.log(" --json emit one JSON object per line");
|
|
@@ -20603,6 +21880,22 @@ function usage2() {
|
|
|
20603
21880
|
console.log(" --print-hook print the hook script");
|
|
20604
21881
|
console.log(" --print-guide print the graph-first guidance");
|
|
20605
21882
|
console.log(" --print-settings print the settings.json block --apply adds");
|
|
21883
|
+
console.log(" codex Install NEAT into the OpenAI Codex CLI: add [mcp_servers.neat]");
|
|
21884
|
+
console.log(" to ~/.codex/config.toml and the graph-first block to ./AGENTS.md.");
|
|
21885
|
+
console.log(" Plan by default; --apply to write.");
|
|
21886
|
+
console.log(" Flags:");
|
|
21887
|
+
console.log(" --apply add the MCP server + AGENTS.md block");
|
|
21888
|
+
console.log(" --print-config print the config.toml table");
|
|
21889
|
+
console.log(" --print-guide print the AGENTS.md block");
|
|
21890
|
+
console.log(" cursor Wire NEAT into Cursor: add the MCP server to ~/.cursor/mcp.json");
|
|
21891
|
+
console.log(" and the graph-first guidance to ./.cursorrules. Plan by default.");
|
|
21892
|
+
console.log(" Flags:");
|
|
21893
|
+
console.log(" --apply write the MCP config + rules file (default: plan)");
|
|
21894
|
+
console.log(" devin Wire NEAT into Devin Desktop (Cascade): add the MCP server to");
|
|
21895
|
+
console.log(" ~/.codeium/windsurf/mcp_config.json and the graph-first guidance");
|
|
21896
|
+
console.log(" to ./.windsurfrules. Plan by default.");
|
|
21897
|
+
console.log(" Flags:");
|
|
21898
|
+
console.log(" --apply write the MCP config + rules file (default: plan)");
|
|
20606
21899
|
console.log(" deploy Detect the deploy substrate, generate NEAT_AUTH_TOKEN,");
|
|
20607
21900
|
console.log(" emit a docker-compose / systemd / docker run artifact, and");
|
|
20608
21901
|
console.log(" print the OTel env-vars block to paste into your platform.");
|
|
@@ -20616,7 +21909,8 @@ function usage2() {
|
|
|
20616
21909
|
console.log(" --no-instrument skip the SDK install apply step");
|
|
20617
21910
|
console.log(" --json emit the delta summary as JSON");
|
|
20618
21911
|
console.log(" connector Configure OBSERVED connectors \u2014 pull (supabase, railway,");
|
|
20619
|
-
console.log(" firebase, cloudflare) and push (vercel,
|
|
21912
|
+
console.log(" firebase, cloudflare, neon, cloud-run) and push (vercel,");
|
|
21913
|
+
console.log(" via a trace Drain).");
|
|
20620
21914
|
console.log(" Subcommands:");
|
|
20621
21915
|
console.log(" add <provider> add a connector; validates the credential");
|
|
20622
21916
|
console.log(" against the provider first (--skip-validate to skip)");
|
|
@@ -20848,7 +22142,7 @@ async function buildPatchSections(services, project) {
|
|
|
20848
22142
|
}
|
|
20849
22143
|
async function runInit(opts) {
|
|
20850
22144
|
const written = [];
|
|
20851
|
-
const stat = await
|
|
22145
|
+
const stat = await import_node_fs45.promises.stat(opts.scanPath).catch(() => null);
|
|
20852
22146
|
if (!stat || !stat.isDirectory()) {
|
|
20853
22147
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
20854
22148
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -20857,13 +22151,13 @@ async function runInit(opts) {
|
|
|
20857
22151
|
printDiscoveryReport(opts, services);
|
|
20858
22152
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
20859
22153
|
const patch = renderPatch(sections);
|
|
20860
|
-
const patchPath =
|
|
22154
|
+
const patchPath = import_node_path73.default.join(opts.scanPath, "neat.patch");
|
|
20861
22155
|
if (opts.dryRun) {
|
|
20862
|
-
await
|
|
22156
|
+
await import_node_fs45.promises.writeFile(patchPath, patch, "utf8");
|
|
20863
22157
|
written.push(patchPath);
|
|
20864
22158
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
20865
|
-
const gitignorePath =
|
|
20866
|
-
const gitignoreExists = await
|
|
22159
|
+
const gitignorePath = import_node_path73.default.join(opts.scanPath, ".gitignore");
|
|
22160
|
+
const gitignoreExists = await import_node_fs45.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
20867
22161
|
const verb = gitignoreExists ? "append" : "create";
|
|
20868
22162
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
20869
22163
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -20874,9 +22168,9 @@ async function runInit(opts) {
|
|
|
20874
22168
|
const graph = getGraph(graphKey);
|
|
20875
22169
|
const projectPaths = pathsForProject(
|
|
20876
22170
|
graphKey,
|
|
20877
|
-
|
|
22171
|
+
import_node_path73.default.join(opts.scanPath, "neat-out")
|
|
20878
22172
|
);
|
|
20879
|
-
const errorsPath =
|
|
22173
|
+
const errorsPath = import_node_path73.default.join(import_node_path73.default.dirname(opts.outPath), import_node_path73.default.basename(projectPaths.errorsPath));
|
|
20880
22174
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
20881
22175
|
await saveGraphToDisk(graph, opts.outPath);
|
|
20882
22176
|
written.push(opts.outPath);
|
|
@@ -20955,7 +22249,7 @@ async function runInit(opts) {
|
|
|
20955
22249
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
20956
22250
|
}
|
|
20957
22251
|
} else {
|
|
20958
|
-
await
|
|
22252
|
+
await import_node_fs45.promises.writeFile(patchPath, patch, "utf8");
|
|
20959
22253
|
written.push(patchPath);
|
|
20960
22254
|
}
|
|
20961
22255
|
}
|
|
@@ -20995,9 +22289,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
20995
22289
|
};
|
|
20996
22290
|
function claudeConfigPath() {
|
|
20997
22291
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
20998
|
-
if (override && override.length > 0) return
|
|
22292
|
+
if (override && override.length > 0) return import_node_path73.default.resolve(override);
|
|
20999
22293
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
21000
|
-
return
|
|
22294
|
+
return import_node_path73.default.join(home, ".claude.json");
|
|
21001
22295
|
}
|
|
21002
22296
|
async function runSkill(opts) {
|
|
21003
22297
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -21009,7 +22303,7 @@ async function runSkill(opts) {
|
|
|
21009
22303
|
const target = claudeConfigPath();
|
|
21010
22304
|
let existing = {};
|
|
21011
22305
|
try {
|
|
21012
|
-
existing = JSON.parse(await
|
|
22306
|
+
existing = JSON.parse(await import_node_fs45.promises.readFile(target, "utf8"));
|
|
21013
22307
|
} catch (err) {
|
|
21014
22308
|
if (err.code !== "ENOENT") {
|
|
21015
22309
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -21021,8 +22315,8 @@ async function runSkill(opts) {
|
|
|
21021
22315
|
...existing,
|
|
21022
22316
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
21023
22317
|
};
|
|
21024
|
-
await
|
|
21025
|
-
await
|
|
22318
|
+
await import_node_fs45.promises.mkdir(import_node_path73.default.dirname(target), { recursive: true });
|
|
22319
|
+
await import_node_fs45.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
21026
22320
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
21027
22321
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
21028
22322
|
console.log("");
|
|
@@ -21049,7 +22343,7 @@ async function main() {
|
|
|
21049
22343
|
const argv = process.argv.slice(2);
|
|
21050
22344
|
const cmd0 = argv[0];
|
|
21051
22345
|
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
21052
|
-
|
|
22346
|
+
usage4();
|
|
21053
22347
|
process.exit(0);
|
|
21054
22348
|
}
|
|
21055
22349
|
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
@@ -21066,6 +22360,16 @@ async function main() {
|
|
|
21066
22360
|
if (code !== 0) process.exit(code);
|
|
21067
22361
|
return;
|
|
21068
22362
|
}
|
|
22363
|
+
if (cmd0 === "codex") {
|
|
22364
|
+
const code = await runCodexCommand(argv.slice(1));
|
|
22365
|
+
if (code !== 0) process.exit(code);
|
|
22366
|
+
return;
|
|
22367
|
+
}
|
|
22368
|
+
if (cmd0 === "cursor" || cmd0 === "devin") {
|
|
22369
|
+
const code = await runEditorCommand(cmd0, argv.slice(1));
|
|
22370
|
+
if (code !== 0) process.exit(code);
|
|
22371
|
+
return;
|
|
22372
|
+
}
|
|
21069
22373
|
const argvParsed = parseArgs(argv);
|
|
21070
22374
|
if (argvParsed.positional.length === 0) {
|
|
21071
22375
|
const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
|
|
@@ -21080,19 +22384,19 @@ async function main() {
|
|
|
21080
22384
|
const target = positional[0];
|
|
21081
22385
|
if (!target) {
|
|
21082
22386
|
console.error("neat init: missing <path>");
|
|
21083
|
-
|
|
22387
|
+
usage4();
|
|
21084
22388
|
process.exit(2);
|
|
21085
22389
|
}
|
|
21086
22390
|
if (apply4 && dryRun) {
|
|
21087
22391
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
21088
22392
|
process.exit(2);
|
|
21089
22393
|
}
|
|
21090
|
-
const scanPath =
|
|
22394
|
+
const scanPath = import_node_path73.default.resolve(target);
|
|
21091
22395
|
const projectExplicit = parsed.project !== null;
|
|
21092
|
-
const projectName = projectExplicit ? project :
|
|
22396
|
+
const projectName = projectExplicit ? project : import_node_path73.default.basename(scanPath);
|
|
21093
22397
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
21094
|
-
const fallback = pathsForProject(projectKey,
|
|
21095
|
-
const outPath =
|
|
22398
|
+
const fallback = pathsForProject(projectKey, import_node_path73.default.join(scanPath, "neat-out")).snapshotPath;
|
|
22399
|
+
const outPath = import_node_path73.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
21096
22400
|
const result = await runInit({
|
|
21097
22401
|
scanPath,
|
|
21098
22402
|
outPath,
|
|
@@ -21110,24 +22414,24 @@ async function main() {
|
|
|
21110
22414
|
const target = positional[0];
|
|
21111
22415
|
if (!target) {
|
|
21112
22416
|
console.error("neat watch: missing <path>");
|
|
21113
|
-
|
|
22417
|
+
usage4();
|
|
21114
22418
|
process.exit(2);
|
|
21115
22419
|
}
|
|
21116
|
-
const scanPath =
|
|
21117
|
-
const stat = await
|
|
22420
|
+
const scanPath = import_node_path73.default.resolve(target);
|
|
22421
|
+
const stat = await import_node_fs45.promises.stat(scanPath).catch(() => null);
|
|
21118
22422
|
if (!stat || !stat.isDirectory()) {
|
|
21119
22423
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
21120
22424
|
process.exit(2);
|
|
21121
22425
|
}
|
|
21122
|
-
const projectPaths = pathsForProject(project,
|
|
21123
|
-
const outPath =
|
|
21124
|
-
const errorsPath =
|
|
21125
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
22426
|
+
const projectPaths = pathsForProject(project, import_node_path73.default.join(scanPath, "neat-out"));
|
|
22427
|
+
const outPath = import_node_path73.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
22428
|
+
const errorsPath = import_node_path73.default.resolve(
|
|
22429
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path73.default.join(import_node_path73.default.dirname(outPath), import_node_path73.default.basename(projectPaths.errorsPath))
|
|
21126
22430
|
);
|
|
21127
|
-
const staleEventsPath =
|
|
21128
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
22431
|
+
const staleEventsPath = import_node_path73.default.resolve(
|
|
22432
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path73.default.join(import_node_path73.default.dirname(outPath), import_node_path73.default.basename(projectPaths.staleEventsPath))
|
|
21129
22433
|
);
|
|
21130
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
22434
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path73.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
21131
22435
|
const handle = await startWatch(getGraph(project), {
|
|
21132
22436
|
scanPath,
|
|
21133
22437
|
outPath,
|
|
@@ -21136,7 +22440,7 @@ async function main() {
|
|
|
21136
22440
|
project,
|
|
21137
22441
|
// Resolve NEAT_HOME so a `neat watch` picks up connectors added to
|
|
21138
22442
|
// ~/.neat/connectors.json (#871). Same resolution the rest of the CLI uses.
|
|
21139
|
-
neatHome: process.env.NEAT_HOME ?
|
|
22443
|
+
neatHome: process.env.NEAT_HOME ? import_node_path73.default.resolve(process.env.NEAT_HOME) : import_node_path73.default.join(import_node_os8.default.homedir(), ".neat"),
|
|
21140
22444
|
...embeddingsCachePath ? { embeddingsCachePath } : {},
|
|
21141
22445
|
host: process.env.HOST ?? "0.0.0.0",
|
|
21142
22446
|
port: Number(process.env.PORT ?? 8080),
|
|
@@ -21172,7 +22476,7 @@ async function main() {
|
|
|
21172
22476
|
const name = positional[0];
|
|
21173
22477
|
if (!name) {
|
|
21174
22478
|
console.error("neat pause: missing <name>");
|
|
21175
|
-
|
|
22479
|
+
usage4();
|
|
21176
22480
|
process.exit(2);
|
|
21177
22481
|
}
|
|
21178
22482
|
const daemon = await findDaemonByProject(name);
|
|
@@ -21197,7 +22501,7 @@ async function main() {
|
|
|
21197
22501
|
const name = positional[0];
|
|
21198
22502
|
if (!name) {
|
|
21199
22503
|
console.error("neat resume: missing <name>");
|
|
21200
|
-
|
|
22504
|
+
usage4();
|
|
21201
22505
|
process.exit(2);
|
|
21202
22506
|
}
|
|
21203
22507
|
const daemon = await findDaemonByProject(name);
|
|
@@ -21227,7 +22531,7 @@ async function main() {
|
|
|
21227
22531
|
const name = positional[0];
|
|
21228
22532
|
if (!name) {
|
|
21229
22533
|
console.error("neat uninstall: missing <name>");
|
|
21230
|
-
|
|
22534
|
+
usage4();
|
|
21231
22535
|
process.exit(2);
|
|
21232
22536
|
}
|
|
21233
22537
|
const daemon = await findDaemonByProject(name);
|
|
@@ -21314,15 +22618,15 @@ async function main() {
|
|
|
21314
22618
|
return;
|
|
21315
22619
|
}
|
|
21316
22620
|
console.error(`neat: unknown command "${cmd}"`);
|
|
21317
|
-
|
|
22621
|
+
usage4();
|
|
21318
22622
|
process.exit(1);
|
|
21319
22623
|
}
|
|
21320
22624
|
async function tryOrchestrator(cmd, parsed) {
|
|
21321
|
-
const scanPath =
|
|
21322
|
-
const stat = await
|
|
22625
|
+
const scanPath = import_node_path73.default.resolve(cmd);
|
|
22626
|
+
const stat = await import_node_fs45.promises.stat(scanPath).catch(() => null);
|
|
21323
22627
|
if (!stat || !stat.isDirectory()) return null;
|
|
21324
22628
|
const projectExplicit = parsed.project !== null;
|
|
21325
|
-
const projectName = projectExplicit ? parsed.project :
|
|
22629
|
+
const projectName = projectExplicit ? parsed.project : import_node_path73.default.basename(scanPath);
|
|
21326
22630
|
const result = await runOrchestrator({
|
|
21327
22631
|
scanPath,
|
|
21328
22632
|
project: projectName,
|
|
@@ -21511,10 +22815,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
21511
22815
|
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
21512
22816
|
const out = [];
|
|
21513
22817
|
for (const p of parts) {
|
|
21514
|
-
const r =
|
|
22818
|
+
const r = import_types71.DivergenceTypeSchema.safeParse(p);
|
|
21515
22819
|
if (!r.success) {
|
|
21516
22820
|
console.error(
|
|
21517
|
-
`neat divergences: unknown --type "${p}". allowed: ${
|
|
22821
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types71.DivergenceTypeSchema.options.join(", ")}`
|
|
21518
22822
|
);
|
|
21519
22823
|
return 2;
|
|
21520
22824
|
}
|