voyageai-cli 1.28.0 → 1.29.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.
@@ -1,8 +1,22 @@
1
1
  'use strict';
2
2
 
3
+ const path = require('path');
3
4
  const pc = require('picocolors');
4
5
  const ui = require('../lib/ui');
5
6
 
7
+ /**
8
+ * Try to get the git user name for default author.
9
+ * @returns {string}
10
+ */
11
+ function getGitAuthor() {
12
+ try {
13
+ const { execSync } = require('child_process');
14
+ return execSync('git config user.name', { encoding: 'utf8' }).trim();
15
+ } catch {
16
+ return '';
17
+ }
18
+ }
19
+
6
20
  /**
7
21
  * Parse repeatable --input key=value options into an object.
8
22
  * Used as Commander's option reducer.
@@ -84,11 +98,31 @@ function registerWorkflow(program) {
84
98
  .option('--verbose', 'Show step details', false)
85
99
  .option('--no-interactive', 'Disable interactive input prompting')
86
100
  .action(async (file, opts) => {
87
- const { loadWorkflow, executeWorkflow, buildExecutionPlan, validateWorkflow } = require('../lib/workflow');
101
+ const { executeWorkflow, buildExecutionPlan, validateWorkflow } = require('../lib/workflow');
102
+ const { resolveWorkflow } = require('../lib/workflow-registry');
88
103
 
89
104
  let definition;
90
105
  try {
91
- definition = loadWorkflow(file);
106
+ const resolved = resolveWorkflow(file);
107
+ definition = resolved.definition;
108
+
109
+ // Show workflow source notice
110
+ if ((resolved.source === 'community' || resolved.source === 'official') && !opts.quiet) {
111
+ const pkg = resolved.metadata?.package;
112
+ const author = typeof pkg?.author === 'string' ? pkg.author : pkg?.author?.name || 'unknown';
113
+ const tools = (pkg?.vai?.tools || []).join(', ');
114
+ const isOfficial = resolved.source === 'official';
115
+ const label = isOfficial ? 'official catalog workflow' : 'community workflow';
116
+ console.error(`${pc.dim('ℹ')} Running ${label}: ${pc.cyan(pkg?.name || file)} ${pc.dim(`v${pkg?.version || '?'}`)}`);
117
+ console.error(` ${pc.dim(`by ${author}`)}${tools ? pc.dim(` | Tools: ${tools}`) : ''}`);
118
+ if (!isOfficial) {
119
+ console.error(` ${pc.dim('This is a community-contributed workflow, not maintained by the vai project.')}`);
120
+ }
121
+ console.error();
122
+ for (const w of resolved.metadata?.warnings || []) {
123
+ console.error(` ${pc.yellow('⚠')} ${w}`);
124
+ }
125
+ }
92
126
  } catch (err) {
93
127
  console.error(ui.error(err.message));
94
128
  process.exit(1);
@@ -265,27 +299,396 @@ function registerWorkflow(program) {
265
299
  // ── workflow list ──
266
300
  wfCmd
267
301
  .command('list')
268
- .description('List built-in workflow templates')
269
- .action(() => {
270
- const { listBuiltinWorkflows } = require('../lib/workflow');
271
-
272
- const workflows = listBuiltinWorkflows();
273
- if (workflows.length === 0) {
274
- console.log(pc.dim('No built-in workflows found.'));
302
+ .description('List available workflows (built-in + official + community)')
303
+ .option('--built-in', 'Show only built-in workflows', false)
304
+ .option('--official', 'Show only official @vaicli workflows', false)
305
+ .option('--community', 'Show only community workflows', false)
306
+ .option('--category <name>', 'Filter by category')
307
+ .option('--tag <name>', 'Filter by tag')
308
+ .option('--json', 'Output JSON', false)
309
+ .action((opts) => {
310
+ const { getRegistry } = require('../lib/workflow-registry');
311
+ const registry = getRegistry({ force: true });
312
+
313
+ const showBuiltIn = !opts.community && !opts.official;
314
+ const showOfficial = !opts.builtIn && !opts.community;
315
+ const showCommunity = !opts.builtIn && !opts.official;
316
+
317
+ if (opts.json) {
318
+ const out = {};
319
+ if (showBuiltIn) out.builtIn = registry.builtIn;
320
+ if (showOfficial) out.official = registry.official.filter(c => c.errors.length === 0);
321
+ if (showCommunity) out.community = registry.community.filter(c => c.errors.length === 0);
322
+ console.log(JSON.stringify(out, null, 2));
275
323
  return;
276
324
  }
277
325
 
278
- console.log();
279
- console.log(pc.bold('Built-in workflow templates:'));
280
- console.log();
281
- for (const wf of workflows) {
282
- console.log(` ${pc.cyan(wf.name.padEnd(28))} ${wf.description}`);
326
+ /**
327
+ * Display a list of package-based workflows (official or community).
328
+ */
329
+ function displayPackageList(items, label, emptyHint) {
330
+ let filtered = items.filter(c => c.errors.length === 0);
331
+ if (opts.category) {
332
+ filtered = filtered.filter(c => (c.pkg?.vai?.category || 'utility') === opts.category);
333
+ }
334
+ if (opts.tag) {
335
+ filtered = filtered.filter(c => (c.pkg?.vai?.tags || []).includes(opts.tag));
336
+ }
337
+
338
+ console.log();
339
+ console.log(pc.bold(`${label} (${filtered.length})`));
340
+ if (filtered.length === 0) {
341
+ console.log(pc.dim(` (${emptyHint})`));
342
+ } else {
343
+ for (const wf of filtered) {
344
+ const pkg = wf.pkg || {};
345
+ const author = typeof pkg.author === 'string' ? pkg.author : pkg.author?.name || '';
346
+ const tags = (pkg.vai?.tags || []).join(' · ');
347
+ console.log(` ${pc.cyan(wf.name.padEnd(42))} ${pkg.description || ''}`);
348
+ if (author || tags) {
349
+ console.log(` ${pc.dim(`by ${author}`)}${pkg.version ? pc.dim(` | v${pkg.version}`) : ''}${tags ? pc.dim(` | ${tags}`) : ''}`);
350
+ }
351
+ }
352
+ }
353
+
354
+ // Show invalid packages as warnings
355
+ const invalid = items.filter(c => c.errors.length > 0);
356
+ if (invalid.length > 0) {
357
+ console.log();
358
+ for (const inv of invalid) {
359
+ console.error(` ${pc.yellow('⚠')} ${inv.name}: ${inv.errors[0]}`);
360
+ }
361
+ }
362
+ }
363
+
364
+ // Built-in
365
+ if (showBuiltIn) {
366
+ console.log();
367
+ console.log(pc.bold(`Built-in Workflows (${registry.builtIn.length})`));
368
+ if (registry.builtIn.length === 0) {
369
+ console.log(pc.dim(' (none)'));
370
+ } else {
371
+ for (const wf of registry.builtIn) {
372
+ console.log(` ${pc.cyan(wf.name.padEnd(28))} ${wf.description}`);
373
+ }
374
+ }
283
375
  }
376
+
377
+ // Official Catalog (@vaicli)
378
+ if (showOfficial) {
379
+ displayPackageList(registry.official, 'Official Catalog (@vaicli)', 'none installed');
380
+ }
381
+
382
+ // Community
383
+ if (showCommunity) {
384
+ displayPackageList(registry.community, 'Community Workflows', 'none installed — install with: vai workflow install <package-name>');
385
+ }
386
+
284
387
  console.log();
285
388
  console.log(pc.dim('Run with: vai workflow run <name> --input key=value'));
286
389
  console.log();
287
390
  });
288
391
 
392
+ // ── workflow install ──
393
+ wfCmd
394
+ .command('install <package>')
395
+ .description('Install a workflow from npm')
396
+ .option('--global', 'Install globally', false)
397
+ .option('--json', 'Output JSON', false)
398
+ .action(async (packageName, opts) => {
399
+ const { installPackage, WORKFLOW_PREFIX, isWorkflowPackage, isOfficialPackage } = require('../lib/npm-utils');
400
+ const { validatePackage, clearRegistryCache } = require('../lib/workflow-registry');
401
+
402
+ // Auto-prefix if needed (but not for scoped packages)
403
+ if (!packageName.startsWith('@') && !packageName.startsWith(WORKFLOW_PREFIX)) {
404
+ packageName = WORKFLOW_PREFIX + packageName;
405
+ }
406
+
407
+ console.log(`Installing ${pc.cyan(packageName)}...`);
408
+
409
+ try {
410
+ const result = installPackage(packageName, { global: opts.global });
411
+ console.log(`${pc.green('✔')} Downloaded ${pc.cyan(packageName)}@${result.version}`);
412
+
413
+ // Validate
414
+ if (result.path) {
415
+ const validation = validatePackage(result.path);
416
+ if (validation.errors.length === 0) {
417
+ const steps = validation.definition?.steps?.length || 0;
418
+ const tools = (validation.pkg?.vai?.tools || []).join(', ');
419
+ console.log(`${pc.green('✔')} Validated workflow definition (${steps} steps${tools ? `, tools: ${tools}` : ''})`);
420
+ } else {
421
+ console.log(`${pc.yellow('⚠')} Validation issues:`);
422
+ for (const e of validation.errors) {
423
+ console.log(` ${pc.yellow('-')} ${e}`);
424
+ }
425
+ console.log();
426
+ console.log(pc.dim('The package was installed but the workflow may not execute correctly.'));
427
+ }
428
+ for (const w of validation.warnings) {
429
+ console.log(`${pc.yellow('⚠')} ${w}`);
430
+ }
431
+ }
432
+
433
+ clearRegistryCache();
434
+
435
+ if (opts.json) {
436
+ console.log(JSON.stringify(result, null, 2));
437
+ } else {
438
+ console.log();
439
+ console.log(`Installed. Run with:`);
440
+ console.log(` ${pc.cyan(`vai workflow run ${packageName}`)} --input key=value`);
441
+ }
442
+ } catch (err) {
443
+ console.error(ui.error(err.message));
444
+ process.exit(1);
445
+ }
446
+ });
447
+
448
+ // ── workflow uninstall ──
449
+ wfCmd
450
+ .command('uninstall <package>')
451
+ .description('Remove a workflow package')
452
+ .option('--global', 'Uninstall globally', false)
453
+ .action((packageName, opts) => {
454
+ const { uninstallPackage, WORKFLOW_PREFIX } = require('../lib/npm-utils');
455
+ const { clearRegistryCache } = require('../lib/workflow-registry');
456
+
457
+ if (!packageName.startsWith('@') && !packageName.startsWith(WORKFLOW_PREFIX)) {
458
+ packageName = WORKFLOW_PREFIX + packageName;
459
+ }
460
+
461
+ console.log(`Uninstalling ${pc.cyan(packageName)}...`);
462
+
463
+ try {
464
+ uninstallPackage(packageName, { global: opts.global });
465
+ clearRegistryCache();
466
+ console.log(`${pc.green('✔')} Removed ${packageName}`);
467
+ } catch (err) {
468
+ console.error(ui.error(err.message));
469
+ process.exit(1);
470
+ }
471
+ });
472
+
473
+ // ── workflow search ──
474
+ wfCmd
475
+ .command('search <query>')
476
+ .description('Search npm for community workflows')
477
+ .option('--limit <n>', 'Maximum results', '10')
478
+ .option('--json', 'Output JSON', false)
479
+ .action(async (query, opts) => {
480
+ const { searchNpm } = require('../lib/npm-utils');
481
+
482
+ console.log(`Searching npm for vai-workflow packages matching "${query}"...`);
483
+ console.log();
484
+
485
+ try {
486
+ const results = await searchNpm(query, { limit: parseInt(opts.limit, 10) });
487
+
488
+ if (opts.json) {
489
+ console.log(JSON.stringify(results, null, 2));
490
+ return;
491
+ }
492
+
493
+ if (results.length === 0) {
494
+ console.log(pc.dim(' No matching workflow packages found.'));
495
+ console.log();
496
+ return;
497
+ }
498
+
499
+ for (const r of results) {
500
+ const badge = r.official ? ` ${pc.green('[OFFICIAL]')}` : '';
501
+ console.log(` ${pc.cyan(r.name)} ${pc.dim(`v${r.version}`)}${badge}`);
502
+ if (r.description) console.log(` ${r.description}`);
503
+ console.log(` ${pc.dim(`by ${r.author}`)}${r.keywords.length ? pc.dim(` | ${r.keywords.slice(0, 5).join(', ')}`) : ''}`);
504
+ console.log();
505
+ }
506
+
507
+ console.log(pc.dim(`Install: vai workflow install <package-name>`));
508
+ console.log();
509
+ } catch (err) {
510
+ console.error(ui.error(err.message));
511
+ process.exit(1);
512
+ }
513
+ });
514
+
515
+ // ── workflow info ──
516
+ wfCmd
517
+ .command('info <name>')
518
+ .description('Show detailed info about an installed workflow')
519
+ .option('--json', 'Output JSON', false)
520
+ .action((name, opts) => {
521
+ const { resolveWorkflow, getRegistry } = require('../lib/workflow-registry');
522
+ const { WORKFLOW_PREFIX } = require('../lib/npm-utils');
523
+
524
+ try {
525
+ const resolved = resolveWorkflow(name);
526
+
527
+ if (opts.json) {
528
+ console.log(JSON.stringify({ source: resolved.source, definition: resolved.definition, metadata: resolved.metadata }, null, 2));
529
+ return;
530
+ }
531
+
532
+ const def = resolved.definition;
533
+ console.log();
534
+
535
+ if (resolved.source === 'community' || resolved.source === 'official') {
536
+ const pkg = resolved.metadata?.package || {};
537
+ const author = typeof pkg.author === 'string' ? pkg.author : pkg.author?.name || 'unknown';
538
+ const vai = pkg.vai || {};
539
+
540
+ console.log(`${pc.bold(pc.cyan(pkg.name || name))} ${pc.dim(`v${pkg.version || '?'}`)}`);
541
+ console.log(` ${pkg.description || def.description || ''}`);
542
+ console.log();
543
+ console.log(` ${pc.dim('Author:')} ${author}`);
544
+ console.log(` ${pc.dim('License:')} ${pkg.license || 'unknown'}`);
545
+ console.log(` ${pc.dim('Category:')} ${vai.category || 'utility'}`);
546
+ if (vai.tags?.length) console.log(` ${pc.dim('Tags:')} ${vai.tags.join(', ')}`);
547
+ if (vai.minVaiVersion) console.log(` ${pc.dim('Min vai:')} v${vai.minVaiVersion}`);
548
+ if (vai.tools?.length) console.log(` ${pc.dim('Tools:')} ${vai.tools.join(', ')}`);
549
+ console.log(` ${pc.dim('Steps:')} ${def.steps?.length || 0}`);
550
+ console.log(` ${pc.dim('Source:')} ${resolved.metadata?.path || 'unknown'}`);
551
+ if (pkg.name) console.log(` ${pc.dim('npm:')} https://www.npmjs.com/package/${pkg.name}`);
552
+ } else {
553
+ console.log(`${pc.bold(pc.cyan(def.name || name))} ${pc.dim(`[${resolved.source}]`)}`);
554
+ console.log(` ${def.description || ''}`);
555
+ console.log(` ${pc.dim('Steps:')} ${def.steps?.length || 0}`);
556
+ }
557
+
558
+ // Show inputs
559
+ if (def.inputs && Object.keys(def.inputs).length > 0) {
560
+ console.log();
561
+ console.log(` ${pc.bold('Inputs:')}`);
562
+ for (const [key, schema] of Object.entries(def.inputs)) {
563
+ const req = schema.required ? pc.red('(required)') : pc.dim(`(default: ${schema.default ?? 'none'})`);
564
+ const desc = schema.description || '';
565
+ console.log(` ${pc.cyan(key.padEnd(16))} ${(schema.type || 'string').padEnd(8)} ${req} ${pc.dim(desc)}`);
566
+ }
567
+ }
568
+
569
+ console.log();
570
+ } catch (err) {
571
+ console.error(ui.error(err.message));
572
+ process.exit(1);
573
+ }
574
+ });
575
+
576
+ // ── workflow create ──
577
+ wfCmd
578
+ .command('create')
579
+ .description('Scaffold a publish-ready npm package from a workflow')
580
+ .option('--from <file>', 'Existing workflow JSON to package')
581
+ .option('--name <name>', 'Package name (without vai-workflow- prefix)')
582
+ .option('--author <name>', 'Author name')
583
+ .option('--description <desc>', 'Package description')
584
+ .option('--category <cat>', 'Category (retrieval, analysis, ingestion, domain-specific, utility, integration)')
585
+ .option('--scope <scope>', 'Package scope (e.g. "vaicli" for @vaicli/vai-workflow-*)')
586
+ .option('--output <dir>', 'Output directory')
587
+ .action(async (opts) => {
588
+ const { scaffoldPackage, toPackageName, CATEGORIES, emptyWorkflowTemplate } = require('../lib/workflow-scaffold');
589
+ const { loadWorkflow } = require('../lib/workflow');
590
+
591
+ let definition;
592
+ let name = opts.name;
593
+ let author = opts.author;
594
+ let description = opts.description;
595
+ let category = opts.category;
596
+
597
+ if (opts.from) {
598
+ // Package an existing workflow
599
+ try {
600
+ definition = loadWorkflow(opts.from);
601
+ } catch (err) {
602
+ console.error(ui.error(err.message));
603
+ process.exit(1);
604
+ }
605
+ if (!name) {
606
+ name = definition.name || path.basename(opts.from, '.json').replace('.vai-workflow', '');
607
+ }
608
+ if (!description) {
609
+ description = definition.description;
610
+ }
611
+ } else if (process.stdin.isTTY) {
612
+ // Interactive mode
613
+ try {
614
+ const p = require('@clack/prompts');
615
+ p.intro(pc.bold('Create a new workflow package'));
616
+
617
+ const answers = await p.group({
618
+ name: () => p.text({ message: 'Workflow name', placeholder: 'my-workflow', validate: v => v ? undefined : 'Required' }),
619
+ description: () => p.text({ message: 'Description', placeholder: 'A brief description of what this workflow does' }),
620
+ category: () => p.select({
621
+ message: 'Category',
622
+ options: CATEGORIES.map(c => ({ value: c, label: c })),
623
+ }),
624
+ author: () => p.text({ message: 'Author', placeholder: 'Your Name', defaultValue: getGitAuthor() }),
625
+ });
626
+
627
+ if (p.isCancel(answers)) {
628
+ p.cancel('Cancelled.');
629
+ process.exit(0);
630
+ }
631
+
632
+ name = answers.name;
633
+ description = answers.description;
634
+ category = answers.category;
635
+ author = answers.author;
636
+ definition = emptyWorkflowTemplate();
637
+ definition.name = name;
638
+ definition.description = description || '';
639
+ // Add a placeholder step so validation passes
640
+ definition.steps = [{
641
+ id: 'search',
642
+ tool: 'query',
643
+ name: 'Search',
644
+ inputs: { query: '{{ inputs.query }}' },
645
+ }];
646
+ definition.inputs = {
647
+ query: { type: 'string', required: true, description: 'Search query' },
648
+ };
649
+ } catch (err) {
650
+ console.error(ui.error(`Interactive mode failed: ${err.message}`));
651
+ process.exit(1);
652
+ }
653
+ } else {
654
+ console.error(ui.error('Provide --from <file> or run interactively (TTY required).'));
655
+ process.exit(1);
656
+ }
657
+
658
+ if (!name) {
659
+ console.error(ui.error('Workflow name is required. Use --name <name>.'));
660
+ process.exit(1);
661
+ }
662
+
663
+ try {
664
+ const result = scaffoldPackage({
665
+ definition,
666
+ name,
667
+ author,
668
+ description,
669
+ category,
670
+ scope: opts.scope,
671
+ outputDir: opts.output,
672
+ });
673
+
674
+ const pkgName = toPackageName(name, { scope: opts.scope });
675
+ console.log();
676
+ console.log(`${pc.green('✔')} Created ${pc.cyan(pkgName)}/`);
677
+ for (const f of result.files) {
678
+ console.log(` ${pc.dim('├──')} ${f}`);
679
+ }
680
+ console.log();
681
+ console.log('Next steps:');
682
+ console.log(` 1. ${opts.from ? '' : pc.dim('Edit workflow.json with your workflow definition')}${opts.from ? 'Review README.md' : ''}`);
683
+ console.log(` 2. cd ${pkgName}`);
684
+ console.log(` 3. npm publish`);
685
+ console.log();
686
+ } catch (err) {
687
+ console.error(ui.error(err.message));
688
+ process.exit(1);
689
+ }
690
+ });
691
+
289
692
  // ── workflow init ──
290
693
  wfCmd
291
694
  .command('init')
@@ -1478,6 +1478,87 @@ const concepts = {
1478
1478
  'vai workflow run multi-collection-search --input query="test" --dry-run',
1479
1479
  ],
1480
1480
  },
1481
+
1482
+ 'workflow-publishing': {
1483
+ title: 'Publishing vai Workflows to npm',
1484
+ summary: 'Share workflows as npm packages using the vai-workflow-* convention',
1485
+ content: [
1486
+ `${pc.bold('WHAT IS A WORKFLOW PACKAGE?')}`,
1487
+ ``,
1488
+ `A vai workflow package is an npm package containing a reusable workflow`,
1489
+ `definition. It follows the naming convention ${pc.cyan('vai-workflow-<name>')} and contains:`,
1490
+ ``,
1491
+ ` • ${pc.cyan('workflow.json')} — The workflow definition (same format as any vai workflow)`,
1492
+ ` • ${pc.cyan('package.json')} — npm metadata + vai-specific fields`,
1493
+ ` • ${pc.cyan('README.md')} — Usage instructions`,
1494
+ ``,
1495
+ `No JavaScript, no build step, no dependencies.`,
1496
+ ``,
1497
+ `${pc.bold('HOW TO PUBLISH')}`,
1498
+ ``,
1499
+ `Option 1: Package an existing workflow`,
1500
+ ``,
1501
+ ` ${pc.cyan('vai workflow create --from my-workflow.json --name my-workflow')}`,
1502
+ ``,
1503
+ ` This creates a ${pc.cyan('vai-workflow-my-workflow/')} directory with everything needed.`,
1504
+ ` Review it, then:`,
1505
+ ``,
1506
+ ` ${pc.cyan('cd vai-workflow-my-workflow')}`,
1507
+ ` ${pc.cyan('npm publish')}`,
1508
+ ``,
1509
+ `Option 2: Start from scratch`,
1510
+ ``,
1511
+ ` ${pc.cyan('vai workflow create --name my-workflow')}`,
1512
+ ``,
1513
+ ` This creates an interactive template you can fill in.`,
1514
+ ``,
1515
+ `${pc.bold('PACKAGE.JSON REQUIREMENTS')}`,
1516
+ ``,
1517
+ `Your package.json needs a ${pc.cyan('"vai"')} field with:`,
1518
+ ``,
1519
+ ` {`,
1520
+ ` "vai": {`,
1521
+ ` "workflowVersion": "1.0",`,
1522
+ ` "tools": ["query", "rerank"],`,
1523
+ ` "category": "retrieval"`,
1524
+ ` }`,
1525
+ ` }`,
1526
+ ``,
1527
+ `${pc.cyan('vai workflow create')} fills this in automatically.`,
1528
+ ``,
1529
+ `${pc.bold('NAMING')}`,
1530
+ ``,
1531
+ `Package names must start with ${pc.cyan('vai-workflow-')} followed by a lowercase, hyphenated name:`,
1532
+ ``,
1533
+ ` ${pc.green('✓')} vai-workflow-legal-research`,
1534
+ ` ${pc.green('✓')} vai-workflow-code-review`,
1535
+ ` ${pc.red('✗')} vai-legal-workflow (wrong prefix)`,
1536
+ ``,
1537
+ `${pc.bold('CATEGORIES')}`,
1538
+ ``,
1539
+ ` ${pc.cyan('retrieval')} — Search and retrieval pipelines`,
1540
+ ` ${pc.cyan('analysis')} — Comparison, consistency checking`,
1541
+ ` ${pc.cyan('ingestion')} — Document processing and storage`,
1542
+ ` ${pc.cyan('domain-specific')} — Industry-focused (legal, clinical, etc.)`,
1543
+ ` ${pc.cyan('utility')} — Cost estimation, benchmarking`,
1544
+ ` ${pc.cyan('integration')} — Multi-system workflows`,
1545
+ ``,
1546
+ `${pc.bold('TESTING BEFORE PUBLISHING')}`,
1547
+ ``,
1548
+ ` ${pc.cyan('vai workflow validate ./vai-workflow-my-workflow/workflow.json')}`,
1549
+ ` ${pc.cyan('npm install ./vai-workflow-my-workflow')}`,
1550
+ ` ${pc.cyan('vai workflow run vai-workflow-my-workflow --dry-run')}`,
1551
+ ].join('\n'),
1552
+ links: [
1553
+ 'https://github.com/mrlynn/voyageai-cli',
1554
+ ],
1555
+ tryIt: [
1556
+ 'vai workflow create --from my-pipeline.json --name my-pipeline',
1557
+ 'vai workflow create',
1558
+ 'vai workflow search legal',
1559
+ 'vai workflow install vai-workflow-legal-research',
1560
+ ],
1561
+ },
1481
1562
  };
1482
1563
 
1483
1564
  /**
@@ -1651,6 +1732,13 @@ const aliases = {
1651
1732
  'vai-workflow': 'workflows',
1652
1733
  pipeline: 'workflows',
1653
1734
  dag: 'workflows',
1735
+ // Workflow publishing aliases
1736
+ 'workflow-publishing': 'workflow-publishing',
1737
+ 'publish-workflow': 'workflow-publishing',
1738
+ 'workflow-registry': 'workflow-publishing',
1739
+ 'community-workflows': 'workflow-publishing',
1740
+ 'share-workflow': 'workflow-publishing',
1741
+ 'npm-workflow': 'workflow-publishing',
1654
1742
  };
1655
1743
 
1656
1744
  /**