hiresquire-cli 1.1.0 → 1.2.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.
package/dist/api.js ADDED
@@ -0,0 +1,689 @@
1
+ "use strict";
2
+ /**
3
+ * HireSquire CLI - API Client
4
+ *
5
+ * HTTP client for interacting with the HireSquire API
6
+ * Handles authentication, request/response typing, and error handling
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ var __importDefault = (this && this.__importDefault) || function (mod) {
42
+ return (mod && mod.__esModule) ? mod : { "default": mod };
43
+ };
44
+ Object.defineProperty(exports, "__esModule", { value: true });
45
+ exports.ApiClient = void 0;
46
+ exports.createApiClient = createApiClient;
47
+ exports.createApiClientFromToken = createApiClientFromToken;
48
+ exports.readResumeFromFile = readResumeFromFile;
49
+ exports.readResumesFromDirectory = readResumesFromDirectory;
50
+ exports.readResumesFromPaths = readResumesFromPaths;
51
+ const axios_1 = __importDefault(require("axios"));
52
+ const fs = __importStar(require("fs"));
53
+ const path = __importStar(require("path"));
54
+ const pdf_parse_1 = __importDefault(require("pdf-parse"));
55
+ const officeparser_1 = __importDefault(require("officeparser"));
56
+ const form_data_1 = __importDefault(require("form-data"));
57
+ const types_1 = require("./types");
58
+ // ============================================================================
59
+ // Constants
60
+ // ============================================================================
61
+ const DEFAULT_BASE_URL = 'https://api.hiresquireai.com/api/v1';
62
+ const DEFAULT_TIMEOUT = 30000; // 30 seconds
63
+ const MAX_RETRIES = 3;
64
+ const RETRY_DELAY = 1000; // 1 second base delay
65
+ const MAX_FILE_SIZE = 20 * 1024 * 1024; // 20MB max file size
66
+ // ============================================================================
67
+ // API Client Class
68
+ // ============================================================================
69
+ class ApiClient {
70
+ constructor(config) {
71
+ this.config = {
72
+ ...config,
73
+ baseUrl: config.baseUrl || DEFAULT_BASE_URL,
74
+ };
75
+ this.client = axios_1.default.create({
76
+ baseURL: this.config.baseUrl,
77
+ timeout: DEFAULT_TIMEOUT,
78
+ headers: {
79
+ 'Authorization': `Bearer ${this.config.apiToken}`,
80
+ 'Content-Type': 'application/json',
81
+ 'Accept': 'application/json',
82
+ 'User-Agent': 'hiresquire-cli/1.2.1',
83
+ },
84
+ });
85
+ // Add response interceptor for error handling with retry
86
+ this.client.interceptors.response.use((response) => response, async (error) => {
87
+ const originalRequest = error.config;
88
+ if (!originalRequest) {
89
+ throw this.handleError(error);
90
+ }
91
+ const shouldRetry = this.shouldRetryRequest(error);
92
+ const retryCount = originalRequest._retryCount || 0;
93
+ if (shouldRetry && retryCount < MAX_RETRIES) {
94
+ originalRequest._retryCount = retryCount + 1;
95
+ const delay = RETRY_DELAY * Math.pow(2, retryCount);
96
+ await this.sleep(delay);
97
+ return this.client(originalRequest);
98
+ }
99
+ throw this.handleError(error);
100
+ });
101
+ }
102
+ shouldRetryRequest(error) {
103
+ const statusCode = error.response?.status;
104
+ const code = error.code;
105
+ // Retry on: rate limit, server errors, timeouts, and network errors
106
+ const retryableCodes = ['ECONNABORTED', 'ETIMEDOUT', 'ENOTFOUND', 'ECONNREFUSED'];
107
+ return statusCode === 429 || statusCode === 503 || statusCode === 500 || retryableCodes.includes(code || '');
108
+ }
109
+ // ============================================================================
110
+ // Error Handling
111
+ // ============================================================================
112
+ handleError(error) {
113
+ const statusCode = error.response?.status;
114
+ const responseData = error.response?.data;
115
+ const message = responseData?.message || responseData?.error || error.message;
116
+ switch (statusCode) {
117
+ case 401:
118
+ case 403:
119
+ return new types_1.AuthenticationError(message);
120
+ case 400:
121
+ return new types_1.ValidationError(message);
122
+ case 404:
123
+ return new types_1.NotFoundError(message);
124
+ case 429:
125
+ const retryAfter = error.response?.headers['retry-after'];
126
+ return new types_1.RateLimitError(message + (retryAfter ? ` Retry after ${retryAfter} seconds.` : ''));
127
+ case 422:
128
+ return new types_1.ValidationError(message);
129
+ default:
130
+ return new types_1.HireSquireError(message, 'API_ERROR', statusCode);
131
+ }
132
+ }
133
+ // ============================================================================
134
+ // Job Operations
135
+ // ============================================================================
136
+ /**
137
+ * Generic GET request
138
+ */
139
+ async get(url, config) {
140
+ const response = await this.client.get(url, config);
141
+ return { data: response.data };
142
+ }
143
+ /**
144
+ * Generic POST request
145
+ */
146
+ async post(url, data, config) {
147
+ const response = await this.client.post(url, data, config);
148
+ return { data: response.data };
149
+ }
150
+ /**
151
+ * Generic PUT request
152
+ */
153
+ async put(url, data, config) {
154
+ const response = await this.client.put(url, data, config);
155
+ return { data: response.data };
156
+ }
157
+ /**
158
+ * Generic DELETE request
159
+ */
160
+ async delete(url, config) {
161
+ const response = await this.client.delete(url, config);
162
+ return { data: response.data };
163
+ }
164
+ /**
165
+ * Create a new screening job
166
+ */
167
+ async createJob(params) {
168
+ const response = await this.client.post('/jobs', params);
169
+ return response.data;
170
+ }
171
+ /**
172
+ * Upload a ZIP file containing resumes to create a new screening job
173
+ */
174
+ async uploadZip(params) {
175
+ const formData = new form_data_1.default();
176
+ formData.append('title', params.title);
177
+ formData.append('description', params.description);
178
+ if (params.leniency_level !== undefined) {
179
+ formData.append('leniency_level', params.leniency_level.toString());
180
+ }
181
+ if (params.custom_instructions) {
182
+ formData.append('custom_instructions', params.custom_instructions);
183
+ }
184
+ if (params.webhook_url) {
185
+ formData.append('webhook_url', params.webhook_url);
186
+ }
187
+ // Append the zip file
188
+ formData.append('zip_file', fs.createReadStream(params.zipPath));
189
+ const response = await this.client.post('/jobs/upload-zip', formData, {
190
+ headers: {
191
+ ...formData.getHeaders()
192
+ }
193
+ });
194
+ return response.data;
195
+ }
196
+ /**
197
+ * Get job status
198
+ */
199
+ async getJobStatus(jobId) {
200
+ const response = await this.client.get(`/jobs/${jobId}`);
201
+ return response.data;
202
+ }
203
+ /**
204
+ * Get job results
205
+ */
206
+ async getResults(jobId, options = {}) {
207
+ const params = new URLSearchParams();
208
+ if (options.min_score)
209
+ params.append('min_score', options.min_score.toString());
210
+ if (options.only_top_n)
211
+ params.append('only_top_n', options.only_top_n.toString());
212
+ const queryString = params.toString();
213
+ const url = queryString
214
+ ? `/jobs/${jobId}/results?${queryString}`
215
+ : `/jobs/${jobId}/results`;
216
+ const response = await this.client.get(url);
217
+ return response.data;
218
+ }
219
+ /**
220
+ * List all jobs
221
+ */
222
+ async listJobs(options = {}) {
223
+ const params = new URLSearchParams();
224
+ if (options.status)
225
+ params.append('status', options.status);
226
+ if (options.page)
227
+ params.append('page', options.page.toString());
228
+ if (options.per_page)
229
+ params.append('per_page', options.per_page.toString());
230
+ const queryString = params.toString();
231
+ const url = queryString ? `/jobs?${queryString}` : '/jobs';
232
+ const response = await this.client.get(url);
233
+ return response.data;
234
+ }
235
+ // ============================================================================
236
+ // Email Operations
237
+ // ============================================================================
238
+ /**
239
+ * Generate an email for a candidate
240
+ */
241
+ async generateEmail(params) {
242
+ const response = await this.client.post(`/jobs/${params.job_id}/generate-email`, {
243
+ candidate_id: params.candidate_id,
244
+ type: params.type,
245
+ custom_message: params.custom_message,
246
+ });
247
+ return response.data;
248
+ }
249
+ // ============================================================================
250
+ // Utility Methods
251
+ // ============================================================================
252
+ /**
253
+ * Poll for job completion
254
+ */
255
+ async pollForCompletion(jobId, options = {}) {
256
+ const interval = options.interval || 3000; // 3 seconds
257
+ const timeout = options.timeout || 300000; // 5 minutes
258
+ const startTime = Date.now();
259
+ while (Date.now() - startTime < timeout) {
260
+ const status = await this.getJobStatus(jobId);
261
+ if (options.onProgress) {
262
+ options.onProgress(status);
263
+ }
264
+ if (status.status === 'completed' || status.status === 'failed') {
265
+ return status;
266
+ }
267
+ await this.sleep(interval);
268
+ }
269
+ throw new types_1.HireSquireError(`Job polling timed out after ${timeout / 1000} seconds`, 'TIMEOUT');
270
+ }
271
+ /**
272
+ * Sleep for specified milliseconds
273
+ */
274
+ sleep(ms) {
275
+ return new Promise((resolve) => setTimeout(resolve, ms));
276
+ }
277
+ /**
278
+ * Test API connection
279
+ */
280
+ async testConnection() {
281
+ try {
282
+ await this.listJobs({ per_page: 1 });
283
+ return true;
284
+ }
285
+ catch {
286
+ return false;
287
+ }
288
+ }
289
+ /**
290
+ * Get current config
291
+ */
292
+ getConfig() {
293
+ return { ...this.config };
294
+ }
295
+ // ============================================================================
296
+ // Job Control
297
+ // ============================================================================
298
+ /**
299
+ * Cancel a running job
300
+ */
301
+ async cancelJob(jobId) {
302
+ const response = await this.client.post(`/jobs/${jobId}/cancel`);
303
+ return response.data;
304
+ }
305
+ /**
306
+ * Compare multiple candidates side-by-side
307
+ */
308
+ async compareCandidates(jobId, candidateIds) {
309
+ const response = await this.client.get(`/jobs/${jobId}/compare?ids=${candidateIds.join(',')}`);
310
+ return response.data;
311
+ }
312
+ /**
313
+ * Report hiring outcome to improve AI accuracy
314
+ */
315
+ async reportOutcome(jobId, candidateId, outcome) {
316
+ const response = await this.client.post(`/jobs/${jobId}/outcome`, { candidate_id: candidateId, outcome });
317
+ return response.data;
318
+ }
319
+ // ============================================================================
320
+ // Webhook
321
+ // ============================================================================
322
+ /**
323
+ * Test a webhook endpoint
324
+ */
325
+ async testWebhook(webhookUrl) {
326
+ const response = await this.client.post('/webhooks/test', { url: webhookUrl });
327
+ return response.data;
328
+ }
329
+ // ============================================================================
330
+ // Rate Limit
331
+ // ============================================================================
332
+ /**
333
+ * Get current rate limit status
334
+ */
335
+ async getRateLimit() {
336
+ const response = await this.client.get('/rate-limit');
337
+ return response.data;
338
+ }
339
+ // ============================================================================
340
+ // Candidate Operations
341
+ // ============================================================================
342
+ /**
343
+ * Get a specific candidate
344
+ */
345
+ async getCandidate(candidateId) {
346
+ const response = await this.client.get(`/candidates/${candidateId}`);
347
+ return response.data;
348
+ }
349
+ /**
350
+ * Update candidate status
351
+ */
352
+ async updateCandidateStatus(candidateId, status) {
353
+ const response = await this.client.patch(`/candidates/${candidateId}/status`, { status });
354
+ return response.data;
355
+ }
356
+ // ============================================================================
357
+ // Schema Discovery
358
+ // ============================================================================
359
+ /**
360
+ * Get API schema for discovery
361
+ */
362
+ async getSchema() {
363
+ const response = await this.client.get('/schema');
364
+ return response.data;
365
+ }
366
+ // ===========================================================================
367
+ // Calendar Operations
368
+ // ===========================================================================
369
+ /**
370
+ * List calendar connections
371
+ */
372
+ async listCalendarConnections() {
373
+ const response = await this.client.get('/calendar/connections');
374
+ return response.data;
375
+ }
376
+ /**
377
+ * Create a calendar connection
378
+ */
379
+ async createCalendarConnection(params) {
380
+ const response = await this.client.post('/calendar/connections', params);
381
+ return response.data;
382
+ }
383
+ /**
384
+ * Delete a calendar connection
385
+ */
386
+ async deleteCalendarConnection(id) {
387
+ const response = await this.client.delete(`/calendar/connections/${id}`);
388
+ return response.data;
389
+ }
390
+ /**
391
+ * Get available slots from calendar
392
+ */
393
+ async getAvailableSlots(params) {
394
+ const response = await this.client.get('/calendar/slots', { params });
395
+ return response.data;
396
+ }
397
+ // ===========================================================================
398
+ // Interview Operations
399
+ // ===========================================================================
400
+ /**
401
+ * List interviews
402
+ */
403
+ async listInterviews(jobId) {
404
+ const params = jobId ? { job_id: jobId } : {};
405
+ const response = await this.client.get('/interviews', { params });
406
+ return response.data;
407
+ }
408
+ /**
409
+ * Create an interview
410
+ */
411
+ async createInterview(params) {
412
+ const response = await this.client.post('/interviews', params);
413
+ return response.data;
414
+ }
415
+ /**
416
+ * Show a specific interview
417
+ */
418
+ async showInterview(id) {
419
+ const response = await this.client.get(`/interviews/${id}`);
420
+ return response.data;
421
+ }
422
+ /**
423
+ * Update an interview
424
+ */
425
+ async updateInterview(id, params) {
426
+ const response = await this.client.put(`/interviews/${id}`, params);
427
+ return response.data;
428
+ }
429
+ /**
430
+ * Delete/cancel an interview
431
+ */
432
+ async deleteInterview(id) {
433
+ const response = await this.client.delete(`/interviews/${id}`);
434
+ return response.data;
435
+ }
436
+ // ===========================================================================
437
+ // Meeting Operations
438
+ // ===========================================================================
439
+ /**
440
+ * Generate a meeting link
441
+ */
442
+ async generateMeetingLink(params) {
443
+ const response = await this.client.post('/meetings/links', params);
444
+ return response.data;
445
+ }
446
+ }
447
+ exports.ApiClient = ApiClient;
448
+ // ===========================================================================
449
+ // Factory Functions
450
+ // ============================================================================
451
+ // Factory Functions
452
+ // ============================================================================
453
+ /**
454
+ * Create API client from config
455
+ */
456
+ function createApiClient(config) {
457
+ return new ApiClient(config);
458
+ }
459
+ /**
460
+ * Create API client from token
461
+ */
462
+ function createApiClientFromToken(token, baseUrl) {
463
+ return new ApiClient({
464
+ apiToken: token,
465
+ baseUrl: baseUrl || DEFAULT_BASE_URL,
466
+ });
467
+ }
468
+ // ============================================================================
469
+ // Resume Parsing Utilities
470
+ // ============================================================================
471
+ /**
472
+ * Read resume from file path (async - supports PDF, DOCX, DOC, TXT, MD)
473
+ */
474
+ async function readResumeFromFile(filePath) {
475
+ const absolutePath = path.resolve(filePath);
476
+ if (!fs.existsSync(absolutePath)) {
477
+ throw new Error(`File not found: ${filePath}`);
478
+ }
479
+ const stats = fs.statSync(absolutePath);
480
+ if (stats.size > MAX_FILE_SIZE) {
481
+ throw new Error(`File too large: ${filePath} (max ${MAX_FILE_SIZE / 1024 / 1024}MB)`);
482
+ }
483
+ const filename = path.basename(filePath);
484
+ const ext = path.extname(filePath).toLowerCase();
485
+ let content;
486
+ try {
487
+ if (ext === '.pdf') {
488
+ content = await parsePdf(absolutePath);
489
+ }
490
+ else if (ext === '.docx') {
491
+ content = await parseDocx(absolutePath);
492
+ }
493
+ else if (ext === '.doc') {
494
+ content = await parseDoc(absolutePath);
495
+ }
496
+ else if (['.txt', '.md'].includes(ext)) {
497
+ content = fs.readFileSync(absolutePath, 'utf-8');
498
+ }
499
+ else {
500
+ throw new Error(`Unsupported file format: ${ext}`);
501
+ }
502
+ }
503
+ catch (error) {
504
+ throw new Error(`Failed to parse ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
505
+ }
506
+ // Validate extracted content
507
+ if (!content || content.trim().length < 50) {
508
+ throw new Error(`Could not extract sufficient text from: ${filePath}`);
509
+ }
510
+ return { filename, content };
511
+ }
512
+ /**
513
+ * Parse PDF file and extract text
514
+ */
515
+ async function parsePdf(filePath) {
516
+ try {
517
+ const dataBuffer = fs.readFileSync(filePath);
518
+ const data = await (0, pdf_parse_1.default)(dataBuffer);
519
+ return data.text || '';
520
+ }
521
+ catch (error) {
522
+ throw new Error(`PDF parsing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
523
+ }
524
+ }
525
+ /**
526
+ * Parse DOCX file and extract text
527
+ */
528
+ async function parseDocx(filePath) {
529
+ try {
530
+ // officeparser returns AST, convert to plain text
531
+ const result = await new Promise((resolve, reject) => {
532
+ officeparser_1.default.parseOffice(filePath, (data) => {
533
+ const text = officeparser_1.default.toText(data);
534
+ resolve({ content: text || '' });
535
+ }, (error) => {
536
+ reject(error);
537
+ });
538
+ });
539
+ return result.content || '';
540
+ }
541
+ catch (error) {
542
+ throw new Error(`DOCX parsing failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
543
+ }
544
+ }
545
+ /**
546
+ * Parse DOC file and extract text (uses same parser as DOCX)
547
+ */
548
+ async function parseDoc(filePath) {
549
+ // officeparser supports DOC files as well
550
+ return parseDocx(filePath);
551
+ }
552
+ /**
553
+ * Read resumes from directory (async)
554
+ */
555
+ async function readResumesFromDirectory(dirPath, currentCount = 0) {
556
+ const absolutePath = path.resolve(dirPath);
557
+ if (!fs.existsSync(absolutePath)) {
558
+ return [];
559
+ }
560
+ const files = fs.readdirSync(absolutePath);
561
+ const resumeExtensions = ['.txt', '.pdf', '.doc', '.docx', '.md'];
562
+ const resumes = [];
563
+ for (const file of files) {
564
+ if (currentCount + resumes.length >= 100)
565
+ break;
566
+ const ext = path.extname(file).toLowerCase();
567
+ if (resumeExtensions.includes(ext)) {
568
+ const filePath = path.join(absolutePath, file);
569
+ try {
570
+ resumes.push(await readResumeFromFile(filePath));
571
+ }
572
+ catch (error) {
573
+ console.warn(`Warning: Could not read ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`);
574
+ }
575
+ }
576
+ }
577
+ return resumes;
578
+ }
579
+ /**
580
+ * Read multiple resume files (async)
581
+ */
582
+ async function readResumesFromPaths(paths) {
583
+ const resumes = [];
584
+ for (const p of paths) {
585
+ if (resumes.length >= 100) {
586
+ break;
587
+ }
588
+ const resolvedPath = path.resolve(p);
589
+ // Check if it's a glob pattern
590
+ if (p.includes('*')) {
591
+ const patternMatches = await matchGlobPattern(resolvedPath, resumes.length);
592
+ resumes.push(...patternMatches);
593
+ continue;
594
+ }
595
+ try {
596
+ const stat = fs.statSync(resolvedPath);
597
+ if (stat.isDirectory()) {
598
+ const dirResumes = await readResumesFromDirectory(resolvedPath, resumes.length);
599
+ resumes.push(...dirResumes);
600
+ }
601
+ else if (stat.isFile()) {
602
+ resumes.push(await readResumeFromFile(resolvedPath));
603
+ }
604
+ }
605
+ catch (error) {
606
+ console.warn(`Warning: Could not read ${p}: ${error instanceof Error ? error.message : 'Unknown error'}`);
607
+ }
608
+ }
609
+ if (resumes.length >= 100) {
610
+ console.warn('⚠️ Maximum limit of 100 resumes reached. Additional files in this batch were ignored.');
611
+ }
612
+ return resumes.slice(0, 100);
613
+ }
614
+ /**
615
+ * Match glob patterns for resume files (async)
616
+ */
617
+ async function matchGlobPattern(pattern, currentCount = 0) {
618
+ const resumes = [];
619
+ const resumeExtensions = ['.txt', '.pdf', '.doc', '.docx', '.md'];
620
+ // Handle **/*.ext patterns (recursive)
621
+ if (pattern.includes('**')) {
622
+ const parts = pattern.split('**');
623
+ const baseDir = path.resolve(parts[0] || '.');
624
+ const suffix = parts[1] || '';
625
+ async function walk(dir) {
626
+ if (!fs.existsSync(dir))
627
+ return;
628
+ if (currentCount + resumes.length >= 100)
629
+ return;
630
+ const files = fs.readdirSync(dir);
631
+ for (const file of files) {
632
+ if (currentCount + resumes.length >= 100)
633
+ break;
634
+ const fullPath = path.join(dir, file);
635
+ const stat = fs.statSync(fullPath);
636
+ if (stat.isDirectory()) {
637
+ await walk(fullPath);
638
+ }
639
+ else if (stat.isFile()) {
640
+ const ext = path.extname(file).toLowerCase();
641
+ if (resumeExtensions.includes(ext)) {
642
+ if (!suffix || fullPath.endsWith(suffix.replace('/', path.sep))) {
643
+ try {
644
+ resumes.push(await readResumeFromFile(fullPath));
645
+ }
646
+ catch (e) {
647
+ console.warn(`Warning: Could not read ${file}: ${e.message}`);
648
+ }
649
+ }
650
+ }
651
+ }
652
+ }
653
+ }
654
+ await walk(baseDir);
655
+ return resumes;
656
+ }
657
+ // Standard glob implementation (non-recursive)
658
+ const lastSlash = pattern.lastIndexOf(path.sep);
659
+ let searchDir = '.';
660
+ let filePattern = pattern;
661
+ if (lastSlash >= 0) {
662
+ searchDir = pattern.slice(0, lastSlash);
663
+ filePattern = pattern.slice(lastSlash + 1);
664
+ }
665
+ const searchPath = path.resolve(searchDir);
666
+ if (!fs.existsSync(searchPath)) {
667
+ return resumes;
668
+ }
669
+ const regexPattern = new RegExp('^' + filePattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$', 'i');
670
+ const files = fs.readdirSync(searchPath);
671
+ for (const file of files) {
672
+ if (currentCount + resumes.length >= 100)
673
+ break;
674
+ if (regexPattern.test(file)) {
675
+ const ext = path.extname(file).toLowerCase();
676
+ if (resumeExtensions.includes(ext)) {
677
+ const filePath = path.join(searchPath, file);
678
+ try {
679
+ resumes.push(await readResumeFromFile(filePath));
680
+ }
681
+ catch (e) {
682
+ console.warn(`Warning: Could not read ${file}: ${e.message}`);
683
+ }
684
+ }
685
+ }
686
+ }
687
+ return resumes;
688
+ }
689
+ //# sourceMappingURL=api.js.map