@proletariat/cli 0.3.35 → 0.3.36

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 (108) hide show
  1. package/dist/commands/agent/auth.d.ts +12 -2
  2. package/dist/commands/agent/auth.js +128 -4
  3. package/dist/commands/agent/list.js +16 -7
  4. package/dist/commands/agent/status.js +32 -4
  5. package/dist/commands/board/watch.js +6 -0
  6. package/dist/commands/branch/list.d.ts +1 -0
  7. package/dist/commands/branch/list.js +43 -12
  8. package/dist/commands/branch/where.js +3 -2
  9. package/dist/commands/category/list.d.ts +2 -1
  10. package/dist/commands/category/list.js +38 -13
  11. package/dist/commands/{claude.d.ts → claude/index.d.ts} +1 -1
  12. package/dist/commands/{claude.js → claude/index.js} +12 -12
  13. package/dist/commands/claude/open.d.ts +13 -0
  14. package/dist/commands/claude/open.js +175 -0
  15. package/dist/commands/diet.js +18 -2
  16. package/dist/commands/docker/logs.js +7 -3
  17. package/dist/commands/docker/shell.js +6 -0
  18. package/dist/commands/docker/start.js +20 -4
  19. package/dist/commands/docker/sync.d.ts +4 -0
  20. package/dist/commands/docker/sync.js +30 -2
  21. package/dist/commands/epic/show.d.ts +13 -0
  22. package/dist/commands/epic/show.js +16 -0
  23. package/dist/commands/epic/view.js +27 -0
  24. package/dist/commands/execution/config.d.ts +0 -4
  25. package/dist/commands/execution/config.js +10 -32
  26. package/dist/commands/execution/index.js +2 -1
  27. package/dist/commands/execution/logs.js +1 -1
  28. package/dist/commands/execution/stop.js +2 -1
  29. package/dist/commands/execution/view.js +22 -26
  30. package/dist/commands/init.js +2 -19
  31. package/dist/commands/label/create.js +2 -1
  32. package/dist/commands/label/delete.js +2 -1
  33. package/dist/commands/label/group/create.js +2 -1
  34. package/dist/commands/label/group/list.js +2 -1
  35. package/dist/commands/label/list.js +2 -1
  36. package/dist/commands/mcp-server.js +25 -0
  37. package/dist/commands/phase/template/list.js +2 -1
  38. package/dist/commands/project/create.js +3 -4
  39. package/dist/commands/project/update.js +5 -6
  40. package/dist/commands/pull.js +24 -0
  41. package/dist/commands/session/create.d.ts +19 -0
  42. package/dist/commands/session/create.js +102 -0
  43. package/dist/commands/session/health.js +1 -20
  44. package/dist/commands/session/index.js +14 -1
  45. package/dist/commands/session/list.js +26 -7
  46. package/dist/commands/session/peek.d.ts +38 -0
  47. package/dist/commands/session/peek.js +316 -0
  48. package/dist/commands/session/poke.d.ts +27 -0
  49. package/dist/commands/session/poke.js +219 -0
  50. package/dist/commands/spec/view.js +29 -0
  51. package/dist/commands/template/list.js +2 -1
  52. package/dist/commands/theme/add-names.d.ts +4 -0
  53. package/dist/commands/theme/add-names.js +11 -1
  54. package/dist/commands/theme/create.d.ts +2 -0
  55. package/dist/commands/theme/create.js +8 -0
  56. package/dist/commands/ticket/bulk.js +2 -2
  57. package/dist/commands/ticket/complete.js +2 -2
  58. package/dist/commands/ticket/create.js +21 -0
  59. package/dist/commands/ticket/delete.js +8 -0
  60. package/dist/commands/ticket/edit.js +25 -0
  61. package/dist/commands/ticket/index.js +2 -2
  62. package/dist/commands/ticket/move.js +25 -2
  63. package/dist/commands/ticket/resolve.js +3 -4
  64. package/dist/commands/ticket/show.d.ts +13 -0
  65. package/dist/commands/ticket/show.js +16 -0
  66. package/dist/commands/ticket/template/list.js +2 -1
  67. package/dist/commands/ticket/view.d.ts +0 -1
  68. package/dist/commands/ticket/view.js +30 -1
  69. package/dist/commands/work/index.js +4 -0
  70. package/dist/commands/work/start.js +169 -94
  71. package/dist/commands/work/status.d.ts +14 -0
  72. package/dist/commands/work/status.js +60 -0
  73. package/dist/commands/workflow/index.js +2 -1
  74. package/dist/commands/workflow/show.d.ts +13 -0
  75. package/dist/commands/workflow/show.js +16 -0
  76. package/dist/commands/workspace/add.js +15 -0
  77. package/dist/commands/workspace/list.js +2 -1
  78. package/dist/commands/workspace/prune.js +5 -5
  79. package/dist/lib/branch/index.d.ts +1 -0
  80. package/dist/lib/execution/config.d.ts +15 -1
  81. package/dist/lib/execution/config.js +28 -0
  82. package/dist/lib/execution/runners.d.ts +11 -0
  83. package/dist/lib/execution/runners.js +53 -19
  84. package/dist/lib/execution/session-utils.d.ts +11 -1
  85. package/dist/lib/execution/session-utils.js +26 -1
  86. package/dist/lib/execution/storage.d.ts +5 -0
  87. package/dist/lib/execution/storage.js +18 -3
  88. package/dist/lib/execution/types.d.ts +2 -0
  89. package/dist/lib/mcp/tools/board.js +4 -6
  90. package/dist/lib/mcp/tools/cli-passthrough.js +25 -6
  91. package/dist/lib/mcp/tools/epic.js +8 -3
  92. package/dist/lib/mcp/tools/spec.js +1 -1
  93. package/dist/lib/mcp/tools/ticket.js +11 -9
  94. package/dist/lib/mcp/tools/work.js +96 -6
  95. package/dist/lib/mcp/types.d.ts +10 -0
  96. package/dist/lib/multiline-input.js +2 -1
  97. package/dist/lib/pmo/base-command.js +4 -4
  98. package/dist/lib/pmo/storage/actions.js +1 -1
  99. package/dist/lib/pmo/storage/base.js +195 -50
  100. package/dist/lib/pmo/storage/types.d.ts +1 -0
  101. package/dist/lib/prompt-command.d.ts +20 -0
  102. package/dist/lib/prompt-command.js +38 -2
  103. package/dist/lib/prompt-json.d.ts +36 -4
  104. package/dist/lib/prompt-json.js +129 -7
  105. package/dist/lib/styles.d.ts +37 -0
  106. package/dist/lib/styles.js +73 -0
  107. package/oclif.manifest.json +3259 -2701
  108. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import { Command } from '@oclif/core';
2
- export default class Auth extends Command {
1
+ import { PromptCommand } from '../../lib/prompt-command.js';
2
+ export default class Auth extends PromptCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
@@ -7,7 +7,13 @@ export default class Auth extends Command {
7
7
  machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
8
  check: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
+ 'api-key': import("@oclif/core/interfaces").BooleanFlag<boolean>;
10
11
  };
12
+ /**
13
+ * Try to open the workspace database for saving auth preferences.
14
+ * Returns null if not in an HQ directory (auth still works, just won't save preference).
15
+ */
16
+ private tryOpenDb;
11
17
  /**
12
18
  * Check if the claude-credentials volume exists
13
19
  */
@@ -30,5 +36,9 @@ export default class Auth extends Command {
30
36
  * Run the interactive login flow in a temporary container
31
37
  */
32
38
  private runLoginFlow;
39
+ /**
40
+ * Handle API key authentication flow
41
+ */
42
+ private handleApiKey;
33
43
  run(): Promise<void>;
34
44
  }
@@ -1,16 +1,22 @@
1
- import { Command, Flags } from '@oclif/core';
1
+ import { Flags } from '@oclif/core';
2
2
  import { execSync, spawnSync } from 'node:child_process';
3
+ import { PromptCommand } from '../../lib/prompt-command.js';
3
4
  import { colors } from '../../lib/colors.js';
4
5
  import { machineOutputFlags } from '../../lib/pmo/index.js';
5
6
  import { isDockerRunning } from '../../lib/execution/runners.js';
6
7
  import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
8
+ import { findHQRoot } from '../../lib/workspace.js';
9
+ import { getWorkspaceDbPath } from '../../lib/workspace.js';
10
+ import { saveAuthMethod } from '../../lib/execution/config.js';
11
+ import Database from 'better-sqlite3';
7
12
  const CLAUDE_CREDENTIALS_VOLUME = 'claude-credentials';
8
- export default class Auth extends Command {
13
+ export default class Auth extends PromptCommand {
9
14
  static description = 'Set up Claude Code authentication for Docker containers (one-time setup)';
10
15
  static examples = [
11
16
  '<%= config.bin %> <%= command.id %>',
12
17
  '<%= config.bin %> <%= command.id %> --check',
13
18
  '<%= config.bin %> <%= command.id %> --force',
19
+ '<%= config.bin %> <%= command.id %> --api-key',
14
20
  ];
15
21
  static flags = {
16
22
  check: Flags.boolean({
@@ -21,8 +27,30 @@ export default class Auth extends Command {
21
27
  description: 'Force re-authentication even if credentials exist',
22
28
  default: false,
23
29
  }),
30
+ 'api-key': Flags.boolean({
31
+ description: 'Use ANTHROPIC_API_KEY instead of OAuth (saves preference)',
32
+ default: false,
33
+ }),
24
34
  ...machineOutputFlags,
25
35
  };
36
+ /**
37
+ * Try to open the workspace database for saving auth preferences.
38
+ * Returns null if not in an HQ directory (auth still works, just won't save preference).
39
+ */
40
+ tryOpenDb() {
41
+ try {
42
+ const hqPath = findHQRoot();
43
+ if (!hqPath)
44
+ return null;
45
+ const dbPath = getWorkspaceDbPath(hqPath);
46
+ const db = new Database(dbPath);
47
+ db.pragma('foreign_keys = ON');
48
+ return db;
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ }
26
54
  /**
27
55
  * Check if the claude-credentials volume exists
28
56
  */
@@ -114,12 +142,74 @@ export default class Auth extends Command {
114
142
  return false;
115
143
  }
116
144
  }
145
+ /**
146
+ * Handle API key authentication flow
147
+ */
148
+ handleApiKey(jsonMode, flags) {
149
+ const apiKey = process.env.ANTHROPIC_API_KEY;
150
+ if (!apiKey) {
151
+ if (jsonMode) {
152
+ outputErrorAsJson('API_KEY_NOT_SET', 'ANTHROPIC_API_KEY is not set in your environment. Export it and try again.', createMetadata('agent auth', flags));
153
+ }
154
+ this.error('ANTHROPIC_API_KEY is not set in your environment.\nExport it with: export ANTHROPIC_API_KEY=sk-ant-...');
155
+ }
156
+ // Save preference to config if in an HQ
157
+ const db = this.tryOpenDb();
158
+ if (db) {
159
+ try {
160
+ saveAuthMethod(db, 'apikey');
161
+ }
162
+ finally {
163
+ db.close();
164
+ }
165
+ }
166
+ if (jsonMode) {
167
+ outputSuccessAsJson({
168
+ authenticated: true,
169
+ method: 'apikey',
170
+ message: 'ANTHROPIC_API_KEY is set. Auth method saved as default.',
171
+ }, createMetadata('agent auth', flags));
172
+ return;
173
+ }
174
+ this.log(colors.success('✓ ANTHROPIC_API_KEY is set'));
175
+ this.log(colors.textSecondary(' Auth method saved as default: apikey'));
176
+ this.log(colors.textSecondary(' Containers will use your API key for authentication.'));
177
+ this.log('');
178
+ this.log(colors.warning(' Note: This uses API credits, not your Max subscription.'));
179
+ this.log(colors.textSecondary(` Run "${this.config.bin} agent auth" (without --api-key) to switch to OAuth.`));
180
+ }
117
181
  async run() {
118
182
  const { flags } = await this.parse(Auth);
119
183
  // Check if JSON output mode is active
120
184
  const jsonMode = shouldOutputJson(flags);
121
- // Check Docker is running
185
+ const jsonModeConfig = jsonMode ? { flags, commandName: 'agent auth' } : null;
186
+ // Handle --api-key shortcut: validate key and save preference
187
+ if (flags['api-key']) {
188
+ this.handleApiKey(jsonMode, flags);
189
+ return;
190
+ }
191
+ // Check Docker is running (needed for OAuth flow)
122
192
  if (!isDockerRunning()) {
193
+ // If Docker isn't running, offer API key as alternative
194
+ const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
195
+ if (hasApiKey && !flags.check) {
196
+ this.log(colors.warning('Docker is not running.'));
197
+ this.log('');
198
+ const { authChoice } = await this.prompt([{
199
+ type: 'list',
200
+ name: 'authChoice',
201
+ message: 'Docker is required for OAuth. Use API key instead?',
202
+ choices: [
203
+ { name: 'Use ANTHROPIC_API_KEY instead (API credits)', value: 'apikey', command: `${this.config.bin} agent auth --api-key --json` },
204
+ { name: 'Cancel (start Docker first for OAuth)', value: 'cancel' },
205
+ ],
206
+ default: 'apikey',
207
+ }], jsonModeConfig);
208
+ if (authChoice === 'apikey') {
209
+ this.handleApiKey(jsonMode, flags);
210
+ return;
211
+ }
212
+ }
123
213
  if (jsonMode) {
124
214
  outputErrorAsJson('DOCKER_NOT_RUNNING', 'Docker is not running. Please start Docker Desktop and try again.', createMetadata('agent auth', flags));
125
215
  }
@@ -141,6 +231,7 @@ export default class Auth extends Command {
141
231
  if (jsonMode) {
142
232
  outputSuccessAsJson({
143
233
  authenticated: true,
234
+ method: 'oauth',
144
235
  subscriptionType: info?.subscriptionType || 'unknown',
145
236
  expiresAt: info?.expiresAt.toISOString(),
146
237
  }, createMetadata('agent auth', flags));
@@ -166,6 +257,7 @@ export default class Auth extends Command {
166
257
  if (jsonMode) {
167
258
  outputSuccessAsJson({
168
259
  authenticated: true,
260
+ method: 'oauth',
169
261
  subscriptionType: info?.subscriptionType || 'unknown',
170
262
  expiresAt: info?.expiresAt.toISOString(),
171
263
  message: 'Credentials already configured. Use --force to re-authenticate.',
@@ -181,17 +273,49 @@ export default class Auth extends Command {
181
273
  this.log(colors.text('Use --force to re-authenticate.'));
182
274
  return;
183
275
  }
276
+ // Prompt for auth method choice (OAuth or API key)
277
+ const hasApiKey = !!process.env.ANTHROPIC_API_KEY;
278
+ // Only prompt if there's a real choice (API key is available)
279
+ let selectedMethod = 'oauth';
280
+ if (hasApiKey) {
281
+ const { selectedMethod: chosen } = await this.prompt([{
282
+ type: 'list',
283
+ name: 'selectedMethod',
284
+ message: 'Which authentication method would you like to use?',
285
+ choices: [
286
+ { name: 'OAuth (recommended — uses Max subscription)', value: 'oauth', command: `${this.config.bin} agent auth --force --json` },
287
+ { name: 'API key (uses API credits, not Max subscription)', value: 'apikey', command: `${this.config.bin} agent auth --api-key --json` },
288
+ ],
289
+ default: 'oauth',
290
+ }], jsonModeConfig);
291
+ selectedMethod = chosen;
292
+ }
293
+ if (selectedMethod === 'apikey') {
294
+ this.handleApiKey(jsonMode, flags);
295
+ return;
296
+ }
184
297
  // JSON mode cannot handle interactive login flow
185
298
  if (jsonMode) {
186
299
  outputErrorAsJson('INTERACTIVE_REQUIRED', 'Authentication requires interactive login. Run without --json flag to authenticate.', createMetadata('agent auth', flags));
187
300
  }
188
- // Run the login flow
301
+ // Run the OAuth login flow
189
302
  const success = this.runLoginFlow();
190
303
  if (success && this.credentialsExist()) {
304
+ // Save OAuth as auth method preference
305
+ const db = this.tryOpenDb();
306
+ if (db) {
307
+ try {
308
+ saveAuthMethod(db, 'oauth');
309
+ }
310
+ finally {
311
+ db.close();
312
+ }
313
+ }
191
314
  this.log('');
192
315
  this.log(colors.success('✓ Authentication successful!'));
193
316
  this.log(colors.textSecondary(' Credentials saved to Docker volume: ' + CLAUDE_CREDENTIALS_VOLUME));
194
317
  this.log(colors.textSecondary(' All agent containers will share these credentials.'));
318
+ this.log(colors.textSecondary(' Auth method saved as default: oauth'));
195
319
  }
196
320
  else {
197
321
  this.log('');
@@ -35,8 +35,8 @@ export default class List extends PMOCommand {
35
35
  const activeAgents = workspaceInfo.agents.filter(a => a.status === 'active');
36
36
  // Determine type filter - prompt if not provided (but not in JSON mode)
37
37
  let typeFilter = flags.type;
38
- // In JSON mode without type filter, output all agents grouped by type
39
- if (jsonMode && !typeFilter) {
38
+ // In JSON mode, output agents as JSON respecting the type filter
39
+ if (jsonMode) {
40
40
  const staffAgents = activeAgents.filter(a => a.type === 'persistent');
41
41
  const tempAgents = activeAgents.filter(a => a.type === 'ephemeral');
42
42
  const agentsStatus = getAllAgentsStatus(workspaceInfo);
@@ -56,11 +56,20 @@ export default class List extends PMOCommand {
56
56
  };
57
57
  });
58
58
  };
59
- const output = {
60
- staff: formatAgentJson(staffAgents),
61
- temp: formatAgentJson(tempAgents),
62
- all: formatAgentJson(activeAgents),
63
- };
59
+ let output;
60
+ if (typeFilter === 'staff') {
61
+ output = { staff: formatAgentJson(staffAgents) };
62
+ }
63
+ else if (typeFilter === 'temp') {
64
+ output = { temp: formatAgentJson(tempAgents) };
65
+ }
66
+ else {
67
+ // No filter or 'all' - return both groups without redundant combined array
68
+ output = {
69
+ staff: formatAgentJson(staffAgents),
70
+ temp: formatAgentJson(tempAgents),
71
+ };
72
+ }
64
73
  this.log(JSON.stringify(output, null, 2));
65
74
  return;
66
75
  }
@@ -1,8 +1,8 @@
1
1
  import { Args } from '@oclif/core';
2
2
  import { colors, format } from '../../lib/colors.js';
3
- import { getWorkspaceInfo, getAgentStatus, formatAgentList } from '../../lib/agents/commands.js';
3
+ import { getWorkspaceInfo, getAgentStatus, getAllAgentsStatus, formatAgentList } from '../../lib/agents/commands.js';
4
4
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
5
- import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
5
+ import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
6
6
  export default class Status extends PMOCommand {
7
7
  static description = 'Show detailed status for a specific agent';
8
8
  static examples = [
@@ -36,6 +36,13 @@ export default class Status extends PMOCommand {
36
36
  return;
37
37
  }
38
38
  let agentName = args.name;
39
+ // In JSON mode with no agent specified, return all agent statuses
40
+ if (jsonMode && !agentName) {
41
+ const allStatuses = getAllAgentsStatus(workspaceInfo);
42
+ outputSuccessAsJson({
43
+ agents: allStatuses,
44
+ }, createMetadata('agent status', flags));
45
+ }
39
46
  // Agent mode config for prompts
40
47
  const agentConfig = jsonMode ? { flags, commandName: 'agent status' } : null;
41
48
  // Interactive mode if no agent specified
@@ -59,15 +66,36 @@ export default class Status extends PMOCommand {
59
66
  }], agentConfig);
60
67
  agentName = selected;
61
68
  }
62
- await this.showDetailedStatus(workspaceInfo, agentName);
69
+ await this.showDetailedStatus(workspaceInfo, agentName, jsonMode);
63
70
  }
64
- async showDetailedStatus(workspaceInfo, agentName) {
71
+ async showDetailedStatus(workspaceInfo, agentName, jsonMode = false) {
65
72
  // Validate agent exists
66
73
  const agent = workspaceInfo.agents.find((a) => a.name === agentName);
67
74
  if (!agent) {
68
75
  this.error(`Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`);
69
76
  }
70
77
  const agentStatus = getAgentStatus(workspaceInfo, agentName);
78
+ // JSON output mode
79
+ if (jsonMode) {
80
+ this.log(JSON.stringify({
81
+ success: true,
82
+ agent: {
83
+ name: agentName,
84
+ type: agent.type,
85
+ exists: agentStatus.exists,
86
+ path: `${workspaceInfo.agentsPath}/${agentName}`,
87
+ branch: agentStatus.branch,
88
+ repositories: agentStatus.repositories.map(r => ({
89
+ name: r.name,
90
+ status: r.status,
91
+ commitsAhead: r.commitsAhead,
92
+ })),
93
+ assignedTickets: agentStatus.assignedTickets,
94
+ completedTickets: agentStatus.completedTickets,
95
+ },
96
+ }, null, 2));
97
+ return;
98
+ }
71
99
  this.log(format.title(`🤖 Agent: ${agentName}`));
72
100
  // Basic status
73
101
  const statusIcon = agentStatus.exists ? '🟢' : '🔴';
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
4
  import chalk from 'chalk';
5
5
  import { PMOCommand, pmoBaseFlags, runWatcherForeground } from '../../lib/pmo/index.js';
6
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
6
7
  import { styles } from '../../lib/styles.js';
7
8
  export default class BoardWatch extends PMOCommand {
8
9
  static description = 'Watch board.md for changes and auto-sync to SQLite';
@@ -23,6 +24,11 @@ export default class BoardWatch extends PMOCommand {
23
24
  }
24
25
  async execute() {
25
26
  const { flags } = await this.parse(BoardWatch);
27
+ if (shouldOutputJson(flags)) {
28
+ this.log(JSON.stringify({ type: 'error', error: { code: 'REQUIRES_TTY', message: 'board watch is a long-running interactive command. Use "prlt board" for non-interactive board data.' } }));
29
+ this.exit(1);
30
+ return;
31
+ }
26
32
  // Load PMO config
27
33
  const configPath = path.join(this.pmoPath, 'config.json');
28
34
  if (!fs.existsSync(configPath)) {
@@ -14,6 +14,7 @@ export default class BranchList extends PMOCommand {
14
14
  promptIfMultiple: boolean;
15
15
  };
16
16
  execute(): Promise<void>;
17
+ private listBranchesAcrossRepos;
17
18
  private outputTable;
18
19
  private outputCompact;
19
20
  }
@@ -2,6 +2,7 @@ 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 { getWorkspaceRepoInfo } from '../../lib/repos/index.js';
5
6
  import { isNonTTY } from '../../lib/prompt-json.js';
6
7
  import { visualPadEnd } from '../../lib/string-utils.js';
7
8
  export default class BranchList extends PMOCommand {
@@ -40,12 +41,15 @@ export default class BranchList extends PMOCommand {
40
41
  if (flags.format === 'table' && isNonTTY()) {
41
42
  flags.format = 'json';
42
43
  }
43
- // Check if in git repo
44
- if (!isGitRepo()) {
45
- this.error('Not in a git repository.');
44
+ let branches;
45
+ if (isGitRepo()) {
46
+ // In a git repo - list branches for current repo
47
+ branches = listBranches(undefined, flags.all);
48
+ }
49
+ else {
50
+ // Not in a git repo - list branches across all registered HQ repos
51
+ branches = this.listBranchesAcrossRepos(flags.all);
46
52
  }
47
- // Get branches
48
- let branches = listBranches(undefined, flags.all);
49
53
  // Filter by type
50
54
  if (flags.type) {
51
55
  branches = branches.filter((b) => b.type === flags.type);
@@ -59,31 +63,55 @@ export default class BranchList extends PMOCommand {
59
63
  }
60
64
  return;
61
65
  }
66
+ // Check if we have multi-repo results
67
+ const hasRepoInfo = branches.some((b) => b.repo);
62
68
  // Output based on format
63
69
  switch (flags.format) {
64
70
  case 'json':
65
71
  this.log(JSON.stringify(branches, null, 2));
66
72
  break;
67
73
  case 'compact':
68
- this.outputCompact(branches);
74
+ this.outputCompact(branches, hasRepoInfo);
69
75
  break;
70
76
  case 'table':
71
77
  default:
72
- this.outputTable(branches);
78
+ this.outputTable(branches, hasRepoInfo);
73
79
  break;
74
80
  }
75
81
  }
76
- outputTable(branches) {
82
+ listBranchesAcrossRepos(includeRemote) {
83
+ let repoInfo;
84
+ try {
85
+ repoInfo = getWorkspaceRepoInfo();
86
+ }
87
+ catch {
88
+ this.error('Not in a git repository and no HQ workspace found.');
89
+ }
90
+ const allBranches = [];
91
+ for (const repo of repoInfo.repositories) {
92
+ if (repo.status === 'missing')
93
+ continue;
94
+ const repoBranches = listBranches(repo.fullPath, includeRemote);
95
+ for (const branch of repoBranches) {
96
+ branch.repo = repo.name;
97
+ allBranches.push(branch);
98
+ }
99
+ }
100
+ return allBranches;
101
+ }
102
+ outputTable(branches, hasRepoInfo = false) {
77
103
  this.log('');
78
104
  this.log(styles.header(`🌿 Branches (${branches.length})`));
79
105
  this.log('');
80
106
  // Header
81
- this.log(styles.muted(visualPadEnd('Name', 35) +
107
+ const repoHeader = hasRepoInfo ? visualPadEnd('Repo', 18) : '';
108
+ this.log(styles.muted(repoHeader +
109
+ visualPadEnd('Name', 35) +
82
110
  visualPadEnd('Type', 8) +
83
111
  visualPadEnd('Owner', 12) +
84
112
  visualPadEnd('Description', 25) +
85
113
  'Status'));
86
- this.log('─'.repeat(90));
114
+ this.log('─'.repeat(hasRepoInfo ? 108 : 90));
87
115
  // Rows
88
116
  for (const branch of branches) {
89
117
  const marker = branch.current ? '* ' : ' ';
@@ -91,6 +119,7 @@ export default class BranchList extends PMOCommand {
91
119
  const typeDisplay = branch.type || '-';
92
120
  const ownerDisplay = branch.owner || '-';
93
121
  const descDisplay = branch.description || '-';
122
+ const repoCol = hasRepoInfo ? visualPadEnd((branch.repo || '-').substring(0, 16), 18) : '';
94
123
  let status = 'local';
95
124
  if (branch.tracking) {
96
125
  status = `tracking ${branch.tracking}`;
@@ -99,6 +128,7 @@ export default class BranchList extends PMOCommand {
99
128
  status = 'current';
100
129
  }
101
130
  this.log(marker +
131
+ repoCol +
102
132
  nameStyle(visualPadEnd(branch.name.substring(0, 33), 33)) +
103
133
  visualPadEnd(typeDisplay, 8) +
104
134
  visualPadEnd(ownerDisplay.substring(0, 10), 12) +
@@ -109,14 +139,15 @@ export default class BranchList extends PMOCommand {
109
139
  this.log(styles.muted('Legend: * = current branch'));
110
140
  this.log('');
111
141
  }
112
- outputCompact(branches) {
142
+ outputCompact(branches, hasRepoInfo = false) {
113
143
  this.log('');
114
144
  for (const branch of branches) {
115
145
  const marker = branch.current ? '* ' : ' ';
116
146
  const icon = branch.type ? '🌿' : ' ';
117
147
  const typeInfo = branch.type ? ` (${branch.type})` : '';
148
+ const repoPrefix = hasRepoInfo && branch.repo ? `[${branch.repo}] ` : '';
118
149
  const nameStyle = branch.current ? styles.success : (s) => s;
119
- this.log(`${icon} ${marker}${nameStyle(branch.name)}${styles.muted(typeInfo)}`);
150
+ this.log(`${icon} ${marker}${repoPrefix}${nameStyle(branch.name)}${styles.muted(typeInfo)}`);
120
151
  }
121
152
  this.log('');
122
153
  }
@@ -2,6 +2,7 @@ import { Args } from '@oclif/core';
2
2
  import { execSync } from 'node:child_process';
3
3
  import * as path from 'node:path';
4
4
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
5
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
5
6
  import { styles } from '../../lib/styles.js';
6
7
  import { isGitRepo, isTicketId } from '../../lib/branch/index.js';
7
8
  import { openWorkspaceDatabase } from '../../lib/database/index.js';
@@ -40,7 +41,7 @@ export default class BranchWhere extends PMOCommand {
40
41
  // Combine results, preferring git worktree info
41
42
  const allMatches = this.mergeResults(matches, dbMatches);
42
43
  if (allMatches.length === 0) {
43
- if (flags.json || flags.machine) {
44
+ if (shouldOutputJson(flags)) {
44
45
  this.log(JSON.stringify({ found: false, search, matches: [] }, null, 2));
45
46
  }
46
47
  else {
@@ -48,7 +49,7 @@ export default class BranchWhere extends PMOCommand {
48
49
  }
49
50
  return;
50
51
  }
51
- if (flags.json || flags.machine) {
52
+ if (shouldOutputJson(flags)) {
52
53
  this.log(JSON.stringify({
53
54
  found: true,
54
55
  search,
@@ -3,7 +3,7 @@ export default class CategoryList extends PMOCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
- type: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
6
+ type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
7
  builtin: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
8
  custom: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
9
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -14,5 +14,6 @@ export default class CategoryList extends PMOCommand {
14
14
  promptIfMultiple: boolean;
15
15
  };
16
16
  execute(): Promise<void>;
17
+ private printCategoryGroup;
17
18
  private printCategory;
18
19
  }
@@ -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[];