@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.
- package/dist/commands/agent/status.js +1 -0
- package/dist/commands/asana/connect.d.ts +15 -0
- package/dist/commands/asana/connect.js +267 -0
- package/dist/commands/asana/sync.d.ts +15 -0
- package/dist/commands/asana/sync.js +189 -0
- package/dist/commands/config/index.js +7 -1
- package/dist/commands/execution/list.js +3 -0
- package/dist/commands/execution/view.js +10 -0
- package/dist/commands/monday/connect.d.ts +16 -0
- package/dist/commands/monday/connect.js +212 -0
- package/dist/commands/monday/sync.d.ts +14 -0
- package/dist/commands/monday/sync.js +178 -0
- package/dist/commands/orchestrator/start.d.ts +6 -0
- package/dist/commands/orchestrator/start.js +149 -11
- package/dist/commands/session/list.js +6 -5
- package/dist/commands/work/index.js +7 -0
- package/dist/commands/work/jira.d.ts +28 -0
- package/dist/commands/work/jira.js +225 -0
- package/dist/commands/work/source/set.d.ts +12 -0
- package/dist/commands/work/source/set.js +52 -0
- package/dist/commands/work/source.d.ts +11 -0
- package/dist/commands/work/source.js +53 -0
- package/dist/commands/work/spawn.d.ts +1 -0
- package/dist/commands/work/spawn.js +73 -8
- package/dist/commands/work/start.d.ts +8 -0
- package/dist/commands/work/start.js +241 -3
- package/dist/lib/asana/client.d.ts +15 -0
- package/dist/lib/asana/client.js +120 -0
- package/dist/lib/asana/config.d.ts +9 -0
- package/dist/lib/asana/config.js +61 -0
- package/dist/lib/asana/index.d.ts +5 -0
- package/dist/lib/asana/index.js +4 -0
- package/dist/lib/asana/mapper.d.ts +13 -0
- package/dist/lib/asana/mapper.js +70 -0
- package/dist/lib/asana/sync.d.ts +13 -0
- package/dist/lib/asana/sync.js +36 -0
- package/dist/lib/asana/types.d.ts +40 -0
- package/dist/lib/asana/types.js +1 -0
- package/dist/lib/database/drizzle-schema.d.ts +393 -0
- package/dist/lib/database/drizzle-schema.js +45 -0
- package/dist/lib/execution/config.d.ts +10 -0
- package/dist/lib/execution/config.js +19 -0
- package/dist/lib/execution/runners.d.ts +10 -0
- package/dist/lib/execution/runners.js +110 -1
- package/dist/lib/execution/spawner.js +26 -0
- package/dist/lib/execution/storage.d.ts +4 -0
- package/dist/lib/execution/storage.js +8 -3
- package/dist/lib/execution/types.d.ts +4 -0
- package/dist/lib/external-issues/adapters.d.ts +18 -1
- package/dist/lib/external-issues/adapters.js +49 -1
- package/dist/lib/external-issues/index.d.ts +4 -1
- package/dist/lib/external-issues/index.js +5 -0
- package/dist/lib/external-issues/jira.d.ts +23 -0
- package/dist/lib/external-issues/jira.js +223 -0
- package/dist/lib/external-issues/linear.js +4 -3
- package/dist/lib/external-issues/mapper.d.ts +3 -2
- package/dist/lib/external-issues/mapper.js +5 -2
- package/dist/lib/external-issues/mapping-store.d.ts +12 -0
- package/dist/lib/external-issues/mapping-store.js +164 -0
- package/dist/lib/external-issues/types.d.ts +34 -0
- package/dist/lib/external-issues/validation.js +11 -0
- package/dist/lib/external-issues/work-start.d.ts +10 -0
- package/dist/lib/external-issues/work-start.js +12 -0
- package/dist/lib/linear/mapper.d.ts +2 -0
- package/dist/lib/linear/mapper.js +66 -2
- package/dist/lib/monday/client.d.ts +14 -0
- package/dist/lib/monday/client.js +113 -0
- package/dist/lib/monday/config.d.ts +10 -0
- package/dist/lib/monday/config.js +64 -0
- package/dist/lib/monday/index.d.ts +5 -0
- package/dist/lib/monday/index.js +4 -0
- package/dist/lib/monday/mapper.d.ts +14 -0
- package/dist/lib/monday/mapper.js +89 -0
- package/dist/lib/monday/sync.d.ts +13 -0
- package/dist/lib/monday/sync.js +45 -0
- package/dist/lib/monday/types.d.ts +38 -0
- package/dist/lib/monday/types.js +4 -0
- package/dist/lib/pmo/schema.d.ts +10 -1
- package/dist/lib/pmo/schema.js +73 -0
- package/dist/lib/pmo/storage/base.js +32 -0
- package/dist/lib/prompt-json.d.ts +11 -0
- package/dist/lib/work-source/config.d.ts +14 -0
- package/dist/lib/work-source/config.js +70 -0
- package/dist/lib/work-source/index.d.ts +1 -0
- package/dist/lib/work-source/index.js +1 -0
- package/oclif.manifest.json +2584 -2017
- 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
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
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 :
|
|
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
|
-
//
|
|
258
|
+
// Linear source: Pull Linear issues → mirror into PMO → spawn from PMO
|
|
197
259
|
// =========================================================================
|
|
198
|
-
if (
|
|
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:
|
|
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
|