Skip to content

Repository files navigation

lin

lin is a Linear CLI for coding agents. Noninteractive commands print TOON, a tabular format an LLM reads at a fraction of the token cost of JSON. It ships as one binary with no runtime to install or config wizard. An explicit lin tui command provides an interactive terminal browser and mouse-first Kanban board; every other command keeps the same automation-safe output in terminals and pipes.

Why TOON

TOON (Token-Oriented Object Notation) names the columns once, then writes one row per record. A page of issues costs about half what the same JSON costs, and a model reads it without a parser. Long text never rides inside it: descriptions and comment bodies come out as raw markdown between --- fences, because escaped newline strings are the most expensive thing you can put in a context window.

issues[3]{id,title,state,priority,updated}:
  ENG-42,Fix login redirect loop,In Progress,high,2026-07-30
  ENG-41,Rotate webhook secrets,Todo,medium,2026-07-29
  ENG-38,Upgrade to Bun 1.3,Todo,low,2026-07-28

Install

Build from source with Bun 1.3.14:

git clone https://github.com/Laurens-Nys/linear-cli
cd linear-cli
bun install --frozen-lockfile
bun run build
cp dist/lin ~/.local/bin/lin
lin doctor

Homebrew (brew tap Laurens-Nys/tap && brew install lin) ships only after a separately approved tagged release. Until then the tap is not published; see DEVELOPMENT.md for the release gate, tap token, and notarization notes.

The noninteractive CLI has no runtime dependencies. The TUI's matched status icons require Material Design Icons on every computer that renders the terminal. On macOS:

brew install --cask font-material-design-icons-webfont

For Ghostty or cmux, add this to ~/.config/ghostty/config, then reload the configuration or open a new terminal:

font-codepoint-map = U+F0159,U+F05E0,U+F0766,U+F0E95,U+F1396,U+F1853=Material Design Icons

When using lin remotely, install and configure the font on the client computer where the terminal appears, not only on the remote host running the command.

Quickstart

Create a personal API key in Linear Settings > Security & access > Personal API keys, then:

export LINEAR_API_KEY
lin doctor               # setup checks: key, API, config, cache, TUI
lin auth                 # who the key is, which workspace, how much rate budget is left
lin today                # started, overdue, urgent/high, or blocked assigned issues
lin ls                   # my open issues, most recently updated first
lin ENG-42               # a bare identifier is always issue view
lin issue create --team ENG -t "Fix login redirect loop" --label Bug --assignee casey
lin tui                  # browse my assigned issues interactively

Interactive terminal browser

lin tui is the only interactive command. It opens issues assigned to the authenticated viewer with a list and detail pane. View tabs switch between All (open), Started, Todo, and Done. Linear-colored status icons identify each issue, and the list is grouped by the team's actual state names (In Progress, In Review, Todo, and so on) while preserving the chosen sort inside each group. The list query stays slim; opening an issue loads its description and last three comments on demand and caches them for the session. The header count is explicit: 23 when the page is complete and 50+ when Linear reports another page beyond the configured limit. The detail pane renders the issue description as markdown (headings, lists, tables, fenced code) and turns mermaid fences into width-aware ASCII diagrams.

Click Board view to open a Kanban board for the selected team. Its adaptive columns follow the team's workflow progression—Backlog, Planned, started states such as In Progress and In Review, then Done—while preserving Linear's position and configured color within each category. Click a card to open its detail, or drag it to another column to update its workflow state in Linear. Dragging dims the source, highlights the destination, and names the pending move in the footer; a failed update visibly restores the same card. A successful drag, Start, or Done keeps one 8-second undo in the footer (Moved … · u undo); press u or click that footer to reverse the state once. Right-click a list row or card for that issue's action menu; press k to search both those actions and the loaded issues and jump to one. The menu can open in Linear, copy the identifier or https URL, move to the team's first Started or Done state, set priority, or add a comment. When worktree_repo is set, Open as worktree replaces Open in Linear, including the header chip and the silent o shortcut: a Herdr worktree named from the issue id, on Linear's suggested branch, with a one-line draft prompt staged unsent after the workspace and agent are focused, then flushed with left/right so Grok paints it. That action requires a Herdr session (HERDR_ENV), never submits the prompt, and never starts or moves the Linear issue. Copy stays on the injected clipboard / OSC52 path and never shells out. The board refreshes quietly every 30 seconds without a Refreshing banner, and both refreshes and moves preserve selection, card identity, and scroll position. Board mode does not reorder cards within a column. Click Team, Project, or Sort in the header to narrow or reorder the issues. Escape returns from detail, and b is the keyboard fallback for switching List/Board. Panes are transparent so the terminal background shows through. / searches titles against Linear. The footer lists the remaining hidden controls. Narrow terminals scroll the board horizontally and otherwise show one pane at a time. Filters, view, and layout are never saved. It must be run directly in an interactive terminal; use lin ls when piping output.

The output contract

Noninteractive commands return one of four shapes. lin tui owns the terminal only when explicitly invoked and does not use these output shapes.

Lists are TOON tables. When a page is cut, the last line is a comment carrying the exact command that fetches the next one:

# 11 more · lin issue list --team ENG --after <cursor>

--all-pages walks every remaining page from --after on the declared paginated list commands (issue list, ls, triage, comment, search) and prints one table with no continuation; other commands reject it. --fields id,title selects and orders columns on table commands; bare --fields lists the ones that command can print. Non-table commands reject --fields. Inbox --all still means include-read or bulk, not pagination.

One record is key: value lines, then the markdown body between fences, then any sub-tables:

id: ENG-42
state: In Progress
assignee: casey
---
Users bounce between /login and /app when the session cookie is stale.
---
comments[1]{ref,author,date,body}:
  9f2ab41c,casey,2026-07-29,Repro: stale cookie, then any deep link

Writes return receipts. Creates print the new identifier and its URL; updates print only the fields that changed, read back from the response:

ENG-42:
  state: Todo -> In Progress
  assignee: none -> casey

Errors go to stderr and name the correction:

error: team ENG has no state "In Progress"
states: Triage, Todo, Doing, In Review, Done, Canceled

Exit codes are part of the contract: 0 ok, 1 API or network, 2 correctable input, 3 auth, 4 not found. Exit 2 always lists the valid values, so a caller can fix its own command.

Commands

Full form is lin <noun> <verb> [args] [flags]. Top-level shortcuts cover the hot path: lin ENG-42, lin today, lin ls, lin start, lin done, lin triage, lin search "term".

noun verbs
issue list, view, create, update, archive, unarchive, delete, relate, unrelate, reorder, link, attach, branch, url, subscribe, unsubscribe
comment list, add, edit, resolve, unresolve
project list, view, create, update, post, posts
milestone list, create, update, delete
cycle list, view, create, update
initiative list, view, create, update, add-project, rm-project, post, posts
doc list, view, create, update
team list, view, states
user list, me
label list, create, update, archive
template list, view
customer list, view, create, need add, need list
inbox read, archive
meta api, schema, auth, doctor, cache, skill, completions, tui

lin --help prints every command your binary has, grouped by noun. lin issue --help prints that noun's commands. lin issue create -h prints one command's arguments, flags and examples. lin api and lin schema reach the rest of Linear's API, the part no verb covers.

DESIGN.md is the full map: curated columns, filters and behaviour per command.

Configuration

lin reads .lin.toml from the current directory, then the git root, then ~/.config/lin/config.toml. The nearest file wins. Flat keys only:

team = "ENG"
limit = 50
worktree_repo = "~/src/app"
worktree_agent = "claude"

Unknown keys, malformed lines, and unreadable files fail with the file path and a correction. LINEAR_API_KEY is the only way to authenticate, and it is never printed, logged or written to disk. A missing key names Linear Settings > Security & access > Personal API keys and export LINEAR_API_KEY with no fake secret. A missing team names lin team list plus --team and .lin.toml examples. lin doctor prints those checks as a table. LIN_TEAM, LIN_LIMIT, LIN_WORKTREE_REPO, and LIN_WORKTREE_AGENT override the files; flags override team and limit. worktree_repo and worktree_agent are optional and replace the TUI Open action with Open as worktree. That action must run inside a Herdr session so HERDR_ENV is set; it focuses the workspace and agent, then stages a one-line draft with herdr pane send-text and a left/right key flush so Grok paints it, and does not submit it. The checkout path is ~/.herdr/worktrees/<repo-basename>/<issue-slug>, so two repos that share a basename can collide in v1. A leading ~/ in worktree_repo expands when the action runs. limit / LIN_LIMIT / --limit must be an integer from 1 to 250 and fail with the same wording before any request.

Name lookups for teams, states, labels, users, projects and templates resolve against a cache at ~/.cache/lin/<workspace>/meta.json with a 24 hour life. lin cache shows its age, lin cache warm follows every vocabulary page then writes once, lin cache clear deletes it, and --no-cache skips it for one command.

For agents

lin skill --install .claude/skills/linear

That writes a SKILL.md cheatsheet: the output contract, first-class workflows and recovery, then every command with its synopsis and one worked example. Completions come from the same place:

lin completions zsh > ~/.zfunc/_lin

Help, skill and completions all render from one command registry at runtime, so none of them can drift from the commands that exist.

Development

bun test            # unit tests, no network
bunx tsc --noEmit   # types
bun run build       # dist/lin
bun run check:budgets  # binary size + startup smoke

Live Linear smoke is a manual workflow (live-smoke) and needs repository secret LINEAR_API_KEY. It is read-only and discards Linear command stdout and stderr so workspace data never reaches public logs. Manual release dry-runs are contents: read only. Tag publication needs the protected release environment and LIN_RELEASE_PUBLISH=true after separate approval. Homebrew cask upload stays skip_upload: true until a separate tap approval. Details: DEVELOPMENT.md.

License

MIT. See LICENSE.

About

A Linear CLI built for coding agents — TOON output, one fast binary

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages