@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.
- package/bin/validate-better-sqlite3.cjs +55 -0
- 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/commit.js +10 -8
- package/dist/commands/config/index.js +2 -3
- package/dist/commands/init.js +9 -1
- package/dist/commands/orchestrator/attach.d.ts +1 -0
- package/dist/commands/orchestrator/attach.js +104 -24
- package/dist/commands/orchestrator/index.js +2 -2
- package/dist/commands/orchestrator/start.d.ts +13 -1
- package/dist/commands/orchestrator/start.js +115 -34
- package/dist/commands/orchestrator/status.d.ts +1 -0
- package/dist/commands/orchestrator/status.js +68 -22
- package/dist/commands/orchestrator/stop.d.ts +1 -0
- package/dist/commands/orchestrator/stop.js +50 -13
- package/dist/commands/session/attach.js +55 -9
- package/dist/commands/session/poke.js +1 -1
- package/dist/commands/work/index.js +8 -0
- package/dist/commands/work/linear.d.ts +24 -0
- package/dist/commands/work/linear.js +195 -0
- package/dist/commands/work/review.d.ts +45 -0
- package/dist/commands/work/review.js +401 -0
- package/dist/commands/work/spawn.js +28 -19
- package/dist/commands/work/start.js +12 -2
- package/dist/hooks/init.js +26 -5
- package/dist/lib/caffeinate.d.ts +64 -0
- package/dist/lib/caffeinate.js +146 -0
- package/dist/lib/database/native-validation.d.ts +21 -0
- package/dist/lib/database/native-validation.js +49 -0
- package/dist/lib/execution/codex-adapter.d.ts +96 -0
- package/dist/lib/execution/codex-adapter.js +148 -0
- package/dist/lib/execution/index.d.ts +1 -0
- package/dist/lib/execution/index.js +1 -0
- package/dist/lib/execution/runners.js +56 -6
- 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 +37 -0
- package/dist/lib/external-issues/linear.js +198 -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/mcp/tools/work.js +36 -0
- package/dist/lib/pmo/storage/actions.js +3 -3
- package/dist/lib/pmo/storage/base.js +85 -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/oclif.manifest.json +4158 -3651
- 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 {
|
|
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.
|
|
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
|
-
|
|
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,
|
|
15
|
-
|
|
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(
|
|
111
|
+
if (hostSessions.includes(sessionName)) {
|
|
62
112
|
if (jsonMode) {
|
|
63
|
-
outputErrorAsJson('ALREADY_RUNNING', `Orchestrator is already running (session: ${
|
|
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: ${
|
|
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:
|
|
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=
|
|
173
|
-
// so buildSessionName produces 'prlt-orchestrator-
|
|
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:
|
|
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
|
-
//
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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 ||
|
|
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 (
|
|
346
|
+
if (displayMode === 'background') {
|
|
266
347
|
this.log(styles.success(`Orchestrator started in background`));
|
|
267
|
-
this.log(styles.muted(` Session: ${result.sessionId ||
|
|
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 {
|
|
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
|
-
|
|
31
|
-
let
|
|
32
|
-
|
|
33
|
-
|
|
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:
|
|
38
|
-
|
|
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 (
|
|
45
|
-
this.log(styles.
|
|
46
|
-
this.log(styles.muted(
|
|
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.
|
|
59
|
-
|
|
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 {
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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:
|
|
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 "${
|
|
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:
|
|
132
|
+
sessionId: sessionName,
|
|
96
133
|
status: 'stopped',
|
|
97
134
|
}, createMetadata('orchestrator stop', flags));
|
|
98
135
|
return;
|