linear-sdk-cli 0.1.3 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +96 -58
- package/COVERAGE.md +5 -5
- package/MIGRATING.md +71 -69
- package/README.md +115 -90
- package/package.json +6 -7
- package/skills/README.md +15 -0
- package/skills/linear-sdk-cli/SKILL.md +27 -3
- package/skills/linear-sdk-cli/references/api.md +9 -9
- package/skills/linear-sdk-cli/references/auth.md +18 -0
- package/skills/linear-sdk-cli/references/commands.md +2 -2
- package/skills/linear-sdk-cli/references/comment.md +9 -6
- package/skills/linear-sdk-cli/references/config.md +9 -11
- package/skills/linear-sdk-cli/references/issue.md +72 -18
- package/skills/linear-sdk-cli/references/open.md +29 -0
- package/skills/linear-sdk-cli/references/team.md +16 -0
- package/skills/linear-sdk-cli/scripts/generate-docs.ts +6 -2
- package/skills/linear-sdk-cli-maintenance/SKILL.md +97 -0
- package/src/bin/linear.ts +16 -6
- package/src/cli.ts +3 -1
- package/src/commands/agent-session.ts +13 -5
- package/src/commands/api.ts +15 -9
- package/src/commands/attach.ts +15 -4
- package/src/commands/comment.ts +50 -11
- package/src/commands/completion.ts +2 -6
- package/src/commands/cycle.ts +6 -1
- package/src/commands/discover.ts +6 -2
- package/src/commands/document.ts +4 -1
- package/src/commands/initiative.ts +5 -1
- package/src/commands/issue.ts +392 -91
- package/src/commands/meta.ts +139 -31
- package/src/commands/milestone.ts +4 -1
- package/src/commands/notification.ts +15 -6
- package/src/commands/open.ts +84 -0
- package/src/commands/project.ts +17 -4
- package/src/commands/team.ts +13 -0
- package/src/commands/user.ts +2 -2
- package/src/commands/webhook.ts +12 -2
- package/src/config.ts +501 -184
- package/src/context.ts +13 -4
- package/src/git.ts +28 -3
- package/src/lib/body.ts +26 -2
- package/src/lib/errors.ts +33 -8
- package/src/lib/introspect.ts +76 -7
- package/src/lib/keyring.ts +51 -9
- package/src/lib/mentions.ts +49 -0
- package/src/lib/open.ts +32 -0
- package/src/lib/options.ts +108 -12
- package/src/lib/output-shapes.ts +81 -9
- package/src/lib/pagination.ts +50 -17
- package/src/lib/resolve.ts +40 -7
- package/src/lib/shape.ts +16 -12
- package/src/lib/upload.ts +3 -1
- package/src/output/color.ts +52 -0
- package/src/output/format.ts +96 -18
- package/src/output/markdown.ts +133 -0
- package/src/output/pager.ts +131 -0
- package/src/output/sanitize.ts +1 -1
- package/src/output/table.ts +7 -7
- package/src/services/agent-session.ts +11 -3
- package/src/services/attachment.ts +14 -5
- package/src/services/comment.ts +37 -9
- package/src/services/cycle.ts +11 -4
- package/src/services/document.ts +29 -10
- package/src/services/favorite.ts +15 -9
- package/src/services/initiative-update.ts +16 -15
- package/src/services/initiative.ts +15 -10
- package/src/services/issue.ts +263 -19
- package/src/services/label.ts +12 -2
- package/src/services/milestone.ts +8 -5
- package/src/services/notification.ts +16 -2
- package/src/services/organization.ts +23 -18
- package/src/services/project.ts +55 -34
- package/src/services/roadmap.ts +7 -6
- package/src/services/state.ts +9 -3
- package/src/services/team.ts +67 -24
- package/src/services/user.ts +2 -2
- package/src/services/webhook.ts +28 -17
package/CHANGELOG.md
CHANGED
|
@@ -4,17 +4,50 @@ All notable changes to this project are documented here. The format is based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
-
## [0.
|
|
7
|
+
## [0.2.3](https://github.com/zhendalf/linear-sdk-cli/compare/v0.2.2...v0.2.3) (2026-08-24)
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **ci:** guard absent release PR output ([#21](https://github.com/zhendalf/linear-sdk-cli/issues/21)) ([ed082bf](https://github.com/zhendalf/linear-sdk-cli/commit/ed082bfc6673736a9705198f8e2a17c9d7b4698d))
|
|
12
|
+
|
|
13
|
+
## [0.2.2](https://github.com/zhendalf/linear-sdk-cli/compare/v0.2.1...v0.2.2) (2026-08-24)
|
|
8
14
|
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
- **ci:** format and verify release PR heads ([#19](https://github.com/zhendalf/linear-sdk-cli/issues/19)) ([a16244d](https://github.com/zhendalf/linear-sdk-cli/commit/a16244d3355a86d3bdaf290d9ee077b06598125e))
|
|
18
|
+
|
|
19
|
+
## [0.2.1](https://github.com/zhendalf/linear-sdk-cli/compare/v0.2.0...v0.2.1) (2026-08-24)
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
- format generated release notes ([3bab1dd](https://github.com/zhendalf/linear-sdk-cli/commit/3bab1dda627cd83d1feb8a2e1c0a7da37bee10db))
|
|
24
|
+
|
|
25
|
+
## [0.2.0](https://github.com/zhendalf/linear-sdk-cli/compare/v0.1.3...v0.2.0) (2026-08-24)
|
|
26
|
+
|
|
27
|
+
### Features
|
|
28
|
+
|
|
29
|
+
- expand CLI workflows and terminal UX ([0fe31cc](https://github.com/zhendalf/linear-sdk-cli/commit/0fe31cca7e71d0fa6eee2b7c1461ecaf0a01103f))
|
|
30
|
+
|
|
31
|
+
### Bug Fixes
|
|
32
|
+
|
|
33
|
+
- **ci:** give release dispatch repository context ([#15](https://github.com/zhendalf/linear-sdk-cli/issues/15)) ([e3f51e6](https://github.com/zhendalf/linear-sdk-cli/commit/e3f51e600102416f3bd3212578b364a13a452e49))
|
|
34
|
+
|
|
35
|
+
## [0.1.3](https://github.com/zhendalf/linear-sdk-cli/compare/v0.1.2...v0.1.3) (2026-08-24)
|
|
9
36
|
|
|
10
37
|
### Bug Fixes
|
|
11
38
|
|
|
12
|
-
|
|
39
|
+
- **ci:** grant OIDC to automated publisher ([#7](https://github.com/zhendalf/linear-sdk-cli/issues/7)) ([acc6e4b](https://github.com/zhendalf/linear-sdk-cli/commit/acc6e4b9c4ab485ce39eeec7eb2c835892016e59))
|
|
13
40
|
|
|
14
41
|
## [Unreleased]
|
|
15
42
|
|
|
16
43
|
### Added
|
|
17
44
|
|
|
45
|
+
- **Explicit, notification-capable comment mentions (`--mention`, TES-738).** Ordinary `@name`
|
|
46
|
+
text remains literal Markdown. Comment-writing commands now accept repeatable
|
|
47
|
+
`--mention <name|email|me|id>`, resolve each user exactly, deduplicate repeats, and prepend real
|
|
48
|
+
Linear mention tokens as their own paragraph. The option is available on `issue comment`,
|
|
49
|
+
`comment add`, `comment reply`, and `comment update`, including the shared
|
|
50
|
+
`issue comment add/update` mounts.
|
|
18
51
|
- **Every command declares its `--json` output, and `linear commands <path>` prints it (TES-610).**
|
|
19
52
|
An agent driving the CLI cold guessed key names and filed false bugs (`.user` read as `.author`,
|
|
20
53
|
`.parent` as `.parentId`); the field names were knowable only from the source. Each node of
|
|
@@ -23,7 +56,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
23
56
|
`completion`), `fields` (key → `"string"`, `"number|null"`, `["string"]`, nested `{…}`,
|
|
24
57
|
`{nullable: {…}}` for a relation that may be null, `"comments?"` for a key present only
|
|
25
58
|
sometimes) and `variants` (the whole shape under `--web`, `--start`, `op=list`, …). New `linear
|
|
26
|
-
|
|
59
|
+
commands <path...>` describes one command — a bare object under `--json` (`.output.fields`),
|
|
27
60
|
or usage, options and one `key: type` line per field for a human, plus its subcommands for a
|
|
28
61
|
group; an unknown path is `not_found` (exit 3) with the nearest paths. The shapes are declared
|
|
29
62
|
beside the interfaces they describe (`shape<IssueRow>({…})`, `src/lib/shape.ts`), where a field
|
|
@@ -68,9 +101,9 @@ All notable changes to this project are documented here. The format is based on
|
|
|
68
101
|
TES-642).** `initiative add-project <initiative> <project>` (`--sort-order`) and
|
|
69
102
|
`remove-project` (confirmed; finds the link on the project's side, so no workspace-wide paging)
|
|
70
103
|
and `initiative unarchive <id>` (resolves among archived initiatives, refuses a live one). `initiative
|
|
71
|
-
|
|
104
|
+
list` gains `--status`, `--owner`, `--archived` — the list stays every-status by default, and
|
|
72
105
|
the reference CLI's `--all-statuses` is accepted as the no-op it is here. `initiative
|
|
73
|
-
|
|
106
|
+
create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `archivedAt` and
|
|
74
107
|
the linked `projects` (`{id, name, status}`).
|
|
75
108
|
- **`project list --all-teams`, `team create --private`, health-only status updates (TES-642).**
|
|
76
109
|
`project list` is scoped to the default team and the only way out was the accidental
|
|
@@ -111,7 +144,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
111
144
|
a repository; `--path` to choose), taking `--team` or offering the workspace's teams in a prompt;
|
|
112
145
|
it refuses to replace an existing file without `--force`. `config set <key> <value>` changes one
|
|
113
146
|
of `team`, `workspace`, `sort`, `vcs` in the project config discovery would actually read — a
|
|
114
|
-
schpet-written `.config/linear.toml` included, in
|
|
147
|
+
schpet-written `.config/linear.toml` included, in _its_ spelling (`team_id`, `issue_sort`), so no
|
|
115
148
|
second competing key appears — or, with `--user`, in `~/.config/linear/config.toml`. The edit is
|
|
116
149
|
textual, one line replaced or appended before the first table, so comments and layout survive;
|
|
117
150
|
the result is parsed back to prove it, falling back to a full re-serialize only if a layout we
|
|
@@ -132,7 +165,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
132
165
|
in exactly one state, so intersecting them would build a filter that can never match. A single
|
|
133
166
|
`--team`/`--state` sends the exact filter it always did (`key: { eq }` / `type: { eq }`);
|
|
134
167
|
several send `key: { in: [...] }` and an `or` of state clauses. `--state` keeps taking a state
|
|
135
|
-
|
|
168
|
+
_name_ or a state _type_, mixed freely: names stay individual `eqIgnoreCase` clauses because
|
|
136
169
|
`in` is exact-case, so `--state 'in progress'` still matches "In Progress".
|
|
137
170
|
`--team` is repeatable **only on the three issue queries**. It is a global option that ~135
|
|
138
171
|
commands use for something other than filtering — the team an issue is created in, the team a
|
|
@@ -146,7 +179,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
146
179
|
ISO 8601. The date is validated locally first: `new Date()` accepts "1", "March 2024" and
|
|
147
180
|
"yesterday", and a garbage bound is not an API error but an empty result set — a filter that
|
|
148
181
|
silently matches nothing looks exactly like a query with no matches.
|
|
149
|
-
- **`--project-label`** — every issue whose
|
|
182
|
+
- **`--project-label`** — every issue whose _project_ carries a label, matched case-insensitively
|
|
150
183
|
(`--project-label backend` finds the "Backend" label). Mutually exclusive with `--project`:
|
|
151
184
|
one names a single project, the other a set of them.
|
|
152
185
|
- **`--search-comments`** (`issue search` only) — match comment bodies as well as titles and
|
|
@@ -159,7 +192,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
159
192
|
only changes precision: with `--project` the name is resolved to a milestone id inside that
|
|
160
193
|
project, without it the milestone is matched by name across projects. A transplanted command
|
|
161
194
|
always passes `--project` and behaves identically either way.
|
|
162
|
-
- **`issue update --team <key>` now actually moves the issue between teams** (
|
|
195
|
+
- **`issue update --team <key>` now actually moves the issue between teams** (the flag
|
|
163
196
|
was accepted and silently dropped — alone it produced a misleading "Nothing to update", and
|
|
164
197
|
alongside another flag it moved nothing at all). Everything team-scoped in the same command is
|
|
165
198
|
resolved against the **destination** team, so `issue update TES-42 --team ENG --state 'In Review'`
|
|
@@ -179,12 +212,12 @@ All notable changes to this project are documented here. The format is based on
|
|
|
179
212
|
- **Short flags.** `-j` for the global `--json` (the reference's most-used flag, on 17 of its
|
|
180
213
|
commands) and `-w` for `--web` on `issue view` / `issue pull-request`. Both letters were
|
|
181
214
|
free in our tree, so neither costs an existing meaning — we still hold one meaning per
|
|
182
|
-
letter everywhere, which is exactly why we are
|
|
183
|
-
(
|
|
215
|
+
letter everywhere, which is exactly why we are _not_ adopting their `-t`/`-p`/`-a`/`-l`/`-s`
|
|
216
|
+
(their own tree spells `-t` as both `--title` and `--team`).
|
|
184
217
|
- **Long-flag spellings.** `--due-date` (`issue create`/`update`), `--target-date` (`project`,
|
|
185
218
|
`milestone`, `initiative` create/update), `--start-date` (`project create`/`update`),
|
|
186
219
|
`--search` (`issue list`/`mine`), and `--all-states` on `issue list`, where it is the
|
|
187
|
-
no-op it describes — `list` already spans every state. Aliases are registered
|
|
220
|
+
no-op it describes — `list` already spans every state. Aliases are registered _hidden_, so
|
|
188
221
|
`--help` and `linear commands --json` keep advertising exactly one spelling per flag; the
|
|
189
222
|
full mapping lives in README's "Coming from linear-cli". Passing both spellings at once is a
|
|
190
223
|
usage error rather than a silent pick: `--due 2026-01-01 --due-date 2026-02-01` is not a typo
|
|
@@ -203,12 +236,12 @@ All notable changes to this project are documented here. The format is based on
|
|
|
203
236
|
`issue comment` gained `add`/`list`/`update`/`delete` subcommands mounted on the same
|
|
204
237
|
handlers as the top-level `comment` group, so there is one implementation, not two.
|
|
205
238
|
`issue comment <id> <body>` is unchanged — commander only dispatches to a subcommand when
|
|
206
|
-
the
|
|
239
|
+
the _first_ operand is one of those four names, which is the one behavior this touches: a
|
|
207
240
|
lone `linear issue comment add` used to mean "comment the word 'add' on the branch's issue"
|
|
208
241
|
and now reaches the subcommand (and says so loudly). The short aliases `ls`/`edit`/`rm` are
|
|
209
242
|
deliberately not registered under `issue comment`, to keep that collision at four words.
|
|
210
243
|
- **`issue mine` — your unstarted work.** The reference `linear-cli` makes this its
|
|
211
|
-
|
|
244
|
+
_default_ listing (`issue list` there is an alias of `mine`), so a script or a habit carried
|
|
212
245
|
over from it silently saw only your own unstarted issues. We keep `list` general — a command
|
|
213
246
|
named "list" should list — and add `mine` next to it with the reference's defaults: the
|
|
214
247
|
viewer's issues, restricted to `unstarted` states, widened by `--all-states`. It takes the same
|
|
@@ -273,9 +306,9 @@ All notable changes to this project are documented here. The format is based on
|
|
|
273
306
|
resolved by name (a team template outranks a shared one of the same name; a miss lists what
|
|
274
307
|
there is), sent as `templateId`; every value you pass alongside overrides what the template
|
|
275
308
|
fills. **The team's default template is now applied**, as Linear's own new-issue form and the
|
|
276
|
-
reference CLI do: the API applies it
|
|
309
|
+
reference CLI do: the API applies it _only when asked_ (verified live — a plain `issueCreate` on
|
|
277
310
|
a team with a default template returns an issue with no description), so `useDefaultTemplate:
|
|
278
|
-
|
|
311
|
+
true` is sent unless `--no-default-template` (also accepted under the reference's spelling,
|
|
279
312
|
`--no-use-default-template`); an explicit `--template` replaces it rather than stacking. A
|
|
280
313
|
`--parent` child now **joins its parent's project** unless `--project` says otherwise — the
|
|
281
314
|
sub-issues we created landed outside the project their parent was in — and `--milestone` can
|
|
@@ -287,13 +320,18 @@ All notable changes to this project are documented here. The format is based on
|
|
|
287
320
|
|
|
288
321
|
### Changed
|
|
289
322
|
|
|
323
|
+
- **Development linting now uses Oxlint instead of ESLint.** The migrated configuration preserves
|
|
324
|
+
the existing recommended JavaScript and TypeScript checks and underscore-prefixed unused-variable
|
|
325
|
+
exceptions while reducing the lint dependency tree and execution time.
|
|
326
|
+
- **TypeScript 7 now powers type-checking.** The native compiler replaces TypeScript 6 without
|
|
327
|
+
source or configuration compatibility changes in this project.
|
|
290
328
|
- **`--fields`, `--limit` and `--all` are refused on commands that never read them** (TES-637 (2),
|
|
291
329
|
TES-596). All twelve globals are registered on every command so they can sit anywhere on the
|
|
292
|
-
line, but `--fields` projects a
|
|
293
|
-
cap or exhaust a
|
|
294
|
-
|
|
330
|
+
line, but `--fields` projects a _rendered_ result (a table or a detail block) and `--limit`/`--all`
|
|
331
|
+
cap or exhaust a _paged_ query; on the ~90 commands that print a receipt (every mutation, `issue
|
|
332
|
+
id`, `commands`, …) all three used to vanish without a word. That silence cost data: schpet's `-f`
|
|
295
333
|
is `--description-file` on `project create`, so `linear project create --name X -f desc.md`
|
|
296
|
-
parsed `-f` as
|
|
334
|
+
parsed `-f` as _our_ `--fields`, created the project with **no description**, and exited 0. Now a
|
|
297
335
|
root `preAction` hook (`assertGlobalsApply` in `lib/options.ts`, driven by two applicability
|
|
298
336
|
tables keyed by command path — `FIELDS_COMMANDS`, `LIMIT_COMMANDS`) rejects them before the
|
|
299
337
|
action runs, with a usage error that names the command and, where the command has one, the flag
|
|
@@ -302,7 +340,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
302
340
|
`project create -n 5` gets "-n is --limit here; the name is --name". Every renderer and paged
|
|
303
341
|
query is unchanged; the tables are pinned against the real program tree so a rename shows up
|
|
304
342
|
in the tests, and a renderer missing from the table fails loudly (its `--fields` is refused),
|
|
305
|
-
never quietly. `--team` is deliberately
|
|
343
|
+
never quietly. `--team` is deliberately _not_ guarded this way: `alias lin='linear -t TES'` is a
|
|
306
344
|
real usage pattern and must keep working on workspace-scoped commands. Not adopted: a local
|
|
307
345
|
`-f = --description-file` on the four commands — one letter, two meanings (see MIGRATING.md §5).
|
|
308
346
|
- **`project create -t/--team` collects, as `--teams` does** (TES-637 (3)). The global `--team` is
|
|
@@ -310,7 +348,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
310
348
|
`--team A --team B` created the project in **B alone**. `--team` is now declared locally there,
|
|
311
349
|
repeatable and comma-separated, one list with `--teams`; both flags at once is a usage error
|
|
312
350
|
("Pass either --teams or --team, not both"). `project update --team` stays refused (that list
|
|
313
|
-
|
|
351
|
+
_replaces_ the project's teams; the message points at `--teams`).
|
|
314
352
|
- **`user list --all` / `team members --all` say what `--all` did not do** (TES-637 (1)). schpet's
|
|
315
353
|
`--all` there means "include inactive members"; ours is the global "exhaust pagination", and
|
|
316
354
|
deactivated users are `--include-disabled`. Same line, both exit 0, different rows. `--all` keeps
|
|
@@ -328,17 +366,17 @@ All notable changes to this project are documented here. The format is based on
|
|
|
328
366
|
the PR `ID Title` and stops copying the issue description into GitHub** (TES-637 (5)).
|
|
329
367
|
`describe` used to print `Title`, a blank line, `Fixes ID`; it now prints `ID Title`, a blank
|
|
330
368
|
line, `Linear-issue: Fixes ID`, `Linear-issue-url: <url>` — git trailers that `git
|
|
331
|
-
|
|
369
|
+
interpret-trailers` / `git log --format=%(trailers:key=Linear-issue)` and jj's `trailers`
|
|
332
370
|
template read back, while Linear still sees the magic word directly before the id
|
|
333
371
|
(linear.app/docs/github lists `fixes` and `references` among the recognized words) and links
|
|
334
372
|
and closes the issue. `-r/--references` swaps the word. `--json` gains `url` and `message` (the
|
|
335
373
|
full text as printed). `pull-request`: title `<ID> <title>` (a custom `--title` is prefixed the
|
|
336
374
|
same way, as there); body the same two trailers — schpet sends the bare URL and relies on the
|
|
337
375
|
branch name for the link, the `Fixes` line keeps the link (and auto-close) when the branch was
|
|
338
|
-
renamed. The issue
|
|
376
|
+
renamed. The issue _description_ is no longer pasted into the PR body: a GitHub PR is a wider
|
|
339
377
|
audience than a Linear issue, and Linear links from the trailer, not the prose.
|
|
340
378
|
- **A team-scoped listing with no team says it is listing every team's** (TES-637 (8)). `issue
|
|
341
|
-
|
|
379
|
+
list`/`mine`/`search` and `project list` with no `--team`, no configured team and no
|
|
342
380
|
`--all-teams` list the whole workspace — by design; schpet errors there ("No default team…"), so
|
|
343
381
|
someone arriving from it could read the workspace as the team. One `info` line on stderr ("No
|
|
344
382
|
default team configured; listing every team's. Pass --team <KEY> …"), silenced by `--quiet`,
|
|
@@ -385,7 +423,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
385
423
|
model has no `team`/`cycle` getter (both `[Internal]` in the schema, though they work with a
|
|
386
424
|
plain API key), and the previous view spent three lazy fetches on what one query selects.
|
|
387
425
|
- **Repeating `--label` now narrows instead of broadening.** `--label bug --label regression`
|
|
388
|
-
used to return issues carrying
|
|
426
|
+
used to return issues carrying _either_ label; it now returns only those carrying _both_.
|
|
389
427
|
Every other repeatable filter in this CLI narrows, the reference CLI narrows, and the broadening
|
|
390
428
|
reading was the surprising one — a ported script got a superset of what it asked for and no
|
|
391
429
|
error to notice. A single `--label` is unchanged, and both forms stay case-insensitive.
|
|
@@ -396,7 +434,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
396
434
|
workflow state **ascending** (active work above the backlog), then priority descending
|
|
397
435
|
(no-priority last), then manual ascending. Membership is unchanged; only the ordering moved.
|
|
398
436
|
`--sort updated` and `--sort created` (which the reference lacks) are untouched.
|
|
399
|
-
The reference CLI hardcodes state
|
|
437
|
+
The reference CLI hardcodes state _descending_ for this flag, which the API answers with Backlog
|
|
400
438
|
**before** In Progress — a Low-priority backlog item outranks an Urgent in-progress one there.
|
|
401
439
|
We match the intent (state-grouped, UI-like) rather than that payload; verified against the API.
|
|
402
440
|
- **`issue search` takes filters, and is now team-scoped by default.** Linear's `searchIssues`
|
|
@@ -441,7 +479,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
441
479
|
|
|
442
480
|
- **`issue list`/`mine`/`search` rows omitted `milestone` and `cycle`** (TES-652). The list
|
|
443
481
|
query selected project and labels but neither relation, so `issue list --project X --json | jq
|
|
444
|
-
|
|
482
|
+
'group_by(.milestone.name)'` put every issue in the null bucket while `issue view` showed the
|
|
445
483
|
milestone. Rows now carry `milestone: {id, name} | null` and `cycle: {id, number, name} | null`
|
|
446
484
|
— the detail's exact object shapes, from the one existing query (two selections, no extra
|
|
447
485
|
request; `LIST_QUERY` and `SEARCH_QUERY` share the relation block so they cannot drift again).
|
|
@@ -462,7 +500,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
462
500
|
Non-interactively, `comment update <id>` with no body is a usage error, not an editor.
|
|
463
501
|
- **`issue comment "<body>"` on a matching branch failed** (TES-619) — the README's headline
|
|
464
502
|
example. `issue comment [id] [body]` read a lone operand as the id, so `linear issue comment
|
|
465
|
-
|
|
503
|
+
'shipped'` on `tes-615-…` produced "No comment body provided" (and, in a terminal, opened
|
|
466
504
|
`$EDITOR` for the body first and only then rejected `'shipped'` as an id — work lost).
|
|
467
505
|
`assign` and `state` already disambiguated a lone operand; `comment` now does the same: one
|
|
468
506
|
operand that looks like an issue id is the id (body from `--body-file` or the editor), anything
|
|
@@ -492,7 +530,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
492
530
|
`--help`; a bare group shows its help** (TES-633). The root has an action (bare `linear` shows the
|
|
493
531
|
branch's issue) and `issue` has a default subcommand (`view`), so commander's own "unknown
|
|
494
532
|
command" never fired for either: `linear issues list` was `too many arguments. Expected 0
|
|
495
|
-
|
|
533
|
+
arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a valid issue id`.
|
|
496
534
|
Now: `Unknown command 'issues'. Did you mean 'issue'? Run 'linear --help' to see the commands.`
|
|
497
535
|
and `'lst' is not a valid issue id (…). Did you mean 'linear issue list'?` — a prefix (`proj`),
|
|
498
536
|
a small typo (`lable`, `isue`) or an alias (`docs`, `notif`) is close enough; two-letter aliases
|
|
@@ -510,7 +548,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
510
548
|
window-title rewrite into a title, and every teammate's `linear issue list` ran it — verified live
|
|
511
549
|
on a `clitest-esc` issue whose title carried `\e[31m` and an OSC-8 link. Every string that
|
|
512
550
|
reaches a person now passes through one function (`sanitizeForTerminal`, `src/output/sanitize.ts`)
|
|
513
|
-
that strips
|
|
551
|
+
that strips _whole_ sequences — CSI, OSC (BEL- or ST-terminated), DCS/SOS/PM/APC, other `ESC`
|
|
514
552
|
sequences, and their 8-bit C1 spellings — so `\e[31m` vanishes rather than leaving `[31m`
|
|
515
553
|
behind, then whatever C0/C1/DEL and bidi-override characters remain (`\n` and `\t` stay: a
|
|
516
554
|
description is multi-line). It is applied in `cell()` (every table cell and detail value) and in
|
|
@@ -527,7 +565,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
527
565
|
resolves the name inside that project (names are unique per project only, the same rule
|
|
528
566
|
`issue update --milestone` applies); a name without `--project` is a usage error that says so:
|
|
529
567
|
`'…' is not a milestone id; pass --project <name|id> to look a milestone up by name.` `state view
|
|
530
|
-
|
|
568
|
+
<name-or-type>` resolves against `--team` or the configured default team, exactly as
|
|
531
569
|
`issue create --state` does, and says to pass `--team` when there is neither. UUIDs still go
|
|
532
570
|
straight through.
|
|
533
571
|
- **`milestone list`/`view` printed progress ×100 — `3846%`** (TES-648, found while fixing the
|
|
@@ -535,7 +573,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
535
573
|
`Project.progress`, which is a fraction; the milestone renderers multiplied it by 100 anyway. The
|
|
536
574
|
human output now reads `38%`; the JSON value stays as the API sends it.
|
|
537
575
|
- **Detail views are one request each, not six to sixteen** (TES-622). Lists have always used a
|
|
538
|
-
tailored GraphQL query, but every
|
|
576
|
+
tailored GraphQL query, but every _detail_ path awaited the SDK model's lazy relation getters,
|
|
539
577
|
each its own HTTP round-trip. Measured live with a fetch counter before/after:
|
|
540
578
|
`getIssueDetail(TES-601)` **8 → 1**; `getProjectDetail(linear-sdk-cli)` **7 → 1**;
|
|
541
579
|
`getMilestoneDetail` with 13 issues **16 → 1** (it awaited `issue.state` per issue, so `-n 50`
|
|
@@ -561,14 +599,14 @@ All notable changes to this project are documented here. The format is based on
|
|
|
561
599
|
- **Rate-limit waits are announced through the Context's `Output`** rather than a bare stderr
|
|
562
600
|
writer, so the "rate limited; retrying in Ns" line honours `--quiet` like every other status line
|
|
563
601
|
and can never land on the JSON stdout a script is parsing.
|
|
564
|
-
- **Mutations could report a success the API never gave
|
|
602
|
+
- **Mutations could report a success the API never gave.** Every Linear mutation
|
|
565
603
|
answers with a payload carrying `success: Boolean!`, and almost nothing here read it. The
|
|
566
|
-
create/update paths did have a guard, but it tested whether the
|
|
604
|
+
create/update paths did have a guard, but it tested whether the _entity_ came back, not whether
|
|
567
605
|
the write happened — so a `{success: false}` that still carried an entity walked through it — and
|
|
568
606
|
the deletes, archives, subscribes and notification writes discarded the payload unread. Driven
|
|
569
607
|
against a client whose every write is refused, **50 of the 51 mutating service entry points
|
|
570
608
|
resolved happily**; `comment delete` was the single one that checked. The sharpest case was
|
|
571
|
-
`issue update`, which fell back to the issue it had resolved
|
|
609
|
+
`issue update`, which fell back to the issue it had resolved _before_ the mutation, so a
|
|
572
610
|
`{success: false, issue: null}` printed `Updated TES-1` and exited **0** — the shell would run the
|
|
573
611
|
`&&` side of a command that changed nothing. All of it now goes through one helper
|
|
574
612
|
(`unwrapMutation` / `assertMutation`, `src/lib/mutation.ts`) that requires `success === true` and,
|
|
@@ -583,27 +621,27 @@ All notable changes to this project are documented here. The format is based on
|
|
|
583
621
|
to print `✓ Marked … read` and `{"read": true}` whatever the payload said.
|
|
584
622
|
|
|
585
623
|
- **`notification read-all` claimed every notification was marked read.** It hardcoded
|
|
586
|
-
`success: true` and reported the number of
|
|
624
|
+
`success: true` and reported the number of _unread_ notifications it found, not the number it
|
|
587
625
|
actually marked, so a batch where some writes were refused looked identical to one where none
|
|
588
626
|
were. It now derives the aggregate: `count` is how many really went through, `attempted` how many
|
|
589
627
|
it tried, and `failed` lists the ones that did not with the API's reason. A single refusal no
|
|
590
628
|
longer aborts the rest — it is reported instead of hidden, and the human output names each one.
|
|
591
629
|
|
|
592
|
-
- **`milestone view` reported `issuesTruncated: false` while hiding issues
|
|
593
|
-
truncation check read `pageInfo.hasNextPage` off the connection
|
|
630
|
+
- **`milestone view` reported `issuesTruncated: false` while hiding issues.** The
|
|
631
|
+
truncation check read `pageInfo.hasNextPage` off the connection _after_ collecting from it, and
|
|
594
632
|
`fetchNext()` mutates that connection in place — so the flag described the last page fetched
|
|
595
633
|
rather than the issues the limit hid. With 180 issues at `--limit 150` it returned 150 and said
|
|
596
634
|
nothing was hidden, suppressing the `… more (use --all)` notice at exactly the moment it was
|
|
597
635
|
needed. Truncation is now a fact rather than an inference: one extra item is requested (in the
|
|
598
636
|
same page, so it costs no round-trip) and its presence is the answer. The unit test that shipped
|
|
599
|
-
alongside the bug passed because its mock's `fetchNext()` returned a
|
|
637
|
+
alongside the bug passed because its mock's `fetchNext()` returned a _fresh object_ instead of
|
|
600
638
|
mutating — every faked connection in the suite now comes from one faithful builder
|
|
601
639
|
(`test/unit/_fakes.ts`) that appends in place and returns `this`, as the SDK does.
|
|
602
640
|
|
|
603
641
|
- **Name resolution stopped at a fixed page, so a large workspace got false `not_found`s.** The
|
|
604
642
|
resolvers that match client-side asked for a fixed `first: 100`/`first: 250` and searched only
|
|
605
643
|
what came back: a team, workflow state, cycle or milestone that existed past the cap could not be
|
|
606
|
-
resolved by name, and — worse, because it is silent — the
|
|
644
|
+
resolved by name, and — worse, because it is silent — the _ambiguity_ check only ever saw a prefix,
|
|
607
645
|
so a duplicate name past the cap was invisible and the CLI picked one arbitrarily. Resolution now
|
|
608
646
|
follows the connection. The page size is 250 (Linear's maximum), so the ordinary workspace still
|
|
609
647
|
costs the single request it always did and only workspaces that really have more than 250 of
|
|
@@ -617,7 +655,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
617
655
|
`--status` is accepted as an alias for the same thing.
|
|
618
656
|
- **`issue list --label` was case-sensitive.** The filter used an exact-case `in` comparator, so
|
|
619
657
|
`--label bug` returned an empty list when the label is stored as `Bug` — wrong results with no
|
|
620
|
-
error, while label
|
|
658
|
+
error, while label _resolution_ everywhere else matches case-insensitively. (Repeating the flag
|
|
621
659
|
now narrows the match — see Changed.)
|
|
622
660
|
- **Deactivated users were invisible.** `team members` and `user list` never sent
|
|
623
661
|
`includeDisabled`, which Linear defaults to `false` — so deactivated users were never returned
|
|
@@ -650,7 +688,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
650
688
|
issue id inferred from the current branch as usual. `issue describe [id]` prints the issue
|
|
651
689
|
title plus a commit-message trailer using Linear's git magic words (`Fixes <ID>`, or
|
|
652
690
|
`References <ID>` with `-r`/`--references`) — drop it into `git commit -m "$(linear issue
|
|
653
|
-
|
|
691
|
+
describe)"`. `issue pull-request [id]` (alias `pr`) creates a GitHub PR via the `gh` CLI:
|
|
654
692
|
the title defaults to the issue title (`--title` to override) and the body is the issue
|
|
655
693
|
description followed by a `Fixes <ID>` trailer and the Linear URL, so the PR and issue
|
|
656
694
|
reference each other. Flags: `--base`, `--head`, `--draft`, `--web`. The created PR URL is the
|
|
@@ -659,8 +697,8 @@ All notable changes to this project are documented here. The format is based on
|
|
|
659
697
|
or when `gh` itself fails.
|
|
660
698
|
- **Multi-workspace credentials.** Store API keys for several workspaces and switch between
|
|
661
699
|
them. New `auth` subcommands: `auth list` (configured workspaces + default), `auth default
|
|
662
|
-
|
|
663
|
-
|
|
700
|
+
<slug>` (set the default), and `auth token` (print the resolved key for scripting). `auth
|
|
701
|
+
login` now accepts the global `--workspace <slug>` (derived from the key's organization when
|
|
664
702
|
omitted) and `auth status` reports the active credential workspace. A new global
|
|
665
703
|
`--workspace <slug>` selects which stored credential to use for any command. Credentials live
|
|
666
704
|
in quoted `[workspaces."<slug>"]` tables under a top-level `default_workspace`. Credential
|
|
@@ -668,12 +706,12 @@ All notable changes to this project are documented here. The format is based on
|
|
|
668
706
|
`--workspace`/`LINEAR_WORKSPACE`/`default_workspace`, and is never steered by project
|
|
669
707
|
`.linear.toml`. When several workspaces are configured with no default, the error is deferred
|
|
670
708
|
until a command actually needs the API — so `auth list`/`default`/`login` still work.
|
|
671
|
-
- **`--no-input` did nothing
|
|
709
|
+
- **`--no-input` did nothing.** It is the flag that makes the CLI safe to run
|
|
672
710
|
unattended, and it had never once had an effect. Two independent faults, either of which alone
|
|
673
711
|
was fatal. First, the key: commander stores a negation under the name with `no-` stripped, so the
|
|
674
712
|
parser wrote `input: false` while `Context` read `noInput` — a field nothing ever set, so the
|
|
675
713
|
guard was reading `undefined` forever. Second, and the reason fixing the key would not have been
|
|
676
|
-
enough: a lone negation is
|
|
714
|
+
enough: a lone negation is _also_ seeded with a default of `true` on every command it is
|
|
677
715
|
registered on, including the root program, and `optsWithGlobals()` lets ancestors overwrite
|
|
678
716
|
descendants. Because global options are usable in any position, the flag is parsed by the
|
|
679
717
|
subcommand — so the root's default `true` then overwrote the subcommand's `false` on the way into
|
|
@@ -686,25 +724,25 @@ All notable changes to this project are documented here. The format is based on
|
|
|
686
724
|
goes for a redirected stdout, which is where inquirer draws the prompt and would therefore write
|
|
687
725
|
the question into the caller's output. Commands that need a missing value now fail with the usual
|
|
688
726
|
usage error naming the flag to pass, instead of waiting forever.
|
|
689
|
-
- **`--no-color` broke `label create
|
|
727
|
+
- **`--no-color` broke `label create`.** The global terminal-colour flag and the
|
|
690
728
|
entity flag `--color <hex>` both reduced to commander's `color` attribute, so
|
|
691
729
|
`label create --name x --team TES --no-color` put `color: false` into the mutation input and
|
|
692
730
|
Linear rejected the call outright: `Variable "$input" got invalid value false at "input.color"`.
|
|
693
731
|
`project` and `roadmap` escaped the crash only because their guards happened to be truthy-based —
|
|
694
732
|
they dropped the flag instead. Beyond the crash, there was simply no way to set an entity colour
|
|
695
|
-
|
|
733
|
+
_and_ turn off terminal colour in one command, because one flag overwrote the other. The global
|
|
696
734
|
is now spelled **`--no-ansi`**, with `--no-color` kept as an alias on every command (it is the
|
|
697
735
|
conventional spelling, and both write the same key, so they cannot drift). Terminal colour and
|
|
698
736
|
entity colour are separate keys now, which makes the whole class of collision structurally
|
|
699
737
|
impossible rather than fixed one command at a time — a test walks the command tree and asserts no
|
|
700
738
|
option anywhere can write `false` into `color`.
|
|
701
|
-
- **`auth login` echoed the API key
|
|
739
|
+
- **`auth login` echoed the API key.** The prompt used inquirer's `input`, so the
|
|
702
740
|
credential appeared on screen as it was typed and then stayed in the terminal's scrollback for as
|
|
703
741
|
long as the window lived. It now uses inquirer's `password` prompt, which never renders the value.
|
|
704
742
|
`--key` is unchanged for scripts, and nothing in the command logs or echoes the key — the receipt
|
|
705
743
|
names the user and the file it was written to.
|
|
706
744
|
- **`--json --debug` produced output that could not be parsed.** The debug detail was appended
|
|
707
|
-
|
|
745
|
+
_after_ the error envelope as a second, plaintext block, so the one combination a caller reaches
|
|
708
746
|
for when a scripted call misbehaves was the one that broke the contract:
|
|
709
747
|
`linear … --json --debug 2>&1 | jq` died with "Invalid numeric literal". In JSON mode the detail
|
|
710
748
|
now lives inside the envelope as `error.detail`. Without `--debug`, or when the error carries no
|
|
@@ -724,15 +762,15 @@ All notable changes to this project are documented here. The format is based on
|
|
|
724
762
|
different request from the one that was typed, executed without comment. A flag value must now be
|
|
725
763
|
a complete integer. Priority additionally validates the 0–4 range locally, matching the wording
|
|
726
764
|
`initiative` has used all along, so the two cannot read as different rules.
|
|
727
|
-
- **`project update --team` was accepted and ignored
|
|
728
|
-
than implemented: a project belongs to
|
|
765
|
+
- **`project update --team` was accepted and ignored.** It is rejected now, rather
|
|
766
|
+
than implemented: a project belongs to _several_ teams, and the existing `--teams` **replaces**
|
|
729
767
|
that whole set — so quietly treating `--team TES` as `--teams TES` would remove every other team
|
|
730
768
|
from the project, which is a destructive reading of a flag the user most likely meant as "also
|
|
731
769
|
this team". The error names `--teams` and says that it replaces. The flag is declared locally on
|
|
732
770
|
the command (which is what stops the global from being injected over it) and hidden, so `--help`
|
|
733
771
|
and `linear commands --json` advertise only the flag that works. `project create --team` is
|
|
734
772
|
untouched — there it is the genuine fallback team.
|
|
735
|
-
- **`api --operation` did nothing at all
|
|
773
|
+
- **`api --operation` did nothing at all.** It was passed as a fourth argument to the
|
|
736
774
|
SDK's `rawRequest`, which takes three — so it was discarded, and a multi-operation document went
|
|
737
775
|
to the API with no operation named. The API's answer to that is not "run the first one" but a
|
|
738
776
|
flat `The operation does not exist on the query.`, which made multi-operation documents unusable
|
|
@@ -745,12 +783,12 @@ All notable changes to this project are documented here. The format is based on
|
|
|
745
783
|
multi-operation document is now a usage error that lists the names, rather than a confusing error
|
|
746
784
|
from the server, and `--operation` naming an operation the document does not define is caught here
|
|
747
785
|
too.
|
|
748
|
-
- **`api --paginate` never checked what it was re-running
|
|
749
|
-
whole document once per cursor, so a
|
|
786
|
+
- **`api --paginate` never checked what it was re-running.** The loop re-executes the
|
|
787
|
+
whole document once per cursor, so a _mutation_ whose payload happens to contain a paginatable
|
|
750
788
|
connection would have been executed once per page — creating duplicate entities, silently, in
|
|
751
789
|
proportion to how much data came back. It now refuses anything that is not a query, and refuses it
|
|
752
790
|
**before the first request**, so the check cannot itself cost a side effect. The kind is read from
|
|
753
|
-
the same parse that `--operation` uses, which means it is the
|
|
791
|
+
the same parse that `--operation` uses, which means it is the _selected_ operation that is
|
|
754
792
|
checked, not merely the first one in the file.
|
|
755
793
|
- **`schema --json --output <file>` ignored the file.** The JSON branch returned before reaching the
|
|
756
794
|
write, so the introspection result went to stdout and no file was ever created — silently, since
|
|
@@ -764,7 +802,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
764
802
|
stderr, where it lands in scrollback, CI logs and bug reports. The same channel let a project
|
|
765
803
|
`.linear.toml` — a file that is not ours and may arrive with a checkout — emit raw escape
|
|
766
804
|
sequences through our error output. We now report only the reason and the position (`line 3,
|
|
767
|
-
|
|
805
|
+
column 36`), never an excerpt from a credential-bearing file, and strip control characters and
|
|
768
806
|
bidi overrides from what remains. The error stays actionable: it still says exactly where to look.
|
|
769
807
|
- **Credential writes could lose a config or leave a truncated one.** Writing the config in place
|
|
770
808
|
truncates it first, so a crash or a concurrently-running `linear` could observe — or leave behind
|
package/COVERAGE.md
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
Generated by `bun run audit:coverage`. Total LinearClient members: **470**.
|
|
4
4
|
|
|
5
|
-
| Status
|
|
6
|
-
|
|
|
7
|
-
| curated
|
|
8
|
-
| raw-only | 140
|
|
9
|
-
| excluded | 228
|
|
5
|
+
| Status | Count | Meaning |
|
|
6
|
+
| -------- | ----- | ----------------------------------------------- |
|
|
7
|
+
| curated | 102 | Has a first-class CLI command |
|
|
8
|
+
| raw-only | 140 | Reachable via `linear api` (no bespoke command) |
|
|
9
|
+
| excluded | 228 | 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.
|