@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.
Files changed (48) 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/commit.js +10 -8
  10. package/dist/commands/config/index.js +2 -3
  11. package/dist/commands/init.js +9 -1
  12. package/dist/commands/orchestrator/attach.js +64 -14
  13. package/dist/commands/orchestrator/start.d.ts +4 -4
  14. package/dist/commands/orchestrator/start.js +26 -16
  15. package/dist/commands/orchestrator/status.js +64 -23
  16. package/dist/commands/orchestrator/stop.js +44 -12
  17. package/dist/commands/session/attach.js +23 -0
  18. package/dist/commands/session/poke.js +1 -1
  19. package/dist/commands/work/index.js +4 -0
  20. package/dist/commands/work/linear.d.ts +24 -0
  21. package/dist/commands/work/linear.js +195 -0
  22. package/dist/commands/work/spawn.js +28 -19
  23. package/dist/commands/work/start.js +12 -2
  24. package/dist/hooks/init.js +8 -0
  25. package/dist/lib/caffeinate.d.ts +64 -0
  26. package/dist/lib/caffeinate.js +146 -0
  27. package/dist/lib/execution/codex-adapter.d.ts +96 -0
  28. package/dist/lib/execution/codex-adapter.js +148 -0
  29. package/dist/lib/execution/index.d.ts +1 -0
  30. package/dist/lib/execution/index.js +1 -0
  31. package/dist/lib/execution/runners.js +50 -6
  32. package/dist/lib/external-issues/index.d.ts +1 -1
  33. package/dist/lib/external-issues/index.js +1 -1
  34. package/dist/lib/external-issues/linear.d.ts +37 -0
  35. package/dist/lib/external-issues/linear.js +198 -0
  36. package/dist/lib/external-issues/types.d.ts +67 -0
  37. package/dist/lib/external-issues/types.js +41 -0
  38. package/dist/lib/init/index.d.ts +4 -0
  39. package/dist/lib/init/index.js +11 -1
  40. package/dist/lib/machine-config.d.ts +1 -0
  41. package/dist/lib/machine-config.js +6 -3
  42. package/dist/lib/pmo/storage/actions.js +3 -3
  43. package/dist/lib/pmo/storage/base.js +85 -6
  44. package/dist/lib/pmo/storage/epics.js +1 -1
  45. package/dist/lib/pmo/storage/tickets.js +2 -2
  46. package/dist/lib/pmo/storage/types.d.ts +2 -1
  47. package/oclif.manifest.json +4363 -4037
  48. 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
+ }
@@ -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
- if (!staged) {
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 isExplicitJsonMode = (flags.json === true || flags.machine === true);
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 (isExplicitJsonMode) {
82
+ if (jsonMode) {
84
83
  outputSuccessAsJson({
85
84
  terminal: {
86
85
  app: config.terminal.app,
@@ -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-'.
@@ -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.
@@ -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) {