@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.
Files changed (123) hide show
  1. package/dist/commands/action/index.js +2 -2
  2. package/dist/commands/action/show.js +7 -1
  3. package/dist/commands/agent/auth.js +1 -1
  4. package/dist/commands/agent/cleanup.js +6 -6
  5. package/dist/commands/agent/discover.js +1 -1
  6. package/dist/commands/agent/remove.js +4 -4
  7. package/dist/commands/autocomplete/setup.d.ts +2 -2
  8. package/dist/commands/autocomplete/setup.js +5 -5
  9. package/dist/commands/branch/create.js +31 -30
  10. package/dist/commands/branch/list.js +14 -11
  11. package/dist/commands/branch/validate.js +10 -1
  12. package/dist/commands/category/create.js +4 -5
  13. package/dist/commands/category/delete.js +2 -3
  14. package/dist/commands/category/rename.js +2 -3
  15. package/dist/commands/claude.d.ts +2 -8
  16. package/dist/commands/claude.js +26 -26
  17. package/dist/commands/commit.d.ts +2 -8
  18. package/dist/commands/commit.js +4 -26
  19. package/dist/commands/config/index.d.ts +2 -10
  20. package/dist/commands/config/index.js +8 -34
  21. package/dist/commands/docker/clean.js +7 -9
  22. package/dist/commands/docker/index.d.ts +2 -2
  23. package/dist/commands/docker/index.js +13 -12
  24. package/dist/commands/docker/list.d.ts +1 -0
  25. package/dist/commands/docker/list.js +31 -17
  26. package/dist/commands/docker/status.d.ts +3 -1
  27. package/dist/commands/docker/status.js +28 -2
  28. package/dist/commands/docker/sync.js +7 -6
  29. package/dist/commands/epic/delete.js +4 -5
  30. package/dist/commands/epic/list.js +17 -2
  31. package/dist/commands/execution/list.js +25 -17
  32. package/dist/commands/feedback/submit.d.ts +2 -2
  33. package/dist/commands/feedback/submit.js +9 -9
  34. package/dist/commands/link/index.js +2 -2
  35. package/dist/commands/pmo/init.d.ts +2 -2
  36. package/dist/commands/pmo/init.js +29 -10
  37. package/dist/commands/project/spec.js +6 -6
  38. package/dist/commands/repo/list.js +14 -8
  39. package/dist/commands/repo/view.js +2 -1
  40. package/dist/commands/roadmap/list.js +16 -1
  41. package/dist/commands/session/health.d.ts +29 -0
  42. package/dist/commands/session/health.js +496 -0
  43. package/dist/commands/session/index.js +4 -0
  44. package/dist/commands/session/list.js +15 -8
  45. package/dist/commands/spec/edit.js +2 -3
  46. package/dist/commands/staff/add.d.ts +2 -2
  47. package/dist/commands/staff/add.js +15 -14
  48. package/dist/commands/staff/index.js +2 -2
  49. package/dist/commands/staff/list.d.ts +3 -1
  50. package/dist/commands/staff/list.js +15 -1
  51. package/dist/commands/staff/remove.js +4 -4
  52. package/dist/commands/status/index.js +6 -7
  53. package/dist/commands/template/apply.js +10 -11
  54. package/dist/commands/template/create.js +18 -17
  55. package/dist/commands/template/index.d.ts +2 -2
  56. package/dist/commands/template/index.js +6 -6
  57. package/dist/commands/template/save.js +8 -7
  58. package/dist/commands/template/update.js +6 -7
  59. package/dist/commands/terminal/title.d.ts +2 -26
  60. package/dist/commands/terminal/title.js +4 -33
  61. package/dist/commands/theme/index.d.ts +2 -2
  62. package/dist/commands/theme/index.js +19 -18
  63. package/dist/commands/theme/list.d.ts +3 -0
  64. package/dist/commands/theme/list.js +25 -0
  65. package/dist/commands/theme/set.d.ts +2 -2
  66. package/dist/commands/theme/set.js +5 -5
  67. package/dist/commands/ticket/complete.js +4 -1
  68. package/dist/commands/ticket/create.d.ts +1 -0
  69. package/dist/commands/ticket/create.js +64 -16
  70. package/dist/commands/ticket/delete.js +18 -16
  71. package/dist/commands/ticket/edit.js +22 -14
  72. package/dist/commands/ticket/epic.js +12 -10
  73. package/dist/commands/ticket/list.js +24 -5
  74. package/dist/commands/ticket/move.js +4 -1
  75. package/dist/commands/ticket/project.js +11 -9
  76. package/dist/commands/ticket/reassign.js +23 -19
  77. package/dist/commands/ticket/spec.js +7 -5
  78. package/dist/commands/ticket/update.js +55 -53
  79. package/dist/commands/ticket/view.js +4 -2
  80. package/dist/commands/whoami.d.ts +3 -0
  81. package/dist/commands/whoami.js +22 -4
  82. package/dist/commands/work/complete.js +2 -2
  83. package/dist/commands/work/ready.js +9 -9
  84. package/dist/commands/work/revise.js +15 -13
  85. package/dist/commands/work/spawn.js +154 -57
  86. package/dist/commands/work/start.d.ts +1 -0
  87. package/dist/commands/work/start.js +299 -177
  88. package/dist/commands/workspace/prune.d.ts +3 -2
  89. package/dist/commands/workspace/prune.js +70 -10
  90. package/dist/hooks/init.js +4 -0
  91. package/dist/lib/agents/commands.js +4 -0
  92. package/dist/lib/agents/index.js +12 -0
  93. package/dist/lib/execution/devcontainer.d.ts +4 -0
  94. package/dist/lib/execution/devcontainer.js +63 -0
  95. package/dist/lib/mcp/helpers.d.ts +15 -0
  96. package/dist/lib/mcp/helpers.js +15 -0
  97. package/dist/lib/mcp/tools/action.js +5 -5
  98. package/dist/lib/mcp/tools/board.js +7 -7
  99. package/dist/lib/mcp/tools/category.js +5 -5
  100. package/dist/lib/mcp/tools/cli-passthrough.js +30 -30
  101. package/dist/lib/mcp/tools/epic.js +8 -8
  102. package/dist/lib/mcp/tools/phase.js +7 -7
  103. package/dist/lib/mcp/tools/project.js +10 -10
  104. package/dist/lib/mcp/tools/roadmap.js +7 -7
  105. package/dist/lib/mcp/tools/spec.js +9 -9
  106. package/dist/lib/mcp/tools/status.js +6 -6
  107. package/dist/lib/mcp/tools/template.js +6 -6
  108. package/dist/lib/mcp/tools/ticket.js +19 -19
  109. package/dist/lib/mcp/tools/view.js +4 -4
  110. package/dist/lib/mcp/tools/work.js +6 -6
  111. package/dist/lib/mcp/tools/workflow.js +5 -5
  112. package/dist/lib/pmo/index.js +4 -0
  113. package/dist/lib/pmo/storage/base.js +49 -0
  114. package/dist/lib/pr/index.d.ts +9 -0
  115. package/dist/lib/pr/index.js +101 -14
  116. package/dist/lib/prompt-command.d.ts +3 -0
  117. package/dist/lib/prompt-json.d.ts +72 -1
  118. package/dist/lib/prompt-json.js +46 -0
  119. package/dist/lib/repos/index.js +4 -0
  120. package/dist/lib/string-utils.d.ts +10 -0
  121. package/dist/lib/string-utils.js +16 -0
  122. package/oclif.manifest.json +594 -449
  123. package/package.json +3 -2
@@ -1,10 +1,10 @@
1
- import { Args, Command, Flags } from '@oclif/core';
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, outputPromptAsJson, createMetadata, normalizeChoices, } from '../../lib/prompt-json.js';
7
- export default class TerminalTitle extends Command {
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 { Command } from '@oclif/core';
2
- export default class Theme extends Command {
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 { Command, Flags } from '@oclif/core';
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 Command {
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 inquirer.prompt([{
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 inquirer.prompt([{
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 inquirer.prompt([{
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 inquirer.prompt([{
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 inquirer.prompt([{
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 inquirer.prompt([{
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 inquirer.prompt([{
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 inquirer.prompt([{
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);
@@ -2,5 +2,8 @@ import { Command } from '@oclif/core';
2
2
  export default class ThemeList extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
+ static flags: {
6
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ };
5
8
  run(): Promise<void>;
6
9
  }
@@ -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 { Command } from '@oclif/core';
2
- export default class ThemeSet extends Command {
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 { Command, Args, Flags } from '@oclif/core';
1
+ import { Args, Flags } from '@oclif/core';
2
2
  import chalk from 'chalk';
3
- import inquirer from 'inquirer';
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 Command {
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 inquirer.prompt([{
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.getBoard(incompleteTickets[0].projectId);
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 inquirer.prompt([
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
- const answers = await inquirer.prompt([
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
- type: 'input',
371
- name: 'customCategory',
372
- message: 'Enter custom category:',
373
- when: (answers) => answers.categoryChoice === '__custom__',
374
- validate: (input) => input.trim() ? true : 'Category cannot be empty',
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 inquirer.prompt([
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, notInScope } = await inquirer.prompt([
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.getBoard(ticket.projectId);
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.name}`);
90
+ this.log(` Project: ${board?.name || ticket.projectId || 'Unknown'}`);
92
91
  this.log(` Status: ${ticket.statusName}`);
93
- const { confirmed } = await inquirer.prompt([{
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 inquirer.prompt([{
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 inquirer.prompt([{
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;