@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.
Files changed (68) hide show
  1. package/dist/commands/action/show.js +7 -1
  2. package/dist/commands/branch/list.js +14 -11
  3. package/dist/commands/branch/validate.js +10 -1
  4. package/dist/commands/docker/clean.js +7 -9
  5. package/dist/commands/docker/index.js +5 -4
  6. package/dist/commands/docker/list.d.ts +1 -0
  7. package/dist/commands/docker/list.js +31 -17
  8. package/dist/commands/docker/status.d.ts +3 -1
  9. package/dist/commands/docker/status.js +28 -2
  10. package/dist/commands/docker/sync.js +7 -6
  11. package/dist/commands/epic/list.js +17 -2
  12. package/dist/commands/execution/list.js +25 -17
  13. package/dist/commands/pmo/init.js +22 -3
  14. package/dist/commands/repo/list.js +14 -8
  15. package/dist/commands/repo/view.js +2 -1
  16. package/dist/commands/roadmap/list.js +16 -1
  17. package/dist/commands/session/health.js +11 -10
  18. package/dist/commands/session/list.js +15 -8
  19. package/dist/commands/staff/list.d.ts +3 -1
  20. package/dist/commands/staff/list.js +15 -1
  21. package/dist/commands/theme/list.d.ts +3 -0
  22. package/dist/commands/theme/list.js +25 -0
  23. package/dist/commands/ticket/complete.js +4 -1
  24. package/dist/commands/ticket/create.d.ts +1 -0
  25. package/dist/commands/ticket/create.js +30 -0
  26. package/dist/commands/ticket/delete.js +3 -3
  27. package/dist/commands/ticket/edit.js +2 -2
  28. package/dist/commands/ticket/list.js +24 -5
  29. package/dist/commands/ticket/move.js +4 -1
  30. package/dist/commands/ticket/view.js +4 -2
  31. package/dist/commands/whoami.d.ts +3 -0
  32. package/dist/commands/whoami.js +22 -5
  33. package/dist/commands/work/complete.js +2 -2
  34. package/dist/commands/work/ready.js +2 -2
  35. package/dist/commands/work/revise.js +2 -2
  36. package/dist/commands/work/start.js +4 -4
  37. package/dist/commands/workspace/prune.d.ts +3 -2
  38. package/dist/commands/workspace/prune.js +70 -10
  39. package/dist/lib/agents/commands.js +4 -0
  40. package/dist/lib/agents/index.js +12 -0
  41. package/dist/lib/execution/devcontainer.d.ts +4 -0
  42. package/dist/lib/execution/devcontainer.js +63 -0
  43. package/dist/lib/mcp/helpers.d.ts +15 -0
  44. package/dist/lib/mcp/helpers.js +15 -0
  45. package/dist/lib/mcp/tools/action.js +5 -5
  46. package/dist/lib/mcp/tools/board.js +7 -7
  47. package/dist/lib/mcp/tools/category.js +5 -5
  48. package/dist/lib/mcp/tools/cli-passthrough.js +30 -30
  49. package/dist/lib/mcp/tools/epic.js +8 -8
  50. package/dist/lib/mcp/tools/phase.js +7 -7
  51. package/dist/lib/mcp/tools/project.js +10 -10
  52. package/dist/lib/mcp/tools/roadmap.js +7 -7
  53. package/dist/lib/mcp/tools/spec.js +9 -9
  54. package/dist/lib/mcp/tools/status.js +6 -6
  55. package/dist/lib/mcp/tools/template.js +6 -6
  56. package/dist/lib/mcp/tools/ticket.js +19 -19
  57. package/dist/lib/mcp/tools/view.js +4 -4
  58. package/dist/lib/mcp/tools/work.js +6 -6
  59. package/dist/lib/mcp/tools/workflow.js +5 -5
  60. package/dist/lib/pmo/index.js +4 -0
  61. package/dist/lib/pmo/storage/base.js +49 -0
  62. package/dist/lib/pr/index.d.ts +5 -0
  63. package/dist/lib/pr/index.js +69 -0
  64. package/dist/lib/repos/index.js +4 -0
  65. package/dist/lib/string-utils.d.ts +10 -0
  66. package/dist/lib/string-utils.js +16 -0
  67. package/oclif.manifest.json +2266 -2189
  68. 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(padEnd('Name', 35) +
76
- padEnd('Type', 8) +
77
- padEnd('Owner', 12) +
78
- padEnd('Description', 25) +
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(padEnd(branch.name.substring(0, 33), 33)) +
97
- padEnd(typeDisplay, 8) +
98
- padEnd(ownerDisplay.substring(0, 10), 12) +
99
- padEnd(descDisplay.substring(0, 23), 25) +
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(padEnd('Container ID', 15) +
76
- padEnd('Name', 30) +
77
- padEnd('Status', 15) +
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(padEnd(container.id, 15) +
82
- padEnd(truncate(container.name, 28), 30) +
83
- padEnd(container.status, 15) +
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'.padEnd(14)} ${'Agent'.padEnd(15)} Status`);
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.padEnd(14)} ${exec.agentName.padEnd(15)} ${styles.success(exec.status)}`,
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).padEnd(14)} ${container.agentName.padEnd(15)} ${statusText}`,
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).padEnd(14)} ${agentName.padEnd(15)} ${statusText}`,
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(padEnd('ID', 15) +
69
- padEnd('Agent', 15) +
70
- padEnd('Ticket', 12) +
71
- padEnd('Status', 12) +
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(padEnd(exec.id, 15) +
84
- padEnd(exec.agentName, 15) +
85
- padEnd(exec.ticketId, 12) +
86
- statusColor(padEnd(exec.status, 12)) +
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(padEnd('Container ID', 15) +
97
- padEnd('Agent', 15) +
98
- padEnd('Status', 15) +
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(padEnd(container.id, 15) +
107
- padEnd(agentName, 15) +
108
- statusColor(padEnd(container.status, 15)) +
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'.padEnd(18) +
58
- 'Agent'.padEnd(15) +
59
- 'Status'.padEnd(12) +
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.padEnd(18) +
68
- container.agentName.padEnd(15) +
69
- statusColor(container.status.padEnd(12)) +
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(padEnd('ID', 14) +
66
- padEnd('Ticket', 9) +
67
- padEnd('Agent', 10) +
68
- padEnd('Env', 13) +
69
- padEnd('Display', 11) +
70
- padEnd('Perms', 8) +
71
- padEnd('Status', 10) +
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(padEnd(exec.id, 14) +
83
- padEnd(exec.ticketId, 9) +
84
- padEnd(exec.agentName, 10) +
85
- padEnd(envStr, 13) +
86
- padEnd(exec.displayMode, 11) +
87
- permsColor(padEnd(permsStr, 8)) +
88
- statusColor(padEnd(exec.status, 10)) +
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` : undefined;
141
- const boardName = flags.name || await promptForBoardName(defaultBoardName);
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'.padEnd(20) +
57
- 'Status'.padEnd(10) +
58
- 'Branch'.padEnd(15) +
59
- 'Commits'.padEnd(12) +
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.padEnd(20)) +
78
- statusColor(repo.status.padEnd(10)) +
79
- colors.warning((repo.branch || '-').padEnd(15)) +
80
- colors.text(commits.padEnd(12)) +
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