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,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
+ });
@@ -2,8 +2,9 @@ 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 { getCliLogger } from '../logger.js';
7
+ import { printCommandError } from '../utils/command-error.js';
7
8
  // OpenCLI 风格的通用选项
8
9
  function withFormat(cmd) {
9
10
  return cmd.option('-f, --format <format>', '输出格式: table|json|yaml|md|csv', 'json');
@@ -76,8 +77,37 @@ withFormat(recordingsCommand.command('list').description('列出录制会话(
76
77
  printResult(r.data, opts.format);
77
78
  }
78
79
  catch (e) {
79
- // server 暂未实现 recordings list 时给出空结果与提示,避免 crash
80
- printResult({ note: '/api/recordings GET 未实现,HunterMate server 正在逐步补齐。先从 /api/recordings/:sessionId GET 查看单个。', sessions: [] }, opts.format);
80
+ // hmh#44: /api/recordings GET 早已实现,连接失败应如实报错,不再回退到假提示
81
+ await printCommandError('ops:recordings-list-failed', e);
82
+ process.exit(1);
83
+ }
84
+ });
85
+ /** hmh#44: 按会话删除录制数据(DELETE /api/recordings/:sessionId) */
86
+ recordingsCommand
87
+ .command('delete <sessionId>')
88
+ .description('删除指定录制会话的事件与快照 (DELETE /api/recordings/:id)')
89
+ .action(async (sessionId) => {
90
+ try {
91
+ const r = await http().delete(`/api/recordings/${sessionId}`);
92
+ console.log(JSON.stringify(r.data, null, 2));
93
+ }
94
+ catch (e) {
95
+ await printCommandError('ops:recordings-delete-failed', e);
96
+ process.exit(1);
97
+ }
98
+ });
99
+ /** hmh#44: 手动 VACUUM 缩容,删除数据后让 SQLite 文件真实释放磁盘 */
100
+ recordingsCommand
101
+ .command('compact')
102
+ .description('VACUUM 压缩录制库,释放已删除数据占用的磁盘 (POST /api/recordings/compact)')
103
+ .action(async () => {
104
+ try {
105
+ const r = await http().post('/api/recordings/compact', {});
106
+ console.log(JSON.stringify(r.data, null, 2));
107
+ }
108
+ catch (e) {
109
+ await printCommandError('ops:recordings-compact-failed', e);
110
+ process.exit(1);
81
111
  }
82
112
  });
83
113
  withFormat(recordingsCommand.command('show <sessionId>').description('查看录制详情 (/api/recordings/:id) + 可选 parse'))
@@ -115,11 +145,12 @@ recordingsCommand
115
145
  // adapter (OpenCLI 风格)
116
146
  // ---------------------------------------------------------------------------
117
147
  export const adapterCommand = withFormat(new Command('adapter')
118
- .description('站点适配器(对应 chromepilot adapter): list | extract <url>')
119
- .argument('[action]', '操作: list | extract', 'list')
148
+ .description('站点适配器(对应 chromepilot adapter): list | extract <url> | sync')
149
+ .argument('[action]', '操作: list | extract | sync', 'list')
120
150
  .argument('[target]', 'list: 站点名过滤;extract: 页面 URL', '')
121
151
  .option('--tab <pageId>', 'extract: 指定目标 tab(page id;默认复用同站点 tab 或新建后台 tab,不抢焦点)')
122
- .option('--settle-ms <n>', 'extract: 导航后首查等待 ms(默认 1200)', '1200')).action(async (action, target, opts) => {
152
+ .option('--settle-ms <n>', 'extract: 导航后首查等待 ms(默认 1200)', '1200')
153
+ .option('--dry-run', 'sync: 只统计差异不写库', false)).action(async (action, target, opts) => {
123
154
  if (action === 'list' || !action) {
124
155
  try {
125
156
  const r = await http().get('/adapters');
@@ -132,6 +163,7 @@ export const adapterCommand = withFormat(new Command('adapter')
132
163
  // server 暂未实现 /adapters 时退回内置清单
133
164
  printResult({ note: `GET /adapters failed (${e.message}),fallback 内置清单`, adapters: [
134
165
  { platform: 'linkedin', host: 'linkedin.com', command: 'hm.linkedin.extract' },
166
+ { platform: 'liepin', host: 'h.liepin.com', command: 'hm.liepin.extract' },
135
167
  ] }, opts.format);
136
168
  }
137
169
  return;
@@ -168,7 +200,29 @@ export const adapterCommand = withFormat(new Command('adapter')
168
200
  }
169
201
  return;
170
202
  }
171
- printResult({ error: `unknown action: ${action}`, supported: ['list', 'extract'] }, opts.format);
203
+ if (action === 'sync') {
204
+ // hunter-mate-harness adapters/ overlay 同步 @fid FID-081-adapter-overlay-suite
205
+ // 路径优先级: env HUNTER_MATE_HARNESS_PATH > config.json harnessPath
206
+ const cfg = loadConfig();
207
+ const harnessPath = process.env.HUNTER_MATE_HARNESS_PATH || cfg?.harnessPath;
208
+ if (!harnessPath || !harnessPath.trim()) {
209
+ getCliLogger().error('ops:adapter-sync-missing-harness-path', {
210
+ hint: '先配置: hunter-mate config --harness-path <hunter-mate-harness 仓绝对路径>(或设 env HUNTER_MATE_HARNESS_PATH)',
211
+ });
212
+ process.exit(1);
213
+ }
214
+ try {
215
+ const r = await http().post('/adapters/sync', { harnessPath, dryRun: !!opts.dryRun });
216
+ printResult(r.data, opts.format);
217
+ }
218
+ catch (e) {
219
+ const detail = e.response?.data?.message || e.message;
220
+ console.error(`✗ ops:adapter-sync-failed: ${detail}`);
221
+ process.exit(1);
222
+ }
223
+ return;
224
+ }
225
+ printResult({ error: `unknown action: ${action}`, supported: ['list', 'extract', 'sync'] }, opts.format);
172
226
  });
173
227
  // ---------------------------------------------------------------------------
174
228
  // memory (站点记忆: 基于 selector/流程建议 的持久化 KV)
@@ -178,8 +232,7 @@ export const memoryCommand = withFormat(new Command('memory')
178
232
  .argument('[action]', '操作 (默认 list)', 'list')
179
233
  .argument('[site]', '站点名', '')
180
234
  .argument('[ref]', 'suggest/delete 的元素引用', '')).action((action, site, ref, opts) => {
181
- const HOME = process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
182
- const STORE = path.join(HOME, '.hunter-mate', 'memory.json');
235
+ const STORE = path.join(resolveDataDir(), 'memory.json');
183
236
  let store = {};
184
237
  try {
185
238
  store = JSON.parse(fs.readFileSync(STORE, 'utf-8'));
@@ -304,8 +357,7 @@ export const traceCommand = withFormat(new Command('trace')
304
357
  }
305
358
  catch (e) {
306
359
  // server 未就绪时: 本地追加 fallbacks 到 session JSONL 文件
307
- const HOME = process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
308
- const f = path.join(HOME, '.hunter-mate', 'sessions', `${sessionId}.trace.jsonl`);
360
+ const f = path.join(resolveDataDir(), 'sessions', `${sessionId}.trace.jsonl`);
309
361
  fs.appendFileSync(f, JSON.stringify({ type: opts.type, payload, ts: Date.now() }) + '\n');
310
362
  printResult({ ok: true, stored: 'local', file: f }, opts.format);
311
363
  }
@@ -318,8 +370,7 @@ export const traceCommand = withFormat(new Command('trace')
318
370
  }
319
371
  printResult({ ok: true, sessionId, note: '本地 trace 文件删除(server 侧请通过 api/recordings/* 管理)' }, opts.format);
320
372
  try {
321
- const HOME = process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
322
- const f = path.join(HOME, '.hunter-mate', 'sessions', `${sessionId}.trace.jsonl`);
373
+ const f = path.join(resolveDataDir(), 'sessions', `${sessionId}.trace.jsonl`);
323
374
  if (fs.existsSync(f))
324
375
  fs.unlinkSync(f);
325
376
  }
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * 三层偏好:scope=user(顾问本人)/ customer(用人单位)/ case(具体职位)。
4
+ * 查询 case 层时自动按 case → customer → user 逐层 merge,按 weight 降序。
5
+ */
6
+ export declare const preferenceCommand: Command;
@@ -0,0 +1,80 @@
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 SCOPES = ['user', 'customer', 'case'];
7
+ async function authedHeaders() {
8
+ const token = await getToken().catch(() => undefined);
9
+ return token ? { Authorization: `UTOKEN ${token}` } : {};
10
+ }
11
+ /**
12
+ * 三层偏好:scope=user(顾问本人)/ customer(用人单位)/ case(具体职位)。
13
+ * 查询 case 层时自动按 case → customer → user 逐层 merge,按 weight 降序。
14
+ */
15
+ export const preferenceCommand = new Command('preference').description('三层偏好沉淀(user/customer/case),用于人才推荐筛选');
16
+ preferenceCommand
17
+ .command('set')
18
+ .requiredOption('--scope <scope>', `范围: ${SCOPES.join('|')}`)
19
+ .requiredOption('--id <scopeId>', '范围 id(user id / customer id / job case id)')
20
+ .requiredOption('--dimension <dim>', '维度,如 industry/comp_size/school_tier/reject_reason/must_have')
21
+ .requiredOption('--value <json>', '偏好值(JSON 字符串,如 \'"electronics"\' 或 \'{"min":5}\')')
22
+ .option('--weight <w>', '权重(默认 1)', parseFloat)
23
+ .option('--evidence <text>', '证据来源:哪次推荐/哪条反馈')
24
+ .option('--json', 'JSON 输出')
25
+ .action(async (opts) => {
26
+ try {
27
+ if (!SCOPES.includes(opts.scope)) {
28
+ throw new Error(`--scope 必须是: ${SCOPES.join('|')}`);
29
+ }
30
+ let value;
31
+ try {
32
+ value = JSON.parse(opts.value);
33
+ }
34
+ catch {
35
+ throw new Error(`--value 必须是合法 JSON 字符串: ${opts.value}`);
36
+ }
37
+ const config = loadConfig();
38
+ const res = await axios.put(`${config.serverUrl}/preferences`, {
39
+ scope: opts.scope,
40
+ scopeId: parseInt(opts.id, 10),
41
+ items: [{ dimension: opts.dimension, value, weight: opts.weight, evidence: opts.evidence }],
42
+ }, { headers: await authedHeaders(), proxy: false });
43
+ if (opts.json)
44
+ console.log(JSON.stringify(res.data, null, 2));
45
+ else
46
+ console.log(`✓ 偏好已写入 (${opts.scope}#${opts.id}, ${opts.dimension}=${opts.value})`);
47
+ }
48
+ catch (err) {
49
+ printCommandError('preference:set-failed', err);
50
+ process.exit(1);
51
+ }
52
+ });
53
+ preferenceCommand
54
+ .command('get')
55
+ .requiredOption('--scope <scope>', `范围: ${SCOPES.join('|')}`)
56
+ .requiredOption('--id <scopeId>', '范围 id')
57
+ .option('--customer-id <id>', 'scope=case 时传客户 id,一并带出 customer/user 两层')
58
+ .option('--json', 'JSON 输出(默认 JSON:三层 merge 结构)')
59
+ .action(async (opts) => {
60
+ try {
61
+ if (!SCOPES.includes(opts.scope)) {
62
+ throw new Error(`--scope 必须是: ${SCOPES.join('|')}`);
63
+ }
64
+ const config = loadConfig();
65
+ const res = await axios.get(`${config.serverUrl}/preferences`, {
66
+ params: {
67
+ scope: opts.scope,
68
+ scopeId: parseInt(opts.id, 10),
69
+ ...(opts.customerId ? { customerId: parseInt(opts.customerId, 10) } : {}),
70
+ },
71
+ headers: await authedHeaders(),
72
+ proxy: false,
73
+ });
74
+ console.log(JSON.stringify(res.data, null, 2));
75
+ }
76
+ catch (err) {
77
+ printCommandError('preference:get-failed', err);
78
+ process.exit(1);
79
+ }
80
+ });
@@ -42,7 +42,7 @@ recordCommand
42
42
  console.log('请使用本地 LLM 对转写文本做 NER/摘要/实体链接后确认归档。');
43
43
  }
44
44
  catch (err) {
45
- printCommandError('record:upload-failed', err);
45
+ await printCommandError('record:upload-failed', err);
46
46
  process.exit(1);
47
47
  }
48
48
  });
@@ -42,7 +42,7 @@ reportCommand
42
42
  console.log('> 请使用本地 LLM 基于以上原始数据生成最终销售方案与交付节奏。');
43
43
  }
44
44
  catch (err) {
45
- printCommandError('report:generate-failed', err);
45
+ await printCommandError('report:generate-failed', err);
46
46
  process.exit(1);
47
47
  }
48
48
  });
@@ -40,7 +40,7 @@ export const searchCommand = new Command('search')
40
40
  console.log(JSON.stringify(data, null, 2));
41
41
  }
42
42
  catch (err) {
43
- printCommandError('search:failed', err);
43
+ await printCommandError('search:failed', err);
44
44
  process.exit(1);
45
45
  }
46
46
  });
@@ -1,4 +1,11 @@
1
1
  import { Command } from 'commander';
2
+ /**
3
+ * server.log 无界增长曾达 800M+ 挤占用户磁盘(FID-068)。
4
+ * 后台 daemon 启动前按大小轮转:超过 HM_LOG_MAX_BYTES(默认 50MB)时
5
+ * server.log → server.log.1 → …保留最近 LOG_KEEP_FILES 份,最旧丢弃。
6
+ * 纯 fs 实现,Windows 兼容(rename 前先清目标,Windows 不允许覆盖已有文件)。
7
+ */
8
+ export declare function rotateLogIfNeeded(logFile: string, maxBytes?: number, keep?: number): boolean;
2
9
  declare function tryReadPid(): number | null;
3
10
  export { tryReadPid };
4
11
  /** 定位 server 包根(dist 优先,ts-node 开发模式兜底);找不到返回 null */
@@ -3,11 +3,12 @@ import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { spawn } from 'child_process';
6
- import { loadConfig, saveConfig } from '../config.js';
6
+ import { loadConfig, saveConfig, resolveDataDir } from '../config.js';
7
7
  import { killGracefullySync } from '../utils/proc.js';
8
8
  import { findBundledAssets } from '../bundled-assets.js';
9
9
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
- const PID_FILE = () => path.join(process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.', '.hunter-mate', 'server.pid');
10
+ /** server.pid 等运行时文件随数据目录走(FID-071:dataDir 可配置后 server 状态文件一并迁移) */
11
+ const PID_FILE = () => path.join(resolveDataDir(), 'server.pid');
11
12
  const LOG_FILE = () => PID_FILE().replace(/server\.pid$/, 'server.log');
12
13
  const WATCHDOG_PID_FILE = () => PID_FILE().replace(/server\.pid$/, 'watchdog.pid');
13
14
  function tryReadPidFile(file) {
@@ -29,6 +30,38 @@ function cleanPidFile(file) {
29
30
  }
30
31
  catch { /* ignore */ }
31
32
  }
33
+ const LOG_MAX_BYTES = () => Number(process.env.HM_LOG_MAX_BYTES || 50 * 1024 * 1024);
34
+ const LOG_KEEP_FILES = 3;
35
+ /**
36
+ * server.log 无界增长曾达 800M+ 挤占用户磁盘(FID-068)。
37
+ * 后台 daemon 启动前按大小轮转:超过 HM_LOG_MAX_BYTES(默认 50MB)时
38
+ * server.log → server.log.1 → …保留最近 LOG_KEEP_FILES 份,最旧丢弃。
39
+ * 纯 fs 实现,Windows 兼容(rename 前先清目标,Windows 不允许覆盖已有文件)。
40
+ */
41
+ export function rotateLogIfNeeded(logFile, maxBytes = LOG_MAX_BYTES(), keep = LOG_KEEP_FILES) {
42
+ try {
43
+ if (!fs.existsSync(logFile))
44
+ return false;
45
+ if (fs.statSync(logFile).size <= maxBytes)
46
+ return false;
47
+ for (let i = keep - 1; i >= 1; i--) {
48
+ const src = `${logFile}.${i}`;
49
+ const dest = `${logFile}.${i + 1}`;
50
+ if (!fs.existsSync(src))
51
+ continue;
52
+ try {
53
+ fs.unlinkSync(dest);
54
+ }
55
+ catch { /* dest 不存在时忽略 */ }
56
+ fs.renameSync(src, dest);
57
+ }
58
+ fs.renameSync(logFile, `${logFile}.1`);
59
+ return true;
60
+ }
61
+ catch {
62
+ return false;
63
+ }
64
+ }
32
65
  function tryReadPid() {
33
66
  const pid = tryReadPidFile(PID_FILE());
34
67
  if (pid)
@@ -131,6 +164,8 @@ export async function runStart(opts) {
131
164
  ...process.env,
132
165
  PORT: String(port),
133
166
  HOST: host,
167
+ // FID-071: server 所有数据落点(db/备份/runtime.json/artifacts)由 CLI 的数据目录决定
168
+ HM_DATA_DIR: resolveDataDir(),
134
169
  };
135
170
  const nodeArgs = [];
136
171
  if (entry.endsWith('.ts')) {
@@ -148,6 +183,8 @@ export async function runStart(opts) {
148
183
  const logDir = path.dirname(LOG_FILE());
149
184
  if (!fs.existsSync(logDir))
150
185
  fs.mkdirSync(logDir, { recursive: true });
186
+ // 后台 daemon 启动前轮转 server.log(超 50MB 保留 3 份),防止无界增长挤占磁盘
187
+ rotateLogIfNeeded(LOG_FILE());
151
188
  // 后台模式由独立 watchdog 进程托管 server,实现崩溃后自动重启
152
189
  const watchdogEntry = path.resolve(__dirname, '../watchdog.js');
153
190
  if (!fs.existsSync(watchdogEntry)) {
@@ -163,6 +200,7 @@ export async function runStart(opts) {
163
200
  HM_SERVER_ENTRY: entry,
164
201
  HM_SERVER_LOG: LOG_FILE(),
165
202
  HM_SERVER_NODE_ARGS: nodeArgs.join(','),
203
+ HM_DATA_DIR: resolveDataDir(),
166
204
  PORT: String(port),
167
205
  HOST: host,
168
206
  };
@@ -1,8 +1,11 @@
1
1
  import { Command } from 'commander';
2
+ import { TokenIssuer } from '../keychain.js';
2
3
  import { InstalledVersions, SystemVersions } from '@hunter-mate/shared';
3
4
  export interface StatusResult {
4
5
  version: string;
5
6
  loggedIn: boolean;
7
+ /** token 签发方(hmh#43):saas 签发的 token 会被本地 server 拒绝 */
8
+ tokenIssuer?: TokenIssuer;
6
9
  server: 'running' | 'not running' | 'unknown';
7
10
  serverUrl: string;
8
11
  extensionCount: number;
@@ -15,6 +18,7 @@ export interface StatusResult {
15
18
  }>;
16
19
  installed?: InstalledVersions;
17
20
  live?: SystemVersions | null;
21
+ dataDir: string;
18
22
  }
19
23
  export declare function runStatus(): Promise<StatusResult>;
20
24
  export declare const statusCommand: Command;
@@ -1,13 +1,14 @@
1
1
  import { Command } from 'commander';
2
2
  import axios from 'axios';
3
3
  import { i18n } from '../i18n/index.js';
4
- import { loadConfig } from '../config.js';
5
- import { getToken } from '../keychain.js';
4
+ import { loadConfig, resolveDataDir } from '../config.js';
5
+ import { getTokenInfo } from '../keychain.js';
6
6
  import { readInstalledVersions, fetchLiveVersions } from '../version.js';
7
7
  import { getCliLogger } from '../logger.js';
8
8
  export async function runStatus() {
9
9
  const config = loadConfig();
10
- const token = await getToken();
10
+ const tokenInfo = await getTokenInfo();
11
+ const token = tokenInfo?.token ?? null;
11
12
  let serverStatus = 'unknown';
12
13
  let extensions = [];
13
14
  let live = null;
@@ -28,15 +29,18 @@ export async function runStatus() {
28
29
  serverStatus = 'not running';
29
30
  getCliLogger().warn('status:server-not-running', { serverUrl: config.serverUrl });
30
31
  }
32
+ const installed = readInstalledVersions();
31
33
  return {
32
- version: '0.1.0',
34
+ version: installed.cli ?? '0.0.0',
33
35
  loggedIn: !!token,
36
+ tokenIssuer: tokenInfo?.issuer,
34
37
  server: serverStatus,
35
38
  serverUrl: config.serverUrl,
36
39
  extensionCount: extensions.length,
37
40
  extensions,
38
- installed: readInstalledVersions(),
41
+ installed,
39
42
  live,
43
+ dataDir: resolveDataDir(),
40
44
  };
41
45
  }
42
46
  export const statusCommand = new Command('status')
@@ -49,8 +53,14 @@ export const statusCommand = new Command('status')
49
53
  return;
50
54
  }
51
55
  console.log(`${i18n.t('status.cli')}: ${status.version}`);
52
- console.log(`${i18n.t('status.loggedIn')}: ${status.loggedIn ? 'yes' : 'no'}`);
56
+ const loggedInText = status.loggedIn
57
+ ? status.tokenIssuer === 'saas'
58
+ ? 'yes (SaaS 签发 — 本地 server 会拒绝,启动 server 后请重新登录)'
59
+ : 'yes'
60
+ : 'no';
61
+ console.log(`${i18n.t('status.loggedIn')}: ${loggedInText}`);
53
62
  console.log(`${i18n.t('status.server')}: ${status.server} (${status.serverUrl})`);
63
+ console.log(`dataDir: ${status.dataDir}`);
54
64
  // 版本一致性: 磁盘(期望) vs 运行中
55
65
  if (status.installed) {
56
66
  const inst = status.installed;
@@ -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();
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();
@@ -59,5 +59,21 @@
59
59
  "purge.done.next": "To re-initialize, run",
60
60
  "purge.done.nothing": "Nothing to purge",
61
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."
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)"
63
79
  }