callkaro-cli-unofficial 0.9.5 → 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 +14 -15
- package/dist/commands/agentCommits.js +102 -0
- package/dist/commands/agentVersions.js +3 -102
- package/dist/commands/agents.js +3 -5
- package/dist/commands/auditReports.js +275 -0
- package/dist/commands/chatAgents.js +41 -17
- package/dist/commands/chatAuditStrategies.js +0 -20
- package/dist/commands/contacts.js +32 -15
- package/dist/commands/liveTest.js +330 -0
- package/dist/commands/models.js +46 -88
- package/dist/commands/platformExtras.js +0 -25
- package/dist/commands/simulations.js +25 -2
- package/dist/commands/transcribers.js +36 -65
- package/dist/commands/update.js +73 -27
- package/dist/commands/whatsappStats.js +105 -0
- package/dist/commands/workflows.js +6 -18
- package/dist/index.js +171 -26
- package/dist/lib/agentUpdate.js +4 -116
- package/dist/lib/api.js +41 -0
- package/dist/lib/config.js +1 -1
- package/dist/lib/dashboardApi.js +1 -43
- package/dist/lib/dashboardAuth.js +0 -33
- package/dist/lib/liveCatalogCheck.js +99 -0
- package/dist/lib/liveTest.js +361 -0
- package/dist/lib/ui.js +15 -0
- package/dist/lib/updateCheck.js +3 -2
- package/package.json +6 -6
- package/dist/data/catalog.json +0 -1976
- package/dist/lib/catalog.js +0 -136
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
|
|
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,
|
|
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
|
|
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,21 +187,23 @@ 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**: `
|
|
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.
|
|
191
191
|
|
|
192
|
-
###
|
|
193
|
-
|
|
194
|
-
The provider tables, and now the model lists, are regenerated from the server rather than hand-copied:
|
|
192
|
+
### Live test calls, with you or an LLM as the caller
|
|
195
193
|
|
|
196
194
|
```bash
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
cku test start <agentId> --yes # a coding agent (or you) plays the caller, one turn per command
|
|
196
|
+
cku test say <sessionId> "Kitne ka hai?"
|
|
197
|
+
cku test end <sessionId> # transcript with function calls and node switches
|
|
198
|
+
|
|
199
|
+
cku test run <agentId> --test <testCaseId> --llm anthropic --yes # the CLI plays the caller and grades the call
|
|
199
200
|
```
|
|
200
201
|
|
|
201
|
-
|
|
202
|
+
This is the agent editor's **Test Chat** tab from a terminal: a real test call on the real call runtime, over the same LiveKit room the dashboard joins, with the caller typing instead of speaking. Unlike a simulation, nothing is mocked, so node switching, function calls and post-call analysis are what production does, and every function runs for real. `run` uses any OpenAI-compatible LLM (OpenAI, Anthropic, Gemini, DeepSeek, Groq, OpenRouter, Ollama, or `--llm-url`), with the key read from the environment, and exits 1 when the judge fails the call. It needs `@livekit/rtc-node`, an optional dependency with prebuilt binaries for Linux, macOS and Windows.
|
|
202
203
|
|
|
203
204
|
### Smaller things
|
|
204
205
|
|
|
206
|
+
- `cku sim create --real-functions` — every function on the version runs for real in the simulation, like switching every Mock off in the dashboard
|
|
205
207
|
- `cku agents chat` — text chat with an agent's real prompt and tools, no phone call. Scriptable with `--say`, replayable with `--history`. Custom in-call functions run for real
|
|
206
208
|
- `cku scheduled list|queued|get` — scheduled calls and the queue of what will actually dial (dashboard login)
|
|
207
209
|
- `cku sim update` — edit a test case in place
|
|
@@ -217,7 +219,7 @@ npm run catalog:check # fail if catalog.json disagrees with the snapshot
|
|
|
217
219
|
|
|
218
220
|
## Staying current
|
|
219
221
|
|
|
220
|
-
A daily workflow checks npm for a newer `@callkaro-official/cli` and, when it finds one,
|
|
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.
|
|
221
223
|
|
|
222
224
|
| Branch | What's on it |
|
|
223
225
|
|---|---|
|
|
@@ -226,7 +228,7 @@ A daily workflow checks npm for a newer `@callkaro-official/cli` and, when it fi
|
|
|
226
228
|
|
|
227
229
|
To take an update: review the PR, merge into `official`, then merge `official` into `main`.
|
|
228
230
|
|
|
229
|
-
Worth knowing what the sync does and doesn't do. It
|
|
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.
|
|
230
232
|
|
|
231
233
|
Keep your customizations in as few files as you can. That diff is the one you have to resolve on every update.
|
|
232
234
|
|
|
@@ -239,9 +241,6 @@ npm run build # tsc -> dist/, copy endpoints.json, chmod +x
|
|
|
239
241
|
npm test # build, then node --test
|
|
240
242
|
npm run test:only # tests without rebuilding
|
|
241
243
|
npm run dev # build and run
|
|
242
|
-
npm run catalog:sync # regenerate the catalogue: provider tables + per-slot LLM lists, from the server
|
|
243
|
-
npm run catalog:check # fail if catalog.json disagrees with the server snapshot (offline)
|
|
244
|
-
npm run catalog:check:live # same, against the live server (needs `cku dashboard login`)
|
|
245
244
|
```
|
|
246
245
|
|
|
247
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
|
|
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 {
|
package/dist/commands/agents.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
+
}
|