site-agent-pro 1.0.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.
Files changed (81) hide show
  1. package/README.md +689 -0
  2. package/dist/auth/credentialStore.js +62 -0
  3. package/dist/auth/inbox.js +193 -0
  4. package/dist/auth/profile.js +379 -0
  5. package/dist/auth/runner.js +1124 -0
  6. package/dist/backend/dashboardData.js +194 -0
  7. package/dist/backend/runArtifacts.js +48 -0
  8. package/dist/backend/runRepository.js +93 -0
  9. package/dist/bin.js +2 -0
  10. package/dist/cli/backfillSiteChecks.js +143 -0
  11. package/dist/cli/run.js +309 -0
  12. package/dist/cli/trade.js +69 -0
  13. package/dist/config.js +199 -0
  14. package/dist/core/agentProfiles.js +55 -0
  15. package/dist/core/aggregateReport.js +382 -0
  16. package/dist/core/audit.js +30 -0
  17. package/dist/core/customTaskSuite.js +148 -0
  18. package/dist/core/evaluator.js +217 -0
  19. package/dist/core/executor.js +788 -0
  20. package/dist/core/fallbackReport.js +335 -0
  21. package/dist/core/formHeuristics.js +411 -0
  22. package/dist/core/gameplaySummary.js +164 -0
  23. package/dist/core/interaction.js +202 -0
  24. package/dist/core/pageState.js +201 -0
  25. package/dist/core/planner.js +1669 -0
  26. package/dist/core/processSubmissionBatch.js +204 -0
  27. package/dist/core/runAuditJob.js +170 -0
  28. package/dist/core/runner.js +2352 -0
  29. package/dist/core/siteBrief.js +107 -0
  30. package/dist/core/siteChecks.js +1526 -0
  31. package/dist/core/taskDirectives.js +279 -0
  32. package/dist/core/taskHeuristics.js +263 -0
  33. package/dist/dashboard/client.js +1256 -0
  34. package/dist/dashboard/contracts.js +95 -0
  35. package/dist/dashboard/narrative.js +277 -0
  36. package/dist/dashboard/server.js +458 -0
  37. package/dist/dashboard/theme.js +888 -0
  38. package/dist/index.js +84 -0
  39. package/dist/llm/client.js +188 -0
  40. package/dist/paystack/account.js +123 -0
  41. package/dist/paystack/client.js +100 -0
  42. package/dist/paystack/index.js +13 -0
  43. package/dist/paystack/test-paystack.js +83 -0
  44. package/dist/paystack/transfer.js +138 -0
  45. package/dist/paystack/types.js +74 -0
  46. package/dist/paystack/webhook.js +121 -0
  47. package/dist/prompts/browserAgent.js +124 -0
  48. package/dist/prompts/reviewer.js +71 -0
  49. package/dist/reporting/clickReplay.js +290 -0
  50. package/dist/reporting/html.js +930 -0
  51. package/dist/reporting/markdown.js +238 -0
  52. package/dist/reporting/template.js +1141 -0
  53. package/dist/schemas/types.js +361 -0
  54. package/dist/submissions/customTasks.js +196 -0
  55. package/dist/submissions/html.js +770 -0
  56. package/dist/submissions/model.js +56 -0
  57. package/dist/submissions/publicUrl.js +76 -0
  58. package/dist/submissions/service.js +74 -0
  59. package/dist/submissions/store.js +37 -0
  60. package/dist/submissions/types.js +65 -0
  61. package/dist/trade/engine.js +241 -0
  62. package/dist/trade/evm/erc20.js +44 -0
  63. package/dist/trade/extractor.js +148 -0
  64. package/dist/trade/policy.js +35 -0
  65. package/dist/trade/session.js +31 -0
  66. package/dist/trade/types.js +107 -0
  67. package/dist/trade/validator.js +148 -0
  68. package/dist/utils/files.js +59 -0
  69. package/dist/utils/log.js +24 -0
  70. package/dist/utils/playwrightCompat.js +14 -0
  71. package/dist/utils/time.js +3 -0
  72. package/dist/wallet/provider.js +345 -0
  73. package/dist/wallet/relay.js +129 -0
  74. package/dist/wallet/wallet.js +178 -0
  75. package/docs/01-installation.md +134 -0
  76. package/docs/02-running-your-first-audit.md +136 -0
  77. package/docs/03-configuration.md +233 -0
  78. package/docs/04-how-the-agent-thinks.md +41 -0
  79. package/docs/05-extending-personas-and-tasks.md +42 -0
  80. package/docs/06-hardening-for-production.md +92 -0
  81. package/package.json +60 -0
@@ -0,0 +1,290 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import sharp from "sharp";
4
+ import WebP from "node-webpmux";
5
+ export const CLICK_REPLAY_ARTIFACT = "click-replay.webp";
6
+ const MAX_REPLAY_WIDTH = 640;
7
+ const MAX_REPLAY_HEIGHT = 720;
8
+ const BEFORE_FRAME_DURATION_MS = 320;
9
+ const AFTER_FRAME_DURATION_MS = 760;
10
+ const MAX_REPLAY_DURATION_MS = 15000;
11
+ const MIN_FRAME_DURATION_MS = 80;
12
+ function normalizeText(value) {
13
+ return value.replace(/\s+/g, " ").trim();
14
+ }
15
+ function clamp(value, min, max) {
16
+ return Math.min(max, Math.max(min, value));
17
+ }
18
+ function escapeXml(value) {
19
+ return value
20
+ .replaceAll("&", "&")
21
+ .replaceAll("<", "&lt;")
22
+ .replaceAll(">", "&gt;")
23
+ .replaceAll("\"", "&quot;")
24
+ .replaceAll("'", "&apos;");
25
+ }
26
+ function truncateText(value, maxLength) {
27
+ if (value.length <= maxLength) {
28
+ return value;
29
+ }
30
+ return `${value.slice(0, Math.max(1, maxLength - 1)).trimEnd()}...`;
31
+ }
32
+ function describeReplayActivity(entry) {
33
+ const target = normalizeText(entry.result.clickIndicator?.targetLabel ?? entry.decision.target);
34
+ const fallbackTarget = target || "visible page";
35
+ const resultLabel = entry.result.success === false ? "failed" : entry.result.stop ? "stopped" : "done";
36
+ switch (entry.decision.action) {
37
+ case "click":
38
+ return `Step ${entry.step}: Click ${truncateText(fallbackTarget, 42)} (${resultLabel})`;
39
+ case "type":
40
+ return `Step ${entry.step}: Type in ${truncateText(fallbackTarget, 42)} (${resultLabel})`;
41
+ case "scroll":
42
+ return `Step ${entry.step}: Scroll page (${resultLabel})`;
43
+ case "wait":
44
+ return `Step ${entry.step}: Wait for page (${resultLabel})`;
45
+ case "back":
46
+ return `Step ${entry.step}: Go back (${resultLabel})`;
47
+ case "extract":
48
+ return `Step ${entry.step}: Capture page state (${resultLabel})`;
49
+ case "trade":
50
+ return `Step ${entry.step}: Wallet trade handoff (${resultLabel})`;
51
+ case "stop":
52
+ return `Step ${entry.step}: Stop path (${resultLabel})`;
53
+ default:
54
+ return `Step ${entry.step}: ${entry.decision.action} (${resultLabel})`;
55
+ }
56
+ }
57
+ function collectReplaySources(args) {
58
+ const sources = [];
59
+ const activities = args.taskResults.flatMap((task) => task.history.map((entry) => describeReplayActivity(entry)));
60
+ let activityIndex = 0;
61
+ function takeActivities() {
62
+ if (activities.length === 0) {
63
+ return [];
64
+ }
65
+ const remainingSources = Math.max(1, expectedVisualSourceCount - sources.length);
66
+ const remainingActivities = activities.length - activityIndex;
67
+ const takeCount = Math.max(1, Math.ceil(remainingActivities / remainingSources));
68
+ const nextActivities = activities.slice(activityIndex, activityIndex + takeCount);
69
+ activityIndex += nextActivities.length;
70
+ return nextActivities;
71
+ }
72
+ const expectedVisualSourceCount = args.taskResults.reduce((count, task) => count +
73
+ task.history.reduce((entryCount, entry) => {
74
+ if (entry.decision.action !== "click") {
75
+ return entryCount;
76
+ }
77
+ return (entryCount +
78
+ (entry.result.beforeScreenshotPath ? 1 : 0) +
79
+ (entry.result.afterScreenshotPath ? 1 : 0));
80
+ }, 0), 0);
81
+ for (const task of args.taskResults) {
82
+ for (const entry of task.history) {
83
+ if (entry.decision.action !== "click") {
84
+ continue;
85
+ }
86
+ const beforePath = entry.result.beforeScreenshotPath
87
+ ? path.join(args.runDir, entry.result.beforeScreenshotPath)
88
+ : null;
89
+ const afterPath = entry.result.afterScreenshotPath
90
+ ? path.join(args.runDir, entry.result.afterScreenshotPath)
91
+ : null;
92
+ if (beforePath && fs.existsSync(beforePath)) {
93
+ sources.push({
94
+ filePath: beforePath,
95
+ durationMs: BEFORE_FRAME_DURATION_MS,
96
+ annotations: takeActivities(),
97
+ ...(entry.result.clickIndicator ? { clickIndicator: entry.result.clickIndicator } : {})
98
+ });
99
+ }
100
+ if (afterPath && fs.existsSync(afterPath)) {
101
+ sources.push({
102
+ filePath: afterPath,
103
+ durationMs: AFTER_FRAME_DURATION_MS,
104
+ annotations: takeActivities()
105
+ });
106
+ }
107
+ }
108
+ }
109
+ for (let sourceIndex = 0; activityIndex < activities.length && sources.length > 0; sourceIndex += 1) {
110
+ const source = sources[sourceIndex % sources.length];
111
+ const activity = activities[activityIndex];
112
+ if (!source || !activity) {
113
+ break;
114
+ }
115
+ source.annotations = [...(source.annotations ?? []), activity];
116
+ activityIndex += 1;
117
+ }
118
+ return sources;
119
+ }
120
+ function normalizeDurations(sources) {
121
+ const totalDurationMs = sources.reduce((sum, source) => sum + source.durationMs, 0);
122
+ if (totalDurationMs <= MAX_REPLAY_DURATION_MS) {
123
+ return sources;
124
+ }
125
+ const scale = MAX_REPLAY_DURATION_MS / totalDurationMs;
126
+ return sources.map((source) => ({
127
+ ...source,
128
+ durationMs: Math.max(MIN_FRAME_DURATION_MS, Math.round(source.durationMs * scale))
129
+ }));
130
+ }
131
+ async function resolveReplayCanvas(sources) {
132
+ for (const source of sources) {
133
+ const metadata = await sharp(source.filePath).metadata();
134
+ if (!metadata.width || !metadata.height) {
135
+ continue;
136
+ }
137
+ const scale = Math.min(1, MAX_REPLAY_WIDTH / metadata.width, MAX_REPLAY_HEIGHT / metadata.height);
138
+ return {
139
+ width: Math.max(1, Math.round(metadata.width * scale)),
140
+ height: Math.max(1, Math.round(metadata.height * scale))
141
+ };
142
+ }
143
+ return null;
144
+ }
145
+ function resolveResizeTransform(args) {
146
+ const scale = Math.min(1, args.targetWidth / args.sourceWidth, args.targetHeight / args.sourceHeight);
147
+ const renderedWidth = Math.max(1, Math.round(args.sourceWidth * scale));
148
+ const renderedHeight = Math.max(1, Math.round(args.sourceHeight * scale));
149
+ return {
150
+ scale,
151
+ renderedWidth,
152
+ renderedHeight,
153
+ offsetX: Math.round((args.targetWidth - renderedWidth) / 2),
154
+ offsetY: Math.round((args.targetHeight - renderedHeight) / 2)
155
+ };
156
+ }
157
+ function buildOverlayBuffer(args) {
158
+ const labels = (args.annotations ?? [])
159
+ .map((annotation) => truncateText(normalizeText(annotation), 76))
160
+ .filter((annotation) => annotation.length > 0)
161
+ .slice(0, 5);
162
+ const fontSize = clamp(Math.round(args.canvasWidth * 0.028), 16, 24);
163
+ const labelPaddingX = 14;
164
+ const labelLineHeight = Math.round(fontSize * 1.24);
165
+ const labelHeight = labels.length > 0 ? labels.length * labelLineHeight + 18 : 0;
166
+ const longestLabel = labels.reduce((longest, label) => Math.max(longest, label.length), 0);
167
+ const labelWidth = labels.length > 0
168
+ ? Math.min(args.canvasWidth - 24, Math.max(240, Math.round(longestLabel * (fontSize * 0.58)) + labelPaddingX * 2))
169
+ : 0;
170
+ let highlightMarkup = "";
171
+ if (args.clickIndicator && args.sourceWidth && args.sourceHeight) {
172
+ const transform = resolveResizeTransform({
173
+ sourceWidth: args.sourceWidth,
174
+ sourceHeight: args.sourceHeight,
175
+ targetWidth: args.canvasWidth,
176
+ targetHeight: args.canvasHeight
177
+ });
178
+ const padding = clamp(Math.round(Math.min(args.canvasWidth, args.canvasHeight) * 0.012), 8, 18);
179
+ const rawX = transform.offsetX + args.clickIndicator.x * transform.scale;
180
+ const rawY = transform.offsetY + args.clickIndicator.y * transform.scale;
181
+ const rawWidth = Math.max(18, args.clickIndicator.width * transform.scale);
182
+ const rawHeight = Math.max(18, args.clickIndicator.height * transform.scale);
183
+ const boxX = clamp(rawX - padding, 0, args.canvasWidth - 2);
184
+ const boxY = clamp(rawY - padding, 0, args.canvasHeight - 2);
185
+ const boxWidth = clamp(rawWidth + padding * 2, 18, args.canvasWidth - boxX);
186
+ const boxHeight = clamp(rawHeight + padding * 2, 18, args.canvasHeight - boxY);
187
+ const centerX = clamp(rawX + rawWidth / 2, 0, args.canvasWidth);
188
+ const centerY = clamp(rawY + rawHeight / 2, 0, args.canvasHeight);
189
+ const outerRadius = clamp(Math.max(boxWidth, boxHeight) * 0.5, 24, 72);
190
+ const innerRadius = clamp(Math.max(boxWidth, boxHeight) * 0.3, 14, 40);
191
+ const centerRadius = clamp(Math.min(boxWidth, boxHeight) * 0.18, 8, 14);
192
+ const strokeWidth = clamp(Math.round(Math.min(args.canvasWidth, args.canvasHeight) * 0.006), 3, 7);
193
+ const cornerRadius = clamp(Math.round(Math.min(boxWidth, boxHeight) * 0.16), 10, 18);
194
+ highlightMarkup = `
195
+ <rect x="${boxX.toFixed(1)}" y="${boxY.toFixed(1)}" width="${boxWidth.toFixed(1)}" height="${boxHeight.toFixed(1)}" rx="${cornerRadius}" fill="#f59e0b" fill-opacity="0.18" stroke="#ffffff" stroke-opacity="0.98" stroke-width="${strokeWidth + 2}" />
196
+ <rect x="${boxX.toFixed(1)}" y="${boxY.toFixed(1)}" width="${boxWidth.toFixed(1)}" height="${boxHeight.toFixed(1)}" rx="${cornerRadius}" fill="none" stroke="#f97316" stroke-opacity="0.98" stroke-width="${strokeWidth}" />
197
+ <circle cx="${centerX.toFixed(1)}" cy="${centerY.toFixed(1)}" r="${outerRadius.toFixed(1)}" fill="none" stroke="#ffffff" stroke-opacity="0.95" stroke-width="${strokeWidth + 2}" />
198
+ <circle cx="${centerX.toFixed(1)}" cy="${centerY.toFixed(1)}" r="${outerRadius.toFixed(1)}" fill="none" stroke="#ef4444" stroke-opacity="0.88" stroke-width="${strokeWidth}" />
199
+ <circle cx="${centerX.toFixed(1)}" cy="${centerY.toFixed(1)}" r="${innerRadius.toFixed(1)}" fill="none" stroke="#facc15" stroke-opacity="0.9" stroke-width="${Math.max(2, strokeWidth - 1)}" />
200
+ <circle cx="${centerX.toFixed(1)}" cy="${centerY.toFixed(1)}" r="${centerRadius.toFixed(1)}" fill="#ef4444" fill-opacity="0.96" stroke="#ffffff" stroke-width="3" />
201
+ `;
202
+ }
203
+ if (!highlightMarkup && labels.length === 0) {
204
+ return null;
205
+ }
206
+ const labelMarkup = labels.length > 0
207
+ ? `
208
+ <g>
209
+ <rect x="16" y="16" width="${labelWidth}" height="${labelHeight}" rx="16" fill="#111827" fill-opacity="0.84" stroke="#ffffff" stroke-opacity="0.25" stroke-width="1" />
210
+ ${labels
211
+ .map((label, index) => `<text x="${16 + labelPaddingX}" y="${16 + 12 + fontSize + index * labelLineHeight}" font-family="Arial, Helvetica, sans-serif" font-size="${fontSize}" font-weight="700" fill="#ffffff">${escapeXml(label)}</text>`)
212
+ .join("")}
213
+ </g>
214
+ `
215
+ : "";
216
+ const svg = `
217
+ <svg width="${args.canvasWidth}" height="${args.canvasHeight}" viewBox="0 0 ${args.canvasWidth} ${args.canvasHeight}" xmlns="http://www.w3.org/2000/svg">
218
+ ${highlightMarkup}
219
+ ${labelMarkup}
220
+ </svg>
221
+ `;
222
+ return Buffer.from(svg);
223
+ }
224
+ async function buildReplayFrameBuffer(args) {
225
+ const metadata = await sharp(args.source.filePath).metadata();
226
+ let frame = sharp(args.source.filePath)
227
+ .rotate()
228
+ .resize({
229
+ width: args.width,
230
+ height: args.height,
231
+ fit: "contain",
232
+ background: "#ffffff",
233
+ withoutEnlargement: true
234
+ })
235
+ .flatten({ background: "#ffffff" });
236
+ const overlayBuffer = buildOverlayBuffer({
237
+ canvasWidth: args.width,
238
+ canvasHeight: args.height,
239
+ ...(metadata.width ? { sourceWidth: metadata.width } : {}),
240
+ ...(metadata.height ? { sourceHeight: metadata.height } : {}),
241
+ ...(args.source.annotations ? { annotations: args.source.annotations } : {}),
242
+ ...(args.source.clickIndicator ? { clickIndicator: args.source.clickIndicator } : {})
243
+ });
244
+ if (overlayBuffer) {
245
+ frame = frame.composite([{ input: overlayBuffer }]);
246
+ }
247
+ return frame
248
+ .webp({
249
+ quality: 58,
250
+ effort: 4
251
+ })
252
+ .toBuffer();
253
+ }
254
+ export async function generateClickReplay(args) {
255
+ const rawSources = collectReplaySources(args);
256
+ if (rawSources.length === 0) {
257
+ return null;
258
+ }
259
+ const sources = normalizeDurations(rawSources);
260
+ const canvas = await resolveReplayCanvas(sources);
261
+ if (!canvas) {
262
+ return null;
263
+ }
264
+ const frames = [];
265
+ for (const source of sources) {
266
+ const buffer = await buildReplayFrameBuffer({
267
+ source,
268
+ width: canvas.width,
269
+ height: canvas.height
270
+ });
271
+ const frame = await WebP.Image.generateFrame({
272
+ buffer,
273
+ delay: source.durationMs
274
+ });
275
+ frames.push(frame);
276
+ }
277
+ const outputPath = path.join(args.runDir, CLICK_REPLAY_ARTIFACT);
278
+ await WebP.Image.save(outputPath, {
279
+ width: canvas.width,
280
+ height: canvas.height,
281
+ frames,
282
+ bgColor: [255, 255, 255, 255],
283
+ loops: 0
284
+ });
285
+ return {
286
+ artifactName: CLICK_REPLAY_ARTIFACT,
287
+ durationMs: sources.reduce((sum, source) => sum + source.durationMs, 0),
288
+ frameCount: frames.length
289
+ };
290
+ }