@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.
- package/dist/commands/agent/auth.d.ts +15 -3
- package/dist/commands/agent/auth.js +136 -15
- package/dist/commands/agent/index.js +11 -2
- package/dist/commands/agent/list.js +16 -7
- package/dist/commands/agent/staff/add.d.ts +1 -0
- package/dist/commands/agent/staff/add.js +1 -0
- package/dist/commands/agent/staff/index.d.ts +15 -0
- package/dist/commands/agent/staff/index.js +83 -0
- package/dist/commands/agent/staff/list.d.ts +1 -0
- package/dist/commands/agent/staff/list.js +1 -0
- package/dist/commands/agent/staff/remove.d.ts +1 -0
- package/dist/commands/agent/staff/remove.js +1 -0
- package/dist/commands/agent/status.js +32 -4
- package/dist/commands/agent/themes/add-names.d.ts +1 -0
- package/dist/commands/agent/themes/add-names.js +1 -0
- package/dist/commands/agent/themes/create.d.ts +1 -0
- package/dist/commands/agent/themes/create.js +1 -0
- package/dist/commands/agent/themes/index.d.ts +10 -0
- package/dist/commands/agent/themes/index.js +144 -0
- package/dist/commands/agent/themes/list.d.ts +1 -0
- package/dist/commands/agent/themes/list.js +1 -0
- package/dist/commands/agent/themes/set.d.ts +1 -0
- package/dist/commands/agent/themes/set.js +1 -0
- package/dist/commands/agents/themes/add-names.d.ts +1 -0
- package/dist/commands/agents/themes/add-names.js +1 -0
- package/dist/commands/agents/themes/create.d.ts +1 -0
- package/dist/commands/agents/themes/create.js +1 -0
- package/dist/commands/agents/themes/list.d.ts +1 -0
- package/dist/commands/agents/themes/list.js +1 -0
- package/dist/commands/board/watch.js +6 -0
- package/dist/commands/branch/list.d.ts +1 -0
- package/dist/commands/branch/list.js +43 -12
- package/dist/commands/branch/where.js +3 -2
- package/dist/commands/category/list.d.ts +2 -1
- package/dist/commands/category/list.js +38 -13
- package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
- package/dist/commands/{claude.js → claude/index.js} +12 -12
- package/dist/commands/claude/open.d.ts +13 -0
- package/dist/commands/claude/open.js +175 -0
- package/dist/commands/diet.js +18 -2
- package/dist/commands/docker/logs.js +7 -3
- package/dist/commands/docker/shell.js +6 -0
- package/dist/commands/docker/start.js +20 -4
- package/dist/commands/docker/sync.d.ts +4 -0
- package/dist/commands/docker/sync.js +30 -2
- package/dist/commands/epic/show.d.ts +13 -0
- package/dist/commands/epic/show.js +16 -0
- package/dist/commands/epic/view.js +27 -0
- package/dist/commands/execution/config.d.ts +0 -4
- package/dist/commands/execution/config.js +10 -32
- package/dist/commands/execution/index.js +2 -1
- package/dist/commands/execution/logs.js +1 -1
- package/dist/commands/execution/stop.js +2 -1
- package/dist/commands/execution/view.js +22 -26
- package/dist/commands/init.js +2 -19
- package/dist/commands/label/create.d.ts +20 -0
- package/dist/commands/label/create.js +57 -0
- package/dist/commands/label/delete.d.ts +17 -0
- package/dist/commands/label/delete.js +32 -0
- package/dist/commands/label/group/create.d.ts +20 -0
- package/dist/commands/label/group/create.js +55 -0
- package/dist/commands/label/group/list.d.ts +14 -0
- package/dist/commands/label/group/list.js +52 -0
- package/dist/commands/label/index.d.ts +15 -0
- package/dist/commands/label/index.js +58 -0
- package/dist/commands/label/list.d.ts +16 -0
- package/dist/commands/label/list.js +83 -0
- package/dist/commands/link/list.js +3 -2
- package/dist/commands/mcp-server.js +27 -1
- package/dist/commands/phase/template/apply.d.ts +26 -0
- package/dist/commands/phase/template/apply.js +14 -0
- package/dist/commands/phase/template/create.d.ts +23 -0
- package/dist/commands/phase/template/create.js +14 -0
- package/dist/commands/phase/template/delete.d.ts +18 -0
- package/dist/commands/phase/template/delete.js +61 -0
- package/dist/commands/phase/template/list.d.ts +17 -0
- package/dist/commands/phase/template/list.js +89 -0
- package/dist/commands/phase/template/update.d.ts +1 -0
- package/dist/commands/phase/template/update.js +1 -0
- package/dist/commands/priority/add.js +1 -1
- package/dist/commands/project/create.js +3 -4
- package/dist/commands/project/update.js +5 -8
- package/dist/commands/pull.js +24 -0
- package/dist/commands/roadmap/generate.js +1 -2
- package/dist/commands/session/create.d.ts +19 -0
- package/dist/commands/session/create.js +102 -0
- package/dist/commands/session/health.js +2 -21
- package/dist/commands/session/index.js +14 -1
- package/dist/commands/session/list.js +26 -7
- package/dist/commands/session/peek.d.ts +38 -0
- package/dist/commands/session/peek.js +316 -0
- package/dist/commands/session/poke.d.ts +27 -0
- package/dist/commands/session/poke.js +219 -0
- package/dist/commands/spec/link/depends.d.ts +18 -0
- package/dist/commands/spec/link/depends.js +86 -0
- package/dist/commands/spec/link/index.d.ts +17 -0
- package/dist/commands/spec/link/index.js +92 -0
- package/dist/commands/spec/link/remove.d.ts +18 -0
- package/dist/commands/spec/link/remove.js +90 -0
- package/dist/commands/spec/view.js +29 -0
- package/dist/commands/support/logs.js +2 -2
- package/dist/commands/template/apply.js +5 -4
- package/dist/commands/template/create.js +1 -1
- package/dist/commands/template/list.js +2 -1
- package/dist/commands/theme/add-names.d.ts +4 -0
- package/dist/commands/theme/add-names.js +11 -1
- package/dist/commands/theme/create.d.ts +2 -0
- package/dist/commands/theme/create.js +8 -0
- package/dist/commands/ticket/bulk.js +2 -2
- package/dist/commands/ticket/complete.js +2 -2
- package/dist/commands/ticket/create.js +21 -0
- package/dist/commands/ticket/delete.js +8 -0
- package/dist/commands/ticket/edit.js +25 -0
- package/dist/commands/ticket/index.js +2 -2
- package/dist/commands/ticket/link/block.d.ts +15 -0
- package/dist/commands/ticket/link/block.js +95 -0
- package/dist/commands/ticket/link/index.d.ts +14 -0
- package/dist/commands/ticket/link/index.js +96 -0
- package/dist/commands/ticket/list.d.ts +1 -0
- package/dist/commands/ticket/list.js +6 -0
- package/dist/commands/ticket/move.js +25 -2
- package/dist/commands/ticket/resolve.js +4 -5
- package/dist/commands/ticket/show.d.ts +13 -0
- package/dist/commands/ticket/show.js +16 -0
- package/dist/commands/ticket/template/apply.d.ts +26 -0
- package/dist/commands/ticket/template/apply.js +14 -0
- package/dist/commands/ticket/template/delete.d.ts +18 -0
- package/dist/commands/ticket/template/delete.js +61 -0
- package/dist/commands/ticket/template/list.d.ts +17 -0
- package/dist/commands/ticket/template/list.js +78 -0
- package/dist/commands/ticket/template/save.d.ts +17 -0
- package/dist/commands/ticket/template/save.js +97 -0
- package/dist/commands/ticket/view.js +30 -0
- package/dist/commands/work/index.js +4 -0
- package/dist/commands/work/ready.js +17 -0
- package/dist/commands/work/resolve.js +1 -1
- package/dist/commands/work/spawn.js +4 -4
- package/dist/commands/work/start.d.ts +1 -0
- package/dist/commands/work/start.js +203 -93
- package/dist/commands/work/status.d.ts +14 -0
- package/dist/commands/work/status.js +60 -0
- package/dist/commands/workflow/index.js +2 -1
- package/dist/commands/workflow/show.d.ts +13 -0
- package/dist/commands/workflow/show.js +16 -0
- package/dist/commands/workspace/add.js +15 -0
- package/dist/commands/workspace/list.js +2 -1
- package/dist/commands/workspace/prune.js +5 -5
- package/dist/lib/branch/index.d.ts +1 -0
- package/dist/lib/database/index.d.ts +1 -1
- package/dist/lib/database/index.js +20 -0
- package/dist/lib/execution/config.d.ts +15 -1
- package/dist/lib/execution/config.js +28 -0
- package/dist/lib/execution/devcontainer.js +3 -1
- package/dist/lib/execution/runners.d.ts +18 -2
- package/dist/lib/execution/runners.js +71 -29
- package/dist/lib/execution/session-utils.d.ts +11 -1
- package/dist/lib/execution/session-utils.js +26 -1
- package/dist/lib/execution/storage.d.ts +5 -0
- package/dist/lib/execution/storage.js +18 -3
- package/dist/lib/execution/types.d.ts +3 -0
- package/dist/lib/flags/resolver.js +1 -0
- package/dist/lib/mcp/helpers.d.ts +1 -2
- package/dist/lib/mcp/tools/board.js +4 -6
- package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
- package/dist/lib/mcp/tools/diet.js +1 -0
- package/dist/lib/mcp/tools/epic.js +8 -3
- package/dist/lib/mcp/tools/index.d.ts +1 -0
- package/dist/lib/mcp/tools/index.js +1 -0
- package/dist/lib/mcp/tools/label.d.ts +6 -0
- package/dist/lib/mcp/tools/label.js +338 -0
- package/dist/lib/mcp/tools/spec.js +1 -1
- package/dist/lib/mcp/tools/ticket.js +57 -19
- package/dist/lib/mcp/tools/work.js +96 -6
- package/dist/lib/mcp/types.d.ts +10 -0
- package/dist/lib/multiline-input.js +8 -19
- package/dist/lib/pmo/base-command.d.ts +0 -1
- package/dist/lib/pmo/base-command.js +4 -5
- package/dist/lib/pmo/schema.d.ts +6 -0
- package/dist/lib/pmo/schema.js +44 -0
- package/dist/lib/pmo/storage/actions.js +1 -1
- package/dist/lib/pmo/storage/base.d.ts +6 -0
- package/dist/lib/pmo/storage/base.js +311 -52
- package/dist/lib/pmo/storage/index.d.ts +23 -1
- package/dist/lib/pmo/storage/index.js +59 -1
- package/dist/lib/pmo/storage/labels.d.ts +55 -0
- package/dist/lib/pmo/storage/labels.js +346 -0
- package/dist/lib/pmo/storage/tickets.js +17 -0
- package/dist/lib/pmo/storage/types.d.ts +25 -0
- package/dist/lib/pmo/types.d.ts +44 -0
- package/dist/lib/pmo/utils.js +1 -1
- package/dist/lib/prompt-command.d.ts +20 -0
- package/dist/lib/prompt-command.js +38 -2
- package/dist/lib/prompt-json.d.ts +36 -4
- package/dist/lib/prompt-json.js +129 -7
- package/dist/lib/styles.d.ts +37 -0
- package/dist/lib/styles.js +73 -0
- package/oclif.manifest.json +6399 -3799
- 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 {
|
|
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.
|
|
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.
|
|
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: '
|
|
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
|
-
//
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
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('⚠️
|
|
1014
|
-
this.log(styles.muted('
|
|
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
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
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
|
|
1061
|
+
else {
|
|
1046
1062
|
this.log('');
|
|
1047
|
-
this.log(styles.
|
|
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
|
-
//
|
|
1050
|
-
const
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
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
|
-
|
|
1064
|
-
|
|
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 '
|
|
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
|
-
|
|
1070
|
-
|
|
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
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
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
|
-
|
|
1144
|
-
|
|
1145
|
-
createPR =
|
|
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
|
|
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
|
+
}
|