@wokuapp/sdk 0.3.0 → 0.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/README.md CHANGED
@@ -28,6 +28,97 @@ support tickets, delivery tracking and survey sends over the public `/v1` API.
28
28
  > **Server-only.** The secret key grants full management access, so the SDK
29
29
  > refuses to run in a browser. Never ship it to a client bundle.
30
30
 
31
+ ## Proposed follow-up support (unreleased)
32
+
33
+ The next release adds `woku.followUps.get(id)` and typed `Ticket.followUp`,
34
+ `Ticket.contentVersion` and conversation origin. Deploy the matching API before
35
+ using this method. It reads `GET /v1/follow-ups/:id` with your management secret
36
+ and returns the conversation from the API's `data` envelope.
37
+
38
+ ```ts
39
+ const ticket = await woku.tickets.get('ticket_123');
40
+ if (ticket.followUp?.conversationId) {
41
+ const conversation = await woku.followUps.get(ticket.followUp.conversationId);
42
+ console.log(conversation.origin, conversation.messages);
43
+ }
44
+ const enriched = (ticket.followUp?.appliedSummaryVersion ?? 0) >= 1;
45
+ ```
46
+
47
+ `completed` means the conversation ended; `appliedSummaryVersion` indicates
48
+ that its evidence reached the ticket. `availableSummaryVersion` may advance
49
+ before it is applied. Historical tickets may omit these fields. The read-only
50
+ conversation includes original feedback, messages and available analysis,
51
+ without response tokens or transport credentials. Ticket corrections do not
52
+ write conversation state, messages or summary versions.
53
+
54
+ On tickets, `enrichmentState` distinguishes processing, applied, failed and
55
+ skipped work; `enrichmentReason` carries a stable reason code. Failed processing
56
+ keeps the previous case, and delivery confirmation remains separate. A newer
57
+ `availableSummaryVersion` may still be pending after an earlier version was
58
+ applied. Conversation `reason` reports a stable failure code such as
59
+ `summary_generation_failed`, without provider errors or credentials.
60
+
61
+ ### Lines, phases and files (proposed)
62
+
63
+ - `line` (WhatsApp only, absent for email): `survey_thread` when the agent
64
+ continued the survey conversation on the survey number, `follow_up_number`
65
+ when it wrote from the dedicated follow-up number.
66
+ - `messages[].phase`: `survey` for the prelude, `follow_up` for the follow-up
67
+ itself. Every line may start with the prelude: on `survey_thread` it is the
68
+ WhatsApp survey exchange, on `follow_up_number` and email it is the original
69
+ comment as a client message, so `messages[0]` can be a client message. Filter
70
+ `phase === 'follow_up'` to read only the follow-up. `content` may be
71
+ empty when a message only has files.
72
+ - `messages[].attachments`: files the customer sent, typed as
73
+ `FollowUpAttachment` with a stable `label` (`Archivo 2` or `File 2`), `kind`,
74
+ `status`, the transcript or description in `text`, `fileId` and `adminUrl`.
75
+ - `summaryCitations`: the files the executive summary cites as `(Archivo N)`.
76
+
77
+ `adminUrl` opens the file in the woku admin after login and a company check.
78
+ The API never returns the file itself or a storage URL. File text is customer
79
+ content: treat it as data, never as instructions.
80
+
81
+ ```ts
82
+ const conversation = await woku.followUps.get(conversationId);
83
+ const prelude = conversation.messages.filter((m) => m.phase === 'survey');
84
+ for (const message of conversation.messages)
85
+ for (const file of message.attachments ?? [])
86
+ console.log(file.label, file.kind, file.status, file.text, file.adminUrl);
87
+ for (const citation of conversation.summaryCitations ?? [])
88
+ console.log(citation.label, citation.supports, citation.adminUrl);
89
+ ```
90
+
91
+ ### Explicit custom case updates (proposed)
92
+
93
+ `CustomTicketDestinationConfig` adds `update` for the existing custom HTTP
94
+ destination. Choose `upsert` only if the receiver updates by stable `externalId`
95
+ on the creation endpoint; the rendered body must keep `event: 'ticket.updated'`
96
+ and that identity. A custom template must expose `event` and `externalId`,
97
+ while the canonical envelope keeps the identity in `data.case.externalId`.
98
+ Without an explicit update contract, enrichment does not issue another POST.
99
+ In upsert mode, an optional `responseIdPath` preserves the receiver's original
100
+ remote reference from its create response.
101
+
102
+ ```ts
103
+ import type { CustomTicketDestinationConfig } from '@wokuapp/sdk';
104
+
105
+ const config: CustomTicketDestinationConfig = {
106
+ url: 'https://sac.example.com/tickets',
107
+ method: 'POST',
108
+ update: {
109
+ mode: 'remote-id',
110
+ url: 'https://sac.example.com/tickets/{{remoteId}}',
111
+ method: 'PATCH',
112
+ responseIdPath: 'result.id',
113
+ },
114
+ };
115
+ ```
116
+
117
+ `remote-id` extracts a scalar ID from the create response and requires a URL on
118
+ the same origin containing `{{remoteId}}`; updates use PUT or PATCH (default).
119
+ URLs cannot contain credentials or fragments. A safe dotted `responseIdPath`
120
+ is required. Existing destination authentication stays in `credentials`.
121
+
31
122
  ## Install
32
123
 
33
124
  ```bash
@@ -222,9 +313,127 @@ available if re-enabled later.
222
313
  Existing definitions retain their contract; create a new v2 journey to adopt these
223
314
  rules. Current participations retain their original definition version.
224
315
 
316
+ ### Flows
317
+
318
+ A flow is an ordered sequence of steps, each a woku or an NPS, CSAT or CES tool,
319
+ that a respondent answers one after the other. Flows live outside folders.
320
+
321
+ ```ts
322
+ const flow = await woku.flows.create({
323
+ name: 'Post-purchase',
324
+ steps: [
325
+ { type: 'nps', toolId: 'nps_tool_id' },
326
+ { type: 'csat', toolId: 'csat_tool_id' },
327
+ { type: 'woku', toolId: 'woku_id' },
328
+ ],
329
+ });
330
+ await woku.flows.update(flow._id, {
331
+ steps: [{ type: 'csat', toolId: 'csat_tool_id' }],
332
+ });
333
+ for await (const f of await woku.flows.list())
334
+ console.log(f.name, f.steps.length);
335
+ await woku.flows.delete(flow._id); // its tools and responses stay
336
+ ```
337
+
338
+ Use 1 to 20 steps, each tool once and all of your company. For an `nps` step,
339
+ your company id as `toolId` means the company-level NPS. `flows.get(id)` returns
340
+ the flow data (steps with their tools, wokus and branding).
341
+
342
+ ### Folders and tools
343
+
344
+ One folder tree holds wokus and NPS, CSAT and CES tools together. Create folders,
345
+ find any tool, read figures per type and move things in batches:
346
+
347
+ ```ts
348
+ const folder = await woku.folders.create({ name: 'Post-sale' });
349
+
350
+ // Create a tool already inside a folder.
351
+ await woku.csatTools.create({
352
+ name: 'Delivery',
353
+ question: 'How satisfied are you with your delivery?',
354
+ folderId: folder.id,
355
+ });
356
+
357
+ // One list for every tool type. Iterate across pages with the cursor.
358
+ const page = await woku.tools.list({
359
+ folderId: folder.id,
360
+ types: ['csat', 'nps'],
361
+ });
362
+ for await (const tool of page) {
363
+ console.log(tool.type, tool.name, tool.reviewCount, tool.headline.value);
364
+ }
365
+ const found = await woku.tools.list({ q: 'delivery', scope: 'company' });
366
+ // Tracker chips (`name:value`): a tool matches when it has every one. Company-wide.
367
+ const tagged = await woku.tools.list({ trackers: ['TRR:00482'] });
368
+
369
+ // "See more" for folders: foldersLimit pages the folders of the level (omit it
370
+ // to receive all of them). The tools keep their own cursor.
371
+ const first = await woku.tools.list({ foldersLimit: 12 });
372
+ if (first.hasMoreFolders()) {
373
+ const more = await first.getMoreFolders(); // only the next folders, no tools
374
+ console.log(more.folders.length);
375
+ }
376
+
377
+ // Headline figures per type (native scales, never mixed), company or folder.
378
+ const stats = await woku.tools.stats({ folderId: folder.id });
379
+
380
+ // Move items of mixed types, or send them to the root with folderId: null.
381
+ await woku.tools.move({
382
+ items: [
383
+ { type: 'csat', id: 'csat_tool_id' },
384
+ { type: 'woku', id: 'woku_id' },
385
+ ],
386
+ folderId: folder.id,
387
+ });
388
+ await woku.npsTools.move('nps_tool_id', { folderId: null });
389
+
390
+ // AI report and CSV over every tool of the folder and its subfolders.
391
+ const report = await woku.folders.report(folder.id, { lang: 'en' });
392
+ const csv: string = await woku.folders.csv(folder.id);
393
+ // PDF of that report with a breakdown per type (bytes, e.g. write them to a file).
394
+ const pdf: Uint8Array = await woku.folders.pdf(folder.id, { lang: 'en' });
395
+
396
+ // Per-tool figures for a period against the one before it (7, 30 or 90 days, or
397
+ // 0 for the whole history), with the daily sums and the histogram of scores.
398
+ // Every type keeps its own scale: NPS -100..100, CSAT %, CES and stars 1-5.
399
+ const metrics = await woku.folders.metrics(folder.id, { days: 30 });
400
+ for (const tool of metrics.tools) {
401
+ console.log(
402
+ tool.name,
403
+ tool.current.headline.value,
404
+ tool.previous.headline.value,
405
+ );
406
+ }
407
+
408
+ // WhatsApp numbers alerted when a comment on any tool of the folder is an
409
+ // improvement (wokus, NPS, CSAT and CES). The list replaces the previous one.
410
+ await woku.folders.update(folder.id, {
411
+ whatsappAlertRecipients: [{ whatsappId: '56912345678', name: 'Ana' }],
412
+ });
413
+
414
+ // Deleting a folder never deletes its content: it moves up to the parent.
415
+ await woku.folders.delete(folder.id);
416
+ ```
417
+
418
+ ```ts
419
+ // Keep customer journeys in folders: Viajes > journey > moment.
420
+ const status = await woku.journeyFolders.status(); // { enabled, organizing, pending: { journeys, tools }, ... }
421
+ if (!status.enabled) await woku.journeyFolders.organize(); // 202; rejects with 409 while a pass is running
422
+
423
+ const journey = await woku.journeys.get('journey_id');
424
+ console.log(journey.folderId, journey.momentFolders); // moment key -> folder id
425
+ ```
426
+
427
+ Folders created by woku (journeys, moments, integrations) report their `origin`
428
+ (`'journeys-root' | 'journey' | 'journey-moment' | 'integration'`); your own folders have `origin: null`.
429
+
430
+ `woku.folders` also has `list`, `get`, `update` and `move`. NPS, CSAT and CES
431
+ tools (`npsTools`, `csatTools`, `cesTools`) accept `folderId` on `create`, expose
432
+ `folderId` on every tool and have `move(id, { folderId })`.
433
+
225
434
  ## Pagination
226
435
 
227
- Paginated resource methods return a `Page` (journey enrollments use the cursor envelope described above). Iterate every item across pages, or walk pages:
436
+ Paginated resource methods return a `Page` (journey enrollments and `tools.list` use a cursor envelope). Iterate every item across pages, or walk pages:
228
437
 
229
438
  ```ts
230
439
  for await (const ticket of await woku.tickets.list({ severity: 'high' })) {
@@ -285,7 +494,7 @@ await woku.npsTools.create(body, { idempotencyKey: 'my-key' });
285
494
  `trackers`, `npsTools` / `csatTools` / `cesTools`, `nps` / `csat` / `ces`,
286
495
  `wokus`, `forms`, `flows`, `actionPlans`, `actionPlanGroups`, `tickets`,
287
496
  `ticketDestinations`, `dispatches`, `reports`, `company`, `quarantines`,
288
- `journeys`.
497
+ `journeys`, `folders`, `tools`, `journeyFolders`.
289
498
 
290
499
  ## License
291
500
 
package/dist/index.cjs CHANGED
@@ -22,8 +22,23 @@ var Media = class {
22
22
  }
23
23
  };
24
24
 
25
+ // src/resources/follow-ups.ts
26
+ var FollowUps = class {
27
+ constructor(client) {
28
+ this.client = client;
29
+ }
30
+ async get(id, opts) {
31
+ const result = await this.client.request(
32
+ "get",
33
+ `/v1/follow-ups/${encodeURIComponent(id)}`,
34
+ opts
35
+ );
36
+ return result.data;
37
+ }
38
+ };
39
+
25
40
  // package.json
26
- var version = "0.3.0";
41
+ var version = "0.5.0";
27
42
 
28
43
  // src/core/errors.ts
29
44
  var WokuError = class extends Error {
@@ -370,7 +385,8 @@ var WokuClient = class {
370
385
  headers,
371
386
  bodyText,
372
387
  timeout,
373
- args.signal
388
+ args.signal,
389
+ args.binary
374
390
  );
375
391
  if (status >= 200 && status < 300) {
376
392
  return body;
@@ -487,7 +503,7 @@ var WokuClient = class {
487
503
  }
488
504
  return headers;
489
505
  }
490
- async send(url, method, headers, body, timeout, signal) {
506
+ async send(url, method, headers, body, timeout, signal, binary) {
491
507
  if (signal?.aborted)
492
508
  throw new WokuConnectionError("Request aborted", { code: "aborted" });
493
509
  const controller = new AbortController();
@@ -508,6 +524,20 @@ var WokuClient = class {
508
524
  body,
509
525
  signal: controller.signal
510
526
  });
527
+ if (binary && response.status >= 200 && response.status < 300) {
528
+ if (!response.arrayBuffer) {
529
+ throw new WokuError(
530
+ "The fetch implementation cannot read binary responses.",
531
+ { code: "config_error" }
532
+ );
533
+ }
534
+ return {
535
+ status: response.status,
536
+ body: new Uint8Array(await response.arrayBuffer()),
537
+ requestId: requestIdFrom(response.headers),
538
+ retryAfterSeconds: retryAfterFromHeaders(response.headers)
539
+ };
540
+ }
511
541
  const text = await response.text();
512
542
  return {
513
543
  status: response.status,
@@ -706,6 +736,14 @@ var NpsTools = class {
706
736
  }
707
737
  );
708
738
  }
739
+ /** Move the tool into a folder, or to the root with `folderId: null`. */
740
+ move(id, body, opts) {
741
+ return this.client.request(
742
+ "patch",
743
+ `/v1/nps-tools/${encodeURIComponent(id)}/move`,
744
+ { ...opts, body }
745
+ );
746
+ }
709
747
  delete(id, opts) {
710
748
  return this.client.request(
711
749
  "delete",
@@ -745,6 +783,14 @@ var CsatTools = class {
745
783
  }
746
784
  );
747
785
  }
786
+ /** Move the tool into a folder, or to the root with `folderId: null`. */
787
+ move(id, body, opts) {
788
+ return this.client.request(
789
+ "patch",
790
+ `/v1/csat-tools/${encodeURIComponent(id)}/move`,
791
+ { ...opts, body }
792
+ );
793
+ }
748
794
  delete(id, opts) {
749
795
  return this.client.request(
750
796
  "delete",
@@ -784,6 +830,14 @@ var CesTools = class {
784
830
  }
785
831
  );
786
832
  }
833
+ /** Move the tool into a folder, or to the root with `folderId: null`. */
834
+ move(id, body, opts) {
835
+ return this.client.request(
836
+ "patch",
837
+ `/v1/ces-tools/${encodeURIComponent(id)}/move`,
838
+ { ...opts, body }
839
+ );
840
+ }
787
841
  delete(id, opts) {
788
842
  return this.client.request(
789
843
  "delete",
@@ -1001,9 +1055,11 @@ var Flows = class {
1001
1055
  constructor(client) {
1002
1056
  this.client = client;
1003
1057
  }
1058
+ /** List the flows of the company, with their ordered `steps`. */
1004
1059
  list(params, opts) {
1005
1060
  return this.client.getPage("/v1/flows", params, opts);
1006
1061
  }
1062
+ /** The data of a flow: ordered steps with their tools, wokus and branding. */
1007
1063
  get(id, opts) {
1008
1064
  return this.client.request(
1009
1065
  "get",
@@ -1011,6 +1067,33 @@ var Flows = class {
1011
1067
  opts
1012
1068
  );
1013
1069
  }
1070
+ /**
1071
+ * Create a flow from 1 to 20 ordered steps (each tool once, all of your
1072
+ * company). Flows live outside folders.
1073
+ */
1074
+ create(body, opts) {
1075
+ return this.client.request("post", "/v1/flows", {
1076
+ ...opts,
1077
+ body,
1078
+ idempotent: true
1079
+ });
1080
+ }
1081
+ /** Rename a flow or replace its full ordered list of steps. */
1082
+ update(id, body, opts) {
1083
+ return this.client.request(
1084
+ "patch",
1085
+ `/v1/flows/${encodeURIComponent(id)}`,
1086
+ { ...opts, body }
1087
+ );
1088
+ }
1089
+ /** Delete a flow. Its tools and their responses stay. */
1090
+ delete(id, opts) {
1091
+ return this.client.request(
1092
+ "delete",
1093
+ `/v1/flows/${encodeURIComponent(id)}`,
1094
+ opts
1095
+ );
1096
+ }
1014
1097
  };
1015
1098
 
1016
1099
  // src/resources/action-plans.ts
@@ -1508,6 +1591,240 @@ var Quarantines = class {
1508
1591
  }
1509
1592
  };
1510
1593
 
1594
+ // src/resources/folders.ts
1595
+ var Folders = class {
1596
+ constructor(client) {
1597
+ this.client = client;
1598
+ }
1599
+ /** Every folder of the company, or the direct subfolders of `parentFolderId`. */
1600
+ list(params, opts) {
1601
+ return this.client.request("get", "/v1/folders", {
1602
+ ...opts,
1603
+ query: { ...params, ...opts?.query }
1604
+ });
1605
+ }
1606
+ get(id, opts) {
1607
+ return this.client.request(
1608
+ "get",
1609
+ `/v1/folders/${encodeURIComponent(id)}`,
1610
+ opts
1611
+ );
1612
+ }
1613
+ create(body, opts) {
1614
+ return this.client.request("post", "/v1/folders", {
1615
+ ...opts,
1616
+ body,
1617
+ idempotent: true
1618
+ });
1619
+ }
1620
+ /**
1621
+ * Rename or recolor a folder (`color: null` clears the color) and/or replace
1622
+ * its WhatsApp alert recipients (`whatsappAlertRecipients`, `[]` clears them).
1623
+ */
1624
+ update(id, body, opts) {
1625
+ return this.client.request(
1626
+ "patch",
1627
+ `/v1/folders/${encodeURIComponent(id)}`,
1628
+ { ...opts, body }
1629
+ );
1630
+ }
1631
+ /**
1632
+ * Move a folder under another folder, or to the root with
1633
+ * `parentFolderId: null`. A folder cannot move into its own subtree.
1634
+ */
1635
+ move(id, body, opts) {
1636
+ return this.client.request(
1637
+ "patch",
1638
+ `/v1/folders/${encodeURIComponent(id)}/move`,
1639
+ { ...opts, body }
1640
+ );
1641
+ }
1642
+ /**
1643
+ * Delete a folder. Nothing inside is deleted: subfolders, wokus and tools
1644
+ * move up to the parent folder (or the root).
1645
+ */
1646
+ delete(id, opts) {
1647
+ return this.client.request(
1648
+ "delete",
1649
+ `/v1/folders/${encodeURIComponent(id)}`,
1650
+ opts
1651
+ );
1652
+ }
1653
+ /**
1654
+ * AI report over every woku, NPS, CSAT and CES tool of the folder and its
1655
+ * subfolders. Scales differ per type, so there is no rating distribution.
1656
+ */
1657
+ report(id, params, opts) {
1658
+ return this.client.request(
1659
+ "get",
1660
+ `/v1/folders/${encodeURIComponent(id)}/report`,
1661
+ { ...opts, query: { ...params, ...opts?.query } }
1662
+ );
1663
+ }
1664
+ /**
1665
+ * Per-tool figures of the woku, NPS, CSAT and CES tools of the folder and its
1666
+ * subfolders for a period against the one before it, with the daily sums and
1667
+ * the histogram of scores. `days` is 7, 30 (default) or 90, or 0 for the whole
1668
+ * history (every `previous` window is then empty). The period is the last
1669
+ * `days` calendar days in UTC, today included. Scales are never mixed across
1670
+ * types.
1671
+ */
1672
+ metrics(id, params, opts) {
1673
+ return this.client.request(
1674
+ "get",
1675
+ `/v1/folders/${encodeURIComponent(id)}/metrics`,
1676
+ { ...opts, query: { ...params, ...opts?.query } }
1677
+ );
1678
+ }
1679
+ /** CSV text with one row per response of every tool of the folder subtree. */
1680
+ csv(id, opts) {
1681
+ return this.client.request(
1682
+ "get",
1683
+ `/v1/folders/${encodeURIComponent(id)}/csv`,
1684
+ opts
1685
+ );
1686
+ }
1687
+ /**
1688
+ * PDF of the folder report (every woku, NPS, CSAT and CES tool of the folder
1689
+ * and its subfolders) with a breakdown per type on each native scale.
1690
+ * Returns the file bytes: write them to disk or send them as is.
1691
+ */
1692
+ pdf(id, params, opts) {
1693
+ return this.client.request(
1694
+ "get",
1695
+ `/v1/folders/${encodeURIComponent(id)}/pdf`,
1696
+ {
1697
+ ...opts,
1698
+ headers: { Accept: "application/pdf", ...opts?.headers },
1699
+ query: { ...params, ...opts?.query },
1700
+ binary: true
1701
+ }
1702
+ );
1703
+ }
1704
+ };
1705
+
1706
+ // src/resources/tools.ts
1707
+ var ToolsPage = class {
1708
+ constructor(response, fetchNext, fetchMoreFolders = () => Promise.reject(new RangeError("No more folders"))) {
1709
+ this.fetchNext = fetchNext;
1710
+ this.fetchMoreFolders = fetchMoreFolders;
1711
+ this.folders = response.folders;
1712
+ this.tools = response.tools;
1713
+ this.nextCursor = response.nextCursor;
1714
+ this.foldersNextCursor = response.foldersNextCursor ?? null;
1715
+ this.facets = response.facets;
1716
+ }
1717
+ hasMoreFolders() {
1718
+ return this.foldersNextCursor !== null;
1719
+ }
1720
+ /** The next page of folders ("see more"): a page with those folders and no tools. */
1721
+ getMoreFolders() {
1722
+ if (this.foldersNextCursor === null)
1723
+ throw new RangeError("No more folders");
1724
+ return this.fetchMoreFolders(this.foldersNextCursor);
1725
+ }
1726
+ hasNextPage() {
1727
+ return this.nextCursor !== null;
1728
+ }
1729
+ getNextPage() {
1730
+ if (this.nextCursor === null) throw new RangeError("No next page");
1731
+ return this.fetchNext(this.nextCursor);
1732
+ }
1733
+ async *iterPages() {
1734
+ yield this;
1735
+ if (this.hasNextPage()) yield* (await this.getNextPage()).iterPages();
1736
+ }
1737
+ async *[Symbol.asyncIterator]() {
1738
+ for await (const page of this.iterPages()) {
1739
+ for (const tool of page.tools) yield tool;
1740
+ }
1741
+ }
1742
+ };
1743
+ var Tools = class {
1744
+ constructor(client) {
1745
+ this.client = client;
1746
+ }
1747
+ /**
1748
+ * List one folder level (or search the company). Folders and tools of every
1749
+ * type come together; the first page also carries the folders and facets.
1750
+ */
1751
+ async list(params, opts) {
1752
+ const fetchPage = async (cursor, foldersCursor) => {
1753
+ const response = await this.client.request(
1754
+ "get",
1755
+ "/v1/tools",
1756
+ {
1757
+ ...opts,
1758
+ query: {
1759
+ ...params,
1760
+ types: params?.types?.join(","),
1761
+ ...cursor ? { cursor } : {},
1762
+ ...foldersCursor ? { foldersCursor } : {},
1763
+ ...opts?.query
1764
+ }
1765
+ }
1766
+ );
1767
+ return new ToolsPage(
1768
+ response,
1769
+ fetchPage,
1770
+ (next) => fetchPage(void 0, next)
1771
+ );
1772
+ };
1773
+ return fetchPage();
1774
+ }
1775
+ /**
1776
+ * Headline figures per type (woku stars, NPS score, CSAT satisfied %, CES
1777
+ * mean) for the company or one folder and its subfolders, in a period.
1778
+ * A type that fails reports `{ error: true }`; the others stay valid.
1779
+ */
1780
+ stats(params, opts) {
1781
+ return this.client.request("get", "/v1/tools/stats", {
1782
+ ...opts,
1783
+ query: { ...params, ...opts?.query }
1784
+ });
1785
+ }
1786
+ /**
1787
+ * Move up to 200 items of mixed types (`woku`, `nps`, `csat`, `ces`,
1788
+ * `folder`) into a folder, or to the root with `folderId: null`. Idempotent;
1789
+ * nothing moves if one item or the destination is not found.
1790
+ */
1791
+ move(body, opts) {
1792
+ return this.client.request("post", "/v1/tools/move", {
1793
+ ...opts,
1794
+ body
1795
+ });
1796
+ }
1797
+ };
1798
+
1799
+ // src/resources/journey-folders.ts
1800
+ var JourneyFolders = class {
1801
+ constructor(client) {
1802
+ this.client = client;
1803
+ }
1804
+ /** Whether the organization is on, running, and how much is still without a folder. */
1805
+ status(opts) {
1806
+ return this.client.request(
1807
+ "get",
1808
+ "/v1/journey-folders",
1809
+ opts
1810
+ );
1811
+ }
1812
+ /**
1813
+ * Switch the organization on and run the one-off pass in the background: tools
1814
+ * of existing journeys without a folder move into their moment folder, and
1815
+ * tools journeys create from now on are placed there. Tools that already have
1816
+ * a folder are never touched. Rejects with a conflict (409) while a pass is
1817
+ * already running; poll {@link status} for progress.
1818
+ */
1819
+ organize(opts) {
1820
+ return this.client.request(
1821
+ "post",
1822
+ "/v1/journey-folders/organize",
1823
+ opts
1824
+ );
1825
+ }
1826
+ };
1827
+
1511
1828
  // src/woku.ts
1512
1829
  var Woku = class {
1513
1830
  constructor(options) {
@@ -1527,11 +1844,15 @@ var Woku = class {
1527
1844
  this.actionPlans = new ActionPlans(this.client);
1528
1845
  this.actionPlanGroups = new ActionPlanGroups(this.client);
1529
1846
  this.tickets = new Tickets(this.client);
1847
+ this.followUps = new FollowUps(this.client);
1530
1848
  this.ticketDestinations = new TicketDestinations(this.client);
1531
1849
  this.dispatches = new Dispatches(this.client);
1532
1850
  this.reports = new Reports(this.client);
1533
1851
  this.company = new Company(this.client);
1534
1852
  this.journeys = new Journeys(this.client);
1853
+ this.folders = new Folders(this.client);
1854
+ this.tools = new Tools(this.client);
1855
+ this.journeyFolders = new JourneyFolders(this.client);
1535
1856
  this.quarantines = new Quarantines(this.client);
1536
1857
  }
1537
1858
  };
@@ -1539,7 +1860,10 @@ var Woku = class {
1539
1860
  exports.AuthenticationError = AuthenticationError;
1540
1861
  exports.BadRequestError = BadRequestError;
1541
1862
  exports.ConflictError = ConflictError;
1863
+ exports.Folders = Folders;
1864
+ exports.FollowUps = FollowUps;
1542
1865
  exports.InternalServerError = InternalServerError;
1866
+ exports.JourneyFolders = JourneyFolders;
1543
1867
  exports.Journeys = Journeys;
1544
1868
  exports.Media = Media;
1545
1869
  exports.NotFoundError = NotFoundError;
@@ -1547,6 +1871,8 @@ exports.Page = Page;
1547
1871
  exports.PayloadTooLargeError = PayloadTooLargeError;
1548
1872
  exports.PermissionDeniedError = PermissionDeniedError;
1549
1873
  exports.RateLimitError = RateLimitError;
1874
+ exports.Tools = Tools;
1875
+ exports.ToolsPage = ToolsPage;
1550
1876
  exports.UnprocessableEntityError = UnprocessableEntityError;
1551
1877
  exports.Woku = Woku;
1552
1878
  exports.WokuAPIError = WokuAPIError;