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