@synergenius/flow-weaver 0.15.2 → 0.16.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,37 +177,27 @@ 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)
202
203
  .option('--install', 'Run npm install after scaffolding')
@@ -205,15 +206,9 @@ program
205
206
  .option('--no-git', 'Skip git init')
206
207
  .option('--force', 'Overwrite existing files', false)
207
208
  .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
- });
209
+ .action(wrapAction(async (directory, options) => {
210
+ await initCommand(directory, options);
211
+ }));
217
212
  // Watch command
218
213
  program
219
214
  .command('watch <input>')
@@ -222,17 +217,13 @@ program
222
217
  .option('-p, --production', 'Generate production code (no debug events)', false)
223
218
  .option('-s, --source-map', 'Generate source maps', false)
224
219
  .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
- });
220
+ .option('-w, --workflow <name>', 'Specific workflow name to compile')
221
+ .option('-f, --format <format>', 'Module format: esm, cjs, or auto', 'auto')
222
+ .action(wrapAction(async (input, options) => {
223
+ if (options.workflow)
224
+ options.workflowName = options.workflow;
225
+ await watchCommand(input, options);
226
+ }));
236
227
  // Dev command (watch + compile + run)
237
228
  program
238
229
  .command('dev <input>')
@@ -241,36 +232,24 @@ program
241
232
  .option('--params-file <path>', 'Path to JSON file with input parameters')
242
233
  .option('-w, --workflow <name>', 'Specific workflow name to run')
243
234
  .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')
235
+ .option('-f, --format <format>', 'Module format: esm, cjs, or auto', 'auto')
245
236
  .option('--clean', 'Omit redundant @param/@returns annotations', false)
246
237
  .option('--once', 'Run once then exit', false)
247
238
  .option('--json', 'Output result as JSON', false)
248
239
  .option('--target <target>', 'Compilation target: typescript or inngest (default: typescript)')
249
240
  .option('--framework <framework>', 'Framework for serve handler (inngest target only)', 'express')
250
241
  .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
- });
242
+ .action(wrapAction(async (input, options) => {
243
+ await devCommand(input, options);
244
+ }));
260
245
  // Listen command
261
246
  program
262
247
  .command('listen')
263
248
  .description('Connect to Studio and stream integration events as JSON lines')
264
249
  .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
- });
250
+ .action(wrapAction(async (options) => {
251
+ await listenCommand(options);
252
+ }));
274
253
  // Tunnel command
275
254
  program
276
255
  .command('tunnel')
@@ -278,30 +257,18 @@ program
278
257
  .requiredOption('-k, --key <apiKey>', 'API key for cloud authentication (fw_xxxx)')
279
258
  .option('-c, --cloud <url>', 'Cloud server URL', 'https://flowweaver.dev')
280
259
  .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
- });
260
+ .action(wrapAction(async (options) => {
261
+ await tunnelCommand(options);
262
+ }));
290
263
  // MCP server command
291
264
  program
292
265
  .command('mcp-server')
293
266
  .description('Start MCP server for Claude Code integration')
294
267
  .option('-s, --server <url>', 'Studio URL', DEFAULT_SERVER_URL)
295
268
  .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
- });
269
+ .action(wrapAction(async (options) => {
270
+ await mcpServerCommand(options);
271
+ }));
305
272
  // MCP setup command
306
273
  program
307
274
  .command('mcp-setup')
@@ -309,82 +276,46 @@ program
309
276
  .option('--tool <tools...>', 'Specific tools to configure (claude, cursor, vscode, windsurf, codex, openclaw)')
310
277
  .option('--all', 'Configure all detected tools without prompting')
311
278
  .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
- });
279
+ .action(wrapAction(async (options) => {
280
+ await mcpSetupCommand(options);
281
+ }));
321
282
  // UI command group (send commands to Studio)
322
283
  const uiCmd = program.command('ui').description('Send commands to Studio');
323
284
  uiCmd
324
285
  .command('focus-node <nodeId>')
325
286
  .description('Select and center a node in Studio')
326
287
  .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
- });
288
+ .action(wrapAction(async (nodeId, options) => {
289
+ await uiFocusNode(nodeId, options);
290
+ }));
336
291
  uiCmd
337
292
  .command('add-node <nodeTypeName>')
338
293
  .description('Add a node at viewport center')
339
294
  .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
- });
295
+ .action(wrapAction(async (nodeTypeName, options) => {
296
+ await uiAddNode(nodeTypeName, options);
297
+ }));
349
298
  uiCmd
350
299
  .command('open-workflow <filePath>')
351
300
  .description('Open a workflow file in Studio')
352
301
  .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
- });
302
+ .action(wrapAction(async (filePath, options) => {
303
+ await uiOpenWorkflow(filePath, options);
304
+ }));
362
305
  uiCmd
363
306
  .command('get-state')
364
307
  .description('Return current workflow state from Studio')
365
308
  .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
- });
309
+ .action(wrapAction(async (options) => {
310
+ await uiGetState(options);
311
+ }));
375
312
  uiCmd
376
313
  .command('batch <json>')
377
314
  .description('Execute a batch of commands with auto-snapshot rollback')
378
315
  .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
- });
316
+ .action(wrapAction(async (json, options) => {
317
+ await uiBatch(json, options);
318
+ }));
388
319
  // Create command (with subcommands)
389
320
  const createCmd = program.command('create').description('Create workflows or nodes from templates');
390
321
  createCmd
@@ -400,91 +331,55 @@ createCmd
400
331
  .option('--nodes <names>', 'Comma-separated node function names (e.g., "fetch,parse,store")')
401
332
  .option('--input <name>', 'Custom input port name (default: "data")')
402
333
  .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
- });
334
+ .action(wrapAction(async (template, file, options) => {
335
+ await createWorkflowCommand(template, file, options);
336
+ }));
412
337
  createCmd
413
338
  .command('node <name> <file>')
414
- .description('Create a node type (uses processor template by default)')
339
+ .description('Create a node type from a template')
415
340
  .option('-l, --line <number>', 'Insert at specific line number', parseInt)
416
- .option('-t, --template <template>', 'Node template to use', 'processor')
341
+ .option('-t, --template <template>', 'Node template to use', 'transformer')
417
342
  .option('-p, --preview', 'Preview generated code without writing', false)
418
343
  .option('--strategy <strategy>', 'Template strategy (e.g. mock, callback, webhook)')
419
344
  .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
- });
345
+ .action(wrapAction(async (name, file, options) => {
346
+ await createNodeCommand(name, file, options);
347
+ }));
429
348
  // Templates command
430
349
  program
431
350
  .command('templates')
432
351
  .description('List available templates')
433
352
  .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
- });
353
+ .action(wrapAction(async (options) => {
354
+ await templatesCommand(options);
355
+ }));
443
356
  // Grammar command
444
357
  program
445
358
  .command('grammar')
446
359
  .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')
360
+ .addOption(new Option('-f, --format <format>', 'Output format').choices(['html', 'ebnf']))
448
361
  .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
- });
362
+ .action(wrapAction(async (options) => {
363
+ await grammarCommand(options);
364
+ }));
458
365
  // Pattern command (with subcommands)
459
366
  const patternCmd = program.command('pattern').description('Work with reusable workflow patterns');
460
367
  patternCmd
461
368
  .command('list <path>')
462
369
  .description('List patterns in a file or directory')
463
370
  .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
- });
371
+ .action(wrapAction(async (inputPath, options) => {
372
+ await patternListCommand(inputPath, options);
373
+ }));
473
374
  patternCmd
474
375
  .command('apply <pattern-file> <target-file>')
475
376
  .description('Apply a pattern to a workflow file')
476
377
  .option('-p, --preview', 'Preview changes without writing', false)
477
378
  .option('--prefix <prefix>', 'Prefix for node instance IDs')
478
379
  .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
- });
380
+ .action(wrapAction(async (patternFile, targetFile, options) => {
381
+ await patternApplyCommand(patternFile, targetFile, options);
382
+ }));
488
383
  patternCmd
489
384
  .command('extract <source-file>')
490
385
  .description('Extract a pattern from workflow nodes')
@@ -492,15 +387,9 @@ patternCmd
492
387
  .requiredOption('-o, --output <file>', 'Output pattern file')
493
388
  .option('-n, --name <name>', 'Pattern name')
494
389
  .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
- });
390
+ .action(wrapAction(async (sourceFile, options) => {
391
+ await patternExtractCommand(sourceFile, options);
392
+ }));
504
393
  // Run command
505
394
  program
506
395
  .command('run <input>')
@@ -519,15 +408,9 @@ program
519
408
  .option('--checkpoint', 'Enable checkpointing to disk after each node')
520
409
  .option('--resume [file]', 'Resume from a checkpoint file (auto-detects latest if no file)')
521
410
  .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
- });
411
+ .action(wrapAction(async (input, options) => {
412
+ await runCommand(input, options);
413
+ }));
531
414
  // Serve command
532
415
  program
533
416
  .command('serve [directory]')
@@ -539,28 +422,22 @@ program
539
422
  .option('--precompile', 'Precompile all workflows on startup', false)
540
423
  .option('--cors <origin>', 'CORS origin', '*')
541
424
  .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
- });
425
+ .action(wrapAction(async (directory, options) => {
426
+ await serveCommand(directory, {
427
+ port: parseInt(options.port, 10),
428
+ host: options.host,
429
+ watch: options.watch,
430
+ production: options.production,
431
+ precompile: options.precompile,
432
+ cors: options.cors,
433
+ swagger: options.swagger,
434
+ });
435
+ }));
559
436
  // Export command
560
437
  program
561
438
  .command('export <input>')
562
439
  .description('Export workflow as serverless function')
563
- .requiredOption('-t, --target <target>', 'Target platform (lambda, vercel, cloudflare)')
440
+ .requiredOption('-t, --target <target>', 'Target platform (install target packs via marketplace)')
564
441
  .requiredOption('-o, --output <path>', 'Output directory')
565
442
  .option('-w, --workflow <name>', 'Specific workflow name to export')
566
443
  .option('-p, --production', 'Production mode', true)
@@ -569,15 +446,9 @@ program
569
446
  .option('--workflows <names>', 'Comma-separated list of workflows to export (used with --multi)')
570
447
  .option('--docs', 'Include API documentation routes (/docs and /openapi.json)', false)
571
448
  .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
- });
449
+ .action(wrapAction(async (input, options) => {
450
+ await exportCommand(input, options);
451
+ }));
581
452
  // OpenAPI command
582
453
  program
583
454
  .command('openapi <directory>')
@@ -586,17 +457,11 @@ program
586
457
  .option('--title <title>', 'API title', 'Flow Weaver API')
587
458
  .option('--version <version>', 'API version', '1.0.0')
588
459
  .option('--description <desc>', 'API description')
589
- .option('-f, --format <format>', 'Output format: json (default), yaml', 'json')
460
+ .option('-f, --format <format>', 'Output format: json, yaml', 'json')
590
461
  .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
- });
462
+ .action(wrapAction(async (directory, options) => {
463
+ await openapiCommand(directory, options);
464
+ }));
600
465
  // Plugin command group
601
466
  const pluginCmd = program.command('plugin').description('Scaffold and manage external plugins');
602
467
  pluginCmd
@@ -606,60 +471,40 @@ pluginCmd
606
471
  .option('--no-system', 'Skip generating a system module')
607
472
  .option('-p, --preview', 'Preview generated files without writing', false)
608
473
  .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
- });
474
+ .action(wrapAction(async (name, options) => {
475
+ await pluginInitCommand(name, options);
476
+ }));
618
477
  // Migrate command
619
478
  program
620
479
  .command('migrate <glob>')
621
480
  .description('Migrate workflow files to current syntax via parse → regenerate round-trip')
622
481
  .option('--dry-run', 'Preview changes without writing files', false)
623
482
  .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
- });
483
+ .action(wrapAction(async (glob, options) => {
484
+ await migrateCommand(glob, options);
485
+ }));
633
486
  // Status command
634
487
  program
635
488
  .command('status <input>')
636
489
  .description('Report implementation progress for stub workflows')
637
- .option('-w, --workflow-name <name>', 'Specific workflow name')
490
+ .option('-w, --workflow <name>', 'Specific workflow name')
638
491
  .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
- });
492
+ .action(wrapAction(async (input, options) => {
493
+ if (options.workflow)
494
+ options.workflowName = options.workflow;
495
+ await statusCommand(input, options);
496
+ }));
648
497
  // Implement command
649
498
  program
650
499
  .command('implement <input> <node>')
651
500
  .description('Replace a stub node with a real function skeleton')
652
- .option('-w, --workflow-name <name>', 'Specific workflow name')
501
+ .option('-w, --workflow <name>', 'Specific workflow name')
653
502
  .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
- });
503
+ .action(wrapAction(async (input, node, options) => {
504
+ if (options.workflow)
505
+ options.workflowName = options.workflow;
506
+ await implementCommand(input, node, options);
507
+ }));
663
508
  // Changelog command
664
509
  program
665
510
  .command('changelog')
@@ -667,43 +512,31 @@ program
667
512
  .option('--last-tag', 'From last git tag to HEAD', false)
668
513
  .option('--since <date>', 'Date-based range (e.g., "2024-01-01")')
669
514
  .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
- });
515
+ .action(wrapAction(async (options) => {
516
+ await changelogCommand(options);
517
+ }));
679
518
  // Docs command: flow-weaver docs [topic] | flow-weaver docs search <query>
680
519
  program
681
520
  .command('docs [args...]')
682
521
  .description('Browse reference documentation')
683
522
  .option('--json', 'Output as JSON', false)
684
523
  .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);
524
+ .action(wrapAction(async (args, options) => {
525
+ if (args.length === 0 || args[0] === 'list') {
526
+ await docsListCommand(options);
527
+ }
528
+ else if (args[0] === 'search') {
529
+ const query = args.slice(1).join(' ');
530
+ if (!query) {
531
+ logger.error('Usage: flow-weaver docs search <query>');
532
+ process.exit(1);
700
533
  }
534
+ await docsSearchCommand(query, options);
701
535
  }
702
- catch (error) {
703
- logger.error(`Command failed: ${getErrorMessage(error)}`);
704
- process.exit(1);
536
+ else {
537
+ await docsReadCommand(args[0], options);
705
538
  }
706
- });
539
+ }));
707
540
  // Context command: generate LLM context bundles
708
541
  program
709
542
  .command('context [preset]')
@@ -714,15 +547,9 @@ program
714
547
  .option('--no-grammar', 'Omit EBNF grammar section')
715
548
  .option('-o, --output <path>', 'Write to file instead of stdout')
716
549
  .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
- });
550
+ .action(wrapAction(async (preset, options) => {
551
+ await contextCommand(preset, options);
552
+ }));
726
553
  // Marketplace command group
727
554
  const marketCmd = program.command('market').description('Discover, install, and publish marketplace packages');
728
555
  marketCmd
@@ -731,158 +558,77 @@ marketCmd
731
558
  .option('-d, --description <desc>', 'Package description')
732
559
  .option('-a, --author <author>', 'Author name')
733
560
  .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
- });
561
+ .action(wrapAction(async (name, options) => {
562
+ await marketInitCommand(name, options);
563
+ }));
743
564
  marketCmd
744
565
  .command('pack [directory]')
745
566
  .description('Validate and generate flowweaver.manifest.json')
746
567
  .option('--json', 'Output results as JSON', false)
747
568
  .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
- });
569
+ .action(wrapAction(async (directory, options) => {
570
+ await marketPackCommand(directory, options);
571
+ }));
757
572
  marketCmd
758
573
  .command('publish [directory]')
759
574
  .description('Pack and publish to npm')
760
575
  .option('--dry-run', 'Preview without publishing', false)
761
576
  .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
- });
577
+ .action(wrapAction(async (directory, options) => {
578
+ await marketPublishCommand(directory, options);
579
+ }));
771
580
  marketCmd
772
581
  .command('install <package>')
773
582
  .description('Install a marketplace package')
774
583
  .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
- });
584
+ .action(wrapAction(async (packageSpec, options) => {
585
+ await marketInstallCommand(packageSpec, options);
586
+ }));
784
587
  marketCmd
785
588
  .command('search [query]')
786
589
  .description('Search npm for marketplace packages')
787
590
  .option('-l, --limit <number>', 'Max results', '20')
788
591
  .option('-r, --registry <url>', 'Custom registry search URL (e.g., private npm registry)')
789
592
  .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
- });
593
+ .action(wrapAction(async (query, options) => {
594
+ await marketSearchCommand(query, { ...options, limit: parseInt(options.limit, 10) });
595
+ }));
799
596
  marketCmd
800
597
  .command('list')
801
598
  .description('List installed marketplace packages')
802
599
  .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
600
+ .action(wrapAction(async (options) => {
601
+ await marketListCommand(options);
602
+ }));
603
+ // Concise examples appended to --help
604
+ program.addHelpText('after', `
605
+ Examples:
606
+
607
+ $ flow-weaver compile my-workflow.ts
608
+ $ flow-weaver validate 'src/**/*.ts'
609
+ $ flow-weaver run workflow.ts --params '{"a": 5}'
610
+ $ flow-weaver describe workflow.ts --format ascii-compact
611
+ $ flow-weaver init my-project
612
+
613
+ Run flow-weaver <command> --help for detailed usage.
614
+ `);
615
+ // Show concise welcome when no command specified (before parse to avoid Commander error handling)
885
616
  if (!process.argv.slice(2).length) {
886
- program.outputHelp();
617
+ logger.banner(version);
618
+ console.log();
619
+ console.log(' Usage: flow-weaver <command> [options]');
620
+ console.log();
621
+ console.log(' Get started:');
622
+ console.log(' init [dir] Create a new project');
623
+ console.log(' compile <input> Compile workflow files');
624
+ console.log(' validate <input> Validate without compiling');
625
+ console.log(' run <input> Execute a workflow');
626
+ console.log(' doctor Check project environment');
627
+ console.log();
628
+ console.log(' Run ' + logger.highlight('flow-weaver --help') + ' for all commands.');
629
+ console.log();
630
+ process.exit(0);
887
631
  }
632
+ // Parse arguments
633
+ program.parse(process.argv);
888
634
  //# sourceMappingURL=index.js.map