@chatlychat/cli 0.1.4 → 0.1.6

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 (73) hide show
  1. package/dist/commands/ai.d.ts +4 -0
  2. package/dist/commands/ai.d.ts.map +1 -0
  3. package/dist/commands/ai.js +149 -0
  4. package/dist/commands/ai.js.map +1 -0
  5. package/dist/commands/common.d.ts +79 -0
  6. package/dist/commands/common.d.ts.map +1 -0
  7. package/dist/commands/common.js +194 -0
  8. package/dist/commands/common.js.map +1 -0
  9. package/dist/commands/config.d.ts +40 -0
  10. package/dist/commands/config.d.ts.map +1 -0
  11. package/dist/commands/config.js +336 -0
  12. package/dist/commands/config.js.map +1 -0
  13. package/dist/commands/insights.d.ts +5 -0
  14. package/dist/commands/insights.d.ts.map +1 -0
  15. package/dist/commands/insights.js +277 -0
  16. package/dist/commands/insights.js.map +1 -0
  17. package/dist/commands/integrations.d.ts +15 -0
  18. package/dist/commands/integrations.d.ts.map +1 -0
  19. package/dist/commands/integrations.js +256 -0
  20. package/dist/commands/integrations.js.map +1 -0
  21. package/dist/commands/resources.d.ts +19 -7
  22. package/dist/commands/resources.d.ts.map +1 -1
  23. package/dist/commands/resources.js +92 -167
  24. package/dist/commands/resources.js.map +1 -1
  25. package/dist/commands/workspace-as-code.d.ts +27 -0
  26. package/dist/commands/workspace-as-code.d.ts.map +1 -0
  27. package/dist/commands/workspace-as-code.js +377 -0
  28. package/dist/commands/workspace-as-code.js.map +1 -0
  29. package/dist/help.d.ts +25 -1
  30. package/dist/help.d.ts.map +1 -1
  31. package/dist/help.js +104 -8
  32. package/dist/help.js.map +1 -1
  33. package/dist/run.d.ts +17 -0
  34. package/dist/run.d.ts.map +1 -1
  35. package/dist/run.js +88 -41
  36. package/dist/run.js.map +1 -1
  37. package/dist/wac/__tests__/wac-fixtures.d.ts +20 -0
  38. package/dist/wac/__tests__/wac-fixtures.d.ts.map +1 -0
  39. package/dist/wac/__tests__/wac-fixtures.js +38 -0
  40. package/dist/wac/__tests__/wac-fixtures.js.map +1 -0
  41. package/dist/wac/apply.d.ts +113 -0
  42. package/dist/wac/apply.d.ts.map +1 -0
  43. package/dist/wac/apply.js +265 -0
  44. package/dist/wac/apply.js.map +1 -0
  45. package/dist/wac/diff.d.ts +137 -0
  46. package/dist/wac/diff.d.ts.map +1 -0
  47. package/dist/wac/diff.js +413 -0
  48. package/dist/wac/diff.js.map +1 -0
  49. package/dist/wac/document.d.ts +110 -0
  50. package/dist/wac/document.d.ts.map +1 -0
  51. package/dist/wac/document.js +429 -0
  52. package/dist/wac/document.js.map +1 -0
  53. package/dist/wac/render.d.ts +52 -0
  54. package/dist/wac/render.d.ts.map +1 -0
  55. package/dist/wac/render.js +156 -0
  56. package/dist/wac/render.js.map +1 -0
  57. package/dist/wac/resource-def.d.ts +116 -0
  58. package/dist/wac/resource-def.d.ts.map +1 -0
  59. package/dist/wac/resource-def.js +44 -0
  60. package/dist/wac/resource-def.js.map +1 -0
  61. package/dist/wac/resources.d.ts +104 -0
  62. package/dist/wac/resources.d.ts.map +1 -0
  63. package/dist/wac/resources.js +541 -0
  64. package/dist/wac/resources.js.map +1 -0
  65. package/dist/wac/values.d.ts +16 -0
  66. package/dist/wac/values.d.ts.map +1 -0
  67. package/dist/wac/values.js +53 -0
  68. package/dist/wac/values.js.map +1 -0
  69. package/package.json +2 -1
  70. package/dist/cli.spec.d.ts +0 -2
  71. package/dist/cli.spec.d.ts.map +0 -1
  72. package/dist/cli.spec.js +0 -226
  73. package/dist/cli.spec.js.map +0 -1
@@ -0,0 +1,336 @@
1
+ /**
2
+ * Config-as-code: the surface an integrator keeps in a repository.
3
+ *
4
+ * Bots, routing rules, triggers, brands, transactional email copy and macros
5
+ * are all workspace CONFIGURATION — the things a team wants reviewed in a
6
+ * pull request and applied from CI, not clicked through a settings screen.
7
+ * That is the reason the SDK manifest grew from 55 routes to 142, and it is
8
+ * the reason these are commands rather than `chatly api` calls.
9
+ *
10
+ * ── One idiom for every write ─────────────────────────────────────────────
11
+ *
12
+ * Writes take a JSON document (`--file`, `--file -`, or `--body`), not a flag
13
+ * per field. These bodies are nested — a bot carries guardrails, a KB scope
14
+ * and an escalation-rule array — and `bodyFrom` explains why a flag per field
15
+ * would be both a worse CLI and a second place for a key name to be wrong.
16
+ *
17
+ * ── Transport ─────────────────────────────────────────────────────────────
18
+ *
19
+ * Through `ChatlyClient.request`, the escape hatch the SDK contract defines
20
+ * for key-accessible routes with no typed method. It is the same auth, the
21
+ * same `Idempotency-Key` per write, the same retry policy and the same typed
22
+ * errors a named method would get — `apps/cli` adds no HTTP of its own.
23
+ */
24
+ import { bool, str } from '../args.js';
25
+ import { bodyFrom, need, pagedVia, queryFrom, renderList, showObject, showRows } from './common.js';
26
+ export async function bots(chatly, args) {
27
+ const sub = args.path[1] ?? 'list';
28
+ switch (sub) {
29
+ case 'list': {
30
+ // A real cursor page, and the only one on this file's surface.
31
+ const pages = pagedVia(chatly, '/v1/bots');
32
+ return renderList(args, ['id', 'name', 'slug', 'status', 'model'], pages.onePage, pages.everyPage);
33
+ }
34
+ case 'get':
35
+ return showObject(await chatly.request('GET', `/v1/bots/${encodeURIComponent(need(args, 2, 'a bot id'))}`));
36
+ case 'create':
37
+ /*
38
+ * `type: "external"` creates a customer's own push-delivered agent and
39
+ * answers with a signing secret and a `cb_` token, EACH SHOWN ONCE.
40
+ * There is no route that reads them back, so this prints the whole
41
+ * response rather than a summary line.
42
+ */
43
+ return showObject(await chatly.request('POST', '/v1/bots', { body: bodyFrom(args, 'the bot') }));
44
+ case 'update':
45
+ return showObject(await chatly.request('PATCH', `/v1/bots/${encodeURIComponent(need(args, 2, 'a bot id'))}`, {
46
+ body: bodyFrom(args, 'the fields to change'),
47
+ }));
48
+ case 'update-external':
49
+ return showObject(await chatly.request('PATCH', `/v1/bots/${encodeURIComponent(need(args, 2, 'a bot id'))}/external`, { body: bodyFrom(args, "the external agent's webhook, timeout or fallback copy") }));
50
+ case 'rotate-secret':
51
+ // Shown once, and the old secret stops working the moment this returns.
52
+ return showObject(await chatly.request('POST', `/v1/bots/${encodeURIComponent(need(args, 2, 'a bot id'))}/external/rotate-secret`));
53
+ /* ── An external agent's delivery ledger ──────────────────────────── */
54
+ case 'deliveries': {
55
+ /*
56
+ * The columns an integrator reading this is actually looking for.
57
+ *
58
+ * `status` first because it answers the question — `failed` will be
59
+ * retried, `dead` will not — and `httpStatus` beside it because the
60
+ * pair is the diagnosis: `dead` with a 401 is their signature check,
61
+ * `dead` with a null is a timeout or DNS, `failed` with a 503 is their
62
+ * load balancer and will fix itself.
63
+ *
64
+ * The signed `payload` is deliberately not a column and is not on a
65
+ * list row at all: `bots delivery <bot> <delivery>` serves it.
66
+ */
67
+ const botId = need(args, 2, 'a bot id');
68
+ const status = str(args.flags, 'status');
69
+ const pages = pagedVia(chatly, `/v1/bots/${encodeURIComponent(botId)}/deliveries`, status === undefined ? {} : { status });
70
+ return renderList(args, ['id', 'status', 'attempt', 'httpStatus', 'durationMs', 'createdAt', 'error'], pages.onePage, pages.everyPage);
71
+ }
72
+ case 'delivery':
73
+ // With `payload` — the exact object that was serialised and signed.
74
+ return showObject(await chatly.request('GET', `/v1/bots/${encodeURIComponent(need(args, 2, 'a bot id'))}/deliveries/` +
75
+ encodeURIComponent(need(args, 3, 'a delivery id'))));
76
+ case 'redeliver':
77
+ /*
78
+ * The RECORDED payload, same `eventId`, from attempt 0.
79
+ *
80
+ * Which means a receiver's own dedupe will discard it if they already
81
+ * processed that id — correct behaviour on their side, and the reason
82
+ * this is for deliveries that never got through rather than for
83
+ * re-running one.
84
+ */
85
+ return showObject(await chatly.request('POST', `/v1/bots/${encodeURIComponent(need(args, 2, 'a bot id'))}/deliveries/` +
86
+ `${encodeURIComponent(need(args, 3, 'a delivery id'))}/redeliver`));
87
+ case 'test-delivery':
88
+ /*
89
+ * Signs a rehearsal payload with the real secret, POSTs it to the real
90
+ * URL, and prints the response — so a 401 from your own verifier
91
+ * arrives here rather than in a log twenty minutes later.
92
+ *
93
+ * Printed WHOLE: `signature` and `payload` are the two fields worth
94
+ * having, because they are the exact header value and the exact object
95
+ * whose serialisation was signed, which is what you need to debug a
96
+ * verifier offline.
97
+ *
98
+ * Writes nothing, so it does not appear under `bots deliveries`.
99
+ */
100
+ return showObject(await chatly.request('POST', `/v1/bots/${encodeURIComponent(need(args, 2, 'a bot id'))}/test-delivery`));
101
+ default:
102
+ throw new Error(`Unknown: bots ${sub}`);
103
+ }
104
+ }
105
+ export async function botRoutes(chatly, args) {
106
+ const sub = args.path[1] ?? 'list';
107
+ switch (sub) {
108
+ case 'list':
109
+ // A bare array in evaluation order — not a cursor page, so no `--all`.
110
+ return showRows(args, await chatly.request('GET', '/v1/bot-routes'), [
111
+ 'id',
112
+ 'name',
113
+ 'botId',
114
+ 'priority',
115
+ 'enabled',
116
+ 'evaluate',
117
+ ]);
118
+ case 'resolve':
119
+ /*
120
+ * The dry-run. `--body` carries the hypothetical visitor:
121
+ *
122
+ * chatly bot-routes resolve --body '{"page":{"path":"/shop/checkout"},
123
+ * "context":{"verified":{"audience":"buyer"}}}'
124
+ *
125
+ * Printed WHOLE rather than as a table. The answer is three things —
126
+ * which route won, why each other route lost, and the event object the
127
+ * conditions were evaluated against — and the third is where you find
128
+ * out that a field path resolves to nothing, which is half of every
129
+ * routing bug.
130
+ */
131
+ return showObject(await chatly.request('POST', '/v1/bot-routes/resolve', {
132
+ body: args.flags['body'] === undefined ? {} : bodyFrom(args, 'the hypothetical visitor'),
133
+ }));
134
+ case 'fields':
135
+ // The condition vocabulary. Read this before authoring a rule: a
136
+ // condition naming a field that is not here is rejected on the write.
137
+ return showObject(await chatly.request('GET', '/v1/bot-routes/fields'));
138
+ case 'create':
139
+ return showObject(await chatly.request('POST', '/v1/bot-routes', {
140
+ body: bodyFrom(args, 'the routing rule'),
141
+ }));
142
+ case 'update':
143
+ return showObject(await chatly.request('PATCH', `/v1/bot-routes/${encodeURIComponent(need(args, 2, 'a route id'))}`, { body: bodyFrom(args, 'the fields to change') }));
144
+ case 'delete':
145
+ return showObject(await chatly.request('DELETE', `/v1/bot-routes/${encodeURIComponent(need(args, 2, 'a route id'))}`));
146
+ default:
147
+ throw new Error(`Unknown: bot-routes ${sub}`);
148
+ }
149
+ }
150
+ export async function triggers(chatly, args) {
151
+ const sub = args.path[1] ?? 'list';
152
+ switch (sub) {
153
+ case 'list': {
154
+ const pages = pagedVia(chatly, '/v1/triggers');
155
+ return renderList(args, ['id', 'name', 'eventType', 'enabled', 'priority'], pages.onePage, pages.everyPage);
156
+ }
157
+ /*
158
+ * `upsert`, not `create`.
159
+ *
160
+ * The manifest calls this route `triggers.create`, but `POST /v1/triggers`
161
+ * is an UPSERT: the handler is named `upsert`, it answers 201 on both
162
+ * arms, and a body carrying an existing trigger's identity replaces it.
163
+ * `create` is kept as an alias so the manifest's name works, but the
164
+ * documented verb is the one the route performs — a CLI that calls this
165
+ * "create" invites someone to run it twice expecting a second trigger.
166
+ */
167
+ case 'upsert':
168
+ case 'create':
169
+ return showObject(await chatly.request('POST', '/v1/triggers', { body: bodyFrom(args, 'the trigger') }));
170
+ case 'delete':
171
+ // 200 with `{ ok: true }`, not a 204.
172
+ return showObject(await chatly.request('DELETE', `/v1/triggers/${encodeURIComponent(need(args, 2, 'a trigger id'))}`));
173
+ default:
174
+ throw new Error(`Unknown: triggers ${sub}`);
175
+ }
176
+ }
177
+ export async function brands(chatly, args) {
178
+ const sub = args.path[1] ?? 'list';
179
+ switch (sub) {
180
+ case 'list':
181
+ return showRows(args, await chatly.request('GET', '/v1/brands'), [
182
+ 'id',
183
+ 'name',
184
+ 'slug',
185
+ 'domain',
186
+ 'isDefault',
187
+ ]);
188
+ case 'create':
189
+ return showObject(await chatly.request('POST', '/v1/brands', { body: bodyFrom(args, 'the brand') }));
190
+ case 'update':
191
+ return showObject(await chatly.request('PATCH', `/v1/brands/${encodeURIComponent(need(args, 2, 'a brand id'))}`, { body: bodyFrom(args, 'the fields to change') }));
192
+ case 'delete':
193
+ // A soft delete that answers 200 with `{ id }` — that id is the only
194
+ // confirmation, so print it rather than saying "deleted".
195
+ return showObject(await chatly.request('DELETE', `/v1/brands/${encodeURIComponent(need(args, 2, 'a brand id'))}`));
196
+ default:
197
+ throw new Error(`Unknown: brands ${sub}`);
198
+ }
199
+ }
200
+ export async function emailTemplates(chatly, args) {
201
+ const sub = args.path[1] ?? 'list';
202
+ switch (sub) {
203
+ case 'list':
204
+ /*
205
+ * All five kinds, always — this is the CATALOGUE, not the rows. A kind
206
+ * the workspace has not customised comes back with `override: null`,
207
+ * which is the field to read to tell a customised kind from a built-in
208
+ * one.
209
+ */
210
+ return showRows(args, await chatly.request('GET', '/v1/email-templates'), [
211
+ 'kind',
212
+ 'label',
213
+ 'audience',
214
+ 'whenSent',
215
+ 'override',
216
+ ]);
217
+ case 'set':
218
+ // A PUT: it REPLACES this workspace's copy, it does not merge into it.
219
+ return showObject(await chatly.request('PUT', `/v1/email-templates/${encodeURIComponent(need(args, 2, 'a template kind'))}`, { body: bodyFrom(args, 'the subject, html and text') }));
220
+ /*
221
+ * `reset`, with `delete` as an alias.
222
+ *
223
+ * `DELETE /v1/email-templates/{kind}` does not delete a template — it
224
+ * drops the workspace's OVERRIDE and puts the built-in copy back, and it
225
+ * answers 200 with `{ restored: false }` when there was nothing to drop.
226
+ * Printing that flag is the only way to tell "I just reverted your copy"
227
+ * from "there was nothing to revert".
228
+ */
229
+ case 'reset':
230
+ case 'delete':
231
+ return showObject(await chatly.request('DELETE', `/v1/email-templates/${encodeURIComponent(need(args, 2, 'a template kind'))}`));
232
+ default:
233
+ throw new Error(`Unknown: email-templates ${sub}`);
234
+ }
235
+ }
236
+ export async function macros(chatly, args) {
237
+ const sub = args.path[1] ?? 'list';
238
+ switch (sub) {
239
+ case 'list': {
240
+ /*
241
+ * NOT cursor-paginated: `@PaginationExempt`, a bare array capped at 200
242
+ * and ordered by usage rank. The shared manifest flagged this `paged`
243
+ * for a day and two SDKs built a paginator over it.
244
+ *
245
+ * `--query`, not `--q`. The handler's schema names it `query`, and zod
246
+ * would strip `q` without a word — the search would run unnarrowed and
247
+ * answer 200.
248
+ */
249
+ if (bool(args.flags, 'all')) {
250
+ process.stderr.write('--all is not available for macros (the route is not cursor-paginated; it ' +
251
+ 'answers a bare array capped at 200 rows).\n');
252
+ return 1;
253
+ }
254
+ const rows = await chatly.request('GET', '/v1/macros', {
255
+ query: queryFrom(args, [
256
+ ['scope', 'scope'],
257
+ ['team', 'team'],
258
+ ['query', 'query'],
259
+ ['language', 'language'],
260
+ ]),
261
+ });
262
+ return showRows(args, rows, ['id', 'name', 'shortCode', 'scope', 'language', 'usageCount']);
263
+ }
264
+ case 'get':
265
+ return showObject(await chatly.request('GET', `/v1/macros/${encodeURIComponent(need(args, 2, 'a macro id'))}`));
266
+ case 'actions':
267
+ // The action registry a macro's `actions` array may reference. A macro
268
+ // naming an action that is not here is a 422 at RUN time, not at save
269
+ // time, so this is the list to check before writing one.
270
+ return showObject(await chatly.request('GET', '/v1/macros/actions'));
271
+ case 'templates':
272
+ return showObject(await chatly.request('GET', '/v1/macros/templates'));
273
+ case 'template':
274
+ return showObject(await chatly.request('GET', `/v1/macros/templates/${encodeURIComponent(need(args, 2, 'a template slug'))}`));
275
+ case 'create':
276
+ return showObject(await chatly.request('POST', '/v1/macros', { body: bodyFrom(args, 'the macro') }));
277
+ case 'update':
278
+ return showObject(await chatly.request('PATCH', `/v1/macros/${encodeURIComponent(need(args, 2, 'a macro id'))}`, { body: bodyFrom(args, 'the fields to change') }));
279
+ case 'delete': {
280
+ // The only real 204 on the whole SDK surface: no body comes back, so
281
+ // there is nothing to print but a confirmation.
282
+ const id = need(args, 2, 'a macro id');
283
+ await chatly.request('DELETE', `/v1/macros/${encodeURIComponent(id)}`);
284
+ if (bool(args.flags, 'json')) {
285
+ return showObject({ deleted: id });
286
+ }
287
+ process.stdout.write(`deleted ${id}\n`);
288
+ return 0;
289
+ }
290
+ default:
291
+ throw new Error(`Unknown: macros ${sub}`);
292
+ }
293
+ }
294
+ /**
295
+ * Knowledge gaps: the questions the KB could not answer, and the loop that
296
+ * closes them.
297
+ *
298
+ * `list` → pick the most-asked → `draft` → the article exists → `triage` it
299
+ * resolved. That whole loop is a terminal job, which is why it is a command.
300
+ */
301
+ export async function knowledgeGaps(chatly, args) {
302
+ const sub = args.path[1] ?? 'list';
303
+ switch (sub) {
304
+ case 'list':
305
+ /*
306
+ * `--limit` here is a plain row cap, NOT a page size: the route is
307
+ * `@PaginationExempt` and the recorder caps a workspace at 200 open
308
+ * gaps, so there is no cursor to follow and `--all` is not offered.
309
+ */
310
+ return showRows(args, await chatly.request('GET', '/v1/knowledge-gaps', {
311
+ query: queryFrom(args, [
312
+ ['status', 'status'],
313
+ ['limit', 'limit', true],
314
+ ]),
315
+ }), ['id', 'question', 'occurrences', 'status', 'lastSeenAt', 'articleId']);
316
+ case 'triage':
317
+ case 'update': {
318
+ const id = need(args, 2, 'a gap id');
319
+ // `{ status }`, strict — one of open / drafted / resolved / dismissed.
320
+ const status = args.path[3] ?? str(args.flags, 'status');
321
+ if (status === undefined) {
322
+ throw new Error('Give a status: `chatly knowledge-gaps triage <id> <status>`.\n' +
323
+ 'One of: open, drafted, resolved, dismissed.');
324
+ }
325
+ return showObject(await chatly.request('PATCH', `/v1/knowledge-gaps/${encodeURIComponent(id)}`, {
326
+ body: { status },
327
+ }));
328
+ }
329
+ case 'draft':
330
+ // Creates a DRAFT article from the gap — 201, and nothing is published.
331
+ return showObject(await chatly.request('POST', `/v1/knowledge-gaps/${encodeURIComponent(need(args, 2, 'a gap id'))}/draft`));
332
+ default:
333
+ throw new Error(`Unknown: knowledge-gaps ${sub}`);
334
+ }
335
+ }
336
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,EAAE,IAAI,EAAE,GAAG,EAAmB,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,UAAU,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAIpG,MAAM,CAAC,KAAK,UAAU,IAAI,CAAC,MAAoB,EAAE,IAAgB;IAC/D,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IACnC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,+DAA+D;YAC/D,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;YAC3C,OAAO,UAAU,CACf,IAAI,EACJ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,EACzC,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,SAAS,CAChB,CAAC;QACJ,CAAC;QACD,KAAK,KAAK;YACR,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,YAAY,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CACzF,CAAC;QACJ,KAAK,QAAQ;YACX;;;;;eAKG;YACH,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC,EAAE,CAAC,CAC9E,CAAC;QACJ,KAAK,QAAQ;YACX,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,EAAE;gBACzF,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;aAC7C,CAAC,CACH,CAAC;QACJ,KAAK,iBAAiB;YACpB,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,OAAO,EACP,YAAY,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,WAAW,EACpE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,wDAAwD,CAAC,EAAE,CACnF,CACF,CAAC;QACJ,KAAK,eAAe;YAClB,wEAAwE;YACxE,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,MAAM,EACN,YAAY,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,yBAAyB,CACnF,CACF,CAAC;QAEJ,yEAAyE;QACzE,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB;;;;;;;;;;;eAWG;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;YACxC,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACzC,MAAM,KAAK,GAAG,QAAQ,CACpB,MAAM,EACN,YAAY,kBAAkB,CAAC,KAAK,CAAC,aAAa,EAClD,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CACvC,CAAC;YACF,OAAO,UAAU,CACf,IAAI,EACJ,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,OAAO,CAAC,EAC7E,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,SAAS,CAChB,CAAC;QACJ,CAAC;QACD,KAAK,UAAU;YACb,oEAAoE;YACpE,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,KAAK,EACL,YAAY,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,cAAc;gBACrE,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC,CACrD,CACF,CAAC;QACJ,KAAK,WAAW;YACd;;;;;;;eAOG;YACH,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,MAAM,EACN,YAAY,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,cAAc;gBACrE,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,eAAe,CAAC,CAAC,YAAY,CACpE,CACF,CAAC;QACJ,KAAK,eAAe;YAClB;;;;;;;;;;;eAWG;YACH,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,MAAM,EACN,YAAY,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,gBAAgB,CAC1E,CACF,CAAC;QACJ;YACE,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,EAAE,CAAC,CAAC;IAC5C,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,MAAoB,EAAE,IAAgB;IACpE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IACnC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM;YACT,uEAAuE;YACvE,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,gBAAgB,CAAC,EAAE;gBACnE,IAAI;gBACJ,MAAM;gBACN,OAAO;gBACP,UAAU;gBACV,SAAS;gBACT,UAAU;aACX,CAAC,CAAC;QACL,KAAK,SAAS;YACZ;;;;;;;;;;;eAWG;YACH,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,wBAAwB,EAAE;gBACrD,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,0BAA0B,CAAC;aACzF,CAAC,CACH,CAAC;QACJ,KAAK,QAAQ;YACX,iEAAiE;YACjE,sEAAsE;YACtE,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,uBAAuB,CAAC,CAAC,CAAC;QAC1E,KAAK,QAAQ;YACX,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,gBAAgB,EAAE;gBAC7C,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;aACzC,CAAC,CACH,CAAC;QACJ,KAAK,QAAQ;YACX,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,OAAO,EACP,kBAAkB,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,EACnE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC,EAAE,CACjD,CACF,CAAC;QACJ,KAAK,QAAQ;YACX,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,QAAQ,EACR,kBAAkB,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CACpE,CACF,CAAC;QACJ;YACE,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,EAAE,CAAC,CAAC;IAClD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,MAAoB,EAAE,IAAgB;IACnE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IACnC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;YAC/C,OAAO,UAAU,CACf,IAAI,EACJ,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,UAAU,CAAC,EAClD,KAAK,CAAC,OAAO,EACb,KAAK,CAAC,SAAS,CAChB,CAAC;QACJ,CAAC;QACD;;;;;;;;;WASG;QACH,KAAK,QAAQ,CAAC;QACd,KAAK,QAAQ;YACX,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,EAAE,CAAC,CACtF,CAAC;QACJ,KAAK,QAAQ;YACX,sCAAsC;YACtC,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,QAAQ,EACR,gBAAgB,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,cAAc,CAAC,CAAC,EAAE,CACpE,CACF,CAAC;QACJ;YACE,MAAM,IAAI,KAAK,CAAC,qBAAqB,GAAG,EAAE,CAAC,CAAC;IAChD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,MAAoB,EAAE,IAAgB;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IACnC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM;YACT,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,YAAY,CAAC,EAAE;gBAC/D,IAAI;gBACJ,MAAM;gBACN,MAAM;gBACN,QAAQ;gBACR,WAAW;aACZ,CAAC,CAAC;QACL,KAAK,QAAQ;YACX,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC,CAClF,CAAC;QACJ,KAAK,QAAQ;YACX,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,OAAO,EACP,cAAc,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,EAC/D,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC,EAAE,CACjD,CACF,CAAC;QACJ,KAAK,QAAQ;YACX,qEAAqE;YACrE,0DAA0D;YAC1D,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,QAAQ,EACR,cAAc,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CAChE,CACF,CAAC;QACJ;YACE,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,MAAoB,EAAE,IAAgB;IACzE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IACnC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM;YACT;;;;;eAKG;YACH,OAAO,QAAQ,CAAC,IAAI,EAAE,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,qBAAqB,CAAC,EAAE;gBACxE,MAAM;gBACN,OAAO;gBACP,UAAU;gBACV,UAAU;gBACV,UAAU;aACX,CAAC,CAAC;QACL,KAAK,KAAK;YACR,uEAAuE;YACvE,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,KAAK,EACL,uBAAuB,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,iBAAiB,CAAC,CAAC,EAAE,EAC7E,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,4BAA4B,CAAC,EAAE,CACvD,CACF,CAAC;QACJ;;;;;;;;WAQG;QACH,KAAK,OAAO,CAAC;QACb,KAAK,QAAQ;YACX,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,QAAQ,EACR,uBAAuB,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAC9E,CACF,CAAC;QACJ;YACE,MAAM,IAAI,KAAK,CAAC,4BAA4B,GAAG,EAAE,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,MAAM,CAAC,MAAoB,EAAE,IAAgB;IACjE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IACnC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ;;;;;;;;eAQG;YACH,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;gBAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,2EAA2E;oBACzE,6CAA6C,CAChD,CAAC;gBACF,OAAO,CAAC,CAAC;YACX,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE;gBACrD,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE;oBACrB,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,MAAM,EAAE,MAAM,CAAC;oBAChB,CAAC,OAAO,EAAE,OAAO,CAAC;oBAClB,CAAC,UAAU,EAAE,UAAU,CAAC;iBACzB,CAAC;aACH,CAAC,CAAC;YACH,OAAO,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;QAC9F,CAAC;QACD,KAAK,KAAK;YACR,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,KAAK,EACL,cAAc,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CAChE,CACF,CAAC;QACJ,KAAK,SAAS;YACZ,uEAAuE;YACvE,sEAAsE;YACtE,yDAAyD;YACzD,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAC,CAAC;QACvE,KAAK,WAAW;YACd,OAAO,UAAU,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,sBAAsB,CAAC,CAAC,CAAC;QACzE,KAAK,UAAU;YACb,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,KAAK,EACL,wBAAwB,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,iBAAiB,CAAC,CAAC,EAAE,CAC/E,CACF,CAAC;QACJ,KAAK,QAAQ;YACX,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC,CAClF,CAAC;QACJ,KAAK,QAAQ;YACX,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,OAAO,EACP,cAAc,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,EAC/D,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC,EAAE,CACjD,CACF,CAAC;QACJ,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,qEAAqE;YACrE,gDAAgD;YAChD,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,YAAY,CAAC,CAAC;YACvC,MAAM,MAAM,CAAC,OAAO,CAAC,QAAQ,EAAE,cAAc,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YACvE,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;gBAC7B,OAAO,UAAU,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CAAC;YACrC,CAAC;YACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;YACxC,OAAO,CAAC,CAAC;QACX,CAAC;QACD;YACE,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,EAAE,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAoB,EAAE,IAAgB;IACxE,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC;IACnC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,MAAM;YACT;;;;eAIG;YACH,OAAO,QAAQ,CACb,IAAI,EACJ,MAAM,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,oBAAoB,EAAE;gBAChD,KAAK,EAAE,SAAS,CAAC,IAAI,EAAE;oBACrB,CAAC,QAAQ,EAAE,QAAQ,CAAC;oBACpB,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;iBACzB,CAAC;aACH,CAAC,EACF,CAAC,IAAI,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,YAAY,EAAE,WAAW,CAAC,CACvE,CAAC;QACJ,KAAK,QAAQ,CAAC;QACd,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC;YACrC,uEAAuE;YACvE,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;YACzD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;gBACzB,MAAM,IAAI,KAAK,CACb,gEAAgE;oBAC9D,6CAA6C,CAChD,CAAC;YACJ,CAAC;YACD,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,sBAAsB,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE;gBAC5E,IAAI,EAAE,EAAE,MAAM,EAAE;aACjB,CAAC,CACH,CAAC;QACJ,CAAC;QACD,KAAK,OAAO;YACV,wEAAwE;YACxE,OAAO,UAAU,CACf,MAAM,MAAM,CAAC,OAAO,CAClB,MAAM,EACN,sBAAsB,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,UAAU,CAAC,CAAC,QAAQ,CAC5E,CACF,CAAC;QACJ;YACE,MAAM,IAAI,KAAK,CAAC,2BAA2B,GAAG,EAAE,CAAC,CAAC;IACtD,CAAC;AACH,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { type ParsedArgs } from '../args.js';
2
+ import type { ChatlyClient } from '@chatlychat/sdk-node';
3
+ export declare function reports(chatly: ChatlyClient, args: ParsedArgs): Promise<number>;
4
+ export declare function billing(chatly: ChatlyClient, args: ParsedArgs): Promise<number>;
5
+ //# sourceMappingURL=insights.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights.d.ts","sourceRoot":"","sources":["../../src/commands/insights.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7D,OAAO,KAAK,EAAE,YAAY,EAAkB,MAAM,sBAAsB,CAAC;AAyBzE,wBAAsB,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAyIrF;AAED,wBAAsB,OAAO,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAmIrF"}
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Numbers without opening the dashboard: reports, billing and usage.
3
+ *
4
+ * ── The window is NOT the same on every report, and that was a live bug ──
5
+ *
6
+ * These commands used to pass `--from`/`--to` to every report. Three of the
7
+ * four they covered do not read them: `reports/overview`, `reports/agents`
8
+ * and `reports/conversations-by-day` take `@Query('days')` and nothing else,
9
+ * so `chatly reports overview --from 2026-01-01 --to 2026-02-01` answered for
10
+ * the last SEVEN days and said nothing about it. That is the zod-strips-an-
11
+ * unknown-key failure in its query form: a confident report of the wrong
12
+ * window.
13
+ *
14
+ * So the window flags differ per report, deliberately, and each group below
15
+ * says which it accepts:
16
+ *
17
+ * --days overview, agents, conversations-by-day,
18
+ * sources, response-times, voice, attribution
19
+ * --weeks cohorts, and ONLY cohorts
20
+ * --date themes
21
+ * --from --to --days --group-by teams, labels, sla (`reports-extended`)
22
+ *
23
+ * The last group is the only one that resolves a real range, and its two
24
+ * snake_case names (`group_by`, `business_hours`) are the only snake_case
25
+ * query parameters on the whole SDK surface.
26
+ */
27
+ import { ATTRIBUTION_DIMENSIONS } from '@chatlychat/sdk-node';
28
+ import { bool, num, str } from '../args.js';
29
+ import { printJson } from '../output.js';
30
+ import { DAYS, RANGE, need, queryFrom, showObject, showRows } from './common.js';
31
+ /**
32
+ * The ten dimensions `reports/attribution` accepts.
33
+ *
34
+ * Listed here because the route REFUSES an unknown value rather than
35
+ * defaulting to `utm_source` — a typo quietly answered as something else
36
+ * would be a confident report of the wrong thing, and the caller would have
37
+ * no way to notice. Printing the list on a refusal saves a round trip.
38
+ */
39
+ /*
40
+ * Imported from the SDK, not re-listed. This file kept its own copy of the
41
+ * ten so it could print them on a refusal; the SDK now exports the array as
42
+ * a value for exactly that, so there is one list in this dependency chain
43
+ * instead of two.
44
+ */
45
+ /** The four meters `billing/usage` and `billing/usage/daily` accept. */
46
+ const USAGE_EVENT_TYPES = [
47
+ 'conversation.opened',
48
+ 'message.sent.agent',
49
+ 'message.sent.bot',
50
+ 'ai.tokens',
51
+ ];
52
+ export async function reports(chatly, args) {
53
+ const sub = args.path[1] ?? 'overview';
54
+ const days = () => queryFrom(args, DAYS);
55
+ switch (sub) {
56
+ // ── `?days` only ──────────────────────────────────────────────────────
57
+ case 'overview':
58
+ return showObject(await chatly.reports.overview(days()));
59
+ case 'agents':
60
+ // A BARE ARRAY, and the latency figures here are MEANS. The
61
+ // percentiles are on `reports response-times`.
62
+ return showRows(args, await chatly.reports.agents(days()), [
63
+ 'userId',
64
+ 'name',
65
+ 'conversations',
66
+ 'messages',
67
+ 'csatAverage',
68
+ ]);
69
+ case 'conversations-by-day':
70
+ // A bare array, and days with no conversations are ABSENT rather than
71
+ // zero — a caller plotting a series has to fill the gaps itself.
72
+ return showRows(args, await chatly.reports.conversationsByDay(days()), ['day', 'count']);
73
+ case 'sources':
74
+ return showRows(args, await chatly.request('GET', '/v1/reports/sources', { query: days() }), [
75
+ 'channelType',
76
+ 'channelId',
77
+ 'channelName',
78
+ 'conversations',
79
+ ]);
80
+ case 'response-times':
81
+ // p50/p95 for first response and resolution. Percentiles are null when
82
+ // `sampleSize` is 0, which is not the same as zero seconds.
83
+ return showObject(await chatly.request('GET', '/v1/reports/response-times', { query: days() }));
84
+ case 'voice':
85
+ // Rates are 0-1, not percentages, and `recordingStorage.exact: false`
86
+ // means the byte total is part extrapolation.
87
+ return showObject(await chatly.request('GET', '/v1/reports/voice', { query: days() }));
88
+ // ── `?dimension` + `?days` ────────────────────────────────────────────
89
+ case 'attribution': {
90
+ const dimension = str(args.flags, 'dimension') ?? args.path[2];
91
+ if (dimension !== undefined &&
92
+ !ATTRIBUTION_DIMENSIONS.includes(dimension)) {
93
+ throw new Error(`--dimension must be one of: ${ATTRIBUTION_DIMENSIONS.join(', ')}.\n` +
94
+ 'The route refuses an unknown value rather than defaulting, so a typo ' +
95
+ 'cannot become a report of the wrong thing.');
96
+ }
97
+ // The row with `value: null` is the UNTAGGED bucket and is a real row —
98
+ // dropping it inflates every other percentage.
99
+ return showRows(args, await chatly.request('GET', '/v1/reports/attribution', {
100
+ query: { ...days(), ...(dimension === undefined ? {} : { dimension }) },
101
+ }), ['value', 'conversations', 'closed', 'percentage']);
102
+ }
103
+ // ── `?weeks`, and only `?weeks` ───────────────────────────────────────
104
+ case 'cohorts': {
105
+ /*
106
+ * `weeks`, NOT `days`. A `--days` here would be stripped by the
107
+ * handler's query read and the report would silently use its default
108
+ * of 8 weeks. The response echoes the CLAMPED window it actually used
109
+ * (2-26), which can differ from what was asked for — label axes off
110
+ * the echo, not off the input.
111
+ */
112
+ const weeks = num(args.flags, 'weeks');
113
+ return showObject(await chatly.request('GET', '/v1/reports/cohorts', {
114
+ query: weeks === undefined ? {} : { weeks },
115
+ }));
116
+ }
117
+ // ── `?date` ───────────────────────────────────────────────────────────
118
+ case 'themes':
119
+ /*
120
+ * A SNAPSHOT, generated by a daily worker job. There is no regenerate.
121
+ * `batchDate` names the batch every row was measured over and is null
122
+ * with an empty list when none has been generated. Rows omit
123
+ * `conversationIds` — `reports theme-conversations <id>` serves those.
124
+ */
125
+ return showObject(await chatly.request('GET', '/v1/reports/themes', {
126
+ query: queryFrom(args, [['date', 'date']]),
127
+ }));
128
+ case 'theme-conversations':
129
+ // An unknown or purged theme id answers 200 with an EMPTY list, not a
130
+ // 404 — this route cannot tell the two apart, and neither can you.
131
+ return showObject(await chatly.request('GET', `/v1/reports/themes/${encodeURIComponent(need(args, 2, 'a theme id'))}/conversations`));
132
+ // ── The full window contract ──────────────────────────────────────────
133
+ case 'teams':
134
+ /*
135
+ * `businessHours.applied: false` means the duration figures are
136
+ * CALENDAR time and `businessHours.reason` says why. The numbers are
137
+ * never zeroed and never relabelled, so read the flag before captioning
138
+ * them.
139
+ */
140
+ return showObject(await chatly.request('GET', '/v1/reports/teams', { query: queryFrom(args, RANGE) }));
141
+ case 'labels':
142
+ // Capped at the 50 busiest labels, with the remainder in
143
+ // `otherLabels`; `trend` covers the top 8 only. There is no cursor, so
144
+ // there is no way to page to the tail.
145
+ return showObject(await chatly.request('GET', '/v1/reports/labels', { query: queryFrom(args, RANGE) }));
146
+ case 'sla':
147
+ /*
148
+ * `--business-hours` is accepted by the two above and IGNORED here: the
149
+ * handler reads the range and passes only that to the service. It is
150
+ * left in `RANGE` rather than special-cased because the parameter is
151
+ * harmless, but do not read an SLA figure as business-hours-adjusted.
152
+ *
153
+ * `breachesTruncated` is true when the breach list hit its 200-row cap.
154
+ */
155
+ return showObject(await chatly.reports.sla(queryFrom(args, RANGE)));
156
+ default:
157
+ throw new Error(`Unknown: reports ${sub}`);
158
+ }
159
+ }
160
+ export async function billing(chatly, args) {
161
+ const sub = args.path[1] ?? 'summary';
162
+ const range = () => {
163
+ const out = {};
164
+ const from = str(args.flags, 'from');
165
+ if (from !== undefined) {
166
+ out['from'] = from;
167
+ }
168
+ const to = str(args.flags, 'to');
169
+ if (to !== undefined) {
170
+ out['to'] = to;
171
+ }
172
+ return out;
173
+ };
174
+ /**
175
+ * The meter, from a positional or `--event-type`.
176
+ *
177
+ * Required by `/v1/billing/usage` and `/v1/billing/usage/daily`: both
178
+ * validate it against a closed list and throw before doing anything else,
179
+ * so a call without it is a guaranteed 400 rather than a summary of
180
+ * everything. Four SDKs shipped it as optional.
181
+ */
182
+ const meter = (what) => {
183
+ const value = args.path[2] ?? str(args.flags, 'event-type');
184
+ if (value === undefined) {
185
+ throw new Error(`Give a meter: \`chatly billing ${what} <event-type>\`.\n` +
186
+ `One of: ${USAGE_EVENT_TYPES.join(', ')}.\n` +
187
+ 'The route reports ONE meter over a window, not a summary of everything.');
188
+ }
189
+ /*
190
+ * Checked HERE rather than left to the server.
191
+ *
192
+ * The handler validates against this same closed list and 400s on an
193
+ * unknown value, so passing one through would turn a typo into an HTTP
194
+ * error a user has to decode. We already hold the list — telling them
195
+ * what the four are costs nothing and is the difference between a CLI
196
+ * and a thin curl wrapper.
197
+ *
198
+ * It also narrows the type: `billing.usage` takes `UsageEventType`, and
199
+ * returning a bare `string` is how this went out as `unknown` in four
200
+ * SDKs at once.
201
+ */
202
+ if (!USAGE_EVENT_TYPES.includes(value)) {
203
+ throw new Error(`\`${value}\` is not a meter.\n` + `One of: ${USAGE_EVENT_TYPES.join(', ')}.`);
204
+ }
205
+ return value;
206
+ };
207
+ switch (sub) {
208
+ case 'summary':
209
+ // `GET /v1/billing` — plan, seats, trial and cap state in one object.
210
+ return showObject(await chatly.request('GET', '/v1/billing'));
211
+ case 'plans':
212
+ return showRows(args, await chatly.billing.plans(), [
213
+ 'id',
214
+ 'name',
215
+ 'priceCents',
216
+ 'interval',
217
+ 'seats',
218
+ ]);
219
+ case 'usage':
220
+ return showObject(await chatly.billing.usage(meter('usage'), range()));
221
+ case 'usage-daily':
222
+ /*
223
+ * DENSE: one entry per UTC day in `[from, to)`, zeros included — unlike
224
+ * `reports conversations-by-day`, which omits empty days. The two look
225
+ * alike and are not interchangeable.
226
+ */
227
+ return showObject(await chatly.request('GET', '/v1/billing/usage/daily', {
228
+ query: { eventType: meter('usage-daily'), ...range() },
229
+ }));
230
+ case 'usage-monthly':
231
+ // One entry per known usage event type, ALWAYS all four, plus a `total`
232
+ // that is already summed. `--month` is a UTC-midnight ISO date.
233
+ return showObject(await chatly.request('GET', '/v1/billing/usage/monthly', {
234
+ query: queryFrom(args, [['month', 'month']]),
235
+ }));
236
+ case 'invoices': {
237
+ /*
238
+ * Cursor-paginated on the wire — `{ data, nextCursor }` — but its
239
+ * envelope key is `data`, not `items`, so the shared manifest does not
240
+ * flag it `paged` and no SDK offers a walker over it. `--all` is
241
+ * therefore not offered here; follow `--cursor` by hand.
242
+ */
243
+ if (bool(args.flags, 'all')) {
244
+ process.stderr.write('--all is not available for invoices: the route pages under a `data` key rather ' +
245
+ 'than the `items` envelope every other list uses, so there is no SDK walker ' +
246
+ 'for it. Follow the printed cursor with --cursor.\n');
247
+ return 1;
248
+ }
249
+ const page = await chatly.request('GET', '/v1/billing/invoices', {
250
+ query: queryFrom(args, [
251
+ ['cursor', 'cursor'],
252
+ ['limit', 'limit', true],
253
+ ]),
254
+ });
255
+ if (bool(args.flags, 'json')) {
256
+ printJson(page);
257
+ return 0;
258
+ }
259
+ showRows(args, page, [
260
+ 'id',
261
+ 'status',
262
+ 'amountCents',
263
+ 'currency',
264
+ 'periodStart',
265
+ 'hostedInvoiceUrl',
266
+ ]);
267
+ const next = page.nextCursor;
268
+ if (typeof next === 'string' && next.length > 0) {
269
+ process.stdout.write(`next page: --cursor ${next}\n`);
270
+ }
271
+ return 0;
272
+ }
273
+ default:
274
+ throw new Error(`Unknown: billing ${sub}`);
275
+ }
276
+ }
277
+ //# sourceMappingURL=insights.js.map