@hasna/connectors 0.3.16 → 0.4.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/bin/index.js +71 -1
- package/bin/mcp.js +71 -1
- package/bin/serve.js +70 -0
- package/connectors/connect-asana/.env.example +11 -0
- package/connectors/connect-asana/CLAUDE.md +128 -0
- package/connectors/connect-asana/README.md +193 -0
- package/connectors/connect-asana/package.json +52 -0
- package/connectors/connect-asana/src/api/client.ts +119 -0
- package/connectors/connect-asana/src/api/index.ts +319 -0
- package/connectors/connect-asana/src/cli/index.ts +731 -0
- package/connectors/connect-asana/src/index.ts +19 -0
- package/connectors/connect-asana/src/types/index.ts +270 -0
- package/connectors/connect-asana/src/utils/config.ts +171 -0
- package/connectors/connect-asana/src/utils/output.ts +119 -0
- package/connectors/connect-asana/tsconfig.json +16 -0
- package/connectors/connect-clickup/.env.example +11 -0
- package/connectors/connect-clickup/CLAUDE.md +128 -0
- package/connectors/connect-clickup/README.md +193 -0
- package/connectors/connect-clickup/package.json +52 -0
- package/connectors/connect-clickup/src/api/client.ts +116 -0
- package/connectors/connect-clickup/src/api/index.ts +400 -0
- package/connectors/connect-clickup/src/cli/index.ts +625 -0
- package/connectors/connect-clickup/src/index.ts +19 -0
- package/connectors/connect-clickup/src/types/index.ts +591 -0
- package/connectors/connect-clickup/src/utils/config.ts +157 -0
- package/connectors/connect-clickup/src/utils/output.ts +119 -0
- package/connectors/connect-clickup/tsconfig.json +16 -0
- package/connectors/connect-confluence/.env.example +11 -0
- package/connectors/connect-confluence/CLAUDE.md +272 -0
- package/connectors/connect-confluence/README.md +193 -0
- package/connectors/connect-confluence/package.json +53 -0
- package/connectors/connect-confluence/scripts/release.ts +179 -0
- package/connectors/connect-confluence/src/api/client.ts +213 -0
- package/connectors/connect-confluence/src/api/example.ts +48 -0
- package/connectors/connect-confluence/src/api/index.ts +51 -0
- package/connectors/connect-confluence/src/cli/index.ts +254 -0
- package/connectors/connect-confluence/src/index.ts +103 -0
- package/connectors/connect-confluence/src/types/index.ts +237 -0
- package/connectors/connect-confluence/src/utils/auth.ts +274 -0
- package/connectors/connect-confluence/src/utils/bulk.ts +212 -0
- package/connectors/connect-confluence/src/utils/config.ts +326 -0
- package/connectors/connect-confluence/src/utils/output.ts +175 -0
- package/connectors/connect-confluence/src/utils/settings.ts +114 -0
- package/connectors/connect-confluence/src/utils/storage.ts +198 -0
- package/connectors/connect-confluence/tsconfig.json +16 -0
- package/connectors/connect-jira/.env.example +11 -0
- package/connectors/connect-jira/CLAUDE.md +128 -0
- package/connectors/connect-jira/README.md +193 -0
- package/connectors/connect-jira/package.json +53 -0
- package/connectors/connect-jira/src/api/client.ts +131 -0
- package/connectors/connect-jira/src/api/index.ts +266 -0
- package/connectors/connect-jira/src/cli/index.ts +653 -0
- package/connectors/connect-jira/src/index.ts +23 -0
- package/connectors/connect-jira/src/types/index.ts +448 -0
- package/connectors/connect-jira/src/utils/config.ts +179 -0
- package/connectors/connect-jira/src/utils/output.ts +119 -0
- package/connectors/connect-jira/tsconfig.json +16 -0
- package/connectors/connect-linear/CLAUDE.md +88 -0
- package/connectors/connect-linear/README.md +201 -0
- package/connectors/connect-linear/package.json +45 -0
- package/connectors/connect-linear/src/api/client.ts +62 -0
- package/connectors/connect-linear/src/api/index.ts +46 -0
- package/connectors/connect-linear/src/api/issues.ts +247 -0
- package/connectors/connect-linear/src/api/projects.ts +179 -0
- package/connectors/connect-linear/src/api/teams.ts +125 -0
- package/connectors/connect-linear/src/api/users.ts +112 -0
- package/connectors/connect-linear/src/cli/index.ts +560 -0
- package/connectors/connect-linear/src/index.ts +27 -0
- package/connectors/connect-linear/src/types/index.ts +275 -0
- package/connectors/connect-linear/src/utils/config.ts +249 -0
- package/connectors/connect-linear/src/utils/output.ts +119 -0
- package/connectors/connect-linear/tsconfig.json +16 -0
- package/connectors/connect-slack/.env.example +7 -0
- package/connectors/connect-slack/CLAUDE.md +69 -0
- package/connectors/connect-slack/README.md +150 -0
- package/connectors/connect-slack/package.json +44 -0
- package/connectors/connect-slack/src/api/channels.ts +112 -0
- package/connectors/connect-slack/src/api/client.ts +97 -0
- package/connectors/connect-slack/src/api/index.ts +42 -0
- package/connectors/connect-slack/src/api/messages.ts +127 -0
- package/connectors/connect-slack/src/api/users.ts +110 -0
- package/connectors/connect-slack/src/cli/index.ts +494 -0
- package/connectors/connect-slack/src/index.ts +21 -0
- package/connectors/connect-slack/src/types/index.ts +263 -0
- package/connectors/connect-slack/src/utils/config.ts +297 -0
- package/connectors/connect-slack/src/utils/output.ts +119 -0
- package/connectors/connect-slack/tsconfig.json +16 -0
- package/connectors/connect-telegram/.env.example +2 -0
- package/connectors/connect-telegram/package.json +49 -0
- package/connectors/connect-todoist/.env.example +11 -0
- package/connectors/connect-todoist/CLAUDE.md +104 -0
- package/connectors/connect-todoist/README.md +193 -0
- package/connectors/connect-todoist/package.json +52 -0
- package/connectors/connect-todoist/src/api/client.ts +117 -0
- package/connectors/connect-todoist/src/api/index.ts +188 -0
- package/connectors/connect-todoist/src/cli/index.ts +990 -0
- package/connectors/connect-todoist/src/index.ts +21 -0
- package/connectors/connect-todoist/src/types/index.ts +240 -0
- package/connectors/connect-todoist/src/utils/config.ts +157 -0
- package/connectors/connect-todoist/src/utils/output.ts +119 -0
- package/connectors/connect-todoist/tsconfig.json +16 -0
- package/connectors/connect-trello/.env.example +11 -0
- package/connectors/connect-trello/CLAUDE.md +128 -0
- package/connectors/connect-trello/README.md +193 -0
- package/connectors/connect-trello/package.json +53 -0
- package/connectors/connect-trello/src/api/client.ts +128 -0
- package/connectors/connect-trello/src/api/index.ts +278 -0
- package/connectors/connect-trello/src/cli/index.ts +737 -0
- package/connectors/connect-trello/src/index.ts +21 -0
- package/connectors/connect-trello/src/types/index.ts +314 -0
- package/connectors/connect-trello/src/utils/config.ts +182 -0
- package/connectors/connect-trello/src/utils/output.ts +119 -0
- package/connectors/connect-trello/tsconfig.json +16 -0
- package/connectors/connect-whatsapp/.env.example +11 -0
- package/connectors/connect-whatsapp/CLAUDE.md +113 -0
- package/connectors/connect-whatsapp/README.md +193 -0
- package/connectors/connect-whatsapp/package.json +53 -0
- package/connectors/connect-whatsapp/src/api/client.ts +133 -0
- package/connectors/connect-whatsapp/src/api/index.ts +365 -0
- package/connectors/connect-whatsapp/src/cli/index.ts +686 -0
- package/connectors/connect-whatsapp/src/index.ts +25 -0
- package/connectors/connect-whatsapp/src/types/index.ts +502 -0
- package/connectors/connect-whatsapp/src/utils/config.ts +179 -0
- package/connectors/connect-whatsapp/src/utils/output.ts +119 -0
- package/connectors/connect-whatsapp/tsconfig.json +16 -0
- package/dist/index.js +70 -0
- package/package.json +1 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import type { LinearClient } from './client';
|
|
2
|
+
import type {
|
|
3
|
+
LinearIssue,
|
|
4
|
+
IssueListOptions,
|
|
5
|
+
CreateIssueInput,
|
|
6
|
+
UpdateIssueInput,
|
|
7
|
+
IssuesResponse,
|
|
8
|
+
IssueResponse,
|
|
9
|
+
CreateIssueResponse,
|
|
10
|
+
UpdateIssueResponse,
|
|
11
|
+
ArchiveIssueResponse,
|
|
12
|
+
IssueFilter,
|
|
13
|
+
} from '../types';
|
|
14
|
+
|
|
15
|
+
const ISSUE_FRAGMENT = `
|
|
16
|
+
fragment IssueFragment on Issue {
|
|
17
|
+
id
|
|
18
|
+
identifier
|
|
19
|
+
title
|
|
20
|
+
description
|
|
21
|
+
priority
|
|
22
|
+
priorityLabel
|
|
23
|
+
estimate
|
|
24
|
+
sortOrder
|
|
25
|
+
boardOrder
|
|
26
|
+
startedAt
|
|
27
|
+
completedAt
|
|
28
|
+
canceledAt
|
|
29
|
+
dueDate
|
|
30
|
+
createdAt
|
|
31
|
+
updatedAt
|
|
32
|
+
archivedAt
|
|
33
|
+
url
|
|
34
|
+
number
|
|
35
|
+
team {
|
|
36
|
+
id
|
|
37
|
+
name
|
|
38
|
+
key
|
|
39
|
+
}
|
|
40
|
+
state {
|
|
41
|
+
id
|
|
42
|
+
name
|
|
43
|
+
color
|
|
44
|
+
type
|
|
45
|
+
}
|
|
46
|
+
assignee {
|
|
47
|
+
id
|
|
48
|
+
name
|
|
49
|
+
displayName
|
|
50
|
+
email
|
|
51
|
+
}
|
|
52
|
+
creator {
|
|
53
|
+
id
|
|
54
|
+
name
|
|
55
|
+
displayName
|
|
56
|
+
}
|
|
57
|
+
labels {
|
|
58
|
+
nodes {
|
|
59
|
+
id
|
|
60
|
+
name
|
|
61
|
+
color
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
project {
|
|
65
|
+
id
|
|
66
|
+
name
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
|
|
71
|
+
export class IssuesApi {
|
|
72
|
+
constructor(private readonly client: LinearClient) {}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* List issues with optional filters
|
|
76
|
+
*/
|
|
77
|
+
async list(options: IssueListOptions = {}): Promise<LinearIssue[]> {
|
|
78
|
+
const { first = 50, after, teamId, projectId, assigneeId, filter } = options;
|
|
79
|
+
|
|
80
|
+
// Build filter object
|
|
81
|
+
const filterObj: IssueFilter = filter || {};
|
|
82
|
+
if (teamId) {
|
|
83
|
+
filterObj.team = { id: { eq: teamId } };
|
|
84
|
+
}
|
|
85
|
+
if (projectId) {
|
|
86
|
+
filterObj.project = { id: { eq: projectId } };
|
|
87
|
+
}
|
|
88
|
+
if (assigneeId) {
|
|
89
|
+
filterObj.assignee = { id: { eq: assigneeId } };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const query = `
|
|
93
|
+
${ISSUE_FRAGMENT}
|
|
94
|
+
query Issues($first: Int, $after: String, $filter: IssueFilter) {
|
|
95
|
+
issues(first: $first, after: $after, filter: $filter) {
|
|
96
|
+
nodes {
|
|
97
|
+
...IssueFragment
|
|
98
|
+
}
|
|
99
|
+
pageInfo {
|
|
100
|
+
hasNextPage
|
|
101
|
+
endCursor
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
|
|
107
|
+
const result = await this.client.query<IssuesResponse>(query, {
|
|
108
|
+
first,
|
|
109
|
+
after,
|
|
110
|
+
filter: Object.keys(filterObj).length > 0 ? filterObj : undefined,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
return result.issues.nodes;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get a single issue by ID
|
|
118
|
+
*/
|
|
119
|
+
async get(id: string): Promise<LinearIssue> {
|
|
120
|
+
const query = `
|
|
121
|
+
${ISSUE_FRAGMENT}
|
|
122
|
+
query Issue($id: String!) {
|
|
123
|
+
issue(id: $id) {
|
|
124
|
+
...IssueFragment
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
`;
|
|
128
|
+
|
|
129
|
+
const result = await this.client.query<IssueResponse>(query, { id });
|
|
130
|
+
return result.issue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Create a new issue
|
|
135
|
+
*/
|
|
136
|
+
async create(input: CreateIssueInput): Promise<LinearIssue> {
|
|
137
|
+
const mutation = `
|
|
138
|
+
${ISSUE_FRAGMENT}
|
|
139
|
+
mutation CreateIssue($input: IssueCreateInput!) {
|
|
140
|
+
issueCreate(input: $input) {
|
|
141
|
+
success
|
|
142
|
+
issue {
|
|
143
|
+
...IssueFragment
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
`;
|
|
148
|
+
|
|
149
|
+
const result = await this.client.mutate<CreateIssueResponse>(mutation, {
|
|
150
|
+
input,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
if (!result.issueCreate.success || !result.issueCreate.issue) {
|
|
154
|
+
throw new Error('Failed to create issue');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return result.issueCreate.issue;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Update an existing issue
|
|
162
|
+
*/
|
|
163
|
+
async update(id: string, input: UpdateIssueInput): Promise<LinearIssue> {
|
|
164
|
+
const mutation = `
|
|
165
|
+
${ISSUE_FRAGMENT}
|
|
166
|
+
mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
|
|
167
|
+
issueUpdate(id: $id, input: $input) {
|
|
168
|
+
success
|
|
169
|
+
issue {
|
|
170
|
+
...IssueFragment
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
const result = await this.client.mutate<UpdateIssueResponse>(mutation, {
|
|
177
|
+
id,
|
|
178
|
+
input,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
if (!result.issueUpdate.success || !result.issueUpdate.issue) {
|
|
182
|
+
throw new Error('Failed to update issue');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return result.issueUpdate.issue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Archive an issue (soft delete)
|
|
190
|
+
*/
|
|
191
|
+
async archive(id: string): Promise<boolean> {
|
|
192
|
+
const mutation = `
|
|
193
|
+
mutation ArchiveIssue($id: String!) {
|
|
194
|
+
issueArchive(id: $id) {
|
|
195
|
+
success
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
`;
|
|
199
|
+
|
|
200
|
+
const result = await this.client.mutate<ArchiveIssueResponse>(mutation, { id });
|
|
201
|
+
return result.issueArchive.success;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Change issue state by state ID
|
|
206
|
+
*/
|
|
207
|
+
async changeState(id: string, stateId: string): Promise<LinearIssue> {
|
|
208
|
+
return this.update(id, { stateId });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Assign issue to a user
|
|
213
|
+
*/
|
|
214
|
+
async assign(id: string, assigneeId: string): Promise<LinearIssue> {
|
|
215
|
+
return this.update(id, { assigneeId });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Add issue to a project
|
|
220
|
+
*/
|
|
221
|
+
async addToProject(id: string, projectId: string): Promise<LinearIssue> {
|
|
222
|
+
return this.update(id, { projectId });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Search issues by query text
|
|
227
|
+
*/
|
|
228
|
+
async search(query: string, first = 20): Promise<LinearIssue[]> {
|
|
229
|
+
const gqlQuery = `
|
|
230
|
+
${ISSUE_FRAGMENT}
|
|
231
|
+
query SearchIssues($query: String!, $first: Int) {
|
|
232
|
+
issueSearch(query: $query, first: $first) {
|
|
233
|
+
nodes {
|
|
234
|
+
...IssueFragment
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
`;
|
|
239
|
+
|
|
240
|
+
const result = await this.client.query<{ issueSearch: { nodes: LinearIssue[] } }>(
|
|
241
|
+
gqlQuery,
|
|
242
|
+
{ query, first }
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return result.issueSearch.nodes;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import type { LinearClient } from './client';
|
|
2
|
+
import type { LinearProject, ProjectsResponse, ProjectResponse, ListOptions } from '../types';
|
|
3
|
+
|
|
4
|
+
const PROJECT_FRAGMENT = `
|
|
5
|
+
fragment ProjectFragment on Project {
|
|
6
|
+
id
|
|
7
|
+
name
|
|
8
|
+
description
|
|
9
|
+
icon
|
|
10
|
+
color
|
|
11
|
+
state
|
|
12
|
+
progress
|
|
13
|
+
startDate
|
|
14
|
+
targetDate
|
|
15
|
+
createdAt
|
|
16
|
+
updatedAt
|
|
17
|
+
lead {
|
|
18
|
+
id
|
|
19
|
+
name
|
|
20
|
+
displayName
|
|
21
|
+
email
|
|
22
|
+
}
|
|
23
|
+
teams {
|
|
24
|
+
nodes {
|
|
25
|
+
id
|
|
26
|
+
name
|
|
27
|
+
key
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export interface CreateProjectInput {
|
|
34
|
+
name: string;
|
|
35
|
+
description?: string;
|
|
36
|
+
teamIds: string[];
|
|
37
|
+
leadId?: string;
|
|
38
|
+
startDate?: string;
|
|
39
|
+
targetDate?: string;
|
|
40
|
+
color?: string;
|
|
41
|
+
icon?: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface UpdateProjectInput {
|
|
45
|
+
name?: string;
|
|
46
|
+
description?: string;
|
|
47
|
+
leadId?: string;
|
|
48
|
+
startDate?: string;
|
|
49
|
+
targetDate?: string;
|
|
50
|
+
color?: string;
|
|
51
|
+
icon?: string;
|
|
52
|
+
state?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface ProjectPayload {
|
|
56
|
+
success: boolean;
|
|
57
|
+
project?: LinearProject;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
interface CreateProjectResponse {
|
|
61
|
+
projectCreate: ProjectPayload;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface UpdateProjectResponse {
|
|
65
|
+
projectUpdate: ProjectPayload;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export class ProjectsApi {
|
|
69
|
+
constructor(private readonly client: LinearClient) {}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* List all projects
|
|
73
|
+
*/
|
|
74
|
+
async list(options: ListOptions = {}): Promise<LinearProject[]> {
|
|
75
|
+
const { first = 50, after } = options;
|
|
76
|
+
|
|
77
|
+
const query = `
|
|
78
|
+
${PROJECT_FRAGMENT}
|
|
79
|
+
query Projects($first: Int, $after: String) {
|
|
80
|
+
projects(first: $first, after: $after) {
|
|
81
|
+
nodes {
|
|
82
|
+
...ProjectFragment
|
|
83
|
+
}
|
|
84
|
+
pageInfo {
|
|
85
|
+
hasNextPage
|
|
86
|
+
endCursor
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
`;
|
|
91
|
+
|
|
92
|
+
const result = await this.client.query<ProjectsResponse>(query, {
|
|
93
|
+
first,
|
|
94
|
+
after,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return result.projects.nodes;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get a single project by ID
|
|
102
|
+
*/
|
|
103
|
+
async get(id: string): Promise<LinearProject> {
|
|
104
|
+
const query = `
|
|
105
|
+
${PROJECT_FRAGMENT}
|
|
106
|
+
query Project($id: String!) {
|
|
107
|
+
project(id: $id) {
|
|
108
|
+
...ProjectFragment
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
|
|
113
|
+
const result = await this.client.query<ProjectResponse>(query, { id });
|
|
114
|
+
return result.project;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Create a new project
|
|
119
|
+
*/
|
|
120
|
+
async create(input: CreateProjectInput): Promise<LinearProject> {
|
|
121
|
+
const mutation = `
|
|
122
|
+
${PROJECT_FRAGMENT}
|
|
123
|
+
mutation CreateProject($input: ProjectCreateInput!) {
|
|
124
|
+
projectCreate(input: $input) {
|
|
125
|
+
success
|
|
126
|
+
project {
|
|
127
|
+
...ProjectFragment
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
const result = await this.client.mutate<CreateProjectResponse>(mutation, {
|
|
134
|
+
input,
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
if (!result.projectCreate.success || !result.projectCreate.project) {
|
|
138
|
+
throw new Error('Failed to create project');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result.projectCreate.project;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Update a project
|
|
146
|
+
*/
|
|
147
|
+
async update(id: string, input: UpdateProjectInput): Promise<LinearProject> {
|
|
148
|
+
const mutation = `
|
|
149
|
+
${PROJECT_FRAGMENT}
|
|
150
|
+
mutation UpdateProject($id: String!, $input: ProjectUpdateInput!) {
|
|
151
|
+
projectUpdate(id: $id, input: $input) {
|
|
152
|
+
success
|
|
153
|
+
project {
|
|
154
|
+
...ProjectFragment
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
`;
|
|
159
|
+
|
|
160
|
+
const result = await this.client.mutate<UpdateProjectResponse>(mutation, {
|
|
161
|
+
id,
|
|
162
|
+
input,
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
if (!result.projectUpdate.success || !result.projectUpdate.project) {
|
|
166
|
+
throw new Error('Failed to update project');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return result.projectUpdate.project;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Search projects by name
|
|
174
|
+
*/
|
|
175
|
+
async findByName(name: string): Promise<LinearProject | undefined> {
|
|
176
|
+
const projects = await this.list();
|
|
177
|
+
return projects.find(p => p.name.toLowerCase() === name.toLowerCase());
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { LinearClient } from './client';
|
|
2
|
+
import type { LinearTeam, LinearWorkflowState, TeamsResponse, TeamResponse, ListOptions } from '../types';
|
|
3
|
+
|
|
4
|
+
const TEAM_FRAGMENT = `
|
|
5
|
+
fragment TeamFragment on Team {
|
|
6
|
+
id
|
|
7
|
+
name
|
|
8
|
+
key
|
|
9
|
+
description
|
|
10
|
+
icon
|
|
11
|
+
color
|
|
12
|
+
private
|
|
13
|
+
createdAt
|
|
14
|
+
updatedAt
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
const WORKFLOW_STATE_FRAGMENT = `
|
|
19
|
+
fragment WorkflowStateFragment on WorkflowState {
|
|
20
|
+
id
|
|
21
|
+
name
|
|
22
|
+
color
|
|
23
|
+
type
|
|
24
|
+
position
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
interface WorkflowStatesResponse {
|
|
29
|
+
workflowStates: {
|
|
30
|
+
nodes: LinearWorkflowState[];
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class TeamsApi {
|
|
35
|
+
constructor(private readonly client: LinearClient) {}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* List all teams
|
|
39
|
+
*/
|
|
40
|
+
async list(options: ListOptions = {}): Promise<LinearTeam[]> {
|
|
41
|
+
const { first = 50, after } = options;
|
|
42
|
+
|
|
43
|
+
const query = `
|
|
44
|
+
${TEAM_FRAGMENT}
|
|
45
|
+
query Teams($first: Int, $after: String) {
|
|
46
|
+
teams(first: $first, after: $after) {
|
|
47
|
+
nodes {
|
|
48
|
+
...TeamFragment
|
|
49
|
+
}
|
|
50
|
+
pageInfo {
|
|
51
|
+
hasNextPage
|
|
52
|
+
endCursor
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const result = await this.client.query<TeamsResponse>(query, {
|
|
59
|
+
first,
|
|
60
|
+
after,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return result.teams.nodes;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get a single team by ID
|
|
68
|
+
*/
|
|
69
|
+
async get(id: string): Promise<LinearTeam> {
|
|
70
|
+
const query = `
|
|
71
|
+
${TEAM_FRAGMENT}
|
|
72
|
+
query Team($id: String!) {
|
|
73
|
+
team(id: $id) {
|
|
74
|
+
...TeamFragment
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
`;
|
|
78
|
+
|
|
79
|
+
const result = await this.client.query<TeamResponse>(query, { id });
|
|
80
|
+
return result.team;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Find team by key (e.g., "ENG", "DES")
|
|
85
|
+
*/
|
|
86
|
+
async findByKey(key: string): Promise<LinearTeam | undefined> {
|
|
87
|
+
const teams = await this.list();
|
|
88
|
+
return teams.find(t => t.key.toLowerCase() === key.toLowerCase());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Find team by name
|
|
93
|
+
*/
|
|
94
|
+
async findByName(name: string): Promise<LinearTeam | undefined> {
|
|
95
|
+
const teams = await this.list();
|
|
96
|
+
return teams.find(t => t.name.toLowerCase() === name.toLowerCase());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get workflow states for a team
|
|
101
|
+
*/
|
|
102
|
+
async getWorkflowStates(teamId: string): Promise<LinearWorkflowState[]> {
|
|
103
|
+
const query = `
|
|
104
|
+
${WORKFLOW_STATE_FRAGMENT}
|
|
105
|
+
query WorkflowStates($teamId: String!) {
|
|
106
|
+
workflowStates(filter: { team: { id: { eq: $teamId } } }) {
|
|
107
|
+
nodes {
|
|
108
|
+
...WorkflowStateFragment
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
`;
|
|
113
|
+
|
|
114
|
+
const result = await this.client.query<WorkflowStatesResponse>(query, { teamId });
|
|
115
|
+
return result.workflowStates.nodes;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Get a specific workflow state by name for a team
|
|
120
|
+
*/
|
|
121
|
+
async findWorkflowState(teamId: string, stateName: string): Promise<LinearWorkflowState | undefined> {
|
|
122
|
+
const states = await this.getWorkflowStates(teamId);
|
|
123
|
+
return states.find(s => s.name.toLowerCase() === stateName.toLowerCase());
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import type { LinearClient } from './client';
|
|
2
|
+
import type { LinearUser, UsersResponse, UserResponse, ViewerResponse, ListOptions } from '../types';
|
|
3
|
+
|
|
4
|
+
const USER_FRAGMENT = `
|
|
5
|
+
fragment UserFragment on User {
|
|
6
|
+
id
|
|
7
|
+
name
|
|
8
|
+
displayName
|
|
9
|
+
email
|
|
10
|
+
avatarUrl
|
|
11
|
+
active
|
|
12
|
+
admin
|
|
13
|
+
createdAt
|
|
14
|
+
updatedAt
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
export class UsersApi {
|
|
19
|
+
constructor(private readonly client: LinearClient) {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* List all users
|
|
23
|
+
*/
|
|
24
|
+
async list(options: ListOptions = {}): Promise<LinearUser[]> {
|
|
25
|
+
const { first = 100, after } = options;
|
|
26
|
+
|
|
27
|
+
const query = `
|
|
28
|
+
${USER_FRAGMENT}
|
|
29
|
+
query Users($first: Int, $after: String) {
|
|
30
|
+
users(first: $first, after: $after) {
|
|
31
|
+
nodes {
|
|
32
|
+
...UserFragment
|
|
33
|
+
}
|
|
34
|
+
pageInfo {
|
|
35
|
+
hasNextPage
|
|
36
|
+
endCursor
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const result = await this.client.query<UsersResponse>(query, {
|
|
43
|
+
first,
|
|
44
|
+
after,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return result.users.nodes;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get a single user by ID
|
|
52
|
+
*/
|
|
53
|
+
async get(id: string): Promise<LinearUser> {
|
|
54
|
+
const query = `
|
|
55
|
+
${USER_FRAGMENT}
|
|
56
|
+
query User($id: String!) {
|
|
57
|
+
user(id: $id) {
|
|
58
|
+
...UserFragment
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
`;
|
|
62
|
+
|
|
63
|
+
const result = await this.client.query<UserResponse>(query, { id });
|
|
64
|
+
return result.user;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get the currently authenticated user
|
|
69
|
+
*/
|
|
70
|
+
async me(): Promise<LinearUser> {
|
|
71
|
+
const query = `
|
|
72
|
+
${USER_FRAGMENT}
|
|
73
|
+
query Viewer {
|
|
74
|
+
viewer {
|
|
75
|
+
...UserFragment
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
const result = await this.client.query<ViewerResponse>(query);
|
|
81
|
+
return result.viewer;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Find user by email
|
|
86
|
+
*/
|
|
87
|
+
async findByEmail(email: string): Promise<LinearUser | undefined> {
|
|
88
|
+
const users = await this.list();
|
|
89
|
+
return users.find(u => u.email.toLowerCase() === email.toLowerCase());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Find user by name or display name
|
|
94
|
+
*/
|
|
95
|
+
async findByName(name: string): Promise<LinearUser | undefined> {
|
|
96
|
+
const users = await this.list();
|
|
97
|
+
const lowerName = name.toLowerCase();
|
|
98
|
+
return users.find(
|
|
99
|
+
u =>
|
|
100
|
+
u.name.toLowerCase() === lowerName ||
|
|
101
|
+
u.displayName.toLowerCase() === lowerName
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* List only active users
|
|
107
|
+
*/
|
|
108
|
+
async listActive(options: ListOptions = {}): Promise<LinearUser[]> {
|
|
109
|
+
const users = await this.list(options);
|
|
110
|
+
return users.filter(u => u.active);
|
|
111
|
+
}
|
|
112
|
+
}
|