@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.
Files changed (75) hide show
  1. package/dist/commands/action/create.js +3 -3
  2. package/dist/commands/action/update.js +3 -3
  3. package/dist/commands/epic/activate.js +9 -17
  4. package/dist/commands/epic/archive.js +13 -24
  5. package/dist/commands/epic/create.js +7 -6
  6. package/dist/commands/epic/move.js +28 -47
  7. package/dist/commands/epic/progress.js +10 -14
  8. package/dist/commands/epic/project.js +42 -59
  9. package/dist/commands/epic/reorder.js +25 -30
  10. package/dist/commands/epic/spec.d.ts +1 -0
  11. package/dist/commands/epic/spec.js +39 -40
  12. package/dist/commands/epic/ticket.d.ts +2 -0
  13. package/dist/commands/epic/ticket.js +63 -37
  14. package/dist/commands/feedback/index.d.ts +10 -0
  15. package/dist/commands/feedback/index.js +60 -0
  16. package/dist/commands/feedback/list.d.ts +12 -0
  17. package/dist/commands/feedback/list.js +126 -0
  18. package/dist/commands/feedback/submit.d.ts +16 -0
  19. package/dist/commands/feedback/submit.js +220 -0
  20. package/dist/commands/feedback/view.d.ts +15 -0
  21. package/dist/commands/feedback/view.js +109 -0
  22. package/dist/commands/gh/index.js +4 -0
  23. package/dist/commands/repo/create.d.ts +38 -0
  24. package/dist/commands/repo/create.js +283 -0
  25. package/dist/commands/repo/index.js +7 -0
  26. package/dist/commands/roadmap/add-project.js +9 -22
  27. package/dist/commands/roadmap/create.d.ts +0 -1
  28. package/dist/commands/roadmap/create.js +46 -40
  29. package/dist/commands/roadmap/delete.js +10 -24
  30. package/dist/commands/roadmap/generate.d.ts +1 -0
  31. package/dist/commands/roadmap/generate.js +21 -22
  32. package/dist/commands/roadmap/remove-project.js +14 -34
  33. package/dist/commands/roadmap/reorder.js +19 -26
  34. package/dist/commands/roadmap/update.js +27 -26
  35. package/dist/commands/roadmap/view.js +5 -12
  36. package/dist/commands/session/attach.d.ts +1 -8
  37. package/dist/commands/session/attach.js +93 -59
  38. package/dist/commands/session/list.d.ts +0 -8
  39. package/dist/commands/session/list.js +130 -81
  40. package/dist/commands/spec/create.js +1 -1
  41. package/dist/commands/spec/edit.js +63 -33
  42. package/dist/commands/support/book.d.ts +10 -0
  43. package/dist/commands/support/book.js +54 -0
  44. package/dist/commands/support/discord.d.ts +10 -0
  45. package/dist/commands/support/discord.js +54 -0
  46. package/dist/commands/support/docs.d.ts +10 -0
  47. package/dist/commands/support/docs.js +54 -0
  48. package/dist/commands/support/index.d.ts +19 -0
  49. package/dist/commands/support/index.js +81 -0
  50. package/dist/commands/support/issues.d.ts +11 -0
  51. package/dist/commands/support/issues.js +77 -0
  52. package/dist/commands/support/logs.d.ts +18 -0
  53. package/dist/commands/support/logs.js +247 -0
  54. package/dist/commands/ticket/create.js +21 -13
  55. package/dist/commands/ticket/edit.js +44 -13
  56. package/dist/commands/ticket/move.d.ts +7 -0
  57. package/dist/commands/ticket/move.js +132 -0
  58. package/dist/commands/work/spawn.d.ts +1 -0
  59. package/dist/commands/work/spawn.js +71 -7
  60. package/dist/commands/work/start.js +6 -0
  61. package/dist/lib/execution/runners.js +21 -17
  62. package/dist/lib/execution/session-utils.d.ts +60 -0
  63. package/dist/lib/execution/session-utils.js +162 -0
  64. package/dist/lib/execution/spawner.d.ts +2 -0
  65. package/dist/lib/execution/spawner.js +42 -0
  66. package/dist/lib/flags/resolver.d.ts +2 -2
  67. package/dist/lib/flags/resolver.js +15 -0
  68. package/dist/lib/init/index.js +18 -0
  69. package/dist/lib/multiline-input.d.ts +63 -0
  70. package/dist/lib/multiline-input.js +360 -0
  71. package/dist/lib/prompt-json.d.ts +5 -5
  72. package/dist/lib/repos/git.d.ts +7 -0
  73. package/dist/lib/repos/git.js +20 -0
  74. package/oclif.manifest.json +2206 -1607
  75. 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, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
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
- // In JSON mode, output epic selection prompt
72
- if (jsonMode) {
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
- // In JSON mode, output spec selection prompt
119
- if (jsonMode) {
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
- // In JSON mode, output spec mismatch handling prompt
151
- if (jsonMode) {
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
- outputPromptAsJson(buildPromptConfig('list', 'specMismatchAction', `${ticketsWithDifferentSpec.length} ticket(s) in this epic have different specs. How to handle spec mismatch?`, mismatchChoices), createMetadata('epic spec', flags));
158
- return;
159
- }
160
- this.log(styles.warning(`\n⚠️ ${ticketsWithDifferentSpec.length} ticket(s) in this epic have different specs:`));
161
- for (const t of ticketsWithDifferentSpec.slice(0, 5)) {
162
- this.log(styles.muted(` - ${t.id}: spec "${t.specId}"`));
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, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
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
- // In JSON mode, output epic selection prompt
105
- if (jsonMode) {
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
- // In JSON mode, output ticket selection prompt
175
- if (jsonMode) {
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 - warn user
231
- this.log(styles.warning(` ⚠️ Spec mismatch: ticket has "${ticketSpecId}", epic has "${epicSpecId}"`));
232
- // eslint-disable-next-line no-await-in-loop
233
- const { action } = await inquirer.prompt([{
234
- type: 'list',
235
- name: 'action',
236
- message: `How to reconcile spec for ${ticketId}?`,
237
- choices: [
238
- { name: `Keep ticket spec (${ticketSpecId})`, value: 'keep_ticket' },
239
- { name: `Use epic spec (${epicSpecId})`, value: 'use_epic' },
240
- { name: 'Skip this ticket', value: 'skip' },
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 - offer to inherit
259
- // eslint-disable-next-line no-await-in-loop
260
- const { inherit } = await inquirer.prompt([{
261
- type: 'confirm',
262
- name: 'inherit',
263
- message: `${ticketId} has no spec. Inherit epic's spec "${epicSpecId}"?`,
264
- default: true,
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
+ }