hunter-mate 0.1.3 → 0.1.4

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 (101) hide show
  1. package/LICENSE +21 -0
  2. package/dist/assets/extension/background/service-worker.js +113 -24
  3. package/dist/assets/extension/content/index.js +239 -134
  4. package/dist/assets/extension/manifest.json +2 -4
  5. package/dist/assets/extension/sidepanel/index.js +76 -2
  6. package/dist/assets/server/app.module.js +4 -2
  7. package/dist/assets/server/app.module.js.map +1 -1
  8. package/dist/assets/server/build-info.json +1 -1
  9. package/dist/assets/server/db/sqlite.module.js +4 -0
  10. package/dist/assets/server/db/sqlite.module.js.map +1 -1
  11. package/dist/assets/server/devops/devops-sink-init.d.ts +14 -1
  12. package/dist/assets/server/devops/devops-sink-init.js +8 -3
  13. package/dist/assets/server/devops/devops-sink-init.js.map +1 -1
  14. package/dist/assets/server/doctor/doctor.controller.d.ts +13 -0
  15. package/dist/assets/server/doctor/doctor.controller.js +62 -0
  16. package/dist/assets/server/doctor/doctor.controller.js.map +1 -0
  17. package/dist/assets/server/doctor/doctor.module.d.ts +2 -0
  18. package/dist/assets/server/doctor/doctor.module.js +27 -0
  19. package/dist/assets/server/doctor/doctor.module.js.map +1 -0
  20. package/dist/assets/server/doctor/doctor.service.d.ts +15 -0
  21. package/dist/assets/server/doctor/doctor.service.js +209 -0
  22. package/dist/assets/server/doctor/doctor.service.js.map +1 -0
  23. package/dist/assets/server/doctor/doctor.types.d.ts +46 -0
  24. package/dist/assets/server/doctor/doctor.types.js +3 -0
  25. package/dist/assets/server/doctor/doctor.types.js.map +1 -0
  26. package/dist/assets/server/extraction/adapter.service.d.ts +18 -4
  27. package/dist/assets/server/extraction/adapter.service.js +129 -15
  28. package/dist/assets/server/extraction/adapter.service.js.map +1 -1
  29. package/dist/assets/server/extraction/anomaly-report.service.d.ts +12 -0
  30. package/dist/assets/server/extraction/anomaly-report.service.js +81 -0
  31. package/dist/assets/server/extraction/anomaly-report.service.js.map +1 -0
  32. package/dist/assets/server/extraction/entities/adapter-rule.entity.d.ts +13 -0
  33. package/dist/assets/server/extraction/entities/adapter-rule.entity.js +71 -0
  34. package/dist/assets/server/extraction/entities/adapter-rule.entity.js.map +1 -0
  35. package/dist/assets/server/extraction/entities/parse-log.entity.d.ts +4 -1
  36. package/dist/assets/server/extraction/entities/parse-log.entity.js +15 -0
  37. package/dist/assets/server/extraction/entities/parse-log.entity.js.map +1 -1
  38. package/dist/assets/server/extraction/extraction-entity.controller.d.ts +40 -2
  39. package/dist/assets/server/extraction/extraction-entity.controller.js +196 -13
  40. package/dist/assets/server/extraction/extraction-entity.controller.js.map +1 -1
  41. package/dist/assets/server/extraction/extraction-rule.service.js +16 -8
  42. package/dist/assets/server/extraction/extraction-rule.service.js.map +1 -1
  43. package/dist/assets/server/extraction/extraction.module.js +12 -2
  44. package/dist/assets/server/extraction/extraction.module.js.map +1 -1
  45. package/dist/assets/server/extraction/extraction.service.d.ts +17 -1
  46. package/dist/assets/server/extraction/extraction.service.js +125 -42
  47. package/dist/assets/server/extraction/extraction.service.js.map +1 -1
  48. package/dist/assets/server/extraction/linkedin-extractor.d.ts +1 -0
  49. package/dist/assets/server/extraction/linkedin-extractor.js +244 -0
  50. package/dist/assets/server/extraction/linkedin-extractor.js.map +1 -0
  51. package/dist/assets/server/extraction/rrweb-snapshot.utils.d.ts +15 -0
  52. package/dist/assets/server/extraction/rrweb-snapshot.utils.js +48 -0
  53. package/dist/assets/server/extraction/rrweb-snapshot.utils.js.map +1 -0
  54. package/dist/assets/server/extraction/rrweb-snapshot.utils.spec.d.ts +1 -0
  55. package/dist/assets/server/extraction/rrweb-snapshot.utils.spec.js +60 -0
  56. package/dist/assets/server/extraction/rrweb-snapshot.utils.spec.js.map +1 -0
  57. package/dist/assets/server/extraction/script-rule.d.ts +1 -0
  58. package/dist/assets/server/extraction/script-rule.js +4 -0
  59. package/dist/assets/server/extraction/script-rule.js.map +1 -1
  60. package/dist/assets/server/extraction/snapshot-analysis.service.d.ts +23 -0
  61. package/dist/assets/server/extraction/snapshot-analysis.service.js +147 -0
  62. package/dist/assets/server/extraction/snapshot-analysis.service.js.map +1 -0
  63. package/dist/assets/server/recording/entities/recording-snapshot.entity.d.ts +16 -0
  64. package/dist/assets/server/recording/entities/recording-snapshot.entity.js +91 -0
  65. package/dist/assets/server/recording/entities/recording-snapshot.entity.js.map +1 -0
  66. package/dist/assets/server/recording/recording-event-bus.service.d.ts +4 -0
  67. package/dist/assets/server/recording/recording-event-bus.service.js +25 -0
  68. package/dist/assets/server/recording/recording-event-bus.service.js.map +1 -0
  69. package/dist/assets/server/recording/recording-snapshot.service.d.ts +13 -0
  70. package/dist/assets/server/recording/recording-snapshot.service.js +81 -0
  71. package/dist/assets/server/recording/recording-snapshot.service.js.map +1 -0
  72. package/dist/assets/server/recording/recording-snapshot.service.spec.d.ts +1 -0
  73. package/dist/assets/server/recording/recording-snapshot.service.spec.js +65 -0
  74. package/dist/assets/server/recording/recording-snapshot.service.spec.js.map +1 -0
  75. package/dist/assets/server/recording/recording.controller.d.ts +3 -1
  76. package/dist/assets/server/recording/recording.controller.js +7 -3
  77. package/dist/assets/server/recording/recording.controller.js.map +1 -1
  78. package/dist/assets/server/recording/recording.module.js +11 -4
  79. package/dist/assets/server/recording/recording.module.js.map +1 -1
  80. package/dist/assets/server/recording/recording.service.d.ts +5 -21
  81. package/dist/assets/server/recording/recording.service.js +22 -9
  82. package/dist/assets/server/recording/recording.service.js.map +1 -1
  83. package/dist/assets/server/site/entities/site-registry.entity.d.ts +2 -0
  84. package/dist/assets/server/site/entities/site-registry.entity.js +10 -0
  85. package/dist/assets/server/site/entities/site-registry.entity.js.map +1 -1
  86. package/dist/assets/server/site/site.controller.d.ts +2 -0
  87. package/dist/assets/server/site/site.controller.js +1 -1
  88. package/dist/assets/server/site/site.controller.js.map +1 -1
  89. package/dist/assets/server/site/site.service.d.ts +2 -1
  90. package/dist/assets/server/site/site.service.js +10 -3
  91. package/dist/assets/server/site/site.service.js.map +1 -1
  92. package/dist/auto-doctor.d.ts +8 -0
  93. package/dist/auto-doctor.js +34 -0
  94. package/dist/commands/doctor.d.ts +38 -0
  95. package/dist/commands/doctor.js +162 -7
  96. package/dist/commands/extension.js +2 -2
  97. package/dist/commands/extras.d.ts +1 -1
  98. package/dist/commands/extras.js +4 -3
  99. package/dist/commands/install.js +11 -8
  100. package/dist/index.js +7 -0
  101. package/package.json +3 -3
@@ -9,6 +9,7 @@ import { runInstall } from './install.js';
9
9
  import { runExtensionCheck } from './extension.js';
10
10
  import { checkNativeModules } from '../native-check.js';
11
11
  import { getCliLogger } from '../logger.js';
12
+ import { getToken } from '../keychain.js';
12
13
  const B = {
13
14
  reset: '\x1b[0m',
14
15
  bold: '\x1b[1m',
@@ -18,6 +19,32 @@ const B = {
18
19
  cyan: '\x1b[36m',
19
20
  magenta: '\x1b[35m',
20
21
  };
22
+ /**
23
+ * 调用 server /doctor/run 统一入口。
24
+ * 失败时返回 null,调用方决定如何处理。
25
+ */
26
+ export async function runDoctorChecks(opts) {
27
+ const config = loadConfig();
28
+ const token = await getToken().catch(() => undefined);
29
+ const checks = opts.checks && opts.checks.length ? opts.checks : undefined;
30
+ const timeout = opts.timeout ?? 60_000;
31
+ try {
32
+ const { data } = await axios.post(`${config.serverUrl}/doctor/run`, {
33
+ checks,
34
+ mode: opts.fix ? 'repair' : 'check',
35
+ dryRun: opts.dryRun === true,
36
+ }, {
37
+ timeout,
38
+ headers: token ? { Authorization: `UTOKEN ${token}` } : {},
39
+ });
40
+ return data;
41
+ }
42
+ catch (e) {
43
+ const message = e?.response?.data?.message || e.message || String(e);
44
+ getCliLogger().warn('doctor:checks:failed', { error: message, fix: opts.fix, checks });
45
+ return null;
46
+ }
47
+ }
21
48
  /** 对应 ChromePilot doctor: 全链路健康检查 — 带 auto-heal, 无感启动 server/安装扩展 */
22
49
  export async function runDoctor(opts) {
23
50
  const checks = [];
@@ -25,7 +52,7 @@ export async function runDoctor(opts) {
25
52
  const healed = [];
26
53
  const actions = [];
27
54
  const autoHeal = opts.autoHeal !== false; // default true
28
- const interactive = opts.interactive !== false; // default true
55
+ const fix = opts.fix === true;
29
56
  // ── Banner (human 模式) ──
30
57
  if (!opts.json) {
31
58
  console.log(`\n${B.bold}${B.cyan}╭─────────────────────────────────────────────╮${B.reset}`);
@@ -184,7 +211,7 @@ export async function runDoctor(opts) {
184
211
  if (!opts.json) {
185
212
  console.log(`${B.magenta}→ hunter-mate 正在为你打开扩展所在目录...${B.reset}`);
186
213
  }
187
- open(initResult.extensionDir).catch(() => { });
214
+ open(path.dirname(initResult.extensionDir)).catch(() => { });
188
215
  healed.push('open_ext_dir');
189
216
  }
190
217
  if (!opts.json) {
@@ -211,6 +238,35 @@ export async function runDoctor(opts) {
211
238
  catch (e) {
212
239
  checks.push({ key: 'auth', status: 'skip', detail: `跳过(keychain 读取失败): ${e.message}` });
213
240
  }
241
+ // ── 6) 数据面 doctor checks ──
242
+ let data = null;
243
+ if (serverUp) {
244
+ if (!opts.json) {
245
+ console.log(`\n${B.bold}${B.cyan}→ 正在运行数据面检查${fix ? ' (repair 模式)' : ' (只读 check 模式)'}...${B.reset}`);
246
+ }
247
+ data = await runDoctorChecks({
248
+ fix,
249
+ checks: opts.checks,
250
+ json: opts.json,
251
+ timeout: 120_000,
252
+ });
253
+ if (data) {
254
+ for (const c of data.checks) {
255
+ const status = c.ok ? 'ok' : c.severity === 'error' ? 'fail' : 'warn';
256
+ checks.push({
257
+ key: `data_${c.id}`,
258
+ status,
259
+ detail: c.message || c.description,
260
+ });
261
+ }
262
+ }
263
+ else {
264
+ checks.push({ key: 'data_doctor', status: 'warn', detail: '无法连接 /doctor/run (server 可能尚未就绪)' });
265
+ }
266
+ }
267
+ else {
268
+ checks.push({ key: 'data_doctor', status: 'skip', detail: 'server 未运行,跳过数据面检查' });
269
+ }
214
270
  // ── Summary ──
215
271
  const anyFail = checks.some((c) => c.status === 'fail');
216
272
  const anyWarn = checks.some((c) => c.status === 'warn');
@@ -247,7 +303,7 @@ export async function runDoctor(opts) {
247
303
  console.log(`\n${B.bold}${B.red}✗ 环境未就绪, 请按上方引导完成修复后重试。${B.reset}\n`);
248
304
  }
249
305
  }
250
- return { summary, checks, hints, healed, actions };
306
+ return { summary, checks, hints, healed, actions, data };
251
307
  }
252
308
  /** 友好中文引导 — 当环境不完备时给用户清晰的操作步骤 */
253
309
  function showFriendlyGuide(extInstalled, extConnected, serverUp, healed, extDir) {
@@ -261,21 +317,24 @@ function showFriendlyGuide(extInstalled, extConnected, serverUp, healed, extDir)
261
317
  console.log(` hunter-mate 已尝试自动启动。若持续失败请手动执行:`);
262
318
  console.log(` ${B.bold}hunter-mate start -f${B.reset} (前台模式可看日志)\n`);
263
319
  }
320
+ const extParentDir = path.dirname(extDir);
321
+ const extFolderName = path.basename(extDir);
264
322
  if (!extInstalled) {
265
323
  console.log(`${B.bold}${B.yellow}⚠ Hunter Mate Chrome 扩展尚未安装${B.reset}`);
266
324
  console.log(` 步骤: hunter-mate 已自动把扩展文件复制到:`);
267
325
  console.log(` ${B.cyan}${extDir}${B.reset}`);
326
+ console.log(` 并已打开扩展所在目录: ${B.cyan}${extParentDir}${B.reset}`);
268
327
  console.log(` ${B.bold}接下来需要你手动在 Chrome 中加载(Chrome 安全策略限制命令行自动化):${B.reset}`);
269
328
  console.log(` ${B.magenta}① 在 Chrome 地址栏输入:${B.reset} chrome://extensions`);
270
329
  console.log(` ${B.magenta}② 打开右上角「开发者模式」开关${B.reset}`);
271
- console.log(` ${B.magenta}③ 点击「加载已解压的扩展程序」${B.reset}`);
272
- console.log(` ${B.magenta}④ 选择上面的目录:${B.reset} ${B.cyan}${extDir}${B.reset}`);
273
- console.log(` ${B.magenta}⑤ Chrome 工具栏应出现 Hunter Mate 图标${B.reset}\n`);
330
+ console.log(` ${B.magenta}③ 把 ${B.bold}${extFolderName}${B.reset} 文件夹拖到 chrome://extensions 页面里${B.reset}`);
331
+ console.log(` (也可以点击「加载已解压的扩展程序」后选择 ${B.cyan}${extDir}${B.reset})`);
332
+ console.log(` ${B.magenta}④ Chrome 工具栏应出现 Hunter Mate 图标,侧栏显示「已连接」${B.reset}\n`);
274
333
  }
275
334
  if (extInstalled && !extConnected) {
276
335
  console.log(`${B.bold}${B.yellow}⚠ 扩展已安装但尚未连接到 Server${B.reset}`);
277
336
  console.log(` hunter-mate 已自动帮你打开了:`);
278
- console.log(` · 扩展所在目录: ${B.cyan}${extDir}${B.reset}`);
337
+ console.log(` · 扩展所在目录: ${B.cyan}${extParentDir}${B.reset}`);
279
338
  console.log(` · Chrome 扩展管理页: ${B.cyan}chrome://extensions${B.reset}`);
280
339
  console.log(` ${B.bold}请确认以下几点:${B.reset}`);
281
340
  console.log(` ${B.magenta}① 扩展已在 chrome://extensions 中「已开启」${B.reset} (灰色开关变蓝色)`);
@@ -304,14 +363,110 @@ export const doctorCommand = new Command('doctor')
304
363
  .option('--json', '仅以 JSON 输出检查结果与建议(跳过引导文案)')
305
364
  .option('--no-auto-heal', '禁用自动修复(仅检查并给出建议, 不自动启动/安装)')
306
365
  .option('--no-interactive', '无交互模式(不打印友好引导, 仅输出结构化结果)')
366
+ .option('--fix', '执行可修复的数据检查(默认只读 check)')
367
+ .option('--checks <ids>', '只跑指定检查,逗号分隔(如 linkedin-dupes)', (val) => val.split(',').map((s) => s.trim()).filter(Boolean))
368
+ .option('--list', '列出 server 上注册的数据检查项')
307
369
  .action(async (opts) => {
370
+ if (opts.list) {
371
+ const config = loadConfig();
372
+ try {
373
+ const { data } = await axios.get(`${config.serverUrl}/doctor/checks`, { timeout: 10_000 });
374
+ if (opts.json) {
375
+ console.log(JSON.stringify(data, null, 2));
376
+ }
377
+ else {
378
+ console.log(`\n${B.bold}${B.cyan}已注册 Doctor 检查项:${B.reset}`);
379
+ for (const c of data) {
380
+ const fixTag = c.fixable ? `${B.green}[可修复]${B.reset}` : `${B.yellow}[只读]${B.reset}`;
381
+ console.log(` ${fixTag} ${B.bold}${c.id}${B.reset} (${c.severity})`);
382
+ console.log(` ${c.name}`);
383
+ console.log(` ${c.description}`);
384
+ }
385
+ }
386
+ process.exit(0);
387
+ }
388
+ catch (e) {
389
+ const message = e?.response?.data?.message || e.message || String(e);
390
+ getCliLogger().error('doctor:list failed', { message });
391
+ if (opts.json) {
392
+ console.log(JSON.stringify({ error: message }, null, 2));
393
+ }
394
+ else {
395
+ console.log(`${B.red}获取检查列表失败:${B.reset} ${message}`);
396
+ }
397
+ process.exit(1);
398
+ }
399
+ return;
400
+ }
308
401
  const r = await runDoctor({
309
402
  json: opts.json,
310
403
  autoHeal: opts.autoHeal !== false,
311
404
  interactive: opts.interactive !== false,
405
+ fix: opts.fix === true,
406
+ checks: opts.checks,
312
407
  });
313
408
  if (opts.json) {
314
409
  console.log(JSON.stringify(r, null, 2));
315
410
  }
316
411
  process.exit(r.summary === 'fail' ? 1 : 0);
317
412
  });
413
+ export const repairLinkedInDupesCommand = new Command('repair-linkedin-dupes')
414
+ .description('修复 LinkedIn /in/<slug>/details/... 子页产生的重复 talent 数据')
415
+ .option('--dry-run', '仅预览,不真正合并/删除')
416
+ .option('--json', '仅输出 JSON 结果')
417
+ .action(async (opts) => {
418
+ const data = await runDoctorChecks({
419
+ fix: true,
420
+ checks: ['linkedin-dupes'],
421
+ dryRun: opts.dryRun === true,
422
+ json: opts.json,
423
+ timeout: 120_000,
424
+ });
425
+ if (!data) {
426
+ const message = '无法连接 server /doctor/run, 请确认 server 已启动';
427
+ getCliLogger().error('repair-linkedin-dupes failed', { message });
428
+ if (opts.json) {
429
+ console.log(JSON.stringify({ error: message }, null, 2));
430
+ }
431
+ else {
432
+ console.log(`修复失败: ${message}`);
433
+ }
434
+ process.exit(1);
435
+ }
436
+ const c = data.checks.find((x) => x.id === 'linkedin-dupes');
437
+ const details = c?.details || {};
438
+ const compat = {
439
+ dryRun: data.dryRun,
440
+ totalCandidates: details.totalCandidates ?? 0,
441
+ duplicateGroups: details.duplicateGroups ?? 0,
442
+ repairedCount: data.repaired?.length ?? 0,
443
+ merged: data.repaired ?? [],
444
+ summary: data.summary,
445
+ };
446
+ if (opts.json) {
447
+ console.log(JSON.stringify({ ...compat, raw: data }, null, 2));
448
+ }
449
+ else {
450
+ const B = {
451
+ reset: '\x1b[0m',
452
+ bold: '\x1b[1m',
453
+ green: '\x1b[32m',
454
+ yellow: '\x1b[33m',
455
+ cyan: '\x1b[36m',
456
+ };
457
+ console.log(`\n${B.bold}${B.cyan}LinkedIn 重复 talent 修复结果${B.reset}`);
458
+ console.log(` 模式: ${data.dryRun ? B.yellow + 'dry-run' + B.reset : B.green + '正式修复' + B.reset}`);
459
+ console.log(` LinkedIn talent 总数: ${compat.totalCandidates}`);
460
+ console.log(` 归一组数: ${compat.duplicateGroups}`);
461
+ console.log(` 需合并组数: ${compat.repairedCount}`);
462
+ if (compat.merged.length) {
463
+ console.log(`\n${B.bold}合并详情:${B.reset}`);
464
+ for (const m of compat.merged) {
465
+ console.log(` • master #${m.masterId} ← ${m.removedIds.map((id) => `#${id}`).join(', ')}`);
466
+ console.log(` ${m.sourceUrl}`);
467
+ }
468
+ }
469
+ }
470
+ process.exit(0);
471
+ });
472
+ doctorCommand.addCommand(repairLinkedInDupesCommand);
@@ -151,10 +151,10 @@ export function ensureEnvReady() {
151
151
  process.exit(1);
152
152
  }
153
153
  console.log(`${B.green}✓ 扩展已自动安装到 ${r.target}${B.reset}`);
154
- // 打开目录引导
154
+ // 打开扩展所在目录(父目录),方便用户把 extension 文件夹拖到 chrome://extensions
155
155
  try {
156
156
  const { default: open } = await import('open');
157
- open(init.extensionDir).catch(() => { });
157
+ open(path.dirname(init.extensionDir)).catch(() => { });
158
158
  }
159
159
  catch { }
160
160
  showInstallGuide(init.extensionDir);
@@ -12,7 +12,7 @@ export declare function runReload(timeoutMs?: number): Promise<{
12
12
  error: string;
13
13
  }>;
14
14
  }>;
15
- /** 对应 ChromePilot open-ext / open-extension-folder: 打开 ~/.hunter-mate/extension */
15
+ /** 对应 ChromePilot open-ext / open-extension-folder: 打开扩展所在目录(~/.hunter-mate),方便拖到 chrome://extensions */
16
16
  export declare function runOpenExt(target?: string): Promise<string>;
17
17
  export declare const reloadCommand: Command;
18
18
  export declare const openExtCommand: Command;
@@ -102,10 +102,11 @@ export async function runReload(timeoutMs = 5000) {
102
102
  });
103
103
  });
104
104
  }
105
- /** 对应 ChromePilot open-ext / open-extension-folder: 打开 ~/.hunter-mate/extension */
105
+ /** 对应 ChromePilot open-ext / open-extension-folder: 打开扩展所在目录(~/.hunter-mate),方便拖到 chrome://extensions */
106
106
  export async function runOpenExt(target) {
107
107
  const init = runInit();
108
- const dir = target ?? init.extensionDir;
108
+ // 默认打开父目录,让用户能把 extension 文件夹拖进 Chrome;--target 可覆盖
109
+ const dir = target ?? path.dirname(init.extensionDir);
109
110
  if (!fs.existsSync(dir))
110
111
  fs.mkdirSync(dir, { recursive: true });
111
112
  try {
@@ -133,7 +134,7 @@ export const reloadCommand = new Command('reload')
133
134
  });
134
135
  export const openExtCommand = new Command('open-extension-folder')
135
136
  .alias('open-ext')
136
- .description('打开 ~/.hunter-mate/extension 文件夹,方便拖到 chrome://extensions 加载(对应 chromepilot open-ext)')
137
+ .description('打开扩展所在目录(~/.hunter-mate),方便把 extension 文件夹拖到 chrome://extensions 加载(对应 chromepilot open-ext)')
137
138
  .option('-t, --target <dir>', '指定自定义扩展目录')
138
139
  .action(async (opts) => {
139
140
  const dir = await runOpenExt(opts.target);
@@ -52,16 +52,19 @@ const B = {
52
52
  };
53
53
  /** 友好引导: 告诉用户如何在 Chrome 中加载 Hunter Mate 扩展 */
54
54
  export function showInstallGuide(extDir) {
55
+ const parentDir = path.dirname(extDir);
56
+ const folderName = path.basename(extDir);
55
57
  console.log(`\n${B.bold}${B.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━ Hunter Mate 扩展安装指引 ━━━━━━━━━━━━━━━━━━━━━━━━━━${B.reset}`);
56
58
  console.log(`\n${B.bold}扩展文件已经准备好:${B.reset}`);
57
- console.log(` ${B.cyan}${extDir}${B.reset}`);
58
- console.log(`\n${B.bold}请按以下 5 步在 Chrome 中加载(约 30 秒):${B.reset}\n`);
59
+ console.log(` 扩展文件夹: ${B.cyan}${extDir}${B.reset}`);
60
+ console.log(` 已为你打开: ${B.cyan}${parentDir}${B.reset}`);
61
+ console.log(`\n${B.bold}请按以下 4 步在 Chrome 中加载(约 30 秒):${B.reset}\n`);
59
62
  console.log(` ${B.magenta}①${B.reset} 打开 Chrome, 地址栏输入: ${B.cyan}chrome://extensions${B.reset}`);
60
63
  console.log(` (hunter-mate 已帮你自动打开了这个页面)`);
61
64
  console.log(` ${B.magenta}②${B.reset} 右上角打开「${B.bold}开发者模式${B.reset}」开关`);
62
- console.log(` ${B.magenta}③${B.reset} 点击「${B.bold}加载已解压的扩展程序${B.reset}」按钮`);
63
- console.log(` ${B.magenta}④${B.reset} 在文件选择框中选择目录: ${B.cyan}${extDir}${B.reset}`);
64
- console.log(` ${B.magenta}⑤${B.reset} Chrome 工具栏应出现 Hunter Mate 🤝 图标\n`);
65
+ console.log(` ${B.magenta}③${B.reset} 把文件夹 ${B.bold}${folderName}${B.reset} 拖到 chrome://extensions 页面里`);
66
+ console.log(` (也可以点击「加载已解压的扩展程序」后选择 ${B.cyan}${extDir}${B.reset})`);
67
+ console.log(` ${B.magenta}④${B.reset} Chrome 工具栏应出现 Hunter Mate 🤝 图标,侧栏显示「已连接」\n`);
65
68
  console.log(`${B.bold}${B.yellow}常见问题:${B.reset}`);
66
69
  console.log(` · 没看到「加载已解压的扩展程序」? → 先开启「开发者模式」开关`);
67
70
  console.log(` · 加载后图标灰显? → 点击图标确认侧栏显示「已连接」`);
@@ -69,7 +72,7 @@ export function showInstallGuide(extDir) {
69
72
  console.log(`\n${B.bold}${B.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${B.reset}\n`);
70
73
  }
71
74
  export const installCommand = new Command('install')
72
- .description('把内置 extension dist 复制到 ~/.hunter-mate/extension(对应 chromepilot install)。完成后自动打开目录并给出中文引导。')
75
+ .description('把内置 extension dist 复制到 ~/.hunter-mate/extension(对应 chromepilot install)。完成后自动打开扩展所在目录(~/.hunter-mate)并给出中文引导,方便把 extension 文件夹拖到 chrome://extensions。')
73
76
  .option('-t, --target <dir>', '目标目录,默认 ~/.hunter-mate/extension')
74
77
  .option('--no-open', '不自动打开扩展所在目录')
75
78
  .action(async (opts) => {
@@ -82,11 +85,11 @@ export const installCommand = new Command('install')
82
85
  return;
83
86
  }
84
87
  console.log(`${B.green}✓ hunter-mate install OK: ${r.copied} files${B.reset} → ${B.cyan}${r.target}${B.reset}`);
85
- // 自动打开目录 + 引导
88
+ // 自动打开扩展所在目录(父目录),方便用户把 extension 文件夹拖到 chrome://extensions
86
89
  if (opts.open !== false) {
87
90
  try {
88
91
  const { default: open } = await import('open');
89
- open(r.target).catch(() => { });
92
+ open(path.dirname(r.target)).catch(() => { });
90
93
  }
91
94
  catch { }
92
95
  }
package/dist/index.js CHANGED
@@ -76,6 +76,13 @@ program
76
76
  for (const line of report.performed)
77
77
  console.log(chalk.gray(` [auto-update] ${line}`));
78
78
  }
79
+ try {
80
+ const { runAutoDoctor } = await import('./auto-doctor.js');
81
+ await runAutoDoctor(report);
82
+ }
83
+ catch {
84
+ /* auto-doctor 永不阻塞主命令 */
85
+ }
79
86
  }
80
87
  catch {
81
88
  /* 一致性检测永不阻塞主命令 */
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "hunter-mate",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "HiredChina 猎头 Agent 数据助手 CLI:人才库/客户/职位数据访问,浏览器录制解析简历,server/扩展一致性自愈",
6
- "license": "UNLICENSED",
6
+ "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "git+github.com:hiredchina-com/hunter-mate.git"
@@ -31,7 +31,7 @@
31
31
  "open": "^8.4.0",
32
32
  "uuid": "^9.0.0",
33
33
  "ws": "^8.21.3",
34
- "@hunter-mate/shared": "0.1.2"
34
+ "@hunter-mate/shared": "0.1.3"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/better-sqlite3": "^7.6.0",