callkaro-cli-unofficial 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +196 -0
  2. package/dist/commands/accounts.js +275 -0
  3. package/dist/commands/agentPhone.js +50 -0
  4. package/dist/commands/agentVersions.js +228 -0
  5. package/dist/commands/agents.js +455 -0
  6. package/dist/commands/analytics.js +77 -0
  7. package/dist/commands/analyzers.js +130 -0
  8. package/dist/commands/auditStrategies.js +178 -0
  9. package/dist/commands/batches.js +189 -0
  10. package/dist/commands/calls.js +193 -0
  11. package/dist/commands/chatAgents.js +251 -0
  12. package/dist/commands/jsonInput.js +56 -0
  13. package/dist/commands/kpi.js +681 -0
  14. package/dist/commands/models.js +99 -0
  15. package/dist/commands/numbers.js +154 -0
  16. package/dist/commands/ongoing.js +39 -0
  17. package/dist/commands/secrets.js +124 -0
  18. package/dist/commands/setJson.js +42 -0
  19. package/dist/commands/simulations.js +139 -0
  20. package/dist/commands/skills.js +318 -0
  21. package/dist/commands/transcribers.js +95 -0
  22. package/dist/commands/update.js +86 -0
  23. package/dist/commands/voices.js +211 -0
  24. package/dist/data/catalog.json +1978 -0
  25. package/dist/endpoints.json +8 -0
  26. package/dist/index.js +684 -0
  27. package/dist/lib/agentImport.js +60 -0
  28. package/dist/lib/agentUpdate.js +262 -0
  29. package/dist/lib/api.js +287 -0
  30. package/dist/lib/browserAuth.js +93 -0
  31. package/dist/lib/catalog.js +136 -0
  32. package/dist/lib/config.js +354 -0
  33. package/dist/lib/dashboardApi.js +197 -0
  34. package/dist/lib/dashboardAuth.js +339 -0
  35. package/dist/lib/dbFields.js +30 -0
  36. package/dist/lib/logQuery.js +140 -0
  37. package/dist/lib/looseJson.js +198 -0
  38. package/dist/lib/session.js +17 -0
  39. package/dist/lib/types.js +15 -0
  40. package/dist/lib/ui.js +91 -0
  41. package/dist/lib/versions.js +19 -0
  42. package/package.json +52 -0
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyticsOverviewCommand = analyticsOverviewCommand;
4
+ exports.analyticsPerformanceCommand = analyticsPerformanceCommand;
5
+ exports.analyticsVersionCommand = analyticsVersionCommand;
6
+ const session_1 = require("../lib/session");
7
+ const ui_1 = require("../lib/ui");
8
+ async function analyticsOverviewCommand(opts) {
9
+ const api = (0, session_1.requireApi)();
10
+ try {
11
+ const res = await api.analytics({
12
+ agent_ids: opts.agent,
13
+ version_ids: opts.versions,
14
+ name: opts.type,
15
+ voice_provider: opts.voiceProvider,
16
+ llm_model: opts.llmModel,
17
+ is_connected: opts.connected,
18
+ is_batch_call: opts.batch,
19
+ startDate: opts.start,
20
+ endDate: opts.end,
21
+ });
22
+ const d = res.data || {};
23
+ if (opts.json)
24
+ return void console.log(JSON.stringify(d, null, 2));
25
+ (0, ui_1.keyValue)({
26
+ totalCalls: d.total_calls,
27
+ totalMinutes: d.total_minutes,
28
+ avgMinutesPerCall: d.avg_minutes_per_call,
29
+ totalCredits: d.total_credits,
30
+ avgCreditsPerCall: d.avg_credits_per_call,
31
+ avgCreditsPerMinute: d.avg_credits_per_minute,
32
+ });
33
+ }
34
+ catch (err) {
35
+ (0, ui_1.handleError)(err);
36
+ }
37
+ }
38
+ async function analyticsPerformanceCommand(opts) {
39
+ const api = (0, session_1.requireApi)();
40
+ try {
41
+ const res = await api.performance({ startDate: opts.start, endDate: opts.end });
42
+ const rows = res.data || [];
43
+ if (opts.json)
44
+ return void console.log(JSON.stringify(rows, null, 2));
45
+ if (!rows.length)
46
+ return void (0, ui_1.info)('No performance data.');
47
+ (0, ui_1.table)(['AGENT ID', 'CALLS', 'CONNECTED', 'CONVERTED', 'RECEIPT→CONV %', 'CONN→CONV %'], rows.map((r) => [
48
+ r._id, r.total_calls ?? '', r.connected_calls ?? '', r.converted_calls ?? '',
49
+ r.receipt_to_conversion_pct ?? '', r.connected_to_conversion_pct ?? '',
50
+ ]));
51
+ }
52
+ catch (err) {
53
+ (0, ui_1.handleError)(err);
54
+ }
55
+ }
56
+ async function analyticsVersionCommand(agentId, opts) {
57
+ const api = (0, session_1.requireApi)();
58
+ try {
59
+ const res = await api.versionAnalytics(agentId, {
60
+ version_ids: opts.versions,
61
+ startDate: opts.start,
62
+ endDate: opts.end,
63
+ });
64
+ const rows = res.data || [];
65
+ if (opts.json)
66
+ return void console.log(JSON.stringify(rows, null, 2));
67
+ if (!rows.length)
68
+ return void (0, ui_1.info)('No version analytics.');
69
+ (0, ui_1.table)(['VERSION', 'CALLS', 'CONNECTED', 'CONVERTED', 'RECEIPT→CONV %', 'CONN→CONV %'], rows.map((r) => [
70
+ r._id, r.total_calls ?? '', r.connected_calls ?? '', r.converted_calls ?? '',
71
+ r.overall_receipt_to_conversion_pct ?? '', r.overall_connected_to_conversion_pct ?? '',
72
+ ]));
73
+ }
74
+ catch (err) {
75
+ (0, ui_1.handleError)(err);
76
+ }
77
+ }
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.analyzersListCommand = analyzersListCommand;
4
+ exports.analyzersShowCommand = analyzersShowCommand;
5
+ exports.analyzersAuditReportsCommand = analyzersAuditReportsCommand;
6
+ exports.analyzersAuditStrategiesCommand = analyzersAuditStrategiesCommand;
7
+ const dashboardApi_1 = require("../lib/dashboardApi");
8
+ const ui_1 = require("../lib/ui");
9
+ const when = (value) => {
10
+ if (!value)
11
+ return '';
12
+ const d = new Date(value);
13
+ return isNaN(d.getTime()) ? '' : d.toLocaleString('en-US');
14
+ };
15
+ /** Scores arrive as null when a run has not produced them yet. */
16
+ const score = (v) => (v === undefined || v === null ? '' : String(v));
17
+ function clip(value, max = 60) {
18
+ const text = value === undefined || value === null ? '' : String(value).replace(/\s+/g, ' ').trim();
19
+ return text.length > max ? `${text.slice(0, max - 1)}…` : text;
20
+ }
21
+ function pageSize(raw, fallback = 25) {
22
+ const n = Number(raw);
23
+ return Number.isFinite(n) && n > 0 ? Math.floor(n) : fallback;
24
+ }
25
+ /** `cku analyzers list` */
26
+ async function analyzersListCommand(opts) {
27
+ try {
28
+ const res = await (0, dashboardApi_1.dashboardApi)().listAnalyzers();
29
+ const list = Array.isArray(res?.callAnalyzers) ? res.callAnalyzers : [];
30
+ if (opts.json)
31
+ return void console.log(JSON.stringify(res, null, 2));
32
+ if (!list.length)
33
+ return void (0, ui_1.info)('No call analyzers yet. Create one in the dashboard.');
34
+ (0, ui_1.table)(['NAME', 'ID', 'MODEL', 'WEBHOOK', 'CREATED'], list.map((a) => [a.analyzerName || '(unnamed)', a._id || '', a.extractionModel || '', a.webhookUrl || '', when(a.createdAt)]));
35
+ (0, ui_1.info)(`\n${list.length} analyzer(s). Full prompt: cku analyzers show <id>`);
36
+ }
37
+ catch (err) {
38
+ (0, ui_1.handleError)(err);
39
+ }
40
+ }
41
+ /** `cku analyzers show <id>` — the whole definition, including the prompt. */
42
+ async function analyzersShowCommand(analyzerId, opts) {
43
+ try {
44
+ const res = await (0, dashboardApi_1.dashboardApi)().listAnalyzers();
45
+ const list = Array.isArray(res?.callAnalyzers) ? res.callAnalyzers : [];
46
+ const found = list.find((a) => a._id === analyzerId);
47
+ if (!found) {
48
+ return (0, ui_1.handleError)(new Error(`No analyzer with id "${analyzerId}".\n` +
49
+ ` ${list.length} analyzer(s) exist — list them with: cku analyzers list`));
50
+ }
51
+ if (opts.json)
52
+ return void console.log(JSON.stringify(found, null, 2));
53
+ (0, ui_1.keyValue)({
54
+ id: found._id,
55
+ name: found.analyzerName,
56
+ model: found.extractionModel,
57
+ webhook: found.webhookUrl || '(none)',
58
+ created: when(found.createdAt),
59
+ updated: when(found.updatedAt),
60
+ });
61
+ if (found.postCallVariables?.length) {
62
+ (0, ui_1.info)('\nPost-call variables:');
63
+ (0, ui_1.table)(['NAME', 'TYPE', 'DESCRIPTION'], found.postCallVariables.map((v) => [v.name || '', v.type || '', clip(v.description, 70)]));
64
+ }
65
+ // The instruction is the point of the analyzer, so print it in full.
66
+ if (found.instruction)
67
+ (0, ui_1.info)(`\nInstruction:\n${found.instruction}`);
68
+ }
69
+ catch (err) {
70
+ (0, ui_1.handleError)(err);
71
+ }
72
+ }
73
+ /** `cku analyzers audit-reports` — completed audit runs. */
74
+ async function analyzersAuditReportsCommand(opts) {
75
+ try {
76
+ const res = await (0, dashboardApi_1.dashboardApi)().listAuditReports();
77
+ const rows = Array.isArray(res?.data) ? res.data : [];
78
+ if (opts.json)
79
+ return void console.log(JSON.stringify(res, null, 2));
80
+ if (!rows.length)
81
+ return void (0, ui_1.info)('No audit reports yet.');
82
+ const limit = pageSize(opts.limit);
83
+ (0, ui_1.info)(`\nAudit reports · showing ${Math.min(limit, rows.length)} of ${rows.length}\n`);
84
+ (0, ui_1.table)(['AGENT', 'STRATEGY', 'STATUS', 'CALLS', 'DONE', 'FAILED', 'SCORE', 'COST', 'LAST AUDIT'], rows.slice(0, limit).map((r) => [
85
+ r.agentName || r.agentId || '',
86
+ clip(r.strategyName, 28),
87
+ r.status || '',
88
+ r.totalCalls ?? '',
89
+ r.completedCalls ?? '',
90
+ r.failedCalls ?? '',
91
+ score(r.avgOverallScore),
92
+ r.totalCost ?? '',
93
+ when(r.lastAuditTime),
94
+ ]));
95
+ const p = res?.pagination;
96
+ if (p)
97
+ (0, ui_1.info)(`\npage ${p.page} · ${p.totalDocs ?? res.total ?? '?'} total · ${p.hasNext ? 'more available' : 'last page'}`);
98
+ }
99
+ catch (err) {
100
+ (0, ui_1.handleError)(err);
101
+ }
102
+ }
103
+ /** `cku analyzers audit-strategies` — rollup per strategy. */
104
+ async function analyzersAuditStrategiesCommand(opts) {
105
+ try {
106
+ const res = await (0, dashboardApi_1.dashboardApi)().listAuditStrategies();
107
+ const rows = Array.isArray(res?.data) ? res.data : [];
108
+ if (opts.json)
109
+ return void console.log(JSON.stringify(res, null, 2));
110
+ if (!rows.length)
111
+ return void (0, ui_1.info)('No audit strategies have run yet.');
112
+ (0, ui_1.table)(['AGENT', 'STRATEGY', 'BATCHES', 'CALLS', 'AUDITED', 'DONE', 'FAILED', 'SCORE', 'COST'], rows.map((r) => [
113
+ r.agentName || r.agentId || '',
114
+ clip(r.strategyName, 30),
115
+ r.batchCount ?? '',
116
+ r.totalCalls ?? '',
117
+ r.auditedCalls ?? '',
118
+ r.completedCalls ?? '',
119
+ r.failedCalls ?? '',
120
+ score(r.avgOverallScore),
121
+ r.totalCost ?? '',
122
+ ]));
123
+ const p = res?.pagination;
124
+ if (p)
125
+ (0, ui_1.info)(`\n${p.totalDocs ?? res.total ?? rows.length} total`);
126
+ }
127
+ catch (err) {
128
+ (0, ui_1.handleError)(err);
129
+ }
130
+ }
@@ -0,0 +1,178 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.auditListCommand = auditListCommand;
4
+ exports.auditGetCommand = auditGetCommand;
5
+ exports.auditFilterOptionsCommand = auditFilterOptionsCommand;
6
+ exports.auditModelsCommand = auditModelsCommand;
7
+ exports.auditCreateCommand = auditCreateCommand;
8
+ exports.auditUpdateCommand = auditUpdateCommand;
9
+ const session_1 = require("../lib/session");
10
+ const ui_1 = require("../lib/ui");
11
+ const setJson_1 = require("./setJson");
12
+ /**
13
+ * Mirrors the web UI's brand-new-strategy defaults. Applied only on create,
14
+ * merged under whatever the caller explicitly provides.
15
+ */
16
+ const DEFAULT_AUDIT_FILTERS = {
17
+ name: 'inbound,outbound,outbound-api,outbound-scheduled',
18
+ hangup_reasons: 'CLIENT_INITIATED,PARTICIPANT_REMOVED,SESSION_TIMEOUT',
19
+ };
20
+ const CREATE_DEFAULTS = {
21
+ active: false,
22
+ extra_variables: [],
23
+ llms: { provider: 'callkaro', name: 'callkaro/krishna-2.5' },
24
+ percentage_calls_to_be_audited_per_hour: 100,
25
+ max_calls_to_be_audited_per_hour: 0,
26
+ retranscribe_audio: false,
27
+ callflow_summary: '',
28
+ };
29
+ const postcallFiltersToKeys = (postcallFilters) => Object.fromEntries((postcallFilters ?? []).map(({ name, value }) => [`postcall_${name}`, value]));
30
+ /**
31
+ * A numeric CLI flag, or undefined when absent.
32
+ *
33
+ * `Number('lots')` is NaN, and NaN serialises to `null` in the JSON body, so the
34
+ * server would silently receive a null rate instead of a rejected flag. An
35
+ * unparseable value is an error the user can act on, so it throws here — where
36
+ * the flag name is still in scope — rather than at the backend.
37
+ */
38
+ function numericFlag(raw, flag) {
39
+ if (raw === undefined || raw === null || raw === '')
40
+ return undefined;
41
+ const n = Number(raw);
42
+ if (!Number.isFinite(n)) {
43
+ throw new Error(`${flag} must be a number (got "${raw}").\n` +
44
+ ' Omit the flag to leave the current value unchanged.');
45
+ }
46
+ return n;
47
+ }
48
+ /** Scalar flags + --set merged; --set wins for structured fields, flags for scalars. */
49
+ function buildFields(opts) {
50
+ const setJson = opts.set ? (0, setJson_1.readSetJson)(opts.set) : {};
51
+ return {
52
+ setJson,
53
+ name: opts.name ?? setJson.name,
54
+ audit_strategy: opts.strategy ?? setJson.audit_strategy,
55
+ active: opts.disable ? false : (opts.active ?? setJson.active),
56
+ llms: opts.model ? { provider: opts.model.split('/')[0], name: opts.model } : setJson.llms,
57
+ percentage_calls_to_be_audited_per_hour: numericFlag(opts.pctPerHour, '--pct-per-hour') ?? setJson.percentage_calls_to_be_audited_per_hour,
58
+ max_calls_to_be_audited_per_hour: numericFlag(opts.maxPerHour, '--max-per-hour') ?? setJson.max_calls_to_be_audited_per_hour,
59
+ retranscribe_audio: opts.retranscribe ?? setJson.retranscribe_audio,
60
+ extra_variables: setJson.extra_variables,
61
+ callflow_summary: setJson.callflow_summary,
62
+ };
63
+ }
64
+ async function auditListCommand(agentId, opts) {
65
+ const api = (0, session_1.requireApi)();
66
+ try {
67
+ const res = await api.listAuditStrategies(agentId);
68
+ const rows = res.auditStrategies || [];
69
+ if (opts.json)
70
+ return void console.log(JSON.stringify(rows, null, 2));
71
+ if (!rows.length)
72
+ return void (0, ui_1.info)('No audit strategies yet.');
73
+ (0, ui_1.table)(['NAME', 'ID', 'ACTIVE'], rows.map((s) => [s.name || '', s._id, s.active ? 'yes' : 'no']));
74
+ }
75
+ catch (err) {
76
+ (0, ui_1.handleError)(err);
77
+ }
78
+ }
79
+ async function auditGetCommand(agentId, strategyId, opts) {
80
+ const api = (0, session_1.requireApi)();
81
+ try {
82
+ const res = await api.listAuditStrategies(agentId);
83
+ const strategy = (res.auditStrategies || []).find((s) => s._id === strategyId);
84
+ if (!strategy)
85
+ return (0, ui_1.handleError)(new Error(`Audit strategy ${strategyId} not found for agent ${agentId}.`));
86
+ if (opts.json)
87
+ return void console.log(JSON.stringify(strategy, null, 2));
88
+ (0, ui_1.keyValue)({
89
+ name: strategy.name,
90
+ id: strategy._id,
91
+ active: strategy.active,
92
+ audit_strategy: strategy.audit_strategy,
93
+ llms: JSON.stringify(strategy.llms),
94
+ audit_filters: JSON.stringify(strategy.audit_filters),
95
+ });
96
+ }
97
+ catch (err) {
98
+ (0, ui_1.handleError)(err);
99
+ }
100
+ }
101
+ async function auditFilterOptionsCommand(agentId, opts) {
102
+ const api = (0, session_1.requireApi)();
103
+ try {
104
+ const res = await api.getAuditFilterOptions(agentId);
105
+ if (opts.json)
106
+ return void console.log(JSON.stringify(res, null, 2));
107
+ (0, ui_1.info)('Hangup reasons:');
108
+ (res.hangupReasons || []).forEach((r) => (0, ui_1.info)(` ${r.value} — ${r.meaning}`));
109
+ (0, ui_1.info)('Drop-off reasons:');
110
+ (res.dropOffReasons || []).forEach((r) => (0, ui_1.info)(` ${r.name} — ${r.description}`));
111
+ (0, ui_1.info)('Boolean postcall filters:');
112
+ (res.postcallBooleanFilters || []).forEach((r) => (0, ui_1.info)(` ${r.name} — ${r.description}`));
113
+ }
114
+ catch (err) {
115
+ (0, ui_1.handleError)(err);
116
+ }
117
+ }
118
+ async function auditModelsCommand(opts) {
119
+ const api = (0, session_1.requireApi)();
120
+ try {
121
+ const res = await api.listAuditModels();
122
+ const rows = res.models || [];
123
+ if (opts.json)
124
+ return void console.log(JSON.stringify(rows, null, 2));
125
+ if (!rows.length)
126
+ return void (0, ui_1.info)('No audit models available.');
127
+ (0, ui_1.table)(['PROVIDER', 'NAME'], rows.map((m) => [m.provider, m.id]));
128
+ }
129
+ catch (err) {
130
+ (0, ui_1.handleError)(err);
131
+ }
132
+ }
133
+ async function auditCreateCommand(agentId, opts) {
134
+ const api = (0, session_1.requireApi)();
135
+ try {
136
+ const { setJson, ...fields } = buildFields(opts);
137
+ if (!fields.audit_strategy) {
138
+ return (0, ui_1.handleError)(new Error('audit_strategy is required — pass --strategy <text> or --set \'{"audit_strategy":"..."}\''));
139
+ }
140
+ const res = await api.createAuditStrategy({
141
+ agentId,
142
+ ...CREATE_DEFAULTS,
143
+ ...(0, setJson_1.pruneUndefined)(fields),
144
+ audit_filters: {
145
+ ...DEFAULT_AUDIT_FILTERS,
146
+ ...(setJson.audit_filters || {}),
147
+ ...postcallFiltersToKeys(setJson.postcall_filters),
148
+ },
149
+ });
150
+ (0, ui_1.success)(`Created audit strategy ${res.auditStrategy?._id || ''}.`);
151
+ }
152
+ catch (err) {
153
+ (0, ui_1.handleError)(err);
154
+ }
155
+ }
156
+ async function auditUpdateCommand(agentId, strategyId, opts) {
157
+ const api = (0, session_1.requireApi)();
158
+ try {
159
+ // The backend replaces audit_filters wholesale on update, not per-key —
160
+ // fetch the existing strategy and merge onto it so only named keys change.
161
+ const existing = await api.listAuditStrategies(agentId);
162
+ const existingStrategy = (existing.auditStrategies || []).find((s) => s._id === strategyId);
163
+ const { setJson, ...fields } = buildFields(opts);
164
+ const res = await api.updateAuditStrategy(strategyId, {
165
+ agentId,
166
+ ...(0, setJson_1.pruneUndefined)(fields),
167
+ audit_filters: {
168
+ ...(existingStrategy?.audit_filters || {}),
169
+ ...(setJson.audit_filters || {}),
170
+ ...postcallFiltersToKeys(setJson.postcall_filters),
171
+ },
172
+ });
173
+ (0, ui_1.success)(res.msg || `Updated audit strategy ${strategyId}.`);
174
+ }
175
+ catch (err) {
176
+ (0, ui_1.handleError)(err);
177
+ }
178
+ }
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.batchesCreateCommand = batchesCreateCommand;
4
+ exports.batchesScheduleCommand = batchesScheduleCommand;
5
+ exports.batchesListCommand = batchesListCommand;
6
+ exports.batchesGetCommand = batchesGetCommand;
7
+ exports.batchesStatusCommand = batchesStatusCommand;
8
+ exports.batchesSendNextTryCommand = batchesSendNextTryCommand;
9
+ exports.batchesSendUntriggeredCommand = batchesSendUntriggeredCommand;
10
+ exports.batchesDownloadCommand = batchesDownloadCommand;
11
+ const fs_1 = require("fs");
12
+ const session_1 = require("../lib/session");
13
+ const ui_1 = require("../lib/ui");
14
+ async function batchesCreateCommand(opts) {
15
+ const api = (0, session_1.requireApi)();
16
+ if (!opts.file)
17
+ return (0, ui_1.handleError)(new Error('--file <path-to-csv> is required'));
18
+ if (!opts.name)
19
+ return (0, ui_1.handleError)(new Error('--name <batch name> is required'));
20
+ if (!(0, fs_1.existsSync)(opts.file))
21
+ return (0, ui_1.handleError)(new Error(`File not found: ${opts.file}`));
22
+ try {
23
+ const res = await api.createBatch(opts.file, {
24
+ name: opts.name,
25
+ agentId: opts.agent,
26
+ language: opts.language,
27
+ funnelMatrics: opts.funnel,
28
+ workflow_id: opts.workflow,
29
+ });
30
+ (0, ui_1.success)(res.msg || 'Batch created.');
31
+ if (res.batchCall?._id)
32
+ (0, ui_1.info)(`Batch id: ${res.batchCall._id} (${res.phonenumbersCount} numbers)`);
33
+ if (res.triggerError)
34
+ (0, ui_1.info)('Dial trigger failed — run `cku batches send-untriggered <id>` to start dialing.');
35
+ }
36
+ catch (err) {
37
+ (0, ui_1.handleError)(err);
38
+ }
39
+ }
40
+ async function batchesScheduleCommand(opts) {
41
+ const api = (0, session_1.requireApi)();
42
+ if (!opts.file)
43
+ return (0, ui_1.handleError)(new Error('--file <path-to-csv> is required'));
44
+ if (!opts.name)
45
+ return (0, ui_1.handleError)(new Error('--name <batch name> is required'));
46
+ if (!opts.at)
47
+ return (0, ui_1.handleError)(new Error('--at <ISO datetime> is required, e.g. --at "2026-08-05T10:00:00+05:30"'));
48
+ if (!(0, fs_1.existsSync)(opts.file))
49
+ return (0, ui_1.handleError)(new Error(`File not found: ${opts.file}`));
50
+ let min_trigger_time, max_trigger_time;
51
+ if (opts.window) {
52
+ const m = opts.window.match(/^(\d{2}:\d{2})-(\d{2}:\d{2})$/);
53
+ if (!m)
54
+ return (0, ui_1.handleError)(new Error('--window must be HH:MM-HH:MM, e.g. 10:00-19:00'));
55
+ [, min_trigger_time, max_trigger_time] = m;
56
+ }
57
+ try {
58
+ const res = await api.scheduleBatch(opts.file, {
59
+ name: opts.name,
60
+ agentId: opts.agent,
61
+ scheduleAt: opts.at,
62
+ min_trigger_time,
63
+ max_trigger_time,
64
+ number_of_retries: opts.retries,
65
+ gap_between_retries: opts.gaps,
66
+ carry_over: opts.carryOver ? 'true' : undefined,
67
+ priority: opts.priority,
68
+ language: opts.language,
69
+ funnelMatrics: opts.funnel,
70
+ workflow_id: opts.workflow,
71
+ });
72
+ (0, ui_1.success)(res.msg || `Batch scheduled for ${opts.at}.`);
73
+ }
74
+ catch (err) {
75
+ (0, ui_1.handleError)(err);
76
+ }
77
+ }
78
+ function fmtDate(v) {
79
+ if (!v)
80
+ return '';
81
+ const d = new Date(v);
82
+ return isNaN(d.getTime()) ? '' : d.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' });
83
+ }
84
+ async function batchesListCommand(opts) {
85
+ const api = (0, session_1.requireApi)();
86
+ try {
87
+ const res = await api.listBatches({ page: 1, pageSize: (0, ui_1.intFlag)(opts.limit, '--limit', 20) });
88
+ const rows = res.batchCalls || [];
89
+ if (opts.json)
90
+ return void console.log(JSON.stringify(rows, null, 2));
91
+ if (!rows.length)
92
+ return void (0, ui_1.info)('No batches yet.');
93
+ (0, ui_1.table)(['NAME', 'ID', 'AGENT', 'NUMBERS', 'TRIGGERED', 'CONNECTED', 'CONVERTED', 'TRIES', 'CREATED'], rows.map((b) => [
94
+ b.name || '', b._id, b.agent?.name || '', b.phonenumbersCount ?? '',
95
+ b.triggered_calls ?? '', b.connected_calls ?? '', b.converted_calls ?? '',
96
+ b.total_tries ?? '', fmtDate(b.createdAt),
97
+ ]));
98
+ }
99
+ catch (err) {
100
+ (0, ui_1.handleError)(err);
101
+ }
102
+ }
103
+ async function batchesGetCommand(batchId, opts) {
104
+ const api = (0, session_1.requireApi)();
105
+ try {
106
+ const res = await api.getBatch(batchId);
107
+ if (opts.json)
108
+ return void console.log(JSON.stringify(res, null, 2));
109
+ const b = res.batchCall || {};
110
+ (0, ui_1.keyValue)({ id: b._id, name: b.name, numbers: b.phonenumbersCount, agent: b.agent?.name, created: fmtDate(b.createdAt) });
111
+ }
112
+ catch (err) {
113
+ (0, ui_1.handleError)(err);
114
+ }
115
+ }
116
+ async function batchesStatusCommand(batchId, opts) {
117
+ const api = (0, session_1.requireApi)();
118
+ try {
119
+ const res = await api.batchStatus(batchId);
120
+ const data = res.data || {};
121
+ if (opts.json)
122
+ return void console.log(JSON.stringify(data, null, 2));
123
+ (0, ui_1.keyValue)({
124
+ connectedCalls: data.connectedCalls,
125
+ nextTry: data.nextTry,
126
+ trials: Array.isArray(data.all_trials) ? data.all_trials.length : undefined,
127
+ });
128
+ (0, ui_1.info)('Use --json for the full per-try distribution.');
129
+ }
130
+ catch (err) {
131
+ (0, ui_1.handleError)(err);
132
+ }
133
+ }
134
+ /**
135
+ * Look up a batch's current total_tries, as the web table does.
136
+ *
137
+ * Throws when the batch is not in the first 100 rather than returning 0. An
138
+ * account with more than 100 batches puts the target on a later page, and a
139
+ * silent 0 would be APPLIED as max_try_count — telling the server to do nothing
140
+ * while the command reports success. Guessing here is worse than asking.
141
+ */
142
+ async function batchTotalTries(api, batchId) {
143
+ const res = await api.listBatches({ page: 1, pageSize: 100 });
144
+ const b = (res.batchCalls || []).find((x) => x._id === batchId);
145
+ if (!b) {
146
+ throw new Error(`Batch ${batchId} is not in the first 100 returned by the API, so its current try count is unknown.
147
+ ` +
148
+ ' Pass it explicitly: cku batches send-next-try ' + batchId + ' --max-try <n>');
149
+ }
150
+ return b.total_tries ?? 0;
151
+ }
152
+ async function batchesSendNextTryCommand(batchId, opts) {
153
+ const api = (0, session_1.requireApi)();
154
+ try {
155
+ const maxTry = opts.maxTry !== undefined ? (0, ui_1.intFlag)(opts.maxTry, '--max-try', 0) : await batchTotalTries(api, batchId);
156
+ const res = await api.sendNextTry(batchId, maxTry);
157
+ (0, ui_1.success)(res.msg || `Next-try dial queued for batch ${batchId}.`);
158
+ }
159
+ catch (err) {
160
+ (0, ui_1.handleError)(err);
161
+ }
162
+ }
163
+ async function batchesSendUntriggeredCommand(batchId) {
164
+ const api = (0, session_1.requireApi)();
165
+ try {
166
+ const res = await api.sendUntriggered(batchId);
167
+ (0, ui_1.success)(res.msg || `Untriggered dial queued for batch ${batchId}.`);
168
+ }
169
+ catch (err) {
170
+ (0, ui_1.handleError)(err);
171
+ }
172
+ }
173
+ async function batchesDownloadCommand(batchId, opts) {
174
+ const api = (0, session_1.requireApi)();
175
+ const kind = opts.type || 'receipts';
176
+ if (!['receipts', 'triggered', 'untriggered', 'not-connected'].includes(kind)) {
177
+ return (0, ui_1.handleError)(new Error('--type must be receipts | triggered | untriggered | not-connected'));
178
+ }
179
+ try {
180
+ const res = await api.batchDownload(batchId, kind);
181
+ const rows = res.receipts || [];
182
+ const out = opts.file || `batch-${batchId}-${kind}.json`;
183
+ (0, fs_1.writeFileSync)(out, JSON.stringify(rows, null, 2));
184
+ (0, ui_1.success)(`Saved ${rows.length} ${kind} row(s) to ${out}`);
185
+ }
186
+ catch (err) {
187
+ (0, ui_1.handleError)(err);
188
+ }
189
+ }