@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
|
@@ -100,6 +100,7 @@ export default class ExecutionStop extends PMOCommand {
|
|
|
100
100
|
let failed = 0;
|
|
101
101
|
for (const execution of activeExecutions) {
|
|
102
102
|
try {
|
|
103
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential stop with user feedback
|
|
103
104
|
const success = await this.stopExecution(execution, flags.force || false);
|
|
104
105
|
if (success) {
|
|
105
106
|
executionStorage.updateStatus(execution.id, 'stopped');
|
package/dist/commands/init.js
CHANGED
|
@@ -161,10 +161,10 @@ export default class Init extends Command {
|
|
|
161
161
|
storageType: 'sqlite',
|
|
162
162
|
},
|
|
163
163
|
};
|
|
164
|
+
// Suppress console output in JSON mode
|
|
165
|
+
const originalLog = console.log;
|
|
166
|
+
console.log = () => { };
|
|
164
167
|
try {
|
|
165
|
-
// Suppress console output in JSON mode
|
|
166
|
-
const originalLog = console.log;
|
|
167
|
-
console.log = () => { };
|
|
168
168
|
// Initialize the HQ
|
|
169
169
|
await initializeHQ(options);
|
|
170
170
|
// Restore console.log
|
|
@@ -183,7 +183,7 @@ export default class Init extends Command {
|
|
|
183
183
|
}
|
|
184
184
|
catch (error) {
|
|
185
185
|
// Restore console.log on error
|
|
186
|
-
console.log =
|
|
186
|
+
console.log = originalLog;
|
|
187
187
|
this.outputJson({
|
|
188
188
|
success: false,
|
|
189
189
|
error: error instanceof Error ? error.message : 'Unknown error',
|
|
@@ -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 Project extends PMOCommand {
|
|
5
5
|
static description = 'Interactive menu for project operations';
|
|
6
6
|
static examples = [
|
|
@@ -133,6 +133,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
133
133
|
let continueLoop = true;
|
|
134
134
|
while (continueLoop) {
|
|
135
135
|
// Refresh project specs each iteration
|
|
136
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive user loop
|
|
136
137
|
const currentSpecs = await this.storage.getSpecsForProject(projectId);
|
|
137
138
|
const currentSpecIds = new Set(currentSpecs.map(s => s.id));
|
|
138
139
|
this.log(styles.title(`\nSpecs for project "${project.name}" (${projectId})`));
|
|
@@ -144,6 +145,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
144
145
|
this.log(` ${styles.code(spec.id)} - ${spec.title} (${spec.status})`);
|
|
145
146
|
}
|
|
146
147
|
}
|
|
148
|
+
// eslint-disable-next-line no-await-in-loop -- Interactive user prompt
|
|
147
149
|
const { action } = await inquirer.prompt([{
|
|
148
150
|
type: 'list',
|
|
149
151
|
name: 'action',
|
|
@@ -161,12 +163,14 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
161
163
|
}
|
|
162
164
|
if (action === 'add') {
|
|
163
165
|
// Get all specs not already associated
|
|
166
|
+
// eslint-disable-next-line no-await-in-loop -- User action handling
|
|
164
167
|
const allSpecs = await this.storage.listSpecs();
|
|
165
168
|
const availableSpecs = allSpecs.filter(s => !currentSpecIds.has(s.id));
|
|
166
169
|
if (availableSpecs.length === 0) {
|
|
167
170
|
this.log(styles.muted('\n All specs are already associated with this project.'));
|
|
168
171
|
continue;
|
|
169
172
|
}
|
|
173
|
+
// eslint-disable-next-line no-await-in-loop -- User selection prompt
|
|
170
174
|
const { selected } = await inquirer.prompt([{
|
|
171
175
|
type: 'checkbox',
|
|
172
176
|
name: 'selected',
|
|
@@ -181,6 +185,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
181
185
|
continue;
|
|
182
186
|
}
|
|
183
187
|
for (const specId of selected) {
|
|
188
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential updates with feedback
|
|
184
189
|
await this.storage.linkProjectToSpec(projectId, specId);
|
|
185
190
|
this.log(styles.success(` Added ${specId}`));
|
|
186
191
|
}
|
|
@@ -191,6 +196,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
191
196
|
this.log(styles.muted('\n No specs to remove.'));
|
|
192
197
|
continue;
|
|
193
198
|
}
|
|
199
|
+
// eslint-disable-next-line no-await-in-loop -- User selection prompt
|
|
194
200
|
const { selected } = await inquirer.prompt([{
|
|
195
201
|
type: 'checkbox',
|
|
196
202
|
name: 'selected',
|
|
@@ -205,6 +211,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
205
211
|
continue;
|
|
206
212
|
}
|
|
207
213
|
for (const specId of selected) {
|
|
214
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential updates with feedback
|
|
208
215
|
await this.storage.unlinkProjectFromSpec(projectId, specId);
|
|
209
216
|
this.log(styles.success(` Removed ${specId}`));
|
|
210
217
|
}
|
|
@@ -96,6 +96,7 @@ export default class Add extends PMOCommand {
|
|
|
96
96
|
let successCount = 0;
|
|
97
97
|
let failCount = 0;
|
|
98
98
|
for (const repo of reposToAdd) {
|
|
99
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential add with user feedback
|
|
99
100
|
const result = await addRepository(hqPath, repo.path, repo.action);
|
|
100
101
|
if (result.success) {
|
|
101
102
|
this.log(format.success(`Repository ${result.name} added`));
|
|
@@ -195,6 +195,7 @@ export default class Remove extends PMOCommand {
|
|
|
195
195
|
let failCount = 0;
|
|
196
196
|
for (const repoName of selectedRepos) {
|
|
197
197
|
this.log(colors.textMuted(`Removing ${repoName}...`));
|
|
198
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential remove with user feedback
|
|
198
199
|
const result = await removeRepository(hqPath, repoName, keepFiles);
|
|
199
200
|
if (result.success) {
|
|
200
201
|
this.log(format.success(`Removed ${repoName}`));
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class RoadmapAddProject 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
|
+
position: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
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,135 @@
|
|
|
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 RoadmapAddProject extends PMOCommand {
|
|
7
|
+
static description = 'Add a project to 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 add',
|
|
20
|
+
required: false,
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
static flags = {
|
|
24
|
+
...pmoBaseFlags,
|
|
25
|
+
position: Flags.integer({
|
|
26
|
+
char: 'p',
|
|
27
|
+
description: 'Position in the roadmap (0-indexed)',
|
|
28
|
+
}),
|
|
29
|
+
json: Flags.boolean({
|
|
30
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
31
|
+
default: false,
|
|
32
|
+
}),
|
|
33
|
+
};
|
|
34
|
+
getPMOOptions() {
|
|
35
|
+
return { promptIfMultiple: false };
|
|
36
|
+
}
|
|
37
|
+
async execute() {
|
|
38
|
+
const { args, flags } = await this.parse(RoadmapAddProject);
|
|
39
|
+
const jsonMode = shouldOutputJson(flags);
|
|
40
|
+
// Select roadmap
|
|
41
|
+
let roadmapId = args.roadmap;
|
|
42
|
+
if (!roadmapId) {
|
|
43
|
+
const roadmaps = await this.storage.listRoadmaps();
|
|
44
|
+
if (roadmaps.length === 0) {
|
|
45
|
+
if (jsonMode) {
|
|
46
|
+
outputErrorAsJson('NO_ROADMAPS', 'No roadmaps found', createMetadata('roadmap add-project', flags));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.error('No roadmaps found. Create one with: prlt roadmap create');
|
|
50
|
+
}
|
|
51
|
+
if (jsonMode) {
|
|
52
|
+
const choices = roadmaps.map(r => ({
|
|
53
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
54
|
+
value: r.id,
|
|
55
|
+
}));
|
|
56
|
+
outputPromptAsJson(buildPromptConfig('list', 'roadmap', 'Select roadmap:', choices), createMetadata('roadmap add-project', flags));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const { selected } = await inquirer.prompt([{
|
|
60
|
+
type: 'list',
|
|
61
|
+
name: 'selected',
|
|
62
|
+
message: 'Select roadmap:',
|
|
63
|
+
choices: roadmaps.map(r => ({
|
|
64
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
65
|
+
value: r.id,
|
|
66
|
+
})),
|
|
67
|
+
}]);
|
|
68
|
+
roadmapId = selected;
|
|
69
|
+
}
|
|
70
|
+
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
71
|
+
if (!roadmap) {
|
|
72
|
+
if (jsonMode) {
|
|
73
|
+
outputErrorAsJson('NOT_FOUND', `Roadmap not found: ${roadmapId}`, createMetadata('roadmap add-project', flags));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
this.error(`Roadmap not found: ${roadmapId}`);
|
|
77
|
+
}
|
|
78
|
+
// Get projects already in roadmap
|
|
79
|
+
const existingProjects = await this.storage.listRoadmapProjects(roadmapId);
|
|
80
|
+
const existingProjectIds = new Set(existingProjects.map(p => p.id));
|
|
81
|
+
// Get all projects and filter out ones already in roadmap
|
|
82
|
+
const allProjects = await this.storage.listProjects({ isArchived: false });
|
|
83
|
+
const availableProjects = allProjects.filter(p => !existingProjectIds.has(p.id));
|
|
84
|
+
if (availableProjects.length === 0) {
|
|
85
|
+
if (jsonMode) {
|
|
86
|
+
outputErrorAsJson('NO_AVAILABLE_PROJECTS', 'All projects are already in this roadmap', createMetadata('roadmap add-project', flags));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
this.error('All projects are already in this roadmap');
|
|
90
|
+
}
|
|
91
|
+
// Select project
|
|
92
|
+
let projectId = args.project;
|
|
93
|
+
if (!projectId) {
|
|
94
|
+
if (jsonMode) {
|
|
95
|
+
const choices = availableProjects.map(p => ({
|
|
96
|
+
name: p.name,
|
|
97
|
+
value: p.id,
|
|
98
|
+
}));
|
|
99
|
+
outputPromptAsJson(buildPromptConfig('list', 'project', 'Select project to add:', choices), createMetadata('roadmap add-project', flags));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
const { selected } = await inquirer.prompt([{
|
|
103
|
+
type: 'list',
|
|
104
|
+
name: 'selected',
|
|
105
|
+
message: 'Select project to add:',
|
|
106
|
+
choices: availableProjects.map(p => ({
|
|
107
|
+
name: p.name,
|
|
108
|
+
value: p.id,
|
|
109
|
+
})),
|
|
110
|
+
}]);
|
|
111
|
+
projectId = selected;
|
|
112
|
+
}
|
|
113
|
+
// Verify project exists and isn't already in roadmap
|
|
114
|
+
const project = await this.storage.getProject(projectId);
|
|
115
|
+
if (!project) {
|
|
116
|
+
if (jsonMode) {
|
|
117
|
+
outputErrorAsJson('NOT_FOUND', `Project not found: ${projectId}`, createMetadata('roadmap add-project', flags));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
this.error(`Project not found: ${projectId}`);
|
|
121
|
+
}
|
|
122
|
+
if (existingProjectIds.has(projectId)) {
|
|
123
|
+
if (jsonMode) {
|
|
124
|
+
outputErrorAsJson('ALREADY_IN_ROADMAP', `Project "${project.name}" is already in this roadmap`, createMetadata('roadmap add-project', flags));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.error(`Project "${project.name}" is already in this roadmap`);
|
|
128
|
+
}
|
|
129
|
+
// Add project to roadmap
|
|
130
|
+
await this.storage.addProjectToRoadmap(roadmapId, projectId, flags.position);
|
|
131
|
+
const projects = await this.storage.listRoadmapProjects(roadmapId);
|
|
132
|
+
const position = projects.findIndex(p => p.id === projectId) + 1;
|
|
133
|
+
this.log(styles.success(`Added "${project.name}" to "${roadmap.name}" at position ${position}`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class RoadmapCreate extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
name: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
default: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
interactive: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
};
|
|
17
|
+
protected getPMOOptions(): {
|
|
18
|
+
promptIfMultiple: boolean;
|
|
19
|
+
};
|
|
20
|
+
execute(): Promise<void>;
|
|
21
|
+
private promptRoadmapData;
|
|
22
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { Flags, Args } 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 { slugify } from '../../lib/pmo/utils.js';
|
|
6
|
+
import { shouldOutputJson, outputPromptAsJson, createMetadata, buildFormPromptConfig, } from '../../lib/prompt-json.js';
|
|
7
|
+
export default class RoadmapCreate extends PMOCommand {
|
|
8
|
+
static description = 'Create a new roadmap';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> <%= command.id %> "Public Roadmap"',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --name "Internal Roadmap" --description "Full project list"',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> -i # Interactive mode',
|
|
13
|
+
];
|
|
14
|
+
static args = {
|
|
15
|
+
name: Args.string({
|
|
16
|
+
description: 'Roadmap name',
|
|
17
|
+
required: false,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
...pmoBaseFlags,
|
|
22
|
+
name: Flags.string({
|
|
23
|
+
char: 'n',
|
|
24
|
+
description: 'Roadmap name',
|
|
25
|
+
}),
|
|
26
|
+
id: Flags.string({
|
|
27
|
+
description: 'Custom roadmap ID (auto-generated from name if not provided)',
|
|
28
|
+
}),
|
|
29
|
+
description: Flags.string({
|
|
30
|
+
char: 'd',
|
|
31
|
+
description: 'Roadmap description',
|
|
32
|
+
}),
|
|
33
|
+
default: Flags.boolean({
|
|
34
|
+
description: 'Set as the default roadmap',
|
|
35
|
+
default: false,
|
|
36
|
+
}),
|
|
37
|
+
interactive: Flags.boolean({
|
|
38
|
+
char: 'i',
|
|
39
|
+
description: 'Interactive mode',
|
|
40
|
+
default: false,
|
|
41
|
+
}),
|
|
42
|
+
json: Flags.boolean({
|
|
43
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
44
|
+
default: false,
|
|
45
|
+
}),
|
|
46
|
+
};
|
|
47
|
+
getPMOOptions() {
|
|
48
|
+
return { promptIfMultiple: false };
|
|
49
|
+
}
|
|
50
|
+
async execute() {
|
|
51
|
+
const { args, flags } = await this.parse(RoadmapCreate);
|
|
52
|
+
const jsonMode = shouldOutputJson(flags);
|
|
53
|
+
let roadmapData;
|
|
54
|
+
if (flags.interactive || (!args.name && !flags.name)) {
|
|
55
|
+
const fields = [
|
|
56
|
+
{ type: 'input', name: 'name', message: 'Roadmap name:', default: flags.name },
|
|
57
|
+
{ type: 'input', name: 'id', message: 'Roadmap ID (leave blank to auto-generate):' },
|
|
58
|
+
{ type: 'input', name: 'description', message: 'Description (optional):', default: flags.description },
|
|
59
|
+
{
|
|
60
|
+
type: 'list',
|
|
61
|
+
name: 'isDefault',
|
|
62
|
+
message: 'Set as default roadmap?',
|
|
63
|
+
choices: [
|
|
64
|
+
{ name: 'No', value: 'false' },
|
|
65
|
+
{ name: 'Yes', value: 'true' },
|
|
66
|
+
],
|
|
67
|
+
default: flags.default ? 'true' : 'false',
|
|
68
|
+
},
|
|
69
|
+
];
|
|
70
|
+
if (jsonMode) {
|
|
71
|
+
outputPromptAsJson(buildFormPromptConfig(fields), createMetadata('roadmap create', flags));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
roadmapData = await this.promptRoadmapData(fields);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
roadmapData = {
|
|
78
|
+
name: args.name || flags.name,
|
|
79
|
+
id: flags.id,
|
|
80
|
+
description: flags.description,
|
|
81
|
+
isDefault: flags.default,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const roadmapId = roadmapData.id || `roadmap-${slugify(roadmapData.name)}`;
|
|
85
|
+
// Check if roadmap already exists
|
|
86
|
+
const existing = await this.storage.getRoadmap(roadmapId);
|
|
87
|
+
if (existing) {
|
|
88
|
+
this.error(`Roadmap "${roadmapId}" already exists.`);
|
|
89
|
+
}
|
|
90
|
+
// Create roadmap in database
|
|
91
|
+
const roadmap = await this.storage.createRoadmap({
|
|
92
|
+
id: roadmapId,
|
|
93
|
+
name: roadmapData.name,
|
|
94
|
+
description: roadmapData.description,
|
|
95
|
+
isDefault: roadmapData.isDefault,
|
|
96
|
+
});
|
|
97
|
+
this.log(styles.success(`\nCreated roadmap "${styles.emphasis(roadmap.name)}"`));
|
|
98
|
+
this.log(styles.muted(` ID: ${roadmap.id}`));
|
|
99
|
+
if (roadmap.description) {
|
|
100
|
+
this.log(styles.muted(` Description: ${roadmap.description}`));
|
|
101
|
+
}
|
|
102
|
+
if (roadmap.isDefault) {
|
|
103
|
+
this.log(styles.muted(` Default: Yes`));
|
|
104
|
+
}
|
|
105
|
+
// Prompt to add projects
|
|
106
|
+
const projects = await this.storage.listProjects({ isArchived: false });
|
|
107
|
+
if (projects.length > 0) {
|
|
108
|
+
this.log('');
|
|
109
|
+
const { addProjects } = await inquirer.prompt([{
|
|
110
|
+
type: 'list',
|
|
111
|
+
name: 'addProjects',
|
|
112
|
+
message: 'Add projects to this roadmap now?',
|
|
113
|
+
choices: [
|
|
114
|
+
{ name: 'Yes', value: true },
|
|
115
|
+
{ name: 'No, I\'ll add them later', value: false },
|
|
116
|
+
],
|
|
117
|
+
}]);
|
|
118
|
+
if (addProjects) {
|
|
119
|
+
const { selectedProjects } = await inquirer.prompt([{
|
|
120
|
+
type: 'checkbox',
|
|
121
|
+
name: 'selectedProjects',
|
|
122
|
+
message: 'Select projects to include:',
|
|
123
|
+
choices: projects.map(p => ({ name: p.name, value: p.id })),
|
|
124
|
+
}]);
|
|
125
|
+
for (const projectId of selectedProjects) {
|
|
126
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential updates
|
|
127
|
+
await this.storage.addProjectToRoadmap(roadmap.id, projectId);
|
|
128
|
+
}
|
|
129
|
+
if (selectedProjects.length > 0) {
|
|
130
|
+
this.log(styles.success(`Added ${selectedProjects.length} project(s) to roadmap`));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
this.log(styles.muted(`\nNext steps:`));
|
|
135
|
+
this.log(styles.muted(` prlt roadmap view ${roadmap.id}`));
|
|
136
|
+
this.log(styles.muted(` prlt roadmap add-project ${roadmap.id}`));
|
|
137
|
+
this.log(styles.muted(` prlt roadmap generate ${roadmap.id}`));
|
|
138
|
+
}
|
|
139
|
+
async promptRoadmapData(fields) {
|
|
140
|
+
const answers = await inquirer.prompt(fields.map(field => ({
|
|
141
|
+
...field,
|
|
142
|
+
validate: field.name === 'name'
|
|
143
|
+
? ((input) => input.length > 0 || 'Name is required')
|
|
144
|
+
: undefined,
|
|
145
|
+
default: field.name === 'id'
|
|
146
|
+
? ((answers) => `roadmap-${slugify(answers.name)}`)
|
|
147
|
+
: field.default,
|
|
148
|
+
})));
|
|
149
|
+
return {
|
|
150
|
+
name: answers.name,
|
|
151
|
+
id: answers.id || undefined,
|
|
152
|
+
description: answers.description || undefined,
|
|
153
|
+
isDefault: answers.isDefault === true || answers.isDefault === 'true',
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class RoadmapDelete extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
protected getPMOOptions(): {
|
|
14
|
+
promptIfMultiple: boolean;
|
|
15
|
+
};
|
|
16
|
+
execute(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
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 RoadmapDelete extends PMOCommand {
|
|
7
|
+
static description = 'Delete a roadmap';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> my-roadmap',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> my-roadmap --force',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> # Interactive selection',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
id: Args.string({
|
|
15
|
+
description: 'Roadmap ID to delete',
|
|
16
|
+
required: false,
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
static flags = {
|
|
20
|
+
...pmoBaseFlags,
|
|
21
|
+
force: Flags.boolean({
|
|
22
|
+
char: 'f',
|
|
23
|
+
description: 'Skip confirmation',
|
|
24
|
+
default: false,
|
|
25
|
+
}),
|
|
26
|
+
json: Flags.boolean({
|
|
27
|
+
description: 'Output prompt configuration as JSON (for AI agents/scripts)',
|
|
28
|
+
default: false,
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
getPMOOptions() {
|
|
32
|
+
return { promptIfMultiple: false };
|
|
33
|
+
}
|
|
34
|
+
async execute() {
|
|
35
|
+
const { args, flags } = await this.parse(RoadmapDelete);
|
|
36
|
+
const jsonMode = shouldOutputJson(flags);
|
|
37
|
+
let roadmapId = args.id;
|
|
38
|
+
if (!roadmapId) {
|
|
39
|
+
const roadmaps = await this.storage.listRoadmaps();
|
|
40
|
+
if (roadmaps.length === 0) {
|
|
41
|
+
if (jsonMode) {
|
|
42
|
+
outputErrorAsJson('NO_ROADMAPS', 'No roadmaps found', createMetadata('roadmap delete', flags));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
this.error('No roadmaps found.');
|
|
46
|
+
}
|
|
47
|
+
if (jsonMode) {
|
|
48
|
+
const choices = roadmaps.map(r => ({
|
|
49
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
50
|
+
value: r.id,
|
|
51
|
+
}));
|
|
52
|
+
outputPromptAsJson(buildPromptConfig('list', 'id', 'Select roadmap to delete:', choices), createMetadata('roadmap delete', flags));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const { selected } = await inquirer.prompt([{
|
|
56
|
+
type: 'list',
|
|
57
|
+
name: 'selected',
|
|
58
|
+
message: 'Select roadmap to delete:',
|
|
59
|
+
choices: roadmaps.map(r => ({
|
|
60
|
+
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
61
|
+
value: r.id,
|
|
62
|
+
})),
|
|
63
|
+
}]);
|
|
64
|
+
roadmapId = selected;
|
|
65
|
+
}
|
|
66
|
+
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
67
|
+
if (!roadmap) {
|
|
68
|
+
if (jsonMode) {
|
|
69
|
+
outputErrorAsJson('NOT_FOUND', `Roadmap not found: ${roadmapId}`, createMetadata('roadmap delete', flags));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
this.error(`Roadmap not found: ${roadmapId}`);
|
|
73
|
+
}
|
|
74
|
+
// Get project count for context
|
|
75
|
+
const projects = await this.storage.listRoadmapProjects(roadmapId);
|
|
76
|
+
// Confirm deletion
|
|
77
|
+
if (!flags.force) {
|
|
78
|
+
const message = `Delete roadmap "${roadmap.name}"${projects.length > 0 ? ` (contains ${projects.length} project reference${projects.length > 1 ? 's' : ''})` : ''}?`;
|
|
79
|
+
if (jsonMode) {
|
|
80
|
+
const confirmChoices = [
|
|
81
|
+
{ name: 'No, cancel', value: 'false' },
|
|
82
|
+
{ name: 'Yes, delete', value: 'true' },
|
|
83
|
+
];
|
|
84
|
+
outputPromptAsJson(buildPromptConfig('list', 'confirmed', message, confirmChoices), createMetadata('roadmap delete', flags));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const { confirm } = await inquirer.prompt([{
|
|
88
|
+
type: 'list',
|
|
89
|
+
name: 'confirm',
|
|
90
|
+
message,
|
|
91
|
+
choices: [
|
|
92
|
+
{ name: 'No, cancel', value: false },
|
|
93
|
+
{ name: 'Yes, delete', value: true },
|
|
94
|
+
],
|
|
95
|
+
}]);
|
|
96
|
+
if (!confirm) {
|
|
97
|
+
this.log(styles.muted('Cancelled.'));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
await this.storage.deleteRoadmap(roadmapId);
|
|
102
|
+
this.log(styles.success(`Deleted roadmap "${roadmap.name}"`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class RoadmapGenerate extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
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
|
+
private generateRoadmap;
|
|
19
|
+
private getPriorityLabel;
|
|
20
|
+
private cleanForTable;
|
|
21
|
+
private truncateDescription;
|
|
22
|
+
}
|