agentsclimarketplace

Linear install auth

Skill jeremylongshore/claude-code-plugins-plus-skills/plugins/saas-packs/linear-pack/skills/linear-install-auth

'Install and configure Linear SDK/CLI authentication.From its SKILL.md

Install
npx -y skills add jeremylongshore/claude-code-plugins-plus-skills --skill linear-install-auth

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

7.1 KB, ~1.5k tokens by cl100k_base, as published. Nobody here has run it

Linear Install & Auth

Overview

Install the @linear/sdk TypeScript SDK and configure authentication for the Linear GraphQL API at https://api.linear.app/graphql. Supports personal API keys for scripts and OAuth 2.0 (with PKCE) for user-facing apps.

Prerequisites

  • Node.js 18+ (SDK is TypeScript-first, works in any JS environment)
  • Package manager (npm, pnpm, or yarn)
  • Linear account with workspace access
  • For API key: Settings > Account > API > Personal API keys
  • For OAuth: Create app at Settings > Account > API > OAuth applications

Instructions

Step 1: Install the SDK

set -euo pipefail
npm install @linear/sdk
# or: pnpm add @linear/sdk
# or: yarn add @linear/sdk

The SDK exposes LinearClient, typed models for every entity (Issue, Project, Cycle, Team), and error classes (LinearError, InvalidInputLinearError).

Step 2: API Key Authentication (Scripts & Server-Side)

Generate a Personal API key at Linear Settings > Account > API > Personal API keys. Keys start with lin_api_ and are shown only once.

import { LinearClient } from "@linear/sdk";

// Environment variable (recommended)
const client = new LinearClient({
  apiKey: process.env.LINEAR_API_KEY,
});

// Verify connection
const me = await client.viewer;
console.log(`Authenticated as: ${me.name} (${me.email})`);

Environment setup:

# .env (never commit)
LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxx

# .gitignore
echo '.env' >> .gitignore

Step 3: OAuth 2.0 Authentication (User-Facing Apps)

Linear supports the standard Authorization Code flow with optional PKCE. As of October 2025, newly created OAuth apps issue refresh tokens by default.

import crypto from "crypto";

// 1. Build authorization URL
const SCOPES = ["read", "write", "issues:create"];
const state = crypto.randomBytes(16).toString("hex");

const authUrl = new URL("https://linear.app/oauth/authorize");
authUrl.searchParams.set("client_id", process.env.LINEAR_CLIENT_ID!);
authUrl.searchParams.set("redirect_uri", process.env.LINEAR_REDIRECT_URI!);
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("scope", SCOPES.join(","));
authUrl.searchParams.set("state", state);
// Optional PKCE:
// authUrl.searchParams.set("code_challenge", challenge);
// authUrl.searchParams.set("code_challenge_method", "S256");

// 2. Exchange authorization code for tokens
const tokenResponse = await fetch("https://api.linear.app/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "authorization_code",
    code: authorizationCode,
    client_id: process.env.LINEAR_CLIENT_ID!,
    client_secret: process.env.LINEAR_CLIENT_SECRET!,
    redirect_uri: process.env.LINEAR_REDIRECT_URI!,
  }),
});

const { access_token, refresh_token, expires_in } = await tokenResponse.json();

// 3. Create client with OAuth token
const client = new LinearClient({ accessToken: access_token });

Available OAuth scopes: read, write, issues:create, admin, initiative:read, initiative:write, customer:read, customer:write.

Step 4: Token Refresh (OAuth)

async function refreshAccessToken(refreshToken: string): Promise<string> {
  const response = await fetch("https://api.linear.app/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "refresh_token",
      refresh_token: refreshToken,
      client_id: process.env.LINEAR_CLIENT_ID!,
      client_secret: process.env.LINEAR_CLIENT_SECRET!,
    }),
  });

  if (!response.ok) {
    throw new Error(`Token refresh failed: ${response.status}`);
  }

  const tokens = await response.json();
  // Store new refresh_token — Linear rotates it on each refresh
  await saveTokens(tokens.access_token, tokens.refresh_token);
  return tokens.access_token;
}

Step 5: Validate Configuration on Startup

function validateLinearConfig(): void {
  const key = process.env.LINEAR_API_KEY;
  if (!key) throw new Error("LINEAR_API_KEY environment variable is required");
  if (!key.startsWith("lin_api_")) throw new Error("LINEAR_API_KEY must start with lin_api_");
  if (key.length < 30) throw new Error("LINEAR_API_KEY appears truncated");
}

// Call before creating client
validateLinearConfig();

Error Handling

ErrorCauseSolution
Authentication requiredInvalid, expired, or missing API keyRegenerate at Settings > Account > API
Invalid API key formatKey doesn't start with lin_api_Copy full key from Linear (shown once)
ForbiddenToken lacks required OAuth scopeRe-authorize with correct scopes
invalid_grant on token exchangeCode expired or PKCE verifier mismatchRestart OAuth flow; codes expire quickly
Module not found: @linear/sdkSDK not installedRun npm install @linear/sdk
ENOTFOUND api.linear.appDNS/firewall issueEnsure outbound HTTPS to api.linear.app

Examples

Raw GraphQL Without SDK

const response = await fetch("https://api.linear.app/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "Authorization": process.env.LINEAR_API_KEY!,
  },
  body: JSON.stringify({
    query: `{ viewer { id name email } }`,
  }),
});

const { data, errors } = await response.json();
if (errors) console.error("GraphQL errors:", errors);
else console.log("Viewer:", data.viewer);

Server-to-Server (Client Credentials)

// For apps without user interaction — uses client_credentials grant
const response = await fetch("https://api.linear.app/oauth/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "client_credentials",
    client_id: process.env.LINEAR_CLIENT_ID!,
    client_secret: process.env.LINEAR_CLIENT_SECRET!,
    scope: "read,write",
  }),
});

const { access_token } = await response.json();
const client = new LinearClient({ accessToken: access_token });

Resources

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,144. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.