@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,357 @@
|
|
|
1
|
+
import { defineTool } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Type } from '@sinclair/typebox';
|
|
3
|
+
import { withLinearAuth, linearGraphQL, resolveTeamId } from '../client';
|
|
4
|
+
import { PaginationParams, FilterParam, RawInputParam, TeamConvenienceParams } from '../params';
|
|
5
|
+
import { DOCUMENT_SELECTION } from '../selections';
|
|
6
|
+
import type { JsonObject } from '../types';
|
|
7
|
+
import { compactObject, asObject, asString } from '../util';
|
|
8
|
+
|
|
9
|
+
export function documentTools() {
|
|
10
|
+
return [
|
|
11
|
+
defineTool({
|
|
12
|
+
name: 'linear_list_documents',
|
|
13
|
+
label: 'Linear List Documents',
|
|
14
|
+
description: 'List documents. Supports full documents query args.',
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
...PaginationParams,
|
|
17
|
+
...FilterParam,
|
|
18
|
+
}),
|
|
19
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
20
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
21
|
+
const variables = compactObject({
|
|
22
|
+
after: params.after,
|
|
23
|
+
before: params.before,
|
|
24
|
+
filter: asObject(params.filter),
|
|
25
|
+
first: params.first ?? 20,
|
|
26
|
+
includeArchived: params.includeArchived,
|
|
27
|
+
last: params.last,
|
|
28
|
+
orderBy: params.orderBy,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const data = await linearGraphQL<{
|
|
32
|
+
documents: { nodes: Array<JsonObject> };
|
|
33
|
+
}>(
|
|
34
|
+
apiKey,
|
|
35
|
+
`query ListDocuments(
|
|
36
|
+
$after: String
|
|
37
|
+
$before: String
|
|
38
|
+
$filter: DocumentFilter
|
|
39
|
+
$first: Int
|
|
40
|
+
$includeArchived: Boolean
|
|
41
|
+
$last: Int
|
|
42
|
+
$orderBy: PaginationOrderBy
|
|
43
|
+
) {
|
|
44
|
+
documents(
|
|
45
|
+
after: $after
|
|
46
|
+
before: $before
|
|
47
|
+
filter: $filter
|
|
48
|
+
first: $first
|
|
49
|
+
includeArchived: $includeArchived
|
|
50
|
+
last: $last
|
|
51
|
+
orderBy: $orderBy
|
|
52
|
+
) {
|
|
53
|
+
nodes {
|
|
54
|
+
${DOCUMENT_SELECTION}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}`,
|
|
58
|
+
variables,
|
|
59
|
+
signal,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const documents = data.documents.nodes;
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: 'text', text: JSON.stringify({ documents }, null, 2) }],
|
|
65
|
+
details: { documents },
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
defineTool({
|
|
71
|
+
name: 'linear_get_document',
|
|
72
|
+
label: 'Linear Get Document',
|
|
73
|
+
description: 'Get a specific document by id.',
|
|
74
|
+
parameters: Type.Object({
|
|
75
|
+
documentId: Type.String(),
|
|
76
|
+
}),
|
|
77
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
78
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
79
|
+
const data = await linearGraphQL<{ document: JsonObject | null }>(
|
|
80
|
+
apiKey,
|
|
81
|
+
`query GetDocument($id: String!) {
|
|
82
|
+
document(id: $id) {
|
|
83
|
+
${DOCUMENT_SELECTION}
|
|
84
|
+
}
|
|
85
|
+
}`,
|
|
86
|
+
{ id: params.documentId },
|
|
87
|
+
signal,
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const document = data.document;
|
|
91
|
+
return {
|
|
92
|
+
content: [
|
|
93
|
+
{ type: 'text', text: JSON.stringify({ document: document ?? null }, null, 2) },
|
|
94
|
+
],
|
|
95
|
+
details: { document: document ?? null },
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
defineTool({
|
|
101
|
+
name: 'linear_create_document',
|
|
102
|
+
label: 'Linear Create Document',
|
|
103
|
+
description:
|
|
104
|
+
'Create a document. Supports top-level DocumentCreateInput fields and raw input.',
|
|
105
|
+
parameters: Type.Object({
|
|
106
|
+
color: Type.Optional(Type.String()),
|
|
107
|
+
content: Type.Optional(Type.String()),
|
|
108
|
+
cycleId: Type.Optional(Type.String()),
|
|
109
|
+
icon: Type.Optional(Type.String()),
|
|
110
|
+
id: Type.Optional(Type.String()),
|
|
111
|
+
initiativeId: Type.Optional(Type.String()),
|
|
112
|
+
issueId: Type.Optional(Type.String()),
|
|
113
|
+
lastAppliedTemplateId: Type.Optional(Type.String()),
|
|
114
|
+
projectId: Type.Optional(Type.String()),
|
|
115
|
+
releaseId: Type.Optional(Type.String()),
|
|
116
|
+
resourceFolderId: Type.Optional(Type.String()),
|
|
117
|
+
sortOrder: Type.Optional(Type.Number()),
|
|
118
|
+
subscriberIds: Type.Optional(Type.Array(Type.String())),
|
|
119
|
+
...TeamConvenienceParams,
|
|
120
|
+
title: Type.Optional(Type.String()),
|
|
121
|
+
...RawInputParam,
|
|
122
|
+
}),
|
|
123
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
124
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
125
|
+
const rawInput = asObject(params.input) || {};
|
|
126
|
+
const rawInputTeamId = asString(rawInput.teamId);
|
|
127
|
+
const teamId =
|
|
128
|
+
params.teamId || params.teamKey || rawInputTeamId
|
|
129
|
+
? await resolveTeamId(
|
|
130
|
+
apiKey,
|
|
131
|
+
{
|
|
132
|
+
teamId: params.teamId || rawInputTeamId,
|
|
133
|
+
teamKey: params.teamKey,
|
|
134
|
+
},
|
|
135
|
+
signal,
|
|
136
|
+
)
|
|
137
|
+
: undefined;
|
|
138
|
+
|
|
139
|
+
const input = {
|
|
140
|
+
...rawInput,
|
|
141
|
+
...compactObject({
|
|
142
|
+
color: params.color,
|
|
143
|
+
content: params.content,
|
|
144
|
+
cycleId: params.cycleId,
|
|
145
|
+
icon: params.icon,
|
|
146
|
+
id: params.id,
|
|
147
|
+
initiativeId: params.initiativeId,
|
|
148
|
+
issueId: params.issueId,
|
|
149
|
+
lastAppliedTemplateId: params.lastAppliedTemplateId,
|
|
150
|
+
projectId: params.projectId,
|
|
151
|
+
releaseId: params.releaseId,
|
|
152
|
+
resourceFolderId: params.resourceFolderId,
|
|
153
|
+
sortOrder: params.sortOrder,
|
|
154
|
+
subscriberIds: params.subscriberIds,
|
|
155
|
+
teamId,
|
|
156
|
+
title: params.title,
|
|
157
|
+
}),
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (!asString(input.title)) {
|
|
161
|
+
throw new Error('Document title is required for documentCreate (title).');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const data = await linearGraphQL<{
|
|
165
|
+
documentCreate: { success: boolean; document?: JsonObject | null };
|
|
166
|
+
}>(
|
|
167
|
+
apiKey,
|
|
168
|
+
`mutation CreateDocument($input: DocumentCreateInput!) {
|
|
169
|
+
documentCreate(input: $input) {
|
|
170
|
+
success
|
|
171
|
+
document {
|
|
172
|
+
${DOCUMENT_SELECTION}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}`,
|
|
176
|
+
{ input },
|
|
177
|
+
signal,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (!data.documentCreate.success || !data.documentCreate.document) {
|
|
181
|
+
throw new Error('Linear documentCreate did not succeed.');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const document = data.documentCreate.document;
|
|
185
|
+
return {
|
|
186
|
+
content: [{ type: 'text', text: JSON.stringify({ document }, null, 2) }],
|
|
187
|
+
details: { document },
|
|
188
|
+
};
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
defineTool({
|
|
193
|
+
name: 'linear_update_document',
|
|
194
|
+
label: 'Linear Update Document',
|
|
195
|
+
description:
|
|
196
|
+
'Update a document by id. Supports top-level DocumentUpdateInput fields and raw input.',
|
|
197
|
+
parameters: Type.Object({
|
|
198
|
+
documentId: Type.String(),
|
|
199
|
+
color: Type.Optional(Type.String()),
|
|
200
|
+
content: Type.Optional(Type.String()),
|
|
201
|
+
cycleId: Type.Optional(Type.String()),
|
|
202
|
+
hiddenAt: Type.Optional(Type.String()),
|
|
203
|
+
icon: Type.Optional(Type.String()),
|
|
204
|
+
initiativeId: Type.Optional(Type.String()),
|
|
205
|
+
issueId: Type.Optional(Type.String()),
|
|
206
|
+
lastAppliedTemplateId: Type.Optional(Type.String()),
|
|
207
|
+
projectId: Type.Optional(Type.String()),
|
|
208
|
+
releaseId: Type.Optional(Type.String()),
|
|
209
|
+
resourceFolderId: Type.Optional(Type.String()),
|
|
210
|
+
sortOrder: Type.Optional(Type.Number()),
|
|
211
|
+
subscriberIds: Type.Optional(Type.Array(Type.String())),
|
|
212
|
+
...TeamConvenienceParams,
|
|
213
|
+
title: Type.Optional(Type.String()),
|
|
214
|
+
trashed: Type.Optional(Type.Boolean()),
|
|
215
|
+
...RawInputParam,
|
|
216
|
+
}),
|
|
217
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
218
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
219
|
+
const rawInput = asObject(params.input) || {};
|
|
220
|
+
const rawInputTeamId = asString(rawInput.teamId);
|
|
221
|
+
const teamId =
|
|
222
|
+
params.teamId || params.teamKey || rawInputTeamId
|
|
223
|
+
? await resolveTeamId(
|
|
224
|
+
apiKey,
|
|
225
|
+
{
|
|
226
|
+
teamId: params.teamId || rawInputTeamId,
|
|
227
|
+
teamKey: params.teamKey,
|
|
228
|
+
},
|
|
229
|
+
signal,
|
|
230
|
+
)
|
|
231
|
+
: undefined;
|
|
232
|
+
|
|
233
|
+
const input = {
|
|
234
|
+
...rawInput,
|
|
235
|
+
...compactObject({
|
|
236
|
+
color: params.color,
|
|
237
|
+
content: params.content,
|
|
238
|
+
cycleId: params.cycleId,
|
|
239
|
+
hiddenAt: params.hiddenAt,
|
|
240
|
+
icon: params.icon,
|
|
241
|
+
initiativeId: params.initiativeId,
|
|
242
|
+
issueId: params.issueId,
|
|
243
|
+
lastAppliedTemplateId: params.lastAppliedTemplateId,
|
|
244
|
+
projectId: params.projectId,
|
|
245
|
+
releaseId: params.releaseId,
|
|
246
|
+
resourceFolderId: params.resourceFolderId,
|
|
247
|
+
sortOrder: params.sortOrder,
|
|
248
|
+
subscriberIds: params.subscriberIds,
|
|
249
|
+
teamId,
|
|
250
|
+
title: params.title,
|
|
251
|
+
trashed: params.trashed,
|
|
252
|
+
}),
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
if (Object.keys(input).length === 0) {
|
|
256
|
+
throw new Error('No document update fields were provided.');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const data = await linearGraphQL<{
|
|
260
|
+
documentUpdate: { success: boolean; document?: JsonObject | null };
|
|
261
|
+
}>(
|
|
262
|
+
apiKey,
|
|
263
|
+
`mutation UpdateDocument($id: String!, $input: DocumentUpdateInput!) {
|
|
264
|
+
documentUpdate(id: $id, input: $input) {
|
|
265
|
+
success
|
|
266
|
+
document {
|
|
267
|
+
${DOCUMENT_SELECTION}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}`,
|
|
271
|
+
{
|
|
272
|
+
id: params.documentId,
|
|
273
|
+
input,
|
|
274
|
+
},
|
|
275
|
+
signal,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
if (!data.documentUpdate.success || !data.documentUpdate.document) {
|
|
279
|
+
throw new Error('Linear documentUpdate did not succeed.');
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const document = data.documentUpdate.document;
|
|
283
|
+
return {
|
|
284
|
+
content: [{ type: 'text', text: JSON.stringify({ document }, null, 2) }],
|
|
285
|
+
details: { document },
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
},
|
|
289
|
+
}),
|
|
290
|
+
defineTool({
|
|
291
|
+
name: 'linear_delete_document',
|
|
292
|
+
label: 'Linear Delete Document',
|
|
293
|
+
description: 'Delete a document by id.',
|
|
294
|
+
parameters: Type.Object({
|
|
295
|
+
documentId: Type.String(),
|
|
296
|
+
}),
|
|
297
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
298
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
299
|
+
const data = await linearGraphQL<{
|
|
300
|
+
documentDelete: { success: boolean };
|
|
301
|
+
}>(
|
|
302
|
+
apiKey,
|
|
303
|
+
`mutation DeleteDocument($id: String!) {
|
|
304
|
+
documentDelete(id: $id) {
|
|
305
|
+
success
|
|
306
|
+
}
|
|
307
|
+
}`,
|
|
308
|
+
{ id: params.documentId },
|
|
309
|
+
signal,
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
if (!data.documentDelete.success) {
|
|
313
|
+
throw new Error('Linear documentDelete did not succeed.');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
318
|
+
details: { success: true },
|
|
319
|
+
};
|
|
320
|
+
});
|
|
321
|
+
},
|
|
322
|
+
}),
|
|
323
|
+
defineTool({
|
|
324
|
+
name: 'linear_unarchive_document',
|
|
325
|
+
label: 'Linear Unarchive Document',
|
|
326
|
+
description: 'Restore an archived document by id.',
|
|
327
|
+
parameters: Type.Object({
|
|
328
|
+
documentId: Type.String(),
|
|
329
|
+
}),
|
|
330
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
331
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
332
|
+
const data = await linearGraphQL<{
|
|
333
|
+
documentUnarchive: { success: boolean };
|
|
334
|
+
}>(
|
|
335
|
+
apiKey,
|
|
336
|
+
`mutation UnarchiveDocument($id: String!) {
|
|
337
|
+
documentUnarchive(id: $id) {
|
|
338
|
+
success
|
|
339
|
+
}
|
|
340
|
+
}`,
|
|
341
|
+
{ id: params.documentId },
|
|
342
|
+
signal,
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
if (!data.documentUnarchive.success) {
|
|
346
|
+
throw new Error('Linear documentUnarchive did not succeed.');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
return {
|
|
350
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
351
|
+
details: { success: true },
|
|
352
|
+
};
|
|
353
|
+
});
|
|
354
|
+
},
|
|
355
|
+
}),
|
|
356
|
+
];
|
|
357
|
+
}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { defineTool } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Type } from '@sinclair/typebox';
|
|
3
|
+
import { withLinearAuth, linearGraphQL } from '../client';
|
|
4
|
+
import { PaginationParams, FilterParam, SortParam, RawInputParam } from '../params';
|
|
5
|
+
import { INITIATIVE_SELECTION } from '../selections';
|
|
6
|
+
import type { JsonObject } from '../types';
|
|
7
|
+
import { compactObject, asObject, asObjectArray, asString } from '../util';
|
|
8
|
+
|
|
9
|
+
export function initiativeTools() {
|
|
10
|
+
return [
|
|
11
|
+
defineTool({
|
|
12
|
+
name: 'linear_list_initiatives',
|
|
13
|
+
label: 'Linear List Initiatives',
|
|
14
|
+
description: 'List initiatives. Supports full initiatives query args.',
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
...PaginationParams,
|
|
17
|
+
...FilterParam,
|
|
18
|
+
...SortParam,
|
|
19
|
+
}),
|
|
20
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
21
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
22
|
+
const variables = compactObject({
|
|
23
|
+
after: params.after,
|
|
24
|
+
before: params.before,
|
|
25
|
+
filter: asObject(params.filter),
|
|
26
|
+
first: params.first ?? 20,
|
|
27
|
+
includeArchived: params.includeArchived,
|
|
28
|
+
last: params.last,
|
|
29
|
+
orderBy: params.orderBy,
|
|
30
|
+
sort: asObjectArray(params.sort),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const data = await linearGraphQL<{
|
|
34
|
+
initiatives: { nodes: Array<JsonObject> };
|
|
35
|
+
}>(
|
|
36
|
+
apiKey,
|
|
37
|
+
`query ListInitiatives(
|
|
38
|
+
$after: String
|
|
39
|
+
$before: String
|
|
40
|
+
$filter: InitiativeFilter
|
|
41
|
+
$first: Int
|
|
42
|
+
$includeArchived: Boolean
|
|
43
|
+
$last: Int
|
|
44
|
+
$orderBy: PaginationOrderBy
|
|
45
|
+
$sort: [InitiativeSortInput!]
|
|
46
|
+
) {
|
|
47
|
+
initiatives(
|
|
48
|
+
after: $after
|
|
49
|
+
before: $before
|
|
50
|
+
filter: $filter
|
|
51
|
+
first: $first
|
|
52
|
+
includeArchived: $includeArchived
|
|
53
|
+
last: $last
|
|
54
|
+
orderBy: $orderBy
|
|
55
|
+
sort: $sort
|
|
56
|
+
) {
|
|
57
|
+
nodes {
|
|
58
|
+
${INITIATIVE_SELECTION}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}`,
|
|
62
|
+
variables,
|
|
63
|
+
signal,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const initiatives = data.initiatives.nodes;
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: 'text', text: JSON.stringify({ initiatives }, null, 2) }],
|
|
69
|
+
details: { initiatives },
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
},
|
|
73
|
+
}),
|
|
74
|
+
defineTool({
|
|
75
|
+
name: 'linear_get_initiative',
|
|
76
|
+
label: 'Linear Get Initiative',
|
|
77
|
+
description: 'Get a specific initiative by id.',
|
|
78
|
+
parameters: Type.Object({
|
|
79
|
+
initiativeId: Type.String(),
|
|
80
|
+
}),
|
|
81
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
82
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
83
|
+
const data = await linearGraphQL<{ initiative: JsonObject | null }>(
|
|
84
|
+
apiKey,
|
|
85
|
+
`query GetInitiative($id: String!) {
|
|
86
|
+
initiative(id: $id) {
|
|
87
|
+
${INITIATIVE_SELECTION}
|
|
88
|
+
}
|
|
89
|
+
}`,
|
|
90
|
+
{ id: params.initiativeId },
|
|
91
|
+
signal,
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const initiative = data.initiative;
|
|
95
|
+
return {
|
|
96
|
+
content: [
|
|
97
|
+
{ type: 'text', text: JSON.stringify({ initiative: initiative ?? null }, null, 2) },
|
|
98
|
+
],
|
|
99
|
+
details: { initiative: initiative ?? null },
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
}),
|
|
104
|
+
defineTool({
|
|
105
|
+
name: 'linear_save_initiative',
|
|
106
|
+
label: 'Linear Save Initiative',
|
|
107
|
+
description:
|
|
108
|
+
'Create or update an initiative. If initiativeId is provided, uses initiativeUpdate; otherwise uses initiativeCreate.',
|
|
109
|
+
parameters: Type.Object({
|
|
110
|
+
initiativeId: Type.Optional(Type.String()),
|
|
111
|
+
color: Type.Optional(Type.String()),
|
|
112
|
+
content: Type.Optional(Type.String()),
|
|
113
|
+
description: Type.Optional(Type.String()),
|
|
114
|
+
icon: Type.Optional(Type.String()),
|
|
115
|
+
id: Type.Optional(Type.String()),
|
|
116
|
+
name: Type.Optional(Type.String()),
|
|
117
|
+
ownerId: Type.Optional(Type.String()),
|
|
118
|
+
sortOrder: Type.Optional(Type.Number()),
|
|
119
|
+
status: Type.Optional(Type.String()),
|
|
120
|
+
targetDate: Type.Optional(Type.String()),
|
|
121
|
+
targetDateResolution: Type.Optional(Type.String()),
|
|
122
|
+
frequencyResolution: Type.Optional(Type.String()),
|
|
123
|
+
trashed: Type.Optional(Type.Boolean()),
|
|
124
|
+
updateReminderFrequency: Type.Optional(Type.Number()),
|
|
125
|
+
updateReminderFrequencyInWeeks: Type.Optional(Type.Number()),
|
|
126
|
+
updateRemindersDay: Type.Optional(Type.String()),
|
|
127
|
+
updateRemindersHour: Type.Optional(Type.Integer()),
|
|
128
|
+
...RawInputParam,
|
|
129
|
+
}),
|
|
130
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
131
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
132
|
+
const rawInput = asObject(params.input) || {};
|
|
133
|
+
const updateId = asString(params.initiativeId);
|
|
134
|
+
|
|
135
|
+
const input = {
|
|
136
|
+
...rawInput,
|
|
137
|
+
...compactObject({
|
|
138
|
+
color: params.color,
|
|
139
|
+
content: params.content,
|
|
140
|
+
description: params.description,
|
|
141
|
+
frequencyResolution: params.frequencyResolution,
|
|
142
|
+
icon: params.icon,
|
|
143
|
+
id: params.id,
|
|
144
|
+
name: params.name,
|
|
145
|
+
ownerId: params.ownerId,
|
|
146
|
+
sortOrder: params.sortOrder,
|
|
147
|
+
status: params.status,
|
|
148
|
+
targetDate: params.targetDate,
|
|
149
|
+
targetDateResolution: params.targetDateResolution,
|
|
150
|
+
trashed: params.trashed,
|
|
151
|
+
updateReminderFrequency: params.updateReminderFrequency,
|
|
152
|
+
updateReminderFrequencyInWeeks: params.updateReminderFrequencyInWeeks,
|
|
153
|
+
updateRemindersDay: params.updateRemindersDay,
|
|
154
|
+
updateRemindersHour: params.updateRemindersHour,
|
|
155
|
+
}),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (updateId) {
|
|
159
|
+
if (Object.keys(input).length === 0) {
|
|
160
|
+
throw new Error('No initiative update fields were provided.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const data = await linearGraphQL<{
|
|
164
|
+
initiativeUpdate: {
|
|
165
|
+
success: boolean;
|
|
166
|
+
initiative?: JsonObject | null;
|
|
167
|
+
};
|
|
168
|
+
}>(
|
|
169
|
+
apiKey,
|
|
170
|
+
`mutation UpdateInitiative($id: String!, $input: InitiativeUpdateInput!) {
|
|
171
|
+
initiativeUpdate(id: $id, input: $input) {
|
|
172
|
+
success
|
|
173
|
+
initiative {
|
|
174
|
+
${INITIATIVE_SELECTION}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}`,
|
|
178
|
+
{ id: updateId, input },
|
|
179
|
+
signal,
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
if (!data.initiativeUpdate.success || !data.initiativeUpdate.initiative) {
|
|
183
|
+
throw new Error('Linear initiativeUpdate did not succeed.');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const initiative = data.initiativeUpdate.initiative;
|
|
187
|
+
return {
|
|
188
|
+
content: [{ type: 'text', text: JSON.stringify({ initiative }, null, 2) }],
|
|
189
|
+
details: { initiative },
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (!asString(input.name)) {
|
|
194
|
+
throw new Error('Initiative name is required for initiativeCreate (name).');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const data = await linearGraphQL<{
|
|
198
|
+
initiativeCreate: {
|
|
199
|
+
success: boolean;
|
|
200
|
+
initiative?: JsonObject | null;
|
|
201
|
+
};
|
|
202
|
+
}>(
|
|
203
|
+
apiKey,
|
|
204
|
+
`mutation CreateInitiative($input: InitiativeCreateInput!) {
|
|
205
|
+
initiativeCreate(input: $input) {
|
|
206
|
+
success
|
|
207
|
+
initiative {
|
|
208
|
+
${INITIATIVE_SELECTION}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}`,
|
|
212
|
+
{ input },
|
|
213
|
+
signal,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
if (!data.initiativeCreate.success || !data.initiativeCreate.initiative) {
|
|
217
|
+
throw new Error('Linear initiativeCreate did not succeed.');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const initiative = data.initiativeCreate.initiative;
|
|
221
|
+
return {
|
|
222
|
+
content: [{ type: 'text', text: JSON.stringify({ initiative }, null, 2) }],
|
|
223
|
+
details: { initiative },
|
|
224
|
+
};
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
}),
|
|
228
|
+
defineTool({
|
|
229
|
+
name: 'linear_delete_initiative',
|
|
230
|
+
label: 'Linear Delete Initiative',
|
|
231
|
+
description: 'Delete an initiative by id.',
|
|
232
|
+
parameters: Type.Object({
|
|
233
|
+
initiativeId: Type.String(),
|
|
234
|
+
}),
|
|
235
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
236
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
237
|
+
const data = await linearGraphQL<{
|
|
238
|
+
initiativeDelete: { success: boolean };
|
|
239
|
+
}>(
|
|
240
|
+
apiKey,
|
|
241
|
+
`mutation DeleteInitiative($id: String!) {
|
|
242
|
+
initiativeDelete(id: $id) {
|
|
243
|
+
success
|
|
244
|
+
}
|
|
245
|
+
}`,
|
|
246
|
+
{ id: params.initiativeId },
|
|
247
|
+
signal,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
if (!data.initiativeDelete.success) {
|
|
251
|
+
throw new Error('Linear initiativeDelete did not succeed.');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
256
|
+
details: { success: true },
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
},
|
|
260
|
+
}),
|
|
261
|
+
defineTool({
|
|
262
|
+
name: 'linear_archive_initiative',
|
|
263
|
+
label: 'Linear Archive Initiative',
|
|
264
|
+
description: 'Archive an initiative by id.',
|
|
265
|
+
parameters: Type.Object({
|
|
266
|
+
initiativeId: Type.String(),
|
|
267
|
+
}),
|
|
268
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
269
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
270
|
+
const data = await linearGraphQL<{
|
|
271
|
+
initiativeArchive: { success: boolean };
|
|
272
|
+
}>(
|
|
273
|
+
apiKey,
|
|
274
|
+
`mutation ArchiveInitiative($id: String!) {
|
|
275
|
+
initiativeArchive(id: $id) {
|
|
276
|
+
success
|
|
277
|
+
}
|
|
278
|
+
}`,
|
|
279
|
+
{ id: params.initiativeId },
|
|
280
|
+
signal,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
if (!data.initiativeArchive.success) {
|
|
284
|
+
throw new Error('Linear initiativeArchive did not succeed.');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return {
|
|
288
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
289
|
+
details: { success: true },
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
},
|
|
293
|
+
}),
|
|
294
|
+
defineTool({
|
|
295
|
+
name: 'linear_unarchive_initiative',
|
|
296
|
+
label: 'Linear Unarchive Initiative',
|
|
297
|
+
description: 'Unarchive an initiative by id.',
|
|
298
|
+
parameters: Type.Object({
|
|
299
|
+
initiativeId: Type.String(),
|
|
300
|
+
}),
|
|
301
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
302
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
303
|
+
const data = await linearGraphQL<{
|
|
304
|
+
initiativeUnarchive: { success: boolean };
|
|
305
|
+
}>(
|
|
306
|
+
apiKey,
|
|
307
|
+
`mutation UnarchiveInitiative($id: String!) {
|
|
308
|
+
initiativeUnarchive(id: $id) {
|
|
309
|
+
success
|
|
310
|
+
}
|
|
311
|
+
}`,
|
|
312
|
+
{ id: params.initiativeId },
|
|
313
|
+
signal,
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
if (!data.initiativeUnarchive.success) {
|
|
317
|
+
throw new Error('Linear initiativeUnarchive did not succeed.');
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return {
|
|
321
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
322
|
+
details: { success: true },
|
|
323
|
+
};
|
|
324
|
+
});
|
|
325
|
+
},
|
|
326
|
+
}),
|
|
327
|
+
];
|
|
328
|
+
}
|