blun-king-cli 4.1.1 → 5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/api.js +965 -0
- package/blun-cli.js +820 -0
- package/blunking-api.js +7 -0
- package/bot.js +188 -0
- package/browser-controller.js +76 -0
- package/chat-memory.js +103 -0
- package/file-helper.js +63 -0
- package/fuzzy-match.js +78 -0
- package/identities.js +106 -0
- package/installer.js +160 -0
- package/job-manager.js +146 -0
- package/local-data.js +71 -0
- package/message-builder.js +28 -0
- package/noisy-evals.js +38 -0
- package/package.json +17 -4
- package/palace-memory.js +246 -0
- package/reference-inspector.js +228 -0
- package/runtime.js +555 -0
- package/task-executor.js +104 -0
- package/tests/browser-controller.test.js +42 -0
- package/tests/cli.test.js +93 -0
- package/tests/file-helper.test.js +18 -0
- package/tests/installer.test.js +39 -0
- package/tests/job-manager.test.js +99 -0
- package/tests/merge-compat.test.js +77 -0
- package/tests/messages.test.js +23 -0
- package/tests/noisy-evals.test.js +12 -0
- package/tests/noisy-intent-corpus.test.js +45 -0
- package/tests/reference-inspector.test.js +36 -0
- package/tests/runtime.test.js +119 -0
- package/tests/task-executor.test.js +40 -0
- package/tests/tools.test.js +23 -0
- package/tests/user-profile.test.js +66 -0
- package/tests/website-builder.test.js +66 -0
- package/tmp-build-smoke/nicrazy-landing/index.html +53 -0
- package/tmp-build-smoke/nicrazy-landing/style.css +110 -0
- package/tmp-shot-smoke/website-shot-1776006760424.png +0 -0
- package/tmp-shot-smoke/website-shot-1776007850007.png +0 -0
- package/tmp-shot-smoke/website-shot-1776007886209.png +0 -0
- package/tmp-shot-smoke/website-shot-1776007903766.png +0 -0
- package/tmp-shot-smoke/website-shot-1776008737117.png +0 -0
- package/tmp-shot-smoke/website-shot-1776008988859.png +0 -0
- package/tmp-smoke/nicrazy-landing/index.html +66 -0
- package/tmp-smoke/nicrazy-landing/style.css +104 -0
- package/tools.js +177 -0
- package/user-profile.js +395 -0
- package/website-builder.js +394 -0
- package/website-shot-1776010648230.png +0 -0
- package/website_builder.txt +38 -0
- package/bin/blun.js +0 -3196
- package/setup.js +0 -30
package/palace-memory.js
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const os = require("os");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { execFileSync } = require("child_process");
|
|
5
|
+
|
|
6
|
+
const BLUN_HOME = process.env.BLUN_HOME || path.join(os.homedir(), ".blun");
|
|
7
|
+
const PALACE_BASE = path.join(BLUN_HOME, "palaces");
|
|
8
|
+
const GLOBAL_PALACE = path.join(PALACE_BASE, "_global");
|
|
9
|
+
|
|
10
|
+
let mempalaceSupport;
|
|
11
|
+
|
|
12
|
+
ensureDir(PALACE_BASE);
|
|
13
|
+
ensurePalaceSkeleton(GLOBAL_PALACE);
|
|
14
|
+
|
|
15
|
+
function ensureDir(dir) {
|
|
16
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ensurePalaceSkeleton(dir) {
|
|
20
|
+
ensureDir(dir);
|
|
21
|
+
ensureDir(path.join(dir, "memory"));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function safeId(value) {
|
|
25
|
+
return String(value || "global").replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getUserPalace(userId) {
|
|
29
|
+
const dir = userId ? path.join(PALACE_BASE, safeId(userId)) : GLOBAL_PALACE;
|
|
30
|
+
ensurePalaceSkeleton(dir);
|
|
31
|
+
maybeInitMempalace(dir);
|
|
32
|
+
return dir;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function getPythonBinary() {
|
|
36
|
+
if (process.env.BLUN_PYTHON) return process.env.BLUN_PYTHON;
|
|
37
|
+
return process.platform === "win32" ? "python" : "python3";
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function detectMempalace() {
|
|
41
|
+
if (mempalaceSupport !== undefined) return mempalaceSupport;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
execFileSync(getPythonBinary(), ["-m", "mempalace", "--help"], {
|
|
45
|
+
encoding: "utf8",
|
|
46
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
47
|
+
timeout: 4000
|
|
48
|
+
});
|
|
49
|
+
mempalaceSupport = true;
|
|
50
|
+
} catch {
|
|
51
|
+
mempalaceSupport = false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return mempalaceSupport;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function maybeInitMempalace(dir) {
|
|
58
|
+
if (!detectMempalace()) return false;
|
|
59
|
+
|
|
60
|
+
const marker = path.join(dir, ".mempalace-init");
|
|
61
|
+
if (fs.existsSync(marker)) return true;
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
execFileSync(getPythonBinary(), ["-m", "mempalace", "init", "--yes", "."], {
|
|
65
|
+
cwd: dir,
|
|
66
|
+
encoding: "utf8",
|
|
67
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
68
|
+
timeout: 15000
|
|
69
|
+
});
|
|
70
|
+
fs.writeFileSync(marker, "ok", "utf8");
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function runMempalace(dir, args, timeout = 10000) {
|
|
78
|
+
return execFileSync(getPythonBinary(), ["-m", "mempalace", ...args], {
|
|
79
|
+
cwd: dir,
|
|
80
|
+
encoding: "utf8",
|
|
81
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
82
|
+
timeout
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function memoryDir(dir) {
|
|
87
|
+
const target = path.join(dir, "memory");
|
|
88
|
+
ensureDir(target);
|
|
89
|
+
return target;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function listMemoryFiles(dir) {
|
|
93
|
+
const target = memoryDir(dir);
|
|
94
|
+
return fs.readdirSync(target)
|
|
95
|
+
.filter((entry) => entry.endsWith(".md"))
|
|
96
|
+
.map((entry) => path.join(target, entry));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function scoreText(text, query) {
|
|
100
|
+
const haystack = String(text || "").toLowerCase();
|
|
101
|
+
const needles = String(query || "").toLowerCase().split(/\s+/).filter(Boolean);
|
|
102
|
+
if (needles.length === 0) return haystack.length ? 1 : 0;
|
|
103
|
+
return needles.reduce((score, needle) => score + (haystack.includes(needle) ? 1 : 0), 0);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function fallbackSearch(query, limit, userId) {
|
|
107
|
+
const dir = getUserPalace(userId);
|
|
108
|
+
const scored = listMemoryFiles(dir)
|
|
109
|
+
.map((file) => {
|
|
110
|
+
const content = fs.readFileSync(file, "utf8");
|
|
111
|
+
return {
|
|
112
|
+
file,
|
|
113
|
+
content,
|
|
114
|
+
score: scoreText(content, query)
|
|
115
|
+
};
|
|
116
|
+
})
|
|
117
|
+
.filter((entry) => entry.score > 0)
|
|
118
|
+
.sort((a, b) => b.score - a.score || b.file.localeCompare(a.file))
|
|
119
|
+
.slice(0, limit);
|
|
120
|
+
|
|
121
|
+
if (scored.length === 0) return "Keine Ergebnisse.";
|
|
122
|
+
|
|
123
|
+
return scored.map((entry) => {
|
|
124
|
+
const snippet = entry.content.replace(/\s+/g, " ").trim().slice(0, 300);
|
|
125
|
+
return `${path.basename(entry.file)}\n${snippet}`;
|
|
126
|
+
}).join("\n\n");
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function fallbackWakeUp(userId) {
|
|
130
|
+
const dir = getUserPalace(userId);
|
|
131
|
+
const entries = listMemoryFiles(dir)
|
|
132
|
+
.map((file) => ({ file, stat: fs.statSync(file) }))
|
|
133
|
+
.sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs)
|
|
134
|
+
.slice(0, 5)
|
|
135
|
+
.map(({ file }) => {
|
|
136
|
+
const content = fs.readFileSync(file, "utf8").replace(/\s+/g, " ").trim();
|
|
137
|
+
return `- ${path.basename(file)}: ${content.slice(0, 220)}`;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
return entries.length ? entries.join("\n") : "";
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function fallbackStats(userId) {
|
|
144
|
+
const dir = getUserPalace(userId);
|
|
145
|
+
const files = listMemoryFiles(dir);
|
|
146
|
+
return `Palace local fallback active. Files: ${files.length}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function appendEntry(dir, filename, text) {
|
|
150
|
+
const file = path.join(memoryDir(dir), filename);
|
|
151
|
+
fs.appendFileSync(file, text, "utf8");
|
|
152
|
+
return file;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function palaceSearch(query, limit = 5, userId) {
|
|
156
|
+
const dir = getUserPalace(userId);
|
|
157
|
+
|
|
158
|
+
if (detectMempalace()) {
|
|
159
|
+
try {
|
|
160
|
+
const out = runMempalace(dir, ["search", String(query || ""), "--results", String(limit)], 10000);
|
|
161
|
+
return out.trim() || "Keine Ergebnisse.";
|
|
162
|
+
} catch {
|
|
163
|
+
return fallbackSearch(query, limit, userId);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return fallbackSearch(query, limit, userId);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function palaceStore(text, source = "misc", userId) {
|
|
171
|
+
const dir = getUserPalace(userId);
|
|
172
|
+
const stamp = new Date();
|
|
173
|
+
const filename = `${stamp.toISOString().slice(0, 10)}_${safeId(source).slice(0, 30) || "misc"}.md`;
|
|
174
|
+
const entry = `\n## ${stamp.toISOString()}\n${String(text || "").trim()}\n`;
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
appendEntry(dir, filename, entry);
|
|
178
|
+
|
|
179
|
+
if (detectMempalace()) {
|
|
180
|
+
try {
|
|
181
|
+
runMempalace(dir, ["mine", "."], 30000);
|
|
182
|
+
} catch {
|
|
183
|
+
// Local markdown store already succeeded.
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return true;
|
|
188
|
+
} catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function palaceWakeUp(userId) {
|
|
194
|
+
const dir = getUserPalace(userId);
|
|
195
|
+
|
|
196
|
+
if (detectMempalace()) {
|
|
197
|
+
try {
|
|
198
|
+
const out = runMempalace(dir, ["wake-up"], 10000);
|
|
199
|
+
return out.trim();
|
|
200
|
+
} catch {
|
|
201
|
+
return fallbackWakeUp(userId);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return fallbackWakeUp(userId);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function palaceLearn(knowledge, source = "learn", userId) {
|
|
209
|
+
return palaceStore(knowledge, `learn_${source}`, userId);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function palaceLogChat(userId, role, content) {
|
|
213
|
+
const dir = getUserPalace(userId);
|
|
214
|
+
const stamp = new Date();
|
|
215
|
+
const filename = `chat_${stamp.toISOString().slice(0, 10)}.md`;
|
|
216
|
+
const snippet = String(content || "").replace(/\s+/g, " ").trim().slice(0, 1200);
|
|
217
|
+
appendEntry(dir, filename, `\n**${stamp.toISOString().slice(11, 16)} ${role}:** ${snippet}\n`);
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function palaceStats(userId) {
|
|
222
|
+
const dir = getUserPalace(userId);
|
|
223
|
+
|
|
224
|
+
if (detectMempalace()) {
|
|
225
|
+
try {
|
|
226
|
+
const out = runMempalace(dir, ["status"], 10000);
|
|
227
|
+
return out.trim();
|
|
228
|
+
} catch {
|
|
229
|
+
return fallbackStats(userId);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return fallbackStats(userId);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = {
|
|
237
|
+
PALACE_BASE,
|
|
238
|
+
GLOBAL_PALACE,
|
|
239
|
+
getUserPalace,
|
|
240
|
+
palaceSearch,
|
|
241
|
+
palaceStore,
|
|
242
|
+
palaceWakeUp,
|
|
243
|
+
palaceLearn,
|
|
244
|
+
palaceLogChat,
|
|
245
|
+
palaceStats
|
|
246
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { execFile } = require("child_process");
|
|
3
|
+
const { storeReference } = require("./local-data");
|
|
4
|
+
|
|
5
|
+
let chromiumLoader = null;
|
|
6
|
+
|
|
7
|
+
function extractUrls(text) {
|
|
8
|
+
return Array.from(String(text || "").matchAll(/https?:\/\/[^\s)>"']+/gi)).map((match) => match[0]);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function summarizeHtml(source) {
|
|
12
|
+
const title = (source.match(/<title[^>]*>([\s\S]*?)<\/title>/i) || [, ""])[1].trim();
|
|
13
|
+
const h1s = [...source.matchAll(/<h1[^>]*>([\s\S]*?)<\/h1>/ig)]
|
|
14
|
+
.map((match) => match[1].replace(/<[^>]+>/g, "").trim())
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
const links = [...source.matchAll(/<a\b/ig)].length;
|
|
17
|
+
const images = [...source.matchAll(/<img\b/ig)].length;
|
|
18
|
+
const text = cleanText((source.match(/<body[^>]*>([\s\S]*?)<\/body>/i) || [, source])[1]).slice(0, 3000);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
title,
|
|
22
|
+
h1s,
|
|
23
|
+
links,
|
|
24
|
+
images,
|
|
25
|
+
text,
|
|
26
|
+
responsive: /<meta[^>]+name=["']viewport["']/i.test(source)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function cleanText(source) {
|
|
31
|
+
return String(source || "")
|
|
32
|
+
.replace(/<script[\s\S]*?<\/script>/gi, " ")
|
|
33
|
+
.replace(/<style[\s\S]*?<\/style>/gi, " ")
|
|
34
|
+
.replace(/<[^>]+>/g, " ")
|
|
35
|
+
.replace(/\s+/g, " ")
|
|
36
|
+
.trim();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function shouldAutoReference(input, task = {}) {
|
|
40
|
+
const text = String(input || "");
|
|
41
|
+
if (!extractUrls(text).length) return false;
|
|
42
|
+
if (["installation", "browser_capture"].includes(task?.type)) return false;
|
|
43
|
+
if (task?.type === "website_builder" || task?.type === "analysis") return true;
|
|
44
|
+
return /\b(referenz|vorlage|inspiriert|angelehnt|wie diese|wie die|schau dir|analysier.*https?:\/\/|bau.*https?:\/\/)\b/i.test(text);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildReferencePromptBlock(references = []) {
|
|
48
|
+
if (!Array.isArray(references) || references.length === 0) return "";
|
|
49
|
+
|
|
50
|
+
const lines = ["[Auto Reference Chain]"];
|
|
51
|
+
references.forEach((ref, index) => {
|
|
52
|
+
const summary = ref.summary || ref;
|
|
53
|
+
lines.push(`Referenz ${index + 1}: ${ref.url || "-"}`);
|
|
54
|
+
if (summary.title) lines.push(`Titel: ${summary.title}`);
|
|
55
|
+
if (Array.isArray(summary.h1s) && summary.h1s.length) lines.push(`H1: ${summary.h1s.join(" | ")}`);
|
|
56
|
+
if (summary.text) lines.push(`Text: ${String(summary.text).slice(0, 500)}`);
|
|
57
|
+
if (typeof summary.links === "number") lines.push(`Links: ${summary.links}`);
|
|
58
|
+
if (typeof summary.images === "number") lines.push(`Bilder: ${summary.images}`);
|
|
59
|
+
if (ref.screenshotPath) lines.push(`Screenshot: ${ref.screenshotPath}`);
|
|
60
|
+
if (ref.error) lines.push(`Fehler: ${ref.error}`);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function loadChromium() {
|
|
67
|
+
if (chromiumLoader !== null) return chromiumLoader;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const playwright = require("playwright");
|
|
71
|
+
chromiumLoader = playwright.chromium;
|
|
72
|
+
} catch {
|
|
73
|
+
chromiumLoader = false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return chromiumLoader;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function execFileAsync(command, args) {
|
|
80
|
+
return new Promise((resolve, reject) => {
|
|
81
|
+
execFile(command, args, { maxBuffer: 20 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
82
|
+
if (error) reject(new Error(stderr || error.message));
|
|
83
|
+
else resolve(stdout);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function fetchHtml(url, options = {}) {
|
|
89
|
+
try {
|
|
90
|
+
const resp = await fetch(url, {
|
|
91
|
+
headers: {
|
|
92
|
+
"User-Agent": options.userAgent || "BLUN-King/5.0 (+reference-inspector)"
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
if (!resp.ok) throw new Error(`Reference fetch failed with HTTP ${resp.status}`);
|
|
96
|
+
return await resp.text();
|
|
97
|
+
} catch {
|
|
98
|
+
try {
|
|
99
|
+
const curlBinary = process.platform === "win32" ? "curl.exe" : "curl";
|
|
100
|
+
return await execFileAsync(curlBinary, [
|
|
101
|
+
"-L",
|
|
102
|
+
"-A",
|
|
103
|
+
options.userAgent || "BLUN-King/5.0 (+reference-inspector)",
|
|
104
|
+
url
|
|
105
|
+
]);
|
|
106
|
+
} catch {
|
|
107
|
+
if (process.platform === "win32") {
|
|
108
|
+
const script = [
|
|
109
|
+
`$ProgressPreference='SilentlyContinue'`,
|
|
110
|
+
`$resp = Invoke-WebRequest -UseBasicParsing -Headers @{'User-Agent'='${options.userAgent || "BLUN-King/5.0 (+reference-inspector)"}'} -Uri '${url}'`,
|
|
111
|
+
`[Console]::Out.Write($resp.Content)`
|
|
112
|
+
].join("; ");
|
|
113
|
+
return await execFileAsync("powershell.exe", ["-NoProfile", "-Command", script]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
throw new Error("Reference fetch failed");
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function inspectReference(url, options = {}) {
|
|
122
|
+
let html = "";
|
|
123
|
+
let summary = {
|
|
124
|
+
title: "",
|
|
125
|
+
h1s: [],
|
|
126
|
+
links: 0,
|
|
127
|
+
images: 0,
|
|
128
|
+
responsive: false
|
|
129
|
+
};
|
|
130
|
+
const result = {
|
|
131
|
+
url,
|
|
132
|
+
fetchedAt: Date.now(),
|
|
133
|
+
summary,
|
|
134
|
+
html,
|
|
135
|
+
screenshotPath: null,
|
|
136
|
+
screenshotAvailable: false,
|
|
137
|
+
error: null
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
html = await fetchHtml(url, options);
|
|
142
|
+
summary = summarizeHtml(html);
|
|
143
|
+
result.html = html;
|
|
144
|
+
result.summary = summary;
|
|
145
|
+
} catch (error) {
|
|
146
|
+
result.error = error.message;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!result.error && options.screenshot !== false) {
|
|
150
|
+
const chromium = await loadChromium();
|
|
151
|
+
if (chromium) {
|
|
152
|
+
try {
|
|
153
|
+
const browser = await chromium.launch({ headless: true });
|
|
154
|
+
try {
|
|
155
|
+
const page = await browser.newPage({ viewport: { width: 1440, height: 1024 } });
|
|
156
|
+
await page.goto(url, { waitUntil: "networkidle", timeout: options.timeout || 30000 });
|
|
157
|
+
const stored = storeReference(url, {
|
|
158
|
+
type: "reference",
|
|
159
|
+
mode: "html+screenshot",
|
|
160
|
+
summary
|
|
161
|
+
});
|
|
162
|
+
const screenshotPath = path.join(path.dirname(stored.filePath), `${stored.id}.png`);
|
|
163
|
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
164
|
+
result.screenshotPath = screenshotPath;
|
|
165
|
+
result.screenshotAvailable = true;
|
|
166
|
+
result.storage = stored.filePath;
|
|
167
|
+
} finally {
|
|
168
|
+
await browser.close();
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
result.screenshotAvailable = false;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!result.storage) {
|
|
177
|
+
const stored = storeReference(url, {
|
|
178
|
+
type: "reference",
|
|
179
|
+
mode: result.error ? "error" : "html",
|
|
180
|
+
summary,
|
|
181
|
+
error: result.error
|
|
182
|
+
});
|
|
183
|
+
result.storage = stored.filePath;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function screenshotUrl(url, options = {}) {
|
|
190
|
+
const result = await inspectReference(url, { ...options, screenshot: true });
|
|
191
|
+
return {
|
|
192
|
+
url: result.url,
|
|
193
|
+
screenshotPath: result.screenshotPath,
|
|
194
|
+
screenshotAvailable: result.screenshotAvailable,
|
|
195
|
+
summary: result.summary,
|
|
196
|
+
storage: result.storage,
|
|
197
|
+
error: result.error
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function autoReferenceChain(input, options = {}) {
|
|
202
|
+
const urls = extractUrls(input).slice(0, options.limit || 2);
|
|
203
|
+
const references = [];
|
|
204
|
+
|
|
205
|
+
for (const url of urls) {
|
|
206
|
+
try {
|
|
207
|
+
references.push(await inspectReference(url, { screenshot: options.screenshot !== false }));
|
|
208
|
+
} catch (error) {
|
|
209
|
+
references.push({
|
|
210
|
+
url,
|
|
211
|
+
error: error.message,
|
|
212
|
+
summary: { title: "", h1s: [], links: 0, images: 0, text: "", responsive: false }
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return references;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
module.exports = {
|
|
221
|
+
extractUrls,
|
|
222
|
+
inspectReference,
|
|
223
|
+
summarizeHtml,
|
|
224
|
+
shouldAutoReference,
|
|
225
|
+
buildReferencePromptBlock,
|
|
226
|
+
screenshotUrl,
|
|
227
|
+
autoReferenceChain
|
|
228
|
+
};
|