@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.
Files changed (235) hide show
  1. package/dist/commands/action/create.js +4 -4
  2. package/dist/commands/action/update.js +3 -3
  3. package/dist/commands/agent/{temp/cleanup.d.ts → cleanup.d.ts} +1 -1
  4. package/dist/commands/agent/{temp/cleanup.js → cleanup.js} +4 -4
  5. package/dist/commands/agent/index.js +8 -8
  6. package/dist/commands/branch/create.js +2 -2
  7. package/dist/commands/epic/activate.js +9 -17
  8. package/dist/commands/epic/archive.js +13 -24
  9. package/dist/commands/epic/create.d.ts +1 -0
  10. package/dist/commands/epic/create.js +46 -8
  11. package/dist/commands/epic/index.js +2 -2
  12. package/dist/commands/epic/move.js +28 -47
  13. package/dist/commands/epic/progress.js +10 -14
  14. package/dist/commands/epic/project.js +42 -59
  15. package/dist/commands/epic/reorder.js +25 -30
  16. package/dist/commands/epic/spec.d.ts +1 -0
  17. package/dist/commands/epic/spec.js +39 -40
  18. package/dist/commands/epic/ticket.d.ts +2 -0
  19. package/dist/commands/epic/ticket.js +63 -37
  20. package/dist/commands/feedback/index.d.ts +10 -0
  21. package/dist/commands/feedback/index.js +60 -0
  22. package/dist/commands/feedback/list.d.ts +12 -0
  23. package/dist/commands/feedback/list.js +126 -0
  24. package/dist/commands/feedback/submit.d.ts +16 -0
  25. package/dist/commands/feedback/submit.js +220 -0
  26. package/dist/commands/{template/phase/delete.d.ts → feedback/view.d.ts} +7 -5
  27. package/dist/commands/feedback/view.js +109 -0
  28. package/dist/commands/gh/index.js +4 -0
  29. package/dist/commands/{epic/link/remove.d.ts → link/create.d.ts} +6 -7
  30. package/dist/commands/link/create.js +141 -0
  31. package/dist/commands/{epic/link/relates.d.ts → link/index.d.ts} +4 -5
  32. package/dist/commands/link/index.js +87 -0
  33. package/dist/commands/{epic/link/duplicates.d.ts → link/list.d.ts} +7 -4
  34. package/dist/commands/link/list.js +182 -0
  35. package/dist/commands/{spec/link → link}/remove.d.ts +4 -5
  36. package/dist/commands/link/remove.js +120 -0
  37. package/dist/commands/mcp-server.d.ts +22 -0
  38. package/dist/commands/mcp-server.js +98 -0
  39. package/dist/commands/phase/create.js +1 -1
  40. package/dist/commands/project/create.d.ts +1 -0
  41. package/dist/commands/project/create.js +38 -4
  42. package/dist/commands/repo/create.d.ts +38 -0
  43. package/dist/commands/repo/create.js +283 -0
  44. package/dist/commands/repo/index.js +7 -0
  45. package/dist/commands/roadmap/add-project.js +9 -22
  46. package/dist/commands/roadmap/create.d.ts +0 -1
  47. package/dist/commands/roadmap/create.js +46 -40
  48. package/dist/commands/roadmap/delete.js +10 -24
  49. package/dist/commands/roadmap/generate.d.ts +1 -0
  50. package/dist/commands/roadmap/generate.js +21 -22
  51. package/dist/commands/roadmap/remove-project.js +14 -34
  52. package/dist/commands/roadmap/reorder.js +19 -26
  53. package/dist/commands/roadmap/update.js +27 -26
  54. package/dist/commands/roadmap/view.js +5 -12
  55. package/dist/commands/session/attach.d.ts +1 -8
  56. package/dist/commands/session/attach.js +93 -59
  57. package/dist/commands/session/list.d.ts +0 -8
  58. package/dist/commands/session/list.js +130 -81
  59. package/dist/commands/spec/create.d.ts +1 -0
  60. package/dist/commands/spec/create.js +44 -3
  61. package/dist/commands/spec/edit.js +63 -33
  62. package/dist/commands/spec/index.js +2 -2
  63. package/dist/commands/{agent/staff → staff}/add.js +10 -10
  64. package/dist/commands/{agent/staff → staff}/index.d.ts +1 -1
  65. package/dist/commands/{agent/staff → staff}/index.js +7 -7
  66. package/dist/commands/{agent/staff → staff}/list.js +3 -3
  67. package/dist/commands/{agent/staff → staff}/remove.d.ts +1 -1
  68. package/dist/commands/{agent/staff → staff}/remove.js +8 -8
  69. package/dist/commands/{template/phase/index.d.ts → support/book.d.ts} +2 -2
  70. package/dist/commands/support/book.js +54 -0
  71. package/dist/commands/{template/ticket/index.d.ts → support/discord.d.ts} +2 -2
  72. package/dist/commands/support/discord.js +54 -0
  73. package/dist/commands/support/docs.d.ts +10 -0
  74. package/dist/commands/support/docs.js +54 -0
  75. package/dist/commands/support/index.d.ts +19 -0
  76. package/dist/commands/support/index.js +81 -0
  77. package/dist/commands/support/issues.d.ts +11 -0
  78. package/dist/commands/support/issues.js +77 -0
  79. package/dist/commands/support/logs.d.ts +18 -0
  80. package/dist/commands/support/logs.js +247 -0
  81. package/dist/commands/{ticket/template → template}/apply.d.ts +8 -6
  82. package/dist/commands/template/apply.js +262 -0
  83. package/dist/commands/{ticket/template → template}/create.d.ts +5 -6
  84. package/dist/commands/template/create.js +238 -0
  85. package/dist/commands/template/index.js +48 -36
  86. package/dist/commands/{ticket/template → template}/save.d.ts +2 -2
  87. package/dist/commands/template/save.js +104 -0
  88. package/dist/commands/{phase/template → template}/update.d.ts +2 -2
  89. package/dist/commands/template/update.js +99 -0
  90. package/dist/commands/{agent/themes → theme}/add-names.d.ts +1 -1
  91. package/dist/commands/{agent/themes → theme}/add-names.js +6 -6
  92. package/dist/commands/{agent/themes → theme}/create.d.ts +1 -1
  93. package/dist/commands/{agent/themes → theme}/create.js +5 -5
  94. package/dist/commands/{agent/themes → theme}/index.d.ts +1 -1
  95. package/dist/commands/{agent/themes → theme}/index.js +10 -10
  96. package/dist/commands/{agent/themes → theme}/list.d.ts +1 -1
  97. package/dist/commands/{agent/themes → theme}/list.js +5 -5
  98. package/dist/commands/{agent/themes → theme}/set.d.ts +1 -1
  99. package/dist/commands/{agent/themes → theme}/set.js +7 -7
  100. package/dist/commands/ticket/create.d.ts +1 -0
  101. package/dist/commands/ticket/create.js +75 -15
  102. package/dist/commands/ticket/edit.js +44 -13
  103. package/dist/commands/ticket/index.js +6 -6
  104. package/dist/commands/ticket/move.d.ts +7 -0
  105. package/dist/commands/ticket/move.js +132 -0
  106. package/dist/commands/work/spawn.d.ts +1 -0
  107. package/dist/commands/work/spawn.js +72 -8
  108. package/dist/commands/work/start.js +6 -0
  109. package/dist/lib/execution/runners.js +21 -17
  110. package/dist/lib/execution/session-utils.d.ts +60 -0
  111. package/dist/lib/execution/session-utils.js +162 -0
  112. package/dist/lib/execution/spawner.d.ts +2 -0
  113. package/dist/lib/execution/spawner.js +42 -0
  114. package/dist/lib/flags/resolver.d.ts +2 -2
  115. package/dist/lib/flags/resolver.js +15 -0
  116. package/dist/lib/init/index.js +18 -0
  117. package/dist/lib/mcp/helpers.d.ts +43 -0
  118. package/dist/lib/mcp/helpers.js +57 -0
  119. package/dist/lib/mcp/index.d.ts +6 -0
  120. package/dist/lib/mcp/index.js +6 -0
  121. package/dist/lib/mcp/tools/action.d.ts +6 -0
  122. package/dist/lib/mcp/tools/action.js +88 -0
  123. package/dist/lib/mcp/tools/board.d.ts +6 -0
  124. package/dist/lib/mcp/tools/board.js +139 -0
  125. package/dist/lib/mcp/tools/category.d.ts +6 -0
  126. package/dist/lib/mcp/tools/category.js +84 -0
  127. package/dist/lib/mcp/tools/cli-passthrough.d.ts +15 -0
  128. package/dist/lib/mcp/tools/cli-passthrough.js +333 -0
  129. package/dist/lib/mcp/tools/epic.d.ts +6 -0
  130. package/dist/lib/mcp/tools/epic.js +178 -0
  131. package/dist/lib/mcp/tools/index.d.ts +18 -0
  132. package/dist/lib/mcp/tools/index.js +19 -0
  133. package/dist/lib/mcp/tools/phase.d.ts +6 -0
  134. package/dist/lib/mcp/tools/phase.js +131 -0
  135. package/dist/lib/mcp/tools/project.d.ts +6 -0
  136. package/dist/lib/mcp/tools/project.js +196 -0
  137. package/dist/lib/mcp/tools/roadmap.d.ts +6 -0
  138. package/dist/lib/mcp/tools/roadmap.js +123 -0
  139. package/dist/lib/mcp/tools/spec.d.ts +6 -0
  140. package/dist/lib/mcp/tools/spec.js +196 -0
  141. package/dist/lib/mcp/tools/status.d.ts +6 -0
  142. package/dist/lib/mcp/tools/status.js +109 -0
  143. package/dist/lib/mcp/tools/template.d.ts +6 -0
  144. package/dist/lib/mcp/tools/template.js +107 -0
  145. package/dist/lib/mcp/tools/ticket.d.ts +6 -0
  146. package/dist/lib/mcp/tools/ticket.js +393 -0
  147. package/dist/lib/mcp/tools/view.d.ts +6 -0
  148. package/dist/lib/mcp/tools/view.js +76 -0
  149. package/dist/lib/mcp/tools/work.d.ts +6 -0
  150. package/dist/lib/mcp/tools/work.js +132 -0
  151. package/dist/lib/mcp/tools/workflow.d.ts +6 -0
  152. package/dist/lib/mcp/tools/workflow.js +95 -0
  153. package/dist/lib/mcp/types.d.ts +17 -0
  154. package/dist/lib/mcp/types.js +4 -0
  155. package/dist/lib/multiline-input.d.ts +63 -0
  156. package/dist/lib/multiline-input.js +360 -0
  157. package/dist/lib/prompt-json.d.ts +57 -6
  158. package/dist/lib/prompt-json.js +45 -0
  159. package/dist/lib/repos/git.d.ts +7 -0
  160. package/dist/lib/repos/git.js +20 -0
  161. package/oclif.manifest.json +3690 -4995
  162. package/package.json +6 -4
  163. package/dist/commands/agent/temp/index.d.ts +0 -14
  164. package/dist/commands/agent/temp/index.js +0 -85
  165. package/dist/commands/agent/temp/list.d.ts +0 -7
  166. package/dist/commands/agent/temp/list.js +0 -108
  167. package/dist/commands/epic/link/block.d.ts +0 -14
  168. package/dist/commands/epic/link/block.js +0 -81
  169. package/dist/commands/epic/link/duplicates.js +0 -68
  170. package/dist/commands/epic/link/index.d.ts +0 -19
  171. package/dist/commands/epic/link/index.js +0 -272
  172. package/dist/commands/epic/link/relates.js +0 -68
  173. package/dist/commands/epic/link/remove.js +0 -93
  174. package/dist/commands/phase/template/apply.d.ts +0 -17
  175. package/dist/commands/phase/template/apply.js +0 -108
  176. package/dist/commands/phase/template/create.d.ts +0 -17
  177. package/dist/commands/phase/template/create.js +0 -104
  178. package/dist/commands/phase/template/delete.d.ts +0 -17
  179. package/dist/commands/phase/template/delete.js +0 -100
  180. package/dist/commands/phase/template/index.d.ts +0 -15
  181. package/dist/commands/phase/template/index.js +0 -130
  182. package/dist/commands/phase/template/list.d.ts +0 -16
  183. package/dist/commands/phase/template/list.js +0 -97
  184. package/dist/commands/phase/template/update.js +0 -89
  185. package/dist/commands/spec/link/depends.d.ts +0 -14
  186. package/dist/commands/spec/link/depends.js +0 -64
  187. package/dist/commands/spec/link/duplicates.d.ts +0 -14
  188. package/dist/commands/spec/link/duplicates.js +0 -63
  189. package/dist/commands/spec/link/index.d.ts +0 -19
  190. package/dist/commands/spec/link/index.js +0 -207
  191. package/dist/commands/spec/link/relates.d.ts +0 -14
  192. package/dist/commands/spec/link/relates.js +0 -63
  193. package/dist/commands/spec/link/remove.js +0 -96
  194. package/dist/commands/template/phase/apply.d.ts +0 -14
  195. package/dist/commands/template/phase/apply.js +0 -43
  196. package/dist/commands/template/phase/create.d.ts +0 -13
  197. package/dist/commands/template/phase/create.js +0 -38
  198. package/dist/commands/template/phase/delete.js +0 -36
  199. package/dist/commands/template/phase/index.js +0 -63
  200. package/dist/commands/template/phase/list.d.ts +0 -11
  201. package/dist/commands/template/phase/list.js +0 -36
  202. package/dist/commands/template/phase/update.d.ts +0 -14
  203. package/dist/commands/template/phase/update.js +0 -43
  204. package/dist/commands/template/ticket/apply.d.ts +0 -17
  205. package/dist/commands/template/ticket/apply.js +0 -60
  206. package/dist/commands/template/ticket/create.d.ts +0 -20
  207. package/dist/commands/template/ticket/create.js +0 -89
  208. package/dist/commands/template/ticket/delete.d.ts +0 -13
  209. package/dist/commands/template/ticket/delete.js +0 -38
  210. package/dist/commands/template/ticket/index.js +0 -63
  211. package/dist/commands/template/ticket/list.d.ts +0 -11
  212. package/dist/commands/template/ticket/list.js +0 -36
  213. package/dist/commands/template/ticket/save.d.ts +0 -15
  214. package/dist/commands/template/ticket/save.js +0 -46
  215. package/dist/commands/ticket/link/block.d.ts +0 -14
  216. package/dist/commands/ticket/link/block.js +0 -96
  217. package/dist/commands/ticket/link/duplicates.d.ts +0 -14
  218. package/dist/commands/ticket/link/duplicates.js +0 -95
  219. package/dist/commands/ticket/link/index.d.ts +0 -19
  220. package/dist/commands/ticket/link/index.js +0 -256
  221. package/dist/commands/ticket/link/relates.d.ts +0 -14
  222. package/dist/commands/ticket/link/relates.js +0 -95
  223. package/dist/commands/ticket/link/remove.d.ts +0 -16
  224. package/dist/commands/ticket/link/remove.js +0 -132
  225. package/dist/commands/ticket/template/apply.js +0 -252
  226. package/dist/commands/ticket/template/create.js +0 -386
  227. package/dist/commands/ticket/template/delete.d.ts +0 -17
  228. package/dist/commands/ticket/template/delete.js +0 -94
  229. package/dist/commands/ticket/template/index.d.ts +0 -15
  230. package/dist/commands/ticket/template/index.js +0 -120
  231. package/dist/commands/ticket/template/list.d.ts +0 -16
  232. package/dist/commands/ticket/template/list.js +0 -112
  233. package/dist/commands/ticket/template/save.js +0 -163
  234. /package/dist/commands/{agent/staff → staff}/add.d.ts +0 -0
  235. /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, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
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
- if (jsonMode) {
54
- const choices = roadmaps.map(r => ({
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
- if (jsonMode) {
97
- const choices = availableProjects.map(p => ({
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
@@ -18,5 +18,4 @@ export default class RoadmapCreate extends PMOCommand {
18
18
  promptIfMultiple: boolean;
19
19
  };
20
20
  execute(): Promise<void>;
21
- private promptRoadmapData;
22
21
  }