@proletariat/cli 0.3.24 → 0.3.26
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/index.js +2 -2
- package/dist/commands/action/update.js +3 -3
- package/dist/commands/agent/auth.js +1 -1
- package/dist/commands/agent/cleanup.js +6 -6
- package/dist/commands/agent/discover.js +1 -1
- package/dist/commands/agent/remove.js +4 -4
- package/dist/commands/autocomplete/setup.d.ts +2 -2
- package/dist/commands/autocomplete/setup.js +5 -5
- package/dist/commands/branch/create.js +31 -30
- package/dist/commands/category/create.js +4 -5
- package/dist/commands/category/delete.js +2 -3
- package/dist/commands/category/rename.js +2 -3
- package/dist/commands/claude.d.ts +2 -8
- package/dist/commands/claude.js +26 -26
- package/dist/commands/commit.d.ts +2 -8
- package/dist/commands/commit.js +4 -26
- package/dist/commands/config/index.d.ts +2 -10
- package/dist/commands/config/index.js +8 -34
- package/dist/commands/docker/index.d.ts +2 -2
- package/dist/commands/docker/index.js +8 -8
- 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/delete.js +4 -5
- 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/link/index.js +2 -2
- package/dist/commands/pmo/init.d.ts +2 -2
- package/dist/commands/pmo/init.js +7 -7
- package/dist/commands/project/spec.js +6 -6
- 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/health.d.ts +29 -0
- package/dist/commands/session/health.js +495 -0
- package/dist/commands/session/index.js +4 -0
- 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 +64 -35
- package/dist/commands/staff/add.d.ts +2 -2
- package/dist/commands/staff/add.js +15 -14
- package/dist/commands/staff/index.js +2 -2
- package/dist/commands/staff/remove.js +4 -4
- package/dist/commands/status/index.js +6 -7
- 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/template/apply.js +10 -11
- package/dist/commands/template/create.js +18 -17
- package/dist/commands/template/index.d.ts +2 -2
- package/dist/commands/template/index.js +6 -6
- package/dist/commands/template/save.js +8 -7
- package/dist/commands/template/update.js +6 -7
- package/dist/commands/terminal/title.d.ts +2 -26
- package/dist/commands/terminal/title.js +4 -33
- package/dist/commands/theme/index.d.ts +2 -2
- package/dist/commands/theme/index.js +19 -18
- package/dist/commands/theme/set.d.ts +2 -2
- package/dist/commands/theme/set.js +5 -5
- package/dist/commands/ticket/create.js +52 -26
- package/dist/commands/ticket/delete.js +15 -13
- package/dist/commands/ticket/edit.js +59 -20
- package/dist/commands/ticket/epic.js +12 -10
- package/dist/commands/ticket/move.d.ts +7 -0
- package/dist/commands/ticket/move.js +132 -0
- package/dist/commands/ticket/project.js +11 -9
- package/dist/commands/ticket/reassign.js +23 -19
- package/dist/commands/ticket/spec.js +7 -5
- package/dist/commands/ticket/update.js +55 -53
- package/dist/commands/whoami.js +1 -0
- package/dist/commands/work/ready.js +7 -7
- package/dist/commands/work/revise.js +13 -11
- package/dist/commands/work/spawn.d.ts +1 -0
- package/dist/commands/work/spawn.js +225 -64
- package/dist/commands/work/start.d.ts +1 -0
- package/dist/commands/work/start.js +301 -173
- package/dist/hooks/init.js +4 -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/pr/index.d.ts +4 -0
- package/dist/lib/pr/index.js +32 -14
- package/dist/lib/prompt-command.d.ts +3 -0
- package/dist/lib/prompt-json.d.ts +77 -6
- package/dist/lib/prompt-json.js +46 -0
- package/dist/lib/repos/git.d.ts +7 -0
- package/dist/lib/repos/git.js +20 -0
- package/oclif.manifest.json +2913 -2246
- package/package.json +1 -1
|
@@ -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 { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
|
+
export default class FeedbackSubmit extends PromptCommand {
|
|
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
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import * as os from 'node:os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
6
|
+
import { styles } from '../../lib/styles.js';
|
|
7
|
+
import { isGHInstalled, isGHAuthenticated } from '../../lib/pr/index.js';
|
|
8
|
+
import { isMachineOutput, outputSuccessAsJson, outputErrorAsJson, outputPromptAsJson, buildPromptConfig, createMetadata, } from '../../lib/prompt-json.js';
|
|
9
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
10
|
+
// Target repository for feedback issues
|
|
11
|
+
const FEEDBACK_REPO = 'chrismcdermut/proletariat';
|
|
12
|
+
// Category to GitHub label mapping
|
|
13
|
+
const CATEGORY_LABELS = {
|
|
14
|
+
bug: 'bug',
|
|
15
|
+
feature: 'enhancement',
|
|
16
|
+
general: 'feedback',
|
|
17
|
+
};
|
|
18
|
+
export default class FeedbackSubmit extends PromptCommand {
|
|
19
|
+
static description = 'Submit feedback, bug reports, or feature requests to GitHub Issues';
|
|
20
|
+
static examples = [
|
|
21
|
+
'<%= config.bin %> <%= command.id %>',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> --title "Bug in CLI" --body "Description here" --category bug',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
24
|
+
];
|
|
25
|
+
static flags = {
|
|
26
|
+
...machineOutputFlags,
|
|
27
|
+
title: Flags.string({
|
|
28
|
+
char: 't',
|
|
29
|
+
description: 'Issue title (one-liner) [required for non-interactive]',
|
|
30
|
+
}),
|
|
31
|
+
body: Flags.string({
|
|
32
|
+
char: 'b',
|
|
33
|
+
description: 'Issue description [required for non-interactive]',
|
|
34
|
+
}),
|
|
35
|
+
category: Flags.string({
|
|
36
|
+
char: 'c',
|
|
37
|
+
description: 'Feedback category',
|
|
38
|
+
options: ['bug', 'feature', 'general'],
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
41
|
+
async run() {
|
|
42
|
+
const { flags } = await this.parse(FeedbackSubmit);
|
|
43
|
+
const jsonMode = isMachineOutput(flags);
|
|
44
|
+
// Helper to handle errors in JSON mode
|
|
45
|
+
const handleError = (code, message) => {
|
|
46
|
+
if (jsonMode) {
|
|
47
|
+
outputErrorAsJson(code, message, createMetadata('feedback submit', flags));
|
|
48
|
+
}
|
|
49
|
+
this.error(message);
|
|
50
|
+
};
|
|
51
|
+
// Check if gh CLI is installed
|
|
52
|
+
if (!isGHInstalled()) {
|
|
53
|
+
return handleError('GH_NOT_INSTALLED', 'GitHub CLI (gh) is not installed. Install it with: brew install gh');
|
|
54
|
+
}
|
|
55
|
+
// Check if gh is authenticated
|
|
56
|
+
if (!isGHAuthenticated()) {
|
|
57
|
+
return handleError('GH_NOT_AUTHENTICATED', 'GitHub CLI is not authenticated. Run: gh auth login');
|
|
58
|
+
}
|
|
59
|
+
// Collect category
|
|
60
|
+
let category = flags.category;
|
|
61
|
+
if (!category) {
|
|
62
|
+
const categoryChoices = [
|
|
63
|
+
{ name: 'Bug report', value: 'bug' },
|
|
64
|
+
{ name: 'Feature request', value: 'feature' },
|
|
65
|
+
{ name: 'General feedback', value: 'general' },
|
|
66
|
+
];
|
|
67
|
+
const categoryMessage = 'What type of feedback would you like to submit?';
|
|
68
|
+
if (jsonMode) {
|
|
69
|
+
outputPromptAsJson(buildPromptConfig('list', 'category', categoryMessage, categoryChoices), createMetadata('feedback submit', flags));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const { selectedCategory } = await this.prompt([{
|
|
73
|
+
type: 'list',
|
|
74
|
+
name: 'selectedCategory',
|
|
75
|
+
message: categoryMessage,
|
|
76
|
+
choices: categoryChoices,
|
|
77
|
+
}], null);
|
|
78
|
+
category = selectedCategory;
|
|
79
|
+
}
|
|
80
|
+
// Collect title
|
|
81
|
+
let title = flags.title;
|
|
82
|
+
if (!title) {
|
|
83
|
+
const titleMessage = 'Enter a brief title for your feedback:';
|
|
84
|
+
if (jsonMode) {
|
|
85
|
+
outputPromptAsJson(buildPromptConfig('input', 'title', titleMessage), createMetadata('feedback submit', { ...flags, category }));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const { inputTitle } = await this.prompt([{
|
|
89
|
+
type: 'input',
|
|
90
|
+
name: 'inputTitle',
|
|
91
|
+
message: titleMessage,
|
|
92
|
+
validate: (input) => {
|
|
93
|
+
if (!input.trim()) {
|
|
94
|
+
return 'Title is required';
|
|
95
|
+
}
|
|
96
|
+
if (input.length > 200) {
|
|
97
|
+
return 'Title must be less than 200 characters';
|
|
98
|
+
}
|
|
99
|
+
return true;
|
|
100
|
+
},
|
|
101
|
+
}], null);
|
|
102
|
+
title = inputTitle;
|
|
103
|
+
}
|
|
104
|
+
// Collect body/description
|
|
105
|
+
let body = flags.body;
|
|
106
|
+
if (!body) {
|
|
107
|
+
const bodyMessage = 'Describe your feedback in detail:';
|
|
108
|
+
if (jsonMode) {
|
|
109
|
+
outputPromptAsJson(buildPromptConfig('editor', 'body', bodyMessage), createMetadata('feedback submit', { ...flags, category, title }));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const { inputBody } = await this.prompt([{
|
|
113
|
+
type: 'editor',
|
|
114
|
+
name: 'inputBody',
|
|
115
|
+
message: bodyMessage,
|
|
116
|
+
validate: (input) => {
|
|
117
|
+
if (!input.trim()) {
|
|
118
|
+
return 'Description is required';
|
|
119
|
+
}
|
|
120
|
+
return true;
|
|
121
|
+
},
|
|
122
|
+
}], null);
|
|
123
|
+
body = inputBody;
|
|
124
|
+
}
|
|
125
|
+
// Build diagnostics section
|
|
126
|
+
const diagnostics = this.collectDiagnostics();
|
|
127
|
+
// Build full issue body with diagnostics
|
|
128
|
+
const fullBody = `${body}\n\n---\n\n## Diagnostics\n\n${diagnostics}`;
|
|
129
|
+
// Get label for category
|
|
130
|
+
const label = CATEGORY_LABELS[category] || 'feedback';
|
|
131
|
+
// Create the GitHub issue
|
|
132
|
+
if (!jsonMode) {
|
|
133
|
+
this.log(styles.muted('\nCreating issue on GitHub...\n'));
|
|
134
|
+
}
|
|
135
|
+
// Escape title for shell
|
|
136
|
+
const escapedTitle = title.replace(/"/g, '\\"');
|
|
137
|
+
let usedLabel = label;
|
|
138
|
+
let result;
|
|
139
|
+
try {
|
|
140
|
+
// Try with label first
|
|
141
|
+
result = execSync(`gh issue create --repo "${FEEDBACK_REPO}" --title "${escapedTitle}" --body-file - --label "${label}"`, {
|
|
142
|
+
encoding: 'utf-8',
|
|
143
|
+
input: fullBody,
|
|
144
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
145
|
+
}).trim();
|
|
146
|
+
}
|
|
147
|
+
catch (labelError) {
|
|
148
|
+
// If label doesn't exist, try without label
|
|
149
|
+
const labelErrorMessage = labelError instanceof Error ? labelError.message : '';
|
|
150
|
+
if (labelErrorMessage.includes('not found') && labelErrorMessage.includes('label')) {
|
|
151
|
+
try {
|
|
152
|
+
usedLabel = null;
|
|
153
|
+
result = execSync(`gh issue create --repo "${FEEDBACK_REPO}" --title "${escapedTitle}" --body-file -`, {
|
|
154
|
+
encoding: 'utf-8',
|
|
155
|
+
input: fullBody,
|
|
156
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
157
|
+
}).trim();
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error creating issue';
|
|
161
|
+
return handleError('ISSUE_CREATE_FAILED', `Failed to create issue: ${errorMessage}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const errorMessage = labelError instanceof Error ? labelError.message : 'Unknown error creating issue';
|
|
166
|
+
return handleError('ISSUE_CREATE_FAILED', `Failed to create issue: ${errorMessage}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// Parse issue URL from result
|
|
170
|
+
const issueUrl = result;
|
|
171
|
+
const issueMatch = issueUrl.match(/\/issues\/(\d+)/);
|
|
172
|
+
const issueNumber = issueMatch ? parseInt(issueMatch[1], 10) : undefined;
|
|
173
|
+
if (jsonMode) {
|
|
174
|
+
outputSuccessAsJson({
|
|
175
|
+
issueUrl,
|
|
176
|
+
issueNumber,
|
|
177
|
+
title: title,
|
|
178
|
+
category: category,
|
|
179
|
+
label: usedLabel,
|
|
180
|
+
repository: FEEDBACK_REPO,
|
|
181
|
+
}, createMetadata('feedback submit', flags));
|
|
182
|
+
}
|
|
183
|
+
this.log(chalk.green(' ✓ Issue created successfully!\n'));
|
|
184
|
+
this.log(`${styles.header('Issue URL:')} ${chalk.cyan(issueUrl)}`);
|
|
185
|
+
if (issueNumber) {
|
|
186
|
+
this.log(`${styles.header('Issue #:')} ${issueNumber}`);
|
|
187
|
+
}
|
|
188
|
+
this.log(`${styles.header('Category:')} ${category}`);
|
|
189
|
+
if (usedLabel) {
|
|
190
|
+
this.log(`${styles.header('Label:')} ${usedLabel}\n`);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
this.log(styles.muted(`(Label '${label}' not found in repository)\n`));
|
|
194
|
+
}
|
|
195
|
+
this.log(styles.muted('Thank you for your feedback!'));
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Collect diagnostic information to include in the issue
|
|
199
|
+
*/
|
|
200
|
+
collectDiagnostics() {
|
|
201
|
+
const lines = [];
|
|
202
|
+
// CLI version
|
|
203
|
+
lines.push(`- **CLI Version:** ${this.config.version}`);
|
|
204
|
+
// Node version
|
|
205
|
+
lines.push(`- **Node Version:** ${process.version}`);
|
|
206
|
+
// OS info
|
|
207
|
+
lines.push(`- **OS:** ${process.platform}`);
|
|
208
|
+
lines.push(`- **OS Version:** ${os.release()}`);
|
|
209
|
+
// Try to get workspace name if available
|
|
210
|
+
try {
|
|
211
|
+
const cwd = process.cwd();
|
|
212
|
+
const workspaceName = cwd.split('/').pop() || 'unknown';
|
|
213
|
+
lines.push(`- **Working Directory:** ${workspaceName}`);
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
// Ignore errors getting workspace name
|
|
217
|
+
}
|
|
218
|
+
return lines.join('\n');
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class FeedbackView extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
};
|
|
8
|
+
static args: {
|
|
9
|
+
number: import("@oclif/core/interfaces").Arg<number, {
|
|
10
|
+
max?: number;
|
|
11
|
+
min?: number;
|
|
12
|
+
}>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { Command, Args } 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
|
+
export default class FeedbackView extends Command {
|
|
11
|
+
static description = 'View details of a specific feedback issue';
|
|
12
|
+
static examples = [
|
|
13
|
+
'<%= config.bin %> <%= command.id %> 123',
|
|
14
|
+
'<%= config.bin %> <%= command.id %> 123 --json',
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
...machineOutputFlags,
|
|
18
|
+
};
|
|
19
|
+
static args = {
|
|
20
|
+
number: Args.integer({
|
|
21
|
+
description: 'Issue number to view',
|
|
22
|
+
required: true,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
async run() {
|
|
26
|
+
const { args, flags } = await this.parse(FeedbackView);
|
|
27
|
+
const jsonMode = isMachineOutput(flags);
|
|
28
|
+
const issueNumber = args.number;
|
|
29
|
+
// Helper to handle errors in JSON mode
|
|
30
|
+
const handleError = (code, message) => {
|
|
31
|
+
if (jsonMode) {
|
|
32
|
+
outputErrorAsJson(code, message, createMetadata('feedback view', flags));
|
|
33
|
+
}
|
|
34
|
+
this.error(message);
|
|
35
|
+
};
|
|
36
|
+
// Check if gh CLI is installed
|
|
37
|
+
if (!isGHInstalled()) {
|
|
38
|
+
return handleError('GH_NOT_INSTALLED', 'GitHub CLI (gh) is not installed. Install it with: brew install gh');
|
|
39
|
+
}
|
|
40
|
+
// Check if gh is authenticated
|
|
41
|
+
if (!isGHAuthenticated()) {
|
|
42
|
+
return handleError('GH_NOT_AUTHENTICATED', 'GitHub CLI is not authenticated. Run: gh auth login');
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
// Get issue details
|
|
46
|
+
const result = execSync(`gh issue view ${issueNumber} --repo "${FEEDBACK_REPO}" --json number,title,body,state,labels,createdAt,updatedAt,author,comments`, {
|
|
47
|
+
encoding: 'utf-8',
|
|
48
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
49
|
+
}).trim();
|
|
50
|
+
const issue = JSON.parse(result);
|
|
51
|
+
if (jsonMode) {
|
|
52
|
+
outputSuccessAsJson({
|
|
53
|
+
number: issue.number,
|
|
54
|
+
title: issue.title,
|
|
55
|
+
body: issue.body,
|
|
56
|
+
state: issue.state,
|
|
57
|
+
labels: issue.labels.map(l => l.name),
|
|
58
|
+
createdAt: issue.createdAt,
|
|
59
|
+
updatedAt: issue.updatedAt,
|
|
60
|
+
author: issue.author.login,
|
|
61
|
+
comments: issue.comments.map(c => ({
|
|
62
|
+
author: c.author.login,
|
|
63
|
+
body: c.body,
|
|
64
|
+
createdAt: c.createdAt,
|
|
65
|
+
})),
|
|
66
|
+
url: `https://github.com/${FEEDBACK_REPO}/issues/${issue.number}`,
|
|
67
|
+
repository: FEEDBACK_REPO,
|
|
68
|
+
}, createMetadata('feedback view', flags));
|
|
69
|
+
}
|
|
70
|
+
// Display issue in human-friendly format
|
|
71
|
+
const stateIcon = issue.state === 'OPEN' ? chalk.green('OPEN') : chalk.red('CLOSED');
|
|
72
|
+
const labels = issue.labels.map(l => chalk.cyan(`[${l.name}]`)).join(' ');
|
|
73
|
+
const createdDate = new Date(issue.createdAt).toLocaleString();
|
|
74
|
+
const updatedDate = new Date(issue.updatedAt).toLocaleString();
|
|
75
|
+
this.log(`\n${styles.header('Issue #')}${chalk.bold(String(issue.number))} ${stateIcon}\n`);
|
|
76
|
+
this.log(`${styles.header('Title:')} ${issue.title}`);
|
|
77
|
+
this.log(`${styles.header('Author:')} ${issue.author.login}`);
|
|
78
|
+
this.log(`${styles.header('Labels:')} ${labels || styles.muted('none')}`);
|
|
79
|
+
this.log(`${styles.header('Created:')} ${createdDate}`);
|
|
80
|
+
this.log(`${styles.header('Updated:')} ${updatedDate}`);
|
|
81
|
+
this.log(`${styles.header('URL:')} ${chalk.cyan(`https://github.com/${FEEDBACK_REPO}/issues/${issue.number}`)}`);
|
|
82
|
+
if (issue.body) {
|
|
83
|
+
this.log(`\n${styles.header('Description:')}`);
|
|
84
|
+
this.log(styles.muted('─'.repeat(50)));
|
|
85
|
+
this.log(issue.body);
|
|
86
|
+
this.log(styles.muted('─'.repeat(50)));
|
|
87
|
+
}
|
|
88
|
+
if (issue.comments && issue.comments.length > 0) {
|
|
89
|
+
this.log(`\n${styles.header('Comments:')} (${issue.comments.length})`);
|
|
90
|
+
this.log(styles.muted('─'.repeat(50)));
|
|
91
|
+
for (const comment of issue.comments) {
|
|
92
|
+
const commentDate = new Date(comment.createdAt).toLocaleString();
|
|
93
|
+
this.log(`\n${chalk.bold(comment.author.login)} ${styles.muted(`on ${commentDate}`)}`);
|
|
94
|
+
this.log(comment.body);
|
|
95
|
+
}
|
|
96
|
+
this.log(styles.muted('─'.repeat(50)));
|
|
97
|
+
}
|
|
98
|
+
this.log('');
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error viewing issue';
|
|
102
|
+
// Check if it's a "not found" error
|
|
103
|
+
if (errorMessage.includes('not found') || errorMessage.includes('Could not resolve')) {
|
|
104
|
+
return handleError('ISSUE_NOT_FOUND', `Issue #${issueNumber} not found in ${FEEDBACK_REPO}`);
|
|
105
|
+
}
|
|
106
|
+
return handleError('ISSUE_VIEW_FAILED', `Failed to view issue: ${errorMessage}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -32,6 +32,7 @@ export default class GH extends Command {
|
|
|
32
32
|
{ name: 'Check status', value: 'status', command: 'prlt gh status --json' },
|
|
33
33
|
{ name: 'Login to GitHub', value: 'login', command: 'prlt gh login --json' },
|
|
34
34
|
{ name: 'Show GH_TOKEN setup', value: 'token', command: 'prlt gh token --json' },
|
|
35
|
+
{ name: 'Create GitHub repository', value: 'repo-create', command: 'prlt repo create --json' },
|
|
35
36
|
],
|
|
36
37
|
skipAutoCommand: true, // Use our custom commands above
|
|
37
38
|
});
|
|
@@ -48,6 +49,9 @@ export default class GH extends Command {
|
|
|
48
49
|
case 'token':
|
|
49
50
|
await this.config.runCommand('gh token');
|
|
50
51
|
break;
|
|
52
|
+
case 'repo-create':
|
|
53
|
+
await this.config.runCommand('repo create');
|
|
54
|
+
break;
|
|
51
55
|
}
|
|
52
56
|
}
|
|
53
57
|
}
|
|
@@ -42,7 +42,7 @@ export default class Link extends PMOCommand {
|
|
|
42
42
|
outputPromptAsJson(buildPromptConfig('list', 'action', message, menuChoices), createMetadata('link', flags));
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
|
-
const { selected } = await
|
|
45
|
+
const { selected } = await this.prompt([{
|
|
46
46
|
type: 'list',
|
|
47
47
|
name: 'selected',
|
|
48
48
|
message,
|
|
@@ -51,7 +51,7 @@ export default class Link extends PMOCommand {
|
|
|
51
51
|
new inquirer.Separator(),
|
|
52
52
|
{ name: menuChoices[3].name, value: menuChoices[3].value }
|
|
53
53
|
]
|
|
54
|
-
}]);
|
|
54
|
+
}], null);
|
|
55
55
|
if (selected === 'cancel') {
|
|
56
56
|
this.log(styles.muted('\nCancelled.'));
|
|
57
57
|
return;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class PMOInit extends
|
|
1
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
|
+
export default class PMOInit extends PromptCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
-
import
|
|
6
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
7
7
|
import Database from 'better-sqlite3';
|
|
8
8
|
import { SQLiteStorage, getColumnsForTemplate, createPMO, promptForPMOLocation, promptForBoardTemplate, promptForBoardName, promptForCustomColumns, determinePMOPath, getPickerTemplates, } from '../../lib/pmo/index.js';
|
|
9
9
|
import { styles } from '../../lib/styles.js';
|
|
@@ -11,7 +11,7 @@ import { isGHInstalled, isGHAuthenticated, getGHUsername, isGHTokenInEnv } from
|
|
|
11
11
|
import { shouldOutputJson, outputPromptAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
12
12
|
// Build template options dynamically from shared definitions (picker templates + custom)
|
|
13
13
|
const PICKER_TEMPLATE_IDS = [...getPickerTemplates().map(t => t.id), 'custom'];
|
|
14
|
-
export default class PMOInit extends
|
|
14
|
+
export default class PMOInit extends PromptCommand {
|
|
15
15
|
static description = 'Initialize PMO (Project Management Org) in current directory or HQ';
|
|
16
16
|
static examples = [
|
|
17
17
|
'<%= config.bin %> <%= command.id %>',
|
|
@@ -268,13 +268,13 @@ export default class PMOInit extends Command {
|
|
|
268
268
|
this.log(chalk.gray(` Location: ${pmoPath}`));
|
|
269
269
|
this.log(chalk.gray(` Projects: ${projectCount}`));
|
|
270
270
|
this.log(chalk.gray(` Tickets: ${ticketCount}\n`));
|
|
271
|
-
const result = await
|
|
271
|
+
const result = await this.prompt([{
|
|
272
272
|
type: 'list',
|
|
273
273
|
name: 'action',
|
|
274
274
|
message: 'What would you like to do?',
|
|
275
275
|
choices: actionChoices,
|
|
276
276
|
default: 'cancel',
|
|
277
|
-
}]);
|
|
277
|
+
}], null);
|
|
278
278
|
action = result.action;
|
|
279
279
|
}
|
|
280
280
|
if (action === 'cancel') {
|
|
@@ -303,11 +303,11 @@ export default class PMOInit extends Command {
|
|
|
303
303
|
this.log(chalk.red(' • All tickets and boards'));
|
|
304
304
|
this.log(chalk.red(' • All specs and documentation'));
|
|
305
305
|
this.log(chalk.red(' • Database tables (pmo_*)\n'));
|
|
306
|
-
const result = await
|
|
306
|
+
const result = await this.prompt([{
|
|
307
307
|
type: 'input',
|
|
308
308
|
name: 'confirmation',
|
|
309
309
|
message: 'Type "delete pmo" to confirm:',
|
|
310
|
-
}]);
|
|
310
|
+
}], null);
|
|
311
311
|
confirmation = result.confirmation;
|
|
312
312
|
}
|
|
313
313
|
if (confirmation !== 'delete pmo') {
|
|
@@ -140,7 +140,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
140
140
|
}
|
|
141
141
|
}
|
|
142
142
|
// eslint-disable-next-line no-await-in-loop -- Interactive user prompt
|
|
143
|
-
const { action } = await
|
|
143
|
+
const { action } = await this.prompt([{
|
|
144
144
|
type: 'list',
|
|
145
145
|
name: 'action',
|
|
146
146
|
message: `Manage specs for ${projectId}:`,
|
|
@@ -150,7 +150,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
150
150
|
new inquirer.Separator(),
|
|
151
151
|
{ name: 'Done', value: 'done' },
|
|
152
152
|
],
|
|
153
|
-
}]);
|
|
153
|
+
}], null);
|
|
154
154
|
if (action === 'done') {
|
|
155
155
|
continueLoop = false;
|
|
156
156
|
continue;
|
|
@@ -165,7 +165,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
165
165
|
continue;
|
|
166
166
|
}
|
|
167
167
|
// eslint-disable-next-line no-await-in-loop -- User selection prompt
|
|
168
|
-
const { selected } = await
|
|
168
|
+
const { selected } = await this.prompt([{
|
|
169
169
|
type: 'checkbox',
|
|
170
170
|
name: 'selected',
|
|
171
171
|
message: 'Select specs to add:',
|
|
@@ -173,7 +173,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
173
173
|
name: `${s.id} - ${s.title} (${s.status})`,
|
|
174
174
|
value: s.id,
|
|
175
175
|
})),
|
|
176
|
-
}]);
|
|
176
|
+
}], null);
|
|
177
177
|
if (selected.length === 0) {
|
|
178
178
|
this.log(styles.muted(' No specs selected.'));
|
|
179
179
|
continue;
|
|
@@ -191,7 +191,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
191
191
|
continue;
|
|
192
192
|
}
|
|
193
193
|
// eslint-disable-next-line no-await-in-loop -- User selection prompt
|
|
194
|
-
const { selected } = await
|
|
194
|
+
const { selected } = await this.prompt([{
|
|
195
195
|
type: 'checkbox',
|
|
196
196
|
name: 'selected',
|
|
197
197
|
message: 'Select specs to remove:',
|
|
@@ -199,7 +199,7 @@ export default class ProjectSpec extends PMOCommand {
|
|
|
199
199
|
name: `${s.id} - ${s.title} (${s.status})`,
|
|
200
200
|
value: s.id,
|
|
201
201
|
})),
|
|
202
|
-
}]);
|
|
202
|
+
}], null);
|
|
203
203
|
if (selected.length === 0) {
|
|
204
204
|
this.log(styles.muted(' No specs selected.'));
|
|
205
205
|
continue;
|