@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,273 @@
|
|
|
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 { ISSUE_LABEL_SELECTION } from '../selections';
|
|
6
|
+
import type { JsonObject } from '../types';
|
|
7
|
+
import { compactObject, asObject, asString, mergeFilters } from '../util';
|
|
8
|
+
|
|
9
|
+
export function issueLabelTools() {
|
|
10
|
+
return [
|
|
11
|
+
defineTool({
|
|
12
|
+
name: 'linear_list_issue_labels',
|
|
13
|
+
label: 'Linear List Issue Labels',
|
|
14
|
+
description: 'List issue labels. Supports full issueLabels query args.',
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
...TeamConvenienceParams,
|
|
17
|
+
...PaginationParams,
|
|
18
|
+
...FilterParam,
|
|
19
|
+
}),
|
|
20
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
21
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
22
|
+
const resolvedTeamId =
|
|
23
|
+
params.teamId || params.teamKey
|
|
24
|
+
? await resolveTeamId(
|
|
25
|
+
apiKey,
|
|
26
|
+
{ teamId: params.teamId, teamKey: params.teamKey },
|
|
27
|
+
signal,
|
|
28
|
+
)
|
|
29
|
+
: undefined;
|
|
30
|
+
|
|
31
|
+
const convenienceFilter = resolvedTeamId
|
|
32
|
+
? ({ team: { id: { eq: resolvedTeamId } } } as JsonObject)
|
|
33
|
+
: undefined;
|
|
34
|
+
|
|
35
|
+
const filter = mergeFilters(asObject(params.filter), convenienceFilter);
|
|
36
|
+
|
|
37
|
+
const variables = compactObject({
|
|
38
|
+
after: params.after,
|
|
39
|
+
before: params.before,
|
|
40
|
+
filter,
|
|
41
|
+
first: params.first ?? 50,
|
|
42
|
+
includeArchived: params.includeArchived,
|
|
43
|
+
last: params.last,
|
|
44
|
+
orderBy: params.orderBy,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const data = await linearGraphQL<{
|
|
48
|
+
issueLabels: { nodes: Array<JsonObject> };
|
|
49
|
+
}>(
|
|
50
|
+
apiKey,
|
|
51
|
+
`query ListIssueLabels(
|
|
52
|
+
$after: String
|
|
53
|
+
$before: String
|
|
54
|
+
$filter: IssueLabelFilter
|
|
55
|
+
$first: Int
|
|
56
|
+
$includeArchived: Boolean
|
|
57
|
+
$last: Int
|
|
58
|
+
$orderBy: PaginationOrderBy
|
|
59
|
+
) {
|
|
60
|
+
issueLabels(
|
|
61
|
+
after: $after
|
|
62
|
+
before: $before
|
|
63
|
+
filter: $filter
|
|
64
|
+
first: $first
|
|
65
|
+
includeArchived: $includeArchived
|
|
66
|
+
last: $last
|
|
67
|
+
orderBy: $orderBy
|
|
68
|
+
) {
|
|
69
|
+
nodes {
|
|
70
|
+
${ISSUE_LABEL_SELECTION}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}`,
|
|
74
|
+
variables,
|
|
75
|
+
signal,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const labels = data.issueLabels.nodes;
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: 'text', text: JSON.stringify({ labels }, null, 2) }],
|
|
81
|
+
details: { labels },
|
|
82
|
+
};
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
}),
|
|
86
|
+
defineTool({
|
|
87
|
+
name: 'linear_create_issue_label',
|
|
88
|
+
label: 'Linear Create Issue Label',
|
|
89
|
+
description:
|
|
90
|
+
'Create an issue label via issueLabelCreate. Supports top-level fields and raw input.',
|
|
91
|
+
parameters: Type.Object({
|
|
92
|
+
name: Type.Optional(Type.String()),
|
|
93
|
+
color: Type.Optional(Type.String()),
|
|
94
|
+
description: Type.Optional(Type.String()),
|
|
95
|
+
id: Type.Optional(Type.String()),
|
|
96
|
+
isGroup: Type.Optional(Type.Boolean()),
|
|
97
|
+
parentId: Type.Optional(Type.String()),
|
|
98
|
+
retiredAt: Type.Optional(Type.String()),
|
|
99
|
+
...TeamConvenienceParams,
|
|
100
|
+
replaceTeamLabels: Type.Optional(Type.Boolean()),
|
|
101
|
+
...RawInputParam,
|
|
102
|
+
}),
|
|
103
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
104
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
105
|
+
const rawInput = asObject(params.input) || {};
|
|
106
|
+
const teamId =
|
|
107
|
+
params.teamId || params.teamKey || asString(rawInput.teamId)
|
|
108
|
+
? await resolveTeamId(
|
|
109
|
+
apiKey,
|
|
110
|
+
{
|
|
111
|
+
teamId: params.teamId || asString(rawInput.teamId),
|
|
112
|
+
teamKey: params.teamKey,
|
|
113
|
+
},
|
|
114
|
+
signal,
|
|
115
|
+
)
|
|
116
|
+
: undefined;
|
|
117
|
+
|
|
118
|
+
const input = {
|
|
119
|
+
...rawInput,
|
|
120
|
+
...compactObject({
|
|
121
|
+
name: params.name,
|
|
122
|
+
color: params.color,
|
|
123
|
+
description: params.description,
|
|
124
|
+
id: params.id,
|
|
125
|
+
isGroup: params.isGroup,
|
|
126
|
+
parentId: params.parentId,
|
|
127
|
+
retiredAt: params.retiredAt,
|
|
128
|
+
teamId,
|
|
129
|
+
}),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (!asString(input.name)) {
|
|
133
|
+
throw new Error('Issue label name is required (name).');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const data = await linearGraphQL<{
|
|
137
|
+
issueLabelCreate: {
|
|
138
|
+
success: boolean;
|
|
139
|
+
issueLabel?: JsonObject | null;
|
|
140
|
+
};
|
|
141
|
+
}>(
|
|
142
|
+
apiKey,
|
|
143
|
+
`mutation CreateIssueLabel($input: IssueLabelCreateInput!, $replaceTeamLabels: Boolean) {
|
|
144
|
+
issueLabelCreate(input: $input, replaceTeamLabels: $replaceTeamLabels) {
|
|
145
|
+
success
|
|
146
|
+
issueLabel {
|
|
147
|
+
${ISSUE_LABEL_SELECTION}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}`,
|
|
151
|
+
{
|
|
152
|
+
input,
|
|
153
|
+
replaceTeamLabels: params.replaceTeamLabels,
|
|
154
|
+
},
|
|
155
|
+
signal,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const label = data.issueLabelCreate.issueLabel;
|
|
159
|
+
if (!data.issueLabelCreate.success || !label) {
|
|
160
|
+
throw new Error('Linear issueLabelCreate did not succeed.');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
content: [{ type: 'text', text: JSON.stringify({ label }, null, 2) }],
|
|
165
|
+
details: { label },
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
},
|
|
169
|
+
}),
|
|
170
|
+
defineTool({
|
|
171
|
+
name: 'linear_update_issue_label',
|
|
172
|
+
label: 'Linear Update Issue Label',
|
|
173
|
+
description: 'Update an issue label by id.',
|
|
174
|
+
parameters: Type.Object({
|
|
175
|
+
id: Type.String(),
|
|
176
|
+
name: Type.Optional(Type.String()),
|
|
177
|
+
description: Type.Optional(Type.String()),
|
|
178
|
+
color: Type.Optional(Type.String()),
|
|
179
|
+
parentId: Type.Optional(Type.String()),
|
|
180
|
+
isGroup: Type.Optional(Type.Boolean()),
|
|
181
|
+
retiredAt: Type.Optional(Type.String()),
|
|
182
|
+
replaceTeamLabels: Type.Optional(Type.Boolean()),
|
|
183
|
+
...RawInputParam,
|
|
184
|
+
}),
|
|
185
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
186
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
187
|
+
const rawInput = asObject(params.input) || {};
|
|
188
|
+
const input = {
|
|
189
|
+
...rawInput,
|
|
190
|
+
...compactObject({
|
|
191
|
+
name: params.name,
|
|
192
|
+
description: params.description,
|
|
193
|
+
color: params.color,
|
|
194
|
+
parentId: params.parentId,
|
|
195
|
+
isGroup: params.isGroup,
|
|
196
|
+
retiredAt: params.retiredAt,
|
|
197
|
+
}),
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
if (Object.keys(input).length === 0) {
|
|
201
|
+
throw new Error('No update fields were provided.');
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const data = await linearGraphQL<{
|
|
205
|
+
issueLabelUpdate: {
|
|
206
|
+
success: boolean;
|
|
207
|
+
issueLabel?: JsonObject | null;
|
|
208
|
+
};
|
|
209
|
+
}>(
|
|
210
|
+
apiKey,
|
|
211
|
+
`mutation UpdateIssueLabel($id: String!, $input: IssueLabelUpdateInput!, $replaceTeamLabels: Boolean) {
|
|
212
|
+
issueLabelUpdate(id: $id, input: $input, replaceTeamLabels: $replaceTeamLabels) {
|
|
213
|
+
success
|
|
214
|
+
issueLabel {
|
|
215
|
+
${ISSUE_LABEL_SELECTION}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}`,
|
|
219
|
+
{
|
|
220
|
+
id: params.id,
|
|
221
|
+
input,
|
|
222
|
+
replaceTeamLabels: params.replaceTeamLabels,
|
|
223
|
+
},
|
|
224
|
+
signal,
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const label = data.issueLabelUpdate.issueLabel;
|
|
228
|
+
if (!data.issueLabelUpdate.success || !label) {
|
|
229
|
+
throw new Error('Linear issueLabelUpdate did not succeed.');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
content: [{ type: 'text', text: JSON.stringify({ label }, null, 2) }],
|
|
234
|
+
details: { label },
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
},
|
|
238
|
+
}),
|
|
239
|
+
defineTool({
|
|
240
|
+
name: 'linear_delete_issue_label',
|
|
241
|
+
label: 'Linear Delete Issue Label',
|
|
242
|
+
description: 'Delete an issue label by id.',
|
|
243
|
+
parameters: Type.Object({
|
|
244
|
+
id: Type.String(),
|
|
245
|
+
}),
|
|
246
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
247
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
248
|
+
const data = await linearGraphQL<{
|
|
249
|
+
issueLabelDelete: { success: boolean };
|
|
250
|
+
}>(
|
|
251
|
+
apiKey,
|
|
252
|
+
`mutation DeleteIssueLabel($id: String!) {
|
|
253
|
+
issueLabelDelete(id: $id) {
|
|
254
|
+
success
|
|
255
|
+
}
|
|
256
|
+
}`,
|
|
257
|
+
{ id: params.id },
|
|
258
|
+
signal,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
if (!data.issueLabelDelete.success) {
|
|
262
|
+
throw new Error('Linear issueLabelDelete did not succeed.');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
267
|
+
details: { success: true },
|
|
268
|
+
};
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
}),
|
|
272
|
+
];
|
|
273
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { defineTool } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Type } from '@sinclair/typebox';
|
|
3
|
+
import { withLinearAuth, linearGraphQL } from '../client';
|
|
4
|
+
import { PaginationParams } from '../params';
|
|
5
|
+
import { ISSUE_RELATION_SELECTION } from '../selections';
|
|
6
|
+
import type { JsonObject } from '../types';
|
|
7
|
+
import { compactObject } from '../util';
|
|
8
|
+
|
|
9
|
+
export function issueRelationTools() {
|
|
10
|
+
return [
|
|
11
|
+
defineTool({
|
|
12
|
+
name: 'linear_list_issue_relations',
|
|
13
|
+
label: 'Linear List Issue Relations',
|
|
14
|
+
description: 'List issue relations. Supports pagination.',
|
|
15
|
+
parameters: Type.Object({
|
|
16
|
+
...PaginationParams,
|
|
17
|
+
}),
|
|
18
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
19
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
20
|
+
const variables = compactObject({
|
|
21
|
+
after: params.after,
|
|
22
|
+
before: params.before,
|
|
23
|
+
first: params.first ?? 20,
|
|
24
|
+
includeArchived: params.includeArchived,
|
|
25
|
+
last: params.last,
|
|
26
|
+
orderBy: params.orderBy,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const data = await linearGraphQL<{
|
|
30
|
+
issueRelations: { nodes: Array<JsonObject> };
|
|
31
|
+
}>(
|
|
32
|
+
apiKey,
|
|
33
|
+
`query ListIssueRelations(
|
|
34
|
+
$after: String
|
|
35
|
+
$before: String
|
|
36
|
+
$first: Int
|
|
37
|
+
$includeArchived: Boolean
|
|
38
|
+
$last: Int
|
|
39
|
+
$orderBy: PaginationOrderBy
|
|
40
|
+
) {
|
|
41
|
+
issueRelations(
|
|
42
|
+
after: $after
|
|
43
|
+
before: $before
|
|
44
|
+
first: $first
|
|
45
|
+
includeArchived: $includeArchived
|
|
46
|
+
last: $last
|
|
47
|
+
orderBy: $orderBy
|
|
48
|
+
) {
|
|
49
|
+
nodes {
|
|
50
|
+
${ISSUE_RELATION_SELECTION}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}`,
|
|
54
|
+
variables,
|
|
55
|
+
signal,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const issueRelations = data.issueRelations.nodes;
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: 'text', text: JSON.stringify({ issueRelations }, null, 2) }],
|
|
61
|
+
details: { issueRelations },
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
defineTool({
|
|
67
|
+
name: 'linear_create_issue_relation',
|
|
68
|
+
label: 'Linear Create Issue Relation',
|
|
69
|
+
description: 'Create a relation between two issues.',
|
|
70
|
+
parameters: Type.Object({
|
|
71
|
+
issueId: Type.String({
|
|
72
|
+
description: 'Issue identifier (e.g. ENG-123) or UUID.',
|
|
73
|
+
}),
|
|
74
|
+
relatedIssueId: Type.String({
|
|
75
|
+
description: 'Related issue identifier (e.g. ENG-456) or UUID.',
|
|
76
|
+
}),
|
|
77
|
+
type: Type.String({
|
|
78
|
+
description: 'Relation type: blocks, duplicate, related, or similar.',
|
|
79
|
+
}),
|
|
80
|
+
}),
|
|
81
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
82
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
83
|
+
const input = {
|
|
84
|
+
issueId: params.issueId,
|
|
85
|
+
relatedIssueId: params.relatedIssueId,
|
|
86
|
+
type: params.type,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const data = await linearGraphQL<{
|
|
90
|
+
issueRelationCreate: {
|
|
91
|
+
success: boolean;
|
|
92
|
+
issueRelation?: JsonObject | null;
|
|
93
|
+
};
|
|
94
|
+
}>(
|
|
95
|
+
apiKey,
|
|
96
|
+
`mutation CreateIssueRelation($input: IssueRelationCreateInput!) {
|
|
97
|
+
issueRelationCreate(input: $input) {
|
|
98
|
+
success
|
|
99
|
+
issueRelation {
|
|
100
|
+
${ISSUE_RELATION_SELECTION}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}`,
|
|
104
|
+
{ input },
|
|
105
|
+
signal,
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const issueRelation = data.issueRelationCreate.issueRelation;
|
|
109
|
+
if (!data.issueRelationCreate.success || !issueRelation) {
|
|
110
|
+
throw new Error('Linear issueRelationCreate did not succeed.');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
content: [{ type: 'text', text: JSON.stringify({ issueRelation }, null, 2) }],
|
|
115
|
+
details: { issueRelation },
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
120
|
+
defineTool({
|
|
121
|
+
name: 'linear_update_issue_relation',
|
|
122
|
+
label: 'Linear Update Issue Relation',
|
|
123
|
+
description: 'Update an issue relation by id.',
|
|
124
|
+
parameters: Type.Object({
|
|
125
|
+
id: Type.String(),
|
|
126
|
+
type: Type.Optional(Type.String()),
|
|
127
|
+
issueId: Type.Optional(Type.String()),
|
|
128
|
+
relatedIssueId: Type.Optional(Type.String()),
|
|
129
|
+
}),
|
|
130
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
131
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
132
|
+
const input = compactObject({
|
|
133
|
+
type: params.type,
|
|
134
|
+
issueId: params.issueId,
|
|
135
|
+
relatedIssueId: params.relatedIssueId,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (Object.keys(input).length === 0) {
|
|
139
|
+
throw new Error('No update fields were provided.');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const data = await linearGraphQL<{
|
|
143
|
+
issueRelationUpdate: {
|
|
144
|
+
success: boolean;
|
|
145
|
+
issueRelation?: JsonObject | null;
|
|
146
|
+
};
|
|
147
|
+
}>(
|
|
148
|
+
apiKey,
|
|
149
|
+
`mutation UpdateIssueRelation($id: String!, $input: IssueRelationUpdateInput!) {
|
|
150
|
+
issueRelationUpdate(id: $id, input: $input) {
|
|
151
|
+
success
|
|
152
|
+
issueRelation {
|
|
153
|
+
${ISSUE_RELATION_SELECTION}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}`,
|
|
157
|
+
{ id: params.id, input },
|
|
158
|
+
signal,
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const issueRelation = data.issueRelationUpdate.issueRelation;
|
|
162
|
+
if (!data.issueRelationUpdate.success || !issueRelation) {
|
|
163
|
+
throw new Error('Linear issueRelationUpdate did not succeed.');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
content: [{ type: 'text', text: JSON.stringify({ issueRelation }, null, 2) }],
|
|
168
|
+
details: { issueRelation },
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
},
|
|
172
|
+
}),
|
|
173
|
+
defineTool({
|
|
174
|
+
name: 'linear_delete_issue_relation',
|
|
175
|
+
label: 'Linear Delete Issue Relation',
|
|
176
|
+
description: 'Delete an issue relation by id.',
|
|
177
|
+
parameters: Type.Object({
|
|
178
|
+
id: Type.String(),
|
|
179
|
+
}),
|
|
180
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
181
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
182
|
+
const data = await linearGraphQL<{
|
|
183
|
+
issueRelationDelete: { success: boolean };
|
|
184
|
+
}>(
|
|
185
|
+
apiKey,
|
|
186
|
+
`mutation DeleteIssueRelation($id: String!) {
|
|
187
|
+
issueRelationDelete(id: $id) {
|
|
188
|
+
success
|
|
189
|
+
}
|
|
190
|
+
}`,
|
|
191
|
+
{ id: params.id },
|
|
192
|
+
signal,
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (!data.issueRelationDelete.success) {
|
|
196
|
+
throw new Error('Linear issueRelationDelete did not succeed.');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
201
|
+
details: { success: true },
|
|
202
|
+
};
|
|
203
|
+
});
|
|
204
|
+
},
|
|
205
|
+
}),
|
|
206
|
+
];
|
|
207
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { defineTool } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Type } from '@sinclair/typebox';
|
|
3
|
+
import { withLinearAuth, linearGraphQL } from '../client';
|
|
4
|
+
import { PaginationParams, FilterParam } from '../params';
|
|
5
|
+
import { WORKFLOW_STATE_SELECTION } from '../selections';
|
|
6
|
+
import type { JsonObject } from '../types';
|
|
7
|
+
import { compactObject, asObject } from '../util';
|
|
8
|
+
|
|
9
|
+
export function issueStatusTools() {
|
|
10
|
+
return [
|
|
11
|
+
defineTool({
|
|
12
|
+
name: 'linear_list_issue_statuses',
|
|
13
|
+
label: 'Linear List Issue Statuses',
|
|
14
|
+
description:
|
|
15
|
+
'List workflow states (issue statuses). Supports full workflowStates query args.',
|
|
16
|
+
parameters: Type.Object({
|
|
17
|
+
...PaginationParams,
|
|
18
|
+
...FilterParam,
|
|
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 ?? 50,
|
|
27
|
+
includeArchived: params.includeArchived,
|
|
28
|
+
last: params.last,
|
|
29
|
+
orderBy: params.orderBy,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const data = await linearGraphQL<{
|
|
33
|
+
workflowStates: { nodes: Array<JsonObject> };
|
|
34
|
+
}>(
|
|
35
|
+
apiKey,
|
|
36
|
+
`query ListIssueStatuses(
|
|
37
|
+
$after: String
|
|
38
|
+
$before: String
|
|
39
|
+
$filter: WorkflowStateFilter
|
|
40
|
+
$first: Int
|
|
41
|
+
$includeArchived: Boolean
|
|
42
|
+
$last: Int
|
|
43
|
+
$orderBy: PaginationOrderBy
|
|
44
|
+
) {
|
|
45
|
+
workflowStates(
|
|
46
|
+
after: $after
|
|
47
|
+
before: $before
|
|
48
|
+
filter: $filter
|
|
49
|
+
first: $first
|
|
50
|
+
includeArchived: $includeArchived
|
|
51
|
+
last: $last
|
|
52
|
+
orderBy: $orderBy
|
|
53
|
+
) {
|
|
54
|
+
nodes {
|
|
55
|
+
${WORKFLOW_STATE_SELECTION}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}`,
|
|
59
|
+
variables,
|
|
60
|
+
signal,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const states = data.workflowStates.nodes;
|
|
64
|
+
return {
|
|
65
|
+
content: [{ type: 'text', text: JSON.stringify({ states }, null, 2) }],
|
|
66
|
+
details: { states },
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
}),
|
|
71
|
+
];
|
|
72
|
+
}
|