@proletariat/cli 0.3.10 → 0.3.11

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 (151) hide show
  1. package/bin/dev.js +0 -0
  2. package/dist/commands/action/index.js +1 -1
  3. package/dist/commands/action/run.js +8 -12
  4. package/dist/commands/agent/auth.d.ts +30 -0
  5. package/dist/commands/agent/auth.js +172 -0
  6. package/dist/commands/agent/discover.d.ts +9 -0
  7. package/dist/commands/agent/discover.js +67 -0
  8. package/dist/commands/agent/index.js +47 -12
  9. package/dist/commands/agent/list.d.ts +4 -1
  10. package/dist/commands/agent/list.js +78 -16
  11. package/dist/commands/agent/login.js +35 -31
  12. package/dist/commands/agent/restart.js +2 -0
  13. package/dist/commands/agent/shell.js +78 -19
  14. package/dist/commands/agent/staff/add.js +1 -12
  15. package/dist/commands/agent/staff/remove.js +9 -7
  16. package/dist/commands/agent/status.js +17 -4
  17. package/dist/commands/agent/temp/cleanup.js +7 -3
  18. package/dist/commands/agent/themes/index.js +4 -5
  19. package/dist/commands/agent/themes/list.js +5 -5
  20. package/dist/commands/agent/visit.js +17 -4
  21. package/dist/commands/branch/create.d.ts +4 -0
  22. package/dist/commands/branch/create.js +16 -8
  23. package/dist/commands/branch/index.js +1 -1
  24. package/dist/commands/branch/where.js +1 -0
  25. package/dist/commands/claude.d.ts +38 -0
  26. package/dist/commands/claude.js +899 -0
  27. package/dist/commands/commit.js +1 -1
  28. package/dist/commands/config/index.d.ts +12 -0
  29. package/dist/commands/config/index.js +271 -0
  30. package/dist/commands/docker/clean.js +2 -2
  31. package/dist/commands/docker/index.js +2 -2
  32. package/dist/commands/docker/list.js +3 -8
  33. package/dist/commands/docker/logs.js +2 -2
  34. package/dist/commands/docker/prune.js +1 -1
  35. package/dist/commands/docker/restart.js +2 -2
  36. package/dist/commands/docker/shell.js +2 -2
  37. package/dist/commands/docker/start.js +2 -2
  38. package/dist/commands/docker/status.js +1 -1
  39. package/dist/commands/docker/stop.js +2 -2
  40. package/dist/commands/docker/sync.js +2 -2
  41. package/dist/commands/epic/index.js +1 -1
  42. package/dist/commands/epic/link/index.js +25 -14
  43. package/dist/commands/epic/link/remove.js +2 -0
  44. package/dist/commands/epic/list.js +5 -5
  45. package/dist/commands/epic/progress.js +10 -4
  46. package/dist/commands/epic/spec.js +2 -0
  47. package/dist/commands/epic/ticket.js +3 -0
  48. package/dist/commands/execution/stop.js +1 -0
  49. package/dist/commands/init.js +4 -4
  50. package/dist/commands/project/index.js +1 -1
  51. package/dist/commands/project/spec.js +7 -0
  52. package/dist/commands/repo/add.js +1 -0
  53. package/dist/commands/repo/remove.js +1 -0
  54. package/dist/commands/roadmap/add-project.d.ts +18 -0
  55. package/dist/commands/roadmap/add-project.js +135 -0
  56. package/dist/commands/roadmap/create.d.ts +22 -0
  57. package/dist/commands/roadmap/create.js +156 -0
  58. package/dist/commands/roadmap/delete.d.ts +17 -0
  59. package/dist/commands/roadmap/delete.js +104 -0
  60. package/dist/commands/roadmap/generate.d.ts +22 -0
  61. package/dist/commands/roadmap/generate.js +201 -0
  62. package/dist/commands/roadmap/index.d.ts +13 -0
  63. package/dist/commands/roadmap/index.js +61 -0
  64. package/dist/commands/roadmap/list.d.ts +12 -0
  65. package/dist/commands/roadmap/list.js +42 -0
  66. package/dist/commands/roadmap/remove-project.d.ts +18 -0
  67. package/dist/commands/roadmap/remove-project.js +147 -0
  68. package/dist/commands/roadmap/reorder.d.ts +17 -0
  69. package/dist/commands/roadmap/reorder.js +157 -0
  70. package/dist/commands/roadmap/update.d.ts +19 -0
  71. package/dist/commands/roadmap/update.js +136 -0
  72. package/dist/commands/roadmap/view.d.ts +16 -0
  73. package/dist/commands/roadmap/view.js +103 -0
  74. package/dist/commands/spec/index.js +1 -1
  75. package/dist/commands/spec/link/index.js +24 -13
  76. package/dist/commands/spec/link/remove.js +2 -0
  77. package/dist/commands/status/index.js +1 -1
  78. package/dist/commands/status/list.js +0 -8
  79. package/dist/commands/template/delete.js +2 -0
  80. package/dist/commands/terminal/title.d.ts +12 -0
  81. package/dist/commands/terminal/title.js +48 -0
  82. package/dist/commands/ticket/complete.js +2 -0
  83. package/dist/commands/ticket/create.js +4 -2
  84. package/dist/commands/ticket/delete.js +2 -0
  85. package/dist/commands/ticket/edit.js +8 -2
  86. package/dist/commands/ticket/link/index.js +17 -3
  87. package/dist/commands/ticket/link/remove.js +2 -0
  88. package/dist/commands/ticket/list.js +1 -2
  89. package/dist/commands/ticket/move.js +2 -0
  90. package/dist/commands/ticket/project.js +3 -1
  91. package/dist/commands/ticket/reassign.js +2 -0
  92. package/dist/commands/ticket/spec.js +4 -2
  93. package/dist/commands/ticket/template/apply.js +4 -3
  94. package/dist/commands/ticket/template/create.js +2 -0
  95. package/dist/commands/ticket/template/index.js +1 -1
  96. package/dist/commands/ticket/update.js +2 -0
  97. package/dist/commands/work/index.js +1 -1
  98. package/dist/commands/work/revise.js +7 -1
  99. package/dist/commands/work/spawn.d.ts +2 -1
  100. package/dist/commands/work/spawn.js +131 -36
  101. package/dist/commands/work/start.d.ts +2 -1
  102. package/dist/commands/work/start.js +349 -69
  103. package/dist/commands/work/watch.js +10 -2
  104. package/dist/commands/workflow/create.js +3 -3
  105. package/dist/commands/workflow/switch.js +2 -1
  106. package/dist/commands/workspace/remove.js +0 -8
  107. package/dist/commands/workspace/use.js +1 -9
  108. package/dist/lib/agents/commands.js +18 -13
  109. package/dist/lib/database/index.d.ts +19 -12
  110. package/dist/lib/database/index.js +158 -42
  111. package/dist/lib/docker/resolve.js +1 -1
  112. package/dist/lib/execution/config.d.ts +6 -0
  113. package/dist/lib/execution/config.js +15 -2
  114. package/dist/lib/execution/devcontainer.d.ts +2 -0
  115. package/dist/lib/execution/devcontainer.js +41 -9
  116. package/dist/lib/execution/runners.d.ts +85 -3
  117. package/dist/lib/execution/runners.js +925 -228
  118. package/dist/lib/execution/spawner.d.ts +2 -2
  119. package/dist/lib/execution/spawner.js +4 -3
  120. package/dist/lib/execution/storage.d.ts +2 -1
  121. package/dist/lib/execution/storage.js +9 -13
  122. package/dist/lib/execution/types.d.ts +10 -1
  123. package/dist/lib/execution/types.js +3 -1
  124. package/dist/lib/init/index.js +1 -0
  125. package/dist/lib/machine-config.js +1 -1
  126. package/dist/lib/pmo/base-command.js +5 -9
  127. package/dist/lib/pmo/index.js +2 -0
  128. package/dist/lib/pmo/schema.d.ts +6 -0
  129. package/dist/lib/pmo/schema.js +36 -0
  130. package/dist/lib/pmo/storage/base.js +3 -3
  131. package/dist/lib/pmo/storage/index.d.ts +16 -1
  132. package/dist/lib/pmo/storage/index.js +45 -0
  133. package/dist/lib/pmo/storage/roadmaps.d.ts +62 -0
  134. package/dist/lib/pmo/storage/roadmaps.js +301 -0
  135. package/dist/lib/pmo/storage/specs.js +2 -0
  136. package/dist/lib/pmo/storage/types.d.ts +14 -0
  137. package/dist/lib/pmo/sync-manager.d.ts +1 -1
  138. package/dist/lib/pmo/sync-manager.js +1 -1
  139. package/dist/lib/pmo/types.d.ts +41 -0
  140. package/dist/lib/pmo/utils.d.ts +2 -0
  141. package/dist/lib/pmo/utils.js +22 -1
  142. package/dist/lib/repos/index.js +7 -1
  143. package/dist/lib/terminal.d.ts +31 -0
  144. package/dist/lib/terminal.js +48 -0
  145. package/dist/lib/themes.d.ts +21 -3
  146. package/dist/lib/themes.js +80 -23
  147. package/dist/lib/workspace-config.d.ts +80 -0
  148. package/dist/lib/workspace-config.js +100 -0
  149. package/oclif.manifest.json +5472 -4632
  150. package/package.json +10 -6
  151. package/LICENSE +0 -21
package/bin/dev.js CHANGED
File without changes
@@ -1,7 +1,7 @@
1
1
  import { Flags } from '@oclif/core';
2
2
  import inquirer from 'inquirer';
3
3
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
4
- import { shouldOutputJson, } from '../../lib/prompt-json.js';
4
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
5
5
  export default class Action extends PMOCommand {
6
6
  static description = 'Interactive menu for work action operations';
7
7
  static aliases = ['actions'];
@@ -83,16 +83,15 @@ export default class ActionRun extends PMOCommand {
83
83
  }
84
84
  }
85
85
  else if (ticketIds.length > 0) {
86
- // Get specific tickets
87
- for (const id of ticketIds) {
86
+ // Get specific tickets in parallel
87
+ const results = await Promise.all(ticketIds.map(async (id) => {
88
88
  const ticket = await this.storage.getTicket(id);
89
- if (ticket) {
90
- tickets.push(ticket);
91
- }
92
- else {
89
+ if (!ticket) {
93
90
  this.warn(`Ticket not found: ${id}`);
94
91
  }
95
- }
92
+ return ticket;
93
+ }));
94
+ tickets = results.filter((t) => t !== null);
96
95
  }
97
96
  else {
98
97
  // Interactive: show list of tickets to select
@@ -120,11 +119,8 @@ export default class ActionRun extends PMOCommand {
120
119
  })),
121
120
  validate: (input) => input.length > 0 || 'Select at least one ticket',
122
121
  }]);
123
- for (const id of selectedTickets) {
124
- const ticket = await this.storage.getTicket(id);
125
- if (ticket)
126
- tickets.push(ticket);
127
- }
122
+ const selectedResults = await Promise.all(selectedTickets.map((id) => this.storage.getTicket(id)));
123
+ tickets = selectedResults.filter((t) => t !== null);
128
124
  }
129
125
  if (tickets.length === 0) {
130
126
  this.error('No tickets matched the criteria.');
@@ -0,0 +1,30 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Auth extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ check: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ /**
10
+ * Check if the claude-credentials volume exists
11
+ */
12
+ private volumeExists;
13
+ /**
14
+ * Create the claude-credentials volume if it doesn't exist
15
+ */
16
+ private createVolume;
17
+ /**
18
+ * Check if valid credentials exist in the volume
19
+ */
20
+ private credentialsExist;
21
+ /**
22
+ * Get credential info for display
23
+ */
24
+ private getCredentialInfo;
25
+ /**
26
+ * Run the interactive login flow in a temporary container
27
+ */
28
+ private runLoginFlow;
29
+ run(): Promise<void>;
30
+ }
@@ -0,0 +1,172 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import { execSync, spawnSync } from 'node:child_process';
3
+ import { colors } from '../../lib/colors.js';
4
+ import { isDockerRunning } from '../../lib/execution/runners.js';
5
+ const CLAUDE_CREDENTIALS_VOLUME = 'claude-credentials';
6
+ export default class Auth extends Command {
7
+ static description = 'Set up Claude Code authentication for Docker containers (one-time setup)';
8
+ static examples = [
9
+ '<%= config.bin %> <%= command.id %>',
10
+ '<%= config.bin %> <%= command.id %> --check',
11
+ '<%= config.bin %> <%= command.id %> --force',
12
+ ];
13
+ static flags = {
14
+ check: Flags.boolean({
15
+ description: 'Only check if credentials exist (do not prompt for login)',
16
+ default: false,
17
+ }),
18
+ force: Flags.boolean({
19
+ description: 'Force re-authentication even if credentials exist',
20
+ default: false,
21
+ }),
22
+ };
23
+ /**
24
+ * Check if the claude-credentials volume exists
25
+ */
26
+ volumeExists() {
27
+ try {
28
+ execSync(`docker volume inspect ${CLAUDE_CREDENTIALS_VOLUME}`, { stdio: 'pipe' });
29
+ return true;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ /**
36
+ * Create the claude-credentials volume if it doesn't exist
37
+ */
38
+ createVolume() {
39
+ try {
40
+ execSync(`docker volume create ${CLAUDE_CREDENTIALS_VOLUME}`, { stdio: 'pipe' });
41
+ }
42
+ catch (error) {
43
+ this.error(`Failed to create Docker volume: ${error}`);
44
+ }
45
+ }
46
+ /**
47
+ * Check if valid credentials exist in the volume
48
+ */
49
+ credentialsExist() {
50
+ try {
51
+ const result = execSync(`docker run --rm -v ${CLAUDE_CREDENTIALS_VOLUME}:/data alpine cat /data/.credentials.json 2>/dev/null`, { stdio: 'pipe', encoding: 'utf-8' });
52
+ // Parse and validate the credentials
53
+ const creds = JSON.parse(result);
54
+ if (creds.claudeAiOauth?.accessToken && creds.claudeAiOauth?.expiresAt) {
55
+ // Check if expired
56
+ const expiresAt = creds.claudeAiOauth.expiresAt;
57
+ if (expiresAt > Date.now()) {
58
+ return true;
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+ catch {
64
+ return false;
65
+ }
66
+ }
67
+ /**
68
+ * Get credential info for display
69
+ */
70
+ getCredentialInfo() {
71
+ try {
72
+ const result = execSync(`docker run --rm -v ${CLAUDE_CREDENTIALS_VOLUME}:/data alpine cat /data/.credentials.json 2>/dev/null`, { stdio: 'pipe', encoding: 'utf-8' });
73
+ const creds = JSON.parse(result);
74
+ if (creds.claudeAiOauth?.expiresAt) {
75
+ return {
76
+ expiresAt: new Date(creds.claudeAiOauth.expiresAt),
77
+ subscriptionType: creds.claudeAiOauth.subscriptionType,
78
+ };
79
+ }
80
+ return null;
81
+ }
82
+ catch {
83
+ return null;
84
+ }
85
+ }
86
+ /**
87
+ * Run the interactive login flow in a temporary container
88
+ */
89
+ runLoginFlow() {
90
+ this.log(colors.primary('🔐 Starting Claude Code authentication...'));
91
+ this.log('');
92
+ this.log(colors.text('A temporary container will start with Claude Code.'));
93
+ this.log(colors.text('When prompted, type: /login'));
94
+ this.log(colors.text('Then complete the browser authentication.'));
95
+ this.log('');
96
+ this.log(colors.textSecondary('Press Ctrl+C to cancel.'));
97
+ this.log('');
98
+ try {
99
+ // Run interactive container with the volume mounted
100
+ const result = spawnSync('docker', [
101
+ 'run',
102
+ '-it',
103
+ '--rm',
104
+ '-v', `${CLAUDE_CREDENTIALS_VOLUME}:/home/node/.claude`,
105
+ 'node:20',
106
+ 'bash', '-c',
107
+ // Install as root, then run claude as node user (so credentials have correct ownership)
108
+ 'npm install -g @anthropic-ai/claude-code@latest --silent 2>/dev/null && chown -R node:node /home/node/.claude && echo "" && echo "Type: /login" && echo "" && su -s /bin/bash -c "HOME=/home/node CLAUDE_CONFIG_DIR=/home/node/.claude claude" node'
109
+ ], { stdio: 'inherit' });
110
+ return result.status === 0;
111
+ }
112
+ catch (error) {
113
+ this.log(colors.error(`Login flow failed: ${error}`));
114
+ return false;
115
+ }
116
+ }
117
+ async run() {
118
+ const { flags } = await this.parse(Auth);
119
+ // Check Docker is running
120
+ if (!isDockerRunning()) {
121
+ this.error('Docker is not running. Please start Docker Desktop and try again.');
122
+ }
123
+ // Ensure volume exists
124
+ if (!this.volumeExists()) {
125
+ this.log(colors.textSecondary(`Creating Docker volume: ${CLAUDE_CREDENTIALS_VOLUME}`));
126
+ this.createVolume();
127
+ }
128
+ // Check for existing credentials
129
+ const hasCredentials = this.credentialsExist();
130
+ if (flags.check) {
131
+ // Just report status
132
+ if (hasCredentials) {
133
+ const info = this.getCredentialInfo();
134
+ this.log(colors.success('✓ Claude Code credentials are configured'));
135
+ if (info) {
136
+ this.log(colors.textSecondary(` Subscription: ${info.subscriptionType || 'unknown'}`));
137
+ this.log(colors.textSecondary(` Expires: ${info.expiresAt.toLocaleDateString()}`));
138
+ }
139
+ }
140
+ else {
141
+ this.log(colors.warning('✗ No Claude Code credentials found'));
142
+ this.log(colors.textSecondary(' Run "prlt agent auth" to authenticate'));
143
+ this.exit(1);
144
+ }
145
+ return;
146
+ }
147
+ if (hasCredentials && !flags.force) {
148
+ const info = this.getCredentialInfo();
149
+ this.log(colors.success('✓ Claude Code credentials already configured'));
150
+ if (info) {
151
+ this.log(colors.textSecondary(` Subscription: ${info.subscriptionType || 'unknown'}`));
152
+ this.log(colors.textSecondary(` Expires: ${info.expiresAt.toLocaleDateString()}`));
153
+ }
154
+ this.log('');
155
+ this.log(colors.text('Use --force to re-authenticate.'));
156
+ return;
157
+ }
158
+ // Run the login flow
159
+ const success = this.runLoginFlow();
160
+ if (success && this.credentialsExist()) {
161
+ this.log('');
162
+ this.log(colors.success('✓ Authentication successful!'));
163
+ this.log(colors.textSecondary(' Credentials saved to Docker volume: ' + CLAUDE_CREDENTIALS_VOLUME));
164
+ this.log(colors.textSecondary(' All agent containers will share these credentials.'));
165
+ }
166
+ else {
167
+ this.log('');
168
+ this.log(colors.warning('Authentication may not have completed.'));
169
+ this.log(colors.textSecondary('Run "prlt agent auth --check" to verify.'));
170
+ }
171
+ }
172
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Discover extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ 'dry-run': import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ };
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,67 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { getWorkspaceInfo } from '../../lib/agents/commands.js';
4
+ import { discoverAgentsOnDisk } from '../../lib/database/index.js';
5
+ export default class Discover extends Command {
6
+ static description = 'Discover agents on disk that are not registered in the database';
7
+ static examples = [
8
+ '<%= config.bin %> <%= command.id %>',
9
+ '<%= config.bin %> <%= command.id %> --dry-run',
10
+ ];
11
+ static flags = {
12
+ 'dry-run': Flags.boolean({
13
+ description: 'Show what would be discovered without making changes',
14
+ default: false,
15
+ }),
16
+ };
17
+ async run() {
18
+ const { flags } = await this.parse(Discover);
19
+ try {
20
+ const workspaceInfo = getWorkspaceInfo();
21
+ this.log(chalk.bold('\n🔍 Agent Discovery\n'));
22
+ if (flags['dry-run']) {
23
+ this.log(chalk.yellow('Dry run mode - no changes will be made\n'));
24
+ }
25
+ const result = discoverAgentsOnDisk(workspaceInfo.path);
26
+ // Report discovered agents
27
+ if (result.discovered.length > 0) {
28
+ this.log(chalk.green.bold(`✅ Discovered ${result.discovered.length} new agent(s):\n`));
29
+ for (const agent of result.discovered) {
30
+ const typeLabel = agent.type === 'persistent' ? chalk.cyan('[staff]') : chalk.yellow('[temp]');
31
+ this.log(` ${chalk.bold(agent.name)} ${typeLabel}`);
32
+ this.log(chalk.dim(` Path: ${agent.path}`));
33
+ }
34
+ this.log('');
35
+ }
36
+ else {
37
+ this.log(chalk.dim('No new agents discovered on disk.\n'));
38
+ }
39
+ // Report cleaned agents
40
+ if (result.cleaned.length > 0) {
41
+ this.log(chalk.yellow.bold(`🧹 Cleaned up ${result.cleaned.length} missing agent(s):\n`));
42
+ for (const name of result.cleaned) {
43
+ this.log(` ${chalk.dim(name)} - directory no longer exists`);
44
+ }
45
+ this.log('');
46
+ }
47
+ // Summary
48
+ if (result.discovered.length === 0 && result.cleaned.length === 0) {
49
+ this.log(chalk.green('✓ Database is in sync with disk.\n'));
50
+ }
51
+ else {
52
+ const total = result.discovered.length + result.cleaned.length;
53
+ this.log(chalk.bold(`Summary: ${total} change(s) made`));
54
+ if (result.discovered.length > 0) {
55
+ this.log(` Discovered: ${result.discovered.length} agent(s)`);
56
+ }
57
+ if (result.cleaned.length > 0) {
58
+ this.log(` Cleaned: ${result.cleaned.length} agent(s)`);
59
+ }
60
+ this.log('');
61
+ }
62
+ }
63
+ catch (error) {
64
+ this.error(error instanceof Error ? error.message : String(error));
65
+ }
66
+ }
67
+ }
@@ -1,7 +1,8 @@
1
1
  import { Flags } from '@oclif/core';
2
+ import inquirer from 'inquirer';
2
3
  import { colors } from '../../lib/colors.js';
3
4
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
4
- import { shouldOutputJson, } from '../../lib/prompt-json.js';
5
+ import { shouldOutputJson, outputPromptAsJson, createMetadata, } from '../../lib/prompt-json.js';
5
6
  export default class Agent extends PMOCommand {
6
7
  static description = 'Manage agents in the workspace';
7
8
  static examples = [
@@ -30,8 +31,7 @@ export default class Agent extends PMOCommand {
30
31
  const { flags } = await this.parse(Agent);
31
32
  // Check if JSON output mode is active
32
33
  const jsonMode = shouldOutputJson(flags);
33
- // Define choices once, use for both JSON and interactive modes
34
- // Each choice includes the full command for AI agents to execute
34
+ // Define choices for JSON mode (flat list with commands)
35
35
  const menuChoices = [
36
36
  { id: 'list', name: 'List all agents', command: 'prlt agent list --format json' },
37
37
  { id: 'status', name: 'Show status', command: 'prlt agent status --json' },
@@ -42,6 +42,7 @@ export default class Agent extends PMOCommand {
42
42
  { id: 'shell', name: 'Open shell', command: 'prlt agent shell --json' },
43
43
  { id: 'restart', name: 'Restart', command: 'prlt agent restart --json' },
44
44
  { id: 'rebuild', name: 'Rebuild', command: 'prlt agent rebuild --json' },
45
+ { id: 'discover', name: 'Discover agents on disk', command: 'prlt agent discover' },
45
46
  { id: 'cancel', name: 'Cancel', command: '' },
46
47
  ];
47
48
  const message = 'What would you like to do?';
@@ -50,15 +51,43 @@ export default class Agent extends PMOCommand {
50
51
  this.log(colors.textMuted('Note: Agent pre-registration is no longer required!'));
51
52
  this.log(colors.textMuted('Use "prlt work spawn" to create ephemeral agents automatically.'));
52
53
  this.log('');
53
- const action = await this.selectFromList({
54
- message,
55
- items: menuChoices,
56
- getName: (c) => c.name,
57
- getValue: (c) => c.id,
58
- getCommand: (c) => c.command,
59
- jsonMode: jsonMode ? { flags, commandName: 'agent' } : null,
60
- });
61
- if (action === 'cancel' || !action) {
54
+ // In JSON mode, output flat list with commands
55
+ if (jsonMode) {
56
+ outputPromptAsJson({
57
+ type: 'list',
58
+ name: 'action',
59
+ message,
60
+ choices: menuChoices.map(c => ({ name: c.name, value: c.id, command: c.command })),
61
+ }, createMetadata('agent', flags));
62
+ return;
63
+ }
64
+ // Interactive mode with separators for logical groupings
65
+ const { action } = await inquirer.prompt([{
66
+ type: 'list',
67
+ name: 'action',
68
+ message,
69
+ choices: [
70
+ // View/Info group
71
+ { name: '📋 List all agents', value: 'list' },
72
+ { name: '📊 Show status', value: 'status' },
73
+ { name: '📂 Visit directory', value: 'visit' },
74
+ new inquirer.Separator(),
75
+ // Management group
76
+ { name: '👔 Manage staff agents', value: 'staff' },
77
+ { name: '⏱️ Manage temp agents', value: 'temp' },
78
+ { name: '🎨 Manage themes', value: 'themes' },
79
+ new inquirer.Separator(),
80
+ // Operations group
81
+ { name: '🐚 Open shell', value: 'shell' },
82
+ { name: '🔄 Restart', value: 'restart' },
83
+ { name: '🔨 Rebuild', value: 'rebuild' },
84
+ { name: '🔍 Discover agents on disk', value: 'discover' },
85
+ new inquirer.Separator(),
86
+ // Cancel
87
+ { name: '❌ Cancel', value: 'cancel' },
88
+ ],
89
+ }]);
90
+ if (action === 'cancel') {
62
91
  this.log(colors.textMuted('Operation cancelled.'));
63
92
  return;
64
93
  }
@@ -120,6 +149,12 @@ export default class Agent extends PMOCommand {
120
149
  await cmd.run();
121
150
  break;
122
151
  }
152
+ case 'discover': {
153
+ const { default: DiscoverCommand } = await import('./discover.js');
154
+ const cmd = new DiscoverCommand([], this.config);
155
+ await cmd.run();
156
+ break;
157
+ }
123
158
  default:
124
159
  this.error(`Unknown action: ${action}`);
125
160
  }
@@ -2,6 +2,9 @@ import { Command } from '@oclif/core';
2
2
  export default class List extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
- static flags: {};
5
+ static flags: {
6
+ type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ };
6
9
  run(): Promise<void>;
7
10
  }
@@ -1,32 +1,85 @@
1
- import { Command } from '@oclif/core';
1
+ import { Command, Flags } from '@oclif/core';
2
2
  import chalk from 'chalk';
3
+ import inquirer from 'inquirer';
3
4
  import * as path from 'node:path';
4
5
  import * as fs from 'node:fs';
5
6
  import { getWorkspaceInfo, getAllAgentsStatus, getAgentTmuxSessions } from '../../lib/agents/commands.js';
7
+ import { shouldOutputJson, outputPromptAsJson, createMetadata, } from '../../lib/prompt-json.js';
6
8
  export default class List extends Command {
7
9
  static description = 'List all agents and their current status';
8
10
  static examples = [
9
11
  '<%= config.bin %> <%= command.id %>',
12
+ '<%= config.bin %> <%= command.id %> --type staff',
13
+ '<%= config.bin %> <%= command.id %> --type temp',
10
14
  ];
11
- static flags = {};
15
+ static flags = {
16
+ type: Flags.string({
17
+ char: 't',
18
+ description: 'Filter by agent type',
19
+ options: ['staff', 'temp', 'all'],
20
+ }),
21
+ json: Flags.boolean({
22
+ description: 'Output prompt configuration as JSON (for AI agents/scripts)',
23
+ default: false,
24
+ }),
25
+ };
12
26
  async run() {
13
27
  try {
28
+ const { flags } = await this.parse(List);
29
+ const jsonMode = shouldOutputJson(flags);
14
30
  // Get workspace information
15
31
  const workspaceInfo = getWorkspaceInfo();
16
32
  // Filter to active agents only
17
33
  const activeAgents = workspaceInfo.agents.filter(a => a.status === 'active');
18
- if (activeAgents.length === 0) {
19
- this.log(chalk.yellow('No active agents found.'));
34
+ // Determine type filter - prompt if not provided
35
+ let typeFilter = flags.type;
36
+ if (!typeFilter) {
37
+ // In JSON mode, output type selection prompt
38
+ if (jsonMode) {
39
+ outputPromptAsJson({
40
+ type: 'list',
41
+ name: 'type',
42
+ message: 'Which agents do you want to list?',
43
+ choices: [
44
+ { name: 'All agents', value: 'all', command: 'prlt agent list --type all --json' },
45
+ { name: 'Staff agents only', value: 'staff', command: 'prlt agent list --type staff --json' },
46
+ { name: 'Temp agents only', value: 'temp', command: 'prlt agent list --type temp --json' },
47
+ ],
48
+ }, createMetadata('agent list', flags));
49
+ return;
50
+ }
51
+ // Interactive mode - prompt for type
52
+ const { selectedType } = await inquirer.prompt([{
53
+ type: 'list',
54
+ name: 'selectedType',
55
+ message: 'Which agents do you want to list?',
56
+ choices: [
57
+ { name: '📋 All agents', value: 'all' },
58
+ { name: '👔 Staff agents only', value: 'staff' },
59
+ { name: '⏱️ Temp agents only', value: 'temp' },
60
+ ],
61
+ }]);
62
+ typeFilter = selectedType;
63
+ }
64
+ // Filter agents based on type selection
65
+ const staffAgents = activeAgents.filter(a => a.type === 'persistent');
66
+ const tempAgents = activeAgents.filter(a => a.type === 'ephemeral');
67
+ const showStaff = typeFilter === 'all' || typeFilter === 'staff';
68
+ const showTemp = typeFilter === 'all' || typeFilter === 'temp';
69
+ const filteredAgents = [
70
+ ...(showStaff ? staffAgents : []),
71
+ ...(showTemp ? tempAgents : []),
72
+ ];
73
+ if (filteredAgents.length === 0) {
74
+ const typeLabel = typeFilter === 'all' ? '' : ` ${typeFilter}`;
75
+ this.log(chalk.yellow(`No active${typeLabel} agents found.`));
20
76
  this.log(chalk.dim('Use "prlt agent staff add" or "prlt work spawn" to create agents.'));
21
77
  return;
22
78
  }
23
79
  // Get status for all active agents
24
80
  const agentsStatus = getAllAgentsStatus(workspaceInfo);
25
- // Separate by type
26
- const staffAgents = activeAgents.filter(a => a.type === 'persistent');
27
- const tempAgents = activeAgents.filter(a => a.type === 'ephemeral');
28
81
  // Staff agents section
29
- if (staffAgents.length > 0) {
82
+ if (showStaff && staffAgents.length > 0) {
30
83
  this.log(chalk.bold.cyan('\n Staff Agents:\n'));
31
84
  const staffStatus = agentsStatus.filter(a => staffAgents.some(s => s.name === a.name));
32
85
  for (const agentStatus of staffStatus) {
@@ -75,7 +128,7 @@ export default class List extends Command {
75
128
  }
76
129
  }
77
130
  // Temp agents section
78
- if (tempAgents.length > 0) {
131
+ if (showTemp && tempAgents.length > 0) {
79
132
  this.log(chalk.bold.yellow('\n Temporary Agents:\n'));
80
133
  const tempStatus = agentsStatus.filter(a => tempAgents.some(s => s.name === a.name));
81
134
  for (const agentStatus of tempStatus) {
@@ -106,17 +159,26 @@ export default class List extends Command {
106
159
  const sessions = getAgentTmuxSessions(a.name);
107
160
  return sessions.length > 0;
108
161
  }).length;
109
- const totalAssignedTickets = agentsStatus.reduce((sum, a) => sum + a.assignedTickets.length, 0);
162
+ // Calculate tickets for filtered agents only
163
+ const filteredAgentNames = new Set(filteredAgents.map(a => a.name));
164
+ const filteredStatus = agentsStatus.filter(a => filteredAgentNames.has(a.name));
165
+ const totalAssignedTickets = filteredStatus.reduce((sum, a) => sum + a.assignedTickets.length, 0);
110
166
  this.log(chalk.bold(`Summary:`));
111
- this.log(` Staff agents: ${staffAgents.length} (${activeStaffCount} active)`);
112
- this.log(` Temp agents: ${tempAgents.length} (${activeTempCount} active${runningTempCount > 0 ? `, ${runningTempCount} running` : ''})`);
167
+ if (showStaff) {
168
+ this.log(` Staff agents: ${staffAgents.length} (${activeStaffCount} active)`);
169
+ }
170
+ if (showTemp) {
171
+ this.log(` Temp agents: ${tempAgents.length} (${activeTempCount} active${runningTempCount > 0 ? `, ${runningTempCount} running` : ''})`);
172
+ }
113
173
  if (workspaceInfo.hasPMO) {
114
174
  this.log(` Tickets assigned: ${totalAssignedTickets}`);
115
175
  }
116
- // Show cleaned agents count if any
117
- const cleanedAgents = workspaceInfo.agents.filter(a => a.status === 'cleaned');
118
- if (cleanedAgents.length > 0) {
119
- this.log(chalk.dim(` Cleaned (historical): ${cleanedAgents.length}`));
176
+ // Show cleaned agents count if any (only when showing all)
177
+ if (typeFilter === 'all') {
178
+ const cleanedAgents = workspaceInfo.agents.filter(a => a.status === 'cleaned');
179
+ if (cleanedAgents.length > 0) {
180
+ this.log(chalk.dim(` Cleaned (historical): ${cleanedAgents.length}`));
181
+ }
120
182
  }
121
183
  }
122
184
  catch (error) {