@alasano/pi-linear 0.0.1
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/README.md +181 -0
- package/assets/screenshot.png +0 -0
- package/extensions/client.ts +291 -0
- package/extensions/index.ts +214 -0
- package/extensions/params.ts +44 -0
- package/extensions/selections.ts +327 -0
- package/extensions/settings.ts +415 -0
- package/extensions/tools/comments.ts +237 -0
- package/extensions/tools/documents.ts +357 -0
- package/extensions/tools/initiatives.ts +328 -0
- package/extensions/tools/issue-labels.ts +273 -0
- package/extensions/tools/issue-relations.ts +207 -0
- package/extensions/tools/issue-statuses.ts +72 -0
- package/extensions/tools/issues.ts +674 -0
- package/extensions/tools/milestones.ts +250 -0
- package/extensions/tools/project-labels.ts +227 -0
- package/extensions/tools/project-relations.ts +219 -0
- package/extensions/tools/projects.ts +365 -0
- package/extensions/tools/teams.ts +107 -0
- package/extensions/tools/users.ts +107 -0
- package/extensions/tools/workspaces.ts +33 -0
- package/extensions/types.ts +31 -0
- package/extensions/util.ts +38 -0
- package/package.json +38 -0
|
@@ -0,0 +1,674 @@
|
|
|
1
|
+
import { defineTool } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Type } from '@sinclair/typebox';
|
|
3
|
+
import {
|
|
4
|
+
withLinearAuth,
|
|
5
|
+
linearGraphQL,
|
|
6
|
+
resolveIssueId,
|
|
7
|
+
resolveTeamId,
|
|
8
|
+
fetchIssueByIdentifier,
|
|
9
|
+
} from '../client';
|
|
10
|
+
import {
|
|
11
|
+
PaginationParams,
|
|
12
|
+
FilterParam,
|
|
13
|
+
SortParam,
|
|
14
|
+
TeamConvenienceParams,
|
|
15
|
+
RawInputParam,
|
|
16
|
+
} from '../params';
|
|
17
|
+
import { ISSUE_SELECTION } from '../selections';
|
|
18
|
+
import type { LinearIssue, JsonObject } from '../types';
|
|
19
|
+
import { compactObject, asObject, asObjectArray, asString, mergeFilters } from '../util';
|
|
20
|
+
|
|
21
|
+
export function issueTools() {
|
|
22
|
+
return [
|
|
23
|
+
defineTool({
|
|
24
|
+
name: 'linear_list_issues',
|
|
25
|
+
label: 'Linear List Issues',
|
|
26
|
+
description:
|
|
27
|
+
'List Linear issues. Supports full issues query args (after, before, filter, first, includeArchived, last, orderBy, sort) and convenience filters (query, teamKey, teamId, stateName, assigneeId).',
|
|
28
|
+
parameters: Type.Object({
|
|
29
|
+
query: Type.Optional(
|
|
30
|
+
Type.String({
|
|
31
|
+
description: 'Convenience filter: title contains this text (containsIgnoreCase).',
|
|
32
|
+
}),
|
|
33
|
+
),
|
|
34
|
+
stateName: Type.Optional(
|
|
35
|
+
Type.String({
|
|
36
|
+
description: 'Convenience filter: state name equals this value.',
|
|
37
|
+
}),
|
|
38
|
+
),
|
|
39
|
+
assigneeId: Type.Optional(
|
|
40
|
+
Type.String({
|
|
41
|
+
description: 'Convenience filter: assignee id equals this value.',
|
|
42
|
+
}),
|
|
43
|
+
),
|
|
44
|
+
...TeamConvenienceParams,
|
|
45
|
+
...PaginationParams,
|
|
46
|
+
...FilterParam,
|
|
47
|
+
...SortParam,
|
|
48
|
+
}),
|
|
49
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
50
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
51
|
+
const convenienceFilter = compactObject({
|
|
52
|
+
title: params.query ? { containsIgnoreCase: params.query } : undefined,
|
|
53
|
+
team: params.teamKey
|
|
54
|
+
? { key: { eq: params.teamKey } }
|
|
55
|
+
: params.teamId
|
|
56
|
+
? { id: { eq: params.teamId } }
|
|
57
|
+
: undefined,
|
|
58
|
+
state: params.stateName ? { name: { eq: params.stateName } } : undefined,
|
|
59
|
+
assignee: params.assigneeId ? { id: { eq: params.assigneeId } } : undefined,
|
|
60
|
+
}) as JsonObject;
|
|
61
|
+
|
|
62
|
+
const filter = mergeFilters(
|
|
63
|
+
asObject(params.filter),
|
|
64
|
+
Object.keys(convenienceFilter).length ? convenienceFilter : undefined,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const variables = compactObject({
|
|
68
|
+
after: params.after,
|
|
69
|
+
before: params.before,
|
|
70
|
+
filter,
|
|
71
|
+
first: params.first ?? 20,
|
|
72
|
+
includeArchived: params.includeArchived,
|
|
73
|
+
last: params.last,
|
|
74
|
+
orderBy: params.orderBy,
|
|
75
|
+
sort: asObjectArray(params.sort),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const data = await linearGraphQL<{ issues: { nodes: LinearIssue[] } }>(
|
|
79
|
+
apiKey,
|
|
80
|
+
`query ListIssues(
|
|
81
|
+
$after: String
|
|
82
|
+
$before: String
|
|
83
|
+
$filter: IssueFilter
|
|
84
|
+
$first: Int
|
|
85
|
+
$includeArchived: Boolean
|
|
86
|
+
$last: Int
|
|
87
|
+
$orderBy: PaginationOrderBy
|
|
88
|
+
$sort: [IssueSortInput!]
|
|
89
|
+
) {
|
|
90
|
+
issues(
|
|
91
|
+
after: $after
|
|
92
|
+
before: $before
|
|
93
|
+
filter: $filter
|
|
94
|
+
first: $first
|
|
95
|
+
includeArchived: $includeArchived
|
|
96
|
+
last: $last
|
|
97
|
+
orderBy: $orderBy
|
|
98
|
+
sort: $sort
|
|
99
|
+
) {
|
|
100
|
+
nodes {
|
|
101
|
+
${ISSUE_SELECTION}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}`,
|
|
105
|
+
variables,
|
|
106
|
+
signal,
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const issues = data.issues.nodes;
|
|
110
|
+
return {
|
|
111
|
+
content: [{ type: 'text', text: JSON.stringify({ issues }, null, 2) }],
|
|
112
|
+
details: { issues },
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
defineTool({
|
|
118
|
+
name: 'linear_get_issue',
|
|
119
|
+
label: 'Linear Get Issue',
|
|
120
|
+
description: 'Get full details for a Linear issue by identifier (e.g. ENG-123) or issue id.',
|
|
121
|
+
parameters: Type.Object({
|
|
122
|
+
issue: Type.String({
|
|
123
|
+
description: 'Issue identifier (ENG-123) or issue id.',
|
|
124
|
+
}),
|
|
125
|
+
}),
|
|
126
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
127
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
128
|
+
const issueRef = params.issue.trim();
|
|
129
|
+
const identifierIssue = await fetchIssueByIdentifier(apiKey, issueRef, signal);
|
|
130
|
+
|
|
131
|
+
const issue =
|
|
132
|
+
identifierIssue ||
|
|
133
|
+
(
|
|
134
|
+
await linearGraphQL<{ issue: LinearIssue | null }>(
|
|
135
|
+
apiKey,
|
|
136
|
+
`query GetIssueById($id: String!) {
|
|
137
|
+
issue(id: $id) {
|
|
138
|
+
${ISSUE_SELECTION}
|
|
139
|
+
}
|
|
140
|
+
}`,
|
|
141
|
+
{ id: issueRef },
|
|
142
|
+
signal,
|
|
143
|
+
)
|
|
144
|
+
).issue;
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
content: [{ type: 'text', text: JSON.stringify({ issue: issue ?? null }, null, 2) }],
|
|
148
|
+
details: { issue: issue ?? null },
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
}),
|
|
153
|
+
defineTool({
|
|
154
|
+
name: 'linear_create_issue',
|
|
155
|
+
label: 'Linear Create Issue',
|
|
156
|
+
description:
|
|
157
|
+
'Create a Linear issue. Supports all IssueCreateInput fields via top-level params and/or input object. Provide teamId or teamKey (or teamId inside input).',
|
|
158
|
+
parameters: Type.Object({
|
|
159
|
+
...TeamConvenienceParams,
|
|
160
|
+
title: Type.Optional(Type.String({ description: 'Issue title.' })),
|
|
161
|
+
description: Type.Optional(Type.String({ description: 'Issue description in markdown.' })),
|
|
162
|
+
assigneeId: Type.Optional(Type.String({ description: 'IssueCreateInput.assigneeId' })),
|
|
163
|
+
completedAt: Type.Optional(Type.String({ description: 'IssueCreateInput.completedAt' })),
|
|
164
|
+
createAsUser: Type.Optional(Type.String({ description: 'IssueCreateInput.createAsUser' })),
|
|
165
|
+
createdAt: Type.Optional(Type.String({ description: 'IssueCreateInput.createdAt' })),
|
|
166
|
+
cycleId: Type.Optional(Type.String({ description: 'IssueCreateInput.cycleId' })),
|
|
167
|
+
delegateId: Type.Optional(Type.String({ description: 'IssueCreateInput.delegateId' })),
|
|
168
|
+
descriptionData: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
169
|
+
displayIconUrl: Type.Optional(
|
|
170
|
+
Type.String({ description: 'IssueCreateInput.displayIconUrl' }),
|
|
171
|
+
),
|
|
172
|
+
dueDate: Type.Optional(
|
|
173
|
+
Type.String({ description: 'IssueCreateInput.dueDate (YYYY-MM-DD)' }),
|
|
174
|
+
),
|
|
175
|
+
estimate: Type.Optional(Type.Integer({ description: 'IssueCreateInput.estimate' })),
|
|
176
|
+
id: Type.Optional(Type.String({ description: 'IssueCreateInput.id' })),
|
|
177
|
+
labelIds: Type.Optional(
|
|
178
|
+
Type.Array(Type.String(), { description: 'IssueCreateInput.labelIds' }),
|
|
179
|
+
),
|
|
180
|
+
lastAppliedTemplateId: Type.Optional(
|
|
181
|
+
Type.String({ description: 'IssueCreateInput.lastAppliedTemplateId' }),
|
|
182
|
+
),
|
|
183
|
+
parentId: Type.Optional(Type.String({ description: 'IssueCreateInput.parentId' })),
|
|
184
|
+
preserveSortOrderOnCreate: Type.Optional(
|
|
185
|
+
Type.Boolean({
|
|
186
|
+
description: 'IssueCreateInput.preserveSortOrderOnCreate',
|
|
187
|
+
}),
|
|
188
|
+
),
|
|
189
|
+
priority: Type.Optional(
|
|
190
|
+
Type.Number({
|
|
191
|
+
minimum: 0,
|
|
192
|
+
maximum: 4,
|
|
193
|
+
description: 'IssueCreateInput.priority (0 none, 1 urgent, 2 high, 3 normal, 4 low).',
|
|
194
|
+
}),
|
|
195
|
+
),
|
|
196
|
+
prioritySortOrder: Type.Optional(
|
|
197
|
+
Type.Number({ description: 'IssueCreateInput.prioritySortOrder' }),
|
|
198
|
+
),
|
|
199
|
+
projectId: Type.Optional(Type.String({ description: 'IssueCreateInput.projectId' })),
|
|
200
|
+
projectMilestoneId: Type.Optional(
|
|
201
|
+
Type.String({ description: 'IssueCreateInput.projectMilestoneId' }),
|
|
202
|
+
),
|
|
203
|
+
referenceCommentId: Type.Optional(
|
|
204
|
+
Type.String({ description: 'IssueCreateInput.referenceCommentId' }),
|
|
205
|
+
),
|
|
206
|
+
slaBreachesAt: Type.Optional(
|
|
207
|
+
Type.String({ description: 'IssueCreateInput.slaBreachesAt' }),
|
|
208
|
+
),
|
|
209
|
+
slaStartedAt: Type.Optional(Type.String({ description: 'IssueCreateInput.slaStartedAt' })),
|
|
210
|
+
slaType: Type.Optional(Type.String({ description: 'IssueCreateInput.slaType' })),
|
|
211
|
+
sortOrder: Type.Optional(Type.Number({ description: 'IssueCreateInput.sortOrder' })),
|
|
212
|
+
sourceCommentId: Type.Optional(
|
|
213
|
+
Type.String({ description: 'IssueCreateInput.sourceCommentId' }),
|
|
214
|
+
),
|
|
215
|
+
sourcePullRequestCommentId: Type.Optional(
|
|
216
|
+
Type.String({
|
|
217
|
+
description: 'IssueCreateInput.sourcePullRequestCommentId',
|
|
218
|
+
}),
|
|
219
|
+
),
|
|
220
|
+
stateId: Type.Optional(Type.String({ description: 'IssueCreateInput.stateId' })),
|
|
221
|
+
subIssueSortOrder: Type.Optional(
|
|
222
|
+
Type.Number({ description: 'IssueCreateInput.subIssueSortOrder' }),
|
|
223
|
+
),
|
|
224
|
+
subscriberIds: Type.Optional(
|
|
225
|
+
Type.Array(Type.String(), {
|
|
226
|
+
description: 'IssueCreateInput.subscriberIds',
|
|
227
|
+
}),
|
|
228
|
+
),
|
|
229
|
+
templateId: Type.Optional(Type.String({ description: 'IssueCreateInput.templateId' })),
|
|
230
|
+
useDefaultTemplate: Type.Optional(
|
|
231
|
+
Type.Boolean({ description: 'IssueCreateInput.useDefaultTemplate' }),
|
|
232
|
+
),
|
|
233
|
+
...RawInputParam,
|
|
234
|
+
}),
|
|
235
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
236
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
237
|
+
const rawInput = asObject(params.input) || {};
|
|
238
|
+
const rawInputTeamId = asString(rawInput.teamId);
|
|
239
|
+
|
|
240
|
+
const teamId = await resolveTeamId(
|
|
241
|
+
apiKey,
|
|
242
|
+
{
|
|
243
|
+
teamId: params.teamId || rawInputTeamId,
|
|
244
|
+
teamKey: params.teamKey,
|
|
245
|
+
},
|
|
246
|
+
signal,
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const convenienceInput = compactObject({
|
|
250
|
+
assigneeId: params.assigneeId,
|
|
251
|
+
completedAt: params.completedAt,
|
|
252
|
+
createAsUser: params.createAsUser,
|
|
253
|
+
createdAt: params.createdAt,
|
|
254
|
+
cycleId: params.cycleId,
|
|
255
|
+
delegateId: params.delegateId,
|
|
256
|
+
description: params.description,
|
|
257
|
+
descriptionData: asObject(params.descriptionData),
|
|
258
|
+
displayIconUrl: params.displayIconUrl,
|
|
259
|
+
dueDate: params.dueDate,
|
|
260
|
+
estimate: params.estimate,
|
|
261
|
+
id: params.id,
|
|
262
|
+
labelIds: params.labelIds,
|
|
263
|
+
lastAppliedTemplateId: params.lastAppliedTemplateId,
|
|
264
|
+
parentId: params.parentId,
|
|
265
|
+
preserveSortOrderOnCreate: params.preserveSortOrderOnCreate,
|
|
266
|
+
priority: params.priority,
|
|
267
|
+
prioritySortOrder: params.prioritySortOrder,
|
|
268
|
+
projectId: params.projectId,
|
|
269
|
+
projectMilestoneId: params.projectMilestoneId,
|
|
270
|
+
referenceCommentId: params.referenceCommentId,
|
|
271
|
+
slaBreachesAt: params.slaBreachesAt,
|
|
272
|
+
slaStartedAt: params.slaStartedAt,
|
|
273
|
+
slaType: params.slaType,
|
|
274
|
+
sortOrder: params.sortOrder,
|
|
275
|
+
sourceCommentId: params.sourceCommentId,
|
|
276
|
+
sourcePullRequestCommentId: params.sourcePullRequestCommentId,
|
|
277
|
+
stateId: params.stateId,
|
|
278
|
+
subIssueSortOrder: params.subIssueSortOrder,
|
|
279
|
+
subscriberIds: params.subscriberIds,
|
|
280
|
+
teamId,
|
|
281
|
+
templateId: params.templateId,
|
|
282
|
+
title: params.title,
|
|
283
|
+
useDefaultTemplate: params.useDefaultTemplate,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const input = {
|
|
287
|
+
...rawInput,
|
|
288
|
+
...convenienceInput,
|
|
289
|
+
teamId,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
if (!asString(input.title)) {
|
|
293
|
+
throw new Error('Issue title is required for issueCreate (title).');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const data = await linearGraphQL<{
|
|
297
|
+
issueCreate: { success: boolean; issue?: LinearIssue | null };
|
|
298
|
+
}>(
|
|
299
|
+
apiKey,
|
|
300
|
+
`mutation CreateIssue($input: IssueCreateInput!) {
|
|
301
|
+
issueCreate(input: $input) {
|
|
302
|
+
success
|
|
303
|
+
issue {
|
|
304
|
+
${ISSUE_SELECTION}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}`,
|
|
308
|
+
{ input },
|
|
309
|
+
signal,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
if (!data.issueCreate.success || !data.issueCreate.issue) {
|
|
313
|
+
throw new Error('Linear issueCreate did not succeed.');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const issue = data.issueCreate.issue;
|
|
317
|
+
return {
|
|
318
|
+
content: [{ type: 'text', text: JSON.stringify({ issue }, null, 2) }],
|
|
319
|
+
details: { issue },
|
|
320
|
+
};
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
}),
|
|
324
|
+
defineTool({
|
|
325
|
+
name: 'linear_update_issue',
|
|
326
|
+
label: 'Linear Update Issue',
|
|
327
|
+
description:
|
|
328
|
+
'Update a Linear issue by identifier (ENG-123) or issue id. Supports all IssueUpdateInput fields via top-level params and/or input object. Use clearDueDate=true (or dueDate=null in input) to clear due date.',
|
|
329
|
+
parameters: Type.Object({
|
|
330
|
+
issue: Type.String({
|
|
331
|
+
description: 'Issue identifier (ENG-123) or issue id.',
|
|
332
|
+
}),
|
|
333
|
+
title: Type.Optional(Type.String({ description: 'IssueUpdateInput.title' })),
|
|
334
|
+
description: Type.Optional(Type.String({ description: 'IssueUpdateInput.description' })),
|
|
335
|
+
priority: Type.Optional(
|
|
336
|
+
Type.Number({
|
|
337
|
+
minimum: 0,
|
|
338
|
+
maximum: 4,
|
|
339
|
+
description: 'IssueUpdateInput.priority (0 none, 1 urgent, 2 high, 3 normal, 4 low).',
|
|
340
|
+
}),
|
|
341
|
+
),
|
|
342
|
+
stateId: Type.Optional(Type.String({ description: 'IssueUpdateInput.stateId' })),
|
|
343
|
+
assigneeId: Type.Optional(Type.String({ description: 'IssueUpdateInput.assigneeId' })),
|
|
344
|
+
dueDate: Type.Optional(
|
|
345
|
+
Type.String({
|
|
346
|
+
description: 'IssueUpdateInput.dueDate (YYYY-MM-DD). Empty string clears.',
|
|
347
|
+
}),
|
|
348
|
+
),
|
|
349
|
+
clearDueDate: Type.Optional(
|
|
350
|
+
Type.Boolean({ description: 'If true, dueDate is set to null.' }),
|
|
351
|
+
),
|
|
352
|
+
addedLabelIds: Type.Optional(
|
|
353
|
+
Type.Array(Type.String(), {
|
|
354
|
+
description: 'IssueUpdateInput.addedLabelIds',
|
|
355
|
+
}),
|
|
356
|
+
),
|
|
357
|
+
autoClosedByParentClosing: Type.Optional(
|
|
358
|
+
Type.Boolean({
|
|
359
|
+
description: 'IssueUpdateInput.autoClosedByParentClosing',
|
|
360
|
+
}),
|
|
361
|
+
),
|
|
362
|
+
cycleId: Type.Optional(Type.String({ description: 'IssueUpdateInput.cycleId' })),
|
|
363
|
+
delegateId: Type.Optional(Type.String({ description: 'IssueUpdateInput.delegateId' })),
|
|
364
|
+
descriptionData: Type.Optional(Type.Record(Type.String(), Type.Any())),
|
|
365
|
+
estimate: Type.Optional(Type.Integer({ description: 'IssueUpdateInput.estimate' })),
|
|
366
|
+
labelIds: Type.Optional(
|
|
367
|
+
Type.Array(Type.String(), { description: 'IssueUpdateInput.labelIds' }),
|
|
368
|
+
),
|
|
369
|
+
lastAppliedTemplateId: Type.Optional(
|
|
370
|
+
Type.String({ description: 'IssueUpdateInput.lastAppliedTemplateId' }),
|
|
371
|
+
),
|
|
372
|
+
parentId: Type.Optional(Type.String({ description: 'IssueUpdateInput.parentId' })),
|
|
373
|
+
prioritySortOrder: Type.Optional(
|
|
374
|
+
Type.Number({ description: 'IssueUpdateInput.prioritySortOrder' }),
|
|
375
|
+
),
|
|
376
|
+
projectId: Type.Optional(Type.String({ description: 'IssueUpdateInput.projectId' })),
|
|
377
|
+
projectMilestoneId: Type.Optional(
|
|
378
|
+
Type.String({ description: 'IssueUpdateInput.projectMilestoneId' }),
|
|
379
|
+
),
|
|
380
|
+
removedLabelIds: Type.Optional(
|
|
381
|
+
Type.Array(Type.String(), {
|
|
382
|
+
description: 'IssueUpdateInput.removedLabelIds',
|
|
383
|
+
}),
|
|
384
|
+
),
|
|
385
|
+
slaBreachesAt: Type.Optional(
|
|
386
|
+
Type.String({ description: 'IssueUpdateInput.slaBreachesAt' }),
|
|
387
|
+
),
|
|
388
|
+
slaStartedAt: Type.Optional(Type.String({ description: 'IssueUpdateInput.slaStartedAt' })),
|
|
389
|
+
slaType: Type.Optional(Type.String({ description: 'IssueUpdateInput.slaType' })),
|
|
390
|
+
snoozedById: Type.Optional(Type.String({ description: 'IssueUpdateInput.snoozedById' })),
|
|
391
|
+
snoozedUntilAt: Type.Optional(
|
|
392
|
+
Type.String({ description: 'IssueUpdateInput.snoozedUntilAt' }),
|
|
393
|
+
),
|
|
394
|
+
sortOrder: Type.Optional(Type.Number({ description: 'IssueUpdateInput.sortOrder' })),
|
|
395
|
+
subIssueSortOrder: Type.Optional(
|
|
396
|
+
Type.Number({ description: 'IssueUpdateInput.subIssueSortOrder' }),
|
|
397
|
+
),
|
|
398
|
+
subscriberIds: Type.Optional(
|
|
399
|
+
Type.Array(Type.String(), {
|
|
400
|
+
description: 'IssueUpdateInput.subscriberIds',
|
|
401
|
+
}),
|
|
402
|
+
),
|
|
403
|
+
teamId: Type.Optional(Type.String({ description: 'IssueUpdateInput.teamId' })),
|
|
404
|
+
trashed: Type.Optional(Type.Boolean({ description: 'IssueUpdateInput.trashed' })),
|
|
405
|
+
...RawInputParam,
|
|
406
|
+
}),
|
|
407
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
408
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
409
|
+
const issueId = await resolveIssueId(apiKey, params.issue, signal);
|
|
410
|
+
const rawInput = asObject(params.input) || {};
|
|
411
|
+
|
|
412
|
+
const dueDate =
|
|
413
|
+
params.clearDueDate || params.dueDate === ''
|
|
414
|
+
? null
|
|
415
|
+
: params.dueDate !== undefined
|
|
416
|
+
? params.dueDate
|
|
417
|
+
: undefined;
|
|
418
|
+
|
|
419
|
+
const convenienceInput = compactObject({
|
|
420
|
+
addedLabelIds: params.addedLabelIds,
|
|
421
|
+
assigneeId: params.assigneeId,
|
|
422
|
+
autoClosedByParentClosing: params.autoClosedByParentClosing,
|
|
423
|
+
cycleId: params.cycleId,
|
|
424
|
+
delegateId: params.delegateId,
|
|
425
|
+
description: params.description,
|
|
426
|
+
descriptionData: asObject(params.descriptionData),
|
|
427
|
+
dueDate,
|
|
428
|
+
estimate: params.estimate,
|
|
429
|
+
labelIds: params.labelIds,
|
|
430
|
+
lastAppliedTemplateId: params.lastAppliedTemplateId,
|
|
431
|
+
parentId: params.parentId,
|
|
432
|
+
priority: params.priority,
|
|
433
|
+
prioritySortOrder: params.prioritySortOrder,
|
|
434
|
+
projectId: params.projectId,
|
|
435
|
+
projectMilestoneId: params.projectMilestoneId,
|
|
436
|
+
removedLabelIds: params.removedLabelIds,
|
|
437
|
+
slaBreachesAt: params.slaBreachesAt,
|
|
438
|
+
slaStartedAt: params.slaStartedAt,
|
|
439
|
+
slaType: params.slaType,
|
|
440
|
+
snoozedById: params.snoozedById,
|
|
441
|
+
snoozedUntilAt: params.snoozedUntilAt,
|
|
442
|
+
sortOrder: params.sortOrder,
|
|
443
|
+
stateId: params.stateId,
|
|
444
|
+
subIssueSortOrder: params.subIssueSortOrder,
|
|
445
|
+
subscriberIds: params.subscriberIds,
|
|
446
|
+
teamId: params.teamId,
|
|
447
|
+
title: params.title,
|
|
448
|
+
trashed: params.trashed,
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
const input = {
|
|
452
|
+
...rawInput,
|
|
453
|
+
...convenienceInput,
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
if (Object.keys(input).length === 0) {
|
|
457
|
+
throw new Error('No update fields were provided.');
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const data = await linearGraphQL<{
|
|
461
|
+
issueUpdate: { success: boolean; issue?: LinearIssue | null };
|
|
462
|
+
}>(
|
|
463
|
+
apiKey,
|
|
464
|
+
`mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
|
|
465
|
+
issueUpdate(id: $id, input: $input) {
|
|
466
|
+
success
|
|
467
|
+
issue {
|
|
468
|
+
${ISSUE_SELECTION}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}`,
|
|
472
|
+
{ id: issueId, input },
|
|
473
|
+
signal,
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
if (!data.issueUpdate.success || !data.issueUpdate.issue) {
|
|
477
|
+
throw new Error('Linear issueUpdate did not succeed.');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
const issue = data.issueUpdate.issue;
|
|
481
|
+
return {
|
|
482
|
+
content: [{ type: 'text', text: JSON.stringify({ issue }, null, 2) }],
|
|
483
|
+
details: { issue },
|
|
484
|
+
};
|
|
485
|
+
});
|
|
486
|
+
},
|
|
487
|
+
}),
|
|
488
|
+
defineTool({
|
|
489
|
+
name: 'linear_delete_issue',
|
|
490
|
+
label: 'Linear Delete Issue',
|
|
491
|
+
description: 'Delete an issue by identifier (ENG-123) or id. Admins can permanently delete.',
|
|
492
|
+
parameters: Type.Object({
|
|
493
|
+
issue: Type.String({
|
|
494
|
+
description: 'Issue identifier (ENG-123) or issue id.',
|
|
495
|
+
}),
|
|
496
|
+
permanentlyDelete: Type.Optional(Type.Boolean()),
|
|
497
|
+
}),
|
|
498
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
499
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
500
|
+
const issueId = await resolveIssueId(apiKey, params.issue, signal);
|
|
501
|
+
|
|
502
|
+
const data = await linearGraphQL<{
|
|
503
|
+
issueDelete: { success: boolean };
|
|
504
|
+
}>(
|
|
505
|
+
apiKey,
|
|
506
|
+
`mutation DeleteIssue($id: String!, $permanentlyDelete: Boolean) {
|
|
507
|
+
issueDelete(id: $id, permanentlyDelete: $permanentlyDelete) {
|
|
508
|
+
success
|
|
509
|
+
}
|
|
510
|
+
}`,
|
|
511
|
+
{ id: issueId, permanentlyDelete: params.permanentlyDelete },
|
|
512
|
+
signal,
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
if (!data.issueDelete.success) {
|
|
516
|
+
throw new Error('Linear issueDelete did not succeed.');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return {
|
|
520
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
521
|
+
details: { success: true },
|
|
522
|
+
};
|
|
523
|
+
});
|
|
524
|
+
},
|
|
525
|
+
}),
|
|
526
|
+
defineTool({
|
|
527
|
+
name: 'linear_archive_issue',
|
|
528
|
+
label: 'Linear Archive Issue',
|
|
529
|
+
description:
|
|
530
|
+
'Archive an issue by identifier (ENG-123) or id. Use trash=true to trash instead.',
|
|
531
|
+
parameters: Type.Object({
|
|
532
|
+
issue: Type.String({
|
|
533
|
+
description: 'Issue identifier (ENG-123) or issue id.',
|
|
534
|
+
}),
|
|
535
|
+
trash: Type.Optional(Type.Boolean()),
|
|
536
|
+
}),
|
|
537
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
538
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
539
|
+
const issueId = await resolveIssueId(apiKey, params.issue, signal);
|
|
540
|
+
|
|
541
|
+
const data = await linearGraphQL<{
|
|
542
|
+
issueArchive: { success: boolean };
|
|
543
|
+
}>(
|
|
544
|
+
apiKey,
|
|
545
|
+
`mutation ArchiveIssue($id: String!, $trash: Boolean) {
|
|
546
|
+
issueArchive(id: $id, trash: $trash) {
|
|
547
|
+
success
|
|
548
|
+
}
|
|
549
|
+
}`,
|
|
550
|
+
{ id: issueId, trash: params.trash },
|
|
551
|
+
signal,
|
|
552
|
+
);
|
|
553
|
+
|
|
554
|
+
if (!data.issueArchive.success) {
|
|
555
|
+
throw new Error('Linear issueArchive did not succeed.');
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
560
|
+
details: { success: true },
|
|
561
|
+
};
|
|
562
|
+
});
|
|
563
|
+
},
|
|
564
|
+
}),
|
|
565
|
+
defineTool({
|
|
566
|
+
name: 'linear_unarchive_issue',
|
|
567
|
+
label: 'Linear Unarchive Issue',
|
|
568
|
+
description: 'Unarchive an issue by identifier (ENG-123) or id.',
|
|
569
|
+
parameters: Type.Object({
|
|
570
|
+
issue: Type.String({
|
|
571
|
+
description: 'Issue identifier (ENG-123) or issue id.',
|
|
572
|
+
}),
|
|
573
|
+
}),
|
|
574
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
575
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
576
|
+
const issueId = await resolveIssueId(apiKey, params.issue, signal);
|
|
577
|
+
|
|
578
|
+
const data = await linearGraphQL<{
|
|
579
|
+
issueUnarchive: { success: boolean };
|
|
580
|
+
}>(
|
|
581
|
+
apiKey,
|
|
582
|
+
`mutation UnarchiveIssue($id: String!) {
|
|
583
|
+
issueUnarchive(id: $id) {
|
|
584
|
+
success
|
|
585
|
+
}
|
|
586
|
+
}`,
|
|
587
|
+
{ id: issueId },
|
|
588
|
+
signal,
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
if (!data.issueUnarchive.success) {
|
|
592
|
+
throw new Error('Linear issueUnarchive did not succeed.');
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return {
|
|
596
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
597
|
+
details: { success: true },
|
|
598
|
+
};
|
|
599
|
+
});
|
|
600
|
+
},
|
|
601
|
+
}),
|
|
602
|
+
defineTool({
|
|
603
|
+
name: 'linear_search_issues',
|
|
604
|
+
label: 'Linear Search Issues',
|
|
605
|
+
description: 'Search issues by text. Supports searching in comments and boosting by team.',
|
|
606
|
+
parameters: Type.Object({
|
|
607
|
+
term: Type.String({ description: 'Search text.' }),
|
|
608
|
+
includeComments: Type.Optional(Type.Boolean({ description: 'Search in comments too.' })),
|
|
609
|
+
teamId: Type.Optional(Type.String({ description: 'Team UUID to boost results for.' })),
|
|
610
|
+
...PaginationParams,
|
|
611
|
+
...FilterParam,
|
|
612
|
+
}),
|
|
613
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
614
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
615
|
+
const variables = compactObject({
|
|
616
|
+
term: params.term,
|
|
617
|
+
includeComments: params.includeComments,
|
|
618
|
+
teamId: params.teamId,
|
|
619
|
+
after: params.after,
|
|
620
|
+
before: params.before,
|
|
621
|
+
filter: asObject(params.filter),
|
|
622
|
+
first: params.first ?? 20,
|
|
623
|
+
includeArchived: params.includeArchived,
|
|
624
|
+
last: params.last,
|
|
625
|
+
orderBy: params.orderBy,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
const data = await linearGraphQL<{
|
|
629
|
+
searchIssues: { nodes: LinearIssue[] };
|
|
630
|
+
}>(
|
|
631
|
+
apiKey,
|
|
632
|
+
`query SearchIssues(
|
|
633
|
+
$term: String!
|
|
634
|
+
$includeComments: Boolean
|
|
635
|
+
$teamId: String
|
|
636
|
+
$after: String
|
|
637
|
+
$before: String
|
|
638
|
+
$filter: IssueFilter
|
|
639
|
+
$first: Int
|
|
640
|
+
$includeArchived: Boolean
|
|
641
|
+
$last: Int
|
|
642
|
+
$orderBy: PaginationOrderBy
|
|
643
|
+
) {
|
|
644
|
+
searchIssues(
|
|
645
|
+
term: $term
|
|
646
|
+
includeComments: $includeComments
|
|
647
|
+
teamId: $teamId
|
|
648
|
+
after: $after
|
|
649
|
+
before: $before
|
|
650
|
+
filter: $filter
|
|
651
|
+
first: $first
|
|
652
|
+
includeArchived: $includeArchived
|
|
653
|
+
last: $last
|
|
654
|
+
orderBy: $orderBy
|
|
655
|
+
) {
|
|
656
|
+
nodes {
|
|
657
|
+
${ISSUE_SELECTION}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}`,
|
|
661
|
+
variables,
|
|
662
|
+
signal,
|
|
663
|
+
);
|
|
664
|
+
|
|
665
|
+
const issues = data.searchIssues.nodes;
|
|
666
|
+
return {
|
|
667
|
+
content: [{ type: 'text', text: JSON.stringify({ issues }, null, 2) }],
|
|
668
|
+
details: { issues },
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
},
|
|
672
|
+
}),
|
|
673
|
+
];
|
|
674
|
+
}
|