@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.
- package/LICENSE +190 -21
- package/README.md +7 -7
- package/dist/commands/action/create.js +1 -1
- package/dist/commands/agent/{temp/cleanup.d.ts → cleanup.d.ts} +1 -1
- package/dist/commands/agent/{temp/cleanup.js → cleanup.js} +4 -4
- package/dist/commands/agent/index.js +8 -8
- package/dist/commands/branch/create.js +2 -2
- package/dist/commands/epic/create.d.ts +1 -0
- package/dist/commands/epic/create.js +39 -2
- package/dist/commands/epic/index.js +2 -2
- package/dist/commands/{epic/link/remove.d.ts → link/create.d.ts} +6 -7
- package/dist/commands/link/create.js +141 -0
- package/dist/commands/{epic/link/relates.d.ts → link/index.d.ts} +4 -5
- package/dist/commands/link/index.js +87 -0
- package/dist/commands/{epic/link/duplicates.d.ts → link/list.d.ts} +7 -4
- package/dist/commands/link/list.js +182 -0
- package/dist/commands/{spec/link → link}/remove.d.ts +4 -5
- package/dist/commands/link/remove.js +120 -0
- package/dist/commands/mcp-server.d.ts +22 -0
- package/dist/commands/mcp-server.js +98 -0
- package/dist/commands/phase/create.js +1 -1
- package/dist/commands/project/create.d.ts +1 -0
- package/dist/commands/project/create.js +38 -4
- package/dist/commands/spec/create.d.ts +1 -0
- package/dist/commands/spec/create.js +43 -2
- package/dist/commands/spec/index.js +2 -2
- package/dist/commands/{agent/staff → staff}/add.js +10 -10
- package/dist/commands/{agent/staff → staff}/index.d.ts +1 -1
- package/dist/commands/{agent/staff → staff}/index.js +7 -7
- package/dist/commands/{agent/staff → staff}/list.js +3 -3
- package/dist/commands/{agent/staff → staff}/remove.d.ts +1 -1
- package/dist/commands/{agent/staff → staff}/remove.js +8 -8
- package/dist/commands/{ticket/template → template}/apply.d.ts +8 -6
- package/dist/commands/template/apply.js +262 -0
- package/dist/commands/{ticket/template → template}/create.d.ts +5 -6
- package/dist/commands/template/create.js +238 -0
- package/dist/commands/template/index.js +48 -36
- package/dist/commands/{ticket/template → template}/save.d.ts +2 -2
- package/dist/commands/template/save.js +104 -0
- package/dist/commands/{phase/template → template}/update.d.ts +2 -2
- package/dist/commands/template/update.js +99 -0
- package/dist/commands/{agent/themes → theme}/add-names.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/add-names.js +6 -6
- package/dist/commands/{agent/themes → theme}/create.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/create.js +5 -5
- package/dist/commands/{agent/themes → theme}/index.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/index.js +10 -10
- package/dist/commands/{agent/themes → theme}/list.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/list.js +5 -5
- package/dist/commands/{agent/themes → theme}/set.d.ts +1 -1
- package/dist/commands/{agent/themes → theme}/set.js +7 -7
- package/dist/commands/ticket/create.d.ts +1 -0
- package/dist/commands/ticket/create.js +54 -2
- package/dist/commands/ticket/index.js +6 -6
- package/dist/commands/work/spawn.js +1 -1
- package/dist/lib/mcp/helpers.d.ts +43 -0
- package/dist/lib/mcp/helpers.js +57 -0
- package/dist/lib/mcp/index.d.ts +6 -0
- package/dist/lib/mcp/index.js +6 -0
- package/dist/lib/mcp/tools/action.d.ts +6 -0
- package/dist/lib/mcp/tools/action.js +88 -0
- package/dist/lib/mcp/tools/board.d.ts +6 -0
- package/dist/lib/mcp/tools/board.js +139 -0
- package/dist/lib/mcp/tools/category.d.ts +6 -0
- package/dist/lib/mcp/tools/category.js +84 -0
- package/dist/lib/mcp/tools/cli-passthrough.d.ts +15 -0
- package/dist/lib/mcp/tools/cli-passthrough.js +333 -0
- package/dist/lib/mcp/tools/epic.d.ts +6 -0
- package/dist/lib/mcp/tools/epic.js +178 -0
- package/dist/lib/mcp/tools/index.d.ts +18 -0
- package/dist/lib/mcp/tools/index.js +19 -0
- package/dist/lib/mcp/tools/phase.d.ts +6 -0
- package/dist/lib/mcp/tools/phase.js +131 -0
- package/dist/lib/mcp/tools/project.d.ts +6 -0
- package/dist/lib/mcp/tools/project.js +196 -0
- package/dist/lib/mcp/tools/roadmap.d.ts +6 -0
- package/dist/lib/mcp/tools/roadmap.js +123 -0
- package/dist/lib/mcp/tools/spec.d.ts +6 -0
- package/dist/lib/mcp/tools/spec.js +196 -0
- package/dist/lib/mcp/tools/status.d.ts +6 -0
- package/dist/lib/mcp/tools/status.js +109 -0
- package/dist/lib/mcp/tools/template.d.ts +6 -0
- package/dist/lib/mcp/tools/template.js +107 -0
- package/dist/lib/mcp/tools/ticket.d.ts +6 -0
- package/dist/lib/mcp/tools/ticket.js +393 -0
- package/dist/lib/mcp/tools/view.d.ts +6 -0
- package/dist/lib/mcp/tools/view.js +76 -0
- package/dist/lib/mcp/tools/work.d.ts +6 -0
- package/dist/lib/mcp/tools/work.js +132 -0
- package/dist/lib/mcp/tools/workflow.d.ts +6 -0
- package/dist/lib/mcp/tools/workflow.js +95 -0
- package/dist/lib/mcp/types.d.ts +17 -0
- package/dist/lib/mcp/types.js +4 -0
- package/dist/lib/prompt-json.d.ts +52 -1
- package/dist/lib/prompt-json.js +45 -0
- package/oclif.manifest.json +3553 -5457
- package/package.json +10 -7
- package/dist/commands/agent/temp/index.d.ts +0 -14
- package/dist/commands/agent/temp/index.js +0 -85
- package/dist/commands/agent/temp/list.d.ts +0 -7
- package/dist/commands/agent/temp/list.js +0 -108
- package/dist/commands/epic/link/block.d.ts +0 -14
- package/dist/commands/epic/link/block.js +0 -81
- package/dist/commands/epic/link/duplicates.js +0 -68
- package/dist/commands/epic/link/index.d.ts +0 -19
- package/dist/commands/epic/link/index.js +0 -272
- package/dist/commands/epic/link/relates.js +0 -68
- package/dist/commands/epic/link/remove.js +0 -93
- package/dist/commands/phase/template/apply.d.ts +0 -17
- package/dist/commands/phase/template/apply.js +0 -108
- package/dist/commands/phase/template/create.d.ts +0 -17
- package/dist/commands/phase/template/create.js +0 -104
- package/dist/commands/phase/template/delete.d.ts +0 -17
- package/dist/commands/phase/template/delete.js +0 -100
- package/dist/commands/phase/template/index.d.ts +0 -15
- package/dist/commands/phase/template/index.js +0 -130
- package/dist/commands/phase/template/list.d.ts +0 -16
- package/dist/commands/phase/template/list.js +0 -97
- package/dist/commands/phase/template/update.js +0 -89
- package/dist/commands/spec/link/depends.d.ts +0 -14
- package/dist/commands/spec/link/depends.js +0 -64
- package/dist/commands/spec/link/duplicates.d.ts +0 -14
- package/dist/commands/spec/link/duplicates.js +0 -63
- package/dist/commands/spec/link/index.d.ts +0 -19
- package/dist/commands/spec/link/index.js +0 -207
- package/dist/commands/spec/link/relates.d.ts +0 -14
- package/dist/commands/spec/link/relates.js +0 -63
- package/dist/commands/spec/link/remove.js +0 -96
- package/dist/commands/template/phase/apply.d.ts +0 -14
- package/dist/commands/template/phase/apply.js +0 -43
- package/dist/commands/template/phase/create.d.ts +0 -13
- package/dist/commands/template/phase/create.js +0 -38
- package/dist/commands/template/phase/delete.d.ts +0 -13
- package/dist/commands/template/phase/delete.js +0 -36
- package/dist/commands/template/phase/index.d.ts +0 -10
- package/dist/commands/template/phase/index.js +0 -63
- package/dist/commands/template/phase/list.d.ts +0 -11
- package/dist/commands/template/phase/list.js +0 -36
- package/dist/commands/template/phase/update.d.ts +0 -14
- package/dist/commands/template/phase/update.js +0 -43
- package/dist/commands/template/ticket/apply.d.ts +0 -17
- package/dist/commands/template/ticket/apply.js +0 -60
- package/dist/commands/template/ticket/create.d.ts +0 -20
- package/dist/commands/template/ticket/create.js +0 -89
- package/dist/commands/template/ticket/delete.d.ts +0 -13
- package/dist/commands/template/ticket/delete.js +0 -38
- package/dist/commands/template/ticket/index.d.ts +0 -10
- package/dist/commands/template/ticket/index.js +0 -63
- package/dist/commands/template/ticket/list.d.ts +0 -11
- package/dist/commands/template/ticket/list.js +0 -36
- package/dist/commands/template/ticket/save.d.ts +0 -15
- package/dist/commands/template/ticket/save.js +0 -46
- package/dist/commands/ticket/link/block.d.ts +0 -14
- package/dist/commands/ticket/link/block.js +0 -96
- package/dist/commands/ticket/link/duplicates.d.ts +0 -14
- package/dist/commands/ticket/link/duplicates.js +0 -95
- package/dist/commands/ticket/link/index.d.ts +0 -19
- package/dist/commands/ticket/link/index.js +0 -256
- package/dist/commands/ticket/link/relates.d.ts +0 -14
- package/dist/commands/ticket/link/relates.js +0 -95
- package/dist/commands/ticket/link/remove.d.ts +0 -16
- package/dist/commands/ticket/link/remove.js +0 -132
- package/dist/commands/ticket/template/apply.js +0 -252
- package/dist/commands/ticket/template/create.js +0 -386
- package/dist/commands/ticket/template/delete.d.ts +0 -17
- package/dist/commands/ticket/template/delete.js +0 -94
- package/dist/commands/ticket/template/index.d.ts +0 -15
- package/dist/commands/ticket/template/index.js +0 -120
- package/dist/commands/ticket/template/list.d.ts +0 -16
- package/dist/commands/ticket/template/list.js +0 -112
- package/dist/commands/ticket/template/save.js +0 -163
- /package/dist/commands/{agent/staff → staff}/add.d.ts +0 -0
- /package/dist/commands/{agent/staff → staff}/list.d.ts +0 -0
|
@@ -0,0 +1,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 '
|
|
2
|
-
export default class
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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 '
|
|
50
|
-
await this.config.runCommand('template:
|
|
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 '
|
|
53
|
-
await this.config.runCommand('template:
|
|
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 '
|
|
2
|
-
export default class
|
|
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: {
|