@proletariat/cli 0.3.24 → 0.3.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/action/create.js +3 -3
- package/dist/commands/action/update.js +3 -3
- package/dist/commands/epic/activate.js +9 -17
- package/dist/commands/epic/archive.js +13 -24
- package/dist/commands/epic/create.js +7 -6
- package/dist/commands/epic/move.js +28 -47
- package/dist/commands/epic/progress.js +10 -14
- package/dist/commands/epic/project.js +42 -59
- package/dist/commands/epic/reorder.js +25 -30
- package/dist/commands/epic/spec.d.ts +1 -0
- package/dist/commands/epic/spec.js +39 -40
- package/dist/commands/epic/ticket.d.ts +2 -0
- package/dist/commands/epic/ticket.js +63 -37
- package/dist/commands/feedback/index.d.ts +10 -0
- package/dist/commands/feedback/index.js +60 -0
- package/dist/commands/feedback/list.d.ts +12 -0
- package/dist/commands/feedback/list.js +126 -0
- package/dist/commands/feedback/submit.d.ts +16 -0
- package/dist/commands/feedback/submit.js +220 -0
- package/dist/commands/feedback/view.d.ts +15 -0
- package/dist/commands/feedback/view.js +109 -0
- package/dist/commands/gh/index.js +4 -0
- package/dist/commands/repo/create.d.ts +38 -0
- package/dist/commands/repo/create.js +283 -0
- package/dist/commands/repo/index.js +7 -0
- package/dist/commands/roadmap/add-project.js +9 -22
- package/dist/commands/roadmap/create.d.ts +0 -1
- package/dist/commands/roadmap/create.js +46 -40
- package/dist/commands/roadmap/delete.js +10 -24
- package/dist/commands/roadmap/generate.d.ts +1 -0
- package/dist/commands/roadmap/generate.js +21 -22
- package/dist/commands/roadmap/remove-project.js +14 -34
- package/dist/commands/roadmap/reorder.js +19 -26
- package/dist/commands/roadmap/update.js +27 -26
- package/dist/commands/roadmap/view.js +5 -12
- package/dist/commands/session/attach.d.ts +1 -8
- package/dist/commands/session/attach.js +93 -59
- package/dist/commands/session/list.d.ts +0 -8
- package/dist/commands/session/list.js +130 -81
- package/dist/commands/spec/create.js +1 -1
- package/dist/commands/spec/edit.js +63 -33
- package/dist/commands/support/book.d.ts +10 -0
- package/dist/commands/support/book.js +54 -0
- package/dist/commands/support/discord.d.ts +10 -0
- package/dist/commands/support/discord.js +54 -0
- package/dist/commands/support/docs.d.ts +10 -0
- package/dist/commands/support/docs.js +54 -0
- package/dist/commands/support/index.d.ts +19 -0
- package/dist/commands/support/index.js +81 -0
- package/dist/commands/support/issues.d.ts +11 -0
- package/dist/commands/support/issues.js +77 -0
- package/dist/commands/support/logs.d.ts +18 -0
- package/dist/commands/support/logs.js +247 -0
- package/dist/commands/ticket/create.js +21 -13
- package/dist/commands/ticket/edit.js +44 -13
- package/dist/commands/ticket/move.d.ts +7 -0
- package/dist/commands/ticket/move.js +132 -0
- package/dist/commands/work/spawn.d.ts +1 -0
- package/dist/commands/work/spawn.js +71 -7
- package/dist/commands/work/start.js +6 -0
- package/dist/lib/execution/runners.js +21 -17
- package/dist/lib/execution/session-utils.d.ts +60 -0
- package/dist/lib/execution/session-utils.js +162 -0
- package/dist/lib/execution/spawner.d.ts +2 -0
- package/dist/lib/execution/spawner.js +42 -0
- package/dist/lib/flags/resolver.d.ts +2 -2
- package/dist/lib/flags/resolver.js +15 -0
- package/dist/lib/init/index.js +18 -0
- package/dist/lib/multiline-input.d.ts +63 -0
- package/dist/lib/multiline-input.js +360 -0
- package/dist/lib/prompt-json.d.ts +5 -5
- package/dist/lib/repos/git.d.ts +7 -0
- package/dist/lib/repos/git.js +20 -0
- package/oclif.manifest.json +2206 -1607
- package/package.json +1 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
|
-
import { shouldOutputJson,
|
|
4
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
5
|
export default class EpicSpec extends PMOCommand {
|
|
7
6
|
static description = 'Assign a spec to an epic (design document)';
|
|
8
7
|
static examples = [
|
|
@@ -34,6 +33,10 @@ export default class EpicSpec extends PMOCommand {
|
|
|
34
33
|
description: 'Remove spec from epic instead of adding',
|
|
35
34
|
default: false,
|
|
36
35
|
}),
|
|
36
|
+
'align-tickets': Flags.boolean({
|
|
37
|
+
description: 'Also update all tickets in the epic to use the same spec',
|
|
38
|
+
default: false,
|
|
39
|
+
}),
|
|
37
40
|
};
|
|
38
41
|
async execute() {
|
|
39
42
|
const { args, flags } = await this.parse(EpicSpec);
|
|
@@ -66,19 +69,16 @@ export default class EpicSpec extends PMOCommand {
|
|
|
66
69
|
return {
|
|
67
70
|
name: `${e.id} ${e.title} (${e.status})${specLabel}`,
|
|
68
71
|
value: e.id,
|
|
72
|
+
command: `prlt epic spec ${e.id} --json`,
|
|
69
73
|
};
|
|
70
74
|
});
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
outputPromptAsJson(buildPromptConfig('list', 'epicId', 'Select epic:', epicChoices), createMetadata('epic spec', flags));
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const { selected } = await inquirer.prompt([{
|
|
75
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'epic spec' } : null;
|
|
76
|
+
const { selected } = await this.prompt([{
|
|
77
77
|
type: 'list',
|
|
78
78
|
name: 'selected',
|
|
79
79
|
message: 'Select epic:',
|
|
80
80
|
choices: epicChoices,
|
|
81
|
-
}]);
|
|
81
|
+
}], jsonModeConfig);
|
|
82
82
|
epicId = selected;
|
|
83
83
|
}
|
|
84
84
|
// Validate epic exists
|
|
@@ -114,18 +114,15 @@ export default class EpicSpec extends PMOCommand {
|
|
|
114
114
|
const specChoices = specs.map(s => ({
|
|
115
115
|
name: `${s.id} - ${s.title} (${s.status})`,
|
|
116
116
|
value: s.id,
|
|
117
|
+
command: `prlt epic spec ${epicId} ${s.id} --json`,
|
|
117
118
|
}));
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
outputPromptAsJson(buildPromptConfig('list', 'specId', `Select spec to link to ${epicId}:`, specChoices), createMetadata('epic spec', flags));
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const { selected } = await inquirer.prompt([{
|
|
119
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'epic spec' } : null;
|
|
120
|
+
const { selected } = await this.prompt([{
|
|
124
121
|
type: 'list',
|
|
125
122
|
name: 'selected',
|
|
126
123
|
message: `Select spec to link to ${epicId}:`,
|
|
127
124
|
choices: specChoices,
|
|
128
|
-
}]);
|
|
125
|
+
}], jsonModeConfig);
|
|
129
126
|
specId = selected;
|
|
130
127
|
}
|
|
131
128
|
// Validate spec exists
|
|
@@ -147,33 +144,35 @@ export default class EpicSpec extends PMOCommand {
|
|
|
147
144
|
const epicTickets = await this.storage.getTicketsForEpic(projectId, epicId);
|
|
148
145
|
const ticketsWithDifferentSpec = epicTickets.filter(t => t.specId && t.specId !== specId);
|
|
149
146
|
if (ticketsWithDifferentSpec.length > 0) {
|
|
150
|
-
//
|
|
151
|
-
|
|
147
|
+
// If --align-tickets flag provided, skip prompt and align all
|
|
148
|
+
let action;
|
|
149
|
+
if (flags['align-tickets']) {
|
|
150
|
+
action = 'align_all';
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
if (!jsonMode) {
|
|
154
|
+
this.log(styles.warning(`\n⚠️ ${ticketsWithDifferentSpec.length} ticket(s) in this epic have different specs:`));
|
|
155
|
+
for (const t of ticketsWithDifferentSpec.slice(0, 5)) {
|
|
156
|
+
this.log(styles.muted(` - ${t.id}: spec "${t.specId}"`));
|
|
157
|
+
}
|
|
158
|
+
if (ticketsWithDifferentSpec.length > 5) {
|
|
159
|
+
this.log(styles.muted(` ... and ${ticketsWithDifferentSpec.length - 5} more`));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
152
162
|
const mismatchChoices = [
|
|
153
|
-
{ name: 'Update epic only (tickets keep their specs)', value: 'epic_only' },
|
|
154
|
-
{ name: `Update epic AND align all tickets to "${specId}"`, value: 'align_all' },
|
|
155
|
-
{ name: 'Cancel', value: 'cancel' },
|
|
163
|
+
{ name: 'Update epic only (tickets keep their specs)', value: 'epic_only', command: `prlt epic spec ${epicId} ${specId} --json` },
|
|
164
|
+
{ name: `Update epic AND align all tickets to "${specId}"`, value: 'align_all', command: `prlt epic spec ${epicId} ${specId} --align-tickets --json` },
|
|
165
|
+
{ name: 'Cancel', value: 'cancel', command: '' },
|
|
156
166
|
];
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'epic spec' } : null;
|
|
168
|
+
const result = await this.prompt([{
|
|
169
|
+
type: 'list',
|
|
170
|
+
name: 'action',
|
|
171
|
+
message: `${ticketsWithDifferentSpec.length} ticket(s) in this epic have different specs. How to handle spec mismatch?`,
|
|
172
|
+
choices: mismatchChoices,
|
|
173
|
+
}], jsonModeConfig);
|
|
174
|
+
action = result.action;
|
|
163
175
|
}
|
|
164
|
-
if (ticketsWithDifferentSpec.length > 5) {
|
|
165
|
-
this.log(styles.muted(` ... and ${ticketsWithDifferentSpec.length - 5} more`));
|
|
166
|
-
}
|
|
167
|
-
const { action } = await inquirer.prompt([{
|
|
168
|
-
type: 'list',
|
|
169
|
-
name: 'action',
|
|
170
|
-
message: 'How to handle spec mismatch?',
|
|
171
|
-
choices: [
|
|
172
|
-
{ name: 'Update epic only (tickets keep their specs)', value: 'epic_only' },
|
|
173
|
-
{ name: `Update epic AND align all tickets to "${specId}"`, value: 'align_all' },
|
|
174
|
-
{ name: 'Cancel', value: 'cancel' },
|
|
175
|
-
],
|
|
176
|
-
}]);
|
|
177
176
|
if (action === 'cancel') {
|
|
178
177
|
return;
|
|
179
178
|
}
|
|
@@ -12,6 +12,8 @@ export default class EpicTicket extends PMOCommand {
|
|
|
12
12
|
unlink: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
13
|
spec: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
14
|
'unlink-spec': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
reconcile: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
|
+
'inherit-spec': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
17
|
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
16
18
|
};
|
|
17
19
|
execute(): Promise<void>;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
|
-
import { shouldOutputJson,
|
|
4
|
+
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
5
|
export default class EpicTicket extends PMOCommand {
|
|
7
6
|
static description = 'Assign tickets to an epic, or link epic to a spec (parent-child)';
|
|
8
7
|
static examples = [
|
|
@@ -45,6 +44,14 @@ export default class EpicTicket extends PMOCommand {
|
|
|
45
44
|
description: 'Remove spec link from epic',
|
|
46
45
|
default: false,
|
|
47
46
|
}),
|
|
47
|
+
reconcile: Flags.string({
|
|
48
|
+
description: 'How to handle spec mismatch: keep (keep ticket spec), epic (use epic spec), skip',
|
|
49
|
+
options: ['keep', 'epic', 'skip'],
|
|
50
|
+
}),
|
|
51
|
+
'inherit-spec': Flags.boolean({
|
|
52
|
+
description: 'Inherit spec from epic when ticket has no spec',
|
|
53
|
+
allowNo: true,
|
|
54
|
+
}),
|
|
48
55
|
};
|
|
49
56
|
async execute() {
|
|
50
57
|
const { args, flags, argv } = await this.parse(EpicTicket);
|
|
@@ -100,18 +107,15 @@ export default class EpicTicket extends PMOCommand {
|
|
|
100
107
|
const epicChoices = epics.map(e => ({
|
|
101
108
|
name: `${e.id} ${e.title} (${e.status}) [${ticketCounts.get(e.id) || 0} tickets]`,
|
|
102
109
|
value: e.id,
|
|
110
|
+
command: `prlt epic ticket ${e.id} --json`,
|
|
103
111
|
}));
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
outputPromptAsJson(buildPromptConfig('list', 'id', 'Select epic to link tickets to:', epicChoices), createMetadata('epic ticket', flags));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
const { selected } = await inquirer.prompt([{
|
|
112
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'epic ticket' } : null;
|
|
113
|
+
const { selected } = await this.prompt([{
|
|
110
114
|
type: 'list',
|
|
111
115
|
name: 'selected',
|
|
112
116
|
message: 'Select epic to link tickets to:',
|
|
113
117
|
choices: epicChoices,
|
|
114
|
-
}]);
|
|
118
|
+
}], jsonModeConfig);
|
|
115
119
|
epicId = selected;
|
|
116
120
|
}
|
|
117
121
|
// Validate epic exists
|
|
@@ -169,19 +173,16 @@ export default class EpicTicket extends PMOCommand {
|
|
|
169
173
|
name: `${t.id} - ${t.title} [${epicLabel}]`,
|
|
170
174
|
value: t.id,
|
|
171
175
|
checked: false,
|
|
176
|
+
command: `prlt epic ticket ${epicId} ${t.id} --json`,
|
|
172
177
|
};
|
|
173
178
|
});
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
outputPromptAsJson(buildPromptConfig('checkbox', 'tickets', `Select tickets to ${flags.unlink ? 'unlink from' : 'link to'} ${epicId}:`, choices), createMetadata('epic ticket', flags));
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
const { selected } = await inquirer.prompt([{
|
|
179
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'epic ticket' } : null;
|
|
180
|
+
const { selected } = await this.prompt([{
|
|
180
181
|
type: 'checkbox',
|
|
181
182
|
name: 'selected',
|
|
182
183
|
message: `Select tickets to ${flags.unlink ? 'unlink from' : 'link to'} ${epicId}:`,
|
|
183
184
|
choices,
|
|
184
|
-
}]);
|
|
185
|
+
}], jsonModeConfig);
|
|
185
186
|
ticketIds = selected;
|
|
186
187
|
}
|
|
187
188
|
if (ticketIds.length === 0) {
|
|
@@ -227,19 +228,31 @@ export default class EpicTicket extends PMOCommand {
|
|
|
227
228
|
const ticketSpecId = ticket.specId;
|
|
228
229
|
const epicSpecId = epic.specId;
|
|
229
230
|
if (ticketSpecId && epicSpecId && ticketSpecId !== epicSpecId) {
|
|
230
|
-
// Both have specs but they differ -
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
231
|
+
// Both have specs but they differ - determine action
|
|
232
|
+
let action;
|
|
233
|
+
// Check if --reconcile flag was provided
|
|
234
|
+
if (flags.reconcile) {
|
|
235
|
+
action = flags.reconcile === 'keep' ? 'keep_ticket' : flags.reconcile === 'epic' ? 'use_epic' : 'skip';
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
if (!jsonMode) {
|
|
239
|
+
this.log(styles.warning(` ⚠️ Spec mismatch: ticket has "${ticketSpecId}", epic has "${epicSpecId}"`));
|
|
240
|
+
}
|
|
241
|
+
const specReconcileChoices = [
|
|
242
|
+
{ name: `Keep ticket spec (${ticketSpecId})`, value: 'keep_ticket', command: `prlt epic ticket ${epicId} ${ticketId} --reconcile keep --json` },
|
|
243
|
+
{ name: `Use epic spec (${epicSpecId})`, value: 'use_epic', command: `prlt epic ticket ${epicId} ${ticketId} --reconcile epic --json` },
|
|
244
|
+
{ name: 'Skip this ticket', value: 'skip', command: `prlt epic ticket ${epicId} ${ticketId} --reconcile skip --json` },
|
|
245
|
+
];
|
|
246
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'epic ticket' } : null;
|
|
247
|
+
// eslint-disable-next-line no-await-in-loop
|
|
248
|
+
const result = await this.prompt([{
|
|
249
|
+
type: 'list',
|
|
250
|
+
name: 'action',
|
|
251
|
+
message: `Spec mismatch for ${ticketId}: ticket has "${ticketSpecId}", epic has "${epicSpecId}". How to reconcile?`,
|
|
252
|
+
choices: specReconcileChoices,
|
|
253
|
+
}], jsonModeConfig);
|
|
254
|
+
action = result.action;
|
|
255
|
+
}
|
|
243
256
|
if (action === 'skip') {
|
|
244
257
|
this.log(styles.muted(` Skipping ${ticketId}`));
|
|
245
258
|
continue;
|
|
@@ -255,14 +268,27 @@ export default class EpicTicket extends PMOCommand {
|
|
|
255
268
|
}
|
|
256
269
|
}
|
|
257
270
|
else if (!ticketSpecId && epicSpecId) {
|
|
258
|
-
// Ticket has no spec but epic does -
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
271
|
+
// Ticket has no spec but epic does - determine if should inherit
|
|
272
|
+
let inherit;
|
|
273
|
+
// Check if --inherit-spec flag was provided
|
|
274
|
+
if (flags['inherit-spec'] !== undefined) {
|
|
275
|
+
inherit = flags['inherit-spec'];
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const inheritChoices = [
|
|
279
|
+
{ name: 'Yes', value: true, command: `prlt epic ticket ${epicId} ${ticketId} --inherit-spec --json` },
|
|
280
|
+
{ name: 'No', value: false, command: `prlt epic ticket ${epicId} ${ticketId} --no-inherit-spec --json` },
|
|
281
|
+
];
|
|
282
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'epic ticket' } : null;
|
|
283
|
+
// eslint-disable-next-line no-await-in-loop
|
|
284
|
+
const result = await this.prompt([{
|
|
285
|
+
type: 'list',
|
|
286
|
+
name: 'inherit',
|
|
287
|
+
message: `${ticketId} has no spec. Inherit epic's spec "${epicSpecId}"?`,
|
|
288
|
+
choices: inheritChoices,
|
|
289
|
+
}], jsonModeConfig);
|
|
290
|
+
inherit = result.inherit;
|
|
291
|
+
}
|
|
266
292
|
if (inherit) {
|
|
267
293
|
db.prepare(`
|
|
268
294
|
UPDATE pmo_tickets
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Feedback extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
action: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { isMachineOutput, } from '../../lib/prompt-json.js';
|
|
3
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
4
|
+
import { FlagResolver } from '../../lib/flags/index.js';
|
|
5
|
+
export default class Feedback extends Command {
|
|
6
|
+
static description = 'Interactive menu for feedback and issue operations';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %>',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --action submit',
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
...machineOutputFlags,
|
|
13
|
+
action: Flags.string({
|
|
14
|
+
char: 'a',
|
|
15
|
+
description: 'Action to perform (submit, list, view)',
|
|
16
|
+
options: ['submit', 'list', 'view'],
|
|
17
|
+
}),
|
|
18
|
+
};
|
|
19
|
+
async run() {
|
|
20
|
+
const { flags } = await this.parse(Feedback);
|
|
21
|
+
// Check if JSON output mode is active
|
|
22
|
+
const jsonMode = isMachineOutput(flags);
|
|
23
|
+
// Use FlagResolver for action selection
|
|
24
|
+
const resolver = new FlagResolver({
|
|
25
|
+
commandName: 'feedback',
|
|
26
|
+
baseCommand: 'prlt feedback',
|
|
27
|
+
jsonMode,
|
|
28
|
+
flags: { action: flags.action },
|
|
29
|
+
});
|
|
30
|
+
resolver.addPrompt({
|
|
31
|
+
flagName: 'action',
|
|
32
|
+
type: 'list',
|
|
33
|
+
message: 'Feedback & Issues - What would you like to do?',
|
|
34
|
+
choices: () => [
|
|
35
|
+
{ name: 'Submit feedback', value: 'submit', command: 'prlt feedback submit --json' },
|
|
36
|
+
{ name: 'View my submissions', value: 'list', command: 'prlt feedback list --json' },
|
|
37
|
+
{ name: 'Browse issues', value: 'view', command: 'prlt feedback list --json' },
|
|
38
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
39
|
+
],
|
|
40
|
+
when: (ctx) => !ctx.flags.action,
|
|
41
|
+
skipAutoCommand: true,
|
|
42
|
+
});
|
|
43
|
+
const resolved = await resolver.resolve();
|
|
44
|
+
if (!resolved.action || resolved.action === 'cancel') {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
// Run the selected subcommand
|
|
48
|
+
switch (resolved.action) {
|
|
49
|
+
case 'submit':
|
|
50
|
+
await this.config.runCommand('feedback:submit', []);
|
|
51
|
+
break;
|
|
52
|
+
case 'list':
|
|
53
|
+
await this.config.runCommand('feedback:list', []);
|
|
54
|
+
break;
|
|
55
|
+
case 'view':
|
|
56
|
+
await this.config.runCommand('feedback:list', []);
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class FeedbackList extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
category: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
state: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { styles } from '../../lib/styles.js';
|
|
5
|
+
import { isGHInstalled, isGHAuthenticated } from '../../lib/pr/index.js';
|
|
6
|
+
import { isMachineOutput, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
7
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
8
|
+
// Target repository for feedback issues
|
|
9
|
+
const FEEDBACK_REPO = 'chrismcdermut/proletariat';
|
|
10
|
+
// Category to GitHub label mapping
|
|
11
|
+
const CATEGORY_LABELS = {
|
|
12
|
+
bug: 'bug',
|
|
13
|
+
feature: 'enhancement',
|
|
14
|
+
general: 'feedback',
|
|
15
|
+
};
|
|
16
|
+
export default class FeedbackList extends Command {
|
|
17
|
+
static description = 'List recent feedback issues from the repository';
|
|
18
|
+
static examples = [
|
|
19
|
+
'<%= config.bin %> <%= command.id %>',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> --category bug',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> --state closed --limit 10',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
23
|
+
];
|
|
24
|
+
static flags = {
|
|
25
|
+
...machineOutputFlags,
|
|
26
|
+
category: Flags.string({
|
|
27
|
+
char: 'c',
|
|
28
|
+
description: 'Filter by category (bug, feature, general)',
|
|
29
|
+
options: ['bug', 'feature', 'general'],
|
|
30
|
+
}),
|
|
31
|
+
state: Flags.string({
|
|
32
|
+
char: 's',
|
|
33
|
+
description: 'Filter by state',
|
|
34
|
+
options: ['open', 'closed', 'all'],
|
|
35
|
+
default: 'open',
|
|
36
|
+
}),
|
|
37
|
+
limit: Flags.integer({
|
|
38
|
+
char: 'l',
|
|
39
|
+
description: 'Maximum number of issues to show',
|
|
40
|
+
default: 20,
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
async run() {
|
|
44
|
+
const { flags } = await this.parse(FeedbackList);
|
|
45
|
+
const jsonMode = isMachineOutput(flags);
|
|
46
|
+
// Helper to handle errors in JSON mode
|
|
47
|
+
const handleError = (code, message) => {
|
|
48
|
+
if (jsonMode) {
|
|
49
|
+
outputErrorAsJson(code, message, createMetadata('feedback list', flags));
|
|
50
|
+
}
|
|
51
|
+
this.error(message);
|
|
52
|
+
};
|
|
53
|
+
// Check if gh CLI is installed
|
|
54
|
+
if (!isGHInstalled()) {
|
|
55
|
+
return handleError('GH_NOT_INSTALLED', 'GitHub CLI (gh) is not installed. Install it with: brew install gh');
|
|
56
|
+
}
|
|
57
|
+
// Check if gh is authenticated
|
|
58
|
+
if (!isGHAuthenticated()) {
|
|
59
|
+
return handleError('GH_NOT_AUTHENTICATED', 'GitHub CLI is not authenticated. Run: gh auth login');
|
|
60
|
+
}
|
|
61
|
+
// Build the gh issue list command
|
|
62
|
+
const args = [
|
|
63
|
+
'issue', 'list',
|
|
64
|
+
'--repo', FEEDBACK_REPO,
|
|
65
|
+
'--limit', String(flags.limit),
|
|
66
|
+
'--json', 'number,title,state,labels,createdAt,author',
|
|
67
|
+
];
|
|
68
|
+
// Add state filter
|
|
69
|
+
if (flags.state && flags.state !== 'all') {
|
|
70
|
+
args.push('--state', flags.state);
|
|
71
|
+
}
|
|
72
|
+
// Add label filter for category
|
|
73
|
+
if (flags.category) {
|
|
74
|
+
const label = CATEGORY_LABELS[flags.category];
|
|
75
|
+
if (label) {
|
|
76
|
+
args.push('--label', label);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const result = execSync(`gh ${args.join(' ')}`, {
|
|
81
|
+
encoding: 'utf-8',
|
|
82
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
83
|
+
}).trim();
|
|
84
|
+
const issues = JSON.parse(result || '[]');
|
|
85
|
+
if (jsonMode) {
|
|
86
|
+
outputSuccessAsJson({
|
|
87
|
+
issues: issues.map(issue => ({
|
|
88
|
+
number: issue.number,
|
|
89
|
+
title: issue.title,
|
|
90
|
+
state: issue.state,
|
|
91
|
+
labels: issue.labels.map(l => l.name),
|
|
92
|
+
createdAt: issue.createdAt,
|
|
93
|
+
author: issue.author.login,
|
|
94
|
+
url: `https://github.com/${FEEDBACK_REPO}/issues/${issue.number}`,
|
|
95
|
+
})),
|
|
96
|
+
count: issues.length,
|
|
97
|
+
repository: FEEDBACK_REPO,
|
|
98
|
+
filters: {
|
|
99
|
+
category: flags.category,
|
|
100
|
+
state: flags.state,
|
|
101
|
+
limit: flags.limit,
|
|
102
|
+
},
|
|
103
|
+
}, createMetadata('feedback list', flags));
|
|
104
|
+
}
|
|
105
|
+
// Display issues in human-friendly format
|
|
106
|
+
if (issues.length === 0) {
|
|
107
|
+
this.log(styles.muted('\nNo issues found matching your criteria.\n'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
this.log(`\n${styles.header('Feedback Issues')} ${styles.muted(`(${FEEDBACK_REPO})`)}\n`);
|
|
111
|
+
for (const issue of issues) {
|
|
112
|
+
const stateIcon = issue.state === 'open' ? chalk.green('●') : chalk.red('●');
|
|
113
|
+
const labels = issue.labels.map(l => chalk.cyan(`[${l.name}]`)).join(' ');
|
|
114
|
+
const date = new Date(issue.createdAt).toLocaleDateString();
|
|
115
|
+
this.log(`${stateIcon} ${chalk.bold(`#${issue.number}`)} ${issue.title}`);
|
|
116
|
+
this.log(` ${labels} ${styles.muted(`by ${issue.author.login} on ${date}`)}`);
|
|
117
|
+
this.log('');
|
|
118
|
+
}
|
|
119
|
+
this.log(styles.muted(`Showing ${issues.length} issue(s). Use 'prlt feedback view <number>' to see details.\n`));
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error listing issues';
|
|
123
|
+
return handleError('ISSUE_LIST_FAILED', `Failed to list issues: ${errorMessage}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class FeedbackSubmit extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
title: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
body: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
category: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Collect diagnostic information to include in the issue
|
|
14
|
+
*/
|
|
15
|
+
private collectDiagnostics;
|
|
16
|
+
}
|