linear-sdk-cli 0.4.0 → 0.5.1
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 +17 -0
- package/COVERAGE.md +8 -3
- package/MIGRATING.md +6 -0
- package/README.md +193 -37
- package/package.json +8 -7
- package/skills/linear-sdk-cli/SKILL.md +11 -0
- package/skills/linear-sdk-cli/references/auth.md +4 -1
- package/skills/linear-sdk-cli/references/comment.md +2 -0
- package/skills/linear-sdk-cli/references/config.md +10 -7
- package/skills/linear-sdk-cli/references/initiative.md +10 -5
- package/skills/linear-sdk-cli/references/issue.md +224 -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 +1 -0
- package/src/commands/comment.ts +9 -7
- package/src/commands/initiative.ts +23 -6
- package/src/commands/issue.ts +313 -12
- package/src/commands/meta.ts +85 -13
- package/src/commands/notification.ts +5 -3
- package/src/commands/open.ts +2 -1
- package/src/commands/project.ts +21 -3
- package/src/config.ts +124 -39
- package/src/context.ts +21 -1
- package/src/lib/action.ts +8 -0
- package/src/lib/delegation.ts +240 -0
- package/src/lib/options.ts +17 -3
- package/src/lib/output-shapes.ts +78 -3
- package/src/lib/resolve.ts +53 -39
- package/src/output/lifecycle.ts +10 -0
- package/src/services/initiative.ts +12 -3
- package/src/services/issue.ts +498 -21
- package/src/services/project.ts +14 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,23 @@ 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.5.1](https://github.com/zhendalf/linear-sdk-cli/compare/v0.5.0...v0.5.1) (2026-09-06)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- associate login with projects and make workspace defaults explicit ([#62](https://github.com/zhendalf/linear-sdk-cli/issues/62)) ([faf4028](https://github.com/zhendalf/linear-sdk-cli/commit/faf4028246a947dc13f674d75df2b1ba3c0ac0f3))
|
|
12
|
+
- **comment:** accept create as an add alias ([#57](https://github.com/zhendalf/linear-sdk-cli/issues/57)) ([c8146b9](https://github.com/zhendalf/linear-sdk-cli/commit/c8146b98f60b68b5cc8bd2063fdd49cc9cc91216))
|
|
13
|
+
- reduce label resolution requests and latency ([#64](https://github.com/zhendalf/linear-sdk-cli/issues/64)) ([d1300a5](https://github.com/zhendalf/linear-sdk-cli/commit/d1300a5379e99a466a6111e24fbcfa7b85f21c1f))
|
|
14
|
+
|
|
15
|
+
## [0.5.0](https://github.com/zhendalf/linear-sdk-cli/compare/v0.4.0...v0.5.0) (2026-09-03)
|
|
16
|
+
|
|
17
|
+
### Features
|
|
18
|
+
|
|
19
|
+
- **config:** support per-workspace default teams ([#50](https://github.com/zhendalf/linear-sdk-cli/issues/50)) ([06be757](https://github.com/zhendalf/linear-sdk-cli/commit/06be757ddb81652dea4da32434e63de773cb6760)), closes [#44](https://github.com/zhendalf/linear-sdk-cli/issues/44)
|
|
20
|
+
- **issue:** add native agent delegation ([#52](https://github.com/zhendalf/linear-sdk-cli/issues/52)) ([15055ff](https://github.com/zhendalf/linear-sdk-cli/commit/15055ff73d46f6ddf31679b7bb5f37fc6e3fddda)), closes [#45](https://github.com/zhendalf/linear-sdk-cli/issues/45)
|
|
21
|
+
- **issue:** set labels by group ([#54](https://github.com/zhendalf/linear-sdk-cli/issues/54)) ([4c471cd](https://github.com/zhendalf/linear-sdk-cli/commit/4c471cd23c9409ba395cf43514f9e251a42ef4d6)), closes [#46](https://github.com/zhendalf/linear-sdk-cli/issues/46)
|
|
22
|
+
- standardize archived-resource listing ([#53](https://github.com/zhendalf/linear-sdk-cli/issues/53)) ([735d1c8](https://github.com/zhendalf/linear-sdk-cli/commit/735d1c87894736de257b8888edb8500934e502f3)), closes [#49](https://github.com/zhendalf/linear-sdk-cli/issues/49)
|
|
23
|
+
|
|
7
24
|
## [0.4.0](https://github.com/zhendalf/linear-sdk-cli/compare/v0.3.3...v0.4.0) (2026-08-28)
|
|
8
25
|
|
|
9
26
|
### Features
|
package/COVERAGE.md
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
# SDK Coverage Audit
|
|
2
2
|
|
|
3
|
-
Generated by `bun run audit:coverage`. Total LinearClient members: **
|
|
3
|
+
Generated by `bun run audit:coverage`. Total LinearClient members: **473**.
|
|
4
4
|
|
|
5
5
|
| Status | Count | Meaning |
|
|
6
6
|
| -------- | ----- | ----------------------------------------------- |
|
|
7
7
|
| curated | 107 | Has a first-class CLI command |
|
|
8
8
|
| raw-only | 142 | Reachable via `linear api` (no bespoke command) |
|
|
9
|
-
| excluded |
|
|
9
|
+
| excluded | 224 | 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
|
+
## Curated field capabilities
|
|
15
|
+
|
|
16
|
+
- 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
|
+
|
|
14
18
|
## Curated (107)
|
|
15
19
|
|
|
16
20
|
- `agentSession` — issue
|
|
@@ -121,7 +125,7 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
|
|
|
121
125
|
- `workflowState` — state
|
|
122
126
|
- `workflowStates` — state
|
|
123
127
|
|
|
124
|
-
## Excluded (
|
|
128
|
+
## Excluded (224)
|
|
125
129
|
|
|
126
130
|
- `agentActivities` — specialized/raw api only
|
|
127
131
|
- `agentActivity` — specialized/raw api only
|
|
@@ -306,6 +310,7 @@ Every member is classified — CI fails otherwise. `raw-only` members are still
|
|
|
306
310
|
- `ssoUrlFromEmail` — auth/session flows (out of scope)
|
|
307
311
|
- `suspendUser` — admin/enterprise (raw api only)
|
|
308
312
|
- `template` — specialized/raw api only
|
|
313
|
+
- `templateSearch` — specialized/raw api only
|
|
309
314
|
- `templates` — specialized/raw api only
|
|
310
315
|
- `templatesForIntegration` — integration/import (raw api only)
|
|
311
316
|
- `timeSchedule` — specialized/raw api only
|
package/MIGRATING.md
CHANGED
|
@@ -124,6 +124,10 @@ Everything with no row is spelled identically.
|
|
|
124
124
|
|
|
125
125
|
Passing both spellings at once (`--due` _and_ `--due-date`) is a usage error, not a coin flip.
|
|
126
126
|
|
|
127
|
+
This CLI previously taught `initiative list --archived`. Its canonical spelling is now
|
|
128
|
+
`--include-archived`, matching issue, project, and notification lists. The old spelling remains a
|
|
129
|
+
compatibility alias throughout `0.x`; passing both spellings together is a usage error.
|
|
130
|
+
|
|
127
131
|
**Short flags are the one place we did not follow.** schpet 2.5's own tree assigns `-a` four
|
|
128
132
|
meanings (`--app`, `--all`, `--assignee`, `--attach`), `-f` four, `-y` three, `-t` two (`--title`
|
|
129
133
|
and `--team`). There is no consistent target to copy. Ours holds **one meaning per letter across
|
|
@@ -154,6 +158,8 @@ Every one is now either identical, or says so on stderr, or is a usage error. Kn
|
|
|
154
158
|
under `--quiet`) that deactivated users are still excluded and names `--include-disabled`, and
|
|
155
159
|
`--help` says so up front. Not adopted: making `--all` mean "and deactivated" on two commands
|
|
156
160
|
would give one global two meanings.
|
|
161
|
+
The same rule applies to lifecycle history: `--all` never includes archived records. Use
|
|
162
|
+
`--include-archived --all` to widen lifecycle visibility and exhaust pagination independently.
|
|
157
163
|
5. **`-f <file>` on `project create/update`, `document create/update`** — theirs: `--description-file` /
|
|
158
164
|
`--content-file`. Ours: `-f` is `--fields`, and it used to be _dropped_ on those commands, so
|
|
159
165
|
`project create --name X -f desc.md` created the project with no description and exited 0.
|
package/README.md
CHANGED
|
@@ -64,7 +64,8 @@ retains their best ideas and provides a careful compatibility and migration path
|
|
|
64
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.
|
|
65
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.
|
|
66
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.
|
|
67
|
-
- **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`.
|
|
68
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.
|
|
69
70
|
|
|
70
71
|
## Install
|
|
@@ -230,20 +231,28 @@ linear auth login --workspace other-org # …and another workspace
|
|
|
230
231
|
linear auth login --workspace acme --key - # personal API-key fallback via stdin
|
|
231
232
|
linear auth list # show configured workspaces + which is default
|
|
232
233
|
linear auth default acme # choose the default workspace
|
|
234
|
+
linear config set team ENG --user --workspace acme # set its validated default team
|
|
233
235
|
linear --workspace other-org issue list # use a specific workspace for one command
|
|
234
236
|
linear auth logout --workspace acme # revoke OAuth, then remove one credential
|
|
235
237
|
```
|
|
236
238
|
|
|
239
|
+
Successful browser and API-key login also save `workspace = "<authenticated slug>"` in the discovered project config, preserving other settings and comments. If no config exists, login creates `.linear.toml` at the Git root (or cwd outside Git). Use `auth login --no-project` for credential-only login. Human output reports the association path; JSON includes `projectConfigPath` (null with `--no-project`). Credentials remain in the global credential store. Environment overrides still take precedence over this association.
|
|
240
|
+
|
|
241
|
+
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.
|
|
242
|
+
|
|
237
243
|
If revocation is intentionally unavailable, `auth logout --local-only` removes only local state.
|
|
238
244
|
If browser login temporarily superseded an existing personal API-key profile for that workspace,
|
|
239
245
|
OAuth logout preserves and reactivates the API key instead of deleting it.
|
|
240
246
|
|
|
241
247
|
**Selection precedence** (strict): an explicit `--api-key` or `--access-token` flag bypasses the
|
|
242
|
-
environment and
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
248
|
+
environment and stored-credential lookup. Otherwise `LINEAR_API_KEY` or `LINEAR_ACCESS_TOKEN` is
|
|
249
|
+
absolute. With no invocation-scoped credential, the workspace is chosen by `--workspace` →
|
|
250
|
+
`LINEAR_WORKSPACE` env → project config `workspace` → `default_workspace` in the user config. With
|
|
251
|
+
one configured workspace it's used automatically; with several and no selection, interactive
|
|
252
|
+
commands prompt for an invocation-only choice. Noninteractive and JSON commands fail clearly
|
|
253
|
+
and explain how to select a workspace. Auth repair and discovery commands remain available. An invocation credential does not
|
|
254
|
+
silently inherit the default or project profile's team; pair it with `--workspace` (or
|
|
255
|
+
`LINEAR_WORKSPACE`) naming a configured profile when that profile metadata should apply.
|
|
247
256
|
|
|
248
257
|
## Core concepts
|
|
249
258
|
|
|
@@ -267,10 +276,76 @@ A few ideas run through every command:
|
|
|
267
276
|
linear issue create --title "Fix login redirect" --team TES -P 2 --assignee me
|
|
268
277
|
linear issue list --assignee me --state started
|
|
269
278
|
linear issue update TES-42 --state "In Review" --add-label backend
|
|
279
|
+
linear issue label TES-42 --set-group 'Team=QA'
|
|
280
|
+
linear issue delegate TES-42 Codex
|
|
270
281
|
linear issue comment TES-42 "ready for another look"
|
|
271
282
|
linear issue archive TES-42 --yes
|
|
272
283
|
```
|
|
273
284
|
|
|
285
|
+
**Agent delegation (Developer Preview)** — the assignee remains the human owner; the delegate is
|
|
286
|
+
the agent working on their behalf. Create/update accept `--delegate <name|id>` and
|
|
287
|
+
`--clear-delegate`; the focused command is convenient on a matching branch:
|
|
288
|
+
|
|
289
|
+
```sh
|
|
290
|
+
linear issue create --title "Investigate flaky CI" --assignee me --delegate Codex
|
|
291
|
+
linear issue update TES-42 --delegate Codex
|
|
292
|
+
linear issue update TES-42 --clear-delegate
|
|
293
|
+
linear issue delegate TES-42 Codex
|
|
294
|
+
linear issue delegate --clear # issue inferred from the branch
|
|
295
|
+
linear issue delegate TES-42 Codex --dry-run --json
|
|
296
|
+
linear issue delegate TES-42 Codex --full-result --json
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Delegate resolution accepts an agent-user UUID, exact display name, or exact full name, preferring
|
|
300
|
+
case-sensitive matches. It rejects human, inactive, non-assignable, ambiguous, and team-ineligible
|
|
301
|
+
users before writing. `--dry-run` resolves every field and prints the exact nullable `delegateId`
|
|
302
|
+
input without a mutation; `--full-result` (hidden alias `--read-back`) returns the relationship
|
|
303
|
+
receipt plus the canonical issue read-back and verifies the resulting delegate. These modes are
|
|
304
|
+
currently the delegation slice only: on `issue create`/`update`, pair them with `--delegate` or
|
|
305
|
+
`--clear-delegate`.
|
|
306
|
+
|
|
307
|
+
Delegating can trigger an externally observable Agent Session/webhook. Clearing a delegate does
|
|
308
|
+
not claim to cancel a session that is already running. Linear for Agents is in Developer Preview;
|
|
309
|
+
an unsupported workspace/schema returns `feature_not_accessible`, while ordinary `issue view`
|
|
310
|
+
falls back safely and reports `delegate: null` if the preview field itself is unavailable.
|
|
311
|
+
See Linear's current [agent integration guide](https://linear.app/developers/agents) and
|
|
312
|
+
[issue-assignment guide](https://linear.app/docs/assigning-issues) for the platform semantics.
|
|
313
|
+
|
|
314
|
+
**Label-group replacement** — replace only the selected group's direct member while preserving
|
|
315
|
+
every unrelated issue label. Repeat `--set-group` to plan several groups into one relative-label
|
|
316
|
+
mutation:
|
|
317
|
+
|
|
318
|
+
```sh
|
|
319
|
+
linear issue label TES-42 --set-group 'Team=QA'
|
|
320
|
+
linear issue label TES-42 \
|
|
321
|
+
--set-group 'Team=QA' \
|
|
322
|
+
--set-group 'Issue Type=Bug' \
|
|
323
|
+
--json
|
|
324
|
+
linear issue label TES-42 --set-group 'Team=QA' --dry-run --json
|
|
325
|
+
linear issue label TES-42 --set-group 'Team=QA' --full-result --json
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
Both sides accept a UUID or an exact, case-sensitive name. The value is split on the first `=`:
|
|
329
|
+
shell-quote the whole assignment when either name contains spaces or shell metacharacters; a label
|
|
330
|
+
name may contain additional `=` characters without escaping. If the group name itself contains
|
|
331
|
+
`=`, use its UUID on the left. Group containers, archived labels, labels outside the issue team's
|
|
332
|
+
workspace/team/inherited scope, and labels that are not direct members of the named group are
|
|
333
|
+
rejected before writing. Repeating an identical assignment normalizes to one operation; conflicting
|
|
334
|
+
targets for the same resolved group are a usage error. `--set-group` cannot be mixed with the
|
|
335
|
+
existing `--add`/`--remove` mode.
|
|
336
|
+
|
|
337
|
+
The JSON receipt includes the resolved group/member IDs plus `changed` and `mutationSent`. An
|
|
338
|
+
already-satisfied request succeeds with both fields `false` and sends no mutation. `--dry-run`
|
|
339
|
+
returns the shared mutation-plan envelope and the exact `addedLabelIds`/`removedLabelIds` input;
|
|
340
|
+
`--full-result` adds the canonical issue read-back, verification, and each requested group's prior
|
|
341
|
+
members (including all members repaired from an inconsistent pre-existing state).
|
|
342
|
+
|
|
343
|
+
This is a read/modify/write operation: the CLI reads the current labels, then sends one update with
|
|
344
|
+
relative added/removed IDs, so unrelated concurrent label changes are not overwritten. Linear does
|
|
345
|
+
not expose a compare-and-swap precondition here, so another writer changing the same group between
|
|
346
|
+
the read and update can race; use `--full-result` when post-write verification matters. See Linear's
|
|
347
|
+
current [label documentation](https://linear.app/docs/labels) for group and inheritance semantics.
|
|
348
|
+
|
|
274
349
|
**Git + GitHub PR** — turn an issue into commits and a pull request. The id is inferred from the
|
|
275
350
|
branch everywhere below.
|
|
276
351
|
|
|
@@ -311,7 +386,9 @@ EOF
|
|
|
311
386
|
|
|
312
387
|
```sh
|
|
313
388
|
linear project list --team TES
|
|
389
|
+
linear project list --include-archived --all --json
|
|
314
390
|
linear project view "Q3 Launch"
|
|
391
|
+
linear initiative list --include-archived --json
|
|
315
392
|
linear project-update create "Q3 Launch" --health onTrack --body "Beta is out to 10% of users."
|
|
316
393
|
linear initiative-update create "Platform" --health atRisk --body-file update.md
|
|
317
394
|
```
|
|
@@ -355,33 +432,89 @@ Every group has `--help` with full options and (for the busy ones) an Examples s
|
|
|
355
432
|
are shown in parentheses. For a machine-readable tree of _every_ command, run
|
|
356
433
|
`linear commands --json`.
|
|
357
434
|
|
|
358
|
-
| Group | What you can do
|
|
359
|
-
| ------------------------------ |
|
|
360
|
-
| **`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` |
|
|
361
|
-
| **`team`** (`t`) | `list` · `view` · `members` · `states` · `labels` · `cycles` · `create` · `update`
|
|
362
|
-
| **`project`** (`p`) | `list` · `view` · `create` · `update` · `archive` · `milestones`
|
|
363
|
-
| **`project-update`** (`pu`) | `create` · `list` (project status updates, with `--health`)
|
|
364
|
-
| **`milestone`** (`m`) | `list` · `view` · `create` · `update` · `delete`
|
|
365
|
-
| **`cycle`** (`c`) | `list` · `view` · `current` · `create` · `update`
|
|
366
|
-
| **`user`** (`u`) | `list` · `view` · `me`
|
|
367
|
-
| **`label`** (`lb`) | `list` · `create` · `update` · `delete`
|
|
368
|
-
| **`state`** (`st`) | `list` · `view` (workflow states)
|
|
369
|
-
| **`comment`** (`cm`) | `list` · `add` · `reply` · `update` · `delete` · `resolve`/`unresolve`
|
|
370
|
-
| **`document`** (`doc`) | `list` · `view` · `create` · `update` · `delete`
|
|
371
|
-
| **`attachment`** (`at`) | `list` · `create` · `delete`
|
|
372
|
-
| **`favorite`** (`fav`) | `list` · `add` · `remove`
|
|
373
|
-
| **`custom-view`** (`cv`) | `list` · `view` · `results` · `create` · `update` · `delete`
|
|
374
|
-
| **`initiative`** (`init`) | `list` · `view` · `create` · `update` · `archive` · `delete`
|
|
375
|
-
| **`initiative-update`** (`iu`) | `create` · `list` (initiative status updates, with `--health`)
|
|
376
|
-
| **`roadmap`** (`rm`) | `list` · `view` · `create` · `update` · `delete` <sup>†</sup>
|
|
377
|
-
| **`notification`** (`notif`) | `list` · `read`/`unread` · `read-all` · `archive` · `snooze`
|
|
378
|
-
| **`organization`** (`org`) | `view` · `members` · `invites`
|
|
379
|
-
| **`webhook`** (`wh`) | `list` · `view` · `create` · `update` · `delete`
|
|
380
|
-
| **top-level** | `whoami` · `auth` (`login` · `list` · `default` · `token` · `status` · `logout`) · `config` · `api` · `commands` · `schema` · `completion`
|
|
435
|
+
| Group | What you can do |
|
|
436
|
+
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
437
|
+
| **`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` |
|
|
438
|
+
| **`team`** (`t`) | `list` · `view` · `members` · `states` · `labels` · `cycles` · `create` · `update` |
|
|
439
|
+
| **`project`** (`p`) | `list` · `view` · `create` · `update` · `archive` · `milestones` |
|
|
440
|
+
| **`project-update`** (`pu`) | `create` · `list` (project status updates, with `--health`) |
|
|
441
|
+
| **`milestone`** (`m`) | `list` · `view` · `create` · `update` · `delete` |
|
|
442
|
+
| **`cycle`** (`c`) | `list` · `view` · `current` · `create` · `update` |
|
|
443
|
+
| **`user`** (`u`) | `list` · `view` · `me` |
|
|
444
|
+
| **`label`** (`lb`) | `list` · `create` · `update` · `delete` |
|
|
445
|
+
| **`state`** (`st`) | `list` · `view` (workflow states) |
|
|
446
|
+
| **`comment`** (`cm`) | `list` · `add` · `reply` · `update` · `delete` · `resolve`/`unresolve` |
|
|
447
|
+
| **`document`** (`doc`) | `list` · `view` · `create` · `update` · `delete` |
|
|
448
|
+
| **`attachment`** (`at`) | `list` · `create` · `delete` |
|
|
449
|
+
| **`favorite`** (`fav`) | `list` · `add` · `remove` |
|
|
450
|
+
| **`custom-view`** (`cv`) | `list` · `view` · `results` · `create` · `update` · `delete` |
|
|
451
|
+
| **`initiative`** (`init`) | `list` · `view` · `create` · `update` · `archive` · `delete` |
|
|
452
|
+
| **`initiative-update`** (`iu`) | `create` · `list` (initiative status updates, with `--health`) |
|
|
453
|
+
| **`roadmap`** (`rm`) | `list` · `view` · `create` · `update` · `delete` <sup>†</sup> |
|
|
454
|
+
| **`notification`** (`notif`) | `list` · `read`/`unread` · `read-all` · `archive` · `snooze` |
|
|
455
|
+
| **`organization`** (`org`) | `view` · `members` · `invites` |
|
|
456
|
+
| **`webhook`** (`wh`) | `list` · `view` · `create` · `update` · `delete` |
|
|
457
|
+
| **top-level** | `whoami` · `auth` (`login` · `list` · `default` · `token` · `status` · `logout`) · `config` · `api` · `commands` · `schema` · `completion` |
|
|
381
458
|
|
|
382
459
|
<sup>†</sup> Linear has **deprecated roadmaps** in favor of initiatives — reads still work, but the
|
|
383
460
|
API rejects roadmap mutations with a deprecation notice. Use `initiative` for new work.
|
|
384
461
|
|
|
462
|
+
### Archived resources
|
|
463
|
+
|
|
464
|
+
Linear hides archived resources from paginated connections by default. On curated listings that
|
|
465
|
+
expose lifecycle history, `--include-archived` widens the result to live plus historical records;
|
|
466
|
+
it is not an archived-only filter. Linear can return trashed resources in that widened set, so
|
|
467
|
+
project, initiative, and issue JSON rows expose both `archivedAt: string|null` and
|
|
468
|
+
`trashed: boolean`, and human tables label `(archived)` and `(trashed)` separately. Notification
|
|
469
|
+
rows expose `archivedAt` (the public schema has no `trashed` field for notifications).
|
|
470
|
+
|
|
471
|
+
`--all` remains pagination-only. The two flags are independent and composable:
|
|
472
|
+
|
|
473
|
+
```sh
|
|
474
|
+
linear project list --include-archived --all --json
|
|
475
|
+
linear initiative list --include-archived --status Completed --json
|
|
476
|
+
linear issue search "migration" --include-archived --all --json
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
`initiative list --archived` remains a compatibility alias for `--include-archived`; passing both
|
|
480
|
+
spellings is an explicit usage error. The alias is retained for the current compatibility window
|
|
481
|
+
(all `0.x` releases; removal no earlier than `1.0`) and is listed by help and
|
|
482
|
+
`linear commands --json`.
|
|
483
|
+
|
|
484
|
+
The following matrix audits every curated list/search surface against the installed
|
|
485
|
+
`@linear/sdk@92.0.0` types and the live public schema exposed by `linear schema`. “Schema support”
|
|
486
|
+
means that the exact connection used by the command accepts `includeArchived`; it does not by
|
|
487
|
+
itself add a CLI contract. This change standardizes the four resource families named by
|
|
488
|
+
the issue. The remaining schema-capable collections stay unchanged until their own output and
|
|
489
|
+
resolver semantics are designed, rather than acquiring a silent partial implementation.
|
|
490
|
+
|
|
491
|
+
| Curated command(s) | Connection audited | Schema support | CLI lifecycle option | Lifecycle fields in list JSON |
|
|
492
|
+
| ------------------------------------------------------ | --------------------------------------------- | -------------- | ---------------------------------------------- | ----------------------------- |
|
|
493
|
+
| `issue list`, `issue mine` | `Query.issues` | yes | `--include-archived` | `archivedAt`, `trashed` |
|
|
494
|
+
| `issue search` | `Query.searchIssues` | yes | `--include-archived` | `archivedAt`, `trashed` |
|
|
495
|
+
| `project list` | `Query.projects` | yes | `--include-archived` | `archivedAt`, `trashed` |
|
|
496
|
+
| `initiative list` | `Query.initiatives` | yes | `--include-archived` (`--archived` alias) | `archivedAt`, `trashed` |
|
|
497
|
+
| `notification list` | `Query.notifications` | yes | `--include-archived` | `archivedAt` |
|
|
498
|
+
| `attachment list` | `Issue.attachments` | yes | not exposed | not exposed |
|
|
499
|
+
| `comment list`, `issue comment list`, `issue comments` | `Issue.comments` | yes | not exposed | not exposed |
|
|
500
|
+
| `cycle list`, `team cycles` | `Team.cycles` | yes | not exposed | not exposed |
|
|
501
|
+
| `custom-view list` | `Query.customViews` | yes | not exposed | `archivedAt` |
|
|
502
|
+
| `custom-view results` | `CustomView.issues/projects/initiatives` | yes | not exposed | not exposed |
|
|
503
|
+
| `document list` | `Query.documents` | yes | not exposed | not exposed |
|
|
504
|
+
| `favorite list` | `Query.favorites` | yes | not exposed | not exposed |
|
|
505
|
+
| `initiative-update list` | `Initiative.initiativeUpdates` | yes | not exposed | not exposed |
|
|
506
|
+
| `issue agent-session list` | `Query.agentSessions` / `Issue.agentSessions` | yes | not exposed | not exposed |
|
|
507
|
+
| `label list`, `team labels` | `Query.issueLabels` / `Team.labels` | yes | not exposed | not exposed |
|
|
508
|
+
| `milestone list`, `project milestones` | `Project.projectMilestones` | yes | not exposed | not exposed |
|
|
509
|
+
| `project-update list` | `Project.projectUpdates` | yes | not exposed | not exposed |
|
|
510
|
+
| `roadmap list` | `Query.roadmaps` | yes | not exposed | not exposed |
|
|
511
|
+
| `state list`, `team states` | `Team.states` | yes | not exposed | not exposed |
|
|
512
|
+
| `team list` | `Query.teams` | yes | not exposed | not exposed |
|
|
513
|
+
| `team members`, `user list`, `organization members` | user connections | yes | not exposed (`--include-disabled` is separate) | not exposed |
|
|
514
|
+
| `organization invites` | `Query.organizationInvites` | yes | not exposed | not exposed |
|
|
515
|
+
| `webhook list` | `Query.webhooks` | yes | not exposed | not exposed |
|
|
516
|
+
| `auth list` | local credential registry (no API connection) | no | not applicable | not applicable |
|
|
517
|
+
|
|
385
518
|
### Global flags
|
|
386
519
|
|
|
387
520
|
| Flag | Effect |
|
|
@@ -452,7 +585,8 @@ so prefer the `code` field for fine-grained handling and the exit code for coars
|
|
|
452
585
|
- **`-q, --quiet`** — suppress success/status lines on stderr (errors still print).
|
|
453
586
|
- **`-n, --limit <n>` / `--all`** — `--limit` caps results; `--all` exhausts pagination. With
|
|
454
587
|
neither, the default cap is **50**. `--all` (and very large `--limit`) can be slow and
|
|
455
|
-
rate-limit-prone on big workspaces.
|
|
588
|
+
rate-limit-prone on big workspaces. It never includes archived resources; combine it with
|
|
589
|
+
`--include-archived` where that option is available.
|
|
456
590
|
|
|
457
591
|
```sh
|
|
458
592
|
# agent-safe: no prompts, no chatter, fail fast with a parseable error
|
|
@@ -563,41 +697,54 @@ Non-secret defaults live in `~/.config/linear/config.toml` (user-wide) or a proj
|
|
|
563
697
|
file. **Secrets never go in a project file** — the API key is only ever read from the flag, the
|
|
564
698
|
env, the user config, or the OS keyring.
|
|
565
699
|
|
|
566
|
-
|
|
700
|
+
Effective team precedence, highest first — `linear config` prints the tier, file, and selected
|
|
701
|
+
profile slug where applicable:
|
|
567
702
|
|
|
568
703
|
1. the flag (`--team`, `--workspace`, `--sort`)
|
|
569
704
|
2. the environment (`LINEAR_TEAM`/`LINEAR_TEAM_ID`, `LINEAR_WORKSPACE`, `LINEAR_ISSUE_SORT`)
|
|
570
705
|
3. the **project config**: the first file found walking up from the working directory, checking in
|
|
571
706
|
each directory `linear.toml`, then `.linear.toml`, then `.config/linear.toml`; compatible
|
|
572
707
|
existing project configuration is picked up as-is
|
|
573
|
-
4. the
|
|
574
|
-
5. the legacy **
|
|
708
|
+
4. `team` on the selected `[workspaces."<slug>"]` profile
|
|
709
|
+
5. the legacy top-level `team` in the **user config**, `~/.config/linear/config.toml`
|
|
710
|
+
(`$XDG_CONFIG_HOME` honored)
|
|
711
|
+
6. the legacy **global config**, `~/.config/linear/linear.toml` — read for non-secret settings
|
|
575
712
|
only, so a migrating user's defaults carry over
|
|
713
|
+
7. no default team
|
|
714
|
+
|
|
715
|
+
`workspace` and `sort` keep their existing flag → environment → project → user → legacy-global
|
|
716
|
+
order. Workspace credential selection is independent and follows the order described above.
|
|
576
717
|
|
|
577
718
|
Keys: `team` (or `team_id`), `workspace`, and `sort` (or `issue_sort`). You can write these from
|
|
578
|
-
the CLI
|
|
719
|
+
the CLI. Project and top-level setting edits preserve comments and layout. Profile updates preserve
|
|
720
|
+
all TOML data and use the same locked, atomic rewrite as credential updates:
|
|
579
721
|
|
|
580
722
|
```sh
|
|
581
723
|
linear config init # pick a team from a list → <git root>/.linear.toml
|
|
582
724
|
linear config init --team TES # …or say which (scripts); --sort, --path, --force
|
|
583
725
|
linear config set sort updated # edit one key in the project config in effect
|
|
584
|
-
linear config set team ENG --user #
|
|
726
|
+
linear config set team ENG --user # legacy top-level user fallback (backward compatible)
|
|
727
|
+
linear config set team ENG --user --workspace acme # validated default for the acme profile
|
|
585
728
|
linear config # show the result, with each value's source
|
|
586
729
|
```
|
|
587
730
|
|
|
588
731
|
`config set` will not write `api_key` or anything else that belongs to the credential store — that
|
|
589
|
-
is what `auth login` is for.
|
|
732
|
+
is what `auth login` is for. A profile team is validated against the selected workspace whenever a
|
|
733
|
+
credential is available. Login, adopt, OAuth refresh/fallback, and credential migration preserve
|
|
734
|
+
profile metadata. Logout reports whether removing a whole profile also removed its team metadata.
|
|
590
735
|
|
|
591
736
|
```toml
|
|
592
737
|
# ~/.config/linear/config.toml
|
|
593
738
|
default_workspace = "acme" # which stored credential is active
|
|
594
|
-
team = "TES" #
|
|
739
|
+
team = "TES" # optional legacy/global fallback team
|
|
595
740
|
sort = "priority" # default issue-list sort: priority | updated | created
|
|
596
741
|
|
|
597
742
|
[workspaces."acme"] # per-workspace credentials (hyphenated slugs are quoted)
|
|
598
743
|
keyring = true # secret lives in the OS keyring (service linear-cli, account acme)
|
|
744
|
+
team = "ENG" # non-secret default team for this workspace
|
|
599
745
|
[workspaces."other-org"]
|
|
600
746
|
api_key = "lin_api_yyyyyyyy" # …or, with `auth login --plaintext`, in this 0600 file
|
|
747
|
+
team = "OPS"
|
|
601
748
|
```
|
|
602
749
|
|
|
603
750
|
Relevant environment variables: **`LINEAR_API_KEY`** or **`LINEAR_ACCESS_TOKEN`** (absolute —
|
|
@@ -655,6 +802,15 @@ bun run audit:coverage # regenerate COVERAGE.md (add --update to re-baseline t
|
|
|
655
802
|
bun run janitor # sweep leaked `clitest-` fixtures from the test workspace
|
|
656
803
|
```
|
|
657
804
|
|
|
805
|
+
The live delegation suite is additionally gated by `LINEAR_CLI_LIVE_AGENT_ID`, the UUID of an
|
|
806
|
+
explicit disposable-test agent. It can trigger that integration's Agent Session/webhook and is
|
|
807
|
+
never enabled by the general live-test flag alone.
|
|
808
|
+
|
|
809
|
+
The live label-group suite is additionally gated by `LINEAR_CLI_TEST_LABEL_GROUP` and
|
|
810
|
+
`LINEAR_CLI_TEST_LABEL_GROUP_MEMBER`, naming (or identifying by UUID) a pre-existing group and one
|
|
811
|
+
direct member usable by `LINEAR_CLI_TEST_TEAM`. It creates only a disposable issue; it does not
|
|
812
|
+
create, rename, move, or delete the shared label-group fixture.
|
|
813
|
+
|
|
658
814
|
Architecture is three layers — **commands** (commander wiring) → **services** (one module per
|
|
659
815
|
resource, the only place that touches the SDK) → **`@linear/sdk`**. The machine JSON envelope is
|
|
660
816
|
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.1",
|
|
4
4
|
"description": "Ergonomic command-line interface for Linear, built on @linear/sdk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"format": "oxfmt .",
|
|
58
58
|
"format:check": "oxfmt --check .",
|
|
59
59
|
"test": "bun test test/unit test/contract",
|
|
60
|
+
"test:performance": "bun test --timeout 15000 test/performance",
|
|
60
61
|
"test:unit": "bun test test/unit",
|
|
61
62
|
"test:contract": "bun test test/contract",
|
|
62
63
|
"test:live": "LINEAR_CLI_LIVE=1 bun test --timeout 60000 test/integration",
|
|
@@ -69,8 +70,8 @@
|
|
|
69
70
|
"janitor": "bun run test/janitor.ts"
|
|
70
71
|
},
|
|
71
72
|
"dependencies": {
|
|
72
|
-
"@inquirer/prompts": "^8.7.
|
|
73
|
-
"@linear/sdk": "^
|
|
73
|
+
"@inquirer/prompts": "^8.7.1",
|
|
74
|
+
"@linear/sdk": "^93.0.1",
|
|
74
75
|
"commander": "^15.0.0",
|
|
75
76
|
"graphql": "^17.0.2",
|
|
76
77
|
"picocolors": "^1.1.1",
|
|
@@ -78,10 +79,10 @@
|
|
|
78
79
|
},
|
|
79
80
|
"packageManager": "bun@1.4.0",
|
|
80
81
|
"devDependencies": {
|
|
81
|
-
"@types/bun": "^1.4.
|
|
82
|
-
"@types/node": "^26.4.
|
|
83
|
-
"oxfmt": "^0.
|
|
84
|
-
"oxlint": "^1.
|
|
82
|
+
"@types/bun": "^1.4.1",
|
|
83
|
+
"@types/node": "^26.4.1",
|
|
84
|
+
"oxfmt": "^0.66.0",
|
|
85
|
+
"oxlint": "^1.81.0",
|
|
85
86
|
"typescript": "^7.0.2"
|
|
86
87
|
}
|
|
87
88
|
}
|
|
@@ -94,6 +94,8 @@ The CLI supports distinct human, hosted-app, and personal-key lifecycles:
|
|
|
94
94
|
linear issue list --workspace acme --json
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
+
Login associates the authenticated workspace with the discovered project config, or creates `.linear.toml` at the Git root (cwd outside Git). Use `--no-project` for credential-only login; JSON reports `projectConfigPath` or null. Credentials stay global. Workspace selection is `--workspace` → `LINEAR_WORKSPACE` → project config → optional global default. A sole stored workspace is automatic; multiple unselected workspaces prompt only interactively and fail in JSON/noninteractive mode. Login/adoption/logout never assign a new default. Existing legacy defaults remain valid because their original intent is unknown; `auth default` is an explicit convenience.
|
|
98
|
+
|
|
97
99
|
An explicit credential flag overrides environment credentials. If both credential kinds occur at
|
|
98
100
|
the same precedence level, the CLI fails rather than silently choosing an actor. Inspect resolution
|
|
99
101
|
with `linear auth status`. `linear auth token` exports stored API keys only and never OAuth tokens.
|
|
@@ -215,6 +217,14 @@ perfectly with a heredoc:
|
|
|
215
217
|
- `comment add` / `comment update` / `comment reply` → `--body-file <path>`
|
|
216
218
|
- `document create` / `document update` → `--content-file <path>`
|
|
217
219
|
|
|
220
|
+
Comments canonically use `add`, while resources such as attachments use `create`.
|
|
221
|
+
`create` is also accepted as a compatibility alias for comment `add`. For a file-backed
|
|
222
|
+
issue comment, copy this form:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
linear issue comment add LUMI-9340 --body-file comment.md --json
|
|
226
|
+
```
|
|
227
|
+
|
|
218
228
|
```bash
|
|
219
229
|
linear issue create --title "Onboarding bug" --team TES --no-input --json \
|
|
220
230
|
--description-file - <<'EOF'
|
|
@@ -374,6 +384,7 @@ linear issue comment list
|
|
|
374
384
|
linear issue comment update
|
|
375
385
|
linear issue comments
|
|
376
386
|
linear issue create
|
|
387
|
+
linear issue delegate
|
|
377
388
|
linear issue delete
|
|
378
389
|
linear issue describe
|
|
379
390
|
linear issue id
|
|
@@ -69,7 +69,7 @@ storage: string
|
|
|
69
69
|
|
|
70
70
|
### `linear auth login`
|
|
71
71
|
|
|
72
|
-
Authenticate
|
|
72
|
+
Authenticate and select the workspace for this project
|
|
73
73
|
|
|
74
74
|
```
|
|
75
75
|
linear auth login [options]
|
|
@@ -77,6 +77,7 @@ linear auth login [options]
|
|
|
77
77
|
|
|
78
78
|
| Option | Description |
|
|
79
79
|
| ---------------------- | --------------------------------------------------------------------- |
|
|
80
|
+
| `--no-project` | save credentials without changing the project workspace |
|
|
80
81
|
| `--key <key>` | use a personal API key ('-' reads it from stdin) |
|
|
81
82
|
| `--plaintext` | Store the key in the config file (0600) instead of the system keyring |
|
|
82
83
|
| `--no-browser` | print the authorization URL instead of opening it |
|
|
@@ -97,6 +98,7 @@ storage: string
|
|
|
97
98
|
scopes?: string[]
|
|
98
99
|
expiresAt?: string
|
|
99
100
|
path: string
|
|
101
|
+
projectConfigPath: string | null
|
|
100
102
|
```
|
|
101
103
|
|
|
102
104
|
### `linear auth logout`
|
|
@@ -119,6 +121,7 @@ workspace: string
|
|
|
119
121
|
removed: boolean
|
|
120
122
|
revocation: string
|
|
121
123
|
fallbackCredentialType: string | null
|
|
124
|
+
teamMetadataRemoved: boolean
|
|
122
125
|
```
|
|
123
126
|
|
|
124
127
|
### `linear auth migrate`
|
|
@@ -22,10 +22,11 @@ apiKeySource: string
|
|
|
22
22
|
accessToken: string
|
|
23
23
|
accessTokenSource: string
|
|
24
24
|
credentialWorkspace: string | null
|
|
25
|
+
workspaceProfile: string | null
|
|
25
26
|
team: string | null
|
|
26
27
|
workspace: string | null
|
|
27
28
|
sort: string
|
|
28
|
-
origins: {team: {source: string, path?: string, key?: string}, workspace: {source: string, path?: string, key?: string}, sort: {source: string, path?: string, key?: string}}
|
|
29
|
+
origins: {team: {source: string, path?: string, key?: string, workspace?: string}, workspace: {source: string, path?: string, key?: string, workspace?: string}, sort: {source: string, path?: string, key?: string, workspace?: string}}
|
|
29
30
|
userConfigPath: string
|
|
30
31
|
projectConfigPath: string | null
|
|
31
32
|
globalConfigPath: string | null
|
|
@@ -57,16 +58,16 @@ sort?: string
|
|
|
57
58
|
|
|
58
59
|
### `linear config set`
|
|
59
60
|
|
|
60
|
-
Set one project setting (team, workspace, sort)
|
|
61
|
+
Set one project or user setting (team, workspace, sort)
|
|
61
62
|
|
|
62
63
|
```
|
|
63
64
|
linear config set [options] <key> <value>
|
|
64
65
|
```
|
|
65
66
|
|
|
66
|
-
| Option | Description
|
|
67
|
-
| --------------- |
|
|
68
|
-
| `--user` | write the user config (
|
|
69
|
-
| `--path <file>` | write this file instead of the project config in effect
|
|
67
|
+
| Option | Description |
|
|
68
|
+
| --------------- | ------------------------------------------------------------------------- |
|
|
69
|
+
| `--user` | write the user config (team + --workspace targets that workspace profile) |
|
|
70
|
+
| `--path <file>` | write this file instead of the project config in effect |
|
|
70
71
|
|
|
71
72
|
**Output (`--json`)**: a receipt object
|
|
72
73
|
|
|
@@ -75,6 +76,7 @@ success: boolean
|
|
|
75
76
|
path: string
|
|
76
77
|
key: string
|
|
77
78
|
value: string
|
|
79
|
+
workspace?: string
|
|
78
80
|
```
|
|
79
81
|
|
|
80
82
|
### `linear config show`
|
|
@@ -93,10 +95,11 @@ apiKeySource: string
|
|
|
93
95
|
accessToken: string
|
|
94
96
|
accessTokenSource: string
|
|
95
97
|
credentialWorkspace: string | null
|
|
98
|
+
workspaceProfile: string | null
|
|
96
99
|
team: string | null
|
|
97
100
|
workspace: string | null
|
|
98
101
|
sort: string
|
|
99
|
-
origins: {team: {source: string, path?: string, key?: string}, workspace: {source: string, path?: string, key?: string}, sort: {source: string, path?: string, key?: string}}
|
|
102
|
+
origins: {team: {source: string, path?: string, key?: string, workspace?: string}, workspace: {source: string, path?: string, key?: string, workspace?: string}, sort: {source: string, path?: string, key?: string, workspace?: string}}
|
|
100
103
|
userConfigPath: string
|
|
101
104
|
projectConfigPath: string | null
|
|
102
105
|
globalConfigPath: string | null
|