@proletariat/cli 0.3.35 → 0.3.40

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 (148) hide show
  1. package/README.md +37 -2
  2. package/bin/dev.js +0 -0
  3. package/dist/commands/agent/auth.d.ts +12 -2
  4. package/dist/commands/agent/auth.js +128 -4
  5. package/dist/commands/agent/list.js +16 -7
  6. package/dist/commands/agent/status.js +32 -4
  7. package/dist/commands/board/watch.js +6 -0
  8. package/dist/commands/branch/list.d.ts +1 -0
  9. package/dist/commands/branch/list.js +43 -12
  10. package/dist/commands/branch/where.js +9 -19
  11. package/dist/commands/category/list.d.ts +2 -1
  12. package/dist/commands/category/list.js +38 -13
  13. package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
  14. package/dist/commands/{claude.js → claude/index.js} +12 -12
  15. package/dist/commands/claude/open.d.ts +13 -0
  16. package/dist/commands/claude/open.js +175 -0
  17. package/dist/commands/diet.js +18 -2
  18. package/dist/commands/docker/logs.js +7 -3
  19. package/dist/commands/docker/shell.js +6 -0
  20. package/dist/commands/docker/start.js +20 -4
  21. package/dist/commands/docker/sync.d.ts +4 -0
  22. package/dist/commands/docker/sync.js +30 -2
  23. package/dist/commands/epic/show.d.ts +13 -0
  24. package/dist/commands/epic/show.js +16 -0
  25. package/dist/commands/epic/ticket.js +7 -24
  26. package/dist/commands/epic/view.js +27 -0
  27. package/dist/commands/execution/config.d.ts +0 -4
  28. package/dist/commands/execution/config.js +14 -46
  29. package/dist/commands/execution/index.js +2 -1
  30. package/dist/commands/execution/logs.js +7 -1
  31. package/dist/commands/execution/stop.js +2 -1
  32. package/dist/commands/execution/view.js +30 -26
  33. package/dist/commands/init.js +2 -19
  34. package/dist/commands/label/create.js +2 -1
  35. package/dist/commands/label/delete.js +2 -1
  36. package/dist/commands/label/group/create.js +2 -1
  37. package/dist/commands/label/group/list.js +2 -1
  38. package/dist/commands/label/list.js +2 -1
  39. package/dist/commands/mcp-server.js +27 -1
  40. package/dist/commands/phase/template/list.js +2 -1
  41. package/dist/commands/pmo/init.js +12 -40
  42. package/dist/commands/project/create.js +3 -4
  43. package/dist/commands/project/update.js +5 -6
  44. package/dist/commands/pull.js +24 -0
  45. package/dist/commands/qa/index.d.ts +54 -0
  46. package/dist/commands/qa/index.js +762 -0
  47. package/dist/commands/repo/view.js +2 -8
  48. package/dist/commands/session/attach.js +4 -4
  49. package/dist/commands/session/create.d.ts +19 -0
  50. package/dist/commands/session/create.js +102 -0
  51. package/dist/commands/session/health.js +4 -23
  52. package/dist/commands/session/index.js +14 -1
  53. package/dist/commands/session/list.js +9 -8
  54. package/dist/commands/session/peek.d.ts +38 -0
  55. package/dist/commands/session/peek.js +316 -0
  56. package/dist/commands/session/poke.d.ts +27 -0
  57. package/dist/commands/session/poke.js +219 -0
  58. package/dist/commands/spec/view.js +29 -0
  59. package/dist/commands/template/list.js +2 -1
  60. package/dist/commands/theme/add-names.d.ts +4 -0
  61. package/dist/commands/theme/add-names.js +11 -1
  62. package/dist/commands/theme/create.d.ts +2 -0
  63. package/dist/commands/theme/create.js +8 -0
  64. package/dist/commands/ticket/bulk.js +2 -2
  65. package/dist/commands/ticket/complete.js +2 -2
  66. package/dist/commands/ticket/create.js +21 -0
  67. package/dist/commands/ticket/delete.js +8 -0
  68. package/dist/commands/ticket/edit.js +25 -0
  69. package/dist/commands/ticket/epic.js +17 -43
  70. package/dist/commands/ticket/index.js +2 -2
  71. package/dist/commands/ticket/move.js +25 -2
  72. package/dist/commands/ticket/resolve.js +3 -4
  73. package/dist/commands/ticket/show.d.ts +13 -0
  74. package/dist/commands/ticket/show.js +16 -0
  75. package/dist/commands/ticket/template/list.js +2 -1
  76. package/dist/commands/ticket/view.d.ts +0 -1
  77. package/dist/commands/ticket/view.js +30 -1
  78. package/dist/commands/work/index.js +4 -0
  79. package/dist/commands/work/spawn-all.js +1 -1
  80. package/dist/commands/work/spawn.js +15 -4
  81. package/dist/commands/work/start.js +186 -103
  82. package/dist/commands/work/status.d.ts +14 -0
  83. package/dist/commands/work/status.js +60 -0
  84. package/dist/commands/work/watch.js +1 -1
  85. package/dist/commands/workflow/index.js +2 -1
  86. package/dist/commands/workflow/show.d.ts +13 -0
  87. package/dist/commands/workflow/show.js +16 -0
  88. package/dist/commands/workspace/add.js +15 -0
  89. package/dist/commands/workspace/list.js +2 -1
  90. package/dist/commands/workspace/prune.js +7 -7
  91. package/dist/hooks/init.js +10 -2
  92. package/dist/lib/agents/commands.d.ts +5 -0
  93. package/dist/lib/agents/commands.js +143 -97
  94. package/dist/lib/branch/index.d.ts +1 -0
  95. package/dist/lib/database/drizzle-schema.d.ts +465 -0
  96. package/dist/lib/database/drizzle-schema.js +53 -0
  97. package/dist/lib/database/index.d.ts +47 -1
  98. package/dist/lib/database/index.js +138 -20
  99. package/dist/lib/execution/config.d.ts +15 -1
  100. package/dist/lib/execution/config.js +28 -0
  101. package/dist/lib/execution/runners.d.ts +45 -0
  102. package/dist/lib/execution/runners.js +187 -26
  103. package/dist/lib/execution/session-utils.d.ts +16 -1
  104. package/dist/lib/execution/session-utils.js +71 -4
  105. package/dist/lib/execution/spawner.js +15 -2
  106. package/dist/lib/execution/storage.d.ts +6 -1
  107. package/dist/lib/execution/storage.js +35 -5
  108. package/dist/lib/execution/types.d.ts +3 -0
  109. package/dist/lib/mcp/tools/board.js +4 -6
  110. package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
  111. package/dist/lib/mcp/tools/epic.js +8 -3
  112. package/dist/lib/mcp/tools/index.d.ts +1 -0
  113. package/dist/lib/mcp/tools/index.js +1 -0
  114. package/dist/lib/mcp/tools/spec.js +1 -1
  115. package/dist/lib/mcp/tools/ticket.js +11 -9
  116. package/dist/lib/mcp/tools/tmux.d.ts +16 -0
  117. package/dist/lib/mcp/tools/tmux.js +182 -0
  118. package/dist/lib/mcp/tools/work.js +148 -6
  119. package/dist/lib/mcp/types.d.ts +10 -0
  120. package/dist/lib/multiline-input.js +2 -1
  121. package/dist/lib/pmo/base-command.js +4 -4
  122. package/dist/lib/pmo/schema.d.ts +1 -1
  123. package/dist/lib/pmo/schema.js +1 -0
  124. package/dist/lib/pmo/storage/actions.js +1 -1
  125. package/dist/lib/pmo/storage/base.js +402 -50
  126. package/dist/lib/pmo/storage/dependencies.d.ts +1 -0
  127. package/dist/lib/pmo/storage/dependencies.js +11 -3
  128. package/dist/lib/pmo/storage/epics.js +1 -1
  129. package/dist/lib/pmo/storage/helpers.d.ts +4 -4
  130. package/dist/lib/pmo/storage/helpers.js +36 -26
  131. package/dist/lib/pmo/storage/projects.d.ts +2 -0
  132. package/dist/lib/pmo/storage/projects.js +207 -119
  133. package/dist/lib/pmo/storage/specs.d.ts +2 -0
  134. package/dist/lib/pmo/storage/specs.js +274 -188
  135. package/dist/lib/pmo/storage/tickets.d.ts +2 -0
  136. package/dist/lib/pmo/storage/tickets.js +350 -290
  137. package/dist/lib/pmo/storage/types.d.ts +1 -0
  138. package/dist/lib/pmo/storage/views.d.ts +2 -0
  139. package/dist/lib/pmo/storage/views.js +183 -130
  140. package/dist/lib/prompt-command.d.ts +20 -0
  141. package/dist/lib/prompt-command.js +38 -2
  142. package/dist/lib/prompt-json.d.ts +41 -4
  143. package/dist/lib/prompt-json.js +138 -7
  144. package/dist/lib/styles.d.ts +37 -0
  145. package/dist/lib/styles.js +73 -0
  146. package/oclif.manifest.json +4046 -3385
  147. package/package.json +11 -6
  148. package/LICENSE +0 -190
@@ -13,7 +13,7 @@ import { getWorkspaceInfo, createEphemeralAgent, getTicketTmuxSession, killTmuxS
13
13
  import { generateBranchName, DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
14
14
  import { runExecution, isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled, dockerCredentialsExist, getDockerCredentialInfo } from '../../lib/execution/runners.js';
15
15
  import { ExecutionStorage, ContainerStorage } from '../../lib/execution/storage.js';
16
- import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, getOrPromptCoderName } from '../../lib/execution/config.js';
16
+ import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, getOrPromptCoderName, getAuthMethod, saveAuthMethod } from '../../lib/execution/config.js';
17
17
  import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
18
18
  import { detectRepoWorktrees, resolveWorktreePath } from '../../lib/execution/context.js';
19
19
  import { isGHInstalled, isGHAuthenticated } from '../../lib/pr/index.js';
@@ -71,6 +71,7 @@ export default class WorkStart extends PMOCommand {
71
71
  static description = 'Start work on a ticket (launches an agent to implement it)';
72
72
  static examples = [
73
73
  '<%= config.bin %> <%= command.id %> TKT-001',
74
+ '<%= config.bin %> <%= command.id %> TKT-001 --create-pr # Create PR when work is ready',
74
75
  '<%= config.bin %> <%= command.id %> TKT-001 --mode foreground',
75
76
  '<%= config.bin %> <%= command.id %> TKT-001 --mode tmux',
76
77
  '<%= config.bin %> <%= command.id %> TKT-001 --mode terminal',
@@ -137,11 +138,11 @@ export default class WorkStart extends PMOCommand {
137
138
  default: false,
138
139
  }),
139
140
  'create-pr': Flags.boolean({
140
- description: 'Create PR when work is ready',
141
+ description: 'Create PR when work is ready (canonical flag for PR behavior)',
141
142
  default: false,
142
143
  }),
143
144
  'no-pr': Flags.boolean({
144
- description: 'Do not create PR when work is ready',
145
+ description: '[deprecated: use --create-pr instead] Skip PR creation when work is ready',
145
146
  default: false,
146
147
  }),
147
148
  output: Flags.string({
@@ -193,6 +194,10 @@ export default class WorkStart extends PMOCommand {
193
194
  if (flags['create-pr'] && flags['no-pr']) {
194
195
  this.error('--create-pr and --no-pr are mutually exclusive');
195
196
  }
197
+ // Deprecation guidance for --no-pr
198
+ if (flags['no-pr']) {
199
+ this.warn('--no-pr is deprecated. Omit --create-pr instead (PR creation is off by default). --no-pr will continue to work.');
200
+ }
196
201
  // Handle --skip-permissions flag (alias for --permission-mode danger)
197
202
  // Check for conflicting flags first
198
203
  if (flags['skip-permissions'] && flags['permission-mode']) {
@@ -272,6 +277,7 @@ export default class WorkStart extends PMOCommand {
272
277
  if (allFlagsProvided && !flags.yes) {
273
278
  // All flags provided but no --yes: return confirmation_needed with plan
274
279
  const metadata = createMetadata('work start', flags);
280
+ metadata.resolvedPRMode = flags['create-pr'] ? 'create-pr' : 'no-pr';
275
281
  // Build the confirm command with --yes
276
282
  let confirmCmd = `prlt work start ${ticketId}`;
277
283
  if (flags.action)
@@ -810,11 +816,20 @@ export default class WorkStart extends PMOCommand {
810
816
  flags: {},
811
817
  });
812
818
  envResolver.addPrompt({
813
- flagName: 'selectedEnvironment',
819
+ flagName: 'environment',
814
820
  type: 'list',
815
821
  message: 'Where should the agent run?',
816
822
  default: 'devcontainer',
817
823
  choices: () => envChoices,
824
+ getCommand: (value) => {
825
+ const base = `prlt work start ${ticketId}`;
826
+ if (value === 'host')
827
+ return `${base} --run-on-host --json`;
828
+ if (value === 'cancel')
829
+ return '';
830
+ // devcontainer is the default when available
831
+ return `${base} --json`;
832
+ },
818
833
  });
819
834
  await envResolver.resolve();
820
835
  // FlagResolver exits in JSON mode, so we never reach here
@@ -1008,109 +1023,171 @@ export default class WorkStart extends PMOCommand {
1008
1023
  const outputMode = flags.output || DEFAULT_EXECUTION_CONFIG.outputMode;
1009
1024
  // Track whether user explicitly chose to use API key instead of OAuth
1010
1025
  let useApiKey = flags['use-api-key'] || false;
1011
- // Check Docker credentials for devcontainer environment
1026
+ // Auth method resolution for devcontainer environment
1012
1027
  if (environment === 'devcontainer' && !useApiKey) {
1013
- const hasCredentials = dockerCredentialsExist();
1014
- if (!hasCredentials) {
1015
- // In JSON mode with --yes, continue anyway (agent can run /login)
1016
- if (jsonMode && flags.yes) {
1017
- // Continue without prompting - agent will need to handle auth
1018
- }
1019
- else {
1020
- const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
1028
+ // Check for saved auth method preference
1029
+ const savedAuthMethod = getAuthMethod(db);
1030
+ const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
1031
+ if (savedAuthMethod === 'apikey') {
1032
+ // Saved preference: API key validate it's still set
1033
+ if (!hasApiKey) {
1021
1034
  this.log('');
1022
- this.log(styles.warning('⚠️ No Claude Code OAuth credentials found for Docker containers'));
1023
- this.log(styles.muted(' Agents need credentials to authenticate with Claude.'));
1035
+ this.log(styles.warning('⚠️ Saved auth method is "apikey" but ANTHROPIC_API_KEY is not set in your environment.'));
1036
+ this.log(styles.muted(' Set the env var or run "' + this.config.bin + ' agent auth" to switch to OAuth.'));
1037
+ db.close();
1038
+ return;
1039
+ }
1040
+ useApiKey = true;
1041
+ }
1042
+ else if (savedAuthMethod === 'oauth') {
1043
+ // Saved preference: OAuth — validate credentials exist
1044
+ const hasCredentials = dockerCredentialsExist();
1045
+ if (!hasCredentials) {
1024
1046
  this.log('');
1025
- // Build choices based on available options
1026
- const authChoices = [
1027
- { name: `🔐 Run ${this.config.bin} agent auth now (recommended — uses Max subscription)`, value: 'auth' },
1028
- ];
1029
- if (hasApiKey) {
1030
- authChoices.push({ name: '🔑 Use ANTHROPIC_API_KEY (⚠️ uses API credits, not Max subscription)', value: 'apikey' });
1031
- }
1032
- authChoices.push({ name: '💻 Switch to host environment instead', value: 'host' }, { name: '✗ Cancel', value: 'cancel' });
1033
- // Use FlagResolver for auth action
1034
- const authResolver = new FlagResolver({
1035
- commandName: 'work start',
1036
- baseCommand: `prlt work start ${ticketId}`,
1037
- jsonMode,
1038
- flags: {},
1039
- });
1040
- authResolver.addPrompt({
1041
- flagName: 'authAction',
1042
- type: 'list',
1043
- message: 'What would you like to do?',
1044
- choices: () => authChoices,
1045
- });
1046
- const authResult = await authResolver.resolve();
1047
- const authAction = authResult.authAction;
1048
- if (authAction === 'cancel') {
1049
- db.close();
1050
- this.log(styles.muted('Cancelled.'));
1051
- return;
1052
- }
1053
- if (authAction === 'host') {
1054
- environment = 'host';
1055
- this.log(styles.muted('Switched to host environment.'));
1056
- }
1057
- else if (authAction === 'apikey') {
1058
- useApiKey = true;
1059
- this.log(styles.warning('Using ANTHROPIC_API_KEY — this will consume API credits.'));
1060
- this.log(styles.muted(`Run "${this.config.bin} agent auth" to set up OAuth and use your Max subscription instead.`));
1061
- this.log('');
1047
+ this.log(styles.warning('⚠️ Saved auth method is "oauth" but no OAuth credentials found.'));
1048
+ this.log(styles.muted(' Run "' + this.config.bin + ' agent auth" to authenticate.'));
1049
+ db.close();
1050
+ return;
1051
+ }
1052
+ // OAuth credentials valid continue (useApiKey stays false)
1053
+ }
1054
+ else {
1055
+ // No saved preference — show auth method menu
1056
+ const hasCredentials = dockerCredentialsExist();
1057
+ if (hasCredentials) {
1058
+ // OAuth credentials exist, use them silently (no menu needed)
1059
+ // useApiKey stays false
1060
+ }
1061
+ else {
1062
+ // No saved preference and no OAuth credentials — prompt user
1063
+ // In JSON mode with --yes, continue anyway (agent can run /login)
1064
+ if (jsonMode && flags.yes) {
1065
+ // Continue without prompting - agent will need to handle auth
1062
1066
  }
1063
- else if (authAction === 'auth') {
1067
+ else {
1064
1068
  this.log('');
1065
- this.log(styles.primary(`Opening ${this.config.bin} agent auth in new tab...`));
1069
+ this.log(styles.warning('⚠️ No Claude Code OAuth credentials found for Docker containers'));
1070
+ this.log(styles.muted(' Agents need credentials to authenticate with Claude.'));
1066
1071
  this.log('');
1067
- // Open auth in a new terminal tab
1068
- const authCmd = `${process.argv[1]} agent auth`;
1069
- try {
1070
- execSync(`osascript -e '
1071
- tell application "iTerm"
1072
- tell current window
1073
- create tab with default profile
1074
- tell current session
1075
- write text "${authCmd}"
1076
- end tell
1077
- end tell
1078
- end tell
1079
- '`);
1072
+ // Build auth method choices
1073
+ const authChoices = [
1074
+ { name: `🔐 OAuth (recommended — uses Max subscription)`, value: 'oauth' },
1075
+ ];
1076
+ if (hasApiKey) {
1077
+ authChoices.push({ name: '🔑 API key (uses API credits, not Max subscription)', value: 'apikey' });
1080
1078
  }
1081
- catch {
1082
- // Fallback: try Terminal.app
1079
+ authChoices.push({ name: '💻 Switch to host environment instead', value: 'host' }, { name: '✗ Cancel', value: 'cancel' });
1080
+ // Use FlagResolver for auth method selection
1081
+ const authResolver = new FlagResolver({
1082
+ commandName: 'work start',
1083
+ baseCommand: `prlt work start ${ticketId}`,
1084
+ jsonMode,
1085
+ flags: {},
1086
+ });
1087
+ authResolver.addPrompt({
1088
+ flagName: 'authAction',
1089
+ type: 'list',
1090
+ message: 'How should the agent authenticate with Claude?',
1091
+ choices: () => authChoices,
1092
+ });
1093
+ const authResult = await authResolver.resolve();
1094
+ const authAction = authResult.authAction;
1095
+ if (authAction === 'cancel') {
1096
+ db.close();
1097
+ this.log(styles.muted('Cancelled.'));
1098
+ return;
1099
+ }
1100
+ if (authAction === 'host') {
1101
+ environment = 'host';
1102
+ this.log(styles.muted('Switched to host environment.'));
1103
+ }
1104
+ else if (authAction === 'apikey') {
1105
+ useApiKey = true;
1106
+ this.log(styles.warning('Using ANTHROPIC_API_KEY — this will consume API credits.'));
1107
+ this.log(styles.muted(`Run "${this.config.bin} agent auth" to set up OAuth and use your Max subscription instead.`));
1108
+ this.log('');
1109
+ }
1110
+ else if (authAction === 'oauth') {
1111
+ this.log('');
1112
+ this.log(styles.primary(`Opening ${this.config.bin} agent auth in new tab...`));
1113
+ this.log('');
1114
+ // Open auth in a new terminal tab
1115
+ const authCmd = `${process.argv[1]} agent auth`;
1083
1116
  try {
1084
- execSync(`osascript -e 'tell application "Terminal" to do script "${authCmd}"'`);
1117
+ execSync(`osascript -e '
1118
+ tell application "iTerm"
1119
+ tell current window
1120
+ create tab with default profile
1121
+ tell current session
1122
+ write text "${authCmd}"
1123
+ end tell
1124
+ end tell
1125
+ end tell
1126
+ '`);
1085
1127
  }
1086
1128
  catch {
1087
- this.log(styles.warning('Could not open new terminal tab.'));
1088
- this.log(styles.muted(`Please run manually: ${authCmd}`));
1129
+ // Fallback: try Terminal.app
1130
+ try {
1131
+ execSync(`osascript -e 'tell application "Terminal" to do script "${authCmd}"'`);
1132
+ }
1133
+ catch {
1134
+ this.log(styles.warning('Could not open new terminal tab.'));
1135
+ this.log(styles.muted(`Please run manually: ${authCmd}`));
1136
+ }
1137
+ }
1138
+ this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
1139
+ this.log('');
1140
+ // Wait for user to complete auth
1141
+ await this.prompt([{
1142
+ type: 'input',
1143
+ name: 'done',
1144
+ message: 'Press Enter when authentication is complete:',
1145
+ }]);
1146
+ // Check if credentials now exist
1147
+ if (!dockerCredentialsExist()) {
1148
+ this.log('');
1149
+ this.log(styles.warning('Authentication did not complete. No credentials found.'));
1150
+ db.close();
1151
+ return;
1152
+ }
1153
+ const info = getDockerCredentialInfo();
1154
+ this.log('');
1155
+ this.log(styles.success('✓ Credentials configured'));
1156
+ if (info) {
1157
+ this.log(styles.muted(` Subscription: ${info.subscriptionType || 'unknown'}`));
1158
+ this.log(styles.muted(` Expires: ${info.expiresAt.toLocaleDateString()}`));
1089
1159
  }
1090
- }
1091
- this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
1092
- this.log('');
1093
- // Wait for user to complete auth
1094
- await this.prompt([{
1095
- type: 'input',
1096
- name: 'done',
1097
- message: 'Press Enter when authentication is complete:',
1098
- }]);
1099
- // Check if credentials now exist
1100
- if (!dockerCredentialsExist()) {
1101
1160
  this.log('');
1102
- this.log(styles.warning('Authentication did not complete. No credentials found.'));
1103
- db.close();
1104
- return;
1105
1161
  }
1106
- const info = getDockerCredentialInfo();
1107
- this.log('');
1108
- this.log(styles.success(' Credentials configured'));
1109
- if (info) {
1110
- this.log(styles.muted(` Subscription: ${info.subscriptionType || 'unknown'}`));
1111
- this.log(styles.muted(` Expires: ${info.expiresAt.toLocaleDateString()}`));
1162
+ // Prompt "Save as default?" after a successful auth method choice
1163
+ // (only if they chose oauth or apikey, not host/cancel)
1164
+ if (authAction === 'oauth' || authAction === 'apikey') {
1165
+ const saveChoices = [
1166
+ { name: 'Yes skip this menu next time', value: true },
1167
+ { name: 'No — ask me each time', value: false },
1168
+ ];
1169
+ const saveMessage = 'Save as default auth method?';
1170
+ const saveResolver = new FlagResolver({
1171
+ commandName: 'work start',
1172
+ baseCommand: `prlt work start ${ticketId}`,
1173
+ jsonMode,
1174
+ flags: {},
1175
+ });
1176
+ saveResolver.addPrompt({
1177
+ flagName: 'saveDefault',
1178
+ type: 'list',
1179
+ message: saveMessage,
1180
+ default: true,
1181
+ choices: () => saveChoices,
1182
+ });
1183
+ const saveResult = await saveResolver.resolve();
1184
+ if (saveResult.saveDefault) {
1185
+ const methodToSave = authAction === 'apikey' ? 'apikey' : 'oauth';
1186
+ saveAuthMethod(db, methodToSave);
1187
+ this.log(styles.muted(`Auth method saved: ${methodToSave}. Will skip this menu next time.`));
1188
+ this.log('');
1189
+ }
1112
1190
  }
1113
- this.log('');
1114
1191
  }
1115
1192
  }
1116
1193
  }
@@ -1161,9 +1238,13 @@ export default class WorkStart extends PMOCommand {
1161
1238
  createPR = false;
1162
1239
  }
1163
1240
  else if (ghAvailable) {
1164
- // In JSON mode with --yes, default to creating PR for code-modifying actions
1165
- if (jsonMode && flags.yes) {
1166
- createPR = context.modifiesCode !== false;
1241
+ if (context.modifiesCode === false) {
1242
+ // Non-code-modifying actions (groom, review, resolve) default to no PR
1243
+ createPR = false;
1244
+ }
1245
+ else if (jsonMode && flags.yes) {
1246
+ // In JSON mode with --yes, default to creating PR for code-modifying actions
1247
+ createPR = true;
1167
1248
  }
1168
1249
  else {
1169
1250
  // Use FlagResolver for PR choice
@@ -1206,9 +1287,7 @@ export default class WorkStart extends PMOCommand {
1206
1287
  this.log(styles.warning(` Permissions: ⚠️ danger (--dangerously-skip-permissions)`));
1207
1288
  }
1208
1289
  this.log(styles.muted(` Output: ${outputMode === 'interactive' ? 'streaming (watch Claude work)' : 'print (final result only)'}`));
1209
- if (ghAvailable) {
1210
- this.log(styles.muted(` Create PR: ${createPR ? 'yes (when work is ready)' : 'no'}`));
1211
- }
1290
+ this.log(styles.muted(` PR mode: ${createPR ? 'create-pr' : 'no-pr'}${ghAvailable ? '' : ' (gh CLI not available)'}`));
1212
1291
  this.log(styles.muted(` Worktree: ${worktreePath}`));
1213
1292
  this.log(styles.muted(` Branch: ${branch}`));
1214
1293
  this.log('');
@@ -1510,7 +1589,9 @@ export default class WorkStart extends PMOCommand {
1510
1589
  });
1511
1590
  // Output results
1512
1591
  if (jsonMode) {
1513
- // Output JSON execution result
1592
+ // Output JSON execution result with resolved PR mode
1593
+ const metadata = createMetadata('work start', flags);
1594
+ metadata.resolvedPRMode = createPR ? 'create-pr' : 'no-pr';
1514
1595
  outputExecutionResultAsJson([{
1515
1596
  workId: execution.id,
1516
1597
  ticketId: ticket.id,
@@ -1518,7 +1599,7 @@ export default class WorkStart extends PMOCommand {
1518
1599
  sessionId: result.sessionId,
1519
1600
  containerId: result.containerId,
1520
1601
  status: 'running',
1521
- }], 1, 0, createMetadata('work start', flags));
1602
+ }], 1, 0, metadata);
1522
1603
  }
1523
1604
  else {
1524
1605
  this.log('');
@@ -1533,13 +1614,15 @@ export default class WorkStart extends PMOCommand {
1533
1614
  else {
1534
1615
  executionStorage.updateStatus(execution.id, 'failed');
1535
1616
  if (jsonMode) {
1536
- // Output JSON failure result
1617
+ // Output JSON failure result with resolved PR mode
1618
+ const failMetadata = createMetadata('work start', flags);
1619
+ failMetadata.resolvedPRMode = createPR ? 'create-pr' : 'no-pr';
1537
1620
  outputExecutionResultAsJson([{
1538
1621
  workId: execution.id,
1539
1622
  ticketId: ticket.id,
1540
1623
  agent: assignedAgent,
1541
1624
  status: 'failed',
1542
- }], 0, 1, createMetadata('work start', flags));
1625
+ }], 0, 1, failMetadata);
1543
1626
  }
1544
1627
  else {
1545
1628
  this.error(`Failed to start work: ${result.error}`);
@@ -0,0 +1,14 @@
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class WorkStatus extends PMOCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ protected getPMOOptions(): {
11
+ promptIfMultiple: boolean;
12
+ };
13
+ execute(): Promise<void>;
14
+ }
@@ -0,0 +1,60 @@
1
+ import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
2
+ import { styles, formatPriority } from '../../lib/styles.js';
3
+ import { shouldOutputJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
4
+ export default class WorkStatus extends PMOCommand {
5
+ static description = 'Show current work status (in-progress tickets)';
6
+ static examples = [
7
+ '<%= config.bin %> <%= command.id %>',
8
+ '<%= config.bin %> <%= command.id %> --json',
9
+ ];
10
+ static flags = {
11
+ ...pmoBaseFlags,
12
+ };
13
+ getPMOOptions() {
14
+ return { promptIfMultiple: false };
15
+ }
16
+ async execute() {
17
+ const { flags } = await this.parse(WorkStatus);
18
+ const projectId = flags.project;
19
+ const jsonMode = shouldOutputJson(flags);
20
+ // List all tickets, optionally filtered by project
21
+ const tickets = await this.storage.listTickets(projectId, { allProjects: !projectId });
22
+ const inProgress = tickets.filter(t => t.statusCategory === 'started' && t.assignee);
23
+ if (jsonMode) {
24
+ outputSuccessAsJson({
25
+ inProgressCount: inProgress.length,
26
+ tickets: inProgress.map(t => ({
27
+ id: t.id,
28
+ title: t.title,
29
+ assignee: t.assignee,
30
+ statusName: t.statusName,
31
+ priority: t.priority,
32
+ projectId: t.projectId,
33
+ branch: t.branch,
34
+ })),
35
+ }, createMetadata('work status', flags));
36
+ return;
37
+ }
38
+ // Interactive mode
39
+ if (inProgress.length === 0) {
40
+ this.log(styles.info('No in-progress work found.'));
41
+ return;
42
+ }
43
+ this.log(styles.title(`\nWork In Progress (${inProgress.length})`));
44
+ this.log(styles.muted('─'.repeat(60)));
45
+ for (const ticket of inProgress) {
46
+ const priority = formatPriority(ticket.priority);
47
+ this.log(` ${styles.code(ticket.id)} ${ticket.title} ${priority}`);
48
+ const details = [
49
+ ticket.assignee ? `Assignee: ${ticket.assignee}` : null,
50
+ ticket.statusName ? `Status: ${ticket.statusName}` : null,
51
+ ticket.projectId ? `Project: ${ticket.projectId}` : null,
52
+ ticket.branch ? `Branch: ${ticket.branch}` : null,
53
+ ].filter(Boolean).join(' | ');
54
+ if (details) {
55
+ this.log(styles.muted(` ${details}`));
56
+ }
57
+ }
58
+ this.log('');
59
+ }
60
+ }
@@ -60,7 +60,7 @@ export default class WorkWatch extends PMOCommand {
60
60
  default: false,
61
61
  }),
62
62
  'create-pr': Flags.boolean({
63
- description: 'Create PR when work is ready',
63
+ description: 'Create PR when work is ready (canonical flag for PR behavior)',
64
64
  default: false,
65
65
  }),
66
66
  };
@@ -1,5 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
3
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
3
4
  import { styles } from '../../lib/styles.js';
4
5
  export default class Workflow extends PMOCommand {
5
6
  static description = 'List all available workflows (alias for workflow list)';
@@ -31,7 +32,7 @@ export default class Workflow extends PMOCommand {
31
32
  if (flags.custom)
32
33
  filter.isBuiltin = false;
33
34
  const workflows = await this.storage.listWorkflows(Object.keys(filter).length > 0 ? filter : undefined);
34
- if (flags.json || flags.machine) {
35
+ if (shouldOutputJson(flags)) {
35
36
  this.log(JSON.stringify(workflows, null, 2));
36
37
  return;
37
38
  }
@@ -0,0 +1,13 @@
1
+ import WorkflowView from './view.js';
2
+ export default class WorkflowShow extends WorkflowView {
3
+ static description: string;
4
+ static hidden: boolean;
5
+ static args: {
6
+ id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ }
@@ -0,0 +1,16 @@
1
+ import { Args } from '@oclif/core';
2
+ import { pmoBaseFlags } from '../../lib/pmo/index.js';
3
+ import WorkflowView from './view.js';
4
+ export default class WorkflowShow extends WorkflowView {
5
+ static description = 'View details of a workflow (alias for workflow view)';
6
+ static hidden = true;
7
+ static args = {
8
+ id: Args.string({
9
+ description: 'Workflow ID - prompts with dropdown if not provided',
10
+ required: false,
11
+ }),
12
+ };
13
+ static flags = {
14
+ ...pmoBaseFlags,
15
+ };
16
+ }
@@ -4,6 +4,7 @@ import * as fs from 'node:fs';
4
4
  import * as path from 'node:path';
5
5
  import { registerWorkspace, normalizePath, isWorkspaceRegistered, getWorkspaceNameFromPath, } from '../../lib/machine-config.js';
6
6
  import { machineOutputFlags } from '../../lib/pmo/index.js';
7
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
7
8
  export default class WorkspaceAdd extends Command {
8
9
  static description = 'Register an existing workspace in the machine config';
9
10
  static examples = [
@@ -26,6 +27,7 @@ export default class WorkspaceAdd extends Command {
26
27
  };
27
28
  async run() {
28
29
  const { args, flags } = await this.parse(WorkspaceAdd);
30
+ const jsonMode = shouldOutputJson(flags);
29
31
  // Normalize the path
30
32
  const workspacePath = normalizePath(args.path);
31
33
  // Check if path exists
@@ -56,6 +58,10 @@ export default class WorkspaceAdd extends Command {
56
58
  }
57
59
  // Check if already registered
58
60
  if (isWorkspaceRegistered(workspacePath)) {
61
+ if (jsonMode) {
62
+ this.log(JSON.stringify({ type: 'success', result: { path: workspacePath, status: 'already_registered' } }));
63
+ return;
64
+ }
59
65
  this.log(chalk.yellow(`Workspace is already registered: ${workspacePath}`));
60
66
  this.log(chalk.gray('Use "prlt workspace use" to set it as active.'));
61
67
  return;
@@ -65,11 +71,20 @@ export default class WorkspaceAdd extends Command {
65
71
  // Register the workspace
66
72
  try {
67
73
  const entry = registerWorkspace(workspacePath, workspaceName, true);
74
+ if (jsonMode) {
75
+ this.log(JSON.stringify({ type: 'success', result: { name: entry.name, path: entry.path, registeredAt: entry.registeredAt, status: 'registered' } }));
76
+ return;
77
+ }
68
78
  this.log(chalk.green(`Registered workspace: ${entry.name}`));
69
79
  this.log(chalk.gray(` Path: ${entry.path}`));
70
80
  this.log(chalk.gray(` Registered at: ${entry.registeredAt}`));
71
81
  }
72
82
  catch (error) {
83
+ if (jsonMode) {
84
+ this.log(JSON.stringify({ type: 'error', error: { code: 'REGISTER_FAILED', message: error.message } }));
85
+ this.exit(1);
86
+ return;
87
+ }
73
88
  this.error(`Failed to register workspace: ${error.message}`);
74
89
  }
75
90
  }
@@ -5,6 +5,7 @@ import { machineOutputFlags } from '../../lib/pmo/index.js';
5
5
  import { getRegisteredWorkspaces, getActiveWorkspace, } from '../../lib/machine-config.js';
6
6
  import * as fs from 'node:fs';
7
7
  import * as path from 'node:path';
8
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
8
9
  export default class WorkspaceList extends Command {
9
10
  static description = 'List all registered and discovered HQ workspaces';
10
11
  static examples = [
@@ -78,7 +79,7 @@ export default class WorkspaceList extends Command {
78
79
  });
79
80
  }
80
81
  // JSON output
81
- if (flags.json || flags.machine) {
82
+ if (shouldOutputJson(flags)) {
82
83
  const output = {
83
84
  workspaces: workspaces.map((w) => ({
84
85
  name: w.name,
@@ -5,7 +5,7 @@ import { PromptCommand } from '../../lib/prompt-command.js';
5
5
  import { styles } from '../../lib/styles.js';
6
6
  import { getRegisteredHeadquarters, unregisterHeadquarters, } from '../../lib/machine-config.js';
7
7
  import { getWorkspaceAgents, removeAgentsFromDatabase, getDatabasePath, } from '../../lib/database/index.js';
8
- import { outputConfirmationNeededAsJson, createMetadata, } from '../../lib/prompt-json.js';
8
+ import { outputConfirmationNeededAsJson, createMetadata, shouldOutputJson, isNonTTY, } from '../../lib/prompt-json.js';
9
9
  import { machineOutputFlags } from '../../lib/pmo/index.js';
10
10
  export default class WorkspacePrune extends PromptCommand {
11
11
  static description = 'Remove stale workspace entries and agents with deleted worktrees';
@@ -29,16 +29,16 @@ export default class WorkspacePrune extends PromptCommand {
29
29
  };
30
30
  async run() {
31
31
  const { flags } = await this.parse(WorkspacePrune);
32
- // In non-TTY mode without --json (CI, scripts, piped), default to dry-run unless --force is set.
33
- // In --json mode, we use confirmation_needed output instead of auto-dry-run so agents can review and confirm.
34
- const isNonTTY = !process.stdout.isTTY;
35
- const effectiveDryRun = flags['dry-run'] || (!(flags.json || flags.machine) && isNonTTY && !flags.force);
32
+ // In non-TTY mode (CI, scripts, piped), default to dry-run unless --force is set.
33
+ // This applies regardless of output format (text or JSON).
34
+ const nonTTY = isNonTTY();
35
+ const effectiveDryRun = flags['dry-run'] || (nonTTY && !flags.force);
36
36
  // Find stale entries
37
37
  const staleWorkspaces = this.findStaleWorkspaces();
38
38
  const staleAgents = this.findStaleAgents();
39
39
  const totalStale = staleWorkspaces.length + staleAgents.length;
40
40
  // JSON output
41
- if (flags.json || flags.machine) {
41
+ if (shouldOutputJson(flags)) {
42
42
  const output = {
43
43
  dryRun: effectiveDryRun,
44
44
  staleWorkspaces: staleWorkspaces.map(w => ({
@@ -112,7 +112,7 @@ export default class WorkspacePrune extends PromptCommand {
112
112
  this.log(styles.muted(` • ${staleAgents.length} agent record(s)`));
113
113
  }
114
114
  this.log('');
115
- if (isNonTTY) {
115
+ if (nonTTY) {
116
116
  this.log(styles.muted('Non-TTY environment detected. Run with --force to remove these entries.'));
117
117
  }
118
118
  else {