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.
- package/README.md +196 -0
- package/dist/commands/accounts.js +275 -0
- package/dist/commands/agentPhone.js +50 -0
- package/dist/commands/agentVersions.js +228 -0
- package/dist/commands/agents.js +455 -0
- package/dist/commands/analytics.js +77 -0
- package/dist/commands/analyzers.js +130 -0
- package/dist/commands/auditStrategies.js +178 -0
- package/dist/commands/batches.js +189 -0
- package/dist/commands/calls.js +193 -0
- package/dist/commands/chatAgents.js +251 -0
- package/dist/commands/jsonInput.js +56 -0
- package/dist/commands/kpi.js +681 -0
- package/dist/commands/models.js +99 -0
- package/dist/commands/numbers.js +154 -0
- package/dist/commands/ongoing.js +39 -0
- package/dist/commands/secrets.js +124 -0
- package/dist/commands/setJson.js +42 -0
- package/dist/commands/simulations.js +139 -0
- package/dist/commands/skills.js +318 -0
- package/dist/commands/transcribers.js +95 -0
- package/dist/commands/update.js +86 -0
- package/dist/commands/voices.js +211 -0
- package/dist/data/catalog.json +1978 -0
- package/dist/endpoints.json +8 -0
- package/dist/index.js +684 -0
- package/dist/lib/agentImport.js +60 -0
- package/dist/lib/agentUpdate.js +262 -0
- package/dist/lib/api.js +287 -0
- package/dist/lib/browserAuth.js +93 -0
- package/dist/lib/catalog.js +136 -0
- package/dist/lib/config.js +354 -0
- package/dist/lib/dashboardApi.js +197 -0
- package/dist/lib/dashboardAuth.js +339 -0
- package/dist/lib/dbFields.js +30 -0
- package/dist/lib/logQuery.js +140 -0
- package/dist/lib/looseJson.js +198 -0
- package/dist/lib/session.js +17 -0
- package/dist/lib/types.js +15 -0
- package/dist/lib/ui.js +91 -0
- package/dist/lib/versions.js +19 -0
- package/package.json +52 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.modelsCommand = modelsCommand;
|
|
4
|
+
const dashboardApi_1 = require("../lib/dashboardApi");
|
|
5
|
+
const ui_1 = require("../lib/ui");
|
|
6
|
+
const agentUpdate_1 = require("../lib/agentUpdate");
|
|
7
|
+
const catalog_1 = require("../lib/catalog");
|
|
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.
|
|
21
|
+
*/
|
|
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
|
+
return { llm, source: 'the server, now' };
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
if (!Object.keys(catalog_1.LLM_MODELS).length) {
|
|
50
|
+
return { llm: {}, source: '', error: err };
|
|
51
|
+
}
|
|
52
|
+
return { llm: catalog_1.LLM_MODELS, source: 'catalog.json, last synced from the server' };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function modelsCommand(opts) {
|
|
56
|
+
const { llm, source, error } = await readSlots();
|
|
57
|
+
if (!Object.keys(llm).length) {
|
|
58
|
+
return (0, ui_1.handleError)(new Error(`Could not read the model catalogue from the server, and catalog.json carries none.\n` +
|
|
59
|
+
` ${error.message}\n` +
|
|
60
|
+
' Sign in with `cku dashboard login`, or refresh the shipped copy with `npm run catalog:sync`.'));
|
|
61
|
+
}
|
|
62
|
+
if (opts.json)
|
|
63
|
+
return void console.log(JSON.stringify(llm, null, 2));
|
|
64
|
+
if (opts.slot) {
|
|
65
|
+
const list = llm[opts.slot];
|
|
66
|
+
if (!list) {
|
|
67
|
+
const known = Object.keys(llm).sort().join(', ');
|
|
68
|
+
return (0, ui_1.handleError)(new Error(`No "${opts.slot}" slot in the server catalogue.\n Slots: ${known}`));
|
|
69
|
+
}
|
|
70
|
+
(0, ui_1.info)(`${opts.slot} · ${list.length} model(s)\n`);
|
|
71
|
+
for (const m of list)
|
|
72
|
+
(0, ui_1.info)(` ${m}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
(0, ui_1.info)('Model slots, and the fields each one governs:\n');
|
|
76
|
+
(0, ui_1.table)(['FIELD', 'SLOT', 'COUNT', 'GOVERNS'], SLOTS.map((s) => [s.field, s.slot, (llm[s.slot] || []).length, s.note || '']));
|
|
77
|
+
const other = OTHER_SLOTS.filter((s) => llm[s]?.length);
|
|
78
|
+
if (other.length) {
|
|
79
|
+
(0, ui_1.info)('\nOther slots the server exposes (not agent fields):');
|
|
80
|
+
(0, ui_1.table)(['SLOT', 'COUNT'], other.map((s) => [s, llm[s].length]));
|
|
81
|
+
}
|
|
82
|
+
const agentList = llm.agent || [];
|
|
83
|
+
if (agentList.length) {
|
|
84
|
+
(0, ui_1.info)(`\n${agentList.length} models for --set model / secondary_model, including:`);
|
|
85
|
+
// Show a sample rather than all 44: the full list is one flag away, and a
|
|
86
|
+
// wall of ids buries the point.
|
|
87
|
+
(0, ui_1.info)(` ${agentList.slice(0, 12).join(', ')}`);
|
|
88
|
+
if (agentList.length > 12)
|
|
89
|
+
(0, ui_1.info)(` … and ${agentList.length - 12} more — cku models --slot agent`);
|
|
90
|
+
}
|
|
91
|
+
const post = llm['post-call'] || [];
|
|
92
|
+
if (post.length) {
|
|
93
|
+
(0, ui_1.info)(`\n${post.length} for --set postcallmodel: ${post.join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
(0, ui_1.info)(`\nSource: ${source}.`);
|
|
96
|
+
if (source !== 'the server, now') {
|
|
97
|
+
(0, ui_1.info)(' `cku dashboard login` re-reads it live; `npm run catalog:check` reports how far it has drifted.');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.numbersListCommand = numbersListCommand;
|
|
7
|
+
exports.numbersCatalogCommand = numbersCatalogCommand;
|
|
8
|
+
exports.numbersBuyCommand = numbersBuyCommand;
|
|
9
|
+
exports.numbersSpamCommand = numbersSpamCommand;
|
|
10
|
+
exports.numbersUnspamCommand = numbersUnspamCommand;
|
|
11
|
+
exports.numbersReleaseCommand = numbersReleaseCommand;
|
|
12
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
13
|
+
const session_1 = require("../lib/session");
|
|
14
|
+
const ui_1 = require("../lib/ui");
|
|
15
|
+
function fmtDate(v) {
|
|
16
|
+
if (!v)
|
|
17
|
+
return '';
|
|
18
|
+
const d = new Date(v);
|
|
19
|
+
return isNaN(d.getTime()) ? '' : d.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' });
|
|
20
|
+
}
|
|
21
|
+
const agentsCol = (list) => list && list.length ? list.map((a) => a.name).join('\n') : '';
|
|
22
|
+
const isObjectId = (s) => /^[0-9a-fA-F]{24}$/.test(s);
|
|
23
|
+
/** Digits only — so "+91 98…", "91-98…" and "9198…" all compare equal. */
|
|
24
|
+
const digits = (s) => (s || '').replace(/\D/g, '');
|
|
25
|
+
/**
|
|
26
|
+
* Accept either a phone-number id or the number itself (with or without `+`,
|
|
27
|
+
* spaces or dashes). Numbers are resolved to their id against the user's list,
|
|
28
|
+
* so `cku numbers spam 918244464128` works like the id form.
|
|
29
|
+
*/
|
|
30
|
+
async function resolveNumberId(api, input, pool = 'mine') {
|
|
31
|
+
if (isObjectId(input))
|
|
32
|
+
return input;
|
|
33
|
+
const wanted = digits(input);
|
|
34
|
+
if (!wanted)
|
|
35
|
+
throw new Error(`"${input}" is not a phone number or id`);
|
|
36
|
+
const { phoneNumbers } = pool === 'catalog' ? await api.numbersCatalog() : await api.listNumbers();
|
|
37
|
+
const matches = (phoneNumbers || []).filter((n) => digits(n.phoneNumber) === wanted);
|
|
38
|
+
if (matches.length === 0) {
|
|
39
|
+
throw new Error(pool === 'catalog'
|
|
40
|
+
? `No buyable number matches ${input} (see \`cku numbers catalog\`)`
|
|
41
|
+
: `You don't own a number matching ${input} (see \`cku numbers list\`)`);
|
|
42
|
+
}
|
|
43
|
+
if (matches.length > 1)
|
|
44
|
+
throw new Error(`${input} matches ${matches.length} numbers — pass the id instead`);
|
|
45
|
+
return matches[0]._id;
|
|
46
|
+
}
|
|
47
|
+
async function numbersListCommand(opts) {
|
|
48
|
+
const api = (0, session_1.requireApi)();
|
|
49
|
+
try {
|
|
50
|
+
const { phoneNumbers } = await api.listNumbers();
|
|
51
|
+
const list = phoneNumbers || [];
|
|
52
|
+
if (opts.json)
|
|
53
|
+
return void console.log(JSON.stringify(list, null, 2));
|
|
54
|
+
if (!list.length)
|
|
55
|
+
return void (0, ui_1.info)('No phone numbers yet.');
|
|
56
|
+
(0, ui_1.table)(['NUMBER', 'ID', 'PURCHASED', 'SPAM', 'STATUS', 'INBOUND AGENT', 'OUTBOUND AGENTS'], list.map((n) => [
|
|
57
|
+
`+${n.phoneNumber}`,
|
|
58
|
+
n._id,
|
|
59
|
+
fmtDate(n.allotedOn),
|
|
60
|
+
n.spammed ? 'spam' : '',
|
|
61
|
+
n.unrented ? 'released' : 'active',
|
|
62
|
+
n.inboundAgent ? n.inboundAgent.name : '',
|
|
63
|
+
agentsCol(n.outboundAgents),
|
|
64
|
+
]));
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
(0, ui_1.handleError)(err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async function numbersCatalogCommand(opts) {
|
|
71
|
+
const api = (0, session_1.requireApi)();
|
|
72
|
+
try {
|
|
73
|
+
const { phoneNumbers, pricepernumber } = await api.numbersCatalog();
|
|
74
|
+
const list = phoneNumbers || [];
|
|
75
|
+
if (opts.json)
|
|
76
|
+
return void console.log(JSON.stringify({ phoneNumbers: list, pricepernumber }, null, 2));
|
|
77
|
+
if (!list.length)
|
|
78
|
+
return void (0, ui_1.info)('No numbers available to buy right now.');
|
|
79
|
+
(0, ui_1.info)(`Price per number: ₹${pricepernumber}\n`);
|
|
80
|
+
(0, ui_1.table)(['NUMBER', 'ID', 'COUNTRY'], list.map((n) => [`+${n.phoneNumber}`, n._id, n.countryCode || '']));
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
(0, ui_1.handleError)(err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function numbersBuyCommand(numberOrId, opts) {
|
|
87
|
+
const api = (0, session_1.requireApi)();
|
|
88
|
+
try {
|
|
89
|
+
const numberId = await resolveNumberId(api, numberOrId, 'catalog');
|
|
90
|
+
const { phoneNumbers, pricepernumber } = await api.numbersCatalog();
|
|
91
|
+
const number = (phoneNumbers || []).find((n) => n._id === numberId);
|
|
92
|
+
if (!number) {
|
|
93
|
+
return (0, ui_1.handleError)(new Error('That number is not in the buyable pool (already taken or invalid id).'));
|
|
94
|
+
}
|
|
95
|
+
if (!opts.yes) {
|
|
96
|
+
const { ok } = await (0, prompts_1.default)({
|
|
97
|
+
type: 'confirm',
|
|
98
|
+
name: 'ok',
|
|
99
|
+
message: `Buy +${number.phoneNumber} for ₹${pricepernumber}? This deducts credits.`,
|
|
100
|
+
initial: false,
|
|
101
|
+
});
|
|
102
|
+
if (!ok)
|
|
103
|
+
return void (0, ui_1.info)('Cancelled.');
|
|
104
|
+
}
|
|
105
|
+
const res = await api.buyNumber(numberId, pricepernumber);
|
|
106
|
+
return void (0, ui_1.success)(res.message || `Bought +${number.phoneNumber}.`);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
(0, ui_1.handleError)(err);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Spam is a single toggle endpoint (`PUT /cli/numbers/:id/spam` with a boolean),
|
|
114
|
+
* so marking and unmarking share one code path. `cku numbers spam --off` and
|
|
115
|
+
* `cku numbers unspam` both land here — `unspam` just exists so it's findable.
|
|
116
|
+
*/
|
|
117
|
+
async function setSpamFlag(numberOrId, spam) {
|
|
118
|
+
const api = (0, session_1.requireApi)();
|
|
119
|
+
try {
|
|
120
|
+
const numberId = await resolveNumberId(api, numberOrId);
|
|
121
|
+
const res = await api.setSpam(numberId, spam);
|
|
122
|
+
(0, ui_1.success)(res.message || `Number ${spam ? 'marked as spam' : 'unmarked as spam'}.`);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
(0, ui_1.handleError)(err);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function numbersSpamCommand(numberOrId, opts) {
|
|
129
|
+
return setSpamFlag(numberOrId, !opts.off);
|
|
130
|
+
}
|
|
131
|
+
async function numbersUnspamCommand(numberOrId) {
|
|
132
|
+
return setSpamFlag(numberOrId, false);
|
|
133
|
+
}
|
|
134
|
+
async function numbersReleaseCommand(numberOrId, opts) {
|
|
135
|
+
const api = (0, session_1.requireApi)();
|
|
136
|
+
try {
|
|
137
|
+
const numberId = await resolveNumberId(api, numberOrId);
|
|
138
|
+
if (!opts.yes) {
|
|
139
|
+
const { ok } = await (0, prompts_1.default)({
|
|
140
|
+
type: 'confirm',
|
|
141
|
+
name: 'ok',
|
|
142
|
+
message: 'Release (unrent) this number? It will be unassigned from all agents.',
|
|
143
|
+
initial: false,
|
|
144
|
+
});
|
|
145
|
+
if (!ok)
|
|
146
|
+
return void (0, ui_1.info)('Cancelled.');
|
|
147
|
+
}
|
|
148
|
+
const res = await api.releaseNumber(numberId);
|
|
149
|
+
(0, ui_1.success)(res.message || 'Released.');
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
(0, ui_1.handleError)(err);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ongoingStatusCommand = ongoingStatusCommand;
|
|
4
|
+
exports.ongoingControlCommand = ongoingControlCommand;
|
|
5
|
+
const session_1 = require("../lib/session");
|
|
6
|
+
const ui_1 = require("../lib/ui");
|
|
7
|
+
async function ongoingStatusCommand(opts) {
|
|
8
|
+
const api = (0, session_1.requireApi)();
|
|
9
|
+
try {
|
|
10
|
+
const res = await api.ongoingStatus();
|
|
11
|
+
const data = res.data || res;
|
|
12
|
+
if (opts.json)
|
|
13
|
+
return void console.log(JSON.stringify(data, null, 2));
|
|
14
|
+
const summary = data.agent_tasks_summary || {};
|
|
15
|
+
const paused = data.agent_pause_summary || {};
|
|
16
|
+
const agentIds = Object.keys(summary);
|
|
17
|
+
(0, ui_1.info)(`User paused: ${data.user_paused ? 'yes' : 'no'} Window: ${data.min_trigger_time ?? '—'}–${data.max_trigger_time ?? '—'}\n`);
|
|
18
|
+
if (!agentIds.length)
|
|
19
|
+
return void console.log('No queued calls.');
|
|
20
|
+
(0, ui_1.table)(['AGENT ID', 'QUEUED', 'STATUS'], agentIds.map((id) => [id, String(summary[id] ?? 0), paused[id] ? 'Paused' : 'Active']));
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
(0, ui_1.handleError)(err);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** `cku ongoing pause|resume|clear [--agent <id>]` — no `--agent` means the whole account. */
|
|
27
|
+
async function ongoingControlCommand(action, opts) {
|
|
28
|
+
const api = (0, session_1.requireApi)();
|
|
29
|
+
try {
|
|
30
|
+
if (opts.agent)
|
|
31
|
+
await api.ongoingAgent(action, opts.agent);
|
|
32
|
+
else
|
|
33
|
+
await api.ongoingUser(action);
|
|
34
|
+
(0, ui_1.success)(`${action} ${opts.agent ? `agent ${opts.agent}` : 'all agents'} — done.`);
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
(0, ui_1.handleError)(err);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.secretsListCommand = secretsListCommand;
|
|
7
|
+
exports.secretsSetCommand = secretsSetCommand;
|
|
8
|
+
exports.secretsRemoveCommand = secretsRemoveCommand;
|
|
9
|
+
exports.secretsRenameCommand = secretsRenameCommand;
|
|
10
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
11
|
+
const session_1 = require("../lib/session");
|
|
12
|
+
const ui_1 = require("../lib/ui");
|
|
13
|
+
function validateName(input) {
|
|
14
|
+
const name = input.trim();
|
|
15
|
+
if (!name)
|
|
16
|
+
throw new Error('Secret name is required');
|
|
17
|
+
if (name.length > 256)
|
|
18
|
+
throw new Error('Secret name must be at most 256 characters');
|
|
19
|
+
return name;
|
|
20
|
+
}
|
|
21
|
+
/** An untouched secret — value blanked so the server keeps what it has. */
|
|
22
|
+
const preserve = (secret) => ({ name: secret.name, value: '', originalName: secret.name });
|
|
23
|
+
function readStdin() {
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
let data = '';
|
|
26
|
+
process.stdin.setEncoding('utf8');
|
|
27
|
+
process.stdin.on('data', (chunk) => (data += chunk));
|
|
28
|
+
process.stdin.on('end', () => resolve(data));
|
|
29
|
+
process.stdin.on('error', reject);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
async function readSecretValue(name, fromStdin) {
|
|
33
|
+
if (fromStdin) {
|
|
34
|
+
if (process.stdin.isTTY)
|
|
35
|
+
throw new Error('--stdin requires piped or redirected input');
|
|
36
|
+
return (await readStdin()).trim();
|
|
37
|
+
}
|
|
38
|
+
const answer = await (0, prompts_1.default)({ type: 'password', name: 'value', message: `Value for ${name}` });
|
|
39
|
+
return typeof answer.value === 'string' ? answer.value.trim() : undefined;
|
|
40
|
+
}
|
|
41
|
+
async function secretsListCommand(opts) {
|
|
42
|
+
const api = (0, session_1.requireApi)();
|
|
43
|
+
try {
|
|
44
|
+
const { secrets } = await api.listSecrets();
|
|
45
|
+
const list = secrets || [];
|
|
46
|
+
if (opts.json)
|
|
47
|
+
return void console.log(JSON.stringify(list, null, 2));
|
|
48
|
+
if (!list.length)
|
|
49
|
+
return void (0, ui_1.info)('No secrets saved.');
|
|
50
|
+
(0, ui_1.table)(['NAME', 'VALUE'], list.map((secret) => [secret.name, secret.value]));
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
(0, ui_1.handleError)(err);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function secretsSetCommand(inputName, opts) {
|
|
57
|
+
const api = (0, session_1.requireApi)();
|
|
58
|
+
try {
|
|
59
|
+
const name = validateName(inputName);
|
|
60
|
+
const value = await readSecretValue(name, opts.stdin);
|
|
61
|
+
if (value === undefined)
|
|
62
|
+
return void (0, ui_1.info)('Cancelled.');
|
|
63
|
+
if (!value)
|
|
64
|
+
throw new Error('Secret value cannot be empty');
|
|
65
|
+
if (value.length > 8192)
|
|
66
|
+
throw new Error('Secret value must be at most 8192 characters');
|
|
67
|
+
const { secrets } = await api.listSecrets();
|
|
68
|
+
const existing = (secrets || []).some((secret) => secret.name === name);
|
|
69
|
+
const payload = (secrets || []).map((secret) => secret.name === name ? { name, value, originalName: name } : preserve(secret));
|
|
70
|
+
if (!existing)
|
|
71
|
+
payload.push({ name, value });
|
|
72
|
+
await api.saveSecrets(payload);
|
|
73
|
+
(0, ui_1.success)(`Secret "${name}" ${existing ? 'updated' : 'created'}.`);
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
(0, ui_1.handleError)(err);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function secretsRemoveCommand(inputName, opts) {
|
|
80
|
+
const api = (0, session_1.requireApi)();
|
|
81
|
+
try {
|
|
82
|
+
const name = validateName(inputName);
|
|
83
|
+
const { secrets } = await api.listSecrets();
|
|
84
|
+
const list = secrets || [];
|
|
85
|
+
if (!list.some((secret) => secret.name === name))
|
|
86
|
+
throw new Error(`Secret "${name}" not found`);
|
|
87
|
+
if (!opts.yes) {
|
|
88
|
+
const answer = await (0, prompts_1.default)({
|
|
89
|
+
type: 'confirm',
|
|
90
|
+
name: 'ok',
|
|
91
|
+
message: `Remove secret "${name}"?`,
|
|
92
|
+
initial: false,
|
|
93
|
+
});
|
|
94
|
+
if (!answer.ok)
|
|
95
|
+
return void (0, ui_1.info)('Cancelled.');
|
|
96
|
+
}
|
|
97
|
+
await api.saveSecrets(list.filter((secret) => secret.name !== name).map(preserve));
|
|
98
|
+
(0, ui_1.success)(`Secret "${name}" removed.`);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
(0, ui_1.handleError)(err);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function secretsRenameCommand(inputOldName, inputNewName) {
|
|
105
|
+
const api = (0, session_1.requireApi)();
|
|
106
|
+
try {
|
|
107
|
+
const oldName = validateName(inputOldName);
|
|
108
|
+
const newName = validateName(inputNewName);
|
|
109
|
+
if (oldName === newName)
|
|
110
|
+
return void (0, ui_1.info)('Secret name is unchanged.');
|
|
111
|
+
const { secrets } = await api.listSecrets();
|
|
112
|
+
const list = secrets || [];
|
|
113
|
+
if (!list.some((secret) => secret.name === oldName))
|
|
114
|
+
throw new Error(`Secret "${oldName}" not found`);
|
|
115
|
+
if (list.some((secret) => secret.name === newName))
|
|
116
|
+
throw new Error(`Secret "${newName}" already exists`);
|
|
117
|
+
const payload = list.map((secret) => secret.name === oldName ? { name: newName, value: '', originalName: oldName } : preserve(secret));
|
|
118
|
+
await api.saveSecrets(payload);
|
|
119
|
+
(0, ui_1.success)(`Secret "${oldName}" renamed to "${newName}".`);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
(0, ui_1.handleError)(err);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.readSetJson = readSetJson;
|
|
4
|
+
exports.pruneUndefined = pruneUndefined;
|
|
5
|
+
exports.requireVersion = requireVersion;
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const looseJson_1 = require("../lib/looseJson");
|
|
8
|
+
/**
|
|
9
|
+
* `--set '<json>'` or `--set @file.json`, one convention across every command
|
|
10
|
+
* that takes structured input.
|
|
11
|
+
*
|
|
12
|
+
* Inline argv may have had its quotes stripped by the shell, so it goes through
|
|
13
|
+
* the lenient repair pass; a file's contents did not, and stay strict.
|
|
14
|
+
*/
|
|
15
|
+
function readSetJson(raw) {
|
|
16
|
+
let text = raw;
|
|
17
|
+
let fromShell = true;
|
|
18
|
+
if (text.startsWith('@')) {
|
|
19
|
+
const path = text.slice(1);
|
|
20
|
+
if (!(0, fs_1.existsSync)(path))
|
|
21
|
+
throw new Error(`File not found: ${path}`);
|
|
22
|
+
text = (0, looseJson_1.readJsonFile)(path);
|
|
23
|
+
fromShell = false;
|
|
24
|
+
}
|
|
25
|
+
try {
|
|
26
|
+
return (fromShell ? (0, looseJson_1.parseLooseJson)(text) : JSON.parse(text));
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
throw new Error(`--set is not valid JSON: ${err.message}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Drop undefined-valued keys, so spreading this over a defaults object doesn't clobber them. */
|
|
33
|
+
function pruneUndefined(obj) {
|
|
34
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined));
|
|
35
|
+
}
|
|
36
|
+
/** First id from a `--versions` spec, or a clear error naming the flag. */
|
|
37
|
+
function requireVersion(spec, flag = '--versions <versionId>') {
|
|
38
|
+
const id = (spec || '').split(',').map((s) => s.trim()).filter(Boolean)[0];
|
|
39
|
+
if (!id)
|
|
40
|
+
throw new Error(`${flag} is required`);
|
|
41
|
+
return id;
|
|
42
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.simTestsListCommand = simTestsListCommand;
|
|
7
|
+
exports.simCreateCommand = simCreateCommand;
|
|
8
|
+
exports.simDeleteCommand = simDeleteCommand;
|
|
9
|
+
exports.simRunCommand = simRunCommand;
|
|
10
|
+
exports.simRunsListCommand = simRunsListCommand;
|
|
11
|
+
exports.simResultsCommand = simResultsCommand;
|
|
12
|
+
const prompts_1 = __importDefault(require("prompts"));
|
|
13
|
+
const session_1 = require("../lib/session");
|
|
14
|
+
const ui_1 = require("../lib/ui");
|
|
15
|
+
async function simTestsListCommand(agentId, opts) {
|
|
16
|
+
const api = (0, session_1.requireApi)();
|
|
17
|
+
try {
|
|
18
|
+
const res = await api.listTestCases(agentId);
|
|
19
|
+
const rows = res.testCases || res.data || [];
|
|
20
|
+
if (opts.json)
|
|
21
|
+
return void console.log(JSON.stringify(rows, null, 2));
|
|
22
|
+
if (!rows.length)
|
|
23
|
+
return void (0, ui_1.info)('No test cases yet.');
|
|
24
|
+
(0, ui_1.table)(['NAME', 'ID', 'PROMPT', 'CRITERIA', 'MODEL'], rows.map((t) => [t.testcaseName || t.name || '', t._id, t.userPrompt || '', t.successCriteria || '', t.testLLMModel || '']));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
(0, ui_1.handleError)(err);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function simCreateCommand(agentId, opts) {
|
|
31
|
+
const api = (0, session_1.requireApi)();
|
|
32
|
+
try {
|
|
33
|
+
let { name, prompt, criteria } = opts;
|
|
34
|
+
if (!name || !prompt || !criteria) {
|
|
35
|
+
const answers = await (0, prompts_1.default)([
|
|
36
|
+
{ type: name ? null : 'text', name: 'name', message: 'Test case name' },
|
|
37
|
+
{ type: prompt ? null : 'text', name: 'prompt', message: 'Simulated user prompt' },
|
|
38
|
+
{ type: criteria ? null : 'text', name: 'criteria', message: 'Success criteria' },
|
|
39
|
+
], { onCancel: () => process.exit(1) });
|
|
40
|
+
name = name || answers.name;
|
|
41
|
+
prompt = prompt || answers.prompt;
|
|
42
|
+
criteria = criteria || answers.criteria;
|
|
43
|
+
}
|
|
44
|
+
if (!name || !prompt || !criteria) {
|
|
45
|
+
return (0, ui_1.handleError)(new Error('--name, --prompt and --criteria are all required (or answer the prompts)'));
|
|
46
|
+
}
|
|
47
|
+
const res = await api.createTestCase({
|
|
48
|
+
agentId,
|
|
49
|
+
testcaseName: name,
|
|
50
|
+
userPrompt: prompt,
|
|
51
|
+
successCriteria: criteria,
|
|
52
|
+
testLLMModel: opts.model || 'gpt-4.1-mini',
|
|
53
|
+
testVariables: {},
|
|
54
|
+
testFunctions: [],
|
|
55
|
+
});
|
|
56
|
+
(0, ui_1.success)(`Created test case ${res.testCase?._id || ''}.`);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
(0, ui_1.handleError)(err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function simDeleteCommand(testCaseId, opts) {
|
|
63
|
+
const api = (0, session_1.requireApi)();
|
|
64
|
+
try {
|
|
65
|
+
if (!opts.yes) {
|
|
66
|
+
const { ok } = await (0, prompts_1.default)({
|
|
67
|
+
type: 'confirm', name: 'ok', message: `Delete test case ${testCaseId}?`, initial: false,
|
|
68
|
+
});
|
|
69
|
+
if (!ok)
|
|
70
|
+
return void (0, ui_1.info)('Cancelled.');
|
|
71
|
+
}
|
|
72
|
+
const res = await api.deleteTestCase(testCaseId);
|
|
73
|
+
(0, ui_1.success)(res.msg || 'Deleted.');
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
(0, ui_1.handleError)(err);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async function simRunCommand(agentId, opts) {
|
|
80
|
+
const api = (0, session_1.requireApi)();
|
|
81
|
+
const testIds = (opts.tests || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
82
|
+
const versionIds = (opts.versions || '').split(',').map((s) => s.trim()).filter(Boolean);
|
|
83
|
+
if (!testIds.length)
|
|
84
|
+
return (0, ui_1.handleError)(new Error('--tests <testCaseIds> is required'));
|
|
85
|
+
if (!versionIds.length)
|
|
86
|
+
return (0, ui_1.handleError)(new Error('--versions <versionIds> is required'));
|
|
87
|
+
try {
|
|
88
|
+
const res = await api.runSimulation({
|
|
89
|
+
agentId, testIds, versionIds,
|
|
90
|
+
batchName: opts.name || `cli-run-${Date.now()}`,
|
|
91
|
+
epoch: 1,
|
|
92
|
+
});
|
|
93
|
+
(0, ui_1.success)(`Simulation run started (${res.batch?._id || ''}). Results arrive asynchronously.`);
|
|
94
|
+
(0, ui_1.info)(`Check with: cku sim results ${res.batch?._id || '<batchId>'}`);
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
(0, ui_1.handleError)(err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function simRunsListCommand(agentId, opts) {
|
|
101
|
+
const api = (0, session_1.requireApi)();
|
|
102
|
+
try {
|
|
103
|
+
const res = await api.listSimulationRuns(agentId);
|
|
104
|
+
const rows = res.runs || res.data || [];
|
|
105
|
+
if (opts.json)
|
|
106
|
+
return void console.log(JSON.stringify(rows, null, 2));
|
|
107
|
+
if (!rows.length)
|
|
108
|
+
return void (0, ui_1.info)('No simulation runs yet.');
|
|
109
|
+
(0, ui_1.table)(['NAME', 'BATCH ID', 'STATUS', 'CREATED'], rows.map((r) => [
|
|
110
|
+
r.batchName || r.name || '',
|
|
111
|
+
r.batch?._id || r._id || '',
|
|
112
|
+
r.status || '',
|
|
113
|
+
r.createdAt ? new Date(r.createdAt).toLocaleString('en-US') : '',
|
|
114
|
+
]));
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
(0, ui_1.handleError)(err);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function simResultsCommand(batchId, opts) {
|
|
121
|
+
const api = (0, session_1.requireApi)();
|
|
122
|
+
try {
|
|
123
|
+
const res = await api.simulationResults(batchId);
|
|
124
|
+
const rows = res.results || res.data || [];
|
|
125
|
+
if (opts.json)
|
|
126
|
+
return void console.log(JSON.stringify(rows, null, 2));
|
|
127
|
+
if (!rows.length)
|
|
128
|
+
return void (0, ui_1.info)('No results yet — runs are asynchronous.');
|
|
129
|
+
(0, ui_1.table)(['TEST CASE', 'VERSION', 'PASSED', 'SCORE'], rows.map((r) => [
|
|
130
|
+
r.testcaseName || r.testCaseId || '',
|
|
131
|
+
r.versionId?.versionName || r.versionId || '',
|
|
132
|
+
r.passed === undefined ? '' : r.passed ? 'yes' : 'no',
|
|
133
|
+
r.score ?? '',
|
|
134
|
+
]));
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
(0, ui_1.handleError)(err);
|
|
138
|
+
}
|
|
139
|
+
}
|