@alasano/pi-linear 0.2.0 → 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.
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,
@@ -31,17 +37,12 @@ export function documentTools() {
31
37
  async execute(_toolCallId, params, signal, _onUpdate, ctx) {
32
38
  return withLinearAuth(ctx, signal, async (apiKey) => {
33
39
  const variables = compactObject({
34
- after: params.after,
35
- before: params.before,
40
+ ...paginationVariables(params, 20),
36
41
  filter: asObject(params.filter),
37
- first: params.first ?? 20,
38
- includeArchived: params.includeArchived,
39
- last: params.last,
40
- orderBy: params.orderBy,
41
42
  });
42
43
 
43
44
  const data = await linearGraphQL<{
44
- documents: { nodes: Array<JsonObject> };
45
+ documents: LinearConnection<JsonObject>;
45
46
  }>(
46
47
  apiKey,
47
48
  `query ListDocuments(
@@ -65,6 +66,12 @@ export function documentTools() {
65
66
  nodes {
66
67
  ${DOCUMENT_SELECTION}
67
68
  }
69
+ pageInfo {
70
+ hasNextPage
71
+ hasPreviousPage
72
+ startCursor
73
+ endCursor
74
+ }
68
75
  }
69
76
  }`,
70
77
  variables,
@@ -72,9 +79,10 @@ export function documentTools() {
72
79
  );
73
80
 
74
81
  const documents = data.documents.nodes;
82
+ const pageInfo = data.documents.pageInfo;
75
83
  return {
76
- content: [{ type: 'text', text: JSON.stringify({ documents }, null, 2) }],
77
- details: { documents },
84
+ content: [{ type: 'text', text: JSON.stringify({ documents, pageInfo }, null, 2) }],
85
+ details: { documents, pageInfo },
78
86
  };
79
87
  });
80
88
  },
@@ -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.0",
4
4
  "description": "Linear integration for pi with 55+ tools, multi-workspace auth, and per-tool settings",
5
5
  "keywords": [
6
6
  "pi-package"