@proletariat/cli 0.3.46 → 0.3.48

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 (58) hide show
  1. package/bin/validate-better-sqlite3.cjs +55 -0
  2. package/dist/commands/caffeinate/index.d.ts +10 -0
  3. package/dist/commands/caffeinate/index.js +64 -0
  4. package/dist/commands/caffeinate/start.d.ts +14 -0
  5. package/dist/commands/caffeinate/start.js +86 -0
  6. package/dist/commands/caffeinate/status.d.ts +10 -0
  7. package/dist/commands/caffeinate/status.js +55 -0
  8. package/dist/commands/caffeinate/stop.d.ts +10 -0
  9. package/dist/commands/caffeinate/stop.js +47 -0
  10. package/dist/commands/commit.js +10 -8
  11. package/dist/commands/config/index.js +2 -3
  12. package/dist/commands/init.js +9 -1
  13. package/dist/commands/orchestrator/attach.d.ts +1 -0
  14. package/dist/commands/orchestrator/attach.js +104 -24
  15. package/dist/commands/orchestrator/index.js +2 -2
  16. package/dist/commands/orchestrator/start.d.ts +13 -1
  17. package/dist/commands/orchestrator/start.js +115 -34
  18. package/dist/commands/orchestrator/status.d.ts +1 -0
  19. package/dist/commands/orchestrator/status.js +68 -22
  20. package/dist/commands/orchestrator/stop.d.ts +1 -0
  21. package/dist/commands/orchestrator/stop.js +50 -13
  22. package/dist/commands/session/attach.js +55 -9
  23. package/dist/commands/session/poke.js +1 -1
  24. package/dist/commands/work/index.js +8 -0
  25. package/dist/commands/work/linear.d.ts +24 -0
  26. package/dist/commands/work/linear.js +195 -0
  27. package/dist/commands/work/review.d.ts +45 -0
  28. package/dist/commands/work/review.js +401 -0
  29. package/dist/commands/work/spawn.js +28 -19
  30. package/dist/commands/work/start.js +12 -2
  31. package/dist/hooks/init.js +26 -5
  32. package/dist/lib/caffeinate.d.ts +64 -0
  33. package/dist/lib/caffeinate.js +146 -0
  34. package/dist/lib/database/native-validation.d.ts +21 -0
  35. package/dist/lib/database/native-validation.js +49 -0
  36. package/dist/lib/execution/codex-adapter.d.ts +96 -0
  37. package/dist/lib/execution/codex-adapter.js +148 -0
  38. package/dist/lib/execution/index.d.ts +1 -0
  39. package/dist/lib/execution/index.js +1 -0
  40. package/dist/lib/execution/runners.js +56 -6
  41. package/dist/lib/external-issues/index.d.ts +1 -1
  42. package/dist/lib/external-issues/index.js +1 -1
  43. package/dist/lib/external-issues/linear.d.ts +37 -0
  44. package/dist/lib/external-issues/linear.js +198 -0
  45. package/dist/lib/external-issues/types.d.ts +67 -0
  46. package/dist/lib/external-issues/types.js +41 -0
  47. package/dist/lib/init/index.d.ts +4 -0
  48. package/dist/lib/init/index.js +11 -1
  49. package/dist/lib/machine-config.d.ts +1 -0
  50. package/dist/lib/machine-config.js +6 -3
  51. package/dist/lib/mcp/tools/work.js +36 -0
  52. package/dist/lib/pmo/storage/actions.js +3 -3
  53. package/dist/lib/pmo/storage/base.js +85 -6
  54. package/dist/lib/pmo/storage/epics.js +1 -1
  55. package/dist/lib/pmo/storage/tickets.js +2 -2
  56. package/dist/lib/pmo/storage/types.d.ts +2 -1
  57. package/oclif.manifest.json +4158 -3651
  58. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
1
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
2
2
  import { shouldOutputJson } from '../../lib/prompt-json.js';
3
3
  import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
4
- import { ORCHESTRATOR_SESSION_NAME } from './start.js';
4
+ import { findRunningOrchestratorSessions } from './start.js';
5
5
  export default class Orchestrator extends PMOCommand {
6
6
  static description = 'Manage the orchestrator agent (start, attach, status, stop)';
7
7
  static examples = [
@@ -22,7 +22,7 @@ export default class Orchestrator extends PMOCommand {
22
22
  const jsonModeConfig = shouldOutputJson(flags) ? { flags, commandName: 'orchestrator' } : null;
23
23
  // Check if orchestrator is currently running to offer contextual options
24
24
  const hostSessions = getHostTmuxSessionNames();
25
- const isRunning = hostSessions.includes(ORCHESTRATOR_SESSION_NAME);
25
+ const isRunning = findRunningOrchestratorSessions(hostSessions).length > 0;
26
26
  // When running, show "Attach to running session" first since that's the likely intent
27
27
  const choices = isRunning
28
28
  ? [
@@ -1,5 +1,15 @@
1
1
  import { PromptCommand } from '../../lib/prompt-command.js';
2
- export declare const ORCHESTRATOR_SESSION_NAME = "prlt-orchestrator-main";
2
+ /**
3
+ * Build orchestrator tmux session name scoped to the HQ workspace.
4
+ * Format: 'prlt-orchestrator-{hqName}-{name}'
5
+ * Example: 'prlt-orchestrator-proletariat-main'
6
+ */
7
+ export declare function buildOrchestratorSessionName(hqName: string, name?: string): string;
8
+ /**
9
+ * Find running orchestrator session(s) by prefix match.
10
+ * Returns all tmux session names that start with 'prlt-orchestrator-'.
11
+ */
12
+ export declare function findRunningOrchestratorSessions(hostSessions: string[]): string[];
3
13
  export default class OrchestratorStart extends PromptCommand {
4
14
  static description: string;
5
15
  static examples: string[];
@@ -9,7 +19,9 @@ export default class OrchestratorStart extends PromptCommand {
9
19
  executor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
20
  'skip-permissions': import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
21
  sandboxed: import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
23
  background: import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
+ foreground: import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
25
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
26
  machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
27
  };
@@ -5,14 +5,40 @@ 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';
11
12
  import { runExecution } from '../../lib/execution/runners.js';
12
13
  import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
13
14
  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';
15
+ import { loadExecutionConfig, getTerminalApp, getShell, detectShell, detectTerminalApp, } from '../../lib/execution/config.js';
16
+ /**
17
+ * Sanitize a name segment for use in tmux session names.
18
+ */
19
+ function sanitizeName(name) {
20
+ return name
21
+ .replace(/[^a-zA-Z0-9._-]/g, '-')
22
+ .replace(/-+/g, '-')
23
+ .replace(/^-|-$/g, '');
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}`;
34
+ }
35
+ /**
36
+ * Find running orchestrator session(s) by prefix match.
37
+ * Returns all tmux session names that start with 'prlt-orchestrator-'.
38
+ */
39
+ export function findRunningOrchestratorSessions(hostSessions) {
40
+ return hostSessions.filter(s => s.startsWith('prlt-orchestrator-'));
41
+ }
16
42
  export default class OrchestratorStart extends PromptCommand {
17
43
  static description = 'Start the orchestrator agent in a tmux session';
18
44
  static examples = [
@@ -47,48 +73,64 @@ export default class OrchestratorStart extends PromptCommand {
47
73
  default: false,
48
74
  exclusive: ['skip-permissions'],
49
75
  }),
76
+ name: Flags.string({
77
+ char: 'n',
78
+ description: 'Name for the orchestrator session (default: main)',
79
+ }),
50
80
  background: Flags.boolean({
51
81
  char: 'b',
52
82
  description: 'Start detached (don\'t open terminal tab)',
53
83
  default: false,
84
+ exclusive: ['foreground'],
85
+ }),
86
+ foreground: Flags.boolean({
87
+ char: 'f',
88
+ description: 'Attach to the tmux session in the current terminal (blocking)',
89
+ default: false,
90
+ exclusive: ['background'],
54
91
  }),
55
92
  };
56
93
  async run() {
57
94
  const { flags } = await this.parse(OrchestratorStart);
58
95
  const jsonMode = shouldOutputJson(flags);
96
+ const orchestratorName = flags.name || 'main';
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);
59
109
  // Check if orchestrator is already running
60
110
  const hostSessions = getHostTmuxSessionNames();
61
- if (hostSessions.includes(ORCHESTRATOR_SESSION_NAME)) {
111
+ if (hostSessions.includes(sessionName)) {
62
112
  if (jsonMode) {
63
- outputErrorAsJson('ALREADY_RUNNING', `Orchestrator is already running (session: ${ORCHESTRATOR_SESSION_NAME}). Use "prlt orchestrator attach" to reattach.`, createMetadata('orchestrator start', flags));
113
+ outputErrorAsJson('ALREADY_RUNNING', `Orchestrator is already running (session: ${sessionName}). Use "prlt orchestrator attach${flags.name ? ` --name ${flags.name}` : ''}" to reattach.`, createMetadata('orchestrator start', flags));
64
114
  return;
65
115
  }
66
116
  this.log('');
67
- this.log(styles.warning(`Orchestrator is already running (session: ${ORCHESTRATOR_SESSION_NAME})`));
117
+ this.log(styles.warning(`Orchestrator is already running (session: ${sessionName})`));
68
118
  this.log('');
119
+ const attachArgs = flags.name ? ['--name', flags.name] : [];
69
120
  const { choice } = await this.prompt([{
70
121
  type: 'list',
71
122
  name: 'choice',
72
123
  message: 'What would you like to do?',
73
124
  choices: [
74
- { name: 'Attach to running orchestrator', value: 'attach', command: 'prlt orchestrator attach --json' },
125
+ { name: 'Attach to running orchestrator', value: 'attach', command: `prlt orchestrator attach${flags.name ? ` --name ${flags.name}` : ''} --json` },
75
126
  { name: 'Cancel', value: 'cancel' },
76
127
  ],
77
128
  }], jsonMode ? { flags, commandName: 'orchestrator start' } : null);
78
129
  if (choice === 'attach') {
79
- await this.config.runCommand('orchestrator:attach', []);
130
+ await this.config.runCommand('orchestrator:attach', attachArgs);
80
131
  }
81
132
  return;
82
133
  }
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
134
  // Executor selection
93
135
  let selectedExecutor;
94
136
  if (flags.executor) {
@@ -169,12 +211,12 @@ export default class OrchestratorStart extends PromptCommand {
169
211
  }
170
212
  }
171
213
  // Build execution context
172
- // Use ticketId='prlt', actionName='orchestrator', agentName='main'
173
- // so buildSessionName produces 'prlt-orchestrator-main'
214
+ // Use ticketId='prlt', actionName='orchestrator', agentName=orchestratorName
215
+ // so buildSessionName produces 'prlt-orchestrator-{name}'
174
216
  const context = {
175
217
  ticketId: 'prlt',
176
218
  ticketTitle: 'Orchestrator',
177
- agentName: 'main',
219
+ agentName: orchestratorName,
178
220
  agentDir: hqPath,
179
221
  worktreePath: hqPath,
180
222
  branch: 'main',
@@ -202,19 +244,54 @@ export default class OrchestratorStart extends PromptCommand {
202
244
  catch {
203
245
  // Ignore config loading errors, use defaults
204
246
  }
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);
247
+ // Auto-detect shell (never prompt for orchestrator)
248
+ if (db) {
249
+ executionConfig.shell = await getShell(db);
250
+ }
251
+ else {
252
+ executionConfig.shell = detectShell() || 'zsh';
253
+ }
254
+ // Determine display mode
255
+ let displayMode;
256
+ if (flags.background) {
257
+ displayMode = 'background';
258
+ }
259
+ else if (flags.foreground) {
260
+ displayMode = 'foreground';
261
+ }
262
+ else {
263
+ const displayChoices = [
264
+ { name: 'New terminal tab — opens attached to the tmux session', value: 'terminal', command: `prlt orchestrator start${flags.name ? ` --name ${flags.name}` : ''} --json` },
265
+ { name: 'Current session — attach to tmux here (foreground, blocking)', value: 'foreground', command: `prlt orchestrator start${flags.name ? ` --name ${flags.name}` : ''} --foreground --json` },
266
+ { name: 'Background — start detached, attach later', value: 'background', command: `prlt orchestrator start${flags.name ? ` --name ${flags.name}` : ''} --background --json` },
267
+ ];
268
+ const displayMessage = 'How do you want to view the orchestrator?';
269
+ if (jsonMode) {
270
+ outputPromptAsJson(buildPromptConfig('list', 'displayMode', displayMessage, displayChoices), createMetadata('orchestrator start', flags));
271
+ return;
209
272
  }
210
- else {
273
+ const { displayMode: selectedMode } = await this.prompt([{
274
+ type: 'list',
275
+ name: 'displayMode',
276
+ message: displayMessage,
277
+ choices: displayChoices,
278
+ }], jsonMode ? { flags, commandName: 'orchestrator start' } : null);
279
+ displayMode = selectedMode;
280
+ }
281
+ // For 'terminal' display mode, auto-detect terminal app
282
+ if (displayMode === 'terminal') {
283
+ if (db) {
211
284
  executionConfig.terminal.app = await getTerminalApp(db);
212
285
  }
213
- if (!hasShellPreference(db)) {
214
- executionConfig.shell = await promptShellPreference(db);
215
- }
216
286
  else {
217
- executionConfig.shell = await getShell(db);
287
+ const detected = detectTerminalApp();
288
+ if (detected) {
289
+ executionConfig.terminal.app = detected;
290
+ }
291
+ else {
292
+ // Can't detect terminal and no db to prompt — fall back to foreground
293
+ displayMode = 'foreground';
294
+ }
218
295
  }
219
296
  }
220
297
  // Show what we're doing
@@ -223,14 +300,17 @@ export default class OrchestratorStart extends PromptCommand {
223
300
  this.log(styles.muted(` Starting orchestrator...`));
224
301
  this.log(styles.muted(` Executor: ${selectedExecutor}`));
225
302
  this.log(styles.muted(` Permission mode: ${sandboxed ? 'sandboxed' : 'skip-permissions'}`));
303
+ this.log(styles.muted(` Display mode: ${displayMode}`));
226
304
  this.log(styles.muted(` Directory: ${hqPath}`));
305
+ if (orchestratorName !== 'main') {
306
+ this.log(styles.muted(` Name: ${orchestratorName}`));
307
+ }
227
308
  if (actionPrompt) {
228
309
  this.log(styles.muted(` Prompt: "${actionPrompt.substring(0, 60)}${actionPrompt.length > 60 ? '...' : ''}"`));
229
310
  }
230
311
  this.log('');
231
312
  }
232
313
  // Launch orchestrator
233
- const displayMode = flags.background ? 'background' : 'terminal';
234
314
  const result = await runExecution('host', context, selectedExecutor, executionConfig, {
235
315
  displayMode,
236
316
  });
@@ -246,7 +326,7 @@ export default class OrchestratorStart extends PromptCommand {
246
326
  environment: 'host',
247
327
  displayMode,
248
328
  sandboxed,
249
- sessionId: result.sessionId || ORCHESTRATOR_SESSION_NAME,
329
+ sessionId: result.sessionId || sessionName,
250
330
  });
251
331
  }
252
332
  catch {
@@ -255,17 +335,18 @@ export default class OrchestratorStart extends PromptCommand {
255
335
  }
256
336
  if (jsonMode) {
257
337
  outputSuccessAsJson({
258
- sessionId: result.sessionId || ORCHESTRATOR_SESSION_NAME,
338
+ sessionId: result.sessionId || sessionName,
259
339
  executor: selectedExecutor,
260
340
  sandboxed,
261
341
  displayMode,
262
342
  directory: hqPath,
343
+ name: orchestratorName,
263
344
  }, createMetadata('orchestrator start', flags));
264
345
  }
265
- if (flags.background) {
346
+ if (displayMode === 'background') {
266
347
  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`));
348
+ this.log(styles.muted(` Session: ${result.sessionId || sessionName}`));
349
+ this.log(styles.muted(` Attach with: prlt orchestrator attach${flags.name ? ` --name ${flags.name}` : ''}`));
269
350
  }
270
351
  else {
271
352
  this.log(styles.success(`Orchestrator started`));
@@ -3,6 +3,7 @@ export default class OrchestratorStatus extends PromptCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
7
  peek: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
8
  lines: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
8
9
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -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 { ORCHESTRATOR_SESSION_NAME } 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 = [
@@ -14,6 +16,10 @@ export default class OrchestratorStatus extends PromptCommand {
14
16
  ];
15
17
  static flags = {
16
18
  ...machineOutputFlags,
19
+ name: Flags.string({
20
+ char: 'n',
21
+ description: 'Name of the orchestrator session to check (default: main)',
22
+ }),
17
23
  peek: Flags.boolean({
18
24
  description: 'Show recent output from the orchestrator',
19
25
  default: false,
@@ -27,36 +33,76 @@ export default class OrchestratorStatus extends PromptCommand {
27
33
  const { flags } = await this.parse(OrchestratorStatus);
28
34
  const jsonMode = shouldOutputJson(flags);
29
35
  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);
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');
34
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);
35
81
  if (jsonMode) {
36
82
  outputSuccessAsJson({
37
- running: isRunning,
38
- sessionId: isRunning ? ORCHESTRATOR_SESSION_NAME : null,
39
- ...(recentOutput !== null && { recentOutput }),
83
+ running: runningSessions.length > 0,
84
+ sessions: runningSessions,
40
85
  }, createMetadata('orchestrator status', flags));
41
86
  return;
42
87
  }
43
88
  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
- }
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'));
56
92
  }
57
93
  else {
58
- this.log(styles.muted('Orchestrator is not running.'));
59
- 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
+ }
60
106
  }
61
107
  this.log('');
62
108
  }
@@ -3,6 +3,7 @@ export default class OrchestratorStop extends PromptCommand {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
6
+ name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
6
7
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
7
8
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
9
  machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
@@ -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 { ORCHESTRATOR_SESSION_NAME } 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 = [
@@ -19,6 +20,10 @@ export default class OrchestratorStop extends PromptCommand {
19
20
  ];
20
21
  static flags = {
21
22
  ...machineOutputFlags,
23
+ name: Flags.string({
24
+ char: 'n',
25
+ description: 'Name of the orchestrator session to stop (default: main)',
26
+ }),
22
27
  force: Flags.boolean({
23
28
  char: 'f',
24
29
  description: 'Skip confirmation',
@@ -28,16 +33,49 @@ export default class OrchestratorStop extends PromptCommand {
28
33
  async run() {
29
34
  const { flags } = await this.parse(OrchestratorStop);
30
35
  const jsonMode = shouldOutputJson(flags);
31
- // Check if orchestrator session exists
32
36
  const hostSessions = getHostTmuxSessionNames();
33
- if (!hostSessions.includes(ORCHESTRATOR_SESSION_NAME)) {
34
- if (jsonMode) {
35
- 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('');
36
58
  return;
37
59
  }
38
- this.log('');
39
- this.log(styles.muted('Orchestrator is not running.'));
40
- 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) {
41
79
  return;
42
80
  }
43
81
  // Confirm unless --force
@@ -45,7 +83,7 @@ export default class OrchestratorStop extends PromptCommand {
45
83
  const { confirmed } = await this.prompt([{
46
84
  type: 'list',
47
85
  name: 'confirmed',
48
- message: 'Stop the orchestrator?',
86
+ message: `Stop the orchestrator (${sessionName})?`,
49
87
  choices: [
50
88
  { name: 'Yes', value: true },
51
89
  { name: 'No', value: false },
@@ -58,7 +96,7 @@ export default class OrchestratorStop extends PromptCommand {
58
96
  }
59
97
  // Kill the tmux session
60
98
  try {
61
- execSync(`tmux kill-session -t "${ORCHESTRATOR_SESSION_NAME}"`, { stdio: 'pipe' });
99
+ execSync(`tmux kill-session -t "${sessionName}"`, { stdio: 'pipe' });
62
100
  }
63
101
  catch (error) {
64
102
  if (jsonMode) {
@@ -67,8 +105,7 @@ export default class OrchestratorStop extends PromptCommand {
67
105
  }
68
106
  this.error(`Failed to stop orchestrator: ${error instanceof Error ? error.message : error}`);
69
107
  }
70
- // Update execution record to stopped
71
- const hqPath = findHQRoot(process.cwd());
108
+ // Update execution record to stopped (only if in HQ)
72
109
  if (hqPath) {
73
110
  const dbPath = path.join(hqPath, '.proletariat', 'workspace.db');
74
111
  if (fs.existsSync(dbPath)) {
@@ -92,7 +129,7 @@ export default class OrchestratorStop extends PromptCommand {
92
129
  }
93
130
  if (jsonMode) {
94
131
  outputSuccessAsJson({
95
- sessionId: ORCHESTRATOR_SESSION_NAME,
132
+ sessionId: sessionName,
96
133
  status: 'stopped',
97
134
  }, createMetadata('orchestrator stop', flags));
98
135
  return;