@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
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
6
|
+
import { styles } from '../../lib/styles.js';
|
|
7
|
+
import { slugify } from '../../lib/pmo/utils.js';
|
|
8
|
+
import { normalizePriority, PRIORITIES } from '../../lib/pmo/types.js';
|
|
9
|
+
import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
10
|
+
export default class RoadmapGenerate extends PMOCommand {
|
|
11
|
+
static description = 'Generate roadmap markdown file';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> <%= command.id %> my-roadmap',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> --all',
|
|
15
|
+
'<%= config.bin %> <%= command.id %> my-roadmap --output ./docs',
|
|
16
|
+
];
|
|
17
|
+
static args = {
|
|
18
|
+
id: Args.string({
|
|
19
|
+
description: 'Roadmap ID to generate',
|
|
20
|
+
required: false,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
static flags = {
|
|
24
|
+
...pmoBaseFlags,
|
|
25
|
+
all: Flags.boolean({
|
|
26
|
+
description: 'Generate all roadmaps',
|
|
27
|
+
default: false,
|
|
28
|
+
}),
|
|
29
|
+
output: Flags.string({
|
|
30
|
+
char: 'o',
|
|
31
|
+
description: 'Output directory (default: {pmoPath}/roadmaps)',
|
|
32
|
+
}),
|
|
33
|
+
json: Flags.boolean({
|
|
34
|
+
description: 'Output prompt configuration as JSON',
|
|
35
|
+
default: false,
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
38
|
+
getPMOOptions() {
|
|
39
|
+
return { promptIfMultiple: false };
|
|
40
|
+
}
|
|
41
|
+
async execute() {
|
|
42
|
+
const { args, flags } = await this.parse(RoadmapGenerate);
|
|
43
|
+
const jsonMode = shouldOutputJson(flags);
|
|
44
|
+
// Determine output directory
|
|
45
|
+
const outputDir = flags.output || path.join(this.pmoPath, 'roadmaps');
|
|
46
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
47
|
+
if (flags.all) {
|
|
48
|
+
// Generate all roadmaps
|
|
49
|
+
const roadmaps = await this.storage.listRoadmaps();
|
|
50
|
+
if (roadmaps.length === 0) {
|
|
51
|
+
this.log(styles.muted('No roadmaps found. Create one with: prlt roadmap create'));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const roadmap of roadmaps) {
|
|
55
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential generation with user feedback
|
|
56
|
+
await this.generateRoadmap(roadmap.id, outputDir);
|
|
57
|
+
}
|
|
58
|
+
this.log(styles.success(`\nGenerated ${roadmaps.length} roadmap(s) to ${outputDir}`));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
// Select specific roadmap
|
|
62
|
+
let roadmapId = args.id;
|
|
63
|
+
if (!roadmapId) {
|
|
64
|
+
const roadmaps = await this.storage.listRoadmaps();
|
|
65
|
+
if (roadmaps.length === 0) {
|
|
66
|
+
if (jsonMode) {
|
|
67
|
+
outputErrorAsJson('NO_ROADMAPS', 'No roadmaps found', createMetadata('roadmap generate', flags));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
this.error('No roadmaps found. Create one with: prlt roadmap create');
|
|
71
|
+
}
|
|
72
|
+
if (jsonMode) {
|
|
73
|
+
const choices = roadmaps.map(r => ({
|
|
74
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
75
|
+
value: r.id,
|
|
76
|
+
}));
|
|
77
|
+
outputPromptAsJson(buildPromptConfig('list', 'id', 'Select roadmap to generate:', choices), createMetadata('roadmap generate', flags));
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const { selected } = await inquirer.prompt([{
|
|
81
|
+
type: 'list',
|
|
82
|
+
name: 'selected',
|
|
83
|
+
message: 'Select roadmap to generate:',
|
|
84
|
+
choices: roadmaps.map(r => ({
|
|
85
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
86
|
+
value: r.id,
|
|
87
|
+
})),
|
|
88
|
+
}]);
|
|
89
|
+
roadmapId = selected;
|
|
90
|
+
}
|
|
91
|
+
await this.generateRoadmap(roadmapId, outputDir);
|
|
92
|
+
}
|
|
93
|
+
async generateRoadmap(roadmapId, outputDir) {
|
|
94
|
+
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
95
|
+
if (!roadmap) {
|
|
96
|
+
this.error(`Roadmap not found: ${roadmapId}`);
|
|
97
|
+
}
|
|
98
|
+
const projects = await this.storage.listRoadmapProjects(roadmapId);
|
|
99
|
+
const lines = [];
|
|
100
|
+
// Header
|
|
101
|
+
lines.push(`# ${roadmap.name}`);
|
|
102
|
+
lines.push('');
|
|
103
|
+
if (roadmap.description) {
|
|
104
|
+
lines.push(roadmap.description);
|
|
105
|
+
lines.push('');
|
|
106
|
+
}
|
|
107
|
+
lines.push('Generated from PMO database. Source of truth: `workspace.db`');
|
|
108
|
+
lines.push('');
|
|
109
|
+
lines.push('---');
|
|
110
|
+
lines.push('');
|
|
111
|
+
// Process each project
|
|
112
|
+
for (let i = 0; i < projects.length; i++) {
|
|
113
|
+
const project = projects[i];
|
|
114
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential processing for output generation
|
|
115
|
+
const tickets = await this.storage.listTickets(project.id);
|
|
116
|
+
if (tickets.length === 0)
|
|
117
|
+
continue;
|
|
118
|
+
// Strip leading number prefix from project name (e.g., "1. MVP Launch" -> "MVP Launch")
|
|
119
|
+
let displayName = project.name;
|
|
120
|
+
if (displayName && /^\d+\.\s/.test(displayName)) {
|
|
121
|
+
displayName = displayName.replace(/^\d+\.\s/, '');
|
|
122
|
+
}
|
|
123
|
+
lines.push(`## ${i + 1}. ${displayName}`);
|
|
124
|
+
lines.push('');
|
|
125
|
+
// Group tickets by priority
|
|
126
|
+
const ticketsByPriority = new Map();
|
|
127
|
+
for (const priority of PRIORITIES) {
|
|
128
|
+
ticketsByPriority.set(priority, []);
|
|
129
|
+
}
|
|
130
|
+
ticketsByPriority.set('unset', []);
|
|
131
|
+
for (const ticket of tickets) {
|
|
132
|
+
const normalizedPriority = normalizePriority(ticket.priority) || 'unset';
|
|
133
|
+
const group = ticketsByPriority.get(normalizedPriority) || ticketsByPriority.get('unset');
|
|
134
|
+
group.push(ticket);
|
|
135
|
+
}
|
|
136
|
+
// Sort each priority group by status category, then ticket ID
|
|
137
|
+
const categoryOrder = {
|
|
138
|
+
'started': 1,
|
|
139
|
+
'unstarted': 2,
|
|
140
|
+
'backlog': 3,
|
|
141
|
+
'completed': 4,
|
|
142
|
+
'canceled': 5,
|
|
143
|
+
'triage': 6,
|
|
144
|
+
};
|
|
145
|
+
for (const [priority, priorityTickets] of ticketsByPriority) {
|
|
146
|
+
if (priorityTickets.length === 0)
|
|
147
|
+
continue;
|
|
148
|
+
priorityTickets.sort((a, b) => {
|
|
149
|
+
const catA = categoryOrder[a.statusCategory || 'backlog'] || 6;
|
|
150
|
+
const catB = categoryOrder[b.statusCategory || 'backlog'] || 6;
|
|
151
|
+
if (catA !== catB)
|
|
152
|
+
return catA - catB;
|
|
153
|
+
return a.id.localeCompare(b.id);
|
|
154
|
+
});
|
|
155
|
+
// Priority header
|
|
156
|
+
const priorityLabel = priority === 'unset' ? 'Unset Priority' : `${priority} - ${this.getPriorityLabel(priority)}`;
|
|
157
|
+
lines.push(`### ${priorityLabel} (${priorityTickets.length} ticket${priorityTickets.length > 1 ? 's' : ''})`);
|
|
158
|
+
lines.push('');
|
|
159
|
+
// Table header
|
|
160
|
+
lines.push('| Ticket | Title | Status | Category | Description |');
|
|
161
|
+
lines.push('| ------ | ----- | ------ | -------- | ----------- |');
|
|
162
|
+
for (const ticket of priorityTickets) {
|
|
163
|
+
const title = this.cleanForTable(ticket.title);
|
|
164
|
+
const status = ticket.statusCategory || 'backlog';
|
|
165
|
+
const category = ticket.category || '';
|
|
166
|
+
const desc = this.truncateDescription(ticket.description || '', 150);
|
|
167
|
+
lines.push(`| ${ticket.id} | ${title} | ${status} | ${category} | ${desc} |`);
|
|
168
|
+
}
|
|
169
|
+
lines.push('');
|
|
170
|
+
}
|
|
171
|
+
lines.push('---');
|
|
172
|
+
lines.push('');
|
|
173
|
+
}
|
|
174
|
+
// Footer
|
|
175
|
+
lines.push(`*Last updated: ${new Date().toISOString().split('T')[0]}*`);
|
|
176
|
+
lines.push('');
|
|
177
|
+
const fileName = `${slugify(roadmap.name)}.md`;
|
|
178
|
+
const filePath = path.join(outputDir, fileName);
|
|
179
|
+
fs.writeFileSync(filePath, lines.join('\n'));
|
|
180
|
+
this.log(styles.success(`Generated ${filePath}`));
|
|
181
|
+
}
|
|
182
|
+
getPriorityLabel(priority) {
|
|
183
|
+
const labels = {
|
|
184
|
+
'P0': 'Critical',
|
|
185
|
+
'P1': 'High',
|
|
186
|
+
'P2': 'Medium',
|
|
187
|
+
'P3': 'Low',
|
|
188
|
+
};
|
|
189
|
+
return labels[priority] || priority;
|
|
190
|
+
}
|
|
191
|
+
cleanForTable(text) {
|
|
192
|
+
return (text || '').replace(/\|/g, '-').replace(/\n/g, ' ').replace(/\r/g, '').trim();
|
|
193
|
+
}
|
|
194
|
+
truncateDescription(desc, maxLength) {
|
|
195
|
+
const cleaned = this.cleanForTable(desc);
|
|
196
|
+
if (cleaned.length > maxLength) {
|
|
197
|
+
return cleaned.substring(0, maxLength - 3) + '...';
|
|
198
|
+
}
|
|
199
|
+
return cleaned;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class Roadmap extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
protected getPMOOptions(): {
|
|
10
|
+
promptIfMultiple: boolean;
|
|
11
|
+
};
|
|
12
|
+
execute(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
|
+
export default class Roadmap extends PMOCommand {
|
|
5
|
+
static description = 'Interactive menu for roadmap operations';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %>',
|
|
8
|
+
];
|
|
9
|
+
static flags = {
|
|
10
|
+
...pmoBaseFlags,
|
|
11
|
+
json: Flags.boolean({
|
|
12
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
13
|
+
default: false,
|
|
14
|
+
}),
|
|
15
|
+
};
|
|
16
|
+
getPMOOptions() {
|
|
17
|
+
return { promptIfMultiple: false };
|
|
18
|
+
}
|
|
19
|
+
async execute() {
|
|
20
|
+
const { flags } = await this.parse(Roadmap);
|
|
21
|
+
const jsonMode = shouldOutputJson(flags);
|
|
22
|
+
const menuChoices = [
|
|
23
|
+
{ id: 'create', name: 'Create new roadmap', command: 'prlt roadmap create --json' },
|
|
24
|
+
{ id: 'list', name: 'List all roadmaps', command: 'prlt roadmap list --json' },
|
|
25
|
+
{ id: 'view', name: 'View roadmap details', command: 'prlt roadmap view --json' },
|
|
26
|
+
{ id: 'generate', name: 'Generate roadmap markdown', command: 'prlt roadmap generate --json' },
|
|
27
|
+
{ id: 'update', name: 'Update roadmap', command: 'prlt roadmap update --json' },
|
|
28
|
+
{ id: 'delete', name: 'Delete roadmap', command: 'prlt roadmap delete --json' },
|
|
29
|
+
{ id: 'add-project', name: 'Add project to roadmap', command: 'prlt roadmap add-project --json' },
|
|
30
|
+
{ id: 'remove-project', name: 'Remove project from roadmap', command: 'prlt roadmap remove-project --json' },
|
|
31
|
+
{ id: 'reorder', name: 'Reorder projects in roadmap', command: 'prlt roadmap reorder --json' },
|
|
32
|
+
{ id: 'cancel', name: 'Cancel', command: '' },
|
|
33
|
+
];
|
|
34
|
+
const message = 'Roadmap Operations - What would you like to do?';
|
|
35
|
+
const action = await this.selectFromList({
|
|
36
|
+
message,
|
|
37
|
+
items: menuChoices,
|
|
38
|
+
getName: (c) => c.name,
|
|
39
|
+
getValue: (c) => c.id,
|
|
40
|
+
getCommand: (c) => c.command,
|
|
41
|
+
jsonMode: jsonMode ? { flags, commandName: 'roadmap' } : null,
|
|
42
|
+
});
|
|
43
|
+
if (action === 'cancel' || !action) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const commandMap = {
|
|
47
|
+
'create': 'roadmap:create',
|
|
48
|
+
'list': 'roadmap:list',
|
|
49
|
+
'view': 'roadmap:view',
|
|
50
|
+
'generate': 'roadmap:generate',
|
|
51
|
+
'update': 'roadmap:update',
|
|
52
|
+
'delete': 'roadmap:delete',
|
|
53
|
+
'add-project': 'roadmap:add-project',
|
|
54
|
+
'remove-project': 'roadmap:remove-project',
|
|
55
|
+
'reorder': 'roadmap:reorder',
|
|
56
|
+
};
|
|
57
|
+
if (commandMap[action]) {
|
|
58
|
+
await this.config.runCommand(commandMap[action], []);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class RoadmapList extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
protected getPMOOptions(): {
|
|
9
|
+
promptIfMultiple: boolean;
|
|
10
|
+
};
|
|
11
|
+
execute(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
2
|
+
import { styles } from '../../lib/styles.js';
|
|
3
|
+
export default class RoadmapList extends PMOCommand {
|
|
4
|
+
static description = 'List all roadmaps';
|
|
5
|
+
static examples = [
|
|
6
|
+
'<%= config.bin %> <%= command.id %>',
|
|
7
|
+
];
|
|
8
|
+
static flags = {
|
|
9
|
+
...pmoBaseFlags,
|
|
10
|
+
};
|
|
11
|
+
getPMOOptions() {
|
|
12
|
+
return { promptIfMultiple: false };
|
|
13
|
+
}
|
|
14
|
+
async execute() {
|
|
15
|
+
const roadmaps = await this.storage.listRoadmaps();
|
|
16
|
+
if (roadmaps.length === 0) {
|
|
17
|
+
this.log(styles.muted('No roadmaps found. Create one with: prlt roadmap create'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
this.log(styles.title('\nRoadmaps\n'));
|
|
21
|
+
// Fetch all project counts in parallel
|
|
22
|
+
const projectCounts = await Promise.all(roadmaps.map(async (roadmap) => {
|
|
23
|
+
const projects = await this.storage.listRoadmapProjects(roadmap.id);
|
|
24
|
+
return projects.length;
|
|
25
|
+
}));
|
|
26
|
+
for (let i = 0; i < roadmaps.length; i++) {
|
|
27
|
+
const roadmap = roadmaps[i];
|
|
28
|
+
const markers = [];
|
|
29
|
+
if (roadmap.isDefault)
|
|
30
|
+
markers.push(styles.success('default'));
|
|
31
|
+
const markerStr = markers.length > 0 ? ` (${markers.join(', ')})` : '';
|
|
32
|
+
this.log(` ${styles.emphasis(roadmap.name)}${markerStr}`);
|
|
33
|
+
this.log(styles.muted(` ID: ${roadmap.id}`));
|
|
34
|
+
// Get project count (already fetched in parallel)
|
|
35
|
+
this.log(styles.muted(` Projects: ${projectCounts[i]}`));
|
|
36
|
+
if (roadmap.description) {
|
|
37
|
+
this.log(styles.muted(` ${roadmap.description}`));
|
|
38
|
+
}
|
|
39
|
+
this.log('');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class RoadmapRemoveProject extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
roadmap: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
project: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
protected getPMOOptions(): {
|
|
15
|
+
promptIfMultiple: boolean;
|
|
16
|
+
};
|
|
17
|
+
execute(): Promise<void>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
|
+
import { styles } from '../../lib/styles.js';
|
|
5
|
+
import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
6
|
+
export default class RoadmapRemoveProject extends PMOCommand {
|
|
7
|
+
static description = 'Remove a project from a roadmap';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> my-roadmap my-project',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> my-roadmap # Interactive project selection',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> # Interactive selection for both',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
roadmap: Args.string({
|
|
15
|
+
description: 'Roadmap ID',
|
|
16
|
+
required: false,
|
|
17
|
+
}),
|
|
18
|
+
project: Args.string({
|
|
19
|
+
description: 'Project ID to remove',
|
|
20
|
+
required: false,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
static flags = {
|
|
24
|
+
...pmoBaseFlags,
|
|
25
|
+
force: Flags.boolean({
|
|
26
|
+
char: 'f',
|
|
27
|
+
description: 'Skip confirmation',
|
|
28
|
+
default: false,
|
|
29
|
+
}),
|
|
30
|
+
json: Flags.boolean({
|
|
31
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
32
|
+
default: false,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
getPMOOptions() {
|
|
36
|
+
return { promptIfMultiple: false };
|
|
37
|
+
}
|
|
38
|
+
async execute() {
|
|
39
|
+
const { args, flags } = await this.parse(RoadmapRemoveProject);
|
|
40
|
+
const jsonMode = shouldOutputJson(flags);
|
|
41
|
+
// Select roadmap
|
|
42
|
+
let roadmapId = args.roadmap;
|
|
43
|
+
if (!roadmapId) {
|
|
44
|
+
const roadmaps = await this.storage.listRoadmaps();
|
|
45
|
+
if (roadmaps.length === 0) {
|
|
46
|
+
if (jsonMode) {
|
|
47
|
+
outputErrorAsJson('NO_ROADMAPS', 'No roadmaps found', createMetadata('roadmap remove-project', flags));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
this.error('No roadmaps found');
|
|
51
|
+
}
|
|
52
|
+
if (jsonMode) {
|
|
53
|
+
const choices = roadmaps.map(r => ({
|
|
54
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
55
|
+
value: r.id,
|
|
56
|
+
}));
|
|
57
|
+
outputPromptAsJson(buildPromptConfig('list', 'roadmap', 'Select roadmap:', choices), createMetadata('roadmap remove-project', flags));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const { selected } = await inquirer.prompt([{
|
|
61
|
+
type: 'list',
|
|
62
|
+
name: 'selected',
|
|
63
|
+
message: 'Select roadmap:',
|
|
64
|
+
choices: roadmaps.map(r => ({
|
|
65
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
66
|
+
value: r.id,
|
|
67
|
+
})),
|
|
68
|
+
}]);
|
|
69
|
+
roadmapId = selected;
|
|
70
|
+
}
|
|
71
|
+
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
72
|
+
if (!roadmap) {
|
|
73
|
+
if (jsonMode) {
|
|
74
|
+
outputErrorAsJson('NOT_FOUND', `Roadmap not found: ${roadmapId}`, createMetadata('roadmap remove-project', flags));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.error(`Roadmap not found: ${roadmapId}`);
|
|
78
|
+
}
|
|
79
|
+
// Get projects in roadmap
|
|
80
|
+
const projects = await this.storage.listRoadmapProjects(roadmapId);
|
|
81
|
+
if (projects.length === 0) {
|
|
82
|
+
if (jsonMode) {
|
|
83
|
+
outputErrorAsJson('NO_PROJECTS', 'No projects in this roadmap', createMetadata('roadmap remove-project', flags));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
this.error('No projects in this roadmap');
|
|
87
|
+
}
|
|
88
|
+
// Select project
|
|
89
|
+
let projectId = args.project;
|
|
90
|
+
if (!projectId) {
|
|
91
|
+
if (jsonMode) {
|
|
92
|
+
const choices = projects.map((p, i) => ({
|
|
93
|
+
name: `${i + 1}. ${p.name}`,
|
|
94
|
+
value: p.id,
|
|
95
|
+
}));
|
|
96
|
+
outputPromptAsJson(buildPromptConfig('list', 'project', 'Select project to remove:', choices), createMetadata('roadmap remove-project', flags));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
const { selected } = await inquirer.prompt([{
|
|
100
|
+
type: 'list',
|
|
101
|
+
name: 'selected',
|
|
102
|
+
message: 'Select project to remove:',
|
|
103
|
+
choices: projects.map((p, i) => ({
|
|
104
|
+
name: `${i + 1}. ${p.name}`,
|
|
105
|
+
value: p.id,
|
|
106
|
+
})),
|
|
107
|
+
}]);
|
|
108
|
+
projectId = selected;
|
|
109
|
+
}
|
|
110
|
+
// Verify project is in roadmap
|
|
111
|
+
const project = projects.find(p => p.id === projectId);
|
|
112
|
+
if (!project) {
|
|
113
|
+
if (jsonMode) {
|
|
114
|
+
outputErrorAsJson('NOT_IN_ROADMAP', `Project "${projectId}" is not in this roadmap`, createMetadata('roadmap remove-project', flags));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
this.error(`Project "${projectId}" is not in this roadmap`);
|
|
118
|
+
}
|
|
119
|
+
// Confirm removal
|
|
120
|
+
if (!flags.force) {
|
|
121
|
+
const message = `Remove "${project.name}" from "${roadmap.name}"?`;
|
|
122
|
+
if (jsonMode) {
|
|
123
|
+
const confirmChoices = [
|
|
124
|
+
{ name: 'No, cancel', value: 'false' },
|
|
125
|
+
{ name: 'Yes, remove', value: 'true' },
|
|
126
|
+
];
|
|
127
|
+
outputPromptAsJson(buildPromptConfig('list', 'confirmed', message, confirmChoices), createMetadata('roadmap remove-project', flags));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const { confirm } = await inquirer.prompt([{
|
|
131
|
+
type: 'list',
|
|
132
|
+
name: 'confirm',
|
|
133
|
+
message,
|
|
134
|
+
choices: [
|
|
135
|
+
{ name: 'No, cancel', value: false },
|
|
136
|
+
{ name: 'Yes, remove', value: true },
|
|
137
|
+
],
|
|
138
|
+
}]);
|
|
139
|
+
if (!confirm) {
|
|
140
|
+
this.log(styles.muted('Cancelled.'));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
await this.storage.removeProjectFromRoadmap(roadmapId, projectId);
|
|
145
|
+
this.log(styles.success(`Removed "${project.name}" from "${roadmap.name}"`));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class RoadmapReorder extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
roadmap: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
position: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
protected getPMOOptions(): {
|
|
14
|
+
promptIfMultiple: boolean;
|
|
15
|
+
};
|
|
16
|
+
execute(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
|
+
import { styles } from '../../lib/styles.js';
|
|
5
|
+
import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
6
|
+
export default class RoadmapReorder extends PMOCommand {
|
|
7
|
+
static description = 'Reorder projects in a roadmap';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> my-roadmap',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> my-roadmap --project my-project --position 0',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> # Interactive selection',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
roadmap: Args.string({
|
|
15
|
+
description: 'Roadmap ID',
|
|
16
|
+
required: false,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
static flags = {
|
|
20
|
+
...pmoBaseFlags,
|
|
21
|
+
project: Flags.string({
|
|
22
|
+
char: 'p',
|
|
23
|
+
description: 'Project ID to move',
|
|
24
|
+
}),
|
|
25
|
+
position: Flags.integer({
|
|
26
|
+
description: 'New position (0-indexed)',
|
|
27
|
+
}),
|
|
28
|
+
json: Flags.boolean({
|
|
29
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
30
|
+
default: false,
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
getPMOOptions() {
|
|
34
|
+
return { promptIfMultiple: false };
|
|
35
|
+
}
|
|
36
|
+
async execute() {
|
|
37
|
+
const { args, flags } = await this.parse(RoadmapReorder);
|
|
38
|
+
const jsonMode = shouldOutputJson(flags);
|
|
39
|
+
// Select roadmap
|
|
40
|
+
let roadmapId = args.roadmap;
|
|
41
|
+
if (!roadmapId) {
|
|
42
|
+
const roadmaps = await this.storage.listRoadmaps();
|
|
43
|
+
if (roadmaps.length === 0) {
|
|
44
|
+
if (jsonMode) {
|
|
45
|
+
outputErrorAsJson('NO_ROADMAPS', 'No roadmaps found', createMetadata('roadmap reorder', flags));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
this.error('No roadmaps found');
|
|
49
|
+
}
|
|
50
|
+
if (jsonMode) {
|
|
51
|
+
const choices = roadmaps.map(r => ({
|
|
52
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
53
|
+
value: r.id,
|
|
54
|
+
}));
|
|
55
|
+
outputPromptAsJson(buildPromptConfig('list', 'roadmap', 'Select roadmap:', choices), createMetadata('roadmap reorder', flags));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const { selected } = await inquirer.prompt([{
|
|
59
|
+
type: 'list',
|
|
60
|
+
name: 'selected',
|
|
61
|
+
message: 'Select roadmap:',
|
|
62
|
+
choices: roadmaps.map(r => ({
|
|
63
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
64
|
+
value: r.id,
|
|
65
|
+
})),
|
|
66
|
+
}]);
|
|
67
|
+
roadmapId = selected;
|
|
68
|
+
}
|
|
69
|
+
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
70
|
+
if (!roadmap) {
|
|
71
|
+
if (jsonMode) {
|
|
72
|
+
outputErrorAsJson('NOT_FOUND', `Roadmap not found: ${roadmapId}`, createMetadata('roadmap reorder', flags));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.error(`Roadmap not found: ${roadmapId}`);
|
|
76
|
+
}
|
|
77
|
+
// Get projects in roadmap
|
|
78
|
+
let projects = await this.storage.listRoadmapProjects(roadmapId);
|
|
79
|
+
if (projects.length < 2) {
|
|
80
|
+
if (jsonMode) {
|
|
81
|
+
outputErrorAsJson('NOT_ENOUGH_PROJECTS', 'Need at least 2 projects to reorder', createMetadata('roadmap reorder', flags));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.error('Need at least 2 projects to reorder');
|
|
85
|
+
}
|
|
86
|
+
// If flags provided, use them
|
|
87
|
+
if (flags.project && flags.position !== undefined) {
|
|
88
|
+
const project = projects.find(p => p.id === flags.project);
|
|
89
|
+
if (!project) {
|
|
90
|
+
if (jsonMode) {
|
|
91
|
+
outputErrorAsJson('NOT_IN_ROADMAP', `Project "${flags.project}" is not in this roadmap`, createMetadata('roadmap reorder', flags));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
this.error(`Project "${flags.project}" is not in this roadmap`);
|
|
95
|
+
}
|
|
96
|
+
await this.storage.reorderRoadmapProject(roadmapId, flags.project, flags.position);
|
|
97
|
+
this.log(styles.success(`Moved "${project.name}" to position ${flags.position + 1}`));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// Interactive mode
|
|
101
|
+
this.log(styles.title(`\nReorder projects in "${roadmap.name}"\n`));
|
|
102
|
+
this.log('Current order:');
|
|
103
|
+
for (let i = 0; i < projects.length; i++) {
|
|
104
|
+
this.log(` ${i + 1}. ${projects[i].name}`);
|
|
105
|
+
}
|
|
106
|
+
this.log('');
|
|
107
|
+
// Prompt to select project to move
|
|
108
|
+
if (jsonMode) {
|
|
109
|
+
const choices = projects.map((p, i) => ({
|
|
110
|
+
name: `${i + 1}. ${p.name}`,
|
|
111
|
+
value: p.id,
|
|
112
|
+
}));
|
|
113
|
+
outputPromptAsJson(buildPromptConfig('list', 'project', 'Select project to move:', choices), createMetadata('roadmap reorder', flags));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const { projectToMove } = await inquirer.prompt([{
|
|
117
|
+
type: 'list',
|
|
118
|
+
name: 'projectToMove',
|
|
119
|
+
message: 'Select project to move:',
|
|
120
|
+
choices: projects.map((p, i) => ({
|
|
121
|
+
name: `${i + 1}. ${p.name}`,
|
|
122
|
+
value: p.id,
|
|
123
|
+
})),
|
|
124
|
+
}]);
|
|
125
|
+
const project = projects.find(p => p.id === projectToMove);
|
|
126
|
+
const currentPosition = projects.findIndex(p => p.id === projectToMove);
|
|
127
|
+
// Generate position choices
|
|
128
|
+
const positionChoices = projects.map((p, i) => {
|
|
129
|
+
if (i === currentPosition) {
|
|
130
|
+
return { name: `${i + 1}. ${p.name} (current position)`, value: i, disabled: true };
|
|
131
|
+
}
|
|
132
|
+
const label = i < currentPosition ? `Move before ${i + 1}. ${p.name}` : `Move after ${i}. ${projects[i - 1]?.name || ''}`;
|
|
133
|
+
return { name: `Position ${i + 1}: ${label}`, value: i };
|
|
134
|
+
}).filter(c => !c.disabled);
|
|
135
|
+
// Add "move to end" option if not already at the end
|
|
136
|
+
if (currentPosition !== projects.length - 1) {
|
|
137
|
+
positionChoices.push({
|
|
138
|
+
name: `Position ${projects.length}: Move to end`,
|
|
139
|
+
value: projects.length - 1,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
const { newPosition } = await inquirer.prompt([{
|
|
143
|
+
type: 'list',
|
|
144
|
+
name: 'newPosition',
|
|
145
|
+
message: `Move "${project.name}" to which position?`,
|
|
146
|
+
choices: positionChoices,
|
|
147
|
+
}]);
|
|
148
|
+
await this.storage.reorderRoadmapProject(roadmapId, projectToMove, newPosition);
|
|
149
|
+
// Show new order
|
|
150
|
+
projects = await this.storage.listRoadmapProjects(roadmapId);
|
|
151
|
+
this.log(styles.success(`\nReordered. New order:`));
|
|
152
|
+
for (let i = 0; i < projects.length; i++) {
|
|
153
|
+
const marker = projects[i].id === projectToMove ? styles.success(' ←') : '';
|
|
154
|
+
this.log(` ${i + 1}. ${projects[i].name}${marker}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|