Skip to content

Built for people who want to own their automations. Join the waitlist for an invite.

Package listing

@kody/linear

README.md

234 lines · 8.4 KB · Markdown

@kody/linear

Intent

Provide reusable, account-agnostic Linear helpers so Kody agents can read and update issues, projects, teams, comments, and workflow states through a saved linear / linear-* OAuth integration or a personal API key — without hand-rolling GraphQL. Mutations are previewable with dryRun: true and only run live after confirm: true.

This listing is meant to be forked. After you fork, connect your Linear workspace. Do not treat the live @kody/linear package storage as yours.

This official OAuth/API-key package is the preferred invoke path. Setup is harder: bring your own Linear OAuth app or personal API key. For a faster first win, connect Linear MCP in Get started and use @kody/linear-mcp.

When To Use

  • List or inspect teams, projects, issues, comments, and workflow states
  • Create or update issues, comments, and projects after explicit confirmation
  • Call an unwrapped Linear GraphQL operation through ./request
  • Connect more than one Linear workspace via integrationName / account

Auth

Linear has no built-in Kody OAuth app. Choose one lane:

LaneWhen to useCredential
OAuth (recommended)Multi-account, refresh tokens, shared helpersSaved integration linear or linear-<purpose>
Personal API keyFastest for a single workspace / personal scriptsUser secret linearApiKey (or linearApiKey-<purpose>)

OAuth access tokens use Authorization: Bearer …. Personal API keys must be sent as Authorization: <API_KEY> without the Bearer prefix — this package handles that.

Required API host: api.linear.app. Approve it in the account secrets UI.

Lane A — BYO OAuth
  1. Create an OAuth application at https://linear.app/settings/api/applications/new
  2. Set the redirect URI exactly to https://kody.codes/connect/oauth
  3. Request scopes read,write (comma-separated; read is always present). Narrower mutation-only scopes: issues:create, comments:create.
  4. Connect while signed in to Kody:

https://kody.codes/connect/oauth?provider=linear&authorizeUrl=https%3A%2F%2Flinear.app%2Foauth%2Fauthorize&tokenUrl=https%3A%2F%2Fapi.linear.app%2Foauth%2Ftoken&apiBaseUrl=https%3A%2F%2Fapi.linear.app&scopes=read%2Cwrite&scopeSeparator=%2C&flow=confidential&allowedHosts=api.linear.app&dashboardUrl=https%3A%2F%2Flinear.app%2Fsettings%2Fapi%2Fapplications%2Fnew

  1. Paste the Linear client id and client secret into the Kody wizard (never into chat). Approve host api.linear.app.
  2. Reconnect later with https://kody.codes/connect/oauth?provider=linear

OAuth token URL: https://api.linear.app/oauth/token. Authorize URL: https://linear.app/oauth/authorize. Flow: confidential (client secret). Linear scopes are comma-separated (scopeSeparator=,).

To connect a second workspace, change provider (for example provider=linear-work) and pass integrationName: 'linear-work' on every call. Add prompt=consent on Linear's authorize screen if you need to pick a different workspace for the same app.

Lane B — Personal API key
  1. Create a key at https://linear.app/settings/account/security
  2. Grant Read plus the write permissions you need (Write, or Create issues / Create comments).
  3. Save it (do not paste the value in chat):

https://kody.codes/account/secrets/new?name=linearApiKey&description=Linear%20personal%20API%20key&allowedHosts=api.linear.app&scope=user

For a second workspace/key, use a distinct secret name such as linearApiKey-work and pass secretName: 'linearApiKey-work' (or account: 'work', which resolves to linear-work / linearApiKey-work).

Scopes

ScopeNeeded for
readAll queries (viewer, teams, projects, issues, comments, workflow states)
issues:create./create-issue
comments:create./create-comment
write./update-issue, ./create-project, other mutations
adminDo not request unless a workflow truly needs admin endpoints

If Linear returns 401/403 or a GraphQL FORBIDDEN / insufficient-scope error, helpers throw a message that names the missing scope and the next setup URL (reconnect OAuth with that scope, or save an API key with the matching permission).

Multiple accounts

Every export accepts:

  • integrationName / integration — exact saved OAuth name (linear-work)
  • accountworklinear-work; linear-work used as-is; omitted → linear
  • secretName — API key secret override
  • auth'oauth' or 'api-key' when both exist

Do not hard-code a personal workspace slug or alias.

Safety

Mutating helpers require confirm: true. Pass dryRun: true to inspect the GraphQL payload without calling Linear. ./request treats query documents as read-only; mutation documents need confirmation.

Exports

ExportDescription
.Package overview, connect URLs, export map
./accountsResolve integration/secret names and report what is connected
./smoke-testLocal helper checks plus optional live viewer read (no email)
./viewerAuthenticated viewer id / name / organization id
./list-teamsList teams
./get-teamGet one team by UUID or key
./list-projectsList projects
./get-projectGet one project by UUID
./create-projectPreview or create a project (teamIds required)
./list-issuesList issues (teamId / teamKey, projectId, assigneeId, stateType, query)
./get-issueGet one issue by UUID or identifier (ENG-123)
./create-issuePreview or create an issue (title + teamId)
./update-issuePreview or update an issue
./list-commentsList comments on an issue
./create-commentPreview or create a comment
./list-workflow-statesList workflow states (teamId / teamKey, type)
./requestGeneric GraphQL escape hatch
./typesShared TypeScript types

Priority values: 0 none, 1 urgent, 2 high, 3 medium, 4 low. Workflow state types: triage, backlog, unstarted, started, completed, canceled.

Smoke test

import smokeTest from 'kody:@kody/linear/smoke-test'

export default async function main() {
	return await smokeTest()
}

Without credentials this returns { ok: true, live: false } plus the connect and API-key URLs. After OAuth or an API key is saved it reads viewer { id } and returns { live: true, hasViewerId, hasOrganizationId } without email.

Preview a mutation without credentials:

import createIssue from 'kody:@kody/linear/create-issue'

export default async function main() {
	return await createIssue({
		title: 'Follow up on onboarding',
		teamId: '00000000-0000-0000-0000-000000000000',
		dryRun: true,
	})
}

Examples

import listIssues from 'kody:@kody/linear/list-issues'
import listWorkflowStates from 'kody:@kody/linear/list-workflow-states'

export default async function main() {
	const states = await listWorkflowStates({ teamKey: 'ENG' })
	return await listIssues({
		teamKey: 'ENG',
		stateType: 'started',
		first: 20,
	})
}
import createIssue from 'kody:@kody/linear/create-issue'

export default async function main() {
	const preview = await createIssue({
		title: 'Investigate checkout timeout',
		teamId: 'team-uuid',
		priority: 2,
		dryRun: true,
	})
	// After the user confirms the exact team and title:
	return await createIssue({
		title: 'Investigate checkout timeout',
		teamId: 'team-uuid',
		priority: 2,
		confirm: true,
	})
}

Unwrapped GraphQL:

import request from 'kody:@kody/linear/request'

export default async function main() {
	return await request({
		query: `query ($key: String!) {
			team(id: $key) { id name key }
		}`,
		variables: { key: 'ENG' },
	})
}

Notes

  • GraphQL endpoint: https://api.linear.app/graphql
  • Helpers project slim objects. Use ./request when you need extra fields.
  • assigneeId: 'me' on ./list-issues filters to the authorizing user.
  • This package is not affiliated with or endorsed by Linear Orbit, Inc.

Branding

The community icon is Linear's official logomark as served by Linear (https://static.linear.app/client/assets/favicon-D8hcELd9.svg), scaled to a square. Paths are unmodified. Linear® is a trademark of Linear Orbit, Inc.

Docs