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.
- package/dist/assets/extension/background/service-worker.js +911 -159
- package/dist/assets/extension/build-info.json +1 -0
- package/dist/assets/extension/content/index.js +22 -4
- package/dist/assets/extension/manifest.json +5 -4
- package/dist/assets/extension/offscreen/offscreen.html +8 -0
- package/dist/assets/extension/offscreen/offscreen.js +149 -0
- package/dist/assets/extension/package.json +12 -4
- package/dist/assets/extension/sidepanel/index.css +2 -0
- package/dist/assets/extension/sidepanel/index.html +7 -394
- package/dist/assets/extension/sidepanel/index.js +53 -923
- package/dist/assets/server/app.module.js +12 -1
- package/dist/assets/server/app.module.js.map +1 -1
- package/dist/assets/server/auth/auth.controller.d.ts +7 -0
- package/dist/assets/server/auth/auth.controller.js +20 -0
- package/dist/assets/server/auth/auth.controller.js.map +1 -1
- package/dist/assets/server/auth/auth.middleware.js +9 -1
- package/dist/assets/server/auth/auth.middleware.js.map +1 -1
- package/dist/assets/server/auth/auth.service.d.ts +11 -0
- package/dist/assets/server/auth/auth.service.js +58 -1
- package/dist/assets/server/auth/auth.service.js.map +1 -1
- package/dist/assets/server/build-info.json +1 -1
- package/dist/assets/server/customer/customer.controller.d.ts +18 -0
- package/dist/assets/server/customer/customer.controller.js +81 -0
- package/dist/assets/server/customer/customer.controller.js.map +1 -0
- package/dist/assets/server/customer/customer.entity.d.ts +17 -0
- package/dist/assets/server/customer/customer.entity.js +82 -0
- package/dist/assets/server/customer/customer.entity.js.map +1 -0
- package/dist/assets/server/customer/customer.module.d.ts +2 -0
- package/dist/assets/server/customer/customer.module.js +28 -0
- package/dist/assets/server/customer/customer.module.js.map +1 -0
- package/dist/assets/server/customer/customer.service.d.ts +36 -0
- package/dist/assets/server/customer/customer.service.js +200 -0
- package/dist/assets/server/customer/customer.service.js.map +1 -0
- package/dist/assets/server/db/schema-validator.service.d.ts +2 -0
- package/dist/assets/server/db/schema-validator.service.js +32 -1
- package/dist/assets/server/db/schema-validator.service.js.map +1 -1
- package/dist/assets/server/db/sqlite.module.d.ts +1 -0
- package/dist/assets/server/db/sqlite.module.js +12 -2
- package/dist/assets/server/db/sqlite.module.js.map +1 -1
- package/dist/assets/server/devops/devops-sink-init.d.ts +1 -14
- package/dist/assets/server/devops/devops-sink-init.js +3 -8
- package/dist/assets/server/devops/devops-sink-init.js.map +1 -1
- package/dist/assets/server/extension/extension-control.controller.d.ts +15 -0
- package/dist/assets/server/extension/extension-control.controller.js +42 -2
- package/dist/assets/server/extension/extension-control.controller.js.map +1 -1
- package/dist/assets/server/extension/extension-control.service.d.ts +14 -1
- package/dist/assets/server/extension/extension-control.service.js +65 -2
- package/dist/assets/server/extension/extension-control.service.js.map +1 -1
- package/dist/assets/server/extraction/adapter.service.d.ts +3 -0
- package/dist/assets/server/extraction/adapter.service.js +127 -6
- package/dist/assets/server/extraction/adapter.service.js.map +1 -1
- package/dist/assets/server/extraction/anomaly-report.service.js +1 -1
- package/dist/assets/server/extraction/anomaly-report.service.js.map +1 -1
- package/dist/assets/server/extraction/entities/adapter-rule.entity.d.ts +2 -0
- package/dist/assets/server/extraction/entities/adapter-rule.entity.js +10 -0
- package/dist/assets/server/extraction/entities/adapter-rule.entity.js.map +1 -1
- package/dist/assets/server/extraction/extraction-entity.controller.d.ts +21 -3
- package/dist/assets/server/extraction/extraction-entity.controller.js +44 -9
- package/dist/assets/server/extraction/extraction-entity.controller.js.map +1 -1
- package/dist/assets/server/extraction/extraction.module.js +13 -3
- package/dist/assets/server/extraction/extraction.module.js.map +1 -1
- package/dist/assets/server/extraction/extraction.service.d.ts +4 -1
- package/dist/assets/server/extraction/extraction.service.js +59 -4
- package/dist/assets/server/extraction/extraction.service.js.map +1 -1
- package/dist/assets/server/extraction/harness-overlay.service.d.ts +41 -0
- package/dist/assets/server/extraction/harness-overlay.service.js +319 -0
- package/dist/assets/server/extraction/harness-overlay.service.js.map +1 -0
- package/dist/assets/server/extraction/liepin-extractor.d.ts +1 -0
- package/dist/assets/server/extraction/liepin-extractor.js +172 -0
- package/dist/assets/server/extraction/liepin-extractor.js.map +1 -0
- package/dist/assets/server/extraction/script-rule.js +4 -0
- package/dist/assets/server/extraction/script-rule.js.map +1 -1
- package/dist/assets/server/governance/action-audit.entity.d.ts +12 -0
- package/dist/assets/server/governance/action-audit.entity.js +66 -0
- package/dist/assets/server/governance/action-audit.entity.js.map +1 -0
- package/dist/assets/server/governance/governance.controller.d.ts +14 -0
- package/dist/assets/server/governance/governance.controller.js +77 -0
- package/dist/assets/server/governance/governance.controller.js.map +1 -0
- package/dist/assets/server/governance/governance.ingester.d.ts +20 -0
- package/dist/assets/server/governance/governance.ingester.js +114 -0
- package/dist/assets/server/governance/governance.ingester.js.map +1 -0
- package/dist/assets/server/governance/governance.module.d.ts +2 -0
- package/dist/assets/server/governance/governance.module.js +26 -0
- package/dist/assets/server/governance/governance.module.js.map +1 -0
- package/dist/assets/server/governance/governance.service.d.ts +114 -0
- package/dist/assets/server/governance/governance.service.js +356 -0
- package/dist/assets/server/governance/governance.service.js.map +1 -0
- package/dist/assets/server/jobcase/jobcase.controller.d.ts +1 -0
- package/dist/assets/server/jobcase/jobcase.entity.d.ts +1 -0
- package/dist/assets/server/jobcase/jobcase.entity.js +6 -0
- package/dist/assets/server/jobcase/jobcase.entity.js.map +1 -1
- package/dist/assets/server/jobcase/jobcase.service.d.ts +2 -0
- package/dist/assets/server/jobcase/jobcase.service.js +2 -1
- package/dist/assets/server/jobcase/jobcase.service.js.map +1 -1
- package/dist/assets/server/main.js +1 -0
- package/dist/assets/server/main.js.map +1 -1
- package/dist/assets/server/package.json +5 -4
- package/dist/assets/server/preference/preference.controller.d.ts +14 -0
- package/dist/assets/server/preference/preference.controller.js +66 -0
- package/dist/assets/server/preference/preference.controller.js.map +1 -0
- package/dist/assets/server/preference/preference.entity.d.ts +13 -0
- package/dist/assets/server/preference/preference.entity.js +77 -0
- package/dist/assets/server/preference/preference.entity.js.map +1 -0
- package/dist/assets/server/preference/preference.module.d.ts +2 -0
- package/dist/assets/server/preference/preference.module.js +26 -0
- package/dist/assets/server/preference/preference.module.js.map +1 -0
- package/dist/assets/server/preference/preference.service.d.ts +33 -0
- package/dist/assets/server/preference/preference.service.js +122 -0
- package/dist/assets/server/preference/preference.service.js.map +1 -0
- package/dist/assets/server/recording/recording-event-queue.service.d.ts +26 -0
- package/dist/assets/server/recording/recording-event-queue.service.js +115 -0
- package/dist/assets/server/recording/recording-event-queue.service.js.map +1 -0
- package/dist/assets/server/recording/recording-retention.service.d.ts +38 -0
- package/dist/assets/server/recording/recording-retention.service.js +174 -0
- package/dist/assets/server/recording/recording-retention.service.js.map +1 -0
- package/dist/assets/server/recording/recording-snapshot.service.d.ts +1 -0
- package/dist/assets/server/recording/recording-snapshot.service.js +4 -0
- package/dist/assets/server/recording/recording-snapshot.service.js.map +1 -1
- package/dist/assets/server/recording/recording.controller.d.ts +21 -1
- package/dist/assets/server/recording/recording.controller.js +37 -2
- package/dist/assets/server/recording/recording.controller.js.map +1 -1
- package/dist/assets/server/recording/recording.gateway.js +11 -0
- package/dist/assets/server/recording/recording.gateway.js.map +1 -1
- package/dist/assets/server/recording/recording.module.js +16 -2
- package/dist/assets/server/recording/recording.module.js.map +1 -1
- package/dist/assets/server/recording/recording.service.d.ts +10 -1
- package/dist/assets/server/recording/recording.service.js +31 -6
- package/dist/assets/server/recording/recording.service.js.map +1 -1
- package/dist/assets/server/remark/remark.controller.d.ts +1 -1
- package/dist/assets/server/remark/remark.controller.js +1 -10
- package/dist/assets/server/remark/remark.controller.js.map +1 -1
- package/dist/assets/server/remark/remark.entity.d.ts +1 -1
- package/dist/assets/server/remark/remark.entity.js.map +1 -1
- package/dist/assets/server/site/entities/site-registry.entity.d.ts +1 -1
- package/dist/assets/server/site/entities/site-registry.entity.js +1 -1
- package/dist/assets/server/site/entities/site-registry.entity.js.map +1 -1
- package/dist/assets/server/site/site.controller.d.ts +1 -1
- package/dist/assets/server/site/site.service.d.ts +5 -1
- package/dist/assets/server/site/site.service.js +13 -0
- package/dist/assets/server/site/site.service.js.map +1 -1
- package/dist/assets/server/system/system.controller.d.ts +1 -0
- package/dist/assets/server/system/system.controller.js +1 -1
- package/dist/assets/server/system/system.controller.js.map +1 -1
- package/dist/assets/server/system/system.service.d.ts +1 -0
- package/dist/assets/server/system/system.service.js +3 -2
- package/dist/assets/server/system/system.service.js.map +1 -1
- package/dist/cache/db.js +6 -6
- package/dist/commands/addons.js +2 -1
- package/dist/commands/cache.js +3 -4
- package/dist/commands/case.d.ts +67 -0
- package/dist/commands/case.js +101 -0
- package/dist/commands/config.d.ts +4 -0
- package/dist/commands/config.js +8 -1
- package/dist/commands/customer.d.ts +25 -0
- package/dist/commands/customer.js +137 -0
- package/dist/commands/doctor.js +119 -13
- package/dist/commands/extension.js +26 -32
- package/dist/commands/extras.d.ts +22 -3
- package/dist/commands/extras.js +116 -7
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +3 -4
- package/dist/commands/issue-env.d.ts +23 -0
- package/dist/commands/issue-env.js +101 -0
- package/dist/commands/job.d.ts +6 -2
- package/dist/commands/job.js +75 -20
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.js +85 -13
- package/dist/commands/migrate.d.ts +32 -0
- package/dist/commands/migrate.js +116 -0
- package/dist/commands/note.d.ts +3 -0
- package/dist/commands/note.js +75 -0
- package/dist/commands/ops.js +82 -15
- package/dist/commands/preference.d.ts +6 -0
- package/dist/commands/preference.js +80 -0
- package/dist/commands/record.js +2 -4
- package/dist/commands/report.js +2 -4
- package/dist/commands/search.js +9 -2
- package/dist/commands/server.d.ts +9 -2
- package/dist/commands/server.js +118 -19
- package/dist/commands/status.d.ts +4 -0
- package/dist/commands/status.js +16 -6
- package/dist/commands/update.js +8 -9
- package/dist/config.d.ts +21 -0
- package/dist/config.js +27 -0
- package/dist/i18n/locales/en-US.json +19 -1
- package/dist/i18n/locales/zh-CN.json +19 -1
- package/dist/index.js +52 -2
- package/dist/keychain.d.ts +16 -1
- package/dist/keychain.js +43 -16
- package/dist/logger.js +2 -2
- package/dist/updater.d.ts +1 -1
- package/dist/updater.js +24 -7
- package/dist/utils/command-error.d.ts +11 -0
- package/dist/utils/command-error.js +47 -0
- package/dist/utils/link.d.ts +18 -0
- package/dist/utils/link.js +83 -0
- package/dist/utils/proxy-detect.d.ts +20 -0
- package/dist/utils/proxy-detect.js +96 -0
- package/dist/utils/script-path.d.ts +5 -0
- package/dist/utils/script-path.js +27 -0
- package/dist/version.js +34 -9
- package/dist/watchdog.d.ts +23 -1
- package/dist/watchdog.js +97 -6
- package/dist/web/dist/404.html +1 -0
- package/dist/web/dist/_next/static/WdJr3NhHjlu5xN0qOap1k/_buildManifest.js +1 -0
- package/dist/web/dist/_next/static/WdJr3NhHjlu5xN0qOap1k/_ssgManifest.js +1 -0
- package/dist/web/dist/_next/static/chunks/166.f30be223f694f8ee.js +1 -0
- package/dist/web/dist/_next/static/chunks/1e6d5a0c.0488932e17ecdf24.js +15 -0
- package/dist/web/dist/_next/static/chunks/539-5d0e8c5c238c56f8.js +1 -0
- package/dist/web/dist/_next/static/chunks/589-d632233641f35681.js +1 -0
- package/dist/web/dist/_next/static/chunks/671.3b8e9cc96b25b6ea.js +1 -0
- package/dist/web/dist/_next/static/chunks/935-99f57b7d4151d9e7.js +5 -0
- package/dist/web/dist/_next/static/chunks/a7067448-96dd4b8457c447e8.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/_not-found/page-fdf1a13a7656886a.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/auth/verify/page-8e5cff53d6f7d8e1.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/companies/page-e38a69e1929d0953.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/entity/page-250f8a9b6d91ab19.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/jobs/page-6a281776fec065f6.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/layout-83e623e973c1693f.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/logs/page-d478031d49f7ba00.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/matching/jobs/[id]/talents/page-f57f4b7529823601.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/matching/portraits/[id]/jobs/page-614d1e62d7b9d215.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/page-e3392de08ae81fba.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/parselog/page-58d3acece95b4089.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/recordings/page-e2e2e3c3a64083fb.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/snapshots/[id]/agent-prompt/page-fd3b7d7373b8f8b7.js +1 -0
- package/dist/web/dist/_next/static/chunks/app/talents/page-30748338da20d7e0.js +1 -0
- package/dist/web/dist/_next/static/chunks/framework-b3a558b17a070965.js +1 -0
- package/dist/web/dist/_next/static/chunks/main-5a4e0cd75dd7f04b.js +1 -0
- package/dist/web/dist/_next/static/chunks/main-app-80b038a312917d90.js +1 -0
- package/dist/web/dist/_next/static/chunks/pages/_app-da7d628f49271ece.js +4 -0
- package/dist/web/dist/_next/static/chunks/pages/_error-17756f67e0a11350.js +1 -0
- package/dist/web/dist/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- package/dist/web/dist/_next/static/chunks/webpack-1784baecc6fdcf0b.js +1 -0
- package/dist/web/dist/_next/static/css/db881c5b3072b88a.css +3 -0
- package/dist/web/dist/_next/static/css/f60d210841fab76b.css +1 -0
- package/dist/web/dist/auth/verify.html +1 -0
- package/dist/web/dist/auth/verify.txt +8 -0
- package/dist/web/dist/companies.html +1 -0
- package/dist/web/dist/companies.txt +7 -0
- package/dist/web/dist/entity.html +1 -0
- package/dist/web/dist/entity.txt +8 -0
- package/dist/web/dist/index.html +1 -0
- package/dist/web/dist/index.txt +8 -0
- package/dist/web/dist/jobs.html +1 -0
- package/dist/web/dist/jobs.txt +7 -0
- package/dist/web/dist/logs.html +1 -0
- package/dist/web/dist/logs.txt +8 -0
- package/dist/web/dist/matching/jobs/placeholder/talents.html +1 -0
- package/dist/web/dist/matching/jobs/placeholder/talents.txt +8 -0
- package/dist/web/dist/matching/portraits/placeholder/jobs.html +1 -0
- package/dist/web/dist/matching/portraits/placeholder/jobs.txt +8 -0
- package/dist/web/dist/parselog.html +1 -0
- package/dist/web/dist/parselog.txt +8 -0
- package/dist/web/dist/recordings.html +1 -0
- package/dist/web/dist/recordings.txt +8 -0
- package/dist/web/dist/snapshots/placeholder/agent-prompt.html +1 -0
- package/dist/web/dist/snapshots/placeholder/agent-prompt.txt +8 -0
- package/dist/web/dist/talents.html +1 -0
- package/dist/web/dist/talents.txt +7 -0
- package/package.json +26 -6
package/dist/commands/ops.js
CHANGED
|
@@ -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
|
-
//
|
|
80
|
-
|
|
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'))
|
|
@@ -91,7 +121,23 @@ withFormat(recordingsCommand.command('show <sessionId>').description('查看录
|
|
|
91
121
|
printResult({ detail, parsed }, opts.format);
|
|
92
122
|
}
|
|
93
123
|
catch (e) {
|
|
94
|
-
|
|
124
|
+
console.error(`✗ ops:recordings-show-failed: ${e.message}`);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
// 手动触发保留期清理(FID-057 #17-B);server 另有每日定时清理
|
|
129
|
+
recordingsCommand
|
|
130
|
+
.command('prune')
|
|
131
|
+
.description('按保留期清理录制事件与快照 (POST /api/recordings/prune)')
|
|
132
|
+
.option('--days <n>', '保留天数(默认取 server 的 RECORDING_RETENTION_DAYS,通常 30)')
|
|
133
|
+
.action(async (opts) => {
|
|
134
|
+
try {
|
|
135
|
+
const body = opts.days !== undefined ? { days: Number(opts.days) } : {};
|
|
136
|
+
const r = await http().post('/api/recordings/prune', body);
|
|
137
|
+
console.log(JSON.stringify(r.data, null, 2));
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
console.error(`✗ ops:recordings-prune-failed: ${e.message}`);
|
|
95
141
|
process.exit(1);
|
|
96
142
|
}
|
|
97
143
|
});
|
|
@@ -99,11 +145,12 @@ withFormat(recordingsCommand.command('show <sessionId>').description('查看录
|
|
|
99
145
|
// adapter (OpenCLI 风格)
|
|
100
146
|
// ---------------------------------------------------------------------------
|
|
101
147
|
export const adapterCommand = withFormat(new Command('adapter')
|
|
102
|
-
.description('站点适配器(对应 chromepilot adapter): list | extract <url>')
|
|
103
|
-
.argument('[action]', '操作: list | extract', 'list')
|
|
148
|
+
.description('站点适配器(对应 chromepilot adapter): list | extract <url> | sync')
|
|
149
|
+
.argument('[action]', '操作: list | extract | sync', 'list')
|
|
104
150
|
.argument('[target]', 'list: 站点名过滤;extract: 页面 URL', '')
|
|
105
151
|
.option('--tab <pageId>', 'extract: 指定目标 tab(page id;默认复用同站点 tab 或新建后台 tab,不抢焦点)')
|
|
106
|
-
.option('--settle-ms <n>', 'extract: 导航后首查等待 ms(默认 1200)', '1200')
|
|
152
|
+
.option('--settle-ms <n>', 'extract: 导航后首查等待 ms(默认 1200)', '1200')
|
|
153
|
+
.option('--dry-run', 'sync: 只统计差异不写库', false)).action(async (action, target, opts) => {
|
|
107
154
|
if (action === 'list' || !action) {
|
|
108
155
|
try {
|
|
109
156
|
const r = await http().get('/adapters');
|
|
@@ -116,6 +163,7 @@ export const adapterCommand = withFormat(new Command('adapter')
|
|
|
116
163
|
// server 暂未实现 /adapters 时退回内置清单
|
|
117
164
|
printResult({ note: `GET /adapters failed (${e.message}),fallback 内置清单`, adapters: [
|
|
118
165
|
{ platform: 'linkedin', host: 'linkedin.com', command: 'hm.linkedin.extract' },
|
|
166
|
+
{ platform: 'liepin', host: 'h.liepin.com', command: 'hm.liepin.extract' },
|
|
119
167
|
] }, opts.format);
|
|
120
168
|
}
|
|
121
169
|
return;
|
|
@@ -147,12 +195,34 @@ export const adapterCommand = withFormat(new Command('adapter')
|
|
|
147
195
|
}
|
|
148
196
|
catch (e) {
|
|
149
197
|
const detail = e.response?.data?.message || e.message;
|
|
150
|
-
|
|
198
|
+
console.error(`✗ ops:adapter-extract-failed: ${detail}`);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
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}`);
|
|
151
221
|
process.exit(1);
|
|
152
222
|
}
|
|
153
223
|
return;
|
|
154
224
|
}
|
|
155
|
-
printResult({ error: `unknown action: ${action}`, supported: ['list', 'extract'] }, opts.format);
|
|
225
|
+
printResult({ error: `unknown action: ${action}`, supported: ['list', 'extract', 'sync'] }, opts.format);
|
|
156
226
|
});
|
|
157
227
|
// ---------------------------------------------------------------------------
|
|
158
228
|
// memory (站点记忆: 基于 selector/流程建议 的持久化 KV)
|
|
@@ -162,8 +232,7 @@ export const memoryCommand = withFormat(new Command('memory')
|
|
|
162
232
|
.argument('[action]', '操作 (默认 list)', 'list')
|
|
163
233
|
.argument('[site]', '站点名', '')
|
|
164
234
|
.argument('[ref]', 'suggest/delete 的元素引用', '')).action((action, site, ref, opts) => {
|
|
165
|
-
const
|
|
166
|
-
const STORE = path.join(HOME, '.hunter-mate', 'memory.json');
|
|
235
|
+
const STORE = path.join(resolveDataDir(), 'memory.json');
|
|
167
236
|
let store = {};
|
|
168
237
|
try {
|
|
169
238
|
store = JSON.parse(fs.readFileSync(STORE, 'utf-8'));
|
|
@@ -288,8 +357,7 @@ export const traceCommand = withFormat(new Command('trace')
|
|
|
288
357
|
}
|
|
289
358
|
catch (e) {
|
|
290
359
|
// server 未就绪时: 本地追加 fallbacks 到 session JSONL 文件
|
|
291
|
-
const
|
|
292
|
-
const f = path.join(HOME, '.hunter-mate', 'sessions', `${sessionId}.trace.jsonl`);
|
|
360
|
+
const f = path.join(resolveDataDir(), 'sessions', `${sessionId}.trace.jsonl`);
|
|
293
361
|
fs.appendFileSync(f, JSON.stringify({ type: opts.type, payload, ts: Date.now() }) + '\n');
|
|
294
362
|
printResult({ ok: true, stored: 'local', file: f }, opts.format);
|
|
295
363
|
}
|
|
@@ -302,8 +370,7 @@ export const traceCommand = withFormat(new Command('trace')
|
|
|
302
370
|
}
|
|
303
371
|
printResult({ ok: true, sessionId, note: '本地 trace 文件删除(server 侧请通过 api/recordings/* 管理)' }, opts.format);
|
|
304
372
|
try {
|
|
305
|
-
const
|
|
306
|
-
const f = path.join(HOME, '.hunter-mate', 'sessions', `${sessionId}.trace.jsonl`);
|
|
373
|
+
const f = path.join(resolveDataDir(), 'sessions', `${sessionId}.trace.jsonl`);
|
|
307
374
|
if (fs.existsSync(f))
|
|
308
375
|
fs.unlinkSync(f);
|
|
309
376
|
}
|
|
@@ -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
|
+
});
|
package/dist/commands/record.js
CHANGED
|
@@ -5,7 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
5
5
|
import { loadConfig } from '../config.js';
|
|
6
6
|
import { getToken } from '../keychain.js';
|
|
7
7
|
import { ensureCacheDir } from '../cache/db.js';
|
|
8
|
-
import {
|
|
8
|
+
import { printCommandError } from '../utils/command-error.js';
|
|
9
9
|
export async function runRecordTranscript(filePath, options) {
|
|
10
10
|
const config = loadConfig();
|
|
11
11
|
const token = await getToken();
|
|
@@ -42,9 +42,7 @@ recordCommand
|
|
|
42
42
|
console.log('请使用本地 LLM 对转写文本做 NER/摘要/实体链接后确认归档。');
|
|
43
43
|
}
|
|
44
44
|
catch (err) {
|
|
45
|
-
|
|
46
|
-
error: err.response?.data?.message || err.message,
|
|
47
|
-
});
|
|
45
|
+
await printCommandError('record:upload-failed', err);
|
|
48
46
|
process.exit(1);
|
|
49
47
|
}
|
|
50
48
|
});
|
package/dist/commands/report.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import axios from 'axios';
|
|
3
3
|
import { loadConfig } from '../config.js';
|
|
4
4
|
import { getToken } from '../keychain.js';
|
|
5
|
-
import {
|
|
5
|
+
import { printCommandError } from '../utils/command-error.js';
|
|
6
6
|
export async function runReportSales(customerId, options) {
|
|
7
7
|
const config = loadConfig();
|
|
8
8
|
const token = await getToken();
|
|
@@ -42,9 +42,7 @@ reportCommand
|
|
|
42
42
|
console.log('> 请使用本地 LLM 基于以上原始数据生成最终销售方案与交付节奏。');
|
|
43
43
|
}
|
|
44
44
|
catch (err) {
|
|
45
|
-
|
|
46
|
-
error: err.response?.data?.message || err.message,
|
|
47
|
-
});
|
|
45
|
+
await printCommandError('report:generate-failed', err);
|
|
48
46
|
process.exit(1);
|
|
49
47
|
}
|
|
50
48
|
});
|
package/dist/commands/search.js
CHANGED
|
@@ -3,6 +3,7 @@ import axios from 'axios';
|
|
|
3
3
|
import { loadConfig } from '../config.js';
|
|
4
4
|
import { getToken } from '../keychain.js';
|
|
5
5
|
import { i18n } from '../i18n/index.js';
|
|
6
|
+
import { printCommandError } from '../utils/command-error.js';
|
|
6
7
|
export async function runSearch(options) {
|
|
7
8
|
const config = loadConfig();
|
|
8
9
|
const token = await getToken();
|
|
@@ -34,6 +35,12 @@ export const searchCommand = new Command('search')
|
|
|
34
35
|
.option('--source <sources>', i18n.t('search.source'), 'u_person')
|
|
35
36
|
.option('--top <n>', i18n.t('search.top'), '10')
|
|
36
37
|
.action(async (options) => {
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
try {
|
|
39
|
+
const data = await runSearch(options);
|
|
40
|
+
console.log(JSON.stringify(data, null, 2));
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
await printCommandError('search:failed', err);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
39
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 */
|
|
@@ -14,10 +21,10 @@ export declare function runStart(opts: {
|
|
|
14
21
|
url: string;
|
|
15
22
|
msg: string;
|
|
16
23
|
}>;
|
|
17
|
-
export declare function runStop(): {
|
|
24
|
+
export declare function runStop(): Promise<{
|
|
18
25
|
stopped: boolean;
|
|
19
26
|
pid: number | null;
|
|
20
27
|
msg: string;
|
|
21
|
-
}
|
|
28
|
+
}>;
|
|
22
29
|
export declare const startCommand: Command;
|
|
23
30
|
export declare const stopCommand: Command;
|
package/dist/commands/server.js
CHANGED
|
@@ -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
|
-
|
|
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)
|
|
@@ -55,25 +88,50 @@ export function findServerRoot() {
|
|
|
55
88
|
}
|
|
56
89
|
return null;
|
|
57
90
|
}
|
|
91
|
+
/** 探活:server /health 是否可访问(短超时,永不 throw) */
|
|
92
|
+
async function probeServerHealth(url, timeoutMs = 1500) {
|
|
93
|
+
try {
|
|
94
|
+
const res = await fetch(`${url}/health`, { signal: AbortSignal.timeout(timeoutMs) });
|
|
95
|
+
return res.ok;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** 从 config.serverUrl 解析默认端口(与 runStart 逻辑一致) */
|
|
102
|
+
function defaultPortFromConfig(serverUrl) {
|
|
103
|
+
return new URL(serverUrl).port ? Number(new URL(serverUrl).port) : 34570;
|
|
104
|
+
}
|
|
58
105
|
/** 对应 ChromePilot start: 启动本地 NestJS Server(提供 REST + WebSocket + SPA) */
|
|
59
106
|
export async function runStart(opts) {
|
|
60
107
|
const config = loadConfig();
|
|
61
|
-
const defaultPort =
|
|
62
|
-
? Number(new URL(config.serverUrl).port)
|
|
63
|
-
: 34570;
|
|
108
|
+
const defaultPort = defaultPortFromConfig(config.serverUrl);
|
|
64
109
|
const port = opts.port ?? defaultPort;
|
|
65
110
|
const host = opts.host ?? '127.0.0.1';
|
|
66
111
|
const url = `http://${host}:${port}`;
|
|
112
|
+
// 启动过程中的告警说明(会拼进最终返回 msg,保证用户看得见)
|
|
113
|
+
const notes = [];
|
|
67
114
|
const existing = tryReadPid();
|
|
68
115
|
if (existing) {
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (
|
|
74
|
-
|
|
116
|
+
// pid 文件存在 ≠ server 存活。pid 可能被系统中其他进程复用
|
|
117
|
+
// (Windows 现场:残留 node.exe 复用了 pid → 误判 already running,
|
|
118
|
+
// server 实际从未被拉起,日志停在旧时间戳,用户无任何感知)。
|
|
119
|
+
// 因此必须先 /health 探活再决定是否走"已在运行"分支。
|
|
120
|
+
if (await probeServerHealth(url)) {
|
|
121
|
+
// 已在运行: 先做版本一致性检测(磁盘版本 vs 运行版本,server 不一致则自动重启)
|
|
122
|
+
// 动态 import 避免与 version.ts 的循环依赖(version.ts 静态引入本文件的 runStart/runStop)
|
|
123
|
+
const { ensureRuntimeConsistency } = await import('../version.js');
|
|
124
|
+
const report = await ensureRuntimeConsistency({ force: true });
|
|
125
|
+
if (report.actions.serverRestart && report.performed.length) {
|
|
126
|
+
return { started: true, pid: null, url, msg: report.performed.join('; ') };
|
|
127
|
+
}
|
|
128
|
+
return { started: false, pid: existing, url, msg: `server already running (pid=${existing})` };
|
|
75
129
|
}
|
|
76
|
-
|
|
130
|
+
// stale pid:进程号存活但不是我们的 server(或 server 已僵死)。清理后继续启动,
|
|
131
|
+
// 不 kill —— 那个进程可能属于别的程序(误杀 foreign 进程红线)。
|
|
132
|
+
notes.push(`⚠ 检测到 stale server.pid (pid=${existing}):该进程存活但 ${url}/health 无响应,` +
|
|
133
|
+
`疑似 pid 被其他进程复用,已忽略并继续启动`);
|
|
134
|
+
cleanPidFile(PID_FILE());
|
|
77
135
|
}
|
|
78
136
|
// server 入口: bundled 模式下 <cli>/dist/assets/server/main.js;monorepo 下 packages/server/dist/main.js 或 src/main.ts(开发模式 ts-node)
|
|
79
137
|
const serverRoot = findServerRoot();
|
|
@@ -106,6 +164,8 @@ export async function runStart(opts) {
|
|
|
106
164
|
...process.env,
|
|
107
165
|
PORT: String(port),
|
|
108
166
|
HOST: host,
|
|
167
|
+
// FID-071: server 所有数据落点(db/备份/runtime.json/artifacts)由 CLI 的数据目录决定
|
|
168
|
+
HM_DATA_DIR: resolveDataDir(),
|
|
109
169
|
};
|
|
110
170
|
const nodeArgs = [];
|
|
111
171
|
if (entry.endsWith('.ts')) {
|
|
@@ -123,6 +183,8 @@ export async function runStart(opts) {
|
|
|
123
183
|
const logDir = path.dirname(LOG_FILE());
|
|
124
184
|
if (!fs.existsSync(logDir))
|
|
125
185
|
fs.mkdirSync(logDir, { recursive: true });
|
|
186
|
+
// 后台 daemon 启动前轮转 server.log(超 50MB 保留 3 份),防止无界增长挤占磁盘
|
|
187
|
+
rotateLogIfNeeded(LOG_FILE());
|
|
126
188
|
// 后台模式由独立 watchdog 进程托管 server,实现崩溃后自动重启
|
|
127
189
|
const watchdogEntry = path.resolve(__dirname, '../watchdog.js');
|
|
128
190
|
if (!fs.existsSync(watchdogEntry)) {
|
|
@@ -138,6 +200,7 @@ export async function runStart(opts) {
|
|
|
138
200
|
HM_SERVER_ENTRY: entry,
|
|
139
201
|
HM_SERVER_LOG: LOG_FILE(),
|
|
140
202
|
HM_SERVER_NODE_ARGS: nodeArgs.join(','),
|
|
203
|
+
HM_DATA_DIR: resolveDataDir(),
|
|
141
204
|
PORT: String(port),
|
|
142
205
|
HOST: host,
|
|
143
206
|
};
|
|
@@ -150,8 +213,11 @@ export async function runStart(opts) {
|
|
|
150
213
|
wd.unref();
|
|
151
214
|
if (wd.pid)
|
|
152
215
|
fs.writeFileSync(WATCHDOG_PID_FILE(), String(wd.pid));
|
|
153
|
-
// 等待 server health
|
|
154
|
-
|
|
216
|
+
// 等待 server health 就绪。窗口给足:NestJS 全量路由映射 + TypeORM schema 同步
|
|
217
|
+
// 在慢机/Windows 上实测 10-15s,原 8s 会在 route mapping 阶段误报"未就绪"
|
|
218
|
+
// (server 实际 3 秒后就绪,watchdog 会保活,误报只会误导用户)。
|
|
219
|
+
const probeDeadlineMs = 30_000;
|
|
220
|
+
const deadline = Date.now() + probeDeadlineMs;
|
|
155
221
|
let ready = false;
|
|
156
222
|
while (Date.now() < deadline) {
|
|
157
223
|
await new Promise((r) => setTimeout(r, 500));
|
|
@@ -179,7 +245,8 @@ export async function runStart(opts) {
|
|
|
179
245
|
started: false,
|
|
180
246
|
pid: wd.pid ?? null,
|
|
181
247
|
url,
|
|
182
|
-
msg:
|
|
248
|
+
msg: (notes.length ? notes.join('\n') + '\n' : '') +
|
|
249
|
+
`✗ server watchdog 启动后 ${probeDeadlineMs / 1000}s 内未就绪(pid=${wd.pid}),日志尾部:\n${tail || '(server.log 为空)'}\n` +
|
|
183
250
|
`排障: hunter-mate doctor 或 TROUBLESHOOTING.md#server-启动即崩abi`,
|
|
184
251
|
};
|
|
185
252
|
}
|
|
@@ -193,12 +260,34 @@ export async function runStart(opts) {
|
|
|
193
260
|
}
|
|
194
261
|
}
|
|
195
262
|
catch { /* ignore */ }
|
|
196
|
-
return {
|
|
263
|
+
return {
|
|
264
|
+
started: true,
|
|
265
|
+
pid: wd.pid ?? null,
|
|
266
|
+
url,
|
|
267
|
+
msg: (notes.length ? notes.join('\n') + '\n' : '') +
|
|
268
|
+
`server watchdog started (pid=${wd.pid}) on ${url} — logs: ${LOG_FILE()}`,
|
|
269
|
+
};
|
|
197
270
|
}
|
|
198
|
-
export function runStop() {
|
|
271
|
+
export async function runStop() {
|
|
272
|
+
const config = loadConfig();
|
|
273
|
+
const url = `http://127.0.0.1:${defaultPortFromConfig(config.serverUrl)}`;
|
|
274
|
+
// kill 任何进程前先 /health 确认它真的是我们的 server。
|
|
275
|
+
// pid 文件可能被复用(Windows 现场:残留 node.exe 占用 pid),误杀 foreign 进程不可接受。
|
|
276
|
+
const healthy = await probeServerHealth(url);
|
|
199
277
|
// 优先停止 watchdog,让它去停止子 server
|
|
200
278
|
const wdPid = tryReadPidFile(WATCHDOG_PID_FILE());
|
|
201
279
|
if (wdPid) {
|
|
280
|
+
if (!healthy) {
|
|
281
|
+
// server 不响应时不杀 watchdog pid(可能已被其他进程复用),只清理 pid 文件
|
|
282
|
+
cleanPidFile(WATCHDOG_PID_FILE());
|
|
283
|
+
cleanPidFile(PID_FILE());
|
|
284
|
+
return {
|
|
285
|
+
stopped: false,
|
|
286
|
+
pid: wdPid,
|
|
287
|
+
msg: `✗ server 无响应(${url}/health 不可达),watchdog pid=${wdPid} 未杀(疑似 stale pid,` +
|
|
288
|
+
`可能已被其他进程复用)。已清理 pid 文件;如确有残留进程请手工确认后 kill`,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
202
291
|
const ok = killGracefullySync(wdPid);
|
|
203
292
|
cleanPidFile(WATCHDOG_PID_FILE());
|
|
204
293
|
if (ok) {
|
|
@@ -218,6 +307,16 @@ export function runStop() {
|
|
|
218
307
|
const pid = tryReadPid();
|
|
219
308
|
if (!pid)
|
|
220
309
|
return { stopped: false, pid: null, msg: 'no server.pid found or process dead' };
|
|
310
|
+
if (!healthy) {
|
|
311
|
+
// pid 存活但 server 不响应:不 kill(可能复用了别人的 pid),只清 pid 文件
|
|
312
|
+
cleanPidFile(PID_FILE());
|
|
313
|
+
return {
|
|
314
|
+
stopped: false,
|
|
315
|
+
pid,
|
|
316
|
+
msg: `✗ pid=${pid} 存活但 server 无响应,未发送 kill 信号(疑似 stale pid,` +
|
|
317
|
+
`可能属于其他进程)。已清理 pid 文件;如确认是僵死 server 请手工 kill`,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
221
320
|
const ok = killGracefullySync(pid);
|
|
222
321
|
if (!ok) {
|
|
223
322
|
return { stopped: false, pid, msg: `failed to signal ${pid}` };
|
|
@@ -238,8 +337,8 @@ export const startCommand = new Command('start')
|
|
|
238
337
|
});
|
|
239
338
|
export const stopCommand = new Command('stop')
|
|
240
339
|
.description('停止本地 server(读 ~/.hunter-mate/server.pid 并 SIGTERM)')
|
|
241
|
-
.action(() => {
|
|
242
|
-
const r = runStop();
|
|
340
|
+
.action(async () => {
|
|
341
|
+
const r = await runStop();
|
|
243
342
|
console.log(r.msg);
|
|
244
343
|
if (!r.stopped && r.pid)
|
|
245
344
|
process.exit(1);
|
|
@@ -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;
|
package/dist/commands/status.js
CHANGED
|
@@ -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 {
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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;
|