linear-sdk-cli 0.8.2 → 0.9.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 CHANGED
@@ -4,6 +4,20 @@ Release Please generates this file from Conventional Commits. Feature and fix pu
4
4
  not edit it directly; their squash-commit titles are the release-note source of truth. This project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.9.0](https://github.com/zhendalf/linear-sdk-cli/compare/v0.8.3...v0.9.0) (2026-10-22)
8
+
9
+ ### Features
10
+
11
+ - add native release commands and folder project defaults ([#97](https://github.com/zhendalf/linear-sdk-cli/issues/97)) ([e382f52](https://github.com/zhendalf/linear-sdk-cli/commit/e382f52a8146ede0d9268d6c6aa557f9836e9274))
12
+ - **auth:** add resumable workspace API-key profile rename ([#95](https://github.com/zhendalf/linear-sdk-cli/issues/95)) ([9297b16](https://github.com/zhendalf/linear-sdk-cli/commit/9297b16e00928441124bffe291a0567e86c59f09))
13
+
14
+ ## [0.8.3](https://github.com/zhendalf/linear-sdk-cli/compare/v0.8.2...v0.8.3) (2026-10-19)
15
+
16
+ ### Bug Fixes
17
+
18
+ - **deps:** finish SDK and formatter upgrades ([b511b91](https://github.com/zhendalf/linear-sdk-cli/commit/b511b91d37fee6c85e362ff1eb436985176fc5cc))
19
+ - **deps:** update compatible dependency versions ([0f89628](https://github.com/zhendalf/linear-sdk-cli/commit/0f89628c9f2dd4aa63f02a7b4139822e8cea3540))
20
+
7
21
  ## [0.8.2](https://github.com/zhendalf/linear-sdk-cli/compare/v0.8.1...v0.8.2) (2026-10-13)
8
22
 
9
23
  ### Bug Fixes
@@ -187,7 +201,7 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
187
201
  `completion`), `fields` (key → `"string"`, `"number|null"`, `["string"]`, nested `{…}`,
188
202
  `{nullable: {…}}` for a relation that may be null, `"comments?"` for a key present only
189
203
  sometimes) and `variants` (the whole shape under `--web`, `--start`, `op=list`, …). New `linear
190
- commands <path...>` describes one command — a bare object under `--json` (`.output.fields`),
204
+ commands <path...>` describes one command — a bare object under `--json` (`.output.fields`),
191
205
  or usage, options and one `key: type` line per field for a human, plus its subcommands for a
192
206
  group; an unknown path is `not_found` (exit 3) with the nearest paths. The shapes are declared
193
207
  beside the interfaces they describe (`shape<IssueRow>({…})`, `src/lib/shape.ts`), where a field
@@ -232,9 +246,9 @@ commands <path...>` describes one command — a bare object under `--json` (`.ou
232
246
  TES-642).** `initiative add-project <initiative> <project>` (`--sort-order`) and
233
247
  `remove-project` (confirmed; finds the link on the project's side, so no workspace-wide paging)
234
248
  and `initiative unarchive <id>` (resolves among archived initiatives, refuses a live one). `initiative
235
- list` gains `--status`, `--owner`, `--archived` — the list stays every-status by default, and
249
+ list` gains `--status`, `--owner`, `--archived` — the list stays every-status by default, and
236
250
  the reference CLI's `--all-statuses` is accepted as the no-op it is here. `initiative
237
- create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `archivedAt` and
251
+ create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `archivedAt` and
238
252
  the linked `projects` (`{id, name, status}`).
239
253
  - **`project list --all-teams`, `team create --private`, health-only status updates (TES-642).**
240
254
  `project list` is scoped to the default team and the only way out was the accidental
@@ -439,7 +453,7 @@ create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `arc
439
453
  fills. **The team's default template is now applied**, as Linear's own new-issue form and the
440
454
  reference CLI do: the API applies it _only when asked_ (verified live — a plain `issueCreate` on
441
455
  a team with a default template returns an issue with no description), so `useDefaultTemplate:
442
- true` is sent unless `--no-default-template` (also accepted under the reference's spelling,
456
+ true` is sent unless `--no-default-template` (also accepted under the reference's spelling,
443
457
  `--no-use-default-template`); an explicit `--template` replaces it rather than stacking. A
444
458
  `--parent` child now **joins its parent's project** unless `--project` says otherwise — the
445
459
  sub-issues we created landed outside the project their parent was in — and `--milestone` can
@@ -455,7 +469,7 @@ true` is sent unless `--no-default-template` (also accepted under the reference'
455
469
  TES-596). All twelve globals are registered on every command so they can sit anywhere on the
456
470
  line, but `--fields` projects a _rendered_ result (a table or a detail block) and `--limit`/`--all`
457
471
  cap or exhaust a _paged_ query; on the ~90 commands that print a receipt (every mutation, `issue
458
- id`, `commands`, …) all three used to vanish without a word. That silence cost data: schpet's `-f`
472
+ id`, `commands`, …) all three used to vanish without a word. That silence cost data: schpet's `-f`
459
473
  is `--description-file` on `project create`, so `linear project create --name X -f desc.md`
460
474
  parsed `-f` as _our_ `--fields`, created the project with **no description**, and exited 0. Now a
461
475
  root `preAction` hook (`assertGlobalsApply` in `lib/options.ts`, driven by two applicability
@@ -492,7 +506,7 @@ id`, `commands`, …) all three used to vanish without a word. That silence cost
492
506
  the PR `ID Title` and stops copying the issue description into GitHub** (TES-637 (5)).
493
507
  `describe` used to print `Title`, a blank line, `Fixes ID`; it now prints `ID Title`, a blank
494
508
  line, `Linear-issue: Fixes ID`, `Linear-issue-url: <url>` — git trailers that `git
495
- interpret-trailers` / `git log --format=%(trailers:key=Linear-issue)` and jj's `trailers`
509
+ interpret-trailers` / `git log --format=%(trailers:key=Linear-issue)` and jj's `trailers`
496
510
  template read back, while Linear still sees the magic word directly before the id
497
511
  (linear.app/docs/github lists `fixes` and `references` among the recognized words) and links
498
512
  and closes the issue. `-r/--references` swaps the word. `--json` gains `url` and `message` (the
@@ -502,7 +516,7 @@ interpret-trailers` / `git log --format=%(trailers:key=Linear-issue)` and jj's `
502
516
  renamed. The issue _description_ is no longer pasted into the PR body: a GitHub PR is a wider
503
517
  audience than a Linear issue, and Linear links from the trailer, not the prose.
504
518
  - **A team-scoped listing with no team says it is listing every team's** (TES-637 (8)). `issue
505
- list`/`mine`/`search` and `project list` with no `--team`, no configured team and no
519
+ list`/`mine`/`search` and `project list` with no `--team`, no configured team and no
506
520
  `--all-teams` list the whole workspace — by design; schpet errors there ("No default team…"), so
507
521
  someone arriving from it could read the workspace as the team. One `info` line on stderr ("No
508
522
  default team configured; listing every team's. Pass --team <KEY> …"), silenced by `--quiet`,
@@ -605,7 +619,7 @@ list`/`mine`/`search` and `project list` with no `--team`, no configured team an
605
619
 
606
620
  - **`issue list`/`mine`/`search` rows omitted `milestone` and `cycle`** (TES-652). The list
607
621
  query selected project and labels but neither relation, so `issue list --project X --json | jq
608
- 'group_by(.milestone.name)'` put every issue in the null bucket while `issue view` showed the
622
+ 'group_by(.milestone.name)'` put every issue in the null bucket while `issue view` showed the
609
623
  milestone. Rows now carry `milestone: {id, name} | null` and `cycle: {id, number, name} | null`
610
624
  — the detail's exact object shapes, from the one existing query (two selections, no extra
611
625
  request; `LIST_QUERY` and `SEARCH_QUERY` share the relation block so they cannot drift again).
@@ -626,7 +640,7 @@ list`/`mine`/`search` and `project list` with no `--team`, no configured team an
626
640
  Non-interactively, `comment update <id>` with no body is a usage error, not an editor.
627
641
  - **`issue comment "<body>"` on a matching branch failed** (TES-619) — the README's headline
628
642
  example. `issue comment [id] [body]` read a lone operand as the id, so `linear issue comment
629
- 'shipped'` on `tes-615-…` produced "No comment body provided" (and, in a terminal, opened
643
+ 'shipped'` on `tes-615-…` produced "No comment body provided" (and, in a terminal, opened
630
644
  `$EDITOR` for the body first and only then rejected `'shipped'` as an id — work lost).
631
645
  `assign` and `state` already disambiguated a lone operand; `comment` now does the same: one
632
646
  operand that looks like an issue id is the id (body from `--body-file` or the editor), anything
@@ -656,7 +670,7 @@ list`/`mine`/`search` and `project list` with no `--team`, no configured team an
656
670
  `--help`; a bare group shows its help** (TES-633). The root has an action (bare `linear` shows the
657
671
  branch's issue) and `issue` has a default subcommand (`view`), so commander's own "unknown
658
672
  command" never fired for either: `linear issues list` was `too many arguments. Expected 0
659
- arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a valid issue id`.
673
+ arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a valid issue id`.
660
674
  Now: `Unknown command 'issues'. Did you mean 'issue'? Run 'linear --help' to see the commands.`
661
675
  and `'lst' is not a valid issue id (…). Did you mean 'linear issue list'?` — a prefix (`proj`),
662
676
  a small typo (`lable`, `isue`) or an alias (`docs`, `notif`) is close enough; two-letter aliases
@@ -691,7 +705,7 @@ arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a v
691
705
  resolves the name inside that project (names are unique per project only, the same rule
692
706
  `issue update --milestone` applies); a name without `--project` is a usage error that says so:
693
707
  `'…' is not a milestone id; pass --project <name|id> to look a milestone up by name.` `state view
694
- <name-or-type>` resolves against `--team` or the configured default team, exactly as
708
+ <name-or-type>` resolves against `--team` or the configured default team, exactly as
695
709
  `issue create --state` does, and says to pass `--team` when there is neither. UUIDs still go
696
710
  straight through.
697
711
  - **`milestone list`/`view` printed progress ×100 — `3846%`** (TES-648, found while fixing the
@@ -814,7 +828,7 @@ arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a v
814
828
  issue id inferred from the current branch as usual. `issue describe [id]` prints the issue
815
829
  title plus a commit-message trailer using Linear's git magic words (`Fixes <ID>`, or
816
830
  `References <ID>` with `-r`/`--references`) — drop it into `git commit -m "$(linear issue
817
- describe)"`. `issue pull-request [id]` (alias `pr`) creates a GitHub PR via the `gh` CLI:
831
+ describe)"`. `issue pull-request [id]` (alias `pr`) creates a GitHub PR via the `gh` CLI:
818
832
  the title defaults to the issue title (`--title` to override) and the body is the issue
819
833
  description followed by a `Fixes <ID>` trailer and the Linear URL, so the PR and issue
820
834
  reference each other. Flags: `--base`, `--head`, `--draft`, `--web`. The created PR URL is the
@@ -823,8 +837,8 @@ describe)"`. `issue pull-request [id]` (alias `pr`) creates a GitHub PR via the
823
837
  or when `gh` itself fails.
824
838
  - **Multi-workspace credentials.** Store API keys for several workspaces and switch between
825
839
  them. New `auth` subcommands: `auth list` (configured workspaces + default), `auth default
826
- <slug>` (set the default), and `auth token` (print the resolved key for scripting). `auth
827
- login` now accepts the global `--workspace <slug>` (derived from the key's organization when
840
+ <slug>` (set the default), and `auth token` (print the resolved key for scripting). `auth
841
+ login` now accepts the global `--workspace <slug>` (derived from the key's organization when
828
842
  omitted) and `auth status` reports the active credential workspace. A new global
829
843
  `--workspace <slug>` selects which stored credential to use for any command. Credentials live
830
844
  in quoted `[workspaces."<slug>"]` tables under a top-level `default_workspace`. Credential
@@ -928,7 +942,7 @@ login` now accepts the global `--workspace <slug>` (derived from the key's organ
928
942
  stderr, where it lands in scrollback, CI logs and bug reports. The same channel let a project
929
943
  `.linear.toml` — a file that is not ours and may arrive with a checkout — emit raw escape
930
944
  sequences through our error output. We now report only the reason and the position (`line 3,
931
- column 36`), never an excerpt from a credential-bearing file, and strip control characters and
945
+ column 36`), never an excerpt from a credential-bearing file, and strip control characters and
932
946
  bidi overrides from what remains. The error stays actionable: it still says exactly where to look.
933
947
  - **Credential writes could lose a config or leave a truncated one.** Writing the config in place
934
948
  truncates it first, so a crash or a concurrently-running `linear` could observe — or leave behind
package/COVERAGE.md CHANGED
@@ -4,18 +4,19 @@ Generated by `bun run audit:coverage`. Total LinearClient members: **481**.
4
4
 
5
5
  | Status | Count | Meaning |
6
6
  | -------- | ----- | ----------------------------------------------- |
7
- | curated | 107 | Has a first-class CLI command |
7
+ | curated | 128 | Has a first-class CLI command |
8
8
  | raw-only | 146 | Reachable via `linear api` (no bespoke command) |
9
- | excluded | 228 | Out of scope (admin/integration/SDK plumbing) |
9
+ | excluded | 207 | Out of scope (admin/integration/SDK plumbing) |
10
10
 
11
11
  Every member is classified — CI fails otherwise. `raw-only` members are still
12
12
  100% reachable through the raw GraphQL escape hatch; they simply lack a tailored command.
13
13
 
14
14
  ## Curated field capabilities
15
15
 
16
+ - Release projections — `ReleasePipeline.stages`, `Release.issues/links`, and release-note document content are selected through the authenticated SDK GraphQL transport, with stable command JSON shapes and offline schema validation.
16
17
  - Issue delegation (Developer Preview) — `Issue.delegate`, `IssueCreateInput.delegateId`, `IssueUpdateInput.delegateId`, and eligible agent-user fields are covered by `issue delegate` and the delegation options on `issue create`/`issue update`.
17
18
 
18
- ## Curated (107)
19
+ ## Curated (128)
19
20
 
20
21
  - `agentSession` — issue
21
22
  - `agentSessions` — issue
@@ -41,9 +42,14 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
41
42
  - `createIssue` — issue
42
43
  - `createIssueLabel` — label
43
44
  - `createIssueRelation` — issue
45
+ - `createIssueToRelease` — release
44
46
  - `createProject` — project
45
47
  - `createProjectMilestone` — milestone
46
48
  - `createProjectUpdate` — project-update
49
+ - `createRelease` — release
50
+ - `createReleaseNote` — release-note
51
+ - `createReleasePipeline` — release-pipeline
52
+ - `createReleaseStage` — release-stage
47
53
  - `createRoadmap` — roadmap
48
54
  - `createTeam` — team
49
55
  - `createWebhook` — webhook
@@ -63,6 +69,7 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
63
69
  - `deleteIssueRelation` — issue
64
70
  - `deleteProject` — project
65
71
  - `deleteProjectMilestone` — milestone
72
+ - `deleteReleaseNote` — release-note
66
73
  - `deleteRoadmap` — roadmap
67
74
  - `deleteTeam` — team
68
75
  - `deleteWebhook` — webhook
@@ -82,6 +89,7 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
82
89
  - `issueRelations` — issue
83
90
  - `issueSearch` — issue
84
91
  - `issueSubscribe` — issue
92
+ - `issueToReleaseDeleteByIssueAndRelease` — release
85
93
  - `issueUnsubscribe` — issue
86
94
  - `issues` — issue
87
95
  - `notification` — notification
@@ -96,6 +104,16 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
96
104
  - `projectStatuses` — project
97
105
  - `projectUpdates` — project-update
98
106
  - `projects` — project
107
+ - `release` — release
108
+ - `releaseComplete` — release
109
+ - `releaseNote` — release-note
110
+ - `releaseNotes` — release-note
111
+ - `releasePipeline` — release-pipeline
112
+ - `releasePipelines` — release-pipeline
113
+ - `releaseStage` — release-stage
114
+ - `releaseSync` — release
115
+ - `releaseUpdateByPipeline` — release
116
+ - `releases` — release
99
117
  - `roadmap` — roadmap
100
118
  - `roadmaps` — roadmap
101
119
  - `searchIssues` — issue
@@ -114,6 +132,10 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
114
132
  - `updateNotification` — notification
115
133
  - `updateProject` — project
116
134
  - `updateProjectMilestone` — milestone
135
+ - `updateRelease` — release
136
+ - `updateReleaseNote` — release-note
137
+ - `updateReleasePipeline` — release-pipeline
138
+ - `updateReleaseStage` — release-stage
117
139
  - `updateRoadmap` — roadmap
118
140
  - `updateTeam` — team
119
141
  - `updateWebhook` — webhook
@@ -125,7 +147,7 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
125
147
  - `workflowState` — state
126
148
  - `workflowStates` — state
127
149
 
128
- ## Excluded (228)
150
+ ## Excluded (207)
129
151
 
130
152
  - `agentActivities` — specialized/raw api only
131
153
  - `agentActivity` — specialized/raw api only
@@ -161,14 +183,9 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
161
183
  - `createIntegrationGithubCommit` — integration/import (raw api only)
162
184
  - `createIntegrationTemplate` — integration/import (raw api only)
163
185
  - `createIntegrationsSettings` — integration/import (raw api only)
164
- - `createIssueToRelease` — specialized/raw api only
165
186
  - `createNotificationSubscription` — specialized/raw api only
166
187
  - `createOrganizationInvite` — admin/enterprise (raw api only)
167
188
  - `createPushSubscription` — specialized/raw api only
168
- - `createRelease` — specialized/raw api only
169
- - `createReleaseNote` — specialized/raw api only
170
- - `createReleasePipeline` — specialized/raw api only
171
- - `createReleaseStage` — specialized/raw api only
172
189
  - `createTemplate` — specialized/raw api only
173
190
  - `createTimeSchedule` — specialized/raw api only
174
191
  - `createTriageResponsibility` — specialized/raw api only
@@ -207,7 +224,6 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
207
224
  - `deleteOrganizationInvite` — admin/enterprise (raw api only)
208
225
  - `deletePushSubscription` — specialized/raw api only
209
226
  - `deleteRelease` — specialized/raw api only
210
- - `deleteReleaseNote` — specialized/raw api only
211
227
  - `deleteReleasePipeline` — specialized/raw api only
212
228
  - `deleteTemplate` — specialized/raw api only
213
229
  - `deleteTimeSchedule` — specialized/raw api only
@@ -276,7 +292,6 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
276
292
  - `issueImportProcess` — integration/import (raw api only)
277
293
  - `issueTitleSuggestionFromCustomerRequest` — specialized/raw api only
278
294
  - `issueToRelease` — specialized/raw api only
279
- - `issueToReleaseDeleteByIssueAndRelease` — specialized/raw api only
280
295
  - `issueToReleases` — specialized/raw api only
281
296
  - `latestReleaseByAccessKey` — specialized/raw api only
282
297
  - `logout` — auth/session flows (out of scope)
@@ -291,22 +306,12 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
291
306
  - `pushSubscriptionTest` — specialized/raw api only
292
307
  - `rateLimitStatus` — SDK plumbing
293
308
  - `recentReleasesByAccessKey` — specialized/raw api only
294
- - `release` — specialized/raw api only
295
- - `releaseComplete` — specialized/raw api only
296
309
  - `releaseCompleteByAccessKey` — specialized/raw api only
297
- - `releaseNote` — specialized/raw api only
298
- - `releaseNotes` — specialized/raw api only
299
- - `releasePipeline` — specialized/raw api only
300
310
  - `releasePipelineByAccessKey` — specialized/raw api only
301
- - `releasePipelines` — specialized/raw api only
302
311
  - `releaseSearch` — specialized/raw api only
303
- - `releaseStage` — specialized/raw api only
304
312
  - `releaseStages` — specialized/raw api only
305
- - `releaseSync` — specialized/raw api only
306
313
  - `releaseSyncByAccessKey` — specialized/raw api only
307
- - `releaseUpdateByPipeline` — specialized/raw api only
308
314
  - `releaseUpdateByPipelineByAccessKey` — specialized/raw api only
309
- - `releases` — specialized/raw api only
310
315
  - `samlTokenUserAccountAuth` — auth/session flows (out of scope)
311
316
  - `semanticSearch` — specialized/raw api only
312
317
  - `slaConfigurations` — specialized/raw api only
@@ -341,10 +346,6 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
341
346
  - `updateIntegrationsSettings` — integration/import (raw api only)
342
347
  - `updateIssueImport` — integration/import (raw api only)
343
348
  - `updateNotificationSubscription` — specialized/raw api only
344
- - `updateRelease` — specialized/raw api only
345
- - `updateReleaseNote` — specialized/raw api only
346
- - `updateReleasePipeline` — specialized/raw api only
347
- - `updateReleaseStage` — specialized/raw api only
348
349
  - `updateTemplate` — specialized/raw api only
349
350
  - `updateTimeSchedule` — specialized/raw api only
350
351
  - `updateTriageResponsibility` — specialized/raw api only
package/MIGRATING.md CHANGED
@@ -179,7 +179,7 @@ Every one is now either identical, or says so on stderr, or is a usage error. Kn
179
179
  8. **`issue describe`** — theirs prints `ID Title`, a blank line, then `Linear-issue: Fixes ID` and
180
180
  `Linear-issue-url: URL`; ours printed `Title` and a bare `Fixes ID`. Piped into `git commit -m`
181
181
  that was a different commit. Adopted, byte for byte: proper trailers are what `git
182
- interpret-trailers` and jj read back, and Linear reads the magic word right before the id
182
+ interpret-trailers` and jj read back, and Linear reads the magic word right before the id
183
183
  either way (linear.app/docs/github). `-r/--references` still swaps in `References`.
184
184
  9. **`issue pull-request`** — theirs titles the PR `ID Title` and sends the issue URL as the body;
185
185
  ours titled it `Title` and pasted the issue _description_ into the body. Title adopted (a custom
package/README.md CHANGED
@@ -298,6 +298,45 @@ Successful browser, app, and API-key login also save `workspace = "<authenticate
298
298
 
299
299
  Global defaults are optional: login, credential adoption, and logout never choose a new one. Use `linear auth default <slug>` to explicitly select a global fallback. Removing that workspace clears its default without promoting another workspace. Existing defaults, including defaults imported from the reference CLI, are preserved on upgrade because older files cannot distinguish a user choice from an automatically assigned default. To opt out of a legacy default, remove the top-level `default_workspace` from the user config and, if present, `default` from the reference CLI's `credentials.toml`; retain the workspace entries.
300
300
 
301
+ After a workspace URL slug changes, migrate its stored **API-key profile** with:
302
+
303
+ ```sh
304
+ linear auth rename old-workspace new-workspace --json
305
+ # Or include additional known project configs without editing them:
306
+ linear auth rename old-workspace new-workspace --project-config /path/to/another/.linear.toml
307
+ ```
308
+
309
+ The command validates the exact stored key against Linear's current organization/slug, preserving
310
+ the credential, storage type, profile team, user workspace/default, and the reference CLI's shared
311
+ keyring list/default. It ignores environment credentials and refuses credential flags. API-key
312
+ profiles do not contain a historical organization ID; the unchanged key proves its own organization,
313
+ which is returned in the result. Configured destinations and unlisted keyring entries are collisions,
314
+ even if they contain the same key. Nothing is revoked, exported, or placed on argv.
315
+ Keyring moves use an atomic create-only operation on macOS Keychain, so another client's
316
+ destination created during the rename cannot be replaced. The Linux `secret-tool` backend lacks
317
+ this operation and keyring-backed renames are refused before changes; file-only profiles remain
318
+ supported when no source keyring entry needs moving.
319
+
320
+ Project files are never edited. JSON reports `checkedProjectConfigs` and `projectConfigUpdates`
321
+ with exact paths and old/new workspace values; human output lists the same required edits. Discovery
322
+ covers the active project config plus explicit `--project-config` paths. Check other repositories
323
+ separately, update their `workspace` selectors, and update `LINEAR_WORKSPACE` overrides. The reference
324
+ CLI's global `linear.toml` selector, if affected, is reported in `globalConfigUpdates` for manual editing.
325
+
326
+ If storage fails or the process stops partway through, repeat **the same rename command**. A
327
+ non-secret `api_key_profile_rename` marker in the private user config reserves the destination and
328
+ records recovery progress. The source is kept until a verified destination and committed metadata
329
+ exist. Recovery checks for conflicting changes and resumes the remaining steps. Do not log out,
330
+ replace, or migrate either profile while recovery is pending. Fix the reported store/permission
331
+ problem, then retry; do not delete the recovery marker or print credential-store contents.
332
+
333
+ OAuth user and app profiles (including dormant keyring records) are refused before validation or
334
+ mutation. User sessions have rotating refresh tokens and require coordination with in-flight
335
+ refreshes; app profiles contain organization-bound client credentials, and token exchanges with
336
+ different scopes can revoke existing app tokens. This command does not refresh, exchange, revoke,
337
+ or rename either OAuth lifecycle. It does not require browser authorization. See Linear's
338
+ [OAuth lifecycle documentation](https://linear.app/developers/oauth-2-0-authentication).
339
+
301
340
  If revocation is intentionally unavailable, `auth logout --local-only` removes only local state.
302
341
  If browser login temporarily superseded an existing personal API-key profile for that workspace,
303
342
  OAuth logout preserves and reactivates the API key instead of deleting it.
@@ -484,6 +523,104 @@ linear cv results 01234567-89ab-cdef-0123-456789abcdef --json
484
523
  # [{"type":"issue","id":"…","identifier":"ENG-42","name":"Fix login","url":"…"}]
485
524
  ```
486
525
 
526
+ ### Releases
527
+
528
+ `release`, `release-pipeline`, `release-stage`, and `release-note` provide native release tracking.
529
+ Linear currently requires a [Business or Enterprise workspace](https://linear.app/docs/releases.md);
530
+ Business supports up to 15 pipelines. Schema visibility or an empty list does not establish
531
+ entitlement to create pipelines, or permission to change them. Linear enforces plan, role, team,
532
+ and credential permissions; the CLI preserves `feature_not_accessible` and `forbidden` errors.
533
+
534
+ These commands use the CLI's usual OAuth or personal API credentials and `--workspace` selection.
535
+ Pipeline **access keys** are separate credentials for the official
536
+ [`linear-release`](https://github.com/linear/linear-release) CI tool. Do not put a pipeline key in
537
+ `LINEAR_API_KEY` or `LINEAR_ACCESS_TOKEN`. This CLI does not consume `LINEAR_ACCESS_KEY`, create
538
+ pipeline keys, or invoke/install the official tool automatically.
539
+
540
+ ```sh
541
+ linear release-pipeline list --json
542
+ linear release-pipeline view production --json # UUID or API slug; not display name
543
+ linear release-stage list production --all --json
544
+ linear release list --pipeline production --stage-type started --json
545
+ linear release view RELEASE_ID --json # release UUID or slug
546
+ linear release issues RELEASE_ID --all --json
547
+ linear release links RELEASE_ID --json
548
+ ```
549
+
550
+ Create a pipeline explicitly as `scheduled` or `continuous`. Creation includes Linear's default
551
+ stages. Add further `started` stages for verification/observation, or rename/reorder the defaults:
552
+
553
+ ```sh
554
+ linear release-pipeline create --name Production --type scheduled --json
555
+ linear release-pipeline update PIPELINE_ID --rollover-issues false --json
556
+ linear release-stage create --pipeline PIPELINE_ID --name Observe --type started \
557
+ --color '#5e6ad2' --position 3 --frozen true --json
558
+ linear release-stage update STAGE_ID --frozen false --json
559
+ ```
560
+
561
+ Stage types are immutable. Only `started` stages can be frozen, and Linear requires at least one
562
+ unfrozen started stage. Freezing excludes releases in that stage from automated sync; it is not a
563
+ deployment approval or a lock on manual membership edits. Pipeline updates support explicit
564
+ `true|false` for production, automatic notes, and issue rollover. Repeated `--include-path` and
565
+ `--team-id` replace the corresponding collections; `--clear-paths` and `--clear-teams` clear them.
566
+ Team associations use UUIDs, not the global default `--team`.
567
+
568
+ For a scheduled rollout, create the release with a full commit SHA, explicit version and stage,
569
+ then add the exact issue scope. `release create` otherwise defaults to a started stage on scheduled
570
+ pipelines and a completed stage on continuous pipelines:
571
+
572
+ ```sh
573
+ linear release create --pipeline PIPELINE_ID --name 'Release 1.2.3' \
574
+ --release-version 1.2.3 --commit-sha "$approved_sha" --stage PLANNED_STAGE_ID \
575
+ --target-date 2026-10-30 --description-file release-plan.md --json
576
+ linear release add-issue RELEASE_ID ENG-42 --json
577
+ linear release update RELEASE_ID --stage VERIFY_STAGE_ID --json
578
+ linear release-note create --pipeline PIPELINE_ID --release RELEASE_ID \
579
+ --title 'Verification' --body-file verification.md --json
580
+ ```
581
+
582
+ `release update <id>` edits that exact release, including SHA/version/dates or a stage UUID from
583
+ the same pipeline. `release update-version`, `sync`, and `complete` require **both** `--pipeline`
584
+ and `--release-version`; they never implicitly pick the latest release. `update-version` accepts a
585
+ stage **name**, matched by Linear, and attaches links without changing the version:
586
+
587
+ ```sh
588
+ linear release update-version --pipeline PIPELINE_ID --release-version 1.2.3 \
589
+ --stage Observe --link "CI=https://ci.example/run/123" --json
590
+ # Only after the external deployment, verification and observation gates pass:
591
+ linear release complete --pipeline PIPELINE_ID --release-version 1.2.3 \
592
+ --link "GitHub=https://github.com/acme/app/releases/tag/v1.2.3" --json
593
+ ```
594
+
595
+ Completion records Linear lifecycle state; it does not deploy, enforce service ordering, check
596
+ approvals, verify a running revision, or wait for observation. Completing a scheduled release may
597
+ move its open issues into the next release (creating one if needed), depending on pipeline settings.
598
+ When `complete --commit-sha` targets a version, Linear preserves an existing stored SHA. Set and
599
+ verify the intended SHA with `create`/`update` first. Stage transitions through `update` also trigger
600
+ Linear's lifecycle behavior, including rollover on completion.
601
+
602
+ For explicit, already-attributed data, `release sync --pipeline PIPELINE_ID --release-version 1.2.3
603
+ --commit-sha FULL_SHA --issue ENG-42 --issue ENG-43=OTHER_FULL_SHA` calls Linear's native sync API.
604
+ It does not scan Git. References accumulate; omitted issues are not removed. `--reverted-issue`
605
+ removes reverted attribution, and `release remove-issue RELEASE_ID ENG-42 --yes` removes exact
606
+ membership. Scheduled sync can assign the version to an unversioned started release or create a
607
+ release; continuous sync creates/completes a release immediately. Use sync at the appropriate point
608
+ in your deployment process, and read the returned release ID, version, SHA and stage.
609
+
610
+ Use the official `linear-release` when you need commit-range scanning, path/subject filters,
611
+ revert detection, branch/PR attribution, and pipeline-key CI authentication. It can work alongside
612
+ this CLI against the same pipeline; pass the same explicit release version. Native commands are
613
+ useful for preparation, exact membership, management, inspection, and subsequent lifecycle steps.
614
+ No Git attribution implementation or additional runtime dependency is duplicated here.
615
+
616
+ Release notes can cover repeated `--release <id>` values, or an inclusive **createdAt** range using
617
+ both `--from-release` and `--to-release`. These shapes are mutually exclusive and all releases must
618
+ belong to the note's pipeline. `release-note update` changes title, Markdown or scope; delete needs
619
+ confirmation (`--yes` noninteractively). Use `--body-file -` or `--description-file -` for stdin.
620
+ List commands emit bare arrays, views and mutations emit objects, and errors remain JSON on stderr.
621
+ `linear commands release sync --json` exposes options and output fields without credentials.
622
+ Archive/trash operations and other advanced fields remain available through `linear api`.
623
+
487
624
  ## Command overview
488
625
 
489
626
  Every group has `--help` with full options and (for the busy ones) an Examples section. Aliases
@@ -498,6 +635,10 @@ are shown in parentheses. For a machine-readable tree of _every_ command, run
498
635
  | **`project-update`** (`pu`) | `create` · `list` (project status updates, with `--health`) |
499
636
  | **`milestone`** (`m`) | `list` · `view` · `create` · `update` · `delete` |
500
637
  | **`cycle`** (`c`) | `list` · `view` · `current` · `create` · `update` |
638
+ | **`release`** | `list` · `view` · `create` · `update` · `sync` · `update-version` · `complete` · `issues` · `links` · `add-issue` · `remove-issue` |
639
+ | **`release-pipeline`** | `list` · `view` · `create` · `update` |
640
+ | **`release-stage`** | `list` · `view` · `create` · `update` |
641
+ | **`release-note`** | `list` · `view` · `create` · `update` · `delete` |
501
642
  | **`user`** (`u`) | `list` · `view` · `me` |
502
643
  | **`label`** (`lb`) | `list` · `create` · `update` · `delete` |
503
644
  | **`state`** (`st`) | `list` · `view` (workflow states) |
@@ -556,6 +697,11 @@ resolver semantics are designed, rather than acquiring a silent partial implemen
556
697
  | `attachment list` | `Issue.attachments` | yes | not exposed | not exposed |
557
698
  | `comment list`, `issue comment list`, `issue comments` | `Issue.comments` | yes | not exposed | not exposed |
558
699
  | `cycle list`, `team cycles` | `Team.cycles` | yes | not exposed | not exposed |
700
+ | `release list` | `Query.releases` | yes | `--include-archived` | `archivedAt` |
701
+ | `release-pipeline list` | `Query.releasePipelines` | yes | `--include-archived` | `archivedAt` |
702
+ | `release-stage list` | `ReleasePipeline.stages` | yes | `--include-archived` | `archivedAt` |
703
+ | `release-note list` | `Query.releaseNotes` | yes | `--include-archived` | `archivedAt` |
704
+ | `release issues`, `release links` | `Release.issues/links` | yes | not exposed | not exposed |
559
705
  | `custom-view list` | `Query.customViews` | yes | not exposed | `archivedAt` |
560
706
  | `custom-view results` | `CustomView.issues/projects/initiatives` | yes | not exposed | not exposed |
561
707
  | `document list` | `Query.documents` | yes | not exposed | not exposed |
@@ -772,10 +918,10 @@ profile slug where applicable:
772
918
  only, so a migrating user's defaults carry over
773
919
  7. no default team
774
920
 
775
- `workspace` and `sort` keep their existing flag → environment → project → user → legacy-global
921
+ `workspace`, `project`, and `sort` use flag → environment → project → user → legacy-global
776
922
  order. Workspace credential selection is independent and follows the order described above.
777
923
 
778
- Keys: `team` (or `team_id`), `workspace`, and `sort` (or `issue_sort`). You can write these from
924
+ Keys: `team` (or `team_id`), `workspace`, `project`, and `sort` (or `issue_sort`). You can write these from
779
925
  the CLI. Project and top-level setting edits preserve comments and layout. Profile updates preserve
780
926
  all TOML data and use the same locked, atomic rewrite as credential updates:
781
927
 
@@ -783,11 +929,26 @@ all TOML data and use the same locked, atomic rewrite as credential updates:
783
929
  linear config init # pick a team from a list → <git root>/.linear.toml
784
930
  linear config init --team TES # …or say which (scripts); --sort, --path, --force
785
931
  linear config set sort updated # edit one key in the project config in effect
932
+ linear config set project "My Project" # default Linear project for new issues (name or UUID)
786
933
  linear config set team ENG --user # legacy top-level user fallback (backward compatible)
787
934
  linear config set team ENG --user --workspace acme # validated default for the acme profile
788
935
  linear config # show the result, with each value's source
789
936
  ```
790
937
 
938
+ To associate a particular folder and its descendants with a Linear project, run
939
+ `linear config set project "My Project" --path .linear.toml` inside that folder. Without `--path`,
940
+ `config set` edits the discovered config, or creates one at the Git root (cwd outside Git).
941
+ Keep `team` and `workspace` in the same file when needed: discovery reads the nearest config
942
+ file, without merging ancestor files.
943
+
944
+ `issue create` (also `issue new`) uses this project automatically. `--project <name|id>` overrides
945
+ it; sub-issues inherit their parent's project ahead of the configured default. `--no-project`
946
+ creates outside any project, including parent and template defaults. `LINEAR_PROJECT` overrides
947
+ the configured value for an invocation. A configured project is resolved before creation, so
948
+ an unknown or ambiguous name fails without creating an issue; UUIDs avoid name ambiguity.
949
+ This default applies to issue creation; list, search, and update commands keep their explicit
950
+ project selection. Remove the `project` line from the config to clear the association.
951
+
791
952
  `config set` will not write `api_key` or anything else that belongs to the credential store — that
792
953
  is what `auth login` is for. A profile team is validated against the selected workspace whenever a
793
954
  credential is available. Login, adopt, OAuth refresh/fallback, and credential migration preserve
@@ -795,15 +956,15 @@ profile metadata. Logout reports whether removing a whole profile also removed i
795
956
 
796
957
  ```toml
797
958
  # ~/.config/linear/config.toml
798
- default_workspace = "acme" # which stored credential is active
799
- team = "TES" # optional legacy/global fallback team
800
- sort = "priority" # default issue-list sort: priority | updated | created
959
+ default_workspace = "acme" # which stored credential is active
960
+ team = "TES" # optional legacy/global fallback team
961
+ sort = "priority" # default issue-list sort: priority | updated | created
801
962
 
802
- [workspaces."acme"] # per-workspace credentials (hyphenated slugs are quoted)
803
- keyring = true # secret lives in the OS keyring (service linear-cli, account acme)
804
- team = "ENG" # non-secret default team for this workspace
963
+ [workspaces."acme"] # per-workspace credentials (hyphenated slugs are quoted)
964
+ keyring = true # secret lives in the OS keyring (service linear-cli, account acme)
965
+ team = "ENG" # non-secret default team for this workspace
805
966
  [workspaces."other-org"]
806
- api_key = "lin_api_yyyyyyyy" # …or, with `auth login --plaintext`, in this 0600 file
967
+ api_key = "lin_api_yyyyyyyy" # …or, with `auth login --plaintext`, in this 0600 file
807
968
  team = "OPS"
808
969
  ```
809
970
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linear-sdk-cli",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "Ergonomic command-line interface for Linear, built on @linear/sdk",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -54,8 +54,8 @@
54
54
  "lin": "bun run src/bin/linear.ts",
55
55
  "typecheck": "tsc --noEmit",
56
56
  "lint": "oxlint .",
57
- "format": "oxfmt .",
58
- "format:check": "oxfmt --check .",
57
+ "format": "oxfmt . '!CHANGELOG.md'",
58
+ "format:check": "oxfmt --check . '!CHANGELOG.md'",
59
59
  "test": "bun test test/unit test/contract",
60
60
  "test:performance": "bun test --timeout 15000 test/performance",
61
61
  "test:unit": "bun test test/unit",
@@ -71,7 +71,7 @@
71
71
  },
72
72
  "dependencies": {
73
73
  "@inquirer/prompts": "^8.7.3",
74
- "@linear/sdk": "^97.1.0",
74
+ "@linear/sdk": "^98.0.0",
75
75
  "commander": "^15.0.0",
76
76
  "graphql": "^17.0.2",
77
77
  "picocolors": "^1.1.1",
@@ -81,8 +81,8 @@
81
81
  "devDependencies": {
82
82
  "@types/bun": "^1.4.3",
83
83
  "@types/node": "^26.6.5",
84
- "oxfmt": "^0.71.0",
85
- "oxlint": "^1.87.0",
84
+ "oxfmt": "^0.73.0",
85
+ "oxlint": "^1.88.0",
86
86
  "typescript": "^7.0.2"
87
87
  }
88
88
  }