@principal-ai/principal-view-cli 0.1.29 → 0.1.31
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/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +160 -34
- package/dist/commands/narrative/eval.d.ts +3 -0
- package/dist/commands/narrative/eval.d.ts.map +1 -0
- package/dist/commands/narrative/eval.js +76 -0
- package/dist/commands/narrative/index.d.ts +3 -0
- package/dist/commands/narrative/index.d.ts.map +1 -0
- package/dist/commands/narrative/index.js +19 -0
- package/dist/commands/narrative/inspect.d.ts +3 -0
- package/dist/commands/narrative/inspect.d.ts.map +1 -0
- package/dist/commands/narrative/inspect.js +109 -0
- package/dist/commands/narrative/list.d.ts +3 -0
- package/dist/commands/narrative/list.d.ts.map +1 -0
- package/dist/commands/narrative/list.js +101 -0
- package/dist/commands/narrative/render.d.ts +3 -0
- package/dist/commands/narrative/render.d.ts.map +1 -0
- package/dist/commands/narrative/render.js +99 -0
- package/dist/commands/narrative/test.d.ts +3 -0
- package/dist/commands/narrative/test.d.ts.map +1 -0
- package/dist/commands/narrative/test.js +150 -0
- package/dist/commands/narrative/utils.d.ts +69 -0
- package/dist/commands/narrative/utils.d.ts.map +1 -0
- package/dist/commands/narrative/utils.js +158 -0
- package/dist/commands/narrative/validate.d.ts +3 -0
- package/dist/commands/narrative/validate.d.ts.map +1 -0
- package/dist/commands/narrative/validate.js +135 -0
- package/dist/commands/validate-execution.d.ts +12 -0
- package/dist/commands/validate-execution.d.ts.map +1 -0
- package/dist/commands/validate-execution.js +230 -0
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/commands/validate.js +0 -4
- package/dist/index.js +4 -0
- package/package.json +3 -2
- package/dist/index.cjs +0 -24272
- package/dist/index.cjs.map +0 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/commands/lint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsUpC,wBAAgB,iBAAiB,IAAI,OAAO,CA+P3C"}
|
package/dist/commands/lint.js
CHANGED
|
@@ -7,7 +7,7 @@ import { resolve, relative, dirname, basename } from 'node:path';
|
|
|
7
7
|
import chalk from 'chalk';
|
|
8
8
|
import { globby } from 'globby';
|
|
9
9
|
import yaml from 'js-yaml';
|
|
10
|
-
import { createDefaultRulesEngine, validatePrivuConfig, mergeConfigs, getDefaultConfig, } from '@principal-ai/principal-view-core';
|
|
10
|
+
import { createDefaultRulesEngine, validatePrivuConfig, mergeConfigs, getDefaultConfig, createNarrativeValidator, } from '@principal-ai/principal-view-core';
|
|
11
11
|
// ============================================================================
|
|
12
12
|
// Config File Loading
|
|
13
13
|
// ============================================================================
|
|
@@ -116,6 +116,56 @@ function loadGraphConfig(filePath) {
|
|
|
116
116
|
return null;
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Load a narrative template file
|
|
121
|
+
*/
|
|
122
|
+
function loadNarrativeTemplate(filePath) {
|
|
123
|
+
if (!existsSync(filePath)) {
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
128
|
+
const narrative = JSON.parse(raw);
|
|
129
|
+
return { narrative, raw };
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Load a canvas file for narrative validation
|
|
137
|
+
*/
|
|
138
|
+
function loadCanvas(filePath) {
|
|
139
|
+
if (!existsSync(filePath)) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const content = readFileSync(filePath, 'utf8');
|
|
144
|
+
const ext = filePath.toLowerCase();
|
|
145
|
+
if (ext.endsWith('.json')) {
|
|
146
|
+
return JSON.parse(content);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
return yaml.load(content);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Determine file type
|
|
158
|
+
*/
|
|
159
|
+
function getFileType(filePath) {
|
|
160
|
+
const name = basename(filePath).toLowerCase();
|
|
161
|
+
if (name.endsWith('.narrative.json')) {
|
|
162
|
+
return 'narrative';
|
|
163
|
+
}
|
|
164
|
+
if (name.endsWith('.canvas') || name.endsWith('.otel.canvas')) {
|
|
165
|
+
return 'canvas';
|
|
166
|
+
}
|
|
167
|
+
return 'config';
|
|
168
|
+
}
|
|
119
169
|
// ============================================================================
|
|
120
170
|
// Output Formatting
|
|
121
171
|
// ============================================================================
|
|
@@ -205,6 +255,19 @@ function formatJsonOutput(results) {
|
|
|
205
255
|
};
|
|
206
256
|
}
|
|
207
257
|
// ============================================================================
|
|
258
|
+
// Helper Functions
|
|
259
|
+
// ============================================================================
|
|
260
|
+
/**
|
|
261
|
+
* Count violations by rule ID
|
|
262
|
+
*/
|
|
263
|
+
function countByRule(violations) {
|
|
264
|
+
const counts = {};
|
|
265
|
+
for (const v of violations) {
|
|
266
|
+
counts[v.ruleId] = (counts[v.ruleId] || 0) + 1;
|
|
267
|
+
}
|
|
268
|
+
return counts;
|
|
269
|
+
}
|
|
270
|
+
// ============================================================================
|
|
208
271
|
// Command Implementation
|
|
209
272
|
// ============================================================================
|
|
210
273
|
export function createLintCommand() {
|
|
@@ -282,15 +345,14 @@ export function createLintCommand() {
|
|
|
282
345
|
ignore: privuConfig.exclude || ['**/node_modules/**'],
|
|
283
346
|
expandDirectories: false,
|
|
284
347
|
});
|
|
285
|
-
// Filter out library files, config files,
|
|
348
|
+
// Filter out library files, config files, and execution artifacts
|
|
349
|
+
// INCLUDE both canvas files and narrative templates for linting
|
|
286
350
|
const configFiles = matchedFiles.filter((f) => {
|
|
287
351
|
const name = basename(f).toLowerCase();
|
|
288
352
|
const isLibraryFile = name.startsWith('library.');
|
|
289
353
|
const isConfigFile = name.startsWith('.privurc');
|
|
290
|
-
const isCanvasFile = f.toLowerCase().endsWith('.canvas');
|
|
291
|
-
const isNarrativeTemplate = name.endsWith('.narrative.json');
|
|
292
354
|
const isExecutionArtifact = f.includes('__executions__/');
|
|
293
|
-
return !isLibraryFile && !isConfigFile && !
|
|
355
|
+
return !isLibraryFile && !isConfigFile && !isExecutionArtifact;
|
|
294
356
|
});
|
|
295
357
|
if (configFiles.length === 0) {
|
|
296
358
|
if (options.json) {
|
|
@@ -315,44 +377,108 @@ export function createLintCommand() {
|
|
|
315
377
|
console.log(chalk.yellow(`Warning: Could not load library from ${libraryPath}`));
|
|
316
378
|
}
|
|
317
379
|
}
|
|
318
|
-
// Create
|
|
380
|
+
// Create validators
|
|
319
381
|
const engine = createDefaultRulesEngine();
|
|
382
|
+
const narrativeValidator = createNarrativeValidator();
|
|
320
383
|
// Lint each file
|
|
321
384
|
const results = new Map();
|
|
322
385
|
for (const filePath of configFiles) {
|
|
323
386
|
const absolutePath = resolve(cwd, filePath);
|
|
324
387
|
const relativePath = relative(cwd, absolutePath);
|
|
325
|
-
const
|
|
326
|
-
if (
|
|
327
|
-
//
|
|
388
|
+
const fileType = getFileType(absolutePath);
|
|
389
|
+
if (fileType === 'narrative') {
|
|
390
|
+
// Validate narrative template
|
|
391
|
+
const loaded = loadNarrativeTemplate(absolutePath);
|
|
392
|
+
if (!loaded) {
|
|
393
|
+
// File couldn't be loaded - report as error
|
|
394
|
+
results.set(relativePath, {
|
|
395
|
+
violations: [
|
|
396
|
+
{
|
|
397
|
+
ruleId: 'parse-error',
|
|
398
|
+
severity: 'error',
|
|
399
|
+
file: relativePath,
|
|
400
|
+
message: `Could not parse narrative file: ${filePath}`,
|
|
401
|
+
impact: 'File cannot be validated',
|
|
402
|
+
fixable: false,
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
errorCount: 1,
|
|
406
|
+
warningCount: 0,
|
|
407
|
+
fixableCount: 0,
|
|
408
|
+
byCategory: { schema: 1, reference: 0, structure: 0, pattern: 0, library: 0 },
|
|
409
|
+
byRule: { 'parse-error': 1 },
|
|
410
|
+
});
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
// Load the referenced canvas if it exists
|
|
414
|
+
const canvasPath = loaded.narrative.canvas
|
|
415
|
+
? resolve(dirname(absolutePath), loaded.narrative.canvas)
|
|
416
|
+
: undefined;
|
|
417
|
+
const canvas = canvasPath ? loadCanvas(canvasPath) : undefined;
|
|
418
|
+
// Run narrative validation
|
|
419
|
+
const narrativeResult = await narrativeValidator.validate({
|
|
420
|
+
narrative: loaded.narrative,
|
|
421
|
+
narrativePath: relativePath,
|
|
422
|
+
canvas: canvas ?? undefined,
|
|
423
|
+
canvasPath: canvasPath ? relative(cwd, canvasPath) : undefined,
|
|
424
|
+
basePath: dirname(absolutePath),
|
|
425
|
+
rawContent: loaded.raw,
|
|
426
|
+
});
|
|
427
|
+
// Convert narrative violations to graph violations format
|
|
428
|
+
const violations = narrativeResult.violations.map((v) => ({
|
|
429
|
+
ruleId: v.ruleId,
|
|
430
|
+
severity: v.severity,
|
|
431
|
+
file: v.file,
|
|
432
|
+
line: v.line,
|
|
433
|
+
path: v.path,
|
|
434
|
+
message: v.message,
|
|
435
|
+
impact: v.impact,
|
|
436
|
+
suggestion: v.suggestion,
|
|
437
|
+
fixable: v.fixable,
|
|
438
|
+
}));
|
|
328
439
|
results.set(relativePath, {
|
|
329
|
-
violations
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
440
|
+
violations,
|
|
441
|
+
errorCount: narrativeResult.errorCount,
|
|
442
|
+
warningCount: narrativeResult.warningCount,
|
|
443
|
+
fixableCount: narrativeResult.fixableCount,
|
|
444
|
+
byCategory: { schema: 0, reference: 0, structure: 0, pattern: 0, library: 0 }, // Could categorize narrative rules
|
|
445
|
+
byRule: countByRule(violations),
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
// Validate canvas/graph configuration
|
|
450
|
+
const loaded = loadGraphConfig(absolutePath);
|
|
451
|
+
if (!loaded) {
|
|
452
|
+
// File couldn't be loaded - report as error
|
|
453
|
+
results.set(relativePath, {
|
|
454
|
+
violations: [
|
|
455
|
+
{
|
|
456
|
+
ruleId: 'parse-error',
|
|
457
|
+
severity: 'error',
|
|
458
|
+
file: relativePath,
|
|
459
|
+
message: `Could not parse file: ${filePath}`,
|
|
460
|
+
impact: 'File cannot be validated',
|
|
461
|
+
fixable: false,
|
|
462
|
+
},
|
|
463
|
+
],
|
|
464
|
+
errorCount: 1,
|
|
465
|
+
warningCount: 0,
|
|
466
|
+
fixableCount: 0,
|
|
467
|
+
byCategory: { schema: 1, reference: 0, structure: 0, pattern: 0, library: 0 },
|
|
468
|
+
byRule: { 'parse-error': 1 },
|
|
469
|
+
});
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
// Run linting
|
|
473
|
+
const result = await engine.lintWithConfig(loaded.config, privuConfig, {
|
|
474
|
+
library,
|
|
475
|
+
configPath: relativePath,
|
|
476
|
+
rawContent: loaded.raw,
|
|
477
|
+
enabledRules: options.rule,
|
|
478
|
+
disabledRules: options.ignoreRule,
|
|
344
479
|
});
|
|
345
|
-
|
|
480
|
+
results.set(relativePath, result);
|
|
346
481
|
}
|
|
347
|
-
// Run linting
|
|
348
|
-
const result = await engine.lintWithConfig(loaded.config, privuConfig, {
|
|
349
|
-
library,
|
|
350
|
-
configPath: relativePath,
|
|
351
|
-
rawContent: loaded.raw,
|
|
352
|
-
enabledRules: options.rule,
|
|
353
|
-
disabledRules: options.ignoreRule,
|
|
354
|
-
});
|
|
355
|
-
results.set(relativePath, result);
|
|
356
482
|
}
|
|
357
483
|
// Output results
|
|
358
484
|
if (options.json) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/eval.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,iBAAiB,IAAI,OAAO,CA2E3C"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { evaluateExpression } from '@principal-ai/principal-view-core';
|
|
5
|
+
import { resolvePath, formatValue } from './utils.js';
|
|
6
|
+
export function createEvalCommand() {
|
|
7
|
+
const command = new Command('eval');
|
|
8
|
+
command
|
|
9
|
+
.description('Evaluate template expression with context')
|
|
10
|
+
.argument('<expression>', 'Template expression to evaluate (e.g., {count > 5 ? \'many\' : \'few\'})')
|
|
11
|
+
.argument('[context]', 'Optional path to JSON context file')
|
|
12
|
+
.option('--context <json>', 'Inline JSON context as string')
|
|
13
|
+
.option('--json', 'Output result as JSON')
|
|
14
|
+
.action(async (expression, contextPath, options) => {
|
|
15
|
+
try {
|
|
16
|
+
let context = {};
|
|
17
|
+
// Load context from file or inline option
|
|
18
|
+
if (contextPath) {
|
|
19
|
+
const content = await readFile(resolvePath(contextPath), 'utf-8');
|
|
20
|
+
context = JSON.parse(content);
|
|
21
|
+
}
|
|
22
|
+
else if (options.context) {
|
|
23
|
+
context = JSON.parse(options.context);
|
|
24
|
+
}
|
|
25
|
+
// Remove curly braces if user included them
|
|
26
|
+
let cleanExpression = expression.trim();
|
|
27
|
+
if (cleanExpression.startsWith('{') && cleanExpression.endsWith('}')) {
|
|
28
|
+
cleanExpression = cleanExpression.slice(1, -1).trim();
|
|
29
|
+
}
|
|
30
|
+
// Evaluate expression
|
|
31
|
+
const result = evaluateExpression(cleanExpression, context);
|
|
32
|
+
if (options.json) {
|
|
33
|
+
const output = {
|
|
34
|
+
expression: cleanExpression,
|
|
35
|
+
context,
|
|
36
|
+
result,
|
|
37
|
+
type: typeof result,
|
|
38
|
+
};
|
|
39
|
+
console.log(JSON.stringify(output, null, 2));
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.log(chalk.bold('\nExpression:'), chalk.cyan(`{${cleanExpression}}`));
|
|
43
|
+
if (Object.keys(context).length > 0) {
|
|
44
|
+
console.log(chalk.bold('\nContext:'));
|
|
45
|
+
for (const [key, value] of Object.entries(context)) {
|
|
46
|
+
console.log(chalk.gray(' •'), `${key} = ${formatValue(value)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
console.log(chalk.yellow('\nNo context provided'));
|
|
51
|
+
}
|
|
52
|
+
console.log(chalk.bold('\nResult:'), formatValue(result));
|
|
53
|
+
console.log(chalk.gray('Type:'), typeof result);
|
|
54
|
+
console.log();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const errorMessage = error.message;
|
|
59
|
+
if (options.json) {
|
|
60
|
+
const output = {
|
|
61
|
+
error: true,
|
|
62
|
+
message: errorMessage,
|
|
63
|
+
expression: expression,
|
|
64
|
+
};
|
|
65
|
+
console.log(JSON.stringify(output, null, 2));
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.error(chalk.red('\nEvaluation Error:'), errorMessage);
|
|
69
|
+
console.error(chalk.gray('\nExpression:'), expression);
|
|
70
|
+
console.log();
|
|
71
|
+
}
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
return command;
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/narrative/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,wBAAgB,sBAAsB,IAAI,OAAO,CAahD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|
|
@@ -0,0 +1,101 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
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"}
|