hunter-mate 0.1.0
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/cache/db.d.ts +7 -0
- package/dist/cache/db.js +105 -0
- package/dist/commands/addons.d.ts +4 -0
- package/dist/commands/addons.js +378 -0
- package/dist/commands/cache.d.ts +8 -0
- package/dist/commands/cache.js +58 -0
- package/dist/commands/config.d.ts +12 -0
- package/dist/commands/config.js +27 -0
- package/dist/commands/doctor.d.ts +21 -0
- package/dist/commands/doctor.js +332 -0
- package/dist/commands/extension.d.ts +5 -0
- package/dist/commands/extension.js +438 -0
- package/dist/commands/extras.d.ts +10 -0
- package/dist/commands/extras.js +111 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.js +87 -0
- package/dist/commands/install.d.ts +9 -0
- package/dist/commands/install.js +126 -0
- package/dist/commands/job.d.ts +9 -0
- package/dist/commands/job.js +114 -0
- package/dist/commands/login.d.ts +3 -0
- package/dist/commands/login.js +51 -0
- package/dist/commands/logout.d.ts +3 -0
- package/dist/commands/logout.js +16 -0
- package/dist/commands/native-check.d.ts +22 -0
- package/dist/commands/native-check.js +93 -0
- package/dist/commands/ops.d.ts +6 -0
- package/dist/commands/ops.js +330 -0
- package/dist/commands/record.d.ts +12 -0
- package/dist/commands/record.js +96 -0
- package/dist/commands/report.d.ts +7 -0
- package/dist/commands/report.js +54 -0
- package/dist/commands/search.d.ts +14 -0
- package/dist/commands/search.js +46 -0
- package/dist/commands/server.d.ts +23 -0
- package/dist/commands/server.js +232 -0
- package/dist/commands/status.d.ts +20 -0
- package/dist/commands/status.js +92 -0
- package/dist/commands/update.d.ts +8 -0
- package/dist/commands/update.js +161 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.js +65 -0
- package/dist/i18n/index.d.ts +7 -0
- package/dist/i18n/index.js +24 -0
- package/dist/i18n/locales/en-US.json +35 -0
- package/dist/i18n/locales/zh-CN.json +35 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +133 -0
- package/dist/keychain.d.ts +3 -0
- package/dist/keychain.js +117 -0
- package/dist/sentry.d.ts +3 -0
- package/dist/sentry.js +62 -0
- package/dist/updater.d.ts +23 -0
- package/dist/updater.js +161 -0
- package/dist/version.d.ts +16 -0
- package/dist/version.js +249 -0
- package/package.json +50 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.autofixCommand = exports.traceCommand = exports.memoryCommand = exports.adapterCommand = exports.recordingsCommand = void 0;
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const axios_1 = __importDefault(require("axios"));
|
|
9
|
+
const config_1 = require("../config");
|
|
10
|
+
// OpenCLI 风格的通用选项
|
|
11
|
+
function withFormat(cmd) {
|
|
12
|
+
return cmd.option('-f, --format <format>', '输出格式: table|json|yaml|md|csv', 'json');
|
|
13
|
+
}
|
|
14
|
+
const http = () => {
|
|
15
|
+
const c = (0, config_1.loadConfig)();
|
|
16
|
+
return axios_1.default.create({ baseURL: c.serverUrl, timeout: 60_000 });
|
|
17
|
+
};
|
|
18
|
+
const printResult = (data, format) => {
|
|
19
|
+
const fmt = (format ?? 'json').toLowerCase();
|
|
20
|
+
if (fmt === 'json') {
|
|
21
|
+
console.log(typeof data === 'string' ? data : JSON.stringify(data, null, 2));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
if (fmt === 'table') {
|
|
25
|
+
if (Array.isArray(data))
|
|
26
|
+
console.table(data);
|
|
27
|
+
else if (data && typeof data === 'object')
|
|
28
|
+
console.table([data]);
|
|
29
|
+
else
|
|
30
|
+
console.log(String(data));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
console.log(typeof data === 'string' ? data : JSON.stringify(data, null, 2));
|
|
34
|
+
};
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// recordings 管理 (对应 chromepilot recordings)
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
exports.recordingsCommand = new commander_1.Command('recordings').description('管理录制会话(rrweb/network/console 事件流)');
|
|
39
|
+
exports.recordingsCommand
|
|
40
|
+
.command('start')
|
|
41
|
+
.description('开始录制: 生成新 sessionId 并通知扩展开始上报事件')
|
|
42
|
+
.requiredOption('--title <text>', '录制标题,例如 LinkedIn 寻访 2026-08-15')
|
|
43
|
+
.option('--tags <csv>', '标签,逗号分隔')
|
|
44
|
+
.action(async (opts) => {
|
|
45
|
+
// MVP: 直接调用 server 的 recordings POST 端点(若存在)或返回本地 sessionId 交给扩展
|
|
46
|
+
const sessionId = 'sess_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
47
|
+
try {
|
|
48
|
+
await http().post(`/recordings/${sessionId}/events`, []);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* 忽略: server REST 未就绪也允许本地启动 */
|
|
52
|
+
}
|
|
53
|
+
console.log(JSON.stringify({
|
|
54
|
+
sessionId,
|
|
55
|
+
title: opts.title,
|
|
56
|
+
tags: opts.tags ? opts.tags.split(',').map((s) => s.trim()) : [],
|
|
57
|
+
startedAt: new Date().toISOString(),
|
|
58
|
+
next: [
|
|
59
|
+
`扩展端在同一台机器会自动捕获并带上 sessionId=${sessionId} 上报;`,
|
|
60
|
+
`用户主动调用 \`hunter-mate extension control cp.state --args '{"sessionId":"${sessionId}"}'\` 确认当前标签页已纳入录制。`,
|
|
61
|
+
].join(''),
|
|
62
|
+
}, null, 2));
|
|
63
|
+
});
|
|
64
|
+
exports.recordingsCommand
|
|
65
|
+
.command('stop')
|
|
66
|
+
.argument('<sessionId>', '会话 ID')
|
|
67
|
+
.description('停止录制(软标记,不再接受新事件)')
|
|
68
|
+
.action(async (sessionId) => {
|
|
69
|
+
console.log(JSON.stringify({
|
|
70
|
+
sessionId,
|
|
71
|
+
stoppedAt: new Date().toISOString(),
|
|
72
|
+
detail: 'stop 目前是客户端软标记。可通过 POST /recordings/:sessionId/parse 触发后端解析。',
|
|
73
|
+
}, null, 2));
|
|
74
|
+
});
|
|
75
|
+
withFormat(exports.recordingsCommand.command('list').description('列出录制会话(从 /recordings 端点)'))
|
|
76
|
+
.action(async (opts) => {
|
|
77
|
+
try {
|
|
78
|
+
const r = await http().get('/recordings');
|
|
79
|
+
printResult(r.data, opts.format);
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
// server 暂未实现 recordings list 时给出空结果与提示,避免 crash
|
|
83
|
+
printResult({ note: '/recordings GET 未实现,HunterMate server 正在逐步补齐。先从 recordings/:sessionId GET 查看单个。', sessions: [] }, opts.format);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
withFormat(exports.recordingsCommand.command('show <sessionId>').description('查看录制详情 (/recordings/:id) + 可选 parse'))
|
|
87
|
+
.option('--parse', '同时触发 parseRecording 规则引擎提取结构化字段')
|
|
88
|
+
.action(async (sessionId, opts) => {
|
|
89
|
+
try {
|
|
90
|
+
const [detail, parsed] = await Promise.all([
|
|
91
|
+
http().get(`/recordings/${sessionId}`).then((r) => r.data).catch(() => null),
|
|
92
|
+
opts.parse ? http().post(`/recordings/${sessionId}/parse`).then((r) => r.data).catch(() => null) : Promise.resolve(null),
|
|
93
|
+
]);
|
|
94
|
+
printResult({ detail, parsed }, opts.format);
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
console.error(`recordings show failed: ${e.message}`);
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// adapter (OpenCLI 风格)
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
withFormat(new commander_1.Command('adapter')
|
|
105
|
+
.description('站点适配器管理(OpenCLI 风格,对应 chromepilot adapter)')
|
|
106
|
+
.argument('[action]', '操作: list (默认)', 'list')
|
|
107
|
+
.argument('[site]', '站点名过滤,如 linkedin/resume/jobsite', '')).action(async (action, site, opts) => {
|
|
108
|
+
// 内置适配器列表(对应 ControlCommandName 里 hm.* 的集合)
|
|
109
|
+
const builtin = [
|
|
110
|
+
{ site: 'linkedin', commands: ['search', 'extract', 'invite'], builtin: true },
|
|
111
|
+
{ site: 'resume', commands: ['extract'], builtin: true },
|
|
112
|
+
];
|
|
113
|
+
const filtered = site ? builtin.filter((b) => b.site === site) : builtin;
|
|
114
|
+
printResult(action === 'list' || !action ? filtered : { error: `unknown action: ${action}`, supported: ['list'] }, opts.format);
|
|
115
|
+
});
|
|
116
|
+
// 因为上面的 adapter 是局部变量返回,这里用一个包装函数把它赋值给导出
|
|
117
|
+
// (为了保持 recordingsCommand 相同的导出风格,单独定义 exports.adapterCommand)
|
|
118
|
+
exports.adapterCommand = withFormat(new commander_1.Command('adapter')
|
|
119
|
+
.description('站点适配器管理(OpenCLI 风格,对应 chromepilot adapter): list')
|
|
120
|
+
.argument('[action]', '操作: list (默认)', 'list')
|
|
121
|
+
.argument('[site]', '站点名过滤', '')).action((action, site, opts) => {
|
|
122
|
+
const builtin = [
|
|
123
|
+
{ site: 'linkedin', commands: ['search', 'extract', 'invite'], builtin: true },
|
|
124
|
+
{ site: 'resume', commands: ['extract'], builtin: true },
|
|
125
|
+
];
|
|
126
|
+
const filtered = site ? builtin.filter((b) => b.site === site) : builtin;
|
|
127
|
+
printResult(action === 'list' ? filtered : { error: `unknown action: ${action}` }, opts.format);
|
|
128
|
+
});
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// memory (站点记忆: 基于 selector/流程建议 的持久化 KV)
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
exports.memoryCommand = withFormat(new commander_1.Command('memory')
|
|
133
|
+
.description('站点记忆管理(OpenCLI 风格,对应 chromepilot memory): list|show|suggest|delete')
|
|
134
|
+
.argument('[action]', '操作 (默认 list)', 'list')
|
|
135
|
+
.argument('[site]', '站点名', '')
|
|
136
|
+
.argument('[ref]', 'suggest/delete 的元素引用', '')).action((action, site, ref, opts) => {
|
|
137
|
+
const HOME = process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
|
|
138
|
+
const STORE = require('path').join(HOME, '.hunter-mate', 'memory.json');
|
|
139
|
+
const fs = require('fs');
|
|
140
|
+
let store = {};
|
|
141
|
+
try {
|
|
142
|
+
store = JSON.parse(fs.readFileSync(STORE, 'utf-8'));
|
|
143
|
+
}
|
|
144
|
+
catch { }
|
|
145
|
+
const save = () => fs.writeFileSync(STORE, JSON.stringify(store, null, 2));
|
|
146
|
+
switch (action) {
|
|
147
|
+
case 'list':
|
|
148
|
+
printResult(Object.entries(store).map(([s, entries]) => ({
|
|
149
|
+
site: s,
|
|
150
|
+
entries: Object.keys(entries).length,
|
|
151
|
+
keys: Object.keys(entries),
|
|
152
|
+
})), opts.format);
|
|
153
|
+
return;
|
|
154
|
+
case 'show':
|
|
155
|
+
if (!site) {
|
|
156
|
+
console.error('memory show 需要 site');
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
printResult(store[site] ?? {}, opts.format);
|
|
160
|
+
return;
|
|
161
|
+
case 'suggest': {
|
|
162
|
+
if (!site || !ref) {
|
|
163
|
+
console.error('memory suggest 需要 site + ref');
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
store[site] = store[site] || {};
|
|
167
|
+
const suggestion = {
|
|
168
|
+
ref,
|
|
169
|
+
selectorHint: '// TODO: 把人工成功过的 selector 记在这里',
|
|
170
|
+
createdAt: new Date().toISOString(),
|
|
171
|
+
};
|
|
172
|
+
store[site][ref] = suggestion;
|
|
173
|
+
save();
|
|
174
|
+
printResult(suggestion, opts.format);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
case 'delete':
|
|
178
|
+
if (!site) {
|
|
179
|
+
console.error('memory delete 需要 site');
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
if (ref)
|
|
183
|
+
delete store[site]?.[ref];
|
|
184
|
+
else
|
|
185
|
+
delete store[site];
|
|
186
|
+
save();
|
|
187
|
+
printResult({ ok: true, site, ref: ref ?? '(whole site)' }, opts.format);
|
|
188
|
+
return;
|
|
189
|
+
default:
|
|
190
|
+
console.error(`memory: unknown action ${action} (支持: list|show|suggest|delete)`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// trace (事件流查询 + 追加)
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
exports.traceCommand = withFormat(new commander_1.Command('trace')
|
|
198
|
+
.description('trace 事件流管理(命令+selector+network 关联): list|show|summary|network|query|record|delete')
|
|
199
|
+
.argument('[action]', '操作 (默认 list)', 'list')
|
|
200
|
+
.argument('[sessionId]', '会话 ID'))
|
|
201
|
+
.option('--type <type>', '事件类型(query 过滤 / record 必填)')
|
|
202
|
+
.option('--from <ts>', '起始时间戳 (query)')
|
|
203
|
+
.option('--to <ts>', '结束时间戳 (query)')
|
|
204
|
+
.option('--payload <json>', '事件 payload (record)')
|
|
205
|
+
.action(async (action, sessionId, opts) => {
|
|
206
|
+
switch (action) {
|
|
207
|
+
case 'list':
|
|
208
|
+
try {
|
|
209
|
+
const r = await http().get('/trace');
|
|
210
|
+
printResult(r.data, opts.format);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
printResult({ sessions: [], note: '/trace 端点尚未在 server 暴露,或 server 未启动(hunter-mate start)' }, opts.format);
|
|
214
|
+
}
|
|
215
|
+
return;
|
|
216
|
+
case 'show':
|
|
217
|
+
case 'summary':
|
|
218
|
+
case 'network':
|
|
219
|
+
if (!sessionId) {
|
|
220
|
+
console.error(`trace ${action} 需要 sessionId`);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const r = await http().get(`/trace/${sessionId}/${action === 'network' ? 'network' : ''}`);
|
|
225
|
+
printResult(r.data, opts.format);
|
|
226
|
+
}
|
|
227
|
+
catch (e) {
|
|
228
|
+
printResult({ error: e.message, sessionId, action }, opts.format);
|
|
229
|
+
}
|
|
230
|
+
return;
|
|
231
|
+
case 'query':
|
|
232
|
+
if (!sessionId) {
|
|
233
|
+
console.error('trace query 需要 sessionId');
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const r = await http().post(`/trace/${sessionId}/query`, {
|
|
238
|
+
type: opts.type, from: opts.from ? Number(opts.from) : undefined, to: opts.to ? Number(opts.to) : undefined,
|
|
239
|
+
});
|
|
240
|
+
printResult(r.data, opts.format);
|
|
241
|
+
}
|
|
242
|
+
catch (e) {
|
|
243
|
+
printResult({ error: e.message }, opts.format);
|
|
244
|
+
}
|
|
245
|
+
return;
|
|
246
|
+
case 'record': {
|
|
247
|
+
if (!sessionId || !opts.type) {
|
|
248
|
+
console.error('trace record 需要 sessionId + --type');
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
let payload = opts.payload;
|
|
252
|
+
if (typeof payload === 'string') {
|
|
253
|
+
try {
|
|
254
|
+
payload = JSON.parse(payload);
|
|
255
|
+
}
|
|
256
|
+
catch { }
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
const r = await http().post(`/trace/${sessionId}/record`, { type: opts.type, payload });
|
|
260
|
+
printResult(r.data, opts.format);
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
// server 未就绪时: 本地追加 fallbacks 到 session JSONL 文件
|
|
264
|
+
const HOME = process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
|
|
265
|
+
const f = require('path').join(HOME, '.hunter-mate', 'sessions', `${sessionId}.trace.jsonl`);
|
|
266
|
+
require('fs').appendFileSync(f, JSON.stringify({ type: opts.type, payload, ts: Date.now() }) + '\n');
|
|
267
|
+
printResult({ ok: true, stored: 'local', file: f }, opts.format);
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
case 'delete':
|
|
272
|
+
if (!sessionId) {
|
|
273
|
+
console.error('trace delete 需要 sessionId');
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
printResult({ ok: true, sessionId, note: '本地 trace 文件删除(server 侧请通过 recordings/* 管理)' }, opts.format);
|
|
277
|
+
try {
|
|
278
|
+
const HOME = process.env.HUNTER_MATE_HOME || process.env.HOME || process.env.USERPROFILE || '.';
|
|
279
|
+
const f = require('path').join(HOME, '.hunter-mate', 'sessions', `${sessionId}.trace.jsonl`);
|
|
280
|
+
if (require('fs').existsSync(f))
|
|
281
|
+
require('fs').unlinkSync(f);
|
|
282
|
+
}
|
|
283
|
+
catch { }
|
|
284
|
+
return;
|
|
285
|
+
default:
|
|
286
|
+
console.error(`trace: unknown action ${action}`);
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
// ---------------------------------------------------------------------------
|
|
291
|
+
// autofix (基于 memory + trace 的修复建议)
|
|
292
|
+
// ---------------------------------------------------------------------------
|
|
293
|
+
exports.autofixCommand = withFormat(new commander_1.Command('autofix')
|
|
294
|
+
.description('自动修复建议(基于站点记忆 + trace): 综合建议|retry|selector|auth|network')
|
|
295
|
+
.argument('[action]', '操作 (默认 综合建议)', '综合建议')
|
|
296
|
+
.argument('[site]', '站点名')
|
|
297
|
+
.argument('[arg2]', 'retry:command / network:sessionId'))
|
|
298
|
+
.option('--session <id>', '综合建议时附带 network 建议 (sessionId)')
|
|
299
|
+
.action((action, site, arg2, opts) => {
|
|
300
|
+
const suggestions = [];
|
|
301
|
+
suggestions.push({
|
|
302
|
+
priority: 1,
|
|
303
|
+
category: 'selector',
|
|
304
|
+
title: 'selector 失效时用 memory.suggest 固化本次成功过的替代 selector',
|
|
305
|
+
command: `hunter-mate memory suggest ${site || '<site>'} selector-<name>`,
|
|
306
|
+
});
|
|
307
|
+
suggestions.push({
|
|
308
|
+
priority: 2,
|
|
309
|
+
category: 'auth',
|
|
310
|
+
title: '登录墙/403: 先在目标站点确认登录态,或 extension 侧栏重新激活会话',
|
|
311
|
+
command: 'hunter-mate extension eval "document.cookie"',
|
|
312
|
+
});
|
|
313
|
+
if (action === 'network' || opts.session) {
|
|
314
|
+
suggestions.push({
|
|
315
|
+
priority: 3,
|
|
316
|
+
category: 'network',
|
|
317
|
+
title: 'trace 网络事件确认 API 阻塞点',
|
|
318
|
+
command: `hunter-mate trace network ${arg2 || opts.session || '<sessionId>'}`,
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
if (action === 'retry') {
|
|
322
|
+
suggestions.push({
|
|
323
|
+
priority: 0,
|
|
324
|
+
category: 'retry',
|
|
325
|
+
title: `立即重试 ${arg2 || '<command>'}(带上一次成功的 memory selector)`,
|
|
326
|
+
command: `hunter-mate extension control cp.${arg2 || 'click'}`,
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
printResult({ action, site, arg2, summary: `${suggestions.length} suggestions`, suggestions }, opts.format);
|
|
330
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export interface RecordTranscriptOptions {
|
|
3
|
+
type?: string;
|
|
4
|
+
personId?: string;
|
|
5
|
+
jobCaseId?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function runRecordTranscript(filePath: string, options: RecordTranscriptOptions): Promise<any>;
|
|
8
|
+
export declare function runRecordCall(audioFile: string): {
|
|
9
|
+
audioFile: string;
|
|
10
|
+
note: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const recordCommand: Command;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.recordCommand = void 0;
|
|
40
|
+
exports.runRecordTranscript = runRecordTranscript;
|
|
41
|
+
exports.runRecordCall = runRecordCall;
|
|
42
|
+
const commander_1 = require("commander");
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const axios_1 = __importDefault(require("axios"));
|
|
45
|
+
const uuid_1 = require("uuid");
|
|
46
|
+
const config_1 = require("../config");
|
|
47
|
+
const keychain_1 = require("../keychain");
|
|
48
|
+
const db_1 = require("../cache/db");
|
|
49
|
+
async function runRecordTranscript(filePath, options) {
|
|
50
|
+
const config = (0, config_1.loadConfig)();
|
|
51
|
+
const token = await (0, keychain_1.getToken)();
|
|
52
|
+
const headers = token ? { Authorization: `UTOKEN ${token}` } : {};
|
|
53
|
+
if (!fs.existsSync(filePath)) {
|
|
54
|
+
throw new Error(`文件不存在: ${filePath}`);
|
|
55
|
+
}
|
|
56
|
+
const transcript = fs.readFileSync(filePath, 'utf-8');
|
|
57
|
+
(0, db_1.ensureCacheDir)();
|
|
58
|
+
const res = await axios_1.default.post(`${config.serverUrl}/communication-records`, {
|
|
59
|
+
externalId: (0, uuid_1.v4)(),
|
|
60
|
+
type: options.type || 'call',
|
|
61
|
+
transcript,
|
|
62
|
+
personId: options.personId ? parseInt(options.personId, 10) : undefined,
|
|
63
|
+
jobCaseId: options.jobCaseId ? parseInt(options.jobCaseId, 10) : undefined,
|
|
64
|
+
}, { headers, proxy: false });
|
|
65
|
+
return res.data;
|
|
66
|
+
}
|
|
67
|
+
function runRecordCall(audioFile) {
|
|
68
|
+
return { audioFile, note: 'STT 处理待接入(本地 faster-whisper 或云端 Whisper API)。' };
|
|
69
|
+
}
|
|
70
|
+
exports.recordCommand = new commander_1.Command('record')
|
|
71
|
+
.description('沟通记录/录音归档');
|
|
72
|
+
exports.recordCommand
|
|
73
|
+
.command('transcript <file>')
|
|
74
|
+
.description('上传转写文本并生成沟通记录草稿')
|
|
75
|
+
.option('--type <type>', '记录类型(call/wechat/email/audio/meeting)', 'call')
|
|
76
|
+
.option('--personId <id>', '关联人才 ID')
|
|
77
|
+
.option('--jobCaseId <id>', '关联 JobCase ID')
|
|
78
|
+
.action(async (filePath, options) => {
|
|
79
|
+
try {
|
|
80
|
+
const data = await runRecordTranscript(filePath, options);
|
|
81
|
+
console.log(`已创建沟通记录草稿: ${data.id} (${data.externalId})`);
|
|
82
|
+
console.log('请使用本地 LLM 对转写文本做 NER/摘要/实体链接后确认归档。');
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
console.error('上传失败:', err.response?.data?.message || err.message);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
exports.recordCommand
|
|
90
|
+
.command('call <audioFile>')
|
|
91
|
+
.description('上传音频文件(MVP 阶段仅保存路径,STT 由本地或云端后续处理)')
|
|
92
|
+
.action((audioFile) => {
|
|
93
|
+
const result = runRecordCall(audioFile);
|
|
94
|
+
console.log(`音频文件已记录: ${result.audioFile}`);
|
|
95
|
+
console.log(result.note);
|
|
96
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export interface ReportSalesOptions {
|
|
3
|
+
name?: string;
|
|
4
|
+
industry?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function runReportSales(customerId: string, options: ReportSalesOptions): Promise<any>;
|
|
7
|
+
export declare const reportCommand: Command;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.reportCommand = void 0;
|
|
7
|
+
exports.runReportSales = runReportSales;
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
const keychain_1 = require("../keychain");
|
|
12
|
+
async function runReportSales(customerId, options) {
|
|
13
|
+
const config = (0, config_1.loadConfig)();
|
|
14
|
+
const token = await (0, keychain_1.getToken)();
|
|
15
|
+
const headers = token ? { Authorization: `UTOKEN ${token}` } : {};
|
|
16
|
+
const res = await axios_1.default.get(`${config.serverUrl}/reports/sales/${encodeURIComponent(customerId)}`, {
|
|
17
|
+
headers,
|
|
18
|
+
params: { name: options.name, industry: options.industry },
|
|
19
|
+
proxy: false,
|
|
20
|
+
});
|
|
21
|
+
return res.data;
|
|
22
|
+
}
|
|
23
|
+
exports.reportCommand = new commander_1.Command('report')
|
|
24
|
+
.description('生成报告');
|
|
25
|
+
exports.reportCommand
|
|
26
|
+
.command('sales <customerId>')
|
|
27
|
+
.description('生成 BD 销售方案报告')
|
|
28
|
+
.option('--name <name>', '客户名称')
|
|
29
|
+
.option('--industry <industry>', '行业')
|
|
30
|
+
.action(async (customerId, options) => {
|
|
31
|
+
try {
|
|
32
|
+
const data = await runReportSales(customerId, options);
|
|
33
|
+
console.log(`# BD 销售方案报告:${data.customerName}`);
|
|
34
|
+
console.log(`\n## 客户信息`);
|
|
35
|
+
console.log(`- 客户 ID: ${data.customerId}`);
|
|
36
|
+
console.log(`- 行业: ${data.industry || '未指定'}`);
|
|
37
|
+
console.log(`\n## 历史相似客户案例`);
|
|
38
|
+
for (const c of data.similarCustomers || []) {
|
|
39
|
+
console.log(`- ${c.name}(${c.industry})成单数:${c.dealCount} — ${c.notes}`);
|
|
40
|
+
}
|
|
41
|
+
console.log(`\n## 可匹配人才池`);
|
|
42
|
+
const pool = data.talentPool || {};
|
|
43
|
+
console.log(`- 总数:${pool.count || 0}`);
|
|
44
|
+
if (pool.error) {
|
|
45
|
+
console.log(`- 人才数据获取失败:${pool.error}`);
|
|
46
|
+
}
|
|
47
|
+
console.log(`\n## 推荐方案`);
|
|
48
|
+
console.log('> 请使用本地 LLM 基于以上原始数据生成最终销售方案与交付节奏。');
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error('生成报告失败:', err.response?.data?.message || err.message);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export interface SearchOptions {
|
|
3
|
+
filter?: string;
|
|
4
|
+
keyword?: string;
|
|
5
|
+
areaId?: string;
|
|
6
|
+
postId?: string;
|
|
7
|
+
nationalityId?: string;
|
|
8
|
+
page?: string;
|
|
9
|
+
limit?: string;
|
|
10
|
+
source?: string;
|
|
11
|
+
top?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function runSearch(options: SearchOptions): Promise<any>;
|
|
14
|
+
export declare const searchCommand: Command;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.searchCommand = void 0;
|
|
7
|
+
exports.runSearch = runSearch;
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
const axios_1 = __importDefault(require("axios"));
|
|
10
|
+
const config_1 = require("../config");
|
|
11
|
+
const keychain_1 = require("../keychain");
|
|
12
|
+
const i18n_1 = require("../i18n");
|
|
13
|
+
async function runSearch(options) {
|
|
14
|
+
const config = (0, config_1.loadConfig)();
|
|
15
|
+
const token = await (0, keychain_1.getToken)();
|
|
16
|
+
const params = {
|
|
17
|
+
keyword: options.keyword || options.filter,
|
|
18
|
+
areaId: options.areaId,
|
|
19
|
+
postId: options.postId,
|
|
20
|
+
nationalityId: options.nationalityId,
|
|
21
|
+
page: options.page,
|
|
22
|
+
limit: options.limit || options.top,
|
|
23
|
+
source: options.source,
|
|
24
|
+
};
|
|
25
|
+
const res = await axios_1.default.get(`${config.serverUrl}/search/talent`, {
|
|
26
|
+
headers: token ? { Authorization: `UTOKEN ${token}` } : {},
|
|
27
|
+
params,
|
|
28
|
+
proxy: false,
|
|
29
|
+
});
|
|
30
|
+
return res.data;
|
|
31
|
+
}
|
|
32
|
+
exports.searchCommand = new commander_1.Command('search')
|
|
33
|
+
.description(i18n_1.i18n.t('search.description'))
|
|
34
|
+
.option('--filter <filter>', i18n_1.i18n.t('search.filter'))
|
|
35
|
+
.option('--keyword <keyword>', '搜索关键词')
|
|
36
|
+
.option('--areaId <areaId>', '现居地 ID')
|
|
37
|
+
.option('--postId <postId>', '职位 ID')
|
|
38
|
+
.option('--nationalityId <nationalityId>', '国籍 ID')
|
|
39
|
+
.option('--page <n>', '页码', '1')
|
|
40
|
+
.option('--limit <n>', '每页数量', '10')
|
|
41
|
+
.option('--source <sources>', i18n_1.i18n.t('search.source'), 'u_person')
|
|
42
|
+
.option('--top <n>', i18n_1.i18n.t('search.top'), '10')
|
|
43
|
+
.action(async (options) => {
|
|
44
|
+
const data = await runSearch(options);
|
|
45
|
+
console.log(JSON.stringify(data, null, 2));
|
|
46
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
declare function tryReadPid(): number | null;
|
|
3
|
+
export { tryReadPid };
|
|
4
|
+
/** 定位 packages/server 包根(dist 优先,ts-node 开发模式兜底);找不到返回 null */
|
|
5
|
+
export declare function findServerRoot(): string | null;
|
|
6
|
+
/** 对应 ChromePilot start: 启动本地 NestJS Server(提供 REST + WebSocket + SPA) */
|
|
7
|
+
export declare function runStart(opts: {
|
|
8
|
+
port?: number;
|
|
9
|
+
host?: string;
|
|
10
|
+
foreground?: boolean;
|
|
11
|
+
}): Promise<{
|
|
12
|
+
started: boolean;
|
|
13
|
+
pid: number | null;
|
|
14
|
+
url: string;
|
|
15
|
+
msg: string;
|
|
16
|
+
}>;
|
|
17
|
+
export declare function runStop(): {
|
|
18
|
+
stopped: boolean;
|
|
19
|
+
pid: number | null;
|
|
20
|
+
msg: string;
|
|
21
|
+
};
|
|
22
|
+
export declare const startCommand: Command;
|
|
23
|
+
export declare const stopCommand: Command;
|