@proletariat/cli 0.3.9 → 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.
- package/README.md +25 -0
- package/bin/dev.js +0 -0
- package/dist/commands/action/index.js +1 -1
- package/dist/commands/action/run.js +8 -12
- package/dist/commands/agent/auth.d.ts +30 -0
- package/dist/commands/agent/auth.js +172 -0
- package/dist/commands/agent/discover.d.ts +9 -0
- package/dist/commands/agent/discover.js +67 -0
- package/dist/commands/agent/index.js +47 -12
- package/dist/commands/agent/list.d.ts +4 -1
- package/dist/commands/agent/list.js +78 -16
- package/dist/commands/agent/login.js +35 -31
- package/dist/commands/agent/restart.js +2 -0
- package/dist/commands/agent/shell.js +78 -19
- package/dist/commands/agent/staff/add.js +1 -12
- package/dist/commands/agent/staff/remove.js +9 -7
- package/dist/commands/agent/status.js +17 -4
- package/dist/commands/agent/temp/cleanup.js +7 -3
- package/dist/commands/agent/themes/index.js +4 -5
- package/dist/commands/agent/themes/list.js +5 -5
- package/dist/commands/agent/visit.js +17 -4
- package/dist/commands/branch/create.d.ts +4 -0
- package/dist/commands/branch/create.js +16 -8
- package/dist/commands/branch/index.js +1 -1
- package/dist/commands/branch/where.js +1 -0
- package/dist/commands/claude.d.ts +38 -0
- package/dist/commands/claude.js +899 -0
- package/dist/commands/commit.js +1 -1
- package/dist/commands/config/index.d.ts +12 -0
- package/dist/commands/config/index.js +271 -0
- package/dist/commands/docker/clean.js +2 -2
- package/dist/commands/docker/index.js +2 -2
- package/dist/commands/docker/list.js +3 -8
- package/dist/commands/docker/logs.js +2 -2
- package/dist/commands/docker/prune.js +1 -1
- package/dist/commands/docker/restart.js +2 -2
- package/dist/commands/docker/shell.js +2 -2
- package/dist/commands/docker/start.js +2 -2
- package/dist/commands/docker/status.js +1 -1
- package/dist/commands/docker/stop.js +2 -2
- package/dist/commands/docker/sync.js +2 -2
- package/dist/commands/epic/index.js +1 -1
- package/dist/commands/epic/link/index.js +25 -14
- package/dist/commands/epic/link/remove.js +2 -0
- package/dist/commands/epic/list.js +5 -5
- package/dist/commands/epic/progress.js +10 -4
- package/dist/commands/epic/spec.js +2 -0
- package/dist/commands/epic/ticket.js +3 -0
- package/dist/commands/execution/stop.js +1 -0
- package/dist/commands/init.js +4 -4
- package/dist/commands/project/index.js +1 -1
- package/dist/commands/project/spec.js +7 -0
- package/dist/commands/repo/add.js +1 -0
- package/dist/commands/repo/remove.js +1 -0
- package/dist/commands/roadmap/add-project.d.ts +18 -0
- package/dist/commands/roadmap/add-project.js +135 -0
- package/dist/commands/roadmap/create.d.ts +22 -0
- package/dist/commands/roadmap/create.js +156 -0
- package/dist/commands/roadmap/delete.d.ts +17 -0
- package/dist/commands/roadmap/delete.js +104 -0
- package/dist/commands/roadmap/generate.d.ts +22 -0
- package/dist/commands/roadmap/generate.js +201 -0
- package/dist/commands/roadmap/index.d.ts +13 -0
- package/dist/commands/roadmap/index.js +61 -0
- package/dist/commands/roadmap/list.d.ts +12 -0
- package/dist/commands/roadmap/list.js +42 -0
- package/dist/commands/roadmap/remove-project.d.ts +18 -0
- package/dist/commands/roadmap/remove-project.js +147 -0
- package/dist/commands/roadmap/reorder.d.ts +17 -0
- package/dist/commands/roadmap/reorder.js +157 -0
- package/dist/commands/roadmap/update.d.ts +19 -0
- package/dist/commands/roadmap/update.js +136 -0
- package/dist/commands/roadmap/view.d.ts +16 -0
- package/dist/commands/roadmap/view.js +103 -0
- package/dist/commands/spec/index.js +1 -1
- package/dist/commands/spec/link/index.js +24 -13
- package/dist/commands/spec/link/remove.js +2 -0
- package/dist/commands/status/index.js +1 -1
- package/dist/commands/status/list.js +0 -8
- package/dist/commands/template/delete.js +2 -0
- package/dist/commands/terminal/title.d.ts +12 -0
- package/dist/commands/terminal/title.js +48 -0
- package/dist/commands/ticket/complete.js +2 -0
- package/dist/commands/ticket/create.js +4 -2
- package/dist/commands/ticket/delete.js +2 -0
- package/dist/commands/ticket/edit.js +8 -2
- package/dist/commands/ticket/link/index.js +17 -3
- package/dist/commands/ticket/link/remove.js +2 -0
- package/dist/commands/ticket/list.js +1 -2
- package/dist/commands/ticket/move.js +2 -0
- package/dist/commands/ticket/project.js +3 -1
- package/dist/commands/ticket/reassign.js +2 -0
- package/dist/commands/ticket/spec.js +4 -2
- package/dist/commands/ticket/template/apply.js +4 -3
- package/dist/commands/ticket/template/create.js +2 -0
- package/dist/commands/ticket/template/index.js +1 -1
- package/dist/commands/ticket/update.js +2 -0
- package/dist/commands/work/index.js +1 -1
- package/dist/commands/work/revise.js +7 -1
- package/dist/commands/work/spawn.d.ts +2 -1
- package/dist/commands/work/spawn.js +131 -36
- package/dist/commands/work/start.d.ts +2 -1
- package/dist/commands/work/start.js +349 -69
- package/dist/commands/work/watch.js +10 -2
- package/dist/commands/workflow/create.js +3 -3
- package/dist/commands/workflow/switch.js +2 -1
- package/dist/commands/workspace/remove.js +0 -8
- package/dist/commands/workspace/use.js +1 -9
- package/dist/lib/agents/commands.js +18 -13
- package/dist/lib/database/index.d.ts +19 -12
- package/dist/lib/database/index.js +158 -42
- package/dist/lib/docker/resolve.js +1 -1
- package/dist/lib/execution/config.d.ts +6 -0
- package/dist/lib/execution/config.js +15 -2
- package/dist/lib/execution/devcontainer.d.ts +2 -0
- package/dist/lib/execution/devcontainer.js +41 -9
- package/dist/lib/execution/runners.d.ts +85 -3
- package/dist/lib/execution/runners.js +925 -228
- package/dist/lib/execution/spawner.d.ts +2 -2
- package/dist/lib/execution/spawner.js +4 -3
- package/dist/lib/execution/storage.d.ts +2 -1
- package/dist/lib/execution/storage.js +9 -13
- package/dist/lib/execution/types.d.ts +10 -1
- package/dist/lib/execution/types.js +3 -1
- package/dist/lib/init/index.js +1 -0
- package/dist/lib/machine-config.js +1 -1
- package/dist/lib/pmo/base-command.js +5 -9
- package/dist/lib/pmo/index.js +2 -0
- package/dist/lib/pmo/schema.d.ts +6 -0
- package/dist/lib/pmo/schema.js +36 -0
- package/dist/lib/pmo/storage/base.js +3 -3
- package/dist/lib/pmo/storage/index.d.ts +16 -1
- package/dist/lib/pmo/storage/index.js +45 -0
- package/dist/lib/pmo/storage/roadmaps.d.ts +62 -0
- package/dist/lib/pmo/storage/roadmaps.js +301 -0
- package/dist/lib/pmo/storage/specs.js +2 -0
- package/dist/lib/pmo/storage/types.d.ts +14 -0
- package/dist/lib/pmo/sync-manager.d.ts +1 -1
- package/dist/lib/pmo/sync-manager.js +1 -1
- package/dist/lib/pmo/types.d.ts +41 -0
- package/dist/lib/pmo/utils.d.ts +2 -0
- package/dist/lib/pmo/utils.js +22 -1
- package/dist/lib/repos/index.js +7 -1
- package/dist/lib/terminal.d.ts +31 -0
- package/dist/lib/terminal.js +48 -0
- package/dist/lib/themes.d.ts +21 -3
- package/dist/lib/themes.js +80 -23
- package/dist/lib/workspace-config.d.ts +80 -0
- package/dist/lib/workspace-config.js +100 -0
- package/oclif.manifest.json +4065 -3225
- package/package.json +10 -6
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -201,6 +201,31 @@ Scrum Workflow
|
|
|
201
201
|
|
|
202
202
|
Tickets flow through statuses as work progresses. Agents automatically move tickets when they start work, open PRs, or complete tasks.
|
|
203
203
|
|
|
204
|
+
### Agent Naming Themes
|
|
205
|
+
|
|
206
|
+
Themes control how agents are named. Staff agents use theme names directly (e.g., `bezos`, `camry`). Ephemeral agents add an adjective prefix (e.g., `bold-bezos`, `keen-camry`). Currently ephemeral names also include a number suffix (`bold-bezos-1`), but this will be removed soon.
|
|
207
|
+
|
|
208
|
+
**Built-in Themes:**
|
|
209
|
+
|
|
210
|
+
| Theme | Description | Example Names |
|
|
211
|
+
| --- | --- | --- |
|
|
212
|
+
| `billionaires` | Tech founders & executives (default) | `musk`, `gates`, `bezos` |
|
|
213
|
+
| `toyotas` | Toyota vehicle models | `camry`, `supra`, `tacoma` |
|
|
214
|
+
| `companies` | Major tech companies | `stripe`, `vercel`, `linear` |
|
|
215
|
+
|
|
216
|
+
> *billionaires* — Finally, they work for us.
|
|
217
|
+
|
|
218
|
+
**Theme Commands:**
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
prlt agent themes list # List available themes
|
|
222
|
+
prlt agent themes set billionaires # Set active theme
|
|
223
|
+
prlt agent themes create mytheme # Create custom theme
|
|
224
|
+
prlt agent themes add-names mytheme # Add names to custom theme
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Themes are selected during `prlt init`.
|
|
228
|
+
|
|
204
229
|
### Workspace Structure
|
|
205
230
|
|
|
206
231
|
Each agent gets a copy of all repos (repo scoping coming soon). Work happens on isolated branches.
|
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
|
|
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
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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) {
|