@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
@@ -2,10 +2,9 @@ import { Args, Flags } from '@oclif/core';
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import { execSync } from 'node:child_process';
5
- import inquirer from 'inquirer';
6
5
  import Database from 'better-sqlite3';
7
6
  import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
8
- import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
7
+ import { shouldOutputJson, outputErrorAsJson, createMetadata, outputConfirmationNeededAsJson, outputExecutionResultAsJson, } from '../../lib/prompt-json.js';
9
8
  import { FlagResolver } from '../../lib/flags/index.js';
10
9
  import { getWorkColumnSetting, findColumnByName } from '../../lib/pmo/utils.js';
11
10
  import { styles } from '../../lib/styles.js';
@@ -178,6 +177,11 @@ export default class WorkStart extends PMOCommand {
178
177
  description: 'Use independent git clone instead of worktree (more isolation, no real-time sync)',
179
178
  default: false,
180
179
  }),
180
+ yes: Flags.boolean({
181
+ char: 'y',
182
+ description: 'Skip confirmation prompt (for non-TTY/scripted execution)',
183
+ default: false,
184
+ }),
181
185
  };
182
186
  async execute() {
183
187
  const { args, flags } = await this.parse(WorkStart);
@@ -198,6 +202,7 @@ export default class WorkStart extends PMOCommand {
198
202
  }
199
203
  // Check if JSON output mode is active
200
204
  const jsonMode = shouldOutputJson(flags);
205
+ const jsonModeConfig = jsonMode ? { flags: flags, commandName: 'work start' } : null;
201
206
  // Helper to handle errors in JSON mode
202
207
  const handleError = (code, message) => {
203
208
  if (jsonMode) {
@@ -253,6 +258,68 @@ export default class WorkStart extends PMOCommand {
253
258
  db.close();
254
259
  return handleError('TICKET_NOT_FOUND', `Ticket "${ticketId}" not found.`);
255
260
  }
261
+ // In JSON mode with explicit flags, implement two-step confirm-then-execute protocol
262
+ if (jsonMode) {
263
+ // Check if all required flags for non-interactive execution are provided
264
+ const hasAction = !!(flags.action || flags.prompt);
265
+ const hasDisplay = !!(flags.display || flags['run-on-host']);
266
+ const hasPermissions = !!(flags['permission-mode'] || flags['skip-permissions']);
267
+ const hasAgent = !!(flags.ephemeral || flags.agent);
268
+ const allFlagsProvided = hasAction && hasDisplay && hasPermissions && hasAgent;
269
+ if (allFlagsProvided && !flags.yes) {
270
+ // All flags provided but no --yes: return confirmation_needed with plan
271
+ const metadata = createMetadata('work start', flags);
272
+ // Build the confirm command with --yes
273
+ let confirmCmd = `prlt work start ${ticketId}`;
274
+ if (flags.action)
275
+ confirmCmd += ` --action ${flags.action}`;
276
+ if (flags.prompt)
277
+ confirmCmd += ` --prompt "${flags.prompt}"`;
278
+ if (flags.display)
279
+ confirmCmd += ` --display ${flags.display}`;
280
+ if (flags['run-on-host'])
281
+ confirmCmd += ' --run-on-host';
282
+ if (flags['permission-mode'])
283
+ confirmCmd += ` --permission-mode ${flags['permission-mode']}`;
284
+ if (flags['skip-permissions'])
285
+ confirmCmd += ' --skip-permissions';
286
+ if (flags.ephemeral)
287
+ confirmCmd += ' --ephemeral';
288
+ if (flags.agent)
289
+ confirmCmd += ` --agent ${flags.agent}`;
290
+ if (flags.executor)
291
+ confirmCmd += ` --executor ${flags.executor}`;
292
+ if (flags.session)
293
+ confirmCmd += ` --session ${flags.session}`;
294
+ if (flags['create-pr'])
295
+ confirmCmd += ' --create-pr';
296
+ if (flags['no-pr'])
297
+ confirmCmd += ' --no-pr';
298
+ if (flags.clone)
299
+ confirmCmd += ' --clone';
300
+ if (flags.focus)
301
+ confirmCmd += ' --focus';
302
+ if (flags.force)
303
+ confirmCmd += ' --force';
304
+ confirmCmd += ' --yes';
305
+ const plan = {
306
+ ticket: {
307
+ id: ticket.id,
308
+ title: ticket.title,
309
+ status: ticket.statusName,
310
+ },
311
+ action: flags.action || 'custom',
312
+ display: flags.display || (flags['run-on-host'] ? 'host' : 'devcontainer'),
313
+ permissions: (flags['permission-mode'] || (flags['skip-permissions'] ? 'danger' : 'safe')),
314
+ agent: flags.agent || 'ephemeral',
315
+ };
316
+ db.close();
317
+ outputConfirmationNeededAsJson(plan, confirmCmd, `Ready to start work on ${ticketId}. Run with --yes to execute.`, metadata);
318
+ return;
319
+ }
320
+ // If --yes is set with all flags, continue to execution (don't return)
321
+ // If missing flags, continue and let FlagResolver handle prompts
322
+ }
256
323
  // Check if ticket is blocked by dependencies
257
324
  const isBlocked = await this.storage.isTicketBlocked(ticketId);
258
325
  if (isBlocked && !flags.force) {
@@ -336,15 +403,22 @@ export default class WorkStart extends PMOCommand {
336
403
  let isEphemeralAgent = flags.ephemeral;
337
404
  if (flags.ephemeral) {
338
405
  // Create ephemeral agent on-demand
339
- this.log(styles.muted('Creating ephemeral agent...'));
406
+ if (!jsonMode) {
407
+ this.log(styles.muted('Creating ephemeral agent...'));
408
+ }
340
409
  const ephemeralResult = await createEphemeralAgent(workspaceInfo, {
341
410
  skipDevcontainer: flags['run-on-host'],
342
- log: (msg) => this.log(msg),
411
+ log: (msg) => {
412
+ if (!jsonMode)
413
+ this.log(msg);
414
+ },
343
415
  mountMode: flags.clone ? 'clone' : 'worktree',
344
416
  });
345
417
  agentName = ephemeralResult.name;
346
418
  agentWorktreePath = ephemeralResult.worktreePath;
347
- this.log(styles.success(`Created ephemeral agent: ${agentName}`));
419
+ if (!jsonMode) {
420
+ this.log(styles.success(`Created ephemeral agent: ${agentName}`));
421
+ }
348
422
  }
349
423
  else if (flags.agent) {
350
424
  // Agent specified via flag
@@ -737,7 +811,7 @@ export default class WorkStart extends PMOCommand {
737
811
  let environmentSelected = false;
738
812
  while (!environmentSelected) {
739
813
  // eslint-disable-next-line no-await-in-loop -- Interactive loop with retry on Docker check
740
- const { selectedEnvironment } = await inquirer.prompt([
814
+ const { selectedEnvironment } = await this.prompt([
741
815
  {
742
816
  type: 'list',
743
817
  name: 'selectedEnvironment',
@@ -745,7 +819,7 @@ export default class WorkStart extends PMOCommand {
745
819
  choices: envChoices,
746
820
  default: devcontainerReady ? 'devcontainer' : 'host',
747
821
  },
748
- ]);
822
+ ], jsonModeConfig);
749
823
  if (selectedEnvironment === 'cancel') {
750
824
  db.close();
751
825
  this.log(styles.muted('Cancelled.'));
@@ -812,19 +886,19 @@ export default class WorkStart extends PMOCommand {
812
886
  environment = 'host';
813
887
  // Skip to host mode prompts
814
888
  // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after user selection
815
- const { selectedDisplay } = await inquirer.prompt([
889
+ const { selectedDisplay } = await this.prompt([
816
890
  {
817
891
  type: 'list',
818
892
  name: 'selectedDisplay',
819
893
  message: 'How should the agent output be displayed?',
820
894
  choices: [
821
- { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
822
- { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground' },
823
- { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background' },
895
+ { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal', command: `prlt work start ${ticketId} --display terminal --run-on-host --json` },
896
+ { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground', command: `prlt work start ${ticketId} --display foreground --run-on-host --json` },
897
+ { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background', command: `prlt work start ${ticketId} --display background --run-on-host --json` },
824
898
  ],
825
899
  default: 'terminal',
826
900
  },
827
- ]);
901
+ ], jsonModeConfig);
828
902
  displayMode = selectedDisplay;
829
903
  environmentSelected = true;
830
904
  continue;
@@ -834,19 +908,19 @@ export default class WorkStart extends PMOCommand {
834
908
  environment = 'devcontainer';
835
909
  // Pick display mode for devcontainer
836
910
  // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after selection
837
- const { selectedDisplay } = await inquirer.prompt([
911
+ const { selectedDisplay } = await this.prompt([
838
912
  {
839
913
  type: 'list',
840
914
  name: 'selectedDisplay',
841
915
  message: 'How should the agent output be displayed?',
842
916
  choices: [
843
- { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
844
- { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground' },
845
- { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background' },
917
+ { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal', command: `prlt work start ${ticketId} --display terminal --json` },
918
+ { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground', command: `prlt work start ${ticketId} --display foreground --json` },
919
+ { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background', command: `prlt work start ${ticketId} --display background --json` },
846
920
  ],
847
921
  default: 'terminal',
848
922
  },
849
- ]);
923
+ ], jsonModeConfig);
850
924
  displayMode = selectedDisplay;
851
925
  environment = 'devcontainer';
852
926
  environmentSelected = true;
@@ -855,19 +929,19 @@ export default class WorkStart extends PMOCommand {
855
929
  // User chose host
856
930
  environment = 'host';
857
931
  // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after selection
858
- const { selectedDisplay } = await inquirer.prompt([
932
+ const { selectedDisplay } = await this.prompt([
859
933
  {
860
934
  type: 'list',
861
935
  name: 'selectedDisplay',
862
936
  message: 'How should the agent output be displayed?',
863
937
  choices: [
864
- { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
865
- { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground' },
866
- { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background' },
938
+ { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal', command: `prlt work start ${ticketId} --display terminal --run-on-host --json` },
939
+ { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground', command: `prlt work start ${ticketId} --display foreground --run-on-host --json` },
940
+ { name: '📦 Background - Runs detached, reattach with: prlt session attach', value: 'background', command: `prlt work start ${ticketId} --display background --run-on-host --json` },
867
941
  ],
868
942
  default: 'terminal',
869
943
  },
870
- ]);
944
+ ], jsonModeConfig);
871
945
  displayMode = selectedDisplay;
872
946
  environmentSelected = true;
873
947
  }
@@ -924,92 +998,98 @@ export default class WorkStart extends PMOCommand {
924
998
  if (environment === 'devcontainer') {
925
999
  const hasCredentials = dockerCredentialsExist();
926
1000
  if (!hasCredentials) {
927
- this.log('');
928
- this.log(styles.warning('⚠️ No Claude Code credentials found for Docker containers'));
929
- this.log(styles.muted(' Agents will fail with 401 authentication errors without credentials.'));
930
- this.log('');
931
- // Use FlagResolver for auth action
932
- const authResolver = new FlagResolver({
933
- commandName: 'work start',
934
- baseCommand: `prlt work start ${ticketId}`,
935
- jsonMode,
936
- flags: {},
937
- });
938
- authResolver.addPrompt({
939
- flagName: 'authAction',
940
- type: 'list',
941
- message: 'What would you like to do?',
942
- choices: () => [
943
- { name: `🔐 Run ${this.config.bin} agent auth now (one-time setup)`, value: 'auth' },
944
- { name: '💻 Switch to host environment instead', value: 'host' },
945
- { name: '⏩ Continue anyway (must run /login in first agent)', value: 'continue' },
946
- { name: '✗ Cancel', value: 'cancel' },
947
- ],
948
- });
949
- const authResult = await authResolver.resolve();
950
- const authAction = authResult.authAction;
951
- if (authAction === 'cancel') {
952
- db.close();
953
- this.log(styles.muted('Cancelled.'));
954
- return;
1001
+ // In JSON mode with --yes, continue anyway (agent can run /login)
1002
+ if (jsonMode && flags.yes) {
1003
+ // Continue without prompting - agent will need to handle auth
955
1004
  }
956
- if (authAction === 'host') {
957
- environment = 'host';
958
- this.log(styles.muted('Switched to host environment.'));
959
- }
960
- else if (authAction === 'auth') {
1005
+ else {
961
1006
  this.log('');
962
- this.log(styles.primary(`Opening ${this.config.bin} agent auth in new tab...`));
1007
+ this.log(styles.warning('⚠️ No Claude Code credentials found for Docker containers'));
1008
+ this.log(styles.muted(' Agents will fail with 401 authentication errors without credentials.'));
963
1009
  this.log('');
964
- // Open auth in a new terminal tab
965
- const authCmd = `${process.argv[1]} agent auth`;
966
- try {
967
- execSync(`osascript -e '
968
- tell application "iTerm"
969
- tell current window
970
- create tab with default profile
971
- tell current session
972
- write text "${authCmd}"
973
- end tell
974
- end tell
975
- end tell
976
- '`);
1010
+ // Use FlagResolver for auth action
1011
+ const authResolver = new FlagResolver({
1012
+ commandName: 'work start',
1013
+ baseCommand: `prlt work start ${ticketId}`,
1014
+ jsonMode,
1015
+ flags: {},
1016
+ });
1017
+ authResolver.addPrompt({
1018
+ flagName: 'authAction',
1019
+ type: 'list',
1020
+ message: 'What would you like to do?',
1021
+ choices: () => [
1022
+ { name: `🔐 Run ${this.config.bin} agent auth now (one-time setup)`, value: 'auth' },
1023
+ { name: '💻 Switch to host environment instead', value: 'host' },
1024
+ { name: '⏩ Continue anyway (must run /login in first agent)', value: 'continue' },
1025
+ { name: '✗ Cancel', value: 'cancel' },
1026
+ ],
1027
+ });
1028
+ const authResult = await authResolver.resolve();
1029
+ const authAction = authResult.authAction;
1030
+ if (authAction === 'cancel') {
1031
+ db.close();
1032
+ this.log(styles.muted('Cancelled.'));
1033
+ return;
977
1034
  }
978
- catch {
979
- // Fallback: try Terminal.app
1035
+ if (authAction === 'host') {
1036
+ environment = 'host';
1037
+ this.log(styles.muted('Switched to host environment.'));
1038
+ }
1039
+ else if (authAction === 'auth') {
1040
+ this.log('');
1041
+ this.log(styles.primary(`Opening ${this.config.bin} agent auth in new tab...`));
1042
+ this.log('');
1043
+ // Open auth in a new terminal tab
1044
+ const authCmd = `${process.argv[1]} agent auth`;
980
1045
  try {
981
- execSync(`osascript -e 'tell application "Terminal" to do script "${authCmd}"'`);
1046
+ execSync(`osascript -e '
1047
+ tell application "iTerm"
1048
+ tell current window
1049
+ create tab with default profile
1050
+ tell current session
1051
+ write text "${authCmd}"
1052
+ end tell
1053
+ end tell
1054
+ end tell
1055
+ '`);
982
1056
  }
983
1057
  catch {
984
- this.log(styles.warning('Could not open new terminal tab.'));
985
- this.log(styles.muted(`Please run manually: ${authCmd}`));
1058
+ // Fallback: try Terminal.app
1059
+ try {
1060
+ execSync(`osascript -e 'tell application "Terminal" to do script "${authCmd}"'`);
1061
+ }
1062
+ catch {
1063
+ this.log(styles.warning('Could not open new terminal tab.'));
1064
+ this.log(styles.muted(`Please run manually: ${authCmd}`));
1065
+ }
1066
+ }
1067
+ this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
1068
+ this.log('');
1069
+ // Wait for user to complete auth
1070
+ await this.prompt([{
1071
+ type: 'input',
1072
+ name: 'done',
1073
+ message: 'Press Enter when authentication is complete:',
1074
+ }]);
1075
+ // Check if credentials now exist
1076
+ if (!dockerCredentialsExist()) {
1077
+ this.log('');
1078
+ this.log(styles.warning('Authentication did not complete. No credentials found.'));
1079
+ db.close();
1080
+ return;
1081
+ }
1082
+ const info = getDockerCredentialInfo();
1083
+ this.log('');
1084
+ this.log(styles.success('✓ Credentials configured'));
1085
+ if (info) {
1086
+ this.log(styles.muted(` Subscription: ${info.subscriptionType || 'unknown'}`));
1087
+ this.log(styles.muted(` Expires: ${info.expiresAt.toLocaleDateString()}`));
986
1088
  }
987
- }
988
- this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
989
- this.log('');
990
- // Wait for user to complete auth
991
- await inquirer.prompt([{
992
- type: 'input',
993
- name: 'done',
994
- message: 'Press Enter when authentication is complete:',
995
- }]);
996
- // Check if credentials now exist
997
- if (!dockerCredentialsExist()) {
998
1089
  this.log('');
999
- this.log(styles.warning('Authentication did not complete. No credentials found.'));
1000
- db.close();
1001
- return;
1002
- }
1003
- const info = getDockerCredentialInfo();
1004
- this.log('');
1005
- this.log(styles.success('✓ Credentials configured'));
1006
- if (info) {
1007
- this.log(styles.muted(` Subscription: ${info.subscriptionType || 'unknown'}`));
1008
- this.log(styles.muted(` Expires: ${info.expiresAt.toLocaleDateString()}`));
1009
1090
  }
1010
- this.log('');
1091
+ // authAction === 'continue' falls through
1011
1092
  }
1012
- // authAction === 'continue' falls through
1013
1093
  }
1014
1094
  }
1015
1095
  // Prompt for permissions mode (all environments)
@@ -1054,50 +1134,58 @@ export default class WorkStart extends PMOCommand {
1054
1134
  createPR = false;
1055
1135
  }
1056
1136
  else if (ghAvailable) {
1057
- // Use FlagResolver for PR choice
1058
- const prResolver = new FlagResolver({
1059
- commandName: 'work start',
1060
- baseCommand: `prlt work start ${ticketId}`,
1061
- jsonMode,
1062
- flags: {},
1063
- });
1064
- prResolver.addPrompt({
1065
- flagName: 'prChoice',
1066
- type: 'list',
1067
- message: 'Create a pull request when work is ready?',
1068
- default: 'yes',
1069
- choices: () => [
1070
- { name: '✓ Yes - Create PR when running `prlt work ready`', value: 'yes' },
1071
- { name: '✗ No - Just move ticket to review (can create PR later)', value: 'no' },
1072
- ],
1073
- });
1074
- const prResult = await prResolver.resolve();
1075
- createPR = prResult.prChoice === 'yes';
1076
- }
1077
- // Show execution info
1078
- this.log('');
1079
- this.log(styles.header(`🚀 Starting work: ${ticket.id}: ${ticket.title}`));
1080
- this.log(styles.muted(` Agent: ${assignedAgent}`));
1081
- this.log(styles.muted(` Action: ${context.actionName || 'None'}`));
1082
- this.log(styles.muted(` Executor: ${executor}`));
1083
- // Environment info
1084
- const envIcon = environment === 'devcontainer' ? '🐳' : '💻';
1085
- this.log(styles.muted(` Environment: ${envIcon} ${environment}`));
1086
- this.log(styles.muted(` Display: ${displayMode}`));
1087
- // Permissions info
1088
- if (sandboxed) {
1089
- this.log(styles.success(` Permissions: 🔒 safe`));
1090
- }
1091
- else {
1092
- this.log(styles.warning(` Permissions: ⚠️ danger (--dangerously-skip-permissions)`));
1137
+ // In JSON mode with --yes, default to creating PR for code-modifying actions
1138
+ if (jsonMode && flags.yes) {
1139
+ createPR = context.modifiesCode !== false;
1140
+ }
1141
+ else {
1142
+ // Use FlagResolver for PR choice
1143
+ const prResolver = new FlagResolver({
1144
+ commandName: 'work start',
1145
+ baseCommand: `prlt work start ${ticketId}`,
1146
+ jsonMode,
1147
+ flags: {},
1148
+ });
1149
+ prResolver.addPrompt({
1150
+ flagName: 'prChoice',
1151
+ type: 'list',
1152
+ message: 'Create a pull request when work is ready?',
1153
+ default: 'yes',
1154
+ choices: () => [
1155
+ { name: '✓ Yes - Create PR when running `prlt work ready`', value: 'yes' },
1156
+ { name: '✗ No - Just move ticket to review (can create PR later)', value: 'no' },
1157
+ ],
1158
+ });
1159
+ const prResult = await prResolver.resolve();
1160
+ createPR = prResult.prChoice === 'yes';
1161
+ }
1093
1162
  }
1094
- this.log(styles.muted(` Output: ${outputMode === 'interactive' ? 'streaming (watch Claude work)' : 'print (final result only)'}`));
1095
- if (ghAvailable) {
1096
- this.log(styles.muted(` Create PR: ${createPR ? 'yes (when work is ready)' : 'no'}`));
1163
+ // Show execution info (skip in JSON mode)
1164
+ if (!jsonMode) {
1165
+ this.log('');
1166
+ this.log(styles.header(`🚀 Starting work: ${ticket.id}: ${ticket.title}`));
1167
+ this.log(styles.muted(` Agent: ${assignedAgent}`));
1168
+ this.log(styles.muted(` Action: ${context.actionName || 'None'}`));
1169
+ this.log(styles.muted(` Executor: ${executor}`));
1170
+ // Environment info
1171
+ const envIcon = environment === 'devcontainer' ? '🐳' : '💻';
1172
+ this.log(styles.muted(` Environment: ${envIcon} ${environment}`));
1173
+ this.log(styles.muted(` Display: ${displayMode}`));
1174
+ // Permissions info
1175
+ if (sandboxed) {
1176
+ this.log(styles.success(` Permissions: 🔒 safe`));
1177
+ }
1178
+ else {
1179
+ this.log(styles.warning(` Permissions: ⚠️ danger (--dangerously-skip-permissions)`));
1180
+ }
1181
+ this.log(styles.muted(` Output: ${outputMode === 'interactive' ? 'streaming (watch Claude work)' : 'print (final result only)'}`));
1182
+ if (ghAvailable) {
1183
+ this.log(styles.muted(` Create PR: ${createPR ? 'yes (when work is ready)' : 'no'}`));
1184
+ }
1185
+ this.log(styles.muted(` Worktree: ${worktreePath}`));
1186
+ this.log(styles.muted(` Branch: ${branch}`));
1187
+ this.log('');
1097
1188
  }
1098
- this.log(styles.muted(` Worktree: ${worktreePath}`));
1099
- this.log(styles.muted(` Branch: ${branch}`));
1100
- this.log('');
1101
1189
  // Add createPR to context
1102
1190
  context.createPR = createPR;
1103
1191
  // Handle git operations
@@ -1149,14 +1237,14 @@ export default class WorkStart extends PMOCommand {
1149
1237
  const branchChoice = branchResult.branchChoice;
1150
1238
  if (branchChoice === 'enter') {
1151
1239
  // User enters existing branch name
1152
- const { enteredBranch } = await inquirer.prompt([
1240
+ const { enteredBranch } = await this.prompt([
1153
1241
  {
1154
1242
  type: 'input',
1155
1243
  name: 'enteredBranch',
1156
1244
  message: 'Enter branch name:',
1157
1245
  validate: (input) => input.trim() ? true : 'Branch name required',
1158
1246
  },
1159
- ]);
1247
+ ], jsonModeConfig);
1160
1248
  finalBranch = enteredBranch.trim();
1161
1249
  // Validate branch exists (locally or in origin)
1162
1250
  try {
@@ -1190,18 +1278,17 @@ export default class WorkStart extends PMOCommand {
1190
1278
  }
1191
1279
  if (remoteBranches.length > 0) {
1192
1280
  const branchChoices = [
1193
- ...remoteBranches.map(b => ({ name: b, value: b.replace('origin/', '') })),
1194
- new inquirer.Separator(),
1195
- { name: 'None of these, create new branch', value: '__create__' },
1281
+ ...remoteBranches.map(b => ({ name: b, value: b.replace('origin/', ''), command: `prlt work start ${ticketId} --json` })),
1282
+ { name: '── None of these, create new branch ──', value: '__create__', command: `prlt work start ${ticketId} --json` },
1196
1283
  ];
1197
- const { selectedBranch } = await inquirer.prompt([
1284
+ const { selectedBranch } = await this.prompt([
1198
1285
  {
1199
1286
  type: 'list',
1200
1287
  name: 'selectedBranch',
1201
1288
  message: `Found ${remoteBranches.length} matching branch(es):`,
1202
1289
  choices: branchChoices,
1203
1290
  },
1204
- ]);
1291
+ ], jsonModeConfig);
1205
1292
  if (selectedBranch !== '__create__') {
1206
1293
  finalBranch = selectedBranch;
1207
1294
  // Fetch and checkout the selected branch
@@ -1285,8 +1372,10 @@ export default class WorkStart extends PMOCommand {
1285
1372
  sandboxed,
1286
1373
  branch,
1287
1374
  });
1288
- this.log(styles.muted(` Work ID: ${execution.id}`));
1289
- this.log('');
1375
+ if (!jsonMode) {
1376
+ this.log(styles.muted(` Work ID: ${execution.id}`));
1377
+ this.log('');
1378
+ }
1290
1379
  // Note: Ticket status update moved to after successful spawn (see below)
1291
1380
  // Load execution config from database
1292
1381
  const executionConfig = loadExecutionConfig(db);
@@ -1327,7 +1416,9 @@ export default class WorkStart extends PMOCommand {
1327
1416
  executionConfig.terminal.openInBackground = false;
1328
1417
  }
1329
1418
  // Run execution
1330
- this.log(styles.muted('Starting agent...'));
1419
+ if (!jsonMode) {
1420
+ this.log(styles.muted('Starting agent...'));
1421
+ }
1331
1422
  const sessionManager = (flags.session || 'tmux');
1332
1423
  const result = await runExecution(environment, context, executor, executionConfig, {
1333
1424
  host: flags['vm-host'],
@@ -1381,18 +1472,47 @@ export default class WorkStart extends PMOCommand {
1381
1472
  this.warn(`Could not move ticket to "${targetColumnName}": ${moveError instanceof Error ? moveError.message : moveError}`);
1382
1473
  }
1383
1474
  }
1384
- await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
1385
- this.log('');
1386
- this.log(styles.success(`✓ Work started (${execution.id})`));
1387
- this.log('');
1388
- this.log(styles.muted('Commands:'));
1389
- this.log(styles.muted(` prlt work status View work status`));
1390
- this.log(styles.muted(` prlt work ready ${ticketId} Mark ready for review`));
1391
- this.log(styles.muted(` prlt work stop ${execution.id} Stop work`));
1475
+ await autoExportToBoard(this.pmoPath, this.storage, (msg) => {
1476
+ if (!jsonMode) {
1477
+ this.log(styles.muted(msg));
1478
+ }
1479
+ });
1480
+ // Output results
1481
+ if (jsonMode) {
1482
+ // Output JSON execution result
1483
+ outputExecutionResultAsJson([{
1484
+ workId: execution.id,
1485
+ ticketId: ticket.id,
1486
+ agent: assignedAgent,
1487
+ sessionId: result.sessionId,
1488
+ containerId: result.containerId,
1489
+ status: 'running',
1490
+ }], 1, 0, createMetadata('work start', flags));
1491
+ }
1492
+ else {
1493
+ this.log('');
1494
+ this.log(styles.success(`✓ Work started (${execution.id})`));
1495
+ this.log('');
1496
+ this.log(styles.muted('Commands:'));
1497
+ this.log(styles.muted(` prlt work status View work status`));
1498
+ this.log(styles.muted(` prlt work ready ${ticketId} Mark ready for review`));
1499
+ this.log(styles.muted(` prlt work stop ${execution.id} Stop work`));
1500
+ }
1392
1501
  }
1393
1502
  else {
1394
1503
  executionStorage.updateStatus(execution.id, 'failed');
1395
- this.error(`Failed to start work: ${result.error}`);
1504
+ if (jsonMode) {
1505
+ // Output JSON failure result
1506
+ outputExecutionResultAsJson([{
1507
+ workId: execution.id,
1508
+ ticketId: ticket.id,
1509
+ agent: assignedAgent,
1510
+ status: 'failed',
1511
+ }], 0, 1, createMetadata('work start', flags));
1512
+ }
1513
+ else {
1514
+ this.error(`Failed to start work: ${result.error}`);
1515
+ }
1396
1516
  }
1397
1517
  db.close();
1398
1518
  }
@@ -1405,6 +1525,8 @@ export default class WorkStart extends PMOCommand {
1405
1525
  * Run batch mode: spawn work for all unassigned backlog tickets
1406
1526
  */
1407
1527
  async runBatchMode(workspaceInfo, db, executionStorage, flags) {
1528
+ const batchJsonMode = shouldOutputJson(flags);
1529
+ const batchJsonModeConfig = batchJsonMode ? { flags: flags, commandName: 'work start' } : null;
1408
1530
  // Get all tickets and filter to backlog/unstarted (not in progress)
1409
1531
  // Note: In batch mode, we get all tickets across all projects (pass undefined for projectId)
1410
1532
  // eslint-disable-next-line unicorn/no-useless-undefined
@@ -1441,17 +1563,17 @@ export default class WorkStart extends PMOCommand {
1441
1563
  this.log(styles.muted(`Tickets to spawn: ${backlogTickets.map(t => t.id).join(', ')}`));
1442
1564
  this.log('');
1443
1565
  // Confirm before batch spawning
1444
- const { confirm } = await inquirer.prompt([
1566
+ const { confirm } = await this.prompt([
1445
1567
  {
1446
1568
  type: 'list',
1447
1569
  name: 'confirm',
1448
1570
  message: `Start work on ${backlogTickets.length} tickets using ${availableAgents.length} available agents?`,
1449
1571
  choices: [
1450
- { name: 'Yes', value: true },
1451
- { name: 'No', value: false },
1572
+ { name: 'Yes', value: true, command: 'prlt work start --all --json' },
1573
+ { name: 'No', value: false, command: '' },
1452
1574
  ],
1453
1575
  },
1454
- ]);
1576
+ ], batchJsonModeConfig);
1455
1577
  if (!confirm) {
1456
1578
  db.close();
1457
1579
  this.log(styles.muted('Cancelled.'));
@@ -1460,18 +1582,18 @@ export default class WorkStart extends PMOCommand {
1460
1582
  // Prompt for permissions mode once for all tickets (TKT-513)
1461
1583
  let batchPermissionMode = flags['permission-mode'];
1462
1584
  if (!batchPermissionMode) {
1463
- const { permissionMode } = await inquirer.prompt([
1585
+ const { permissionMode } = await this.prompt([
1464
1586
  {
1465
1587
  type: 'list',
1466
1588
  name: 'permissionMode',
1467
1589
  message: 'Permission mode for Claude Code:',
1468
1590
  choices: [
1469
- { name: '⚠️ danger - Skip permission checks (faster, container provides isolation)', value: 'danger' },
1470
- { name: '🔒 safe - Requires approval for dangerous operations', value: 'safe' },
1591
+ { name: '⚠️ danger - Skip permission checks (faster, container provides isolation)', value: 'danger', command: 'prlt work start --all --permission-mode danger --json' },
1592
+ { name: '🔒 safe - Requires approval for dangerous operations', value: 'safe', command: 'prlt work start --all --permission-mode safe --json' },
1471
1593
  ],
1472
1594
  default: 'danger',
1473
1595
  },
1474
- ]);
1596
+ ], batchJsonModeConfig);
1475
1597
  batchPermissionMode = permissionMode;
1476
1598
  }
1477
1599
  // Check Docker credentials if any agents use devcontainers
@@ -1486,18 +1608,18 @@ export default class WorkStart extends PMOCommand {
1486
1608
  this.log(styles.warning('⚠️ No Claude Code credentials found for Docker containers'));
1487
1609
  this.log(styles.muted(' Agents will fail with 401 authentication errors without credentials.'));
1488
1610
  this.log('');
1489
- const { authAction } = await inquirer.prompt([
1611
+ const { authAction } = await this.prompt([
1490
1612
  {
1491
1613
  type: 'list',
1492
1614
  name: 'authAction',
1493
1615
  message: 'What would you like to do?',
1494
1616
  choices: [
1495
- { name: `🔐 Run ${this.config.bin} agent auth now (one-time setup)`, value: 'auth' },
1496
- { name: '💻 Run all agents on host instead (--run-on-host)', value: 'host' },
1497
- { name: '✗ Cancel', value: 'cancel' },
1617
+ { name: `🔐 Run ${this.config.bin} agent auth now (one-time setup)`, value: 'auth', command: `${this.config.bin} agent auth` },
1618
+ { name: '💻 Run all agents on host instead (--run-on-host)', value: 'host', command: 'prlt work start --all --run-on-host --json' },
1619
+ { name: '✗ Cancel', value: 'cancel', command: '' },
1498
1620
  ],
1499
1621
  },
1500
- ]);
1622
+ ], batchJsonModeConfig);
1501
1623
  if (authAction === 'cancel') {
1502
1624
  db.close();
1503
1625
  this.log(styles.muted('Cancelled.'));
@@ -1538,11 +1660,11 @@ export default class WorkStart extends PMOCommand {
1538
1660
  this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
1539
1661
  this.log('');
1540
1662
  // Wait for user to complete auth
1541
- await inquirer.prompt([{
1663
+ await this.prompt([{
1542
1664
  type: 'input',
1543
1665
  name: 'done',
1544
1666
  message: 'Press Enter when authentication is complete:',
1545
- }]);
1667
+ }], batchJsonModeConfig);
1546
1668
  // Check if credentials now exist
1547
1669
  if (!dockerCredentialsExist()) {
1548
1670
  this.log('');