Install
$ agentstack add skill-furkangonel-cowrangler-linear Open-source listing — not yet scanned by AgentStack. Follow the source repository for install instructions.
Security review
⚠ Flagged1 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.
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
- Go to https://linear.app/settings/api
- 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_KEYset 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.
- Author: furkangonel
- Source: furkangonel/cowrangler
- License: MIT
- Homepage: https://cowrangler.com
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.