@principal-ai/principal-view-cli 0.3.0 → 0.3.2
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/package.json +2 -2
- package/dist/commands/coverage.d.ts +0 -9
- package/dist/commands/coverage.d.ts.map +0 -1
- package/dist/commands/coverage.js +0 -158
- package/dist/commands/create.d.ts +0 -6
- package/dist/commands/create.d.ts.map +0 -1
- package/dist/commands/create.js +0 -50
- package/dist/commands/doctor.d.ts +0 -10
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -274
- package/dist/commands/formats.d.ts +0 -6
- package/dist/commands/formats.d.ts.map +0 -1
- package/dist/commands/formats.js +0 -475
- package/dist/commands/hooks.d.ts +0 -9
- package/dist/commands/hooks.d.ts.map +0 -1
- package/dist/commands/hooks.js +0 -295
- package/dist/commands/init.d.ts +0 -6
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -271
- package/dist/commands/lint.d.ts +0 -6
- package/dist/commands/lint.d.ts.map +0 -1
- package/dist/commands/lint.js +0 -506
- package/dist/commands/list.d.ts +0 -6
- package/dist/commands/list.d.ts.map +0 -1
- package/dist/commands/list.js +0 -80
- package/dist/commands/narrative/eval.d.ts +0 -3
- package/dist/commands/narrative/eval.d.ts.map +0 -1
- package/dist/commands/narrative/eval.js +0 -76
- package/dist/commands/narrative/index.d.ts +0 -3
- package/dist/commands/narrative/index.d.ts.map +0 -1
- package/dist/commands/narrative/index.js +0 -19
- package/dist/commands/narrative/inspect.d.ts +0 -3
- package/dist/commands/narrative/inspect.d.ts.map +0 -1
- package/dist/commands/narrative/inspect.js +0 -109
- package/dist/commands/narrative/list.d.ts +0 -3
- package/dist/commands/narrative/list.d.ts.map +0 -1
- package/dist/commands/narrative/list.js +0 -101
- package/dist/commands/narrative/render.d.ts +0 -3
- package/dist/commands/narrative/render.d.ts.map +0 -1
- package/dist/commands/narrative/render.js +0 -99
- package/dist/commands/narrative/test.d.ts +0 -3
- package/dist/commands/narrative/test.d.ts.map +0 -1
- package/dist/commands/narrative/test.js +0 -150
- package/dist/commands/narrative/utils.d.ts +0 -69
- package/dist/commands/narrative/utils.d.ts.map +0 -1
- package/dist/commands/narrative/utils.js +0 -158
- package/dist/commands/narrative/validate.d.ts +0 -3
- package/dist/commands/narrative/validate.d.ts.map +0 -1
- package/dist/commands/narrative/validate.js +0 -149
- package/dist/commands/schema.d.ts +0 -6
- package/dist/commands/schema.d.ts.map +0 -1
- package/dist/commands/schema.js +0 -336
- package/dist/commands/validate-execution.d.ts +0 -11
- package/dist/commands/validate-execution.d.ts.map +0 -1
- package/dist/commands/validate-execution.js +0 -223
- package/dist/commands/validate.d.ts +0 -6
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/validate.js +0 -1065
- package/dist/index.cjs +0 -243779
- package/dist/index.cjs.map +0 -7
- package/dist/index.d.ts +0 -8
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -45
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { createValidateCommand } from './validate.js';
|
|
3
|
-
import { createInspectCommand } from './inspect.js';
|
|
4
|
-
import { createRenderCommand } from './render.js';
|
|
5
|
-
import { createTestCommand } from './test.js';
|
|
6
|
-
import { createEvalCommand } from './eval.js';
|
|
7
|
-
import { createListCommand } from './list.js';
|
|
8
|
-
export function createNarrativeCommand() {
|
|
9
|
-
const command = new Command('narrative');
|
|
10
|
-
command
|
|
11
|
-
.description('Validate, test, and debug narrative templates')
|
|
12
|
-
.addCommand(createValidateCommand())
|
|
13
|
-
.addCommand(createInspectCommand())
|
|
14
|
-
.addCommand(createRenderCommand())
|
|
15
|
-
.addCommand(createTestCommand())
|
|
16
|
-
.addCommand(createEvalCommand())
|
|
17
|
-
.addCommand(createListCommand());
|
|
18
|
-
return command;
|
|
19
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/inspect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,wBAAgB,oBAAoB,IAAI,OAAO,CAiI9C"}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { computeAggregates } from '@principal-ai/principal-view-core';
|
|
4
|
-
import { loadExecution, executionToEvents, resolvePath, formatTimestamp, formatDuration, groupAttributesByPrefix, filterAttributes, countEventsByType, formatValue, capitalize, } from './utils.js';
|
|
5
|
-
export function createInspectCommand() {
|
|
6
|
-
const command = new Command('inspect');
|
|
7
|
-
command
|
|
8
|
-
.description('Inspect execution file and show available attributes for templates')
|
|
9
|
-
.argument('<execution>', 'Path to .otel.json execution file')
|
|
10
|
-
.option('--events', 'Show all events')
|
|
11
|
-
.option('--aggregates', 'Show computed aggregates (default)', true)
|
|
12
|
-
.option('--json', 'Output as JSON')
|
|
13
|
-
.option('--filter <pattern>', 'Filter attributes by pattern (e.g., auth.*)')
|
|
14
|
-
.action(async (execution, options) => {
|
|
15
|
-
try {
|
|
16
|
-
const executionPath = resolvePath(execution);
|
|
17
|
-
// Load execution
|
|
18
|
-
const executionData = await loadExecution(executionPath);
|
|
19
|
-
const events = executionToEvents(executionData);
|
|
20
|
-
// Compute aggregates
|
|
21
|
-
const aggregates = computeAggregates(events);
|
|
22
|
-
// Apply filter if provided
|
|
23
|
-
const filteredAggregates = options.filter
|
|
24
|
-
? filterAttributes(aggregates, options.filter)
|
|
25
|
-
: aggregates;
|
|
26
|
-
// Count events by type
|
|
27
|
-
const eventCounts = countEventsByType(events);
|
|
28
|
-
// Calculate time range
|
|
29
|
-
const timestamps = events.map((e) => typeof e.timestamp === 'string' ? parseInt(e.timestamp, 10) : e.timestamp);
|
|
30
|
-
const minTime = Math.min(...timestamps);
|
|
31
|
-
const maxTime = Math.max(...timestamps);
|
|
32
|
-
const duration = maxTime - minTime;
|
|
33
|
-
if (options.json) {
|
|
34
|
-
const output = {
|
|
35
|
-
file: execution,
|
|
36
|
-
summary: {
|
|
37
|
-
eventCount: events.length,
|
|
38
|
-
spanCount: executionData.spans.length,
|
|
39
|
-
timeRange: {
|
|
40
|
-
start: minTime,
|
|
41
|
-
end: maxTime,
|
|
42
|
-
duration: duration,
|
|
43
|
-
},
|
|
44
|
-
status: executionData.metadata?.status || 'UNKNOWN',
|
|
45
|
-
},
|
|
46
|
-
eventTypes: Array.from(eventCounts.entries()).map(([name, count]) => ({
|
|
47
|
-
name,
|
|
48
|
-
count,
|
|
49
|
-
})),
|
|
50
|
-
attributes: filteredAggregates,
|
|
51
|
-
};
|
|
52
|
-
if (options.events) {
|
|
53
|
-
output.events = events.map((e) => ({
|
|
54
|
-
name: e.name,
|
|
55
|
-
timestamp: e.timestamp,
|
|
56
|
-
attributes: e.attributes,
|
|
57
|
-
}));
|
|
58
|
-
}
|
|
59
|
-
console.log(JSON.stringify(output, null, 2));
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
// Text output
|
|
63
|
-
console.log(chalk.bold(`\nInspecting: ${execution}\n`));
|
|
64
|
-
// Execution Summary
|
|
65
|
-
console.log(chalk.bold('Execution Summary:'));
|
|
66
|
-
console.log('━'.repeat(60));
|
|
67
|
-
console.log(chalk.gray(' • Total Events:'), events.length);
|
|
68
|
-
console.log(chalk.gray(' • Total Spans:'), executionData.spans.length);
|
|
69
|
-
console.log(chalk.gray(' • Time Range:'), `${formatTimestamp(minTime)} → ${formatTimestamp(maxTime)} (${formatDuration(duration)})`);
|
|
70
|
-
const status = executionData.metadata?.status || 'UNKNOWN';
|
|
71
|
-
const statusColor = status === 'OK' ? chalk.green : chalk.red;
|
|
72
|
-
console.log(chalk.gray(' • Status:'), statusColor(status));
|
|
73
|
-
// Event Types
|
|
74
|
-
console.log(chalk.bold('\nEvent Types:'));
|
|
75
|
-
console.log('━'.repeat(60));
|
|
76
|
-
for (const [name, count] of eventCounts) {
|
|
77
|
-
console.log(chalk.gray(' •'), `${name} (${count})`);
|
|
78
|
-
}
|
|
79
|
-
// Available Attributes
|
|
80
|
-
console.log(chalk.bold('\nAvailable Attributes (for templates):'));
|
|
81
|
-
console.log('━'.repeat(60));
|
|
82
|
-
const grouped = groupAttributesByPrefix(filteredAggregates);
|
|
83
|
-
for (const [prefix, attrs] of Object.entries(grouped)) {
|
|
84
|
-
console.log(chalk.bold(`\n${capitalize(prefix)}:`));
|
|
85
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
86
|
-
console.log(chalk.gray(' •'), `${key}: ${formatValue(value)}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Show events if requested
|
|
90
|
-
if (options.events) {
|
|
91
|
-
console.log(chalk.bold('\nEvents:'));
|
|
92
|
-
console.log('━'.repeat(60));
|
|
93
|
-
for (const event of events) {
|
|
94
|
-
console.log(chalk.cyan(`\n[${formatTimestamp(typeof event.timestamp === 'string' ? parseInt(event.timestamp, 10) : event.timestamp)}]`), chalk.bold(event.name));
|
|
95
|
-
for (const [key, value] of Object.entries(event.attributes || {})) {
|
|
96
|
-
console.log(chalk.gray(' •'), `${key}: ${formatValue(value)}`);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
console.log();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
console.error(chalk.red('Error:'), error.message);
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
return command;
|
|
109
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,wBAAgB,iBAAiB,IAAI,OAAO,CA6G3C"}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { access } from 'node:fs/promises';
|
|
4
|
-
import { resolve, dirname, join } from 'node:path';
|
|
5
|
-
import { globby } from 'globby';
|
|
6
|
-
import { loadNarrative } from './utils.js';
|
|
7
|
-
export function createListCommand() {
|
|
8
|
-
const command = new Command('list');
|
|
9
|
-
command
|
|
10
|
-
.description('List all narrative files in project')
|
|
11
|
-
.argument('[dir]', 'Directory to search (default: .principal-views/)')
|
|
12
|
-
.option('--json', 'Output as JSON')
|
|
13
|
-
.option('--show-canvas', 'Show linked canvas files')
|
|
14
|
-
.action(async (dir, options) => {
|
|
15
|
-
try {
|
|
16
|
-
const searchDir = dir || '.principal-views';
|
|
17
|
-
const searchPath = resolve(process.cwd(), searchDir);
|
|
18
|
-
// Find all .narrative.json files
|
|
19
|
-
const files = await globby('**/*.narrative.json', {
|
|
20
|
-
cwd: searchPath,
|
|
21
|
-
ignore: ['node_modules/**', '.git/**', '__executions__/**'],
|
|
22
|
-
});
|
|
23
|
-
// Load each narrative and check canvas existence
|
|
24
|
-
const narratives = await Promise.all(files.map(async (file) => {
|
|
25
|
-
const fullPath = join(searchPath, file);
|
|
26
|
-
const narrative = await loadNarrative(fullPath);
|
|
27
|
-
let canvasExists;
|
|
28
|
-
if (options.showCanvas && narrative.canvas) {
|
|
29
|
-
const narrativeDir = dirname(fullPath);
|
|
30
|
-
const canvasPath = resolve(narrativeDir, narrative.canvas);
|
|
31
|
-
try {
|
|
32
|
-
await access(canvasPath);
|
|
33
|
-
canvasExists = true;
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
canvasExists = false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
const defaultCount = narrative.scenarios.filter((s) => s.condition.default).length;
|
|
40
|
-
return {
|
|
41
|
-
file: join(searchDir, file),
|
|
42
|
-
canvas: narrative.canvas,
|
|
43
|
-
canvasExists,
|
|
44
|
-
scenarioCount: narrative.scenarios.length,
|
|
45
|
-
defaultCount,
|
|
46
|
-
mode: narrative.mode,
|
|
47
|
-
name: narrative.name,
|
|
48
|
-
};
|
|
49
|
-
}));
|
|
50
|
-
if (options.json) {
|
|
51
|
-
const output = {
|
|
52
|
-
searchDir,
|
|
53
|
-
count: narratives.length,
|
|
54
|
-
narratives: narratives.map((n) => ({
|
|
55
|
-
file: n.file,
|
|
56
|
-
name: n.name,
|
|
57
|
-
canvas: n.canvas,
|
|
58
|
-
canvasExists: n.canvasExists,
|
|
59
|
-
scenarios: n.scenarioCount,
|
|
60
|
-
defaultScenarios: n.defaultCount,
|
|
61
|
-
mode: n.mode,
|
|
62
|
-
})),
|
|
63
|
-
};
|
|
64
|
-
console.log(JSON.stringify(output, null, 2));
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
console.log(chalk.bold('\nNarrative Templates:'));
|
|
68
|
-
console.log('━'.repeat(60));
|
|
69
|
-
if (narratives.length === 0) {
|
|
70
|
-
console.log(chalk.yellow(`\nNo narrative templates found in ${searchDir}`));
|
|
71
|
-
console.log();
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
for (const narrative of narratives) {
|
|
75
|
-
console.log(chalk.bold(`\n${narrative.file}`));
|
|
76
|
-
if (narrative.name) {
|
|
77
|
-
console.log(chalk.gray(` Name: ${narrative.name}`));
|
|
78
|
-
}
|
|
79
|
-
if (options.showCanvas && narrative.canvas) {
|
|
80
|
-
const status = narrative.canvasExists
|
|
81
|
-
? chalk.green('✓')
|
|
82
|
-
: chalk.red('✗');
|
|
83
|
-
console.log(chalk.gray(` Canvas: ${narrative.canvas} ${status}`));
|
|
84
|
-
}
|
|
85
|
-
else if (narrative.canvas) {
|
|
86
|
-
console.log(chalk.gray(` Canvas: ${narrative.canvas}`));
|
|
87
|
-
}
|
|
88
|
-
console.log(chalk.gray(` Scenarios: ${narrative.scenarioCount} (${narrative.defaultCount} default)`));
|
|
89
|
-
console.log(chalk.gray(` Mode: ${narrative.mode}`));
|
|
90
|
-
}
|
|
91
|
-
console.log(chalk.bold(`\nFound ${narratives.length} narrative template(s)`));
|
|
92
|
-
console.log();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
console.error(chalk.red('Error:'), error.message);
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
return command;
|
|
101
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC,wBAAgB,mBAAmB,IAAI,OAAO,CAmH7C"}
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { renderNarrative } from '@principal-ai/principal-view-core';
|
|
4
|
-
import { loadNarrative, loadExecution, executionToEvents, resolvePath, } from './utils.js';
|
|
5
|
-
export function createRenderCommand() {
|
|
6
|
-
const command = new Command('render');
|
|
7
|
-
command
|
|
8
|
-
.description('Render narrative template using execution data')
|
|
9
|
-
.argument('<narrative>', 'Path to .narrative.json file')
|
|
10
|
-
.argument('<execution>', 'Path to .otel.json execution file')
|
|
11
|
-
.option('--mode <mode>', 'Override rendering mode: span-tree, timeline')
|
|
12
|
-
.option('--scenario <id>', 'Force specific scenario (skip auto-selection)')
|
|
13
|
-
.option('--json', 'Output structured result as JSON')
|
|
14
|
-
.option('--format <format>', 'Output format: text (default), markdown, json', 'text')
|
|
15
|
-
.option('--show-metadata', 'Include rendering metadata in output')
|
|
16
|
-
.action(async (narrativePath, executionPath, options) => {
|
|
17
|
-
try {
|
|
18
|
-
const narrative = await loadNarrative(resolvePath(narrativePath));
|
|
19
|
-
const executionData = await loadExecution(resolvePath(executionPath));
|
|
20
|
-
const events = executionToEvents(executionData);
|
|
21
|
-
// Override mode if specified
|
|
22
|
-
if (options.mode) {
|
|
23
|
-
const validModes = ['span-tree', 'timeline'];
|
|
24
|
-
if (!validModes.includes(options.mode)) {
|
|
25
|
-
throw new Error(`Invalid mode: ${options.mode}. Must be one of: ${validModes.join(', ')}`);
|
|
26
|
-
}
|
|
27
|
-
narrative.mode = options.mode;
|
|
28
|
-
}
|
|
29
|
-
// Render narrative
|
|
30
|
-
const result = renderNarrative(narrative, events);
|
|
31
|
-
// Get the selected scenario from the result
|
|
32
|
-
const selectedScenario = narrative.scenarios.find((s) => s.id === result.scenarioId);
|
|
33
|
-
// Force scenario if specified
|
|
34
|
-
if (options.scenario) {
|
|
35
|
-
const scenario = narrative.scenarios.find((s) => s.id === options.scenario);
|
|
36
|
-
if (!scenario) {
|
|
37
|
-
throw new Error(`Scenario not found: ${options.scenario}`);
|
|
38
|
-
}
|
|
39
|
-
// Note: This would require a way to force scenario in renderNarrative API
|
|
40
|
-
// For now, we'll just validate the scenario exists
|
|
41
|
-
}
|
|
42
|
-
if (options.json) {
|
|
43
|
-
const output = {
|
|
44
|
-
narrative: narrativePath,
|
|
45
|
-
execution: executionPath,
|
|
46
|
-
mode: narrative.mode,
|
|
47
|
-
scenario: {
|
|
48
|
-
id: selectedScenario?.id,
|
|
49
|
-
priority: selectedScenario?.priority,
|
|
50
|
-
matched: true,
|
|
51
|
-
},
|
|
52
|
-
text: result.text,
|
|
53
|
-
};
|
|
54
|
-
if (options.showMetadata) {
|
|
55
|
-
output.metadata = result.metadata;
|
|
56
|
-
}
|
|
57
|
-
console.log(JSON.stringify(output, null, 2));
|
|
58
|
-
}
|
|
59
|
-
else {
|
|
60
|
-
// Text output
|
|
61
|
-
if (!options.format || options.format === 'text') {
|
|
62
|
-
console.log(chalk.gray(`Rendering: ${narrativePath}`));
|
|
63
|
-
console.log(chalk.gray(`Execution: ${executionPath}`));
|
|
64
|
-
console.log(chalk.gray(`Mode: ${narrative.mode}`));
|
|
65
|
-
if (selectedScenario) {
|
|
66
|
-
console.log(chalk.gray(`Scenario: ${selectedScenario.id} (priority: ${selectedScenario.priority})`));
|
|
67
|
-
}
|
|
68
|
-
console.log();
|
|
69
|
-
console.log('━'.repeat(60));
|
|
70
|
-
console.log();
|
|
71
|
-
console.log(result.text);
|
|
72
|
-
console.log();
|
|
73
|
-
console.log('━'.repeat(60));
|
|
74
|
-
if (options.showMetadata && result.metadata) {
|
|
75
|
-
console.log();
|
|
76
|
-
console.log(chalk.bold('Metadata:'));
|
|
77
|
-
console.log(chalk.gray(' • Event Count:'), result.metadata.eventCount);
|
|
78
|
-
console.log(chalk.gray(' • Span Count:'), result.metadata.spanCount);
|
|
79
|
-
if (result.metadata.timeRange) {
|
|
80
|
-
console.log(chalk.gray(' • Time Range:'), `${result.metadata.timeRange.start} → ${result.metadata.timeRange.end}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
console.log();
|
|
84
|
-
}
|
|
85
|
-
else if (options.format === 'markdown') {
|
|
86
|
-
console.log(result.text);
|
|
87
|
-
}
|
|
88
|
-
else if (options.format === 'json') {
|
|
89
|
-
console.log(JSON.stringify({ text: result.text }, null, 2));
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
catch (error) {
|
|
94
|
-
console.error(chalk.red('Error:'), error.message);
|
|
95
|
-
process.exit(1);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
return command;
|
|
99
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBpC,wBAAgB,iBAAiB,IAAI,OAAO,CAuL3C"}
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import chalk from 'chalk';
|
|
3
|
-
import { selectScenario, matchesCondition, computeAggregates, hasEventMatching, } from '@principal-ai/principal-view-core';
|
|
4
|
-
import { loadNarrative, loadExecution, executionToEvents, resolvePath, formatValue, } from './utils.js';
|
|
5
|
-
export function createTestCommand() {
|
|
6
|
-
const command = new Command('test');
|
|
7
|
-
command
|
|
8
|
-
.description('Test scenario matching and show why scenarios match or don\'t match')
|
|
9
|
-
.argument('<narrative>', 'Path to .narrative.json file')
|
|
10
|
-
.argument('<execution>', 'Path to .otel.json execution file')
|
|
11
|
-
.option('--show-all', 'Show all scenarios (not just matches)')
|
|
12
|
-
.option('--show-aggregates', 'Display computed aggregates')
|
|
13
|
-
.option('--json', 'Output as JSON')
|
|
14
|
-
.action(async (narrativePath, executionPath, options) => {
|
|
15
|
-
try {
|
|
16
|
-
const narrative = await loadNarrative(resolvePath(narrativePath));
|
|
17
|
-
const executionData = await loadExecution(resolvePath(executionPath));
|
|
18
|
-
const events = executionToEvents(executionData);
|
|
19
|
-
// Compute aggregates
|
|
20
|
-
const aggregates = computeAggregates(events);
|
|
21
|
-
// Test each scenario
|
|
22
|
-
const scenarioResults = narrative.scenarios.map((scenario) => {
|
|
23
|
-
const condition = scenario.condition;
|
|
24
|
-
const matched = matchesCondition(condition, events, aggregates);
|
|
25
|
-
let reason;
|
|
26
|
-
const requiresResults = [];
|
|
27
|
-
const excludesResults = [];
|
|
28
|
-
// Check requires
|
|
29
|
-
if (condition.requires) {
|
|
30
|
-
for (const pattern of condition.requires) {
|
|
31
|
-
const hasMatch = hasEventMatching(events, pattern);
|
|
32
|
-
const count = events.filter((e) => hasEventMatching([e], pattern)).length;
|
|
33
|
-
requiresResults.push({ pattern, matched: hasMatch, count });
|
|
34
|
-
if (!hasMatch && !reason) {
|
|
35
|
-
reason = `Missing required event '${pattern}'`;
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
// Check excludes
|
|
40
|
-
if (condition.excludes) {
|
|
41
|
-
for (const pattern of condition.excludes) {
|
|
42
|
-
const hasMatch = hasEventMatching(events, pattern);
|
|
43
|
-
const count = events.filter((e) => hasEventMatching([e], pattern)).length;
|
|
44
|
-
excludesResults.push({ pattern, matched: hasMatch, count });
|
|
45
|
-
if (hasMatch && !reason) {
|
|
46
|
-
reason = `Found excluded event '${pattern}'`;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
// Default scenario
|
|
51
|
-
if (condition.default) {
|
|
52
|
-
reason = 'Default scenario (always matches)';
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
scenario,
|
|
56
|
-
matched,
|
|
57
|
-
reason,
|
|
58
|
-
requiresResults,
|
|
59
|
-
excludesResults,
|
|
60
|
-
};
|
|
61
|
-
});
|
|
62
|
-
// Select the winning scenario
|
|
63
|
-
const matchResult = selectScenario(narrative, events, aggregates);
|
|
64
|
-
const selectedScenario = matchResult.scenario;
|
|
65
|
-
if (options.json) {
|
|
66
|
-
const output = {
|
|
67
|
-
narrative: narrativePath,
|
|
68
|
-
execution: executionPath,
|
|
69
|
-
scenarios: scenarioResults.map((r) => ({
|
|
70
|
-
id: r.scenario.id,
|
|
71
|
-
priority: r.scenario.priority,
|
|
72
|
-
matched: r.matched,
|
|
73
|
-
reason: r.reason,
|
|
74
|
-
requires: r.requiresResults,
|
|
75
|
-
excludes: r.excludesResults,
|
|
76
|
-
})),
|
|
77
|
-
selectedScenario: selectedScenario?.id,
|
|
78
|
-
aggregates: options.showAggregates ? aggregates : undefined,
|
|
79
|
-
};
|
|
80
|
-
console.log(JSON.stringify(output, null, 2));
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
// Text output
|
|
84
|
-
console.log(chalk.bold(`\nTesting: ${narrativePath}`));
|
|
85
|
-
console.log(chalk.gray(`Execution: ${executionPath}\n`));
|
|
86
|
-
console.log(chalk.bold('Scenario Matching Results:'));
|
|
87
|
-
console.log('━'.repeat(60));
|
|
88
|
-
// Sort by priority (lower = higher priority)
|
|
89
|
-
const sorted = [...scenarioResults].sort((a, b) => a.scenario.priority - b.scenario.priority);
|
|
90
|
-
for (const result of sorted) {
|
|
91
|
-
if (!options.showAll && !result.matched) {
|
|
92
|
-
continue;
|
|
93
|
-
}
|
|
94
|
-
const icon = result.matched ? chalk.green('✓') : chalk.red('✗');
|
|
95
|
-
const status = result.matched
|
|
96
|
-
? chalk.green('MATCHED')
|
|
97
|
-
: chalk.gray('NOT MATCHED');
|
|
98
|
-
console.log(`\n${icon} ${chalk.bold(result.scenario.id)} (priority: ${result.scenario.priority}) - ${status}`);
|
|
99
|
-
// Show requires
|
|
100
|
-
if (result.requiresResults.length > 0) {
|
|
101
|
-
console.log(chalk.gray(' Requires:'));
|
|
102
|
-
for (const req of result.requiresResults) {
|
|
103
|
-
const reqIcon = req.matched ? chalk.green('✓') : chalk.red('✗');
|
|
104
|
-
const countMsg = req.matched ? `Found ${req.count} event(s)` : 'No matching events';
|
|
105
|
-
console.log(` ${reqIcon} ${req.pattern} - ${countMsg}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// Show excludes
|
|
109
|
-
if (result.excludesResults.length > 0) {
|
|
110
|
-
console.log(chalk.gray(' Excludes:'));
|
|
111
|
-
for (const exc of result.excludesResults) {
|
|
112
|
-
const excIcon = exc.matched ? chalk.red('✗') : chalk.green('✓');
|
|
113
|
-
const countMsg = exc.matched ? `Found ${exc.count} event(s)` : 'No matching events';
|
|
114
|
-
console.log(` ${excIcon} ${exc.pattern} - ${countMsg}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (result.reason && !result.matched) {
|
|
118
|
-
console.log(chalk.gray(` Reason: ${result.reason}`));
|
|
119
|
-
}
|
|
120
|
-
if (result.scenario.condition.default) {
|
|
121
|
-
console.log(chalk.gray(' Default scenario (always matches)'));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
console.log('\n' + '━'.repeat(60));
|
|
125
|
-
if (selectedScenario) {
|
|
126
|
-
console.log(chalk.bold('\nSelected Scenario:'), chalk.cyan(`${selectedScenario.id} (priority: ${selectedScenario.priority})`));
|
|
127
|
-
}
|
|
128
|
-
else {
|
|
129
|
-
console.log(chalk.yellow('\nNo scenario selected'));
|
|
130
|
-
}
|
|
131
|
-
if (options.showAggregates) {
|
|
132
|
-
console.log(chalk.bold('\nComputed Aggregates:'));
|
|
133
|
-
for (const [key, value] of Object.entries(aggregates)) {
|
|
134
|
-
console.log(chalk.gray(' •'), `${key}: ${formatValue(value)}`);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
console.log();
|
|
138
|
-
}
|
|
139
|
-
// Exit with error if no scenario matched
|
|
140
|
-
if (!selectedScenario) {
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
console.error(chalk.red('Error:'), error.message);
|
|
146
|
-
process.exit(1);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
149
|
-
return command;
|
|
150
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import type { NarrativeTemplate, OtelEvent } from '@principal-ai/principal-view-core';
|
|
2
|
-
export interface ExecutionData {
|
|
3
|
-
metadata?: {
|
|
4
|
-
status?: string;
|
|
5
|
-
testName?: string;
|
|
6
|
-
sessionId?: string;
|
|
7
|
-
startTime?: number;
|
|
8
|
-
endTime?: number;
|
|
9
|
-
};
|
|
10
|
-
spans: Array<{
|
|
11
|
-
id: string;
|
|
12
|
-
name: string;
|
|
13
|
-
startTime?: number;
|
|
14
|
-
endTime?: number;
|
|
15
|
-
duration?: number;
|
|
16
|
-
status?: 'OK' | 'ERROR';
|
|
17
|
-
attributes?: Record<string, unknown>;
|
|
18
|
-
events: Array<{
|
|
19
|
-
time: number;
|
|
20
|
-
name: string;
|
|
21
|
-
attributes: Record<string, unknown>;
|
|
22
|
-
}>;
|
|
23
|
-
}>;
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Load a narrative template from a file
|
|
27
|
-
*/
|
|
28
|
-
export declare function loadNarrative(filePath: string): Promise<NarrativeTemplate>;
|
|
29
|
-
/**
|
|
30
|
-
* Load execution data from a .otel.json file
|
|
31
|
-
*/
|
|
32
|
-
export declare function loadExecution(filePath: string): Promise<ExecutionData>;
|
|
33
|
-
/**
|
|
34
|
-
* Convert execution data to OtelEvent array format expected by narrative APIs
|
|
35
|
-
*/
|
|
36
|
-
export declare function executionToEvents(execution: ExecutionData): OtelEvent[];
|
|
37
|
-
/**
|
|
38
|
-
* Resolve a file path relative to a base directory
|
|
39
|
-
*/
|
|
40
|
-
export declare function resolvePath(filePath: string, baseDir?: string): string;
|
|
41
|
-
/**
|
|
42
|
-
* Format a timestamp as human-readable date/time
|
|
43
|
-
*/
|
|
44
|
-
export declare function formatTimestamp(timestamp: number): string;
|
|
45
|
-
/**
|
|
46
|
-
* Format duration in milliseconds
|
|
47
|
-
*/
|
|
48
|
-
export declare function formatDuration(ms: number): string;
|
|
49
|
-
/**
|
|
50
|
-
* Group attributes by prefix (e.g., 'auth.method' -> 'auth')
|
|
51
|
-
*/
|
|
52
|
-
export declare function groupAttributesByPrefix(attributes: Record<string, unknown>): Record<string, Record<string, unknown>>;
|
|
53
|
-
/**
|
|
54
|
-
* Filter attributes by pattern (supports glob-like patterns)
|
|
55
|
-
*/
|
|
56
|
-
export declare function filterAttributes(attributes: Record<string, unknown>, pattern: string): Record<string, unknown>;
|
|
57
|
-
/**
|
|
58
|
-
* Count events by type/name
|
|
59
|
-
*/
|
|
60
|
-
export declare function countEventsByType(events: OtelEvent[]): Map<string, number>;
|
|
61
|
-
/**
|
|
62
|
-
* Format attribute value for display
|
|
63
|
-
*/
|
|
64
|
-
export declare function formatValue(value: unknown): string;
|
|
65
|
-
/**
|
|
66
|
-
* Capitalize first letter of a string
|
|
67
|
-
*/
|
|
68
|
-
export declare function capitalize(str: string): string;
|
|
69
|
-
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAEtF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE;QACT,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,KAAK,EAAE,KAAK,CAAC;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,MAAM,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;QACxB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,MAAM,EAAE,KAAK,CAAC;YACZ,IAAI,EAAE,MAAM,CAAC;YACb,IAAI,EAAE,MAAM,CAAC;YACb,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;SACrC,CAAC,CAAC;KACJ,CAAC,CAAC;CACJ;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAUhF;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAU5E;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,aAAa,GAAG,SAAS,EAAE,CAmBvE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAKtE;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAWzD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAoBzC;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnC,OAAO,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAazB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAS1E;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAclD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C"}
|