linear-sdk-cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -46
- package/COVERAGE.md +5 -5
- package/MIGRATING.md +69 -71
- package/README.md +88 -96
- package/package.json +8 -9
- package/skills/linear-sdk-cli/SKILL.md +3 -13
- package/skills/linear-sdk-cli/references/api.md +9 -9
- package/skills/linear-sdk-cli/references/auth.md +0 -18
- package/skills/linear-sdk-cli/references/commands.md +2 -2
- package/skills/linear-sdk-cli/references/config.md +11 -9
- package/skills/linear-sdk-cli/references/issue.md +12 -63
- package/skills/linear-sdk-cli/references/team.md +0 -16
- package/skills/linear-sdk-cli/scripts/generate-docs.ts +2 -6
- package/src/bin/linear.ts +6 -16
- package/src/cli.ts +1 -3
- package/src/commands/agent-session.ts +5 -13
- package/src/commands/api.ts +9 -15
- package/src/commands/attach.ts +4 -15
- package/src/commands/comment.ts +2 -6
- package/src/commands/completion.ts +6 -2
- package/src/commands/cycle.ts +1 -6
- package/src/commands/discover.ts +2 -6
- package/src/commands/document.ts +1 -4
- package/src/commands/initiative.ts +1 -5
- package/src/commands/issue.ts +88 -376
- package/src/commands/meta.ts +31 -139
- package/src/commands/milestone.ts +1 -4
- package/src/commands/notification.ts +6 -15
- package/src/commands/project.ts +4 -17
- package/src/commands/team.ts +0 -13
- package/src/commands/webhook.ts +2 -12
- package/src/config.ts +183 -500
- package/src/context.ts +4 -13
- package/src/git.ts +3 -28
- package/src/lib/body.ts +2 -26
- package/src/lib/errors.ts +7 -32
- package/src/lib/introspect.ts +7 -76
- package/src/lib/keyring.ts +9 -51
- package/src/lib/options.ts +12 -108
- package/src/lib/output-shapes.ts +9 -81
- package/src/lib/pagination.ts +17 -50
- package/src/lib/resolve.ts +7 -40
- package/src/lib/shape.ts +12 -16
- package/src/lib/upload.ts +1 -3
- package/src/output/format.ts +18 -96
- package/src/output/table.ts +7 -7
- package/src/services/agent-session.ts +3 -11
- package/src/services/attachment.ts +5 -14
- package/src/services/comment.ts +4 -12
- package/src/services/cycle.ts +4 -11
- package/src/services/document.ts +10 -29
- package/src/services/favorite.ts +9 -15
- package/src/services/initiative-update.ts +15 -16
- package/src/services/initiative.ts +10 -15
- package/src/services/issue.ts +18 -262
- package/src/services/label.ts +2 -12
- package/src/services/milestone.ts +5 -8
- package/src/services/notification.ts +2 -16
- package/src/services/organization.ts +18 -23
- package/src/services/project.ts +34 -55
- package/src/services/roadmap.ts +6 -7
- package/src/services/state.ts +3 -9
- package/src/services/team.ts +24 -67
- package/src/services/user.ts +2 -2
- package/src/services/webhook.ts +17 -28
- package/skills/linear-sdk-cli/references/open.md +0 -29
- package/src/commands/open.ts +0 -84
- package/src/lib/open.ts +0 -32
- package/src/output/color.ts +0 -52
- package/src/output/markdown.ts +0 -133
- package/src/output/pager.ts +0 -131
package/MIGRATING.md
CHANGED
|
@@ -27,7 +27,7 @@ things make it safe anyway:
|
|
|
27
27
|
deno install -A --reload -f -g -n linear-schpet jsr:@schpet/linear-cli && deno uninstall -g linear # deno
|
|
28
28
|
brew unlink schpet/tap/linear && ln -s "$(brew --prefix)/opt/linear/bin/linear" ~/.local/bin/linear-schpet # homebrew
|
|
29
29
|
mv "$(command -v linear)" "$(dirname "$(command -v linear)")/linear-schpet" # npm/bun -g
|
|
30
|
-
|
|
30
|
+
bun add -g linear-sdk-cli # or: bun link, from a clone
|
|
31
31
|
linear --version # ours: 0.x
|
|
32
32
|
```
|
|
33
33
|
|
|
@@ -60,7 +60,7 @@ We look in every place schpet does, in its order. Walking up from the working di
|
|
|
60
60
|
directory is checked for `linear.toml`, then `.linear.toml`, then `.config/linear.toml` (schpet
|
|
61
61
|
checks cwd and the git root; we check every directory between, which agrees with it wherever it
|
|
62
62
|
would find something). Below that, our `~/.config/linear/config.toml`, then schpet's global
|
|
63
|
-
`~/.config/linear/linear.toml` — read for `team_id`, `workspace`, `issue_sort`; **not** for
|
|
63
|
+
`~/.config/linear/linear.toml` — read for `team_id`, `workspace`, `issue_sort`, `vcs`; **not** for
|
|
64
64
|
`api_key`, which schpet allows there and we never take from any file but our own. `linear config`
|
|
65
65
|
prints each value with the file it came from, so nothing is a mystery:
|
|
66
66
|
|
|
@@ -70,91 +70,90 @@ Sort: priority (global: /Users/you/.config/linear/linear.toml)
|
|
|
70
70
|
```
|
|
71
71
|
|
|
72
72
|
Two key differences to know: `issue_sort = "manual"` (schpet's board order) has no equivalent here
|
|
73
|
-
and is a usage error naming the file — change it to `priority` or drop the line
|
|
74
|
-
is ignored; this CLI supports Git and does not claim partial jj support. Schpet's
|
|
73
|
+
and is a usage error naming the file — change it to `priority` or drop the line; and schpet's
|
|
75
74
|
`issue_create_*`, `download_images`, `hyperlink_format`, `attachment_dir` keys are ignored (no
|
|
76
75
|
such features yet). `linear config init` writes a `.linear.toml`; `linear config set <key>
|
|
77
76
|
<value>` edits one key.
|
|
78
77
|
|
|
79
78
|
## 4. Commands — same words, or an alias
|
|
80
79
|
|
|
81
|
-
| schpet
|
|
82
|
-
|
|
83
|
-
| `issue list`
|
|
84
|
-
| `issue query`
|
|
85
|
-
| `issue mine`
|
|
86
|
-
| `issue comment add\|list\|update\|delete`
|
|
87
|
-
| `issue comment <id> "<body>"`
|
|
88
|
-
| `issue attach <file>`
|
|
89
|
-
| `issue link <url>`
|
|
90
|
-
| `issue comment add --attach <file>`
|
|
91
|
-
| `auth whoami`
|
|
92
|
-
| `auth migrate`
|
|
93
|
-
| `config` (writes toml)
|
|
94
|
-
| `team states` / `team members` / `user list`
|
|
95
|
-
| `issue start`
|
|
96
|
-
| `issue describe`
|
|
97
|
-
| `issue pull-request`
|
|
98
|
-
| `project create -t A -t B`
|
|
99
|
-
| `document create\|list\|update --project\|--issue\|--initiative\|--team\|--cycle\|--release` | same
|
|
100
|
-
| `initiative add-project` / `remove-project` / `unarchive`
|
|
101
|
-
| `project delete` / `team delete`
|
|
102
|
-
| `schema` / `api`
|
|
80
|
+
| schpet | here | notes |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `issue list` | **`issue mine`** | ⚠️ their `list` is an alias of `mine` (you, unstarted). Our `list` is the whole team. See §6. |
|
|
83
|
+
| `issue query` | `issue list` | alias, same command |
|
|
84
|
+
| `issue mine` | `issue mine` | same defaults: yours, unstarted; `--all-states` widens |
|
|
85
|
+
| `issue comment add\|list\|update\|delete` | same, or top-level `comment …` | one implementation |
|
|
86
|
+
| `issue comment <id> "<body>"` | same | |
|
|
87
|
+
| `issue attach <file>` | `issue attach <issue> <file...>` | same posture: private by default, `--public` for raster images only; ours takes several files and has `--json` |
|
|
88
|
+
| `issue link <url>` | `attachment create --url` | |
|
|
89
|
+
| `issue comment add --attach <file>` | same, or `comment add --attach` | repeatable; images render inline |
|
|
90
|
+
| `auth whoami` | `whoami` (also `auth whoami`) | |
|
|
91
|
+
| `auth migrate` | `auth migrate` | |
|
|
92
|
+
| `config` (writes toml) | `config init` / `config set` | |
|
|
93
|
+
| `team states` / `team members` / `user list` | same | `--all` there means "inactive too"; here it is pagination — use `--include-disabled` (see §6) |
|
|
94
|
+
| `issue start` | same | moves the issue to the first `started` state, as there; `--no-move` to only branch |
|
|
95
|
+
| `issue describe` | same | byte-identical output: `ID Title`, then `Linear-issue:` / `Linear-issue-url:` trailers |
|
|
96
|
+
| `issue pull-request` | same | title `ID Title` as there; body is the two `Linear-issue` trailers (theirs: the URL alone) — the issue description is not copied into GitHub |
|
|
97
|
+
| `project create -t A -t B` | same, or `--teams A,B` | collects both, as there (see §6) |
|
|
98
|
+
| `document create\|list\|update --project\|--issue\|--initiative\|--team\|--cycle\|--release` | same | `update` re-points, as there; `--team` is the global flag (with `--cycle` it scopes the lookup) |
|
|
99
|
+
| `initiative add-project` / `remove-project` / `unarchive` | same | |
|
|
100
|
+
| `project delete` / `team delete` | same, confirmation-gated | |
|
|
101
|
+
| `schema` / `api` | same | ours adds `--operation`, `--vars-file`, and refuses to `--paginate` a mutation |
|
|
103
102
|
|
|
104
103
|
Everything with no row is spelled identically.
|
|
105
104
|
|
|
106
105
|
## 5. Flags — theirs are accepted
|
|
107
106
|
|
|
108
|
-
| schpet
|
|
109
|
-
|
|
110
|
-
| `-j, --json`
|
|
111
|
-
| `-w, --web`
|
|
112
|
-
| `--due-date`
|
|
113
|
-
| `--target-date`, `--start-date`
|
|
114
|
-
| `--search`
|
|
115
|
-
| `--status`
|
|
116
|
-
| `--limit 0`
|
|
117
|
-
| `--assignee self`
|
|
118
|
-
| `--cycle active`
|
|
119
|
-
| `-U/--unassigned`, `--created-after`, `--updated-after`, `--project-label`, `--milestone`, `--search-comments`, repeatable `--team`/`--state` | same
|
|
120
|
-
| `--add-label`, `--remove-label`, `--unassign`, `--clear-cycle`
|
|
121
|
-
| `--no-use-default-template`
|
|
122
|
-
| `--start`, `--parent` (child joins the parent's project)
|
|
123
|
-
|
|
124
|
-
Passing both spellings at once (`--due`
|
|
107
|
+
| schpet | here (canonical) | where |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `-j, --json` | same | every command |
|
|
110
|
+
| `-w, --web` | same | every `view` |
|
|
111
|
+
| `--due-date` | `--due` | issue create/update |
|
|
112
|
+
| `--target-date`, `--start-date` | `--target`, `--start` | project / milestone / initiative |
|
|
113
|
+
| `--search` | `--query` | issue list / mine |
|
|
114
|
+
| `--status` | `--state` | project list |
|
|
115
|
+
| `--limit 0` | `--all` | every list |
|
|
116
|
+
| `--assignee self` | `me` / `@me` | anywhere |
|
|
117
|
+
| `--cycle active` | `current` | anywhere; `now`/`next`/`previous`/`+1` also work |
|
|
118
|
+
| `-U/--unassigned`, `--created-after`, `--updated-after`, `--project-label`, `--milestone`, `--search-comments`, repeatable `--team`/`--state` | same | issue queries |
|
|
119
|
+
| `--add-label`, `--remove-label`, `--unassign`, `--clear-cycle` | same | issue update |
|
|
120
|
+
| `--no-use-default-template` | `--no-default-template` | issue create — the team default is applied unless you say so, as there |
|
|
121
|
+
| `--start`, `--parent` (child joins the parent's project) | same | issue create |
|
|
122
|
+
|
|
123
|
+
Passing both spellings at once (`--due` *and* `--due-date`) is a usage error, not a coin flip.
|
|
125
124
|
|
|
126
125
|
**Short flags are the one place we did not follow.** schpet 2.5's own tree assigns `-a` four
|
|
127
126
|
meanings (`--app`, `--all`, `--assignee`, `--attach`), `-f` four, `-y` three, `-t` two (`--title`
|
|
128
127
|
and `--team`). There is no consistent target to copy. Ours holds **one meaning per letter across
|
|
129
128
|
all 141 commands**, so `-t` is always `--team`, `-p` always `--project`, `-P` always `--priority`,
|
|
130
129
|
`-f` always `--fields`, `-n` always `--limit`. Every collision fails loudly — `-p 2` says "No
|
|
131
|
-
project matching '2'", not "set priority 2" — and the two that
|
|
130
|
+
project matching '2'", not "set priority 2" — and the two that *used* not to are loud now: `-f`
|
|
132
131
|
and `-n` are refused on any command that prints no table (`project create -f desc.md` says
|
|
133
132
|
"--fields does not apply … use --description-file <path>"; `project create -n 'My Project'` says
|
|
134
133
|
"--limit does not apply … the name is --name"), instead of creating the project without either.
|
|
135
134
|
|
|
136
135
|
## 6. What would silently differ — and how we made it loud (or the same)
|
|
137
136
|
|
|
138
|
-
These are the spots where the same command line could
|
|
137
|
+
These are the spots where the same command line could *succeed on both and mean different things*.
|
|
139
138
|
Every one is now either identical, or says so on stderr, or is a usage error. Know them.
|
|
140
139
|
|
|
141
|
-
1. **`issue list`** — theirs shows
|
|
140
|
+
1. **`issue list`** — theirs shows *your unstarted* issues; ours shows the *team's* issues.
|
|
142
141
|
We did not alias `list` to `mine`: a command named "list" that hides your colleagues' work and
|
|
143
142
|
your own in-progress work is the sharpest transition hazard there is. Type `issue mine` for the
|
|
144
143
|
old behavior.
|
|
145
144
|
2. **Repeated `--label`** narrows (AND) in both CLIs now — same as schpet.
|
|
146
145
|
3. **`--sort priority`** groups by workflow state in both — but we sort state **ascending** so active
|
|
147
|
-
work is on top. schpet hardcodes descending, which the API answers with Backlog
|
|
146
|
+
work is on top. schpet hardcodes descending, which the API answers with Backlog *above* In
|
|
148
147
|
Progress; a Low-priority backlog item outranks an Urgent in-progress one there. We diverge on
|
|
149
148
|
purpose.
|
|
150
|
-
4. **`user list --all` / `team members --all`** — theirs: include
|
|
149
|
+
4. **`user list --all` / `team members --all`** — theirs: include *inactive* members. Ours: `--all`
|
|
151
150
|
is the global "fetch every page" on all 141 commands, and deactivated users are
|
|
152
151
|
`--include-disabled`. We kept the one meaning; the command prints a warning on stderr (even
|
|
153
152
|
under `--quiet`) that deactivated users are still excluded and names `--include-disabled`, and
|
|
154
153
|
`--help` says so up front. Not adopted: making `--all` mean "and deactivated" on two commands
|
|
155
154
|
would give one global two meanings.
|
|
156
155
|
5. **`-f <file>` on `project create/update`, `document create/update`** — theirs: `--description-file` /
|
|
157
|
-
`--content-file`. Ours: `-f` is `--fields`, and it used to be
|
|
156
|
+
`--content-file`. Ours: `-f` is `--fields`, and it used to be *dropped* on those commands, so
|
|
158
157
|
`project create --name X -f desc.md` created the project with no description and exited 0.
|
|
159
158
|
`--fields`, `--limit` and `--all` are now usage errors on every command that prints no table or
|
|
160
159
|
detail block, and the message names `--description-file` / `--content-file` where they exist.
|
|
@@ -163,7 +162,7 @@ Every one is now either identical, or says so on stderr, or is a usage error. Kn
|
|
|
163
162
|
6. **`project create --team A --team B`** — theirs collects both; ours took the last one and created
|
|
164
163
|
the project in B alone. `--team` on `project create` is now the same repeatable list as
|
|
165
164
|
`--teams`; both spellings at once is a usage error. (`project update --team` stays refused,
|
|
166
|
-
pointing at `--teams`, because that list
|
|
165
|
+
pointing at `--teams`, because that list *replaces* the project's teams.)
|
|
167
166
|
7. **`issue start`** — theirs always moves the issue to the first `started` state after branching;
|
|
168
167
|
ours only did with `--move`, so the transplanted command left the issue in Backlog without a
|
|
169
168
|
word. Adopted: moving is the default (that is what "start" means, and what an agent that says
|
|
@@ -172,37 +171,36 @@ Every one is now either identical, or says so on stderr, or is a usage error. Kn
|
|
|
172
171
|
8. **`issue describe`** — theirs prints `ID Title`, a blank line, then `Linear-issue: Fixes ID` and
|
|
173
172
|
`Linear-issue-url: URL`; ours printed `Title` and a bare `Fixes ID`. Piped into `git commit -m`
|
|
174
173
|
that was a different commit. Adopted, byte for byte: proper trailers are what `git
|
|
175
|
-
interpret-trailers` and jj read back, and Linear reads the magic word right before the id
|
|
174
|
+
interpret-trailers` and jj read back, and Linear reads the magic word right before the id
|
|
176
175
|
either way (linear.app/docs/github). `-r/--references` still swaps in `References`.
|
|
177
176
|
9. **`issue pull-request`** — theirs titles the PR `ID Title` and sends the issue URL as the body;
|
|
178
|
-
ours titled it `Title` and pasted the issue
|
|
177
|
+
ours titled it `Title` and pasted the issue *description* into the body. Title adopted (a custom
|
|
179
178
|
`--title` is prefixed too). Body: the same two trailers `describe` prints — the URL as there,
|
|
180
179
|
plus the magic word so the link (and auto-close) does not depend on the branch name — and the
|
|
181
180
|
description stays in Linear rather than in a GitHub PR body.
|
|
182
181
|
10. **`issue list`/`mine`/`search`, `project list` with no team configured** — theirs error
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
11. **`--cycle +1`** — active cycle plus one on both (it was cycle
|
|
182
|
+
("No default team…"); ours list the whole workspace, on purpose (`--all-teams` explicitly). So a
|
|
183
|
+
migrating user cannot mistake that for a team's list, the command prints one stderr note ("No
|
|
184
|
+
default team configured; listing every team's…"); `--quiet` silences it.
|
|
185
|
+
11. **`--cycle +1`** — active cycle plus one on both (it was cycle *#1* here once).
|
|
187
186
|
12. **`document list --team X`** — filters to the team's documents on both.
|
|
188
187
|
|
|
189
188
|
Loud already, listed so you are not surprised:
|
|
190
189
|
|
|
191
|
-
| you type (schpet habit)
|
|
192
|
-
|
|
193
|
-
| `issue update TES-1 -t 'New title'` (`-t` = `--title` there) | `--title`
|
|
194
|
-
| `project create -n 'My Project'` (`-n` = `--name` there)
|
|
195
|
-
| `issue comment delete <id>` in a script
|
|
196
|
-
| `--assignee <partial name>` (fuzzy there)
|
|
197
|
-
| `issue view 123` (bare number, team from config there)
|
|
198
|
-
| `linear config` (writes a toml there)
|
|
199
|
-
| `project update <p> -t A` (replaces the teams there)
|
|
200
|
-
| `--all` on `user list`
|
|
201
|
-
|
|
202
|
-
## 7.
|
|
203
|
-
|
|
204
|
-
`issue commits`, `team autolinks`,
|
|
205
|
-
supports `--bulk`, `--bulk-file`, and `--bulk-stdin`; Markdown rendering and paging are built in.
|
|
190
|
+
| you type (schpet habit) | here | what happens |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `issue update TES-1 -t 'New title'` (`-t` = `--title` there) | `--title` | `-t` is `--team`: "No team matching 'New title'", exit 3 — unless a team is really named that |
|
|
193
|
+
| `project create -n 'My Project'` (`-n` = `--name` there) | `--name` | `-n` is `--limit`: refused on `create`, and the message says so |
|
|
194
|
+
| `issue comment delete <id>` in a script | same, `--yes` | destructive actions off-TTY need `--yes` (exit 2), there it just deletes |
|
|
195
|
+
| `--assignee <partial name>` (fuzzy there) | `me`, an email, an exact display/full name | "No user matching", exit 3 |
|
|
196
|
+
| `issue view 123` (bare number, team from config there) | `TES-123` | rejected, exit 2 |
|
|
197
|
+
| `linear config` (writes a toml there) | prints; `config init` / `config set` write | |
|
|
198
|
+
| `project update <p> -t A` (replaces the teams there) | `--teams A` | `--team` refused with the reason; `--teams` replaces, as there |
|
|
199
|
+
| `--all` on `user list` | see 4 above | pagination + a warning |
|
|
200
|
+
|
|
201
|
+
## 7. Not here yet
|
|
202
|
+
|
|
203
|
+
`issue commits`, `team autolinks`, jj support, bulk `--bulk-file`, markdown rendering + pager.
|
|
206
204
|
Each is tracked; the raw `linear api` reaches all of the API surface in the meantime. If you type
|
|
207
205
|
one of these out of habit, the CLI says where the equivalent lives rather than just rejecting it:
|
|
208
206
|
`linear issue link x` answers "Use 'linear attachment create <issue> --url <url>'", and `issue
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**An ergonomic command-line interface for [Linear](https://linear.app), built on the official
|
|
4
4
|
[`@linear/sdk`](https://www.npmjs.com/package/@linear/sdk).**
|
|
5
5
|
|
|
6
|
-
It's designed to be pleasant for humans
|
|
6
|
+
It's designed to be pleasant for humans *and* dependable for scripts and agents. By default you
|
|
7
7
|
get clean, aligned tables and detail views; add `--json` to any command for a stable,
|
|
8
8
|
machine-readable shape. It's git-aware (the "current issue" comes from your branch name) and
|
|
9
9
|
forgiving about input (`--assignee me`, team key `TES`, state and label by name). Anything the
|
|
@@ -21,8 +21,7 @@ linear issue list --json | jq -r '.[].identifier' # ready for scripts
|
|
|
21
21
|
|
|
22
22
|
## Highlights
|
|
23
23
|
|
|
24
|
-
- **Human-first by default** — aligned tables
|
|
25
|
-
color controls, and a clear notice when a list was truncated.
|
|
24
|
+
- **Human-first by default** — aligned tables and readable detail views, color-aware, paged sanely.
|
|
26
25
|
- **Agent- and script-friendly** — every data command takes `--json` and emits a stable, documented envelope on stdout; status text stays on stderr.
|
|
27
26
|
- **Git-aware** — the current issue is inferred from your branch (`tes-123-fix` → `TES-123`), so most issue commands let you drop the id.
|
|
28
27
|
- **Git + GitHub workflow** — `issue start` checks out the branch and marks the issue started, `issue describe` prints a commit message with Linear-issue trailers, `issue pr` opens a GitHub PR — all linked back to the issue.
|
|
@@ -37,15 +36,10 @@ Requires [Bun](https://bun.sh) **1.1 or newer** and a Linear API key
|
|
|
37
36
|
directly on Bun — no build step, no bundle, no Node.
|
|
38
37
|
|
|
39
38
|
```sh
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
bun install
|
|
43
|
-
bun run dev -- --help
|
|
39
|
+
bun add -g linear-sdk-cli # or, for a one-off: bunx linear-sdk-cli --help
|
|
40
|
+
linear --help
|
|
44
41
|
```
|
|
45
42
|
|
|
46
|
-
The npm package name has not been published yet. Source installation is the supported route until
|
|
47
|
-
the maintainers choose package ownership and publish the first release.
|
|
48
|
-
|
|
49
43
|
This installs two equivalent binaries: **`linear`** and the shorter **`lin`**. If you already
|
|
50
44
|
have a different tool named `linear` on your `PATH`, just use `lin`.
|
|
51
45
|
|
|
@@ -71,7 +65,6 @@ the other is silently shadowed — `linear --version` tells you which you got (`
|
|
|
71
65
|
```
|
|
72
66
|
|
|
73
67
|
(A package-manager upgrade of theirs may put `linear` back; re-run the line for your channel.)
|
|
74
|
-
|
|
75
68
|
- **Uninstall theirs** once you no longer need it. Your credentials survive: they are in the OS
|
|
76
69
|
keyring under the same service and account, and we read them — see [Authentication](#authentication).
|
|
77
70
|
|
|
@@ -83,11 +76,10 @@ collide with a global install of ours; inside such a project `bunx linear` is th
|
|
|
83
76
|
<summary>Run from source instead</summary>
|
|
84
77
|
|
|
85
78
|
```sh
|
|
86
|
-
git clone
|
|
79
|
+
git clone <this-repo> && cd linear-sdk-cli
|
|
87
80
|
bun install
|
|
88
81
|
bun run src/bin/linear.ts --help # or: bun run dev -- --help
|
|
89
82
|
```
|
|
90
|
-
|
|
91
83
|
</details>
|
|
92
84
|
|
|
93
85
|
## Quickstart
|
|
@@ -162,7 +154,7 @@ A few ideas run through every command:
|
|
|
162
154
|
`issue view TES-123`), and nearly every issue subcommand infers the id from the branch — so
|
|
163
155
|
`linear issue comment "…"`, `linear issue start`, `linear issue pr` all "just work" in context.
|
|
164
156
|
- **Human by default, `--json` for machines.** Without `--json` you get tables and detail views
|
|
165
|
-
meant to be read. With `--json`, stdout carries
|
|
157
|
+
meant to be read. With `--json`, stdout carries *only* machine JSON (a [stable
|
|
166
158
|
envelope](#scripting--agents)); status and progress always go to stderr.
|
|
167
159
|
- **Forgiving inputs.** Resolve things by how you think about them: `--assignee me`, assignee by
|
|
168
160
|
email or name, team key `TES`, `--cycle current`, workflow state and label by name (case-
|
|
@@ -219,55 +211,55 @@ Status updates (`project-update`/`pu`, `initiative-update`/`iu`) take the body f
|
|
|
219
211
|
## Command overview
|
|
220
212
|
|
|
221
213
|
Every group has `--help` with full options and (for the busy ones) an Examples section. Aliases
|
|
222
|
-
are shown in parentheses. For a machine-readable tree of
|
|
214
|
+
are shown in parentheses. For a machine-readable tree of *every* command, run
|
|
223
215
|
`linear commands --json`.
|
|
224
216
|
|
|
225
|
-
| Group
|
|
226
|
-
|
|
|
227
|
-
| **`issue`** (`i`)
|
|
228
|
-
| **`team`** (`t`)
|
|
229
|
-
| **`project`** (`p`)
|
|
230
|
-
| **`project-update`** (`pu`)
|
|
231
|
-
| **`milestone`** (`m`)
|
|
232
|
-
| **`cycle`** (`c`)
|
|
233
|
-
| **`user`** (`u`)
|
|
234
|
-
| **`label`** (`lb`)
|
|
235
|
-
| **`state`** (`st`)
|
|
236
|
-
| **`comment`** (`cm`)
|
|
237
|
-
| **`document`** (`doc`)
|
|
238
|
-
| **`attachment`** (`at`)
|
|
239
|
-
| **`favorite`** (`fav`)
|
|
240
|
-
| **`initiative`** (`init`)
|
|
241
|
-
| **`initiative-update`** (`iu`) | `create` · `list` (initiative status updates, with `--health`)
|
|
242
|
-
| **`roadmap`** (`rm`)
|
|
243
|
-
| **`notification`** (`notif`)
|
|
244
|
-
| **`organization`** (`org`)
|
|
245
|
-
| **`webhook`** (`wh`)
|
|
246
|
-
| **top-level**
|
|
217
|
+
| Group | What you can do |
|
|
218
|
+
| --- | --- |
|
|
219
|
+
| **`issue`** (`i`) | `view` · `list` · `mine` · `search` · `create` · `update` · `delete` · `archive`/`unarchive` · `start` (git branch) · `describe` · `pull-request`/`pr` · `assign` · `state` · `label` · `comment`/`comments` · `relation` · `subscribe`/`unsubscribe` · `id`/`title`/`url`/`branch` |
|
|
220
|
+
| **`team`** (`t`) | `list` · `view` · `members` · `states` · `labels` · `cycles` · `create` · `update` |
|
|
221
|
+
| **`project`** (`p`) | `list` · `view` · `create` · `update` · `archive` · `milestones` |
|
|
222
|
+
| **`project-update`** (`pu`) | `create` · `list` (project status updates, with `--health`) |
|
|
223
|
+
| **`milestone`** (`m`) | `list` · `view` · `create` · `update` · `delete` |
|
|
224
|
+
| **`cycle`** (`c`) | `list` · `view` · `current` · `create` · `update` |
|
|
225
|
+
| **`user`** (`u`) | `list` · `view` · `me` |
|
|
226
|
+
| **`label`** (`lb`) | `list` · `create` · `update` · `delete` |
|
|
227
|
+
| **`state`** (`st`) | `list` · `view` (workflow states) |
|
|
228
|
+
| **`comment`** (`cm`) | `list` · `add` · `reply` · `update` · `delete` · `resolve`/`unresolve` |
|
|
229
|
+
| **`document`** (`doc`) | `list` · `view` · `create` · `update` · `delete` |
|
|
230
|
+
| **`attachment`** (`at`) | `list` · `create` · `delete` |
|
|
231
|
+
| **`favorite`** (`fav`) | `list` · `add` · `remove` |
|
|
232
|
+
| **`initiative`** (`init`) | `list` · `view` · `create` · `update` · `archive` · `delete` |
|
|
233
|
+
| **`initiative-update`** (`iu`) | `create` · `list` (initiative status updates, with `--health`) |
|
|
234
|
+
| **`roadmap`** (`rm`) | `list` · `view` · `create` · `update` · `delete` <sup>†</sup> |
|
|
235
|
+
| **`notification`** (`notif`) | `list` · `read`/`unread` · `read-all` · `archive` · `snooze` |
|
|
236
|
+
| **`organization`** (`org`) | `view` · `members` · `invites` |
|
|
237
|
+
| **`webhook`** (`wh`) | `list` · `view` · `create` · `update` · `delete` |
|
|
238
|
+
| **top-level** | `whoami` · `auth` (`login` · `list` · `default` · `token` · `status` · `logout`) · `config` · `api` · `commands` · `schema` · `completion` |
|
|
247
239
|
|
|
248
240
|
<sup>†</sup> Linear has **deprecated roadmaps** in favor of initiatives — reads still work, but the
|
|
249
241
|
API rejects roadmap mutations with a deprecation notice. Use `initiative` for new work.
|
|
250
242
|
|
|
251
243
|
### Global flags
|
|
252
244
|
|
|
253
|
-
| Flag
|
|
254
|
-
|
|
|
255
|
-
| `--json`
|
|
256
|
-
| `-f, --fields a,b,c`
|
|
257
|
-
| `-n, --limit <n>` / `--all` | Cap results, or fetch every page. Refused on a command that pages nothing.
|
|
258
|
-
| `-t, --team <key>`
|
|
259
|
-
| `--workspace <slug>`
|
|
260
|
-
| `-y, --yes`
|
|
261
|
-
| `--no-input`
|
|
262
|
-
| `--no-ansi`
|
|
263
|
-
| `-q, --quiet` · `--debug`
|
|
245
|
+
| Flag | Effect |
|
|
246
|
+
| --- | --- |
|
|
247
|
+
| `--json` | Emit machine JSON only on stdout (see [the contract](#scripting--agents)). |
|
|
248
|
+
| `-f, --fields a,b,c` | Choose which columns to show (table output), detail lines, or JSON keys. Refused on a command that prints only a receipt. |
|
|
249
|
+
| `-n, --limit <n>` / `--all` | Cap results, or fetch every page. Refused on a command that pages nothing. |
|
|
250
|
+
| `-t, --team <key>` | Set the default team for the command. |
|
|
251
|
+
| `--workspace <slug>` | Select which stored workspace credential to use. |
|
|
252
|
+
| `-y, --yes` | Skip confirmation prompts (required for destructive actions when not a TTY). |
|
|
253
|
+
| `--no-input` | Never prompt; fail with a usage error instead of hanging. |
|
|
254
|
+
| `--no-ansi` | Disable colored output (`--no-color` is accepted as an alias). |
|
|
255
|
+
| `-q, --quiet` · `--debug` | Silence status output · verbose errors. |
|
|
264
256
|
|
|
265
257
|
## Scripting & agents
|
|
266
258
|
|
|
267
259
|
The CLI is built to be driven by scripts and agents. Everything in this section is a stable
|
|
268
260
|
contract.
|
|
269
261
|
|
|
270
|
-
**The `--json` envelope.** With `--json`, stdout carries
|
|
262
|
+
**The `--json` envelope.** With `--json`, stdout carries *only* machine JSON, pretty-printed, one
|
|
271
263
|
value per command:
|
|
272
264
|
|
|
273
265
|
- **list** commands emit a **bare array** (`[...]`) — even when empty (`[]`) and even for a single result.
|
|
@@ -276,7 +268,7 @@ value per command:
|
|
|
276
268
|
small shape like `{ "id", "identifier", "url" }`, or `{ "id", "success": true }` when the API
|
|
277
269
|
returns no body. Destructive commands add a flag such as `{ "deleted": true }` / `{ "archived": true }`.
|
|
278
270
|
- **errors** go to **stderr** as `{"error":{"message":"…","code":"…"}}` and never to stdout. With
|
|
279
|
-
`--debug`, the extra detail is carried
|
|
271
|
+
`--debug`, the extra detail is carried *inside* that object as `error.detail` rather than appended
|
|
280
272
|
after it, so `--json --debug` output stays parseable.
|
|
281
273
|
|
|
282
274
|
Status, progress, and pagination notes always go to **stderr**, so `cmd --json` is safe to pipe
|
|
@@ -290,15 +282,15 @@ ID=$(linear issue create --title "Fix" --team TES --json | jq -r '.id')
|
|
|
290
282
|
|
|
291
283
|
**Exit codes** are stable and distinct, so a script can branch on the failure class:
|
|
292
284
|
|
|
293
|
-
| Code | Name
|
|
294
|
-
| ---: |
|
|
295
|
-
|
|
|
296
|
-
|
|
|
297
|
-
|
|
|
298
|
-
|
|
|
299
|
-
|
|
|
300
|
-
|
|
|
301
|
-
|
|
|
285
|
+
| Code | Name | When |
|
|
286
|
+
| ---: | --- | --- |
|
|
287
|
+
| `0` | ok | success |
|
|
288
|
+
| `1` | runtime/API | network/GraphQL/other runtime failure (also feature-not-accessible) |
|
|
289
|
+
| `2` | usage | bad flags/arguments, missing required input, validation |
|
|
290
|
+
| `3` | not-found/ambiguous | the referenced resource doesn't exist, or a name matched many |
|
|
291
|
+
| `4` | auth | missing/invalid API key, or forbidden |
|
|
292
|
+
| `5` | rate-limited | Linear rate limit hit |
|
|
293
|
+
| `6` | cancelled | a confirmation prompt was declined — nothing was changed |
|
|
302
294
|
|
|
303
295
|
The error `code` field in the JSON envelope is one of: `usage`, `auth`, `not_found`, `ambiguous`,
|
|
304
296
|
`forbidden`, `validation`, `rate_limited`, `network`, `feature_not_accessible`, `api`, `runtime`.
|
|
@@ -312,7 +304,7 @@ so prefer the `code` field for fine-grained handling and the exit code for coars
|
|
|
312
304
|
implies this**, as does a stdout that is not a TTY: a prompt inside a pipeline is a hang, not a
|
|
313
305
|
question, so you never have to remember both flags.
|
|
314
306
|
- **`-y, --yes`** — pre-confirm destructive actions (`delete`/`archive`). Without a TTY,
|
|
315
|
-
destructive commands
|
|
307
|
+
destructive commands *require* `--yes` (they refuse rather than block). If a human declines the
|
|
316
308
|
prompt, the command exits **`6`** and reports `{"cancelled": true, "action": "…"}` under `--json`
|
|
317
309
|
— never `0`, so `linear issue delete X && …` cannot run the `&&` side after a "no".
|
|
318
310
|
- **`-q, --quiet`** — suppress success/status lines on stderr (errors still print).
|
|
@@ -342,8 +334,7 @@ grep 'type Issue ' /tmp/linear.graphql
|
|
|
342
334
|
**File-based bodies & stdin.** Long text (issue descriptions, comments, status updates) can come
|
|
343
335
|
from a file or stdin instead of a flag — e.g. `--body-file <path>` with `-` for stdin, or
|
|
344
336
|
`--editor` to open `$EDITOR`. The raw `linear api` reads from `--query-file -` and `--vars-file -`
|
|
345
|
-
too, so you can pipe GraphQL straight in.
|
|
346
|
-
rejected with this guidance: ordinary shell quotes do not convert them into line breaks.
|
|
337
|
+
too, so you can pipe GraphQL straight in.
|
|
347
338
|
|
|
348
339
|
**Agent skill.** This repo ships a Claude agent skill at `skills/linear-sdk-cli/` that teaches an
|
|
349
340
|
agent to drive the CLI — the JSON envelope, exit codes, discovery, forgiving inputs, and how to
|
|
@@ -358,11 +349,11 @@ claude plugin install linear-sdk-cli
|
|
|
358
349
|
ln -s "$PWD/skills/linear-sdk-cli" ~/.claude/skills/linear-sdk-cli
|
|
359
350
|
```
|
|
360
351
|
|
|
361
|
-
The skill is self-contained: an agent that has only the skill is told to
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
352
|
+
The skill is self-contained: an agent that has only the skill and no CLI is told to
|
|
353
|
+
`bun add -g linear-sdk-cli`, and one arriving from `schpet/linear-cli` is told not to re-enter a
|
|
354
|
+
key before `linear auth status`, since existing credentials are found. `linear commands --json`
|
|
355
|
+
gives it the full command surface at runtime, so the reference docs are a starting point, not a
|
|
356
|
+
cage.
|
|
366
357
|
|
|
367
358
|
## Coming from linear-cli
|
|
368
359
|
|
|
@@ -374,37 +365,37 @@ If your fingers or your scripts learned the other `linear-cli`, most of its spel
|
|
|
374
365
|
unchanged. The left column is theirs, the right is the canonical one this CLI documents and prints
|
|
375
366
|
in `--help`; both are accepted, and passing both at once is a usage error rather than a silent pick.
|
|
376
367
|
|
|
377
|
-
| linear-cli
|
|
378
|
-
|
|
|
379
|
-
| `-j, --json`
|
|
380
|
-
| `-w, --web`
|
|
381
|
-
| `--due-date`
|
|
382
|
-
| `--target-date`
|
|
383
|
-
| `--start-date`
|
|
384
|
-
| `--search`
|
|
385
|
-
| `--status`
|
|
386
|
-
| `--all-states`
|
|
387
|
-
| `--limit 0`
|
|
388
|
-
| `--assignee self`
|
|
389
|
-
| `--cycle active`
|
|
390
|
-
| `--cycle "<name>"`
|
|
391
|
-
| `issue query`
|
|
392
|
-
| `auth whoami`
|
|
393
|
-
| `issue comment add\|list\|update\|delete` | `comment add\|list\|…` | both mounted on one implementation
|
|
368
|
+
| linear-cli | here | where |
|
|
369
|
+
| --- | --- | --- |
|
|
370
|
+
| `-j, --json` | same | every command (global) |
|
|
371
|
+
| `-w, --web` | same | `issue view`, `issue pull-request` |
|
|
372
|
+
| `--due-date` | `--due` | `issue create`, `issue update` |
|
|
373
|
+
| `--target-date` | `--target` | `project`/`milestone`/`initiative` create & update |
|
|
374
|
+
| `--start-date` | `--start` | `project create`, `project update` |
|
|
375
|
+
| `--search` | `--query` | `issue list`, `issue mine` |
|
|
376
|
+
| `--status` | `--state` | `project list` |
|
|
377
|
+
| `--all-states` | (no-op) | `issue list` — it already spans every state |
|
|
378
|
+
| `--limit 0` | `--all` | every list; `--all` is the spelling we teach |
|
|
379
|
+
| `--assignee self` | `me` / `@me` | anywhere a user is named |
|
|
380
|
+
| `--cycle active` | `current` | anywhere a cycle is named |
|
|
381
|
+
| `--cycle "<name>"` | number, name, or id | all three resolve |
|
|
382
|
+
| `issue query` | `issue list` | same command |
|
|
383
|
+
| `auth whoami` | `whoami` | both spellings registered |
|
|
384
|
+
| `issue comment add\|list\|update\|delete` | `comment add\|list\|…` | both mounted on one implementation |
|
|
394
385
|
|
|
395
386
|
Their query filters all exist here too, under the same names — `issue list`, `issue mine` and
|
|
396
387
|
`issue search` share one filter set:
|
|
397
388
|
|
|
398
|
-
| linear-cli
|
|
399
|
-
|
|
|
400
|
-
| `-U, --unassigned`
|
|
401
|
-
| `--team A --team B`
|
|
402
|
-
| `--state a --state b`
|
|
403
|
-
| `--created-after`, `--updated-after` | same
|
|
404
|
-
| `--project-label`
|
|
405
|
-
| `--milestone`
|
|
406
|
-
| `--search-comments`
|
|
407
|
-
| `issue update --team`
|
|
389
|
+
| linear-cli | here | notes |
|
|
390
|
+
| --- | --- | --- |
|
|
391
|
+
| `-U, --unassigned` | same | `issue list`/`search`; passing it with `--assignee` is a usage error |
|
|
392
|
+
| `--team A --team B` | same | repeatable **on the three issue queries only**; elsewhere `--team` is the single default-team global |
|
|
393
|
+
| `--state a --state b` | same | repeatable; several states OR together (an issue is in one state), and each value is a state name *or* type |
|
|
394
|
+
| `--created-after`, `--updated-after` | same | `YYYY-MM-DD` or ISO 8601, inclusive; a malformed date is rejected locally instead of returning an empty list |
|
|
395
|
+
| `--project-label` | same | matches the *project's* label; mutually exclusive with `--project` |
|
|
396
|
+
| `--milestone` | same | theirs requires `--project`; here that scoping is optional — without it the milestone is matched by name across projects |
|
|
397
|
+
| `--search-comments` | `--search-comments` | `issue search` only — the plain list query has nowhere to put it |
|
|
398
|
+
| `issue update --team` | same | a real team move: the issue is renumbered, and Linear remaps its state while dropping the cycle, team-scoped labels and any project the new team is not part of |
|
|
408
399
|
|
|
409
400
|
Four differences we deliberately did **not** adopt (see `ALIGNMENT.md` for the reasoning): their
|
|
410
401
|
`issue list` is an alias of `mine` (a `list` that silently filters to you and hides started work is
|
|
@@ -423,7 +414,7 @@ env, the user config, or the OS keyring.
|
|
|
423
414
|
Precedence, highest first — `linear config` prints each value with the tier and file it came from:
|
|
424
415
|
|
|
425
416
|
1. the flag (`--team`, `--workspace`, `--sort`)
|
|
426
|
-
2. the environment (`LINEAR_TEAM`/`LINEAR_TEAM_ID`, `LINEAR_WORKSPACE`, `LINEAR_ISSUE_SORT`)
|
|
417
|
+
2. the environment (`LINEAR_TEAM`/`LINEAR_TEAM_ID`, `LINEAR_WORKSPACE`, `LINEAR_ISSUE_SORT`, `LINEAR_VCS`)
|
|
427
418
|
3. the **project config**: the first file found walking up from the working directory, checking in
|
|
428
419
|
each directory `linear.toml`, then `.linear.toml`, then `.config/linear.toml` — the same
|
|
429
420
|
names and order as [schpet/linear-cli](https://github.com/schpet/linear-cli), so a repo set
|
|
@@ -432,7 +423,7 @@ Precedence, highest first — `linear config` prints each value with the tier an
|
|
|
432
423
|
5. schpet/linear-cli's **global config**, `~/.config/linear/linear.toml` — read for non-secret
|
|
433
424
|
settings only, so a migrating user's defaults carry over
|
|
434
425
|
|
|
435
|
-
Keys: `team` (or `team_id`), `workspace`,
|
|
426
|
+
Keys: `team` (or `team_id`), `workspace`, `sort` (or `issue_sort`), `vcs`. You can write these from
|
|
436
427
|
the CLI — comments and layout in an existing file are preserved, and the write is atomic:
|
|
437
428
|
|
|
438
429
|
```sh
|
|
@@ -451,6 +442,7 @@ is what `auth login` is for.
|
|
|
451
442
|
default_workspace = "acme" # which stored credential is active
|
|
452
443
|
team = "TES" # default team key
|
|
453
444
|
sort = "priority" # default issue-list sort: priority | updated | created
|
|
445
|
+
vcs = "git"
|
|
454
446
|
|
|
455
447
|
[workspaces."acme"] # per-workspace credentials (hyphenated slugs are quoted)
|
|
456
448
|
keyring = true # secret lives in the OS keyring (service linear-cli, account acme)
|