@proletariat/cli 0.3.26 → 0.3.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/action/show.js +7 -1
- package/dist/commands/branch/list.js +14 -11
- package/dist/commands/branch/validate.js +10 -1
- package/dist/commands/docker/clean.js +7 -9
- package/dist/commands/docker/index.js +5 -4
- package/dist/commands/docker/list.d.ts +1 -0
- package/dist/commands/docker/list.js +31 -17
- package/dist/commands/docker/status.d.ts +3 -1
- package/dist/commands/docker/status.js +28 -2
- package/dist/commands/docker/sync.js +7 -6
- package/dist/commands/epic/list.js +17 -2
- package/dist/commands/execution/list.js +25 -17
- package/dist/commands/pmo/init.js +22 -3
- package/dist/commands/repo/list.js +14 -8
- package/dist/commands/repo/view.js +2 -1
- package/dist/commands/roadmap/list.js +16 -1
- package/dist/commands/session/health.js +11 -10
- package/dist/commands/session/list.js +15 -8
- package/dist/commands/staff/list.d.ts +3 -1
- package/dist/commands/staff/list.js +15 -1
- package/dist/commands/theme/list.d.ts +3 -0
- package/dist/commands/theme/list.js +25 -0
- package/dist/commands/ticket/complete.js +4 -1
- package/dist/commands/ticket/create.d.ts +1 -0
- package/dist/commands/ticket/create.js +30 -0
- package/dist/commands/ticket/delete.js +3 -3
- package/dist/commands/ticket/edit.js +2 -2
- package/dist/commands/ticket/list.js +24 -5
- package/dist/commands/ticket/move.js +4 -1
- package/dist/commands/ticket/view.js +4 -2
- package/dist/commands/whoami.d.ts +3 -0
- package/dist/commands/whoami.js +22 -5
- package/dist/commands/work/complete.js +2 -2
- package/dist/commands/work/ready.js +2 -2
- package/dist/commands/work/revise.js +2 -2
- package/dist/commands/work/start.js +4 -4
- package/dist/commands/workspace/prune.d.ts +3 -2
- package/dist/commands/workspace/prune.js +70 -10
- package/dist/lib/agents/commands.js +4 -0
- package/dist/lib/agents/index.js +12 -0
- package/dist/lib/execution/devcontainer.d.ts +4 -0
- package/dist/lib/execution/devcontainer.js +63 -0
- package/dist/lib/mcp/helpers.d.ts +15 -0
- package/dist/lib/mcp/helpers.js +15 -0
- package/dist/lib/mcp/tools/action.js +5 -5
- package/dist/lib/mcp/tools/board.js +7 -7
- package/dist/lib/mcp/tools/category.js +5 -5
- package/dist/lib/mcp/tools/cli-passthrough.js +30 -30
- package/dist/lib/mcp/tools/epic.js +8 -8
- package/dist/lib/mcp/tools/phase.js +7 -7
- package/dist/lib/mcp/tools/project.js +10 -10
- package/dist/lib/mcp/tools/roadmap.js +7 -7
- package/dist/lib/mcp/tools/spec.js +9 -9
- package/dist/lib/mcp/tools/status.js +6 -6
- package/dist/lib/mcp/tools/template.js +6 -6
- package/dist/lib/mcp/tools/ticket.js +19 -19
- package/dist/lib/mcp/tools/view.js +4 -4
- package/dist/lib/mcp/tools/work.js +6 -6
- package/dist/lib/mcp/tools/workflow.js +5 -5
- package/dist/lib/pmo/index.js +4 -0
- package/dist/lib/pmo/storage/base.js +49 -0
- package/dist/lib/pr/index.d.ts +5 -0
- package/dist/lib/pr/index.js +69 -0
- package/dist/lib/repos/index.js +4 -0
- package/dist/lib/string-utils.d.ts +10 -0
- package/dist/lib/string-utils.js +16 -0
- package/oclif.manifest.json +2266 -2189
- package/package.json +3 -2
|
@@ -4,6 +4,7 @@ import { colors, format } from '../../lib/colors.js';
|
|
|
4
4
|
import { findHQRoot, getWorkspaceRepoInfo, } from '../../lib/repos/index.js';
|
|
5
5
|
import { openWorkspaceDatabase } from '../../lib/database/index.js';
|
|
6
6
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
7
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
7
8
|
export default class View extends PMOCommand {
|
|
8
9
|
static description = 'View detailed information about a repository';
|
|
9
10
|
static examples = [
|
|
@@ -113,7 +114,7 @@ export default class View extends PMOCommand {
|
|
|
113
114
|
for (const wt of worktrees) {
|
|
114
115
|
const wtStatus = wt.is_clean ? 'clean' : 'dirty';
|
|
115
116
|
const wtColor = wt.is_clean ? colors.repoClean : colors.repoDirty;
|
|
116
|
-
let line = ` • ${colors.agentName(wt.agent_name
|
|
117
|
+
let line = ` • ${colors.agentName(visualPadEnd(wt.agent_name, 10))} - ${wtColor(wtStatus)}`;
|
|
117
118
|
if (wt.commits_ahead > 0) {
|
|
118
119
|
line += colors.commitsAhead(` (${wt.commits_ahead} ahead)`);
|
|
119
120
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
2
2
|
import { styles } from '../../lib/styles.js';
|
|
3
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
3
4
|
export default class RoadmapList extends PMOCommand {
|
|
4
5
|
static description = 'List all roadmaps';
|
|
5
6
|
static examples = [
|
|
@@ -12,17 +13,31 @@ export default class RoadmapList extends PMOCommand {
|
|
|
12
13
|
return { promptIfMultiple: false };
|
|
13
14
|
}
|
|
14
15
|
async execute() {
|
|
16
|
+
const { flags } = await this.parse(RoadmapList);
|
|
17
|
+
const jsonMode = shouldOutputJson(flags);
|
|
15
18
|
const roadmaps = await this.storage.listRoadmaps();
|
|
16
19
|
if (roadmaps.length === 0) {
|
|
20
|
+
if (jsonMode) {
|
|
21
|
+
this.log(JSON.stringify([], null, 2));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
17
24
|
this.log(styles.muted('No roadmaps found. Create one with: prlt roadmap create'));
|
|
18
25
|
return;
|
|
19
26
|
}
|
|
20
|
-
this.log(styles.title('\nRoadmaps\n'));
|
|
21
27
|
// Fetch all project counts in parallel
|
|
22
28
|
const projectCounts = await Promise.all(roadmaps.map(async (roadmap) => {
|
|
23
29
|
const projects = await this.storage.listRoadmapProjects(roadmap.id);
|
|
24
30
|
return projects.length;
|
|
25
31
|
}));
|
|
32
|
+
if (jsonMode) {
|
|
33
|
+
const roadmapsWithCounts = roadmaps.map((roadmap, i) => ({
|
|
34
|
+
...roadmap,
|
|
35
|
+
projectCount: projectCounts[i],
|
|
36
|
+
}));
|
|
37
|
+
this.log(JSON.stringify(roadmapsWithCounts, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
this.log(styles.title('\nRoadmaps\n'));
|
|
26
41
|
for (let i = 0; i < roadmaps.length; i++) {
|
|
27
42
|
const roadmap = roadmaps[i];
|
|
28
43
|
const markers = [];
|
|
@@ -7,6 +7,7 @@ import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
|
7
7
|
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
8
8
|
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
9
9
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
10
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
10
11
|
import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
11
12
|
// =============================================================================
|
|
12
13
|
// Detection Logic
|
|
@@ -361,11 +362,11 @@ export default class SessionHealth extends PMOCommand {
|
|
|
361
362
|
this.log(styles.header('Agent Health Status'));
|
|
362
363
|
this.log('═'.repeat(95));
|
|
363
364
|
this.log(styles.muted(' ' +
|
|
364
|
-
'Ticket'
|
|
365
|
-
'Agent'
|
|
366
|
-
'State'
|
|
367
|
-
'Type'
|
|
368
|
-
'Elapsed'
|
|
365
|
+
visualPadEnd('Ticket', 12) +
|
|
366
|
+
visualPadEnd('Agent', 20) +
|
|
367
|
+
visualPadEnd('State', 12) +
|
|
368
|
+
visualPadEnd('Type', 15) +
|
|
369
|
+
visualPadEnd('Elapsed', 10) +
|
|
369
370
|
'Session'));
|
|
370
371
|
this.log(' ' + '─'.repeat(93));
|
|
371
372
|
for (const agent of agents) {
|
|
@@ -384,11 +385,11 @@ export default class SessionHealth extends PMOCommand {
|
|
|
384
385
|
? agent.sessionId.substring(0, 21) + '...'
|
|
385
386
|
: agent.sessionId;
|
|
386
387
|
this.log(' ' +
|
|
387
|
-
agent.ticketId
|
|
388
|
-
agent.agentName
|
|
389
|
-
stateColor(`${stateIcon} ${agent.state}
|
|
390
|
-
typeIcon
|
|
391
|
-
agent.elapsed
|
|
388
|
+
visualPadEnd(agent.ticketId, 12) +
|
|
389
|
+
visualPadEnd(agent.agentName, 20) +
|
|
390
|
+
stateColor(visualPadEnd(`${stateIcon} ${agent.state}`, 12)) +
|
|
391
|
+
visualPadEnd(typeIcon, 15) +
|
|
392
|
+
visualPadEnd(agent.elapsed, 10) +
|
|
392
393
|
styles.muted(displaySession));
|
|
393
394
|
}
|
|
394
395
|
this.log('');
|
|
@@ -6,6 +6,8 @@ import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
|
6
6
|
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
7
7
|
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
8
8
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
9
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
10
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
9
11
|
export default class SessionList extends PMOCommand {
|
|
10
12
|
static description = 'List active tmux sessions (host and container)';
|
|
11
13
|
static examples = [
|
|
@@ -25,6 +27,7 @@ export default class SessionList extends PMOCommand {
|
|
|
25
27
|
}
|
|
26
28
|
async execute() {
|
|
27
29
|
const { flags } = await this.parse(SessionList);
|
|
30
|
+
const jsonMode = shouldOutputJson(flags);
|
|
28
31
|
// Get workspace info for execution records
|
|
29
32
|
let executionStorage = null;
|
|
30
33
|
let db = null;
|
|
@@ -153,15 +156,19 @@ export default class SessionList extends PMOCommand {
|
|
|
153
156
|
});
|
|
154
157
|
}
|
|
155
158
|
}
|
|
159
|
+
if (jsonMode) {
|
|
160
|
+
this.log(JSON.stringify(sessions, null, 2));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
156
163
|
if (sessions.length > 0) {
|
|
157
164
|
this.log('');
|
|
158
165
|
this.log(styles.header('🖥️ Active Sessions'));
|
|
159
166
|
this.log('═'.repeat(90));
|
|
160
167
|
this.log(styles.muted(' ' +
|
|
161
|
-
'Session'
|
|
162
|
-
'Ticket'
|
|
163
|
-
'Agent'
|
|
164
|
-
'Type'
|
|
168
|
+
visualPadEnd('Session', 34) +
|
|
169
|
+
visualPadEnd('Ticket', 12) +
|
|
170
|
+
visualPadEnd('Agent', 18) +
|
|
171
|
+
visualPadEnd('Type', 15) +
|
|
165
172
|
'Status'));
|
|
166
173
|
this.log(' ' + '─'.repeat(88));
|
|
167
174
|
for (const session of sessions) {
|
|
@@ -177,10 +184,10 @@ export default class SessionList extends PMOCommand {
|
|
|
177
184
|
? session.sessionId.substring(0, 29) + '...'
|
|
178
185
|
: session.sessionId;
|
|
179
186
|
this.log(' ' +
|
|
180
|
-
displaySession
|
|
181
|
-
session.ticketId
|
|
182
|
-
session.agentName
|
|
183
|
-
typeIcon
|
|
187
|
+
visualPadEnd(displaySession, 34) +
|
|
188
|
+
visualPadEnd(session.ticketId, 12) +
|
|
189
|
+
visualPadEnd(session.agentName, 18) +
|
|
190
|
+
visualPadEnd(typeIcon, 15) +
|
|
184
191
|
statusColor(statusText));
|
|
185
192
|
}
|
|
186
193
|
this.log('');
|
|
@@ -2,6 +2,8 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
export default class List extends Command {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
|
-
static flags: {
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
};
|
|
6
8
|
run(): Promise<void>;
|
|
7
9
|
}
|
|
@@ -3,25 +3,39 @@ import chalk from 'chalk';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
5
|
import { getWorkspaceInfo, getAllAgentsStatus } from '../../lib/agents/commands.js';
|
|
6
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
7
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
6
8
|
export default class List extends Command {
|
|
7
9
|
static description = 'List all staff (persistent) agents and their status';
|
|
8
10
|
static examples = [
|
|
9
11
|
'<%= config.bin %> <%= command.id %>',
|
|
10
12
|
];
|
|
11
|
-
static flags = {
|
|
13
|
+
static flags = {
|
|
14
|
+
...machineOutputFlags,
|
|
15
|
+
};
|
|
12
16
|
async run() {
|
|
17
|
+
const { flags } = await this.parse(List);
|
|
18
|
+
const jsonMode = shouldOutputJson(flags);
|
|
13
19
|
try {
|
|
14
20
|
// Get workspace information
|
|
15
21
|
const workspaceInfo = getWorkspaceInfo();
|
|
16
22
|
// Filter to staff agents only
|
|
17
23
|
const staffAgents = workspaceInfo.agents.filter(a => a.type === 'persistent');
|
|
18
24
|
if (staffAgents.length === 0) {
|
|
25
|
+
if (jsonMode) {
|
|
26
|
+
this.log(JSON.stringify([], null, 2));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
19
29
|
this.log(chalk.yellow('No staff agents found. Add staff agents with "prlt staff add"'));
|
|
20
30
|
return;
|
|
21
31
|
}
|
|
22
32
|
// Get status for all agents and filter to staff
|
|
23
33
|
const agentsStatus = getAllAgentsStatus(workspaceInfo);
|
|
24
34
|
const staffStatus = agentsStatus.filter(a => staffAgents.some(s => s.name === a.name));
|
|
35
|
+
if (jsonMode) {
|
|
36
|
+
this.log(JSON.stringify(staffStatus, null, 2));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
25
39
|
this.log(chalk.bold.cyan('\n Staff Agents:\n'));
|
|
26
40
|
for (const agentStatus of staffStatus) {
|
|
27
41
|
// Agent info line
|
|
@@ -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);
|
|
@@ -59,7 +59,10 @@ export default class TicketComplete extends PMOCommand {
|
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
// Get board for columns (use the first incomplete ticket's project)
|
|
62
|
-
const board = await this.storage.
|
|
62
|
+
const board = await this.storage.getProjectBoard(incompleteTickets[0].projectId);
|
|
63
|
+
if (!board) {
|
|
64
|
+
return handleError('PROJECT_NOT_FOUND', `Project "${incompleteTickets[0].projectId}" not found. The ticket may belong to an orphaned project.`);
|
|
65
|
+
}
|
|
63
66
|
// Find the "Done" column (case-insensitive)
|
|
64
67
|
const doneColumn = board.columns.find(col => col.name.toLowerCase().includes('done'));
|
|
65
68
|
if (!doneColumn) {
|
|
@@ -9,6 +9,7 @@ export default class TicketCreate extends PMOCommand {
|
|
|
9
9
|
priority: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
10
|
category: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
11
|
description: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'description-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
14
|
interactive: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
15
|
epic: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
@@ -1,4 +1,5 @@
|
|
|
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';
|
|
4
5
|
// Note: inquirer import kept for inquirer.Separator usage in interactive mode
|
|
@@ -16,6 +17,8 @@ export default class TicketCreate extends PMOCommand {
|
|
|
16
17
|
'<%= config.bin %> <%= command.id %> -t "Add feature" -c "In Progress" -p P1',
|
|
17
18
|
'<%= config.bin %> <%= command.id %> --project mobile-app -t "New feature"',
|
|
18
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',
|
|
19
22
|
'<%= config.bin %> <%= command.id %> --json # Output column choices as JSON',
|
|
20
23
|
'<%= config.bin %> <%= command.id %> --title "Test" -P PROJ-001 --dry-run --json # Validate without creating',
|
|
21
24
|
];
|
|
@@ -47,6 +50,11 @@ export default class TicketCreate extends PMOCommand {
|
|
|
47
50
|
char: 'd',
|
|
48
51
|
description: 'Ticket description',
|
|
49
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
|
+
}),
|
|
50
58
|
id: Flags.string({
|
|
51
59
|
description: 'Custom ticket ID (auto-generated if not provided)',
|
|
52
60
|
}),
|
|
@@ -65,6 +73,7 @@ export default class TicketCreate extends PMOCommand {
|
|
|
65
73
|
}),
|
|
66
74
|
labels: Flags.string({
|
|
67
75
|
char: 'l',
|
|
76
|
+
aliases: ['label'],
|
|
68
77
|
description: 'Labels (comma-separated)',
|
|
69
78
|
}),
|
|
70
79
|
'dry-run': Flags.boolean({
|
|
@@ -95,6 +104,27 @@ export default class TicketCreate extends PMOCommand {
|
|
|
95
104
|
}
|
|
96
105
|
this.error(message);
|
|
97
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
|
+
}
|
|
98
128
|
// Validate epic if provided
|
|
99
129
|
if (flags.epic) {
|
|
100
130
|
const epic = await this.storage.getEpic(flags.epic);
|
|
@@ -81,13 +81,13 @@ export default class TicketDelete extends PMOCommand {
|
|
|
81
81
|
if (!ticket) {
|
|
82
82
|
this.error(`Ticket "${ticketId}" not found.`);
|
|
83
83
|
}
|
|
84
|
-
// Get board for project name
|
|
85
|
-
const board = await this.storage.
|
|
84
|
+
// Get board for project name (may be null if project was deleted/orphaned)
|
|
85
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
86
86
|
// Confirmation prompt (unless --force)
|
|
87
87
|
if (!flags.force) {
|
|
88
88
|
this.log(`\nDelete ticket ${styles.emphasis(ticketId)}?`);
|
|
89
89
|
this.log(` Title: ${ticket.title}`);
|
|
90
|
-
this.log(` Project: ${board.
|
|
90
|
+
this.log(` Project: ${board?.name || ticket.projectId || 'Unknown'}`);
|
|
91
91
|
this.log(` Status: ${ticket.statusName}`);
|
|
92
92
|
const jsonModeConfig = jsonMode ? { flags, commandName: 'ticket delete' } : null;
|
|
93
93
|
const { confirmed } = await this.prompt([{
|
|
@@ -153,8 +153,8 @@ export default class TicketEdit extends PMOCommand {
|
|
|
153
153
|
return; // outputPromptAsJson exits, but TypeScript doesn't know
|
|
154
154
|
}
|
|
155
155
|
// Interactive mode - prompt for all editable fields
|
|
156
|
-
const board = await this.storage.
|
|
157
|
-
const columns = board.columns.map(col => col.name);
|
|
156
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
157
|
+
const columns = board ? board.columns.map(col => col.name) : [];
|
|
158
158
|
updates = await this.promptForEdits(ticket, columns);
|
|
159
159
|
}
|
|
160
160
|
else {
|
|
@@ -3,6 +3,7 @@ import { pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
|
3
3
|
import { PRIORITIES } from '../../lib/pmo/types.js';
|
|
4
4
|
import { getPMOContext } from '../../lib/pmo/pmo-context.js';
|
|
5
5
|
import { styles, formatPriority, formatCategory, getColumnStyle, getColumnEmoji, divider, getPriorityStyle, } from '../../lib/styles.js';
|
|
6
|
+
import { isNonTTY } from '../../lib/prompt-json.js';
|
|
6
7
|
// Priority order for grouping: P0, P1, P2, P3, None
|
|
7
8
|
const PRIORITY_ORDER = ['P0', 'P1', 'P2', 'P3', 'None'];
|
|
8
9
|
export default class TicketList extends Command {
|
|
@@ -67,6 +68,10 @@ export default class TicketList extends Command {
|
|
|
67
68
|
};
|
|
68
69
|
async run() {
|
|
69
70
|
const { flags } = await this.parse(TicketList);
|
|
71
|
+
// Default format to 'json' in non-TTY environments (piped output, CI, agents)
|
|
72
|
+
if (flags.format === 'table' && isNonTTY()) {
|
|
73
|
+
flags.format = 'json';
|
|
74
|
+
}
|
|
70
75
|
// When --all is set, we don't need to select a specific project
|
|
71
76
|
// Otherwise, use the normal project selection flow
|
|
72
77
|
// Get PMO context - no project selection needed
|
|
@@ -110,10 +115,12 @@ export default class TicketList extends Command {
|
|
|
110
115
|
// Get the project board to validate the column
|
|
111
116
|
const targetProjectId = projectId || (await pmoContext.storage.listProjectSummaries())[0]?.id;
|
|
112
117
|
if (targetProjectId) {
|
|
113
|
-
const board = await pmoContext.storage.
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
118
|
+
const board = await pmoContext.storage.getProjectBoard(targetProjectId);
|
|
119
|
+
if (board) {
|
|
120
|
+
const validColumns = board.columns.map(c => c.name);
|
|
121
|
+
if (!validColumns.includes(flags.column)) {
|
|
122
|
+
this.error(`Column "${flags.column}" not found. Valid columns: ${validColumns.join(', ')}`);
|
|
123
|
+
}
|
|
117
124
|
}
|
|
118
125
|
}
|
|
119
126
|
}
|
|
@@ -149,7 +156,19 @@ export default class TicketList extends Command {
|
|
|
149
156
|
this.log(styles.warning('No project found.'));
|
|
150
157
|
return;
|
|
151
158
|
}
|
|
152
|
-
const board = await pmoContext.storage.
|
|
159
|
+
const board = await pmoContext.storage.getProjectBoard(actualProjectId);
|
|
160
|
+
if (!board) {
|
|
161
|
+
// Project doesn't exist (orphaned tickets) - fall back to cross-project view
|
|
162
|
+
this.log(styles.warning(`Project "${actualProjectId}" not found. Showing tickets without board layout.`));
|
|
163
|
+
switch (flags.format) {
|
|
164
|
+
case 'json':
|
|
165
|
+
this.log(JSON.stringify(tickets, null, 2));
|
|
166
|
+
break;
|
|
167
|
+
default:
|
|
168
|
+
this.outputCrossProjectTable(tickets, groupBy);
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
153
172
|
const columns = board.columns.map(col => col.name);
|
|
154
173
|
switch (flags.format) {
|
|
155
174
|
case 'json':
|
|
@@ -216,7 +216,10 @@ export default class TicketMove extends PMOCommand {
|
|
|
216
216
|
this.log(styles.emphasis('📦 Move Multiple Tickets\n'));
|
|
217
217
|
}
|
|
218
218
|
// Get columns
|
|
219
|
-
const board = await this.storage.
|
|
219
|
+
const board = await this.storage.getProjectBoard(projectId);
|
|
220
|
+
if (!board) {
|
|
221
|
+
this.error(`Project "${projectId}" not found. The ticket may belong to an orphaned project.`);
|
|
222
|
+
}
|
|
220
223
|
const columns = board.columns.map(col => col.name);
|
|
221
224
|
// Agent mode config for prompts
|
|
222
225
|
const jsonModeConfig = flags.json ? { flags, commandName: 'ticket move --bulk' } : null;
|
|
@@ -63,11 +63,13 @@ export default class TicketView extends PMOCommand {
|
|
|
63
63
|
if (!ticket) {
|
|
64
64
|
this.error(`Ticket "${ticketId}" not found.`);
|
|
65
65
|
}
|
|
66
|
-
|
|
66
|
+
// Get project board (may be null if project was deleted/orphaned)
|
|
67
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
68
|
+
const projectName = board?.name || ticket.projectId || 'Unknown';
|
|
67
69
|
// Display ticket details
|
|
68
70
|
this.log(`\n${styles.header('📄 Ticket')} ${styles.emphasis(ticket.id)}\n`);
|
|
69
71
|
this.log(`${styles.header('Title:')} ${ticket.title}`);
|
|
70
|
-
this.log(`${styles.header('Project:')} ${
|
|
72
|
+
this.log(`${styles.header('Project:')} ${projectName}`);
|
|
71
73
|
this.log(`${styles.header('Status:')} ${ticket.statusName}`);
|
|
72
74
|
this.log(`${styles.header('Priority:')} ${ticket.priority || 'none'}`);
|
|
73
75
|
this.log(`${styles.header('Category:')} ${ticket.category || 'none'}`);
|
|
@@ -2,6 +2,9 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
export default class Whoami 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
|
private detectAgentName;
|
|
7
10
|
private findWorkspaceRoot;
|
package/dist/commands/whoami.js
CHANGED
|
@@ -4,18 +4,39 @@ import * as fs from 'node:fs';
|
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
5
|
import { colors } from '../lib/colors.js';
|
|
6
6
|
import { getAgentByPath } from '../lib/database/index.js';
|
|
7
|
+
import { shouldOutputJson } from '../lib/prompt-json.js';
|
|
8
|
+
import { machineOutputFlags } from '../lib/pmo/index.js';
|
|
7
9
|
export default class Whoami extends Command {
|
|
8
10
|
static description = 'Show current agent/environment context';
|
|
9
11
|
static examples = [
|
|
10
12
|
'<%= config.bin %> <%= command.id %>',
|
|
11
13
|
];
|
|
14
|
+
static flags = {
|
|
15
|
+
...machineOutputFlags,
|
|
16
|
+
};
|
|
12
17
|
async run() {
|
|
13
|
-
await this.parse(Whoami);
|
|
18
|
+
const { flags } = await this.parse(Whoami);
|
|
19
|
+
const jsonMode = shouldOutputJson(flags);
|
|
14
20
|
const isDevcontainer = process.env.DEVCONTAINER === 'true';
|
|
15
21
|
const agentName = this.detectAgentName();
|
|
16
22
|
const repoName = this.detectRepoName();
|
|
17
23
|
const branch = this.getCurrentBranch();
|
|
18
24
|
const hqPath = process.env.PRLT_HQ_PATH;
|
|
25
|
+
const pmoPath = process.env.PRLT_PMO_PATH;
|
|
26
|
+
const hostPath = process.env.PRLT_HOST_PATH;
|
|
27
|
+
if (jsonMode) {
|
|
28
|
+
this.log(JSON.stringify({
|
|
29
|
+
agent: agentName,
|
|
30
|
+
repository: repoName,
|
|
31
|
+
branch,
|
|
32
|
+
environment: isDevcontainer ? 'devcontainer' : 'host',
|
|
33
|
+
workingDir: process.cwd(),
|
|
34
|
+
hqPath: hqPath || null,
|
|
35
|
+
pmoPath: pmoPath || null,
|
|
36
|
+
hostPath: hostPath || null,
|
|
37
|
+
}, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
19
40
|
this.log('');
|
|
20
41
|
this.log(colors.primary('🔍 Proletariat Context'));
|
|
21
42
|
this.log('');
|
|
@@ -41,13 +62,9 @@ export default class Whoami extends Command {
|
|
|
41
62
|
if (hqPath) {
|
|
42
63
|
this.log(` HQ path: ${colors.textMuted(hqPath)}`);
|
|
43
64
|
}
|
|
44
|
-
// Show PMO path if available
|
|
45
|
-
const pmoPath = process.env.PRLT_PMO_PATH;
|
|
46
65
|
if (pmoPath) {
|
|
47
66
|
this.log(` PMO path: ${colors.textMuted(pmoPath)}`);
|
|
48
67
|
}
|
|
49
|
-
// Show host path if available (set in devcontainer for agent identity)
|
|
50
|
-
const hostPath = process.env.PRLT_HOST_PATH;
|
|
51
68
|
if (hostPath) {
|
|
52
69
|
this.log(` Host path: ${colors.textMuted(hostPath)}`);
|
|
53
70
|
}
|
|
@@ -91,8 +91,8 @@ export default class WorkComplete extends PMOCommand {
|
|
|
91
91
|
}
|
|
92
92
|
// Get configured column name (from pmo_settings or default)
|
|
93
93
|
const targetColumnName = getWorkColumnSetting(db, 'done');
|
|
94
|
-
const board = await this.storage.
|
|
95
|
-
const columnNames = board.columns.map(col => col.name);
|
|
94
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
95
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
96
96
|
const doneColumn = findColumnByName(columnNames, targetColumnName);
|
|
97
97
|
if (!doneColumn) {
|
|
98
98
|
db.close();
|
|
@@ -113,8 +113,8 @@ export default class WorkReady extends PMOCommand {
|
|
|
113
113
|
// Get configured column name (from pmo_settings or default)
|
|
114
114
|
// "ready" moves ticket to Review column (work complete moves to Done)
|
|
115
115
|
const targetColumnName = getWorkColumnSetting(db, 'review');
|
|
116
|
-
const board = await this.storage.
|
|
117
|
-
const columnNames = board.columns.map(col => col.name);
|
|
116
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
117
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
118
118
|
const reviewColumn = findColumnByName(columnNames, targetColumnName);
|
|
119
119
|
if (!reviewColumn) {
|
|
120
120
|
db.close();
|
|
@@ -304,8 +304,8 @@ export default class WorkRevise extends PMOCommand {
|
|
|
304
304
|
this.log('');
|
|
305
305
|
// Move ticket back to In Progress column
|
|
306
306
|
const inProgressColumnName = getWorkColumnSetting(db, 'in_progress');
|
|
307
|
-
const board = await this.storage.
|
|
308
|
-
const columnNames = board.columns.map(col => col.name);
|
|
307
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
308
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
309
309
|
const inProgressColumn = findColumnByName(columnNames, inProgressColumnName);
|
|
310
310
|
if (inProgressColumn && ticket.statusName !== inProgressColumn) {
|
|
311
311
|
await this.storage.moveTicket(ticket.projectId, ticket.id, inProgressColumn);
|
|
@@ -1452,8 +1452,8 @@ export default class WorkStart extends PMOCommand {
|
|
|
1452
1452
|
// Move ticket to target column based on action's defaultMoveToCategory
|
|
1453
1453
|
// If action has a target category, find the matching column; otherwise use "started" default
|
|
1454
1454
|
const targetCategory = selectedAction?.defaultMoveToCategory || 'started';
|
|
1455
|
-
const board = await this.storage.
|
|
1456
|
-
const columnNames = board.columns.map(col => col.name);
|
|
1455
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
1456
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
1457
1457
|
// Map category to column type for lookup
|
|
1458
1458
|
const columnType = targetCategory === 'started' ? 'in_progress' :
|
|
1459
1459
|
targetCategory === 'unstarted' ? 'planned' :
|
|
@@ -1868,8 +1868,8 @@ export default class WorkStart extends PMOCommand {
|
|
|
1868
1868
|
}
|
|
1869
1869
|
// Move ticket to In Progress column ONLY after successful spawn
|
|
1870
1870
|
const targetColumnName = getWorkColumnSetting(db, 'in_progress');
|
|
1871
|
-
const board = await this.storage.
|
|
1872
|
-
const columnNames = board.columns.map(col => col.name);
|
|
1871
|
+
const board = ticket.projectId ? await this.storage.getProjectBoard(ticket.projectId) : null;
|
|
1872
|
+
const columnNames = board ? board.columns.map(col => col.name) : [];
|
|
1873
1873
|
const inProgressColumn = findColumnByName(columnNames, targetColumnName);
|
|
1874
1874
|
if (inProgressColumn && ticket.status !== inProgressColumn) {
|
|
1875
1875
|
await this.storage.moveTicket(ticket.projectId, ticket.id, inProgressColumn);
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export default class WorkspacePrune extends
|
|
1
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
|
+
export default class WorkspacePrune extends PromptCommand {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
6
|
'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
8
|
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
9
|
};
|
|
9
10
|
run(): Promise<void>;
|