AgentStack
SKILL unreviewed MIT Self-run

Linear

skill-furkangonel-cowrangler-linear · by furkangonel

Linear project management — issues, cycles, and projects via GraphQL API.

No reviews yet
0 installs
6 views
0.0% view→install

Install

$ agentstack add skill-furkangonel-cowrangler-linear

Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Reads credentials/environment and may exfiltrate them.

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Linear? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Linear Project Management SOP

Create and update issues, manage cycles (sprints), query project status, and automate workflows via the Linear GraphQL API.

When to Use

  • User wants to create a new Linear issue
  • User wants to update issue status, priority, or assignee
  • User wants to list issues for a team or cycle
  • User wants to query active sprint / cycle progress
  • User wants to bulk-create or bulk-update issues

Part 1 — Auth Setup

1. Get an API Key

  1. Go to https://linear.app/settings/api
  2. Click Create key → name it → copy the key (shown only once)
export LINEAR_API_KEY="lin_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

Or store in ~/.cowrangler/credentials.env:

LINEAR_API_KEY=lin_api_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

GraphQL Helper

LINEAR_API_KEY="${LINEAR_API_KEY:-$(grep '^LINEAR_API_KEY=' ~/.cowrangler/credentials.env 2>/dev/null | cut -d= -f2 | tr -d '\n\r')}"

linear_query() {
  # Usage: linear_query '{ viewer { id name } }'
  curl -s -X POST https://api.linear.app/graphql \
    -H "Authorization: $LINEAR_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"query\": \"$1\"}"
}

linear_mutate() {
  # Usage: linear_mutate 'mutation { ... }' '{"input": {...}}'
  curl -s -X POST https://api.linear.app/graphql \
    -H "Authorization: $LINEAR_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"query\": \"$1\", \"variables\": $2}"
}

Verify Auth

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 -c "import sys,json; d=json.load(sys.stdin); print(d['data']['viewer'])"

Part 2 — Key IDs You'll Need

Before creating issues, discover your team and workflow state IDs:

# List all 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 -c "
import sys, json
teams = json.load(sys.stdin)['data']['teams']['nodes']
for t in teams:
    print(f\"{t['id']}  {t['key']:10s}  {t['name']}\")
"
# List workflow states for a team
TEAM_ID="your-team-id"
curl -s -X POST https://api.linear.app/graphql \
  -H "Authorization: $LINEAR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"query\": \"{ workflowStates(filter: { team: { id: { eq: \\\"$TEAM_ID\\\" } } }) { nodes { id name type } } }\" }" \
  | python3 -c "
import sys, json
states = json.load(sys.stdin)['data']['workflowStates']['nodes']
for s in states:
    print(f\"{s['id']}  {s['type']:12s}  {s['name']}\")
"

Part 3 — Issues

Create an Issue

import os, json, urllib.request

API_KEY = os.environ["LINEAR_API_KEY"]

def linear(query, variables=None):
    body = {"query": query}
    if variables:
        body["variables"] = variables
    req = urllib.request.Request(
        "https://api.linear.app/graphql",
        data=json.dumps(body).encode(),
        headers={"Authorization": API_KEY, "Content-Type": "application/json"},
        method="POST",
    )
    with urllib.request.urlopen(req) as r:
        return json.load(r)

result = linear(
    """
    mutation CreateIssue($input: IssueCreateInput!) {
      issueCreate(input: $input) {
        success
        issue {
          id
          identifier
          url
          title
        }
      }
    }
    """,
    {
        "input": {
            "teamId": "TEAM_ID",
            "title": "Add rate limiting to the API",
            "description": "Implement token-bucket rate limiting on all public endpoints.\n\n## Acceptance Criteria\n- 100 req/min per API key\n- Returns 429 with Retry-After header\n- Logged in metrics dashboard",
            "priority": 2,          # 0=No priority, 1=Urgent, 2=High, 3=Medium, 4=Low
            "stateId": "STATE_ID",  # "Todo" state ID from workflow states query
            "assigneeId": "USER_ID",  # optional
            "estimate": 3,           # story points, optional
            "labelIds": ["LABEL_ID"],  # optional
        }
    }
)
issue = result["data"]["issueCreate"]["issue"]
print(f"Created {issue['identifier']}: {issue['url']}")

Update an Issue

result = linear(
    """
    mutation UpdateIssue($id: String!, $input: IssueUpdateInput!) {
      issueUpdate(id: $id, input: $input) {
        success
        issue { id identifier title state { name } }
      }
    }
    """,
    {
        "id": "ISSUE_ID",
        "input": {
            "stateId": "IN_PROGRESS_STATE_ID",
            "assigneeId": "USER_ID",
            "priority": 1,  # Urgent
        }
    }
)
print(result["data"]["issueUpdate"]["issue"])

List Team Issues

result = linear(
    """
    query TeamIssues($teamId: String!, $states: [String!]) {
      issues(
        filter: {
          team: { id: { eq: $teamId } }
          state: { type: { in: $states } }
        }
        orderBy: priority
        first: 50
      ) {
        nodes {
          id
          identifier
          title
          priority
          state { name }
          assignee { name }
          estimate
          dueDate
        }
      }
    }
    """,
    {
        "teamId": "TEAM_ID",
        "states": ["started", "unstarted"]  # state types: triage, backlog, unstarted, started, completed, cancelled
    }
)
for issue in result["data"]["issues"]["nodes"]:
    assignee = issue["assignee"]["name"] if issue["assignee"] else "unassigned"
    print(f"{issue['identifier']:12s}  P{issue['priority']}  {assignee:20s}  {issue['title']}")

Search Issues

result = linear(
    """
    query SearchIssues($query: String!) {
      issueSearch(query: $query, first: 20) {
        nodes {
          identifier
          title
          state { name }
          url
        }
      }
    }
    """,
    {"query": "rate limiting"}
)
for issue in result["data"]["issueSearch"]["nodes"]:
    print(f"{issue['identifier']}  [{issue['state']['name']}]  {issue['title']}")

Part 4 — Cycles (Sprints)

List Active Cycle

result = linear(
    """
    query ActiveCycle($teamId: String!) {
      cycles(
        filter: {
          team: { id: { eq: $teamId } }
          isActive: { eq: true }
        }
      ) {
        nodes {
          id
          number
          name
          startsAt
          endsAt
          progress
          completedIssueCountHistory
          issueCountHistory
        }
      }
    }
    """,
    {"teamId": "TEAM_ID"}
)
cycles = result["data"]["cycles"]["nodes"]
if cycles:
    c = cycles[0]
    print(f"Cycle {c['number']}: {c['name']}")
    print(f"  {c['startsAt'][:10]} → {c['endsAt'][:10]}")
    print(f"  Progress: {c['progress']*100:.0f}%")

List Issues in Active Cycle

CYCLE_ID = "your-cycle-id"
result = linear(
    """
    query CycleIssues($cycleId: String!) {
      cycle(id: $cycleId) {
        number
        name
        issues(first: 100, orderBy: priority) {
          nodes {
            identifier
            title
            state { name type }
            assignee { name }
            estimate
          }
        }
      }
    }
    """,
    {"cycleId": CYCLE_ID}
)
data = result["data"]["cycle"]
print(f"Cycle {data['number']}: {data['name']}")
for issue in data["issues"]["nodes"]:
    done = "X" if issue["state"]["type"] == "completed" else " "
    assignee = issue["assignee"]["name"] if issue["assignee"] else "-"
    print(f"  [{done}] {issue['identifier']:12s}  {assignee:15s}  {issue['title']}")

Add Issue to Cycle

result = linear(
    """
    mutation AddToCycle($id: String!, $input: IssueUpdateInput!) {
      issueUpdate(id: $id, input: $input) {
        success
      }
    }
    """,
    {"id": "ISSUE_ID", "input": {"cycleId": "CYCLE_ID"}}
)

Part 5 — Bulk Operations

Bulk Create Issues from a List

titles = [
    ("Set up CI pipeline", 2),
    ("Write API documentation", 3),
    ("Add error monitoring", 2),
]

for title, priority in titles:
    result = linear(
        """
        mutation CreateIssue($input: IssueCreateInput!) {
          issueCreate(input: $input) {
            issue { identifier url }
          }
        }
        """,
        {"input": {"teamId": "TEAM_ID", "title": title, "priority": priority}}
    )
    issue = result["data"]["issueCreate"]["issue"]
    print(f"Created {issue['identifier']}: {title}")

Priority and State Reference

| Priority | Value | |----------|-------| | No priority | 0 | | Urgent | 1 | | High | 2 | | Medium | 3 | | Low | 4 |

| State type | Meaning | |------------|---------| | triage | Needs review | | backlog | Not yet planned | | unstarted | Planned, not started | | started | In progress | | completed | Done | | cancelled | Won't do |


Checklist

  • [ ] LINEAR_API_KEY set in environment
  • [ ] Team ID retrieved via teams query
  • [ ] Workflow state IDs retrieved for the target team
  • [ ] Issues created with correct teamId, stateId
  • [ ] Cycle ID retrieved if assigning to a sprint
  • [ ] Priority values follow 0–4 scale

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.