@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,250 @@
|
|
|
1
|
+
import { defineTool } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Type } from '@sinclair/typebox';
|
|
3
|
+
import { withLinearAuth, linearGraphQL } from '../client';
|
|
4
|
+
import { PaginationParams, FilterParam, RawInputParam } from '../params';
|
|
5
|
+
import { MILESTONE_SELECTION } from '../selections';
|
|
6
|
+
import type { JsonObject } from '../types';
|
|
7
|
+
import { compactObject, asObject, asString, GenericObjectSchema } from '../util';
|
|
8
|
+
|
|
9
|
+
export function milestoneTools() {
|
|
10
|
+
return [
|
|
11
|
+
defineTool({
|
|
12
|
+
name: 'linear_list_milestones',
|
|
13
|
+
label: 'Linear List Milestones',
|
|
14
|
+
description: 'List project milestones. Supports full projectMilestones 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
|
+
projectMilestones: { nodes: Array<JsonObject> };
|
|
33
|
+
}>(
|
|
34
|
+
apiKey,
|
|
35
|
+
`query ListMilestones(
|
|
36
|
+
$after: String
|
|
37
|
+
$before: String
|
|
38
|
+
$filter: ProjectMilestoneFilter
|
|
39
|
+
$first: Int
|
|
40
|
+
$includeArchived: Boolean
|
|
41
|
+
$last: Int
|
|
42
|
+
$orderBy: PaginationOrderBy
|
|
43
|
+
) {
|
|
44
|
+
projectMilestones(
|
|
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
|
+
${MILESTONE_SELECTION}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}`,
|
|
58
|
+
variables,
|
|
59
|
+
signal,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const milestones = data.projectMilestones.nodes;
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: 'text', text: JSON.stringify({ milestones }, null, 2) }],
|
|
65
|
+
details: { milestones },
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
defineTool({
|
|
71
|
+
name: 'linear_get_milestone',
|
|
72
|
+
label: 'Linear Get Milestone',
|
|
73
|
+
description: 'Get a specific project milestone by id.',
|
|
74
|
+
parameters: Type.Object({
|
|
75
|
+
milestoneId: Type.String(),
|
|
76
|
+
}),
|
|
77
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
78
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
79
|
+
const data = await linearGraphQL<{
|
|
80
|
+
projectMilestone: JsonObject | null;
|
|
81
|
+
}>(
|
|
82
|
+
apiKey,
|
|
83
|
+
`query GetMilestone($id: String!) {
|
|
84
|
+
projectMilestone(id: $id) {
|
|
85
|
+
${MILESTONE_SELECTION}
|
|
86
|
+
}
|
|
87
|
+
}`,
|
|
88
|
+
{ id: params.milestoneId },
|
|
89
|
+
signal,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
const milestone = data.projectMilestone;
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{ type: 'text', text: JSON.stringify({ milestone: milestone ?? null }, null, 2) },
|
|
96
|
+
],
|
|
97
|
+
details: { milestone: milestone ?? null },
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
defineTool({
|
|
103
|
+
name: 'linear_save_milestone',
|
|
104
|
+
label: 'Linear Save Milestone',
|
|
105
|
+
description:
|
|
106
|
+
'Create or update a project milestone. If milestoneId is provided, uses projectMilestoneUpdate; otherwise uses projectMilestoneCreate.',
|
|
107
|
+
parameters: Type.Object({
|
|
108
|
+
milestoneId: Type.Optional(Type.String()),
|
|
109
|
+
description: Type.Optional(Type.String()),
|
|
110
|
+
descriptionData: Type.Optional(GenericObjectSchema),
|
|
111
|
+
id: Type.Optional(Type.String()),
|
|
112
|
+
name: Type.Optional(Type.String()),
|
|
113
|
+
projectId: Type.Optional(Type.String()),
|
|
114
|
+
sortOrder: Type.Optional(Type.Number()),
|
|
115
|
+
targetDate: Type.Optional(Type.String()),
|
|
116
|
+
...RawInputParam,
|
|
117
|
+
}),
|
|
118
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
119
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
120
|
+
const rawInput = asObject(params.input) || {};
|
|
121
|
+
const updateId = asString(params.milestoneId);
|
|
122
|
+
|
|
123
|
+
const input = {
|
|
124
|
+
...rawInput,
|
|
125
|
+
...compactObject({
|
|
126
|
+
description: params.description,
|
|
127
|
+
descriptionData: asObject(params.descriptionData),
|
|
128
|
+
id: params.id,
|
|
129
|
+
name: params.name,
|
|
130
|
+
projectId: params.projectId,
|
|
131
|
+
sortOrder: params.sortOrder,
|
|
132
|
+
targetDate: params.targetDate,
|
|
133
|
+
}),
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
if (updateId) {
|
|
137
|
+
if (Object.keys(input).length === 0) {
|
|
138
|
+
throw new Error('No milestone update fields were provided.');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const data = await linearGraphQL<{
|
|
142
|
+
projectMilestoneUpdate: {
|
|
143
|
+
success: boolean;
|
|
144
|
+
projectMilestone?: JsonObject | null;
|
|
145
|
+
};
|
|
146
|
+
}>(
|
|
147
|
+
apiKey,
|
|
148
|
+
`mutation UpdateMilestone($id: String!, $input: ProjectMilestoneUpdateInput!) {
|
|
149
|
+
projectMilestoneUpdate(id: $id, input: $input) {
|
|
150
|
+
success
|
|
151
|
+
projectMilestone {
|
|
152
|
+
${MILESTONE_SELECTION}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}`,
|
|
156
|
+
{ id: updateId, input },
|
|
157
|
+
signal,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
if (
|
|
161
|
+
!data.projectMilestoneUpdate.success ||
|
|
162
|
+
!data.projectMilestoneUpdate.projectMilestone
|
|
163
|
+
) {
|
|
164
|
+
throw new Error('Linear projectMilestoneUpdate did not succeed.');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const milestone = data.projectMilestoneUpdate.projectMilestone;
|
|
168
|
+
return {
|
|
169
|
+
content: [{ type: 'text', text: JSON.stringify({ milestone }, null, 2) }],
|
|
170
|
+
details: { milestone },
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!asString(input.name)) {
|
|
175
|
+
throw new Error('Milestone name is required for projectMilestoneCreate (name).');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!asString(input.projectId)) {
|
|
179
|
+
throw new Error('projectId is required for projectMilestoneCreate.');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const data = await linearGraphQL<{
|
|
183
|
+
projectMilestoneCreate: {
|
|
184
|
+
success: boolean;
|
|
185
|
+
projectMilestone?: JsonObject | null;
|
|
186
|
+
};
|
|
187
|
+
}>(
|
|
188
|
+
apiKey,
|
|
189
|
+
`mutation CreateMilestone($input: ProjectMilestoneCreateInput!) {
|
|
190
|
+
projectMilestoneCreate(input: $input) {
|
|
191
|
+
success
|
|
192
|
+
projectMilestone {
|
|
193
|
+
${MILESTONE_SELECTION}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}`,
|
|
197
|
+
{ input },
|
|
198
|
+
signal,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (
|
|
202
|
+
!data.projectMilestoneCreate.success ||
|
|
203
|
+
!data.projectMilestoneCreate.projectMilestone
|
|
204
|
+
) {
|
|
205
|
+
throw new Error('Linear projectMilestoneCreate did not succeed.');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const milestone = data.projectMilestoneCreate.projectMilestone;
|
|
209
|
+
return {
|
|
210
|
+
content: [{ type: 'text', text: JSON.stringify({ milestone }, null, 2) }],
|
|
211
|
+
details: { milestone },
|
|
212
|
+
};
|
|
213
|
+
});
|
|
214
|
+
},
|
|
215
|
+
}),
|
|
216
|
+
defineTool({
|
|
217
|
+
name: 'linear_delete_milestone',
|
|
218
|
+
label: 'Linear Delete Milestone',
|
|
219
|
+
description: 'Delete a project milestone by id.',
|
|
220
|
+
parameters: Type.Object({
|
|
221
|
+
milestoneId: Type.String(),
|
|
222
|
+
}),
|
|
223
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
224
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
225
|
+
const data = await linearGraphQL<{
|
|
226
|
+
projectMilestoneDelete: { success: boolean };
|
|
227
|
+
}>(
|
|
228
|
+
apiKey,
|
|
229
|
+
`mutation DeleteMilestone($id: String!) {
|
|
230
|
+
projectMilestoneDelete(id: $id) {
|
|
231
|
+
success
|
|
232
|
+
}
|
|
233
|
+
}`,
|
|
234
|
+
{ id: params.milestoneId },
|
|
235
|
+
signal,
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
if (!data.projectMilestoneDelete.success) {
|
|
239
|
+
throw new Error('Linear projectMilestoneDelete did not succeed.');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
244
|
+
details: { success: true },
|
|
245
|
+
};
|
|
246
|
+
});
|
|
247
|
+
},
|
|
248
|
+
}),
|
|
249
|
+
];
|
|
250
|
+
}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { defineTool } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import { Type } from '@sinclair/typebox';
|
|
3
|
+
import { withLinearAuth, linearGraphQL } from '../client';
|
|
4
|
+
import { PaginationParams, FilterParam, RawInputParam } from '../params';
|
|
5
|
+
import { PROJECT_LABEL_SELECTION } from '../selections';
|
|
6
|
+
import type { JsonObject } from '../types';
|
|
7
|
+
import { compactObject, asObject, asString } from '../util';
|
|
8
|
+
|
|
9
|
+
export function projectLabelTools() {
|
|
10
|
+
return [
|
|
11
|
+
defineTool({
|
|
12
|
+
name: 'linear_list_project_labels',
|
|
13
|
+
label: 'Linear List Project Labels',
|
|
14
|
+
description: 'List project labels. Supports full projectLabels 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 ?? 50,
|
|
26
|
+
includeArchived: params.includeArchived,
|
|
27
|
+
last: params.last,
|
|
28
|
+
orderBy: params.orderBy,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const data = await linearGraphQL<{
|
|
32
|
+
projectLabels: { nodes: Array<JsonObject> };
|
|
33
|
+
}>(
|
|
34
|
+
apiKey,
|
|
35
|
+
`query ListProjectLabels(
|
|
36
|
+
$after: String
|
|
37
|
+
$before: String
|
|
38
|
+
$filter: ProjectLabelFilter
|
|
39
|
+
$first: Int
|
|
40
|
+
$includeArchived: Boolean
|
|
41
|
+
$last: Int
|
|
42
|
+
$orderBy: PaginationOrderBy
|
|
43
|
+
) {
|
|
44
|
+
projectLabels(
|
|
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
|
+
${PROJECT_LABEL_SELECTION}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}`,
|
|
58
|
+
variables,
|
|
59
|
+
signal,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const labels = data.projectLabels.nodes;
|
|
63
|
+
return {
|
|
64
|
+
content: [{ type: 'text', text: JSON.stringify({ labels }, null, 2) }],
|
|
65
|
+
details: { labels },
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
defineTool({
|
|
71
|
+
name: 'linear_create_project_label',
|
|
72
|
+
label: 'Linear Create Project Label',
|
|
73
|
+
description: 'Create a project label.',
|
|
74
|
+
parameters: Type.Object({
|
|
75
|
+
name: Type.Optional(Type.String()),
|
|
76
|
+
description: Type.Optional(Type.String()),
|
|
77
|
+
color: Type.Optional(Type.String()),
|
|
78
|
+
parentId: Type.Optional(Type.String()),
|
|
79
|
+
isGroup: Type.Optional(Type.Boolean()),
|
|
80
|
+
...RawInputParam,
|
|
81
|
+
}),
|
|
82
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
83
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
84
|
+
const rawInput = asObject(params.input) || {};
|
|
85
|
+
const input = {
|
|
86
|
+
...rawInput,
|
|
87
|
+
...compactObject({
|
|
88
|
+
name: params.name,
|
|
89
|
+
description: params.description,
|
|
90
|
+
color: params.color,
|
|
91
|
+
parentId: params.parentId,
|
|
92
|
+
isGroup: params.isGroup,
|
|
93
|
+
}),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (!asString(input.name)) {
|
|
97
|
+
throw new Error('Project label name is required (name).');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const data = await linearGraphQL<{
|
|
101
|
+
projectLabelCreate: {
|
|
102
|
+
success: boolean;
|
|
103
|
+
projectLabel?: JsonObject | null;
|
|
104
|
+
};
|
|
105
|
+
}>(
|
|
106
|
+
apiKey,
|
|
107
|
+
`mutation CreateProjectLabel($input: ProjectLabelCreateInput!) {
|
|
108
|
+
projectLabelCreate(input: $input) {
|
|
109
|
+
success
|
|
110
|
+
projectLabel {
|
|
111
|
+
${PROJECT_LABEL_SELECTION}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}`,
|
|
115
|
+
{ input },
|
|
116
|
+
signal,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const label = data.projectLabelCreate.projectLabel;
|
|
120
|
+
if (!data.projectLabelCreate.success || !label) {
|
|
121
|
+
throw new Error('Linear projectLabelCreate did not succeed.');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
content: [{ type: 'text', text: JSON.stringify({ label }, null, 2) }],
|
|
126
|
+
details: { label },
|
|
127
|
+
};
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
}),
|
|
131
|
+
defineTool({
|
|
132
|
+
name: 'linear_update_project_label',
|
|
133
|
+
label: 'Linear Update Project Label',
|
|
134
|
+
description: 'Update a project label by id.',
|
|
135
|
+
parameters: Type.Object({
|
|
136
|
+
id: Type.String(),
|
|
137
|
+
name: Type.Optional(Type.String()),
|
|
138
|
+
description: Type.Optional(Type.String()),
|
|
139
|
+
color: Type.Optional(Type.String()),
|
|
140
|
+
parentId: Type.Optional(Type.String()),
|
|
141
|
+
isGroup: Type.Optional(Type.Boolean()),
|
|
142
|
+
...RawInputParam,
|
|
143
|
+
}),
|
|
144
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
145
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
146
|
+
const rawInput = asObject(params.input) || {};
|
|
147
|
+
const input = {
|
|
148
|
+
...rawInput,
|
|
149
|
+
...compactObject({
|
|
150
|
+
name: params.name,
|
|
151
|
+
description: params.description,
|
|
152
|
+
color: params.color,
|
|
153
|
+
parentId: params.parentId,
|
|
154
|
+
isGroup: params.isGroup,
|
|
155
|
+
}),
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
if (Object.keys(input).length === 0) {
|
|
159
|
+
throw new Error('No update fields were provided.');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const data = await linearGraphQL<{
|
|
163
|
+
projectLabelUpdate: {
|
|
164
|
+
success: boolean;
|
|
165
|
+
projectLabel?: JsonObject | null;
|
|
166
|
+
};
|
|
167
|
+
}>(
|
|
168
|
+
apiKey,
|
|
169
|
+
`mutation UpdateProjectLabel($id: String!, $input: ProjectLabelUpdateInput!) {
|
|
170
|
+
projectLabelUpdate(id: $id, input: $input) {
|
|
171
|
+
success
|
|
172
|
+
projectLabel {
|
|
173
|
+
${PROJECT_LABEL_SELECTION}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}`,
|
|
177
|
+
{ id: params.id, input },
|
|
178
|
+
signal,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const label = data.projectLabelUpdate.projectLabel;
|
|
182
|
+
if (!data.projectLabelUpdate.success || !label) {
|
|
183
|
+
throw new Error('Linear projectLabelUpdate did not succeed.');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
content: [{ type: 'text', text: JSON.stringify({ label }, null, 2) }],
|
|
188
|
+
details: { label },
|
|
189
|
+
};
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
}),
|
|
193
|
+
defineTool({
|
|
194
|
+
name: 'linear_delete_project_label',
|
|
195
|
+
label: 'Linear Delete Project Label',
|
|
196
|
+
description: 'Delete a project label by id.',
|
|
197
|
+
parameters: Type.Object({
|
|
198
|
+
id: Type.String(),
|
|
199
|
+
}),
|
|
200
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
201
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
202
|
+
const data = await linearGraphQL<{
|
|
203
|
+
projectLabelDelete: { success: boolean };
|
|
204
|
+
}>(
|
|
205
|
+
apiKey,
|
|
206
|
+
`mutation DeleteProjectLabel($id: String!) {
|
|
207
|
+
projectLabelDelete(id: $id) {
|
|
208
|
+
success
|
|
209
|
+
}
|
|
210
|
+
}`,
|
|
211
|
+
{ id: params.id },
|
|
212
|
+
signal,
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
if (!data.projectLabelDelete.success) {
|
|
216
|
+
throw new Error('Linear projectLabelDelete did not succeed.');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
221
|
+
details: { success: true },
|
|
222
|
+
};
|
|
223
|
+
});
|
|
224
|
+
},
|
|
225
|
+
}),
|
|
226
|
+
];
|
|
227
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
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 { PROJECT_RELATION_SELECTION } from '../selections';
|
|
6
|
+
import type { JsonObject } from '../types';
|
|
7
|
+
import { compactObject } from '../util';
|
|
8
|
+
|
|
9
|
+
export function projectRelationTools() {
|
|
10
|
+
return [
|
|
11
|
+
defineTool({
|
|
12
|
+
name: 'linear_list_project_relations',
|
|
13
|
+
label: 'Linear List Project Relations',
|
|
14
|
+
description: 'List project 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
|
+
projectRelations: { nodes: Array<JsonObject> };
|
|
31
|
+
}>(
|
|
32
|
+
apiKey,
|
|
33
|
+
`query ListProjectRelations(
|
|
34
|
+
$after: String
|
|
35
|
+
$before: String
|
|
36
|
+
$first: Int
|
|
37
|
+
$includeArchived: Boolean
|
|
38
|
+
$last: Int
|
|
39
|
+
$orderBy: PaginationOrderBy
|
|
40
|
+
) {
|
|
41
|
+
projectRelations(
|
|
42
|
+
after: $after
|
|
43
|
+
before: $before
|
|
44
|
+
first: $first
|
|
45
|
+
includeArchived: $includeArchived
|
|
46
|
+
last: $last
|
|
47
|
+
orderBy: $orderBy
|
|
48
|
+
) {
|
|
49
|
+
nodes {
|
|
50
|
+
${PROJECT_RELATION_SELECTION}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}`,
|
|
54
|
+
variables,
|
|
55
|
+
signal,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const projectRelations = data.projectRelations.nodes;
|
|
59
|
+
return {
|
|
60
|
+
content: [{ type: 'text', text: JSON.stringify({ projectRelations }, null, 2) }],
|
|
61
|
+
details: { projectRelations },
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
},
|
|
65
|
+
}),
|
|
66
|
+
defineTool({
|
|
67
|
+
name: 'linear_create_project_relation',
|
|
68
|
+
label: 'Linear Create Project Relation',
|
|
69
|
+
description: 'Create a relation between two projects.',
|
|
70
|
+
parameters: Type.Object({
|
|
71
|
+
projectId: Type.String(),
|
|
72
|
+
relatedProjectId: Type.String(),
|
|
73
|
+
type: Type.String({ description: 'Relation type.' }),
|
|
74
|
+
anchorType: Type.String({ description: 'Anchor type for the project.' }),
|
|
75
|
+
relatedAnchorType: Type.String({
|
|
76
|
+
description: 'Anchor type for the related project.',
|
|
77
|
+
}),
|
|
78
|
+
projectMilestoneId: Type.Optional(Type.String()),
|
|
79
|
+
relatedProjectMilestoneId: Type.Optional(Type.String()),
|
|
80
|
+
}),
|
|
81
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
82
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
83
|
+
const input = compactObject({
|
|
84
|
+
projectId: params.projectId,
|
|
85
|
+
relatedProjectId: params.relatedProjectId,
|
|
86
|
+
type: params.type,
|
|
87
|
+
anchorType: params.anchorType,
|
|
88
|
+
relatedAnchorType: params.relatedAnchorType,
|
|
89
|
+
projectMilestoneId: params.projectMilestoneId,
|
|
90
|
+
relatedProjectMilestoneId: params.relatedProjectMilestoneId,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const data = await linearGraphQL<{
|
|
94
|
+
projectRelationCreate: {
|
|
95
|
+
success: boolean;
|
|
96
|
+
projectRelation?: JsonObject | null;
|
|
97
|
+
};
|
|
98
|
+
}>(
|
|
99
|
+
apiKey,
|
|
100
|
+
`mutation CreateProjectRelation($input: ProjectRelationCreateInput!) {
|
|
101
|
+
projectRelationCreate(input: $input) {
|
|
102
|
+
success
|
|
103
|
+
projectRelation {
|
|
104
|
+
${PROJECT_RELATION_SELECTION}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}`,
|
|
108
|
+
{ input },
|
|
109
|
+
signal,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const projectRelation = data.projectRelationCreate.projectRelation;
|
|
113
|
+
if (!data.projectRelationCreate.success || !projectRelation) {
|
|
114
|
+
throw new Error('Linear projectRelationCreate did not succeed.');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
content: [{ type: 'text', text: JSON.stringify({ projectRelation }, null, 2) }],
|
|
119
|
+
details: { projectRelation },
|
|
120
|
+
};
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
}),
|
|
124
|
+
defineTool({
|
|
125
|
+
name: 'linear_update_project_relation',
|
|
126
|
+
label: 'Linear Update Project Relation',
|
|
127
|
+
description: 'Update a project relation by id.',
|
|
128
|
+
parameters: Type.Object({
|
|
129
|
+
id: Type.String(),
|
|
130
|
+
type: Type.Optional(Type.String()),
|
|
131
|
+
projectId: Type.Optional(Type.String()),
|
|
132
|
+
relatedProjectId: Type.Optional(Type.String()),
|
|
133
|
+
anchorType: Type.Optional(Type.String()),
|
|
134
|
+
relatedAnchorType: Type.Optional(Type.String()),
|
|
135
|
+
projectMilestoneId: Type.Optional(Type.String()),
|
|
136
|
+
relatedProjectMilestoneId: Type.Optional(Type.String()),
|
|
137
|
+
}),
|
|
138
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
139
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
140
|
+
const input = compactObject({
|
|
141
|
+
type: params.type,
|
|
142
|
+
projectId: params.projectId,
|
|
143
|
+
relatedProjectId: params.relatedProjectId,
|
|
144
|
+
anchorType: params.anchorType,
|
|
145
|
+
relatedAnchorType: params.relatedAnchorType,
|
|
146
|
+
projectMilestoneId: params.projectMilestoneId,
|
|
147
|
+
relatedProjectMilestoneId: params.relatedProjectMilestoneId,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
if (Object.keys(input).length === 0) {
|
|
151
|
+
throw new Error('No update fields were provided.');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const data = await linearGraphQL<{
|
|
155
|
+
projectRelationUpdate: {
|
|
156
|
+
success: boolean;
|
|
157
|
+
projectRelation?: JsonObject | null;
|
|
158
|
+
};
|
|
159
|
+
}>(
|
|
160
|
+
apiKey,
|
|
161
|
+
`mutation UpdateProjectRelation($id: String!, $input: ProjectRelationUpdateInput!) {
|
|
162
|
+
projectRelationUpdate(id: $id, input: $input) {
|
|
163
|
+
success
|
|
164
|
+
projectRelation {
|
|
165
|
+
${PROJECT_RELATION_SELECTION}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}`,
|
|
169
|
+
{ id: params.id, input },
|
|
170
|
+
signal,
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const projectRelation = data.projectRelationUpdate.projectRelation;
|
|
174
|
+
if (!data.projectRelationUpdate.success || !projectRelation) {
|
|
175
|
+
throw new Error('Linear projectRelationUpdate did not succeed.');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
content: [{ type: 'text', text: JSON.stringify({ projectRelation }, null, 2) }],
|
|
180
|
+
details: { projectRelation },
|
|
181
|
+
};
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
defineTool({
|
|
186
|
+
name: 'linear_delete_project_relation',
|
|
187
|
+
label: 'Linear Delete Project Relation',
|
|
188
|
+
description: 'Delete a project relation by id.',
|
|
189
|
+
parameters: Type.Object({
|
|
190
|
+
id: Type.String(),
|
|
191
|
+
}),
|
|
192
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
193
|
+
return withLinearAuth(ctx, signal, async (apiKey) => {
|
|
194
|
+
const data = await linearGraphQL<{
|
|
195
|
+
projectRelationDelete: { success: boolean };
|
|
196
|
+
}>(
|
|
197
|
+
apiKey,
|
|
198
|
+
`mutation DeleteProjectRelation($id: String!) {
|
|
199
|
+
projectRelationDelete(id: $id) {
|
|
200
|
+
success
|
|
201
|
+
}
|
|
202
|
+
}`,
|
|
203
|
+
{ id: params.id },
|
|
204
|
+
signal,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (!data.projectRelationDelete.success) {
|
|
208
|
+
throw new Error('Linear projectRelationDelete did not succeed.');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true }, null, 2) }],
|
|
213
|
+
details: { success: true },
|
|
214
|
+
};
|
|
215
|
+
});
|
|
216
|
+
},
|
|
217
|
+
}),
|
|
218
|
+
];
|
|
219
|
+
}
|