@chanl/eval-cli 0.4.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 (98) hide show
  1. package/LICENSE +21 -0
  2. package/dist/agent-loader.d.ts +39 -0
  3. package/dist/agent-loader.d.ts.map +1 -0
  4. package/dist/agent-loader.js +166 -0
  5. package/dist/agent-loader.js.map +1 -0
  6. package/dist/analytics.d.ts +15 -0
  7. package/dist/analytics.d.ts.map +1 -0
  8. package/dist/analytics.js +94 -0
  9. package/dist/analytics.js.map +1 -0
  10. package/dist/assertions.d.ts +73 -0
  11. package/dist/assertions.d.ts.map +1 -0
  12. package/dist/assertions.js +282 -0
  13. package/dist/assertions.js.map +1 -0
  14. package/dist/baseline.d.ts +100 -0
  15. package/dist/baseline.d.ts.map +1 -0
  16. package/dist/baseline.js +327 -0
  17. package/dist/baseline.js.map +1 -0
  18. package/dist/bin/chanl.d.ts +3 -0
  19. package/dist/bin/chanl.d.ts.map +1 -0
  20. package/dist/bin/chanl.js +11 -0
  21. package/dist/bin/chanl.js.map +1 -0
  22. package/dist/client.d.ts +40 -0
  23. package/dist/client.d.ts.map +1 -0
  24. package/dist/client.js +99 -0
  25. package/dist/client.js.map +1 -0
  26. package/dist/commands/analytics.d.ts +3 -0
  27. package/dist/commands/analytics.d.ts.map +1 -0
  28. package/dist/commands/analytics.js +44 -0
  29. package/dist/commands/analytics.js.map +1 -0
  30. package/dist/commands/compare.d.ts +51 -0
  31. package/dist/commands/compare.d.ts.map +1 -0
  32. package/dist/commands/compare.js +429 -0
  33. package/dist/commands/compare.js.map +1 -0
  34. package/dist/commands/config.d.ts +3 -0
  35. package/dist/commands/config.d.ts.map +1 -0
  36. package/dist/commands/config.js +94 -0
  37. package/dist/commands/config.js.map +1 -0
  38. package/dist/commands/dataset.d.ts +6 -0
  39. package/dist/commands/dataset.d.ts.map +1 -0
  40. package/dist/commands/dataset.js +225 -0
  41. package/dist/commands/dataset.js.map +1 -0
  42. package/dist/commands/executions.d.ts +3 -0
  43. package/dist/commands/executions.d.ts.map +1 -0
  44. package/dist/commands/executions.js +249 -0
  45. package/dist/commands/executions.js.map +1 -0
  46. package/dist/commands/generate.d.ts +3 -0
  47. package/dist/commands/generate.d.ts.map +1 -0
  48. package/dist/commands/generate.js +159 -0
  49. package/dist/commands/generate.js.map +1 -0
  50. package/dist/commands/init.d.ts +29 -0
  51. package/dist/commands/init.d.ts.map +1 -0
  52. package/dist/commands/init.js +545 -0
  53. package/dist/commands/init.js.map +1 -0
  54. package/dist/commands/login.d.ts +3 -0
  55. package/dist/commands/login.d.ts.map +1 -0
  56. package/dist/commands/login.js +65 -0
  57. package/dist/commands/login.js.map +1 -0
  58. package/dist/commands/personas.d.ts +3 -0
  59. package/dist/commands/personas.d.ts.map +1 -0
  60. package/dist/commands/personas.js +269 -0
  61. package/dist/commands/personas.js.map +1 -0
  62. package/dist/commands/scenarios.d.ts +16 -0
  63. package/dist/commands/scenarios.d.ts.map +1 -0
  64. package/dist/commands/scenarios.js +755 -0
  65. package/dist/commands/scenarios.js.map +1 -0
  66. package/dist/commands/scorecards.d.ts +3 -0
  67. package/dist/commands/scorecards.d.ts.map +1 -0
  68. package/dist/commands/scorecards.js +220 -0
  69. package/dist/commands/scorecards.js.map +1 -0
  70. package/dist/commands/server.d.ts +8 -0
  71. package/dist/commands/server.d.ts.map +1 -0
  72. package/dist/commands/server.js +357 -0
  73. package/dist/commands/server.js.map +1 -0
  74. package/dist/commands/test.d.ts +3 -0
  75. package/dist/commands/test.d.ts.map +1 -0
  76. package/dist/commands/test.js +410 -0
  77. package/dist/commands/test.js.map +1 -0
  78. package/dist/commands/tool-fixtures.d.ts +3 -0
  79. package/dist/commands/tool-fixtures.d.ts.map +1 -0
  80. package/dist/commands/tool-fixtures.js +324 -0
  81. package/dist/commands/tool-fixtures.js.map +1 -0
  82. package/dist/config.d.ts +32 -0
  83. package/dist/config.d.ts.map +1 -0
  84. package/dist/config.js +132 -0
  85. package/dist/config.js.map +1 -0
  86. package/dist/index.d.ts +8 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +146 -0
  89. package/dist/index.js.map +1 -0
  90. package/dist/output.d.ts +30 -0
  91. package/dist/output.d.ts.map +1 -0
  92. package/dist/output.js +77 -0
  93. package/dist/output.js.map +1 -0
  94. package/dist/update-check.d.ts +6 -0
  95. package/dist/update-check.d.ts.map +1 -0
  96. package/dist/update-check.js +50 -0
  97. package/dist/update-check.js.map +1 -0
  98. package/package.json +42 -0
@@ -0,0 +1,755 @@
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.runScenarioAction = runScenarioAction;
40
+ exports.registerScenariosCommand = registerScenariosCommand;
41
+ const fs = __importStar(require("fs"));
42
+ const path = __importStar(require("path"));
43
+ const yaml = __importStar(require("js-yaml"));
44
+ const chalk_1 = __importDefault(require("chalk"));
45
+ const ora_1 = __importDefault(require("ora"));
46
+ const client_1 = require("../client");
47
+ const output_1 = require("../output");
48
+ /**
49
+ * Shared run action — used by both `chanl scenarios run` and `chanl run`.
50
+ */
51
+ async function runScenarioAction(idOrNameOrFile, options, format) {
52
+ // Handle --all: run all active scenarios
53
+ if (options.all || !idOrNameOrFile) {
54
+ const result = await (0, client_1.get)('/scenarios', { status: 'active', limit: '100' });
55
+ const items = result.scenarios || [];
56
+ if (items.length === 0) {
57
+ (0, output_1.printError)('No active scenarios found. Create one with: chanl scenarios create');
58
+ process.exit(1);
59
+ }
60
+ console.log(`Running ${items.length} scenario(s)...\n`);
61
+ for (const scenario of items) {
62
+ const sid = scenario.id || scenario._id;
63
+ console.log(chalk_1.default.bold(`→ ${scenario.name}`));
64
+ try {
65
+ await executeAndPoll(sid, options, format);
66
+ }
67
+ catch (err) {
68
+ (0, output_1.printError)(` ${(0, client_1.formatError)(err)}`);
69
+ }
70
+ console.log('');
71
+ }
72
+ return;
73
+ }
74
+ let scenarioId = idOrNameOrFile;
75
+ // Check if the argument is a YAML file path
76
+ if (idOrNameOrFile.endsWith('.yaml') || idOrNameOrFile.endsWith('.yml')) {
77
+ const filePath = path.resolve(idOrNameOrFile);
78
+ if (!fs.existsSync(filePath)) {
79
+ (0, output_1.printError)(`File not found: ${filePath}`);
80
+ process.exit(1);
81
+ }
82
+ const spinner = (0, ora_1.default)('Importing scenario from YAML...').start();
83
+ try {
84
+ const yamlContent = fs.readFileSync(filePath, 'utf-8');
85
+ yaml.load(yamlContent);
86
+ const importResult = await (0, client_1.post)('/scenarios/import/yaml', {
87
+ yaml: yamlContent,
88
+ });
89
+ const imported = importResult.scenario;
90
+ scenarioId = imported.id || imported._id;
91
+ spinner.succeed(`Imported scenario: ${imported.name} (${scenarioId})`);
92
+ }
93
+ catch (importErr) {
94
+ spinner.fail('Failed to import YAML');
95
+ (0, output_1.printError)((0, client_1.formatError)(importErr));
96
+ process.exit(1);
97
+ }
98
+ }
99
+ else if (!isObjectId(idOrNameOrFile)) {
100
+ // Try to find scenario by name (slug match)
101
+ const result = await (0, client_1.get)('/scenarios', { limit: '200' });
102
+ const items = result.scenarios || [];
103
+ const match = items.find((s) => slugify(s.name) === slugify(idOrNameOrFile) ||
104
+ s.name.toLowerCase() === idOrNameOrFile.toLowerCase());
105
+ if (match) {
106
+ scenarioId = match.id || match._id;
107
+ }
108
+ else {
109
+ (0, output_1.printError)(`Scenario "${idOrNameOrFile}" not found. Run "chanl scenarios list" to see available scenarios.`);
110
+ process.exit(1);
111
+ }
112
+ }
113
+ await executeAndPoll(scenarioId, options, format);
114
+ }
115
+ async function executeAndPoll(scenarioId, options, format) {
116
+ const executeDto = {};
117
+ // promptId is required — the Prompt entity + server Settings are the only config source
118
+ if (!options.promptId) {
119
+ const msg = 'Missing --prompt-id. The server resolves adapter config from the Prompt entity.\n' +
120
+ 'Create a prompt in the dashboard or via the API, then pass its ID:\n' +
121
+ ' chanl scenarios run <scenario> --prompt-id <id>';
122
+ (0, output_1.printError)(msg);
123
+ process.exitCode = 1;
124
+ throw new Error(msg);
125
+ }
126
+ executeDto.promptId = options.promptId;
127
+ if (options.personaId)
128
+ executeDto.personaId = options.personaId;
129
+ if (options.scorecardId)
130
+ executeDto.scorecardId = options.scorecardId;
131
+ if (options.mode)
132
+ executeDto.mode = options.mode;
133
+ if (options.dryRun)
134
+ executeDto.dryRun = true;
135
+ if (options.tools) {
136
+ executeDto.toolFixtureIds = options.tools
137
+ .split(',')
138
+ .map((id) => id.trim())
139
+ .filter(Boolean);
140
+ }
141
+ console.log(chalk_1.default.dim(`Using prompt: ${options.promptId}`));
142
+ const spinner = (0, ora_1.default)('Starting scenario execution...').start();
143
+ const execResult = await (0, client_1.post)(`/scenarios/${scenarioId}/execute`, executeDto);
144
+ const execution = execResult.execution;
145
+ const executionId = execution.executionId || execution.id || execution._id;
146
+ spinner.succeed(`Execution started: ${executionId}`);
147
+ if (options.wait !== false) {
148
+ await pollExecution(executionId, format);
149
+ }
150
+ else {
151
+ console.log(`\nCheck status with: chanl scenarios results ${executionId}`);
152
+ }
153
+ }
154
+ function registerScenariosCommand(program) {
155
+ const scenarios = program
156
+ .command('scenarios')
157
+ .description('Manage evaluation scenarios');
158
+ // --- list ---
159
+ scenarios
160
+ .command('list')
161
+ .description('List all scenarios')
162
+ .option('--status <status>', 'Filter by status')
163
+ .option('--category <category>', 'Filter by category')
164
+ .option('--page <page>', 'Page number', '1')
165
+ .option('--limit <limit>', 'Results per page', '20')
166
+ .action(async (options) => {
167
+ try {
168
+ const params = {
169
+ page: options.page,
170
+ limit: options.limit,
171
+ };
172
+ if (options.status)
173
+ params.status = options.status;
174
+ if (options.category)
175
+ params.category = options.category;
176
+ const result = await (0, client_1.get)('/scenarios', params);
177
+ const items = result.scenarios || [];
178
+ const format = program.opts().format;
179
+ const headers = [
180
+ 'ID',
181
+ 'Name',
182
+ 'Category',
183
+ 'Difficulty',
184
+ 'Status',
185
+ 'Personas',
186
+ ];
187
+ const rows = items.map((s) => [
188
+ s.id || s._id || '',
189
+ (0, output_1.truncate)(s.name || '', 30),
190
+ s.category || '',
191
+ s.difficulty || '',
192
+ s.status || '',
193
+ String((s.personaIds || []).length),
194
+ ]);
195
+ (0, output_1.printOutput)(format, headers, rows, result);
196
+ if (format !== 'json' && result.pagination) {
197
+ const p = result.pagination;
198
+ console.log(chalk_1.default.dim(`\nShowing page ${p.page}/${p.totalPages} (${result.total} total)`));
199
+ }
200
+ }
201
+ catch (err) {
202
+ (0, output_1.printError)((0, client_1.formatError)(err));
203
+ process.exit(1);
204
+ }
205
+ });
206
+ // --- run ---
207
+ scenarios
208
+ .command('run [idOrFile]')
209
+ .description('Execute a scenario by ID, name, or from a YAML file')
210
+ .requiredOption('--prompt-id <promptId>', 'Prompt entity ID (defines the agent under test)')
211
+ .option('--persona-id <personaId>', 'Override persona ID')
212
+ .option('--scorecard-id <scorecardId>', 'Override scorecard ID')
213
+ .option('--tools <ids>', 'Comma-separated tool fixture IDs to attach')
214
+ .option('--mode <mode>', 'Execution mode: text or phone', 'text')
215
+ .option('--dry-run', 'Dry run without actually executing')
216
+ .option('--no-wait', 'Do not wait for completion')
217
+ .option('--all', 'Run all active scenarios')
218
+ .action(async (idOrFile, options) => {
219
+ try {
220
+ await runScenarioAction(idOrFile, options, program.opts().format);
221
+ }
222
+ catch (err) {
223
+ (0, output_1.printError)((0, client_1.formatError)(err));
224
+ process.exitCode = 1;
225
+ throw err;
226
+ }
227
+ });
228
+ // --- create ---
229
+ scenarios
230
+ .command('create')
231
+ .description('Create a new scenario')
232
+ .requiredOption('--name <name>', 'Scenario name')
233
+ .requiredOption('--prompt <prompt>', 'Opening prompt for the persona')
234
+ .option('--description <description>', 'Description')
235
+ .option('--category <category>', 'Category (support, sales, booking, technical, onboarding, feedback)', 'support')
236
+ .option('--difficulty <difficulty>', 'Difficulty (easy, medium, hard)', 'medium')
237
+ .option('--persona-ids <ids>', 'Comma-separated persona IDs')
238
+ .option('--scorecard-id <id>', 'Scorecard ID')
239
+ .option('--tags <tags>', 'Comma-separated tags')
240
+ .option('--status <status>', 'Status (draft, active)', 'draft')
241
+ .action(async (options) => {
242
+ try {
243
+ const dto = {
244
+ name: options.name,
245
+ prompt: options.prompt,
246
+ category: options.category,
247
+ difficulty: options.difficulty,
248
+ status: options.status,
249
+ personaIds: options.personaIds
250
+ ? options.personaIds.split(',').map((s) => s.trim())
251
+ : [],
252
+ };
253
+ if (options.description)
254
+ dto.description = options.description;
255
+ if (options.scorecardId)
256
+ dto.scorecardId = options.scorecardId;
257
+ if (options.tags)
258
+ dto.tags = options.tags.split(',').map((t) => t.trim());
259
+ const result = await (0, client_1.post)('/scenarios', dto);
260
+ const scenario = result.scenario || result;
261
+ const format = program.opts().format;
262
+ if (format === 'json') {
263
+ console.log(JSON.stringify(result, null, 2));
264
+ }
265
+ else {
266
+ (0, output_1.printSuccess)(`Created scenario: ${scenario.name} (${scenario.id || scenario._id})`);
267
+ }
268
+ }
269
+ catch (err) {
270
+ (0, output_1.printError)((0, client_1.formatError)(err));
271
+ process.exit(1);
272
+ }
273
+ });
274
+ // --- get ---
275
+ scenarios
276
+ .command('get <id>')
277
+ .description('Get scenario details')
278
+ .action(async (id) => {
279
+ try {
280
+ const result = await (0, client_1.get)(`/scenarios/${id}`);
281
+ const format = program.opts().format;
282
+ if (format === 'json') {
283
+ console.log(JSON.stringify(result, null, 2));
284
+ }
285
+ else {
286
+ const s = result.scenario;
287
+ console.log(`Name: ${s.name}`);
288
+ console.log(`ID: ${s.id || s._id}`);
289
+ console.log(`Category: ${s.category || '-'}`);
290
+ console.log(`Difficulty: ${s.difficulty || '-'}`);
291
+ console.log(`Status: ${s.status || '-'}`);
292
+ console.log(`Personas: ${(s.personaIds || []).length}`);
293
+ if (s.description) {
294
+ console.log(`Description: ${s.description}`);
295
+ }
296
+ if (s.prompt) {
297
+ console.log(`Prompt: ${(0, output_1.truncate)(s.prompt, 80)}`);
298
+ }
299
+ if (s.tags && s.tags.length > 0) {
300
+ console.log(`Tags: ${s.tags.join(', ')}`);
301
+ }
302
+ }
303
+ }
304
+ catch (err) {
305
+ (0, output_1.printError)((0, client_1.formatError)(err));
306
+ process.exit(1);
307
+ }
308
+ });
309
+ // --- update ---
310
+ scenarios
311
+ .command('update <id>')
312
+ .description('Update a scenario')
313
+ .option('--name <name>', 'New name')
314
+ .option('--description <description>', 'New description')
315
+ .option('--prompt <prompt>', 'New opening prompt')
316
+ .option('--category <category>', 'New category')
317
+ .option('--difficulty <difficulty>', 'New difficulty')
318
+ .option('--status <status>', 'New status (draft, active)')
319
+ .option('--persona-ids <ids>', 'Comma-separated persona IDs')
320
+ .option('--scorecard-id <id>', 'New scorecard ID')
321
+ .option('--tags <tags>', 'Comma-separated tags')
322
+ .action(async (id, options) => {
323
+ try {
324
+ const dto = {};
325
+ if (options.name)
326
+ dto.name = options.name;
327
+ if (options.description)
328
+ dto.description = options.description;
329
+ if (options.prompt)
330
+ dto.prompt = options.prompt;
331
+ if (options.category)
332
+ dto.category = options.category;
333
+ if (options.difficulty)
334
+ dto.difficulty = options.difficulty;
335
+ if (options.status)
336
+ dto.status = options.status;
337
+ if (options.scorecardId)
338
+ dto.scorecardId = options.scorecardId;
339
+ if (options.personaIds) {
340
+ dto.personaIds = options.personaIds.split(',').map((s) => s.trim());
341
+ }
342
+ if (options.tags) {
343
+ dto.tags = options.tags.split(',').map((t) => t.trim());
344
+ }
345
+ const result = await (0, client_1.patch)(`/scenarios/${id}`, dto);
346
+ const scenario = result.scenario || result;
347
+ const format = program.opts().format;
348
+ if (format === 'json') {
349
+ console.log(JSON.stringify(result, null, 2));
350
+ }
351
+ else {
352
+ (0, output_1.printSuccess)(`Updated scenario: ${scenario.name} (${scenario.id || scenario._id})`);
353
+ }
354
+ }
355
+ catch (err) {
356
+ (0, output_1.printError)((0, client_1.formatError)(err));
357
+ process.exit(1);
358
+ }
359
+ });
360
+ // --- delete ---
361
+ scenarios
362
+ .command('delete <id>')
363
+ .description('Delete a scenario')
364
+ .option('--force', 'Skip confirmation prompt')
365
+ .action(async (id, options) => {
366
+ try {
367
+ if (!options.force) {
368
+ const inquirer = await Promise.resolve().then(() => __importStar(require('inquirer')));
369
+ const answers = await inquirer.default.prompt([
370
+ {
371
+ type: 'confirm',
372
+ name: 'confirm',
373
+ message: `Delete scenario ${id}?`,
374
+ default: false,
375
+ },
376
+ ]);
377
+ if (!answers.confirm) {
378
+ console.log('Cancelled.');
379
+ return;
380
+ }
381
+ }
382
+ await (0, client_1.del)(`/scenarios/${id}`);
383
+ (0, output_1.printSuccess)(`Deleted scenario: ${id}`);
384
+ }
385
+ catch (err) {
386
+ (0, output_1.printError)((0, client_1.formatError)(err));
387
+ process.exit(1);
388
+ }
389
+ });
390
+ // --- import ---
391
+ scenarios
392
+ .command('import <dirOrFile>')
393
+ .description('Import scenarios from a YAML file or directory')
394
+ .action(async (dirOrFile) => {
395
+ try {
396
+ const resolvedPath = path.resolve(dirOrFile);
397
+ if (!fs.existsSync(resolvedPath)) {
398
+ (0, output_1.printError)(`Path not found: ${resolvedPath}`);
399
+ process.exit(1);
400
+ }
401
+ const stat = fs.statSync(resolvedPath);
402
+ let files;
403
+ if (stat.isDirectory()) {
404
+ files = fs
405
+ .readdirSync(resolvedPath)
406
+ .filter((f) => f.endsWith('.yaml') || f.endsWith('.yml'))
407
+ .map((f) => path.join(resolvedPath, f));
408
+ }
409
+ else {
410
+ files = [resolvedPath];
411
+ }
412
+ if (files.length === 0) {
413
+ (0, output_1.printError)('No YAML files found');
414
+ process.exit(1);
415
+ }
416
+ console.log(`Importing ${files.length} scenario file(s)...\n`);
417
+ let successCount = 0;
418
+ let failCount = 0;
419
+ for (const file of files) {
420
+ const basename = path.basename(file);
421
+ const spinner = (0, ora_1.default)(`Importing ${basename}...`).start();
422
+ try {
423
+ const yamlContent = fs.readFileSync(file, 'utf-8');
424
+ yaml.load(yamlContent);
425
+ const result = await (0, client_1.post)('/scenarios/import/yaml', {
426
+ yaml: yamlContent,
427
+ });
428
+ const scenario = result.scenario;
429
+ spinner.succeed(`${basename} -> ${scenario.name} (${scenario.id || scenario._id})`);
430
+ successCount++;
431
+ }
432
+ catch (err) {
433
+ spinner.fail(`${basename}: ${(0, client_1.formatError)(err)}`);
434
+ failCount++;
435
+ }
436
+ }
437
+ console.log(`\nImported: ${chalk_1.default.green(String(successCount))} | Failed: ${chalk_1.default.red(String(failCount))}`);
438
+ }
439
+ catch (err) {
440
+ (0, output_1.printError)((0, client_1.formatError)(err));
441
+ process.exit(1);
442
+ }
443
+ });
444
+ // --- results ---
445
+ scenarios
446
+ .command('results <executionId>')
447
+ .description('View execution results')
448
+ .action(async (executionId) => {
449
+ try {
450
+ const result = await (0, client_1.get)(`/scenarios/executions/${executionId}`);
451
+ const execution = result.execution;
452
+ const format = program.opts().format;
453
+ if (format === 'json') {
454
+ console.log(JSON.stringify(result, null, 2));
455
+ return;
456
+ }
457
+ printExecutionDetails(execution);
458
+ }
459
+ catch (err) {
460
+ (0, output_1.printError)((0, client_1.formatError)(err));
461
+ process.exit(1);
462
+ }
463
+ });
464
+ }
465
+ /**
466
+ * Poll an execution until it reaches a terminal state.
467
+ */
468
+ async function pollExecution(executionId, format) {
469
+ const TERMINAL_STATES = ['completed', 'failed', 'timeout', 'cancelled'];
470
+ const POLL_INTERVAL = 2000;
471
+ const MAX_POLLS = 150;
472
+ const spinner = (0, ora_1.default)('Waiting for execution to complete...').start();
473
+ for (let i = 0; i < MAX_POLLS; i++) {
474
+ await sleep(POLL_INTERVAL);
475
+ try {
476
+ const result = await (0, client_1.get)(`/scenarios/executions/${executionId}`);
477
+ const execution = result.execution;
478
+ spinner.text = `Status: ${execution.status}`;
479
+ if (execution.metrics) {
480
+ const pct = Math.round(((execution.metrics.completedSteps || 0) /
481
+ (execution.metrics.totalSteps || 1)) *
482
+ 100);
483
+ spinner.text = `Status: ${execution.status} (${pct}% steps done)`;
484
+ }
485
+ if (TERMINAL_STATES.includes(execution.status)) {
486
+ if (execution.status === 'completed') {
487
+ spinner.succeed('Execution completed');
488
+ }
489
+ else if (execution.status === 'failed') {
490
+ spinner.fail('Execution failed');
491
+ }
492
+ else {
493
+ spinner.warn(`Execution ${execution.status}`);
494
+ }
495
+ console.log('');
496
+ if (format === 'json') {
497
+ console.log(JSON.stringify(result, null, 2));
498
+ }
499
+ else {
500
+ printExecutionDetails(execution);
501
+ }
502
+ return;
503
+ }
504
+ }
505
+ catch (err) {
506
+ spinner.text = `Polling... (${(0, client_1.formatError)(err)})`;
507
+ }
508
+ }
509
+ spinner.warn('Polling timeout reached');
510
+ console.log(`Check status with: chanl scenarios results ${executionId}`);
511
+ }
512
+ function printExecutionDetails(execution) {
513
+ const statusColor = execution.status === 'completed'
514
+ ? chalk_1.default.green
515
+ : execution.status === 'failed'
516
+ ? chalk_1.default.red
517
+ : chalk_1.default.yellow;
518
+ console.log(chalk_1.default.bold('Execution Results'));
519
+ console.log(chalk_1.default.dim('\u2500'.repeat(60)));
520
+ console.log(` ID: ${execution.executionId || execution.id || ''}`);
521
+ console.log(` Status: ${statusColor(execution.status)}`);
522
+ if (execution.duration) {
523
+ console.log(` Duration: ${(execution.duration / 1000).toFixed(1)}s`);
524
+ }
525
+ if (execution.startTime) {
526
+ console.log(` Started: ${new Date(execution.startTime).toLocaleString()}`);
527
+ }
528
+ // -- Transcript --------------------------------------------------------
529
+ if (execution.stepResults && execution.stepResults.length > 0) {
530
+ console.log('');
531
+ console.log(chalk_1.default.bold(' Transcript'));
532
+ console.log(chalk_1.default.dim(' ' + '\u2500'.repeat(52)));
533
+ for (const step of execution.stepResults) {
534
+ const text = step.actualResponse;
535
+ if (!text)
536
+ continue;
537
+ const isAgent = typeof step.stepId === 'string' && step.stepId.includes('agent');
538
+ const label = isAgent
539
+ ? chalk_1.default.cyan.bold('Agent')
540
+ : chalk_1.default.magenta.bold('Persona');
541
+ const latencyTag = isAgent && step.duration
542
+ ? chalk_1.default.dim(` [${step.duration}ms]`)
543
+ : '';
544
+ console.log(` ${label}${latencyTag}`);
545
+ // Wrap text at ~68 chars, indented
546
+ const lines = wordWrap(text, 68);
547
+ for (const line of lines) {
548
+ console.log(` ${line}`);
549
+ }
550
+ console.log('');
551
+ }
552
+ }
553
+ // -- Score with visual bar ---------------------------------------------
554
+ if (execution.overallScore !== undefined &&
555
+ execution.overallScore !== null) {
556
+ console.log(chalk_1.default.bold(' Score'));
557
+ console.log(chalk_1.default.dim(' ' + '\u2500'.repeat(52)));
558
+ const score = Math.round(execution.overallScore);
559
+ const scoreColor = score >= 80
560
+ ? chalk_1.default.green
561
+ : score >= 50
562
+ ? chalk_1.default.yellow
563
+ : chalk_1.default.red;
564
+ const bar = buildScoreBar(score, 20);
565
+ console.log(` ${scoreColor(`${score}/100`)} ${bar}`);
566
+ console.log('');
567
+ }
568
+ // -- Scorecard criteria results ----------------------------------------
569
+ const criteriaResults = extractCriteriaResults(execution);
570
+ if (criteriaResults.length > 0) {
571
+ console.log(chalk_1.default.bold(' Scorecard'));
572
+ console.log(chalk_1.default.dim(' ' + '\u2500'.repeat(52)));
573
+ for (const cr of criteriaResults) {
574
+ const icon = cr.passed ? chalk_1.default.green('\u2713') : chalk_1.default.red('\u2717');
575
+ const nameStr = cr.name || cr.key || 'criterion';
576
+ const typeStr = cr.type ? chalk_1.default.dim(` [${cr.type}]`) : '';
577
+ const scoreStr = cr.score !== undefined && cr.score !== null
578
+ ? chalk_1.default.dim(` (${cr.score})`)
579
+ : '';
580
+ const reasonStr = cr.reasoning
581
+ ? chalk_1.default.dim(` ${(0, output_1.truncate)(cr.reasoning, 60)}`)
582
+ : '';
583
+ console.log(` ${icon} ${nameStr}${typeStr}${scoreStr}`);
584
+ if (reasonStr) {
585
+ console.log(` ${reasonStr}`);
586
+ }
587
+ }
588
+ console.log('');
589
+ }
590
+ // -- Latency stats -----------------------------------------------------
591
+ const latencyStats = computeLatencyStats(execution);
592
+ if (latencyStats) {
593
+ console.log(chalk_1.default.bold(' Latency'));
594
+ console.log(chalk_1.default.dim(' ' + '\u2500'.repeat(52)));
595
+ console.log(` Avg response: ${chalk_1.default.cyan(latencyStats.avg + 'ms')}`);
596
+ console.log(` Slowest turn: ${chalk_1.default.yellow(latencyStats.max + 'ms')}${latencyStats.slowestTurn ? chalk_1.default.dim(` (turn ${latencyStats.slowestTurn})`) : ''}`);
597
+ if (latencyStats.min !== latencyStats.max) {
598
+ console.log(` Fastest turn: ${chalk_1.default.green(latencyStats.min + 'ms')}`);
599
+ }
600
+ console.log(` Total turns: ${latencyStats.count}`);
601
+ console.log('');
602
+ }
603
+ // -- Metrics -----------------------------------------------------------
604
+ if (execution.metrics) {
605
+ const m = execution.metrics;
606
+ const hasMetrics = m.totalSteps || m.accuracy !== undefined || m.completion !== undefined;
607
+ if (hasMetrics) {
608
+ console.log(chalk_1.default.bold(' Metrics'));
609
+ console.log(chalk_1.default.dim(' ' + '\u2500'.repeat(52)));
610
+ if (m.totalSteps) {
611
+ console.log(` Steps: ${m.completedSteps || 0}/${m.totalSteps} completed, ${m.failedSteps || 0} failed`);
612
+ }
613
+ if (m.accuracy !== undefined) {
614
+ console.log(` Accuracy: ${m.accuracy}%`);
615
+ }
616
+ if (m.completion !== undefined) {
617
+ console.log(` Completion: ${m.completion}%`);
618
+ }
619
+ console.log('');
620
+ }
621
+ }
622
+ // -- Errors ------------------------------------------------------------
623
+ if (execution.errorMessages && execution.errorMessages.length > 0) {
624
+ console.log(chalk_1.default.bold.red(' Errors'));
625
+ console.log(chalk_1.default.dim(' ' + '\u2500'.repeat(52)));
626
+ for (const msg of execution.errorMessages) {
627
+ console.log(` ${chalk_1.default.red('\u2717')} ${msg}`);
628
+ }
629
+ console.log('');
630
+ }
631
+ // -- Cloud teaser (subtle, one line) -----------------------------------
632
+ console.log(chalk_1.default.dim(' \u{1F4A1} chanl cloud \u2192 dashboard, voice testing, trends \u2192 chanl.ai'));
633
+ }
634
+ /**
635
+ * Build a visual score bar using block characters.
636
+ * Filled blocks for the score portion, light shade for the remainder.
637
+ */
638
+ function buildScoreBar(score, width) {
639
+ const clamped = Math.max(0, Math.min(100, score));
640
+ const filled = Math.round((clamped / 100) * width);
641
+ const empty = width - filled;
642
+ const filledChar = '\u2588'; // full block
643
+ const emptyChar = '\u2591'; // light shade
644
+ const bar = filledChar.repeat(filled) + emptyChar.repeat(empty);
645
+ if (score >= 80)
646
+ return chalk_1.default.green(bar);
647
+ if (score >= 50)
648
+ return chalk_1.default.yellow(bar);
649
+ return chalk_1.default.red(bar);
650
+ }
651
+ /**
652
+ * Compute latency statistics from agent response times in stepResults.
653
+ */
654
+ function computeLatencyStats(execution) {
655
+ if (!execution.stepResults || execution.stepResults.length === 0) {
656
+ return null;
657
+ }
658
+ const agentSteps = execution.stepResults.filter((s) => typeof s.stepId === 'string' &&
659
+ s.stepId.includes('agent') &&
660
+ s.duration > 0);
661
+ if (agentSteps.length === 0)
662
+ return null;
663
+ let total = 0;
664
+ let max = 0;
665
+ let min = Infinity;
666
+ let slowestTurn = null;
667
+ for (const step of agentSteps) {
668
+ const d = step.duration;
669
+ total += d;
670
+ if (d > max) {
671
+ max = d;
672
+ // Extract turn number from stepId like "turn-2-agent"
673
+ const match = step.stepId.match(/turn-(\d+)/);
674
+ slowestTurn = match ? parseInt(match[1], 10) + 1 : null;
675
+ }
676
+ if (d < min) {
677
+ min = d;
678
+ }
679
+ }
680
+ return {
681
+ avg: Math.round(total / agentSteps.length),
682
+ max,
683
+ min: min === Infinity ? 0 : min,
684
+ count: agentSteps.length,
685
+ slowestTurn,
686
+ };
687
+ }
688
+ /**
689
+ * Extract scorecard criteria results from execution data.
690
+ * Criteria results may be present at the top level (if the API enriches the response)
691
+ * or nested in execution metadata.
692
+ */
693
+ function extractCriteriaResults(execution) {
694
+ // Top-level criteriaResults (if API returns enriched data)
695
+ if (execution.criteriaResults && Array.isArray(execution.criteriaResults)) {
696
+ return execution.criteriaResults.map((cr) => ({
697
+ name: cr.criteriaName || cr.criteriaKey || '',
698
+ key: cr.criteriaKey || '',
699
+ type: cr.type,
700
+ score: typeof cr.result === 'number' ? cr.result : undefined,
701
+ passed: !!cr.passed,
702
+ reasoning: cr.reasoning,
703
+ }));
704
+ }
705
+ // Nested in metadata (if execution processor stored them)
706
+ if (execution.metadata?.scorecardResult?.criteriaResults) {
707
+ return execution.metadata.scorecardResult.criteriaResults.map((cr) => ({
708
+ name: cr.criteriaName || cr.criteriaKey || '',
709
+ key: cr.criteriaKey || '',
710
+ type: cr.type,
711
+ score: typeof cr.result === 'number' ? cr.result : undefined,
712
+ passed: !!cr.passed,
713
+ reasoning: cr.reasoning,
714
+ }));
715
+ }
716
+ return [];
717
+ }
718
+ /**
719
+ * Simple word wrap for transcript display.
720
+ */
721
+ function wordWrap(text, maxWidth) {
722
+ if (!text)
723
+ return [''];
724
+ const words = text.split(/\s+/);
725
+ const lines = [];
726
+ let currentLine = '';
727
+ for (const word of words) {
728
+ if (currentLine.length + word.length + 1 > maxWidth &&
729
+ currentLine.length > 0) {
730
+ lines.push(currentLine);
731
+ currentLine = word;
732
+ }
733
+ else {
734
+ currentLine = currentLine ? currentLine + ' ' + word : word;
735
+ }
736
+ }
737
+ if (currentLine)
738
+ lines.push(currentLine);
739
+ return lines.length > 0 ? lines : [''];
740
+ }
741
+ /** Convert a name to a URL-friendly slug. */
742
+ function slugify(name) {
743
+ return name
744
+ .toLowerCase()
745
+ .replace(/[^a-z0-9]+/g, '-')
746
+ .replace(/^-+|-+$/g, '');
747
+ }
748
+ /** Check if a string looks like a MongoDB ObjectId. */
749
+ function isObjectId(str) {
750
+ return /^[a-f0-9]{24}$/.test(str);
751
+ }
752
+ function sleep(ms) {
753
+ return new Promise((resolve) => setTimeout(resolve, ms));
754
+ }
755
+ //# sourceMappingURL=scenarios.js.map