@proletariat/cli 0.3.35 → 0.3.40

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 (148) hide show
  1. package/README.md +37 -2
  2. package/bin/dev.js +0 -0
  3. package/dist/commands/agent/auth.d.ts +12 -2
  4. package/dist/commands/agent/auth.js +128 -4
  5. package/dist/commands/agent/list.js +16 -7
  6. package/dist/commands/agent/status.js +32 -4
  7. package/dist/commands/board/watch.js +6 -0
  8. package/dist/commands/branch/list.d.ts +1 -0
  9. package/dist/commands/branch/list.js +43 -12
  10. package/dist/commands/branch/where.js +9 -19
  11. package/dist/commands/category/list.d.ts +2 -1
  12. package/dist/commands/category/list.js +38 -13
  13. package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
  14. package/dist/commands/{claude.js → claude/index.js} +12 -12
  15. package/dist/commands/claude/open.d.ts +13 -0
  16. package/dist/commands/claude/open.js +175 -0
  17. package/dist/commands/diet.js +18 -2
  18. package/dist/commands/docker/logs.js +7 -3
  19. package/dist/commands/docker/shell.js +6 -0
  20. package/dist/commands/docker/start.js +20 -4
  21. package/dist/commands/docker/sync.d.ts +4 -0
  22. package/dist/commands/docker/sync.js +30 -2
  23. package/dist/commands/epic/show.d.ts +13 -0
  24. package/dist/commands/epic/show.js +16 -0
  25. package/dist/commands/epic/ticket.js +7 -24
  26. package/dist/commands/epic/view.js +27 -0
  27. package/dist/commands/execution/config.d.ts +0 -4
  28. package/dist/commands/execution/config.js +14 -46
  29. package/dist/commands/execution/index.js +2 -1
  30. package/dist/commands/execution/logs.js +7 -1
  31. package/dist/commands/execution/stop.js +2 -1
  32. package/dist/commands/execution/view.js +30 -26
  33. package/dist/commands/init.js +2 -19
  34. package/dist/commands/label/create.js +2 -1
  35. package/dist/commands/label/delete.js +2 -1
  36. package/dist/commands/label/group/create.js +2 -1
  37. package/dist/commands/label/group/list.js +2 -1
  38. package/dist/commands/label/list.js +2 -1
  39. package/dist/commands/mcp-server.js +27 -1
  40. package/dist/commands/phase/template/list.js +2 -1
  41. package/dist/commands/pmo/init.js +12 -40
  42. package/dist/commands/project/create.js +3 -4
  43. package/dist/commands/project/update.js +5 -6
  44. package/dist/commands/pull.js +24 -0
  45. package/dist/commands/qa/index.d.ts +54 -0
  46. package/dist/commands/qa/index.js +762 -0
  47. package/dist/commands/repo/view.js +2 -8
  48. package/dist/commands/session/attach.js +4 -4
  49. package/dist/commands/session/create.d.ts +19 -0
  50. package/dist/commands/session/create.js +102 -0
  51. package/dist/commands/session/health.js +4 -23
  52. package/dist/commands/session/index.js +14 -1
  53. package/dist/commands/session/list.js +9 -8
  54. package/dist/commands/session/peek.d.ts +38 -0
  55. package/dist/commands/session/peek.js +316 -0
  56. package/dist/commands/session/poke.d.ts +27 -0
  57. package/dist/commands/session/poke.js +219 -0
  58. package/dist/commands/spec/view.js +29 -0
  59. package/dist/commands/template/list.js +2 -1
  60. package/dist/commands/theme/add-names.d.ts +4 -0
  61. package/dist/commands/theme/add-names.js +11 -1
  62. package/dist/commands/theme/create.d.ts +2 -0
  63. package/dist/commands/theme/create.js +8 -0
  64. package/dist/commands/ticket/bulk.js +2 -2
  65. package/dist/commands/ticket/complete.js +2 -2
  66. package/dist/commands/ticket/create.js +21 -0
  67. package/dist/commands/ticket/delete.js +8 -0
  68. package/dist/commands/ticket/edit.js +25 -0
  69. package/dist/commands/ticket/epic.js +17 -43
  70. package/dist/commands/ticket/index.js +2 -2
  71. package/dist/commands/ticket/move.js +25 -2
  72. package/dist/commands/ticket/resolve.js +3 -4
  73. package/dist/commands/ticket/show.d.ts +13 -0
  74. package/dist/commands/ticket/show.js +16 -0
  75. package/dist/commands/ticket/template/list.js +2 -1
  76. package/dist/commands/ticket/view.d.ts +0 -1
  77. package/dist/commands/ticket/view.js +30 -1
  78. package/dist/commands/work/index.js +4 -0
  79. package/dist/commands/work/spawn-all.js +1 -1
  80. package/dist/commands/work/spawn.js +15 -4
  81. package/dist/commands/work/start.js +186 -103
  82. package/dist/commands/work/status.d.ts +14 -0
  83. package/dist/commands/work/status.js +60 -0
  84. package/dist/commands/work/watch.js +1 -1
  85. package/dist/commands/workflow/index.js +2 -1
  86. package/dist/commands/workflow/show.d.ts +13 -0
  87. package/dist/commands/workflow/show.js +16 -0
  88. package/dist/commands/workspace/add.js +15 -0
  89. package/dist/commands/workspace/list.js +2 -1
  90. package/dist/commands/workspace/prune.js +7 -7
  91. package/dist/hooks/init.js +10 -2
  92. package/dist/lib/agents/commands.d.ts +5 -0
  93. package/dist/lib/agents/commands.js +143 -97
  94. package/dist/lib/branch/index.d.ts +1 -0
  95. package/dist/lib/database/drizzle-schema.d.ts +465 -0
  96. package/dist/lib/database/drizzle-schema.js +53 -0
  97. package/dist/lib/database/index.d.ts +47 -1
  98. package/dist/lib/database/index.js +138 -20
  99. package/dist/lib/execution/config.d.ts +15 -1
  100. package/dist/lib/execution/config.js +28 -0
  101. package/dist/lib/execution/runners.d.ts +45 -0
  102. package/dist/lib/execution/runners.js +187 -26
  103. package/dist/lib/execution/session-utils.d.ts +16 -1
  104. package/dist/lib/execution/session-utils.js +71 -4
  105. package/dist/lib/execution/spawner.js +15 -2
  106. package/dist/lib/execution/storage.d.ts +6 -1
  107. package/dist/lib/execution/storage.js +35 -5
  108. package/dist/lib/execution/types.d.ts +3 -0
  109. package/dist/lib/mcp/tools/board.js +4 -6
  110. package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
  111. package/dist/lib/mcp/tools/epic.js +8 -3
  112. package/dist/lib/mcp/tools/index.d.ts +1 -0
  113. package/dist/lib/mcp/tools/index.js +1 -0
  114. package/dist/lib/mcp/tools/spec.js +1 -1
  115. package/dist/lib/mcp/tools/ticket.js +11 -9
  116. package/dist/lib/mcp/tools/tmux.d.ts +16 -0
  117. package/dist/lib/mcp/tools/tmux.js +182 -0
  118. package/dist/lib/mcp/tools/work.js +148 -6
  119. package/dist/lib/mcp/types.d.ts +10 -0
  120. package/dist/lib/multiline-input.js +2 -1
  121. package/dist/lib/pmo/base-command.js +4 -4
  122. package/dist/lib/pmo/schema.d.ts +1 -1
  123. package/dist/lib/pmo/schema.js +1 -0
  124. package/dist/lib/pmo/storage/actions.js +1 -1
  125. package/dist/lib/pmo/storage/base.js +402 -50
  126. package/dist/lib/pmo/storage/dependencies.d.ts +1 -0
  127. package/dist/lib/pmo/storage/dependencies.js +11 -3
  128. package/dist/lib/pmo/storage/epics.js +1 -1
  129. package/dist/lib/pmo/storage/helpers.d.ts +4 -4
  130. package/dist/lib/pmo/storage/helpers.js +36 -26
  131. package/dist/lib/pmo/storage/projects.d.ts +2 -0
  132. package/dist/lib/pmo/storage/projects.js +207 -119
  133. package/dist/lib/pmo/storage/specs.d.ts +2 -0
  134. package/dist/lib/pmo/storage/specs.js +274 -188
  135. package/dist/lib/pmo/storage/tickets.d.ts +2 -0
  136. package/dist/lib/pmo/storage/tickets.js +350 -290
  137. package/dist/lib/pmo/storage/types.d.ts +1 -0
  138. package/dist/lib/pmo/storage/views.d.ts +2 -0
  139. package/dist/lib/pmo/storage/views.js +183 -130
  140. package/dist/lib/prompt-command.d.ts +20 -0
  141. package/dist/lib/prompt-command.js +38 -2
  142. package/dist/lib/prompt-json.d.ts +41 -4
  143. package/dist/lib/prompt-json.js +138 -7
  144. package/dist/lib/styles.d.ts +37 -0
  145. package/dist/lib/styles.js +73 -0
  146. package/oclif.manifest.json +4046 -3385
  147. package/package.json +11 -6
  148. package/LICENSE +0 -190
@@ -1,9 +1,11 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
3
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
3
4
  import { styles } from '../../lib/styles.js';
4
5
  export default class CategoryList extends PMOCommand {
5
6
  static description = 'List categories for ticket or status types';
6
7
  static examples = [
8
+ '<%= config.bin %> <%= command.id %>',
7
9
  '<%= config.bin %> <%= command.id %> --type ticket',
8
10
  '<%= config.bin %> <%= command.id %> --type status',
9
11
  '<%= config.bin %> <%= command.id %> --type ticket --builtin',
@@ -13,9 +15,8 @@ export default class CategoryList extends PMOCommand {
13
15
  ...pmoBaseFlags,
14
16
  type: Flags.string({
15
17
  char: 't',
16
- description: 'Category type to list',
18
+ description: 'Category type to list (omit to list all)',
17
19
  options: ['ticket', 'status'],
18
- required: true,
19
20
  }),
20
21
  builtin: Flags.boolean({
21
22
  description: 'Show only built-in categories',
@@ -36,25 +37,52 @@ export default class CategoryList extends PMOCommand {
36
37
  async execute() {
37
38
  const { flags } = await this.parse(CategoryList);
38
39
  const categoryType = flags.type;
39
- const filter = { type: categoryType };
40
+ const filter = {};
41
+ if (categoryType)
42
+ filter.type = categoryType;
40
43
  if (flags.builtin)
41
44
  filter.isBuiltin = true;
42
45
  if (flags.custom)
43
46
  filter.isBuiltin = false;
44
47
  const categories = await this.storage.listCategories(filter);
45
- if (flags.json || flags.machine) {
48
+ if (shouldOutputJson(flags)) {
46
49
  this.log(JSON.stringify(categories, null, 2));
47
50
  return;
48
51
  }
49
52
  if (categories.length === 0) {
50
- this.log(styles.muted(`\nNo ${categoryType} categories found.`));
51
- this.log(styles.muted(`Create one: prlt category create --type ${categoryType}`));
53
+ const typeStr = categoryType ? `${categoryType} ` : '';
54
+ this.log(styles.muted(`\nNo ${typeStr}categories found.`));
55
+ this.log(styles.muted(`Create one: prlt category create --type ticket`));
52
56
  return;
53
57
  }
54
- const typeLabel = categoryType === 'ticket' ? 'Ticket' : 'Status';
55
- this.log(`\n📁 ${styles.emphasis(`${typeLabel} Categories`)}`);
56
- this.log(''.repeat(60));
57
- // Group by builtin vs custom
58
+ if (categoryType) {
59
+ // Single type: show as before
60
+ const typeLabel = categoryType === 'ticket' ? 'Ticket' : 'Status';
61
+ this.log(`\n📁 ${styles.emphasis(`${typeLabel} Categories`)}`);
62
+ this.log('═'.repeat(60));
63
+ this.printCategoryGroup(categories, flags);
64
+ this.log('');
65
+ this.log(styles.muted(`Create new: prlt category create --type ${categoryType} <name>`));
66
+ this.log('');
67
+ }
68
+ else {
69
+ // All types: group by type
70
+ const ticketCategories = categories.filter(c => c.type === 'ticket');
71
+ const statusCategories = categories.filter(c => c.type === 'status');
72
+ if (ticketCategories.length > 0) {
73
+ this.log(`\n📁 ${styles.emphasis('Ticket Categories')}`);
74
+ this.log('═'.repeat(60));
75
+ this.printCategoryGroup(ticketCategories, flags);
76
+ }
77
+ if (statusCategories.length > 0) {
78
+ this.log(`\n📁 ${styles.emphasis('Status Categories')}`);
79
+ this.log('═'.repeat(60));
80
+ this.printCategoryGroup(statusCategories, flags);
81
+ }
82
+ this.log('');
83
+ }
84
+ }
85
+ printCategoryGroup(categories, flags) {
58
86
  const builtinCategories = categories.filter(c => c.isBuiltin);
59
87
  const customCategories = categories.filter(c => !c.isBuiltin);
60
88
  if (builtinCategories.length > 0 && !flags.custom) {
@@ -71,9 +99,6 @@ export default class CategoryList extends PMOCommand {
71
99
  this.printCategory(category);
72
100
  }
73
101
  }
74
- this.log('');
75
- this.log(styles.muted(`Create new: prlt category create --type ${categoryType} <name>`));
76
- this.log('');
77
102
  }
78
103
  printCategory(category) {
79
104
  const builtinBadge = category.isBuiltin ? '' : ' [custom]';
@@ -1,4 +1,4 @@
1
- import { PromptCommand } from '../lib/prompt-command.js';
1
+ import { PromptCommand } from '../../lib/prompt-command.js';
2
2
  export default class Claude extends PromptCommand {
3
3
  static description: string;
4
4
  static examples: string[];
@@ -3,18 +3,18 @@ import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import * as os from 'node:os';
5
5
  import { execSync } from 'node:child_process';
6
- import { PromptCommand } from '../lib/prompt-command.js';
7
- import { machineOutputFlags } from '../lib/pmo/index.js';
6
+ import { PromptCommand } from '../../lib/prompt-command.js';
7
+ import { machineOutputFlags } from '../../lib/pmo/index.js';
8
8
  import Database from 'better-sqlite3';
9
- import { findHQRoot } from '../lib/workspace.js';
10
- import { getWorkspaceInfo, createEphemeralAgent, } from '../lib/agents/commands.js';
11
- import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../lib/prompt-json.js';
12
- import { styles } from '../lib/styles.js';
13
- import { DEFAULT_EXECUTION_CONFIG, } from '../lib/execution/types.js';
14
- import { runExecution, isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled } from '../lib/execution/runners.js';
15
- import { ExecutionStorage } from '../lib/execution/storage.js';
16
- import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, } from '../lib/execution/config.js';
17
- import { hasDevcontainerConfig } from '../lib/execution/devcontainer.js';
9
+ import { findHQRoot } from '../../lib/workspace.js';
10
+ import { getWorkspaceInfo, createEphemeralAgent, } from '../../lib/agents/commands.js';
11
+ import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
12
+ import { styles } from '../../lib/styles.js';
13
+ import { DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
14
+ import { runExecution, isDockerRunning, isGitHubTokenAvailable, isDevcontainerCliInstalled } from '../../lib/execution/runners.js';
15
+ import { ExecutionStorage } from '../../lib/execution/storage.js';
16
+ import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, } from '../../lib/execution/config.js';
17
+ import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
18
18
  // Catch-all devcontainer image for directories without .devcontainer
19
19
  const CATCHALL_DEVCONTAINER_IMAGE = 'ghcr.io/chrismcdermut/proletariat-claude:latest';
20
20
  /**
@@ -450,7 +450,7 @@ export default class Claude extends PromptCommand {
450
450
  const executionStorage = new ExecutionStorage(db);
451
451
  try {
452
452
  // Import PMO storage for ticket/project operations
453
- const { getPMOContext } = await import('../lib/pmo/index.js');
453
+ const { getPMOContext } = await import('../../lib/pmo/index.js');
454
454
  let pmoPath;
455
455
  let storage;
456
456
  try {
@@ -0,0 +1,13 @@
1
+ import { PromptCommand } from '../../lib/prompt-command.js';
2
+ export default class Open extends PromptCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ agent: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ directory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ prompt: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ run(): Promise<void>;
13
+ }
@@ -0,0 +1,175 @@
1
+ import { Flags } from '@oclif/core';
2
+ import * as path from 'node:path';
3
+ import * as fs from 'node:fs';
4
+ import Database from 'better-sqlite3';
5
+ import { PromptCommand } from '../../lib/prompt-command.js';
6
+ import { machineOutputFlags } from '../../lib/pmo/index.js';
7
+ import { findHQRoot } from '../../lib/workspace.js';
8
+ import { getWorkspaceInfo } from '../../lib/agents/commands.js';
9
+ import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
10
+ import { styles } from '../../lib/styles.js';
11
+ import { DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
12
+ import { runExecution } from '../../lib/execution/runners.js';
13
+ import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, } from '../../lib/execution/config.js';
14
+ export default class Open extends PromptCommand {
15
+ static description = 'Open Claude Code in a new terminal tab (side-by-side workflow)';
16
+ static examples = [
17
+ '<%= config.bin %> <%= command.id %>',
18
+ '<%= config.bin %> <%= command.id %> --agent altman',
19
+ '<%= config.bin %> <%= command.id %> --directory /path/to/project',
20
+ '<%= config.bin %> <%= command.id %> --prompt "help me debug the auth flow"',
21
+ ];
22
+ static flags = {
23
+ ...machineOutputFlags,
24
+ agent: Flags.string({
25
+ char: 'a',
26
+ description: 'Open in an agent\'s workspace directory',
27
+ }),
28
+ directory: Flags.string({
29
+ char: 'C',
30
+ description: 'Directory to open in (default: current directory)',
31
+ }),
32
+ prompt: Flags.string({
33
+ char: 'p',
34
+ description: 'Initial prompt for Claude Code',
35
+ }),
36
+ };
37
+ async run() {
38
+ const { flags } = await this.parse(Open);
39
+ const jsonMode = shouldOutputJson(flags);
40
+ // Resolve the target directory
41
+ let workDir;
42
+ let label;
43
+ if (flags.agent) {
44
+ // Resolve agent workspace directory
45
+ let workspaceInfo;
46
+ try {
47
+ workspaceInfo = getWorkspaceInfo();
48
+ }
49
+ catch {
50
+ if (jsonMode) {
51
+ outputErrorAsJson('NO_WORKSPACE', 'Not in an HQ or workspace. --agent requires an HQ.', createMetadata('open', flags));
52
+ }
53
+ this.error('Not in an HQ or workspace. --agent requires an HQ.');
54
+ }
55
+ const agent = workspaceInfo.agents.find(a => a.name === flags.agent);
56
+ if (!agent) {
57
+ const available = workspaceInfo.agents.map(a => a.name).join(', ');
58
+ const msg = `Agent "${flags.agent}" not found. Available: ${available || 'none'}`;
59
+ if (jsonMode) {
60
+ outputErrorAsJson('AGENT_NOT_FOUND', msg, createMetadata('open', flags));
61
+ }
62
+ this.error(msg);
63
+ }
64
+ workDir = agent.type === 'ephemeral'
65
+ ? path.join(workspaceInfo.path, 'agents', workspaceInfo.ephemeralAgentsDir, flags.agent)
66
+ : path.join(workspaceInfo.path, 'agents', workspaceInfo.persistentAgentsDir, flags.agent);
67
+ label = flags.agent;
68
+ }
69
+ else if (flags.directory) {
70
+ workDir = path.resolve(flags.directory);
71
+ label = path.basename(workDir);
72
+ }
73
+ else {
74
+ workDir = process.cwd();
75
+ label = path.basename(workDir);
76
+ }
77
+ // Validate directory exists
78
+ if (!fs.existsSync(workDir)) {
79
+ if (jsonMode) {
80
+ outputErrorAsJson('DIRECTORY_NOT_FOUND', `Directory not found: ${workDir}`, createMetadata('open', flags));
81
+ }
82
+ this.error(`Directory not found: ${workDir}`);
83
+ }
84
+ // Build execution context
85
+ const sessionName = `open-${label}`;
86
+ const context = {
87
+ ticketId: 'OPEN',
88
+ ticketTitle: `Open: ${label}`,
89
+ agentName: label,
90
+ agentDir: workDir,
91
+ worktreePath: workDir,
92
+ branch: 'main',
93
+ actionName: 'open',
94
+ actionPrompt: flags.prompt,
95
+ modifiesCode: false,
96
+ };
97
+ // Try to find HQ for execution config, fall back to home dir config
98
+ const hqPath = findHQRoot(workDir);
99
+ if (hqPath) {
100
+ context.hqPath = hqPath;
101
+ }
102
+ // Load execution config
103
+ const executionConfig = { ...DEFAULT_EXECUTION_CONFIG };
104
+ executionConfig.outputMode = 'interactive';
105
+ executionConfig.sandboxed = false; // Default to dangerously-skip-permissions for quick open
106
+ // Try to load saved preferences from workspace DB or home dir
107
+ const dbPath = hqPath
108
+ ? path.join(hqPath, '.proletariat', 'workspace.db')
109
+ : path.join(process.env.HOME || '', '.proletariat', 'adhoc.db');
110
+ let db = null;
111
+ try {
112
+ if (fs.existsSync(dbPath)) {
113
+ db = new Database(dbPath);
114
+ const savedConfig = loadExecutionConfig(db);
115
+ executionConfig.terminal = savedConfig.terminal;
116
+ executionConfig.shell = savedConfig.shell;
117
+ executionConfig.tmux = savedConfig.tmux;
118
+ }
119
+ }
120
+ catch {
121
+ // Ignore config loading errors, use defaults
122
+ }
123
+ // If no terminal preference saved, prompt for it (first run only)
124
+ if (!jsonMode && db) {
125
+ if (!hasTerminalPreference(db)) {
126
+ executionConfig.terminal.app = await promptTerminalPreference(db);
127
+ }
128
+ else {
129
+ executionConfig.terminal.app = await getTerminalApp(db);
130
+ }
131
+ if (!hasShellPreference(db)) {
132
+ executionConfig.shell = await promptShellPreference(db);
133
+ }
134
+ else {
135
+ executionConfig.shell = await getShell(db);
136
+ }
137
+ }
138
+ if (db) {
139
+ db.close();
140
+ }
141
+ // Show what we're doing
142
+ if (!jsonMode) {
143
+ this.log('');
144
+ this.log(styles.muted(` Opening Claude Code in new tab...`));
145
+ this.log(styles.muted(` Directory: ${workDir}`));
146
+ if (flags.prompt) {
147
+ this.log(styles.muted(` Prompt: "${flags.prompt.substring(0, 60)}${flags.prompt.length > 60 ? '...' : ''}"`));
148
+ }
149
+ this.log('');
150
+ }
151
+ // Launch Claude Code in a new terminal tab
152
+ const result = await runExecution('host', context, 'claude-code', executionConfig, {
153
+ displayMode: 'terminal',
154
+ });
155
+ if (result.success) {
156
+ if (jsonMode) {
157
+ outputSuccessAsJson({
158
+ directory: workDir,
159
+ sessionId: result.sessionId,
160
+ sessionName,
161
+ }, createMetadata('open', flags));
162
+ }
163
+ this.log(styles.success(`✓ Claude Code opened in new tab`));
164
+ if (result.sessionId) {
165
+ this.log(styles.muted(` Session: ${result.sessionId}`));
166
+ }
167
+ }
168
+ else {
169
+ if (jsonMode) {
170
+ outputErrorAsJson('EXECUTION_FAILED', `Failed to open: ${result.error}`, createMetadata('open', flags));
171
+ }
172
+ this.error(`Failed to open: ${result.error}`);
173
+ }
174
+ }
175
+ }
@@ -1,5 +1,6 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import { PMOCommand, pmoBaseFlags } from '../lib/pmo/base-command.js';
3
+ import { shouldOutputJson } from '../lib/prompt-json.js';
3
4
  import { styles, divider } from '../lib/styles.js';
4
5
  import { loadDietConfig, saveDietConfig, parseDietString, formatDietConfig, } from '../lib/pmo/diet.js';
5
6
  export default class Diet extends PMOCommand {
@@ -23,6 +24,7 @@ export default class Diet extends PMOCommand {
23
24
  };
24
25
  async execute() {
25
26
  const { flags } = await this.parse(Diet);
27
+ const jsonMode = shouldOutputJson(flags);
26
28
  const projectId = await this.requireProject();
27
29
  const db = this.storage.getDatabase();
28
30
  // Handle --set
@@ -30,7 +32,12 @@ export default class Diet extends PMOCommand {
30
32
  try {
31
33
  const newConfig = parseDietString(flags.set);
32
34
  saveDietConfig(db, newConfig);
33
- this.log(styles.success(`Diet updated: ${formatDietConfig(newConfig)}`));
35
+ if (jsonMode) {
36
+ this.log(JSON.stringify({ type: 'success', result: { action: 'set', diet: newConfig } }, null, 2));
37
+ }
38
+ else {
39
+ this.log(styles.success(`Diet updated: ${formatDietConfig(newConfig)}`));
40
+ }
34
41
  }
35
42
  catch (error) {
36
43
  const message = error instanceof Error ? error.message : String(error);
@@ -42,12 +49,21 @@ export default class Diet extends PMOCommand {
42
49
  if (flags.reset) {
43
50
  const { DEFAULT_DIET_CONFIG } = await import('../lib/pmo/diet.js');
44
51
  saveDietConfig(db, DEFAULT_DIET_CONFIG);
45
- this.log(styles.success(`Diet reset to defaults: ${formatDietConfig(DEFAULT_DIET_CONFIG)}`));
52
+ if (jsonMode) {
53
+ this.log(JSON.stringify({ type: 'success', result: { action: 'reset', diet: DEFAULT_DIET_CONFIG } }, null, 2));
54
+ }
55
+ else {
56
+ this.log(styles.success(`Diet reset to defaults: ${formatDietConfig(DEFAULT_DIET_CONFIG)}`));
57
+ }
46
58
  return;
47
59
  }
48
60
  // Show diet report
49
61
  const dietConfig = loadDietConfig(db);
50
62
  const report = await this.buildDietReport(projectId, dietConfig);
63
+ if (jsonMode) {
64
+ this.log(JSON.stringify({ type: 'success', result: { diet: dietConfig, report } }, null, 2));
65
+ return;
66
+ }
51
67
  this.displayDietReport(report, dietConfig);
52
68
  }
53
69
  /**
@@ -8,6 +8,7 @@ import { ExecutionStorage } from '../../lib/execution/storage.js';
8
8
  import { isDockerRunning } from '../../lib/execution/runners.js';
9
9
  import { resolveContainerId } from '../../lib/docker/resolve.js';
10
10
  import { machineOutputFlags } from '../../lib/pmo/index.js';
11
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
11
12
  export default class DockerLogs extends Command {
12
13
  static description = 'View logs from a container (by execution ID, agent name, or container ID)';
13
14
  static examples = [
@@ -42,6 +43,7 @@ export default class DockerLogs extends Command {
42
43
  };
43
44
  async run() {
44
45
  const { args, flags } = await this.parse(DockerLogs);
46
+ const jsonMode = shouldOutputJson(flags);
45
47
  if (!isDockerRunning()) {
46
48
  this.error('Docker is not running. Start Docker Desktop or the Docker daemon first.');
47
49
  }
@@ -81,9 +83,11 @@ export default class DockerLogs extends Command {
81
83
  dockerArgs.push('--timestamps');
82
84
  }
83
85
  dockerArgs.push(result.containerId);
84
- this.log(`\n${styles.header(`Logs for ${result.displayName}`)}`);
85
- this.log(styles.muted(`Container: ${result.containerId}`));
86
- this.log('─'.repeat(60) + '\n');
86
+ if (!jsonMode) {
87
+ this.log(`\n${styles.header(`Logs for ${result.displayName}`)}`);
88
+ this.log(styles.muted(`Container: ${result.containerId}`));
89
+ this.log('─'.repeat(60) + '\n');
90
+ }
87
91
  db.close();
88
92
  if (flags.follow) {
89
93
  // Stream logs
@@ -8,6 +8,7 @@ import { ExecutionStorage } from '../../lib/execution/storage.js';
8
8
  import { isDockerRunning } from '../../lib/execution/runners.js';
9
9
  import { resolveContainerId, isContainerRunning } from '../../lib/docker/resolve.js';
10
10
  import { machineOutputFlags } from '../../lib/pmo/index.js';
11
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
11
12
  export default class DockerShell extends Command {
12
13
  static description = 'Open a shell in a running container (by execution ID, agent name, or container ID)';
13
14
  static examples = [
@@ -39,6 +40,11 @@ export default class DockerShell extends Command {
39
40
  };
40
41
  async run() {
41
42
  const { args, flags } = await this.parse(DockerShell);
43
+ if (shouldOutputJson(flags)) {
44
+ this.log(JSON.stringify({ type: 'error', error: { code: 'REQUIRES_TTY', message: 'docker shell requires an interactive terminal' } }));
45
+ this.exit(1);
46
+ return;
47
+ }
42
48
  if (!isDockerRunning()) {
43
49
  this.error('Docker is not running. Start Docker Desktop or the Docker daemon first.');
44
50
  }
@@ -8,6 +8,7 @@ import { ExecutionStorage } from '../../lib/execution/storage.js';
8
8
  import { isDockerRunning } from '../../lib/execution/runners.js';
9
9
  import { resolveContainerId, isContainerRunning, sanitizeContainerId } from '../../lib/docker/resolve.js';
10
10
  import { machineOutputFlags } from '../../lib/pmo/index.js';
11
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
11
12
  export default class DockerStart extends Command {
12
13
  static description = 'Start a stopped container (by execution ID, agent name, or container ID)';
13
14
  static examples = [
@@ -31,6 +32,7 @@ export default class DockerStart extends Command {
31
32
  };
32
33
  async run() {
33
34
  const { args, flags } = await this.parse(DockerStart);
35
+ const jsonMode = shouldOutputJson(flags);
34
36
  if (!isDockerRunning()) {
35
37
  this.error('Docker is not running. Start Docker Desktop or the Docker daemon first.');
36
38
  }
@@ -60,15 +62,24 @@ export default class DockerStart extends Command {
60
62
  }
61
63
  // Check if container is already running
62
64
  if (isContainerRunning(result.containerId)) {
65
+ if (jsonMode) {
66
+ this.log(JSON.stringify({ type: 'success', result: { containerId: result.containerId, displayName: result.displayName, executionId: result.executionId, status: 'already_running' } }, null, 2));
67
+ db.close();
68
+ return;
69
+ }
63
70
  this.log(`\n${styles.warning(`Container ${result.displayName} is already running`)}\n`);
64
71
  db.close();
65
72
  return;
66
73
  }
67
- this.log(`\n${styles.header('Start Container')}`);
68
- this.log(styles.muted(`Target: ${result.displayName}`));
69
- this.log(styles.muted(`Container: ${result.containerId.substring(0, 12)}\n`));
74
+ if (!jsonMode) {
75
+ this.log(`\n${styles.header('Start Container')}`);
76
+ this.log(styles.muted(`Target: ${result.displayName}`));
77
+ this.log(styles.muted(`Container: ${result.containerId.substring(0, 12)}\n`));
78
+ }
70
79
  // Start container
71
- this.log(styles.muted('Starting container...'));
80
+ if (!jsonMode) {
81
+ this.log(styles.muted('Starting container...'));
82
+ }
72
83
  try {
73
84
  const attachFlag = flags.attach ? '-a' : '';
74
85
  const safeId = sanitizeContainerId(result.containerId);
@@ -79,6 +90,11 @@ export default class DockerStart extends Command {
79
90
  if (result.executionId) {
80
91
  executionStorage.updateStatus(result.executionId, 'running');
81
92
  }
93
+ if (jsonMode) {
94
+ this.log(JSON.stringify({ type: 'success', result: { containerId: result.containerId, displayName: result.displayName, executionId: result.executionId, status: 'started' } }, null, 2));
95
+ db.close();
96
+ return;
97
+ }
82
98
  this.log(`${styles.success('Container started successfully')}\n`);
83
99
  }
84
100
  catch (error) {
@@ -2,6 +2,10 @@ import { Command } from '@oclif/core';
2
2
  export default class DockerSync extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
+ static flags: {
6
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ };
5
9
  run(): Promise<void>;
6
10
  /**
7
11
  * Get raw container info from Docker
@@ -7,12 +7,19 @@ 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
9
  import { visualPadEnd } from '../../lib/string-utils.js';
10
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
11
+ import { machineOutputFlags } from '../../lib/pmo/index.js';
10
12
  export default class DockerSync extends Command {
11
13
  static description = 'Sync container status from Docker into the database';
12
14
  static examples = [
13
15
  '<%= config.bin %> <%= command.id %>',
14
16
  ];
17
+ static flags = {
18
+ ...machineOutputFlags,
19
+ };
15
20
  async run() {
21
+ const { flags } = await this.parse(DockerSync);
22
+ const jsonMode = shouldOutputJson(flags);
16
23
  if (!isDockerRunning()) {
17
24
  this.error('Docker is not running. Start Docker Desktop or the Docker daemon first.');
18
25
  }
@@ -37,8 +44,10 @@ export default class DockerSync extends Command {
37
44
  const containerStorage = new ContainerStorage(db);
38
45
  // Get devcontainers from Docker
39
46
  const dockerContainers = this.getDockerContainers();
40
- this.log(`\n${styles.header('Syncing Containers')}`);
41
- this.log(styles.muted(`Found ${dockerContainers.length} devcontainers in Docker\n`));
47
+ if (!jsonMode) {
48
+ this.log(`\n${styles.header('Syncing Containers')}`);
49
+ this.log(styles.muted(`Found ${dockerContainers.length} devcontainers in Docker\n`));
50
+ }
42
51
  // Add agent name from image (only used for new containers)
43
52
  const containersWithAgent = dockerContainers.map(c => ({
44
53
  ...c,
@@ -46,6 +55,25 @@ export default class DockerSync extends Command {
46
55
  }));
47
56
  // Sync with database
48
57
  const result = containerStorage.syncFromDocker(containersWithAgent);
58
+ if (jsonMode) {
59
+ const containers = containerStorage.listContainers({ limit: 20 });
60
+ this.log(JSON.stringify({
61
+ type: 'success',
62
+ result: {
63
+ added: result.added,
64
+ updated: result.updated,
65
+ removed: result.removed,
66
+ containers: containers.map(c => ({
67
+ id: c.id,
68
+ agentName: c.agentName,
69
+ status: c.status,
70
+ dockerId: c.dockerId,
71
+ })),
72
+ },
73
+ }, null, 2));
74
+ db.close();
75
+ return;
76
+ }
49
77
  this.log(styles.success('✓ Sync complete'));
50
78
  this.log(styles.muted(` Added: ${result.added}`));
51
79
  this.log(styles.muted(` Updated: ${result.updated}`));
@@ -0,0 +1,13 @@
1
+ import EpicView from './view.js';
2
+ export default class EpicShow extends EpicView {
3
+ static description: string;
4
+ static hidden: boolean;
5
+ static args: {
6
+ id: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ }
@@ -0,0 +1,16 @@
1
+ import { Args } from '@oclif/core';
2
+ import { pmoBaseFlags } from '../../lib/pmo/index.js';
3
+ import EpicView from './view.js';
4
+ export default class EpicShow extends EpicView {
5
+ static description = 'View epic details and linked tickets (alias for epic view)';
6
+ static hidden = true;
7
+ static args = {
8
+ id: Args.string({
9
+ description: 'Epic ID',
10
+ required: false,
11
+ }),
12
+ };
13
+ static flags = {
14
+ ...pmoBaseFlags,
15
+ };
16
+ }
@@ -81,11 +81,10 @@ export default class EpicTicket extends PMOCommand {
81
81
  this.log(styles.muted('\nNo tickets found.'));
82
82
  return;
83
83
  }
84
- // Get epic_id for each ticket via direct DB query
85
- const db = this.storage.db;
84
+ // Helper to get ticket's epic ID from the loaded ticket list
86
85
  const getTicketEpicId = (ticketId) => {
87
- const row = db.prepare(`SELECT epic_id FROM pmo_tickets WHERE id = ?`).get(ticketId);
88
- return row?.epic_id || null;
86
+ const ticket = allTickets.find((t) => t.id === ticketId);
87
+ return ticket?.epicId ?? null;
89
88
  };
90
89
  let epicId = args.id;
91
90
  // If no epic ID provided, prompt for selection
@@ -201,11 +200,7 @@ export default class EpicTicket extends PMOCommand {
201
200
  this.log(styles.muted(` ${ticketId} is not linked to ${epicId}, skipping`));
202
201
  continue;
203
202
  }
204
- db.prepare(`
205
- UPDATE pmo_tickets
206
- SET epic_id = NULL, updated_at = ?
207
- WHERE id = ?
208
- `).run(Date.now(), ticketId);
203
+ await this.storage.unlinkTicketFromEpic(ticketId);
209
204
  }
210
205
  else {
211
206
  // Link: check if already linked to same epic
@@ -253,11 +248,7 @@ export default class EpicTicket extends PMOCommand {
253
248
  }
254
249
  if (action === 'use_epic') {
255
250
  // Update ticket to use epic's spec
256
- db.prepare(`
257
- UPDATE pmo_tickets
258
- SET spec_id = ?, updated_at = ?
259
- WHERE id = ?
260
- `).run(epicSpecId, Date.now(), ticketId);
251
+ await this.storage.updateTicket(ticketId, { specId: epicSpecId });
261
252
  this.log(styles.muted(` Updated ${ticketId} to use spec "${epicSpecId}"`));
262
253
  }
263
254
  }
@@ -284,19 +275,11 @@ export default class EpicTicket extends PMOCommand {
284
275
  inherit = result.inherit;
285
276
  }
286
277
  if (inherit) {
287
- db.prepare(`
288
- UPDATE pmo_tickets
289
- SET spec_id = ?, updated_at = ?
290
- WHERE id = ?
291
- `).run(epicSpecId, Date.now(), ticketId);
278
+ await this.storage.updateTicket(ticketId, { specId: epicSpecId });
292
279
  this.log(styles.muted(` Assigned spec "${epicSpecId}" to ${ticketId}`));
293
280
  }
294
281
  }
295
- db.prepare(`
296
- UPDATE pmo_tickets
297
- SET epic_id = ?, updated_at = ?
298
- WHERE id = ?
299
- `).run(epicId, Date.now(), ticketId);
282
+ await this.storage.linkTicketToEpic(ticketId, epicId);
300
283
  }
301
284
  linkedTickets.push(`${ticketId}: ${ticket.title}`);
302
285
  successCount++;