@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.
Files changed (152) hide show
  1. package/README.md +25 -0
  2. package/bin/dev.js +0 -0
  3. package/dist/commands/action/index.js +1 -1
  4. package/dist/commands/action/run.js +8 -12
  5. package/dist/commands/agent/auth.d.ts +30 -0
  6. package/dist/commands/agent/auth.js +172 -0
  7. package/dist/commands/agent/discover.d.ts +9 -0
  8. package/dist/commands/agent/discover.js +67 -0
  9. package/dist/commands/agent/index.js +47 -12
  10. package/dist/commands/agent/list.d.ts +4 -1
  11. package/dist/commands/agent/list.js +78 -16
  12. package/dist/commands/agent/login.js +35 -31
  13. package/dist/commands/agent/restart.js +2 -0
  14. package/dist/commands/agent/shell.js +78 -19
  15. package/dist/commands/agent/staff/add.js +1 -12
  16. package/dist/commands/agent/staff/remove.js +9 -7
  17. package/dist/commands/agent/status.js +17 -4
  18. package/dist/commands/agent/temp/cleanup.js +7 -3
  19. package/dist/commands/agent/themes/index.js +4 -5
  20. package/dist/commands/agent/themes/list.js +5 -5
  21. package/dist/commands/agent/visit.js +17 -4
  22. package/dist/commands/branch/create.d.ts +4 -0
  23. package/dist/commands/branch/create.js +16 -8
  24. package/dist/commands/branch/index.js +1 -1
  25. package/dist/commands/branch/where.js +1 -0
  26. package/dist/commands/claude.d.ts +38 -0
  27. package/dist/commands/claude.js +899 -0
  28. package/dist/commands/commit.js +1 -1
  29. package/dist/commands/config/index.d.ts +12 -0
  30. package/dist/commands/config/index.js +271 -0
  31. package/dist/commands/docker/clean.js +2 -2
  32. package/dist/commands/docker/index.js +2 -2
  33. package/dist/commands/docker/list.js +3 -8
  34. package/dist/commands/docker/logs.js +2 -2
  35. package/dist/commands/docker/prune.js +1 -1
  36. package/dist/commands/docker/restart.js +2 -2
  37. package/dist/commands/docker/shell.js +2 -2
  38. package/dist/commands/docker/start.js +2 -2
  39. package/dist/commands/docker/status.js +1 -1
  40. package/dist/commands/docker/stop.js +2 -2
  41. package/dist/commands/docker/sync.js +2 -2
  42. package/dist/commands/epic/index.js +1 -1
  43. package/dist/commands/epic/link/index.js +25 -14
  44. package/dist/commands/epic/link/remove.js +2 -0
  45. package/dist/commands/epic/list.js +5 -5
  46. package/dist/commands/epic/progress.js +10 -4
  47. package/dist/commands/epic/spec.js +2 -0
  48. package/dist/commands/epic/ticket.js +3 -0
  49. package/dist/commands/execution/stop.js +1 -0
  50. package/dist/commands/init.js +4 -4
  51. package/dist/commands/project/index.js +1 -1
  52. package/dist/commands/project/spec.js +7 -0
  53. package/dist/commands/repo/add.js +1 -0
  54. package/dist/commands/repo/remove.js +1 -0
  55. package/dist/commands/roadmap/add-project.d.ts +18 -0
  56. package/dist/commands/roadmap/add-project.js +135 -0
  57. package/dist/commands/roadmap/create.d.ts +22 -0
  58. package/dist/commands/roadmap/create.js +156 -0
  59. package/dist/commands/roadmap/delete.d.ts +17 -0
  60. package/dist/commands/roadmap/delete.js +104 -0
  61. package/dist/commands/roadmap/generate.d.ts +22 -0
  62. package/dist/commands/roadmap/generate.js +201 -0
  63. package/dist/commands/roadmap/index.d.ts +13 -0
  64. package/dist/commands/roadmap/index.js +61 -0
  65. package/dist/commands/roadmap/list.d.ts +12 -0
  66. package/dist/commands/roadmap/list.js +42 -0
  67. package/dist/commands/roadmap/remove-project.d.ts +18 -0
  68. package/dist/commands/roadmap/remove-project.js +147 -0
  69. package/dist/commands/roadmap/reorder.d.ts +17 -0
  70. package/dist/commands/roadmap/reorder.js +157 -0
  71. package/dist/commands/roadmap/update.d.ts +19 -0
  72. package/dist/commands/roadmap/update.js +136 -0
  73. package/dist/commands/roadmap/view.d.ts +16 -0
  74. package/dist/commands/roadmap/view.js +103 -0
  75. package/dist/commands/spec/index.js +1 -1
  76. package/dist/commands/spec/link/index.js +24 -13
  77. package/dist/commands/spec/link/remove.js +2 -0
  78. package/dist/commands/status/index.js +1 -1
  79. package/dist/commands/status/list.js +0 -8
  80. package/dist/commands/template/delete.js +2 -0
  81. package/dist/commands/terminal/title.d.ts +12 -0
  82. package/dist/commands/terminal/title.js +48 -0
  83. package/dist/commands/ticket/complete.js +2 -0
  84. package/dist/commands/ticket/create.js +4 -2
  85. package/dist/commands/ticket/delete.js +2 -0
  86. package/dist/commands/ticket/edit.js +8 -2
  87. package/dist/commands/ticket/link/index.js +17 -3
  88. package/dist/commands/ticket/link/remove.js +2 -0
  89. package/dist/commands/ticket/list.js +1 -2
  90. package/dist/commands/ticket/move.js +2 -0
  91. package/dist/commands/ticket/project.js +3 -1
  92. package/dist/commands/ticket/reassign.js +2 -0
  93. package/dist/commands/ticket/spec.js +4 -2
  94. package/dist/commands/ticket/template/apply.js +4 -3
  95. package/dist/commands/ticket/template/create.js +2 -0
  96. package/dist/commands/ticket/template/index.js +1 -1
  97. package/dist/commands/ticket/update.js +2 -0
  98. package/dist/commands/work/index.js +1 -1
  99. package/dist/commands/work/revise.js +7 -1
  100. package/dist/commands/work/spawn.d.ts +2 -1
  101. package/dist/commands/work/spawn.js +131 -36
  102. package/dist/commands/work/start.d.ts +2 -1
  103. package/dist/commands/work/start.js +349 -69
  104. package/dist/commands/work/watch.js +10 -2
  105. package/dist/commands/workflow/create.js +3 -3
  106. package/dist/commands/workflow/switch.js +2 -1
  107. package/dist/commands/workspace/remove.js +0 -8
  108. package/dist/commands/workspace/use.js +1 -9
  109. package/dist/lib/agents/commands.js +18 -13
  110. package/dist/lib/database/index.d.ts +19 -12
  111. package/dist/lib/database/index.js +158 -42
  112. package/dist/lib/docker/resolve.js +1 -1
  113. package/dist/lib/execution/config.d.ts +6 -0
  114. package/dist/lib/execution/config.js +15 -2
  115. package/dist/lib/execution/devcontainer.d.ts +2 -0
  116. package/dist/lib/execution/devcontainer.js +41 -9
  117. package/dist/lib/execution/runners.d.ts +85 -3
  118. package/dist/lib/execution/runners.js +925 -228
  119. package/dist/lib/execution/spawner.d.ts +2 -2
  120. package/dist/lib/execution/spawner.js +4 -3
  121. package/dist/lib/execution/storage.d.ts +2 -1
  122. package/dist/lib/execution/storage.js +9 -13
  123. package/dist/lib/execution/types.d.ts +10 -1
  124. package/dist/lib/execution/types.js +3 -1
  125. package/dist/lib/init/index.js +1 -0
  126. package/dist/lib/machine-config.js +1 -1
  127. package/dist/lib/pmo/base-command.js +5 -9
  128. package/dist/lib/pmo/index.js +2 -0
  129. package/dist/lib/pmo/schema.d.ts +6 -0
  130. package/dist/lib/pmo/schema.js +36 -0
  131. package/dist/lib/pmo/storage/base.js +3 -3
  132. package/dist/lib/pmo/storage/index.d.ts +16 -1
  133. package/dist/lib/pmo/storage/index.js +45 -0
  134. package/dist/lib/pmo/storage/roadmaps.d.ts +62 -0
  135. package/dist/lib/pmo/storage/roadmaps.js +301 -0
  136. package/dist/lib/pmo/storage/specs.js +2 -0
  137. package/dist/lib/pmo/storage/types.d.ts +14 -0
  138. package/dist/lib/pmo/sync-manager.d.ts +1 -1
  139. package/dist/lib/pmo/sync-manager.js +1 -1
  140. package/dist/lib/pmo/types.d.ts +41 -0
  141. package/dist/lib/pmo/utils.d.ts +2 -0
  142. package/dist/lib/pmo/utils.js +22 -1
  143. package/dist/lib/repos/index.js +7 -1
  144. package/dist/lib/terminal.d.ts +31 -0
  145. package/dist/lib/terminal.js +48 -0
  146. package/dist/lib/themes.d.ts +21 -3
  147. package/dist/lib/themes.js +80 -23
  148. package/dist/lib/workspace-config.d.ts +80 -0
  149. package/dist/lib/workspace-config.js +100 -0
  150. package/oclif.manifest.json +4065 -3225
  151. package/package.json +10 -6
  152. package/LICENSE +0 -21
@@ -6,7 +6,7 @@ import inquirer from 'inquirer';
6
6
  import { colors } from '../../lib/colors.js';
7
7
  import { getWorkspaceInfo } from '../../lib/agents/commands.js';
8
8
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
9
- import { isDockerRunning } from '../../lib/execution/runners.js';
9
+ import { isDockerRunning, getAgentContainerName, isContainerRunning, getContainerId, } from '../../lib/execution/runners.js';
10
10
  import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
11
11
  export default class Login extends PMOCommand {
12
12
  static description = 'Authenticate Claude Code inside an agent container (one-time setup)';
@@ -65,15 +65,28 @@ export default class Login extends PMOCommand {
65
65
  outputPromptAsJson(buildPromptConfig('list', 'name', 'Select agent to authenticate:', agentChoices), createMetadata('agent login', flags));
66
66
  return;
67
67
  }
68
+ // Group agents by type
69
+ const staffAgents = workspaceInfo.agents.filter(a => a.type === 'persistent');
70
+ const tempAgents = workspaceInfo.agents.filter(a => a.type === 'ephemeral');
71
+ const choices = [];
72
+ if (staffAgents.length > 0) {
73
+ choices.push(new inquirer.Separator('── Staff Agents ──'));
74
+ for (const agent of staffAgents) {
75
+ choices.push({ name: `👔 ${agent.name}`, value: agent.name });
76
+ }
77
+ }
78
+ if (tempAgents.length > 0) {
79
+ choices.push(new inquirer.Separator('── Temp Agents ──'));
80
+ for (const agent of tempAgents) {
81
+ choices.push({ name: `⏱️ ${agent.name}`, value: agent.name });
82
+ }
83
+ }
68
84
  const { selected } = await inquirer.prompt([
69
85
  {
70
86
  type: 'list',
71
87
  name: 'selected',
72
88
  message: 'Select agent to authenticate:',
73
- choices: workspaceInfo.agents.map(agent => ({
74
- name: agent.name,
75
- value: agent.name
76
- }))
89
+ choices
77
90
  }
78
91
  ]);
79
92
  agentName = selected;
@@ -84,38 +97,29 @@ export default class Login extends PMOCommand {
84
97
  this.error(`Agent "${agentName}" not found. Available agents: ${workspaceInfo.agents.map(a => a.name).join(', ')}`);
85
98
  }
86
99
  const agentDir = path.join(workspaceInfo.agentsPath, agentName);
87
- // Check if devcontainer exists
88
- const devcontainerPath = path.join(agentDir, '.devcontainer');
89
- try {
90
- execSync(`test -d "${devcontainerPath}"`, { stdio: 'ignore' });
91
- }
92
- catch {
93
- this.error(`Agent "${agentName}" does not have a devcontainer configuration. Run "prlt agent add ${agentName}" to initialize.`);
100
+ // Check if Docker config exists
101
+ const dockerfilePath = path.join(agentDir, '.devcontainer', 'Dockerfile');
102
+ if (!fs.existsSync(dockerfilePath)) {
103
+ this.error(`Agent "${agentName}" does not have a Docker configuration. Run "prlt agent add ${agentName}" to initialize.`);
94
104
  }
95
- // Get container ID
105
+ // Get container using the standard naming convention
96
106
  this.log(colors.primary(`🔐 Authenticating agent: ${agentName}`));
97
107
  this.log('');
98
- let containerId;
99
- try {
100
- containerId = execSync(`docker ps --filter "label=devcontainer.local_folder=${agentDir}" --format "{{.ID}}"`, { encoding: 'utf-8' }).trim();
101
- }
102
- catch {
103
- this.error('Failed to find running container. Make sure the agent container is running.');
108
+ const containerName = getAgentContainerName(agentName);
109
+ let containerId = null;
110
+ // Check if container is running
111
+ if (isContainerRunning(containerName)) {
112
+ containerId = getContainerId(containerName);
104
113
  }
105
114
  if (!containerId) {
106
- this.log(colors.warning('Container is not running. Starting it now...'));
115
+ this.log(colors.warning('Container is not running.'));
107
116
  this.log('');
108
- try {
109
- execSync(`devcontainer up --workspace-folder "${agentDir}"`, {
110
- stdio: 'inherit',
111
- cwd: agentDir
112
- });
113
- // Get container ID again
114
- containerId = execSync(`docker ps --filter "label=devcontainer.local_folder=${agentDir}" --format "{{.ID}}"`, { encoding: 'utf-8' }).trim();
115
- }
116
- catch {
117
- this.error('Failed to start container.');
118
- }
117
+ this.log('Start the container first by running a work command:');
118
+ this.log(' prlt work start <ticket-id>');
119
+ this.log('');
120
+ this.log('Or start an interactive session:');
121
+ this.log(' prlt agent shell ' + agentName);
122
+ this.error('Container must be running to authenticate.');
119
123
  }
120
124
  // Create a helper script to launch interactive session
121
125
  this.log(colors.success(`✓ Container running: ${containerId}`));
@@ -80,8 +80,10 @@ export default class AgentRestart extends PMOCommand {
80
80
  }
81
81
  for (const containerName of containerNames) {
82
82
  this.log(colors.textSecondary(` Stopping container: ${containerName}...`));
83
+ // eslint-disable-next-line no-await-in-loop -- Sequential container operations with user feedback
83
84
  await execAsync(`docker stop ${containerName}`);
84
85
  this.log(colors.textSecondary(` Removing container: ${containerName}...`));
86
+ // eslint-disable-next-line no-await-in-loop
85
87
  await execAsync(`docker rm ${containerName}`);
86
88
  }
87
89
  this.log('');
@@ -5,10 +5,10 @@ import { execSync, spawn } from 'node:child_process';
5
5
  import inquirer from 'inquirer';
6
6
  import Database from 'better-sqlite3';
7
7
  import { colors } from '../../lib/colors.js';
8
- import { getWorkspaceInfo } from '../../lib/agents/commands.js';
8
+ import { getWorkspaceInfo, getAgentTmuxSessions } from '../../lib/agents/commands.js';
9
9
  import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
10
10
  import { getTerminalApp } from '../../lib/execution/config.js';
11
- import { isDockerRunning } from '../../lib/execution/runners.js';
11
+ import { isDockerRunning, getAgentContainerName, isContainerRunning, getContainerId, } from '../../lib/execution/runners.js';
12
12
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
13
13
  import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../lib/prompt-json.js';
14
14
  export default class Shell extends PMOCommand {
@@ -58,19 +58,34 @@ export default class Shell extends PMOCommand {
58
58
  let agentName = args.name;
59
59
  // Interactive mode if no agent specified
60
60
  if (!agentName) {
61
- // Build choices once, use for both JSON and interactive modes
62
- const agentChoices = workspaceInfo.agents.map((agent) => ({ name: agent.name, value: agent.name }));
63
61
  const selectMessage = 'Select agent to open shell in:';
64
62
  // In JSON mode, output agent selection prompt and exit
65
63
  if (jsonMode) {
64
+ const agentChoices = workspaceInfo.agents.map((agent) => ({ name: agent.name, value: agent.name }));
66
65
  outputPromptAsJson(buildPromptConfig('list', 'name', selectMessage, agentChoices), createMetadata('agent shell', flags));
67
66
  }
67
+ // Group agents by type for interactive mode
68
+ const staffAgents = workspaceInfo.agents.filter(a => a.type === 'persistent');
69
+ const tempAgents = workspaceInfo.agents.filter(a => a.type === 'ephemeral');
70
+ const choices = [];
71
+ if (staffAgents.length > 0) {
72
+ choices.push(new inquirer.Separator('── Staff Agents ──'));
73
+ for (const agent of staffAgents) {
74
+ choices.push({ name: `👔 ${agent.name}`, value: agent.name });
75
+ }
76
+ }
77
+ if (tempAgents.length > 0) {
78
+ choices.push(new inquirer.Separator('── Temp Agents ──'));
79
+ for (const agent of tempAgents) {
80
+ choices.push({ name: `⏱️ ${agent.name}`, value: agent.name });
81
+ }
82
+ }
68
83
  const { selected } = await inquirer.prompt([
69
84
  {
70
85
  type: 'list',
71
86
  name: 'selected',
72
87
  message: selectMessage,
73
- choices: agentChoices
88
+ choices
74
89
  }
75
90
  ]);
76
91
  agentName = selected;
@@ -80,6 +95,44 @@ export default class Shell extends PMOCommand {
80
95
  if (!agent) {
81
96
  return handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available agents: ${workspaceInfo.agents.map(a => a.name).join(', ')}`);
82
97
  }
98
+ // Check for existing tmux sessions
99
+ const existingSessions = getAgentTmuxSessions(agentName);
100
+ if (existingSessions.length > 0 && !jsonMode) {
101
+ this.log(colors.warning(`\n⚠️ Agent "${agentName}" has ${existingSessions.length} active tmux session(s):`));
102
+ for (const session of existingSessions) {
103
+ this.log(colors.textMuted(` • ${session}`));
104
+ }
105
+ this.log('');
106
+ const { sessionAction } = await inquirer.prompt([{
107
+ type: 'list',
108
+ name: 'sessionAction',
109
+ message: 'What would you like to do?',
110
+ choices: [
111
+ { name: '🔗 Attach to existing session', value: 'attach' },
112
+ { name: '⚠️ Open new shell anyway (may cause conflicts)', value: 'continue' },
113
+ { name: '❌ Cancel', value: 'cancel' },
114
+ ],
115
+ }]);
116
+ if (sessionAction === 'cancel') {
117
+ this.log(colors.textMuted('Operation cancelled.'));
118
+ return;
119
+ }
120
+ if (sessionAction === 'attach') {
121
+ // Attach to the first session
122
+ const sessionName = existingSessions[0];
123
+ this.log(colors.primary(`\nAttaching to session: ${sessionName}`));
124
+ try {
125
+ execSync(`tmux attach-session -t "${sessionName}"`, { stdio: 'inherit' });
126
+ }
127
+ catch {
128
+ // User detached or session ended
129
+ this.log(colors.textMuted('\nDetached from session.'));
130
+ }
131
+ return;
132
+ }
133
+ // If 'continue', proceed with opening a new shell
134
+ this.log(colors.warning('\nProceeding with new shell - be careful of conflicts!\n'));
135
+ }
83
136
  const agentDir = path.join(workspaceInfo.agentsPath, agentName);
84
137
  // Check if agent has devcontainer
85
138
  const hasDevcontainer = hasDevcontainerConfig(agentDir);
@@ -156,22 +209,28 @@ export default class Shell extends PMOCommand {
156
209
  if (!isDockerRunning()) {
157
210
  this.error('Docker is not running. Please start Docker Desktop and try again.');
158
211
  }
159
- // Start or ensure container is running
160
- try {
161
- execSync(`devcontainer up --workspace-folder "${agentDir}"`, {
162
- stdio: 'pipe',
163
- });
164
- }
165
- catch (error) {
166
- this.error(`Failed to start devcontainer: ${error instanceof Error ? error.message : error}`);
167
- }
168
- // Get container ID
212
+ // Get container using the standard naming convention
213
+ const containerName = getAgentContainerName(agentName);
169
214
  let containerId = null;
170
- try {
171
- containerId = execSync(`docker ps -q --filter "label=devcontainer.local_folder=${agentDir}"`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
215
+ // Check if container is running
216
+ if (isContainerRunning(containerName)) {
217
+ containerId = getContainerId(containerName);
172
218
  }
173
- catch {
174
- // Ignore
219
+ else {
220
+ // Try to start a stopped container
221
+ try {
222
+ execSync(`docker start ${containerName}`, { stdio: 'pipe' });
223
+ containerId = getContainerId(containerName);
224
+ }
225
+ catch {
226
+ // Container doesn't exist - user needs to run a work command first
227
+ this.log(colors.warning('Container is not running.'));
228
+ this.log('');
229
+ this.log('Start the container first by running a work command:');
230
+ this.log(' prlt work start <ticket-id>');
231
+ this.log('');
232
+ this.error('Container must exist. Run a work command first to create it.');
233
+ }
175
234
  }
176
235
  if (!containerId) {
177
236
  this.error('Failed to find running container.');
@@ -3,7 +3,7 @@ import chalk from 'chalk';
3
3
  import inquirer from 'inquirer';
4
4
  import { getWorkspaceInfo, validateAgentNames, addAgentsToWorkspace } from '../../../lib/agents/commands.js';
5
5
  import { ensureBuiltinThemes, BUILTIN_THEMES, isValidAgentName, normalizeAgentName } from '../../../lib/themes.js';
6
- import { getTheme, getThemes, getAvailableThemeNames, markThemeNameUsed, getActiveTheme } from '../../../lib/database/index.js';
6
+ import { getTheme, getThemes, getAvailableThemeNames, getActiveTheme } from '../../../lib/database/index.js';
7
7
  import { shouldOutputJson, outputPromptAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../../lib/prompt-json.js';
8
8
  export default class Add extends Command {
9
9
  static description = 'Add new agents to the workspace';
@@ -84,10 +84,6 @@ export default class Add extends Command {
84
84
  validate: (input) => input.length > 0 || 'Please select at least one name'
85
85
  }]);
86
86
  agentNames = selected;
87
- // Mark selected names as used
88
- for (const name of agentNames) {
89
- markThemeNameUsed(workspaceInfo.path, themeId, name);
90
- }
91
87
  }
92
88
  // Interactive mode: show names from workspace's active theme
93
89
  else if (agentNames.length === 0) {
@@ -153,10 +149,6 @@ export default class Add extends Command {
153
149
  }
154
150
  if (themedSelections.length > 0) {
155
151
  agentNames.push(...themedSelections);
156
- // Mark themed names as used
157
- for (const name of themedSelections) {
158
- markThemeNameUsed(workspaceInfo.path, activeTheme.id, name);
159
- }
160
152
  }
161
153
  }
162
154
  else {
@@ -233,9 +225,6 @@ export default class Add extends Command {
233
225
  validate: (input) => input.length > 0 || 'Please select at least one name'
234
226
  }]);
235
227
  agentNames = selected;
236
- for (const name of agentNames) {
237
- markThemeNameUsed(workspaceInfo.path, selectedTheme, name);
238
- }
239
228
  }
240
229
  }
241
230
  if (agentNames.length === 0) {
@@ -40,12 +40,14 @@ export default class Remove extends PMOCommand {
40
40
  };
41
41
  // Get workspace information
42
42
  const workspaceInfo = getWorkspaceInfo();
43
- if (workspaceInfo.agents.length === 0) {
43
+ // Filter to staff (persistent) agents only
44
+ const staffAgents = workspaceInfo.agents.filter(a => a.type === 'persistent');
45
+ if (staffAgents.length === 0) {
44
46
  if (jsonMode) {
45
- outputErrorAsJson('NO_AGENTS', 'No agents to remove.', createMetadata('agent remove', flags));
47
+ outputErrorAsJson('NO_AGENTS', 'No staff agents to remove.', createMetadata('agent staff remove', flags));
46
48
  return;
47
49
  }
48
- this.log(colors.warning('No agents to remove.'));
50
+ this.log(colors.warning('No staff agents to remove.'));
49
51
  return;
50
52
  }
51
53
  let agentName = args.name;
@@ -53,7 +55,7 @@ export default class Remove extends PMOCommand {
53
55
  if (!agentName) {
54
56
  // Build choices once, use for both JSON and interactive modes
55
57
  const agentChoices = [
56
- ...workspaceInfo.agents.map((agent) => ({ name: agent.name, value: agent.name })),
58
+ ...staffAgents.map((agent) => ({ name: agent.name, value: agent.name })),
57
59
  { name: 'Cancel', value: 'cancel' },
58
60
  ];
59
61
  const selectMessage = 'Select agent to remove:';
@@ -80,10 +82,10 @@ export default class Remove extends PMOCommand {
80
82
  }
81
83
  agentName = selected;
82
84
  }
83
- // Validate agent exists
84
- const agent = workspaceInfo.agents.find((a) => a.name === agentName);
85
+ // Validate agent exists and is a staff agent
86
+ const agent = staffAgents.find((a) => a.name === agentName);
85
87
  if (!agent) {
86
- return handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available agents: ${workspaceInfo.agents.map((a) => a.name).join(', ')}`);
88
+ return handleError('AGENT_NOT_FOUND', `Staff agent "${agentName}" not found. Available staff agents: ${staffAgents.map((a) => a.name).join(', ')}`);
87
89
  }
88
90
  const agentsToRemove = [agentName];
89
91
  // Build choices once, use for both JSON and interactive modes
@@ -49,15 +49,28 @@ export default class Status extends PMOCommand {
49
49
  outputPromptAsJson(buildPromptConfig('list', 'name', 'Select agent to view status:', agentChoices), createMetadata('agent status', flags));
50
50
  return;
51
51
  }
52
+ // Group agents by type
53
+ const staffAgents = workspaceInfo.agents.filter(a => a.type === 'persistent');
54
+ const tempAgents = workspaceInfo.agents.filter(a => a.type === 'ephemeral');
55
+ const choices = [];
56
+ if (staffAgents.length > 0) {
57
+ choices.push(new inquirer.Separator('── Staff Agents ──'));
58
+ for (const agent of staffAgents) {
59
+ choices.push({ name: `👔 ${agent.name}`, value: agent.name });
60
+ }
61
+ }
62
+ if (tempAgents.length > 0) {
63
+ choices.push(new inquirer.Separator('── Temp Agents ──'));
64
+ for (const agent of tempAgents) {
65
+ choices.push({ name: `⏱️ ${agent.name}`, value: agent.name });
66
+ }
67
+ }
52
68
  const { selected } = await inquirer.prompt([
53
69
  {
54
70
  type: 'list',
55
71
  name: 'selected',
56
72
  message: 'Select agent to view status:',
57
- choices: workspaceInfo.agents.map((agent) => ({
58
- name: agent.name,
59
- value: agent.name
60
- }))
73
+ choices
61
74
  }
62
75
  ]);
63
76
  agentName = selected;
@@ -225,9 +225,10 @@ export default class Cleanup extends PMOCommand {
225
225
  if (!skipConfirm && !dryRun) {
226
226
  // In JSON mode, output confirmation prompt with context
227
227
  if (jsonMode) {
228
- const promptConfig = buildPromptConfig('list', 'confirmed', confirmMessage, confirmChoices);
229
- // Add context about what will be cleaned
230
- promptConfig.context = { agentsToCleanup };
228
+ const promptConfig = {
229
+ ...buildPromptConfig('list', 'confirmed', confirmMessage, confirmChoices),
230
+ context: { agentsToCleanup },
231
+ };
231
232
  outputPromptAsJson(promptConfig, createMetadata('agent cleanup', flags));
232
233
  return;
233
234
  }
@@ -272,6 +273,7 @@ export default class Cleanup extends PMOCommand {
272
273
  if (!jsonMode) {
273
274
  this.log(colors.primary(`\nCleaning up: ${agentName}`));
274
275
  }
276
+ // eslint-disable-next-line no-await-in-loop -- Sequential cleanup with user interaction
275
277
  const result = await cleanupAgent(workspaceInfo, agentName, {
276
278
  log: jsonMode ? undefined : (msg) => this.log(colors.textMuted(` ${msg}`)),
277
279
  dryRun,
@@ -313,6 +315,7 @@ export default class Cleanup extends PMOCommand {
313
315
  choices.push({ name: '🗑️ Force cleanup (discard all changes)', value: 'force' });
314
316
  choices.push({ name: '⏭️ Skip this agent', value: 'skip' });
315
317
  // In interactive mode, prompt for action
318
+ // eslint-disable-next-line no-await-in-loop -- User interaction per agent
316
319
  const { action } = await inquirer.prompt([
317
320
  {
318
321
  type: 'list',
@@ -327,6 +330,7 @@ export default class Cleanup extends PMOCommand {
327
330
  continue;
328
331
  }
329
332
  // Re-run cleanup with the selected option
333
+ // eslint-disable-next-line no-await-in-loop
330
334
  const retryResult = await cleanupAgent(workspaceInfo, agentName, {
331
335
  log: (msg) => this.log(colors.textMuted(` ${msg}`)),
332
336
  dryRun,
@@ -3,7 +3,7 @@ import inquirer from 'inquirer';
3
3
  import chalk from 'chalk';
4
4
  import { getWorkspaceInfo } from '../../../lib/agents/commands.js';
5
5
  import { ensureBuiltinThemes } from '../../../lib/themes.js';
6
- import { getThemes, getThemeNames } from '../../../lib/database/index.js';
6
+ import { getThemes, getAvailableThemeNames } from '../../../lib/database/index.js';
7
7
  import { shouldOutputJson, outputPromptAsJson, createMetadata, buildPromptConfig, } from '../../../lib/prompt-json.js';
8
8
  export default class Themes extends Command {
9
9
  static description = 'Manage agent naming themes';
@@ -119,15 +119,14 @@ export default class Themes extends Command {
119
119
  }
120
120
  // Build choices with theme info
121
121
  const themeChoices = themes.map(t => {
122
- const names = getThemeNames(workspaceInfo.path, t.id);
123
- const available = names.filter(n => !n.used).length;
122
+ const availableNames = getAvailableThemeNames(workspaceInfo.path, t.id);
124
123
  const builtinTag = t.builtin ? chalk.dim(' [built-in]') : '';
125
124
  return {
126
- name: `${t.display_name}${builtinTag} ${chalk.dim(`(${available} names available)`)}`,
125
+ name: `${t.display_name}${builtinTag} ${chalk.dim(`(${availableNames.length} names available)`)}`,
127
126
  value: t.id
128
127
  };
129
128
  });
130
- // Add option to create new theme
129
+ // Add option to create new theme (using type assertion for mixed array)
131
130
  themeChoices.push(new inquirer.Separator());
132
131
  themeChoices.push({ name: chalk.green('+ Create new theme'), value: '__create_new__' });
133
132
  const { selectedTheme } = await inquirer.prompt([{
@@ -2,7 +2,7 @@ import { Command } from '@oclif/core';
2
2
  import chalk from 'chalk';
3
3
  import { getWorkspaceInfo } from '../../../lib/agents/commands.js';
4
4
  import { ensureBuiltinThemes } from '../../../lib/themes.js';
5
- import { getThemes, getThemeNames } from '../../../lib/database/index.js';
5
+ import { getThemes, getThemeNames, getAvailableThemeNames } from '../../../lib/database/index.js';
6
6
  export default class ThemesList extends Command {
7
7
  static description = 'List available agent themes';
8
8
  static examples = [
@@ -20,16 +20,16 @@ export default class ThemesList extends Command {
20
20
  }
21
21
  this.log(chalk.bold('\nAgent Themes\n'));
22
22
  for (const theme of themes) {
23
- const names = getThemeNames(workspaceInfo.path, theme.id);
24
- const available = names.filter(n => !n.used).length;
25
- const used = names.filter(n => n.used).length;
23
+ const allNames = getThemeNames(workspaceInfo.path, theme.id);
24
+ const availableNames = getAvailableThemeNames(workspaceInfo.path, theme.id);
25
+ const inUse = allNames.length - availableNames.length;
26
26
  const builtinTag = theme.builtin ? chalk.gray(' [built-in]') : '';
27
27
  this.log(` ${chalk.cyan(theme.display_name)}${builtinTag}`);
28
28
  this.log(chalk.gray(` ID: ${theme.id}`));
29
29
  if (theme.description) {
30
30
  this.log(chalk.gray(` ${theme.description}`));
31
31
  }
32
- this.log(chalk.gray(` Names: ${chalk.green(available + ' available')}, ${chalk.yellow(used + ' in use')}`));
32
+ this.log(chalk.gray(` Names: ${chalk.green(availableNames.length + ' available')}, ${chalk.yellow(inUse + ' in use')}`));
33
33
  this.log('');
34
34
  }
35
35
  this.log(chalk.blue('Use: prlt agent add --theme <theme-id>'));
@@ -58,15 +58,28 @@ export default class Visit extends PMOCommand {
58
58
  outputPromptAsJson(buildPromptConfig('list', 'name', 'Select agent to visit:', agentChoices), createMetadata('agent visit', flags));
59
59
  return;
60
60
  }
61
+ // Group agents by type
62
+ const staffAgents = workspaceInfo.agents.filter(a => a.type === 'persistent');
63
+ const tempAgents = workspaceInfo.agents.filter(a => a.type === 'ephemeral');
64
+ const choices = [];
65
+ if (staffAgents.length > 0) {
66
+ choices.push(new inquirer.Separator('── Staff Agents ──'));
67
+ for (const agent of staffAgents) {
68
+ choices.push({ name: `👔 ${agent.name}`, value: agent.name });
69
+ }
70
+ }
71
+ if (tempAgents.length > 0) {
72
+ choices.push(new inquirer.Separator('── Temp Agents ──'));
73
+ for (const agent of tempAgents) {
74
+ choices.push({ name: `⏱️ ${agent.name}`, value: agent.name });
75
+ }
76
+ }
61
77
  const { selected } = await inquirer.prompt([
62
78
  {
63
79
  type: 'list',
64
80
  name: 'selected',
65
81
  message: 'Select agent to visit:',
66
- choices: workspaceInfo.agents.map(agent => ({
67
- name: agent.name,
68
- value: agent.name
69
- }))
82
+ choices
70
83
  }
71
84
  ]);
72
85
  agentName = selected;
@@ -1,5 +1,9 @@
1
1
  import { PMOCommand } from '../../lib/pmo/index.js';
2
2
  export default class BranchCreate extends PMOCommand {
3
+ private _selectedTicket?;
4
+ private _autoCommit?;
5
+ private _fromOrigin?;
6
+ private _customStartPoint?;
3
7
  static description: string;
4
8
  static examples: string[];
5
9
  static args: {
@@ -1,6 +1,6 @@
1
1
  import { Args, Flags } from '@oclif/core';
2
- import * as fs from 'fs';
3
- import * as path from 'path';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
4
  import inquirer from 'inquirer';
5
5
  import Database from 'better-sqlite3';
6
6
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
@@ -11,6 +11,11 @@ import { getCoderName, getGitUserName, getGitHubUsername } from '../../lib/execu
11
11
  import { getBranchType } from '../../lib/execution/types.js';
12
12
  import { detectAgentName } from '../../lib/agents/index.js';
13
13
  export default class BranchCreate extends PMOCommand {
14
+ // Internal state for passing data between methods
15
+ _selectedTicket;
16
+ _autoCommit;
17
+ _fromOrigin;
18
+ _customStartPoint;
14
19
  static description = 'Create a new branch with conventional naming';
15
20
  static examples = [
16
21
  '<%= config.bin %> <%= command.id %>',
@@ -155,6 +160,7 @@ export default class BranchCreate extends PMOCommand {
155
160
  if (!wizardResult)
156
161
  return;
157
162
  branchName = wizardResult.branchName;
163
+ // Store ticket info, autoCommit, fromOrigin, and customStartPoint flags for later
158
164
  this._selectedTicket = wizardResult.ticket;
159
165
  this._autoCommit = wizardResult.autoCommit;
160
166
  this._fromOrigin = wizardResult.fromOrigin;
@@ -286,13 +292,14 @@ export default class BranchCreate extends PMOCommand {
286
292
  /**
287
293
  * Create branch from ticket ID with defaults (non-interactive).
288
294
  */
289
- async createFromTicketId(ticketId, ownerOverride, projectId) {
295
+ async createFromTicketId(ticketId, ownerOverride, _projectId) {
290
296
  try {
291
297
  // Search for ticket across all projects
292
298
  const projects = await this.storage.listProjects();
293
299
  let foundTicket = null;
294
- for (const project of projects) {
295
- const tickets = await this.storage.listTickets(projectId);
300
+ for (const proj of projects) {
301
+ // eslint-disable-next-line no-await-in-loop -- Search with early exit
302
+ const tickets = await this.storage.listTickets(proj.id);
296
303
  const match = tickets.find(t => t.id === ticketId);
297
304
  if (match) {
298
305
  foundTicket = match;
@@ -323,12 +330,13 @@ export default class BranchCreate extends PMOCommand {
323
330
  // Get default owner name from config or GitHub
324
331
  const defaultOwnerName = this.getDefaultOwnerName();
325
332
  // Load tickets from PMO (across all projects)
326
- let tickets = [];
333
+ const tickets = [];
327
334
  try {
328
335
  // Get all projects and their tickets
329
336
  const projects = await this.storage.listProjects();
330
337
  const projectId = flags.project;
331
338
  for (const project of projects) {
339
+ // eslint-disable-next-line no-await-in-loop -- Collecting tickets from projects
332
340
  const projectTickets = await this.storage.listTickets(projectId);
333
341
  // Filter to actionable tickets (todo, in-progress, backlog)
334
342
  const actionable = projectTickets.filter(t => !t.status || ['todo', 'in-progress', 'backlog', 'in_progress'].includes(t.status.toLowerCase()));
@@ -484,7 +492,7 @@ export default class BranchCreate extends PMOCommand {
484
492
  default: 0,
485
493
  },
486
494
  ]);
487
- let fromOrigin = branchFrom === 'origin-main';
495
+ const fromOrigin = branchFrom === 'origin-main';
488
496
  let customStartPoint;
489
497
  if (branchFrom === 'other') {
490
498
  customStartPoint = await this.promptForBranch();
@@ -586,7 +594,7 @@ export default class BranchCreate extends PMOCommand {
586
594
  default: 0,
587
595
  },
588
596
  ]);
589
- let fromOrigin = branchFrom === 'origin-main';
597
+ const fromOrigin = branchFrom === 'origin-main';
590
598
  let customStartPoint;
591
599
  if (branchFrom === 'other') {
592
600
  customStartPoint = await this.promptForBranch();
@@ -1,6 +1,6 @@
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
+ import { shouldOutputJson } from '../../lib/prompt-json.js';
4
4
  export default class Branch extends PMOCommand {
5
5
  static description = 'Interactive menu for branch operations';
6
6
  static examples = ['<%= config.bin %> <%= command.id %>'];
@@ -186,6 +186,7 @@ export default class BranchWhere extends PMOCommand {
186
186
  while (current !== root) {
187
187
  try {
188
188
  const dbPath = path.join(current, '.proletariat', 'workspace.db');
189
+ // eslint-disable-next-line unicorn/prefer-module
189
190
  const fs = require('node:fs');
190
191
  if (fs.existsSync(dbPath)) {
191
192
  return current;
@@ -0,0 +1,38 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class Claude extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
+ slug: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'permission-mode': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ environment: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ 'display-mode': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ prompt: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ directory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ title: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ };
16
+ run(): Promise<void>;
17
+ /**
18
+ * Run in "yolo mode" - outside any HQ
19
+ * No ticket creation, no tracking, just launch Claude
20
+ */
21
+ private runOutsideHQ;
22
+ /**
23
+ * Run in "tracked mode" - inside an HQ
24
+ * Creates adhoc ticket, ephemeral agent, full tracking
25
+ */
26
+ private runInsideHQ;
27
+ /**
28
+ * Set up catch-all devcontainer for directories without one
29
+ * Uses a temp directory to avoid polluting user's cwd
30
+ * Returns { configDir, workDir } where configDir contains .devcontainer
31
+ */
32
+ private setupCatchallDevcontainer;
33
+ /**
34
+ * Check if catch-all container image is available
35
+ * Returns true if image exists locally or can be pulled
36
+ */
37
+ private checkCatchallImage;
38
+ }