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/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
- so nothing in the Linear API is out of bounds.
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 for several workspaces and switch with a global `--workspace`.
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** and a Linear API key
36
- (Settings → Security & access → **Personal API keys**). The CLI ships as TypeScript and runs
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
- ### If you already have `schpet/linear-cli` installed
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
- - **Uninstall theirs** once you no longer need it. Your credentials survive: they are in the OS
71
- keyring under the same service and account, and we read them — see [Authentication](#authentication).
72
-
73
- Their project-pinned install (`bun add -D @schpet/linear-cli`, run as `bunx linear`) does not
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
- export LINEAR_API_KEY=lin_api_xxxxxxxx # quickest way to get going
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>`, matching
145
- [schpet/linear-cli](https://github.com/schpet/linear-cli). Passing a key as `--key <value>` works but
146
- warns because argv is visible to other processes. `auth migrate` moves legacy plaintext keys into
147
- the keyring, and `auth token` exports stored API keys only; OAuth tokens are never printed.
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 workspace selection entirely. Otherwise `LINEAR_API_KEY` or
229
- `LINEAR_ACCESS_TOKEN` is absolute. With no invocation-scoped credential, the workspace is chosen
230
- by `--workspace` → `LINEAR_WORKSPACE` env → project config `workspace` → `default_workspace` in the
231
- user config. With one configured workspace it's used automatically; with several and no selection,
232
- the CLI asks you to pick (via `--workspace`, project config, or `auth default`).
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>` (the same text
273
- schpet/linear-cli prints, so `git interpret-trailers` and jj read it back). The magic word sits
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
- | **`initiative`** (`init`) | `list` · `view` · `create` · `update` · `archive` · `delete` |
331
- | **`initiative-update`** (`iu`) | `create` · `list` (initiative status updates, with `--health`) |
332
- | **`roadmap`** (`rm`) | `list` · `view` · `create` · `update` · `delete` &nbsp;<sup>†</sup> |
333
- | **`notification`** (`notif`) | `list` · `read`/`unread` · `read-all` · `archive` · `snooze` |
334
- | **`organization`** (`org`) | `view` · `members` · `invites` |
335
- | **`webhook`** (`wh`) | `list` · `view` · `create` · `update` · `delete` |
336
- | **top-level** | `whoami` · `auth` (`login` · `list` · `default` · `token` · `status` · `logout`) · `config` · `api` · `commands` · `schema` · `completion` |
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` &nbsp;<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 one arriving from `schpet/linear-cli` is told not to re-enter a
462
- key before `linear auth status`, since existing credentials are found. `linear commands --json`
463
- gives it the full command surface at runtime, so the reference docs are a starting point, not a
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
- > read from the same files, and the three places the two CLIs would silently differ — is
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
- Precedence, highest first — `linear config` prints each value with the tier and file it came from:
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` — the same
528
- names and order as [schpet/linear-cli](https://github.com/schpet/linear-cli), so a repo set
529
- up with its `linear config` (which writes `<git root>/.config/linear.toml`) is picked up as-is
530
- 4. the **user config**, `~/.config/linear/config.toml` (`$XDG_CONFIG_HOME` honored)
531
- 5. schpet/linear-cli's **global config**, `~/.config/linear/linear.toml` — read for non-secret
532
- settings only, so a migrating user's defaults carry over
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 — comments and layout in an existing file are preserved, and the write is atomic:
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 # …or in ~/.config/linear/config.toml
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" # default team key
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 one of the ~460 `LinearClient`
590
- members as `curated` (a first-class command), `raw-only` (reachable via `linear api`), or
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.3",
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.0",
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.0",
83
- "oxfmt": "^0.65.0",
84
- "oxlint": "^1.80.0",
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
@@ -119,6 +119,7 @@ workspace: string
119
119
  removed: boolean
120
120
  revocation: string
121
121
  fallbackCredentialType: string | null
122
+ teamMetadataRemoved: boolean
122
123
  ```
123
124
 
124
125
  ### `linear auth migrate`