@pmate/cli 0.12.7 → 0.12.8
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/index.d.ts +346 -3
- package/dist/index.js +20884 -1474
- package/package.json +6 -5
- package/dist/agentCommandParser.d.ts +0 -2
- package/dist/agentCommandParser.js +0 -151
- package/dist/agentCommandParser.js.map +0 -1
- package/dist/alioss.d.ts +0 -21
- package/dist/alioss.js +0 -281
- package/dist/alioss.js.map +0 -1
- package/dist/appCommandParser.d.ts +0 -2
- package/dist/appCommandParser.js +0 -106
- package/dist/appCommandParser.js.map +0 -1
- package/dist/appDir.d.ts +0 -2
- package/dist/appDir.js +0 -33
- package/dist/appDir.js.map +0 -1
- package/dist/cli.d.ts +0 -8
- package/dist/cli.js +0 -43
- package/dist/cli.js.map +0 -1
- package/dist/commands/AddDnsCommandHandler.d.ts +0 -4
- package/dist/commands/AddDnsCommandHandler.js +0 -26
- package/dist/commands/AddDnsCommandHandler.js.map +0 -1
- package/dist/commands/AgentCommandHandler.d.ts +0 -11
- package/dist/commands/AgentCommandHandler.js +0 -146
- package/dist/commands/AgentCommandHandler.js.map +0 -1
- package/dist/commands/AppCommandHandler.d.ts +0 -10
- package/dist/commands/AppCommandHandler.js +0 -125
- package/dist/commands/AppCommandHandler.js.map +0 -1
- package/dist/commands/BucketCommandHandler.d.ts +0 -4
- package/dist/commands/BucketCommandHandler.js +0 -14
- package/dist/commands/BucketCommandHandler.js.map +0 -1
- package/dist/commands/CollectCommandHandler.d.ts +0 -20
- package/dist/commands/CollectCommandHandler.js +0 -470
- package/dist/commands/CollectCommandHandler.js.map +0 -1
- package/dist/commands/CommitCommandHandler.d.ts +0 -4
- package/dist/commands/CommitCommandHandler.js +0 -12
- package/dist/commands/CommitCommandHandler.js.map +0 -1
- package/dist/commands/DeployCommandHandler.d.ts +0 -20
- package/dist/commands/DeployCommandHandler.js +0 -297
- package/dist/commands/DeployCommandHandler.js.map +0 -1
- package/dist/commands/DevCommandHandler.d.ts +0 -8
- package/dist/commands/DevCommandHandler.js +0 -81
- package/dist/commands/DevCommandHandler.js.map +0 -1
- package/dist/commands/EnvCommandHandler.d.ts +0 -12
- package/dist/commands/EnvCommandHandler.js +0 -346
- package/dist/commands/EnvCommandHandler.js.map +0 -1
- package/dist/commands/InitCommandHandler.d.ts +0 -21
- package/dist/commands/InitCommandHandler.js +0 -468
- package/dist/commands/InitCommandHandler.js.map +0 -1
- package/dist/commands/LinearCommandHandler.d.ts +0 -52
- package/dist/commands/LinearCommandHandler.js +0 -1652
- package/dist/commands/LinearCommandHandler.js.map +0 -1
- package/dist/commands/ListCommandHandler.d.ts +0 -6
- package/dist/commands/ListCommandHandler.js +0 -43
- package/dist/commands/ListCommandHandler.js.map +0 -1
- package/dist/commands/LogCommandHandler.d.ts +0 -15
- package/dist/commands/LogCommandHandler.js +0 -201
- package/dist/commands/LogCommandHandler.js.map +0 -1
- package/dist/commands/LoginCommandHandler.d.ts +0 -11
- package/dist/commands/LoginCommandHandler.js +0 -178
- package/dist/commands/LoginCommandHandler.js.map +0 -1
- package/dist/commands/NotionCommandHandler.d.ts +0 -5
- package/dist/commands/NotionCommandHandler.js +0 -59
- package/dist/commands/NotionCommandHandler.js.map +0 -1
- package/dist/commands/OssCommandHandler.d.ts +0 -7
- package/dist/commands/OssCommandHandler.js +0 -82
- package/dist/commands/OssCommandHandler.js.map +0 -1
- package/dist/commands/PrCommandHandler.d.ts +0 -44
- package/dist/commands/PrCommandHandler.js +0 -673
- package/dist/commands/PrCommandHandler.js.map +0 -1
- package/dist/commands/QrcodeCommandHandler.d.ts +0 -9
- package/dist/commands/QrcodeCommandHandler.js +0 -135
- package/dist/commands/QrcodeCommandHandler.js.map +0 -1
- package/dist/commands/RandomCommandHandler.d.ts +0 -6
- package/dist/commands/RandomCommandHandler.js +0 -201
- package/dist/commands/RandomCommandHandler.js.map +0 -1
- package/dist/commands/StsCommandHandler.d.ts +0 -5
- package/dist/commands/StsCommandHandler.js +0 -45
- package/dist/commands/StsCommandHandler.js.map +0 -1
- package/dist/commands/SummaryCommandHandler.d.ts +0 -16
- package/dist/commands/SummaryCommandHandler.js +0 -190
- package/dist/commands/SummaryCommandHandler.js.map +0 -1
- package/dist/commands/VercelCommandHandler.d.ts +0 -38
- package/dist/commands/VercelCommandHandler.js +0 -126
- package/dist/commands/VercelCommandHandler.js.map +0 -1
- package/dist/config.d.ts +0 -62
- package/dist/config.js +0 -265
- package/dist/config.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/llm/AgentLlmClient.d.ts +0 -14
- package/dist/llm/AgentLlmClient.js +0 -81
- package/dist/llm/AgentLlmClient.js.map +0 -1
- package/dist/llm/types.d.ts +0 -37
- package/dist/llm/types.js +0 -3
- package/dist/llm/types.js.map +0 -1
- package/dist/serverAddress.d.ts +0 -2
- package/dist/serverAddress.js +0 -21
- package/dist/serverAddress.js.map +0 -1
- package/dist/services/AliyunService.d.ts +0 -68
- package/dist/services/AliyunService.js +0 -622
- package/dist/services/AliyunService.js.map +0 -1
- package/dist/services/NotionService.d.ts +0 -47
- package/dist/services/NotionService.js +0 -292
- package/dist/services/NotionService.js.map +0 -1
- package/dist/shell.d.ts +0 -2
- package/dist/shell.js +0 -11
- package/dist/shell.js.map +0 -1
- package/dist/tests/AgentCommandHandler.test.d.ts +0 -1
- package/dist/tests/AgentCommandHandler.test.js +0 -138
- package/dist/tests/AgentCommandHandler.test.js.map +0 -1
- package/dist/tests/AppCommandHandler.test.d.ts +0 -1
- package/dist/tests/AppCommandHandler.test.js +0 -94
- package/dist/tests/AppCommandHandler.test.js.map +0 -1
- package/dist/tests/LoginCommandHandler.test.d.ts +0 -1
- package/dist/tests/LoginCommandHandler.test.js +0 -13
- package/dist/tests/LoginCommandHandler.test.js.map +0 -1
- package/dist/tests/PrCommandHandler.test.d.ts +0 -1
- package/dist/tests/PrCommandHandler.test.js +0 -27
- package/dist/tests/PrCommandHandler.test.js.map +0 -1
- package/dist/tests/VercelCommandHandler.test.d.ts +0 -1
- package/dist/tests/VercelCommandHandler.test.js +0 -84
- package/dist/tests/VercelCommandHandler.test.js.map +0 -1
- package/dist/tests/agentCommandParser.test.d.ts +0 -1
- package/dist/tests/agentCommandParser.test.js +0 -75
- package/dist/tests/agentCommandParser.test.js.map +0 -1
- package/dist/tests/appCommandParser.test.d.ts +0 -1
- package/dist/tests/appCommandParser.test.js +0 -28
- package/dist/tests/appCommandParser.test.js.map +0 -1
- package/dist/tests/config.test.d.ts +0 -1
- package/dist/tests/config.test.js +0 -41
- package/dist/tests/config.test.js.map +0 -1
- package/dist/tests/pythonAppSupport.test.d.ts +0 -1
- package/dist/tests/pythonAppSupport.test.js +0 -47
- package/dist/tests/pythonAppSupport.test.js.map +0 -1
- package/dist/tests/serverAddress.test.d.ts +0 -1
- package/dist/tests/serverAddress.test.js +0 -14
- package/dist/tests/serverAddress.test.js.map +0 -1
- package/dist/tests/shell.test.d.ts +0 -1
- package/dist/tests/shell.test.js +0 -16
- package/dist/tests/shell.test.js.map +0 -1
- package/dist/tests/vercelParser.test.d.ts +0 -1
- package/dist/tests/vercelParser.test.js +0 -39
- package/dist/tests/vercelParser.test.js.map +0 -1
- package/dist/types.d.ts +0 -394
- package/dist/types.js +0 -30
- package/dist/types.js.map +0 -1
|
@@ -1,1652 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LinearCommandHandler = void 0;
|
|
4
|
-
const tslib_1 = require("tslib");
|
|
5
|
-
const node_child_process_1 = require("node:child_process");
|
|
6
|
-
const node_fs_1 = require("node:fs");
|
|
7
|
-
const node_os_1 = tslib_1.__importDefault(require("node:os"));
|
|
8
|
-
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
9
|
-
const promises_1 = require("node:readline/promises");
|
|
10
|
-
const openai_1 = tslib_1.__importDefault(require("openai"));
|
|
11
|
-
const sdk_1 = require("@linear/sdk");
|
|
12
|
-
const cli_1 = require("../cli");
|
|
13
|
-
const config_1 = require("../config");
|
|
14
|
-
const ISSUE_SUMMARY_FIELDS = `
|
|
15
|
-
id
|
|
16
|
-
identifier
|
|
17
|
-
title
|
|
18
|
-
description
|
|
19
|
-
estimate
|
|
20
|
-
priority
|
|
21
|
-
priorityLabel
|
|
22
|
-
url
|
|
23
|
-
createdAt
|
|
24
|
-
updatedAt
|
|
25
|
-
team {
|
|
26
|
-
id
|
|
27
|
-
key
|
|
28
|
-
name
|
|
29
|
-
}
|
|
30
|
-
project {
|
|
31
|
-
id
|
|
32
|
-
name
|
|
33
|
-
}
|
|
34
|
-
state {
|
|
35
|
-
id
|
|
36
|
-
name
|
|
37
|
-
type
|
|
38
|
-
}
|
|
39
|
-
cycle {
|
|
40
|
-
id
|
|
41
|
-
number
|
|
42
|
-
name
|
|
43
|
-
}
|
|
44
|
-
parent {
|
|
45
|
-
id
|
|
46
|
-
identifier
|
|
47
|
-
title
|
|
48
|
-
}
|
|
49
|
-
`;
|
|
50
|
-
const ISSUE_BY_ID_QUERY = `
|
|
51
|
-
query IssueById($id: String!) {
|
|
52
|
-
issue(id: $id) {
|
|
53
|
-
${ISSUE_SUMMARY_FIELDS}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
`;
|
|
57
|
-
const ISSUE_BY_IDENTIFIER_QUERY = `
|
|
58
|
-
query IssueByIdentifier($teamKey: String!, $number: Float!) {
|
|
59
|
-
issues(
|
|
60
|
-
first: 1
|
|
61
|
-
filter: {
|
|
62
|
-
team: { key: { eq: $teamKey } }
|
|
63
|
-
number: { eq: $number }
|
|
64
|
-
}
|
|
65
|
-
) {
|
|
66
|
-
nodes {
|
|
67
|
-
${ISSUE_SUMMARY_FIELDS}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
`;
|
|
72
|
-
const ISSUE_CHILDREN_QUERY = `
|
|
73
|
-
query IssueChildren($id: String!, $first: Int!) {
|
|
74
|
-
issue(id: $id) {
|
|
75
|
-
id
|
|
76
|
-
identifier
|
|
77
|
-
title
|
|
78
|
-
children(first: $first) {
|
|
79
|
-
nodes {
|
|
80
|
-
id
|
|
81
|
-
identifier
|
|
82
|
-
title
|
|
83
|
-
url
|
|
84
|
-
priority
|
|
85
|
-
priorityLabel
|
|
86
|
-
updatedAt
|
|
87
|
-
state {
|
|
88
|
-
id
|
|
89
|
-
name
|
|
90
|
-
type
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
`;
|
|
97
|
-
const ISSUE_COMMENTS_QUERY = `
|
|
98
|
-
query IssueComments($issueId: String!, $first: Int!) {
|
|
99
|
-
issue(id: $issueId) {
|
|
100
|
-
id
|
|
101
|
-
identifier
|
|
102
|
-
title
|
|
103
|
-
comments(first: $first) {
|
|
104
|
-
nodes {
|
|
105
|
-
id
|
|
106
|
-
body
|
|
107
|
-
url
|
|
108
|
-
createdAt
|
|
109
|
-
updatedAt
|
|
110
|
-
user {
|
|
111
|
-
id
|
|
112
|
-
name
|
|
113
|
-
displayName
|
|
114
|
-
email
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
`;
|
|
121
|
-
const USER_BY_QUERY = `
|
|
122
|
-
query UserByQuery($query: String!, $first: Int!) {
|
|
123
|
-
users(
|
|
124
|
-
first: $first
|
|
125
|
-
filter: {
|
|
126
|
-
or: [
|
|
127
|
-
{ name: { containsIgnoreCase: $query } }
|
|
128
|
-
{ displayName: { containsIgnoreCase: $query } }
|
|
129
|
-
{ email: { containsIgnoreCase: $query } }
|
|
130
|
-
]
|
|
131
|
-
}
|
|
132
|
-
) {
|
|
133
|
-
nodes {
|
|
134
|
-
id
|
|
135
|
-
name
|
|
136
|
-
displayName
|
|
137
|
-
email
|
|
138
|
-
active
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
`;
|
|
143
|
-
const USERS_LIST_QUERY = `
|
|
144
|
-
query UsersList($first: Int!) {
|
|
145
|
-
users(first: $first) {
|
|
146
|
-
nodes {
|
|
147
|
-
id
|
|
148
|
-
name
|
|
149
|
-
displayName
|
|
150
|
-
email
|
|
151
|
-
active
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
`;
|
|
156
|
-
const USER_ISSUES_BY_ASSIGNEE_QUERY = `
|
|
157
|
-
query UserIssuesByAssignee($assigneeId: ID!, $first: Int!) {
|
|
158
|
-
issues(
|
|
159
|
-
first: $first
|
|
160
|
-
filter: {
|
|
161
|
-
assignee: { id: { eq: $assigneeId } }
|
|
162
|
-
}
|
|
163
|
-
) {
|
|
164
|
-
nodes {
|
|
165
|
-
id
|
|
166
|
-
identifier
|
|
167
|
-
title
|
|
168
|
-
url
|
|
169
|
-
estimate
|
|
170
|
-
priority
|
|
171
|
-
priorityLabel
|
|
172
|
-
updatedAt
|
|
173
|
-
state {
|
|
174
|
-
id
|
|
175
|
-
name
|
|
176
|
-
type
|
|
177
|
-
}
|
|
178
|
-
team {
|
|
179
|
-
id
|
|
180
|
-
key
|
|
181
|
-
name
|
|
182
|
-
}
|
|
183
|
-
cycle {
|
|
184
|
-
id
|
|
185
|
-
number
|
|
186
|
-
name
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
`;
|
|
192
|
-
const ISSUES_LIST_QUERY = `
|
|
193
|
-
query IssuesList($first: Int!) {
|
|
194
|
-
issues(first: $first) {
|
|
195
|
-
nodes {
|
|
196
|
-
id
|
|
197
|
-
identifier
|
|
198
|
-
title
|
|
199
|
-
description
|
|
200
|
-
url
|
|
201
|
-
estimate
|
|
202
|
-
priority
|
|
203
|
-
priorityLabel
|
|
204
|
-
updatedAt
|
|
205
|
-
state {
|
|
206
|
-
id
|
|
207
|
-
name
|
|
208
|
-
type
|
|
209
|
-
}
|
|
210
|
-
team {
|
|
211
|
-
id
|
|
212
|
-
key
|
|
213
|
-
name
|
|
214
|
-
}
|
|
215
|
-
cycle {
|
|
216
|
-
id
|
|
217
|
-
number
|
|
218
|
-
name
|
|
219
|
-
}
|
|
220
|
-
assignee {
|
|
221
|
-
id
|
|
222
|
-
name
|
|
223
|
-
displayName
|
|
224
|
-
email
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
`;
|
|
230
|
-
const CURRENT_CYCLE_QUERY = `
|
|
231
|
-
query CurrentCycle($teamId: String!, $first: Int!) {
|
|
232
|
-
team(id: $teamId) {
|
|
233
|
-
key
|
|
234
|
-
cycles(first: $first) {
|
|
235
|
-
nodes {
|
|
236
|
-
id
|
|
237
|
-
number
|
|
238
|
-
name
|
|
239
|
-
startsAt
|
|
240
|
-
endsAt
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
`;
|
|
246
|
-
const VIEWER_QUERY = `
|
|
247
|
-
query Viewer {
|
|
248
|
-
viewer {
|
|
249
|
-
id
|
|
250
|
-
name
|
|
251
|
-
displayName
|
|
252
|
-
email
|
|
253
|
-
createdAt
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
`;
|
|
257
|
-
class LinearCommandHandler {
|
|
258
|
-
async handle(args) {
|
|
259
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29;
|
|
260
|
-
const cwd = this.resolveCwd(args.cwd);
|
|
261
|
-
const repoConfig = (0, config_1.loadRepoConfig)(cwd);
|
|
262
|
-
const account = await this.resolveLinearAccount(cwd, repoConfig, args.account);
|
|
263
|
-
const client = new sdk_1.LinearClient({ apiKey: account.apiKey });
|
|
264
|
-
if (args.action === "create") {
|
|
265
|
-
const teamId = await this.resolveTeamId(client, {
|
|
266
|
-
teamId: args.teamId,
|
|
267
|
-
teamKey: args.teamKey,
|
|
268
|
-
defaultTeamId: account.teamId,
|
|
269
|
-
defaultTeamKey: account.teamKey,
|
|
270
|
-
}, repoConfig === null || repoConfig === void 0 ? void 0 : repoConfig.linear);
|
|
271
|
-
if (args.mode === "repl") {
|
|
272
|
-
const replInput = await this.collectReplInput();
|
|
273
|
-
if (!replInput.text && replInput.images.length === 0) {
|
|
274
|
-
throw new Error("No input provided for linear repl.");
|
|
275
|
-
}
|
|
276
|
-
const ticket = await this.getAiTicketFromNotes(replInput);
|
|
277
|
-
const payload = await client.createIssue({
|
|
278
|
-
teamId,
|
|
279
|
-
title: ticket.title,
|
|
280
|
-
description: ticket.description,
|
|
281
|
-
priority: args.priority,
|
|
282
|
-
parentId: args.parentId,
|
|
283
|
-
stateId: args.stateId,
|
|
284
|
-
});
|
|
285
|
-
const issue = await payload.issue;
|
|
286
|
-
if (!issue) {
|
|
287
|
-
throw new Error("Linear issue creation returned no issue.");
|
|
288
|
-
}
|
|
289
|
-
if (replInput.images.length > 0) {
|
|
290
|
-
await this.attachImages(client, issue.id, replInput.images);
|
|
291
|
-
}
|
|
292
|
-
cli_1.ui.success(`Created Linear issue ${issue.identifier}`, issue.url);
|
|
293
|
-
return;
|
|
294
|
-
}
|
|
295
|
-
if (args.mode === "screenshot") {
|
|
296
|
-
const images = await this.collectScreenshotInput();
|
|
297
|
-
const ticket = await this.getAiTicketFromNotes({ text: "", images });
|
|
298
|
-
const payload = await client.createIssue({
|
|
299
|
-
teamId,
|
|
300
|
-
title: ticket.title,
|
|
301
|
-
description: ticket.description,
|
|
302
|
-
priority: args.priority,
|
|
303
|
-
parentId: args.parentId,
|
|
304
|
-
stateId: args.stateId,
|
|
305
|
-
});
|
|
306
|
-
const issue = await payload.issue;
|
|
307
|
-
if (!issue) {
|
|
308
|
-
throw new Error("Linear issue creation returned no issue.");
|
|
309
|
-
}
|
|
310
|
-
await this.attachImages(client, issue.id, images);
|
|
311
|
-
cli_1.ui.success(`Created Linear issue ${issue.identifier}`, issue.url);
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
const payload = await client.createIssue({
|
|
315
|
-
teamId,
|
|
316
|
-
title: (_a = args.title) !== null && _a !== void 0 ? _a : "",
|
|
317
|
-
description: args.description,
|
|
318
|
-
priority: args.priority,
|
|
319
|
-
parentId: args.parentId,
|
|
320
|
-
stateId: args.stateId,
|
|
321
|
-
});
|
|
322
|
-
const issue = await payload.issue;
|
|
323
|
-
if (!issue) {
|
|
324
|
-
throw new Error("Linear issue creation returned no issue.");
|
|
325
|
-
}
|
|
326
|
-
cli_1.ui.success(`Created Linear issue ${issue.identifier}`, issue.url);
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
if (args.action === "whoami") {
|
|
330
|
-
const data = await client.client.request(VIEWER_QUERY, {});
|
|
331
|
-
const user = data.viewer;
|
|
332
|
-
if (!user) {
|
|
333
|
-
throw new Error("Linear viewer query returned no user.");
|
|
334
|
-
}
|
|
335
|
-
const name = ((_b = user.displayName) === null || _b === void 0 ? void 0 : _b.trim()) || ((_c = user.name) === null || _c === void 0 ? void 0 : _c.trim()) || "Unknown";
|
|
336
|
-
const label = args.account
|
|
337
|
-
? `using account "${args.account}"`
|
|
338
|
-
: "using current Linear account config";
|
|
339
|
-
cli_1.ui.info(`Linear identity (${label})`);
|
|
340
|
-
cli_1.ui.info(` ID: ${user.id}`);
|
|
341
|
-
cli_1.ui.info(` Name: ${name}`);
|
|
342
|
-
cli_1.ui.info(` Email: ${((_d = user.email) === null || _d === void 0 ? void 0 : _d.trim()) || "N/A"}`);
|
|
343
|
-
cli_1.ui.info(` Created At: ${user.createdAt ? new Date(user.createdAt).toLocaleString() : "N/A"}`);
|
|
344
|
-
cli_1.ui.success(`Authenticated as ${name}`);
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
if (args.action === "view") {
|
|
348
|
-
const issue = await this.lookupIssue(client, args.issueRef);
|
|
349
|
-
if (!issue) {
|
|
350
|
-
cli_1.ui.error("Issue not found. Double-check the identifier or ID.");
|
|
351
|
-
process.exitCode = 1;
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
this.printIssueDetails(issue);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
if (args.action === "update") {
|
|
358
|
-
const issue = await this.lookupIssue(client, args.issueRef);
|
|
359
|
-
if (!issue) {
|
|
360
|
-
cli_1.ui.error("Issue not found. Double-check the identifier or ID.");
|
|
361
|
-
process.exitCode = 1;
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
const updateInput = {};
|
|
365
|
-
if (args.title !== undefined) {
|
|
366
|
-
updateInput.title = args.title;
|
|
367
|
-
}
|
|
368
|
-
if (args.description !== undefined) {
|
|
369
|
-
updateInput.description = args.description;
|
|
370
|
-
}
|
|
371
|
-
if (args.priority !== undefined) {
|
|
372
|
-
updateInput.priority = args.priority;
|
|
373
|
-
}
|
|
374
|
-
if (args.parentId !== undefined) {
|
|
375
|
-
updateInput.parentId = args.parentId;
|
|
376
|
-
}
|
|
377
|
-
if (args.stateId !== undefined) {
|
|
378
|
-
updateInput.stateId = args.stateId;
|
|
379
|
-
}
|
|
380
|
-
if (args.teamId || args.teamKey) {
|
|
381
|
-
updateInput.teamId = await this.resolveTeamId(client, {
|
|
382
|
-
teamId: args.teamId,
|
|
383
|
-
teamKey: args.teamKey,
|
|
384
|
-
defaultTeamId: account.teamId,
|
|
385
|
-
defaultTeamKey: account.teamKey,
|
|
386
|
-
}, repoConfig === null || repoConfig === void 0 ? void 0 : repoConfig.linear);
|
|
387
|
-
}
|
|
388
|
-
const payload = await client.updateIssue(issue.id, updateInput);
|
|
389
|
-
const updated = await payload.issue;
|
|
390
|
-
if (!updated) {
|
|
391
|
-
throw new Error("Linear issue update returned no issue.");
|
|
392
|
-
}
|
|
393
|
-
cli_1.ui.success(`Updated Linear issue ${updated.identifier}`, updated.url);
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
if (args.action === "sub-issues") {
|
|
397
|
-
const issueId = await this.lookupIssueId(client, args.issueRef);
|
|
398
|
-
if (!issueId) {
|
|
399
|
-
cli_1.ui.error("Issue not found. Double-check the identifier or ID.");
|
|
400
|
-
process.exitCode = 1;
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
const data = await client.client.request(ISSUE_CHILDREN_QUERY, { id: issueId, first: 50 });
|
|
404
|
-
const parent = data.issue;
|
|
405
|
-
if (!parent) {
|
|
406
|
-
cli_1.ui.error("Issue not found. Double-check the identifier or ID.");
|
|
407
|
-
process.exitCode = 1;
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
const children = (_f = (_e = parent.children) === null || _e === void 0 ? void 0 : _e.nodes) !== null && _f !== void 0 ? _f : [];
|
|
411
|
-
if (children.length === 0) {
|
|
412
|
-
cli_1.ui.info(`No sub-issues found for ${parent.identifier}.`);
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
cli_1.ui.info(`Sub-issues for ${parent.identifier}: ${parent.title}`);
|
|
416
|
-
for (const child of children) {
|
|
417
|
-
const stateLabel = (_h = (_g = child.state) === null || _g === void 0 ? void 0 : _g.name) !== null && _h !== void 0 ? _h : "Unknown state";
|
|
418
|
-
const updatedAt = new Date(child.updatedAt).toLocaleString();
|
|
419
|
-
cli_1.ui.info(`${child.identifier} | ${stateLabel} | ${child.priorityLabel} | Updated: ${updatedAt}`);
|
|
420
|
-
cli_1.ui.info(` ${child.title}`);
|
|
421
|
-
cli_1.ui.info(` ${child.url}`);
|
|
422
|
-
}
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
if (args.action === "comment") {
|
|
426
|
-
const issue = await this.lookupIssue(client, args.issueRef);
|
|
427
|
-
if (!issue) {
|
|
428
|
-
cli_1.ui.error("Issue not found. Double-check the identifier or ID.");
|
|
429
|
-
process.exitCode = 1;
|
|
430
|
-
return;
|
|
431
|
-
}
|
|
432
|
-
const payload = await client.createComment({
|
|
433
|
-
issueId: issue.id,
|
|
434
|
-
body: args.body,
|
|
435
|
-
});
|
|
436
|
-
const comment = await payload.comment;
|
|
437
|
-
if (!comment) {
|
|
438
|
-
throw new Error("Linear comment creation returned no comment.");
|
|
439
|
-
}
|
|
440
|
-
cli_1.ui.success(`Added comment to ${issue.identifier}`, comment.url);
|
|
441
|
-
return;
|
|
442
|
-
}
|
|
443
|
-
if (args.action === "comments") {
|
|
444
|
-
const issueId = await this.lookupIssueId(client, args.issueRef);
|
|
445
|
-
if (!issueId) {
|
|
446
|
-
cli_1.ui.error("Issue not found. Double-check the identifier or ID.");
|
|
447
|
-
process.exitCode = 1;
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
const data = await client.client.request(ISSUE_COMMENTS_QUERY, { issueId, first: 50 });
|
|
451
|
-
const issue = data.issue;
|
|
452
|
-
if (!issue) {
|
|
453
|
-
cli_1.ui.error("Issue not found. Double-check the identifier or ID.");
|
|
454
|
-
process.exitCode = 1;
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
const comments = (_k = (_j = issue.comments) === null || _j === void 0 ? void 0 : _j.nodes) !== null && _k !== void 0 ? _k : [];
|
|
458
|
-
if (comments.length === 0) {
|
|
459
|
-
cli_1.ui.info(`No comments found for ${issue.identifier}.`);
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
cli_1.ui.info(`Comments for ${issue.identifier}: ${issue.title}`);
|
|
463
|
-
for (const comment of comments) {
|
|
464
|
-
const author = ((_m = (_l = comment.user) === null || _l === void 0 ? void 0 : _l.displayName) === null || _m === void 0 ? void 0 : _m.trim()) ||
|
|
465
|
-
((_p = (_o = comment.user) === null || _o === void 0 ? void 0 : _o.name) === null || _p === void 0 ? void 0 : _p.trim()) ||
|
|
466
|
-
((_r = (_q = comment.user) === null || _q === void 0 ? void 0 : _q.email) === null || _r === void 0 ? void 0 : _r.trim()) ||
|
|
467
|
-
"Unknown user";
|
|
468
|
-
const updatedAt = new Date(comment.updatedAt).toLocaleString();
|
|
469
|
-
const body = ((_s = comment.body) === null || _s === void 0 ? void 0 : _s.trim()) || "(empty comment body)";
|
|
470
|
-
cli_1.ui.info(`${author} | Updated: ${updatedAt}`);
|
|
471
|
-
cli_1.ui.info(` ${body}`);
|
|
472
|
-
cli_1.ui.info(` ${comment.url}`);
|
|
473
|
-
}
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
if (args.action === "user-issues") {
|
|
477
|
-
const user = await this.lookupLinearUser(client, args.user);
|
|
478
|
-
if (!user) {
|
|
479
|
-
cli_1.ui.error(`No Linear user matched "${args.user}".`);
|
|
480
|
-
process.exitCode = 1;
|
|
481
|
-
return;
|
|
482
|
-
}
|
|
483
|
-
const data = await client.client.request(USER_ISSUES_BY_ASSIGNEE_QUERY, {
|
|
484
|
-
assigneeId: user.id,
|
|
485
|
-
first: 250,
|
|
486
|
-
});
|
|
487
|
-
const rawIssues = (_u = (_t = data.issues) === null || _t === void 0 ? void 0 : _t.nodes) !== null && _u !== void 0 ? _u : [];
|
|
488
|
-
const statusFilter = (_v = args.status) === null || _v === void 0 ? void 0 : _v.trim().toLowerCase();
|
|
489
|
-
const issues = rawIssues.filter((issue) => {
|
|
490
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
491
|
-
if (args.cycle !== undefined && ((_a = issue.cycle) === null || _a === void 0 ? void 0 : _a.number) !== args.cycle) {
|
|
492
|
-
return false;
|
|
493
|
-
}
|
|
494
|
-
if (!statusFilter) {
|
|
495
|
-
return true;
|
|
496
|
-
}
|
|
497
|
-
const stateName = (_d = (_c = (_b = issue.state) === null || _b === void 0 ? void 0 : _b.name) === null || _c === void 0 ? void 0 : _c.trim().toLowerCase()) !== null && _d !== void 0 ? _d : "";
|
|
498
|
-
const stateType = (_g = (_f = (_e = issue.state) === null || _e === void 0 ? void 0 : _e.type) === null || _f === void 0 ? void 0 : _f.trim().toLowerCase()) !== null && _g !== void 0 ? _g : "";
|
|
499
|
-
return stateName === statusFilter || stateType === statusFilter;
|
|
500
|
-
});
|
|
501
|
-
const filters = [];
|
|
502
|
-
if (args.cycle !== undefined) {
|
|
503
|
-
filters.push(`cycle ${args.cycle}`);
|
|
504
|
-
}
|
|
505
|
-
if (args.status) {
|
|
506
|
-
filters.push(`status "${args.status}"`);
|
|
507
|
-
}
|
|
508
|
-
const filterLabel = filters.join(", ");
|
|
509
|
-
if (issues.length === 0) {
|
|
510
|
-
cli_1.ui.info(`No issues found for ${this.formatUserLabel(user)}${filterLabel ? ` with ${filterLabel}` : ""}.`);
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
cli_1.ui.info(`Issues for ${this.formatUserLabel(user)}${filterLabel ? ` with ${filterLabel}` : ""}:`);
|
|
514
|
-
for (const issue of issues) {
|
|
515
|
-
const stateLabel = (_x = (_w = issue.state) === null || _w === void 0 ? void 0 : _w.name) !== null && _x !== void 0 ? _x : "Unknown state";
|
|
516
|
-
const teamKey = (_z = (_y = issue.team) === null || _y === void 0 ? void 0 : _y.key) !== null && _z !== void 0 ? _z : "?";
|
|
517
|
-
const estimateLabel = (_0 = issue.estimate) !== null && _0 !== void 0 ? _0 : "N/A";
|
|
518
|
-
const cycleLabel = ((_1 = issue.cycle) === null || _1 === void 0 ? void 0 : _1.name)
|
|
519
|
-
? `${issue.cycle.name} (#${issue.cycle.number})`
|
|
520
|
-
: `#${(_3 = (_2 = issue.cycle) === null || _2 === void 0 ? void 0 : _2.number) !== null && _3 !== void 0 ? _3 : "N/A"}`;
|
|
521
|
-
const updatedAt = new Date(issue.updatedAt).toLocaleString();
|
|
522
|
-
cli_1.ui.info(`${issue.identifier} | ${issue.title} | ${teamKey} | ${stateLabel} | ${issue.priorityLabel} | Est: ${estimateLabel} | ${cycleLabel} | Updated: ${updatedAt}`);
|
|
523
|
-
cli_1.ui.info(` ${issue.url}`);
|
|
524
|
-
}
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
if (args.action === "issues") {
|
|
528
|
-
let assigneeId;
|
|
529
|
-
let assigneeLabel;
|
|
530
|
-
if (args.user) {
|
|
531
|
-
const user = await this.lookupLinearUser(client, args.user);
|
|
532
|
-
if (!user) {
|
|
533
|
-
cli_1.ui.error(`No Linear user matched "${args.user}".`);
|
|
534
|
-
process.exitCode = 1;
|
|
535
|
-
return;
|
|
536
|
-
}
|
|
537
|
-
assigneeId = user.id;
|
|
538
|
-
assigneeLabel = this.formatUserLabel(user);
|
|
539
|
-
}
|
|
540
|
-
const data = await client.client.request(ISSUES_LIST_QUERY, { first: args.limit });
|
|
541
|
-
const statusFilter = (_4 = args.status) === null || _4 === void 0 ? void 0 : _4.trim().toLowerCase();
|
|
542
|
-
const teamIdFilter = (_5 = args.teamId) === null || _5 === void 0 ? void 0 : _5.trim();
|
|
543
|
-
const teamKeyFilter = (_6 = args.teamKey) === null || _6 === void 0 ? void 0 : _6.trim().toLowerCase();
|
|
544
|
-
const textFilter = (_7 = args.query) === null || _7 === void 0 ? void 0 : _7.trim().toLowerCase();
|
|
545
|
-
const issues = ((_9 = (_8 = data.issues) === null || _8 === void 0 ? void 0 : _8.nodes) !== null && _9 !== void 0 ? _9 : []).filter((issue) => {
|
|
546
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1;
|
|
547
|
-
if (teamIdFilter && ((_a = issue.team) === null || _a === void 0 ? void 0 : _a.id) !== teamIdFilter) {
|
|
548
|
-
return false;
|
|
549
|
-
}
|
|
550
|
-
if (teamKeyFilter && ((_c = (_b = issue.team) === null || _b === void 0 ? void 0 : _b.key) === null || _c === void 0 ? void 0 : _c.trim().toLowerCase()) !== teamKeyFilter) {
|
|
551
|
-
return false;
|
|
552
|
-
}
|
|
553
|
-
if (assigneeId && ((_d = issue.assignee) === null || _d === void 0 ? void 0 : _d.id) !== assigneeId) {
|
|
554
|
-
return false;
|
|
555
|
-
}
|
|
556
|
-
if (args.cycle !== undefined && ((_e = issue.cycle) === null || _e === void 0 ? void 0 : _e.number) !== args.cycle) {
|
|
557
|
-
return false;
|
|
558
|
-
}
|
|
559
|
-
if (statusFilter) {
|
|
560
|
-
const stateName = (_h = (_g = (_f = issue.state) === null || _f === void 0 ? void 0 : _f.name) === null || _g === void 0 ? void 0 : _g.trim().toLowerCase()) !== null && _h !== void 0 ? _h : "";
|
|
561
|
-
const stateType = (_l = (_k = (_j = issue.state) === null || _j === void 0 ? void 0 : _j.type) === null || _k === void 0 ? void 0 : _k.trim().toLowerCase()) !== null && _l !== void 0 ? _l : "";
|
|
562
|
-
if (stateName !== statusFilter && stateType !== statusFilter) {
|
|
563
|
-
return false;
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
if (textFilter) {
|
|
567
|
-
const assigneeText = [
|
|
568
|
-
(_o = (_m = issue.assignee) === null || _m === void 0 ? void 0 : _m.displayName) !== null && _o !== void 0 ? _o : "",
|
|
569
|
-
(_q = (_p = issue.assignee) === null || _p === void 0 ? void 0 : _p.name) !== null && _q !== void 0 ? _q : "",
|
|
570
|
-
(_s = (_r = issue.assignee) === null || _r === void 0 ? void 0 : _r.email) !== null && _s !== void 0 ? _s : "",
|
|
571
|
-
].join(" ");
|
|
572
|
-
const haystack = [
|
|
573
|
-
issue.identifier,
|
|
574
|
-
issue.title,
|
|
575
|
-
(_t = issue.description) !== null && _t !== void 0 ? _t : "",
|
|
576
|
-
(_v = (_u = issue.state) === null || _u === void 0 ? void 0 : _u.name) !== null && _v !== void 0 ? _v : "",
|
|
577
|
-
(_x = (_w = issue.state) === null || _w === void 0 ? void 0 : _w.type) !== null && _x !== void 0 ? _x : "",
|
|
578
|
-
(_z = (_y = issue.team) === null || _y === void 0 ? void 0 : _y.key) !== null && _z !== void 0 ? _z : "",
|
|
579
|
-
(_1 = (_0 = issue.team) === null || _0 === void 0 ? void 0 : _0.name) !== null && _1 !== void 0 ? _1 : "",
|
|
580
|
-
assigneeText,
|
|
581
|
-
]
|
|
582
|
-
.join(" ")
|
|
583
|
-
.toLowerCase();
|
|
584
|
-
if (!haystack.includes(textFilter)) {
|
|
585
|
-
return false;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
return true;
|
|
589
|
-
});
|
|
590
|
-
const filters = [];
|
|
591
|
-
if (args.query)
|
|
592
|
-
filters.push(`query "${args.query}"`);
|
|
593
|
-
if (assigneeLabel)
|
|
594
|
-
filters.push(`user ${assigneeLabel}`);
|
|
595
|
-
if (args.status)
|
|
596
|
-
filters.push(`status "${args.status}"`);
|
|
597
|
-
if (args.cycle !== undefined)
|
|
598
|
-
filters.push(`cycle ${args.cycle}`);
|
|
599
|
-
if (args.teamKey)
|
|
600
|
-
filters.push(`team ${args.teamKey}`);
|
|
601
|
-
if (args.teamId)
|
|
602
|
-
filters.push(`teamId ${args.teamId}`);
|
|
603
|
-
const filterLabel = filters.length > 0 ? ` with ${filters.join(", ")}` : "";
|
|
604
|
-
if (issues.length === 0) {
|
|
605
|
-
cli_1.ui.info(`No issues found${filterLabel}.`);
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
cli_1.ui.info(`Issues${filterLabel}:`);
|
|
609
|
-
for (const issue of issues) {
|
|
610
|
-
const stateLabel = (_11 = (_10 = issue.state) === null || _10 === void 0 ? void 0 : _10.name) !== null && _11 !== void 0 ? _11 : "Unknown state";
|
|
611
|
-
const teamKey = (_13 = (_12 = issue.team) === null || _12 === void 0 ? void 0 : _12.key) !== null && _13 !== void 0 ? _13 : "?";
|
|
612
|
-
const estimateLabel = (_14 = issue.estimate) !== null && _14 !== void 0 ? _14 : "N/A";
|
|
613
|
-
const cycleLabel = ((_15 = issue.cycle) === null || _15 === void 0 ? void 0 : _15.name)
|
|
614
|
-
? `${issue.cycle.name} (#${issue.cycle.number})`
|
|
615
|
-
: `#${(_17 = (_16 = issue.cycle) === null || _16 === void 0 ? void 0 : _16.number) !== null && _17 !== void 0 ? _17 : "N/A"}`;
|
|
616
|
-
const assignee = ((_19 = (_18 = issue.assignee) === null || _18 === void 0 ? void 0 : _18.displayName) === null || _19 === void 0 ? void 0 : _19.trim()) ||
|
|
617
|
-
((_21 = (_20 = issue.assignee) === null || _20 === void 0 ? void 0 : _20.name) === null || _21 === void 0 ? void 0 : _21.trim()) ||
|
|
618
|
-
((_23 = (_22 = issue.assignee) === null || _22 === void 0 ? void 0 : _22.email) === null || _23 === void 0 ? void 0 : _23.trim()) ||
|
|
619
|
-
"Unassigned";
|
|
620
|
-
const updatedAt = new Date(issue.updatedAt).toLocaleString();
|
|
621
|
-
cli_1.ui.info(`${issue.identifier} | ${issue.title} | ${teamKey} | ${stateLabel} | ${issue.priorityLabel} | Est: ${estimateLabel} | ${cycleLabel} | ${assignee} | Updated: ${updatedAt}`);
|
|
622
|
-
cli_1.ui.info(` ${issue.url}`);
|
|
623
|
-
}
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
if (args.action === "cycle") {
|
|
627
|
-
const teamId = await this.resolveTeamId(client, {
|
|
628
|
-
teamId: args.teamId,
|
|
629
|
-
teamKey: args.teamKey,
|
|
630
|
-
defaultTeamId: account.teamId,
|
|
631
|
-
defaultTeamKey: account.teamKey,
|
|
632
|
-
}, repoConfig === null || repoConfig === void 0 ? void 0 : repoConfig.linear);
|
|
633
|
-
const currentCycle = await this.getCurrentCycle(client, teamId);
|
|
634
|
-
if (!currentCycle) {
|
|
635
|
-
throw new Error("No current Linear cycle found for the selected team.");
|
|
636
|
-
}
|
|
637
|
-
const label = currentCycle.name
|
|
638
|
-
? `${currentCycle.name} (#${currentCycle.number})`
|
|
639
|
-
: `Cycle #${currentCycle.number}`;
|
|
640
|
-
cli_1.ui.success(`Current cycle for ${currentCycle.teamKey}: ${label}`);
|
|
641
|
-
return;
|
|
642
|
-
}
|
|
643
|
-
if (args.action === "teams") {
|
|
644
|
-
const teams = await client.teams();
|
|
645
|
-
const nodes = (_24 = teams.nodes) !== null && _24 !== void 0 ? _24 : [];
|
|
646
|
-
if (nodes.length === 0) {
|
|
647
|
-
cli_1.ui.info("No Linear teams found for this account.");
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
const sorted = [...nodes].sort((a, b) => {
|
|
651
|
-
const aKey = a.key.trim().toLowerCase();
|
|
652
|
-
const bKey = b.key.trim().toLowerCase();
|
|
653
|
-
return aKey.localeCompare(bKey);
|
|
654
|
-
});
|
|
655
|
-
cli_1.ui.info("Linear teams:");
|
|
656
|
-
for (const team of sorted) {
|
|
657
|
-
const label = [team.key, team.name].filter(Boolean).join(" - ");
|
|
658
|
-
cli_1.ui.info(` ${label}`);
|
|
659
|
-
cli_1.ui.info(` id: ${team.id}`);
|
|
660
|
-
}
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
if (args.action !== "pr") {
|
|
664
|
-
throw new Error("Missing linear action. Use linear create, linear cycle, linear teams, linear whoami, linear update, linear view, linear sub-issues, linear comment, linear comments, linear user-issues, linear issues, or linear pr.");
|
|
665
|
-
}
|
|
666
|
-
const teamId = await this.resolveTeamId(client, {
|
|
667
|
-
defaultTeamId: account.teamId,
|
|
668
|
-
defaultTeamKey: account.teamKey,
|
|
669
|
-
}, repoConfig === null || repoConfig === void 0 ? void 0 : repoConfig.linear);
|
|
670
|
-
await this.ensureGitRepo(cwd);
|
|
671
|
-
await this.ensureGhCli();
|
|
672
|
-
await this.ensureGhAuth();
|
|
673
|
-
const pr = await this.getCurrentPr(cwd);
|
|
674
|
-
const diff = await this.getPrDiff(cwd);
|
|
675
|
-
const description = await this.getAiDescription({
|
|
676
|
-
title: pr.title,
|
|
677
|
-
body: (_25 = pr.body) !== null && _25 !== void 0 ? _25 : "",
|
|
678
|
-
url: pr.url,
|
|
679
|
-
repository: (_27 = (_26 = pr.headRepository) === null || _26 === void 0 ? void 0 : _26.nameWithOwner) !== null && _27 !== void 0 ? _27 : "",
|
|
680
|
-
headRefName: pr.headRefName,
|
|
681
|
-
baseRefName: pr.baseRefName,
|
|
682
|
-
author: (_29 = (_28 = pr.author) === null || _28 === void 0 ? void 0 : _28.login) !== null && _29 !== void 0 ? _29 : "",
|
|
683
|
-
diff,
|
|
684
|
-
});
|
|
685
|
-
const payload = await client.createIssue({
|
|
686
|
-
teamId,
|
|
687
|
-
title: pr.title,
|
|
688
|
-
description: this.buildDescription(pr, description),
|
|
689
|
-
});
|
|
690
|
-
const issue = await payload.issue;
|
|
691
|
-
if (!issue) {
|
|
692
|
-
throw new Error("Linear issue creation returned no issue.");
|
|
693
|
-
}
|
|
694
|
-
cli_1.ui.success(`Created Linear issue ${issue.identifier}`, issue.url);
|
|
695
|
-
}
|
|
696
|
-
async getCurrentPr(cwd) {
|
|
697
|
-
const output = await this.runCaptureGh([
|
|
698
|
-
"pr",
|
|
699
|
-
"view",
|
|
700
|
-
"--json",
|
|
701
|
-
"title,body,url,number,headRefName,baseRefName,author,headRepository",
|
|
702
|
-
], false, cwd);
|
|
703
|
-
try {
|
|
704
|
-
const parsed = JSON.parse(output);
|
|
705
|
-
if (!(parsed === null || parsed === void 0 ? void 0 : parsed.title) || !(parsed === null || parsed === void 0 ? void 0 : parsed.url)) {
|
|
706
|
-
throw new Error("Invalid PR data from gh.");
|
|
707
|
-
}
|
|
708
|
-
return parsed;
|
|
709
|
-
}
|
|
710
|
-
catch (error) {
|
|
711
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
712
|
-
throw new Error(`Failed to parse gh pr view output: ${message}`);
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
async getCurrentCycle(client, teamId) {
|
|
716
|
-
var _a, _b, _c, _d;
|
|
717
|
-
const data = await client.client.request(CURRENT_CYCLE_QUERY, { teamId, first: 250 });
|
|
718
|
-
const team = data.team;
|
|
719
|
-
if (!team) {
|
|
720
|
-
throw new Error("No Linear team found for the selected credentials.");
|
|
721
|
-
}
|
|
722
|
-
const nodes = (_b = (_a = team.cycles) === null || _a === void 0 ? void 0 : _a.nodes) !== null && _b !== void 0 ? _b : [];
|
|
723
|
-
const now = Date.now();
|
|
724
|
-
const currentCandidates = nodes.filter((cycle) => {
|
|
725
|
-
const startsAt = cycle.startsAt ? Date.parse(cycle.startsAt) : NaN;
|
|
726
|
-
const endsAt = cycle.endsAt ? Date.parse(cycle.endsAt) : NaN;
|
|
727
|
-
return (Number.isFinite(startsAt) &&
|
|
728
|
-
Number.isFinite(endsAt) &&
|
|
729
|
-
startsAt <= now &&
|
|
730
|
-
now <= endsAt);
|
|
731
|
-
});
|
|
732
|
-
const fallbackCandidates = nodes.filter((cycle) => {
|
|
733
|
-
const startsAt = cycle.startsAt ? Date.parse(cycle.startsAt) : NaN;
|
|
734
|
-
return Number.isFinite(startsAt) && startsAt <= now;
|
|
735
|
-
});
|
|
736
|
-
const pickLatestByStart = (items) => {
|
|
737
|
-
if (items.length === 0)
|
|
738
|
-
return undefined;
|
|
739
|
-
return items.reduce((latest, cycle) => {
|
|
740
|
-
var _a, _b;
|
|
741
|
-
const latestStart = Date.parse((_a = latest.startsAt) !== null && _a !== void 0 ? _a : "");
|
|
742
|
-
const cycleStart = Date.parse((_b = cycle.startsAt) !== null && _b !== void 0 ? _b : "");
|
|
743
|
-
if (!Number.isFinite(cycleStart)) {
|
|
744
|
-
return latest;
|
|
745
|
-
}
|
|
746
|
-
if (!Number.isFinite(latestStart) || cycleStart > latestStart) {
|
|
747
|
-
return cycle;
|
|
748
|
-
}
|
|
749
|
-
return latest;
|
|
750
|
-
});
|
|
751
|
-
};
|
|
752
|
-
const cycle = (_c = pickLatestByStart(currentCandidates)) !== null && _c !== void 0 ? _c : pickLatestByStart(fallbackCandidates);
|
|
753
|
-
if (!cycle) {
|
|
754
|
-
return null;
|
|
755
|
-
}
|
|
756
|
-
return {
|
|
757
|
-
number: cycle.number,
|
|
758
|
-
name: (_d = cycle.name) !== null && _d !== void 0 ? _d : null,
|
|
759
|
-
teamKey: team.key,
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
buildDescription(pr, aiDescription) {
|
|
763
|
-
var _a, _b, _c;
|
|
764
|
-
const body = ((_a = pr.body) === null || _a === void 0 ? void 0 : _a.trim()) || "No PR description.";
|
|
765
|
-
const lines = [
|
|
766
|
-
`GitHub PR: ${pr.url}`,
|
|
767
|
-
((_b = pr.headRepository) === null || _b === void 0 ? void 0 : _b.nameWithOwner)
|
|
768
|
-
? `Repository: ${pr.headRepository.nameWithOwner}`
|
|
769
|
-
: null,
|
|
770
|
-
`Branch: ${pr.headRefName} -> ${pr.baseRefName}`,
|
|
771
|
-
((_c = pr.author) === null || _c === void 0 ? void 0 : _c.login) ? `Author: ${pr.author.login}` : null,
|
|
772
|
-
"",
|
|
773
|
-
aiDescription.trim() || body,
|
|
774
|
-
].filter((line) => line !== null);
|
|
775
|
-
return lines.join("\n");
|
|
776
|
-
}
|
|
777
|
-
async resolveTeamId(client, options = {}, repoLinear) {
|
|
778
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
779
|
-
const config = (0, config_1.loadPmateConfig)();
|
|
780
|
-
const repoTeamId = (_a = repoLinear === null || repoLinear === void 0 ? void 0 : repoLinear.teamId) === null || _a === void 0 ? void 0 : _a.trim();
|
|
781
|
-
const explicitTeamId = ((_b = options.teamId) === null || _b === void 0 ? void 0 : _b.trim()) || ((_c = options.defaultTeamId) === null || _c === void 0 ? void 0 : _c.trim()) || repoTeamId;
|
|
782
|
-
if (explicitTeamId) {
|
|
783
|
-
return explicitTeamId;
|
|
784
|
-
}
|
|
785
|
-
const teamId = repoTeamId || ((_e = (_d = config.linear) === null || _d === void 0 ? void 0 : _d.teamId) === null || _e === void 0 ? void 0 : _e.trim());
|
|
786
|
-
if (teamId) {
|
|
787
|
-
return teamId;
|
|
788
|
-
}
|
|
789
|
-
const teamKey = (_l = (_j = (_g = (_f = options.teamKey) === null || _f === void 0 ? void 0 : _f.trim()) !== null && _g !== void 0 ? _g : (_h = options.defaultTeamKey) === null || _h === void 0 ? void 0 : _h.trim()) !== null && _j !== void 0 ? _j : (_k = repoLinear === null || repoLinear === void 0 ? void 0 : repoLinear.teamKey) === null || _k === void 0 ? void 0 : _k.trim()) !== null && _l !== void 0 ? _l : (_o = (_m = config.linear) === null || _m === void 0 ? void 0 : _m.teamKey) === null || _o === void 0 ? void 0 : _o.trim();
|
|
790
|
-
const teams = await client.teams();
|
|
791
|
-
const nodes = (_p = teams.nodes) !== null && _p !== void 0 ? _p : [];
|
|
792
|
-
if (nodes.length === 0) {
|
|
793
|
-
throw new Error("No Linear teams available for this account.");
|
|
794
|
-
}
|
|
795
|
-
if (teamKey) {
|
|
796
|
-
const match = nodes.find((team) => team.key.toLowerCase() === teamKey.toLowerCase() ||
|
|
797
|
-
team.name.toLowerCase() === teamKey.toLowerCase());
|
|
798
|
-
if (!match) {
|
|
799
|
-
throw new Error(`No Linear team found for linear.teamKey=${teamKey}.`);
|
|
800
|
-
}
|
|
801
|
-
return match.id;
|
|
802
|
-
}
|
|
803
|
-
if (nodes.length === 1) {
|
|
804
|
-
return nodes[0].id;
|
|
805
|
-
}
|
|
806
|
-
throw new Error("Multiple Linear teams found. Use --team-id/--team-key, set linear.teamId/linear.teamKey in .pmate.yaml or ~/.pmate/config.yaml, or set teamId/teamKey on the selected linear account.");
|
|
807
|
-
}
|
|
808
|
-
async resolveLinearAccount(cwd, repoConfig, requestedAccount) {
|
|
809
|
-
var _a, _b, _c;
|
|
810
|
-
const normalizedRequested = requestedAccount === null || requestedAccount === void 0 ? void 0 : requestedAccount.trim();
|
|
811
|
-
const repoAccount = (_a = repoConfig === null || repoConfig === void 0 ? void 0 : repoConfig.linear) === null || _a === void 0 ? void 0 : _a.account;
|
|
812
|
-
if (normalizedRequested &&
|
|
813
|
-
repoAccount &&
|
|
814
|
-
repoAccount.title.trim().toLowerCase() === normalizedRequested.toLowerCase()) {
|
|
815
|
-
return repoAccount;
|
|
816
|
-
}
|
|
817
|
-
if (repoAccount && !normalizedRequested) {
|
|
818
|
-
return repoAccount;
|
|
819
|
-
}
|
|
820
|
-
const config = (0, config_1.loadPmateConfig)();
|
|
821
|
-
const accounts = (_c = (_b = config.linear) === null || _b === void 0 ? void 0 : _b.accounts) !== null && _c !== void 0 ? _c : [];
|
|
822
|
-
if (accounts.length === 0) {
|
|
823
|
-
if (normalizedRequested && repoAccount) {
|
|
824
|
-
throw new Error(`No Linear account found for --account="${normalizedRequested}". Available account: ${repoAccount.title}.`);
|
|
825
|
-
}
|
|
826
|
-
throw new Error("Missing Linear config. Add linear.accounts to ~/.pmate/config.yaml.");
|
|
827
|
-
}
|
|
828
|
-
const titles = accounts.map((account) => account.title);
|
|
829
|
-
const normalized = titles.map((title) => title.toLowerCase());
|
|
830
|
-
const unique = new Set(normalized);
|
|
831
|
-
if (unique.size !== normalized.length) {
|
|
832
|
-
throw new Error("Duplicate Linear account titles in ~/.pmate/config.yaml. Each linear entry must have a unique title.");
|
|
833
|
-
}
|
|
834
|
-
if (normalizedRequested) {
|
|
835
|
-
const index = normalized.indexOf(normalizedRequested.toLowerCase());
|
|
836
|
-
if (index < 0) {
|
|
837
|
-
throw new Error(`No Linear account found for --account="${normalizedRequested}". Available accounts: ${titles.join(", ")}.`);
|
|
838
|
-
}
|
|
839
|
-
const account = accounts[index];
|
|
840
|
-
this.writeCachedLinearSelection(cwd, account.title);
|
|
841
|
-
return account;
|
|
842
|
-
}
|
|
843
|
-
if (accounts.length === 1) {
|
|
844
|
-
return accounts[0];
|
|
845
|
-
}
|
|
846
|
-
const cached = this.readCachedLinearSelection(cwd);
|
|
847
|
-
if (cached) {
|
|
848
|
-
const index = normalized.indexOf(cached.toLowerCase());
|
|
849
|
-
if (index >= 0) {
|
|
850
|
-
return accounts[index];
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
const rl = (0, promises_1.createInterface)({
|
|
854
|
-
input: process.stdin,
|
|
855
|
-
output: process.stdout,
|
|
856
|
-
});
|
|
857
|
-
try {
|
|
858
|
-
cli_1.ui.title("Select Linear account:");
|
|
859
|
-
accounts.forEach((account, index) => {
|
|
860
|
-
cli_1.ui.plain(`${index + 1}) ${account.title}`);
|
|
861
|
-
});
|
|
862
|
-
while (true) {
|
|
863
|
-
const answer = (await rl.question(`Select account (1-${accounts.length}): `))
|
|
864
|
-
.trim()
|
|
865
|
-
.toLowerCase();
|
|
866
|
-
const index = Number.parseInt(answer, 10);
|
|
867
|
-
if (Number.isFinite(index) && index >= 1 && index <= accounts.length) {
|
|
868
|
-
const account = accounts[index - 1];
|
|
869
|
-
this.writeCachedLinearSelection(cwd, account.title);
|
|
870
|
-
return account;
|
|
871
|
-
}
|
|
872
|
-
const titleIndex = normalized.indexOf(answer);
|
|
873
|
-
if (titleIndex >= 0) {
|
|
874
|
-
const account = accounts[titleIndex];
|
|
875
|
-
this.writeCachedLinearSelection(cwd, account.title);
|
|
876
|
-
return account;
|
|
877
|
-
}
|
|
878
|
-
cli_1.ui.warn("Please choose a valid account number or title.");
|
|
879
|
-
}
|
|
880
|
-
}
|
|
881
|
-
finally {
|
|
882
|
-
rl.close();
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
getLinearSelectionPath() {
|
|
886
|
-
return node_path_1.default.join(node_os_1.default.homedir(), ".pmate", "linear-selection.json");
|
|
887
|
-
}
|
|
888
|
-
readCachedLinearSelection(cwd) {
|
|
889
|
-
const selectionPath = this.getLinearSelectionPath();
|
|
890
|
-
if (!(0, node_fs_1.existsSync)(selectionPath)) {
|
|
891
|
-
return undefined;
|
|
892
|
-
}
|
|
893
|
-
try {
|
|
894
|
-
const raw = (0, node_fs_1.readFileSync)(selectionPath, "utf8").trim();
|
|
895
|
-
if (!raw)
|
|
896
|
-
return undefined;
|
|
897
|
-
const parsed = JSON.parse(raw);
|
|
898
|
-
const value = parsed[cwd];
|
|
899
|
-
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
900
|
-
}
|
|
901
|
-
catch {
|
|
902
|
-
return undefined;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
writeCachedLinearSelection(cwd, title) {
|
|
906
|
-
const selectionPath = this.getLinearSelectionPath();
|
|
907
|
-
const dir = node_path_1.default.dirname(selectionPath);
|
|
908
|
-
(0, node_fs_1.mkdirSync)(dir, { recursive: true });
|
|
909
|
-
let data = {};
|
|
910
|
-
if ((0, node_fs_1.existsSync)(selectionPath)) {
|
|
911
|
-
try {
|
|
912
|
-
const raw = (0, node_fs_1.readFileSync)(selectionPath, "utf8").trim();
|
|
913
|
-
if (raw) {
|
|
914
|
-
data = JSON.parse(raw);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
catch {
|
|
918
|
-
data = {};
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
data[cwd] = title;
|
|
922
|
-
(0, node_fs_1.writeFileSync)(selectionPath, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
923
|
-
}
|
|
924
|
-
async lookupIssue(client, issueRef) {
|
|
925
|
-
const identifier = this.extractIssueIdentifier(issueRef);
|
|
926
|
-
if (identifier) {
|
|
927
|
-
return this.fetchIssueByIdentifier(client, identifier);
|
|
928
|
-
}
|
|
929
|
-
return this.fetchIssueById(client, issueRef);
|
|
930
|
-
}
|
|
931
|
-
async lookupIssueId(client, issueRef) {
|
|
932
|
-
var _a;
|
|
933
|
-
const issue = await this.lookupIssue(client, issueRef);
|
|
934
|
-
return (_a = issue === null || issue === void 0 ? void 0 : issue.id) !== null && _a !== void 0 ? _a : null;
|
|
935
|
-
}
|
|
936
|
-
async lookupLinearUser(client, query) {
|
|
937
|
-
var _a, _b, _c, _d;
|
|
938
|
-
const data = await client.client.request(USER_BY_QUERY, { query: query.trim(), first: 50 });
|
|
939
|
-
let nodes = (_b = (_a = data.users) === null || _a === void 0 ? void 0 : _a.nodes) !== null && _b !== void 0 ? _b : [];
|
|
940
|
-
if (nodes.length === 0) {
|
|
941
|
-
const fallback = await client.client.request(USERS_LIST_QUERY, { first: 250 });
|
|
942
|
-
const all = (_d = (_c = fallback.users) === null || _c === void 0 ? void 0 : _c.nodes) !== null && _d !== void 0 ? _d : [];
|
|
943
|
-
const normalized = query.trim().toLowerCase();
|
|
944
|
-
nodes = all.filter((node) => {
|
|
945
|
-
const haystacks = [node.name, node.displayName, node.email]
|
|
946
|
-
.filter((value) => Boolean(value === null || value === void 0 ? void 0 : value.trim()))
|
|
947
|
-
.map((value) => value.trim().toLowerCase());
|
|
948
|
-
return haystacks.some((value) => value.includes(normalized));
|
|
949
|
-
});
|
|
950
|
-
if (nodes.length === 0) {
|
|
951
|
-
return null;
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
const normalized = query.trim().toLowerCase();
|
|
955
|
-
const activeNodes = nodes.filter((node) => node.active !== false);
|
|
956
|
-
const candidates = activeNodes.length > 0 ? activeNodes : nodes;
|
|
957
|
-
const exact = candidates.find((node) => {
|
|
958
|
-
var _a, _b, _c;
|
|
959
|
-
const name = (_a = node.name) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase();
|
|
960
|
-
const displayName = (_b = node.displayName) === null || _b === void 0 ? void 0 : _b.trim().toLowerCase();
|
|
961
|
-
const email = (_c = node.email) === null || _c === void 0 ? void 0 : _c.trim().toLowerCase();
|
|
962
|
-
return name === normalized || displayName === normalized || email === normalized;
|
|
963
|
-
});
|
|
964
|
-
if (exact) {
|
|
965
|
-
return exact;
|
|
966
|
-
}
|
|
967
|
-
return candidates[0];
|
|
968
|
-
}
|
|
969
|
-
formatUserLabel(user) {
|
|
970
|
-
var _a, _b, _c, _d;
|
|
971
|
-
const primary = ((_a = user.displayName) === null || _a === void 0 ? void 0 : _a.trim()) || ((_b = user.name) === null || _b === void 0 ? void 0 : _b.trim()) || ((_c = user.email) === null || _c === void 0 ? void 0 : _c.trim()) || "Unknown user";
|
|
972
|
-
const secondary = (_d = user.email) === null || _d === void 0 ? void 0 : _d.trim();
|
|
973
|
-
if (secondary && secondary !== primary) {
|
|
974
|
-
return `${primary} <${secondary}>`;
|
|
975
|
-
}
|
|
976
|
-
return primary;
|
|
977
|
-
}
|
|
978
|
-
extractIssueIdentifier(input) {
|
|
979
|
-
const pattern = /([A-Za-z0-9]+)-(\d+)/i;
|
|
980
|
-
const match = input.match(pattern);
|
|
981
|
-
if (!match)
|
|
982
|
-
return null;
|
|
983
|
-
const [, rawKey, rawNumber] = match;
|
|
984
|
-
if (!rawKey || !rawNumber)
|
|
985
|
-
return null;
|
|
986
|
-
return `${rawKey.toUpperCase()}-${rawNumber}`;
|
|
987
|
-
}
|
|
988
|
-
async fetchIssueByIdentifier(client, identifier) {
|
|
989
|
-
var _a;
|
|
990
|
-
const [teamKey, numberPart] = identifier.split("-");
|
|
991
|
-
const number = Number.parseInt(numberPart !== null && numberPart !== void 0 ? numberPart : "", 10);
|
|
992
|
-
if (!teamKey || Number.isNaN(number)) {
|
|
993
|
-
throw new Error(`Invalid issue identifier "${identifier}".`);
|
|
994
|
-
}
|
|
995
|
-
const data = await client.client.request(ISSUE_BY_IDENTIFIER_QUERY, { teamKey, number });
|
|
996
|
-
return (_a = data.issues.nodes[0]) !== null && _a !== void 0 ? _a : null;
|
|
997
|
-
}
|
|
998
|
-
async fetchIssueById(client, id) {
|
|
999
|
-
var _a;
|
|
1000
|
-
const data = await client.client.request(ISSUE_BY_ID_QUERY, { id });
|
|
1001
|
-
return (_a = data.issue) !== null && _a !== void 0 ? _a : null;
|
|
1002
|
-
}
|
|
1003
|
-
printIssueDetails(issue) {
|
|
1004
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
1005
|
-
const teamName = (_b = (_a = issue.team) === null || _a === void 0 ? void 0 : _a.name) === null || _b === void 0 ? void 0 : _b.trim();
|
|
1006
|
-
const teamLabel = teamName && teamName !== "" ? teamName : (_d = (_c = issue.team) === null || _c === void 0 ? void 0 : _c.key) !== null && _d !== void 0 ? _d : "Unknown team";
|
|
1007
|
-
const projectLabel = ((_f = (_e = issue.project) === null || _e === void 0 ? void 0 : _e.name) === null || _f === void 0 ? void 0 : _f.trim()) || "None";
|
|
1008
|
-
const stateLabel = (_h = (_g = issue.state) === null || _g === void 0 ? void 0 : _g.name) !== null && _h !== void 0 ? _h : "Unknown state";
|
|
1009
|
-
const estimateLabel = (_j = issue.estimate) !== null && _j !== void 0 ? _j : "N/A";
|
|
1010
|
-
const cycleLabel = ((_k = issue.cycle) === null || _k === void 0 ? void 0 : _k.name)
|
|
1011
|
-
? `${issue.cycle.name} (#${issue.cycle.number})`
|
|
1012
|
-
: `#${(_m = (_l = issue.cycle) === null || _l === void 0 ? void 0 : _l.number) !== null && _m !== void 0 ? _m : "N/A"}`;
|
|
1013
|
-
const updatedAt = new Date(issue.updatedAt).toLocaleString();
|
|
1014
|
-
const parentLabel = issue.parent
|
|
1015
|
-
? `${issue.parent.identifier} ${issue.parent.title}`
|
|
1016
|
-
: "None";
|
|
1017
|
-
cli_1.ui.info(`${issue.identifier} | ${stateLabel} | ${issue.priorityLabel} | Est: ${estimateLabel} | ${teamLabel} | Cycle: ${cycleLabel} | Project: ${projectLabel}`);
|
|
1018
|
-
cli_1.ui.info(`Title: ${issue.title}`);
|
|
1019
|
-
cli_1.ui.info(`URL: ${issue.url}`);
|
|
1020
|
-
cli_1.ui.info(`Parent: ${parentLabel}`);
|
|
1021
|
-
cli_1.ui.info(`Updated: ${updatedAt}`);
|
|
1022
|
-
cli_1.ui.info("Description:");
|
|
1023
|
-
cli_1.ui.info(((_o = issue.description) === null || _o === void 0 ? void 0 : _o.trim()) ? issue.description : " (no description)");
|
|
1024
|
-
}
|
|
1025
|
-
async collectReplInput() {
|
|
1026
|
-
const rl = (0, promises_1.createInterface)({
|
|
1027
|
-
input: process.stdin,
|
|
1028
|
-
output: process.stdout,
|
|
1029
|
-
});
|
|
1030
|
-
let text = "";
|
|
1031
|
-
const images = [];
|
|
1032
|
-
try {
|
|
1033
|
-
cli_1.ui.info("Linear repl: choose 0=text, 1=image, done.");
|
|
1034
|
-
while (true) {
|
|
1035
|
-
const choice = (await rl.question("Select (0=text, 1=image, done): "))
|
|
1036
|
-
.trim()
|
|
1037
|
-
.toLowerCase();
|
|
1038
|
-
if (choice === "done") {
|
|
1039
|
-
break;
|
|
1040
|
-
}
|
|
1041
|
-
if (choice === "0" || choice === "text") {
|
|
1042
|
-
const input = await this.readMultiline(rl, "Paste text. End with a line containing only END or DONE.");
|
|
1043
|
-
text = input.trim();
|
|
1044
|
-
cli_1.ui.info("Text updated.");
|
|
1045
|
-
cli_1.ui.info("Tip: type done at the main prompt to finish.");
|
|
1046
|
-
continue;
|
|
1047
|
-
}
|
|
1048
|
-
if (choice === "1" || choice === "image") {
|
|
1049
|
-
if (process.platform !== "darwin") {
|
|
1050
|
-
const raw = await this.readMultiline(rl, "Paste base64 image (data URL ok). End with a line containing only END or DONE.");
|
|
1051
|
-
const image = this.parseImageInput(raw, images.length + 1);
|
|
1052
|
-
images.push(image);
|
|
1053
|
-
cli_1.ui.info(`Added image ${images.length}.`);
|
|
1054
|
-
cli_1.ui.info("Tip: type done at the main prompt to finish.");
|
|
1055
|
-
continue;
|
|
1056
|
-
}
|
|
1057
|
-
cli_1.ui.info("Starting macOS screencapture...");
|
|
1058
|
-
try {
|
|
1059
|
-
const image = await this.captureScreenshotImage(images.length + 1);
|
|
1060
|
-
images.push(image);
|
|
1061
|
-
cli_1.ui.info(`Added image ${images.length}.`);
|
|
1062
|
-
cli_1.ui.info("Tip: type done at the main prompt to finish.");
|
|
1063
|
-
}
|
|
1064
|
-
catch (error) {
|
|
1065
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1066
|
-
cli_1.ui.warn(message);
|
|
1067
|
-
}
|
|
1068
|
-
continue;
|
|
1069
|
-
}
|
|
1070
|
-
cli_1.ui.warn("Unknown selection. Use 0, 1, or done.");
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
finally {
|
|
1074
|
-
rl.close();
|
|
1075
|
-
}
|
|
1076
|
-
return { text, images };
|
|
1077
|
-
}
|
|
1078
|
-
async collectScreenshotInput() {
|
|
1079
|
-
if (process.platform === "darwin") {
|
|
1080
|
-
cli_1.ui.info("Capture a screenshot now (macOS screencapture).");
|
|
1081
|
-
}
|
|
1082
|
-
else {
|
|
1083
|
-
cli_1.ui.info("Copy a screenshot to your clipboard, then press Enter.");
|
|
1084
|
-
}
|
|
1085
|
-
const rl = (0, promises_1.createInterface)({
|
|
1086
|
-
input: process.stdin,
|
|
1087
|
-
output: process.stdout,
|
|
1088
|
-
});
|
|
1089
|
-
try {
|
|
1090
|
-
await rl.question("Press Enter when ready: ");
|
|
1091
|
-
}
|
|
1092
|
-
finally {
|
|
1093
|
-
rl.close();
|
|
1094
|
-
}
|
|
1095
|
-
const image = process.platform === "darwin"
|
|
1096
|
-
? await this.captureScreenshotImage(1)
|
|
1097
|
-
: await this.readClipboardImage(1);
|
|
1098
|
-
return [image];
|
|
1099
|
-
}
|
|
1100
|
-
async captureScreenshotImage(index) {
|
|
1101
|
-
if (process.platform !== "darwin") {
|
|
1102
|
-
throw new Error("Screenshot capture only supports macOS.");
|
|
1103
|
-
}
|
|
1104
|
-
const dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), "pmate-screenshot-"));
|
|
1105
|
-
const filePath = node_path_1.default.join(dir, `screenshot-${index}.png`);
|
|
1106
|
-
try {
|
|
1107
|
-
const code = await this.run("screencapture", ["-i", "-x", filePath], true);
|
|
1108
|
-
if (code !== 0) {
|
|
1109
|
-
throw new Error("Screenshot capture canceled or failed.");
|
|
1110
|
-
}
|
|
1111
|
-
const buffer = await node_fs_1.promises.readFile(filePath);
|
|
1112
|
-
if (buffer.length === 0) {
|
|
1113
|
-
throw new Error("Screenshot capture produced an empty file.");
|
|
1114
|
-
}
|
|
1115
|
-
const mime = this.detectImageMime(buffer);
|
|
1116
|
-
if (!mime) {
|
|
1117
|
-
throw new Error("Screenshot capture produced an unsupported image format.");
|
|
1118
|
-
}
|
|
1119
|
-
const extension = this.extensionFromMime(mime);
|
|
1120
|
-
return { buffer, mime, filename: `screenshot-${index}.${extension}` };
|
|
1121
|
-
}
|
|
1122
|
-
finally {
|
|
1123
|
-
try {
|
|
1124
|
-
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
1125
|
-
}
|
|
1126
|
-
catch {
|
|
1127
|
-
// ignore
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
}
|
|
1131
|
-
async readMultiline(rl, prompt) {
|
|
1132
|
-
cli_1.ui.info(prompt);
|
|
1133
|
-
const lines = [];
|
|
1134
|
-
while (true) {
|
|
1135
|
-
const line = await rl.question("> ");
|
|
1136
|
-
const sentinel = line.trim().toLowerCase();
|
|
1137
|
-
if (sentinel === "end" || sentinel === "done") {
|
|
1138
|
-
break;
|
|
1139
|
-
}
|
|
1140
|
-
lines.push(line);
|
|
1141
|
-
}
|
|
1142
|
-
return lines.join("\n");
|
|
1143
|
-
}
|
|
1144
|
-
parseImageInput(raw, index) {
|
|
1145
|
-
const trimmed = raw.trim();
|
|
1146
|
-
if (!trimmed) {
|
|
1147
|
-
throw new Error("Empty image input.");
|
|
1148
|
-
}
|
|
1149
|
-
let mime = "";
|
|
1150
|
-
let base64 = trimmed;
|
|
1151
|
-
const dataUrlMatch = trimmed.match(/^data:(.+?);base64,(.+)$/s);
|
|
1152
|
-
if (dataUrlMatch) {
|
|
1153
|
-
mime = dataUrlMatch[1];
|
|
1154
|
-
base64 = dataUrlMatch[2];
|
|
1155
|
-
}
|
|
1156
|
-
base64 = base64.replace(/\s+/g, "");
|
|
1157
|
-
const buffer = Buffer.from(base64, "base64");
|
|
1158
|
-
if (buffer.length === 0) {
|
|
1159
|
-
throw new Error("Invalid base64 image input.");
|
|
1160
|
-
}
|
|
1161
|
-
if (!mime) {
|
|
1162
|
-
mime = this.detectImageMime(buffer);
|
|
1163
|
-
}
|
|
1164
|
-
if (!mime) {
|
|
1165
|
-
throw new Error("Unsupported image format. Use PNG, JPEG, GIF, or WEBP.");
|
|
1166
|
-
}
|
|
1167
|
-
const extension = this.extensionFromMime(mime);
|
|
1168
|
-
const filename = `screenshot-${index}.${extension}`;
|
|
1169
|
-
return { buffer, mime, filename };
|
|
1170
|
-
}
|
|
1171
|
-
async readClipboardImage(index) {
|
|
1172
|
-
if (process.platform !== "darwin") {
|
|
1173
|
-
throw new Error("Clipboard image capture only supports macOS.");
|
|
1174
|
-
}
|
|
1175
|
-
let buffer = Buffer.alloc(0);
|
|
1176
|
-
// Screenshots can take a moment to land in the clipboard; retry briefly.
|
|
1177
|
-
for (let attempt = 0; attempt < 12; attempt++) {
|
|
1178
|
-
buffer = await this.captureClipboardImageAsPng();
|
|
1179
|
-
if (buffer.length > 0)
|
|
1180
|
-
break;
|
|
1181
|
-
await sleep(150);
|
|
1182
|
-
}
|
|
1183
|
-
if (buffer.length === 0) {
|
|
1184
|
-
const info = await this.getClipboardDebugInfo();
|
|
1185
|
-
throw new Error(`Clipboard does not contain a supported image.${info ? ` Clipboard info: ${info}` : ""}`);
|
|
1186
|
-
}
|
|
1187
|
-
const mime = this.detectImageMime(buffer);
|
|
1188
|
-
if (!mime) {
|
|
1189
|
-
const info = await this.getClipboardDebugInfo();
|
|
1190
|
-
throw new Error(`Clipboard does not contain a supported image format.${info ? ` Clipboard info: ${info}` : ""}`);
|
|
1191
|
-
}
|
|
1192
|
-
const extension = this.extensionFromMime(mime);
|
|
1193
|
-
return {
|
|
1194
|
-
buffer,
|
|
1195
|
-
mime,
|
|
1196
|
-
filename: `clipboard-${index}.${extension}`,
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
async captureClipboardImageAsPng() {
|
|
1200
|
-
const png = await this.captureClipboard("png");
|
|
1201
|
-
if (this.detectImageMime(png) === "image/png") {
|
|
1202
|
-
return png;
|
|
1203
|
-
}
|
|
1204
|
-
const tiff = await this.captureClipboard("tiff");
|
|
1205
|
-
if (this.isTiff(tiff)) {
|
|
1206
|
-
return this.convertToPng(tiff, "tiff");
|
|
1207
|
-
}
|
|
1208
|
-
const pdf = await this.captureClipboard("pdf");
|
|
1209
|
-
if (this.isPdf(pdf)) {
|
|
1210
|
-
return this.convertToPng(pdf, "pdf");
|
|
1211
|
-
}
|
|
1212
|
-
// pbpaste may return a small non-image payload (often text). Treat as empty.
|
|
1213
|
-
if (this.isProbablyText(png) || png.length < 1024) {
|
|
1214
|
-
return Buffer.alloc(0);
|
|
1215
|
-
}
|
|
1216
|
-
return Buffer.alloc(0);
|
|
1217
|
-
}
|
|
1218
|
-
isProbablyText(buffer) {
|
|
1219
|
-
if (buffer.length === 0)
|
|
1220
|
-
return false;
|
|
1221
|
-
const sample = buffer.subarray(0, Math.min(buffer.length, 200)).toString("utf8");
|
|
1222
|
-
const printable = sample.replace(/[^\x09\x0a\x0d\x20-\x7e]/g, "");
|
|
1223
|
-
const ratio = printable.length / Math.max(1, sample.length);
|
|
1224
|
-
if (ratio < 0.85)
|
|
1225
|
-
return false;
|
|
1226
|
-
return (sample.includes("pbpaste") ||
|
|
1227
|
-
sample.includes("xxd") ||
|
|
1228
|
-
sample.includes("wc -c") ||
|
|
1229
|
-
sample.includes("git "));
|
|
1230
|
-
}
|
|
1231
|
-
isTiff(buffer) {
|
|
1232
|
-
if (buffer.length < 4)
|
|
1233
|
-
return false;
|
|
1234
|
-
// II*\0 or MM\0*
|
|
1235
|
-
return ((buffer[0] === 0x49 &&
|
|
1236
|
-
buffer[1] === 0x49 &&
|
|
1237
|
-
buffer[2] === 0x2a &&
|
|
1238
|
-
buffer[3] === 0x00) ||
|
|
1239
|
-
(buffer[0] === 0x4d &&
|
|
1240
|
-
buffer[1] === 0x4d &&
|
|
1241
|
-
buffer[2] === 0x00 &&
|
|
1242
|
-
buffer[3] === 0x2a));
|
|
1243
|
-
}
|
|
1244
|
-
isPdf(buffer) {
|
|
1245
|
-
if (buffer.length < 4)
|
|
1246
|
-
return false;
|
|
1247
|
-
return (buffer[0] === 0x25 && // %
|
|
1248
|
-
buffer[1] === 0x50 && // P
|
|
1249
|
-
buffer[2] === 0x44 && // D
|
|
1250
|
-
buffer[3] === 0x46 // F
|
|
1251
|
-
);
|
|
1252
|
-
}
|
|
1253
|
-
async getClipboardDebugInfo() {
|
|
1254
|
-
try {
|
|
1255
|
-
const [png, tiff, pdf] = await Promise.all([
|
|
1256
|
-
this.captureClipboard("png"),
|
|
1257
|
-
this.captureClipboard("tiff"),
|
|
1258
|
-
this.captureClipboard("pdf"),
|
|
1259
|
-
]);
|
|
1260
|
-
const text = await this.captureClipboard("txt");
|
|
1261
|
-
const sample = this.toPrintableSample(text, 120);
|
|
1262
|
-
const parts = [
|
|
1263
|
-
`sizes(png=${png.length}, tiff=${tiff.length}, pdf=${pdf.length})`,
|
|
1264
|
-
];
|
|
1265
|
-
if (sample) {
|
|
1266
|
-
parts.push(`textSample="${sample}"`);
|
|
1267
|
-
parts.push("hint=clipboard contains text; copying commands overwrites screenshots");
|
|
1268
|
-
}
|
|
1269
|
-
return parts.join(" ");
|
|
1270
|
-
}
|
|
1271
|
-
catch {
|
|
1272
|
-
return "";
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
toPrintableSample(buffer, limit) {
|
|
1276
|
-
if (buffer.length === 0)
|
|
1277
|
-
return "";
|
|
1278
|
-
const raw = buffer.subarray(0, Math.min(buffer.length, limit)).toString("utf8");
|
|
1279
|
-
const printable = raw.replace(/[^\x09\x0a\x0d\x20-\x7e]/g, "");
|
|
1280
|
-
const ratio = printable.length / Math.max(1, raw.length);
|
|
1281
|
-
if (ratio < 0.85)
|
|
1282
|
-
return "";
|
|
1283
|
-
const compact = printable.replace(/\s+/g, " ").trim();
|
|
1284
|
-
if (!compact)
|
|
1285
|
-
return "";
|
|
1286
|
-
return compact.length > limit ? `${compact.slice(0, limit)}...` : compact;
|
|
1287
|
-
}
|
|
1288
|
-
async convertToPng(buffer, ext) {
|
|
1289
|
-
const dir = await node_fs_1.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), "pmate-"));
|
|
1290
|
-
const inputPath = node_path_1.default.join(dir, `clipboard.${ext}`);
|
|
1291
|
-
const outputPath = node_path_1.default.join(dir, "clipboard.png");
|
|
1292
|
-
try {
|
|
1293
|
-
await node_fs_1.promises.writeFile(inputPath, buffer);
|
|
1294
|
-
const stderr = await this.runCapture("sips", ["-s", "format", "png", inputPath, "--out", outputPath], true);
|
|
1295
|
-
const out = await node_fs_1.promises.readFile(outputPath);
|
|
1296
|
-
if (this.detectImageMime(out) !== "image/png") {
|
|
1297
|
-
throw new Error((stderr === null || stderr === void 0 ? void 0 : stderr.trim())
|
|
1298
|
-
? `Failed to convert clipboard ${ext} to PNG: ${stderr.trim()}`
|
|
1299
|
-
: `Failed to convert clipboard ${ext} to PNG.`);
|
|
1300
|
-
}
|
|
1301
|
-
return out;
|
|
1302
|
-
}
|
|
1303
|
-
finally {
|
|
1304
|
-
try {
|
|
1305
|
-
await node_fs_1.promises.rm(dir, { recursive: true, force: true });
|
|
1306
|
-
}
|
|
1307
|
-
catch {
|
|
1308
|
-
// ignore
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
captureClipboard(preferred) {
|
|
1313
|
-
return new Promise((resolve, reject) => {
|
|
1314
|
-
const child = (0, node_child_process_1.spawn)("pbpaste", ["-Prefer", preferred], {
|
|
1315
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1316
|
-
});
|
|
1317
|
-
const chunks = [];
|
|
1318
|
-
const errors = [];
|
|
1319
|
-
child.stdout.on("data", (chunk) => {
|
|
1320
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1321
|
-
});
|
|
1322
|
-
child.stderr.on("data", (chunk) => {
|
|
1323
|
-
errors.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1324
|
-
});
|
|
1325
|
-
child.on("error", (error) => {
|
|
1326
|
-
reject(error);
|
|
1327
|
-
});
|
|
1328
|
-
child.on("close", (code) => {
|
|
1329
|
-
if (code !== 0) {
|
|
1330
|
-
const message = Buffer.concat(errors).toString("utf8").trim();
|
|
1331
|
-
reject(new Error(message ? `pbpaste failed: ${message}` : "pbpaste failed."));
|
|
1332
|
-
return;
|
|
1333
|
-
}
|
|
1334
|
-
resolve(Buffer.concat(chunks));
|
|
1335
|
-
});
|
|
1336
|
-
});
|
|
1337
|
-
}
|
|
1338
|
-
detectImageMime(buffer) {
|
|
1339
|
-
if (buffer.length >= 8 &&
|
|
1340
|
-
buffer[0] === 0x89 &&
|
|
1341
|
-
buffer[1] === 0x50 &&
|
|
1342
|
-
buffer[2] === 0x4e &&
|
|
1343
|
-
buffer[3] === 0x47 &&
|
|
1344
|
-
buffer[4] === 0x0d &&
|
|
1345
|
-
buffer[5] === 0x0a &&
|
|
1346
|
-
buffer[6] === 0x1a &&
|
|
1347
|
-
buffer[7] === 0x0a) {
|
|
1348
|
-
return "image/png";
|
|
1349
|
-
}
|
|
1350
|
-
if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8) {
|
|
1351
|
-
return "image/jpeg";
|
|
1352
|
-
}
|
|
1353
|
-
if (buffer.length >= 4 &&
|
|
1354
|
-
buffer[0] === 0x47 &&
|
|
1355
|
-
buffer[1] === 0x49 &&
|
|
1356
|
-
buffer[2] === 0x46 &&
|
|
1357
|
-
buffer[3] === 0x38) {
|
|
1358
|
-
return "image/gif";
|
|
1359
|
-
}
|
|
1360
|
-
if (buffer.length >= 12 &&
|
|
1361
|
-
buffer[0] === 0x52 &&
|
|
1362
|
-
buffer[1] === 0x49 &&
|
|
1363
|
-
buffer[2] === 0x46 &&
|
|
1364
|
-
buffer[3] === 0x46 &&
|
|
1365
|
-
buffer[8] === 0x57 &&
|
|
1366
|
-
buffer[9] === 0x45 &&
|
|
1367
|
-
buffer[10] === 0x42 &&
|
|
1368
|
-
buffer[11] === 0x50) {
|
|
1369
|
-
return "image/webp";
|
|
1370
|
-
}
|
|
1371
|
-
return "";
|
|
1372
|
-
}
|
|
1373
|
-
extensionFromMime(mime) {
|
|
1374
|
-
switch (mime) {
|
|
1375
|
-
case "image/png":
|
|
1376
|
-
return "png";
|
|
1377
|
-
case "image/jpeg":
|
|
1378
|
-
return "jpg";
|
|
1379
|
-
case "image/gif":
|
|
1380
|
-
return "gif";
|
|
1381
|
-
case "image/webp":
|
|
1382
|
-
return "webp";
|
|
1383
|
-
default:
|
|
1384
|
-
return "png";
|
|
1385
|
-
}
|
|
1386
|
-
}
|
|
1387
|
-
async getPrDiff(cwd) {
|
|
1388
|
-
return this.runCaptureGh(["pr", "diff", "--color=never"], false, cwd);
|
|
1389
|
-
}
|
|
1390
|
-
async getAiDescription(context) {
|
|
1391
|
-
var _a;
|
|
1392
|
-
const { AgentLlmClient } = await Promise.resolve().then(() => tslib_1.__importStar(require("../llm/AgentLlmClient")));
|
|
1393
|
-
const client = new AgentLlmClient();
|
|
1394
|
-
const parsed = await client.prompt("linear-pr-description", {
|
|
1395
|
-
title: context.title,
|
|
1396
|
-
body: context.body,
|
|
1397
|
-
url: context.url,
|
|
1398
|
-
repository: context.repository,
|
|
1399
|
-
headRefName: context.headRefName,
|
|
1400
|
-
baseRefName: context.baseRefName,
|
|
1401
|
-
author: context.author,
|
|
1402
|
-
diff: truncate(context.diff, 8000),
|
|
1403
|
-
});
|
|
1404
|
-
if (!parsed) {
|
|
1405
|
-
throw new Error("Agent response missing content.");
|
|
1406
|
-
}
|
|
1407
|
-
if (!((_a = parsed.description) === null || _a === void 0 ? void 0 : _a.trim())) {
|
|
1408
|
-
throw new Error("Agent response missing description.");
|
|
1409
|
-
}
|
|
1410
|
-
return parsed.description.trim();
|
|
1411
|
-
}
|
|
1412
|
-
async getAiTicketFromNotes(input) {
|
|
1413
|
-
var _a, _b, _c;
|
|
1414
|
-
const client = this.createOpenAiClient();
|
|
1415
|
-
const prompt = [
|
|
1416
|
-
"You create Linear tickets from chat notes and screenshots.",
|
|
1417
|
-
"Return ONLY valid JSON with keys: title, description.",
|
|
1418
|
-
"The description should summarize the request, context, and expected outcome.",
|
|
1419
|
-
"Do not include tool or product names like 'pmate' or phrases like 'Created by Pmate'.",
|
|
1420
|
-
"",
|
|
1421
|
-
"Text input:",
|
|
1422
|
-
((_a = input.text) === null || _a === void 0 ? void 0 : _a.trim()) || "(empty)",
|
|
1423
|
-
].join("\n");
|
|
1424
|
-
const content = [
|
|
1425
|
-
{ type: "input_text", text: prompt },
|
|
1426
|
-
];
|
|
1427
|
-
for (const image of input.images) {
|
|
1428
|
-
const base64 = image.buffer.toString("base64");
|
|
1429
|
-
content.push({
|
|
1430
|
-
type: "input_image",
|
|
1431
|
-
image_url: `data:${image.mime};base64,${base64}`,
|
|
1432
|
-
detail: "auto",
|
|
1433
|
-
});
|
|
1434
|
-
}
|
|
1435
|
-
const response = await client.responses.create({
|
|
1436
|
-
model: "gpt-5.2-2025-12-11",
|
|
1437
|
-
instructions: "You output strict JSON only.",
|
|
1438
|
-
input: [
|
|
1439
|
-
{
|
|
1440
|
-
role: "user",
|
|
1441
|
-
content,
|
|
1442
|
-
},
|
|
1443
|
-
],
|
|
1444
|
-
text: {
|
|
1445
|
-
format: {
|
|
1446
|
-
type: "json_schema",
|
|
1447
|
-
name: "linear_ticket",
|
|
1448
|
-
strict: true,
|
|
1449
|
-
schema: {
|
|
1450
|
-
type: "object",
|
|
1451
|
-
additionalProperties: false,
|
|
1452
|
-
required: ["title", "description"],
|
|
1453
|
-
properties: {
|
|
1454
|
-
title: { type: "string" },
|
|
1455
|
-
description: { type: "string" },
|
|
1456
|
-
},
|
|
1457
|
-
},
|
|
1458
|
-
},
|
|
1459
|
-
},
|
|
1460
|
-
temperature: 0.2,
|
|
1461
|
-
});
|
|
1462
|
-
const contentText = typeof response.output_text === "string"
|
|
1463
|
-
? response.output_text.trim()
|
|
1464
|
-
: this.getResponseText(response);
|
|
1465
|
-
if (!contentText) {
|
|
1466
|
-
throw new Error("OpenAI response missing content.");
|
|
1467
|
-
}
|
|
1468
|
-
const parsed = parseJson(contentText);
|
|
1469
|
-
if (!((_b = parsed.title) === null || _b === void 0 ? void 0 : _b.trim()) || !((_c = parsed.description) === null || _c === void 0 ? void 0 : _c.trim())) {
|
|
1470
|
-
throw new Error("OpenAI response missing title or description.");
|
|
1471
|
-
}
|
|
1472
|
-
return {
|
|
1473
|
-
title: parsed.title.trim(),
|
|
1474
|
-
description: parsed.description.trim(),
|
|
1475
|
-
};
|
|
1476
|
-
}
|
|
1477
|
-
async attachImages(client, issueId, images) {
|
|
1478
|
-
for (const image of images) {
|
|
1479
|
-
const upload = await client.fileUpload(image.mime, image.filename, image.buffer.length);
|
|
1480
|
-
const uploadFile = upload.uploadFile;
|
|
1481
|
-
if (!uploadFile) {
|
|
1482
|
-
throw new Error("Linear file upload did not return an upload URL.");
|
|
1483
|
-
}
|
|
1484
|
-
await this.uploadToLinear(uploadFile, image.buffer);
|
|
1485
|
-
await client.createAttachment({
|
|
1486
|
-
issueId,
|
|
1487
|
-
title: image.filename,
|
|
1488
|
-
url: uploadFile.assetUrl,
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
async uploadToLinear(uploadFile, data) {
|
|
1493
|
-
var _a;
|
|
1494
|
-
const headers = {};
|
|
1495
|
-
for (const header of (_a = uploadFile.headers) !== null && _a !== void 0 ? _a : []) {
|
|
1496
|
-
headers[header.key] = header.value;
|
|
1497
|
-
}
|
|
1498
|
-
if (!headers["Content-Type"]) {
|
|
1499
|
-
headers["Content-Type"] = uploadFile.contentType;
|
|
1500
|
-
}
|
|
1501
|
-
const response = await fetch(uploadFile.uploadUrl, {
|
|
1502
|
-
method: "PUT",
|
|
1503
|
-
headers,
|
|
1504
|
-
body: data,
|
|
1505
|
-
});
|
|
1506
|
-
if (!response.ok) {
|
|
1507
|
-
const detail = await response.text();
|
|
1508
|
-
throw new Error(`Failed to upload image to Linear (${response.status}): ${detail || response.statusText}`);
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
async ensureGitRepo(cwd) {
|
|
1512
|
-
const inside = await this.runCapture("git", ["rev-parse", "--is-inside-work-tree"], false, undefined, cwd);
|
|
1513
|
-
if (inside.trim() !== "true") {
|
|
1514
|
-
throw new Error("This command must be run inside a git repository.");
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
async ensureGhAuth() {
|
|
1518
|
-
const code = await this.runGh(["auth", "status"], true);
|
|
1519
|
-
if (code !== 0) {
|
|
1520
|
-
throw new Error("GitHub CLI is not authenticated. Run: gh auth login.");
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
async ensureGhCli() {
|
|
1524
|
-
await this.runGh(["--version"]);
|
|
1525
|
-
}
|
|
1526
|
-
run(command, args, quietError = false, env, cwd) {
|
|
1527
|
-
return new Promise((resolve, reject) => {
|
|
1528
|
-
const child = (0, node_child_process_1.spawn)(command, args, {
|
|
1529
|
-
stdio: quietError ? "ignore" : "inherit",
|
|
1530
|
-
env: env !== null && env !== void 0 ? env : process.env,
|
|
1531
|
-
cwd,
|
|
1532
|
-
});
|
|
1533
|
-
child.on("error", (error) => {
|
|
1534
|
-
reject(error);
|
|
1535
|
-
});
|
|
1536
|
-
child.on("close", (code) => {
|
|
1537
|
-
if (code === 0 || quietError) {
|
|
1538
|
-
resolve(code !== null && code !== void 0 ? code : 1);
|
|
1539
|
-
return;
|
|
1540
|
-
}
|
|
1541
|
-
reject(new Error(`${command} exited with code ${code}`));
|
|
1542
|
-
});
|
|
1543
|
-
});
|
|
1544
|
-
}
|
|
1545
|
-
runCapture(command, args, allowFailure = false, env, cwd) {
|
|
1546
|
-
return new Promise((resolve, reject) => {
|
|
1547
|
-
var _a, _b;
|
|
1548
|
-
const child = (0, node_child_process_1.spawn)(command, args, {
|
|
1549
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
1550
|
-
env: env !== null && env !== void 0 ? env : process.env,
|
|
1551
|
-
cwd,
|
|
1552
|
-
});
|
|
1553
|
-
let output = "";
|
|
1554
|
-
let errorOutput = "";
|
|
1555
|
-
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", (chunk) => {
|
|
1556
|
-
output += chunk.toString();
|
|
1557
|
-
});
|
|
1558
|
-
(_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on("data", (chunk) => {
|
|
1559
|
-
errorOutput += chunk.toString();
|
|
1560
|
-
});
|
|
1561
|
-
child.on("error", (error) => {
|
|
1562
|
-
reject(error);
|
|
1563
|
-
});
|
|
1564
|
-
child.on("close", (code) => {
|
|
1565
|
-
if (code === 0 || allowFailure) {
|
|
1566
|
-
resolve(output);
|
|
1567
|
-
return;
|
|
1568
|
-
}
|
|
1569
|
-
const trimmed = errorOutput.trim();
|
|
1570
|
-
reject(new Error(trimmed
|
|
1571
|
-
? `${command} failed: ${trimmed}`
|
|
1572
|
-
: `${command} exited with code ${code}`));
|
|
1573
|
-
});
|
|
1574
|
-
});
|
|
1575
|
-
}
|
|
1576
|
-
async getGhEnv(cwd) {
|
|
1577
|
-
var _a;
|
|
1578
|
-
const env = { ...process.env };
|
|
1579
|
-
delete env.GH_TOKEN;
|
|
1580
|
-
const repoConfig = (0, config_1.loadRepoConfig)(cwd !== null && cwd !== void 0 ? cwd : process.cwd());
|
|
1581
|
-
const githubUser = (_a = repoConfig === null || repoConfig === void 0 ? void 0 : repoConfig.githubUser) === null || _a === void 0 ? void 0 : _a.trim();
|
|
1582
|
-
if (!githubUser) {
|
|
1583
|
-
return env;
|
|
1584
|
-
}
|
|
1585
|
-
const token = (await this.runCapture("gh", ["auth", "token", "--user", githubUser], false, env, cwd)).trim();
|
|
1586
|
-
if (!token) {
|
|
1587
|
-
throw new Error(`Failed to resolve GitHub token for ${githubUser}.`);
|
|
1588
|
-
}
|
|
1589
|
-
env.GH_TOKEN = token;
|
|
1590
|
-
return env;
|
|
1591
|
-
}
|
|
1592
|
-
async runGh(args, quietError = false, cwd) {
|
|
1593
|
-
return this.run("gh", args, quietError, await this.getGhEnv(cwd), cwd);
|
|
1594
|
-
}
|
|
1595
|
-
async runCaptureGh(args, allowFailure = false, cwd) {
|
|
1596
|
-
return this.runCapture("gh", args, allowFailure, await this.getGhEnv(cwd), cwd);
|
|
1597
|
-
}
|
|
1598
|
-
resolveCwd(value) {
|
|
1599
|
-
return value ? node_path_1.default.resolve(process.cwd(), value) : process.cwd();
|
|
1600
|
-
}
|
|
1601
|
-
createOpenAiClient() {
|
|
1602
|
-
var _a, _b, _c, _d;
|
|
1603
|
-
const config = (0, config_1.loadPmateConfig)();
|
|
1604
|
-
const apiKey = (_b = (_a = config.openAI) === null || _a === void 0 ? void 0 : _a.apiKey) === null || _b === void 0 ? void 0 : _b.trim();
|
|
1605
|
-
if (!apiKey) {
|
|
1606
|
-
throw new Error("Missing openAI.apiKey in ~/.pmate/config.yaml.");
|
|
1607
|
-
}
|
|
1608
|
-
const baseUrl = (_d = (_c = config.openAI) === null || _c === void 0 ? void 0 : _c.baseUrl) === null || _d === void 0 ? void 0 : _d.trim();
|
|
1609
|
-
return new openai_1.default({
|
|
1610
|
-
apiKey,
|
|
1611
|
-
baseURL: baseUrl ? `${baseUrl}/v1` : undefined,
|
|
1612
|
-
});
|
|
1613
|
-
}
|
|
1614
|
-
getResponseText(response) {
|
|
1615
|
-
var _a, _b;
|
|
1616
|
-
const data = response;
|
|
1617
|
-
const output = (_a = data.output) !== null && _a !== void 0 ? _a : [];
|
|
1618
|
-
for (const item of output) {
|
|
1619
|
-
const content = (_b = item.content) !== null && _b !== void 0 ? _b : [];
|
|
1620
|
-
for (const part of content) {
|
|
1621
|
-
if (part.type === "output_text" && typeof part.text === "string") {
|
|
1622
|
-
return part.text.trim();
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
}
|
|
1626
|
-
return "";
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
exports.LinearCommandHandler = LinearCommandHandler;
|
|
1630
|
-
function truncate(value, limit) {
|
|
1631
|
-
if (value.length <= limit) {
|
|
1632
|
-
return value;
|
|
1633
|
-
}
|
|
1634
|
-
return `${value.slice(0, limit)}\n...[truncated]`;
|
|
1635
|
-
}
|
|
1636
|
-
function parseJson(raw) {
|
|
1637
|
-
const trimmed = raw.trim();
|
|
1638
|
-
try {
|
|
1639
|
-
return JSON.parse(trimmed);
|
|
1640
|
-
}
|
|
1641
|
-
catch {
|
|
1642
|
-
const match = trimmed.match(/\{[\s\S]*\}/);
|
|
1643
|
-
if (!match) {
|
|
1644
|
-
throw new Error("Failed to parse JSON from OpenAI response.");
|
|
1645
|
-
}
|
|
1646
|
-
return JSON.parse(match[0]);
|
|
1647
|
-
}
|
|
1648
|
-
}
|
|
1649
|
-
function sleep(ms) {
|
|
1650
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1651
|
-
}
|
|
1652
|
-
//# sourceMappingURL=LinearCommandHandler.js.map
|