@principal-ai/principal-view-cli 0.6.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/formats.js +19 -19
- package/dist/commands/lint.js +44 -44
- package/dist/commands/workflow/index.d.ts +3 -0
- package/dist/commands/workflow/index.d.ts.map +1 -0
- package/dist/commands/{narrative → workflow}/index.js +3 -3
- package/dist/commands/workflow/inspect.d.ts.map +1 -0
- package/dist/commands/workflow/list.d.ts.map +1 -0
- package/dist/commands/{narrative → workflow}/list.js +32 -32
- package/dist/commands/workflow/render.d.ts.map +1 -0
- package/dist/commands/{narrative → workflow}/render.js +16 -16
- package/dist/commands/workflow/test.d.ts.map +1 -0
- package/dist/commands/{narrative → workflow}/test.js +8 -8
- package/dist/commands/{narrative → workflow}/utils.d.ts +4 -4
- package/dist/commands/workflow/utils.d.ts.map +1 -0
- package/dist/commands/{narrative → workflow}/utils.js +5 -5
- package/dist/commands/workflow/validate.d.ts.map +1 -0
- package/dist/commands/{narrative → workflow}/validate.js +21 -21
- package/dist/index.cjs +370 -267
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +2 -2
- package/package.json +3 -3
- package/dist/commands/narrative/index.d.ts +0 -3
- package/dist/commands/narrative/index.d.ts.map +0 -1
- package/dist/commands/narrative/inspect.d.ts.map +0 -1
- package/dist/commands/narrative/list.d.ts.map +0 -1
- package/dist/commands/narrative/render.d.ts.map +0 -1
- package/dist/commands/narrative/test.d.ts.map +0 -1
- package/dist/commands/narrative/utils.d.ts.map +0 -1
- package/dist/commands/narrative/validate.d.ts.map +0 -1
- /package/dist/commands/{narrative → workflow}/inspect.d.ts +0 -0
- /package/dist/commands/{narrative → workflow}/inspect.js +0 -0
- /package/dist/commands/{narrative → workflow}/list.d.ts +0 -0
- /package/dist/commands/{narrative → workflow}/render.d.ts +0 -0
- /package/dist/commands/{narrative → workflow}/test.d.ts +0 -0
- /package/dist/commands/{narrative → workflow}/validate.d.ts +0 -0
package/dist/commands/formats.js
CHANGED
|
@@ -14,9 +14,9 @@ ${chalk.bold('1. Canvas Files')} ${chalk.yellow('.otel.canvas')}
|
|
|
14
14
|
Define OTEL event schemas and telemetry structure for a feature.
|
|
15
15
|
These are the single source of truth for what events should be emitted.
|
|
16
16
|
|
|
17
|
-
${chalk.bold('2.
|
|
17
|
+
${chalk.bold('2. Workflow Files')} ${chalk.yellow('.workflow.json')}
|
|
18
18
|
Define scenarios and templates for rendering executions as human-readable
|
|
19
|
-
|
|
19
|
+
workflows based on the emitted events.
|
|
20
20
|
|
|
21
21
|
${chalk.bold('3. Execution Files')} ${chalk.yellow('.otel.json')}
|
|
22
22
|
Captured OTEL spans from test runs or production code, exported for
|
|
@@ -24,7 +24,7 @@ ${chalk.bold('3. Execution Files')} ${chalk.yellow('.otel.json')}
|
|
|
24
24
|
|
|
25
25
|
Run ${chalk.cyan('npx @principal-ai/principal-view-cli formats <section>')} for details on:
|
|
26
26
|
${chalk.yellow('canvas')} .otel.canvas format and event schemas
|
|
27
|
-
${chalk.yellow('
|
|
27
|
+
${chalk.yellow('workflow')} .workflow.json format and scenario structure
|
|
28
28
|
${chalk.yellow('execution')} .otel.json format for captured spans
|
|
29
29
|
${chalk.yellow('examples')} Complete example files
|
|
30
30
|
`,
|
|
@@ -108,28 +108,28 @@ ${chalk.cyan('5. Required vs Optional attributes:')}
|
|
|
108
108
|
${chalk.bold('Validation:')}
|
|
109
109
|
${chalk.cyan('npx @principal-ai/principal-view-cli validate')}
|
|
110
110
|
`,
|
|
111
|
-
|
|
112
|
-
${chalk.bold.cyan('
|
|
111
|
+
workflow: `
|
|
112
|
+
${chalk.bold.cyan('Workflow Format (.workflow.json)')}
|
|
113
113
|
${chalk.dim('═'.repeat(70))}
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
Workflow files define scenarios for rendering execution data as human-readable
|
|
116
116
|
stories. They evaluate conditions against captured events to select the best
|
|
117
|
-
matching
|
|
117
|
+
matching workflow template.
|
|
118
118
|
|
|
119
119
|
${chalk.bold('File Location:')}
|
|
120
|
-
${chalk.dim('.principal-views/')}${chalk.yellow('<feature-name>.
|
|
120
|
+
${chalk.dim('.principal-views/')}${chalk.yellow('<feature-name>.workflow.json')}
|
|
121
121
|
${chalk.dim('(co-located with corresponding .otel.canvas file)')}
|
|
122
122
|
|
|
123
123
|
${chalk.bold('Required Structure:')}
|
|
124
124
|
${chalk.dim('┌────────────────────────────────────────────────────────────────────┐')}
|
|
125
125
|
${chalk.dim('│')} { ${chalk.dim('│')}
|
|
126
|
-
${chalk.dim('│')} ${chalk.green('"name"')}: "Feature Name", ${chalk.dim('// NOT "Feature Name
|
|
127
|
-
${chalk.dim('│')} ${chalk.green('"description"')}: "What the feature does", ${chalk.dim('// Purpose, not "
|
|
126
|
+
${chalk.dim('│')} ${chalk.green('"name"')}: "Feature Name", ${chalk.dim('// NOT "Feature Name Workflows"')} ${chalk.dim('│')}
|
|
127
|
+
${chalk.dim('│')} ${chalk.green('"description"')}: "What the feature does", ${chalk.dim('// Purpose, not "Workflows for..."')} ${chalk.dim('│')}
|
|
128
128
|
${chalk.dim('│')} ${chalk.green('"scenarios"')}: [ ${chalk.dim('│')}
|
|
129
129
|
${chalk.dim('│')} { ${chalk.dim('│')}
|
|
130
130
|
${chalk.dim('│')} ${chalk.yellow('"priority"')}: 1, ${chalk.dim('// Lower = higher priority')} ${chalk.dim('│')}
|
|
131
131
|
${chalk.dim('│')} ${chalk.yellow('"condition"')}: "...", ${chalk.dim('// JSONPath/logic expression')} ${chalk.dim('│')}
|
|
132
|
-
${chalk.dim('│')} ${chalk.yellow('"template"')}: { ${chalk.dim('//
|
|
132
|
+
${chalk.dim('│')} ${chalk.yellow('"template"')}: { ${chalk.dim('// Workflow template')} ${chalk.dim('│')}
|
|
133
133
|
${chalk.dim('│')} ${chalk.cyan('"summary"')}: "Completed {{count}} items", ${chalk.dim('// Handlebars template')} ${chalk.dim('│')}
|
|
134
134
|
${chalk.dim('│')} ${chalk.cyan('"events"')}: { ${chalk.dim('// Per-event templates')} ${chalk.dim('│')}
|
|
135
135
|
${chalk.dim('│')} "event.name": "Event {{attribute}} occurred" ${chalk.dim('│')}
|
|
@@ -142,14 +142,14 @@ ${chalk.dim('└─────────────────────
|
|
|
142
142
|
|
|
143
143
|
${chalk.bold('Naming Guidelines:')}
|
|
144
144
|
|
|
145
|
-
❌ DON'T append "
|
|
146
|
-
"name": "Package Processor
|
|
145
|
+
❌ DON'T append "Workflows" to the name:
|
|
146
|
+
"name": "Package Processor Workflows"
|
|
147
147
|
|
|
148
148
|
✅ DO use the feature name directly:
|
|
149
149
|
"name": "Package Processor"
|
|
150
150
|
|
|
151
151
|
❌ DON'T prefix description with boilerplate:
|
|
152
|
-
"description": "Human-readable
|
|
152
|
+
"description": "Human-readable workflows for package extraction..."
|
|
153
153
|
|
|
154
154
|
✅ DO describe the feature's purpose:
|
|
155
155
|
"description": "Package extraction and analysis from repository file trees"
|
|
@@ -200,7 +200,7 @@ ${chalk.cyan('5. Template style:')}
|
|
|
200
200
|
- Include key metrics and IDs
|
|
201
201
|
|
|
202
202
|
${chalk.bold('Validation:')}
|
|
203
|
-
${chalk.cyan('npx @principal-ai/principal-view-cli
|
|
203
|
+
${chalk.cyan('npx @principal-ai/principal-view-cli workflow validate')}
|
|
204
204
|
`,
|
|
205
205
|
execution: `
|
|
206
206
|
${chalk.bold.cyan('Execution Format (.otel.json)')}
|
|
@@ -399,9 +399,9 @@ ${chalk.yellow('.principal-views/data-validator.otel.canvas')}
|
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
|
|
402
|
-
${chalk.bold('Example 2:
|
|
402
|
+
${chalk.bold('Example 2: Workflow Scenarios')}
|
|
403
403
|
${chalk.dim('─'.repeat(70))}
|
|
404
|
-
${chalk.yellow('.principal-views/data-validator.
|
|
404
|
+
${chalk.yellow('.principal-views/data-validator.workflow.json')}
|
|
405
405
|
|
|
406
406
|
{
|
|
407
407
|
"name": "Data Validator",
|
|
@@ -501,7 +501,7 @@ ${chalk.yellow('__executions__/data-validator.otel.json')}
|
|
|
501
501
|
|
|
502
502
|
${chalk.bold('Next Steps:')}
|
|
503
503
|
${chalk.cyan('npx @principal-ai/principal-view-cli validate')} Validate canvas
|
|
504
|
-
${chalk.cyan('npx @principal-ai/principal-view-cli
|
|
504
|
+
${chalk.cyan('npx @principal-ai/principal-view-cli workflow validate')} Validate workflows
|
|
505
505
|
${chalk.cyan('npx @principal-ai/principal-view-cli validate-execution')} Validate execution
|
|
506
506
|
`,
|
|
507
507
|
};
|
|
@@ -509,7 +509,7 @@ export function createFormatsCommand() {
|
|
|
509
509
|
const command = new Command('formats');
|
|
510
510
|
command
|
|
511
511
|
.description('Display documentation about file formats')
|
|
512
|
-
.argument('[section]', 'Section to display: overview, canvas,
|
|
512
|
+
.argument('[section]', 'Section to display: overview, canvas, workflow, execution, examples')
|
|
513
513
|
.action((section) => {
|
|
514
514
|
const validSections = Object.keys(FORMAT_SECTIONS);
|
|
515
515
|
if (!section) {
|
package/dist/commands/lint.js
CHANGED
|
@@ -8,7 +8,7 @@ import chalk from 'chalk';
|
|
|
8
8
|
import { globby } from 'globby';
|
|
9
9
|
import yaml from 'js-yaml';
|
|
10
10
|
// Node.js-specific imports (rules engine, config management, validators)
|
|
11
|
-
import { createDefaultRulesEngine, validatePrivuConfig, mergeConfigs, getDefaultConfig,
|
|
11
|
+
import { createDefaultRulesEngine, validatePrivuConfig, mergeConfigs, getDefaultConfig, createWorkflowValidator, } from '@principal-ai/principal-view-core/node';
|
|
12
12
|
// ============================================================================
|
|
13
13
|
// Config File Loading
|
|
14
14
|
// ============================================================================
|
|
@@ -118,23 +118,23 @@ function loadGraphConfig(filePath) {
|
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
/**
|
|
121
|
-
* Load a
|
|
121
|
+
* Load a workflow template file
|
|
122
122
|
*/
|
|
123
|
-
function
|
|
123
|
+
function loadWorkflowTemplate(filePath) {
|
|
124
124
|
if (!existsSync(filePath)) {
|
|
125
125
|
return null;
|
|
126
126
|
}
|
|
127
127
|
try {
|
|
128
128
|
const raw = readFileSync(filePath, 'utf8');
|
|
129
|
-
const
|
|
130
|
-
return {
|
|
129
|
+
const workflow = JSON.parse(raw);
|
|
130
|
+
return { workflow, raw };
|
|
131
131
|
}
|
|
132
132
|
catch {
|
|
133
133
|
return null;
|
|
134
134
|
}
|
|
135
135
|
}
|
|
136
136
|
/**
|
|
137
|
-
* Load a canvas file for
|
|
137
|
+
* Load a canvas file for workflow validation
|
|
138
138
|
*/
|
|
139
139
|
function loadCanvas(filePath) {
|
|
140
140
|
if (!existsSync(filePath)) {
|
|
@@ -159,8 +159,8 @@ function loadCanvas(filePath) {
|
|
|
159
159
|
*/
|
|
160
160
|
function getFileType(filePath) {
|
|
161
161
|
const name = basename(filePath).toLowerCase();
|
|
162
|
-
if (name.endsWith('.
|
|
163
|
-
return '
|
|
162
|
+
if (name.endsWith('.workflow.json')) {
|
|
163
|
+
return 'workflow';
|
|
164
164
|
}
|
|
165
165
|
if (name.endsWith('.canvas') || name.endsWith('.otel.canvas')) {
|
|
166
166
|
return 'canvas';
|
|
@@ -347,7 +347,7 @@ export function createLintCommand() {
|
|
|
347
347
|
expandDirectories: false,
|
|
348
348
|
});
|
|
349
349
|
// Filter out library files, config files, and execution artifacts
|
|
350
|
-
// INCLUDE both canvas files and
|
|
350
|
+
// INCLUDE both canvas files and workflow templates for linting
|
|
351
351
|
const configFiles = matchedFiles.filter((f) => {
|
|
352
352
|
const name = basename(f).toLowerCase();
|
|
353
353
|
const isLibraryFile = name.startsWith('library.');
|
|
@@ -380,45 +380,45 @@ export function createLintCommand() {
|
|
|
380
380
|
}
|
|
381
381
|
// Create validators
|
|
382
382
|
const engine = createDefaultRulesEngine();
|
|
383
|
-
const
|
|
383
|
+
const workflowValidator = createWorkflowValidator();
|
|
384
384
|
// Lint each file
|
|
385
385
|
const results = new Map();
|
|
386
|
-
// PHASE 1: Group
|
|
387
|
-
const
|
|
386
|
+
// PHASE 1: Group workflows by canvas and collect all events used
|
|
387
|
+
const workflowsByCanvas = new Map();
|
|
388
388
|
const canvasEventMap = new Map();
|
|
389
389
|
for (const filePath of configFiles) {
|
|
390
390
|
const absolutePath = resolve(cwd, filePath);
|
|
391
391
|
const relativePath = relative(cwd, absolutePath);
|
|
392
392
|
const fileType = getFileType(absolutePath);
|
|
393
|
-
if (fileType === '
|
|
394
|
-
const loaded =
|
|
393
|
+
if (fileType === 'workflow') {
|
|
394
|
+
const loaded = loadWorkflowTemplate(absolutePath);
|
|
395
395
|
if (!loaded)
|
|
396
396
|
continue;
|
|
397
|
-
const canvasPath = loaded.
|
|
398
|
-
? resolve(dirname(absolutePath), loaded.
|
|
397
|
+
const canvasPath = loaded.workflow.canvas
|
|
398
|
+
? resolve(dirname(absolutePath), loaded.workflow.canvas)
|
|
399
399
|
: undefined;
|
|
400
400
|
if (canvasPath) {
|
|
401
401
|
const canvasKey = relative(cwd, canvasPath);
|
|
402
|
-
// Group
|
|
403
|
-
if (!
|
|
404
|
-
|
|
402
|
+
// Group workflow by canvas
|
|
403
|
+
if (!workflowsByCanvas.has(canvasKey)) {
|
|
404
|
+
workflowsByCanvas.set(canvasKey, []);
|
|
405
405
|
canvasEventMap.set(canvasKey, new Set());
|
|
406
406
|
}
|
|
407
|
-
|
|
408
|
-
// Collect events from this
|
|
409
|
-
const
|
|
410
|
-
for (const scenario of loaded.
|
|
407
|
+
workflowsByCanvas.get(canvasKey).push({ absolutePath, relativePath, loaded });
|
|
408
|
+
// Collect events from this workflow
|
|
409
|
+
const workflowEvents = canvasEventMap.get(canvasKey);
|
|
410
|
+
for (const scenario of loaded.workflow.scenarios) {
|
|
411
411
|
if (scenario.condition?.requires) {
|
|
412
412
|
for (const eventPattern of scenario.condition.requires) {
|
|
413
413
|
if (!eventPattern.includes('*')) {
|
|
414
|
-
|
|
414
|
+
workflowEvents.add(eventPattern);
|
|
415
415
|
}
|
|
416
416
|
}
|
|
417
417
|
}
|
|
418
418
|
if (scenario.template?.events) {
|
|
419
419
|
for (const eventName of Object.keys(scenario.template.events)) {
|
|
420
420
|
if (!eventName.includes('*')) {
|
|
421
|
-
|
|
421
|
+
workflowEvents.add(eventName);
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
424
|
}
|
|
@@ -431,9 +431,9 @@ export function createLintCommand() {
|
|
|
431
431
|
const absolutePath = resolve(cwd, filePath);
|
|
432
432
|
const relativePath = relative(cwd, absolutePath);
|
|
433
433
|
const fileType = getFileType(absolutePath);
|
|
434
|
-
if (fileType === '
|
|
435
|
-
// Validate
|
|
436
|
-
const loaded =
|
|
434
|
+
if (fileType === 'workflow') {
|
|
435
|
+
// Validate workflow template
|
|
436
|
+
const loaded = loadWorkflowTemplate(absolutePath);
|
|
437
437
|
if (!loaded) {
|
|
438
438
|
// File couldn't be loaded - report as error
|
|
439
439
|
results.set(relativePath, {
|
|
@@ -442,7 +442,7 @@ export function createLintCommand() {
|
|
|
442
442
|
ruleId: 'parse-error',
|
|
443
443
|
severity: 'error',
|
|
444
444
|
file: relativePath,
|
|
445
|
-
message: `Could not parse
|
|
445
|
+
message: `Could not parse workflow file: ${filePath}`,
|
|
446
446
|
impact: 'File cannot be validated',
|
|
447
447
|
fixable: false,
|
|
448
448
|
},
|
|
@@ -456,25 +456,25 @@ export function createLintCommand() {
|
|
|
456
456
|
continue;
|
|
457
457
|
}
|
|
458
458
|
// Load the referenced canvas if it exists
|
|
459
|
-
const canvasPath = loaded.
|
|
460
|
-
? resolve(dirname(absolutePath), loaded.
|
|
459
|
+
const canvasPath = loaded.workflow.canvas
|
|
460
|
+
? resolve(dirname(absolutePath), loaded.workflow.canvas)
|
|
461
461
|
: undefined;
|
|
462
462
|
const canvas = canvasPath ? loadCanvas(canvasPath) : undefined;
|
|
463
|
-
// Get all events used across
|
|
463
|
+
// Get all events used across workflows for this canvas
|
|
464
464
|
const canvasKey = canvasPath ? relative(cwd, canvasPath) : undefined;
|
|
465
|
-
const
|
|
466
|
-
// Run
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
465
|
+
const allWorkflowEvents = canvasKey ? canvasEventMap.get(canvasKey) : undefined;
|
|
466
|
+
// Run workflow validation with canvas-wide event knowledge
|
|
467
|
+
const workflowResult = await workflowValidator.validate({
|
|
468
|
+
workflow: loaded.workflow,
|
|
469
|
+
workflowPath: relativePath,
|
|
470
470
|
canvas: canvas ?? undefined,
|
|
471
471
|
canvasPath: canvasKey,
|
|
472
472
|
basePath: dirname(absolutePath),
|
|
473
473
|
rawContent: loaded.raw,
|
|
474
|
-
|
|
474
|
+
allWorkflowEvents,
|
|
475
475
|
});
|
|
476
|
-
// Convert
|
|
477
|
-
const violations =
|
|
476
|
+
// Convert workflow violations to graph violations format
|
|
477
|
+
const violations = workflowResult.violations.map((v) => ({
|
|
478
478
|
ruleId: v.ruleId,
|
|
479
479
|
severity: v.severity,
|
|
480
480
|
file: v.file,
|
|
@@ -487,10 +487,10 @@ export function createLintCommand() {
|
|
|
487
487
|
}));
|
|
488
488
|
results.set(relativePath, {
|
|
489
489
|
violations,
|
|
490
|
-
errorCount:
|
|
491
|
-
warningCount:
|
|
492
|
-
fixableCount:
|
|
493
|
-
byCategory: { schema: 0, reference: 0, structure: 0, pattern: 0, library: 0 }, // Could categorize
|
|
490
|
+
errorCount: workflowResult.errorCount,
|
|
491
|
+
warningCount: workflowResult.warningCount,
|
|
492
|
+
fixableCount: workflowResult.fixableCount,
|
|
493
|
+
byCategory: { schema: 0, reference: 0, structure: 0, pattern: 0, library: 0 }, // Could categorize workflow rules
|
|
494
494
|
byRule: countByRule(violations),
|
|
495
495
|
});
|
|
496
496
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,wBAAgB,qBAAqB,IAAI,OAAO,CAY/C"}
|
|
@@ -4,10 +4,10 @@ import { createInspectCommand } from './inspect.js';
|
|
|
4
4
|
import { createRenderCommand } from './render.js';
|
|
5
5
|
import { createTestCommand } from './test.js';
|
|
6
6
|
import { createListCommand } from './list.js';
|
|
7
|
-
export function
|
|
8
|
-
const command = new Command('
|
|
7
|
+
export function createWorkflowCommand() {
|
|
8
|
+
const command = new Command('workflow');
|
|
9
9
|
command
|
|
10
|
-
.description('Validate, test, and debug
|
|
10
|
+
.description('Validate, test, and debug workflow templates')
|
|
11
11
|
.addCommand(createValidateCommand())
|
|
12
12
|
.addCommand(createInspectCommand())
|
|
13
13
|
.addCommand(createRenderCommand())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/inspect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuBpC,wBAAgB,oBAAoB,IAAI,OAAO,CAiI9C"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/list.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,wBAAgB,iBAAiB,IAAI,OAAO,CA6G3C"}
|
|
@@ -3,11 +3,11 @@ import chalk from 'chalk';
|
|
|
3
3
|
import { access } from 'node:fs/promises';
|
|
4
4
|
import { resolve, dirname, join } from 'node:path';
|
|
5
5
|
import { globby } from 'globby';
|
|
6
|
-
import {
|
|
6
|
+
import { loadWorkflow } from './utils.js';
|
|
7
7
|
export function createListCommand() {
|
|
8
8
|
const command = new Command('list');
|
|
9
9
|
command
|
|
10
|
-
.description('List all
|
|
10
|
+
.description('List all workflow files in project')
|
|
11
11
|
.argument('[dir]', 'Directory to search (default: .principal-views/)')
|
|
12
12
|
.option('--json', 'Output as JSON')
|
|
13
13
|
.option('--show-canvas', 'Show linked canvas files')
|
|
@@ -15,19 +15,19 @@ export function createListCommand() {
|
|
|
15
15
|
try {
|
|
16
16
|
const searchDir = dir || '.principal-views';
|
|
17
17
|
const searchPath = resolve(process.cwd(), searchDir);
|
|
18
|
-
// Find all .
|
|
19
|
-
const files = await globby('**/*.
|
|
18
|
+
// Find all .workflow.json files
|
|
19
|
+
const files = await globby('**/*.workflow.json', {
|
|
20
20
|
cwd: searchPath,
|
|
21
21
|
ignore: ['node_modules/**', '.git/**', '__executions__/**'],
|
|
22
22
|
});
|
|
23
|
-
// Load each
|
|
24
|
-
const
|
|
23
|
+
// Load each workflow and check canvas existence
|
|
24
|
+
const workflows = await Promise.all(files.map(async (file) => {
|
|
25
25
|
const fullPath = join(searchPath, file);
|
|
26
|
-
const
|
|
26
|
+
const workflow = await loadWorkflow(fullPath);
|
|
27
27
|
let canvasExists;
|
|
28
|
-
if (options.showCanvas &&
|
|
29
|
-
const
|
|
30
|
-
const canvasPath = resolve(
|
|
28
|
+
if (options.showCanvas && workflow.canvas) {
|
|
29
|
+
const workflowDir = dirname(fullPath);
|
|
30
|
+
const canvasPath = resolve(workflowDir, workflow.canvas);
|
|
31
31
|
try {
|
|
32
32
|
await access(canvasPath);
|
|
33
33
|
canvasExists = true;
|
|
@@ -36,22 +36,22 @@ export function createListCommand() {
|
|
|
36
36
|
canvasExists = false;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
const defaultCount =
|
|
39
|
+
const defaultCount = workflow.scenarios.filter((s) => s.condition.default).length;
|
|
40
40
|
return {
|
|
41
41
|
file: join(searchDir, file),
|
|
42
|
-
canvas:
|
|
42
|
+
canvas: workflow.canvas,
|
|
43
43
|
canvasExists,
|
|
44
|
-
scenarioCount:
|
|
44
|
+
scenarioCount: workflow.scenarios.length,
|
|
45
45
|
defaultCount,
|
|
46
|
-
mode:
|
|
47
|
-
name:
|
|
46
|
+
mode: workflow.mode,
|
|
47
|
+
name: workflow.name,
|
|
48
48
|
};
|
|
49
49
|
}));
|
|
50
50
|
if (options.json) {
|
|
51
51
|
const output = {
|
|
52
52
|
searchDir,
|
|
53
|
-
count:
|
|
54
|
-
|
|
53
|
+
count: workflows.length,
|
|
54
|
+
workflows: workflows.map((n) => ({
|
|
55
55
|
file: n.file,
|
|
56
56
|
name: n.name,
|
|
57
57
|
canvas: n.canvas,
|
|
@@ -64,31 +64,31 @@ export function createListCommand() {
|
|
|
64
64
|
console.log(JSON.stringify(output, null, 2));
|
|
65
65
|
}
|
|
66
66
|
else {
|
|
67
|
-
console.log(chalk.bold('\
|
|
67
|
+
console.log(chalk.bold('\nWorkflow Templates:'));
|
|
68
68
|
console.log('━'.repeat(60));
|
|
69
|
-
if (
|
|
70
|
-
console.log(chalk.yellow(`\nNo
|
|
69
|
+
if (workflows.length === 0) {
|
|
70
|
+
console.log(chalk.yellow(`\nNo workflow templates found in ${searchDir}`));
|
|
71
71
|
console.log();
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
|
-
for (const
|
|
75
|
-
console.log(chalk.bold(`\n${
|
|
76
|
-
if (
|
|
77
|
-
console.log(chalk.gray(` Name: ${
|
|
74
|
+
for (const workflow of workflows) {
|
|
75
|
+
console.log(chalk.bold(`\n${workflow.file}`));
|
|
76
|
+
if (workflow.name) {
|
|
77
|
+
console.log(chalk.gray(` Name: ${workflow.name}`));
|
|
78
78
|
}
|
|
79
|
-
if (options.showCanvas &&
|
|
80
|
-
const status =
|
|
79
|
+
if (options.showCanvas && workflow.canvas) {
|
|
80
|
+
const status = workflow.canvasExists
|
|
81
81
|
? chalk.green('✓')
|
|
82
82
|
: chalk.red('✗');
|
|
83
|
-
console.log(chalk.gray(` Canvas: ${
|
|
83
|
+
console.log(chalk.gray(` Canvas: ${workflow.canvas} ${status}`));
|
|
84
84
|
}
|
|
85
|
-
else if (
|
|
86
|
-
console.log(chalk.gray(` Canvas: ${
|
|
85
|
+
else if (workflow.canvas) {
|
|
86
|
+
console.log(chalk.gray(` Canvas: ${workflow.canvas}`));
|
|
87
87
|
}
|
|
88
|
-
console.log(chalk.gray(` Scenarios: ${
|
|
89
|
-
console.log(chalk.gray(` Mode: ${
|
|
88
|
+
console.log(chalk.gray(` Scenarios: ${workflow.scenarioCount} (${workflow.defaultCount} default)`));
|
|
89
|
+
console.log(chalk.gray(` Mode: ${workflow.mode}`));
|
|
90
90
|
}
|
|
91
|
-
console.log(chalk.bold(`\nFound ${
|
|
91
|
+
console.log(chalk.bold(`\nFound ${workflows.length} workflow template(s)`));
|
|
92
92
|
console.log();
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBpC,wBAAgB,mBAAmB,IAAI,OAAO,CAmH7C"}
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { renderWorkflow } from '@principal-ai/principal-view-core';
|
|
4
|
+
import { loadWorkflow, loadExecution, executionToEvents, resolvePath, } from './utils.js';
|
|
5
5
|
export function createRenderCommand() {
|
|
6
6
|
const command = new Command('render');
|
|
7
7
|
command
|
|
8
|
-
.description('Render
|
|
9
|
-
.argument('<
|
|
8
|
+
.description('Render workflow template using execution data')
|
|
9
|
+
.argument('<workflow>', 'Path to .workflow.json file')
|
|
10
10
|
.argument('<execution>', 'Path to .otel.json execution file')
|
|
11
11
|
.option('--mode <mode>', 'Override rendering mode: span-tree, timeline')
|
|
12
12
|
.option('--scenario <id>', 'Force specific scenario (skip auto-selection)')
|
|
13
13
|
.option('--json', 'Output structured result as JSON')
|
|
14
14
|
.option('--format <format>', 'Output format: text (default), markdown, json', 'text')
|
|
15
15
|
.option('--show-metadata', 'Include rendering metadata in output')
|
|
16
|
-
.action(async (
|
|
16
|
+
.action(async (workflowPath, executionPath, options) => {
|
|
17
17
|
try {
|
|
18
|
-
const
|
|
18
|
+
const workflow = await loadWorkflow(resolvePath(workflowPath));
|
|
19
19
|
const executionData = await loadExecution(resolvePath(executionPath));
|
|
20
20
|
const events = executionToEvents(executionData);
|
|
21
21
|
// Override mode if specified
|
|
@@ -24,26 +24,26 @@ export function createRenderCommand() {
|
|
|
24
24
|
if (!validModes.includes(options.mode)) {
|
|
25
25
|
throw new Error(`Invalid mode: ${options.mode}. Must be one of: ${validModes.join(', ')}`);
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
workflow.mode = options.mode;
|
|
28
28
|
}
|
|
29
|
-
// Render
|
|
30
|
-
const result =
|
|
29
|
+
// Render workflow
|
|
30
|
+
const result = renderWorkflow(workflow, events);
|
|
31
31
|
// Get the selected scenario from the result
|
|
32
|
-
const selectedScenario =
|
|
32
|
+
const selectedScenario = workflow.scenarios.find((s) => s.id === result.scenarioId);
|
|
33
33
|
// Force scenario if specified
|
|
34
34
|
if (options.scenario) {
|
|
35
|
-
const scenario =
|
|
35
|
+
const scenario = workflow.scenarios.find((s) => s.id === options.scenario);
|
|
36
36
|
if (!scenario) {
|
|
37
37
|
throw new Error(`Scenario not found: ${options.scenario}`);
|
|
38
38
|
}
|
|
39
|
-
// Note: This would require a way to force scenario in
|
|
39
|
+
// Note: This would require a way to force scenario in renderWorkflow API
|
|
40
40
|
// For now, we'll just validate the scenario exists
|
|
41
41
|
}
|
|
42
42
|
if (options.json) {
|
|
43
43
|
const output = {
|
|
44
|
-
|
|
44
|
+
workflow: workflowPath,
|
|
45
45
|
execution: executionPath,
|
|
46
|
-
mode:
|
|
46
|
+
mode: workflow.mode,
|
|
47
47
|
scenario: {
|
|
48
48
|
id: selectedScenario?.id,
|
|
49
49
|
priority: selectedScenario?.priority,
|
|
@@ -59,9 +59,9 @@ export function createRenderCommand() {
|
|
|
59
59
|
else {
|
|
60
60
|
// Text output
|
|
61
61
|
if (!options.format || options.format === 'text') {
|
|
62
|
-
console.log(chalk.gray(`Rendering: ${
|
|
62
|
+
console.log(chalk.gray(`Rendering: ${workflowPath}`));
|
|
63
63
|
console.log(chalk.gray(`Execution: ${executionPath}`));
|
|
64
|
-
console.log(chalk.gray(`Mode: ${
|
|
64
|
+
console.log(chalk.gray(`Mode: ${workflow.mode}`));
|
|
65
65
|
if (selectedScenario) {
|
|
66
66
|
console.log(chalk.gray(`Scenario: ${selectedScenario.id} (priority: ${selectedScenario.priority})`));
|
|
67
67
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"test.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBpC,wBAAgB,iBAAiB,IAAI,OAAO,CAuL3C"}
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { selectScenario, matchesCondition, computeAggregates, hasEventMatching, } from '@principal-ai/principal-view-core';
|
|
4
|
-
import {
|
|
4
|
+
import { loadWorkflow, loadExecution, executionToEvents, resolvePath, formatValue, } from './utils.js';
|
|
5
5
|
export function createTestCommand() {
|
|
6
6
|
const command = new Command('test');
|
|
7
7
|
command
|
|
8
8
|
.description('Test scenario matching and show why scenarios match or don\'t match')
|
|
9
|
-
.argument('<
|
|
9
|
+
.argument('<workflow>', 'Path to .workflow.json file')
|
|
10
10
|
.argument('<execution>', 'Path to .otel.json execution file')
|
|
11
11
|
.option('--show-all', 'Show all scenarios (not just matches)')
|
|
12
12
|
.option('--show-aggregates', 'Display computed aggregates')
|
|
13
13
|
.option('--json', 'Output as JSON')
|
|
14
|
-
.action(async (
|
|
14
|
+
.action(async (workflowPath, executionPath, options) => {
|
|
15
15
|
try {
|
|
16
|
-
const
|
|
16
|
+
const workflow = await loadWorkflow(resolvePath(workflowPath));
|
|
17
17
|
const executionData = await loadExecution(resolvePath(executionPath));
|
|
18
18
|
const events = executionToEvents(executionData);
|
|
19
19
|
// Compute aggregates
|
|
20
20
|
const aggregates = computeAggregates(events);
|
|
21
21
|
// Test each scenario
|
|
22
|
-
const scenarioResults =
|
|
22
|
+
const scenarioResults = workflow.scenarios.map((scenario) => {
|
|
23
23
|
const condition = scenario.condition;
|
|
24
24
|
const matched = matchesCondition(condition, events, aggregates);
|
|
25
25
|
let reason;
|
|
@@ -60,11 +60,11 @@ export function createTestCommand() {
|
|
|
60
60
|
};
|
|
61
61
|
});
|
|
62
62
|
// Select the winning scenario
|
|
63
|
-
const matchResult = selectScenario(
|
|
63
|
+
const matchResult = selectScenario(workflow, events, aggregates);
|
|
64
64
|
const selectedScenario = matchResult.scenario;
|
|
65
65
|
if (options.json) {
|
|
66
66
|
const output = {
|
|
67
|
-
|
|
67
|
+
workflow: workflowPath,
|
|
68
68
|
execution: executionPath,
|
|
69
69
|
scenarios: scenarioResults.map((r) => ({
|
|
70
70
|
id: r.scenario.id,
|
|
@@ -81,7 +81,7 @@ export function createTestCommand() {
|
|
|
81
81
|
}
|
|
82
82
|
else {
|
|
83
83
|
// Text output
|
|
84
|
-
console.log(chalk.bold(`\nTesting: ${
|
|
84
|
+
console.log(chalk.bold(`\nTesting: ${workflowPath}`));
|
|
85
85
|
console.log(chalk.gray(`Execution: ${executionPath}\n`));
|
|
86
86
|
console.log(chalk.bold('Scenario Matching Results:'));
|
|
87
87
|
console.log('━'.repeat(60));
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { WorkflowTemplate, OtelEvent } from '@principal-ai/principal-view-core';
|
|
2
2
|
import { type ExecutionData } from '@principal-ai/principal-view-core';
|
|
3
3
|
/**
|
|
4
|
-
* Load a
|
|
4
|
+
* Load a workflow template from a file
|
|
5
5
|
*/
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function loadWorkflow(filePath: string): Promise<WorkflowTemplate>;
|
|
7
7
|
/**
|
|
8
8
|
* Load execution data from a .otel.json file
|
|
9
9
|
*
|
|
@@ -11,7 +11,7 @@ export declare function loadNarrative(filePath: string): Promise<NarrativeTempla
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function loadExecution(filePath: string): Promise<ExecutionData>;
|
|
13
13
|
/**
|
|
14
|
-
* Convert execution data to OtelEvent array format expected by
|
|
14
|
+
* Convert execution data to OtelEvent array format expected by workflow APIs
|
|
15
15
|
*/
|
|
16
16
|
export declare function executionToEvents(execution: ExecutionData): OtelEvent[];
|
|
17
17
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AACrF,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,mCAAmC,CAAC;AAE3F;;GAEG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAU9E;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAc5E;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"}
|