@toothfairyai/cli 1.0.3 → 1.0.7

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 CHANGED
@@ -1,57 +1,63 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { Command } = require("commander");
4
- const chalk = require("chalk");
5
- const ora = require("ora");
6
- const { table } = require("table");
7
- require("dotenv").config();
3
+ const { Command } = require('commander');
4
+ const chalk = require('chalk');
5
+ const ora = require('ora');
6
+ const { table } = require('table');
7
+ require('dotenv').config();
8
8
 
9
- const ToothFairyAPI = require("../src/api");
9
+ const ToothFairyAPI = require('../src/api');
10
10
  const {
11
11
  loadConfig,
12
12
  saveConfig,
13
13
  ToothFairyConfig,
14
14
  getConfigPath,
15
15
  validateConfiguration,
16
- } = require("../src/config");
16
+ } = require('../src/config');
17
17
 
18
18
  const program = new Command();
19
19
 
20
20
  program
21
- .name("toothfairy")
21
+ .name('toothfairy')
22
22
  .description(
23
- "ToothFairy AI CLI - Interact with ToothFairy AI agents via command line"
23
+ 'ToothFairy AI CLI - Interact with ToothFairy AI agents via command line'
24
24
  )
25
- .version("1.0.0");
25
+ .version('1.0.0');
26
26
 
27
27
  program
28
- .option("-c, --config <path>", "path to configuration file")
29
- .option("-v, --verbose", "enable verbose logging");
28
+ .option('-c, --config <path>', 'path to configuration file')
29
+ .option('-v, --verbose', 'enable verbose logging');
30
30
 
31
31
  // Configure command
32
32
  program
33
- .command("configure")
34
- .description("Configure ToothFairy CLI credentials and settings")
33
+ .command('configure')
34
+ .description('Configure ToothFairy CLI credentials and settings')
35
35
  .option(
36
- "--base-url <url>",
37
- "ToothFairy API base URL",
38
- "https://api.toothfairyai.com"
36
+ '--base-url <url>',
37
+ 'ToothFairy API base URL',
38
+ 'https://api.toothfairyai.com'
39
39
  )
40
- .option("--ai-url <url>", "ToothFairy AI URL", "https://ai.toothfairyai.com")
41
- .requiredOption("--api-key <key>", "API key")
42
- .requiredOption("--workspace-id <id>", "Workspace ID")
43
- .option("--config-path <path>", "Path to save config file")
40
+ .option('--ai-url <url>', 'ToothFairy AI URL', 'https://ai.toothfairyai.com')
41
+ .option(
42
+ '--ai-stream-url <url>',
43
+ 'ToothFairy AI Streaming URL',
44
+ 'https://ais.toothfairyai.com'
45
+ )
46
+ .requiredOption('--api-key <key>', 'API key')
47
+ .requiredOption('--workspace-id <id>', 'Workspace ID')
48
+ .option('--config-path <path>', 'Path to save config file')
44
49
  .action(async (options) => {
45
50
  try {
46
51
  const config = new ToothFairyConfig(
47
52
  options.baseUrl,
48
53
  options.aiUrl,
54
+ options.aiStreamUrl,
49
55
  options.apiKey,
50
56
  options.workspaceId
51
57
  );
52
58
 
53
59
  saveConfig(config, options.configPath);
54
- console.log(chalk.green("Configuration saved successfully!"));
60
+ console.log(chalk.green('Configuration saved successfully!'));
55
61
  if (!options.configPath) {
56
62
  console.log(`Config saved to: ${getConfigPath()}`);
57
63
  }
@@ -63,16 +69,16 @@ program
63
69
 
64
70
  // Send command
65
71
  program
66
- .command("send")
67
- .description("Send a message to a ToothFairy AI agent")
68
- .argument("<message>", "message to send")
69
- .requiredOption("--agent-id <id>", "Agent ID to send message to")
70
- .option("--phone-number <number>", "Phone number for SMS channel")
71
- .option("--customer-id <id>", "Customer ID")
72
- .option("--provider-id <id>", "SMS provider ID")
73
- .option("--customer-info <json>", "Customer info as JSON string")
74
- .option("-o, --output <format>", "Output format (json|text)", "text")
75
- .option("-v, --verbose", "Show detailed response information")
72
+ .command('send')
73
+ .description('Send a message to a ToothFairy AI agent')
74
+ .argument('<message>', 'message to send')
75
+ .requiredOption('--agent-id <id>', 'Agent ID to send message to')
76
+ .option('--phone-number <number>', 'Phone number for SMS channel')
77
+ .option('--customer-id <id>', 'Customer ID')
78
+ .option('--provider-id <id>', 'SMS provider ID')
79
+ .option('--customer-info <json>', 'Customer info as JSON string')
80
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
81
+ .option('-v, --verbose', 'Show detailed response information')
76
82
  .action(async (message, options, command) => {
77
83
  try {
78
84
  const globalOptions = command.parent.opts();
@@ -82,6 +88,7 @@ program
82
88
  const api = new ToothFairyAPI(
83
89
  config.baseUrl,
84
90
  config.aiUrl,
91
+ config.aiStreamUrl,
85
92
  config.apiKey,
86
93
  config.workspaceId,
87
94
  globalOptions.verbose || options.verbose
@@ -93,12 +100,12 @@ program
93
100
  try {
94
101
  customerInfo = JSON.parse(options.customerInfo);
95
102
  } catch (error) {
96
- console.error(chalk.red("Invalid JSON in customer-info"));
103
+ console.error(chalk.red('Invalid JSON in customer-info'));
97
104
  process.exit(1);
98
105
  }
99
106
  }
100
107
 
101
- const spinner = ora("Sending message to agent...").start();
108
+ const spinner = ora('Sending message to agent...').start();
102
109
 
103
110
  const response = await api.sendMessageToAgent(
104
111
  message,
@@ -111,36 +118,36 @@ program
111
118
 
112
119
  spinner.stop();
113
120
 
114
- if (options.output === "json") {
121
+ if (options.output === 'json') {
115
122
  console.log(JSON.stringify(response, null, 2));
116
123
  } else {
117
124
  const agentResp = response.agentResponse;
118
125
 
119
126
  if (options.verbose) {
120
127
  // Verbose mode: show all details
121
- console.log(chalk.green.bold("Message sent successfully!"));
128
+ console.log(chalk.green.bold('Message sent successfully!'));
122
129
  console.log();
123
130
 
124
131
  const data = [
125
- ["Field", "Value"],
126
- ["Chat ID", response.chatId],
127
- ["Message ID", response.messageId],
132
+ ['Field', 'Value'],
133
+ ['Chat ID', response.chatId],
134
+ ['Message ID', response.messageId],
128
135
  ];
129
136
 
130
137
  console.log(
131
138
  table(data, {
132
139
  header: {
133
- alignment: "center",
134
- content: "Response Details",
140
+ alignment: 'center',
141
+ content: 'Response Details',
135
142
  },
136
143
  })
137
144
  );
138
145
 
139
146
  // Show full agent response
140
- console.log(chalk.blue.bold("Agent Response (Full):"));
141
- console.log("─".repeat(50));
147
+ console.log(chalk.blue.bold('Agent Response (Full):'));
148
+ console.log('─'.repeat(50));
142
149
  console.log(JSON.stringify(agentResp, null, 2));
143
- console.log("─".repeat(50));
150
+ console.log('─'.repeat(50));
144
151
  } else {
145
152
  // Default mode: show only the clean agent text
146
153
  if (agentResp.contents && agentResp.contents.content) {
@@ -153,7 +160,7 @@ program
153
160
  // Fallback if no recognizable text format
154
161
  console.log(
155
162
  chalk.yellow(
156
- "No text response found. Use --verbose for full details."
163
+ 'No text response found. Use --verbose for full details.'
157
164
  )
158
165
  );
159
166
  }
@@ -165,24 +172,272 @@ program
165
172
  }
166
173
  });
167
174
 
175
+ // Send Stream command
176
+ program
177
+ .command('send-stream')
178
+ .description(
179
+ 'Send a message to a ToothFairy AI agent with real-time streaming response'
180
+ )
181
+ .argument('<message>', 'message to send')
182
+ .requiredOption('--agent-id <id>', 'Agent ID to send message to')
183
+ .option('--phone-number <number>', 'Phone number for SMS channel')
184
+ .option('--customer-id <id>', 'Customer ID')
185
+ .option('--provider-id <id>', 'SMS provider ID')
186
+ .option('--customer-info <json>', 'Customer info as JSON string')
187
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
188
+ .option('-v, --verbose', 'Show detailed streaming information')
189
+ .option('--show-progress', 'Show agent processing status updates')
190
+ .action(async (message, options, command) => {
191
+ /*
192
+ STREAMING BEHAVIOR EXPLAINED:
193
+
194
+ šŸ”„ STATUS UPDATES: The agent goes through several processing phases:
195
+
196
+ • 'connected': Connection established with streaming server
197
+ • 'init': Agent initialization started
198
+ • 'initial_setup_completed': Basic setup and context loading finished
199
+ • 'tools_processing_completed': Any required tools/functions processed
200
+ • 'replying': Agent begins generating the actual response (text starts streaming)
201
+ • 'updating_memory': Agent updates conversation memory
202
+ • 'memory_updated': Memory update completed
203
+ • 'complete': Stream finished successfully
204
+
205
+ šŸ“ TEXT STREAMING: Once the agent reaches 'replying' status, you'll see the response
206
+ text being built progressively, word by word, just like ChatGPT or similar AI assistants.
207
+
208
+ šŸ’” TIP: Use --show-progress to see detailed status updates, or --verbose for full debug info.
209
+ */
210
+
211
+ try {
212
+ const globalOptions = command.parent.opts();
213
+ const config = loadConfig(globalOptions.config);
214
+ validateConfiguration(config);
215
+
216
+ const api = new ToothFairyAPI(
217
+ config.baseUrl,
218
+ config.aiUrl,
219
+ config.aiStreamUrl,
220
+ config.apiKey,
221
+ config.workspaceId,
222
+ globalOptions.verbose || options.verbose
223
+ );
224
+
225
+ // Parse customer info if provided
226
+ let customerInfo = {};
227
+ if (options.customerInfo) {
228
+ try {
229
+ customerInfo = JSON.parse(options.customerInfo);
230
+ } catch (error) {
231
+ console.error(chalk.red('Invalid JSON in customer-info'));
232
+ process.exit(1);
233
+ }
234
+ }
235
+
236
+ console.log(
237
+ chalk.cyan(`Streaming message to agent ${options.agentId}...`)
238
+ );
239
+ console.log(chalk.dim(`Message: ${message}`));
240
+ console.log();
241
+
242
+ // Initialize variables for streaming
243
+ let currentText = '';
244
+ let finalResponse = null;
245
+ let processingStatus = null;
246
+
247
+ const formatStatusMessage = (status) => {
248
+ const statusMessages = {
249
+ connected: 'šŸ”— Connected to streaming server',
250
+ init: 'šŸš€ Initializing agent...',
251
+ initial_setup_completed: 'āœ… Agent setup completed',
252
+ tools_processing_completed: 'šŸ› ļø Tools processing finished',
253
+ replying: 'šŸ’­ Agent is thinking and responding...',
254
+ updating_memory: 'šŸ’¾ Updating conversation memory...',
255
+ memory_updated: 'āœ… Memory updated successfully',
256
+ complete: 'šŸŽ‰ Response complete!',
257
+ };
258
+ return statusMessages[status] || `šŸ“Š Status: ${status}`;
259
+ };
260
+
261
+ if (options.output === 'json') {
262
+ // JSON mode: collect all events and output at the end
263
+ const allEvents = [];
264
+
265
+ await api.sendMessageToAgentStream(
266
+ message,
267
+ options.agentId,
268
+ options.phoneNumber,
269
+ options.customerId,
270
+ options.providerId,
271
+ customerInfo,
272
+ (eventType, eventData) => {
273
+ allEvents.push({ event_type: eventType, event_data: eventData });
274
+
275
+ if (eventType === 'error') {
276
+ console.error(
277
+ chalk.red(
278
+ `Streaming error: ${eventData.message || 'Unknown error'}`
279
+ )
280
+ );
281
+ }
282
+ }
283
+ );
284
+
285
+ console.log(JSON.stringify(allEvents, null, 2));
286
+ } else {
287
+ // Text mode: show live streaming
288
+ let currentSpinner = null;
289
+
290
+ // Function to update display
291
+ const updateDisplay = (text, type = 'response') => {
292
+ if (currentSpinner) {
293
+ currentSpinner.stop();
294
+ currentSpinner = null;
295
+ }
296
+
297
+ // Clear previous lines if we're updating the same response
298
+ if (text && type === 'response') {
299
+ // For real-time streaming, we overwrite the current line
300
+ process.stdout.write('\r\x1b[K'); // Clear current line
301
+ process.stdout.write(chalk.green('🧚 Response: ') + text);
302
+ }
303
+ };
304
+
305
+ try {
306
+ await api.sendMessageToAgentStream(
307
+ message,
308
+ options.agentId,
309
+ options.phoneNumber,
310
+ options.customerId,
311
+ options.providerId,
312
+ customerInfo,
313
+ (eventType, eventData) => {
314
+ if (options.verbose) {
315
+ // Verbose mode: show all event details
316
+ console.log(
317
+ chalk.dim(
318
+ `\nEvent: ${eventType} | Data: ${JSON.stringify(
319
+ eventData,
320
+ null,
321
+ 2
322
+ )}`
323
+ )
324
+ );
325
+ }
326
+
327
+ if (eventType === 'status') {
328
+ if (eventData.status === 'connected') {
329
+ if (options.showProgress) {
330
+ console.log(
331
+ chalk.green('šŸ”— Connected to streaming server')
332
+ );
333
+ }
334
+ } else if (eventData.status === 'complete') {
335
+ if (options.showProgress) {
336
+ console.log(
337
+ chalk.green('\nšŸŽ‰ Stream completed successfully!')
338
+ );
339
+ }
340
+ }
341
+ } else if (eventType === 'progress') {
342
+ // Agent processing status updates
343
+ const newStatus = eventData.processing_status;
344
+ if (newStatus && newStatus !== processingStatus) {
345
+ processingStatus = newStatus;
346
+ if (options.showProgress) {
347
+ const statusMsg = formatStatusMessage(processingStatus);
348
+ if (currentSpinner) {
349
+ currentSpinner.stop();
350
+ }
351
+ currentSpinner = ora(chalk.yellow(statusMsg)).start();
352
+ }
353
+ }
354
+ } else if (eventType === 'data') {
355
+ // Streaming text data
356
+ if (eventData.text) {
357
+ currentText = eventData.text;
358
+ updateDisplay(currentText.trim());
359
+ }
360
+ } else if (eventType === 'complete') {
361
+ // Final response with all metadata
362
+ finalResponse = eventData;
363
+ if (currentText) {
364
+ // Ensure we have a clean final display - just add the final emoji
365
+ if (currentSpinner) {
366
+ currentSpinner.stop();
367
+ currentSpinner = null;
368
+ }
369
+ process.stdout.write('\r\x1b[K'); // Clear current line
370
+ process.stdout.write(chalk.blue('šŸŖ„ ') + currentText.trim());
371
+ }
372
+ } else if (eventType === 'error') {
373
+ const errorMsg = eventData.message || 'Unknown streaming error';
374
+ if (currentSpinner) {
375
+ currentSpinner.stop();
376
+ }
377
+ console.error(chalk.red(`\nāŒ Error: ${errorMsg}`));
378
+ process.exit(1);
379
+ }
380
+ }
381
+ );
382
+
383
+ // Clean up spinner after streaming completes
384
+ if (currentSpinner) {
385
+ currentSpinner.stop();
386
+ }
387
+
388
+ console.log(); // Add newline after streaming
389
+
390
+ if (options.verbose && finalResponse) {
391
+ // Show final response metadata in verbose mode
392
+ console.log(chalk.cyan.bold('šŸ“Š Final Response Metadata'));
393
+ console.log('─'.repeat(40));
394
+
395
+ if (finalResponse.metadata_parsed) {
396
+ const metadata = finalResponse.metadata_parsed;
397
+ for (const [key, value] of Object.entries(metadata)) {
398
+ if (key !== 'agent_processing_status') {
399
+ // Already shown during streaming
400
+ console.log(chalk.cyan(`${key}:`), String(value));
401
+ }
402
+ }
403
+ }
404
+ console.log('─'.repeat(40));
405
+ }
406
+
407
+ if (!currentText) {
408
+ console.log(chalk.yellow('No text response received from agent.'));
409
+ }
410
+ } catch (streamError) {
411
+ if (currentSpinner) {
412
+ currentSpinner.stop();
413
+ }
414
+ throw streamError;
415
+ }
416
+ }
417
+ } catch (error) {
418
+ console.error(chalk.red(`Error during streaming: ${error.message}`));
419
+ process.exit(1);
420
+ }
421
+ });
422
+
168
423
  // Search command
169
424
  program
170
- .command("search")
171
- .description("Search for documents in the knowledge hub")
172
- .argument("<query>", "search query text")
425
+ .command('search')
426
+ .description('Search for documents in the knowledge hub')
427
+ .argument('<query>', 'search query text')
173
428
  .option(
174
- "-k, --top-k <number>",
175
- "Number of documents to retrieve (1-50)",
176
- "10"
429
+ '-k, --top-k <number>',
430
+ 'Number of documents to retrieve (1-50)',
431
+ '10'
177
432
  )
178
433
  .option(
179
- "--status <status>",
180
- "Filter by document status (published|suspended)"
434
+ '--status <status>',
435
+ 'Filter by document status (published|suspended)'
181
436
  )
182
- .option("--document-id <id>", "Search within specific document ID")
183
- .option("--topics <topics>", "Comma-separated topic IDs to filter by")
184
- .option("-o, --output <format>", "Output format (json|text)", "text")
185
- .option("-v, --verbose", "Show detailed search information")
437
+ .option('--document-id <id>', 'Search within specific document ID')
438
+ .option('--topics <topics>', 'Comma-separated topic IDs to filter by')
439
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
440
+ .option('-v, --verbose', 'Show detailed search information')
186
441
  .action(async (query, options, command) => {
187
442
  try {
188
443
  const globalOptions = command.parent.opts();
@@ -192,6 +447,7 @@ program
192
447
  const api = new ToothFairyAPI(
193
448
  config.baseUrl,
194
449
  config.aiUrl,
450
+ config.aiStreamUrl,
195
451
  config.apiKey,
196
452
  config.workspaceId,
197
453
  globalOptions.verbose || options.verbose
@@ -201,7 +457,7 @@ program
201
457
  const topK = parseInt(options.topK);
202
458
  if (isNaN(topK) || topK < 1 || topK > 50) {
203
459
  console.error(
204
- chalk.red("Error: --top-k must be a number between 1 and 50")
460
+ chalk.red('Error: --top-k must be a number between 1 and 50')
205
461
  );
206
462
  process.exit(1);
207
463
  }
@@ -209,7 +465,7 @@ program
209
465
  // Build metadata filters
210
466
  const metadata = {};
211
467
  if (options.status) {
212
- if (!["published", "suspended"].includes(options.status)) {
468
+ if (!['published', 'suspended'].includes(options.status)) {
213
469
  console.error(
214
470
  chalk.red(
215
471
  'Error: --status must be either "published" or "suspended"'
@@ -224,7 +480,7 @@ program
224
480
  }
225
481
  if (options.topics) {
226
482
  const topicList = options.topics
227
- .split(",")
483
+ .split(',')
228
484
  .map((t) => t.trim())
229
485
  .filter((t) => t);
230
486
  if (topicList.length > 0) {
@@ -232,7 +488,7 @@ program
232
488
  }
233
489
  }
234
490
 
235
- const spinner = ora("Searching knowledge hub...").start();
491
+ const spinner = ora('Searching knowledge hub...').start();
236
492
 
237
493
  const results = await api.searchDocuments(
238
494
  query,
@@ -242,7 +498,7 @@ program
242
498
 
243
499
  spinner.stop();
244
500
 
245
- if (options.output === "json") {
501
+ if (options.output === 'json') {
246
502
  console.log(JSON.stringify(results, null, 2));
247
503
  } else {
248
504
  // Handle different response formats - results might be array or dict
@@ -251,22 +507,22 @@ program
251
507
  : results.results || [];
252
508
 
253
509
  if (!documents || documents.length === 0) {
254
- console.log(chalk.yellow("No documents found for your query"));
510
+ console.log(chalk.yellow('No documents found for your query'));
255
511
  return;
256
512
  }
257
513
 
258
514
  console.log(chalk.green.bold(`Found ${documents.length} document(s)`));
259
- console.log("=".repeat(50));
515
+ console.log('='.repeat(50));
260
516
 
261
- documents.forEach((doc, index) => {
517
+ documents.forEach((doc) => {
262
518
  const score = doc.cosinesim || 0;
263
- const docId = doc.doc_id || doc.chunk_id || "N/A";
519
+ const docId = doc.doc_id || doc.chunk_id || 'N/A';
264
520
 
265
521
  // Extract text content directly from document
266
- const textContent = doc.raw_text || "No content available";
267
- const docStatus = doc.status || "unknown";
522
+ const textContent = doc.raw_text || 'No content available';
523
+ const docStatus = doc.status || 'unknown';
268
524
  const docTopics = doc.topics || [];
269
- const docTitle = doc.title || "Untitled";
525
+ const docTitle = doc.title || 'Untitled';
270
526
 
271
527
  console.log(
272
528
  chalk.cyan.bold(`\n${docTitle}`),
@@ -276,16 +532,16 @@ program
276
532
  if (options.verbose) {
277
533
  // Verbose mode: show all details
278
534
  const data = [
279
- ["Field", "Value"],
280
- ["Document ID", docId],
281
- ["Title", docTitle],
282
- ["Relevance Score", score.toFixed(4)],
283
- ["Status", docStatus],
284
- ["Topics", docTopics.length > 0 ? docTopics.join(", ") : "None"],
535
+ ['Field', 'Value'],
536
+ ['Document ID', docId],
537
+ ['Title', docTitle],
538
+ ['Relevance Score', score.toFixed(4)],
539
+ ['Status', docStatus],
540
+ ['Topics', docTopics.length > 0 ? docTopics.join(', ') : 'None'],
285
541
  [
286
- "Content Preview",
542
+ 'Content Preview',
287
543
  textContent.length > 200
288
- ? textContent.substring(0, 200) + "..."
544
+ ? textContent.substring(0, 200) + '...'
289
545
  : textContent,
290
546
  ],
291
547
  ];
@@ -293,13 +549,13 @@ program
293
549
  console.log(table(data));
294
550
  } else {
295
551
  // Default mode: show clean content
296
- console.log("─".repeat(50));
552
+ console.log('─'.repeat(50));
297
553
  const displayContent =
298
554
  textContent.length > 500
299
- ? textContent.substring(0, 500) + "..."
555
+ ? textContent.substring(0, 500) + '...'
300
556
  : textContent;
301
557
  console.log(displayContent);
302
- console.log("─".repeat(50));
558
+ console.log('─'.repeat(50));
303
559
  }
304
560
  });
305
561
  }
@@ -311,9 +567,9 @@ program
311
567
 
312
568
  // Chats command
313
569
  program
314
- .command("chats")
315
- .description("List all chats in the workspace")
316
- .option("-o, --output <format>", "Output format (json|text)", "text")
570
+ .command('chats')
571
+ .description('List all chats in the workspace')
572
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
317
573
  .action(async (options, command) => {
318
574
  try {
319
575
  const globalOptions = command.parent.opts();
@@ -323,23 +579,24 @@ program
323
579
  const api = new ToothFairyAPI(
324
580
  config.baseUrl,
325
581
  config.aiUrl,
582
+ config.aiStreamUrl,
326
583
  config.apiKey,
327
584
  config.workspaceId,
328
585
  globalOptions.verbose || options.verbose
329
586
  );
330
587
 
331
- const spinner = ora("Fetching chats...").start();
588
+ const spinner = ora('Fetching chats...').start();
332
589
  const chatsData = await api.getAllChats();
333
590
  spinner.stop();
334
591
 
335
- if (options.output === "json") {
592
+ if (options.output === 'json') {
336
593
  console.log(JSON.stringify(chatsData, null, 2));
337
594
  } else {
338
595
  if (
339
596
  !chatsData ||
340
597
  (Array.isArray(chatsData) && chatsData.length === 0)
341
598
  ) {
342
- console.log(chalk.yellow("No chats found"));
599
+ console.log(chalk.yellow('No chats found'));
343
600
  return;
344
601
  }
345
602
 
@@ -347,22 +604,22 @@ program
347
604
  ? chatsData
348
605
  : chatsData.items || [];
349
606
 
350
- const data = [["Chat ID", "Name", "Customer ID", "Created"]];
607
+ const data = [['Chat ID', 'Name', 'Customer ID', 'Created']];
351
608
 
352
609
  chatList.forEach((chat) => {
353
610
  data.push([
354
- chat.id || "N/A",
355
- chat.name || "N/A",
356
- chat.customerId || "N/A",
357
- chat.createdAt || "N/A",
611
+ chat.id || 'N/A',
612
+ chat.name || 'N/A',
613
+ chat.customerId || 'N/A',
614
+ chat.createdAt || 'N/A',
358
615
  ]);
359
616
  });
360
617
 
361
618
  console.log(
362
619
  table(data, {
363
620
  header: {
364
- alignment: "center",
365
- content: "Workspace Chats",
621
+ alignment: 'center',
622
+ content: 'Workspace Chats',
366
623
  },
367
624
  })
368
625
  );
@@ -375,10 +632,10 @@ program
375
632
 
376
633
  // Chat command (get specific chat)
377
634
  program
378
- .command("chat")
379
- .description("Get details of a specific chat")
380
- .argument("<chat-id>", "Chat ID to retrieve")
381
- .option("-o, --output <format>", "Output format (json|text)", "text")
635
+ .command('chat')
636
+ .description('Get details of a specific chat')
637
+ .argument('<chat-id>', 'Chat ID to retrieve')
638
+ .option('-o, --output <format>', 'Output format (json|text)', 'text')
382
639
  .action(async (chatId, options, command) => {
383
640
  try {
384
641
  const globalOptions = command.parent.opts();
@@ -388,6 +645,7 @@ program
388
645
  const api = new ToothFairyAPI(
389
646
  config.baseUrl,
390
647
  config.aiUrl,
648
+ config.aiStreamUrl,
391
649
  config.apiKey,
392
650
  config.workspaceId,
393
651
  globalOptions.verbose || options.verbose
@@ -397,17 +655,17 @@ program
397
655
  const chatData = await api.getChat(chatId);
398
656
  spinner.stop();
399
657
 
400
- if (options.output === "json") {
658
+ if (options.output === 'json') {
401
659
  console.log(JSON.stringify(chatData, null, 2));
402
660
  } else {
403
- console.log(chalk.green.bold("Chat Details"));
661
+ console.log(chalk.green.bold('Chat Details'));
404
662
  console.log();
405
663
 
406
- const data = [["Field", "Value"]];
664
+ const data = [['Field', 'Value']];
407
665
 
408
666
  Object.entries(chatData).forEach(([key, value]) => {
409
667
  let displayValue = value;
410
- if (typeof value === "object" && value !== null) {
668
+ if (typeof value === 'object' && value !== null) {
411
669
  displayValue = JSON.stringify(value, null, 2);
412
670
  }
413
671
  data.push([key, String(displayValue)]);
@@ -423,31 +681,31 @@ program
423
681
 
424
682
  // Config show command
425
683
  program
426
- .command("config-show")
427
- .description("Show current configuration")
684
+ .command('config-show')
685
+ .description('Show current configuration')
428
686
  .action(async (options, command) => {
429
687
  try {
430
688
  const globalOptions = command.parent.opts();
431
689
  const config = loadConfig(globalOptions.config);
432
690
 
433
691
  const data = [
434
- ["Setting", "Value"],
435
- ["Base URL", config.baseUrl],
436
- ["AI URL", config.aiUrl],
692
+ ['Setting', 'Value'],
693
+ ['Base URL', config.baseUrl],
694
+ ['AI URL', config.aiUrl],
437
695
  [
438
- "API Key",
696
+ 'API Key',
439
697
  config.apiKey
440
- ? `${"*".repeat(20)}...${config.apiKey.slice(-4)}`
441
- : "Not set",
698
+ ? `${'*'.repeat(20)}...${config.apiKey.slice(-4)}`
699
+ : 'Not set',
442
700
  ],
443
- ["Workspace ID", config.workspaceId],
701
+ ['Workspace ID', config.workspaceId],
444
702
  ];
445
703
 
446
704
  console.log(
447
705
  table(data, {
448
706
  header: {
449
- alignment: "center",
450
- content: "Current Configuration",
707
+ alignment: 'center',
708
+ content: 'Current Configuration',
451
709
  },
452
710
  })
453
711
  );
@@ -459,196 +717,196 @@ program
459
717
 
460
718
  // Help guide command
461
719
  program
462
- .command("help-guide")
463
- .description("Show detailed help and usage examples")
720
+ .command('help-guide')
721
+ .description('Show detailed help and usage examples')
464
722
  .action(() => {
465
- console.log(chalk.cyan.bold("šŸš€ ToothFairy AI CLI - Quick Start Guide"));
466
- console.log("=".repeat(50));
723
+ console.log(chalk.cyan.bold('šŸš€ ToothFairy AI CLI - Quick Start Guide'));
724
+ console.log('='.repeat(50));
467
725
 
468
- console.log(chalk.green.bold("\nšŸš€ Getting Started"));
469
- console.log("1. First, configure your credentials:");
726
+ console.log(chalk.green.bold('\nšŸš€ Getting Started'));
727
+ console.log('1. First, configure your credentials:');
470
728
  console.log(
471
729
  chalk.dim(
472
- " tf configure --api-key YOUR_KEY --workspace-id YOUR_WORKSPACE"
730
+ ' tf configure --api-key YOUR_KEY --workspace-id YOUR_WORKSPACE'
473
731
  )
474
732
  );
475
733
 
476
- console.log("\n2. Send a message to an agent:");
734
+ console.log('\n2. Send a message to an agent:');
477
735
  console.log(
478
736
  chalk.dim(' tf send "Hello, I need help" --agent-id YOUR_AGENT_ID')
479
737
  );
480
738
 
481
- console.log("\n3. Search the knowledge hub:");
739
+ console.log('\n3. Search the knowledge hub:');
482
740
  console.log(chalk.dim(' tf search "AI configuration help"'));
483
741
 
484
- console.log("\n4. Explore your workspace:");
742
+ console.log('\n4. Explore your workspace:');
485
743
  console.log(
486
- chalk.dim(" tf chats # List all conversations")
744
+ chalk.dim(' tf chats # List all conversations')
487
745
  );
488
746
  console.log(
489
- chalk.dim(" tf config-show # View current settings")
747
+ chalk.dim(' tf config-show # View current settings')
490
748
  );
491
749
 
492
- console.log(chalk.blue.bold("\nšŸ’¬ Agent Communication Examples"));
750
+ console.log(chalk.blue.bold('\nšŸ’¬ Agent Communication Examples'));
493
751
 
494
752
  const agentExamples = [
495
753
  [
496
- "Simple message",
754
+ 'Simple message',
497
755
  'tf send "What are your hours?" --agent-id "info-agent"',
498
756
  ],
499
757
  [
500
- "With customer info",
758
+ 'With customer info',
501
759
  'tf send "Schedule appointment" --agent-id "scheduler" --customer-info \'{"name": "John"}\'',
502
760
  ],
503
- ["Verbose output", 'tf send "Hello" --agent-id "agent-123" --verbose'],
761
+ ['Verbose output', 'tf send "Hello" --agent-id "agent-123" --verbose'],
504
762
  [
505
- "JSON for scripting",
763
+ 'JSON for scripting',
506
764
  'tf send "Help" --agent-id "agent-123" --output json',
507
765
  ],
508
766
  ];
509
767
 
510
- const agentData = [["Use Case", "Command"], ...agentExamples];
768
+ const agentData = [['Use Case', 'Command'], ...agentExamples];
511
769
  console.log(
512
770
  table(agentData, {
513
771
  header: {
514
- alignment: "center",
515
- content: "Agent Communication",
772
+ alignment: 'center',
773
+ content: 'Agent Communication',
516
774
  },
517
775
  })
518
776
  );
519
777
 
520
- console.log(chalk.magenta.bold("\nšŸ” Knowledge Hub Search Examples"));
778
+ console.log(chalk.magenta.bold('\nšŸ” Knowledge Hub Search Examples'));
521
779
 
522
780
  const searchExamples = [
523
- ["Basic search", 'tf search "AI agent configuration"'],
524
- ["Filter by status", 'tf search "machine learning" --status published'],
525
- ["Limit results", 'tf search "troubleshooting" --top-k 3'],
781
+ ['Basic search', 'tf search "AI agent configuration"'],
782
+ ['Filter by status', 'tf search "machine learning" --status published'],
783
+ ['Limit results', 'tf search "troubleshooting" --top-k 3'],
526
784
  [
527
- "Topic filtering",
785
+ 'Topic filtering',
528
786
  'tf search "automation" --topics "topic_123,topic_456"',
529
787
  ],
530
- ["Specific document", 'tf search "settings" --document-id "doc_550..."'],
531
- ["Verbose details", 'tf search "deployment" --verbose'],
532
- ["JSON output", 'tf search "API docs" --output json'],
788
+ ['Specific document', 'tf search "settings" --document-id "doc_550..."'],
789
+ ['Verbose details', 'tf search "deployment" --verbose'],
790
+ ['JSON output', 'tf search "API docs" --output json'],
533
791
  ];
534
792
 
535
- const searchData = [["Use Case", "Command"], ...searchExamples];
793
+ const searchData = [['Use Case', 'Command'], ...searchExamples];
536
794
  console.log(
537
795
  table(searchData, {
538
796
  header: {
539
- alignment: "center",
540
- content: "Knowledge Hub Search",
797
+ alignment: 'center',
798
+ content: 'Knowledge Hub Search',
541
799
  },
542
800
  })
543
801
  );
544
802
 
545
- console.log(chalk.green.bold("\nšŸ“‹ Workspace Management Examples"));
803
+ console.log(chalk.green.bold('\nšŸ“‹ Workspace Management Examples'));
546
804
 
547
805
  const mgmtExamples = [
548
- ["List all chats", "tf chats"],
549
- ["View chat details", "tf chat CHAT_ID"],
550
- ["Show config", "tf config-show"],
551
- ["Detailed help", "tf help-guide"],
806
+ ['List all chats', 'tf chats'],
807
+ ['View chat details', 'tf chat CHAT_ID'],
808
+ ['Show config', 'tf config-show'],
809
+ ['Detailed help', 'tf help-guide'],
552
810
  ];
553
811
 
554
- const mgmtData = [["Use Case", "Command"], ...mgmtExamples];
812
+ const mgmtData = [['Use Case', 'Command'], ...mgmtExamples];
555
813
  console.log(
556
814
  table(mgmtData, {
557
815
  header: {
558
- alignment: "center",
559
- content: "Workspace Management",
816
+ alignment: 'center',
817
+ content: 'Workspace Management',
560
818
  },
561
819
  })
562
820
  );
563
821
 
564
- console.log(chalk.yellow.bold("\nšŸ”§ Configuration Options"));
822
+ console.log(chalk.yellow.bold('\nšŸ”§ Configuration Options'));
565
823
  const configOptions = [
566
- ["Method", "Description", "Example"],
824
+ ['Method', 'Description', 'Example'],
567
825
  [
568
- "Environment",
569
- "Set environment variables",
570
- "export TF_API_KEY=your_key",
826
+ 'Environment',
827
+ 'Set environment variables',
828
+ 'export TF_API_KEY=your_key',
571
829
  ],
572
830
  [
573
- "Config file",
574
- "Use ~/.toothfairy/config.yml",
575
- "api_key: your_key\\nworkspace_id: your_workspace",
831
+ 'Config file',
832
+ 'Use ~/.toothfairy/config.yml',
833
+ 'api_key: your_key\\nworkspace_id: your_workspace',
576
834
  ],
577
835
  [
578
- "CLI arguments",
579
- "Pass config file path",
580
- "tf --config /path/to/config.yml send ...",
836
+ 'CLI arguments',
837
+ 'Pass config file path',
838
+ 'tf --config /path/to/config.yml send ...',
581
839
  ],
582
840
  ];
583
841
 
584
842
  console.log(table(configOptions));
585
843
 
586
- console.log(chalk.red.bold("\nāš ļø Common Issues & Solutions"));
844
+ console.log(chalk.red.bold('\nāš ļø Common Issues & Solutions'));
587
845
  const issues = [
588
- ["Issue", "Solution"],
846
+ ['Issue', 'Solution'],
589
847
  [
590
- "Configuration incomplete",
591
- "Run: tf configure --api-key YOUR_KEY --workspace-id YOUR_WORKSPACE",
848
+ 'Configuration incomplete',
849
+ 'Run: tf configure --api-key YOUR_KEY --workspace-id YOUR_WORKSPACE',
592
850
  ],
593
851
  [
594
- "No text response found",
595
- "Use --verbose flag to see full response details",
852
+ 'No text response found',
853
+ 'Use --verbose flag to see full response details',
596
854
  ],
597
- ["Agent not responding", "Check agent-id is correct and agent is active"],
855
+ ['Agent not responding', 'Check agent-id is correct and agent is active'],
598
856
  [
599
- "Network errors",
600
- "Verify API endpoints are accessible and credentials are valid",
857
+ 'Network errors',
858
+ 'Verify API endpoints are accessible and credentials are valid',
601
859
  ],
602
860
  ];
603
861
 
604
862
  console.log(table(issues));
605
863
 
606
- console.log(chalk.cyan.bold("\nšŸ” Search Filtering Guide"));
607
- console.log("Knowledge Hub search supports powerful filtering options:");
864
+ console.log(chalk.cyan.bold('\nšŸ” Search Filtering Guide'));
865
+ console.log('Knowledge Hub search supports powerful filtering options:');
608
866
  console.log(
609
- "• " +
610
- chalk.cyan("--status") +
611
- ": Filter documents by 'published' or 'suspended' status"
867
+ '• ' +
868
+ chalk.cyan('--status') +
869
+ ': Filter documents by \'published\' or \'suspended\' status'
612
870
  );
613
871
  console.log(
614
- "• " +
615
- chalk.cyan("--topics") +
616
- ": Use topic IDs from ToothFairyAI (comma-separated)"
872
+ '• ' +
873
+ chalk.cyan('--topics') +
874
+ ': Use topic IDs from ToothFairyAI (comma-separated)'
617
875
  );
618
876
  console.log(
619
- "• " + chalk.cyan("--document-id") + ": Search within a specific document"
877
+ '• ' + chalk.cyan('--document-id') + ': Search within a specific document'
620
878
  );
621
879
  console.log(
622
- "• " + chalk.cyan("--top-k") + ": Control number of results (1-50)"
880
+ '• ' + chalk.cyan('--top-k') + ': Control number of results (1-50)'
623
881
  );
624
882
  console.log(
625
- "• " + chalk.cyan("--verbose") + ": Show relevance scores and metadata"
883
+ '• ' + chalk.cyan('--verbose') + ': Show relevance scores and metadata'
626
884
  );
627
885
 
628
- console.log(chalk.magenta.bold("\nšŸ“– More Help"));
886
+ console.log(chalk.magenta.bold('\nšŸ“– More Help'));
629
887
  console.log(
630
- "• Use " + chalk.cyan("tf COMMAND --help") + " for command-specific help"
888
+ '• Use ' + chalk.cyan('tf COMMAND --help') + ' for command-specific help'
631
889
  );
632
890
  console.log(
633
- "• Use " +
634
- chalk.cyan("--verbose") +
635
- " flag to see detailed request/response information"
891
+ '• Use ' +
892
+ chalk.cyan('--verbose') +
893
+ ' flag to see detailed request/response information'
636
894
  );
637
895
  console.log(
638
- "• Use " + chalk.cyan("--output json") + " for machine-readable output"
896
+ '• Use ' + chalk.cyan('--output json') + ' for machine-readable output'
639
897
  );
640
898
  console.log(
641
- "• Configuration is loaded from: environment variables → ~/.toothfairy/config.yml → CLI args"
899
+ '• Configuration is loaded from: environment variables → ~/.toothfairy/config.yml → CLI args'
642
900
  );
643
901
 
644
- console.log(chalk.green.bold("\n✨ Pro Tips"));
902
+ console.log(chalk.green.bold('\n✨ Pro Tips'));
645
903
  const tips = [
646
- "šŸ’¾ Save time: Configure once with 'tf configure', then just use 'tf send' and 'tf search'",
647
- "šŸ” Debug issues: Use '--verbose' to see full API responses and troubleshoot",
648
- "šŸ“ Scripting: Use '--output json' and tools like 'jq' to parse responses",
649
- "⚔ Quick tests: Only --agent-id is required for send, only query for search",
650
- "šŸŽÆ Better search: Use --status, --topics, and --document-id for targeted results",
651
- "šŸ”§ Multiple environments: Use different config files with '--config' flag",
904
+ 'šŸ’¾ Save time: Configure once with \'tf configure\', then just use \'tf send\' and \'tf search\'',
905
+ 'šŸ” Debug issues: Use \'--verbose\' to see full API responses and troubleshoot',
906
+ 'šŸ“ Scripting: Use \'--output json\' and tools like \'jq\' to parse responses',
907
+ '⚔ Quick tests: Only --agent-id is required for send, only query for search',
908
+ 'šŸŽÆ Better search: Use --status, --topics, and --document-id for targeted results',
909
+ 'šŸ”§ Multiple environments: Use different config files with \'--config\' flag',
652
910
  ];
653
911
 
654
912
  tips.forEach((tip) => {
@@ -657,7 +915,7 @@ program
657
915
 
658
916
  console.log(
659
917
  chalk.dim(
660
- "\nToothFairy CLI v1.0.0 - For more help, visit the documentation"
918
+ '\nToothFairy CLI v1.0.0 - For more help, visit the documentation'
661
919
  )
662
920
  );
663
921
  });