@proletariat/cli 0.3.23 → 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 (171) hide show
  1. package/dist/commands/action/create.js +1 -1
  2. package/dist/commands/agent/{temp/cleanup.d.ts → cleanup.d.ts} +1 -1
  3. package/dist/commands/agent/{temp/cleanup.js → cleanup.js} +4 -4
  4. package/dist/commands/agent/index.js +8 -8
  5. package/dist/commands/branch/create.js +2 -2
  6. package/dist/commands/epic/create.d.ts +1 -0
  7. package/dist/commands/epic/create.js +39 -2
  8. package/dist/commands/epic/index.js +2 -2
  9. package/dist/commands/{epic/link/remove.d.ts → link/create.d.ts} +6 -7
  10. package/dist/commands/link/create.js +141 -0
  11. package/dist/commands/{epic/link/relates.d.ts → link/index.d.ts} +4 -5
  12. package/dist/commands/link/index.js +87 -0
  13. package/dist/commands/{epic/link/duplicates.d.ts → link/list.d.ts} +7 -4
  14. package/dist/commands/link/list.js +182 -0
  15. package/dist/commands/{spec/link → link}/remove.d.ts +4 -5
  16. package/dist/commands/link/remove.js +120 -0
  17. package/dist/commands/mcp-server.d.ts +22 -0
  18. package/dist/commands/mcp-server.js +98 -0
  19. package/dist/commands/phase/create.js +1 -1
  20. package/dist/commands/project/create.d.ts +1 -0
  21. package/dist/commands/project/create.js +38 -4
  22. package/dist/commands/spec/create.d.ts +1 -0
  23. package/dist/commands/spec/create.js +43 -2
  24. package/dist/commands/spec/index.js +2 -2
  25. package/dist/commands/{agent/staff → staff}/add.js +10 -10
  26. package/dist/commands/{agent/staff → staff}/index.d.ts +1 -1
  27. package/dist/commands/{agent/staff → staff}/index.js +7 -7
  28. package/dist/commands/{agent/staff → staff}/list.js +3 -3
  29. package/dist/commands/{agent/staff → staff}/remove.d.ts +1 -1
  30. package/dist/commands/{agent/staff → staff}/remove.js +8 -8
  31. package/dist/commands/{ticket/template → template}/apply.d.ts +8 -6
  32. package/dist/commands/template/apply.js +262 -0
  33. package/dist/commands/{ticket/template → template}/create.d.ts +5 -6
  34. package/dist/commands/template/create.js +238 -0
  35. package/dist/commands/template/index.js +48 -36
  36. package/dist/commands/{ticket/template → template}/save.d.ts +2 -2
  37. package/dist/commands/template/save.js +104 -0
  38. package/dist/commands/{phase/template → template}/update.d.ts +2 -2
  39. package/dist/commands/template/update.js +99 -0
  40. package/dist/commands/{agent/themes → theme}/add-names.d.ts +1 -1
  41. package/dist/commands/{agent/themes → theme}/add-names.js +6 -6
  42. package/dist/commands/{agent/themes → theme}/create.d.ts +1 -1
  43. package/dist/commands/{agent/themes → theme}/create.js +5 -5
  44. package/dist/commands/{agent/themes → theme}/index.d.ts +1 -1
  45. package/dist/commands/{agent/themes → theme}/index.js +10 -10
  46. package/dist/commands/{agent/themes → theme}/list.d.ts +1 -1
  47. package/dist/commands/{agent/themes → theme}/list.js +5 -5
  48. package/dist/commands/{agent/themes → theme}/set.d.ts +1 -1
  49. package/dist/commands/{agent/themes → theme}/set.js +7 -7
  50. package/dist/commands/ticket/create.d.ts +1 -0
  51. package/dist/commands/ticket/create.js +54 -2
  52. package/dist/commands/ticket/index.js +6 -6
  53. package/dist/commands/work/spawn.js +1 -1
  54. package/dist/lib/mcp/helpers.d.ts +43 -0
  55. package/dist/lib/mcp/helpers.js +57 -0
  56. package/dist/lib/mcp/index.d.ts +6 -0
  57. package/dist/lib/mcp/index.js +6 -0
  58. package/dist/lib/mcp/tools/action.d.ts +6 -0
  59. package/dist/lib/mcp/tools/action.js +88 -0
  60. package/dist/lib/mcp/tools/board.d.ts +6 -0
  61. package/dist/lib/mcp/tools/board.js +139 -0
  62. package/dist/lib/mcp/tools/category.d.ts +6 -0
  63. package/dist/lib/mcp/tools/category.js +84 -0
  64. package/dist/lib/mcp/tools/cli-passthrough.d.ts +15 -0
  65. package/dist/lib/mcp/tools/cli-passthrough.js +333 -0
  66. package/dist/lib/mcp/tools/epic.d.ts +6 -0
  67. package/dist/lib/mcp/tools/epic.js +178 -0
  68. package/dist/lib/mcp/tools/index.d.ts +18 -0
  69. package/dist/lib/mcp/tools/index.js +19 -0
  70. package/dist/lib/mcp/tools/phase.d.ts +6 -0
  71. package/dist/lib/mcp/tools/phase.js +131 -0
  72. package/dist/lib/mcp/tools/project.d.ts +6 -0
  73. package/dist/lib/mcp/tools/project.js +196 -0
  74. package/dist/lib/mcp/tools/roadmap.d.ts +6 -0
  75. package/dist/lib/mcp/tools/roadmap.js +123 -0
  76. package/dist/lib/mcp/tools/spec.d.ts +6 -0
  77. package/dist/lib/mcp/tools/spec.js +196 -0
  78. package/dist/lib/mcp/tools/status.d.ts +6 -0
  79. package/dist/lib/mcp/tools/status.js +109 -0
  80. package/dist/lib/mcp/tools/template.d.ts +6 -0
  81. package/dist/lib/mcp/tools/template.js +107 -0
  82. package/dist/lib/mcp/tools/ticket.d.ts +6 -0
  83. package/dist/lib/mcp/tools/ticket.js +393 -0
  84. package/dist/lib/mcp/tools/view.d.ts +6 -0
  85. package/dist/lib/mcp/tools/view.js +76 -0
  86. package/dist/lib/mcp/tools/work.d.ts +6 -0
  87. package/dist/lib/mcp/tools/work.js +132 -0
  88. package/dist/lib/mcp/tools/workflow.d.ts +6 -0
  89. package/dist/lib/mcp/tools/workflow.js +95 -0
  90. package/dist/lib/mcp/types.d.ts +17 -0
  91. package/dist/lib/mcp/types.js +4 -0
  92. package/dist/lib/prompt-json.d.ts +52 -1
  93. package/dist/lib/prompt-json.js +45 -0
  94. package/oclif.manifest.json +3660 -5564
  95. package/package.json +6 -4
  96. package/dist/commands/agent/temp/index.d.ts +0 -14
  97. package/dist/commands/agent/temp/index.js +0 -85
  98. package/dist/commands/agent/temp/list.d.ts +0 -7
  99. package/dist/commands/agent/temp/list.js +0 -108
  100. package/dist/commands/epic/link/block.d.ts +0 -14
  101. package/dist/commands/epic/link/block.js +0 -81
  102. package/dist/commands/epic/link/duplicates.js +0 -68
  103. package/dist/commands/epic/link/index.d.ts +0 -19
  104. package/dist/commands/epic/link/index.js +0 -272
  105. package/dist/commands/epic/link/relates.js +0 -68
  106. package/dist/commands/epic/link/remove.js +0 -93
  107. package/dist/commands/phase/template/apply.d.ts +0 -17
  108. package/dist/commands/phase/template/apply.js +0 -108
  109. package/dist/commands/phase/template/create.d.ts +0 -17
  110. package/dist/commands/phase/template/create.js +0 -104
  111. package/dist/commands/phase/template/delete.d.ts +0 -17
  112. package/dist/commands/phase/template/delete.js +0 -100
  113. package/dist/commands/phase/template/index.d.ts +0 -15
  114. package/dist/commands/phase/template/index.js +0 -130
  115. package/dist/commands/phase/template/list.d.ts +0 -16
  116. package/dist/commands/phase/template/list.js +0 -97
  117. package/dist/commands/phase/template/update.js +0 -89
  118. package/dist/commands/spec/link/depends.d.ts +0 -14
  119. package/dist/commands/spec/link/depends.js +0 -64
  120. package/dist/commands/spec/link/duplicates.d.ts +0 -14
  121. package/dist/commands/spec/link/duplicates.js +0 -63
  122. package/dist/commands/spec/link/index.d.ts +0 -19
  123. package/dist/commands/spec/link/index.js +0 -207
  124. package/dist/commands/spec/link/relates.d.ts +0 -14
  125. package/dist/commands/spec/link/relates.js +0 -63
  126. package/dist/commands/spec/link/remove.js +0 -96
  127. package/dist/commands/template/phase/apply.d.ts +0 -14
  128. package/dist/commands/template/phase/apply.js +0 -43
  129. package/dist/commands/template/phase/create.d.ts +0 -13
  130. package/dist/commands/template/phase/create.js +0 -38
  131. package/dist/commands/template/phase/delete.d.ts +0 -13
  132. package/dist/commands/template/phase/delete.js +0 -36
  133. package/dist/commands/template/phase/index.d.ts +0 -10
  134. package/dist/commands/template/phase/index.js +0 -63
  135. package/dist/commands/template/phase/list.d.ts +0 -11
  136. package/dist/commands/template/phase/list.js +0 -36
  137. package/dist/commands/template/phase/update.d.ts +0 -14
  138. package/dist/commands/template/phase/update.js +0 -43
  139. package/dist/commands/template/ticket/apply.d.ts +0 -17
  140. package/dist/commands/template/ticket/apply.js +0 -60
  141. package/dist/commands/template/ticket/create.d.ts +0 -20
  142. package/dist/commands/template/ticket/create.js +0 -89
  143. package/dist/commands/template/ticket/delete.d.ts +0 -13
  144. package/dist/commands/template/ticket/delete.js +0 -38
  145. package/dist/commands/template/ticket/index.d.ts +0 -10
  146. package/dist/commands/template/ticket/index.js +0 -63
  147. package/dist/commands/template/ticket/list.d.ts +0 -11
  148. package/dist/commands/template/ticket/list.js +0 -36
  149. package/dist/commands/template/ticket/save.d.ts +0 -15
  150. package/dist/commands/template/ticket/save.js +0 -46
  151. package/dist/commands/ticket/link/block.d.ts +0 -14
  152. package/dist/commands/ticket/link/block.js +0 -96
  153. package/dist/commands/ticket/link/duplicates.d.ts +0 -14
  154. package/dist/commands/ticket/link/duplicates.js +0 -95
  155. package/dist/commands/ticket/link/index.d.ts +0 -19
  156. package/dist/commands/ticket/link/index.js +0 -256
  157. package/dist/commands/ticket/link/relates.d.ts +0 -14
  158. package/dist/commands/ticket/link/relates.js +0 -95
  159. package/dist/commands/ticket/link/remove.d.ts +0 -16
  160. package/dist/commands/ticket/link/remove.js +0 -132
  161. package/dist/commands/ticket/template/apply.js +0 -252
  162. package/dist/commands/ticket/template/create.js +0 -386
  163. package/dist/commands/ticket/template/delete.d.ts +0 -17
  164. package/dist/commands/ticket/template/delete.js +0 -94
  165. package/dist/commands/ticket/template/index.d.ts +0 -15
  166. package/dist/commands/ticket/template/index.js +0 -120
  167. package/dist/commands/ticket/template/list.d.ts +0 -16
  168. package/dist/commands/ticket/template/list.js +0 -112
  169. package/dist/commands/ticket/template/save.js +0 -163
  170. /package/dist/commands/{agent/staff → staff}/add.d.ts +0 -0
  171. /package/dist/commands/{agent/staff → staff}/list.d.ts +0 -0
@@ -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: {
@@ -0,0 +1,104 @@
1
+ import { Flags, Args } from '@oclif/core';
2
+ import inquirer from 'inquirer';
3
+ import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
4
+ import { styles } from '../../lib/styles.js';
5
+ import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
6
+ export default class TemplateSave extends PMOCommand {
7
+ static description = 'Create a ticket template from an existing ticket';
8
+ static examples = [
9
+ '<%= config.bin %> <%= command.id %> TKT-001 "Bug Report Template"',
10
+ '<%= config.bin %> <%= command.id %> TKT-042 "Feature Request" -d "Standard feature request"',
11
+ ];
12
+ static args = {
13
+ ticket: Args.string({
14
+ description: 'Ticket ID to create template from',
15
+ required: false,
16
+ }),
17
+ name: Args.string({
18
+ description: 'Template name',
19
+ required: false,
20
+ }),
21
+ };
22
+ static flags = {
23
+ ...pmoBaseFlags,
24
+ 'template-name': Flags.string({
25
+ char: 'n',
26
+ description: 'Template name (alternative to positional arg)',
27
+ }),
28
+ description: Flags.string({
29
+ char: 'd',
30
+ description: 'Template description',
31
+ }),
32
+ json: Flags.boolean({
33
+ char: 'm',
34
+ aliases: ['machine'],
35
+ description: 'Output as JSON for AI agents/scripts',
36
+ default: false,
37
+ }),
38
+ };
39
+ async execute() {
40
+ const { args, flags } = await this.parse(TemplateSave);
41
+ const jsonMode = shouldOutputJson(flags);
42
+ const handleError = (code, message) => {
43
+ if (jsonMode) {
44
+ outputErrorAsJson(code, message, createMetadata('template save', flags));
45
+ }
46
+ this.error(message);
47
+ };
48
+ // Get ticket ID
49
+ let ticketId = args.ticket;
50
+ if (!ticketId) {
51
+ const projectId = await this.requireProject();
52
+ const tickets = await this.storage.listTickets(projectId);
53
+ if (tickets.length === 0) {
54
+ return handleError('NO_TICKETS', 'No tickets found. Create a ticket first.');
55
+ }
56
+ if (jsonMode) {
57
+ const choices = tickets.slice(0, 20).map(t => ({ name: `${t.id} - ${t.title}`, value: t.id }));
58
+ outputPromptAsJson(buildPromptConfig('list', 'ticket', 'Select ticket:', choices), createMetadata('template save', flags));
59
+ return;
60
+ }
61
+ const { selected } = await inquirer.prompt([{
62
+ type: 'list',
63
+ name: 'selected',
64
+ message: 'Select ticket:',
65
+ choices: tickets.slice(0, 20).map(t => ({ name: `${t.id} - ${t.title}`, value: t.id })),
66
+ }]);
67
+ ticketId = selected;
68
+ }
69
+ const ticket = await this.storage.getTicket(ticketId);
70
+ if (!ticket) {
71
+ return handleError('TICKET_NOT_FOUND', `Ticket not found: ${ticketId}`);
72
+ }
73
+ // Get template name
74
+ let templateName = flags['template-name'] || args.name;
75
+ if (!templateName) {
76
+ if (jsonMode) {
77
+ outputPromptAsJson(buildPromptConfig('input', 'name', 'Template name:', undefined, ticket.category || ticket.title.split(' ')[0]), createMetadata('template save', flags));
78
+ return;
79
+ }
80
+ const { name } = await inquirer.prompt([{
81
+ type: 'input',
82
+ name: 'name',
83
+ message: 'Template name:',
84
+ default: ticket.category || ticket.title.split(' ')[0],
85
+ validate: (i) => i.length > 0 || 'Required',
86
+ }]);
87
+ templateName = name;
88
+ }
89
+ // Get description
90
+ let description = flags.description;
91
+ if (description === undefined && !jsonMode) {
92
+ const { desc } = await inquirer.prompt([{ type: 'input', name: 'desc', message: 'Description (optional):' }]);
93
+ description = desc || undefined;
94
+ }
95
+ const template = await this.storage.createTicketTemplateFromTicket(ticketId, templateName, description);
96
+ if (jsonMode) {
97
+ outputSuccessAsJson({ template, sourceTicketId: ticketId }, createMetadata('template save', flags));
98
+ return;
99
+ }
100
+ this.log(styles.success(`\nCreated template "${styles.emphasis(template.name)}" from ${ticketId}`));
101
+ this.log(styles.muted(` ID: ${template.id}`));
102
+ this.log(styles.muted(`\nApply with: prlt template apply --type ticket ${template.id}`));
103
+ }
104
+ }
@@ -1,5 +1,5 @@
1
- import { PMOCommand } from '../../../lib/pmo/index.js';
2
- export default class PhaseTemplateUpdate extends PMOCommand {
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class TemplateUpdate extends PMOCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -0,0 +1,99 @@
1
+ import { Flags, Args } from '@oclif/core';
2
+ import inquirer from 'inquirer';
3
+ import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
4
+ import { styles } from '../../lib/styles.js';
5
+ import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
6
+ export default class TemplateUpdate extends PMOCommand {
7
+ static description = 'Update a phase template';
8
+ static examples = [
9
+ '<%= config.bin %> <%= command.id %> my-template --name "New Name"',
10
+ '<%= config.bin %> <%= command.id %> my-template -d "Updated description"',
11
+ ];
12
+ static args = {
13
+ id: Args.string({
14
+ description: 'Template ID to update',
15
+ required: false,
16
+ }),
17
+ };
18
+ static flags = {
19
+ ...pmoBaseFlags,
20
+ name: Flags.string({
21
+ char: 'n',
22
+ description: 'New template name',
23
+ }),
24
+ description: Flags.string({
25
+ char: 'd',
26
+ description: 'New template description',
27
+ }),
28
+ json: Flags.boolean({
29
+ char: 'm',
30
+ aliases: ['machine'],
31
+ description: 'Output as JSON for AI agents/scripts',
32
+ default: false,
33
+ }),
34
+ };
35
+ getPMOOptions() {
36
+ return { promptIfMultiple: false };
37
+ }
38
+ async execute() {
39
+ const { args, flags } = await this.parse(TemplateUpdate);
40
+ const jsonMode = shouldOutputJson(flags);
41
+ const handleError = (code, message) => {
42
+ if (jsonMode) {
43
+ outputErrorAsJson(code, message, createMetadata('template update', flags));
44
+ }
45
+ this.error(message);
46
+ };
47
+ // Get template ID
48
+ let templateId = args.id;
49
+ if (!templateId) {
50
+ const templates = await this.storage.listPhaseTemplates();
51
+ const editable = templates.filter(t => !t.isBuiltin);
52
+ if (editable.length === 0) {
53
+ return handleError('NO_TEMPLATES', 'No editable phase templates (built-in cannot be updated).');
54
+ }
55
+ if (jsonMode) {
56
+ const choices = editable.map(t => ({ name: `${t.name}${t.description ? ` - ${t.description}` : ''}`, value: t.id }));
57
+ outputPromptAsJson(buildPromptConfig('list', 'id', 'Select template:', choices), createMetadata('template update', flags));
58
+ return;
59
+ }
60
+ const { selected } = await inquirer.prompt([{
61
+ type: 'list',
62
+ name: 'selected',
63
+ message: 'Select template:',
64
+ choices: editable.map(t => ({ name: `${t.name}${t.description ? ` - ${t.description}` : ''}`, value: t.id })),
65
+ }]);
66
+ templateId = selected;
67
+ }
68
+ // Get updates
69
+ let newName = flags.name;
70
+ let newDescription = flags.description;
71
+ if (!newName && newDescription === undefined && !jsonMode) {
72
+ const { updateName } = await inquirer.prompt([{
73
+ type: 'input', name: 'updateName', message: 'New name (leave empty to keep):',
74
+ }]);
75
+ if (updateName)
76
+ newName = updateName;
77
+ const { updateDesc } = await inquirer.prompt([{
78
+ type: 'input', name: 'updateDesc', message: 'New description (leave empty to keep):',
79
+ }]);
80
+ if (updateDesc)
81
+ newDescription = updateDesc;
82
+ if (!newName && !newDescription) {
83
+ this.log(styles.muted('No changes.'));
84
+ return;
85
+ }
86
+ }
87
+ const changes = {};
88
+ if (newName)
89
+ changes.name = newName;
90
+ if (newDescription !== undefined)
91
+ changes.description = newDescription;
92
+ const template = await this.storage.updatePhaseTemplate(templateId, changes);
93
+ if (jsonMode) {
94
+ outputSuccessAsJson({ template }, createMetadata('template update', flags));
95
+ return;
96
+ }
97
+ this.log(styles.success(`\nUpdated template "${styles.emphasis(template.name)}"`));
98
+ }
99
+ }
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class ThemesAddNames extends Command {
2
+ export default class ThemeAddNames extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -1,9 +1,9 @@
1
1
  import { Command, Args } from '@oclif/core';
2
2
  import chalk from 'chalk';
3
- import { getWorkspaceInfo } from '../../../lib/agents/commands.js';
4
- import { isValidAgentName, normalizeAgentName } from '../../../lib/themes.js';
5
- import { getTheme, addThemeNames, getThemeNames } from '../../../lib/database/index.js';
6
- export default class ThemesAddNames extends Command {
3
+ import { getWorkspaceInfo } from '../../lib/agents/commands.js';
4
+ import { isValidAgentName, normalizeAgentName } from '../../lib/themes.js';
5
+ import { getTheme, addThemeNames, getThemeNames } from '../../lib/database/index.js';
6
+ export default class ThemeAddNames extends Command {
7
7
  static description = 'Add names to a theme';
8
8
  static examples = [
9
9
  '<%= config.bin %> <%= command.id %> greek-gods zeus athena poseidon',
@@ -21,13 +21,13 @@ export default class ThemesAddNames extends Command {
21
21
  };
22
22
  static strict = false; // Allow multiple name arguments
23
23
  async run() {
24
- const { args, argv } = await this.parse(ThemesAddNames);
24
+ const { args, argv } = await this.parse(ThemeAddNames);
25
25
  try {
26
26
  const workspaceInfo = getWorkspaceInfo();
27
27
  // Validate theme exists
28
28
  const theme = getTheme(workspaceInfo.path, args.theme);
29
29
  if (!theme) {
30
- this.error(`Theme "${args.theme}" not found. Run "prlt agent themes list" to see available themes.`);
30
+ this.error(`Theme "${args.theme}" not found. Run "prlt theme list" to see available themes.`);
31
31
  }
32
32
  // Get names from remaining arguments (skip the theme arg)
33
33
  const rawNames = argv.slice(1);
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class ThemesCreate extends Command {
2
+ export default class ThemeCreate extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
@@ -1,8 +1,8 @@
1
1
  import { Command, Args, Flags } from '@oclif/core';
2
2
  import chalk from 'chalk';
3
- import { getWorkspaceInfo } from '../../../lib/agents/commands.js';
4
- import { createTheme, getTheme } from '../../../lib/database/index.js';
5
- export default class ThemesCreate extends Command {
3
+ import { getWorkspaceInfo } from '../../lib/agents/commands.js';
4
+ import { createTheme, getTheme } from '../../lib/database/index.js';
5
+ export default class ThemeCreate extends Command {
6
6
  static description = 'Create a custom agent theme';
7
7
  static examples = [
8
8
  '<%= config.bin %> <%= command.id %> greek-gods',
@@ -25,7 +25,7 @@ export default class ThemesCreate extends Command {
25
25
  }),
26
26
  };
27
27
  async run() {
28
- const { args, flags } = await this.parse(ThemesCreate);
28
+ const { args, flags } = await this.parse(ThemeCreate);
29
29
  try {
30
30
  const workspaceInfo = getWorkspaceInfo();
31
31
  // Validate theme name format
@@ -57,7 +57,7 @@ export default class ThemesCreate extends Command {
57
57
  this.log(chalk.gray(` ${theme.description}`));
58
58
  }
59
59
  this.log('');
60
- this.log(chalk.blue('Add names with: prlt agent themes add-names ' + name + ' <names...>'));
60
+ this.log(chalk.blue('Add names with: prlt theme add-names ' + name + ' <names...>'));
61
61
  }
62
62
  catch (error) {
63
63
  this.error(error instanceof Error ? error.message : String(error));
@@ -1,5 +1,5 @@
1
1
  import { Command } from '@oclif/core';
2
- export default class Themes extends Command {
2
+ export default class Theme extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {