@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
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const SUPPORTED_NODE_MAJORS = [20, 22, 23, 24, 25]
|
|
4
|
+
|
|
5
|
+
function parseNodeMajor(version) {
|
|
6
|
+
const match = /^v?(\d+)/.exec(version)
|
|
7
|
+
return match ? Number.parseInt(match[1], 10) : null
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function runtimeInfo() {
|
|
11
|
+
return {
|
|
12
|
+
nodeVersion: process.version,
|
|
13
|
+
nodeMajor: parseNodeMajor(process.version),
|
|
14
|
+
abi: process.versions.modules,
|
|
15
|
+
platform: process.platform,
|
|
16
|
+
arch: process.arch,
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildMessage(error, context) {
|
|
21
|
+
const info = runtimeInfo()
|
|
22
|
+
const nodeMajorHint = info.nodeMajor === null || SUPPORTED_NODE_MAJORS.includes(info.nodeMajor)
|
|
23
|
+
? ''
|
|
24
|
+
: `\n- Unsupported Node major for this CLI: ${info.nodeMajor} (supported: ${SUPPORTED_NODE_MAJORS.join(', ')})`
|
|
25
|
+
const reason = error instanceof Error ? error.message : String(error)
|
|
26
|
+
|
|
27
|
+
return [
|
|
28
|
+
`better-sqlite3 native module validation failed (${context}).`,
|
|
29
|
+
`Runtime: node ${info.nodeVersion} (ABI ${info.abi}) on ${info.platform}-${info.arch}.${nodeMajorHint}`,
|
|
30
|
+
`Load error: ${reason}`,
|
|
31
|
+
'',
|
|
32
|
+
'Fix steps:',
|
|
33
|
+
'1. Rebuild native bindings for this runtime: npm rebuild better-sqlite3',
|
|
34
|
+
'2. Verify runtime architecture: node -p "process.platform + \'-\' + process.arch + \' abi=\' + process.versions.modules"',
|
|
35
|
+
'3. Reinstall with your active Node version if needed: npm uninstall -g @proletariat/cli && npm install -g @proletariat/cli',
|
|
36
|
+
].join('\n')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function validate(context) {
|
|
40
|
+
try {
|
|
41
|
+
const Database = require('better-sqlite3')
|
|
42
|
+
const db = new Database(':memory:')
|
|
43
|
+
db.pragma('foreign_keys = ON')
|
|
44
|
+
db.close()
|
|
45
|
+
} catch (error) {
|
|
46
|
+
throw new Error(buildMessage(error, context))
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
validate('postinstall')
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
54
|
+
process.exit(1)
|
|
55
|
+
}
|
|
@@ -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)) {
|
|
@@ -9,6 +9,7 @@ export default class OrchestratorAttach extends PromptCommand {
|
|
|
9
9
|
static description: string;
|
|
10
10
|
static examples: string[];
|
|
11
11
|
static flags: {
|
|
12
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
13
|
'new-tab': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
14
|
terminal: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
15
|
'current-terminal': import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
@@ -3,12 +3,17 @@ import { execSync } from 'node:child_process';
|
|
|
3
3
|
import * as path from 'node:path';
|
|
4
4
|
import * as fs from 'node:fs';
|
|
5
5
|
import * as os from 'node:os';
|
|
6
|
+
import Database from 'better-sqlite3';
|
|
6
7
|
import { PromptCommand } from '../../lib/prompt-command.js';
|
|
7
8
|
import { machineOutputFlags } from '../../lib/pmo/index.js';
|
|
8
9
|
import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
9
10
|
import { styles } from '../../lib/styles.js';
|
|
10
11
|
import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
|
|
11
|
-
import {
|
|
12
|
+
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
13
|
+
import { findHQRoot } from '../../lib/workspace.js';
|
|
14
|
+
import { getHeadquartersNameFromPath } from '../../lib/machine-config.js';
|
|
15
|
+
import { loadExecutionConfig, shouldUseControlMode, buildTmuxAttachCommand } from '../../lib/execution/index.js';
|
|
16
|
+
import { buildOrchestratorSessionName, findRunningOrchestratorSessions } from './start.js';
|
|
12
17
|
/**
|
|
13
18
|
* Detect the terminal emulator from environment variables.
|
|
14
19
|
* Returns a terminal app name suitable for AppleScript tab creation,
|
|
@@ -24,8 +29,6 @@ export function detectTerminalApp() {
|
|
|
24
29
|
return null;
|
|
25
30
|
}
|
|
26
31
|
const termProgram = process.env.TERM_PROGRAM;
|
|
27
|
-
if (!termProgram)
|
|
28
|
-
return null;
|
|
29
32
|
switch (termProgram) {
|
|
30
33
|
case 'iTerm.app':
|
|
31
34
|
return 'iTerm';
|
|
@@ -35,9 +38,13 @@ export function detectTerminalApp() {
|
|
|
35
38
|
return 'Terminal';
|
|
36
39
|
case 'WezTerm':
|
|
37
40
|
return 'WezTerm';
|
|
38
|
-
default:
|
|
39
|
-
return null;
|
|
40
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;
|
|
41
48
|
}
|
|
42
49
|
export default class OrchestratorAttach extends PromptCommand {
|
|
43
50
|
static description = 'Attach to the running orchestrator tmux session';
|
|
@@ -48,6 +55,10 @@ export default class OrchestratorAttach extends PromptCommand {
|
|
|
48
55
|
];
|
|
49
56
|
static flags = {
|
|
50
57
|
...machineOutputFlags,
|
|
58
|
+
name: Flags.string({
|
|
59
|
+
char: 'n',
|
|
60
|
+
description: 'Name of the orchestrator session to attach to (default: main)',
|
|
61
|
+
}),
|
|
51
62
|
'new-tab': Flags.boolean({
|
|
52
63
|
description: 'Open in a new terminal tab instead of attaching in the current terminal',
|
|
53
64
|
default: false,
|
|
@@ -66,22 +77,55 @@ export default class OrchestratorAttach extends PromptCommand {
|
|
|
66
77
|
async run() {
|
|
67
78
|
const { flags } = await this.parse(OrchestratorAttach);
|
|
68
79
|
const jsonMode = shouldOutputJson(flags);
|
|
69
|
-
// Check if orchestrator session exists
|
|
70
80
|
const hostSessions = getHostTmuxSessionNames();
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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('');
|
|
74
103
|
return;
|
|
75
104
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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) {
|
|
80
124
|
return;
|
|
81
125
|
}
|
|
82
126
|
if (jsonMode) {
|
|
83
127
|
outputSuccessAsJson({
|
|
84
|
-
sessionId:
|
|
128
|
+
sessionId: sessionName,
|
|
85
129
|
status: 'attaching',
|
|
86
130
|
}, createMetadata('orchestrator attach', flags));
|
|
87
131
|
return;
|
|
@@ -93,7 +137,27 @@ export default class OrchestratorAttach extends PromptCommand {
|
|
|
93
137
|
this.log(styles.warning('--terminal has no effect without --new-tab. Ignoring.'));
|
|
94
138
|
}
|
|
95
139
|
this.log('');
|
|
96
|
-
this.log(styles.info(`Attaching to orchestrator session: ${
|
|
140
|
+
this.log(styles.info(`Attaching to orchestrator session: ${sessionName}`));
|
|
141
|
+
// Determine if we should use tmux control mode (-u -CC) for iTerm
|
|
142
|
+
let useControlMode = false;
|
|
143
|
+
try {
|
|
144
|
+
const workspaceInfo = getWorkspaceInfo();
|
|
145
|
+
const dbPath = path.join(workspaceInfo.path, '.proletariat', 'workspace.db');
|
|
146
|
+
const db = new Database(dbPath);
|
|
147
|
+
try {
|
|
148
|
+
const config = loadExecutionConfig(db);
|
|
149
|
+
const termApp = detectTerminalApp();
|
|
150
|
+
if (termApp === 'iTerm') {
|
|
151
|
+
useControlMode = shouldUseControlMode('iTerm', config.tmux.controlMode);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
db.close();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Not in a workspace or DB not available - fall back to no control mode
|
|
160
|
+
}
|
|
97
161
|
if (flags['new-tab']) {
|
|
98
162
|
// Determine terminal app: explicit flag > auto-detect > error
|
|
99
163
|
const terminalApp = flags.terminal ?? detectTerminalApp();
|
|
@@ -102,26 +166,39 @@ export default class OrchestratorAttach extends PromptCommand {
|
|
|
102
166
|
this.log(styles.muted('Falling back to direct tmux attach in current terminal.'));
|
|
103
167
|
this.log(styles.muted('Tip: Use --terminal <app> to specify your terminal (iTerm, Terminal, Ghostty).'));
|
|
104
168
|
this.log('');
|
|
105
|
-
this.attachInCurrentTerminal();
|
|
169
|
+
this.attachInCurrentTerminal(useControlMode, sessionName);
|
|
106
170
|
return;
|
|
107
171
|
}
|
|
108
|
-
await this.openInNewTab(terminalApp);
|
|
172
|
+
await this.openInNewTab(terminalApp, useControlMode, sessionName);
|
|
109
173
|
}
|
|
110
174
|
else {
|
|
111
|
-
this.attachInCurrentTerminal();
|
|
175
|
+
this.attachInCurrentTerminal(useControlMode, sessionName);
|
|
112
176
|
}
|
|
113
177
|
}
|
|
114
|
-
attachInCurrentTerminal() {
|
|
178
|
+
attachInCurrentTerminal(useControlMode, sessionName) {
|
|
115
179
|
try {
|
|
116
|
-
|
|
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
|
+
}
|
|
190
|
+
const tmuxAttach = buildTmuxAttachCommand(useControlMode);
|
|
191
|
+
execSync(`${tmuxAttach} -t "${sessionName}"`, { stdio: 'inherit' });
|
|
117
192
|
}
|
|
118
193
|
catch {
|
|
119
|
-
this.error(`Failed to attach to orchestrator session "${
|
|
194
|
+
this.error(`Failed to attach to orchestrator session "${sessionName}"`);
|
|
120
195
|
}
|
|
121
196
|
}
|
|
122
|
-
async openInNewTab(terminalApp) {
|
|
197
|
+
async openInNewTab(terminalApp, useControlMode, sessionName) {
|
|
123
198
|
const title = 'Orchestrator';
|
|
124
|
-
const
|
|
199
|
+
const tmuxAttach = buildTmuxAttachCommand(useControlMode);
|
|
200
|
+
const attachCmd = `${tmuxAttach} -t "${sessionName}"`;
|
|
201
|
+
const mouseMode = useControlMode ? 'off' : 'on';
|
|
125
202
|
const baseDir = path.join(os.homedir(), '.proletariat', 'scripts');
|
|
126
203
|
fs.mkdirSync(baseDir, { recursive: true });
|
|
127
204
|
const scriptPath = path.join(baseDir, `attach-orch-${Date.now()}.sh`);
|
|
@@ -130,7 +207,10 @@ export default class OrchestratorAttach extends PromptCommand {
|
|
|
130
207
|
echo -ne "\\033]0;${title}\\007"
|
|
131
208
|
echo -ne "\\033]1;${title}\\007"
|
|
132
209
|
|
|
133
|
-
|
|
210
|
+
# Set mouse mode before attaching
|
|
211
|
+
tmux set-option -t "${sessionName}" mouse ${mouseMode} 2>/dev/null || true
|
|
212
|
+
|
|
213
|
+
echo "Attaching to: ${sessionName}"
|
|
134
214
|
${attachCmd}
|
|
135
215
|
|
|
136
216
|
# Clean up
|