@indemn/cli 1.5.0 → 1.7.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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +9 -4
- package/.github/workflows/ci.yml +10 -0
- package/.github/workflows/publish.yml +2 -2
- package/.mcp.json +1 -4
- package/CONTRIBUTING.md +3 -1
- package/README.md +69 -176
- package/dist/cli/agents.d.ts.map +1 -1
- package/dist/cli/agents.js +226 -18
- package/dist/cli/agents.js.map +1 -1
- package/dist/cli/analytics.d.ts +3 -0
- package/dist/cli/analytics.d.ts.map +1 -0
- package/dist/cli/analytics.js +133 -0
- package/dist/cli/analytics.js.map +1 -0
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +62 -17
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/billing.d.ts +3 -0
- package/dist/cli/billing.d.ts.map +1 -0
- package/dist/cli/billing.js +23 -0
- package/dist/cli/billing.js.map +1 -0
- package/dist/cli/campaigns.d.ts +3 -0
- package/dist/cli/campaigns.d.ts.map +1 -0
- package/dist/cli/campaigns.js +34 -0
- package/dist/cli/campaigns.js.map +1 -0
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +69 -102
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +71 -27
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/conversations.d.ts +3 -0
- package/dist/cli/conversations.d.ts.map +1 -0
- package/dist/cli/conversations.js +129 -0
- package/dist/cli/conversations.js.map +1 -0
- package/dist/cli/distribution.d.ts +3 -0
- package/dist/cli/distribution.d.ts.map +1 -0
- package/dist/cli/distribution.js +122 -0
- package/dist/cli/distribution.js.map +1 -0
- package/dist/cli/eval.d.ts.map +1 -1
- package/dist/cli/eval.js +315 -71
- package/dist/cli/eval.js.map +1 -1
- package/dist/cli/functions.d.ts.map +1 -1
- package/dist/cli/functions.js +125 -35
- package/dist/cli/functions.js.map +1 -1
- package/dist/cli/index.js +63 -54
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.d.ts +27 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +152 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/kb.d.ts.map +1 -1
- package/dist/cli/kb.js +141 -15
- package/dist/cli/kb.js.map +1 -1
- package/dist/cli/link.d.ts +21 -0
- package/dist/cli/link.d.ts.map +1 -0
- package/dist/cli/link.js +112 -0
- package/dist/cli/link.js.map +1 -0
- package/dist/cli/org.d.ts.map +1 -1
- package/dist/cli/org.js +79 -67
- package/dist/cli/org.js.map +1 -1
- package/dist/cli/platform.d.ts +3 -0
- package/dist/cli/platform.d.ts.map +1 -0
- package/dist/cli/platform.js +133 -0
- package/dist/cli/platform.js.map +1 -0
- package/dist/cli/tickets.d.ts +3 -0
- package/dist/cli/tickets.d.ts.map +1 -0
- package/dist/cli/tickets.js +125 -0
- package/dist/cli/tickets.js.map +1 -0
- package/dist/cli/utils/output.d.ts +15 -9
- package/dist/cli/utils/output.d.ts.map +1 -1
- package/dist/cli/utils/output.js +53 -46
- package/dist/cli/utils/output.js.map +1 -1
- package/dist/cli/voice.d.ts +3 -0
- package/dist/cli/voice.d.ts.map +1 -0
- package/dist/cli/voice.js +206 -0
- package/dist/cli/voice.js.map +1 -0
- package/dist/core/diff.d.ts +16 -0
- package/dist/core/diff.d.ts.map +1 -0
- package/dist/core/diff.js +57 -0
- package/dist/core/diff.js.map +1 -0
- package/dist/core/org-resolver.d.ts +37 -0
- package/dist/core/org-resolver.d.ts.map +1 -0
- package/dist/core/org-resolver.js +73 -0
- package/dist/core/org-resolver.js.map +1 -0
- package/dist/core/runtime.d.ts +36 -0
- package/dist/core/runtime.d.ts.map +1 -0
- package/dist/core/runtime.js +62 -0
- package/dist/core/runtime.js.map +1 -0
- package/dist/core/scope.d.ts +33 -0
- package/dist/core/scope.d.ts.map +1 -0
- package/dist/core/scope.js +37 -0
- package/dist/core/scope.js.map +1 -0
- package/dist/mcp/registry.d.ts +67 -0
- package/dist/mcp/registry.d.ts.map +1 -0
- package/dist/mcp/registry.js +195 -0
- package/dist/mcp/registry.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +87 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-meta.d.ts +21 -0
- package/dist/mcp/tool-meta.d.ts.map +1 -0
- package/dist/mcp/tool-meta.js +203 -0
- package/dist/mcp/tool-meta.js.map +1 -0
- package/dist/mcp/tools.d.ts +2 -1
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +1462 -215
- package/dist/mcp/tools.js.map +1 -1
- package/dist/sdk/agent-bundle.d.ts +99 -0
- package/dist/sdk/agent-bundle.d.ts.map +1 -0
- package/dist/sdk/agent-bundle.js +211 -0
- package/dist/sdk/agent-bundle.js.map +1 -0
- package/dist/sdk/agents.d.ts +54 -1
- package/dist/sdk/agents.d.ts.map +1 -1
- package/dist/sdk/agents.js +59 -5
- package/dist/sdk/agents.js.map +1 -1
- package/dist/sdk/analytics.d.ts +158 -0
- package/dist/sdk/analytics.d.ts.map +1 -0
- package/dist/sdk/analytics.js +185 -0
- package/dist/sdk/analytics.js.map +1 -0
- package/dist/sdk/auth.d.ts +34 -12
- package/dist/sdk/auth.d.ts.map +1 -1
- package/dist/sdk/auth.js +60 -77
- package/dist/sdk/auth.js.map +1 -1
- package/dist/sdk/billing.d.ts +25 -0
- package/dist/sdk/billing.d.ts.map +1 -0
- package/dist/sdk/billing.js +61 -0
- package/dist/sdk/billing.js.map +1 -0
- package/dist/sdk/campaigns.d.ts +35 -0
- package/dist/sdk/campaigns.d.ts.map +1 -0
- package/dist/sdk/campaigns.js +59 -0
- package/dist/sdk/campaigns.js.map +1 -0
- package/dist/sdk/chat.d.ts +25 -2
- package/dist/sdk/chat.d.ts.map +1 -1
- package/dist/sdk/chat.js +37 -10
- package/dist/sdk/chat.js.map +1 -1
- package/dist/sdk/client.d.ts +93 -5
- package/dist/sdk/client.d.ts.map +1 -1
- package/dist/sdk/client.js +362 -100
- package/dist/sdk/client.js.map +1 -1
- package/dist/sdk/config-change.d.ts +74 -0
- package/dist/sdk/config-change.d.ts.map +1 -0
- package/dist/sdk/config-change.js +117 -0
- package/dist/sdk/config-change.js.map +1 -0
- package/dist/sdk/config.d.ts.map +1 -1
- package/dist/sdk/config.js +2 -1
- package/dist/sdk/config.js.map +1 -1
- package/dist/sdk/conversations.d.ts +101 -0
- package/dist/sdk/conversations.d.ts.map +1 -0
- package/dist/sdk/conversations.js +155 -0
- package/dist/sdk/conversations.js.map +1 -0
- package/dist/sdk/credentials.d.ts +25 -0
- package/dist/sdk/credentials.d.ts.map +1 -0
- package/dist/sdk/credentials.js +44 -0
- package/dist/sdk/credentials.js.map +1 -0
- package/dist/sdk/distribution.d.ts +94 -0
- package/dist/sdk/distribution.d.ts.map +1 -0
- package/dist/sdk/distribution.js +100 -0
- package/dist/sdk/distribution.js.map +1 -0
- package/dist/sdk/eval.d.ts +81 -0
- package/dist/sdk/eval.d.ts.map +1 -1
- package/dist/sdk/eval.js +117 -0
- package/dist/sdk/eval.js.map +1 -1
- package/dist/sdk/functions.d.ts +14 -1
- package/dist/sdk/functions.d.ts.map +1 -1
- package/dist/sdk/functions.js +48 -5
- package/dist/sdk/functions.js.map +1 -1
- package/dist/sdk/index.d.ts +2 -0
- package/dist/sdk/index.d.ts.map +1 -1
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/index.js.map +1 -1
- package/dist/sdk/jarvis-chat.d.ts +28 -0
- package/dist/sdk/jarvis-chat.d.ts.map +1 -0
- package/dist/sdk/jarvis-chat.js +117 -0
- package/dist/sdk/jarvis-chat.js.map +1 -0
- package/dist/sdk/kb.d.ts +69 -1
- package/dist/sdk/kb.d.ts.map +1 -1
- package/dist/sdk/kb.js +111 -24
- package/dist/sdk/kb.js.map +1 -1
- package/dist/sdk/orgs.d.ts +13 -0
- package/dist/sdk/orgs.d.ts.map +1 -0
- package/dist/sdk/orgs.js +59 -0
- package/dist/sdk/orgs.js.map +1 -0
- package/dist/sdk/platform.d.ts +62 -0
- package/dist/sdk/platform.d.ts.map +1 -0
- package/dist/sdk/platform.js +95 -0
- package/dist/sdk/platform.js.map +1 -0
- package/dist/sdk/session.d.ts +25 -0
- package/dist/sdk/session.d.ts.map +1 -0
- package/dist/sdk/session.js +69 -0
- package/dist/sdk/session.js.map +1 -0
- package/dist/sdk/skills.d.ts +46 -0
- package/dist/sdk/skills.d.ts.map +1 -0
- package/dist/sdk/skills.js +203 -0
- package/dist/sdk/skills.js.map +1 -0
- package/dist/sdk/test-chat.d.ts +52 -0
- package/dist/sdk/test-chat.d.ts.map +1 -0
- package/dist/sdk/test-chat.js +134 -0
- package/dist/sdk/test-chat.js.map +1 -0
- package/dist/sdk/tickets.d.ts +98 -0
- package/dist/sdk/tickets.d.ts.map +1 -0
- package/dist/sdk/tickets.js +91 -0
- package/dist/sdk/tickets.js.map +1 -0
- package/dist/sdk/types.d.ts +91 -6
- package/dist/sdk/types.d.ts.map +1 -1
- package/dist/sdk/voice.d.ts +147 -0
- package/dist/sdk/voice.d.ts.map +1 -0
- package/dist/sdk/voice.js +236 -0
- package/dist/sdk/voice.js.map +1 -0
- package/package.json +22 -3
- package/scripts/check-skills.mjs +25 -0
- package/skills/agent-builder/SKILL.md +89 -252
- package/skills/evaluations/SKILL.md +23 -234
- package/skills/evaluations/references/analysis.md +95 -0
- package/skills/evaluations/references/cli-reference.md +28 -0
- package/skills/evaluations/references/json-formats.md +100 -0
- package/src/cli/agents.ts +202 -22
- package/src/cli/analytics.ts +138 -0
- package/src/cli/auth.ts +56 -18
- package/src/cli/billing.ts +25 -0
- package/src/cli/campaigns.ts +33 -0
- package/src/cli/chat.ts +66 -115
- package/src/cli/config.ts +65 -31
- package/src/cli/conversations.ts +133 -0
- package/src/cli/distribution.ts +116 -0
- package/src/cli/eval.ts +254 -70
- package/src/cli/functions.ts +122 -36
- package/src/cli/index.ts +58 -55
- package/src/cli/init.ts +154 -0
- package/src/cli/kb.ts +120 -17
- package/src/cli/link.ts +117 -0
- package/src/cli/org.ts +81 -75
- package/src/cli/platform.ts +128 -0
- package/src/cli/tickets.ts +130 -0
- package/src/cli/utils/output.ts +51 -42
- package/src/cli/voice.ts +204 -0
- package/src/core/diff.ts +52 -0
- package/src/core/org-resolver.ts +113 -0
- package/src/core/runtime.ts +73 -0
- package/src/core/scope.ts +64 -0
- package/src/mcp/registry.ts +236 -0
- package/src/mcp/server.ts +84 -2
- package/src/mcp/tool-meta.ts +241 -0
- package/src/mcp/tools.ts +1529 -265
- package/src/sdk/agent-bundle.ts +245 -0
- package/src/sdk/agents.ts +74 -5
- package/src/sdk/analytics.ts +220 -0
- package/src/sdk/auth.ts +87 -107
- package/src/sdk/billing.ts +59 -0
- package/src/sdk/campaigns.ts +60 -0
- package/src/sdk/chat.ts +44 -10
- package/src/sdk/client.ts +373 -102
- package/src/sdk/config-change.ts +148 -0
- package/src/sdk/config.ts +2 -1
- package/src/sdk/conversations.ts +199 -0
- package/src/sdk/credentials.ts +53 -0
- package/src/sdk/distribution.ts +132 -0
- package/src/sdk/eval.ts +140 -0
- package/src/sdk/functions.ts +52 -6
- package/src/sdk/index.ts +2 -0
- package/src/sdk/jarvis-chat.ts +112 -0
- package/src/sdk/kb.ts +116 -29
- package/src/sdk/orgs.ts +62 -0
- package/src/sdk/platform.ts +115 -0
- package/src/sdk/session.ts +90 -0
- package/src/sdk/skills.ts +188 -0
- package/src/sdk/test-chat.ts +151 -0
- package/src/sdk/tickets.ts +120 -0
- package/src/sdk/types.ts +88 -6
- package/src/sdk/voice.ts +262 -0
- package/test/cli/entry.test.ts +34 -0
- package/test/cli/init.test.ts +66 -0
- package/test/cli/link.test.ts +57 -0
- package/test/core/org-resolver.test.ts +92 -0
- package/test/core/runtime.test.ts +30 -0
- package/test/core/scope.test.ts +93 -0
- package/test/harness.test.ts +24 -0
- package/test/helpers/config-home.ts +22 -0
- package/test/helpers/fake-middleware.ts +56 -0
- package/test/helpers/fetch-recorder.ts +75 -0
- package/test/helpers/jwt.ts +9 -0
- package/test/helpers/mcp-pair.ts +38 -0
- package/test/mcp/__snapshots__/tools-snapshot.test.ts.snap +145 -0
- package/test/mcp/askers.test.ts +60 -0
- package/test/mcp/elicit-timeout.test.ts +29 -0
- package/test/mcp/registry.test.ts +169 -0
- package/test/mcp/shaping.test.ts +41 -0
- package/test/mcp/tools-snapshot.test.ts +42 -0
- package/test/sdk/agents.test.ts +232 -0
- package/test/sdk/analytics.test.ts +89 -0
- package/test/sdk/auth.test.ts +70 -0
- package/test/sdk/billing.test.ts +47 -0
- package/test/sdk/campaigns.test.ts +43 -0
- package/test/sdk/client.test.ts +80 -0
- package/test/sdk/config-change.test.ts +142 -0
- package/test/sdk/conversations.test.ts +80 -0
- package/test/sdk/credentials.test.ts +33 -0
- package/test/sdk/distribution.test.ts +55 -0
- package/test/sdk/eval.test.ts +155 -0
- package/test/sdk/kb-upload.test.ts +50 -0
- package/test/sdk/kb.test.ts +95 -0
- package/test/sdk/orgs.test.ts +48 -0
- package/test/sdk/platform.test.ts +89 -0
- package/test/sdk/renewal.test.ts +115 -0
- package/test/sdk/session.test.ts +60 -0
- package/test/sdk/skills.test.ts +235 -0
- package/test/sdk/test-chat.test.ts +112 -0
- package/test/sdk/tickets.test.ts +42 -0
- package/test/sdk/voice.test.ts +147 -0
- package/vitest.config.ts +11 -0
package/dist/cli/eval.js
CHANGED
|
@@ -2,45 +2,60 @@ import * as fs from 'fs';
|
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import { IndemnClient } from '../sdk/client.js';
|
|
5
|
-
import { EvalSDK } from '../sdk/eval.js';
|
|
5
|
+
import { EvalSDK, gateVerdict } from '../sdk/eval.js';
|
|
6
|
+
import { CliError, EXIT } from '../core/runtime.js';
|
|
7
|
+
import { confirmOrExit } from './link.js';
|
|
6
8
|
import { RubricsSDK } from '../sdk/rubrics.js';
|
|
7
9
|
import { TestSetsSDK } from '../sdk/test-sets.js';
|
|
8
10
|
import { exportEvalMarkdown, exportEvalReport } from '../report/export.js';
|
|
9
|
-
import { formatTable, printJson, printError, printSuccess
|
|
11
|
+
import { formatTable, printJson, printError, printSuccess } from './utils/output.js';
|
|
10
12
|
export function registerEvalCommands(program) {
|
|
11
13
|
const eval_ = program.command('eval').description('Run and manage evaluations');
|
|
12
14
|
eval_
|
|
13
15
|
.command('run')
|
|
14
16
|
.argument('<agent-id>', 'Agent ID')
|
|
15
|
-
.description('Run an evaluation')
|
|
16
|
-
.requiredOption('--test-set <id>', 'Test set ID')
|
|
17
|
+
.description('Run an evaluation (costs model calls; asks first). With a threshold it becomes a CI gate')
|
|
18
|
+
.requiredOption('--test-set <id>', 'Test set ID (must belong to this agent)')
|
|
17
19
|
.option('--rubric <id>', 'Rubric ID')
|
|
18
20
|
.option('--concurrency <n>', 'Concurrency level')
|
|
19
21
|
.option('--wait', 'Wait for evaluation to complete')
|
|
22
|
+
.option('--timeout <seconds>', 'With --wait: give up after this long', '1800')
|
|
20
23
|
.option('--eval-model <model>', 'Model for evaluation judging')
|
|
21
|
-
.option('--keep', 'Keep evaluation data after completion')
|
|
22
24
|
.option('--limit <n>', 'Limit number of items to evaluate')
|
|
23
25
|
.option('--component-scope <scope>', 'Component scope filter')
|
|
24
26
|
.option('--component-ids <ids>', 'Comma-separated component IDs')
|
|
25
|
-
.option('--conversation-ids <ids>', 'Comma-separated conversation IDs')
|
|
26
|
-
.option('--
|
|
27
|
+
.option('--conversation-ids <ids>', 'Comma-separated conversation IDs (transcript mode)')
|
|
28
|
+
.option('--date-from <date>', 'Grade real conversations from this date (transcript mode)')
|
|
29
|
+
.option('--date-to <date>', 'Up to this date, exclusive (needs --date-from)')
|
|
30
|
+
.option('--mode <mode>', 'Evaluation mode: "transcript" grades real conversations')
|
|
31
|
+
.option('--webhook-url <url>', 'Called when the run finishes')
|
|
32
|
+
.option('--min-pass-rate <rate>', 'CI gate: exit 1 when passed/total is below this (0..1). Implies --wait')
|
|
33
|
+
.option('--max-p95-ms <ms>', 'CI gate: exit 1 when p95 latency is above this. Implies --wait')
|
|
34
|
+
.option('--error-policy <policy>', 'CI gate: block (a failed run fails the gate) or warn', 'block')
|
|
35
|
+
.option('--fail-on-error', 'CI gate: also exit 1 when any case had an infrastructure error. Implies --wait')
|
|
36
|
+
.option('--allow-other-agent', 'Run a test set written for another agent')
|
|
37
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
38
|
+
.option('--json', 'Print the final run as JSON')
|
|
27
39
|
.action(async (agentId, options) => {
|
|
28
40
|
try {
|
|
29
41
|
const client = new IndemnClient();
|
|
30
42
|
const sdk = new EvalSDK(client);
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
await sdk.assertAgentInOrg(agentId);
|
|
44
|
+
const testSet = await new TestSetsSDK(client).getTestSet(options.testSet);
|
|
45
|
+
const owner = testSet.agent_id;
|
|
46
|
+
if (owner && owner !== agentId && !options.allowOtherAgent) {
|
|
47
|
+
throw new CliError(`Test set ${options.testSet} was written for agent ${owner}, not ${agentId}. Pass --allow-other-agent to run it anyway.`, EXIT.NEEDS_INPUT);
|
|
48
|
+
}
|
|
49
|
+
const input = { bot_id: agentId, test_set_id: options.testSet };
|
|
36
50
|
if (options.rubric)
|
|
37
51
|
input.rubric_id = options.rubric;
|
|
38
52
|
if (options.concurrency)
|
|
39
53
|
input.concurrency = parseInt(options.concurrency, 10);
|
|
54
|
+
// Voice simulations share call rooms; the dashboard runs them one at a time.
|
|
55
|
+
if ((testSet.items ?? []).some((i) => i.type === 'voice_simulation'))
|
|
56
|
+
input.concurrency = 1;
|
|
40
57
|
if (options.evalModel)
|
|
41
58
|
input.eval_model = options.evalModel;
|
|
42
|
-
if (options.keep !== undefined)
|
|
43
|
-
input.keep = options.keep;
|
|
44
59
|
if (options.limit)
|
|
45
60
|
input.limit = parseInt(options.limit, 10);
|
|
46
61
|
if (options.componentScope)
|
|
@@ -49,33 +64,67 @@ export function registerEvalCommands(program) {
|
|
|
49
64
|
input.component_ids = options.componentIds.split(',');
|
|
50
65
|
if (options.conversationIds)
|
|
51
66
|
input.conversation_ids = options.conversationIds.split(',');
|
|
52
|
-
if (options.
|
|
67
|
+
if (options.webhookUrl)
|
|
68
|
+
input.webhook_url = options.webhookUrl;
|
|
69
|
+
if (Boolean(options.dateFrom) !== Boolean(options.dateTo)) {
|
|
70
|
+
throw new CliError('--date-from and --date-to go together.', EXIT.NEEDS_INPUT);
|
|
71
|
+
}
|
|
72
|
+
if (options.dateFrom) {
|
|
73
|
+
input.date_from = options.dateFrom;
|
|
74
|
+
input.date_to = options.dateTo;
|
|
75
|
+
if (options.mode && options.mode !== 'transcript')
|
|
76
|
+
throw new CliError('A date range only works with --mode transcript.', EXIT.NEEDS_INPUT);
|
|
77
|
+
// The service ignores dates outside transcript mode and would run the test set instead.
|
|
78
|
+
input.mode = 'transcript';
|
|
79
|
+
}
|
|
80
|
+
else if (options.mode) {
|
|
53
81
|
input.mode = options.mode;
|
|
82
|
+
}
|
|
83
|
+
const items = testSet.items?.length ?? 0;
|
|
84
|
+
const count = input.mode === 'transcript' ? 'real conversations' : `${input.limit ? Math.min(input.limit, items) : items} item(s)`;
|
|
85
|
+
await confirmOrExit(`Run an evaluation of agent ${agentId} on ${count} with concurrency ${input.concurrency ?? 1}? It uses paid model calls and cannot be cancelled.`, Boolean(options.yes));
|
|
86
|
+
const gate = {
|
|
87
|
+
minPassRate: options.minPassRate !== undefined ? Number(options.minPassRate) : undefined,
|
|
88
|
+
maxP95Ms: options.maxP95Ms !== undefined ? Number(options.maxP95Ms) : undefined,
|
|
89
|
+
errorPolicy: options.errorPolicy === 'warn' ? 'warn' : 'block',
|
|
90
|
+
failOnError: Boolean(options.failOnError),
|
|
91
|
+
};
|
|
92
|
+
const gating = gate.minPassRate !== undefined || gate.maxP95Ms !== undefined || gate.failOnError;
|
|
93
|
+
const spinner = ora('Triggering evaluation...').start();
|
|
54
94
|
const result = await sdk.triggerEvaluation(input);
|
|
55
95
|
spinner.stop();
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if (options.wait) {
|
|
61
|
-
const pollSpinner = ora('Waiting for evaluation to complete...').start();
|
|
62
|
-
try {
|
|
63
|
-
const run = await sdk.pollRunUntilComplete(result.run_id);
|
|
64
|
-
pollSpinner.stop();
|
|
65
|
-
printSuccess(`Evaluation completed: ${run.status}`);
|
|
66
|
-
if (run.passed !== undefined)
|
|
67
|
-
console.log(` Passed: ${run.passed}`);
|
|
68
|
-
if (run.failed !== undefined)
|
|
69
|
-
console.log(` Failed: ${run.failed}`);
|
|
70
|
-
if (run.criteria_passed !== undefined && run.criteria_total !== undefined) {
|
|
71
|
-
console.log(` Criteria: ${run.criteria_passed}/${run.criteria_total}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
pollSpinner.fail('Evaluation did not complete in time');
|
|
76
|
-
throw err;
|
|
77
|
-
}
|
|
96
|
+
if (!options.json) {
|
|
97
|
+
printSuccess(`Evaluation started: ${result.run_id}`);
|
|
98
|
+
if (result.total)
|
|
99
|
+
console.log(` Total items: ${result.total}`);
|
|
78
100
|
}
|
|
101
|
+
if (!options.wait && !gating)
|
|
102
|
+
return;
|
|
103
|
+
const pollSpinner = options.json ? null : ora('Waiting for evaluation to complete...').start();
|
|
104
|
+
const run = await sdk.pollRunUntilComplete(result.run_id, 10_000, Number(options.timeout) * 1000).catch((err) => {
|
|
105
|
+
pollSpinner?.fail('Evaluation did not complete in time');
|
|
106
|
+
throw err;
|
|
107
|
+
});
|
|
108
|
+
pollSpinner?.stop();
|
|
109
|
+
const verdict = gateVerdict(run, gate);
|
|
110
|
+
if (options.json) {
|
|
111
|
+
printJson({ run, gate: gating ? verdict : undefined });
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
printSuccess(`Evaluation ${run.status}: ${run.passed ?? 0}/${run.total ?? 0} passed (${(verdict.passRate * 100).toFixed(1)}%)`);
|
|
115
|
+
if (run.errors)
|
|
116
|
+
console.log(chalk.yellow(` Infrastructure errors: ${run.errors}`));
|
|
117
|
+
if (run.latency?.p95_ms !== undefined)
|
|
118
|
+
console.log(` p95 latency: ${run.latency.p95_ms} ms`);
|
|
119
|
+
if (run.criteria_passed !== undefined && run.criteria_total !== undefined)
|
|
120
|
+
console.log(` Criteria: ${run.criteria_passed}/${run.criteria_total}`);
|
|
121
|
+
}
|
|
122
|
+
if (gating && !verdict.pass) {
|
|
123
|
+
console.error(chalk.red(`Gate failed: ${verdict.reasons.join('; ')}`));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
if (gating && !options.json)
|
|
127
|
+
printSuccess('Gate passed.');
|
|
79
128
|
}
|
|
80
129
|
catch (err) {
|
|
81
130
|
printError(err);
|
|
@@ -84,8 +133,8 @@ export function registerEvalCommands(program) {
|
|
|
84
133
|
});
|
|
85
134
|
eval_
|
|
86
135
|
.command('list')
|
|
87
|
-
.description('List evaluation runs')
|
|
88
|
-
.
|
|
136
|
+
.description('List evaluation runs of an agent')
|
|
137
|
+
.requiredOption('--agent-id <id>', 'Agent ID (runs of other orgs are never listed)')
|
|
89
138
|
.option('--json', 'Output as JSON')
|
|
90
139
|
.option('--limit <n>', 'Limit number of results')
|
|
91
140
|
.option('--page <n>', 'Page number (use with --limit)')
|
|
@@ -94,6 +143,7 @@ export function registerEvalCommands(program) {
|
|
|
94
143
|
const client = new IndemnClient();
|
|
95
144
|
const sdk = new EvalSDK(client);
|
|
96
145
|
const spinner = ora('Fetching evaluation runs...').start();
|
|
146
|
+
await sdk.assertAgentInOrg(options.agentId);
|
|
97
147
|
let runs = await sdk.listRuns(options.agentId);
|
|
98
148
|
spinner.stop();
|
|
99
149
|
if (options.page && options.limit) {
|
|
@@ -137,7 +187,7 @@ export function registerEvalCommands(program) {
|
|
|
137
187
|
const client = new IndemnClient();
|
|
138
188
|
const sdk = new EvalSDK(client);
|
|
139
189
|
const spinner = ora('Fetching run status...').start();
|
|
140
|
-
const run = await sdk.
|
|
190
|
+
const run = await sdk.getRunInOrg(runId);
|
|
141
191
|
spinner.stop();
|
|
142
192
|
if (options.json) {
|
|
143
193
|
printJson(run);
|
|
@@ -181,6 +231,7 @@ export function registerEvalCommands(program) {
|
|
|
181
231
|
const client = new IndemnClient();
|
|
182
232
|
const sdk = new EvalSDK(client);
|
|
183
233
|
const spinner = ora('Fetching results...').start();
|
|
234
|
+
await sdk.getRunInOrg(runId);
|
|
184
235
|
const results = await sdk.getRunResults(runId);
|
|
185
236
|
spinner.stop();
|
|
186
237
|
if (options.json) {
|
|
@@ -209,6 +260,7 @@ export function registerEvalCommands(program) {
|
|
|
209
260
|
const client = new IndemnClient();
|
|
210
261
|
const sdk = new EvalSDK(client);
|
|
211
262
|
const spinner = ora('Fetching bot context...').start();
|
|
263
|
+
await sdk.assertAgentInOrg(agentId);
|
|
212
264
|
const context = await sdk.getBotContext(agentId);
|
|
213
265
|
spinner.stop();
|
|
214
266
|
if (options.json) {
|
|
@@ -248,6 +300,7 @@ export function registerEvalCommands(program) {
|
|
|
248
300
|
try {
|
|
249
301
|
const client = new IndemnClient();
|
|
250
302
|
const spinner = ora('Fetching evaluation data...').start();
|
|
303
|
+
await new EvalSDK(client).getRunInOrg(runId);
|
|
251
304
|
const result = await exportEvalMarkdown(runId, options.output, client);
|
|
252
305
|
spinner.stop();
|
|
253
306
|
printSuccess(`Exported to ${result.file_path}`);
|
|
@@ -260,13 +313,33 @@ export function registerEvalCommands(program) {
|
|
|
260
313
|
eval_
|
|
261
314
|
.command('report')
|
|
262
315
|
.argument('<run-id>', 'Evaluation run ID')
|
|
263
|
-
.description('
|
|
316
|
+
.description('Branded evaluation report PDF (made here, or by the evaluations service with --server)')
|
|
264
317
|
.option('--output <path>', 'Output file path')
|
|
318
|
+
.option('--server', 'Ask the evaluations service to render it (completed runs only)')
|
|
319
|
+
.option('--force', 'With --server: render again instead of reusing the last report')
|
|
265
320
|
.action(async (runId, options) => {
|
|
266
321
|
try {
|
|
267
322
|
const client = new IndemnClient();
|
|
268
|
-
const
|
|
269
|
-
|
|
323
|
+
const sdk = new EvalSDK(client);
|
|
324
|
+
await sdk.getRunInOrg(runId);
|
|
325
|
+
if (options.server) {
|
|
326
|
+
const spinner = ora('Rendering on the evaluations service...').start();
|
|
327
|
+
const { download_url, report_id } = await sdk.exportReport(runId, Boolean(options.force));
|
|
328
|
+
spinner.stop();
|
|
329
|
+
if (options.output) {
|
|
330
|
+
const res = await fetch(download_url);
|
|
331
|
+
if (!res.ok)
|
|
332
|
+
throw new Error(`Download failed (${res.status}).`);
|
|
333
|
+
fs.writeFileSync(options.output, Buffer.from(await res.arrayBuffer()));
|
|
334
|
+
printSuccess(`Report saved to ${options.output}`);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
console.log(download_url);
|
|
338
|
+
console.log(chalk.dim(`Link valid for 15 minutes. New link later: indemn eval report-link ${report_id}`));
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const spinner = ora('Generating PDF...').start();
|
|
270
343
|
const result = await exportEvalReport(runId, options.output, client);
|
|
271
344
|
spinner.stop();
|
|
272
345
|
printSuccess(`Report saved to ${result.file_path}`);
|
|
@@ -276,12 +349,198 @@ export function registerEvalCommands(program) {
|
|
|
276
349
|
process.exit(1);
|
|
277
350
|
}
|
|
278
351
|
});
|
|
352
|
+
eval_
|
|
353
|
+
.command('report-link')
|
|
354
|
+
.argument('<report-id>', 'Report id printed by `eval report --server`')
|
|
355
|
+
.description('A fresh download link for a server-rendered report')
|
|
356
|
+
.action(async (reportId) => {
|
|
357
|
+
try {
|
|
358
|
+
console.log((await new EvalSDK(new IndemnClient()).downloadReport(reportId)).download_url);
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
printError(err);
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
eval_
|
|
366
|
+
.command('trends')
|
|
367
|
+
.requiredOption('--agent <id>', 'Agent ID')
|
|
368
|
+
.option('--test-set <id>', 'Only runs of this test set')
|
|
369
|
+
.option('--limit <n>', 'Runs to include', '50')
|
|
370
|
+
.option('--json', 'Output as JSON')
|
|
371
|
+
.description('Pass rate and latency over past runs, with a drift warning when the pass rate dropped')
|
|
372
|
+
.action(async (options) => {
|
|
373
|
+
try {
|
|
374
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
375
|
+
await sdk.assertAgentInOrg(options.agent);
|
|
376
|
+
const res = await sdk.trends(options.agent, options.testSet, Number(options.limit));
|
|
377
|
+
if (options.json) {
|
|
378
|
+
printJson(res);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
console.log(formatTable(['Run', 'When', 'Pass rate', 'Total', 'p95 ms'], res.series.map((r) => [
|
|
382
|
+
String(r.run_id), String(r.created_at ?? ''), `${(Number(r.pass_rate ?? 0) * 100).toFixed(1)}%`, String(r.total ?? ''), String(r.latency_p95_ms ?? ''),
|
|
383
|
+
])));
|
|
384
|
+
if (res.drift)
|
|
385
|
+
console.log(chalk.yellow(`Drift: pass rate fell from ${res.drift.from} to ${res.drift.to} in run ${res.drift.run_id}.`));
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
printError(err);
|
|
389
|
+
process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
eval_
|
|
393
|
+
.command('compare')
|
|
394
|
+
.argument('<run-a>', 'First run')
|
|
395
|
+
.argument('<run-b>', 'Second run')
|
|
396
|
+
.description('Pass rate and p95 latency difference between two runs')
|
|
397
|
+
.action(async (a, b) => {
|
|
398
|
+
try {
|
|
399
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
400
|
+
await sdk.getRunInOrg(a);
|
|
401
|
+
await sdk.getRunInOrg(b);
|
|
402
|
+
printJson(await sdk.compareRuns(a, b));
|
|
403
|
+
}
|
|
404
|
+
catch (err) {
|
|
405
|
+
printError(err);
|
|
406
|
+
process.exit(1);
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
eval_
|
|
410
|
+
.command('metrics')
|
|
411
|
+
.description('Voice quality metrics a test item may require (names for expected.voice.required_metrics)')
|
|
412
|
+
.action(async () => {
|
|
413
|
+
try {
|
|
414
|
+
printJson(await new EvalSDK(new IndemnClient()).qualityMetrics());
|
|
415
|
+
}
|
|
416
|
+
catch (err) {
|
|
417
|
+
printError(err);
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
eval_
|
|
422
|
+
.command('transcript')
|
|
423
|
+
.requiredOption('--agent <id>', 'Agent ID (needs a stored evaluator config)')
|
|
424
|
+
.requiredOption('--baseline <file>', 'JSON list of { sender: "user"|"bot", text }')
|
|
425
|
+
.requiredOption('--replay <file>', 'JSON list of { sender: "user"|"bot", text }')
|
|
426
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
427
|
+
.description('Grade a replayed conversation against a baseline one')
|
|
428
|
+
.action(async (options) => {
|
|
429
|
+
try {
|
|
430
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
431
|
+
await sdk.assertAgentInOrg(options.agent);
|
|
432
|
+
await confirmOrExit('Grade the transcript? It uses paid model calls.', Boolean(options.yes));
|
|
433
|
+
printJson(await sdk.evaluateTranscript({
|
|
434
|
+
agent_id: options.agent,
|
|
435
|
+
baseline_transcript: JSON.parse(fs.readFileSync(options.baseline, 'utf8')),
|
|
436
|
+
replay_transcript: JSON.parse(fs.readFileSync(options.replay, 'utf8')),
|
|
437
|
+
}));
|
|
438
|
+
}
|
|
439
|
+
catch (err) {
|
|
440
|
+
printError(err);
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
const prod = eval_.command('prod-calls').description('Ingested production voice calls, and turning them into regression tests');
|
|
445
|
+
prod.command('list')
|
|
446
|
+
.requiredOption('--agent <id>', 'Agent ID')
|
|
447
|
+
.option('--search <text>', 'Only calls whose transcript contains this')
|
|
448
|
+
.option('--limit <n>', 'At most this many', '50')
|
|
449
|
+
.action(async (options) => {
|
|
450
|
+
try {
|
|
451
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
452
|
+
await sdk.assertAgentInOrg(options.agent);
|
|
453
|
+
printJson(await sdk.prodCalls(options.agent, { search: options.search, limit: Number(options.limit) }));
|
|
454
|
+
}
|
|
455
|
+
catch (err) {
|
|
456
|
+
printError(err);
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
prod.command('promote')
|
|
461
|
+
.argument('<session-id>', 'Call session id')
|
|
462
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
463
|
+
.description('Add the call as a test item to the agent\'s "Regressions (review)" test set')
|
|
464
|
+
.action(async (sessionId, options) => {
|
|
465
|
+
try {
|
|
466
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
467
|
+
const call = await sdk.prodCall(sessionId);
|
|
468
|
+
if (call.bot_id)
|
|
469
|
+
await sdk.assertAgentInOrg(String(call.bot_id));
|
|
470
|
+
await confirmOrExit(`Add call ${sessionId} to the "Regressions (review)" test set?`, Boolean(options.yes));
|
|
471
|
+
printJson(await sdk.promoteProdCall(sessionId));
|
|
472
|
+
}
|
|
473
|
+
catch (err) {
|
|
474
|
+
printError(err);
|
|
475
|
+
process.exit(1);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
const datasets = eval_.command('datasets').description('Low-level datasets the evaluation trigger creates');
|
|
479
|
+
datasets.command('list').requiredOption('--agent <id>', 'Agent ID').action(async (options) => {
|
|
480
|
+
try {
|
|
481
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
482
|
+
await sdk.assertAgentInOrg(options.agent);
|
|
483
|
+
printJson(await sdk.listDatasets(options.agent));
|
|
484
|
+
}
|
|
485
|
+
catch (err) {
|
|
486
|
+
printError(err);
|
|
487
|
+
process.exit(1);
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
datasets.command('cases').argument('<dataset-id>').description('Test cases of a dataset').action(async (id) => {
|
|
491
|
+
try {
|
|
492
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
493
|
+
const ds = await sdk.getDataset(id);
|
|
494
|
+
if (ds.agent_id)
|
|
495
|
+
await sdk.assertAgentInOrg(String(ds.agent_id));
|
|
496
|
+
printJson(await sdk.listTestCases(id));
|
|
497
|
+
}
|
|
498
|
+
catch (err) {
|
|
499
|
+
printError(err);
|
|
500
|
+
process.exit(1);
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
const evaluators = eval_.command('evaluators').description('Evaluator types and the stored evaluator configs of an agent');
|
|
504
|
+
evaluators.command('types').action(async () => {
|
|
505
|
+
try {
|
|
506
|
+
printJson(await new EvalSDK(new IndemnClient()).evaluatorTypes());
|
|
507
|
+
}
|
|
508
|
+
catch (err) {
|
|
509
|
+
printError(err);
|
|
510
|
+
process.exit(1);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
evaluators.command('list').requiredOption('--agent <id>', 'Agent ID').action(async (options) => {
|
|
514
|
+
try {
|
|
515
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
516
|
+
await sdk.assertAgentInOrg(options.agent);
|
|
517
|
+
printJson(await sdk.listEvaluatorConfigs(options.agent));
|
|
518
|
+
}
|
|
519
|
+
catch (err) {
|
|
520
|
+
printError(err);
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
evaluators.command('delete').argument('<config-id>').option('--yes', 'Do not ask for confirmation').action(async (id, options) => {
|
|
525
|
+
try {
|
|
526
|
+
const sdk = new EvalSDK(new IndemnClient());
|
|
527
|
+
const cfg = await sdk.getEvaluatorConfig(id);
|
|
528
|
+
if (cfg.agent_id)
|
|
529
|
+
await sdk.assertAgentInOrg(String(cfg.agent_id));
|
|
530
|
+
await confirmOrExit(`Delete evaluator config ${id}?`, Boolean(options.yes));
|
|
531
|
+
printJson(await sdk.deleteEvaluatorConfig(id));
|
|
532
|
+
}
|
|
533
|
+
catch (err) {
|
|
534
|
+
printError(err);
|
|
535
|
+
process.exit(1);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
279
538
|
// Rubric commands — registered on program, not nested under eval
|
|
280
539
|
const rubric = program.command('rubric').description('Manage evaluation rubrics');
|
|
281
540
|
rubric
|
|
282
541
|
.command('list')
|
|
283
|
-
.description('List rubrics')
|
|
284
|
-
.
|
|
542
|
+
.description('List rubrics of an agent')
|
|
543
|
+
.requiredOption('--agent-id <id>', 'Agent ID (rubrics of other orgs are never listed)')
|
|
285
544
|
.option('--json', 'Output as JSON')
|
|
286
545
|
.option('--limit <n>', 'Limit number of results')
|
|
287
546
|
.option('--page <n>', 'Page number (use with --limit)')
|
|
@@ -290,6 +549,7 @@ export function registerEvalCommands(program) {
|
|
|
290
549
|
const client = new IndemnClient();
|
|
291
550
|
const sdk = new RubricsSDK(client);
|
|
292
551
|
const spinner = ora('Fetching rubrics...').start();
|
|
552
|
+
await new EvalSDK(client).assertAgentInOrg(options.agentId);
|
|
293
553
|
let rubrics = await sdk.listRubrics(options.agentId);
|
|
294
554
|
spinner.stop();
|
|
295
555
|
if (options.page && options.limit) {
|
|
@@ -457,13 +717,10 @@ export function registerEvalCommands(program) {
|
|
|
457
717
|
.command('delete')
|
|
458
718
|
.argument('<id>', 'Rubric ID')
|
|
459
719
|
.description('Delete a rubric')
|
|
460
|
-
.
|
|
720
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
721
|
+
.action(async (id, options) => {
|
|
461
722
|
try {
|
|
462
|
-
|
|
463
|
-
if (!confirmed) {
|
|
464
|
-
console.log('Cancelled.');
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
723
|
+
await confirmOrExit(`Delete rubric ${id}? This cannot be undone.`, Boolean(options.yes));
|
|
467
724
|
const client = new IndemnClient();
|
|
468
725
|
const sdk = new RubricsSDK(client);
|
|
469
726
|
const spinner = ora('Deleting rubric...').start();
|
|
@@ -512,8 +769,8 @@ export function registerEvalCommands(program) {
|
|
|
512
769
|
const testset = program.command('testset').description('Manage evaluation test sets');
|
|
513
770
|
testset
|
|
514
771
|
.command('list')
|
|
515
|
-
.description('List test sets')
|
|
516
|
-
.
|
|
772
|
+
.description('List test sets of an agent')
|
|
773
|
+
.requiredOption('--agent-id <id>', 'Agent ID (test sets of other orgs are never listed)')
|
|
517
774
|
.option('--json', 'Output as JSON')
|
|
518
775
|
.option('--limit <n>', 'Limit number of results')
|
|
519
776
|
.option('--page <n>', 'Page number (use with --limit)')
|
|
@@ -522,18 +779,8 @@ export function registerEvalCommands(program) {
|
|
|
522
779
|
const client = new IndemnClient();
|
|
523
780
|
const sdk = new TestSetsSDK(client);
|
|
524
781
|
const spinner = ora('Fetching test sets...').start();
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
sets = await sdk.listTestSets(options.agentId);
|
|
528
|
-
}
|
|
529
|
-
catch (err) {
|
|
530
|
-
spinner.stop();
|
|
531
|
-
if (!options.agentId && err?.message?.includes('500')) {
|
|
532
|
-
printError('The server returned an error listing all test sets. Try filtering by agent: indemn testset list --agent-id <id>');
|
|
533
|
-
process.exit(1);
|
|
534
|
-
}
|
|
535
|
-
throw err;
|
|
536
|
-
}
|
|
782
|
+
await new EvalSDK(client).assertAgentInOrg(options.agentId);
|
|
783
|
+
let sets = await sdk.listTestSets(options.agentId);
|
|
537
784
|
spinner.stop();
|
|
538
785
|
if (options.page && options.limit) {
|
|
539
786
|
const page = parseInt(options.page, 10);
|
|
@@ -700,13 +947,10 @@ export function registerEvalCommands(program) {
|
|
|
700
947
|
.command('delete')
|
|
701
948
|
.argument('<id>', 'Test set ID')
|
|
702
949
|
.description('Delete a test set')
|
|
703
|
-
.
|
|
950
|
+
.option('--yes', 'Do not ask for confirmation')
|
|
951
|
+
.action(async (id, options) => {
|
|
704
952
|
try {
|
|
705
|
-
|
|
706
|
-
if (!confirmed) {
|
|
707
|
-
console.log('Cancelled.');
|
|
708
|
-
return;
|
|
709
|
-
}
|
|
953
|
+
await confirmOrExit(`Delete test set ${id}? This cannot be undone.`, Boolean(options.yes));
|
|
710
954
|
const client = new IndemnClient();
|
|
711
955
|
const sdk = new TestSetsSDK(client);
|
|
712
956
|
const spinner = ora('Deleting test set...').start();
|