@proletariat/cli 0.3.34 → 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 (198) hide show
  1. package/dist/commands/agent/auth.d.ts +15 -3
  2. package/dist/commands/agent/auth.js +136 -15
  3. package/dist/commands/agent/index.js +11 -2
  4. package/dist/commands/agent/list.js +16 -7
  5. package/dist/commands/agent/staff/add.d.ts +1 -0
  6. package/dist/commands/agent/staff/add.js +1 -0
  7. package/dist/commands/agent/staff/index.d.ts +15 -0
  8. package/dist/commands/agent/staff/index.js +83 -0
  9. package/dist/commands/agent/staff/list.d.ts +1 -0
  10. package/dist/commands/agent/staff/list.js +1 -0
  11. package/dist/commands/agent/staff/remove.d.ts +1 -0
  12. package/dist/commands/agent/staff/remove.js +1 -0
  13. package/dist/commands/agent/status.js +32 -4
  14. package/dist/commands/agent/themes/add-names.d.ts +1 -0
  15. package/dist/commands/agent/themes/add-names.js +1 -0
  16. package/dist/commands/agent/themes/create.d.ts +1 -0
  17. package/dist/commands/agent/themes/create.js +1 -0
  18. package/dist/commands/agent/themes/index.d.ts +10 -0
  19. package/dist/commands/agent/themes/index.js +144 -0
  20. package/dist/commands/agent/themes/list.d.ts +1 -0
  21. package/dist/commands/agent/themes/list.js +1 -0
  22. package/dist/commands/agent/themes/set.d.ts +1 -0
  23. package/dist/commands/agent/themes/set.js +1 -0
  24. package/dist/commands/agents/themes/add-names.d.ts +1 -0
  25. package/dist/commands/agents/themes/add-names.js +1 -0
  26. package/dist/commands/agents/themes/create.d.ts +1 -0
  27. package/dist/commands/agents/themes/create.js +1 -0
  28. package/dist/commands/agents/themes/list.d.ts +1 -0
  29. package/dist/commands/agents/themes/list.js +1 -0
  30. package/dist/commands/board/watch.js +6 -0
  31. package/dist/commands/branch/list.d.ts +1 -0
  32. package/dist/commands/branch/list.js +43 -12
  33. package/dist/commands/branch/where.js +3 -2
  34. package/dist/commands/category/list.d.ts +2 -1
  35. package/dist/commands/category/list.js +38 -13
  36. package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
  37. package/dist/commands/{claude.js → claude/index.js} +12 -12
  38. package/dist/commands/claude/open.d.ts +13 -0
  39. package/dist/commands/claude/open.js +175 -0
  40. package/dist/commands/diet.js +18 -2
  41. package/dist/commands/docker/logs.js +7 -3
  42. package/dist/commands/docker/shell.js +6 -0
  43. package/dist/commands/docker/start.js +20 -4
  44. package/dist/commands/docker/sync.d.ts +4 -0
  45. package/dist/commands/docker/sync.js +30 -2
  46. package/dist/commands/epic/show.d.ts +13 -0
  47. package/dist/commands/epic/show.js +16 -0
  48. package/dist/commands/epic/view.js +27 -0
  49. package/dist/commands/execution/config.d.ts +0 -4
  50. package/dist/commands/execution/config.js +10 -32
  51. package/dist/commands/execution/index.js +2 -1
  52. package/dist/commands/execution/logs.js +1 -1
  53. package/dist/commands/execution/stop.js +2 -1
  54. package/dist/commands/execution/view.js +22 -26
  55. package/dist/commands/init.js +2 -19
  56. package/dist/commands/label/create.d.ts +20 -0
  57. package/dist/commands/label/create.js +57 -0
  58. package/dist/commands/label/delete.d.ts +17 -0
  59. package/dist/commands/label/delete.js +32 -0
  60. package/dist/commands/label/group/create.d.ts +20 -0
  61. package/dist/commands/label/group/create.js +55 -0
  62. package/dist/commands/label/group/list.d.ts +14 -0
  63. package/dist/commands/label/group/list.js +52 -0
  64. package/dist/commands/label/index.d.ts +15 -0
  65. package/dist/commands/label/index.js +58 -0
  66. package/dist/commands/label/list.d.ts +16 -0
  67. package/dist/commands/label/list.js +83 -0
  68. package/dist/commands/link/list.js +3 -2
  69. package/dist/commands/mcp-server.js +27 -1
  70. package/dist/commands/phase/template/apply.d.ts +26 -0
  71. package/dist/commands/phase/template/apply.js +14 -0
  72. package/dist/commands/phase/template/create.d.ts +23 -0
  73. package/dist/commands/phase/template/create.js +14 -0
  74. package/dist/commands/phase/template/delete.d.ts +18 -0
  75. package/dist/commands/phase/template/delete.js +61 -0
  76. package/dist/commands/phase/template/list.d.ts +17 -0
  77. package/dist/commands/phase/template/list.js +89 -0
  78. package/dist/commands/phase/template/update.d.ts +1 -0
  79. package/dist/commands/phase/template/update.js +1 -0
  80. package/dist/commands/priority/add.js +1 -1
  81. package/dist/commands/project/create.js +3 -4
  82. package/dist/commands/project/update.js +5 -8
  83. package/dist/commands/pull.js +24 -0
  84. package/dist/commands/roadmap/generate.js +1 -2
  85. package/dist/commands/session/create.d.ts +19 -0
  86. package/dist/commands/session/create.js +102 -0
  87. package/dist/commands/session/health.js +2 -21
  88. package/dist/commands/session/index.js +14 -1
  89. package/dist/commands/session/list.js +26 -7
  90. package/dist/commands/session/peek.d.ts +38 -0
  91. package/dist/commands/session/peek.js +316 -0
  92. package/dist/commands/session/poke.d.ts +27 -0
  93. package/dist/commands/session/poke.js +219 -0
  94. package/dist/commands/spec/link/depends.d.ts +18 -0
  95. package/dist/commands/spec/link/depends.js +86 -0
  96. package/dist/commands/spec/link/index.d.ts +17 -0
  97. package/dist/commands/spec/link/index.js +92 -0
  98. package/dist/commands/spec/link/remove.d.ts +18 -0
  99. package/dist/commands/spec/link/remove.js +90 -0
  100. package/dist/commands/spec/view.js +29 -0
  101. package/dist/commands/support/logs.js +2 -2
  102. package/dist/commands/template/apply.js +5 -4
  103. package/dist/commands/template/create.js +1 -1
  104. package/dist/commands/template/list.js +2 -1
  105. package/dist/commands/theme/add-names.d.ts +4 -0
  106. package/dist/commands/theme/add-names.js +11 -1
  107. package/dist/commands/theme/create.d.ts +2 -0
  108. package/dist/commands/theme/create.js +8 -0
  109. package/dist/commands/ticket/bulk.js +2 -2
  110. package/dist/commands/ticket/complete.js +2 -2
  111. package/dist/commands/ticket/create.js +21 -0
  112. package/dist/commands/ticket/delete.js +8 -0
  113. package/dist/commands/ticket/edit.js +25 -0
  114. package/dist/commands/ticket/index.js +2 -2
  115. package/dist/commands/ticket/link/block.d.ts +15 -0
  116. package/dist/commands/ticket/link/block.js +95 -0
  117. package/dist/commands/ticket/link/index.d.ts +14 -0
  118. package/dist/commands/ticket/link/index.js +96 -0
  119. package/dist/commands/ticket/list.d.ts +1 -0
  120. package/dist/commands/ticket/list.js +6 -0
  121. package/dist/commands/ticket/move.js +25 -2
  122. package/dist/commands/ticket/resolve.js +4 -5
  123. package/dist/commands/ticket/show.d.ts +13 -0
  124. package/dist/commands/ticket/show.js +16 -0
  125. package/dist/commands/ticket/template/apply.d.ts +26 -0
  126. package/dist/commands/ticket/template/apply.js +14 -0
  127. package/dist/commands/ticket/template/delete.d.ts +18 -0
  128. package/dist/commands/ticket/template/delete.js +61 -0
  129. package/dist/commands/ticket/template/list.d.ts +17 -0
  130. package/dist/commands/ticket/template/list.js +78 -0
  131. package/dist/commands/ticket/template/save.d.ts +17 -0
  132. package/dist/commands/ticket/template/save.js +97 -0
  133. package/dist/commands/ticket/view.js +30 -0
  134. package/dist/commands/work/index.js +4 -0
  135. package/dist/commands/work/ready.js +17 -0
  136. package/dist/commands/work/resolve.js +1 -1
  137. package/dist/commands/work/spawn.js +4 -4
  138. package/dist/commands/work/start.d.ts +1 -0
  139. package/dist/commands/work/start.js +203 -93
  140. package/dist/commands/work/status.d.ts +14 -0
  141. package/dist/commands/work/status.js +60 -0
  142. package/dist/commands/workflow/index.js +2 -1
  143. package/dist/commands/workflow/show.d.ts +13 -0
  144. package/dist/commands/workflow/show.js +16 -0
  145. package/dist/commands/workspace/add.js +15 -0
  146. package/dist/commands/workspace/list.js +2 -1
  147. package/dist/commands/workspace/prune.js +5 -5
  148. package/dist/lib/branch/index.d.ts +1 -0
  149. package/dist/lib/database/index.d.ts +1 -1
  150. package/dist/lib/database/index.js +20 -0
  151. package/dist/lib/execution/config.d.ts +15 -1
  152. package/dist/lib/execution/config.js +28 -0
  153. package/dist/lib/execution/devcontainer.js +3 -1
  154. package/dist/lib/execution/runners.d.ts +18 -2
  155. package/dist/lib/execution/runners.js +71 -29
  156. package/dist/lib/execution/session-utils.d.ts +11 -1
  157. package/dist/lib/execution/session-utils.js +26 -1
  158. package/dist/lib/execution/storage.d.ts +5 -0
  159. package/dist/lib/execution/storage.js +18 -3
  160. package/dist/lib/execution/types.d.ts +3 -0
  161. package/dist/lib/flags/resolver.js +1 -0
  162. package/dist/lib/mcp/helpers.d.ts +1 -2
  163. package/dist/lib/mcp/tools/board.js +4 -6
  164. package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
  165. package/dist/lib/mcp/tools/diet.js +1 -0
  166. package/dist/lib/mcp/tools/epic.js +8 -3
  167. package/dist/lib/mcp/tools/index.d.ts +1 -0
  168. package/dist/lib/mcp/tools/index.js +1 -0
  169. package/dist/lib/mcp/tools/label.d.ts +6 -0
  170. package/dist/lib/mcp/tools/label.js +338 -0
  171. package/dist/lib/mcp/tools/spec.js +1 -1
  172. package/dist/lib/mcp/tools/ticket.js +57 -19
  173. package/dist/lib/mcp/tools/work.js +96 -6
  174. package/dist/lib/mcp/types.d.ts +10 -0
  175. package/dist/lib/multiline-input.js +8 -19
  176. package/dist/lib/pmo/base-command.d.ts +0 -1
  177. package/dist/lib/pmo/base-command.js +4 -5
  178. package/dist/lib/pmo/schema.d.ts +6 -0
  179. package/dist/lib/pmo/schema.js +44 -0
  180. package/dist/lib/pmo/storage/actions.js +1 -1
  181. package/dist/lib/pmo/storage/base.d.ts +6 -0
  182. package/dist/lib/pmo/storage/base.js +311 -52
  183. package/dist/lib/pmo/storage/index.d.ts +23 -1
  184. package/dist/lib/pmo/storage/index.js +59 -1
  185. package/dist/lib/pmo/storage/labels.d.ts +55 -0
  186. package/dist/lib/pmo/storage/labels.js +346 -0
  187. package/dist/lib/pmo/storage/tickets.js +17 -0
  188. package/dist/lib/pmo/storage/types.d.ts +25 -0
  189. package/dist/lib/pmo/types.d.ts +44 -0
  190. package/dist/lib/pmo/utils.js +1 -1
  191. package/dist/lib/prompt-command.d.ts +20 -0
  192. package/dist/lib/prompt-command.js +38 -2
  193. package/dist/lib/prompt-json.d.ts +36 -4
  194. package/dist/lib/prompt-json.js +129 -7
  195. package/dist/lib/styles.d.ts +37 -0
  196. package/dist/lib/styles.js +73 -0
  197. package/oclif.manifest.json +6399 -3799
  198. package/package.json +1 -1
@@ -0,0 +1,97 @@
1
+ import { Flags, Args } from '@oclif/core';
2
+ import { PMOCommand, pmoBaseFlags } from '../../../lib/pmo/index.js';
3
+ import { styles } from '../../../lib/styles.js';
4
+ import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../../lib/prompt-json.js';
5
+ export default class TicketTemplateSave extends PMOCommand {
6
+ static description = 'Create a ticket template from an existing ticket';
7
+ static examples = [
8
+ '<%= config.bin %> <%= command.id %> TKT-001 "Bug Report Template"',
9
+ '<%= config.bin %> <%= command.id %> TKT-042 "Feature Request" -d "Standard feature request"',
10
+ ];
11
+ static args = {
12
+ ticket: Args.string({
13
+ description: 'Ticket ID to create template from',
14
+ required: false,
15
+ }),
16
+ name: Args.string({
17
+ description: 'Template name',
18
+ required: false,
19
+ }),
20
+ };
21
+ static flags = {
22
+ ...pmoBaseFlags,
23
+ 'template-name': Flags.string({
24
+ char: 'n',
25
+ description: 'Template name (alternative to positional arg)',
26
+ }),
27
+ description: Flags.string({
28
+ char: 'd',
29
+ description: 'Template description',
30
+ }),
31
+ };
32
+ async execute() {
33
+ const { args, flags } = await this.parse(TicketTemplateSave);
34
+ const jsonMode = shouldOutputJson(flags);
35
+ const handleError = (code, message) => {
36
+ if (jsonMode) {
37
+ outputErrorAsJson(code, message, createMetadata('ticket template save', flags));
38
+ }
39
+ this.error(message);
40
+ };
41
+ // Get ticket ID
42
+ let ticketId = args.ticket;
43
+ if (!ticketId) {
44
+ const projectId = await this.requireProject();
45
+ const tickets = await this.storage.listTickets(projectId);
46
+ if (tickets.length === 0) {
47
+ return handleError('NO_TICKETS', 'No tickets found. Create a ticket first.');
48
+ }
49
+ if (jsonMode) {
50
+ const choices = tickets.slice(0, 20).map(t => ({ name: `${t.id} - ${t.title}`, value: t.id }));
51
+ outputPromptAsJson(buildPromptConfig('list', 'ticket', 'Select ticket:', choices), createMetadata('ticket template save', flags));
52
+ return;
53
+ }
54
+ const { selected } = await this.prompt([{
55
+ type: 'list',
56
+ name: 'selected',
57
+ message: 'Select ticket:',
58
+ choices: tickets.slice(0, 20).map(t => ({ name: `${t.id} - ${t.title}`, value: t.id })),
59
+ }], null);
60
+ ticketId = selected;
61
+ }
62
+ const ticket = await this.storage.getTicket(ticketId);
63
+ if (!ticket) {
64
+ return handleError('TICKET_NOT_FOUND', `Ticket not found: ${ticketId}`);
65
+ }
66
+ // Get template name
67
+ let templateName = flags['template-name'] || args.name;
68
+ if (!templateName) {
69
+ if (jsonMode) {
70
+ outputPromptAsJson(buildPromptConfig('input', 'name', 'Template name:', undefined, ticket.category || ticket.title.split(' ')[0]), createMetadata('ticket template save', flags));
71
+ return;
72
+ }
73
+ const { name } = await this.prompt([{
74
+ type: 'input',
75
+ name: 'name',
76
+ message: 'Template name:',
77
+ default: ticket.category || ticket.title.split(' ')[0],
78
+ validate: (i) => i.length > 0 || 'Required',
79
+ }], null);
80
+ templateName = name;
81
+ }
82
+ // Get description
83
+ let description = flags.description;
84
+ if (description === undefined && !jsonMode) {
85
+ const { desc } = await this.prompt([{ type: 'input', name: 'desc', message: 'Description (optional):' }], null);
86
+ description = desc || undefined;
87
+ }
88
+ const template = await this.storage.createTicketTemplateFromTicket(ticketId, templateName, description);
89
+ if (flags.json === true) {
90
+ outputSuccessAsJson({ template, sourceTicketId: ticketId }, createMetadata('ticket template save', flags));
91
+ return;
92
+ }
93
+ this.log(styles.success(`\nCreated template "${styles.emphasis(template.name)}" from ${ticketId}`));
94
+ this.log(styles.muted(` ID: ${template.id}`));
95
+ this.log(styles.muted(`\nApply with: prlt ticket template apply ${template.id}`));
96
+ }
97
+ }
@@ -57,6 +57,36 @@ export default class TicketView extends PMOCommand {
57
57
  if (!ticket) {
58
58
  this.error(`Ticket "${ticketId}" not found.`);
59
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
+ }
60
90
  // Get project board (may be null if project was deleted/orphaned)
61
91
  const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
62
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;
@@ -254,6 +254,23 @@ export default class WorkReady extends PMOCommand {
254
254
  }
255
255
  try {
256
256
  const baseBranch = getDefaultBaseBranch();
257
+ // Check if PR already exists for this branch
258
+ const existingPR = getPRForBranch(currentBranch);
259
+ if (existingPR) {
260
+ if (existingPR.state === 'MERGED') {
261
+ this.log(styles.muted(` PR #${existingPR.number} already merged: ${existingPR.url}`));
262
+ return existingPR.url;
263
+ }
264
+ if (existingPR.state === 'OPEN') {
265
+ // Push any unpushed commits so the existing PR is up to date
266
+ if (hasUnpushedCommits(currentBranch)) {
267
+ this.log(styles.muted(` Pushing unpushed commits to existing PR...`));
268
+ pushBranch(currentBranch);
269
+ }
270
+ this.log(styles.muted(` PR #${existingPR.number} already exists: ${existingPR.url}`));
271
+ return existingPR.url;
272
+ }
273
+ }
257
274
  // Push branch if needed
258
275
  if (!hasBranchBeenPushed(currentBranch)) {
259
276
  this.log(styles.muted(` Pushing branch to origin...`));
@@ -26,7 +26,7 @@ export default class WorkResolve extends PMOCommand {
26
26
  }),
27
27
  };
28
28
  async execute() {
29
- const { args, flags, argv } = await this.parse(WorkResolve);
29
+ const { flags, argv } = await this.parse(WorkResolve);
30
30
  const projectId = flags.project;
31
31
  const jsonMode = shouldOutputJson(flags);
32
32
  const handleError = (code, message) => {
@@ -491,18 +491,18 @@ export default class WorkSpawn extends PMOCommand {
491
491
  let candidates = [...allTickets];
492
492
  // Apply category filter
493
493
  if (flags.category) {
494
- const categoryList = flags.category.split(',').map(c => c.trim().toLowerCase());
494
+ const categoryList = new Set(flags.category.split(',').map(c => c.trim().toLowerCase()));
495
495
  candidates = candidates.filter(t => {
496
496
  const ticketCat = (t.category || '').toLowerCase();
497
- return categoryList.includes(ticketCat);
497
+ return categoryList.has(ticketCat);
498
498
  });
499
499
  }
500
500
  // Apply priority filter
501
501
  if (flags.priority) {
502
- const priorityList = flags.priority.split(',').map(p => p.trim().toUpperCase());
502
+ const priorityList = new Set(flags.priority.split(',').map(p => p.trim().toUpperCase()));
503
503
  candidates = candidates.filter(t => {
504
504
  const ticketPriority = (t.priority || '').toUpperCase();
505
- return priorityList.includes(ticketPriority);
505
+ return priorityList.has(ticketPriority);
506
506
  });
507
507
  }
508
508
  // Apply epic filter
@@ -28,6 +28,7 @@ export default class WorkStart extends PMOCommand {
28
28
  focus: import("@oclif/core/interfaces").BooleanFlag<boolean>;
29
29
  clone: import("@oclif/core/interfaces").BooleanFlag<boolean>;
30
30
  yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
31
+ 'use-api-key': import("@oclif/core/interfaces").BooleanFlag<boolean>;
31
32
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
32
33
  machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
33
34
  project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -1,3 +1,4 @@
1
+ /* eslint-disable max-lines -- large command with many execution paths */
1
2
  import { Args, Flags } from '@oclif/core';
2
3
  import * as fs from 'node:fs';
3
4
  import * as path from 'node:path';
@@ -12,7 +13,7 @@ import { getWorkspaceInfo, createEphemeralAgent, getTicketTmuxSession, killTmuxS
12
13
  import { generateBranchName, DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
13
14
  import { runExecution, isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled, dockerCredentialsExist, getDockerCredentialInfo } from '../../lib/execution/runners.js';
14
15
  import { ExecutionStorage, ContainerStorage } from '../../lib/execution/storage.js';
15
- 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';
16
17
  import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
17
18
  import { detectRepoWorktrees, resolveWorktreePath } from '../../lib/execution/context.js';
18
19
  import { isGHInstalled, isGHAuthenticated } from '../../lib/pr/index.js';
@@ -179,6 +180,11 @@ export default class WorkStart extends PMOCommand {
179
180
  description: 'Skip confirmation prompt (for non-TTY/scripted execution)',
180
181
  default: false,
181
182
  }),
183
+ 'use-api-key': Flags.boolean({
184
+ description: 'Use ANTHROPIC_API_KEY for Docker containers instead of OAuth credentials',
185
+ default: false,
186
+ hidden: true,
187
+ }),
182
188
  };
183
189
  async execute() {
184
190
  const { args, flags } = await this.parse(WorkStart);
@@ -804,11 +810,20 @@ export default class WorkStart extends PMOCommand {
804
810
  flags: {},
805
811
  });
806
812
  envResolver.addPrompt({
807
- flagName: 'selectedEnvironment',
813
+ flagName: 'environment',
808
814
  type: 'list',
809
815
  message: 'Where should the agent run?',
810
816
  default: 'devcontainer',
811
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
+ },
812
827
  });
813
828
  await envResolver.resolve();
814
829
  // FlagResolver exits in JSON mode, so we never reach here
@@ -1000,104 +1015,181 @@ export default class WorkStart extends PMOCommand {
1000
1015
  // Default to interactive output mode (streaming UI)
1001
1016
  // Can be overridden via --output flag if needed
1002
1017
  const outputMode = flags.output || DEFAULT_EXECUTION_CONFIG.outputMode;
1003
- // Check Docker credentials for devcontainer environment
1004
- if (environment === 'devcontainer') {
1005
- const hasCredentials = dockerCredentialsExist();
1006
- if (!hasCredentials) {
1007
- // In JSON mode with --yes, continue anyway (agent can run /login)
1008
- if (jsonMode && flags.yes) {
1009
- // Continue without prompting - agent will need to handle auth
1010
- }
1011
- else {
1018
+ // Track whether user explicitly chose to use API key instead of OAuth
1019
+ let useApiKey = flags['use-api-key'] || false;
1020
+ // Auth method resolution for devcontainer environment
1021
+ if (environment === 'devcontainer' && !useApiKey) {
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) {
1012
1028
  this.log('');
1013
- this.log(styles.warning('⚠️ No Claude Code credentials found for Docker containers'));
1014
- this.log(styles.muted(' Agents will fail with 401 authentication errors without credentials.'));
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) {
1015
1040
  this.log('');
1016
- // Use FlagResolver for auth action
1017
- const authResolver = new FlagResolver({
1018
- commandName: 'work start',
1019
- baseCommand: `prlt work start ${ticketId}`,
1020
- jsonMode,
1021
- flags: {},
1022
- });
1023
- authResolver.addPrompt({
1024
- flagName: 'authAction',
1025
- type: 'list',
1026
- message: 'What would you like to do?',
1027
- choices: () => [
1028
- { name: `🔐 Run ${this.config.bin} agent auth now (one-time setup)`, value: 'auth' },
1029
- { name: '💻 Switch to host environment instead', value: 'host' },
1030
- { name: '⏩ Continue anyway (must run /login in first agent)', value: 'continue' },
1031
- { name: '✗ Cancel', value: 'cancel' },
1032
- ],
1033
- });
1034
- const authResult = await authResolver.resolve();
1035
- const authAction = authResult.authAction;
1036
- if (authAction === 'cancel') {
1037
- db.close();
1038
- this.log(styles.muted('Cancelled.'));
1039
- return;
1040
- }
1041
- if (authAction === 'host') {
1042
- environment = 'host';
1043
- this.log(styles.muted('Switched to host environment.'));
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
1044
1060
  }
1045
- else if (authAction === 'auth') {
1061
+ else {
1046
1062
  this.log('');
1047
- 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.'));
1048
1065
  this.log('');
1049
- // Open auth in a new terminal tab
1050
- const authCmd = `${process.argv[1]} agent auth`;
1051
- try {
1052
- execSync(`osascript -e '
1053
- tell application "iTerm"
1054
- tell current window
1055
- create tab with default profile
1056
- tell current session
1057
- write text "${authCmd}"
1058
- end tell
1059
- end tell
1060
- end tell
1061
- '`);
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' });
1062
1072
  }
1063
- catch {
1064
- // 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`;
1065
1110
  try {
1066
- 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
+ '`);
1067
1121
  }
1068
1122
  catch {
1069
- this.log(styles.warning('Could not open new terminal tab.'));
1070
- 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()}`));
1071
1153
  }
1072
- }
1073
- this.log(styles.muted('Complete the /login flow in the new tab, then press Enter here...'));
1074
- this.log('');
1075
- // Wait for user to complete auth
1076
- await this.prompt([{
1077
- type: 'input',
1078
- name: 'done',
1079
- message: 'Press Enter when authentication is complete:',
1080
- }]);
1081
- // Check if credentials now exist
1082
- if (!dockerCredentialsExist()) {
1083
1154
  this.log('');
1084
- this.log(styles.warning('Authentication did not complete. No credentials found.'));
1085
- db.close();
1086
- return;
1087
1155
  }
1088
- const info = getDockerCredentialInfo();
1089
- this.log('');
1090
- this.log(styles.success(' Credentials configured'));
1091
- if (info) {
1092
- this.log(styles.muted(` Subscription: ${info.subscriptionType || 'unknown'}`));
1093
- 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
+ }
1094
1184
  }
1095
- this.log('');
1096
1185
  }
1097
- // authAction === 'continue' falls through
1098
1186
  }
1099
1187
  }
1100
1188
  }
1189
+ // Pass API key preference to execution context
1190
+ if (useApiKey) {
1191
+ context.useApiKey = true;
1192
+ }
1101
1193
  // Prompt for permissions mode (all environments)
1102
1194
  // Use FlagResolver to handle both JSON mode and interactive prompts consistently
1103
1195
  if (flags['permission-mode']) {
@@ -1140,9 +1232,13 @@ export default class WorkStart extends PMOCommand {
1140
1232
  createPR = false;
1141
1233
  }
1142
1234
  else if (ghAvailable) {
1143
- // In JSON mode with --yes, default to creating PR for code-modifying actions
1144
- if (jsonMode && flags.yes) {
1145
- 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;
1146
1242
  }
1147
1243
  else {
1148
1244
  // Use FlagResolver for PR choice
@@ -1611,23 +1707,30 @@ export default class WorkStart extends PMOCommand {
1611
1707
  const agentDir = path.join(workspaceInfo.agentsPath, agent.name);
1612
1708
  return hasDevcontainerConfig(agentDir) && !flags['run-on-host'];
1613
1709
  });
1710
+ // Track whether user explicitly chose to use API key instead of OAuth
1711
+ let batchUseApiKey = false;
1614
1712
  if (anyUseDevcontainer) {
1615
1713
  const hasCredentials = dockerCredentialsExist();
1616
1714
  if (!hasCredentials) {
1715
+ const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
1617
1716
  this.log('');
1618
- this.log(styles.warning('⚠️ No Claude Code credentials found for Docker containers'));
1619
- this.log(styles.muted(' Agents will fail with 401 authentication errors without credentials.'));
1717
+ this.log(styles.warning('⚠️ No Claude Code OAuth credentials found for Docker containers'));
1718
+ this.log(styles.muted(' Agents need credentials to authenticate with Claude.'));
1620
1719
  this.log('');
1720
+ // Build choices based on available options
1721
+ const batchAuthChoices = [
1722
+ { name: `🔐 Run ${this.config.bin} agent auth now (recommended — uses Max subscription)`, value: 'auth', command: `${this.config.bin} agent auth` },
1723
+ ];
1724
+ if (hasApiKey) {
1725
+ batchAuthChoices.push({ name: '🔑 Use ANTHROPIC_API_KEY (⚠️ uses API credits, not Max subscription)', value: 'apikey', command: '' });
1726
+ }
1727
+ batchAuthChoices.push({ name: '💻 Run all agents on host instead (--run-on-host)', value: 'host', command: 'prlt work start --all --run-on-host --json' }, { name: '✗ Cancel', value: 'cancel', command: '' });
1621
1728
  const { authAction } = await this.prompt([
1622
1729
  {
1623
1730
  type: 'list',
1624
1731
  name: 'authAction',
1625
1732
  message: 'What would you like to do?',
1626
- choices: [
1627
- { name: `🔐 Run ${this.config.bin} agent auth now (one-time setup)`, value: 'auth', command: `${this.config.bin} agent auth` },
1628
- { name: '💻 Run all agents on host instead (--run-on-host)', value: 'host', command: 'prlt work start --all --run-on-host --json' },
1629
- { name: '✗ Cancel', value: 'cancel', command: '' },
1630
- ],
1733
+ choices: batchAuthChoices,
1631
1734
  },
1632
1735
  ], batchJsonModeConfig);
1633
1736
  if (authAction === 'cancel') {
@@ -1639,6 +1742,12 @@ export default class WorkStart extends PMOCommand {
1639
1742
  flags['run-on-host'] = true;
1640
1743
  this.log(styles.muted('All agents will run on host.'));
1641
1744
  }
1745
+ else if (authAction === 'apikey') {
1746
+ batchUseApiKey = true;
1747
+ this.log(styles.warning('Using ANTHROPIC_API_KEY — this will consume API credits.'));
1748
+ this.log(styles.muted(`Run "${this.config.bin} agent auth" to set up OAuth and use your Max subscription instead.`));
1749
+ this.log('');
1750
+ }
1642
1751
  else if (authAction === 'auth') {
1643
1752
  this.log('');
1644
1753
  this.log(styles.primary(`Opening ${this.config.bin} agent auth in new tab...`));
@@ -1715,6 +1824,7 @@ export default class WorkStart extends PMOCommand {
1715
1824
  '--display', flags.display || 'background',
1716
1825
  ...(flags.executor ? ['--executor', flags.executor] : []),
1717
1826
  ...(flags['run-on-host'] ? ['--run-on-host'] : []),
1827
+ ...(batchUseApiKey ? ['--use-api-key'] : []),
1718
1828
  ...(flags.force ? ['--force'] : []),
1719
1829
  '--permission-mode', batchPermissionMode,
1720
1830
  ...(flags.clone ? ['--clone'] : []),
@@ -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
+ }