@alasano/pi-linear 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +16 -2
  2. package/assets/linear_list_issues.png +0 -0
  3. package/assets/screenshot.png +0 -0
  4. package/extensions/params.ts +40 -1
  5. package/extensions/renderers/comments.ts +323 -0
  6. package/extensions/renderers/common.ts +305 -0
  7. package/extensions/renderers/documents.ts +326 -0
  8. package/extensions/renderers/initiatives.ts +344 -0
  9. package/extensions/renderers/issue-labels.ts +294 -0
  10. package/extensions/renderers/issue-relations.ts +318 -0
  11. package/extensions/renderers/issue-statuses.ts +199 -0
  12. package/extensions/renderers/issues.ts +373 -0
  13. package/extensions/renderers/milestones.ts +294 -0
  14. package/extensions/renderers/project-labels.ts +279 -0
  15. package/extensions/renderers/project-relations.ts +344 -0
  16. package/extensions/renderers/projects.ts +437 -0
  17. package/extensions/renderers/state.ts +35 -0
  18. package/extensions/renderers/teams.ts +246 -0
  19. package/extensions/renderers/users.ts +242 -0
  20. package/extensions/renderers/workspaces.ts +44 -0
  21. package/extensions/selections.ts +10 -3
  22. package/extensions/settings.ts +40 -7
  23. package/extensions/tools/comments.ts +30 -11
  24. package/extensions/tools/documents.ts +42 -11
  25. package/extensions/tools/initiatives.ts +43 -11
  26. package/extensions/tools/issue-labels.ts +36 -11
  27. package/extensions/tools/issue-relations.ts +32 -13
  28. package/extensions/tools/issue-statuses.ts +19 -11
  29. package/extensions/tools/issues.ts +53 -19
  30. package/extensions/tools/milestones.ts +31 -11
  31. package/extensions/tools/project-labels.ts +30 -11
  32. package/extensions/tools/project-relations.ts +32 -13
  33. package/extensions/tools/projects.ts +48 -16
  34. package/extensions/tools/teams.ts +23 -11
  35. package/extensions/tools/users.ts +23 -11
  36. package/extensions/tools/workspaces.ts +6 -0
  37. package/extensions/types.ts +12 -0
  38. package/package.json +1 -1
@@ -1,10 +1,19 @@
1
1
  import { defineTool } from '@mariozechner/pi-coding-agent';
2
2
  import { Type } from '@sinclair/typebox';
3
3
  import { withLinearAuth, linearGraphQL } from '../client';
4
- import { PaginationParams, FilterParam, RawInputParam } from '../params';
4
+ import { PaginationParams, paginationVariables, FilterParam, RawInputParam } from '../params';
5
5
  import { COMMENT_SELECTION } from '../selections';
6
- import type { JsonObject } from '../types';
6
+ import type { JsonObject, LinearConnection } from '../types';
7
7
  import { compactObject, asObject, asString, GenericObjectSchema } from '../util';
8
+ import {
9
+ renderLinearCommentListCall,
10
+ renderLinearCommentListResult,
11
+ renderLinearCommentResult,
12
+ renderLinearCreateCommentCall,
13
+ renderLinearDeleteCommentCall,
14
+ renderLinearDeleteCommentResult,
15
+ renderLinearUpdateCommentCall,
16
+ } from '../renderers/comments';
8
17
 
9
18
  export function commentTools() {
10
19
  return [
@@ -16,20 +25,16 @@ export function commentTools() {
16
25
  ...PaginationParams,
17
26
  ...FilterParam,
18
27
  }),
28
+ renderCall: renderLinearCommentListCall,
19
29
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
20
30
  return withLinearAuth(ctx, signal, async (apiKey) => {
21
31
  const variables = compactObject({
22
- after: params.after,
23
- before: params.before,
32
+ ...paginationVariables(params, 20),
24
33
  filter: asObject(params.filter),
25
- first: params.first ?? 20,
26
- includeArchived: params.includeArchived,
27
- last: params.last,
28
- orderBy: params.orderBy,
29
34
  });
30
35
 
31
36
  const data = await linearGraphQL<{
32
- comments: { nodes: Array<JsonObject> };
37
+ comments: LinearConnection<JsonObject>;
33
38
  }>(
34
39
  apiKey,
35
40
  `query ListComments(
@@ -53,6 +58,12 @@ export function commentTools() {
53
58
  nodes {
54
59
  ${COMMENT_SELECTION}
55
60
  }
61
+ pageInfo {
62
+ hasNextPage
63
+ hasPreviousPage
64
+ startCursor
65
+ endCursor
66
+ }
56
67
  }
57
68
  }`,
58
69
  variables,
@@ -60,12 +71,14 @@ export function commentTools() {
60
71
  );
61
72
 
62
73
  const comments = data.comments.nodes;
74
+ const pageInfo = data.comments.pageInfo;
63
75
  return {
64
- content: [{ type: 'text', text: JSON.stringify({ comments }, null, 2) }],
65
- details: { comments },
76
+ content: [{ type: 'text', text: JSON.stringify({ comments, pageInfo }, null, 2) }],
77
+ details: { comments, pageInfo },
66
78
  };
67
79
  });
68
80
  },
81
+ renderResult: renderLinearCommentListResult,
69
82
  }),
70
83
  defineTool({
71
84
  name: 'linear_create_comment',
@@ -90,6 +103,7 @@ export function commentTools() {
90
103
  subscriberIds: Type.Optional(Type.Array(Type.String())),
91
104
  ...RawInputParam,
92
105
  }),
106
+ renderCall: renderLinearCreateCommentCall,
93
107
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
94
108
  return withLinearAuth(ctx, signal, async (apiKey) => {
95
109
  const rawInput = asObject(params.input) || {};
@@ -146,6 +160,7 @@ export function commentTools() {
146
160
  };
147
161
  });
148
162
  },
163
+ renderResult: renderLinearCommentResult('Created comment'),
149
164
  }),
150
165
  defineTool({
151
166
  name: 'linear_update_comment',
@@ -157,6 +172,7 @@ export function commentTools() {
157
172
  quotedText: Type.Optional(Type.String()),
158
173
  ...RawInputParam,
159
174
  }),
175
+ renderCall: renderLinearUpdateCommentCall,
160
176
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
161
177
  return withLinearAuth(ctx, signal, async (apiKey) => {
162
178
  const rawInput = asObject(params.input) || {};
@@ -199,6 +215,7 @@ export function commentTools() {
199
215
  };
200
216
  });
201
217
  },
218
+ renderResult: renderLinearCommentResult('Updated comment'),
202
219
  }),
203
220
  defineTool({
204
221
  name: 'linear_delete_comment',
@@ -207,6 +224,7 @@ export function commentTools() {
207
224
  parameters: Type.Object({
208
225
  id: Type.String(),
209
226
  }),
227
+ renderCall: renderLinearDeleteCommentCall,
210
228
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
211
229
  return withLinearAuth(ctx, signal, async (apiKey) => {
212
230
  const data = await linearGraphQL<{
@@ -232,6 +250,7 @@ export function commentTools() {
232
250
  };
233
251
  });
234
252
  },
253
+ renderResult: renderLinearDeleteCommentResult,
235
254
  }),
236
255
  ];
237
256
  }
@@ -1,10 +1,27 @@
1
1
  import { defineTool } from '@mariozechner/pi-coding-agent';
2
2
  import { Type } from '@sinclair/typebox';
3
3
  import { withLinearAuth, linearGraphQL, resolveTeamId } from '../client';
4
- import { PaginationParams, FilterParam, RawInputParam, TeamConvenienceParams } from '../params';
4
+ import {
5
+ PaginationParams,
6
+ paginationVariables,
7
+ FilterParam,
8
+ RawInputParam,
9
+ TeamConvenienceParams,
10
+ } from '../params';
5
11
  import { DOCUMENT_SELECTION } from '../selections';
6
- import type { JsonObject } from '../types';
12
+ import type { JsonObject, LinearConnection } from '../types';
7
13
  import { compactObject, asObject, asString } from '../util';
14
+ import {
15
+ renderLinearCreateDocumentCall,
16
+ renderLinearDeleteDocumentCall,
17
+ renderLinearDocumentListCall,
18
+ renderLinearDocumentListResult,
19
+ renderLinearDocumentResult,
20
+ renderLinearDocumentSuccessResult,
21
+ renderLinearGetDocumentCall,
22
+ renderLinearUnarchiveDocumentCall,
23
+ renderLinearUpdateDocumentCall,
24
+ } from '../renderers/documents';
8
25
 
9
26
  export function documentTools() {
10
27
  return [
@@ -16,20 +33,16 @@ export function documentTools() {
16
33
  ...PaginationParams,
17
34
  ...FilterParam,
18
35
  }),
36
+ renderCall: renderLinearDocumentListCall,
19
37
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
20
38
  return withLinearAuth(ctx, signal, async (apiKey) => {
21
39
  const variables = compactObject({
22
- after: params.after,
23
- before: params.before,
40
+ ...paginationVariables(params, 20),
24
41
  filter: asObject(params.filter),
25
- first: params.first ?? 20,
26
- includeArchived: params.includeArchived,
27
- last: params.last,
28
- orderBy: params.orderBy,
29
42
  });
30
43
 
31
44
  const data = await linearGraphQL<{
32
- documents: { nodes: Array<JsonObject> };
45
+ documents: LinearConnection<JsonObject>;
33
46
  }>(
34
47
  apiKey,
35
48
  `query ListDocuments(
@@ -53,6 +66,12 @@ export function documentTools() {
53
66
  nodes {
54
67
  ${DOCUMENT_SELECTION}
55
68
  }
69
+ pageInfo {
70
+ hasNextPage
71
+ hasPreviousPage
72
+ startCursor
73
+ endCursor
74
+ }
56
75
  }
57
76
  }`,
58
77
  variables,
@@ -60,12 +79,14 @@ export function documentTools() {
60
79
  );
61
80
 
62
81
  const documents = data.documents.nodes;
82
+ const pageInfo = data.documents.pageInfo;
63
83
  return {
64
- content: [{ type: 'text', text: JSON.stringify({ documents }, null, 2) }],
65
- details: { documents },
84
+ content: [{ type: 'text', text: JSON.stringify({ documents, pageInfo }, null, 2) }],
85
+ details: { documents, pageInfo },
66
86
  };
67
87
  });
68
88
  },
89
+ renderResult: renderLinearDocumentListResult,
69
90
  }),
70
91
  defineTool({
71
92
  name: 'linear_get_document',
@@ -74,6 +95,7 @@ export function documentTools() {
74
95
  parameters: Type.Object({
75
96
  documentId: Type.String(),
76
97
  }),
98
+ renderCall: renderLinearGetDocumentCall,
77
99
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
78
100
  return withLinearAuth(ctx, signal, async (apiKey) => {
79
101
  const data = await linearGraphQL<{ document: JsonObject | null }>(
@@ -96,6 +118,7 @@ export function documentTools() {
96
118
  };
97
119
  });
98
120
  },
121
+ renderResult: renderLinearDocumentResult('Document'),
99
122
  }),
100
123
  defineTool({
101
124
  name: 'linear_create_document',
@@ -120,6 +143,7 @@ export function documentTools() {
120
143
  title: Type.Optional(Type.String()),
121
144
  ...RawInputParam,
122
145
  }),
146
+ renderCall: renderLinearCreateDocumentCall,
123
147
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
124
148
  return withLinearAuth(ctx, signal, async (apiKey) => {
125
149
  const rawInput = asObject(params.input) || {};
@@ -188,6 +212,7 @@ export function documentTools() {
188
212
  };
189
213
  });
190
214
  },
215
+ renderResult: renderLinearDocumentResult('Created document'),
191
216
  }),
192
217
  defineTool({
193
218
  name: 'linear_update_document',
@@ -214,6 +239,7 @@ export function documentTools() {
214
239
  trashed: Type.Optional(Type.Boolean()),
215
240
  ...RawInputParam,
216
241
  }),
242
+ renderCall: renderLinearUpdateDocumentCall,
217
243
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
218
244
  return withLinearAuth(ctx, signal, async (apiKey) => {
219
245
  const rawInput = asObject(params.input) || {};
@@ -286,6 +312,7 @@ export function documentTools() {
286
312
  };
287
313
  });
288
314
  },
315
+ renderResult: renderLinearDocumentResult('Updated document'),
289
316
  }),
290
317
  defineTool({
291
318
  name: 'linear_delete_document',
@@ -294,6 +321,7 @@ export function documentTools() {
294
321
  parameters: Type.Object({
295
322
  documentId: Type.String(),
296
323
  }),
324
+ renderCall: renderLinearDeleteDocumentCall,
297
325
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
298
326
  return withLinearAuth(ctx, signal, async (apiKey) => {
299
327
  const data = await linearGraphQL<{
@@ -319,6 +347,7 @@ export function documentTools() {
319
347
  };
320
348
  });
321
349
  },
350
+ renderResult: renderLinearDocumentSuccessResult('Deleted'),
322
351
  }),
323
352
  defineTool({
324
353
  name: 'linear_unarchive_document',
@@ -327,6 +356,7 @@ export function documentTools() {
327
356
  parameters: Type.Object({
328
357
  documentId: Type.String(),
329
358
  }),
359
+ renderCall: renderLinearUnarchiveDocumentCall,
330
360
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
331
361
  return withLinearAuth(ctx, signal, async (apiKey) => {
332
362
  const data = await linearGraphQL<{
@@ -352,6 +382,7 @@ export function documentTools() {
352
382
  };
353
383
  });
354
384
  },
385
+ renderResult: renderLinearDocumentSuccessResult('Unarchived'),
355
386
  }),
356
387
  ];
357
388
  }
@@ -1,10 +1,28 @@
1
1
  import { defineTool } from '@mariozechner/pi-coding-agent';
2
2
  import { Type } from '@sinclair/typebox';
3
3
  import { withLinearAuth, linearGraphQL } from '../client';
4
- import { PaginationParams, FilterParam, SortParam, RawInputParam } from '../params';
4
+ import {
5
+ PaginationParams,
6
+ paginationVariables,
7
+ FilterParam,
8
+ SortParam,
9
+ RawInputParam,
10
+ } from '../params';
5
11
  import { INITIATIVE_SELECTION } from '../selections';
6
- import type { JsonObject } from '../types';
12
+ import type { JsonObject, LinearConnection } from '../types';
7
13
  import { compactObject, asObject, asObjectArray, asString } from '../util';
14
+ import {
15
+ renderLinearArchiveInitiativeCall,
16
+ renderLinearDeleteInitiativeCall,
17
+ renderLinearGetInitiativeCall,
18
+ renderLinearInitiativeListCall,
19
+ renderLinearInitiativeListResult,
20
+ renderLinearInitiativeResult,
21
+ renderLinearInitiativeSuccessResult,
22
+ renderLinearSaveInitiativeCall,
23
+ renderLinearSaveInitiativeResult,
24
+ renderLinearUnarchiveInitiativeCall,
25
+ } from '../renderers/initiatives';
8
26
 
9
27
  export function initiativeTools() {
10
28
  return [
@@ -17,21 +35,17 @@ export function initiativeTools() {
17
35
  ...FilterParam,
18
36
  ...SortParam,
19
37
  }),
38
+ renderCall: renderLinearInitiativeListCall,
20
39
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
21
40
  return withLinearAuth(ctx, signal, async (apiKey) => {
22
41
  const variables = compactObject({
23
- after: params.after,
24
- before: params.before,
42
+ ...paginationVariables(params, 20),
25
43
  filter: asObject(params.filter),
26
- first: params.first ?? 20,
27
- includeArchived: params.includeArchived,
28
- last: params.last,
29
- orderBy: params.orderBy,
30
44
  sort: asObjectArray(params.sort),
31
45
  });
32
46
 
33
47
  const data = await linearGraphQL<{
34
- initiatives: { nodes: Array<JsonObject> };
48
+ initiatives: LinearConnection<JsonObject>;
35
49
  }>(
36
50
  apiKey,
37
51
  `query ListInitiatives(
@@ -57,6 +71,12 @@ export function initiativeTools() {
57
71
  nodes {
58
72
  ${INITIATIVE_SELECTION}
59
73
  }
74
+ pageInfo {
75
+ hasNextPage
76
+ hasPreviousPage
77
+ startCursor
78
+ endCursor
79
+ }
60
80
  }
61
81
  }`,
62
82
  variables,
@@ -64,12 +84,14 @@ export function initiativeTools() {
64
84
  );
65
85
 
66
86
  const initiatives = data.initiatives.nodes;
87
+ const pageInfo = data.initiatives.pageInfo;
67
88
  return {
68
- content: [{ type: 'text', text: JSON.stringify({ initiatives }, null, 2) }],
69
- details: { initiatives },
89
+ content: [{ type: 'text', text: JSON.stringify({ initiatives, pageInfo }, null, 2) }],
90
+ details: { initiatives, pageInfo },
70
91
  };
71
92
  });
72
93
  },
94
+ renderResult: renderLinearInitiativeListResult,
73
95
  }),
74
96
  defineTool({
75
97
  name: 'linear_get_initiative',
@@ -78,6 +100,7 @@ export function initiativeTools() {
78
100
  parameters: Type.Object({
79
101
  initiativeId: Type.String(),
80
102
  }),
103
+ renderCall: renderLinearGetInitiativeCall,
81
104
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
82
105
  return withLinearAuth(ctx, signal, async (apiKey) => {
83
106
  const data = await linearGraphQL<{ initiative: JsonObject | null }>(
@@ -100,6 +123,7 @@ export function initiativeTools() {
100
123
  };
101
124
  });
102
125
  },
126
+ renderResult: renderLinearInitiativeResult('Initiative'),
103
127
  }),
104
128
  defineTool({
105
129
  name: 'linear_save_initiative',
@@ -127,6 +151,7 @@ export function initiativeTools() {
127
151
  updateRemindersHour: Type.Optional(Type.Integer()),
128
152
  ...RawInputParam,
129
153
  }),
154
+ renderCall: renderLinearSaveInitiativeCall,
130
155
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
131
156
  return withLinearAuth(ctx, signal, async (apiKey) => {
132
157
  const rawInput = asObject(params.input) || {};
@@ -224,6 +249,7 @@ export function initiativeTools() {
224
249
  };
225
250
  });
226
251
  },
252
+ renderResult: renderLinearSaveInitiativeResult,
227
253
  }),
228
254
  defineTool({
229
255
  name: 'linear_delete_initiative',
@@ -232,6 +258,7 @@ export function initiativeTools() {
232
258
  parameters: Type.Object({
233
259
  initiativeId: Type.String(),
234
260
  }),
261
+ renderCall: renderLinearDeleteInitiativeCall,
235
262
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
236
263
  return withLinearAuth(ctx, signal, async (apiKey) => {
237
264
  const data = await linearGraphQL<{
@@ -257,6 +284,7 @@ export function initiativeTools() {
257
284
  };
258
285
  });
259
286
  },
287
+ renderResult: renderLinearInitiativeSuccessResult('Deleted'),
260
288
  }),
261
289
  defineTool({
262
290
  name: 'linear_archive_initiative',
@@ -265,6 +293,7 @@ export function initiativeTools() {
265
293
  parameters: Type.Object({
266
294
  initiativeId: Type.String(),
267
295
  }),
296
+ renderCall: renderLinearArchiveInitiativeCall,
268
297
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
269
298
  return withLinearAuth(ctx, signal, async (apiKey) => {
270
299
  const data = await linearGraphQL<{
@@ -290,6 +319,7 @@ export function initiativeTools() {
290
319
  };
291
320
  });
292
321
  },
322
+ renderResult: renderLinearInitiativeSuccessResult('Archived'),
293
323
  }),
294
324
  defineTool({
295
325
  name: 'linear_unarchive_initiative',
@@ -298,6 +328,7 @@ export function initiativeTools() {
298
328
  parameters: Type.Object({
299
329
  initiativeId: Type.String(),
300
330
  }),
331
+ renderCall: renderLinearUnarchiveInitiativeCall,
301
332
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
302
333
  return withLinearAuth(ctx, signal, async (apiKey) => {
303
334
  const data = await linearGraphQL<{
@@ -323,6 +354,7 @@ export function initiativeTools() {
323
354
  };
324
355
  });
325
356
  },
357
+ renderResult: renderLinearInitiativeSuccessResult('Unarchived'),
326
358
  }),
327
359
  ];
328
360
  }
@@ -1,10 +1,25 @@
1
1
  import { defineTool } from '@mariozechner/pi-coding-agent';
2
2
  import { Type } from '@sinclair/typebox';
3
3
  import { withLinearAuth, linearGraphQL, resolveTeamId } from '../client';
4
- import { PaginationParams, FilterParam, RawInputParam, TeamConvenienceParams } from '../params';
4
+ import {
5
+ PaginationParams,
6
+ paginationVariables,
7
+ FilterParam,
8
+ RawInputParam,
9
+ TeamConvenienceParams,
10
+ } from '../params';
5
11
  import { ISSUE_LABEL_SELECTION } from '../selections';
6
- import type { JsonObject } from '../types';
12
+ import type { JsonObject, LinearConnection } from '../types';
7
13
  import { compactObject, asObject, asString, mergeFilters } from '../util';
14
+ import {
15
+ renderLinearCreateIssueLabelCall,
16
+ renderLinearDeleteIssueLabelCall,
17
+ renderLinearIssueLabelDeleteResult,
18
+ renderLinearIssueLabelListCall,
19
+ renderLinearIssueLabelListResult,
20
+ renderLinearIssueLabelResult,
21
+ renderLinearUpdateIssueLabelCall,
22
+ } from '../renderers/issue-labels';
8
23
 
9
24
  export function issueLabelTools() {
10
25
  return [
@@ -17,6 +32,7 @@ export function issueLabelTools() {
17
32
  ...PaginationParams,
18
33
  ...FilterParam,
19
34
  }),
35
+ renderCall: renderLinearIssueLabelListCall,
20
36
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
21
37
  return withLinearAuth(ctx, signal, async (apiKey) => {
22
38
  const resolvedTeamId =
@@ -35,17 +51,12 @@ export function issueLabelTools() {
35
51
  const filter = mergeFilters(asObject(params.filter), convenienceFilter);
36
52
 
37
53
  const variables = compactObject({
38
- after: params.after,
39
- before: params.before,
54
+ ...paginationVariables(params, 50),
40
55
  filter,
41
- first: params.first ?? 50,
42
- includeArchived: params.includeArchived,
43
- last: params.last,
44
- orderBy: params.orderBy,
45
56
  });
46
57
 
47
58
  const data = await linearGraphQL<{
48
- issueLabels: { nodes: Array<JsonObject> };
59
+ issueLabels: LinearConnection<JsonObject>;
49
60
  }>(
50
61
  apiKey,
51
62
  `query ListIssueLabels(
@@ -69,6 +80,12 @@ export function issueLabelTools() {
69
80
  nodes {
70
81
  ${ISSUE_LABEL_SELECTION}
71
82
  }
83
+ pageInfo {
84
+ hasNextPage
85
+ hasPreviousPage
86
+ startCursor
87
+ endCursor
88
+ }
72
89
  }
73
90
  }`,
74
91
  variables,
@@ -76,12 +93,14 @@ export function issueLabelTools() {
76
93
  );
77
94
 
78
95
  const labels = data.issueLabels.nodes;
96
+ const pageInfo = data.issueLabels.pageInfo;
79
97
  return {
80
- content: [{ type: 'text', text: JSON.stringify({ labels }, null, 2) }],
81
- details: { labels },
98
+ content: [{ type: 'text', text: JSON.stringify({ labels, pageInfo }, null, 2) }],
99
+ details: { labels, pageInfo },
82
100
  };
83
101
  });
84
102
  },
103
+ renderResult: renderLinearIssueLabelListResult,
85
104
  }),
86
105
  defineTool({
87
106
  name: 'linear_create_issue_label',
@@ -100,6 +119,7 @@ export function issueLabelTools() {
100
119
  replaceTeamLabels: Type.Optional(Type.Boolean()),
101
120
  ...RawInputParam,
102
121
  }),
122
+ renderCall: renderLinearCreateIssueLabelCall,
103
123
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
104
124
  return withLinearAuth(ctx, signal, async (apiKey) => {
105
125
  const rawInput = asObject(params.input) || {};
@@ -166,6 +186,7 @@ export function issueLabelTools() {
166
186
  };
167
187
  });
168
188
  },
189
+ renderResult: renderLinearIssueLabelResult('Created issue label'),
169
190
  }),
170
191
  defineTool({
171
192
  name: 'linear_update_issue_label',
@@ -182,6 +203,7 @@ export function issueLabelTools() {
182
203
  replaceTeamLabels: Type.Optional(Type.Boolean()),
183
204
  ...RawInputParam,
184
205
  }),
206
+ renderCall: renderLinearUpdateIssueLabelCall,
185
207
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
186
208
  return withLinearAuth(ctx, signal, async (apiKey) => {
187
209
  const rawInput = asObject(params.input) || {};
@@ -235,6 +257,7 @@ export function issueLabelTools() {
235
257
  };
236
258
  });
237
259
  },
260
+ renderResult: renderLinearIssueLabelResult('Updated issue label'),
238
261
  }),
239
262
  defineTool({
240
263
  name: 'linear_delete_issue_label',
@@ -243,6 +266,7 @@ export function issueLabelTools() {
243
266
  parameters: Type.Object({
244
267
  id: Type.String(),
245
268
  }),
269
+ renderCall: renderLinearDeleteIssueLabelCall,
246
270
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
247
271
  return withLinearAuth(ctx, signal, async (apiKey) => {
248
272
  const data = await linearGraphQL<{
@@ -268,6 +292,7 @@ export function issueLabelTools() {
268
292
  };
269
293
  });
270
294
  },
295
+ renderResult: renderLinearIssueLabelDeleteResult,
271
296
  }),
272
297
  ];
273
298
  }
@@ -1,10 +1,19 @@
1
1
  import { defineTool } from '@mariozechner/pi-coding-agent';
2
2
  import { Type } from '@sinclair/typebox';
3
3
  import { withLinearAuth, linearGraphQL, resolveIssueId } from '../client';
4
- import { PaginationParams } from '../params';
4
+ import { PaginationParams, paginationVariables } from '../params';
5
5
  import { ISSUE_RELATION_SELECTION } from '../selections';
6
- import type { JsonObject } from '../types';
6
+ import type { JsonObject, LinearConnection } from '../types';
7
7
  import { compactObject } from '../util';
8
+ import {
9
+ renderLinearCreateIssueRelationCall,
10
+ renderLinearDeleteIssueRelationCall,
11
+ renderLinearDeleteIssueRelationResult,
12
+ renderLinearIssueRelationListCall,
13
+ renderLinearIssueRelationListResult,
14
+ renderLinearIssueRelationResult,
15
+ renderLinearUpdateIssueRelationCall,
16
+ } from '../renderers/issue-relations';
8
17
 
9
18
  export function issueRelationTools() {
10
19
  return [
@@ -15,19 +24,13 @@ export function issueRelationTools() {
15
24
  parameters: Type.Object({
16
25
  ...PaginationParams,
17
26
  }),
27
+ renderCall: renderLinearIssueRelationListCall,
18
28
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
19
29
  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
- });
30
+ const variables = paginationVariables(params, 20);
28
31
 
29
32
  const data = await linearGraphQL<{
30
- issueRelations: { nodes: Array<JsonObject> };
33
+ issueRelations: LinearConnection<JsonObject>;
31
34
  }>(
32
35
  apiKey,
33
36
  `query ListIssueRelations(
@@ -49,6 +52,12 @@ export function issueRelationTools() {
49
52
  nodes {
50
53
  ${ISSUE_RELATION_SELECTION}
51
54
  }
55
+ pageInfo {
56
+ hasNextPage
57
+ hasPreviousPage
58
+ startCursor
59
+ endCursor
60
+ }
52
61
  }
53
62
  }`,
54
63
  variables,
@@ -56,12 +65,16 @@ export function issueRelationTools() {
56
65
  );
57
66
 
58
67
  const issueRelations = data.issueRelations.nodes;
68
+ const pageInfo = data.issueRelations.pageInfo;
59
69
  return {
60
- content: [{ type: 'text', text: JSON.stringify({ issueRelations }, null, 2) }],
61
- details: { issueRelations },
70
+ content: [
71
+ { type: 'text', text: JSON.stringify({ issueRelations, pageInfo }, null, 2) },
72
+ ],
73
+ details: { issueRelations, pageInfo },
62
74
  };
63
75
  });
64
76
  },
77
+ renderResult: renderLinearIssueRelationListResult,
65
78
  }),
66
79
  defineTool({
67
80
  name: 'linear_create_issue_relation',
@@ -78,6 +91,7 @@ export function issueRelationTools() {
78
91
  description: 'Relation type: blocks, duplicate, related, or similar.',
79
92
  }),
80
93
  }),
94
+ renderCall: renderLinearCreateIssueRelationCall,
81
95
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
82
96
  return withLinearAuth(ctx, signal, async (apiKey) => {
83
97
  const input = {
@@ -116,6 +130,7 @@ export function issueRelationTools() {
116
130
  };
117
131
  });
118
132
  },
133
+ renderResult: renderLinearIssueRelationResult('Created issue relation'),
119
134
  }),
120
135
  defineTool({
121
136
  name: 'linear_update_issue_relation',
@@ -127,6 +142,7 @@ export function issueRelationTools() {
127
142
  issueId: Type.Optional(Type.String()),
128
143
  relatedIssueId: Type.Optional(Type.String()),
129
144
  }),
145
+ renderCall: renderLinearUpdateIssueRelationCall,
130
146
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
131
147
  return withLinearAuth(ctx, signal, async (apiKey) => {
132
148
  const [resolvedIssueId, resolvedRelatedIssueId] = await Promise.all([
@@ -176,6 +192,7 @@ export function issueRelationTools() {
176
192
  };
177
193
  });
178
194
  },
195
+ renderResult: renderLinearIssueRelationResult('Updated issue relation'),
179
196
  }),
180
197
  defineTool({
181
198
  name: 'linear_delete_issue_relation',
@@ -184,6 +201,7 @@ export function issueRelationTools() {
184
201
  parameters: Type.Object({
185
202
  id: Type.String(),
186
203
  }),
204
+ renderCall: renderLinearDeleteIssueRelationCall,
187
205
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
188
206
  return withLinearAuth(ctx, signal, async (apiKey) => {
189
207
  const data = await linearGraphQL<{
@@ -209,6 +227,7 @@ export function issueRelationTools() {
209
227
  };
210
228
  });
211
229
  },
230
+ renderResult: renderLinearDeleteIssueRelationResult,
212
231
  }),
213
232
  ];
214
233
  }