hunter-mate 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/assets/extension/background/service-worker.js +113 -24
- package/dist/assets/extension/content/index.js +239 -134
- package/dist/assets/extension/manifest.json +2 -4
- package/dist/assets/extension/sidepanel/index.js +76 -2
- package/dist/assets/server/app.module.js +4 -2
- package/dist/assets/server/app.module.js.map +1 -1
- package/dist/assets/server/build-info.json +1 -1
- package/dist/assets/server/db/sqlite.module.js +4 -0
- package/dist/assets/server/db/sqlite.module.js.map +1 -1
- package/dist/assets/server/devops/devops-sink-init.d.ts +14 -1
- package/dist/assets/server/devops/devops-sink-init.js +8 -3
- package/dist/assets/server/devops/devops-sink-init.js.map +1 -1
- package/dist/assets/server/doctor/doctor.controller.d.ts +13 -0
- package/dist/assets/server/doctor/doctor.controller.js +62 -0
- package/dist/assets/server/doctor/doctor.controller.js.map +1 -0
- package/dist/assets/server/doctor/doctor.module.d.ts +2 -0
- package/dist/assets/server/doctor/doctor.module.js +27 -0
- package/dist/assets/server/doctor/doctor.module.js.map +1 -0
- package/dist/assets/server/doctor/doctor.service.d.ts +15 -0
- package/dist/assets/server/doctor/doctor.service.js +209 -0
- package/dist/assets/server/doctor/doctor.service.js.map +1 -0
- package/dist/assets/server/doctor/doctor.types.d.ts +46 -0
- package/dist/assets/server/doctor/doctor.types.js +3 -0
- package/dist/assets/server/doctor/doctor.types.js.map +1 -0
- package/dist/assets/server/extraction/adapter.service.d.ts +18 -4
- package/dist/assets/server/extraction/adapter.service.js +129 -15
- package/dist/assets/server/extraction/adapter.service.js.map +1 -1
- package/dist/assets/server/extraction/anomaly-report.service.d.ts +12 -0
- package/dist/assets/server/extraction/anomaly-report.service.js +81 -0
- package/dist/assets/server/extraction/anomaly-report.service.js.map +1 -0
- package/dist/assets/server/extraction/entities/adapter-rule.entity.d.ts +13 -0
- package/dist/assets/server/extraction/entities/adapter-rule.entity.js +71 -0
- package/dist/assets/server/extraction/entities/adapter-rule.entity.js.map +1 -0
- package/dist/assets/server/extraction/entities/parse-log.entity.d.ts +4 -1
- package/dist/assets/server/extraction/entities/parse-log.entity.js +15 -0
- package/dist/assets/server/extraction/entities/parse-log.entity.js.map +1 -1
- package/dist/assets/server/extraction/extraction-entity.controller.d.ts +40 -2
- package/dist/assets/server/extraction/extraction-entity.controller.js +196 -13
- package/dist/assets/server/extraction/extraction-entity.controller.js.map +1 -1
- package/dist/assets/server/extraction/extraction-rule.service.js +16 -8
- package/dist/assets/server/extraction/extraction-rule.service.js.map +1 -1
- package/dist/assets/server/extraction/extraction.module.js +12 -2
- package/dist/assets/server/extraction/extraction.module.js.map +1 -1
- package/dist/assets/server/extraction/extraction.service.d.ts +17 -1
- package/dist/assets/server/extraction/extraction.service.js +125 -42
- package/dist/assets/server/extraction/extraction.service.js.map +1 -1
- package/dist/assets/server/extraction/linkedin-extractor.d.ts +1 -0
- package/dist/assets/server/extraction/linkedin-extractor.js +244 -0
- package/dist/assets/server/extraction/linkedin-extractor.js.map +1 -0
- package/dist/assets/server/extraction/rrweb-snapshot.utils.d.ts +15 -0
- package/dist/assets/server/extraction/rrweb-snapshot.utils.js +48 -0
- package/dist/assets/server/extraction/rrweb-snapshot.utils.js.map +1 -0
- package/dist/assets/server/extraction/rrweb-snapshot.utils.spec.d.ts +1 -0
- package/dist/assets/server/extraction/rrweb-snapshot.utils.spec.js +60 -0
- package/dist/assets/server/extraction/rrweb-snapshot.utils.spec.js.map +1 -0
- package/dist/assets/server/extraction/script-rule.d.ts +1 -0
- package/dist/assets/server/extraction/script-rule.js +4 -0
- package/dist/assets/server/extraction/script-rule.js.map +1 -1
- package/dist/assets/server/extraction/snapshot-analysis.service.d.ts +23 -0
- package/dist/assets/server/extraction/snapshot-analysis.service.js +147 -0
- package/dist/assets/server/extraction/snapshot-analysis.service.js.map +1 -0
- package/dist/assets/server/recording/entities/recording-snapshot.entity.d.ts +16 -0
- package/dist/assets/server/recording/entities/recording-snapshot.entity.js +91 -0
- package/dist/assets/server/recording/entities/recording-snapshot.entity.js.map +1 -0
- package/dist/assets/server/recording/recording-event-bus.service.d.ts +4 -0
- package/dist/assets/server/recording/recording-event-bus.service.js +25 -0
- package/dist/assets/server/recording/recording-event-bus.service.js.map +1 -0
- package/dist/assets/server/recording/recording-snapshot.service.d.ts +13 -0
- package/dist/assets/server/recording/recording-snapshot.service.js +81 -0
- package/dist/assets/server/recording/recording-snapshot.service.js.map +1 -0
- package/dist/assets/server/recording/recording-snapshot.service.spec.d.ts +1 -0
- package/dist/assets/server/recording/recording-snapshot.service.spec.js +65 -0
- package/dist/assets/server/recording/recording-snapshot.service.spec.js.map +1 -0
- package/dist/assets/server/recording/recording.controller.d.ts +3 -1
- package/dist/assets/server/recording/recording.controller.js +7 -3
- package/dist/assets/server/recording/recording.controller.js.map +1 -1
- package/dist/assets/server/recording/recording.module.js +11 -4
- package/dist/assets/server/recording/recording.module.js.map +1 -1
- package/dist/assets/server/recording/recording.service.d.ts +5 -21
- package/dist/assets/server/recording/recording.service.js +22 -9
- package/dist/assets/server/recording/recording.service.js.map +1 -1
- package/dist/assets/server/site/entities/site-registry.entity.d.ts +2 -0
- package/dist/assets/server/site/entities/site-registry.entity.js +10 -0
- package/dist/assets/server/site/entities/site-registry.entity.js.map +1 -1
- package/dist/assets/server/site/site.controller.d.ts +2 -0
- package/dist/assets/server/site/site.controller.js +1 -1
- package/dist/assets/server/site/site.controller.js.map +1 -1
- package/dist/assets/server/site/site.service.d.ts +2 -1
- package/dist/assets/server/site/site.service.js +10 -3
- package/dist/assets/server/site/site.service.js.map +1 -1
- package/dist/auto-doctor.d.ts +8 -0
- package/dist/auto-doctor.js +34 -0
- package/dist/commands/doctor.d.ts +43 -0
- package/dist/commands/doctor.js +179 -7
- package/dist/commands/extension.js +2 -2
- package/dist/commands/extras.d.ts +1 -1
- package/dist/commands/extras.js +4 -3
- package/dist/commands/install.js +11 -8
- package/dist/index.js +7 -0
- package/dist/native-check.js +8 -1
- package/dist/updater.d.ts +9 -5
- package/dist/updater.js +75 -9
- package/package.json +3 -3
package/dist/commands/doctor.js
CHANGED
|
@@ -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,13 +52,21 @@ export async function runDoctor(opts) {
|
|
|
25
52
|
const healed = [];
|
|
26
53
|
const actions = [];
|
|
27
54
|
const autoHeal = opts.autoHeal !== false; // default true
|
|
28
|
-
const
|
|
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}`);
|
|
32
59
|
console.log(`${B.bold}${B.cyan}│ HunterMate Doctor · 环境全链路自检与自愈 │${B.reset}`);
|
|
33
60
|
console.log(`${B.bold}${B.cyan}╰─────────────────────────────────────────────╯${B.reset}\n`);
|
|
34
61
|
}
|
|
62
|
+
// ── 0) Node 版本(Windows better-sqlite3 预构建友好度) ──
|
|
63
|
+
const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
|
|
64
|
+
const nodeVersionFriendly = [20, 22, 24].includes(nodeMajor);
|
|
65
|
+
checks.push({
|
|
66
|
+
key: 'node_version',
|
|
67
|
+
status: nodeVersionFriendly ? 'ok' : 'warn',
|
|
68
|
+
detail: `Node ${process.versions.node}${nodeVersionFriendly ? '' : ' (hunter-mate 预构建包对 Node 20/22/24 支持最好,Windows 无编译器时建议切到 Node 22 LTS)'}`,
|
|
69
|
+
});
|
|
35
70
|
// ── 1) 目录 & token ──
|
|
36
71
|
let initResult = null;
|
|
37
72
|
try {
|
|
@@ -184,7 +219,7 @@ export async function runDoctor(opts) {
|
|
|
184
219
|
if (!opts.json) {
|
|
185
220
|
console.log(`${B.magenta}→ hunter-mate 正在为你打开扩展所在目录...${B.reset}`);
|
|
186
221
|
}
|
|
187
|
-
open(initResult.extensionDir).catch(() => { });
|
|
222
|
+
open(path.dirname(initResult.extensionDir)).catch(() => { });
|
|
188
223
|
healed.push('open_ext_dir');
|
|
189
224
|
}
|
|
190
225
|
if (!opts.json) {
|
|
@@ -197,9 +232,11 @@ export async function runDoctor(opts) {
|
|
|
197
232
|
}
|
|
198
233
|
}
|
|
199
234
|
// ── 5) 登录态(不强制) ──
|
|
235
|
+
let loggedIn = false;
|
|
200
236
|
try {
|
|
201
237
|
const { getToken } = await import('../keychain.js');
|
|
202
238
|
const token = await getToken();
|
|
239
|
+
loggedIn = !!token;
|
|
203
240
|
checks.push({
|
|
204
241
|
key: 'auth',
|
|
205
242
|
status: token ? 'ok' : 'warn',
|
|
@@ -211,6 +248,35 @@ export async function runDoctor(opts) {
|
|
|
211
248
|
catch (e) {
|
|
212
249
|
checks.push({ key: 'auth', status: 'skip', detail: `跳过(keychain 读取失败): ${e.message}` });
|
|
213
250
|
}
|
|
251
|
+
// ── 6) 数据面 doctor checks ──
|
|
252
|
+
let data = null;
|
|
253
|
+
if (serverUp) {
|
|
254
|
+
if (!opts.json) {
|
|
255
|
+
console.log(`\n${B.bold}${B.cyan}→ 正在运行数据面检查${fix ? ' (repair 模式)' : ' (只读 check 模式)'}...${B.reset}`);
|
|
256
|
+
}
|
|
257
|
+
data = await runDoctorChecks({
|
|
258
|
+
fix,
|
|
259
|
+
checks: opts.checks,
|
|
260
|
+
json: opts.json,
|
|
261
|
+
timeout: 120_000,
|
|
262
|
+
});
|
|
263
|
+
if (data) {
|
|
264
|
+
for (const c of data.checks) {
|
|
265
|
+
const status = c.ok ? 'ok' : c.severity === 'error' ? 'fail' : 'warn';
|
|
266
|
+
checks.push({
|
|
267
|
+
key: `data_${c.id}`,
|
|
268
|
+
status,
|
|
269
|
+
detail: c.message || c.description,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
checks.push({ key: 'data_doctor', status: 'warn', detail: '无法连接 /doctor/run (server 可能尚未就绪)' });
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
checks.push({ key: 'data_doctor', status: 'skip', detail: 'server 未运行,跳过数据面检查' });
|
|
279
|
+
}
|
|
214
280
|
// ── Summary ──
|
|
215
281
|
const anyFail = checks.some((c) => c.status === 'fail');
|
|
216
282
|
const anyWarn = checks.some((c) => c.status === 'warn');
|
|
@@ -247,7 +313,14 @@ export async function runDoctor(opts) {
|
|
|
247
313
|
console.log(`\n${B.bold}${B.red}✗ 环境未就绪, 请按上方引导完成修复后重试。${B.reset}\n`);
|
|
248
314
|
}
|
|
249
315
|
}
|
|
250
|
-
return {
|
|
316
|
+
return {
|
|
317
|
+
summary, checks, hints, healed, actions, data,
|
|
318
|
+
nodeVersion: process.versions.node,
|
|
319
|
+
serverRunning: serverUp,
|
|
320
|
+
extensionInstalled: extInstalled,
|
|
321
|
+
extensionConnected: extConnected,
|
|
322
|
+
loggedIn,
|
|
323
|
+
};
|
|
251
324
|
}
|
|
252
325
|
/** 友好中文引导 — 当环境不完备时给用户清晰的操作步骤 */
|
|
253
326
|
function showFriendlyGuide(extInstalled, extConnected, serverUp, healed, extDir) {
|
|
@@ -261,21 +334,24 @@ function showFriendlyGuide(extInstalled, extConnected, serverUp, healed, extDir)
|
|
|
261
334
|
console.log(` hunter-mate 已尝试自动启动。若持续失败请手动执行:`);
|
|
262
335
|
console.log(` ${B.bold}hunter-mate start -f${B.reset} (前台模式可看日志)\n`);
|
|
263
336
|
}
|
|
337
|
+
const extParentDir = path.dirname(extDir);
|
|
338
|
+
const extFolderName = path.basename(extDir);
|
|
264
339
|
if (!extInstalled) {
|
|
265
340
|
console.log(`${B.bold}${B.yellow}⚠ Hunter Mate Chrome 扩展尚未安装${B.reset}`);
|
|
266
341
|
console.log(` 步骤: hunter-mate 已自动把扩展文件复制到:`);
|
|
267
342
|
console.log(` ${B.cyan}${extDir}${B.reset}`);
|
|
343
|
+
console.log(` 并已打开扩展所在目录: ${B.cyan}${extParentDir}${B.reset}`);
|
|
268
344
|
console.log(` ${B.bold}接下来需要你手动在 Chrome 中加载(Chrome 安全策略限制命令行自动化):${B.reset}`);
|
|
269
345
|
console.log(` ${B.magenta}① 在 Chrome 地址栏输入:${B.reset} chrome://extensions`);
|
|
270
346
|
console.log(` ${B.magenta}② 打开右上角「开发者模式」开关${B.reset}`);
|
|
271
|
-
console.log(` ${B.magenta}③
|
|
272
|
-
console.log(`
|
|
273
|
-
console.log(` ${B.magenta}
|
|
347
|
+
console.log(` ${B.magenta}③ 把 ${B.bold}${extFolderName}${B.reset} 文件夹拖到 chrome://extensions 页面里${B.reset}`);
|
|
348
|
+
console.log(` (也可以点击「加载已解压的扩展程序」后选择 ${B.cyan}${extDir}${B.reset})`);
|
|
349
|
+
console.log(` ${B.magenta}④ Chrome 工具栏应出现 Hunter Mate 图标,侧栏显示「已连接」${B.reset}\n`);
|
|
274
350
|
}
|
|
275
351
|
if (extInstalled && !extConnected) {
|
|
276
352
|
console.log(`${B.bold}${B.yellow}⚠ 扩展已安装但尚未连接到 Server${B.reset}`);
|
|
277
353
|
console.log(` hunter-mate 已自动帮你打开了:`);
|
|
278
|
-
console.log(` · 扩展所在目录: ${B.cyan}${
|
|
354
|
+
console.log(` · 扩展所在目录: ${B.cyan}${extParentDir}${B.reset}`);
|
|
279
355
|
console.log(` · Chrome 扩展管理页: ${B.cyan}chrome://extensions${B.reset}`);
|
|
280
356
|
console.log(` ${B.bold}请确认以下几点:${B.reset}`);
|
|
281
357
|
console.log(` ${B.magenta}① 扩展已在 chrome://extensions 中「已开启」${B.reset} (灰色开关变蓝色)`);
|
|
@@ -304,14 +380,110 @@ export const doctorCommand = new Command('doctor')
|
|
|
304
380
|
.option('--json', '仅以 JSON 输出检查结果与建议(跳过引导文案)')
|
|
305
381
|
.option('--no-auto-heal', '禁用自动修复(仅检查并给出建议, 不自动启动/安装)')
|
|
306
382
|
.option('--no-interactive', '无交互模式(不打印友好引导, 仅输出结构化结果)')
|
|
383
|
+
.option('--fix', '执行可修复的数据检查(默认只读 check)')
|
|
384
|
+
.option('--checks <ids>', '只跑指定检查,逗号分隔(如 linkedin-dupes)', (val) => val.split(',').map((s) => s.trim()).filter(Boolean))
|
|
385
|
+
.option('--list', '列出 server 上注册的数据检查项')
|
|
307
386
|
.action(async (opts) => {
|
|
387
|
+
if (opts.list) {
|
|
388
|
+
const config = loadConfig();
|
|
389
|
+
try {
|
|
390
|
+
const { data } = await axios.get(`${config.serverUrl}/doctor/checks`, { timeout: 10_000 });
|
|
391
|
+
if (opts.json) {
|
|
392
|
+
console.log(JSON.stringify(data, null, 2));
|
|
393
|
+
}
|
|
394
|
+
else {
|
|
395
|
+
console.log(`\n${B.bold}${B.cyan}已注册 Doctor 检查项:${B.reset}`);
|
|
396
|
+
for (const c of data) {
|
|
397
|
+
const fixTag = c.fixable ? `${B.green}[可修复]${B.reset}` : `${B.yellow}[只读]${B.reset}`;
|
|
398
|
+
console.log(` ${fixTag} ${B.bold}${c.id}${B.reset} (${c.severity})`);
|
|
399
|
+
console.log(` ${c.name}`);
|
|
400
|
+
console.log(` ${c.description}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
process.exit(0);
|
|
404
|
+
}
|
|
405
|
+
catch (e) {
|
|
406
|
+
const message = e?.response?.data?.message || e.message || String(e);
|
|
407
|
+
getCliLogger().error('doctor:list failed', { message });
|
|
408
|
+
if (opts.json) {
|
|
409
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
console.log(`${B.red}获取检查列表失败:${B.reset} ${message}`);
|
|
413
|
+
}
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
308
418
|
const r = await runDoctor({
|
|
309
419
|
json: opts.json,
|
|
310
420
|
autoHeal: opts.autoHeal !== false,
|
|
311
421
|
interactive: opts.interactive !== false,
|
|
422
|
+
fix: opts.fix === true,
|
|
423
|
+
checks: opts.checks,
|
|
312
424
|
});
|
|
313
425
|
if (opts.json) {
|
|
314
426
|
console.log(JSON.stringify(r, null, 2));
|
|
315
427
|
}
|
|
316
428
|
process.exit(r.summary === 'fail' ? 1 : 0);
|
|
317
429
|
});
|
|
430
|
+
export const repairLinkedInDupesCommand = new Command('repair-linkedin-dupes')
|
|
431
|
+
.description('修复 LinkedIn /in/<slug>/details/... 子页产生的重复 talent 数据')
|
|
432
|
+
.option('--dry-run', '仅预览,不真正合并/删除')
|
|
433
|
+
.option('--json', '仅输出 JSON 结果')
|
|
434
|
+
.action(async (opts) => {
|
|
435
|
+
const data = await runDoctorChecks({
|
|
436
|
+
fix: true,
|
|
437
|
+
checks: ['linkedin-dupes'],
|
|
438
|
+
dryRun: opts.dryRun === true,
|
|
439
|
+
json: opts.json,
|
|
440
|
+
timeout: 120_000,
|
|
441
|
+
});
|
|
442
|
+
if (!data) {
|
|
443
|
+
const message = '无法连接 server /doctor/run, 请确认 server 已启动';
|
|
444
|
+
getCliLogger().error('repair-linkedin-dupes failed', { message });
|
|
445
|
+
if (opts.json) {
|
|
446
|
+
console.log(JSON.stringify({ error: message }, null, 2));
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
console.log(`修复失败: ${message}`);
|
|
450
|
+
}
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
const c = data.checks.find((x) => x.id === 'linkedin-dupes');
|
|
454
|
+
const details = c?.details || {};
|
|
455
|
+
const compat = {
|
|
456
|
+
dryRun: data.dryRun,
|
|
457
|
+
totalCandidates: details.totalCandidates ?? 0,
|
|
458
|
+
duplicateGroups: details.duplicateGroups ?? 0,
|
|
459
|
+
repairedCount: data.repaired?.length ?? 0,
|
|
460
|
+
merged: data.repaired ?? [],
|
|
461
|
+
summary: data.summary,
|
|
462
|
+
};
|
|
463
|
+
if (opts.json) {
|
|
464
|
+
console.log(JSON.stringify({ ...compat, raw: data }, null, 2));
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
const B = {
|
|
468
|
+
reset: '\x1b[0m',
|
|
469
|
+
bold: '\x1b[1m',
|
|
470
|
+
green: '\x1b[32m',
|
|
471
|
+
yellow: '\x1b[33m',
|
|
472
|
+
cyan: '\x1b[36m',
|
|
473
|
+
};
|
|
474
|
+
console.log(`\n${B.bold}${B.cyan}LinkedIn 重复 talent 修复结果${B.reset}`);
|
|
475
|
+
console.log(` 模式: ${data.dryRun ? B.yellow + 'dry-run' + B.reset : B.green + '正式修复' + B.reset}`);
|
|
476
|
+
console.log(` LinkedIn talent 总数: ${compat.totalCandidates}`);
|
|
477
|
+
console.log(` 归一组数: ${compat.duplicateGroups}`);
|
|
478
|
+
console.log(` 需合并组数: ${compat.repairedCount}`);
|
|
479
|
+
if (compat.merged.length) {
|
|
480
|
+
console.log(`\n${B.bold}合并详情:${B.reset}`);
|
|
481
|
+
for (const m of compat.merged) {
|
|
482
|
+
console.log(` • master #${m.masterId} ← ${m.removedIds.map((id) => `#${id}`).join(', ')}`);
|
|
483
|
+
console.log(` ${m.sourceUrl}`);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
process.exit(0);
|
|
488
|
+
});
|
|
489
|
+
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:
|
|
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;
|
package/dist/commands/extras.js
CHANGED
|
@@ -102,10 +102,11 @@ export async function runReload(timeoutMs = 5000) {
|
|
|
102
102
|
});
|
|
103
103
|
});
|
|
104
104
|
}
|
|
105
|
-
/** 对应 ChromePilot open-ext / open-extension-folder:
|
|
105
|
+
/** 对应 ChromePilot open-ext / open-extension-folder: 打开扩展所在目录(~/.hunter-mate),方便拖到 chrome://extensions */
|
|
106
106
|
export async function runOpenExt(target) {
|
|
107
107
|
const init = runInit();
|
|
108
|
-
|
|
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('
|
|
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);
|
package/dist/commands/install.js
CHANGED
|
@@ -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(
|
|
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}
|
|
63
|
-
console.log(`
|
|
64
|
-
console.log(` ${B.magenta}
|
|
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/dist/native-check.js
CHANGED
|
@@ -18,6 +18,13 @@ function locateServerEntryDir() {
|
|
|
18
18
|
const checker = createNativeChecker({
|
|
19
19
|
probeDir: () => locateServerEntryDir(),
|
|
20
20
|
skipDetail: "未找到 server 入口(packages/server/dist|src),跳过原生模块检查",
|
|
21
|
-
rebuildHint: (moduleName, compiledLabel) =>
|
|
21
|
+
rebuildHint: (moduleName, compiledLabel) => {
|
|
22
|
+
const isWin = process.platform === "win32";
|
|
23
|
+
const base = `二选一:① 切回 ${compiledLabel} 的 node 再运行;② 用当前 node 重编译:pnpm rebuild -r ${moduleName}(重编后重启 server)`;
|
|
24
|
+
if (isWin && moduleName === "better-sqlite3") {
|
|
25
|
+
return `${base}。Windows 无 C++ 编译器时,推荐直接安装 Node 22 LTS(预构建包最全),无需安装 Visual Studio Build Tools`;
|
|
26
|
+
}
|
|
27
|
+
return base;
|
|
28
|
+
},
|
|
22
29
|
});
|
|
23
30
|
export const checkNativeModules = checker.checkNativeModules;
|
package/dist/updater.d.ts
CHANGED
|
@@ -6,17 +6,21 @@
|
|
|
6
6
|
export declare function computeDevLinked(pkgRoot: string): boolean;
|
|
7
7
|
export declare function isDevLinked(): boolean;
|
|
8
8
|
export declare const npmLatestVersion: (pkg?: string) => string | null;
|
|
9
|
-
export declare const npmSelfUpdate: (pkg?: string) => {
|
|
10
|
-
ok: boolean;
|
|
11
|
-
msg: string;
|
|
12
|
-
};
|
|
13
9
|
export declare const updateCheckIntervalMs: () => number;
|
|
14
10
|
export declare const shouldCheckUpdate: (stateFile?: string, now?: number, intervalMs?: number) => boolean;
|
|
15
11
|
export declare const markUpdateChecked: (stateFile?: string) => void;
|
|
12
|
+
export interface NpmSelfUpdateResult {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
msg: string;
|
|
15
|
+
}
|
|
16
|
+
export declare function npmSelfUpdate(pkg?: string): NpmSelfUpdateResult;
|
|
16
17
|
export interface CliUpdateOutcome {
|
|
17
18
|
logs: string[];
|
|
18
19
|
devSkipped: boolean;
|
|
19
20
|
updated: boolean;
|
|
20
21
|
}
|
|
21
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* preAction 集成入口:返回日志行数组,永不 throw。
|
|
24
|
+
* 逻辑等价于 kit 的 autoCliUpdate,但安装命令走 --prefix。
|
|
25
|
+
*/
|
|
22
26
|
export declare function maybeCliSelfUpdate(): Promise<CliUpdateOutcome>;
|
package/dist/updater.js
CHANGED
|
@@ -6,18 +6,25 @@
|
|
|
6
6
|
* - dev 判定(hc-hw local worktree 布局 + HUNTER_MATE_DEV=1)为本仓特有安装形态
|
|
7
7
|
* - 已安装版本读取走 version.ts 的 package.json SSOT 扫描
|
|
8
8
|
*
|
|
9
|
+
* 重要: hunter-mate 使用用户级安装(--prefix ~/.hunter-mate/cli),
|
|
10
|
+
* 避免 Workbuddy/Trae 沙盒拦截 `npm install -g` 全局写入。
|
|
11
|
+
* 因此覆盖 kit 默认的 npmSelfUpdate/autoCliUpdate,统一走 --prefix。
|
|
12
|
+
*
|
|
9
13
|
* 逃生开关(kit autoCliUpdate 内统一处理):
|
|
10
14
|
* - HUNTER_MATE_NO_AUTOUPDATE=1 关闭全部自动更新(CLI + 一致性,preAction 层)
|
|
11
15
|
* - HUNTER_MATE_UPDATE_CHECK_INTERVAL_MS 覆盖节流间隔(默认 3600000)
|
|
12
16
|
* - HUNTER_MATE_DEV=1 强制视为 dev 模式(测试用)
|
|
13
17
|
*/
|
|
18
|
+
import * as fs from 'node:fs';
|
|
14
19
|
import * as path from 'path';
|
|
20
|
+
import { spawnSync } from 'node:child_process';
|
|
15
21
|
import { fileURLToPath } from 'node:url';
|
|
16
22
|
import { createCliUpdater } from '@chromepilot/kit';
|
|
17
23
|
import { readInstalledVersions } from './version.js';
|
|
18
24
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
19
25
|
const HOME = () => process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
|
|
20
26
|
const ROOT = () => path.join(HOME(), '.hunter-mate');
|
|
27
|
+
const CLI_PREFIX = () => path.join(ROOT(), 'cli');
|
|
21
28
|
/**
|
|
22
29
|
* 判定是否开发软链模式(纯函数,便于单测):
|
|
23
30
|
* 包根路径含 /worktrees/ 段(hc-hw local worktree 布局)或 HUNTER_MATE_DEV=1。
|
|
@@ -31,7 +38,7 @@ export function computeDevLinked(pkgRoot) {
|
|
|
31
38
|
export function isDevLinked() {
|
|
32
39
|
return computeDevLinked(path.join(__dirname, '..'));
|
|
33
40
|
}
|
|
34
|
-
const
|
|
41
|
+
const kitUpdater = createCliUpdater({
|
|
35
42
|
npmPackage: 'hunter-mate',
|
|
36
43
|
envPrefix: 'HUNTER_MATE',
|
|
37
44
|
stateDir: ROOT,
|
|
@@ -39,14 +46,73 @@ const updater = createCliUpdater({
|
|
|
39
46
|
isDev: isDevLinked,
|
|
40
47
|
});
|
|
41
48
|
// ---------------------------------------------------------------------------
|
|
42
|
-
//
|
|
49
|
+
// 复用 kit 的工具函数(版本查询、节流标记等)
|
|
43
50
|
// ---------------------------------------------------------------------------
|
|
44
|
-
export const npmLatestVersion =
|
|
45
|
-
export const
|
|
46
|
-
export const
|
|
47
|
-
export const
|
|
48
|
-
export
|
|
49
|
-
|
|
51
|
+
export const npmLatestVersion = kitUpdater.npmLatestVersion;
|
|
52
|
+
export const updateCheckIntervalMs = kitUpdater.updateCheckIntervalMs;
|
|
53
|
+
export const shouldCheckUpdate = kitUpdater.shouldCheckUpdate;
|
|
54
|
+
export const markUpdateChecked = kitUpdater.markUpdateChecked;
|
|
55
|
+
export 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
|
+
const r = spawnSync(npmBin, args, {
|
|
62
|
+
stdio: 'inherit',
|
|
63
|
+
timeout: 120_000,
|
|
64
|
+
env: {
|
|
65
|
+
...process.env,
|
|
66
|
+
// 防止 npm 因为用户 home 不可写而失败;--prefix 已指定安装位置
|
|
67
|
+
npm_config_cache: path.join(prefix, '.npm-cache'),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
if (r.status === 0) {
|
|
71
|
+
return {
|
|
72
|
+
ok: true,
|
|
73
|
+
msg: `${npmBin} install ${pkg}@latest --prefix ${prefix} 完成,下一条命令起使用新版本`,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
ok: false,
|
|
78
|
+
msg: `${npmBin} install ${pkg}@latest --prefix ${prefix} 失败 (exit=${r.status}),可手动执行`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* preAction 集成入口:返回日志行数组,永不 throw。
|
|
83
|
+
* 逻辑等价于 kit 的 autoCliUpdate,但安装命令走 --prefix。
|
|
84
|
+
*/
|
|
50
85
|
export async function maybeCliSelfUpdate() {
|
|
51
|
-
|
|
86
|
+
const logs = [];
|
|
87
|
+
const outcome = { logs, devSkipped: false, updated: false };
|
|
88
|
+
try {
|
|
89
|
+
if (process.env.HUNTER_MATE_NO_AUTOUPDATE === '1')
|
|
90
|
+
return outcome;
|
|
91
|
+
if (isDevLinked()) {
|
|
92
|
+
outcome.devSkipped = true;
|
|
93
|
+
logs.push('dev 软链模式,跳过 CLI 自更新(改代码请 pnpm build;reload/restart 自愈不受影响)');
|
|
94
|
+
return outcome;
|
|
95
|
+
}
|
|
96
|
+
if (!shouldCheckUpdate())
|
|
97
|
+
return outcome;
|
|
98
|
+
markUpdateChecked();
|
|
99
|
+
const installed = readInstalledVersions().cli;
|
|
100
|
+
if (!installed || installed === '0.0.0')
|
|
101
|
+
return outcome;
|
|
102
|
+
const latest = npmLatestVersion();
|
|
103
|
+
if (!latest) {
|
|
104
|
+
logs.push(`npm registry 查询 hunter-mate 失败,本次跳过自更新`);
|
|
105
|
+
return outcome;
|
|
106
|
+
}
|
|
107
|
+
if (latest === installed)
|
|
108
|
+
return outcome;
|
|
109
|
+
logs.push(`CLI 自更新: ${installed} → ${latest}`);
|
|
110
|
+
const r = npmSelfUpdate();
|
|
111
|
+
logs.push(r.msg);
|
|
112
|
+
outcome.updated = r.ok;
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
logs.push(`自动更新检测异常(不影响命令执行): ${err instanceof Error ? err.message : String(err)}`);
|
|
116
|
+
}
|
|
117
|
+
return outcome;
|
|
52
118
|
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hunter-mate",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "HiredChina 猎头 Agent 数据助手 CLI:人才库/客户/职位数据访问,浏览器录制解析简历,server/扩展一致性自愈",
|
|
6
|
-
"license": "
|
|
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.
|
|
34
|
+
"@hunter-mate/shared": "0.1.4"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/better-sqlite3": "^7.6.0",
|