@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.
Files changed (127) hide show
  1. package/bin/index.js +71 -1
  2. package/bin/mcp.js +71 -1
  3. package/bin/serve.js +70 -0
  4. package/connectors/connect-asana/.env.example +11 -0
  5. package/connectors/connect-asana/CLAUDE.md +128 -0
  6. package/connectors/connect-asana/README.md +193 -0
  7. package/connectors/connect-asana/package.json +52 -0
  8. package/connectors/connect-asana/src/api/client.ts +119 -0
  9. package/connectors/connect-asana/src/api/index.ts +319 -0
  10. package/connectors/connect-asana/src/cli/index.ts +731 -0
  11. package/connectors/connect-asana/src/index.ts +19 -0
  12. package/connectors/connect-asana/src/types/index.ts +270 -0
  13. package/connectors/connect-asana/src/utils/config.ts +171 -0
  14. package/connectors/connect-asana/src/utils/output.ts +119 -0
  15. package/connectors/connect-asana/tsconfig.json +16 -0
  16. package/connectors/connect-clickup/.env.example +11 -0
  17. package/connectors/connect-clickup/CLAUDE.md +128 -0
  18. package/connectors/connect-clickup/README.md +193 -0
  19. package/connectors/connect-clickup/package.json +52 -0
  20. package/connectors/connect-clickup/src/api/client.ts +116 -0
  21. package/connectors/connect-clickup/src/api/index.ts +400 -0
  22. package/connectors/connect-clickup/src/cli/index.ts +625 -0
  23. package/connectors/connect-clickup/src/index.ts +19 -0
  24. package/connectors/connect-clickup/src/types/index.ts +591 -0
  25. package/connectors/connect-clickup/src/utils/config.ts +157 -0
  26. package/connectors/connect-clickup/src/utils/output.ts +119 -0
  27. package/connectors/connect-clickup/tsconfig.json +16 -0
  28. package/connectors/connect-confluence/.env.example +11 -0
  29. package/connectors/connect-confluence/CLAUDE.md +272 -0
  30. package/connectors/connect-confluence/README.md +193 -0
  31. package/connectors/connect-confluence/package.json +53 -0
  32. package/connectors/connect-confluence/scripts/release.ts +179 -0
  33. package/connectors/connect-confluence/src/api/client.ts +213 -0
  34. package/connectors/connect-confluence/src/api/example.ts +48 -0
  35. package/connectors/connect-confluence/src/api/index.ts +51 -0
  36. package/connectors/connect-confluence/src/cli/index.ts +254 -0
  37. package/connectors/connect-confluence/src/index.ts +103 -0
  38. package/connectors/connect-confluence/src/types/index.ts +237 -0
  39. package/connectors/connect-confluence/src/utils/auth.ts +274 -0
  40. package/connectors/connect-confluence/src/utils/bulk.ts +212 -0
  41. package/connectors/connect-confluence/src/utils/config.ts +326 -0
  42. package/connectors/connect-confluence/src/utils/output.ts +175 -0
  43. package/connectors/connect-confluence/src/utils/settings.ts +114 -0
  44. package/connectors/connect-confluence/src/utils/storage.ts +198 -0
  45. package/connectors/connect-confluence/tsconfig.json +16 -0
  46. package/connectors/connect-jira/.env.example +11 -0
  47. package/connectors/connect-jira/CLAUDE.md +128 -0
  48. package/connectors/connect-jira/README.md +193 -0
  49. package/connectors/connect-jira/package.json +53 -0
  50. package/connectors/connect-jira/src/api/client.ts +131 -0
  51. package/connectors/connect-jira/src/api/index.ts +266 -0
  52. package/connectors/connect-jira/src/cli/index.ts +653 -0
  53. package/connectors/connect-jira/src/index.ts +23 -0
  54. package/connectors/connect-jira/src/types/index.ts +448 -0
  55. package/connectors/connect-jira/src/utils/config.ts +179 -0
  56. package/connectors/connect-jira/src/utils/output.ts +119 -0
  57. package/connectors/connect-jira/tsconfig.json +16 -0
  58. package/connectors/connect-linear/CLAUDE.md +88 -0
  59. package/connectors/connect-linear/README.md +201 -0
  60. package/connectors/connect-linear/package.json +45 -0
  61. package/connectors/connect-linear/src/api/client.ts +62 -0
  62. package/connectors/connect-linear/src/api/index.ts +46 -0
  63. package/connectors/connect-linear/src/api/issues.ts +247 -0
  64. package/connectors/connect-linear/src/api/projects.ts +179 -0
  65. package/connectors/connect-linear/src/api/teams.ts +125 -0
  66. package/connectors/connect-linear/src/api/users.ts +112 -0
  67. package/connectors/connect-linear/src/cli/index.ts +560 -0
  68. package/connectors/connect-linear/src/index.ts +27 -0
  69. package/connectors/connect-linear/src/types/index.ts +275 -0
  70. package/connectors/connect-linear/src/utils/config.ts +249 -0
  71. package/connectors/connect-linear/src/utils/output.ts +119 -0
  72. package/connectors/connect-linear/tsconfig.json +16 -0
  73. package/connectors/connect-slack/.env.example +7 -0
  74. package/connectors/connect-slack/CLAUDE.md +69 -0
  75. package/connectors/connect-slack/README.md +150 -0
  76. package/connectors/connect-slack/package.json +44 -0
  77. package/connectors/connect-slack/src/api/channels.ts +112 -0
  78. package/connectors/connect-slack/src/api/client.ts +97 -0
  79. package/connectors/connect-slack/src/api/index.ts +42 -0
  80. package/connectors/connect-slack/src/api/messages.ts +127 -0
  81. package/connectors/connect-slack/src/api/users.ts +110 -0
  82. package/connectors/connect-slack/src/cli/index.ts +494 -0
  83. package/connectors/connect-slack/src/index.ts +21 -0
  84. package/connectors/connect-slack/src/types/index.ts +263 -0
  85. package/connectors/connect-slack/src/utils/config.ts +297 -0
  86. package/connectors/connect-slack/src/utils/output.ts +119 -0
  87. package/connectors/connect-slack/tsconfig.json +16 -0
  88. package/connectors/connect-telegram/.env.example +2 -0
  89. package/connectors/connect-telegram/package.json +49 -0
  90. package/connectors/connect-todoist/.env.example +11 -0
  91. package/connectors/connect-todoist/CLAUDE.md +104 -0
  92. package/connectors/connect-todoist/README.md +193 -0
  93. package/connectors/connect-todoist/package.json +52 -0
  94. package/connectors/connect-todoist/src/api/client.ts +117 -0
  95. package/connectors/connect-todoist/src/api/index.ts +188 -0
  96. package/connectors/connect-todoist/src/cli/index.ts +990 -0
  97. package/connectors/connect-todoist/src/index.ts +21 -0
  98. package/connectors/connect-todoist/src/types/index.ts +240 -0
  99. package/connectors/connect-todoist/src/utils/config.ts +157 -0
  100. package/connectors/connect-todoist/src/utils/output.ts +119 -0
  101. package/connectors/connect-todoist/tsconfig.json +16 -0
  102. package/connectors/connect-trello/.env.example +11 -0
  103. package/connectors/connect-trello/CLAUDE.md +128 -0
  104. package/connectors/connect-trello/README.md +193 -0
  105. package/connectors/connect-trello/package.json +53 -0
  106. package/connectors/connect-trello/src/api/client.ts +128 -0
  107. package/connectors/connect-trello/src/api/index.ts +278 -0
  108. package/connectors/connect-trello/src/cli/index.ts +737 -0
  109. package/connectors/connect-trello/src/index.ts +21 -0
  110. package/connectors/connect-trello/src/types/index.ts +314 -0
  111. package/connectors/connect-trello/src/utils/config.ts +182 -0
  112. package/connectors/connect-trello/src/utils/output.ts +119 -0
  113. package/connectors/connect-trello/tsconfig.json +16 -0
  114. package/connectors/connect-whatsapp/.env.example +11 -0
  115. package/connectors/connect-whatsapp/CLAUDE.md +113 -0
  116. package/connectors/connect-whatsapp/README.md +193 -0
  117. package/connectors/connect-whatsapp/package.json +53 -0
  118. package/connectors/connect-whatsapp/src/api/client.ts +133 -0
  119. package/connectors/connect-whatsapp/src/api/index.ts +365 -0
  120. package/connectors/connect-whatsapp/src/cli/index.ts +686 -0
  121. package/connectors/connect-whatsapp/src/index.ts +25 -0
  122. package/connectors/connect-whatsapp/src/types/index.ts +502 -0
  123. package/connectors/connect-whatsapp/src/utils/config.ts +179 -0
  124. package/connectors/connect-whatsapp/src/utils/output.ts +119 -0
  125. package/connectors/connect-whatsapp/tsconfig.json +16 -0
  126. package/dist/index.js +70 -0
  127. package/package.json +1 -1
@@ -0,0 +1,119 @@
1
+ import type { AsanaConfig } from '../types';
2
+ import { AsanaApiError } from '../types';
3
+
4
+ const DEFAULT_BASE_URL = 'https://app.asana.com/api/1.0';
5
+
6
+ export interface RequestOptions {
7
+ method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH';
8
+ params?: Record<string, string | number | boolean | undefined>;
9
+ body?: Record<string, unknown> | unknown[] | string;
10
+ headers?: Record<string, string>;
11
+ }
12
+
13
+ export class AsanaClient {
14
+ private readonly accessToken: string;
15
+ private readonly baseUrl: string;
16
+
17
+ constructor(config: AsanaConfig) {
18
+ if (!config.accessToken) {
19
+ throw new Error('Access token is required');
20
+ }
21
+ this.accessToken = config.accessToken;
22
+ this.baseUrl = config.baseUrl || DEFAULT_BASE_URL;
23
+ }
24
+
25
+ private buildUrl(path: string, params?: Record<string, string | number | boolean | undefined>): string {
26
+ const url = new URL(`${this.baseUrl}${path}`);
27
+
28
+ if (params) {
29
+ Object.entries(params).forEach(([key, value]) => {
30
+ if (value !== undefined && value !== null && value !== '') {
31
+ url.searchParams.append(key, String(value));
32
+ }
33
+ });
34
+ }
35
+
36
+ return url.toString();
37
+ }
38
+
39
+ async request<T>(path: string, options: RequestOptions = {}): Promise<T> {
40
+ const { method = 'GET', params, body, headers = {} } = options;
41
+
42
+ const url = this.buildUrl(path, params);
43
+
44
+ const requestHeaders: Record<string, string> = {
45
+ 'Authorization': `Bearer ${this.accessToken}`,
46
+ 'Accept': 'application/json',
47
+ ...headers,
48
+ };
49
+
50
+ if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
51
+ requestHeaders['Content-Type'] = 'application/json';
52
+ }
53
+
54
+ const fetchOptions: RequestInit = {
55
+ method,
56
+ headers: requestHeaders,
57
+ };
58
+
59
+ if (body && ['POST', 'PUT', 'PATCH'].includes(method)) {
60
+ fetchOptions.body = typeof body === 'string' ? body : JSON.stringify(body);
61
+ }
62
+
63
+ const response = await fetch(url, fetchOptions);
64
+
65
+ // Handle 204 No Content
66
+ if (response.status === 204) {
67
+ return {} as T;
68
+ }
69
+
70
+ // Parse response
71
+ let data: unknown;
72
+ const contentType = response.headers.get('content-type') || '';
73
+
74
+ if (contentType.includes('application/json')) {
75
+ const text = await response.text();
76
+ if (text) {
77
+ try {
78
+ data = JSON.parse(text);
79
+ } catch {
80
+ data = text;
81
+ }
82
+ }
83
+ } else {
84
+ data = await response.text();
85
+ }
86
+
87
+ // Handle errors
88
+ if (!response.ok) {
89
+ const errorData = data as { errors?: { message: string }[] } | undefined;
90
+ const errorMessage = errorData?.errors?.[0]?.message || response.statusText;
91
+ throw new AsanaApiError(errorMessage, response.status, errorData?.errors as any);
92
+ }
93
+
94
+ return data as T;
95
+ }
96
+
97
+ async get<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
98
+ return this.request<T>(path, { method: 'GET', params });
99
+ }
100
+
101
+ async post<T>(path: string, body?: Record<string, unknown> | unknown[] | string | object, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
102
+ return this.request<T>(path, { method: 'POST', body: body as Record<string, unknown>, params });
103
+ }
104
+
105
+ async put<T>(path: string, body?: Record<string, unknown> | object, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
106
+ return this.request<T>(path, { method: 'PUT', body: body as Record<string, unknown>, params });
107
+ }
108
+
109
+ async delete<T>(path: string, params?: Record<string, string | number | boolean | undefined>): Promise<T> {
110
+ return this.request<T>(path, { method: 'DELETE', params });
111
+ }
112
+
113
+ getAccessTokenPreview(): string {
114
+ if (this.accessToken.length > 10) {
115
+ return `${this.accessToken.substring(0, 6)}...${this.accessToken.substring(this.accessToken.length - 4)}`;
116
+ }
117
+ return '***';
118
+ }
119
+ }
@@ -0,0 +1,319 @@
1
+ import { AsanaClient } from './client';
2
+ import type {
3
+ AsanaConfig,
4
+ AsanaResponse,
5
+ AsanaListResponse,
6
+ Workspace,
7
+ User,
8
+ Team,
9
+ Project,
10
+ CreateProjectInput,
11
+ Section,
12
+ CreateSectionInput,
13
+ Task,
14
+ CreateTaskInput,
15
+ Tag,
16
+ CreateTagInput,
17
+ Story,
18
+ CreateStoryInput,
19
+ Attachment,
20
+ } from '../types';
21
+
22
+ export { AsanaClient } from './client';
23
+
24
+ export class Asana {
25
+ private client: AsanaClient;
26
+
27
+ constructor(config: AsanaConfig) {
28
+ this.client = new AsanaClient(config);
29
+ }
30
+
31
+ // ============================================
32
+ // Workspaces
33
+ // ============================================
34
+
35
+ async listWorkspaces(): Promise<Workspace[]> {
36
+ const response = await this.client.get<AsanaListResponse<Workspace>>('/workspaces');
37
+ return response.data;
38
+ }
39
+
40
+ async getWorkspace(gid: string): Promise<Workspace> {
41
+ const response = await this.client.get<AsanaResponse<Workspace>>(`/workspaces/${gid}`);
42
+ return response.data;
43
+ }
44
+
45
+ // ============================================
46
+ // Users
47
+ // ============================================
48
+
49
+ async getMe(): Promise<User> {
50
+ const response = await this.client.get<AsanaResponse<User>>('/users/me');
51
+ return response.data;
52
+ }
53
+
54
+ async getUser(gid: string): Promise<User> {
55
+ const response = await this.client.get<AsanaResponse<User>>(`/users/${gid}`);
56
+ return response.data;
57
+ }
58
+
59
+ async listUsersInWorkspace(workspaceGid: string): Promise<User[]> {
60
+ const response = await this.client.get<AsanaListResponse<User>>(`/workspaces/${workspaceGid}/users`);
61
+ return response.data;
62
+ }
63
+
64
+ // ============================================
65
+ // Teams
66
+ // ============================================
67
+
68
+ async listTeamsInWorkspace(workspaceGid: string): Promise<Team[]> {
69
+ const response = await this.client.get<AsanaListResponse<Team>>(`/organizations/${workspaceGid}/teams`);
70
+ return response.data;
71
+ }
72
+
73
+ async getTeam(gid: string): Promise<Team> {
74
+ const response = await this.client.get<AsanaResponse<Team>>(`/teams/${gid}`);
75
+ return response.data;
76
+ }
77
+
78
+ async listTeamsForUser(userGid: string, organizationGid: string): Promise<Team[]> {
79
+ const response = await this.client.get<AsanaListResponse<Team>>(`/users/${userGid}/teams`, {
80
+ organization: organizationGid,
81
+ });
82
+ return response.data;
83
+ }
84
+
85
+ // ============================================
86
+ // Projects
87
+ // ============================================
88
+
89
+ async listProjects(options: { workspace?: string; team?: string; archived?: boolean; limit?: number } = {}): Promise<Project[]> {
90
+ const params: Record<string, string | number | boolean | undefined> = {
91
+ limit: options.limit || 100,
92
+ archived: options.archived,
93
+ };
94
+
95
+ let path = '/projects';
96
+ if (options.workspace) {
97
+ path = `/workspaces/${options.workspace}/projects`;
98
+ } else if (options.team) {
99
+ path = `/teams/${options.team}/projects`;
100
+ }
101
+
102
+ const response = await this.client.get<AsanaListResponse<Project>>(path, params);
103
+ return response.data;
104
+ }
105
+
106
+ async getProject(gid: string): Promise<Project> {
107
+ const response = await this.client.get<AsanaResponse<Project>>(`/projects/${gid}`);
108
+ return response.data;
109
+ }
110
+
111
+ async createProject(input: CreateProjectInput): Promise<Project> {
112
+ const response = await this.client.post<AsanaResponse<Project>>('/projects', { data: input });
113
+ return response.data;
114
+ }
115
+
116
+ async updateProject(gid: string, input: Partial<CreateProjectInput>): Promise<Project> {
117
+ const response = await this.client.put<AsanaResponse<Project>>(`/projects/${gid}`, { data: input });
118
+ return response.data;
119
+ }
120
+
121
+ async deleteProject(gid: string): Promise<void> {
122
+ await this.client.delete(`/projects/${gid}`);
123
+ }
124
+
125
+ // ============================================
126
+ // Sections
127
+ // ============================================
128
+
129
+ async listSections(projectGid: string): Promise<Section[]> {
130
+ const response = await this.client.get<AsanaListResponse<Section>>(`/projects/${projectGid}/sections`);
131
+ return response.data;
132
+ }
133
+
134
+ async getSection(gid: string): Promise<Section> {
135
+ const response = await this.client.get<AsanaResponse<Section>>(`/sections/${gid}`);
136
+ return response.data;
137
+ }
138
+
139
+ async createSection(projectGid: string, input: CreateSectionInput): Promise<Section> {
140
+ const response = await this.client.post<AsanaResponse<Section>>(`/projects/${projectGid}/sections`, { data: input });
141
+ return response.data;
142
+ }
143
+
144
+ async updateSection(gid: string, name: string): Promise<Section> {
145
+ const response = await this.client.put<AsanaResponse<Section>>(`/sections/${gid}`, { data: { name } });
146
+ return response.data;
147
+ }
148
+
149
+ async deleteSection(gid: string): Promise<void> {
150
+ await this.client.delete(`/sections/${gid}`);
151
+ }
152
+
153
+ // ============================================
154
+ // Tasks
155
+ // ============================================
156
+
157
+ async listTasks(options: { project?: string; section?: string; assignee?: string; workspace?: string; completed_since?: string; limit?: number } = {}): Promise<Task[]> {
158
+ const params: Record<string, string | number | boolean | undefined> = {
159
+ limit: options.limit || 100,
160
+ completed_since: options.completed_since,
161
+ };
162
+
163
+ let path = '/tasks';
164
+ if (options.project) {
165
+ path = `/projects/${options.project}/tasks`;
166
+ } else if (options.section) {
167
+ path = `/sections/${options.section}/tasks`;
168
+ } else if (options.assignee && options.workspace) {
169
+ params.assignee = options.assignee;
170
+ params.workspace = options.workspace;
171
+ }
172
+
173
+ const response = await this.client.get<AsanaListResponse<Task>>(path, params);
174
+ return response.data;
175
+ }
176
+
177
+ async getTask(gid: string): Promise<Task> {
178
+ const response = await this.client.get<AsanaResponse<Task>>(`/tasks/${gid}`);
179
+ return response.data;
180
+ }
181
+
182
+ async createTask(input: CreateTaskInput): Promise<Task> {
183
+ const response = await this.client.post<AsanaResponse<Task>>('/tasks', { data: input });
184
+ return response.data;
185
+ }
186
+
187
+ async updateTask(gid: string, input: Partial<CreateTaskInput>): Promise<Task> {
188
+ const response = await this.client.put<AsanaResponse<Task>>(`/tasks/${gid}`, { data: input });
189
+ return response.data;
190
+ }
191
+
192
+ async deleteTask(gid: string): Promise<void> {
193
+ await this.client.delete(`/tasks/${gid}`);
194
+ }
195
+
196
+ async getSubtasks(taskGid: string): Promise<Task[]> {
197
+ const response = await this.client.get<AsanaListResponse<Task>>(`/tasks/${taskGid}/subtasks`);
198
+ return response.data;
199
+ }
200
+
201
+ async createSubtask(taskGid: string, input: CreateTaskInput): Promise<Task> {
202
+ const response = await this.client.post<AsanaResponse<Task>>(`/tasks/${taskGid}/subtasks`, { data: input });
203
+ return response.data;
204
+ }
205
+
206
+ async addTaskToProject(taskGid: string, projectGid: string, sectionGid?: string): Promise<void> {
207
+ await this.client.post(`/tasks/${taskGid}/addProject`, {
208
+ data: {
209
+ project: projectGid,
210
+ section: sectionGid,
211
+ },
212
+ });
213
+ }
214
+
215
+ async removeTaskFromProject(taskGid: string, projectGid: string): Promise<void> {
216
+ await this.client.post(`/tasks/${taskGid}/removeProject`, {
217
+ data: { project: projectGid },
218
+ });
219
+ }
220
+
221
+ async setTaskDependencies(taskGid: string, dependsOn: string[]): Promise<void> {
222
+ await this.client.post(`/tasks/${taskGid}/addDependencies`, {
223
+ data: { dependencies: dependsOn },
224
+ });
225
+ }
226
+
227
+ // ============================================
228
+ // Tags
229
+ // ============================================
230
+
231
+ async listTags(workspaceGid: string): Promise<Tag[]> {
232
+ const response = await this.client.get<AsanaListResponse<Tag>>(`/workspaces/${workspaceGid}/tags`);
233
+ return response.data;
234
+ }
235
+
236
+ async getTag(gid: string): Promise<Tag> {
237
+ const response = await this.client.get<AsanaResponse<Tag>>(`/tags/${gid}`);
238
+ return response.data;
239
+ }
240
+
241
+ async createTag(input: CreateTagInput): Promise<Tag> {
242
+ const response = await this.client.post<AsanaResponse<Tag>>('/tags', { data: input });
243
+ return response.data;
244
+ }
245
+
246
+ async addTagToTask(taskGid: string, tagGid: string): Promise<void> {
247
+ await this.client.post(`/tasks/${taskGid}/addTag`, { data: { tag: tagGid } });
248
+ }
249
+
250
+ async removeTagFromTask(taskGid: string, tagGid: string): Promise<void> {
251
+ await this.client.post(`/tasks/${taskGid}/removeTag`, { data: { tag: tagGid } });
252
+ }
253
+
254
+ // ============================================
255
+ // Stories (Comments)
256
+ // ============================================
257
+
258
+ async listStories(taskGid: string): Promise<Story[]> {
259
+ const response = await this.client.get<AsanaListResponse<Story>>(`/tasks/${taskGid}/stories`);
260
+ return response.data;
261
+ }
262
+
263
+ async getStory(gid: string): Promise<Story> {
264
+ const response = await this.client.get<AsanaResponse<Story>>(`/stories/${gid}`);
265
+ return response.data;
266
+ }
267
+
268
+ async createStory(taskGid: string, input: CreateStoryInput): Promise<Story> {
269
+ const response = await this.client.post<AsanaResponse<Story>>(`/tasks/${taskGid}/stories`, { data: input });
270
+ return response.data;
271
+ }
272
+
273
+ async updateStory(gid: string, text: string): Promise<Story> {
274
+ const response = await this.client.put<AsanaResponse<Story>>(`/stories/${gid}`, { data: { text } });
275
+ return response.data;
276
+ }
277
+
278
+ async deleteStory(gid: string): Promise<void> {
279
+ await this.client.delete(`/stories/${gid}`);
280
+ }
281
+
282
+ // ============================================
283
+ // Attachments
284
+ // ============================================
285
+
286
+ async listAttachments(taskGid: string): Promise<Attachment[]> {
287
+ const response = await this.client.get<AsanaListResponse<Attachment>>(`/tasks/${taskGid}/attachments`);
288
+ return response.data;
289
+ }
290
+
291
+ async getAttachment(gid: string): Promise<Attachment> {
292
+ const response = await this.client.get<AsanaResponse<Attachment>>(`/attachments/${gid}`);
293
+ return response.data;
294
+ }
295
+
296
+ async deleteAttachment(gid: string): Promise<void> {
297
+ await this.client.delete(`/attachments/${gid}`);
298
+ }
299
+
300
+ // ============================================
301
+ // Search
302
+ // ============================================
303
+
304
+ async searchTasks(workspaceGid: string, options: {
305
+ text?: string;
306
+ 'projects.any'?: string;
307
+ 'assignee.any'?: string;
308
+ completed?: boolean;
309
+ is_subtask?: boolean;
310
+ sort_by?: 'due_date' | 'created_at' | 'completed_at' | 'likes' | 'modified_at';
311
+ sort_ascending?: boolean;
312
+ } = {}): Promise<Task[]> {
313
+ const params: Record<string, string | number | boolean | undefined> = {
314
+ ...options,
315
+ };
316
+ const response = await this.client.get<AsanaListResponse<Task>>(`/workspaces/${workspaceGid}/tasks/search`, params);
317
+ return response.data;
318
+ }
319
+ }