A small, fast Go CLI for Linear, wrapping the Linear
GraphQL API. Built for terminal and agent use: every read command supports
--json, and single-issue commands default to the issue on your current git
branch.
Modelled on the colflow-cli scaffold (urfave/cli/v3 + a thin GraphQL client +
one file per command).
brew install CogappLabs/tap/linear-cliUse the tap-qualified name. A bare brew install linear-cli resolves to
unrelated core formulae (linode-cli, the linear cask).
Or build from source:
go build -o linear-cli ./cmd/linear
# then move `linear-cli` onto your PATHCreate a personal API key at linear.app/settings/account/security.
For a Homebrew install (the binary runs from any directory), store the key once:
linear-cli auth login # prompts, verifies, saves to the config fileThe key is saved to ~/Library/Application Support/linear-cli/config.json on
macOS (~/.config/linear-cli/config.json on Linux) and used from anywhere.
Resolution order, highest first:
LINEAR_API_KEYalready in the environment (set it in your shell profile for a global key).LINEAR_API_KEYfrom a.env/.env.localin the current directory or any parent (a real shell variable takes precedence over the file). Per-project convenience: it only applies when you run the CLI inside that tree, so it does not replaceauth loginfor a Homebrew install.- The config file written by
auth login.
The key is sent as the raw Authorization header (Linear does not use a
Bearer/Basic prefix for personal keys).
linear-cli auth login|status
linear-cli whoami [--json]
linear-cli issue list [-t KEY] [--mine] [-s STATE_TYPE ...] [--not-done] [-n N] [--all] [--json]
linear-cli issue pull -t KEY [-s STATE_TYPE ...] [--not-done] [-o out.parquet | --json]
linear-cli issue view [ID] [--comments] [--json]
linear-cli issue create -T TITLE [-t KEY] [-d DESC] [-a EMAIL|me] [-p 0-4] [--json]
linear-cli issue update [ID] [-T TITLE] [-d DESC] [-s STATE_NAME] [-a EMAIL|me] [-p 0-4] [--json]
linear-cli issue start [ID] [--json]
linear-cli issue comment [ID] -b BODY
linear-cli issue comment-edit COMMENT_ID -b BODY
linear-cli issue comment-delete COMMENT_ID
linear-cli issue url [ID]
linear-cli issue branch [ID]
linear-cli team list [--json]
linear-cli team view KEY [--json]
linear-cli user list [--all] [--json]
linear-cli user view EMAIL [--json]
linear-cli project list [-t KEY] [--json]
linear-cli project view ID [--json]
linear-cli project save [ID] --name NAME [-t KEY] [-d DESC] [--target-date YYYY-MM-DD] [--json]
linear-cli milestone list -p PROJECT_ID [--json]
linear-cli milestone view ID [--json]
linear-cli milestone save [ID] --name NAME [-p PROJECT_ID] [-d DESC] [--target-date YYYY-MM-DD] [--json]
linear-cli cycle list -t KEY [--json]
linear-cli label list [-t KEY] [--json]
linear-cli label create -t KEY --name NAME [--color HEX] [--description D] [--json]
linear-cli document list [--json]
linear-cli document view ID [--json]
linear-cli document save [ID] -T TITLE [-c CONTENT] [-t KEY | -p PROJECT_ID] [--json]
linear-cli status list -t KEY [--json]
linear-cli status view -t KEY NAME [--json]
linear-cli attachment list ISSUE [--json]
linear-cli attachment add ISSUE URL [--title T] [--json]
linear-cli attachment upload ISSUE FILE [--title T] [--json]
linear-cli attachment extract-images ISSUE [--json]
linear-cli attachment delete ATTACHMENT_ID
linear-cli release list [--json]
linear-cli release view ID [--json]
linear-cli release notes RELEASE_ID [--json]
linear-cli status-update list -p PROJECT_ID [--json]
linear-cli status-update create -p PROJECT_ID -b BODY [--health onTrack|atRisk|offTrack] [--json]
linear-cli status-update delete ID
linear-cli agent-skill list [--json]
linear-cli agent-skill view ID [--json]
linear-cli config set-team KEY
linear-cli config show[ID] is optional on single-issue commands; when omitted the CLI reads an
ABC-123 identifier from the current git branch name.
issue pull fetches a team's entire matching set (paging through all results,
not one page) and can write it to a Parquet file:
linear-cli issue pull -t ENG --not-done -o eng-open.parquet # everything not completed/canceled
linear-cli issue pull -t ENG -s started -s unstarted --json # multiple statuses, JSON to stdout
linear-cli issue pull -t ENG # table to stdoutParquet is written with the pure-Go parquet-go writer (no CGO, no duckdb
shellout). Columns: identifier, title, state, state_type, assignee, team,
priority, labels, url, created_at, updated_at, description. Query it with
duckdb: duckdb -c "SELECT state_type, count(*) FROM 'eng-open.parquet' GROUP BY 1".
Flag conventions:
-t/--team= team key (e.g.ENG).linear-cli config set-team ENGsets a default.-s/--stateonupdate= state name ("In Review"); onlist= state type (started,unstarted,completed,canceled,backlog,triage).-a/--assignee=meor an email.-p/--priority=0 none, 1 urgent, 2 high, 3 medium, 4 low.
The Linear MCP server exposes 53 tools; each tool's schema costs context
whenever the server is active. This CLI adds no persistent tool schema, and its
--json output is trimmed to the fields the commands select rather than the
full 100+-field Linear objects. For the common path (issue list/view/create/
update/start/comment) it is cheaper on context and quicker to read. For the long
tail, fall back to the MCP server (see the parity table below).
Every Linear MCP tool, the matching CLI command, and its status. Linear's MCP
uses a save_* upsert pattern (create when no id, update when id present); the
CLI mirrors that with a save subcommand where it makes sense, or splits into
create/update.
Status: β built Β· π‘ not built (buildable) Β· βͺ not planned.
| MCP tool | CLI | Status |
|---|---|---|
get_issue |
issue view [ID] |
β |
list_issues |
issue list |
β |
save_issue |
issue create / issue update / issue start |
β |
| MCP tool | CLI | Status |
|---|---|---|
list_comments |
issue comments [ID] |
β |
save_comment |
issue comment [ID] -b (create); issue comment-edit ID -b (update) |
β |
delete_comment |
issue comment-delete COMMENT_ID |
β |
| MCP tool | CLI | Status |
|---|---|---|
list_teams |
team list |
β |
get_team |
team view KEY |
β |
list_users |
user list |
β |
get_user |
user view EMAIL |
β |
| MCP tool | CLI | Status |
|---|---|---|
list_issue_labels |
label list [-t KEY] |
β |
list_project_labels |
label list --project ID |
π‘ |
create_issue_label |
label create -t KEY --name NAME [--color] |
β |
| MCP tool | CLI | Status |
|---|---|---|
list_projects |
project list [-t KEY] |
β |
get_project |
project view ID |
β |
save_project |
project save [ID] --name β¦ --team β¦ |
β |
| MCP tool | CLI | Status |
|---|---|---|
list_milestones |
milestone list -p PROJECT_ID |
β |
get_milestone |
milestone view ID |
β |
save_milestone |
milestone save [ID] --project β¦ --name β¦ --target-date β¦ |
β |
| MCP tool | CLI | Status |
|---|---|---|
list_cycles |
cycle list -t KEY |
β |
| MCP tool | CLI | Status |
|---|---|---|
list_documents |
document list |
β |
get_document |
document view ID |
β |
save_document |
`document save [ID] --title β¦ --content β¦ --team | --project β¦` |
| MCP tool | CLI | Status |
|---|---|---|
list_issue_statuses |
status list -t KEY |
β |
get_issue_status |
status view -t KEY NAME |
β |
| MCP tool | CLI | Status |
|---|---|---|
get_attachment / list |
attachment list ISSUE |
β |
create_attachment |
attachment add ISSUE URL --title β¦ (via attachmentLinkURL) |
β |
delete_attachment |
attachment delete ID |
β |
create_attachment_from_upload |
attachment upload ISSUE FILE |
β |
prepare_attachment_upload |
(internal to attachment upload) |
β |
extract_images |
attachment extract-images ISSUE |
β |
| MCP tool | CLI | Status |
|---|---|---|
list_releases |
release list |
β |
get_release |
release view ID |
β |
list_release_notes |
release notes RELEASE_ID |
β |
get_release_note / save_release* / list_release_pipelines |
n/a | βͺ |
| MCP tool | CLI | Status |
|---|---|---|
get_status_updates |
status-update list -p PROJECT_ID |
β |
save_status_update |
status-update create -p PROJECT_ID -b BODY |
β |
delete_status_update |
status-update delete ID |
β |
| MCP tool | CLI | Status |
|---|---|---|
list_agent_skills |
agent-skill list |
β |
get_agent_skill |
agent-skill view ID |
β |
These MCP tools are server-side abstractions with no matching public-schema query/mutation, so the CLI cannot cover them. Use the MCP server:
- Code diffs & review:
list_diffs,get_diff,get_diff_threads,save_diff_comment,delete_diff_comment,resolve_diff_thread,submit_diff_review,merge_diff. - Docs search:
search_documentation.
- β v1: auth, whoami, issues, teams, users, config.
- β Read parity: comments, team/user view, projects, milestones, cycles, labels, documents, statuses.
- β
MCP holes: attachments (list/add/delete), releases (list/view/notes),
status updates (list/create/delete), agent skills, comment edit/delete, and
savefor projects / milestones / documents. - β
Bulk:
issue pull(full team pull, multi-status, Parquet export). - βͺ Not planned: diffs & review and docs search (MCP-only, see above); attachment file uploads; release-note/pipeline writes.
go build -o linear-cli ./cmd/linear
go test ./...
go vet ./...
gofmt -l . # should print nothing
# Introspect the GraphQL schema for a type (needs LINEAR_API_KEY):
./scripts/introspect.sh Issue Project CycleArchitecture and GraphQL notes are in CLAUDE.md.
Push a v* tag. GoReleaser builds darwin/linux amd64+arm64, publishes a GitHub
release, and updates the CogappLabs/homebrew-tap formula (via the
TAP_GITHUB_TOKEN repo secret). Config in .goreleaser.yaml, workflow in
.github/workflows/release.yml. Mirrors colflow-cli.