@proletariat/cli 0.3.35 → 0.3.36

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 (108) hide show
  1. package/dist/commands/agent/auth.d.ts +12 -2
  2. package/dist/commands/agent/auth.js +128 -4
  3. package/dist/commands/agent/list.js +16 -7
  4. package/dist/commands/agent/status.js +32 -4
  5. package/dist/commands/board/watch.js +6 -0
  6. package/dist/commands/branch/list.d.ts +1 -0
  7. package/dist/commands/branch/list.js +43 -12
  8. package/dist/commands/branch/where.js +3 -2
  9. package/dist/commands/category/list.d.ts +2 -1
  10. package/dist/commands/category/list.js +38 -13
  11. package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
  12. package/dist/commands/{claude.js → claude/index.js} +12 -12
  13. package/dist/commands/claude/open.d.ts +13 -0
  14. package/dist/commands/claude/open.js +175 -0
  15. package/dist/commands/diet.js +18 -2
  16. package/dist/commands/docker/logs.js +7 -3
  17. package/dist/commands/docker/shell.js +6 -0
  18. package/dist/commands/docker/start.js +20 -4
  19. package/dist/commands/docker/sync.d.ts +4 -0
  20. package/dist/commands/docker/sync.js +30 -2
  21. package/dist/commands/epic/show.d.ts +13 -0
  22. package/dist/commands/epic/show.js +16 -0
  23. package/dist/commands/epic/view.js +27 -0
  24. package/dist/commands/execution/config.d.ts +0 -4
  25. package/dist/commands/execution/config.js +10 -32
  26. package/dist/commands/execution/index.js +2 -1
  27. package/dist/commands/execution/logs.js +1 -1
  28. package/dist/commands/execution/stop.js +2 -1
  29. package/dist/commands/execution/view.js +22 -26
  30. package/dist/commands/init.js +2 -19
  31. package/dist/commands/label/create.js +2 -1
  32. package/dist/commands/label/delete.js +2 -1
  33. package/dist/commands/label/group/create.js +2 -1
  34. package/dist/commands/label/group/list.js +2 -1
  35. package/dist/commands/label/list.js +2 -1
  36. package/dist/commands/mcp-server.js +25 -0
  37. package/dist/commands/phase/template/list.js +2 -1
  38. package/dist/commands/project/create.js +3 -4
  39. package/dist/commands/project/update.js +5 -6
  40. package/dist/commands/pull.js +24 -0
  41. package/dist/commands/session/create.d.ts +19 -0
  42. package/dist/commands/session/create.js +102 -0
  43. package/dist/commands/session/health.js +1 -20
  44. package/dist/commands/session/index.js +14 -1
  45. package/dist/commands/session/list.js +26 -7
  46. package/dist/commands/session/peek.d.ts +38 -0
  47. package/dist/commands/session/peek.js +316 -0
  48. package/dist/commands/session/poke.d.ts +27 -0
  49. package/dist/commands/session/poke.js +219 -0
  50. package/dist/commands/spec/view.js +29 -0
  51. package/dist/commands/template/list.js +2 -1
  52. package/dist/commands/theme/add-names.d.ts +4 -0
  53. package/dist/commands/theme/add-names.js +11 -1
  54. package/dist/commands/theme/create.d.ts +2 -0
  55. package/dist/commands/theme/create.js +8 -0
  56. package/dist/commands/ticket/bulk.js +2 -2
  57. package/dist/commands/ticket/complete.js +2 -2
  58. package/dist/commands/ticket/create.js +21 -0
  59. package/dist/commands/ticket/delete.js +8 -0
  60. package/dist/commands/ticket/edit.js +25 -0
  61. package/dist/commands/ticket/index.js +2 -2
  62. package/dist/commands/ticket/move.js +25 -2
  63. package/dist/commands/ticket/resolve.js +3 -4
  64. package/dist/commands/ticket/show.d.ts +13 -0
  65. package/dist/commands/ticket/show.js +16 -0
  66. package/dist/commands/ticket/template/list.js +2 -1
  67. package/dist/commands/ticket/view.d.ts +0 -1
  68. package/dist/commands/ticket/view.js +30 -1
  69. package/dist/commands/work/index.js +4 -0
  70. package/dist/commands/work/start.js +169 -94
  71. package/dist/commands/work/status.d.ts +14 -0
  72. package/dist/commands/work/status.js +60 -0
  73. package/dist/commands/workflow/index.js +2 -1
  74. package/dist/commands/workflow/show.d.ts +13 -0
  75. package/dist/commands/workflow/show.js +16 -0
  76. package/dist/commands/workspace/add.js +15 -0
  77. package/dist/commands/workspace/list.js +2 -1
  78. package/dist/commands/workspace/prune.js +5 -5
  79. package/dist/lib/branch/index.d.ts +1 -0
  80. package/dist/lib/execution/config.d.ts +15 -1
  81. package/dist/lib/execution/config.js +28 -0
  82. package/dist/lib/execution/runners.d.ts +11 -0
  83. package/dist/lib/execution/runners.js +53 -19
  84. package/dist/lib/execution/session-utils.d.ts +11 -1
  85. package/dist/lib/execution/session-utils.js +26 -1
  86. package/dist/lib/execution/storage.d.ts +5 -0
  87. package/dist/lib/execution/storage.js +18 -3
  88. package/dist/lib/execution/types.d.ts +2 -0
  89. package/dist/lib/mcp/tools/board.js +4 -6
  90. package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
  91. package/dist/lib/mcp/tools/epic.js +8 -3
  92. package/dist/lib/mcp/tools/spec.js +1 -1
  93. package/dist/lib/mcp/tools/ticket.js +11 -9
  94. package/dist/lib/mcp/tools/work.js +96 -6
  95. package/dist/lib/mcp/types.d.ts +10 -0
  96. package/dist/lib/multiline-input.js +2 -1
  97. package/dist/lib/pmo/base-command.js +4 -4
  98. package/dist/lib/pmo/storage/actions.js +1 -1
  99. package/dist/lib/pmo/storage/base.js +195 -50
  100. package/dist/lib/pmo/storage/types.d.ts +1 -0
  101. package/dist/lib/prompt-command.d.ts +20 -0
  102. package/dist/lib/prompt-command.js +38 -2
  103. package/dist/lib/prompt-json.d.ts +36 -4
  104. package/dist/lib/prompt-json.js +129 -7
  105. package/dist/lib/styles.d.ts +37 -0
  106. package/dist/lib/styles.js +73 -0
  107. package/oclif.manifest.json +3259 -2701
  108. package/package.json +1 -1
@@ -3,7 +3,6 @@ import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
3
3
  import { styles } from '../../lib/styles.js';
4
4
  import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
5
5
  export default class TicketView extends PMOCommand {
6
- static aliases = ['ticket:show'];
7
6
  static description = 'View detailed ticket information';
8
7
  static examples = [
9
8
  '<%= config.bin %> <%= command.id %> TICK-001',
@@ -58,6 +57,36 @@ export default class TicketView extends PMOCommand {
58
57
  if (!ticket) {
59
58
  this.error(`Ticket "${ticketId}" not found.`);
60
59
  }
60
+ // JSON output mode
61
+ if (jsonMode) {
62
+ this.log(JSON.stringify({
63
+ success: true,
64
+ ticket: {
65
+ id: ticket.id,
66
+ title: ticket.title,
67
+ description: ticket.description,
68
+ priority: ticket.priority,
69
+ category: ticket.category,
70
+ statusName: ticket.statusName,
71
+ statusCategory: ticket.statusCategory,
72
+ projectId: ticket.projectId,
73
+ assignee: ticket.assignee,
74
+ owner: ticket.owner,
75
+ branch: ticket.branch,
76
+ epicId: ticket.epicId,
77
+ position: ticket.position,
78
+ subtasks: ticket.subtasks,
79
+ labels: ticket.labels,
80
+ metadata: ticket.metadata,
81
+ blockedBy: ticket.blockedBy,
82
+ acceptanceCriteria: ticket.acceptanceCriteria,
83
+ specId: ticket.specId,
84
+ createdAt: ticket.createdAt?.toISOString(),
85
+ updatedAt: ticket.updatedAt?.toISOString(),
86
+ },
87
+ }, null, 2));
88
+ return;
89
+ }
61
90
  // Get project board (may be null if project was deleted/orphaned)
62
91
  const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
63
92
  const projectName = board?.name || ticket.projectId || 'Unknown';
@@ -22,6 +22,7 @@ export default class Work extends PMOCommand {
22
22
  // Execution actions first (most common), then ownership
23
23
  // Each choice includes the full command for AI agents to execute
24
24
  const menuChoices = [
25
+ { id: 'status', name: 'View work status (in-progress tickets)', command: `prlt work status -P ${projectId} --json` },
25
26
  { id: 'start', name: 'Start work (launch single agent)', command: `prlt work start -P ${projectId} --json` },
26
27
  { id: 'resolve', name: 'Resolve questions (agent-assisted)', command: `prlt work resolve -P ${projectId} --json` },
27
28
  { id: 'spawn', name: 'Spawn work (batch by column)', command: `prlt work spawn -P ${projectId} --json` },
@@ -46,6 +47,9 @@ export default class Work extends PMOCommand {
46
47
  // Pass --project to avoid re-prompting for project selection
47
48
  const projectArgs = ['--project', projectId];
48
49
  switch (action) {
50
+ case 'status':
51
+ await this.config.runCommand('work:status', projectArgs);
52
+ break;
49
53
  case 'start':
50
54
  await this.config.runCommand('work:start', projectArgs);
51
55
  break;
@@ -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';
@@ -810,11 +810,20 @@ export default class WorkStart extends PMOCommand {
810
810
  flags: {},
811
811
  });
812
812
  envResolver.addPrompt({
813
- flagName: 'selectedEnvironment',
813
+ flagName: 'environment',
814
814
  type: 'list',
815
815
  message: 'Where should the agent run?',
816
816
  default: 'devcontainer',
817
817
  choices: () => envChoices,
818
+ getCommand: (value) => {
819
+ const base = `prlt work start ${ticketId}`;
820
+ if (value === 'host')
821
+ return `${base} --run-on-host --json`;
822
+ if (value === 'cancel')
823
+ return '';
824
+ // devcontainer is the default when available
825
+ return `${base} --json`;
826
+ },
818
827
  });
819
828
  await envResolver.resolve();
820
829
  // FlagResolver exits in JSON mode, so we never reach here
@@ -1008,109 +1017,171 @@ export default class WorkStart extends PMOCommand {
1008
1017
  const outputMode = flags.output || DEFAULT_EXECUTION_CONFIG.outputMode;
1009
1018
  // Track whether user explicitly chose to use API key instead of OAuth
1010
1019
  let useApiKey = flags['use-api-key'] || false;
1011
- // Check Docker credentials for devcontainer environment
1020
+ // Auth method resolution for devcontainer environment
1012
1021
  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;
1022
+ // Check for saved auth method preference
1023
+ const savedAuthMethod = getAuthMethod(db);
1024
+ const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
1025
+ if (savedAuthMethod === 'apikey') {
1026
+ // Saved preference: API key validate it's still set
1027
+ if (!hasApiKey) {
1021
1028
  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.'));
1029
+ this.log(styles.warning('⚠️ Saved auth method is "apikey" but ANTHROPIC_API_KEY is not set in your environment.'));
1030
+ this.log(styles.muted(' Set the env var or run "' + this.config.bin + ' agent auth" to switch to OAuth.'));
1031
+ db.close();
1032
+ return;
1033
+ }
1034
+ useApiKey = true;
1035
+ }
1036
+ else if (savedAuthMethod === 'oauth') {
1037
+ // Saved preference: OAuth — validate credentials exist
1038
+ const hasCredentials = dockerCredentialsExist();
1039
+ if (!hasCredentials) {
1024
1040
  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('');
1041
+ this.log(styles.warning('⚠️ Saved auth method is "oauth" but no OAuth credentials found.'));
1042
+ this.log(styles.muted(' Run "' + this.config.bin + ' agent auth" to authenticate.'));
1043
+ db.close();
1044
+ return;
1045
+ }
1046
+ // OAuth credentials valid continue (useApiKey stays false)
1047
+ }
1048
+ else {
1049
+ // No saved preference — show auth method menu
1050
+ const hasCredentials = dockerCredentialsExist();
1051
+ if (hasCredentials) {
1052
+ // OAuth credentials exist, use them silently (no menu needed)
1053
+ // useApiKey stays false
1054
+ }
1055
+ else {
1056
+ // No saved preference and no OAuth credentials — prompt user
1057
+ // In JSON mode with --yes, continue anyway (agent can run /login)
1058
+ if (jsonMode && flags.yes) {
1059
+ // Continue without prompting - agent will need to handle auth
1062
1060
  }
1063
- else if (authAction === 'auth') {
1061
+ else {
1064
1062
  this.log('');
1065
- this.log(styles.primary(`Opening ${this.config.bin} agent auth in new tab...`));
1063
+ this.log(styles.warning('⚠️ No Claude Code OAuth credentials found for Docker containers'));
1064
+ this.log(styles.muted(' Agents need credentials to authenticate with Claude.'));
1066
1065
  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
- '`);
1066
+ // Build auth method choices
1067
+ const authChoices = [
1068
+ { name: `🔐 OAuth (recommended — uses Max subscription)`, value: 'oauth' },
1069
+ ];
1070
+ if (hasApiKey) {
1071
+ authChoices.push({ name: '🔑 API key (uses API credits, not Max subscription)', value: 'apikey' });
1080
1072
  }
1081
- catch {
1082
- // Fallback: try Terminal.app
1073
+ authChoices.push({ name: '💻 Switch to host environment instead', value: 'host' }, { name: '✗ Cancel', value: 'cancel' });
1074
+ // Use FlagResolver for auth method selection
1075
+ const authResolver = new FlagResolver({
1076
+ commandName: 'work start',
1077
+ baseCommand: `prlt work start ${ticketId}`,
1078
+ jsonMode,
1079
+ flags: {},
1080
+ });
1081
+ authResolver.addPrompt({
1082
+ flagName: 'authAction',
1083
+ type: 'list',
1084
+ message: 'How should the agent authenticate with Claude?',
1085
+ choices: () => authChoices,
1086
+ });
1087
+ const authResult = await authResolver.resolve();
1088
+ const authAction = authResult.authAction;
1089
+ if (authAction === 'cancel') {
1090
+ db.close();
1091
+ this.log(styles.muted('Cancelled.'));
1092
+ return;
1093
+ }
1094
+ if (authAction === 'host') {
1095
+ environment = 'host';
1096
+ this.log(styles.muted('Switched to host environment.'));
1097
+ }
1098
+ else if (authAction === 'apikey') {
1099
+ useApiKey = true;
1100
+ this.log(styles.warning('Using ANTHROPIC_API_KEY — this will consume API credits.'));
1101
+ this.log(styles.muted(`Run "${this.config.bin} agent auth" to set up OAuth and use your Max subscription instead.`));
1102
+ this.log('');
1103
+ }
1104
+ else if (authAction === 'oauth') {
1105
+ this.log('');
1106
+ this.log(styles.primary(`Opening ${this.config.bin} agent auth in new tab...`));
1107
+ this.log('');
1108
+ // Open auth in a new terminal tab
1109
+ const authCmd = `${process.argv[1]} agent auth`;
1083
1110
  try {
1084
- execSync(`osascript -e 'tell application "Terminal" to do script "${authCmd}"'`);
1111
+ execSync(`osascript -e '
1112
+ tell application "iTerm"
1113
+ tell current window
1114
+ create tab with default profile
1115
+ tell current session
1116
+ write text "${authCmd}"
1117
+ end tell
1118
+ end tell
1119
+ end tell
1120
+ '`);
1085
1121
  }
1086
1122
  catch {
1087
- this.log(styles.warning('Could not open new terminal tab.'));
1088
- this.log(styles.muted(`Please run manually: ${authCmd}`));
1123
+ // Fallback: try Terminal.app
1124
+ try {
1125
+ execSync(`osascript -e 'tell application "Terminal" to do script "${authCmd}"'`);
1126
+ }
1127
+ catch {
1128
+ this.log(styles.warning('Could not open new terminal tab.'));
1129
+ this.log(styles.muted(`Please run manually: ${authCmd}`));
1130
+ }
1131
+ }
1132
+ this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
1133
+ this.log('');
1134
+ // Wait for user to complete auth
1135
+ await this.prompt([{
1136
+ type: 'input',
1137
+ name: 'done',
1138
+ message: 'Press Enter when authentication is complete:',
1139
+ }]);
1140
+ // Check if credentials now exist
1141
+ if (!dockerCredentialsExist()) {
1142
+ this.log('');
1143
+ this.log(styles.warning('Authentication did not complete. No credentials found.'));
1144
+ db.close();
1145
+ return;
1146
+ }
1147
+ const info = getDockerCredentialInfo();
1148
+ this.log('');
1149
+ this.log(styles.success('✓ Credentials configured'));
1150
+ if (info) {
1151
+ this.log(styles.muted(` Subscription: ${info.subscriptionType || 'unknown'}`));
1152
+ this.log(styles.muted(` Expires: ${info.expiresAt.toLocaleDateString()}`));
1089
1153
  }
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
1154
  this.log('');
1102
- this.log(styles.warning('Authentication did not complete. No credentials found.'));
1103
- db.close();
1104
- return;
1105
1155
  }
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()}`));
1156
+ // Prompt "Save as default?" after a successful auth method choice
1157
+ // (only if they chose oauth or apikey, not host/cancel)
1158
+ if (authAction === 'oauth' || authAction === 'apikey') {
1159
+ const saveChoices = [
1160
+ { name: 'Yes skip this menu next time', value: true },
1161
+ { name: 'No — ask me each time', value: false },
1162
+ ];
1163
+ const saveMessage = 'Save as default auth method?';
1164
+ const saveResolver = new FlagResolver({
1165
+ commandName: 'work start',
1166
+ baseCommand: `prlt work start ${ticketId}`,
1167
+ jsonMode,
1168
+ flags: {},
1169
+ });
1170
+ saveResolver.addPrompt({
1171
+ flagName: 'saveDefault',
1172
+ type: 'list',
1173
+ message: saveMessage,
1174
+ default: true,
1175
+ choices: () => saveChoices,
1176
+ });
1177
+ const saveResult = await saveResolver.resolve();
1178
+ if (saveResult.saveDefault) {
1179
+ const methodToSave = authAction === 'apikey' ? 'apikey' : 'oauth';
1180
+ saveAuthMethod(db, methodToSave);
1181
+ this.log(styles.muted(`Auth method saved: ${methodToSave}. Will skip this menu next time.`));
1182
+ this.log('');
1183
+ }
1112
1184
  }
1113
- this.log('');
1114
1185
  }
1115
1186
  }
1116
1187
  }
@@ -1161,9 +1232,13 @@ export default class WorkStart extends PMOCommand {
1161
1232
  createPR = false;
1162
1233
  }
1163
1234
  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;
1235
+ if (context.modifiesCode === false) {
1236
+ // Non-code-modifying actions (groom, review, resolve) default to no PR
1237
+ createPR = false;
1238
+ }
1239
+ else if (jsonMode && flags.yes) {
1240
+ // In JSON mode with --yes, default to creating PR for code-modifying actions
1241
+ createPR = true;
1167
1242
  }
1168
1243
  else {
1169
1244
  // Use FlagResolver for PR choice
@@ -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
+ }
@@ -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';
@@ -31,14 +31,14 @@ export default class WorkspacePrune extends PromptCommand {
31
31
  const { flags } = await this.parse(WorkspacePrune);
32
32
  // In non-TTY mode without --json (CI, scripts, piped), default to dry-run unless --force is set.
33
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);
34
+ const nonTTY = isNonTTY();
35
+ const effectiveDryRun = flags['dry-run'] || (!shouldOutputJson(flags) && 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 {
@@ -84,6 +84,7 @@ export interface BranchInfo {
84
84
  agent?: string;
85
85
  description?: string;
86
86
  tracking?: string;
87
+ repo?: string;
87
88
  }
88
89
  /**
89
90
  * Get current branch name.