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,228 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentsCloneVersionCommand = agentsCloneVersionCommand;
4
+ exports.agentsPublishCommand = agentsPublishCommand;
5
+ exports.agentsToggleActiveCommand = agentsToggleActiveCommand;
6
+ exports.agentsAbCommand = agentsAbCommand;
7
+ exports.agentsAbAdvancedCommand = agentsAbAdvancedCommand;
8
+ const session_1 = require("../lib/session");
9
+ const ui_1 = require("../lib/ui");
10
+ const versions_1 = require("../lib/versions");
11
+ const agentUpdate_1 = require("../lib/agentUpdate");
12
+ const setJson_1 = require("./setJson");
13
+ /** Keys a clone may override. Anything else is rejected before the request. */
14
+ const CLONE_OVERRIDE_KEYS = new Set([
15
+ 'transcriber',
16
+ 'secondary_transcriber',
17
+ 'voice_configuration',
18
+ 'secondary_voice_configuration',
19
+ 'silence_language',
20
+ ]);
21
+ /** `cku agents clone-version <agentId> --versions <src> --name <n> --prompt-type <0-3> --language <code>` */
22
+ async function agentsCloneVersionCommand(agentId, opts) {
23
+ const api = (0, session_1.requireApi)();
24
+ const sourceVersionId = (0, versions_1.firstVersion)(opts.versions);
25
+ if (!sourceVersionId)
26
+ return (0, ui_1.handleError)(new Error('--versions <sourceVersionId> is required'));
27
+ if (!opts.name?.trim())
28
+ return (0, ui_1.handleError)(new Error('--name <versionName> is required'));
29
+ const promptType = Number(opts.promptType);
30
+ if (!Number.isInteger(promptType) || promptType < 0 || promptType > 3) {
31
+ return (0, ui_1.handleError)(new Error('--prompt-type must be 0, 1, 2, or 3'));
32
+ }
33
+ const language = opts.language?.trim();
34
+ if (!language)
35
+ return (0, ui_1.handleError)(new Error('--language <code> is required'));
36
+ if (!agentUpdate_1.ENUMS.default_language.includes(language)) {
37
+ return (0, ui_1.handleError)(new Error(`Invalid --language value: "${language}"\n allowed: ${agentUpdate_1.ENUMS.default_language.join(', ')}`));
38
+ }
39
+ try {
40
+ let overrides = {};
41
+ if (opts.set) {
42
+ let parsed;
43
+ try {
44
+ parsed = (0, setJson_1.readSetJson)(opts.set);
45
+ }
46
+ catch (err) {
47
+ return (0, ui_1.handleError)(err);
48
+ }
49
+ const unsupported = Object.keys(parsed).filter((key) => !CLONE_OVERRIDE_KEYS.has(key));
50
+ if (unsupported.length) {
51
+ return (0, ui_1.handleError)(new Error(`Unsupported clone override(s): ${unsupported.join(', ')}\n allowed: ${[...CLONE_OVERRIDE_KEYS].join(', ')}`));
52
+ }
53
+ overrides = (0, agentUpdate_1.preparePatch)(parsed).patch;
54
+ }
55
+ const res = await api.cloneAgentVersion(agentId, sourceVersionId, {
56
+ ...overrides,
57
+ versionName: opts.name.trim(),
58
+ systempromptType: promptType,
59
+ default_language: language,
60
+ });
61
+ if (opts.json)
62
+ return void console.log(JSON.stringify(res, null, 2));
63
+ (0, ui_1.success)(res.msg || 'Agent version cloned.');
64
+ if (res.newVersion?._id)
65
+ (0, ui_1.info)(` id: ${res.newVersion._id}`);
66
+ if (res.newVersion?.versionName)
67
+ (0, ui_1.info)(` name: ${res.newVersion.versionName}`);
68
+ }
69
+ catch (err) {
70
+ (0, ui_1.handleError)(err);
71
+ }
72
+ }
73
+ async function agentsPublishCommand(agentId, opts) {
74
+ const api = (0, session_1.requireApi)();
75
+ const versionId = (0, versions_1.firstVersion)(opts.versions);
76
+ if (!versionId)
77
+ return (0, ui_1.handleError)(new Error('--versions <versionId> is required'));
78
+ try {
79
+ const res = await api.publishVersion(agentId, versionId);
80
+ (0, ui_1.success)(res.msg || `Published version ${versionId}.`);
81
+ }
82
+ catch (err) {
83
+ (0, ui_1.handleError)(err);
84
+ }
85
+ }
86
+ async function agentsToggleActiveCommand(agentId, opts) {
87
+ const api = (0, session_1.requireApi)();
88
+ const versionId = (0, versions_1.firstVersion)(opts.versions);
89
+ if (!versionId)
90
+ return (0, ui_1.handleError)(new Error('--versions <versionId> is required'));
91
+ try {
92
+ const res = await api.toggleVersionActive(agentId, versionId);
93
+ (0, ui_1.success)(res.msg || `Toggled version ${versionId}.`);
94
+ }
95
+ catch (err) {
96
+ (0, ui_1.handleError)(err);
97
+ }
98
+ }
99
+ /** Parse `--versions "v1=60,v2=40"` into [{versionId, ratio}], validating the split. */
100
+ function parseWeightedVersions(spec) {
101
+ const parts = spec.split(',').map((s) => s.trim()).filter(Boolean);
102
+ const out = parts.map((p) => {
103
+ const [versionId, ratioStr] = p.split('=').map((x) => (x || '').trim());
104
+ const ratio = Number(ratioStr);
105
+ if (!versionId || !ratioStr || isNaN(ratio) || ratio < 0) {
106
+ throw new Error(`Bad entry "${p}" — use versionId=ratio, e.g. 68d...=60`);
107
+ }
108
+ return { versionId, ratio };
109
+ });
110
+ if (out.length < 2)
111
+ throw new Error('A/B testing needs at least 2 versions');
112
+ // Two entries for one version would send 100% of traffic to it while the CLI
113
+ // reports a two-way split, so refuse it rather than apply a silent no-op.
114
+ const seen = new Set();
115
+ for (const v of out) {
116
+ if (seen.has(v.versionId)) {
117
+ throw new Error(`Version ${v.versionId} appears twice in --versions.\n` +
118
+ ' Each version may appear once, or the split does nothing.');
119
+ }
120
+ seen.add(v.versionId);
121
+ }
122
+ const total = out.reduce((sum, v) => sum + v.ratio, 0);
123
+ // Exact comparison is right: every ratio must be an integer percentage, so a
124
+ // fractional total means the user mistyped something.
125
+ if (total !== 100)
126
+ throw new Error(`Ratios must sum to 100 (got ${total})`);
127
+ return out;
128
+ }
129
+ async function agentsAbCommand(agentId, opts) {
130
+ const api = (0, session_1.requireApi)();
131
+ try {
132
+ if (opts.disable) {
133
+ await api.disableAbTest(agentId);
134
+ return void (0, ui_1.success)(`A/B testing disabled for agent ${agentId}.`);
135
+ }
136
+ if (!opts.versions) {
137
+ return (0, ui_1.handleError)(new Error('Provide --versions "v1=60,v2=40" (ratios summing to 100), or --disable'));
138
+ }
139
+ let weighted;
140
+ try {
141
+ weighted = parseWeightedVersions(opts.versions);
142
+ }
143
+ catch (e) {
144
+ return (0, ui_1.handleError)(e);
145
+ }
146
+ await api.enableAbTest(agentId, weighted);
147
+ (0, ui_1.success)(`A/B testing enabled for agent ${agentId}:`);
148
+ weighted.forEach((v) => (0, ui_1.info)(` ${v.versionId} → ${v.ratio}%`));
149
+ (0, ui_1.info)('Note: versions must be active (`cku agents toggle-active`) to serve traffic.');
150
+ }
151
+ catch (err) {
152
+ (0, ui_1.handleError)(err);
153
+ }
154
+ }
155
+ /** Human-readable one-liner per rule, for --show and confirmation output. */
156
+ function describeRule(r) {
157
+ const head = r.kind === 'else' ? 'ELSE' : `${r.kind.toUpperCase()} ${r.condition}`;
158
+ const targets = (r.targets || [])
159
+ .map((t) => `${r.returnType === 'version' ? t.versionId : t.language}=${t.ratio ?? 100}%`)
160
+ .join(', ');
161
+ return `${head} → ${r.returnType}: ${targets}`;
162
+ }
163
+ /**
164
+ * `cku agents ab-advanced <agentId> --rules '<json>' | --rules @rules.json | --show | --disable`
165
+ *
166
+ * Rule-based A/B: an ordered if/elseif/else chain evaluated against call
167
+ * metadata. `returnType: "version"` pins the version; `"language"` overrides
168
+ * only the language. Full semantic validation happens server-side.
169
+ */
170
+ async function agentsAbAdvancedCommand(agentId, opts) {
171
+ const api = (0, session_1.requireApi)();
172
+ try {
173
+ if (opts.disable) {
174
+ await api.updateAgent(agentId, undefined, { advancedAbTestEnabled: false });
175
+ return void (0, ui_1.success)(`Advanced A/B testing disabled for agent ${agentId}.`);
176
+ }
177
+ if (opts.show) {
178
+ const { agent } = await api.getAgent(agentId);
179
+ if (opts.json) {
180
+ return void console.log(JSON.stringify({ advancedAbTestEnabled: !!agent.advancedAbTestEnabled, advancedAbTestRules: agent.advancedAbTestRules || [] }, null, 2));
181
+ }
182
+ (0, ui_1.info)(`Advanced A/B: ${agent.advancedAbTestEnabled ? 'ENABLED' : 'disabled'}`);
183
+ (agent.advancedAbTestRules || []).forEach((r) => (0, ui_1.info)(` ${describeRule(r)}`));
184
+ return;
185
+ }
186
+ if (!opts.rules) {
187
+ return (0, ui_1.handleError)(new Error('Provide --rules \'<json array>\' or --rules @rules.json (or --show / --disable).\n' +
188
+ 'Rule shape: {"kind":"if|elseif|else","condition":"(metadata.age > 24)",' +
189
+ '"returnType":"version|language","targets":[{"versionId":"...","ratio":50}]}'));
190
+ }
191
+ let rules;
192
+ try {
193
+ rules = (0, setJson_1.readSetJson)(opts.rules);
194
+ }
195
+ catch (e) {
196
+ return (0, ui_1.handleError)(e);
197
+ }
198
+ // Accept `--show --json` output as-is, so edit round-trips need no jq.
199
+ if (!Array.isArray(rules) && rules && Array.isArray(rules.advancedAbTestRules)) {
200
+ rules = rules.advancedAbTestRules;
201
+ }
202
+ if (!Array.isArray(rules) || rules.length === 0) {
203
+ return (0, ui_1.handleError)(new Error('--rules must be a non-empty JSON ARRAY of rule objects'));
204
+ }
205
+ // Light structural checks for fast feedback; the server does the full
206
+ // semantic validation (expressions, version ownership, ratio sums).
207
+ if (rules[0]?.kind !== 'if')
208
+ return (0, ui_1.handleError)(new Error('The first rule must have "kind": "if"'));
209
+ for (const r of rules) {
210
+ if (!['if', 'elseif', 'else'].includes(r?.kind)) {
211
+ return (0, ui_1.handleError)(new Error(`Unknown rule kind "${r?.kind}" — use if | elseif | else`));
212
+ }
213
+ if (!['version', 'language'].includes(r?.returnType)) {
214
+ return (0, ui_1.handleError)(new Error(`returnType must be "version" or "language" (got "${r?.returnType}")`));
215
+ }
216
+ if (!Array.isArray(r?.targets) || r.targets.length === 0) {
217
+ return (0, ui_1.handleError)(new Error(`Every rule needs a non-empty "targets" array (${r.kind})`));
218
+ }
219
+ }
220
+ await api.updateAgent(agentId, undefined, { advancedAbTestEnabled: true, advancedAbTestRules: rules });
221
+ (0, ui_1.success)(`Advanced A/B testing enabled for agent ${agentId}:`);
222
+ rules.forEach((r) => (0, ui_1.info)(` ${describeRule(r)}`));
223
+ (0, ui_1.info)('Rules run before ratio A/B, only when the call does not pin a version.');
224
+ }
225
+ catch (err) {
226
+ (0, ui_1.handleError)(err);
227
+ }
228
+ }
@@ -0,0 +1,455 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.preparePatch = void 0;
40
+ exports.agentsListCommand = agentsListCommand;
41
+ exports.agentsGetCommand = agentsGetCommand;
42
+ exports.agentsVersionsCommand = agentsVersionsCommand;
43
+ exports.agentsExportCommand = agentsExportCommand;
44
+ exports.agentsImportCommand = agentsImportCommand;
45
+ exports.agentsCreateCommand = agentsCreateCommand;
46
+ exports.agentsUpdateCommand = agentsUpdateCommand;
47
+ const fs_1 = require("fs");
48
+ const os_1 = require("os");
49
+ const path_1 = require("path");
50
+ const prompts_1 = __importDefault(require("prompts"));
51
+ const session_1 = require("../lib/session");
52
+ const ui_1 = require("../lib/ui");
53
+ const versions_1 = require("../lib/versions");
54
+ const agentUpdate_1 = require("../lib/agentUpdate");
55
+ Object.defineProperty(exports, "preparePatch", { enumerable: true, get: function () { return agentUpdate_1.preparePatch; } });
56
+ const dashboardAuth_1 = require("../lib/dashboardAuth");
57
+ const agentImport_1 = require("../lib/agentImport");
58
+ const dbFields_1 = require("../lib/dbFields");
59
+ const jsonInput_1 = require("./jsonInput");
60
+ const NOT_SET = 'Not Selected';
61
+ /** Match the UI's "MMMM D, YYYY" created-at format. */
62
+ function formatDate(value) {
63
+ if (!value)
64
+ return 'N/A';
65
+ const d = new Date(value);
66
+ return isNaN(d.getTime()) ? 'N/A' : d.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
67
+ }
68
+ /** UI shows 'live' → "Live", anything else → "In Progress". */
69
+ const statusLabel = (status) => (status === 'live' ? 'Live' : 'In Progress');
70
+ /** Read `.phoneNumber` off a populated phone ref; bare IDs/strings → ''. */
71
+ const phoneOf = (ref) => ref && typeof ref === 'object' ? (ref.phoneNumber || '') : '';
72
+ const outboundPhone = (a) => phoneOf(a.outboundPhoneNumber) || NOT_SET;
73
+ function inboundPhones(a) {
74
+ const list = Array.isArray(a.inboundPhoneNumbers) ? a.inboundPhoneNumbers : [];
75
+ const nums = list.map(phoneOf).filter(Boolean);
76
+ return nums.length ? nums.join(', ') : NOT_SET;
77
+ }
78
+ /** Published versions like the UI: "{language}-{versionName}", comma-joined. */
79
+ function publishedVersions(a) {
80
+ const list = Array.isArray(a.publishedVersionsByLanguage) ? a.publishedVersionsByLanguage : [];
81
+ const labels = list
82
+ .map((v) => {
83
+ const ver = v.versionId;
84
+ if (!ver || !ver.versionName)
85
+ return '';
86
+ return ver.default_language ? `${ver.default_language}-${ver.versionName}` : ver.versionName;
87
+ })
88
+ .filter(Boolean);
89
+ return labels.length ? labels.join('\n') : 'No Published Versions';
90
+ }
91
+ async function agentsListCommand(opts) {
92
+ const api = (0, session_1.requireApi)();
93
+ try {
94
+ const { agents } = await api.listAgents();
95
+ const list = agents || [];
96
+ // --file: persist the raw agent data as JSON for scripting/backup.
97
+ if (opts.file) {
98
+ (0, fs_1.writeFileSync)(opts.file, JSON.stringify(list, null, 2));
99
+ return void (0, ui_1.success)(`Saved ${list.length} agent(s) to ${opts.file}`);
100
+ }
101
+ if (opts.json)
102
+ return void console.log(JSON.stringify(list, null, 2));
103
+ if (list.length === 0)
104
+ return void (0, ui_1.info)('No agents yet.');
105
+ // Columns mirror the web dashboard (V2Agents.jsx), minus the Action column.
106
+ (0, ui_1.table)(['AGENT NAME', 'ID', 'STATUS', 'OUTBOUND', 'INBOUND', 'PUBLISHED VERSIONS', 'CREATED AT'], list.map((a) => [
107
+ a.name || '(unnamed)',
108
+ a._id,
109
+ statusLabel(a.agentStatus),
110
+ outboundPhone(a),
111
+ inboundPhones(a),
112
+ publishedVersions(a),
113
+ formatDate(a.createdAt),
114
+ ]));
115
+ }
116
+ catch (err) {
117
+ (0, ui_1.handleError)(err);
118
+ }
119
+ }
120
+ /**
121
+ * Agent-level fields (models/v2Agent.js). `getAgent` returns the agent merged
122
+ * with a version doc, so without an explicit version we strip back to just
123
+ * these — the caller asked for "the agent", not a version.
124
+ */
125
+ const AGENT_FIELDS = [
126
+ '_id', 'userId', 'folderId', 'name', 'phoneNumber',
127
+ 'inboundPhoneNumbers', 'outboundPhoneNumber', 'secondaryOutboundPhoneNumber',
128
+ 'whatsappPhoneNumber', 'whatsappInboundPhoneNumber', 'default_agent_language',
129
+ 'publishedVersionsByLanguage', 'publishedVersionId', 'lastPublishedDate',
130
+ 'abTestEnabled', 'abTestVersions', 'advancedAbTestEnabled', 'advancedAbTestRules',
131
+ 'agentStatus', 'useMultiTranscribers',
132
+ 'createdAt', 'updatedAt',
133
+ ];
134
+ function pickAgentFields(agent) {
135
+ const out = {};
136
+ for (const k of AGENT_FIELDS)
137
+ if (k in agent)
138
+ out[k] = agent[k];
139
+ return out;
140
+ }
141
+ async function agentsGetCommand(agentId, opts) {
142
+ const api = (0, session_1.requireApi)();
143
+ try {
144
+ const versionId = (0, versions_1.firstVersion)(opts.versions);
145
+ const res = await api.getAgent(agentId, versionId);
146
+ const { agent, phoneNumbers, allVersions } = res;
147
+ // JSON/file output: with a version → the merged agent+version doc the API
148
+ // already builds; without one → agent fields only.
149
+ const payload = versionId ? agent : pickAgentFields(agent);
150
+ if (opts.file) {
151
+ (0, fs_1.writeFileSync)(opts.file, JSON.stringify(payload, null, 2));
152
+ return void (0, ui_1.success)(`Saved agent ${agentId} to ${opts.file}`);
153
+ }
154
+ if (opts.json)
155
+ return void console.log(JSON.stringify(payload, null, 2));
156
+ (0, ui_1.keyValue)({
157
+ id: agent._id,
158
+ name: agent.name,
159
+ status: statusLabel(agent.agentStatus),
160
+ language: agent.default_agent_language,
161
+ created: formatDate(agent.createdAt),
162
+ updated: formatDate(agent.updatedAt),
163
+ });
164
+ if (versionId) {
165
+ (0, ui_1.info)('\nVersion:');
166
+ (0, ui_1.keyValue)({ id: versionId, name: agent.versionName, model: agent.model, language: agent.default_language });
167
+ }
168
+ if (Array.isArray(allVersions) && allVersions.length) {
169
+ (0, ui_1.info)('\nVersions:');
170
+ (0, ui_1.table)(['VERSION', 'ID', 'LANGUAGE', 'PROMPT TYPE', 'ACTIVE'], allVersions.map((v) => [
171
+ v.versionName || '',
172
+ v._id,
173
+ v.default_language || '',
174
+ v.systempromptType ?? '',
175
+ v.is_active ? 'yes' : 'no',
176
+ ]));
177
+ }
178
+ if (Array.isArray(phoneNumbers) && phoneNumbers.length) {
179
+ (0, ui_1.info)('\nPhone numbers:');
180
+ (0, ui_1.table)(['NUMBER', 'TYPE'], phoneNumbers.map((p) => [p.phoneNumber || '', p.type || '']));
181
+ }
182
+ }
183
+ catch (err) {
184
+ (0, ui_1.handleError)(err);
185
+ }
186
+ }
187
+ async function agentsVersionsCommand(agentId, opts) {
188
+ const api = (0, session_1.requireApi)();
189
+ try {
190
+ const res = await api.listAgentVersions(agentId);
191
+ const versions = res.versions || [];
192
+ if (opts.file) {
193
+ (0, fs_1.writeFileSync)(opts.file, JSON.stringify(versions, null, 2));
194
+ return void (0, ui_1.success)(`Saved ${versions.length} version(s) to ${opts.file}`);
195
+ }
196
+ if (opts.json)
197
+ return void console.log(JSON.stringify(versions, null, 2));
198
+ if (!versions.length)
199
+ return void (0, ui_1.info)('No versions yet.');
200
+ (0, ui_1.table)(['VERSION', 'ID', 'LANGUAGE', 'PROMPT TYPE', 'ACTIVE'], versions.map((v) => [
201
+ v.versionName || '',
202
+ v._id,
203
+ v.default_language || '',
204
+ v.systempromptType ?? '',
205
+ v.is_active ? 'yes' : 'no',
206
+ ]));
207
+ }
208
+ catch (err) {
209
+ (0, ui_1.handleError)(err);
210
+ }
211
+ }
212
+ /** Slugify an agent name for a default filename, matching the UI's rule. */
213
+ function safeName(name) {
214
+ return String(name || 'agent').replace(/[^\w-]+/g, '-').replace(/-+/g, '-').toLowerCase();
215
+ }
216
+ function defaultFilename(name) {
217
+ const ts = new Date().toISOString().slice(0, 19).replace(/[:T]/g, '-');
218
+ return `${safeName(name)}-${ts}.json`;
219
+ }
220
+ /**
221
+ * Where a bare `export` lands, mirroring the browser's "Export" download: the
222
+ * user's Downloads folder, or the current directory if that doesn't exist.
223
+ */
224
+ function downloadDir() {
225
+ const dir = (0, path_1.join)((0, os_1.homedir)(), 'Downloads');
226
+ return (0, fs_1.existsSync)(dir) ? dir : process.cwd();
227
+ }
228
+ async function agentsExportCommand(agentId, opts) {
229
+ const api = (0, session_1.requireApi)();
230
+ try {
231
+ let ids = (0, versions_1.versionList)(opts.versions);
232
+ if (ids.length === 0) {
233
+ const res = await api.listAgentVersions(agentId);
234
+ const versions = res.versions || [];
235
+ if (!versions.length)
236
+ return (0, ui_1.handleError)(new Error('This agent has no versions'));
237
+ const picked = await (0, prompts_1.default)({
238
+ type: 'multiselect',
239
+ name: 'ids',
240
+ message: 'Which versions to export?',
241
+ choices: versions.map((v) => ({ title: v.versionName || v._id, value: v._id })),
242
+ });
243
+ if (!picked.ids || !picked.ids.length) {
244
+ return (0, ui_1.handleError)(new Error('No versions selected — pass --versions <ids> to skip the picker.'));
245
+ }
246
+ ids = picked.ids;
247
+ }
248
+ const { agents } = await api.exportAgent(agentId, ids);
249
+ if (!agents || agents.length === 0)
250
+ return (0, ui_1.handleError)(new Error('No data returned'));
251
+ // Match the UI: a single version exports as an object, many as an array.
252
+ const payload = agents.length === 1 ? agents[0] : agents;
253
+ const name = agents[0]?.name || agentId;
254
+ // Default → the Downloads folder (like the browser export); --file wins as typed.
255
+ const out = opts.file ? (0, path_1.resolve)(opts.file) : (0, path_1.join)(downloadDir(), defaultFilename(name));
256
+ try {
257
+ (0, fs_1.writeFileSync)(out, JSON.stringify(payload, null, 2));
258
+ }
259
+ catch (e) {
260
+ return (0, ui_1.handleError)(new Error(`Could not write ${out}: ${e.message}`));
261
+ }
262
+ (0, ui_1.success)(`Exported ${agents.length} version(s) to:`);
263
+ (0, ui_1.info)(` ${out}`);
264
+ }
265
+ catch (err) {
266
+ (0, ui_1.handleError)(err);
267
+ }
268
+ }
269
+ async function agentsImportCommand(filepath, opts) {
270
+ const api = (0, session_1.requireApi)();
271
+ try {
272
+ const { readJsonFile } = await Promise.resolve().then(() => __importStar(require('../lib/looseJson')));
273
+ const { existsSync } = await Promise.resolve().then(() => __importStar(require('fs')));
274
+ if (!existsSync(filepath))
275
+ return (0, ui_1.handleError)(new Error(`File not found: ${filepath}`));
276
+ let parsed = JSON.parse(readJsonFile(filepath));
277
+ // Older `cku agents export` wrote the raw `{ agents: [...] }` response; unwrap it.
278
+ if (parsed && Array.isArray(parsed.agents))
279
+ parsed = parsed.agents;
280
+ const entries = Array.isArray(parsed) ? parsed : [parsed];
281
+ // Validate every entry before touching the network, so a bad file fails fast.
282
+ for (let i = 0; i < entries.length; i++) {
283
+ const missing = (0, agentImport_1.missingKeys)(entries[i]);
284
+ if (missing.length) {
285
+ return (0, ui_1.handleError)(new Error(`Entry ${i + 1} is missing ${missing.length} required field(s): ${missing.slice(0, 8).join(', ')}` +
286
+ `${missing.length > 8 ? ', …' : ''}\n Use \`cku agents export\` to get a complete template.`));
287
+ }
288
+ const found = (0, dbFields_1.dbManagedFields)(entries[i]);
289
+ if (found.length)
290
+ return (0, ui_1.handleError)(new Error((0, dbFields_1.dbManagedFieldsError)(found, `entry ${i + 1}`)));
291
+ }
292
+ const mismatches = (0, agentImport_1.agentLevelMismatches)(entries);
293
+ if (mismatches.length) {
294
+ return (0, ui_1.handleError)(new Error(`Agent-level fields differ across versions, so they cannot merge into one agent:\n` +
295
+ mismatches.map((m) => ` ${m.key}: entry 1 has ${JSON.stringify(m.expected)}, entry ${m.entry} has ${JSON.stringify(m.got)}`).join('\n')));
296
+ }
297
+ if (opts.dryRun)
298
+ return void (0, ui_1.success)(`File looks valid — ${entries.length} version(s). Nothing created (--dry-run).`);
299
+ const res = entries.length === 1
300
+ ? await api.createAgent(entries[0])
301
+ : await api.importMultiple(entries);
302
+ (0, ui_1.success)(res.msg || `Imported ${entries.length} version(s).`);
303
+ }
304
+ catch (err) {
305
+ (0, ui_1.handleError)(err);
306
+ }
307
+ }
308
+ /**
309
+ * `cku agents create '<json>'` — create an agent from a JSON object supplied
310
+ * inline, via `--file`, an `@file` argument, or stdin. `name` is required;
311
+ * `versionName` defaults to "v1"; the backend fills the rest of the defaults.
312
+ */
313
+ async function agentsCreateCommand(json, opts) {
314
+ const api = (0, session_1.requireApi)();
315
+ let resolved;
316
+ try {
317
+ resolved = await (0, jsonInput_1.resolveJsonInput)(json, opts.file);
318
+ }
319
+ catch (e) {
320
+ return (0, ui_1.handleError)(e);
321
+ }
322
+ let obj;
323
+ try {
324
+ obj = (0, jsonInput_1.parseJsonInput)(resolved);
325
+ }
326
+ catch (e) {
327
+ return (0, ui_1.handleError)(new Error(`Not valid JSON: ${e.message}${(0, jsonInput_1.jsonHelpTip)(resolved.raw, resolved.fromShell)}`));
328
+ }
329
+ if (Array.isArray(obj)) {
330
+ return (0, ui_1.handleError)(new Error('That JSON is an array (multiple versions) — `create` takes a single agent object.\n' +
331
+ ' For a multi-version file, use: cku agents import <file>'));
332
+ }
333
+ if (!obj || typeof obj !== 'object')
334
+ return (0, ui_1.handleError)(new Error('Agent config must be a JSON object'));
335
+ const found = (0, dbFields_1.dbManagedFields)(obj);
336
+ if (found.length)
337
+ return (0, ui_1.handleError)(new Error((0, dbFields_1.dbManagedFieldsError)(found, 'agent JSON')));
338
+ if (!obj.name || typeof obj.name !== 'string') {
339
+ return (0, ui_1.handleError)(new Error('"name" is required to create an agent'));
340
+ }
341
+ if (!obj.versionName)
342
+ obj.versionName = 'v1'; // version model requires it
343
+ try {
344
+ const res = await api.createAgent(obj);
345
+ (0, ui_1.success)(res.msg || 'Agent created.');
346
+ if (res.agent?._id) {
347
+ (0, ui_1.info)(` name: ${res.agent.name || obj.name}`);
348
+ (0, ui_1.info)(` id: ${res.agent._id}`);
349
+ }
350
+ }
351
+ catch (err) {
352
+ (0, ui_1.handleError)(err);
353
+ }
354
+ }
355
+ /**
356
+ * `cku agents update <agentId> --set '<json>' --commit "msg"` — update multiple
357
+ * fields at once. Values are validated (enums/models) before sending; version-
358
+ * level fields require --versions.
359
+ *
360
+ * Dotted paths into a REPLACE_WHOLESALE_KEY object are deep-merged against the
361
+ * agent's current value first. The backend replaces those objects wholesale, so
362
+ * sending only the named sub-key would silently delete its siblings.
363
+ */
364
+ async function agentsUpdateCommand(agentId, opts) {
365
+ const api = (0, session_1.requireApi)();
366
+ if (!opts.set) {
367
+ return (0, ui_1.handleError)(new Error('--set \'{...}\' is required (a JSON object of fields to update)'));
368
+ }
369
+ // `--set @patch.json` reads the JSON from a file — quote-proof, like create.
370
+ let rawSet = opts.set;
371
+ let fromShell = true;
372
+ if (rawSet.startsWith('@')) {
373
+ const { existsSync } = await Promise.resolve().then(() => __importStar(require('fs')));
374
+ const { readJsonFile } = await Promise.resolve().then(() => __importStar(require('../lib/looseJson')));
375
+ const path = rawSet.slice(1);
376
+ if (!existsSync(path))
377
+ return (0, ui_1.handleError)(new Error(`File not found: ${path}`));
378
+ rawSet = readJsonFile(path);
379
+ fromShell = false;
380
+ }
381
+ let obj;
382
+ try {
383
+ // Inline JSON may have had its quotes eaten by the shell (`{name:mini}`);
384
+ // parseLooseJson puts them back. File contents are parsed strictly.
385
+ obj = fromShell
386
+ ? (await Promise.resolve().then(() => __importStar(require('../lib/looseJson')))).parseLooseJson(rawSet)
387
+ : JSON.parse(rawSet);
388
+ }
389
+ catch (e) {
390
+ return (0, ui_1.handleError)(new Error(`--set is not valid JSON: ${e.message}${(0, jsonInput_1.jsonHelpTip)(rawSet, fromShell)}`));
391
+ }
392
+ let patch;
393
+ let versionKeys;
394
+ let wholesaleKeys;
395
+ try {
396
+ // Validate model fields against the server's own per-slot list when we can.
397
+ // Best-effort: without a dashboard session the built-in list still applies.
398
+ await (0, dashboardAuth_1.refreshModelCatalog)();
399
+ const dbf = (0, dbFields_1.dbManagedFields)(obj);
400
+ if (dbf.length)
401
+ throw new Error((0, dbFields_1.dbManagedFieldsError)(dbf, '--set object'));
402
+ const prepared = (0, agentUpdate_1.preparePatch)(obj); // throws on invalid enum/model values
403
+ patch = prepared.patch;
404
+ versionKeys = prepared.versionKeys;
405
+ wholesaleKeys = prepared.wholesaleKeys;
406
+ }
407
+ catch (e) {
408
+ return (0, ui_1.handleError)(e);
409
+ }
410
+ if (Object.keys(patch).length === 0)
411
+ return (0, ui_1.handleError)(new Error('Nothing to update'));
412
+ if (versionKeys.length && !(0, versions_1.firstVersion)(opts.versions)) {
413
+ return (0, ui_1.handleError)(new Error(`--versions is required to update version field(s): ${versionKeys.join(', ')}`));
414
+ }
415
+ // One PUT targets one version. Taking the first id and dropping the rest would
416
+ // report success for versions that were never touched.
417
+ const targets = (0, versions_1.versionList)(opts.versions);
418
+ if (targets.length > 1) {
419
+ return (0, ui_1.handleError)(new Error(`agents update changes one version at a time, but --versions lists ${targets.length}.\n` +
420
+ ' Run the command once per version id.'));
421
+ }
422
+ try {
423
+ // The backend replaces these objects wholesale, so a partial --set would drop
424
+ // every sibling key. Merge against the agent's current value first. This is
425
+ // a no-op when the object sent is already complete. Merge against the
426
+ // *target* version: without one, getAgent returns the default version, and
427
+ // its siblings (provider, voice id, …) would be written onto a different
428
+ // version. Wholesale keys are version-level, so --versions is set here.
429
+ if (wholesaleKeys.length) {
430
+ const { agent } = await api.getAgent(agentId, (0, versions_1.firstVersion)(opts.versions));
431
+ patch = (0, agentUpdate_1.mergeWholesaleKeys)(patch, agent);
432
+ }
433
+ // A commit message is recorded against a version. Version-level updates
434
+ // already name one; for an agent-level-only update with --commit, attach it
435
+ // to the agent's current version so the change (e.g. a rename) is logged.
436
+ let versionTarget = (0, versions_1.firstVersion)(opts.versions);
437
+ if (opts.commit && !versionTarget) {
438
+ const { agent } = await api.getAgent(agentId);
439
+ versionTarget = agent.versionId;
440
+ }
441
+ const body = { ...patch };
442
+ if (opts.commit)
443
+ body.commitMessage = opts.commit;
444
+ const res = await api.updateAgent(agentId, versionTarget, body);
445
+ if (opts.json)
446
+ return void console.log(JSON.stringify(res, null, 2));
447
+ (0, ui_1.success)(res.msg || 'Agent updated.');
448
+ (0, ui_1.info)(` updated: ${Object.keys(patch).join(', ')}`);
449
+ if (opts.commit)
450
+ (0, ui_1.info)(` commit: ${opts.commit}`);
451
+ }
452
+ catch (err) {
453
+ (0, ui_1.handleError)(err);
454
+ }
455
+ }