@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.
Files changed (74) hide show
  1. package/dist/commands/caffeinate/index.d.ts +10 -0
  2. package/dist/commands/caffeinate/index.js +64 -0
  3. package/dist/commands/caffeinate/start.d.ts +14 -0
  4. package/dist/commands/caffeinate/start.js +86 -0
  5. package/dist/commands/caffeinate/status.d.ts +10 -0
  6. package/dist/commands/caffeinate/status.js +55 -0
  7. package/dist/commands/caffeinate/stop.d.ts +10 -0
  8. package/dist/commands/caffeinate/stop.js +47 -0
  9. package/dist/commands/claude/index.js +21 -21
  10. package/dist/commands/claude/open.js +1 -1
  11. package/dist/commands/commit.js +10 -8
  12. package/dist/commands/config/index.js +4 -5
  13. package/dist/commands/execution/config.d.ts +2 -2
  14. package/dist/commands/execution/config.js +18 -18
  15. package/dist/commands/execution/list.js +2 -2
  16. package/dist/commands/execution/view.js +2 -2
  17. package/dist/commands/init.js +9 -1
  18. package/dist/commands/orchestrator/attach.js +64 -14
  19. package/dist/commands/orchestrator/start.d.ts +5 -5
  20. package/dist/commands/orchestrator/start.js +45 -35
  21. package/dist/commands/orchestrator/status.js +64 -23
  22. package/dist/commands/orchestrator/stop.js +44 -12
  23. package/dist/commands/qa/index.js +12 -12
  24. package/dist/commands/session/attach.js +23 -0
  25. package/dist/commands/session/poke.js +1 -1
  26. package/dist/commands/staff/add.js +1 -1
  27. package/dist/commands/work/index.js +4 -0
  28. package/dist/commands/work/linear.d.ts +24 -0
  29. package/dist/commands/work/linear.js +218 -0
  30. package/dist/commands/work/revise.js +8 -8
  31. package/dist/commands/work/spawn.js +29 -20
  32. package/dist/commands/work/start.js +22 -12
  33. package/dist/commands/work/watch.js +3 -3
  34. package/dist/hooks/init.js +8 -0
  35. package/dist/lib/agents/index.js +2 -2
  36. package/dist/lib/caffeinate.d.ts +64 -0
  37. package/dist/lib/caffeinate.js +146 -0
  38. package/dist/lib/database/drizzle-schema.d.ts +7 -7
  39. package/dist/lib/database/drizzle-schema.js +1 -1
  40. package/dist/lib/execution/codex-adapter.d.ts +96 -0
  41. package/dist/lib/execution/codex-adapter.js +148 -0
  42. package/dist/lib/execution/config.d.ts +6 -6
  43. package/dist/lib/execution/config.js +17 -10
  44. package/dist/lib/execution/devcontainer.d.ts +3 -3
  45. package/dist/lib/execution/devcontainer.js +3 -3
  46. package/dist/lib/execution/index.d.ts +1 -0
  47. package/dist/lib/execution/index.js +1 -0
  48. package/dist/lib/execution/runners.d.ts +2 -2
  49. package/dist/lib/execution/runners.js +69 -26
  50. package/dist/lib/execution/spawner.js +3 -3
  51. package/dist/lib/execution/storage.d.ts +2 -2
  52. package/dist/lib/execution/storage.js +3 -3
  53. package/dist/lib/execution/types.d.ts +2 -2
  54. package/dist/lib/execution/types.js +1 -1
  55. package/dist/lib/external-issues/index.d.ts +1 -1
  56. package/dist/lib/external-issues/index.js +1 -1
  57. package/dist/lib/external-issues/linear.d.ts +43 -0
  58. package/dist/lib/external-issues/linear.js +261 -0
  59. package/dist/lib/external-issues/types.d.ts +67 -0
  60. package/dist/lib/external-issues/types.js +41 -0
  61. package/dist/lib/init/index.d.ts +4 -0
  62. package/dist/lib/init/index.js +11 -1
  63. package/dist/lib/machine-config.d.ts +1 -0
  64. package/dist/lib/machine-config.js +6 -3
  65. package/dist/lib/pmo/schema.d.ts +1 -1
  66. package/dist/lib/pmo/schema.js +1 -1
  67. package/dist/lib/pmo/storage/actions.js +3 -3
  68. package/dist/lib/pmo/storage/base.js +116 -6
  69. package/dist/lib/pmo/storage/epics.js +1 -1
  70. package/dist/lib/pmo/storage/tickets.js +2 -2
  71. package/dist/lib/pmo/storage/types.d.ts +2 -1
  72. package/dist/lib/repos/index.js +1 -1
  73. package/oclif.manifest.json +3052 -2721
  74. 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
- sandboxed: execution.sandboxed,
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.sandboxed ? styles.success('sandboxed (safe)') : styles.warning('unrestricted (danger)')}`);
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
  }
@@ -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
- if (!hostSessions.includes(sessionName)) {
80
- if (jsonMode) {
81
- outputErrorAsJson('NOT_RUNNING', 'Orchestrator is not running. Start it with: prlt orchestrator start', createMetadata('orchestrator attach', flags));
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
- this.log('');
85
- this.log(styles.warning('Orchestrator is not running.'));
86
- this.log(styles.muted('Start it with: prlt orchestrator start'));
87
- this.log('');
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
- * Default: 'prlt-orchestrator-main'
5
- * With --name: 'prlt-orchestrator-{name}'
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
- sandboxed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
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
- * Build orchestrator tmux session name.
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
- export function buildOrchestratorSessionName(name = 'main') {
21
- const safeName = name
19
+ function sanitizeName(name) {
20
+ return name
22
21
  .replace(/[^a-zA-Z0-9._-]/g, '-')
23
22
  .replace(/-+/g, '-')
24
23
  .replace(/^-|-$/g, '');
25
- return `prlt-orchestrator-${safeName || 'main'}`;
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 %> --skip-permissions',
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: 'Run with --dangerously-skip-permissions',
67
+ description: 'Skip permission checks (shorthand for --permission-mode danger)',
60
68
  default: false,
61
- exclusive: ['sandboxed'],
69
+ exclusive: ['permission-mode'],
62
70
  }),
63
- sandboxed: Flags.boolean({
64
- description: 'Run in sandboxed mode (requires approval for dangerous operations)',
65
- default: false,
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
- const sessionName = buildOrchestratorSessionName(orchestratorName);
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 sandboxed;
160
+ let permissionMode;
151
161
  if (flags['skip-permissions']) {
152
- sandboxed = false;
162
+ permissionMode = 'danger';
153
163
  }
154
- else if (flags.sandboxed) {
155
- sandboxed = true;
164
+ else if (flags['permission-mode']) {
165
+ permissionMode = flags['permission-mode'];
156
166
  }
157
167
  else {
158
168
  const permissionChoices = [
159
- { name: 'Sandboxed (requires approval for dangerous operations)', value: 'sandboxed', command: 'prlt orchestrator start --sandboxed --json' },
160
- { name: 'Accept all (--dangerously-skip-permissions)', value: 'skip', command: 'prlt orchestrator start --skip-permissions --json' },
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 = 'Select permission mode:';
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
- sandboxed = permissionMode === 'sandboxed';
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.sandboxed = sandboxed;
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: ${sandboxed ? 'sandboxed' : 'skip-permissions'}`));
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
- sandboxed,
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
- sandboxed,
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 { buildOrchestratorSessionName } from './start.js';
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
- const isRunning = hostSessions.includes(sessionName);
36
- let recentOutput = null;
37
- if (isRunning && flags.peek) {
38
- recentOutput = captureTmuxPane(sessionName, flags.lines);
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: isRunning,
43
- sessionId: isRunning ? sessionName : null,
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 (isRunning) {
50
- this.log(styles.success(`Orchestrator is running`));
51
- this.log(styles.muted(` Session: ${sessionName}`));
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.muted('Orchestrator is not running.'));
64
- this.log(styles.muted('Start it with: prlt orchestrator start'));
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
- if (!hostSessions.includes(sessionName)) {
39
- if (jsonMode) {
40
- outputErrorAsJson('NOT_RUNNING', 'Orchestrator is not running.', createMetadata('orchestrator stop', flags));
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
- this.log('');
44
- this.log(styles.muted('Orchestrator is not running.'));
45
- this.log('');
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: 'Stop the orchestrator?',
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)) {