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