linear-sdk-cli 0.3.3 → 0.5.0
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 +15 -0
- package/COVERAGE.md +13 -9
- package/MIGRATING.md +6 -0
- package/README.md +280 -86
- package/package.json +5 -5
- package/skills/linear-sdk-cli/SKILL.md +10 -0
- package/skills/linear-sdk-cli/references/auth.md +1 -0
- package/skills/linear-sdk-cli/references/config.md +10 -7
- package/skills/linear-sdk-cli/references/custom-view.md +205 -0
- package/skills/linear-sdk-cli/references/initiative.md +10 -5
- package/skills/linear-sdk-cli/references/issue.md +222 -77
- package/skills/linear-sdk-cli/references/notification.md +3 -3
- package/skills/linear-sdk-cli/references/project.md +9 -4
- package/src/cli.ts +2 -0
- package/src/commands/custom-view.ts +279 -0
- package/src/commands/initiative.ts +23 -6
- package/src/commands/issue.ts +313 -12
- package/src/commands/meta.ts +55 -9
- package/src/commands/notification.ts +5 -3
- package/src/commands/project.ts +21 -3
- package/src/config.ts +111 -10
- package/src/lib/delegation.ts +240 -0
- package/src/lib/json-input.ts +46 -0
- package/src/lib/options.ts +23 -3
- package/src/lib/output-shapes.ts +102 -3
- package/src/output/lifecycle.ts +10 -0
- package/src/services/custom-view.ts +376 -0
- package/src/services/initiative.ts +12 -3
- package/src/services/issue.ts +498 -21
- package/src/services/project.ts +14 -3
package/README.md
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
[`@linear/sdk`](https://www.npmjs.com/package/@linear/sdk).**
|
|
5
5
|
|
|
6
6
|
It's designed to be pleasant for humans _and_ dependable for scripts and agents. By default you
|
|
7
|
-
get clean, aligned tables and detail views; add `--json` to any command for a stable,
|
|
7
|
+
get clean, aligned tables and detail views; add `--json` to any data 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
|
|
10
10
|
curated commands don't wrap is still reachable through a raw GraphQL escape hatch (`linear api`),
|
|
11
|
-
|
|
11
|
+
subject to the permissions granted to your Linear credential.
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
14
|
linear issue list --assignee me --state started # what's on my plate
|
|
@@ -19,6 +19,43 @@ linear issue list --json | jq -r '.[].identifier' # ready for scripts
|
|
|
19
19
|
> Design influenced by [`schpet/linear-cli`](https://github.com/schpet/linear-cli) (human-first,
|
|
20
20
|
> git-aware) and [`linearis`](https://github.com/linearis-oss/linearis) (JSON-first for agents).
|
|
21
21
|
|
|
22
|
+
## Why this project exists
|
|
23
|
+
|
|
24
|
+
This project grew from strong ideas in earlier tools, especially the human-first, git-aware
|
|
25
|
+
workflow credited above. Four requirements made a separate implementation the better fit for our
|
|
26
|
+
use case:
|
|
27
|
+
|
|
28
|
+
- **A smaller cloud deployment surface.** The reference implementation and its development path
|
|
29
|
+
centered on the full Deno runtime. Carrying that runtime into our cloud environments was more
|
|
30
|
+
infrastructure than the CLI warranted. This package ships its TypeScript source directly and
|
|
31
|
+
reuses the Bun runtime already present in our environment instead of packaging another runtime
|
|
32
|
+
for one tool. Later prebuilt releases moved that runtime into the artifact rather than
|
|
33
|
+
eliminating its footprint: for example, the
|
|
34
|
+
[v2.5.0 macOS ARM build](https://github.com/schpet/linear-cli/releases/tag/v2.5.0) expands to a
|
|
35
|
+
146 MB executable. Avoiding a heavyweight per-tool binary keeps cloud images and ephemeral agent
|
|
36
|
+
environments simpler and smaller.
|
|
37
|
+
- **Lower invocation overhead for agent loops.** An interactive CLI may start once; an agent can
|
|
38
|
+
start it dozens or hundreds of times while completing one workflow, so even modest cold-start
|
|
39
|
+
latency compounds. A public
|
|
40
|
+
[startup-time report](https://github.com/schpet/linear-cli/issues/261) documented the difference
|
|
41
|
+
against a compiled alternative and identified repeated agent calls as the motivating case. The
|
|
42
|
+
Bun-first implementation makes process startup a product constraint and reduces that recurring
|
|
43
|
+
overhead in our deployment model.
|
|
44
|
+
- **Alignment with Linear's official SDK.** The CLI is built directly on `@linear/sdk` and follows
|
|
45
|
+
its release cycle. Linear's SDK remains the source of truth for the API model, generated types,
|
|
46
|
+
connections, and transport; this project can focus on command design and stable output instead
|
|
47
|
+
of recreating official API machinery. A coverage audit catches additions, removals, and
|
|
48
|
+
classification drift among the SDK client's top-level members.
|
|
49
|
+
- **Agent-native development and maintenance.** This project was built from scratch by coding
|
|
50
|
+
agents, and agents continue to implement features, update dependencies, regenerate
|
|
51
|
+
documentation, run verification, and prepare maintenance and release work. Versioned maintenance
|
|
52
|
+
policy, CI, coverage checks, and release automation provide the guardrails that make that model
|
|
53
|
+
repeatable rather than ad hoc.
|
|
54
|
+
|
|
55
|
+
Together, those requirements made `linear-sdk-cli` the default for our deployments and automation
|
|
56
|
+
workflows. That is a statement about fit, not a claim that earlier tools are obsolete; this CLI
|
|
57
|
+
retains their best ideas and provides a careful compatibility and migration path.
|
|
58
|
+
|
|
22
59
|
## Highlights
|
|
23
60
|
|
|
24
61
|
- **Human-first by default** — aligned tables, rendered Markdown, safe paging for long detail,
|
|
@@ -27,13 +64,14 @@ linear issue list --json | jq -r '.[].identifier' # ready for scripts
|
|
|
27
64
|
- **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
65
|
- **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.
|
|
29
66
|
- **Forgiving inputs** — refer to things the way you think of them: `TES-123`, team `TES`, `--assignee me`, `--cycle current`, state and label by name.
|
|
30
|
-
- **Multi-workspace** — store credentials
|
|
67
|
+
- **Multi-workspace** — store credentials and a non-secret default team per workspace, then switch
|
|
68
|
+
both contexts with a global `--workspace`.
|
|
31
69
|
- **Complete & honest** — first-class commands for the core resource graph, a raw `linear api` for everything else, and a [measured coverage audit](#coverage) that CI keeps honest.
|
|
32
70
|
|
|
33
71
|
## Install
|
|
34
72
|
|
|
35
|
-
Requires [Bun](https://bun.sh) **1.1 or newer
|
|
36
|
-
|
|
73
|
+
Requires [Bun](https://bun.sh) **1.1 or newer**. Authenticate with browser OAuth (the default), an
|
|
74
|
+
invocation-scoped OAuth access token, or a personal API key. The CLI ships as TypeScript and runs
|
|
37
75
|
directly on Bun — no build step, no bundle, no Node.
|
|
38
76
|
|
|
39
77
|
```sh
|
|
@@ -44,35 +82,12 @@ lin --help
|
|
|
44
82
|
This installs two equivalent binaries: **`linear`** and the shorter **`lin`**. If you already
|
|
45
83
|
have a different tool named `linear` on your `PATH`, just use `lin`.
|
|
46
84
|
|
|
47
|
-
###
|
|
48
|
-
|
|
49
|
-
Both CLIs install a binary called **`linear`**, so whichever is earlier on your `PATH` wins and
|
|
50
|
-
the other is silently shadowed — `linear --version` tells you which you got (`0.x` is this one,
|
|
51
|
-
`2.x` is schpet's), and `which -a linear` lists both. Three ways out, pick one:
|
|
52
|
-
|
|
53
|
-
- **Use `lin`.** It is ours alone; nothing of theirs claims it. Every example in this README works
|
|
54
|
-
with `lin` in place of `linear`. Zero-risk for the transition week.
|
|
55
|
-
- **Keep theirs reachable as `linear-schpet`** and let ours have `linear`:
|
|
56
|
-
|
|
57
|
-
```sh
|
|
58
|
-
# installed with deno: reinstall under a new name, then drop the old one
|
|
59
|
-
deno install -A --reload -f -g -n linear-schpet jsr:@schpet/linear-cli
|
|
60
|
-
deno uninstall -g linear
|
|
61
|
-
# installed with homebrew (schpet/tap/linear): keep the keg, relink under a new name
|
|
62
|
-
brew unlink schpet/tap/linear
|
|
63
|
-
ln -s "$(brew --prefix)/opt/linear/bin/linear" ~/.local/bin/linear-schpet # any dir on your PATH
|
|
64
|
-
# installed with npm/bun globally: rename the shim in place
|
|
65
|
-
mv "$(command -v linear)" "$(dirname "$(command -v linear)")/linear-schpet"
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
(A package-manager upgrade of theirs may put `linear` back; re-run the line for your channel.)
|
|
85
|
+
### Existing `linear` installations
|
|
69
86
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
collide with a global install of ours; inside such a project `bunx linear` is theirs and
|
|
75
|
-
`linear`/`lin` on your `PATH` is ours.
|
|
87
|
+
Another CLI may already provide a binary named `linear`; whichever appears first on `PATH` wins.
|
|
88
|
+
Use this package's collision-free **`lin`** alias while evaluating or migrating. See
|
|
89
|
+
[`MIGRATING.md`](MIGRATING.md) for side-by-side installation, credential and config compatibility,
|
|
90
|
+
and the few commands whose behavior intentionally differs.
|
|
76
91
|
|
|
77
92
|
<details>
|
|
78
93
|
<summary>Run from source instead</summary>
|
|
@@ -88,7 +103,7 @@ bun run src/bin/linear.ts --help # or: bun run dev -- --help
|
|
|
88
103
|
## Quickstart
|
|
89
104
|
|
|
90
105
|
```sh
|
|
91
|
-
|
|
106
|
+
linear auth login # browser OAuth; or set LINEAR_API_KEY
|
|
92
107
|
linear whoami # confirm you're connected
|
|
93
108
|
|
|
94
109
|
linear issue list --assignee me --state started # my in-progress work
|
|
@@ -141,10 +156,10 @@ with `LINEAR_OAUTH_REDIRECT_URI`. No OAuth client secret is embedded, accepted,
|
|
|
141
156
|
|
|
142
157
|
For a personal API key, `auth login --key -` reads stdin, validates the viewer/workspace, stores the
|
|
143
158
|
key in the system keyring by default, and writes only a marker to the config. `--plaintext` is an
|
|
144
|
-
API-key-only compatibility option. API-key entries use `account = <workspace slug>`,
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
159
|
+
API-key-only compatibility option. API-key entries use `account = <workspace slug>`, preserving
|
|
160
|
+
compatibility with existing keyring profiles. Passing a key as `--key <value>` works but warns
|
|
161
|
+
because argv is visible to other processes. `auth migrate` moves legacy plaintext keys into the
|
|
162
|
+
keyring, and `auth token` exports stored API keys only; OAuth tokens are never printed.
|
|
148
163
|
|
|
149
164
|
### Apps, agents, and service accounts
|
|
150
165
|
|
|
@@ -216,6 +231,7 @@ linear auth login --workspace other-org # …and another workspace
|
|
|
216
231
|
linear auth login --workspace acme --key - # personal API-key fallback via stdin
|
|
217
232
|
linear auth list # show configured workspaces + which is default
|
|
218
233
|
linear auth default acme # choose the default workspace
|
|
234
|
+
linear config set team ENG --user --workspace acme # set its validated default team
|
|
219
235
|
linear --workspace other-org issue list # use a specific workspace for one command
|
|
220
236
|
linear auth logout --workspace acme # revoke OAuth, then remove one credential
|
|
221
237
|
```
|
|
@@ -225,11 +241,13 @@ If browser login temporarily superseded an existing personal API-key profile for
|
|
|
225
241
|
OAuth logout preserves and reactivates the API key instead of deleting it.
|
|
226
242
|
|
|
227
243
|
**Selection precedence** (strict): an explicit `--api-key` or `--access-token` flag bypasses the
|
|
228
|
-
environment and
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
244
|
+
environment and stored-credential lookup. Otherwise `LINEAR_API_KEY` or `LINEAR_ACCESS_TOKEN` is
|
|
245
|
+
absolute. With no invocation-scoped credential, the workspace is chosen by `--workspace` →
|
|
246
|
+
`LINEAR_WORKSPACE` env → project config `workspace` → `default_workspace` in the user config. With
|
|
247
|
+
one configured workspace it's used automatically; with several and no selection, the CLI asks you
|
|
248
|
+
to pick (via `--workspace`, project config, or `auth default`). An invocation credential does not
|
|
249
|
+
silently inherit the default or project profile's team; pair it with `--workspace` (or
|
|
250
|
+
`LINEAR_WORKSPACE`) naming a configured profile when that profile metadata should apply.
|
|
233
251
|
|
|
234
252
|
## Core concepts
|
|
235
253
|
|
|
@@ -253,10 +271,76 @@ A few ideas run through every command:
|
|
|
253
271
|
linear issue create --title "Fix login redirect" --team TES -P 2 --assignee me
|
|
254
272
|
linear issue list --assignee me --state started
|
|
255
273
|
linear issue update TES-42 --state "In Review" --add-label backend
|
|
274
|
+
linear issue label TES-42 --set-group 'Team=QA'
|
|
275
|
+
linear issue delegate TES-42 Codex
|
|
256
276
|
linear issue comment TES-42 "ready for another look"
|
|
257
277
|
linear issue archive TES-42 --yes
|
|
258
278
|
```
|
|
259
279
|
|
|
280
|
+
**Agent delegation (Developer Preview)** — the assignee remains the human owner; the delegate is
|
|
281
|
+
the agent working on their behalf. Create/update accept `--delegate <name|id>` and
|
|
282
|
+
`--clear-delegate`; the focused command is convenient on a matching branch:
|
|
283
|
+
|
|
284
|
+
```sh
|
|
285
|
+
linear issue create --title "Investigate flaky CI" --assignee me --delegate Codex
|
|
286
|
+
linear issue update TES-42 --delegate Codex
|
|
287
|
+
linear issue update TES-42 --clear-delegate
|
|
288
|
+
linear issue delegate TES-42 Codex
|
|
289
|
+
linear issue delegate --clear # issue inferred from the branch
|
|
290
|
+
linear issue delegate TES-42 Codex --dry-run --json
|
|
291
|
+
linear issue delegate TES-42 Codex --full-result --json
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Delegate resolution accepts an agent-user UUID, exact display name, or exact full name, preferring
|
|
295
|
+
case-sensitive matches. It rejects human, inactive, non-assignable, ambiguous, and team-ineligible
|
|
296
|
+
users before writing. `--dry-run` resolves every field and prints the exact nullable `delegateId`
|
|
297
|
+
input without a mutation; `--full-result` (hidden alias `--read-back`) returns the relationship
|
|
298
|
+
receipt plus the canonical issue read-back and verifies the resulting delegate. These modes are
|
|
299
|
+
currently the delegation slice only: on `issue create`/`update`, pair them with `--delegate` or
|
|
300
|
+
`--clear-delegate`.
|
|
301
|
+
|
|
302
|
+
Delegating can trigger an externally observable Agent Session/webhook. Clearing a delegate does
|
|
303
|
+
not claim to cancel a session that is already running. Linear for Agents is in Developer Preview;
|
|
304
|
+
an unsupported workspace/schema returns `feature_not_accessible`, while ordinary `issue view`
|
|
305
|
+
falls back safely and reports `delegate: null` if the preview field itself is unavailable.
|
|
306
|
+
See Linear's current [agent integration guide](https://linear.app/developers/agents) and
|
|
307
|
+
[issue-assignment guide](https://linear.app/docs/assigning-issues) for the platform semantics.
|
|
308
|
+
|
|
309
|
+
**Label-group replacement** — replace only the selected group's direct member while preserving
|
|
310
|
+
every unrelated issue label. Repeat `--set-group` to plan several groups into one relative-label
|
|
311
|
+
mutation:
|
|
312
|
+
|
|
313
|
+
```sh
|
|
314
|
+
linear issue label TES-42 --set-group 'Team=QA'
|
|
315
|
+
linear issue label TES-42 \
|
|
316
|
+
--set-group 'Team=QA' \
|
|
317
|
+
--set-group 'Issue Type=Bug' \
|
|
318
|
+
--json
|
|
319
|
+
linear issue label TES-42 --set-group 'Team=QA' --dry-run --json
|
|
320
|
+
linear issue label TES-42 --set-group 'Team=QA' --full-result --json
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
Both sides accept a UUID or an exact, case-sensitive name. The value is split on the first `=`:
|
|
324
|
+
shell-quote the whole assignment when either name contains spaces or shell metacharacters; a label
|
|
325
|
+
name may contain additional `=` characters without escaping. If the group name itself contains
|
|
326
|
+
`=`, use its UUID on the left. Group containers, archived labels, labels outside the issue team's
|
|
327
|
+
workspace/team/inherited scope, and labels that are not direct members of the named group are
|
|
328
|
+
rejected before writing. Repeating an identical assignment normalizes to one operation; conflicting
|
|
329
|
+
targets for the same resolved group are a usage error. `--set-group` cannot be mixed with the
|
|
330
|
+
existing `--add`/`--remove` mode.
|
|
331
|
+
|
|
332
|
+
The JSON receipt includes the resolved group/member IDs plus `changed` and `mutationSent`. An
|
|
333
|
+
already-satisfied request succeeds with both fields `false` and sends no mutation. `--dry-run`
|
|
334
|
+
returns the shared mutation-plan envelope and the exact `addedLabelIds`/`removedLabelIds` input;
|
|
335
|
+
`--full-result` adds the canonical issue read-back, verification, and each requested group's prior
|
|
336
|
+
members (including all members repaired from an inconsistent pre-existing state).
|
|
337
|
+
|
|
338
|
+
This is a read/modify/write operation: the CLI reads the current labels, then sends one update with
|
|
339
|
+
relative added/removed IDs, so unrelated concurrent label changes are not overwritten. Linear does
|
|
340
|
+
not expose a compare-and-swap precondition here, so another writer changing the same group between
|
|
341
|
+
the read and update can race; use `--full-result` when post-write verification matters. See Linear's
|
|
342
|
+
current [label documentation](https://linear.app/docs/labels) for group and inheritance semantics.
|
|
343
|
+
|
|
260
344
|
**Git + GitHub PR** — turn an issue into commits and a pull request. The id is inferred from the
|
|
261
345
|
branch everywhere below.
|
|
262
346
|
|
|
@@ -269,8 +353,8 @@ linear issue pr --json | jq -r .url # the created PR URL is the only thin
|
|
|
269
353
|
```
|
|
270
354
|
|
|
271
355
|
`issue describe` prints a whole commit message — `TES-123 Title`, a blank line, then two git
|
|
272
|
-
trailers, `Linear-issue: Fixes TES-123` and `Linear-issue-url: <url>` (
|
|
273
|
-
|
|
356
|
+
trailers, `Linear-issue: Fixes TES-123` and `Linear-issue-url: <url>` (compatible with existing
|
|
357
|
+
workflows, so `git interpret-trailers` and jj read it back). The magic word sits
|
|
274
358
|
right before the id, which is where Linear's
|
|
275
359
|
[git integration](https://linear.app/docs/github#link-prs-and-commits) reads it, so the issue is
|
|
276
360
|
linked — and closed on merge — when the commit lands; `-r` swaps in `References` to link without
|
|
@@ -297,7 +381,9 @@ EOF
|
|
|
297
381
|
|
|
298
382
|
```sh
|
|
299
383
|
linear project list --team TES
|
|
384
|
+
linear project list --include-archived --all --json
|
|
300
385
|
linear project view "Q3 Launch"
|
|
386
|
+
linear initiative list --include-archived --json
|
|
301
387
|
linear project-update create "Q3 Launch" --health onTrack --body "Beta is out to 10% of users."
|
|
302
388
|
linear initiative-update create "Platform" --health atRisk --body-file update.md
|
|
303
389
|
```
|
|
@@ -306,38 +392,124 @@ Status updates (`project-update`/`pu`, `initiative-update`/`iu`) take the body f
|
|
|
306
392
|
`--body-file <path>` (`-` for stdin), or `--editor` (`$EDITOR`), plus an optional
|
|
307
393
|
`--health <onTrack|atRisk|offTrack>`.
|
|
308
394
|
|
|
395
|
+
**Custom views** — create and inspect saved Issue, Project, and Initiative filters without writing
|
|
396
|
+
GraphQL. A filter is the current Linear `IssueFilter`, `ProjectFilter`, or `InitiativeFilter` JSON
|
|
397
|
+
object; pass it inline or from a file/stdin. The selected `--type` decides which typed SDK input
|
|
398
|
+
field receives it, and an omitted filter becomes `{}` so the view type still round-trips.
|
|
399
|
+
|
|
400
|
+
```sh
|
|
401
|
+
linear custom-view list
|
|
402
|
+
linear cv create --name "Urgent" --type issue \
|
|
403
|
+
--filter '{"priority":{"eq":1}}' --shared
|
|
404
|
+
linear cv view 01234567-89ab-cdef-0123-456789abcdef
|
|
405
|
+
linear cv results 01234567-89ab-cdef-0123-456789abcdef --all
|
|
406
|
+
linear cv update 01234567-89ab-cdef-0123-456789abcdef \
|
|
407
|
+
--filter-file issue-filter.json --personal
|
|
408
|
+
linear cv delete 01234567-89ab-cdef-0123-456789abcdef --yes
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Create can attach a view with one of `--scope-team`, `--scope-project`, or `--scope-initiative`.
|
|
412
|
+
Linear marks project/initiative scope fields internal on update, so update exposes only the public
|
|
413
|
+
team scope (`--scope-team` / `--clear-team-scope`) and never rewrites an unspecified scope. View
|
|
414
|
+
references are UUID-only: names are not unique, and Linear's `customViews` query intentionally
|
|
415
|
+
omits project- and initiative-scoped views. Those scoped views remain accessible by UUID. Initiative
|
|
416
|
+
views require an eligible Linear plan.
|
|
417
|
+
|
|
418
|
+
```sh
|
|
419
|
+
linear cv list --json | jq -r '.[] | [.id, .name, .type] | @tsv'
|
|
420
|
+
linear cv results 01234567-89ab-cdef-0123-456789abcdef --json
|
|
421
|
+
# [{"type":"issue","id":"…","identifier":"ENG-42","name":"Fix login","url":"…"}]
|
|
422
|
+
```
|
|
423
|
+
|
|
309
424
|
## Command overview
|
|
310
425
|
|
|
311
426
|
Every group has `--help` with full options and (for the busy ones) an Examples section. Aliases
|
|
312
427
|
are shown in parentheses. For a machine-readable tree of _every_ command, run
|
|
313
428
|
`linear commands --json`.
|
|
314
429
|
|
|
315
|
-
| Group | What you can do
|
|
316
|
-
| ------------------------------ |
|
|
317
|
-
| **`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` |
|
|
318
|
-
| **`team`** (`t`) | `list` · `view` · `members` · `states` · `labels` · `cycles` · `create` · `update`
|
|
319
|
-
| **`project`** (`p`) | `list` · `view` · `create` · `update` · `archive` · `milestones`
|
|
320
|
-
| **`project-update`** (`pu`) | `create` · `list` (project status updates, with `--health`)
|
|
321
|
-
| **`milestone`** (`m`) | `list` · `view` · `create` · `update` · `delete`
|
|
322
|
-
| **`cycle`** (`c`) | `list` · `view` · `current` · `create` · `update`
|
|
323
|
-
| **`user`** (`u`) | `list` · `view` · `me`
|
|
324
|
-
| **`label`** (`lb`) | `list` · `create` · `update` · `delete`
|
|
325
|
-
| **`state`** (`st`) | `list` · `view` (workflow states)
|
|
326
|
-
| **`comment`** (`cm`) | `list` · `add` · `reply` · `update` · `delete` · `resolve`/`unresolve`
|
|
327
|
-
| **`document`** (`doc`) | `list` · `view` · `create` · `update` · `delete`
|
|
328
|
-
| **`attachment`** (`at`) | `list` · `create` · `delete`
|
|
329
|
-
| **`favorite`** (`fav`) | `list` · `add` · `remove`
|
|
330
|
-
| **`
|
|
331
|
-
| **`initiative
|
|
332
|
-
| **`
|
|
333
|
-
| **`
|
|
334
|
-
| **`
|
|
335
|
-
| **`
|
|
336
|
-
|
|
|
430
|
+
| Group | What you can do |
|
|
431
|
+
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
432
|
+
| **`issue`** (`i`) | `view` · `list` · `mine` · `search` · `create` · `update` · `delete` · `archive`/`unarchive` · `start` (git branch) · `describe` · `pull-request`/`pr` · `assign` · `delegate` · `state` · `label` · `comment`/`comments` · `relation` · `subscribe`/`unsubscribe` · `id`/`title`/`url`/`branch` |
|
|
433
|
+
| **`team`** (`t`) | `list` · `view` · `members` · `states` · `labels` · `cycles` · `create` · `update` |
|
|
434
|
+
| **`project`** (`p`) | `list` · `view` · `create` · `update` · `archive` · `milestones` |
|
|
435
|
+
| **`project-update`** (`pu`) | `create` · `list` (project status updates, with `--health`) |
|
|
436
|
+
| **`milestone`** (`m`) | `list` · `view` · `create` · `update` · `delete` |
|
|
437
|
+
| **`cycle`** (`c`) | `list` · `view` · `current` · `create` · `update` |
|
|
438
|
+
| **`user`** (`u`) | `list` · `view` · `me` |
|
|
439
|
+
| **`label`** (`lb`) | `list` · `create` · `update` · `delete` |
|
|
440
|
+
| **`state`** (`st`) | `list` · `view` (workflow states) |
|
|
441
|
+
| **`comment`** (`cm`) | `list` · `add` · `reply` · `update` · `delete` · `resolve`/`unresolve` |
|
|
442
|
+
| **`document`** (`doc`) | `list` · `view` · `create` · `update` · `delete` |
|
|
443
|
+
| **`attachment`** (`at`) | `list` · `create` · `delete` |
|
|
444
|
+
| **`favorite`** (`fav`) | `list` · `add` · `remove` |
|
|
445
|
+
| **`custom-view`** (`cv`) | `list` · `view` · `results` · `create` · `update` · `delete` |
|
|
446
|
+
| **`initiative`** (`init`) | `list` · `view` · `create` · `update` · `archive` · `delete` |
|
|
447
|
+
| **`initiative-update`** (`iu`) | `create` · `list` (initiative status updates, with `--health`) |
|
|
448
|
+
| **`roadmap`** (`rm`) | `list` · `view` · `create` · `update` · `delete` <sup>†</sup> |
|
|
449
|
+
| **`notification`** (`notif`) | `list` · `read`/`unread` · `read-all` · `archive` · `snooze` |
|
|
450
|
+
| **`organization`** (`org`) | `view` · `members` · `invites` |
|
|
451
|
+
| **`webhook`** (`wh`) | `list` · `view` · `create` · `update` · `delete` |
|
|
452
|
+
| **top-level** | `whoami` · `auth` (`login` · `list` · `default` · `token` · `status` · `logout`) · `config` · `api` · `commands` · `schema` · `completion` |
|
|
337
453
|
|
|
338
454
|
<sup>†</sup> Linear has **deprecated roadmaps** in favor of initiatives — reads still work, but the
|
|
339
455
|
API rejects roadmap mutations with a deprecation notice. Use `initiative` for new work.
|
|
340
456
|
|
|
457
|
+
### Archived resources
|
|
458
|
+
|
|
459
|
+
Linear hides archived resources from paginated connections by default. On curated listings that
|
|
460
|
+
expose lifecycle history, `--include-archived` widens the result to live plus historical records;
|
|
461
|
+
it is not an archived-only filter. Linear can return trashed resources in that widened set, so
|
|
462
|
+
project, initiative, and issue JSON rows expose both `archivedAt: string|null` and
|
|
463
|
+
`trashed: boolean`, and human tables label `(archived)` and `(trashed)` separately. Notification
|
|
464
|
+
rows expose `archivedAt` (the public schema has no `trashed` field for notifications).
|
|
465
|
+
|
|
466
|
+
`--all` remains pagination-only. The two flags are independent and composable:
|
|
467
|
+
|
|
468
|
+
```sh
|
|
469
|
+
linear project list --include-archived --all --json
|
|
470
|
+
linear initiative list --include-archived --status Completed --json
|
|
471
|
+
linear issue search "migration" --include-archived --all --json
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
`initiative list --archived` remains a compatibility alias for `--include-archived`; passing both
|
|
475
|
+
spellings is an explicit usage error. The alias is retained for the current compatibility window
|
|
476
|
+
(all `0.x` releases; removal no earlier than `1.0`) and is listed by help and
|
|
477
|
+
`linear commands --json`.
|
|
478
|
+
|
|
479
|
+
The following matrix audits every curated list/search surface against the installed
|
|
480
|
+
`@linear/sdk@92.0.0` types and the live public schema exposed by `linear schema`. “Schema support”
|
|
481
|
+
means that the exact connection used by the command accepts `includeArchived`; it does not by
|
|
482
|
+
itself add a CLI contract. This change standardizes the four resource families named by
|
|
483
|
+
the issue. The remaining schema-capable collections stay unchanged until their own output and
|
|
484
|
+
resolver semantics are designed, rather than acquiring a silent partial implementation.
|
|
485
|
+
|
|
486
|
+
| Curated command(s) | Connection audited | Schema support | CLI lifecycle option | Lifecycle fields in list JSON |
|
|
487
|
+
| ------------------------------------------------------ | --------------------------------------------- | -------------- | ---------------------------------------------- | ----------------------------- |
|
|
488
|
+
| `issue list`, `issue mine` | `Query.issues` | yes | `--include-archived` | `archivedAt`, `trashed` |
|
|
489
|
+
| `issue search` | `Query.searchIssues` | yes | `--include-archived` | `archivedAt`, `trashed` |
|
|
490
|
+
| `project list` | `Query.projects` | yes | `--include-archived` | `archivedAt`, `trashed` |
|
|
491
|
+
| `initiative list` | `Query.initiatives` | yes | `--include-archived` (`--archived` alias) | `archivedAt`, `trashed` |
|
|
492
|
+
| `notification list` | `Query.notifications` | yes | `--include-archived` | `archivedAt` |
|
|
493
|
+
| `attachment list` | `Issue.attachments` | yes | not exposed | not exposed |
|
|
494
|
+
| `comment list`, `issue comment list`, `issue comments` | `Issue.comments` | yes | not exposed | not exposed |
|
|
495
|
+
| `cycle list`, `team cycles` | `Team.cycles` | yes | not exposed | not exposed |
|
|
496
|
+
| `custom-view list` | `Query.customViews` | yes | not exposed | `archivedAt` |
|
|
497
|
+
| `custom-view results` | `CustomView.issues/projects/initiatives` | yes | not exposed | not exposed |
|
|
498
|
+
| `document list` | `Query.documents` | yes | not exposed | not exposed |
|
|
499
|
+
| `favorite list` | `Query.favorites` | yes | not exposed | not exposed |
|
|
500
|
+
| `initiative-update list` | `Initiative.initiativeUpdates` | yes | not exposed | not exposed |
|
|
501
|
+
| `issue agent-session list` | `Query.agentSessions` / `Issue.agentSessions` | yes | not exposed | not exposed |
|
|
502
|
+
| `label list`, `team labels` | `Query.issueLabels` / `Team.labels` | yes | not exposed | not exposed |
|
|
503
|
+
| `milestone list`, `project milestones` | `Project.projectMilestones` | yes | not exposed | not exposed |
|
|
504
|
+
| `project-update list` | `Project.projectUpdates` | yes | not exposed | not exposed |
|
|
505
|
+
| `roadmap list` | `Query.roadmaps` | yes | not exposed | not exposed |
|
|
506
|
+
| `state list`, `team states` | `Team.states` | yes | not exposed | not exposed |
|
|
507
|
+
| `team list` | `Query.teams` | yes | not exposed | not exposed |
|
|
508
|
+
| `team members`, `user list`, `organization members` | user connections | yes | not exposed (`--include-disabled` is separate) | not exposed |
|
|
509
|
+
| `organization invites` | `Query.organizationInvites` | yes | not exposed | not exposed |
|
|
510
|
+
| `webhook list` | `Query.webhooks` | yes | not exposed | not exposed |
|
|
511
|
+
| `auth list` | local credential registry (no API connection) | no | not applicable | not applicable |
|
|
512
|
+
|
|
341
513
|
### Global flags
|
|
342
514
|
|
|
343
515
|
| Flag | Effect |
|
|
@@ -408,7 +580,8 @@ so prefer the `code` field for fine-grained handling and the exit code for coars
|
|
|
408
580
|
- **`-q, --quiet`** — suppress success/status lines on stderr (errors still print).
|
|
409
581
|
- **`-n, --limit <n>` / `--all`** — `--limit` caps results; `--all` exhausts pagination. With
|
|
410
582
|
neither, the default cap is **50**. `--all` (and very large `--limit`) can be slow and
|
|
411
|
-
rate-limit-prone on big workspaces.
|
|
583
|
+
rate-limit-prone on big workspaces. It never includes archived resources; combine it with
|
|
584
|
+
`--include-archived` where that option is available.
|
|
412
585
|
|
|
413
586
|
```sh
|
|
414
587
|
# agent-safe: no prompts, no chatter, fail fast with a parseable error
|
|
@@ -458,15 +631,15 @@ versioned maintenance policy. See [`skills/README.md`](skills/README.md) for the
|
|
|
458
631
|
contract.
|
|
459
632
|
|
|
460
633
|
The CLI skill is self-contained: an agent that has only the skill and no CLI is told to
|
|
461
|
-
`bun add -g linear-sdk-cli`, and
|
|
462
|
-
|
|
463
|
-
gives it the full command surface at runtime, so the reference docs are a starting point,
|
|
464
|
-
cage.
|
|
634
|
+
`bun add -g linear-sdk-cli`, and a migrating user is told to check `linear auth status` before
|
|
635
|
+
re-entering a key because compatible credentials are discovered automatically. `linear commands
|
|
636
|
+
--json` gives it the full command surface at runtime, so the reference docs are a starting point,
|
|
637
|
+
not a cage.
|
|
465
638
|
|
|
466
639
|
## Coming from linear-cli
|
|
467
640
|
|
|
468
641
|
> The full guide — install without a collision, credentials found without a re-login, config
|
|
469
|
-
>
|
|
642
|
+
> compatibility, and the places where behavior intentionally differs — is
|
|
470
643
|
> [`MIGRATING.md`](MIGRATING.md). This section is the flag-level cheat sheet.
|
|
471
644
|
|
|
472
645
|
If your fingers or your scripts learned the other `linear-cli`, most of its spellings work here
|
|
@@ -519,42 +692,54 @@ Non-secret defaults live in `~/.config/linear/config.toml` (user-wide) or a proj
|
|
|
519
692
|
file. **Secrets never go in a project file** — the API key is only ever read from the flag, the
|
|
520
693
|
env, the user config, or the OS keyring.
|
|
521
694
|
|
|
522
|
-
|
|
695
|
+
Effective team precedence, highest first — `linear config` prints the tier, file, and selected
|
|
696
|
+
profile slug where applicable:
|
|
523
697
|
|
|
524
698
|
1. the flag (`--team`, `--workspace`, `--sort`)
|
|
525
699
|
2. the environment (`LINEAR_TEAM`/`LINEAR_TEAM_ID`, `LINEAR_WORKSPACE`, `LINEAR_ISSUE_SORT`)
|
|
526
700
|
3. the **project config**: the first file found walking up from the working directory, checking in
|
|
527
|
-
each directory `linear.toml`, then `.linear.toml`, then `.config/linear.toml
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
701
|
+
each directory `linear.toml`, then `.linear.toml`, then `.config/linear.toml`; compatible
|
|
702
|
+
existing project configuration is picked up as-is
|
|
703
|
+
4. `team` on the selected `[workspaces."<slug>"]` profile
|
|
704
|
+
5. the legacy top-level `team` in the **user config**, `~/.config/linear/config.toml`
|
|
705
|
+
(`$XDG_CONFIG_HOME` honored)
|
|
706
|
+
6. the legacy **global config**, `~/.config/linear/linear.toml` — read for non-secret settings
|
|
707
|
+
only, so a migrating user's defaults carry over
|
|
708
|
+
7. no default team
|
|
709
|
+
|
|
710
|
+
`workspace` and `sort` keep their existing flag → environment → project → user → legacy-global
|
|
711
|
+
order. Workspace credential selection is independent and follows the order described above.
|
|
533
712
|
|
|
534
713
|
Keys: `team` (or `team_id`), `workspace`, and `sort` (or `issue_sort`). You can write these from
|
|
535
|
-
the CLI
|
|
714
|
+
the CLI. Project and top-level setting edits preserve comments and layout. Profile updates preserve
|
|
715
|
+
all TOML data and use the same locked, atomic rewrite as credential updates:
|
|
536
716
|
|
|
537
717
|
```sh
|
|
538
718
|
linear config init # pick a team from a list → <git root>/.linear.toml
|
|
539
719
|
linear config init --team TES # …or say which (scripts); --sort, --path, --force
|
|
540
720
|
linear config set sort updated # edit one key in the project config in effect
|
|
541
|
-
linear config set team ENG --user #
|
|
721
|
+
linear config set team ENG --user # legacy top-level user fallback (backward compatible)
|
|
722
|
+
linear config set team ENG --user --workspace acme # validated default for the acme profile
|
|
542
723
|
linear config # show the result, with each value's source
|
|
543
724
|
```
|
|
544
725
|
|
|
545
726
|
`config set` will not write `api_key` or anything else that belongs to the credential store — that
|
|
546
|
-
is what `auth login` is for.
|
|
727
|
+
is what `auth login` is for. A profile team is validated against the selected workspace whenever a
|
|
728
|
+
credential is available. Login, adopt, OAuth refresh/fallback, and credential migration preserve
|
|
729
|
+
profile metadata. Logout reports whether removing a whole profile also removed its team metadata.
|
|
547
730
|
|
|
548
731
|
```toml
|
|
549
732
|
# ~/.config/linear/config.toml
|
|
550
733
|
default_workspace = "acme" # which stored credential is active
|
|
551
|
-
team = "TES" #
|
|
734
|
+
team = "TES" # optional legacy/global fallback team
|
|
552
735
|
sort = "priority" # default issue-list sort: priority | updated | created
|
|
553
736
|
|
|
554
737
|
[workspaces."acme"] # per-workspace credentials (hyphenated slugs are quoted)
|
|
555
738
|
keyring = true # secret lives in the OS keyring (service linear-cli, account acme)
|
|
739
|
+
team = "ENG" # non-secret default team for this workspace
|
|
556
740
|
[workspaces."other-org"]
|
|
557
741
|
api_key = "lin_api_yyyyyyyy" # …or, with `auth login --plaintext`, in this 0600 file
|
|
742
|
+
team = "OPS"
|
|
558
743
|
```
|
|
559
744
|
|
|
560
745
|
Relevant environment variables: **`LINEAR_API_KEY`** or **`LINEAR_ACCESS_TOKEN`** (absolute —
|
|
@@ -586,8 +771,8 @@ Use `linear schema` to discover types and fields first, then reach for `linear a
|
|
|
586
771
|
### Coverage
|
|
587
772
|
|
|
588
773
|
Coverage of the SDK is **measured, not asserted.** `linear api` reaches the full GraphQL API, and
|
|
589
|
-
a generated audit ([COVERAGE.md](./COVERAGE.md)) classifies every
|
|
590
|
-
|
|
774
|
+
a generated audit ([COVERAGE.md](./COVERAGE.md)) classifies every current `LinearClient` member as
|
|
775
|
+
`curated` (a first-class command), `raw-only` (reachable via `linear api`), or
|
|
591
776
|
`excluded` (admin/integration/SDK plumbing). CI fails on any drift from the committed snapshot, so
|
|
592
777
|
the claim stays honest as the SDK evolves.
|
|
593
778
|
|
|
@@ -612,6 +797,15 @@ bun run audit:coverage # regenerate COVERAGE.md (add --update to re-baseline t
|
|
|
612
797
|
bun run janitor # sweep leaked `clitest-` fixtures from the test workspace
|
|
613
798
|
```
|
|
614
799
|
|
|
800
|
+
The live delegation suite is additionally gated by `LINEAR_CLI_LIVE_AGENT_ID`, the UUID of an
|
|
801
|
+
explicit disposable-test agent. It can trigger that integration's Agent Session/webhook and is
|
|
802
|
+
never enabled by the general live-test flag alone.
|
|
803
|
+
|
|
804
|
+
The live label-group suite is additionally gated by `LINEAR_CLI_TEST_LABEL_GROUP` and
|
|
805
|
+
`LINEAR_CLI_TEST_LABEL_GROUP_MEMBER`, naming (or identifying by UUID) a pre-existing group and one
|
|
806
|
+
direct member usable by `LINEAR_CLI_TEST_TEAM`. It creates only a disposable issue; it does not
|
|
807
|
+
create, rename, move, or delete the shared label-group fixture.
|
|
808
|
+
|
|
615
809
|
Architecture is three layers — **commands** (commander wiring) → **services** (one module per
|
|
616
810
|
resource, the only place that touches the SDK) → **`@linear/sdk`**. The machine JSON envelope is
|
|
617
811
|
locked and contract-tested so output never silently drifts.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linear-sdk-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Ergonomic command-line interface for Linear, built on @linear/sdk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"janitor": "bun run test/janitor.ts"
|
|
70
70
|
},
|
|
71
71
|
"dependencies": {
|
|
72
|
-
"@inquirer/prompts": "^8.7.
|
|
72
|
+
"@inquirer/prompts": "^8.7.1",
|
|
73
73
|
"@linear/sdk": "^92.0.0",
|
|
74
74
|
"commander": "^15.0.0",
|
|
75
75
|
"graphql": "^17.0.2",
|
|
@@ -79,9 +79,9 @@
|
|
|
79
79
|
"packageManager": "bun@1.4.0",
|
|
80
80
|
"devDependencies": {
|
|
81
81
|
"@types/bun": "^1.4.0",
|
|
82
|
-
"@types/node": "^26.4.
|
|
83
|
-
"oxfmt": "^0.
|
|
84
|
-
"oxlint": "^1.
|
|
82
|
+
"@types/node": "^26.4.1",
|
|
83
|
+
"oxfmt": "^0.66.0",
|
|
84
|
+
"oxlint": "^1.81.0",
|
|
85
85
|
"typescript": "^7.0.2"
|
|
86
86
|
}
|
|
87
87
|
}
|
|
@@ -317,6 +317,14 @@ linear config init
|
|
|
317
317
|
linear config set
|
|
318
318
|
linear config show
|
|
319
319
|
|
|
320
|
+
linear custom-view
|
|
321
|
+
linear custom-view create
|
|
322
|
+
linear custom-view delete
|
|
323
|
+
linear custom-view list
|
|
324
|
+
linear custom-view results
|
|
325
|
+
linear custom-view update
|
|
326
|
+
linear custom-view view
|
|
327
|
+
|
|
320
328
|
linear cycle
|
|
321
329
|
linear cycle create
|
|
322
330
|
linear cycle current
|
|
@@ -366,6 +374,7 @@ linear issue comment list
|
|
|
366
374
|
linear issue comment update
|
|
367
375
|
linear issue comments
|
|
368
376
|
linear issue create
|
|
377
|
+
linear issue delegate
|
|
369
378
|
linear issue delete
|
|
370
379
|
linear issue describe
|
|
371
380
|
linear issue id
|
|
@@ -481,6 +490,7 @@ prints the same). These are supplementary — `--help` on any command is authori
|
|
|
481
490
|
- [comment](references/comment.md) — Manage comments
|
|
482
491
|
- [completion](references/completion.md) — Output a shell completion script
|
|
483
492
|
- [config](references/config.md) — Show the resolved configuration (secrets redacted)
|
|
493
|
+
- [custom-view](references/custom-view.md) — Create and manage saved custom views
|
|
484
494
|
- [cycle](references/cycle.md) — Work with cycles
|
|
485
495
|
- [document](references/document.md) — Work with documents
|
|
486
496
|
- [favorite](references/favorite.md) — Manage your favorites
|