@ainyc/canonry 5.1.3 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agent-workspace/skills/aero/SKILL.md +43 -9
- package/assets/agent-workspace/skills/aero/references/agent-operations.md +231 -0
- package/assets/agent-workspace/skills/aero/references/orchestration.md +11 -5
- package/assets/agent-workspace/skills/aero/references/portfolio-analysis.md +93 -0
- package/assets/agent-workspace/skills/aero/references/reporting.md +10 -1
- package/assets/agent-workspace/skills/aero/references/site-health.md +104 -0
- package/assets/agent-workspace/skills/aero/soul.md +5 -0
- package/assets/assets/{AuditHistoryPanel-Dc_pymFg.js → AuditHistoryPanel-D0H5TtxC.js} +1 -1
- package/assets/assets/{BacklinksPage-BkSo48YX.js → BacklinksPage-DRihwG5O.js} +1 -1
- package/assets/assets/{HistoryPage-BbswsdU7.js → HistoryPage-BQ2nEXly.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-BbgtalkM.js → MeasurementPropertyPage-Bp6EURzJ.js} +1 -1
- package/assets/assets/ProjectPage-Cd03Phwx.js +8 -0
- package/assets/assets/{RunRow-2cNW8FHz.js → RunRow-Bo_Adiy0.js} +1 -1
- package/assets/assets/{RunsPage-Cz3lJ6VJ.js → RunsPage-CDkbzjuo.js} +1 -1
- package/assets/assets/{SettingsPage-BQwB0r9k.js → SettingsPage-CF4i_EzR.js} +1 -1
- package/assets/assets/SiteHealthSection-BrzBV7dt.js +4 -0
- package/assets/assets/{TrafficPage-DTic3zhO.js → TrafficPage-BHSz1N4P.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-BKfabnCw.js → TrafficSourceDetailPage-DzHLwhpy.js} +1 -1
- package/assets/assets/{extract-error-message-BzB8j3fC.js → extract-error-message-WHM24Sai.js} +1 -1
- package/assets/assets/{index-DQGEzxrN.js → index-5W4ZZnwg.js} +22 -22
- package/assets/assets/index-DCLmKL1s.css +1 -0
- package/assets/assets/{react-sigma_core.esm.min-rYv0aKKx.js → react-sigma_core.esm.min-BB2Lcq4W.js} +1 -1
- package/assets/assets/{v2-overview-adapter-CWWPfsm-.js → v2-overview-adapter-ebrNjcUG.js} +1 -1
- package/assets/assets/vendor-lucide-D_WBzb76.js +1 -0
- package/assets/index.html +3 -3
- package/dist/{chunk-MMSPU72Z.js → chunk-2U5IW3G5.js} +4 -0
- package/dist/chunk-5VONCLVQ.js +652 -0
- package/dist/chunk-AOJU5LKX.js +8 -0
- package/dist/{chunk-KJM4DRZN.js → chunk-C7PITYL3.js} +5 -3
- package/dist/{chunk-5ZOLPHTO.js → chunk-CW2KPN3L.js} +331 -943
- package/dist/{chunk-AEE5DGTE.js → chunk-IOP36L3C.js} +4 -7
- package/dist/chunk-RB2F4D3Z.js +67 -0
- package/dist/{chunk-EBUX3C5H.js → chunk-YQNKULAD.js} +30 -10
- package/dist/cli.js +100 -5
- package/dist/demo-server-BIPT53EY.js +2125 -0
- package/dist/index.js +6 -4
- package/dist/intelligence-service-SJO5E7WN.js +9 -0
- package/dist/mcp.js +4 -3
- package/package.json +11 -11
- package/assets/assets/ProjectPage-CAcRQp-T.js +0 -8
- package/assets/assets/SiteHealthSection-C0aJ_UhN.js +0 -4
- package/assets/assets/index-CjEprBri.css +0 -1
- package/assets/assets/vendor-lucide-CW2qe0uH.js +0 -1
- package/dist/intelligence-service-25NQCETC.js +0 -7
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
import {
|
|
2
|
+
siteCrawlEdges,
|
|
3
|
+
siteCrawlGraphEdges,
|
|
4
|
+
siteCrawlGraphLayouts,
|
|
5
|
+
siteCrawlGraphNodes,
|
|
6
|
+
siteCrawlPages,
|
|
7
|
+
siteCrawlSnapshots
|
|
8
|
+
} from "./chunk-YQNKULAD.js";
|
|
9
|
+
import {
|
|
10
|
+
SITE_CRAWL_GRAPH_MAX_EDGES,
|
|
11
|
+
SITE_CRAWL_GRAPH_MAX_NODES,
|
|
12
|
+
TEMPLATE_LINK_RATIO_THRESHOLD
|
|
13
|
+
} from "./chunk-2U5IW3G5.js";
|
|
14
|
+
|
|
15
|
+
// src/site-crawl-graph-layout.ts
|
|
16
|
+
import crypto from "crypto";
|
|
17
|
+
import { createRequire } from "module";
|
|
18
|
+
import { Worker } from "worker_threads";
|
|
19
|
+
import { and, asc, count, desc, eq, isNull, ne, or, sql } from "drizzle-orm";
|
|
20
|
+
import { alias } from "drizzle-orm/sqlite-core";
|
|
21
|
+
var SITE_CRAWL_GRAPH_LAYOUT_TIMEOUT_MS = 15e3;
|
|
22
|
+
var SITE_CRAWL_GRAPH_SEED_SCALE = 100;
|
|
23
|
+
var graphSourcePage = alias(siteCrawlPages, "layout_graph_source_page");
|
|
24
|
+
var graphTargetPage = alias(siteCrawlPages, "layout_graph_target_page");
|
|
25
|
+
var FORCE_ATLAS_SETTINGS = Object.freeze({
|
|
26
|
+
adjustSizes: false,
|
|
27
|
+
barnesHutOptimize: true,
|
|
28
|
+
barnesHutTheta: 0.5,
|
|
29
|
+
edgeWeightInfluence: 0.5,
|
|
30
|
+
gravity: 0.2,
|
|
31
|
+
linLogMode: true,
|
|
32
|
+
outboundAttractionDistribution: true,
|
|
33
|
+
scalingRatio: 2,
|
|
34
|
+
slowDown: 10,
|
|
35
|
+
strongGravityMode: false
|
|
36
|
+
});
|
|
37
|
+
var RENDER_MAX_NODE_SIZE = 14;
|
|
38
|
+
var RENDER_HALF_EXTENT_PX = 420;
|
|
39
|
+
var NODE_SIZE_EXTENT_FRACTION = RENDER_MAX_NODE_SIZE / RENDER_HALF_EXTENT_PX;
|
|
40
|
+
function siteCrawlGraphNodeSize(extent, nodeCount) {
|
|
41
|
+
if (!Number.isFinite(extent) || extent <= 0 || nodeCount <= 0) return 0;
|
|
42
|
+
const byExtent = extent * NODE_SIZE_EXTENT_FRACTION;
|
|
43
|
+
const byArea = Math.sqrt(NOVERLAP_SETTINGS.maxPackingFraction * (2 * extent) ** 2 / nodeCount) / 2;
|
|
44
|
+
return Math.min(byExtent, byArea);
|
|
45
|
+
}
|
|
46
|
+
function siteCrawlGraphExtent(positions) {
|
|
47
|
+
if (positions.length === 0) return 0;
|
|
48
|
+
const centerX = positions.reduce((sum, p) => sum + p.x, 0) / positions.length;
|
|
49
|
+
const centerY = positions.reduce((sum, p) => sum + p.y, 0) / positions.length;
|
|
50
|
+
let extent = 0;
|
|
51
|
+
for (const position of positions) {
|
|
52
|
+
extent = Math.max(extent, Math.abs(position.x - centerX), Math.abs(position.y - centerY));
|
|
53
|
+
}
|
|
54
|
+
return extent;
|
|
55
|
+
}
|
|
56
|
+
var NOVERLAP_SETTINGS = Object.freeze({
|
|
57
|
+
/** Half a node of clear space between two touching nodes. */
|
|
58
|
+
marginFactor: 0.5,
|
|
59
|
+
expansion: 1.1,
|
|
60
|
+
ratio: 1,
|
|
61
|
+
speed: 3,
|
|
62
|
+
/**
|
|
63
|
+
* Node discs may claim at most this share of the graph's bounding box. At
|
|
64
|
+
* the 20k cap the extent-derived size asks for spacing the graph physically
|
|
65
|
+
* does not have, and noverlap either runs for minutes or overflows its
|
|
66
|
+
* collision set. This bound keeps the pass solvable at every graph size,
|
|
67
|
+
* and it only binds on graphs far too dense to read node-by-node anyway.
|
|
68
|
+
*/
|
|
69
|
+
maxPackingFraction: 0.15,
|
|
70
|
+
/** Cells hold a handful of nodes each, which is what keeps the pass linear. */
|
|
71
|
+
gridSizeMin: 10,
|
|
72
|
+
gridSizeMax: 200,
|
|
73
|
+
iterationLadder: [
|
|
74
|
+
{ maxNodes: 1e3, iterations: 120 },
|
|
75
|
+
{ maxNodes: 5e3, iterations: 60 }
|
|
76
|
+
],
|
|
77
|
+
iterationsFloor: 20
|
|
78
|
+
});
|
|
79
|
+
function noverlapGridSize(nodeCount) {
|
|
80
|
+
return Math.max(
|
|
81
|
+
NOVERLAP_SETTINGS.gridSizeMin,
|
|
82
|
+
Math.min(NOVERLAP_SETTINGS.gridSizeMax, Math.ceil(Math.sqrt(Math.max(1, nodeCount))))
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
function noverlapMaxIterations(nodeCount) {
|
|
86
|
+
for (const step of NOVERLAP_SETTINGS.iterationLadder) {
|
|
87
|
+
if (nodeCount <= step.maxNodes) return step.iterations;
|
|
88
|
+
}
|
|
89
|
+
return NOVERLAP_SETTINGS.iterationsFloor;
|
|
90
|
+
}
|
|
91
|
+
var COMPONENT_PACKING_SPACING_FACTOR = 3;
|
|
92
|
+
function siteCrawlGraphLayoutSettingsFingerprint(settings) {
|
|
93
|
+
return crypto.createHash("sha256").update(JSON.stringify(settings)).digest("hex").slice(0, 8);
|
|
94
|
+
}
|
|
95
|
+
var SITE_CRAWL_GRAPH_LAYOUT_ALGORITHM = "site-health-fa2-v5";
|
|
96
|
+
var SITE_CRAWL_GRAPH_LAYOUT_VERSION = `${SITE_CRAWL_GRAPH_LAYOUT_ALGORITHM}-${siteCrawlGraphLayoutSettingsFingerprint({
|
|
97
|
+
forceAtlas: FORCE_ATLAS_SETTINGS,
|
|
98
|
+
noverlap: NOVERLAP_SETTINGS,
|
|
99
|
+
nodeSizeExtentFraction: NODE_SIZE_EXTENT_FRACTION,
|
|
100
|
+
componentPackingSpacingFactor: COMPONENT_PACKING_SPACING_FACTOR,
|
|
101
|
+
templateLinkRatioThreshold: TEMPLATE_LINK_RATIO_THRESHOLD
|
|
102
|
+
})}`;
|
|
103
|
+
var WORKER_SOURCE = String.raw`
|
|
104
|
+
const {parentPort, workerData} = require('node:worker_threads');
|
|
105
|
+
try {
|
|
106
|
+
const graphologyModule = require(workerData.graphologyPath);
|
|
107
|
+
const forceAtlasModule = require(workerData.forceAtlasPath);
|
|
108
|
+
const noverlapModule = require(workerData.noverlapPath);
|
|
109
|
+
const MultiDirectedGraph = graphologyModule.MultiDirectedGraph;
|
|
110
|
+
const forceAtlas2 = forceAtlasModule.default || forceAtlasModule;
|
|
111
|
+
const noverlap = noverlapModule.default || noverlapModule;
|
|
112
|
+
const graph = new MultiDirectedGraph({allowSelfLoops: true});
|
|
113
|
+
for (const node of workerData.input.nodes) {
|
|
114
|
+
graph.addNode(node.nodeKey, {x: node.x, y: node.y});
|
|
115
|
+
}
|
|
116
|
+
for (const edge of workerData.input.edges) {
|
|
117
|
+
graph.addDirectedEdgeWithKey(edge.edgeKey, edge.sourceNodeKey, edge.targetNodeKey, {
|
|
118
|
+
weight: 1 + Math.log2(Math.max(1, edge.occurrences)),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
if (graph.order > 1 && graph.size > 0) {
|
|
122
|
+
forceAtlas2.assign(graph, {
|
|
123
|
+
iterations: workerData.input.iterations,
|
|
124
|
+
settings: workerData.settings,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
let nodeSize = 0;
|
|
128
|
+
// Anti-overlap runs on the spatialization ForceAtlas2 just produced. Node
|
|
129
|
+
// size is uniform and derived from the graph's own extent, so the pass is
|
|
130
|
+
// scale invariant and stays deterministic for a fixed input.
|
|
131
|
+
if (graph.order > 1) {
|
|
132
|
+
let sumX = 0;
|
|
133
|
+
let sumY = 0;
|
|
134
|
+
graph.forEachNode(function (nodeKey, attributes) {
|
|
135
|
+
sumX += attributes.x;
|
|
136
|
+
sumY += attributes.y;
|
|
137
|
+
});
|
|
138
|
+
const centerX = sumX / graph.order;
|
|
139
|
+
const centerY = sumY / graph.order;
|
|
140
|
+
let extent = 0;
|
|
141
|
+
graph.forEachNode(function (nodeKey, attributes) {
|
|
142
|
+
extent = Math.max(extent, Math.abs(attributes.x - centerX), Math.abs(attributes.y - centerY));
|
|
143
|
+
});
|
|
144
|
+
if (Number.isFinite(extent) && extent > 0) {
|
|
145
|
+
// Whichever bound is tighter: one render-sized node, or the largest disc
|
|
146
|
+
// that still leaves the graph mostly empty space at this node count.
|
|
147
|
+
// Mirrors siteCrawlGraphNodeSize; a test asserts the two agree.
|
|
148
|
+
const byExtent = extent * workerData.nodeSizeExtentFraction;
|
|
149
|
+
const byArea = Math.sqrt(
|
|
150
|
+
(workerData.noverlap.maxPackingFraction * Math.pow(2 * extent, 2)) / graph.order,
|
|
151
|
+
) / 2;
|
|
152
|
+
nodeSize = Math.min(byExtent, byArea);
|
|
153
|
+
graph.forEachNode(function (nodeKey) {
|
|
154
|
+
graph.setNodeAttribute(nodeKey, 'size', nodeSize);
|
|
155
|
+
});
|
|
156
|
+
noverlap.assign(graph, {
|
|
157
|
+
maxIterations: workerData.noverlap.maxIterations,
|
|
158
|
+
settings: {
|
|
159
|
+
gridSize: workerData.noverlap.gridSize,
|
|
160
|
+
margin: nodeSize * workerData.noverlap.marginFactor,
|
|
161
|
+
expansion: workerData.noverlap.expansion,
|
|
162
|
+
ratio: workerData.noverlap.ratio,
|
|
163
|
+
speed: workerData.noverlap.speed,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const positions = [];
|
|
169
|
+
graph.forEachNode((nodeKey, attributes) => {
|
|
170
|
+
positions.push({nodeKey, x: attributes.x, y: attributes.y});
|
|
171
|
+
});
|
|
172
|
+
parentPort.postMessage({ok: true, positions, nodeSize});
|
|
173
|
+
} catch (error) {
|
|
174
|
+
parentPort.postMessage({
|
|
175
|
+
ok: false,
|
|
176
|
+
error: error && typeof error.message === 'string' ? error.message : String(error),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
`;
|
|
180
|
+
var GraphLayoutTimeoutError = class extends Error {
|
|
181
|
+
name = "GraphLayoutTimeoutError";
|
|
182
|
+
};
|
|
183
|
+
function hashUnit(value) {
|
|
184
|
+
let hash = 2166136261;
|
|
185
|
+
for (let i = 0; i < value.length; i++) {
|
|
186
|
+
hash ^= value.charCodeAt(i);
|
|
187
|
+
hash = Math.imul(hash, 16777619);
|
|
188
|
+
}
|
|
189
|
+
return (hash >>> 0) / 4294967296;
|
|
190
|
+
}
|
|
191
|
+
function pathSection(path) {
|
|
192
|
+
return path.split("/").filter(Boolean)[0] ?? "__root__";
|
|
193
|
+
}
|
|
194
|
+
function seedSiteCrawlGraphNodes(nodes, rootNodeKey, priorPositions) {
|
|
195
|
+
return [...nodes].sort((a, b) => a.sampleRank - b.sampleRank || a.nodeKey.localeCompare(b.nodeKey)).map((node) => {
|
|
196
|
+
const prior = priorPositions?.get(node.nodeKey);
|
|
197
|
+
if (prior && Number.isFinite(prior.x) && Number.isFinite(prior.y)) {
|
|
198
|
+
return {
|
|
199
|
+
...node,
|
|
200
|
+
x: prior.x * SITE_CRAWL_GRAPH_SEED_SCALE,
|
|
201
|
+
y: prior.y * SITE_CRAWL_GRAPH_SEED_SCALE
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (node.nodeKey === rootNodeKey) return { ...node, x: 0, y: 0 };
|
|
205
|
+
const section = pathSection(node.path);
|
|
206
|
+
const sectionAngle = hashUnit(`section:${section}`) * Math.PI * 2;
|
|
207
|
+
const sectionRadius = section === "__root__" ? 24 : 100;
|
|
208
|
+
const clusterX = Math.cos(sectionAngle) * sectionRadius;
|
|
209
|
+
const clusterY = Math.sin(sectionAngle) * sectionRadius;
|
|
210
|
+
const localAngle = hashUnit(`node-angle:${node.nodeKey}`) * Math.PI * 2;
|
|
211
|
+
const depth = Math.max(0, Math.min(12, node.depth ?? 4));
|
|
212
|
+
const localRadius = 4 + depth * 2.5 + Math.sqrt(hashUnit(`node-radius:${node.nodeKey}`)) * 14;
|
|
213
|
+
return {
|
|
214
|
+
...node,
|
|
215
|
+
x: clusterX + Math.cos(localAngle) * localRadius,
|
|
216
|
+
y: clusterY + Math.sin(localAngle) * localRadius
|
|
217
|
+
};
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function adaptiveForceAtlasIterations(nodeCount, edgeCount) {
|
|
221
|
+
if (nodeCount <= 100 && edgeCount <= 500) return 100;
|
|
222
|
+
if (nodeCount <= 500 && edgeCount <= 2500) return 70;
|
|
223
|
+
if (nodeCount <= 2e3 && edgeCount <= 1e4) return 40;
|
|
224
|
+
if (nodeCount <= 5e3 && edgeCount <= 2e4) return 24;
|
|
225
|
+
if (nodeCount <= 1e4 && edgeCount <= 35e3) return 12;
|
|
226
|
+
return 6;
|
|
227
|
+
}
|
|
228
|
+
async function computeForceAtlasPositions(input, options = {}) {
|
|
229
|
+
if (input.nodes.length <= 1 || input.edges.length === 0) {
|
|
230
|
+
const positions = input.nodes.map(({ nodeKey, x, y }) => ({ nodeKey, x, y }));
|
|
231
|
+
return { positions, nodeSize: siteCrawlGraphNodeSize(siteCrawlGraphExtent(positions), positions.length) };
|
|
232
|
+
}
|
|
233
|
+
const requireFromPackage = createRequire(import.meta.url);
|
|
234
|
+
const worker = new Worker(WORKER_SOURCE, {
|
|
235
|
+
eval: true,
|
|
236
|
+
workerData: {
|
|
237
|
+
graphologyPath: requireFromPackage.resolve("graphology"),
|
|
238
|
+
forceAtlasPath: requireFromPackage.resolve("graphology-layout-forceatlas2"),
|
|
239
|
+
noverlapPath: requireFromPackage.resolve("graphology-layout-noverlap"),
|
|
240
|
+
input,
|
|
241
|
+
settings: FORCE_ATLAS_SETTINGS,
|
|
242
|
+
noverlap: {
|
|
243
|
+
...NOVERLAP_SETTINGS,
|
|
244
|
+
gridSize: noverlapGridSize(input.nodes.length),
|
|
245
|
+
maxIterations: noverlapMaxIterations(input.nodes.length)
|
|
246
|
+
},
|
|
247
|
+
nodeSizeExtentFraction: NODE_SIZE_EXTENT_FRACTION
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
const timeoutMs = options.timeoutMs ?? SITE_CRAWL_GRAPH_LAYOUT_TIMEOUT_MS;
|
|
251
|
+
return await new Promise((resolve, reject) => {
|
|
252
|
+
let settled = false;
|
|
253
|
+
const finish = (result) => {
|
|
254
|
+
if (settled) return;
|
|
255
|
+
settled = true;
|
|
256
|
+
clearTimeout(timer);
|
|
257
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
258
|
+
void worker.terminate();
|
|
259
|
+
if (result.error) reject(result.error instanceof Error ? result.error : new Error("Graph layout failed with a non-Error reason", { cause: result.error }));
|
|
260
|
+
else resolve({ positions: result.positions ?? [], nodeSize: result.nodeSize ?? 0 });
|
|
261
|
+
};
|
|
262
|
+
const onAbort = () => finish({ error: options.signal?.reason ?? new DOMException("Layout aborted", "AbortError") });
|
|
263
|
+
const timer = setTimeout(() => finish({ error: new GraphLayoutTimeoutError(`Graph layout exceeded ${timeoutMs}ms`) }), timeoutMs);
|
|
264
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
265
|
+
if (options.signal?.aborted) return onAbort();
|
|
266
|
+
worker.once("message", (message) => {
|
|
267
|
+
if (!message.ok) finish({ error: new Error(message.error ?? "Graph layout worker failed") });
|
|
268
|
+
else finish({ positions: message.positions, nodeSize: message.nodeSize });
|
|
269
|
+
});
|
|
270
|
+
worker.once("error", (error) => finish({ error }));
|
|
271
|
+
worker.once("exit", (code) => {
|
|
272
|
+
if (code !== 0 && !settled) finish({ error: new Error(`Graph layout worker exited with code ${code}`) });
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function siteCrawlGraphComponents(nodeKeys, edges) {
|
|
277
|
+
const parent = new Map(nodeKeys.map((key) => [key, key]));
|
|
278
|
+
const find = (key) => {
|
|
279
|
+
let root = key;
|
|
280
|
+
while (parent.get(root) !== root) root = parent.get(root);
|
|
281
|
+
let cursor = key;
|
|
282
|
+
while (parent.get(cursor) !== root) {
|
|
283
|
+
const next = parent.get(cursor);
|
|
284
|
+
parent.set(cursor, root);
|
|
285
|
+
cursor = next;
|
|
286
|
+
}
|
|
287
|
+
return root;
|
|
288
|
+
};
|
|
289
|
+
for (const edge of edges) {
|
|
290
|
+
if (!parent.has(edge.sourceNodeKey) || !parent.has(edge.targetNodeKey)) continue;
|
|
291
|
+
const sourceRoot = find(edge.sourceNodeKey);
|
|
292
|
+
const targetRoot = find(edge.targetNodeKey);
|
|
293
|
+
if (sourceRoot === targetRoot) continue;
|
|
294
|
+
if (sourceRoot < targetRoot) parent.set(targetRoot, sourceRoot);
|
|
295
|
+
else parent.set(sourceRoot, targetRoot);
|
|
296
|
+
}
|
|
297
|
+
const members = /* @__PURE__ */ new Map();
|
|
298
|
+
for (const key of [...nodeKeys].sort()) {
|
|
299
|
+
const root = find(key);
|
|
300
|
+
const group = members.get(root);
|
|
301
|
+
if (group) group.push(key);
|
|
302
|
+
else members.set(root, [key]);
|
|
303
|
+
}
|
|
304
|
+
return [...members.values()].sort((left, right) => right.length - left.length || (left[0] < right[0] ? -1 : left[0] > right[0] ? 1 : 0));
|
|
305
|
+
}
|
|
306
|
+
function describeComponent(nodeKeys, byKey) {
|
|
307
|
+
let sumX = 0;
|
|
308
|
+
let sumY = 0;
|
|
309
|
+
for (const nodeKey of nodeKeys) {
|
|
310
|
+
const position = byKey.get(nodeKey);
|
|
311
|
+
sumX += position.x;
|
|
312
|
+
sumY += position.y;
|
|
313
|
+
}
|
|
314
|
+
const centerX = sumX / nodeKeys.length;
|
|
315
|
+
const centerY = sumY / nodeKeys.length;
|
|
316
|
+
let radius = 0;
|
|
317
|
+
for (const nodeKey of nodeKeys) {
|
|
318
|
+
const position = byKey.get(nodeKey);
|
|
319
|
+
radius = Math.max(radius, Math.hypot(position.x - centerX, position.y - centerY));
|
|
320
|
+
}
|
|
321
|
+
return { nodeKeys, centerX, centerY, radius };
|
|
322
|
+
}
|
|
323
|
+
function packSiteCrawlGraphComponents(positions, edges, nodeSpacing) {
|
|
324
|
+
const components = siteCrawlGraphComponents(positions.map((position) => position.nodeKey), edges);
|
|
325
|
+
if (components.length <= 1) return [...positions];
|
|
326
|
+
const byKey = new Map(positions.map((position) => [position.nodeKey, position]));
|
|
327
|
+
if (positions.some((position) => !Number.isFinite(position.x) || !Number.isFinite(position.y))) {
|
|
328
|
+
return [...positions];
|
|
329
|
+
}
|
|
330
|
+
const described = components.map((nodeKeys) => describeComponent(nodeKeys, byKey));
|
|
331
|
+
const [anchor, ...rest] = described;
|
|
332
|
+
const spacing = Math.max(nodeSpacing, Number.EPSILON) * COMPONENT_PACKING_SPACING_FACTOR;
|
|
333
|
+
const ordered = [...rest].sort((left, right) => right.radius - left.radius || (left.nodeKeys[0] < right.nodeKeys[0] ? -1 : left.nodeKeys[0] > right.nodeKeys[0] ? 1 : 0));
|
|
334
|
+
const moved = /* @__PURE__ */ new Map();
|
|
335
|
+
let ringRadius = anchor.radius + spacing;
|
|
336
|
+
let ringPitch = 0;
|
|
337
|
+
let placedOnRing = 0;
|
|
338
|
+
let ringCapacity = 0;
|
|
339
|
+
for (const component of ordered) {
|
|
340
|
+
if (placedOnRing >= ringCapacity) {
|
|
341
|
+
ringPitch = 2 * component.radius + spacing;
|
|
342
|
+
ringRadius += ringPitch / 2 + (placedOnRing === 0 ? 0 : ringPitch / 2);
|
|
343
|
+
ringCapacity = Math.max(1, Math.floor(2 * Math.PI * ringRadius / ringPitch));
|
|
344
|
+
placedOnRing = 0;
|
|
345
|
+
}
|
|
346
|
+
const angle = placedOnRing / ringCapacity * Math.PI * 2;
|
|
347
|
+
placedOnRing += 1;
|
|
348
|
+
const targetX = anchor.centerX + Math.cos(angle) * ringRadius;
|
|
349
|
+
const targetY = anchor.centerY + Math.sin(angle) * ringRadius;
|
|
350
|
+
const shiftX = targetX - component.centerX;
|
|
351
|
+
const shiftY = targetY - component.centerY;
|
|
352
|
+
for (const nodeKey of component.nodeKeys) {
|
|
353
|
+
const position = byKey.get(nodeKey);
|
|
354
|
+
moved.set(nodeKey, { nodeKey, x: position.x + shiftX, y: position.y + shiftY });
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return positions.map((position) => moved.get(position.nodeKey) ?? position);
|
|
358
|
+
}
|
|
359
|
+
function normalizePositions(seededNodes, computed, rootNodeKey) {
|
|
360
|
+
const byKey = new Map(computed.map((position) => [position.nodeKey, position]));
|
|
361
|
+
if (byKey.size !== seededNodes.length) throw new Error("Graph layout returned an incomplete node set");
|
|
362
|
+
const root = rootNodeKey ? byKey.get(rootNodeKey) : null;
|
|
363
|
+
const centerX = root?.x ?? computed.reduce((sum, position) => sum + position.x, 0) / Math.max(1, computed.length);
|
|
364
|
+
const centerY = root?.y ?? computed.reduce((sum, position) => sum + position.y, 0) / Math.max(1, computed.length);
|
|
365
|
+
if (!Number.isFinite(centerX) || !Number.isFinite(centerY)) throw new Error("Graph layout returned a non-finite center");
|
|
366
|
+
let scale = 0;
|
|
367
|
+
for (const position of computed) {
|
|
368
|
+
if (!Number.isFinite(position.x) || !Number.isFinite(position.y)) throw new Error("Graph layout returned non-finite coordinates");
|
|
369
|
+
scale = Math.max(scale, Math.abs(position.x - centerX), Math.abs(position.y - centerY));
|
|
370
|
+
}
|
|
371
|
+
scale = scale > 0 ? scale : 1;
|
|
372
|
+
return seededNodes.map((node) => {
|
|
373
|
+
const position = byKey.get(node.nodeKey);
|
|
374
|
+
if (!position) throw new Error(`Graph layout omitted ${node.nodeKey}`);
|
|
375
|
+
return {
|
|
376
|
+
nodeKey: node.nodeKey,
|
|
377
|
+
sampleRank: node.sampleRank,
|
|
378
|
+
x: Math.round((position.x - centerX) / scale * 1e6) / 1e6,
|
|
379
|
+
y: Math.round((position.y - centerY) / scale * 1e6) / 1e6
|
|
380
|
+
};
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
async function layoutSiteCrawlGraphInput(input, options = {}) {
|
|
384
|
+
options.signal?.throwIfAborted();
|
|
385
|
+
if (input.nodes.length === 0) {
|
|
386
|
+
return {
|
|
387
|
+
state: "unavailable",
|
|
388
|
+
failureCode: "empty-crawl",
|
|
389
|
+
totalNodes: input.totalNodes,
|
|
390
|
+
totalEdges: input.totalEdges,
|
|
391
|
+
totalTemplateEdges: input.totalTemplateEdges,
|
|
392
|
+
nodeCount: 0,
|
|
393
|
+
edgeCount: 0,
|
|
394
|
+
nodes: [],
|
|
395
|
+
edges: []
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
const layoutEdges = input.edges.filter((edge) => !edge.isTemplate);
|
|
399
|
+
const seeded = seedSiteCrawlGraphNodes(input.nodes, input.rootNodeKey, input.priorPositions);
|
|
400
|
+
const timeoutMs = options.timeoutMs ?? SITE_CRAWL_GRAPH_LAYOUT_TIMEOUT_MS;
|
|
401
|
+
const deadline = Date.now() + timeoutMs;
|
|
402
|
+
const assertWithinBudget = () => {
|
|
403
|
+
options.signal?.throwIfAborted();
|
|
404
|
+
if (Date.now() > deadline) throw new GraphLayoutTimeoutError(`Graph layout exceeded ${timeoutMs}ms`);
|
|
405
|
+
};
|
|
406
|
+
try {
|
|
407
|
+
const compute = options.computePositions ?? computeForceAtlasPositions;
|
|
408
|
+
const { positions, nodeSize } = await compute({
|
|
409
|
+
nodes: seeded,
|
|
410
|
+
edges: layoutEdges,
|
|
411
|
+
iterations: adaptiveForceAtlasIterations(input.nodes.length, layoutEdges.length)
|
|
412
|
+
}, { signal: options.signal, timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
413
|
+
assertWithinBudget();
|
|
414
|
+
const framed = packSiteCrawlGraphComponents(positions, layoutEdges, nodeSize);
|
|
415
|
+
assertWithinBudget();
|
|
416
|
+
return {
|
|
417
|
+
state: "ready",
|
|
418
|
+
layoutVersion: SITE_CRAWL_GRAPH_LAYOUT_VERSION,
|
|
419
|
+
totalNodes: input.totalNodes,
|
|
420
|
+
totalEdges: input.totalEdges,
|
|
421
|
+
totalTemplateEdges: input.totalTemplateEdges,
|
|
422
|
+
nodeCount: input.nodes.length,
|
|
423
|
+
edgeCount: input.edges.length,
|
|
424
|
+
templateLinksExcluded: true,
|
|
425
|
+
nodes: normalizePositions(seeded, framed, input.rootNodeKey),
|
|
426
|
+
edges: input.edges.map((edge, sampleRank) => ({ ...edge, sampleRank }))
|
|
427
|
+
};
|
|
428
|
+
} catch (error) {
|
|
429
|
+
if (options.signal?.aborted) options.signal.throwIfAborted();
|
|
430
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
431
|
+
return {
|
|
432
|
+
state: "unavailable",
|
|
433
|
+
failureCode: error instanceof GraphLayoutTimeoutError ? "layout-timeout" : "layout-error",
|
|
434
|
+
totalNodes: input.totalNodes,
|
|
435
|
+
totalEdges: input.totalEdges,
|
|
436
|
+
totalTemplateEdges: input.totalTemplateEdges,
|
|
437
|
+
nodeCount: 0,
|
|
438
|
+
edgeCount: 0,
|
|
439
|
+
nodes: [],
|
|
440
|
+
edges: []
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
function loadPriorSiteCrawlGraphPositions(db, scope) {
|
|
445
|
+
const prior = db.select({
|
|
446
|
+
runId: siteCrawlSnapshots.runId,
|
|
447
|
+
attemptId: siteCrawlSnapshots.attemptId
|
|
448
|
+
}).from(siteCrawlSnapshots).innerJoin(siteCrawlGraphLayouts, and(
|
|
449
|
+
eq(siteCrawlGraphLayouts.projectId, siteCrawlSnapshots.projectId),
|
|
450
|
+
eq(siteCrawlGraphLayouts.runId, siteCrawlSnapshots.runId),
|
|
451
|
+
eq(siteCrawlGraphLayouts.attemptId, siteCrawlSnapshots.attemptId)
|
|
452
|
+
)).where(and(
|
|
453
|
+
eq(siteCrawlSnapshots.projectId, scope.projectId),
|
|
454
|
+
ne(siteCrawlSnapshots.runId, scope.runId),
|
|
455
|
+
eq(siteCrawlSnapshots.complete, true),
|
|
456
|
+
eq(siteCrawlGraphLayouts.state, "ready"),
|
|
457
|
+
eq(siteCrawlGraphLayouts.layoutVersion, SITE_CRAWL_GRAPH_LAYOUT_VERSION)
|
|
458
|
+
)).orderBy(desc(siteCrawlSnapshots.createdAt), desc(siteCrawlSnapshots.id)).limit(1).get();
|
|
459
|
+
if (!prior?.attemptId) return /* @__PURE__ */ new Map();
|
|
460
|
+
return new Map(db.select({
|
|
461
|
+
nodeKey: siteCrawlGraphNodes.nodeKey,
|
|
462
|
+
x: siteCrawlGraphNodes.x,
|
|
463
|
+
y: siteCrawlGraphNodes.y
|
|
464
|
+
}).from(siteCrawlGraphNodes).where(and(
|
|
465
|
+
eq(siteCrawlGraphNodes.projectId, scope.projectId),
|
|
466
|
+
eq(siteCrawlGraphNodes.runId, prior.runId),
|
|
467
|
+
eq(siteCrawlGraphNodes.attemptId, prior.attemptId)
|
|
468
|
+
)).all().map((node) => [node.nodeKey, { x: node.x, y: node.y }]));
|
|
469
|
+
}
|
|
470
|
+
async function prepareSiteCrawlGraphLayout(db, scope, options = {}) {
|
|
471
|
+
let totalNodes = 0;
|
|
472
|
+
let totalEdges = 0;
|
|
473
|
+
let totalTemplateEdges = 0;
|
|
474
|
+
try {
|
|
475
|
+
const assertNotAborted = () => options.signal?.throwIfAborted();
|
|
476
|
+
assertNotAborted();
|
|
477
|
+
const pageScope = [
|
|
478
|
+
eq(siteCrawlPages.projectId, scope.projectId),
|
|
479
|
+
eq(siteCrawlPages.runId, scope.runId),
|
|
480
|
+
eq(siteCrawlPages.attemptId, scope.attemptId)
|
|
481
|
+
];
|
|
482
|
+
const edgeScope = [
|
|
483
|
+
eq(siteCrawlEdges.projectId, scope.projectId),
|
|
484
|
+
eq(siteCrawlEdges.runId, scope.runId),
|
|
485
|
+
eq(siteCrawlEdges.attemptId, scope.attemptId),
|
|
486
|
+
eq(siteCrawlEdges.internal, true),
|
|
487
|
+
eq(siteCrawlEdges.relation, "anchor")
|
|
488
|
+
];
|
|
489
|
+
totalNodes = db.select({ value: count() }).from(siteCrawlPages).where(and(...pageScope)).get()?.value ?? 0;
|
|
490
|
+
assertNotAborted();
|
|
491
|
+
const countGraphCompatibleEdges = (...extra) => db.select({ value: count() }).from(siteCrawlEdges).innerJoin(graphSourcePage, and(
|
|
492
|
+
eq(graphSourcePage.projectId, scope.projectId),
|
|
493
|
+
eq(graphSourcePage.runId, scope.runId),
|
|
494
|
+
eq(graphSourcePage.attemptId, scope.attemptId),
|
|
495
|
+
eq(graphSourcePage.nodeKey, siteCrawlEdges.sourceNodeKey)
|
|
496
|
+
)).innerJoin(graphTargetPage, and(
|
|
497
|
+
eq(graphTargetPage.projectId, scope.projectId),
|
|
498
|
+
eq(graphTargetPage.runId, scope.runId),
|
|
499
|
+
eq(graphTargetPage.attemptId, scope.attemptId),
|
|
500
|
+
eq(graphTargetPage.nodeKey, siteCrawlEdges.targetNodeKey)
|
|
501
|
+
)).where(and(...edgeScope, ...extra)).get()?.value ?? 0;
|
|
502
|
+
totalEdges = countGraphCompatibleEdges();
|
|
503
|
+
assertNotAborted();
|
|
504
|
+
totalTemplateEdges = countGraphCompatibleEdges(eq(siteCrawlEdges.isTemplate, true));
|
|
505
|
+
assertNotAborted();
|
|
506
|
+
const graphSeedColumns = {
|
|
507
|
+
nodeKey: siteCrawlPages.nodeKey,
|
|
508
|
+
path: siteCrawlPages.path,
|
|
509
|
+
depth: siteCrawlPages.depth
|
|
510
|
+
};
|
|
511
|
+
const rootRows = db.select(graphSeedColumns).from(siteCrawlPages).where(and(...pageScope, eq(siteCrawlPages.url, scope.rootUrl))).orderBy(asc(siteCrawlPages.nodeKey)).limit(1).all();
|
|
512
|
+
assertNotAborted();
|
|
513
|
+
const retainedRows = [...rootRows];
|
|
514
|
+
if (retainedRows.length < SITE_CRAWL_GRAPH_MAX_NODES) {
|
|
515
|
+
retainedRows.push(...db.select(graphSeedColumns).from(siteCrawlPages).where(and(...pageScope, eq(siteCrawlPages.depth, 0), ne(siteCrawlPages.url, scope.rootUrl))).orderBy(asc(siteCrawlPages.nodeKey)).limit(SITE_CRAWL_GRAPH_MAX_NODES - retainedRows.length).all());
|
|
516
|
+
}
|
|
517
|
+
assertNotAborted();
|
|
518
|
+
if (retainedRows.length < SITE_CRAWL_GRAPH_MAX_NODES) {
|
|
519
|
+
retainedRows.push(...db.select(graphSeedColumns).from(siteCrawlPages).where(and(
|
|
520
|
+
...pageScope,
|
|
521
|
+
or(isNull(siteCrawlPages.depth), ne(siteCrawlPages.depth, 0)),
|
|
522
|
+
ne(siteCrawlPages.url, scope.rootUrl)
|
|
523
|
+
)).orderBy(desc(siteCrawlPages.linkScoreNormalized), asc(siteCrawlPages.nodeKey)).limit(SITE_CRAWL_GRAPH_MAX_NODES - retainedRows.length).all());
|
|
524
|
+
}
|
|
525
|
+
assertNotAborted();
|
|
526
|
+
const nodes = retainedRows.map((row, sampleRank) => ({
|
|
527
|
+
nodeKey: row.nodeKey,
|
|
528
|
+
path: row.path,
|
|
529
|
+
depth: row.depth,
|
|
530
|
+
sampleRank
|
|
531
|
+
}));
|
|
532
|
+
const rootNodeKey = rootRows[0]?.nodeKey ?? null;
|
|
533
|
+
const priorPositions = loadPriorSiteCrawlGraphPositions(db, scope);
|
|
534
|
+
assertNotAborted();
|
|
535
|
+
const selectedKeys = JSON.stringify(nodes.map((node) => node.nodeKey));
|
|
536
|
+
const edgeRows = nodes.length === 0 ? [] : db.all(sql`
|
|
537
|
+
WITH selected(node_key) AS (SELECT value FROM json_each(${selectedKeys}))
|
|
538
|
+
SELECT
|
|
539
|
+
edge.edge_key AS edgeKey,
|
|
540
|
+
edge.source_node_key AS sourceNodeKey,
|
|
541
|
+
edge.target_node_key AS targetNodeKey,
|
|
542
|
+
edge.followable AS followable,
|
|
543
|
+
edge.occurrences AS occurrences,
|
|
544
|
+
edge.is_template AS isTemplate
|
|
545
|
+
FROM site_crawl_edges AS edge
|
|
546
|
+
INNER JOIN selected AS source ON source.node_key = edge.source_node_key
|
|
547
|
+
INNER JOIN selected AS target ON target.node_key = edge.target_node_key
|
|
548
|
+
WHERE edge.project_id = ${scope.projectId}
|
|
549
|
+
AND edge.run_id = ${scope.runId}
|
|
550
|
+
AND edge.attempt_id = ${scope.attemptId}
|
|
551
|
+
AND edge.internal = 1
|
|
552
|
+
AND edge.relation = 'anchor'
|
|
553
|
+
ORDER BY edge.occurrences DESC, edge.edge_key ASC
|
|
554
|
+
LIMIT ${SITE_CRAWL_GRAPH_MAX_EDGES}
|
|
555
|
+
`);
|
|
556
|
+
assertNotAborted();
|
|
557
|
+
return await layoutSiteCrawlGraphInput({
|
|
558
|
+
totalNodes,
|
|
559
|
+
totalEdges,
|
|
560
|
+
totalTemplateEdges,
|
|
561
|
+
nodes,
|
|
562
|
+
edges: edgeRows.map((row) => ({ ...row, isTemplate: Boolean(row.isTemplate) })),
|
|
563
|
+
rootNodeKey,
|
|
564
|
+
priorPositions
|
|
565
|
+
}, options);
|
|
566
|
+
} catch (error) {
|
|
567
|
+
if (options.signal?.aborted) options.signal.throwIfAborted();
|
|
568
|
+
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
569
|
+
return {
|
|
570
|
+
state: "unavailable",
|
|
571
|
+
failureCode: "layout-error",
|
|
572
|
+
totalNodes,
|
|
573
|
+
totalEdges,
|
|
574
|
+
totalTemplateEdges,
|
|
575
|
+
nodeCount: 0,
|
|
576
|
+
edgeCount: 0,
|
|
577
|
+
nodes: [],
|
|
578
|
+
edges: []
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
function persistSiteCrawlGraphLayout(db, scope, layout, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
583
|
+
db.transaction((tx) => {
|
|
584
|
+
tx.delete(siteCrawlGraphLayouts).where(and(
|
|
585
|
+
eq(siteCrawlGraphLayouts.projectId, scope.projectId),
|
|
586
|
+
eq(siteCrawlGraphLayouts.runId, scope.runId),
|
|
587
|
+
eq(siteCrawlGraphLayouts.attemptId, scope.attemptId)
|
|
588
|
+
)).run();
|
|
589
|
+
tx.insert(siteCrawlGraphLayouts).values({
|
|
590
|
+
id: crypto.randomUUID(),
|
|
591
|
+
projectId: scope.projectId,
|
|
592
|
+
runId: scope.runId,
|
|
593
|
+
attemptId: scope.attemptId,
|
|
594
|
+
state: layout.state,
|
|
595
|
+
layoutVersion: layout.state === "ready" ? layout.layoutVersion : null,
|
|
596
|
+
failureCode: layout.state === "unavailable" ? layout.failureCode : null,
|
|
597
|
+
totalNodes: layout.totalNodes,
|
|
598
|
+
totalEdges: layout.totalEdges,
|
|
599
|
+
totalTemplateEdges: layout.totalTemplateEdges,
|
|
600
|
+
nodeCount: layout.nodeCount,
|
|
601
|
+
edgeCount: layout.edgeCount,
|
|
602
|
+
templateLinksExcluded: layout.state === "ready",
|
|
603
|
+
createdAt: now,
|
|
604
|
+
updatedAt: now
|
|
605
|
+
}).run();
|
|
606
|
+
if (layout.state !== "ready") return;
|
|
607
|
+
const batchSize = 250;
|
|
608
|
+
for (let offset = 0; offset < layout.nodes.length; offset += batchSize) {
|
|
609
|
+
tx.insert(siteCrawlGraphNodes).values(layout.nodes.slice(offset, offset + batchSize).map((node) => ({
|
|
610
|
+
id: crypto.randomUUID(),
|
|
611
|
+
projectId: scope.projectId,
|
|
612
|
+
runId: scope.runId,
|
|
613
|
+
attemptId: scope.attemptId,
|
|
614
|
+
nodeKey: node.nodeKey,
|
|
615
|
+
sampleRank: node.sampleRank,
|
|
616
|
+
x: node.x,
|
|
617
|
+
y: node.y,
|
|
618
|
+
createdAt: now
|
|
619
|
+
}))).run();
|
|
620
|
+
}
|
|
621
|
+
for (let offset = 0; offset < layout.edges.length; offset += batchSize) {
|
|
622
|
+
tx.insert(siteCrawlGraphEdges).values(layout.edges.slice(offset, offset + batchSize).map((edge) => ({
|
|
623
|
+
id: crypto.randomUUID(),
|
|
624
|
+
projectId: scope.projectId,
|
|
625
|
+
runId: scope.runId,
|
|
626
|
+
attemptId: scope.attemptId,
|
|
627
|
+
edgeKey: edge.edgeKey,
|
|
628
|
+
sampleRank: edge.sampleRank,
|
|
629
|
+
sourceNodeKey: edge.sourceNodeKey,
|
|
630
|
+
targetNodeKey: edge.targetNodeKey,
|
|
631
|
+
followable: edge.followable,
|
|
632
|
+
occurrences: edge.occurrences,
|
|
633
|
+
isTemplate: edge.isTemplate,
|
|
634
|
+
createdAt: now
|
|
635
|
+
}))).run();
|
|
636
|
+
}
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
function deleteSiteCrawlGraphLayout(db, scope) {
|
|
640
|
+
db.delete(siteCrawlGraphLayouts).where(and(
|
|
641
|
+
eq(siteCrawlGraphLayouts.projectId, scope.projectId),
|
|
642
|
+
eq(siteCrawlGraphLayouts.runId, scope.runId),
|
|
643
|
+
eq(siteCrawlGraphLayouts.attemptId, scope.attemptId)
|
|
644
|
+
)).run();
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
export {
|
|
648
|
+
layoutSiteCrawlGraphInput,
|
|
649
|
+
prepareSiteCrawlGraphLayout,
|
|
650
|
+
persistSiteCrawlGraphLayout,
|
|
651
|
+
deleteSiteCrawlGraphLayout
|
|
652
|
+
};
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BUNDLED_SKILL_NAMES,
|
|
3
|
-
PACKAGE_VERSION,
|
|
4
3
|
configExists,
|
|
5
4
|
installSkills,
|
|
6
5
|
loadConfigRaw,
|
|
7
6
|
saveConfigPatch
|
|
8
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IOP36L3C.js";
|
|
8
|
+
import {
|
|
9
|
+
PACKAGE_VERSION
|
|
10
|
+
} from "./chunk-AOJU5LKX.js";
|
|
9
11
|
import {
|
|
10
12
|
SKILL_MANIFEST_FILENAME,
|
|
11
13
|
SkillsClients
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-2U5IW3G5.js";
|
|
13
15
|
|
|
14
16
|
// src/skills-autosync.ts
|
|
15
17
|
import fs from "fs";
|