hunter-mate 0.1.13 → 0.1.14

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 (47) hide show
  1. package/dist/assets/extension/background/service-worker.js +7 -0
  2. package/dist/assets/extension/build-info.json +1 -1
  3. package/dist/assets/extension/content/index.js +4 -3
  4. package/dist/assets/extension/manifest.json +1 -1
  5. package/dist/assets/extension/sidepanel/index.css +1 -1
  6. package/dist/assets/extension/sidepanel/index.js +30 -8
  7. package/dist/assets/server/build-info.json +1 -1
  8. package/dist/assets/server/datasync/registry.d.ts +1 -1
  9. package/dist/assets/server/datasync/registry.js +32 -23
  10. package/dist/assets/server/datasync/registry.js.map +1 -1
  11. package/dist/assets/server/package.json +1 -0
  12. package/dist/commands/doctor.js +46 -0
  13. package/dist/commands/ops.js +45 -8
  14. package/dist/commands/update.js +5 -5
  15. package/dist/index.js +5 -1
  16. package/dist/updater.d.ts +95 -2
  17. package/dist/updater.js +372 -32
  18. package/dist/web/dist/404.html +1 -1
  19. package/dist/web/dist/_next/static/chunks/{main-app-0af848156a5b13fc.js → main-app-cb4554c86eba200c.js} +1 -1
  20. package/dist/web/dist/_next/static/chunks/pages/{_app-3d844792ec91e4d1.js → _app-834b8a94a4a3c493.js} +2 -2
  21. package/dist/web/dist/auth/verify.html +1 -1
  22. package/dist/web/dist/auth/verify.txt +1 -1
  23. package/dist/web/dist/companies.html +1 -1
  24. package/dist/web/dist/companies.txt +1 -1
  25. package/dist/web/dist/entity.html +1 -1
  26. package/dist/web/dist/entity.txt +1 -1
  27. package/dist/web/dist/index.html +1 -1
  28. package/dist/web/dist/index.txt +1 -1
  29. package/dist/web/dist/jobs.html +1 -1
  30. package/dist/web/dist/jobs.txt +1 -1
  31. package/dist/web/dist/logs.html +1 -1
  32. package/dist/web/dist/logs.txt +1 -1
  33. package/dist/web/dist/matching/jobs/placeholder/talents.html +1 -1
  34. package/dist/web/dist/matching/jobs/placeholder/talents.txt +1 -1
  35. package/dist/web/dist/matching/portraits/placeholder/jobs.html +1 -1
  36. package/dist/web/dist/matching/portraits/placeholder/jobs.txt +1 -1
  37. package/dist/web/dist/parselog.html +1 -1
  38. package/dist/web/dist/parselog.txt +1 -1
  39. package/dist/web/dist/recordings.html +1 -1
  40. package/dist/web/dist/recordings.txt +1 -1
  41. package/dist/web/dist/snapshots/placeholder/agent-prompt.html +1 -1
  42. package/dist/web/dist/snapshots/placeholder/agent-prompt.txt +1 -1
  43. package/dist/web/dist/talents.html +1 -1
  44. package/dist/web/dist/talents.txt +1 -1
  45. package/package.json +3 -2
  46. /package/dist/web/dist/_next/static/{6dSStiRxUpJkNfwFqjG-R → ZJtEh3uyN_mQhaXvrDYzJ}/_buildManifest.js +0 -0
  47. /package/dist/web/dist/_next/static/{6dSStiRxUpJkNfwFqjG-R → ZJtEh3uyN_mQhaXvrDYzJ}/_ssgManifest.js +0 -0
package/dist/updater.js CHANGED
@@ -8,23 +8,56 @@
8
8
  *
9
9
  * 重要: hunter-mate 使用用户级安装(--prefix ~/.hunter-mate/cli),
10
10
  * 避免 Workbuddy/Trae 沙盒拦截 `npm install -g` 全局写入。
11
- * 因此覆盖 kit 默认的 npmSelfUpdate/autoCliUpdate,统一走 --prefix。
11
+ * 因此覆盖 kit 默认的 npmSelfUpdate/autoCliUpdate。
12
+ * 但自更新必须装回【当前运行 CLI 的真实安装根】(全局 -g / 自定义 prefix /
13
+ * ~/.hunter-mate/cli 三形态检测),否则会装进孤儿目录,用户永远跑旧版
14
+ * (2026-09 现场:同事全部卡在 0.1.11,即此因)。
15
+ *
16
+ * TODO(kit 收敛): npmLatestVersionRobust / resolveNpmCommand 与 chromepilot
17
+ * kit updater 的健壮化实现同构但分置两处(kit 本次不发版)。下轮 kit 发版后,
18
+ * 在 createCliUpdater 注入 sanitizeEnv 并删除本地重复实现。
12
19
  *
13
20
  * 逃生开关(kit autoCliUpdate 内统一处理):
14
21
  * - HUNTER_MATE_NO_AUTOUPDATE=1 关闭全部自动更新(CLI + 一致性,preAction 层)
15
22
  * - HUNTER_MATE_UPDATE_CHECK_INTERVAL_MS 覆盖节流间隔(默认 3600000)
23
+ * - HUNTER_MATE_NPM_BIN 指定 npm 可执行文件路径(沙盒内局部 npm)
16
24
  * - HUNTER_MATE_DEV=1 强制视为 dev 模式(测试用)
17
25
  */
18
26
  import * as fs from 'node:fs';
27
+ import * as os from 'node:os';
19
28
  import * as path from 'path';
20
29
  import { spawnSync } from 'node:child_process';
21
30
  import { fileURLToPath } from 'node:url';
31
+ import axios from 'axios';
22
32
  import { createCliUpdater } from '@chromepilot/kit';
23
33
  import { readInstalledVersions } from './version.js';
24
34
  import { resolveDataDir } from './config.js';
25
35
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
36
  const ROOT = resolveDataDir;
27
37
  const CLI_PREFIX = () => path.join(ROOT(), 'cli');
38
+ /**
39
+ * 七牛 CDN 发布镜像(hcweb-temp-file bucket,hunter-mate-releases/ 前缀)。
40
+ * 工厂 pnpm-release 发布后上传 tgz + latest.json,并只保留最近 5 个版本;
41
+ * npm registry 不可达时(国内网络/Windows 现场)CLI 回退到这里查版本与下载。
42
+ * 覆盖方式:HUNTER_MATE_CDN_BASE(测试/内网镜像用)。
43
+ */
44
+ export const CDN_RELEASES_BASE = process.env.HUNTER_MATE_CDN_BASE || 'https://tempfile.hiredchina.com/hunter-mate-releases';
45
+ /**
46
+ * 从 CDN 读 latest.json 发布清单(10s 超时,proxy:false 绕开死代理)。
47
+ * 工厂上传脚本负责维护该文件;不存在/解析失败返回 null。
48
+ */
49
+ export async function fetchCdnLatestManifest() {
50
+ try {
51
+ const res = await axios.get(`${CDN_RELEASES_BASE}/latest.json`, { timeout: 10_000, proxy: false });
52
+ const m = res.data;
53
+ if (m && typeof m.version === 'string' && m.version && typeof m.tgz === 'string' && m.tgz)
54
+ return m;
55
+ return null;
56
+ }
57
+ catch {
58
+ return null;
59
+ }
60
+ }
28
61
  /**
29
62
  * 判定是否开发软链模式(纯函数,便于单测):
30
63
  * 包根路径含 /worktrees/ 段(hc-hw local worktree 布局)或 HUNTER_MATE_DEV=1。
@@ -46,53 +79,359 @@ const kitUpdater = createCliUpdater({
46
79
  isDev: isDevLinked,
47
80
  });
48
81
  // ---------------------------------------------------------------------------
49
- // 复用 kit 的工具函数(版本查询、节流标记等)
82
+ // 复用 kit 的工具函数(节流标记等);版本查询改走本地健壮实现(见文件头 TODO)
50
83
  // ---------------------------------------------------------------------------
51
- export const npmLatestVersion = kitUpdater.npmLatestVersion;
52
84
  export const updateCheckIntervalMs = kitUpdater.updateCheckIntervalMs;
53
85
  export const shouldCheckUpdate = kitUpdater.shouldCheckUpdate;
54
86
  export const markUpdateChecked = kitUpdater.markUpdateChecked;
55
- export async function npmSelfUpdate(pkg = 'hunter-mate') {
56
- const prefix = CLI_PREFIX();
57
- fs.mkdirSync(prefix, { recursive: true });
58
- // 优先使用用户通过环境变量指定的 npm 路径(沙盒内可能只有局部 npm)
59
- const npmBin = process.env.HUNTER_MATE_NPM_BIN || 'npm';
60
- const args = ['install', `${pkg}@latest`, '--prefix', prefix];
61
- // 死代理处理:npm 会继承 HTTP(S)_PROXY;指向死端口时 registry 查询必失败
62
- // (现场真凶:update 查 registry 失败 + prebuild 下载失败回退 7 分钟源码编译)。
63
- // 检测后从子进程环境剔除死代理变量,让 npm 直连,并在结果里告知用户。
64
- const childEnv = { ...process.env };
87
+ // ---------------------------------------------------------------------------
88
+ // npm 子进程健壮化(断点 1:Windows npm.cmd / 死代理 / 超时)
89
+ // ---------------------------------------------------------------------------
90
+ /**
91
+ * 解析 npm 可执行命令(纯函数,便于单测)。
92
+ * - HUNTER_MATE_NPM_BIN 显式指定时优先(沙盒内局部 npm)
93
+ * - win32 下 spawn 不解析 PATHEXT,直接 spawn 'npm' 会 ENOENT;必须走 npm.cmd + shell
94
+ * - 其余平台原生 npm,无需 shell
95
+ */
96
+ export function resolveNpmCommand(platform = process.platform, env = process.env) {
97
+ if (env.HUNTER_MATE_NPM_BIN)
98
+ return { cmd: env.HUNTER_MATE_NPM_BIN, shell: false };
99
+ if (platform === 'win32')
100
+ return { cmd: 'npm.cmd', shell: true };
101
+ return { cmd: 'npm', shell: false };
102
+ }
103
+ /**
104
+ * spawn npm,带平台分派与逐级回退:
105
+ * win32 shell:true 不可用(受限沙盒)时回退无 shell 直 spawn(对 .exe 包装器有效),
106
+ * 再失败才向上抛。args 全为静态字面量,无注入面。
107
+ */
108
+ function spawnNpmSync(args, opts) {
109
+ const { cmd, shell } = resolveNpmCommand();
110
+ const base = { ...opts, shell };
111
+ let r = spawnSync(cmd, args, base);
112
+ if (shell && r.error) {
113
+ // 沙盒内 shell 不可用:回退无 shell 直 spawn(仅当 npm 是 .exe 时有效)
114
+ r = spawnSync(cmd, args, { ...opts, shell: false });
115
+ if (r.error && cmd.endsWith('.cmd')) {
116
+ r = spawnSync(cmd.replace(/\.cmd$/i, ''), args, { ...opts, shell: false });
117
+ }
118
+ }
119
+ return r;
120
+ }
121
+ /**
122
+ * 构造剔除死代理的子进程 env(复用 utils/proxy-detect)。
123
+ * 返回 env 与给用户的提示语;检测失败不阻断(沿用 npmSelfUpdate 原有语义)。
124
+ */
125
+ export async function buildSanitizedNpmEnv() {
126
+ const env = { ...process.env };
65
127
  let proxyNote = '';
66
128
  try {
67
129
  const { findDeadProxies } = await import('./utils/proxy-detect.js');
68
130
  const dead = await findDeadProxies(800);
69
131
  if (dead.length) {
70
132
  for (const d of dead)
71
- delete childEnv[d.varName];
133
+ delete env[d.varName];
72
134
  proxyNote =
73
135
  `⚠ 检测到死代理(${dead.map((d) => `${d.varName}=${d.value}`).join('; ')}),` +
74
- `已在本轮 npm install 中临时剔除使其直连;清除方法见 hunter-mate doctor 的 proxy_env 检查项\n`;
136
+ `已在本轮 npm 操作中临时剔除使其直连;清除方法见 hunter-mate doctor 的 proxy_env 检查项\n`;
137
+ }
138
+ }
139
+ catch { /* 检测失败不阻断 */ }
140
+ return { env, proxyNote };
141
+ }
142
+ /**
143
+ * 健壮版最新版本查询:npm registry 优先,失败回退七牛 CDN latest.json。
144
+ * (kit 版 15s 裸 spawn 在 Windows/代理环境必失败,见文件头 TODO)
145
+ */
146
+ export async function npmLatestVersionRobust(pkg = 'hunter-mate') {
147
+ const { env } = await buildSanitizedNpmEnv();
148
+ let r;
149
+ try {
150
+ r = spawnNpmSync(['view', pkg, 'version'], { env, timeout: 30_000, encoding: 'utf-8' });
151
+ }
152
+ catch (err) {
153
+ return cdnFallbackVersionResult(pkg, `spawn 异常: ${err instanceof Error ? err.message : String(err)}`);
154
+ }
155
+ if (r.error) {
156
+ const e = r.error;
157
+ const kind = e.code === 'ENOENT' ? 'ENOENT(npm 不在 PATH,Windows 需 npm.cmd)' : e.code || 'unknown';
158
+ return cdnFallbackVersionResult(pkg, `${kind}: ${e.message}`);
159
+ }
160
+ if (r.status !== 0) {
161
+ return cdnFallbackVersionResult(pkg, `exit=${r.status}${r.stderr ? ` ${String(r.stderr).slice(0, 200)}` : ''}`);
162
+ }
163
+ const v = String(r.stdout || '').trim();
164
+ if (!v)
165
+ return cdnFallbackVersionResult(pkg, 'empty output');
166
+ return { version: v, detail: 'ok(npm registry)' };
167
+ }
168
+ /** npm 查询失败时回退 CDN 清单;CDN 也不可达则合并两处原因返回 null */
169
+ async function cdnFallbackVersionResult(pkg, npmDetail) {
170
+ const m = await fetchCdnLatestManifest();
171
+ if (m)
172
+ return { version: m.version, detail: `ok(七牛 CDN,npm 失败原因: ${npmDetail})` };
173
+ return { version: null, detail: `npm: ${npmDetail}; CDN: latest.json 不可达(${CDN_RELEASES_BASE})` };
174
+ }
175
+ // ---------------------------------------------------------------------------
176
+ // 安装形态检测(断点 2:自更新必须装回当前运行 CLI 的真实安装根)
177
+ // ---------------------------------------------------------------------------
178
+ /** 路径归一:normalize + realpath(Windows junction 必须双侧 realpath)+ win32 大小写不敏感 */
179
+ function normPath(p) {
180
+ let n;
181
+ try {
182
+ n = fs.realpathSync(p);
183
+ }
184
+ catch {
185
+ n = p;
186
+ }
187
+ n = path.normalize(n);
188
+ return process.platform === 'win32' ? n.toLowerCase() : n;
189
+ }
190
+ function isWithin(child, parent) {
191
+ return child === parent || child.startsWith(parent + path.sep);
192
+ }
193
+ /** 当前运行 CLI 的包根(dist 的上一级),realpath 归一 */
194
+ export function getCliPkgRoot() {
195
+ try {
196
+ return fs.realpathSync(path.join(__dirname, '..'));
197
+ }
198
+ catch {
199
+ return path.join(__dirname, '..');
200
+ }
201
+ }
202
+ /** 向上找最近的 node_modules 祖先目录(含自身),找不到返回 null */
203
+ function findNodeModulesAncestor(start) {
204
+ let dir = start;
205
+ for (;;) {
206
+ if (path.basename(dir) === 'node_modules')
207
+ return dir;
208
+ const parent = path.dirname(dir);
209
+ if (parent === dir)
210
+ return null;
211
+ dir = parent;
212
+ }
213
+ }
214
+ /**
215
+ * 判定当前运行 CLI 的安装形态(纯函数;global 判定依赖调用方预解析的 globalPrefix,
216
+ * globalPrefix===undefined 表示尚未解析,有 node_modules 祖先时返回 fallback 提示调用方先解析)。
217
+ *
218
+ * 优先级:
219
+ * 1. pkgRoot 在 cliPrefix(~/.hunter-mate/cli)下 → prefix(设计形态,现有行为)
220
+ * 2. pkgRoot 在 ROOT 内但不在 cliPrefix(Release 二进制/daemon 布局)→ skip,npm 自更新不适用
221
+ * 3. 有 node_modules 祖先:
222
+ * - globalPrefix 已知且 pkgRoot 在其下 → global(npm install -g,不带 --prefix)
223
+ * - globalPrefix 已知但不在其下 → custom-prefix(prefix = node_modules 父目录,装回原处)
224
+ * - globalPrefix 未知 → fallback(调用方通常应先 spawn `npm prefix -g` 解析)
225
+ * 4. 无 node_modules 祖先且不在 ROOT 下 → fallback(未识别形态,沿用原 --prefix 行为)
226
+ */
227
+ export function detectCliInstallLayout(pkgRootIn, cliPrefixIn, globalPrefixIn) {
228
+ const pkgRoot = normPath(pkgRootIn);
229
+ const cliPrefix = normPath(cliPrefixIn);
230
+ const root = normPath(ROOT());
231
+ if (isWithin(pkgRoot, cliPrefix)) {
232
+ return { mode: 'prefix', prefix: cliPrefixIn, reason: '用户级 --prefix 安装(设计形态)' };
233
+ }
234
+ if (isWithin(pkgRoot, root)) {
235
+ return { mode: 'skip', reason: 'Release 二进制/daemon 布局(位于数据目录内),npm 自更新不适用该安装形态' };
236
+ }
237
+ const nm = findNodeModulesAncestor(pkgRoot);
238
+ if (nm) {
239
+ if (globalPrefixIn !== undefined && globalPrefixIn !== null) {
240
+ const gp = normPath(globalPrefixIn);
241
+ if (isWithin(pkgRoot, gp)) {
242
+ return { mode: 'global', reason: `位于 npm 全局前缀 ${globalPrefixIn}` };
243
+ }
244
+ const prefix = path.dirname(nm);
245
+ return { mode: 'custom-prefix', prefix, reason: `位于自定义 npm prefix ${prefix}` };
246
+ }
247
+ return {
248
+ mode: 'fallback',
249
+ prefix: cliPrefixIn,
250
+ reason: '检测到 node_modules 祖先但 global 前缀未解析(npm prefix -g 失败),沿用原 --prefix 行为',
251
+ };
252
+ }
253
+ return {
254
+ mode: 'fallback',
255
+ prefix: cliPrefixIn,
256
+ reason: '未识别的安装形态,沿用原 --prefix 行为',
257
+ };
258
+ }
259
+ /** spawn `npm prefix -g` 解析全局前缀(10s 封顶,失败返回 null,不阻断) */
260
+ export async function resolveGlobalNpmPrefix() {
261
+ const { env } = await buildSanitizedNpmEnv();
262
+ try {
263
+ const r = spawnNpmSync(['prefix', '-g'], { env, timeout: 10_000, encoding: 'utf-8' });
264
+ if (r.status === 0 && r.stdout) {
265
+ const p = String(r.stdout).trim().split('\n').pop()?.trim();
266
+ return p || null;
267
+ }
268
+ }
269
+ catch { /* 失败不阻断 */ }
270
+ return null;
271
+ }
272
+ /** 孤儿 ~/.hunter-mate/cli 安装检测(doctor orphan_cli_install 检查项;纯函数) */
273
+ export function detectOrphanPrefixInstall(pkgRootIn, cliPrefixIn) {
274
+ const runRoot = pkgRootIn ?? getCliPkgRoot();
275
+ const cliPrefix = cliPrefixIn ?? CLI_PREFIX();
276
+ const orphanPkgDir = path.join(cliPrefix, 'node_modules', 'hunter-mate');
277
+ const orphan = !isWithin(normPath(runRoot), normPath(cliPrefix)) &&
278
+ fs.existsSync(path.join(orphanPkgDir, 'package.json'));
279
+ return { orphan, orphanPkgDir, runPkgRoot: runRoot };
280
+ }
281
+ /** 读目标 node_modules 下已装版本(验证闭环用) */
282
+ function readInstalledVersionAt(nodeModulesDir, pkg) {
283
+ try {
284
+ const p = path.join(nodeModulesDir, pkg, 'package.json');
285
+ const j = JSON.parse(fs.readFileSync(p, 'utf-8'));
286
+ return j.version ?? null;
287
+ }
288
+ catch {
289
+ return null;
290
+ }
291
+ }
292
+ /** 验证闭环(断点 3):读回目标位置版本,确认更新真的落到了运行入口 */
293
+ function verifyInstallOutcome(pkg, layout, globalPrefix, prefix, expectedVersion) {
294
+ // 全局 node_modules 位置跨平台不同:unix 为 <prefix>/lib/node_modules,Windows 为 <prefix>/node_modules,逐个探测
295
+ const candidates = layout.mode === 'global'
296
+ ? globalPrefix
297
+ ? [path.join(globalPrefix, 'lib', 'node_modules'), path.join(globalPrefix, 'node_modules')]
298
+ : []
299
+ : [path.join(prefix, 'node_modules')];
300
+ const read = candidates.map((d) => readInstalledVersionAt(d, pkg)).find((v) => v !== null) ?? null;
301
+ return {
302
+ verified: read === expectedVersion,
303
+ verifyNote: read === expectedVersion ? `,已验证 ${read}` : `,验证失败: 目标位置读到 ${read ?? '?'}`,
304
+ };
305
+ }
306
+ /** 下载 CDN tgz 到临时目录,返回本地文件路径(失败返回 null;120s 超时,proxy:false 绕开死代理) */
307
+ async function downloadCdnTgz(manifest) {
308
+ const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'hm-cdn-'));
309
+ const file = path.join(tmpDir, manifest.tgz);
310
+ try {
311
+ const res = await axios.get(`${CDN_RELEASES_BASE}/${manifest.tgz}`, {
312
+ timeout: 120_000,
313
+ proxy: false,
314
+ responseType: 'stream',
315
+ });
316
+ await new Promise((resolve, reject) => {
317
+ const w = fs.createWriteStream(file);
318
+ res.data.pipe(w);
319
+ w.on('finish', () => resolve());
320
+ w.on('error', reject);
321
+ res.data.on('error', reject);
322
+ });
323
+ return file;
324
+ }
325
+ catch {
326
+ try {
327
+ fs.rmSync(tmpDir, { recursive: true, force: true });
75
328
  }
329
+ catch { /* 尽力清理 */ }
330
+ return null;
76
331
  }
77
- catch { /* 检测失败不阻断更新 */ }
78
- const r = spawnSync(npmBin, args, {
332
+ }
333
+ /**
334
+ * npm install 失败时的七牛 CDN 回退:下载工厂镜像 tgz,本地文件方式装回同一布局。
335
+ * 布局分派/死代理剔除/验证闭环与 npm 路径完全一致;CDN 也不可达则返回 null 由调用方报原失败。
336
+ */
337
+ async function cdnFallbackInstall(pkg, expectedVersion, layout, globalPrefix, prefix, env, proxyNote) {
338
+ if (layout.mode === 'skip')
339
+ return null;
340
+ const manifest = await fetchCdnLatestManifest();
341
+ // 清单版本必须与期望版本一致,避免装了比 npm 查询结果更旧/更新的版本
342
+ if (!manifest || manifest.version !== expectedVersion)
343
+ return null;
344
+ const tgz = await downloadCdnTgz(manifest);
345
+ if (!tgz)
346
+ return null;
347
+ const args = layout.mode === 'global'
348
+ ? ['install', '-g', tgz]
349
+ : ['install', tgz, '--prefix', prefix];
350
+ const r = spawnNpmSync(args, {
79
351
  stdio: 'inherit',
80
352
  timeout: 120_000,
81
- env: {
82
- ...childEnv,
83
- // 防止 npm 因为用户 home 不可写而失败;--prefix 已指定安装位置
84
- npm_config_cache: path.join(prefix, '.npm-cache'),
85
- },
353
+ env: prefix
354
+ ? { ...env, npm_config_cache: path.join(prefix, '.npm-cache') }
355
+ : env,
86
356
  });
87
- if (r.status === 0) {
357
+ try {
358
+ fs.rmSync(path.dirname(tgz), { recursive: true, force: true });
359
+ }
360
+ catch { /* 尽力清理 */ }
361
+ if (r.status !== 0) {
88
362
  return {
89
- ok: true,
90
- msg: proxyNote + `${npmBin} install ${pkg}@latest --prefix ${prefix} 完成,下一条命令起使用新版本`,
363
+ ok: false,
364
+ msg: proxyNote + `七牛 CDN 回退安装也失败 (exit=${r.status}),可手动从 ${CDN_RELEASES_BASE}/${manifest.tgz} 下载安装`,
91
365
  };
92
366
  }
367
+ const { verified, verifyNote } = verifyInstallOutcome(pkg, layout, globalPrefix, prefix, expectedVersion);
368
+ return {
369
+ ok: true,
370
+ verified,
371
+ msg: proxyNote +
372
+ `七牛 CDN 回退安装完成(${layout.mode}${prefix ? ` ${prefix}` : ''})${verifyNote}`,
373
+ };
374
+ }
375
+ export async function npmSelfUpdate(pkg = 'hunter-mate', expectedVersion,
376
+ /** 测试注入:覆盖"当前运行 CLI 包根"的判定 */
377
+ opts) {
378
+ const cliPkgRoot = opts?.pkgRoot ?? getCliPkgRoot();
379
+ // global 前缀只解析一次:布局检测与装后验证共用(每次 spawn 都有 10s 超时成本)
380
+ const pkgRoot = normPath(cliPkgRoot);
381
+ const inManagedDirs = isWithin(pkgRoot, normPath(CLI_PREFIX())) || isWithin(pkgRoot, normPath(ROOT()));
382
+ const globalPrefix = inManagedDirs ? null : await resolveGlobalNpmPrefix();
383
+ const layout = detectCliInstallLayout(cliPkgRoot, CLI_PREFIX(), globalPrefix);
384
+ if (layout.mode === 'skip') {
385
+ return { ok: false, msg: layout.reason };
386
+ }
387
+ const { env, proxyNote } = await buildSanitizedNpmEnv();
388
+ let args;
389
+ let prefix = null;
390
+ if (layout.mode === 'global') {
391
+ // 全局安装:必须 -g 装回全局前缀,装到 --prefix 会落入孤儿目录(断点 2 真凶)
392
+ args = ['install', '-g', `${pkg}@latest`];
393
+ }
394
+ else {
395
+ prefix = layout.prefix;
396
+ fs.mkdirSync(prefix, { recursive: true });
397
+ args = ['install', `${pkg}@latest`, '--prefix', prefix];
398
+ }
399
+ const r = spawnNpmSync(args, {
400
+ stdio: 'inherit',
401
+ timeout: 120_000,
402
+ env: prefix
403
+ ? {
404
+ ...env,
405
+ // 防止 npm 因为用户 home 不可写而失败;--prefix 已指定安装位置
406
+ npm_config_cache: path.join(prefix, '.npm-cache'),
407
+ }
408
+ : env,
409
+ });
410
+ if (r.status !== 0) {
411
+ // npm 装不上(registry 不可达/超时/沙盒拦截)→ 回退七牛 CDN 镜像 tgz
412
+ if (expectedVersion) {
413
+ const cdn = await cdnFallbackInstall(pkg, expectedVersion, layout, globalPrefix, prefix, env, proxyNote);
414
+ if (cdn)
415
+ return cdn;
416
+ }
417
+ return {
418
+ ok: false,
419
+ msg: proxyNote + `npm install ${pkg}@latest(${layout.mode})失败 (exit=${r.status}),可手动执行`,
420
+ };
421
+ }
422
+ // 验证闭环(断点 3):读回目标位置版本,确认更新真的落到了运行入口
423
+ let verified;
424
+ let verifyNote = '';
425
+ if (expectedVersion) {
426
+ const v = verifyInstallOutcome(pkg, layout, globalPrefix, prefix, expectedVersion);
427
+ verified = v.verified;
428
+ verifyNote = v.verifyNote;
429
+ }
93
430
  return {
94
- ok: false,
95
- msg: proxyNote + `${npmBin} install ${pkg}@latest --prefix ${prefix} 失败 (exit=${r.status}),可手动执行`,
431
+ ok: true,
432
+ verified,
433
+ msg: proxyNote +
434
+ `npm install ${pkg}@latest(${layout.mode}${prefix ? ` ${prefix}` : ''})完成,下一条命令起使用新版本${verifyNote}`,
96
435
  };
97
436
  }
98
437
  /**
@@ -116,17 +455,18 @@ export async function maybeCliSelfUpdate() {
116
455
  const installed = readInstalledVersions().cli;
117
456
  if (!installed || installed === '0.0.0')
118
457
  return outcome;
119
- const latest = npmLatestVersion();
458
+ const { version: latest, detail } = await npmLatestVersionRobust();
120
459
  if (!latest) {
121
- logs.push(`npm registry 查询 hunter-mate 失败,本次跳过自更新`);
460
+ logs.push(`npm registry 查询 hunter-mate 失败(${detail}),本次跳过自更新`);
122
461
  return outcome;
123
462
  }
124
463
  if (latest === installed)
125
464
  return outcome;
126
- logs.push(`CLI 自更新: ${installed} → ${latest}`);
127
- const r = await npmSelfUpdate();
465
+ logs.push(`CLI 自更新: ${installed} → ${latest}(${detectCliInstallLayout(getCliPkgRoot(), CLI_PREFIX()).reason})`);
466
+ const r = await npmSelfUpdate('hunter-mate', latest);
128
467
  logs.push(r.msg);
129
- outcome.updated = r.ok;
468
+ // 验证闭环:只有装后读回版本匹配才算更新成功,否则下条命令会再来一次(可感知,不再静默空转)
469
+ outcome.updated = r.ok && r.verified !== false;
130
470
  }
131
471
  catch (err) {
132
472
  logs.push(`自动更新检测异常(不影响命令执行): ${err instanceof Error ? err.message : String(err)}`);
@@ -1 +1 @@
1
- <!DOCTYPE html><html lang="zh-CN"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/b7097b50a6497650.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-1784baecc6fdcf0b.js"/><script src="/_next/static/chunks/a7067448-96dd4b8457c447e8.js" async=""></script><script src="/_next/static/chunks/935-99f57b7d4151d9e7.js" async=""></script><script src="/_next/static/chunks/main-app-0af848156a5b13fc.js" async=""></script><script src="/_next/static/chunks/589-d632233641f35681.js" async=""></script><script src="/_next/static/chunks/app/layout-83e623e973c1693f.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>HunterMate 采集仪表盘</title><meta name="description" content="HunterMate 采集/解析/入库仪表盘"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="flex h-screen overflow-hidden bg-gray-50 text-gray-900"><aside class="flex h-screen w-56 flex-col border-r border-gray-200 bg-white"><div class="flex items-center gap-2 border-b border-gray-200 px-4 py-3"><span class="text-xl">🤝</span><span class="font-semibold text-gray-900">HunterMate</span></div><nav class="flex-1 overflow-auto py-3"><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/"><span class="text-base">📊</span>仪表盘</a><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/talents"><span class="text-base">👤</span>人才</a><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/jobs"><span class="text-base">💼</span>职位</a><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/companies"><span class="text-base">🏢</span>公司</a><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/parselog"><span class="text-base">📋</span>解析日志</a><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/recordings"><span class="text-base">🔴</span>录制</a></nav></aside><main class="flex-1 overflow-hidden"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div></main><script src="/_next/static/chunks/webpack-1784baecc6fdcf0b.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/b7097b50a6497650.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[6939,[],\"\"]\n4:I[4268,[],\"\"]\n5:I[1050,[],\"\"]\n6:I[4798,[\"589\",\"static/chunks/589-d632233641f35681.js\",\"185\",\"static/chunks/app/layout-83e623e973c1693f.js\"],\"SystemSidebar\"]\nc:I[2319,[],\"\"]\n7:{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"}\n8:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n9:{\"display\":\"inline-block\"}\na:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nd:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"6dSStiRxUpJkNfwFqjG-R\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"_not-found\"],\"initialTree\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null],null],null]},[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"/_not-found\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/b7097b50a6497650.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"zh-CN\",\"children\":[\"$\",\"body\",null,{\"className\":\"flex h-screen overflow-hidden bg-gray-50 text-gray-900\",\"children\":[[\"$\",\"$L6\",null,{}],[\"$\",\"main\",null,{\"className\":\"flex-1 overflow-hidden\",\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$7\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$8\",\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":\"$9\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$a\",\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],\"$Lb\"],\"globalErrorComponent\":\"$c\",\"missingSlots\":\"$Wd\"}]\n"])</script><script>self.__next_f.push([1,"b:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"HunterMate 采集仪表盘\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"HunterMate 采集/解析/入库仪表盘\"}]]\n3:null\n"])</script></body></html>
1
+ <!DOCTYPE html><html lang="zh-CN"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/_next/static/css/b7097b50a6497650.css" data-precedence="next"/><link rel="preload" as="script" fetchPriority="low" href="/_next/static/chunks/webpack-1784baecc6fdcf0b.js"/><script src="/_next/static/chunks/a7067448-96dd4b8457c447e8.js" async=""></script><script src="/_next/static/chunks/935-99f57b7d4151d9e7.js" async=""></script><script src="/_next/static/chunks/main-app-cb4554c86eba200c.js" async=""></script><script src="/_next/static/chunks/589-d632233641f35681.js" async=""></script><script src="/_next/static/chunks/app/layout-83e623e973c1693f.js" async=""></script><meta name="robots" content="noindex"/><title>404: This page could not be found.</title><title>HunterMate 采集仪表盘</title><meta name="description" content="HunterMate 采集/解析/入库仪表盘"/><script src="/_next/static/chunks/polyfills-42372ed130431b0a.js" noModule=""></script></head><body class="flex h-screen overflow-hidden bg-gray-50 text-gray-900"><aside class="flex h-screen w-56 flex-col border-r border-gray-200 bg-white"><div class="flex items-center gap-2 border-b border-gray-200 px-4 py-3"><span class="text-xl">🤝</span><span class="font-semibold text-gray-900">HunterMate</span></div><nav class="flex-1 overflow-auto py-3"><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/"><span class="text-base">📊</span>仪表盘</a><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/talents"><span class="text-base">👤</span>人才</a><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/jobs"><span class="text-base">💼</span>职位</a><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/companies"><span class="text-base">🏢</span>公司</a><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/parselog"><span class="text-base">📋</span>解析日志</a><a class="mx-2 flex items-center gap-3 rounded-lg px-3 py-2 text-sm transition-colors text-gray-600 hover:bg-gray-50 hover:text-gray-900" href="/recordings"><span class="text-base">🔴</span>录制</a></nav></aside><main class="flex-1 overflow-hidden"><div style="font-family:system-ui,&quot;Segoe UI&quot;,Roboto,Helvetica,Arial,sans-serif,&quot;Apple Color Emoji&quot;,&quot;Segoe UI Emoji&quot;;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center"><div><style>body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class="next-error-h1" style="display:inline-block;margin:0 20px 0 0;padding:0 23px 0 0;font-size:24px;font-weight:500;vertical-align:top;line-height:49px">404</h1><div style="display:inline-block"><h2 style="font-size:14px;font-weight:400;line-height:49px;margin:0">This page could not be found.</h2></div></div></div></main><script src="/_next/static/chunks/webpack-1784baecc6fdcf0b.js" async=""></script><script>(self.__next_f=self.__next_f||[]).push([0]);self.__next_f.push([2,null])</script><script>self.__next_f.push([1,"1:HL[\"/_next/static/css/b7097b50a6497650.css\",\"style\"]\n"])</script><script>self.__next_f.push([1,"2:I[6939,[],\"\"]\n4:I[4268,[],\"\"]\n5:I[1050,[],\"\"]\n6:I[4798,[\"589\",\"static/chunks/589-d632233641f35681.js\",\"185\",\"static/chunks/app/layout-83e623e973c1693f.js\"],\"SystemSidebar\"]\nc:I[2319,[],\"\"]\n7:{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"}\n8:{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"}\n9:{\"display\":\"inline-block\"}\na:{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0}\nd:[]\n"])</script><script>self.__next_f.push([1,"0:[\"$\",\"$L2\",null,{\"buildId\":\"ZJtEh3uyN_mQhaXvrDYzJ\",\"assetPrefix\":\"\",\"urlParts\":[\"\",\"_not-found\"],\"initialTree\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{}]}]},\"$undefined\",\"$undefined\",true],\"initialSeedData\":[\"\",{\"children\":[\"/_not-found\",{\"children\":[\"__PAGE__\",{},[[\"$L3\",[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":{\"fontFamily\":\"system-ui,\\\"Segoe UI\\\",Roboto,Helvetica,Arial,sans-serif,\\\"Apple Color Emoji\\\",\\\"Segoe UI Emoji\\\"\",\"height\":\"100vh\",\"textAlign\":\"center\",\"display\":\"flex\",\"flexDirection\":\"column\",\"alignItems\":\"center\",\"justifyContent\":\"center\"},\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":{\"display\":\"inline-block\",\"margin\":\"0 20px 0 0\",\"padding\":\"0 23px 0 0\",\"fontSize\":24,\"fontWeight\":500,\"verticalAlign\":\"top\",\"lineHeight\":\"49px\"},\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":{\"display\":\"inline-block\"},\"children\":[\"$\",\"h2\",null,{\"style\":{\"fontSize\":14,\"fontWeight\":400,\"lineHeight\":\"49px\",\"margin\":0},\"children\":\"This page could not be found.\"}]}]]}]}]],null],null],null]},[null,[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\",\"/_not-found\",\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":\"$undefined\",\"notFoundStyles\":\"$undefined\"}]],null]},[[[[\"$\",\"link\",\"0\",{\"rel\":\"stylesheet\",\"href\":\"/_next/static/css/b7097b50a6497650.css\",\"precedence\":\"next\",\"crossOrigin\":\"$undefined\"}]],[\"$\",\"html\",null,{\"lang\":\"zh-CN\",\"children\":[\"$\",\"body\",null,{\"className\":\"flex h-screen overflow-hidden bg-gray-50 text-gray-900\",\"children\":[[\"$\",\"$L6\",null,{}],[\"$\",\"main\",null,{\"className\":\"flex-1 overflow-hidden\",\"children\":[\"$\",\"$L4\",null,{\"parallelRouterKey\":\"children\",\"segmentPath\":[\"children\"],\"error\":\"$undefined\",\"errorStyles\":\"$undefined\",\"errorScripts\":\"$undefined\",\"template\":[\"$\",\"$L5\",null,{}],\"templateStyles\":\"$undefined\",\"templateScripts\":\"$undefined\",\"notFound\":[[\"$\",\"title\",null,{\"children\":\"404: This page could not be found.\"}],[\"$\",\"div\",null,{\"style\":\"$7\",\"children\":[\"$\",\"div\",null,{\"children\":[[\"$\",\"style\",null,{\"dangerouslySetInnerHTML\":{\"__html\":\"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}\"}}],[\"$\",\"h1\",null,{\"className\":\"next-error-h1\",\"style\":\"$8\",\"children\":\"404\"}],[\"$\",\"div\",null,{\"style\":\"$9\",\"children\":[\"$\",\"h2\",null,{\"style\":\"$a\",\"children\":\"This page could not be found.\"}]}]]}]}]],\"notFoundStyles\":[]}]}]]}]}]],null],null],\"couldBeIntercepted\":false,\"initialHead\":[[\"$\",\"meta\",null,{\"name\":\"robots\",\"content\":\"noindex\"}],\"$Lb\"],\"globalErrorComponent\":\"$c\",\"missingSlots\":\"$Wd\"}]\n"])</script><script>self.__next_f.push([1,"b:[[\"$\",\"meta\",\"0\",{\"name\":\"viewport\",\"content\":\"width=device-width, initial-scale=1\"}],[\"$\",\"meta\",\"1\",{\"charSet\":\"utf-8\"}],[\"$\",\"title\",\"2\",{\"children\":\"HunterMate 采集仪表盘\"}],[\"$\",\"meta\",\"3\",{\"name\":\"description\",\"content\":\"HunterMate 采集/解析/入库仪表盘\"}]]\n3:null\n"])</script></body></html>
@@ -1 +1 @@
1
- !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="b5a0b51e-c12c-4e5b-a564-a33a8fe86d58",e._sentryDebugIdIdentifier="sentry-dbid-b5a0b51e-c12c-4e5b-a564-a33a8fe86d58")}catch(e){}}(),(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[744],{5746:function(){},7539:function(e,n,t){Promise.resolve().then(t.t.bind(t,6939,23)),Promise.resolve().then(t.t.bind(t,7360,23)),Promise.resolve().then(t.t.bind(t,2319,23)),Promise.resolve().then(t.t.bind(t,4268,23)),Promise.resolve().then(t.t.bind(t,742,23)),Promise.resolve().then(t.t.bind(t,1050,23))},9531:function(e,n,t){"use strict";var r=t(258),i=t(529);globalThis._sentryRewritesTunnelPath=void 0,globalThis.SENTRY_RELEASE={id:"a678db1e3d9efb345ab829dd6fb95a5b5cc724e6"},globalThis._sentryBasePath=void 0,globalThis._sentryRewriteFramesAssetPrefixPath="";let s=i.env.NEXT_PUBLIC_HM_ERROR_TRACKING_DSN;"true"===i.env.NEXT_PUBLIC_HM_ERROR_TRACKING_ENABLED&&s&&r.S1({dsn:s,release:i.env.NEXT_PUBLIC_HM_RELEASE,environment:i.env.NEXT_PUBLIC_HM_ENVIRONMENT||"development",tracesSampleRate:0,sendClientReports:!1,beforeSend(e){var n;if(null===(n=e.request)||void 0===n?void 0:n.headers){let n=e.request.headers;n.authorization&&(n.authorization="***"),n.cookie&&(n.cookie="***")}return e}})}},function(e){var n=function(n){return e(e.s=n)};e.O(0,[400,935],function(){return n(9531),n(8063),n(7539)}),_N_E=e.O()}]);
1
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="1289ef8e-5404-4b12-9fbc-b5bbaee8cb1e",e._sentryDebugIdIdentifier="sentry-dbid-1289ef8e-5404-4b12-9fbc-b5bbaee8cb1e")}catch(e){}}(),(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[744],{5746:function(){},7539:function(e,n,t){Promise.resolve().then(t.t.bind(t,6939,23)),Promise.resolve().then(t.t.bind(t,7360,23)),Promise.resolve().then(t.t.bind(t,2319,23)),Promise.resolve().then(t.t.bind(t,4268,23)),Promise.resolve().then(t.t.bind(t,742,23)),Promise.resolve().then(t.t.bind(t,1050,23))},9531:function(e,n,t){"use strict";var r=t(258),i=t(529);globalThis._sentryRewritesTunnelPath=void 0,globalThis.SENTRY_RELEASE={id:"450a0e2db507eb196e3d9a512b0fd772c2a72a88"},globalThis._sentryBasePath=void 0,globalThis._sentryRewriteFramesAssetPrefixPath="";let s=i.env.NEXT_PUBLIC_HM_ERROR_TRACKING_DSN;"true"===i.env.NEXT_PUBLIC_HM_ERROR_TRACKING_ENABLED&&s&&r.S1({dsn:s,release:i.env.NEXT_PUBLIC_HM_RELEASE,environment:i.env.NEXT_PUBLIC_HM_ENVIRONMENT||"development",tracesSampleRate:0,sendClientReports:!1,beforeSend(e){var n;if(null===(n=e.request)||void 0===n?void 0:n.headers){let n=e.request.headers;n.authorization&&(n.authorization="***"),n.cookie&&(n.cookie="***")}return e}})}},function(e){var n=function(n){return e(e.s=n)};e.O(0,[400,935],function(){return n(9531),n(8063),n(7539)}),_N_E=e.O()}]);