@toothfairyai/cli 1.2.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/toothfairy.js +4648 -72
- package/package.json +7 -5
- package/src/api.js +625 -15
- package/src/config.js +7 -3
package/bin/toothfairy.js
CHANGED
|
@@ -7,6 +7,7 @@ const { table } = require('table');
|
|
|
7
7
|
require('dotenv').config();
|
|
8
8
|
const path = require('path');
|
|
9
9
|
|
|
10
|
+
const fs = require('fs');
|
|
10
11
|
const ToothFairyAPI = require('../src/api');
|
|
11
12
|
const {
|
|
12
13
|
loadConfig,
|
|
@@ -17,6 +18,128 @@ const {
|
|
|
17
18
|
validateDocumentConfiguration,
|
|
18
19
|
} = require('../src/config');
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Determines the best way to handle a local file based on its type and size.
|
|
23
|
+
* Text files under a certain size are read as text content.
|
|
24
|
+
* Binary files or large text files should be uploaded.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} filePath - Path to the local file
|
|
27
|
+
* @returns {Object} - { strategy: 'text'|'upload', content?: string, mimeType: string, size: number }
|
|
28
|
+
*/
|
|
29
|
+
function analyzeLocalFile(filePath) {
|
|
30
|
+
const absolutePath = path.resolve(filePath);
|
|
31
|
+
|
|
32
|
+
if (!fs.existsSync(absolutePath)) {
|
|
33
|
+
throw new Error(`File not found: ${filePath}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const stats = fs.statSync(absolutePath);
|
|
37
|
+
const ext = path.extname(filePath).toLowerCase().slice(1);
|
|
38
|
+
const sizeInMB = stats.size / (1024 * 1024);
|
|
39
|
+
|
|
40
|
+
// Text-based file extensions that can be read as content
|
|
41
|
+
const textExtensions = [
|
|
42
|
+
'txt', 'md', 'markdown', 'json', 'jsonl', 'yaml', 'yml',
|
|
43
|
+
'js', 'ts', 'jsx', 'tsx', 'py', 'java', 'c', 'cpp', 'h', 'hpp',
|
|
44
|
+
'cs', 'rb', 'go', 'rs', 'php', 'swift', 'kt', 'scala',
|
|
45
|
+
'html', 'htm', 'css', 'scss', 'sass', 'less',
|
|
46
|
+
'xml', 'svg', 'sql', 'sh', 'bash', 'zsh', 'ps1',
|
|
47
|
+
'env', 'ini', 'cfg', 'conf', 'toml', 'properties',
|
|
48
|
+
'csv', 'log', 'gitignore', 'dockerignore', 'editorconfig'
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
// Binary file extensions that must be uploaded
|
|
52
|
+
const binaryExtensions = [
|
|
53
|
+
'pdf', 'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx',
|
|
54
|
+
'png', 'jpg', 'jpeg', 'gif', 'bmp', 'webp', 'ico', 'tiff',
|
|
55
|
+
'mp3', 'wav', 'aac', 'ogg', 'flac', 'm4a',
|
|
56
|
+
'mp4', 'avi', 'mov', 'wmv', 'flv', 'webm', 'mkv',
|
|
57
|
+
'zip', 'tar', 'gz', 'rar', '7z',
|
|
58
|
+
'exe', 'dll', 'so', 'dylib',
|
|
59
|
+
'woff', 'woff2', 'ttf', 'otf', 'eot'
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const isTextFile = textExtensions.includes(ext) || ext === '';
|
|
63
|
+
const isBinaryFile = binaryExtensions.includes(ext);
|
|
64
|
+
|
|
65
|
+
// Max size for reading as text (500KB)
|
|
66
|
+
const maxTextSize = 0.5;
|
|
67
|
+
|
|
68
|
+
// Determine strategy
|
|
69
|
+
let strategy;
|
|
70
|
+
let content = null;
|
|
71
|
+
|
|
72
|
+
if (isBinaryFile) {
|
|
73
|
+
strategy = 'upload';
|
|
74
|
+
} else if (isTextFile && sizeInMB <= maxTextSize) {
|
|
75
|
+
strategy = 'text';
|
|
76
|
+
content = fs.readFileSync(absolutePath, 'utf-8');
|
|
77
|
+
} else if (sizeInMB > 15) {
|
|
78
|
+
throw new Error(`File size (${sizeInMB.toFixed(2)}MB) exceeds 15MB limit`);
|
|
79
|
+
} else {
|
|
80
|
+
// Large text file or unknown extension - upload it
|
|
81
|
+
strategy = 'upload';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
strategy,
|
|
86
|
+
content,
|
|
87
|
+
absolutePath,
|
|
88
|
+
fileName: path.basename(filePath),
|
|
89
|
+
extension: ext,
|
|
90
|
+
size: stats.size,
|
|
91
|
+
sizeInMB
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Processes local files and prepares them for sending to an agent.
|
|
97
|
+
* Returns message text with file contents embedded and/or attachments for upload.
|
|
98
|
+
*
|
|
99
|
+
* @param {string[]} localFiles - Array of local file paths
|
|
100
|
+
* @param {string} originalMessage - The original message from the user
|
|
101
|
+
* @param {Object} existingAttachments - Existing attachment object
|
|
102
|
+
* @returns {Object} - { message: string, attachments: Object }
|
|
103
|
+
*/
|
|
104
|
+
function processLocalFiles(localFiles, originalMessage, existingAttachments = {}) {
|
|
105
|
+
const textContents = [];
|
|
106
|
+
const attachments = { ...existingAttachments };
|
|
107
|
+
|
|
108
|
+
for (const filePath of localFiles) {
|
|
109
|
+
const analysis = analyzeLocalFile(filePath);
|
|
110
|
+
|
|
111
|
+
if (analysis.strategy === 'text') {
|
|
112
|
+
// Add file content as text
|
|
113
|
+
textContents.push({
|
|
114
|
+
fileName: analysis.fileName,
|
|
115
|
+
content: analysis.content
|
|
116
|
+
});
|
|
117
|
+
} else {
|
|
118
|
+
// Add to attachments for upload
|
|
119
|
+
if (!attachments.files) {
|
|
120
|
+
attachments.files = [];
|
|
121
|
+
}
|
|
122
|
+
attachments.files.push(analysis.absolutePath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Build enhanced message with text file contents
|
|
127
|
+
let enhancedMessage = originalMessage;
|
|
128
|
+
|
|
129
|
+
if (textContents.length > 0) {
|
|
130
|
+
const fileSection = textContents.map(file =>
|
|
131
|
+
`\n\n--- File: ${file.fileName} ---\n${file.content}\n--- End of ${file.fileName} ---`
|
|
132
|
+
).join('');
|
|
133
|
+
|
|
134
|
+
enhancedMessage = `${originalMessage}${fileSection}`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
message: enhancedMessage,
|
|
139
|
+
attachments
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
20
143
|
const program = new Command();
|
|
21
144
|
|
|
22
145
|
program
|
|
@@ -48,6 +171,7 @@ program
|
|
|
48
171
|
.option('--api-key <key>', 'API key')
|
|
49
172
|
.option('--workspace-id <id>', 'Workspace ID')
|
|
50
173
|
.option('--user-id <id>', 'User ID')
|
|
174
|
+
.option('--default-agent-id <id>', 'Default agent ID for send/chat commands')
|
|
51
175
|
.option('--config-path <path>', 'Path to save config file')
|
|
52
176
|
.action(async (options) => {
|
|
53
177
|
try {
|
|
@@ -57,7 +181,7 @@ program
|
|
|
57
181
|
existingConfig = loadConfig(options.configPath);
|
|
58
182
|
} catch (error) {
|
|
59
183
|
// No existing config, use empty defaults
|
|
60
|
-
existingConfig = new ToothFairyConfig('', '', '', '', '', '');
|
|
184
|
+
existingConfig = new ToothFairyConfig('', '', '', '', '', '', '');
|
|
61
185
|
}
|
|
62
186
|
|
|
63
187
|
// Merge provided options with existing config
|
|
@@ -71,7 +195,8 @@ program
|
|
|
71
195
|
'https://ais.toothfairyai.com',
|
|
72
196
|
options.apiKey || existingConfig.apiKey || '',
|
|
73
197
|
options.workspaceId || existingConfig.workspaceId || '',
|
|
74
|
-
options.userId || existingConfig.userId || ''
|
|
198
|
+
options.userId || existingConfig.userId || '',
|
|
199
|
+
options.defaultAgentId || existingConfig.defaultAgentId || ''
|
|
75
200
|
);
|
|
76
201
|
|
|
77
202
|
// Validate that at least API key and workspace ID are set
|
|
@@ -109,7 +234,7 @@ program
|
|
|
109
234
|
.command('send')
|
|
110
235
|
.description('Send a message to a ToothFairyAI agent')
|
|
111
236
|
.argument('<message>', 'message to send')
|
|
112
|
-
.
|
|
237
|
+
.option('--agent-id <id>', 'Agent ID to send message to (uses default if configured)')
|
|
113
238
|
.option('--chat-id <id>', 'Chat ID to use existing conversation (optional)')
|
|
114
239
|
.option('--phone-number <number>', 'Phone number for SMS channel')
|
|
115
240
|
.option('--customer-id <id>', 'Customer ID')
|
|
@@ -119,6 +244,7 @@ program
|
|
|
119
244
|
.option('--audio <path>', 'Path to audio file (wav, mp3, aac, ogg, flac)')
|
|
120
245
|
.option('--video <path>', 'Path to video file (mp4, avi, mov, wmv, flv, webm)')
|
|
121
246
|
.option('--file <path...>', 'Path to document files (max 5)')
|
|
247
|
+
.option('--local-file <path...>', 'Path to local files to include (text files embedded in message, binary files uploaded)')
|
|
122
248
|
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
123
249
|
.option('-v, --verbose', 'Show detailed response information')
|
|
124
250
|
.action(async (message, options, command) => {
|
|
@@ -127,6 +253,15 @@ program
|
|
|
127
253
|
const config = loadConfig(globalOptions.config);
|
|
128
254
|
validateConfiguration(config);
|
|
129
255
|
|
|
256
|
+
// Use provided agent-id or fall back to default from config
|
|
257
|
+
const agentId = options.agentId || config.defaultAgentId;
|
|
258
|
+
if (!agentId) {
|
|
259
|
+
console.error(chalk.red('Error: --agent-id is required'));
|
|
260
|
+
console.error(chalk.yellow('Either provide --agent-id or set a default with:'));
|
|
261
|
+
console.error(chalk.dim(' tf configure --default-agent-id YOUR_AGENT_ID'));
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
|
|
130
265
|
const api = new ToothFairyAPI(
|
|
131
266
|
config.baseUrl,
|
|
132
267
|
config.aiUrl,
|
|
@@ -148,7 +283,7 @@ program
|
|
|
148
283
|
}
|
|
149
284
|
|
|
150
285
|
// Process file attachments if provided
|
|
151
|
-
|
|
286
|
+
let attachments = {};
|
|
152
287
|
if (options.image) {
|
|
153
288
|
attachments.images = [options.image];
|
|
154
289
|
}
|
|
@@ -162,11 +297,28 @@ program
|
|
|
162
297
|
attachments.files = Array.isArray(options.file) ? options.file : [options.file];
|
|
163
298
|
}
|
|
164
299
|
|
|
300
|
+
// Process local files - text files are embedded in message, binary files are uploaded
|
|
301
|
+
let finalMessage = message;
|
|
302
|
+
if (options.localFile) {
|
|
303
|
+
const localFiles = Array.isArray(options.localFile) ? options.localFile : [options.localFile];
|
|
304
|
+
const processed = processLocalFiles(localFiles, message, attachments);
|
|
305
|
+
finalMessage = processed.message;
|
|
306
|
+
attachments = processed.attachments;
|
|
307
|
+
|
|
308
|
+
if (globalOptions.verbose || options.verbose) {
|
|
309
|
+
console.log(chalk.dim(`Processing ${localFiles.length} local file(s)...`));
|
|
310
|
+
for (const filePath of localFiles) {
|
|
311
|
+
const analysis = analyzeLocalFile(filePath);
|
|
312
|
+
console.log(chalk.dim(` - ${analysis.fileName}: ${analysis.strategy} (${analysis.sizeInMB.toFixed(2)}MB)`));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
165
317
|
const spinner = ora('Sending message to agent...').start();
|
|
166
318
|
|
|
167
319
|
const response = await api.sendMessageToAgent(
|
|
168
|
-
|
|
169
|
-
|
|
320
|
+
finalMessage,
|
|
321
|
+
agentId,
|
|
170
322
|
options.phoneNumber,
|
|
171
323
|
options.customerId,
|
|
172
324
|
options.providerId,
|
|
@@ -238,7 +390,7 @@ program
|
|
|
238
390
|
'Send a message to a ToothFairyAI agent with real-time streaming response'
|
|
239
391
|
)
|
|
240
392
|
.argument('<message>', 'message to send')
|
|
241
|
-
.
|
|
393
|
+
.option('--agent-id <id>', 'Agent ID to send message to (uses default if configured)')
|
|
242
394
|
.option('--chat-id <id>', 'Chat ID to use existing conversation (optional)')
|
|
243
395
|
.option('--phone-number <number>', 'Phone number for SMS channel')
|
|
244
396
|
.option('--customer-id <id>', 'Customer ID')
|
|
@@ -248,6 +400,7 @@ program
|
|
|
248
400
|
.option('--audio <path>', 'Path to audio file (WAV, max 1)')
|
|
249
401
|
.option('--video <path>', 'Path to video file (MP4, max 1)')
|
|
250
402
|
.option('--file <path>', 'Path to file (max 5, can be used multiple times)', (value, previous) => previous.concat([value]), [])
|
|
403
|
+
.option('--local-file <path...>', 'Path to local files to include (text files embedded in message, binary files uploaded)')
|
|
251
404
|
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
252
405
|
.option('-v, --verbose', 'Show detailed streaming information')
|
|
253
406
|
.option('--show-progress', 'Show agent processing status updates')
|
|
@@ -277,6 +430,15 @@ program
|
|
|
277
430
|
const config = loadConfig(globalOptions.config);
|
|
278
431
|
validateConfiguration(config);
|
|
279
432
|
|
|
433
|
+
// Use provided agent-id or fall back to default from config
|
|
434
|
+
const agentId = options.agentId || config.defaultAgentId;
|
|
435
|
+
if (!agentId) {
|
|
436
|
+
console.error(chalk.red('Error: --agent-id is required'));
|
|
437
|
+
console.error(chalk.yellow('Either provide --agent-id or set a default with:'));
|
|
438
|
+
console.error(chalk.dim(' tf configure --default-agent-id YOUR_AGENT_ID'));
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
|
|
280
442
|
const api = new ToothFairyAPI(
|
|
281
443
|
config.baseUrl,
|
|
282
444
|
config.aiUrl,
|
|
@@ -298,7 +460,7 @@ program
|
|
|
298
460
|
}
|
|
299
461
|
|
|
300
462
|
// Process file attachments if provided
|
|
301
|
-
|
|
463
|
+
let attachments = {};
|
|
302
464
|
if (options.image) {
|
|
303
465
|
attachments.images = [options.image];
|
|
304
466
|
}
|
|
@@ -312,18 +474,34 @@ program
|
|
|
312
474
|
attachments.files = Array.isArray(options.file) ? options.file : [options.file];
|
|
313
475
|
}
|
|
314
476
|
|
|
477
|
+
// Process local files - text files are embedded in message, binary files are uploaded
|
|
478
|
+
let finalMessage = message;
|
|
479
|
+
if (options.localFile) {
|
|
480
|
+
const localFiles = Array.isArray(options.localFile) ? options.localFile : [options.localFile];
|
|
481
|
+
const processed = processLocalFiles(localFiles, message, attachments);
|
|
482
|
+
finalMessage = processed.message;
|
|
483
|
+
attachments = processed.attachments;
|
|
484
|
+
|
|
485
|
+
if (globalOptions.verbose || options.verbose) {
|
|
486
|
+
console.log(chalk.dim(`Processing ${localFiles.length} local file(s)...`));
|
|
487
|
+
for (const filePath of localFiles) {
|
|
488
|
+
const analysis = analyzeLocalFile(filePath);
|
|
489
|
+
console.log(chalk.dim(` - ${analysis.fileName}: ${analysis.strategy} (${analysis.sizeInMB.toFixed(2)}MB)`));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
315
494
|
console.log(
|
|
316
|
-
chalk.cyan(`Streaming message to agent ${
|
|
495
|
+
chalk.cyan(`Streaming message to agent ${agentId}...`)
|
|
317
496
|
);
|
|
318
|
-
console.log(chalk.dim(`Message: ${message}`));
|
|
497
|
+
console.log(chalk.dim(`Message: ${options.localFile ? '(with local files) ' : ''}${message}`));
|
|
319
498
|
console.log();
|
|
320
499
|
|
|
321
500
|
// Initialize variables for streaming
|
|
322
|
-
let currentText = '';
|
|
323
501
|
let finalResponse = null;
|
|
324
502
|
let processingStatus = null;
|
|
325
|
-
let lastUpdateTime = 0;
|
|
326
503
|
let hasStartedResponse = false;
|
|
504
|
+
let hasCompletedResponse = false;
|
|
327
505
|
|
|
328
506
|
const mapStateWithLabel = (state) => {
|
|
329
507
|
switch (state) {
|
|
@@ -377,8 +555,8 @@ program
|
|
|
377
555
|
const allEvents = [];
|
|
378
556
|
|
|
379
557
|
await api.sendMessageToAgentStream(
|
|
380
|
-
|
|
381
|
-
|
|
558
|
+
finalMessage,
|
|
559
|
+
agentId,
|
|
382
560
|
options.phoneNumber,
|
|
383
561
|
options.customerId,
|
|
384
562
|
options.providerId,
|
|
@@ -396,7 +574,8 @@ program
|
|
|
396
574
|
},
|
|
397
575
|
attachments,
|
|
398
576
|
options.showProgress,
|
|
399
|
-
options.chatId
|
|
577
|
+
options.chatId,
|
|
578
|
+
true // rawStream - enable chunk-by-chunk streaming
|
|
400
579
|
);
|
|
401
580
|
|
|
402
581
|
console.log(JSON.stringify(allEvents, null, 2));
|
|
@@ -404,42 +583,10 @@ program
|
|
|
404
583
|
// Text mode: show live streaming
|
|
405
584
|
let currentSpinner = null;
|
|
406
585
|
|
|
407
|
-
// Function to update display with status filtering
|
|
408
|
-
const updateDisplay = (text, agentStatus, type = 'response') => {
|
|
409
|
-
if (currentSpinner) {
|
|
410
|
-
currentSpinner.stop();
|
|
411
|
-
currentSpinner = null;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// Only show text when agent is actually replying
|
|
415
|
-
if (text && type === 'response' && agentStatus === 'replying') {
|
|
416
|
-
const trimmedText = text.trim();
|
|
417
|
-
const now = Date.now();
|
|
418
|
-
|
|
419
|
-
// Only update if the text is actually different and enough time has passed
|
|
420
|
-
// This prevents rapid duplicate updates
|
|
421
|
-
if (
|
|
422
|
-
trimmedText !== currentText &&
|
|
423
|
-
trimmedText?.length > currentText?.length &&
|
|
424
|
-
now - lastUpdateTime > 50
|
|
425
|
-
) {
|
|
426
|
-
if (!hasStartedResponse) {
|
|
427
|
-
// First time showing response - print header on new line
|
|
428
|
-
console.log(chalk.green('🧚 Responding'));
|
|
429
|
-
hasStartedResponse = true;
|
|
430
|
-
}
|
|
431
|
-
// Stream new text content on new line
|
|
432
|
-
console.log(trimmedText);
|
|
433
|
-
currentText = trimmedText;
|
|
434
|
-
lastUpdateTime = now;
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
};
|
|
438
|
-
|
|
439
586
|
try {
|
|
440
587
|
await api.sendMessageToAgentStream(
|
|
441
|
-
|
|
442
|
-
|
|
588
|
+
finalMessage,
|
|
589
|
+
agentId,
|
|
443
590
|
options.phoneNumber,
|
|
444
591
|
options.customerId,
|
|
445
592
|
options.providerId,
|
|
@@ -480,19 +627,71 @@ program
|
|
|
480
627
|
return;
|
|
481
628
|
}
|
|
482
629
|
|
|
483
|
-
// Handle
|
|
630
|
+
// Handle streaming data events (tokens from raw_stream mode)
|
|
631
|
+
if (eventType === 'data' && eventData.text) {
|
|
632
|
+
// Stop spinner when text starts streaming
|
|
633
|
+
if (currentSpinner) {
|
|
634
|
+
currentSpinner.stop();
|
|
635
|
+
currentSpinner = null;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
if (!hasStartedResponse) {
|
|
639
|
+
console.log(chalk.green('🧚 Responding'));
|
|
640
|
+
hasStartedResponse = true;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// Stream chunk directly to output
|
|
644
|
+
process.stdout.write(eventData.text);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// Handle progress events (from raw_stream mode)
|
|
648
|
+
if (eventType === 'progress') {
|
|
649
|
+
const status = eventData.processing_status || eventData.metadata_parsed?.agent_processing_status;
|
|
650
|
+
if (status && status !== processingStatus) {
|
|
651
|
+
processingStatus = status;
|
|
652
|
+
if (options.showProgress) {
|
|
653
|
+
const statusMsg = mapStateWithLabel(processingStatus);
|
|
654
|
+
if (currentSpinner) {
|
|
655
|
+
currentSpinner.stop();
|
|
656
|
+
}
|
|
657
|
+
currentSpinner = ora(chalk.yellow(statusMsg)).start();
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Handle chat_created events
|
|
663
|
+
if (eventType === 'chat_created' && eventData.chatId) {
|
|
664
|
+
if (options.verbose) {
|
|
665
|
+
console.log(chalk.dim(`\n📝 Chat created: ${eventData.chatId}`));
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Handle complete events
|
|
670
|
+
if (eventType === 'complete') {
|
|
671
|
+
finalResponse = eventData;
|
|
672
|
+
if (currentSpinner) {
|
|
673
|
+
currentSpinner.stop();
|
|
674
|
+
currentSpinner = null;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Handle message events (legacy format / additional metadata)
|
|
484
679
|
if (eventData.type === 'message') {
|
|
485
680
|
let metadata = {};
|
|
486
681
|
let agentStatus = null;
|
|
487
682
|
|
|
488
683
|
// Parse metadata if available
|
|
489
684
|
if (eventData.metadata) {
|
|
490
|
-
|
|
491
|
-
metadata =
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
685
|
+
if (typeof eventData.metadata === 'object') {
|
|
686
|
+
metadata = eventData.metadata;
|
|
687
|
+
} else {
|
|
688
|
+
try {
|
|
689
|
+
metadata = JSON.parse(eventData.metadata);
|
|
690
|
+
} catch (e) {
|
|
691
|
+
// Metadata parsing failed, continue without it
|
|
692
|
+
}
|
|
495
693
|
}
|
|
694
|
+
agentStatus = metadata.agent_processing_status;
|
|
496
695
|
}
|
|
497
696
|
|
|
498
697
|
// Handle status changes
|
|
@@ -507,22 +706,15 @@ program
|
|
|
507
706
|
}
|
|
508
707
|
}
|
|
509
708
|
|
|
510
|
-
// Handle progressive text streaming
|
|
511
|
-
if (eventData.text && agentStatus === 'replying') {
|
|
512
|
-
updateDisplay(eventData.text, agentStatus, 'response');
|
|
513
|
-
}
|
|
514
|
-
|
|
515
709
|
// Handle fulfilled status (final response)
|
|
516
|
-
if (eventData.status === 'fulfilled') {
|
|
710
|
+
if (eventData.status === 'fulfilled' && !hasCompletedResponse) {
|
|
517
711
|
finalResponse = eventData;
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
currentSpinner = null;
|
|
523
|
-
}
|
|
524
|
-
console.log(chalk.blue('🪄 Response complete'));
|
|
712
|
+
hasCompletedResponse = true;
|
|
713
|
+
if (currentSpinner) {
|
|
714
|
+
currentSpinner.stop();
|
|
715
|
+
currentSpinner = null;
|
|
525
716
|
}
|
|
717
|
+
console.log(chalk.blue('\n🪄 Response complete'));
|
|
526
718
|
}
|
|
527
719
|
|
|
528
720
|
// Handle additional metadata events (images, files, callback metadata)
|
|
@@ -530,14 +722,12 @@ program
|
|
|
530
722
|
eventData.images !== undefined ||
|
|
531
723
|
eventData.files !== undefined
|
|
532
724
|
) {
|
|
533
|
-
// These are attachment events - could show notification if needed
|
|
534
725
|
if (options.verbose) {
|
|
535
726
|
console.log(chalk.dim('\n📎 Attachments processed'));
|
|
536
727
|
}
|
|
537
728
|
}
|
|
538
729
|
|
|
539
730
|
if (eventData.callbackMetadata) {
|
|
540
|
-
// Function execution metadata
|
|
541
731
|
if (options.verbose) {
|
|
542
732
|
console.log(
|
|
543
733
|
chalk.dim('\n⚙️ Function execution metadata received')
|
|
@@ -558,7 +748,8 @@ program
|
|
|
558
748
|
},
|
|
559
749
|
attachments,
|
|
560
750
|
options.showProgress,
|
|
561
|
-
options.chatId
|
|
751
|
+
options.chatId,
|
|
752
|
+
true // rawStream - enable chunk-by-chunk streaming
|
|
562
753
|
);
|
|
563
754
|
|
|
564
755
|
// Clean up spinner after streaming completes
|
|
@@ -610,7 +801,7 @@ program
|
|
|
610
801
|
console.log('─'.repeat(50));
|
|
611
802
|
}
|
|
612
803
|
|
|
613
|
-
if (!
|
|
804
|
+
if (!hasStartedResponse) {
|
|
614
805
|
console.log(chalk.yellow('No text response received from agent.'));
|
|
615
806
|
}
|
|
616
807
|
} catch (streamError) {
|
|
@@ -626,6 +817,378 @@ program
|
|
|
626
817
|
}
|
|
627
818
|
});
|
|
628
819
|
|
|
820
|
+
// Interactive Chat command
|
|
821
|
+
program
|
|
822
|
+
.command('chat')
|
|
823
|
+
.description('Start an interactive chat session with a ToothFairyAI agent (use @ to reference local files)')
|
|
824
|
+
.option('--agent-id <id>', 'Agent ID to chat with (uses default if configured)')
|
|
825
|
+
.option('--chat-id <id>', 'Resume an existing chat conversation')
|
|
826
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
827
|
+
.option('--show-progress', 'Show agent processing status updates')
|
|
828
|
+
.action(async (options, command) => {
|
|
829
|
+
const readline = require('readline');
|
|
830
|
+
const glob = require('glob');
|
|
831
|
+
|
|
832
|
+
try {
|
|
833
|
+
const globalOptions = command.parent.opts();
|
|
834
|
+
const config = loadConfig(globalOptions.config);
|
|
835
|
+
validateConfiguration(config);
|
|
836
|
+
|
|
837
|
+
// Use provided agent-id or fall back to default from config
|
|
838
|
+
const agentId = options.agentId || config.defaultAgentId;
|
|
839
|
+
if (!agentId) {
|
|
840
|
+
console.error(chalk.red('Error: --agent-id is required'));
|
|
841
|
+
console.error(chalk.yellow('Either provide --agent-id or set a default with:'));
|
|
842
|
+
console.error(chalk.dim(' tf configure --default-agent-id YOUR_AGENT_ID'));
|
|
843
|
+
process.exit(1);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
const api = new ToothFairyAPI(
|
|
847
|
+
config.baseUrl,
|
|
848
|
+
config.aiUrl,
|
|
849
|
+
config.aiStreamUrl,
|
|
850
|
+
config.apiKey,
|
|
851
|
+
config.workspaceId,
|
|
852
|
+
globalOptions.verbose || options.verbose
|
|
853
|
+
);
|
|
854
|
+
|
|
855
|
+
// Track current chat ID for conversation continuity
|
|
856
|
+
let currentChatId = options.chatId || null;
|
|
857
|
+
const workingDir = process.cwd();
|
|
858
|
+
|
|
859
|
+
// Helper to get files matching a pattern
|
|
860
|
+
const getMatchingFiles = (pattern) => {
|
|
861
|
+
try {
|
|
862
|
+
// Remove @ prefix if present
|
|
863
|
+
const cleanPattern = pattern.startsWith('@') ? pattern.slice(1) : pattern;
|
|
864
|
+
|
|
865
|
+
// If it's a directory, list its contents
|
|
866
|
+
if (fs.existsSync(cleanPattern) && fs.statSync(cleanPattern).isDirectory()) {
|
|
867
|
+
return fs.readdirSync(cleanPattern).map(f => path.join(cleanPattern, f));
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// Try glob pattern
|
|
871
|
+
const matches = glob.sync(cleanPattern, {
|
|
872
|
+
cwd: workingDir,
|
|
873
|
+
nodir: false,
|
|
874
|
+
dot: false
|
|
875
|
+
});
|
|
876
|
+
return matches;
|
|
877
|
+
} catch (e) {
|
|
878
|
+
return [];
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
|
|
882
|
+
// Helper to list files for autocomplete
|
|
883
|
+
const listFilesForCompletion = (partial) => {
|
|
884
|
+
const cleanPartial = partial.startsWith('@') ? partial.slice(1) : partial;
|
|
885
|
+
const dir = path.dirname(cleanPartial) || '.';
|
|
886
|
+
const prefix = path.basename(cleanPartial);
|
|
887
|
+
|
|
888
|
+
try {
|
|
889
|
+
let searchDir = dir === '.' ? workingDir : path.resolve(workingDir, dir);
|
|
890
|
+
if (!fs.existsSync(searchDir)) {
|
|
891
|
+
searchDir = workingDir;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
const files = fs.readdirSync(searchDir);
|
|
895
|
+
const matches = files
|
|
896
|
+
.filter(f => f.startsWith(prefix) || prefix === '')
|
|
897
|
+
.map(f => {
|
|
898
|
+
const fullPath = path.join(dir === '.' ? '' : dir, f);
|
|
899
|
+
const isDir = fs.statSync(path.join(searchDir, f)).isDirectory();
|
|
900
|
+
return isDir ? fullPath + '/' : fullPath;
|
|
901
|
+
});
|
|
902
|
+
return matches;
|
|
903
|
+
} catch (e) {
|
|
904
|
+
return [];
|
|
905
|
+
}
|
|
906
|
+
};
|
|
907
|
+
|
|
908
|
+
// Parse @ references from message
|
|
909
|
+
const parseFileReferences = (message) => {
|
|
910
|
+
const fileRefs = [];
|
|
911
|
+
// Match @path/to/file or @"path with spaces/file" or @./relative/path
|
|
912
|
+
const regex = /@(?:"([^"]+)"|([^\s@]+))/g;
|
|
913
|
+
let match;
|
|
914
|
+
|
|
915
|
+
while ((match = regex.exec(message)) !== null) {
|
|
916
|
+
const filePath = match[1] || match[2];
|
|
917
|
+
fileRefs.push(filePath);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return fileRefs;
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
// Remove @ references from message for cleaner prompt
|
|
924
|
+
const cleanMessage = (message) => {
|
|
925
|
+
return message.replace(/@(?:"[^"]+"|[^\s@]+)/g, '').trim();
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
// Print welcome banner
|
|
929
|
+
console.log(chalk.cyan.bold('\n🧚 ToothFairy Interactive Chat'));
|
|
930
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
931
|
+
console.log(chalk.white(`Agent: ${agentId}`));
|
|
932
|
+
console.log(chalk.white(`Working directory: ${workingDir}`));
|
|
933
|
+
if (currentChatId) {
|
|
934
|
+
console.log(chalk.white(`Resuming chat: ${currentChatId}`));
|
|
935
|
+
}
|
|
936
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
937
|
+
console.log(chalk.yellow('\nTips:'));
|
|
938
|
+
console.log(chalk.dim(' • Use @filename to include a file in your message'));
|
|
939
|
+
console.log(chalk.dim(' • Use @path/to/file or @./relative/path for paths'));
|
|
940
|
+
console.log(chalk.dim(' • Use @"path with spaces/file.txt" for paths with spaces'));
|
|
941
|
+
console.log(chalk.dim(' • Use @*.js or @src/**/*.ts for glob patterns'));
|
|
942
|
+
console.log(chalk.dim(' • Type /files to list files in current directory'));
|
|
943
|
+
console.log(chalk.dim(' • Type /cd <path> to change directory'));
|
|
944
|
+
console.log(chalk.dim(' • Type /chat to see current chat ID'));
|
|
945
|
+
console.log(chalk.dim(' • Type /clear to start a new chat'));
|
|
946
|
+
console.log(chalk.dim(' • Type /exit or Ctrl+C to quit'));
|
|
947
|
+
console.log(chalk.dim('─'.repeat(50)));
|
|
948
|
+
console.log();
|
|
949
|
+
|
|
950
|
+
// Create readline interface with custom completer
|
|
951
|
+
const rl = readline.createInterface({
|
|
952
|
+
input: process.stdin,
|
|
953
|
+
output: process.stdout,
|
|
954
|
+
completer: (line) => {
|
|
955
|
+
// Check if we're completing a file reference
|
|
956
|
+
const atMatch = line.match(/@([^\s]*)$/);
|
|
957
|
+
if (atMatch) {
|
|
958
|
+
const partial = atMatch[1];
|
|
959
|
+
const completions = listFilesForCompletion(partial);
|
|
960
|
+
const hits = completions.map(c => '@' + c);
|
|
961
|
+
return [hits.length ? hits : [], line];
|
|
962
|
+
}
|
|
963
|
+
return [[], line];
|
|
964
|
+
},
|
|
965
|
+
prompt: chalk.green('You: ')
|
|
966
|
+
});
|
|
967
|
+
|
|
968
|
+
rl.prompt();
|
|
969
|
+
|
|
970
|
+
rl.on('line', async (input) => {
|
|
971
|
+
const trimmedInput = input.trim();
|
|
972
|
+
|
|
973
|
+
if (!trimmedInput) {
|
|
974
|
+
rl.prompt();
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Handle special commands
|
|
979
|
+
if (trimmedInput.startsWith('/')) {
|
|
980
|
+
const [cmd, ...args] = trimmedInput.slice(1).split(' ');
|
|
981
|
+
|
|
982
|
+
switch (cmd.toLowerCase()) {
|
|
983
|
+
case 'exit':
|
|
984
|
+
case 'quit':
|
|
985
|
+
case 'q':
|
|
986
|
+
console.log(chalk.cyan('\nGoodbye! 👋'));
|
|
987
|
+
rl.close();
|
|
988
|
+
process.exit(0);
|
|
989
|
+
break;
|
|
990
|
+
|
|
991
|
+
case 'files':
|
|
992
|
+
case 'ls': {
|
|
993
|
+
const targetDir = args[0] || '.';
|
|
994
|
+
try {
|
|
995
|
+
const files = fs.readdirSync(path.resolve(workingDir, targetDir));
|
|
996
|
+
console.log(chalk.cyan(`\nFiles in ${targetDir}:`));
|
|
997
|
+
files.forEach(f => {
|
|
998
|
+
const fullPath = path.join(workingDir, targetDir, f);
|
|
999
|
+
const isDir = fs.statSync(fullPath).isDirectory();
|
|
1000
|
+
console.log(chalk.dim(` ${isDir ? '📁' : '📄'} ${f}${isDir ? '/' : ''}`));
|
|
1001
|
+
});
|
|
1002
|
+
console.log();
|
|
1003
|
+
} catch (e) {
|
|
1004
|
+
console.log(chalk.red(`Cannot list directory: ${e.message}`));
|
|
1005
|
+
}
|
|
1006
|
+
break;
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
case 'cd': {
|
|
1010
|
+
if (args[0]) {
|
|
1011
|
+
const newDir = path.resolve(workingDir, args[0]);
|
|
1012
|
+
if (fs.existsSync(newDir) && fs.statSync(newDir).isDirectory()) {
|
|
1013
|
+
process.chdir(newDir);
|
|
1014
|
+
console.log(chalk.cyan(`\nChanged directory to: ${newDir}\n`));
|
|
1015
|
+
} else {
|
|
1016
|
+
console.log(chalk.red(`Directory not found: ${args[0]}`));
|
|
1017
|
+
}
|
|
1018
|
+
} else {
|
|
1019
|
+
console.log(chalk.yellow('Usage: /cd <path>'));
|
|
1020
|
+
}
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
case 'chat':
|
|
1025
|
+
console.log(chalk.cyan(`\nCurrent chat ID: ${currentChatId || '(new chat)'}\n`));
|
|
1026
|
+
break;
|
|
1027
|
+
|
|
1028
|
+
case 'clear':
|
|
1029
|
+
case 'new':
|
|
1030
|
+
currentChatId = null;
|
|
1031
|
+
console.log(chalk.cyan('\nStarting new chat session.\n'));
|
|
1032
|
+
break;
|
|
1033
|
+
|
|
1034
|
+
case 'help':
|
|
1035
|
+
console.log(chalk.yellow('\nCommands:'));
|
|
1036
|
+
console.log(chalk.dim(' /files [path] - List files in directory'));
|
|
1037
|
+
console.log(chalk.dim(' /cd <path> - Change directory'));
|
|
1038
|
+
console.log(chalk.dim(' /chat - Show current chat ID'));
|
|
1039
|
+
console.log(chalk.dim(' /clear - Start new chat'));
|
|
1040
|
+
console.log(chalk.dim(' /exit - Quit'));
|
|
1041
|
+
console.log();
|
|
1042
|
+
break;
|
|
1043
|
+
|
|
1044
|
+
default:
|
|
1045
|
+
console.log(chalk.yellow(`Unknown command: /${cmd}. Type /help for available commands.`));
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
rl.prompt();
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// Parse file references from the message
|
|
1053
|
+
const fileRefs = parseFileReferences(trimmedInput);
|
|
1054
|
+
let messageText = trimmedInput;
|
|
1055
|
+
let attachments = {};
|
|
1056
|
+
|
|
1057
|
+
// Process file references
|
|
1058
|
+
if (fileRefs.length > 0) {
|
|
1059
|
+
const allFiles = [];
|
|
1060
|
+
|
|
1061
|
+
for (const ref of fileRefs) {
|
|
1062
|
+
const matches = getMatchingFiles(ref);
|
|
1063
|
+
if (matches.length === 0) {
|
|
1064
|
+
// Check if it's a direct file path
|
|
1065
|
+
const directPath = path.resolve(workingDir, ref);
|
|
1066
|
+
if (fs.existsSync(directPath)) {
|
|
1067
|
+
allFiles.push(directPath);
|
|
1068
|
+
} else {
|
|
1069
|
+
console.log(chalk.yellow(`Warning: No files found matching @${ref}`));
|
|
1070
|
+
}
|
|
1071
|
+
} else {
|
|
1072
|
+
allFiles.push(...matches.map(m => path.resolve(workingDir, m)));
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
if (allFiles.length > 0) {
|
|
1077
|
+
// Filter out directories
|
|
1078
|
+
const validFiles = allFiles.filter(f => {
|
|
1079
|
+
try {
|
|
1080
|
+
return fs.existsSync(f) && fs.statSync(f).isFile();
|
|
1081
|
+
} catch {
|
|
1082
|
+
return false;
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
if (validFiles.length > 0) {
|
|
1087
|
+
console.log(chalk.dim(`\nIncluding ${validFiles.length} file(s):`));
|
|
1088
|
+
validFiles.forEach(f => console.log(chalk.dim(` 📄 ${path.relative(workingDir, f)}`)));
|
|
1089
|
+
console.log();
|
|
1090
|
+
|
|
1091
|
+
// Process the files
|
|
1092
|
+
const cleanedMessage = cleanMessage(messageText);
|
|
1093
|
+
const processed = processLocalFiles(validFiles, cleanedMessage || 'Please review these files:', attachments);
|
|
1094
|
+
messageText = processed.message;
|
|
1095
|
+
attachments = processed.attachments;
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// Send message to agent with streaming
|
|
1101
|
+
console.log(chalk.blue('\n🧚 Agent: '));
|
|
1102
|
+
|
|
1103
|
+
let processingStatus = null;
|
|
1104
|
+
let currentSpinner = null;
|
|
1105
|
+
|
|
1106
|
+
try {
|
|
1107
|
+
await api.sendMessageToAgentStream(
|
|
1108
|
+
messageText,
|
|
1109
|
+
agentId,
|
|
1110
|
+
null, // phoneNumber
|
|
1111
|
+
null, // customerId
|
|
1112
|
+
null, // providerId
|
|
1113
|
+
{}, // customerInfo
|
|
1114
|
+
(eventType, eventData) => {
|
|
1115
|
+
// Handle chat creation event
|
|
1116
|
+
if (eventData.type === 'chat_created' || eventData.event === 'chat_created') {
|
|
1117
|
+
if (eventData.chatId) {
|
|
1118
|
+
currentChatId = eventData.chatId;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
// Update chat ID from response
|
|
1123
|
+
if (eventData.chatId && !currentChatId) {
|
|
1124
|
+
currentChatId = eventData.chatId;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// Handle status events
|
|
1128
|
+
if (eventData.status === 'connected' && options.showProgress) {
|
|
1129
|
+
console.log(chalk.dim('Connected...'));
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Handle progress events
|
|
1133
|
+
if (eventType === 'progress' && options.showProgress) {
|
|
1134
|
+
const status = eventData.processing_status || eventData.status;
|
|
1135
|
+
if (status && status !== processingStatus) {
|
|
1136
|
+
processingStatus = status;
|
|
1137
|
+
if (currentSpinner) currentSpinner.stop();
|
|
1138
|
+
currentSpinner = ora(chalk.dim(status)).start();
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// Handle streaming text (raw_stream tokens come as 'data' events with chunk in text)
|
|
1143
|
+
if (eventType === 'data' && eventData.text) {
|
|
1144
|
+
if (currentSpinner) {
|
|
1145
|
+
currentSpinner.stop();
|
|
1146
|
+
currentSpinner = null;
|
|
1147
|
+
}
|
|
1148
|
+
// Print chunk directly (raw_stream sends individual chunks, not cumulative)
|
|
1149
|
+
process.stdout.write(eventData.text);
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
// Handle completion
|
|
1153
|
+
if (eventData.status === 'fulfilled' || eventType === 'complete') {
|
|
1154
|
+
if (currentSpinner) {
|
|
1155
|
+
currentSpinner.stop();
|
|
1156
|
+
currentSpinner = null;
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
// Handle errors
|
|
1161
|
+
if (eventType === 'error') {
|
|
1162
|
+
if (currentSpinner) currentSpinner.stop();
|
|
1163
|
+
console.log(chalk.red(`\nError: ${eventData.message || 'Unknown error'}`));
|
|
1164
|
+
}
|
|
1165
|
+
},
|
|
1166
|
+
attachments,
|
|
1167
|
+
options.showProgress,
|
|
1168
|
+
currentChatId,
|
|
1169
|
+
true // rawStream - enable chunk-by-chunk streaming
|
|
1170
|
+
);
|
|
1171
|
+
|
|
1172
|
+
console.log('\n');
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
if (currentSpinner) currentSpinner.stop();
|
|
1175
|
+
console.log(chalk.red(`\nError: ${error.message}\n`));
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
rl.prompt();
|
|
1179
|
+
});
|
|
1180
|
+
|
|
1181
|
+
rl.on('close', () => {
|
|
1182
|
+
console.log(chalk.cyan('\nGoodbye! 👋\n'));
|
|
1183
|
+
process.exit(0);
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
console.error(chalk.red(`Error starting chat: ${error.message}`));
|
|
1188
|
+
process.exit(1);
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1191
|
+
|
|
629
1192
|
// Search command
|
|
630
1193
|
program
|
|
631
1194
|
.command('search')
|
|
@@ -963,6 +1526,7 @@ program
|
|
|
963
1526
|
],
|
|
964
1527
|
['Workspace ID', config.workspaceId],
|
|
965
1528
|
['User ID', config.userId ? config.userId : 'Not set'],
|
|
1529
|
+
['Default Agent ID', config.defaultAgentId ? config.defaultAgentId : 'Not set'],
|
|
966
1530
|
];
|
|
967
1531
|
|
|
968
1532
|
console.log(
|
|
@@ -3387,5 +3951,4017 @@ program
|
|
|
3387
3951
|
}
|
|
3388
3952
|
});
|
|
3389
3953
|
|
|
3954
|
+
// Agent commands
|
|
3955
|
+
program
|
|
3956
|
+
.command('create-agent')
|
|
3957
|
+
.description('Create a new agent')
|
|
3958
|
+
.option('--label <label>', 'Agent label')
|
|
3959
|
+
.option('--description <description>', 'Agent description')
|
|
3960
|
+
.option('--emoji <emoji>', 'Agent emoji')
|
|
3961
|
+
.option('--mode <mode>', 'Agent mode (retriever|coder|chatter|planner|computer|voice|accuracy)')
|
|
3962
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
3963
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
3964
|
+
.action(async (options, command) => {
|
|
3965
|
+
try {
|
|
3966
|
+
const globalOptions = command.parent.opts();
|
|
3967
|
+
const config = loadConfig(globalOptions.config);
|
|
3968
|
+
validateConfiguration(config);
|
|
3969
|
+
|
|
3970
|
+
const api = new ToothFairyAPI(
|
|
3971
|
+
config.baseUrl,
|
|
3972
|
+
config.aiUrl,
|
|
3973
|
+
config.aiStreamUrl,
|
|
3974
|
+
config.apiKey,
|
|
3975
|
+
config.workspaceId,
|
|
3976
|
+
globalOptions.verbose || options.verbose
|
|
3977
|
+
);
|
|
3978
|
+
|
|
3979
|
+
const spinner = ora('Creating agent...').start();
|
|
3980
|
+
const result = await api._makeRequest('POST', 'agent/create', {
|
|
3981
|
+
label: options.label,
|
|
3982
|
+
description: options.description,
|
|
3983
|
+
emoji: options.emoji,
|
|
3984
|
+
mode: options.mode,
|
|
3985
|
+
});
|
|
3986
|
+
spinner.stop();
|
|
3987
|
+
|
|
3988
|
+
if (options.output === 'json') {
|
|
3989
|
+
console.log(JSON.stringify(result, null, 2));
|
|
3990
|
+
} else {
|
|
3991
|
+
console.log(chalk.green.bold('✅ Agent created successfully!'));
|
|
3992
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
3993
|
+
}
|
|
3994
|
+
} catch (error) {
|
|
3995
|
+
console.error(chalk.red(`Error creating agent: ${error.message}`));
|
|
3996
|
+
process.exit(1);
|
|
3997
|
+
}
|
|
3998
|
+
});
|
|
3999
|
+
|
|
4000
|
+
program
|
|
4001
|
+
.command('list-agents')
|
|
4002
|
+
.description('List all agents')
|
|
4003
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4004
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4005
|
+
.action(async (options, command) => {
|
|
4006
|
+
try {
|
|
4007
|
+
const globalOptions = command.parent.opts();
|
|
4008
|
+
const config = loadConfig(globalOptions.config);
|
|
4009
|
+
validateConfiguration(config);
|
|
4010
|
+
|
|
4011
|
+
const api = new ToothFairyAPI(
|
|
4012
|
+
config.baseUrl,
|
|
4013
|
+
config.aiUrl,
|
|
4014
|
+
config.aiStreamUrl,
|
|
4015
|
+
config.apiKey,
|
|
4016
|
+
config.workspaceId,
|
|
4017
|
+
globalOptions.verbose || options.verbose
|
|
4018
|
+
);
|
|
4019
|
+
|
|
4020
|
+
const spinner = ora('Fetching agents...').start();
|
|
4021
|
+
const result = await api._makeRequest('GET', 'agent/list');
|
|
4022
|
+
spinner.stop();
|
|
4023
|
+
|
|
4024
|
+
if (options.output === 'json') {
|
|
4025
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4026
|
+
} else {
|
|
4027
|
+
const agents = Array.isArray(result) ? result : result.items || [];
|
|
4028
|
+
console.log(chalk.green.bold(`Found ${agents.length} agent(s)`));
|
|
4029
|
+
agents.forEach(agent => {
|
|
4030
|
+
console.log(chalk.cyan(` • ${agent.label || 'Unnamed'} (${agent.id})`));
|
|
4031
|
+
});
|
|
4032
|
+
}
|
|
4033
|
+
} catch (error) {
|
|
4034
|
+
console.error(chalk.red(`Error listing agents: ${error.message}`));
|
|
4035
|
+
process.exit(1);
|
|
4036
|
+
}
|
|
4037
|
+
});
|
|
4038
|
+
|
|
4039
|
+
program
|
|
4040
|
+
.command('get-agent')
|
|
4041
|
+
.description('Get details of a specific agent')
|
|
4042
|
+
.argument('<id>', 'Agent ID')
|
|
4043
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4044
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4045
|
+
.action(async (agentId, options, command) => {
|
|
4046
|
+
try {
|
|
4047
|
+
const globalOptions = command.parent.opts();
|
|
4048
|
+
const config = loadConfig(globalOptions.config);
|
|
4049
|
+
validateConfiguration(config);
|
|
4050
|
+
|
|
4051
|
+
const api = new ToothFairyAPI(
|
|
4052
|
+
config.baseUrl,
|
|
4053
|
+
config.aiUrl,
|
|
4054
|
+
config.aiStreamUrl,
|
|
4055
|
+
config.apiKey,
|
|
4056
|
+
config.workspaceId,
|
|
4057
|
+
globalOptions.verbose || options.verbose
|
|
4058
|
+
);
|
|
4059
|
+
|
|
4060
|
+
const spinner = ora('Fetching agent...').start();
|
|
4061
|
+
const result = await api._makeRequest('GET', `agent/get/${agentId}`);
|
|
4062
|
+
spinner.stop();
|
|
4063
|
+
|
|
4064
|
+
if (options.output === 'json') {
|
|
4065
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4066
|
+
} else {
|
|
4067
|
+
console.log(chalk.green.bold('Agent Details'));
|
|
4068
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
4069
|
+
console.log(chalk.dim(`Label: ${result.label}`));
|
|
4070
|
+
console.log(chalk.dim(`Description: ${result.description || 'N/A'}`));
|
|
4071
|
+
console.log(chalk.dim(`Mode: ${result.mode || 'N/A'}`));
|
|
4072
|
+
}
|
|
4073
|
+
} catch (error) {
|
|
4074
|
+
console.error(chalk.red(`Error getting agent: ${error.message}`));
|
|
4075
|
+
process.exit(1);
|
|
4076
|
+
}
|
|
4077
|
+
});
|
|
4078
|
+
|
|
4079
|
+
program
|
|
4080
|
+
.command('update-agent')
|
|
4081
|
+
.description('Update an existing agent')
|
|
4082
|
+
.argument('<id>', 'Agent ID')
|
|
4083
|
+
.option('--label <label>', 'Agent label')
|
|
4084
|
+
.option('--description <description>', 'Agent description')
|
|
4085
|
+
.option('--emoji <emoji>', 'Agent emoji')
|
|
4086
|
+
.option('--mode <mode>', 'Agent mode')
|
|
4087
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4088
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4089
|
+
.action(async (agentId, options, command) => {
|
|
4090
|
+
try {
|
|
4091
|
+
const globalOptions = command.parent.opts();
|
|
4092
|
+
const config = loadConfig(globalOptions.config);
|
|
4093
|
+
validateConfiguration(config);
|
|
4094
|
+
|
|
4095
|
+
const api = new ToothFairyAPI(
|
|
4096
|
+
config.baseUrl,
|
|
4097
|
+
config.aiUrl,
|
|
4098
|
+
config.aiStreamUrl,
|
|
4099
|
+
config.apiKey,
|
|
4100
|
+
config.workspaceId,
|
|
4101
|
+
globalOptions.verbose || options.verbose
|
|
4102
|
+
);
|
|
4103
|
+
|
|
4104
|
+
const spinner = ora('Updating agent...').start();
|
|
4105
|
+
const result = await api._makeRequest('POST', `agent/update/${agentId}`, {
|
|
4106
|
+
label: options.label,
|
|
4107
|
+
description: options.description,
|
|
4108
|
+
emoji: options.emoji,
|
|
4109
|
+
mode: options.mode,
|
|
4110
|
+
});
|
|
4111
|
+
spinner.stop();
|
|
4112
|
+
|
|
4113
|
+
if (options.output === 'json') {
|
|
4114
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4115
|
+
} else {
|
|
4116
|
+
console.log(chalk.green.bold('✅ Agent updated successfully!'));
|
|
4117
|
+
}
|
|
4118
|
+
} catch (error) {
|
|
4119
|
+
console.error(chalk.red(`Error updating agent: ${error.message}`));
|
|
4120
|
+
process.exit(1);
|
|
4121
|
+
}
|
|
4122
|
+
});
|
|
4123
|
+
|
|
4124
|
+
program
|
|
4125
|
+
.command('delete-agent')
|
|
4126
|
+
.description('Delete an agent')
|
|
4127
|
+
.argument('<id>', 'Agent ID')
|
|
4128
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
4129
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4130
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4131
|
+
.action(async (agentId, options, command) => {
|
|
4132
|
+
try {
|
|
4133
|
+
const globalOptions = command.parent.opts();
|
|
4134
|
+
const config = loadConfig(globalOptions.config);
|
|
4135
|
+
validateConfiguration(config);
|
|
4136
|
+
|
|
4137
|
+
if (!options.confirm) {
|
|
4138
|
+
const readline = require('readline');
|
|
4139
|
+
const rl = readline.createInterface({
|
|
4140
|
+
input: process.stdin,
|
|
4141
|
+
output: process.stdout,
|
|
4142
|
+
});
|
|
4143
|
+
|
|
4144
|
+
const answer = await new Promise((resolve) => {
|
|
4145
|
+
rl.question(
|
|
4146
|
+
chalk.yellow(`⚠️ Are you sure you want to delete agent ${agentId}? (y/N): `),
|
|
4147
|
+
resolve
|
|
4148
|
+
);
|
|
4149
|
+
});
|
|
4150
|
+
rl.close();
|
|
4151
|
+
|
|
4152
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
4153
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
4154
|
+
process.exit(0);
|
|
4155
|
+
}
|
|
4156
|
+
}
|
|
4157
|
+
|
|
4158
|
+
const api = new ToothFairyAPI(
|
|
4159
|
+
config.baseUrl,
|
|
4160
|
+
config.aiUrl,
|
|
4161
|
+
config.aiStreamUrl,
|
|
4162
|
+
config.apiKey,
|
|
4163
|
+
config.workspaceId,
|
|
4164
|
+
globalOptions.verbose || options.verbose
|
|
4165
|
+
);
|
|
4166
|
+
|
|
4167
|
+
const spinner = ora('Deleting agent...').start();
|
|
4168
|
+
const result = await api._makeRequest('DELETE', `agent/delete/${agentId}`);
|
|
4169
|
+
spinner.stop();
|
|
4170
|
+
|
|
4171
|
+
if (options.output === 'json') {
|
|
4172
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4173
|
+
} else {
|
|
4174
|
+
console.log(chalk.green.bold('✅ Agent deleted successfully!'));
|
|
4175
|
+
}
|
|
4176
|
+
} catch (error) {
|
|
4177
|
+
console.error(chalk.red(`Error deleting agent: ${error.message}`));
|
|
4178
|
+
process.exit(1);
|
|
4179
|
+
}
|
|
4180
|
+
});
|
|
4181
|
+
|
|
4182
|
+
// Function commands
|
|
4183
|
+
program
|
|
4184
|
+
.command('create-function')
|
|
4185
|
+
.description('Create a new agent function')
|
|
4186
|
+
.option('--name <name>', 'Function name')
|
|
4187
|
+
.option('--description <description>', 'Function description')
|
|
4188
|
+
.option('--code <code>', 'Function code')
|
|
4189
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4190
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4191
|
+
.action(async (options, command) => {
|
|
4192
|
+
try {
|
|
4193
|
+
const globalOptions = command.parent.opts();
|
|
4194
|
+
const config = loadConfig(globalOptions.config);
|
|
4195
|
+
validateConfiguration(config);
|
|
4196
|
+
|
|
4197
|
+
const api = new ToothFairyAPI(
|
|
4198
|
+
config.baseUrl,
|
|
4199
|
+
config.aiUrl,
|
|
4200
|
+
config.aiStreamUrl,
|
|
4201
|
+
config.apiKey,
|
|
4202
|
+
config.workspaceId,
|
|
4203
|
+
globalOptions.verbose || options.verbose
|
|
4204
|
+
);
|
|
4205
|
+
|
|
4206
|
+
const spinner = ora('Creating function...').start();
|
|
4207
|
+
const result = await api._makeRequest('POST', 'function/create', {
|
|
4208
|
+
name: options.name,
|
|
4209
|
+
description: options.description,
|
|
4210
|
+
code: options.code,
|
|
4211
|
+
});
|
|
4212
|
+
spinner.stop();
|
|
4213
|
+
|
|
4214
|
+
if (options.output === 'json') {
|
|
4215
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4216
|
+
} else {
|
|
4217
|
+
console.log(chalk.green.bold('✅ Function created successfully!'));
|
|
4218
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
4219
|
+
}
|
|
4220
|
+
} catch (error) {
|
|
4221
|
+
console.error(chalk.red(`Error creating function: ${error.message}`));
|
|
4222
|
+
process.exit(1);
|
|
4223
|
+
}
|
|
4224
|
+
});
|
|
4225
|
+
|
|
4226
|
+
program
|
|
4227
|
+
.command('list-functions')
|
|
4228
|
+
.description('List all functions')
|
|
4229
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4230
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4231
|
+
.action(async (options, command) => {
|
|
4232
|
+
try {
|
|
4233
|
+
const globalOptions = command.parent.opts();
|
|
4234
|
+
const config = loadConfig(globalOptions.config);
|
|
4235
|
+
validateConfiguration(config);
|
|
4236
|
+
|
|
4237
|
+
const api = new ToothFairyAPI(
|
|
4238
|
+
config.baseUrl,
|
|
4239
|
+
config.aiUrl,
|
|
4240
|
+
config.aiStreamUrl,
|
|
4241
|
+
config.apiKey,
|
|
4242
|
+
config.workspaceId,
|
|
4243
|
+
globalOptions.verbose || options.verbose
|
|
4244
|
+
);
|
|
4245
|
+
|
|
4246
|
+
const spinner = ora('Fetching functions...').start();
|
|
4247
|
+
const result = await api._makeRequest('GET', 'function/list');
|
|
4248
|
+
spinner.stop();
|
|
4249
|
+
|
|
4250
|
+
if (options.output === 'json') {
|
|
4251
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4252
|
+
} else {
|
|
4253
|
+
const functions = Array.isArray(result) ? result : result.items || [];
|
|
4254
|
+
console.log(chalk.green.bold(`Found ${functions.length} function(s)`));
|
|
4255
|
+
functions.forEach(fn => {
|
|
4256
|
+
console.log(chalk.cyan(` • ${fn.name || 'Unnamed'} (${fn.id})`));
|
|
4257
|
+
});
|
|
4258
|
+
}
|
|
4259
|
+
} catch (error) {
|
|
4260
|
+
console.error(chalk.red(`Error listing functions: ${error.message}`));
|
|
4261
|
+
process.exit(1);
|
|
4262
|
+
}
|
|
4263
|
+
});
|
|
4264
|
+
|
|
4265
|
+
program
|
|
4266
|
+
.command('get-function')
|
|
4267
|
+
.description('Get details of a specific function')
|
|
4268
|
+
.argument('<id>', 'Function ID')
|
|
4269
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4270
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4271
|
+
.action(async (functionId, options, command) => {
|
|
4272
|
+
try {
|
|
4273
|
+
const globalOptions = command.parent.opts();
|
|
4274
|
+
const config = loadConfig(globalOptions.config);
|
|
4275
|
+
validateConfiguration(config);
|
|
4276
|
+
|
|
4277
|
+
const api = new ToothFairyAPI(
|
|
4278
|
+
config.baseUrl,
|
|
4279
|
+
config.aiUrl,
|
|
4280
|
+
config.aiStreamUrl,
|
|
4281
|
+
config.apiKey,
|
|
4282
|
+
config.workspaceId,
|
|
4283
|
+
globalOptions.verbose || options.verbose
|
|
4284
|
+
);
|
|
4285
|
+
|
|
4286
|
+
const spinner = ora('Fetching function...').start();
|
|
4287
|
+
const result = await api._makeRequest('GET', `function/get/${functionId}`);
|
|
4288
|
+
spinner.stop();
|
|
4289
|
+
|
|
4290
|
+
if (options.output === 'json') {
|
|
4291
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4292
|
+
} else {
|
|
4293
|
+
console.log(chalk.green.bold('Function Details'));
|
|
4294
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
4295
|
+
console.log(chalk.dim(`Name: ${result.name}`));
|
|
4296
|
+
console.log(chalk.dim(`Description: ${result.description || 'N/A'}`));
|
|
4297
|
+
}
|
|
4298
|
+
} catch (error) {
|
|
4299
|
+
console.error(chalk.red(`Error getting function: ${error.message}`));
|
|
4300
|
+
process.exit(1);
|
|
4301
|
+
}
|
|
4302
|
+
});
|
|
4303
|
+
|
|
4304
|
+
program
|
|
4305
|
+
.command('update-function')
|
|
4306
|
+
.description('Update an existing function')
|
|
4307
|
+
.argument('<id>', 'Function ID')
|
|
4308
|
+
.option('--name <name>', 'Function name')
|
|
4309
|
+
.option('--description <description>', 'Function description')
|
|
4310
|
+
.option('--code <code>', 'Function code')
|
|
4311
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4312
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4313
|
+
.action(async (functionId, options, command) => {
|
|
4314
|
+
try {
|
|
4315
|
+
const globalOptions = command.parent.opts();
|
|
4316
|
+
const config = loadConfig(globalOptions.config);
|
|
4317
|
+
validateConfiguration(config);
|
|
4318
|
+
|
|
4319
|
+
const api = new ToothFairyAPI(
|
|
4320
|
+
config.baseUrl,
|
|
4321
|
+
config.aiUrl,
|
|
4322
|
+
config.aiStreamUrl,
|
|
4323
|
+
config.apiKey,
|
|
4324
|
+
config.workspaceId,
|
|
4325
|
+
globalOptions.verbose || options.verbose
|
|
4326
|
+
);
|
|
4327
|
+
|
|
4328
|
+
const spinner = ora('Updating function...').start();
|
|
4329
|
+
const result = await api._makeRequest('POST', `function/update/${functionId}`, {
|
|
4330
|
+
name: options.name,
|
|
4331
|
+
description: options.description,
|
|
4332
|
+
code: options.code,
|
|
4333
|
+
});
|
|
4334
|
+
spinner.stop();
|
|
4335
|
+
|
|
4336
|
+
if (options.output === 'json') {
|
|
4337
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4338
|
+
} else {
|
|
4339
|
+
console.log(chalk.green.bold('✅ Function updated successfully!'));
|
|
4340
|
+
}
|
|
4341
|
+
} catch (error) {
|
|
4342
|
+
console.error(chalk.red(`Error updating function: ${error.message}`));
|
|
4343
|
+
process.exit(1);
|
|
4344
|
+
}
|
|
4345
|
+
});
|
|
4346
|
+
|
|
4347
|
+
program
|
|
4348
|
+
.command('delete-function')
|
|
4349
|
+
.description('Delete a function')
|
|
4350
|
+
.argument('<id>', 'Function ID')
|
|
4351
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
4352
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4353
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4354
|
+
.action(async (functionId, options, command) => {
|
|
4355
|
+
try {
|
|
4356
|
+
const globalOptions = command.parent.opts();
|
|
4357
|
+
const config = loadConfig(globalOptions.config);
|
|
4358
|
+
validateConfiguration(config);
|
|
4359
|
+
|
|
4360
|
+
if (!options.confirm) {
|
|
4361
|
+
const readline = require('readline');
|
|
4362
|
+
const rl = readline.createInterface({
|
|
4363
|
+
input: process.stdin,
|
|
4364
|
+
output: process.stdout,
|
|
4365
|
+
});
|
|
4366
|
+
|
|
4367
|
+
const answer = await new Promise((resolve) => {
|
|
4368
|
+
rl.question(
|
|
4369
|
+
chalk.yellow(`⚠️ Are you sure you want to delete function ${functionId}? (y/N): `),
|
|
4370
|
+
resolve
|
|
4371
|
+
);
|
|
4372
|
+
});
|
|
4373
|
+
rl.close();
|
|
4374
|
+
|
|
4375
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
4376
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
4377
|
+
process.exit(0);
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
const api = new ToothFairyAPI(
|
|
4382
|
+
config.baseUrl,
|
|
4383
|
+
config.aiUrl,
|
|
4384
|
+
config.aiStreamUrl,
|
|
4385
|
+
config.apiKey,
|
|
4386
|
+
config.workspaceId,
|
|
4387
|
+
globalOptions.verbose || options.verbose
|
|
4388
|
+
);
|
|
4389
|
+
|
|
4390
|
+
const spinner = ora('Deleting function...').start();
|
|
4391
|
+
const result = await api._makeRequest('DELETE', `function/delete/${functionId}`);
|
|
4392
|
+
spinner.stop();
|
|
4393
|
+
|
|
4394
|
+
if (options.output === 'json') {
|
|
4395
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4396
|
+
} else {
|
|
4397
|
+
console.log(chalk.green.bold('✅ Function deleted successfully!'));
|
|
4398
|
+
}
|
|
4399
|
+
} catch (error) {
|
|
4400
|
+
console.error(chalk.red(`Error deleting function: ${error.message}`));
|
|
4401
|
+
process.exit(1);
|
|
4402
|
+
}
|
|
4403
|
+
});
|
|
4404
|
+
|
|
4405
|
+
// Channel commands
|
|
4406
|
+
program
|
|
4407
|
+
.command('create-channel')
|
|
4408
|
+
.description('Create a new channel')
|
|
4409
|
+
.option('--name <name>', 'Channel name')
|
|
4410
|
+
.option('--type <type>', 'Channel type')
|
|
4411
|
+
.option('--config <config>', 'Channel configuration (JSON)')
|
|
4412
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4413
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4414
|
+
.action(async (options, command) => {
|
|
4415
|
+
try {
|
|
4416
|
+
const globalOptions = command.parent.opts();
|
|
4417
|
+
const config = loadConfig(globalOptions.config);
|
|
4418
|
+
validateConfiguration(config);
|
|
4419
|
+
|
|
4420
|
+
const api = new ToothFairyAPI(
|
|
4421
|
+
config.baseUrl,
|
|
4422
|
+
config.aiUrl,
|
|
4423
|
+
config.aiStreamUrl,
|
|
4424
|
+
config.apiKey,
|
|
4425
|
+
config.workspaceId,
|
|
4426
|
+
globalOptions.verbose || options.verbose
|
|
4427
|
+
);
|
|
4428
|
+
|
|
4429
|
+
let channelConfig = {};
|
|
4430
|
+
if (options.config) {
|
|
4431
|
+
try {
|
|
4432
|
+
channelConfig = JSON.parse(options.config);
|
|
4433
|
+
} catch (e) {
|
|
4434
|
+
console.error(chalk.red('Invalid JSON in config'));
|
|
4435
|
+
process.exit(1);
|
|
4436
|
+
}
|
|
4437
|
+
}
|
|
4438
|
+
|
|
4439
|
+
const spinner = ora('Creating channel...').start();
|
|
4440
|
+
const result = await api._makeRequest('POST', 'channel/create', {
|
|
4441
|
+
name: options.name,
|
|
4442
|
+
type: options.type,
|
|
4443
|
+
config: channelConfig,
|
|
4444
|
+
});
|
|
4445
|
+
spinner.stop();
|
|
4446
|
+
|
|
4447
|
+
if (options.output === 'json') {
|
|
4448
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4449
|
+
} else {
|
|
4450
|
+
console.log(chalk.green.bold('✅ Channel created successfully!'));
|
|
4451
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
4452
|
+
}
|
|
4453
|
+
} catch (error) {
|
|
4454
|
+
console.error(chalk.red(`Error creating channel: ${error.message}`));
|
|
4455
|
+
process.exit(1);
|
|
4456
|
+
}
|
|
4457
|
+
});
|
|
4458
|
+
|
|
4459
|
+
program
|
|
4460
|
+
.command('list-channels')
|
|
4461
|
+
.description('List all channels')
|
|
4462
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4463
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4464
|
+
.action(async (options, command) => {
|
|
4465
|
+
try {
|
|
4466
|
+
const globalOptions = command.parent.opts();
|
|
4467
|
+
const config = loadConfig(globalOptions.config);
|
|
4468
|
+
validateConfiguration(config);
|
|
4469
|
+
|
|
4470
|
+
const api = new ToothFairyAPI(
|
|
4471
|
+
config.baseUrl,
|
|
4472
|
+
config.aiUrl,
|
|
4473
|
+
config.aiStreamUrl,
|
|
4474
|
+
config.apiKey,
|
|
4475
|
+
config.workspaceId,
|
|
4476
|
+
globalOptions.verbose || options.verbose
|
|
4477
|
+
);
|
|
4478
|
+
|
|
4479
|
+
const spinner = ora('Fetching channels...').start();
|
|
4480
|
+
const result = await api._makeRequest('GET', 'channel/list');
|
|
4481
|
+
spinner.stop();
|
|
4482
|
+
|
|
4483
|
+
if (options.output === 'json') {
|
|
4484
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4485
|
+
} else {
|
|
4486
|
+
const channels = Array.isArray(result) ? result : result.items || [];
|
|
4487
|
+
console.log(chalk.green.bold(`Found ${channels.length} channel(s)`));
|
|
4488
|
+
channels.forEach(ch => {
|
|
4489
|
+
console.log(chalk.cyan(` • ${ch.name || 'Unnamed'} (${ch.id})`));
|
|
4490
|
+
});
|
|
4491
|
+
}
|
|
4492
|
+
} catch (error) {
|
|
4493
|
+
console.error(chalk.red(`Error listing channels: ${error.message}`));
|
|
4494
|
+
process.exit(1);
|
|
4495
|
+
}
|
|
4496
|
+
});
|
|
4497
|
+
|
|
4498
|
+
program
|
|
4499
|
+
.command('get-channel')
|
|
4500
|
+
.description('Get details of a specific channel')
|
|
4501
|
+
.argument('<id>', 'Channel ID')
|
|
4502
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4503
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4504
|
+
.action(async (channelId, options, command) => {
|
|
4505
|
+
try {
|
|
4506
|
+
const globalOptions = command.parent.opts();
|
|
4507
|
+
const config = loadConfig(globalOptions.config);
|
|
4508
|
+
validateConfiguration(config);
|
|
4509
|
+
|
|
4510
|
+
const api = new ToothFairyAPI(
|
|
4511
|
+
config.baseUrl,
|
|
4512
|
+
config.aiUrl,
|
|
4513
|
+
config.aiStreamUrl,
|
|
4514
|
+
config.apiKey,
|
|
4515
|
+
config.workspaceId,
|
|
4516
|
+
globalOptions.verbose || options.verbose
|
|
4517
|
+
);
|
|
4518
|
+
|
|
4519
|
+
const spinner = ora('Fetching channel...').start();
|
|
4520
|
+
const result = await api._makeRequest('GET', `channel/get/${channelId}`);
|
|
4521
|
+
spinner.stop();
|
|
4522
|
+
|
|
4523
|
+
if (options.output === 'json') {
|
|
4524
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4525
|
+
} else {
|
|
4526
|
+
console.log(chalk.green.bold('Channel Details'));
|
|
4527
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
4528
|
+
console.log(chalk.dim(`Name: ${result.name}`));
|
|
4529
|
+
console.log(chalk.dim(`Type: ${result.type || 'N/A'}`));
|
|
4530
|
+
}
|
|
4531
|
+
} catch (error) {
|
|
4532
|
+
console.error(chalk.red(`Error getting channel: ${error.message}`));
|
|
4533
|
+
process.exit(1);
|
|
4534
|
+
}
|
|
4535
|
+
});
|
|
4536
|
+
|
|
4537
|
+
program
|
|
4538
|
+
.command('update-channel')
|
|
4539
|
+
.description('Update an existing channel')
|
|
4540
|
+
.argument('<id>', 'Channel ID')
|
|
4541
|
+
.option('--name <name>', 'Channel name')
|
|
4542
|
+
.option('--type <type>', 'Channel type')
|
|
4543
|
+
.option('--config <config>', 'Channel configuration (JSON)')
|
|
4544
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4545
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4546
|
+
.action(async (channelId, options, command) => {
|
|
4547
|
+
try {
|
|
4548
|
+
const globalOptions = command.parent.opts();
|
|
4549
|
+
const config = loadConfig(globalOptions.config);
|
|
4550
|
+
validateConfiguration(config);
|
|
4551
|
+
|
|
4552
|
+
const api = new ToothFairyAPI(
|
|
4553
|
+
config.baseUrl,
|
|
4554
|
+
config.aiUrl,
|
|
4555
|
+
config.aiStreamUrl,
|
|
4556
|
+
config.apiKey,
|
|
4557
|
+
config.workspaceId,
|
|
4558
|
+
globalOptions.verbose || options.verbose
|
|
4559
|
+
);
|
|
4560
|
+
|
|
4561
|
+
let channelConfig = {};
|
|
4562
|
+
if (options.config) {
|
|
4563
|
+
try {
|
|
4564
|
+
channelConfig = JSON.parse(options.config);
|
|
4565
|
+
} catch (e) {
|
|
4566
|
+
console.error(chalk.red('Invalid JSON in config'));
|
|
4567
|
+
process.exit(1);
|
|
4568
|
+
}
|
|
4569
|
+
}
|
|
4570
|
+
|
|
4571
|
+
const spinner = ora('Updating channel...').start();
|
|
4572
|
+
const result = await api._makeRequest('POST', 'channel/update', {
|
|
4573
|
+
id: channelId,
|
|
4574
|
+
name: options.name,
|
|
4575
|
+
type: options.type,
|
|
4576
|
+
config: channelConfig,
|
|
4577
|
+
});
|
|
4578
|
+
spinner.stop();
|
|
4579
|
+
|
|
4580
|
+
if (options.output === 'json') {
|
|
4581
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4582
|
+
} else {
|
|
4583
|
+
console.log(chalk.green.bold('✅ Channel updated successfully!'));
|
|
4584
|
+
}
|
|
4585
|
+
} catch (error) {
|
|
4586
|
+
console.error(chalk.red(`Error updating channel: ${error.message}`));
|
|
4587
|
+
process.exit(1);
|
|
4588
|
+
}
|
|
4589
|
+
});
|
|
4590
|
+
|
|
4591
|
+
program
|
|
4592
|
+
.command('delete-channel')
|
|
4593
|
+
.description('Delete a channel')
|
|
4594
|
+
.argument('<id>', 'Channel ID')
|
|
4595
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
4596
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4597
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4598
|
+
.action(async (channelId, options, command) => {
|
|
4599
|
+
try {
|
|
4600
|
+
const globalOptions = command.parent.opts();
|
|
4601
|
+
const config = loadConfig(globalOptions.config);
|
|
4602
|
+
validateConfiguration(config);
|
|
4603
|
+
|
|
4604
|
+
if (!options.confirm) {
|
|
4605
|
+
const readline = require('readline');
|
|
4606
|
+
const rl = readline.createInterface({
|
|
4607
|
+
input: process.stdin,
|
|
4608
|
+
output: process.stdout,
|
|
4609
|
+
});
|
|
4610
|
+
|
|
4611
|
+
const answer = await new Promise((resolve) => {
|
|
4612
|
+
rl.question(
|
|
4613
|
+
chalk.yellow(`⚠️ Are you sure you want to delete channel ${channelId}? (y/N): `),
|
|
4614
|
+
resolve
|
|
4615
|
+
);
|
|
4616
|
+
});
|
|
4617
|
+
rl.close();
|
|
4618
|
+
|
|
4619
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
4620
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
4621
|
+
process.exit(0);
|
|
4622
|
+
}
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4625
|
+
const api = new ToothFairyAPI(
|
|
4626
|
+
config.baseUrl,
|
|
4627
|
+
config.aiUrl,
|
|
4628
|
+
config.aiStreamUrl,
|
|
4629
|
+
config.apiKey,
|
|
4630
|
+
config.workspaceId,
|
|
4631
|
+
globalOptions.verbose || options.verbose
|
|
4632
|
+
);
|
|
4633
|
+
|
|
4634
|
+
const spinner = ora('Deleting channel...').start();
|
|
4635
|
+
const result = await api._makeRequest('DELETE', `channel/delete/${channelId}`);
|
|
4636
|
+
spinner.stop();
|
|
4637
|
+
|
|
4638
|
+
if (options.output === 'json') {
|
|
4639
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4640
|
+
} else {
|
|
4641
|
+
console.log(chalk.green.bold('✅ Channel deleted successfully!'));
|
|
4642
|
+
}
|
|
4643
|
+
} catch (error) {
|
|
4644
|
+
console.error(chalk.red(`Error deleting channel: ${error.message}`));
|
|
4645
|
+
process.exit(1);
|
|
4646
|
+
}
|
|
4647
|
+
});
|
|
4648
|
+
|
|
4649
|
+
// Billing command
|
|
4650
|
+
program
|
|
4651
|
+
.command('billing')
|
|
4652
|
+
.description('Get monthly billing costs')
|
|
4653
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4654
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4655
|
+
.action(async (options, command) => {
|
|
4656
|
+
try {
|
|
4657
|
+
const globalOptions = command.parent.opts();
|
|
4658
|
+
const config = loadConfig(globalOptions.config);
|
|
4659
|
+
validateConfiguration(config);
|
|
4660
|
+
|
|
4661
|
+
const api = new ToothFairyAPI(
|
|
4662
|
+
config.baseUrl,
|
|
4663
|
+
config.aiUrl,
|
|
4664
|
+
config.aiStreamUrl,
|
|
4665
|
+
config.apiKey,
|
|
4666
|
+
config.workspaceId,
|
|
4667
|
+
globalOptions.verbose || options.verbose
|
|
4668
|
+
);
|
|
4669
|
+
|
|
4670
|
+
const spinner = ora('Fetching billing information...').start();
|
|
4671
|
+
const result = await api._makeRequest('GET', 'billing/monthCosts');
|
|
4672
|
+
spinner.stop();
|
|
4673
|
+
|
|
4674
|
+
if (options.output === 'json') {
|
|
4675
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4676
|
+
} else {
|
|
4677
|
+
console.log(chalk.green.bold('💰 Monthly Billing'));
|
|
4678
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
4679
|
+
}
|
|
4680
|
+
} catch (error) {
|
|
4681
|
+
console.error(chalk.red(`Error fetching billing: ${error.message}`));
|
|
4682
|
+
process.exit(1);
|
|
4683
|
+
}
|
|
4684
|
+
});
|
|
4685
|
+
|
|
4686
|
+
// Embedding command
|
|
4687
|
+
program
|
|
4688
|
+
.command('get-embedding')
|
|
4689
|
+
.description('Get embedding for text')
|
|
4690
|
+
.argument('<text>', 'Text to get embedding for')
|
|
4691
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4692
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4693
|
+
.action(async (text, options, command) => {
|
|
4694
|
+
try {
|
|
4695
|
+
const globalOptions = command.parent.opts();
|
|
4696
|
+
const config = loadConfig(globalOptions.config);
|
|
4697
|
+
validateConfiguration(config);
|
|
4698
|
+
|
|
4699
|
+
const api = new ToothFairyAPI(
|
|
4700
|
+
config.baseUrl,
|
|
4701
|
+
config.aiUrl,
|
|
4702
|
+
config.aiStreamUrl,
|
|
4703
|
+
config.apiKey,
|
|
4704
|
+
config.workspaceId,
|
|
4705
|
+
globalOptions.verbose || options.verbose
|
|
4706
|
+
);
|
|
4707
|
+
|
|
4708
|
+
const spinner = ora('Generating embedding...').start();
|
|
4709
|
+
const result = await api._makeRequest('GET', 'embedding/get', { text });
|
|
4710
|
+
spinner.stop();
|
|
4711
|
+
|
|
4712
|
+
if (options.output === 'json') {
|
|
4713
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4714
|
+
} else {
|
|
4715
|
+
console.log(chalk.green.bold('🔢 Embedding Generated'));
|
|
4716
|
+
console.log(chalk.dim(`Dimensions: ${result.embedding?.length || 'N/A'}`));
|
|
4717
|
+
}
|
|
4718
|
+
} catch (error) {
|
|
4719
|
+
console.error(chalk.red(`Error getting embedding: ${error.message}`));
|
|
4720
|
+
process.exit(1);
|
|
4721
|
+
}
|
|
4722
|
+
});
|
|
4723
|
+
|
|
4724
|
+
// Dictionary commands
|
|
4725
|
+
program
|
|
4726
|
+
.command('get-dictionary')
|
|
4727
|
+
.description('Get dictionary entry')
|
|
4728
|
+
.argument('<id>', 'Dictionary ID')
|
|
4729
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4730
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4731
|
+
.action(async (dictId, options, command) => {
|
|
4732
|
+
try {
|
|
4733
|
+
const globalOptions = command.parent.opts();
|
|
4734
|
+
const config = loadConfig(globalOptions.config);
|
|
4735
|
+
validateConfiguration(config);
|
|
4736
|
+
|
|
4737
|
+
const api = new ToothFairyAPI(
|
|
4738
|
+
config.baseUrl,
|
|
4739
|
+
config.aiUrl,
|
|
4740
|
+
config.aiStreamUrl,
|
|
4741
|
+
config.apiKey,
|
|
4742
|
+
config.workspaceId,
|
|
4743
|
+
globalOptions.verbose || options.verbose
|
|
4744
|
+
);
|
|
4745
|
+
|
|
4746
|
+
const spinner = ora('Fetching dictionary...').start();
|
|
4747
|
+
const result = await api._makeRequest('GET', `dictionary/get/${dictId}`);
|
|
4748
|
+
spinner.stop();
|
|
4749
|
+
|
|
4750
|
+
if (options.output === 'json') {
|
|
4751
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4752
|
+
} else {
|
|
4753
|
+
console.log(chalk.green.bold('Dictionary Entry'));
|
|
4754
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
4755
|
+
}
|
|
4756
|
+
} catch (error) {
|
|
4757
|
+
console.error(chalk.red(`Error getting dictionary: ${error.message}`));
|
|
4758
|
+
process.exit(1);
|
|
4759
|
+
}
|
|
4760
|
+
});
|
|
4761
|
+
|
|
4762
|
+
program
|
|
4763
|
+
.command('list-dictionaries')
|
|
4764
|
+
.description('List all dictionaries')
|
|
4765
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4766
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4767
|
+
.action(async (options, command) => {
|
|
4768
|
+
try {
|
|
4769
|
+
const globalOptions = command.parent.opts();
|
|
4770
|
+
const config = loadConfig(globalOptions.config);
|
|
4771
|
+
validateConfiguration(config);
|
|
4772
|
+
|
|
4773
|
+
const api = new ToothFairyAPI(
|
|
4774
|
+
config.baseUrl,
|
|
4775
|
+
config.aiUrl,
|
|
4776
|
+
config.aiStreamUrl,
|
|
4777
|
+
config.apiKey,
|
|
4778
|
+
config.workspaceId,
|
|
4779
|
+
globalOptions.verbose || options.verbose
|
|
4780
|
+
);
|
|
4781
|
+
|
|
4782
|
+
const spinner = ora('Fetching dictionaries...').start();
|
|
4783
|
+
const result = await api._makeRequest('GET', 'dictionary/list');
|
|
4784
|
+
spinner.stop();
|
|
4785
|
+
|
|
4786
|
+
if (options.output === 'json') {
|
|
4787
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4788
|
+
} else {
|
|
4789
|
+
const dicts = Array.isArray(result) ? result : result.items || [];
|
|
4790
|
+
console.log(chalk.green.bold(`Found ${dicts.length} dictionary/ies`));
|
|
4791
|
+
dicts.forEach(d => {
|
|
4792
|
+
console.log(chalk.cyan(` • ${d.id}`));
|
|
4793
|
+
});
|
|
4794
|
+
}
|
|
4795
|
+
} catch (error) {
|
|
4796
|
+
console.error(chalk.red(`Error listing dictionaries: ${error.message}`));
|
|
4797
|
+
process.exit(1);
|
|
4798
|
+
}
|
|
4799
|
+
});
|
|
4800
|
+
|
|
4801
|
+
// Connection commands
|
|
4802
|
+
program
|
|
4803
|
+
.command('list-connections')
|
|
4804
|
+
.description('List all connections')
|
|
4805
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4806
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4807
|
+
.action(async (options, command) => {
|
|
4808
|
+
try {
|
|
4809
|
+
const globalOptions = command.parent.opts();
|
|
4810
|
+
const config = loadConfig(globalOptions.config);
|
|
4811
|
+
validateConfiguration(config);
|
|
4812
|
+
|
|
4813
|
+
const api = new ToothFairyAPI(
|
|
4814
|
+
config.baseUrl,
|
|
4815
|
+
config.aiUrl,
|
|
4816
|
+
config.aiStreamUrl,
|
|
4817
|
+
config.apiKey,
|
|
4818
|
+
config.workspaceId,
|
|
4819
|
+
globalOptions.verbose || options.verbose
|
|
4820
|
+
);
|
|
4821
|
+
|
|
4822
|
+
const spinner = ora('Fetching connections...').start();
|
|
4823
|
+
const result = await api._makeRequest('GET', 'connection/list');
|
|
4824
|
+
spinner.stop();
|
|
4825
|
+
|
|
4826
|
+
if (options.output === 'json') {
|
|
4827
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4828
|
+
} else {
|
|
4829
|
+
const connections = Array.isArray(result) ? result : result.items || [];
|
|
4830
|
+
console.log(chalk.green.bold(`Found ${connections.length} connection(s)`));
|
|
4831
|
+
connections.forEach(c => {
|
|
4832
|
+
console.log(chalk.cyan(` • ${c.id}`));
|
|
4833
|
+
});
|
|
4834
|
+
}
|
|
4835
|
+
} catch (error) {
|
|
4836
|
+
console.error(chalk.red(`Error listing connections: ${error.message}`));
|
|
4837
|
+
process.exit(1);
|
|
4838
|
+
}
|
|
4839
|
+
});
|
|
4840
|
+
|
|
4841
|
+
program
|
|
4842
|
+
.command('get-connection')
|
|
4843
|
+
.description('Get details of a specific connection')
|
|
4844
|
+
.argument('<id>', 'Connection ID')
|
|
4845
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4846
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4847
|
+
.action(async (connectionId, options, command) => {
|
|
4848
|
+
try {
|
|
4849
|
+
const globalOptions = command.parent.opts();
|
|
4850
|
+
const config = loadConfig(globalOptions.config);
|
|
4851
|
+
validateConfiguration(config);
|
|
4852
|
+
|
|
4853
|
+
const api = new ToothFairyAPI(
|
|
4854
|
+
config.baseUrl,
|
|
4855
|
+
config.aiUrl,
|
|
4856
|
+
config.aiStreamUrl,
|
|
4857
|
+
config.apiKey,
|
|
4858
|
+
config.workspaceId,
|
|
4859
|
+
globalOptions.verbose || options.verbose
|
|
4860
|
+
);
|
|
4861
|
+
|
|
4862
|
+
const spinner = ora('Fetching connection...').start();
|
|
4863
|
+
const result = await api._makeRequest('GET', `connection/get/${connectionId}`);
|
|
4864
|
+
spinner.stop();
|
|
4865
|
+
|
|
4866
|
+
if (options.output === 'json') {
|
|
4867
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4868
|
+
} else {
|
|
4869
|
+
console.log(chalk.green.bold('Connection Details'));
|
|
4870
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
4871
|
+
}
|
|
4872
|
+
} catch (error) {
|
|
4873
|
+
console.error(chalk.red(`Error getting connection: ${error.message}`));
|
|
4874
|
+
process.exit(1);
|
|
4875
|
+
}
|
|
4876
|
+
});
|
|
4877
|
+
|
|
4878
|
+
program
|
|
4879
|
+
.command('delete-connection')
|
|
4880
|
+
.description('Delete a connection')
|
|
4881
|
+
.argument('<id>', 'Connection ID')
|
|
4882
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
4883
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4884
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4885
|
+
.action(async (connectionId, options, command) => {
|
|
4886
|
+
try {
|
|
4887
|
+
const globalOptions = command.parent.opts();
|
|
4888
|
+
const config = loadConfig(globalOptions.config);
|
|
4889
|
+
validateConfiguration(config);
|
|
4890
|
+
|
|
4891
|
+
if (!options.confirm) {
|
|
4892
|
+
const readline = require('readline');
|
|
4893
|
+
const rl = readline.createInterface({
|
|
4894
|
+
input: process.stdin,
|
|
4895
|
+
output: process.stdout,
|
|
4896
|
+
});
|
|
4897
|
+
|
|
4898
|
+
const answer = await new Promise((resolve) => {
|
|
4899
|
+
rl.question(
|
|
4900
|
+
chalk.yellow(`⚠️ Are you sure you want to delete connection ${connectionId}? (y/N): `),
|
|
4901
|
+
resolve
|
|
4902
|
+
);
|
|
4903
|
+
});
|
|
4904
|
+
rl.close();
|
|
4905
|
+
|
|
4906
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
4907
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
4908
|
+
process.exit(0);
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
|
|
4912
|
+
const api = new ToothFairyAPI(
|
|
4913
|
+
config.baseUrl,
|
|
4914
|
+
config.aiUrl,
|
|
4915
|
+
config.aiStreamUrl,
|
|
4916
|
+
config.apiKey,
|
|
4917
|
+
config.workspaceId,
|
|
4918
|
+
globalOptions.verbose || options.verbose
|
|
4919
|
+
);
|
|
4920
|
+
|
|
4921
|
+
const spinner = ora('Deleting connection...').start();
|
|
4922
|
+
const result = await api._makeRequest('DELETE', `connection/delete/${connectionId}`);
|
|
4923
|
+
spinner.stop();
|
|
4924
|
+
|
|
4925
|
+
if (options.output === 'json') {
|
|
4926
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4927
|
+
} else {
|
|
4928
|
+
console.log(chalk.green.bold('✅ Connection deleted successfully!'));
|
|
4929
|
+
}
|
|
4930
|
+
} catch (error) {
|
|
4931
|
+
console.error(chalk.red(`Error deleting connection: ${error.message}`));
|
|
4932
|
+
process.exit(1);
|
|
4933
|
+
}
|
|
4934
|
+
});
|
|
4935
|
+
|
|
4936
|
+
// Member commands
|
|
4937
|
+
program
|
|
4938
|
+
.command('list-members')
|
|
4939
|
+
.description('List all members')
|
|
4940
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4941
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4942
|
+
.action(async (options, command) => {
|
|
4943
|
+
try {
|
|
4944
|
+
const globalOptions = command.parent.opts();
|
|
4945
|
+
const config = loadConfig(globalOptions.config);
|
|
4946
|
+
validateConfiguration(config);
|
|
4947
|
+
|
|
4948
|
+
const api = new ToothFairyAPI(
|
|
4949
|
+
config.baseUrl,
|
|
4950
|
+
config.aiUrl,
|
|
4951
|
+
config.aiStreamUrl,
|
|
4952
|
+
config.apiKey,
|
|
4953
|
+
config.workspaceId,
|
|
4954
|
+
globalOptions.verbose || options.verbose
|
|
4955
|
+
);
|
|
4956
|
+
|
|
4957
|
+
const spinner = ora('Fetching members...').start();
|
|
4958
|
+
const result = await api._makeRequest('GET', 'member/list');
|
|
4959
|
+
spinner.stop();
|
|
4960
|
+
|
|
4961
|
+
if (options.output === 'json') {
|
|
4962
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4963
|
+
} else {
|
|
4964
|
+
const members = Array.isArray(result) ? result : result.items || [];
|
|
4965
|
+
console.log(chalk.green.bold(`Found ${members.length} member(s)`));
|
|
4966
|
+
members.forEach(m => {
|
|
4967
|
+
console.log(chalk.cyan(` • ${m.name || m.email || m.id}`));
|
|
4968
|
+
});
|
|
4969
|
+
}
|
|
4970
|
+
} catch (error) {
|
|
4971
|
+
console.error(chalk.red(`Error listing members: ${error.message}`));
|
|
4972
|
+
process.exit(1);
|
|
4973
|
+
}
|
|
4974
|
+
});
|
|
4975
|
+
|
|
4976
|
+
program
|
|
4977
|
+
.command('get-member')
|
|
4978
|
+
.description('Get details of a specific member')
|
|
4979
|
+
.argument('<id>', 'Member ID')
|
|
4980
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
4981
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
4982
|
+
.action(async (memberId, options, command) => {
|
|
4983
|
+
try {
|
|
4984
|
+
const globalOptions = command.parent.opts();
|
|
4985
|
+
const config = loadConfig(globalOptions.config);
|
|
4986
|
+
validateConfiguration(config);
|
|
4987
|
+
|
|
4988
|
+
const api = new ToothFairyAPI(
|
|
4989
|
+
config.baseUrl,
|
|
4990
|
+
config.aiUrl,
|
|
4991
|
+
config.aiStreamUrl,
|
|
4992
|
+
config.apiKey,
|
|
4993
|
+
config.workspaceId,
|
|
4994
|
+
globalOptions.verbose || options.verbose
|
|
4995
|
+
);
|
|
4996
|
+
|
|
4997
|
+
const spinner = ora('Fetching member...').start();
|
|
4998
|
+
const result = await api._makeRequest('GET', `member/get/${memberId}`);
|
|
4999
|
+
spinner.stop();
|
|
5000
|
+
|
|
5001
|
+
if (options.output === 'json') {
|
|
5002
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5003
|
+
} else {
|
|
5004
|
+
console.log(chalk.green.bold('Member Details'));
|
|
5005
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
5006
|
+
}
|
|
5007
|
+
} catch (error) {
|
|
5008
|
+
console.error(chalk.red(`Error getting member: ${error.message}`));
|
|
5009
|
+
process.exit(1);
|
|
5010
|
+
}
|
|
5011
|
+
});
|
|
5012
|
+
|
|
5013
|
+
program
|
|
5014
|
+
.command('update-member')
|
|
5015
|
+
.description('Update a member')
|
|
5016
|
+
.argument('<id>', 'Member ID')
|
|
5017
|
+
.option('--name <name>', 'Member name')
|
|
5018
|
+
.option('--email <email>', 'Member email')
|
|
5019
|
+
.option('--role <role>', 'Member role')
|
|
5020
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5021
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5022
|
+
.action(async (memberId, options, command) => {
|
|
5023
|
+
try {
|
|
5024
|
+
const globalOptions = command.parent.opts();
|
|
5025
|
+
const config = loadConfig(globalOptions.config);
|
|
5026
|
+
validateConfiguration(config);
|
|
5027
|
+
|
|
5028
|
+
const api = new ToothFairyAPI(
|
|
5029
|
+
config.baseUrl,
|
|
5030
|
+
config.aiUrl,
|
|
5031
|
+
config.aiStreamUrl,
|
|
5032
|
+
config.apiKey,
|
|
5033
|
+
config.workspaceId,
|
|
5034
|
+
globalOptions.verbose || options.verbose
|
|
5035
|
+
);
|
|
5036
|
+
|
|
5037
|
+
const spinner = ora('Updating member...').start();
|
|
5038
|
+
const result = await api._makeRequest('POST', 'member/update', {
|
|
5039
|
+
id: memberId,
|
|
5040
|
+
name: options.name,
|
|
5041
|
+
email: options.email,
|
|
5042
|
+
role: options.role,
|
|
5043
|
+
});
|
|
5044
|
+
spinner.stop();
|
|
5045
|
+
|
|
5046
|
+
if (options.output === 'json') {
|
|
5047
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5048
|
+
} else {
|
|
5049
|
+
console.log(chalk.green.bold('✅ Member updated successfully!'));
|
|
5050
|
+
}
|
|
5051
|
+
} catch (error) {
|
|
5052
|
+
console.error(chalk.red(`Error updating member: ${error.message}`));
|
|
5053
|
+
process.exit(1);
|
|
5054
|
+
}
|
|
5055
|
+
});
|
|
5056
|
+
|
|
5057
|
+
program
|
|
5058
|
+
.command('delete-member')
|
|
5059
|
+
.description('Delete a member')
|
|
5060
|
+
.argument('<id>', 'Member ID')
|
|
5061
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
5062
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5063
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5064
|
+
.action(async (memberId, options, command) => {
|
|
5065
|
+
try {
|
|
5066
|
+
const globalOptions = command.parent.opts();
|
|
5067
|
+
const config = loadConfig(globalOptions.config);
|
|
5068
|
+
validateConfiguration(config);
|
|
5069
|
+
|
|
5070
|
+
if (!options.confirm) {
|
|
5071
|
+
const readline = require('readline');
|
|
5072
|
+
const rl = readline.createInterface({
|
|
5073
|
+
input: process.stdin,
|
|
5074
|
+
output: process.stdout,
|
|
5075
|
+
});
|
|
5076
|
+
|
|
5077
|
+
const answer = await new Promise((resolve) => {
|
|
5078
|
+
rl.question(
|
|
5079
|
+
chalk.yellow(`⚠️ Are you sure you want to delete member ${memberId}? (y/N): `),
|
|
5080
|
+
resolve
|
|
5081
|
+
);
|
|
5082
|
+
});
|
|
5083
|
+
rl.close();
|
|
5084
|
+
|
|
5085
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
5086
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
5087
|
+
process.exit(0);
|
|
5088
|
+
}
|
|
5089
|
+
}
|
|
5090
|
+
|
|
5091
|
+
const api = new ToothFairyAPI(
|
|
5092
|
+
config.baseUrl,
|
|
5093
|
+
config.aiUrl,
|
|
5094
|
+
config.aiStreamUrl,
|
|
5095
|
+
config.apiKey,
|
|
5096
|
+
config.workspaceId,
|
|
5097
|
+
globalOptions.verbose || options.verbose
|
|
5098
|
+
);
|
|
5099
|
+
|
|
5100
|
+
const spinner = ora('Deleting member...').start();
|
|
5101
|
+
const result = await api._makeRequest('DELETE', `member/delete/${memberId}`);
|
|
5102
|
+
spinner.stop();
|
|
5103
|
+
|
|
5104
|
+
if (options.output === 'json') {
|
|
5105
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5106
|
+
} else {
|
|
5107
|
+
console.log(chalk.green.bold('✅ Member deleted successfully!'));
|
|
5108
|
+
}
|
|
5109
|
+
} catch (error) {
|
|
5110
|
+
console.error(chalk.red(`Error deleting member: ${error.message}`));
|
|
5111
|
+
process.exit(1);
|
|
5112
|
+
}
|
|
5113
|
+
});
|
|
5114
|
+
|
|
5115
|
+
// Site commands
|
|
5116
|
+
program
|
|
5117
|
+
.command('list-sites')
|
|
5118
|
+
.description('List all sites')
|
|
5119
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5120
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5121
|
+
.action(async (options, command) => {
|
|
5122
|
+
try {
|
|
5123
|
+
const globalOptions = command.parent.opts();
|
|
5124
|
+
const config = loadConfig(globalOptions.config);
|
|
5125
|
+
validateConfiguration(config);
|
|
5126
|
+
|
|
5127
|
+
const api = new ToothFairyAPI(
|
|
5128
|
+
config.baseUrl,
|
|
5129
|
+
config.aiUrl,
|
|
5130
|
+
config.aiStreamUrl,
|
|
5131
|
+
config.apiKey,
|
|
5132
|
+
config.workspaceId,
|
|
5133
|
+
globalOptions.verbose || options.verbose
|
|
5134
|
+
);
|
|
5135
|
+
|
|
5136
|
+
const spinner = ora('Fetching sites...').start();
|
|
5137
|
+
const result = await api._makeRequest('GET', 'site/list');
|
|
5138
|
+
spinner.stop();
|
|
5139
|
+
|
|
5140
|
+
if (options.output === 'json') {
|
|
5141
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5142
|
+
} else {
|
|
5143
|
+
const sites = Array.isArray(result) ? result : result.items || [];
|
|
5144
|
+
console.log(chalk.green.bold(`Found ${sites.length} site(s)`));
|
|
5145
|
+
sites.forEach(s => {
|
|
5146
|
+
console.log(chalk.cyan(` • ${s.name || s.id}`));
|
|
5147
|
+
});
|
|
5148
|
+
}
|
|
5149
|
+
} catch (error) {
|
|
5150
|
+
console.error(chalk.red(`Error listing sites: ${error.message}`));
|
|
5151
|
+
process.exit(1);
|
|
5152
|
+
}
|
|
5153
|
+
});
|
|
5154
|
+
|
|
5155
|
+
program
|
|
5156
|
+
.command('get-site')
|
|
5157
|
+
.description('Get details of a specific site')
|
|
5158
|
+
.argument('<id>', 'Site ID')
|
|
5159
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5160
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5161
|
+
.action(async (siteId, options, command) => {
|
|
5162
|
+
try {
|
|
5163
|
+
const globalOptions = command.parent.opts();
|
|
5164
|
+
const config = loadConfig(globalOptions.config);
|
|
5165
|
+
validateConfiguration(config);
|
|
5166
|
+
|
|
5167
|
+
const api = new ToothFairyAPI(
|
|
5168
|
+
config.baseUrl,
|
|
5169
|
+
config.aiUrl,
|
|
5170
|
+
config.aiStreamUrl,
|
|
5171
|
+
config.apiKey,
|
|
5172
|
+
config.workspaceId,
|
|
5173
|
+
globalOptions.verbose || options.verbose
|
|
5174
|
+
);
|
|
5175
|
+
|
|
5176
|
+
const spinner = ora('Fetching site...').start();
|
|
5177
|
+
const result = await api._makeRequest('GET', `site/get/${siteId}`);
|
|
5178
|
+
spinner.stop();
|
|
5179
|
+
|
|
5180
|
+
if (options.output === 'json') {
|
|
5181
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5182
|
+
} else {
|
|
5183
|
+
console.log(chalk.green.bold('Site Details'));
|
|
5184
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
5185
|
+
}
|
|
5186
|
+
} catch (error) {
|
|
5187
|
+
console.error(chalk.red(`Error getting site: ${error.message}`));
|
|
5188
|
+
process.exit(1);
|
|
5189
|
+
}
|
|
5190
|
+
});
|
|
5191
|
+
|
|
5192
|
+
program
|
|
5193
|
+
.command('update-site')
|
|
5194
|
+
.description('Update a site')
|
|
5195
|
+
.argument('<id>', 'Site ID')
|
|
5196
|
+
.option('--name <name>', 'Site name')
|
|
5197
|
+
.option('--url <url>', 'Site URL')
|
|
5198
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5199
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5200
|
+
.action(async (siteId, options, command) => {
|
|
5201
|
+
try {
|
|
5202
|
+
const globalOptions = command.parent.opts();
|
|
5203
|
+
const config = loadConfig(globalOptions.config);
|
|
5204
|
+
validateConfiguration(config);
|
|
5205
|
+
|
|
5206
|
+
const api = new ToothFairyAPI(
|
|
5207
|
+
config.baseUrl,
|
|
5208
|
+
config.aiUrl,
|
|
5209
|
+
config.aiStreamUrl,
|
|
5210
|
+
config.apiKey,
|
|
5211
|
+
config.workspaceId,
|
|
5212
|
+
globalOptions.verbose || options.verbose
|
|
5213
|
+
);
|
|
5214
|
+
|
|
5215
|
+
const spinner = ora('Updating site...').start();
|
|
5216
|
+
const result = await api._makeRequest('POST', 'site/update', {
|
|
5217
|
+
id: siteId,
|
|
5218
|
+
name: options.name,
|
|
5219
|
+
url: options.url,
|
|
5220
|
+
});
|
|
5221
|
+
spinner.stop();
|
|
5222
|
+
|
|
5223
|
+
if (options.output === 'json') {
|
|
5224
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5225
|
+
} else {
|
|
5226
|
+
console.log(chalk.green.bold('✅ Site updated successfully!'));
|
|
5227
|
+
}
|
|
5228
|
+
} catch (error) {
|
|
5229
|
+
console.error(chalk.red(`Error updating site: ${error.message}`));
|
|
5230
|
+
process.exit(1);
|
|
5231
|
+
}
|
|
5232
|
+
});
|
|
5233
|
+
|
|
5234
|
+
program
|
|
5235
|
+
.command('delete-site')
|
|
5236
|
+
.description('Delete a site')
|
|
5237
|
+
.argument('<id>', 'Site ID')
|
|
5238
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
5239
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5240
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5241
|
+
.action(async (siteId, options, command) => {
|
|
5242
|
+
try {
|
|
5243
|
+
const globalOptions = command.parent.opts();
|
|
5244
|
+
const config = loadConfig(globalOptions.config);
|
|
5245
|
+
validateConfiguration(config);
|
|
5246
|
+
|
|
5247
|
+
if (!options.confirm) {
|
|
5248
|
+
const readline = require('readline');
|
|
5249
|
+
const rl = readline.createInterface({
|
|
5250
|
+
input: process.stdin,
|
|
5251
|
+
output: process.stdout,
|
|
5252
|
+
});
|
|
5253
|
+
|
|
5254
|
+
const answer = await new Promise((resolve) => {
|
|
5255
|
+
rl.question(
|
|
5256
|
+
chalk.yellow(`⚠️ Are you sure you want to delete site ${siteId}? (y/N): `),
|
|
5257
|
+
resolve
|
|
5258
|
+
);
|
|
5259
|
+
});
|
|
5260
|
+
rl.close();
|
|
5261
|
+
|
|
5262
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
5263
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
5264
|
+
process.exit(0);
|
|
5265
|
+
}
|
|
5266
|
+
}
|
|
5267
|
+
|
|
5268
|
+
const api = new ToothFairyAPI(
|
|
5269
|
+
config.baseUrl,
|
|
5270
|
+
config.aiUrl,
|
|
5271
|
+
config.aiStreamUrl,
|
|
5272
|
+
config.apiKey,
|
|
5273
|
+
config.workspaceId,
|
|
5274
|
+
globalOptions.verbose || options.verbose
|
|
5275
|
+
);
|
|
5276
|
+
|
|
5277
|
+
const spinner = ora('Deleting site...').start();
|
|
5278
|
+
const result = await api._makeRequest('DELETE', `site/delete/${siteId}`);
|
|
5279
|
+
spinner.stop();
|
|
5280
|
+
|
|
5281
|
+
if (options.output === 'json') {
|
|
5282
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5283
|
+
} else {
|
|
5284
|
+
console.log(chalk.green.bold('✅ Site deleted successfully!'));
|
|
5285
|
+
}
|
|
5286
|
+
} catch (error) {
|
|
5287
|
+
console.error(chalk.red(`Error deleting site: ${error.message}`));
|
|
5288
|
+
process.exit(1);
|
|
5289
|
+
}
|
|
5290
|
+
});
|
|
5291
|
+
|
|
5292
|
+
// Stream commands
|
|
5293
|
+
program
|
|
5294
|
+
.command('list-streams')
|
|
5295
|
+
.description('List all streams')
|
|
5296
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5297
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5298
|
+
.action(async (options, command) => {
|
|
5299
|
+
try {
|
|
5300
|
+
const globalOptions = command.parent.opts();
|
|
5301
|
+
const config = loadConfig(globalOptions.config);
|
|
5302
|
+
validateConfiguration(config);
|
|
5303
|
+
|
|
5304
|
+
const api = new ToothFairyAPI(
|
|
5305
|
+
config.baseUrl,
|
|
5306
|
+
config.aiUrl,
|
|
5307
|
+
config.aiStreamUrl,
|
|
5308
|
+
config.apiKey,
|
|
5309
|
+
config.workspaceId,
|
|
5310
|
+
globalOptions.verbose || options.verbose
|
|
5311
|
+
);
|
|
5312
|
+
|
|
5313
|
+
const spinner = ora('Fetching streams...').start();
|
|
5314
|
+
const result = await api._makeRequest('GET', 'stream/list');
|
|
5315
|
+
spinner.stop();
|
|
5316
|
+
|
|
5317
|
+
if (options.output === 'json') {
|
|
5318
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5319
|
+
} else {
|
|
5320
|
+
const streams = Array.isArray(result) ? result : result.items || [];
|
|
5321
|
+
console.log(chalk.green.bold(`Found ${streams.length} stream(s)`));
|
|
5322
|
+
streams.forEach(s => {
|
|
5323
|
+
console.log(chalk.cyan(` • ${s.id}`));
|
|
5324
|
+
});
|
|
5325
|
+
}
|
|
5326
|
+
} catch (error) {
|
|
5327
|
+
console.error(chalk.red(`Error listing streams: ${error.message}`));
|
|
5328
|
+
process.exit(1);
|
|
5329
|
+
}
|
|
5330
|
+
});
|
|
5331
|
+
|
|
5332
|
+
program
|
|
5333
|
+
.command('get-stream')
|
|
5334
|
+
.description('Get details of a specific stream')
|
|
5335
|
+
.argument('<id>', 'Stream ID')
|
|
5336
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5337
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5338
|
+
.action(async (streamId, options, command) => {
|
|
5339
|
+
try {
|
|
5340
|
+
const globalOptions = command.parent.opts();
|
|
5341
|
+
const config = loadConfig(globalOptions.config);
|
|
5342
|
+
validateConfiguration(config);
|
|
5343
|
+
|
|
5344
|
+
const api = new ToothFairyAPI(
|
|
5345
|
+
config.baseUrl,
|
|
5346
|
+
config.aiUrl,
|
|
5347
|
+
config.aiStreamUrl,
|
|
5348
|
+
config.apiKey,
|
|
5349
|
+
config.workspaceId,
|
|
5350
|
+
globalOptions.verbose || options.verbose
|
|
5351
|
+
);
|
|
5352
|
+
|
|
5353
|
+
const spinner = ora('Fetching stream...').start();
|
|
5354
|
+
const result = await api._makeRequest('GET', `stream/get/${streamId}`);
|
|
5355
|
+
spinner.stop();
|
|
5356
|
+
|
|
5357
|
+
if (options.output === 'json') {
|
|
5358
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5359
|
+
} else {
|
|
5360
|
+
console.log(chalk.green.bold('Stream Details'));
|
|
5361
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
5362
|
+
}
|
|
5363
|
+
} catch (error) {
|
|
5364
|
+
console.error(chalk.red(`Error getting stream: ${error.message}`));
|
|
5365
|
+
process.exit(1);
|
|
5366
|
+
}
|
|
5367
|
+
});
|
|
5368
|
+
|
|
5369
|
+
// Request commands
|
|
5370
|
+
program
|
|
5371
|
+
.command('list-requests')
|
|
5372
|
+
.description('List all requests')
|
|
5373
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5374
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5375
|
+
.action(async (options, command) => {
|
|
5376
|
+
try {
|
|
5377
|
+
const globalOptions = command.parent.opts();
|
|
5378
|
+
const config = loadConfig(globalOptions.config);
|
|
5379
|
+
validateConfiguration(config);
|
|
5380
|
+
|
|
5381
|
+
const api = new ToothFairyAPI(
|
|
5382
|
+
config.baseUrl,
|
|
5383
|
+
config.aiUrl,
|
|
5384
|
+
config.aiStreamUrl,
|
|
5385
|
+
config.apiKey,
|
|
5386
|
+
config.workspaceId,
|
|
5387
|
+
globalOptions.verbose || options.verbose
|
|
5388
|
+
);
|
|
5389
|
+
|
|
5390
|
+
const spinner = ora('Fetching requests...').start();
|
|
5391
|
+
const result = await api._makeRequest('GET', 'request/list');
|
|
5392
|
+
spinner.stop();
|
|
5393
|
+
|
|
5394
|
+
if (options.output === 'json') {
|
|
5395
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5396
|
+
} else {
|
|
5397
|
+
const requests = Array.isArray(result) ? result : result.items || [];
|
|
5398
|
+
console.log(chalk.green.bold(`Found ${requests.length} request(s)`));
|
|
5399
|
+
requests.forEach(r => {
|
|
5400
|
+
console.log(chalk.cyan(` • ${r.id}`));
|
|
5401
|
+
});
|
|
5402
|
+
}
|
|
5403
|
+
} catch (error) {
|
|
5404
|
+
console.error(chalk.red(`Error listing requests: ${error.message}`));
|
|
5405
|
+
process.exit(1);
|
|
5406
|
+
}
|
|
5407
|
+
});
|
|
5408
|
+
|
|
5409
|
+
program
|
|
5410
|
+
.command('get-request')
|
|
5411
|
+
.description('Get details of a specific request')
|
|
5412
|
+
.argument('<id>', 'Request ID')
|
|
5413
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5414
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5415
|
+
.action(async (requestId, options, command) => {
|
|
5416
|
+
try {
|
|
5417
|
+
const globalOptions = command.parent.opts();
|
|
5418
|
+
const config = loadConfig(globalOptions.config);
|
|
5419
|
+
validateConfiguration(config);
|
|
5420
|
+
|
|
5421
|
+
const api = new ToothFairyAPI(
|
|
5422
|
+
config.baseUrl,
|
|
5423
|
+
config.aiUrl,
|
|
5424
|
+
config.aiStreamUrl,
|
|
5425
|
+
config.apiKey,
|
|
5426
|
+
config.workspaceId,
|
|
5427
|
+
globalOptions.verbose || options.verbose
|
|
5428
|
+
);
|
|
5429
|
+
|
|
5430
|
+
const spinner = ora('Fetching request...').start();
|
|
5431
|
+
const result = await api._makeRequest('GET', `request/get/${requestId}`);
|
|
5432
|
+
spinner.stop();
|
|
5433
|
+
|
|
5434
|
+
if (options.output === 'json') {
|
|
5435
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5436
|
+
} else {
|
|
5437
|
+
console.log(chalk.green.bold('Request Details'));
|
|
5438
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
5439
|
+
}
|
|
5440
|
+
} catch (error) {
|
|
5441
|
+
console.error(chalk.red(`Error getting request: ${error.message}`));
|
|
5442
|
+
process.exit(1);
|
|
5443
|
+
}
|
|
5444
|
+
});
|
|
5445
|
+
|
|
5446
|
+
// Secret commands
|
|
5447
|
+
program
|
|
5448
|
+
.command('create-secret')
|
|
5449
|
+
.description('Create a new secret')
|
|
5450
|
+
.option('--name <name>', 'Secret name')
|
|
5451
|
+
.option('--value <value>', 'Secret value')
|
|
5452
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5453
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5454
|
+
.action(async (options, command) => {
|
|
5455
|
+
try {
|
|
5456
|
+
const globalOptions = command.parent.opts();
|
|
5457
|
+
const config = loadConfig(globalOptions.config);
|
|
5458
|
+
validateConfiguration(config);
|
|
5459
|
+
|
|
5460
|
+
const api = new ToothFairyAPI(
|
|
5461
|
+
config.baseUrl,
|
|
5462
|
+
config.aiUrl,
|
|
5463
|
+
config.aiStreamUrl,
|
|
5464
|
+
config.apiKey,
|
|
5465
|
+
config.workspaceId,
|
|
5466
|
+
globalOptions.verbose || options.verbose
|
|
5467
|
+
);
|
|
5468
|
+
|
|
5469
|
+
const spinner = ora('Creating secret...').start();
|
|
5470
|
+
const result = await api._makeRequest('POST', 'secret/create', {
|
|
5471
|
+
name: options.name,
|
|
5472
|
+
value: options.value,
|
|
5473
|
+
});
|
|
5474
|
+
spinner.stop();
|
|
5475
|
+
|
|
5476
|
+
if (options.output === 'json') {
|
|
5477
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5478
|
+
} else {
|
|
5479
|
+
console.log(chalk.green.bold('✅ Secret created successfully!'));
|
|
5480
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
5481
|
+
}
|
|
5482
|
+
} catch (error) {
|
|
5483
|
+
console.error(chalk.red(`Error creating secret: ${error.message}`));
|
|
5484
|
+
process.exit(1);
|
|
5485
|
+
}
|
|
5486
|
+
});
|
|
5487
|
+
|
|
5488
|
+
program
|
|
5489
|
+
.command('delete-secret')
|
|
5490
|
+
.description('Delete a secret')
|
|
5491
|
+
.argument('<id>', 'Secret ID')
|
|
5492
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
5493
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5494
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5495
|
+
.action(async (secretId, options, command) => {
|
|
5496
|
+
try {
|
|
5497
|
+
const globalOptions = command.parent.opts();
|
|
5498
|
+
const config = loadConfig(globalOptions.config);
|
|
5499
|
+
validateConfiguration(config);
|
|
5500
|
+
|
|
5501
|
+
if (!options.confirm) {
|
|
5502
|
+
const readline = require('readline');
|
|
5503
|
+
const rl = readline.createInterface({
|
|
5504
|
+
input: process.stdin,
|
|
5505
|
+
output: process.stdout,
|
|
5506
|
+
});
|
|
5507
|
+
|
|
5508
|
+
const answer = await new Promise((resolve) => {
|
|
5509
|
+
rl.question(
|
|
5510
|
+
chalk.yellow(`⚠️ Are you sure you want to delete secret ${secretId}? (y/N): `),
|
|
5511
|
+
resolve
|
|
5512
|
+
);
|
|
5513
|
+
});
|
|
5514
|
+
rl.close();
|
|
5515
|
+
|
|
5516
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
5517
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
5518
|
+
process.exit(0);
|
|
5519
|
+
}
|
|
5520
|
+
}
|
|
5521
|
+
|
|
5522
|
+
const api = new ToothFairyAPI(
|
|
5523
|
+
config.baseUrl,
|
|
5524
|
+
config.aiUrl,
|
|
5525
|
+
config.aiStreamUrl,
|
|
5526
|
+
config.apiKey,
|
|
5527
|
+
config.workspaceId,
|
|
5528
|
+
globalOptions.verbose || options.verbose
|
|
5529
|
+
);
|
|
5530
|
+
|
|
5531
|
+
const spinner = ora('Deleting secret...').start();
|
|
5532
|
+
const result = await api._makeRequest('DELETE', `secret/delete/${secretId}`);
|
|
5533
|
+
spinner.stop();
|
|
5534
|
+
|
|
5535
|
+
if (options.output === 'json') {
|
|
5536
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5537
|
+
} else {
|
|
5538
|
+
console.log(chalk.green.bold('✅ Secret deleted successfully!'));
|
|
5539
|
+
}
|
|
5540
|
+
} catch (error) {
|
|
5541
|
+
console.error(chalk.red(`Error deleting secret: ${error.message}`));
|
|
5542
|
+
process.exit(1);
|
|
5543
|
+
}
|
|
5544
|
+
});
|
|
5545
|
+
|
|
5546
|
+
// Hook commands
|
|
5547
|
+
program
|
|
5548
|
+
.command('create-hook')
|
|
5549
|
+
.description('Create a new hook')
|
|
5550
|
+
.option('--name <name>', 'Hook name')
|
|
5551
|
+
.option('--url <url>', 'Hook URL')
|
|
5552
|
+
.option('--events <events>', 'Events to trigger on (comma-separated)')
|
|
5553
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5554
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5555
|
+
.action(async (options, command) => {
|
|
5556
|
+
try {
|
|
5557
|
+
const globalOptions = command.parent.opts();
|
|
5558
|
+
const config = loadConfig(globalOptions.config);
|
|
5559
|
+
validateConfiguration(config);
|
|
5560
|
+
|
|
5561
|
+
const api = new ToothFairyAPI(
|
|
5562
|
+
config.baseUrl,
|
|
5563
|
+
config.aiUrl,
|
|
5564
|
+
config.aiStreamUrl,
|
|
5565
|
+
config.apiKey,
|
|
5566
|
+
config.workspaceId,
|
|
5567
|
+
globalOptions.verbose || options.verbose
|
|
5568
|
+
);
|
|
5569
|
+
|
|
5570
|
+
const events = options.events ? options.events.split(',').map(e => e.trim()) : [];
|
|
5571
|
+
|
|
5572
|
+
const spinner = ora('Creating hook...').start();
|
|
5573
|
+
const result = await api._makeRequest('POST', 'hook/create', {
|
|
5574
|
+
name: options.name,
|
|
5575
|
+
url: options.url,
|
|
5576
|
+
events,
|
|
5577
|
+
});
|
|
5578
|
+
spinner.stop();
|
|
5579
|
+
|
|
5580
|
+
if (options.output === 'json') {
|
|
5581
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5582
|
+
} else {
|
|
5583
|
+
console.log(chalk.green.bold('✅ Hook created successfully!'));
|
|
5584
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
5585
|
+
}
|
|
5586
|
+
} catch (error) {
|
|
5587
|
+
console.error(chalk.red(`Error creating hook: ${error.message}`));
|
|
5588
|
+
process.exit(1);
|
|
5589
|
+
}
|
|
5590
|
+
});
|
|
5591
|
+
|
|
5592
|
+
program
|
|
5593
|
+
.command('list-hooks')
|
|
5594
|
+
.description('List all hooks')
|
|
5595
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5596
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5597
|
+
.action(async (options, command) => {
|
|
5598
|
+
try {
|
|
5599
|
+
const globalOptions = command.parent.opts();
|
|
5600
|
+
const config = loadConfig(globalOptions.config);
|
|
5601
|
+
validateConfiguration(config);
|
|
5602
|
+
|
|
5603
|
+
const api = new ToothFairyAPI(
|
|
5604
|
+
config.baseUrl,
|
|
5605
|
+
config.aiUrl,
|
|
5606
|
+
config.aiStreamUrl,
|
|
5607
|
+
config.apiKey,
|
|
5608
|
+
config.workspaceId,
|
|
5609
|
+
globalOptions.verbose || options.verbose
|
|
5610
|
+
);
|
|
5611
|
+
|
|
5612
|
+
const spinner = ora('Fetching hooks...').start();
|
|
5613
|
+
const result = await api._makeRequest('GET', 'hook/list');
|
|
5614
|
+
spinner.stop();
|
|
5615
|
+
|
|
5616
|
+
if (options.output === 'json') {
|
|
5617
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5618
|
+
} else {
|
|
5619
|
+
const hooks = Array.isArray(result) ? result : result.items || [];
|
|
5620
|
+
console.log(chalk.green.bold(`Found ${hooks.length} hook(s)`));
|
|
5621
|
+
hooks.forEach(h => {
|
|
5622
|
+
console.log(chalk.cyan(` • ${h.name || 'Unnamed'} (${h.id})`));
|
|
5623
|
+
});
|
|
5624
|
+
}
|
|
5625
|
+
} catch (error) {
|
|
5626
|
+
console.error(chalk.red(`Error listing hooks: ${error.message}`));
|
|
5627
|
+
process.exit(1);
|
|
5628
|
+
}
|
|
5629
|
+
});
|
|
5630
|
+
|
|
5631
|
+
program
|
|
5632
|
+
.command('get-hook')
|
|
5633
|
+
.description('Get details of a specific hook')
|
|
5634
|
+
.argument('<id>', 'Hook ID')
|
|
5635
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5636
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5637
|
+
.action(async (hookId, options, command) => {
|
|
5638
|
+
try {
|
|
5639
|
+
const globalOptions = command.parent.opts();
|
|
5640
|
+
const config = loadConfig(globalOptions.config);
|
|
5641
|
+
validateConfiguration(config);
|
|
5642
|
+
|
|
5643
|
+
const api = new ToothFairyAPI(
|
|
5644
|
+
config.baseUrl,
|
|
5645
|
+
config.aiUrl,
|
|
5646
|
+
config.aiStreamUrl,
|
|
5647
|
+
config.apiKey,
|
|
5648
|
+
config.workspaceId,
|
|
5649
|
+
globalOptions.verbose || options.verbose
|
|
5650
|
+
);
|
|
5651
|
+
|
|
5652
|
+
const spinner = ora('Fetching hook...').start();
|
|
5653
|
+
const result = await api._makeRequest('GET', `hook/get/${hookId}`);
|
|
5654
|
+
spinner.stop();
|
|
5655
|
+
|
|
5656
|
+
if (options.output === 'json') {
|
|
5657
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5658
|
+
} else {
|
|
5659
|
+
console.log(chalk.green.bold('Hook Details'));
|
|
5660
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
5661
|
+
}
|
|
5662
|
+
} catch (error) {
|
|
5663
|
+
console.error(chalk.red(`Error getting hook: ${error.message}`));
|
|
5664
|
+
process.exit(1);
|
|
5665
|
+
}
|
|
5666
|
+
});
|
|
5667
|
+
|
|
5668
|
+
program
|
|
5669
|
+
.command('update-hook')
|
|
5670
|
+
.description('Update an existing hook')
|
|
5671
|
+
.argument('<id>', 'Hook ID')
|
|
5672
|
+
.option('--name <name>', 'Hook name')
|
|
5673
|
+
.option('--url <url>', 'Hook URL')
|
|
5674
|
+
.option('--events <events>', 'Events to trigger on (comma-separated)')
|
|
5675
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5676
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5677
|
+
.action(async (hookId, options, command) => {
|
|
5678
|
+
try {
|
|
5679
|
+
const globalOptions = command.parent.opts();
|
|
5680
|
+
const config = loadConfig(globalOptions.config);
|
|
5681
|
+
validateConfiguration(config);
|
|
5682
|
+
|
|
5683
|
+
const api = new ToothFairyAPI(
|
|
5684
|
+
config.baseUrl,
|
|
5685
|
+
config.aiUrl,
|
|
5686
|
+
config.aiStreamUrl,
|
|
5687
|
+
config.apiKey,
|
|
5688
|
+
config.workspaceId,
|
|
5689
|
+
globalOptions.verbose || options.verbose
|
|
5690
|
+
);
|
|
5691
|
+
|
|
5692
|
+
const events = options.events ? options.events.split(',').map(e => e.trim()) : [];
|
|
5693
|
+
|
|
5694
|
+
const spinner = ora('Updating hook...').start();
|
|
5695
|
+
const result = await api._makeRequest('POST', `hook/update/${hookId}`, {
|
|
5696
|
+
name: options.name,
|
|
5697
|
+
url: options.url,
|
|
5698
|
+
events,
|
|
5699
|
+
});
|
|
5700
|
+
spinner.stop();
|
|
5701
|
+
|
|
5702
|
+
if (options.output === 'json') {
|
|
5703
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5704
|
+
} else {
|
|
5705
|
+
console.log(chalk.green.bold('✅ Hook updated successfully!'));
|
|
5706
|
+
}
|
|
5707
|
+
} catch (error) {
|
|
5708
|
+
console.error(chalk.red(`Error updating hook: ${error.message}`));
|
|
5709
|
+
process.exit(1);
|
|
5710
|
+
}
|
|
5711
|
+
});
|
|
5712
|
+
|
|
5713
|
+
program
|
|
5714
|
+
.command('delete-hook')
|
|
5715
|
+
.description('Delete a hook')
|
|
5716
|
+
.argument('<id>', 'Hook ID')
|
|
5717
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
5718
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5719
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5720
|
+
.action(async (hookId, options, command) => {
|
|
5721
|
+
try {
|
|
5722
|
+
const globalOptions = command.parent.opts();
|
|
5723
|
+
const config = loadConfig(globalOptions.config);
|
|
5724
|
+
validateConfiguration(config);
|
|
5725
|
+
|
|
5726
|
+
if (!options.confirm) {
|
|
5727
|
+
const readline = require('readline');
|
|
5728
|
+
const rl = readline.createInterface({
|
|
5729
|
+
input: process.stdin,
|
|
5730
|
+
output: process.stdout,
|
|
5731
|
+
});
|
|
5732
|
+
|
|
5733
|
+
const answer = await new Promise((resolve) => {
|
|
5734
|
+
rl.question(
|
|
5735
|
+
chalk.yellow(`⚠️ Are you sure you want to delete hook ${hookId}? (y/N): `),
|
|
5736
|
+
resolve
|
|
5737
|
+
);
|
|
5738
|
+
});
|
|
5739
|
+
rl.close();
|
|
5740
|
+
|
|
5741
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
5742
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
5743
|
+
process.exit(0);
|
|
5744
|
+
}
|
|
5745
|
+
}
|
|
5746
|
+
|
|
5747
|
+
const api = new ToothFairyAPI(
|
|
5748
|
+
config.baseUrl,
|
|
5749
|
+
config.aiUrl,
|
|
5750
|
+
config.aiStreamUrl,
|
|
5751
|
+
config.apiKey,
|
|
5752
|
+
config.workspaceId,
|
|
5753
|
+
globalOptions.verbose || options.verbose
|
|
5754
|
+
);
|
|
5755
|
+
|
|
5756
|
+
const spinner = ora('Deleting hook...').start();
|
|
5757
|
+
const result = await api._makeRequest('DELETE', `hook/delete/${hookId}`);
|
|
5758
|
+
spinner.stop();
|
|
5759
|
+
|
|
5760
|
+
if (options.output === 'json') {
|
|
5761
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5762
|
+
} else {
|
|
5763
|
+
console.log(chalk.green.bold('✅ Hook deleted successfully!'));
|
|
5764
|
+
}
|
|
5765
|
+
} catch (error) {
|
|
5766
|
+
console.error(chalk.red(`Error deleting hook: ${error.message}`));
|
|
5767
|
+
process.exit(1);
|
|
5768
|
+
}
|
|
5769
|
+
});
|
|
5770
|
+
|
|
5771
|
+
// Benchmark commands
|
|
5772
|
+
program
|
|
5773
|
+
.command('create-benchmark')
|
|
5774
|
+
.description('Create a new benchmark')
|
|
5775
|
+
.option('--name <name>', 'Benchmark name')
|
|
5776
|
+
.option('--description <description>', 'Benchmark description')
|
|
5777
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5778
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5779
|
+
.action(async (options, command) => {
|
|
5780
|
+
try {
|
|
5781
|
+
const globalOptions = command.parent.opts();
|
|
5782
|
+
const config = loadConfig(globalOptions.config);
|
|
5783
|
+
validateConfiguration(config);
|
|
5784
|
+
|
|
5785
|
+
const api = new ToothFairyAPI(
|
|
5786
|
+
config.baseUrl,
|
|
5787
|
+
config.aiUrl,
|
|
5788
|
+
config.aiStreamUrl,
|
|
5789
|
+
config.apiKey,
|
|
5790
|
+
config.workspaceId,
|
|
5791
|
+
globalOptions.verbose || options.verbose
|
|
5792
|
+
);
|
|
5793
|
+
|
|
5794
|
+
const spinner = ora('Creating benchmark...').start();
|
|
5795
|
+
const result = await api._makeRequest('POST', 'benchmark/create', {
|
|
5796
|
+
name: options.name,
|
|
5797
|
+
description: options.description,
|
|
5798
|
+
});
|
|
5799
|
+
spinner.stop();
|
|
5800
|
+
|
|
5801
|
+
if (options.output === 'json') {
|
|
5802
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5803
|
+
} else {
|
|
5804
|
+
console.log(chalk.green.bold('✅ Benchmark created successfully!'));
|
|
5805
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
5806
|
+
}
|
|
5807
|
+
} catch (error) {
|
|
5808
|
+
console.error(chalk.red(`Error creating benchmark: ${error.message}`));
|
|
5809
|
+
process.exit(1);
|
|
5810
|
+
}
|
|
5811
|
+
});
|
|
5812
|
+
|
|
5813
|
+
program
|
|
5814
|
+
.command('list-benchmarks')
|
|
5815
|
+
.description('List all benchmarks')
|
|
5816
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5817
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5818
|
+
.action(async (options, command) => {
|
|
5819
|
+
try {
|
|
5820
|
+
const globalOptions = command.parent.opts();
|
|
5821
|
+
const config = loadConfig(globalOptions.config);
|
|
5822
|
+
validateConfiguration(config);
|
|
5823
|
+
|
|
5824
|
+
const api = new ToothFairyAPI(
|
|
5825
|
+
config.baseUrl,
|
|
5826
|
+
config.aiUrl,
|
|
5827
|
+
config.aiStreamUrl,
|
|
5828
|
+
config.apiKey,
|
|
5829
|
+
config.workspaceId,
|
|
5830
|
+
globalOptions.verbose || options.verbose
|
|
5831
|
+
);
|
|
5832
|
+
|
|
5833
|
+
const spinner = ora('Fetching benchmarks...').start();
|
|
5834
|
+
const result = await api._makeRequest('GET', 'benchmark/list');
|
|
5835
|
+
spinner.stop();
|
|
5836
|
+
|
|
5837
|
+
if (options.output === 'json') {
|
|
5838
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5839
|
+
} else {
|
|
5840
|
+
const benchmarks = Array.isArray(result) ? result : result.items || [];
|
|
5841
|
+
console.log(chalk.green.bold(`Found ${benchmarks.length} benchmark(s)`));
|
|
5842
|
+
benchmarks.forEach(b => {
|
|
5843
|
+
console.log(chalk.cyan(` • ${b.name || 'Unnamed'} (${b.id})`));
|
|
5844
|
+
});
|
|
5845
|
+
}
|
|
5846
|
+
} catch (error) {
|
|
5847
|
+
console.error(chalk.red(`Error listing benchmarks: ${error.message}`));
|
|
5848
|
+
process.exit(1);
|
|
5849
|
+
}
|
|
5850
|
+
});
|
|
5851
|
+
|
|
5852
|
+
program
|
|
5853
|
+
.command('get-benchmark')
|
|
5854
|
+
.description('Get details of a specific benchmark')
|
|
5855
|
+
.argument('<id>', 'Benchmark ID')
|
|
5856
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5857
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5858
|
+
.action(async (benchmarkId, options, command) => {
|
|
5859
|
+
try {
|
|
5860
|
+
const globalOptions = command.parent.opts();
|
|
5861
|
+
const config = loadConfig(globalOptions.config);
|
|
5862
|
+
validateConfiguration(config);
|
|
5863
|
+
|
|
5864
|
+
const api = new ToothFairyAPI(
|
|
5865
|
+
config.baseUrl,
|
|
5866
|
+
config.aiUrl,
|
|
5867
|
+
config.aiStreamUrl,
|
|
5868
|
+
config.apiKey,
|
|
5869
|
+
config.workspaceId,
|
|
5870
|
+
globalOptions.verbose || options.verbose
|
|
5871
|
+
);
|
|
5872
|
+
|
|
5873
|
+
const spinner = ora('Fetching benchmark...').start();
|
|
5874
|
+
const result = await api._makeRequest('GET', `benchmark/get/${benchmarkId}`);
|
|
5875
|
+
spinner.stop();
|
|
5876
|
+
|
|
5877
|
+
if (options.output === 'json') {
|
|
5878
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5879
|
+
} else {
|
|
5880
|
+
console.log(chalk.green.bold('Benchmark Details'));
|
|
5881
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
5882
|
+
}
|
|
5883
|
+
} catch (error) {
|
|
5884
|
+
console.error(chalk.red(`Error getting benchmark: ${error.message}`));
|
|
5885
|
+
process.exit(1);
|
|
5886
|
+
}
|
|
5887
|
+
});
|
|
5888
|
+
|
|
5889
|
+
program
|
|
5890
|
+
.command('update-benchmark')
|
|
5891
|
+
.description('Update an existing benchmark')
|
|
5892
|
+
.argument('<id>', 'Benchmark ID')
|
|
5893
|
+
.option('--name <name>', 'Benchmark name')
|
|
5894
|
+
.option('--description <description>', 'Benchmark description')
|
|
5895
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5896
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5897
|
+
.action(async (benchmarkId, options, command) => {
|
|
5898
|
+
try {
|
|
5899
|
+
const globalOptions = command.parent.opts();
|
|
5900
|
+
const config = loadConfig(globalOptions.config);
|
|
5901
|
+
validateConfiguration(config);
|
|
5902
|
+
|
|
5903
|
+
const api = new ToothFairyAPI(
|
|
5904
|
+
config.baseUrl,
|
|
5905
|
+
config.aiUrl,
|
|
5906
|
+
config.aiStreamUrl,
|
|
5907
|
+
config.apiKey,
|
|
5908
|
+
config.workspaceId,
|
|
5909
|
+
globalOptions.verbose || options.verbose
|
|
5910
|
+
);
|
|
5911
|
+
|
|
5912
|
+
const spinner = ora('Updating benchmark...').start();
|
|
5913
|
+
const result = await api._makeRequest('POST', `benchmark/update/${benchmarkId}`, {
|
|
5914
|
+
name: options.name,
|
|
5915
|
+
description: options.description,
|
|
5916
|
+
});
|
|
5917
|
+
spinner.stop();
|
|
5918
|
+
|
|
5919
|
+
if (options.output === 'json') {
|
|
5920
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5921
|
+
} else {
|
|
5922
|
+
console.log(chalk.green.bold('✅ Benchmark updated successfully!'));
|
|
5923
|
+
}
|
|
5924
|
+
} catch (error) {
|
|
5925
|
+
console.error(chalk.red(`Error updating benchmark: ${error.message}`));
|
|
5926
|
+
process.exit(1);
|
|
5927
|
+
}
|
|
5928
|
+
});
|
|
5929
|
+
|
|
5930
|
+
program
|
|
5931
|
+
.command('delete-benchmark')
|
|
5932
|
+
.description('Delete a benchmark')
|
|
5933
|
+
.argument('<id>', 'Benchmark ID')
|
|
5934
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
5935
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5936
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5937
|
+
.action(async (benchmarkId, options, command) => {
|
|
5938
|
+
try {
|
|
5939
|
+
const globalOptions = command.parent.opts();
|
|
5940
|
+
const config = loadConfig(globalOptions.config);
|
|
5941
|
+
validateConfiguration(config);
|
|
5942
|
+
|
|
5943
|
+
if (!options.confirm) {
|
|
5944
|
+
const readline = require('readline');
|
|
5945
|
+
const rl = readline.createInterface({
|
|
5946
|
+
input: process.stdin,
|
|
5947
|
+
output: process.stdout,
|
|
5948
|
+
});
|
|
5949
|
+
|
|
5950
|
+
const answer = await new Promise((resolve) => {
|
|
5951
|
+
rl.question(
|
|
5952
|
+
chalk.yellow(`⚠️ Are you sure you want to delete benchmark ${benchmarkId}? (y/N): `),
|
|
5953
|
+
resolve
|
|
5954
|
+
);
|
|
5955
|
+
});
|
|
5956
|
+
rl.close();
|
|
5957
|
+
|
|
5958
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
5959
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
5960
|
+
process.exit(0);
|
|
5961
|
+
}
|
|
5962
|
+
}
|
|
5963
|
+
|
|
5964
|
+
const api = new ToothFairyAPI(
|
|
5965
|
+
config.baseUrl,
|
|
5966
|
+
config.aiUrl,
|
|
5967
|
+
config.aiStreamUrl,
|
|
5968
|
+
config.apiKey,
|
|
5969
|
+
config.workspaceId,
|
|
5970
|
+
globalOptions.verbose || options.verbose
|
|
5971
|
+
);
|
|
5972
|
+
|
|
5973
|
+
const spinner = ora('Deleting benchmark...').start();
|
|
5974
|
+
const result = await api._makeRequest('DELETE', `benchmark/delete/${benchmarkId}`);
|
|
5975
|
+
spinner.stop();
|
|
5976
|
+
|
|
5977
|
+
if (options.output === 'json') {
|
|
5978
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5979
|
+
} else {
|
|
5980
|
+
console.log(chalk.green.bold('✅ Benchmark deleted successfully!'));
|
|
5981
|
+
}
|
|
5982
|
+
} catch (error) {
|
|
5983
|
+
console.error(chalk.red(`Error deleting benchmark: ${error.message}`));
|
|
5984
|
+
process.exit(1);
|
|
5985
|
+
}
|
|
5986
|
+
});
|
|
5987
|
+
|
|
5988
|
+
// Scheduled Job commands
|
|
5989
|
+
program
|
|
5990
|
+
.command('create-scheduled-job')
|
|
5991
|
+
.description('Create a new scheduled job')
|
|
5992
|
+
.option('--name <name>', 'Job name')
|
|
5993
|
+
.option('--schedule <schedule>', 'Cron schedule expression')
|
|
5994
|
+
.option('--task <task>', 'Task to execute')
|
|
5995
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
5996
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
5997
|
+
.action(async (options, command) => {
|
|
5998
|
+
try {
|
|
5999
|
+
const globalOptions = command.parent.opts();
|
|
6000
|
+
const config = loadConfig(globalOptions.config);
|
|
6001
|
+
validateConfiguration(config);
|
|
6002
|
+
|
|
6003
|
+
const api = new ToothFairyAPI(
|
|
6004
|
+
config.baseUrl,
|
|
6005
|
+
config.aiUrl,
|
|
6006
|
+
config.aiStreamUrl,
|
|
6007
|
+
config.apiKey,
|
|
6008
|
+
config.workspaceId,
|
|
6009
|
+
globalOptions.verbose || options.verbose
|
|
6010
|
+
);
|
|
6011
|
+
|
|
6012
|
+
const spinner = ora('Creating scheduled job...').start();
|
|
6013
|
+
const result = await api._makeRequest('POST', 'scheduled_job/create', {
|
|
6014
|
+
name: options.name,
|
|
6015
|
+
schedule: options.schedule,
|
|
6016
|
+
task: options.task,
|
|
6017
|
+
});
|
|
6018
|
+
spinner.stop();
|
|
6019
|
+
|
|
6020
|
+
if (options.output === 'json') {
|
|
6021
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6022
|
+
} else {
|
|
6023
|
+
console.log(chalk.green.bold('✅ Scheduled job created successfully!'));
|
|
6024
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
6025
|
+
}
|
|
6026
|
+
} catch (error) {
|
|
6027
|
+
console.error(chalk.red(`Error creating scheduled job: ${error.message}`));
|
|
6028
|
+
process.exit(1);
|
|
6029
|
+
}
|
|
6030
|
+
});
|
|
6031
|
+
|
|
6032
|
+
program
|
|
6033
|
+
.command('list-scheduled-jobs')
|
|
6034
|
+
.description('List all scheduled jobs')
|
|
6035
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6036
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6037
|
+
.action(async (options, command) => {
|
|
6038
|
+
try {
|
|
6039
|
+
const globalOptions = command.parent.opts();
|
|
6040
|
+
const config = loadConfig(globalOptions.config);
|
|
6041
|
+
validateConfiguration(config);
|
|
6042
|
+
|
|
6043
|
+
const api = new ToothFairyAPI(
|
|
6044
|
+
config.baseUrl,
|
|
6045
|
+
config.aiUrl,
|
|
6046
|
+
config.aiStreamUrl,
|
|
6047
|
+
config.apiKey,
|
|
6048
|
+
config.workspaceId,
|
|
6049
|
+
globalOptions.verbose || options.verbose
|
|
6050
|
+
);
|
|
6051
|
+
|
|
6052
|
+
const spinner = ora('Fetching scheduled jobs...').start();
|
|
6053
|
+
const result = await api._makeRequest('GET', 'scheduled_job/list');
|
|
6054
|
+
spinner.stop();
|
|
6055
|
+
|
|
6056
|
+
if (options.output === 'json') {
|
|
6057
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6058
|
+
} else {
|
|
6059
|
+
const jobs = Array.isArray(result) ? result : result.items || [];
|
|
6060
|
+
console.log(chalk.green.bold(`Found ${jobs.length} scheduled job(s)`));
|
|
6061
|
+
jobs.forEach(j => {
|
|
6062
|
+
console.log(chalk.cyan(` • ${j.name || 'Unnamed'} (${j.id})`));
|
|
6063
|
+
});
|
|
6064
|
+
}
|
|
6065
|
+
} catch (error) {
|
|
6066
|
+
console.error(chalk.red(`Error listing scheduled jobs: ${error.message}`));
|
|
6067
|
+
process.exit(1);
|
|
6068
|
+
}
|
|
6069
|
+
});
|
|
6070
|
+
|
|
6071
|
+
program
|
|
6072
|
+
.command('get-scheduled-job')
|
|
6073
|
+
.description('Get details of a specific scheduled job')
|
|
6074
|
+
.argument('<id>', 'Scheduled Job ID')
|
|
6075
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6076
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6077
|
+
.action(async (jobId, options, command) => {
|
|
6078
|
+
try {
|
|
6079
|
+
const globalOptions = command.parent.opts();
|
|
6080
|
+
const config = loadConfig(globalOptions.config);
|
|
6081
|
+
validateConfiguration(config);
|
|
6082
|
+
|
|
6083
|
+
const api = new ToothFairyAPI(
|
|
6084
|
+
config.baseUrl,
|
|
6085
|
+
config.aiUrl,
|
|
6086
|
+
config.aiStreamUrl,
|
|
6087
|
+
config.apiKey,
|
|
6088
|
+
config.workspaceId,
|
|
6089
|
+
globalOptions.verbose || options.verbose
|
|
6090
|
+
);
|
|
6091
|
+
|
|
6092
|
+
const spinner = ora('Fetching scheduled job...').start();
|
|
6093
|
+
const result = await api._makeRequest('GET', `scheduled_job/get/${jobId}`);
|
|
6094
|
+
spinner.stop();
|
|
6095
|
+
|
|
6096
|
+
if (options.output === 'json') {
|
|
6097
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6098
|
+
} else {
|
|
6099
|
+
console.log(chalk.green.bold('Scheduled Job Details'));
|
|
6100
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
6101
|
+
}
|
|
6102
|
+
} catch (error) {
|
|
6103
|
+
console.error(chalk.red(`Error getting scheduled job: ${error.message}`));
|
|
6104
|
+
process.exit(1);
|
|
6105
|
+
}
|
|
6106
|
+
});
|
|
6107
|
+
|
|
6108
|
+
program
|
|
6109
|
+
.command('update-scheduled-job')
|
|
6110
|
+
.description('Update an existing scheduled job')
|
|
6111
|
+
.argument('<id>', 'Scheduled Job ID')
|
|
6112
|
+
.option('--name <name>', 'Job name')
|
|
6113
|
+
.option('--schedule <schedule>', 'Cron schedule expression')
|
|
6114
|
+
.option('--task <task>', 'Task to execute')
|
|
6115
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6116
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6117
|
+
.action(async (jobId, options, command) => {
|
|
6118
|
+
try {
|
|
6119
|
+
const globalOptions = command.parent.opts();
|
|
6120
|
+
const config = loadConfig(globalOptions.config);
|
|
6121
|
+
validateConfiguration(config);
|
|
6122
|
+
|
|
6123
|
+
const api = new ToothFairyAPI(
|
|
6124
|
+
config.baseUrl,
|
|
6125
|
+
config.aiUrl,
|
|
6126
|
+
config.aiStreamUrl,
|
|
6127
|
+
config.apiKey,
|
|
6128
|
+
config.workspaceId,
|
|
6129
|
+
globalOptions.verbose || options.verbose
|
|
6130
|
+
);
|
|
6131
|
+
|
|
6132
|
+
const spinner = ora('Updating scheduled job...').start();
|
|
6133
|
+
const result = await api._makeRequest('POST', `scheduled_job/update/${jobId}`, {
|
|
6134
|
+
name: options.name,
|
|
6135
|
+
schedule: options.schedule,
|
|
6136
|
+
task: options.task,
|
|
6137
|
+
});
|
|
6138
|
+
spinner.stop();
|
|
6139
|
+
|
|
6140
|
+
if (options.output === 'json') {
|
|
6141
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6142
|
+
} else {
|
|
6143
|
+
console.log(chalk.green.bold('✅ Scheduled job updated successfully!'));
|
|
6144
|
+
}
|
|
6145
|
+
} catch (error) {
|
|
6146
|
+
console.error(chalk.red(`Error updating scheduled job: ${error.message}`));
|
|
6147
|
+
process.exit(1);
|
|
6148
|
+
}
|
|
6149
|
+
});
|
|
6150
|
+
|
|
6151
|
+
program
|
|
6152
|
+
.command('delete-scheduled-job')
|
|
6153
|
+
.description('Delete a scheduled job')
|
|
6154
|
+
.argument('<id>', 'Scheduled Job ID')
|
|
6155
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
6156
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6157
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6158
|
+
.action(async (jobId, options, command) => {
|
|
6159
|
+
try {
|
|
6160
|
+
const globalOptions = command.parent.opts();
|
|
6161
|
+
const config = loadConfig(globalOptions.config);
|
|
6162
|
+
validateConfiguration(config);
|
|
6163
|
+
|
|
6164
|
+
if (!options.confirm) {
|
|
6165
|
+
const readline = require('readline');
|
|
6166
|
+
const rl = readline.createInterface({
|
|
6167
|
+
input: process.stdin,
|
|
6168
|
+
output: process.stdout,
|
|
6169
|
+
});
|
|
6170
|
+
|
|
6171
|
+
const answer = await new Promise((resolve) => {
|
|
6172
|
+
rl.question(
|
|
6173
|
+
chalk.yellow(`⚠️ Are you sure you want to delete scheduled job ${jobId}? (y/N): `),
|
|
6174
|
+
resolve
|
|
6175
|
+
);
|
|
6176
|
+
});
|
|
6177
|
+
rl.close();
|
|
6178
|
+
|
|
6179
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
6180
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
6181
|
+
process.exit(0);
|
|
6182
|
+
}
|
|
6183
|
+
}
|
|
6184
|
+
|
|
6185
|
+
const api = new ToothFairyAPI(
|
|
6186
|
+
config.baseUrl,
|
|
6187
|
+
config.aiUrl,
|
|
6188
|
+
config.aiStreamUrl,
|
|
6189
|
+
config.apiKey,
|
|
6190
|
+
config.workspaceId,
|
|
6191
|
+
globalOptions.verbose || options.verbose
|
|
6192
|
+
);
|
|
6193
|
+
|
|
6194
|
+
const spinner = ora('Deleting scheduled job...').start();
|
|
6195
|
+
const result = await api._makeRequest('DELETE', `scheduled_job/delete/${jobId}`);
|
|
6196
|
+
spinner.stop();
|
|
6197
|
+
|
|
6198
|
+
if (options.output === 'json') {
|
|
6199
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6200
|
+
} else {
|
|
6201
|
+
console.log(chalk.green.bold('✅ Scheduled job deleted successfully!'));
|
|
6202
|
+
}
|
|
6203
|
+
} catch (error) {
|
|
6204
|
+
console.error(chalk.red(`Error deleting scheduled job: ${error.message}`));
|
|
6205
|
+
process.exit(1);
|
|
6206
|
+
}
|
|
6207
|
+
});
|
|
6208
|
+
|
|
6209
|
+
// Authorisation commands
|
|
6210
|
+
program
|
|
6211
|
+
.command('create-authorisation')
|
|
6212
|
+
.description('Create a new authorisation')
|
|
6213
|
+
.option('--name <name>', 'Authorisation name')
|
|
6214
|
+
.option('--permissions <permissions>', 'Permissions (JSON)')
|
|
6215
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6216
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6217
|
+
.action(async (options, command) => {
|
|
6218
|
+
try {
|
|
6219
|
+
const globalOptions = command.parent.opts();
|
|
6220
|
+
const config = loadConfig(globalOptions.config);
|
|
6221
|
+
validateConfiguration(config);
|
|
6222
|
+
|
|
6223
|
+
const api = new ToothFairyAPI(
|
|
6224
|
+
config.baseUrl,
|
|
6225
|
+
config.aiUrl,
|
|
6226
|
+
config.aiStreamUrl,
|
|
6227
|
+
config.apiKey,
|
|
6228
|
+
config.workspaceId,
|
|
6229
|
+
globalOptions.verbose || options.verbose
|
|
6230
|
+
);
|
|
6231
|
+
|
|
6232
|
+
let permissions = {};
|
|
6233
|
+
if (options.permissions) {
|
|
6234
|
+
try {
|
|
6235
|
+
permissions = JSON.parse(options.permissions);
|
|
6236
|
+
} catch (e) {
|
|
6237
|
+
console.error(chalk.red('Invalid JSON in permissions'));
|
|
6238
|
+
process.exit(1);
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
|
|
6242
|
+
const spinner = ora('Creating authorisation...').start();
|
|
6243
|
+
const result = await api._makeRequest('POST', 'authorisation/create', {
|
|
6244
|
+
name: options.name,
|
|
6245
|
+
permissions,
|
|
6246
|
+
});
|
|
6247
|
+
spinner.stop();
|
|
6248
|
+
|
|
6249
|
+
if (options.output === 'json') {
|
|
6250
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6251
|
+
} else {
|
|
6252
|
+
console.log(chalk.green.bold('✅ Authorisation created successfully!'));
|
|
6253
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
6254
|
+
}
|
|
6255
|
+
} catch (error) {
|
|
6256
|
+
console.error(chalk.red(`Error creating authorisation: ${error.message}`));
|
|
6257
|
+
process.exit(1);
|
|
6258
|
+
}
|
|
6259
|
+
});
|
|
6260
|
+
|
|
6261
|
+
program
|
|
6262
|
+
.command('list-authorisations')
|
|
6263
|
+
.description('List all authorisations')
|
|
6264
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6265
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6266
|
+
.action(async (options, command) => {
|
|
6267
|
+
try {
|
|
6268
|
+
const globalOptions = command.parent.opts();
|
|
6269
|
+
const config = loadConfig(globalOptions.config);
|
|
6270
|
+
validateConfiguration(config);
|
|
6271
|
+
|
|
6272
|
+
const api = new ToothFairyAPI(
|
|
6273
|
+
config.baseUrl,
|
|
6274
|
+
config.aiUrl,
|
|
6275
|
+
config.aiStreamUrl,
|
|
6276
|
+
config.apiKey,
|
|
6277
|
+
config.workspaceId,
|
|
6278
|
+
globalOptions.verbose || options.verbose
|
|
6279
|
+
);
|
|
6280
|
+
|
|
6281
|
+
const spinner = ora('Fetching authorisations...').start();
|
|
6282
|
+
const result = await api._makeRequest('GET', 'authorisation/list');
|
|
6283
|
+
spinner.stop();
|
|
6284
|
+
|
|
6285
|
+
if (options.output === 'json') {
|
|
6286
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6287
|
+
} else {
|
|
6288
|
+
const auths = Array.isArray(result) ? result : result.items || [];
|
|
6289
|
+
console.log(chalk.green.bold(`Found ${auths.length} authorisation(s)`));
|
|
6290
|
+
auths.forEach(a => {
|
|
6291
|
+
console.log(chalk.cyan(` • ${a.name || 'Unnamed'} (${a.id})`));
|
|
6292
|
+
});
|
|
6293
|
+
}
|
|
6294
|
+
} catch (error) {
|
|
6295
|
+
console.error(chalk.red(`Error listing authorisations: ${error.message}`));
|
|
6296
|
+
process.exit(1);
|
|
6297
|
+
}
|
|
6298
|
+
});
|
|
6299
|
+
|
|
6300
|
+
program
|
|
6301
|
+
.command('get-authorisation')
|
|
6302
|
+
.description('Get details of a specific authorisation')
|
|
6303
|
+
.argument('<id>', 'Authorisation ID')
|
|
6304
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6305
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6306
|
+
.action(async (authId, options, command) => {
|
|
6307
|
+
try {
|
|
6308
|
+
const globalOptions = command.parent.opts();
|
|
6309
|
+
const config = loadConfig(globalOptions.config);
|
|
6310
|
+
validateConfiguration(config);
|
|
6311
|
+
|
|
6312
|
+
const api = new ToothFairyAPI(
|
|
6313
|
+
config.baseUrl,
|
|
6314
|
+
config.aiUrl,
|
|
6315
|
+
config.aiStreamUrl,
|
|
6316
|
+
config.apiKey,
|
|
6317
|
+
config.workspaceId,
|
|
6318
|
+
globalOptions.verbose || options.verbose
|
|
6319
|
+
);
|
|
6320
|
+
|
|
6321
|
+
const spinner = ora('Fetching authorisation...').start();
|
|
6322
|
+
const result = await api._makeRequest('GET', `authorisation/get/${authId}`);
|
|
6323
|
+
spinner.stop();
|
|
6324
|
+
|
|
6325
|
+
if (options.output === 'json') {
|
|
6326
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6327
|
+
} else {
|
|
6328
|
+
console.log(chalk.green.bold('Authorisation Details'));
|
|
6329
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
6330
|
+
}
|
|
6331
|
+
} catch (error) {
|
|
6332
|
+
console.error(chalk.red(`Error getting authorisation: ${error.message}`));
|
|
6333
|
+
process.exit(1);
|
|
6334
|
+
}
|
|
6335
|
+
});
|
|
6336
|
+
|
|
6337
|
+
program
|
|
6338
|
+
.command('update-authorisation')
|
|
6339
|
+
.description('Update an existing authorisation')
|
|
6340
|
+
.argument('<id>', 'Authorisation ID')
|
|
6341
|
+
.option('--name <name>', 'Authorisation name')
|
|
6342
|
+
.option('--permissions <permissions>', 'Permissions (JSON)')
|
|
6343
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6344
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6345
|
+
.action(async (authId, options, command) => {
|
|
6346
|
+
try {
|
|
6347
|
+
const globalOptions = command.parent.opts();
|
|
6348
|
+
const config = loadConfig(globalOptions.config);
|
|
6349
|
+
validateConfiguration(config);
|
|
6350
|
+
|
|
6351
|
+
const api = new ToothFairyAPI(
|
|
6352
|
+
config.baseUrl,
|
|
6353
|
+
config.aiUrl,
|
|
6354
|
+
config.aiStreamUrl,
|
|
6355
|
+
config.apiKey,
|
|
6356
|
+
config.workspaceId,
|
|
6357
|
+
globalOptions.verbose || options.verbose
|
|
6358
|
+
);
|
|
6359
|
+
|
|
6360
|
+
let permissions = {};
|
|
6361
|
+
if (options.permissions) {
|
|
6362
|
+
try {
|
|
6363
|
+
permissions = JSON.parse(options.permissions);
|
|
6364
|
+
} catch (e) {
|
|
6365
|
+
console.error(chalk.red('Invalid JSON in permissions'));
|
|
6366
|
+
process.exit(1);
|
|
6367
|
+
}
|
|
6368
|
+
}
|
|
6369
|
+
|
|
6370
|
+
const spinner = ora('Updating authorisation...').start();
|
|
6371
|
+
const result = await api._makeRequest('POST', 'authorisation/update', {
|
|
6372
|
+
id: authId,
|
|
6373
|
+
name: options.name,
|
|
6374
|
+
permissions,
|
|
6375
|
+
});
|
|
6376
|
+
spinner.stop();
|
|
6377
|
+
|
|
6378
|
+
if (options.output === 'json') {
|
|
6379
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6380
|
+
} else {
|
|
6381
|
+
console.log(chalk.green.bold('✅ Authorisation updated successfully!'));
|
|
6382
|
+
}
|
|
6383
|
+
} catch (error) {
|
|
6384
|
+
console.error(chalk.red(`Error updating authorisation: ${error.message}`));
|
|
6385
|
+
process.exit(1);
|
|
6386
|
+
}
|
|
6387
|
+
});
|
|
6388
|
+
|
|
6389
|
+
program
|
|
6390
|
+
.command('delete-authorisation')
|
|
6391
|
+
.description('Delete an authorisation')
|
|
6392
|
+
.argument('<id>', 'Authorisation ID')
|
|
6393
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
6394
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6395
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6396
|
+
.action(async (authId, options, command) => {
|
|
6397
|
+
try {
|
|
6398
|
+
const globalOptions = command.parent.opts();
|
|
6399
|
+
const config = loadConfig(globalOptions.config);
|
|
6400
|
+
validateConfiguration(config);
|
|
6401
|
+
|
|
6402
|
+
if (!options.confirm) {
|
|
6403
|
+
const readline = require('readline');
|
|
6404
|
+
const rl = readline.createInterface({
|
|
6405
|
+
input: process.stdin,
|
|
6406
|
+
output: process.stdout,
|
|
6407
|
+
});
|
|
6408
|
+
|
|
6409
|
+
const answer = await new Promise((resolve) => {
|
|
6410
|
+
rl.question(
|
|
6411
|
+
chalk.yellow(`⚠️ Are you sure you want to delete authorisation ${authId}? (y/N): `),
|
|
6412
|
+
resolve
|
|
6413
|
+
);
|
|
6414
|
+
});
|
|
6415
|
+
rl.close();
|
|
6416
|
+
|
|
6417
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
6418
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
6419
|
+
process.exit(0);
|
|
6420
|
+
}
|
|
6421
|
+
}
|
|
6422
|
+
|
|
6423
|
+
const api = new ToothFairyAPI(
|
|
6424
|
+
config.baseUrl,
|
|
6425
|
+
config.aiUrl,
|
|
6426
|
+
config.aiStreamUrl,
|
|
6427
|
+
config.apiKey,
|
|
6428
|
+
config.workspaceId,
|
|
6429
|
+
globalOptions.verbose || options.verbose
|
|
6430
|
+
);
|
|
6431
|
+
|
|
6432
|
+
const spinner = ora('Deleting authorisation...').start();
|
|
6433
|
+
const result = await api._makeRequest('DELETE', `authorisation/delete/${authId}`);
|
|
6434
|
+
spinner.stop();
|
|
6435
|
+
|
|
6436
|
+
if (options.output === 'json') {
|
|
6437
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6438
|
+
} else {
|
|
6439
|
+
console.log(chalk.green.bold('✅ Authorisation deleted successfully!'));
|
|
6440
|
+
}
|
|
6441
|
+
} catch (error) {
|
|
6442
|
+
console.error(chalk.red(`Error deleting authorisation: ${error.message}`));
|
|
6443
|
+
process.exit(1);
|
|
6444
|
+
}
|
|
6445
|
+
});
|
|
6446
|
+
|
|
6447
|
+
// Charting Settings commands
|
|
6448
|
+
program
|
|
6449
|
+
.command('get-charting-settings')
|
|
6450
|
+
.description('Get charting settings')
|
|
6451
|
+
.argument('<id>', 'Settings ID')
|
|
6452
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6453
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6454
|
+
.action(async (settingsId, options, command) => {
|
|
6455
|
+
try {
|
|
6456
|
+
const globalOptions = command.parent.opts();
|
|
6457
|
+
const config = loadConfig(globalOptions.config);
|
|
6458
|
+
validateConfiguration(config);
|
|
6459
|
+
|
|
6460
|
+
const api = new ToothFairyAPI(
|
|
6461
|
+
config.baseUrl,
|
|
6462
|
+
config.aiUrl,
|
|
6463
|
+
config.aiStreamUrl,
|
|
6464
|
+
config.apiKey,
|
|
6465
|
+
config.workspaceId,
|
|
6466
|
+
globalOptions.verbose || options.verbose
|
|
6467
|
+
);
|
|
6468
|
+
|
|
6469
|
+
const spinner = ora('Fetching charting settings...').start();
|
|
6470
|
+
const result = await api._makeRequest('GET', `charting_settings/get/${settingsId}`);
|
|
6471
|
+
spinner.stop();
|
|
6472
|
+
|
|
6473
|
+
if (options.output === 'json') {
|
|
6474
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6475
|
+
} else {
|
|
6476
|
+
console.log(chalk.green.bold('Charting Settings'));
|
|
6477
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
6478
|
+
}
|
|
6479
|
+
} catch (error) {
|
|
6480
|
+
console.error(chalk.red(`Error getting charting settings: ${error.message}`));
|
|
6481
|
+
process.exit(1);
|
|
6482
|
+
}
|
|
6483
|
+
});
|
|
6484
|
+
|
|
6485
|
+
program
|
|
6486
|
+
.command('update-charting-settings')
|
|
6487
|
+
.description('Update charting settings')
|
|
6488
|
+
.argument('<id>', 'Settings ID')
|
|
6489
|
+
.option('--config <config>', 'Settings configuration (JSON)')
|
|
6490
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6491
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6492
|
+
.action(async (settingsId, options, command) => {
|
|
6493
|
+
try {
|
|
6494
|
+
const globalOptions = command.parent.opts();
|
|
6495
|
+
const config = loadConfig(globalOptions.config);
|
|
6496
|
+
validateConfiguration(config);
|
|
6497
|
+
|
|
6498
|
+
const api = new ToothFairyAPI(
|
|
6499
|
+
config.baseUrl,
|
|
6500
|
+
config.aiUrl,
|
|
6501
|
+
config.aiStreamUrl,
|
|
6502
|
+
config.apiKey,
|
|
6503
|
+
config.workspaceId,
|
|
6504
|
+
globalOptions.verbose || options.verbose
|
|
6505
|
+
);
|
|
6506
|
+
|
|
6507
|
+
let settingsConfig = {};
|
|
6508
|
+
if (options.config) {
|
|
6509
|
+
try {
|
|
6510
|
+
settingsConfig = JSON.parse(options.config);
|
|
6511
|
+
} catch (e) {
|
|
6512
|
+
console.error(chalk.red('Invalid JSON in config'));
|
|
6513
|
+
process.exit(1);
|
|
6514
|
+
}
|
|
6515
|
+
}
|
|
6516
|
+
|
|
6517
|
+
const spinner = ora('Updating charting settings...').start();
|
|
6518
|
+
const result = await api._makeRequest('POST', 'charting_settings/update', {
|
|
6519
|
+
id: settingsId,
|
|
6520
|
+
...settingsConfig,
|
|
6521
|
+
});
|
|
6522
|
+
spinner.stop();
|
|
6523
|
+
|
|
6524
|
+
if (options.output === 'json') {
|
|
6525
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6526
|
+
} else {
|
|
6527
|
+
console.log(chalk.green.bold('✅ Charting settings updated successfully!'));
|
|
6528
|
+
}
|
|
6529
|
+
} catch (error) {
|
|
6530
|
+
console.error(chalk.red(`Error updating charting settings: ${error.message}`));
|
|
6531
|
+
process.exit(1);
|
|
6532
|
+
}
|
|
6533
|
+
});
|
|
6534
|
+
|
|
6535
|
+
// Embeddings Settings commands
|
|
6536
|
+
program
|
|
6537
|
+
.command('get-embeddings-settings')
|
|
6538
|
+
.description('Get embeddings settings')
|
|
6539
|
+
.argument('<id>', 'Settings ID')
|
|
6540
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6541
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6542
|
+
.action(async (settingsId, options, command) => {
|
|
6543
|
+
try {
|
|
6544
|
+
const globalOptions = command.parent.opts();
|
|
6545
|
+
const config = loadConfig(globalOptions.config);
|
|
6546
|
+
validateConfiguration(config);
|
|
6547
|
+
|
|
6548
|
+
const api = new ToothFairyAPI(
|
|
6549
|
+
config.baseUrl,
|
|
6550
|
+
config.aiUrl,
|
|
6551
|
+
config.aiStreamUrl,
|
|
6552
|
+
config.apiKey,
|
|
6553
|
+
config.workspaceId,
|
|
6554
|
+
globalOptions.verbose || options.verbose
|
|
6555
|
+
);
|
|
6556
|
+
|
|
6557
|
+
const spinner = ora('Fetching embeddings settings...').start();
|
|
6558
|
+
const result = await api._makeRequest('GET', `embeddings_settings/get/${settingsId}`);
|
|
6559
|
+
spinner.stop();
|
|
6560
|
+
|
|
6561
|
+
if (options.output === 'json') {
|
|
6562
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6563
|
+
} else {
|
|
6564
|
+
console.log(chalk.green.bold('Embeddings Settings'));
|
|
6565
|
+
console.log(chalk.dim(JSON.stringify(result, null, 2)));
|
|
6566
|
+
}
|
|
6567
|
+
} catch (error) {
|
|
6568
|
+
console.error(chalk.red(`Error getting embeddings settings: ${error.message}`));
|
|
6569
|
+
process.exit(1);
|
|
6570
|
+
}
|
|
6571
|
+
});
|
|
6572
|
+
|
|
6573
|
+
program
|
|
6574
|
+
.command('update-embeddings-settings')
|
|
6575
|
+
.description('Update embeddings settings')
|
|
6576
|
+
.argument('<id>', 'Settings ID')
|
|
6577
|
+
.option('--config <config>', 'Settings configuration (JSON)')
|
|
6578
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6579
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6580
|
+
.action(async (settingsId, options, command) => {
|
|
6581
|
+
try {
|
|
6582
|
+
const globalOptions = command.parent.opts();
|
|
6583
|
+
const config = loadConfig(globalOptions.config);
|
|
6584
|
+
validateConfiguration(config);
|
|
6585
|
+
|
|
6586
|
+
const api = new ToothFairyAPI(
|
|
6587
|
+
config.baseUrl,
|
|
6588
|
+
config.aiUrl,
|
|
6589
|
+
config.aiStreamUrl,
|
|
6590
|
+
config.apiKey,
|
|
6591
|
+
config.workspaceId,
|
|
6592
|
+
globalOptions.verbose || options.verbose
|
|
6593
|
+
);
|
|
6594
|
+
|
|
6595
|
+
let settingsConfig = {};
|
|
6596
|
+
if (options.config) {
|
|
6597
|
+
try {
|
|
6598
|
+
settingsConfig = JSON.parse(options.config);
|
|
6599
|
+
} catch (e) {
|
|
6600
|
+
console.error(chalk.red('Invalid JSON in config'));
|
|
6601
|
+
process.exit(1);
|
|
6602
|
+
}
|
|
6603
|
+
}
|
|
6604
|
+
|
|
6605
|
+
const spinner = ora('Updating embeddings settings...').start();
|
|
6606
|
+
const result = await api._makeRequest('POST', 'embeddings_settings/update', {
|
|
6607
|
+
id: settingsId,
|
|
6608
|
+
...settingsConfig,
|
|
6609
|
+
});
|
|
6610
|
+
spinner.stop();
|
|
6611
|
+
|
|
6612
|
+
if (options.output === 'json') {
|
|
6613
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6614
|
+
} else {
|
|
6615
|
+
console.log(chalk.green.bold('✅ Embeddings settings updated successfully!'));
|
|
6616
|
+
}
|
|
6617
|
+
} catch (error) {
|
|
6618
|
+
console.error(chalk.red(`Error updating embeddings settings: ${error.message}`));
|
|
6619
|
+
process.exit(1);
|
|
6620
|
+
}
|
|
6621
|
+
});
|
|
6622
|
+
|
|
6623
|
+
// Authorization Management Commands
|
|
6624
|
+
program
|
|
6625
|
+
.command('create-authorization')
|
|
6626
|
+
.description('Create a new authorization')
|
|
6627
|
+
.option('--name <name>', 'Authorization name')
|
|
6628
|
+
.option('--type <type>', 'Authorization type')
|
|
6629
|
+
.option('--config <config>', 'Authorization configuration (JSON)')
|
|
6630
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6631
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6632
|
+
.action(async (options, command) => {
|
|
6633
|
+
try {
|
|
6634
|
+
const globalOptions = command.parent.opts();
|
|
6635
|
+
const config = loadConfig(globalOptions.config);
|
|
6636
|
+
validateConfiguration(config);
|
|
6637
|
+
|
|
6638
|
+
const api = new ToothFairyAPI(
|
|
6639
|
+
config.baseUrl,
|
|
6640
|
+
config.aiUrl,
|
|
6641
|
+
config.aiStreamUrl,
|
|
6642
|
+
config.apiKey,
|
|
6643
|
+
config.workspaceId,
|
|
6644
|
+
globalOptions.verbose || options.verbose
|
|
6645
|
+
);
|
|
6646
|
+
|
|
6647
|
+
let authConfig = {};
|
|
6648
|
+
if (options.config) {
|
|
6649
|
+
try {
|
|
6650
|
+
authConfig = JSON.parse(options.config);
|
|
6651
|
+
} catch (e) {
|
|
6652
|
+
console.error(chalk.red('Invalid JSON in config'));
|
|
6653
|
+
process.exit(1);
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
|
|
6657
|
+
const spinner = ora('Creating authorization...').start();
|
|
6658
|
+
const result = await api.createAuthorization({
|
|
6659
|
+
name: options.name,
|
|
6660
|
+
type: options.type,
|
|
6661
|
+
...authConfig,
|
|
6662
|
+
});
|
|
6663
|
+
spinner.stop();
|
|
6664
|
+
|
|
6665
|
+
if (options.output === 'json') {
|
|
6666
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6667
|
+
} else {
|
|
6668
|
+
console.log(chalk.green.bold('✅ Authorization created successfully!'));
|
|
6669
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
6670
|
+
}
|
|
6671
|
+
} catch (error) {
|
|
6672
|
+
console.error(chalk.red(`Error creating authorization: ${error.message}`));
|
|
6673
|
+
process.exit(1);
|
|
6674
|
+
}
|
|
6675
|
+
});
|
|
6676
|
+
|
|
6677
|
+
program
|
|
6678
|
+
.command('list-authorizations')
|
|
6679
|
+
.description('List all authorizations')
|
|
6680
|
+
.option('--limit <number>', 'Maximum number to return', '50')
|
|
6681
|
+
.option('--offset <number>', 'Number to skip', '0')
|
|
6682
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6683
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6684
|
+
.action(async (options, command) => {
|
|
6685
|
+
try {
|
|
6686
|
+
const globalOptions = command.parent.opts();
|
|
6687
|
+
const config = loadConfig(globalOptions.config);
|
|
6688
|
+
validateConfiguration(config);
|
|
6689
|
+
|
|
6690
|
+
const api = new ToothFairyAPI(
|
|
6691
|
+
config.baseUrl,
|
|
6692
|
+
config.aiUrl,
|
|
6693
|
+
config.aiStreamUrl,
|
|
6694
|
+
config.apiKey,
|
|
6695
|
+
config.workspaceId,
|
|
6696
|
+
globalOptions.verbose || options.verbose
|
|
6697
|
+
);
|
|
6698
|
+
|
|
6699
|
+
const spinner = ora('Fetching authorizations...').start();
|
|
6700
|
+
const result = await api.listAuthorizations(
|
|
6701
|
+
parseInt(options.limit),
|
|
6702
|
+
parseInt(options.offset)
|
|
6703
|
+
);
|
|
6704
|
+
spinner.stop();
|
|
6705
|
+
|
|
6706
|
+
if (options.output === 'json') {
|
|
6707
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6708
|
+
} else {
|
|
6709
|
+
const auths = Array.isArray(result) ? result : result.items || [];
|
|
6710
|
+
console.log(chalk.green.bold(`Found ${auths.length} authorization(s)`));
|
|
6711
|
+
auths.forEach(auth => {
|
|
6712
|
+
console.log(chalk.cyan(` • ${auth.name || 'Unnamed'} (${auth.id})`));
|
|
6713
|
+
});
|
|
6714
|
+
}
|
|
6715
|
+
} catch (error) {
|
|
6716
|
+
console.error(chalk.red(`Error listing authorizations: ${error.message}`));
|
|
6717
|
+
process.exit(1);
|
|
6718
|
+
}
|
|
6719
|
+
});
|
|
6720
|
+
|
|
6721
|
+
program
|
|
6722
|
+
.command('get-authorization')
|
|
6723
|
+
.description('Get details of a specific authorization')
|
|
6724
|
+
.argument('<id>', 'Authorization ID')
|
|
6725
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6726
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6727
|
+
.action(async (authId, options, command) => {
|
|
6728
|
+
try {
|
|
6729
|
+
const globalOptions = command.parent.opts();
|
|
6730
|
+
const config = loadConfig(globalOptions.config);
|
|
6731
|
+
validateConfiguration(config);
|
|
6732
|
+
|
|
6733
|
+
const api = new ToothFairyAPI(
|
|
6734
|
+
config.baseUrl,
|
|
6735
|
+
config.aiUrl,
|
|
6736
|
+
config.aiStreamUrl,
|
|
6737
|
+
config.apiKey,
|
|
6738
|
+
config.workspaceId,
|
|
6739
|
+
globalOptions.verbose || options.verbose
|
|
6740
|
+
);
|
|
6741
|
+
|
|
6742
|
+
const spinner = ora('Fetching authorization...').start();
|
|
6743
|
+
const result = await api.getAuthorization(authId);
|
|
6744
|
+
spinner.stop();
|
|
6745
|
+
|
|
6746
|
+
if (options.output === 'json') {
|
|
6747
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6748
|
+
} else {
|
|
6749
|
+
console.log(chalk.green.bold('Authorization Details'));
|
|
6750
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
6751
|
+
console.log(chalk.dim(`Name: ${result.name || 'N/A'}`));
|
|
6752
|
+
console.log(chalk.dim(`Type: ${result.type || 'N/A'}`));
|
|
6753
|
+
}
|
|
6754
|
+
} catch (error) {
|
|
6755
|
+
console.error(chalk.red(`Error getting authorization: ${error.message}`));
|
|
6756
|
+
process.exit(1);
|
|
6757
|
+
}
|
|
6758
|
+
});
|
|
6759
|
+
|
|
6760
|
+
program
|
|
6761
|
+
.command('update-authorization')
|
|
6762
|
+
.description('Update an existing authorization')
|
|
6763
|
+
.option('--id <id>', 'Authorization ID')
|
|
6764
|
+
.option('--name <name>', 'Authorization name')
|
|
6765
|
+
.option('--type <type>', 'Authorization type')
|
|
6766
|
+
.option('--config <config>', 'Authorization configuration (JSON)')
|
|
6767
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6768
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6769
|
+
.action(async (options, command) => {
|
|
6770
|
+
try {
|
|
6771
|
+
const globalOptions = command.parent.opts();
|
|
6772
|
+
const config = loadConfig(globalOptions.config);
|
|
6773
|
+
validateConfiguration(config);
|
|
6774
|
+
|
|
6775
|
+
const api = new ToothFairyAPI(
|
|
6776
|
+
config.baseUrl,
|
|
6777
|
+
config.aiUrl,
|
|
6778
|
+
config.aiStreamUrl,
|
|
6779
|
+
config.apiKey,
|
|
6780
|
+
config.workspaceId,
|
|
6781
|
+
globalOptions.verbose || options.verbose
|
|
6782
|
+
);
|
|
6783
|
+
|
|
6784
|
+
let authConfig = {};
|
|
6785
|
+
if (options.config) {
|
|
6786
|
+
try {
|
|
6787
|
+
authConfig = JSON.parse(options.config);
|
|
6788
|
+
} catch (e) {
|
|
6789
|
+
console.error(chalk.red('Invalid JSON in config'));
|
|
6790
|
+
process.exit(1);
|
|
6791
|
+
}
|
|
6792
|
+
}
|
|
6793
|
+
|
|
6794
|
+
const spinner = ora('Updating authorization...').start();
|
|
6795
|
+
const result = await api.updateAuthorization({
|
|
6796
|
+
id: options.id,
|
|
6797
|
+
name: options.name,
|
|
6798
|
+
type: options.type,
|
|
6799
|
+
...authConfig,
|
|
6800
|
+
});
|
|
6801
|
+
spinner.stop();
|
|
6802
|
+
|
|
6803
|
+
if (options.output === 'json') {
|
|
6804
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6805
|
+
} else {
|
|
6806
|
+
console.log(chalk.green.bold('✅ Authorization updated successfully!'));
|
|
6807
|
+
}
|
|
6808
|
+
} catch (error) {
|
|
6809
|
+
console.error(chalk.red(`Error updating authorization: ${error.message}`));
|
|
6810
|
+
process.exit(1);
|
|
6811
|
+
}
|
|
6812
|
+
});
|
|
6813
|
+
|
|
6814
|
+
program
|
|
6815
|
+
.command('delete-authorization')
|
|
6816
|
+
.description('Delete an authorization')
|
|
6817
|
+
.argument('<id>', 'Authorization ID')
|
|
6818
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
6819
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6820
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6821
|
+
.action(async (authId, options, command) => {
|
|
6822
|
+
try {
|
|
6823
|
+
const globalOptions = command.parent.opts();
|
|
6824
|
+
const config = loadConfig(globalOptions.config);
|
|
6825
|
+
validateConfiguration(config);
|
|
6826
|
+
|
|
6827
|
+
const api = new ToothFairyAPI(
|
|
6828
|
+
config.baseUrl,
|
|
6829
|
+
config.aiUrl,
|
|
6830
|
+
config.aiStreamUrl,
|
|
6831
|
+
config.apiKey,
|
|
6832
|
+
config.workspaceId,
|
|
6833
|
+
globalOptions.verbose || options.verbose
|
|
6834
|
+
);
|
|
6835
|
+
|
|
6836
|
+
if (!options.confirm) {
|
|
6837
|
+
const readline = require('readline');
|
|
6838
|
+
const rl = readline.createInterface({
|
|
6839
|
+
input: process.stdin,
|
|
6840
|
+
output: process.stdout,
|
|
6841
|
+
});
|
|
6842
|
+
|
|
6843
|
+
const answer = await new Promise((resolve) => {
|
|
6844
|
+
rl.question(
|
|
6845
|
+
chalk.yellow(
|
|
6846
|
+
`⚠️ Are you sure you want to delete authorization ${authId}? (y/N): `
|
|
6847
|
+
),
|
|
6848
|
+
resolve
|
|
6849
|
+
);
|
|
6850
|
+
});
|
|
6851
|
+
|
|
6852
|
+
rl.close();
|
|
6853
|
+
|
|
6854
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
6855
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
6856
|
+
process.exit(0);
|
|
6857
|
+
}
|
|
6858
|
+
}
|
|
6859
|
+
|
|
6860
|
+
const spinner = ora('Deleting authorization...').start();
|
|
6861
|
+
const result = await api.deleteAuthorization(authId);
|
|
6862
|
+
spinner.stop();
|
|
6863
|
+
|
|
6864
|
+
if (options.output === 'json') {
|
|
6865
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6866
|
+
} else {
|
|
6867
|
+
console.log(chalk.green.bold('✅ Authorization deleted successfully!'));
|
|
6868
|
+
}
|
|
6869
|
+
} catch (error) {
|
|
6870
|
+
console.error(chalk.red(`Error deleting authorization: ${error.message}`));
|
|
6871
|
+
process.exit(1);
|
|
6872
|
+
}
|
|
6873
|
+
});
|
|
6874
|
+
|
|
6875
|
+
// Benchmark Management Commands
|
|
6876
|
+
program
|
|
6877
|
+
.command('create-benchmark')
|
|
6878
|
+
.description('Create a new benchmark')
|
|
6879
|
+
.option('--name <name>', 'Benchmark name')
|
|
6880
|
+
.option('--description <description>', 'Benchmark description')
|
|
6881
|
+
.option('--questions <questions>', 'Questions JSON array')
|
|
6882
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6883
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6884
|
+
.action(async (options, command) => {
|
|
6885
|
+
try {
|
|
6886
|
+
const globalOptions = command.parent.opts();
|
|
6887
|
+
const config = loadConfig(globalOptions.config);
|
|
6888
|
+
validateConfiguration(config);
|
|
6889
|
+
|
|
6890
|
+
const api = new ToothFairyAPI(
|
|
6891
|
+
config.baseUrl,
|
|
6892
|
+
config.aiUrl,
|
|
6893
|
+
config.aiStreamUrl,
|
|
6894
|
+
config.apiKey,
|
|
6895
|
+
config.workspaceId,
|
|
6896
|
+
globalOptions.verbose || options.verbose
|
|
6897
|
+
);
|
|
6898
|
+
|
|
6899
|
+
let questions = [];
|
|
6900
|
+
if (options.questions) {
|
|
6901
|
+
try {
|
|
6902
|
+
questions = JSON.parse(options.questions);
|
|
6903
|
+
} catch (e) {
|
|
6904
|
+
console.error(chalk.red('Invalid JSON in questions'));
|
|
6905
|
+
process.exit(1);
|
|
6906
|
+
}
|
|
6907
|
+
}
|
|
6908
|
+
|
|
6909
|
+
const spinner = ora('Creating benchmark...').start();
|
|
6910
|
+
const result = await api.createBenchmark({
|
|
6911
|
+
name: options.name,
|
|
6912
|
+
description: options.description,
|
|
6913
|
+
questions: questions,
|
|
6914
|
+
});
|
|
6915
|
+
spinner.stop();
|
|
6916
|
+
|
|
6917
|
+
if (options.output === 'json') {
|
|
6918
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6919
|
+
} else {
|
|
6920
|
+
console.log(chalk.green.bold('✅ Benchmark created successfully!'));
|
|
6921
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
6922
|
+
}
|
|
6923
|
+
} catch (error) {
|
|
6924
|
+
console.error(chalk.red(`Error creating benchmark: ${error.message}`));
|
|
6925
|
+
process.exit(1);
|
|
6926
|
+
}
|
|
6927
|
+
});
|
|
6928
|
+
|
|
6929
|
+
program
|
|
6930
|
+
.command('list-benchmarks')
|
|
6931
|
+
.description('List all benchmarks')
|
|
6932
|
+
.option('--limit <number>', 'Maximum number to return', '50')
|
|
6933
|
+
.option('--offset <number>', 'Number to skip', '0')
|
|
6934
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6935
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6936
|
+
.action(async (options, command) => {
|
|
6937
|
+
try {
|
|
6938
|
+
const globalOptions = command.parent.opts();
|
|
6939
|
+
const config = loadConfig(globalOptions.config);
|
|
6940
|
+
validateConfiguration(config);
|
|
6941
|
+
|
|
6942
|
+
const api = new ToothFairyAPI(
|
|
6943
|
+
config.baseUrl,
|
|
6944
|
+
config.aiUrl,
|
|
6945
|
+
config.aiStreamUrl,
|
|
6946
|
+
config.apiKey,
|
|
6947
|
+
config.workspaceId,
|
|
6948
|
+
globalOptions.verbose || options.verbose
|
|
6949
|
+
);
|
|
6950
|
+
|
|
6951
|
+
const spinner = ora('Fetching benchmarks...').start();
|
|
6952
|
+
const result = await api.listBenchmarks(
|
|
6953
|
+
parseInt(options.limit),
|
|
6954
|
+
parseInt(options.offset)
|
|
6955
|
+
);
|
|
6956
|
+
spinner.stop();
|
|
6957
|
+
|
|
6958
|
+
if (options.output === 'json') {
|
|
6959
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6960
|
+
} else {
|
|
6961
|
+
const benchmarks = Array.isArray(result) ? result : result.items || [];
|
|
6962
|
+
console.log(chalk.green.bold(`Found ${benchmarks.length} benchmark(s)`));
|
|
6963
|
+
benchmarks.forEach(bm => {
|
|
6964
|
+
console.log(chalk.cyan(` • ${bm.name || 'Unnamed'} (${bm.id})`));
|
|
6965
|
+
});
|
|
6966
|
+
}
|
|
6967
|
+
} catch (error) {
|
|
6968
|
+
console.error(chalk.red(`Error listing benchmarks: ${error.message}`));
|
|
6969
|
+
process.exit(1);
|
|
6970
|
+
}
|
|
6971
|
+
});
|
|
6972
|
+
|
|
6973
|
+
program
|
|
6974
|
+
.command('get-benchmark')
|
|
6975
|
+
.description('Get details of a specific benchmark')
|
|
6976
|
+
.argument('<id>', 'Benchmark ID')
|
|
6977
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
6978
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
6979
|
+
.action(async (benchmarkId, options, command) => {
|
|
6980
|
+
try {
|
|
6981
|
+
const globalOptions = command.parent.opts();
|
|
6982
|
+
const config = loadConfig(globalOptions.config);
|
|
6983
|
+
validateConfiguration(config);
|
|
6984
|
+
|
|
6985
|
+
const api = new ToothFairyAPI(
|
|
6986
|
+
config.baseUrl,
|
|
6987
|
+
config.aiUrl,
|
|
6988
|
+
config.aiStreamUrl,
|
|
6989
|
+
config.apiKey,
|
|
6990
|
+
config.workspaceId,
|
|
6991
|
+
globalOptions.verbose || options.verbose
|
|
6992
|
+
);
|
|
6993
|
+
|
|
6994
|
+
const spinner = ora('Fetching benchmark...').start();
|
|
6995
|
+
const result = await api.getBenchmark(benchmarkId);
|
|
6996
|
+
spinner.stop();
|
|
6997
|
+
|
|
6998
|
+
if (options.output === 'json') {
|
|
6999
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7000
|
+
} else {
|
|
7001
|
+
console.log(chalk.green.bold('Benchmark Details'));
|
|
7002
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
7003
|
+
console.log(chalk.dim(`Name: ${result.name || 'N/A'}`));
|
|
7004
|
+
console.log(chalk.dim(`Description: ${result.description || 'N/A'}`));
|
|
7005
|
+
}
|
|
7006
|
+
} catch (error) {
|
|
7007
|
+
console.error(chalk.red(`Error getting benchmark: ${error.message}`));
|
|
7008
|
+
process.exit(1);
|
|
7009
|
+
}
|
|
7010
|
+
});
|
|
7011
|
+
|
|
7012
|
+
program
|
|
7013
|
+
.command('update-benchmark')
|
|
7014
|
+
.description('Update an existing benchmark')
|
|
7015
|
+
.option('--id <id>', 'Benchmark ID')
|
|
7016
|
+
.option('--name <name>', 'Benchmark name')
|
|
7017
|
+
.option('--description <description>', 'Benchmark description')
|
|
7018
|
+
.option('--questions <questions>', 'Questions JSON array')
|
|
7019
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7020
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7021
|
+
.action(async (options, command) => {
|
|
7022
|
+
try {
|
|
7023
|
+
const globalOptions = command.parent.opts();
|
|
7024
|
+
const config = loadConfig(globalOptions.config);
|
|
7025
|
+
validateConfiguration(config);
|
|
7026
|
+
|
|
7027
|
+
const api = new ToothFairyAPI(
|
|
7028
|
+
config.baseUrl,
|
|
7029
|
+
config.aiUrl,
|
|
7030
|
+
config.aiStreamUrl,
|
|
7031
|
+
config.apiKey,
|
|
7032
|
+
config.workspaceId,
|
|
7033
|
+
globalOptions.verbose || options.verbose
|
|
7034
|
+
);
|
|
7035
|
+
|
|
7036
|
+
let questions = undefined;
|
|
7037
|
+
if (options.questions) {
|
|
7038
|
+
try {
|
|
7039
|
+
questions = JSON.parse(options.questions);
|
|
7040
|
+
} catch (e) {
|
|
7041
|
+
console.error(chalk.red('Invalid JSON in questions'));
|
|
7042
|
+
process.exit(1);
|
|
7043
|
+
}
|
|
7044
|
+
}
|
|
7045
|
+
|
|
7046
|
+
const spinner = ora('Updating benchmark...').start();
|
|
7047
|
+
const result = await api.updateBenchmark({
|
|
7048
|
+
id: options.id,
|
|
7049
|
+
name: options.name,
|
|
7050
|
+
description: options.description,
|
|
7051
|
+
questions: questions,
|
|
7052
|
+
});
|
|
7053
|
+
spinner.stop();
|
|
7054
|
+
|
|
7055
|
+
if (options.output === 'json') {
|
|
7056
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7057
|
+
} else {
|
|
7058
|
+
console.log(chalk.green.bold('✅ Benchmark updated successfully!'));
|
|
7059
|
+
}
|
|
7060
|
+
} catch (error) {
|
|
7061
|
+
console.error(chalk.red(`Error updating benchmark: ${error.message}`));
|
|
7062
|
+
process.exit(1);
|
|
7063
|
+
}
|
|
7064
|
+
});
|
|
7065
|
+
|
|
7066
|
+
program
|
|
7067
|
+
.command('delete-benchmark')
|
|
7068
|
+
.description('Delete a benchmark')
|
|
7069
|
+
.argument('<id>', 'Benchmark ID')
|
|
7070
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
7071
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7072
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7073
|
+
.action(async (benchmarkId, options, command) => {
|
|
7074
|
+
try {
|
|
7075
|
+
const globalOptions = command.parent.opts();
|
|
7076
|
+
const config = loadConfig(globalOptions.config);
|
|
7077
|
+
validateConfiguration(config);
|
|
7078
|
+
|
|
7079
|
+
const api = new ToothFairyAPI(
|
|
7080
|
+
config.baseUrl,
|
|
7081
|
+
config.aiUrl,
|
|
7082
|
+
config.aiStreamUrl,
|
|
7083
|
+
config.apiKey,
|
|
7084
|
+
config.workspaceId,
|
|
7085
|
+
globalOptions.verbose || options.verbose
|
|
7086
|
+
);
|
|
7087
|
+
|
|
7088
|
+
if (!options.confirm) {
|
|
7089
|
+
const readline = require('readline');
|
|
7090
|
+
const rl = readline.createInterface({
|
|
7091
|
+
input: process.stdin,
|
|
7092
|
+
output: process.stdout,
|
|
7093
|
+
});
|
|
7094
|
+
|
|
7095
|
+
const answer = await new Promise((resolve) => {
|
|
7096
|
+
rl.question(
|
|
7097
|
+
chalk.yellow(
|
|
7098
|
+
`⚠️ Are you sure you want to delete benchmark ${benchmarkId}? (y/N): `
|
|
7099
|
+
),
|
|
7100
|
+
resolve
|
|
7101
|
+
);
|
|
7102
|
+
});
|
|
7103
|
+
|
|
7104
|
+
rl.close();
|
|
7105
|
+
|
|
7106
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
7107
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
7108
|
+
process.exit(0);
|
|
7109
|
+
}
|
|
7110
|
+
}
|
|
7111
|
+
|
|
7112
|
+
const spinner = ora('Deleting benchmark...').start();
|
|
7113
|
+
const result = await api.deleteBenchmark(benchmarkId);
|
|
7114
|
+
spinner.stop();
|
|
7115
|
+
|
|
7116
|
+
if (options.output === 'json') {
|
|
7117
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7118
|
+
} else {
|
|
7119
|
+
console.log(chalk.green.bold('✅ Benchmark deleted successfully!'));
|
|
7120
|
+
}
|
|
7121
|
+
} catch (error) {
|
|
7122
|
+
console.error(chalk.red(`Error deleting benchmark: ${error.message}`));
|
|
7123
|
+
process.exit(1);
|
|
7124
|
+
}
|
|
7125
|
+
});
|
|
7126
|
+
|
|
7127
|
+
// Billing Commands
|
|
7128
|
+
program
|
|
7129
|
+
.command('billing-month-costs')
|
|
7130
|
+
.description('Get monthly usage and cost information')
|
|
7131
|
+
.argument('<month>', 'Month number (1-12)')
|
|
7132
|
+
.argument('<year>', 'Year (4-digit)')
|
|
7133
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7134
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7135
|
+
.action(async (month, year, options, command) => {
|
|
7136
|
+
try {
|
|
7137
|
+
const globalOptions = command.parent.opts();
|
|
7138
|
+
const config = loadConfig(globalOptions.config);
|
|
7139
|
+
validateConfiguration(config);
|
|
7140
|
+
|
|
7141
|
+
const api = new ToothFairyAPI(
|
|
7142
|
+
config.baseUrl,
|
|
7143
|
+
config.aiUrl,
|
|
7144
|
+
config.aiStreamUrl,
|
|
7145
|
+
config.apiKey,
|
|
7146
|
+
config.workspaceId,
|
|
7147
|
+
globalOptions.verbose || options.verbose
|
|
7148
|
+
);
|
|
7149
|
+
|
|
7150
|
+
const monthNum = parseInt(month);
|
|
7151
|
+
const yearNum = parseInt(year);
|
|
7152
|
+
|
|
7153
|
+
if (isNaN(monthNum) || monthNum < 1 || monthNum > 12) {
|
|
7154
|
+
console.error(chalk.red('Error: Month must be a number between 1 and 12'));
|
|
7155
|
+
process.exit(1);
|
|
7156
|
+
}
|
|
7157
|
+
|
|
7158
|
+
if (isNaN(yearNum) || yearNum < 2020 || yearNum > 2100) {
|
|
7159
|
+
console.error(chalk.red('Error: Year must be a valid 4-digit year'));
|
|
7160
|
+
process.exit(1);
|
|
7161
|
+
}
|
|
7162
|
+
|
|
7163
|
+
const spinner = ora('Fetching billing information...').start();
|
|
7164
|
+
const result = await api.getMonthCosts(monthNum, yearNum);
|
|
7165
|
+
spinner.stop();
|
|
7166
|
+
|
|
7167
|
+
if (options.output === 'json') {
|
|
7168
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7169
|
+
} else {
|
|
7170
|
+
console.log(chalk.green.bold(`Billing Information for ${month}/${year}`));
|
|
7171
|
+
console.log();
|
|
7172
|
+
|
|
7173
|
+
if (result.apiUsage) {
|
|
7174
|
+
console.log(chalk.cyan('API Usage:'));
|
|
7175
|
+
if (result.apiUsage.totalUoI !== undefined) {
|
|
7176
|
+
console.log(chalk.dim(` Total Units of Interaction: ${result.apiUsage.totalUoI}`));
|
|
7177
|
+
}
|
|
7178
|
+
if (result.apiUsage.totalCostUSD !== undefined) {
|
|
7179
|
+
console.log(chalk.dim(` Total Cost: $${result.apiUsage.totalCostUSD}`));
|
|
7180
|
+
}
|
|
7181
|
+
}
|
|
7182
|
+
|
|
7183
|
+
if (result.trainingUsage) {
|
|
7184
|
+
console.log(chalk.cyan('Training Usage:'));
|
|
7185
|
+
console.log(chalk.dim(` See --verbose for details`));
|
|
7186
|
+
}
|
|
7187
|
+
}
|
|
7188
|
+
} catch (error) {
|
|
7189
|
+
console.error(chalk.red(`Error getting billing information: ${error.message}`));
|
|
7190
|
+
process.exit(1);
|
|
7191
|
+
}
|
|
7192
|
+
});
|
|
7193
|
+
|
|
7194
|
+
// Channel Management Commands
|
|
7195
|
+
program
|
|
7196
|
+
.command('create-channel')
|
|
7197
|
+
.description('Create a new communication channel')
|
|
7198
|
+
.option('--name <name>', 'Channel name')
|
|
7199
|
+
.option('--channel <channel>', 'Channel type (sms|whatsapp|email)')
|
|
7200
|
+
.option('--provider <provider>', 'Service provider')
|
|
7201
|
+
.option('--senderid <senderid>', 'Sender ID')
|
|
7202
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7203
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7204
|
+
.action(async (options, command) => {
|
|
7205
|
+
try {
|
|
7206
|
+
const globalOptions = command.parent.opts();
|
|
7207
|
+
const config = loadConfig(globalOptions.config);
|
|
7208
|
+
validateConfiguration(config);
|
|
7209
|
+
|
|
7210
|
+
const api = new ToothFairyAPI(
|
|
7211
|
+
config.baseUrl,
|
|
7212
|
+
config.aiUrl,
|
|
7213
|
+
config.aiStreamUrl,
|
|
7214
|
+
config.apiKey,
|
|
7215
|
+
config.workspaceId,
|
|
7216
|
+
globalOptions.verbose || options.verbose
|
|
7217
|
+
);
|
|
7218
|
+
|
|
7219
|
+
const spinner = ora('Creating channel...').start();
|
|
7220
|
+
const result = await api.createChannel({
|
|
7221
|
+
name: options.name,
|
|
7222
|
+
channel: options.channel,
|
|
7223
|
+
provider: options.provider,
|
|
7224
|
+
senderid: options.senderid,
|
|
7225
|
+
});
|
|
7226
|
+
spinner.stop();
|
|
7227
|
+
|
|
7228
|
+
if (options.output === 'json') {
|
|
7229
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7230
|
+
} else {
|
|
7231
|
+
console.log(chalk.green.bold('✅ Channel created successfully!'));
|
|
7232
|
+
console.log(chalk.dim(`ID: ${result.id || 'N/A'}`));
|
|
7233
|
+
}
|
|
7234
|
+
} catch (error) {
|
|
7235
|
+
console.error(chalk.red(`Error creating channel: ${error.message}`));
|
|
7236
|
+
process.exit(1);
|
|
7237
|
+
}
|
|
7238
|
+
});
|
|
7239
|
+
|
|
7240
|
+
program
|
|
7241
|
+
.command('list-channels')
|
|
7242
|
+
.description('List all channels')
|
|
7243
|
+
.option('--limit <number>', 'Maximum number to return', '50')
|
|
7244
|
+
.option('--offset <number>', 'Number to skip', '0')
|
|
7245
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7246
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7247
|
+
.action(async (options, command) => {
|
|
7248
|
+
try {
|
|
7249
|
+
const globalOptions = command.parent.opts();
|
|
7250
|
+
const config = loadConfig(globalOptions.config);
|
|
7251
|
+
validateConfiguration(config);
|
|
7252
|
+
|
|
7253
|
+
const api = new ToothFairyAPI(
|
|
7254
|
+
config.baseUrl,
|
|
7255
|
+
config.aiUrl,
|
|
7256
|
+
config.aiStreamUrl,
|
|
7257
|
+
config.apiKey,
|
|
7258
|
+
config.workspaceId,
|
|
7259
|
+
globalOptions.verbose || options.verbose
|
|
7260
|
+
);
|
|
7261
|
+
|
|
7262
|
+
const spinner = ora('Fetching channels...').start();
|
|
7263
|
+
const result = await api.listChannels(
|
|
7264
|
+
parseInt(options.limit),
|
|
7265
|
+
parseInt(options.offset)
|
|
7266
|
+
);
|
|
7267
|
+
spinner.stop();
|
|
7268
|
+
|
|
7269
|
+
if (options.output === 'json') {
|
|
7270
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7271
|
+
} else {
|
|
7272
|
+
const channels = Array.isArray(result) ? result : result.items || [];
|
|
7273
|
+
console.log(chalk.green.bold(`Found ${channels.length} channel(s)`));
|
|
7274
|
+
channels.forEach(ch => {
|
|
7275
|
+
console.log(chalk.cyan(` • ${ch.name || 'Unnamed'} (${ch.id})`));
|
|
7276
|
+
});
|
|
7277
|
+
}
|
|
7278
|
+
} catch (error) {
|
|
7279
|
+
console.error(chalk.red(`Error listing channels: ${error.message}`));
|
|
7280
|
+
process.exit(1);
|
|
7281
|
+
}
|
|
7282
|
+
});
|
|
7283
|
+
|
|
7284
|
+
program
|
|
7285
|
+
.command('get-channel')
|
|
7286
|
+
.description('Get details of a specific channel')
|
|
7287
|
+
.argument('<id>', 'Channel ID')
|
|
7288
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7289
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7290
|
+
.action(async (channelId, options, command) => {
|
|
7291
|
+
try {
|
|
7292
|
+
const globalOptions = command.parent.opts();
|
|
7293
|
+
const config = loadConfig(globalOptions.config);
|
|
7294
|
+
validateConfiguration(config);
|
|
7295
|
+
|
|
7296
|
+
const api = new ToothFairyAPI(
|
|
7297
|
+
config.baseUrl,
|
|
7298
|
+
config.aiUrl,
|
|
7299
|
+
config.aiStreamUrl,
|
|
7300
|
+
config.apiKey,
|
|
7301
|
+
config.workspaceId,
|
|
7302
|
+
globalOptions.verbose || options.verbose
|
|
7303
|
+
);
|
|
7304
|
+
|
|
7305
|
+
const spinner = ora('Fetching channel...').start();
|
|
7306
|
+
const result = await api.getChannel(channelId);
|
|
7307
|
+
spinner.stop();
|
|
7308
|
+
|
|
7309
|
+
if (options.output === 'json') {
|
|
7310
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7311
|
+
} else {
|
|
7312
|
+
console.log(chalk.green.bold('Channel Details'));
|
|
7313
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
7314
|
+
console.log(chalk.dim(`Name: ${result.name || 'N/A'}`));
|
|
7315
|
+
console.log(chalk.dim(`Channel: ${result.channel || 'N/A'}`));
|
|
7316
|
+
console.log(chalk.dim(`Provider: ${result.provider || 'N/A'}`));
|
|
7317
|
+
}
|
|
7318
|
+
} catch (error) {
|
|
7319
|
+
console.error(chalk.red(`Error getting channel: ${error.message}`));
|
|
7320
|
+
process.exit(1);
|
|
7321
|
+
}
|
|
7322
|
+
});
|
|
7323
|
+
|
|
7324
|
+
program
|
|
7325
|
+
.command('update-channel')
|
|
7326
|
+
.description('Update an existing channel')
|
|
7327
|
+
.option('--id <id>', 'Channel ID')
|
|
7328
|
+
.option('--name <name>', 'Channel name')
|
|
7329
|
+
.option('--channel <channel>', 'Channel type')
|
|
7330
|
+
.option('--provider <provider>', 'Service provider')
|
|
7331
|
+
.option('--senderid <senderid>', 'Sender ID')
|
|
7332
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7333
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7334
|
+
.action(async (options, command) => {
|
|
7335
|
+
try {
|
|
7336
|
+
const globalOptions = command.parent.opts();
|
|
7337
|
+
const config = loadConfig(globalOptions.config);
|
|
7338
|
+
validateConfiguration(config);
|
|
7339
|
+
|
|
7340
|
+
const api = new ToothFairyAPI(
|
|
7341
|
+
config.baseUrl,
|
|
7342
|
+
config.aiUrl,
|
|
7343
|
+
config.aiStreamUrl,
|
|
7344
|
+
config.apiKey,
|
|
7345
|
+
config.workspaceId,
|
|
7346
|
+
globalOptions.verbose || options.verbose
|
|
7347
|
+
);
|
|
7348
|
+
|
|
7349
|
+
const spinner = ora('Updating channel...').start();
|
|
7350
|
+
const result = await api.updateChannel({
|
|
7351
|
+
id: options.id,
|
|
7352
|
+
name: options.name,
|
|
7353
|
+
channel: options.channel,
|
|
7354
|
+
provider: options.provider,
|
|
7355
|
+
senderid: options.senderid,
|
|
7356
|
+
});
|
|
7357
|
+
spinner.stop();
|
|
7358
|
+
|
|
7359
|
+
if (options.output === 'json') {
|
|
7360
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7361
|
+
} else {
|
|
7362
|
+
console.log(chalk.green.bold('✅ Channel updated successfully!'));
|
|
7363
|
+
}
|
|
7364
|
+
} catch (error) {
|
|
7365
|
+
console.error(chalk.red(`Error updating channel: ${error.message}`));
|
|
7366
|
+
process.exit(1);
|
|
7367
|
+
}
|
|
7368
|
+
});
|
|
7369
|
+
|
|
7370
|
+
program
|
|
7371
|
+
.command('delete-channel')
|
|
7372
|
+
.description('Delete a channel')
|
|
7373
|
+
.argument('<id>', 'Channel ID')
|
|
7374
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
7375
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7376
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7377
|
+
.action(async (channelId, options, command) => {
|
|
7378
|
+
try {
|
|
7379
|
+
const globalOptions = command.parent.opts();
|
|
7380
|
+
const config = loadConfig(globalOptions.config);
|
|
7381
|
+
validateConfiguration(config);
|
|
7382
|
+
|
|
7383
|
+
const api = new ToothFairyAPI(
|
|
7384
|
+
config.baseUrl,
|
|
7385
|
+
config.aiUrl,
|
|
7386
|
+
config.aiStreamUrl,
|
|
7387
|
+
config.apiKey,
|
|
7388
|
+
config.workspaceId,
|
|
7389
|
+
globalOptions.verbose || options.verbose
|
|
7390
|
+
);
|
|
7391
|
+
|
|
7392
|
+
if (!options.confirm) {
|
|
7393
|
+
const readline = require('readline');
|
|
7394
|
+
const rl = readline.createInterface({
|
|
7395
|
+
input: process.stdin,
|
|
7396
|
+
output: process.stdout,
|
|
7397
|
+
});
|
|
7398
|
+
|
|
7399
|
+
const answer = await new Promise((resolve) => {
|
|
7400
|
+
rl.question(
|
|
7401
|
+
chalk.yellow(
|
|
7402
|
+
`⚠️ Are you sure you want to delete channel ${channelId}? (y/N): `
|
|
7403
|
+
),
|
|
7404
|
+
resolve
|
|
7405
|
+
);
|
|
7406
|
+
});
|
|
7407
|
+
|
|
7408
|
+
rl.close();
|
|
7409
|
+
|
|
7410
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
7411
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
7412
|
+
process.exit(0);
|
|
7413
|
+
}
|
|
7414
|
+
}
|
|
7415
|
+
|
|
7416
|
+
const spinner = ora('Deleting channel...').start();
|
|
7417
|
+
const result = await api.deleteChannel(channelId);
|
|
7418
|
+
spinner.stop();
|
|
7419
|
+
|
|
7420
|
+
if (options.output === 'json') {
|
|
7421
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7422
|
+
} else {
|
|
7423
|
+
console.log(chalk.green.bold('✅ Channel deleted successfully!'));
|
|
7424
|
+
}
|
|
7425
|
+
} catch (error) {
|
|
7426
|
+
console.error(chalk.red(`Error deleting channel: ${error.message}`));
|
|
7427
|
+
process.exit(1);
|
|
7428
|
+
}
|
|
7429
|
+
});
|
|
7430
|
+
|
|
7431
|
+
// Connection Management Commands
|
|
7432
|
+
program
|
|
7433
|
+
.command('list-connections')
|
|
7434
|
+
.description('List all connections')
|
|
7435
|
+
.option('--limit <number>', 'Maximum number to return', '50')
|
|
7436
|
+
.option('--offset <number>', 'Number to skip', '0')
|
|
7437
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7438
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7439
|
+
.action(async (options, command) => {
|
|
7440
|
+
try {
|
|
7441
|
+
const globalOptions = command.parent.opts();
|
|
7442
|
+
const config = loadConfig(globalOptions.config);
|
|
7443
|
+
validateConfiguration(config);
|
|
7444
|
+
|
|
7445
|
+
const api = new ToothFairyAPI(
|
|
7446
|
+
config.baseUrl,
|
|
7447
|
+
config.aiUrl,
|
|
7448
|
+
config.aiStreamUrl,
|
|
7449
|
+
config.apiKey,
|
|
7450
|
+
config.workspaceId,
|
|
7451
|
+
globalOptions.verbose || options.verbose
|
|
7452
|
+
);
|
|
7453
|
+
|
|
7454
|
+
const spinner = ora('Fetching connections...').start();
|
|
7455
|
+
const result = await api.listConnections(
|
|
7456
|
+
parseInt(options.limit),
|
|
7457
|
+
parseInt(options.offset)
|
|
7458
|
+
);
|
|
7459
|
+
spinner.stop();
|
|
7460
|
+
|
|
7461
|
+
if (options.output === 'json') {
|
|
7462
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7463
|
+
} else {
|
|
7464
|
+
const connections = Array.isArray(result) ? result : result.items || [];
|
|
7465
|
+
console.log(chalk.green.bold(`Found ${connections.length} connection(s)`));
|
|
7466
|
+
connections.forEach(conn => {
|
|
7467
|
+
console.log(chalk.cyan(` • ${conn.name || 'Unnamed'} (${conn.id})`));
|
|
7468
|
+
});
|
|
7469
|
+
}
|
|
7470
|
+
} catch (error) {
|
|
7471
|
+
console.error(chalk.red(`Error listing connections: ${error.message}`));
|
|
7472
|
+
process.exit(1);
|
|
7473
|
+
}
|
|
7474
|
+
});
|
|
7475
|
+
|
|
7476
|
+
program
|
|
7477
|
+
.command('get-connection')
|
|
7478
|
+
.description('Get details of a specific connection')
|
|
7479
|
+
.argument('<id>', 'Connection ID')
|
|
7480
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7481
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7482
|
+
.action(async (connectionId, options, command) => {
|
|
7483
|
+
try {
|
|
7484
|
+
const globalOptions = command.parent.opts();
|
|
7485
|
+
const config = loadConfig(globalOptions.config);
|
|
7486
|
+
validateConfiguration(config);
|
|
7487
|
+
|
|
7488
|
+
const api = new ToothFairyAPI(
|
|
7489
|
+
config.baseUrl,
|
|
7490
|
+
config.aiUrl,
|
|
7491
|
+
config.aiStreamUrl,
|
|
7492
|
+
config.apiKey,
|
|
7493
|
+
config.workspaceId,
|
|
7494
|
+
globalOptions.verbose || options.verbose
|
|
7495
|
+
);
|
|
7496
|
+
|
|
7497
|
+
const spinner = ora('Fetching connection...').start();
|
|
7498
|
+
const result = await api.getConnection(connectionId);
|
|
7499
|
+
spinner.stop();
|
|
7500
|
+
|
|
7501
|
+
if (options.output === 'json') {
|
|
7502
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7503
|
+
} else {
|
|
7504
|
+
console.log(chalk.green.bold('Connection Details'));
|
|
7505
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
7506
|
+
console.log(chalk.dim(`Name: ${result.name || 'N/A'}`));
|
|
7507
|
+
console.log(chalk.dim(`Type: ${result.type || 'N/A'}`));
|
|
7508
|
+
console.log(chalk.dim(`Host: ${result.host || 'N/A'}`));
|
|
7509
|
+
}
|
|
7510
|
+
} catch (error) {
|
|
7511
|
+
console.error(chalk.red(`Error getting connection: ${error.message}`));
|
|
7512
|
+
process.exit(1);
|
|
7513
|
+
}
|
|
7514
|
+
});
|
|
7515
|
+
|
|
7516
|
+
program
|
|
7517
|
+
.command('delete-connection')
|
|
7518
|
+
.description('Delete a connection')
|
|
7519
|
+
.argument('<id>', 'Connection ID')
|
|
7520
|
+
.option('--confirm', 'Skip confirmation prompt')
|
|
7521
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7522
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7523
|
+
.action(async (connectionId, options, command) => {
|
|
7524
|
+
try {
|
|
7525
|
+
const globalOptions = command.parent.opts();
|
|
7526
|
+
const config = loadConfig(globalOptions.config);
|
|
7527
|
+
validateConfiguration(config);
|
|
7528
|
+
|
|
7529
|
+
const api = new ToothFairyAPI(
|
|
7530
|
+
config.baseUrl,
|
|
7531
|
+
config.aiUrl,
|
|
7532
|
+
config.aiStreamUrl,
|
|
7533
|
+
config.apiKey,
|
|
7534
|
+
config.workspaceId,
|
|
7535
|
+
globalOptions.verbose || options.verbose
|
|
7536
|
+
);
|
|
7537
|
+
|
|
7538
|
+
if (!options.confirm) {
|
|
7539
|
+
const readline = require('readline');
|
|
7540
|
+
const rl = readline.createInterface({
|
|
7541
|
+
input: process.stdin,
|
|
7542
|
+
output: process.stdout,
|
|
7543
|
+
});
|
|
7544
|
+
|
|
7545
|
+
const answer = await new Promise((resolve) => {
|
|
7546
|
+
rl.question(
|
|
7547
|
+
chalk.yellow(
|
|
7548
|
+
`⚠️ Are you sure you want to delete connection ${connectionId}? (y/N): `
|
|
7549
|
+
),
|
|
7550
|
+
resolve
|
|
7551
|
+
);
|
|
7552
|
+
});
|
|
7553
|
+
|
|
7554
|
+
rl.close();
|
|
7555
|
+
|
|
7556
|
+
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
|
|
7557
|
+
console.log(chalk.gray('Deletion cancelled.'));
|
|
7558
|
+
process.exit(0);
|
|
7559
|
+
}
|
|
7560
|
+
}
|
|
7561
|
+
|
|
7562
|
+
const spinner = ora('Deleting connection...').start();
|
|
7563
|
+
const result = await api.deleteConnection(connectionId);
|
|
7564
|
+
spinner.stop();
|
|
7565
|
+
|
|
7566
|
+
if (options.output === 'json') {
|
|
7567
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7568
|
+
} else {
|
|
7569
|
+
console.log(chalk.green.bold('✅ Connection deleted successfully!'));
|
|
7570
|
+
}
|
|
7571
|
+
} catch (error) {
|
|
7572
|
+
console.error(chalk.red(`Error deleting connection: ${error.message}`));
|
|
7573
|
+
process.exit(1);
|
|
7574
|
+
}
|
|
7575
|
+
});
|
|
7576
|
+
|
|
7577
|
+
// Dictionary Management Commands
|
|
7578
|
+
program
|
|
7579
|
+
.command('list-dictionaries')
|
|
7580
|
+
.description('List all dictionary entries')
|
|
7581
|
+
.option('--limit <number>', 'Maximum number to return', '50')
|
|
7582
|
+
.option('--offset <number>', 'Number to skip', '0')
|
|
7583
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7584
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7585
|
+
.action(async (options, command) => {
|
|
7586
|
+
try {
|
|
7587
|
+
const globalOptions = command.parent.opts();
|
|
7588
|
+
const config = loadConfig(globalOptions.config);
|
|
7589
|
+
validateConfiguration(config);
|
|
7590
|
+
|
|
7591
|
+
const api = new ToothFairyAPI(
|
|
7592
|
+
config.baseUrl,
|
|
7593
|
+
config.aiUrl,
|
|
7594
|
+
config.aiStreamUrl,
|
|
7595
|
+
config.apiKey,
|
|
7596
|
+
config.workspaceId,
|
|
7597
|
+
globalOptions.verbose || options.verbose
|
|
7598
|
+
);
|
|
7599
|
+
|
|
7600
|
+
const spinner = ora('Fetching dictionary entries...').start();
|
|
7601
|
+
const result = await api.listDictionaries(
|
|
7602
|
+
parseInt(options.limit),
|
|
7603
|
+
parseInt(options.offset)
|
|
7604
|
+
);
|
|
7605
|
+
spinner.stop();
|
|
7606
|
+
|
|
7607
|
+
if (options.output === 'json') {
|
|
7608
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7609
|
+
} else {
|
|
7610
|
+
const dicts = Array.isArray(result) ? result : result.items || [];
|
|
7611
|
+
console.log(chalk.green.bold(`Found ${dicts.length} dictionary entr(y/ies)`));
|
|
7612
|
+
dicts.forEach(dict => {
|
|
7613
|
+
console.log(chalk.cyan(` • ${dict.sourceText || 'N/A'} → ${dict.targetText || 'N/A'} (${dict.id})`));
|
|
7614
|
+
});
|
|
7615
|
+
}
|
|
7616
|
+
} catch (error) {
|
|
7617
|
+
console.error(chalk.red(`Error listing dictionaries: ${error.message}`));
|
|
7618
|
+
process.exit(1);
|
|
7619
|
+
}
|
|
7620
|
+
});
|
|
7621
|
+
|
|
7622
|
+
program
|
|
7623
|
+
.command('get-dictionary')
|
|
7624
|
+
.description('Get details of a specific dictionary entry')
|
|
7625
|
+
.argument('<id>', 'Dictionary entry ID')
|
|
7626
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7627
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7628
|
+
.action(async (dictionaryId, options, command) => {
|
|
7629
|
+
try {
|
|
7630
|
+
const globalOptions = command.parent.opts();
|
|
7631
|
+
const config = loadConfig(globalOptions.config);
|
|
7632
|
+
validateConfiguration(config);
|
|
7633
|
+
|
|
7634
|
+
const api = new ToothFairyAPI(
|
|
7635
|
+
config.baseUrl,
|
|
7636
|
+
config.aiUrl,
|
|
7637
|
+
config.aiStreamUrl,
|
|
7638
|
+
config.apiKey,
|
|
7639
|
+
config.workspaceId,
|
|
7640
|
+
globalOptions.verbose || options.verbose
|
|
7641
|
+
);
|
|
7642
|
+
|
|
7643
|
+
const spinner = ora('Fetching dictionary entry...').start();
|
|
7644
|
+
const result = await api.getDictionary(dictionaryId);
|
|
7645
|
+
spinner.stop();
|
|
7646
|
+
|
|
7647
|
+
if (options.output === 'json') {
|
|
7648
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7649
|
+
} else {
|
|
7650
|
+
console.log(chalk.green.bold('Dictionary Entry Details'));
|
|
7651
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
7652
|
+
console.log(chalk.dim(`Source: ${result.sourceText || 'N/A'}`));
|
|
7653
|
+
console.log(chalk.dim(`Target: ${result.targetText || 'N/A'}`));
|
|
7654
|
+
console.log(chalk.dim(`Source Language: ${result.sourceLanguage || 'N/A'}`));
|
|
7655
|
+
console.log(chalk.dim(`Target Language: ${result.targetLanguage || 'N/A'}`));
|
|
7656
|
+
}
|
|
7657
|
+
} catch (error) {
|
|
7658
|
+
console.error(chalk.red(`Error getting dictionary entry: ${error.message}`));
|
|
7659
|
+
process.exit(1);
|
|
7660
|
+
}
|
|
7661
|
+
});
|
|
7662
|
+
|
|
7663
|
+
// Embedding Management Commands
|
|
7664
|
+
program
|
|
7665
|
+
.command('get-embedding')
|
|
7666
|
+
.description('Get details of a specific embedding')
|
|
7667
|
+
.argument('<id>', 'Embedding ID')
|
|
7668
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7669
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7670
|
+
.action(async (embeddingId, options, command) => {
|
|
7671
|
+
try {
|
|
7672
|
+
const globalOptions = command.parent.opts();
|
|
7673
|
+
const config = loadConfig(globalOptions.config);
|
|
7674
|
+
validateConfiguration(config);
|
|
7675
|
+
|
|
7676
|
+
const api = new ToothFairyAPI(
|
|
7677
|
+
config.baseUrl,
|
|
7678
|
+
config.aiUrl,
|
|
7679
|
+
config.aiStreamUrl,
|
|
7680
|
+
config.apiKey,
|
|
7681
|
+
config.workspaceId,
|
|
7682
|
+
globalOptions.verbose || options.verbose
|
|
7683
|
+
);
|
|
7684
|
+
|
|
7685
|
+
const spinner = ora('Fetching embedding...').start();
|
|
7686
|
+
const result = await api.getEmbedding(embeddingId);
|
|
7687
|
+
spinner.stop();
|
|
7688
|
+
|
|
7689
|
+
if (options.output === 'json') {
|
|
7690
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7691
|
+
} else {
|
|
7692
|
+
console.log(chalk.green.bold('Embedding Details'));
|
|
7693
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
7694
|
+
console.log(chalk.dim(`Chunk ID: ${result.chunk_id || 'N/A'}`));
|
|
7695
|
+
console.log(chalk.dim(`Title: ${result.title || 'N/A'}`));
|
|
7696
|
+
}
|
|
7697
|
+
} catch (error) {
|
|
7698
|
+
console.error(chalk.red(`Error getting embedding: ${error.message}`));
|
|
7699
|
+
process.exit(1);
|
|
7700
|
+
}
|
|
7701
|
+
});
|
|
7702
|
+
|
|
7703
|
+
// Settings Management Commands
|
|
7704
|
+
program
|
|
7705
|
+
.command('get-charting-settings')
|
|
7706
|
+
.description('Get charting settings for the workspace')
|
|
7707
|
+
.argument('<id>', 'Settings ID')
|
|
7708
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7709
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7710
|
+
.action(async (settingsId, options, command) => {
|
|
7711
|
+
try {
|
|
7712
|
+
const globalOptions = command.parent.opts();
|
|
7713
|
+
const config = loadConfig(globalOptions.config);
|
|
7714
|
+
validateConfiguration(config);
|
|
7715
|
+
|
|
7716
|
+
const api = new ToothFairyAPI(
|
|
7717
|
+
config.baseUrl,
|
|
7718
|
+
config.aiUrl,
|
|
7719
|
+
config.aiStreamUrl,
|
|
7720
|
+
config.apiKey,
|
|
7721
|
+
config.workspaceId,
|
|
7722
|
+
globalOptions.verbose || options.verbose
|
|
7723
|
+
);
|
|
7724
|
+
|
|
7725
|
+
const spinner = ora('Fetching charting settings...').start();
|
|
7726
|
+
const result = await api.getChartingSettings(settingsId);
|
|
7727
|
+
spinner.stop();
|
|
7728
|
+
|
|
7729
|
+
if (options.output === 'json') {
|
|
7730
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7731
|
+
} else {
|
|
7732
|
+
console.log(chalk.green.bold('Charting Settings'));
|
|
7733
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
7734
|
+
console.log(chalk.dim(`Primary Color: ${result.primaryColor || 'N/A'}`));
|
|
7735
|
+
console.log(chalk.dim(`Secondary Color: ${result.secondaryColor || 'N/A'}`));
|
|
7736
|
+
}
|
|
7737
|
+
} catch (error) {
|
|
7738
|
+
console.error(chalk.red(`Error getting charting settings: ${error.message}`));
|
|
7739
|
+
process.exit(1);
|
|
7740
|
+
}
|
|
7741
|
+
});
|
|
7742
|
+
|
|
7743
|
+
program
|
|
7744
|
+
.command('update-charting-settings')
|
|
7745
|
+
.description('Update charting settings for the workspace')
|
|
7746
|
+
.argument('<id>', 'Settings ID')
|
|
7747
|
+
.option('--config <config>', 'Settings configuration (JSON)')
|
|
7748
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7749
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7750
|
+
.action(async (settingsId, options, command) => {
|
|
7751
|
+
try {
|
|
7752
|
+
const globalOptions = command.parent.opts();
|
|
7753
|
+
const config = loadConfig(globalOptions.config);
|
|
7754
|
+
validateConfiguration(config);
|
|
7755
|
+
|
|
7756
|
+
const api = new ToothFairyAPI(
|
|
7757
|
+
config.baseUrl,
|
|
7758
|
+
config.aiUrl,
|
|
7759
|
+
config.aiStreamUrl,
|
|
7760
|
+
config.apiKey,
|
|
7761
|
+
config.workspaceId,
|
|
7762
|
+
globalOptions.verbose || options.verbose
|
|
7763
|
+
);
|
|
7764
|
+
|
|
7765
|
+
let settingsConfig = {};
|
|
7766
|
+
if (options.config) {
|
|
7767
|
+
try {
|
|
7768
|
+
settingsConfig = JSON.parse(options.config);
|
|
7769
|
+
} catch (e) {
|
|
7770
|
+
console.error(chalk.red('Invalid JSON in config'));
|
|
7771
|
+
process.exit(1);
|
|
7772
|
+
}
|
|
7773
|
+
}
|
|
7774
|
+
|
|
7775
|
+
const spinner = ora('Updating charting settings...').start();
|
|
7776
|
+
const result = await api.updateChartingSettings({
|
|
7777
|
+
id: settingsId,
|
|
7778
|
+
...settingsConfig,
|
|
7779
|
+
});
|
|
7780
|
+
spinner.stop();
|
|
7781
|
+
|
|
7782
|
+
if (options.output === 'json') {
|
|
7783
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7784
|
+
} else {
|
|
7785
|
+
console.log(chalk.green.bold('✅ Charting settings updated successfully!'));
|
|
7786
|
+
}
|
|
7787
|
+
} catch (error) {
|
|
7788
|
+
console.error(chalk.red(`Error updating charting settings: ${error.message}`));
|
|
7789
|
+
process.exit(1);
|
|
7790
|
+
}
|
|
7791
|
+
});
|
|
7792
|
+
|
|
7793
|
+
program
|
|
7794
|
+
.command('get-embeddings-settings')
|
|
7795
|
+
.description('Get embeddings settings for the workspace')
|
|
7796
|
+
.argument('<id>', 'Settings ID')
|
|
7797
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7798
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7799
|
+
.action(async (settingsId, options, command) => {
|
|
7800
|
+
try {
|
|
7801
|
+
const globalOptions = command.parent.opts();
|
|
7802
|
+
const config = loadConfig(globalOptions.config);
|
|
7803
|
+
validateConfiguration(config);
|
|
7804
|
+
|
|
7805
|
+
const api = new ToothFairyAPI(
|
|
7806
|
+
config.baseUrl,
|
|
7807
|
+
config.aiUrl,
|
|
7808
|
+
config.aiStreamUrl,
|
|
7809
|
+
config.apiKey,
|
|
7810
|
+
config.workspaceId,
|
|
7811
|
+
globalOptions.verbose || options.verbose
|
|
7812
|
+
);
|
|
7813
|
+
|
|
7814
|
+
const spinner = ora('Fetching embeddings settings...').start();
|
|
7815
|
+
const result = await api.getEmbeddingsSettings(settingsId);
|
|
7816
|
+
spinner.stop();
|
|
7817
|
+
|
|
7818
|
+
if (options.output === 'json') {
|
|
7819
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7820
|
+
} else {
|
|
7821
|
+
console.log(chalk.green.bold('Embeddings Settings'));
|
|
7822
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
7823
|
+
console.log(chalk.dim(`Max Chunk Words: ${result.maxChunkWords || 'N/A'}`));
|
|
7824
|
+
console.log(chalk.dim(`Chunking Strategy: ${result.chunkingStrategy || 'N/A'}`));
|
|
7825
|
+
}
|
|
7826
|
+
} catch (error) {
|
|
7827
|
+
console.error(chalk.red(`Error getting embeddings settings: ${error.message}`));
|
|
7828
|
+
process.exit(1);
|
|
7829
|
+
}
|
|
7830
|
+
});
|
|
7831
|
+
|
|
7832
|
+
program
|
|
7833
|
+
.command('update-embeddings-settings')
|
|
7834
|
+
.description('Update embeddings settings for the workspace')
|
|
7835
|
+
.argument('<id>', 'Settings ID')
|
|
7836
|
+
.option('--config <config>', 'Settings configuration (JSON)')
|
|
7837
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7838
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7839
|
+
.action(async (settingsId, options, command) => {
|
|
7840
|
+
try {
|
|
7841
|
+
const globalOptions = command.parent.opts();
|
|
7842
|
+
const config = loadConfig(globalOptions.config);
|
|
7843
|
+
validateConfiguration(config);
|
|
7844
|
+
|
|
7845
|
+
const api = new ToothFairyAPI(
|
|
7846
|
+
config.baseUrl,
|
|
7847
|
+
config.aiUrl,
|
|
7848
|
+
config.aiStreamUrl,
|
|
7849
|
+
config.apiKey,
|
|
7850
|
+
config.workspaceId,
|
|
7851
|
+
globalOptions.verbose || options.verbose
|
|
7852
|
+
);
|
|
7853
|
+
|
|
7854
|
+
let settingsConfig = {};
|
|
7855
|
+
if (options.config) {
|
|
7856
|
+
try {
|
|
7857
|
+
settingsConfig = JSON.parse(options.config);
|
|
7858
|
+
} catch (e) {
|
|
7859
|
+
console.error(chalk.red('Invalid JSON in config'));
|
|
7860
|
+
process.exit(1);
|
|
7861
|
+
}
|
|
7862
|
+
}
|
|
7863
|
+
|
|
7864
|
+
const spinner = ora('Updating embeddings settings...').start();
|
|
7865
|
+
const result = await api.updateEmbeddingsSettings({
|
|
7866
|
+
id: settingsId,
|
|
7867
|
+
...settingsConfig,
|
|
7868
|
+
});
|
|
7869
|
+
spinner.stop();
|
|
7870
|
+
|
|
7871
|
+
if (options.output === 'json') {
|
|
7872
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7873
|
+
} else {
|
|
7874
|
+
console.log(chalk.green.bold('✅ Embeddings settings updated successfully!'));
|
|
7875
|
+
}
|
|
7876
|
+
} catch (error) {
|
|
7877
|
+
console.error(chalk.red(`Error updating embeddings settings: ${error.message}`));
|
|
7878
|
+
process.exit(1);
|
|
7879
|
+
}
|
|
7880
|
+
});
|
|
7881
|
+
|
|
7882
|
+
// Stream Management Commands
|
|
7883
|
+
program
|
|
7884
|
+
.command('list-streams')
|
|
7885
|
+
.description('List all streams')
|
|
7886
|
+
.option('--limit <number>', 'Maximum number to return', '50')
|
|
7887
|
+
.option('--offset <number>', 'Number to skip', '0')
|
|
7888
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7889
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7890
|
+
.action(async (options, command) => {
|
|
7891
|
+
try {
|
|
7892
|
+
const globalOptions = command.parent.opts();
|
|
7893
|
+
const config = loadConfig(globalOptions.config);
|
|
7894
|
+
validateConfiguration(config);
|
|
7895
|
+
|
|
7896
|
+
const api = new ToothFairyAPI(
|
|
7897
|
+
config.baseUrl,
|
|
7898
|
+
config.aiUrl,
|
|
7899
|
+
config.aiStreamUrl,
|
|
7900
|
+
config.apiKey,
|
|
7901
|
+
config.workspaceId,
|
|
7902
|
+
globalOptions.verbose || options.verbose
|
|
7903
|
+
);
|
|
7904
|
+
|
|
7905
|
+
const spinner = ora('Fetching streams...').start();
|
|
7906
|
+
const result = await api.listStreams(
|
|
7907
|
+
parseInt(options.limit),
|
|
7908
|
+
parseInt(options.offset)
|
|
7909
|
+
);
|
|
7910
|
+
spinner.stop();
|
|
7911
|
+
|
|
7912
|
+
if (options.output === 'json') {
|
|
7913
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7914
|
+
} else {
|
|
7915
|
+
const streams = Array.isArray(result) ? result : result.items || [];
|
|
7916
|
+
console.log(chalk.green.bold(`Found ${streams.length} stream(s)`));
|
|
7917
|
+
streams.forEach(stream => {
|
|
7918
|
+
console.log(chalk.cyan(` • ${stream.id}`));
|
|
7919
|
+
});
|
|
7920
|
+
}
|
|
7921
|
+
} catch (error) {
|
|
7922
|
+
console.error(chalk.red(`Error listing streams: ${error.message}`));
|
|
7923
|
+
process.exit(1);
|
|
7924
|
+
}
|
|
7925
|
+
});
|
|
7926
|
+
|
|
7927
|
+
program
|
|
7928
|
+
.command('get-stream')
|
|
7929
|
+
.description('Get details of a specific stream')
|
|
7930
|
+
.argument('<id>', 'Stream ID')
|
|
7931
|
+
.option('-o, --output <format>', 'Output format (json|text)', 'text')
|
|
7932
|
+
.option('-v, --verbose', 'Show detailed information')
|
|
7933
|
+
.action(async (streamId, options, command) => {
|
|
7934
|
+
try {
|
|
7935
|
+
const globalOptions = command.parent.opts();
|
|
7936
|
+
const config = loadConfig(globalOptions.config);
|
|
7937
|
+
validateConfiguration(config);
|
|
7938
|
+
|
|
7939
|
+
const api = new ToothFairyAPI(
|
|
7940
|
+
config.baseUrl,
|
|
7941
|
+
config.aiUrl,
|
|
7942
|
+
config.aiStreamUrl,
|
|
7943
|
+
config.apiKey,
|
|
7944
|
+
config.workspaceId,
|
|
7945
|
+
globalOptions.verbose || options.verbose
|
|
7946
|
+
);
|
|
7947
|
+
|
|
7948
|
+
const spinner = ora('Fetching stream...').start();
|
|
7949
|
+
const result = await api.getStream(streamId);
|
|
7950
|
+
spinner.stop();
|
|
7951
|
+
|
|
7952
|
+
if (options.output === 'json') {
|
|
7953
|
+
console.log(JSON.stringify(result, null, 2));
|
|
7954
|
+
} else {
|
|
7955
|
+
console.log(chalk.green.bold('Stream Details'));
|
|
7956
|
+
console.log(chalk.dim(`ID: ${result.id}`));
|
|
7957
|
+
console.log(chalk.dim(`Type: ${result.type || 'N/A'}`));
|
|
7958
|
+
console.log(chalk.dim(`Status: ${result.status || 'N/A'}`));
|
|
7959
|
+
}
|
|
7960
|
+
} catch (error) {
|
|
7961
|
+
console.error(chalk.red(`Error getting stream: ${error.message}`));
|
|
7962
|
+
process.exit(1);
|
|
7963
|
+
}
|
|
7964
|
+
});
|
|
7965
|
+
|
|
3390
7966
|
// Parse command line arguments
|
|
3391
7967
|
program.parse();
|