@mkterswingman/5mghost-wonder 0.0.10 → 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.
@@ -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 MIN_KEY_COOKIES_REQUIRED = 3;
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
- * MIN_KEY_COOKIES_REQUIRED key cookies are present → return Record<string,string>.
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
- * MIN_KEY_COOKIES_REQUIRED are present, null otherwise.
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 presentKeys = KEY_COOKIES.filter((k) => cookieNames.has(k));
301
- if (presentKeys.length < MIN_KEY_COOKIES_REQUIRED)
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 delay(500);
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
+ }
@@ -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.10",
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**.
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.10`, 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.
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
 
@@ -79,18 +79,27 @@ Check current cookie status:
79
79
  wonder wecom status
80
80
  ```
81
81
 
82
- If expired or missing, run:
82
+ If the cookie is expired, missing, invalid, or the check gets 401/403, do not
83
+ give the user a manual cookie-export path. Ask the user whether now is a
84
+ convenient time to open the browser for WeCom login. If they confirm, run the
85
+ CLI yourself:
83
86
 
84
87
  ```bash
85
88
  wonder wecom cookie
86
89
  ```
87
90
 
88
- This launches the local Chrome/Edge browser via CDP and blocks until the user scans the QR code in WeCom mobile (or the CDP wait times out). When the command returns, verify:
91
+ This is expected to launch the local Chrome/Edge browser login page via CDP and
92
+ block until the user logs in or scans the QR code in WeCom mobile. When the
93
+ command returns, verify:
89
94
 
90
95
  ```bash
91
96
  wonder wecom status
92
97
  ```
93
98
 
99
+ Never ask the user to install Cookie-Editor, export JSON, paste a cookie
100
+ string, run `wonder wecom cookie` themselves, or use `wonder wecom set-cookie`
101
+ unless the user explicitly asks for a manual/dev/CI cookie path.
102
+
94
103
  ---
95
104
 
96
105
  ## Step 4 — Detect and install docx skill (for the AI you are running in)
@@ -7,7 +7,7 @@ description: Use this skill when the user wants to update or upgrade wonder, say
7
7
 
8
8
  ## Skill version
9
9
 
10
- This skill matches **wonder 0.0.10**.
10
+ This skill matches **wonder 0.0.13**.
11
11
 
12
12
  ---
13
13
 
@@ -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**.
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.10`. 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.
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,26 +19,36 @@ 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.10)
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
26
26
  wonder wecom status
27
27
  ```
28
28
 
29
- If `wonder wecom status` reports that the cookie is expired or missing:
29
+ ## Cookie refresh behavior
30
+
31
+ If `wonder wecom status`, `wonder read <url>`, or any WeCom request reports
32
+ that the cookie is expired, missing, invalid, or got 401/403, do not give the
33
+ user a manual cookie-export path. Ask the user whether now is a convenient time
34
+ to open the browser for WeCom login. If they confirm, run the CLI yourself:
30
35
 
31
36
  ```bash
32
37
  wonder wecom cookie
33
38
  ```
34
39
 
35
- Wait for the user to complete the QR code scan in the browser, then verify:
40
+ `wonder wecom cookie` is expected to launch the local browser login page. Wait
41
+ for the user to complete the browser login or QR scan, then verify:
36
42
 
37
43
  ```bash
38
44
  wonder wecom status
39
45
  ```
40
46
 
41
- Proceed only after cookie is valid.
47
+ Proceed only after the cookie is valid, then retry the original `wonder read`
48
+ command. Never ask the user to install Cookie-Editor, export JSON, paste a
49
+ cookie string, run `wonder wecom cookie` themselves, or use
50
+ `wonder wecom set-cookie` unless the user explicitly asks for a manual/dev/CI
51
+ cookie path.
42
52
 
43
53
  ---
44
54