hiresquire-cli 1.0.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/README.md +82 -9
- package/dist/api.d.ts +250 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +689 -0
- package/dist/api.js.map +1 -0
- package/dist/config.d.ts +49 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +195 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1370 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +353 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +49 -0
- package/dist/types.js.map +1 -0
- package/package.json +73 -57
package/dist/index.js
ADDED
|
@@ -0,0 +1,1370 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* HireSquire CLI - Main Entry Point
|
|
5
|
+
*
|
|
6
|
+
* Command-line interface for HireSquire AI-powered candidate screening
|
|
7
|
+
*
|
|
8
|
+
* Supported agents:
|
|
9
|
+
* - Claude Code / Claude Desktop
|
|
10
|
+
* - OpenCode
|
|
11
|
+
* - OpenClaw
|
|
12
|
+
* - Codex
|
|
13
|
+
* - And any other CLI-capable agent
|
|
14
|
+
*
|
|
15
|
+
* @packageDocumentation
|
|
16
|
+
* @module HireSquire CLI
|
|
17
|
+
*/
|
|
18
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
19
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
exports.ValidationError = exports.HireSquireError = exports.saveConfig = exports.getConfigManager = exports.readResumesFromPaths = exports.createApiClient = exports.ApiClient = void 0;
|
|
23
|
+
exports.createCli = createCli;
|
|
24
|
+
const commander_1 = require("commander");
|
|
25
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
26
|
+
const ora_1 = __importDefault(require("ora"));
|
|
27
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
28
|
+
const api_1 = require("./api");
|
|
29
|
+
Object.defineProperty(exports, "ApiClient", { enumerable: true, get: function () { return api_1.ApiClient; } });
|
|
30
|
+
Object.defineProperty(exports, "createApiClient", { enumerable: true, get: function () { return api_1.createApiClient; } });
|
|
31
|
+
Object.defineProperty(exports, "readResumesFromPaths", { enumerable: true, get: function () { return api_1.readResumesFromPaths; } });
|
|
32
|
+
const config_1 = require("./config");
|
|
33
|
+
Object.defineProperty(exports, "getConfigManager", { enumerable: true, get: function () { return config_1.getConfigManager; } });
|
|
34
|
+
Object.defineProperty(exports, "saveConfig", { enumerable: true, get: function () { return config_1.saveConfig; } });
|
|
35
|
+
const types_1 = require("./types");
|
|
36
|
+
var types_2 = require("./types");
|
|
37
|
+
Object.defineProperty(exports, "HireSquireError", { enumerable: true, get: function () { return types_2.HireSquireError; } });
|
|
38
|
+
Object.defineProperty(exports, "ValidationError", { enumerable: true, get: function () { return types_2.ValidationError; } });
|
|
39
|
+
// For testing purposes
|
|
40
|
+
function createCli() {
|
|
41
|
+
return new commander_1.Command();
|
|
42
|
+
}
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// Setup
|
|
45
|
+
// ============================================================================
|
|
46
|
+
const program = new commander_1.Command();
|
|
47
|
+
let apiClient = null;
|
|
48
|
+
let isJsonOutput = false;
|
|
49
|
+
// ============================================================================
|
|
50
|
+
// Utility Functions
|
|
51
|
+
// ============================================================================
|
|
52
|
+
/**
|
|
53
|
+
* Initialize API client
|
|
54
|
+
*/
|
|
55
|
+
function initApi() {
|
|
56
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
57
|
+
if (!config.apiToken) {
|
|
58
|
+
console.error(chalk_1.default.red('Error: API token not configured.'));
|
|
59
|
+
console.log(chalk_1.default.blue('Run ') + chalk_1.default.cyan('hiresquire init') + chalk_1.default.blue(' to configure your API token.'));
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
return (0, api_1.createApiClient)(config);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Output JSON and exit
|
|
66
|
+
* @param data The data to output
|
|
67
|
+
* @param shouldExit Whether to exit the process (default: true)
|
|
68
|
+
*/
|
|
69
|
+
function outputJson(data, shouldExit = true) {
|
|
70
|
+
console.log(JSON.stringify(data, null, 2));
|
|
71
|
+
if (shouldExit) {
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Handle errors
|
|
77
|
+
*/
|
|
78
|
+
function handleError(error, context = 'Operation failed') {
|
|
79
|
+
if (isJsonOutput) {
|
|
80
|
+
outputJson({
|
|
81
|
+
success: false,
|
|
82
|
+
error: error instanceof Error ? error.message : context,
|
|
83
|
+
context,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
if (error instanceof types_1.HireSquireError) {
|
|
87
|
+
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
88
|
+
}
|
|
89
|
+
else if (error instanceof types_1.ValidationError) {
|
|
90
|
+
console.error(chalk_1.default.red(`Validation Error: ${error.message}`));
|
|
91
|
+
}
|
|
92
|
+
else if (error instanceof Error) {
|
|
93
|
+
console.error(chalk_1.default.red(`${context}: ${error.message}`));
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
console.error(chalk_1.default.red(context));
|
|
97
|
+
}
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Confirm action
|
|
102
|
+
*/
|
|
103
|
+
async function confirm(message) {
|
|
104
|
+
const answers = await inquirer_1.default.prompt([
|
|
105
|
+
{
|
|
106
|
+
type: 'confirm',
|
|
107
|
+
name: 'confirm',
|
|
108
|
+
message,
|
|
109
|
+
default: false,
|
|
110
|
+
},
|
|
111
|
+
]);
|
|
112
|
+
return answers.confirm;
|
|
113
|
+
}
|
|
114
|
+
// ============================================================================
|
|
115
|
+
// Commands
|
|
116
|
+
// ============================================================================
|
|
117
|
+
/**
|
|
118
|
+
* Init command - Configure API token
|
|
119
|
+
*/
|
|
120
|
+
program
|
|
121
|
+
.command('init')
|
|
122
|
+
.description('Initialize configuration with API token')
|
|
123
|
+
.requiredOption('-t, --token <token>', 'API token from HireSquire dashboard')
|
|
124
|
+
.option('-u, --base-url <url>', 'API base URL', 'https://api.hiresquireai.com/api/v1')
|
|
125
|
+
.option('-w, --webhook <url>', 'Default webhook URL')
|
|
126
|
+
.option('-y, --yes', 'Skip confirmation (auto-enabled for non-interactive/JSON mode)')
|
|
127
|
+
.action(async (options) => {
|
|
128
|
+
try {
|
|
129
|
+
// Auto-skip confirmation for agents using JSON output or CI mode
|
|
130
|
+
const autoYes = isJsonOutput || process.env.CI === 'true';
|
|
131
|
+
if (!options.yes && !autoYes) {
|
|
132
|
+
const confirmed = await confirm(`Save API token to ${(0, config_1.getConfigManager)().getConfigPath()}?`);
|
|
133
|
+
if (!confirmed) {
|
|
134
|
+
if (isJsonOutput) {
|
|
135
|
+
outputJson({ success: false, message: 'Configuration cancelled' });
|
|
136
|
+
}
|
|
137
|
+
console.log(chalk_1.default.yellow('Configuration cancelled.'));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
(0, config_1.saveConfig)({
|
|
142
|
+
apiToken: options.token,
|
|
143
|
+
baseUrl: options.baseUrl,
|
|
144
|
+
webhookUrl: options.webhook,
|
|
145
|
+
});
|
|
146
|
+
if (isJsonOutput) {
|
|
147
|
+
outputJson({
|
|
148
|
+
success: true,
|
|
149
|
+
message: 'Configuration saved successfully',
|
|
150
|
+
configPath: (0, config_1.getConfigManager)().getConfigPath(),
|
|
151
|
+
});
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
console.log(chalk_1.default.green('✓ Configuration saved successfully'));
|
|
155
|
+
console.log(chalk_1.default.gray(` Config: ${(0, config_1.getConfigManager)().getConfigPath()}`));
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
handleError(error, 'Failed to save configuration');
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
/**
|
|
162
|
+
* Screen command - Submit screening job
|
|
163
|
+
*/
|
|
164
|
+
program
|
|
165
|
+
.command('screen')
|
|
166
|
+
.description('Submit a candidate screening job')
|
|
167
|
+
.requiredOption('-t, --title <title>', 'Job posting title')
|
|
168
|
+
.requiredOption('-d, --description <description>', 'Job description (string or @file)')
|
|
169
|
+
.option('-r, --resumes <paths>', 'Resume files or directory (comma-separated or @file)')
|
|
170
|
+
.option('-z, --zip <path>', 'Path to a ZIP file containing resumes (overrides --resumes)')
|
|
171
|
+
.option('-l, --leniency <1-10>', 'Screening leniency level (1=strict, 10=loose)', '5')
|
|
172
|
+
.option('-w, --webhook <url>', 'Webhook URL for notifications')
|
|
173
|
+
.option('--watch', 'Poll for completion and show results')
|
|
174
|
+
.option('--min-score <number>', 'Minimum score threshold for webhook notifications (0-100)')
|
|
175
|
+
.option('--only-top-n <number>', 'Only send top N candidates to webhook')
|
|
176
|
+
.action(async (options) => {
|
|
177
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Submitting screening job...').start() : null;
|
|
178
|
+
try {
|
|
179
|
+
// Validate leniency level
|
|
180
|
+
const leniency = parseInt(options.leniency);
|
|
181
|
+
if (isNaN(leniency) || leniency < 1 || leniency > 10) {
|
|
182
|
+
throw new types_1.ValidationError('Leniency level must be an integer between 1 and 10');
|
|
183
|
+
}
|
|
184
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
185
|
+
const api = initApi();
|
|
186
|
+
// Parse job description
|
|
187
|
+
let jobDescription = options.description;
|
|
188
|
+
if (options.description.startsWith('@')) {
|
|
189
|
+
const fs = require('fs');
|
|
190
|
+
jobDescription = fs.readFileSync(options.description.slice(1), 'utf-8');
|
|
191
|
+
}
|
|
192
|
+
let result;
|
|
193
|
+
// Prepare webhook conditions
|
|
194
|
+
const webhookConditions = {};
|
|
195
|
+
if (options.minScore)
|
|
196
|
+
webhookConditions.min_score = parseInt(options.minScore);
|
|
197
|
+
if (options.onlyTopN)
|
|
198
|
+
webhookConditions.only_top_n = parseInt(options.onlyTopN);
|
|
199
|
+
if (options.zip) {
|
|
200
|
+
const params = {
|
|
201
|
+
title: options.title,
|
|
202
|
+
description: jobDescription,
|
|
203
|
+
zipPath: options.zip,
|
|
204
|
+
leniency_level: parseInt(options.leniency),
|
|
205
|
+
webhook_url: options.webhook || config.webhookUrl,
|
|
206
|
+
webhook_conditions: Object.keys(webhookConditions).length > 0
|
|
207
|
+
? webhookConditions
|
|
208
|
+
: undefined,
|
|
209
|
+
};
|
|
210
|
+
result = await api.uploadZip(params);
|
|
211
|
+
if (spinner)
|
|
212
|
+
spinner.succeed(`Job #${result.job_id} created from ZIP archive`);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
if (!options.resumes) {
|
|
216
|
+
throw new types_1.ValidationError('You must provide either --resumes or --zip');
|
|
217
|
+
}
|
|
218
|
+
// Parse resumes
|
|
219
|
+
let resumes = [];
|
|
220
|
+
if (options.resumes.startsWith('@')) {
|
|
221
|
+
// Read from file (one filename per line)
|
|
222
|
+
const fs = require('fs');
|
|
223
|
+
const files = fs.readFileSync(options.resumes.slice(1), 'utf-8')
|
|
224
|
+
.split('\n')
|
|
225
|
+
.filter((line) => line.trim());
|
|
226
|
+
resumes = await (0, api_1.readResumesFromPaths)(files);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
// Comma-separated paths
|
|
230
|
+
const paths = options.resumes.split(',').map((p) => p.trim());
|
|
231
|
+
resumes = await (0, api_1.readResumesFromPaths)(paths);
|
|
232
|
+
}
|
|
233
|
+
if (resumes.length === 0) {
|
|
234
|
+
throw new types_1.ValidationError('No resume files found');
|
|
235
|
+
}
|
|
236
|
+
// Create job params
|
|
237
|
+
const params = {
|
|
238
|
+
title: options.title,
|
|
239
|
+
description: jobDescription,
|
|
240
|
+
resumes,
|
|
241
|
+
leniency_level: parseInt(options.leniency),
|
|
242
|
+
webhook_url: options.webhook || config.webhookUrl,
|
|
243
|
+
webhook_conditions: Object.keys(webhookConditions).length > 0
|
|
244
|
+
? webhookConditions
|
|
245
|
+
: undefined,
|
|
246
|
+
};
|
|
247
|
+
// Submit job
|
|
248
|
+
result = await api.createJob(params);
|
|
249
|
+
if (spinner)
|
|
250
|
+
spinner.succeed(`Job #${result.job_id} created`);
|
|
251
|
+
}
|
|
252
|
+
if (isJsonOutput) {
|
|
253
|
+
if (options.watch) {
|
|
254
|
+
// Output intermediate result for watch mode
|
|
255
|
+
console.log(JSON.stringify({
|
|
256
|
+
type: 'job_created',
|
|
257
|
+
job_id: result.job_id,
|
|
258
|
+
status: result.status,
|
|
259
|
+
}));
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
outputJson({
|
|
263
|
+
success: true,
|
|
264
|
+
job_id: result.job_id,
|
|
265
|
+
status: result.status,
|
|
266
|
+
status_url: result.status_url,
|
|
267
|
+
});
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
console.log(chalk_1.default.blue(` Status: ${chalk_1.default.bold(result.status)}`));
|
|
272
|
+
console.log(chalk_1.default.gray(` Poll: ${result.status_url}`));
|
|
273
|
+
// Watch mode
|
|
274
|
+
if (options.watch) {
|
|
275
|
+
if (!isJsonOutput) {
|
|
276
|
+
console.log(chalk_1.default.blue('\n⏳ Waiting for completion...\n'));
|
|
277
|
+
}
|
|
278
|
+
const finalStatus = await api.pollForCompletion(result.job_id, {
|
|
279
|
+
onProgress: (status) => {
|
|
280
|
+
const progress = status.progress || 0;
|
|
281
|
+
const bar = '█'.repeat(Math.floor(progress / 10)) + '░'.repeat(10 - Math.floor(progress / 10));
|
|
282
|
+
process.stdout.write(`\r Progress: [${bar}] ${progress}% ${status.message || ''}`);
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
if (!isJsonOutput) {
|
|
286
|
+
console.log(chalk_1.default.green(`\n✓ Screening complete!`));
|
|
287
|
+
console.log(chalk_1.default.blue(` Final status: ${chalk_1.default.bold(finalStatus.status)}`));
|
|
288
|
+
}
|
|
289
|
+
if (finalStatus.status === 'completed') {
|
|
290
|
+
const results = await api.getResults(result.job_id);
|
|
291
|
+
if (isJsonOutput) {
|
|
292
|
+
outputJson({
|
|
293
|
+
type: 'job_completed',
|
|
294
|
+
success: true,
|
|
295
|
+
job_id: result.job_id,
|
|
296
|
+
results,
|
|
297
|
+
});
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
console.log(chalk_1.default.blue(`\n📊 Results (${results.candidates.length} candidates):\n`));
|
|
301
|
+
// Sort by score
|
|
302
|
+
const sorted = [...results.candidates].sort((a, b) => b.score - a.score);
|
|
303
|
+
sorted.forEach((candidate, index) => {
|
|
304
|
+
const scoreColor = candidate.score >= 80 ? chalk_1.default.green :
|
|
305
|
+
candidate.score >= 60 ? chalk_1.default.yellow : chalk_1.default.red;
|
|
306
|
+
console.log(`${chalk_1.default.bold(index + 1)}. ${candidate.name} ${scoreColor(`(${candidate.score}/100)`)}`);
|
|
307
|
+
console.log(chalk_1.default.gray(` ${candidate.summary?.substring(0, 80) || 'No summary'}...`));
|
|
308
|
+
console.log();
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
catch (error) {
|
|
314
|
+
if (spinner)
|
|
315
|
+
spinner.fail('Failed to submit job');
|
|
316
|
+
handleError(error, 'Screening failed');
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
/**
|
|
320
|
+
* Jobs command - List all jobs
|
|
321
|
+
*/
|
|
322
|
+
program
|
|
323
|
+
.command('jobs')
|
|
324
|
+
.description('List all screening jobs')
|
|
325
|
+
.option('-s, --status <status>', 'Filter by status (pending, processing, completed, failed)')
|
|
326
|
+
.option('-p, --page <number>', 'Page number', '1')
|
|
327
|
+
.option('-l, --limit <number>', 'Results per page', '10')
|
|
328
|
+
.action(async (options) => {
|
|
329
|
+
try {
|
|
330
|
+
const api = initApi();
|
|
331
|
+
const spinner = (0, ora_1.default)('Fetching jobs...').start();
|
|
332
|
+
const result = await api.listJobs({
|
|
333
|
+
status: options.status,
|
|
334
|
+
page: parseInt(options.page),
|
|
335
|
+
per_page: parseInt(options.limit),
|
|
336
|
+
});
|
|
337
|
+
spinner.stop();
|
|
338
|
+
if (isJsonOutput) {
|
|
339
|
+
outputJson({
|
|
340
|
+
success: true,
|
|
341
|
+
jobs: result.jobs,
|
|
342
|
+
total: result.total,
|
|
343
|
+
page: result.page,
|
|
344
|
+
});
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (result.jobs.length === 0) {
|
|
348
|
+
console.log(chalk_1.default.yellow('No jobs found.'));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
console.log(chalk_1.default.blue(`📋 Jobs (${result.total} total):\n`));
|
|
352
|
+
result.jobs.forEach((job) => {
|
|
353
|
+
const statusColor = job.status === 'completed' ? chalk_1.default.green :
|
|
354
|
+
job.status === 'failed' ? chalk_1.default.red :
|
|
355
|
+
job.status === 'processing' ? chalk_1.default.yellow : chalk_1.default.gray;
|
|
356
|
+
console.log(` ${chalk_1.default.bold(`#${job.id}`)} ${job.title}`);
|
|
357
|
+
console.log(` Status: ${statusColor(job.status)} | Candidates: ${job.total_candidates}`);
|
|
358
|
+
console.log(` Created: ${chalk_1.default.gray(new Date(job.created_at).toLocaleString())}`);
|
|
359
|
+
console.log();
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
catch (error) {
|
|
363
|
+
handleError(error, 'Failed to fetch jobs');
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
/**
|
|
367
|
+
* Results command - Get job results
|
|
368
|
+
*/
|
|
369
|
+
program
|
|
370
|
+
.command('results')
|
|
371
|
+
.description('Get results for a screening job')
|
|
372
|
+
.requiredOption('-j, --job <id>', 'Job ID')
|
|
373
|
+
.option('--min-score <number>', 'Filter by minimum score')
|
|
374
|
+
.option('--only-top-n <number>', 'Only return top N candidates')
|
|
375
|
+
.action(async (options) => {
|
|
376
|
+
try {
|
|
377
|
+
const api = initApi();
|
|
378
|
+
const spinner = (0, ora_1.default)('Fetching results...').start();
|
|
379
|
+
const jobId = parseInt(options.job);
|
|
380
|
+
if (isNaN(jobId)) {
|
|
381
|
+
throw new types_1.ValidationError('Invalid job ID');
|
|
382
|
+
}
|
|
383
|
+
const result = await api.getResults(jobId, {
|
|
384
|
+
min_score: options.minScore ? parseInt(options.minScore) : undefined,
|
|
385
|
+
only_top_n: options.onlyTopN ? parseInt(options.onlyTopN) : undefined,
|
|
386
|
+
});
|
|
387
|
+
spinner.stop();
|
|
388
|
+
if (isJsonOutput) {
|
|
389
|
+
outputJson({
|
|
390
|
+
success: true,
|
|
391
|
+
job_id: result.job_id,
|
|
392
|
+
job_title: result.job_title,
|
|
393
|
+
status: result.status,
|
|
394
|
+
results: result.candidates,
|
|
395
|
+
});
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
console.log(chalk_1.default.blue(`📊 Results for: ${result.job_title}\n`));
|
|
399
|
+
console.log(chalk_1.default.gray(` Status: ${result.status}`));
|
|
400
|
+
console.log(chalk_1.default.gray(` Screened: ${new Date(result.screened_at).toLocaleString()}`));
|
|
401
|
+
console.log(chalk_1.default.gray(` Total: ${result.total_candidates} candidates\n`));
|
|
402
|
+
// Sort by score
|
|
403
|
+
const sorted = [...result.candidates].sort((a, b) => b.score - a.score);
|
|
404
|
+
sorted.forEach((candidate, index) => {
|
|
405
|
+
const scoreColor = candidate.score >= 80 ? chalk_1.default.green :
|
|
406
|
+
candidate.score >= 60 ? chalk_1.default.yellow : chalk_1.default.red;
|
|
407
|
+
console.log(`${chalk_1.default.bold(index + 1)}. ${candidate.name} ${scoreColor(`(${candidate.score}/100)`)}`);
|
|
408
|
+
console.log(chalk_1.default.gray(` ${candidate.summary?.substring(0, 100) || 'No summary'}...`));
|
|
409
|
+
if (candidate.interview_questions?.length > 0) {
|
|
410
|
+
console.log(chalk_1.default.blue(` Top Interview Question:`));
|
|
411
|
+
console.log(chalk_1.default.gray(` "${candidate.interview_questions[0]}"`));
|
|
412
|
+
}
|
|
413
|
+
console.log();
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
catch (error) {
|
|
417
|
+
handleError(error, 'Failed to fetch results');
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
/**
|
|
421
|
+
* Status command - Check job status
|
|
422
|
+
*/
|
|
423
|
+
program
|
|
424
|
+
.command('status')
|
|
425
|
+
.description('Check status of a screening job')
|
|
426
|
+
.requiredOption('-j, --job <id>', 'Job ID')
|
|
427
|
+
.option('-w, --watch', 'Watch for status changes')
|
|
428
|
+
.action(async (options) => {
|
|
429
|
+
try {
|
|
430
|
+
const api = initApi();
|
|
431
|
+
const jobId = parseInt(options.job);
|
|
432
|
+
if (isNaN(jobId)) {
|
|
433
|
+
throw new types_1.ValidationError('Invalid job ID');
|
|
434
|
+
}
|
|
435
|
+
if (options.watch) {
|
|
436
|
+
console.log(chalk_1.default.blue('⏳ Watching for status changes...\n'));
|
|
437
|
+
const finalStatus = await api.pollForCompletion(jobId, {
|
|
438
|
+
onProgress: (status) => {
|
|
439
|
+
const progress = status.progress || 0;
|
|
440
|
+
const bar = '█'.repeat(Math.floor(progress / 10)) + '░'.repeat(10 - Math.floor(progress / 10));
|
|
441
|
+
console.clear();
|
|
442
|
+
console.log(chalk_1.default.blue(`📋 Job #${jobId}\n`));
|
|
443
|
+
console.log(chalk_1.default.gray(` Status: ${chalk_1.default.bold(status.status)}`));
|
|
444
|
+
console.log(chalk_1.default.gray(` Progress: [${bar}] ${progress}%`));
|
|
445
|
+
if (status.message) {
|
|
446
|
+
console.log(chalk_1.default.gray(` Message: ${status.message}`));
|
|
447
|
+
}
|
|
448
|
+
},
|
|
449
|
+
});
|
|
450
|
+
console.log(chalk_1.default.green(`\n✓ Job ${finalStatus.status}!`));
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
const spinner = (0, ora_1.default)('Checking status...').start();
|
|
454
|
+
const status = await api.getJobStatus(jobId);
|
|
455
|
+
spinner.stop();
|
|
456
|
+
if (isJsonOutput) {
|
|
457
|
+
outputJson({
|
|
458
|
+
success: true,
|
|
459
|
+
job_id: status.job_id,
|
|
460
|
+
status: status.status,
|
|
461
|
+
progress: status.progress,
|
|
462
|
+
});
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
const statusColor = status.status === 'completed' ? chalk_1.default.green :
|
|
466
|
+
status.status === 'failed' ? chalk_1.default.red :
|
|
467
|
+
status.status === 'processing' ? chalk_1.default.yellow : chalk_1.default.gray;
|
|
468
|
+
console.log(chalk_1.default.blue(`📋 Job #${jobId}\n`));
|
|
469
|
+
console.log(chalk_1.default.gray(` Status: ${statusColor(status.status)}`));
|
|
470
|
+
if (status.progress !== undefined) {
|
|
471
|
+
console.log(chalk_1.default.gray(` Progress: ${status.progress}%`));
|
|
472
|
+
}
|
|
473
|
+
if (status.message) {
|
|
474
|
+
console.log(chalk_1.default.gray(` Message: ${status.message}`));
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
handleError(error, 'Failed to check status');
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
/**
|
|
483
|
+
* Email command - Generate email for candidate
|
|
484
|
+
*/
|
|
485
|
+
program
|
|
486
|
+
.command('email')
|
|
487
|
+
.description('Generate an email for a candidate')
|
|
488
|
+
.requiredOption('-j, --job <id>', 'Job ID')
|
|
489
|
+
.requiredOption('-c, --candidate <id>', 'Candidate ID')
|
|
490
|
+
.requiredOption('-t, --type <type>', 'Email type (invite, rejection, followup)')
|
|
491
|
+
.option('-m, --message <text>', 'Custom message to include')
|
|
492
|
+
.action(async (options) => {
|
|
493
|
+
try {
|
|
494
|
+
const api = initApi();
|
|
495
|
+
const spinner = (0, ora_1.default)('Generating email...').start();
|
|
496
|
+
const jobId = parseInt(options.job);
|
|
497
|
+
const candidateId = parseInt(options.candidate);
|
|
498
|
+
if (isNaN(jobId) || isNaN(candidateId)) {
|
|
499
|
+
throw new types_1.ValidationError('Invalid job or candidate ID');
|
|
500
|
+
}
|
|
501
|
+
const validTypes = ['invite', 'rejection', 'followup'];
|
|
502
|
+
if (!validTypes.includes(options.type)) {
|
|
503
|
+
throw new types_1.ValidationError(`Invalid email type. Must be: ${validTypes.join(', ')}`);
|
|
504
|
+
}
|
|
505
|
+
const result = await api.generateEmail({
|
|
506
|
+
job_id: jobId,
|
|
507
|
+
candidate_id: candidateId,
|
|
508
|
+
type: options.type,
|
|
509
|
+
custom_message: options.message,
|
|
510
|
+
});
|
|
511
|
+
spinner.stop();
|
|
512
|
+
if (isJsonOutput) {
|
|
513
|
+
outputJson({
|
|
514
|
+
success: true,
|
|
515
|
+
candidate_id: result.candidate_id,
|
|
516
|
+
email_type: result.email_type,
|
|
517
|
+
subject: result.subject,
|
|
518
|
+
body: result.body,
|
|
519
|
+
});
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
console.log(chalk_1.default.blue(`📧 Email Generated\n`));
|
|
523
|
+
console.log(chalk_1.default.gray(` Type: ${result.email_type}`));
|
|
524
|
+
console.log(chalk_1.default.gray(` Subject: ${result.subject}\n`));
|
|
525
|
+
console.log(result.body);
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
handleError(error, 'Failed to generate email');
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
/**
|
|
532
|
+
* Configure command - Update configuration
|
|
533
|
+
*/
|
|
534
|
+
program
|
|
535
|
+
.command('configure')
|
|
536
|
+
.description('Update configuration settings')
|
|
537
|
+
.option('-t, --token <token>', 'API token')
|
|
538
|
+
.option('-u, --base-url <url>', 'API base URL')
|
|
539
|
+
.option('-w, --webhook <url>', 'Default webhook URL')
|
|
540
|
+
.option('-l, --leniency <number>', 'Default leniency level (1-10)')
|
|
541
|
+
.option('-y, --yes', 'Skip confirmation (auto-enabled for non-interactive/JSON mode)')
|
|
542
|
+
.option('--clear', 'Clear configuration')
|
|
543
|
+
.action(async (options) => {
|
|
544
|
+
try {
|
|
545
|
+
if (options.clear) {
|
|
546
|
+
// Auto-skip confirmation for agents using JSON output or CI mode
|
|
547
|
+
const autoYes = isJsonOutput || process.env.CI === 'true';
|
|
548
|
+
if (!options.yes && !autoYes) {
|
|
549
|
+
const confirmed = await confirm('Clear all configuration?');
|
|
550
|
+
if (!confirmed) {
|
|
551
|
+
console.log(chalk_1.default.yellow('Configuration cancelled.'));
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
(0, config_1.getConfigManager)().clear();
|
|
556
|
+
if (isJsonOutput) {
|
|
557
|
+
outputJson({ success: true, message: 'Configuration cleared' });
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
console.log(chalk_1.default.green('✓ Configuration cleared'));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const updates = {};
|
|
564
|
+
if (options.token)
|
|
565
|
+
updates.apiToken = options.token;
|
|
566
|
+
if (options.baseUrl)
|
|
567
|
+
updates.baseUrl = options.baseUrl;
|
|
568
|
+
if (options.webhook)
|
|
569
|
+
updates.webhookUrl = options.webhook;
|
|
570
|
+
if (options.leniency)
|
|
571
|
+
updates.defaultLeniency = parseInt(options.leniency);
|
|
572
|
+
if (Object.keys(updates).length === 0) {
|
|
573
|
+
// Show current config
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
(0, config_1.saveConfig)(updates);
|
|
577
|
+
}
|
|
578
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
579
|
+
if (isJsonOutput) {
|
|
580
|
+
outputJson({
|
|
581
|
+
success: true,
|
|
582
|
+
config: {
|
|
583
|
+
baseUrl: config.baseUrl,
|
|
584
|
+
webhookUrl: config.webhookUrl,
|
|
585
|
+
defaultLeniency: config.defaultLeniency,
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
console.log(chalk_1.default.green('✓ Configuration updated'));
|
|
591
|
+
console.log(chalk_1.default.gray(`\n API URL: ${config.baseUrl}`));
|
|
592
|
+
console.log(chalk_1.default.gray(` Webhook: ${config.webhookUrl || '(not set)'}`));
|
|
593
|
+
console.log(chalk_1.default.gray(` Default Leniency: ${config.defaultLeniency}`));
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
handleError(error, 'Failed to update configuration');
|
|
597
|
+
}
|
|
598
|
+
});
|
|
599
|
+
/**
|
|
600
|
+
* Cancel command - Cancel a running job
|
|
601
|
+
*/
|
|
602
|
+
program
|
|
603
|
+
.command('cancel')
|
|
604
|
+
.description('Cancel a running screening job')
|
|
605
|
+
.requiredOption('-j, --job <id>', 'Job ID to cancel')
|
|
606
|
+
.action(async (options) => {
|
|
607
|
+
try {
|
|
608
|
+
const api = initApi();
|
|
609
|
+
const jobId = parseInt(options.job);
|
|
610
|
+
if (isNaN(jobId)) {
|
|
611
|
+
throw new types_1.ValidationError('Invalid job ID');
|
|
612
|
+
}
|
|
613
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Cancelling job...').start() : null;
|
|
614
|
+
const result = await api.cancelJob(jobId);
|
|
615
|
+
if (spinner)
|
|
616
|
+
spinner.stop();
|
|
617
|
+
if (isJsonOutput) {
|
|
618
|
+
outputJson(result);
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
console.log(chalk_1.default.green(`✓ Job #${jobId} cancelled`));
|
|
622
|
+
console.log(chalk_1.default.gray(` Status: ${result.status}`));
|
|
623
|
+
console.log(chalk_1.default.gray(` Message: ${result.message}`));
|
|
624
|
+
}
|
|
625
|
+
catch (error) {
|
|
626
|
+
handleError(error, 'Failed to cancel job');
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
/**
|
|
630
|
+
* Compare command - Compare candidates side-by-side
|
|
631
|
+
*/
|
|
632
|
+
program
|
|
633
|
+
.command('compare')
|
|
634
|
+
.description('Compare multiple candidates side-by-side')
|
|
635
|
+
.requiredOption('-j, --job <id>', 'Job ID')
|
|
636
|
+
.requiredOption('-c, --candidates <ids>', 'Comma-separated candidate IDs')
|
|
637
|
+
.action(async (options) => {
|
|
638
|
+
try {
|
|
639
|
+
const api = initApi();
|
|
640
|
+
const jobId = parseInt(options.job);
|
|
641
|
+
const candidateIds = options.candidates.split(',').map(id => parseInt(id.trim()));
|
|
642
|
+
if (isNaN(jobId)) {
|
|
643
|
+
throw new types_1.ValidationError('Invalid job ID');
|
|
644
|
+
}
|
|
645
|
+
if (candidateIds.some(id => isNaN(id))) {
|
|
646
|
+
throw new types_1.ValidationError('Invalid candidate ID');
|
|
647
|
+
}
|
|
648
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Comparing candidates...').start() : null;
|
|
649
|
+
const result = await api.compareCandidates(jobId, candidateIds);
|
|
650
|
+
if (spinner)
|
|
651
|
+
spinner.stop();
|
|
652
|
+
if (isJsonOutput) {
|
|
653
|
+
outputJson(result);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
console.log(chalk_1.default.blue(`📊 Comparison (${result.candidates.length} candidates)\n`));
|
|
657
|
+
console.log(chalk_1.default.green(` Top Candidate: ${result.comparison.top_candidate}`));
|
|
658
|
+
console.log(chalk_1.default.gray(` Score Difference: ${result.comparison.score_diff}\n`));
|
|
659
|
+
result.candidates.forEach((cand, idx) => {
|
|
660
|
+
const scoreColor = cand.score >= 80 ? chalk_1.default.green : cand.score >= 60 ? chalk_1.default.yellow : chalk_1.default.red;
|
|
661
|
+
console.log(`${idx + 1}. ${cand.name} ${scoreColor(`(${cand.score})`)}`);
|
|
662
|
+
console.log(chalk_1.default.gray(` ${cand.summary?.substring(0, 80) || 'No summary'}...`));
|
|
663
|
+
console.log();
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
catch (error) {
|
|
667
|
+
handleError(error, 'Failed to compare candidates');
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
/**
|
|
671
|
+
* Outcome command - Report hiring outcome
|
|
672
|
+
*/
|
|
673
|
+
program
|
|
674
|
+
.command('outcome')
|
|
675
|
+
.description('Report hiring outcome to improve AI accuracy')
|
|
676
|
+
.requiredOption('-j, --job <id>', 'Job ID')
|
|
677
|
+
.requiredOption('-c, --candidate <id>', 'Candidate ID')
|
|
678
|
+
.requiredOption('-o, --outcome <outcome>', 'Outcome (hired, rejected, withdrawn)')
|
|
679
|
+
.action(async (options) => {
|
|
680
|
+
try {
|
|
681
|
+
const api = initApi();
|
|
682
|
+
const jobId = parseInt(options.job);
|
|
683
|
+
const candidateId = parseInt(options.candidate);
|
|
684
|
+
const validOutcomes = ['hired', 'rejected', 'withdrawn'];
|
|
685
|
+
if (isNaN(jobId) || isNaN(candidateId)) {
|
|
686
|
+
throw new types_1.ValidationError('Invalid job or candidate ID');
|
|
687
|
+
}
|
|
688
|
+
if (!validOutcomes.includes(options.outcome)) {
|
|
689
|
+
throw new types_1.ValidationError(`Invalid outcome. Must be: ${validOutcomes.join(', ')}`);
|
|
690
|
+
}
|
|
691
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Reporting outcome...').start() : null;
|
|
692
|
+
const result = await api.reportOutcome(jobId, candidateId, options.outcome);
|
|
693
|
+
if (spinner)
|
|
694
|
+
spinner.stop();
|
|
695
|
+
if (isJsonOutput) {
|
|
696
|
+
outputJson(result);
|
|
697
|
+
return;
|
|
698
|
+
}
|
|
699
|
+
console.log(chalk_1.default.green('✓ Outcome reported'));
|
|
700
|
+
console.log(chalk_1.default.gray(` Message: ${result.message}`));
|
|
701
|
+
}
|
|
702
|
+
catch (error) {
|
|
703
|
+
handleError(error, 'Failed to report outcome');
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
/**
|
|
707
|
+
* Webhook-test command - Test a webhook endpoint
|
|
708
|
+
*/
|
|
709
|
+
program
|
|
710
|
+
.command('webhook-test')
|
|
711
|
+
.description('Test a webhook endpoint')
|
|
712
|
+
.requiredOption('-u, --url <url>', 'Webhook URL to test')
|
|
713
|
+
.action(async (options) => {
|
|
714
|
+
try {
|
|
715
|
+
const api = initApi();
|
|
716
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Testing webhook...').start() : null;
|
|
717
|
+
const result = await api.testWebhook(options.url);
|
|
718
|
+
if (spinner)
|
|
719
|
+
spinner.stop();
|
|
720
|
+
if (isJsonOutput) {
|
|
721
|
+
outputJson(result);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
if (result.success) {
|
|
725
|
+
console.log(chalk_1.default.green('✓ Webhook test successful'));
|
|
726
|
+
}
|
|
727
|
+
else {
|
|
728
|
+
console.log(chalk_1.default.red('✗ Webhook test failed'));
|
|
729
|
+
}
|
|
730
|
+
console.log(chalk_1.default.gray(` Message: ${result.message}`));
|
|
731
|
+
if (result.response_code) {
|
|
732
|
+
console.log(chalk_1.default.gray(` Response Code: ${result.response_code}`));
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
handleError(error, 'Failed to test webhook');
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
/**
|
|
740
|
+
* Rate-limit command - Check rate limit status
|
|
741
|
+
*/
|
|
742
|
+
program
|
|
743
|
+
.command('rate-limit')
|
|
744
|
+
.description('Check current API rate limit status')
|
|
745
|
+
.action(async (options) => {
|
|
746
|
+
try {
|
|
747
|
+
const api = initApi();
|
|
748
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Checking rate limits...').start() : null;
|
|
749
|
+
const result = await api.getRateLimit();
|
|
750
|
+
if (spinner)
|
|
751
|
+
spinner.stop();
|
|
752
|
+
if (isJsonOutput) {
|
|
753
|
+
outputJson(result);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
console.log(chalk_1.default.blue('📊 Rate Limit Status\n'));
|
|
757
|
+
console.log(chalk_1.default.gray(` Limit: ${result.limit} requests/minute`));
|
|
758
|
+
console.log(chalk_1.default.gray(` Remaining: ${result.remaining}`));
|
|
759
|
+
console.log(chalk_1.default.gray(` Resets: ${result.reset_at}`));
|
|
760
|
+
console.log(chalk_1.default.gray(` Reset in: ${result.reset_in_seconds} seconds`));
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
handleError(error, 'Failed to check rate limit');
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
/**
|
|
767
|
+
* Candidate command - Get candidate details
|
|
768
|
+
*/
|
|
769
|
+
program
|
|
770
|
+
.command('candidate')
|
|
771
|
+
.description('Get details of a specific candidate')
|
|
772
|
+
.requiredOption('-i, --id <id>', 'Candidate ID')
|
|
773
|
+
.action(async (options) => {
|
|
774
|
+
try {
|
|
775
|
+
const api = initApi();
|
|
776
|
+
const candidateId = parseInt(options.id);
|
|
777
|
+
if (isNaN(candidateId)) {
|
|
778
|
+
throw new types_1.ValidationError('Invalid candidate ID');
|
|
779
|
+
}
|
|
780
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Fetching candidate...').start() : null;
|
|
781
|
+
const result = await api.getCandidate(candidateId);
|
|
782
|
+
if (spinner)
|
|
783
|
+
spinner.stop();
|
|
784
|
+
if (isJsonOutput) {
|
|
785
|
+
outputJson(result);
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
const scoreColor = result.score >= 80 ? chalk_1.default.green : result.score >= 60 ? chalk_1.default.yellow : chalk_1.default.red;
|
|
789
|
+
console.log(chalk_1.default.blue(`👤 ${result.name}\n`));
|
|
790
|
+
console.log(chalk_1.default.gray(` Score: ${scoreColor(result.score)}`));
|
|
791
|
+
console.log(chalk_1.default.gray(` Email: ${result.email || 'N/A'}`));
|
|
792
|
+
console.log(chalk_1.default.gray(` Status: ${result.status || 'N/A'}`));
|
|
793
|
+
console.log(chalk_1.default.gray(`\n Summary: ${result.summary?.substring(0, 150) || 'No summary'}...`));
|
|
794
|
+
}
|
|
795
|
+
catch (error) {
|
|
796
|
+
handleError(error, 'Failed to fetch candidate');
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
/**
|
|
800
|
+
* Set-status command - Update candidate status
|
|
801
|
+
*/
|
|
802
|
+
program
|
|
803
|
+
.command('set-status')
|
|
804
|
+
.description('Update a candidate\'s status')
|
|
805
|
+
.requiredOption('-i, --id <id>', 'Candidate ID')
|
|
806
|
+
.requiredOption('-s, --status <status>', 'New status (pending, shortlisted, rejected, interviewed, offered, hired)')
|
|
807
|
+
.action(async (options) => {
|
|
808
|
+
try {
|
|
809
|
+
const api = initApi();
|
|
810
|
+
const candidateId = parseInt(options.id);
|
|
811
|
+
const validStatuses = ['pending', 'shortlisted', 'rejected', 'interviewed', 'offered', 'hired'];
|
|
812
|
+
if (isNaN(candidateId)) {
|
|
813
|
+
throw new types_1.ValidationError('Invalid candidate ID');
|
|
814
|
+
}
|
|
815
|
+
if (!validStatuses.includes(options.status)) {
|
|
816
|
+
throw new types_1.ValidationError(`Invalid status. Must be: ${validStatuses.join(', ')}`);
|
|
817
|
+
}
|
|
818
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Updating status...').start() : null;
|
|
819
|
+
const result = await api.updateCandidateStatus(candidateId, options.status);
|
|
820
|
+
if (spinner)
|
|
821
|
+
spinner.stop();
|
|
822
|
+
if (isJsonOutput) {
|
|
823
|
+
outputJson(result);
|
|
824
|
+
return;
|
|
825
|
+
}
|
|
826
|
+
console.log(chalk_1.default.green(`✓ Status updated to ${options.status}`));
|
|
827
|
+
}
|
|
828
|
+
catch (error) {
|
|
829
|
+
handleError(error, 'Failed to update status');
|
|
830
|
+
}
|
|
831
|
+
});
|
|
832
|
+
/**
|
|
833
|
+
* Schema command - Get API schema
|
|
834
|
+
*/
|
|
835
|
+
program
|
|
836
|
+
.command('schema')
|
|
837
|
+
.description('Get API schema for discovery')
|
|
838
|
+
.action(async (options) => {
|
|
839
|
+
try {
|
|
840
|
+
const api = initApi();
|
|
841
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Fetching schema...').start() : null;
|
|
842
|
+
const result = await api.getSchema();
|
|
843
|
+
if (spinner)
|
|
844
|
+
spinner.stop();
|
|
845
|
+
if (isJsonOutput) {
|
|
846
|
+
outputJson(result);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
console.log(chalk_1.default.blue(`📖 API Schema v${result.version}\n`));
|
|
850
|
+
result.endpoints.forEach((ep) => {
|
|
851
|
+
console.log(` ${chalk_1.default.bold(ep.path)}`);
|
|
852
|
+
console.log(chalk_1.default.gray(` Methods: ${ep.methods.join(', ')}`));
|
|
853
|
+
console.log(chalk_1.default.gray(` ${ep.description}`));
|
|
854
|
+
console.log();
|
|
855
|
+
});
|
|
856
|
+
}
|
|
857
|
+
catch (error) {
|
|
858
|
+
handleError(error, 'Failed to fetch schema');
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
/**
|
|
862
|
+
* WhoAmI command - Verify token and get profile info
|
|
863
|
+
*/
|
|
864
|
+
program
|
|
865
|
+
.command('whoami')
|
|
866
|
+
.alias('profile')
|
|
867
|
+
.description('Verify API token and get profile info')
|
|
868
|
+
.action(async () => {
|
|
869
|
+
try {
|
|
870
|
+
const api = initApi();
|
|
871
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Verifying token...').start() : null;
|
|
872
|
+
const result = await api.get('/schema/validate');
|
|
873
|
+
if (spinner)
|
|
874
|
+
spinner.stop();
|
|
875
|
+
if (isJsonOutput) {
|
|
876
|
+
outputJson(result.data);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
console.log(chalk_1.default.blue('👤 Agent Profile\n'));
|
|
880
|
+
console.log(` Name: ${chalk_1.default.bold(result.data.user.name)}`);
|
|
881
|
+
console.log(` Email: ${result.data.user.email}`);
|
|
882
|
+
console.log(` Balance: ${chalk_1.default.green(result.data.credits.formatted_balance)}`);
|
|
883
|
+
if (result.data.credits.is_low) {
|
|
884
|
+
console.log(chalk_1.default.yellow(' ⚠️ Warning: Your credit balance is low.'));
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
catch (error) {
|
|
888
|
+
handleError(error, 'Token verification failed');
|
|
889
|
+
}
|
|
890
|
+
});
|
|
891
|
+
/**
|
|
892
|
+
* Agent Keys command - Manage agent API keys
|
|
893
|
+
*/
|
|
894
|
+
program
|
|
895
|
+
.command('agent-keys')
|
|
896
|
+
.description('Manage agent API keys')
|
|
897
|
+
.option('-a, --action <action>', 'Action: list, create, show, revoke', 'list')
|
|
898
|
+
.option('-n, --name <name>', 'Key name (for create)')
|
|
899
|
+
.option('-m, --monthly-limit <amount>', 'Monthly spend limit in dollars')
|
|
900
|
+
.option('-d, --daily-limit <amount>', 'Daily spend limit in dollars')
|
|
901
|
+
.option('-l, --lifetime-limit <amount>', 'Lifetime spend limit in dollars')
|
|
902
|
+
.option('-i, --id <id>', 'Key ID (for show/revoke)')
|
|
903
|
+
.option('-p, --permissions <perms>', 'Comma-separated permissions (read,jobs,emails,credits,agent-keys)')
|
|
904
|
+
.action(async (options) => {
|
|
905
|
+
try {
|
|
906
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
907
|
+
if (!config.apiToken) {
|
|
908
|
+
throw new Error('API token not configured. Run: hiresquire init -t <token>');
|
|
909
|
+
}
|
|
910
|
+
const api = initApi();
|
|
911
|
+
if (options.action === 'list') {
|
|
912
|
+
const response = await api.get('/agent-keys');
|
|
913
|
+
if (isJsonOutput) {
|
|
914
|
+
outputJson(response.data);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
console.log(chalk_1.default.blue('📋 Agent API Keys\n'));
|
|
918
|
+
response.data.keys.forEach((key) => {
|
|
919
|
+
console.log(` ${chalk_1.default.bold(key.name)} (${key.key_prefix}...)`);
|
|
920
|
+
console.log(` Status: ${key.is_active ? chalk_1.default.green('Active') : chalk_1.default.red('Inactive')}`);
|
|
921
|
+
console.log(` Monthly Spent: $${key.month_spent} / $${key.monthly_spend_limit || '∞'}`);
|
|
922
|
+
console.log(` Daily Spent: $${key.day_spent} / $${key.daily_spend_limit || '∞'}`);
|
|
923
|
+
console.log(` Total Spent: $${key.total_spent}`);
|
|
924
|
+
if (key.permissions && key.permissions.length > 0) {
|
|
925
|
+
console.log(` Permissions: ${key.permissions.join(', ')}`);
|
|
926
|
+
}
|
|
927
|
+
console.log();
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
else if (options.action === 'create') {
|
|
931
|
+
if (!options.name) {
|
|
932
|
+
throw new Error('Key name required for create. Use: --name "My Agent Key"');
|
|
933
|
+
}
|
|
934
|
+
const perms = options.permissions
|
|
935
|
+
? options.permissions.split(',').map(p => p.trim().toLowerCase())
|
|
936
|
+
: undefined;
|
|
937
|
+
const response = await api.post('/agent-keys', {
|
|
938
|
+
name: options.name,
|
|
939
|
+
monthly_spend_limit: options.monthlyLimit ? parseFloat(options.monthlyLimit) : null,
|
|
940
|
+
daily_spend_limit: options.dailyLimit ? parseFloat(options.dailyLimit) : null,
|
|
941
|
+
lifetime_spend_limit: options.lifetimeLimit ? parseFloat(options.lifetimeLimit) : null,
|
|
942
|
+
permissions: perms,
|
|
943
|
+
});
|
|
944
|
+
if (isJsonOutput) {
|
|
945
|
+
outputJson(response.data);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
console.log(chalk_1.default.green('✓ Agent API key created!'));
|
|
949
|
+
console.log(chalk_1.default.yellow('⚠️ Save this key - it will not be shown again:'));
|
|
950
|
+
console.log(chalk_1.default.bold(response.data.key.key));
|
|
951
|
+
console.log(chalk_1.default.gray(`Key prefix: ${response.data.key.key_prefix}`));
|
|
952
|
+
if (perms) {
|
|
953
|
+
console.log(chalk_1.default.gray(`Permissions: ${perms.join(', ')}`));
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
else if (options.action === 'show') {
|
|
957
|
+
if (!options.id) {
|
|
958
|
+
throw new Error('Key ID required. Use: --id <key_id>');
|
|
959
|
+
}
|
|
960
|
+
const response = await api.get(`/agent-keys/${options.id}`);
|
|
961
|
+
if (isJsonOutput) {
|
|
962
|
+
outputJson(response.data);
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
console.log(chalk_1.default.blue(`📋 Key: ${response.data.key.name}\n`));
|
|
966
|
+
console.log(` Prefix: ${response.data.key.key_prefix}`);
|
|
967
|
+
console.log(` Status: ${response.data.key.is_active ? 'Active' : 'Inactive'}`);
|
|
968
|
+
console.log(` Total Spent: $${response.data.key.total_spent}`);
|
|
969
|
+
console.log(` Month Spent: $${response.data.key.month_spent}`);
|
|
970
|
+
console.log(` Daily Spent: $${response.data.key.day_spent}`);
|
|
971
|
+
}
|
|
972
|
+
else if (options.action === 'revoke') {
|
|
973
|
+
if (!options.id) {
|
|
974
|
+
throw new Error('Key ID required. Use: --id <key_id>');
|
|
975
|
+
}
|
|
976
|
+
const response = await api.delete(`/agent-keys/${options.id}`);
|
|
977
|
+
if (isJsonOutput) {
|
|
978
|
+
outputJson(response.data);
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
console.log(chalk_1.default.green('✓ Agent API key revoked'));
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
catch (error) {
|
|
985
|
+
handleError(error, 'Failed to manage agent keys');
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
/**
|
|
989
|
+
* Credits command - Manage prepaid credits
|
|
990
|
+
*/
|
|
991
|
+
program
|
|
992
|
+
.command('credits')
|
|
993
|
+
.description('Manage prepaid credits')
|
|
994
|
+
.option('-a, --action <action>', 'Action: balance, purchase, transactions, checkout, list-packs, auto-reload-enable, auto-reload-disable', 'balance')
|
|
995
|
+
.option('-m, --amount <amount>', 'Amount to purchase in dollars (min $5)')
|
|
996
|
+
.option('-p, --pack <pack>', 'Credit pack to purchase: pouch, satchel, chest')
|
|
997
|
+
.option('-l, --limit <number>', 'Number of transactions to show')
|
|
998
|
+
.option('--threshold <number>', 'Auto-reload threshold in dollars')
|
|
999
|
+
.option('--payment-method-id <id>', 'Stripe payment method ID')
|
|
1000
|
+
.action(async (options) => {
|
|
1001
|
+
try {
|
|
1002
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
1003
|
+
if (!config.apiToken) {
|
|
1004
|
+
throw new Error('API token not configured. Run: hiresquire init -t <token>');
|
|
1005
|
+
}
|
|
1006
|
+
const api = initApi();
|
|
1007
|
+
if (options.action === 'balance') {
|
|
1008
|
+
const response = await api.get('/credits/balance');
|
|
1009
|
+
if (isJsonOutput) {
|
|
1010
|
+
outputJson(response.data);
|
|
1011
|
+
return;
|
|
1012
|
+
}
|
|
1013
|
+
console.log(chalk_1.default.blue('💳 Credit Balance\n'));
|
|
1014
|
+
console.log(` Balance: ${chalk_1.default.bold(response.data.formatted_balance)}`);
|
|
1015
|
+
console.log(` Total Purchased: $${response.data.total_purchased}`);
|
|
1016
|
+
console.log(` Total Spent: $${response.data.total_spent}`);
|
|
1017
|
+
console.log(` Auto-reload: ${response.data.auto_reload_enabled ? chalk_1.default.green('Enabled') : chalk_1.default.gray('Disabled')}`);
|
|
1018
|
+
}
|
|
1019
|
+
else if (options.action === 'list-packs') {
|
|
1020
|
+
const response = await api.get('/credits/packs');
|
|
1021
|
+
if (isJsonOutput) {
|
|
1022
|
+
outputJson(response.data);
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
console.log(chalk_1.default.blue('📦 Credit Packs\n'));
|
|
1026
|
+
response.data.packs.forEach((pack) => {
|
|
1027
|
+
console.log(` ${chalk_1.default.bold(pack.id)}: $${pack.price} for ${pack.credits} credits${pack.bonus > 0 ? ` + ${pack.bonus} bonus` : ''}`);
|
|
1028
|
+
});
|
|
1029
|
+
}
|
|
1030
|
+
else if (options.action === 'checkout') {
|
|
1031
|
+
const pack = options.pack || 'pouch';
|
|
1032
|
+
if (!['pouch', 'satchel', 'chest'].includes(pack)) {
|
|
1033
|
+
throw new Error('Invalid pack. Use: pouch, satchel, or chest');
|
|
1034
|
+
}
|
|
1035
|
+
const response = await api.post('/credits/checkout-session', {
|
|
1036
|
+
pack: pack,
|
|
1037
|
+
success_url: 'https://hiresquireai.com/credits/success',
|
|
1038
|
+
cancel_url: 'https://hiresquireai.com/credits/cancel',
|
|
1039
|
+
});
|
|
1040
|
+
if (isJsonOutput) {
|
|
1041
|
+
outputJson(response.data);
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
console.log(chalk_1.default.blue('🔗 Checkout Session Created\n'));
|
|
1045
|
+
console.log(` Pack: ${pack}`);
|
|
1046
|
+
console.log(chalk_1.default.cyan(` Checkout URL: ${response.data.checkout_url}`));
|
|
1047
|
+
console.log(chalk_1.default.gray(' Open this URL in your browser to complete payment.'));
|
|
1048
|
+
}
|
|
1049
|
+
else if (options.action === 'transactions') {
|
|
1050
|
+
const response = await api.get('/credits/transactions', {
|
|
1051
|
+
params: { limit: options.limit || 20 }
|
|
1052
|
+
});
|
|
1053
|
+
if (isJsonOutput) {
|
|
1054
|
+
outputJson(response.data);
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
console.log(chalk_1.default.blue('📜 Credit Transactions\n'));
|
|
1058
|
+
response.data.transactions.forEach((tx) => {
|
|
1059
|
+
const sign = tx.amount >= 0 ? '+' : '';
|
|
1060
|
+
console.log(` ${sign}$${tx.amount} - ${tx.description || tx.type}`);
|
|
1061
|
+
console.log(chalk_1.default.gray(` Balance: $${tx.balance_after} | ${tx.created_at}`));
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
else if (options.action === 'auto-reload-enable') {
|
|
1065
|
+
if (!options.threshold || !options.amount || !options.paymentMethodId) {
|
|
1066
|
+
throw new Error('Auto-reload requires --threshold, --amount, and --payment-method-id');
|
|
1067
|
+
}
|
|
1068
|
+
const response = await api.post('/credits/auto-reload/enable', {
|
|
1069
|
+
threshold: parseFloat(options.threshold),
|
|
1070
|
+
amount: parseFloat(options.amount),
|
|
1071
|
+
payment_method_id: options.paymentMethodId
|
|
1072
|
+
});
|
|
1073
|
+
if (isJsonOutput) {
|
|
1074
|
+
outputJson(response.data);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
console.log(chalk_1.default.green('✓ Auto-reload enabled'));
|
|
1078
|
+
console.log(chalk_1.default.gray(` Threshold: $${response.data.auto_reload.threshold}`));
|
|
1079
|
+
console.log(chalk_1.default.gray(` Amount: $${response.data.auto_reload.amount} per reload`));
|
|
1080
|
+
}
|
|
1081
|
+
else if (options.action === 'auto-reload-disable') {
|
|
1082
|
+
const response = await api.post('/credits/auto-reload/disable');
|
|
1083
|
+
if (isJsonOutput) {
|
|
1084
|
+
outputJson(response.data);
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
console.log(chalk_1.default.green('✓ Auto-reload disabled'));
|
|
1088
|
+
}
|
|
1089
|
+
else if (options.action === 'purchase') {
|
|
1090
|
+
if (!options.paymentMethodId) {
|
|
1091
|
+
throw new Error('Direct purchase requires --payment-method-id');
|
|
1092
|
+
}
|
|
1093
|
+
const requestData = {
|
|
1094
|
+
payment_method_id: options.paymentMethodId
|
|
1095
|
+
};
|
|
1096
|
+
if (options.pack) {
|
|
1097
|
+
requestData.pack = options.pack;
|
|
1098
|
+
}
|
|
1099
|
+
else if (options.amount) {
|
|
1100
|
+
requestData.amount = parseFloat(options.amount);
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
throw new Error('Purchase requires either --pack or --amount');
|
|
1104
|
+
}
|
|
1105
|
+
const response = await api.post('/credits/purchase', requestData);
|
|
1106
|
+
if (isJsonOutput) {
|
|
1107
|
+
outputJson(response.data);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
console.log(chalk_1.default.green('✓ Purchase successful'));
|
|
1111
|
+
console.log(chalk_1.default.gray(` New balance: ${response.data.balance}`));
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
catch (error) {
|
|
1115
|
+
handleError(error, 'Failed to manage credits');
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
// ============================================================================
|
|
1119
|
+
// Calendar Commands
|
|
1120
|
+
// ============================================================================
|
|
1121
|
+
/**
|
|
1122
|
+
* Calendar:connect command - Connect a calendar provider
|
|
1123
|
+
*/
|
|
1124
|
+
program
|
|
1125
|
+
.command('calendar:connect <provider>')
|
|
1126
|
+
.description('Connect a calendar provider (calendly, calcom)')
|
|
1127
|
+
.option('-t, --token <token>', 'API token/key for calendly/calcom')
|
|
1128
|
+
.option('-c, --calendar-id <id>', 'Calendar ID (optional)')
|
|
1129
|
+
.action(async (provider, options) => {
|
|
1130
|
+
try {
|
|
1131
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
1132
|
+
if (!config.apiToken) {
|
|
1133
|
+
throw new Error('API token not configured. Run: hiresquire init -t <token>');
|
|
1134
|
+
}
|
|
1135
|
+
const api = initApi();
|
|
1136
|
+
const params = { provider };
|
|
1137
|
+
if (options.token)
|
|
1138
|
+
params.api_key = options.token;
|
|
1139
|
+
if (options.calendarId)
|
|
1140
|
+
params.calendar_id = options.calendarId;
|
|
1141
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Connecting calendar...').start() : null;
|
|
1142
|
+
const response = await api.createCalendarConnection(params);
|
|
1143
|
+
if (spinner)
|
|
1144
|
+
spinner.stop();
|
|
1145
|
+
if (isJsonOutput) {
|
|
1146
|
+
outputJson(response);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
console.log(chalk_1.default.green('✓ Calendar connection created'));
|
|
1150
|
+
console.log(` Provider: ${chalk_1.default.bold(response.data.provider)}`);
|
|
1151
|
+
}
|
|
1152
|
+
catch (error) {
|
|
1153
|
+
handleError(error, 'Failed to connect calendar');
|
|
1154
|
+
}
|
|
1155
|
+
});
|
|
1156
|
+
/**
|
|
1157
|
+
* Calendar:list command - List calendar connections
|
|
1158
|
+
*/
|
|
1159
|
+
program
|
|
1160
|
+
.command('calendar:list')
|
|
1161
|
+
.description('List calendar connections')
|
|
1162
|
+
.action(async () => {
|
|
1163
|
+
try {
|
|
1164
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
1165
|
+
if (!config.apiToken) {
|
|
1166
|
+
throw new Error('API token not configured. Run: hiresquire init -t <token>');
|
|
1167
|
+
}
|
|
1168
|
+
const api = initApi();
|
|
1169
|
+
const response = await api.listCalendarConnections();
|
|
1170
|
+
if (isJsonOutput) {
|
|
1171
|
+
outputJson(response);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
console.log(chalk_1.default.blue('📅 Calendar Connections\n'));
|
|
1175
|
+
response.data.forEach((conn) => {
|
|
1176
|
+
console.log(` ${chalk_1.default.bold(conn.provider)} - ${conn.status}`);
|
|
1177
|
+
});
|
|
1178
|
+
}
|
|
1179
|
+
catch (error) {
|
|
1180
|
+
handleError(error, 'Failed to list calendar connections');
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
// ============================================================================
|
|
1184
|
+
// Interview Commands
|
|
1185
|
+
// ============================================================================
|
|
1186
|
+
/**
|
|
1187
|
+
* Interviews:schedule command - Schedule an interview
|
|
1188
|
+
*/
|
|
1189
|
+
program
|
|
1190
|
+
.command('interviews:schedule')
|
|
1191
|
+
.description('Schedule an interview')
|
|
1192
|
+
.requiredOption('-j, --job <id>', 'Job ID')
|
|
1193
|
+
.requiredOption('-c, --candidate <id>', 'Candidate ID')
|
|
1194
|
+
.requiredOption('-t, --time <datetime>', 'Scheduled time (ISO 8601)')
|
|
1195
|
+
.option('-d, --duration <minutes>', 'Duration in minutes', '60')
|
|
1196
|
+
.option('-p, --provider <provider>', 'Calendar provider (calendly, calcom)')
|
|
1197
|
+
.action(async (options) => {
|
|
1198
|
+
try {
|
|
1199
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
1200
|
+
if (!config.apiToken) {
|
|
1201
|
+
throw new Error('API token not configured. Run: hiresquire init -t <token>');
|
|
1202
|
+
}
|
|
1203
|
+
const api = initApi();
|
|
1204
|
+
const params = {
|
|
1205
|
+
job_id: parseInt(options.job),
|
|
1206
|
+
candidate_id: parseInt(options.candidate),
|
|
1207
|
+
scheduled_at: options.time,
|
|
1208
|
+
duration_minutes: parseInt(options.duration),
|
|
1209
|
+
};
|
|
1210
|
+
if (options.provider)
|
|
1211
|
+
params.provider = options.provider;
|
|
1212
|
+
const response = await api.createInterview(params);
|
|
1213
|
+
if (isJsonOutput) {
|
|
1214
|
+
outputJson(response);
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
console.log(chalk_1.default.green('✓ Interview scheduled'));
|
|
1218
|
+
console.log(` Interview ID: ${chalk_1.default.bold(response.data.id)}`);
|
|
1219
|
+
if (response.data.meeting_link) {
|
|
1220
|
+
console.log(` Meeting Link: ${chalk_1.default.cyan(response.data.meeting_link)}`);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
catch (error) {
|
|
1224
|
+
handleError(error, 'Failed to schedule interview');
|
|
1225
|
+
}
|
|
1226
|
+
});
|
|
1227
|
+
/**
|
|
1228
|
+
* Interviews:list command - List interviews
|
|
1229
|
+
*/
|
|
1230
|
+
program
|
|
1231
|
+
.command('interviews:list')
|
|
1232
|
+
.description('List interviews')
|
|
1233
|
+
.option('-j, --job <id>', 'Filter by job ID')
|
|
1234
|
+
.action(async (options) => {
|
|
1235
|
+
try {
|
|
1236
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
1237
|
+
if (!config.apiToken) {
|
|
1238
|
+
throw new Error('API token not configured. Run: hiresquire init -t <token>');
|
|
1239
|
+
}
|
|
1240
|
+
const api = initApi();
|
|
1241
|
+
const params = {};
|
|
1242
|
+
if (options.job)
|
|
1243
|
+
params.job_id = parseInt(options.job);
|
|
1244
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Fetching interviews...').start() : null;
|
|
1245
|
+
const response = await api.listInterviews(params.job_id);
|
|
1246
|
+
if (spinner)
|
|
1247
|
+
spinner.stop();
|
|
1248
|
+
if (isJsonOutput) {
|
|
1249
|
+
outputJson(response);
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
console.log(chalk_1.default.blue('📅 Interviews\n'));
|
|
1253
|
+
response.data.forEach((interview) => {
|
|
1254
|
+
console.log(` ID: ${interview.id} | Job: ${interview.job_posting_id} | Candidate: ${interview.candidate_id}`);
|
|
1255
|
+
console.log(` Scheduled: ${interview.scheduled_at} | Status: ${interview.status}`);
|
|
1256
|
+
if (interview.meeting_link) {
|
|
1257
|
+
console.log(` Link: ${interview.meeting_link}`);
|
|
1258
|
+
}
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
catch (error) {
|
|
1262
|
+
handleError(error, 'Failed to list interviews');
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
// ============================================================================
|
|
1266
|
+
// Meeting Commands
|
|
1267
|
+
// ============================================================================
|
|
1268
|
+
/**
|
|
1269
|
+
* Meetings:link command - Generate a meeting link
|
|
1270
|
+
*/
|
|
1271
|
+
program
|
|
1272
|
+
.command('meetings:link')
|
|
1273
|
+
.description('Generate a meeting link')
|
|
1274
|
+
.requiredOption('-p, --provider <provider>', 'Calendar provider (calendly, calcom)')
|
|
1275
|
+
.requiredOption('-t, --topic <topic>', 'Meeting topic')
|
|
1276
|
+
.option('-d, --duration <minutes>', 'Duration in minutes', '60')
|
|
1277
|
+
.action(async (options) => {
|
|
1278
|
+
try {
|
|
1279
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
1280
|
+
if (!config.apiToken) {
|
|
1281
|
+
throw new Error('API token not configured. Run: hiresquire init -t <token>');
|
|
1282
|
+
}
|
|
1283
|
+
const api = initApi();
|
|
1284
|
+
const params = {
|
|
1285
|
+
provider: options.provider,
|
|
1286
|
+
topic: options.topic,
|
|
1287
|
+
duration: parseInt(options.duration),
|
|
1288
|
+
};
|
|
1289
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Generating meeting link...').start() : null;
|
|
1290
|
+
const response = await api.generateMeetingLink(params);
|
|
1291
|
+
if (spinner)
|
|
1292
|
+
spinner.stop();
|
|
1293
|
+
if (isJsonOutput) {
|
|
1294
|
+
outputJson(response);
|
|
1295
|
+
return;
|
|
1296
|
+
}
|
|
1297
|
+
console.log(chalk_1.default.green('✓ Meeting link generated'));
|
|
1298
|
+
if (response.data) {
|
|
1299
|
+
console.log(` Link: ${chalk_1.default.cyan(response.data.link)}`);
|
|
1300
|
+
console.log(` Provider: ${response.data.provider}`);
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
catch (error) {
|
|
1304
|
+
handleError(error, 'Failed to generate meeting link');
|
|
1305
|
+
}
|
|
1306
|
+
});
|
|
1307
|
+
/**
|
|
1308
|
+
* Estimate command - Estimate screening cost
|
|
1309
|
+
*/
|
|
1310
|
+
program
|
|
1311
|
+
.command('estimate')
|
|
1312
|
+
.description('Estimate screening cost')
|
|
1313
|
+
.option('-c, --candidates <number>', 'Number of candidates', '10')
|
|
1314
|
+
.action(async (options) => {
|
|
1315
|
+
try {
|
|
1316
|
+
const config = (0, config_1.getConfigManager)().load();
|
|
1317
|
+
if (!config.apiToken) {
|
|
1318
|
+
throw new Error('API token not configured. Run: hiresquire init -t <token>');
|
|
1319
|
+
}
|
|
1320
|
+
const api = initApi();
|
|
1321
|
+
const spinner = !isJsonOutput ? (0, ora_1.default)('Estimating cost...').start() : null;
|
|
1322
|
+
const response = await api.get('/credits/estimate', {
|
|
1323
|
+
params: { candidate_count: parseInt(options.candidates) }
|
|
1324
|
+
});
|
|
1325
|
+
if (spinner)
|
|
1326
|
+
spinner.stop();
|
|
1327
|
+
if (isJsonOutput) {
|
|
1328
|
+
outputJson(response.data);
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
console.log(chalk_1.default.blue('💰 Cost Estimate\n'));
|
|
1332
|
+
console.log(` Candidates: ${response.data.candidate_count}`);
|
|
1333
|
+
console.log(` Cost per candidate: $${response.data.cost_per_candidate}`);
|
|
1334
|
+
console.log(chalk_1.default.bold(` Total: $${response.data.total_cost}`));
|
|
1335
|
+
}
|
|
1336
|
+
catch (error) {
|
|
1337
|
+
handleError(error, 'Failed to estimate cost');
|
|
1338
|
+
}
|
|
1339
|
+
});
|
|
1340
|
+
// ============================================================================
|
|
1341
|
+
// Global Options
|
|
1342
|
+
// ============================================================================
|
|
1343
|
+
program
|
|
1344
|
+
.option('--json', 'Output as JSON')
|
|
1345
|
+
.option('-v, --verbose', 'Enable verbose logging')
|
|
1346
|
+
.version('1.2.1');
|
|
1347
|
+
// ============================================================================
|
|
1348
|
+
// Parse & Execute
|
|
1349
|
+
// ============================================================================
|
|
1350
|
+
// Check for JSON flag in argv (before command runs)
|
|
1351
|
+
if (process.argv.includes('--json')) {
|
|
1352
|
+
isJsonOutput = true;
|
|
1353
|
+
}
|
|
1354
|
+
// Show help if no command provided
|
|
1355
|
+
const commandArgs = process.argv.slice(2).filter(arg => !arg.startsWith('-'));
|
|
1356
|
+
if (commandArgs.length === 0) {
|
|
1357
|
+
if (isJsonOutput) {
|
|
1358
|
+
outputJson({
|
|
1359
|
+
success: false,
|
|
1360
|
+
error: "No command provided",
|
|
1361
|
+
commands: program.commands.map(cmd => cmd.name())
|
|
1362
|
+
});
|
|
1363
|
+
process.exit(1);
|
|
1364
|
+
}
|
|
1365
|
+
else {
|
|
1366
|
+
program.help();
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
program.parse(process.argv);
|
|
1370
|
+
//# sourceMappingURL=index.js.map
|