@proletariat/cli 0.3.51 → 0.3.52

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 (87) hide show
  1. package/dist/commands/agent/status.js +1 -0
  2. package/dist/commands/asana/connect.d.ts +15 -0
  3. package/dist/commands/asana/connect.js +267 -0
  4. package/dist/commands/asana/sync.d.ts +15 -0
  5. package/dist/commands/asana/sync.js +189 -0
  6. package/dist/commands/config/index.js +7 -1
  7. package/dist/commands/execution/list.js +3 -0
  8. package/dist/commands/execution/view.js +10 -0
  9. package/dist/commands/monday/connect.d.ts +16 -0
  10. package/dist/commands/monday/connect.js +212 -0
  11. package/dist/commands/monday/sync.d.ts +14 -0
  12. package/dist/commands/monday/sync.js +178 -0
  13. package/dist/commands/orchestrator/start.d.ts +6 -0
  14. package/dist/commands/orchestrator/start.js +149 -11
  15. package/dist/commands/session/list.js +6 -5
  16. package/dist/commands/work/index.js +7 -0
  17. package/dist/commands/work/jira.d.ts +28 -0
  18. package/dist/commands/work/jira.js +225 -0
  19. package/dist/commands/work/source/set.d.ts +12 -0
  20. package/dist/commands/work/source/set.js +52 -0
  21. package/dist/commands/work/source.d.ts +11 -0
  22. package/dist/commands/work/source.js +53 -0
  23. package/dist/commands/work/spawn.d.ts +1 -0
  24. package/dist/commands/work/spawn.js +73 -8
  25. package/dist/commands/work/start.d.ts +8 -0
  26. package/dist/commands/work/start.js +241 -3
  27. package/dist/lib/asana/client.d.ts +15 -0
  28. package/dist/lib/asana/client.js +120 -0
  29. package/dist/lib/asana/config.d.ts +9 -0
  30. package/dist/lib/asana/config.js +61 -0
  31. package/dist/lib/asana/index.d.ts +5 -0
  32. package/dist/lib/asana/index.js +4 -0
  33. package/dist/lib/asana/mapper.d.ts +13 -0
  34. package/dist/lib/asana/mapper.js +70 -0
  35. package/dist/lib/asana/sync.d.ts +13 -0
  36. package/dist/lib/asana/sync.js +36 -0
  37. package/dist/lib/asana/types.d.ts +40 -0
  38. package/dist/lib/asana/types.js +1 -0
  39. package/dist/lib/database/drizzle-schema.d.ts +393 -0
  40. package/dist/lib/database/drizzle-schema.js +45 -0
  41. package/dist/lib/execution/config.d.ts +10 -0
  42. package/dist/lib/execution/config.js +19 -0
  43. package/dist/lib/execution/runners.d.ts +10 -0
  44. package/dist/lib/execution/runners.js +110 -1
  45. package/dist/lib/execution/spawner.js +26 -0
  46. package/dist/lib/execution/storage.d.ts +4 -0
  47. package/dist/lib/execution/storage.js +8 -3
  48. package/dist/lib/execution/types.d.ts +4 -0
  49. package/dist/lib/external-issues/adapters.d.ts +18 -1
  50. package/dist/lib/external-issues/adapters.js +49 -1
  51. package/dist/lib/external-issues/index.d.ts +4 -1
  52. package/dist/lib/external-issues/index.js +5 -0
  53. package/dist/lib/external-issues/jira.d.ts +23 -0
  54. package/dist/lib/external-issues/jira.js +223 -0
  55. package/dist/lib/external-issues/linear.js +4 -3
  56. package/dist/lib/external-issues/mapper.d.ts +3 -2
  57. package/dist/lib/external-issues/mapper.js +5 -2
  58. package/dist/lib/external-issues/mapping-store.d.ts +12 -0
  59. package/dist/lib/external-issues/mapping-store.js +164 -0
  60. package/dist/lib/external-issues/types.d.ts +34 -0
  61. package/dist/lib/external-issues/validation.js +11 -0
  62. package/dist/lib/external-issues/work-start.d.ts +10 -0
  63. package/dist/lib/external-issues/work-start.js +12 -0
  64. package/dist/lib/linear/mapper.d.ts +2 -0
  65. package/dist/lib/linear/mapper.js +66 -2
  66. package/dist/lib/monday/client.d.ts +14 -0
  67. package/dist/lib/monday/client.js +113 -0
  68. package/dist/lib/monday/config.d.ts +10 -0
  69. package/dist/lib/monday/config.js +64 -0
  70. package/dist/lib/monday/index.d.ts +5 -0
  71. package/dist/lib/monday/index.js +4 -0
  72. package/dist/lib/monday/mapper.d.ts +14 -0
  73. package/dist/lib/monday/mapper.js +89 -0
  74. package/dist/lib/monday/sync.d.ts +13 -0
  75. package/dist/lib/monday/sync.js +45 -0
  76. package/dist/lib/monday/types.d.ts +38 -0
  77. package/dist/lib/monday/types.js +4 -0
  78. package/dist/lib/pmo/schema.d.ts +10 -1
  79. package/dist/lib/pmo/schema.js +73 -0
  80. package/dist/lib/pmo/storage/base.js +32 -0
  81. package/dist/lib/prompt-json.d.ts +11 -0
  82. package/dist/lib/work-source/config.d.ts +14 -0
  83. package/dist/lib/work-source/config.js +70 -0
  84. package/dist/lib/work-source/index.d.ts +1 -0
  85. package/dist/lib/work-source/index.js +1 -0
  86. package/oclif.manifest.json +2584 -2017
  87. package/package.json +1 -1
@@ -47,6 +47,8 @@ export default class SessionList extends PMOCommand {
47
47
  const runningExecutions = executionStorage?.listExecutions({ status: 'running' }) || [];
48
48
  const startingExecutions = executionStorage?.listExecutions({ status: 'starting' }) || [];
49
49
  const activeExecutions = [...runningExecutions, ...startingExecutions];
50
+ // Refresh execution state so dead sessions aren't reported as running.
51
+ executionStorage?.cleanupStaleExecutions();
50
52
  // Get list of actual tmux sessions for verification
51
53
  const hostTmuxSessions = getHostTmuxSessionNames();
52
54
  const containerTmuxSessions = getContainerTmuxSessionMap();
@@ -105,15 +107,14 @@ export default class SessionList extends PMOCommand {
105
107
  matchedHostSessions.add(actualSessionId);
106
108
  }
107
109
  }
108
- // Always include sessions from DB that have a sessionId.
109
- // When tmux verification fails (e.g., Docker/tmux not accessible from MCP context),
110
- // show the session with the DB status instead of silently dropping it.
111
- if (actualSessionId) {
110
+ // Only include active sessions by default.
111
+ // Use --all to include stale DB records (exists=false).
112
+ if (actualSessionId && (exists || flags.all)) {
112
113
  sessions.push({
113
114
  sessionId: actualSessionId,
114
115
  ticketId: exec.ticketId,
115
116
  agentName: exec.agentName,
116
- status: exists ? exec.status : (flags.all ? 'stale' : exec.status),
117
+ status: exists ? exec.status : 'stale',
117
118
  environment: isContainer ? 'container' : 'host',
118
119
  containerId,
119
120
  exists,
@@ -25,6 +25,8 @@ export default class Work extends PMOCommand {
25
25
  { id: 'status', name: 'View work status (in-progress tickets)', command: `prlt work status -P ${projectId} --json` },
26
26
  { id: 'start', name: 'Start work (launch single agent)', command: `prlt work start -P ${projectId} --json` },
27
27
  { id: 'linear', name: 'Spawn from Linear issue', command: `prlt work linear -P ${projectId} --json` },
28
+ { id: 'jira', name: 'Spawn from Jira issue', command: `prlt work jira -P ${projectId} --json` },
29
+ { id: 'source', name: 'Set active work source defaults', command: `prlt work source -P ${projectId} --json` },
28
30
  { id: 'resolve', name: 'Resolve questions (agent-assisted)', command: `prlt work resolve -P ${projectId} --json` },
29
31
  { id: 'spawn', name: 'Spawn work (batch by column)', command: `prlt work spawn -P ${projectId} --json` },
30
32
  { id: 'watch', name: 'Watch column (auto-spawn)', command: `prlt work watch -P ${projectId} --json` },
@@ -58,6 +60,11 @@ export default class Work extends PMOCommand {
58
60
  case 'linear':
59
61
  await this.config.runCommand('work:linear', projectArgs);
60
62
  break;
63
+ case 'jira':
64
+ await this.config.runCommand('work:jira', projectArgs);
65
+ case 'source':
66
+ await this.config.runCommand('work:source', projectArgs);
67
+ break;
61
68
  case 'resolve':
62
69
  await this.config.runCommand('work:resolve', projectArgs);
63
70
  break;
@@ -0,0 +1,28 @@
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class WorkJira extends PMOCommand {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ host: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ email: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ token: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ 'project-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ jql: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ issue: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
13
+ executor: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ display: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
+ action: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
16
+ message: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ 'run-on-host': import("@oclif/core/interfaces").BooleanFlag<boolean>;
18
+ 'skip-permissions': import("@oclif/core/interfaces").BooleanFlag<boolean>;
19
+ 'create-pr': import("@oclif/core/interfaces").BooleanFlag<boolean>;
20
+ yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
21
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
24
+ };
25
+ private findLinkedTicket;
26
+ private createOrUpdateLinkedTicket;
27
+ execute(): Promise<void>;
28
+ }
@@ -0,0 +1,225 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { PMOCommand, pmoBaseFlags, autoExportToBoard, } from '../../lib/pmo/index.js';
3
+ import { shouldOutputJson, } from '../../lib/prompt-json.js';
4
+ import { ExternalIssueAdapterError, } from '../../lib/external-issues/types.js';
5
+ import { listJiraIssues, getJiraIssueByKey, buildJiraIssueChoiceCommand, buildJiraTicketDescription, buildJiraMetadata, buildJiraSpawnContextMessage, } from '../../lib/external-issues/jira.js';
6
+ function buildWorkStartArgs(options) {
7
+ const args = [options.ticketId, '--project', options.projectId, '--ephemeral'];
8
+ if (options.executor)
9
+ args.push('--executor', options.executor);
10
+ if (options.display)
11
+ args.push('--display', options.display);
12
+ if (options.runOnHost)
13
+ args.push('--run-on-host');
14
+ if (options.skipPermissions)
15
+ args.push('--skip-permissions');
16
+ if (options.createPr)
17
+ args.push('--create-pr');
18
+ if (options.action)
19
+ args.push('--action', options.action);
20
+ if (options.message)
21
+ args.push('--message', options.message);
22
+ if (options.json)
23
+ args.push('--json');
24
+ if (options.machine)
25
+ args.push('--machine');
26
+ if (options.yes)
27
+ args.push('--yes');
28
+ return args;
29
+ }
30
+ export default class WorkJira extends PMOCommand {
31
+ static description = 'List/select Jira issues and spawn work using the existing work-start flow';
32
+ static examples = [
33
+ '<%= config.bin %> <%= command.id %> --host https://myorg.atlassian.net --project-key PROJ',
34
+ '<%= config.bin %> <%= command.id %> --host https://myorg.atlassian.net --issue PROJ-123',
35
+ '<%= config.bin %> <%= command.id %> --host https://myorg.atlassian.net --issue PROJ-123 --yes --skip-permissions --display terminal',
36
+ ];
37
+ static flags = {
38
+ ...pmoBaseFlags,
39
+ host: Flags.string({
40
+ description: 'Jira host URL (fallback: PRLT_JIRA_HOST or JIRA_HOST)',
41
+ }),
42
+ email: Flags.string({
43
+ description: 'Jira account email (fallback: PRLT_JIRA_EMAIL or JIRA_EMAIL)',
44
+ }),
45
+ token: Flags.string({
46
+ description: 'Jira API token (fallback: PRLT_JIRA_API_TOKEN or JIRA_API_TOKEN)',
47
+ }),
48
+ 'project-key': Flags.string({
49
+ description: 'Jira project key for listing issues (fallback: PRLT_JIRA_PROJECT or JIRA_PROJECT_KEY)',
50
+ }),
51
+ jql: Flags.string({
52
+ description: 'Custom JQL for listing issues (overrides project key filters)',
53
+ }),
54
+ issue: Flags.string({
55
+ description: 'Jira issue key (for example: PROJ-123)',
56
+ }),
57
+ limit: Flags.integer({
58
+ char: 'l',
59
+ description: 'Maximum number of Jira issues to fetch',
60
+ default: 20,
61
+ min: 1,
62
+ max: 100,
63
+ }),
64
+ executor: Flags.string({
65
+ char: 'e',
66
+ description: 'Override executor',
67
+ options: ['claude-code', 'codex', 'aider', 'custom'],
68
+ }),
69
+ display: Flags.string({
70
+ char: 'd',
71
+ description: 'Display mode',
72
+ options: ['terminal', 'background', 'foreground'],
73
+ }),
74
+ action: Flags.string({
75
+ char: 'A',
76
+ description: 'Action to run in work start (default: implement)',
77
+ default: 'implement',
78
+ }),
79
+ message: Flags.string({
80
+ description: 'Additional instructions appended to spawn context',
81
+ }),
82
+ 'run-on-host': Flags.boolean({
83
+ description: 'Run on host even if devcontainer exists',
84
+ default: false,
85
+ }),
86
+ 'skip-permissions': Flags.boolean({
87
+ description: 'Skip permission prompts (danger mode)',
88
+ default: false,
89
+ }),
90
+ 'create-pr': Flags.boolean({
91
+ description: 'Create PR when work is ready',
92
+ default: false,
93
+ }),
94
+ yes: Flags.boolean({
95
+ char: 'y',
96
+ description: 'Skip confirmation prompts in downstream work start',
97
+ default: false,
98
+ }),
99
+ };
100
+ async findLinkedTicket(projectId, envelope) {
101
+ const tickets = await this.storage.listTickets(projectId);
102
+ return tickets.find((ticket) => {
103
+ const source = ticket.metadata?.external_source;
104
+ const key = ticket.metadata?.external_key;
105
+ const id = ticket.metadata?.external_id;
106
+ return source === 'jira'
107
+ && (key === envelope.source.externalKey || id === envelope.source.externalId);
108
+ });
109
+ }
110
+ async createOrUpdateLinkedTicket(projectId, envelope) {
111
+ const existing = await this.findLinkedTicket(projectId, envelope);
112
+ const description = buildJiraTicketDescription(envelope);
113
+ const metadata = buildJiraMetadata(envelope);
114
+ if (existing) {
115
+ const updated = await this.storage.updateTicket(existing.id, {
116
+ title: envelope.title,
117
+ description,
118
+ priority: envelope.priority ?? undefined,
119
+ category: envelope.category ?? undefined,
120
+ labels: envelope.labels,
121
+ metadata: {
122
+ ...existing.metadata,
123
+ ...metadata,
124
+ },
125
+ });
126
+ return updated;
127
+ }
128
+ return this.storage.createTicket(projectId, {
129
+ title: envelope.title,
130
+ description,
131
+ priority: envelope.priority ?? undefined,
132
+ category: envelope.category ?? undefined,
133
+ labels: envelope.labels,
134
+ metadata,
135
+ });
136
+ }
137
+ async execute() {
138
+ const { flags } = await this.parse(WorkJira);
139
+ const jsonMode = shouldOutputJson(flags);
140
+ const projectId = await this.requireProject({
141
+ jsonMode: {
142
+ flags,
143
+ commandName: 'work jira',
144
+ baseCommand: 'prlt work jira',
145
+ },
146
+ });
147
+ const config = {
148
+ host: flags.host,
149
+ email: flags.email,
150
+ apiToken: flags.token,
151
+ projectKey: flags['project-key'],
152
+ jql: flags.jql,
153
+ };
154
+ let issues;
155
+ try {
156
+ issues = await listJiraIssues(config, {
157
+ limit: flags.limit,
158
+ });
159
+ }
160
+ catch (error) {
161
+ if (error instanceof ExternalIssueAdapterError) {
162
+ return this.handleError(error.code, error.message, { jsonMode, commandName: 'work jira', flags });
163
+ }
164
+ const msg = error instanceof Error ? error.message : 'Failed to fetch Jira issues.';
165
+ return this.handleError('JIRA_REQUEST_FAILED', msg, { jsonMode, commandName: 'work jira', flags });
166
+ }
167
+ if (issues.length === 0) {
168
+ return this.handleError('NO_JIRA_ISSUES', 'No matching Jira issues found for the configured query.', { jsonMode, commandName: 'work jira', flags });
169
+ }
170
+ let selectedIssue = issues.find(issue => issue.source.externalKey === flags.issue?.toUpperCase());
171
+ if (!selectedIssue && flags.issue) {
172
+ try {
173
+ selectedIssue = await getJiraIssueByKey(config, flags.issue) ?? undefined;
174
+ }
175
+ catch (error) {
176
+ if (error instanceof ExternalIssueAdapterError) {
177
+ return this.handleError(error.code, error.message, { jsonMode, commandName: 'work jira', flags });
178
+ }
179
+ const msg = error instanceof Error ? error.message : 'Failed to fetch Jira issue.';
180
+ return this.handleError('JIRA_REQUEST_FAILED', msg, { jsonMode, commandName: 'work jira', flags });
181
+ }
182
+ if (!selectedIssue) {
183
+ return this.handleError('JIRA_ISSUE_NOT_FOUND', `Jira issue "${flags.issue}" was not found.`, { jsonMode, commandName: 'work jira', flags });
184
+ }
185
+ }
186
+ if (!selectedIssue) {
187
+ const selectedKey = await this.selectFromList({
188
+ message: 'Select Jira issue to spawn:',
189
+ items: issues,
190
+ getName: (issue) => {
191
+ const priority = issue.priority || 'None';
192
+ return `[${priority}] ${issue.source.externalKey} - ${issue.title}`;
193
+ },
194
+ getValue: issue => issue.source.externalKey,
195
+ getCommand: issue => buildJiraIssueChoiceCommand(issue.source.externalKey, projectId),
196
+ jsonMode: jsonMode ? { flags, commandName: 'work jira' } : null,
197
+ });
198
+ if (!selectedKey) {
199
+ return;
200
+ }
201
+ selectedIssue = issues.find(issue => issue.source.externalKey === selectedKey);
202
+ if (!selectedIssue) {
203
+ return this.handleError('JIRA_ISSUE_NOT_FOUND', `Jira issue "${selectedKey}" was not found.`, { jsonMode, commandName: 'work jira', flags });
204
+ }
205
+ }
206
+ const ticket = await this.createOrUpdateLinkedTicket(projectId, selectedIssue);
207
+ await autoExportToBoard(this.pmoPath, this.storage);
208
+ const contextMessage = buildJiraSpawnContextMessage(selectedIssue, flags.message);
209
+ const args = buildWorkStartArgs({
210
+ ticketId: ticket.id,
211
+ projectId,
212
+ executor: flags.executor,
213
+ display: flags.display,
214
+ runOnHost: flags['run-on-host'],
215
+ skipPermissions: flags['skip-permissions'],
216
+ createPr: flags['create-pr'],
217
+ action: flags.action,
218
+ message: contextMessage,
219
+ json: flags.json,
220
+ machine: flags.machine,
221
+ yes: flags.yes,
222
+ });
223
+ await this.config.runCommand('work:start', args);
224
+ }
225
+ }
@@ -0,0 +1,12 @@
1
+ import { PMOCommand } from '../../../lib/pmo/index.js';
2
+ export default class WorkSourceSet extends PMOCommand {
3
+ static description: string;
4
+ static strict: boolean;
5
+ static examples: string[];
6
+ static flags: {
7
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
8
+ machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ };
11
+ execute(): Promise<void>;
12
+ }
@@ -0,0 +1,52 @@
1
+ import { PMOCommand, pmoBaseFlags } from '../../../lib/pmo/index.js';
2
+ import { styles } from '../../../lib/styles.js';
3
+ import { shouldOutputJson, outputErrorAsJson, outputSuccessAsJson, createMetadata, } from '../../../lib/prompt-json.js';
4
+ import { parseWorkSourceRef, formatWorkSourceRef, saveActiveWorkSource, } from '../../../lib/work-source/index.js';
5
+ export default class WorkSourceSet extends PMOCommand {
6
+ static description = 'Set the active work source used by "work spawn"';
7
+ static strict = false;
8
+ static examples = [
9
+ '<%= config.bin %> <%= command.id %> pmo',
10
+ '<%= config.bin %> <%= command.id %> linear:PRO',
11
+ '<%= config.bin %> <%= command.id %> jira:ABC',
12
+ ];
13
+ static flags = {
14
+ ...pmoBaseFlags,
15
+ };
16
+ async execute() {
17
+ const { flags, argv } = await this.parse(WorkSourceSet);
18
+ const jsonMode = shouldOutputJson(flags);
19
+ const sourceInput = argv[0]?.trim();
20
+ if (!sourceInput) {
21
+ const message = 'Usage: prlt work source set <provider[:context]>';
22
+ if (jsonMode) {
23
+ outputErrorAsJson('SOURCE_REQUIRED', message, createMetadata('work source set', flags));
24
+ }
25
+ this.error(message);
26
+ }
27
+ let source;
28
+ try {
29
+ source = parseWorkSourceRef(sourceInput);
30
+ }
31
+ catch (error) {
32
+ const message = error instanceof Error ? error.message : 'Invalid work source.';
33
+ if (jsonMode) {
34
+ outputErrorAsJson('INVALID_SOURCE', message, createMetadata('work source set', flags));
35
+ }
36
+ this.error(message);
37
+ }
38
+ const db = this.storage.getDatabase();
39
+ saveActiveWorkSource(db, source);
40
+ const ref = formatWorkSourceRef(source);
41
+ if (jsonMode) {
42
+ outputSuccessAsJson({
43
+ activeSource: {
44
+ provider: source.provider,
45
+ context: source.context ?? null,
46
+ ref,
47
+ },
48
+ }, createMetadata('work source set', flags));
49
+ }
50
+ this.log(styles.success(`Active work source set to ${ref}`));
51
+ }
52
+ }
@@ -0,0 +1,11 @@
1
+ import { PMOCommand } from '../../lib/pmo/index.js';
2
+ export default class WorkSource extends PMOCommand {
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
+ project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ };
10
+ execute(): Promise<void>;
11
+ }
@@ -0,0 +1,53 @@
1
+ import { PMOCommand, pmoBaseFlags } from '../../lib/pmo/index.js';
2
+ import { styles } from '../../lib/styles.js';
3
+ import { shouldOutputJson, outputSuccessAsJson, createMetadata, } from '../../lib/prompt-json.js';
4
+ import { loadActiveWorkSource, getRegisteredWorkSources, formatWorkSourceRef, } from '../../lib/work-source/index.js';
5
+ export default class WorkSource extends PMOCommand {
6
+ static description = 'Show the active work source used by "work spawn"';
7
+ static examples = [
8
+ '<%= config.bin %> <%= command.id %>',
9
+ '<%= config.bin %> work source set linear:PRO',
10
+ '<%= config.bin %> <%= command.id %> --json',
11
+ ];
12
+ static flags = {
13
+ ...pmoBaseFlags,
14
+ };
15
+ async execute() {
16
+ const { flags } = await this.parse(WorkSource);
17
+ const jsonMode = shouldOutputJson(flags);
18
+ const db = this.storage.getDatabase();
19
+ const active = loadActiveWorkSource(db);
20
+ const registered = getRegisteredWorkSources(db);
21
+ const activeResult = active ? {
22
+ provider: active.provider,
23
+ context: active.context ?? null,
24
+ ref: formatWorkSourceRef(active),
25
+ } : null;
26
+ const registeredResult = registered.map((source) => ({
27
+ provider: source.provider,
28
+ context: source.context ?? null,
29
+ ref: formatWorkSourceRef(source),
30
+ }));
31
+ if (jsonMode) {
32
+ outputSuccessAsJson({
33
+ activeSource: activeResult,
34
+ registeredSources: registeredResult,
35
+ }, createMetadata('work source', flags));
36
+ }
37
+ this.log(styles.header('Work Source'));
38
+ if (active) {
39
+ this.log(styles.success(` Active: ${formatWorkSourceRef(active)}`));
40
+ this.log(styles.muted(` Provider: ${active.provider}`));
41
+ if (active.context) {
42
+ this.log(styles.muted(` Context: ${active.context}`));
43
+ }
44
+ }
45
+ else {
46
+ this.log(styles.muted(' Active: not set'));
47
+ this.log(styles.muted(' Default behavior: PMO tickets unless an external source is selected'));
48
+ }
49
+ this.log('');
50
+ this.log(styles.muted(`Registered sources: ${registeredResult.map(source => source.ref).join(', ')}`));
51
+ this.log(styles.muted('Set with: prlt work source set <provider[:context]>'));
52
+ }
53
+ }
@@ -32,6 +32,7 @@ export default class WorkSpawn extends PMOCommand {
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
34
  'from-linear': import("@oclif/core/interfaces").BooleanFlag<boolean>;
35
+ from: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
35
36
  'linear-team': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
36
37
  'linear-state': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
37
38
  'linear-label': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -10,6 +10,7 @@ import { shouldOutputJson, outputSuccessAsJson, outputErrorAsJson, createMetadat
10
10
  import { FlagResolver } from '../../lib/flags/index.js';
11
11
  import { loadDietConfig, formatDietConfig, } from '../../lib/pmo/diet.js';
12
12
  import { LinearClient, LinearMapper, isLinearConfigured, loadLinearConfig, } from '../../lib/linear/index.js';
13
+ import { parseWorkSourceRef, formatWorkSourceRef, loadActiveWorkSource, saveActiveWorkSource, getRegisteredWorkSources, } from '../../lib/work-source/index.js';
13
14
  export default class WorkSpawn extends PMOCommand {
14
15
  static description = 'Spawn work for multiple tickets by column (batch mode)';
15
16
  static strict = false; // Allow multiple ticket ID args without defining them
@@ -28,6 +29,8 @@ export default class WorkSpawn extends PMOCommand {
28
29
  '<%= config.bin %> <%= command.id %> --count 5 --priority P0 --action implement # Filtered by priority',
29
30
  '<%= config.bin %> <%= command.id %> --from-linear # Pull Linear issues → PMO → spawn',
30
31
  '<%= config.bin %> <%= command.id %> --from-linear ENG-123 ENG-124 # Pull specific Linear issues → spawn',
32
+ '<%= config.bin %> <%= command.id %> --from linear:ENG # Use source override (provider[:context])',
33
+ '<%= config.bin %> work source set linear:ENG # Persist default source for future spawn',
31
34
  '<%= config.bin %> <%= command.id %> --from-linear --linear-team ENG # Pull from specific team',
32
35
  '<%= config.bin %> <%= command.id %> --from-linear --linear-state "In Progress" # Filter by state',
33
36
  '<%= config.bin %> <%= command.id %> --count 10 --diet --category ship,grow --action groom # Combined',
@@ -152,22 +155,21 @@ export default class WorkSpawn extends PMOCommand {
152
155
  description: 'Pull issues from Linear, mirror into PMO, then spawn agents',
153
156
  default: false,
154
157
  }),
158
+ from: Flags.string({
159
+ description: 'Source override in provider[:context] format (e.g., pmo, linear:PRO)',
160
+ }),
155
161
  'linear-team': Flags.string({
156
162
  description: 'Linear team key to pull issues from (e.g., ENG)',
157
- dependsOn: ['from-linear'],
158
163
  }),
159
164
  'linear-state': Flags.string({
160
165
  description: 'Filter Linear issues by state name (e.g., "In Progress")',
161
- dependsOn: ['from-linear'],
162
166
  }),
163
167
  'linear-label': Flags.string({
164
168
  description: 'Filter Linear issues by label name',
165
- dependsOn: ['from-linear'],
166
169
  }),
167
170
  'linear-limit': Flags.integer({
168
171
  description: 'Maximum number of Linear issues to pull',
169
172
  default: 20,
170
- dependsOn: ['from-linear'],
171
173
  }),
172
174
  };
173
175
  async execute() {
@@ -192,10 +194,70 @@ export default class WorkSpawn extends PMOCommand {
192
194
  }
193
195
  // Parse ticket IDs from args (everything after flags)
194
196
  let ticketIdArgs = argv;
197
+ // Resolve source precedence:
198
+ // 1) --from override (new canonical behavior)
199
+ // 2) --from-linear (legacy compatibility)
200
+ // 3) persisted active source
201
+ // 4) one-time prompt when multiple sources are registered
202
+ let resolvedSource = null;
203
+ const sourceDb = this.storage.getDatabase();
204
+ if (flags.from && flags['from-linear']) {
205
+ return handleError('CONFLICTING_SOURCE_FLAGS', '--from and --from-linear cannot be used together.');
206
+ }
207
+ if (flags.from) {
208
+ try {
209
+ resolvedSource = parseWorkSourceRef(flags.from);
210
+ }
211
+ catch (error) {
212
+ const message = error instanceof Error ? error.message : 'Invalid value for --from';
213
+ return handleError('INVALID_SOURCE_OVERRIDE', message);
214
+ }
215
+ }
216
+ else if (flags['from-linear']) {
217
+ resolvedSource = {
218
+ provider: 'linear',
219
+ context: flags['linear-team'],
220
+ };
221
+ }
222
+ else {
223
+ const activeSource = loadActiveWorkSource(sourceDb);
224
+ if (activeSource) {
225
+ resolvedSource = activeSource;
226
+ }
227
+ else {
228
+ const registeredSources = getRegisteredWorkSources(sourceDb);
229
+ if (registeredSources.length > 1) {
230
+ const selectedRef = await this.selectFromList({
231
+ message: 'Select the default work source for this workspace:',
232
+ items: registeredSources,
233
+ getName: (source) => {
234
+ const ref = formatWorkSourceRef(source);
235
+ return source.provider === 'linear'
236
+ ? `Linear (${source.context ?? 'default team'})`
237
+ : ref.toUpperCase();
238
+ },
239
+ getValue: source => formatWorkSourceRef(source),
240
+ getCommand: source => `prlt work spawn --from ${formatWorkSourceRef(source)} --json`,
241
+ jsonMode: jsonMode ? { flags, commandName: 'work spawn' } : null,
242
+ });
243
+ if (!selectedRef) {
244
+ return;
245
+ }
246
+ resolvedSource = parseWorkSourceRef(selectedRef);
247
+ saveActiveWorkSource(sourceDb, resolvedSource);
248
+ if (!jsonMode) {
249
+ this.log(styles.muted(`Saved active work source: ${formatWorkSourceRef(resolvedSource)}`));
250
+ }
251
+ }
252
+ else if (registeredSources.length === 1) {
253
+ resolvedSource = registeredSources[0];
254
+ }
255
+ }
256
+ }
195
257
  // =========================================================================
196
- // --from-linear: Pull Linear issues → mirror into PMO → spawn from PMO
258
+ // Linear source: Pull Linear issues → mirror into PMO → spawn from PMO
197
259
  // =========================================================================
198
- if (flags['from-linear']) {
260
+ if (resolvedSource?.provider === 'linear') {
199
261
  const db = this.storage.getDatabase();
200
262
  if (!isLinearConfigured(db)) {
201
263
  return handleError('LINEAR_NOT_CONFIGURED', 'Linear is not configured. Run "prlt linear auth" first.');
@@ -208,7 +270,7 @@ export default class WorkSpawn extends PMOCommand {
208
270
  jsonMode: jsonMode ? {
209
271
  flags,
210
272
  commandName: 'work spawn',
211
- baseCommand: 'prlt work spawn --from-linear',
273
+ baseCommand: `prlt work spawn --from ${formatWorkSourceRef(resolvedSource)}`,
212
274
  } : undefined,
213
275
  });
214
276
  // Get project workflow statuses for mapping
@@ -245,7 +307,7 @@ export default class WorkSpawn extends PMOCommand {
245
307
  else {
246
308
  // Fetch issues using filters
247
309
  const filter = {
248
- teamKey: flags['linear-team'] ?? linearConfig.defaultTeamKey,
310
+ teamKey: resolvedSource.context ?? flags['linear-team'] ?? linearConfig.defaultTeamKey,
249
311
  stateName: flags['linear-state'],
250
312
  labelName: flags['linear-label'],
251
313
  limit: flags['linear-limit'],
@@ -297,6 +359,9 @@ export default class WorkSpawn extends PMOCommand {
297
359
  // Replace ticket args with the imported PMO ticket IDs
298
360
  ticketIdArgs = pmoTicketIds;
299
361
  }
362
+ else if (resolvedSource && resolvedSource.provider !== 'pmo') {
363
+ return handleError('SOURCE_NOT_SUPPORTED', `Source "${resolvedSource.provider}" is configured but not supported by work spawn yet. Use --from pmo or --from linear[:team].`);
364
+ }
300
365
  // Try to infer project from ticket IDs if provided
301
366
  let projectId;
302
367
  if (ticketIdArgs.length > 0) {
@@ -11,6 +11,10 @@ export default class WorkStart extends PMOCommand {
11
11
  action: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
12
  prompt: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
13
  message: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
+ 'from-issue': import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
+ source: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ key: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
+ 'mirror-to-pmo': import("@oclif/core/interfaces").BooleanFlag<boolean>;
14
18
  watch: import("@oclif/core/interfaces").BooleanFlag<boolean>;
15
19
  force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
20
  'vm-host': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
@@ -33,6 +37,10 @@ export default class WorkStart extends PMOCommand {
33
37
  machine: import("@oclif/core/interfaces").BooleanFlag<boolean>;
34
38
  project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
35
39
  };
40
+ private findLinkedTicketByEnvelope;
41
+ private createOrUpdateLinkedTicket;
42
+ private fetchExternalIssue;
43
+ private resolveIssueSourceAndKey;
36
44
  execute(): Promise<void>;
37
45
  /**
38
46
  * Run batch mode: spawn work for all unassigned backlog tickets