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
@@ -0,0 +1,101 @@
1
+ import { Command } from 'commander';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as os from 'os';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { execFileSync } from 'node:child_process';
7
+ import axios from 'axios';
8
+ import { i18n } from '../i18n/index.js';
9
+ import { loadConfig, resolveDataDir } from '../config.js';
10
+ import { readInstalledVersions } from '../version.js';
11
+ // dev 软链/shim 安装时 __dirname 位于仓内(packages/cli/dist/commands),
12
+ // 发布包安装时位于全局 node_modules —— 向上找含 .git 的目录即可判定并定位仓根。
13
+ const START_DIR = path.dirname(fileURLToPath(import.meta.url));
14
+ function findRepoRoot(start) {
15
+ let dir = start;
16
+ while (true) {
17
+ if (fs.existsSync(path.join(dir, '.git')))
18
+ return dir;
19
+ const parent = path.dirname(dir);
20
+ if (parent === dir)
21
+ return null;
22
+ dir = parent;
23
+ }
24
+ }
25
+ /** 静默执行 git,失败(git 不可用/非工作区)返回 null */
26
+ function git(args, cwd) {
27
+ try {
28
+ const out = execFileSync('git', args, {
29
+ cwd,
30
+ encoding: 'utf-8',
31
+ stdio: ['ignore', 'pipe', 'ignore'],
32
+ }).trim();
33
+ return out.length > 0 ? out : null;
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ export async function runIssueEnv() {
40
+ const installed = readInstalledVersions();
41
+ const repoRoot = findRepoRoot(START_DIR);
42
+ let gitInfo = null;
43
+ if (repoRoot) {
44
+ const porcelain = git(['status', '--porcelain'], repoRoot) ?? '';
45
+ gitInfo = {
46
+ repoRoot,
47
+ head: git(['rev-parse', '--short', 'HEAD'], repoRoot),
48
+ branch: git(['branch', '--show-current'], repoRoot),
49
+ lastCommit: git(['log', '-1', '--format=%h %ci %s'], repoRoot),
50
+ dirty: porcelain.length > 0,
51
+ };
52
+ }
53
+ let serverBuildTime = null;
54
+ try {
55
+ const r = await axios.get(`${loadConfig().serverUrl}/health`, { timeout: 2000 });
56
+ serverBuildTime = r.data?.buildTime ?? null;
57
+ }
58
+ catch {
59
+ /* server 未运行,保持 null */
60
+ }
61
+ return {
62
+ cliVersion: installed.cli ?? 'unknown',
63
+ installType: repoRoot ? 'dev' : 'package',
64
+ git: gitInfo,
65
+ serverBuildTime,
66
+ os: `${os.platform()} ${os.release()} ${os.arch()}`,
67
+ node: process.version,
68
+ dataDir: resolveDataDir(),
69
+ };
70
+ }
71
+ function renderText(r) {
72
+ const lines = [];
73
+ lines.push(`${i18n.t('issueEnv.cliVersion')}: ${r.cliVersion}`);
74
+ lines.push(`${i18n.t('issueEnv.installType')}: ` +
75
+ (r.installType === 'dev' ? i18n.t('issueEnv.installDev') : i18n.t('issueEnv.installPackage')));
76
+ if (r.git) {
77
+ lines.push(`${i18n.t('issueEnv.gitHead')}: ${r.git.lastCommit ?? r.git.head ?? 'unknown'}`);
78
+ lines.push(`${i18n.t('issueEnv.gitBranch')}: ${r.git.branch ?? 'unknown'} · ` +
79
+ (r.git.dirty ? i18n.t('issueEnv.gitDirty') : i18n.t('issueEnv.gitClean')));
80
+ lines.push(`${i18n.t('issueEnv.repoRoot')}: ${r.git.repoRoot}`);
81
+ }
82
+ else {
83
+ lines.push(`${i18n.t('issueEnv.gitHead')}: ${i18n.t('issueEnv.gitUnavailable')}`);
84
+ }
85
+ lines.push(`${i18n.t('issueEnv.serverBuildTime')}: ${r.serverBuildTime ?? i18n.t('issueEnv.serverNotRunning')}`);
86
+ lines.push(`OS: ${r.os}`);
87
+ lines.push(`Node: ${r.node}`);
88
+ lines.push(`dataDir: ${r.dataDir}`);
89
+ return lines.join('\n');
90
+ }
91
+ export const issueEnvCommand = new Command('issue-env')
92
+ .description(i18n.t('issueEnv.description'))
93
+ .option('-f, --format <fmt>', '输出格式: text|json', 'text')
94
+ .action(async (opts) => {
95
+ const result = await runIssueEnv();
96
+ if (opts.format === 'json') {
97
+ console.log(JSON.stringify(result, null, 2));
98
+ return;
99
+ }
100
+ console.log(renderText(result));
101
+ });
@@ -3,7 +3,11 @@ export interface CreateJobCaseResult {
3
3
  id: number;
4
4
  externalId: string;
5
5
  status: string;
6
+ customerId?: number;
6
7
  }
7
- export declare function runJobCreate(folderPath: string): Promise<CreateJobCaseResult>;
8
- export declare function runJobList(): any[];
8
+ export declare function runJobCreate(folderPath: string, customerRef?: string): Promise<CreateJobCaseResult>;
9
+ /**
10
+ * 优先查 server 的 /job-cases;server 不可达或未登录时兜底本地 sqlite 缓存。
11
+ */
12
+ export declare function runJobList(): Promise<any[]>;
9
13
  export declare const jobCommand: Command;
@@ -7,27 +7,63 @@ import { ensureCacheDir, saveJobCase, listJobCases } from '../cache/db.js';
7
7
  import { loadConfig } from '../config.js';
8
8
  import { getToken } from '../keychain.js';
9
9
  import { i18n } from '../i18n/index.js';
10
- import { getCliLogger } from '../logger.js';
11
- export async function runJobCreate(folderPath) {
10
+ import { printCommandError } from '../utils/command-error.js';
11
+ import { runCustomerList } from './customer.js';
12
+ /** 递归收集目录下的 .md/.txt/.pdf(含 raw/jd/ 等子目录),键为相对路径(根目录文件不带前缀)。 */
13
+ function collectJobFiles(dir, relPrefix = '', out = {}) {
14
+ const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
15
+ // 根目录文件优先,再递归子目录,保证 keys[0] 是根目录第一个文件(用于 title)
16
+ for (const entry of entries) {
17
+ if (entry.isFile() && /\.(md|txt|pdf)$/.test(entry.name)) {
18
+ out[relPrefix + entry.name] = fs.readFileSync(path.join(dir, entry.name), 'utf-8');
19
+ }
20
+ }
21
+ for (const entry of entries) {
22
+ if (entry.isDirectory()) {
23
+ collectJobFiles(path.join(dir, entry.name), relPrefix + entry.name + '/', out);
24
+ }
25
+ }
26
+ return out;
27
+ }
28
+ export async function runJobCreate(folderPath, customerRef) {
12
29
  const config = loadConfig();
13
30
  const token = await getToken();
14
31
  const headers = token ? { Authorization: `UTOKEN ${token}` } : {};
32
+ // --customer:按名称/别名解析客户 id,让职位 case 挂到客户锚点上
33
+ let customerId;
34
+ if (customerRef) {
35
+ if (/^\d+$/.test(customerRef.trim())) {
36
+ customerId = parseInt(customerRef.trim(), 10);
37
+ }
38
+ else {
39
+ const hits = await runCustomerList(customerRef.trim());
40
+ if (!hits.length) {
41
+ throw new Error(`客户 "${customerRef}" 未入库,请先: hunter-mate customer upsert --name "${customerRef}"`);
42
+ }
43
+ customerId = hits[0].id;
44
+ if (hits.length > 1) {
45
+ console.error(`ℹ 客户 "${customerRef}" 命中 ${hits.length} 条,使用第一个: [${hits[0].id}] ${hits[0].canonicalName}`);
46
+ }
47
+ }
48
+ }
15
49
  const resolved = path.resolve(folderPath);
16
50
  if (!fs.existsSync(resolved)) {
17
51
  throw new Error(`Folder not found: ${resolved}`);
18
52
  }
19
- const files = fs.readdirSync(resolved).filter((f) => f.endsWith('.md') || f.endsWith('.txt') || f.endsWith('.pdf'));
20
- const rawFiles = {};
21
- for (const f of files) {
22
- rawFiles[f] = fs.readFileSync(path.join(resolved, f), 'utf-8');
53
+ const rawFiles = collectJobFiles(resolved);
54
+ if (!Object.keys(rawFiles).length) {
55
+ throw new Error(`Folder 内未找到 .md/.txt/.pdf 文件: ${resolved}`);
23
56
  }
24
57
  const rawText = Object.values(rawFiles).join('\n---\n');
25
- const title = Object.keys(rawFiles)[0]?.replace(/\.[^.]+$/, '') || 'Untitled JobCase';
58
+ const firstKey = Object.keys(rawFiles)[0];
59
+ const titleBase = firstKey.includes('/') ? firstKey.split('/').pop() : firstKey;
60
+ const title = titleBase.replace(/\.[^.]+$/, '') || 'Untitled JobCase';
26
61
  const res = await axios.post(`${config.serverUrl}/job-cases`, {
27
62
  externalId: uuidv4(),
28
63
  title,
29
64
  folderPath: resolved,
30
65
  rawFiles,
66
+ customerId,
31
67
  }, { headers, proxy: false });
32
68
  const serverCase = res.data;
33
69
  ensureCacheDir();
@@ -36,7 +72,7 @@ export async function runJobCreate(folderPath) {
36
72
  externalId: serverCase.externalId,
37
73
  parsedFrom: {
38
74
  folderPath: resolved,
39
- fileNames: files.join(','),
75
+ fileNames: Object.keys(rawFiles).join(','),
40
76
  rawText,
41
77
  },
42
78
  requirements: {},
@@ -47,30 +83,49 @@ export async function runJobCreate(folderPath) {
47
83
  saveJobCase(jobCase);
48
84
  return serverCase;
49
85
  }
50
- export function runJobList() {
51
- return listJobCases();
86
+ /**
87
+ * 优先查 server 的 /job-cases;server 不可达或未登录时兜底本地 sqlite 缓存。
88
+ */
89
+ export async function runJobList() {
90
+ const config = loadConfig();
91
+ const token = await getToken().catch(() => undefined);
92
+ try {
93
+ const res = await axios.get(`${config.serverUrl}/job-cases`, {
94
+ headers: token ? { Authorization: `UTOKEN ${token}` } : {},
95
+ proxy: false,
96
+ timeout: 8000,
97
+ });
98
+ const list = Array.isArray(res.data) ? res.data : [];
99
+ // server 为空时兜底本地缓存(server 数据可能被清理)
100
+ return list.length ? list : listJobCases();
101
+ }
102
+ catch (err) {
103
+ if (err?.response?.status === 401 || err?.response?.status === 403) {
104
+ console.error('登录已过期或无权访问,请运行 hunter-mate login 后重试(本次展示本地缓存数据)');
105
+ }
106
+ return listJobCases();
107
+ }
52
108
  }
53
109
  export const jobCommand = new Command('job')
54
110
  .description(i18n.t('job.description'));
55
111
  jobCommand
56
112
  .command('create <folder>')
57
113
  .description(i18n.t('job.create'))
58
- .action(async (folderPath) => {
114
+ .option('--customer <idOrName>', '关联客户(名称/别名/id),未入库会先报错提示 upsert')
115
+ .action(async (folderPath, opts) => {
59
116
  try {
60
- const serverCase = await runJobCreate(folderPath);
61
- console.log(`Created JobCase ${serverCase.id} (${serverCase.externalId})`);
117
+ const serverCase = await runJobCreate(folderPath, opts.customer);
118
+ console.log(`Created JobCase ${serverCase.id} (${serverCase.externalId})${serverCase.customerId ? ` customer=${serverCase.customerId}` : ''}`);
62
119
  }
63
120
  catch (err) {
64
- getCliLogger().error('job:create-failed', {
65
- error: err.response?.data?.message || err.message,
66
- });
121
+ await printCommandError('job:create-failed', err);
67
122
  process.exit(1);
68
123
  }
69
124
  });
70
125
  jobCommand
71
126
  .command('list')
72
- .description('List local JobCases')
73
- .action(() => {
74
- const cases = runJobList();
75
- console.table(cases.map((c) => ({ id: c.id, folder: c.parsedFrom?.folderPath, status: c.status, createdAt: c.createdAt })));
127
+ .description('List JobCases (优先 server,失败兜底本地缓存)')
128
+ .action(async () => {
129
+ const cases = await runJobList();
130
+ console.table(cases.map((c) => ({ id: c.id, folder: c.parsedFrom?.folderPath ?? c.folderPath, status: c.status, createdAt: c.createdAt })));
76
131
  });
@@ -1,3 +1,22 @@
1
1
  import { Command } from 'commander';
2
+ export type EmailLoginSource = 'server' | 'saas';
3
+ /**
4
+ * 邮箱登录第一步:发送验证码。
5
+ * 优先走本地 server(SaaS 代理模式,verify 后拿到的是本地自签 UTOKEN,
6
+ * 受保护路由立即可用);server 不可达时 fallback 直连 SaaS。
7
+ */
8
+ export declare function sendEmailCode(email: string): Promise<EmailLoginSource>;
9
+ /**
10
+ * 邮箱登录第二步:校验验证码并保存 token。
11
+ * 分支选择与 sendEmailCode 保持一致:server 在线走本地代理自签,否则直连 SaaS。
12
+ *
13
+ * hmh#43:两个分支签发的 token 密钥不同,必须打 issuer 标记 —
14
+ * SaaS fallback 拿到的是 SaaS 签发的 token,本地 server 的 AuthMiddleware
15
+ * (USER_SESSION_SECRET)验不过,数据面(search 等)会 401。
16
+ */
17
+ export declare function verifyEmailCode(email: string, code: string): Promise<{
18
+ token: string;
19
+ source: EmailLoginSource;
20
+ }>;
2
21
  export declare function runLogin(): Promise<string>;
3
22
  export declare const loginCommand: Command;
@@ -2,12 +2,64 @@ import { Command } from 'commander';
2
2
  import axios from 'axios';
3
3
  import open from 'open';
4
4
  import { loadConfig } from '../config.js';
5
- import { saveToken } from '../keychain.js';
5
+ import { saveToken, getToken } from '../keychain.js';
6
6
  import { i18n } from '../i18n/index.js';
7
7
  import { getCliLogger } from '../logger.js';
8
8
  function sleep(ms) {
9
9
  return new Promise((resolve) => setTimeout(resolve, ms));
10
10
  }
11
+ /** 探测本地 server 是否在线(参照 doctor 的 server 检测) */
12
+ async function isLocalServerReachable(serverUrl) {
13
+ try {
14
+ await axios.get(`${serverUrl}/health`, { timeout: 2000, proxy: false });
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ /**
22
+ * 邮箱登录第一步:发送验证码。
23
+ * 优先走本地 server(SaaS 代理模式,verify 后拿到的是本地自签 UTOKEN,
24
+ * 受保护路由立即可用);server 不可达时 fallback 直连 SaaS。
25
+ */
26
+ export async function sendEmailCode(email) {
27
+ const config = loadConfig();
28
+ if (await isLocalServerReachable(config.serverUrl)) {
29
+ await axios.post(`${config.serverUrl}/auth/email/send-code`, { email }, { proxy: false });
30
+ return 'server';
31
+ }
32
+ await axios.post(`${config.saasUrl}/hunter-mate/auth/email/send-code`, { email }, { proxy: false });
33
+ return 'saas';
34
+ }
35
+ /**
36
+ * 邮箱登录第二步:校验验证码并保存 token。
37
+ * 分支选择与 sendEmailCode 保持一致:server 在线走本地代理自签,否则直连 SaaS。
38
+ *
39
+ * hmh#43:两个分支签发的 token 密钥不同,必须打 issuer 标记 —
40
+ * SaaS fallback 拿到的是 SaaS 签发的 token,本地 server 的 AuthMiddleware
41
+ * (USER_SESSION_SECRET)验不过,数据面(search 等)会 401。
42
+ */
43
+ export async function verifyEmailCode(email, code) {
44
+ const config = loadConfig();
45
+ if (await isLocalServerReachable(config.serverUrl)) {
46
+ const { data } = await axios.post(`${config.serverUrl}/auth/email/verify`, { email, code }, { proxy: false });
47
+ if (!data.access_token) {
48
+ throw new Error('verify returned no access_token');
49
+ }
50
+ await saveToken(data.access_token, 'local');
51
+ return { token: data.access_token, source: 'server' };
52
+ }
53
+ const { data } = await axios.post(`${config.saasUrl}/hunter-mate/auth/email/verify`, { email, code }, { proxy: false });
54
+ if (!data.access_token) {
55
+ throw new Error('verify returned no access_token');
56
+ }
57
+ await saveToken(data.access_token, 'saas');
58
+ console.warn('⚠️ 本地 server 未运行,本次登录由 SaaS 签发 token。\n' +
59
+ ' 本地数据面(search 等)在 server 启动后会提示登录失效;\n' +
60
+ ' 请先运行 hunter-mate server(或 hunter-mate extension check 自动拉起)再重新登录。');
61
+ return { token: data.access_token, source: 'saas' };
62
+ }
11
63
  export async function runLogin() {
12
64
  const config = loadConfig();
13
65
  const { data: deviceCode } = await axios.post(`${config.serverUrl}/auth/device-code`);
@@ -44,28 +96,48 @@ export const loginCommand = new Command('login')
44
96
  .option('--code <code>', 'Email verification code (6 digits, step 2)')
45
97
  .action(async (options) => {
46
98
  try {
47
- const config = loadConfig();
48
- // 邮箱验证码登录(两步式无状态;SaaS 服务端,不经本地 server)
99
+ // 邮箱验证码登录(两步式无状态):优先走本地 server(SaaS 代理,自签 UTOKEN),
100
+ // server 不在线时 fallback 直连 SaaS 并警告(FID-057 #8)
49
101
  if (options.email) {
50
102
  if (!options.code) {
51
103
  // 第一步:发送验证码
52
- await axios.post(`${config.saasUrl}/hunter-mate/auth/email/send-code`, {
53
- email: options.email,
54
- });
55
- getCliLogger().info('login:email-code-sent', { email: options.email });
104
+ const source = await sendEmailCode(options.email);
105
+ if (source === 'saas') {
106
+ console.warn(i18n.t('login.serverOfflineSaasFallback'));
107
+ }
108
+ getCliLogger().info('login:email-code-sent', { email: options.email, source });
56
109
  console.log(`${i18n.t('login.emailCodeSent')} ${options.email}`);
57
110
  console.log(` ${i18n.t('login.emailCodeNextStep')}`);
58
111
  return;
59
112
  }
60
113
  // 第二步:校验验证码 → 保存 token
61
- const { data } = await axios.post(`${config.saasUrl}/hunter-mate/auth/email/verify`, { email: options.email, code: options.code });
62
- if (!data.access_token) {
63
- getCliLogger().error('login:email-verify-no-token');
64
- throw new Error('verify returned no access_token');
114
+ const { source } = await verifyEmailCode(options.email, options.code);
115
+ if (source === 'saas') {
116
+ console.warn(i18n.t('login.serverOfflineSaasFallback'));
117
+ console.warn(i18n.t('login.serverOfflineReloginHint'));
65
118
  }
66
- await saveToken(data.access_token);
67
- getCliLogger().info('login:token-saved', { method: 'email', email: options.email });
119
+ getCliLogger().info('login:token-saved', { method: 'email', email: options.email, source });
68
120
  console.log(i18n.t('login.success'));
121
+ // 登录后自检:拿新 token 打一次受保护端点,把「登录成功但搜索 401」的
122
+ // 静默失败变成确定的成功/失败提示(FID-057 #36)
123
+ try {
124
+ const token = await getToken();
125
+ const probe = await axios.get(`${loadConfig().serverUrl}/job-cases`, {
126
+ headers: token ? { Authorization: `UTOKEN ${token}` } : {},
127
+ timeout: 5000,
128
+ proxy: false,
129
+ });
130
+ console.log(`${i18n.t('login.selfCheckOk')} (HTTP ${probe.status})`);
131
+ }
132
+ catch (probeErr) {
133
+ const status = probeErr?.response?.status;
134
+ if (status === 401 || status === 403) {
135
+ console.error(`${i18n.t('login.selfCheckFail')} (HTTP ${status})`);
136
+ }
137
+ else {
138
+ console.warn(`${i18n.t('login.selfCheckSkipped')} (HTTP ${status || 'network'})`);
139
+ }
140
+ }
69
141
  return;
70
142
  }
71
143
  if (options.code) {
@@ -0,0 +1,32 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * 跨卷安全移动:同卷 renameSync;rename 失败(典型 EXDEV 跨盘,Windows C:→D:)
4
+ * 回退递归 copy + rm。symlink 项读 link 目标后经 linkDev 重建
5
+ * (Windows 普通用户无 symlink 权限,junction/copy+marker 兜底)。
6
+ */
7
+ export declare function moveEntry(src: string, dest: string): void;
8
+ export interface MigrateResult {
9
+ from: string;
10
+ to: string;
11
+ moved: string[];
12
+ skipped: string[];
13
+ serverRestarted: boolean;
14
+ }
15
+ /**
16
+ * 数据目录迁移(FID-071):
17
+ * 1. server 在跑则先停(记录状态,迁移后按需重启)
18
+ * 2. 源目录(除 config.json 外)全部移动到目标目录
19
+ * 3. config.json 留在原 CONFIG_DIR,写入 dataDir=目标
20
+ * 4. 重启 server(若迁移前在跑)
21
+ */
22
+ export declare function runMigrateDataDir(targetArg: string, deps: {
23
+ isRunning: () => Promise<boolean>;
24
+ stop: () => Promise<{
25
+ stopped: boolean;
26
+ }>;
27
+ start: () => Promise<{
28
+ started: boolean;
29
+ msg: string;
30
+ }>;
31
+ }): Promise<MigrateResult>;
32
+ export declare const migrateDataDirCommand: Command;
@@ -0,0 +1,116 @@
1
+ import { Command } from 'commander';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { loadConfig, saveConfig, resolveDataDir } from '../config.js';
5
+ import { getCliLogger } from '../logger.js';
6
+ import { linkDev } from '../utils/link.js';
7
+ /**
8
+ * 跨卷安全移动:同卷 renameSync;rename 失败(典型 EXDEV 跨盘,Windows C:→D:)
9
+ * 回退递归 copy + rm。symlink 项读 link 目标后经 linkDev 重建
10
+ * (Windows 普通用户无 symlink 权限,junction/copy+marker 兜底)。
11
+ */
12
+ export function moveEntry(src, dest) {
13
+ const stat = fs.lstatSync(src);
14
+ if (stat.isSymbolicLink()) {
15
+ const linkTarget = fs.readlinkSync(src);
16
+ const r = linkDev(linkTarget, dest, stat.isDirectory() ? 'dir' : 'file');
17
+ if (!r.ok)
18
+ throw new Error(`重建软链失败 ${src} → ${dest}: ${r.error}`);
19
+ fs.unlinkSync(src);
20
+ return;
21
+ }
22
+ try {
23
+ fs.renameSync(src, dest);
24
+ }
25
+ catch {
26
+ fs.cpSync(src, dest, { recursive: true });
27
+ fs.rmSync(src, { recursive: true, force: true });
28
+ }
29
+ }
30
+ /**
31
+ * 数据目录迁移(FID-071):
32
+ * 1. server 在跑则先停(记录状态,迁移后按需重启)
33
+ * 2. 源目录(除 config.json 外)全部移动到目标目录
34
+ * 3. config.json 留在原 CONFIG_DIR,写入 dataDir=目标
35
+ * 4. 重启 server(若迁移前在跑)
36
+ */
37
+ export async function runMigrateDataDir(targetArg, deps) {
38
+ const target = path.resolve(targetArg);
39
+ const source = resolveDataDir();
40
+ if (target === source) {
41
+ throw new Error(`目标目录与当前数据目录相同: ${target}`);
42
+ }
43
+ if (target.startsWith(source + path.sep)) {
44
+ throw new Error(`目标目录不能位于当前数据目录内部: ${target}`);
45
+ }
46
+ if (!fs.existsSync(source)) {
47
+ throw new Error(`当前数据目录不存在: ${source}(先运行 hunter-mate init)`);
48
+ }
49
+ // 1) server 状态探测并停止
50
+ const wasRunning = await deps.isRunning();
51
+ if (wasRunning) {
52
+ const stopRes = await deps.stop();
53
+ if (!stopRes.stopped) {
54
+ throw new Error('server 停止失败,已中止迁移(避免移动正在被写入的数据库)');
55
+ }
56
+ }
57
+ // 2) 逐项移动(config.json 是配置的锚点,永远留在 CONFIG_DIR)
58
+ fs.mkdirSync(target, { recursive: true });
59
+ const entries = fs.readdirSync(source, { withFileTypes: true });
60
+ const moved = [];
61
+ const skipped = [];
62
+ for (const entry of entries) {
63
+ if (entry.name === 'config.json') {
64
+ skipped.push(entry.name);
65
+ continue;
66
+ }
67
+ moveEntry(path.join(source, entry.name), path.join(target, entry.name));
68
+ moved.push(entry.name);
69
+ }
70
+ // 3) 写回配置
71
+ saveConfig({ dataDir: target });
72
+ loadConfig();
73
+ // 4) 按需重启
74
+ let serverRestarted = false;
75
+ if (wasRunning) {
76
+ const startRes = await deps.start();
77
+ serverRestarted = startRes.started;
78
+ if (!startRes.started) {
79
+ getCliLogger().warn('migrate:server-restart-failed', { msg: startRes.msg });
80
+ }
81
+ }
82
+ return { from: source, to: target, moved, skipped, serverRestarted };
83
+ }
84
+ export const migrateDataDirCommand = new Command('migrate-data-dir')
85
+ .description('把 ~/.hunter-mate 的重数据(数据库/日志/缓存/扩展等)迁移到指定目录,如 D:\\hm-data(FID-071)')
86
+ .argument('<target>', '目标目录(绝对路径,如 D:\\hm-data)')
87
+ .action(async (target) => {
88
+ const { tryReadPid, runStop, runStart } = await import('./server.js');
89
+ const result = await runMigrateDataDir(target, {
90
+ isRunning: async () => {
91
+ const pid = tryReadPid();
92
+ if (!pid)
93
+ return false;
94
+ const config = loadConfig();
95
+ try {
96
+ const res = await fetch(`${config.serverUrl}/health`, {
97
+ signal: AbortSignal.timeout(1500),
98
+ });
99
+ return res.ok;
100
+ }
101
+ catch {
102
+ return false;
103
+ }
104
+ },
105
+ stop: () => runStop(),
106
+ start: () => runStart({}),
107
+ });
108
+ console.log(`✓ 数据目录已迁移: ${result.from} → ${result.to}`);
109
+ console.log(` 移动 ${result.moved.length} 项: ${result.moved.join(', ') || '(空)'}`);
110
+ if (result.skipped.length) {
111
+ console.log(` 保留在原位: ${result.skipped.join(', ')}(配置文件锚点,不迁移)`);
112
+ }
113
+ console.log(` server: ${result.serverRestarted ? '已重启' : '迁移前未运行(下次 start 自动使用新目录)'}`);
114
+ console.log(' 提示: 若 Chrome 扩展加载自旧目录,请在新目录重新加载扩展(chrome://extensions)');
115
+ getCliLogger().info('migrate-data-dir:done', { from: result.from, to: result.to, moved: result.moved.length });
116
+ });
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ /** 业务备注(Remark 实体)CLI 入口:推荐反馈/沟通结论/不合适原因等记忆载体 */
3
+ export declare const noteCommand: Command;
@@ -0,0 +1,75 @@
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
+ const TARGETS = ['customer', 'job', 'talent', 'company', 'talent-job-match'];
7
+ async function authedHeaders() {
8
+ const token = await getToken().catch(() => undefined);
9
+ return token ? { Authorization: `UTOKEN ${token}` } : {};
10
+ }
11
+ /** 业务备注(Remark 实体)CLI 入口:推荐反馈/沟通结论/不合适原因等记忆载体 */
12
+ export const noteCommand = new Command('note').description('业务备注:挂在 customer/job/talent/company 上的记忆记录(Remark)');
13
+ noteCommand
14
+ .command('add')
15
+ .requiredOption('--target <type>', `目标类型: ${TARGETS.join('|')}`)
16
+ .requiredOption('--id <targetId>', '目标 id')
17
+ .requiredOption('--content <text>', '备注内容')
18
+ .option('--category <cat>', '分类,如 feedback/general/interview', 'general')
19
+ .option('--tags <a,b>', '标签,逗号分隔')
20
+ .option('--json', 'JSON 输出')
21
+ .action(async (opts) => {
22
+ try {
23
+ if (!TARGETS.includes(opts.target)) {
24
+ throw new Error(`--target 必须是: ${TARGETS.join('|')}`);
25
+ }
26
+ const config = loadConfig();
27
+ const res = await axios.post(`${config.serverUrl}/remarks`, {
28
+ targetType: opts.target,
29
+ targetId: String(opts.id),
30
+ author: { userId: 'cli', userName: 'cli', source: 'cli' },
31
+ content: opts.content,
32
+ category: opts.category,
33
+ tags: opts.tags ? opts.tags.split(',').map((s) => s.trim()).filter(Boolean) : undefined,
34
+ }, { headers: await authedHeaders(), proxy: false });
35
+ if (opts.json)
36
+ console.log(JSON.stringify(res.data, null, 2));
37
+ else
38
+ console.log(`✓ 备注已记录 (${opts.target}#${opts.id}, category=${opts.category})`);
39
+ }
40
+ catch (err) {
41
+ printCommandError('note:add-failed', err);
42
+ process.exit(1);
43
+ }
44
+ });
45
+ noteCommand
46
+ .command('list')
47
+ .requiredOption('--target <type>', `目标类型: ${TARGETS.join('|')}`)
48
+ .requiredOption('--id <targetId>', '目标 id')
49
+ .option('--json', 'JSON 输出')
50
+ .action(async (opts) => {
51
+ try {
52
+ if (!TARGETS.includes(opts.target)) {
53
+ throw new Error(`--target 必须是: ${TARGETS.join('|')}`);
54
+ }
55
+ const config = loadConfig();
56
+ const res = await axios.get(`${config.serverUrl}/remarks/${opts.target}/${encodeURIComponent(String(opts.id))}`, { headers: await authedHeaders(), proxy: false });
57
+ const list = Array.isArray(res.data) ? res.data : [];
58
+ if (opts.json) {
59
+ console.log(JSON.stringify(list, null, 2));
60
+ }
61
+ else {
62
+ if (!list.length) {
63
+ console.log(`无备注 (${opts.target}#${opts.id})`);
64
+ return;
65
+ }
66
+ for (const r of list) {
67
+ console.log(`- [${r.category}] ${String(r.content).slice(0, 100)} (${r.createdAt})`);
68
+ }
69
+ }
70
+ }
71
+ catch (err) {
72
+ printCommandError('note:list-failed', err);
73
+ process.exit(1);
74
+ }
75
+ });