@proletariat/cli 0.3.25 → 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 (69) hide show
  1. package/dist/commands/action/index.js +2 -2
  2. package/dist/commands/agent/auth.js +1 -1
  3. package/dist/commands/agent/cleanup.js +6 -6
  4. package/dist/commands/agent/discover.js +1 -1
  5. package/dist/commands/agent/remove.js +4 -4
  6. package/dist/commands/autocomplete/setup.d.ts +2 -2
  7. package/dist/commands/autocomplete/setup.js +5 -5
  8. package/dist/commands/branch/create.js +31 -30
  9. package/dist/commands/category/create.js +4 -5
  10. package/dist/commands/category/delete.js +2 -3
  11. package/dist/commands/category/rename.js +2 -3
  12. package/dist/commands/claude.d.ts +2 -8
  13. package/dist/commands/claude.js +26 -26
  14. package/dist/commands/commit.d.ts +2 -8
  15. package/dist/commands/commit.js +4 -26
  16. package/dist/commands/config/index.d.ts +2 -10
  17. package/dist/commands/config/index.js +8 -34
  18. package/dist/commands/docker/index.d.ts +2 -2
  19. package/dist/commands/docker/index.js +8 -8
  20. package/dist/commands/epic/delete.js +4 -5
  21. package/dist/commands/feedback/submit.d.ts +2 -2
  22. package/dist/commands/feedback/submit.js +9 -9
  23. package/dist/commands/link/index.js +2 -2
  24. package/dist/commands/pmo/init.d.ts +2 -2
  25. package/dist/commands/pmo/init.js +7 -7
  26. package/dist/commands/project/spec.js +6 -6
  27. package/dist/commands/session/health.d.ts +29 -0
  28. package/dist/commands/session/health.js +495 -0
  29. package/dist/commands/session/index.js +4 -0
  30. package/dist/commands/spec/edit.js +2 -3
  31. package/dist/commands/staff/add.d.ts +2 -2
  32. package/dist/commands/staff/add.js +15 -14
  33. package/dist/commands/staff/index.js +2 -2
  34. package/dist/commands/staff/remove.js +4 -4
  35. package/dist/commands/status/index.js +6 -7
  36. package/dist/commands/template/apply.js +10 -11
  37. package/dist/commands/template/create.js +18 -17
  38. package/dist/commands/template/index.d.ts +2 -2
  39. package/dist/commands/template/index.js +6 -6
  40. package/dist/commands/template/save.js +8 -7
  41. package/dist/commands/template/update.js +6 -7
  42. package/dist/commands/terminal/title.d.ts +2 -26
  43. package/dist/commands/terminal/title.js +4 -33
  44. package/dist/commands/theme/index.d.ts +2 -2
  45. package/dist/commands/theme/index.js +19 -18
  46. package/dist/commands/theme/set.d.ts +2 -2
  47. package/dist/commands/theme/set.js +5 -5
  48. package/dist/commands/ticket/create.js +34 -16
  49. package/dist/commands/ticket/delete.js +15 -13
  50. package/dist/commands/ticket/edit.js +20 -12
  51. package/dist/commands/ticket/epic.js +12 -10
  52. package/dist/commands/ticket/project.js +11 -9
  53. package/dist/commands/ticket/reassign.js +23 -19
  54. package/dist/commands/ticket/spec.js +7 -5
  55. package/dist/commands/ticket/update.js +55 -53
  56. package/dist/commands/whoami.js +1 -0
  57. package/dist/commands/work/ready.js +7 -7
  58. package/dist/commands/work/revise.js +13 -11
  59. package/dist/commands/work/spawn.js +154 -57
  60. package/dist/commands/work/start.d.ts +1 -0
  61. package/dist/commands/work/start.js +295 -173
  62. package/dist/hooks/init.js +4 -0
  63. package/dist/lib/pr/index.d.ts +4 -0
  64. package/dist/lib/pr/index.js +32 -14
  65. package/dist/lib/prompt-command.d.ts +3 -0
  66. package/dist/lib/prompt-json.d.ts +72 -1
  67. package/dist/lib/prompt-json.js +46 -0
  68. package/oclif.manifest.json +1184 -1116
  69. 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)';
@@ -255,18 +254,78 @@ export default class WorkSpawn extends PMOCommand {
255
254
  db.close();
256
255
  return handleError('NO_VALID_TICKETS', 'No valid tickets found from provided IDs.');
257
256
  }
258
- // In JSON mode with explicit tickets, output success
257
+ // In JSON mode with explicit tickets, check if we should execute or return confirmation
259
258
  if (jsonMode) {
260
- outputSuccessAsJson({
261
- ticketsSelected: ticketsToSpawn.map(t => ({
262
- id: t.id,
263
- title: t.title,
264
- status: t.statusName,
265
- })),
266
- count: ticketsToSpawn.length,
267
- }, createMetadata('work spawn', flags));
268
- db.close();
269
- 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
+ }
270
329
  }
271
330
  this.log('');
272
331
  this.log(styles.header(`🚀 Spawn: ${ticketsToSpawn.length} ticket(s)`));
@@ -396,23 +455,18 @@ export default class WorkSpawn extends PMOCommand {
396
455
  }
397
456
  ticketsByPriority.get(priority).push(ticket);
398
457
  }
399
- // Build choices with priority separators
400
- const choices = [];
401
- // Also build flat choices for JSON mode (without separators)
458
+ // Build flat choices for ticket selection
402
459
  const flatChoices = [];
403
460
  for (const priority of PRIORITY_ORDER) {
404
461
  const tickets = ticketsByPriority.get(priority) || [];
405
462
  if (tickets.length === 0)
406
463
  continue;
407
- choices.push(new inquirer.Separator(`── ${priority} (${tickets.length}) ──`));
408
464
  for (const ticket of tickets) {
409
465
  const statusBadge = ticket.statusName ? ` [${ticket.statusName}]` : '';
410
- const ticketChoice = {
466
+ flatChoices.push({
411
467
  name: `[${priority}] ${ticket.id} - ${ticket.title}${statusBadge}`,
412
468
  value: ticket.id,
413
- };
414
- choices.push(ticketChoice);
415
- flatChoices.push(ticketChoice);
469
+ });
416
470
  }
417
471
  }
418
472
  // Use FlagResolver for ticket selection (checkbox)
@@ -464,24 +518,25 @@ export default class WorkSpawn extends PMOCommand {
464
518
  }
465
519
  }
466
520
  // Handle tickets with active sessions
521
+ const spawnJsonModeConfig = jsonMode ? { flags: flags, commandName: 'work spawn' } : null;
467
522
  if (ticketsWithActiveSessions.length > 0 && !jsonMode) {
468
523
  this.log(styles.warning(`Found ${ticketsWithActiveSessions.length} ticket(s) with active tmux sessions:`));
469
524
  for (const { ticketId, agent } of ticketsWithActiveSessions) {
470
525
  this.log(styles.muted(` ${ticketId} → ${agent}`));
471
526
  }
472
527
  this.log('');
473
- const { sessionAction } = await inquirer.prompt([
528
+ const { sessionAction } = await this.prompt([
474
529
  {
475
530
  type: 'list',
476
531
  name: 'sessionAction',
477
532
  message: 'What would you like to do with these tickets?',
478
533
  choices: [
479
- { name: 'Skip them (only spawn tickets without active sessions)', value: 'skip' },
480
- { name: 'Kill sessions and respawn with new agents', value: 'kill' },
481
- { 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: '' },
482
537
  ],
483
538
  },
484
- ]);
539
+ ], spawnJsonModeConfig);
485
540
  if (sessionAction === 'cancel') {
486
541
  db.close();
487
542
  this.log(styles.muted('Cancelled.'));
@@ -730,7 +785,7 @@ export default class WorkSpawn extends PMOCommand {
730
785
  let environmentSelected = false;
731
786
  while (!environmentSelected) {
732
787
  // eslint-disable-next-line no-await-in-loop -- Interactive loop with retry on Docker check
733
- const { selectedEnvironment } = await inquirer.prompt([
788
+ const { selectedEnvironment } = await this.prompt([
734
789
  {
735
790
  type: 'list',
736
791
  name: 'selectedEnvironment',
@@ -738,7 +793,7 @@ export default class WorkSpawn extends PMOCommand {
738
793
  choices: envChoices,
739
794
  default: devcontainerReady ? 'devcontainer' : 'host',
740
795
  },
741
- ]);
796
+ ], spawnJsonModeConfig);
742
797
  if (selectedEnvironment === 'cancel') {
743
798
  db.close();
744
799
  this.log(styles.muted('Cancelled.'));
@@ -812,18 +867,18 @@ export default class WorkSpawn extends PMOCommand {
812
867
  // For devcontainer, prompt for display mode
813
868
  // Simplified: tmux is always used inside container for session persistence
814
869
  // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after selection
815
- const { selectedDisplay } = await inquirer.prompt([
870
+ const { selectedDisplay } = await this.prompt([
816
871
  {
817
872
  type: 'list',
818
873
  name: 'selectedDisplay',
819
874
  message: 'How should agent output be displayed?',
820
875
  choices: [
821
- { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
822
- { 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' },
823
878
  ],
824
879
  default: 'terminal',
825
880
  },
826
- ]);
881
+ ], spawnJsonModeConfig);
827
882
  batchDisplayMode = selectedDisplay;
828
883
  // Always use tmux inside container for session persistence
829
884
  flags.session = 'tmux';
@@ -888,26 +943,33 @@ export default class WorkSpawn extends PMOCommand {
888
943
  const actionModifiesCode = selectedActionDetails?.modifiesCode ?? true;
889
944
  if (!batchCreatePr && !batchNoPr) {
890
945
  if (actionModifiesCode) {
891
- // Use FlagResolver for PR choice
892
- const prResolver = new FlagResolver({
893
- commandName: 'work spawn',
894
- baseCommand: 'prlt work spawn',
895
- jsonMode,
896
- flags: {},
897
- });
898
- prResolver.addPrompt({
899
- flagName: 'prChoice',
900
- type: 'list',
901
- message: 'Create pull requests when work is ready?',
902
- default: 'yes',
903
- choices: () => [
904
- { name: '✓ Yes - Create PR for each ticket', value: 'yes' },
905
- { name: '✗ No - Just move tickets to review', value: 'no' },
906
- ],
907
- });
908
- const prResult = await prResolver.resolve();
909
- batchCreatePr = prResult.prChoice === 'yes';
910
- 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
+ }
911
973
  }
912
974
  else {
913
975
  // Non-code-modifying action - no PR needed
@@ -926,10 +988,14 @@ export default class WorkSpawn extends PMOCommand {
926
988
  // Spawn each ticket - work:start will create ephemeral agents on-demand
927
989
  let successCount = 0;
928
990
  let failCount = 0;
991
+ // Track execution results for JSON output
992
+ const executionResults = [];
929
993
  // Process sequentially for clear logging and resource management
930
994
  for (const ticket of ticketsToSpawn) {
931
995
  try {
932
- 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
+ }
933
999
  // Build args for work:start
934
1000
  // IMPORTANT: Pass --project to avoid re-prompting for project selection
935
1001
  // Pass --ephemeral to signal work:start should create an ephemeral agent
@@ -937,6 +1003,10 @@ export default class WorkSpawn extends PMOCommand {
937
1003
  // Pass clone flag if specified
938
1004
  if (flags.clone)
939
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
+ }
940
1010
  if (flags['per-ticket']) {
941
1011
  // Per-ticket mode: only pass display flag, let start prompt for the rest
942
1012
  // batchDisplayMode is for devcontainer, batchDisplay is for host
@@ -996,16 +1066,43 @@ export default class WorkSpawn extends PMOCommand {
996
1066
  // eslint-disable-next-line no-await-in-loop
997
1067
  await this.config.runCommand('work:start', startArgs);
998
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
+ });
999
1076
  }
1000
1077
  catch (error) {
1001
1078
  failCount++;
1002
- 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
+ });
1003
1089
  }
1004
1090
  }
1005
- 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
+ });
1006
1096
  db.close();
1007
- this.log('');
1008
- 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
+ }
1009
1106
  }
1010
1107
  catch (error) {
1011
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>;