@proletariat/cli 0.3.20 → 0.3.21

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 (43) hide show
  1. package/dist/commands/agent/login.js +2 -2
  2. package/dist/commands/agent/remove.d.ts +1 -0
  3. package/dist/commands/agent/remove.js +36 -28
  4. package/dist/commands/agent/shell.js +2 -2
  5. package/dist/commands/agent/staff/remove.js +2 -2
  6. package/dist/commands/agent/status.js +2 -2
  7. package/dist/commands/agent/themes/add-names.d.ts +1 -0
  8. package/dist/commands/agent/themes/add-names.js +5 -1
  9. package/dist/commands/agent/visit.js +2 -2
  10. package/dist/commands/epic/link/index.js +17 -0
  11. package/dist/commands/execution/config.js +22 -0
  12. package/dist/commands/execution/kill.d.ts +3 -0
  13. package/dist/commands/execution/kill.js +1 -0
  14. package/dist/commands/execution/list.js +5 -4
  15. package/dist/commands/execution/logs.js +1 -0
  16. package/dist/commands/phase/move.js +8 -0
  17. package/dist/commands/phase/template/apply.js +2 -2
  18. package/dist/commands/phase/template/create.js +6 -6
  19. package/dist/commands/phase/template/list.js +1 -1
  20. package/dist/commands/status/list.js +5 -3
  21. package/dist/commands/template/phase/index.js +4 -4
  22. package/dist/commands/template/ticket/delete.d.ts +1 -1
  23. package/dist/commands/template/ticket/delete.js +4 -2
  24. package/dist/commands/ticket/create.js +1 -1
  25. package/dist/commands/ticket/edit.js +1 -1
  26. package/dist/commands/ticket/list.d.ts +2 -0
  27. package/dist/commands/ticket/list.js +39 -2
  28. package/dist/commands/ticket/update.js +2 -2
  29. package/dist/commands/work/spawn.js +32 -8
  30. package/dist/commands/work/watch.js +2 -0
  31. package/dist/lib/agents/commands.d.ts +7 -0
  32. package/dist/lib/agents/commands.js +11 -0
  33. package/dist/lib/execution/runners.js +1 -2
  34. package/dist/lib/pmo/storage/epics.js +20 -10
  35. package/dist/lib/pmo/storage/helpers.d.ts +10 -0
  36. package/dist/lib/pmo/storage/helpers.js +59 -1
  37. package/dist/lib/pmo/storage/projects.js +20 -8
  38. package/dist/lib/pmo/storage/specs.js +23 -13
  39. package/dist/lib/pmo/storage/statuses.js +39 -18
  40. package/dist/lib/pmo/storage/subtasks.js +19 -8
  41. package/dist/lib/pmo/storage/tickets.js +27 -15
  42. package/oclif.manifest.json +2742 -2713
  43. package/package.json +1 -1
@@ -3,7 +3,7 @@ import * as path from 'node:path';
3
3
  import * as fs from 'node:fs';
4
4
  import { execSync } from 'node:child_process';
5
5
  import { colors } from '../../lib/colors.js';
6
- import { getWorkspaceInfo } from '../../lib/agents/commands.js';
6
+ import { getWorkspaceInfo, formatAgentList } from '../../lib/agents/commands.js';
7
7
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
8
8
  import { isDockerRunning, getAgentContainerName, isContainerRunning, getContainerId, } from '../../lib/execution/runners.js';
9
9
  import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
@@ -76,7 +76,7 @@ export default class Login extends PMOCommand {
76
76
  // Validate agent exists
77
77
  const agent = workspaceInfo.agents.find(a => a.name === agentName);
78
78
  if (!agent) {
79
- this.error(`Agent "${agentName}" not found. Available agents: ${workspaceInfo.agents.map(a => a.name).join(', ')}`);
79
+ this.error(`Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`);
80
80
  }
81
81
  const agentDir = path.join(workspaceInfo.agentsPath, agentName);
82
82
  // Check if Docker config exists
@@ -7,6 +7,7 @@ export default class Remove extends PMOCommand {
7
7
  };
8
8
  static flags: {
9
9
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
12
  project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
13
  };
@@ -1,7 +1,7 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import inquirer from 'inquirer';
3
3
  import { colors, format } from '../../lib/colors.js';
4
- import { getWorkspaceInfo, removeAgentsFromWorkspace } from '../../lib/agents/commands.js';
4
+ import { getWorkspaceInfo, removeAgentsFromWorkspace, formatAgentList } from '../../lib/agents/commands.js';
5
5
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
6
6
  import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
7
7
  export default class Remove extends PMOCommand {
@@ -22,6 +22,11 @@ export default class Remove extends PMOCommand {
22
22
  description: 'Output prompt configuration as JSON (for AI agents/scripts)',
23
23
  default: false,
24
24
  }),
25
+ force: Flags.boolean({
26
+ char: 'f',
27
+ description: 'Skip confirmation prompt (for non-interactive use)',
28
+ default: false,
29
+ }),
25
30
  };
26
31
  getPMOOptions() {
27
32
  return { promptIfMultiple: false };
@@ -90,36 +95,39 @@ export default class Remove extends PMOCommand {
90
95
  // Validate agent exists
91
96
  const agent = workspaceInfo.agents.find((a) => a.name === agentName);
92
97
  if (!agent) {
93
- return handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available agents: ${workspaceInfo.agents.map((a) => a.name).join(', ')}`);
98
+ return handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`);
94
99
  }
95
100
  const agentsToRemove = [agentName];
96
- // Build choices once, use for both JSON and interactive modes
97
- const confirmChoices = [
98
- { name: 'No, cancel', value: 'false' },
99
- { name: 'Yes, remove agent', value: 'true' },
100
- ];
101
- const confirmMessage = `Are you sure you want to remove agent "${agentName}"? This will delete its worktree.`;
102
- // Confirm removal
103
- // In JSON mode, output confirmation prompt
104
- if (jsonMode) {
105
- outputPromptAsJson(buildPromptConfig('list', 'confirmed', confirmMessage, confirmChoices), createMetadata('agent remove', flags));
106
- return;
107
- }
108
- const { confirm } = await inquirer.prompt([
109
- {
110
- type: 'list',
111
- name: 'confirm',
112
- message: confirmMessage,
113
- choices: [
114
- { name: '❌ ' + confirmChoices[0].name, value: false },
115
- { name: '⚠️ ' + confirmChoices[1].name, value: true }
116
- ],
117
- default: 0 // Default to "No, cancel"
101
+ // Skip confirmation if --force flag is passed
102
+ if (!flags.force) {
103
+ // Build choices once, use for both JSON and interactive modes
104
+ const confirmChoices = [
105
+ { name: 'No, cancel', value: 'false' },
106
+ { name: 'Yes, remove agent', value: 'true' },
107
+ ];
108
+ const confirmMessage = `Are you sure you want to remove agent "${agentName}"? This will delete its worktree.`;
109
+ // Confirm removal
110
+ // In JSON mode, output confirmation prompt
111
+ if (jsonMode) {
112
+ outputPromptAsJson(buildPromptConfig('list', 'confirmed', confirmMessage, confirmChoices), createMetadata('agent remove', flags));
113
+ return;
114
+ }
115
+ const { confirm } = await inquirer.prompt([
116
+ {
117
+ type: 'list',
118
+ name: 'confirm',
119
+ message: confirmMessage,
120
+ choices: [
121
+ { name: '❌ ' + confirmChoices[0].name, value: false },
122
+ { name: '⚠️ ' + confirmChoices[1].name, value: true }
123
+ ],
124
+ default: 0 // Default to "No, cancel"
125
+ }
126
+ ]);
127
+ if (!confirm) {
128
+ this.log(colors.textMuted('Removal cancelled.'));
129
+ return;
118
130
  }
119
- ]);
120
- if (!confirm) {
121
- this.log(colors.textMuted('Removal cancelled.'));
122
- return;
123
131
  }
124
132
  // Remove agents
125
133
  this.log(colors.primary(`Removing agent "${agentName}"...`));
@@ -4,7 +4,7 @@ import * as fs from 'node:fs';
4
4
  import { execSync, spawn } from 'node:child_process';
5
5
  import Database from 'better-sqlite3';
6
6
  import { colors } from '../../lib/colors.js';
7
- import { getWorkspaceInfo, getAgentTmuxSessions } from '../../lib/agents/commands.js';
7
+ import { getWorkspaceInfo, getAgentTmuxSessions, formatAgentList } from '../../lib/agents/commands.js';
8
8
  import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
9
9
  import { getTerminalApp } from '../../lib/execution/config.js';
10
10
  import { isDockerRunning, getAgentContainerName, isContainerRunning, getContainerId, } from '../../lib/execution/runners.js';
@@ -74,7 +74,7 @@ export default class Shell extends PMOCommand {
74
74
  // Validate agent exists
75
75
  const agent = workspaceInfo.agents.find(a => a.name === agentName);
76
76
  if (!agent) {
77
- this.handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available agents: ${workspaceInfo.agents.map(a => a.name).join(', ')}`, errorConfig);
77
+ this.handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`, errorConfig);
78
78
  }
79
79
  // Check for existing tmux sessions (skip in JSON mode - can't handle interactive tmux)
80
80
  const existingSessions = getAgentTmuxSessions(agentName);
@@ -1,7 +1,7 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import inquirer from 'inquirer';
3
3
  import { colors, format } from '../../../lib/colors.js';
4
- import { getWorkspaceInfo, removeAgentsFromWorkspace } from '../../../lib/agents/commands.js';
4
+ import { getWorkspaceInfo, removeAgentsFromWorkspace, formatAgentList } from '../../../lib/agents/commands.js';
5
5
  import { PMOCommand, pmoBaseFlags } from '../../../lib/pmo/index.js';
6
6
  import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../../lib/prompt-json.js';
7
7
  export default class Remove extends PMOCommand {
@@ -90,7 +90,7 @@ export default class Remove extends PMOCommand {
90
90
  // Validate agent exists and is a staff agent
91
91
  const agent = staffAgents.find((a) => a.name === agentName);
92
92
  if (!agent) {
93
- return handleError('AGENT_NOT_FOUND', `Staff agent "${agentName}" not found. Available staff agents: ${staffAgents.map((a) => a.name).join(', ')}`);
93
+ return handleError('AGENT_NOT_FOUND', `Staff agent "${agentName}" not found. Available: ${formatAgentList(staffAgents)}`);
94
94
  }
95
95
  const agentsToRemove = [agentName];
96
96
  // Skip confirmation if --force flag is passed
@@ -1,6 +1,6 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import { colors, format } from '../../lib/colors.js';
3
- import { getWorkspaceInfo, getAgentStatus } from '../../lib/agents/commands.js';
3
+ import { getWorkspaceInfo, getAgentStatus, formatAgentList } from '../../lib/agents/commands.js';
4
4
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
5
5
  import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
6
6
  export default class Status extends PMOCommand {
@@ -69,7 +69,7 @@ export default class Status extends PMOCommand {
69
69
  // Validate agent exists
70
70
  const agent = workspaceInfo.agents.find((a) => a.name === agentName);
71
71
  if (!agent) {
72
- this.error(`Agent "${agentName}" not found. Available agents: ${workspaceInfo.agents.map((a) => a.name).join(', ')}`);
72
+ this.error(`Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`);
73
73
  }
74
74
  const agentStatus = getAgentStatus(workspaceInfo, agentName);
75
75
  this.log(format.title(`🤖 Agent: ${agentName}`));
@@ -4,6 +4,7 @@ export default class ThemesAddNames extends Command {
4
4
  static examples: string[];
5
5
  static args: {
6
6
  theme: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ names: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
8
  };
8
9
  static strict: boolean;
9
10
  run(): Promise<void>;
@@ -11,9 +11,13 @@ export default class ThemesAddNames extends Command {
11
11
  ];
12
12
  static args = {
13
13
  theme: Args.string({
14
- description: 'Theme ID to add names to',
14
+ description: 'Theme ID',
15
15
  required: true,
16
16
  }),
17
+ names: Args.string({
18
+ description: 'Names to add to the theme (space-separated)',
19
+ required: false,
20
+ }),
17
21
  };
18
22
  static strict = false; // Allow multiple name arguments
19
23
  async run() {
@@ -1,7 +1,7 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
2
  import * as path from 'node:path';
3
3
  import { colors } from '../../lib/colors.js';
4
- import { getWorkspaceInfo } from '../../lib/agents/commands.js';
4
+ import { getWorkspaceInfo, formatAgentList } from '../../lib/agents/commands.js';
5
5
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
6
6
  import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
7
7
  export default class Visit extends PMOCommand {
@@ -75,7 +75,7 @@ export default class Visit extends PMOCommand {
75
75
  // Validate agent exists
76
76
  const agent = workspaceInfo.agents.find(a => a.name === agentName);
77
77
  if (!agent) {
78
- return handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available agents: ${workspaceInfo.agents.map(a => a.name).join(', ')}`);
78
+ return handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`);
79
79
  }
80
80
  // Calculate path to agent directory
81
81
  const agentDir = path.join(workspaceInfo.agentsPath, agentName);
@@ -97,6 +97,23 @@ export default class EpicLink extends PMOCommand {
97
97
  return;
98
98
  }
99
99
  // Interactive mode: show menu in a loop
100
+ // In JSON mode, output the interactive menu config instead of prompting
101
+ if (jsonMode) {
102
+ const menuChoices = [
103
+ { name: 'View dependencies', value: 'view' },
104
+ { name: 'Add blocking dependency (blocked by...)', value: 'blocks' },
105
+ { name: 'Add relates_to dependency', value: 'relates_to' },
106
+ { name: 'Add duplicates dependency', value: 'duplicates' },
107
+ { name: 'Remove dependency', value: 'remove' },
108
+ { name: 'Done', value: 'done' },
109
+ ];
110
+ outputPromptAsJson(buildPromptConfig('list', 'action', `Dependencies for ${epicId}:`, menuChoices), createMetadata('epic link', flags));
111
+ return;
112
+ }
113
+ // Check for TTY before showing interactive menu
114
+ if (!process.stdin.isTTY) {
115
+ this.error('Interactive mode requires a TTY. Use --json for scripted usage or provide flags like --blocks, --relates, or --duplicates.');
116
+ }
100
117
  let continueLoop = true;
101
118
  while (continueLoop) {
102
119
  // eslint-disable-next-line no-await-in-loop -- Interactive user loop
@@ -348,6 +348,28 @@ export default class ExecutionConfig extends PMOCommand {
348
348
  }
349
349
  setConfigValue(db, key, value, jsonMode) {
350
350
  const normalizedKey = key.toLowerCase();
351
+ // Define valid values for each config key
352
+ const VALID_VALUES = {
353
+ defaultenvironment: ['host', 'devcontainer', 'docker', 'vm'],
354
+ outputmode: ['interactive', 'print'],
355
+ sandboxed: ['true', 'false'],
356
+ 'terminal.app': ['Terminal', 'iTerm', 'Alacritty', 'Ghostty', 'Kitty', 'tmux', 'Warp', 'WezTerm'],
357
+ 'terminal.openinbackground': ['true', 'false'],
358
+ shell: ['bash', 'zsh', 'fish'],
359
+ 'tmux.controlmode': ['true', 'false'],
360
+ };
361
+ // Validate value against allowed options
362
+ const validValues = VALID_VALUES[normalizedKey];
363
+ if (validValues && !validValues.includes(value)) {
364
+ const errorMsg = `Invalid value "${value}" for ${key}. Valid options: ${validValues.join(', ')}`;
365
+ if (jsonMode) {
366
+ outputErrorAsJson('INVALID_VALUE', errorMsg, createMetadata('execution config', {}));
367
+ }
368
+ else {
369
+ this.error(errorMsg);
370
+ }
371
+ return;
372
+ }
351
373
  switch (normalizedKey) {
352
374
  case 'defaultenvironment':
353
375
  saveExecutionSetting(db, 'defaultMode', value);
@@ -5,5 +5,8 @@ import ExecutionStop from './stop.js';
5
5
  */
6
6
  export default class ExecutionKill extends ExecutionStop {
7
7
  static description: string;
8
+ static args: {
9
+ id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
10
+ };
8
11
  static examples: string[];
9
12
  }
@@ -5,6 +5,7 @@ import ExecutionStop from './stop.js';
5
5
  */
6
6
  export default class ExecutionKill extends ExecutionStop {
7
7
  static description = 'Stop running execution(s) (alias for "execution stop")';
8
+ static args = ExecutionStop.args;
8
9
  static examples = [
9
10
  '<%= config.bin %> <%= command.id %> WORK-001',
10
11
  '<%= config.bin %> <%= command.id %> WORK-001 --force',
@@ -28,6 +28,7 @@ export default class ExecutionList extends PMOCommand {
28
28
  char: 'l',
29
29
  description: 'Number of results',
30
30
  default: 20,
31
+ min: 1,
31
32
  }),
32
33
  };
33
34
  getPMOOptions() {
@@ -61,10 +62,10 @@ export default class ExecutionList extends PMOCommand {
61
62
  this.log('');
62
63
  this.log(styles.header('🚀 Agent Work'));
63
64
  this.log('═'.repeat(100));
64
- this.log(styles.muted(padEnd('ID', 11) +
65
+ this.log(styles.muted(padEnd('ID', 14) +
65
66
  padEnd('Ticket', 9) +
66
67
  padEnd('Agent', 10) +
67
- padEnd('Env', 12) +
68
+ padEnd('Env', 13) +
68
69
  padEnd('Display', 11) +
69
70
  padEnd('Perms', 8) +
70
71
  padEnd('Status', 10) +
@@ -78,10 +79,10 @@ export default class ExecutionList extends PMOCommand {
78
79
  const envStr = `${envIcon} ${exec.environment}`;
79
80
  const permsStr = exec.sandboxed ? 'safe' : 'danger';
80
81
  const permsColor = exec.sandboxed ? styles.success : styles.warning;
81
- this.log(padEnd(exec.id, 11) +
82
+ this.log(padEnd(exec.id, 14) +
82
83
  padEnd(exec.ticketId, 9) +
83
84
  padEnd(exec.agentName, 10) +
84
- padEnd(envStr, 12) +
85
+ padEnd(envStr, 13) +
85
86
  padEnd(exec.displayMode, 11) +
86
87
  permsColor(padEnd(permsStr, 8)) +
87
88
  statusColor(padEnd(exec.status, 10)) +
@@ -32,6 +32,7 @@ export default class ExecutionLogs extends PMOCommand {
32
32
  tail: Flags.integer({
33
33
  char: 'n',
34
34
  description: 'Show last n lines',
35
+ min: 1,
35
36
  }),
36
37
  json: Flags.boolean({
37
38
  description: 'Output prompt configuration as JSON (for AI agents/scripts)',
@@ -101,9 +101,17 @@ export default class PhaseMove extends PMOCommand {
101
101
  }
102
102
  newPosition = parseInt(resolved.position, 10);
103
103
  }
104
+ // Get phases in same category for validation
105
+ const allPhases = await this.storage.listPhases();
106
+ const categoryPhases = allPhases.filter(p => p.category === phase.category);
107
+ const maxPosition = categoryPhases.length - 1;
104
108
  if (newPosition < 0) {
105
109
  this.error('Position must be >= 0');
106
110
  }
111
+ if (newPosition > maxPosition) {
112
+ this.warn(`Position ${newPosition} exceeds max (${maxPosition}). Clamping to ${maxPosition}.`);
113
+ newPosition = maxPosition;
114
+ }
107
115
  const updated = await this.storage.reorderPhase(phaseId, newPosition);
108
116
  if (phase.position === updated.position) {
109
117
  this.log(styles.muted(`Phase "${updated.name}" is already at position ${updated.position}`));
@@ -48,7 +48,7 @@ export default class PhaseTemplateApply extends PMOCommand {
48
48
  if (!templateId) {
49
49
  const templates = await this.storage.listPhaseTemplates();
50
50
  if (templates.length === 0) {
51
- return handleError('NO_TEMPLATES', `No phase templates found.\nCreate one with: prlt phase template create "Template Name"`);
51
+ return handleError('NO_TEMPLATES', `No phase templates found.\nCreate one with: prlt template phase create "Template Name"`);
52
52
  }
53
53
  const { selectedTemplate } = await inquirer.prompt([{
54
54
  type: 'list',
@@ -64,7 +64,7 @@ export default class PhaseTemplateApply extends PMOCommand {
64
64
  // Verify template exists
65
65
  const template = await this.storage.getPhaseTemplate(templateId);
66
66
  if (!template) {
67
- return handleError('TEMPLATE_NOT_FOUND', `Phase template not found: ${templateId}. Run 'prlt phase template list' to see available templates.`);
67
+ return handleError('TEMPLATE_NOT_FOUND', `Phase template not found: ${templateId}. Run 'prlt template phase list' to see available templates.`);
68
68
  }
69
69
  // Check if workspace has existing phases
70
70
  const existingPhases = await this.storage.listPhases();
@@ -32,8 +32,8 @@ export default class PhaseTemplateCreate extends PMOCommand {
32
32
  const jsonMode = shouldOutputJson(flags);
33
33
  // Build base command with positional arg if name provided
34
34
  const baseCmd = args.name
35
- ? `prlt phase template create "${args.name}"`
36
- : 'prlt phase template create';
35
+ ? `prlt template phase create "${args.name}"`
36
+ : 'prlt template phase create';
37
37
  // Use FlagResolver for unified JSON mode and interactive handling
38
38
  const resolver = new FlagResolver({
39
39
  commandName: 'phase template create',
@@ -54,11 +54,11 @@ export default class PhaseTemplateCreate extends PMOCommand {
54
54
  message: 'Template name:',
55
55
  validate: (value) => value.length > 0 || 'Name is required',
56
56
  context: {
57
- hint: 'Provide name with: prlt phase template create "Template Name"',
58
- example: 'prlt phase template create "My Phases" --description "Custom phases"',
57
+ hint: 'Provide name with: prlt template phase create "Template Name"',
58
+ example: 'prlt template phase create "My Phases" --description "Custom phases"',
59
59
  },
60
60
  // For input prompts, the agent will re-run with the positional arg
61
- getCommand: (value) => `prlt phase template create "${value}" --json`,
61
+ getCommand: (value) => `prlt template phase create "${value}" --json`,
62
62
  });
63
63
  }
64
64
  // Description prompt - optional (only in interactive mode without --json)
@@ -75,7 +75,7 @@ export default class PhaseTemplateCreate extends PMOCommand {
75
75
  const templateName = args.name || resolved.name;
76
76
  // Validate required fields
77
77
  if (!templateName) {
78
- this.error('Name is required. Provide as positional argument: prlt phase template create "Template Name"');
78
+ this.error('Name is required. Provide as positional argument: prlt template phase create "Template Name"');
79
79
  }
80
80
  // Get description from flags or resolved
81
81
  const description = flags.description ?? resolved.description ?? undefined;
@@ -62,7 +62,7 @@ export default class PhaseTemplateList extends PMOCommand {
62
62
  }
63
63
  }
64
64
  this.log('');
65
- this.log(styles.muted('Apply a template: prlt phase template apply <template-id>'));
65
+ this.log(styles.muted('Apply a template: prlt template phase apply <template-id>'));
66
66
  this.log('');
67
67
  }
68
68
  printTemplate(template) {
@@ -35,7 +35,11 @@ export default class StatusList extends PMOCommand {
35
35
  if (!project?.workflowId) {
36
36
  this.error(`Project "${projectId}" has no workflow assigned.`);
37
37
  }
38
- const statuses = await this.storage.listStatuses(project.workflowId);
38
+ const allStatuses = await this.storage.listStatuses(project.workflowId);
39
+ // Apply category filter if specified
40
+ const statuses = flags.category
41
+ ? allStatuses.filter(s => s.category === flags.category)
42
+ : allStatuses;
39
43
  if (jsonMode) {
40
44
  this.log(JSON.stringify(statuses, null, 2));
41
45
  return;
@@ -59,8 +63,6 @@ export default class StatusList extends PMOCommand {
59
63
  canceled: '🚫',
60
64
  };
61
65
  for (const category of STATE_CATEGORY_ORDER) {
62
- if (flags.category && flags.category !== category)
63
- continue;
64
66
  const categoryStatuses = grouped.get(category);
65
67
  if (!categoryStatuses || categoryStatuses.length === 0)
66
68
  continue;
@@ -30,10 +30,10 @@ export default class TemplatePhase extends Command {
30
30
  message: 'What would you like to do?',
31
31
  choices: () => [
32
32
  { name: 'List phase templates', value: 'list', command: 'prlt template phase list --json' },
33
- { name: 'Apply a phase template to project', value: 'apply', command: 'prlt phase template apply --json' },
34
- { name: 'Create a new phase template', value: 'create', command: 'prlt phase template create --json' },
35
- { name: 'Update a phase template', value: 'update', command: 'prlt phase template update --json' },
36
- { name: 'Delete a phase template', value: 'delete', command: 'prlt phase template delete --json' },
33
+ { name: 'Apply a phase template to project', value: 'apply', command: 'prlt template phase apply --json' },
34
+ { name: 'Create a new phase template', value: 'create', command: 'prlt template phase create --json' },
35
+ { name: 'Update a phase template', value: 'update', command: 'prlt template phase update --json' },
36
+ { name: 'Delete a phase template', value: 'delete', command: 'prlt template phase delete --json' },
37
37
  ],
38
38
  });
39
39
  // In JSON mode, this outputs the prompt and exits
@@ -3,7 +3,7 @@ export default class TemplateTicketDelete extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
6
- id: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
6
+ id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
7
  };
8
8
  static flags: {
9
9
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -8,7 +8,7 @@ export default class TemplateTicketDelete extends Command {
8
8
  static args = {
9
9
  id: Args.string({
10
10
  description: 'Template ID to delete',
11
- required: true,
11
+ required: false,
12
12
  }),
13
13
  };
14
14
  static flags = {
@@ -24,7 +24,9 @@ export default class TemplateTicketDelete extends Command {
24
24
  };
25
25
  async run() {
26
26
  const { args, flags } = await this.parse(TemplateTicketDelete);
27
- const cmdArgs = [args.id];
27
+ const cmdArgs = [];
28
+ if (args.id)
29
+ cmdArgs.push(args.id);
28
30
  if (flags.force)
29
31
  cmdArgs.push('--force');
30
32
  if (flags.json)
@@ -11,7 +11,7 @@ export default class TicketCreate extends PMOCommand {
11
11
  static examples = [
12
12
  '<%= config.bin %> <%= command.id %>',
13
13
  '<%= config.bin %> <%= command.id %> --title "Fix login bug" --column Backlog',
14
- '<%= config.bin %> <%= command.id %> -t "Add feature" -c "In Progress" -p HIGH',
14
+ '<%= config.bin %> <%= command.id %> -t "Add feature" -c "In Progress" -p P1',
15
15
  '<%= config.bin %> <%= command.id %> --project mobile-app -t "New feature"',
16
16
  '<%= config.bin %> <%= command.id %> --epic EPIC-001 -t "Implement auth flow"',
17
17
  '<%= config.bin %> <%= command.id %> --json # Output column choices as JSON',
@@ -9,7 +9,7 @@ export default class TicketEdit extends PMOCommand {
9
9
  static examples = [
10
10
  '<%= config.bin %> <%= command.id %> TICK-001',
11
11
  '<%= config.bin %> <%= command.id %> TICK-001 --title "New title"',
12
- '<%= config.bin %> <%= command.id %> TICK-001 --priority HIGH --category bug',
12
+ '<%= config.bin %> <%= command.id %> TICK-001 --priority P1 --category bug',
13
13
  '<%= config.bin %> <%= command.id %> TICK-001 --add-subtask "Implement feature" --add-subtask "Write tests"',
14
14
  '<%= config.bin %> <%= command.id %> TICK-001 --owner "john" --assignee "agent-1"',
15
15
  '<%= config.bin %> <%= command.id %> # Interactive mode',
@@ -10,6 +10,8 @@ export default class TicketList extends Command {
10
10
  format: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
11
  all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
12
  'group-by': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
+ limit: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ offset: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
15
  machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
16
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
17
  project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -10,13 +10,15 @@ export default class TicketList extends Command {
10
10
  static examples = [
11
11
  '<%= config.bin %> <%= command.id %>',
12
12
  '<%= config.bin %> <%= command.id %> --column Backlog',
13
- '<%= config.bin %> <%= command.id %> --priority URGENT',
13
+ '<%= config.bin %> <%= command.id %> --priority P0',
14
14
  '<%= config.bin %> <%= command.id %> --category bug',
15
15
  '<%= config.bin %> <%= command.id %> --search "login"',
16
16
  '<%= config.bin %> <%= command.id %> --project mobile-app',
17
17
  '<%= config.bin %> <%= command.id %> --all',
18
18
  '<%= config.bin %> <%= command.id %> --all --group-by priority',
19
19
  '<%= config.bin %> <%= command.id %> -g priority',
20
+ '<%= config.bin %> <%= command.id %> --limit 10',
21
+ '<%= config.bin %> <%= command.id %> --limit 10 --offset 20',
20
22
  ];
21
23
  static flags = {
22
24
  ...pmoBaseFlags,
@@ -53,6 +55,15 @@ export default class TicketList extends Command {
53
55
  options: ['status', 'priority'],
54
56
  default: 'status',
55
57
  }),
58
+ limit: Flags.integer({
59
+ char: 'l',
60
+ description: 'Maximum number of tickets to display',
61
+ min: 1,
62
+ }),
63
+ offset: Flags.integer({
64
+ description: 'Skip first N tickets (for pagination)',
65
+ min: 0,
66
+ }),
56
67
  };
57
68
  async run() {
58
69
  const { flags } = await this.parse(TicketList);
@@ -85,7 +96,33 @@ export default class TicketList extends Command {
85
96
  }
86
97
  // Determine projectId for the query
87
98
  const projectId = flags.all ? undefined : (filter.projectId || undefined);
88
- const tickets = await pmoContext.storage.listTickets(projectId, filter);
99
+ // Validate project if specified (not in --all mode)
100
+ if (flags.project && !flags.all) {
101
+ const project = await pmoContext.storage.getProject(flags.project);
102
+ if (!project) {
103
+ const allProjects = await pmoContext.storage.listProjectSummaries();
104
+ const validProjectIds = allProjects.map(p => p.id);
105
+ this.error(`Project "${flags.project}" not found. Valid projects: ${validProjectIds.join(', ')}`);
106
+ }
107
+ }
108
+ // Validate column if specified (requires knowing the project)
109
+ if (flags.column && !flags.all) {
110
+ // Get the project board to validate the column
111
+ const targetProjectId = projectId || (await pmoContext.storage.listProjectSummaries())[0]?.id;
112
+ if (targetProjectId) {
113
+ const board = await pmoContext.storage.getBoard(targetProjectId);
114
+ const validColumns = board.columns.map(c => c.name);
115
+ if (!validColumns.includes(flags.column)) {
116
+ this.error(`Column "${flags.column}" not found. Valid columns: ${validColumns.join(', ')}`);
117
+ }
118
+ }
119
+ }
120
+ let tickets = await pmoContext.storage.listTickets(projectId, filter);
121
+ // Apply pagination
122
+ if (flags.offset)
123
+ tickets = tickets.slice(flags.offset);
124
+ if (flags.limit)
125
+ tickets = tickets.slice(0, flags.limit);
89
126
  if (tickets.length === 0) {
90
127
  this.log(styles.warning('No tickets found.'));
91
128
  return;
@@ -7,10 +7,10 @@ import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/
7
7
  export default class TicketUpdate extends PMOCommand {
8
8
  static description = 'Update priority/category for ticket(s)';
9
9
  static examples = [
10
- '<%= config.bin %> <%= command.id %> TKT-001 --priority HIGH',
10
+ '<%= config.bin %> <%= command.id %> TKT-001 --priority P1',
11
11
  '<%= config.bin %> <%= command.id %> TKT-001 --category bug',
12
12
  '<%= config.bin %> <%= command.id %> --bulk',
13
- '<%= config.bin %> <%= command.id %> --bulk --priority HIGH',
13
+ '<%= config.bin %> <%= command.id %> --bulk --priority P1',
14
14
  '<%= config.bin %> <%= command.id %> --json # Output choices as JSON',
15
15
  ];
16
16
  static args = {