@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.
Files changed (35) hide show
  1. package/dist/commands/lint.d.ts.map +1 -1
  2. package/dist/commands/lint.js +160 -34
  3. package/dist/commands/narrative/eval.d.ts +3 -0
  4. package/dist/commands/narrative/eval.d.ts.map +1 -0
  5. package/dist/commands/narrative/eval.js +76 -0
  6. package/dist/commands/narrative/index.d.ts +3 -0
  7. package/dist/commands/narrative/index.d.ts.map +1 -0
  8. package/dist/commands/narrative/index.js +19 -0
  9. package/dist/commands/narrative/inspect.d.ts +3 -0
  10. package/dist/commands/narrative/inspect.d.ts.map +1 -0
  11. package/dist/commands/narrative/inspect.js +109 -0
  12. package/dist/commands/narrative/list.d.ts +3 -0
  13. package/dist/commands/narrative/list.d.ts.map +1 -0
  14. package/dist/commands/narrative/list.js +101 -0
  15. package/dist/commands/narrative/render.d.ts +3 -0
  16. package/dist/commands/narrative/render.d.ts.map +1 -0
  17. package/dist/commands/narrative/render.js +99 -0
  18. package/dist/commands/narrative/test.d.ts +3 -0
  19. package/dist/commands/narrative/test.d.ts.map +1 -0
  20. package/dist/commands/narrative/test.js +150 -0
  21. package/dist/commands/narrative/utils.d.ts +69 -0
  22. package/dist/commands/narrative/utils.d.ts.map +1 -0
  23. package/dist/commands/narrative/utils.js +158 -0
  24. package/dist/commands/narrative/validate.d.ts +3 -0
  25. package/dist/commands/narrative/validate.d.ts.map +1 -0
  26. package/dist/commands/narrative/validate.js +135 -0
  27. package/dist/commands/validate-execution.d.ts +12 -0
  28. package/dist/commands/validate-execution.d.ts.map +1 -0
  29. package/dist/commands/validate-execution.js +230 -0
  30. package/dist/commands/validate.d.ts.map +1 -1
  31. package/dist/commands/validate.js +0 -4
  32. package/dist/index.js +4 -0
  33. package/package.json +3 -2
  34. package/dist/index.cjs +0 -24272
  35. 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;AA2PpC,wBAAgB,iBAAiB,IAAI,OAAO,CA6L3C"}
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"}
@@ -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, canvas files, narrative templates, and execution artifacts
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 && !isCanvasFile && !isNarrativeTemplate && !isExecutionArtifact;
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 rules engine
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 loaded = loadGraphConfig(absolutePath);
326
- if (!loaded) {
327
- // File couldn't be loaded - report as error
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
- ruleId: 'parse-error',
332
- severity: 'error',
333
- file: relativePath,
334
- message: `Could not parse file: ${filePath}`,
335
- impact: 'File cannot be validated',
336
- fixable: false,
337
- },
338
- ],
339
- errorCount: 1,
340
- warningCount: 0,
341
- fixableCount: 0,
342
- byCategory: { schema: 1, reference: 0, structure: 0, pattern: 0, library: 0 },
343
- byRule: { 'parse-error': 1 },
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
- continue;
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,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createEvalCommand(): Command;
3
+ //# sourceMappingURL=eval.d.ts.map
@@ -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,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createNarrativeCommand(): Command;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createInspectCommand(): Command;
3
+ //# sourceMappingURL=inspect.d.ts.map
@@ -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,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createListCommand(): Command;
3
+ //# sourceMappingURL=list.d.ts.map
@@ -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,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createRenderCommand(): Command;
3
+ //# sourceMappingURL=render.d.ts.map
@@ -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"}