@synergenius/flow-weaver 0.15.2 → 0.17.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/cli/index.js CHANGED
@@ -5,7 +5,9 @@
5
5
  * Note: Shebang is added by build script (scripts/build-cli.ts) to the CJS bundle.
6
6
  * Do not add #!/usr/bin/env node here - it will cause duplicate shebangs.
7
7
  */
8
- import { Command } from 'commander';
8
+ // Must be imported first: sets up env vars before picocolors reads them
9
+ import './env-setup.js';
10
+ import { Command, Option } from 'commander';
9
11
  import { compileCommand } from './commands/compile.js';
10
12
  import { createWorkflowCommand, createNodeCommand } from './commands/create.js';
11
13
  import { describeCommand } from './commands/describe.js';
@@ -45,9 +47,24 @@ const program = new Command();
45
47
  program
46
48
  .name('flow-weaver')
47
49
  .description('Flow Weaver Annotations - Compile and validate workflow files')
48
- .version(version, '-v, --version', 'Output the current version');
50
+ .option('-v, --version', 'Output the current version')
51
+ .option('--no-color', 'Disable colors')
52
+ .option('--color', 'Force colors')
53
+ .on('option:version', () => {
54
+ logger.banner(version);
55
+ process.exit(0);
56
+ })
57
+ .configureHelp({
58
+ sortSubcommands: false,
59
+ subcommandTerm: (cmd) => cmd.name() + (cmd.usage() ? ' ' + cmd.usage() : ''),
60
+ });
61
+ // Track whether our action handler already printed the error,
62
+ // so Commander's own error handling doesn't duplicate it.
63
+ let actionErrorHandled = false;
49
64
  program.configureOutput({
50
65
  writeErr: (str) => {
66
+ if (actionErrorHandled)
67
+ return;
51
68
  const trimmed = str.replace(/^error:\s*/i, '').trimEnd();
52
69
  if (trimmed) {
53
70
  logger.error(trimmed);
@@ -55,6 +72,22 @@ program.configureOutput({
55
72
  },
56
73
  writeOut: (str) => process.stdout.write(str),
57
74
  });
75
+ /**
76
+ * Wraps a command action with centralized error handling.
77
+ * Catches errors, prints them once, sets the flag to prevent Commander duplication.
78
+ */
79
+ function wrapAction(fn) {
80
+ return async (...args) => {
81
+ try {
82
+ await fn(...args);
83
+ }
84
+ catch (error) {
85
+ actionErrorHandled = true;
86
+ logger.error(getErrorMessage(error));
87
+ process.exit(1);
88
+ }
89
+ };
90
+ }
58
91
  // Compile command
59
92
  program
60
93
  .command('compile <input>')
@@ -64,8 +97,8 @@ program
64
97
  .option('-s, --source-map', 'Generate source maps', false)
65
98
  .option('--verbose', 'Verbose output', false)
66
99
  .option('--dry-run', 'Preview compilation without writing files', false)
67
- .option('-w, --workflow-name <name>', 'Specific workflow name to compile')
68
- .option('-f, --format <format>', 'Module format: esm, cjs, or auto (default: auto)', 'auto')
100
+ .option('-w, --workflow <name>', 'Specific workflow name to compile')
101
+ .addOption(new Option('-f, --format <format>', 'Module format').choices(['esm', 'cjs', 'auto']).default('auto'))
69
102
  .option('--strict', 'Treat type coercion warnings as errors', false)
70
103
  .option('--inline-runtime', 'Force inline runtime even when @synergenius/flow-weaver package is installed', false)
71
104
  .option('--clean', 'Omit redundant @param/@returns annotations from compiled output', false)
@@ -76,15 +109,11 @@ program
76
109
  .option('--typed-events', 'Generate Zod event schemas from workflow @param annotations')
77
110
  .option('--retries <n>', 'Number of retries per function (Inngest target only)', parseInt)
78
111
  .option('--timeout <duration>', 'Function timeout (e.g. "30m", "1h")')
79
- .action(async (input, options) => {
80
- try {
81
- await compileCommand(input, options);
82
- }
83
- catch (error) {
84
- logger.error(`Command failed: ${getErrorMessage(error)}`);
85
- process.exit(1);
86
- }
87
- });
112
+ .action(wrapAction(async (input, options) => {
113
+ if (options.workflow)
114
+ options.workflowName = options.workflow;
115
+ await compileCommand(input, options);
116
+ }));
88
117
  // Strip command
89
118
  program
90
119
  .command('strip <input>')
@@ -92,73 +121,55 @@ program
92
121
  .option('-o, --output <path>', 'Output directory (default: in-place)')
93
122
  .option('--dry-run', 'Preview without writing', false)
94
123
  .option('--verbose', 'Verbose output', false)
95
- .action(async (input, options) => {
96
- try {
97
- await stripCommand(input, options);
98
- }
99
- catch (error) {
100
- logger.error(`Command failed: ${getErrorMessage(error)}`);
101
- process.exit(1);
102
- }
103
- });
124
+ .action(wrapAction(async (input, options) => {
125
+ await stripCommand(input, options);
126
+ }));
104
127
  // Describe command
105
128
  program
106
129
  .command('describe <input>')
107
130
  .description('Output workflow structure in LLM-friendly formats (JSON, text, mermaid)')
108
- .option('-f, --format <format>', 'Output format: json (default), text, mermaid, paths, ascii, ascii-compact', 'json')
131
+ .addOption(new Option('-f, --format <format>', 'Output format').choices(['json', 'text', 'mermaid', 'paths', 'ascii', 'ascii-compact']).default('json'))
109
132
  .option('-n, --node <id>', 'Focus on a specific node')
110
133
  .option('--compile', 'Also update runtime markers in the source file')
111
- .option('-w, --workflow-name <name>', 'Specific workflow name to describe')
112
- .action(async (input, options) => {
113
- try {
114
- await describeCommand(input, options);
115
- }
116
- catch (error) {
117
- logger.error(`Command failed: ${getErrorMessage(error)}`);
118
- process.exit(1);
119
- }
120
- });
134
+ .option('-w, --workflow <name>', 'Specific workflow name to describe')
135
+ .action(wrapAction(async (input, options) => {
136
+ if (options.workflow)
137
+ options.workflowName = options.workflow;
138
+ await describeCommand(input, options);
139
+ }));
121
140
  // Diagram command
122
141
  program
123
142
  .command('diagram <input>')
124
143
  .description('Generate SVG or interactive HTML diagram of a workflow')
125
- .option('-t, --theme <theme>', 'Color theme: dark (default), light', 'dark')
126
- .option('-w, --width <pixels>', 'SVG width in pixels')
144
+ .option('-t, --theme <theme>', 'Color theme: dark, light', 'dark')
145
+ .option('--width <pixels>', 'SVG width in pixels')
127
146
  .option('-p, --padding <pixels>', 'Canvas padding in pixels')
128
147
  .option('--no-port-labels', 'Hide data type labels on ports')
129
- .option('--workflow-name <name>', 'Specific workflow to render')
130
- .option('-f, --format <format>', 'Output format: svg (default), html, ascii, ascii-compact, text', 'svg')
148
+ .option('-w, --workflow <name>', 'Specific workflow to render')
149
+ .addOption(new Option('-f, --format <format>', 'Output format').choices(['svg', 'html', 'ascii', 'ascii-compact', 'text']).default('svg'))
131
150
  .option('-o, --output <file>', 'Write output to file instead of stdout')
132
- .action(async (input, options) => {
133
- try {
134
- if (options.width)
135
- options.width = Number(options.width);
136
- if (options.padding)
137
- options.padding = Number(options.padding);
138
- options.showPortLabels = options.portLabels;
139
- await diagramCommand(input, options);
140
- }
141
- catch (error) {
142
- logger.error(`Command failed: ${getErrorMessage(error)}`);
143
- process.exit(1);
144
- }
145
- });
151
+ .action(wrapAction(async (input, options) => {
152
+ if (options.width)
153
+ options.width = Number(options.width);
154
+ if (options.padding)
155
+ options.padding = Number(options.padding);
156
+ options.showPortLabels = options.portLabels;
157
+ if (options.workflow)
158
+ options.workflowName = options.workflow;
159
+ await diagramCommand(input, options);
160
+ }));
146
161
  // Diff command
147
162
  program
148
163
  .command('diff <file1> <file2>')
149
164
  .description('Compare two workflow files semantically')
150
- .option('-f, --format <format>', 'Output format: text (default), json, compact', 'text')
151
- .option('-w, --workflow-name <name>', 'Specific workflow name to compare')
165
+ .addOption(new Option('-f, --format <format>', 'Output format').choices(['text', 'json', 'compact']).default('text'))
166
+ .option('-w, --workflow <name>', 'Specific workflow name to compare')
152
167
  .option('--exit-zero', 'Exit 0 even when differences are found', false)
153
- .action(async (file1, file2, options) => {
154
- try {
155
- await diffCommand(file1, file2, options);
156
- }
157
- catch (error) {
158
- logger.error(`Command failed: ${getErrorMessage(error)}`);
159
- process.exit(1);
160
- }
161
- });
168
+ .action(wrapAction(async (file1, file2, options) => {
169
+ if (options.workflow)
170
+ options.workflowName = options.workflow;
171
+ await diffCommand(file1, file2, options);
172
+ }));
162
173
  // Validate command
163
174
  program
164
175
  .command('validate <input>')
@@ -166,54 +177,43 @@ program
166
177
  .option('--verbose', 'Verbose output', false)
167
178
  .option('-q, --quiet', 'Suppress warnings', false)
168
179
  .option('--json', 'Output results as JSON', false)
169
- .option('-w, --workflow-name <name>', 'Specific workflow name to validate')
180
+ .option('-w, --workflow <name>', 'Specific workflow name to validate')
170
181
  .option('--strict', 'Treat type coercion warnings as errors', false)
171
- .action(async (input, options) => {
172
- try {
173
- await validateCommand(input, options);
174
- }
175
- catch (error) {
176
- logger.error(`Command failed: ${getErrorMessage(error)}`);
177
- process.exit(1);
178
- }
179
- });
182
+ .action(wrapAction(async (input, options) => {
183
+ if (options.workflow)
184
+ options.workflowName = options.workflow;
185
+ await validateCommand(input, options);
186
+ }));
180
187
  // Doctor command
181
188
  program
182
189
  .command('doctor')
183
190
  .description('Check project environment and configuration for flow-weaver compatibility')
184
191
  .option('--json', 'Output results as JSON', false)
185
- .action(async (options) => {
186
- try {
187
- await doctorCommand(options);
188
- }
189
- catch (error) {
190
- logger.error(`Command failed: ${getErrorMessage(error)}`);
191
- process.exit(1);
192
- }
193
- });
192
+ .action(wrapAction(async (options) => {
193
+ await doctorCommand(options);
194
+ }));
194
195
  // Init command
195
196
  program
196
197
  .command('init [directory]')
197
198
  .description('Create a new flow-weaver project')
198
199
  .option('-n, --name <name>', 'Project name (defaults to directory name)')
199
- .option('-t, --template <template>', 'Workflow template (default: simple)')
200
+ .option('-t, --template <template>', 'Workflow template (default: sequential)')
200
201
  .option('-f, --format <format>', 'Module format: esm or cjs (default: esm)')
201
202
  .option('-y, --yes', 'Skip prompts and use defaults', false)
203
+ .option('--preset <persona>', 'User preset: nocode, vibecoder, lowcode, expert')
204
+ .option('--use-case <category>', 'Use case: data, ai, api, automation, cicd, minimal')
205
+ .option('--mcp', 'Auto-configure MCP for AI editors after scaffolding')
206
+ .option('--no-mcp', 'Skip MCP setup prompt')
207
+ .option('--no-agent', 'Skip post-init agent launch prompt')
202
208
  .option('--install', 'Run npm install after scaffolding')
203
209
  .option('--no-install', 'Skip npm install')
204
210
  .option('--git', 'Initialize a git repository')
205
211
  .option('--no-git', 'Skip git init')
206
212
  .option('--force', 'Overwrite existing files', false)
207
213
  .option('--json', 'Output results as JSON', false)
208
- .action(async (directory, options) => {
209
- try {
210
- await initCommand(directory, options);
211
- }
212
- catch (error) {
213
- logger.error(`Command failed: ${getErrorMessage(error)}`);
214
- process.exit(1);
215
- }
216
- });
214
+ .action(wrapAction(async (directory, options) => {
215
+ await initCommand(directory, options);
216
+ }));
217
217
  // Watch command
218
218
  program
219
219
  .command('watch <input>')
@@ -222,17 +222,13 @@ program
222
222
  .option('-p, --production', 'Generate production code (no debug events)', false)
223
223
  .option('-s, --source-map', 'Generate source maps', false)
224
224
  .option('--verbose', 'Verbose output', false)
225
- .option('-w, --workflow-name <name>', 'Specific workflow name to compile')
226
- .option('-f, --format <format>', 'Module format: esm, cjs, or auto (default: auto)', 'auto')
227
- .action(async (input, options) => {
228
- try {
229
- await watchCommand(input, options);
230
- }
231
- catch (error) {
232
- logger.error(`Command failed: ${getErrorMessage(error)}`);
233
- process.exit(1);
234
- }
235
- });
225
+ .option('-w, --workflow <name>', 'Specific workflow name to compile')
226
+ .option('-f, --format <format>', 'Module format: esm, cjs, or auto', 'auto')
227
+ .action(wrapAction(async (input, options) => {
228
+ if (options.workflow)
229
+ options.workflowName = options.workflow;
230
+ await watchCommand(input, options);
231
+ }));
236
232
  // Dev command (watch + compile + run)
237
233
  program
238
234
  .command('dev <input>')
@@ -241,36 +237,24 @@ program
241
237
  .option('--params-file <path>', 'Path to JSON file with input parameters')
242
238
  .option('-w, --workflow <name>', 'Specific workflow name to run')
243
239
  .option('-p, --production', 'Run in production mode (no trace events)', false)
244
- .option('-f, --format <format>', 'Module format: esm, cjs, or auto (default: auto)', 'auto')
240
+ .option('-f, --format <format>', 'Module format: esm, cjs, or auto', 'auto')
245
241
  .option('--clean', 'Omit redundant @param/@returns annotations', false)
246
242
  .option('--once', 'Run once then exit', false)
247
243
  .option('--json', 'Output result as JSON', false)
248
244
  .option('--target <target>', 'Compilation target: typescript or inngest (default: typescript)')
249
245
  .option('--framework <framework>', 'Framework for serve handler (inngest target only)', 'express')
250
246
  .option('--port <port>', 'Port for dev server (inngest target only)', (v) => parseInt(v, 10), 3000)
251
- .action(async (input, options) => {
252
- try {
253
- await devCommand(input, options);
254
- }
255
- catch (error) {
256
- logger.error(`Command failed: ${getErrorMessage(error)}`);
257
- process.exit(1);
258
- }
259
- });
247
+ .action(wrapAction(async (input, options) => {
248
+ await devCommand(input, options);
249
+ }));
260
250
  // Listen command
261
251
  program
262
252
  .command('listen')
263
253
  .description('Connect to Studio and stream integration events as JSON lines')
264
254
  .option('-s, --server <url>', 'Studio URL', DEFAULT_SERVER_URL)
265
- .action(async (options) => {
266
- try {
267
- await listenCommand(options);
268
- }
269
- catch (error) {
270
- logger.error(`Command failed: ${getErrorMessage(error)}`);
271
- process.exit(1);
272
- }
273
- });
255
+ .action(wrapAction(async (options) => {
256
+ await listenCommand(options);
257
+ }));
274
258
  // Tunnel command
275
259
  program
276
260
  .command('tunnel')
@@ -278,30 +262,18 @@ program
278
262
  .requiredOption('-k, --key <apiKey>', 'API key for cloud authentication (fw_xxxx)')
279
263
  .option('-c, --cloud <url>', 'Cloud server URL', 'https://flowweaver.dev')
280
264
  .option('-d, --dir <path>', 'Project directory', process.cwd())
281
- .action(async (options) => {
282
- try {
283
- await tunnelCommand(options);
284
- }
285
- catch (error) {
286
- logger.error(`Command failed: ${getErrorMessage(error)}`);
287
- process.exit(1);
288
- }
289
- });
265
+ .action(wrapAction(async (options) => {
266
+ await tunnelCommand(options);
267
+ }));
290
268
  // MCP server command
291
269
  program
292
270
  .command('mcp-server')
293
271
  .description('Start MCP server for Claude Code integration')
294
272
  .option('-s, --server <url>', 'Studio URL', DEFAULT_SERVER_URL)
295
273
  .option('--stdio', 'Run in MCP stdio mode (skip interactive registration)')
296
- .action(async (options) => {
297
- try {
298
- await mcpServerCommand(options);
299
- }
300
- catch (error) {
301
- logger.error(`Command failed: ${getErrorMessage(error)}`);
302
- process.exit(1);
303
- }
304
- });
274
+ .action(wrapAction(async (options) => {
275
+ await mcpServerCommand(options);
276
+ }));
305
277
  // MCP setup command
306
278
  program
307
279
  .command('mcp-setup')
@@ -309,82 +281,46 @@ program
309
281
  .option('--tool <tools...>', 'Specific tools to configure (claude, cursor, vscode, windsurf, codex, openclaw)')
310
282
  .option('--all', 'Configure all detected tools without prompting')
311
283
  .option('--list', 'List detected tools without configuring')
312
- .action(async (options) => {
313
- try {
314
- await mcpSetupCommand(options);
315
- }
316
- catch (error) {
317
- logger.error(`Command failed: ${getErrorMessage(error)}`);
318
- process.exit(1);
319
- }
320
- });
284
+ .action(wrapAction(async (options) => {
285
+ await mcpSetupCommand(options);
286
+ }));
321
287
  // UI command group (send commands to Studio)
322
288
  const uiCmd = program.command('ui').description('Send commands to Studio');
323
289
  uiCmd
324
290
  .command('focus-node <nodeId>')
325
291
  .description('Select and center a node in Studio')
326
292
  .option('-s, --server <url>', 'Studio URL', DEFAULT_SERVER_URL)
327
- .action(async (nodeId, options) => {
328
- try {
329
- await uiFocusNode(nodeId, options);
330
- }
331
- catch (error) {
332
- logger.error(`Command failed: ${getErrorMessage(error)}`);
333
- process.exit(1);
334
- }
335
- });
293
+ .action(wrapAction(async (nodeId, options) => {
294
+ await uiFocusNode(nodeId, options);
295
+ }));
336
296
  uiCmd
337
297
  .command('add-node <nodeTypeName>')
338
298
  .description('Add a node at viewport center')
339
299
  .option('-s, --server <url>', 'Studio URL', DEFAULT_SERVER_URL)
340
- .action(async (nodeTypeName, options) => {
341
- try {
342
- await uiAddNode(nodeTypeName, options);
343
- }
344
- catch (error) {
345
- logger.error(`Command failed: ${getErrorMessage(error)}`);
346
- process.exit(1);
347
- }
348
- });
300
+ .action(wrapAction(async (nodeTypeName, options) => {
301
+ await uiAddNode(nodeTypeName, options);
302
+ }));
349
303
  uiCmd
350
304
  .command('open-workflow <filePath>')
351
305
  .description('Open a workflow file in Studio')
352
306
  .option('-s, --server <url>', 'Studio URL', DEFAULT_SERVER_URL)
353
- .action(async (filePath, options) => {
354
- try {
355
- await uiOpenWorkflow(filePath, options);
356
- }
357
- catch (error) {
358
- logger.error(`Command failed: ${getErrorMessage(error)}`);
359
- process.exit(1);
360
- }
361
- });
307
+ .action(wrapAction(async (filePath, options) => {
308
+ await uiOpenWorkflow(filePath, options);
309
+ }));
362
310
  uiCmd
363
311
  .command('get-state')
364
312
  .description('Return current workflow state from Studio')
365
313
  .option('-s, --server <url>', 'Studio URL', DEFAULT_SERVER_URL)
366
- .action(async (options) => {
367
- try {
368
- await uiGetState(options);
369
- }
370
- catch (error) {
371
- logger.error(`Command failed: ${getErrorMessage(error)}`);
372
- process.exit(1);
373
- }
374
- });
314
+ .action(wrapAction(async (options) => {
315
+ await uiGetState(options);
316
+ }));
375
317
  uiCmd
376
318
  .command('batch <json>')
377
319
  .description('Execute a batch of commands with auto-snapshot rollback')
378
320
  .option('-s, --server <url>', 'Studio URL', DEFAULT_SERVER_URL)
379
- .action(async (json, options) => {
380
- try {
381
- await uiBatch(json, options);
382
- }
383
- catch (error) {
384
- logger.error(`Command failed: ${getErrorMessage(error)}`);
385
- process.exit(1);
386
- }
387
- });
321
+ .action(wrapAction(async (json, options) => {
322
+ await uiBatch(json, options);
323
+ }));
388
324
  // Create command (with subcommands)
389
325
  const createCmd = program.command('create').description('Create workflows or nodes from templates');
390
326
  createCmd
@@ -400,91 +336,55 @@ createCmd
400
336
  .option('--nodes <names>', 'Comma-separated node function names (e.g., "fetch,parse,store")')
401
337
  .option('--input <name>', 'Custom input port name (default: "data")')
402
338
  .option('--output <name>', 'Custom output port name (default: "result")')
403
- .action(async (template, file, options) => {
404
- try {
405
- await createWorkflowCommand(template, file, options);
406
- }
407
- catch (error) {
408
- logger.error(`Command failed: ${getErrorMessage(error)}`);
409
- process.exit(1);
410
- }
411
- });
339
+ .action(wrapAction(async (template, file, options) => {
340
+ await createWorkflowCommand(template, file, options);
341
+ }));
412
342
  createCmd
413
343
  .command('node <name> <file>')
414
- .description('Create a node type (uses processor template by default)')
344
+ .description('Create a node type from a template')
415
345
  .option('-l, --line <number>', 'Insert at specific line number', parseInt)
416
- .option('-t, --template <template>', 'Node template to use', 'processor')
346
+ .option('-t, --template <template>', 'Node template to use', 'transformer')
417
347
  .option('-p, --preview', 'Preview generated code without writing', false)
418
348
  .option('--strategy <strategy>', 'Template strategy (e.g. mock, callback, webhook)')
419
349
  .option('--config <json>', 'Additional configuration (JSON)')
420
- .action(async (name, file, options) => {
421
- try {
422
- await createNodeCommand(name, file, options);
423
- }
424
- catch (error) {
425
- logger.error(`Command failed: ${getErrorMessage(error)}`);
426
- process.exit(1);
427
- }
428
- });
350
+ .action(wrapAction(async (name, file, options) => {
351
+ await createNodeCommand(name, file, options);
352
+ }));
429
353
  // Templates command
430
354
  program
431
355
  .command('templates')
432
356
  .description('List available templates')
433
357
  .option('--json', 'Output as JSON', false)
434
- .action(async (options) => {
435
- try {
436
- await templatesCommand(options);
437
- }
438
- catch (error) {
439
- logger.error(`Command failed: ${getErrorMessage(error)}`);
440
- process.exit(1);
441
- }
442
- });
358
+ .action(wrapAction(async (options) => {
359
+ await templatesCommand(options);
360
+ }));
443
361
  // Grammar command
444
362
  program
445
363
  .command('grammar')
446
364
  .description('Output JSDoc annotation grammar (@input, @output, @connect, @node, @scope) as HTML railroad diagrams or EBNF text')
447
- .option('-f, --format <format>', 'Output format: html (default), ebnf', 'html')
365
+ .addOption(new Option('-f, --format <format>', 'Output format').choices(['html', 'ebnf']))
448
366
  .option('-o, --output <path>', 'Write output to file instead of stdout')
449
- .action(async (options) => {
450
- try {
451
- await grammarCommand(options);
452
- }
453
- catch (error) {
454
- logger.error(`Command failed: ${getErrorMessage(error)}`);
455
- process.exit(1);
456
- }
457
- });
367
+ .action(wrapAction(async (options) => {
368
+ await grammarCommand(options);
369
+ }));
458
370
  // Pattern command (with subcommands)
459
371
  const patternCmd = program.command('pattern').description('Work with reusable workflow patterns');
460
372
  patternCmd
461
373
  .command('list <path>')
462
374
  .description('List patterns in a file or directory')
463
375
  .option('--json', 'Output as JSON', false)
464
- .action(async (inputPath, options) => {
465
- try {
466
- await patternListCommand(inputPath, options);
467
- }
468
- catch (error) {
469
- logger.error(`Command failed: ${getErrorMessage(error)}`);
470
- process.exit(1);
471
- }
472
- });
376
+ .action(wrapAction(async (inputPath, options) => {
377
+ await patternListCommand(inputPath, options);
378
+ }));
473
379
  patternCmd
474
380
  .command('apply <pattern-file> <target-file>')
475
381
  .description('Apply a pattern to a workflow file')
476
382
  .option('-p, --preview', 'Preview changes without writing', false)
477
383
  .option('--prefix <prefix>', 'Prefix for node instance IDs')
478
384
  .option('-n, --name <name>', 'Specific pattern name to apply')
479
- .action(async (patternFile, targetFile, options) => {
480
- try {
481
- await patternApplyCommand(patternFile, targetFile, options);
482
- }
483
- catch (error) {
484
- logger.error(`Command failed: ${getErrorMessage(error)}`);
485
- process.exit(1);
486
- }
487
- });
385
+ .action(wrapAction(async (patternFile, targetFile, options) => {
386
+ await patternApplyCommand(patternFile, targetFile, options);
387
+ }));
488
388
  patternCmd
489
389
  .command('extract <source-file>')
490
390
  .description('Extract a pattern from workflow nodes')
@@ -492,15 +392,9 @@ patternCmd
492
392
  .requiredOption('-o, --output <file>', 'Output pattern file')
493
393
  .option('-n, --name <name>', 'Pattern name')
494
394
  .option('-p, --preview', 'Preview pattern without writing', false)
495
- .action(async (sourceFile, options) => {
496
- try {
497
- await patternExtractCommand(sourceFile, options);
498
- }
499
- catch (error) {
500
- logger.error(`Command failed: ${getErrorMessage(error)}`);
501
- process.exit(1);
502
- }
503
- });
395
+ .action(wrapAction(async (sourceFile, options) => {
396
+ await patternExtractCommand(sourceFile, options);
397
+ }));
504
398
  // Run command
505
399
  program
506
400
  .command('run <input>')
@@ -519,15 +413,9 @@ program
519
413
  .option('--checkpoint', 'Enable checkpointing to disk after each node')
520
414
  .option('--resume [file]', 'Resume from a checkpoint file (auto-detects latest if no file)')
521
415
  .option('-b, --breakpoint <nodeIds...>', 'Set initial breakpoints (repeatable)')
522
- .action(async (input, options) => {
523
- try {
524
- await runCommand(input, options);
525
- }
526
- catch (error) {
527
- logger.error(`Command failed: ${getErrorMessage(error)}`);
528
- process.exit(1);
529
- }
530
- });
416
+ .action(wrapAction(async (input, options) => {
417
+ await runCommand(input, options);
418
+ }));
531
419
  // Serve command
532
420
  program
533
421
  .command('serve [directory]')
@@ -539,28 +427,22 @@ program
539
427
  .option('--precompile', 'Precompile all workflows on startup', false)
540
428
  .option('--cors <origin>', 'CORS origin', '*')
541
429
  .option('--swagger', 'Enable Swagger UI at /docs', false)
542
- .action(async (directory, options) => {
543
- try {
544
- await serveCommand(directory, {
545
- port: parseInt(options.port, 10),
546
- host: options.host,
547
- watch: options.watch,
548
- production: options.production,
549
- precompile: options.precompile,
550
- cors: options.cors,
551
- swagger: options.swagger,
552
- });
553
- }
554
- catch (error) {
555
- logger.error(`Command failed: ${getErrorMessage(error)}`);
556
- process.exit(1);
557
- }
558
- });
430
+ .action(wrapAction(async (directory, options) => {
431
+ await serveCommand(directory, {
432
+ port: parseInt(options.port, 10),
433
+ host: options.host,
434
+ watch: options.watch,
435
+ production: options.production,
436
+ precompile: options.precompile,
437
+ cors: options.cors,
438
+ swagger: options.swagger,
439
+ });
440
+ }));
559
441
  // Export command
560
442
  program
561
443
  .command('export <input>')
562
444
  .description('Export workflow as serverless function')
563
- .requiredOption('-t, --target <target>', 'Target platform (lambda, vercel, cloudflare)')
445
+ .requiredOption('-t, --target <target>', 'Target platform (install target packs via marketplace)')
564
446
  .requiredOption('-o, --output <path>', 'Output directory')
565
447
  .option('-w, --workflow <name>', 'Specific workflow name to export')
566
448
  .option('-p, --production', 'Production mode', true)
@@ -569,15 +451,9 @@ program
569
451
  .option('--workflows <names>', 'Comma-separated list of workflows to export (used with --multi)')
570
452
  .option('--docs', 'Include API documentation routes (/docs and /openapi.json)', false)
571
453
  .option('--durable-steps', 'Use deep generator with per-node Inngest steps for durability (inngest target only)', false)
572
- .action(async (input, options) => {
573
- try {
574
- await exportCommand(input, options);
575
- }
576
- catch (error) {
577
- logger.error(`Command failed: ${getErrorMessage(error)}`);
578
- process.exit(1);
579
- }
580
- });
454
+ .action(wrapAction(async (input, options) => {
455
+ await exportCommand(input, options);
456
+ }));
581
457
  // OpenAPI command
582
458
  program
583
459
  .command('openapi <directory>')
@@ -586,17 +462,11 @@ program
586
462
  .option('--title <title>', 'API title', 'Flow Weaver API')
587
463
  .option('--version <version>', 'API version', '1.0.0')
588
464
  .option('--description <desc>', 'API description')
589
- .option('-f, --format <format>', 'Output format: json (default), yaml', 'json')
465
+ .option('-f, --format <format>', 'Output format: json, yaml', 'json')
590
466
  .option('--server <url>', 'Server URL')
591
- .action(async (directory, options) => {
592
- try {
593
- await openapiCommand(directory, options);
594
- }
595
- catch (error) {
596
- logger.error(`Command failed: ${getErrorMessage(error)}`);
597
- process.exit(1);
598
- }
599
- });
467
+ .action(wrapAction(async (directory, options) => {
468
+ await openapiCommand(directory, options);
469
+ }));
600
470
  // Plugin command group
601
471
  const pluginCmd = program.command('plugin').description('Scaffold and manage external plugins');
602
472
  pluginCmd
@@ -606,60 +476,40 @@ pluginCmd
606
476
  .option('--no-system', 'Skip generating a system module')
607
477
  .option('-p, --preview', 'Preview generated files without writing', false)
608
478
  .option('--force', 'Overwrite existing files', false)
609
- .action(async (name, options) => {
610
- try {
611
- await pluginInitCommand(name, options);
612
- }
613
- catch (error) {
614
- logger.error(`Command failed: ${getErrorMessage(error)}`);
615
- process.exit(1);
616
- }
617
- });
479
+ .action(wrapAction(async (name, options) => {
480
+ await pluginInitCommand(name, options);
481
+ }));
618
482
  // Migrate command
619
483
  program
620
484
  .command('migrate <glob>')
621
485
  .description('Migrate workflow files to current syntax via parse → regenerate round-trip')
622
486
  .option('--dry-run', 'Preview changes without writing files', false)
623
487
  .option('--diff', 'Show semantic diff before/after', false)
624
- .action(async (glob, options) => {
625
- try {
626
- await migrateCommand(glob, options);
627
- }
628
- catch (error) {
629
- logger.error(`Command failed: ${getErrorMessage(error)}`);
630
- process.exit(1);
631
- }
632
- });
488
+ .action(wrapAction(async (glob, options) => {
489
+ await migrateCommand(glob, options);
490
+ }));
633
491
  // Status command
634
492
  program
635
493
  .command('status <input>')
636
494
  .description('Report implementation progress for stub workflows')
637
- .option('-w, --workflow-name <name>', 'Specific workflow name')
495
+ .option('-w, --workflow <name>', 'Specific workflow name')
638
496
  .option('--json', 'Output as JSON', false)
639
- .action(async (input, options) => {
640
- try {
641
- await statusCommand(input, options);
642
- }
643
- catch (error) {
644
- logger.error(`Command failed: ${getErrorMessage(error)}`);
645
- process.exit(1);
646
- }
647
- });
497
+ .action(wrapAction(async (input, options) => {
498
+ if (options.workflow)
499
+ options.workflowName = options.workflow;
500
+ await statusCommand(input, options);
501
+ }));
648
502
  // Implement command
649
503
  program
650
504
  .command('implement <input> <node>')
651
505
  .description('Replace a stub node with a real function skeleton')
652
- .option('-w, --workflow-name <name>', 'Specific workflow name')
506
+ .option('-w, --workflow <name>', 'Specific workflow name')
653
507
  .option('-p, --preview', 'Preview the generated code without writing', false)
654
- .action(async (input, node, options) => {
655
- try {
656
- await implementCommand(input, node, options);
657
- }
658
- catch (error) {
659
- logger.error(`Command failed: ${getErrorMessage(error)}`);
660
- process.exit(1);
661
- }
662
- });
508
+ .action(wrapAction(async (input, node, options) => {
509
+ if (options.workflow)
510
+ options.workflowName = options.workflow;
511
+ await implementCommand(input, node, options);
512
+ }));
663
513
  // Changelog command
664
514
  program
665
515
  .command('changelog')
@@ -667,43 +517,31 @@ program
667
517
  .option('--last-tag', 'From last git tag to HEAD', false)
668
518
  .option('--since <date>', 'Date-based range (e.g., "2024-01-01")')
669
519
  .option('-r, --range <range>', 'Custom git range (e.g., "v0.1.0..HEAD")')
670
- .action(async (options) => {
671
- try {
672
- await changelogCommand(options);
673
- }
674
- catch (error) {
675
- logger.error(`Command failed: ${getErrorMessage(error)}`);
676
- process.exit(1);
677
- }
678
- });
520
+ .action(wrapAction(async (options) => {
521
+ await changelogCommand(options);
522
+ }));
679
523
  // Docs command: flow-weaver docs [topic] | flow-weaver docs search <query>
680
524
  program
681
525
  .command('docs [args...]')
682
526
  .description('Browse reference documentation')
683
527
  .option('--json', 'Output as JSON', false)
684
528
  .option('--compact', 'Return compact LLM-friendly version', false)
685
- .action(async (args, options) => {
686
- try {
687
- if (args.length === 0) {
688
- await docsListCommand(options);
689
- }
690
- else if (args[0] === 'search') {
691
- const query = args.slice(1).join(' ');
692
- if (!query) {
693
- logger.error('Usage: flow-weaver docs search <query>');
694
- process.exit(1);
695
- }
696
- await docsSearchCommand(query, options);
697
- }
698
- else {
699
- await docsReadCommand(args[0], options);
529
+ .action(wrapAction(async (args, options) => {
530
+ if (args.length === 0 || args[0] === 'list') {
531
+ await docsListCommand(options);
532
+ }
533
+ else if (args[0] === 'search') {
534
+ const query = args.slice(1).join(' ');
535
+ if (!query) {
536
+ logger.error('Usage: flow-weaver docs search <query>');
537
+ process.exit(1);
700
538
  }
539
+ await docsSearchCommand(query, options);
701
540
  }
702
- catch (error) {
703
- logger.error(`Command failed: ${getErrorMessage(error)}`);
704
- process.exit(1);
541
+ else {
542
+ await docsReadCommand(args[0], options);
705
543
  }
706
- });
544
+ }));
707
545
  // Context command: generate LLM context bundles
708
546
  program
709
547
  .command('context [preset]')
@@ -714,15 +552,9 @@ program
714
552
  .option('--no-grammar', 'Omit EBNF grammar section')
715
553
  .option('-o, --output <path>', 'Write to file instead of stdout')
716
554
  .option('--list', 'List available presets and exit')
717
- .action(async (preset, options) => {
718
- try {
719
- await contextCommand(preset, options);
720
- }
721
- catch (error) {
722
- logger.error(`Command failed: ${getErrorMessage(error)}`);
723
- process.exit(1);
724
- }
725
- });
555
+ .action(wrapAction(async (preset, options) => {
556
+ await contextCommand(preset, options);
557
+ }));
726
558
  // Marketplace command group
727
559
  const marketCmd = program.command('market').description('Discover, install, and publish marketplace packages');
728
560
  marketCmd
@@ -731,158 +563,77 @@ marketCmd
731
563
  .option('-d, --description <desc>', 'Package description')
732
564
  .option('-a, --author <author>', 'Author name')
733
565
  .option('-y, --yes', 'Skip prompts and use defaults', false)
734
- .action(async (name, options) => {
735
- try {
736
- await marketInitCommand(name, options);
737
- }
738
- catch (error) {
739
- logger.error(`Command failed: ${getErrorMessage(error)}`);
740
- process.exit(1);
741
- }
742
- });
566
+ .action(wrapAction(async (name, options) => {
567
+ await marketInitCommand(name, options);
568
+ }));
743
569
  marketCmd
744
570
  .command('pack [directory]')
745
571
  .description('Validate and generate flowweaver.manifest.json')
746
572
  .option('--json', 'Output results as JSON', false)
747
573
  .option('--verbose', 'Show parse warnings', false)
748
- .action(async (directory, options) => {
749
- try {
750
- await marketPackCommand(directory, options);
751
- }
752
- catch (error) {
753
- logger.error(`Command failed: ${getErrorMessage(error)}`);
754
- process.exit(1);
755
- }
756
- });
574
+ .action(wrapAction(async (directory, options) => {
575
+ await marketPackCommand(directory, options);
576
+ }));
757
577
  marketCmd
758
578
  .command('publish [directory]')
759
579
  .description('Pack and publish to npm')
760
580
  .option('--dry-run', 'Preview without publishing', false)
761
581
  .option('--tag <tag>', 'npm dist-tag')
762
- .action(async (directory, options) => {
763
- try {
764
- await marketPublishCommand(directory, options);
765
- }
766
- catch (error) {
767
- logger.error(`Command failed: ${getErrorMessage(error)}`);
768
- process.exit(1);
769
- }
770
- });
582
+ .action(wrapAction(async (directory, options) => {
583
+ await marketPublishCommand(directory, options);
584
+ }));
771
585
  marketCmd
772
586
  .command('install <package>')
773
587
  .description('Install a marketplace package')
774
588
  .option('--json', 'Output results as JSON', false)
775
- .action(async (packageSpec, options) => {
776
- try {
777
- await marketInstallCommand(packageSpec, options);
778
- }
779
- catch (error) {
780
- logger.error(`Command failed: ${getErrorMessage(error)}`);
781
- process.exit(1);
782
- }
783
- });
589
+ .action(wrapAction(async (packageSpec, options) => {
590
+ await marketInstallCommand(packageSpec, options);
591
+ }));
784
592
  marketCmd
785
593
  .command('search [query]')
786
594
  .description('Search npm for marketplace packages')
787
595
  .option('-l, --limit <number>', 'Max results', '20')
788
596
  .option('-r, --registry <url>', 'Custom registry search URL (e.g., private npm registry)')
789
597
  .option('--json', 'Output as JSON', false)
790
- .action(async (query, options) => {
791
- try {
792
- await marketSearchCommand(query, { ...options, limit: parseInt(options.limit, 10) });
793
- }
794
- catch (error) {
795
- logger.error(`Command failed: ${getErrorMessage(error)}`);
796
- process.exit(1);
797
- }
798
- });
598
+ .action(wrapAction(async (query, options) => {
599
+ await marketSearchCommand(query, { ...options, limit: parseInt(options.limit, 10) });
600
+ }));
799
601
  marketCmd
800
602
  .command('list')
801
603
  .description('List installed marketplace packages')
802
604
  .option('--json', 'Output as JSON', false)
803
- .action(async (options) => {
804
- try {
805
- await marketListCommand(options);
806
- }
807
- catch (error) {
808
- logger.error(`Command failed: ${getErrorMessage(error)}`);
809
- process.exit(1);
810
- }
811
- });
812
- // Help command (default commander behavior)
813
- program.on('--help', () => {
814
- logger.newline();
815
- logger.section('Examples');
816
- logger.log(' $ flow-weaver compile my-workflow.ts');
817
- logger.log(" $ flow-weaver compile '**/*.ts' -o .output");
818
- logger.log(' $ flow-weaver compile my-workflow.ts --format cjs');
819
- logger.log(' $ flow-weaver describe workflow.ts');
820
- logger.log(' $ flow-weaver describe workflow.ts --format mermaid');
821
- logger.log(' $ flow-weaver describe workflow.ts --node validator');
822
- logger.log(' $ flow-weaver diagram workflow.ts');
823
- logger.log(' $ flow-weaver diagram workflow.ts --theme light -o diagram.svg');
824
- logger.log(' $ flow-weaver diagram workflow.ts --format html -o diagram.html');
825
- logger.log(' $ flow-weaver diff workflow-v1.ts workflow-v2.ts');
826
- logger.log(' $ flow-weaver diff workflow-v1.ts workflow-v2.ts --format json');
827
- logger.log(" $ flow-weaver validate '**/*.ts'");
828
- logger.log(" $ flow-weaver watch 'src/**/*.ts' -o dist");
829
- logger.log(' $ flow-weaver create workflow simple my-workflow.ts');
830
- logger.log(' $ flow-weaver create workflow ai-agent agent.ts --provider openai --model gpt-4o');
831
- logger.log(' $ flow-weaver create node myProcessor my-workflow.ts');
832
- logger.log(' $ flow-weaver templates');
833
- logger.log(' $ flow-weaver pattern list ./patterns');
834
- logger.log(' $ flow-weaver pattern apply pattern.ts workflow.ts');
835
- logger.log(' $ flow-weaver pattern extract workflow.ts --nodes a,b -o extracted.ts');
836
- logger.log(' $ flow-weaver init my-project');
837
- logger.log(' $ flow-weaver init --template ai-agent -y');
838
- logger.log(' $ flow-weaver init my-project --format cjs');
839
- logger.log(' $ flow-weaver doctor');
840
- logger.log(' $ flow-weaver doctor --json');
841
- logger.newline();
842
- logger.section('Plugin Commands');
843
- logger.log(' $ flow-weaver plugin init my-plugin');
844
- logger.log(' $ flow-weaver plugin init my-plugin --area sidebar --no-system');
845
- logger.log(' $ flow-weaver plugin init my-plugin --preview');
846
- logger.newline();
847
- logger.section('Deployment Commands');
848
- logger.log(' $ flow-weaver run workflow.ts --params \'{"a": 5}\'');
849
- logger.log(' $ flow-weaver serve ./workflows --port 8080');
850
- logger.log(' $ flow-weaver export workflow.ts --target vercel --output api/');
851
- logger.log(' $ flow-weaver export workflows.ts --target lambda --output dist/ --multi');
852
- logger.log(' $ flow-weaver export workflows.ts --target lambda --output dist/ --multi --docs');
853
- logger.log(' $ flow-weaver export workflow.ts --target inngest --output dist/ --durable-steps');
854
- logger.log(' $ flow-weaver compile workflow.ts --target inngest');
855
- logger.log(' $ flow-weaver openapi ./workflows --output api-spec.json');
856
- logger.newline();
857
- logger.section('Marketplace Commands');
858
- logger.log(' $ flow-weaver market init openai');
859
- logger.log(' $ flow-weaver market pack');
860
- logger.log(' $ flow-weaver market publish');
861
- logger.log(' $ flow-weaver market publish --dry-run');
862
- logger.log(' $ flow-weaver market install flowweaver-pack-openai');
863
- logger.log(' $ flow-weaver market search openai');
864
- logger.log(' $ flow-weaver market list');
865
- logger.newline();
866
- logger.section('Documentation');
867
- logger.log(' $ flow-weaver docs');
868
- logger.log(' $ flow-weaver docs read error-codes');
869
- logger.log(' $ flow-weaver docs read scaffold --compact');
870
- logger.log(' $ flow-weaver docs search "missing workflow"');
871
- logger.log(' $ flow-weaver docs read error-codes --json');
872
- logger.newline();
873
- logger.section('Migration & Changelog');
874
- logger.log(" $ flow-weaver migrate '**/*.ts'");
875
- logger.log(" $ flow-weaver migrate '**/*.ts' --dry-run");
876
- logger.log(" $ flow-weaver migrate 'src/**/*.ts' --diff");
877
- logger.log(' $ flow-weaver changelog --last-tag');
878
- logger.log(' $ flow-weaver changelog --range v0.1.0..HEAD');
879
- logger.log(' $ flow-weaver changelog --since 2024-01-01');
880
- logger.newline();
881
- });
882
- // Parse arguments
883
- program.parse(process.argv);
884
- // Show help if no command specified
605
+ .action(wrapAction(async (options) => {
606
+ await marketListCommand(options);
607
+ }));
608
+ // Concise examples appended to --help
609
+ program.addHelpText('after', `
610
+ Examples:
611
+
612
+ $ flow-weaver compile my-workflow.ts
613
+ $ flow-weaver validate 'src/**/*.ts'
614
+ $ flow-weaver run workflow.ts --params '{"a": 5}'
615
+ $ flow-weaver describe workflow.ts --format ascii-compact
616
+ $ flow-weaver init my-project
617
+
618
+ Run flow-weaver <command> --help for detailed usage.
619
+ `);
620
+ // Show concise welcome when no command specified (before parse to avoid Commander error handling)
885
621
  if (!process.argv.slice(2).length) {
886
- program.outputHelp();
622
+ logger.banner(version);
623
+ console.log();
624
+ console.log(' Usage: flow-weaver <command> [options]');
625
+ console.log();
626
+ console.log(' Get started:');
627
+ console.log(' init [dir] Create a new project');
628
+ console.log(' compile <input> Compile workflow files');
629
+ console.log(' validate <input> Validate without compiling');
630
+ console.log(' run <input> Execute a workflow');
631
+ console.log(' doctor Check project environment');
632
+ console.log();
633
+ console.log(' Run ' + logger.highlight('flow-weaver --help') + ' for all commands.');
634
+ console.log();
635
+ process.exit(0);
887
636
  }
637
+ // Parse arguments
638
+ program.parse(process.argv);
888
639
  //# sourceMappingURL=index.js.map