@mkterswingman/5mghost-wonder 0.0.14 → 0.0.15
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/commands/browser.js +16 -5
- package/dist/commands/help.js +1 -0
- package/dist/wecom/browser-probe.js +280 -5
- package/package.json +2 -2
- package/skills/setup-5mghost-wonder/SKILL.md +2 -2
- package/skills/update-5mghost-wonder/SKILL.md +1 -1
- package/skills/use-5mghost-wonder/SKILL.md +2 -2
- package/skills/use-5mghost-wonder/references/cli-reference.md +3 -2
- package/skills/use-5mghost-wonder/references/read-no-export-browser.md +16 -6
package/dist/commands/browser.js
CHANGED
|
@@ -2,19 +2,27 @@
|
|
|
2
2
|
// Experimental browser runtime commands.
|
|
3
3
|
import { resolveWonderPaths } from "../platform/paths.js";
|
|
4
4
|
import { parseWecomUrl } from "../wecom/url.js";
|
|
5
|
-
import { runBrowserNoExportProbe } from "../wecom/browser-probe.js";
|
|
5
|
+
import { runBrowserNoExportProbe, runBrowserNoExportRead, } from "../wecom/browser-probe.js";
|
|
6
6
|
export async function runBrowserCommand(argv, context) {
|
|
7
7
|
const [sub, ...rest] = argv;
|
|
8
8
|
switch (sub) {
|
|
9
9
|
case "probe":
|
|
10
10
|
return runBrowserProbe(rest, context);
|
|
11
|
+
case "read":
|
|
12
|
+
return runBrowserRead(rest, context);
|
|
11
13
|
default:
|
|
12
14
|
context.io.stderr(`Unknown subcommand: browser ${sub ?? "(none)"}\n` +
|
|
13
|
-
"Usage: wonder browser probe <url> [--headed] [--timeout-ms <ms>] [--save <dir>]");
|
|
15
|
+
"Usage: wonder browser probe|read <url> [--headed] [--timeout-ms <ms>] [--save <dir>]");
|
|
14
16
|
return { exitCode: 1 };
|
|
15
17
|
}
|
|
16
18
|
}
|
|
19
|
+
async function runBrowserRead(args, context) {
|
|
20
|
+
return runBrowserAction(args, context, "read");
|
|
21
|
+
}
|
|
17
22
|
async function runBrowserProbe(args, context) {
|
|
23
|
+
return runBrowserAction(args, context, "probe");
|
|
24
|
+
}
|
|
25
|
+
async function runBrowserAction(args, context, action) {
|
|
18
26
|
let url;
|
|
19
27
|
let saveDir;
|
|
20
28
|
let timeoutMs;
|
|
@@ -37,7 +45,7 @@ async function runBrowserProbe(args, context) {
|
|
|
37
45
|
if (!url) {
|
|
38
46
|
context.io.stderr(JSON.stringify({
|
|
39
47
|
error: "missing_url",
|
|
40
|
-
message:
|
|
48
|
+
message: `用法:wonder browser ${action} <url> [--headed] [--timeout-ms <ms>] [--save <dir>]`,
|
|
41
49
|
}));
|
|
42
50
|
return { exitCode: 1, telemetry: { outcome: "failure", errorKind: "missing_url" } };
|
|
43
51
|
}
|
|
@@ -57,14 +65,17 @@ async function runBrowserProbe(args, context) {
|
|
|
57
65
|
return { exitCode: 1, telemetry: { outcome: "failure", errorKind: "invalid_timeout" } };
|
|
58
66
|
}
|
|
59
67
|
const paths = resolveWonderPaths({ homeDir: context.homeDir });
|
|
60
|
-
const
|
|
68
|
+
const options = {
|
|
61
69
|
url,
|
|
62
70
|
chromeProfilePath: paths.chromeProfilePath,
|
|
63
71
|
saveDir,
|
|
64
72
|
headed,
|
|
65
73
|
timeoutMs,
|
|
66
74
|
io: context.io,
|
|
67
|
-
}
|
|
75
|
+
};
|
|
76
|
+
const result = action === "read"
|
|
77
|
+
? await runBrowserNoExportRead(options)
|
|
78
|
+
: await runBrowserNoExportProbe(options);
|
|
68
79
|
context.io.stdout(JSON.stringify(result));
|
|
69
80
|
return {
|
|
70
81
|
exitCode: result.status === "fail" ? 1 : 0,
|
package/dist/commands/help.js
CHANGED
|
@@ -34,6 +34,7 @@ export function renderHelpText() {
|
|
|
34
34
|
"Experimental browser runtime:",
|
|
35
35
|
" browser probe <url> Open in Wonder browser profile and capture",
|
|
36
36
|
" no-export evidence summaries",
|
|
37
|
+
" browser read <url> Experimental no-export text read",
|
|
37
38
|
" browser probe <url> --headed Show the browser while probing",
|
|
38
39
|
].join("\n");
|
|
39
40
|
}
|
|
@@ -52,7 +52,8 @@ export async function runBrowserNoExportProbe(options) {
|
|
|
52
52
|
await sendCdpCommand(socket, "Page.navigate", { url: options.url });
|
|
53
53
|
await delay(timeoutMs);
|
|
54
54
|
const pageInfo = await evaluatePageInfo(socket);
|
|
55
|
-
const
|
|
55
|
+
const networkArtifacts = await collectNetworkArtifacts(socket, responses);
|
|
56
|
+
const networkCandidates = summarizeNetworkArtifacts(networkArtifacts);
|
|
56
57
|
const evidence = ["browser-page"];
|
|
57
58
|
if (pageInfo.visibleTextSample)
|
|
58
59
|
evidence.push("visible-text");
|
|
@@ -107,6 +108,104 @@ export async function runBrowserNoExportProbe(options) {
|
|
|
107
108
|
await terminateBrowserProcess(child);
|
|
108
109
|
}
|
|
109
110
|
}
|
|
111
|
+
export async function runBrowserNoExportRead(options) {
|
|
112
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_CAPTURE_MS;
|
|
113
|
+
const browser = findInstalledBrowser(options.executablePath);
|
|
114
|
+
const port = await getFreePort();
|
|
115
|
+
const endpoint = `http://127.0.0.1:${port}`;
|
|
116
|
+
const child = spawnBrowser({
|
|
117
|
+
browser,
|
|
118
|
+
port,
|
|
119
|
+
profilePath: options.chromeProfilePath,
|
|
120
|
+
url: options.url,
|
|
121
|
+
headed: options.headed ?? false,
|
|
122
|
+
});
|
|
123
|
+
try {
|
|
124
|
+
await waitForDebugger(endpoint, BROWSER_START_TIMEOUT_MS);
|
|
125
|
+
const pageWsUrl = await waitForPageWebSocket(endpoint, options.url, BROWSER_START_TIMEOUT_MS);
|
|
126
|
+
const socket = await openDevToolsSocket(pageWsUrl);
|
|
127
|
+
const responses = [];
|
|
128
|
+
try {
|
|
129
|
+
socket.addEventListener("message", (event) => {
|
|
130
|
+
const payload = safeJsonParse(String(event.data));
|
|
131
|
+
if (payload?.method !== "Network.responseReceived")
|
|
132
|
+
return;
|
|
133
|
+
const response = payload.params?.response;
|
|
134
|
+
const requestId = payload.params?.requestId;
|
|
135
|
+
if (!response?.url || !requestId)
|
|
136
|
+
return;
|
|
137
|
+
if (!isInterestingNetworkUrl(response.url, response.mimeType ?? ""))
|
|
138
|
+
return;
|
|
139
|
+
responses.push({
|
|
140
|
+
requestId,
|
|
141
|
+
url: response.url,
|
|
142
|
+
status: response.status ?? 0,
|
|
143
|
+
mimeType: response.mimeType ?? "",
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
await sendCdpCommand(socket, "Network.enable");
|
|
147
|
+
await sendCdpCommand(socket, "Page.enable");
|
|
148
|
+
await sendCdpCommand(socket, "Runtime.enable");
|
|
149
|
+
await sendCdpCommand(socket, "Page.navigate", { url: options.url });
|
|
150
|
+
await delay(timeoutMs);
|
|
151
|
+
const pageInfo = await evaluatePageInfo(socket);
|
|
152
|
+
const networkArtifacts = await collectNetworkArtifacts(socket, responses);
|
|
153
|
+
const opendoc = networkArtifacts.find((artifact) => artifact.rawUrl.includes("/dop-api/opendoc") && artifact.body);
|
|
154
|
+
const extracted = opendoc?.body ? extractTextFromOpendoc(opendoc.body) : null;
|
|
155
|
+
const evidence = ["browser-page", "network-response-summary"];
|
|
156
|
+
if (opendoc)
|
|
157
|
+
evidence.push("opendoc-response");
|
|
158
|
+
if (extracted?.text)
|
|
159
|
+
evidence.push("initial-attributed-text");
|
|
160
|
+
const result = {
|
|
161
|
+
mode: "browser-no-export-read",
|
|
162
|
+
status: extracted?.text ? "partial" : "fail",
|
|
163
|
+
url: redactUrl(options.url),
|
|
164
|
+
finalUrl: pageInfo.finalUrl ? redactUrl(pageInfo.finalUrl) : undefined,
|
|
165
|
+
title: extracted?.title ?? pageInfo.title,
|
|
166
|
+
padType: extracted?.padType,
|
|
167
|
+
text: extracted?.text,
|
|
168
|
+
textLength: extracted?.text.length,
|
|
169
|
+
imageUrls: extracted?.imageUrls,
|
|
170
|
+
extraction: extracted?.text ? "opendoc-initial-attributed-text" : "none",
|
|
171
|
+
evidence,
|
|
172
|
+
warnings: extracted?.warnings ?? [
|
|
173
|
+
"No readable opendoc initialAttributedText was captured.",
|
|
174
|
+
],
|
|
175
|
+
missing: [
|
|
176
|
+
"table merge ranges",
|
|
177
|
+
...(extracted?.imageUrls && extracted.imageUrls.length > 0 ? [] : ["image original resources"]),
|
|
178
|
+
"image anchors",
|
|
179
|
+
"floating vs fixed image classification",
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
if (options.saveDir) {
|
|
183
|
+
mkdirSync(options.saveDir, { recursive: true });
|
|
184
|
+
const savedPath = resolve(options.saveDir, `wonder-browser-read-${Date.now()}.json`);
|
|
185
|
+
writeFileSync(savedPath, JSON.stringify(result, null, 2), { mode: 0o600 });
|
|
186
|
+
result.savedPath = savedPath;
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
socket.close();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
return {
|
|
196
|
+
mode: "browser-no-export-read",
|
|
197
|
+
status: "fail",
|
|
198
|
+
url: redactUrl(options.url),
|
|
199
|
+
extraction: "none",
|
|
200
|
+
evidence: [],
|
|
201
|
+
warnings: [],
|
|
202
|
+
missing: [err instanceof Error ? err.message : String(err)],
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
finally {
|
|
206
|
+
await terminateBrowserProcess(child);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
110
209
|
async function evaluatePageInfo(socket) {
|
|
111
210
|
const expression = `(() => {
|
|
112
211
|
const text = (document.body && document.body.innerText || "").replace(/\\s+/g, " ").trim();
|
|
@@ -136,14 +235,14 @@ async function evaluatePageInfo(socket) {
|
|
|
136
235
|
runtimeGlobals: value?.runtimeGlobals ?? [],
|
|
137
236
|
};
|
|
138
237
|
}
|
|
139
|
-
async function
|
|
238
|
+
async function collectNetworkArtifacts(socket, responses) {
|
|
140
239
|
const unique = new Map();
|
|
141
240
|
for (const response of responses) {
|
|
142
241
|
if (unique.size >= MAX_NETWORK_CANDIDATES)
|
|
143
242
|
break;
|
|
144
243
|
unique.set(response.requestId, response);
|
|
145
244
|
}
|
|
146
|
-
const
|
|
245
|
+
const artifacts = [];
|
|
147
246
|
for (const response of unique.values()) {
|
|
148
247
|
let body = "";
|
|
149
248
|
let base64Encoded = false;
|
|
@@ -156,16 +255,192 @@ async function collectNetworkCandidates(socket, responses) {
|
|
|
156
255
|
// Some responses are streamed, cached, too large, or not retained.
|
|
157
256
|
}
|
|
158
257
|
const signals = detectBodySignals(body, response.mimeType);
|
|
159
|
-
|
|
258
|
+
artifacts.push({
|
|
259
|
+
requestId: response.requestId,
|
|
260
|
+
rawUrl: response.url,
|
|
160
261
|
url: redactUrl(response.url),
|
|
161
262
|
status: response.status,
|
|
162
263
|
mimeType: response.mimeType,
|
|
163
264
|
bodyLength: body ? body.length : undefined,
|
|
164
265
|
base64Encoded: body ? base64Encoded : undefined,
|
|
165
266
|
signals,
|
|
267
|
+
body,
|
|
166
268
|
});
|
|
167
269
|
}
|
|
168
|
-
return
|
|
270
|
+
return artifacts;
|
|
271
|
+
}
|
|
272
|
+
function summarizeNetworkArtifacts(artifacts) {
|
|
273
|
+
return artifacts.map((artifact) => ({
|
|
274
|
+
url: artifact.url,
|
|
275
|
+
status: artifact.status,
|
|
276
|
+
mimeType: artifact.mimeType,
|
|
277
|
+
bodyLength: artifact.bodyLength,
|
|
278
|
+
base64Encoded: artifact.base64Encoded,
|
|
279
|
+
signals: artifact.signals,
|
|
280
|
+
}));
|
|
281
|
+
}
|
|
282
|
+
export function extractTextFromOpendoc(body) {
|
|
283
|
+
let payload;
|
|
284
|
+
try {
|
|
285
|
+
payload = JSON.parse(body);
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
const initialText = payload.clientVars?.collab_client_vars?.initialAttributedText?.text;
|
|
291
|
+
const chunks = Array.isArray(initialText) ? initialText : [];
|
|
292
|
+
const decodedChunks = chunks
|
|
293
|
+
.filter((chunk) => typeof chunk === "string")
|
|
294
|
+
.flatMap((chunk) => extractInitialAttributedTextCandidates(chunk));
|
|
295
|
+
const textCandidate = chooseTextCandidate(decodedChunks);
|
|
296
|
+
const text = normalizeExtractedText(textCandidate ?? decodedChunks.join("\n"));
|
|
297
|
+
if (!text)
|
|
298
|
+
return null;
|
|
299
|
+
const imageUrls = extractImageUrls(decodedChunks.join("\n"));
|
|
300
|
+
return {
|
|
301
|
+
title: payload.clientVars?.title ?? payload.clientVars?.initialTitle,
|
|
302
|
+
padType: payload.clientVars?.padType ?? payload.padType,
|
|
303
|
+
text,
|
|
304
|
+
imageUrls,
|
|
305
|
+
warnings: [
|
|
306
|
+
"No-export text is decoded from opendoc initialAttributedText; rich styles are not reconstructed yet.",
|
|
307
|
+
...(imageUrls.length > 0
|
|
308
|
+
? ["Image resource URLs were detected, but image anchors and fixed/floating placement are not reconstructed yet."]
|
|
309
|
+
: []),
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function extractInitialAttributedTextCandidates(chunk) {
|
|
314
|
+
let decoded;
|
|
315
|
+
try {
|
|
316
|
+
decoded = Buffer.from(chunk, "base64");
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
decoded = Buffer.from(chunk, "utf8");
|
|
320
|
+
}
|
|
321
|
+
const candidates = collectProtobufStringCandidates(decoded);
|
|
322
|
+
if (candidates.length > 0)
|
|
323
|
+
return candidates.map((candidate) => candidate.text);
|
|
324
|
+
return [decoded.toString("utf8")];
|
|
325
|
+
}
|
|
326
|
+
function chooseTextCandidate(candidates) {
|
|
327
|
+
const scored = candidates
|
|
328
|
+
.map((text) => ({
|
|
329
|
+
text,
|
|
330
|
+
score: text.length +
|
|
331
|
+
(/\[[^\]]+\]/.test(text) ? 200 : 0) +
|
|
332
|
+
(/[\u4e00-\u9fff]/.test(text) ? 200 : 0) -
|
|
333
|
+
((text.match(/https?:\/\//g)?.length ?? 0) * 100),
|
|
334
|
+
}))
|
|
335
|
+
.filter((candidate) => candidate.text.length > 0)
|
|
336
|
+
.sort((a, b) => b.score - a.score);
|
|
337
|
+
return scored[0]?.text ?? null;
|
|
338
|
+
}
|
|
339
|
+
function collectProtobufStringCandidates(buffer) {
|
|
340
|
+
const candidates = [];
|
|
341
|
+
walkProtobuf(buffer, 0, buffer.length, 0, candidates);
|
|
342
|
+
return candidates
|
|
343
|
+
.filter((candidate) => candidate.byteLength >= 2 &&
|
|
344
|
+
candidate.printableRatio >= 0.8 &&
|
|
345
|
+
/[\p{L}\p{N}\[]/u.test(candidate.text))
|
|
346
|
+
.sort((a, b) => b.byteLength - a.byteLength);
|
|
347
|
+
}
|
|
348
|
+
function walkProtobuf(buffer, start, end, depth, candidates) {
|
|
349
|
+
if (depth > 6)
|
|
350
|
+
return;
|
|
351
|
+
let offset = start;
|
|
352
|
+
let fieldsSeen = 0;
|
|
353
|
+
while (offset < end && fieldsSeen < 10_000) {
|
|
354
|
+
fieldsSeen++;
|
|
355
|
+
const key = readVarint(buffer, offset, end);
|
|
356
|
+
if (!key)
|
|
357
|
+
return;
|
|
358
|
+
offset = key.nextOffset;
|
|
359
|
+
const wireType = key.value & 7;
|
|
360
|
+
if (wireType === 0) {
|
|
361
|
+
const value = readVarint(buffer, offset, end);
|
|
362
|
+
if (!value)
|
|
363
|
+
return;
|
|
364
|
+
offset = value.nextOffset;
|
|
365
|
+
}
|
|
366
|
+
else if (wireType === 1) {
|
|
367
|
+
offset += 8;
|
|
368
|
+
}
|
|
369
|
+
else if (wireType === 5) {
|
|
370
|
+
offset += 4;
|
|
371
|
+
}
|
|
372
|
+
else if (wireType === 2) {
|
|
373
|
+
const length = readVarint(buffer, offset, end);
|
|
374
|
+
if (!length)
|
|
375
|
+
return;
|
|
376
|
+
offset = length.nextOffset;
|
|
377
|
+
const fieldEnd = offset + length.value;
|
|
378
|
+
if (fieldEnd > end)
|
|
379
|
+
return;
|
|
380
|
+
const raw = buffer.subarray(offset, fieldEnd);
|
|
381
|
+
const text = raw.toString("utf8");
|
|
382
|
+
candidates.push({
|
|
383
|
+
text,
|
|
384
|
+
byteLength: length.value,
|
|
385
|
+
printableRatio: printableRatio(text),
|
|
386
|
+
});
|
|
387
|
+
if (length.value > 2) {
|
|
388
|
+
walkProtobuf(buffer, offset, fieldEnd, depth + 1, candidates);
|
|
389
|
+
}
|
|
390
|
+
offset = fieldEnd;
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
function readVarint(buffer, offset, end) {
|
|
398
|
+
let value = 0n;
|
|
399
|
+
let shift = 0n;
|
|
400
|
+
let current = offset;
|
|
401
|
+
while (current < end) {
|
|
402
|
+
const byte = buffer[current++];
|
|
403
|
+
value |= BigInt(byte & 0x7f) << shift;
|
|
404
|
+
if ((byte & 0x80) === 0) {
|
|
405
|
+
return { value: Number(value), nextOffset: current };
|
|
406
|
+
}
|
|
407
|
+
shift += 7n;
|
|
408
|
+
if (shift > 63n)
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
function printableRatio(text) {
|
|
414
|
+
if (!text)
|
|
415
|
+
return 0;
|
|
416
|
+
let printable = 0;
|
|
417
|
+
for (const char of text) {
|
|
418
|
+
if (/[\p{L}\p{N}\p{P}\p{Zs}\r\n\t]/u.test(char))
|
|
419
|
+
printable++;
|
|
420
|
+
}
|
|
421
|
+
return printable / text.length;
|
|
422
|
+
}
|
|
423
|
+
function extractImageUrls(text) {
|
|
424
|
+
const urls = text.match(/https?:\/\/[^\s"'<>\\\u0000-\u001f]+/g) ?? [];
|
|
425
|
+
return Array.from(new Set(urls
|
|
426
|
+
.map((url) => url.replace(/[)*,.;:]+$/g, ""))
|
|
427
|
+
.filter((url) => /qpic\.cn|weixin\.qq\.com|doc\.weixin\.qq\.com/i.test(url))));
|
|
428
|
+
}
|
|
429
|
+
function normalizeExtractedText(text) {
|
|
430
|
+
return text
|
|
431
|
+
.replace(/p\.\d{8,}/g, "")
|
|
432
|
+
.replace(/\b\d{12,}@eJ\b/g, "")
|
|
433
|
+
.replace(/[^\S\r\n]+/g, " ")
|
|
434
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F-\u009F]+/g, "\n")
|
|
435
|
+
.split(/\r?\n/)
|
|
436
|
+
.map((line) => line.trim())
|
|
437
|
+
.filter((line) => line.length > 0)
|
|
438
|
+
.filter((line) => line.length > 1 || /[\u4e00-\u9fff]/.test(line))
|
|
439
|
+
.filter((line) => !line.includes("\uFFFD"))
|
|
440
|
+
.filter((line) => /[\p{L}\p{N}\]\)]/u.test(line))
|
|
441
|
+
.join("\n")
|
|
442
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
443
|
+
.trim();
|
|
169
444
|
}
|
|
170
445
|
function detectBodySignals(body, mimeType) {
|
|
171
446
|
const haystack = `${mimeType}\n${body.slice(0, 200_000)}`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mkterswingman/5mghost-wonder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15",
|
|
4
4
|
"description": "企微文档读取 CLI — WeCom document reader",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"build": "rm -rf dist && tsc && chmod +x dist/cli.js",
|
|
27
27
|
"typecheck": "tsc --noEmit",
|
|
28
28
|
"check:skills": "node scripts/check-skills.mjs",
|
|
29
|
-
"test": "npm run check:skills && node dist/wecom/url.test.js && node --test tests/sheet-parity.test.mjs && node --test tests/export-sanitize.test.mjs && node --test tests/format.test.mjs && node --test tests/cookies-validation.test.mjs",
|
|
29
|
+
"test": "npm run check:skills && node dist/wecom/url.test.js && node --test tests/sheet-parity.test.mjs && node --test tests/export-sanitize.test.mjs && node --test tests/format.test.mjs && node --test tests/cookies-validation.test.mjs && node --test tests/browser-read.test.mjs",
|
|
30
30
|
"smoke": "npm run build && node dist/cli.js help > /dev/null",
|
|
31
31
|
"postinstall": "node scripts/postinstall.mjs"
|
|
32
32
|
},
|
|
@@ -7,9 +7,9 @@ description: Use this skill when the user wants to install or set up wonder, say
|
|
|
7
7
|
|
|
8
8
|
## Skill version
|
|
9
9
|
|
|
10
|
-
This skill matches **wonder 0.0.
|
|
10
|
+
This skill matches **wonder 0.0.15**.
|
|
11
11
|
|
|
12
|
-
Once the CLI is installed in Step 1, run `wonder --version`. If the output does not equal `0.0.
|
|
12
|
+
Once the CLI is installed in Step 1, run `wonder --version`. If the output does not equal `0.0.15`, the CLI on disk has drifted from the skill text loaded in this session. Ask the user to run `/update-5mghost-wonder`, then **start a fresh AI session** (`/exit` and re-enter, or open a new chat) — skill text already loaded into a running session does not refresh after `wonder update`, even though the file on disk has been replaced.
|
|
13
13
|
|
|
14
14
|
After a successful first install, also remind the user to start a fresh AI session before invoking `/use-5mghost-wonder` for the first time. The skill files were just written to disk; the current session never loaded them.
|
|
15
15
|
|
|
@@ -10,10 +10,10 @@ the referenced workflow files needed for the current task.
|
|
|
10
10
|
|
|
11
11
|
## Version Gate
|
|
12
12
|
|
|
13
|
-
This skill matches **wonder 0.0.
|
|
13
|
+
This skill matches **wonder 0.0.15**.
|
|
14
14
|
|
|
15
15
|
On first use in a session, follow `references/session-init.md`. If the installed
|
|
16
|
-
CLI version differs from `0.0.
|
|
16
|
+
CLI version differs from `0.0.15`, stop and ask the user to run
|
|
17
17
|
`/update-5mghost-wonder`, then start a fresh AI session.
|
|
18
18
|
|
|
19
19
|
## Hard Rules
|
|
@@ -7,6 +7,7 @@ wonder read <url> --save <dir> # Output directory
|
|
|
7
7
|
wonder read <url> --no-cache # Force fresh export
|
|
8
8
|
|
|
9
9
|
wonder browser probe <url> # Experimental no-export browser evidence probe
|
|
10
|
+
wonder browser read <url> # Experimental no-export text read
|
|
10
11
|
wonder browser probe <url> --headed
|
|
11
12
|
wonder browser probe <url> --save <dir>
|
|
12
13
|
|
|
@@ -22,5 +23,5 @@ wonder auth login # Browser OAuth login
|
|
|
22
23
|
wonder version # Print version
|
|
23
24
|
```
|
|
24
25
|
|
|
25
|
-
Browser editor runtime remains experimental. No-export read
|
|
26
|
-
|
|
26
|
+
Browser editor runtime remains experimental. No-export read has experimental
|
|
27
|
+
browser commands, but it is not a lossless reader yet.
|
|
@@ -30,7 +30,17 @@ Do not describe screenshot/OCR output as lossless.
|
|
|
30
30
|
## Procedure
|
|
31
31
|
|
|
32
32
|
1. Confirm cookies are valid. If not, follow `cookie-recovery.md`.
|
|
33
|
-
2. Start with the built-in
|
|
33
|
+
2. Start with the built-in browser read:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
wonder browser read <url> --save /tmp/wonder-browser-read
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This currently attempts text extraction from the browser `opendoc`
|
|
40
|
+
`initialAttributedText`. Treat it as `partial` unless it also proves image and
|
|
41
|
+
table structure.
|
|
42
|
+
|
|
43
|
+
3. If the read is insufficient, run the evidence probe:
|
|
34
44
|
|
|
35
45
|
```bash
|
|
36
46
|
wonder browser probe <url> --save /tmp/wonder-browser-probe
|
|
@@ -38,9 +48,9 @@ wonder browser probe <url> --save /tmp/wonder-browser-probe
|
|
|
38
48
|
|
|
39
49
|
Use `--headed` only when login/debug visibility is needed.
|
|
40
50
|
|
|
41
|
-
|
|
51
|
+
4. If the probe is insufficient, continue with the available browser automation
|
|
42
52
|
runtime.
|
|
43
|
-
|
|
53
|
+
5. Capture non-destructive evidence:
|
|
44
54
|
- page URL and document type
|
|
45
55
|
- visible title
|
|
46
56
|
- loaded script/runtime globals that look like document stores
|
|
@@ -48,16 +58,16 @@ Use `--headed` only when login/debug visibility is needed.
|
|
|
48
58
|
media URLs
|
|
49
59
|
- WebSocket messages if they expose structured document operations
|
|
50
60
|
- screenshots only as a fallback or visual cross-check
|
|
51
|
-
|
|
61
|
+
6. For tables, specifically look for:
|
|
52
62
|
- cell coordinates
|
|
53
63
|
- displayed text
|
|
54
64
|
- merge ranges
|
|
55
65
|
- row/column sizes if available
|
|
56
66
|
- fixed cell images
|
|
57
67
|
- floating images with position, size, and anchor
|
|
58
|
-
|
|
68
|
+
7. For image-heavy docs, verify whether each image can be fetched as an original
|
|
59
69
|
resource or only appears in a raster screenshot.
|
|
60
|
-
|
|
70
|
+
8. Report `pass`, `partial`, or `fail` with the missing fields.
|
|
61
71
|
|
|
62
72
|
## Output Contract
|
|
63
73
|
|