@proletariat/cli 0.3.45 → 0.3.47
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/config/index.js +39 -1
- package/dist/commands/linear/auth.d.ts +14 -0
- package/dist/commands/linear/auth.js +211 -0
- package/dist/commands/linear/import.d.ts +21 -0
- package/dist/commands/linear/import.js +260 -0
- package/dist/commands/linear/status.d.ts +11 -0
- package/dist/commands/linear/status.js +88 -0
- package/dist/commands/linear/sync.d.ts +15 -0
- package/dist/commands/linear/sync.js +233 -0
- package/dist/commands/orchestrator/attach.d.ts +10 -1
- package/dist/commands/orchestrator/attach.js +102 -18
- package/dist/commands/orchestrator/index.js +22 -7
- package/dist/commands/orchestrator/start.d.ts +13 -1
- package/dist/commands/orchestrator/start.js +96 -25
- package/dist/commands/orchestrator/status.d.ts +1 -0
- package/dist/commands/orchestrator/status.js +10 -5
- package/dist/commands/orchestrator/stop.d.ts +1 -0
- package/dist/commands/orchestrator/stop.js +9 -4
- package/dist/commands/session/attach.js +32 -9
- package/dist/commands/ticket/link/duplicates.d.ts +15 -0
- package/dist/commands/ticket/link/duplicates.js +95 -0
- package/dist/commands/ticket/link/index.js +14 -0
- package/dist/commands/ticket/link/relates.d.ts +15 -0
- package/dist/commands/ticket/link/relates.js +95 -0
- package/dist/commands/work/index.js +4 -0
- package/dist/commands/work/review.d.ts +45 -0
- package/dist/commands/work/review.js +401 -0
- package/dist/commands/work/revise.js +4 -3
- package/dist/commands/work/spawn.d.ts +5 -0
- package/dist/commands/work/spawn.js +195 -14
- package/dist/commands/work/start.js +75 -19
- package/dist/hooks/init.js +18 -5
- package/dist/lib/database/native-validation.d.ts +21 -0
- package/dist/lib/database/native-validation.js +49 -0
- package/dist/lib/execution/config.d.ts +15 -0
- package/dist/lib/execution/config.js +54 -0
- package/dist/lib/execution/devcontainer.d.ts +6 -3
- package/dist/lib/execution/devcontainer.js +39 -12
- package/dist/lib/execution/runners.d.ts +28 -32
- package/dist/lib/execution/runners.js +353 -277
- package/dist/lib/execution/spawner.js +62 -5
- package/dist/lib/execution/types.d.ts +4 -0
- package/dist/lib/execution/types.js +3 -0
- package/dist/lib/external-issues/adapters.d.ts +26 -0
- package/dist/lib/external-issues/adapters.js +251 -0
- package/dist/lib/external-issues/index.d.ts +10 -0
- package/dist/lib/external-issues/index.js +14 -0
- package/dist/lib/external-issues/mapper.d.ts +21 -0
- package/dist/lib/external-issues/mapper.js +86 -0
- package/dist/lib/external-issues/types.d.ts +144 -0
- package/dist/lib/external-issues/types.js +26 -0
- package/dist/lib/external-issues/validation.d.ts +34 -0
- package/dist/lib/external-issues/validation.js +219 -0
- package/dist/lib/linear/client.d.ts +55 -0
- package/dist/lib/linear/client.js +254 -0
- package/dist/lib/linear/config.d.ts +37 -0
- package/dist/lib/linear/config.js +100 -0
- package/dist/lib/linear/index.d.ts +11 -0
- package/dist/lib/linear/index.js +10 -0
- package/dist/lib/linear/mapper.d.ts +67 -0
- package/dist/lib/linear/mapper.js +219 -0
- package/dist/lib/linear/sync.d.ts +37 -0
- package/dist/lib/linear/sync.js +89 -0
- package/dist/lib/linear/types.d.ts +139 -0
- package/dist/lib/linear/types.js +34 -0
- package/dist/lib/mcp/helpers.d.ts +8 -0
- package/dist/lib/mcp/helpers.js +10 -0
- package/dist/lib/mcp/tools/board.js +63 -11
- package/dist/lib/mcp/tools/work.js +36 -0
- package/dist/lib/pmo/schema.d.ts +2 -0
- package/dist/lib/pmo/schema.js +20 -0
- package/dist/lib/pmo/storage/base.js +92 -13
- package/dist/lib/pmo/storage/dependencies.js +15 -0
- package/dist/lib/prompt-json.d.ts +4 -0
- package/oclif.manifest.json +3205 -2537
- package/package.json +3 -2
|
@@ -11,8 +11,26 @@ import { DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
|
|
|
11
11
|
import { runExecution } from '../../lib/execution/runners.js';
|
|
12
12
|
import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
|
|
13
13
|
import { ExecutionStorage } from '../../lib/execution/storage.js';
|
|
14
|
-
import { loadExecutionConfig, getTerminalApp,
|
|
15
|
-
|
|
14
|
+
import { loadExecutionConfig, getTerminalApp, getShell, detectShell, detectTerminalApp, } from '../../lib/execution/config.js';
|
|
15
|
+
/**
|
|
16
|
+
* Build orchestrator tmux session name.
|
|
17
|
+
* Default: 'prlt-orchestrator-main'
|
|
18
|
+
* With --name: 'prlt-orchestrator-{name}'
|
|
19
|
+
*/
|
|
20
|
+
export function buildOrchestratorSessionName(name = 'main') {
|
|
21
|
+
const safeName = name
|
|
22
|
+
.replace(/[^a-zA-Z0-9._-]/g, '-')
|
|
23
|
+
.replace(/-+/g, '-')
|
|
24
|
+
.replace(/^-|-$/g, '');
|
|
25
|
+
return `prlt-orchestrator-${safeName || 'main'}`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Find running orchestrator session(s) by prefix match.
|
|
29
|
+
* Returns all tmux session names that start with 'prlt-orchestrator-'.
|
|
30
|
+
*/
|
|
31
|
+
export function findRunningOrchestratorSessions(hostSessions) {
|
|
32
|
+
return hostSessions.filter(s => s.startsWith('prlt-orchestrator-'));
|
|
33
|
+
}
|
|
16
34
|
export default class OrchestratorStart extends PromptCommand {
|
|
17
35
|
static description = 'Start the orchestrator agent in a tmux session';
|
|
18
36
|
static examples = [
|
|
@@ -47,36 +65,50 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
47
65
|
default: false,
|
|
48
66
|
exclusive: ['skip-permissions'],
|
|
49
67
|
}),
|
|
68
|
+
name: Flags.string({
|
|
69
|
+
char: 'n',
|
|
70
|
+
description: 'Name for the orchestrator session (default: main)',
|
|
71
|
+
}),
|
|
50
72
|
background: Flags.boolean({
|
|
51
73
|
char: 'b',
|
|
52
74
|
description: 'Start detached (don\'t open terminal tab)',
|
|
53
75
|
default: false,
|
|
76
|
+
exclusive: ['foreground'],
|
|
77
|
+
}),
|
|
78
|
+
foreground: Flags.boolean({
|
|
79
|
+
char: 'f',
|
|
80
|
+
description: 'Attach to the tmux session in the current terminal (blocking)',
|
|
81
|
+
default: false,
|
|
82
|
+
exclusive: ['background'],
|
|
54
83
|
}),
|
|
55
84
|
};
|
|
56
85
|
async run() {
|
|
57
86
|
const { flags } = await this.parse(OrchestratorStart);
|
|
58
87
|
const jsonMode = shouldOutputJson(flags);
|
|
88
|
+
const orchestratorName = flags.name || 'main';
|
|
89
|
+
const sessionName = buildOrchestratorSessionName(orchestratorName);
|
|
59
90
|
// Check if orchestrator is already running
|
|
60
91
|
const hostSessions = getHostTmuxSessionNames();
|
|
61
|
-
if (hostSessions.includes(
|
|
92
|
+
if (hostSessions.includes(sessionName)) {
|
|
62
93
|
if (jsonMode) {
|
|
63
|
-
outputErrorAsJson('ALREADY_RUNNING', `Orchestrator is already running (session: ${
|
|
94
|
+
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
95
|
return;
|
|
65
96
|
}
|
|
66
97
|
this.log('');
|
|
67
|
-
this.log(styles.warning(`Orchestrator is already running (session: ${
|
|
98
|
+
this.log(styles.warning(`Orchestrator is already running (session: ${sessionName})`));
|
|
68
99
|
this.log('');
|
|
100
|
+
const attachArgs = flags.name ? ['--name', flags.name] : [];
|
|
69
101
|
const { choice } = await this.prompt([{
|
|
70
102
|
type: 'list',
|
|
71
103
|
name: 'choice',
|
|
72
104
|
message: 'What would you like to do?',
|
|
73
105
|
choices: [
|
|
74
|
-
{ name: 'Attach to running orchestrator', value: 'attach', command:
|
|
106
|
+
{ name: 'Attach to running orchestrator', value: 'attach', command: `prlt orchestrator attach${flags.name ? ` --name ${flags.name}` : ''} --json` },
|
|
75
107
|
{ name: 'Cancel', value: 'cancel' },
|
|
76
108
|
],
|
|
77
109
|
}], jsonMode ? { flags, commandName: 'orchestrator start' } : null);
|
|
78
110
|
if (choice === 'attach') {
|
|
79
|
-
await this.config.runCommand('orchestrator:attach',
|
|
111
|
+
await this.config.runCommand('orchestrator:attach', attachArgs);
|
|
80
112
|
}
|
|
81
113
|
return;
|
|
82
114
|
}
|
|
@@ -169,12 +201,12 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
169
201
|
}
|
|
170
202
|
}
|
|
171
203
|
// Build execution context
|
|
172
|
-
// Use ticketId='prlt', actionName='orchestrator', agentName=
|
|
173
|
-
// so buildSessionName produces 'prlt-orchestrator-
|
|
204
|
+
// Use ticketId='prlt', actionName='orchestrator', agentName=orchestratorName
|
|
205
|
+
// so buildSessionName produces 'prlt-orchestrator-{name}'
|
|
174
206
|
const context = {
|
|
175
207
|
ticketId: 'prlt',
|
|
176
208
|
ticketTitle: 'Orchestrator',
|
|
177
|
-
agentName:
|
|
209
|
+
agentName: orchestratorName,
|
|
178
210
|
agentDir: hqPath,
|
|
179
211
|
worktreePath: hqPath,
|
|
180
212
|
branch: 'main',
|
|
@@ -202,19 +234,54 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
202
234
|
catch {
|
|
203
235
|
// Ignore config loading errors, use defaults
|
|
204
236
|
}
|
|
205
|
-
//
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
|
|
237
|
+
// Auto-detect shell (never prompt for orchestrator)
|
|
238
|
+
if (db) {
|
|
239
|
+
executionConfig.shell = await getShell(db);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
executionConfig.shell = detectShell() || 'zsh';
|
|
243
|
+
}
|
|
244
|
+
// Determine display mode
|
|
245
|
+
let displayMode;
|
|
246
|
+
if (flags.background) {
|
|
247
|
+
displayMode = 'background';
|
|
248
|
+
}
|
|
249
|
+
else if (flags.foreground) {
|
|
250
|
+
displayMode = 'foreground';
|
|
251
|
+
}
|
|
252
|
+
else {
|
|
253
|
+
const displayChoices = [
|
|
254
|
+
{ name: 'New terminal tab — opens attached to the tmux session', value: 'terminal', command: `prlt orchestrator start${flags.name ? ` --name ${flags.name}` : ''} --json` },
|
|
255
|
+
{ name: 'Current session — attach to tmux here (foreground, blocking)', value: 'foreground', command: `prlt orchestrator start${flags.name ? ` --name ${flags.name}` : ''} --foreground --json` },
|
|
256
|
+
{ name: 'Background — start detached, attach later', value: 'background', command: `prlt orchestrator start${flags.name ? ` --name ${flags.name}` : ''} --background --json` },
|
|
257
|
+
];
|
|
258
|
+
const displayMessage = 'How do you want to view the orchestrator?';
|
|
259
|
+
if (jsonMode) {
|
|
260
|
+
outputPromptAsJson(buildPromptConfig('list', 'displayMode', displayMessage, displayChoices), createMetadata('orchestrator start', flags));
|
|
261
|
+
return;
|
|
209
262
|
}
|
|
210
|
-
|
|
263
|
+
const { displayMode: selectedMode } = await this.prompt([{
|
|
264
|
+
type: 'list',
|
|
265
|
+
name: 'displayMode',
|
|
266
|
+
message: displayMessage,
|
|
267
|
+
choices: displayChoices,
|
|
268
|
+
}], jsonMode ? { flags, commandName: 'orchestrator start' } : null);
|
|
269
|
+
displayMode = selectedMode;
|
|
270
|
+
}
|
|
271
|
+
// For 'terminal' display mode, auto-detect terminal app
|
|
272
|
+
if (displayMode === 'terminal') {
|
|
273
|
+
if (db) {
|
|
211
274
|
executionConfig.terminal.app = await getTerminalApp(db);
|
|
212
275
|
}
|
|
213
|
-
if (!hasShellPreference(db)) {
|
|
214
|
-
executionConfig.shell = await promptShellPreference(db);
|
|
215
|
-
}
|
|
216
276
|
else {
|
|
217
|
-
|
|
277
|
+
const detected = detectTerminalApp();
|
|
278
|
+
if (detected) {
|
|
279
|
+
executionConfig.terminal.app = detected;
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
// Can't detect terminal and no db to prompt — fall back to foreground
|
|
283
|
+
displayMode = 'foreground';
|
|
284
|
+
}
|
|
218
285
|
}
|
|
219
286
|
}
|
|
220
287
|
// Show what we're doing
|
|
@@ -223,14 +290,17 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
223
290
|
this.log(styles.muted(` Starting orchestrator...`));
|
|
224
291
|
this.log(styles.muted(` Executor: ${selectedExecutor}`));
|
|
225
292
|
this.log(styles.muted(` Permission mode: ${sandboxed ? 'sandboxed' : 'skip-permissions'}`));
|
|
293
|
+
this.log(styles.muted(` Display mode: ${displayMode}`));
|
|
226
294
|
this.log(styles.muted(` Directory: ${hqPath}`));
|
|
295
|
+
if (orchestratorName !== 'main') {
|
|
296
|
+
this.log(styles.muted(` Name: ${orchestratorName}`));
|
|
297
|
+
}
|
|
227
298
|
if (actionPrompt) {
|
|
228
299
|
this.log(styles.muted(` Prompt: "${actionPrompt.substring(0, 60)}${actionPrompt.length > 60 ? '...' : ''}"`));
|
|
229
300
|
}
|
|
230
301
|
this.log('');
|
|
231
302
|
}
|
|
232
303
|
// Launch orchestrator
|
|
233
|
-
const displayMode = flags.background ? 'background' : 'terminal';
|
|
234
304
|
const result = await runExecution('host', context, selectedExecutor, executionConfig, {
|
|
235
305
|
displayMode,
|
|
236
306
|
});
|
|
@@ -246,7 +316,7 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
246
316
|
environment: 'host',
|
|
247
317
|
displayMode,
|
|
248
318
|
sandboxed,
|
|
249
|
-
sessionId: result.sessionId ||
|
|
319
|
+
sessionId: result.sessionId || sessionName,
|
|
250
320
|
});
|
|
251
321
|
}
|
|
252
322
|
catch {
|
|
@@ -255,17 +325,18 @@ export default class OrchestratorStart extends PromptCommand {
|
|
|
255
325
|
}
|
|
256
326
|
if (jsonMode) {
|
|
257
327
|
outputSuccessAsJson({
|
|
258
|
-
sessionId: result.sessionId ||
|
|
328
|
+
sessionId: result.sessionId || sessionName,
|
|
259
329
|
executor: selectedExecutor,
|
|
260
330
|
sandboxed,
|
|
261
331
|
displayMode,
|
|
262
332
|
directory: hqPath,
|
|
333
|
+
name: orchestratorName,
|
|
263
334
|
}, createMetadata('orchestrator start', flags));
|
|
264
335
|
}
|
|
265
|
-
if (
|
|
336
|
+
if (displayMode === 'background') {
|
|
266
337
|
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`));
|
|
338
|
+
this.log(styles.muted(` Session: ${result.sessionId || sessionName}`));
|
|
339
|
+
this.log(styles.muted(` Attach with: prlt orchestrator attach${flags.name ? ` --name ${flags.name}` : ''}`));
|
|
269
340
|
}
|
|
270
341
|
else {
|
|
271
342
|
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,7 @@ 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 { buildOrchestratorSessionName } from './start.js';
|
|
8
8
|
export default class OrchestratorStatus extends PromptCommand {
|
|
9
9
|
static description = 'Check if the orchestrator is running';
|
|
10
10
|
static examples = [
|
|
@@ -14,6 +14,10 @@ export default class OrchestratorStatus extends PromptCommand {
|
|
|
14
14
|
];
|
|
15
15
|
static flags = {
|
|
16
16
|
...machineOutputFlags,
|
|
17
|
+
name: Flags.string({
|
|
18
|
+
char: 'n',
|
|
19
|
+
description: 'Name of the orchestrator session to check (default: main)',
|
|
20
|
+
}),
|
|
17
21
|
peek: Flags.boolean({
|
|
18
22
|
description: 'Show recent output from the orchestrator',
|
|
19
23
|
default: false,
|
|
@@ -26,16 +30,17 @@ export default class OrchestratorStatus extends PromptCommand {
|
|
|
26
30
|
async run() {
|
|
27
31
|
const { flags } = await this.parse(OrchestratorStatus);
|
|
28
32
|
const jsonMode = shouldOutputJson(flags);
|
|
33
|
+
const sessionName = buildOrchestratorSessionName(flags.name || 'main');
|
|
29
34
|
const hostSessions = getHostTmuxSessionNames();
|
|
30
|
-
const isRunning = hostSessions.includes(
|
|
35
|
+
const isRunning = hostSessions.includes(sessionName);
|
|
31
36
|
let recentOutput = null;
|
|
32
37
|
if (isRunning && flags.peek) {
|
|
33
|
-
recentOutput = captureTmuxPane(
|
|
38
|
+
recentOutput = captureTmuxPane(sessionName, flags.lines);
|
|
34
39
|
}
|
|
35
40
|
if (jsonMode) {
|
|
36
41
|
outputSuccessAsJson({
|
|
37
42
|
running: isRunning,
|
|
38
|
-
sessionId: isRunning ?
|
|
43
|
+
sessionId: isRunning ? sessionName : null,
|
|
39
44
|
...(recentOutput !== null && { recentOutput }),
|
|
40
45
|
}, createMetadata('orchestrator status', flags));
|
|
41
46
|
return;
|
|
@@ -43,7 +48,7 @@ export default class OrchestratorStatus extends PromptCommand {
|
|
|
43
48
|
this.log('');
|
|
44
49
|
if (isRunning) {
|
|
45
50
|
this.log(styles.success(`Orchestrator is running`));
|
|
46
|
-
this.log(styles.muted(` Session: ${
|
|
51
|
+
this.log(styles.muted(` Session: ${sessionName}`));
|
|
47
52
|
this.log(styles.muted(` Attach: prlt orchestrator attach`));
|
|
48
53
|
this.log(styles.muted(` Poke: prlt session poke orchestrator "message"`));
|
|
49
54
|
if (recentOutput) {
|
|
@@ -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,7 @@ 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 } from './start.js';
|
|
14
14
|
export default class OrchestratorStop extends PromptCommand {
|
|
15
15
|
static description = 'Stop the running orchestrator';
|
|
16
16
|
static examples = [
|
|
@@ -19,6 +19,10 @@ export default class OrchestratorStop extends PromptCommand {
|
|
|
19
19
|
];
|
|
20
20
|
static flags = {
|
|
21
21
|
...machineOutputFlags,
|
|
22
|
+
name: Flags.string({
|
|
23
|
+
char: 'n',
|
|
24
|
+
description: 'Name of the orchestrator session to stop (default: main)',
|
|
25
|
+
}),
|
|
22
26
|
force: Flags.boolean({
|
|
23
27
|
char: 'f',
|
|
24
28
|
description: 'Skip confirmation',
|
|
@@ -28,9 +32,10 @@ export default class OrchestratorStop extends PromptCommand {
|
|
|
28
32
|
async run() {
|
|
29
33
|
const { flags } = await this.parse(OrchestratorStop);
|
|
30
34
|
const jsonMode = shouldOutputJson(flags);
|
|
35
|
+
const sessionName = buildOrchestratorSessionName(flags.name || 'main');
|
|
31
36
|
// Check if orchestrator session exists
|
|
32
37
|
const hostSessions = getHostTmuxSessionNames();
|
|
33
|
-
if (!hostSessions.includes(
|
|
38
|
+
if (!hostSessions.includes(sessionName)) {
|
|
34
39
|
if (jsonMode) {
|
|
35
40
|
outputErrorAsJson('NOT_RUNNING', 'Orchestrator is not running.', createMetadata('orchestrator stop', flags));
|
|
36
41
|
return;
|
|
@@ -58,7 +63,7 @@ export default class OrchestratorStop extends PromptCommand {
|
|
|
58
63
|
}
|
|
59
64
|
// Kill the tmux session
|
|
60
65
|
try {
|
|
61
|
-
execSync(`tmux kill-session -t "${
|
|
66
|
+
execSync(`tmux kill-session -t "${sessionName}"`, { stdio: 'pipe' });
|
|
62
67
|
}
|
|
63
68
|
catch (error) {
|
|
64
69
|
if (jsonMode) {
|
|
@@ -92,7 +97,7 @@ export default class OrchestratorStop extends PromptCommand {
|
|
|
92
97
|
}
|
|
93
98
|
if (jsonMode) {
|
|
94
99
|
outputSuccessAsJson({
|
|
95
|
-
sessionId:
|
|
100
|
+
sessionId: sessionName,
|
|
96
101
|
status: 'stopped',
|
|
97
102
|
}, createMetadata('orchestrator stop', flags));
|
|
98
103
|
return;
|
|
@@ -6,7 +6,8 @@ import * as os from 'node:os';
|
|
|
6
6
|
import Database from 'better-sqlite3';
|
|
7
7
|
import { styles } from '../../lib/styles.js';
|
|
8
8
|
import { getWorkspaceInfo } from '../../lib/agents/commands.js';
|
|
9
|
-
import { ExecutionStorage } from '../../lib/execution/index.js';
|
|
9
|
+
import { ExecutionStorage, loadExecutionConfig, shouldUseControlMode, buildTmuxAttachCommand } from '../../lib/execution/index.js';
|
|
10
|
+
import { detectTerminalApp } from '../orchestrator/attach.js';
|
|
10
11
|
import { parseSessionName, getHostTmuxSessionNames, getContainerTmuxSessionMap, flattenContainerSessions, findContainerSessionsByPrefix, findSessionForExecution, } from '../../lib/execution/session-utils.js';
|
|
11
12
|
import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
|
|
12
13
|
import { shouldOutputJson, outputErrorAsJson, createMetadata, } from '../../lib/prompt-json.js';
|
|
@@ -106,12 +107,32 @@ export default class SessionAttach extends PMOCommand {
|
|
|
106
107
|
// Attach to the session
|
|
107
108
|
this.log('');
|
|
108
109
|
this.log(styles.info(`Attaching to session: ${selectedSession.sessionId}`));
|
|
110
|
+
// Determine if we should use tmux control mode (-u -CC) for iTerm
|
|
111
|
+
let useControlMode = false;
|
|
112
|
+
try {
|
|
113
|
+
const workspaceInfo = getWorkspaceInfo();
|
|
114
|
+
const dbPath = path.join(workspaceInfo.path, '.proletariat', 'workspace.db');
|
|
115
|
+
const db = new Database(dbPath);
|
|
116
|
+
try {
|
|
117
|
+
const config = loadExecutionConfig(db);
|
|
118
|
+
const termApp = detectTerminalApp();
|
|
119
|
+
if (termApp === 'iTerm') {
|
|
120
|
+
useControlMode = shouldUseControlMode('iTerm', config.tmux.controlMode);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
db.close();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Not in a workspace or DB not available - fall back to no control mode
|
|
129
|
+
}
|
|
109
130
|
// Default to new tab unless --current-terminal is specified
|
|
110
131
|
if (flags['current-terminal']) {
|
|
111
|
-
await this.attachInCurrentTerminal(selectedSession);
|
|
132
|
+
await this.attachInCurrentTerminal(selectedSession, useControlMode);
|
|
112
133
|
}
|
|
113
134
|
else {
|
|
114
|
-
await this.attachInNewTab(selectedSession, flags.terminal);
|
|
135
|
+
await this.attachInNewTab(selectedSession, flags.terminal, useControlMode);
|
|
115
136
|
}
|
|
116
137
|
}
|
|
117
138
|
/**
|
|
@@ -247,13 +268,14 @@ export default class SessionAttach extends PMOCommand {
|
|
|
247
268
|
/**
|
|
248
269
|
* Attach to session in current terminal
|
|
249
270
|
*/
|
|
250
|
-
async attachInCurrentTerminal(session) {
|
|
271
|
+
async attachInCurrentTerminal(session, useControlMode) {
|
|
251
272
|
try {
|
|
273
|
+
const tmuxAttach = buildTmuxAttachCommand(useControlMode, session.type === 'container');
|
|
252
274
|
if (session.type === 'container' && session.containerId) {
|
|
253
|
-
execSync(`docker exec -it ${session.containerId}
|
|
275
|
+
execSync(`docker exec -it ${session.containerId} ${tmuxAttach} -t "${session.sessionId}"`, { stdio: 'inherit' });
|
|
254
276
|
}
|
|
255
277
|
else {
|
|
256
|
-
execSync(
|
|
278
|
+
execSync(`${tmuxAttach} -t "${session.sessionId}"`, { stdio: 'inherit' });
|
|
257
279
|
}
|
|
258
280
|
}
|
|
259
281
|
catch {
|
|
@@ -263,7 +285,7 @@ export default class SessionAttach extends PMOCommand {
|
|
|
263
285
|
/**
|
|
264
286
|
* Attach to session in a new terminal tab
|
|
265
287
|
*/
|
|
266
|
-
async attachInNewTab(session, terminalApp) {
|
|
288
|
+
async attachInNewTab(session, terminalApp, useControlMode) {
|
|
267
289
|
// Build a readable title for the tab
|
|
268
290
|
const title = `${session.ticketId} (${session.agentName})`;
|
|
269
291
|
// Create a script that sets tab title and attaches to tmux
|
|
@@ -271,9 +293,10 @@ export default class SessionAttach extends PMOCommand {
|
|
|
271
293
|
fs.mkdirSync(baseDir, { recursive: true });
|
|
272
294
|
const scriptPath = path.join(baseDir, `attach-${Date.now()}.sh`);
|
|
273
295
|
// Different attach command for container vs host sessions
|
|
296
|
+
const tmuxAttach = buildTmuxAttachCommand(useControlMode, session.type === 'container');
|
|
274
297
|
const attachCmd = session.type === 'container' && session.containerId
|
|
275
|
-
? `docker exec -it ${session.containerId}
|
|
276
|
-
:
|
|
298
|
+
? `docker exec -it ${session.containerId} ${tmuxAttach} -t "${session.sessionId}"`
|
|
299
|
+
: `${tmuxAttach} -t "${session.sessionId}"`;
|
|
277
300
|
const script = `#!/bin/bash
|
|
278
301
|
# Set terminal tab title
|
|
279
302
|
echo -ne "\\033]0;${title}\\007"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PMOCommand } from '../../../lib/pmo/index.js';
|
|
2
|
+
export default class TicketLinkDuplicates extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
ticket: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
original: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
execute(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Args } from '@oclif/core';
|
|
2
|
+
import { autoExportToBoard, PMOCommand, pmoBaseFlags } from '../../../lib/pmo/index.js';
|
|
3
|
+
import { styles } from '../../../lib/styles.js';
|
|
4
|
+
import { shouldOutputJson, outputPromptAsJson, outputSuccessAsJson, outputErrorAsJson, createMetadata, buildPromptConfig, } from '../../../lib/prompt-json.js';
|
|
5
|
+
export default class TicketLinkDuplicates extends PMOCommand {
|
|
6
|
+
static description = 'Mark a ticket as a duplicate of another';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %> TKT-001 TKT-002',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> TKT-001',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> TKT-001 --json',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
ticket: Args.string({
|
|
14
|
+
description: 'Ticket that is a duplicate',
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
original: Args.string({
|
|
18
|
+
description: 'Original ticket (that this duplicates)',
|
|
19
|
+
required: false,
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
static flags = {
|
|
23
|
+
...pmoBaseFlags,
|
|
24
|
+
};
|
|
25
|
+
async execute() {
|
|
26
|
+
const { args, flags } = await this.parse(TicketLinkDuplicates);
|
|
27
|
+
const jsonMode = shouldOutputJson(flags);
|
|
28
|
+
const projectId = await this.requireProject();
|
|
29
|
+
const handleError = (code, message) => {
|
|
30
|
+
if (jsonMode) {
|
|
31
|
+
outputErrorAsJson(code, message, createMetadata('ticket link duplicates', flags));
|
|
32
|
+
this.exit(1);
|
|
33
|
+
}
|
|
34
|
+
this.error(message);
|
|
35
|
+
};
|
|
36
|
+
// Verify the source ticket exists
|
|
37
|
+
const ticket = await this.storage.getTicket(args.ticket);
|
|
38
|
+
if (!ticket) {
|
|
39
|
+
return handleError('TICKET_NOT_FOUND', `Ticket not found: ${args.ticket}`);
|
|
40
|
+
}
|
|
41
|
+
// If original ticket not provided, prompt for selection
|
|
42
|
+
if (!args.original) {
|
|
43
|
+
const tickets = await this.storage.listTickets(projectId);
|
|
44
|
+
const otherTickets = tickets.filter(t => t.id !== args.ticket);
|
|
45
|
+
if (otherTickets.length === 0) {
|
|
46
|
+
return handleError('NO_TICKETS', 'No other tickets to select as original.');
|
|
47
|
+
}
|
|
48
|
+
const projectFlag = flags.project ? ` -P ${flags.project}` : '';
|
|
49
|
+
const choices = otherTickets.map(t => ({
|
|
50
|
+
name: `${t.id} - ${t.title}`,
|
|
51
|
+
value: t.id,
|
|
52
|
+
command: `prlt ticket link duplicates ${args.ticket} ${t.id}${projectFlag} --json`,
|
|
53
|
+
}));
|
|
54
|
+
const message = `Select the original ticket that ${args.ticket} duplicates:`;
|
|
55
|
+
if (jsonMode) {
|
|
56
|
+
outputPromptAsJson(buildPromptConfig('list', 'original', message, choices), createMetadata('ticket link duplicates', flags));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const { selected } = await this.prompt([{
|
|
60
|
+
type: 'list',
|
|
61
|
+
name: 'selected',
|
|
62
|
+
message,
|
|
63
|
+
choices,
|
|
64
|
+
}], null);
|
|
65
|
+
args.original = selected;
|
|
66
|
+
}
|
|
67
|
+
// Verify original ticket exists
|
|
68
|
+
const originalTicket = await this.storage.getTicket(args.original);
|
|
69
|
+
if (!originalTicket) {
|
|
70
|
+
return handleError('ORIGINAL_NOT_FOUND', `Original ticket not found: ${args.original}`);
|
|
71
|
+
}
|
|
72
|
+
// Create the duplicates dependency
|
|
73
|
+
try {
|
|
74
|
+
await this.storage.createTicketDependency(args.ticket, args.original, 'duplicates');
|
|
75
|
+
await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
|
|
76
|
+
if (jsonMode) {
|
|
77
|
+
outputSuccessAsJson({
|
|
78
|
+
ticketId: args.ticket,
|
|
79
|
+
originalTicketId: args.original,
|
|
80
|
+
type: 'duplicates',
|
|
81
|
+
}, createMetadata('ticket link duplicates', flags));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
this.log(styles.success(`\n${args.ticket} marked as duplicate of ${args.original}`));
|
|
85
|
+
this.log(styles.muted(` ${ticket.title}`));
|
|
86
|
+
this.log(styles.muted(` duplicates: ${originalTicket.title}`));
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error instanceof Error && error.message.includes('already exists')) {
|
|
90
|
+
return handleError('ALREADY_EXISTS', 'Duplicates dependency already exists.');
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -55,6 +55,8 @@ export default class TicketLink extends PMOCommand {
|
|
|
55
55
|
const projectFlag = flags.project ? ` -P ${flags.project}` : '';
|
|
56
56
|
const menuChoices = [
|
|
57
57
|
{ name: 'Add blocker', value: 'block', command: `prlt ticket link block ${args.ticket}${projectFlag} --json` },
|
|
58
|
+
{ name: 'Add related ticket', value: 'relates', command: `prlt ticket link relates ${args.ticket}${projectFlag} --json` },
|
|
59
|
+
{ name: 'Mark as duplicate', value: 'duplicates', command: `prlt ticket link duplicates ${args.ticket}${projectFlag} --json` },
|
|
58
60
|
{ name: 'View links', value: 'view', command: `prlt link list ${args.ticket}${projectFlag} --json` },
|
|
59
61
|
{ name: 'Remove link', value: 'remove', command: `prlt link remove ${args.ticket}${projectFlag} --json` },
|
|
60
62
|
{ name: 'Cancel', value: 'cancel', command: '' },
|
|
@@ -81,6 +83,18 @@ export default class TicketLink extends PMOCommand {
|
|
|
81
83
|
await cmd.run();
|
|
82
84
|
break;
|
|
83
85
|
}
|
|
86
|
+
case 'relates': {
|
|
87
|
+
const { default: RelatesCommand } = await import('./relates.js');
|
|
88
|
+
const cmd = new RelatesCommand([args.ticket, ...(flags.project ? ['-P', flags.project] : [])], this.config);
|
|
89
|
+
await cmd.run();
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
case 'duplicates': {
|
|
93
|
+
const { default: DuplicatesCommand } = await import('./duplicates.js');
|
|
94
|
+
const cmd = new DuplicatesCommand([args.ticket, ...(flags.project ? ['-P', flags.project] : [])], this.config);
|
|
95
|
+
await cmd.run();
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
84
98
|
case 'view': {
|
|
85
99
|
const { default: LinkListCommand } = await import('../../link/list.js');
|
|
86
100
|
const cmd = new LinkListCommand([args.ticket, ...(flags.project ? ['-P', flags.project] : [])], this.config);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { PMOCommand } from '../../../lib/pmo/index.js';
|
|
2
|
+
export default class TicketLinkRelates extends PMOCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
ticket: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
|
+
related: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
execute(): Promise<void>;
|
|
15
|
+
}
|