mit einem Klick
linear-integration
// Linear API patterns and examples for autolinear. Includes authentication, webhooks, issue CRUD, state transitions, file attachments, and comment handling.
// Linear API patterns and examples for autolinear. Includes authentication, webhooks, issue CRUD, state transitions, file attachments, and comment handling.
| name | linear-integration |
| description | Linear API patterns and examples for autolinear. Includes authentication, webhooks, issue CRUD, state transitions, file attachments, and comment handling. |
| version | 0.1.0 |
| tags | ["linear","api","webhook","integration"] |
| keywords | ["linear","api","webhook","issue","comment","state","attachment"] |
| user-invocable | false |
plugin: autolinear updated: 2026-01-20
Version: 0.1.0 Purpose: Patterns for Linear API integration in autolinear workflows Status: Phase 1
Use this skill when you need to:
This skill provides patterns for:
Personal API Key (MVP):
import { LinearClient } from '@linear/sdk';
const linear = new LinearClient({
apiKey: process.env.LINEAR_API_KEY
});
Verification:
async function verifyConnection(): Promise<boolean> {
try {
const me = await linear.viewer;
console.log(`Connected as: ${me.name}`);
return true;
} catch (error) {
console.error('Linear connection failed:', error);
return false;
}
}
Bun HTTP Server:
import { serve } from 'bun';
import { createHmac } from 'crypto';
interface LinearWebhookPayload {
action: 'created' | 'updated' | 'deleted';
type: 'Issue' | 'Comment' | 'Label';
data: {
id: string;
title?: string;
description?: string;
state: { id: string; name: string };
labels: Array<{ id: string; name: string }>;
};
}
serve({
port: process.env.AUTOLINEAR_WEBHOOK_PORT || 3001,
async fetch(req: Request): Promise<Response> {
if (req.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
// Verify signature
const signature = req.headers.get('Linear-Signature');
const body = await req.text();
if (!verifySignature(body, signature)) {
return new Response('Unauthorized', { status: 401 });
}
const payload: LinearWebhookPayload = JSON.parse(body);
// Route to handler
await routeWebhook(payload);
return new Response('OK', { status: 200 });
}
});
function verifySignature(body: string, signature: string | null): boolean {
if (!signature) return false;
const hmac = createHmac('sha256', process.env.LINEAR_WEBHOOK_SECRET!);
const expectedSignature = hmac.update(body).digest('hex');
return signature === expectedSignature;
}
Create Issue:
async function createIssue(
teamId: string,
title: string,
description: string,
labels: string[]
): Promise<string> {
// Note: Linear SDK uses linear.createIssue() method
const result = await linear.createIssue({
teamId,
title,
description,
labelIds: await resolveLabelIds(labels),
assigneeId: process.env.AUTOLINEAR_BOT_USER_ID,
priority: 2,
});
const issue = await result.issue;
return issue!.id;
}
Query Issues:
async function getAutoLinearTasks(teamId: string) {
const issues = await linear.issues({
filter: {
team: { id: { eq: teamId } },
assignee: { id: { eq: process.env.AUTOLINEAR_BOT_USER_ID } },
state: { name: { in: ['Todo', 'In Progress'] } },
},
});
return issues.nodes;
}
Transition State:
async function transitionState(
issueId: string,
newStateName: string
): Promise<void> {
// Get workflow states for the issue's team
const issue = await linear.issue(issueId);
const team = await issue.team;
const states = await team.states();
const targetState = states.nodes.find(s => s.name === newStateName);
if (!targetState) {
throw new Error(`State "${newStateName}" not found`);
}
// Note: Linear SDK uses linear.updateIssue() method
await linear.updateIssue(issueId, {
stateId: targetState.id,
});
}
Upload and Attach:
async function attachFile(
issueId: string,
filePath: string,
fileName: string
): Promise<void> {
// Request upload URL
const uploadPayload = await linear.fileUpload(
getMimeType(filePath),
fileName,
getFileSize(filePath)
);
// Upload to storage
const fileContent = await Bun.file(filePath).arrayBuffer();
await fetch(uploadPayload.uploadUrl, {
method: 'PUT',
body: fileContent,
headers: { 'Content-Type': getMimeType(filePath) },
});
// Attach to issue
await linear.attachmentCreate({
issueId,
url: uploadPayload.assetUrl,
title: fileName,
});
}
Add Comment:
async function addComment(
issueId: string,
body: string
): Promise<void> {
// Note: Linear SDK uses linear.createComment() method
await linear.createComment({
issueId,
body,
});
}
// Create issue
const issueId = await createIssue(
teamId,
"Add user profile page",
"Implement user profile with avatar upload",
["frontend", "feature"]
);
// Transition to In Progress
await transitionState(issueId, "In Progress");
// ... work happens ...
// Attach proof artifacts
await attachFile(issueId, "screenshot.png", "Desktop Screenshot");
// Add completion comment
await addComment(issueId, "Implementation complete. See attached proof.");
// Transition to In Review
await transitionState(issueId, "In Review");
const tasks = await getAutoLinearTasks(teamId);
console.log(`AutoLinear queue: ${tasks.length} tasks`);
for (const task of tasks) {
console.log(`- ${task.identifier}: ${task.title} (${task.state.name})`);
}
Proof artifact generation patterns for task validation. Covers screenshots, test results, deployments, and confidence scoring.
Task lifecycle state transitions with validation gates. Defines states, triggers, and required proofs.
How tag-to-command routing works in autolinear. Defines default mappings, precedence rules, and customization patterns.