@mkterswingman/5mghost-wonder 0.0.11 → 0.0.13
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/wecom/browser.js
CHANGED
|
@@ -13,7 +13,9 @@ const LOGIN_URL = "https://doc.weixin.qq.com";
|
|
|
13
13
|
const LOGIN_TIMEOUT_MS = 3 * 60 * 1000;
|
|
14
14
|
const COOKIE_POLL_INTERVAL_MS = 2000;
|
|
15
15
|
const CDP_CALL_TIMEOUT_MS = 5000;
|
|
16
|
-
const
|
|
16
|
+
const PROFILE_FLUSH_DELAY_MS = 1500;
|
|
17
|
+
const GRACEFUL_BROWSER_CLOSE_TIMEOUT_MS = 5000;
|
|
18
|
+
const SIGTERM_BROWSER_CLOSE_TIMEOUT_MS = 3000;
|
|
17
19
|
const KEY_COOKIES = [
|
|
18
20
|
"TOK",
|
|
19
21
|
"wedrive_sid",
|
|
@@ -47,7 +49,7 @@ function assertLocalhostUrl(url) {
|
|
|
47
49
|
}
|
|
48
50
|
/**
|
|
49
51
|
* Spawn Chrome → connect via CDP WebSocket → poll Storage.getCookies until
|
|
50
|
-
*
|
|
52
|
+
* all key cookies are present → return Record<string,string>.
|
|
51
53
|
*/
|
|
52
54
|
export async function collectWecomCookiesViaBrowser(options) {
|
|
53
55
|
const timeoutMs = options.timeoutMs ?? LOGIN_TIMEOUT_MS;
|
|
@@ -83,14 +85,24 @@ export async function collectWecomCookiesViaBrowser(options) {
|
|
|
83
85
|
const startedAt = Date.now();
|
|
84
86
|
while (Date.now() - startedAt < timeoutMs) {
|
|
85
87
|
const cookies = await readWecomCookies(socket);
|
|
86
|
-
if (cookies !== null)
|
|
88
|
+
if (cookies !== null) {
|
|
89
|
+
// Give Chrome a short window to flush cookie/profile state before
|
|
90
|
+
// shutdown. Returning immediately made the exported cookies valid
|
|
91
|
+
// while the dedicated browser profile could remain effectively
|
|
92
|
+
// logged out on the next refresh.
|
|
93
|
+
if (child)
|
|
94
|
+
await delay(PROFILE_FLUSH_DELAY_MS);
|
|
87
95
|
return cookies;
|
|
96
|
+
}
|
|
88
97
|
await delay(COOKIE_POLL_INTERVAL_MS);
|
|
89
98
|
}
|
|
90
99
|
throw new Error(`Timed out waiting for WeCom login after ${Math.round(timeoutMs / 1000)}s. ` +
|
|
91
100
|
"Sign in to WeCom in the opened browser, then rerun `wonder wecom cookie`.");
|
|
92
101
|
}
|
|
93
102
|
finally {
|
|
103
|
+
if (child) {
|
|
104
|
+
await closeBrowserGracefully(socket, child);
|
|
105
|
+
}
|
|
94
106
|
socket.close();
|
|
95
107
|
}
|
|
96
108
|
}
|
|
@@ -291,14 +303,14 @@ function isTrustedDomain(domain) {
|
|
|
291
303
|
}
|
|
292
304
|
/**
|
|
293
305
|
* Poll CDP for WeCom cookies. Returns Record<string,string> when
|
|
294
|
-
*
|
|
306
|
+
* all key cookies are present, null otherwise.
|
|
295
307
|
*/
|
|
296
308
|
async function readWecomCookies(socket) {
|
|
297
309
|
const payload = await sendCdpCommand(socket, "Storage.getCookies");
|
|
298
310
|
const trusted = (payload.cookies ?? []).filter((c) => isTrustedDomain(c.domain));
|
|
299
311
|
const cookieNames = new Set(trusted.map((c) => c.name));
|
|
300
|
-
const
|
|
301
|
-
if (
|
|
312
|
+
const missingKeys = KEY_COOKIES.filter((k) => !cookieNames.has(k));
|
|
313
|
+
if (missingKeys.length > 0)
|
|
302
314
|
return null;
|
|
303
315
|
// Build Record; later entries overwrite earlier ones for the same name.
|
|
304
316
|
const result = {};
|
|
@@ -327,7 +339,9 @@ async function terminateBrowserProcess(child) {
|
|
|
327
339
|
else {
|
|
328
340
|
child.kill();
|
|
329
341
|
}
|
|
330
|
-
await
|
|
342
|
+
const exitedAfterSigterm = await waitForBrowserExit(child, SIGTERM_BROWSER_CLOSE_TIMEOUT_MS);
|
|
343
|
+
if (exitedAfterSigterm)
|
|
344
|
+
return;
|
|
331
345
|
if (child.exitCode === null) {
|
|
332
346
|
try {
|
|
333
347
|
if (process.platform !== "win32" && child.pid) {
|
|
@@ -342,3 +356,31 @@ async function terminateBrowserProcess(child) {
|
|
|
342
356
|
}
|
|
343
357
|
}
|
|
344
358
|
}
|
|
359
|
+
async function closeBrowserGracefully(socket, child) {
|
|
360
|
+
if (child.exitCode !== null)
|
|
361
|
+
return;
|
|
362
|
+
try {
|
|
363
|
+
await sendCdpCommand(socket, "Browser.close");
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
// Browser.close can close the websocket before the response arrives. The
|
|
367
|
+
// process exit is the authoritative signal here.
|
|
368
|
+
}
|
|
369
|
+
await waitForBrowserExit(child, GRACEFUL_BROWSER_CLOSE_TIMEOUT_MS);
|
|
370
|
+
}
|
|
371
|
+
function waitForBrowserExit(child, timeoutMs) {
|
|
372
|
+
if (child.exitCode !== null || child.signalCode !== null) {
|
|
373
|
+
return Promise.resolve(true);
|
|
374
|
+
}
|
|
375
|
+
return new Promise((resolve) => {
|
|
376
|
+
const timer = setTimeout(() => {
|
|
377
|
+
child.removeListener("exit", onExit);
|
|
378
|
+
resolve(false);
|
|
379
|
+
}, timeoutMs);
|
|
380
|
+
const onExit = () => {
|
|
381
|
+
clearTimeout(timer);
|
|
382
|
+
resolve(true);
|
|
383
|
+
};
|
|
384
|
+
child.once("exit", onExit);
|
|
385
|
+
});
|
|
386
|
+
}
|
package/dist/wecom/cookies.js
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
3
3
|
// Storage: ~/.wonder/cookies.json (atomic write via tmp+rename, chmod 600)
|
|
4
4
|
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
5
5
|
import { dirname } from "node:path";
|
|
6
|
+
const REQUIRED_COOKIES_FOR_VALIDATION = [
|
|
7
|
+
"TOK",
|
|
8
|
+
"wedrive_sid",
|
|
9
|
+
"uid",
|
|
10
|
+
"uid_key",
|
|
11
|
+
"wedrive_ticket",
|
|
12
|
+
"wedrive_skey",
|
|
13
|
+
];
|
|
6
14
|
// ---------------------------------------------------------------------------
|
|
7
15
|
// Atomic write helper
|
|
8
16
|
// ---------------------------------------------------------------------------
|
|
@@ -68,6 +76,9 @@ export function removeCookies(cookiesPath) {
|
|
|
68
76
|
* - Network error → conservatively return true (avoid false "expired" on offline)
|
|
69
77
|
*/
|
|
70
78
|
export async function validateCookies(cookies) {
|
|
79
|
+
const missingRequired = REQUIRED_COOKIES_FOR_VALIDATION.some((name) => !cookies[name]);
|
|
80
|
+
if (missingRequired)
|
|
81
|
+
return false;
|
|
71
82
|
// W-02: strip `;` and `\n` from cookie values to prevent header injection.
|
|
72
83
|
const cookieHeader = Object.entries(cookies)
|
|
73
84
|
.map(([k, v]) => `${k}=${String(v).replace(/[;\n]/g, "")}`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mkterswingman/5mghost-wonder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"description": "企微文档读取 CLI — WeCom document reader",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "rm -rf dist && tsc && chmod +x dist/cli.js",
|
|
27
27
|
"typecheck": "tsc --noEmit",
|
|
28
|
-
"test": "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",
|
|
28
|
+
"test": "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
29
|
"smoke": "npm run build && node dist/cli.js help > /dev/null",
|
|
30
30
|
"postinstall": "node scripts/postinstall.mjs"
|
|
31
31
|
},
|
|
@@ -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.13**.
|
|
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.13`, 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
|
|
|
@@ -7,9 +7,9 @@ description: Use this skill when the user shares a URL containing "doc.weixin.qq
|
|
|
7
7
|
|
|
8
8
|
## Skill version
|
|
9
9
|
|
|
10
|
-
This skill matches **wonder 0.0.
|
|
10
|
+
This skill matches **wonder 0.0.13**.
|
|
11
11
|
|
|
12
|
-
In the Session Initialization step below, after running `wonder --version`, compare the output to `0.0.
|
|
12
|
+
In the Session Initialization step below, after running `wonder --version`, compare the output to `0.0.13`. If they differ, the loaded skill text and the CLI on disk are out of sync. Stop, tell the user to run `/update-5mghost-wonder`, and then **start a fresh AI session** (`/exit` and re-enter, or open a new chat) before trying again. `wonder update` rewrites the skill files on disk, but skill text already loaded into the current session does not auto-refresh.
|
|
13
13
|
|
|
14
14
|
## Session Initialization (first use only per session)
|
|
15
15
|
|
|
@@ -19,7 +19,7 @@ The **first time** you encounter a WeCom document URL in a session, run these ch
|
|
|
19
19
|
# 1. Check for updates
|
|
20
20
|
wonder update
|
|
21
21
|
|
|
22
|
-
# 2. Confirm the CLI version matches this skill's expected version (0.0.
|
|
22
|
+
# 2. Confirm the CLI version matches this skill's expected version (0.0.13)
|
|
23
23
|
wonder --version
|
|
24
24
|
|
|
25
25
|
# 3. Check WeCom cookie status
|