@proletariat/cli 0.3.43 → 0.3.44
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/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/oclif.manifest.json +4581 -4284
- package/package.json +1 -1
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Flags } from '@oclif/core';
|
|
2
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
3
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
4
|
+
import { shouldOutputJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
5
|
+
import { styles } from '../../lib/styles.js';
|
|
6
|
+
import { getHostTmuxSessionNames, captureTmuxPane } from '../../lib/execution/session-utils.js';
|
|
7
|
+
import { ORCHESTRATOR_SESSION_NAME } from './start.js';
|
|
8
|
+
export default class OrchestratorStatus extends PromptCommand {
|
|
9
|
+
static description = 'Check if the orchestrator is running';
|
|
10
|
+
static examples = [
|
|
11
|
+
'<%= config.bin %> <%= command.id %>',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> --peek',
|
|
13
|
+
'<%= config.bin %> <%= command.id %> --peek --lines 50',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
...machineOutputFlags,
|
|
17
|
+
peek: Flags.boolean({
|
|
18
|
+
description: 'Show recent output from the orchestrator',
|
|
19
|
+
default: false,
|
|
20
|
+
}),
|
|
21
|
+
lines: Flags.integer({
|
|
22
|
+
description: 'Number of lines to show when peeking',
|
|
23
|
+
default: 20,
|
|
24
|
+
}),
|
|
25
|
+
};
|
|
26
|
+
async run() {
|
|
27
|
+
const { flags } = await this.parse(OrchestratorStatus);
|
|
28
|
+
const jsonMode = shouldOutputJson(flags);
|
|
29
|
+
const hostSessions = getHostTmuxSessionNames();
|
|
30
|
+
const isRunning = hostSessions.includes(ORCHESTRATOR_SESSION_NAME);
|
|
31
|
+
let recentOutput = null;
|
|
32
|
+
if (isRunning && flags.peek) {
|
|
33
|
+
recentOutput = captureTmuxPane(ORCHESTRATOR_SESSION_NAME, flags.lines);
|
|
34
|
+
}
|
|
35
|
+
if (jsonMode) {
|
|
36
|
+
outputSuccessAsJson({
|
|
37
|
+
running: isRunning,
|
|
38
|
+
sessionId: isRunning ? ORCHESTRATOR_SESSION_NAME : null,
|
|
39
|
+
...(recentOutput !== null && { recentOutput }),
|
|
40
|
+
}, createMetadata('orchestrator status', flags));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
this.log('');
|
|
44
|
+
if (isRunning) {
|
|
45
|
+
this.log(styles.success(`Orchestrator is running`));
|
|
46
|
+
this.log(styles.muted(` Session: ${ORCHESTRATOR_SESSION_NAME}`));
|
|
47
|
+
this.log(styles.muted(` Attach: prlt orchestrator attach`));
|
|
48
|
+
this.log(styles.muted(` Poke: prlt session poke orchestrator "message"`));
|
|
49
|
+
if (recentOutput) {
|
|
50
|
+
this.log('');
|
|
51
|
+
this.log(styles.header('Recent output:'));
|
|
52
|
+
this.log(styles.muted('─'.repeat(60)));
|
|
53
|
+
this.log(recentOutput);
|
|
54
|
+
this.log(styles.muted('─'.repeat(60)));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
this.log(styles.muted('Orchestrator is not running.'));
|
|
59
|
+
this.log(styles.muted('Start it with: prlt orchestrator start'));
|
|
60
|
+
}
|
|
61
|
+
this.log('');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
|
+
export default class OrchestratorStop extends PromptCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|