@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.
- package/dist/commands/agent/login.js +2 -2
- package/dist/commands/agent/remove.d.ts +1 -0
- package/dist/commands/agent/remove.js +36 -28
- package/dist/commands/agent/shell.js +2 -2
- package/dist/commands/agent/staff/remove.js +2 -2
- package/dist/commands/agent/status.js +2 -2
- package/dist/commands/agent/themes/add-names.d.ts +1 -0
- package/dist/commands/agent/themes/add-names.js +5 -1
- package/dist/commands/agent/visit.js +2 -2
- package/dist/commands/epic/link/index.js +17 -0
- package/dist/commands/execution/config.js +22 -0
- package/dist/commands/execution/kill.d.ts +3 -0
- package/dist/commands/execution/kill.js +1 -0
- package/dist/commands/execution/list.js +5 -4
- package/dist/commands/execution/logs.js +1 -0
- package/dist/commands/phase/move.js +8 -0
- package/dist/commands/phase/template/apply.js +2 -2
- package/dist/commands/phase/template/create.js +6 -6
- package/dist/commands/phase/template/list.js +1 -1
- package/dist/commands/status/list.js +5 -3
- package/dist/commands/template/phase/index.js +4 -4
- package/dist/commands/template/ticket/delete.d.ts +1 -1
- package/dist/commands/template/ticket/delete.js +4 -2
- package/dist/commands/ticket/create.js +1 -1
- package/dist/commands/ticket/edit.js +1 -1
- package/dist/commands/ticket/list.d.ts +2 -0
- package/dist/commands/ticket/list.js +39 -2
- package/dist/commands/ticket/update.js +2 -2
- package/dist/commands/work/spawn.js +32 -8
- package/dist/commands/work/watch.js +2 -0
- package/dist/lib/agents/commands.d.ts +7 -0
- package/dist/lib/agents/commands.js +11 -0
- package/dist/lib/execution/runners.js +1 -2
- package/dist/lib/pmo/storage/epics.js +20 -10
- package/dist/lib/pmo/storage/helpers.d.ts +10 -0
- package/dist/lib/pmo/storage/helpers.js +59 -1
- package/dist/lib/pmo/storage/projects.js +20 -8
- package/dist/lib/pmo/storage/specs.js +23 -13
- package/dist/lib/pmo/storage/statuses.js +39 -18
- package/dist/lib/pmo/storage/subtasks.js +19 -8
- package/dist/lib/pmo/storage/tickets.js +27 -15
- package/oclif.manifest.json +2742 -2713
- 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
|
|
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
|
|
98
|
+
return handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`);
|
|
94
99
|
}
|
|
95
100
|
const agentsToRemove = [agentName];
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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',
|
|
65
|
+
this.log(styles.muted(padEnd('ID', 14) +
|
|
65
66
|
padEnd('Ticket', 9) +
|
|
66
67
|
padEnd('Agent', 10) +
|
|
67
|
-
padEnd('Env',
|
|
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,
|
|
82
|
+
this.log(padEnd(exec.id, 14) +
|
|
82
83
|
padEnd(exec.ticketId, 9) +
|
|
83
84
|
padEnd(exec.agentName, 10) +
|
|
84
|
-
padEnd(envStr,
|
|
85
|
+
padEnd(envStr, 13) +
|
|
85
86
|
padEnd(exec.displayMode, 11) +
|
|
86
87
|
permsColor(padEnd(permsStr, 8)) +
|
|
87
88
|
statusColor(padEnd(exec.status, 10)) +
|
|
@@ -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
|
|
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
|
|
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
|
|
36
|
-
: 'prlt phase
|
|
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
|
|
58
|
-
example: 'prlt phase
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
34
|
-
{ name: 'Create a new phase template', value: 'create', command: 'prlt phase
|
|
35
|
-
{ name: 'Update a phase template', value: 'update', command: 'prlt phase
|
|
36
|
-
{ name: 'Delete a phase template', value: 'delete', command: 'prlt phase
|
|
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:
|
|
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 = [
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 = {
|