@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Args } from '@oclif/core';
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
3
|
import { styles } from '../../lib/styles.js';
|
|
4
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
5
|
export default class ActionShow extends PMOCommand {
|
|
5
6
|
static description = 'Show details of a work action';
|
|
6
7
|
static examples = [
|
|
@@ -20,11 +21,16 @@ export default class ActionShow extends PMOCommand {
|
|
|
20
21
|
return { promptIfMultiple: false };
|
|
21
22
|
}
|
|
22
23
|
async execute() {
|
|
23
|
-
const { args } = await this.parse(ActionShow);
|
|
24
|
+
const { args, flags } = await this.parse(ActionShow);
|
|
25
|
+
const jsonMode = shouldOutputJson(flags);
|
|
24
26
|
const action = await this.storage.getAction(args.id);
|
|
25
27
|
if (!action) {
|
|
26
28
|
this.error(`Action not found: ${args.id}`);
|
|
27
29
|
}
|
|
30
|
+
if (jsonMode) {
|
|
31
|
+
this.log(JSON.stringify(action, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
28
34
|
this.log(`\n${styles.emphasis(`Action: ${action.name}`)} ${styles.muted(`(${action.id})`)}`);
|
|
29
35
|
this.log('');
|
|
30
36
|
if (action.description) {
|
|
@@ -2,6 +2,8 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
3
|
import { styles } from '../../lib/styles.js';
|
|
4
4
|
import { BRANCH_TYPES, listBranches, isGitRepo, } from '../../lib/branch/index.js';
|
|
5
|
+
import { isNonTTY } from '../../lib/prompt-json.js';
|
|
6
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
5
7
|
export default class BranchList extends PMOCommand {
|
|
6
8
|
static description = 'List branches with conventional naming information';
|
|
7
9
|
static examples = [
|
|
@@ -34,6 +36,10 @@ export default class BranchList extends PMOCommand {
|
|
|
34
36
|
}
|
|
35
37
|
async execute() {
|
|
36
38
|
const { flags } = await this.parse(BranchList);
|
|
39
|
+
// Default format to 'json' in non-TTY environments (piped output, CI, agents)
|
|
40
|
+
if (flags.format === 'table' && isNonTTY()) {
|
|
41
|
+
flags.format = 'json';
|
|
42
|
+
}
|
|
37
43
|
// Check if in git repo
|
|
38
44
|
if (!isGitRepo()) {
|
|
39
45
|
this.error('Not in a git repository.');
|
|
@@ -72,10 +78,10 @@ export default class BranchList extends PMOCommand {
|
|
|
72
78
|
this.log(styles.header(`🌿 Branches (${branches.length})`));
|
|
73
79
|
this.log('');
|
|
74
80
|
// Header
|
|
75
|
-
this.log(styles.muted(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
this.log(styles.muted(visualPadEnd('Name', 35) +
|
|
82
|
+
visualPadEnd('Type', 8) +
|
|
83
|
+
visualPadEnd('Owner', 12) +
|
|
84
|
+
visualPadEnd('Description', 25) +
|
|
79
85
|
'Status'));
|
|
80
86
|
this.log('─'.repeat(90));
|
|
81
87
|
// Rows
|
|
@@ -93,10 +99,10 @@ export default class BranchList extends PMOCommand {
|
|
|
93
99
|
status = 'current';
|
|
94
100
|
}
|
|
95
101
|
this.log(marker +
|
|
96
|
-
nameStyle(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
nameStyle(visualPadEnd(branch.name.substring(0, 33), 33)) +
|
|
103
|
+
visualPadEnd(typeDisplay, 8) +
|
|
104
|
+
visualPadEnd(ownerDisplay.substring(0, 10), 12) +
|
|
105
|
+
visualPadEnd(descDisplay.substring(0, 23), 25) +
|
|
100
106
|
styles.muted(status));
|
|
101
107
|
}
|
|
102
108
|
this.log('');
|
|
@@ -115,6 +121,3 @@ export default class BranchList extends PMOCommand {
|
|
|
115
121
|
this.log('');
|
|
116
122
|
}
|
|
117
123
|
}
|
|
118
|
-
function padEnd(str, length) {
|
|
119
|
-
return str.padEnd(length);
|
|
120
|
-
}
|
|
@@ -2,6 +2,7 @@ import { Args } from '@oclif/core';
|
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
3
|
import { styles } from '../../lib/styles.js';
|
|
4
4
|
import { BRANCH_TYPES, validateBranchName, getCurrentBranch, isGitRepo, } from '../../lib/branch/index.js';
|
|
5
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
5
6
|
export default class BranchValidate extends PMOCommand {
|
|
6
7
|
static description = 'Validate branch name against conventional format';
|
|
7
8
|
static examples = [
|
|
@@ -22,7 +23,8 @@ export default class BranchValidate extends PMOCommand {
|
|
|
22
23
|
return { promptIfMultiple: false };
|
|
23
24
|
}
|
|
24
25
|
async execute() {
|
|
25
|
-
const { args } = await this.parse(BranchValidate);
|
|
26
|
+
const { args, flags } = await this.parse(BranchValidate);
|
|
27
|
+
const jsonMode = shouldOutputJson(flags);
|
|
26
28
|
let branchName = args.name || '';
|
|
27
29
|
// Use current branch if not provided
|
|
28
30
|
if (!branchName) {
|
|
@@ -36,6 +38,13 @@ export default class BranchValidate extends PMOCommand {
|
|
|
36
38
|
branchName = currentBranch;
|
|
37
39
|
}
|
|
38
40
|
const result = validateBranchName(branchName);
|
|
41
|
+
if (jsonMode) {
|
|
42
|
+
this.log(JSON.stringify({ branch: branchName, ...result }, null, 2));
|
|
43
|
+
if (!result.valid) {
|
|
44
|
+
this.exit(1);
|
|
45
|
+
}
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
39
48
|
this.log('');
|
|
40
49
|
if (result.valid && result.parts) {
|
|
41
50
|
if (args.name) {
|
|
@@ -9,6 +9,7 @@ import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
|
9
9
|
import { sanitizeContainerId } from '../../lib/docker/resolve.js';
|
|
10
10
|
import { FlagResolver, shouldOutputJson } from '../../lib/flags/index.js';
|
|
11
11
|
import { machineOutputFlags } from '../../lib/pmo/base-command.js';
|
|
12
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
12
13
|
export default class DockerClean extends Command {
|
|
13
14
|
static description = 'Remove orphaned containers (containers without running agents)';
|
|
14
15
|
static examples = [
|
|
@@ -72,15 +73,15 @@ export default class DockerClean extends Command {
|
|
|
72
73
|
// Display orphaned containers
|
|
73
74
|
this.log(`\n${styles.header('Orphaned Containers')}`);
|
|
74
75
|
this.log('='.repeat(80));
|
|
75
|
-
this.log(styles.muted(
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
this.log(styles.muted(visualPadEnd('Container ID', 15) +
|
|
77
|
+
visualPadEnd('Name', 30) +
|
|
78
|
+
visualPadEnd('Status', 15) +
|
|
78
79
|
'Reason'));
|
|
79
80
|
this.log('-'.repeat(80));
|
|
80
81
|
for (const container of orphanedContainers) {
|
|
81
|
-
this.log(
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
this.log(visualPadEnd(container.id, 15) +
|
|
83
|
+
visualPadEnd(truncate(container.name, 28), 30) +
|
|
84
|
+
visualPadEnd(container.status, 15) +
|
|
84
85
|
styles.muted(container.reason));
|
|
85
86
|
}
|
|
86
87
|
if (flags['dry-run']) {
|
|
@@ -205,9 +206,6 @@ export default class DockerClean extends Command {
|
|
|
205
206
|
}
|
|
206
207
|
}
|
|
207
208
|
}
|
|
208
|
-
function padEnd(str, length) {
|
|
209
|
-
return str.padEnd(length);
|
|
210
|
-
}
|
|
211
209
|
function truncate(str, maxLength) {
|
|
212
210
|
if (str.length <= maxLength)
|
|
213
211
|
return str;
|
|
@@ -9,6 +9,7 @@ import { ExecutionStorage, ContainerStorage } from '../../lib/execution/storage.
|
|
|
9
9
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
10
10
|
import { FlagResolver, shouldOutputJson } from '../../lib/flags/index.js';
|
|
11
11
|
import { machineOutputFlags } from '../../lib/pmo/base-command.js';
|
|
12
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
12
13
|
export default class Docker extends PromptCommand {
|
|
13
14
|
static description = 'Manage Docker containers used by agents';
|
|
14
15
|
static examples = [
|
|
@@ -127,7 +128,7 @@ export default class Docker extends PromptCommand {
|
|
|
127
128
|
// Build choices
|
|
128
129
|
const choices = [];
|
|
129
130
|
// Column header
|
|
130
|
-
const header = styles.muted(` ${'ID'
|
|
131
|
+
const header = styles.muted(` ${visualPadEnd('ID', 14)} ${visualPadEnd('Agent', 15)} Status`);
|
|
131
132
|
// Add active executions first (with container info from DB)
|
|
132
133
|
const activeExecutions = trackedExecutions.filter(e => e.status === 'running' || e.status === 'starting');
|
|
133
134
|
if (activeExecutions.length > 0) {
|
|
@@ -143,7 +144,7 @@ export default class Docker extends PromptCommand {
|
|
|
143
144
|
const statusIcon = isRunning ? styles.success('●') : styles.warning('◐');
|
|
144
145
|
// Use agent name from execution record (DB source of truth)
|
|
145
146
|
choices.push({
|
|
146
|
-
name: `${statusIcon} ${exec.id
|
|
147
|
+
name: `${statusIcon} ${visualPadEnd(exec.id, 14)} ${visualPadEnd(exec.agentName, 15)} ${styles.success(exec.status)}`,
|
|
147
148
|
value: exec.id,
|
|
148
149
|
});
|
|
149
150
|
if (exec.containerId) {
|
|
@@ -167,7 +168,7 @@ export default class Docker extends PromptCommand {
|
|
|
167
168
|
const statusText = isRunning ? styles.success('running') : styles.muted(container.status);
|
|
168
169
|
// Agent name from DB (source of truth)
|
|
169
170
|
choices.push({
|
|
170
|
-
name: `${statusIcon} ${container.dockerId.substring(0, 12)
|
|
171
|
+
name: `${statusIcon} ${visualPadEnd(container.dockerId.substring(0, 12), 14)} ${visualPadEnd(container.agentName, 15)} ${statusText}`,
|
|
171
172
|
value: container.dockerId,
|
|
172
173
|
});
|
|
173
174
|
addedDockerIds.add(container.dockerId.substring(0, 12));
|
|
@@ -185,7 +186,7 @@ export default class Docker extends PromptCommand {
|
|
|
185
186
|
// Parse agent from image only for untracked containers
|
|
186
187
|
const agentName = this.extractAgentFromImage(container.image);
|
|
187
188
|
choices.push({
|
|
188
|
-
name: `${statusIcon} ${container.id.substring(0, 12)
|
|
189
|
+
name: `${statusIcon} ${visualPadEnd(container.id.substring(0, 12), 14)} ${visualPadEnd(agentName, 15)} ${statusText}`,
|
|
189
190
|
value: container.id,
|
|
190
191
|
});
|
|
191
192
|
}
|
|
@@ -5,6 +5,7 @@ export default class DockerList extends Command {
|
|
|
5
5
|
static flags: {
|
|
6
6
|
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
7
|
running: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
9
|
};
|
|
9
10
|
run(): Promise<void>;
|
|
10
11
|
private getTrackedContainers;
|
|
@@ -6,6 +6,9 @@ import { styles } from '../../lib/styles.js';
|
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
7
7
|
import { ExecutionStorage, ContainerStorage } from '../../lib/execution/storage.js';
|
|
8
8
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
9
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
10
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
11
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
9
12
|
export default class DockerList extends Command {
|
|
10
13
|
static description = 'Show Docker containers from agent_work table with status';
|
|
11
14
|
static examples = [
|
|
@@ -14,6 +17,7 @@ export default class DockerList extends Command {
|
|
|
14
17
|
'<%= config.bin %> <%= command.id %> --running',
|
|
15
18
|
];
|
|
16
19
|
static flags = {
|
|
20
|
+
...machineOutputFlags,
|
|
17
21
|
all: Flags.boolean({
|
|
18
22
|
char: 'a',
|
|
19
23
|
description: 'Show all containers (including non-devcontainer)',
|
|
@@ -27,8 +31,13 @@ export default class DockerList extends Command {
|
|
|
27
31
|
};
|
|
28
32
|
async run() {
|
|
29
33
|
const { flags } = await this.parse(DockerList);
|
|
34
|
+
const jsonMode = shouldOutputJson(flags);
|
|
30
35
|
// Check Docker status first
|
|
31
36
|
if (!isDockerRunning()) {
|
|
37
|
+
if (jsonMode) {
|
|
38
|
+
this.log(JSON.stringify({ error: 'Docker is not running' }, null, 2));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
32
41
|
this.log(`\n${styles.error('Docker is not running')}`);
|
|
33
42
|
this.log(`${styles.muted('Start Docker Desktop or the Docker daemon first.')}\n`);
|
|
34
43
|
return;
|
|
@@ -59,16 +68,24 @@ export default class DockerList extends Command {
|
|
|
59
68
|
const dbContainers = containerStorage.listContainers({ limit: 50 });
|
|
60
69
|
// Get running docker containers
|
|
61
70
|
const runningContainers = this.getDockerContainers(workspaceInfo.agentsPath, flags.all);
|
|
71
|
+
if (jsonMode) {
|
|
72
|
+
this.log(JSON.stringify({
|
|
73
|
+
executions: trackedExecutions,
|
|
74
|
+
containers: runningContainers,
|
|
75
|
+
}, null, 2));
|
|
76
|
+
db.close();
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
62
79
|
this.log(`\n${styles.header('Docker Containers')}`);
|
|
63
80
|
this.log('='.repeat(100));
|
|
64
81
|
// Display active executions from database
|
|
65
82
|
const activeExecutions = trackedExecutions.filter(e => e.status === 'running' || e.status === 'starting');
|
|
66
83
|
if (activeExecutions.length > 0) {
|
|
67
84
|
this.log(styles.subheader('\nActive Executions:'));
|
|
68
|
-
this.log(styles.muted(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
85
|
+
this.log(styles.muted(visualPadEnd('ID', 15) +
|
|
86
|
+
visualPadEnd('Agent', 15) +
|
|
87
|
+
visualPadEnd('Ticket', 12) +
|
|
88
|
+
visualPadEnd('Status', 12) +
|
|
72
89
|
'Container'));
|
|
73
90
|
this.log('-'.repeat(80));
|
|
74
91
|
for (const exec of activeExecutions) {
|
|
@@ -80,10 +97,10 @@ export default class DockerList extends Command {
|
|
|
80
97
|
const isRunning = runningContainers.some(c => c.id.startsWith(exec.containerId.substring(0, 12)));
|
|
81
98
|
containerStatus = isRunning ? styles.success(' (up)') : styles.warning(' (down)');
|
|
82
99
|
}
|
|
83
|
-
this.log(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
statusColor(
|
|
100
|
+
this.log(visualPadEnd(exec.id, 15) +
|
|
101
|
+
visualPadEnd(exec.agentName, 15) +
|
|
102
|
+
visualPadEnd(exec.ticketId, 12) +
|
|
103
|
+
statusColor(visualPadEnd(exec.status, 12)) +
|
|
87
104
|
containerId + containerStatus);
|
|
88
105
|
}
|
|
89
106
|
}
|
|
@@ -93,9 +110,9 @@ export default class DockerList extends Command {
|
|
|
93
110
|
: runningContainers;
|
|
94
111
|
if (filteredContainers.length > 0) {
|
|
95
112
|
this.log(styles.subheader('\nDocker Containers' + (flags.all ? ' (all)' : ' (devcontainers)') + ':'));
|
|
96
|
-
this.log(styles.muted(
|
|
97
|
-
|
|
98
|
-
|
|
113
|
+
this.log(styles.muted(visualPadEnd('Container ID', 15) +
|
|
114
|
+
visualPadEnd('Agent', 15) +
|
|
115
|
+
visualPadEnd('Status', 15) +
|
|
99
116
|
'Uptime'));
|
|
100
117
|
this.log('-'.repeat(70));
|
|
101
118
|
for (const container of filteredContainers) {
|
|
@@ -103,9 +120,9 @@ export default class DockerList extends Command {
|
|
|
103
120
|
// Look up agent name from DB (source of truth)
|
|
104
121
|
const dbContainer = dbContainers.find(c => c.dockerId.startsWith(container.id.substring(0, 12)));
|
|
105
122
|
const agentName = dbContainer?.agentName || this.extractAgentFromImage(container.image) || container.name;
|
|
106
|
-
this.log(
|
|
107
|
-
|
|
108
|
-
statusColor(
|
|
123
|
+
this.log(visualPadEnd(container.id, 15) +
|
|
124
|
+
visualPadEnd(agentName, 15) +
|
|
125
|
+
statusColor(visualPadEnd(container.status, 15)) +
|
|
109
126
|
styles.muted(container.uptime));
|
|
110
127
|
}
|
|
111
128
|
}
|
|
@@ -174,9 +191,6 @@ export default class DockerList extends Command {
|
|
|
174
191
|
return match ? match[1] : null;
|
|
175
192
|
}
|
|
176
193
|
}
|
|
177
|
-
function padEnd(str, length) {
|
|
178
|
-
return str.padEnd(length);
|
|
179
|
-
}
|
|
180
194
|
function getStatusColor(status) {
|
|
181
195
|
switch (status) {
|
|
182
196
|
case 'running':
|
|
@@ -2,6 +2,8 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
export default class DockerStatus 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
|
}
|
|
@@ -2,16 +2,42 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
import { execSync } from 'node:child_process';
|
|
3
3
|
import { styles } from '../../lib/styles.js';
|
|
4
4
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
5
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
6
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
5
7
|
export default class DockerStatus extends Command {
|
|
6
8
|
static description = 'Check if Docker daemon is running';
|
|
7
9
|
static examples = [
|
|
8
10
|
'<%= config.bin %> <%= command.id %>',
|
|
9
11
|
];
|
|
10
|
-
static flags = {
|
|
12
|
+
static flags = {
|
|
13
|
+
...machineOutputFlags,
|
|
14
|
+
};
|
|
11
15
|
async run() {
|
|
16
|
+
const { flags } = await this.parse(DockerStatus);
|
|
17
|
+
const jsonMode = shouldOutputJson(flags);
|
|
18
|
+
const running = isDockerRunning();
|
|
19
|
+
if (jsonMode) {
|
|
20
|
+
const result = { running };
|
|
21
|
+
if (running) {
|
|
22
|
+
try {
|
|
23
|
+
result.version = execSync('docker version --format "{{.Server.Version}}"', {
|
|
24
|
+
encoding: 'utf-8',
|
|
25
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
26
|
+
}).trim();
|
|
27
|
+
result.info = execSync('docker info --format "{{.Containers}} containers ({{.ContainersRunning}} running)"', {
|
|
28
|
+
encoding: 'utf-8',
|
|
29
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
30
|
+
}).trim();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Ignore errors getting additional info
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
this.log(JSON.stringify(result, null, 2));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
12
39
|
this.log(`\n${styles.header('Docker Status')}`);
|
|
13
40
|
this.log('─'.repeat(50));
|
|
14
|
-
const running = isDockerRunning();
|
|
15
41
|
if (running) {
|
|
16
42
|
this.log(`${styles.success('Running')} Docker daemon is available`);
|
|
17
43
|
// Get more details
|
|
@@ -6,6 +6,7 @@ import { styles } from '../../lib/styles.js';
|
|
|
6
6
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
7
7
|
import { ContainerStorage } from '../../lib/execution/storage.js';
|
|
8
8
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
9
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
9
10
|
export default class DockerSync extends Command {
|
|
10
11
|
static description = 'Sync container status from Docker into the database';
|
|
11
12
|
static examples = [
|
|
@@ -54,9 +55,9 @@ export default class DockerSync extends Command {
|
|
|
54
55
|
if (containers.length > 0) {
|
|
55
56
|
this.log(styles.subheader('Tracked Containers:'));
|
|
56
57
|
this.log(styles.muted(' ' +
|
|
57
|
-
'ID'
|
|
58
|
-
'Agent'
|
|
59
|
-
'Status'
|
|
58
|
+
visualPadEnd('ID', 18) +
|
|
59
|
+
visualPadEnd('Agent', 15) +
|
|
60
|
+
visualPadEnd('Status', 12) +
|
|
60
61
|
'Docker ID'));
|
|
61
62
|
this.log(styles.muted(' ' + '-'.repeat(65)));
|
|
62
63
|
for (const container of containers) {
|
|
@@ -64,9 +65,9 @@ export default class DockerSync extends Command {
|
|
|
64
65
|
container.status === 'exited' ? styles.muted :
|
|
65
66
|
container.status === 'removed' ? styles.error : styles.warning;
|
|
66
67
|
this.log(' ' +
|
|
67
|
-
container.id
|
|
68
|
-
container.agentName
|
|
69
|
-
statusColor(container.status
|
|
68
|
+
visualPadEnd(container.id, 18) +
|
|
69
|
+
visualPadEnd(container.agentName, 15) +
|
|
70
|
+
statusColor(visualPadEnd(container.status, 12)) +
|
|
70
71
|
styles.muted(container.dockerId.substring(0, 12)));
|
|
71
72
|
}
|
|
72
73
|
this.log('');
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
3
|
import { styles } from '../../lib/styles.js';
|
|
4
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
5
|
// Progress bar helper
|
|
5
6
|
function progressBar(percent, width = 20) {
|
|
6
7
|
const filled = Math.round((percent / 100) * width);
|
|
@@ -23,15 +24,18 @@ export default class EpicList extends PMOCommand {
|
|
|
23
24
|
};
|
|
24
25
|
async execute() {
|
|
25
26
|
const { flags } = await this.parse(EpicList);
|
|
27
|
+
const jsonMode = shouldOutputJson(flags);
|
|
26
28
|
const projectId = await this.requireProject();
|
|
27
29
|
const epics = await this.storage.listEpics(projectId, flags.status ? { status: flags.status } : undefined);
|
|
28
30
|
if (epics.length === 0) {
|
|
31
|
+
if (jsonMode) {
|
|
32
|
+
this.log(JSON.stringify([], null, 2));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
29
35
|
this.log(styles.muted('\nNo epics found.'));
|
|
30
36
|
this.log(styles.muted('Create one with: prlt epic create'));
|
|
31
37
|
return;
|
|
32
38
|
}
|
|
33
|
-
// Group epics by status
|
|
34
|
-
const grouped = this.groupByStatus(epics);
|
|
35
39
|
// Get ticket counts for each epic in parallel
|
|
36
40
|
const ticketCounts = await Promise.all(epics.map(async (epic) => {
|
|
37
41
|
const tickets = await this.storage.getTicketsForEpic(projectId, epic.id);
|
|
@@ -39,6 +43,17 @@ export default class EpicList extends PMOCommand {
|
|
|
39
43
|
return { epicId: epic.id, done, total: tickets.length };
|
|
40
44
|
}));
|
|
41
45
|
const epicProgress = new Map(ticketCounts.map(({ epicId, done, total }) => [epicId, { done, total }]));
|
|
46
|
+
if (jsonMode) {
|
|
47
|
+
const epicsWithProgress = epics.map(epic => {
|
|
48
|
+
const progress = epicProgress.get(epic.id) || { done: 0, total: 0 };
|
|
49
|
+
const percent = progress.total > 0 ? Math.round((progress.done / progress.total) * 100) : 0;
|
|
50
|
+
return { ...epic, progress: { ...progress, percent } };
|
|
51
|
+
});
|
|
52
|
+
this.log(JSON.stringify(epicsWithProgress, null, 2));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Group epics by status
|
|
56
|
+
const grouped = this.groupByStatus(epics);
|
|
42
57
|
const projectName = await this.getProjectName(projectId);
|
|
43
58
|
this.log(`\n🎯 ${styles.emphasis('Epics')} - ${projectName}`);
|
|
44
59
|
this.log('═'.repeat(55));
|
|
@@ -5,6 +5,8 @@ import { styles } from '../../lib/styles.js';
|
|
|
5
5
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
6
6
|
import { ExecutionStorage } from '../../lib/execution/storage.js';
|
|
7
7
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
8
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
9
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
8
10
|
export default class ExecutionList extends PMOCommand {
|
|
9
11
|
static description = 'List running and recent executions';
|
|
10
12
|
static examples = [
|
|
@@ -36,6 +38,7 @@ export default class ExecutionList extends PMOCommand {
|
|
|
36
38
|
}
|
|
37
39
|
async execute() {
|
|
38
40
|
const { flags } = await this.parse(ExecutionList);
|
|
41
|
+
const jsonMode = shouldOutputJson(flags);
|
|
39
42
|
// Get workspace info
|
|
40
43
|
let workspaceInfo;
|
|
41
44
|
try {
|
|
@@ -55,20 +58,28 @@ export default class ExecutionList extends PMOCommand {
|
|
|
55
58
|
limit: flags.limit,
|
|
56
59
|
});
|
|
57
60
|
if (executions.length === 0) {
|
|
61
|
+
if (jsonMode) {
|
|
62
|
+
this.log(JSON.stringify([], null, 2));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
58
65
|
this.log(styles.muted('\nNo executions found.\n'));
|
|
59
66
|
return;
|
|
60
67
|
}
|
|
68
|
+
if (jsonMode) {
|
|
69
|
+
this.log(JSON.stringify(executions, null, 2));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
61
72
|
// Display header
|
|
62
73
|
this.log('');
|
|
63
74
|
this.log(styles.header('🚀 Agent Work'));
|
|
64
75
|
this.log('═'.repeat(100));
|
|
65
|
-
this.log(styles.muted(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
76
|
+
this.log(styles.muted(visualPadEnd('ID', 14) +
|
|
77
|
+
visualPadEnd('Ticket', 9) +
|
|
78
|
+
visualPadEnd('Agent', 10) +
|
|
79
|
+
visualPadEnd('Env', 13) +
|
|
80
|
+
visualPadEnd('Display', 11) +
|
|
81
|
+
visualPadEnd('Perms', 8) +
|
|
82
|
+
visualPadEnd('Status', 10) +
|
|
72
83
|
'Started'));
|
|
73
84
|
this.log('─'.repeat(100));
|
|
74
85
|
// Display executions
|
|
@@ -79,13 +90,13 @@ export default class ExecutionList extends PMOCommand {
|
|
|
79
90
|
const envStr = `${envIcon} ${exec.environment}`;
|
|
80
91
|
const permsStr = exec.sandboxed ? 'safe' : 'danger';
|
|
81
92
|
const permsColor = exec.sandboxed ? styles.success : styles.warning;
|
|
82
|
-
this.log(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
permsColor(
|
|
88
|
-
statusColor(
|
|
93
|
+
this.log(visualPadEnd(exec.id, 14) +
|
|
94
|
+
visualPadEnd(exec.ticketId, 9) +
|
|
95
|
+
visualPadEnd(exec.agentName, 10) +
|
|
96
|
+
visualPadEnd(envStr, 13) +
|
|
97
|
+
visualPadEnd(exec.displayMode, 11) +
|
|
98
|
+
permsColor(visualPadEnd(permsStr, 8)) +
|
|
99
|
+
statusColor(visualPadEnd(exec.status, 10)) +
|
|
89
100
|
styles.muted(timeAgo));
|
|
90
101
|
}
|
|
91
102
|
this.log('═'.repeat(100));
|
|
@@ -110,9 +121,6 @@ export default class ExecutionList extends PMOCommand {
|
|
|
110
121
|
// =============================================================================
|
|
111
122
|
// Helper Functions
|
|
112
123
|
// =============================================================================
|
|
113
|
-
function padEnd(str, length) {
|
|
114
|
-
return str.padEnd(length);
|
|
115
|
-
}
|
|
116
124
|
function getStatusColor(status) {
|
|
117
125
|
switch (status) {
|
|
118
126
|
case 'running':
|
|
@@ -102,6 +102,9 @@ export default class PMOInit extends PromptCommand {
|
|
|
102
102
|
if (flags.location) {
|
|
103
103
|
location = flags.location;
|
|
104
104
|
}
|
|
105
|
+
else if (jsonMode) {
|
|
106
|
+
location = 'separate';
|
|
107
|
+
}
|
|
105
108
|
else {
|
|
106
109
|
location = await promptForPMOLocation(hqRoot);
|
|
107
110
|
}
|
|
@@ -111,6 +114,9 @@ export default class PMOInit extends PromptCommand {
|
|
|
111
114
|
if (flags.template) {
|
|
112
115
|
template = flags.template;
|
|
113
116
|
}
|
|
117
|
+
else if (jsonMode) {
|
|
118
|
+
template = 'kanban';
|
|
119
|
+
}
|
|
114
120
|
else {
|
|
115
121
|
let storage;
|
|
116
122
|
if (hqRoot) {
|
|
@@ -131,14 +137,27 @@ export default class PMOInit extends PromptCommand {
|
|
|
131
137
|
}
|
|
132
138
|
// Get columns for template
|
|
133
139
|
let columns = getColumnsForTemplate(template);
|
|
134
|
-
if (template === 'custom') {
|
|
140
|
+
if (template === 'custom' && !jsonMode) {
|
|
135
141
|
columns = await promptForCustomColumns();
|
|
136
142
|
}
|
|
143
|
+
else if (template === 'custom' && jsonMode) {
|
|
144
|
+
// Custom column prompts not supported in JSON mode — use default columns
|
|
145
|
+
this.log(JSON.stringify({ warning: 'Custom columns not supported in JSON mode, using default columns. Use a named template (e.g. --template kanban) for predictable results.' }));
|
|
146
|
+
}
|
|
137
147
|
// Get board name using shared prompt (or from flag)
|
|
138
148
|
// Default to {hqname}-kanban pattern
|
|
139
149
|
const hqName = hqRoot ? path.basename(hqRoot).replace(/-hq$/, '') : undefined;
|
|
140
|
-
const defaultBoardName = hqName ? `${hqName}-kanban` :
|
|
141
|
-
|
|
150
|
+
const defaultBoardName = hqName ? `${hqName}-kanban` : 'Project Board';
|
|
151
|
+
let boardName;
|
|
152
|
+
if (flags.name) {
|
|
153
|
+
boardName = flags.name;
|
|
154
|
+
}
|
|
155
|
+
else if (jsonMode) {
|
|
156
|
+
boardName = defaultBoardName;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
boardName = await promptForBoardName(defaultBoardName);
|
|
160
|
+
}
|
|
142
161
|
// For standalone PMO (no HQ), we need to create mini-HQ structure first
|
|
143
162
|
const isStandalone = !hqRoot;
|
|
144
163
|
let effectiveHqPath;
|
|
@@ -2,6 +2,8 @@ import { Flags } from '@oclif/core';
|
|
|
2
2
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
3
3
|
import { colors, format } from '../../lib/colors.js';
|
|
4
4
|
import { findHQRoot, getWorkspaceRepoInfo } from '../../lib/repos/index.js';
|
|
5
|
+
import { isNonTTY } from '../../lib/prompt-json.js';
|
|
6
|
+
import { visualPadEnd } from '../../lib/string-utils.js';
|
|
5
7
|
export default class List extends PMOCommand {
|
|
6
8
|
static description = 'List all repositories in the HQ';
|
|
7
9
|
static examples = [
|
|
@@ -23,6 +25,10 @@ export default class List extends PMOCommand {
|
|
|
23
25
|
}
|
|
24
26
|
async execute() {
|
|
25
27
|
const { flags } = await this.parse(List);
|
|
28
|
+
// Default format to 'json' in non-TTY environments (piped output, CI, agents)
|
|
29
|
+
if (flags.format === 'table' && isNonTTY()) {
|
|
30
|
+
flags.format = 'json';
|
|
31
|
+
}
|
|
26
32
|
// Find HQ root
|
|
27
33
|
const hqPath = findHQRoot();
|
|
28
34
|
if (!hqPath) {
|
|
@@ -53,10 +59,10 @@ export default class List extends PMOCommand {
|
|
|
53
59
|
this.log(format.title(`📦 Repositories (${repositories.length})`));
|
|
54
60
|
this.log('');
|
|
55
61
|
// Header
|
|
56
|
-
this.log(colors.textMuted('Name'
|
|
57
|
-
'Status'
|
|
58
|
-
'Branch'
|
|
59
|
-
'Commits'
|
|
62
|
+
this.log(colors.textMuted(visualPadEnd('Name', 20) +
|
|
63
|
+
visualPadEnd('Status', 10) +
|
|
64
|
+
visualPadEnd('Branch', 15) +
|
|
65
|
+
visualPadEnd('Commits', 12) +
|
|
60
66
|
'Added'));
|
|
61
67
|
this.log(colors.textMuted('─'.repeat(70)));
|
|
62
68
|
for (const repo of repositories) {
|
|
@@ -74,10 +80,10 @@ export default class List extends PMOCommand {
|
|
|
74
80
|
commits = `${repo.commitsBehind} behind`;
|
|
75
81
|
}
|
|
76
82
|
const added = repo.addedAt ? new Date(repo.addedAt).toLocaleDateString() : '-';
|
|
77
|
-
this.log(colors.text(repo.name
|
|
78
|
-
statusColor(repo.status
|
|
79
|
-
colors.warning((repo.branch || '-'
|
|
80
|
-
colors.text(commits
|
|
83
|
+
this.log(colors.text(visualPadEnd(repo.name, 20)) +
|
|
84
|
+
statusColor(visualPadEnd(repo.status, 10)) +
|
|
85
|
+
colors.warning(visualPadEnd(repo.branch || '-', 15)) +
|
|
86
|
+
colors.text(visualPadEnd(commits, 12)) +
|
|
81
87
|
colors.textMuted(added));
|
|
82
88
|
}
|
|
83
89
|
// Summary
|