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.
@@ -44,16 +44,14 @@ exports.chatAgentsCreateCommand = chatAgentsCreateCommand;
44
44
  exports.chatAgentsUpdateCommand = chatAgentsUpdateCommand;
45
45
  exports.chatAgentsCreateVersionCommand = chatAgentsCreateVersionCommand;
46
46
  exports.chatAgentsPublishCommand = chatAgentsPublishCommand;
47
- exports.chatAgentsDiffCommand = chatAgentsDiffCommand;
47
+ exports.chatAgentsCompareCommand = chatAgentsCompareCommand;
48
48
  exports.chatAgentsDeleteVersionCommand = chatAgentsDeleteVersionCommand;
49
49
  exports.chatAgentsImportVersionsCommand = chatAgentsImportVersionsCommand;
50
50
  exports.chatAgentsImportCommand = chatAgentsImportCommand;
51
51
  exports.chatAgentsCommitsCommand = chatAgentsCommitsCommand;
52
52
  exports.chatAgentsCommitCommand = chatAgentsCommitCommand;
53
53
  exports.chatAgentsRevertCommand = chatAgentsRevertCommand;
54
- const dashboardApi_1 = require("../lib/dashboardApi");
55
54
  const session_1 = require("../lib/session");
56
- const dashboardAuth_1 = require("../lib/dashboardAuth");
57
55
  const fs_1 = require("fs");
58
56
  const ui_1 = require("../lib/ui");
59
57
  const looseJson_1 = require("../lib/looseJson");
@@ -218,7 +216,6 @@ async function chatAgentsCreateCommand(json, opts) {
218
216
  async function chatAgentsUpdateCommand(agentId, opts) {
219
217
  const api = (0, session_1.requireApi)();
220
218
  try {
221
- await (0, dashboardAuth_1.refreshModelCatalog)();
222
219
  const patch = await readChatAgentJson(opts.set, undefined, '--set object');
223
220
  const res = await api.updateChatAgent(agentId, (0, versions_1.firstVersion)(opts.versions), patch);
224
221
  if (opts.json)
@@ -261,20 +258,47 @@ async function chatAgentsPublishCommand(agentId, opts) {
261
258
  }
262
259
  }
263
260
  /** `cku chat-agents diff <chatAgentId> --versions vA,vB | --commits cA,cB` */
264
- async function chatAgentsDiffCommand(chatAgentId, opts) {
261
+ const lineCount = (s) => (s ? s.split('\n').length : 0);
262
+ /** `cku chat-agents compare <agentId> --a <id> --b <id> [--commits] [--field name]` — versions by default (official 5.4.8). */
263
+ async function chatAgentsCompareCommand(agentId, opts) {
265
264
  try {
266
- const pair = (s, flag) => {
267
- const p = (s || '').split(',').map((x) => x.trim()).filter(Boolean);
268
- if (p.length !== 2)
269
- throw new Error(`${flag} needs exactly two ids, comma-separated.`);
270
- return [p[0], p[1]];
271
- };
272
- if (!!opts.versions === !!opts.commits)
273
- throw new Error('Pass exactly one of --versions <vA,vB> or --commits <cA,cB>.');
274
- const res = opts.versions
275
- ? await (0, dashboardApi_1.dashboardApi)().compareChatAgentVersions(pair(opts.versions, '--versions'))
276
- : await (0, dashboardApi_1.dashboardApi)().compareChatAgentCommits(chatAgentId, ...pair(opts.commits, '--commits'));
277
- console.log(JSON.stringify(res, null, 2));
265
+ if (!opts.a || !opts.b)
266
+ return (0, ui_1.handleError)(new Error('--a <id> and --b <id> are required (version ids, or commit ids with --commits)'));
267
+ const api = (0, session_1.requireApi)();
268
+ const res = opts.commits
269
+ ? await api.compareChatAgentCommits(agentId, opts.a, opts.b)
270
+ : await api.compareChatAgentVersions(agentId, [opts.a, opts.b]);
271
+ if ((0, ui_1.output)(res, opts, 'comparison'))
272
+ return;
273
+ const label = (s) => `${s.versionName || s.id} (${s.id})${s.agentMode ? ` [${s.agentMode}]` : ''}`;
274
+ (0, ui_1.info)(`A: ${label(res.left)}\nB: ${label(res.right)}\n`);
275
+ if (res.modeMismatch)
276
+ (0, ui_1.info)(`Different agent types (${res.modeMismatch.left} vs ${res.modeMismatch.right}): only fields both use are compared.\n`);
277
+ if (opts.field) {
278
+ const t = res.textFieldDiffs[opts.field];
279
+ const c = res.changedFields[opts.field];
280
+ if (!t && !c)
281
+ return (0, ui_1.handleError)(new Error(`"${opts.field}" did not change. Changed: ${Object.keys(res.changedFields).join(', ') || '(none)'}`));
282
+ const a = t ? t.left : JSON.stringify(c.left, null, 2);
283
+ const b = t ? t.right : JSON.stringify(c.right, null, 2);
284
+ return void console.log(`--- A\n${a}\n\n--- B\n${b}`);
285
+ }
286
+ const rows = [];
287
+ for (const f of Object.keys(res.changedFields)) {
288
+ const t = res.textFieldDiffs[f];
289
+ const l = res.listDiffs?.[f];
290
+ rows.push([f, t ? `text: ${lineCount(t.left)} → ${lineCount(t.right)} lines` : l ? `+${l.added.length} -${l.removed.length} ~${l.changed.length}` : 'value changed']);
291
+ }
292
+ if (res.nodeDiffs)
293
+ rows.push(['flowGraph nodes', `+${res.nodeDiffs.added.length} -${res.nodeDiffs.removed.length} ~${res.nodeDiffs.changed.length}`]);
294
+ if (!rows.length)
295
+ console.log('No differences.');
296
+ else {
297
+ (0, ui_1.table)(['FIELD', 'CHANGE'], rows);
298
+ (0, ui_1.info)(`\n${rows.length} difference(s). Use --field <name> to see one side by side, or --json for everything (incl. per-node and per-function diffs).`);
299
+ }
300
+ if (res.notComparable?.length)
301
+ (0, ui_1.info)(`Not comparable (not used by both agent types): ${res.notComparable.join(', ')}`);
278
302
  }
279
303
  catch (err) {
280
304
  (0, ui_1.handleError)(err);
@@ -7,8 +7,6 @@ exports.chatAuditCreateCommand = chatAuditCreateCommand;
7
7
  exports.chatAuditUpdateCommand = chatAuditUpdateCommand;
8
8
  exports.chatAuditDeleteCommand = chatAuditDeleteCommand;
9
9
  exports.chatAuditBatchCommand = chatAuditBatchCommand;
10
- exports.chatAuditReportsCommand = chatAuditReportsCommand;
11
- const dashboardApi_1 = require("../lib/dashboardApi");
12
10
  const session_1 = require("../lib/session");
13
11
  const ui_1 = require("../lib/ui");
14
12
  const setJson_1 = require("./setJson");
@@ -142,21 +140,3 @@ async function chatAuditBatchCommand(agentId, strategyId, opts) {
142
140
  (0, ui_1.handleError)(err);
143
141
  }
144
142
  }
145
- /** `cku chat-audit reports [--agent <id>] [--page <n>]` — dashboard session. */
146
- async function chatAuditReportsCommand(opts) {
147
- try {
148
- const res = await (0, dashboardApi_1.dashboardApi)().chatAuditReportBatches({
149
- agentId: opts.agent,
150
- page: opts.page ? Number(opts.page) : undefined,
151
- });
152
- if (opts.json)
153
- return void console.log(JSON.stringify(res, null, 2));
154
- const rows = res?.data?.batches || res?.batches || (Array.isArray(res?.data) ? res.data : []);
155
- if (!rows.length)
156
- return void (0, ui_1.info)('No chat audit report batches.');
157
- (0, ui_1.table)(['ID', 'STATUS', 'CREATED'], rows.map((r) => [r._id || r.id || '', r.status || '', r.createdAt || '']));
158
- }
159
- catch (err) {
160
- (0, ui_1.handleError)(err);
161
- }
162
- }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.collect = void 0;
4
4
  exports.contactsListCommand = contactsListCommand;
5
+ exports.contactsMessagesCommand = contactsMessagesCommand;
5
6
  exports.contactsGetCommand = contactsGetCommand;
6
7
  exports.contactsCreateCommand = contactsCreateCommand;
7
8
  exports.contactsUpdateCommand = contactsUpdateCommand;
@@ -53,18 +54,6 @@ const attrValue = (c, name) => {
53
54
  const v = a && typeof a === 'object' && 'value' in a ? a.value : a;
54
55
  return v === undefined || v === null ? '' : typeof v === 'object' ? JSON.stringify(v) : String(v);
55
56
  };
56
- function output(value, opts, label) {
57
- if (opts.file) {
58
- (0, fs_1.writeFileSync)(opts.file, JSON.stringify(value, null, 2));
59
- (0, ui_1.success)(`Saved ${label} to ${opts.file}`);
60
- return true;
61
- }
62
- if (opts.json) {
63
- console.log(JSON.stringify(value, null, 2));
64
- return true;
65
- }
66
- return false;
67
- }
68
57
  // ---- contacts -------------------------------------------------------------
69
58
  async function contactsListCommand(opts) {
70
59
  const api = (0, session_1.requireApi)();
@@ -75,7 +64,7 @@ async function contactsListCommand(opts) {
75
64
  page: opts.page,
76
65
  pageSize: opts.pageSize,
77
66
  });
78
- if (output({ contacts, pagination }, opts, 'contacts'))
67
+ if ((0, ui_1.output)({ contacts, pagination }, opts, 'contacts'))
79
68
  return;
80
69
  if (!contacts.length)
81
70
  return void (0, ui_1.info)('No contacts match.');
@@ -89,11 +78,39 @@ async function contactsListCommand(opts) {
89
78
  (0, ui_1.handleError)(err);
90
79
  }
91
80
  }
81
+ /** `cku contacts messages <phone>` — a contact's chat messages, newest first (official 5.4.8). */
82
+ async function contactsMessagesCommand(phone, opts) {
83
+ try {
84
+ const result = await (0, session_1.requireApi)().getContactMessages(phone.replace(/\D/g, ''), {
85
+ agentPhoneNumber: opts.agentPhoneNumber?.replace(/\D/g, ''),
86
+ channel: opts.channel,
87
+ threadId: opts.threadId,
88
+ page: opts.page,
89
+ pageSize: opts.pageSize,
90
+ });
91
+ if ((0, ui_1.output)(result, opts, 'contact messages'))
92
+ return;
93
+ if (!result.messages?.length)
94
+ return void (0, ui_1.info)('No messages found for this contact.');
95
+ (0, ui_1.table)(['TIME', 'DIRECTION', 'CHANNEL', 'MESSAGE', 'STATUS'], result.messages.map((m) => [
96
+ String(m.createdAt || '').slice(0, 19), // drop milliseconds
97
+ m.direction || '',
98
+ m.channel || '',
99
+ String(m.body || m.contentLink || m.template || '').replace(/\s+/g, ' ').slice(0, 100),
100
+ m.status || '',
101
+ ]));
102
+ const { page, totalPages, total, hasNext } = result.pagination || {};
103
+ (0, ui_1.info)(`Page ${page ?? 1}/${totalPages || 1} - ${total ?? result.messages.length} message(s)${hasNext ? ' (use --page to see more)' : ''}`);
104
+ }
105
+ catch (err) {
106
+ (0, ui_1.handleError)(err);
107
+ }
108
+ }
92
109
  async function contactsGetCommand(phone, opts) {
93
110
  const api = (0, session_1.requireApi)();
94
111
  try {
95
112
  const { contact } = await api.getContact(phone);
96
- if (output(contact, opts, 'contact'))
113
+ if ((0, ui_1.output)(contact, opts, 'contact'))
97
114
  return;
98
115
  (0, ui_1.keyValue)({
99
116
  Phone: contact.phone_number,
@@ -303,7 +320,7 @@ async function attributesListCommand(opts) {
303
320
  const api = (0, session_1.requireApi)();
304
321
  try {
305
322
  const { definitions } = await api.listContactAttributes();
306
- if (output(definitions, opts, 'attributes'))
323
+ if ((0, ui_1.output)(definitions, opts, 'attributes'))
307
324
  return;
308
325
  if (!definitions.length) {
309
326
  return void (0, ui_1.info)('No attributes defined yet. Create one with `cku contacts attributes set <name>`.');
@@ -1,100 +1,58 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.slotsFromLlms = slotsFromLlms;
3
4
  exports.modelsCommand = modelsCommand;
4
- const dashboardApi_1 = require("../lib/dashboardApi");
5
+ const session_1 = require("../lib/session");
5
6
  const ui_1 = require("../lib/ui");
6
- const agentUpdate_1 = require("../lib/agentUpdate");
7
- const catalog_1 = require("../lib/catalog");
7
+ const liveCatalogCheck_1 = require("../lib/liveCatalogCheck");
8
8
  /**
9
- * `cku models` — what the server actually accepts, per slot.
10
- *
11
- * The point of this command is that the answer changes. `ENUMS` used to carry a
12
- * hardcoded list of 7 ids while the server accepted 44 for agent models, so
13
- * `agents update --set '{"model":"…"}'` rejected values that work fine in the
14
- * dashboard.
15
- *
16
- * With a dashboard session this reads the live `/auth/get-user` payload, which is
17
- * the same one the validator uses, so the two cannot drift apart. Without one it
18
- * reads the identical lists out of `catalog.json` — the file `catalog:sync` last
19
- * overlaid from the server, and the one `npm run catalog:check` compares against
20
- * it. Either way the answer is the server's; the only difference is how fresh.
9
+ * `cku models` — what the server accepts, per slot, from `GET /cli/llms` (the account's own list;
10
+ * Spinny and an internal account differ). The same list `agents update` validates against
11
+ * (lib/liveCatalogCheck.ts), so a value copied from here is not rejected a moment later.
21
12
  */
22
- /** Field -> slot, mirroring MODEL_SLOTS in lib/agentUpdate.ts. */
23
- const SLOTS = [
24
- { field: 'model', slot: 'agent', note: 'the agent\'s own LLM' },
25
- { field: 'secondary_model', slot: 'agent', note: 'secondary LLM' },
26
- { field: 'postcallmodel', slot: 'post-call', note: 'post-call analysis' },
27
- ];
28
- const OTHER_SLOTS = [
29
- 'capability', 'filler', 'simulation', 'chat-agent',
30
- 'call-analyzer', 'ai-auditor', 'kpi-rca', 'workflow-analytics',
31
- ];
32
- /**
33
- * The server's lists, or the shipped copy. A missing dashboard session is an
34
- * ordinary state, not an error — the shipped table is good enough to plan an
35
- * update with, and refusing to print it would push people back to guessing.
36
- */
37
- async function readSlots() {
38
- try {
39
- const res = await (0, dashboardApi_1.dashboardApi)().getUser();
40
- const llm = res?.catalogs?.llmModels || {};
41
- if (!Object.keys(llm).length)
42
- throw new Error('the response carried no model catalogue');
43
- // Keep the validator in step with what is on screen, so a value copied from
44
- // this output is not rejected a moment later.
45
- (0, agentUpdate_1.applyLiveModels)(res?.catalogs);
46
- // Lists are per account, so name whose they are.
47
- return { llm, source: `the server, now${res?.email ? ` (${res.email})` : ''}` };
48
- }
49
- catch (err) {
50
- if (!Object.keys(catalog_1.LLM_MODELS).length) {
51
- return { llm: {}, source: '', error: err };
52
- }
53
- return { llm: catalog_1.LLM_MODELS, source: 'catalog.json, last synced from the server' };
54
- }
13
+ const NOTES = { model: "the agent's own LLM", secondary_model: 'secondary LLM', postcallmodel: 'post-call analysis' };
14
+ /** `{slot: [model ids]}` from the catalogue's per-model category tags. */
15
+ function slotsFromLlms(models) {
16
+ const slots = {};
17
+ for (const m of models)
18
+ for (const c of m.categories || [])
19
+ (slots[c.id] ||= []).push(m.id);
20
+ return slots;
55
21
  }
56
22
  async function modelsCommand(opts) {
57
- const { llm, source, error } = await readSlots();
58
- if (!Object.keys(llm).length) {
59
- return (0, ui_1.handleError)(new Error(`Could not read the model catalogue from the server, and catalog.json carries none.\n` +
60
- ` ${error.message}\n` +
61
- ' Sign in with `cku dashboard login`, or refresh the shipped copy with `npm run catalog:sync`.'));
62
- }
63
- if (opts.json)
64
- return void console.log(JSON.stringify(llm, null, 2));
65
- if (opts.slot) {
66
- const list = llm[opts.slot];
67
- if (!list) {
68
- const known = Object.keys(llm).sort().join(', ');
69
- return (0, ui_1.handleError)(new Error(`No "${opts.slot}" slot in the server catalogue.\n Slots: ${known}`));
23
+ try {
24
+ const llm = slotsFromLlms((await (0, session_1.requireApi)().getLlms()).models || []);
25
+ if (opts.json)
26
+ return void console.log(JSON.stringify(llm, null, 2));
27
+ if (opts.slot) {
28
+ const list = llm[opts.slot];
29
+ if (!list)
30
+ return (0, ui_1.handleError)(new Error(`No "${opts.slot}" slot in the server catalogue.\n Slots: ${Object.keys(llm).sort().join(', ')}`));
31
+ (0, ui_1.info)(`${opts.slot} · ${list.length} model(s)\n`);
32
+ for (const m of list)
33
+ (0, ui_1.info)(` ${m}`);
34
+ return;
70
35
  }
71
- (0, ui_1.info)(`${opts.slot} · ${list.length} model(s)\n`);
72
- for (const m of list)
73
- (0, ui_1.info)(` ${m}`);
74
- return;
75
- }
76
- (0, ui_1.info)('Model slots, and the fields each one governs:\n');
77
- (0, ui_1.table)(['FIELD', 'SLOT', 'COUNT', 'GOVERNS'], SLOTS.map((s) => [s.field, s.slot, (llm[s.slot] || []).length, s.note || '']));
78
- const other = OTHER_SLOTS.filter((s) => llm[s]?.length);
79
- if (other.length) {
80
- (0, ui_1.info)('\nOther slots the server exposes (not agent fields):');
81
- (0, ui_1.table)(['SLOT', 'COUNT'], other.map((s) => [s, llm[s].length]));
82
- }
83
- const agentList = llm.agent || [];
84
- if (agentList.length) {
85
- (0, ui_1.info)(`\n${agentList.length} models for --set model / secondary_model, including:`);
86
- // Show a sample rather than all 44: the full list is one flag away, and a
87
- // wall of ids buries the point.
88
- (0, ui_1.info)(` ${agentList.slice(0, 12).join(', ')}`);
89
- if (agentList.length > 12)
90
- (0, ui_1.info)(` … and ${agentList.length - 12} more — cku models --slot agent`);
91
- }
92
- const post = llm['post-call'] || [];
93
- if (post.length) {
94
- (0, ui_1.info)(`\n${post.length} for --set postcallmodel: ${post.join(', ')}`);
36
+ (0, ui_1.info)('Model slots, and the fields each one governs:\n');
37
+ (0, ui_1.table)(['FIELD', 'SLOT', 'COUNT', 'GOVERNS'], Object.entries(liveCatalogCheck_1.LLM_FIELDS).map(([f, s]) => [f, s, (llm[s] || []).length, NOTES[f]]));
38
+ const fieldSlots = new Set(Object.values(liveCatalogCheck_1.LLM_FIELDS));
39
+ const other = Object.keys(llm).filter((s) => !fieldSlots.has(s));
40
+ if (other.length) {
41
+ (0, ui_1.info)('\nOther slots the server exposes (not agent fields):');
42
+ (0, ui_1.table)(['SLOT', 'COUNT'], other.map((s) => [s, llm[s].length]));
43
+ }
44
+ const agentList = llm.agent || [];
45
+ if (agentList.length) {
46
+ (0, ui_1.info)(`\n${agentList.length} models for --set model / secondary_model, including:`);
47
+ (0, ui_1.info)(` ${agentList.slice(0, 12).join(', ')}`);
48
+ if (agentList.length > 12)
49
+ (0, ui_1.info)(` … and ${agentList.length - 12} more — cku models --slot agent`);
50
+ }
51
+ const post = llm['post-call'] || [];
52
+ if (post.length)
53
+ (0, ui_1.info)(`\n${post.length} for --set postcallmodel: ${post.join(', ')}`);
95
54
  }
96
- (0, ui_1.info)(`\nSource: ${source}.`);
97
- if (source !== 'the server, now') {
98
- (0, ui_1.info)(' `cku dashboard login` re-reads it live; `npm run catalog:check` reports how far it has drifted.');
55
+ catch (err) {
56
+ (0, ui_1.handleError)(err);
99
57
  }
100
58
  }
@@ -7,9 +7,6 @@ exports.inboxReleaseHandoffCommand = inboxReleaseHandoffCommand;
7
7
  exports.inboxContactCommand = inboxContactCommand;
8
8
  exports.inboxClearCommand = inboxClearCommand;
9
9
  exports.inboxDeassignCommand = inboxDeassignCommand;
10
- exports.waCampaignsListCommand = waCampaignsListCommand;
11
- exports.waCampaignAnalyticsCommand = waCampaignAnalyticsCommand;
12
- exports.waCampaignTimeseriesCommand = waCampaignTimeseriesCommand;
13
10
  exports.waCampaignExportCommand = waCampaignExportCommand;
14
11
  exports.waCampaignSendCommand = waCampaignSendCommand;
15
12
  exports.finetuneListCommand = finetuneListCommand;
@@ -133,28 +130,6 @@ async function inboxDeassignCommand(phone, opts) {
133
130
  await run(opts, () => (0, dashboardApi_1.dashboardApi)().inboxDeassignChat({ phoneNumber: phone, agentPhoneNumber: opts.number }), ok(`Deassigned the chat with ${phone}.`));
134
131
  }
135
132
  // --- WhatsApp campaigns --------------------------------------------------------
136
- async function waCampaignsListCommand(opts) {
137
- await run(opts, () => (0, dashboardApi_1.dashboardApi)().listWaCampaigns(), list('waCampaign', 'No WhatsApp campaigns.', [
138
- ['ID', id], ['NAME', (r) => r.waCampaignName ?? r.name ?? r.campaignName], ['TEMPLATE', (r) => r.templateName], ['SENT', (r) => `${r.sentCount ?? 0}/${r.recipientCount ?? 0}`], ['CREATED', (r) => r.createdAt],
139
- ]));
140
- }
141
- async function waCampaignAnalyticsCommand(campaignId, opts) {
142
- await run(opts, () => (0, dashboardApi_1.dashboardApi)().waCampaignAnalytics(campaignId), (res) => {
143
- const flat = Object.entries(res?.analytics ?? res ?? {}).filter(([, v]) => typeof v !== 'object');
144
- if (!flat.length)
145
- return (0, ui_1.info)('No analytics yet.');
146
- (0, ui_1.table)(['METRIC', 'VALUE'], flat);
147
- });
148
- }
149
- async function waCampaignTimeseriesCommand(campaignId, opts) {
150
- await run(opts, () => (0, dashboardApi_1.dashboardApi)().waCampaignTimeseries(campaignId, { start: opts.start, end: opts.end }), (res) => {
151
- const rows = Array.isArray(res) ? res : res?.timeseries || res?.data || [];
152
- if (!rows.length)
153
- return (0, ui_1.info)('No data in that range.');
154
- const cols = Object.keys(rows[0]);
155
- (0, ui_1.table)(cols, rows.map((r) => cols.map((c) => cell(r[c]))));
156
- });
157
- }
158
133
  async function waCampaignExportCommand(campaignId, opts) {
159
134
  try {
160
135
  const { bytes, filename } = await (0, dashboardApi_1.dashboardApi)().exportWaCampaignContacts(campaignId, {
@@ -1,93 +1,64 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.transcribersCommand = transcribersCommand;
4
+ const session_1 = require("../lib/session");
4
5
  const ui_1 = require("../lib/ui");
5
- const dashboardAuth_1 = require("../lib/dashboardAuth");
6
- const catalog_1 = require("../lib/catalog");
7
- function rowsFor(p) {
8
- if (!p.models.length) {
9
- return [{
10
- provider: p.name,
11
- model: '(none)',
12
- languageKind: p.languageKind || '—',
13
- languages: p.languages.join(' '),
14
- }];
15
- }
16
- return p.models.map((m) => ({
17
- provider: p.name,
18
- model: m.name,
19
- languageKind: p.languageKind || '—',
20
- languages: m.languages.join(' '),
21
- }));
22
- }
6
+ /**
7
+ * `cku transcribers` — from `GET /cli/transcribers`, the same catalogue the dashboard and MCP use and
8
+ * the one `agents update` validates against (lib/liveCatalogCheck.ts). Official 5.4.8.
9
+ */
10
+ const slug = (s) => s.toLowerCase().replace(/[\s_-]+/g, '');
11
+ /** "hi" matches "hi" and "hi-IN"; comparisons are case-insensitive. */
12
+ const langMatches = (candidate, wanted) => {
13
+ const a = candidate.toLowerCase();
14
+ const b = wanted.toLowerCase();
15
+ return a === b || a.split('-')[0] === b.split('-')[0];
16
+ };
23
17
  /** `--provider X --fields` — the exact keys a provider's `transcriber` object takes. */
24
- function showFields(p, json) {
18
+ function showFields(provider, rows, json) {
19
+ const models = rows.map((r) => r.model).filter(Boolean);
20
+ const languages = [...new Set(rows.flatMap((r) => r.languages))];
25
21
  if (json) {
26
- return void console.log(JSON.stringify({ provider: p.name, fields: p.fields, models: p.models, languageKind: p.languageKind, languages: p.languages, gate: p.gate, notes: p.notes }, null, 2));
22
+ return void console.log(JSON.stringify({ provider, fields: rows[0].fields, models, languageKind: rows[0].languageKind, languages, rows }, null, 2));
27
23
  }
28
24
  (0, ui_1.keyValue)({
29
- provider: p.name,
30
- 'transcriber keys': p.fields.join(', '),
31
- models: p.models.length ? p.models.map((m) => m.name).join(', ') : '(no transcriber_model key)',
32
- transcriber_language: p.languageKind === 'array' ? 'array of locales' : 'single string',
33
- languages: p.languages.join(' '),
34
- access: p.gate === 'admin' ? 'master admin only' : 'all accounts',
25
+ provider,
26
+ 'transcriber keys': rows[0].fields.join(', '),
27
+ models: models.length ? models.join(', ') : '(no transcriber_model key)',
28
+ transcriber_language: rows[0].languageKind === 'array' ? 'array of locales' : 'single string',
29
+ languages: languages.join(' '),
35
30
  });
36
- if (p.notes)
37
- (0, ui_1.info)(`\n${p.notes}`);
38
31
  (0, ui_1.info)("\nCopy transcriber_language verbatim — never reformat [\"en-IN\"] to \"en\", and never wrap a string provider's value in an array.");
39
32
  }
40
- function unknownProvider(name) {
41
- (0, ui_1.info)(`Unknown transcriber provider "${name}". Known: ${catalog_1.TRANSCRIBER_PROVIDERS.map((p) => p.name).join(', ')}.`);
42
- process.exitCode = 1;
43
- }
44
33
  async function transcribersCommand(opts) {
45
34
  try {
46
- // Prefer the server's own model list; the built-in copy has drifted from it
47
- // (Groq is listed here but not served, and Smallest's model id has changed).
48
- await (0, dashboardAuth_1.refreshModelCatalog)();
49
- const providers = (0, catalog_1.visibleTranscriberProviders)({ all: opts.all });
50
- const stale = providers.filter((p) => p.servedBy === false);
35
+ const all = (await (0, session_1.requireApi)().getTranscribers()).transcribers || [];
36
+ let rows = all;
37
+ if (opts.provider) {
38
+ rows = all.filter((r) => slug(r.provider) === slug(opts.provider));
39
+ if (!rows.length) {
40
+ (0, ui_1.info)(`Unknown transcriber provider "${opts.provider}". Known: ${[...new Set(all.map((r) => r.provider))].join(', ')}.`);
41
+ process.exitCode = 1;
42
+ return;
43
+ }
44
+ }
51
45
  if (opts.fields) {
52
46
  if (!opts.provider) {
53
47
  (0, ui_1.info)('--fields needs a provider, e.g. `cku transcribers --provider sarvam --fields`.');
54
48
  process.exitCode = 1;
55
49
  return;
56
50
  }
57
- const p = (0, catalog_1.findTranscriberProvider)(opts.provider);
58
- if (!p)
59
- return unknownProvider(opts.provider);
60
- return showFields(p, opts.json);
61
- }
62
- let scoped = providers;
63
- if (opts.provider) {
64
- const p = (0, catalog_1.findTranscriberProvider)(opts.provider);
65
- if (!p)
66
- return unknownProvider(opts.provider);
67
- scoped = providers.filter((x) => x.name === p.name);
68
- // A gated provider asked for by name should still show, not be silently empty.
69
- if (!scoped.length)
70
- scoped = [p];
71
- }
72
- let rows = scoped.flatMap(rowsFor);
73
- if (opts.language) {
74
- rows = rows.filter((r) => r.languages.split(' ').some((l) => (0, catalog_1.langMatches)(l, [opts.language])));
51
+ return showFields(rows[0].provider, rows, opts.json);
75
52
  }
53
+ if (opts.language)
54
+ rows = rows.filter((r) => r.languages.some((l) => langMatches(l, opts.language)));
76
55
  if (opts.json)
77
56
  return void console.log(JSON.stringify(rows, null, 2));
78
57
  if (!rows.length)
79
- return void (0, ui_1.info)('No transcribers matched. Try fewer filters.');
80
- (0, ui_1.table)(['PROVIDER', 'MODEL', 'LANGUAGE FIELD', 'LANGUAGES'], rows.map((r) => [r.provider, r.model, r.languageKind, r.languages]));
58
+ return void console.log('No transcribers matched. Try fewer filters.');
59
+ (0, ui_1.table)(['PROVIDER', 'MODEL', 'LANGUAGE FIELD', 'LANGUAGES'], rows.map((r) => [r.provider, r.model ?? '(none)', r.languageKind, r.languages.join(' ')]));
81
60
  (0, ui_1.info)(`\n${rows.length} provider/model combination(s).`);
82
61
  (0, ui_1.info)('Use `cku transcribers --provider <name> --fields` for the exact keys a provider takes.');
83
- if (!opts.all && catalog_1.TRANSCRIBER_PROVIDERS.some((p) => p.gate === 'admin')) {
84
- (0, ui_1.info)('Admin-gated providers are hidden — add --all to include them.');
85
- }
86
- if (stale.length) {
87
- (0, ui_1.info)('');
88
- (0, ui_1.info)(`Not currently served by the backend, kept from the built-in list: ${stale.map((p) => p.name).join(', ')}`);
89
- (0, ui_1.info)(' Their models are stale. Re-run `npm run catalog:sync` to drop them.');
90
- }
91
62
  }
92
63
  catch (err) {
93
64
  (0, ui_1.handleError)(err);