@proletariat/cli 0.3.47 → 0.3.49
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/caffeinate/index.d.ts +10 -0
- package/dist/commands/caffeinate/index.js +64 -0
- package/dist/commands/caffeinate/start.d.ts +14 -0
- package/dist/commands/caffeinate/start.js +86 -0
- package/dist/commands/caffeinate/status.d.ts +10 -0
- package/dist/commands/caffeinate/status.js +55 -0
- package/dist/commands/caffeinate/stop.d.ts +10 -0
- package/dist/commands/caffeinate/stop.js +47 -0
- package/dist/commands/claude/index.js +21 -21
- package/dist/commands/claude/open.js +1 -1
- package/dist/commands/commit.js +10 -8
- package/dist/commands/config/index.js +4 -5
- package/dist/commands/execution/config.d.ts +2 -2
- package/dist/commands/execution/config.js +18 -18
- package/dist/commands/execution/list.js +2 -2
- package/dist/commands/execution/view.js +2 -2
- package/dist/commands/init.js +9 -1
- package/dist/commands/orchestrator/attach.js +64 -14
- package/dist/commands/orchestrator/start.d.ts +5 -5
- package/dist/commands/orchestrator/start.js +45 -35
- package/dist/commands/orchestrator/status.js +64 -23
- package/dist/commands/orchestrator/stop.js +44 -12
- package/dist/commands/qa/index.js +12 -12
- package/dist/commands/session/attach.js +23 -0
- package/dist/commands/session/poke.js +1 -1
- package/dist/commands/staff/add.js +1 -1
- package/dist/commands/work/index.js +4 -0
- package/dist/commands/work/linear.d.ts +24 -0
- package/dist/commands/work/linear.js +218 -0
- package/dist/commands/work/revise.js +8 -8
- package/dist/commands/work/spawn.js +29 -20
- package/dist/commands/work/start.js +22 -12
- package/dist/commands/work/watch.js +3 -3
- package/dist/hooks/init.js +8 -0
- package/dist/lib/agents/index.js +2 -2
- package/dist/lib/caffeinate.d.ts +64 -0
- package/dist/lib/caffeinate.js +146 -0
- package/dist/lib/database/drizzle-schema.d.ts +7 -7
- package/dist/lib/database/drizzle-schema.js +1 -1
- package/dist/lib/execution/codex-adapter.d.ts +96 -0
- package/dist/lib/execution/codex-adapter.js +148 -0
- package/dist/lib/execution/config.d.ts +6 -6
- package/dist/lib/execution/config.js +17 -10
- package/dist/lib/execution/devcontainer.d.ts +3 -3
- package/dist/lib/execution/devcontainer.js +3 -3
- package/dist/lib/execution/index.d.ts +1 -0
- package/dist/lib/execution/index.js +1 -0
- package/dist/lib/execution/runners.d.ts +2 -2
- package/dist/lib/execution/runners.js +69 -26
- package/dist/lib/execution/spawner.js +3 -3
- package/dist/lib/execution/storage.d.ts +2 -2
- package/dist/lib/execution/storage.js +3 -3
- package/dist/lib/execution/types.d.ts +2 -2
- package/dist/lib/execution/types.js +1 -1
- package/dist/lib/external-issues/index.d.ts +1 -1
- package/dist/lib/external-issues/index.js +1 -1
- package/dist/lib/external-issues/linear.d.ts +43 -0
- package/dist/lib/external-issues/linear.js +261 -0
- package/dist/lib/external-issues/types.d.ts +67 -0
- package/dist/lib/external-issues/types.js +41 -0
- package/dist/lib/init/index.d.ts +4 -0
- package/dist/lib/init/index.js +11 -1
- package/dist/lib/machine-config.d.ts +1 -0
- package/dist/lib/machine-config.js +6 -3
- package/dist/lib/pmo/schema.d.ts +1 -1
- package/dist/lib/pmo/schema.js +1 -1
- package/dist/lib/pmo/storage/actions.js +3 -3
- package/dist/lib/pmo/storage/base.js +116 -6
- package/dist/lib/pmo/storage/epics.js +1 -1
- package/dist/lib/pmo/storage/tickets.js +2 -2
- package/dist/lib/pmo/storage/types.d.ts +2 -1
- package/dist/lib/repos/index.js +1 -1
- package/oclif.manifest.json +3052 -2721
- package/package.json +1 -1
|
@@ -93,7 +93,7 @@ export default class ExecutionView extends PMOCommand {
|
|
|
93
93
|
executor: execution.executor,
|
|
94
94
|
environment: execution.environment,
|
|
95
95
|
displayMode: execution.displayMode,
|
|
96
|
-
|
|
96
|
+
permissionMode: execution.permissionMode,
|
|
97
97
|
status: execution.status,
|
|
98
98
|
branch: execution.branch || null,
|
|
99
99
|
pid: execution.pid || null,
|
|
@@ -124,7 +124,7 @@ export default class ExecutionView extends PMOCommand {
|
|
|
124
124
|
const envIcon = getEnvironmentIcon(execution.environment);
|
|
125
125
|
this.log(`${styles.muted('Type:')} ${envIcon} ${execution.environment}`);
|
|
126
126
|
this.log(`${styles.muted('Display:')} ${execution.displayMode}`);
|
|
127
|
-
this.log(`${styles.muted('Permissions:')} ${execution.
|
|
127
|
+
this.log(`${styles.muted('Permissions:')} ${execution.permissionMode === 'safe' ? styles.success('safe') : styles.warning('danger')}`);
|
|
128
128
|
if (execution.branch) {
|
|
129
129
|
this.log(`${styles.muted('Branch:')} ${execution.branch}`);
|
|
130
130
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Command, Flags } from '@oclif/core';
|
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
|
-
import { promptForHQName, promptForHQLocation, initializeHQ, showNextSteps, validateHQLocation } from '../lib/init/index.js';
|
|
5
|
+
import { promptForHQName, promptForHQLocation, initializeHQ, showNextSteps, validateHQLocation, isHQNameTaken, } from '../lib/init/index.js';
|
|
6
6
|
import { promptForAgentsWithTheme } from '../lib/agents/index.js';
|
|
7
7
|
import { promptForRepositories } from '../lib/repos/index.js';
|
|
8
8
|
import { promptForPMOSetup, machineOutputFlags } from '../lib/pmo/index.js';
|
|
@@ -90,6 +90,14 @@ export default class Init extends Command {
|
|
|
90
90
|
outputPromptAsJson(buildPromptConfig('input', 'name', 'Enter a name for your headquarters:', undefined, undefined), createMetadata('init', flags));
|
|
91
91
|
}
|
|
92
92
|
const hqName = flags.name;
|
|
93
|
+
// Check if HQ name is already in use
|
|
94
|
+
if (hqName && isHQNameTaken(hqName)) {
|
|
95
|
+
this.outputJson({
|
|
96
|
+
success: false,
|
|
97
|
+
error: `HQ name "${hqName}" is already in use on this machine. Pick another name.`,
|
|
98
|
+
});
|
|
99
|
+
this.exit(1);
|
|
100
|
+
}
|
|
93
101
|
const hqPath = flags.path || path.resolve(`./${hqName}-hq`);
|
|
94
102
|
// Validate HQ path is not inside a git repo
|
|
95
103
|
if (!validateHQLocation(hqPath)) {
|
|
@@ -10,8 +10,10 @@ import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadat
|
|
|
10
10
|
import { styles } from '../../lib/styles.js';
|
|
11
11
|
import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
|
|
12
12
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
13
|
+
import { findHQRoot } from '../../lib/workspace.js';
|
|
14
|
+
import { getHeadquartersNameFromPath } from '../../lib/machine-config.js';
|
|
13
15
|
import { loadExecutionConfig, shouldUseControlMode, buildTmuxAttachCommand } from '../../lib/execution/index.js';
|
|
14
|
-
import { buildOrchestratorSessionName } from './start.js';
|
|
16
|
+
import { buildOrchestratorSessionName, findRunningOrchestratorSessions } from './start.js';
|
|
15
17
|
/**
|
|
16
18
|
* Detect the terminal emulator from environment variables.
|
|
17
19
|
* Returns a terminal app name suitable for AppleScript tab creation,
|
|
@@ -27,8 +29,6 @@ export function detectTerminalApp() {
|
|
|
27
29
|
return null;
|
|
28
30
|
}
|
|
29
31
|
const termProgram = process.env.TERM_PROGRAM;
|
|
30
|
-
if (!termProgram)
|
|
31
|
-
return null;
|
|
32
32
|
switch (termProgram) {
|
|
33
33
|
case 'iTerm.app':
|
|
34
34
|
return 'iTerm';
|
|
@@ -38,9 +38,13 @@ export function detectTerminalApp() {
|
|
|
38
38
|
return 'Terminal';
|
|
39
39
|
case 'WezTerm':
|
|
40
40
|
return 'WezTerm';
|
|
41
|
-
default:
|
|
42
|
-
return null;
|
|
43
41
|
}
|
|
42
|
+
// TERM_PROGRAM is overwritten to 'tmux' inside tmux sessions.
|
|
43
|
+
// Fall back to vars that persist through tmux to detect the outer terminal.
|
|
44
|
+
if (process.env.LC_TERMINAL === 'iTerm2' || process.env.ITERM_SESSION_ID) {
|
|
45
|
+
return 'iTerm';
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
44
48
|
}
|
|
45
49
|
export default class OrchestratorAttach extends PromptCommand {
|
|
46
50
|
static description = 'Attach to the running orchestrator tmux session';
|
|
@@ -73,18 +77,50 @@ export default class OrchestratorAttach extends PromptCommand {
|
|
|
73
77
|
async run() {
|
|
74
78
|
const { flags } = await this.parse(OrchestratorAttach);
|
|
75
79
|
const jsonMode = shouldOutputJson(flags);
|
|
76
|
-
const sessionName = buildOrchestratorSessionName(flags.name || 'main');
|
|
77
|
-
// Check if orchestrator session exists
|
|
78
80
|
const hostSessions = getHostTmuxSessionNames();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
// Resolve session name: try HQ-scoped first, fall back to discovery
|
|
82
|
+
let sessionName;
|
|
83
|
+
const hqPath = findHQRoot(process.cwd());
|
|
84
|
+
if (hqPath) {
|
|
85
|
+
const hqName = getHeadquartersNameFromPath(hqPath);
|
|
86
|
+
sessionName = buildOrchestratorSessionName(hqName, flags.name || 'main');
|
|
87
|
+
if (!hostSessions.includes(sessionName)) {
|
|
88
|
+
sessionName = undefined; // Not running for this HQ
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// If not in HQ or session not found, discover running orchestrator sessions
|
|
92
|
+
if (!sessionName) {
|
|
93
|
+
const runningSessions = findRunningOrchestratorSessions(hostSessions);
|
|
94
|
+
if (runningSessions.length === 0) {
|
|
95
|
+
if (jsonMode) {
|
|
96
|
+
outputErrorAsJson('NOT_RUNNING', 'Orchestrator is not running. Start it with: prlt orchestrator start', createMetadata('orchestrator attach', flags));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.log('');
|
|
100
|
+
this.log(styles.warning('Orchestrator is not running.'));
|
|
101
|
+
this.log(styles.muted('Start it with: prlt orchestrator start'));
|
|
102
|
+
this.log('');
|
|
82
103
|
return;
|
|
83
104
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
105
|
+
else if (runningSessions.length === 1) {
|
|
106
|
+
sessionName = runningSessions[0];
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
// Multiple sessions — let user pick
|
|
110
|
+
const { session } = await this.prompt([{
|
|
111
|
+
type: 'list',
|
|
112
|
+
name: 'session',
|
|
113
|
+
message: 'Multiple orchestrator sessions found. Select one to attach:',
|
|
114
|
+
choices: runningSessions.map(s => ({
|
|
115
|
+
name: s,
|
|
116
|
+
value: s,
|
|
117
|
+
command: `prlt orchestrator attach --name "${s}" --json`,
|
|
118
|
+
})),
|
|
119
|
+
}], jsonMode ? { flags, commandName: 'orchestrator attach' } : null);
|
|
120
|
+
sessionName = session;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
if (!sessionName) {
|
|
88
124
|
return;
|
|
89
125
|
}
|
|
90
126
|
if (jsonMode) {
|
|
@@ -141,6 +177,16 @@ export default class OrchestratorAttach extends PromptCommand {
|
|
|
141
177
|
}
|
|
142
178
|
attachInCurrentTerminal(useControlMode, sessionName) {
|
|
143
179
|
try {
|
|
180
|
+
// Set mouse mode based on attach type:
|
|
181
|
+
// - Plain terminal: mouse on (enables scroll in tmux; hold Shift/Option to bypass)
|
|
182
|
+
// - iTerm -CC: mouse off (iTerm handles scrolling natively)
|
|
183
|
+
const mouseMode = useControlMode ? 'off' : 'on';
|
|
184
|
+
try {
|
|
185
|
+
execSync(`tmux set-option -t "${sessionName}" mouse ${mouseMode}`, { stdio: 'pipe' });
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Non-fatal: mouse mode is a convenience, don't block attach
|
|
189
|
+
}
|
|
144
190
|
const tmuxAttach = buildTmuxAttachCommand(useControlMode);
|
|
145
191
|
execSync(`${tmuxAttach} -t "${sessionName}"`, { stdio: 'inherit' });
|
|
146
192
|
}
|
|
@@ -152,6 +198,7 @@ export default class OrchestratorAttach extends PromptCommand {
|
|
|
152
198
|
const title = 'Orchestrator';
|
|
153
199
|
const tmuxAttach = buildTmuxAttachCommand(useControlMode);
|
|
154
200
|
const attachCmd = `${tmuxAttach} -t "${sessionName}"`;
|
|
201
|
+
const mouseMode = useControlMode ? 'off' : 'on';
|
|
155
202
|
const baseDir = path.join(os.homedir(), '.proletariat', 'scripts');
|
|
156
203
|
fs.mkdirSync(baseDir, { recursive: true });
|
|
157
204
|
const scriptPath = path.join(baseDir, `attach-orch-${Date.now()}.sh`);
|
|
@@ -160,6 +207,9 @@ export default class OrchestratorAttach extends PromptCommand {
|
|
|
160
207
|
echo -ne "\\033]0;${title}\\007"
|
|
161
208
|
echo -ne "\\033]1;${title}\\007"
|
|
162
209
|
|
|
210
|
+
# Set mouse mode before attaching
|
|
211
|
+
tmux set-option -t "${sessionName}" mouse ${mouseMode} 2>/dev/null || true
|
|
212
|
+
|
|
163
213
|
echo "Attaching to: ${sessionName}"
|
|
164
214
|
${attachCmd}
|
|
165
215
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
2
2
|
/**
|
|
3
|
-
* Build orchestrator tmux session name.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* Build orchestrator tmux session name scoped to the HQ workspace.
|
|
4
|
+
* Format: 'prlt-orchestrator-{hqName}-{name}'
|
|
5
|
+
* Example: 'prlt-orchestrator-proletariat-main'
|
|
6
6
|
*/
|
|
7
|
-
export declare function buildOrchestratorSessionName(name?: string): string;
|
|
7
|
+
export declare function buildOrchestratorSessionName(hqName: string, name?: string): string;
|
|
8
8
|
/**
|
|
9
9
|
* Find running orchestrator session(s) by prefix match.
|
|
10
10
|
* Returns all tmux session names that start with 'prlt-orchestrator-'.
|
|
@@ -18,7 +18,7 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
18
18
|
action: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
19
19
|
executor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
20
20
|
'skip-permissions': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
21
|
-
|
|
21
|
+
'permission-mode': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
22
22
|
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
23
23
|
background: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
24
24
|
foreground: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -5,6 +5,7 @@ import Database from 'better-sqlite3';
|
|
|
5
5
|
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
6
6
|
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
7
7
|
import { findHQRoot } from '../../lib/workspace.js';
|
|
8
|
+
import { getHeadquartersNameFromPath } from '../../lib/machine-config.js';
|
|
8
9
|
import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, buildPromptConfig, outputPromptAsJson, } from '../../lib/prompt-json.js';
|
|
9
10
|
import { styles } from '../../lib/styles.js';
|
|
10
11
|
import { DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
|
|
@@ -13,16 +14,23 @@ import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
|
|
|
13
14
|
import { ExecutionStorage } from '../../lib/execution/storage.js';
|
|
14
15
|
import { loadExecutionConfig, getTerminalApp, getShell, detectShell, detectTerminalApp, } from '../../lib/execution/config.js';
|
|
15
16
|
/**
|
|
16
|
-
*
|
|
17
|
-
* Default: 'prlt-orchestrator-main'
|
|
18
|
-
* With --name: 'prlt-orchestrator-{name}'
|
|
17
|
+
* Sanitize a name segment for use in tmux session names.
|
|
19
18
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
function sanitizeName(name) {
|
|
20
|
+
return name
|
|
22
21
|
.replace(/[^a-zA-Z0-9._-]/g, '-')
|
|
23
22
|
.replace(/-+/g, '-')
|
|
24
23
|
.replace(/^-|-$/g, '');
|
|
25
|
-
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build orchestrator tmux session name scoped to the HQ workspace.
|
|
27
|
+
* Format: 'prlt-orchestrator-{hqName}-{name}'
|
|
28
|
+
* Example: 'prlt-orchestrator-proletariat-main'
|
|
29
|
+
*/
|
|
30
|
+
export function buildOrchestratorSessionName(hqName, name = 'main') {
|
|
31
|
+
const safeHqName = sanitizeName(hqName) || 'default';
|
|
32
|
+
const safeName = sanitizeName(name) || 'main';
|
|
33
|
+
return `prlt-orchestrator-${safeHqName}-${safeName}`;
|
|
26
34
|
}
|
|
27
35
|
/**
|
|
28
36
|
* Find running orchestrator session(s) by prefix match.
|
|
@@ -36,7 +44,7 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
36
44
|
static examples = [
|
|
37
45
|
'<%= config.bin %> <%= command.id %>',
|
|
38
46
|
'<%= config.bin %> <%= command.id %> --executor codex',
|
|
39
|
-
'<%= config.bin %> <%= command.id %> --
|
|
47
|
+
'<%= config.bin %> <%= command.id %> --permission-mode danger',
|
|
40
48
|
'<%= config.bin %> <%= command.id %> --prompt "coordinate all agents on TKT-100"',
|
|
41
49
|
'<%= config.bin %> <%= command.id %> --background',
|
|
42
50
|
];
|
|
@@ -56,13 +64,13 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
56
64
|
options: ['claude-code', 'codex', 'aider', 'custom'],
|
|
57
65
|
}),
|
|
58
66
|
'skip-permissions': Flags.boolean({
|
|
59
|
-
description: '
|
|
67
|
+
description: 'Skip permission checks (shorthand for --permission-mode danger)',
|
|
60
68
|
default: false,
|
|
61
|
-
exclusive: ['
|
|
69
|
+
exclusive: ['permission-mode'],
|
|
62
70
|
}),
|
|
63
|
-
|
|
64
|
-
description: '
|
|
65
|
-
|
|
71
|
+
'permission-mode': Flags.string({
|
|
72
|
+
description: 'Permission mode for the orchestrator (danger=skip checks, safe=require approval)',
|
|
73
|
+
options: ['danger', 'safe'],
|
|
66
74
|
exclusive: ['skip-permissions'],
|
|
67
75
|
}),
|
|
68
76
|
name: Flags.string({
|
|
@@ -86,7 +94,18 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
86
94
|
const { flags } = await this.parse(OrchestratorStart);
|
|
87
95
|
const jsonMode = shouldOutputJson(flags);
|
|
88
96
|
const orchestratorName = flags.name || 'main';
|
|
89
|
-
|
|
97
|
+
// Resolve HQ path first (needed for scoped session name)
|
|
98
|
+
const hqPath = findHQRoot(process.cwd());
|
|
99
|
+
if (!hqPath) {
|
|
100
|
+
if (jsonMode) {
|
|
101
|
+
outputErrorAsJson('NO_HQ', 'Not in an HQ workspace. Run "prlt init" first.', createMetadata('orchestrator start', flags));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.error('Not in an HQ workspace. Run "prlt init" first.');
|
|
105
|
+
}
|
|
106
|
+
// Build session name scoped to this HQ
|
|
107
|
+
const hqName = getHeadquartersNameFromPath(hqPath);
|
|
108
|
+
const sessionName = buildOrchestratorSessionName(hqName, orchestratorName);
|
|
90
109
|
// Check if orchestrator is already running
|
|
91
110
|
const hostSessions = getHostTmuxSessionNames();
|
|
92
111
|
if (hostSessions.includes(sessionName)) {
|
|
@@ -112,15 +131,6 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
112
131
|
}
|
|
113
132
|
return;
|
|
114
133
|
}
|
|
115
|
-
// Resolve HQ path
|
|
116
|
-
const hqPath = findHQRoot(process.cwd());
|
|
117
|
-
if (!hqPath) {
|
|
118
|
-
if (jsonMode) {
|
|
119
|
-
outputErrorAsJson('NO_HQ', 'Not in an HQ workspace. Run "prlt init" first.', createMetadata('orchestrator start', flags));
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
this.error('Not in an HQ workspace. Run "prlt init" first.');
|
|
123
|
-
}
|
|
124
134
|
// Executor selection
|
|
125
135
|
let selectedExecutor;
|
|
126
136
|
if (flags.executor) {
|
|
@@ -147,30 +157,30 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
147
157
|
selectedExecutor = executor;
|
|
148
158
|
}
|
|
149
159
|
// Permission mode selection
|
|
150
|
-
let
|
|
160
|
+
let permissionMode;
|
|
151
161
|
if (flags['skip-permissions']) {
|
|
152
|
-
|
|
162
|
+
permissionMode = 'danger';
|
|
153
163
|
}
|
|
154
|
-
else if (flags
|
|
155
|
-
|
|
164
|
+
else if (flags['permission-mode']) {
|
|
165
|
+
permissionMode = flags['permission-mode'];
|
|
156
166
|
}
|
|
157
167
|
else {
|
|
158
168
|
const permissionChoices = [
|
|
159
|
-
{ name: '
|
|
160
|
-
{ name: '
|
|
169
|
+
{ name: '⚠️ danger - Skip permission checks (faster)', value: 'danger', command: 'prlt orchestrator start --permission-mode danger --json' },
|
|
170
|
+
{ name: '🔒 safe - Requires approval for dangerous operations', value: 'safe', command: 'prlt orchestrator start --permission-mode safe --json' },
|
|
161
171
|
];
|
|
162
|
-
const permissionMessage = '
|
|
172
|
+
const permissionMessage = 'Permission mode:';
|
|
163
173
|
if (jsonMode) {
|
|
164
174
|
outputPromptAsJson(buildPromptConfig('list', 'permissionMode', permissionMessage, permissionChoices), createMetadata('orchestrator start', flags));
|
|
165
175
|
return;
|
|
166
176
|
}
|
|
167
|
-
const { permissionMode } = await this.prompt([{
|
|
177
|
+
const { permissionMode: selectedMode } = await this.prompt([{
|
|
168
178
|
type: 'list',
|
|
169
179
|
name: 'permissionMode',
|
|
170
180
|
message: permissionMessage,
|
|
171
181
|
choices: permissionChoices,
|
|
172
182
|
}]);
|
|
173
|
-
|
|
183
|
+
permissionMode = selectedMode;
|
|
174
184
|
}
|
|
175
185
|
// Resolve action prompt
|
|
176
186
|
let actionPrompt = flags.prompt;
|
|
@@ -218,7 +228,7 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
218
228
|
// Build execution config
|
|
219
229
|
const executionConfig = { ...DEFAULT_EXECUTION_CONFIG };
|
|
220
230
|
executionConfig.outputMode = 'interactive';
|
|
221
|
-
executionConfig.
|
|
231
|
+
executionConfig.permissionMode = permissionMode;
|
|
222
232
|
// Load saved preferences from workspace DB
|
|
223
233
|
const dbPath = path.join(hqPath, '.proletariat', 'workspace.db');
|
|
224
234
|
let db = null;
|
|
@@ -289,7 +299,7 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
289
299
|
this.log('');
|
|
290
300
|
this.log(styles.muted(` Starting orchestrator...`));
|
|
291
301
|
this.log(styles.muted(` Executor: ${selectedExecutor}`));
|
|
292
|
-
this.log(styles.muted(` Permission mode: ${
|
|
302
|
+
this.log(styles.muted(` Permission mode: ${permissionMode}`));
|
|
293
303
|
this.log(styles.muted(` Display mode: ${displayMode}`));
|
|
294
304
|
this.log(styles.muted(` Directory: ${hqPath}`));
|
|
295
305
|
if (orchestratorName !== 'main') {
|
|
@@ -315,7 +325,7 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
315
325
|
executor: selectedExecutor,
|
|
316
326
|
environment: 'host',
|
|
317
327
|
displayMode,
|
|
318
|
-
|
|
328
|
+
permissionMode,
|
|
319
329
|
sessionId: result.sessionId || sessionName,
|
|
320
330
|
});
|
|
321
331
|
}
|
|
@@ -327,7 +337,7 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
327
337
|
outputSuccessAsJson({
|
|
328
338
|
sessionId: result.sessionId || sessionName,
|
|
329
339
|
executor: selectedExecutor,
|
|
330
|
-
|
|
340
|
+
permissionMode,
|
|
331
341
|
displayMode,
|
|
332
342
|
directory: hqPath,
|
|
333
343
|
name: orchestratorName,
|
|
@@ -4,7 +4,9 @@ import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
|
4
4
|
import { shouldOutputJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
5
5
|
import { styles } from '../../lib/styles.js';
|
|
6
6
|
import { getHostTmuxSessionNames, captureTmuxPane } from '../../lib/execution/session-utils.js';
|
|
7
|
-
import {
|
|
7
|
+
import { findHQRoot } from '../../lib/workspace.js';
|
|
8
|
+
import { getHeadquartersNameFromPath } from '../../lib/machine-config.js';
|
|
9
|
+
import { buildOrchestratorSessionName, findRunningOrchestratorSessions } from './start.js';
|
|
8
10
|
export default class OrchestratorStatus extends PromptCommand {
|
|
9
11
|
static description = 'Check if the orchestrator is running';
|
|
10
12
|
static examples = [
|
|
@@ -30,38 +32,77 @@ export default class OrchestratorStatus extends PromptCommand {
|
|
|
30
32
|
async run() {
|
|
31
33
|
const { flags } = await this.parse(OrchestratorStatus);
|
|
32
34
|
const jsonMode = shouldOutputJson(flags);
|
|
33
|
-
const sessionName = buildOrchestratorSessionName(flags.name || 'main');
|
|
34
35
|
const hostSessions = getHostTmuxSessionNames();
|
|
35
|
-
|
|
36
|
-
let
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// Resolve session name: try HQ-scoped first, fall back to discovery
|
|
37
|
+
let sessionName;
|
|
38
|
+
const hqPath = findHQRoot(process.cwd());
|
|
39
|
+
if (hqPath) {
|
|
40
|
+
const hqName = getHeadquartersNameFromPath(hqPath);
|
|
41
|
+
sessionName = buildOrchestratorSessionName(hqName, flags.name || 'main');
|
|
39
42
|
}
|
|
43
|
+
// If in HQ, check specifically for this HQ's session
|
|
44
|
+
if (sessionName) {
|
|
45
|
+
const isRunning = hostSessions.includes(sessionName);
|
|
46
|
+
let recentOutput = null;
|
|
47
|
+
if (isRunning && flags.peek) {
|
|
48
|
+
recentOutput = captureTmuxPane(sessionName, flags.lines);
|
|
49
|
+
}
|
|
50
|
+
if (jsonMode) {
|
|
51
|
+
outputSuccessAsJson({
|
|
52
|
+
running: isRunning,
|
|
53
|
+
sessionId: isRunning ? sessionName : null,
|
|
54
|
+
...(recentOutput !== null && { recentOutput }),
|
|
55
|
+
}, createMetadata('orchestrator status', flags));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.log('');
|
|
59
|
+
if (isRunning) {
|
|
60
|
+
this.log(styles.success(`Orchestrator is running`));
|
|
61
|
+
this.log(styles.muted(` Session: ${sessionName}`));
|
|
62
|
+
this.log(styles.muted(` Attach: prlt orchestrator attach`));
|
|
63
|
+
this.log(styles.muted(` Poke: prlt session poke orchestrator "message"`));
|
|
64
|
+
if (recentOutput) {
|
|
65
|
+
this.log('');
|
|
66
|
+
this.log(styles.header('Recent output:'));
|
|
67
|
+
this.log(styles.muted('─'.repeat(60)));
|
|
68
|
+
this.log(recentOutput);
|
|
69
|
+
this.log(styles.muted('─'.repeat(60)));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
this.log(styles.muted('Orchestrator is not running.'));
|
|
74
|
+
this.log(styles.muted('Start it with: prlt orchestrator start'));
|
|
75
|
+
}
|
|
76
|
+
this.log('');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Not in HQ — discover all running orchestrator sessions
|
|
80
|
+
const runningSessions = findRunningOrchestratorSessions(hostSessions);
|
|
40
81
|
if (jsonMode) {
|
|
41
82
|
outputSuccessAsJson({
|
|
42
|
-
running:
|
|
43
|
-
|
|
44
|
-
...(recentOutput !== null && { recentOutput }),
|
|
83
|
+
running: runningSessions.length > 0,
|
|
84
|
+
sessions: runningSessions,
|
|
45
85
|
}, createMetadata('orchestrator status', flags));
|
|
46
86
|
return;
|
|
47
87
|
}
|
|
48
88
|
this.log('');
|
|
49
|
-
if (
|
|
50
|
-
this.log(styles.
|
|
51
|
-
this.log(styles.muted(
|
|
52
|
-
this.log(styles.muted(` Attach: prlt orchestrator attach`));
|
|
53
|
-
this.log(styles.muted(` Poke: prlt session poke orchestrator "message"`));
|
|
54
|
-
if (recentOutput) {
|
|
55
|
-
this.log('');
|
|
56
|
-
this.log(styles.header('Recent output:'));
|
|
57
|
-
this.log(styles.muted('─'.repeat(60)));
|
|
58
|
-
this.log(recentOutput);
|
|
59
|
-
this.log(styles.muted('─'.repeat(60)));
|
|
60
|
-
}
|
|
89
|
+
if (runningSessions.length === 0) {
|
|
90
|
+
this.log(styles.muted('No orchestrator sessions running.'));
|
|
91
|
+
this.log(styles.muted('Start one with: prlt orchestrator start'));
|
|
61
92
|
}
|
|
62
93
|
else {
|
|
63
|
-
this.log(styles.
|
|
64
|
-
|
|
94
|
+
this.log(styles.success(`${runningSessions.length} orchestrator session(s) running:`));
|
|
95
|
+
for (const s of runningSessions) {
|
|
96
|
+
this.log(styles.muted(` ${s}`));
|
|
97
|
+
if (flags.peek) {
|
|
98
|
+
const output = captureTmuxPane(s, flags.lines);
|
|
99
|
+
if (output) {
|
|
100
|
+
this.log(styles.muted('─'.repeat(60)));
|
|
101
|
+
this.log(output);
|
|
102
|
+
this.log(styles.muted('─'.repeat(60)));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
65
106
|
}
|
|
66
107
|
this.log('');
|
|
67
108
|
}
|
|
@@ -10,7 +10,8 @@ import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadat
|
|
|
10
10
|
import { styles } from '../../lib/styles.js';
|
|
11
11
|
import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
|
|
12
12
|
import { ExecutionStorage } from '../../lib/execution/storage.js';
|
|
13
|
-
import { buildOrchestratorSessionName } from './start.js';
|
|
13
|
+
import { buildOrchestratorSessionName, findRunningOrchestratorSessions } from './start.js';
|
|
14
|
+
import { getHeadquartersNameFromPath } from '../../lib/machine-config.js';
|
|
14
15
|
export default class OrchestratorStop extends PromptCommand {
|
|
15
16
|
static description = 'Stop the running orchestrator';
|
|
16
17
|
static examples = [
|
|
@@ -32,17 +33,49 @@ export default class OrchestratorStop extends PromptCommand {
|
|
|
32
33
|
async run() {
|
|
33
34
|
const { flags } = await this.parse(OrchestratorStop);
|
|
34
35
|
const jsonMode = shouldOutputJson(flags);
|
|
35
|
-
const sessionName = buildOrchestratorSessionName(flags.name || 'main');
|
|
36
|
-
// Check if orchestrator session exists
|
|
37
36
|
const hostSessions = getHostTmuxSessionNames();
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
// Resolve session name: try HQ-scoped first, fall back to discovery
|
|
38
|
+
let sessionName;
|
|
39
|
+
const hqPath = findHQRoot(process.cwd());
|
|
40
|
+
if (hqPath) {
|
|
41
|
+
const hqName = getHeadquartersNameFromPath(hqPath);
|
|
42
|
+
sessionName = buildOrchestratorSessionName(hqName, flags.name || 'main');
|
|
43
|
+
if (!hostSessions.includes(sessionName)) {
|
|
44
|
+
sessionName = undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// If not in HQ or session not found, discover running orchestrator sessions
|
|
48
|
+
if (!sessionName) {
|
|
49
|
+
const runningSessions = findRunningOrchestratorSessions(hostSessions);
|
|
50
|
+
if (runningSessions.length === 0) {
|
|
51
|
+
if (jsonMode) {
|
|
52
|
+
outputErrorAsJson('NOT_RUNNING', 'Orchestrator is not running.', createMetadata('orchestrator stop', flags));
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.log('');
|
|
56
|
+
this.log(styles.muted('Orchestrator is not running.'));
|
|
57
|
+
this.log('');
|
|
41
58
|
return;
|
|
42
59
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
else if (runningSessions.length === 1) {
|
|
61
|
+
sessionName = runningSessions[0];
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Multiple sessions — let user pick
|
|
65
|
+
const { session } = await this.prompt([{
|
|
66
|
+
type: 'list',
|
|
67
|
+
name: 'session',
|
|
68
|
+
message: 'Multiple orchestrator sessions found. Select one to stop:',
|
|
69
|
+
choices: runningSessions.map(s => ({
|
|
70
|
+
name: s,
|
|
71
|
+
value: s,
|
|
72
|
+
command: `prlt orchestrator stop --name "${s}" --force --json`,
|
|
73
|
+
})),
|
|
74
|
+
}], jsonMode ? { flags, commandName: 'orchestrator stop' } : null);
|
|
75
|
+
sessionName = session;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!sessionName) {
|
|
46
79
|
return;
|
|
47
80
|
}
|
|
48
81
|
// Confirm unless --force
|
|
@@ -50,7 +83,7 @@ export default class OrchestratorStop extends PromptCommand {
|
|
|
50
83
|
const { confirmed } = await this.prompt([{
|
|
51
84
|
type: 'list',
|
|
52
85
|
name: 'confirmed',
|
|
53
|
-
message:
|
|
86
|
+
message: `Stop the orchestrator (${sessionName})?`,
|
|
54
87
|
choices: [
|
|
55
88
|
{ name: 'Yes', value: true },
|
|
56
89
|
{ name: 'No', value: false },
|
|
@@ -72,8 +105,7 @@ export default class OrchestratorStop extends PromptCommand {
|
|
|
72
105
|
}
|
|
73
106
|
this.error(`Failed to stop orchestrator: ${error instanceof Error ? error.message : error}`);
|
|
74
107
|
}
|
|
75
|
-
// Update execution record to stopped
|
|
76
|
-
const hqPath = findHQRoot(process.cwd());
|
|
108
|
+
// Update execution record to stopped (only if in HQ)
|
|
77
109
|
if (hqPath) {
|
|
78
110
|
const dbPath = path.join(hqPath, '.proletariat', 'workspace.db');
|
|
79
111
|
if (fs.existsSync(dbPath)) {
|