@mrpatronz/nexusflow 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/commands/diff.d.ts +2 -0
  2. package/dist/commands/diff.d.ts.map +1 -1
  3. package/dist/commands/diff.js +13 -1
  4. package/dist/commands/diff.js.map +1 -1
  5. package/dist/commands/finish.d.ts.map +1 -1
  6. package/dist/commands/finish.js +7 -0
  7. package/dist/commands/finish.js.map +1 -1
  8. package/dist/commands/list.d.ts +3 -1
  9. package/dist/commands/list.d.ts.map +1 -1
  10. package/dist/commands/list.js +5 -1
  11. package/dist/commands/list.js.map +1 -1
  12. package/dist/commands/schedule.d.ts +3 -1
  13. package/dist/commands/schedule.d.ts.map +1 -1
  14. package/dist/commands/schedule.js +15 -2
  15. package/dist/commands/schedule.js.map +1 -1
  16. package/dist/commands/status.d.ts +4 -1
  17. package/dist/commands/status.d.ts.map +1 -1
  18. package/dist/commands/status.js +9 -3
  19. package/dist/commands/status.js.map +1 -1
  20. package/dist/commands/ui.d.ts +9 -0
  21. package/dist/commands/ui.d.ts.map +1 -1
  22. package/dist/commands/ui.js +42 -1
  23. package/dist/commands/ui.js.map +1 -1
  24. package/dist/core/doctor.d.ts.map +1 -1
  25. package/dist/core/doctor.js +24 -0
  26. package/dist/core/doctor.js.map +1 -1
  27. package/dist/core/worktree.test.js +2 -2
  28. package/dist/desktop-server.d.ts +1 -1
  29. package/dist/desktop-server.js +1 -1
  30. package/dist/gui/assets/{AgentsPage-BNHR9qux.js → AgentsPage-CGvhYtRS.js} +1 -1
  31. package/dist/gui/assets/{DashboardPage-BScH3Cxp.js → DashboardPage-BSVVVXuM.js} +1 -1
  32. package/dist/gui/assets/{GettingStartedPage-AWlI-176.js → GettingStartedPage-srl_5DA_.js} +1 -1
  33. package/dist/gui/assets/{ProjectsPage-Bojj7gxI.js → ProjectsPage-BfFX3Rg1.js} +1 -1
  34. package/dist/gui/assets/{ScaffoldRepoInline-DJMbeHVM.js → ScaffoldRepoInline-DWsd4vxF.js} +1 -1
  35. package/dist/gui/assets/{SettingsPage-BAdXO8X0.js → SettingsPage-C16CjqlK.js} +1 -1
  36. package/dist/gui/assets/{SkillsPage-CF6Db1FA.js → SkillsPage-DKLoq_sH.js} +1 -1
  37. package/dist/gui/assets/StartWorkPage-D56-WIZp.js +1 -0
  38. package/dist/gui/assets/{StrategiesPage-nGbR9TAc.js → StrategiesPage-CY8xp7Lz.js} +1 -1
  39. package/dist/gui/assets/{WorkspacesPage-7XRtVJNF.js → WorkspacesPage-CXtIMMUl.js} +1 -1
  40. package/dist/gui/assets/{alert-dialog-67uzXJi4.js → alert-dialog-CCwlEjU2.js} +1 -1
  41. package/dist/gui/assets/{index-V1dDX1O_.js → index-pxzwuRLq.js} +3 -3
  42. package/dist/gui/assets/{vsc-Di05BTNr.js → vsc-DkRhgYbO.js} +1 -1
  43. package/dist/gui/index.html +1 -1
  44. package/dist/index.js +130 -546
  45. package/dist/index.js.map +1 -1
  46. package/dist/server.d.ts.map +1 -1
  47. package/dist/server.js +48 -11
  48. package/dist/server.js.map +1 -1
  49. package/dist/server.test.js +1 -0
  50. package/dist/server.test.js.map +1 -1
  51. package/package.json +3 -3
  52. package/dist/gui/assets/StartWorkPage-BQid3uIw.js +0 -1
package/dist/index.js CHANGED
@@ -54,136 +54,84 @@ program.hook('preAction', (thisCommand) => {
54
54
  process.env.NEXUSFLOW_DEBUG = '1';
55
55
  }
56
56
  });
57
+ function isDebugMode() {
58
+ return process.argv.includes('--debug') || Boolean(process.env.NEXUSFLOW_DEBUG);
59
+ }
60
+ /**
61
+ * Wraps a command action: clean exit on prompt cancellation (Ctrl+C inside an
62
+ * inquirer prompt), clean red error message by default, and exit code 1.
63
+ * When --debug is provided, prints the full error stack.
64
+ */
65
+ function runAction(fn) {
66
+ return async (...args) => {
67
+ try {
68
+ await fn(...args);
69
+ }
70
+ catch (error) {
71
+ if (error instanceof Error && error.message.includes('User force closed')) {
72
+ console.log('\nCancelled.');
73
+ process.exit(0);
74
+ }
75
+ const message = error instanceof Error ? error.message : String(error);
76
+ console.error(chalk.red(`\n✖ ${message}`));
77
+ if (isDebugMode() && error instanceof Error && error.stack) {
78
+ console.error(chalk.dim(error.stack));
79
+ }
80
+ else if (!isDebugMode()) {
81
+ console.error(chalk.dim(' (Run with --debug for details)\n'));
82
+ }
83
+ process.exit(1);
84
+ }
85
+ };
86
+ }
57
87
  program
58
88
  .command('create')
59
89
  .description('Create a new feature workspace — pick repos, pick AI assistants, generate context')
60
- .action(async () => {
61
- try {
62
- await createCommand();
63
- }
64
- catch (error) {
65
- if (error instanceof Error && error.message.includes('User force closed')) {
66
- console.log('\nCancelled.');
67
- process.exit(0);
68
- }
69
- console.error(error);
70
- process.exit(1);
71
- }
72
- });
90
+ .action(runAction(createCommand));
73
91
  program
74
92
  .command('list')
75
93
  .alias('ls')
76
94
  .description('List all existing workspaces')
77
- .action(async () => {
78
- try {
79
- await listCommand();
80
- }
81
- catch (error) {
82
- console.error(error);
83
- process.exit(1);
84
- }
85
- });
95
+ .option('--json', 'Output as JSON')
96
+ .action(runAction(listCommand));
86
97
  program
87
98
  .command('open')
88
99
  .description('Re-open an existing workspace in an editor')
89
- .action(async () => {
90
- try {
91
- await openCommand();
92
- }
93
- catch (error) {
94
- if (error instanceof Error && error.message.includes('User force closed')) {
95
- console.log('\nCancelled.');
96
- process.exit(0);
97
- }
98
- console.error(error);
99
- process.exit(1);
100
- }
101
- });
100
+ .action(runAction(openCommand));
102
101
  program
103
102
  .command('init')
104
103
  .description('Initialize NexusFlow configuration')
105
- .action(async () => {
106
- try {
107
- await initCommand();
108
- }
109
- catch (error) {
110
- if (error instanceof Error && error.message.includes('User force closed')) {
111
- console.log('\nCancelled.');
112
- process.exit(0);
113
- }
114
- console.error(error);
115
- process.exit(1);
116
- }
117
- });
104
+ .action(runAction(initCommand));
118
105
  program
119
106
  .command('start')
120
107
  .description('Start all services in a workspace')
121
108
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
122
- .action(async (workspace) => {
123
- try {
124
- await startCommand(workspace);
125
- }
126
- catch (error) {
127
- if (error instanceof Error && error.message.includes('User force closed')) {
128
- console.log('\nCancelled.');
129
- process.exit(0);
130
- }
131
- console.error(error);
132
- process.exit(1);
133
- }
134
- });
109
+ .action(runAction(async (workspace) => {
110
+ await startCommand(workspace);
111
+ }));
135
112
  program
136
113
  .command('stop')
137
114
  .description('Stop all running services in a workspace')
138
115
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
139
- .action(async (workspace) => {
140
- try {
141
- await stopCommand(workspace);
142
- }
143
- catch (error) {
144
- if (error instanceof Error && error.message.includes('User force closed')) {
145
- console.log('\nCancelled.');
146
- process.exit(0);
147
- }
148
- console.error(error);
149
- process.exit(1);
150
- }
151
- });
116
+ .action(runAction(async (workspace) => {
117
+ await stopCommand(workspace);
118
+ }));
152
119
  program
153
120
  .command('logs')
154
121
  .description('Show logs from running services')
155
122
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
156
123
  .option('-n, --lines <number>', 'Number of lines per service', '30')
157
- .action(async (workspace, options) => {
158
- try {
159
- await logsCommand(workspace, parseInt(options.lines, 10));
160
- }
161
- catch (error) {
162
- if (error instanceof Error && error.message.includes('User force closed')) {
163
- console.log('\nCancelled.');
164
- process.exit(0);
165
- }
166
- console.error(error);
167
- process.exit(1);
168
- }
169
- });
124
+ .action(runAction(async (workspace, options) => {
125
+ await logsCommand(workspace, parseInt(options.lines, 10));
126
+ }));
170
127
  program
171
128
  .command('status')
172
129
  .description('Show status of running services')
173
130
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
174
- .action(async (workspace) => {
175
- try {
176
- await statusCommand(workspace);
177
- }
178
- catch (error) {
179
- if (error instanceof Error && error.message.includes('User force closed')) {
180
- console.log('\nCancelled.');
181
- process.exit(0);
182
- }
183
- console.error(error);
184
- process.exit(1);
185
- }
186
- });
131
+ .option('--json', 'Output in JSON format')
132
+ .action(runAction(async (workspace, options) => {
133
+ await statusCommand(workspace, options);
134
+ }));
187
135
  program
188
136
  .command('ui')
189
137
  .description('Start the NexusFlow dashboard server (the backend the desktop app embeds)')
@@ -192,59 +140,31 @@ program
192
140
  .option('--open', 'Also open the dashboard in your default browser')
193
141
  .option('--server-only', '(deprecated — server-only is now the default)')
194
142
  .option('--strict-port', 'Fail instead of auto-incrementing when the port is in use')
195
- .action(async (options) => {
196
- try {
197
- await uiCommand(options);
198
- }
199
- catch (error) {
200
- console.error(error);
201
- process.exit(1);
202
- }
203
- });
143
+ .action(runAction(async (options) => {
144
+ await uiCommand(options);
145
+ }));
204
146
  program
205
147
  .command('dashboard')
206
148
  .alias('dash')
207
149
  .description('Open the web dashboard in your default browser')
208
150
  .option('-p, --port <number>', 'Port to run the dashboard server on', '3000')
209
- .action(async (options) => {
210
- try {
211
- await uiCommand({ ...options, daemon: true, open: true });
212
- }
213
- catch (error) {
214
- console.error(error);
215
- process.exit(1);
216
- }
217
- });
151
+ .action(runAction(async (options) => {
152
+ await uiCommand({ ...options, daemon: true, open: true });
153
+ }));
218
154
  program
219
155
  .command('tui')
220
156
  .description('Open the interactive terminal GUI (TUI) dashboard')
221
157
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
222
- .action(async (workspace) => {
223
- try {
224
- await tuiCommand({ workspace });
225
- }
226
- catch (error) {
227
- console.error(error);
228
- process.exit(1);
229
- }
230
- });
158
+ .action(runAction(async (workspace) => {
159
+ await tuiCommand({ workspace });
160
+ }));
231
161
  program
232
162
  .command('sync')
233
163
  .description('Sync all repositories in a workspace')
234
164
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
235
- .action(async (workspace) => {
236
- try {
237
- await syncCommand(workspace);
238
- }
239
- catch (error) {
240
- if (error instanceof Error && error.message.includes('User force closed')) {
241
- console.log('\nCancelled.');
242
- process.exit(0);
243
- }
244
- console.error(error);
245
- process.exit(1);
246
- }
247
- });
165
+ .action(runAction(async (workspace) => {
166
+ await syncCommand(workspace);
167
+ }));
248
168
  program
249
169
  .command('commit')
250
170
  .description('Commit changes across all repositories in a workspace')
@@ -253,75 +173,36 @@ program
253
173
  .option('--no-push', 'Stage and commit changes without pushing to remote')
254
174
  .option('--dry-run', 'Preview changes without committing')
255
175
  .option('-r, --repo <repos...>', 'Only commit the given repositories (by name)')
256
- .action(async (workspace, options) => {
257
- try {
258
- await commitCommand(options.message, workspace, options);
259
- }
260
- catch (error) {
261
- if (error instanceof Error && error.message.includes('User force closed')) {
262
- console.log('\nCancelled.');
263
- process.exit(0);
264
- }
265
- console.error(error);
266
- process.exit(1);
267
- }
268
- });
176
+ .action(runAction(async (workspace, options) => {
177
+ await commitCommand(options.message, workspace, options);
178
+ }));
269
179
  program
270
180
  .command('diff')
271
181
  .description('Display a unified summary of changes across all repositories (including unpushed commits)')
272
182
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
273
183
  .option('-r, --repo <repos...>', 'Only show the given repositories (by name)')
274
- .action(async (workspace, options) => {
275
- try {
276
- await diffCommand(workspace, options);
277
- }
278
- catch (error) {
279
- if (error instanceof Error && error.message.includes('User force closed')) {
280
- console.log('\nCancelled.');
281
- process.exit(0);
282
- }
283
- console.error(error);
284
- process.exit(1);
285
- }
286
- });
184
+ .option('--json', 'Output in JSON format')
185
+ .action(runAction(async (workspace, options) => {
186
+ await diffCommand(workspace, options);
187
+ }));
287
188
  // Pack command removed.
288
189
  program
289
190
  .command('remove')
290
191
  .alias('rm')
291
192
  .description('Delete a workspace and cleanly prune/remove its git worktrees')
292
193
  .argument('[workspace]', 'Workspace name or path')
293
- .action(async (workspace) => {
294
- try {
295
- await removeCommand(workspace);
296
- }
297
- catch (error) {
298
- if (error instanceof Error && error.message.includes('User force closed')) {
299
- console.log('\nCancelled.');
300
- process.exit(0);
301
- }
302
- console.error(error);
303
- process.exit(1);
304
- }
305
- });
194
+ .action(runAction(async (workspace) => {
195
+ await removeCommand(workspace);
196
+ }));
306
197
  program
307
198
  .command('add-repo')
308
199
  .alias('add')
309
200
  .description('Add a repository to an existing workspace and update configurations')
310
201
  .argument('[repo-path]', 'Path to the repository to add')
311
202
  .argument('[workspace]', 'Workspace name or path')
312
- .action(async (repoPath, workspace) => {
313
- try {
314
- await addRepoCommand(repoPath, workspace);
315
- }
316
- catch (error) {
317
- if (error instanceof Error && error.message.includes('User force closed')) {
318
- console.log('\nCancelled.');
319
- process.exit(0);
320
- }
321
- console.error(error);
322
- process.exit(1);
323
- }
324
- });
203
+ .action(runAction(async (repoPath, workspace) => {
204
+ await addRepoCommand(repoPath, workspace);
205
+ }));
325
206
  program
326
207
  .command('isolate')
327
208
  .description('Dynamically isolate a repository in an in-place workspace into a dedicated worktree on demand')
@@ -330,60 +211,32 @@ program
330
211
  .option('-b, --branch <branch>', 'Target feature branch name')
331
212
  .option('--base <base>', 'Base branch to branch off')
332
213
  .option('-w, --workspace <workspace>', 'Workspace name or path')
333
- .action(async (repo, branchArg, options) => {
334
- try {
335
- await isolateCommand(repo, branchArg, options);
336
- }
337
- catch (error) {
338
- if (error instanceof Error && error.message.includes('User force closed')) {
339
- console.log('\nCancelled.');
340
- process.exit(0);
341
- }
342
- console.error(error);
343
- process.exit(1);
344
- }
345
- });
214
+ .action(runAction(async (repo, branchArg, options) => {
215
+ await isolateCommand(repo, branchArg, options);
216
+ }));
346
217
  program
347
218
  .command('handoff')
348
219
  .description('Generate a compact handoff bundle (nexusflow-handoff.md) for session resumption')
349
220
  .argument('[workspace]', 'Path to the workspace')
350
- .action(async (workspace) => {
351
- try {
352
- await handoffCommand(workspace);
353
- }
354
- catch (error) {
355
- console.error(error);
356
- process.exit(1);
357
- }
358
- });
221
+ .action(runAction(async (workspace) => {
222
+ await handoffCommand(workspace);
223
+ }));
359
224
  program
360
225
  .command('refresh')
361
226
  .description('Refresh workspace context, plan and handoff files (re-analyzes only changed repos)')
362
227
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
363
228
  .option('-f, --force', 'Ignore the analysis cache and re-analyze every repository')
364
229
  .option('-s, --strategy <id>', 'Update the teamwork strategy (use template ID, or "auto" for AI suggestion)')
365
- .action(async (workspace, options) => {
366
- try {
367
- await refreshCommand(options, workspace);
368
- }
369
- catch (error) {
370
- console.error(error);
371
- process.exit(1);
372
- }
373
- });
230
+ .action(runAction(async (workspace, options) => {
231
+ await refreshCommand(options, workspace);
232
+ }));
374
233
  program
375
234
  .command('doctor')
376
235
  .description('Run diagnostics to verify workspace health and check for local loop issues')
377
236
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
378
- .action(async (workspace) => {
379
- try {
380
- await doctorCommand(workspace);
381
- }
382
- catch (error) {
383
- console.error(error);
384
- process.exit(1);
385
- }
386
- });
237
+ .action(runAction(async (workspace) => {
238
+ await doctorCommand(workspace);
239
+ }));
387
240
  program
388
241
  .command('finish')
389
242
  .description('Finish a feature — commit & push everything, open PRs, promote learnings, optionally remove the workspace')
@@ -394,19 +247,9 @@ program
394
247
  .option('--cleanup', 'Remove the workspace after everything is confirmed pushed (still asks for confirmation)')
395
248
  .option('-y, --yes', 'Accept defaults for non-destructive prompts')
396
249
  .option('--dry-run', 'Show what finish would do without changing anything')
397
- .action(async (workspace, options) => {
398
- try {
399
- await finishCommand(workspace, options);
400
- }
401
- catch (error) {
402
- if (error instanceof Error && error.message.includes('User force closed')) {
403
- console.log('\nCancelled.');
404
- process.exit(0);
405
- }
406
- console.error(error);
407
- process.exit(1);
408
- }
409
- });
250
+ .action(runAction(async (workspace, options) => {
251
+ await finishCommand(workspace, options);
252
+ }));
410
253
  // Knowledge command group
411
254
  const knowledgeCmd = program
412
255
  .command('knowledge')
@@ -420,38 +263,18 @@ knowledgeCmd
420
263
  .requiredOption('-m, --message <msg>', 'The learning to record, as a rule plus its reason (max 300 chars)')
421
264
  .option('--title <title>', 'Short title (used for decision headings)')
422
265
  .option('-r, --repo <repo>', "Write to this repo's persistent base knowledge instead")
423
- .action(async (workspace, options) => {
424
- try {
425
- await knowledgeAddCommand(workspace, options);
426
- }
427
- catch (error) {
428
- if (error instanceof Error && error.message.includes('User force closed')) {
429
- console.log('\nCancelled.');
430
- process.exit(0);
431
- }
432
- console.error(error);
433
- process.exit(1);
434
- }
435
- });
266
+ .action(runAction(async (workspace, options) => {
267
+ await knowledgeAddCommand(workspace, options);
268
+ }));
436
269
  knowledgeCmd
437
270
  .command('show')
438
271
  .description("Print the workspace knowledge file (or a repo's base knowledge)")
439
272
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
440
273
  .option('-s, --section <name>', 'Only show one section')
441
274
  .option('-r, --repo <repo>', "Show the repo's base knowledge file instead")
442
- .action(async (workspace, options) => {
443
- try {
444
- await knowledgeShowCommand(workspace, options);
445
- }
446
- catch (error) {
447
- if (error instanceof Error && error.message.includes('User force closed')) {
448
- console.log('\nCancelled.');
449
- process.exit(0);
450
- }
451
- console.error(error);
452
- process.exit(1);
453
- }
454
- });
275
+ .action(runAction(async (workspace, options) => {
276
+ await knowledgeShowCommand(workspace, options);
277
+ }));
455
278
  knowledgeCmd
456
279
  .command('promote')
457
280
  .description('Copy workspace learnings into per-repo base knowledge so they persist across features')
@@ -461,39 +284,9 @@ knowledgeCmd
461
284
  .option('-m, --message <msg>', 'Promote this text directly (non-interactive)')
462
285
  .option('--move', 'Remove the entry from the workspace file after promoting (default: copy)')
463
286
  .option('--all', 'Promote all decisions, gotchas and assumptions without prompting')
464
- .action(async (workspace, options) => {
465
- try {
466
- await knowledgePromoteCommand(workspace, options);
467
- }
468
- catch (error) {
469
- if (error instanceof Error && error.message.includes('User force closed')) {
470
- console.log('\nCancelled.');
471
- process.exit(0);
472
- }
473
- console.error(error);
474
- process.exit(1);
475
- }
476
- });
477
- /**
478
- * Wraps a command action: clean exit on prompt cancellation (Ctrl+C inside an
479
- * inquirer prompt), exit code 1 with the error otherwise. New command
480
- * registrations should use this instead of copy-pasting the try/catch.
481
- */
482
- function runAction(fn) {
483
- return async (...args) => {
484
- try {
485
- await fn(...args);
486
- }
487
- catch (error) {
488
- if (error instanceof Error && error.message.includes('User force closed')) {
489
- console.log('\nCancelled.');
490
- process.exit(0);
491
- }
492
- console.error(error);
493
- process.exit(1);
494
- }
495
- };
496
- }
287
+ .action(runAction(async (workspace, options) => {
288
+ await knowledgePromoteCommand(workspace, options);
289
+ }));
497
290
  // Project command group
498
291
  const projectCmd = program
499
292
  .command('project')
@@ -532,216 +325,76 @@ strategyCmd
532
325
  .command('list')
533
326
  .alias('ls')
534
327
  .description('List all available strategy templates (built-in and custom)')
535
- .action(async () => {
536
- try {
537
- await strategyListCommand();
538
- }
539
- catch (error) {
540
- if (error instanceof Error && error.message.includes('User force closed')) {
541
- console.log('\nCancelled.');
542
- process.exit(0);
543
- }
544
- console.error(error);
545
- process.exit(1);
546
- }
547
- });
328
+ .action(runAction(strategyListCommand));
548
329
  strategyCmd
549
330
  .command('create')
550
331
  .description('Create a new custom strategy template')
551
332
  .option('-n, --name <name>', 'Strategy name')
552
333
  .option('-f, --file <path>', 'Load content from a .md or .txt file')
553
- .action(async (options) => {
554
- try {
555
- await strategyCreateCommand(options);
556
- }
557
- catch (error) {
558
- if (error instanceof Error && error.message.includes('User force closed')) {
559
- console.log('\nCancelled.');
560
- process.exit(0);
561
- }
562
- console.error(error);
563
- process.exit(1);
564
- }
565
- });
334
+ .action(runAction(strategyCreateCommand));
566
335
  strategyCmd
567
336
  .command('edit')
568
337
  .description('Edit an existing custom strategy template')
569
338
  .option('--id <id>', 'Template ID to edit (skips the selection prompt)')
570
- .action(async (options) => {
571
- try {
572
- await strategyEditCommand(options);
573
- }
574
- catch (error) {
575
- if (error instanceof Error && error.message.includes('User force closed')) {
576
- console.log('\nCancelled.');
577
- process.exit(0);
578
- }
579
- console.error(error);
580
- process.exit(1);
581
- }
582
- });
339
+ .action(runAction(strategyEditCommand));
583
340
  strategyCmd
584
341
  .command('delete')
585
342
  .alias('rm')
586
343
  .description('Delete a custom strategy template')
587
344
  .option('--id <id>', 'Template ID to delete (skips the selection prompt)')
588
345
  .option('-y, --yes', 'Skip confirmation prompt')
589
- .action(async (options) => {
590
- try {
591
- await strategyDeleteCommand(options);
592
- }
593
- catch (error) {
594
- if (error instanceof Error && error.message.includes('User force closed')) {
595
- console.log('\nCancelled.');
596
- process.exit(0);
597
- }
598
- console.error(error);
599
- process.exit(1);
600
- }
601
- });
346
+ .action(runAction(strategyDeleteCommand));
602
347
  strategyCmd
603
348
  .command('show')
604
349
  .description('Display the full content of a strategy template')
605
350
  .option('--id <id>', 'Template ID to show (skips the selection prompt)')
606
- .action(async (options) => {
607
- try {
608
- await strategyShowCommand(options);
609
- }
610
- catch (error) {
611
- if (error instanceof Error && error.message.includes('User force closed')) {
612
- console.log('\nCancelled.');
613
- process.exit(0);
614
- }
615
- console.error(error);
616
- process.exit(1);
617
- }
618
- });
351
+ .action(runAction(strategyShowCommand));
619
352
  program
620
353
  .command('desktop')
621
354
  .description('Launch the NexusFlow desktop application')
622
- .action(async () => {
623
- try {
624
- await desktopCommand();
625
- }
626
- catch (error) {
627
- console.error(error);
628
- process.exit(1);
629
- }
630
- });
355
+ .action(runAction(desktopCommand));
631
356
  // Config command group
632
357
  const configCmd = program.command('config').description('View and update NexusFlow configuration');
633
358
  configCmd
634
359
  .command('show')
635
360
  .description('Display the current configuration')
636
- .action(async () => {
637
- try {
638
- await configShowCommand();
639
- }
640
- catch (error) {
641
- console.error(error);
642
- process.exit(1);
643
- }
644
- });
361
+ .action(runAction(configShowCommand));
645
362
  configCmd
646
363
  .command('get')
647
364
  .description('Get a specific configuration key')
648
365
  .argument('<key>', 'Configuration key to read')
649
- .action(async (key) => {
650
- try {
651
- await configGetCommand(key);
652
- }
653
- catch (error) {
654
- console.error(error);
655
- process.exit(1);
656
- }
657
- });
366
+ .action(runAction(configGetCommand));
658
367
  configCmd
659
368
  .command('set')
660
369
  .description('Set a configuration key to a value')
661
370
  .argument('<key>', 'Configuration key to set')
662
371
  .argument('<value>', 'Value to assign')
663
- .action(async (key, value) => {
664
- try {
665
- await configSetCommand(key, value);
666
- }
667
- catch (error) {
668
- console.error(error);
669
- process.exit(1);
670
- }
671
- });
372
+ .action(runAction(configSetCommand));
672
373
  // Default action when 'nexusflow config' is run without a subcommand
673
- configCmd.action(async () => {
674
- try {
675
- await configShowCommand();
676
- }
677
- catch (error) {
678
- console.error(error);
679
- process.exit(1);
680
- }
681
- });
374
+ configCmd.action(runAction(configShowCommand));
682
375
  // Adapter command group
683
376
  const adapterCmd = program.command('adapter').description('Manage storage adapters — list, switch, configure, or create new ones');
684
377
  adapterCmd
685
378
  .command('list')
686
379
  .description('List all available storage adapters')
687
- .action(async () => {
688
- try {
689
- await adapterListCommand();
690
- }
691
- catch (error) {
692
- console.error(error);
693
- process.exit(1);
694
- }
695
- });
380
+ .action(runAction(adapterListCommand));
696
381
  adapterCmd
697
382
  .command('use')
698
383
  .description('Switch to a different storage adapter (prompts for config if needed)')
699
384
  .argument('<name>', 'Adapter name to activate')
700
- .action(async (name) => {
701
- try {
702
- await adapterUseCommand(name);
703
- }
704
- catch (error) {
705
- console.error(error);
706
- process.exit(1);
707
- }
708
- });
385
+ .action(runAction(adapterUseCommand));
709
386
  adapterCmd
710
387
  .command('info')
711
388
  .description('Show detailed information about an adapter')
712
389
  .argument('<name>', 'Adapter name to inspect')
713
- .action(async (name) => {
714
- try {
715
- await adapterInfoCommand(name);
716
- }
717
- catch (error) {
718
- console.error(error);
719
- process.exit(1);
720
- }
721
- });
390
+ .action(runAction(adapterInfoCommand));
722
391
  adapterCmd
723
392
  .command('init')
724
393
  .description('Scaffold a new adapter plugin project')
725
394
  .argument('<name>', 'Name for the new adapter')
726
- .action(async (name) => {
727
- try {
728
- await adapterInitCommand(name);
729
- }
730
- catch (error) {
731
- console.error(error);
732
- process.exit(1);
733
- }
734
- });
395
+ .action(runAction(adapterInitCommand));
735
396
  // Default action when 'nexusflow adapter' is run without a subcommand
736
- adapterCmd.action(async () => {
737
- try {
738
- await adapterListCommand();
739
- }
740
- catch (error) {
741
- console.error(error);
742
- process.exit(1);
743
- }
744
- });
397
+ adapterCmd.action(runAction(adapterListCommand));
745
398
  // Schedule command group
746
399
  const scheduleCmd = program
747
400
  .command('schedule')
@@ -752,124 +405,55 @@ scheduleCmd
752
405
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
753
406
  .option('-t, --task <task>', 'Job to run: "sync" or "refresh"', 'sync')
754
407
  .requiredOption('-e, --every <interval>', 'How often to run, e.g. 30m, 2h, 1d')
755
- .action(async (workspace, options) => {
756
- try {
757
- await scheduleAddCommand(workspace, options);
758
- }
759
- catch (error) {
760
- if (error instanceof Error && error.message.includes('User force closed')) {
761
- console.log('\nCancelled.');
762
- process.exit(0);
763
- }
764
- console.error(error);
765
- process.exit(1);
766
- }
767
- });
408
+ .action(runAction(async (workspace, options) => {
409
+ await scheduleAddCommand(workspace, options);
410
+ }));
768
411
  scheduleCmd
769
412
  .command('list')
770
413
  .alias('ls')
771
414
  .description('List all scheduled jobs')
772
- .action(async () => {
773
- try {
774
- await scheduleListCommand();
775
- }
776
- catch (error) {
777
- console.error(error);
778
- process.exit(1);
779
- }
780
- });
415
+ .option('--json', 'Output in JSON format')
416
+ .action(runAction(scheduleListCommand));
781
417
  scheduleCmd
782
418
  .command('remove')
783
419
  .alias('rm')
784
420
  .description('Remove a scheduled job')
785
421
  .argument('<id>', 'Job id (see "nexusflow schedule list")')
786
- .action(async (id) => {
787
- try {
788
- await scheduleRemoveCommand(id);
789
- }
790
- catch (error) {
791
- console.error(error);
792
- process.exit(1);
793
- }
794
- });
422
+ .action(runAction(scheduleRemoveCommand));
795
423
  scheduleCmd
796
424
  .command('enable')
797
425
  .description('Enable a scheduled job')
798
426
  .argument('<id>', 'Job id (see "nexusflow schedule list")')
799
- .action(async (id) => {
800
- try {
801
- await scheduleToggleCommand(id, true);
802
- }
803
- catch (error) {
804
- console.error(error);
805
- process.exit(1);
806
- }
807
- });
427
+ .action(runAction(async (id) => {
428
+ await scheduleToggleCommand(id, true);
429
+ }));
808
430
  scheduleCmd
809
431
  .command('disable')
810
432
  .description('Disable a scheduled job without removing it')
811
433
  .argument('<id>', 'Job id (see "nexusflow schedule list")')
812
- .action(async (id) => {
813
- try {
814
- await scheduleToggleCommand(id, false);
815
- }
816
- catch (error) {
817
- console.error(error);
818
- process.exit(1);
819
- }
820
- });
434
+ .action(runAction(async (id) => {
435
+ await scheduleToggleCommand(id, false);
436
+ }));
821
437
  scheduleCmd
822
438
  .command('run')
823
439
  .description('Run a scheduled job immediately')
824
440
  .argument('<id>', 'Job id (see "nexusflow schedule list")')
825
- .action(async (id) => {
826
- try {
827
- await scheduleRunCommand(id);
828
- }
829
- catch (error) {
830
- console.error(error);
831
- process.exit(1);
832
- }
833
- });
441
+ .action(runAction(scheduleRunCommand));
834
442
  // Default action when 'nexusflow schedule' is run without a subcommand
835
- scheduleCmd.action(async () => {
836
- try {
837
- await scheduleListCommand();
838
- }
839
- catch (error) {
840
- console.error(error);
841
- process.exit(1);
842
- }
843
- });
443
+ scheduleCmd.action(runAction(scheduleListCommand));
844
444
  const mcp = program.command('mcp').description('Manage the NexusFlow MCP Server for AI assistants');
845
445
  mcp
846
446
  .command('run')
847
447
  .description('Start the NexusFlow MCP Server (typically called automatically by AI assistants)')
848
448
  .argument('[workspace]', 'Path to workspace (auto-detects from CWD)')
849
- .action(async (workspace) => {
850
- try {
851
- await mcpRunCommand(workspace);
852
- }
853
- catch (error) {
854
- console.error(error);
855
- process.exit(1);
856
- }
857
- });
449
+ .action(runAction(mcpRunCommand));
858
450
  mcp
859
451
  .command('setup')
860
452
  .description('Automatically configure your AI environments (Claude Desktop, Cursor, VS Code) to use the NexusFlow MCP Server')
861
- .action(async () => {
862
- try {
863
- await mcpSetupCommand();
864
- }
865
- catch (error) {
866
- console.error(error);
867
- process.exit(1);
868
- }
869
- });
453
+ .action(runAction(mcpSetupCommand));
870
454
  program.hook('postAction', async (thisCommand, actionCommand) => {
871
- // Skip update check for MCP run to prevent contaminating stdout stream
872
- if (actionCommand.name() === 'run' && actionCommand.parent?.name() === 'mcp') {
455
+ // Skip update check for non-TTY streams and MCP runs to prevent contaminating stdout stream
456
+ if (!process.stdout.isTTY || (actionCommand.name() === 'run' && actionCommand.parent?.name() === 'mcp')) {
873
457
  return;
874
458
  }
875
459
  try {