hunter-mate 0.1.7 → 0.1.11
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 +808 -153
- package/dist/assets/extension/build-info.json +1 -0
- package/dist/assets/extension/content/index.js +23 -5
- 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 +8 -1
- 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 +11 -1
- package/dist/assets/server/app.module.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/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 +17 -1
- package/dist/assets/server/extension/extension-control.controller.js +68 -12
- package/dist/assets/server/extension/extension-control.controller.js.map +1 -1
- package/dist/assets/server/extension/extension-control.service.d.ts +6 -1
- package/dist/assets/server/extension/extension-control.service.js +27 -1
- package/dist/assets/server/extension/extension-control.service.js.map +1 -1
- package/dist/assets/server/extension/extension.module.js +2 -1
- package/dist/assets/server/extension/extension.module.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 +71 -13
- 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 +14 -2
- 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.command-recorder.d.ts +17 -0
- package/dist/assets/server/governance/governance.command-recorder.js +98 -0
- package/dist/assets/server/governance/governance.command-recorder.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 +21 -0
- package/dist/assets/server/governance/governance.ingester.js +117 -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 +27 -0
- package/dist/assets/server/governance/governance.module.js.map +1 -0
- package/dist/assets/server/governance/governance.service.d.ts +122 -0
- package/dist/assets/server/governance/governance.service.js +398 -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 +1 -1
- 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.js +1 -1
- package/dist/assets/server/recording/recording-event-queue.service.js.map +1 -1
- package/dist/assets/server/recording/recording-retention.service.d.ts +19 -2
- package/dist/assets/server/recording/recording-retention.service.js +95 -6
- package/dist/assets/server/recording/recording-retention.service.js.map +1 -1
- 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 +10 -0
- package/dist/assets/server/recording/recording.controller.js +19 -0
- package/dist/assets/server/recording/recording.controller.js.map +1 -1
- package/dist/assets/server/recording/recording.gateway.js +15 -0
- package/dist/assets/server/recording/recording.gateway.js.map +1 -1
- package/dist/assets/server/recording/recording.module.js +6 -1
- package/dist/assets/server/recording/recording.module.js.map +1 -1
- package/dist/assets/server/recording/recording.service.d.ts +5 -0
- package/dist/assets/server/recording/recording.service.js +10 -0
- 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 +1 -1
- 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 +33 -9
- package/dist/commands/extension.js +21 -16
- package/dist/commands/extras.d.ts +18 -3
- package/dist/commands/extras.js +162 -5
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +3 -4
- package/dist/commands/install.d.ts +5 -0
- package/dist/commands/install.js +22 -42
- package/dist/commands/issue-env.d.ts +23 -0
- package/dist/commands/issue-env.js +101 -0
- package/dist/commands/job.d.ts +2 -1
- package/dist/commands/job.js +25 -5
- package/dist/commands/login.d.ts +4 -0
- package/dist/commands/login.js +57 -4
- 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 +64 -13
- package/dist/commands/preference.d.ts +6 -0
- package/dist/commands/preference.js +80 -0
- package/dist/commands/record.js +1 -1
- package/dist/commands/report.js +1 -1
- package/dist/commands/search.js +1 -1
- package/dist/commands/server.d.ts +12 -0
- package/dist/commands/server.js +182 -18
- package/dist/commands/status.d.ts +4 -0
- package/dist/commands/status.js +16 -6
- package/dist/commands/update.js +7 -8
- package/dist/config.d.ts +21 -0
- package/dist/config.js +27 -0
- package/dist/extension-sync.d.ts +35 -0
- package/dist/extension-sync.js +159 -0
- package/dist/i18n/locales/en-US.json +17 -1
- package/dist/i18n/locales/zh-CN.json +17 -1
- package/dist/index.js +17 -0
- package/dist/keychain.d.ts +16 -1
- package/dist/keychain.js +43 -16
- package/dist/logger.js +2 -2
- package/dist/postinstall.d.ts +1 -0
- package/dist/postinstall.js +31 -0
- package/dist/updater.js +2 -2
- package/dist/utils/artifact-checksum.d.ts +4 -0
- package/dist/utils/artifact-checksum.js +40 -0
- package/dist/utils/command-error.d.ts +4 -2
- package/dist/utils/command-error.js +19 -3
- package/dist/utils/link.d.ts +18 -0
- package/dist/utils/link.js +83 -0
- package/dist/utils/local-http.d.ts +7 -0
- package/dist/utils/local-http.js +30 -0
- package/dist/version.js +86 -49
- package/dist/watchdog.d.ts +8 -0
- package/dist/watchdog.js +49 -9
- package/dist/web/dist/404.html +1 -1
- package/dist/web/dist/_next/static/chunks/app/logs/page-d478031d49f7ba00.js +1 -0
- package/dist/web/dist/_next/static/chunks/{main-app-80b038a312917d90.js → main-app-0af848156a5b13fc.js} +1 -1
- package/dist/web/dist/_next/static/chunks/pages/{_app-da7d628f49271ece.js → _app-3d844792ec91e4d1.js} +2 -2
- package/dist/web/dist/auth/verify.html +1 -1
- package/dist/web/dist/auth/verify.txt +1 -1
- package/dist/web/dist/companies.html +1 -1
- package/dist/web/dist/companies.txt +1 -1
- package/dist/web/dist/entity.html +1 -1
- package/dist/web/dist/entity.txt +1 -1
- package/dist/web/dist/index.html +1 -1
- package/dist/web/dist/index.txt +1 -1
- package/dist/web/dist/jobs.html +1 -1
- package/dist/web/dist/jobs.txt +1 -1
- package/dist/web/dist/logs.html +1 -1
- package/dist/web/dist/logs.txt +2 -2
- package/dist/web/dist/matching/jobs/placeholder/talents.html +1 -1
- package/dist/web/dist/matching/jobs/placeholder/talents.txt +1 -1
- package/dist/web/dist/matching/portraits/placeholder/jobs.html +1 -1
- package/dist/web/dist/matching/portraits/placeholder/jobs.txt +1 -1
- package/dist/web/dist/parselog.html +1 -1
- package/dist/web/dist/parselog.txt +1 -1
- package/dist/web/dist/recordings.html +1 -1
- package/dist/web/dist/recordings.txt +1 -1
- package/dist/web/dist/snapshots/placeholder/agent-prompt.html +1 -1
- package/dist/web/dist/snapshots/placeholder/agent-prompt.txt +1 -1
- package/dist/web/dist/talents.html +1 -1
- package/dist/web/dist/talents.txt +1 -1
- package/package.json +6 -4
- package/scripts/postinstall-run.mjs +14 -0
- package/dist/web/dist/_next/static/chunks/app/logs/page-9f41d44fa1add723.js +0 -1
- /package/dist/web/dist/_next/static/{hqJ-7vr4X71DNfzZugHJ- → 3bVkK4pAgbbmp5r0ThyBL}/_buildManifest.js +0 -0
- /package/dist/web/dist/_next/static/{hqJ-7vr4X71DNfzZugHJ- → 3bVkK4pAgbbmp5r0ThyBL}/_ssgManifest.js +0 -0
package/dist/commands/job.js
CHANGED
|
@@ -8,6 +8,7 @@ import { loadConfig } from '../config.js';
|
|
|
8
8
|
import { getToken } from '../keychain.js';
|
|
9
9
|
import { i18n } from '../i18n/index.js';
|
|
10
10
|
import { printCommandError } from '../utils/command-error.js';
|
|
11
|
+
import { runCustomerList } from './customer.js';
|
|
11
12
|
/** 递归收集目录下的 .md/.txt/.pdf(含 raw/jd/ 等子目录),键为相对路径(根目录文件不带前缀)。 */
|
|
12
13
|
function collectJobFiles(dir, relPrefix = '', out = {}) {
|
|
13
14
|
const entries = fs.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -24,10 +25,27 @@ function collectJobFiles(dir, relPrefix = '', out = {}) {
|
|
|
24
25
|
}
|
|
25
26
|
return out;
|
|
26
27
|
}
|
|
27
|
-
export async function runJobCreate(folderPath) {
|
|
28
|
+
export async function runJobCreate(folderPath, customerRef) {
|
|
28
29
|
const config = loadConfig();
|
|
29
30
|
const token = await getToken();
|
|
30
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
|
+
}
|
|
31
49
|
const resolved = path.resolve(folderPath);
|
|
32
50
|
if (!fs.existsSync(resolved)) {
|
|
33
51
|
throw new Error(`Folder not found: ${resolved}`);
|
|
@@ -45,6 +63,7 @@ export async function runJobCreate(folderPath) {
|
|
|
45
63
|
title,
|
|
46
64
|
folderPath: resolved,
|
|
47
65
|
rawFiles,
|
|
66
|
+
customerId,
|
|
48
67
|
}, { headers, proxy: false });
|
|
49
68
|
const serverCase = res.data;
|
|
50
69
|
ensureCacheDir();
|
|
@@ -92,13 +111,14 @@ export const jobCommand = new Command('job')
|
|
|
92
111
|
jobCommand
|
|
93
112
|
.command('create <folder>')
|
|
94
113
|
.description(i18n.t('job.create'))
|
|
95
|
-
.
|
|
114
|
+
.option('--customer <idOrName>', '关联客户(名称/别名/id),未入库会先报错提示 upsert')
|
|
115
|
+
.action(async (folderPath, opts) => {
|
|
96
116
|
try {
|
|
97
|
-
const serverCase = await runJobCreate(folderPath);
|
|
98
|
-
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}` : ''}`);
|
|
99
119
|
}
|
|
100
120
|
catch (err) {
|
|
101
|
-
printCommandError('job:create-failed', err);
|
|
121
|
+
await printCommandError('job:create-failed', err);
|
|
102
122
|
process.exit(1);
|
|
103
123
|
}
|
|
104
124
|
});
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -9,6 +9,10 @@ export declare function sendEmailCode(email: string): Promise<EmailLoginSource>;
|
|
|
9
9
|
/**
|
|
10
10
|
* 邮箱登录第二步:校验验证码并保存 token。
|
|
11
11
|
* 分支选择与 sendEmailCode 保持一致:server 在线走本地代理自签,否则直连 SaaS。
|
|
12
|
+
*
|
|
13
|
+
* hmh#43:两个分支签发的 token 密钥不同,必须打 issuer 标记 —
|
|
14
|
+
* SaaS fallback 拿到的是 SaaS 签发的 token,本地 server 的 AuthMiddleware
|
|
15
|
+
* (USER_SESSION_SECRET)验不过,数据面(search 等)会 401。
|
|
12
16
|
*/
|
|
13
17
|
export declare function verifyEmailCode(email: string, code: string): Promise<{
|
|
14
18
|
token: string;
|
package/dist/commands/login.js
CHANGED
|
@@ -2,7 +2,7 @@ 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) {
|
|
@@ -35,6 +35,10 @@ export async function sendEmailCode(email) {
|
|
|
35
35
|
/**
|
|
36
36
|
* 邮箱登录第二步:校验验证码并保存 token。
|
|
37
37
|
* 分支选择与 sendEmailCode 保持一致:server 在线走本地代理自签,否则直连 SaaS。
|
|
38
|
+
*
|
|
39
|
+
* hmh#43:两个分支签发的 token 密钥不同,必须打 issuer 标记 —
|
|
40
|
+
* SaaS fallback 拿到的是 SaaS 签发的 token,本地 server 的 AuthMiddleware
|
|
41
|
+
* (USER_SESSION_SECRET)验不过,数据面(search 等)会 401。
|
|
38
42
|
*/
|
|
39
43
|
export async function verifyEmailCode(email, code) {
|
|
40
44
|
const config = loadConfig();
|
|
@@ -43,14 +47,17 @@ export async function verifyEmailCode(email, code) {
|
|
|
43
47
|
if (!data.access_token) {
|
|
44
48
|
throw new Error('verify returned no access_token');
|
|
45
49
|
}
|
|
46
|
-
await saveToken(data.access_token);
|
|
50
|
+
await saveToken(data.access_token, 'local');
|
|
47
51
|
return { token: data.access_token, source: 'server' };
|
|
48
52
|
}
|
|
49
53
|
const { data } = await axios.post(`${config.saasUrl}/hunter-mate/auth/email/verify`, { email, code }, { proxy: false });
|
|
50
54
|
if (!data.access_token) {
|
|
51
55
|
throw new Error('verify returned no access_token');
|
|
52
56
|
}
|
|
53
|
-
await saveToken(data.access_token);
|
|
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 自动拉起)再重新登录。');
|
|
54
61
|
return { token: data.access_token, source: 'saas' };
|
|
55
62
|
}
|
|
56
63
|
export async function runLogin() {
|
|
@@ -63,7 +70,33 @@ export async function runLogin() {
|
|
|
63
70
|
});
|
|
64
71
|
console.log(i18n.t('login.userCode'), deviceCode.user_code);
|
|
65
72
|
console.log(i18n.t('login.verifyUrl'), deviceCode.verification_uri);
|
|
66
|
-
|
|
73
|
+
// 打开验证页:走扩展的 cp.tab.open(查重复用,已有同 URL 存活 tab 就不再开,
|
|
74
|
+
// 防止重复 login/多会话叠加把浏览器刷满 web.test/auth 式废 tab);
|
|
75
|
+
// 扩展/server 不可用时回退系统 open。tabId 由扩展侧记录并返回。
|
|
76
|
+
let openedVia = 'open';
|
|
77
|
+
try {
|
|
78
|
+
const { data } = await axios.post(`${config.serverUrl}/extensions/control`, {
|
|
79
|
+
command: 'cp.tab.open',
|
|
80
|
+
params: { url: deviceCode.verification_uri, active: true },
|
|
81
|
+
sessionId: 'hm-cli-login',
|
|
82
|
+
timeoutMs: 10_000,
|
|
83
|
+
}, { proxy: false, timeout: 12_000 });
|
|
84
|
+
if (data?.result?.page) {
|
|
85
|
+
openedVia = data.result.reused ? 'extension-reused' : 'extension-created';
|
|
86
|
+
getCliLogger().info('login:verify-tab', {
|
|
87
|
+
tabId: data.result.page,
|
|
88
|
+
reused: !!data.result.reused,
|
|
89
|
+
url: data.result.url ?? deviceCode.verification_uri,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
await open(deviceCode.verification_uri);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
await open(deviceCode.verification_uri);
|
|
98
|
+
}
|
|
99
|
+
getCliLogger().info('login:verify-opened', { via: openedVia, verifyUrl: deviceCode.verification_uri });
|
|
67
100
|
const maxAttempts = Math.ceil(deviceCode.expires_in / deviceCode.interval);
|
|
68
101
|
for (let i = 0; i < maxAttempts; i++) {
|
|
69
102
|
await sleep(deviceCode.interval * 1000);
|
|
@@ -111,6 +144,26 @@ export const loginCommand = new Command('login')
|
|
|
111
144
|
}
|
|
112
145
|
getCliLogger().info('login:token-saved', { method: 'email', email: options.email, source });
|
|
113
146
|
console.log(i18n.t('login.success'));
|
|
147
|
+
// 登录后自检:拿新 token 打一次受保护端点,把「登录成功但搜索 401」的
|
|
148
|
+
// 静默失败变成确定的成功/失败提示(FID-057 #36)
|
|
149
|
+
try {
|
|
150
|
+
const token = await getToken();
|
|
151
|
+
const probe = await axios.get(`${loadConfig().serverUrl}/job-cases`, {
|
|
152
|
+
headers: token ? { Authorization: `UTOKEN ${token}` } : {},
|
|
153
|
+
timeout: 5000,
|
|
154
|
+
proxy: false,
|
|
155
|
+
});
|
|
156
|
+
console.log(`${i18n.t('login.selfCheckOk')} (HTTP ${probe.status})`);
|
|
157
|
+
}
|
|
158
|
+
catch (probeErr) {
|
|
159
|
+
const status = probeErr?.response?.status;
|
|
160
|
+
if (status === 401 || status === 403) {
|
|
161
|
+
console.error(`${i18n.t('login.selfCheckFail')} (HTTP ${status})`);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
console.warn(`${i18n.t('login.selfCheckSkipped')} (HTTP ${status || 'network'})`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
114
167
|
return;
|
|
115
168
|
}
|
|
116
169
|
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,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
|
+
});
|
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'))
|
|
@@ -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')
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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,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
package/dist/commands/report.js
CHANGED
package/dist/commands/search.js
CHANGED