hunter-mate 0.1.6 → 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 (261) hide show
  1. package/dist/assets/extension/background/service-worker.js +911 -159
  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 +12 -4
  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 +12 -1
  12. package/dist/assets/server/app.module.js.map +1 -1
  13. package/dist/assets/server/auth/auth.controller.d.ts +7 -0
  14. package/dist/assets/server/auth/auth.controller.js +20 -0
  15. package/dist/assets/server/auth/auth.controller.js.map +1 -1
  16. package/dist/assets/server/auth/auth.middleware.js +9 -1
  17. package/dist/assets/server/auth/auth.middleware.js.map +1 -1
  18. package/dist/assets/server/auth/auth.service.d.ts +11 -0
  19. package/dist/assets/server/auth/auth.service.js +58 -1
  20. package/dist/assets/server/auth/auth.service.js.map +1 -1
  21. package/dist/assets/server/build-info.json +1 -1
  22. package/dist/assets/server/customer/customer.controller.d.ts +18 -0
  23. package/dist/assets/server/customer/customer.controller.js +81 -0
  24. package/dist/assets/server/customer/customer.controller.js.map +1 -0
  25. package/dist/assets/server/customer/customer.entity.d.ts +17 -0
  26. package/dist/assets/server/customer/customer.entity.js +82 -0
  27. package/dist/assets/server/customer/customer.entity.js.map +1 -0
  28. package/dist/assets/server/customer/customer.module.d.ts +2 -0
  29. package/dist/assets/server/customer/customer.module.js +28 -0
  30. package/dist/assets/server/customer/customer.module.js.map +1 -0
  31. package/dist/assets/server/customer/customer.service.d.ts +36 -0
  32. package/dist/assets/server/customer/customer.service.js +200 -0
  33. package/dist/assets/server/customer/customer.service.js.map +1 -0
  34. package/dist/assets/server/db/schema-validator.service.d.ts +2 -0
  35. package/dist/assets/server/db/schema-validator.service.js +32 -1
  36. package/dist/assets/server/db/schema-validator.service.js.map +1 -1
  37. package/dist/assets/server/db/sqlite.module.d.ts +1 -0
  38. package/dist/assets/server/db/sqlite.module.js +12 -2
  39. package/dist/assets/server/db/sqlite.module.js.map +1 -1
  40. package/dist/assets/server/devops/devops-sink-init.d.ts +1 -14
  41. package/dist/assets/server/devops/devops-sink-init.js +3 -8
  42. package/dist/assets/server/devops/devops-sink-init.js.map +1 -1
  43. package/dist/assets/server/extension/extension-control.controller.d.ts +15 -0
  44. package/dist/assets/server/extension/extension-control.controller.js +42 -2
  45. package/dist/assets/server/extension/extension-control.controller.js.map +1 -1
  46. package/dist/assets/server/extension/extension-control.service.d.ts +14 -1
  47. package/dist/assets/server/extension/extension-control.service.js +65 -2
  48. package/dist/assets/server/extension/extension-control.service.js.map +1 -1
  49. package/dist/assets/server/extraction/adapter.service.d.ts +3 -0
  50. package/dist/assets/server/extraction/adapter.service.js +127 -6
  51. package/dist/assets/server/extraction/adapter.service.js.map +1 -1
  52. package/dist/assets/server/extraction/anomaly-report.service.js +1 -1
  53. package/dist/assets/server/extraction/anomaly-report.service.js.map +1 -1
  54. package/dist/assets/server/extraction/entities/adapter-rule.entity.d.ts +2 -0
  55. package/dist/assets/server/extraction/entities/adapter-rule.entity.js +10 -0
  56. package/dist/assets/server/extraction/entities/adapter-rule.entity.js.map +1 -1
  57. package/dist/assets/server/extraction/extraction-entity.controller.d.ts +21 -3
  58. package/dist/assets/server/extraction/extraction-entity.controller.js +44 -9
  59. package/dist/assets/server/extraction/extraction-entity.controller.js.map +1 -1
  60. package/dist/assets/server/extraction/extraction.module.js +13 -3
  61. package/dist/assets/server/extraction/extraction.module.js.map +1 -1
  62. package/dist/assets/server/extraction/extraction.service.d.ts +4 -1
  63. package/dist/assets/server/extraction/extraction.service.js +59 -4
  64. package/dist/assets/server/extraction/extraction.service.js.map +1 -1
  65. package/dist/assets/server/extraction/harness-overlay.service.d.ts +41 -0
  66. package/dist/assets/server/extraction/harness-overlay.service.js +319 -0
  67. package/dist/assets/server/extraction/harness-overlay.service.js.map +1 -0
  68. package/dist/assets/server/extraction/liepin-extractor.d.ts +1 -0
  69. package/dist/assets/server/extraction/liepin-extractor.js +172 -0
  70. package/dist/assets/server/extraction/liepin-extractor.js.map +1 -0
  71. package/dist/assets/server/extraction/script-rule.js +4 -0
  72. package/dist/assets/server/extraction/script-rule.js.map +1 -1
  73. package/dist/assets/server/governance/action-audit.entity.d.ts +12 -0
  74. package/dist/assets/server/governance/action-audit.entity.js +66 -0
  75. package/dist/assets/server/governance/action-audit.entity.js.map +1 -0
  76. package/dist/assets/server/governance/governance.controller.d.ts +14 -0
  77. package/dist/assets/server/governance/governance.controller.js +77 -0
  78. package/dist/assets/server/governance/governance.controller.js.map +1 -0
  79. package/dist/assets/server/governance/governance.ingester.d.ts +20 -0
  80. package/dist/assets/server/governance/governance.ingester.js +114 -0
  81. package/dist/assets/server/governance/governance.ingester.js.map +1 -0
  82. package/dist/assets/server/governance/governance.module.d.ts +2 -0
  83. package/dist/assets/server/governance/governance.module.js +26 -0
  84. package/dist/assets/server/governance/governance.module.js.map +1 -0
  85. package/dist/assets/server/governance/governance.service.d.ts +114 -0
  86. package/dist/assets/server/governance/governance.service.js +356 -0
  87. package/dist/assets/server/governance/governance.service.js.map +1 -0
  88. package/dist/assets/server/jobcase/jobcase.controller.d.ts +1 -0
  89. package/dist/assets/server/jobcase/jobcase.entity.d.ts +1 -0
  90. package/dist/assets/server/jobcase/jobcase.entity.js +6 -0
  91. package/dist/assets/server/jobcase/jobcase.entity.js.map +1 -1
  92. package/dist/assets/server/jobcase/jobcase.service.d.ts +2 -0
  93. package/dist/assets/server/jobcase/jobcase.service.js +2 -1
  94. package/dist/assets/server/jobcase/jobcase.service.js.map +1 -1
  95. package/dist/assets/server/main.js +1 -0
  96. package/dist/assets/server/main.js.map +1 -1
  97. package/dist/assets/server/package.json +5 -4
  98. package/dist/assets/server/preference/preference.controller.d.ts +14 -0
  99. package/dist/assets/server/preference/preference.controller.js +66 -0
  100. package/dist/assets/server/preference/preference.controller.js.map +1 -0
  101. package/dist/assets/server/preference/preference.entity.d.ts +13 -0
  102. package/dist/assets/server/preference/preference.entity.js +77 -0
  103. package/dist/assets/server/preference/preference.entity.js.map +1 -0
  104. package/dist/assets/server/preference/preference.module.d.ts +2 -0
  105. package/dist/assets/server/preference/preference.module.js +26 -0
  106. package/dist/assets/server/preference/preference.module.js.map +1 -0
  107. package/dist/assets/server/preference/preference.service.d.ts +33 -0
  108. package/dist/assets/server/preference/preference.service.js +122 -0
  109. package/dist/assets/server/preference/preference.service.js.map +1 -0
  110. package/dist/assets/server/recording/recording-event-queue.service.d.ts +26 -0
  111. package/dist/assets/server/recording/recording-event-queue.service.js +115 -0
  112. package/dist/assets/server/recording/recording-event-queue.service.js.map +1 -0
  113. package/dist/assets/server/recording/recording-retention.service.d.ts +38 -0
  114. package/dist/assets/server/recording/recording-retention.service.js +174 -0
  115. package/dist/assets/server/recording/recording-retention.service.js.map +1 -0
  116. package/dist/assets/server/recording/recording-snapshot.service.d.ts +1 -0
  117. package/dist/assets/server/recording/recording-snapshot.service.js +4 -0
  118. package/dist/assets/server/recording/recording-snapshot.service.js.map +1 -1
  119. package/dist/assets/server/recording/recording.controller.d.ts +21 -1
  120. package/dist/assets/server/recording/recording.controller.js +37 -2
  121. package/dist/assets/server/recording/recording.controller.js.map +1 -1
  122. package/dist/assets/server/recording/recording.gateway.js +11 -0
  123. package/dist/assets/server/recording/recording.gateway.js.map +1 -1
  124. package/dist/assets/server/recording/recording.module.js +16 -2
  125. package/dist/assets/server/recording/recording.module.js.map +1 -1
  126. package/dist/assets/server/recording/recording.service.d.ts +10 -1
  127. package/dist/assets/server/recording/recording.service.js +31 -6
  128. package/dist/assets/server/recording/recording.service.js.map +1 -1
  129. package/dist/assets/server/remark/remark.controller.d.ts +1 -1
  130. package/dist/assets/server/remark/remark.controller.js +1 -10
  131. package/dist/assets/server/remark/remark.controller.js.map +1 -1
  132. package/dist/assets/server/remark/remark.entity.d.ts +1 -1
  133. package/dist/assets/server/remark/remark.entity.js.map +1 -1
  134. package/dist/assets/server/site/entities/site-registry.entity.d.ts +1 -1
  135. package/dist/assets/server/site/entities/site-registry.entity.js +1 -1
  136. package/dist/assets/server/site/entities/site-registry.entity.js.map +1 -1
  137. package/dist/assets/server/site/site.controller.d.ts +1 -1
  138. package/dist/assets/server/site/site.service.d.ts +5 -1
  139. package/dist/assets/server/site/site.service.js +13 -0
  140. package/dist/assets/server/site/site.service.js.map +1 -1
  141. package/dist/assets/server/system/system.controller.d.ts +1 -0
  142. package/dist/assets/server/system/system.controller.js +1 -1
  143. package/dist/assets/server/system/system.controller.js.map +1 -1
  144. package/dist/assets/server/system/system.service.d.ts +1 -0
  145. package/dist/assets/server/system/system.service.js +3 -2
  146. package/dist/assets/server/system/system.service.js.map +1 -1
  147. package/dist/cache/db.js +6 -6
  148. package/dist/commands/addons.js +2 -1
  149. package/dist/commands/cache.js +3 -4
  150. package/dist/commands/case.d.ts +67 -0
  151. package/dist/commands/case.js +101 -0
  152. package/dist/commands/config.d.ts +4 -0
  153. package/dist/commands/config.js +8 -1
  154. package/dist/commands/customer.d.ts +25 -0
  155. package/dist/commands/customer.js +137 -0
  156. package/dist/commands/doctor.js +119 -13
  157. package/dist/commands/extension.js +26 -32
  158. package/dist/commands/extras.d.ts +22 -3
  159. package/dist/commands/extras.js +116 -7
  160. package/dist/commands/init.d.ts +1 -1
  161. package/dist/commands/init.js +3 -4
  162. package/dist/commands/issue-env.d.ts +23 -0
  163. package/dist/commands/issue-env.js +101 -0
  164. package/dist/commands/job.d.ts +6 -2
  165. package/dist/commands/job.js +75 -20
  166. package/dist/commands/login.d.ts +19 -0
  167. package/dist/commands/login.js +85 -13
  168. package/dist/commands/migrate.d.ts +32 -0
  169. package/dist/commands/migrate.js +116 -0
  170. package/dist/commands/note.d.ts +3 -0
  171. package/dist/commands/note.js +75 -0
  172. package/dist/commands/ops.js +82 -15
  173. package/dist/commands/preference.d.ts +6 -0
  174. package/dist/commands/preference.js +80 -0
  175. package/dist/commands/record.js +2 -4
  176. package/dist/commands/report.js +2 -4
  177. package/dist/commands/search.js +9 -2
  178. package/dist/commands/server.d.ts +9 -2
  179. package/dist/commands/server.js +118 -19
  180. package/dist/commands/status.d.ts +4 -0
  181. package/dist/commands/status.js +16 -6
  182. package/dist/commands/update.js +8 -9
  183. package/dist/config.d.ts +21 -0
  184. package/dist/config.js +27 -0
  185. package/dist/i18n/locales/en-US.json +19 -1
  186. package/dist/i18n/locales/zh-CN.json +19 -1
  187. package/dist/index.js +52 -2
  188. package/dist/keychain.d.ts +16 -1
  189. package/dist/keychain.js +43 -16
  190. package/dist/logger.js +2 -2
  191. package/dist/updater.d.ts +1 -1
  192. package/dist/updater.js +24 -7
  193. package/dist/utils/command-error.d.ts +11 -0
  194. package/dist/utils/command-error.js +47 -0
  195. package/dist/utils/link.d.ts +18 -0
  196. package/dist/utils/link.js +83 -0
  197. package/dist/utils/proxy-detect.d.ts +20 -0
  198. package/dist/utils/proxy-detect.js +96 -0
  199. package/dist/utils/script-path.d.ts +5 -0
  200. package/dist/utils/script-path.js +27 -0
  201. package/dist/version.js +34 -9
  202. package/dist/watchdog.d.ts +23 -1
  203. package/dist/watchdog.js +97 -6
  204. package/dist/web/dist/404.html +1 -0
  205. package/dist/web/dist/_next/static/WdJr3NhHjlu5xN0qOap1k/_buildManifest.js +1 -0
  206. package/dist/web/dist/_next/static/WdJr3NhHjlu5xN0qOap1k/_ssgManifest.js +1 -0
  207. package/dist/web/dist/_next/static/chunks/166.f30be223f694f8ee.js +1 -0
  208. package/dist/web/dist/_next/static/chunks/1e6d5a0c.0488932e17ecdf24.js +15 -0
  209. package/dist/web/dist/_next/static/chunks/539-5d0e8c5c238c56f8.js +1 -0
  210. package/dist/web/dist/_next/static/chunks/589-d632233641f35681.js +1 -0
  211. package/dist/web/dist/_next/static/chunks/671.3b8e9cc96b25b6ea.js +1 -0
  212. package/dist/web/dist/_next/static/chunks/935-99f57b7d4151d9e7.js +5 -0
  213. package/dist/web/dist/_next/static/chunks/a7067448-96dd4b8457c447e8.js +1 -0
  214. package/dist/web/dist/_next/static/chunks/app/_not-found/page-fdf1a13a7656886a.js +1 -0
  215. package/dist/web/dist/_next/static/chunks/app/auth/verify/page-8e5cff53d6f7d8e1.js +1 -0
  216. package/dist/web/dist/_next/static/chunks/app/companies/page-e38a69e1929d0953.js +1 -0
  217. package/dist/web/dist/_next/static/chunks/app/entity/page-250f8a9b6d91ab19.js +1 -0
  218. package/dist/web/dist/_next/static/chunks/app/jobs/page-6a281776fec065f6.js +1 -0
  219. package/dist/web/dist/_next/static/chunks/app/layout-83e623e973c1693f.js +1 -0
  220. package/dist/web/dist/_next/static/chunks/app/logs/page-d478031d49f7ba00.js +1 -0
  221. package/dist/web/dist/_next/static/chunks/app/matching/jobs/[id]/talents/page-f57f4b7529823601.js +1 -0
  222. package/dist/web/dist/_next/static/chunks/app/matching/portraits/[id]/jobs/page-614d1e62d7b9d215.js +1 -0
  223. package/dist/web/dist/_next/static/chunks/app/page-e3392de08ae81fba.js +1 -0
  224. package/dist/web/dist/_next/static/chunks/app/parselog/page-58d3acece95b4089.js +1 -0
  225. package/dist/web/dist/_next/static/chunks/app/recordings/page-e2e2e3c3a64083fb.js +1 -0
  226. package/dist/web/dist/_next/static/chunks/app/snapshots/[id]/agent-prompt/page-fd3b7d7373b8f8b7.js +1 -0
  227. package/dist/web/dist/_next/static/chunks/app/talents/page-30748338da20d7e0.js +1 -0
  228. package/dist/web/dist/_next/static/chunks/framework-b3a558b17a070965.js +1 -0
  229. package/dist/web/dist/_next/static/chunks/main-5a4e0cd75dd7f04b.js +1 -0
  230. package/dist/web/dist/_next/static/chunks/main-app-80b038a312917d90.js +1 -0
  231. package/dist/web/dist/_next/static/chunks/pages/_app-da7d628f49271ece.js +4 -0
  232. package/dist/web/dist/_next/static/chunks/pages/_error-17756f67e0a11350.js +1 -0
  233. package/dist/web/dist/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  234. package/dist/web/dist/_next/static/chunks/webpack-1784baecc6fdcf0b.js +1 -0
  235. package/dist/web/dist/_next/static/css/db881c5b3072b88a.css +3 -0
  236. package/dist/web/dist/_next/static/css/f60d210841fab76b.css +1 -0
  237. package/dist/web/dist/auth/verify.html +1 -0
  238. package/dist/web/dist/auth/verify.txt +8 -0
  239. package/dist/web/dist/companies.html +1 -0
  240. package/dist/web/dist/companies.txt +7 -0
  241. package/dist/web/dist/entity.html +1 -0
  242. package/dist/web/dist/entity.txt +8 -0
  243. package/dist/web/dist/index.html +1 -0
  244. package/dist/web/dist/index.txt +8 -0
  245. package/dist/web/dist/jobs.html +1 -0
  246. package/dist/web/dist/jobs.txt +7 -0
  247. package/dist/web/dist/logs.html +1 -0
  248. package/dist/web/dist/logs.txt +8 -0
  249. package/dist/web/dist/matching/jobs/placeholder/talents.html +1 -0
  250. package/dist/web/dist/matching/jobs/placeholder/talents.txt +8 -0
  251. package/dist/web/dist/matching/portraits/placeholder/jobs.html +1 -0
  252. package/dist/web/dist/matching/portraits/placeholder/jobs.txt +8 -0
  253. package/dist/web/dist/parselog.html +1 -0
  254. package/dist/web/dist/parselog.txt +8 -0
  255. package/dist/web/dist/recordings.html +1 -0
  256. package/dist/web/dist/recordings.txt +8 -0
  257. package/dist/web/dist/snapshots/placeholder/agent-prompt.html +1 -0
  258. package/dist/web/dist/snapshots/placeholder/agent-prompt.txt +8 -0
  259. package/dist/web/dist/talents.html +1 -0
  260. package/dist/web/dist/talents.txt +7 -0
  261. package/package.json +26 -6
@@ -2,29 +2,28 @@ import { Command } from 'commander';
2
2
  import axios from 'axios';
3
3
  import * as fs from 'fs';
4
4
  import * as path from 'path';
5
- import { loadConfig } from '../config.js';
5
+ import { loadConfig, resolveDataDir } from '../config.js';
6
6
  import { getToken } from '../keychain.js';
7
7
  import { i18n } from '../i18n/index.js';
8
8
  import { getCliLogger } from '../logger.js';
9
9
  import chalk from 'chalk';
10
10
  import { readInstalledVersions, ensureRuntimeConsistency } from '../version.js';
11
11
  import { isDevLinked, npmLatestVersion, npmSelfUpdate } from '../updater.js';
12
- const HOME = process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
13
- const RULES_DIR = path.join(HOME, '.hunter-mate', 'rules');
14
- const VERSION_FILE = path.join(RULES_DIR, 'version.json');
12
+ const RULES_DIR = () => path.join(resolveDataDir(), 'rules');
13
+ const VERSION_FILE = () => path.join(RULES_DIR(), 'version.json');
15
14
  function loadLocalVersion() {
16
- if (!fs.existsSync(VERSION_FILE))
15
+ if (!fs.existsSync(VERSION_FILE()))
17
16
  return null;
18
17
  try {
19
- return JSON.parse(fs.readFileSync(VERSION_FILE, 'utf-8'));
18
+ return JSON.parse(fs.readFileSync(VERSION_FILE(), 'utf-8'));
20
19
  }
21
20
  catch {
22
21
  return null;
23
22
  }
24
23
  }
25
24
  function saveLocalVersion(version) {
26
- fs.mkdirSync(RULES_DIR, { recursive: true });
27
- fs.writeFileSync(VERSION_FILE, JSON.stringify({ version, updatedAt: new Date().toISOString() }, null, 2));
25
+ fs.mkdirSync(RULES_DIR(), { recursive: true });
26
+ fs.writeFileSync(VERSION_FILE(), JSON.stringify({ version, updatedAt: new Date().toISOString() }, null, 2));
28
27
  }
29
28
  export async function runUpdate() {
30
29
  const config = loadConfig();
@@ -105,7 +104,7 @@ export const updateCommand = new Command('update')
105
104
  }
106
105
  else {
107
106
  console.log(chalk.cyan(`CLI 自更新: ${installed.cli} → ${latest}`));
108
- const r = npmSelfUpdate(npmPackage);
107
+ const r = await npmSelfUpdate(npmPackage);
109
108
  console.log(r.ok ? chalk.green(r.msg) : chalk.yellow(r.msg));
110
109
  }
111
110
  }
package/dist/config.d.ts CHANGED
@@ -5,9 +5,28 @@ export interface HunterMateConfig {
5
5
  saasUrl: string;
6
6
  lang: string;
7
7
  watchFolder?: string;
8
+ /**
9
+ * 重数据目录(数据库/日志/缓存/扩展/CLI 安装树等),不含 config.json 本身。
10
+ * 通过 `hunter-mate migrate-data-dir <dir>` 迁移;留空表示默认 ~/.hunter-mate。
11
+ */
12
+ dataDir?: string;
13
+ /**
14
+ * hunter-mate-harness 仓绝对路径(外部 adapters overlay 真相源)。
15
+ * `hunter-mate adapter sync` 读取;env HUNTER_MATE_HARNESS_PATH 优先级更高。 @fid FID-081-adapter-overlay-suite
16
+ */
17
+ harnessPath?: string;
8
18
  }
9
19
  export declare const CONFIG_DIR: () => string;
10
20
  export declare const CONFIG_FILE: () => string;
21
+ /**
22
+ * 重数据目录解析(FID-071):
23
+ * HM_DATA_DIR(env,进程级覆盖,CLI spawn server 时注入) > config.dataDir(用户配置) > 默认 CONFIG_DIR。
24
+ * config.json 本身始终留在 CONFIG_DIR(KB 级不迁移),保证任何时刻都能定位配置。
25
+ * 注意:每次调用都实时读取(不缓存),config --data-dir 写入后同一进程内立即生效。
26
+ */
27
+ export declare function resolveDataDir(): string;
28
+ /** resolveDataDir 的别名,语义化表达"数据目录"(与 CONFIG_DIR 配置目录区分) */
29
+ export declare const DATA_DIR: typeof resolveDataDir;
11
30
  /**
12
31
  * 已知废弃的 URL 路径前缀(按部署形态演进顺序追加)。
13
32
  * 每当 nginx / 服务端路径收敛(如子路径 → 根路径)时,把旧前缀加到这里,
@@ -43,4 +62,6 @@ export declare function saveConfig(config: Partial<HunterMateConfig>): {
43
62
  saasUrl: string;
44
63
  lang: string;
45
64
  watchFolder?: string;
65
+ dataDir?: string;
66
+ harnessPath?: string;
46
67
  };
package/dist/config.js CHANGED
@@ -4,6 +4,29 @@ import { createJsonConfigStore } from '@chromepilot/kit';
4
4
  const HOME = () => process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
5
5
  export const CONFIG_DIR = () => path.join(HOME(), '.hunter-mate');
6
6
  export const CONFIG_FILE = () => path.join(CONFIG_DIR(), 'config.json');
7
+ /**
8
+ * 重数据目录解析(FID-071):
9
+ * HM_DATA_DIR(env,进程级覆盖,CLI spawn server 时注入) > config.dataDir(用户配置) > 默认 CONFIG_DIR。
10
+ * config.json 本身始终留在 CONFIG_DIR(KB 级不迁移),保证任何时刻都能定位配置。
11
+ * 注意:每次调用都实时读取(不缓存),config --data-dir 写入后同一进程内立即生效。
12
+ */
13
+ export function resolveDataDir() {
14
+ if (process.env.HM_DATA_DIR && process.env.HM_DATA_DIR.trim()) {
15
+ return path.resolve(process.env.HM_DATA_DIR);
16
+ }
17
+ try {
18
+ const raw = store.load();
19
+ if (raw && typeof raw.dataDir === 'string' && raw.dataDir.trim()) {
20
+ return path.resolve(raw.dataDir);
21
+ }
22
+ }
23
+ catch {
24
+ /* 配置不可读时回落默认目录 */
25
+ }
26
+ return CONFIG_DIR();
27
+ }
28
+ /** resolveDataDir 的别名,语义化表达"数据目录"(与 CONFIG_DIR 配置目录区分) */
29
+ export const DATA_DIR = resolveDataDir;
7
30
  /**
8
31
  * 已知废弃的 URL 路径前缀(按部署形态演进顺序追加)。
9
32
  * 每当 nginx / 服务端路径收敛(如子路径 → 根路径)时,把旧前缀加到这里,
@@ -70,6 +93,10 @@ export function ensureConfigDir() {
70
93
  if (!fs.existsSync(CONFIG_DIR())) {
71
94
  fs.mkdirSync(CONFIG_DIR(), { recursive: true });
72
95
  }
96
+ const dataDir = resolveDataDir();
97
+ if (dataDir !== CONFIG_DIR() && !fs.existsSync(dataDir)) {
98
+ fs.mkdirSync(dataDir, { recursive: true });
99
+ }
73
100
  }
74
101
  export function loadConfig() {
75
102
  const raw = store.load();
@@ -57,5 +57,23 @@
57
57
  "purge.done.removed": "Removed",
58
58
  "purge.done.server": "Server",
59
59
  "purge.done.next": "To re-initialize, run",
60
- "purge.done.nothing": "Nothing to purge"
60
+ "purge.done.nothing": "Nothing to purge",
61
+ "login.serverOfflineSaasFallback": "Warning: local server is not running; logged in via SaaS directly. Start the server and run login again for local data operations.",
62
+ "login.serverOfflineReloginHint": "Note: the saved SaaS token will be rejected (401) by local server protected routes.",
63
+ "login.selfCheckOk": "Login self-check passed: protected endpoint reachable",
64
+ "login.selfCheckFail": "Login self-check failed: new token rejected by local server; please report with full output",
65
+ "login.selfCheckSkipped": "Login self-check skipped: local server unreachable; re-login once it is back",
66
+ "issueEnv.description": "Print the environment block needed for issue reports (CLI version / install type / git HEAD / server buildTime / OS); --format json for agents to paste directly",
67
+ "issueEnv.cliVersion": "CLI version",
68
+ "issueEnv.installType": "Install type",
69
+ "issueEnv.installDev": "dev (git worktree of source repo)",
70
+ "issueEnv.installPackage": "package (published npm build)",
71
+ "issueEnv.gitHead": "Code HEAD (version/buildTime do not identify code state; HEAD does)",
72
+ "issueEnv.gitBranch": "Branch",
73
+ "issueEnv.gitDirty": "uncommitted changes (dist may lag HEAD)",
74
+ "issueEnv.gitClean": "clean worktree",
75
+ "issueEnv.repoRoot": "Repo root",
76
+ "issueEnv.gitUnavailable": "unavailable (published package has no git info; use CLI version instead)",
77
+ "issueEnv.serverBuildTime": "server buildTime",
78
+ "issueEnv.serverNotRunning": "server not running (unavailable)"
61
79
  }
@@ -57,5 +57,23 @@
57
57
  "purge.done.removed": "清理数据量",
58
58
  "purge.done.server": "Server 状态",
59
59
  "purge.done.next": "如需重新初始化,请运行",
60
- "purge.done.nothing": "无需清理"
60
+ "purge.done.nothing": "无需清理",
61
+ "login.serverOfflineSaasFallback": "警告: 本地 server 未运行,已直连 SaaS 登录;本地数据操作需启动 server 后重新 login。",
62
+ "login.serverOfflineReloginHint": "提示: 本次保存的是 SaaS 令牌,本地 server 的受保护路由将返回 401。",
63
+ "login.selfCheckOk": "✓ 登录自检通过:受保护接口可达,search/report/job 可直接使用",
64
+ "login.selfCheckFail": "✗ 登录自检失败:新令牌被本地 server 拒绝,请带完整输出反馈,勿反复重试",
65
+ "login.selfCheckSkipped": "⚠ 登录自检跳过:本地 server 暂不可达,恢复后请重新 login",
66
+ "issueEnv.description": "输出提 issue 所需环境块(CLI 版本/安装方式/代码 git HEAD/server buildTime/OS),--format json 可供 agent 直接粘贴",
67
+ "issueEnv.cliVersion": "CLI 版本",
68
+ "issueEnv.installType": "安装方式",
69
+ "issueEnv.installDev": "dev(源码仓 git 工作区)",
70
+ "issueEnv.installPackage": "package(发布包)",
71
+ "issueEnv.gitHead": "代码 HEAD(版本号/buildTime 不说明代码状态,以 HEAD 为准)",
72
+ "issueEnv.gitBranch": "分支",
73
+ "issueEnv.gitDirty": "有未提交改动(dist 可能滞后于 HEAD)",
74
+ "issueEnv.gitClean": "工作区干净",
75
+ "issueEnv.repoRoot": "代码仓",
76
+ "issueEnv.gitUnavailable": "未获取(发布包无 git 信息,用 CLI 版本代替)",
77
+ "issueEnv.serverBuildTime": "server buildTime",
78
+ "issueEnv.serverNotRunning": "server 未运行(未获取)"
61
79
  }
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ import { extensionCommand } from './commands/extension.js';
14
14
  import { reportCommand } from './commands/report.js';
15
15
  import { recordCommand } from './commands/record.js';
16
16
  import { cacheCommand } from './commands/cache.js';
17
+ import { migrateDataDirCommand } from './commands/migrate.js';
17
18
  // 对应 ChromePilot 的新命令(环境管理 / 运维 / 扩展管理)
18
19
  import { initCommand } from './commands/init.js';
19
20
  import { installCommand } from './commands/install.js';
@@ -22,10 +23,34 @@ import { doctorCommand } from './commands/doctor.js';
22
23
  import { reloadCommand, openExtCommand, purgeCommand } from './commands/extras.js';
23
24
  import { recordingsCommand, adapterCommand, memoryCommand, traceCommand, autofixCommand } from './commands/ops.js';
24
25
  import { pluginCommand, skillCommand, externalCommand } from './commands/addons.js';
26
+ import { customerCommand } from './commands/customer.js';
27
+ import { noteCommand } from './commands/note.js';
28
+ import { preferenceCommand } from './commands/preference.js';
29
+ import { caseCommand } from './commands/case.js';
30
+ import { issueEnvCommand } from './commands/issue-env.js';
25
31
  import { i18n } from './i18n/index.js';
26
32
  import { initCliLogger, flushCliLogger, getCliLogger } from './logger.js';
27
33
  import packageJson from '../package.json' with { type: 'json' };
28
34
  const CLI_VERSION = packageJson.version;
35
+ // 未捕获异常兜底(fix #27):任何命令抛出的未处理 Promise 统一友好提示 + exit 1,
36
+ // 不再裸奔 stack 或以 exit code 0 静默结束。
37
+ process.on('unhandledRejection', (reason) => {
38
+ const status = reason?.response?.status;
39
+ const message = reason?.response?.data?.message || reason?.response?.data?.error || reason?.message || String(reason);
40
+ if (status === 401 || status === 403) {
41
+ console.error('登录已过期或无权访问,请运行 hunter-mate login 后重试');
42
+ }
43
+ else {
44
+ console.error(`命令执行出错: ${message}`);
45
+ }
46
+ try {
47
+ getCliLogger().error('cli:unhandled-rejection', { status, error: message });
48
+ }
49
+ catch {
50
+ // logger 未就绪时忽略
51
+ }
52
+ process.exit(1);
53
+ });
29
54
  initSentry();
30
55
  // patchConsole + patchStreams:把业务代码里的 console.error/warn 统一转发到 logger-kit,
31
56
  // 避免两套日志写法。AGENTS.md §11。
@@ -55,16 +80,35 @@ program
55
80
  // 2) server 在跑时对比磁盘版本 vs 运行版本,插件不一致 → reload,server 不一致 → 重启。
56
81
  // HUNTER_MATE_NO_AUTOUPDATE=1 可禁用;update/start/stop 自行处理避免递归;
57
82
  // server 未运行时一致性零成本跳过(本地 pid 探测,60s 节流)。
83
+ // doctor 跳过(fix #13): doctor 是只读自检, 不应触发 extension reload 广播,
84
+ // 也不应让 auto-doctor 以 repair 模式写数据(repair-linkedin-dupes 同属此类)。
58
85
  if (process.env.HUNTER_MATE_NO_AUTOUPDATE === '1')
59
86
  return;
60
87
  const name = thisCommand.name();
61
88
  if (name === 'update' || name === 'start' || name === 'stop')
62
89
  return;
90
+ if (name === 'migrate-data-dir')
91
+ return; // 迁移过程中一致性检测/自更新会干扰移动与重启
92
+ if (name === 'doctor' || name === 'repair-linkedin-dupes')
93
+ return;
94
+ // fix #30: 命令以 --json(或 --format/-f json)输出时, [auto-update] 提示不得混入 stdout 的 JSON
95
+ const argv = process.argv;
96
+ const jsonMode = argv.includes('--json') ||
97
+ argv.includes('--format=json') ||
98
+ argv.includes('-f=json') ||
99
+ argv.some((a, i) => (a === '--format' || a === '-f') && argv[i + 1] === 'json');
100
+ const notify = (line) => {
101
+ const text = chalk.gray(` [auto-update] ${line}`);
102
+ if (jsonMode)
103
+ console.error(text);
104
+ else
105
+ console.log(text);
106
+ };
63
107
  try {
64
108
  const { maybeCliSelfUpdate } = await import('./updater.js');
65
109
  const upd = await maybeCliSelfUpdate();
66
110
  for (const line of upd.logs)
67
- console.log(chalk.gray(` [auto-update] ${line}`));
111
+ notify(line);
68
112
  }
69
113
  catch {
70
114
  /* CLI 自更新检测永不阻塞主命令 */
@@ -74,7 +118,7 @@ program
74
118
  const report = await ensureRuntimeConsistency({});
75
119
  if (report.performed.length) {
76
120
  for (const line of report.performed)
77
- console.log(chalk.gray(` [auto-update] ${line}`));
121
+ notify(line);
78
122
  }
79
123
  try {
80
124
  const { runAutoDoctor } = await import('./auto-doctor.js');
@@ -124,4 +168,10 @@ program.addCommand(purgeCommand);
124
168
  program.addCommand(reportCommand);
125
169
  program.addCommand(recordCommand);
126
170
  program.addCommand(cacheCommand);
171
+ program.addCommand(migrateDataDirCommand);
172
+ program.addCommand(customerCommand);
173
+ program.addCommand(noteCommand);
174
+ program.addCommand(preferenceCommand);
175
+ program.addCommand(caseCommand);
176
+ program.addCommand(issueEnvCommand);
127
177
  program.parse();
@@ -1,3 +1,18 @@
1
- export declare function saveToken(token: string): Promise<void>;
1
+ /**
2
+ * token 签发方(hmh#43):
3
+ * - local: 本地 server 自签 UTOKEN(设备码/邮箱代理路径),AuthMiddleware 可直接验签;
4
+ * - saas: 邮箱登录时本地 server 不可达,CLI 直连 SaaS 验证拿到的 token,
5
+ * 由 SaaS 密钥签发,本地 server 的 USER_SESSION_SECRET 验不过 → 数据面 401;
6
+ * - unknown: 旧版本存的裸 token(无 issuer 标记),无法区分签发方。
7
+ */
8
+ export type TokenIssuer = 'local' | 'saas' | 'unknown';
9
+ export interface TokenInfo {
10
+ token: string;
11
+ issuer: TokenIssuer;
12
+ }
13
+ export declare function saveToken(token: string, issuer?: TokenIssuer): Promise<void>;
14
+ /** 只取 token 字符串(兼容全部历史调用方) */
2
15
  export declare function getToken(): Promise<string | null>;
16
+ /** 取 token + 签发方标记(hmh#43 诊断用) */
17
+ export declare function getTokenInfo(): Promise<TokenInfo | null>;
3
18
  export declare function deleteToken(): Promise<void>;
package/dist/keychain.js CHANGED
@@ -1,30 +1,50 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import { resolveDataDir } from './config.js';
3
4
  const SERVICE = 'hunter-mate';
4
5
  const ACCOUNT = 'user';
5
- const HOME = process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
6
- const TOKEN_FILE = path.join(HOME, '.hunter-mate', 'token.json');
7
- function fallbackSave(token) {
8
- fs.mkdirSync(path.dirname(TOKEN_FILE), { recursive: true });
6
+ /** token 落盘文件(FID-071:随 dataDir 配置迁移;keytar 可用时不使用) */
7
+ function tokenFile() {
8
+ return path.join(resolveDataDir(), 'token.json');
9
+ }
10
+ /** 兼容旧格式:裸 token 字符串或无 issuer 字段的 JSON */
11
+ function parseStored(raw) {
12
+ if (!raw)
13
+ return null;
14
+ try {
15
+ const data = JSON.parse(raw);
16
+ if (typeof data === 'string')
17
+ return { token: data, issuer: 'unknown' };
18
+ if (data && typeof data.token === 'string') {
19
+ const issuer = data.issuer === 'saas' || data.issuer === 'local' ? data.issuer : 'unknown';
20
+ return { token: data.token, issuer };
21
+ }
22
+ return null;
23
+ }
24
+ catch {
25
+ return { token: raw, issuer: 'unknown' };
26
+ }
27
+ }
28
+ function fallbackSave(info) {
29
+ fs.mkdirSync(path.dirname(tokenFile()), { recursive: true });
9
30
  // POSIX 上 mode 0o600 强制 owner-only;Windows NTFS 上 mode bit 被忽略,
10
31
  // 由用户目录 ACL 兜底(%USERPROFILE%\.hunter-mate 默认只有当前用户可访问)。
11
32
  // 真正安全的 Windows 路径是走 keytar(Windows Credential Manager)→ setPassword。
12
- fs.writeFileSync(TOKEN_FILE, JSON.stringify({ token }), { mode: 0o600 });
33
+ fs.writeFileSync(tokenFile(), JSON.stringify(info), { mode: 0o600 });
13
34
  }
14
35
  function fallbackGet() {
15
- if (!fs.existsSync(TOKEN_FILE))
36
+ if (!fs.existsSync(tokenFile()))
16
37
  return null;
17
38
  try {
18
- const data = JSON.parse(fs.readFileSync(TOKEN_FILE, 'utf-8'));
19
- return data.token || null;
39
+ return parseStored(fs.readFileSync(tokenFile(), 'utf-8'));
20
40
  }
21
41
  catch {
22
42
  return null;
23
43
  }
24
44
  }
25
45
  function fallbackDelete() {
26
- if (fs.existsSync(TOKEN_FILE)) {
27
- fs.unlinkSync(TOKEN_FILE);
46
+ if (fs.existsSync(tokenFile())) {
47
+ fs.unlinkSync(tokenFile());
28
48
  }
29
49
  }
30
50
  async function getKeytar() {
@@ -41,26 +61,33 @@ async function getKeytar() {
41
61
  return null;
42
62
  }
43
63
  }
44
- export async function saveToken(token) {
64
+ export async function saveToken(token, issuer = 'local') {
65
+ const payload = JSON.stringify({ token, issuer });
45
66
  const keytar = await getKeytar();
46
67
  if (keytar) {
47
68
  try {
48
- await keytar.setPassword(SERVICE, ACCOUNT, token);
69
+ await keytar.setPassword(SERVICE, ACCOUNT, payload);
49
70
  return;
50
71
  }
51
72
  catch {
52
73
  // 降级到文件
53
74
  }
54
75
  }
55
- fallbackSave(token);
76
+ fallbackSave({ token, issuer });
56
77
  }
78
+ /** 只取 token 字符串(兼容全部历史调用方) */
57
79
  export async function getToken() {
80
+ const info = await getTokenInfo();
81
+ return info?.token ?? null;
82
+ }
83
+ /** 取 token + 签发方标记(hmh#43 诊断用) */
84
+ export async function getTokenInfo() {
58
85
  const keytar = await getKeytar();
59
86
  if (keytar) {
60
87
  try {
61
- const token = await keytar.getPassword(SERVICE, ACCOUNT);
62
- if (token)
63
- return token;
88
+ const raw = await keytar.getPassword(SERVICE, ACCOUNT);
89
+ if (raw)
90
+ return parseStored(raw);
64
91
  }
65
92
  catch {
66
93
  // 降级
package/dist/logger.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Logger, createHttpTransport } from "@chromepilot/logger-kit/client";
2
2
  import { PersistentLogQueue } from "@chromepilot/logger-kit/queue-node";
3
- import { loadConfig } from "./config.js";
3
+ import { loadConfig, resolveDataDir } from "./config.js";
4
4
  import { getToken } from "./keychain.js";
5
5
  import path from "node:path";
6
6
  let logger = null;
@@ -25,7 +25,7 @@ export function getCliLogger() {
25
25
  return logger;
26
26
  }
27
27
  function homeDir() {
28
- return process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || ".";
28
+ return resolveDataDir();
29
29
  }
30
30
  export async function initCliLogger(options) {
31
31
  const cfg = loadConfig();
package/dist/updater.d.ts CHANGED
@@ -13,7 +13,7 @@ export interface NpmSelfUpdateResult {
13
13
  ok: boolean;
14
14
  msg: string;
15
15
  }
16
- export declare function npmSelfUpdate(pkg?: string): NpmSelfUpdateResult;
16
+ export declare function npmSelfUpdate(pkg?: string): Promise<NpmSelfUpdateResult>;
17
17
  export interface CliUpdateOutcome {
18
18
  logs: string[];
19
19
  devSkipped: boolean;
package/dist/updater.js CHANGED
@@ -21,9 +21,9 @@ import { spawnSync } from 'node:child_process';
21
21
  import { fileURLToPath } from 'node:url';
22
22
  import { createCliUpdater } from '@chromepilot/kit';
23
23
  import { readInstalledVersions } from './version.js';
24
+ import { resolveDataDir } from './config.js';
24
25
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
25
- const HOME = () => process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
26
- const ROOT = () => path.join(HOME(), '.hunter-mate');
26
+ const ROOT = resolveDataDir;
27
27
  const CLI_PREFIX = () => path.join(ROOT(), 'cli');
28
28
  /**
29
29
  * 判定是否开发软链模式(纯函数,便于单测):
@@ -52,17 +52,34 @@ export const npmLatestVersion = kitUpdater.npmLatestVersion;
52
52
  export const updateCheckIntervalMs = kitUpdater.updateCheckIntervalMs;
53
53
  export const shouldCheckUpdate = kitUpdater.shouldCheckUpdate;
54
54
  export const markUpdateChecked = kitUpdater.markUpdateChecked;
55
- export function npmSelfUpdate(pkg = 'hunter-mate') {
55
+ export async function npmSelfUpdate(pkg = 'hunter-mate') {
56
56
  const prefix = CLI_PREFIX();
57
57
  fs.mkdirSync(prefix, { recursive: true });
58
58
  // 优先使用用户通过环境变量指定的 npm 路径(沙盒内可能只有局部 npm)
59
59
  const npmBin = process.env.HUNTER_MATE_NPM_BIN || 'npm';
60
60
  const args = ['install', `${pkg}@latest`, '--prefix', prefix];
61
+ // 死代理处理:npm 会继承 HTTP(S)_PROXY;指向死端口时 registry 查询必失败
62
+ // (现场真凶:update 查 registry 失败 + prebuild 下载失败回退 7 分钟源码编译)。
63
+ // 检测后从子进程环境剔除死代理变量,让 npm 直连,并在结果里告知用户。
64
+ const childEnv = { ...process.env };
65
+ let proxyNote = '';
66
+ try {
67
+ const { findDeadProxies } = await import('./utils/proxy-detect.js');
68
+ const dead = await findDeadProxies(800);
69
+ if (dead.length) {
70
+ for (const d of dead)
71
+ delete childEnv[d.varName];
72
+ proxyNote =
73
+ `⚠ 检测到死代理(${dead.map((d) => `${d.varName}=${d.value}`).join('; ')}),` +
74
+ `已在本轮 npm install 中临时剔除使其直连;清除方法见 hunter-mate doctor 的 proxy_env 检查项\n`;
75
+ }
76
+ }
77
+ catch { /* 检测失败不阻断更新 */ }
61
78
  const r = spawnSync(npmBin, args, {
62
79
  stdio: 'inherit',
63
80
  timeout: 120_000,
64
81
  env: {
65
- ...process.env,
82
+ ...childEnv,
66
83
  // 防止 npm 因为用户 home 不可写而失败;--prefix 已指定安装位置
67
84
  npm_config_cache: path.join(prefix, '.npm-cache'),
68
85
  },
@@ -70,12 +87,12 @@ export function npmSelfUpdate(pkg = 'hunter-mate') {
70
87
  if (r.status === 0) {
71
88
  return {
72
89
  ok: true,
73
- msg: `${npmBin} install ${pkg}@latest --prefix ${prefix} 完成,下一条命令起使用新版本`,
90
+ msg: proxyNote + `${npmBin} install ${pkg}@latest --prefix ${prefix} 完成,下一条命令起使用新版本`,
74
91
  };
75
92
  }
76
93
  return {
77
94
  ok: false,
78
- msg: `${npmBin} install ${pkg}@latest --prefix ${prefix} 失败 (exit=${r.status}),可手动执行`,
95
+ msg: proxyNote + `${npmBin} install ${pkg}@latest --prefix ${prefix} 失败 (exit=${r.status}),可手动执行`,
79
96
  };
80
97
  }
81
98
  /**
@@ -107,7 +124,7 @@ export async function maybeCliSelfUpdate() {
107
124
  if (latest === installed)
108
125
  return outcome;
109
126
  logs.push(`CLI 自更新: ${installed} → ${latest}`);
110
- const r = npmSelfUpdate();
127
+ const r = await npmSelfUpdate();
111
128
  logs.push(r.msg);
112
129
  outcome.updated = r.ok;
113
130
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 命令错误统一出口(对应 fix #18/#28):
3
+ * - logger 留痕(后台可检索,与现有 getCliLogger().error 一致)
4
+ * - console.error 落终端(用户能直接看懂,不再静默 exit 1)
5
+ * - 401/403 时给出重新登录的中文提示;
6
+ * hmh#43:token 由 SaaS 签发(本地 server 未运行时登录所致)时给出定向诊断,
7
+ * 不再笼统报"登录已过期"
8
+ *
9
+ * 调用方随后自行 process.exit(1)。
10
+ */
11
+ export declare function printCommandError(scope: string, err: unknown): Promise<void>;
@@ -0,0 +1,47 @@
1
+ import { getCliLogger } from '../logger.js';
2
+ import { getTokenInfo } from '../keychain.js';
3
+ /**
4
+ * 命令错误统一出口(对应 fix #18/#28):
5
+ * - logger 留痕(后台可检索,与现有 getCliLogger().error 一致)
6
+ * - console.error 落终端(用户能直接看懂,不再静默 exit 1)
7
+ * - 401/403 时给出重新登录的中文提示;
8
+ * hmh#43:token 由 SaaS 签发(本地 server 未运行时登录所致)时给出定向诊断,
9
+ * 不再笼统报"登录已过期"
10
+ *
11
+ * 调用方随后自行 process.exit(1)。
12
+ */
13
+ export async function printCommandError(scope, err) {
14
+ const e = err;
15
+ const status = e?.response?.status;
16
+ const body = e?.response?.data;
17
+ const bodyText = body ? (typeof body === 'string' ? body : JSON.stringify(body)) : '';
18
+ const message = e?.response?.data?.message || e?.response?.data?.error || e?.message || String(err);
19
+ try {
20
+ getCliLogger().error(scope, { status, error: message });
21
+ }
22
+ catch {
23
+ // logger 未就绪时忽略
24
+ }
25
+ if (status === 401 || status === 403) {
26
+ console.error(`✗ ${message} (HTTP ${status})`);
27
+ let issuer = 'unknown';
28
+ try {
29
+ issuer = (await getTokenInfo())?.issuer ?? 'unknown';
30
+ }
31
+ catch {
32
+ // keychain 不可用时按 unknown 处理
33
+ }
34
+ if (issuer === 'saas') {
35
+ console.error('当前 token 由 SaaS 签发(登录时本地 server 未运行),本地 server 拒绝使用。\n' +
36
+ '请先启动本地 server(hunter-mate start),再重新执行 hunter-mate login。');
37
+ }
38
+ else {
39
+ console.error('登录已过期或无权访问,请运行 hunter-mate login 后重试');
40
+ }
41
+ return;
42
+ }
43
+ console.error(`✗ ${scope}: ${message}${status ? ` (HTTP ${status})` : ''}`);
44
+ if (bodyText && bodyText !== message) {
45
+ console.error(` 响应: ${bodyText}`);
46
+ }
47
+ }
@@ -0,0 +1,18 @@
1
+ export type LinkKind = 'dir' | 'file';
2
+ export interface LinkResult {
3
+ ok: boolean;
4
+ fallback?: 'copy+marker';
5
+ error?: string;
6
+ }
7
+ /**
8
+ * linkDev: cross-platform "point at the build artifact" dev-link helper.
9
+ * 实现对齐 chromepilot packages/cli/src/utils/link.ts(SSOT,同构复制)。
10
+ *
11
+ * - POSIX: fs.symlinkSync(target, linkPath, type)
12
+ * - Windows directory: NTFS junction (no admin needed; cannot cross drives)。
13
+ * 跨盘或 junction 失败退化 copy + `<linkPath>.devlink` marker。
14
+ * - Windows file: copy + marker(Windows 文件软链需要 SeCreateSymbolicLink 权限,避免使用)。
15
+ */
16
+ export declare function linkDev(target: string, linkPath: string, kind?: LinkKind): LinkResult;
17
+ /** lstat 对悬空软链会抛异常而不是返回 false;包一层保证调用方拿到 boolean */
18
+ export declare function isSymbolicLinkSafe(p: string): boolean;
@@ -0,0 +1,83 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ /**
4
+ * linkDev: cross-platform "point at the build artifact" dev-link helper.
5
+ * 实现对齐 chromepilot packages/cli/src/utils/link.ts(SSOT,同构复制)。
6
+ *
7
+ * - POSIX: fs.symlinkSync(target, linkPath, type)
8
+ * - Windows directory: NTFS junction (no admin needed; cannot cross drives)。
9
+ * 跨盘或 junction 失败退化 copy + `<linkPath>.devlink` marker。
10
+ * - Windows file: copy + marker(Windows 文件软链需要 SeCreateSymbolicLink 权限,避免使用)。
11
+ */
12
+ export function linkDev(target, linkPath, kind = 'dir') {
13
+ if (fs.existsSync(linkPath)) {
14
+ try {
15
+ fs.rmSync(linkPath, { recursive: true, force: true });
16
+ }
17
+ catch (err) {
18
+ const e = err;
19
+ return { ok: false, error: `清理已存在目标失败: ${e.message}` };
20
+ }
21
+ }
22
+ if (process.platform !== 'win32') {
23
+ try {
24
+ fs.symlinkSync(target, linkPath, kind);
25
+ return { ok: true };
26
+ }
27
+ catch (err) {
28
+ const e = err;
29
+ return { ok: false, error: `symlink 失败: ${e.message}` };
30
+ }
31
+ }
32
+ // Windows + directory: try junction
33
+ if (kind === 'dir') {
34
+ const linkDrive = path.parse(path.resolve(linkPath)).root;
35
+ const targetDrive = path.parse(path.resolve(target)).root;
36
+ if (linkDrive && targetDrive && linkDrive !== targetDrive) {
37
+ try {
38
+ fs.cpSync(target, linkPath, { recursive: true });
39
+ fs.writeFileSync(`${linkPath}.devlink`, path.resolve(target), 'utf-8');
40
+ return { ok: true, fallback: 'copy+marker' };
41
+ }
42
+ catch (err) {
43
+ const e = err;
44
+ return { ok: false, error: `junction 跨盘符退化失败: ${e.message}` };
45
+ }
46
+ }
47
+ try {
48
+ fs.symlinkSync(target, linkPath, 'junction');
49
+ return { ok: true };
50
+ }
51
+ catch (err) {
52
+ const e = err;
53
+ try {
54
+ fs.cpSync(target, linkPath, { recursive: true });
55
+ fs.writeFileSync(`${linkPath}.devlink`, path.resolve(target), 'utf-8');
56
+ return { ok: true, fallback: 'copy+marker' };
57
+ }
58
+ catch (err2) {
59
+ const e2 = err2;
60
+ return { ok: false, error: `junction 与 copy 兜底均失败: ${e.message} / ${e2.message}` };
61
+ }
62
+ }
63
+ }
64
+ // Windows + file: copy + marker
65
+ try {
66
+ fs.copyFileSync(target, linkPath);
67
+ fs.writeFileSync(`${linkPath}.devlink`, path.resolve(target), 'utf-8');
68
+ return { ok: true, fallback: 'copy+marker' };
69
+ }
70
+ catch (err) {
71
+ const e = err;
72
+ return { ok: false, error: `copy+marker 失败: ${e.message}` };
73
+ }
74
+ }
75
+ /** lstat 对悬空软链会抛异常而不是返回 false;包一层保证调用方拿到 boolean */
76
+ export function isSymbolicLinkSafe(p) {
77
+ try {
78
+ return fs.lstatSync(p).isSymbolicLink();
79
+ }
80
+ catch {
81
+ return false;
82
+ }
83
+ }