@proletariat/cli 0.3.23 → 0.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/action/create.js +4 -4
- package/dist/commands/action/update.js +3 -3
- package/dist/commands/agent/{temp/cleanup.d.ts → cleanup.d.ts} +1 -1
- package/dist/commands/agent/{temp/cleanup.js → cleanup.js} +4 -4
- package/dist/commands/agent/index.js +8 -8
- package/dist/commands/branch/create.js +2 -2
- package/dist/commands/epic/activate.js +9 -17
- package/dist/commands/epic/archive.js +13 -24
- package/dist/commands/epic/create.d.ts +1 -0
- package/dist/commands/epic/create.js +46 -8
- package/dist/commands/epic/index.js +2 -2
- package/dist/commands/epic/move.js +28 -47
- package/dist/commands/epic/progress.js +10 -14
- package/dist/commands/epic/project.js +42 -59
- package/dist/commands/epic/reorder.js +25 -30
- package/dist/commands/epic/spec.d.ts +1 -0
- package/dist/commands/epic/spec.js +39 -40
- package/dist/commands/epic/ticket.d.ts +2 -0
- package/dist/commands/epic/ticket.js +63 -37
- package/dist/commands/feedback/index.d.ts +10 -0
- package/dist/commands/feedback/index.js +60 -0
- package/dist/commands/feedback/list.d.ts +12 -0
- package/dist/commands/feedback/list.js +126 -0
- package/dist/commands/feedback/submit.d.ts +16 -0
- package/dist/commands/feedback/submit.js +220 -0
- package/dist/commands/{template/phase/delete.d.ts → feedback/view.d.ts} +7 -5
- package/dist/commands/feedback/view.js +109 -0
- package/dist/commands/gh/index.js +4 -0
- package/dist/commands/{epic/link/remove.d.ts → link/create.d.ts} +6 -7
- package/dist/commands/link/create.js +141 -0
- package/dist/commands/{epic/link/relates.d.ts → link/index.d.ts} +4 -5
- package/dist/commands/link/index.js +87 -0
- package/dist/commands/{epic/link/duplicates.d.ts → link/list.d.ts} +7 -4
- package/dist/commands/link/list.js +182 -0
- package/dist/commands/{spec/link → link}/remove.d.ts +4 -5
- package/dist/commands/link/remove.js +120 -0
- package/dist/commands/mcp-server.d.ts +22 -0
- package/dist/commands/mcp-server.js +98 -0
- package/dist/commands/phase/create.js +1 -1
- package/dist/commands/project/create.d.ts +1 -0
- package/dist/commands/project/create.js +38 -4
- package/dist/commands/repo/create.d.ts +38 -0
- package/dist/commands/repo/create.js +283 -0
- package/dist/commands/repo/index.js +7 -0
- package/dist/commands/roadmap/add-project.js +9 -22
- package/dist/commands/roadmap/create.d.ts +0 -1
- package/dist/commands/roadmap/create.js +46 -40
- package/dist/commands/roadmap/delete.js +10 -24
- package/dist/commands/roadmap/generate.d.ts +1 -0
- package/dist/commands/roadmap/generate.js +21 -22
- package/dist/commands/roadmap/remove-project.js +14 -34
- package/dist/commands/roadmap/reorder.js +19 -26
- package/dist/commands/roadmap/update.js +27 -26
- package/dist/commands/roadmap/view.js +5 -12
- package/dist/commands/session/attach.d.ts +1 -8
- package/dist/commands/session/attach.js +93 -59
- package/dist/commands/session/list.d.ts +0 -8
- package/dist/commands/session/list.js +130 -81
- package/dist/commands/spec/create.d.ts +1 -0
- package/dist/commands/spec/create.js +44 -3
- package/dist/commands/spec/edit.js +63 -33
- package/dist/commands/spec/index.js +2 -2
- package/dist/commands/{agent/staff → staff}/add.js +10 -10
- package/dist/commands/{agent/staff → staff}/index.d.ts +1 -1
- package/dist/commands/{agent/staff → staff}/index.js +7 -7
- package/dist/commands/{agent/staff → staff}/list.js +3 -3
- package/dist/commands/{agent/staff → staff}/remove.d.ts +1 -1
- package/dist/commands/{agent/staff → staff}/remove.js +8 -8
- package/dist/commands/{template/phase/index.d.ts → support/book.d.ts} +2 -2
- package/dist/commands/support/book.js +54 -0
- package/dist/commands/{template/ticket/index.d.ts → support/discord.d.ts} +2 -2
- package/dist/commands/support/discord.js +54 -0
- package/dist/commands/support/docs.d.ts +10 -0
- package/dist/commands/support/docs.js +54 -0
- package/dist/commands/support/index.d.ts +19 -0
- package/dist/commands/support/index.js +81 -0
- package/dist/commands/support/issues.d.ts +11 -0
- package/dist/commands/support/issues.js +77 -0
- package/dist/commands/support/logs.d.ts +18 -0
- package/dist/commands/support/logs.js +247 -0
- package/dist/commands/{ticket/template → template}/apply.d.ts +8 -6
- package/dist/commands/template/apply.js +262 -0
- package/dist/commands/{ticket/template → template}/create.d.ts +5 -6
- package/dist/commands/template/create.js +238 -0
- package/dist/commands/template/index.js +48 -36
- package/dist/commands/{ticket/template → template}/save.d.ts +2 -2
- package/dist/commands/template/save.js +104 -0
- package/dist/commands/{phase/template → template}/update.d.ts +2 -2
- package/dist/commands/template/update.js +99 -0
- package/dist/commands/{agent/themes → theme}/add-names.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/add-names.js +6 -6
- package/dist/commands/{agent/themes → theme}/create.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/create.js +5 -5
- package/dist/commands/{agent/themes → theme}/index.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/index.js +10 -10
- package/dist/commands/{agent/themes → theme}/list.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/list.js +5 -5
- package/dist/commands/{agent/themes → theme}/set.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/set.js +7 -7
- package/dist/commands/ticket/create.d.ts +1 -0
- package/dist/commands/ticket/create.js +75 -15
- package/dist/commands/ticket/edit.js +44 -13
- package/dist/commands/ticket/index.js +6 -6
- package/dist/commands/ticket/move.d.ts +7 -0
- package/dist/commands/ticket/move.js +132 -0
- package/dist/commands/work/spawn.d.ts +1 -0
- package/dist/commands/work/spawn.js +72 -8
- package/dist/commands/work/start.js +6 -0
- package/dist/lib/execution/runners.js +21 -17
- package/dist/lib/execution/session-utils.d.ts +60 -0
- package/dist/lib/execution/session-utils.js +162 -0
- package/dist/lib/execution/spawner.d.ts +2 -0
- package/dist/lib/execution/spawner.js +42 -0
- package/dist/lib/flags/resolver.d.ts +2 -2
- package/dist/lib/flags/resolver.js +15 -0
- package/dist/lib/init/index.js +18 -0
- package/dist/lib/mcp/helpers.d.ts +43 -0
- package/dist/lib/mcp/helpers.js +57 -0
- package/dist/lib/mcp/index.d.ts +6 -0
- package/dist/lib/mcp/index.js +6 -0
- package/dist/lib/mcp/tools/action.d.ts +6 -0
- package/dist/lib/mcp/tools/action.js +88 -0
- package/dist/lib/mcp/tools/board.d.ts +6 -0
- package/dist/lib/mcp/tools/board.js +139 -0
- package/dist/lib/mcp/tools/category.d.ts +6 -0
- package/dist/lib/mcp/tools/category.js +84 -0
- package/dist/lib/mcp/tools/cli-passthrough.d.ts +15 -0
- package/dist/lib/mcp/tools/cli-passthrough.js +333 -0
- package/dist/lib/mcp/tools/epic.d.ts +6 -0
- package/dist/lib/mcp/tools/epic.js +178 -0
- package/dist/lib/mcp/tools/index.d.ts +18 -0
- package/dist/lib/mcp/tools/index.js +19 -0
- package/dist/lib/mcp/tools/phase.d.ts +6 -0
- package/dist/lib/mcp/tools/phase.js +131 -0
- package/dist/lib/mcp/tools/project.d.ts +6 -0
- package/dist/lib/mcp/tools/project.js +196 -0
- package/dist/lib/mcp/tools/roadmap.d.ts +6 -0
- package/dist/lib/mcp/tools/roadmap.js +123 -0
- package/dist/lib/mcp/tools/spec.d.ts +6 -0
- package/dist/lib/mcp/tools/spec.js +196 -0
- package/dist/lib/mcp/tools/status.d.ts +6 -0
- package/dist/lib/mcp/tools/status.js +109 -0
- package/dist/lib/mcp/tools/template.d.ts +6 -0
- package/dist/lib/mcp/tools/template.js +107 -0
- package/dist/lib/mcp/tools/ticket.d.ts +6 -0
- package/dist/lib/mcp/tools/ticket.js +393 -0
- package/dist/lib/mcp/tools/view.d.ts +6 -0
- package/dist/lib/mcp/tools/view.js +76 -0
- package/dist/lib/mcp/tools/work.d.ts +6 -0
- package/dist/lib/mcp/tools/work.js +132 -0
- package/dist/lib/mcp/tools/workflow.d.ts +6 -0
- package/dist/lib/mcp/tools/workflow.js +95 -0
- package/dist/lib/mcp/types.d.ts +17 -0
- package/dist/lib/mcp/types.js +4 -0
- package/dist/lib/multiline-input.d.ts +63 -0
- package/dist/lib/multiline-input.js +360 -0
- package/dist/lib/prompt-json.d.ts +57 -6
- package/dist/lib/prompt-json.js +45 -0
- package/dist/lib/repos/git.d.ts +7 -0
- package/dist/lib/repos/git.js +20 -0
- package/oclif.manifest.json +3690 -4995
- package/package.json +6 -4
- package/dist/commands/agent/temp/index.d.ts +0 -14
- package/dist/commands/agent/temp/index.js +0 -85
- package/dist/commands/agent/temp/list.d.ts +0 -7
- package/dist/commands/agent/temp/list.js +0 -108
- package/dist/commands/epic/link/block.d.ts +0 -14
- package/dist/commands/epic/link/block.js +0 -81
- package/dist/commands/epic/link/duplicates.js +0 -68
- package/dist/commands/epic/link/index.d.ts +0 -19
- package/dist/commands/epic/link/index.js +0 -272
- package/dist/commands/epic/link/relates.js +0 -68
- package/dist/commands/epic/link/remove.js +0 -93
- package/dist/commands/phase/template/apply.d.ts +0 -17
- package/dist/commands/phase/template/apply.js +0 -108
- package/dist/commands/phase/template/create.d.ts +0 -17
- package/dist/commands/phase/template/create.js +0 -104
- package/dist/commands/phase/template/delete.d.ts +0 -17
- package/dist/commands/phase/template/delete.js +0 -100
- package/dist/commands/phase/template/index.d.ts +0 -15
- package/dist/commands/phase/template/index.js +0 -130
- package/dist/commands/phase/template/list.d.ts +0 -16
- package/dist/commands/phase/template/list.js +0 -97
- package/dist/commands/phase/template/update.js +0 -89
- package/dist/commands/spec/link/depends.d.ts +0 -14
- package/dist/commands/spec/link/depends.js +0 -64
- package/dist/commands/spec/link/duplicates.d.ts +0 -14
- package/dist/commands/spec/link/duplicates.js +0 -63
- package/dist/commands/spec/link/index.d.ts +0 -19
- package/dist/commands/spec/link/index.js +0 -207
- package/dist/commands/spec/link/relates.d.ts +0 -14
- package/dist/commands/spec/link/relates.js +0 -63
- package/dist/commands/spec/link/remove.js +0 -96
- package/dist/commands/template/phase/apply.d.ts +0 -14
- package/dist/commands/template/phase/apply.js +0 -43
- package/dist/commands/template/phase/create.d.ts +0 -13
- package/dist/commands/template/phase/create.js +0 -38
- package/dist/commands/template/phase/delete.js +0 -36
- package/dist/commands/template/phase/index.js +0 -63
- package/dist/commands/template/phase/list.d.ts +0 -11
- package/dist/commands/template/phase/list.js +0 -36
- package/dist/commands/template/phase/update.d.ts +0 -14
- package/dist/commands/template/phase/update.js +0 -43
- package/dist/commands/template/ticket/apply.d.ts +0 -17
- package/dist/commands/template/ticket/apply.js +0 -60
- package/dist/commands/template/ticket/create.d.ts +0 -20
- package/dist/commands/template/ticket/create.js +0 -89
- package/dist/commands/template/ticket/delete.d.ts +0 -13
- package/dist/commands/template/ticket/delete.js +0 -38
- package/dist/commands/template/ticket/index.js +0 -63
- package/dist/commands/template/ticket/list.d.ts +0 -11
- package/dist/commands/template/ticket/list.js +0 -36
- package/dist/commands/template/ticket/save.d.ts +0 -15
- package/dist/commands/template/ticket/save.js +0 -46
- package/dist/commands/ticket/link/block.d.ts +0 -14
- package/dist/commands/ticket/link/block.js +0 -96
- package/dist/commands/ticket/link/duplicates.d.ts +0 -14
- package/dist/commands/ticket/link/duplicates.js +0 -95
- package/dist/commands/ticket/link/index.d.ts +0 -19
- package/dist/commands/ticket/link/index.js +0 -256
- package/dist/commands/ticket/link/relates.d.ts +0 -14
- package/dist/commands/ticket/link/relates.js +0 -95
- package/dist/commands/ticket/link/remove.d.ts +0 -16
- package/dist/commands/ticket/link/remove.js +0 -132
- package/dist/commands/ticket/template/apply.js +0 -252
- package/dist/commands/ticket/template/create.js +0 -386
- package/dist/commands/ticket/template/delete.d.ts +0 -17
- package/dist/commands/ticket/template/delete.js +0 -94
- package/dist/commands/ticket/template/index.d.ts +0 -15
- package/dist/commands/ticket/template/index.js +0 -120
- package/dist/commands/ticket/template/list.d.ts +0 -16
- package/dist/commands/ticket/template/list.js +0 -112
- package/dist/commands/ticket/template/save.js +0 -163
- /package/dist/commands/{agent/staff → staff}/add.d.ts +0 -0
- /package/dist/commands/{agent/staff → staff}/list.d.ts +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Server Command
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive MCP (Model Context Protocol) server exposing ALL prlt commands
|
|
5
|
+
* as tools for AI agent integration.
|
|
6
|
+
*
|
|
7
|
+
* Usage in Claude Code config:
|
|
8
|
+
* ```json
|
|
9
|
+
* {
|
|
10
|
+
* "mcpServers": {
|
|
11
|
+
* "prlt": { "command": "prlt", "args": ["mcp-server"] }
|
|
12
|
+
* }
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import { Command } from '@oclif/core';
|
|
17
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
18
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
19
|
+
import { execSync } from 'node:child_process';
|
|
20
|
+
import * as path from 'node:path';
|
|
21
|
+
import { getPMOContext } from '../lib/pmo/pmo-context.js';
|
|
22
|
+
import { registerTicketTools, registerProjectTools, registerBoardTools, registerSpecTools, registerEpicTools, registerWorkTools, registerWorkflowTools, registerStatusTools, registerPhaseTools, registerActionTools, registerRoadmapTools, registerCategoryTools, registerTemplateTools, registerViewTools, registerAgentTools, registerDockerTools, registerRepoTools, registerBranchTools, registerGitHubTools, registerInitTools, registerUtilityTools, } from '../lib/mcp/index.js';
|
|
23
|
+
export default class McpServerCommand extends Command {
|
|
24
|
+
static description = 'Start MCP server for AI agent integration (exposes all prlt commands as tools)';
|
|
25
|
+
static hidden = true;
|
|
26
|
+
static examples = ['<%= config.bin %> mcp-server'];
|
|
27
|
+
async run() {
|
|
28
|
+
// Initialize PMO context (may be null if not in a workspace)
|
|
29
|
+
let pmoContext = null;
|
|
30
|
+
try {
|
|
31
|
+
pmoContext = await getPMOContext({ logger: () => { } });
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
pmoContext = null;
|
|
35
|
+
}
|
|
36
|
+
// Create MCP server
|
|
37
|
+
const server = new McpServer({
|
|
38
|
+
name: 'prlt',
|
|
39
|
+
version: this.config.version,
|
|
40
|
+
});
|
|
41
|
+
// Create tool context
|
|
42
|
+
const ctx = {
|
|
43
|
+
get storage() {
|
|
44
|
+
if (!pmoContext) {
|
|
45
|
+
throw new Error('PMO not initialized. Run "prlt init" first.');
|
|
46
|
+
}
|
|
47
|
+
return pmoContext.storage;
|
|
48
|
+
},
|
|
49
|
+
runCommand: (cmd) => {
|
|
50
|
+
try {
|
|
51
|
+
const result = execSync(cmd, {
|
|
52
|
+
encoding: 'utf-8',
|
|
53
|
+
cwd: process.cwd(),
|
|
54
|
+
env: {
|
|
55
|
+
...process.env,
|
|
56
|
+
PATH: `${path.dirname(process.execPath)}:${process.env.PATH}`,
|
|
57
|
+
},
|
|
58
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
59
|
+
});
|
|
60
|
+
return result.trim();
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
const execError = error;
|
|
64
|
+
if (execError.stderr)
|
|
65
|
+
return execError.stderr.trim();
|
|
66
|
+
if (execError.stdout)
|
|
67
|
+
return execError.stdout.trim();
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
// Register all tool categories
|
|
73
|
+
registerTicketTools(server, ctx);
|
|
74
|
+
registerProjectTools(server, ctx);
|
|
75
|
+
registerBoardTools(server, ctx);
|
|
76
|
+
registerSpecTools(server, ctx);
|
|
77
|
+
registerEpicTools(server, ctx);
|
|
78
|
+
registerWorkTools(server, ctx);
|
|
79
|
+
registerWorkflowTools(server, ctx);
|
|
80
|
+
registerStatusTools(server, ctx);
|
|
81
|
+
registerPhaseTools(server, ctx);
|
|
82
|
+
registerActionTools(server, ctx);
|
|
83
|
+
registerRoadmapTools(server, ctx);
|
|
84
|
+
registerCategoryTools(server, ctx);
|
|
85
|
+
registerTemplateTools(server, ctx);
|
|
86
|
+
registerViewTools(server, ctx);
|
|
87
|
+
registerAgentTools(server, ctx);
|
|
88
|
+
registerDockerTools(server, ctx);
|
|
89
|
+
registerRepoTools(server, ctx);
|
|
90
|
+
registerBranchTools(server, ctx);
|
|
91
|
+
registerGitHubTools(server, ctx);
|
|
92
|
+
registerInitTools(server, ctx);
|
|
93
|
+
registerUtilityTools(server, ctx);
|
|
94
|
+
// Connect via stdio transport
|
|
95
|
+
const transport = new StdioServerTransport();
|
|
96
|
+
await server.connect(transport);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -21,7 +21,7 @@ export default class PhaseCreate extends PMOCommand {
|
|
|
21
21
|
...pmoBaseFlags,
|
|
22
22
|
category: Flags.string({
|
|
23
23
|
char: 'c',
|
|
24
|
-
description: 'State category',
|
|
24
|
+
description: 'State category [required for non-interactive]',
|
|
25
25
|
options: ['backlog', 'unstarted', 'started', 'completed', 'canceled'],
|
|
26
26
|
}),
|
|
27
27
|
color: Flags.string({
|
|
@@ -11,6 +11,7 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
11
11
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
12
|
template: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
13
|
interactive: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
16
|
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
17
|
};
|
|
@@ -5,7 +5,7 @@ import inquirer from 'inquirer';
|
|
|
5
5
|
import { createBoardContent, createSpecFolders, PMOCommand, pmoBaseFlags, BUILTIN_TEMPLATES } from '../../lib/pmo/index.js';
|
|
6
6
|
import { styles } from '../../lib/styles.js';
|
|
7
7
|
import { slugify } from '../../lib/pmo/utils.js';
|
|
8
|
-
import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, createMetadata, buildFormPromptConfig, } from '../../lib/prompt-json.js';
|
|
8
|
+
import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, outputDryRunSuccessAsJson, outputDryRunErrorsAsJson, createMetadata, buildFormPromptConfig, } from '../../lib/prompt-json.js';
|
|
9
9
|
// Build template options dynamically from shared definitions
|
|
10
10
|
const TEMPLATE_IDS = BUILTIN_TEMPLATES.map(t => t.id);
|
|
11
11
|
export default class ProjectCreate extends PMOCommand {
|
|
@@ -14,6 +14,7 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
14
14
|
'<%= config.bin %> <%= command.id %> "My New Project"',
|
|
15
15
|
'<%= config.bin %> <%= command.id %> --name "Mobile App" --description "iOS and Android app"',
|
|
16
16
|
'<%= config.bin %> <%= command.id %> -i # Interactive mode',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> --name "Test" --dry-run --json # Validate without creating',
|
|
17
18
|
];
|
|
18
19
|
static args = {
|
|
19
20
|
name: Args.string({
|
|
@@ -25,7 +26,7 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
25
26
|
...pmoBaseFlags,
|
|
26
27
|
name: Flags.string({
|
|
27
28
|
char: 'n',
|
|
28
|
-
description: 'Project name',
|
|
29
|
+
description: 'Project name [required for non-interactive]',
|
|
29
30
|
}),
|
|
30
31
|
id: Flags.string({
|
|
31
32
|
description: 'Custom project ID (auto-generated from name if not provided)',
|
|
@@ -45,6 +46,10 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
45
46
|
description: 'Interactive mode',
|
|
46
47
|
default: false,
|
|
47
48
|
}),
|
|
49
|
+
'dry-run': Flags.boolean({
|
|
50
|
+
description: 'Validate inputs without creating project (use with --json for structured output)',
|
|
51
|
+
default: false,
|
|
52
|
+
}),
|
|
48
53
|
};
|
|
49
54
|
getPMOOptions() {
|
|
50
55
|
return { promptIfMultiple: false };
|
|
@@ -86,8 +91,39 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
86
91
|
// Check if project already exists
|
|
87
92
|
const existing = await this.storage.getProject(projectId);
|
|
88
93
|
if (existing) {
|
|
94
|
+
if (flags['dry-run']) {
|
|
95
|
+
if (jsonMode) {
|
|
96
|
+
outputDryRunErrorsAsJson([{ field: 'id', error: `Project "${projectId}" already exists` }], createMetadata('project create', flags));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
89
99
|
this.error(`Project "${projectId}" already exists.`);
|
|
90
100
|
}
|
|
101
|
+
// Get the statuses from the workflow (for dry-run preview)
|
|
102
|
+
const statuses = await this.storage.listStatuses(projectData.template);
|
|
103
|
+
// Handle dry-run: show what would be created without actually creating
|
|
104
|
+
if (flags['dry-run']) {
|
|
105
|
+
const wouldCreate = {
|
|
106
|
+
id: projectId,
|
|
107
|
+
name: projectData.name,
|
|
108
|
+
template: projectData.template,
|
|
109
|
+
statuses: statuses.map(s => s.name),
|
|
110
|
+
...(projectData.description && { description: projectData.description }),
|
|
111
|
+
};
|
|
112
|
+
if (jsonMode) {
|
|
113
|
+
outputDryRunSuccessAsJson('project', wouldCreate, createMetadata('project create', flags));
|
|
114
|
+
}
|
|
115
|
+
// Human-readable dry-run output
|
|
116
|
+
this.log(styles.warning('\n[DRY RUN] Would create project:'));
|
|
117
|
+
this.log(styles.muted(` ID: ${projectId}`));
|
|
118
|
+
this.log(styles.muted(` Name: ${projectData.name}`));
|
|
119
|
+
this.log(styles.muted(` Template: ${projectData.template}`));
|
|
120
|
+
this.log(styles.muted(` Statuses: ${statuses.map(s => s.name).join(' → ')}`));
|
|
121
|
+
if (projectData.description) {
|
|
122
|
+
this.log(styles.muted(` Description: ${projectData.description}`));
|
|
123
|
+
}
|
|
124
|
+
this.log(styles.muted('\n(No project was created)'));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
91
127
|
// Create project in database
|
|
92
128
|
const project = await this.storage.createProject({
|
|
93
129
|
id: projectId,
|
|
@@ -104,8 +140,6 @@ export default class ProjectCreate extends PMOCommand {
|
|
|
104
140
|
fs.writeFileSync(boardPath, boardContent);
|
|
105
141
|
// Create spec folders in project directory
|
|
106
142
|
const specsPath = createSpecFolders(this.pmoPath, projectId);
|
|
107
|
-
// Get the statuses from the workflow (template name = workflow ID for built-in templates)
|
|
108
|
-
const statuses = await this.storage.listStatuses(projectData.template);
|
|
109
143
|
// In JSON mode, output success with project details
|
|
110
144
|
if (jsonMode) {
|
|
111
145
|
outputSuccessAsJson({
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
interface CreateRepoResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
url?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Get the user's GitHub organizations.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getGHOrganizations(): string[];
|
|
12
|
+
/**
|
|
13
|
+
* Create a GitHub repository using `gh` CLI.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createGHRepo(options: {
|
|
16
|
+
name: string;
|
|
17
|
+
visibility: 'public' | 'private';
|
|
18
|
+
org?: string;
|
|
19
|
+
cwd?: string;
|
|
20
|
+
push?: boolean;
|
|
21
|
+
}): CreateRepoResult;
|
|
22
|
+
export default class Create extends PMOCommand {
|
|
23
|
+
static description: string;
|
|
24
|
+
static examples: string[];
|
|
25
|
+
static flags: {
|
|
26
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
27
|
+
visibility: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
28
|
+
org: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
29
|
+
push: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
30
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
31
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
32
|
+
};
|
|
33
|
+
protected getPMOOptions(): {
|
|
34
|
+
promptIfMultiple: boolean;
|
|
35
|
+
};
|
|
36
|
+
execute(): Promise<void>;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { execSync, spawnSync } from 'node:child_process';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
5
|
+
import { colors, format } from '../../lib/colors.js';
|
|
6
|
+
import { findHQRoot, isInGitRepo } from '../../lib/repos/index.js';
|
|
7
|
+
import { hasGitHubRemote } from '../../lib/repos/git.js';
|
|
8
|
+
import { isGHInstalled, isGHAuthenticated, getGHUsername, } from '../../lib/pr/index.js';
|
|
9
|
+
import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, outputPromptAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
10
|
+
import { addRepositoriesToDatabase } from '../../lib/database/index.js';
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// GitHub Helpers
|
|
13
|
+
// =============================================================================
|
|
14
|
+
/**
|
|
15
|
+
* Get the user's GitHub organizations.
|
|
16
|
+
*/
|
|
17
|
+
export function getGHOrganizations() {
|
|
18
|
+
try {
|
|
19
|
+
const result = execSync('gh api user/orgs --jq ".[].login"', {
|
|
20
|
+
encoding: 'utf-8',
|
|
21
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
22
|
+
}).trim();
|
|
23
|
+
return result ? result.split('\n').filter(Boolean) : [];
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Create a GitHub repository using `gh` CLI.
|
|
31
|
+
*/
|
|
32
|
+
export function createGHRepo(options) {
|
|
33
|
+
const { name, visibility, org, cwd, push = true } = options;
|
|
34
|
+
const repoName = org ? `${org}/${name}` : name;
|
|
35
|
+
const args = [
|
|
36
|
+
'repo', 'create', repoName,
|
|
37
|
+
`--${visibility}`,
|
|
38
|
+
'--source=.',
|
|
39
|
+
'--remote=origin',
|
|
40
|
+
];
|
|
41
|
+
if (push) {
|
|
42
|
+
args.push('--push');
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
const result = spawnSync('gh', args, {
|
|
46
|
+
cwd,
|
|
47
|
+
encoding: 'utf-8',
|
|
48
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
49
|
+
});
|
|
50
|
+
if (result.status !== 0) {
|
|
51
|
+
return {
|
|
52
|
+
success: false,
|
|
53
|
+
error: result.stderr || 'Failed to create repository',
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
// Parse the repo URL from stdout
|
|
57
|
+
const url = result.stdout.trim();
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
url,
|
|
61
|
+
name: repoName,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
return {
|
|
66
|
+
success: false,
|
|
67
|
+
error: error instanceof Error ? error.message : 'Unknown error',
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// =============================================================================
|
|
72
|
+
// Command
|
|
73
|
+
// =============================================================================
|
|
74
|
+
export default class Create extends PMOCommand {
|
|
75
|
+
static description = 'Create a GitHub repository and set up remote';
|
|
76
|
+
static examples = [
|
|
77
|
+
'<%= config.bin %> <%= command.id %>',
|
|
78
|
+
'<%= config.bin %> <%= command.id %> --name my-repo --visibility private',
|
|
79
|
+
'<%= config.bin %> <%= command.id %> --name my-repo --org my-company',
|
|
80
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
81
|
+
];
|
|
82
|
+
static flags = {
|
|
83
|
+
...pmoBaseFlags,
|
|
84
|
+
name: Flags.string({
|
|
85
|
+
char: 'n',
|
|
86
|
+
description: 'Repository name (defaults to current directory name)',
|
|
87
|
+
}),
|
|
88
|
+
visibility: Flags.string({
|
|
89
|
+
char: 'v',
|
|
90
|
+
description: 'Repository visibility',
|
|
91
|
+
options: ['public', 'private'],
|
|
92
|
+
default: 'private',
|
|
93
|
+
}),
|
|
94
|
+
org: Flags.string({
|
|
95
|
+
char: 'o',
|
|
96
|
+
description: 'GitHub organization (creates personal repo if not specified)',
|
|
97
|
+
}),
|
|
98
|
+
push: Flags.boolean({
|
|
99
|
+
description: 'Push initial commit after creating repo',
|
|
100
|
+
default: true,
|
|
101
|
+
allowNo: true,
|
|
102
|
+
}),
|
|
103
|
+
};
|
|
104
|
+
getPMOOptions() {
|
|
105
|
+
return { promptIfMultiple: false };
|
|
106
|
+
}
|
|
107
|
+
async execute() {
|
|
108
|
+
const { flags } = await this.parse(Create);
|
|
109
|
+
const jsonMode = shouldOutputJson(flags);
|
|
110
|
+
const metadata = createMetadata('repo create', flags);
|
|
111
|
+
const handleError = (code, message) => {
|
|
112
|
+
if (jsonMode) {
|
|
113
|
+
outputErrorAsJson(code, message, metadata);
|
|
114
|
+
this.exit(1);
|
|
115
|
+
}
|
|
116
|
+
this.error(message);
|
|
117
|
+
};
|
|
118
|
+
// Check prerequisites
|
|
119
|
+
if (!isGHInstalled()) {
|
|
120
|
+
return handleError('GH_NOT_INSTALLED', 'GitHub CLI (gh) is not installed. Install with: brew install gh');
|
|
121
|
+
}
|
|
122
|
+
if (!isGHAuthenticated()) {
|
|
123
|
+
return handleError('GH_NOT_AUTHENTICATED', 'Not authenticated with GitHub. Run: gh auth login');
|
|
124
|
+
}
|
|
125
|
+
// Must be in a git repo
|
|
126
|
+
if (!isInGitRepo()) {
|
|
127
|
+
return handleError('NOT_GIT_REPO', 'Not in a git repository. Initialize one with: git init');
|
|
128
|
+
}
|
|
129
|
+
// Check if remote already exists
|
|
130
|
+
if (hasGitHubRemote()) {
|
|
131
|
+
return handleError('REMOTE_EXISTS', 'This repository already has a GitHub remote configured.');
|
|
132
|
+
}
|
|
133
|
+
// Gather parameters
|
|
134
|
+
let repoName = flags.name;
|
|
135
|
+
let visibility = flags.visibility;
|
|
136
|
+
let org = flags.org;
|
|
137
|
+
// Get default repo name from directory
|
|
138
|
+
if (!repoName) {
|
|
139
|
+
repoName = path.basename(process.cwd());
|
|
140
|
+
}
|
|
141
|
+
// Prompt for repo name
|
|
142
|
+
const nameMessage = 'Repository name:';
|
|
143
|
+
if (jsonMode) {
|
|
144
|
+
if (!flags.name) {
|
|
145
|
+
outputPromptAsJson(buildPromptConfig('input', 'repoName', nameMessage, undefined, repoName), metadata);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
const nameResult = await this.prompt([{
|
|
151
|
+
type: 'input',
|
|
152
|
+
name: 'repoName',
|
|
153
|
+
message: nameMessage,
|
|
154
|
+
default: repoName,
|
|
155
|
+
validate: (input) => {
|
|
156
|
+
const value = String(input || '');
|
|
157
|
+
if (!value.trim())
|
|
158
|
+
return 'Repository name is required';
|
|
159
|
+
if (!/^[a-zA-Z0-9._-]+$/.test(value)) {
|
|
160
|
+
return 'Repository name can only contain letters, numbers, dots, hyphens, and underscores';
|
|
161
|
+
}
|
|
162
|
+
return true;
|
|
163
|
+
},
|
|
164
|
+
}], null);
|
|
165
|
+
repoName = nameResult.repoName;
|
|
166
|
+
}
|
|
167
|
+
// Prompt for visibility
|
|
168
|
+
const visibilityChoices = [
|
|
169
|
+
{ name: 'Private', value: 'private' },
|
|
170
|
+
{ name: 'Public', value: 'public' },
|
|
171
|
+
];
|
|
172
|
+
const visibilityMessage = 'Repository visibility:';
|
|
173
|
+
if (jsonMode) {
|
|
174
|
+
if (!flags.visibility) {
|
|
175
|
+
outputPromptAsJson(buildPromptConfig('list', 'visibility', visibilityMessage, visibilityChoices, visibility), metadata);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
const visibilityResult = await this.prompt([{
|
|
181
|
+
type: 'list',
|
|
182
|
+
name: 'visibility',
|
|
183
|
+
message: visibilityMessage,
|
|
184
|
+
choices: visibilityChoices,
|
|
185
|
+
default: visibility,
|
|
186
|
+
}], null);
|
|
187
|
+
visibility = visibilityResult.visibility;
|
|
188
|
+
}
|
|
189
|
+
// Prompt for org if orgs are available
|
|
190
|
+
const orgs = getGHOrganizations();
|
|
191
|
+
const username = getGHUsername();
|
|
192
|
+
if (orgs.length > 0 && !flags.org) {
|
|
193
|
+
const ownerChoices = [
|
|
194
|
+
{ name: `${username || 'Personal'} (personal account)`, value: '' },
|
|
195
|
+
...orgs.map(o => ({ name: o, value: o })),
|
|
196
|
+
];
|
|
197
|
+
const orgMessage = 'Create repository under:';
|
|
198
|
+
if (jsonMode) {
|
|
199
|
+
outputPromptAsJson(buildPromptConfig('list', 'org', orgMessage, ownerChoices), metadata);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const orgResult = await this.prompt([{
|
|
203
|
+
type: 'list',
|
|
204
|
+
name: 'org',
|
|
205
|
+
message: orgMessage,
|
|
206
|
+
choices: ownerChoices,
|
|
207
|
+
}], null);
|
|
208
|
+
org = orgResult.org || undefined;
|
|
209
|
+
}
|
|
210
|
+
// Confirm creation (interactive only)
|
|
211
|
+
if (!jsonMode) {
|
|
212
|
+
this.log('');
|
|
213
|
+
this.log(colors.primary('Creating GitHub repository:'));
|
|
214
|
+
this.log(` Name: ${org ? `${org}/${repoName}` : repoName}`);
|
|
215
|
+
this.log(` Visibility: ${visibility}`);
|
|
216
|
+
this.log(` Push initial commit: ${flags.push ? 'Yes' : 'No'}`);
|
|
217
|
+
this.log('');
|
|
218
|
+
const confirmChoices = [
|
|
219
|
+
{ name: 'Yes', value: true },
|
|
220
|
+
{ name: 'No', value: false },
|
|
221
|
+
];
|
|
222
|
+
const confirmResult = await this.prompt([{
|
|
223
|
+
type: 'list',
|
|
224
|
+
name: 'confirm',
|
|
225
|
+
message: 'Proceed with creation?',
|
|
226
|
+
choices: confirmChoices,
|
|
227
|
+
}], null);
|
|
228
|
+
if (!confirmResult.confirm) {
|
|
229
|
+
this.log(colors.textMuted('Operation cancelled.'));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Create the repository
|
|
234
|
+
if (!jsonMode) {
|
|
235
|
+
this.log(colors.primary('Creating GitHub repository...'));
|
|
236
|
+
}
|
|
237
|
+
const result = createGHRepo({
|
|
238
|
+
name: repoName,
|
|
239
|
+
visibility,
|
|
240
|
+
org,
|
|
241
|
+
push: flags.push,
|
|
242
|
+
});
|
|
243
|
+
if (!result.success) {
|
|
244
|
+
return handleError('CREATE_FAILED', `Failed to create repository: ${result.error}`);
|
|
245
|
+
}
|
|
246
|
+
// Update prlt database if in HQ
|
|
247
|
+
const hqPath = findHQRoot();
|
|
248
|
+
if (hqPath) {
|
|
249
|
+
try {
|
|
250
|
+
// Use the actual local folder name for the DB path, not repoName
|
|
251
|
+
// (repoName may differ if user overrides it via --name)
|
|
252
|
+
const localFolderName = path.basename(process.cwd());
|
|
253
|
+
addRepositoriesToDatabase(hqPath, [{
|
|
254
|
+
name: repoName,
|
|
255
|
+
path: `repos/${localFolderName}`,
|
|
256
|
+
source_url: result.url,
|
|
257
|
+
}]);
|
|
258
|
+
if (!jsonMode) {
|
|
259
|
+
this.log(colors.textMuted('Updated prlt workspace database.'));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
// Ignore database update errors - repo was still created
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
// Success output
|
|
267
|
+
if (jsonMode) {
|
|
268
|
+
outputSuccessAsJson({
|
|
269
|
+
created: true,
|
|
270
|
+
name: result.name,
|
|
271
|
+
url: result.url,
|
|
272
|
+
visibility,
|
|
273
|
+
org: org || null,
|
|
274
|
+
}, metadata);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
this.log('');
|
|
278
|
+
this.log(format.success(`Repository created: ${result.url}`));
|
|
279
|
+
this.log('');
|
|
280
|
+
this.log(colors.textMuted('Your local repository is now connected to GitHub.'));
|
|
281
|
+
this.log(colors.textMuted('You can push changes with: git push'));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
@@ -23,6 +23,7 @@ export default class Repo extends PMOCommand {
|
|
|
23
23
|
const menuChoices = [
|
|
24
24
|
{ name: 'List all repositories', value: 'list', command: 'prlt repo list --json' },
|
|
25
25
|
{ name: 'Add repository', value: 'add', command: 'prlt repo add --json' },
|
|
26
|
+
{ name: 'Create GitHub repository', value: 'create', command: 'prlt repo create --json' },
|
|
26
27
|
{ name: 'Remove repository', value: 'remove', command: 'prlt repo remove --json' },
|
|
27
28
|
{ name: 'View repository details', value: 'view', command: 'prlt repo view --json' },
|
|
28
29
|
{ name: 'Add multiple repositories', value: 'add-bulk', command: 'prlt repo add --bulk --json' },
|
|
@@ -55,6 +56,12 @@ export default class Repo extends PMOCommand {
|
|
|
55
56
|
await cmd.run();
|
|
56
57
|
break;
|
|
57
58
|
}
|
|
59
|
+
case 'create': {
|
|
60
|
+
const { default: CreateCommand } = await import('./create.js');
|
|
61
|
+
const cmd = new CreateCommand([], this.config);
|
|
62
|
+
await cmd.run();
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
58
65
|
case 'add': {
|
|
59
66
|
const { default: AddCommand } = await import('./add.js');
|
|
60
67
|
const cmd = new AddCommand([], this.config);
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
|
-
import { shouldOutputJson,
|
|
4
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
5
|
export default class RoadmapAddProject extends PMOCommand {
|
|
7
6
|
static description = 'Add a project to a roadmap';
|
|
8
7
|
static examples = [
|
|
@@ -50,23 +49,17 @@ export default class RoadmapAddProject extends PMOCommand {
|
|
|
50
49
|
}
|
|
51
50
|
this.error('No roadmaps found. Create one with: prlt roadmap create');
|
|
52
51
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
56
|
-
value: r.id,
|
|
57
|
-
}));
|
|
58
|
-
outputPromptAsJson(buildPromptConfig('list', 'roadmap', 'Select roadmap:', choices), createMetadata('roadmap add-project', flags));
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const { selected } = await inquirer.prompt([{
|
|
52
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'roadmap add-project' } : null;
|
|
53
|
+
const { selected } = await this.prompt([{
|
|
62
54
|
type: 'list',
|
|
63
55
|
name: 'selected',
|
|
64
56
|
message: 'Select roadmap:',
|
|
65
57
|
choices: roadmaps.map(r => ({
|
|
66
58
|
name: `${r.name}${r.isDefault ? ' (default)' : ''}`,
|
|
67
59
|
value: r.id,
|
|
60
|
+
command: `prlt roadmap add-project "${r.id}" --json`,
|
|
68
61
|
})),
|
|
69
|
-
}]);
|
|
62
|
+
}], jsonModeConfig);
|
|
70
63
|
roadmapId = selected;
|
|
71
64
|
}
|
|
72
65
|
const roadmap = await this.storage.getRoadmap(roadmapId);
|
|
@@ -93,23 +86,17 @@ export default class RoadmapAddProject extends PMOCommand {
|
|
|
93
86
|
// Select project
|
|
94
87
|
let projectId = args.project;
|
|
95
88
|
if (!projectId) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
name: p.name,
|
|
99
|
-
value: p.id,
|
|
100
|
-
}));
|
|
101
|
-
outputPromptAsJson(buildPromptConfig('list', 'project', 'Select project to add:', choices), createMetadata('roadmap add-project', flags));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
const { selected } = await inquirer.prompt([{
|
|
89
|
+
const projectJsonModeConfig = jsonMode ? { flags, commandName: 'roadmap add-project' } : null;
|
|
90
|
+
const { selected } = await this.prompt([{
|
|
105
91
|
type: 'list',
|
|
106
92
|
name: 'selected',
|
|
107
93
|
message: 'Select project to add:',
|
|
108
94
|
choices: availableProjects.map(p => ({
|
|
109
95
|
name: p.name,
|
|
110
96
|
value: p.id,
|
|
97
|
+
command: `prlt roadmap add-project "${roadmapId}" "${p.id}" --json`,
|
|
111
98
|
})),
|
|
112
|
-
}]);
|
|
99
|
+
}], projectJsonModeConfig);
|
|
113
100
|
projectId = selected;
|
|
114
101
|
}
|
|
115
102
|
// Verify project exists and isn't already in roadmap
|