@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.
Files changed (152) hide show
  1. package/README.md +25 -0
  2. package/bin/dev.js +0 -0
  3. package/dist/commands/action/index.js +1 -1
  4. package/dist/commands/action/run.js +8 -12
  5. package/dist/commands/agent/auth.d.ts +30 -0
  6. package/dist/commands/agent/auth.js +172 -0
  7. package/dist/commands/agent/discover.d.ts +9 -0
  8. package/dist/commands/agent/discover.js +67 -0
  9. package/dist/commands/agent/index.js +47 -12
  10. package/dist/commands/agent/list.d.ts +4 -1
  11. package/dist/commands/agent/list.js +78 -16
  12. package/dist/commands/agent/login.js +35 -31
  13. package/dist/commands/agent/restart.js +2 -0
  14. package/dist/commands/agent/shell.js +78 -19
  15. package/dist/commands/agent/staff/add.js +1 -12
  16. package/dist/commands/agent/staff/remove.js +9 -7
  17. package/dist/commands/agent/status.js +17 -4
  18. package/dist/commands/agent/temp/cleanup.js +7 -3
  19. package/dist/commands/agent/themes/index.js +4 -5
  20. package/dist/commands/agent/themes/list.js +5 -5
  21. package/dist/commands/agent/visit.js +17 -4
  22. package/dist/commands/branch/create.d.ts +4 -0
  23. package/dist/commands/branch/create.js +16 -8
  24. package/dist/commands/branch/index.js +1 -1
  25. package/dist/commands/branch/where.js +1 -0
  26. package/dist/commands/claude.d.ts +38 -0
  27. package/dist/commands/claude.js +899 -0
  28. package/dist/commands/commit.js +1 -1
  29. package/dist/commands/config/index.d.ts +12 -0
  30. package/dist/commands/config/index.js +271 -0
  31. package/dist/commands/docker/clean.js +2 -2
  32. package/dist/commands/docker/index.js +2 -2
  33. package/dist/commands/docker/list.js +3 -8
  34. package/dist/commands/docker/logs.js +2 -2
  35. package/dist/commands/docker/prune.js +1 -1
  36. package/dist/commands/docker/restart.js +2 -2
  37. package/dist/commands/docker/shell.js +2 -2
  38. package/dist/commands/docker/start.js +2 -2
  39. package/dist/commands/docker/status.js +1 -1
  40. package/dist/commands/docker/stop.js +2 -2
  41. package/dist/commands/docker/sync.js +2 -2
  42. package/dist/commands/epic/index.js +1 -1
  43. package/dist/commands/epic/link/index.js +25 -14
  44. package/dist/commands/epic/link/remove.js +2 -0
  45. package/dist/commands/epic/list.js +5 -5
  46. package/dist/commands/epic/progress.js +10 -4
  47. package/dist/commands/epic/spec.js +2 -0
  48. package/dist/commands/epic/ticket.js +3 -0
  49. package/dist/commands/execution/stop.js +1 -0
  50. package/dist/commands/init.js +4 -4
  51. package/dist/commands/project/index.js +1 -1
  52. package/dist/commands/project/spec.js +7 -0
  53. package/dist/commands/repo/add.js +1 -0
  54. package/dist/commands/repo/remove.js +1 -0
  55. package/dist/commands/roadmap/add-project.d.ts +18 -0
  56. package/dist/commands/roadmap/add-project.js +135 -0
  57. package/dist/commands/roadmap/create.d.ts +22 -0
  58. package/dist/commands/roadmap/create.js +156 -0
  59. package/dist/commands/roadmap/delete.d.ts +17 -0
  60. package/dist/commands/roadmap/delete.js +104 -0
  61. package/dist/commands/roadmap/generate.d.ts +22 -0
  62. package/dist/commands/roadmap/generate.js +201 -0
  63. package/dist/commands/roadmap/index.d.ts +13 -0
  64. package/dist/commands/roadmap/index.js +61 -0
  65. package/dist/commands/roadmap/list.d.ts +12 -0
  66. package/dist/commands/roadmap/list.js +42 -0
  67. package/dist/commands/roadmap/remove-project.d.ts +18 -0
  68. package/dist/commands/roadmap/remove-project.js +147 -0
  69. package/dist/commands/roadmap/reorder.d.ts +17 -0
  70. package/dist/commands/roadmap/reorder.js +157 -0
  71. package/dist/commands/roadmap/update.d.ts +19 -0
  72. package/dist/commands/roadmap/update.js +136 -0
  73. package/dist/commands/roadmap/view.d.ts +16 -0
  74. package/dist/commands/roadmap/view.js +103 -0
  75. package/dist/commands/spec/index.js +1 -1
  76. package/dist/commands/spec/link/index.js +24 -13
  77. package/dist/commands/spec/link/remove.js +2 -0
  78. package/dist/commands/status/index.js +1 -1
  79. package/dist/commands/status/list.js +0 -8
  80. package/dist/commands/template/delete.js +2 -0
  81. package/dist/commands/terminal/title.d.ts +12 -0
  82. package/dist/commands/terminal/title.js +48 -0
  83. package/dist/commands/ticket/complete.js +2 -0
  84. package/dist/commands/ticket/create.js +4 -2
  85. package/dist/commands/ticket/delete.js +2 -0
  86. package/dist/commands/ticket/edit.js +8 -2
  87. package/dist/commands/ticket/link/index.js +17 -3
  88. package/dist/commands/ticket/link/remove.js +2 -0
  89. package/dist/commands/ticket/list.js +1 -2
  90. package/dist/commands/ticket/move.js +2 -0
  91. package/dist/commands/ticket/project.js +3 -1
  92. package/dist/commands/ticket/reassign.js +2 -0
  93. package/dist/commands/ticket/spec.js +4 -2
  94. package/dist/commands/ticket/template/apply.js +4 -3
  95. package/dist/commands/ticket/template/create.js +2 -0
  96. package/dist/commands/ticket/template/index.js +1 -1
  97. package/dist/commands/ticket/update.js +2 -0
  98. package/dist/commands/work/index.js +1 -1
  99. package/dist/commands/work/revise.js +7 -1
  100. package/dist/commands/work/spawn.d.ts +2 -1
  101. package/dist/commands/work/spawn.js +131 -36
  102. package/dist/commands/work/start.d.ts +2 -1
  103. package/dist/commands/work/start.js +349 -69
  104. package/dist/commands/work/watch.js +10 -2
  105. package/dist/commands/workflow/create.js +3 -3
  106. package/dist/commands/workflow/switch.js +2 -1
  107. package/dist/commands/workspace/remove.js +0 -8
  108. package/dist/commands/workspace/use.js +1 -9
  109. package/dist/lib/agents/commands.js +18 -13
  110. package/dist/lib/database/index.d.ts +19 -12
  111. package/dist/lib/database/index.js +158 -42
  112. package/dist/lib/docker/resolve.js +1 -1
  113. package/dist/lib/execution/config.d.ts +6 -0
  114. package/dist/lib/execution/config.js +15 -2
  115. package/dist/lib/execution/devcontainer.d.ts +2 -0
  116. package/dist/lib/execution/devcontainer.js +41 -9
  117. package/dist/lib/execution/runners.d.ts +85 -3
  118. package/dist/lib/execution/runners.js +925 -228
  119. package/dist/lib/execution/spawner.d.ts +2 -2
  120. package/dist/lib/execution/spawner.js +4 -3
  121. package/dist/lib/execution/storage.d.ts +2 -1
  122. package/dist/lib/execution/storage.js +9 -13
  123. package/dist/lib/execution/types.d.ts +10 -1
  124. package/dist/lib/execution/types.js +3 -1
  125. package/dist/lib/init/index.js +1 -0
  126. package/dist/lib/machine-config.js +1 -1
  127. package/dist/lib/pmo/base-command.js +5 -9
  128. package/dist/lib/pmo/index.js +2 -0
  129. package/dist/lib/pmo/schema.d.ts +6 -0
  130. package/dist/lib/pmo/schema.js +36 -0
  131. package/dist/lib/pmo/storage/base.js +3 -3
  132. package/dist/lib/pmo/storage/index.d.ts +16 -1
  133. package/dist/lib/pmo/storage/index.js +45 -0
  134. package/dist/lib/pmo/storage/roadmaps.d.ts +62 -0
  135. package/dist/lib/pmo/storage/roadmaps.js +301 -0
  136. package/dist/lib/pmo/storage/specs.js +2 -0
  137. package/dist/lib/pmo/storage/types.d.ts +14 -0
  138. package/dist/lib/pmo/sync-manager.d.ts +1 -1
  139. package/dist/lib/pmo/sync-manager.js +1 -1
  140. package/dist/lib/pmo/types.d.ts +41 -0
  141. package/dist/lib/pmo/utils.d.ts +2 -0
  142. package/dist/lib/pmo/utils.js +22 -1
  143. package/dist/lib/repos/index.js +7 -1
  144. package/dist/lib/terminal.d.ts +31 -0
  145. package/dist/lib/terminal.js +48 -0
  146. package/dist/lib/themes.d.ts +21 -3
  147. package/dist/lib/themes.js +80 -23
  148. package/dist/lib/workspace-config.d.ts +80 -0
  149. package/dist/lib/workspace-config.js +100 -0
  150. package/oclif.manifest.json +4065 -3225
  151. package/package.json +10 -6
  152. 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
+ }