hunter-mate 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/dist/assets/extension/background/service-worker.js +727 -152
  2. package/dist/assets/extension/build-info.json +1 -0
  3. package/dist/assets/extension/content/index.js +22 -4
  4. package/dist/assets/extension/manifest.json +5 -4
  5. package/dist/assets/extension/offscreen/offscreen.html +8 -0
  6. package/dist/assets/extension/offscreen/offscreen.js +149 -0
  7. package/dist/assets/extension/package.json +8 -1
  8. package/dist/assets/extension/sidepanel/index.css +2 -0
  9. package/dist/assets/extension/sidepanel/index.html +7 -394
  10. package/dist/assets/extension/sidepanel/index.js +53 -923
  11. package/dist/assets/server/app.module.js +11 -1
  12. package/dist/assets/server/app.module.js.map +1 -1
  13. package/dist/assets/server/auth/auth.middleware.js +9 -1
  14. package/dist/assets/server/auth/auth.middleware.js.map +1 -1
  15. package/dist/assets/server/build-info.json +1 -1
  16. package/dist/assets/server/customer/customer.controller.d.ts +18 -0
  17. package/dist/assets/server/customer/customer.controller.js +81 -0
  18. package/dist/assets/server/customer/customer.controller.js.map +1 -0
  19. package/dist/assets/server/customer/customer.entity.d.ts +17 -0
  20. package/dist/assets/server/customer/customer.entity.js +82 -0
  21. package/dist/assets/server/customer/customer.entity.js.map +1 -0
  22. package/dist/assets/server/customer/customer.module.d.ts +2 -0
  23. package/dist/assets/server/customer/customer.module.js +28 -0
  24. package/dist/assets/server/customer/customer.module.js.map +1 -0
  25. package/dist/assets/server/customer/customer.service.d.ts +36 -0
  26. package/dist/assets/server/customer/customer.service.js +200 -0
  27. package/dist/assets/server/customer/customer.service.js.map +1 -0
  28. package/dist/assets/server/db/schema-validator.service.d.ts +2 -0
  29. package/dist/assets/server/db/schema-validator.service.js +32 -1
  30. package/dist/assets/server/db/schema-validator.service.js.map +1 -1
  31. package/dist/assets/server/db/sqlite.module.d.ts +1 -0
  32. package/dist/assets/server/db/sqlite.module.js +12 -2
  33. package/dist/assets/server/db/sqlite.module.js.map +1 -1
  34. package/dist/assets/server/devops/devops-sink-init.d.ts +1 -14
  35. package/dist/assets/server/devops/devops-sink-init.js +3 -8
  36. package/dist/assets/server/devops/devops-sink-init.js.map +1 -1
  37. package/dist/assets/server/extension/extension-control.controller.d.ts +12 -0
  38. package/dist/assets/server/extension/extension-control.controller.js +41 -1
  39. package/dist/assets/server/extension/extension-control.controller.js.map +1 -1
  40. package/dist/assets/server/extension/extension-control.service.d.ts +6 -1
  41. package/dist/assets/server/extension/extension-control.service.js +27 -1
  42. package/dist/assets/server/extension/extension-control.service.js.map +1 -1
  43. package/dist/assets/server/extraction/adapter.service.d.ts +3 -0
  44. package/dist/assets/server/extraction/adapter.service.js +127 -6
  45. package/dist/assets/server/extraction/adapter.service.js.map +1 -1
  46. package/dist/assets/server/extraction/anomaly-report.service.js +1 -1
  47. package/dist/assets/server/extraction/anomaly-report.service.js.map +1 -1
  48. package/dist/assets/server/extraction/entities/adapter-rule.entity.d.ts +2 -0
  49. package/dist/assets/server/extraction/entities/adapter-rule.entity.js +10 -0
  50. package/dist/assets/server/extraction/entities/adapter-rule.entity.js.map +1 -1
  51. package/dist/assets/server/extraction/extraction-entity.controller.d.ts +21 -3
  52. package/dist/assets/server/extraction/extraction-entity.controller.js +44 -9
  53. package/dist/assets/server/extraction/extraction-entity.controller.js.map +1 -1
  54. package/dist/assets/server/extraction/extraction.module.js +13 -3
  55. package/dist/assets/server/extraction/extraction.module.js.map +1 -1
  56. package/dist/assets/server/extraction/extraction.service.d.ts +4 -1
  57. package/dist/assets/server/extraction/extraction.service.js +59 -4
  58. package/dist/assets/server/extraction/extraction.service.js.map +1 -1
  59. package/dist/assets/server/extraction/harness-overlay.service.d.ts +41 -0
  60. package/dist/assets/server/extraction/harness-overlay.service.js +319 -0
  61. package/dist/assets/server/extraction/harness-overlay.service.js.map +1 -0
  62. package/dist/assets/server/extraction/liepin-extractor.d.ts +1 -0
  63. package/dist/assets/server/extraction/liepin-extractor.js +172 -0
  64. package/dist/assets/server/extraction/liepin-extractor.js.map +1 -0
  65. package/dist/assets/server/extraction/script-rule.js +4 -0
  66. package/dist/assets/server/extraction/script-rule.js.map +1 -1
  67. package/dist/assets/server/governance/action-audit.entity.d.ts +12 -0
  68. package/dist/assets/server/governance/action-audit.entity.js +66 -0
  69. package/dist/assets/server/governance/action-audit.entity.js.map +1 -0
  70. package/dist/assets/server/governance/governance.controller.d.ts +14 -0
  71. package/dist/assets/server/governance/governance.controller.js +77 -0
  72. package/dist/assets/server/governance/governance.controller.js.map +1 -0
  73. package/dist/assets/server/governance/governance.ingester.d.ts +20 -0
  74. package/dist/assets/server/governance/governance.ingester.js +114 -0
  75. package/dist/assets/server/governance/governance.ingester.js.map +1 -0
  76. package/dist/assets/server/governance/governance.module.d.ts +2 -0
  77. package/dist/assets/server/governance/governance.module.js +26 -0
  78. package/dist/assets/server/governance/governance.module.js.map +1 -0
  79. package/dist/assets/server/governance/governance.service.d.ts +114 -0
  80. package/dist/assets/server/governance/governance.service.js +356 -0
  81. package/dist/assets/server/governance/governance.service.js.map +1 -0
  82. package/dist/assets/server/jobcase/jobcase.controller.d.ts +1 -0
  83. package/dist/assets/server/jobcase/jobcase.entity.d.ts +1 -0
  84. package/dist/assets/server/jobcase/jobcase.entity.js +6 -0
  85. package/dist/assets/server/jobcase/jobcase.entity.js.map +1 -1
  86. package/dist/assets/server/jobcase/jobcase.service.d.ts +2 -0
  87. package/dist/assets/server/jobcase/jobcase.service.js +2 -1
  88. package/dist/assets/server/jobcase/jobcase.service.js.map +1 -1
  89. package/dist/assets/server/main.js +1 -0
  90. package/dist/assets/server/main.js.map +1 -1
  91. package/dist/assets/server/package.json +1 -1
  92. package/dist/assets/server/preference/preference.controller.d.ts +14 -0
  93. package/dist/assets/server/preference/preference.controller.js +66 -0
  94. package/dist/assets/server/preference/preference.controller.js.map +1 -0
  95. package/dist/assets/server/preference/preference.entity.d.ts +13 -0
  96. package/dist/assets/server/preference/preference.entity.js +77 -0
  97. package/dist/assets/server/preference/preference.entity.js.map +1 -0
  98. package/dist/assets/server/preference/preference.module.d.ts +2 -0
  99. package/dist/assets/server/preference/preference.module.js +26 -0
  100. package/dist/assets/server/preference/preference.module.js.map +1 -0
  101. package/dist/assets/server/preference/preference.service.d.ts +33 -0
  102. package/dist/assets/server/preference/preference.service.js +122 -0
  103. package/dist/assets/server/preference/preference.service.js.map +1 -0
  104. package/dist/assets/server/recording/recording-event-queue.service.js +1 -1
  105. package/dist/assets/server/recording/recording-event-queue.service.js.map +1 -1
  106. package/dist/assets/server/recording/recording-retention.service.d.ts +19 -2
  107. package/dist/assets/server/recording/recording-retention.service.js +95 -6
  108. package/dist/assets/server/recording/recording-retention.service.js.map +1 -1
  109. package/dist/assets/server/recording/recording-snapshot.service.d.ts +1 -0
  110. package/dist/assets/server/recording/recording-snapshot.service.js +4 -0
  111. package/dist/assets/server/recording/recording-snapshot.service.js.map +1 -1
  112. package/dist/assets/server/recording/recording.controller.d.ts +10 -0
  113. package/dist/assets/server/recording/recording.controller.js +19 -0
  114. package/dist/assets/server/recording/recording.controller.js.map +1 -1
  115. package/dist/assets/server/recording/recording.gateway.js +11 -0
  116. package/dist/assets/server/recording/recording.gateway.js.map +1 -1
  117. package/dist/assets/server/recording/recording.module.js +6 -1
  118. package/dist/assets/server/recording/recording.module.js.map +1 -1
  119. package/dist/assets/server/recording/recording.service.d.ts +5 -0
  120. package/dist/assets/server/recording/recording.service.js +10 -0
  121. package/dist/assets/server/recording/recording.service.js.map +1 -1
  122. package/dist/assets/server/remark/remark.controller.d.ts +1 -1
  123. package/dist/assets/server/remark/remark.controller.js +1 -10
  124. package/dist/assets/server/remark/remark.controller.js.map +1 -1
  125. package/dist/assets/server/remark/remark.entity.d.ts +1 -1
  126. package/dist/assets/server/remark/remark.entity.js.map +1 -1
  127. package/dist/assets/server/site/entities/site-registry.entity.d.ts +1 -1
  128. package/dist/assets/server/site/entities/site-registry.entity.js +1 -1
  129. package/dist/assets/server/site/entities/site-registry.entity.js.map +1 -1
  130. package/dist/assets/server/site/site.controller.d.ts +1 -1
  131. package/dist/assets/server/site/site.service.d.ts +5 -1
  132. package/dist/assets/server/site/site.service.js +13 -0
  133. package/dist/assets/server/site/site.service.js.map +1 -1
  134. package/dist/assets/server/system/system.controller.d.ts +1 -0
  135. package/dist/assets/server/system/system.controller.js +1 -1
  136. package/dist/assets/server/system/system.controller.js.map +1 -1
  137. package/dist/assets/server/system/system.service.d.ts +1 -0
  138. package/dist/assets/server/system/system.service.js +3 -2
  139. package/dist/assets/server/system/system.service.js.map +1 -1
  140. package/dist/cache/db.js +6 -6
  141. package/dist/commands/addons.js +2 -1
  142. package/dist/commands/cache.js +1 -1
  143. package/dist/commands/case.d.ts +67 -0
  144. package/dist/commands/case.js +101 -0
  145. package/dist/commands/config.d.ts +4 -0
  146. package/dist/commands/config.js +8 -1
  147. package/dist/commands/customer.d.ts +25 -0
  148. package/dist/commands/customer.js +137 -0
  149. package/dist/commands/doctor.js +33 -9
  150. package/dist/commands/extension.js +18 -15
  151. package/dist/commands/extras.d.ts +22 -3
  152. package/dist/commands/extras.js +114 -5
  153. package/dist/commands/init.d.ts +1 -1
  154. package/dist/commands/init.js +3 -4
  155. package/dist/commands/issue-env.d.ts +23 -0
  156. package/dist/commands/issue-env.js +101 -0
  157. package/dist/commands/job.d.ts +2 -1
  158. package/dist/commands/job.js +25 -5
  159. package/dist/commands/login.d.ts +4 -0
  160. package/dist/commands/login.js +30 -3
  161. package/dist/commands/migrate.d.ts +32 -0
  162. package/dist/commands/migrate.js +116 -0
  163. package/dist/commands/note.d.ts +3 -0
  164. package/dist/commands/note.js +75 -0
  165. package/dist/commands/ops.js +64 -13
  166. package/dist/commands/preference.d.ts +6 -0
  167. package/dist/commands/preference.js +80 -0
  168. package/dist/commands/record.js +1 -1
  169. package/dist/commands/report.js +1 -1
  170. package/dist/commands/search.js +1 -1
  171. package/dist/commands/server.d.ts +7 -0
  172. package/dist/commands/server.js +40 -2
  173. package/dist/commands/status.d.ts +4 -0
  174. package/dist/commands/status.js +16 -6
  175. package/dist/commands/update.js +7 -8
  176. package/dist/config.d.ts +21 -0
  177. package/dist/config.js +27 -0
  178. package/dist/i18n/locales/en-US.json +17 -1
  179. package/dist/i18n/locales/zh-CN.json +17 -1
  180. package/dist/index.js +14 -0
  181. package/dist/keychain.d.ts +16 -1
  182. package/dist/keychain.js +43 -16
  183. package/dist/logger.js +2 -2
  184. package/dist/updater.js +2 -2
  185. package/dist/utils/command-error.d.ts +4 -2
  186. package/dist/utils/command-error.js +19 -3
  187. package/dist/utils/link.d.ts +18 -0
  188. package/dist/utils/link.js +83 -0
  189. package/dist/version.js +33 -8
  190. package/dist/watchdog.js +3 -3
  191. package/dist/web/dist/404.html +1 -1
  192. package/dist/web/dist/_next/static/chunks/app/logs/page-d478031d49f7ba00.js +1 -0
  193. package/dist/web/dist/auth/verify.html +1 -1
  194. package/dist/web/dist/auth/verify.txt +1 -1
  195. package/dist/web/dist/companies.html +1 -1
  196. package/dist/web/dist/companies.txt +1 -1
  197. package/dist/web/dist/entity.html +1 -1
  198. package/dist/web/dist/entity.txt +1 -1
  199. package/dist/web/dist/index.html +1 -1
  200. package/dist/web/dist/index.txt +1 -1
  201. package/dist/web/dist/jobs.html +1 -1
  202. package/dist/web/dist/jobs.txt +1 -1
  203. package/dist/web/dist/logs.html +1 -1
  204. package/dist/web/dist/logs.txt +2 -2
  205. package/dist/web/dist/matching/jobs/placeholder/talents.html +1 -1
  206. package/dist/web/dist/matching/jobs/placeholder/talents.txt +1 -1
  207. package/dist/web/dist/matching/portraits/placeholder/jobs.html +1 -1
  208. package/dist/web/dist/matching/portraits/placeholder/jobs.txt +1 -1
  209. package/dist/web/dist/parselog.html +1 -1
  210. package/dist/web/dist/parselog.txt +1 -1
  211. package/dist/web/dist/recordings.html +1 -1
  212. package/dist/web/dist/recordings.txt +1 -1
  213. package/dist/web/dist/snapshots/placeholder/agent-prompt.html +1 -1
  214. package/dist/web/dist/snapshots/placeholder/agent-prompt.txt +1 -1
  215. package/dist/web/dist/talents.html +1 -1
  216. package/dist/web/dist/talents.txt +1 -1
  217. package/package.json +2 -2
  218. package/dist/web/dist/_next/static/chunks/app/logs/page-9f41d44fa1add723.js +0 -1
  219. /package/dist/web/dist/_next/static/{hqJ-7vr4X71DNfzZugHJ- → WdJr3NhHjlu5xN0qOap1k}/_buildManifest.js +0 -0
  220. /package/dist/web/dist/_next/static/{hqJ-7vr4X71DNfzZugHJ- → WdJr3NhHjlu5xN0qOap1k}/_ssgManifest.js +0 -0
@@ -0,0 +1,101 @@
1
+ import { Command } from 'commander';
2
+ import axios from 'axios';
3
+ import { loadConfig } from '../config.js';
4
+ import { getToken } from '../keychain.js';
5
+ import { printCommandError } from '../utils/command-error.js';
6
+ import { resolveCustomer } from './customer.js';
7
+ async function authedHeaders() {
8
+ const token = await getToken().catch(() => undefined);
9
+ return token ? { Authorization: `UTOKEN ${token}` } : {};
10
+ }
11
+ async function getMergedPreferences(scope, scopeId, customerId) {
12
+ const config = loadConfig();
13
+ const res = await axios.get(`${config.serverUrl}/preferences`, {
14
+ params: { scope, scopeId, ...(customerId ? { customerId } : {}) },
15
+ headers: await authedHeaders(),
16
+ proxy: false,
17
+ });
18
+ return res.data;
19
+ }
20
+ /**
21
+ * 记忆完整性探针:推荐前自检用。
22
+ * 输出 customer / positions / memory(备注+不合适原因) / preferences(三层) / missing(缺口清单,空=完整)。
23
+ */
24
+ export async function runCaseCheck(customerRef, jobExternalId) {
25
+ const related = await resolveCustomer(customerRef);
26
+ if (!related) {
27
+ return {
28
+ customer: { known: false, ref: customerRef },
29
+ positions: [],
30
+ memory: { remarksCount: 0, lastRemarkAt: null, rejectedReasons: [] },
31
+ preferences: { user: [], customer: [], case: [] },
32
+ missing: [`客户 "${customerRef}" 未入库 — 先运行: hunter-mate customer upsert --name "${customerRef}"`],
33
+ };
34
+ }
35
+ const { customer, jobCases, remarks } = related;
36
+ let jobCase = null;
37
+ if (jobExternalId) {
38
+ jobCase = jobCases.find((j) => j.externalId === jobExternalId || String(j.id) === String(jobExternalId)) ?? null;
39
+ }
40
+ else if (jobCases.length) {
41
+ // 未指定 --job 时取最新职位 case 做三层检查;否则 scope 退化为 customer,
42
+ // case 层永远空数组、case 层缺口检测失效(RS 模拟 R2 抓出)
43
+ jobCase = [...jobCases].sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())[0];
44
+ }
45
+ const prefs = await getMergedPreferences(jobCase ? 'case' : 'customer', jobCase ? jobCase.id : customer.id, customer.id).catch(() => ({ case: [], customer: [], user: [] }));
46
+ const feedbackRemarks = remarks.filter((r) => r.category === 'feedback');
47
+ const rejectedReasons = [
48
+ ...feedbackRemarks
49
+ .map((r) => ({ source: 'remark', content: String(r.content).slice(0, 200), at: r.createdAt })),
50
+ ...prefs.case
51
+ .filter((p) => p.dimension === 'reject_reason')
52
+ .map((p) => ({ source: 'preference', content: JSON.stringify(p.value), at: p.updatedAt })),
53
+ ];
54
+ const missing = [];
55
+ if (!jobCases.length)
56
+ missing.push(`客户 ${customer.canonicalName} 无关联职位 — 职位入库时带 --customer`);
57
+ if (jobCase && !prefs.case.length)
58
+ missing.push(`职位 "${jobCase.title ?? jobCase.externalId}" 无 case 层画像偏好 — 寻访后 preference set --scope case`);
59
+ if (!prefs.customer.length)
60
+ missing.push(`客户 ${customer.canonicalName} 无客户层偏好 — 沟通反馈后 preference set --scope customer`);
61
+ if (!remarks.length)
62
+ missing.push(`客户 ${customer.canonicalName} 无任何备注记录 — 推荐/反馈后 note add --target customer --id ${customer.id}`);
63
+ if (remarks.length && !feedbackRemarks.length)
64
+ missing.push('无 category=feedback 的反馈记录(过往推荐/不合适原因)');
65
+ return {
66
+ customer: {
67
+ known: true,
68
+ id: customer.id,
69
+ canonicalName: customer.canonicalName,
70
+ aliases: customer.aliases ?? [],
71
+ },
72
+ positions: jobCases.map((j) => ({ id: j.id, externalId: j.externalId, title: j.title, status: j.status, createdAt: j.createdAt })),
73
+ jobCase: jobCase ? { id: jobCase.id, externalId: jobCase.externalId, title: jobCase.title } : null,
74
+ memory: {
75
+ remarksCount: remarks.length,
76
+ lastRemarkAt: remarks[0]?.createdAt ?? null,
77
+ rejectedReasons,
78
+ },
79
+ preferences: prefs,
80
+ missing,
81
+ };
82
+ }
83
+ export const caseCommand = new Command('case').description('职位 case 记忆完整性检查(推荐前自检 / agent 流程判据)');
84
+ caseCommand
85
+ .command('check')
86
+ .requiredOption('--customer <nameOrId>', '客户名或 id')
87
+ .option('--job <externalId>', '职位 externalId 或 id(可选,细查单个 case)')
88
+ .option('--json', 'JSON 输出(默认)')
89
+ .action(async (opts) => {
90
+ try {
91
+ const report = await runCaseCheck(opts.customer, opts.job);
92
+ console.log(JSON.stringify(report, null, 2));
93
+ if (!opts.json && report.missing.length) {
94
+ console.error(`\n⚠ ${report.missing.length} 个记忆缺口(见 missing 字段)`);
95
+ }
96
+ }
97
+ catch (err) {
98
+ printCommandError('case:check-failed', err);
99
+ process.exit(1);
100
+ }
101
+ });
@@ -3,11 +3,15 @@ export interface ConfigOptions {
3
3
  serverUrl?: string;
4
4
  watchFolder?: string;
5
5
  lang?: string;
6
+ dataDir?: string;
7
+ harnessPath?: string;
6
8
  }
7
9
  export declare function runConfig(options: ConfigOptions): {
8
10
  serverUrl: string;
9
11
  saasUrl: string;
10
12
  lang: string;
11
13
  watchFolder?: string;
14
+ dataDir?: string;
15
+ harnessPath?: string;
12
16
  };
13
17
  export declare const configCommand: Command;
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { saveConfig } from '../config.js';
2
+ import { saveConfig, resolveDataDir } from '../config.js';
3
3
  import { i18n } from '../i18n/index.js';
4
4
  export function runConfig(options) {
5
5
  const updates = {};
@@ -9,6 +9,10 @@ export function runConfig(options) {
9
9
  updates.watchFolder = options.watchFolder;
10
10
  if (options.lang)
11
11
  updates.lang = options.lang;
12
+ if (options.dataDir)
13
+ updates.dataDir = options.dataDir;
14
+ if (options.harnessPath)
15
+ updates.harnessPath = options.harnessPath;
12
16
  const saved = saveConfig(updates);
13
17
  return saved;
14
18
  }
@@ -17,7 +21,10 @@ export const configCommand = new Command('config')
17
21
  .option('--server-url <url>', 'Server URL')
18
22
  .option('--watch-folder <path>', 'Watch folder for job cases')
19
23
  .option('--lang <lang>', 'Output language')
24
+ .option('--data-dir <path>', '重数据目录(数据库/日志/缓存等);迁移已有数据用 migrate-data-dir')
25
+ .option('--harness-path <path>', 'hunter-mate-harness 仓绝对路径(adapter sync 的 overlay 真相源;env HUNTER_MATE_HARNESS_PATH 优先)')
20
26
  .action((options) => {
21
27
  runConfig(options);
22
28
  console.log(i18n.t('config.saved'));
29
+ console.log(`dataDir: ${resolveDataDir()}`);
23
30
  });
@@ -0,0 +1,25 @@
1
+ import { Command } from 'commander';
2
+ export declare function runCustomerUpsert(opts: {
3
+ name: string;
4
+ alias?: string;
5
+ type?: string;
6
+ status?: string;
7
+ notes?: string;
8
+ }): Promise<{
9
+ customer: any;
10
+ created: boolean;
11
+ merged: boolean;
12
+ }>;
13
+ export declare function runCustomerList(query?: string): Promise<any[]>;
14
+ export declare function runCustomerRelated(id: number): Promise<{
15
+ customer: any;
16
+ jobCases: any[];
17
+ remarks: any[];
18
+ }>;
19
+ /** 按名称/别名/id 解析客户,返回 related 聚合;未命中返回 null */
20
+ export declare function resolveCustomer(nameOrId: string): Promise<{
21
+ customer: any;
22
+ jobCases: any[];
23
+ remarks: any[];
24
+ } | null>;
25
+ export declare const customerCommand: Command;
@@ -0,0 +1,137 @@
1
+ import { Command } from 'commander';
2
+ import axios from 'axios';
3
+ import { loadConfig } from '../config.js';
4
+ import { getToken } from '../keychain.js';
5
+ import { printCommandError } from '../utils/command-error.js';
6
+ async function authedHeaders() {
7
+ const token = await getToken().catch(() => undefined);
8
+ return token ? { Authorization: `UTOKEN ${token}` } : {};
9
+ }
10
+ function emit(jsonMode, data, human) {
11
+ if (jsonMode) {
12
+ console.log(JSON.stringify(data, null, 2));
13
+ }
14
+ else {
15
+ human();
16
+ }
17
+ }
18
+ export async function runCustomerUpsert(opts) {
19
+ const config = loadConfig();
20
+ const res = await axios.post(`${config.serverUrl}/customers`, {
21
+ name: opts.name,
22
+ aliases: opts.alias ? opts.alias.split(',').map((s) => s.trim()).filter(Boolean) : undefined,
23
+ typeStatus: opts.type || opts.status ? { type: opts.type, status: opts.status } : undefined,
24
+ notes: opts.notes,
25
+ }, { headers: await authedHeaders(), proxy: false });
26
+ return res.data;
27
+ }
28
+ export async function runCustomerList(query) {
29
+ const config = loadConfig();
30
+ const res = await axios.get(`${config.serverUrl}/customers`, {
31
+ params: query ? { query } : {},
32
+ headers: await authedHeaders(),
33
+ proxy: false,
34
+ });
35
+ return res.data;
36
+ }
37
+ export async function runCustomerRelated(id) {
38
+ const config = loadConfig();
39
+ const res = await axios.get(`${config.serverUrl}/customers/${id}/related`, {
40
+ headers: await authedHeaders(),
41
+ proxy: false,
42
+ });
43
+ return res.data;
44
+ }
45
+ /** 按名称/别名/id 解析客户,返回 related 聚合;未命中返回 null */
46
+ export async function resolveCustomer(nameOrId) {
47
+ if (/^\d+$/.test(nameOrId.trim())) {
48
+ try {
49
+ return await runCustomerRelated(parseInt(nameOrId, 10));
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ const hits = await runCustomerList(nameOrId);
56
+ if (!hits.length)
57
+ return null;
58
+ return runCustomerRelated(hits[0].id);
59
+ }
60
+ export const customerCommand = new Command('customer').description('客户(用人单位)归一化入库与查询 — 推荐记忆闭环锚点');
61
+ customerCommand
62
+ .command('upsert')
63
+ .requiredOption('--name <name>', '客户名(自动归一化去重,命中别名则合并)')
64
+ .option('--alias <a,b>', '额外别名,逗号分隔')
65
+ .option('--type <type>', '客户类型,如 enterprise/startup')
66
+ .option('--status <status>', '合作状态,如 active/paused')
67
+ .option('--notes <notes>', '备注')
68
+ .option('--json', 'JSON 输出(供 agent 解析)')
69
+ .action(async (opts) => {
70
+ try {
71
+ const result = await runCustomerUpsert(opts);
72
+ emit(opts.json, result, () => {
73
+ const { customer, created, merged } = result;
74
+ const tag = created ? '✓ 新建客户' : merged ? '✓ 命中已有客户(已合并别名/信息)' : '✓ 客户已存在';
75
+ console.log(`${tag}: [${customer.id}] ${customer.canonicalName}`);
76
+ if (customer.aliases?.length)
77
+ console.log(` 别名: ${customer.aliases.join(', ')}`);
78
+ });
79
+ }
80
+ catch (err) {
81
+ printCommandError('customer:upsert-failed', err);
82
+ process.exit(1);
83
+ }
84
+ });
85
+ customerCommand
86
+ .command('list')
87
+ .option('--query <q>', '按名称/别名搜索')
88
+ .option('--json', 'JSON 输出')
89
+ .action(async (opts) => {
90
+ try {
91
+ const list = await runCustomerList(opts.query);
92
+ emit(opts.json, list, () => {
93
+ if (!list.length) {
94
+ console.log('无客户记录');
95
+ return;
96
+ }
97
+ console.table(list.map((c) => ({
98
+ id: c.id,
99
+ name: c.canonicalName,
100
+ aliases: (c.aliases ?? []).join(','),
101
+ updatedAt: c.updatedAt,
102
+ })));
103
+ });
104
+ }
105
+ catch (err) {
106
+ printCommandError('customer:list-failed', err);
107
+ process.exit(1);
108
+ }
109
+ });
110
+ customerCommand
111
+ .command('show <id>')
112
+ .description('客户详情(含关联职位与备注)')
113
+ .option('--json', 'JSON 输出')
114
+ .action(async (id, opts) => {
115
+ try {
116
+ const data = await runCustomerRelated(parseInt(id, 10));
117
+ emit(opts.json, data, () => {
118
+ const { customer, jobCases, remarks } = data;
119
+ console.log(`[${customer.id}] ${customer.canonicalName}`);
120
+ if (customer.aliases?.length)
121
+ console.log(`别名: ${customer.aliases.join(', ')}`);
122
+ if (customer.notes)
123
+ console.log(`备注: ${customer.notes}`);
124
+ console.log(`职位(${jobCases.length}):`);
125
+ for (const j of jobCases)
126
+ console.log(` - [${j.id}] ${j.title ?? j.externalId} (${j.status})`);
127
+ console.log(`备注记录(${remarks.length}):`);
128
+ for (const r of remarks.slice(0, 10)) {
129
+ console.log(` - [${r.category}] ${String(r.content).slice(0, 80)}`);
130
+ }
131
+ });
132
+ }
133
+ catch (err) {
134
+ printCommandError('customer:show-failed', err);
135
+ process.exit(1);
136
+ }
137
+ });
@@ -9,7 +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
+ import { getToken, getTokenInfo } from '../keychain.js';
13
13
  const B = {
14
14
  reset: '\x1b[0m',
15
15
  bold: '\x1b[1m',
@@ -321,12 +321,24 @@ export async function runDoctor(opts) {
321
321
  hints.push('hunter-mate login');
322
322
  }
323
323
  else if (!serverUp) {
324
- loggedIn = true;
325
- checks.push({
326
- key: 'auth',
327
- status: 'ok',
328
- detail: '已登录 (server 未运行, 仅验证 token 存在性)',
329
- });
324
+ const issuer = (await getTokenInfo())?.issuer ?? 'unknown';
325
+ // hmh#43: SaaS 签发的 token 存在性 ok 但 server 起来后必 401,不能笼统报"已登录"
326
+ if (issuer === 'saas') {
327
+ checks.push({
328
+ key: 'auth',
329
+ status: 'warn',
330
+ detail: 'token 由 SaaS 签发(server 未运行,仅验证存在性);server 启动后数据面会 401,请重新登录',
331
+ });
332
+ hints.push('启动 server 后重新登录: hunter-mate login');
333
+ }
334
+ else {
335
+ loggedIn = true;
336
+ checks.push({
337
+ key: 'auth',
338
+ status: 'ok',
339
+ detail: '已登录 (server 未运行, 仅验证 token 存在性)',
340
+ });
341
+ }
330
342
  }
331
343
  else {
332
344
  try {
@@ -342,8 +354,20 @@ export async function runDoctor(opts) {
342
354
  const status = e?.response?.status;
343
355
  if (status === 401 || status === 403) {
344
356
  // 真实 fail: server 明确拒绝了这个 token
345
- checks.push({ key: 'auth', status: 'fail', detail: `token 已被 server 拒绝 (HTTP ${status}), 登录已过期或无权访问` });
346
- hints.push('重新登录: hunter-mate login');
357
+ const issuer = (await getTokenInfo())?.issuer ?? 'unknown';
358
+ if (issuer === 'saas') {
359
+ // hmh#43: 定向诊断,不再笼统报"登录已过期"
360
+ checks.push({
361
+ key: 'auth',
362
+ status: 'fail',
363
+ detail: `token 由 SaaS 签发,被本地 server 拒绝 (HTTP ${status});登录时本地 server 未运行`,
364
+ });
365
+ hints.push('保持 server 运行后重新登录: hunter-mate login');
366
+ }
367
+ else {
368
+ checks.push({ key: 'auth', status: 'fail', detail: `token 已被 server 拒绝 (HTTP ${status}), 登录已过期或无权访问` });
369
+ hints.push('重新登录: hunter-mate login');
370
+ }
347
371
  }
348
372
  else {
349
373
  // 网络/其他原因: 标 warn,不误报 fail
@@ -21,13 +21,15 @@ function http(baseUrl) {
21
21
  headers: { 'Content-Type': 'application/json' },
22
22
  });
23
23
  }
24
- /** 带鉴权的 client(server /extensions* 端点要求 UTOKEN,与 job/record/search 等模块一致)。 */
25
- async function httpAuth(baseUrl) {
24
+ /** 带鉴权的 client(server /extensions* 端点要求 UTOKEN,与 job/record/search 等模块一致)。
25
+ * D4(R3 定案):timeoutMs 可传 —— axios 层默认 60s,server 侧 timeoutMs(-t)更大时
26
+ * HTTP 层先弃,linkedin search 等多页编排(分钟级)走 CLI 必死;+15s 缓冲覆盖 server 处理+回传。 */
27
+ async function httpAuth(baseUrl, timeoutMs) {
26
28
  const { getToken } = await import('../keychain.js');
27
29
  const token = await getToken();
28
30
  return axios.create({
29
31
  baseURL: baseUrl,
30
- timeout: 60_000,
32
+ timeout: timeoutMs ?? 60_000,
31
33
  headers: {
32
34
  'Content-Type': 'application/json',
33
35
  ...(token ? { Authorization: `UTOKEN ${token}` } : {}),
@@ -37,7 +39,8 @@ async function httpAuth(baseUrl) {
37
39
  function requireServerUrl() {
38
40
  const config = loadConfig();
39
41
  if (!config.serverUrl) {
40
- printCommandError('extension:no-server-url', new Error('请先 hunter-mate config 设置 serverUrl'));
42
+ // 同步上下文:手工构造的 Error 无 response.status,不会触发 issuer 诊断分支
43
+ void printCommandError('extension:no-server-url', new Error('请先 hunter-mate config 设置 serverUrl'));
41
44
  process.exit(1);
42
45
  }
43
46
  return config.serverUrl;
@@ -126,7 +129,7 @@ export function ensureEnvReady() {
126
129
  catch { /* ignore */ }
127
130
  }
128
131
  if (!serverUp) {
129
- printCommandError('extension:server-start-failed', new Error('请手动执行 hunter-mate start -f'));
132
+ await printCommandError('extension:server-start-failed', new Error('请手动执行 hunter-mate start -f'));
130
133
  process.exit(1);
131
134
  }
132
135
  // 2) 检查扩展是否安装
@@ -141,7 +144,7 @@ export function ensureEnvReady() {
141
144
  const r = runInstall(init.extensionDir);
142
145
  if (!r.source) {
143
146
  // 内置 dist 不存在, 无法自动安装
144
- printCommandError('extension:auto-install-failed', new Error('内置 extension dist 不存在,请在 hunter-mate monorepo 执行 pnpm --filter @hunter-mate/extension build,或手动 hunter-mate install'));
147
+ await printCommandError('extension:auto-install-failed', new Error('内置 extension dist 不存在,请在 hunter-mate monorepo 执行 pnpm --filter @hunter-mate/extension build,或手动 hunter-mate install'));
145
148
  process.exit(1);
146
149
  }
147
150
  console.log(`${B.green}✓ 扩展已自动安装到 ${r.target}${B.reset}`);
@@ -183,7 +186,7 @@ export function ensureEnvReady() {
183
186
  }
184
187
  catch {
185
188
  // /extensions 端点不可达
186
- printCommandError('extension:check-failed', new Error('Server 可能未正确启动, 请执行 hunter-mate start -f'));
189
+ await printCommandError('extension:check-failed', new Error('Server 可能未正确启动, 请执行 hunter-mate start -f'));
187
190
  process.exit(1);
188
191
  }
189
192
  })();
@@ -247,7 +250,7 @@ async function withEnvCheck(fn) {
247
250
  await fn();
248
251
  }
249
252
  catch (err) {
250
- printCommandError('extension:exec-failed', err);
253
+ await printCommandError('extension:exec-failed', err);
251
254
  const msg = err.response?.data?.message ?? err.response?.data?.error ?? err.message;
252
255
  if (msg.includes('no Hunter Mate Chrome extension registered') || msg.includes('no Hunter Mate')) {
253
256
  const init = await import('./init.js').then((m) => m.runInit());
@@ -279,7 +282,7 @@ async function cmdControl(command, argsJson, opts) {
279
282
  process.exit(1);
280
283
  }
281
284
  }
282
- const r = await (await httpAuth(serverUrl)).post('/extensions/control', {
285
+ const r = await (await httpAuth(serverUrl, opts.timeout ? Number(opts.timeout) + 15_000 : undefined)).post('/extensions/control', {
283
286
  command,
284
287
  params,
285
288
  timeoutMs: opts.timeout ? Number(opts.timeout) : undefined,
@@ -292,7 +295,7 @@ async function cmdControl(command, argsJson, opts) {
292
295
  async function cmdGoto(url, opts) {
293
296
  return withEnvCheck(async () => {
294
297
  const serverUrl = requireServerUrl();
295
- const r = await (await httpAuth(serverUrl)).post('/extensions/goto', {
298
+ const r = await (await httpAuth(serverUrl, opts.timeout ? Number(opts.timeout) + 15_000 : undefined)).post('/extensions/goto', {
296
299
  url,
297
300
  waitUntil: opts.waitUntil,
298
301
  timeoutMs: opts.timeout ? Number(opts.timeout) : undefined,
@@ -305,7 +308,7 @@ async function cmdGoto(url, opts) {
305
308
  async function cmdEval(expression, opts) {
306
309
  return withEnvCheck(async () => {
307
310
  const serverUrl = requireServerUrl();
308
- const r = await (await httpAuth(serverUrl)).post('/extensions/eval', {
311
+ const r = await (await httpAuth(serverUrl, opts.timeout ? Number(opts.timeout) + 15_000 : undefined)).post('/extensions/eval', {
309
312
  expression,
310
313
  timeoutMs: opts.timeout ? Number(opts.timeout) : undefined,
311
314
  extensionId: opts.extensionId,
@@ -317,7 +320,7 @@ async function cmdEval(expression, opts) {
317
320
  async function cmdScreenshot(opts) {
318
321
  return withEnvCheck(async () => {
319
322
  const serverUrl = requireServerUrl();
320
- const r = await (await httpAuth(serverUrl)).post('/extensions/screenshot', {
323
+ const r = await (await httpAuth(serverUrl, opts.timeout ? Number(opts.timeout) + 15_000 : undefined)).post('/extensions/screenshot', {
321
324
  fullPage: opts.full ? true : undefined,
322
325
  selector: opts.selector,
323
326
  format: opts.imageFormat ?? 'png',
@@ -354,7 +357,7 @@ async function cmdRun(site, command, opts) {
354
357
  timeoutMs: opts.timeout ? Number(opts.timeout) : undefined,
355
358
  extensionId: opts.extensionId,
356
359
  };
357
- const r = await (await httpAuth(serverUrl)).post(endpoint, payload);
360
+ const r = await (await httpAuth(serverUrl, opts.timeout ? Number(opts.timeout) + 15_000 : undefined)).post(endpoint, payload);
358
361
  printResult(r.data, opts.format);
359
362
  });
360
363
  }
@@ -382,7 +385,7 @@ extensionCommand
382
385
  console.log(`\n${B.green}✓ Hunter Mate 扩展已连接 (${count} 个浏览器实例)${B.reset}\n`);
383
386
  }
384
387
  catch (err) {
385
- printCommandError('extension:check-error', err);
388
+ await printCommandError('extension:check-error', err);
386
389
  process.exit(1);
387
390
  }
388
391
  });
@@ -410,6 +413,6 @@ common(extensionCommand
410
413
  .action(async (opts) => cmdScreenshot(opts));
411
414
  common(extensionCommand
412
415
  .command('run <site> <command>')
413
- .description('站点适配器命令 (对应 chromepilot run): linkedin search|extract|invite 或 resume extract'))
416
+ .description('站点适配器命令 (对应 chromepilot run): linkedin search|extract|invite、liepin search|extract 或 resume extract'))
414
417
  .option('--args <json>', '命令参数 JSON 字符串')
415
418
  .action(async (site, command, opts) => cmdRun(site, command, opts));
@@ -1,8 +1,27 @@
1
1
  import { Command } from 'commander';
2
2
  /**
3
- * 通知所有已连接扩展 reload(自愈主链路):
4
- * 1) 优先 HTTP POST /system/broadcast-extension(返回明确 broadcast 计数和错误)
5
- * 2) Fallback: 直连 WS /ws 发 {type:'broadcast', payload:{type:'reload'}}(兼容旧 server 无 HTTP 端点)
3
+ * FID-092:扫描扩展目录最新 mtime,等待其静止——reload 广播前确认 dist 已写完。
4
+ * 背景:R5 实机 reload 后 3 秒 dist 仍在写入,Chrome 重读读到写了一半的
5
+ * service-worker.js → SW eval 崩溃 → 无 alarm 监听/无 offscreen/零重连,
6
+ * 所有自愈路径全部失效,只能人工点。Windows 兼容:纯 node fs,无 shell。
7
+ */
8
+ export declare function waitForExtensionDirQuiet(dir: string, opts?: {
9
+ quietMs?: number;
10
+ maxWaitMs?: number;
11
+ pollMs?: number;
12
+ }): Promise<{
13
+ quiet: boolean;
14
+ waitedMs: number;
15
+ newestMtimeMs: number;
16
+ }>;
17
+ /** FID-092:轮询 server 已连接扩展数(>=1 即判定 reload 后已回连)。 */
18
+ export declare function extensionConnectedCount(serverUrl: string, timeoutMs?: number): Promise<number>;
19
+ /**
20
+ * 通知所有已连接扩展 reload(自愈主链路),FID-092 三段式:
21
+ * 1) dist 静止确认(防 reload 读到写一半的扩展文件)
22
+ * 2) 广播(broadcastReload):优先 HTTP POST /system/broadcast-extension;
23
+ * 旧 server 无该端点时退回直连 WS /ws 广播
24
+ * 3) 健康校验 + 自动重试(广播后 12s 内未回连 → 再广播,最多 3 次)
6
25
  */
7
26
  export declare function runReload(timeoutMs?: number): Promise<{
8
27
  broadcast: number;