@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
@@ -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';
@@ -76,6 +75,7 @@ export default class WorkStart extends PMOCommand {
76
75
  '<%= config.bin %> <%= command.id %> TKT-001 --mode terminal',
77
76
  '<%= config.bin %> <%= command.id %> # Interactive mode',
78
77
  '<%= config.bin %> <%= command.id %> --all # Spawn all backlog tickets',
78
+ '<%= config.bin %> <%= command.id %> TKT-001 --prompt "Add unit tests for the API" # Custom prompt',
79
79
  ];
80
80
  static args = {
81
81
  ticketId: Args.string({
@@ -177,6 +177,11 @@ export default class WorkStart extends PMOCommand {
177
177
  description: 'Use independent git clone instead of worktree (more isolation, no real-time sync)',
178
178
  default: false,
179
179
  }),
180
+ yes: Flags.boolean({
181
+ char: 'y',
182
+ description: 'Skip confirmation prompt (for non-TTY/scripted execution)',
183
+ default: false,
184
+ }),
180
185
  };
181
186
  async execute() {
182
187
  const { args, flags } = await this.parse(WorkStart);
@@ -197,6 +202,7 @@ export default class WorkStart extends PMOCommand {
197
202
  }
198
203
  // Check if JSON output mode is active
199
204
  const jsonMode = shouldOutputJson(flags);
205
+ const jsonModeConfig = jsonMode ? { flags: flags, commandName: 'work start' } : null;
200
206
  // Helper to handle errors in JSON mode
201
207
  const handleError = (code, message) => {
202
208
  if (jsonMode) {
@@ -252,6 +258,68 @@ export default class WorkStart extends PMOCommand {
252
258
  db.close();
253
259
  return handleError('TICKET_NOT_FOUND', `Ticket "${ticketId}" not found.`);
254
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
+ }
255
323
  // Check if ticket is blocked by dependencies
256
324
  const isBlocked = await this.storage.isTicketBlocked(ticketId);
257
325
  if (isBlocked && !flags.force) {
@@ -335,15 +403,22 @@ export default class WorkStart extends PMOCommand {
335
403
  let isEphemeralAgent = flags.ephemeral;
336
404
  if (flags.ephemeral) {
337
405
  // Create ephemeral agent on-demand
338
- this.log(styles.muted('Creating ephemeral agent...'));
406
+ if (!jsonMode) {
407
+ this.log(styles.muted('Creating ephemeral agent...'));
408
+ }
339
409
  const ephemeralResult = await createEphemeralAgent(workspaceInfo, {
340
410
  skipDevcontainer: flags['run-on-host'],
341
- log: (msg) => this.log(msg),
411
+ log: (msg) => {
412
+ if (!jsonMode)
413
+ this.log(msg);
414
+ },
342
415
  mountMode: flags.clone ? 'clone' : 'worktree',
343
416
  });
344
417
  agentName = ephemeralResult.name;
345
418
  agentWorktreePath = ephemeralResult.worktreePath;
346
- this.log(styles.success(`Created ephemeral agent: ${agentName}`));
419
+ if (!jsonMode) {
420
+ this.log(styles.success(`Created ephemeral agent: ${agentName}`));
421
+ }
347
422
  }
348
423
  else if (flags.agent) {
349
424
  // Agent specified via flag
@@ -572,6 +647,11 @@ export default class WorkStart extends PMOCommand {
572
647
  customPrompt = flags.prompt;
573
648
  }
574
649
  else if (flags.action) {
650
+ // Handle special "custom" action - requires --prompt flag
651
+ if (flags.action === 'custom') {
652
+ db.close();
653
+ this.error('--action custom requires --prompt flag.\nUsage: prlt work start TKT-001 --action custom --prompt "your custom instructions"');
654
+ }
575
655
  // Specific action requested
576
656
  selectedAction = await this.storage.getAction(flags.action);
577
657
  if (!selectedAction) {
@@ -731,7 +811,7 @@ export default class WorkStart extends PMOCommand {
731
811
  let environmentSelected = false;
732
812
  while (!environmentSelected) {
733
813
  // eslint-disable-next-line no-await-in-loop -- Interactive loop with retry on Docker check
734
- const { selectedEnvironment } = await inquirer.prompt([
814
+ const { selectedEnvironment } = await this.prompt([
735
815
  {
736
816
  type: 'list',
737
817
  name: 'selectedEnvironment',
@@ -739,7 +819,7 @@ export default class WorkStart extends PMOCommand {
739
819
  choices: envChoices,
740
820
  default: devcontainerReady ? 'devcontainer' : 'host',
741
821
  },
742
- ]);
822
+ ], jsonModeConfig);
743
823
  if (selectedEnvironment === 'cancel') {
744
824
  db.close();
745
825
  this.log(styles.muted('Cancelled.'));
@@ -806,19 +886,19 @@ export default class WorkStart extends PMOCommand {
806
886
  environment = 'host';
807
887
  // Skip to host mode prompts
808
888
  // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after user selection
809
- const { selectedDisplay } = await inquirer.prompt([
889
+ const { selectedDisplay } = await this.prompt([
810
890
  {
811
891
  type: 'list',
812
892
  name: 'selectedDisplay',
813
893
  message: 'How should the agent output be displayed?',
814
894
  choices: [
815
- { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
816
- { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground' },
817
- { 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` },
818
898
  ],
819
899
  default: 'terminal',
820
900
  },
821
- ]);
901
+ ], jsonModeConfig);
822
902
  displayMode = selectedDisplay;
823
903
  environmentSelected = true;
824
904
  continue;
@@ -828,19 +908,19 @@ export default class WorkStart extends PMOCommand {
828
908
  environment = 'devcontainer';
829
909
  // Pick display mode for devcontainer
830
910
  // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after selection
831
- const { selectedDisplay } = await inquirer.prompt([
911
+ const { selectedDisplay } = await this.prompt([
832
912
  {
833
913
  type: 'list',
834
914
  name: 'selectedDisplay',
835
915
  message: 'How should the agent output be displayed?',
836
916
  choices: [
837
- { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
838
- { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground' },
839
- { 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` },
840
920
  ],
841
921
  default: 'terminal',
842
922
  },
843
- ]);
923
+ ], jsonModeConfig);
844
924
  displayMode = selectedDisplay;
845
925
  environment = 'devcontainer';
846
926
  environmentSelected = true;
@@ -849,19 +929,19 @@ export default class WorkStart extends PMOCommand {
849
929
  // User chose host
850
930
  environment = 'host';
851
931
  // eslint-disable-next-line no-await-in-loop -- Follow-up prompt after selection
852
- const { selectedDisplay } = await inquirer.prompt([
932
+ const { selectedDisplay } = await this.prompt([
853
933
  {
854
934
  type: 'list',
855
935
  name: 'selectedDisplay',
856
936
  message: 'How should the agent output be displayed?',
857
937
  choices: [
858
- { name: '🖥️ New tab - Opens in new terminal tab (recommended)', value: 'terminal' },
859
- { name: '▶️ Foreground - Run in current terminal (blocking)', value: 'foreground' },
860
- { 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` },
861
941
  ],
862
942
  default: 'terminal',
863
943
  },
864
- ]);
944
+ ], jsonModeConfig);
865
945
  displayMode = selectedDisplay;
866
946
  environmentSelected = true;
867
947
  }
@@ -918,92 +998,98 @@ export default class WorkStart extends PMOCommand {
918
998
  if (environment === 'devcontainer') {
919
999
  const hasCredentials = dockerCredentialsExist();
920
1000
  if (!hasCredentials) {
921
- this.log('');
922
- this.log(styles.warning('⚠️ No Claude Code credentials found for Docker containers'));
923
- this.log(styles.muted(' Agents will fail with 401 authentication errors without credentials.'));
924
- this.log('');
925
- // Use FlagResolver for auth action
926
- const authResolver = new FlagResolver({
927
- commandName: 'work start',
928
- baseCommand: `prlt work start ${ticketId}`,
929
- jsonMode,
930
- flags: {},
931
- });
932
- authResolver.addPrompt({
933
- flagName: 'authAction',
934
- type: 'list',
935
- message: 'What would you like to do?',
936
- choices: () => [
937
- { name: `🔐 Run ${this.config.bin} agent auth now (one-time setup)`, value: 'auth' },
938
- { name: '💻 Switch to host environment instead', value: 'host' },
939
- { name: '⏩ Continue anyway (must run /login in first agent)', value: 'continue' },
940
- { name: '✗ Cancel', value: 'cancel' },
941
- ],
942
- });
943
- const authResult = await authResolver.resolve();
944
- const authAction = authResult.authAction;
945
- if (authAction === 'cancel') {
946
- db.close();
947
- this.log(styles.muted('Cancelled.'));
948
- return;
949
- }
950
- if (authAction === 'host') {
951
- environment = 'host';
952
- this.log(styles.muted('Switched to host environment.'));
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
953
1004
  }
954
- else if (authAction === 'auth') {
1005
+ else {
955
1006
  this.log('');
956
- 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.'));
957
1009
  this.log('');
958
- // Open auth in a new terminal tab
959
- const authCmd = `${process.argv[1]} agent auth`;
960
- try {
961
- execSync(`osascript -e '
962
- tell application "iTerm"
963
- tell current window
964
- create tab with default profile
965
- tell current session
966
- write text "${authCmd}"
967
- end tell
968
- end tell
969
- end tell
970
- '`);
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;
971
1034
  }
972
- catch {
973
- // 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`;
974
1045
  try {
975
- 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
+ '`);
976
1056
  }
977
1057
  catch {
978
- this.log(styles.warning('Could not open new terminal tab.'));
979
- 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()}`));
980
1088
  }
981
- }
982
- this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
983
- this.log('');
984
- // Wait for user to complete auth
985
- await inquirer.prompt([{
986
- type: 'input',
987
- name: 'done',
988
- message: 'Press Enter when authentication is complete:',
989
- }]);
990
- // Check if credentials now exist
991
- if (!dockerCredentialsExist()) {
992
1089
  this.log('');
993
- this.log(styles.warning('Authentication did not complete. No credentials found.'));
994
- db.close();
995
- return;
996
- }
997
- const info = getDockerCredentialInfo();
998
- this.log('');
999
- this.log(styles.success('✓ Credentials configured'));
1000
- if (info) {
1001
- this.log(styles.muted(` Subscription: ${info.subscriptionType || 'unknown'}`));
1002
- this.log(styles.muted(` Expires: ${info.expiresAt.toLocaleDateString()}`));
1003
1090
  }
1004
- this.log('');
1091
+ // authAction === 'continue' falls through
1005
1092
  }
1006
- // authAction === 'continue' falls through
1007
1093
  }
1008
1094
  }
1009
1095
  // Prompt for permissions mode (all environments)
@@ -1048,50 +1134,58 @@ export default class WorkStart extends PMOCommand {
1048
1134
  createPR = false;
1049
1135
  }
1050
1136
  else if (ghAvailable) {
1051
- // Use FlagResolver for PR choice
1052
- const prResolver = new FlagResolver({
1053
- commandName: 'work start',
1054
- baseCommand: `prlt work start ${ticketId}`,
1055
- jsonMode,
1056
- flags: {},
1057
- });
1058
- prResolver.addPrompt({
1059
- flagName: 'prChoice',
1060
- type: 'list',
1061
- message: 'Create a pull request when work is ready?',
1062
- default: 'yes',
1063
- choices: () => [
1064
- { name: '✓ Yes - Create PR when running `prlt work ready`', value: 'yes' },
1065
- { name: '✗ No - Just move ticket to review (can create PR later)', value: 'no' },
1066
- ],
1067
- });
1068
- const prResult = await prResolver.resolve();
1069
- createPR = prResult.prChoice === 'yes';
1070
- }
1071
- // Show execution info
1072
- this.log('');
1073
- this.log(styles.header(`🚀 Starting work: ${ticket.id}: ${ticket.title}`));
1074
- this.log(styles.muted(` Agent: ${assignedAgent}`));
1075
- this.log(styles.muted(` Action: ${context.actionName || 'None'}`));
1076
- this.log(styles.muted(` Executor: ${executor}`));
1077
- // Environment info
1078
- const envIcon = environment === 'devcontainer' ? '🐳' : '💻';
1079
- this.log(styles.muted(` Environment: ${envIcon} ${environment}`));
1080
- this.log(styles.muted(` Display: ${displayMode}`));
1081
- // Permissions info
1082
- if (sandboxed) {
1083
- this.log(styles.success(` Permissions: 🔒 safe`));
1084
- }
1085
- else {
1086
- 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
+ }
1087
1162
  }
1088
- this.log(styles.muted(` Output: ${outputMode === 'interactive' ? 'streaming (watch Claude work)' : 'print (final result only)'}`));
1089
- if (ghAvailable) {
1090
- 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('');
1091
1188
  }
1092
- this.log(styles.muted(` Worktree: ${worktreePath}`));
1093
- this.log(styles.muted(` Branch: ${branch}`));
1094
- this.log('');
1095
1189
  // Add createPR to context
1096
1190
  context.createPR = createPR;
1097
1191
  // Handle git operations
@@ -1143,14 +1237,14 @@ export default class WorkStart extends PMOCommand {
1143
1237
  const branchChoice = branchResult.branchChoice;
1144
1238
  if (branchChoice === 'enter') {
1145
1239
  // User enters existing branch name
1146
- const { enteredBranch } = await inquirer.prompt([
1240
+ const { enteredBranch } = await this.prompt([
1147
1241
  {
1148
1242
  type: 'input',
1149
1243
  name: 'enteredBranch',
1150
1244
  message: 'Enter branch name:',
1151
1245
  validate: (input) => input.trim() ? true : 'Branch name required',
1152
1246
  },
1153
- ]);
1247
+ ], jsonModeConfig);
1154
1248
  finalBranch = enteredBranch.trim();
1155
1249
  // Validate branch exists (locally or in origin)
1156
1250
  try {
@@ -1184,18 +1278,17 @@ export default class WorkStart extends PMOCommand {
1184
1278
  }
1185
1279
  if (remoteBranches.length > 0) {
1186
1280
  const branchChoices = [
1187
- ...remoteBranches.map(b => ({ name: b, value: b.replace('origin/', '') })),
1188
- new inquirer.Separator(),
1189
- { 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` },
1190
1283
  ];
1191
- const { selectedBranch } = await inquirer.prompt([
1284
+ const { selectedBranch } = await this.prompt([
1192
1285
  {
1193
1286
  type: 'list',
1194
1287
  name: 'selectedBranch',
1195
1288
  message: `Found ${remoteBranches.length} matching branch(es):`,
1196
1289
  choices: branchChoices,
1197
1290
  },
1198
- ]);
1291
+ ], jsonModeConfig);
1199
1292
  if (selectedBranch !== '__create__') {
1200
1293
  finalBranch = selectedBranch;
1201
1294
  // Fetch and checkout the selected branch
@@ -1279,8 +1372,10 @@ export default class WorkStart extends PMOCommand {
1279
1372
  sandboxed,
1280
1373
  branch,
1281
1374
  });
1282
- this.log(styles.muted(` Work ID: ${execution.id}`));
1283
- this.log('');
1375
+ if (!jsonMode) {
1376
+ this.log(styles.muted(` Work ID: ${execution.id}`));
1377
+ this.log('');
1378
+ }
1284
1379
  // Note: Ticket status update moved to after successful spawn (see below)
1285
1380
  // Load execution config from database
1286
1381
  const executionConfig = loadExecutionConfig(db);
@@ -1321,7 +1416,9 @@ export default class WorkStart extends PMOCommand {
1321
1416
  executionConfig.terminal.openInBackground = false;
1322
1417
  }
1323
1418
  // Run execution
1324
- this.log(styles.muted('Starting agent...'));
1419
+ if (!jsonMode) {
1420
+ this.log(styles.muted('Starting agent...'));
1421
+ }
1325
1422
  const sessionManager = (flags.session || 'tmux');
1326
1423
  const result = await runExecution(environment, context, executor, executionConfig, {
1327
1424
  host: flags['vm-host'],
@@ -1375,18 +1472,47 @@ export default class WorkStart extends PMOCommand {
1375
1472
  this.warn(`Could not move ticket to "${targetColumnName}": ${moveError instanceof Error ? moveError.message : moveError}`);
1376
1473
  }
1377
1474
  }
1378
- await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
1379
- this.log('');
1380
- this.log(styles.success(`✓ Work started (${execution.id})`));
1381
- this.log('');
1382
- this.log(styles.muted('Commands:'));
1383
- this.log(styles.muted(` prlt work status View work status`));
1384
- this.log(styles.muted(` prlt work ready ${ticketId} Mark ready for review`));
1385
- 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
+ }
1386
1501
  }
1387
1502
  else {
1388
1503
  executionStorage.updateStatus(execution.id, 'failed');
1389
- 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
+ }
1390
1516
  }
1391
1517
  db.close();
1392
1518
  }
@@ -1399,6 +1525,8 @@ export default class WorkStart extends PMOCommand {
1399
1525
  * Run batch mode: spawn work for all unassigned backlog tickets
1400
1526
  */
1401
1527
  async runBatchMode(workspaceInfo, db, executionStorage, flags) {
1528
+ const batchJsonMode = shouldOutputJson(flags);
1529
+ const batchJsonModeConfig = batchJsonMode ? { flags: flags, commandName: 'work start' } : null;
1402
1530
  // Get all tickets and filter to backlog/unstarted (not in progress)
1403
1531
  // Note: In batch mode, we get all tickets across all projects (pass undefined for projectId)
1404
1532
  // eslint-disable-next-line unicorn/no-useless-undefined
@@ -1435,17 +1563,17 @@ export default class WorkStart extends PMOCommand {
1435
1563
  this.log(styles.muted(`Tickets to spawn: ${backlogTickets.map(t => t.id).join(', ')}`));
1436
1564
  this.log('');
1437
1565
  // Confirm before batch spawning
1438
- const { confirm } = await inquirer.prompt([
1566
+ const { confirm } = await this.prompt([
1439
1567
  {
1440
1568
  type: 'list',
1441
1569
  name: 'confirm',
1442
1570
  message: `Start work on ${backlogTickets.length} tickets using ${availableAgents.length} available agents?`,
1443
1571
  choices: [
1444
- { name: 'Yes', value: true },
1445
- { name: 'No', value: false },
1572
+ { name: 'Yes', value: true, command: 'prlt work start --all --json' },
1573
+ { name: 'No', value: false, command: '' },
1446
1574
  ],
1447
1575
  },
1448
- ]);
1576
+ ], batchJsonModeConfig);
1449
1577
  if (!confirm) {
1450
1578
  db.close();
1451
1579
  this.log(styles.muted('Cancelled.'));
@@ -1454,18 +1582,18 @@ export default class WorkStart extends PMOCommand {
1454
1582
  // Prompt for permissions mode once for all tickets (TKT-513)
1455
1583
  let batchPermissionMode = flags['permission-mode'];
1456
1584
  if (!batchPermissionMode) {
1457
- const { permissionMode } = await inquirer.prompt([
1585
+ const { permissionMode } = await this.prompt([
1458
1586
  {
1459
1587
  type: 'list',
1460
1588
  name: 'permissionMode',
1461
1589
  message: 'Permission mode for Claude Code:',
1462
1590
  choices: [
1463
- { name: '⚠️ danger - Skip permission checks (faster, container provides isolation)', value: 'danger' },
1464
- { 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' },
1465
1593
  ],
1466
1594
  default: 'danger',
1467
1595
  },
1468
- ]);
1596
+ ], batchJsonModeConfig);
1469
1597
  batchPermissionMode = permissionMode;
1470
1598
  }
1471
1599
  // Check Docker credentials if any agents use devcontainers
@@ -1480,18 +1608,18 @@ export default class WorkStart extends PMOCommand {
1480
1608
  this.log(styles.warning('⚠️ No Claude Code credentials found for Docker containers'));
1481
1609
  this.log(styles.muted(' Agents will fail with 401 authentication errors without credentials.'));
1482
1610
  this.log('');
1483
- const { authAction } = await inquirer.prompt([
1611
+ const { authAction } = await this.prompt([
1484
1612
  {
1485
1613
  type: 'list',
1486
1614
  name: 'authAction',
1487
1615
  message: 'What would you like to do?',
1488
1616
  choices: [
1489
- { name: `🔐 Run ${this.config.bin} agent auth now (one-time setup)`, value: 'auth' },
1490
- { name: '💻 Run all agents on host instead (--run-on-host)', value: 'host' },
1491
- { 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: '' },
1492
1620
  ],
1493
1621
  },
1494
- ]);
1622
+ ], batchJsonModeConfig);
1495
1623
  if (authAction === 'cancel') {
1496
1624
  db.close();
1497
1625
  this.log(styles.muted('Cancelled.'));
@@ -1532,11 +1660,11 @@ export default class WorkStart extends PMOCommand {
1532
1660
  this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
1533
1661
  this.log('');
1534
1662
  // Wait for user to complete auth
1535
- await inquirer.prompt([{
1663
+ await this.prompt([{
1536
1664
  type: 'input',
1537
1665
  name: 'done',
1538
1666
  message: 'Press Enter when authentication is complete:',
1539
- }]);
1667
+ }], batchJsonModeConfig);
1540
1668
  // Check if credentials now exist
1541
1669
  if (!dockerCredentialsExist()) {
1542
1670
  this.log('');