@viyzhu/boss-cli-fork 0.6.8

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 (159) hide show
  1. package/AGENTS.md +30 -0
  2. package/LICENSE +674 -0
  3. package/README.md +223 -0
  4. package/dist/browser/browser_session.d.ts +27 -0
  5. package/dist/browser/browser_session.d.ts.map +1 -0
  6. package/dist/browser/browser_session.js +246 -0
  7. package/dist/browser/browser_session.js.map +1 -0
  8. package/dist/browser/cdp_browser.d.ts +50 -0
  9. package/dist/browser/cdp_browser.d.ts.map +1 -0
  10. package/dist/browser/cdp_browser.js +315 -0
  11. package/dist/browser/cdp_browser.js.map +1 -0
  12. package/dist/browser/human_delay.d.ts +117 -0
  13. package/dist/browser/human_delay.d.ts.map +1 -0
  14. package/dist/browser/human_delay.js +71 -0
  15. package/dist/browser/human_delay.js.map +1 -0
  16. package/dist/browser/index.d.ts +9 -0
  17. package/dist/browser/index.d.ts.map +1 -0
  18. package/dist/browser/index.js +9 -0
  19. package/dist/browser/index.js.map +1 -0
  20. package/dist/browser/timing.d.ts +7 -0
  21. package/dist/browser/timing.d.ts.map +1 -0
  22. package/dist/browser/timing.js +34 -0
  23. package/dist/browser/timing.js.map +1 -0
  24. package/dist/browser/viewport_temp.d.ts +15 -0
  25. package/dist/browser/viewport_temp.d.ts.map +1 -0
  26. package/dist/browser/viewport_temp.js +78 -0
  27. package/dist/browser/viewport_temp.js.map +1 -0
  28. package/dist/cli/banner.d.ts +2 -0
  29. package/dist/cli/banner.d.ts.map +1 -0
  30. package/dist/cli/banner.js +69 -0
  31. package/dist/cli/banner.js.map +1 -0
  32. package/dist/cli/cliRouter.d.ts +9 -0
  33. package/dist/cli/cliRouter.d.ts.map +1 -0
  34. package/dist/cli/cliRouter.js +604 -0
  35. package/dist/cli/cliRouter.js.map +1 -0
  36. package/dist/cli/index.d.ts +3 -0
  37. package/dist/cli/index.d.ts.map +1 -0
  38. package/dist/cli/index.js +34 -0
  39. package/dist/cli/index.js.map +1 -0
  40. package/dist/cli/version.d.ts +29 -0
  41. package/dist/cli/version.d.ts.map +1 -0
  42. package/dist/cli/version.js +180 -0
  43. package/dist/cli/version.js.map +1 -0
  44. package/dist/common/auth.d.ts +44 -0
  45. package/dist/common/auth.d.ts.map +1 -0
  46. package/dist/common/auth.js +168 -0
  47. package/dist/common/auth.js.map +1 -0
  48. package/dist/common/baidu_user_env.d.ts +7 -0
  49. package/dist/common/baidu_user_env.d.ts.map +1 -0
  50. package/dist/common/baidu_user_env.js +56 -0
  51. package/dist/common/baidu_user_env.js.map +1 -0
  52. package/dist/common/boss_availability.d.ts +5 -0
  53. package/dist/common/boss_availability.d.ts.map +1 -0
  54. package/dist/common/boss_availability.js +245 -0
  55. package/dist/common/boss_availability.js.map +1 -0
  56. package/dist/common/boss_modal.d.ts +14 -0
  57. package/dist/common/boss_modal.d.ts.map +1 -0
  58. package/dist/common/boss_modal.js +81 -0
  59. package/dist/common/boss_modal.js.map +1 -0
  60. package/dist/common/boss_page_guards.d.ts +14 -0
  61. package/dist/common/boss_page_guards.d.ts.map +1 -0
  62. package/dist/common/boss_page_guards.js +596 -0
  63. package/dist/common/boss_page_guards.js.map +1 -0
  64. package/dist/common/boss_paywall_popup.d.ts +28 -0
  65. package/dist/common/boss_paywall_popup.d.ts.map +1 -0
  66. package/dist/common/boss_paywall_popup.js +158 -0
  67. package/dist/common/boss_paywall_popup.js.map +1 -0
  68. package/dist/common/boss_session_lock.d.ts +2 -0
  69. package/dist/common/boss_session_lock.d.ts.map +1 -0
  70. package/dist/common/boss_session_lock.js +127 -0
  71. package/dist/common/boss_session_lock.js.map +1 -0
  72. package/dist/common/boss_session_page.d.ts +27 -0
  73. package/dist/common/boss_session_page.d.ts.map +1 -0
  74. package/dist/common/boss_session_page.js +173 -0
  75. package/dist/common/boss_session_page.js.map +1 -0
  76. package/dist/common/boss_sidebar_nav.d.ts +7 -0
  77. package/dist/common/boss_sidebar_nav.d.ts.map +1 -0
  78. package/dist/common/boss_sidebar_nav.js +53 -0
  79. package/dist/common/boss_sidebar_nav.js.map +1 -0
  80. package/dist/common/c_resume_capture.d.ts +19 -0
  81. package/dist/common/c_resume_capture.d.ts.map +1 -0
  82. package/dist/common/c_resume_capture.js +185 -0
  83. package/dist/common/c_resume_capture.js.map +1 -0
  84. package/dist/common/ensure_page.d.ts +9 -0
  85. package/dist/common/ensure_page.d.ts.map +1 -0
  86. package/dist/common/ensure_page.js +14 -0
  87. package/dist/common/ensure_page.js.map +1 -0
  88. package/dist/config.d.ts +22 -0
  89. package/dist/config.d.ts.map +1 -0
  90. package/dist/config.js +57 -0
  91. package/dist/config.js.map +1 -0
  92. package/dist/ocr/baidu_ocr.d.ts +13 -0
  93. package/dist/ocr/baidu_ocr.d.ts.map +1 -0
  94. package/dist/ocr/baidu_ocr.js +80 -0
  95. package/dist/ocr/baidu_ocr.js.map +1 -0
  96. package/dist/ocr/index.d.ts +3 -0
  97. package/dist/ocr/index.d.ts.map +1 -0
  98. package/dist/ocr/index.js +3 -0
  99. package/dist/ocr/index.js.map +1 -0
  100. package/dist/ocr/resume_ocr.d.ts +13 -0
  101. package/dist/ocr/resume_ocr.d.ts.map +1 -0
  102. package/dist/ocr/resume_ocr.js +38 -0
  103. package/dist/ocr/resume_ocr.js.map +1 -0
  104. package/dist/toolset/action.d.ts +12 -0
  105. package/dist/toolset/action.d.ts.map +1 -0
  106. package/dist/toolset/action.js +521 -0
  107. package/dist/toolset/action.js.map +1 -0
  108. package/dist/toolset/baidu_credentials.d.ts +5 -0
  109. package/dist/toolset/baidu_credentials.d.ts.map +1 -0
  110. package/dist/toolset/baidu_credentials.js +24 -0
  111. package/dist/toolset/baidu_credentials.js.map +1 -0
  112. package/dist/toolset/chat.d.ts +10 -0
  113. package/dist/toolset/chat.d.ts.map +1 -0
  114. package/dist/toolset/chat.js +763 -0
  115. package/dist/toolset/chat.js.map +1 -0
  116. package/dist/toolset/deep-search.d.ts +35 -0
  117. package/dist/toolset/deep-search.d.ts.map +1 -0
  118. package/dist/toolset/deep-search.js +1505 -0
  119. package/dist/toolset/deep-search.js.map +1 -0
  120. package/dist/toolset/greet.d.ts +6 -0
  121. package/dist/toolset/greet.d.ts.map +1 -0
  122. package/dist/toolset/greet.js +81 -0
  123. package/dist/toolset/greet.js.map +1 -0
  124. package/dist/toolset/index.d.ts +48 -0
  125. package/dist/toolset/index.d.ts.map +1 -0
  126. package/dist/toolset/index.js +71 -0
  127. package/dist/toolset/index.js.map +1 -0
  128. package/dist/toolset/jd.d.ts +10 -0
  129. package/dist/toolset/jd.d.ts.map +1 -0
  130. package/dist/toolset/jd.js +406 -0
  131. package/dist/toolset/jd.js.map +1 -0
  132. package/dist/toolset/list.d.ts +6 -0
  133. package/dist/toolset/list.d.ts.map +1 -0
  134. package/dist/toolset/list.js +146 -0
  135. package/dist/toolset/list.js.map +1 -0
  136. package/dist/toolset/login.d.ts +6 -0
  137. package/dist/toolset/login.d.ts.map +1 -0
  138. package/dist/toolset/login.js +76 -0
  139. package/dist/toolset/login.js.map +1 -0
  140. package/dist/toolset/normal-search.d.ts +12 -0
  141. package/dist/toolset/normal-search.d.ts.map +1 -0
  142. package/dist/toolset/normal-search.js +301 -0
  143. package/dist/toolset/normal-search.js.map +1 -0
  144. package/dist/toolset/preview.d.ts +5 -0
  145. package/dist/toolset/preview.d.ts.map +1 -0
  146. package/dist/toolset/preview.js +100 -0
  147. package/dist/toolset/preview.js.map +1 -0
  148. package/dist/toolset/recommend.d.ts +36 -0
  149. package/dist/toolset/recommend.d.ts.map +1 -0
  150. package/dist/toolset/recommend.js +428 -0
  151. package/dist/toolset/recommend.js.map +1 -0
  152. package/dist/toolset/send.d.ts +7 -0
  153. package/dist/toolset/send.d.ts.map +1 -0
  154. package/dist/toolset/send.js +52 -0
  155. package/dist/toolset/send.js.map +1 -0
  156. package/package.json +69 -0
  157. package/skills/boss-frontend-analysis/SKILL.md +58 -0
  158. package/skills/boss-frontend-analysis/agents/openai.yaml +4 -0
  159. package/skills/boss-frontend-analysis/scripts/capture_boss_frontend.mjs +604 -0
@@ -0,0 +1,604 @@
1
+ #!/usr/bin/env node
2
+ import { createHash } from 'node:crypto';
3
+ import { mkdir, readFile, readdir, stat, writeFile } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import process from 'node:process';
6
+
7
+ const ENTRY_PAGES = [
8
+ {
9
+ label: 'chat',
10
+ url: 'https://www.zhipin.com/web/chat/index',
11
+ },
12
+ {
13
+ label: 'login',
14
+ url: 'https://www.zhipin.com/web/user/?ka=header-login',
15
+ },
16
+ ];
17
+
18
+ const USER_AGENT =
19
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36';
20
+ const FETCH_TIMEOUT_MS = 30_000;
21
+ const RESEARCH_ROOT = path.join('docs', 'research', 'boss-online-js');
22
+ const PREVIOUS_BASELINE = '2026-07-01';
23
+ const HIGH_RISK_PATTERNS = [
24
+ 'risk-detection',
25
+ 'zhipin-sign',
26
+ 'zhipin-security',
27
+ 'browser-check',
28
+ 'warlock',
29
+ 'patas',
30
+ ];
31
+ const CAPTURE_HOSTS = new Set([
32
+ 'static.zhipin.com',
33
+ 'www.zhipin.com',
34
+ 'img.bosszhipin.com',
35
+ ]);
36
+ const SEARCH_TERMS = [
37
+ '99001',
38
+ '99002',
39
+ '99004',
40
+ '99005',
41
+ 'srcdoc',
42
+ 'MutationObserver',
43
+ 'isTrusted',
44
+ 'sendAction',
45
+ 'debugger',
46
+ 'Function(',
47
+ 'constructor',
48
+ 'setInterval',
49
+ 'console',
50
+ 'devtools',
51
+ 'verify',
52
+ 'security',
53
+ '403.html',
54
+ ];
55
+
56
+ function usage() {
57
+ return [
58
+ 'Usage: node skills/boss-frontend-analysis/scripts/capture_boss_frontend.mjs [--date YYYY-MM-DD] [--force]',
59
+ '',
60
+ 'Captures Boss/Zhipin frontend JavaScript into docs/research/boss-online-js/<date>.',
61
+ 'Without --force, the output directory must not already exist.',
62
+ ].join('\n');
63
+ }
64
+
65
+ function parseArgs(argv) {
66
+ const out = { force: false, date: null };
67
+ for (let i = 0; i < argv.length; i++) {
68
+ const arg = argv[i];
69
+ if (arg === '--force') {
70
+ out.force = true;
71
+ continue;
72
+ }
73
+ if (arg === '--date') {
74
+ const value = argv[i + 1];
75
+ if (!value || value.startsWith('--')) {
76
+ throw new Error('--date requires YYYY-MM-DD');
77
+ }
78
+ out.date = value;
79
+ i++;
80
+ continue;
81
+ }
82
+ if (arg === '--help' || arg === '-h') {
83
+ console.log(usage());
84
+ process.exit(0);
85
+ }
86
+ throw new Error(`Unknown argument: ${arg}`);
87
+ }
88
+ if (out.date && !/^\d{4}-\d{2}-\d{2}$/.test(out.date)) {
89
+ throw new Error(`Invalid --date: ${out.date}`);
90
+ }
91
+ return out;
92
+ }
93
+
94
+ function todayInShanghai() {
95
+ const parts = new Intl.DateTimeFormat('en-CA', {
96
+ timeZone: 'Asia/Shanghai',
97
+ year: 'numeric',
98
+ month: '2-digit',
99
+ day: '2-digit',
100
+ }).formatToParts(new Date());
101
+ const values = Object.fromEntries(parts.map((p) => [p.type, p.value]));
102
+ return `${values.year}-${values.month}-${values.day}`;
103
+ }
104
+
105
+ function sha256(buffer) {
106
+ return createHash('sha256').update(buffer).digest('hex');
107
+ }
108
+
109
+ function normalizeUrl(raw, baseUrl) {
110
+ try {
111
+ return new URL(raw, baseUrl).toString();
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+
117
+ function localPathForUrl(url) {
118
+ const u = new URL(url);
119
+ const parts = u.pathname.split('/').filter(Boolean);
120
+ const filename = parts.pop() || 'index';
121
+ let dir = parts.join('/');
122
+ dir = dir
123
+ .replace(/^assets\//, 'assets/')
124
+ .replace(/^static\//, 'static/')
125
+ .replace(/^zhipin-boss\/index\/([^/]+)\/static\/js$/, 'boss-index-$1')
126
+ .replace(/^zhipin-boss\/bundle\/([^/]+)\/static$/, 'boss-bundle-$1')
127
+ .replace(/^zhipin-boss\/bundle\/([^/]+)\/static\/js$/, 'boss-bundle-$1/static/js')
128
+ .replace(/^zhipin-sign\/([^/]+)\/static\/js$/, 'zhipin-sign-$1')
129
+ .replace(/^zhipin-security\/web\/geek\/polyfill$/, 'zhipin-security/geek-polyfill')
130
+ .replace(/^web\/user$/, 'entry');
131
+ const safeDir = dir || u.hostname.replace(/[^A-Za-z0-9.-]+/g, '_');
132
+ return path.join('raw', safeDir, filename);
133
+ }
134
+
135
+ function addDiscovered(discovered, url, source, note, required = true) {
136
+ if (!url) {
137
+ return;
138
+ }
139
+ const normalized = normalizeUrl(url, source.url);
140
+ if (!normalized) {
141
+ return;
142
+ }
143
+ if (!new URL(normalized).pathname.endsWith('.js')) {
144
+ return;
145
+ }
146
+ if (!discovered.has(normalized)) {
147
+ discovered.set(normalized, { url: normalized, sources: [], required: false });
148
+ }
149
+ discovered.get(normalized).required = discovered.get(normalized).required || required;
150
+ discovered.get(normalized).sources.push({ source: source.label, note });
151
+ }
152
+
153
+ function isCaptureScope(url) {
154
+ try {
155
+ const host = new URL(url).hostname;
156
+ return CAPTURE_HOSTS.has(host);
157
+ } catch {
158
+ return false;
159
+ }
160
+ }
161
+
162
+ function extractHtmlScripts(html, baseUrl) {
163
+ const urls = [];
164
+ const re = /<script\b[^>]+src=["']([^"']+)["']/gi;
165
+ for (const match of html.matchAll(re)) {
166
+ const url = normalizeUrl(match[1], baseUrl);
167
+ if (url && new URL(url).pathname.endsWith('.js')) {
168
+ urls.push(url);
169
+ }
170
+ }
171
+ return urls;
172
+ }
173
+
174
+ function extractAbsoluteScriptUrls(jsText, baseUrl) {
175
+ const urls = new Set();
176
+ const absoluteRe = /https?:\/\/[^"'()<>\s\\]+?\.js(?:\?[^"'()<>\s\\]*)?/g;
177
+ for (const match of jsText.matchAll(absoluteRe)) {
178
+ urls.add(match[0]);
179
+ }
180
+ const protocolRelativeRe = /\/\/[^"'()<>\s\\]+?\.js(?:\?[^"'()<>\s\\]*)?/g;
181
+ for (const match of jsText.matchAll(protocolRelativeRe)) {
182
+ urls.add(`https:${match[0]}`);
183
+ }
184
+ const current = new URL(baseUrl);
185
+ const rootRelativeRe = /["'](\/[^"'()<>\s\\]+?\.js(?:\?[^"'()<>\s\\]*)?)["']/g;
186
+ for (const match of jsText.matchAll(rootRelativeRe)) {
187
+ urls.add(`${current.origin}${match[1]}`);
188
+ }
189
+ return Array.from(urls);
190
+ }
191
+
192
+ function extractStaticJsPaths(jsText, baseUrl) {
193
+ const urls = new Set();
194
+ const base = new URL(baseUrl);
195
+ const publicPathMatches = Array.from(
196
+ jsText.matchAll(/[A-Za-z_$][\w$]*\.p\s*=\s*["']([^"']+)["']/g),
197
+ );
198
+ const publicPaths = publicPathMatches
199
+ .map((match) => normalizeUrl(match[1], baseUrl))
200
+ .filter(Boolean);
201
+
202
+ const literalPathRe = /["'](static\/js\/[^"']+?\.js)["']/g;
203
+ for (const match of jsText.matchAll(literalPathRe)) {
204
+ for (const publicPath of publicPaths) {
205
+ urls.add(normalizeUrl(match[1], publicPath));
206
+ }
207
+ }
208
+
209
+ return Array.from(urls).filter(Boolean);
210
+ }
211
+
212
+ function extractInferredWebpackChunkUrls(jsText, baseUrl) {
213
+ const publicPathMatches = Array.from(
214
+ jsText.matchAll(/[A-Za-z_$][\w$]*\.p\s*=\s*["']([^"']+)["']/g),
215
+ );
216
+ const publicPaths = publicPathMatches
217
+ .map((match) => normalizeUrl(match[1], baseUrl))
218
+ .filter(Boolean);
219
+ if (publicPaths.length === 0 || !/\.u\s*=\s*function\([^)]*\)\s*\{return["']static\/js\//.test(jsText)) {
220
+ return [];
221
+ }
222
+ const ids = Array.from(new Set(Array.from(jsText.matchAll(/\.e\((\d+)\)/g)).map((m) => m[1])));
223
+ const urls = [];
224
+ for (const publicPath of publicPaths) {
225
+ for (const id of ids) {
226
+ urls.push(normalizeUrl(`static/js/${id}.js`, publicPath));
227
+ }
228
+ }
229
+ return urls.filter(Boolean);
230
+ }
231
+
232
+ async function fetchStrict(url) {
233
+ const controller = new AbortController();
234
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
235
+ let res;
236
+ try {
237
+ res = await fetch(url, {
238
+ headers: {
239
+ 'user-agent': USER_AGENT,
240
+ accept: '*/*',
241
+ },
242
+ redirect: 'follow',
243
+ signal: controller.signal,
244
+ });
245
+ } catch (e) {
246
+ clearTimeout(timeout);
247
+ const msg = e instanceof Error ? e.message : String(e);
248
+ throw new Error(`Fetch failed for ${url}: ${msg}`);
249
+ }
250
+ if (!res.ok) {
251
+ clearTimeout(timeout);
252
+ throw new Error(`HTTP ${res.status} ${res.statusText} while fetching ${url}`);
253
+ }
254
+ try {
255
+ return {
256
+ finalUrl: res.url || url,
257
+ contentType: res.headers.get('content-type') || '',
258
+ buffer: Buffer.from(await res.arrayBuffer()),
259
+ };
260
+ } finally {
261
+ clearTimeout(timeout);
262
+ }
263
+ }
264
+
265
+ async function pathExists(target) {
266
+ try {
267
+ await stat(target);
268
+ return true;
269
+ } catch (e) {
270
+ if (e && e.code === 'ENOENT') {
271
+ return false;
272
+ }
273
+ throw e;
274
+ }
275
+ }
276
+
277
+ async function latestBaselineFolder(root, excludeDate) {
278
+ if (!(await pathExists(root))) {
279
+ return null;
280
+ }
281
+ const names = await readdir(root);
282
+ const dated = names
283
+ .filter((name) => /^\d{4}-\d{2}-\d{2}$/.test(name) && name !== excludeDate)
284
+ .sort();
285
+ return dated.at(-1) || null;
286
+ }
287
+
288
+ async function readPreviousManifest(root, date) {
289
+ const manifestPath = path.join(root, date, 'manifest.json');
290
+ if (!(await pathExists(manifestPath))) {
291
+ return null;
292
+ }
293
+ return JSON.parse(await readFile(manifestPath, 'utf8'));
294
+ }
295
+
296
+ function parseVersion(url, marker) {
297
+ const idx = url.indexOf(marker);
298
+ if (idx === -1) {
299
+ return null;
300
+ }
301
+ const rest = url.slice(idx + marker.length);
302
+ const version = rest.split('/')[0];
303
+ return version || null;
304
+ }
305
+
306
+ function summarizeVersions(entries) {
307
+ const urls = entries.map((e) => e.url);
308
+ return {
309
+ bossIndex: Array.from(
310
+ new Set(urls.map((u) => parseVersion(u, '/zhipin-boss/index/')).filter(Boolean)),
311
+ ).sort(),
312
+ bossBundle: Array.from(
313
+ new Set(urls.map((u) => parseVersion(u, '/zhipin-boss/bundle/')).filter(Boolean)),
314
+ ).sort(),
315
+ zhipinSign: Array.from(
316
+ new Set(urls.map((u) => parseVersion(u, '/zhipin-sign/')).filter(Boolean)),
317
+ ).sort(),
318
+ };
319
+ }
320
+
321
+ function searchHits(text) {
322
+ const hits = {};
323
+ for (const term of SEARCH_TERMS) {
324
+ const count = text.split(term).length - 1;
325
+ if (count > 0) {
326
+ hits[term] = count;
327
+ }
328
+ }
329
+ return hits;
330
+ }
331
+
332
+ function uniqueSorted(values) {
333
+ return Array.from(new Set(values)).sort();
334
+ }
335
+
336
+ function buildAnalysis({ date, baselineDate, manifest, previousManifest }) {
337
+ const currentVersions = summarizeVersions(manifest.entries);
338
+ const previousVersions = previousManifest ? summarizeVersions(previousManifest.entries || []) : null;
339
+ const currentUrls = new Set(manifest.entries.map((e) => e.url));
340
+ const previousUrls = new Set((previousManifest?.entries || []).map((e) => e.url));
341
+ const added = uniqueSorted(Array.from(currentUrls).filter((url) => !previousUrls.has(url)));
342
+ const removed = uniqueSorted(Array.from(previousUrls).filter((url) => !currentUrls.has(url)));
343
+ const changedHashes = [];
344
+
345
+ if (previousManifest) {
346
+ const prevByUrl = new Map(previousManifest.entries.map((entry) => [entry.url, entry]));
347
+ for (const entry of manifest.entries) {
348
+ const prev = prevByUrl.get(entry.url);
349
+ if (prev && prev.sha256 !== entry.sha256) {
350
+ changedHashes.push({
351
+ url: entry.url,
352
+ previousSha256: prev.sha256,
353
+ currentSha256: entry.sha256,
354
+ });
355
+ }
356
+ }
357
+ }
358
+
359
+ const highRisk = manifest.entries.filter((entry) =>
360
+ HIGH_RISK_PATTERNS.some((pattern) => entry.url.includes(pattern)),
361
+ );
362
+ const riskHits = highRisk
363
+ .map((entry) => ({
364
+ path: entry.localPath,
365
+ url: entry.url,
366
+ hits: entry.searchHits,
367
+ }))
368
+ .filter((entry) => Object.keys(entry.hits).length > 0);
369
+
370
+ const baselineChanged =
371
+ JSON.stringify(currentVersions.bossIndex) !== JSON.stringify(previousVersions?.bossIndex || []) ||
372
+ JSON.stringify(currentVersions.bossBundle) !==
373
+ JSON.stringify(previousVersions?.bossBundle || []) ||
374
+ JSON.stringify(currentVersions.zhipinSign) !==
375
+ JSON.stringify(previousVersions?.zhipinSign || []);
376
+
377
+ const lines = [
378
+ `# Boss Frontend Analysis - ${date}`,
379
+ '',
380
+ `Baseline compared: ${baselineDate || 'none'}`,
381
+ '',
382
+ '## Versions',
383
+ '',
384
+ `- Current Boss index: ${currentVersions.bossIndex.join(', ') || 'none'}`,
385
+ `- Previous Boss index: ${previousVersions?.bossIndex.join(', ') || 'none'}`,
386
+ `- Current Boss bundle: ${currentVersions.bossBundle.join(', ') || 'none'}`,
387
+ `- Previous Boss bundle: ${previousVersions?.bossBundle.join(', ') || 'none'}`,
388
+ `- Current Zhipin sign: ${currentVersions.zhipinSign.join(', ') || 'none'}`,
389
+ `- Previous Zhipin sign: ${previousVersions?.zhipinSign.join(', ') || 'none'}`,
390
+ '',
391
+ '## Asset Diff',
392
+ '',
393
+ `- Added URLs: ${added.length}`,
394
+ `- Removed URLs: ${removed.length}`,
395
+ `- Same-URL hash changes: ${changedHashes.length}`,
396
+ '',
397
+ ];
398
+
399
+ if (added.length > 0) {
400
+ lines.push('### Added URLs', '', ...added.map((url) => `- ${url}`), '');
401
+ }
402
+ if (removed.length > 0) {
403
+ lines.push('### Removed URLs', '', ...removed.map((url) => `- ${url}`), '');
404
+ }
405
+ if (changedHashes.length > 0) {
406
+ lines.push(
407
+ '### Same-URL Hash Changes',
408
+ '',
409
+ ...changedHashes.map(
410
+ (item) => `- ${item.url}: ${item.previousSha256} -> ${item.currentSha256}`,
411
+ ),
412
+ '',
413
+ );
414
+ }
415
+
416
+ lines.push('## High-Risk Pattern Hits', '');
417
+ if (riskHits.length === 0) {
418
+ lines.push('- No configured high-risk terms found in high-risk scripts.', '');
419
+ } else {
420
+ for (const item of riskHits) {
421
+ const summary = Object.entries(item.hits)
422
+ .map(([term, count]) => `${term}=${count}`)
423
+ .join(', ');
424
+ lines.push(`- ${item.path}: ${summary}`);
425
+ }
426
+ lines.push('');
427
+ }
428
+
429
+ if (manifest.skippedNestedScriptUrls.length > 0) {
430
+ lines.push('## Skipped Nested Script URLs', '');
431
+ lines.push(
432
+ 'These URLs were discovered inside downloaded JavaScript but are outside the capture host allowlist.',
433
+ '',
434
+ ...manifest.skippedNestedScriptUrls.map((item) => `- ${item.url} (from ${item.source})`),
435
+ '',
436
+ );
437
+ }
438
+
439
+ if (manifest.unresolvedScriptUrls.length > 0) {
440
+ lines.push('## Unresolved Inferred Script URLs', '');
441
+ lines.push(
442
+ 'These URLs were inferred from webpack runtime chunk ids but could not be downloaded.',
443
+ '',
444
+ ...manifest.unresolvedScriptUrls.map(
445
+ (item) => `- ${item.url}: ${item.error} (from ${item.source})`,
446
+ ),
447
+ '',
448
+ );
449
+ }
450
+
451
+ lines.push('## Recommendation', '');
452
+ if (baselineChanged || added.length > 0 || removed.length > 0 || changedHashes.length > 0) {
453
+ lines.push(
454
+ '- Keep boss-cli disabled until the archived raw scripts are manually reviewed.',
455
+ '- Update `src/common/boss_availability.ts` only after accepting the new script versions and hashes.',
456
+ '- Re-check `src/common/boss_page_guards.ts` request patterns against any new risk, security, or reporting script URLs.',
457
+ '- Update `docs/anti-detection.md` with the new versions and risk findings.',
458
+ );
459
+ } else {
460
+ lines.push(
461
+ '- Online scripts match the compared baseline at URL/version/hash level.',
462
+ '- Keep existing availability constants unless manual review finds behavior changes.',
463
+ );
464
+ }
465
+ lines.push('');
466
+
467
+ return lines.join('\n');
468
+ }
469
+
470
+ async function main() {
471
+ const args = parseArgs(process.argv.slice(2));
472
+ const date = args.date || todayInShanghai();
473
+ const outDir = path.join(RESEARCH_ROOT, date);
474
+ if ((await pathExists(outDir)) && !args.force) {
475
+ throw new Error(`Output directory already exists: ${outDir}. Use --force to overwrite files.`);
476
+ }
477
+
478
+ await mkdir(path.join(outDir, 'raw'), { recursive: true });
479
+
480
+ const discovered = new Map();
481
+ const entryPages = [];
482
+ for (const entry of ENTRY_PAGES) {
483
+ const fetched = await fetchStrict(entry.url);
484
+ const html = fetched.buffer.toString('utf8');
485
+ entryPages.push({
486
+ label: entry.label,
487
+ url: entry.url,
488
+ finalUrl: fetched.finalUrl,
489
+ bytes: fetched.buffer.length,
490
+ sha256: sha256(fetched.buffer),
491
+ scripts: extractHtmlScripts(html, entry.url),
492
+ });
493
+ for (const scriptUrl of extractHtmlScripts(html, entry.url)) {
494
+ addDiscovered(discovered, scriptUrl, entry, 'entry page script');
495
+ }
496
+ }
497
+
498
+ const queue = Array.from(discovered.keys());
499
+ const fetchedScripts = new Map();
500
+ const skippedNestedScripts = new Map();
501
+ const unresolvedScripts = [];
502
+
503
+ for (let i = 0; i < queue.length; i++) {
504
+ const url = queue[i];
505
+ const meta = discovered.get(url);
506
+ let fetched;
507
+ try {
508
+ fetched = await fetchStrict(url);
509
+ } catch (e) {
510
+ if (!meta?.required) {
511
+ unresolvedScripts.push({
512
+ url,
513
+ source: (meta?.sources || []).map((item) => item.source).join(', '),
514
+ error: e instanceof Error ? e.message : String(e),
515
+ });
516
+ continue;
517
+ }
518
+ throw e;
519
+ }
520
+ const text = fetched.buffer.toString('utf8');
521
+ fetchedScripts.set(url, { fetched, text });
522
+
523
+ const source = { label: url, url };
524
+ const nested = [...extractAbsoluteScriptUrls(text, url), ...extractStaticJsPaths(text, url)];
525
+ for (const nestedUrl of nested) {
526
+ if (!nestedUrl || !nestedUrl.endsWith('.js')) {
527
+ continue;
528
+ }
529
+ if (!new URL(nestedUrl).pathname.endsWith('.js')) {
530
+ continue;
531
+ }
532
+ if (!isCaptureScope(nestedUrl)) {
533
+ if (!skippedNestedScripts.has(nestedUrl)) {
534
+ skippedNestedScripts.set(nestedUrl, []);
535
+ }
536
+ skippedNestedScripts.get(nestedUrl).push(url);
537
+ continue;
538
+ }
539
+ if (!discovered.has(nestedUrl)) {
540
+ discovered.set(nestedUrl, { url: nestedUrl, sources: [] });
541
+ queue.push(nestedUrl);
542
+ }
543
+ discovered.get(nestedUrl).sources.push({ source: source.label, note: 'nested script reference' });
544
+ }
545
+ for (const chunkUrl of extractInferredWebpackChunkUrls(text, url)) {
546
+ addDiscovered(discovered, chunkUrl, source, 'inferred webpack chunk', false);
547
+ if (!queue.includes(chunkUrl)) {
548
+ queue.push(chunkUrl);
549
+ }
550
+ }
551
+ }
552
+
553
+ const entries = [];
554
+ for (const url of Array.from(fetchedScripts.keys()).sort()) {
555
+ const { fetched, text } = fetchedScripts.get(url);
556
+ const localPath = localPathForUrl(url);
557
+ const absLocal = path.join(outDir, localPath);
558
+ await mkdir(path.dirname(absLocal), { recursive: true });
559
+ await writeFile(absLocal, fetched.buffer);
560
+ entries.push({
561
+ url,
562
+ finalUrl: fetched.finalUrl,
563
+ localPath: localPath.replace(/\\/g, '/'),
564
+ bytes: fetched.buffer.length,
565
+ sha256: sha256(fetched.buffer),
566
+ contentType: fetched.contentType,
567
+ sources: discovered.get(url)?.sources || [],
568
+ searchHits: searchHits(text),
569
+ });
570
+ }
571
+
572
+ const manifest = {
573
+ capturedAt: new Date().toISOString(),
574
+ entryPages,
575
+ count: entries.length,
576
+ totalBytes: entries.reduce((sum, entry) => sum + entry.bytes, 0),
577
+ skippedNestedScriptUrls: Array.from(skippedNestedScripts.entries())
578
+ .map(([url, sources]) => ({ url, source: uniqueSorted(sources).join(', ') }))
579
+ .sort((a, b) => a.url.localeCompare(b.url)),
580
+ unresolvedScriptUrls: unresolvedScripts.sort((a, b) => a.url.localeCompare(b.url)),
581
+ entries,
582
+ };
583
+ await writeFile(path.join(outDir, 'manifest.json'), `${JSON.stringify(manifest, null, 2)}\n`);
584
+ await writeFile(
585
+ path.join(outDir, '.gitattributes'),
586
+ ['raw/** -text diff', 'manifest.json text eol=lf', 'analysis.md text eol=lf', ''].join('\n'),
587
+ );
588
+
589
+ const baselineDate =
590
+ (await latestBaselineFolder(RESEARCH_ROOT, date)) ||
591
+ (await pathExists(path.join(RESEARCH_ROOT, PREVIOUS_BASELINE)) ? PREVIOUS_BASELINE : null);
592
+ const previousManifest = baselineDate ? await readPreviousManifest(RESEARCH_ROOT, baselineDate) : null;
593
+ const analysis = buildAnalysis({ date, baselineDate, manifest, previousManifest });
594
+ await writeFile(path.join(outDir, 'analysis.md'), analysis);
595
+
596
+ console.log(`Captured ${entries.length} scripts to ${outDir}`);
597
+ console.log(`Wrote ${path.join(outDir, 'manifest.json')}`);
598
+ console.log(`Wrote ${path.join(outDir, 'analysis.md')}`);
599
+ }
600
+
601
+ main().catch((error) => {
602
+ console.error(error instanceof Error ? error.message : String(error));
603
+ process.exit(1);
604
+ });