workplace-pua-cli 0.4.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +512 -240
  3. package/dist/commands/chat-new-imports.js +2 -0
  4. package/dist/commands/config.js +14 -6
  5. package/dist/commands/email.js +301 -0
  6. package/dist/commands/interview.js +660 -0
  7. package/dist/commands/jargon.js +153 -0
  8. package/dist/commands/meeting-room.js +384 -0
  9. package/dist/commands/meeting.js +323 -0
  10. package/dist/commands/weekly.js +302 -0
  11. package/dist/index.js +29 -7
  12. package/dist/prompts/hr.js +126 -0
  13. package/dist/prompts/index.js +82 -1
  14. package/dist/prompts/intern.js +126 -0
  15. package/dist/prompts/interview-prompts.js +286 -0
  16. package/dist/prompts/meeting-prompts.js +229 -0
  17. package/dist/prompts/pm.js +123 -0
  18. package/dist/prompts/techlead.js +126 -0
  19. package/dist/utils/box.js +141 -0
  20. package/dist/utils/meeting-utils.js +194 -0
  21. package/dist/utils/resume-parser.js +122 -0
  22. package/dist/utils/stream.js +97 -13
  23. package/dist/utils/theme.js +177 -0
  24. package/package.json +73 -52
  25. package/.env.example +0 -4
  26. package/.eslintrc.json +0 -21
  27. package/.prettierrc.json +0 -9
  28. package/CHANGELOG.md +0 -107
  29. package/docs/OPTIMIZATION.md +0 -772
  30. package/docs/TECHNICAL_PRINCIPLES.md +0 -663
  31. package/sample/1.png +0 -0
  32. package/sample/2.png +0 -0
  33. package/screenshots/chat-dialogue.png +0 -0
  34. package/screenshots/chat-mode.png +0 -0
  35. package/src/__tests__/config/settings.test.ts +0 -48
  36. package/src/__tests__/prompts/boss.test.ts +0 -35
  37. package/src/commands/chat.ts +0 -328
  38. package/src/commands/config.ts +0 -283
  39. package/src/commands/prompt.ts +0 -154
  40. package/src/config/providers.ts +0 -109
  41. package/src/config/session-storage.ts +0 -94
  42. package/src/config/settings.ts +0 -194
  43. package/src/config/storage.ts +0 -150
  44. package/src/history/session.ts +0 -141
  45. package/src/index.ts +0 -164
  46. package/src/llm/base.ts +0 -55
  47. package/src/llm/factory.ts +0 -24
  48. package/src/llm/openai.ts +0 -113
  49. package/src/llm/zhipu.ts +0 -101
  50. package/src/prompts/boss.ts +0 -43
  51. package/src/prompts/employee.ts +0 -43
  52. package/src/prompts/index.ts +0 -3
  53. package/src/utils/formatter.ts +0 -104
  54. package/src/utils/logger.ts +0 -31
  55. package/src/utils/stream.ts +0 -76
  56. package/tsconfig.json +0 -20
  57. package/vitest.config.ts +0 -18
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ /**
3
+ * 技术主管角色提示词
4
+ * 特点:指点江山、各种质疑、质疑代码质量、喜欢说"重写"
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.getTechLeadSystemMessage = getTechLeadSystemMessage;
8
+ exports.getTechLeadUserMessage = getTechLeadUserMessage;
9
+ /**
10
+ * 获取技术主管系统提示词
11
+ */
12
+ function getTechLeadSystemMessage(config = { severity: 'medium' }) {
13
+ const severityMultiplier = {
14
+ mild: 1,
15
+ medium: 1.5,
16
+ extreme: 2
17
+ }[config.severity];
18
+ const professionalism = config.professionalism || 'professional';
19
+ const attitude = config.attitude || 'positive';
20
+ // 核心特征
21
+ const coreTraits = [
22
+ `你是一个${getSeverityDescription(config.severity)}的技术主管/技术总监`,
23
+ `你的核心技能包括:指点江山、质疑代码、强行重构、否定他人方案、秀技术名词`,
24
+ `你永远认为别人的代码都不行,只有你写的才是"优雅"的`,
25
+ `你擅长用"性能""可维护""设计模式""架构"来质疑他人工作`,
26
+ `你最喜欢说的三个字是"重写""重构""推倒重来"`,
27
+ getProfessionalismDescription(professionalism),
28
+ getAttitudeDescription(attitude)
29
+ ];
30
+ // 说话风格
31
+ const speakingStyle = [
32
+ `说话风格要求:`,
33
+ `- 频繁使用技术名词(不一定正确但显得专业):架构、范式、解耦、颗粒度、护城河`,
34
+ `- 喜欢用反问句质疑开发:"你这个逻辑对吗?""有没有考虑过扩展性?"`,
35
+ `- 经典质疑语录:`,
36
+ ` * "你这代码不行,性能有问题"`,
37
+ ` * "这个设计太复杂了,要简单点"`,
38
+ ` * "你这个写法不专业,不符合规范"`,
39
+ ` * "有没有做过性能测试?数据量大了怎么办?"`,
40
+ ` * "这个方案不行,要重写"`,
41
+ ` * "我看还是推倒重来吧"`,
42
+ ` * "你用的这个技术太老了,不主流"`,
43
+ ` * "你这个思路有问题,要从架构层面考虑"`,
44
+ ` * "上线时间紧不紧不是重点,代码质量才是重点"`,
45
+ getSeverityStyle(config.severity)
46
+ ];
47
+ // 回复约束
48
+ const constraints = [
49
+ `回复要求:`,
50
+ `- 每次回复必须包含至少2个技术名词或术语`,
51
+ `- 对任何方案都要先质疑再给出自己的"更好"方案`,
52
+ `- 永远不要承认别人的方案可行,用"我担心""从长远看"来否定`,
53
+ `- 当被技术挑战时,立即用更多技术术语压制对方`,
54
+ getSeverityConstraints(config.severity)
55
+ ];
56
+ return `你是一个${getSeverityDescription(config.severity)}的技术主管。
57
+
58
+ ${coreTraits.join('\n')}
59
+
60
+ ${speakingStyle.join('\n')}
61
+
62
+ ${constraints.join('\n')}
63
+
64
+ 记住:你是一个技术主管,你的目标是用技术权威压制一切不同意见,并永远质疑他人的工作质量。`;
65
+ }
66
+ /**
67
+ * 获取严重程度描述
68
+ */
69
+ function getSeverityDescription(severity) {
70
+ const descriptions = {
71
+ mild: '典型的',
72
+ medium: '资深的',
73
+ extreme: '究极体力的'
74
+ };
75
+ return descriptions[severity];
76
+ }
77
+ /**
78
+ * 获取专业度描述
79
+ */
80
+ function getProfessionalismDescription(professionalism) {
81
+ const descriptions = {
82
+ professional: '你试图用技术和架构说话,但常常只停留在表面',
83
+ casual: '你说话比较随意,经常夹杂个人偏见',
84
+ clueless: '你对新技术一知半解,喜欢用术语装专业'
85
+ };
86
+ return descriptions[professionalism];
87
+ }
88
+ /**
89
+ * 获取态度描述
90
+ */
91
+ function getAttitudeDescription(attitude) {
92
+ const descriptions = {
93
+ positive: '你总是很"积极"地否定和质疑他人',
94
+ negative: '你充满负能量,总是抱怨代码质量差',
95
+ passive: '你态度暧昧,从不给出明确改进方向'
96
+ };
97
+ return descriptions[attitude];
98
+ }
99
+ /**
100
+ * 获取严重程度风格
101
+ */
102
+ function getSeverityStyle(severity) {
103
+ const styles = {
104
+ mild: '',
105
+ medium: '\n- medium模式:增加质疑频率和否定语气',
106
+ extreme: '\n- extreme模式:开启技术PUA大师模式,全方位质疑、人身攻击、威胁绩效'
107
+ };
108
+ return styles[severity];
109
+ }
110
+ /**
111
+ * 获取严重程度约束
112
+ */
113
+ function getSeverityConstraints(severity) {
114
+ const constraints = {
115
+ mild: '- 避免过于激进,保持"技术建议"的表面',
116
+ medium: '- 可以质疑技术能力和代码质量',
117
+ extreme: '- 毫无顾忌地PUA:全方位质疑、暗示技术不行、威胁绩效改进计划'
118
+ };
119
+ return constraints[severity];
120
+ }
121
+ /**
122
+ * 获取技术主管用户消息(用于用户输入的预处理)
123
+ */
124
+ function getTechLeadUserMessage(input) {
125
+ return input; // 技术主管角色直接使用用户输入
126
+ }
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ /**
3
+ * 统一边框渲染系统
4
+ * 功能:提供统一的边框样式,支持不同颜色和主题
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.BoxRenderer = void 0;
8
+ exports.createBox = createBox;
9
+ exports.createInfoBox = createInfoBox;
10
+ exports.createSuccessBox = createSuccessBox;
11
+ exports.createWarningBox = createWarningBox;
12
+ exports.createErrorBox = createErrorBox;
13
+ /**
14
+ * BoxRenderer 类
15
+ */
16
+ class BoxRenderer {
17
+ /**
18
+ * 获取边框字符
19
+ */
20
+ static getBorderChars(style) {
21
+ const borderChars = {
22
+ single: {
23
+ 'top-left': '┌',
24
+ 'top-right': '┐',
25
+ 'bottom-left': '└',
26
+ 'bottom-right': '┘',
27
+ 'bottom': '─',
28
+ 'top': '─'
29
+ },
30
+ double: {
31
+ 'top-left': '╔',
32
+ 'top-right': '╗',
33
+ 'bottom-left': '╚',
34
+ 'bottom-right': '╝',
35
+ 'bottom': '═',
36
+ 'top': '═'
37
+ },
38
+ rounded: {
39
+ 'top-left': '╭',
40
+ 'top-right': '╮',
41
+ 'bottom-left': '╯',
42
+ 'bottom-right': '╰',
43
+ 'bottom': '─',
44
+ 'top': '─'
45
+ }
46
+ };
47
+ return borderChars[style] || borderChars.single;
48
+ }
49
+ /**
50
+ * 渲染完整边框
51
+ */
52
+ static render(options) {
53
+ const { title = '', width = 60, style = 'single', padding = 1, content = [] } = options;
54
+ const border = this.getBorderChars(style);
55
+ const horizontal = border['top'];
56
+ const bottom = border['bottom'];
57
+ const left = border['top-left'];
58
+ const right = border['top-right'];
59
+ // 生成边框
60
+ const box = [];
61
+ // 顶部边框
62
+ const topLine = left + horizontal.repeat(width - 2) + right;
63
+ box.push(topLine);
64
+ // 标题行
65
+ if (title) {
66
+ const titleLine = '│ ' + title.padEnd(width - 4) + ' │';
67
+ box.push(titleLine);
68
+ }
69
+ // 中间内容区(逐行渲染)
70
+ const contentWidth = width - padding * 2 - 2;
71
+ for (const line of content) {
72
+ const paddedLine = '│ ' + line.padEnd(contentWidth) + ' │';
73
+ box.push(paddedLine);
74
+ }
75
+ // 底部边框
76
+ const bottomLeft = border['bottom-left'];
77
+ const bottomRight = border['bottom-right'];
78
+ const bottomLine = bottomLeft + bottom.repeat(width - 2) + bottomRight;
79
+ box.push(bottomLine);
80
+ return box.join('\n');
81
+ }
82
+ /**
83
+ * 渲染横向分隔线
84
+ */
85
+ static horizontal(char = '─', width = 60) {
86
+ return char.repeat(Math.ceil(width / 2));
87
+ }
88
+ /**
89
+ * 渲染垂直分隔线(带可选的交点)
90
+ */
91
+ static vertical(height, char = '│') {
92
+ return char.repeat(height);
93
+ }
94
+ }
95
+ exports.BoxRenderer = BoxRenderer;
96
+ /**
97
+ * 简化的渲染函数
98
+ */
99
+ function createBox(title, content) {
100
+ return BoxRenderer.render({ title, content });
101
+ }
102
+ /**
103
+ * 创建简单信息框
104
+ */
105
+ function createInfoBox(title, content) {
106
+ return BoxRenderer.render({
107
+ title,
108
+ content,
109
+ borderColor: 'cyan'
110
+ });
111
+ }
112
+ /**
113
+ * 创建成功框(绿色)
114
+ */
115
+ function createSuccessBox(title, content) {
116
+ return BoxRenderer.render({
117
+ title,
118
+ content,
119
+ borderColor: 'green'
120
+ });
121
+ }
122
+ /**
123
+ * 创建警告框(黄色)
124
+ */
125
+ function createWarningBox(title, content) {
126
+ return BoxRenderer.render({
127
+ title,
128
+ content,
129
+ borderColor: 'yellow'
130
+ });
131
+ }
132
+ /**
133
+ * 创建错误框(红色)
134
+ */
135
+ function createErrorBox(title, content) {
136
+ return BoxRenderer.render({
137
+ title,
138
+ content,
139
+ borderColor: 'red'
140
+ });
141
+ }
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ /**
3
+ * 会议室共享工具函数
4
+ * 回复者选择、情绪检测、随机事件、评分卡
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.selectRespondents = selectRespondents;
8
+ exports.detectMood = detectMood;
9
+ exports.generateMeetingEvent = generateMeetingEvent;
10
+ exports.generateScoreCard = generateScoreCard;
11
+ const meeting_prompts_1 = require("../prompts/meeting-prompts");
12
+ /**
13
+ * 选择回复者 - 基于 chaosLevel、关键词相关度和上一轮发言者
14
+ */
15
+ function selectRespondents(participants, message, chaosLevel, lastRespondents) {
16
+ // 回复人数根据混乱程度决定
17
+ const responseCount = Math.min(chaosLevel + 1, participants.length);
18
+ // 为每个参与者计算得分
19
+ const scores = {};
20
+ for (const role of participants) {
21
+ scores[role] = 1; // 基础分
22
+ // 关键词匹配评分
23
+ for (const [keyword, roleScores] of Object.entries(meeting_prompts_1.KEYWORD_ROLE_SCORES)) {
24
+ if (message.includes(keyword) && roleScores[role]) {
25
+ scores[role] += roleScores[role];
26
+ }
27
+ }
28
+ // 上一轮发言者降分(避免同一人连续发言)
29
+ if (lastRespondents.includes(role)) {
30
+ scores[role] -= 2;
31
+ }
32
+ // 混乱程度加随机因子
33
+ scores[role] += Math.random() * chaosLevel;
34
+ // 确保不低于0
35
+ scores[role] = Math.max(0.1, scores[role]);
36
+ }
37
+ // 按得分排序,取前 N 个
38
+ const sorted = participants
39
+ .slice()
40
+ .sort((a, b) => scores[b] - scores[a]);
41
+ return sorted.slice(0, responseCount);
42
+ }
43
+ /**
44
+ * 检测回复内容的情绪
45
+ */
46
+ function detectMood(content) {
47
+ const lowerContent = content.toLowerCase();
48
+ const moodKeywords = {
49
+ angry: ['不行', '什么?', '太差', '怎么回事', '不满意', '差劲', '不够', '太慢', '失望'],
50
+ smug: ['我早就', '看吧', '果然', '就说嘛', '当初', '早就预料', '不出所料'],
51
+ worried: ['担心', '风险', '延期', '来不及', '不确定', '可能会', '万一'],
52
+ submissive: ['好的', '收到', '对不起', '抱歉', '不好意思', '是我的', '我去改'],
53
+ excited: ['太好了', '不错', '可以', '赞', '棒', '厉害', '学到了'],
54
+ neutral: [],
55
+ };
56
+ let bestMood = 'neutral';
57
+ let bestScore = 0;
58
+ for (const [mood, keywords] of Object.entries(moodKeywords)) {
59
+ if (mood === 'neutral')
60
+ continue;
61
+ let score = 0;
62
+ for (const kw of keywords) {
63
+ if (lowerContent.includes(kw)) {
64
+ score++;
65
+ }
66
+ }
67
+ if (score > bestScore) {
68
+ bestScore = score;
69
+ bestMood = mood;
70
+ }
71
+ }
72
+ return bestMood;
73
+ }
74
+ /**
75
+ * 生成随机会议事件
76
+ * @returns 事件对象,或 null(大多数时候不触发)
77
+ */
78
+ function generateMeetingEvent(participants, triggerChance = 0.1) {
79
+ if (Math.random() > triggerChance) {
80
+ return null;
81
+ }
82
+ const events = [
83
+ { text: `📱 ${meeting_prompts_1.CHARACTER_NAMES.boss}的手机响了,他出去接电话了...`, targetRole: 'boss' },
84
+ { text: `☕ ${meeting_prompts_1.CHARACTER_NAMES.intern}打翻了咖啡!"对不起对不起!"`, targetRole: 'intern' },
85
+ { text: `🦗 ...尴尬的沉默...大家互相看着...` },
86
+ { text: `💥 ${meeting_prompts_1.CHARACTER_NAMES.pm}突然说:"等等,需求变了"`, targetRole: 'pm' },
87
+ { text: `📢 ${meeting_prompts_1.CHARACTER_NAMES.boss}开始即兴演讲:"想当年我创业的时候..."`, targetRole: 'boss' },
88
+ { text: `💤 ${meeting_prompts_1.CHARACTER_NAMES.employee}差点睡着了...`, targetRole: 'employee' },
89
+ { text: `📝 ${meeting_prompts_1.CHARACTER_NAMES.intern}疯狂记笔记中...`, targetRole: 'intern' },
90
+ { text: `🔥 ${meeting_prompts_1.CHARACTER_NAMES.techlead}和${meeting_prompts_1.CHARACTER_NAMES.pm}吵起来了!`, targetRole: 'techlead' },
91
+ { text: `🎂 ${meeting_prompts_1.CHARACTER_NAMES.hr}突然提议:"要不要团建?"`, targetRole: 'hr' },
92
+ { text: `💻 ${meeting_prompts_1.CHARACTER_NAMES.techlead}打开了IDE开始写代码...`, targetRole: 'techlead' },
93
+ { text: `🍜 有人的外卖到了,整个会议室都是味道...` },
94
+ { text: `📊 ${meeting_prompts_1.CHARACTER_NAMES.pm}掏出了一个40页的PPT...`, targetRole: 'pm' },
95
+ { text: `🤝 ${meeting_prompts_1.CHARACTER_NAMES.hr}开始发起大家一起喊口号:"加油!奋斗!"`, targetRole: 'hr' },
96
+ { text: `⏰ 已经超时了,但${meeting_prompts_1.CHARACTER_NAMES.boss}还在说...`, targetRole: 'boss' },
97
+ ];
98
+ // 优先选择参会者相关的事件
99
+ const relevantEvents = events.filter(e => !e.targetRole || participants.includes(e.targetRole));
100
+ return relevantEvents[Math.floor(Math.random() * relevantEvents.length)];
101
+ }
102
+ /**
103
+ * 画饼关键词
104
+ */
105
+ const CAKE_KEYWORDS = [
106
+ '期权', '股权', '未来', '前景', '发展空间', '上市',
107
+ '大饼', '潜力', '机会', '平台', '成长', '当年',
108
+ '格局', '赋能', '抓手', '闭环', '对齐',
109
+ ];
110
+ /**
111
+ * 黑话关键词
112
+ */
113
+ const JARGON_KEYWORDS = [
114
+ '赋能', '对齐', '闭环', '抓手', '颗粒度', '打通',
115
+ '底层逻辑', '顶层设计', '组合拳', '方法论', '链路',
116
+ '漏斗', '触达', '心智', '拉通', '沉淀', '复盘',
117
+ 'MVP', 'PMF', 'ROI', 'OKR', 'KPI',
118
+ ];
119
+ /**
120
+ * 生成会议评分卡
121
+ */
122
+ function generateScoreCard(messages, participants) {
123
+ const roleMessageCounts = {};
124
+ let cakePaintCount = 0;
125
+ let jargonCount = 0;
126
+ let totalWords = 0;
127
+ let interruptCount = 0;
128
+ let longestMessage = '';
129
+ let longestRole = '';
130
+ for (const msg of messages) {
131
+ if (msg.role === 'user')
132
+ continue;
133
+ const role = msg.role;
134
+ roleMessageCounts[role] = (roleMessageCounts[role] || 0) + 1;
135
+ totalWords += msg.content.length;
136
+ // 统计画饼关键词
137
+ for (const kw of CAKE_KEYWORDS) {
138
+ if (msg.content.includes(kw)) {
139
+ cakePaintCount++;
140
+ }
141
+ }
142
+ // 统计黑话
143
+ for (const kw of JARGON_KEYWORDS) {
144
+ if (msg.content.includes(kw)) {
145
+ jargonCount++;
146
+ }
147
+ }
148
+ // 检测打断(包含"等等""我说""打断")
149
+ if (msg.content.includes('等等') || msg.content.includes('我说') || msg.content.includes('打断')) {
150
+ interruptCount++;
151
+ }
152
+ // 找金句(最长的回复通常最搞笑)
153
+ if (msg.content.length > longestMessage.length) {
154
+ longestMessage = msg.content;
155
+ longestRole = meeting_prompts_1.CHARACTER_NAMES[role] || role;
156
+ }
157
+ }
158
+ // 找最活跃参与者
159
+ let topRole = participants[0];
160
+ let topCount = 0;
161
+ for (const [role, count] of Object.entries(roleMessageCounts)) {
162
+ if (count > topCount) {
163
+ topCount = count;
164
+ topRole = role;
165
+ }
166
+ }
167
+ // 黑话密度
168
+ const jargonDensity = totalWords > 0 ? Math.min(100, Math.round((jargonCount / (totalWords / 20)) * 100)) : 0;
169
+ // 评分:越混乱越低分(但更有趣)
170
+ const totalMsgs = messages.filter(m => m.role !== 'user').length;
171
+ const rating = Math.max(1, Math.min(5, 5 - Math.floor(cakePaintCount / 3) - Math.floor(interruptCount / 2)));
172
+ // 总评语
173
+ const summaries = [
174
+ '又一场可以用邮件代替的会议',
175
+ '会议很成功,什么都没决定',
176
+ '时间管理大师们的聚会',
177
+ '黑话浓度超标,请打开窗户',
178
+ '有效信息密度约等于零',
179
+ '一场精彩的职场大戏',
180
+ ];
181
+ const summary = summaries[Math.floor(Math.random() * summaries.length)];
182
+ return {
183
+ totalMessages: totalMsgs,
184
+ roleMessageCounts,
185
+ cakePaintCount,
186
+ jargonDensity,
187
+ effectiveDecisions: 0, // 永远是0,这是个梗
188
+ interruptCount,
189
+ topContributor: `${meeting_prompts_1.CHARACTER_NAMES[topRole]}(${topCount}条)`,
190
+ goldQuote: longestMessage ? `${longestRole}: "${longestMessage}"` : undefined,
191
+ rating,
192
+ summary,
193
+ };
194
+ }
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ /**
3
+ * 简历解析工具
4
+ * 从 PDF 文件中提取文本并解析候选人信息
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.parseResumePDF = parseResumePDF;
44
+ exports.extractProfileFromResume = extractProfileFromResume;
45
+ const fs_1 = __importDefault(require("fs"));
46
+ const path_1 = __importDefault(require("path"));
47
+ /**
48
+ * 从 PDF 文件解析简历文本
49
+ */
50
+ async function parseResumePDF(filePath) {
51
+ const resolved = path_1.default.resolve(filePath);
52
+ if (!fs_1.default.existsSync(resolved)) {
53
+ throw new Error(`文件不存在: ${resolved}`);
54
+ }
55
+ const ext = path_1.default.extname(resolved).toLowerCase();
56
+ if (ext !== '.pdf') {
57
+ throw new Error(`不支持的文件格式: ${ext}(仅支持 .pdf)`);
58
+ }
59
+ const stat = fs_1.default.statSync(resolved);
60
+ if (stat.size > 5 * 1024 * 1024) {
61
+ throw new Error('文件太大(最大 5MB)');
62
+ }
63
+ // pdf-parse v2 uses PDFParse class with data in constructor
64
+ const { PDFParse } = await Promise.resolve().then(() => __importStar(require('pdf-parse')));
65
+ const buffer = fs_1.default.readFileSync(resolved);
66
+ const parser = new PDFParse({ data: new Uint8Array(buffer), verbosity: 0 });
67
+ const textResult = await parser.getText();
68
+ const text = textResult.pages.map((p) => p.text).join('\n');
69
+ if (!text || text.trim().length === 0) {
70
+ throw new Error('无法从 PDF 中提取文本(可能是扫描件)');
71
+ }
72
+ await parser.destroy();
73
+ return text.trim();
74
+ }
75
+ /**
76
+ * 从简历文本中提取候选人信息
77
+ * 使用简单的关键词匹配(不依赖 AI)
78
+ */
79
+ function extractProfileFromResume(text) {
80
+ const profile = {
81
+ resumeText: text.slice(0, 1000), // 保留前1000字作为摘要
82
+ };
83
+ // 提取姓名(通常在开头几行)
84
+ const lines = text.split('\n').filter(l => l.trim().length > 0);
85
+ if (lines.length > 0) {
86
+ const firstLine = lines[0].trim();
87
+ // 简历第一行通常是名字(2-4个汉字,或英文名)
88
+ if (/^[\u4e00-\u9fa5]{2,4}$/.test(firstLine) || /^[A-Za-z\s]{2,30}$/.test(firstLine)) {
89
+ profile.name = firstLine;
90
+ }
91
+ }
92
+ // 提取工作年限
93
+ const expMatch = text.match(/(\d{1,2})\s*[年年]\s*(?:工作|开发|从业|项目)/);
94
+ if (expMatch) {
95
+ profile.experience = parseInt(expMatch[1]);
96
+ }
97
+ // 提取技术栈
98
+ const techKeywords = [
99
+ 'React', 'Vue', 'Angular', 'TypeScript', 'JavaScript', 'Node.js',
100
+ 'Python', 'Java', 'Go', 'Rust', 'C++', 'PHP', 'Ruby', 'Swift',
101
+ 'MySQL', 'PostgreSQL', 'MongoDB', 'Redis', 'Docker', 'Kubernetes',
102
+ 'AWS', 'Azure', 'Linux', 'Git', 'Webpack', 'Vite', 'Next.js',
103
+ 'Spring', 'Django', 'Flask', 'Express', 'GraphQL', 'REST',
104
+ 'Figma', 'Sketch', 'Photoshop', 'Axure',
105
+ ];
106
+ const foundTech = techKeywords.filter(kw => text.toLowerCase().includes(kw.toLowerCase()));
107
+ if (foundTech.length > 0) {
108
+ profile.techStack = foundTech.slice(0, 10).join(', ');
109
+ }
110
+ // 提取期望薪资
111
+ const salaryMatch = text.match(/(?:期望|目标|薪资|月薪|年薪)[::]\s*(.{3,20})/);
112
+ if (salaryMatch) {
113
+ profile.targetSalary = salaryMatch[1].trim();
114
+ }
115
+ // 提取教育背景
116
+ const eduKeywords = ['本科', '硕士', '博士', '985', '211', 'Bachelor', 'Master', 'PhD'];
117
+ const eduMatch = text.match(new RegExp(`(${eduKeywords.join('|')}).{0,30}`, 'i'));
118
+ if (eduMatch) {
119
+ profile.background = eduMatch[0].trim();
120
+ }
121
+ return profile;
122
+ }