Generates a full dedicated Docusaurus page for every one of the 132 skills
(73 bundled + 59 optional) under website/docs/user-guide/skills/{bundled,optional}/<category>/.
Each page carries the skill's description, metadata (version, author, license,
dependencies, platform gating, tags, related skills cross-linked to their own
pages), and the complete SKILL.md body that Hermes loads at runtime.
Previously the two catalog pages just listed skills with a one-line blurb and
no way to see what the skill actually did — users had to go read the source
repo. Now every skill has a browsable, searchable, cross-linked reference in
the docs.
- website/scripts/generate-skill-docs.py — generator that reads skills/ and
optional-skills/, writes per-skill pages, regenerates both catalog indexes,
and rewrites the Skills section of sidebars.ts. Handles MDX escaping
(outside fenced code blocks: curly braces, unsafe HTML-ish tags) and
rewrites relative references/*.md links to point at the GitHub source.
- website/docs/reference/skills-catalog.md — regenerated; each row links to
the new dedicated page.
- website/docs/reference/optional-skills-catalog.md — same.
- website/sidebars.ts — Skills section now has Bundled / Optional subtrees
with one nested category per skill folder.
- .github/workflows/{docs-site-checks,deploy-site}.yml — run the generator
before docusaurus build so CI stays in sync with the source SKILL.md files.
Build verified locally with `npx docusaurus build`. Only remaining warnings
are pre-existing broken link/anchor issues in unrelated pages.
12 KiB
title, sidebar_label, description
| title | sidebar_label | description |
|---|---|---|
| Linear — Manage Linear issues, projects, and teams via the GraphQL API | Linear | Manage Linear issues, projects, and teams via the GraphQL API |
{/* This page is auto-generated from the skill's SKILL.md by website/scripts/generate-skill-docs.py. Edit the source SKILL.md, not this page. */}
Linear
Manage Linear issues, projects, and teams via the GraphQL API. Create, update, search, and organize issues. Uses API key auth (no OAuth needed). All operations via curl — no dependencies.
Skill metadata
| Source | Bundled (installed by default) |
| Path | skills/productivity/linear |
| Version | 1.0.0 |
| Author | Hermes Agent |
| License | MIT |
| Tags | Linear, Project Management, Issues, GraphQL, API, Productivity |
Reference: full SKILL.md
:::info The following is the complete skill definition that Hermes loads when this skill is triggered. This is what the agent sees as instructions when the skill is active. :::
Linear — Issue & Project Management
Manage Linear issues, projects, and teams directly via the GraphQL API using curl. No MCP server, no OAuth flow, no extra dependencies.
Setup
- Get a personal API key from Linear Settings > API > Personal API keys
- Set
LINEAR_API_KEYin your environment (viahermes setupor your env config)
API Basics
- Endpoint:
https://api.linear.app/graphql(POST) - Auth header:
Authorization: $LINEAR_API_KEY(no "Bearer" prefix for API keys) - All requests are POST with
Content-Type: application/json - Both UUIDs and short identifiers (e.g.,
ENG-123) work forissue(id:)
Base curl pattern:
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ viewer { id name } }"}' | python3 -m json.tool
Workflow States
Linear uses WorkflowState objects with a type field. 6 state types:
| Type | Description |
|---|---|
triage |
Incoming issues needing review |
backlog |
Acknowledged but not yet planned |
unstarted |
Planned/ready but not started |
started |
Actively being worked on |
completed |
Done |
canceled |
Won't do |
Each team has its own named states (e.g., "In Progress" is type started). To change an issue's status, you need the stateId (UUID) of the target state — query workflow states first.
Priority values: 0 = None, 1 = Urgent, 2 = High, 3 = Medium, 4 = Low
Common Queries
Get current user
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ viewer { id name email } }"}' | python3 -m json.tool
List teams
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ teams { nodes { id name key } } }"}' | python3 -m json.tool
List workflow states for a team
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ workflowStates(filter: { team: { key: { eq: \"ENG\" } } }) { nodes { id name type } } }"}' | python3 -m json.tool
List issues (first 20)
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ issues(first: 20) { nodes { identifier title priority state { name type } assignee { name } team { key } url } pageInfo { hasNextPage endCursor } } }"}' | python3 -m json.tool
List my assigned issues
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ viewer { assignedIssues(first: 25) { nodes { identifier title state { name type } priority url } } } }"}' | python3 -m json.tool
Get a single issue (by identifier like ENG-123)
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ issue(id: \"ENG-123\") { id identifier title description priority state { id name type } assignee { id name } team { key } project { name } labels { nodes { name } } comments { nodes { body user { name } createdAt } } url } }"}' | python3 -m json.tool
Search issues by text
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ issueSearch(query: \"bug login\", first: 10) { nodes { identifier title state { name } assignee { name } url } } }"}' | python3 -m json.tool
Filter issues by state type
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ issues(filter: { state: { type: { in: [\"started\"] } } }, first: 20) { nodes { identifier title state { name } assignee { name } } } }"}' | python3 -m json.tool
Filter by team and assignee
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ issues(filter: { team: { key: { eq: \"ENG\" } }, assignee: { email: { eq: \"user@example.com\" } } }, first: 20) { nodes { identifier title state { name } priority } } }"}' | python3 -m json.tool
List projects
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ projects(first: 20) { nodes { id name description progress lead { name } teams { nodes { key } } url } } }"}' | python3 -m json.tool
List team members
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ users { nodes { id name email active } } }"}' | python3 -m json.tool
List labels
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ issueLabels { nodes { id name color } } }"}' | python3 -m json.tool
Common Mutations
Create an issue
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($input: IssueCreateInput!) { issueCreate(input: $input) { success issue { id identifier title url } } }",
"variables": {
"input": {
"teamId": "TEAM_UUID",
"title": "Fix login bug",
"description": "Users cannot login with SSO",
"priority": 2
}
}
}' | python3 -m json.tool
Update issue status
First get the target state UUID from the workflow states query above, then:
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { issueUpdate(id: \"ENG-123\", input: { stateId: \"STATE_UUID\" }) { success issue { identifier state { name type } } } }"}' | python3 -m json.tool
Assign an issue
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { issueUpdate(id: \"ENG-123\", input: { assigneeId: \"USER_UUID\" }) { success issue { identifier assignee { name } } } }"}' | python3 -m json.tool
Set priority
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { issueUpdate(id: \"ENG-123\", input: { priority: 1 }) { success issue { identifier priority } } }"}' | python3 -m json.tool
Add a comment
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { commentCreate(input: { issueId: \"ISSUE_UUID\", body: \"Investigated. Root cause is X.\" }) { success comment { id body } } }"}' | python3 -m json.tool
Set due date
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { issueUpdate(id: \"ENG-123\", input: { dueDate: \"2026-04-01\" }) { success issue { identifier dueDate } } }"}' | python3 -m json.tool
Add labels to an issue
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { issueUpdate(id: \"ENG-123\", input: { labelIds: [\"LABEL_UUID_1\", \"LABEL_UUID_2\"] }) { success issue { identifier labels { nodes { name } } } } }"}' | python3 -m json.tool
Add issue to a project
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "mutation { issueUpdate(id: \"ENG-123\", input: { projectId: \"PROJECT_UUID\" }) { success issue { identifier project { name } } } }"}' | python3 -m json.tool
Create a project
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"query": "mutation($input: ProjectCreateInput!) { projectCreate(input: $input) { success project { id name url } } }",
"variables": {
"input": {
"name": "Q2 Auth Overhaul",
"description": "Replace legacy auth with OAuth2 and PKCE",
"teamIds": ["TEAM_UUID"]
}
}
}' | python3 -m json.tool
Pagination
Linear uses Relay-style cursor pagination:
# First page
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ issues(first: 20) { nodes { identifier title } pageInfo { hasNextPage endCursor } } }"}' | python3 -m json.tool
# Next page — use endCursor from previous response
curl -s -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"query": "{ issues(first: 20, after: \"CURSOR_FROM_PREVIOUS\") { nodes { identifier title } pageInfo { hasNextPage endCursor } } }"}' | python3 -m json.tool
Default page size: 50. Max: 250. Always use first: N to limit results.
Filtering Reference
Comparators: eq, neq, in, nin, lt, lte, gt, gte, contains, startsWith, containsIgnoreCase
Combine filters with or: [...] for OR logic (default is AND within a filter object).
Typical Workflow
- Query teams to get team IDs and keys
- Query workflow states for target team to get state UUIDs
- List or search issues to find what needs work
- Create issues with team ID, title, description, priority
- Update status by setting
stateIdto the target workflow state - Add comments to track progress
- Mark complete by setting
stateIdto the team's "completed" type state
Rate Limits
- 5,000 requests/hour per API key
- 3,000,000 complexity points/hour
- Use
first: Nto limit results and reduce complexity cost - Monitor
X-RateLimit-Requests-Remainingresponse header
Important Notes
- Always use
terminaltool withcurlfor API calls — do NOT useweb_extractorbrowser - Always check the
errorsarray in GraphQL responses — HTTP 200 can still contain errors - If
stateIdis omitted when creating issues, Linear defaults to the first backlog state - The
descriptionfield supports Markdown - Use
python3 -m json.toolorjqto format JSON responses for readability