chub-dev 0.1.0 → 0.1.2-beta.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 +55 -0
- package/bin/chub-mcp +2 -0
- package/dist/airtable/docs/database/javascript/DOC.md +1437 -0
- package/dist/airtable/docs/database/python/DOC.md +1735 -0
- package/dist/amplitude/docs/analytics/javascript/DOC.md +1282 -0
- package/dist/amplitude/docs/analytics/python/DOC.md +1199 -0
- package/dist/anthropic/docs/claude-api/javascript/DOC.md +503 -0
- package/dist/anthropic/docs/claude-api/python/DOC.md +389 -0
- package/dist/asana/docs/tasks/DOC.md +1396 -0
- package/dist/assemblyai/docs/transcription/DOC.md +1043 -0
- package/dist/atlassian/docs/confluence/javascript/DOC.md +1347 -0
- package/dist/atlassian/docs/confluence/python/DOC.md +1604 -0
- package/dist/auth0/docs/identity/javascript/DOC.md +968 -0
- package/dist/auth0/docs/identity/python/DOC.md +1199 -0
- package/dist/aws/docs/s3/javascript/DOC.md +1773 -0
- package/dist/aws/docs/s3/python/DOC.md +1807 -0
- package/dist/binance/docs/trading/javascript/DOC.md +1315 -0
- package/dist/binance/docs/trading/python/DOC.md +1454 -0
- package/dist/braintree/docs/gateway/javascript/DOC.md +1278 -0
- package/dist/braintree/docs/gateway/python/DOC.md +1179 -0
- package/dist/chromadb/docs/embeddings-db/javascript/DOC.md +1263 -0
- package/dist/chromadb/docs/embeddings-db/python/DOC.md +1707 -0
- package/dist/clerk/docs/auth/javascript/DOC.md +1220 -0
- package/dist/clerk/docs/auth/python/DOC.md +274 -0
- package/dist/cloudflare/docs/workers/javascript/DOC.md +918 -0
- package/dist/cloudflare/docs/workers/python/DOC.md +994 -0
- package/dist/cockroachdb/docs/distributed-db/DOC.md +1500 -0
- package/dist/cohere/docs/llm/DOC.md +1335 -0
- package/dist/datadog/docs/monitoring/javascript/DOC.md +1740 -0
- package/dist/datadog/docs/monitoring/python/DOC.md +1815 -0
- package/dist/deepgram/docs/speech/javascript/DOC.md +885 -0
- package/dist/deepgram/docs/speech/python/DOC.md +685 -0
- package/dist/deepl/docs/translation/javascript/DOC.md +887 -0
- package/dist/deepl/docs/translation/python/DOC.md +944 -0
- package/dist/deepseek/docs/llm/DOC.md +1220 -0
- package/dist/directus/docs/headless-cms/javascript/DOC.md +1128 -0
- package/dist/directus/docs/headless-cms/python/DOC.md +1276 -0
- package/dist/discord/docs/bot/javascript/DOC.md +1090 -0
- package/dist/discord/docs/bot/python/DOC.md +1130 -0
- package/dist/elasticsearch/docs/search/DOC.md +1634 -0
- package/dist/elevenlabs/docs/text-to-speech/javascript/DOC.md +336 -0
- package/dist/elevenlabs/docs/text-to-speech/python/DOC.md +552 -0
- package/dist/firebase/docs/auth/DOC.md +1015 -0
- package/dist/gemini/docs/genai/javascript/DOC.md +691 -0
- package/dist/gemini/docs/genai/python/DOC.md +555 -0
- package/dist/github/docs/octokit/DOC.md +1560 -0
- package/dist/google/docs/bigquery/javascript/DOC.md +1688 -0
- package/dist/google/docs/bigquery/python/DOC.md +1503 -0
- package/dist/hubspot/docs/crm/javascript/DOC.md +1805 -0
- package/dist/hubspot/docs/crm/python/DOC.md +2033 -0
- package/dist/huggingface/docs/transformers/DOC.md +948 -0
- package/dist/intercom/docs/messaging/javascript/DOC.md +1844 -0
- package/dist/intercom/docs/messaging/python/DOC.md +1797 -0
- package/dist/jira/docs/issues/javascript/DOC.md +1420 -0
- package/dist/jira/docs/issues/python/DOC.md +1492 -0
- package/dist/kafka/docs/streaming/javascript/DOC.md +1671 -0
- package/dist/kafka/docs/streaming/python/DOC.md +1464 -0
- package/dist/landingai-ade/docs/api/DOC.md +620 -0
- package/dist/landingai-ade/docs/sdk/python/DOC.md +489 -0
- package/dist/landingai-ade/docs/sdk/typescript/DOC.md +542 -0
- package/dist/landingai-ade/skills/SKILL.md +489 -0
- package/dist/launchdarkly/docs/feature-flags/javascript/DOC.md +1191 -0
- package/dist/launchdarkly/docs/feature-flags/python/DOC.md +1671 -0
- package/dist/linear/docs/tracker/DOC.md +1554 -0
- package/dist/livekit/docs/realtime/javascript/DOC.md +303 -0
- package/dist/livekit/docs/realtime/python/DOC.md +163 -0
- package/dist/mailchimp/docs/marketing/DOC.md +1420 -0
- package/dist/meilisearch/docs/search/DOC.md +1241 -0
- package/dist/microsoft/docs/onedrive/javascript/DOC.md +1421 -0
- package/dist/microsoft/docs/onedrive/python/DOC.md +1549 -0
- package/dist/mongodb/docs/atlas/DOC.md +2041 -0
- package/dist/notion/docs/workspace-api/javascript/DOC.md +1435 -0
- package/dist/notion/docs/workspace-api/python/DOC.md +1400 -0
- package/dist/okta/docs/identity/javascript/DOC.md +1171 -0
- package/dist/okta/docs/identity/python/DOC.md +1401 -0
- package/dist/openai/docs/chat/javascript/DOC.md +407 -0
- package/dist/openai/docs/chat/python/DOC.md +568 -0
- package/dist/paypal/docs/checkout/DOC.md +278 -0
- package/dist/pinecone/docs/sdk/javascript/DOC.md +984 -0
- package/dist/pinecone/docs/sdk/python/DOC.md +1395 -0
- package/dist/plaid/docs/banking/javascript/DOC.md +1163 -0
- package/dist/plaid/docs/banking/python/DOC.md +1203 -0
- package/dist/playwright-community/skills/login-flows/SKILL.md +108 -0
- package/dist/postmark/docs/transactional-email/DOC.md +1168 -0
- package/dist/prisma/docs/orm/javascript/DOC.md +1419 -0
- package/dist/prisma/docs/orm/python/DOC.md +1317 -0
- package/dist/qdrant/docs/vector-search/javascript/DOC.md +1221 -0
- package/dist/qdrant/docs/vector-search/python/DOC.md +1653 -0
- package/dist/rabbitmq/docs/message-queue/javascript/DOC.md +1193 -0
- package/dist/rabbitmq/docs/message-queue/python/DOC.md +1243 -0
- package/dist/razorpay/docs/payments/javascript/DOC.md +1219 -0
- package/dist/razorpay/docs/payments/python/DOC.md +1330 -0
- package/dist/redis/docs/key-value/javascript/DOC.md +1851 -0
- package/dist/redis/docs/key-value/python/DOC.md +2054 -0
- package/dist/registry.json +2817 -0
- package/dist/replicate/docs/model-hosting/DOC.md +1318 -0
- package/dist/resend/docs/email/DOC.md +1271 -0
- package/dist/salesforce/docs/crm/javascript/DOC.md +1241 -0
- package/dist/salesforce/docs/crm/python/DOC.md +1183 -0
- package/dist/search-index.json +1 -0
- package/dist/sendgrid/docs/email-api/javascript/DOC.md +371 -0
- package/dist/sendgrid/docs/email-api/python/DOC.md +656 -0
- package/dist/sentry/docs/error-tracking/javascript/DOC.md +1073 -0
- package/dist/sentry/docs/error-tracking/python/DOC.md +1309 -0
- package/dist/shopify/docs/storefront/DOC.md +457 -0
- package/dist/slack/docs/workspace/javascript/DOC.md +933 -0
- package/dist/slack/docs/workspace/python/DOC.md +271 -0
- package/dist/square/docs/payments/javascript/DOC.md +1855 -0
- package/dist/square/docs/payments/python/DOC.md +1728 -0
- package/dist/stripe/docs/api/DOC.md +1727 -0
- package/dist/stripe/docs/payments/DOC.md +1726 -0
- package/dist/stytch/docs/auth/javascript/DOC.md +1813 -0
- package/dist/stytch/docs/auth/python/DOC.md +1962 -0
- package/dist/supabase/docs/client/DOC.md +1606 -0
- package/dist/twilio/docs/messaging/python/DOC.md +469 -0
- package/dist/twilio/docs/messaging/typescript/DOC.md +946 -0
- package/dist/vercel/docs/platform/DOC.md +1940 -0
- package/dist/weaviate/docs/vector-db/javascript/DOC.md +1268 -0
- package/dist/weaviate/docs/vector-db/python/DOC.md +1388 -0
- package/dist/zendesk/docs/support/javascript/DOC.md +2150 -0
- package/dist/zendesk/docs/support/python/DOC.md +2297 -0
- package/package.json +22 -6
- package/skills/get-api-docs/SKILL.md +84 -0
- package/src/commands/annotate.js +83 -0
- package/src/commands/build.js +12 -1
- package/src/commands/feedback.js +150 -0
- package/src/commands/get.js +83 -42
- package/src/commands/search.js +7 -0
- package/src/index.js +43 -17
- package/src/lib/analytics.js +90 -0
- package/src/lib/annotations.js +57 -0
- package/src/lib/bm25.js +170 -0
- package/src/lib/cache.js +69 -6
- package/src/lib/config.js +8 -3
- package/src/lib/identity.js +99 -0
- package/src/lib/registry.js +103 -20
- package/src/lib/telemetry.js +86 -0
- package/src/mcp/server.js +177 -0
- package/src/mcp/tools.js +251 -0
|
@@ -0,0 +1,1554 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tracker
|
|
3
|
+
description: "Linear SDK for JavaScript/TypeScript for issue tracking and project management via GraphQL"
|
|
4
|
+
metadata:
|
|
5
|
+
languages: "javascript"
|
|
6
|
+
versions: "62.0.0"
|
|
7
|
+
updated-on: "2026-03-02"
|
|
8
|
+
source: maintainer
|
|
9
|
+
tags: "linear,tracker,issues,project-management,graphql"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# Linear SDK for JavaScript/TypeScript
|
|
13
|
+
|
|
14
|
+
## Golden Rule
|
|
15
|
+
|
|
16
|
+
**ALWAYS use `@linear/sdk` version 62.0.0 or later.**
|
|
17
|
+
|
|
18
|
+
Install with:
|
|
19
|
+
```bash
|
|
20
|
+
npm install @linear/sdk
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
**DO NOT use:**
|
|
24
|
+
- Unofficial Linear packages
|
|
25
|
+
- Direct GraphQL requests without the SDK (unless specifically required)
|
|
26
|
+
- Deprecated authentication methods
|
|
27
|
+
- Outdated Linear client libraries
|
|
28
|
+
|
|
29
|
+
The `@linear/sdk` is the official Linear TypeScript SDK that provides strongly-typed access to Linear's GraphQL API.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Installation
|
|
34
|
+
|
|
35
|
+
### Install the SDK
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @linear/sdk
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Environment Setup
|
|
42
|
+
|
|
43
|
+
Create a `.env` file:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
LINEAR_API_KEY=lin_api_your_personal_api_key_here
|
|
47
|
+
# OR for OAuth
|
|
48
|
+
LINEAR_ACCESS_TOKEN=your_oauth_access_token_here
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### TypeScript Configuration (Optional)
|
|
52
|
+
|
|
53
|
+
The SDK ships with TypeScript types. No additional `@types` package needed.
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"compilerOptions": {
|
|
58
|
+
"target": "ES2020",
|
|
59
|
+
"module": "commonjs",
|
|
60
|
+
"esModuleInterop": true,
|
|
61
|
+
"strict": true
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Initialization
|
|
69
|
+
|
|
70
|
+
### Import the Client
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { LinearClient } from '@linear/sdk'
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
const { LinearClient } = require('@linear/sdk')
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Authentication with API Key
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { LinearClient } from '@linear/sdk'
|
|
84
|
+
|
|
85
|
+
const client = new LinearClient({
|
|
86
|
+
apiKey: process.env.LINEAR_API_KEY
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
const { LinearClient } = require('@linear/sdk')
|
|
92
|
+
require('dotenv').config()
|
|
93
|
+
|
|
94
|
+
const client = new LinearClient({
|
|
95
|
+
apiKey: process.env.LINEAR_API_KEY
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Authentication with OAuth 2.0
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
const client = new LinearClient({
|
|
103
|
+
accessToken: process.env.LINEAR_ACCESS_TOKEN
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Get Current User
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
async function getCurrentUser() {
|
|
111
|
+
const me = await client.viewer
|
|
112
|
+
console.log(`Logged in as: ${me.name} (${me.email})`)
|
|
113
|
+
return me
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Core API Surfaces
|
|
120
|
+
|
|
121
|
+
### Issues
|
|
122
|
+
|
|
123
|
+
#### Fetch All Issues
|
|
124
|
+
|
|
125
|
+
**Minimal:**
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
async function getAllIssues() {
|
|
129
|
+
const issues = await client.issues()
|
|
130
|
+
|
|
131
|
+
issues.nodes.forEach(issue => {
|
|
132
|
+
console.log(`${issue.identifier}: ${issue.title}`)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
return issues
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Advanced with Pagination:**
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
async function getIssuesPaginated() {
|
|
143
|
+
const issues = await client.issues({
|
|
144
|
+
first: 50,
|
|
145
|
+
orderBy: 'createdAt'
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
for (const issue of issues.nodes) {
|
|
149
|
+
console.log(`[${issue.identifier}] ${issue.title}`)
|
|
150
|
+
console.log(` Status: ${(await issue.state)?.name}`)
|
|
151
|
+
console.log(` Assignee: ${(await issue.assignee)?.displayName || 'Unassigned'}`)
|
|
152
|
+
console.log(` Priority: ${issue.priority}`)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Handle pagination
|
|
156
|
+
if (issues.pageInfo.hasNextPage) {
|
|
157
|
+
const nextPage = await client.issues({
|
|
158
|
+
first: 50,
|
|
159
|
+
after: issues.pageInfo.endCursor
|
|
160
|
+
})
|
|
161
|
+
// Process next page
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return issues
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### Get User's Assigned Issues
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
async function getMyIssues() {
|
|
172
|
+
const me = await client.viewer
|
|
173
|
+
const myIssues = await me.assignedIssues()
|
|
174
|
+
|
|
175
|
+
if (myIssues.nodes.length) {
|
|
176
|
+
myIssues.nodes.forEach(issue => {
|
|
177
|
+
console.log(`${me.displayName} has issue: ${issue.title}`)
|
|
178
|
+
})
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return myIssues
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### Query Single Issue by ID
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
async function getIssueById(issueId: string) {
|
|
189
|
+
const issue = await client.issue(issueId)
|
|
190
|
+
|
|
191
|
+
console.log(`Title: ${issue.title}`)
|
|
192
|
+
console.log(`Description: ${issue.description}`)
|
|
193
|
+
console.log(`Created: ${issue.createdAt}`)
|
|
194
|
+
|
|
195
|
+
const state = await issue.state
|
|
196
|
+
console.log(`State: ${state?.name}`)
|
|
197
|
+
|
|
198
|
+
const assignee = await issue.assignee
|
|
199
|
+
console.log(`Assignee: ${assignee?.displayName}`)
|
|
200
|
+
|
|
201
|
+
return issue
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Usage: Can use UUID or identifier like "ENG-123"
|
|
205
|
+
getIssueById("ENG-123")
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
#### Create Issue
|
|
209
|
+
|
|
210
|
+
**Minimal:**
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
async function createIssue(teamId: string, title: string) {
|
|
214
|
+
const issuePayload = await client.issueCreate({
|
|
215
|
+
teamId: teamId,
|
|
216
|
+
title: title
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
if (issuePayload.success) {
|
|
220
|
+
console.log(`Created issue: ${issuePayload.issue?.identifier}`)
|
|
221
|
+
return issuePayload.issue
|
|
222
|
+
} else {
|
|
223
|
+
throw new Error('Failed to create issue')
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Advanced with All Fields:**
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
async function createDetailedIssue() {
|
|
232
|
+
// First get team ID
|
|
233
|
+
const teams = await client.teams()
|
|
234
|
+
const team = teams.nodes[0]
|
|
235
|
+
|
|
236
|
+
// Get workflow state
|
|
237
|
+
const states = await client.workflowStates({
|
|
238
|
+
filter: { team: { id: { eq: team.id } } }
|
|
239
|
+
})
|
|
240
|
+
const todoState = states.nodes.find(s => s.name === 'Todo')
|
|
241
|
+
|
|
242
|
+
// Get labels
|
|
243
|
+
const labels = await client.issueLabels()
|
|
244
|
+
const bugLabel = labels.nodes.find(l => l.name === 'Bug')
|
|
245
|
+
|
|
246
|
+
const issuePayload = await client.issueCreate({
|
|
247
|
+
teamId: team.id,
|
|
248
|
+
title: 'Fix authentication error',
|
|
249
|
+
description: '## Problem\n\nUsers cannot log in\n\n## Steps to reproduce\n\n1. Go to login\n2. Enter credentials',
|
|
250
|
+
priority: 1, // 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low
|
|
251
|
+
estimate: 3, // Story points
|
|
252
|
+
stateId: todoState?.id,
|
|
253
|
+
labelIds: bugLabel ? [bugLabel.id] : [],
|
|
254
|
+
assigneeId: (await client.viewer).id,
|
|
255
|
+
dueDate: new Date('2025-12-31')
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
if (issuePayload.success) {
|
|
259
|
+
return issuePayload.issue
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
throw new Error('Failed to create issue')
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
#### Update Issue
|
|
267
|
+
|
|
268
|
+
**Minimal:**
|
|
269
|
+
|
|
270
|
+
```typescript
|
|
271
|
+
async function updateIssue(issueId: string, title: string) {
|
|
272
|
+
const updatePayload = await client.issueUpdate(issueId, {
|
|
273
|
+
title: title
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
if (updatePayload.success) {
|
|
277
|
+
console.log('Issue updated')
|
|
278
|
+
return updatePayload.issue
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Advanced:**
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
async function updateIssueComplete(issueId: string) {
|
|
287
|
+
// Get "Done" state
|
|
288
|
+
const issue = await client.issue(issueId)
|
|
289
|
+
const team = await issue.team
|
|
290
|
+
const states = await client.workflowStates({
|
|
291
|
+
filter: { team: { id: { eq: team.id } } }
|
|
292
|
+
})
|
|
293
|
+
const doneState = states.nodes.find(s => s.name === 'Done')
|
|
294
|
+
|
|
295
|
+
const updatePayload = await client.issueUpdate(issueId, {
|
|
296
|
+
stateId: doneState?.id,
|
|
297
|
+
title: 'Updated Issue Title',
|
|
298
|
+
description: 'New description',
|
|
299
|
+
priority: 3,
|
|
300
|
+
estimate: 5
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
if (updatePayload.success) {
|
|
304
|
+
console.log(`Updated: ${updatePayload.issue?.title}`)
|
|
305
|
+
return updatePayload.issue
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
#### Filter Issues
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
async function filterIssues() {
|
|
314
|
+
const issues = await client.issues({
|
|
315
|
+
filter: {
|
|
316
|
+
assignee: {
|
|
317
|
+
email: { eq: 'user@example.com' }
|
|
318
|
+
},
|
|
319
|
+
state: {
|
|
320
|
+
name: { in: ['Todo', 'In Progress'] }
|
|
321
|
+
},
|
|
322
|
+
priority: {
|
|
323
|
+
gte: 2 // High priority and above
|
|
324
|
+
},
|
|
325
|
+
createdAt: {
|
|
326
|
+
gte: new Date('2025-01-01')
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
orderBy: 'priority',
|
|
330
|
+
first: 25
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
return issues.nodes
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
#### Complex Filtering with AND/OR
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
async function complexFilterIssues() {
|
|
341
|
+
const issues = await client.issues({
|
|
342
|
+
filter: {
|
|
343
|
+
or: [
|
|
344
|
+
{ priority: { eq: 1 } }, // Urgent
|
|
345
|
+
{
|
|
346
|
+
and: [
|
|
347
|
+
{ priority: { eq: 2 } }, // High
|
|
348
|
+
{ dueDate: { lte: new Date() } } // Overdue
|
|
349
|
+
]
|
|
350
|
+
}
|
|
351
|
+
]
|
|
352
|
+
}
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
return issues.nodes
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
#### Archive Issue
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
async function archiveIssue(issueId: string) {
|
|
363
|
+
const payload = await client.issueArchive(issueId)
|
|
364
|
+
|
|
365
|
+
if (payload.success) {
|
|
366
|
+
console.log('Issue archived')
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return payload
|
|
370
|
+
}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Comments
|
|
374
|
+
|
|
375
|
+
#### Get Issue Comments
|
|
376
|
+
|
|
377
|
+
```typescript
|
|
378
|
+
async function getIssueComments(issueId: string) {
|
|
379
|
+
const issue = await client.issue(issueId)
|
|
380
|
+
const comments = await issue.comments()
|
|
381
|
+
|
|
382
|
+
for (const comment of comments.nodes) {
|
|
383
|
+
const user = await comment.user
|
|
384
|
+
console.log(`${user?.displayName}: ${comment.body}`)
|
|
385
|
+
console.log(`Posted: ${comment.createdAt}`)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
return comments
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
#### Create Comment
|
|
393
|
+
|
|
394
|
+
**Minimal:**
|
|
395
|
+
|
|
396
|
+
```typescript
|
|
397
|
+
async function addComment(issueId: string, body: string) {
|
|
398
|
+
const commentPayload = await client.commentCreate({
|
|
399
|
+
issueId: issueId,
|
|
400
|
+
body: body
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
if (commentPayload.success) {
|
|
404
|
+
console.log('Comment added')
|
|
405
|
+
return commentPayload.comment
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
throw new Error('Failed to create comment')
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**Advanced with Markdown:**
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
async function addDetailedComment(issueId: string) {
|
|
416
|
+
const body = `## Update
|
|
417
|
+
|
|
418
|
+
I've investigated this issue and found:
|
|
419
|
+
|
|
420
|
+
- The authentication token expires too quickly
|
|
421
|
+
- Need to implement refresh token logic
|
|
422
|
+
|
|
423
|
+
**Next steps:**
|
|
424
|
+
1. Update token service
|
|
425
|
+
2. Add refresh endpoint
|
|
426
|
+
3. Test token renewal
|
|
427
|
+
|
|
428
|
+
cc @teammate`
|
|
429
|
+
|
|
430
|
+
const commentPayload = await client.commentCreate({
|
|
431
|
+
issueId: issueId,
|
|
432
|
+
body: body
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
return commentPayload.comment
|
|
436
|
+
}
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### Update Comment
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
async function updateComment(commentId: string, newBody: string) {
|
|
443
|
+
const payload = await client.commentUpdate(commentId, {
|
|
444
|
+
body: newBody
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
if (payload.success) {
|
|
448
|
+
return payload.comment
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### Delete Comment
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
async function deleteComment(commentId: string) {
|
|
457
|
+
const payload = await client.commentDelete(commentId)
|
|
458
|
+
|
|
459
|
+
if (payload.success) {
|
|
460
|
+
console.log('Comment deleted')
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return payload
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Teams
|
|
468
|
+
|
|
469
|
+
#### Get All Teams
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
async function getTeams() {
|
|
473
|
+
const teams = await client.teams()
|
|
474
|
+
|
|
475
|
+
teams.nodes.forEach(team => {
|
|
476
|
+
console.log(`${team.name} (${team.key})`)
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
return teams
|
|
480
|
+
}
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
#### Get Team by ID
|
|
484
|
+
|
|
485
|
+
```typescript
|
|
486
|
+
async function getTeam(teamId: string) {
|
|
487
|
+
const team = await client.team(teamId)
|
|
488
|
+
|
|
489
|
+
console.log(`Team: ${team.name}`)
|
|
490
|
+
console.log(`Key: ${team.key}`)
|
|
491
|
+
console.log(`Description: ${team.description}`)
|
|
492
|
+
|
|
493
|
+
return team
|
|
494
|
+
}
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
#### Get Team Issues
|
|
498
|
+
|
|
499
|
+
```typescript
|
|
500
|
+
async function getTeamIssues(teamId: string) {
|
|
501
|
+
const team = await client.team(teamId)
|
|
502
|
+
const issues = await team.issues({
|
|
503
|
+
first: 50,
|
|
504
|
+
orderBy: 'updatedAt'
|
|
505
|
+
})
|
|
506
|
+
|
|
507
|
+
console.log(`${team.name} has ${issues.nodes.length} issues`)
|
|
508
|
+
|
|
509
|
+
return issues
|
|
510
|
+
}
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
#### Get Team Members
|
|
514
|
+
|
|
515
|
+
```typescript
|
|
516
|
+
async function getTeamMembers(teamId: string) {
|
|
517
|
+
const team = await client.team(teamId)
|
|
518
|
+
const members = await team.members()
|
|
519
|
+
|
|
520
|
+
for (const member of members.nodes) {
|
|
521
|
+
console.log(`${member.displayName} - ${member.email}`)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return members
|
|
525
|
+
}
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
### Projects
|
|
529
|
+
|
|
530
|
+
#### Get All Projects
|
|
531
|
+
|
|
532
|
+
```typescript
|
|
533
|
+
async function getProjects() {
|
|
534
|
+
const projects = await client.projects()
|
|
535
|
+
|
|
536
|
+
for (const project of projects.nodes) {
|
|
537
|
+
console.log(`${project.name}`)
|
|
538
|
+
console.log(` State: ${project.state}`)
|
|
539
|
+
console.log(` Progress: ${project.progress}%`)
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
return projects
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
#### Get Project by ID
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
async function getProject(projectId: string) {
|
|
550
|
+
const project = await client.project(projectId)
|
|
551
|
+
|
|
552
|
+
console.log(`Project: ${project.name}`)
|
|
553
|
+
console.log(`Description: ${project.description}`)
|
|
554
|
+
console.log(`Start: ${project.startDate}`)
|
|
555
|
+
console.log(`Target: ${project.targetDate}`)
|
|
556
|
+
|
|
557
|
+
const lead = await project.lead
|
|
558
|
+
console.log(`Lead: ${lead?.displayName}`)
|
|
559
|
+
|
|
560
|
+
return project
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
#### Get Project Issues
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
async function getProjectIssues(projectId: string) {
|
|
568
|
+
const project = await client.project(projectId)
|
|
569
|
+
const issues = await project.issues()
|
|
570
|
+
|
|
571
|
+
console.log(`${project.name} issues:`)
|
|
572
|
+
issues.nodes.forEach(issue => {
|
|
573
|
+
console.log(` - ${issue.identifier}: ${issue.title}`)
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
return issues
|
|
577
|
+
}
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
#### Create Project
|
|
581
|
+
|
|
582
|
+
```typescript
|
|
583
|
+
async function createProject(teamId: string) {
|
|
584
|
+
const payload = await client.projectCreate({
|
|
585
|
+
teamIds: [teamId],
|
|
586
|
+
name: 'Q4 Authentication Improvements',
|
|
587
|
+
description: 'Improve authentication flow and security',
|
|
588
|
+
state: 'started',
|
|
589
|
+
targetDate: new Date('2025-12-31')
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
if (payload.success) {
|
|
593
|
+
return payload.project
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
#### Update Project
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
async function updateProject(projectId: string) {
|
|
602
|
+
const payload = await client.projectUpdate(projectId, {
|
|
603
|
+
state: 'completed',
|
|
604
|
+
progress: 100
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
if (payload.success) {
|
|
608
|
+
console.log('Project completed')
|
|
609
|
+
return payload.project
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
### Labels
|
|
615
|
+
|
|
616
|
+
#### Get All Labels
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
async function getLabels() {
|
|
620
|
+
const labels = await client.issueLabels()
|
|
621
|
+
|
|
622
|
+
labels.nodes.forEach(label => {
|
|
623
|
+
console.log(`${label.name} - ${label.color}`)
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
return labels
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
#### Get Label by ID
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
async function getLabel(labelId: string) {
|
|
634
|
+
const label = await client.issueLabel(labelId)
|
|
635
|
+
|
|
636
|
+
console.log(`Label: ${label.name}`)
|
|
637
|
+
console.log(`Description: ${label.description}`)
|
|
638
|
+
|
|
639
|
+
return label
|
|
640
|
+
}
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
#### Create Label
|
|
644
|
+
|
|
645
|
+
```typescript
|
|
646
|
+
async function createLabel(teamId: string) {
|
|
647
|
+
const payload = await client.issueLabelCreate({
|
|
648
|
+
teamId: teamId,
|
|
649
|
+
name: 'security',
|
|
650
|
+
description: 'Security related issues',
|
|
651
|
+
color: '#ff0000'
|
|
652
|
+
})
|
|
653
|
+
|
|
654
|
+
if (payload.success) {
|
|
655
|
+
return payload.issueLabel
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
```
|
|
659
|
+
|
|
660
|
+
#### Filter Issues by Label
|
|
661
|
+
|
|
662
|
+
```typescript
|
|
663
|
+
async function getIssuesByLabel(labelName: string) {
|
|
664
|
+
const issues = await client.issues({
|
|
665
|
+
filter: {
|
|
666
|
+
labels: {
|
|
667
|
+
name: { eq: labelName }
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
})
|
|
671
|
+
|
|
672
|
+
return issues.nodes
|
|
673
|
+
}
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
### Workflow States
|
|
677
|
+
|
|
678
|
+
#### Get All Workflow States
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
async function getWorkflowStates() {
|
|
682
|
+
const states = await client.workflowStates()
|
|
683
|
+
|
|
684
|
+
states.nodes.forEach(state => {
|
|
685
|
+
console.log(`${state.name} (${state.type})`)
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
return states
|
|
689
|
+
}
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
#### Get Workflow States for Team
|
|
693
|
+
|
|
694
|
+
```typescript
|
|
695
|
+
async function getTeamWorkflowStates(teamId: string) {
|
|
696
|
+
const states = await client.workflowStates({
|
|
697
|
+
filter: {
|
|
698
|
+
team: { id: { eq: teamId } }
|
|
699
|
+
}
|
|
700
|
+
})
|
|
701
|
+
|
|
702
|
+
console.log('Available states:')
|
|
703
|
+
states.nodes.forEach(state => {
|
|
704
|
+
console.log(` - ${state.name}`)
|
|
705
|
+
})
|
|
706
|
+
|
|
707
|
+
return states
|
|
708
|
+
}
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
#### Get Issues in Specific State
|
|
712
|
+
|
|
713
|
+
```typescript
|
|
714
|
+
async function getIssuesInState(stateId: string) {
|
|
715
|
+
const state = await client.workflowState(stateId)
|
|
716
|
+
const issues = await state.issues()
|
|
717
|
+
|
|
718
|
+
console.log(`Issues in "${state.name}":`)
|
|
719
|
+
issues.nodes.forEach(issue => {
|
|
720
|
+
console.log(` ${issue.identifier}: ${issue.title}`)
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
return issues
|
|
724
|
+
}
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
### Users
|
|
728
|
+
|
|
729
|
+
#### Get Current User
|
|
730
|
+
|
|
731
|
+
```typescript
|
|
732
|
+
async function getCurrentUser() {
|
|
733
|
+
const me = await client.viewer
|
|
734
|
+
|
|
735
|
+
console.log(`Name: ${me.displayName}`)
|
|
736
|
+
console.log(`Email: ${me.email}`)
|
|
737
|
+
console.log(`Admin: ${me.admin}`)
|
|
738
|
+
|
|
739
|
+
return me
|
|
740
|
+
}
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
#### Get User by ID
|
|
744
|
+
|
|
745
|
+
```typescript
|
|
746
|
+
async function getUser(userId: string) {
|
|
747
|
+
const user = await client.user(userId)
|
|
748
|
+
|
|
749
|
+
console.log(`${user.displayName}`)
|
|
750
|
+
console.log(`Email: ${user.email}`)
|
|
751
|
+
console.log(`Active: ${user.active}`)
|
|
752
|
+
|
|
753
|
+
return user
|
|
754
|
+
}
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
#### Get All Users
|
|
758
|
+
|
|
759
|
+
```typescript
|
|
760
|
+
async function getUsers() {
|
|
761
|
+
const users = await client.users()
|
|
762
|
+
|
|
763
|
+
users.nodes.forEach(user => {
|
|
764
|
+
console.log(`${user.displayName} - ${user.email}`)
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
return users
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
#### Get User's Teams
|
|
772
|
+
|
|
773
|
+
```typescript
|
|
774
|
+
async function getUserTeams(userId: string) {
|
|
775
|
+
const user = await client.user(userId)
|
|
776
|
+
const teams = await user.teams()
|
|
777
|
+
|
|
778
|
+
console.log(`${user.displayName}'s teams:`)
|
|
779
|
+
teams.nodes.forEach(team => {
|
|
780
|
+
console.log(` - ${team.name}`)
|
|
781
|
+
})
|
|
782
|
+
|
|
783
|
+
return teams
|
|
784
|
+
}
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Cycles
|
|
788
|
+
|
|
789
|
+
#### Get Active Cycles
|
|
790
|
+
|
|
791
|
+
```typescript
|
|
792
|
+
async function getActiveCycles() {
|
|
793
|
+
const cycles = await client.cycles({
|
|
794
|
+
filter: {
|
|
795
|
+
isActive: { eq: true }
|
|
796
|
+
}
|
|
797
|
+
})
|
|
798
|
+
|
|
799
|
+
for (const cycle of cycles.nodes) {
|
|
800
|
+
console.log(`${cycle.name}`)
|
|
801
|
+
console.log(` Start: ${cycle.startsAt}`)
|
|
802
|
+
console.log(` End: ${cycle.endsAt}`)
|
|
803
|
+
console.log(` Progress: ${cycle.progress}%`)
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return cycles
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
#### Get Cycle Issues
|
|
811
|
+
|
|
812
|
+
```typescript
|
|
813
|
+
async function getCycleIssues(cycleId: string) {
|
|
814
|
+
const cycle = await client.cycle(cycleId)
|
|
815
|
+
const issues = await cycle.issues()
|
|
816
|
+
|
|
817
|
+
console.log(`${cycle.name} issues:`)
|
|
818
|
+
issues.nodes.forEach(issue => {
|
|
819
|
+
console.log(` ${issue.identifier}: ${issue.title}`)
|
|
820
|
+
})
|
|
821
|
+
|
|
822
|
+
return issues
|
|
823
|
+
}
|
|
824
|
+
```
|
|
825
|
+
|
|
826
|
+
### Attachments
|
|
827
|
+
|
|
828
|
+
#### Create Attachment
|
|
829
|
+
|
|
830
|
+
```typescript
|
|
831
|
+
async function createAttachment(issueId: string) {
|
|
832
|
+
const payload = await client.attachmentCreate({
|
|
833
|
+
issueId: issueId,
|
|
834
|
+
title: 'Design Mockup',
|
|
835
|
+
url: 'https://example.com/mockup.png',
|
|
836
|
+
subtitle: 'Login page redesign'
|
|
837
|
+
})
|
|
838
|
+
|
|
839
|
+
if (payload.success) {
|
|
840
|
+
return payload.attachment
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
#### Get Issue Attachments
|
|
846
|
+
|
|
847
|
+
```typescript
|
|
848
|
+
async function getIssueAttachments(issueId: string) {
|
|
849
|
+
const issue = await client.issue(issueId)
|
|
850
|
+
const attachments = await issue.attachments()
|
|
851
|
+
|
|
852
|
+
attachments.nodes.forEach(attachment => {
|
|
853
|
+
console.log(`${attachment.title}: ${attachment.url}`)
|
|
854
|
+
})
|
|
855
|
+
|
|
856
|
+
return attachments
|
|
857
|
+
}
|
|
858
|
+
```
|
|
859
|
+
|
|
860
|
+
### Webhooks
|
|
861
|
+
|
|
862
|
+
#### Create Webhook
|
|
863
|
+
|
|
864
|
+
```typescript
|
|
865
|
+
async function createWebhook() {
|
|
866
|
+
const payload = await client.webhookCreate({
|
|
867
|
+
url: 'https://example.com/webhook',
|
|
868
|
+
label: 'Issue Updates',
|
|
869
|
+
resourceTypes: ['Issue', 'Comment'],
|
|
870
|
+
enabled: true
|
|
871
|
+
})
|
|
872
|
+
|
|
873
|
+
if (payload.success) {
|
|
874
|
+
console.log(`Webhook created: ${payload.webhook?.id}`)
|
|
875
|
+
return payload.webhook
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
```
|
|
879
|
+
|
|
880
|
+
#### Get Webhooks
|
|
881
|
+
|
|
882
|
+
```typescript
|
|
883
|
+
async function getWebhooks() {
|
|
884
|
+
const webhooks = await client.webhooks()
|
|
885
|
+
|
|
886
|
+
webhooks.nodes.forEach(webhook => {
|
|
887
|
+
console.log(`${webhook.label}`)
|
|
888
|
+
console.log(` URL: ${webhook.url}`)
|
|
889
|
+
console.log(` Enabled: ${webhook.enabled}`)
|
|
890
|
+
console.log(` Resources: ${webhook.resourceTypes.join(', ')}`)
|
|
891
|
+
})
|
|
892
|
+
|
|
893
|
+
return webhooks
|
|
894
|
+
}
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
#### Update Webhook
|
|
898
|
+
|
|
899
|
+
```typescript
|
|
900
|
+
async function updateWebhook(webhookId: string) {
|
|
901
|
+
const payload = await client.webhookUpdate(webhookId, {
|
|
902
|
+
enabled: false
|
|
903
|
+
})
|
|
904
|
+
|
|
905
|
+
if (payload.success) {
|
|
906
|
+
return payload.webhook
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
#### Delete Webhook
|
|
912
|
+
|
|
913
|
+
```typescript
|
|
914
|
+
async function deleteWebhook(webhookId: string) {
|
|
915
|
+
const payload = await client.webhookDelete(webhookId)
|
|
916
|
+
|
|
917
|
+
if (payload.success) {
|
|
918
|
+
console.log('Webhook deleted')
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
return payload
|
|
922
|
+
}
|
|
923
|
+
```
|
|
924
|
+
|
|
925
|
+
### Search
|
|
926
|
+
|
|
927
|
+
#### Search Issues
|
|
928
|
+
|
|
929
|
+
```typescript
|
|
930
|
+
async function searchIssues(query: string) {
|
|
931
|
+
const results = await client.searchIssues(query)
|
|
932
|
+
|
|
933
|
+
results.nodes.forEach(issue => {
|
|
934
|
+
console.log(`${issue.identifier}: ${issue.title}`)
|
|
935
|
+
})
|
|
936
|
+
|
|
937
|
+
return results
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Usage
|
|
941
|
+
searchIssues('authentication bug')
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
#### Search Projects
|
|
945
|
+
|
|
946
|
+
```typescript
|
|
947
|
+
async function searchProjects(query: string) {
|
|
948
|
+
const results = await client.searchProjects(query)
|
|
949
|
+
|
|
950
|
+
results.nodes.forEach(project => {
|
|
951
|
+
console.log(`${project.name}`)
|
|
952
|
+
})
|
|
953
|
+
|
|
954
|
+
return results
|
|
955
|
+
}
|
|
956
|
+
```
|
|
957
|
+
|
|
958
|
+
---
|
|
959
|
+
|
|
960
|
+
## Pagination
|
|
961
|
+
|
|
962
|
+
### Basic Pagination
|
|
963
|
+
|
|
964
|
+
```typescript
|
|
965
|
+
async function paginateIssues() {
|
|
966
|
+
let allIssues = []
|
|
967
|
+
let hasNextPage = true
|
|
968
|
+
let cursor = null
|
|
969
|
+
|
|
970
|
+
while (hasNextPage) {
|
|
971
|
+
const issues = await client.issues({
|
|
972
|
+
first: 50,
|
|
973
|
+
after: cursor
|
|
974
|
+
})
|
|
975
|
+
|
|
976
|
+
allIssues.push(...issues.nodes)
|
|
977
|
+
|
|
978
|
+
hasNextPage = issues.pageInfo.hasNextPage
|
|
979
|
+
cursor = issues.pageInfo.endCursor
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
console.log(`Total issues: ${allIssues.length}`)
|
|
983
|
+
return allIssues
|
|
984
|
+
}
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
### Reverse Pagination
|
|
988
|
+
|
|
989
|
+
```typescript
|
|
990
|
+
async function paginateReverse() {
|
|
991
|
+
const issues = await client.issues({
|
|
992
|
+
last: 25,
|
|
993
|
+
before: someCursor
|
|
994
|
+
})
|
|
995
|
+
|
|
996
|
+
return issues.nodes
|
|
997
|
+
}
|
|
998
|
+
```
|
|
999
|
+
|
|
1000
|
+
---
|
|
1001
|
+
|
|
1002
|
+
## Error Handling
|
|
1003
|
+
|
|
1004
|
+
### Try-Catch Pattern
|
|
1005
|
+
|
|
1006
|
+
```typescript
|
|
1007
|
+
async function safeCreateIssue(teamId: string, title: string) {
|
|
1008
|
+
try {
|
|
1009
|
+
const payload = await client.issueCreate({
|
|
1010
|
+
teamId: teamId,
|
|
1011
|
+
title: title
|
|
1012
|
+
})
|
|
1013
|
+
|
|
1014
|
+
if (!payload.success) {
|
|
1015
|
+
console.error('Issue creation failed')
|
|
1016
|
+
return null
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
return payload.issue
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
console.error('Error creating issue:', error.message)
|
|
1022
|
+
throw error
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
```
|
|
1026
|
+
|
|
1027
|
+
### Check Success Field
|
|
1028
|
+
|
|
1029
|
+
```typescript
|
|
1030
|
+
async function updateWithCheck(issueId: string) {
|
|
1031
|
+
const payload = await client.issueUpdate(issueId, {
|
|
1032
|
+
title: 'Updated Title'
|
|
1033
|
+
})
|
|
1034
|
+
|
|
1035
|
+
if (payload.success) {
|
|
1036
|
+
console.log('Update successful')
|
|
1037
|
+
return payload.issue
|
|
1038
|
+
} else {
|
|
1039
|
+
console.error('Update failed')
|
|
1040
|
+
return null
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
---
|
|
1046
|
+
|
|
1047
|
+
## Advanced Operations
|
|
1048
|
+
|
|
1049
|
+
### Batch Operations
|
|
1050
|
+
|
|
1051
|
+
```typescript
|
|
1052
|
+
async function batchCreateIssues(teamId: string, titles: string[]) {
|
|
1053
|
+
const promises = titles.map(title =>
|
|
1054
|
+
client.issueCreate({
|
|
1055
|
+
teamId: teamId,
|
|
1056
|
+
title: title
|
|
1057
|
+
})
|
|
1058
|
+
)
|
|
1059
|
+
|
|
1060
|
+
const results = await Promise.all(promises)
|
|
1061
|
+
|
|
1062
|
+
const created = results.filter(r => r.success)
|
|
1063
|
+
console.log(`Created ${created.length}/${titles.length} issues`)
|
|
1064
|
+
|
|
1065
|
+
return created.map(r => r.issue)
|
|
1066
|
+
}
|
|
1067
|
+
```
|
|
1068
|
+
|
|
1069
|
+
### Complex Relationships
|
|
1070
|
+
|
|
1071
|
+
```typescript
|
|
1072
|
+
async function getCompleteIssueData(issueId: string) {
|
|
1073
|
+
const issue = await client.issue(issueId)
|
|
1074
|
+
|
|
1075
|
+
// Fetch all related data
|
|
1076
|
+
const [state, assignee, team, comments, attachments, labels, project, cycle] = await Promise.all([
|
|
1077
|
+
issue.state,
|
|
1078
|
+
issue.assignee,
|
|
1079
|
+
issue.team,
|
|
1080
|
+
issue.comments(),
|
|
1081
|
+
issue.attachments(),
|
|
1082
|
+
issue.labels(),
|
|
1083
|
+
issue.project,
|
|
1084
|
+
issue.cycle
|
|
1085
|
+
])
|
|
1086
|
+
|
|
1087
|
+
return {
|
|
1088
|
+
issue,
|
|
1089
|
+
state,
|
|
1090
|
+
assignee,
|
|
1091
|
+
team,
|
|
1092
|
+
comments: comments.nodes,
|
|
1093
|
+
attachments: attachments.nodes,
|
|
1094
|
+
labels: labels.nodes,
|
|
1095
|
+
project,
|
|
1096
|
+
cycle
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
### Conditional Updates
|
|
1102
|
+
|
|
1103
|
+
```typescript
|
|
1104
|
+
async function updateIfAssigned(issueId: string) {
|
|
1105
|
+
const issue = await client.issue(issueId)
|
|
1106
|
+
const assignee = await issue.assignee
|
|
1107
|
+
|
|
1108
|
+
if (assignee) {
|
|
1109
|
+
await client.issueUpdate(issueId, {
|
|
1110
|
+
priority: 2 // Increase priority if assigned
|
|
1111
|
+
})
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
```
|
|
1115
|
+
|
|
1116
|
+
### Archive Completed Issues
|
|
1117
|
+
|
|
1118
|
+
```typescript
|
|
1119
|
+
async function archiveCompletedIssues(teamId: string) {
|
|
1120
|
+
const team = await client.team(teamId)
|
|
1121
|
+
const states = await client.workflowStates({
|
|
1122
|
+
filter: { team: { id: { eq: teamId } } }
|
|
1123
|
+
})
|
|
1124
|
+
|
|
1125
|
+
const doneState = states.nodes.find(s => s.name === 'Done')
|
|
1126
|
+
|
|
1127
|
+
if (!doneState) return
|
|
1128
|
+
|
|
1129
|
+
const issues = await client.issues({
|
|
1130
|
+
filter: {
|
|
1131
|
+
state: { id: { eq: doneState.id } },
|
|
1132
|
+
completedAt: { lte: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000) } // 30 days ago
|
|
1133
|
+
}
|
|
1134
|
+
})
|
|
1135
|
+
|
|
1136
|
+
for (const issue of issues.nodes) {
|
|
1137
|
+
await client.issueArchive(issue.id)
|
|
1138
|
+
console.log(`Archived: ${issue.identifier}`)
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
```
|
|
1142
|
+
|
|
1143
|
+
---
|
|
1144
|
+
|
|
1145
|
+
## Real-World Examples
|
|
1146
|
+
|
|
1147
|
+
### Daily Standup Report
|
|
1148
|
+
|
|
1149
|
+
```typescript
|
|
1150
|
+
async function generateStandupReport() {
|
|
1151
|
+
const me = await client.viewer
|
|
1152
|
+
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000)
|
|
1153
|
+
|
|
1154
|
+
// Issues completed yesterday
|
|
1155
|
+
const completed = await client.issues({
|
|
1156
|
+
filter: {
|
|
1157
|
+
assignee: { id: { eq: me.id } },
|
|
1158
|
+
completedAt: { gte: yesterday }
|
|
1159
|
+
}
|
|
1160
|
+
})
|
|
1161
|
+
|
|
1162
|
+
// Issues in progress
|
|
1163
|
+
const inProgress = await client.issues({
|
|
1164
|
+
filter: {
|
|
1165
|
+
assignee: { id: { eq: me.id } },
|
|
1166
|
+
state: { name: { eq: 'In Progress' } }
|
|
1167
|
+
}
|
|
1168
|
+
})
|
|
1169
|
+
|
|
1170
|
+
console.log('## Completed Yesterday')
|
|
1171
|
+
completed.nodes.forEach(i => console.log(`- ${i.identifier}: ${i.title}`))
|
|
1172
|
+
|
|
1173
|
+
console.log('\n## In Progress')
|
|
1174
|
+
inProgress.nodes.forEach(i => console.log(`- ${i.identifier}: ${i.title}`))
|
|
1175
|
+
|
|
1176
|
+
return { completed: completed.nodes, inProgress: inProgress.nodes }
|
|
1177
|
+
}
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
### Team Velocity Report
|
|
1181
|
+
|
|
1182
|
+
```typescript
|
|
1183
|
+
async function calculateTeamVelocity(teamId: string, days: number = 7) {
|
|
1184
|
+
const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000)
|
|
1185
|
+
|
|
1186
|
+
const completed = await client.issues({
|
|
1187
|
+
filter: {
|
|
1188
|
+
team: { id: { eq: teamId } },
|
|
1189
|
+
completedAt: { gte: since }
|
|
1190
|
+
}
|
|
1191
|
+
})
|
|
1192
|
+
|
|
1193
|
+
let totalEstimate = 0
|
|
1194
|
+
for (const issue of completed.nodes) {
|
|
1195
|
+
totalEstimate += issue.estimate || 0
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
console.log(`Team completed ${completed.nodes.length} issues`)
|
|
1199
|
+
console.log(`Total points: ${totalEstimate}`)
|
|
1200
|
+
console.log(`Velocity: ${(totalEstimate / days * 7).toFixed(1)} points/week`)
|
|
1201
|
+
|
|
1202
|
+
return {
|
|
1203
|
+
issueCount: completed.nodes.length,
|
|
1204
|
+
totalPoints: totalEstimate,
|
|
1205
|
+
velocityPerWeek: totalEstimate / days * 7
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
### Bug Triage
|
|
1211
|
+
|
|
1212
|
+
```typescript
|
|
1213
|
+
async function triageBugs(teamId: string) {
|
|
1214
|
+
const labels = await client.issueLabels()
|
|
1215
|
+
const bugLabel = labels.nodes.find(l => l.name.toLowerCase() === 'bug')
|
|
1216
|
+
|
|
1217
|
+
if (!bugLabel) {
|
|
1218
|
+
console.log('No bug label found')
|
|
1219
|
+
return
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const bugs = await client.issues({
|
|
1223
|
+
filter: {
|
|
1224
|
+
team: { id: { eq: teamId } },
|
|
1225
|
+
labels: { id: { eq: bugLabel.id } },
|
|
1226
|
+
priority: { eq: 0 } // No priority set
|
|
1227
|
+
},
|
|
1228
|
+
orderBy: 'createdAt'
|
|
1229
|
+
})
|
|
1230
|
+
|
|
1231
|
+
console.log(`Found ${bugs.nodes.length} untriaged bugs`)
|
|
1232
|
+
|
|
1233
|
+
for (const bug of bugs.nodes) {
|
|
1234
|
+
console.log(`\n${bug.identifier}: ${bug.title}`)
|
|
1235
|
+
console.log(`Created: ${bug.createdAt}`)
|
|
1236
|
+
|
|
1237
|
+
// Auto-prioritize based on keywords
|
|
1238
|
+
const desc = (bug.description || '').toLowerCase()
|
|
1239
|
+
let priority = 4 // Low
|
|
1240
|
+
|
|
1241
|
+
if (desc.includes('crash') || desc.includes('error')) {
|
|
1242
|
+
priority = 2 // High
|
|
1243
|
+
} else if (desc.includes('login') || desc.includes('payment')) {
|
|
1244
|
+
priority = 1 // Urgent
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
await client.issueUpdate(bug.id, { priority })
|
|
1248
|
+
console.log(`Set priority to ${priority}`)
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
### Sync GitHub Issues to Linear
|
|
1254
|
+
|
|
1255
|
+
```typescript
|
|
1256
|
+
async function syncGitHubIssue(teamId: string, githubIssue: any) {
|
|
1257
|
+
const existingIssues = await client.issues({
|
|
1258
|
+
filter: {
|
|
1259
|
+
team: { id: { eq: teamId } },
|
|
1260
|
+
title: { contains: githubIssue.title }
|
|
1261
|
+
}
|
|
1262
|
+
})
|
|
1263
|
+
|
|
1264
|
+
if (existingIssues.nodes.length > 0) {
|
|
1265
|
+
console.log('Issue already exists')
|
|
1266
|
+
return existingIssues.nodes[0]
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const labels = await client.issueLabels()
|
|
1270
|
+
const githubLabel = labels.nodes.find(l => l.name === 'github')
|
|
1271
|
+
|
|
1272
|
+
const payload = await client.issueCreate({
|
|
1273
|
+
teamId: teamId,
|
|
1274
|
+
title: githubIssue.title,
|
|
1275
|
+
description: `${githubIssue.body}\n\n---\nSource: ${githubIssue.html_url}`,
|
|
1276
|
+
labelIds: githubLabel ? [githubLabel.id] : []
|
|
1277
|
+
})
|
|
1278
|
+
|
|
1279
|
+
if (payload.success) {
|
|
1280
|
+
console.log(`Created Linear issue: ${payload.issue?.identifier}`)
|
|
1281
|
+
return payload.issue
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
```
|
|
1285
|
+
|
|
1286
|
+
### Automated Sprint Planning
|
|
1287
|
+
|
|
1288
|
+
```typescript
|
|
1289
|
+
async function planSprint(teamId: string, sprintCapacity: number) {
|
|
1290
|
+
const states = await client.workflowStates({
|
|
1291
|
+
filter: { team: { id: { eq: teamId } } }
|
|
1292
|
+
})
|
|
1293
|
+
const backlogState = states.nodes.find(s => s.name === 'Backlog')
|
|
1294
|
+
const todoState = states.nodes.find(s => s.name === 'Todo')
|
|
1295
|
+
|
|
1296
|
+
if (!backlogState || !todoState) return
|
|
1297
|
+
|
|
1298
|
+
const backlogIssues = await client.issues({
|
|
1299
|
+
filter: {
|
|
1300
|
+
team: { id: { eq: teamId } },
|
|
1301
|
+
state: { id: { eq: backlogState.id } }
|
|
1302
|
+
},
|
|
1303
|
+
orderBy: 'priority'
|
|
1304
|
+
})
|
|
1305
|
+
|
|
1306
|
+
let totalEstimate = 0
|
|
1307
|
+
const sprintIssues = []
|
|
1308
|
+
|
|
1309
|
+
for (const issue of backlogIssues.nodes) {
|
|
1310
|
+
const estimate = issue.estimate || 1
|
|
1311
|
+
|
|
1312
|
+
if (totalEstimate + estimate <= sprintCapacity) {
|
|
1313
|
+
await client.issueUpdate(issue.id, {
|
|
1314
|
+
stateId: todoState.id
|
|
1315
|
+
})
|
|
1316
|
+
|
|
1317
|
+
sprintIssues.push(issue)
|
|
1318
|
+
totalEstimate += estimate
|
|
1319
|
+
|
|
1320
|
+
console.log(`Added to sprint: ${issue.identifier} (${estimate} points)`)
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
if (totalEstimate >= sprintCapacity) break
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
console.log(`\nSprint planned with ${totalEstimate}/${sprintCapacity} points`)
|
|
1327
|
+
return sprintIssues
|
|
1328
|
+
}
|
|
1329
|
+
```
|
|
1330
|
+
|
|
1331
|
+
---
|
|
1332
|
+
|
|
1333
|
+
## Environment Variable Configuration
|
|
1334
|
+
|
|
1335
|
+
### Complete Setup Example
|
|
1336
|
+
|
|
1337
|
+
```typescript
|
|
1338
|
+
import { LinearClient } from '@linear/sdk'
|
|
1339
|
+
import * as dotenv from 'dotenv'
|
|
1340
|
+
|
|
1341
|
+
dotenv.config()
|
|
1342
|
+
|
|
1343
|
+
interface LinearConfig {
|
|
1344
|
+
apiKey?: string
|
|
1345
|
+
accessToken?: string
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
function createLinearClient(): LinearClient {
|
|
1349
|
+
const config: LinearConfig = {}
|
|
1350
|
+
|
|
1351
|
+
if (process.env.LINEAR_API_KEY) {
|
|
1352
|
+
config.apiKey = process.env.LINEAR_API_KEY
|
|
1353
|
+
} else if (process.env.LINEAR_ACCESS_TOKEN) {
|
|
1354
|
+
config.accessToken = process.env.LINEAR_ACCESS_TOKEN
|
|
1355
|
+
} else {
|
|
1356
|
+
throw new Error('LINEAR_API_KEY or LINEAR_ACCESS_TOKEN must be set')
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
return new LinearClient(config)
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
export const client = createLinearClient()
|
|
1363
|
+
```
|
|
1364
|
+
|
|
1365
|
+
---
|
|
1366
|
+
|
|
1367
|
+
## Type Definitions
|
|
1368
|
+
|
|
1369
|
+
### Important Types
|
|
1370
|
+
|
|
1371
|
+
```typescript
|
|
1372
|
+
import {
|
|
1373
|
+
LinearClient,
|
|
1374
|
+
LinearFetch,
|
|
1375
|
+
User,
|
|
1376
|
+
Issue,
|
|
1377
|
+
IssueConnection,
|
|
1378
|
+
Team,
|
|
1379
|
+
Project,
|
|
1380
|
+
Comment,
|
|
1381
|
+
WorkflowState,
|
|
1382
|
+
IssueLabel
|
|
1383
|
+
} from '@linear/sdk'
|
|
1384
|
+
|
|
1385
|
+
// LinearFetch is a Promise-like type
|
|
1386
|
+
const user: LinearFetch<User> = client.viewer
|
|
1387
|
+
|
|
1388
|
+
// Connections have nodes and pageInfo
|
|
1389
|
+
const issues: LinearFetch<IssueConnection> = client.issues()
|
|
1390
|
+
|
|
1391
|
+
// Access the actual data
|
|
1392
|
+
const issuesData = await issues
|
|
1393
|
+
const firstIssue: Issue = issuesData.nodes[0]
|
|
1394
|
+
```
|
|
1395
|
+
|
|
1396
|
+
### Custom Types
|
|
1397
|
+
|
|
1398
|
+
```typescript
|
|
1399
|
+
interface IssueWithDetails {
|
|
1400
|
+
issue: Issue
|
|
1401
|
+
state: WorkflowState | null
|
|
1402
|
+
assignee: User | null
|
|
1403
|
+
team: Team
|
|
1404
|
+
labels: IssueLabel[]
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
async function getIssueWithDetails(id: string): Promise<IssueWithDetails> {
|
|
1408
|
+
const issue = await client.issue(id)
|
|
1409
|
+
|
|
1410
|
+
return {
|
|
1411
|
+
issue,
|
|
1412
|
+
state: await issue.state,
|
|
1413
|
+
assignee: await issue.assignee,
|
|
1414
|
+
team: await issue.team,
|
|
1415
|
+
labels: (await issue.labels()).nodes
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
```
|
|
1419
|
+
|
|
1420
|
+
---
|
|
1421
|
+
|
|
1422
|
+
## Priority Values
|
|
1423
|
+
|
|
1424
|
+
```typescript
|
|
1425
|
+
// Priority mapping
|
|
1426
|
+
const PRIORITY = {
|
|
1427
|
+
NONE: 0,
|
|
1428
|
+
URGENT: 1,
|
|
1429
|
+
HIGH: 2,
|
|
1430
|
+
MEDIUM: 3,
|
|
1431
|
+
LOW: 4
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
// Usage
|
|
1435
|
+
await client.issueCreate({
|
|
1436
|
+
teamId: teamId,
|
|
1437
|
+
title: 'Critical bug',
|
|
1438
|
+
priority: PRIORITY.URGENT
|
|
1439
|
+
})
|
|
1440
|
+
```
|
|
1441
|
+
|
|
1442
|
+
---
|
|
1443
|
+
|
|
1444
|
+
## GraphQL Direct Queries (Advanced)
|
|
1445
|
+
|
|
1446
|
+
### Raw GraphQL Query
|
|
1447
|
+
|
|
1448
|
+
```typescript
|
|
1449
|
+
async function rawGraphQL() {
|
|
1450
|
+
const query = `
|
|
1451
|
+
query {
|
|
1452
|
+
viewer {
|
|
1453
|
+
id
|
|
1454
|
+
name
|
|
1455
|
+
assignedIssues(first: 10) {
|
|
1456
|
+
nodes {
|
|
1457
|
+
id
|
|
1458
|
+
title
|
|
1459
|
+
state {
|
|
1460
|
+
name
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
`
|
|
1467
|
+
|
|
1468
|
+
// The SDK wraps the GraphQL API, but you can access raw client if needed
|
|
1469
|
+
// For most use cases, use the typed SDK methods instead
|
|
1470
|
+
}
|
|
1471
|
+
```
|
|
1472
|
+
|
|
1473
|
+
---
|
|
1474
|
+
|
|
1475
|
+
## Complete Application Example
|
|
1476
|
+
|
|
1477
|
+
```typescript
|
|
1478
|
+
import { LinearClient } from '@linear/sdk'
|
|
1479
|
+
import * as dotenv from 'dotenv'
|
|
1480
|
+
|
|
1481
|
+
dotenv.config()
|
|
1482
|
+
|
|
1483
|
+
const client = new LinearClient({
|
|
1484
|
+
apiKey: process.env.LINEAR_API_KEY
|
|
1485
|
+
})
|
|
1486
|
+
|
|
1487
|
+
async function main() {
|
|
1488
|
+
try {
|
|
1489
|
+
// Get current user
|
|
1490
|
+
const me = await client.viewer
|
|
1491
|
+
console.log(`Logged in as: ${me.displayName}`)
|
|
1492
|
+
|
|
1493
|
+
// Get teams
|
|
1494
|
+
const teams = await client.teams()
|
|
1495
|
+
const myTeam = teams.nodes[0]
|
|
1496
|
+
console.log(`Working with team: ${myTeam.name}`)
|
|
1497
|
+
|
|
1498
|
+
// Create an issue
|
|
1499
|
+
const issuePayload = await client.issueCreate({
|
|
1500
|
+
teamId: myTeam.id,
|
|
1501
|
+
title: 'Test issue from SDK',
|
|
1502
|
+
description: 'This is a test issue created via the Linear SDK',
|
|
1503
|
+
priority: 3
|
|
1504
|
+
})
|
|
1505
|
+
|
|
1506
|
+
if (issuePayload.success && issuePayload.issue) {
|
|
1507
|
+
const issue = issuePayload.issue
|
|
1508
|
+
console.log(`Created issue: ${issue.identifier}`)
|
|
1509
|
+
|
|
1510
|
+
// Add a comment
|
|
1511
|
+
const commentPayload = await client.commentCreate({
|
|
1512
|
+
issueId: issue.id,
|
|
1513
|
+
body: 'First comment on this issue!'
|
|
1514
|
+
})
|
|
1515
|
+
|
|
1516
|
+
if (commentPayload.success) {
|
|
1517
|
+
console.log('Comment added')
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// Get workflow states
|
|
1521
|
+
const states = await client.workflowStates({
|
|
1522
|
+
filter: { team: { id: { eq: myTeam.id } } }
|
|
1523
|
+
})
|
|
1524
|
+
|
|
1525
|
+
const inProgressState = states.nodes.find(s => s.name === 'In Progress')
|
|
1526
|
+
|
|
1527
|
+
if (inProgressState) {
|
|
1528
|
+
// Update issue state
|
|
1529
|
+
const updatePayload = await client.issueUpdate(issue.id, {
|
|
1530
|
+
stateId: inProgressState.id
|
|
1531
|
+
})
|
|
1532
|
+
|
|
1533
|
+
if (updatePayload.success) {
|
|
1534
|
+
console.log('Issue moved to In Progress')
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// Get updated issue data
|
|
1539
|
+
const updatedIssue = await client.issue(issue.id)
|
|
1540
|
+
const currentState = await updatedIssue.state
|
|
1541
|
+
console.log(`Current state: ${currentState?.name}`)
|
|
1542
|
+
|
|
1543
|
+
// Get all comments
|
|
1544
|
+
const comments = await updatedIssue.comments()
|
|
1545
|
+
console.log(`Issue has ${comments.nodes.length} comment(s)`)
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
console.error('Error:', error.message)
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
main()
|
|
1554
|
+
```
|