@toothfairyai/cli 1.0.16 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +75 -0
  2. package/package.json +1 -1
  3. package/src/api.js +67 -41
package/README.md CHANGED
@@ -205,6 +205,81 @@ toothfairy send "Hello" --agent-id "agent-123" --output json | jq '.agentRespons
205
205
  toothfairy search "documentation" --output json | jq '.[].title'
206
206
  ```
207
207
 
208
+ ## SDK Usage
209
+
210
+ This package also provides a JavaScript SDK for programmatic access to ToothFairyAI.
211
+
212
+ ### Basic SDK Usage
213
+
214
+ ```javascript
215
+ const ToothFairyAPI = require('@toothfairyai/cli/src/api');
216
+
217
+ const api = new ToothFairyAPI(
218
+ 'https://api.toothfairyai.com', // baseUrl
219
+ 'https://ai.toothfairyai.com', // aiUrl
220
+ 'https://stream.toothfairyai.com', // aiStreamUrl
221
+ 'your-api-key', // apiKey
222
+ 'your-workspace-id', // workspaceId
223
+ false // verbose mode
224
+ );
225
+
226
+ // Send a message with streaming response
227
+ await api.sendMessageToAgentStream(
228
+ 'Hello!',
229
+ 'agent-id',
230
+ null, // phoneNumber
231
+ null, // customerId
232
+ null, // providerId
233
+ {}, // customerInfo
234
+ (eventType, data) => {
235
+ console.log(`Event: ${eventType}`, data);
236
+ }
237
+ );
238
+ ```
239
+
240
+ ### New in v1.1.0: Show Progress Flag
241
+
242
+ The SDK now supports a `showProgress` flag that enables tracking of all events from the SSE endpoint, similar to the CLI's `--show-progress` flag:
243
+
244
+ ```javascript
245
+ // Default behavior (standard events only)
246
+ await api.sendMessageToAgentStream(
247
+ 'Hello!',
248
+ 'agent-id',
249
+ null, null, null, {},
250
+ (eventType, data) => {
251
+ // Receives: 'status', 'progress', 'data', 'complete', 'error', etc.
252
+ console.log(eventType, data);
253
+ }
254
+ );
255
+
256
+ // With showProgress enabled (all SSE events)
257
+ await api.sendMessageToAgentStream(
258
+ 'Hello!',
259
+ 'agent-id',
260
+ null, null, null, {},
261
+ (eventType, data) => {
262
+ if (eventType === 'sse_event') {
263
+ // Raw SSE event with complete data from streaming endpoint
264
+ console.log('Raw SSE event:', data);
265
+ } else {
266
+ // Standard processed events
267
+ console.log('Standard event:', eventType, data);
268
+ }
269
+ },
270
+ {}, // attachments
271
+ true // showProgress = true
272
+ );
273
+ ```
274
+
275
+ **Event Types:**
276
+ - `'status'`: Connection status ('connected', 'complete')
277
+ - `'progress'`: Agent processing status updates
278
+ - `'data'`: Streaming response text chunks
279
+ - `'complete'`: Final response with metadata
280
+ - `'error'`: Error events
281
+ - `'sse_event'`: All raw SSE events (when showProgress=true)
282
+
208
283
  For full documentation and cross-platform examples, see the main [README.md](../README.md) in the parent directory.
209
284
 
210
285
  ## Development
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toothfairyai/cli",
3
- "version": "1.0.16",
3
+ "version": "1.1.0",
4
4
  "description": "Command-line interface for ToothFairyAI API",
5
5
  "main": "index.js",
6
6
  "bin": {
package/src/api.js CHANGED
@@ -193,14 +193,14 @@ class ToothFairyAPI {
193
193
  customerId ||
194
194
  `cli-user-${
195
195
  Math.abs(
196
- message.split('').reduce((a, b) => {
196
+ message.split("").reduce((a, b) => {
197
197
  a = (a << 5) - a + b.charCodeAt(0);
198
198
  return a & a;
199
199
  }, 0)
200
200
  ) % 10000
201
201
  }`;
202
- phoneNumber = phoneNumber || '+1234567890';
203
- providerId = providerId || 'default-sms-provider';
202
+ phoneNumber = phoneNumber || "+1234567890";
203
+ providerId = providerId || "default-sms-provider";
204
204
 
205
205
  // Process file attachments if provided
206
206
  const processedAttachments = await this._processAttachments(attachments);
@@ -227,8 +227,8 @@ class ToothFairyAPI {
227
227
  const messageData = {
228
228
  chatID: createdChat.id,
229
229
  text: message,
230
- role: 'user',
231
- userID: 'CLI',
230
+ role: "user",
231
+ userID: "CLI",
232
232
  ...processedAttachments,
233
233
  };
234
234
  const createdMessage = await this.createMessage(messageData);
@@ -310,6 +310,8 @@ class ToothFairyAPI {
310
310
  * @param {string|null} providerId - SMS provider ID
311
311
  * @param {Object} customerInfo - Additional customer information
312
312
  * @param {Function} onEvent - Callback function called for each event
313
+ * @param {Object} attachments - File attachments (images, audios, videos, files)
314
+ * @param {boolean} showProgress - Show all progress events from SSE endpoint (default: false)
313
315
  * @returns {Promise<void>} - Promise resolves when streaming is complete
314
316
  *
315
317
  * Event Types Explained:
@@ -324,8 +326,13 @@ class ToothFairyAPI {
324
326
  * - 'data': Actual response text chunks (progressive text building)
325
327
  * - 'complete': Final response with all metadata
326
328
  * - 'error': Error occurred during streaming
329
+ * - 'sse_event': All raw SSE events (only when showProgress=true)
327
330
  *
328
331
  * The onEvent callback receives: (eventType, eventData) => {}
332
+ *
333
+ * When showProgress is true, all SSE events will be emitted to the onEvent callback
334
+ * with the 'sse_event' type, providing access to all raw events from the streaming
335
+ * endpoint, similar to the CLI's --show-progress flag behavior.
329
336
  */
330
337
  async sendMessageToAgentStream(
331
338
  message,
@@ -335,7 +342,8 @@ class ToothFairyAPI {
335
342
  providerId = null,
336
343
  customerInfo = {},
337
344
  onEvent,
338
- attachments = {}
345
+ attachments = {},
346
+ showProgress = false
339
347
  ) {
340
348
  try {
341
349
  // Use defaults for optional parameters
@@ -343,14 +351,14 @@ class ToothFairyAPI {
343
351
  customerId ||
344
352
  `cli-user-${
345
353
  Math.abs(
346
- message.split('').reduce((a, b) => {
354
+ message.split("").reduce((a, b) => {
347
355
  a = (a << 5) - a + b.charCodeAt(0);
348
356
  return a & a;
349
357
  }, 0)
350
358
  ) % 10000
351
359
  }`;
352
- phoneNumber = phoneNumber || '+1234567890';
353
- providerId = providerId || 'default-sms-provider';
360
+ phoneNumber = phoneNumber || "+1234567890";
361
+ providerId = providerId || "default-sms-provider";
354
362
 
355
363
  // Process file attachments if provided
356
364
  const processedAttachments = await this._processAttachments(attachments);
@@ -381,8 +389,8 @@ class ToothFairyAPI {
381
389
  const messageData = {
382
390
  chatID: createdChat.id,
383
391
  text: message,
384
- role: 'user',
385
- userID: 'CLI',
392
+ role: "user",
393
+ userID: "CLI",
386
394
  ...processedAttachments,
387
395
  };
388
396
  const createdMessage = await this.createMessage(messageData);
@@ -442,13 +450,19 @@ class ToothFairyAPI {
442
450
  try {
443
451
  const eventData = JSON.parse(dataStr);
444
452
 
445
- // Determine event type based on the data structure
453
+ // If showProgress is enabled, emit all events
454
+ if (showProgress) {
455
+ // Emit all events with their raw structure
456
+ onEvent('sse_event', eventData);
457
+ }
458
+
459
+ // Standard event processing (always executed for backward compatibility)
446
460
  if (eventData.status) {
447
- if (eventData.status === "connected") {
448
- onEvent("status", eventData);
449
- } else if (eventData.status === "complete") {
450
- onEvent("status", eventData);
451
- } else if (eventData.status === "inProgress") {
461
+ if (eventData.status === 'connected') {
462
+ onEvent('status', eventData);
463
+ } else if (eventData.status === 'complete') {
464
+ onEvent('status', eventData);
465
+ } else if (eventData.status === 'inProgress') {
452
466
  // Parse metadata to understand what's happening
453
467
  let metadata = {};
454
468
  if (eventData.metadata) {
@@ -463,39 +477,39 @@ class ToothFairyAPI {
463
477
  if (metadata.agent_processing_status) {
464
478
  const processingStatus =
465
479
  metadata.agent_processing_status;
466
- onEvent("progress", {
480
+ onEvent('progress', {
467
481
  ...eventData,
468
482
  processing_status: processingStatus,
469
483
  metadata_parsed: metadata,
470
484
  });
471
485
  } else {
472
- onEvent("progress", {
486
+ onEvent('progress', {
473
487
  ...eventData,
474
488
  metadata_parsed: metadata,
475
489
  });
476
490
  }
477
- } else if (eventData.status === "fulfilled") {
491
+ } else if (eventData.status === 'fulfilled') {
478
492
  // Final response with complete data
479
- onEvent("complete", eventData);
493
+ onEvent('complete', eventData);
480
494
  }
481
- } else if (eventData.text && eventData.type === "message") {
495
+ } else if (eventData.text && eventData.type === 'message') {
482
496
  // This is streaming text data
483
- onEvent("data", eventData);
497
+ onEvent('data', eventData);
484
498
  } else if (
485
- eventData.type === "message" &&
499
+ eventData.type === 'message' &&
486
500
  eventData.images !== undefined
487
501
  ) {
488
502
  // Additional message metadata (images, files, etc.)
489
- onEvent("metadata", eventData);
503
+ onEvent('metadata', eventData);
490
504
  } else if (
491
- eventData.type === "message" &&
505
+ eventData.type === 'message' &&
492
506
  eventData.callbackMetadata
493
507
  ) {
494
508
  // Callback metadata with function details and execution plan
495
- onEvent("callback", eventData);
509
+ onEvent('callback', eventData);
496
510
  } else {
497
511
  // Generic event data
498
- onEvent("unknown", eventData);
512
+ onEvent('unknown', eventData);
499
513
  }
500
514
  } catch (e) {
501
515
  console.error(
@@ -1689,7 +1703,7 @@ class ToothFairyAPI {
1689
1703
 
1690
1704
  /**
1691
1705
  * Process file attachments and upload them for message inclusion
1692
- *
1706
+ *
1693
1707
  * @param {Object} attachments - Attachment configuration
1694
1708
  * @param {string[]} attachments.images - Array of image file paths (max 1)
1695
1709
  * @param {string[]} attachments.audios - Array of audio file paths (max 1)
@@ -1699,7 +1713,7 @@ class ToothFairyAPI {
1699
1713
  */
1700
1714
  async _processAttachments(attachments = {}) {
1701
1715
  const result = {};
1702
-
1716
+
1703
1717
  if (!attachments || Object.keys(attachments).length === 0) {
1704
1718
  return result;
1705
1719
  }
@@ -1707,23 +1721,26 @@ class ToothFairyAPI {
1707
1721
  try {
1708
1722
  // Validate attachment limits
1709
1723
  if (attachments.images && attachments.images.length > 1) {
1710
- throw new Error('Maximum 1 image attachment allowed');
1724
+ throw new Error("Maximum 1 image attachment allowed");
1711
1725
  }
1712
1726
  if (attachments.audios && attachments.audios.length > 1) {
1713
- throw new Error('Maximum 1 audio attachment allowed');
1727
+ throw new Error("Maximum 1 audio attachment allowed");
1714
1728
  }
1715
1729
  if (attachments.videos && attachments.videos.length > 1) {
1716
- throw new Error('Maximum 1 video attachment allowed');
1730
+ throw new Error("Maximum 1 video attachment allowed");
1717
1731
  }
1718
1732
  if (attachments.files && attachments.files.length > 5) {
1719
- throw new Error('Maximum 5 file attachments allowed');
1733
+ throw new Error("Maximum 5 file attachments allowed");
1720
1734
  }
1721
1735
 
1722
1736
  // Process images
1723
1737
  if (attachments.images && attachments.images.length > 0) {
1724
1738
  const imageResults = [];
1725
1739
  for (const imagePath of attachments.images) {
1726
- const uploadResult = await this.uploadFile(imagePath, this.workspaceId);
1740
+ const uploadResult = await this.uploadFile(
1741
+ imagePath,
1742
+ this.workspaceId
1743
+ );
1727
1744
  imageResults.push(uploadResult.filename);
1728
1745
  }
1729
1746
  result.images = imageResults;
@@ -1733,7 +1750,10 @@ class ToothFairyAPI {
1733
1750
  if (attachments.audios && attachments.audios.length > 0) {
1734
1751
  const audioResults = [];
1735
1752
  for (const audioPath of attachments.audios) {
1736
- const uploadResult = await this.uploadFile(audioPath, this.workspaceId);
1753
+ const uploadResult = await this.uploadFile(
1754
+ audioPath,
1755
+ this.workspaceId
1756
+ );
1737
1757
  audioResults.push(uploadResult.filename);
1738
1758
  }
1739
1759
  result.audios = audioResults;
@@ -1743,7 +1763,10 @@ class ToothFairyAPI {
1743
1763
  if (attachments.videos && attachments.videos.length > 0) {
1744
1764
  const videoResults = [];
1745
1765
  for (const videoPath of attachments.videos) {
1746
- const uploadResult = await this.uploadFile(videoPath, this.workspaceId);
1766
+ const uploadResult = await this.uploadFile(
1767
+ videoPath,
1768
+ this.workspaceId
1769
+ );
1747
1770
  videoResults.push(uploadResult.filename);
1748
1771
  }
1749
1772
  result.videos = videoResults;
@@ -1753,17 +1776,20 @@ class ToothFairyAPI {
1753
1776
  if (attachments.files && attachments.files.length > 0) {
1754
1777
  const fileResults = [];
1755
1778
  for (const filePath of attachments.files) {
1756
- const uploadResult = await this.uploadFile(filePath, this.workspaceId);
1779
+ const uploadResult = await this.uploadFile(
1780
+ filePath,
1781
+ this.workspaceId
1782
+ );
1757
1783
  fileResults.push(uploadResult.filename);
1758
1784
  }
1759
1785
  result.files = fileResults;
1760
1786
  }
1761
1787
 
1762
1788
  if (this.verbose) {
1763
- const chalk = require('chalk');
1764
- console.error(chalk.dim('\n--- Processed Attachments ---'));
1789
+ const chalk = require("chalk");
1790
+ console.error(chalk.dim("\n--- Processed Attachments ---"));
1765
1791
  console.error(chalk.dim(`Result: ${JSON.stringify(result, null, 2)}`));
1766
- console.error(chalk.dim('-----------------------------\n'));
1792
+ console.error(chalk.dim("-----------------------------\n"));
1767
1793
  }
1768
1794
 
1769
1795
  return result;