@proletariat/cli 0.3.9 → 0.3.11
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/README.md +25 -0
- package/bin/dev.js +0 -0
- package/dist/commands/action/index.js +1 -1
- package/dist/commands/action/run.js +8 -12
- package/dist/commands/agent/auth.d.ts +30 -0
- package/dist/commands/agent/auth.js +172 -0
- package/dist/commands/agent/discover.d.ts +9 -0
- package/dist/commands/agent/discover.js +67 -0
- package/dist/commands/agent/index.js +47 -12
- package/dist/commands/agent/list.d.ts +4 -1
- package/dist/commands/agent/list.js +78 -16
- package/dist/commands/agent/login.js +35 -31
- package/dist/commands/agent/restart.js +2 -0
- package/dist/commands/agent/shell.js +78 -19
- package/dist/commands/agent/staff/add.js +1 -12
- package/dist/commands/agent/staff/remove.js +9 -7
- package/dist/commands/agent/status.js +17 -4
- package/dist/commands/agent/temp/cleanup.js +7 -3
- package/dist/commands/agent/themes/index.js +4 -5
- package/dist/commands/agent/themes/list.js +5 -5
- package/dist/commands/agent/visit.js +17 -4
- package/dist/commands/branch/create.d.ts +4 -0
- package/dist/commands/branch/create.js +16 -8
- package/dist/commands/branch/index.js +1 -1
- package/dist/commands/branch/where.js +1 -0
- package/dist/commands/claude.d.ts +38 -0
- package/dist/commands/claude.js +899 -0
- package/dist/commands/commit.js +1 -1
- package/dist/commands/config/index.d.ts +12 -0
- package/dist/commands/config/index.js +271 -0
- package/dist/commands/docker/clean.js +2 -2
- package/dist/commands/docker/index.js +2 -2
- package/dist/commands/docker/list.js +3 -8
- package/dist/commands/docker/logs.js +2 -2
- package/dist/commands/docker/prune.js +1 -1
- package/dist/commands/docker/restart.js +2 -2
- package/dist/commands/docker/shell.js +2 -2
- package/dist/commands/docker/start.js +2 -2
- package/dist/commands/docker/status.js +1 -1
- package/dist/commands/docker/stop.js +2 -2
- package/dist/commands/docker/sync.js +2 -2
- package/dist/commands/epic/index.js +1 -1
- package/dist/commands/epic/link/index.js +25 -14
- package/dist/commands/epic/link/remove.js +2 -0
- package/dist/commands/epic/list.js +5 -5
- package/dist/commands/epic/progress.js +10 -4
- package/dist/commands/epic/spec.js +2 -0
- package/dist/commands/epic/ticket.js +3 -0
- package/dist/commands/execution/stop.js +1 -0
- package/dist/commands/init.js +4 -4
- package/dist/commands/project/index.js +1 -1
- package/dist/commands/project/spec.js +7 -0
- package/dist/commands/repo/add.js +1 -0
- package/dist/commands/repo/remove.js +1 -0
- package/dist/commands/roadmap/add-project.d.ts +18 -0
- package/dist/commands/roadmap/add-project.js +135 -0
- package/dist/commands/roadmap/create.d.ts +22 -0
- package/dist/commands/roadmap/create.js +156 -0
- package/dist/commands/roadmap/delete.d.ts +17 -0
- package/dist/commands/roadmap/delete.js +104 -0
- package/dist/commands/roadmap/generate.d.ts +22 -0
- package/dist/commands/roadmap/generate.js +201 -0
- package/dist/commands/roadmap/index.d.ts +13 -0
- package/dist/commands/roadmap/index.js +61 -0
- package/dist/commands/roadmap/list.d.ts +12 -0
- package/dist/commands/roadmap/list.js +42 -0
- package/dist/commands/roadmap/remove-project.d.ts +18 -0
- package/dist/commands/roadmap/remove-project.js +147 -0
- package/dist/commands/roadmap/reorder.d.ts +17 -0
- package/dist/commands/roadmap/reorder.js +157 -0
- package/dist/commands/roadmap/update.d.ts +19 -0
- package/dist/commands/roadmap/update.js +136 -0
- package/dist/commands/roadmap/view.d.ts +16 -0
- package/dist/commands/roadmap/view.js +103 -0
- package/dist/commands/spec/index.js +1 -1
- package/dist/commands/spec/link/index.js +24 -13
- package/dist/commands/spec/link/remove.js +2 -0
- package/dist/commands/status/index.js +1 -1
- package/dist/commands/status/list.js +0 -8
- package/dist/commands/template/delete.js +2 -0
- package/dist/commands/terminal/title.d.ts +12 -0
- package/dist/commands/terminal/title.js +48 -0
- package/dist/commands/ticket/complete.js +2 -0
- package/dist/commands/ticket/create.js +4 -2
- package/dist/commands/ticket/delete.js +2 -0
- package/dist/commands/ticket/edit.js +8 -2
- package/dist/commands/ticket/link/index.js +17 -3
- package/dist/commands/ticket/link/remove.js +2 -0
- package/dist/commands/ticket/list.js +1 -2
- package/dist/commands/ticket/move.js +2 -0
- package/dist/commands/ticket/project.js +3 -1
- package/dist/commands/ticket/reassign.js +2 -0
- package/dist/commands/ticket/spec.js +4 -2
- package/dist/commands/ticket/template/apply.js +4 -3
- package/dist/commands/ticket/template/create.js +2 -0
- package/dist/commands/ticket/template/index.js +1 -1
- package/dist/commands/ticket/update.js +2 -0
- package/dist/commands/work/index.js +1 -1
- package/dist/commands/work/revise.js +7 -1
- package/dist/commands/work/spawn.d.ts +2 -1
- package/dist/commands/work/spawn.js +131 -36
- package/dist/commands/work/start.d.ts +2 -1
- package/dist/commands/work/start.js +349 -69
- package/dist/commands/work/watch.js +10 -2
- package/dist/commands/workflow/create.js +3 -3
- package/dist/commands/workflow/switch.js +2 -1
- package/dist/commands/workspace/remove.js +0 -8
- package/dist/commands/workspace/use.js +1 -9
- package/dist/lib/agents/commands.js +18 -13
- package/dist/lib/database/index.d.ts +19 -12
- package/dist/lib/database/index.js +158 -42
- package/dist/lib/docker/resolve.js +1 -1
- package/dist/lib/execution/config.d.ts +6 -0
- package/dist/lib/execution/config.js +15 -2
- package/dist/lib/execution/devcontainer.d.ts +2 -0
- package/dist/lib/execution/devcontainer.js +41 -9
- package/dist/lib/execution/runners.d.ts +85 -3
- package/dist/lib/execution/runners.js +925 -228
- package/dist/lib/execution/spawner.d.ts +2 -2
- package/dist/lib/execution/spawner.js +4 -3
- package/dist/lib/execution/storage.d.ts +2 -1
- package/dist/lib/execution/storage.js +9 -13
- package/dist/lib/execution/types.d.ts +10 -1
- package/dist/lib/execution/types.js +3 -1
- package/dist/lib/init/index.js +1 -0
- package/dist/lib/machine-config.js +1 -1
- package/dist/lib/pmo/base-command.js +5 -9
- package/dist/lib/pmo/index.js +2 -0
- package/dist/lib/pmo/schema.d.ts +6 -0
- package/dist/lib/pmo/schema.js +36 -0
- package/dist/lib/pmo/storage/base.js +3 -3
- package/dist/lib/pmo/storage/index.d.ts +16 -1
- package/dist/lib/pmo/storage/index.js +45 -0
- package/dist/lib/pmo/storage/roadmaps.d.ts +62 -0
- package/dist/lib/pmo/storage/roadmaps.js +301 -0
- package/dist/lib/pmo/storage/specs.js +2 -0
- package/dist/lib/pmo/storage/types.d.ts +14 -0
- package/dist/lib/pmo/sync-manager.d.ts +1 -1
- package/dist/lib/pmo/sync-manager.js +1 -1
- package/dist/lib/pmo/types.d.ts +41 -0
- package/dist/lib/pmo/utils.d.ts +2 -0
- package/dist/lib/pmo/utils.js +22 -1
- package/dist/lib/repos/index.js +7 -1
- package/dist/lib/terminal.d.ts +31 -0
- package/dist/lib/terminal.js +48 -0
- package/dist/lib/themes.d.ts +21 -3
- package/dist/lib/themes.js +80 -23
- package/dist/lib/workspace-config.d.ts +80 -0
- package/dist/lib/workspace-config.js +100 -0
- package/oclif.manifest.json +4065 -3225
- package/package.json +10 -6
- package/LICENSE +0 -21
|
@@ -82,9 +82,10 @@ export default class TicketLink extends PMOCommand {
|
|
|
82
82
|
await this.addDependency(this.storage, this.pmoPath, ticketId, targetId, dependencyType, ticket.title);
|
|
83
83
|
return;
|
|
84
84
|
}
|
|
85
|
-
// Interactive mode: show menu in a loop
|
|
85
|
+
// Interactive mode: show menu in a loop - user interaction requires sequential processing
|
|
86
86
|
let continueLoop = true;
|
|
87
87
|
while (continueLoop) {
|
|
88
|
+
// eslint-disable-next-line no-await-in-loop
|
|
88
89
|
const allTickets = await this.storage.listTickets(projectId);
|
|
89
90
|
const otherTickets = allTickets.filter(t => t.id !== ticketId);
|
|
90
91
|
const menuChoices = [
|
|
@@ -95,6 +96,7 @@ export default class TicketLink extends PMOCommand {
|
|
|
95
96
|
{ id: 'remove', name: 'Remove dependency' },
|
|
96
97
|
{ id: 'done', name: 'Done' },
|
|
97
98
|
];
|
|
99
|
+
// eslint-disable-next-line no-await-in-loop
|
|
98
100
|
const action = await this.selectFromList({
|
|
99
101
|
message: `Dependencies for ${ticket.id}:`,
|
|
100
102
|
items: menuChoices,
|
|
@@ -117,15 +119,18 @@ export default class TicketLink extends PMOCommand {
|
|
|
117
119
|
continue;
|
|
118
120
|
}
|
|
119
121
|
if (action === 'view') {
|
|
122
|
+
// eslint-disable-next-line no-await-in-loop
|
|
120
123
|
await this.viewDependencies(this.storage, ticketId, ticket, flags.all);
|
|
121
124
|
continue;
|
|
122
125
|
}
|
|
123
126
|
if (action === 'remove') {
|
|
127
|
+
// eslint-disable-next-line no-await-in-loop
|
|
124
128
|
const dependencies = await this.storage.listTicketDependencies(ticketId);
|
|
125
129
|
if (dependencies.length === 0) {
|
|
126
130
|
this.log(styles.muted('\nNo dependencies to remove.'));
|
|
127
131
|
continue;
|
|
128
132
|
}
|
|
133
|
+
// eslint-disable-next-line no-await-in-loop
|
|
129
134
|
const depChoices = await Promise.all(dependencies.map(async (dep) => {
|
|
130
135
|
const depTicket = await this.storage.getTicket(dep.dependsOnTicketId);
|
|
131
136
|
return {
|
|
@@ -134,6 +139,7 @@ export default class TicketLink extends PMOCommand {
|
|
|
134
139
|
type: dep.dependencyType
|
|
135
140
|
};
|
|
136
141
|
}));
|
|
142
|
+
// eslint-disable-next-line no-await-in-loop
|
|
137
143
|
const selected = await this.selectFromList({
|
|
138
144
|
message: 'Select dependency to remove:',
|
|
139
145
|
items: depChoices,
|
|
@@ -147,7 +153,9 @@ export default class TicketLink extends PMOCommand {
|
|
|
147
153
|
}
|
|
148
154
|
const selectedDep = depChoices.find(d => d.id === selected);
|
|
149
155
|
if (selectedDep) {
|
|
156
|
+
// eslint-disable-next-line no-await-in-loop
|
|
150
157
|
await this.storage.deleteTicketDependency(ticketId, selected, selectedDep.type);
|
|
158
|
+
// eslint-disable-next-line no-await-in-loop
|
|
151
159
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
152
160
|
this.log(styles.success(`\n✅ Removed dependency: ${ticketId} → ${selected}`));
|
|
153
161
|
}
|
|
@@ -158,6 +166,7 @@ export default class TicketLink extends PMOCommand {
|
|
|
158
166
|
this.log(styles.muted('\nNo other tickets to link to.'));
|
|
159
167
|
continue;
|
|
160
168
|
}
|
|
169
|
+
// eslint-disable-next-line no-await-in-loop
|
|
161
170
|
const targetId = await this.selectFromList({
|
|
162
171
|
message: `Select ticket that ${ticketId} ${action === 'blocks' ? 'is blocked by' : action === 'relates_to' ? 'relates to' : 'duplicates'}:`,
|
|
163
172
|
items: otherTickets,
|
|
@@ -167,6 +176,7 @@ export default class TicketLink extends PMOCommand {
|
|
|
167
176
|
jsonMode: jsonMode ? { flags, commandName: 'ticket link' } : null,
|
|
168
177
|
});
|
|
169
178
|
if (targetId) {
|
|
179
|
+
// eslint-disable-next-line no-await-in-loop
|
|
170
180
|
await this.addDependency(this.storage, this.pmoPath, ticketId, targetId, action, ticket.title);
|
|
171
181
|
}
|
|
172
182
|
}
|
|
@@ -215,8 +225,12 @@ export default class TicketLink extends PMOCommand {
|
|
|
215
225
|
const otherDeps = dependencies.filter(d => d.dependencyType !== 'blocks');
|
|
216
226
|
if (otherDeps.length > 0) {
|
|
217
227
|
this.log(styles.muted('\n Related:'));
|
|
218
|
-
|
|
219
|
-
|
|
228
|
+
// Fetch all related tickets in parallel
|
|
229
|
+
const relatedTickets = await Promise.all(otherDeps.map(async (dep) => ({
|
|
230
|
+
dep,
|
|
231
|
+
ticket: await this.storage.getTicket(dep.dependsOnTicketId)
|
|
232
|
+
})));
|
|
233
|
+
for (const { dep, ticket: relatedTicket } of relatedTickets) {
|
|
220
234
|
if (relatedTicket) {
|
|
221
235
|
this.log(` - ${dep.dependencyType}: ${relatedTicket.id} - ${relatedTicket.title}`);
|
|
222
236
|
}
|
|
@@ -80,7 +80,9 @@ export default class TicketLinkRemove extends PMOCommand {
|
|
|
80
80
|
this.log(styles.muted('\nCancelled.'));
|
|
81
81
|
return;
|
|
82
82
|
}
|
|
83
|
+
// Delete sequentially for data integrity
|
|
83
84
|
for (const dep of dependencies) {
|
|
85
|
+
// eslint-disable-next-line no-await-in-loop
|
|
84
86
|
await this.storage.deleteTicketDependency(args.id, dep.dependsOnTicketId, dep.dependencyType);
|
|
85
87
|
}
|
|
86
88
|
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
@@ -58,9 +58,8 @@ export default class TicketList extends Command {
|
|
|
58
58
|
const { flags } = await this.parse(TicketList);
|
|
59
59
|
// When --all is set, we don't need to select a specific project
|
|
60
60
|
// Otherwise, use the normal project selection flow
|
|
61
|
-
let pmoContext;
|
|
62
61
|
// Get PMO context - no project selection needed
|
|
63
|
-
pmoContext = await getPMOContext({
|
|
62
|
+
const pmoContext = await getPMOContext({
|
|
64
63
|
logger: (msg) => this.log(styles.muted(msg)),
|
|
65
64
|
});
|
|
66
65
|
try {
|
|
@@ -187,8 +187,10 @@ export default class TicketMove extends PMOCommand {
|
|
|
187
187
|
// Move each ticket
|
|
188
188
|
let successCount = 0;
|
|
189
189
|
let failCount = 0;
|
|
190
|
+
// Process sequentially for clear success/failure logging
|
|
190
191
|
for (const ticketId of selectedTickets) {
|
|
191
192
|
try {
|
|
193
|
+
// eslint-disable-next-line no-await-in-loop
|
|
192
194
|
await this.storage.moveTicket(projectId, ticketId, targetColumn);
|
|
193
195
|
this.log(styles.success(`Moved ${ticketId} to ${targetColumn}`));
|
|
194
196
|
successCount++;
|
|
@@ -235,15 +235,17 @@ export default class TicketProject extends PMOCommand {
|
|
|
235
235
|
return;
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
-
// Move each ticket using the storage method
|
|
238
|
+
// Move each ticket using the storage method - sequential for clear logging
|
|
239
239
|
let lastMovedTicket;
|
|
240
240
|
for (const ticketId of selectedTickets) {
|
|
241
241
|
const ticket = tickets.find(t => t.id === ticketId);
|
|
242
242
|
// Unlink from epic if needed
|
|
243
243
|
if (ticket?.epicId) {
|
|
244
|
+
// eslint-disable-next-line no-await-in-loop
|
|
244
245
|
await this.storage.updateTicket(ticketId, { epicId: undefined });
|
|
245
246
|
}
|
|
246
247
|
// Move ticket to new project
|
|
248
|
+
// eslint-disable-next-line no-await-in-loop
|
|
247
249
|
lastMovedTicket = await this.storage.moveTicketToProject(ticketId, targetProjectId);
|
|
248
250
|
this.log(styles.success(` Moved ${ticketId} to ${targetProjectId}`));
|
|
249
251
|
}
|
|
@@ -252,8 +252,10 @@ export default class TicketReassign extends PMOCommand {
|
|
|
252
252
|
// Reassign each ticket
|
|
253
253
|
let successCount = 0;
|
|
254
254
|
let failCount = 0;
|
|
255
|
+
// Process sequentially for clear success/failure logging
|
|
255
256
|
for (const ticketId of selectedTickets) {
|
|
256
257
|
try {
|
|
258
|
+
// eslint-disable-next-line no-await-in-loop
|
|
257
259
|
await this.storage.updateTicket(ticketId, { assignee: targetAssignee || undefined });
|
|
258
260
|
const action = targetAssignee ? `Reassigned to ${targetAssignee}` : 'Unassigned';
|
|
259
261
|
this.log(styles.success(`${ticketId}: ${action}`));
|
|
@@ -214,9 +214,10 @@ export default class TicketSpec extends PMOCommand {
|
|
|
214
214
|
this.log(styles.muted('No tickets selected.'));
|
|
215
215
|
return;
|
|
216
216
|
}
|
|
217
|
-
// Handle unlink
|
|
217
|
+
// Handle unlink - sequential for clear logging
|
|
218
218
|
if (flags.unlink) {
|
|
219
219
|
for (const ticketId of selectedTickets) {
|
|
220
|
+
// eslint-disable-next-line no-await-in-loop
|
|
220
221
|
await this.storage.updateTicket(ticketId, { specId: undefined });
|
|
221
222
|
this.log(styles.success(` Unlinked spec from ${ticketId}`));
|
|
222
223
|
}
|
|
@@ -248,8 +249,9 @@ export default class TicketSpec extends PMOCommand {
|
|
|
248
249
|
if (!spec) {
|
|
249
250
|
this.error(`Spec not found: ${specId}`);
|
|
250
251
|
}
|
|
251
|
-
// Assign spec to all selected tickets
|
|
252
|
+
// Assign spec to all selected tickets - sequential for clear logging
|
|
252
253
|
for (const ticketId of selectedTickets) {
|
|
254
|
+
// eslint-disable-next-line no-await-in-loop
|
|
253
255
|
await this.storage.updateTicket(ticketId, { specId });
|
|
254
256
|
this.log(styles.success(` Linked ${ticketId} to ${specId}`));
|
|
255
257
|
}
|
|
@@ -127,8 +127,8 @@ export default class TicketTemplateApply extends PMOCommand {
|
|
|
127
127
|
let category = flags.category || template.defaultCategory;
|
|
128
128
|
let assignee = flags.assignee || template.defaultAssignee;
|
|
129
129
|
let owner = flags.owner || template.defaultOwner;
|
|
130
|
-
|
|
131
|
-
|
|
130
|
+
const statusId = flags.status || template.defaultStatusId;
|
|
131
|
+
const labels = flags.labels ? flags.labels.split(',').map(l => l.trim()).filter(Boolean) : template.defaultLabels;
|
|
132
132
|
let description = flags.description || template.descriptionTemplate;
|
|
133
133
|
// Interactive mode - prompt for values
|
|
134
134
|
if (flags.interactive || !title) {
|
|
@@ -207,9 +207,10 @@ export default class TicketTemplateApply extends PMOCommand {
|
|
|
207
207
|
description,
|
|
208
208
|
epicId: flags.epic,
|
|
209
209
|
});
|
|
210
|
-
// Add subtasks from template (unless disabled)
|
|
210
|
+
// Add subtasks from template (unless disabled) - sequential for ordering
|
|
211
211
|
if (!flags['no-subtasks'] && template.suggestedSubtasks.length > 0) {
|
|
212
212
|
for (const subtask of template.suggestedSubtasks) {
|
|
213
|
+
// eslint-disable-next-line no-await-in-loop
|
|
213
214
|
await this.storage.addSubtask(ticket.id, subtask.title);
|
|
214
215
|
}
|
|
215
216
|
}
|
|
@@ -134,6 +134,7 @@ export default class TicketTemplateCreate extends PMOCommand {
|
|
|
134
134
|
if (wantSubtasks) {
|
|
135
135
|
let addMore = true;
|
|
136
136
|
while (addMore) {
|
|
137
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive loop for subtask creation
|
|
137
138
|
const { subtaskTitle } = await inquirer.prompt([{
|
|
138
139
|
type: 'input',
|
|
139
140
|
name: 'subtaskTitle',
|
|
@@ -141,6 +142,7 @@ export default class TicketTemplateCreate extends PMOCommand {
|
|
|
141
142
|
validate: (input) => input.length > 0 || 'Title is required',
|
|
142
143
|
}]);
|
|
143
144
|
subtasks.push(subtaskTitle);
|
|
145
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive loop continuation
|
|
144
146
|
const { another } = await inquirer.prompt([{
|
|
145
147
|
type: 'list',
|
|
146
148
|
name: 'another',
|
|
@@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import { PMOCommand, pmoBaseFlags } from '../../../lib/pmo/index.js';
|
|
4
4
|
import { styles } from '../../../lib/styles.js';
|
|
5
|
-
import { shouldOutputJson
|
|
5
|
+
import { shouldOutputJson } from '../../../lib/prompt-json.js';
|
|
6
6
|
export default class TicketTemplateIndex extends PMOCommand {
|
|
7
7
|
static description = 'Interactive menu for ticket template operations';
|
|
8
8
|
static aliases = ['ticket:templates'];
|
|
@@ -288,6 +288,7 @@ export default class TicketUpdate extends PMOCommand {
|
|
|
288
288
|
// Update each ticket
|
|
289
289
|
let successCount = 0;
|
|
290
290
|
let failCount = 0;
|
|
291
|
+
// Process sequentially for clear success/failure logging
|
|
291
292
|
for (const ticketId of selectedTickets) {
|
|
292
293
|
try {
|
|
293
294
|
const changes = {};
|
|
@@ -297,6 +298,7 @@ export default class TicketUpdate extends PMOCommand {
|
|
|
297
298
|
if (updateCategory !== undefined) {
|
|
298
299
|
changes.category = updateCategory || undefined;
|
|
299
300
|
}
|
|
301
|
+
// eslint-disable-next-line no-await-in-loop
|
|
300
302
|
await this.storage.updateTicket(ticketId, changes);
|
|
301
303
|
const updates = [];
|
|
302
304
|
if (updatePriority !== undefined)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
-
import { shouldOutputJson
|
|
3
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
4
|
export default class Work extends PMOCommand {
|
|
5
5
|
static description = 'Interactive menu for work operations (ownership, assignment, execution)';
|
|
6
6
|
static examples = [
|
|
@@ -9,7 +9,7 @@ import { getWorkColumnSetting, findColumnByName } from '../../lib/pmo/utils.js';
|
|
|
9
9
|
import { styles } from '../../lib/styles.js';
|
|
10
10
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
11
11
|
import { DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
|
|
12
|
-
import { runExecution, isDockerRunning } from '../../lib/execution/runners.js';
|
|
12
|
+
import { runExecution, isDockerRunning, isDevcontainerCliInstalled } from '../../lib/execution/runners.js';
|
|
13
13
|
import { ExecutionStorage } from '../../lib/execution/storage.js';
|
|
14
14
|
import { loadExecutionConfig, getTerminalApp, getShell, hasTerminalPreference, hasShellPreference } from '../../lib/execution/config.js';
|
|
15
15
|
import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
|
|
@@ -84,6 +84,12 @@ export default class WorkRevise extends PMOCommand {
|
|
|
84
84
|
'Please start Docker Desktop and try again.\n\n' +
|
|
85
85
|
'Alternatively, use --run-on-host to run directly on your machine (bypasses sandbox).');
|
|
86
86
|
}
|
|
87
|
+
// Early devcontainer CLI check
|
|
88
|
+
if (!flags['run-on-host'] && !isDevcontainerCliInstalled()) {
|
|
89
|
+
return handleError('DEVCONTAINER_CLI_NOT_INSTALLED', 'devcontainer CLI is not installed.\n\n' +
|
|
90
|
+
'Install with: npm install -g @devcontainers/cli\n\n' +
|
|
91
|
+
'Alternatively, use --run-on-host to run directly on your machine (bypasses sandbox).');
|
|
92
|
+
}
|
|
87
93
|
// Get workspace info
|
|
88
94
|
let workspaceInfo;
|
|
89
95
|
try {
|
|
@@ -10,7 +10,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
10
10
|
column: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
strategy: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
-
|
|
13
|
+
display: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
14
|
executor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
15
15
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
16
|
'run-on-host': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -23,6 +23,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
23
23
|
'no-pr': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
24
24
|
action: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
25
25
|
session: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
26
|
+
focus: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
26
27
|
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
28
|
};
|
|
28
29
|
execute(): Promise<void>;
|
|
@@ -5,8 +5,7 @@ import Database from 'better-sqlite3';
|
|
|
5
5
|
import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
|
|
6
6
|
import { styles } from '../../lib/styles.js';
|
|
7
7
|
import { getWorkspaceInfo, getTicketTmuxSession, killTmuxSession } from '../../lib/agents/commands.js';
|
|
8
|
-
import {
|
|
9
|
-
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
8
|
+
import { isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled } from '../../lib/execution/runners.js';
|
|
10
9
|
import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
11
10
|
export default class WorkSpawn extends PMOCommand {
|
|
12
11
|
static description = 'Spawn work for multiple tickets by column (batch mode)';
|
|
@@ -49,10 +48,10 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
49
48
|
description: 'Show what would be spawned without executing',
|
|
50
49
|
default: false,
|
|
51
50
|
}),
|
|
52
|
-
|
|
53
|
-
char: '
|
|
54
|
-
description: '
|
|
55
|
-
options: ['
|
|
51
|
+
display: Flags.string({
|
|
52
|
+
char: 'd',
|
|
53
|
+
description: 'Display mode for spawned agents (foreground not available for batch)',
|
|
54
|
+
options: ['terminal', 'background'],
|
|
56
55
|
}),
|
|
57
56
|
executor: Flags.string({
|
|
58
57
|
char: 'e',
|
|
@@ -106,6 +105,10 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
106
105
|
options: ['tmux', 'direct'],
|
|
107
106
|
default: 'tmux',
|
|
108
107
|
}),
|
|
108
|
+
focus: Flags.boolean({
|
|
109
|
+
description: 'Bring terminal to foreground when opening new tabs (default: opens in background)',
|
|
110
|
+
default: false,
|
|
111
|
+
}),
|
|
109
112
|
};
|
|
110
113
|
async execute() {
|
|
111
114
|
const { flags, argv } = await this.parse(WorkSpawn);
|
|
@@ -133,10 +136,9 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
133
136
|
catch {
|
|
134
137
|
return handleError('NOT_IN_WORKSPACE', 'Not in a workspace. Run "prlt init" first.');
|
|
135
138
|
}
|
|
136
|
-
// Open database
|
|
139
|
+
// Open database
|
|
137
140
|
const dbPath = path.join(workspaceInfo.path, '.proletariat', 'workspace.db');
|
|
138
141
|
const db = new Database(dbPath);
|
|
139
|
-
const executionStorage = new ExecutionStorage(db);
|
|
140
142
|
try {
|
|
141
143
|
// Get board to list available columns
|
|
142
144
|
const board = await this.storage.getBoard(projectId);
|
|
@@ -446,9 +448,10 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
446
448
|
return;
|
|
447
449
|
}
|
|
448
450
|
// Batch mode settings - prompt once for all tickets
|
|
449
|
-
let
|
|
451
|
+
let batchDisplay = flags.display;
|
|
450
452
|
let batchOutput = flags.output;
|
|
451
|
-
|
|
453
|
+
// Track permission mode - default to 'safe', check flag to determine if prompting needed
|
|
454
|
+
let batchPermissionMode = flags['skip-permissions'] ? 'danger' : 'safe';
|
|
452
455
|
let batchCreatePr = flags['create-pr'];
|
|
453
456
|
let batchNoPr = flags['no-pr'];
|
|
454
457
|
let batchRunOnHost = flags['run-on-host'];
|
|
@@ -473,6 +476,11 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
473
476
|
name: `${a.id.padEnd(12)} - ${a.description || a.name}`,
|
|
474
477
|
value: a.id,
|
|
475
478
|
}));
|
|
479
|
+
// Add adhoc option at the end
|
|
480
|
+
actionChoices.push({
|
|
481
|
+
name: 'adhoc - Unstructured exploration/debugging',
|
|
482
|
+
value: '__adhoc__',
|
|
483
|
+
});
|
|
476
484
|
const { selectedAction } = await inquirer.prompt([
|
|
477
485
|
{
|
|
478
486
|
type: 'list',
|
|
@@ -482,12 +490,27 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
482
490
|
default: 'implement',
|
|
483
491
|
},
|
|
484
492
|
]);
|
|
485
|
-
batchAction = selectedAction;
|
|
493
|
+
batchAction = selectedAction === '__adhoc__' ? 'adhoc' : selectedAction;
|
|
486
494
|
}
|
|
487
495
|
// Now fetch action details after selection is made
|
|
488
|
-
|
|
496
|
+
if (batchAction === 'adhoc') {
|
|
497
|
+
// Adhoc is a synthetic action, not stored in database
|
|
498
|
+
selectedActionDetails = {
|
|
499
|
+
id: 'adhoc',
|
|
500
|
+
name: 'Ad-hoc',
|
|
501
|
+
description: 'Unstructured exploration and debugging',
|
|
502
|
+
prompt: 'You are working on an ad-hoc session for exploration and debugging. Help the user with whatever they need.',
|
|
503
|
+
modifiesCode: false,
|
|
504
|
+
defaultMoveToCategory: 'started',
|
|
505
|
+
isBuiltin: false,
|
|
506
|
+
createdAt: new Date(),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
selectedActionDetails = await this.storage.getAction(batchAction || 'implement');
|
|
511
|
+
}
|
|
489
512
|
// Check if any explicit settings were provided via flags
|
|
490
|
-
const hasExplicitSettings = flags.
|
|
513
|
+
const hasExplicitSettings = flags.display || flags.output || flags['skip-permissions'] ||
|
|
491
514
|
flags['create-pr'] || flags['no-pr'] || flags['run-on-host'];
|
|
492
515
|
// Offer to use default settings if no explicit flags provided
|
|
493
516
|
if (!hasExplicitSettings) {
|
|
@@ -511,14 +534,14 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
511
534
|
if (useDefaults) {
|
|
512
535
|
// Apply defaults
|
|
513
536
|
if (hasDevcontainer) {
|
|
514
|
-
|
|
537
|
+
batchDisplay = 'devcontainer';
|
|
515
538
|
batchDisplayMode = 'terminal';
|
|
516
539
|
}
|
|
517
540
|
else {
|
|
518
|
-
|
|
541
|
+
batchDisplay = 'terminal';
|
|
519
542
|
}
|
|
520
543
|
batchOutput = 'interactive';
|
|
521
|
-
|
|
544
|
+
batchPermissionMode = 'safe';
|
|
522
545
|
// For non-code-modifying actions, don't create PRs
|
|
523
546
|
if (modifiesCode) {
|
|
524
547
|
batchCreatePr = true;
|
|
@@ -532,20 +555,35 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
532
555
|
}
|
|
533
556
|
}
|
|
534
557
|
// Prompt for environment (devcontainer vs host) if devcontainer available and not already set
|
|
535
|
-
if (hasDevcontainer && !batchRunOnHost && !
|
|
558
|
+
if (hasDevcontainer && !batchRunOnHost && !batchDisplay) {
|
|
559
|
+
// Check devcontainer prerequisites upfront
|
|
560
|
+
const dockerRunning = isDockerRunning();
|
|
561
|
+
const devcontainerCliInstalled = isDevcontainerCliInstalled();
|
|
562
|
+
const devcontainerReady = dockerRunning && devcontainerCliInstalled;
|
|
563
|
+
// Build missing requirements message for devcontainer option
|
|
564
|
+
let devcontainerLabel = '🐳 devcontainer (sandboxed, recommended)';
|
|
565
|
+
if (!devcontainerReady) {
|
|
566
|
+
const missing = [];
|
|
567
|
+
if (!dockerRunning)
|
|
568
|
+
missing.push('Docker');
|
|
569
|
+
if (!devcontainerCliInstalled)
|
|
570
|
+
missing.push('devcontainer CLI');
|
|
571
|
+
devcontainerLabel = `🐳 devcontainer (requires: ${missing.join(', ')})`;
|
|
572
|
+
}
|
|
536
573
|
let environmentSelected = false;
|
|
537
574
|
while (!environmentSelected) {
|
|
575
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive loop with retry on Docker check
|
|
538
576
|
const { selectedEnvironment } = await inquirer.prompt([
|
|
539
577
|
{
|
|
540
578
|
type: 'list',
|
|
541
579
|
name: 'selectedEnvironment',
|
|
542
580
|
message: 'Where should agents run?',
|
|
543
581
|
choices: [
|
|
544
|
-
{ name:
|
|
582
|
+
{ name: devcontainerLabel, value: 'devcontainer', disabled: !devcontainerReady },
|
|
545
583
|
{ name: '💻 host (runs directly on your machine)', value: 'host' },
|
|
546
584
|
{ name: '✗ cancel', value: 'cancel' },
|
|
547
585
|
],
|
|
548
|
-
default: 'devcontainer',
|
|
586
|
+
default: devcontainerReady ? 'devcontainer' : 'host',
|
|
549
587
|
},
|
|
550
588
|
]);
|
|
551
589
|
if (selectedEnvironment === 'cancel') {
|
|
@@ -554,6 +592,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
554
592
|
return;
|
|
555
593
|
}
|
|
556
594
|
if (selectedEnvironment === 'devcontainer') {
|
|
595
|
+
// Double-check prerequisites (in case user retried after starting Docker)
|
|
557
596
|
if (!isDockerRunning()) {
|
|
558
597
|
this.log('');
|
|
559
598
|
this.warn('Docker is not running.\n' +
|
|
@@ -562,10 +601,59 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
562
601
|
this.log('');
|
|
563
602
|
continue;
|
|
564
603
|
}
|
|
565
|
-
|
|
604
|
+
if (!isDevcontainerCliInstalled()) {
|
|
605
|
+
this.log('');
|
|
606
|
+
this.warn('devcontainer CLI is not installed.\n' +
|
|
607
|
+
'Install with: npm install -g @devcontainers/cli\n' +
|
|
608
|
+
'Or select "host" to run directly on your machine.');
|
|
609
|
+
this.log('');
|
|
610
|
+
continue;
|
|
611
|
+
}
|
|
612
|
+
// Check GitHub token is available for git push operations
|
|
613
|
+
if (!isGitHubTokenAvailable()) {
|
|
614
|
+
const tokenChoices = [
|
|
615
|
+
{ name: 'Yes, continue anyway (git push may fail)', value: 'continue' },
|
|
616
|
+
{ name: 'No, let me run gh auth login first', value: 'cancel' },
|
|
617
|
+
{ name: 'Switch to host mode instead', value: 'host' },
|
|
618
|
+
];
|
|
619
|
+
const tokenMessage = 'GitHub token not found. Git push may fail. Continue without token?';
|
|
620
|
+
if (jsonMode) {
|
|
621
|
+
outputPromptAsJson(buildPromptConfig('list', 'tokenAction', tokenMessage, tokenChoices), createMetadata('work spawn', flags));
|
|
622
|
+
db.close();
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
this.log('');
|
|
626
|
+
this.warn('GitHub token not found.\n' +
|
|
627
|
+
'Git push operations may fail inside containers.\n' +
|
|
628
|
+
'Run `gh auth login` to authenticate, or continue without token.');
|
|
629
|
+
this.log('');
|
|
630
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive user prompt in loop
|
|
631
|
+
const { tokenAction } = await inquirer.prompt([
|
|
632
|
+
{
|
|
633
|
+
type: 'list',
|
|
634
|
+
name: 'tokenAction',
|
|
635
|
+
message: tokenMessage,
|
|
636
|
+
choices: tokenChoices,
|
|
637
|
+
default: 'continue',
|
|
638
|
+
},
|
|
639
|
+
]);
|
|
640
|
+
if (tokenAction === 'cancel') {
|
|
641
|
+
db.close();
|
|
642
|
+
this.log(styles.muted('Run `gh auth login` and try again.'));
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
if (tokenAction === 'host') {
|
|
646
|
+
batchRunOnHost = true;
|
|
647
|
+
environmentSelected = true;
|
|
648
|
+
continue;
|
|
649
|
+
}
|
|
650
|
+
// tokenAction === 'continue' - fall through to devcontainer setup
|
|
651
|
+
}
|
|
652
|
+
batchDisplay = 'devcontainer';
|
|
566
653
|
environmentSelected = true;
|
|
567
654
|
// For devcontainer, prompt for display mode
|
|
568
655
|
// Simplified: tmux is always used inside container for session persistence
|
|
656
|
+
// eslint-disable-next-line no-await-in-loop -- Follow-up prompt after selection
|
|
569
657
|
const { selectedDisplay } = await inquirer.prompt([
|
|
570
658
|
{
|
|
571
659
|
type: 'list',
|
|
@@ -589,7 +677,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
589
677
|
}
|
|
590
678
|
}
|
|
591
679
|
// Prompt for display mode if not already set (for host mode without devcontainer)
|
|
592
|
-
if (!
|
|
680
|
+
if (!batchDisplay) {
|
|
593
681
|
const { selectedMode } = await inquirer.prompt([
|
|
594
682
|
{
|
|
595
683
|
type: 'list',
|
|
@@ -601,15 +689,15 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
601
689
|
],
|
|
602
690
|
},
|
|
603
691
|
]);
|
|
604
|
-
|
|
692
|
+
batchDisplay = selectedMode;
|
|
605
693
|
}
|
|
606
694
|
// Default to interactive output mode (streaming UI)
|
|
607
695
|
// Can be overridden via --output flag if needed
|
|
608
696
|
if (!batchOutput) {
|
|
609
697
|
batchOutput = 'interactive';
|
|
610
698
|
}
|
|
611
|
-
// Prompt for permissions mode if not
|
|
612
|
-
if (!
|
|
699
|
+
// Prompt for permissions mode if not explicitly set via --skip-permissions flag
|
|
700
|
+
if (!flags['skip-permissions']) {
|
|
613
701
|
const { permissionMode } = await inquirer.prompt([
|
|
614
702
|
{
|
|
615
703
|
type: 'list',
|
|
@@ -622,7 +710,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
622
710
|
default: 'danger',
|
|
623
711
|
},
|
|
624
712
|
]);
|
|
625
|
-
|
|
713
|
+
batchPermissionMode = permissionMode;
|
|
626
714
|
}
|
|
627
715
|
// Prompt for PR creation if not provided AND action modifies code
|
|
628
716
|
// Skip this prompt entirely for non-code-modifying actions (like groom)
|
|
@@ -661,6 +749,7 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
661
749
|
// Spawn each ticket - work:start will create ephemeral agents on-demand
|
|
662
750
|
let successCount = 0;
|
|
663
751
|
let failCount = 0;
|
|
752
|
+
// Process sequentially for clear logging and resource management
|
|
664
753
|
for (const ticket of ticketsToSpawn) {
|
|
665
754
|
try {
|
|
666
755
|
this.log(styles.muted(`Starting ${ticket.id} with ephemeral agent...`));
|
|
@@ -669,24 +758,26 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
669
758
|
// Pass --ephemeral to signal work:start should create an ephemeral agent
|
|
670
759
|
const startArgs = [ticket.id, '--project', projectId, '--ephemeral'];
|
|
671
760
|
if (flags['per-ticket']) {
|
|
672
|
-
// Per-ticket mode: only pass
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (
|
|
676
|
-
startArgs.push('--display',
|
|
761
|
+
// Per-ticket mode: only pass display flag, let start prompt for the rest
|
|
762
|
+
// batchDisplayMode is for devcontainer, batchDisplay is for host
|
|
763
|
+
const displayToUse = batchDisplayMode || batchDisplay;
|
|
764
|
+
if (displayToUse && displayToUse !== 'devcontainer')
|
|
765
|
+
startArgs.push('--display', displayToUse);
|
|
677
766
|
if (flags.executor)
|
|
678
767
|
startArgs.push('--executor', flags.executor);
|
|
679
768
|
if (batchRunOnHost)
|
|
680
769
|
startArgs.push('--run-on-host');
|
|
681
770
|
if (flags.force)
|
|
682
771
|
startArgs.push('--force');
|
|
772
|
+
if (flags.focus)
|
|
773
|
+
startArgs.push('--focus');
|
|
683
774
|
}
|
|
684
775
|
else {
|
|
685
776
|
// Batch mode: pass all settings to skip prompts
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
if (
|
|
689
|
-
startArgs.push('--display',
|
|
777
|
+
// batchDisplayMode is for devcontainer, batchDisplay is for host
|
|
778
|
+
const displayToUse = batchDisplayMode || batchDisplay;
|
|
779
|
+
if (displayToUse && displayToUse !== 'devcontainer')
|
|
780
|
+
startArgs.push('--display', displayToUse);
|
|
690
781
|
if (flags.executor)
|
|
691
782
|
startArgs.push('--executor', flags.executor);
|
|
692
783
|
if (batchRunOnHost)
|
|
@@ -695,8 +786,8 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
695
786
|
startArgs.push('--force');
|
|
696
787
|
if (batchOutput)
|
|
697
788
|
startArgs.push('--output', batchOutput);
|
|
698
|
-
|
|
699
|
-
|
|
789
|
+
// Always pass permission mode to skip the prompt in work:start
|
|
790
|
+
startArgs.push('--permission-mode', batchPermissionMode);
|
|
700
791
|
if (batchCreatePr)
|
|
701
792
|
startArgs.push('--create-pr');
|
|
702
793
|
if (batchNoPr)
|
|
@@ -706,7 +797,11 @@ export default class WorkSpawn extends PMOCommand {
|
|
|
706
797
|
// Pass session manager (tmux inside container by default)
|
|
707
798
|
if (flags.session)
|
|
708
799
|
startArgs.push('--session', flags.session);
|
|
800
|
+
// Pass focus flag (brings terminal to foreground)
|
|
801
|
+
if (flags.focus)
|
|
802
|
+
startArgs.push('--focus');
|
|
709
803
|
}
|
|
804
|
+
// eslint-disable-next-line no-await-in-loop
|
|
710
805
|
await this.config.runCommand('work:start', startArgs);
|
|
711
806
|
successCount++;
|
|
712
807
|
}
|