@proletariat/cli 0.3.24 → 0.3.26

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 (134) hide show
  1. package/dist/commands/action/create.js +3 -3
  2. package/dist/commands/action/index.js +2 -2
  3. package/dist/commands/action/update.js +3 -3
  4. package/dist/commands/agent/auth.js +1 -1
  5. package/dist/commands/agent/cleanup.js +6 -6
  6. package/dist/commands/agent/discover.js +1 -1
  7. package/dist/commands/agent/remove.js +4 -4
  8. package/dist/commands/autocomplete/setup.d.ts +2 -2
  9. package/dist/commands/autocomplete/setup.js +5 -5
  10. package/dist/commands/branch/create.js +31 -30
  11. package/dist/commands/category/create.js +4 -5
  12. package/dist/commands/category/delete.js +2 -3
  13. package/dist/commands/category/rename.js +2 -3
  14. package/dist/commands/claude.d.ts +2 -8
  15. package/dist/commands/claude.js +26 -26
  16. package/dist/commands/commit.d.ts +2 -8
  17. package/dist/commands/commit.js +4 -26
  18. package/dist/commands/config/index.d.ts +2 -10
  19. package/dist/commands/config/index.js +8 -34
  20. package/dist/commands/docker/index.d.ts +2 -2
  21. package/dist/commands/docker/index.js +8 -8
  22. package/dist/commands/epic/activate.js +9 -17
  23. package/dist/commands/epic/archive.js +13 -24
  24. package/dist/commands/epic/create.js +7 -6
  25. package/dist/commands/epic/delete.js +4 -5
  26. package/dist/commands/epic/move.js +28 -47
  27. package/dist/commands/epic/progress.js +10 -14
  28. package/dist/commands/epic/project.js +42 -59
  29. package/dist/commands/epic/reorder.js +25 -30
  30. package/dist/commands/epic/spec.d.ts +1 -0
  31. package/dist/commands/epic/spec.js +39 -40
  32. package/dist/commands/epic/ticket.d.ts +2 -0
  33. package/dist/commands/epic/ticket.js +63 -37
  34. package/dist/commands/feedback/index.d.ts +10 -0
  35. package/dist/commands/feedback/index.js +60 -0
  36. package/dist/commands/feedback/list.d.ts +12 -0
  37. package/dist/commands/feedback/list.js +126 -0
  38. package/dist/commands/feedback/submit.d.ts +16 -0
  39. package/dist/commands/feedback/submit.js +220 -0
  40. package/dist/commands/feedback/view.d.ts +15 -0
  41. package/dist/commands/feedback/view.js +109 -0
  42. package/dist/commands/gh/index.js +4 -0
  43. package/dist/commands/link/index.js +2 -2
  44. package/dist/commands/pmo/init.d.ts +2 -2
  45. package/dist/commands/pmo/init.js +7 -7
  46. package/dist/commands/project/spec.js +6 -6
  47. package/dist/commands/repo/create.d.ts +38 -0
  48. package/dist/commands/repo/create.js +283 -0
  49. package/dist/commands/repo/index.js +7 -0
  50. package/dist/commands/roadmap/add-project.js +9 -22
  51. package/dist/commands/roadmap/create.d.ts +0 -1
  52. package/dist/commands/roadmap/create.js +46 -40
  53. package/dist/commands/roadmap/delete.js +10 -24
  54. package/dist/commands/roadmap/generate.d.ts +1 -0
  55. package/dist/commands/roadmap/generate.js +21 -22
  56. package/dist/commands/roadmap/remove-project.js +14 -34
  57. package/dist/commands/roadmap/reorder.js +19 -26
  58. package/dist/commands/roadmap/update.js +27 -26
  59. package/dist/commands/roadmap/view.js +5 -12
  60. package/dist/commands/session/attach.d.ts +1 -8
  61. package/dist/commands/session/attach.js +93 -59
  62. package/dist/commands/session/health.d.ts +29 -0
  63. package/dist/commands/session/health.js +495 -0
  64. package/dist/commands/session/index.js +4 -0
  65. package/dist/commands/session/list.d.ts +0 -8
  66. package/dist/commands/session/list.js +130 -81
  67. package/dist/commands/spec/create.js +1 -1
  68. package/dist/commands/spec/edit.js +64 -35
  69. package/dist/commands/staff/add.d.ts +2 -2
  70. package/dist/commands/staff/add.js +15 -14
  71. package/dist/commands/staff/index.js +2 -2
  72. package/dist/commands/staff/remove.js +4 -4
  73. package/dist/commands/status/index.js +6 -7
  74. package/dist/commands/support/book.d.ts +10 -0
  75. package/dist/commands/support/book.js +54 -0
  76. package/dist/commands/support/discord.d.ts +10 -0
  77. package/dist/commands/support/discord.js +54 -0
  78. package/dist/commands/support/docs.d.ts +10 -0
  79. package/dist/commands/support/docs.js +54 -0
  80. package/dist/commands/support/index.d.ts +19 -0
  81. package/dist/commands/support/index.js +81 -0
  82. package/dist/commands/support/issues.d.ts +11 -0
  83. package/dist/commands/support/issues.js +77 -0
  84. package/dist/commands/support/logs.d.ts +18 -0
  85. package/dist/commands/support/logs.js +247 -0
  86. package/dist/commands/template/apply.js +10 -11
  87. package/dist/commands/template/create.js +18 -17
  88. package/dist/commands/template/index.d.ts +2 -2
  89. package/dist/commands/template/index.js +6 -6
  90. package/dist/commands/template/save.js +8 -7
  91. package/dist/commands/template/update.js +6 -7
  92. package/dist/commands/terminal/title.d.ts +2 -26
  93. package/dist/commands/terminal/title.js +4 -33
  94. package/dist/commands/theme/index.d.ts +2 -2
  95. package/dist/commands/theme/index.js +19 -18
  96. package/dist/commands/theme/set.d.ts +2 -2
  97. package/dist/commands/theme/set.js +5 -5
  98. package/dist/commands/ticket/create.js +52 -26
  99. package/dist/commands/ticket/delete.js +15 -13
  100. package/dist/commands/ticket/edit.js +59 -20
  101. package/dist/commands/ticket/epic.js +12 -10
  102. package/dist/commands/ticket/move.d.ts +7 -0
  103. package/dist/commands/ticket/move.js +132 -0
  104. package/dist/commands/ticket/project.js +11 -9
  105. package/dist/commands/ticket/reassign.js +23 -19
  106. package/dist/commands/ticket/spec.js +7 -5
  107. package/dist/commands/ticket/update.js +55 -53
  108. package/dist/commands/whoami.js +1 -0
  109. package/dist/commands/work/ready.js +7 -7
  110. package/dist/commands/work/revise.js +13 -11
  111. package/dist/commands/work/spawn.d.ts +1 -0
  112. package/dist/commands/work/spawn.js +225 -64
  113. package/dist/commands/work/start.d.ts +1 -0
  114. package/dist/commands/work/start.js +301 -173
  115. package/dist/hooks/init.js +4 -0
  116. package/dist/lib/execution/runners.js +21 -17
  117. package/dist/lib/execution/session-utils.d.ts +60 -0
  118. package/dist/lib/execution/session-utils.js +162 -0
  119. package/dist/lib/execution/spawner.d.ts +2 -0
  120. package/dist/lib/execution/spawner.js +42 -0
  121. package/dist/lib/flags/resolver.d.ts +2 -2
  122. package/dist/lib/flags/resolver.js +15 -0
  123. package/dist/lib/init/index.js +18 -0
  124. package/dist/lib/multiline-input.d.ts +63 -0
  125. package/dist/lib/multiline-input.js +360 -0
  126. package/dist/lib/pr/index.d.ts +4 -0
  127. package/dist/lib/pr/index.js +32 -14
  128. package/dist/lib/prompt-command.d.ts +3 -0
  129. package/dist/lib/prompt-json.d.ts +77 -6
  130. package/dist/lib/prompt-json.js +46 -0
  131. package/dist/lib/repos/git.d.ts +7 -0
  132. package/dist/lib/repos/git.js +20 -0
  133. package/oclif.manifest.json +2913 -2246
  134. package/package.json +1 -1
@@ -1,12 +1,11 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import * as path from 'node:path';
3
- import inquirer from 'inquirer';
4
3
  import Database from 'better-sqlite3';
5
4
  import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
6
5
  import { styles } from '../../lib/styles.js';
7
6
  import { getWorkspaceInfo, getTicketTmuxSession, killTmuxSession } from '../../lib/agents/commands.js';
8
7
  import { isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled } from '../../lib/execution/runners.js';
9
- import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
8
+ import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, outputConfirmationNeededAsJson, outputExecutionResultAsJson, } from '../../lib/prompt-json.js';
10
9
  import { FlagResolver } from '../../lib/flags/index.js';
11
10
  export default class WorkSpawn extends PMOCommand {
12
11
  static description = 'Spawn work for multiple tickets by column (batch mode)';
@@ -19,6 +18,7 @@ export default class WorkSpawn extends PMOCommand {
19
18
  '<%= config.bin %> <%= command.id %> TKT-001 TKT-002 # Spawn specific tickets by ID',
20
19
  '<%= config.bin %> <%= command.id %> --dry-run # Preview without executing',
21
20
  '<%= config.bin %> <%= command.id %> --many --json # Output ticket choices as JSON (for agents)',
21
+ '<%= config.bin %> <%= command.id %> TKT-001 --action custom --message "Add unit tests" # Custom prompt',
22
22
  ];
23
23
  static flags = {
24
24
  ...pmoBaseFlags,
@@ -101,7 +101,10 @@ export default class WorkSpawn extends PMOCommand {
101
101
  default: false,
102
102
  }),
103
103
  action: Flags.string({
104
- description: 'Action to perform (e.g., groom, implement, review). Prompts if not provided.',
104
+ description: 'Action to perform (e.g., groom, implement, review, custom). Prompts if not provided.',
105
+ }),
106
+ message: Flags.string({
107
+ description: 'Custom prompt/message for the agent (use with --action custom)',
105
108
  }),
106
109
  session: Flags.string({
107
110
  description: 'Session manager inside container (tmux runs agent in tmux inside container)',
@@ -251,18 +254,78 @@ export default class WorkSpawn extends PMOCommand {
251
254
  db.close();
252
255
  return handleError('NO_VALID_TICKETS', 'No valid tickets found from provided IDs.');
253
256
  }
254
- // In JSON mode with explicit tickets, output success
257
+ // In JSON mode with explicit tickets, check if we should execute or return confirmation
255
258
  if (jsonMode) {
256
- outputSuccessAsJson({
257
- ticketsSelected: ticketsToSpawn.map(t => ({
258
- id: t.id,
259
- title: t.title,
260
- status: t.statusName,
261
- })),
262
- count: ticketsToSpawn.length,
263
- }, createMetadata('work spawn', flags));
264
- db.close();
265
- return;
259
+ // Check if all required flags for non-interactive execution are provided
260
+ const hasAction = !!flags.action;
261
+ const hasDisplay = !!flags.display || flags['run-on-host'];
262
+ const hasPermissions = flags['skip-permissions'] || flags['per-ticket']; // per-ticket mode prompts individually
263
+ const allFlagsProvided = hasAction && hasDisplay && hasPermissions;
264
+ if (allFlagsProvided && flags.yes) {
265
+ // All flags provided and --yes is set: fall through to execution loop
266
+ // Don't return here - continue to execution
267
+ }
268
+ else if (allFlagsProvided && !flags.yes) {
269
+ // All flags provided but no --yes: return confirmation_needed with plan
270
+ const metadata = createMetadata('work spawn', flags);
271
+ // Build the confirm command with --yes
272
+ const ticketIds = ticketsToSpawn.map(t => t.id).join(' ');
273
+ let confirmCmd = `prlt work spawn ${ticketIds}`;
274
+ if (flags.action)
275
+ confirmCmd += ` --action ${flags.action}`;
276
+ if (flags.display)
277
+ confirmCmd += ` --display ${flags.display}`;
278
+ if (flags['run-on-host'])
279
+ confirmCmd += ' --run-on-host';
280
+ if (flags['skip-permissions'])
281
+ confirmCmd += ' --skip-permissions';
282
+ if (flags.executor)
283
+ confirmCmd += ` --executor ${flags.executor}`;
284
+ if (flags.session)
285
+ confirmCmd += ` --session ${flags.session}`;
286
+ if (flags['create-pr'])
287
+ confirmCmd += ' --create-pr';
288
+ if (flags['no-pr'])
289
+ confirmCmd += ' --no-pr';
290
+ if (flags.clone)
291
+ confirmCmd += ' --clone';
292
+ if (flags.focus)
293
+ confirmCmd += ' --focus';
294
+ confirmCmd += ' --yes';
295
+ const plan = {
296
+ tickets: ticketsToSpawn.map(t => ({
297
+ id: t.id,
298
+ title: t.title,
299
+ status: t.statusName,
300
+ })),
301
+ action: flags.action,
302
+ display: flags.display || (flags['run-on-host'] ? 'host' : 'devcontainer'),
303
+ permissions: flags['skip-permissions'] ? 'danger' : 'safe',
304
+ count: ticketsToSpawn.length,
305
+ };
306
+ db.close();
307
+ outputConfirmationNeededAsJson(plan, confirmCmd, `Ready to spawn ${ticketsToSpawn.length} agent(s). Run with --yes to execute.`, metadata);
308
+ return;
309
+ }
310
+ else {
311
+ // Missing required flags: return success with tickets selected (legacy behavior)
312
+ // This allows the calling agent to see what tickets are available
313
+ outputSuccessAsJson({
314
+ ticketsSelected: ticketsToSpawn.map(t => ({
315
+ id: t.id,
316
+ title: t.title,
317
+ status: t.statusName,
318
+ })),
319
+ count: ticketsToSpawn.length,
320
+ missingFlags: {
321
+ action: !hasAction,
322
+ display: !hasDisplay,
323
+ permissions: !hasPermissions,
324
+ },
325
+ }, createMetadata('work spawn', flags));
326
+ db.close();
327
+ return;
328
+ }
266
329
  }
267
330
  this.log('');
268
331
  this.log(styles.header(`🚀 Spawn: ${ticketsToSpawn.length} ticket(s)`));
@@ -392,23 +455,18 @@ export default class WorkSpawn extends PMOCommand {
392
455
  }
393
456
  ticketsByPriority.get(priority).push(ticket);
394
457
  }
395
- // Build choices with priority separators
396
- const choices = [];
397
- // Also build flat choices for JSON mode (without separators)
458
+ // Build flat choices for ticket selection
398
459
  const flatChoices = [];
399
460
  for (const priority of PRIORITY_ORDER) {
400
461
  const tickets = ticketsByPriority.get(priority) || [];
401
462
  if (tickets.length === 0)
402
463
  continue;
403
- choices.push(new inquirer.Separator(`── ${priority} (${tickets.length}) ──`));
404
464
  for (const ticket of tickets) {
405
465
  const statusBadge = ticket.statusName ? ` [${ticket.statusName}]` : '';
406
- const ticketChoice = {
466
+ flatChoices.push({
407
467
  name: `[${priority}] ${ticket.id} - ${ticket.title}${statusBadge}`,
408
468
  value: ticket.id,
409
- };
410
- choices.push(ticketChoice);
411
- flatChoices.push(ticketChoice);
469
+ });
412
470
  }
413
471
  }
414
472
  // Use FlagResolver for ticket selection (checkbox)
@@ -460,24 +518,25 @@ export default class WorkSpawn extends PMOCommand {
460
518
  }
461
519
  }
462
520
  // Handle tickets with active sessions
521
+ const spawnJsonModeConfig = jsonMode ? { flags: flags, commandName: 'work spawn' } : null;
463
522
  if (ticketsWithActiveSessions.length > 0 && !jsonMode) {
464
523
  this.log(styles.warning(`Found ${ticketsWithActiveSessions.length} ticket(s) with active tmux sessions:`));
465
524
  for (const { ticketId, agent } of ticketsWithActiveSessions) {
466
525
  this.log(styles.muted(` ${ticketId} → ${agent}`));
467
526
  }
468
527
  this.log('');
469
- const { sessionAction } = await inquirer.prompt([
528
+ const { sessionAction } = await this.prompt([
470
529
  {
471
530
  type: 'list',
472
531
  name: 'sessionAction',
473
532
  message: 'What would you like to do with these tickets?',
474
533
  choices: [
475
- { name: 'Skip them (only spawn tickets without active sessions)', value: 'skip' },
476
- { name: 'Kill sessions and respawn with new agents', value: 'kill' },
477
- { name: 'Cancel', value: 'cancel' },
534
+ { name: 'Skip them (only spawn tickets without active sessions)', value: 'skip', command: 'prlt work spawn --json' },
535
+ { name: 'Kill sessions and respawn with new agents', value: 'kill', command: 'prlt work spawn --json' },
536
+ { name: 'Cancel', value: 'cancel', command: '' },
478
537
  ],
479
538
  },
480
- ]);
539
+ ], spawnJsonModeConfig);
481
540
  if (sessionAction === 'cancel') {
482
541
  db.close();
483
542
  this.log(styles.muted('Cancelled.'));
@@ -522,6 +581,8 @@ export default class WorkSpawn extends PMOCommand {
522
581
  let batchNoPr = flags['no-pr'];
523
582
  let batchRunOnHost = flags['run-on-host'];
524
583
  let batchAction = flags.action;
584
+ // Track custom message for custom action (needs to be outside the if block)
585
+ let batchCustomMessage = flags.message;
525
586
  // Track display mode separately for devcontainer (needs to be outside the if block)
526
587
  let batchDisplayMode;
527
588
  // For ephemeral agents, we'll create devcontainers on-demand
@@ -542,12 +603,16 @@ export default class WorkSpawn extends PMOCommand {
542
603
  name: `${a.id.padEnd(12)} - ${a.description || a.name}`,
543
604
  value: a.id,
544
605
  }));
545
- // Add adhoc option at the end
606
+ // Add custom and adhoc options at the end
607
+ actionChoices.push({
608
+ name: 'custom - Enter a custom prompt/instruction',
609
+ value: '__custom__',
610
+ });
546
611
  actionChoices.push({
547
612
  name: 'adhoc - Unstructured exploration/debugging',
548
613
  value: '__adhoc__',
549
614
  });
550
- // Use FlagResolver for action selection
615
+ // Use FlagResolver for action selection with optional custom input
551
616
  const actionResolver = new FlagResolver({
552
617
  commandName: 'work spawn',
553
618
  baseCommand: 'prlt work spawn',
@@ -561,12 +626,54 @@ export default class WorkSpawn extends PMOCommand {
561
626
  default: 'implement',
562
627
  choices: () => actionChoices,
563
628
  });
629
+ actionResolver.addPrompt({
630
+ flagName: 'customInput',
631
+ type: 'input',
632
+ message: 'Enter custom prompt for the agent:',
633
+ when: (ctx) => ctx.flags.selectedAction === '__custom__',
634
+ validate: (value) => value.trim() ? true : 'Prompt cannot be empty',
635
+ });
564
636
  const actionResult = await actionResolver.resolve();
565
637
  const selectedAction = actionResult.selectedAction;
566
- batchAction = selectedAction === '__adhoc__' ? 'adhoc' : selectedAction;
638
+ if (selectedAction === '__custom__') {
639
+ batchAction = 'custom';
640
+ batchCustomMessage = actionResult.customInput.trim();
641
+ }
642
+ else if (selectedAction === '__adhoc__') {
643
+ batchAction = 'adhoc';
644
+ }
645
+ else {
646
+ batchAction = selectedAction;
647
+ }
648
+ }
649
+ else if (flags.action === 'custom') {
650
+ // Custom action specified via flag - require --message
651
+ if (!flags.message) {
652
+ db.close();
653
+ return handleError('MISSING_MESSAGE', '--action custom requires --message flag with the custom prompt');
654
+ }
655
+ batchAction = 'custom';
656
+ batchCustomMessage = flags.message;
657
+ }
658
+ else if (flags.message && flags.action !== 'custom') {
659
+ // --message provided without --action custom - warn user
660
+ this.warn('--message flag is only used with --action custom, ignoring');
567
661
  }
568
662
  // Now fetch action details after selection is made
569
- if (batchAction === 'adhoc') {
663
+ if (batchAction === 'custom') {
664
+ // Custom action - user provides their own prompt
665
+ selectedActionDetails = {
666
+ id: 'custom',
667
+ name: 'Custom',
668
+ description: 'Custom prompt/instruction',
669
+ prompt: batchCustomMessage || '',
670
+ modifiesCode: true, // Assume custom prompts may modify code
671
+ defaultMoveToCategory: 'started',
672
+ isBuiltin: false,
673
+ createdAt: new Date(),
674
+ };
675
+ }
676
+ else if (batchAction === 'adhoc') {
570
677
  // Adhoc is a synthetic action, not stored in database
571
678
  selectedActionDetails = {
572
679
  id: 'adhoc',
@@ -678,7 +785,7 @@ export default class WorkSpawn extends PMOCommand {
678
785
  let environmentSelected = false;
679
786
  while (!environmentSelected) {
680
787
  // eslint-disable-next-line no-await-in-loop -- Interactive loop with retry on Docker check
681
- const { selectedEnvironment } = await inquirer.prompt([
788
+ const { selectedEnvironment } = await this.prompt([
682
789
  {
683
790
  type: 'list',
684
791
  name: 'selectedEnvironment',
@@ -686,7 +793,7 @@ export default class WorkSpawn extends PMOCommand {
686
793
  choices: envChoices,
687
794
  default: devcontainerReady ? 'devcontainer' : 'host',
688
795
  },
689
- ]);
796
+ ], spawnJsonModeConfig);
690
797
  if (selectedEnvironment === 'cancel') {
691
798
  db.close();
692
799
  this.log(styles.muted('Cancelled.'));
@@ -760,18 +867,18 @@ export default class WorkSpawn extends PMOCommand {
760
867
  // For devcontainer, prompt for display mode
761
868
  // Simplified: tmux is always used inside container for session persistence
762
869
  // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after selection
763
- const { selectedDisplay } = await inquirer.prompt([
870
+ const { selectedDisplay } = await this.prompt([
764
871
  {
765
872
  type: 'list',
766
873
  name: 'selectedDisplay',
767
874
  message: 'How should agent output be displayed?',
768
875
  choices: [
769
- { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
770
- { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background' },
876
+ { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal', command: 'prlt work spawn --display terminal --json' },
877
+ { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background', command: 'prlt work spawn --display background --json' },
771
878
  ],
772
879
  default: 'terminal',
773
880
  },
774
- ]);
881
+ ], spawnJsonModeConfig);
775
882
  batchDisplayMode = selectedDisplay;
776
883
  // Always use tmux inside container for session persistence
777
884
  flags.session = 'tmux';
@@ -836,26 +943,33 @@ export default class WorkSpawn extends PMOCommand {
836
943
  const actionModifiesCode = selectedActionDetails?.modifiesCode ?? true;
837
944
  if (!batchCreatePr && !batchNoPr) {
838
945
  if (actionModifiesCode) {
839
- // Use FlagResolver for PR choice
840
- const prResolver = new FlagResolver({
841
- commandName: 'work spawn',
842
- baseCommand: 'prlt work spawn',
843
- jsonMode,
844
- flags: {},
845
- });
846
- prResolver.addPrompt({
847
- flagName: 'prChoice',
848
- type: 'list',
849
- message: 'Create pull requests when work is ready?',
850
- default: 'yes',
851
- choices: () => [
852
- { name: '✓ Yes - Create PR for each ticket', value: 'yes' },
853
- { name: '✗ No - Just move tickets to review', value: 'no' },
854
- ],
855
- });
856
- const prResult = await prResolver.resolve();
857
- batchCreatePr = prResult.prChoice === 'yes';
858
- batchNoPr = prResult.prChoice === 'no';
946
+ // In JSON mode with --yes, default to creating PRs for code-modifying actions
947
+ if (jsonMode && flags.yes) {
948
+ batchCreatePr = true;
949
+ batchNoPr = false;
950
+ }
951
+ else {
952
+ // Use FlagResolver for PR choice
953
+ const prResolver = new FlagResolver({
954
+ commandName: 'work spawn',
955
+ baseCommand: 'prlt work spawn',
956
+ jsonMode,
957
+ flags: {},
958
+ });
959
+ prResolver.addPrompt({
960
+ flagName: 'prChoice',
961
+ type: 'list',
962
+ message: 'Create pull requests when work is ready?',
963
+ default: 'yes',
964
+ choices: () => [
965
+ { name: '✓ Yes - Create PR for each ticket', value: 'yes' },
966
+ { name: '✗ No - Just move tickets to review', value: 'no' },
967
+ ],
968
+ });
969
+ const prResult = await prResolver.resolve();
970
+ batchCreatePr = prResult.prChoice === 'yes';
971
+ batchNoPr = prResult.prChoice === 'no';
972
+ }
859
973
  }
860
974
  else {
861
975
  // Non-code-modifying action - no PR needed
@@ -874,10 +988,14 @@ export default class WorkSpawn extends PMOCommand {
874
988
  // Spawn each ticket - work:start will create ephemeral agents on-demand
875
989
  let successCount = 0;
876
990
  let failCount = 0;
991
+ // Track execution results for JSON output
992
+ const executionResults = [];
877
993
  // Process sequentially for clear logging and resource management
878
994
  for (const ticket of ticketsToSpawn) {
879
995
  try {
880
- this.log(styles.muted(`Starting ${ticket.id} with ephemeral agent...`));
996
+ if (!jsonMode) {
997
+ this.log(styles.muted(`Starting ${ticket.id} with ephemeral agent...`));
998
+ }
881
999
  // Build args for work:start
882
1000
  // IMPORTANT: Pass --project to avoid re-prompting for project selection
883
1001
  // Pass --ephemeral to signal work:start should create an ephemeral agent
@@ -885,6 +1003,10 @@ export default class WorkSpawn extends PMOCommand {
885
1003
  // Pass clone flag if specified
886
1004
  if (flags.clone)
887
1005
  startArgs.push('--clone');
1006
+ // In JSON mode with --yes, pass --yes to work:start to skip prompts there too
1007
+ if (jsonMode && flags.yes) {
1008
+ startArgs.push('--yes');
1009
+ }
888
1010
  if (flags['per-ticket']) {
889
1011
  // Per-ticket mode: only pass display flag, let start prompt for the rest
890
1012
  // batchDisplayMode is for devcontainer, batchDisplay is for host
@@ -899,6 +1021,13 @@ export default class WorkSpawn extends PMOCommand {
899
1021
  startArgs.push('--force');
900
1022
  if (flags.focus)
901
1023
  startArgs.push('--focus');
1024
+ // Pass action/prompt - custom action uses --prompt, others use --action
1025
+ if (batchAction === 'custom' && batchCustomMessage) {
1026
+ startArgs.push('--prompt', batchCustomMessage);
1027
+ }
1028
+ else if (batchAction) {
1029
+ startArgs.push('--action', batchAction);
1030
+ }
902
1031
  }
903
1032
  else {
904
1033
  // Batch mode: pass all settings to skip prompts
@@ -920,8 +1049,13 @@ export default class WorkSpawn extends PMOCommand {
920
1049
  startArgs.push('--create-pr');
921
1050
  if (batchNoPr)
922
1051
  startArgs.push('--no-pr');
923
- // Pass action flag (from prompt or flag)
924
- startArgs.push('--action', batchAction || 'implement');
1052
+ // Pass action/prompt - custom action uses --prompt, others use --action
1053
+ if (batchAction === 'custom' && batchCustomMessage) {
1054
+ startArgs.push('--prompt', batchCustomMessage);
1055
+ }
1056
+ else {
1057
+ startArgs.push('--action', batchAction || 'implement');
1058
+ }
925
1059
  // Pass session manager (tmux inside container by default)
926
1060
  if (flags.session)
927
1061
  startArgs.push('--session', flags.session);
@@ -932,16 +1066,43 @@ export default class WorkSpawn extends PMOCommand {
932
1066
  // eslint-disable-next-line no-await-in-loop
933
1067
  await this.config.runCommand('work:start', startArgs);
934
1068
  successCount++;
1069
+ // Track for JSON output
1070
+ executionResults.push({
1071
+ workId: `WORK-${ticket.id}`, // Placeholder - actual work ID comes from work:start
1072
+ ticketId: ticket.id,
1073
+ agent: 'ephemeral', // Ephemeral agent name determined by work:start
1074
+ status: 'running',
1075
+ });
935
1076
  }
936
1077
  catch (error) {
937
1078
  failCount++;
938
- this.log(styles.error(`Failed to start ${ticket.id}: ${error instanceof Error ? error.message : error}`));
1079
+ if (!jsonMode) {
1080
+ this.log(styles.error(`Failed to start ${ticket.id}: ${error instanceof Error ? error.message : error}`));
1081
+ }
1082
+ // Track failed executions for JSON output
1083
+ executionResults.push({
1084
+ workId: '',
1085
+ ticketId: ticket.id,
1086
+ agent: '',
1087
+ status: 'failed',
1088
+ });
939
1089
  }
940
1090
  }
941
- await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
1091
+ await autoExportToBoard(this.pmoPath, this.storage, (msg) => {
1092
+ if (!jsonMode) {
1093
+ this.log(styles.muted(msg));
1094
+ }
1095
+ });
942
1096
  db.close();
943
- this.log('');
944
- this.log(styles.success(`✓ Spawn results: ${successCount} started, ${failCount} failed`));
1097
+ // Output results
1098
+ if (jsonMode) {
1099
+ // Output JSON execution results
1100
+ outputExecutionResultAsJson(executionResults, successCount, failCount, createMetadata('work spawn', flags));
1101
+ }
1102
+ else {
1103
+ this.log('');
1104
+ this.log(styles.success(`✓ Spawn results: ${successCount} started, ${failCount} failed`));
1105
+ }
945
1106
  }
946
1107
  catch (error) {
947
1108
  db.close();
@@ -27,6 +27,7 @@ export default class WorkStart extends PMOCommand {
27
27
  ephemeral: import("@oclif/core/interfaces").BooleanFlag<boolean>;
28
28
  focus: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
29
  clone: import("@oclif/core/interfaces").BooleanFlag<boolean>;
30
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
30
31
  project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
31
32
  };
32
33
  execute(): Promise<void>;