@alasano/pi-linear 0.2.0 → 0.3.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 CHANGED
@@ -110,6 +110,8 @@ Run `/linear-settings` to open an overlay where you can choose the default outpu
110
110
  | `linear_archive_project` | Archive or trash a project |
111
111
  | `linear_unarchive_project` | Restore an archived project |
112
112
 
113
+ List and search results include `pageInfo` in the JSON output for cursor pagination. Project list results use a bounded summary selection, and `linear_get_project` plus project save results include the project's markdown `content` body.
114
+
113
115
  ### Project Labels
114
116
 
115
117
  | Tool | Description |
@@ -1,5 +1,5 @@
1
1
  import { Type } from '@sinclair/typebox';
2
- import { GenericObjectSchema } from './util';
2
+ import { compactObject, GenericObjectSchema } from './util';
3
3
 
4
4
  export const PaginationParams = {
5
5
  after: Type.Optional(Type.String({ description: 'Pagination cursor.' })),
@@ -26,6 +26,45 @@ export const PaginationParams = {
26
26
  ),
27
27
  };
28
28
 
29
+ type PaginationVariableParams = {
30
+ after?: string;
31
+ before?: string;
32
+ first?: number;
33
+ includeArchived?: boolean;
34
+ last?: number;
35
+ orderBy?: string;
36
+ };
37
+
38
+ export function paginationVariables(
39
+ params: PaginationVariableParams,
40
+ defaultPageSize: number,
41
+ ): Partial<PaginationVariableParams> {
42
+ const hasForwardPagination = params.after !== undefined || params.first !== undefined;
43
+ const hasBackwardPagination = params.before !== undefined || params.last !== undefined;
44
+
45
+ if (hasForwardPagination && hasBackwardPagination) {
46
+ throw new Error(
47
+ 'Use either forward pagination (first/after) or backward pagination (last/before), not both.',
48
+ );
49
+ }
50
+
51
+ if (hasBackwardPagination) {
52
+ return compactObject({
53
+ before: params.before,
54
+ includeArchived: params.includeArchived,
55
+ last: params.last ?? defaultPageSize,
56
+ orderBy: params.orderBy,
57
+ });
58
+ }
59
+
60
+ return compactObject({
61
+ after: params.after,
62
+ first: params.first ?? defaultPageSize,
63
+ includeArchived: params.includeArchived,
64
+ orderBy: params.orderBy,
65
+ });
66
+ }
67
+
29
68
  export const SortParam = {
30
69
  sort: Type.Optional(Type.Array(GenericObjectSchema, { description: 'Sort input array.' })),
31
70
  };
@@ -39,6 +39,7 @@ type ProjectLike = {
39
39
  id?: string;
40
40
  name?: string | null;
41
41
  description?: string | null;
42
+ content?: string | null;
42
43
  state?: string | null;
43
44
  status?: { id?: string; name?: string | null } | null;
44
45
  priority?: number | null;
@@ -55,6 +56,12 @@ type ProjectLike = {
55
56
  type ProjectResultDetails = {
56
57
  project?: ProjectLike | null;
57
58
  projects?: ProjectLike[];
59
+ pageInfo?: {
60
+ hasNextPage?: boolean;
61
+ hasPreviousPage?: boolean;
62
+ startCursor?: string | null;
63
+ endCursor?: string | null;
64
+ };
58
65
  success?: boolean;
59
66
  };
60
67
 
@@ -94,7 +94,7 @@ export const ISSUE_LABEL_SELECTION = `
94
94
  }
95
95
  `;
96
96
 
97
- export const PROJECT_SELECTION = `
97
+ const PROJECT_BASE_SELECTION = `
98
98
  id
99
99
  name
100
100
  description
@@ -116,7 +116,7 @@ export const PROJECT_SELECTION = `
116
116
  createdAt
117
117
  updatedAt
118
118
  url
119
- teams {
119
+ teams(first: 10) {
120
120
  nodes {
121
121
  id
122
122
  key
@@ -128,7 +128,7 @@ export const PROJECT_SELECTION = `
128
128
  name
129
129
  email
130
130
  }
131
- members {
131
+ members(first: 10) {
132
132
  nodes {
133
133
  id
134
134
  name
@@ -141,6 +141,13 @@ export const PROJECT_SELECTION = `
141
141
  }
142
142
  `;
143
143
 
144
+ export const PROJECT_LIST_SELECTION = PROJECT_BASE_SELECTION;
145
+
146
+ export const PROJECT_DETAIL_SELECTION = `
147
+ ${PROJECT_BASE_SELECTION}
148
+ content
149
+ `;
150
+
144
151
  export const PROJECT_LABEL_SELECTION = `
145
152
  id
146
153
  name
@@ -1,9 +1,9 @@
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
8
  import {
9
9
  renderLinearCommentListCall,
@@ -29,17 +29,12 @@ export function commentTools() {
29
29
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
30
30
  return withLinearAuth(ctx, signal, async (apiKey) => {
31
31
  const variables = compactObject({
32
- after: params.after,
33
- before: params.before,
32
+ ...paginationVariables(params, 20),
34
33
  filter: asObject(params.filter),
35
- first: params.first ?? 20,
36
- includeArchived: params.includeArchived,
37
- last: params.last,
38
- orderBy: params.orderBy,
39
34
  });
40
35
 
41
36
  const data = await linearGraphQL<{
42
- comments: { nodes: Array<JsonObject> };
37
+ comments: LinearConnection<JsonObject>;
43
38
  }>(
44
39
  apiKey,
45
40
  `query ListComments(
@@ -63,6 +58,12 @@ export function commentTools() {
63
58
  nodes {
64
59
  ${COMMENT_SELECTION}
65
60
  }
61
+ pageInfo {
62
+ hasNextPage
63
+ hasPreviousPage
64
+ startCursor
65
+ endCursor
66
+ }
66
67
  }
67
68
  }`,
68
69
  variables,
@@ -70,9 +71,10 @@ export function commentTools() {
70
71
  );
71
72
 
72
73
  const comments = data.comments.nodes;
74
+ const pageInfo = data.comments.pageInfo;
73
75
  return {
74
- content: [{ type: 'text', text: JSON.stringify({ comments }, null, 2) }],
75
- details: { comments },
76
+ content: [{ type: 'text', text: JSON.stringify({ comments, pageInfo }, null, 2) }],
77
+ details: { comments, pageInfo },
76
78
  };
77
79
  });
78
80
  },
@@ -1,9 +1,15 @@
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';
8
14
  import {
9
15
  renderLinearCreateDocumentCall,
@@ -17,6 +23,36 @@ import {
17
23
  renderLinearUpdateDocumentCall,
18
24
  } from '../renderers/documents';
19
25
 
26
+ const DOCUMENT_RELATED_CONTEXT_FIELDS = [
27
+ 'cycleId',
28
+ 'initiativeId',
29
+ 'issueId',
30
+ 'projectId',
31
+ 'releaseId',
32
+ 'resourceFolderId',
33
+ ] as const;
34
+
35
+ type DocumentRelatedContextField = (typeof DOCUMENT_RELATED_CONTEXT_FIELDS)[number];
36
+
37
+ function hasDocumentRelatedContext(
38
+ params: Partial<Record<DocumentRelatedContextField, unknown>>,
39
+ rawInput: JsonObject,
40
+ ): boolean {
41
+ return DOCUMENT_RELATED_CONTEXT_FIELDS.some((field) =>
42
+ Boolean(asString(params[field]) || asString(rawInput[field])),
43
+ );
44
+ }
45
+
46
+ function documentInputBase(rawInput: JsonObject, omitTeamId: boolean): JsonObject {
47
+ if (!omitTeamId) return rawInput;
48
+
49
+ // Linear rejects document inputs with multiple parent/context IDs (for example
50
+ // issueId + teamId). Treat teamId/teamKey as the document context only when no
51
+ // more specific relation is provided; issue/project/etc. imply their context.
52
+ const { teamId: _teamId, ...inputWithoutTeamId } = rawInput;
53
+ return inputWithoutTeamId;
54
+ }
55
+
20
56
  export function documentTools() {
21
57
  return [
22
58
  defineTool({
@@ -31,17 +67,12 @@ export function documentTools() {
31
67
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
32
68
  return withLinearAuth(ctx, signal, async (apiKey) => {
33
69
  const variables = compactObject({
34
- after: params.after,
35
- before: params.before,
70
+ ...paginationVariables(params, 20),
36
71
  filter: asObject(params.filter),
37
- first: params.first ?? 20,
38
- includeArchived: params.includeArchived,
39
- last: params.last,
40
- orderBy: params.orderBy,
41
72
  });
42
73
 
43
74
  const data = await linearGraphQL<{
44
- documents: { nodes: Array<JsonObject> };
75
+ documents: LinearConnection<JsonObject>;
45
76
  }>(
46
77
  apiKey,
47
78
  `query ListDocuments(
@@ -65,6 +96,12 @@ export function documentTools() {
65
96
  nodes {
66
97
  ${DOCUMENT_SELECTION}
67
98
  }
99
+ pageInfo {
100
+ hasNextPage
101
+ hasPreviousPage
102
+ startCursor
103
+ endCursor
104
+ }
68
105
  }
69
106
  }`,
70
107
  variables,
@@ -72,9 +109,10 @@ export function documentTools() {
72
109
  );
73
110
 
74
111
  const documents = data.documents.nodes;
112
+ const pageInfo = data.documents.pageInfo;
75
113
  return {
76
- content: [{ type: 'text', text: JSON.stringify({ documents }, null, 2) }],
77
- details: { documents },
114
+ content: [{ type: 'text', text: JSON.stringify({ documents, pageInfo }, null, 2) }],
115
+ details: { documents, pageInfo },
78
116
  };
79
117
  });
80
118
  },
@@ -116,7 +154,7 @@ export function documentTools() {
116
154
  name: 'linear_create_document',
117
155
  label: 'Linear Create Document',
118
156
  description:
119
- 'Create a document. Supports top-level DocumentCreateInput fields and raw input.',
157
+ 'Create a document. Supports top-level DocumentCreateInput fields and raw input. Use issueId/projectId/etc. for related documents; teamId/teamKey are only for team-scoped documents.',
120
158
  parameters: Type.Object({
121
159
  color: Type.Optional(Type.String()),
122
160
  content: Type.Optional(Type.String()),
@@ -139,9 +177,10 @@ export function documentTools() {
139
177
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
140
178
  return withLinearAuth(ctx, signal, async (apiKey) => {
141
179
  const rawInput = asObject(params.input) || {};
142
- const rawInputTeamId = asString(rawInput.teamId);
180
+ const hasRelatedContext = hasDocumentRelatedContext(params, rawInput);
181
+ const rawInputTeamId = hasRelatedContext ? undefined : asString(rawInput.teamId);
143
182
  const teamId =
144
- params.teamId || params.teamKey || rawInputTeamId
183
+ !hasRelatedContext && (params.teamId || params.teamKey || rawInputTeamId)
145
184
  ? await resolveTeamId(
146
185
  apiKey,
147
186
  {
@@ -153,7 +192,7 @@ export function documentTools() {
153
192
  : undefined;
154
193
 
155
194
  const input = {
156
- ...rawInput,
195
+ ...documentInputBase(rawInput, hasRelatedContext),
157
196
  ...compactObject({
158
197
  color: params.color,
159
198
  content: params.content,
@@ -210,7 +249,7 @@ export function documentTools() {
210
249
  name: 'linear_update_document',
211
250
  label: 'Linear Update Document',
212
251
  description:
213
- 'Update a document by id. Supports top-level DocumentUpdateInput fields and raw input.',
252
+ 'Update a document by id. Supports top-level DocumentUpdateInput fields and raw input. Use issueId/projectId/etc. for related documents; teamId/teamKey are only for team-scoped documents.',
214
253
  parameters: Type.Object({
215
254
  documentId: Type.String(),
216
255
  color: Type.Optional(Type.String()),
@@ -235,9 +274,10 @@ export function documentTools() {
235
274
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
236
275
  return withLinearAuth(ctx, signal, async (apiKey) => {
237
276
  const rawInput = asObject(params.input) || {};
238
- const rawInputTeamId = asString(rawInput.teamId);
277
+ const hasRelatedContext = hasDocumentRelatedContext(params, rawInput);
278
+ const rawInputTeamId = hasRelatedContext ? undefined : asString(rawInput.teamId);
239
279
  const teamId =
240
- params.teamId || params.teamKey || rawInputTeamId
280
+ !hasRelatedContext && (params.teamId || params.teamKey || rawInputTeamId)
241
281
  ? await resolveTeamId(
242
282
  apiKey,
243
283
  {
@@ -249,7 +289,7 @@ export function documentTools() {
249
289
  : undefined;
250
290
 
251
291
  const input = {
252
- ...rawInput,
292
+ ...documentInputBase(rawInput, hasRelatedContext),
253
293
  ...compactObject({
254
294
  color: params.color,
255
295
  content: params.content,
@@ -1,9 +1,15 @@
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';
8
14
  import {
9
15
  renderLinearArchiveInitiativeCall,
@@ -33,18 +39,13 @@ export function initiativeTools() {
33
39
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
34
40
  return withLinearAuth(ctx, signal, async (apiKey) => {
35
41
  const variables = compactObject({
36
- after: params.after,
37
- before: params.before,
42
+ ...paginationVariables(params, 20),
38
43
  filter: asObject(params.filter),
39
- first: params.first ?? 20,
40
- includeArchived: params.includeArchived,
41
- last: params.last,
42
- orderBy: params.orderBy,
43
44
  sort: asObjectArray(params.sort),
44
45
  });
45
46
 
46
47
  const data = await linearGraphQL<{
47
- initiatives: { nodes: Array<JsonObject> };
48
+ initiatives: LinearConnection<JsonObject>;
48
49
  }>(
49
50
  apiKey,
50
51
  `query ListInitiatives(
@@ -70,6 +71,12 @@ export function initiativeTools() {
70
71
  nodes {
71
72
  ${INITIATIVE_SELECTION}
72
73
  }
74
+ pageInfo {
75
+ hasNextPage
76
+ hasPreviousPage
77
+ startCursor
78
+ endCursor
79
+ }
73
80
  }
74
81
  }`,
75
82
  variables,
@@ -77,9 +84,10 @@ export function initiativeTools() {
77
84
  );
78
85
 
79
86
  const initiatives = data.initiatives.nodes;
87
+ const pageInfo = data.initiatives.pageInfo;
80
88
  return {
81
- content: [{ type: 'text', text: JSON.stringify({ initiatives }, null, 2) }],
82
- details: { initiatives },
89
+ content: [{ type: 'text', text: JSON.stringify({ initiatives, pageInfo }, null, 2) }],
90
+ details: { initiatives, pageInfo },
83
91
  };
84
92
  });
85
93
  },
@@ -1,9 +1,15 @@
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';
8
14
  import {
9
15
  renderLinearCreateIssueLabelCall,
@@ -45,17 +51,12 @@ export function issueLabelTools() {
45
51
  const filter = mergeFilters(asObject(params.filter), convenienceFilter);
46
52
 
47
53
  const variables = compactObject({
48
- after: params.after,
49
- before: params.before,
54
+ ...paginationVariables(params, 50),
50
55
  filter,
51
- first: params.first ?? 50,
52
- includeArchived: params.includeArchived,
53
- last: params.last,
54
- orderBy: params.orderBy,
55
56
  });
56
57
 
57
58
  const data = await linearGraphQL<{
58
- issueLabels: { nodes: Array<JsonObject> };
59
+ issueLabels: LinearConnection<JsonObject>;
59
60
  }>(
60
61
  apiKey,
61
62
  `query ListIssueLabels(
@@ -79,6 +80,12 @@ export function issueLabelTools() {
79
80
  nodes {
80
81
  ${ISSUE_LABEL_SELECTION}
81
82
  }
83
+ pageInfo {
84
+ hasNextPage
85
+ hasPreviousPage
86
+ startCursor
87
+ endCursor
88
+ }
82
89
  }
83
90
  }`,
84
91
  variables,
@@ -86,9 +93,10 @@ export function issueLabelTools() {
86
93
  );
87
94
 
88
95
  const labels = data.issueLabels.nodes;
96
+ const pageInfo = data.issueLabels.pageInfo;
89
97
  return {
90
- content: [{ type: 'text', text: JSON.stringify({ labels }, null, 2) }],
91
- details: { labels },
98
+ content: [{ type: 'text', text: JSON.stringify({ labels, pageInfo }, null, 2) }],
99
+ details: { labels, pageInfo },
92
100
  };
93
101
  });
94
102
  },
@@ -1,9 +1,9 @@
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
8
  import {
9
9
  renderLinearCreateIssueRelationCall,
@@ -27,17 +27,10 @@ export function issueRelationTools() {
27
27
  renderCall: renderLinearIssueRelationListCall,
28
28
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
29
29
  return withLinearAuth(ctx, signal, async (apiKey) => {
30
- const variables = compactObject({
31
- after: params.after,
32
- before: params.before,
33
- first: params.first ?? 20,
34
- includeArchived: params.includeArchived,
35
- last: params.last,
36
- orderBy: params.orderBy,
37
- });
30
+ const variables = paginationVariables(params, 20);
38
31
 
39
32
  const data = await linearGraphQL<{
40
- issueRelations: { nodes: Array<JsonObject> };
33
+ issueRelations: LinearConnection<JsonObject>;
41
34
  }>(
42
35
  apiKey,
43
36
  `query ListIssueRelations(
@@ -59,6 +52,12 @@ export function issueRelationTools() {
59
52
  nodes {
60
53
  ${ISSUE_RELATION_SELECTION}
61
54
  }
55
+ pageInfo {
56
+ hasNextPage
57
+ hasPreviousPage
58
+ startCursor
59
+ endCursor
60
+ }
62
61
  }
63
62
  }`,
64
63
  variables,
@@ -66,9 +65,12 @@ export function issueRelationTools() {
66
65
  );
67
66
 
68
67
  const issueRelations = data.issueRelations.nodes;
68
+ const pageInfo = data.issueRelations.pageInfo;
69
69
  return {
70
- content: [{ type: 'text', text: JSON.stringify({ issueRelations }, null, 2) }],
71
- details: { issueRelations },
70
+ content: [
71
+ { type: 'text', text: JSON.stringify({ issueRelations, pageInfo }, null, 2) },
72
+ ],
73
+ details: { issueRelations, pageInfo },
72
74
  };
73
75
  });
74
76
  },
@@ -1,9 +1,9 @@
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 } from '../params';
4
+ import { PaginationParams, paginationVariables, FilterParam } from '../params';
5
5
  import { WORKFLOW_STATE_SELECTION } from '../selections';
6
- import type { JsonObject } from '../types';
6
+ import type { JsonObject, LinearConnection } from '../types';
7
7
  import { compactObject, asObject } from '../util';
8
8
  import {
9
9
  renderLinearIssueStatusListCall,
@@ -25,17 +25,12 @@ export function issueStatusTools() {
25
25
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
26
26
  return withLinearAuth(ctx, signal, async (apiKey) => {
27
27
  const variables = compactObject({
28
- after: params.after,
29
- before: params.before,
28
+ ...paginationVariables(params, 50),
30
29
  filter: asObject(params.filter),
31
- first: params.first ?? 50,
32
- includeArchived: params.includeArchived,
33
- last: params.last,
34
- orderBy: params.orderBy,
35
30
  });
36
31
 
37
32
  const data = await linearGraphQL<{
38
- workflowStates: { nodes: Array<JsonObject> };
33
+ workflowStates: LinearConnection<JsonObject>;
39
34
  }>(
40
35
  apiKey,
41
36
  `query ListIssueStatuses(
@@ -59,6 +54,12 @@ export function issueStatusTools() {
59
54
  nodes {
60
55
  ${WORKFLOW_STATE_SELECTION}
61
56
  }
57
+ pageInfo {
58
+ hasNextPage
59
+ hasPreviousPage
60
+ startCursor
61
+ endCursor
62
+ }
62
63
  }
63
64
  }`,
64
65
  variables,
@@ -66,9 +67,10 @@ export function issueStatusTools() {
66
67
  );
67
68
 
68
69
  const states = data.workflowStates.nodes;
70
+ const pageInfo = data.workflowStates.pageInfo;
69
71
  return {
70
- content: [{ type: 'text', text: JSON.stringify({ states }, null, 2) }],
71
- details: { states },
72
+ content: [{ type: 'text', text: JSON.stringify({ states, pageInfo }, null, 2) }],
73
+ details: { states, pageInfo },
72
74
  };
73
75
  });
74
76
  },
@@ -9,13 +9,14 @@ import {
9
9
  } from '../client';
10
10
  import {
11
11
  PaginationParams,
12
+ paginationVariables,
12
13
  FilterParam,
13
14
  SortParam,
14
15
  TeamConvenienceParams,
15
16
  RawInputParam,
16
17
  } from '../params';
17
18
  import { ISSUE_SELECTION } from '../selections';
18
- import type { LinearIssue, JsonObject } from '../types';
19
+ import type { LinearIssue, JsonObject, LinearConnection } from '../types';
19
20
  import { compactObject, asObject, asObjectArray, asString, mergeFilters } from '../util';
20
21
  import {
21
22
  renderLinearArchiveIssueCall,
@@ -79,17 +80,12 @@ export function issueTools() {
79
80
  );
80
81
 
81
82
  const variables = compactObject({
82
- after: params.after,
83
- before: params.before,
83
+ ...paginationVariables(params, 20),
84
84
  filter,
85
- first: params.first ?? 20,
86
- includeArchived: params.includeArchived,
87
- last: params.last,
88
- orderBy: params.orderBy,
89
85
  sort: asObjectArray(params.sort),
90
86
  });
91
87
 
92
- const data = await linearGraphQL<{ issues: { nodes: LinearIssue[] } }>(
88
+ const data = await linearGraphQL<{ issues: LinearConnection<LinearIssue> }>(
93
89
  apiKey,
94
90
  `query ListIssues(
95
91
  $after: String
@@ -114,6 +110,12 @@ export function issueTools() {
114
110
  nodes {
115
111
  ${ISSUE_SELECTION}
116
112
  }
113
+ pageInfo {
114
+ hasNextPage
115
+ hasPreviousPage
116
+ startCursor
117
+ endCursor
118
+ }
117
119
  }
118
120
  }`,
119
121
  variables,
@@ -121,9 +123,10 @@ export function issueTools() {
121
123
  );
122
124
 
123
125
  const issues = data.issues.nodes;
126
+ const pageInfo = data.issues.pageInfo;
124
127
  return {
125
- content: [{ type: 'text', text: JSON.stringify({ issues }, null, 2) }],
126
- details: { issues },
128
+ content: [{ type: 'text', text: JSON.stringify({ issues, pageInfo }, null, 2) }],
129
+ details: { issues, pageInfo },
127
130
  };
128
131
  });
129
132
  },
@@ -641,20 +644,15 @@ export function issueTools() {
641
644
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
642
645
  return withLinearAuth(ctx, signal, async (apiKey) => {
643
646
  const variables = compactObject({
647
+ ...paginationVariables(params, 20),
644
648
  term: params.term,
645
649
  includeComments: params.includeComments,
646
650
  teamId: params.teamId,
647
- after: params.after,
648
- before: params.before,
649
651
  filter: asObject(params.filter),
650
- first: params.first ?? 20,
651
- includeArchived: params.includeArchived,
652
- last: params.last,
653
- orderBy: params.orderBy,
654
652
  });
655
653
 
656
654
  const data = await linearGraphQL<{
657
- searchIssues: { nodes: LinearIssue[] };
655
+ searchIssues: LinearConnection<LinearIssue>;
658
656
  }>(
659
657
  apiKey,
660
658
  `query SearchIssues(
@@ -684,6 +682,12 @@ export function issueTools() {
684
682
  nodes {
685
683
  ${ISSUE_SELECTION}
686
684
  }
685
+ pageInfo {
686
+ hasNextPage
687
+ hasPreviousPage
688
+ startCursor
689
+ endCursor
690
+ }
687
691
  }
688
692
  }`,
689
693
  variables,
@@ -691,9 +695,10 @@ export function issueTools() {
691
695
  );
692
696
 
693
697
  const issues = data.searchIssues.nodes;
698
+ const pageInfo = data.searchIssues.pageInfo;
694
699
  return {
695
- content: [{ type: 'text', text: JSON.stringify({ issues }, null, 2) }],
696
- details: { issues },
700
+ content: [{ type: 'text', text: JSON.stringify({ issues, pageInfo }, null, 2) }],
701
+ details: { issues, pageInfo },
697
702
  };
698
703
  });
699
704
  },
@@ -1,9 +1,9 @@
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 { MILESTONE_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
8
  import {
9
9
  renderLinearMilestoneDeleteCall,
@@ -30,17 +30,12 @@ export function milestoneTools() {
30
30
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
31
31
  return withLinearAuth(ctx, signal, async (apiKey) => {
32
32
  const variables = compactObject({
33
- after: params.after,
34
- before: params.before,
33
+ ...paginationVariables(params, 20),
35
34
  filter: asObject(params.filter),
36
- first: params.first ?? 20,
37
- includeArchived: params.includeArchived,
38
- last: params.last,
39
- orderBy: params.orderBy,
40
35
  });
41
36
 
42
37
  const data = await linearGraphQL<{
43
- projectMilestones: { nodes: Array<JsonObject> };
38
+ projectMilestones: LinearConnection<JsonObject>;
44
39
  }>(
45
40
  apiKey,
46
41
  `query ListMilestones(
@@ -64,6 +59,12 @@ export function milestoneTools() {
64
59
  nodes {
65
60
  ${MILESTONE_SELECTION}
66
61
  }
62
+ pageInfo {
63
+ hasNextPage
64
+ hasPreviousPage
65
+ startCursor
66
+ endCursor
67
+ }
67
68
  }
68
69
  }`,
69
70
  variables,
@@ -71,9 +72,10 @@ export function milestoneTools() {
71
72
  );
72
73
 
73
74
  const milestones = data.projectMilestones.nodes;
75
+ const pageInfo = data.projectMilestones.pageInfo;
74
76
  return {
75
- content: [{ type: 'text', text: JSON.stringify({ milestones }, null, 2) }],
76
- details: { milestones },
77
+ content: [{ type: 'text', text: JSON.stringify({ milestones, pageInfo }, null, 2) }],
78
+ details: { milestones, pageInfo },
77
79
  };
78
80
  });
79
81
  },
@@ -1,9 +1,9 @@
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 { PROJECT_LABEL_SELECTION } from '../selections';
6
- import type { JsonObject } from '../types';
6
+ import type { JsonObject, LinearConnection } from '../types';
7
7
  import { compactObject, asObject, asString } from '../util';
8
8
  import {
9
9
  renderLinearCreateProjectLabelCall,
@@ -29,17 +29,12 @@ export function projectLabelTools() {
29
29
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
30
30
  return withLinearAuth(ctx, signal, async (apiKey) => {
31
31
  const variables = compactObject({
32
- after: params.after,
33
- before: params.before,
32
+ ...paginationVariables(params, 50),
34
33
  filter: asObject(params.filter),
35
- first: params.first ?? 50,
36
- includeArchived: params.includeArchived,
37
- last: params.last,
38
- orderBy: params.orderBy,
39
34
  });
40
35
 
41
36
  const data = await linearGraphQL<{
42
- projectLabels: { nodes: Array<JsonObject> };
37
+ projectLabels: LinearConnection<JsonObject>;
43
38
  }>(
44
39
  apiKey,
45
40
  `query ListProjectLabels(
@@ -63,6 +58,12 @@ export function projectLabelTools() {
63
58
  nodes {
64
59
  ${PROJECT_LABEL_SELECTION}
65
60
  }
61
+ pageInfo {
62
+ hasNextPage
63
+ hasPreviousPage
64
+ startCursor
65
+ endCursor
66
+ }
66
67
  }
67
68
  }`,
68
69
  variables,
@@ -70,9 +71,10 @@ export function projectLabelTools() {
70
71
  );
71
72
 
72
73
  const labels = data.projectLabels.nodes;
74
+ const pageInfo = data.projectLabels.pageInfo;
73
75
  return {
74
- content: [{ type: 'text', text: JSON.stringify({ labels }, null, 2) }],
75
- details: { labels },
76
+ content: [{ type: 'text', text: JSON.stringify({ labels, pageInfo }, null, 2) }],
77
+ details: { labels, pageInfo },
76
78
  };
77
79
  });
78
80
  },
@@ -1,9 +1,9 @@
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 } from '../params';
4
+ import { PaginationParams, paginationVariables } from '../params';
5
5
  import { PROJECT_RELATION_SELECTION } from '../selections';
6
- import type { JsonObject } from '../types';
6
+ import type { JsonObject, LinearConnection } from '../types';
7
7
  import { compactObject } from '../util';
8
8
  import {
9
9
  renderLinearCreateProjectRelationCall,
@@ -27,17 +27,10 @@ export function projectRelationTools() {
27
27
  renderCall: renderLinearProjectRelationListCall,
28
28
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
29
29
  return withLinearAuth(ctx, signal, async (apiKey) => {
30
- const variables = compactObject({
31
- after: params.after,
32
- before: params.before,
33
- first: params.first ?? 20,
34
- includeArchived: params.includeArchived,
35
- last: params.last,
36
- orderBy: params.orderBy,
37
- });
30
+ const variables = paginationVariables(params, 20);
38
31
 
39
32
  const data = await linearGraphQL<{
40
- projectRelations: { nodes: Array<JsonObject> };
33
+ projectRelations: LinearConnection<JsonObject>;
41
34
  }>(
42
35
  apiKey,
43
36
  `query ListProjectRelations(
@@ -59,6 +52,12 @@ export function projectRelationTools() {
59
52
  nodes {
60
53
  ${PROJECT_RELATION_SELECTION}
61
54
  }
55
+ pageInfo {
56
+ hasNextPage
57
+ hasPreviousPage
58
+ startCursor
59
+ endCursor
60
+ }
62
61
  }
63
62
  }`,
64
63
  variables,
@@ -66,9 +65,12 @@ export function projectRelationTools() {
66
65
  );
67
66
 
68
67
  const projectRelations = data.projectRelations.nodes;
68
+ const pageInfo = data.projectRelations.pageInfo;
69
69
  return {
70
- content: [{ type: 'text', text: JSON.stringify({ projectRelations }, null, 2) }],
71
- details: { projectRelations },
70
+ content: [
71
+ { type: 'text', text: JSON.stringify({ projectRelations, pageInfo }, null, 2) },
72
+ ],
73
+ details: { projectRelations, pageInfo },
72
74
  };
73
75
  });
74
76
  },
@@ -1,9 +1,15 @@
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';
5
- import { PROJECT_SELECTION } from '../selections';
6
- import type { JsonObject } from '../types';
4
+ import {
5
+ PaginationParams,
6
+ paginationVariables,
7
+ FilterParam,
8
+ SortParam,
9
+ RawInputParam,
10
+ } from '../params';
11
+ import { PROJECT_DETAIL_SELECTION, PROJECT_LIST_SELECTION } from '../selections';
12
+ import type { JsonObject, LinearConnection } from '../types';
7
13
  import { compactObject, asObject, asObjectArray, asString } from '../util';
8
14
  import {
9
15
  renderLinearArchiveProjectCall,
@@ -33,18 +39,13 @@ export function projectTools() {
33
39
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
34
40
  return withLinearAuth(ctx, signal, async (apiKey) => {
35
41
  const variables = compactObject({
36
- after: params.after,
37
- before: params.before,
42
+ ...paginationVariables(params, 20),
38
43
  filter: asObject(params.filter),
39
- first: params.first ?? 20,
40
- includeArchived: params.includeArchived,
41
- last: params.last,
42
- orderBy: params.orderBy,
43
44
  sort: asObjectArray(params.sort),
44
45
  });
45
46
 
46
47
  const data = await linearGraphQL<{
47
- projects: { nodes: Array<JsonObject> };
48
+ projects: LinearConnection<JsonObject>;
48
49
  }>(
49
50
  apiKey,
50
51
  `query ListProjects(
@@ -68,7 +69,13 @@ export function projectTools() {
68
69
  sort: $sort
69
70
  ) {
70
71
  nodes {
71
- ${PROJECT_SELECTION}
72
+ ${PROJECT_LIST_SELECTION}
73
+ }
74
+ pageInfo {
75
+ hasNextPage
76
+ hasPreviousPage
77
+ startCursor
78
+ endCursor
72
79
  }
73
80
  }
74
81
  }`,
@@ -77,9 +84,10 @@ export function projectTools() {
77
84
  );
78
85
 
79
86
  const projects = data.projects.nodes;
87
+ const pageInfo = data.projects.pageInfo;
80
88
  return {
81
- content: [{ type: 'text', text: JSON.stringify({ projects }, null, 2) }],
82
- details: { projects },
89
+ content: [{ type: 'text', text: JSON.stringify({ projects, pageInfo }, null, 2) }],
90
+ details: { projects, pageInfo },
83
91
  };
84
92
  });
85
93
  },
@@ -99,7 +107,7 @@ export function projectTools() {
99
107
  apiKey,
100
108
  `query GetProject($id: String!) {
101
109
  project(id: $id) {
102
- ${PROJECT_SELECTION}
110
+ ${PROJECT_DETAIL_SELECTION}
103
111
  }
104
112
  }`,
105
113
  { id: params.projectId },
@@ -220,7 +228,7 @@ export function projectTools() {
220
228
  projectUpdate(id: $id, input: $input) {
221
229
  success
222
230
  project {
223
- ${PROJECT_SELECTION}
231
+ ${PROJECT_DETAIL_SELECTION}
224
232
  }
225
233
  }
226
234
  }`,
@@ -255,7 +263,7 @@ export function projectTools() {
255
263
  projectCreate(input: $input, slackChannelName: $slackChannelName) {
256
264
  success
257
265
  project {
258
- ${PROJECT_SELECTION}
266
+ ${PROJECT_DETAIL_SELECTION}
259
267
  }
260
268
  }
261
269
  }`,
@@ -1,9 +1,9 @@
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 } from '../params';
4
+ import { PaginationParams, paginationVariables, FilterParam } from '../params';
5
5
  import { TEAM_SELECTION } from '../selections';
6
- import type { LinearTeam, JsonObject } from '../types';
6
+ import type { LinearTeam, JsonObject, LinearConnection } from '../types';
7
7
  import { compactObject, asObject } from '../util';
8
8
  import {
9
9
  renderLinearGetTeamCall,
@@ -27,16 +27,11 @@ export function teamTools() {
27
27
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
28
28
  return withLinearAuth(ctx, signal, async (apiKey) => {
29
29
  const variables = compactObject({
30
- after: params.after,
31
- before: params.before,
30
+ ...paginationVariables(params, 50),
32
31
  filter: asObject(params.filter),
33
- first: params.first,
34
- includeArchived: params.includeArchived,
35
- last: params.last,
36
- orderBy: params.orderBy,
37
32
  });
38
33
 
39
- const data = await linearGraphQL<{ teams: { nodes: LinearTeam[] } }>(
34
+ const data = await linearGraphQL<{ teams: LinearConnection<LinearTeam> }>(
40
35
  apiKey,
41
36
  `query ListTeams(
42
37
  $after: String
@@ -66,6 +61,12 @@ export function teamTools() {
66
61
  }
67
62
  }
68
63
  }
64
+ pageInfo {
65
+ hasNextPage
66
+ hasPreviousPage
67
+ startCursor
68
+ endCursor
69
+ }
69
70
  }
70
71
  }`,
71
72
  variables,
@@ -73,9 +74,10 @@ export function teamTools() {
73
74
  );
74
75
 
75
76
  const teams = data.teams.nodes;
77
+ const pageInfo = data.teams.pageInfo;
76
78
  return {
77
- content: [{ type: 'text', text: JSON.stringify({ teams }, null, 2) }],
78
- details: { teams },
79
+ content: [{ type: 'text', text: JSON.stringify({ teams, pageInfo }, null, 2) }],
80
+ details: { teams, pageInfo },
79
81
  };
80
82
  });
81
83
  },
@@ -1,9 +1,9 @@
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 } from '../params';
4
+ import { PaginationParams, paginationVariables, FilterParam, SortParam } from '../params';
5
5
  import { USER_SELECTION } from '../selections';
6
- import type { JsonObject } from '../types';
6
+ import type { JsonObject, LinearConnection } from '../types';
7
7
  import { compactObject, asObject, asObjectArray } from '../util';
8
8
  import {
9
9
  renderLinearGetUserCall,
@@ -28,19 +28,14 @@ export function userTools() {
28
28
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
29
29
  return withLinearAuth(ctx, signal, async (apiKey) => {
30
30
  const variables = compactObject({
31
- after: params.after,
32
- before: params.before,
31
+ ...paginationVariables(params, 50),
33
32
  filter: asObject(params.filter),
34
- first: params.first ?? 50,
35
- includeArchived: params.includeArchived,
36
33
  includeDisabled: params.includeDisabled,
37
- last: params.last,
38
- orderBy: params.orderBy,
39
34
  sort: asObjectArray(params.sort),
40
35
  });
41
36
 
42
37
  const data = await linearGraphQL<{
43
- users: { nodes: Array<JsonObject> };
38
+ users: LinearConnection<JsonObject>;
44
39
  }>(
45
40
  apiKey,
46
41
  `query ListUsers(
@@ -68,6 +63,12 @@ export function userTools() {
68
63
  nodes {
69
64
  ${USER_SELECTION}
70
65
  }
66
+ pageInfo {
67
+ hasNextPage
68
+ hasPreviousPage
69
+ startCursor
70
+ endCursor
71
+ }
71
72
  }
72
73
  }`,
73
74
  variables,
@@ -75,9 +76,10 @@ export function userTools() {
75
76
  );
76
77
 
77
78
  const users = data.users.nodes;
79
+ const pageInfo = data.users.pageInfo;
78
80
  return {
79
- content: [{ type: 'text', text: JSON.stringify({ users }, null, 2) }],
80
- details: { users },
81
+ content: [{ type: 'text', text: JSON.stringify({ users, pageInfo }, null, 2) }],
82
+ details: { users, pageInfo },
81
83
  };
82
84
  });
83
85
  },
@@ -4,6 +4,18 @@ export type LinearGraphQLError = {
4
4
  message: string;
5
5
  };
6
6
 
7
+ export type LinearPageInfo = {
8
+ hasNextPage: boolean;
9
+ hasPreviousPage: boolean;
10
+ startCursor?: string | null;
11
+ endCursor?: string | null;
12
+ };
13
+
14
+ export type LinearConnection<T> = {
15
+ nodes: T[];
16
+ pageInfo: LinearPageInfo;
17
+ };
18
+
7
19
  export type LinearIssue = {
8
20
  id: string;
9
21
  identifier: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alasano/pi-linear",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Linear integration for pi with 55+ tools, multi-workspace auth, and per-tool settings",
5
5
  "keywords": [
6
6
  "pi-package"