@proletariat/cli 0.3.43 → 0.3.45
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/dist/commands/agent/list.js +2 -3
- package/dist/commands/agent/login.js +2 -2
- package/dist/commands/agent/rebuild.js +2 -3
- package/dist/commands/agent/shell.js +2 -2
- package/dist/commands/agent/status.js +3 -3
- package/dist/commands/agent/visit.js +2 -2
- package/dist/commands/orchestrator/attach.d.ts +13 -0
- package/dist/commands/orchestrator/attach.js +140 -0
- package/dist/commands/orchestrator/index.d.ts +14 -0
- package/dist/commands/orchestrator/index.js +51 -0
- package/dist/commands/orchestrator/start.d.ts +17 -0
- package/dist/commands/orchestrator/start.js +287 -0
- package/dist/commands/orchestrator/status.d.ts +12 -0
- package/dist/commands/orchestrator/status.js +63 -0
- package/dist/commands/orchestrator/stop.d.ts +11 -0
- package/dist/commands/orchestrator/stop.js +104 -0
- package/dist/commands/staff/list.js +2 -3
- package/dist/commands/work/revise.js +3 -3
- package/dist/commands/work/start.js +4 -4
- package/dist/commands/work/watch.js +2 -2
- package/dist/lib/agents/commands.d.ts +11 -0
- package/dist/lib/agents/commands.js +40 -10
- package/dist/lib/execution/runners.js +4 -0
- package/dist/lib/execution/spawner.js +3 -2
- package/dist/lib/themes.js +32 -16
- package/oclif.manifest.json +3268 -2971
- package/package.json +1 -1
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Flags } from '@oclif/core';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import * as path from 'node:path';
|
|
4
3
|
import * as fs from 'node:fs';
|
|
5
|
-
import { getWorkspaceInfo, getAllAgentsStatus, getAgentTmuxSessions } from '../../lib/agents/commands.js';
|
|
4
|
+
import { getWorkspaceInfo, getAllAgentsStatus, getAgentTmuxSessions, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
6
5
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
7
6
|
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
8
7
|
export default class List extends PMOCommand {
|
|
@@ -139,7 +138,7 @@ export default class List extends PMOCommand {
|
|
|
139
138
|
}
|
|
140
139
|
}
|
|
141
140
|
else {
|
|
142
|
-
const agentDir =
|
|
141
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentStatus.name);
|
|
143
142
|
const dirExists = fs.existsSync(agentDir);
|
|
144
143
|
if (dirExists) {
|
|
145
144
|
this.log(chalk.red(` Invalid or broken worktrees`));
|
|
@@ -3,7 +3,7 @@ import * as path from 'node:path';
|
|
|
3
3
|
import * as fs from 'node:fs';
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
5
|
import { colors } from '../../lib/colors.js';
|
|
6
|
-
import { getWorkspaceInfo, formatAgentList } from '../../lib/agents/commands.js';
|
|
6
|
+
import { getWorkspaceInfo, formatAgentList, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
7
7
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
8
8
|
import { isDockerRunning, getAgentContainerName, isContainerRunning, getContainerId, } from '../../lib/execution/runners.js';
|
|
9
9
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -74,7 +74,7 @@ export default class Login extends PMOCommand {
|
|
|
74
74
|
if (!agent) {
|
|
75
75
|
this.error(`Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`);
|
|
76
76
|
}
|
|
77
|
-
const agentDir =
|
|
77
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
78
78
|
// Check if Docker config exists
|
|
79
79
|
const dockerfilePath = path.join(agentDir, '.devcontainer', 'Dockerfile');
|
|
80
80
|
if (!fs.existsSync(dockerfilePath)) {
|
|
@@ -4,7 +4,7 @@ import { promisify } from 'node:util';
|
|
|
4
4
|
import * as path from 'node:path';
|
|
5
5
|
import * as fs from 'node:fs';
|
|
6
6
|
import { colors } from '../../lib/colors.js';
|
|
7
|
-
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
7
|
+
import { getWorkspaceInfo, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
8
8
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
9
9
|
import { isDockerRunning } from '../../lib/execution/runners.js';
|
|
10
10
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -76,8 +76,7 @@ export default class AgentRebuild extends PMOCommand {
|
|
|
76
76
|
this.error('Not in a proletariat workspace. Run `prlt init` first.');
|
|
77
77
|
}
|
|
78
78
|
this.log(colors.primary(`🔨 Rebuilding agent: ${agentName}\n`));
|
|
79
|
-
const
|
|
80
|
-
const agentDir = path.join(agentsPath, agentName);
|
|
79
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
81
80
|
try {
|
|
82
81
|
this.log(colors.textSecondary(' Building devcontainer...'));
|
|
83
82
|
const buildCommand = [
|
|
@@ -4,7 +4,7 @@ import * as fs from 'node:fs';
|
|
|
4
4
|
import { execSync, spawn } from 'node:child_process';
|
|
5
5
|
import Database from 'better-sqlite3';
|
|
6
6
|
import { colors } from '../../lib/colors.js';
|
|
7
|
-
import { getWorkspaceInfo, getAgentTmuxSessions, formatAgentList } from '../../lib/agents/commands.js';
|
|
7
|
+
import { getWorkspaceInfo, getAgentTmuxSessions, formatAgentList, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
8
8
|
import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
|
|
9
9
|
import { getTerminalApp } from '../../lib/execution/config.js';
|
|
10
10
|
import { isDockerRunning, getAgentContainerName, isContainerRunning, getContainerId, } from '../../lib/execution/runners.js';
|
|
@@ -110,7 +110,7 @@ export default class Shell extends PMOCommand {
|
|
|
110
110
|
// If 'continue', proceed with opening a new shell
|
|
111
111
|
this.log(colors.warning('\nProceeding with new shell - be careful of conflicts!\n'));
|
|
112
112
|
}
|
|
113
|
-
const agentDir =
|
|
113
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
114
114
|
// Check if agent has devcontainer
|
|
115
115
|
const hasDevcontainer = hasDevcontainerConfig(agentDir);
|
|
116
116
|
// In JSON mode with agent name provided, output combined config prompt
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Args } from '@oclif/core';
|
|
2
2
|
import { colors, format } from '../../lib/colors.js';
|
|
3
|
-
import { getWorkspaceInfo, getAgentStatus, getAllAgentsStatus, formatAgentList } from '../../lib/agents/commands.js';
|
|
3
|
+
import { getWorkspaceInfo, getAgentStatus, getAllAgentsStatus, formatAgentList, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
4
4
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
5
5
|
import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
6
6
|
export default class Status extends PMOCommand {
|
|
@@ -83,7 +83,7 @@ export default class Status extends PMOCommand {
|
|
|
83
83
|
name: agentName,
|
|
84
84
|
type: agent.type,
|
|
85
85
|
exists: agentStatus.exists,
|
|
86
|
-
path:
|
|
86
|
+
path: resolveAgentDir(workspaceInfo, agentName),
|
|
87
87
|
branch: agentStatus.branch,
|
|
88
88
|
repositories: agentStatus.repositories.map(r => ({
|
|
89
89
|
name: r.name,
|
|
@@ -107,7 +107,7 @@ export default class Status extends PMOCommand {
|
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
109
109
|
// Location
|
|
110
|
-
this.log(`📍 Location: ${colors.path(
|
|
110
|
+
this.log(`📍 Location: ${colors.path(resolveAgentDir(workspaceInfo, agentName))}`);
|
|
111
111
|
// Branch info
|
|
112
112
|
if (agentStatus.branch) {
|
|
113
113
|
this.log(`🌿 Branch: ${colors.warning(agentStatus.branch)}`);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Args } from '@oclif/core';
|
|
2
2
|
import * as path from 'node:path';
|
|
3
3
|
import { colors } from '../../lib/colors.js';
|
|
4
|
-
import { getWorkspaceInfo, formatAgentList } from '../../lib/agents/commands.js';
|
|
4
|
+
import { getWorkspaceInfo, formatAgentList, resolveAgentDir } from '../../lib/agents/commands.js';
|
|
5
5
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
6
6
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
7
7
|
export default class Visit extends PMOCommand {
|
|
@@ -74,7 +74,7 @@ export default class Visit extends PMOCommand {
|
|
|
74
74
|
return handleError('AGENT_NOT_FOUND', `Agent "${agentName}" not found. Available: ${formatAgentList(workspaceInfo.agents)}`);
|
|
75
75
|
}
|
|
76
76
|
// Calculate path to agent directory
|
|
77
|
-
const agentDir =
|
|
77
|
+
const agentDir = resolveAgentDir(workspaceInfo, agentName);
|
|
78
78
|
const relativePath = path.relative(process.cwd(), agentDir);
|
|
79
79
|
// Display navigation command
|
|
80
80
|
this.log(colors.primary(`🤖 Visiting agent: ${agentName}`));
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
|
+
export default class OrchestratorAttach extends PromptCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
'current-terminal': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
terminal: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
private openInNewTab;
|
|
13
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { execSync } from 'node:child_process';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as os from 'node:os';
|
|
6
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
7
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
8
|
+
import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
9
|
+
import { styles } from '../../lib/styles.js';
|
|
10
|
+
import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
|
|
11
|
+
import { ORCHESTRATOR_SESSION_NAME } from './start.js';
|
|
12
|
+
export default class OrchestratorAttach extends PromptCommand {
|
|
13
|
+
static description = 'Attach to the running orchestrator tmux session';
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> <%= command.id %>',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> --current-terminal',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
...machineOutputFlags,
|
|
20
|
+
'current-terminal': Flags.boolean({
|
|
21
|
+
char: 'c',
|
|
22
|
+
description: 'Attach in current terminal instead of new tab',
|
|
23
|
+
default: false,
|
|
24
|
+
}),
|
|
25
|
+
terminal: Flags.string({
|
|
26
|
+
char: 't',
|
|
27
|
+
description: 'Terminal app to use (iTerm, Terminal, Ghostty)',
|
|
28
|
+
default: 'iTerm',
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
async run() {
|
|
32
|
+
const { flags } = await this.parse(OrchestratorAttach);
|
|
33
|
+
const jsonMode = shouldOutputJson(flags);
|
|
34
|
+
// Check if orchestrator session exists
|
|
35
|
+
const hostSessions = getHostTmuxSessionNames();
|
|
36
|
+
if (!hostSessions.includes(ORCHESTRATOR_SESSION_NAME)) {
|
|
37
|
+
if (jsonMode) {
|
|
38
|
+
outputErrorAsJson('NOT_RUNNING', 'Orchestrator is not running. Start it with: prlt orchestrator start', createMetadata('orchestrator attach', flags));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.log('');
|
|
42
|
+
this.log(styles.warning('Orchestrator is not running.'));
|
|
43
|
+
this.log(styles.muted('Start it with: prlt orchestrator start'));
|
|
44
|
+
this.log('');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (jsonMode) {
|
|
48
|
+
outputSuccessAsJson({
|
|
49
|
+
sessionId: ORCHESTRATOR_SESSION_NAME,
|
|
50
|
+
status: 'attaching',
|
|
51
|
+
}, createMetadata('orchestrator attach', flags));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.log('');
|
|
55
|
+
this.log(styles.info(`Attaching to orchestrator session: ${ORCHESTRATOR_SESSION_NAME}`));
|
|
56
|
+
if (flags['current-terminal']) {
|
|
57
|
+
try {
|
|
58
|
+
execSync(`tmux attach -t "${ORCHESTRATOR_SESSION_NAME}"`, { stdio: 'inherit' });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
this.error(`Failed to attach to orchestrator session "${ORCHESTRATOR_SESSION_NAME}"`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
await this.openInNewTab(flags.terminal);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
async openInNewTab(terminalApp) {
|
|
69
|
+
const title = 'Orchestrator';
|
|
70
|
+
const attachCmd = `tmux attach -t "${ORCHESTRATOR_SESSION_NAME}"`;
|
|
71
|
+
const baseDir = path.join(os.homedir(), '.proletariat', 'scripts');
|
|
72
|
+
fs.mkdirSync(baseDir, { recursive: true });
|
|
73
|
+
const scriptPath = path.join(baseDir, `attach-orch-${Date.now()}.sh`);
|
|
74
|
+
const script = `#!/bin/bash
|
|
75
|
+
# Set terminal tab title
|
|
76
|
+
echo -ne "\\033]0;${title}\\007"
|
|
77
|
+
echo -ne "\\033]1;${title}\\007"
|
|
78
|
+
|
|
79
|
+
echo "Attaching to: ${ORCHESTRATOR_SESSION_NAME}"
|
|
80
|
+
${attachCmd}
|
|
81
|
+
|
|
82
|
+
# Clean up
|
|
83
|
+
rm -f "${scriptPath}"
|
|
84
|
+
exec $SHELL
|
|
85
|
+
`;
|
|
86
|
+
fs.writeFileSync(scriptPath, script, { mode: 0o755 });
|
|
87
|
+
try {
|
|
88
|
+
switch (terminalApp) {
|
|
89
|
+
case 'iTerm':
|
|
90
|
+
execSync(`osascript -e '
|
|
91
|
+
tell application "iTerm"
|
|
92
|
+
activate
|
|
93
|
+
tell current window
|
|
94
|
+
set newTab to (create tab with default profile)
|
|
95
|
+
tell current session of newTab
|
|
96
|
+
set name to "${title}"
|
|
97
|
+
write text "${scriptPath}"
|
|
98
|
+
end tell
|
|
99
|
+
end tell
|
|
100
|
+
end tell
|
|
101
|
+
'`);
|
|
102
|
+
break;
|
|
103
|
+
case 'Ghostty':
|
|
104
|
+
execSync(`osascript -e '
|
|
105
|
+
tell application "Ghostty"
|
|
106
|
+
activate
|
|
107
|
+
end tell
|
|
108
|
+
tell application "System Events"
|
|
109
|
+
tell process "Ghostty"
|
|
110
|
+
keystroke "t" using command down
|
|
111
|
+
delay 0.3
|
|
112
|
+
keystroke "${scriptPath}"
|
|
113
|
+
keystroke return
|
|
114
|
+
end tell
|
|
115
|
+
end tell
|
|
116
|
+
'`);
|
|
117
|
+
break;
|
|
118
|
+
case 'Terminal':
|
|
119
|
+
default:
|
|
120
|
+
execSync(`osascript -e '
|
|
121
|
+
tell application "Terminal"
|
|
122
|
+
activate
|
|
123
|
+
tell application "System Events"
|
|
124
|
+
tell process "Terminal"
|
|
125
|
+
keystroke "t" using command down
|
|
126
|
+
end tell
|
|
127
|
+
end tell
|
|
128
|
+
delay 0.3
|
|
129
|
+
do script "${scriptPath}" in front window
|
|
130
|
+
end tell
|
|
131
|
+
'`);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
this.log(styles.success('Opened new tab and attaching to orchestrator'));
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
this.error(`Failed to open terminal tab: ${error instanceof Error ? error.message : error}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PMOCommand } from '../../lib/pmo/index.js';
|
|
2
|
+
export default class Orchestrator extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
protected getPMOOptions(): {
|
|
11
|
+
promptIfMultiple: boolean;
|
|
12
|
+
};
|
|
13
|
+
execute(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
2
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
3
|
+
export default class Orchestrator extends PMOCommand {
|
|
4
|
+
static description = 'Manage the orchestrator agent (start, attach, status, stop)';
|
|
5
|
+
static examples = [
|
|
6
|
+
'<%= config.bin %> <%= command.id %>',
|
|
7
|
+
'<%= config.bin %> <%= command.id %> start',
|
|
8
|
+
'<%= config.bin %> <%= command.id %> attach',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> status',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> stop',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
...pmoBaseFlags,
|
|
14
|
+
};
|
|
15
|
+
getPMOOptions() {
|
|
16
|
+
return { promptIfMultiple: false };
|
|
17
|
+
}
|
|
18
|
+
async execute() {
|
|
19
|
+
const { flags } = await this.parse(Orchestrator);
|
|
20
|
+
const jsonModeConfig = shouldOutputJson(flags) ? { flags, commandName: 'orchestrator' } : null;
|
|
21
|
+
const { action } = await this.prompt([{
|
|
22
|
+
type: 'list',
|
|
23
|
+
name: 'action',
|
|
24
|
+
message: 'Orchestrator - What would you like to do?',
|
|
25
|
+
choices: [
|
|
26
|
+
{ name: 'Start orchestrator', value: 'start', command: 'prlt orchestrator start --json' },
|
|
27
|
+
{ name: 'Attach to orchestrator', value: 'attach', command: 'prlt orchestrator attach --json' },
|
|
28
|
+
{ name: 'Check orchestrator status', value: 'status', command: 'prlt orchestrator status --json' },
|
|
29
|
+
{ name: 'Stop orchestrator', value: 'stop', command: 'prlt orchestrator stop --json' },
|
|
30
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
31
|
+
],
|
|
32
|
+
}], jsonModeConfig);
|
|
33
|
+
if (action === 'cancel') {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
switch (action) {
|
|
37
|
+
case 'start':
|
|
38
|
+
await this.config.runCommand('orchestrator:start', []);
|
|
39
|
+
break;
|
|
40
|
+
case 'attach':
|
|
41
|
+
await this.config.runCommand('orchestrator:attach', []);
|
|
42
|
+
break;
|
|
43
|
+
case 'status':
|
|
44
|
+
await this.config.runCommand('orchestrator:status', []);
|
|
45
|
+
break;
|
|
46
|
+
case 'stop':
|
|
47
|
+
await this.config.runCommand('orchestrator:stop', []);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
|
+
export declare const ORCHESTRATOR_SESSION_NAME = "prlt-orchestrator-main";
|
|
3
|
+
export default class OrchestratorStart extends PromptCommand {
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
prompt: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
action: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
executor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'skip-permissions': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
sandboxed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
background: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
};
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import * as fs from 'node:fs';
|
|
4
|
+
import Database from 'better-sqlite3';
|
|
5
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
6
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
7
|
+
import { findHQRoot } from '../../lib/workspace.js';
|
|
8
|
+
import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, buildPromptConfig, outputPromptAsJson, } from '../../lib/prompt-json.js';
|
|
9
|
+
import { styles } from '../../lib/styles.js';
|
|
10
|
+
import { DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
|
|
11
|
+
import { runExecution } from '../../lib/execution/runners.js';
|
|
12
|
+
import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
|
|
13
|
+
import { ExecutionStorage } from '../../lib/execution/storage.js';
|
|
14
|
+
import { loadExecutionConfig, getTerminalApp, promptTerminalPreference, getShell, promptShellPreference, hasTerminalPreference, hasShellPreference, } from '../../lib/execution/config.js';
|
|
15
|
+
export const ORCHESTRATOR_SESSION_NAME = 'prlt-orchestrator-main';
|
|
16
|
+
export default class OrchestratorStart extends PromptCommand {
|
|
17
|
+
static description = 'Start the orchestrator agent in a tmux session';
|
|
18
|
+
static examples = [
|
|
19
|
+
'<%= config.bin %> <%= command.id %>',
|
|
20
|
+
'<%= config.bin %> <%= command.id %> --executor codex',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> --skip-permissions',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> --prompt "coordinate all agents on TKT-100"',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> --background',
|
|
24
|
+
];
|
|
25
|
+
static flags = {
|
|
26
|
+
...machineOutputFlags,
|
|
27
|
+
prompt: Flags.string({
|
|
28
|
+
char: 'p',
|
|
29
|
+
description: 'Initial prompt for the orchestrator',
|
|
30
|
+
}),
|
|
31
|
+
action: Flags.string({
|
|
32
|
+
char: 'A',
|
|
33
|
+
description: 'Load an action by name from the actions table (uses its prompt)',
|
|
34
|
+
}),
|
|
35
|
+
executor: Flags.string({
|
|
36
|
+
char: 'e',
|
|
37
|
+
description: 'Executor type',
|
|
38
|
+
options: ['claude-code', 'codex', 'aider', 'custom'],
|
|
39
|
+
}),
|
|
40
|
+
'skip-permissions': Flags.boolean({
|
|
41
|
+
description: 'Run with --dangerously-skip-permissions',
|
|
42
|
+
default: false,
|
|
43
|
+
exclusive: ['sandboxed'],
|
|
44
|
+
}),
|
|
45
|
+
sandboxed: Flags.boolean({
|
|
46
|
+
description: 'Run in sandboxed mode (requires approval for dangerous operations)',
|
|
47
|
+
default: false,
|
|
48
|
+
exclusive: ['skip-permissions'],
|
|
49
|
+
}),
|
|
50
|
+
background: Flags.boolean({
|
|
51
|
+
char: 'b',
|
|
52
|
+
description: 'Start detached (don\'t open terminal tab)',
|
|
53
|
+
default: false,
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
async run() {
|
|
57
|
+
const { flags } = await this.parse(OrchestratorStart);
|
|
58
|
+
const jsonMode = shouldOutputJson(flags);
|
|
59
|
+
// Check if orchestrator is already running
|
|
60
|
+
const hostSessions = getHostTmuxSessionNames();
|
|
61
|
+
if (hostSessions.includes(ORCHESTRATOR_SESSION_NAME)) {
|
|
62
|
+
if (jsonMode) {
|
|
63
|
+
outputErrorAsJson('ALREADY_RUNNING', `Orchestrator is already running (session: ${ORCHESTRATOR_SESSION_NAME}). Use "prlt orchestrator attach" to reattach.`, createMetadata('orchestrator start', flags));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.log('');
|
|
67
|
+
this.log(styles.warning(`Orchestrator is already running (session: ${ORCHESTRATOR_SESSION_NAME})`));
|
|
68
|
+
this.log('');
|
|
69
|
+
const { choice } = await this.prompt([{
|
|
70
|
+
type: 'list',
|
|
71
|
+
name: 'choice',
|
|
72
|
+
message: 'What would you like to do?',
|
|
73
|
+
choices: [
|
|
74
|
+
{ name: 'Attach to running orchestrator', value: 'attach', command: 'prlt orchestrator attach --json' },
|
|
75
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
76
|
+
],
|
|
77
|
+
}], jsonMode ? { flags, commandName: 'orchestrator start' } : null);
|
|
78
|
+
if (choice === 'attach') {
|
|
79
|
+
await this.config.runCommand('orchestrator:attach', []);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Resolve HQ path
|
|
84
|
+
const hqPath = findHQRoot(process.cwd());
|
|
85
|
+
if (!hqPath) {
|
|
86
|
+
if (jsonMode) {
|
|
87
|
+
outputErrorAsJson('NO_HQ', 'Not in an HQ workspace. Run "prlt init" first.', createMetadata('orchestrator start', flags));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.error('Not in an HQ workspace. Run "prlt init" first.');
|
|
91
|
+
}
|
|
92
|
+
// Executor selection
|
|
93
|
+
let selectedExecutor;
|
|
94
|
+
if (flags.executor) {
|
|
95
|
+
selectedExecutor = flags.executor;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
const executorChoices = [
|
|
99
|
+
{ name: 'Claude Code', value: 'claude-code', command: 'prlt orchestrator start --executor claude-code --json' },
|
|
100
|
+
{ name: 'Codex', value: 'codex', command: 'prlt orchestrator start --executor codex --json' },
|
|
101
|
+
{ name: 'Aider', value: 'aider', command: 'prlt orchestrator start --executor aider --json' },
|
|
102
|
+
{ name: 'Custom', value: 'custom', command: 'prlt orchestrator start --executor custom --json' },
|
|
103
|
+
];
|
|
104
|
+
const executorMessage = 'Select executor:';
|
|
105
|
+
if (jsonMode) {
|
|
106
|
+
outputPromptAsJson(buildPromptConfig('list', 'executor', executorMessage, executorChoices), createMetadata('orchestrator start', flags));
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const { executor } = await this.prompt([{
|
|
110
|
+
type: 'list',
|
|
111
|
+
name: 'executor',
|
|
112
|
+
message: executorMessage,
|
|
113
|
+
choices: executorChoices,
|
|
114
|
+
}]);
|
|
115
|
+
selectedExecutor = executor;
|
|
116
|
+
}
|
|
117
|
+
// Permission mode selection
|
|
118
|
+
let sandboxed;
|
|
119
|
+
if (flags['skip-permissions']) {
|
|
120
|
+
sandboxed = false;
|
|
121
|
+
}
|
|
122
|
+
else if (flags.sandboxed) {
|
|
123
|
+
sandboxed = true;
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const permissionChoices = [
|
|
127
|
+
{ name: 'Sandboxed (requires approval for dangerous operations)', value: 'sandboxed', command: 'prlt orchestrator start --sandboxed --json' },
|
|
128
|
+
{ name: 'Accept all (--dangerously-skip-permissions)', value: 'skip', command: 'prlt orchestrator start --skip-permissions --json' },
|
|
129
|
+
];
|
|
130
|
+
const permissionMessage = 'Select permission mode:';
|
|
131
|
+
if (jsonMode) {
|
|
132
|
+
outputPromptAsJson(buildPromptConfig('list', 'permissionMode', permissionMessage, permissionChoices), createMetadata('orchestrator start', flags));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const { permissionMode } = await this.prompt([{
|
|
136
|
+
type: 'list',
|
|
137
|
+
name: 'permissionMode',
|
|
138
|
+
message: permissionMessage,
|
|
139
|
+
choices: permissionChoices,
|
|
140
|
+
}]);
|
|
141
|
+
sandboxed = permissionMode === 'sandboxed';
|
|
142
|
+
}
|
|
143
|
+
// Resolve action prompt
|
|
144
|
+
let actionPrompt = flags.prompt;
|
|
145
|
+
let actionName = 'orchestrate';
|
|
146
|
+
if (flags.action && !actionPrompt) {
|
|
147
|
+
// Load action from DB
|
|
148
|
+
const dbPath = path.join(hqPath, '.proletariat', 'workspace.db');
|
|
149
|
+
if (fs.existsSync(dbPath)) {
|
|
150
|
+
let db = null;
|
|
151
|
+
try {
|
|
152
|
+
db = new Database(dbPath);
|
|
153
|
+
const row = db.prepare('SELECT prompt, name FROM actions WHERE id = ? OR name = ?').get(flags.action, flags.action);
|
|
154
|
+
if (row) {
|
|
155
|
+
actionPrompt = row.prompt;
|
|
156
|
+
actionName = row.name;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
if (jsonMode) {
|
|
160
|
+
outputErrorAsJson('ACTION_NOT_FOUND', `Action "${flags.action}" not found.`, createMetadata('orchestrator start', flags));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
this.error(`Action "${flags.action}" not found.`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
db?.close();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Build execution context
|
|
172
|
+
// Use ticketId='prlt', actionName='orchestrator', agentName='main'
|
|
173
|
+
// so buildSessionName produces 'prlt-orchestrator-main'
|
|
174
|
+
const context = {
|
|
175
|
+
ticketId: 'prlt',
|
|
176
|
+
ticketTitle: 'Orchestrator',
|
|
177
|
+
agentName: 'main',
|
|
178
|
+
agentDir: hqPath,
|
|
179
|
+
worktreePath: hqPath,
|
|
180
|
+
branch: 'main',
|
|
181
|
+
actionName: 'orchestrator',
|
|
182
|
+
actionPrompt,
|
|
183
|
+
modifiesCode: false,
|
|
184
|
+
hqPath,
|
|
185
|
+
};
|
|
186
|
+
// Build execution config
|
|
187
|
+
const executionConfig = { ...DEFAULT_EXECUTION_CONFIG };
|
|
188
|
+
executionConfig.outputMode = 'interactive';
|
|
189
|
+
executionConfig.sandboxed = sandboxed;
|
|
190
|
+
// Load saved preferences from workspace DB
|
|
191
|
+
const dbPath = path.join(hqPath, '.proletariat', 'workspace.db');
|
|
192
|
+
let db = null;
|
|
193
|
+
try {
|
|
194
|
+
if (fs.existsSync(dbPath)) {
|
|
195
|
+
db = new Database(dbPath);
|
|
196
|
+
const savedConfig = loadExecutionConfig(db);
|
|
197
|
+
executionConfig.terminal = savedConfig.terminal;
|
|
198
|
+
executionConfig.shell = savedConfig.shell;
|
|
199
|
+
executionConfig.tmux = savedConfig.tmux;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// Ignore config loading errors, use defaults
|
|
204
|
+
}
|
|
205
|
+
// If no terminal preference saved, prompt for it (first run only)
|
|
206
|
+
if (!jsonMode && db) {
|
|
207
|
+
if (!hasTerminalPreference(db)) {
|
|
208
|
+
executionConfig.terminal.app = await promptTerminalPreference(db);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
executionConfig.terminal.app = await getTerminalApp(db);
|
|
212
|
+
}
|
|
213
|
+
if (!hasShellPreference(db)) {
|
|
214
|
+
executionConfig.shell = await promptShellPreference(db);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
executionConfig.shell = await getShell(db);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Show what we're doing
|
|
221
|
+
if (!jsonMode) {
|
|
222
|
+
this.log('');
|
|
223
|
+
this.log(styles.muted(` Starting orchestrator...`));
|
|
224
|
+
this.log(styles.muted(` Executor: ${selectedExecutor}`));
|
|
225
|
+
this.log(styles.muted(` Permission mode: ${sandboxed ? 'sandboxed' : 'skip-permissions'}`));
|
|
226
|
+
this.log(styles.muted(` Directory: ${hqPath}`));
|
|
227
|
+
if (actionPrompt) {
|
|
228
|
+
this.log(styles.muted(` Prompt: "${actionPrompt.substring(0, 60)}${actionPrompt.length > 60 ? '...' : ''}"`));
|
|
229
|
+
}
|
|
230
|
+
this.log('');
|
|
231
|
+
}
|
|
232
|
+
// Launch orchestrator
|
|
233
|
+
const displayMode = flags.background ? 'background' : 'terminal';
|
|
234
|
+
const result = await runExecution('host', context, selectedExecutor, executionConfig, {
|
|
235
|
+
displayMode,
|
|
236
|
+
});
|
|
237
|
+
if (result.success) {
|
|
238
|
+
// Create execution record so `prlt session poke orchestrator "message"` works
|
|
239
|
+
if (db) {
|
|
240
|
+
try {
|
|
241
|
+
const executionStorage = new ExecutionStorage(db);
|
|
242
|
+
executionStorage.createExecution({
|
|
243
|
+
ticketId: 'ORCH',
|
|
244
|
+
agentName: 'orchestrator',
|
|
245
|
+
executor: selectedExecutor,
|
|
246
|
+
environment: 'host',
|
|
247
|
+
displayMode,
|
|
248
|
+
sandboxed,
|
|
249
|
+
sessionId: result.sessionId || ORCHESTRATOR_SESSION_NAME,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Non-fatal: poke won't work but orchestrator is running
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
if (jsonMode) {
|
|
257
|
+
outputSuccessAsJson({
|
|
258
|
+
sessionId: result.sessionId || ORCHESTRATOR_SESSION_NAME,
|
|
259
|
+
executor: selectedExecutor,
|
|
260
|
+
sandboxed,
|
|
261
|
+
displayMode,
|
|
262
|
+
directory: hqPath,
|
|
263
|
+
}, createMetadata('orchestrator start', flags));
|
|
264
|
+
}
|
|
265
|
+
if (flags.background) {
|
|
266
|
+
this.log(styles.success(`Orchestrator started in background`));
|
|
267
|
+
this.log(styles.muted(` Session: ${result.sessionId || ORCHESTRATOR_SESSION_NAME}`));
|
|
268
|
+
this.log(styles.muted(` Attach with: prlt orchestrator attach`));
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
this.log(styles.success(`Orchestrator started`));
|
|
272
|
+
if (result.sessionId) {
|
|
273
|
+
this.log(styles.muted(` Session: ${result.sessionId}`));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
if (jsonMode) {
|
|
279
|
+
outputErrorAsJson('EXECUTION_FAILED', `Failed to start orchestrator: ${result.error}`, createMetadata('orchestrator start', flags));
|
|
280
|
+
}
|
|
281
|
+
this.error(`Failed to start orchestrator: ${result.error}`);
|
|
282
|
+
}
|
|
283
|
+
if (db) {
|
|
284
|
+
db.close();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
|
+
export default class OrchestratorStatus extends PromptCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
peek: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
lines: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|