@proletariat/cli 0.3.47 → 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/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.js +64 -14
- package/dist/commands/orchestrator/start.d.ts +4 -4
- package/dist/commands/orchestrator/start.js +26 -16
- package/dist/commands/orchestrator/status.js +64 -23
- package/dist/commands/orchestrator/stop.js +44 -12
- package/dist/commands/session/attach.js +23 -0
- package/dist/commands/session/poke.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 +195 -0
- package/dist/commands/work/spawn.js +28 -19
- package/dist/commands/work/start.js +12 -2
- package/dist/hooks/init.js +8 -0
- package/dist/lib/caffeinate.d.ts +64 -0
- package/dist/lib/caffeinate.js +146 -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 +50 -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/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 +4363 -4037
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Caffeinate extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { styles } from '../../lib/styles.js';
|
|
3
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
5
|
+
import { FlagResolver } from '../../lib/flags/index.js';
|
|
6
|
+
import { isMacOS } from '../../lib/caffeinate.js';
|
|
7
|
+
export default class Caffeinate extends Command {
|
|
8
|
+
static description = 'Manage caffeinate to keep macOS awake';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> caffeinate',
|
|
11
|
+
'<%= config.bin %> caffeinate start',
|
|
12
|
+
'<%= config.bin %> caffeinate status',
|
|
13
|
+
'<%= config.bin %> caffeinate stop',
|
|
14
|
+
'<%= config.bin %> caffeinate start --duration 3600',
|
|
15
|
+
'<%= config.bin %> caffeinate start --display',
|
|
16
|
+
];
|
|
17
|
+
static flags = {
|
|
18
|
+
...machineOutputFlags,
|
|
19
|
+
};
|
|
20
|
+
async run() {
|
|
21
|
+
const { flags } = await this.parse(Caffeinate);
|
|
22
|
+
const jsonMode = shouldOutputJson(flags);
|
|
23
|
+
if (!isMacOS()) {
|
|
24
|
+
if (jsonMode) {
|
|
25
|
+
this.log(JSON.stringify({
|
|
26
|
+
type: 'error',
|
|
27
|
+
error: { code: 'UNSUPPORTED_PLATFORM', message: `caffeinate is only supported on macOS (current platform: ${process.platform})` },
|
|
28
|
+
}, null, 2));
|
|
29
|
+
this.exit(1);
|
|
30
|
+
}
|
|
31
|
+
this.error(`caffeinate is only supported on macOS (current platform: ${process.platform})`);
|
|
32
|
+
}
|
|
33
|
+
const menuChoices = [
|
|
34
|
+
{ name: 'Check caffeinate status', value: 'status', command: 'prlt caffeinate status --json' },
|
|
35
|
+
{ name: 'Start caffeinate', value: 'start', command: 'prlt caffeinate start --json' },
|
|
36
|
+
{ name: 'Stop caffeinate', value: 'stop', command: 'prlt caffeinate stop --json' },
|
|
37
|
+
{ name: 'Exit', value: 'exit', command: 'prlt caffeinate --exit' },
|
|
38
|
+
];
|
|
39
|
+
const resolver = new FlagResolver({
|
|
40
|
+
commandName: 'caffeinate',
|
|
41
|
+
baseCommand: 'prlt caffeinate',
|
|
42
|
+
jsonMode,
|
|
43
|
+
flags,
|
|
44
|
+
});
|
|
45
|
+
resolver.addPrompt({
|
|
46
|
+
flagName: 'action',
|
|
47
|
+
type: 'list',
|
|
48
|
+
message: 'What would you like to do?',
|
|
49
|
+
choices: () => menuChoices,
|
|
50
|
+
skipAutoCommand: true,
|
|
51
|
+
});
|
|
52
|
+
if (!jsonMode) {
|
|
53
|
+
this.log('');
|
|
54
|
+
this.log(styles.header('Caffeinate'));
|
|
55
|
+
this.log('');
|
|
56
|
+
}
|
|
57
|
+
const resolved = await resolver.resolve();
|
|
58
|
+
const action = resolved.action;
|
|
59
|
+
if (!action || action === 'exit') {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
await this.config.runCommand(`caffeinate:${action}`, []);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class CaffeinateStart extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
duration: import("@oclif/core/interfaces").OptionFlag<number | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
display: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
idle: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
system: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { styles } from '../../lib/styles.js';
|
|
3
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
5
|
+
import { isMacOS, startCaffeinate, getStatus } from '../../lib/caffeinate.js';
|
|
6
|
+
export default class CaffeinateStart extends Command {
|
|
7
|
+
static description = 'Start caffeinate to prevent macOS from sleeping';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> caffeinate start',
|
|
10
|
+
'<%= config.bin %> caffeinate start --duration 3600',
|
|
11
|
+
'<%= config.bin %> caffeinate start --display',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
...machineOutputFlags,
|
|
15
|
+
duration: Flags.integer({
|
|
16
|
+
char: 't',
|
|
17
|
+
description: 'Duration in seconds (default: indefinite)',
|
|
18
|
+
}),
|
|
19
|
+
display: Flags.boolean({
|
|
20
|
+
char: 'd',
|
|
21
|
+
description: 'Prevent display from sleeping (adds -d flag)',
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
idle: Flags.boolean({
|
|
25
|
+
char: 'i',
|
|
26
|
+
description: 'Prevent idle sleep (adds -i flag)',
|
|
27
|
+
default: false,
|
|
28
|
+
}),
|
|
29
|
+
system: Flags.boolean({
|
|
30
|
+
char: 's',
|
|
31
|
+
description: 'Prevent system sleep (adds -s flag)',
|
|
32
|
+
default: false,
|
|
33
|
+
}),
|
|
34
|
+
};
|
|
35
|
+
async run() {
|
|
36
|
+
const { flags } = await this.parse(CaffeinateStart);
|
|
37
|
+
const jsonMode = shouldOutputJson(flags);
|
|
38
|
+
if (!isMacOS()) {
|
|
39
|
+
if (jsonMode) {
|
|
40
|
+
this.log(JSON.stringify({
|
|
41
|
+
type: 'error',
|
|
42
|
+
error: { code: 'UNSUPPORTED_PLATFORM', message: `caffeinate is only supported on macOS (current platform: ${process.platform})` },
|
|
43
|
+
}, null, 2));
|
|
44
|
+
this.exit(1);
|
|
45
|
+
}
|
|
46
|
+
this.error(`caffeinate is only supported on macOS (current platform: ${process.platform})`);
|
|
47
|
+
}
|
|
48
|
+
// Build caffeinate flags
|
|
49
|
+
const caffeinateFlags = [];
|
|
50
|
+
if (flags.display)
|
|
51
|
+
caffeinateFlags.push('-d');
|
|
52
|
+
if (flags.idle)
|
|
53
|
+
caffeinateFlags.push('-i');
|
|
54
|
+
if (flags.system)
|
|
55
|
+
caffeinateFlags.push('-s');
|
|
56
|
+
// Check if already running (idempotent)
|
|
57
|
+
const { running, state: existingState } = getStatus();
|
|
58
|
+
if (running && existingState) {
|
|
59
|
+
if (jsonMode) {
|
|
60
|
+
this.log(JSON.stringify({
|
|
61
|
+
type: 'success',
|
|
62
|
+
result: { ...existingState, status: 'already_running' },
|
|
63
|
+
}, null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.log(`\n${styles.warning('caffeinate is already running')} ${styles.muted(`(PID: ${existingState.pid})`)}\n`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const state = startCaffeinate(caffeinateFlags, flags.duration);
|
|
70
|
+
if (jsonMode) {
|
|
71
|
+
this.log(JSON.stringify({
|
|
72
|
+
type: 'success',
|
|
73
|
+
result: { ...state, status: 'started' },
|
|
74
|
+
}, null, 2));
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
this.log(`\n${styles.success('caffeinate started')} ${styles.muted(`(PID: ${state.pid})`)}`);
|
|
78
|
+
if (state.duration) {
|
|
79
|
+
this.log(styles.muted(` Duration: ${state.duration}s`));
|
|
80
|
+
}
|
|
81
|
+
if (state.flags.length > 0) {
|
|
82
|
+
this.log(styles.muted(` Flags: ${state.flags.join(' ')}`));
|
|
83
|
+
}
|
|
84
|
+
this.log('');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class CaffeinateStatus extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { styles } from '../../lib/styles.js';
|
|
3
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
5
|
+
import { isMacOS, getStatus } from '../../lib/caffeinate.js';
|
|
6
|
+
export default class CaffeinateStatus extends Command {
|
|
7
|
+
static description = 'Check if caffeinate is running';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> caffeinate status',
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
...machineOutputFlags,
|
|
13
|
+
};
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(CaffeinateStatus);
|
|
16
|
+
const jsonMode = shouldOutputJson(flags);
|
|
17
|
+
if (!isMacOS()) {
|
|
18
|
+
if (jsonMode) {
|
|
19
|
+
this.log(JSON.stringify({
|
|
20
|
+
type: 'error',
|
|
21
|
+
error: { code: 'UNSUPPORTED_PLATFORM', message: `caffeinate is only supported on macOS (current platform: ${process.platform})` },
|
|
22
|
+
}, null, 2));
|
|
23
|
+
this.exit(1);
|
|
24
|
+
}
|
|
25
|
+
this.error(`caffeinate is only supported on macOS (current platform: ${process.platform})`);
|
|
26
|
+
}
|
|
27
|
+
const { running, state } = getStatus();
|
|
28
|
+
if (jsonMode) {
|
|
29
|
+
this.log(JSON.stringify({
|
|
30
|
+
type: 'success',
|
|
31
|
+
result: {
|
|
32
|
+
running,
|
|
33
|
+
...(state ? { pid: state.pid, startedAt: state.startedAt, flags: state.flags, duration: state.duration ?? null } : {}),
|
|
34
|
+
},
|
|
35
|
+
}, null, 2));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.log(`\n${styles.header('Caffeinate Status')}`);
|
|
39
|
+
this.log('─'.repeat(40));
|
|
40
|
+
if (running && state) {
|
|
41
|
+
this.log(`${styles.success('Running')} ${styles.muted(`(PID: ${state.pid})`)}`);
|
|
42
|
+
this.log(styles.muted(` Started: ${state.startedAt}`));
|
|
43
|
+
if (state.flags.length > 0) {
|
|
44
|
+
this.log(styles.muted(` Flags: ${state.flags.join(' ')}`));
|
|
45
|
+
}
|
|
46
|
+
if (state.duration) {
|
|
47
|
+
this.log(styles.muted(` Duration: ${state.duration}s`));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
this.log(styles.muted('Not running'));
|
|
52
|
+
}
|
|
53
|
+
this.log('');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class CaffeinateStop extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import { styles } from '../../lib/styles.js';
|
|
3
|
+
import { shouldOutputJson } from '../../lib/prompt-json.js';
|
|
4
|
+
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
5
|
+
import { isMacOS, stopCaffeinate, getStatus } from '../../lib/caffeinate.js';
|
|
6
|
+
export default class CaffeinateStop extends Command {
|
|
7
|
+
static description = 'Stop the managed caffeinate process';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> caffeinate stop',
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
...machineOutputFlags,
|
|
13
|
+
};
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(CaffeinateStop);
|
|
16
|
+
const jsonMode = shouldOutputJson(flags);
|
|
17
|
+
if (!isMacOS()) {
|
|
18
|
+
if (jsonMode) {
|
|
19
|
+
this.log(JSON.stringify({
|
|
20
|
+
type: 'error',
|
|
21
|
+
error: { code: 'UNSUPPORTED_PLATFORM', message: `caffeinate is only supported on macOS (current platform: ${process.platform})` },
|
|
22
|
+
}, null, 2));
|
|
23
|
+
this.exit(1);
|
|
24
|
+
}
|
|
25
|
+
this.error(`caffeinate is only supported on macOS (current platform: ${process.platform})`);
|
|
26
|
+
}
|
|
27
|
+
// Get current state for reporting
|
|
28
|
+
const { state } = getStatus();
|
|
29
|
+
const stopped = stopCaffeinate();
|
|
30
|
+
if (jsonMode) {
|
|
31
|
+
this.log(JSON.stringify({
|
|
32
|
+
type: 'success',
|
|
33
|
+
result: {
|
|
34
|
+
stopped,
|
|
35
|
+
pid: state?.pid ?? null,
|
|
36
|
+
},
|
|
37
|
+
}, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (stopped) {
|
|
41
|
+
this.log(`\n${styles.success('caffeinate stopped')} ${styles.muted(`(PID: ${state?.pid})`)}\n`);
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
this.log(`\n${styles.muted('caffeinate is not running')}\n`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/dist/commands/commit.js
CHANGED
|
@@ -438,23 +438,25 @@ export default class Commit extends PromptCommand {
|
|
|
438
438
|
}
|
|
439
439
|
}
|
|
440
440
|
// Check if there are staged changes
|
|
441
|
+
let hasStagedChanges = true;
|
|
441
442
|
try {
|
|
442
443
|
const staged = execSync('git diff --cached --name-only', {
|
|
443
444
|
encoding: 'utf-8',
|
|
444
445
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
445
446
|
}).trim();
|
|
446
|
-
|
|
447
|
-
this.error('No staged changes to commit.\n\n' +
|
|
448
|
-
'Stage your changes first:\n' +
|
|
449
|
-
' git add <files>\n' +
|
|
450
|
-
' git add -A\n\n' +
|
|
451
|
-
'Or use --all flag:\n' +
|
|
452
|
-
' prlt commit --all "your message"');
|
|
453
|
-
}
|
|
447
|
+
hasStagedChanges = staged.length > 0;
|
|
454
448
|
}
|
|
455
449
|
catch {
|
|
456
450
|
// Ignore errors checking staged changes
|
|
457
451
|
}
|
|
452
|
+
if (!hasStagedChanges) {
|
|
453
|
+
this.error('No staged changes to commit.\n\n' +
|
|
454
|
+
'Stage your changes first:\n' +
|
|
455
|
+
' git add <files>\n' +
|
|
456
|
+
' git add -A\n\n' +
|
|
457
|
+
'Or use --all flag:\n' +
|
|
458
|
+
' prlt commit --all "your message"');
|
|
459
|
+
}
|
|
458
460
|
// Create commit
|
|
459
461
|
try {
|
|
460
462
|
execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
|
|
@@ -77,10 +77,9 @@ export default class Config extends PromptCommand {
|
|
|
77
77
|
}
|
|
78
78
|
// Handle --list or --json flag without --setting (just show config)
|
|
79
79
|
// Also handle non-TTY mode without explicit flags - output config as readable list
|
|
80
|
-
const
|
|
81
|
-
const shouldShowConfigList = flags.list || (isExplicitJsonMode && !flags.setting) || (isNonTTY() && !flags.setting && !flags.set?.length);
|
|
80
|
+
const shouldShowConfigList = flags.list || (jsonMode && !flags.setting) || (isNonTTY() && !flags.setting && !flags.set?.length);
|
|
82
81
|
if (shouldShowConfigList) {
|
|
83
|
-
if (
|
|
82
|
+
if (jsonMode) {
|
|
84
83
|
outputSuccessAsJson({
|
|
85
84
|
terminal: {
|
|
86
85
|
app: config.terminal.app,
|
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-'.
|
|
@@ -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.
|
|
@@ -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) {
|