@proletariat/cli 0.3.22 → 0.3.24

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 (173) hide show
  1. package/LICENSE +190 -21
  2. package/README.md +7 -7
  3. package/dist/commands/action/create.js +1 -1
  4. package/dist/commands/agent/{temp/cleanup.d.ts → cleanup.d.ts} +1 -1
  5. package/dist/commands/agent/{temp/cleanup.js → cleanup.js} +4 -4
  6. package/dist/commands/agent/index.js +8 -8
  7. package/dist/commands/branch/create.js +2 -2
  8. package/dist/commands/epic/create.d.ts +1 -0
  9. package/dist/commands/epic/create.js +39 -2
  10. package/dist/commands/epic/index.js +2 -2
  11. package/dist/commands/{epic/link/remove.d.ts → link/create.d.ts} +6 -7
  12. package/dist/commands/link/create.js +141 -0
  13. package/dist/commands/{epic/link/relates.d.ts → link/index.d.ts} +4 -5
  14. package/dist/commands/link/index.js +87 -0
  15. package/dist/commands/{epic/link/duplicates.d.ts → link/list.d.ts} +7 -4
  16. package/dist/commands/link/list.js +182 -0
  17. package/dist/commands/{spec/link → link}/remove.d.ts +4 -5
  18. package/dist/commands/link/remove.js +120 -0
  19. package/dist/commands/mcp-server.d.ts +22 -0
  20. package/dist/commands/mcp-server.js +98 -0
  21. package/dist/commands/phase/create.js +1 -1
  22. package/dist/commands/project/create.d.ts +1 -0
  23. package/dist/commands/project/create.js +38 -4
  24. package/dist/commands/spec/create.d.ts +1 -0
  25. package/dist/commands/spec/create.js +43 -2
  26. package/dist/commands/spec/index.js +2 -2
  27. package/dist/commands/{agent/staff → staff}/add.js +10 -10
  28. package/dist/commands/{agent/staff → staff}/index.d.ts +1 -1
  29. package/dist/commands/{agent/staff → staff}/index.js +7 -7
  30. package/dist/commands/{agent/staff → staff}/list.js +3 -3
  31. package/dist/commands/{agent/staff → staff}/remove.d.ts +1 -1
  32. package/dist/commands/{agent/staff → staff}/remove.js +8 -8
  33. package/dist/commands/{ticket/template → template}/apply.d.ts +8 -6
  34. package/dist/commands/template/apply.js +262 -0
  35. package/dist/commands/{ticket/template → template}/create.d.ts +5 -6
  36. package/dist/commands/template/create.js +238 -0
  37. package/dist/commands/template/index.js +48 -36
  38. package/dist/commands/{ticket/template → template}/save.d.ts +2 -2
  39. package/dist/commands/template/save.js +104 -0
  40. package/dist/commands/{phase/template → template}/update.d.ts +2 -2
  41. package/dist/commands/template/update.js +99 -0
  42. package/dist/commands/{agent/themes → theme}/add-names.d.ts +1 -1
  43. package/dist/commands/{agent/themes → theme}/add-names.js +6 -6
  44. package/dist/commands/{agent/themes → theme}/create.d.ts +1 -1
  45. package/dist/commands/{agent/themes → theme}/create.js +5 -5
  46. package/dist/commands/{agent/themes → theme}/index.d.ts +1 -1
  47. package/dist/commands/{agent/themes → theme}/index.js +10 -10
  48. package/dist/commands/{agent/themes → theme}/list.d.ts +1 -1
  49. package/dist/commands/{agent/themes → theme}/list.js +5 -5
  50. package/dist/commands/{agent/themes → theme}/set.d.ts +1 -1
  51. package/dist/commands/{agent/themes → theme}/set.js +7 -7
  52. package/dist/commands/ticket/create.d.ts +1 -0
  53. package/dist/commands/ticket/create.js +54 -2
  54. package/dist/commands/ticket/index.js +6 -6
  55. package/dist/commands/work/spawn.js +1 -1
  56. package/dist/lib/mcp/helpers.d.ts +43 -0
  57. package/dist/lib/mcp/helpers.js +57 -0
  58. package/dist/lib/mcp/index.d.ts +6 -0
  59. package/dist/lib/mcp/index.js +6 -0
  60. package/dist/lib/mcp/tools/action.d.ts +6 -0
  61. package/dist/lib/mcp/tools/action.js +88 -0
  62. package/dist/lib/mcp/tools/board.d.ts +6 -0
  63. package/dist/lib/mcp/tools/board.js +139 -0
  64. package/dist/lib/mcp/tools/category.d.ts +6 -0
  65. package/dist/lib/mcp/tools/category.js +84 -0
  66. package/dist/lib/mcp/tools/cli-passthrough.d.ts +15 -0
  67. package/dist/lib/mcp/tools/cli-passthrough.js +333 -0
  68. package/dist/lib/mcp/tools/epic.d.ts +6 -0
  69. package/dist/lib/mcp/tools/epic.js +178 -0
  70. package/dist/lib/mcp/tools/index.d.ts +18 -0
  71. package/dist/lib/mcp/tools/index.js +19 -0
  72. package/dist/lib/mcp/tools/phase.d.ts +6 -0
  73. package/dist/lib/mcp/tools/phase.js +131 -0
  74. package/dist/lib/mcp/tools/project.d.ts +6 -0
  75. package/dist/lib/mcp/tools/project.js +196 -0
  76. package/dist/lib/mcp/tools/roadmap.d.ts +6 -0
  77. package/dist/lib/mcp/tools/roadmap.js +123 -0
  78. package/dist/lib/mcp/tools/spec.d.ts +6 -0
  79. package/dist/lib/mcp/tools/spec.js +196 -0
  80. package/dist/lib/mcp/tools/status.d.ts +6 -0
  81. package/dist/lib/mcp/tools/status.js +109 -0
  82. package/dist/lib/mcp/tools/template.d.ts +6 -0
  83. package/dist/lib/mcp/tools/template.js +107 -0
  84. package/dist/lib/mcp/tools/ticket.d.ts +6 -0
  85. package/dist/lib/mcp/tools/ticket.js +393 -0
  86. package/dist/lib/mcp/tools/view.d.ts +6 -0
  87. package/dist/lib/mcp/tools/view.js +76 -0
  88. package/dist/lib/mcp/tools/work.d.ts +6 -0
  89. package/dist/lib/mcp/tools/work.js +132 -0
  90. package/dist/lib/mcp/tools/workflow.d.ts +6 -0
  91. package/dist/lib/mcp/tools/workflow.js +95 -0
  92. package/dist/lib/mcp/types.d.ts +17 -0
  93. package/dist/lib/mcp/types.js +4 -0
  94. package/dist/lib/prompt-json.d.ts +52 -1
  95. package/dist/lib/prompt-json.js +45 -0
  96. package/oclif.manifest.json +3553 -5457
  97. package/package.json +10 -7
  98. package/dist/commands/agent/temp/index.d.ts +0 -14
  99. package/dist/commands/agent/temp/index.js +0 -85
  100. package/dist/commands/agent/temp/list.d.ts +0 -7
  101. package/dist/commands/agent/temp/list.js +0 -108
  102. package/dist/commands/epic/link/block.d.ts +0 -14
  103. package/dist/commands/epic/link/block.js +0 -81
  104. package/dist/commands/epic/link/duplicates.js +0 -68
  105. package/dist/commands/epic/link/index.d.ts +0 -19
  106. package/dist/commands/epic/link/index.js +0 -272
  107. package/dist/commands/epic/link/relates.js +0 -68
  108. package/dist/commands/epic/link/remove.js +0 -93
  109. package/dist/commands/phase/template/apply.d.ts +0 -17
  110. package/dist/commands/phase/template/apply.js +0 -108
  111. package/dist/commands/phase/template/create.d.ts +0 -17
  112. package/dist/commands/phase/template/create.js +0 -104
  113. package/dist/commands/phase/template/delete.d.ts +0 -17
  114. package/dist/commands/phase/template/delete.js +0 -100
  115. package/dist/commands/phase/template/index.d.ts +0 -15
  116. package/dist/commands/phase/template/index.js +0 -130
  117. package/dist/commands/phase/template/list.d.ts +0 -16
  118. package/dist/commands/phase/template/list.js +0 -97
  119. package/dist/commands/phase/template/update.js +0 -89
  120. package/dist/commands/spec/link/depends.d.ts +0 -14
  121. package/dist/commands/spec/link/depends.js +0 -64
  122. package/dist/commands/spec/link/duplicates.d.ts +0 -14
  123. package/dist/commands/spec/link/duplicates.js +0 -63
  124. package/dist/commands/spec/link/index.d.ts +0 -19
  125. package/dist/commands/spec/link/index.js +0 -207
  126. package/dist/commands/spec/link/relates.d.ts +0 -14
  127. package/dist/commands/spec/link/relates.js +0 -63
  128. package/dist/commands/spec/link/remove.js +0 -96
  129. package/dist/commands/template/phase/apply.d.ts +0 -14
  130. package/dist/commands/template/phase/apply.js +0 -43
  131. package/dist/commands/template/phase/create.d.ts +0 -13
  132. package/dist/commands/template/phase/create.js +0 -38
  133. package/dist/commands/template/phase/delete.d.ts +0 -13
  134. package/dist/commands/template/phase/delete.js +0 -36
  135. package/dist/commands/template/phase/index.d.ts +0 -10
  136. package/dist/commands/template/phase/index.js +0 -63
  137. package/dist/commands/template/phase/list.d.ts +0 -11
  138. package/dist/commands/template/phase/list.js +0 -36
  139. package/dist/commands/template/phase/update.d.ts +0 -14
  140. package/dist/commands/template/phase/update.js +0 -43
  141. package/dist/commands/template/ticket/apply.d.ts +0 -17
  142. package/dist/commands/template/ticket/apply.js +0 -60
  143. package/dist/commands/template/ticket/create.d.ts +0 -20
  144. package/dist/commands/template/ticket/create.js +0 -89
  145. package/dist/commands/template/ticket/delete.d.ts +0 -13
  146. package/dist/commands/template/ticket/delete.js +0 -38
  147. package/dist/commands/template/ticket/index.d.ts +0 -10
  148. package/dist/commands/template/ticket/index.js +0 -63
  149. package/dist/commands/template/ticket/list.d.ts +0 -11
  150. package/dist/commands/template/ticket/list.js +0 -36
  151. package/dist/commands/template/ticket/save.d.ts +0 -15
  152. package/dist/commands/template/ticket/save.js +0 -46
  153. package/dist/commands/ticket/link/block.d.ts +0 -14
  154. package/dist/commands/ticket/link/block.js +0 -96
  155. package/dist/commands/ticket/link/duplicates.d.ts +0 -14
  156. package/dist/commands/ticket/link/duplicates.js +0 -95
  157. package/dist/commands/ticket/link/index.d.ts +0 -19
  158. package/dist/commands/ticket/link/index.js +0 -256
  159. package/dist/commands/ticket/link/relates.d.ts +0 -14
  160. package/dist/commands/ticket/link/relates.js +0 -95
  161. package/dist/commands/ticket/link/remove.d.ts +0 -16
  162. package/dist/commands/ticket/link/remove.js +0 -132
  163. package/dist/commands/ticket/template/apply.js +0 -252
  164. package/dist/commands/ticket/template/create.js +0 -386
  165. package/dist/commands/ticket/template/delete.d.ts +0 -17
  166. package/dist/commands/ticket/template/delete.js +0 -94
  167. package/dist/commands/ticket/template/index.d.ts +0 -15
  168. package/dist/commands/ticket/template/index.js +0 -120
  169. package/dist/commands/ticket/template/list.d.ts +0 -16
  170. package/dist/commands/ticket/template/list.js +0 -112
  171. package/dist/commands/ticket/template/save.js +0 -163
  172. /package/dist/commands/{agent/staff → staff}/add.d.ts +0 -0
  173. /package/dist/commands/{agent/staff → staff}/list.d.ts +0 -0
@@ -0,0 +1,262 @@
1
+ import { Flags, Args } from '@oclif/core';
2
+ import inquirer from 'inquirer';
3
+ import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
4
+ import { PRIORITIES, PRIORITY_LABELS } from '../../lib/pmo/types.js';
5
+ import { styles } from '../../lib/styles.js';
6
+ import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildFormPromptConfig, buildPromptConfig, } from '../../lib/prompt-json.js';
7
+ export default class TemplateApply extends PMOCommand {
8
+ static description = 'Apply a template';
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %> --type ticket bug-report',
11
+ '<%= config.bin %> <%= command.id %> --type ticket bug-report --title "Login fails"',
12
+ '<%= config.bin %> <%= command.id %> --type phase agile',
13
+ '<%= config.bin %> <%= command.id %> --type phase default --force',
14
+ ];
15
+ static args = {
16
+ template: Args.string({
17
+ description: 'Template ID to apply',
18
+ required: false,
19
+ }),
20
+ };
21
+ static flags = {
22
+ ...pmoBaseFlags,
23
+ type: Flags.string({
24
+ char: 't',
25
+ description: 'Template type',
26
+ options: ['ticket', 'phase'],
27
+ }),
28
+ // Ticket-specific flags
29
+ title: Flags.string({
30
+ description: 'Ticket title (ticket only)',
31
+ }),
32
+ column: Flags.string({
33
+ char: 'c',
34
+ description: 'Column to place ticket (ticket only)',
35
+ }),
36
+ priority: Flags.string({
37
+ char: 'p',
38
+ description: 'Priority override (ticket only)',
39
+ options: [...PRIORITIES],
40
+ }),
41
+ category: Flags.string({
42
+ description: 'Category override (ticket only)',
43
+ }),
44
+ assignee: Flags.string({
45
+ char: 'a',
46
+ description: 'Assignee (ticket only)',
47
+ }),
48
+ owner: Flags.string({
49
+ char: 'o',
50
+ description: 'Owner (ticket only)',
51
+ }),
52
+ description: Flags.string({
53
+ char: 'd',
54
+ description: 'Description override (ticket only)',
55
+ }),
56
+ epic: Flags.string({
57
+ char: 'e',
58
+ description: 'Link to epic (ticket only)',
59
+ }),
60
+ 'no-subtasks': Flags.boolean({
61
+ description: 'Skip creating subtasks (ticket only)',
62
+ default: false,
63
+ }),
64
+ interactive: Flags.boolean({
65
+ char: 'i',
66
+ description: 'Interactive mode (ticket only)',
67
+ default: false,
68
+ }),
69
+ // Phase-specific flags
70
+ force: Flags.boolean({
71
+ char: 'f',
72
+ description: 'Skip confirmation (phase only)',
73
+ default: false,
74
+ }),
75
+ json: Flags.boolean({
76
+ char: 'm',
77
+ aliases: ['machine'],
78
+ description: 'Output as JSON for AI agents/scripts',
79
+ default: false,
80
+ }),
81
+ };
82
+ async execute() {
83
+ const { args, flags } = await this.parse(TemplateApply);
84
+ const jsonMode = shouldOutputJson(flags);
85
+ // Determine template type
86
+ let templateType = flags.type;
87
+ if (!templateType) {
88
+ if (jsonMode) {
89
+ outputPromptAsJson(buildPromptConfig('list', 'type', 'What type of template?', [
90
+ { name: 'Ticket template', value: 'ticket' },
91
+ { name: 'Phase template', value: 'phase' },
92
+ ]), createMetadata('template apply', flags));
93
+ return;
94
+ }
95
+ const { selectedType } = await inquirer.prompt([{
96
+ type: 'list',
97
+ name: 'selectedType',
98
+ message: 'What type of template?',
99
+ choices: [
100
+ { name: 'Ticket template', value: 'ticket' },
101
+ { name: 'Phase template', value: 'phase' },
102
+ ],
103
+ }]);
104
+ templateType = selectedType;
105
+ }
106
+ if (templateType === 'ticket') {
107
+ await this.applyTicketTemplate(args.template, flags, jsonMode);
108
+ }
109
+ else {
110
+ await this.applyPhaseTemplate(args.template, flags, jsonMode);
111
+ }
112
+ }
113
+ async applyTicketTemplate(templateId, flags, jsonMode) {
114
+ const projectId = await this.requireProject();
115
+ const board = await this.storage.getBoard(projectId);
116
+ const columns = board.columns.map(col => col.name);
117
+ const handleError = (code, message) => {
118
+ if (jsonMode) {
119
+ outputErrorAsJson(code, message, createMetadata('template apply', flags));
120
+ this.exit(1);
121
+ }
122
+ this.error(message);
123
+ };
124
+ // Get template
125
+ if (!templateId) {
126
+ const templates = await this.storage.listTicketTemplates();
127
+ if (templates.length === 0) {
128
+ return handleError('NO_TEMPLATES', 'No ticket templates. Create with: prlt template create --type ticket');
129
+ }
130
+ const { selected } = await inquirer.prompt([{
131
+ type: 'list',
132
+ name: 'selected',
133
+ message: 'Select template:',
134
+ choices: templates.map(t => ({ name: `${t.name}${t.description ? ` - ${t.description}` : ''}`, value: t.id })),
135
+ }]);
136
+ templateId = selected;
137
+ }
138
+ const template = await this.storage.getTicketTemplate(templateId);
139
+ if (!template) {
140
+ return handleError('TEMPLATE_NOT_FOUND', `Template not found: ${templateId}`);
141
+ }
142
+ // Determine ticket data
143
+ let title = flags.title || template.titlePattern || '';
144
+ let column = flags.column || columns[0];
145
+ let priority = flags.priority || template.defaultPriority;
146
+ let category = flags.category || template.defaultCategory;
147
+ let assignee = flags.assignee || template.defaultAssignee;
148
+ let owner = flags.owner || template.defaultOwner;
149
+ let description = flags.description || template.descriptionTemplate;
150
+ const labels = template.defaultLabels;
151
+ // Interactive mode
152
+ if (flags.interactive || !title) {
153
+ const fields = [
154
+ { type: 'input', name: 'title', message: 'Title:', default: title || undefined },
155
+ { type: 'list', name: 'column', message: 'Column:', choices: columns.map(c => ({ name: c, value: c })), default: column },
156
+ { type: 'list', name: 'priority', message: 'Priority:', choices: [{ name: 'None', value: '' }, ...PRIORITIES.map(p => ({ name: PRIORITY_LABELS[p], value: p }))], default: priority },
157
+ ];
158
+ if (jsonMode) {
159
+ outputPromptAsJson(buildFormPromptConfig(fields), createMetadata('template apply', flags));
160
+ return;
161
+ }
162
+ const answers = await inquirer.prompt(fields.map(f => ({
163
+ ...f,
164
+ validate: f.name === 'title' ? ((i) => i.length > 0 || 'Required') : undefined,
165
+ })));
166
+ title = answers.title;
167
+ column = answers.column;
168
+ priority = answers.priority || undefined;
169
+ }
170
+ // Validate epic if provided
171
+ if (flags.epic) {
172
+ const epic = await this.storage.getEpic(flags.epic);
173
+ if (!epic)
174
+ this.error(`Epic not found: ${flags.epic}`);
175
+ }
176
+ // Create ticket
177
+ const ticket = await this.storage.createTicket(projectId, {
178
+ title,
179
+ statusName: column,
180
+ priority,
181
+ category,
182
+ assignee,
183
+ owner,
184
+ labels,
185
+ description,
186
+ epicId: flags.epic,
187
+ });
188
+ // Add subtasks
189
+ if (!flags['no-subtasks'] && template.suggestedSubtasks.length > 0) {
190
+ for (const subtask of template.suggestedSubtasks) {
191
+ await this.storage.addSubtask(ticket.id, subtask.title);
192
+ }
193
+ }
194
+ await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
195
+ this.log(styles.success(`\nCreated ticket ${styles.emphasis(ticket.id)} from template "${template.name}"`));
196
+ this.log(styles.muted(` Title: ${ticket.title}`));
197
+ this.log(styles.muted(` Status: ${ticket.statusName}`));
198
+ if (priority)
199
+ this.log(styles.muted(` Priority: ${priority}`));
200
+ if (template.suggestedSubtasks.length > 0 && !flags['no-subtasks']) {
201
+ this.log(styles.muted(` Subtasks: ${template.suggestedSubtasks.length} created`));
202
+ }
203
+ }
204
+ async applyPhaseTemplate(templateId, flags, jsonMode) {
205
+ const handleError = (code, message) => {
206
+ if (jsonMode) {
207
+ outputErrorAsJson(code, message, createMetadata('template apply', flags));
208
+ this.exit(1);
209
+ }
210
+ this.error(message);
211
+ };
212
+ // Get template
213
+ if (!templateId) {
214
+ const templates = await this.storage.listPhaseTemplates();
215
+ if (templates.length === 0) {
216
+ return handleError('NO_TEMPLATES', 'No phase templates. Create with: prlt template create --type phase');
217
+ }
218
+ const { selected } = await inquirer.prompt([{
219
+ type: 'list',
220
+ name: 'selected',
221
+ message: 'Select phase template:',
222
+ choices: templates.map(t => ({ name: `${t.name}${t.description ? ` - ${t.description}` : ''}`, value: t.id })),
223
+ }]);
224
+ templateId = selected;
225
+ }
226
+ const template = await this.storage.getPhaseTemplate(templateId);
227
+ if (!template) {
228
+ return handleError('TEMPLATE_NOT_FOUND', `Template not found: ${templateId}`);
229
+ }
230
+ // Check existing phases
231
+ const existingPhases = await this.storage.listPhases();
232
+ if (existingPhases.length > 0 && !flags.force) {
233
+ if (jsonMode) {
234
+ outputPromptAsJson(buildPromptConfig('list', 'confirmed', `Replace ${existingPhases.length} existing phase(s)?`, [
235
+ { name: 'No', value: 'false' },
236
+ { name: 'Yes', value: 'true' },
237
+ ]), createMetadata('template apply', flags));
238
+ return;
239
+ }
240
+ this.log(styles.warning(`\nThis will REPLACE ${existingPhases.length} existing phase(s).`));
241
+ const { confirm } = await inquirer.prompt([{
242
+ type: 'list',
243
+ name: 'confirm',
244
+ message: `Apply template "${template.name}"?`,
245
+ choices: [
246
+ { name: 'No', value: false },
247
+ { name: 'Yes', value: true },
248
+ ],
249
+ }]);
250
+ if (!confirm) {
251
+ this.log(styles.muted('Cancelled'));
252
+ return;
253
+ }
254
+ }
255
+ const phases = await this.storage.applyPhaseTemplate(templateId);
256
+ this.log(styles.success(`\nApplied phase template "${styles.emphasis(template.name)}"`));
257
+ this.log(styles.muted(`Created ${phases.length} phases:`));
258
+ for (const phase of phases) {
259
+ this.log(styles.muted(` • ${phase.name} [${phase.category}]`));
260
+ }
261
+ }
262
+ }
@@ -1,11 +1,12 @@
1
- import { PMOCommand } from '../../../lib/pmo/index.js';
2
- export default class TicketTemplateCreate extends PMOCommand {
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class TemplateCreate extends PMOCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
6
6
  name: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
7
  };
8
8
  static flags: {
9
+ type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
10
  description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
11
  'title-pattern': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
12
  'description-template': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -21,8 +22,6 @@ export default class TicketTemplateCreate extends PMOCommand {
21
22
  promptIfMultiple: boolean;
22
23
  };
23
24
  execute(): Promise<void>;
24
- /**
25
- * Check if any non-default flags were provided (indicating non-interactive intent)
26
- */
27
- private hasNonDefaultFlags;
25
+ private createTicketTemplate;
26
+ private createPhaseTemplate;
28
27
  }
@@ -0,0 +1,238 @@
1
+ import { Args, Flags } from '@oclif/core';
2
+ import inquirer from 'inquirer';
3
+ import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
4
+ import { PRIORITIES, PRIORITY_LABELS, TICKET_CATEGORIES } from '../../lib/pmo/types.js';
5
+ import { styles } from '../../lib/styles.js';
6
+ import { shouldOutputJson, outputSuccessAsJson, outputPromptAsJson, outputErrorAsJson, buildFormPromptConfig, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
7
+ export default class TemplateCreate extends PMOCommand {
8
+ static description = 'Create a new template';
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %> --type ticket "Bug Report"',
11
+ '<%= config.bin %> <%= command.id %> --type ticket "Feature" -d "For new features"',
12
+ '<%= config.bin %> <%= command.id %> --type phase "My Phases"',
13
+ '<%= config.bin %> <%= command.id %> --type phase "Enterprise" --description "Enterprise lifecycle"',
14
+ ];
15
+ static args = {
16
+ name: Args.string({
17
+ description: 'Template name',
18
+ required: false,
19
+ }),
20
+ };
21
+ static flags = {
22
+ ...pmoBaseFlags,
23
+ type: Flags.string({
24
+ char: 't',
25
+ description: 'Template type',
26
+ options: ['ticket', 'phase'],
27
+ }),
28
+ description: Flags.string({
29
+ char: 'd',
30
+ description: 'Template description',
31
+ }),
32
+ // Ticket-specific flags
33
+ 'title-pattern': Flags.string({
34
+ description: 'Default title prefix/pattern (ticket only)',
35
+ }),
36
+ 'description-template': Flags.string({
37
+ description: 'Default description template (ticket only)',
38
+ }),
39
+ priority: Flags.string({
40
+ char: 'p',
41
+ description: 'Default priority (ticket only)',
42
+ options: [...PRIORITIES],
43
+ }),
44
+ category: Flags.string({
45
+ char: 'c',
46
+ description: 'Default category (ticket only)',
47
+ options: [...TICKET_CATEGORIES],
48
+ }),
49
+ subtask: Flags.string({
50
+ description: 'Add a suggested subtask (ticket only, can repeat)',
51
+ multiple: true,
52
+ }),
53
+ ac: Flags.string({
54
+ description: 'Add an acceptance criterion (ticket only, can repeat)',
55
+ multiple: true,
56
+ }),
57
+ label: Flags.string({
58
+ char: 'l',
59
+ description: 'Add a default label (ticket only, can repeat)',
60
+ multiple: true,
61
+ }),
62
+ json: Flags.boolean({
63
+ char: 'm',
64
+ aliases: ['machine'],
65
+ description: 'Output as JSON for AI agents/scripts',
66
+ default: false,
67
+ }),
68
+ };
69
+ getPMOOptions() {
70
+ return { promptIfMultiple: false };
71
+ }
72
+ async execute() {
73
+ const { args, flags } = await this.parse(TemplateCreate);
74
+ const jsonMode = shouldOutputJson(flags);
75
+ // Determine template type
76
+ let templateType = flags.type;
77
+ if (!templateType) {
78
+ if (jsonMode) {
79
+ outputPromptAsJson(buildPromptConfig('list', 'type', 'What type of template?', [
80
+ { name: 'Ticket template', value: 'ticket' },
81
+ { name: 'Phase template', value: 'phase' },
82
+ ]), createMetadata('template create', flags));
83
+ return;
84
+ }
85
+ const { selectedType } = await inquirer.prompt([{
86
+ type: 'list',
87
+ name: 'selectedType',
88
+ message: 'What type of template?',
89
+ choices: [
90
+ { name: 'Ticket template', value: 'ticket' },
91
+ { name: 'Phase template', value: 'phase' },
92
+ ],
93
+ }]);
94
+ templateType = selectedType;
95
+ }
96
+ if (templateType === 'ticket') {
97
+ await this.createTicketTemplate(args.name, flags, jsonMode);
98
+ }
99
+ else {
100
+ await this.createPhaseTemplate(args.name, flags, jsonMode);
101
+ }
102
+ }
103
+ async createTicketTemplate(name, flags, jsonMode) {
104
+ // Check if we have required data
105
+ if (!name) {
106
+ if (jsonMode) {
107
+ const fields = [
108
+ { type: 'input', name: 'name', message: 'Template name:' },
109
+ { type: 'input', name: 'description', message: 'Description (optional):' },
110
+ {
111
+ type: 'list',
112
+ name: 'priority',
113
+ message: 'Default priority:',
114
+ choices: [
115
+ { name: 'None', value: '' },
116
+ ...PRIORITIES.map(p => ({ name: PRIORITY_LABELS[p], value: p })),
117
+ ],
118
+ },
119
+ {
120
+ type: 'list',
121
+ name: 'category',
122
+ message: 'Default category:',
123
+ choices: [
124
+ { name: 'None', value: '' },
125
+ ...TICKET_CATEGORIES.map(c => ({ name: c, value: c })),
126
+ ],
127
+ },
128
+ ];
129
+ outputPromptAsJson(buildFormPromptConfig(fields), createMetadata('template create', flags));
130
+ return;
131
+ }
132
+ const { templateName } = await inquirer.prompt([{
133
+ type: 'input',
134
+ name: 'templateName',
135
+ message: 'Template name:',
136
+ validate: (input) => input.trim() ? true : 'Name required',
137
+ }]);
138
+ name = templateName;
139
+ }
140
+ // Get optional values
141
+ let description = flags.description;
142
+ let titlePattern = flags['title-pattern'];
143
+ let priority = flags.priority;
144
+ let category = flags.category;
145
+ const subtasks = flags.subtask || [];
146
+ const acs = flags.ac || [];
147
+ const labels = flags.label || [];
148
+ let descriptionTemplate = flags['description-template'];
149
+ // Interactive prompts for missing optional values
150
+ if (!jsonMode && description === undefined) {
151
+ const { desc } = await inquirer.prompt([{
152
+ type: 'input', name: 'desc', message: 'Description (optional):',
153
+ }]);
154
+ description = desc || undefined;
155
+ }
156
+ if (!jsonMode && priority === undefined) {
157
+ const { p } = await inquirer.prompt([{
158
+ type: 'list',
159
+ name: 'p',
160
+ message: 'Default priority:',
161
+ choices: [
162
+ { name: 'None', value: '' },
163
+ ...PRIORITIES.map(pr => ({ name: PRIORITY_LABELS[pr], value: pr })),
164
+ ],
165
+ }]);
166
+ priority = p || undefined;
167
+ }
168
+ if (!jsonMode && category === undefined) {
169
+ const { c } = await inquirer.prompt([{
170
+ type: 'list',
171
+ name: 'c',
172
+ message: 'Default category:',
173
+ choices: [
174
+ { name: 'None', value: '' },
175
+ ...TICKET_CATEGORIES.map(cat => ({ name: cat, value: cat })),
176
+ ],
177
+ }]);
178
+ category = c || undefined;
179
+ }
180
+ // Build description template with ACs
181
+ if (acs.length > 0 && !descriptionTemplate) {
182
+ descriptionTemplate = '## Description\n\n## Acceptance Criteria\n' +
183
+ acs.map(ac => `- [ ] ${ac}`).join('\n') + '\n';
184
+ }
185
+ // Create the template
186
+ const template = await this.storage.createTicketTemplate({
187
+ name: name,
188
+ description,
189
+ titlePattern,
190
+ defaultPriority: priority,
191
+ defaultCategory: category,
192
+ descriptionTemplate,
193
+ suggestedSubtasks: subtasks.map(title => ({ title })),
194
+ defaultLabels: labels,
195
+ });
196
+ if (jsonMode) {
197
+ outputSuccessAsJson({ template }, createMetadata('template create', flags));
198
+ return;
199
+ }
200
+ this.log(styles.success(`\nCreated ticket template "${styles.emphasis(template.name)}"`));
201
+ this.log(styles.muted(` ID: ${template.id}`));
202
+ this.log(styles.muted(`\nApply with: prlt template apply --type ticket ${template.id}`));
203
+ }
204
+ async createPhaseTemplate(name, flags, jsonMode) {
205
+ if (!name) {
206
+ if (jsonMode) {
207
+ outputErrorAsJson('NAME_REQUIRED', 'Name required: prlt template create --type phase "Name"', createMetadata('template create', flags));
208
+ return;
209
+ }
210
+ const { templateName } = await inquirer.prompt([{
211
+ type: 'input',
212
+ name: 'templateName',
213
+ message: 'Template name:',
214
+ validate: (input) => input.trim() ? true : 'Name required',
215
+ }]);
216
+ name = templateName;
217
+ }
218
+ let description = flags.description;
219
+ if (!jsonMode && description === undefined) {
220
+ const { desc } = await inquirer.prompt([{
221
+ type: 'input', name: 'desc', message: 'Description (optional):',
222
+ }]);
223
+ description = desc || undefined;
224
+ }
225
+ const template = await this.storage.savePhaseTemplate(name, description);
226
+ if (jsonMode) {
227
+ outputSuccessAsJson({
228
+ id: template.id,
229
+ name: template.name,
230
+ phasesCount: template.phases.length,
231
+ }, createMetadata('template create', flags));
232
+ return;
233
+ }
234
+ this.log(styles.success(`\nCreated phase template "${styles.emphasis(template.name)}" (${template.id})`));
235
+ this.log(styles.muted(`Saved ${template.phases.length} phases`));
236
+ this.log(styles.muted(`\nApply with: prlt template apply --type phase ${template.id}`));
237
+ }
238
+ }
@@ -1,56 +1,68 @@
1
- import { Command } from '@oclif/core';
1
+ import { Command, Flags } from '@oclif/core';
2
+ import inquirer from 'inquirer';
2
3
  import { styles } from '../../lib/styles.js';
3
- import { FlagResolver, shouldOutputJson } from '../../lib/flags/index.js';
4
- import { machineOutputFlags } from '../../lib/pmo/index.js';
4
+ import { shouldOutputJson, outputPromptAsJson, buildPromptConfig, createMetadata } from '../../lib/prompt-json.js';
5
5
  export default class Template extends Command {
6
6
  static description = 'Manage templates (ticket and phase)';
7
7
  static aliases = ['templates'];
8
8
  static examples = [
9
- '<%= config.bin %> <%= command.id %>',
10
9
  '<%= config.bin %> <%= command.id %> list',
11
10
  '<%= config.bin %> <%= command.id %> list --type ticket',
12
- '<%= config.bin %> <%= command.id %> ticket',
13
- '<%= config.bin %> <%= command.id %> phase',
11
+ '<%= config.bin %> <%= command.id %> create --type ticket "Bug Report"',
12
+ '<%= config.bin %> <%= command.id %> apply --type phase agile',
14
13
  ];
15
14
  static flags = {
16
- ...machineOutputFlags,
15
+ json: Flags.boolean({
16
+ char: 'm',
17
+ aliases: ['machine'],
18
+ description: 'Output as JSON for AI agents/scripts',
19
+ default: false,
20
+ }),
17
21
  };
18
22
  async run() {
19
23
  const { flags } = await this.parse(Template);
20
24
  const jsonMode = shouldOutputJson(flags);
21
- // Create resolver for action selection
22
- const resolver = new FlagResolver({
23
- commandName: 'template',
24
- baseCommand: 'prlt template',
25
- jsonMode,
26
- flags: {},
27
- });
28
- resolver.addPrompt({
29
- flagName: 'action',
30
- type: 'list',
31
- message: 'What would you like to do?',
32
- choices: () => [
33
- { name: 'List all templates', value: 'list', command: 'prlt template list --json' },
34
- { name: 'Ticket templates (ticket presets)', value: 'ticket', command: 'prlt template ticket --json' },
35
- { name: 'Phase templates (project phases)', value: 'phase', command: 'prlt template phase --json' },
36
- ],
37
- });
38
- // In JSON mode, this outputs the prompt and exits
39
- // In interactive mode, this prompts and returns the value
40
- const resolved = await resolver.resolve();
41
- // Only reached in interactive mode
42
- this.log('');
43
- this.log(styles.header('Templates'));
44
- this.log('');
45
- switch (resolved.action) {
25
+ const menuChoices = [
26
+ { name: 'List templates', value: 'list', command: 'prlt template list --json' },
27
+ { name: 'Create template', value: 'create', command: 'prlt template create --json' },
28
+ { name: 'Apply template', value: 'apply', command: 'prlt template apply --json' },
29
+ { name: 'Save ticket as template', value: 'save', command: 'prlt template save --json' },
30
+ { name: 'Update phase template', value: 'update', command: 'prlt template update --json' },
31
+ { name: 'Delete template', value: 'delete', command: 'prlt template delete --json' },
32
+ { name: 'Cancel', value: 'cancel', command: '' },
33
+ ];
34
+ if (jsonMode) {
35
+ outputPromptAsJson(buildPromptConfig('list', 'action', 'What would you like to do?', menuChoices), createMetadata('template', flags));
36
+ return;
37
+ }
38
+ this.log(`\n${styles.emphasis('Templates')}`);
39
+ this.log(styles.muted('Manage ticket and phase templates\n'));
40
+ const { action } = await inquirer.prompt([{
41
+ type: 'list',
42
+ name: 'action',
43
+ message: 'What would you like to do?',
44
+ choices: menuChoices.map(c => ({ name: c.name, value: c.value })),
45
+ }]);
46
+ if (action === 'cancel')
47
+ return;
48
+ switch (action) {
46
49
  case 'list':
47
50
  await this.config.runCommand('template:list', []);
48
51
  break;
49
- case 'ticket':
50
- await this.config.runCommand('template:ticket', []);
52
+ case 'create':
53
+ await this.config.runCommand('template:create', []);
54
+ break;
55
+ case 'apply':
56
+ await this.config.runCommand('template:apply', []);
57
+ break;
58
+ case 'save':
59
+ await this.config.runCommand('template:save', []);
60
+ break;
61
+ case 'update':
62
+ await this.config.runCommand('template:update', []);
51
63
  break;
52
- case 'phase':
53
- await this.config.runCommand('template:phase', []);
64
+ case 'delete':
65
+ await this.config.runCommand('template:delete', []);
54
66
  break;
55
67
  }
56
68
  }
@@ -1,5 +1,5 @@
1
- import { PMOCommand } from '../../../lib/pmo/index.js';
2
- export default class TicketTemplateSave extends PMOCommand {
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class TemplateSave extends PMOCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {