callkaro-cli-unofficial 0.10.0 → 0.10.1

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
@@ -19,7 +19,7 @@ cku accounts use tejas@acme.com
19
19
 
20
20
  **It holds several accounts at once.** The official CLI keeps one session. This one keeps a file per account, and can pin an account to a folder, so two agents working in two directories can't switch each other out from under themselves.
21
21
 
22
- **It reaches the dashboard API.** The official CLI only talks to `/cli/*`. That leaves the KPI board, the call analyzers, and the server's own model catalogue unreachable from a terminal — [`cku kpi`](#only-in-cku) is the whole of that surface.
22
+ **It reaches the dashboard API.** The official CLI only talks to `/cli/*`. That leaves the KPI board and the call analyzers unreachable from a terminal — [`cku kpi`](#only-in-cku) is the whole of that surface.
23
23
 
24
24
  **It fixes three upstream bugs.** Details and evidence in [`docs/BUG-REPORT.md`](docs/BUG-REPORT.md).
25
25
 
@@ -69,7 +69,7 @@ Every arrow above is a real import or a real call, checked against the source. `
69
69
  │ sim, analytics, audits, skills │
70
70
  │ ↓ │
71
71
  │ lib/ api.ts, config.ts, agentUpdate.ts, │
72
- │ looseJson.ts, ui.ts, catalog.ts │
72
+ │ looseJson.ts, ui.ts, liveTest.ts │
73
73
  └──────┬────────────────────────────────────────────┘
74
74
  ↓
75
75
  account resolution, highest precedence first
@@ -164,7 +164,7 @@ cku dashboard whoami
164
164
  cku dashboard probe # which dashboard routes this token reaches
165
165
  ```
166
166
 
167
- The dashboard API needs a *different* token from `/cli/*` — they're not interchangeable, and each rejects the other with an opaque 401. This also unlocks `cku analyzers` and `cku models`. The dashboard token expires every 24h with no refresh endpoint, so `cku dashboard login` saves your password (plain text, mode 0600, alongside the tokens) and re-logs in for you when it lapses. Pass `--no-save-password` to keep it off disk and re-enter it daily; `cku dashboard logout` deletes it.
167
+ The dashboard API needs a *different* token from `/cli/*` — they're not interchangeable, and each rejects the other with an opaque 401. This also unlocks `cku analyzers`. The dashboard token expires every 24h with no refresh endpoint, so `cku dashboard login` saves your password (plain text, mode 0600, alongside the tokens) and re-logs in for you when it lapses. Pass `--no-save-password` to keep it off disk and re-enter it daily; `cku dashboard logout` deletes it.
168
168
 
169
169
  ### The admin console credential
170
170
 
@@ -187,18 +187,7 @@ cku models # per slot, and which field each governs
187
187
  cku models --slot agent
188
188
  ```
189
189
 
190
- Models are scoped per slot and **the slots disagree**: `agent` has 44, `post-call` has 9 and excludes `gpt-4o` and `o4-mini`. So the same value is valid in `model` and refused in `postcallmodel`. Works with or without a dashboard session, and says which source it read.
191
-
192
- ### A catalogue that can't silently rot
193
-
194
- The provider tables, and now the model lists, are regenerated from the server rather than hand-copied:
195
-
196
- ```bash
197
- npm run catalog:sync # refresh from the bundle + the live server
198
- npm run catalog:check # fail if catalog.json disagrees with the snapshot
199
- ```
200
-
201
- `catalog:check` runs offline against a committed snapshot, so it works in CI with no credentials. On 1 October 2026 it caught shipped tables that listed withdrawn models *and* missed live ones in both directions. A wrong voice model otherwise persists silently and only surfaces as a broken call.
190
+ Models are scoped per slot and per account, and **the slots disagree**: `post-call` is narrower than `agent`, so the same value is valid in `model` and refused in `postcallmodel`. `cku models` and `cku transcribers` read the account's live lists (`/cli/llms`, `/cli/transcribers`, official 5.4.8), the same ones `agents update` checks before saving. There is no bundled list to go stale.
202
191
 
203
192
  ### Live test calls, with you or an LLM as the caller
204
193
 
@@ -230,7 +219,7 @@ This is the agent editor's **Test Chat** tab from a terminal: a real test call o
230
219
 
231
220
  ## Staying current
232
221
 
233
- A daily workflow checks npm for a newer `@callkaro-official/cli` and, when it finds one, regenerates the provider catalogue and opens a PR onto the `official` branch. It never merges anything itself.
222
+ A daily workflow checks npm for a newer `@callkaro-official/cli` and, when it finds one, opens a PR onto the `official` branch with the new pin and a report of which commands changed. It never merges anything itself.
234
223
 
235
224
  | Branch | What's on it |
236
225
  |---|---|
@@ -239,7 +228,7 @@ A daily workflow checks npm for a newer `@callkaro-official/cli` and, when it fi
239
228
 
240
229
  To take an update: review the PR, merge into `official`, then merge `official` into `main`.
241
230
 
242
- Worth knowing what the sync does and doesn't do. It regenerates `src/data/catalog.json` — the provider tables and the per-slot LLM model lists — because those change when the dashboard changes. It does **not** port command code, because the official source isn't available, so that's maintained by hand. The PR lists which commands were added or removed so you know what to look at.
231
+ Worth knowing what the sync does and doesn't do. It does **not** port command code, because the official source isn't available, so that's maintained by hand. The PR lists which commands were added or removed so you know what to look at.
243
232
 
244
233
  Keep your customizations in as few files as you can. That diff is the one you have to resolve on every update.
245
234
 
@@ -252,9 +241,6 @@ npm run build # tsc -> dist/, copy endpoints.json, chmod +x
252
241
  npm test # build, then node --test
253
242
  npm run test:only # tests without rebuilding
254
243
  npm run dev # build and run
255
- npm run catalog:sync # regenerate the catalogue: provider tables + per-slot LLM lists, from the server
256
- npm run catalog:check # fail if catalog.json disagrees with the server snapshot (offline)
257
- npm run catalog:check:live # same, against the live server (needs `cku dashboard login`)
258
244
  ```
259
245
 
260
246
  Single test file:
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentsCommitsCommand = agentsCommitsCommand;
4
+ exports.agentsCommitCommand = agentsCommitCommand;
5
+ exports.agentsRevertCommand = agentsRevertCommand;
6
+ exports.agentsCompareCommand = agentsCompareCommand;
7
+ const session_1 = require("../lib/session");
8
+ const ui_1 = require("../lib/ui");
9
+ const versions_1 = require("../lib/versions");
10
+ /** `cku agents commits <agentId> [--versions id] [--page n]` */
11
+ async function agentsCommitsCommand(agentId, opts) {
12
+ try {
13
+ const res = await (0, session_1.requireApi)().listAgentCommits(agentId, {
14
+ version: opts.versions ? (0, versions_1.firstVersion)(opts.versions) : undefined,
15
+ page: opts.page ? Number(opts.page) : undefined,
16
+ });
17
+ // Each commit carries the version's whole saved state, so even one page is large: the
18
+ // table shows the summary and --json/--file have everything.
19
+ if ((0, ui_1.output)(res, opts, `commits for ${agentId}`))
20
+ return;
21
+ if (!res.commits?.length)
22
+ return void (0, ui_1.info)('No commits found.');
23
+ (0, ui_1.table)(['COMMIT ID', 'VERSION', 'MESSAGE', 'BY', 'WHEN'], res.commits.map((c) => [
24
+ c._id, c.versionName || '', c.commitMessage || '', c.commited_by?.name || '',
25
+ String(c.createdAt || '').slice(0, 19).replace('T', ' '),
26
+ ]));
27
+ const { page, totalPages, totalDocs } = res.pagination || {};
28
+ (0, ui_1.info)(`page ${page ?? 1} of ${totalPages || 1} (${totalDocs ?? res.commits.length} commits)`);
29
+ }
30
+ catch (err) {
31
+ (0, ui_1.handleError)(err);
32
+ }
33
+ }
34
+ /** `cku agents commit <commitId>` */
35
+ async function agentsCommitCommand(commitId, opts) {
36
+ try {
37
+ const { commit: c } = await (0, session_1.requireApi)().getAgentCommit(commitId);
38
+ if ((0, ui_1.output)({ commit: c }, opts, `commit ${commitId}`))
39
+ return;
40
+ (0, ui_1.keyValue)({ id: c._id, version: c.versionName, versionId: c.versionId, message: c.commitMessage, by: c.commited_by?.name, at: c.createdAt });
41
+ (0, ui_1.info)('Use --json to see the full saved state.');
42
+ }
43
+ catch (err) {
44
+ (0, ui_1.handleError)(err);
45
+ }
46
+ }
47
+ /** `cku agents revert <agentId> <commitId> [--yes]` */
48
+ async function agentsRevertCommand(agentId, commitId, opts) {
49
+ try {
50
+ const api = (0, session_1.requireApi)();
51
+ if (!opts.yes) {
52
+ (0, ui_1.needTty)('Re-run with --yes to confirm.'); // before the lookup, so a refused revert sends nothing
53
+ // Name what is being restored: revert overwrites the version's current settings.
54
+ const { commit: c } = await api.getAgentCommit(commitId);
55
+ const what = `version "${c.versionName || c.versionId}" to commit ${commitId}${c.commitMessage ? ` ("${c.commitMessage}")` : ''}`;
56
+ if (!(await (0, ui_1.confirm)(`Revert ${what}? Its current settings are overwritten (a new commit is recorded).`)))
57
+ return;
58
+ }
59
+ const res = await api.revertAgentCommit(agentId, commitId);
60
+ if (opts.json)
61
+ return void console.log(JSON.stringify(res, null, 2));
62
+ (0, ui_1.success)(res.msg || 'Reverted.');
63
+ }
64
+ catch (err) {
65
+ (0, ui_1.handleError)(err);
66
+ }
67
+ }
68
+ const lines = (s) => (s ? s.split('\n').length : 0);
69
+ /** `cku agents compare <agentId> --a <id> --b <id> [--commits] [--field name]` — versions by default. */
70
+ async function agentsCompareCommand(agentId, opts) {
71
+ try {
72
+ if (!opts.a || !opts.b)
73
+ return (0, ui_1.handleError)(new Error('--a <id> and --b <id> are required (version ids, or commit ids with --commits)'));
74
+ const api = (0, session_1.requireApi)();
75
+ const res = opts.commits
76
+ ? await api.compareAgentCommits(agentId, opts.a, opts.b)
77
+ : await api.compareAgentVersions(agentId, [opts.a, opts.b]);
78
+ if ((0, ui_1.output)(res, opts, 'comparison'))
79
+ return;
80
+ const label = (s) => `${s.versionName || s.id} (${s.id})`;
81
+ (0, ui_1.info)(`A: ${label(res.left)}\nB: ${label(res.right)}\n`);
82
+ if (!res.changedFields.length)
83
+ return void console.log('No differences.');
84
+ if (opts.field) {
85
+ if (!res.changedFields.includes(opts.field)) {
86
+ return (0, ui_1.handleError)(new Error(`"${opts.field}" did not change. Changed: ${res.changedFields.join(', ')}`));
87
+ }
88
+ const t = res.textFieldDiffs[opts.field];
89
+ const a = t ? t.left : JSON.stringify(res.filteredA[opts.field], null, 2);
90
+ const b = t ? t.right : JSON.stringify(res.filteredB[opts.field], null, 2);
91
+ return void console.log(`--- A\n${a}\n\n--- B\n${b}`);
92
+ }
93
+ (0, ui_1.table)(['FIELD', 'CHANGE'], res.changedFields.map((f) => {
94
+ const t = res.textFieldDiffs[f];
95
+ return [f, t ? `text: ${lines(t.left)} → ${lines(t.right)} lines` : 'value changed'];
96
+ }));
97
+ (0, ui_1.info)(`\n${res.changedFields.length} field(s) differ. Use --field <name> to see one side by side, or --json for everything.`);
98
+ }
99
+ catch (err) {
100
+ (0, ui_1.handleError)(err);
101
+ }
102
+ }
@@ -5,10 +5,6 @@ exports.agentsPublishCommand = agentsPublishCommand;
5
5
  exports.agentsToggleActiveCommand = agentsToggleActiveCommand;
6
6
  exports.agentsAbCommand = agentsAbCommand;
7
7
  exports.agentsAbAdvancedCommand = agentsAbAdvancedCommand;
8
- exports.agentsCommitsCommand = agentsCommitsCommand;
9
- exports.agentsCommitShowCommand = agentsCommitShowCommand;
10
- exports.agentsDiffCommand = agentsDiffCommand;
11
- exports.agentsRevertCommand = agentsRevertCommand;
12
8
  exports.agentsSaveV2Command = agentsSaveV2Command;
13
9
  exports.agentsDeleteVersionCommand = agentsDeleteVersionCommand;
14
10
  exports.agentsDuplicateCommand = agentsDuplicateCommand;
@@ -20,6 +16,7 @@ const ui_1 = require("../lib/ui");
20
16
  const versions_1 = require("../lib/versions");
21
17
  const agentUpdate_1 = require("../lib/agentUpdate");
22
18
  const liveVoiceCheck_1 = require("../lib/liveVoiceCheck");
19
+ const liveCatalogCheck_1 = require("../lib/liveCatalogCheck");
23
20
  const setJson_1 = require("./setJson");
24
21
  /** Keys a clone may override. Anything else is rejected before the request. */
25
22
  const CLONE_OVERRIDE_KEYS = new Set([
@@ -61,7 +58,8 @@ async function agentsCloneVersionCommand(agentId, opts) {
61
58
  if (unsupported.length) {
62
59
  return (0, ui_1.handleError)(new Error(`Unsupported clone override(s): ${unsupported.join(', ')}\n allowed: ${[...CLONE_OVERRIDE_KEYS].join(', ')}`));
63
60
  }
64
- overrides = (0, agentUpdate_1.preparePatch)(parsed, { skipVoices: true }).patch;
61
+ overrides = (0, agentUpdate_1.preparePatch)(parsed).patch;
62
+ await (0, liveCatalogCheck_1.checkLiveCatalog)(api, overrides, async () => (await api.getAgent(agentId, sourceVersionId)).agent);
65
63
  await (0, liveVoiceCheck_1.checkVoiceConfigs)(api, { voice_configuration: overrides.voice_configuration, secondary_voice_configuration: overrides.secondary_voice_configuration }, sourceVersionId ? async () => (0, liveVoiceCheck_1.savedVoices)((await api.getAgent(agentId, sourceVersionId)).agent) : undefined);
66
64
  }
67
65
  const res = await api.cloneAgentVersion(agentId, sourceVersionId, {
@@ -238,103 +236,6 @@ async function agentsAbAdvancedCommand(agentId, opts) {
238
236
  (0, ui_1.handleError)(err);
239
237
  }
240
238
  }
241
- /** `cku agents commits <agentId>` */
242
- async function agentsCommitsCommand(agentId, opts) {
243
- try {
244
- const res = await (0, dashboardApi_1.dashboardApi)().agentCommits(agentId, {
245
- version: opts.versions ? (0, versions_1.firstVersion)(opts.versions) : undefined,
246
- page: Number(opts.page) || 1,
247
- pageSize: Number(opts.limit) || 25,
248
- });
249
- if (opts.json)
250
- return void console.log(JSON.stringify(res?.commits || res, null, 2));
251
- const rows = res?.commits || [];
252
- if (!rows.length)
253
- return void (0, ui_1.info)('No commits.');
254
- (0, ui_1.table)(['COMMIT ID', 'VERSION', 'MESSAGE', 'AUTHOR', 'CREATED'], rows.map((c) => [
255
- c._id,
256
- c.versionId,
257
- c.commitMessage || c.message || '',
258
- c.author?.name || c.author?.email || c.user?.email || '',
259
- c.createdAt,
260
- ]));
261
- }
262
- catch (err) {
263
- (0, ui_1.handleError)(err);
264
- }
265
- }
266
- /** `cku agents commit-show <commitId>` */
267
- async function agentsCommitShowCommand(commitId, opts) {
268
- try {
269
- const res = await (0, dashboardApi_1.dashboardApi)().agentCommit(commitId);
270
- const c = res?.commit || res;
271
- if (opts.json)
272
- return void console.log(JSON.stringify(c, null, 2));
273
- (0, ui_1.keyValue)({
274
- id: c?._id,
275
- versionId: c?.versionId,
276
- commitMessage: c?.commitMessage,
277
- createdAt: c?.createdAt,
278
- model: c?.model,
279
- voice_provider: c?.voice_provider,
280
- functions: (c?.functions || []).length,
281
- });
282
- }
283
- catch (err) {
284
- (0, ui_1.handleError)(err);
285
- }
286
- }
287
- /** `cku agents diff <agentId> --commits a,b | --versions a,b` */
288
- async function agentsDiffCommand(agentId, opts) {
289
- const usage = 'Pass --commits <idA,idB> or --versions <vA,vB> (two comma-separated ids).';
290
- const commits = opts.commits ? (0, versions_1.versionList)(opts.commits) : [];
291
- const versions = opts.versions ? (0, versions_1.versionList)(opts.versions) : [];
292
- const ids = commits.length ? commits : versions;
293
- if (ids.length < 2)
294
- return (0, ui_1.handleError)(new Error(usage));
295
- try {
296
- const api = (0, dashboardApi_1.dashboardApi)();
297
- const res = commits.length
298
- ? await api.compareAgentCommits(agentId, ids[0], ids[1])
299
- : await api.compareAgentVersions([ids[0], ids[1]]);
300
- if (opts.json)
301
- return void console.log(JSON.stringify(res, null, 2));
302
- const diff = res?.diff ?? res?.changes ?? res;
303
- if (Array.isArray(diff)) {
304
- if (!diff.length)
305
- return void (0, ui_1.info)('No differences.');
306
- (0, ui_1.table)(['FIELD', 'A', 'B'], diff.map((d) => [d.field || d.key || d.path || '', clipVal(d.a ?? d.before ?? d.from), clipVal(d.b ?? d.after ?? d.to)]));
307
- return;
308
- }
309
- const keys = diff && typeof diff === 'object' ? Object.keys(diff) : [];
310
- if (!keys.length)
311
- return void (0, ui_1.info)('No differences.');
312
- (0, ui_1.info)(`Changed keys (${keys.length}):`);
313
- keys.forEach((k) => (0, ui_1.info)(` ${k}`));
314
- (0, ui_1.info)('Full detail: add --json');
315
- }
316
- catch (err) {
317
- (0, ui_1.handleError)(err);
318
- }
319
- }
320
- function clipVal(v) {
321
- const t = v === undefined || v === null ? '' : typeof v === 'string' ? v : JSON.stringify(v);
322
- return t.length > 60 ? `${t.slice(0, 59)}…` : t;
323
- }
324
- /** `cku agents revert <agentId> <commitId>` */
325
- async function agentsRevertCommand(agentId, commitId, opts) {
326
- try {
327
- if (!(await (0, ui_1.confirm)(`Revert agent ${agentId} to commit ${commitId}?`, opts.yes)))
328
- return;
329
- const res = await (0, dashboardApi_1.dashboardApi)().revertAgentCommit(agentId, commitId);
330
- if (opts.json)
331
- return void console.log(JSON.stringify(res, null, 2));
332
- (0, ui_1.success)(`Reverted ${agentId} to commit ${commitId}.`);
333
- }
334
- catch (err) {
335
- (0, ui_1.handleError)(err);
336
- }
337
- }
338
239
  /** `cku agents save-v2 <agentId> --set <json>` */
339
240
  async function agentsSaveV2Command(agentId, opts) {
340
241
  try {
@@ -54,7 +54,7 @@ const versions_1 = require("../lib/versions");
54
54
  const agentUpdate_1 = require("../lib/agentUpdate");
55
55
  Object.defineProperty(exports, "preparePatch", { enumerable: true, get: function () { return agentUpdate_1.preparePatch; } });
56
56
  const liveVoiceCheck_1 = require("../lib/liveVoiceCheck");
57
- const dashboardAuth_1 = require("../lib/dashboardAuth");
57
+ const liveCatalogCheck_1 = require("../lib/liveCatalogCheck");
58
58
  const agentImport_1 = require("../lib/agentImport");
59
59
  const dbFields_1 = require("../lib/dbFields");
60
60
  const jsonInput_1 = require("./jsonInput");
@@ -395,13 +395,10 @@ async function agentsUpdateCommand(agentId, opts) {
395
395
  let versionKeys;
396
396
  let wholesaleKeys;
397
397
  try {
398
- // Validate model fields against the server's own per-slot list when we can.
399
- // Best-effort: without a dashboard session the built-in list still applies.
400
- await (0, dashboardAuth_1.refreshModelCatalog)();
401
398
  const dbf = (0, dbFields_1.dbManagedFields)(obj);
402
399
  if (dbf.length)
403
400
  throw new Error((0, dbFields_1.dbManagedFieldsError)(dbf, '--set object'));
404
- const prepared = (0, agentUpdate_1.preparePatch)(obj, { skipVoices: true });
401
+ const prepared = (0, agentUpdate_1.preparePatch)(obj);
405
402
  patch = prepared.patch;
406
403
  versionKeys = prepared.versionKeys;
407
404
  wholesaleKeys = prepared.wholesaleKeys;
@@ -423,6 +420,7 @@ async function agentsUpdateCommand(agentId, opts) {
423
420
  }
424
421
  try {
425
422
  const vid = (0, versions_1.firstVersion)(opts.versions);
423
+ await (0, liveCatalogCheck_1.checkLiveCatalog)(api, patch, vid ? async () => (await api.getAgent(agentId, vid)).agent : undefined);
426
424
  await (0, liveVoiceCheck_1.checkVoiceConfigs)(api, { voice_configuration: patch.voice_configuration, secondary_voice_configuration: patch.secondary_voice_configuration }, vid ? async () => (0, liveVoiceCheck_1.savedVoices)((await api.getAgent(agentId, vid)).agent) : undefined);
427
425
  let versionTarget = vid;
428
426
  if (opts.commit && !versionTarget)
@@ -0,0 +1,275 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.auditBatchesCommand = auditBatchesCommand;
4
+ exports.auditStrategyReportsCommand = auditStrategyReportsCommand;
5
+ exports.auditReportCommand = auditReportCommand;
6
+ exports.auditSummaryCommand = auditSummaryCommand;
7
+ exports.auditCallsCommand = auditCallsCommand;
8
+ exports.auditCallCommand = auditCallCommand;
9
+ exports.chatAuditList = chatAuditList;
10
+ exports.chatAuditReportCommand = chatAuditReportCommand;
11
+ exports.chatAuditAuditsCommand = chatAuditAuditsCommand;
12
+ exports.chatAuditOneCommand = chatAuditOneCommand;
13
+ const session_1 = require("../lib/session");
14
+ const ui_1 = require("../lib/ui");
15
+ const cell = (v, max = 60) => {
16
+ if (v === null || v === undefined)
17
+ return '';
18
+ if (typeof v === 'number' && !Number.isInteger(v))
19
+ return String(Number(v.toFixed(4))); // 1.9724110000000001 -> 1.9724
20
+ return (typeof v === 'object' ? JSON.stringify(v) : String(v)).replace(/\s+/g, ' ').slice(0, max);
21
+ };
22
+ const day = (v) => (v ? String(v).slice(0, 16).replace('T', ' ') : '');
23
+ const humanize = (k) => k.replace(/([a-z])([A-Z])/g, '$1 $2').replace(/_/g, ' ').toUpperCase();
24
+ const pct = (v) => (v === null || v === undefined || v === '' ? '-' : `${v}`);
25
+ /** Voice report lists page with {page,pageSize,hasNext}; summary lists with {page,page_size,has_next}. */
26
+ function pagingHint(p) {
27
+ if (!p)
28
+ return '';
29
+ const next = p.hasNext ?? p.has_next;
30
+ return `Page ${p.page ?? 1}${next ? ' (more: use --page)' : ''}`;
31
+ }
32
+ /** Table over whichever of `preferred` keys the rows actually have (rows come from several controllers). */
33
+ function rowsTable(rows, preferred, fallbackCount = 5) {
34
+ const keys = preferred.filter((k) => rows.some((r) => r[k] !== undefined));
35
+ const cols = keys.length ? keys : Object.keys(rows[0]).filter((k) => typeof rows[0][k] !== 'object').slice(0, fallbackCount);
36
+ (0, ui_1.table)(cols.map(humanize), rows.map((r) => cols.map((c) => cell(/time|At$|date/i.test(c) ? day(r[c]) : r[c]))));
37
+ }
38
+ /** Scalar fields in full (summaries are the point of a detail view), then every non-empty issue list. */
39
+ function printAuditDetail(d) {
40
+ const scalars = Object.entries(d).filter(([, v]) => v === null || typeof v !== 'object');
41
+ (0, ui_1.keyValue)(Object.fromEntries(scalars.map(([k, v]) => [k, cell(v, 600)])));
42
+ for (const [k, v] of Object.entries(d)) {
43
+ if (!Array.isArray(v) || !v.length || !/issues$|errors$/.test(k))
44
+ continue;
45
+ console.log(`\n${k.replace(/_/g, ' ')} (${v.length})`);
46
+ v.forEach((it, i) => {
47
+ if (!it || typeof it !== 'object')
48
+ return void console.log(` ${i + 1}. ${cell(it, 300)}`);
49
+ console.log(` ${i + 1}. ${it.severity ? `[${it.severity}] ` : ''}${cell(it.problem ?? it.message ?? it.error ?? JSON.stringify(it), 400)}`);
50
+ for (const f of ['proof', 'expected', 'actual'])
51
+ if (it[f])
52
+ console.log(` ${f}: ${cell(it[f], 300)}`);
53
+ });
54
+ }
55
+ }
56
+ const scopeOf = (id, byStrategy) => (byStrategy ? `strategies/${id}` : `batches/${id}`);
57
+ const dates = (o) => ({ startDate: o.from, endDate: o.to });
58
+ // ───────── voice ─────────
59
+ /** `cku audit batches [--agent id] [--from d --to d]` */
60
+ async function auditBatchesCommand(opts) {
61
+ try {
62
+ const res = await (0, session_1.requireApi)().getAuditData('audit-reports/batches', { agentId: opts.agent, ...dates(opts), page: opts.page, pageSize: opts.pageSize });
63
+ if ((0, ui_1.output)(res, opts, 'audit batches'))
64
+ return;
65
+ if (!res.data?.length)
66
+ return void console.log('No audit batches found.');
67
+ rowsTable(res.data, ['id', 'strategyName', 'agentName', 'status', 'totalCalls', 'avgOverallScore', 'lastAuditTime']);
68
+ (0, ui_1.info)(pagingHint(res.pagination));
69
+ }
70
+ catch (err) {
71
+ (0, ui_1.handleError)(err);
72
+ }
73
+ }
74
+ /** `cku audit strategy-reports [--agent id] [--from d --to d]` */
75
+ async function auditStrategyReportsCommand(opts) {
76
+ try {
77
+ const res = await (0, session_1.requireApi)().getAuditData('audit-reports/strategies', { agentId: opts.agent, ...dates(opts), page: opts.page, pageSize: opts.pageSize });
78
+ if ((0, ui_1.output)(res, opts, 'strategy reports'))
79
+ return;
80
+ if (!res.data?.length)
81
+ return void console.log('No audit reports found.');
82
+ rowsTable(res.data, ['id', 'strategyName', 'agentName', 'batchCount', 'auditedCalls', 'failedCalls', 'avgOverallScore', 'lastAuditTime']);
83
+ (0, ui_1.info)(pagingHint(res.pagination));
84
+ }
85
+ catch (err) {
86
+ (0, ui_1.handleError)(err);
87
+ }
88
+ }
89
+ /** `cku audit report <id> [--by-strategy]` */
90
+ async function auditReportCommand(id, opts) {
91
+ try {
92
+ const res = await (0, session_1.requireApi)().getAuditData(`audit-reports/${scopeOf(id, opts.byStrategy)}`, {
93
+ ...(opts.byStrategy ? dates(opts) : {}), page: opts.page, pageSize: opts.pageSize,
94
+ });
95
+ if ((0, ui_1.output)(res, opts, 'audit report'))
96
+ return;
97
+ const head = res.batch || res.strategy || {}; // batch scope returns `batch`, strategy scope `strategy`
98
+ (0, ui_1.keyValue)({
99
+ id: head.id, strategy: head.strategyName || head.name, agent: head.agentName, status: head.status, batches: head.batchCount,
100
+ created: day(head.createdAt) || undefined, completed: day(head.completedAt) || undefined,
101
+ });
102
+ const tiles = Array.isArray(res.data) ? res.data : [];
103
+ if (tiles.length) {
104
+ console.log();
105
+ (0, ui_1.keyValue)(Object.fromEntries(tiles.map((t) => [t.name, pct(t.value)])));
106
+ }
107
+ if (res.calls?.length) {
108
+ console.log();
109
+ rowsTable(res.calls, ['call_id', 'callId', 'id', '_id', 'status', 'overall_score', 'overallScore', 'audit_timestamp', 'auditTimestamp']);
110
+ (0, ui_1.info)(`${res.pagination?.totalDocs ?? res.calls.length} call(s). ${pagingHint(res.pagination)}`);
111
+ }
112
+ }
113
+ catch (err) {
114
+ (0, ui_1.handleError)(err);
115
+ }
116
+ }
117
+ /** `cku audit summary <id> [--by-strategy] [--severity s] [--dimension d]` */
118
+ async function auditSummaryCommand(id, opts) {
119
+ try {
120
+ const res = await (0, session_1.requireApi)().getAuditData(`audit-summary/${scopeOf(id, opts.byStrategy)}/summary`, {
121
+ ...(opts.byStrategy ? dates(opts) : {}), severity: opts.severity, dimension: opts.dimension,
122
+ });
123
+ if ((0, ui_1.output)(res, opts, 'audit summary'))
124
+ return;
125
+ const k = res.kpis || {};
126
+ (0, ui_1.keyValue)({
127
+ calls: k.total_calls, 'avg score': k.avg_score, 'failed audits': k.failed_audits, 'total issues': k.total_issues,
128
+ cost: k.total_cost, started: day(k.started_at), finished: day(k.finished_at),
129
+ });
130
+ if (res.scores_by_dimension) {
131
+ console.log('\nScores by dimension');
132
+ (0, ui_1.keyValue)(Object.fromEntries(Object.entries(res.scores_by_dimension).map(([a, b]) => [a, cell(b)])));
133
+ }
134
+ if (res.issues_by_severity?.length) {
135
+ console.log('\nIssues by severity');
136
+ rowsTable(res.issues_by_severity, ['severity', 'occurrences']);
137
+ }
138
+ if (res.categories?.length) {
139
+ console.log('\nCategories');
140
+ rowsTable(res.categories, ['dimension', 'category', 'occurrences', 'calls_affected', 'critical', 'high', 'medium', 'low']);
141
+ }
142
+ if (res.top_issue_calls?.length) {
143
+ console.log('\nTop issue calls');
144
+ rowsTable(res.top_issue_calls, ['call_id', 'score', 'dimension', 'category', 'problem', 'critical', 'high']);
145
+ }
146
+ (0, ui_1.info)('\nDrill in with `cku audit calls <id> --severity|--below-80|--category|--var`, or `cku audit call <id> <callId>`.');
147
+ }
148
+ catch (err) {
149
+ (0, ui_1.handleError)(err);
150
+ }
151
+ }
152
+ /** `cku audit calls <id> (--severity s | --below-80 | --category dimension:category | --var name=value)` */
153
+ async function auditCallsCommand(id, opts) {
154
+ try {
155
+ const chosen = [opts.severity, opts.below80, opts.category, opts.var].filter(Boolean).length;
156
+ if (chosen !== 1) {
157
+ return (0, ui_1.handleError)(new Error('Pick exactly one filter: --severity <s> | --below-80 | --category <dimension:category> | --var <name=value>'));
158
+ }
159
+ const base = `audit-summary/${scopeOf(id, opts.byStrategy)}`;
160
+ let path;
161
+ if (opts.severity)
162
+ path = `${base}/severities/${encodeURIComponent(opts.severity.toLowerCase())}/calls`;
163
+ else if (opts.below80)
164
+ path = `${base}/scores/below-80/calls`;
165
+ else if (opts.category) {
166
+ const [dimension, ...rest] = opts.category.split(':');
167
+ if (!dimension || !rest.length)
168
+ return (0, ui_1.handleError)(new Error('--category must be <dimension:category>, as listed by `cku audit summary`'));
169
+ path = `${base}/categories/${encodeURIComponent(dimension)}/${encodeURIComponent(rest.join(':'))}/calls`;
170
+ }
171
+ else {
172
+ const eq = opts.var.indexOf('=');
173
+ if (eq < 1)
174
+ return (0, ui_1.handleError)(new Error('--var must be <name=value>'));
175
+ path = `${base}/extra-variables/${encodeURIComponent(opts.var.slice(0, eq))}/${encodeURIComponent(opts.var.slice(eq + 1))}/calls`;
176
+ }
177
+ const res = await (0, session_1.requireApi)().getAuditData(path, {
178
+ ...(opts.byStrategy ? dates(opts) : {}), page: opts.page, page_size: opts.pageSize, sort: opts.sort,
179
+ });
180
+ if ((0, ui_1.output)(res, opts, 'audited calls'))
181
+ return;
182
+ if (!res.calls?.length)
183
+ return void console.log('No calls match.');
184
+ rowsTable(res.calls, ['call_id', 'score', 'status', 'problem', 'occurrences', 'issue_count', 'audit_timestamp']);
185
+ (0, ui_1.info)(pagingHint(res.pagination));
186
+ }
187
+ catch (err) {
188
+ (0, ui_1.handleError)(err);
189
+ }
190
+ }
191
+ /** `cku audit call <id> <callId> [--by-strategy]` */
192
+ async function auditCallCommand(id, callId, opts) {
193
+ try {
194
+ const res = await (0, session_1.requireApi)().getAuditData(`audit-summary/${scopeOf(id, opts.byStrategy)}/calls/${encodeURIComponent(callId)}`, opts.byStrategy ? dates(opts) : {});
195
+ if ((0, ui_1.output)(res, opts, `audited call ${callId}`))
196
+ return;
197
+ printAuditDetail(res);
198
+ (0, ui_1.info)('\n--json has every field.');
199
+ }
200
+ catch (err) {
201
+ (0, ui_1.handleError)(err);
202
+ }
203
+ }
204
+ // ───────── chat ─────────
205
+ const chatRows = ['id', 'strategyName', 'agentName', 'status', 'trigger', 'totalChats', 'failedChats', 'avgOverallScore', 'totalIssues', 'totalCost', 'lastAuditTime'];
206
+ /** `cku chat-audit batches|strategy-reports [--agent id] [--from d --to d]` */
207
+ function chatAuditList(kind) {
208
+ return async (opts) => {
209
+ try {
210
+ const res = await (0, session_1.requireApi)().getAuditData(`chat-audit-reports/${kind}`, { agentId: opts.agent, ...dates(opts), page: opts.page, pageSize: opts.pageSize });
211
+ if ((0, ui_1.output)(res, opts, `chat audit ${kind}`))
212
+ return;
213
+ if (!res.data?.length)
214
+ return void console.log('No chat audit reports found.');
215
+ rowsTable(res.data, chatRows);
216
+ (0, ui_1.info)(pagingHint(res.pagination));
217
+ }
218
+ catch (err) {
219
+ (0, ui_1.handleError)(err);
220
+ }
221
+ };
222
+ }
223
+ /** `cku chat-audit report <id> [--by-strategy]` */
224
+ async function chatAuditReportCommand(id, opts) {
225
+ try {
226
+ const res = await (0, session_1.requireApi)().getAuditData(`chat-audit-reports/${scopeOf(id, opts.byStrategy)}`, dates(opts));
227
+ if ((0, ui_1.output)(res, opts, 'chat audit report'))
228
+ return;
229
+ const d = res.data || {};
230
+ (0, ui_1.keyValue)(Object.fromEntries(Object.entries(d).filter(([, v]) => v === null || typeof v !== 'object').map(([k, v]) => [k, cell(v)])));
231
+ if (d.issuesBySeverity) {
232
+ console.log('\nIssues by severity');
233
+ (0, ui_1.keyValue)(Object.fromEntries(Object.entries(d.issuesBySeverity).map(([k, v]) => [k, cell(v)])));
234
+ }
235
+ if (Array.isArray(d.issuesByDimension) && d.issuesByDimension.length) {
236
+ console.log('\nIssues by dimension');
237
+ rowsTable(d.issuesByDimension, ['dimension', 'occurrences', 'chatsAffected']);
238
+ }
239
+ (0, ui_1.info)('\nSee the conversations with `cku chat-audit audits <id>`; use --json for everything.');
240
+ }
241
+ catch (err) {
242
+ (0, ui_1.handleError)(err);
243
+ }
244
+ }
245
+ /** `cku chat-audit audits <id> [--by-strategy] [--status s] [--sort s]` */
246
+ async function chatAuditAuditsCommand(id, opts) {
247
+ try {
248
+ const res = await (0, session_1.requireApi)().getAuditData(`chat-audit-reports/${scopeOf(id, opts.byStrategy)}/audits`, {
249
+ ...dates(opts), status: opts.status, sort: opts.sort, page: opts.page, pageSize: opts.pageSize,
250
+ });
251
+ if ((0, ui_1.output)(res, opts, 'chat audits'))
252
+ return;
253
+ if (!res.data?.length)
254
+ return void console.log('No audits match.');
255
+ rowsTable(res.data, ['id', 'userPhone', 'status', 'overall', 'chatflow', 'functionCall', 'system', 'issues', 'problem', 'auditTime']);
256
+ (0, ui_1.info)(pagingHint(res.pagination));
257
+ }
258
+ catch (err) {
259
+ (0, ui_1.handleError)(err);
260
+ }
261
+ }
262
+ /** `cku chat-audit audit <auditId>` */
263
+ async function chatAuditOneCommand(auditId, opts) {
264
+ try {
265
+ const res = await (0, session_1.requireApi)().getAuditData(`chat-audit-reports/audits/${encodeURIComponent(auditId)}`);
266
+ if ((0, ui_1.output)(res, opts, `chat audit ${auditId}`))
267
+ return;
268
+ const d = res.data || {};
269
+ printAuditDetail(d);
270
+ (0, ui_1.info)(`\n${Array.isArray(d.messages) ? d.messages.length : 0} message(s) in the audited window. --json has the full conversation.`);
271
+ }
272
+ catch (err) {
273
+ (0, ui_1.handleError)(err);
274
+ }
275
+ }