@proletariat/cli 0.3.44 → 0.3.46

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 (73) hide show
  1. package/dist/commands/agent/list.js +2 -3
  2. package/dist/commands/agent/login.js +2 -2
  3. package/dist/commands/agent/rebuild.js +2 -3
  4. package/dist/commands/agent/shell.js +2 -2
  5. package/dist/commands/agent/status.js +3 -3
  6. package/dist/commands/agent/visit.js +2 -2
  7. package/dist/commands/config/index.js +39 -1
  8. package/dist/commands/linear/auth.d.ts +14 -0
  9. package/dist/commands/linear/auth.js +211 -0
  10. package/dist/commands/linear/import.d.ts +21 -0
  11. package/dist/commands/linear/import.js +260 -0
  12. package/dist/commands/linear/status.d.ts +11 -0
  13. package/dist/commands/linear/status.js +88 -0
  14. package/dist/commands/linear/sync.d.ts +15 -0
  15. package/dist/commands/linear/sync.js +233 -0
  16. package/dist/commands/orchestrator/attach.d.ts +9 -1
  17. package/dist/commands/orchestrator/attach.js +67 -13
  18. package/dist/commands/orchestrator/index.js +22 -7
  19. package/dist/commands/staff/list.js +2 -3
  20. package/dist/commands/ticket/link/duplicates.d.ts +15 -0
  21. package/dist/commands/ticket/link/duplicates.js +95 -0
  22. package/dist/commands/ticket/link/index.js +14 -0
  23. package/dist/commands/ticket/link/relates.d.ts +15 -0
  24. package/dist/commands/ticket/link/relates.js +95 -0
  25. package/dist/commands/work/revise.js +7 -6
  26. package/dist/commands/work/spawn.d.ts +5 -0
  27. package/dist/commands/work/spawn.js +195 -14
  28. package/dist/commands/work/start.js +79 -23
  29. package/dist/commands/work/watch.js +2 -2
  30. package/dist/lib/agents/commands.d.ts +11 -0
  31. package/dist/lib/agents/commands.js +40 -10
  32. package/dist/lib/execution/config.d.ts +15 -0
  33. package/dist/lib/execution/config.js +54 -0
  34. package/dist/lib/execution/devcontainer.d.ts +6 -3
  35. package/dist/lib/execution/devcontainer.js +39 -12
  36. package/dist/lib/execution/runners.d.ts +28 -32
  37. package/dist/lib/execution/runners.js +345 -271
  38. package/dist/lib/execution/spawner.js +65 -7
  39. package/dist/lib/execution/types.d.ts +4 -0
  40. package/dist/lib/execution/types.js +3 -0
  41. package/dist/lib/external-issues/adapters.d.ts +26 -0
  42. package/dist/lib/external-issues/adapters.js +251 -0
  43. package/dist/lib/external-issues/index.d.ts +10 -0
  44. package/dist/lib/external-issues/index.js +14 -0
  45. package/dist/lib/external-issues/mapper.d.ts +21 -0
  46. package/dist/lib/external-issues/mapper.js +86 -0
  47. package/dist/lib/external-issues/types.d.ts +144 -0
  48. package/dist/lib/external-issues/types.js +26 -0
  49. package/dist/lib/external-issues/validation.d.ts +34 -0
  50. package/dist/lib/external-issues/validation.js +219 -0
  51. package/dist/lib/linear/client.d.ts +55 -0
  52. package/dist/lib/linear/client.js +254 -0
  53. package/dist/lib/linear/config.d.ts +37 -0
  54. package/dist/lib/linear/config.js +100 -0
  55. package/dist/lib/linear/index.d.ts +11 -0
  56. package/dist/lib/linear/index.js +10 -0
  57. package/dist/lib/linear/mapper.d.ts +67 -0
  58. package/dist/lib/linear/mapper.js +219 -0
  59. package/dist/lib/linear/sync.d.ts +37 -0
  60. package/dist/lib/linear/sync.js +89 -0
  61. package/dist/lib/linear/types.d.ts +139 -0
  62. package/dist/lib/linear/types.js +34 -0
  63. package/dist/lib/mcp/helpers.d.ts +8 -0
  64. package/dist/lib/mcp/helpers.js +10 -0
  65. package/dist/lib/mcp/tools/board.js +63 -11
  66. package/dist/lib/pmo/schema.d.ts +2 -0
  67. package/dist/lib/pmo/schema.js +20 -0
  68. package/dist/lib/pmo/storage/base.js +92 -13
  69. package/dist/lib/pmo/storage/dependencies.js +15 -0
  70. package/dist/lib/prompt-json.d.ts +4 -0
  71. package/dist/lib/themes.js +32 -16
  72. package/oclif.manifest.json +2823 -2336
  73. package/package.json +2 -1
@@ -9,23 +9,58 @@ import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadat
9
9
  import { styles } from '../../lib/styles.js';
10
10
  import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
11
11
  import { ORCHESTRATOR_SESSION_NAME } from './start.js';
12
+ /**
13
+ * Detect the terminal emulator from environment variables.
14
+ * Returns a terminal app name suitable for AppleScript tab creation,
15
+ * or null if detection fails or we're in a remote/headless environment.
16
+ */
17
+ export function detectTerminalApp() {
18
+ // Remote sessions should never attempt AppleScript/GUI operations
19
+ if (process.env.SSH_TTY || process.env.SSH_CONNECTION) {
20
+ return null;
21
+ }
22
+ // Headless / no display — skip GUI attempts
23
+ if (process.platform !== 'darwin' && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY) {
24
+ return null;
25
+ }
26
+ const termProgram = process.env.TERM_PROGRAM;
27
+ if (!termProgram)
28
+ return null;
29
+ switch (termProgram) {
30
+ case 'iTerm.app':
31
+ return 'iTerm';
32
+ case 'ghostty':
33
+ return 'Ghostty';
34
+ case 'Apple_Terminal':
35
+ return 'Terminal';
36
+ case 'WezTerm':
37
+ return 'WezTerm';
38
+ default:
39
+ return null;
40
+ }
41
+ }
12
42
  export default class OrchestratorAttach extends PromptCommand {
13
43
  static description = 'Attach to the running orchestrator tmux session';
14
44
  static examples = [
15
45
  '<%= config.bin %> <%= command.id %>',
16
- '<%= config.bin %> <%= command.id %> --current-terminal',
46
+ '<%= config.bin %> <%= command.id %> --new-tab',
47
+ '<%= config.bin %> <%= command.id %> --new-tab --terminal Ghostty',
17
48
  ];
18
49
  static flags = {
19
50
  ...machineOutputFlags,
20
- 'current-terminal': Flags.boolean({
21
- char: 'c',
22
- description: 'Attach in current terminal instead of new tab',
51
+ 'new-tab': Flags.boolean({
52
+ description: 'Open in a new terminal tab instead of attaching in the current terminal',
23
53
  default: false,
24
54
  }),
25
55
  terminal: Flags.string({
26
56
  char: 't',
27
- description: 'Terminal app to use (iTerm, Terminal, Ghostty)',
28
- default: 'iTerm',
57
+ description: 'Terminal app to use for new tab (iTerm, Terminal, Ghostty). Auto-detected if not specified.',
58
+ }),
59
+ 'current-terminal': Flags.boolean({
60
+ char: 'c',
61
+ description: '[deprecated] Attach in current terminal (this is now the default behavior)',
62
+ hidden: true,
63
+ default: false,
29
64
  }),
30
65
  };
31
66
  async run() {
@@ -51,18 +86,37 @@ export default class OrchestratorAttach extends PromptCommand {
51
86
  }, createMetadata('orchestrator attach', flags));
52
87
  return;
53
88
  }
89
+ if (flags['current-terminal']) {
90
+ this.log(styles.warning('--current-terminal is deprecated. Direct tmux attach is now the default behavior.'));
91
+ }
92
+ if (flags.terminal && !flags['new-tab']) {
93
+ this.log(styles.warning('--terminal has no effect without --new-tab. Ignoring.'));
94
+ }
54
95
  this.log('');
55
96
  this.log(styles.info(`Attaching to orchestrator session: ${ORCHESTRATOR_SESSION_NAME}`));
56
- if (flags['current-terminal']) {
57
- try {
58
- execSync(`tmux attach -t "${ORCHESTRATOR_SESSION_NAME}"`, { stdio: 'inherit' });
59
- }
60
- catch {
61
- this.error(`Failed to attach to orchestrator session "${ORCHESTRATOR_SESSION_NAME}"`);
97
+ if (flags['new-tab']) {
98
+ // Determine terminal app: explicit flag > auto-detect > error
99
+ const terminalApp = flags.terminal ?? detectTerminalApp();
100
+ if (!terminalApp) {
101
+ this.log(styles.warning('Could not detect terminal emulator for new tab.'));
102
+ this.log(styles.muted('Falling back to direct tmux attach in current terminal.'));
103
+ this.log(styles.muted('Tip: Use --terminal <app> to specify your terminal (iTerm, Terminal, Ghostty).'));
104
+ this.log('');
105
+ this.attachInCurrentTerminal();
106
+ return;
62
107
  }
108
+ await this.openInNewTab(terminalApp);
63
109
  }
64
110
  else {
65
- await this.openInNewTab(flags.terminal);
111
+ this.attachInCurrentTerminal();
112
+ }
113
+ }
114
+ attachInCurrentTerminal() {
115
+ try {
116
+ execSync(`tmux attach -t "${ORCHESTRATOR_SESSION_NAME}"`, { stdio: 'inherit' });
117
+ }
118
+ catch {
119
+ this.error(`Failed to attach to orchestrator session "${ORCHESTRATOR_SESSION_NAME}"`);
66
120
  }
67
121
  }
68
122
  async openInNewTab(terminalApp) {
@@ -1,5 +1,7 @@
1
1
  import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
2
2
  import { shouldOutputJson } from '../../lib/prompt-json.js';
3
+ import { getHostTmuxSessionNames } from '../../lib/execution/session-utils.js';
4
+ import { ORCHESTRATOR_SESSION_NAME } from './start.js';
3
5
  export default class Orchestrator extends PMOCommand {
4
6
  static description = 'Manage the orchestrator agent (start, attach, status, stop)';
5
7
  static examples = [
@@ -18,17 +20,30 @@ export default class Orchestrator extends PMOCommand {
18
20
  async execute() {
19
21
  const { flags } = await this.parse(Orchestrator);
20
22
  const jsonModeConfig = shouldOutputJson(flags) ? { flags, commandName: 'orchestrator' } : null;
23
+ // Check if orchestrator is currently running to offer contextual options
24
+ const hostSessions = getHostTmuxSessionNames();
25
+ const isRunning = hostSessions.includes(ORCHESTRATOR_SESSION_NAME);
26
+ // When running, show "Attach to running session" first since that's the likely intent
27
+ const choices = isRunning
28
+ ? [
29
+ { name: 'Attach to running session', value: 'attach', command: 'prlt orchestrator attach --json' },
30
+ { name: 'Start orchestrator', value: 'start', command: 'prlt orchestrator start --json' },
31
+ { name: 'Check orchestrator status', value: 'status', command: 'prlt orchestrator status --json' },
32
+ { name: 'Stop orchestrator', value: 'stop', command: 'prlt orchestrator stop --json' },
33
+ { name: 'Cancel', value: 'cancel' },
34
+ ]
35
+ : [
36
+ { name: 'Start orchestrator', value: 'start', command: 'prlt orchestrator start --json' },
37
+ { name: 'Attach to orchestrator', value: 'attach', command: 'prlt orchestrator attach --json' },
38
+ { name: 'Check orchestrator status', value: 'status', command: 'prlt orchestrator status --json' },
39
+ { name: 'Stop orchestrator', value: 'stop', command: 'prlt orchestrator stop --json' },
40
+ { name: 'Cancel', value: 'cancel' },
41
+ ];
21
42
  const { action } = await this.prompt([{
22
43
  type: 'list',
23
44
  name: 'action',
24
45
  message: 'Orchestrator - What would you like to do?',
25
- choices: [
26
- { name: 'Start orchestrator', value: 'start', command: 'prlt orchestrator start --json' },
27
- { name: 'Attach to orchestrator', value: 'attach', command: 'prlt orchestrator attach --json' },
28
- { name: 'Check orchestrator status', value: 'status', command: 'prlt orchestrator status --json' },
29
- { name: 'Stop orchestrator', value: 'stop', command: 'prlt orchestrator stop --json' },
30
- { name: 'Cancel', value: 'cancel' },
31
- ],
46
+ choices,
32
47
  }], jsonModeConfig);
33
48
  if (action === 'cancel') {
34
49
  return;
@@ -1,8 +1,7 @@
1
1
  import { Command } from '@oclif/core';
2
2
  import chalk from 'chalk';
3
- import * as path from 'node:path';
4
3
  import * as fs from 'node:fs';
5
- import { getWorkspaceInfo, getAllAgentsStatus } from '../../lib/agents/commands.js';
4
+ import { getWorkspaceInfo, getAllAgentsStatus, resolveAgentDir } from '../../lib/agents/commands.js';
6
5
  import { shouldOutputJson } from '../../lib/prompt-json.js';
7
6
  import { machineOutputFlags } from '../../lib/pmo/index.js';
8
7
  export default class List extends Command {
@@ -73,7 +72,7 @@ export default class List extends Command {
73
72
  }
74
73
  }
75
74
  else {
76
- const agentDir = path.join(workspaceInfo.agentsPath, agentStatus.name);
75
+ const agentDir = resolveAgentDir(workspaceInfo, agentStatus.name);
77
76
  const dirExists = fs.existsSync(agentDir);
78
77
  if (dirExists) {
79
78
  this.log(chalk.red(` Invalid or broken worktrees`));
@@ -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
+ }
@@ -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 TicketLinkRelates extends PMOCommand {
6
+ static description = 'Add a relates-to dependency between two tickets';
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: 'First ticket',
15
+ required: true,
16
+ }),
17
+ related: Args.string({
18
+ description: 'Second ticket (related)',
19
+ required: false,
20
+ }),
21
+ };
22
+ static flags = {
23
+ ...pmoBaseFlags,
24
+ };
25
+ async execute() {
26
+ const { args, flags } = await this.parse(TicketLinkRelates);
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 relates', 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 related ticket not provided, prompt for selection
42
+ if (!args.related) {
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 related.');
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 relates ${args.ticket} ${t.id}${projectFlag} --json`,
53
+ }));
54
+ const message = `Select ticket to relate to ${args.ticket}:`;
55
+ if (jsonMode) {
56
+ outputPromptAsJson(buildPromptConfig('list', 'related', message, choices), createMetadata('ticket link relates', flags));
57
+ return;
58
+ }
59
+ const { selected } = await this.prompt([{
60
+ type: 'list',
61
+ name: 'selected',
62
+ message,
63
+ choices,
64
+ }], null);
65
+ args.related = selected;
66
+ }
67
+ // Verify related ticket exists
68
+ const relatedTicket = await this.storage.getTicket(args.related);
69
+ if (!relatedTicket) {
70
+ return handleError('RELATED_NOT_FOUND', `Related ticket not found: ${args.related}`);
71
+ }
72
+ // Create the relates_to dependency
73
+ try {
74
+ await this.storage.createTicketDependency(args.ticket, args.related, 'relates_to');
75
+ await autoExportToBoard(this.pmoPath, this.storage, (msg) => this.log(styles.muted(msg)));
76
+ if (jsonMode) {
77
+ outputSuccessAsJson({
78
+ ticketId: args.ticket,
79
+ relatedTicketId: args.related,
80
+ type: 'relates_to',
81
+ }, createMetadata('ticket link relates', flags));
82
+ return;
83
+ }
84
+ this.log(styles.success(`\n${args.ticket} now relates to ${args.related}`));
85
+ this.log(styles.muted(` ${ticket.title}`));
86
+ this.log(styles.muted(` relates to: ${relatedTicket.title}`));
87
+ }
88
+ catch (error) {
89
+ if (error instanceof Error && error.message.includes('already exists')) {
90
+ return handleError('ALREADY_EXISTS', 'Relates-to dependency already exists.');
91
+ }
92
+ throw error;
93
+ }
94
+ }
95
+ }
@@ -6,9 +6,9 @@ import Database from 'better-sqlite3';
6
6
  import { PMOCommand, pmoBaseFlags, autoExportToBoard } from '../../lib/pmo/index.js';
7
7
  import { getWorkColumnSetting, findColumnByName } from '../../lib/pmo/utils.js';
8
8
  import { styles } from '../../lib/styles.js';
9
- import { getWorkspaceInfo } from '../../lib/agents/commands.js';
9
+ import { getWorkspaceInfo, resolveAgentDir } from '../../lib/agents/commands.js';
10
10
  import { DEFAULT_EXECUTION_CONFIG, } from '../../lib/execution/types.js';
11
- import { runExecution, isDockerRunning, isDevcontainerCliInstalled } from '../../lib/execution/runners.js';
11
+ import { runExecution, isDockerRunning, isDevcontainerCliInstalled, getExecutorDisplayName } from '../../lib/execution/runners.js';
12
12
  import { ExecutionStorage } from '../../lib/execution/storage.js';
13
13
  import { loadExecutionConfig, getTerminalApp, getShell, hasTerminalPreference, hasShellPreference } from '../../lib/execution/config.js';
14
14
  import { hasDevcontainerConfig } from '../../lib/execution/devcontainer.js';
@@ -179,8 +179,8 @@ export default class WorkRevise extends PMOCommand {
179
179
  this.error(`Ticket "${ticketId}" already has work in progress: ${runningExecution.id}\n` +
180
180
  `Stop it first with "prlt work stop ${runningExecution.id}"`);
181
181
  }
182
- // Find worktree path
183
- const agentDir = path.join(workspaceInfo.agentsPath, agentName);
182
+ // Find worktree path (handles staff and temp agents)
183
+ const agentDir = resolveAgentDir(workspaceInfo, agentName);
184
184
  if (!fs.existsSync(agentDir)) {
185
185
  db.close();
186
186
  this.error(`Agent directory not found at ${agentDir}.`);
@@ -242,12 +242,14 @@ export default class WorkRevise extends PMOCommand {
242
242
  // Host environment: terminal/background are display modes
243
243
  displayMode = flags.mode;
244
244
  }
245
+ const executor = flags.executor || DEFAULT_EXECUTION_CONFIG.defaultExecutor;
246
+ const executorName = getExecutorDisplayName(executor);
245
247
  // Permission mode
246
248
  const { permissionMode } = await this.prompt([
247
249
  {
248
250
  type: 'list',
249
251
  name: 'permissionMode',
250
- message: 'Permission mode for Claude Code:',
252
+ message: `Permission mode for ${executorName}:`,
251
253
  choices: [
252
254
  { name: 'danger - Skip permission checks (faster for revisions)', value: 'danger', command: `prlt work revise ${ticketId} --json` },
253
255
  { name: 'safe - Requires approval for dangerous operations', value: 'safe', command: `prlt work revise ${ticketId} --json` },
@@ -256,7 +258,6 @@ export default class WorkRevise extends PMOCommand {
256
258
  },
257
259
  ], reviseJsonModeConfig);
258
260
  sandboxed = permissionMode === 'safe';
259
- const executor = flags.executor || DEFAULT_EXECUTION_CONFIG.defaultExecutor;
260
261
  // Show execution info
261
262
  this.log('');
262
263
  this.log(styles.header(`Revising: ${ticket.id}: ${ticket.title}`));
@@ -31,6 +31,11 @@ export default class WorkSpawn extends PMOCommand {
31
31
  priority: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
32
32
  epic: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
33
33
  status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
34
+ 'from-linear': import("@oclif/core/interfaces").BooleanFlag<boolean>;
35
+ 'linear-team': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
36
+ 'linear-state': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
37
+ 'linear-label': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
38
+ 'linear-limit': import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
34
39
  json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
35
40
  machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
36
41
  project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;