shared/skills/linear/SKILL.md
Read, create, and update Linear issues/projects/cycles using the Linear GraphQL API with a personal API key (no MCP required). Use when the user wants to look up, file, or edit Linear tickets.
npx skillsauth add nsimonfr/nic-os linearInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Two Linear workspaces are available:
| Env var | Workspace | Team | Key |
|---|---|---|---|
| $LINEAR_KEY | nsimon (personal) | nSimon (NSI) | default |
| $LINEAR_KEY_TRUSK | trusk (work) | — | use for trusk-specific queries |
Default to $LINEAR_KEY (nsimon workspace) for all queries unless the user explicitly asks about Trusk.
Keys are format lin_api_…. All API calls go to https://api.linear.app/graphql with header Authorization: $LINEAR_KEY (no Bearer prefix — Linear personal keys are sent raw).
If $LINEAR_KEY is unset, resolve it before giving up. Interactive nsimon shells source it from secrets.zsh, but systemd-spawned agents (e.g. claude-remote-control) run with a minimal env and their Bash subshells don't. On rpi5 the same keys live in the system agenix file /run/agenix/picoclaw-env (KEY=VAL, owner nsimon, always present — no dependency on picoclaw running). Run this once at the start of a Linear task:
# Self-heal LINEAR_KEY / LINEAR_KEY_TRUSK if the shell didn't export them.
if [ -z "$LINEAR_KEY" ] && [ -r /run/agenix/picoclaw-env ]; then
export LINEAR_KEY=$(sed -n 's/^LINEAR_KEY=//p' /run/agenix/picoclaw-env)
export LINEAR_KEY_TRUSK=$(sed -n 's/^LINEAR_KEY_TRUSK=//p' /run/agenix/picoclaw-env)
fi
Only if $LINEAR_KEY is still empty after this, stop and tell the user.
# Sanity check — should print your name
curl -sS -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_KEY" \
-H "Content-Type: application/json" \
-d '{"query":"{ viewer { id name email } }"}' | jq .
For the nsimon workspace, the default team is nSimon:
f70e4ca5-9442-4489-82f8-9a988269961bNSIWhen creating issues or searching, use this team ID unless told otherwise.
All examples use the default nsimon workspace ($LINEAR_KEY). For trusk, swap $LINEAR_KEY → $LINEAR_KEY_TRUSK.
linear_q() {
# Self-heal LINEAR_KEY for systemd-spawned agents (see "How auth works here").
[ -z "$LINEAR_KEY" ] && [ -r /run/agenix/picoclaw-env ] && \
export LINEAR_KEY=$(sed -n 's/^LINEAR_KEY=//p' /run/agenix/picoclaw-env)
curl -sS -X POST https://api.linear.app/graphql \
-H "Authorization: $LINEAR_KEY" \
-H "Content-Type: application/json" \
-d "$(jq -nc --arg q "$1" --argjson v "${2:-{}}" '{query:$q, variables:$v}')"
}
# nsimon team ID (default)
NSIMON_TEAM="f70e4ca5-9442-4489-82f8-9a988269961b"
# Teams (id, key, name)
linear_q 'query { teams(first:50) { nodes { id key name } } }' | jq '.data.teams.nodes'
# My open issues (nsimon)
linear_q 'query { viewer { assignedIssues(filter:{state:{type:{nin:["completed","canceled"]}}}, first:50) { nodes { identifier title state { name } url } } } }' | jq '.data.viewer.assignedIssues.nodes'
# Issue by identifier (e.g. NSI-42)
linear_q 'query($id:String!){ issue(id:$id){ id identifier title description state{name} priority assignee{name} labels{nodes{name}} url } }' '{"id":"NSI-42"}' | jq '.data.issue'
# Search issues in the nSimon team
linear_q 'query($q:String!,$tid:String!){ issues(filter:{team:{id:{eq:$tid}}, title:{containsIgnoreCase:$q}}, first:25){ nodes{ identifier title state{name} url } } }' '{"q":"flaky","tid":"'$NSIMON_TEAM'"}' | jq
# Workflow states for the nSimon team
linear_q 'query($tid:String!){ team(id:$tid){ states{ nodes{ id name type } } } }' '{"tid":"'$NSIMON_TEAM'"}' | jq
# List all open issues in nSimon team
linear_q 'query($tid:String!){ issues(filter:{team:{id:{eq:$tid}}, state:{type:{nin:["completed","canceled"]}}}, first:25){ nodes{ identifier title state{name} url } } }' '{"tid":"'$NSIMON_TEAM'"}' | jq
# Create an issue in nSimon (default team)
linear_q 'mutation($i:IssueCreateInput!){ issueCreate(input:$i){ success issue{ identifier url } } }' \
'{"i":{"teamId":"'$NSIMON_TEAM'","title":"Fix flaky test","description":"Repro steps…","priority":2}}' | jq
# Add a comment
linear_q 'mutation($i:CommentCreateInput!){ commentCreate(input:$i){ success comment{ id url } } }' \
'{"i":{"issueId":"<issue-uuid>","body":"Update: deployed v1.2.3"}}' | jq
# Update title / state / assignee
linear_q 'mutation($id:String!,$i:IssueUpdateInput!){ issueUpdate(id:$id, input:$i){ success } }' \
'{"id":"<issue-uuid>","i":{"stateId":"<state-uuid>","assigneeId":"<user-uuid>"}}' | jq
# --- Trusk workspace queries ---
# For trusk-specific work, set LINEAR_KEY=$LINEAR_KEY_TRUSK before queries
Notes:
issueUpdate and commentCreate need the issue's UUID, not its identifier (NSI-42). Resolve issue(id:"NSI-42"){ id } first when needed.priority: 0 = none, 1 = urgent, 2 = high, 3 = medium, 4 = low.state types: triage, backlog, unstarted, started, completed, canceled.viewer.assignedIssues filtered by priority:{lte:2}, then issueUpdate to bump state to "In Progress".cycle for the nSimon team, list backlog issues, batch-create assignments.commentCreate with the latest status.team.labels.nodes, then issueUpdate with labelIds:[…].$LINEAR_KEY_TRUSK and use the trusk team IDs (e.g. IN, EXTERN).401: LINEAR_KEY empty/invalid — verify with the viewer query at the top.400 / GraphQL errors: read the errors[].message in the response — usually a missing required field or wrong UUID vs identifier.development
Log workouts, wishlist media, and query the self-hosted Ryot tracker via its GraphQL API with a Bearer token. Use when the user wants to record a gym session/workout, add a book/film/game/show to their wishlist, or look up what they've tracked in Ryot.
development
AI-optimized web search via Tavily API. Returns concise, relevant results for AI agents.
content-media
Summarize or extract text/transcripts from URLs, podcasts, and local files (great fallback for “transcribe this YouTube/video”).
tools
<!-- vendored via `npx skills add steipete/clawdis@himalaya` (commit 27e5d49); locally adapted for personal ProtonMail via the hydroxide bridge on rpi5 --> --- name: protonmail description: Read, search, and send the user's personal ProtonMail using the himalaya CLI against the local hydroxide bridge (IMAP :1143 / SMTP :1025). homepage: https://github.com/pimalaya/himalaya metadata: {"openclaw":{"emoji":"📧","os":["linux"],"requires":{"bins":["himalaya"]}}} --- # ProtonMail (himalaya + hydroxid