@proletariat/cli 0.3.25 → 0.3.27
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/index.js +2 -2
- package/dist/commands/action/show.js +7 -1
- 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/branch/list.js +14 -11
- package/dist/commands/branch/validate.js +10 -1
- 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/clean.js +7 -9
- package/dist/commands/docker/index.d.ts +2 -2
- package/dist/commands/docker/index.js +13 -12
- package/dist/commands/docker/list.d.ts +1 -0
- package/dist/commands/docker/list.js +31 -17
- package/dist/commands/docker/status.d.ts +3 -1
- package/dist/commands/docker/status.js +28 -2
- package/dist/commands/docker/sync.js +7 -6
- package/dist/commands/epic/delete.js +4 -5
- package/dist/commands/epic/list.js +17 -2
- package/dist/commands/execution/list.js +25 -17
- package/dist/commands/feedback/submit.d.ts +2 -2
- package/dist/commands/feedback/submit.js +9 -9
- package/dist/commands/link/index.js +2 -2
- package/dist/commands/pmo/init.d.ts +2 -2
- package/dist/commands/pmo/init.js +29 -10
- package/dist/commands/project/spec.js +6 -6
- package/dist/commands/repo/list.js +14 -8
- package/dist/commands/repo/view.js +2 -1
- package/dist/commands/roadmap/list.js +16 -1
- package/dist/commands/session/health.d.ts +29 -0
- package/dist/commands/session/health.js +496 -0
- package/dist/commands/session/index.js +4 -0
- package/dist/commands/session/list.js +15 -8
- package/dist/commands/spec/edit.js +2 -3
- 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/list.d.ts +3 -1
- package/dist/commands/staff/list.js +15 -1
- package/dist/commands/staff/remove.js +4 -4
- package/dist/commands/status/index.js +6 -7
- 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/list.d.ts +3 -0
- package/dist/commands/theme/list.js +25 -0
- package/dist/commands/theme/set.d.ts +2 -2
- package/dist/commands/theme/set.js +5 -5
- package/dist/commands/ticket/complete.js +4 -1
- package/dist/commands/ticket/create.d.ts +1 -0
- package/dist/commands/ticket/create.js +64 -16
- package/dist/commands/ticket/delete.js +18 -16
- package/dist/commands/ticket/edit.js +22 -14
- package/dist/commands/ticket/epic.js +12 -10
- package/dist/commands/ticket/list.js +24 -5
- package/dist/commands/ticket/move.js +4 -1
- 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/ticket/view.js +4 -2
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.js +22 -4
- package/dist/commands/work/complete.js +2 -2
- package/dist/commands/work/ready.js +9 -9
- package/dist/commands/work/revise.js +15 -13
- package/dist/commands/work/spawn.js +154 -57
- package/dist/commands/work/start.d.ts +1 -0
- package/dist/commands/work/start.js +299 -177
- package/dist/commands/workspace/prune.d.ts +3 -2
- package/dist/commands/workspace/prune.js +70 -10
- package/dist/hooks/init.js +4 -0
- package/dist/lib/agents/commands.js +4 -0
- package/dist/lib/agents/index.js +12 -0
- package/dist/lib/execution/devcontainer.d.ts +4 -0
- package/dist/lib/execution/devcontainer.js +63 -0
- package/dist/lib/mcp/helpers.d.ts +15 -0
- package/dist/lib/mcp/helpers.js +15 -0
- package/dist/lib/mcp/tools/action.js +5 -5
- package/dist/lib/mcp/tools/board.js +7 -7
- package/dist/lib/mcp/tools/category.js +5 -5
- package/dist/lib/mcp/tools/cli-passthrough.js +30 -30
- package/dist/lib/mcp/tools/epic.js +8 -8
- package/dist/lib/mcp/tools/phase.js +7 -7
- package/dist/lib/mcp/tools/project.js +10 -10
- package/dist/lib/mcp/tools/roadmap.js +7 -7
- package/dist/lib/mcp/tools/spec.js +9 -9
- package/dist/lib/mcp/tools/status.js +6 -6
- package/dist/lib/mcp/tools/template.js +6 -6
- package/dist/lib/mcp/tools/ticket.js +19 -19
- package/dist/lib/mcp/tools/view.js +4 -4
- package/dist/lib/mcp/tools/work.js +6 -6
- package/dist/lib/mcp/tools/workflow.js +5 -5
- package/dist/lib/pmo/index.js +4 -0
- package/dist/lib/pmo/storage/base.js +49 -0
- package/dist/lib/pr/index.d.ts +9 -0
- package/dist/lib/pr/index.js +101 -14
- package/dist/lib/prompt-command.d.ts +3 -0
- package/dist/lib/prompt-json.d.ts +72 -1
- package/dist/lib/prompt-json.js +46 -0
- package/dist/lib/repos/index.js +4 -0
- package/dist/lib/string-utils.d.ts +10 -0
- package/dist/lib/string-utils.js +16 -0
- package/oclif.manifest.json +594 -449
- package/package.json +3 -2
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Args,
|
|
2
|
-
import inquirer from 'inquirer';
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
3
2
|
import { setTerminalTitle, resetTerminalTitle } from '../../lib/terminal.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
4
|
import { machineOutputFlags } from '../../lib/pmo/base-command.js';
|
|
6
|
-
import { isAgentMode
|
|
7
|
-
|
|
5
|
+
import { isAgentMode } from '../../lib/prompt-json.js';
|
|
6
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
7
|
+
export default class TerminalTitle extends PromptCommand {
|
|
8
8
|
static description = 'Set the terminal tab/window title';
|
|
9
9
|
static examples = [
|
|
10
10
|
'<%= config.bin %> <%= command.id %> "My Custom Name"',
|
|
@@ -26,35 +26,6 @@ export default class TerminalTitle extends Command {
|
|
|
26
26
|
}),
|
|
27
27
|
...machineOutputFlags,
|
|
28
28
|
};
|
|
29
|
-
/**
|
|
30
|
-
* Prompt wrapper - drop-in replacement for inquirer.prompt with JSON mode support.
|
|
31
|
-
* Matches the pattern from PMOCommand.prompt().
|
|
32
|
-
*
|
|
33
|
-
* In JSON mode: outputs prompt config as JSON and exits
|
|
34
|
-
* In interactive mode: shows normal inquirer prompt
|
|
35
|
-
*/
|
|
36
|
-
async prompt(questions, jsonModeConfig) {
|
|
37
|
-
// Check for JSON/agent mode
|
|
38
|
-
if (jsonModeConfig && isAgentMode(jsonModeConfig.flags)) {
|
|
39
|
-
const firstQuestion = questions[0];
|
|
40
|
-
if (firstQuestion) {
|
|
41
|
-
const choices = firstQuestion.choices
|
|
42
|
-
? normalizeChoices(firstQuestion.choices)
|
|
43
|
-
: undefined;
|
|
44
|
-
outputPromptAsJson({
|
|
45
|
-
type: firstQuestion.type,
|
|
46
|
-
name: firstQuestion.name,
|
|
47
|
-
message: firstQuestion.message,
|
|
48
|
-
choices,
|
|
49
|
-
default: firstQuestion.default,
|
|
50
|
-
}, createMetadata(jsonModeConfig.commandName, jsonModeConfig.flags));
|
|
51
|
-
// outputPromptAsJson calls process.exit, never returns
|
|
52
|
-
}
|
|
53
|
-
return {};
|
|
54
|
-
}
|
|
55
|
-
// Interactive mode: just call inquirer
|
|
56
|
-
return inquirer.prompt(questions);
|
|
57
|
-
}
|
|
58
29
|
async run() {
|
|
59
30
|
const { args, flags } = await this.parse(TerminalTitle);
|
|
60
31
|
// Handle reset flag
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class Theme extends
|
|
1
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
|
+
export default class Theme extends PromptCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
4
5
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
5
6
|
import { ensureBuiltinThemes } from '../../lib/themes.js';
|
|
6
7
|
import { getThemes, getAvailableThemeNames } from '../../lib/database/index.js';
|
|
7
8
|
import { shouldOutputJson, outputPromptAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
8
|
-
export default class Theme extends
|
|
9
|
+
export default class Theme extends PromptCommand {
|
|
9
10
|
static description = 'Manage agent naming themes';
|
|
10
11
|
static examples = [
|
|
11
12
|
'<%= config.bin %> <%= command.id %> list',
|
|
@@ -44,7 +45,7 @@ export default class Theme extends Command {
|
|
|
44
45
|
}
|
|
45
46
|
this.log(chalk.bold('\nAgent Themes'));
|
|
46
47
|
this.log(chalk.dim('Optional themed name pools for your agents.\n'));
|
|
47
|
-
const { action } = await
|
|
48
|
+
const { action } = await this.prompt([{
|
|
48
49
|
type: 'list',
|
|
49
50
|
name: 'action',
|
|
50
51
|
message,
|
|
@@ -53,7 +54,7 @@ export default class Theme extends Command {
|
|
|
53
54
|
new inquirer.Separator(),
|
|
54
55
|
{ name: menuChoices[3].name, value: menuChoices[3].id }
|
|
55
56
|
]
|
|
56
|
-
}]);
|
|
57
|
+
}], null);
|
|
57
58
|
if (action === 'cancel') {
|
|
58
59
|
this.log(chalk.dim('Cancelled.'));
|
|
59
60
|
return;
|
|
@@ -68,7 +69,7 @@ export default class Theme extends Command {
|
|
|
68
69
|
}
|
|
69
70
|
case 'create': {
|
|
70
71
|
// Prompt for theme name interactively
|
|
71
|
-
const { themeName } = await
|
|
72
|
+
const { themeName } = await this.prompt([{
|
|
72
73
|
type: 'input',
|
|
73
74
|
name: 'themeName',
|
|
74
75
|
message: 'Theme name:',
|
|
@@ -77,7 +78,7 @@ export default class Theme extends Command {
|
|
|
77
78
|
return 'Theme name is required';
|
|
78
79
|
return true;
|
|
79
80
|
}
|
|
80
|
-
}]);
|
|
81
|
+
}], null);
|
|
81
82
|
// Normalize: lowercase, spaces to dashes
|
|
82
83
|
const normalized = themeName.trim().toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
|
|
83
84
|
if (themeName.trim() !== normalized) {
|
|
@@ -87,7 +88,7 @@ export default class Theme extends Command {
|
|
|
87
88
|
const cmd = new CreateCommand([normalized], this.config);
|
|
88
89
|
await cmd.run();
|
|
89
90
|
// Prompt to add names immediately
|
|
90
|
-
const { addNamesNow } = await
|
|
91
|
+
const { addNamesNow } = await this.prompt([{
|
|
91
92
|
type: 'list',
|
|
92
93
|
name: 'addNamesNow',
|
|
93
94
|
message: 'Add names to this theme now?',
|
|
@@ -95,14 +96,14 @@ export default class Theme extends Command {
|
|
|
95
96
|
{ name: 'Yes', value: true },
|
|
96
97
|
{ name: 'No', value: false },
|
|
97
98
|
],
|
|
98
|
-
}]);
|
|
99
|
+
}], null);
|
|
99
100
|
if (addNamesNow) {
|
|
100
|
-
const { names } = await
|
|
101
|
+
const { names } = await this.prompt([{
|
|
101
102
|
type: 'input',
|
|
102
103
|
name: 'names',
|
|
103
104
|
message: 'Enter names (space-separated):',
|
|
104
105
|
validate: (input) => input.trim() ? true : 'At least one name is required'
|
|
105
|
-
}]);
|
|
106
|
+
}], null);
|
|
106
107
|
const args = [normalized, ...names.trim().split(/\s+/)];
|
|
107
108
|
const { default: AddNamesCommand } = await import('./add-names.js');
|
|
108
109
|
const addCmd = new AddNamesCommand(args, this.config);
|
|
@@ -131,15 +132,15 @@ export default class Theme extends Command {
|
|
|
131
132
|
// Add option to create new theme (using type assertion for mixed array)
|
|
132
133
|
themeChoices.push(new inquirer.Separator());
|
|
133
134
|
themeChoices.push({ name: chalk.green('+ Create new theme'), value: '__create_new__' });
|
|
134
|
-
const { selectedTheme } = await
|
|
135
|
+
const { selectedTheme } = await this.prompt([{
|
|
135
136
|
type: 'list',
|
|
136
137
|
name: 'selectedTheme',
|
|
137
138
|
message: 'Select theme to add names to:',
|
|
138
139
|
choices: themeChoices
|
|
139
|
-
}]);
|
|
140
|
+
}], null);
|
|
140
141
|
// If they want to create a new theme first
|
|
141
142
|
if (selectedTheme === '__create_new__') {
|
|
142
|
-
const { themeName } = await
|
|
143
|
+
const { themeName } = await this.prompt([{
|
|
143
144
|
type: 'input',
|
|
144
145
|
name: 'themeName',
|
|
145
146
|
message: 'Theme name:',
|
|
@@ -148,7 +149,7 @@ export default class Theme extends Command {
|
|
|
148
149
|
return 'Theme name is required';
|
|
149
150
|
return true;
|
|
150
151
|
}
|
|
151
|
-
}]);
|
|
152
|
+
}], null);
|
|
152
153
|
// Normalize: lowercase, spaces to dashes
|
|
153
154
|
const normalizedTheme = themeName.trim().toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '');
|
|
154
155
|
if (themeName.trim() !== normalizedTheme) {
|
|
@@ -158,12 +159,12 @@ export default class Theme extends Command {
|
|
|
158
159
|
const createCmd = new CreateCommand([normalizedTheme], this.config);
|
|
159
160
|
await createCmd.run();
|
|
160
161
|
// Now prompt for names to add to the new theme
|
|
161
|
-
const { names } = await
|
|
162
|
+
const { names } = await this.prompt([{
|
|
162
163
|
type: 'input',
|
|
163
164
|
name: 'names',
|
|
164
165
|
message: 'Names to add (space-separated):',
|
|
165
166
|
validate: (input) => input.trim() ? true : 'At least one name is required'
|
|
166
|
-
}]);
|
|
167
|
+
}], null);
|
|
167
168
|
const args = [normalizedTheme, ...names.trim().split(/\s+/)];
|
|
168
169
|
const { default: AddNamesCommand } = await import('./add-names.js');
|
|
169
170
|
const cmd = new AddNamesCommand(args, this.config);
|
|
@@ -171,12 +172,12 @@ export default class Theme extends Command {
|
|
|
171
172
|
}
|
|
172
173
|
else {
|
|
173
174
|
// Prompt for names to add
|
|
174
|
-
const { names } = await
|
|
175
|
+
const { names } = await this.prompt([{
|
|
175
176
|
type: 'input',
|
|
176
177
|
name: 'names',
|
|
177
178
|
message: 'Names to add (space-separated):',
|
|
178
179
|
validate: (input) => input.trim() ? true : 'At least one name is required'
|
|
179
|
-
}]);
|
|
180
|
+
}], null);
|
|
180
181
|
const args = [selectedTheme, ...names.trim().split(/\s+/)];
|
|
181
182
|
const { default: AddNamesCommand } = await import('./add-names.js');
|
|
182
183
|
const cmd = new AddNamesCommand(args, this.config);
|
|
@@ -3,21 +3,46 @@ import chalk from 'chalk';
|
|
|
3
3
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
4
4
|
import { ensureBuiltinThemes } from '../../lib/themes.js';
|
|
5
5
|
import { getThemes, getThemeNames, getAvailableThemeNames } from '../../lib/database/index.js';
|
|
6
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
7
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
6
8
|
export default class ThemeList extends Command {
|
|
7
9
|
static description = 'List available agent themes';
|
|
8
10
|
static examples = [
|
|
9
11
|
'<%= config.bin %> <%= command.id %>',
|
|
10
12
|
];
|
|
13
|
+
static flags = {
|
|
14
|
+
...machineOutputFlags,
|
|
15
|
+
};
|
|
11
16
|
async run() {
|
|
17
|
+
const { flags } = await this.parse(ThemeList);
|
|
18
|
+
const jsonMode = shouldOutputJson(flags);
|
|
12
19
|
try {
|
|
13
20
|
const workspaceInfo = getWorkspaceInfo();
|
|
14
21
|
// Ensure built-in themes are seeded
|
|
15
22
|
ensureBuiltinThemes(workspaceInfo.path);
|
|
16
23
|
const themes = getThemes(workspaceInfo.path);
|
|
17
24
|
if (themes.length === 0) {
|
|
25
|
+
if (jsonMode) {
|
|
26
|
+
this.log(JSON.stringify([], null, 2));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
18
29
|
this.log(chalk.yellow('No themes found.'));
|
|
19
30
|
return;
|
|
20
31
|
}
|
|
32
|
+
if (jsonMode) {
|
|
33
|
+
const themesWithNames = themes.map(theme => {
|
|
34
|
+
const allNames = getThemeNames(workspaceInfo.path, theme.id);
|
|
35
|
+
const availableNames = getAvailableThemeNames(workspaceInfo.path, theme.id);
|
|
36
|
+
return {
|
|
37
|
+
...theme,
|
|
38
|
+
availableNames: availableNames.length,
|
|
39
|
+
inUse: allNames.length - availableNames.length,
|
|
40
|
+
totalNames: allNames.length,
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
this.log(JSON.stringify(themesWithNames, null, 2));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
21
46
|
this.log(chalk.bold('\nAgent Themes\n'));
|
|
22
47
|
for (const theme of themes) {
|
|
23
48
|
const allNames = getThemeNames(workspaceInfo.path, theme.id);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class ThemeSet extends
|
|
1
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
|
+
export default class ThemeSet extends PromptCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static args: {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Args, Flags } from '@oclif/core';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import
|
|
3
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
4
4
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
5
5
|
import { ensureBuiltinThemes } from '../../lib/themes.js';
|
|
6
6
|
import { getThemes, getAvailableThemeNames, setActiveTheme, getActiveTheme } from '../../lib/database/index.js';
|
|
7
7
|
import { shouldOutputJson, outputPromptAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
|
|
8
|
-
export default class ThemeSet extends
|
|
8
|
+
export default class ThemeSet extends PromptCommand {
|
|
9
9
|
static description = 'Set the active theme for this workspace';
|
|
10
10
|
static examples = [
|
|
11
11
|
'<%= config.bin %> <%= command.id %> billionaires',
|
|
@@ -58,12 +58,12 @@ export default class ThemeSet extends Command {
|
|
|
58
58
|
value: t.id
|
|
59
59
|
};
|
|
60
60
|
});
|
|
61
|
-
const { selected } = await
|
|
61
|
+
const { selected } = await this.prompt([{
|
|
62
62
|
type: 'list',
|
|
63
63
|
name: 'selected',
|
|
64
64
|
message: 'Select theme for this workspace:',
|
|
65
65
|
choices
|
|
66
|
-
}]);
|
|
66
|
+
}], null);
|
|
67
67
|
themeId = selected;
|
|
68
68
|
}
|
|
69
69
|
setActiveTheme(workspaceInfo.path, themeId);
|
|
@@ -59,7 +59,10 @@ export default class TicketComplete extends PMOCommand {
|
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
// Get board for columns (use the first incomplete ticket's project)
|
|
62
|
-
const board = await this.storage.
|
|
62
|
+
const board = await this.storage.getProjectBoard(incompleteTickets[0].projectId);
|
|
63
|
+
if (!board) {
|
|
64
|
+
return handleError('PROJECT_NOT_FOUND', `Project "${incompleteTickets[0].projectId}" not found. The ticket may belong to an orphaned project.`);
|
|
65
|
+
}
|
|
63
66
|
// Find the "Done" column (case-insensitive)
|
|
64
67
|
const doneColumn = board.columns.find(col => col.name.toLowerCase().includes('done'));
|
|
65
68
|
if (!doneColumn) {
|
|
@@ -9,6 +9,7 @@ export default class TicketCreate extends PMOCommand {
|
|
|
9
9
|
priority: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
category: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'description-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
interactive: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
epic: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
|
+
import * as fs from 'node:fs';
|
|
2
3
|
import inquirer from 'inquirer';
|
|
3
4
|
import { autoExportToBoard, PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
5
|
+
// Note: inquirer import kept for inquirer.Separator usage in interactive mode
|
|
4
6
|
import { styles } from '../../lib/styles.js';
|
|
5
7
|
import { updateEpicTicketsSection } from '../../lib/pmo/epic-files.js';
|
|
6
8
|
import { PRIORITIES, PRIORITY_LABELS } from '../../lib/pmo/types.js';
|
|
@@ -15,6 +17,8 @@ export default class TicketCreate extends PMOCommand {
|
|
|
15
17
|
'<%= config.bin %> <%= command.id %> -t "Add feature" -c "In Progress" -p P1',
|
|
16
18
|
'<%= config.bin %> <%= command.id %> --project mobile-app -t "New feature"',
|
|
17
19
|
'<%= config.bin %> <%= command.id %> --epic EPIC-001 -t "Implement auth flow"',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> --title "My ticket" --description-file ./ticket-desc.md',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> --title "My ticket" --description-file - # Read from stdin',
|
|
18
22
|
'<%= config.bin %> <%= command.id %> --json # Output column choices as JSON',
|
|
19
23
|
'<%= config.bin %> <%= command.id %> --title "Test" -P PROJ-001 --dry-run --json # Validate without creating',
|
|
20
24
|
];
|
|
@@ -46,6 +50,11 @@ export default class TicketCreate extends PMOCommand {
|
|
|
46
50
|
char: 'd',
|
|
47
51
|
description: 'Ticket description',
|
|
48
52
|
}),
|
|
53
|
+
'description-file': Flags.string({
|
|
54
|
+
char: 'D',
|
|
55
|
+
description: 'Path to a markdown file for the ticket description (use - for stdin)',
|
|
56
|
+
exclusive: ['description'],
|
|
57
|
+
}),
|
|
49
58
|
id: Flags.string({
|
|
50
59
|
description: 'Custom ticket ID (auto-generated if not provided)',
|
|
51
60
|
}),
|
|
@@ -64,6 +73,7 @@ export default class TicketCreate extends PMOCommand {
|
|
|
64
73
|
}),
|
|
65
74
|
labels: Flags.string({
|
|
66
75
|
char: 'l',
|
|
76
|
+
aliases: ['label'],
|
|
67
77
|
description: 'Labels (comma-separated)',
|
|
68
78
|
}),
|
|
69
79
|
'dry-run': Flags.boolean({
|
|
@@ -94,6 +104,27 @@ export default class TicketCreate extends PMOCommand {
|
|
|
94
104
|
}
|
|
95
105
|
this.error(message);
|
|
96
106
|
};
|
|
107
|
+
// Read description from file if --description-file is provided
|
|
108
|
+
if (flags['description-file']) {
|
|
109
|
+
const filePath = flags['description-file'];
|
|
110
|
+
try {
|
|
111
|
+
if (filePath === '-') {
|
|
112
|
+
// Guard: prevent hanging when no input is piped
|
|
113
|
+
if (process.stdin.isTTY) {
|
|
114
|
+
return handleError('DESCRIPTION_FILE_ERROR', 'Cannot read from stdin: no input piped. Use --description-file <path> with a file path instead, or pipe content via: echo "desc" | prlt ticket create --description-file -');
|
|
115
|
+
}
|
|
116
|
+
// Read from stdin
|
|
117
|
+
flags.description = fs.readFileSync(0, 'utf-8');
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
flags.description = fs.readFileSync(filePath, 'utf-8');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
125
|
+
return handleError('DESCRIPTION_FILE_ERROR', `Failed to read description file "${filePath}": ${errMsg}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
97
128
|
// Validate epic if provided
|
|
98
129
|
if (flags.epic) {
|
|
99
130
|
const epic = await this.storage.getEpic(flags.epic);
|
|
@@ -290,7 +321,7 @@ export default class TicketCreate extends PMOCommand {
|
|
|
290
321
|
if (!template && !flags.template) {
|
|
291
322
|
const templates = await storage.listTicketTemplates();
|
|
292
323
|
if (templates.length > 0) {
|
|
293
|
-
const { selectedTemplate } = await
|
|
324
|
+
const { selectedTemplate } = await this.prompt([
|
|
294
325
|
{
|
|
295
326
|
type: 'list',
|
|
296
327
|
name: 'selectedTemplate',
|
|
@@ -304,13 +335,14 @@ export default class TicketCreate extends PMOCommand {
|
|
|
304
335
|
})),
|
|
305
336
|
],
|
|
306
337
|
},
|
|
307
|
-
]);
|
|
338
|
+
], null);
|
|
308
339
|
if (selectedTemplate) {
|
|
309
340
|
template = templates.find(t => t.id === selectedTemplate) || null;
|
|
310
341
|
}
|
|
311
342
|
}
|
|
312
343
|
}
|
|
313
|
-
|
|
344
|
+
// Prompt for title
|
|
345
|
+
const { title: answerTitle } = await this.prompt([
|
|
314
346
|
{
|
|
315
347
|
type: 'input',
|
|
316
348
|
name: 'title',
|
|
@@ -318,13 +350,19 @@ export default class TicketCreate extends PMOCommand {
|
|
|
318
350
|
default: flags.title || template?.titlePattern,
|
|
319
351
|
validate: (input) => input.trim() ? true : 'Title cannot be empty',
|
|
320
352
|
},
|
|
353
|
+
], null);
|
|
354
|
+
// Prompt for column
|
|
355
|
+
const { column: answerColumn } = await this.prompt([
|
|
321
356
|
{
|
|
322
357
|
type: 'list',
|
|
323
358
|
name: 'column',
|
|
324
359
|
message: 'Column:',
|
|
325
|
-
choices: columns,
|
|
360
|
+
choices: columns.map(c => ({ name: c, value: c })),
|
|
326
361
|
default: flags.column || columns[0],
|
|
327
362
|
},
|
|
363
|
+
], null);
|
|
364
|
+
// Prompt for priority
|
|
365
|
+
const { priority: answerPriority } = await this.prompt([
|
|
328
366
|
{
|
|
329
367
|
type: 'list',
|
|
330
368
|
name: 'priority',
|
|
@@ -335,6 +373,9 @@ export default class TicketCreate extends PMOCommand {
|
|
|
335
373
|
],
|
|
336
374
|
default: flags.priority || template?.defaultPriority,
|
|
337
375
|
},
|
|
376
|
+
], null);
|
|
377
|
+
// Prompt for category
|
|
378
|
+
const { categoryChoice } = await this.prompt([
|
|
338
379
|
{
|
|
339
380
|
type: 'list',
|
|
340
381
|
name: 'categoryChoice',
|
|
@@ -366,14 +407,19 @@ export default class TicketCreate extends PMOCommand {
|
|
|
366
407
|
],
|
|
367
408
|
default: flags.category || template?.defaultCategory || '',
|
|
368
409
|
},
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
410
|
+
], null);
|
|
411
|
+
// Custom category prompt if needed
|
|
412
|
+
let customCategory;
|
|
413
|
+
if (categoryChoice === '__custom__') {
|
|
414
|
+
const result = await this.prompt([{
|
|
415
|
+
type: 'input',
|
|
416
|
+
name: 'customCategory',
|
|
417
|
+
message: 'Enter custom category:',
|
|
418
|
+
validate: (input) => input.trim() ? true : 'Category cannot be empty',
|
|
419
|
+
}], null);
|
|
420
|
+
customCategory = result.customCategory;
|
|
421
|
+
}
|
|
422
|
+
const answers = { title: answerTitle, column: answerColumn, priority: answerPriority, categoryChoice, customCategory };
|
|
377
423
|
// Resolve category from choice or custom input
|
|
378
424
|
const category = answers.categoryChoice === '__custom__'
|
|
379
425
|
? answers.customCategory
|
|
@@ -402,14 +448,14 @@ export default class TicketCreate extends PMOCommand {
|
|
|
402
448
|
}
|
|
403
449
|
this.log(styles.muted('\n─── Ticket Description (for agent execution) ───'));
|
|
404
450
|
// Prompt for "What" - the main outcome
|
|
405
|
-
const { what } = await
|
|
451
|
+
const { what } = await this.prompt([
|
|
406
452
|
{
|
|
407
453
|
type: 'input',
|
|
408
454
|
name: 'what',
|
|
409
455
|
message: 'What is the concrete outcome? (one sentence):',
|
|
410
456
|
validate: (input) => input.trim() ? true : 'Outcome cannot be empty - what does success look like?',
|
|
411
457
|
},
|
|
412
|
-
]);
|
|
458
|
+
], null);
|
|
413
459
|
// Prompt for acceptance criteria using multiline input
|
|
414
460
|
const doneWhenResult = await multiLineInput({
|
|
415
461
|
message: 'Done when (acceptance criteria):',
|
|
@@ -419,20 +465,22 @@ export default class TicketCreate extends PMOCommand {
|
|
|
419
465
|
throw new Error('Ticket creation cancelled');
|
|
420
466
|
}
|
|
421
467
|
// Continue with remaining prompts
|
|
422
|
-
const { context
|
|
468
|
+
const { context } = await this.prompt([
|
|
423
469
|
{
|
|
424
470
|
type: 'input',
|
|
425
471
|
name: 'context',
|
|
426
472
|
message: 'Context (files, patterns, hints - optional):',
|
|
427
473
|
default: '',
|
|
428
474
|
},
|
|
475
|
+
], null);
|
|
476
|
+
const { notInScope } = await this.prompt([
|
|
429
477
|
{
|
|
430
478
|
type: 'input',
|
|
431
479
|
name: 'notInScope',
|
|
432
480
|
message: 'Not in scope (explicit exclusions - optional):',
|
|
433
481
|
default: '',
|
|
434
482
|
},
|
|
435
|
-
]);
|
|
483
|
+
], null);
|
|
436
484
|
// Build structured description
|
|
437
485
|
const parts = [];
|
|
438
486
|
parts.push(`## What\n${what}`);
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { Args, Flags } from '@oclif/core';
|
|
2
|
-
import inquirer from 'inquirer';
|
|
3
2
|
import { autoExportToBoard, PMOCommand, pmoBaseFlags, } from '../../lib/pmo/index.js';
|
|
4
3
|
import { styles } from '../../lib/styles.js';
|
|
5
4
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -57,7 +56,7 @@ export default class TicketDelete extends PMOCommand {
|
|
|
57
56
|
}
|
|
58
57
|
// Bulk mode
|
|
59
58
|
if (flags.bulk) {
|
|
60
|
-
await this.executeBulk(allTickets, flags.force);
|
|
59
|
+
await this.executeBulk(allTickets, flags.force, flags);
|
|
61
60
|
return;
|
|
62
61
|
}
|
|
63
62
|
// Single ticket mode
|
|
@@ -82,24 +81,25 @@ export default class TicketDelete extends PMOCommand {
|
|
|
82
81
|
if (!ticket) {
|
|
83
82
|
this.error(`Ticket "${ticketId}" not found.`);
|
|
84
83
|
}
|
|
85
|
-
// Get board for project name
|
|
86
|
-
const board = await this.storage.
|
|
84
|
+
// Get board for project name (may be null if project was deleted/orphaned)
|
|
85
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
87
86
|
// Confirmation prompt (unless --force)
|
|
88
87
|
if (!flags.force) {
|
|
89
88
|
this.log(`\nDelete ticket ${styles.emphasis(ticketId)}?`);
|
|
90
89
|
this.log(` Title: ${ticket.title}`);
|
|
91
|
-
this.log(` Project: ${board.
|
|
90
|
+
this.log(` Project: ${board?.name || ticket.projectId || 'Unknown'}`);
|
|
92
91
|
this.log(` Status: ${ticket.statusName}`);
|
|
93
|
-
const {
|
|
92
|
+
const jsonModeConfig = jsonMode ? { flags, commandName: 'ticket delete' } : null;
|
|
93
|
+
const { confirmed } = await this.prompt([{
|
|
94
94
|
type: 'list',
|
|
95
95
|
name: 'confirmed',
|
|
96
96
|
message: 'Are you sure?',
|
|
97
97
|
choices: [
|
|
98
|
-
{ name: 'No, cancel', value: false },
|
|
99
|
-
{ name: 'Yes, delete', value: true },
|
|
98
|
+
{ name: 'No, cancel', value: false, command: '' },
|
|
99
|
+
{ name: 'Yes, delete', value: true, command: `prlt ticket delete ${ticketId} --force --json` },
|
|
100
100
|
],
|
|
101
101
|
default: 0,
|
|
102
|
-
}]);
|
|
102
|
+
}], jsonModeConfig);
|
|
103
103
|
if (!confirmed) {
|
|
104
104
|
this.log(styles.warning('Deletion cancelled.'));
|
|
105
105
|
return;
|
|
@@ -112,10 +112,12 @@ export default class TicketDelete extends PMOCommand {
|
|
|
112
112
|
this.log(styles.success(`\n✅ Ticket ${styles.emphasis(ticketId)} deleted`));
|
|
113
113
|
this.log(styles.muted(' Removed from database and board'));
|
|
114
114
|
}
|
|
115
|
-
async executeBulk(allTickets, force) {
|
|
115
|
+
async executeBulk(allTickets, force, flags) {
|
|
116
|
+
const jsonMode = flags ? shouldOutputJson(flags) : false;
|
|
117
|
+
const jsonModeConfig = jsonMode ? { flags: flags, commandName: 'ticket delete' } : null;
|
|
116
118
|
this.log(styles.emphasis('🗑️ Delete Multiple Tickets\n'));
|
|
117
119
|
// Select tickets to delete
|
|
118
|
-
const { selectedTickets } = await
|
|
120
|
+
const { selectedTickets } = await this.prompt([{
|
|
119
121
|
type: 'checkbox',
|
|
120
122
|
name: 'selectedTickets',
|
|
121
123
|
message: 'Select tickets to DELETE:',
|
|
@@ -123,7 +125,7 @@ export default class TicketDelete extends PMOCommand {
|
|
|
123
125
|
name: `${t.id} - ${t.title} (${t.statusName})`,
|
|
124
126
|
value: t.id,
|
|
125
127
|
})),
|
|
126
|
-
}]);
|
|
128
|
+
}], jsonModeConfig);
|
|
127
129
|
if (selectedTickets.length === 0) {
|
|
128
130
|
this.log(styles.muted('No tickets selected.'));
|
|
129
131
|
return;
|
|
@@ -136,16 +138,16 @@ export default class TicketDelete extends PMOCommand {
|
|
|
136
138
|
this.log(styles.primary(` • ${ticketId}: ${ticket?.title}`));
|
|
137
139
|
}
|
|
138
140
|
this.log('');
|
|
139
|
-
const { confirm } = await
|
|
141
|
+
const { confirm } = await this.prompt([{
|
|
140
142
|
type: 'list',
|
|
141
143
|
name: 'confirm',
|
|
142
144
|
message: 'Are you sure? This cannot be undone.',
|
|
143
145
|
choices: [
|
|
144
|
-
{ name: 'No, cancel', value: false },
|
|
145
|
-
{ name: 'Yes, DELETE tickets', value: true }
|
|
146
|
+
{ name: 'No, cancel', value: false, command: '' },
|
|
147
|
+
{ name: 'Yes, DELETE tickets', value: true, command: 'prlt ticket delete --bulk --force --json' }
|
|
146
148
|
],
|
|
147
149
|
default: 0
|
|
148
|
-
}]);
|
|
150
|
+
}], jsonModeConfig);
|
|
149
151
|
if (!confirm) {
|
|
150
152
|
this.log(styles.muted('Deletion cancelled.'));
|
|
151
153
|
return;
|