linear-sdk-cli 0.1.0 → 0.1.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 +46 -46
- package/COVERAGE.md +5 -5
- package/MIGRATING.md +69 -71
- package/README.md +88 -96
- package/package.json +3 -4
- package/skills/linear-sdk-cli/SKILL.md +3 -13
- package/skills/linear-sdk-cli/references/api.md +9 -9
- package/skills/linear-sdk-cli/references/auth.md +0 -18
- package/skills/linear-sdk-cli/references/commands.md +2 -2
- package/skills/linear-sdk-cli/references/config.md +11 -9
- package/skills/linear-sdk-cli/references/issue.md +12 -63
- package/skills/linear-sdk-cli/references/team.md +0 -16
- package/skills/linear-sdk-cli/scripts/generate-docs.ts +2 -6
- package/src/bin/linear.ts +6 -16
- package/src/cli.ts +1 -3
- package/src/commands/agent-session.ts +5 -13
- package/src/commands/api.ts +9 -15
- package/src/commands/attach.ts +4 -15
- package/src/commands/comment.ts +2 -6
- package/src/commands/completion.ts +6 -2
- package/src/commands/cycle.ts +1 -6
- package/src/commands/discover.ts +2 -6
- package/src/commands/document.ts +1 -4
- package/src/commands/initiative.ts +1 -5
- package/src/commands/issue.ts +88 -376
- package/src/commands/meta.ts +31 -139
- package/src/commands/milestone.ts +1 -4
- package/src/commands/notification.ts +6 -15
- package/src/commands/project.ts +4 -17
- package/src/commands/team.ts +0 -13
- package/src/commands/webhook.ts +2 -12
- package/src/config.ts +183 -500
- package/src/context.ts +4 -13
- package/src/git.ts +3 -28
- package/src/lib/body.ts +2 -26
- package/src/lib/errors.ts +7 -32
- package/src/lib/introspect.ts +7 -76
- package/src/lib/keyring.ts +9 -51
- package/src/lib/options.ts +12 -108
- package/src/lib/output-shapes.ts +9 -81
- package/src/lib/pagination.ts +17 -50
- package/src/lib/resolve.ts +7 -40
- package/src/lib/shape.ts +12 -16
- package/src/lib/upload.ts +1 -3
- package/src/output/format.ts +18 -96
- package/src/output/table.ts +7 -7
- package/src/services/agent-session.ts +3 -11
- package/src/services/attachment.ts +5 -14
- package/src/services/comment.ts +4 -12
- package/src/services/cycle.ts +4 -11
- package/src/services/document.ts +10 -29
- package/src/services/favorite.ts +9 -15
- package/src/services/initiative-update.ts +15 -16
- package/src/services/initiative.ts +10 -15
- package/src/services/issue.ts +18 -262
- package/src/services/label.ts +2 -12
- package/src/services/milestone.ts +5 -8
- package/src/services/notification.ts +2 -16
- package/src/services/organization.ts +18 -23
- package/src/services/project.ts +34 -55
- package/src/services/roadmap.ts +6 -7
- package/src/services/state.ts +3 -9
- package/src/services/team.ts +24 -67
- package/src/services/user.ts +2 -2
- package/src/services/webhook.ts +17 -28
- package/skills/linear-sdk-cli/references/open.md +0 -29
- package/src/commands/open.ts +0 -84
- package/src/lib/open.ts +0 -32
- package/src/output/color.ts +0 -52
- package/src/output/markdown.ts +0 -133
- package/src/output/pager.ts +0 -131
package/CHANGELOG.md
CHANGED
|
@@ -16,7 +16,7 @@ All notable changes to this project are documented here. The format is based on
|
|
|
16
16
|
`completion`), `fields` (key → `"string"`, `"number|null"`, `["string"]`, nested `{…}`,
|
|
17
17
|
`{nullable: {…}}` for a relation that may be null, `"comments?"` for a key present only
|
|
18
18
|
sometimes) and `variants` (the whole shape under `--web`, `--start`, `op=list`, …). New `linear
|
|
19
|
-
commands <path...>` describes one command — a bare object under `--json` (`.output.fields`),
|
|
19
|
+
commands <path...>` describes one command — a bare object under `--json` (`.output.fields`),
|
|
20
20
|
or usage, options and one `key: type` line per field for a human, plus its subcommands for a
|
|
21
21
|
group; an unknown path is `not_found` (exit 3) with the nearest paths. The shapes are declared
|
|
22
22
|
beside the interfaces they describe (`shape<IssueRow>({…})`, `src/lib/shape.ts`), where a field
|
|
@@ -61,9 +61,9 @@ commands <path...>` describes one command — a bare object under `--json` (`.ou
|
|
|
61
61
|
TES-642).** `initiative add-project <initiative> <project>` (`--sort-order`) and
|
|
62
62
|
`remove-project` (confirmed; finds the link on the project's side, so no workspace-wide paging)
|
|
63
63
|
and `initiative unarchive <id>` (resolves among archived initiatives, refuses a live one). `initiative
|
|
64
|
-
list` gains `--status`, `--owner`, `--archived` — the list stays every-status by default, and
|
|
64
|
+
list` gains `--status`, `--owner`, `--archived` — the list stays every-status by default, and
|
|
65
65
|
the reference CLI's `--all-statuses` is accepted as the no-op it is here. `initiative
|
|
66
|
-
create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `archivedAt` and
|
|
66
|
+
create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `archivedAt` and
|
|
67
67
|
the linked `projects` (`{id, name, status}`).
|
|
68
68
|
- **`project list --all-teams`, `team create --private`, health-only status updates (TES-642).**
|
|
69
69
|
`project list` is scoped to the default team and the only way out was the accidental
|
|
@@ -104,7 +104,7 @@ create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `arc
|
|
|
104
104
|
a repository; `--path` to choose), taking `--team` or offering the workspace's teams in a prompt;
|
|
105
105
|
it refuses to replace an existing file without `--force`. `config set <key> <value>` changes one
|
|
106
106
|
of `team`, `workspace`, `sort`, `vcs` in the project config discovery would actually read — a
|
|
107
|
-
schpet-written `.config/linear.toml` included, in
|
|
107
|
+
schpet-written `.config/linear.toml` included, in *its* spelling (`team_id`, `issue_sort`), so no
|
|
108
108
|
second competing key appears — or, with `--user`, in `~/.config/linear/config.toml`. The edit is
|
|
109
109
|
textual, one line replaced or appended before the first table, so comments and layout survive;
|
|
110
110
|
the result is parsed back to prove it, falling back to a full re-serialize only if a layout we
|
|
@@ -125,7 +125,7 @@ create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `arc
|
|
|
125
125
|
in exactly one state, so intersecting them would build a filter that can never match. A single
|
|
126
126
|
`--team`/`--state` sends the exact filter it always did (`key: { eq }` / `type: { eq }`);
|
|
127
127
|
several send `key: { in: [...] }` and an `or` of state clauses. `--state` keeps taking a state
|
|
128
|
-
|
|
128
|
+
*name* or a state *type*, mixed freely: names stay individual `eqIgnoreCase` clauses because
|
|
129
129
|
`in` is exact-case, so `--state 'in progress'` still matches "In Progress".
|
|
130
130
|
`--team` is repeatable **only on the three issue queries**. It is a global option that ~135
|
|
131
131
|
commands use for something other than filtering — the team an issue is created in, the team a
|
|
@@ -139,7 +139,7 @@ create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `arc
|
|
|
139
139
|
ISO 8601. The date is validated locally first: `new Date()` accepts "1", "March 2024" and
|
|
140
140
|
"yesterday", and a garbage bound is not an API error but an empty result set — a filter that
|
|
141
141
|
silently matches nothing looks exactly like a query with no matches.
|
|
142
|
-
- **`--project-label`** — every issue whose
|
|
142
|
+
- **`--project-label`** — every issue whose *project* carries a label, matched case-insensitively
|
|
143
143
|
(`--project-label backend` finds the "Backend" label). Mutually exclusive with `--project`:
|
|
144
144
|
one names a single project, the other a set of them.
|
|
145
145
|
- **`--search-comments`** (`issue search` only) — match comment bodies as well as titles and
|
|
@@ -172,12 +172,12 @@ create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `arc
|
|
|
172
172
|
- **Short flags.** `-j` for the global `--json` (the reference's most-used flag, on 17 of its
|
|
173
173
|
commands) and `-w` for `--web` on `issue view` / `issue pull-request`. Both letters were
|
|
174
174
|
free in our tree, so neither costs an existing meaning — we still hold one meaning per
|
|
175
|
-
letter everywhere, which is exactly why we are
|
|
175
|
+
letter everywhere, which is exactly why we are *not* adopting their `-t`/`-p`/`-a`/`-l`/`-s`
|
|
176
176
|
(see `ALIGNMENT.md`: their own tree spells `-t` as both `--title` and `--team`).
|
|
177
177
|
- **Long-flag spellings.** `--due-date` (`issue create`/`update`), `--target-date` (`project`,
|
|
178
178
|
`milestone`, `initiative` create/update), `--start-date` (`project create`/`update`),
|
|
179
179
|
`--search` (`issue list`/`mine`), and `--all-states` on `issue list`, where it is the
|
|
180
|
-
no-op it describes — `list` already spans every state. Aliases are registered
|
|
180
|
+
no-op it describes — `list` already spans every state. Aliases are registered *hidden*, so
|
|
181
181
|
`--help` and `linear commands --json` keep advertising exactly one spelling per flag; the
|
|
182
182
|
full mapping lives in README's "Coming from linear-cli". Passing both spellings at once is a
|
|
183
183
|
usage error rather than a silent pick: `--due 2026-01-01 --due-date 2026-02-01` is not a typo
|
|
@@ -196,12 +196,12 @@ create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `arc
|
|
|
196
196
|
`issue comment` gained `add`/`list`/`update`/`delete` subcommands mounted on the same
|
|
197
197
|
handlers as the top-level `comment` group, so there is one implementation, not two.
|
|
198
198
|
`issue comment <id> <body>` is unchanged — commander only dispatches to a subcommand when
|
|
199
|
-
the
|
|
199
|
+
the *first* operand is one of those four names, which is the one behavior this touches: a
|
|
200
200
|
lone `linear issue comment add` used to mean "comment the word 'add' on the branch's issue"
|
|
201
201
|
and now reaches the subcommand (and says so loudly). The short aliases `ls`/`edit`/`rm` are
|
|
202
202
|
deliberately not registered under `issue comment`, to keep that collision at four words.
|
|
203
203
|
- **`issue mine` — your unstarted work.** The reference `linear-cli` makes this its
|
|
204
|
-
|
|
204
|
+
*default* listing (`issue list` there is an alias of `mine`), so a script or a habit carried
|
|
205
205
|
over from it silently saw only your own unstarted issues. We keep `list` general — a command
|
|
206
206
|
named "list" should list — and add `mine` next to it with the reference's defaults: the
|
|
207
207
|
viewer's issues, restricted to `unstarted` states, widened by `--all-states`. It takes the same
|
|
@@ -266,9 +266,9 @@ create/update` take `--icon`/`--color`, and `initiative view` shows `icon`, `arc
|
|
|
266
266
|
resolved by name (a team template outranks a shared one of the same name; a miss lists what
|
|
267
267
|
there is), sent as `templateId`; every value you pass alongside overrides what the template
|
|
268
268
|
fills. **The team's default template is now applied**, as Linear's own new-issue form and the
|
|
269
|
-
reference CLI do: the API applies it
|
|
269
|
+
reference CLI do: the API applies it *only when asked* (verified live — a plain `issueCreate` on
|
|
270
270
|
a team with a default template returns an issue with no description), so `useDefaultTemplate:
|
|
271
|
-
true` is sent unless `--no-default-template` (also accepted under the reference's spelling,
|
|
271
|
+
true` is sent unless `--no-default-template` (also accepted under the reference's spelling,
|
|
272
272
|
`--no-use-default-template`); an explicit `--template` replaces it rather than stacking. A
|
|
273
273
|
`--parent` child now **joins its parent's project** unless `--project` says otherwise — the
|
|
274
274
|
sub-issues we created landed outside the project their parent was in — and `--milestone` can
|
|
@@ -282,11 +282,11 @@ true` is sent unless `--no-default-template` (also accepted under the reference'
|
|
|
282
282
|
|
|
283
283
|
- **`--fields`, `--limit` and `--all` are refused on commands that never read them** (TES-637 (2),
|
|
284
284
|
TES-596). All twelve globals are registered on every command so they can sit anywhere on the
|
|
285
|
-
line, but `--fields` projects a
|
|
286
|
-
cap or exhaust a
|
|
287
|
-
id`, `commands`, …) all three used to vanish without a word. That silence cost data: schpet's `-f`
|
|
285
|
+
line, but `--fields` projects a *rendered* result (a table or a detail block) and `--limit`/`--all`
|
|
286
|
+
cap or exhaust a *paged* query; on the ~90 commands that print a receipt (every mutation, `issue
|
|
287
|
+
id`, `commands`, …) all three used to vanish without a word. That silence cost data: schpet's `-f`
|
|
288
288
|
is `--description-file` on `project create`, so `linear project create --name X -f desc.md`
|
|
289
|
-
parsed `-f` as
|
|
289
|
+
parsed `-f` as *our* `--fields`, created the project with **no description**, and exited 0. Now a
|
|
290
290
|
root `preAction` hook (`assertGlobalsApply` in `lib/options.ts`, driven by two applicability
|
|
291
291
|
tables keyed by command path — `FIELDS_COMMANDS`, `LIMIT_COMMANDS`) rejects them before the
|
|
292
292
|
action runs, with a usage error that names the command and, where the command has one, the flag
|
|
@@ -295,7 +295,7 @@ id`, `commands`, …) all three used to vanish without a word. That silence cost
|
|
|
295
295
|
`project create -n 5` gets "-n is --limit here; the name is --name". Every renderer and paged
|
|
296
296
|
query is unchanged; the tables are pinned against the real program tree so a rename shows up
|
|
297
297
|
in the tests, and a renderer missing from the table fails loudly (its `--fields` is refused),
|
|
298
|
-
never quietly. `--team` is deliberately
|
|
298
|
+
never quietly. `--team` is deliberately *not* guarded this way: `alias lin='linear -t TES'` is a
|
|
299
299
|
real usage pattern and must keep working on workspace-scoped commands. Not adopted: a local
|
|
300
300
|
`-f = --description-file` on the four commands — one letter, two meanings (see MIGRATING.md §5).
|
|
301
301
|
- **`project create -t/--team` collects, as `--teams` does** (TES-637 (3)). The global `--team` is
|
|
@@ -303,7 +303,7 @@ id`, `commands`, …) all three used to vanish without a word. That silence cost
|
|
|
303
303
|
`--team A --team B` created the project in **B alone**. `--team` is now declared locally there,
|
|
304
304
|
repeatable and comma-separated, one list with `--teams`; both flags at once is a usage error
|
|
305
305
|
("Pass either --teams or --team, not both"). `project update --team` stays refused (that list
|
|
306
|
-
|
|
306
|
+
*replaces* the project's teams; the message points at `--teams`).
|
|
307
307
|
- **`user list --all` / `team members --all` say what `--all` did not do** (TES-637 (1)). schpet's
|
|
308
308
|
`--all` there means "include inactive members"; ours is the global "exhaust pagination", and
|
|
309
309
|
deactivated users are `--include-disabled`. Same line, both exit 0, different rows. `--all` keeps
|
|
@@ -321,17 +321,17 @@ id`, `commands`, …) all three used to vanish without a word. That silence cost
|
|
|
321
321
|
the PR `ID Title` and stops copying the issue description into GitHub** (TES-637 (5)).
|
|
322
322
|
`describe` used to print `Title`, a blank line, `Fixes ID`; it now prints `ID Title`, a blank
|
|
323
323
|
line, `Linear-issue: Fixes ID`, `Linear-issue-url: <url>` — git trailers that `git
|
|
324
|
-
interpret-trailers` / `git log --format=%(trailers:key=Linear-issue)` and jj's `trailers`
|
|
324
|
+
interpret-trailers` / `git log --format=%(trailers:key=Linear-issue)` and jj's `trailers`
|
|
325
325
|
template read back, while Linear still sees the magic word directly before the id
|
|
326
326
|
(linear.app/docs/github lists `fixes` and `references` among the recognized words) and links
|
|
327
327
|
and closes the issue. `-r/--references` swaps the word. `--json` gains `url` and `message` (the
|
|
328
328
|
full text as printed). `pull-request`: title `<ID> <title>` (a custom `--title` is prefixed the
|
|
329
329
|
same way, as there); body the same two trailers — schpet sends the bare URL and relies on the
|
|
330
330
|
branch name for the link, the `Fixes` line keeps the link (and auto-close) when the branch was
|
|
331
|
-
renamed. The issue
|
|
331
|
+
renamed. The issue *description* is no longer pasted into the PR body: a GitHub PR is a wider
|
|
332
332
|
audience than a Linear issue, and Linear links from the trailer, not the prose.
|
|
333
333
|
- **A team-scoped listing with no team says it is listing every team's** (TES-637 (8)). `issue
|
|
334
|
-
list`/`mine`/`search` and `project list` with no `--team`, no configured team and no
|
|
334
|
+
list`/`mine`/`search` and `project list` with no `--team`, no configured team and no
|
|
335
335
|
`--all-teams` list the whole workspace — by design; schpet errors there ("No default team…"), so
|
|
336
336
|
someone arriving from it could read the workspace as the team. One `info` line on stderr ("No
|
|
337
337
|
default team configured; listing every team's. Pass --team <KEY> …"), silenced by `--quiet`,
|
|
@@ -378,7 +378,7 @@ list`/`mine`/`search` and `project list` with no `--team`, no configured team an
|
|
|
378
378
|
model has no `team`/`cycle` getter (both `[Internal]` in the schema, though they work with a
|
|
379
379
|
plain API key), and the previous view spent three lazy fetches on what one query selects.
|
|
380
380
|
- **Repeating `--label` now narrows instead of broadening.** `--label bug --label regression`
|
|
381
|
-
used to return issues carrying
|
|
381
|
+
used to return issues carrying *either* label; it now returns only those carrying *both*.
|
|
382
382
|
Every other repeatable filter in this CLI narrows, the reference CLI narrows, and the broadening
|
|
383
383
|
reading was the surprising one — a ported script got a superset of what it asked for and no
|
|
384
384
|
error to notice. A single `--label` is unchanged, and both forms stay case-insensitive.
|
|
@@ -389,7 +389,7 @@ list`/`mine`/`search` and `project list` with no `--team`, no configured team an
|
|
|
389
389
|
workflow state **ascending** (active work above the backlog), then priority descending
|
|
390
390
|
(no-priority last), then manual ascending. Membership is unchanged; only the ordering moved.
|
|
391
391
|
`--sort updated` and `--sort created` (which the reference lacks) are untouched.
|
|
392
|
-
The reference CLI hardcodes state
|
|
392
|
+
The reference CLI hardcodes state *descending* for this flag, which the API answers with Backlog
|
|
393
393
|
**before** In Progress — a Low-priority backlog item outranks an Urgent in-progress one there.
|
|
394
394
|
We match the intent (state-grouped, UI-like) rather than that payload; verified against the API.
|
|
395
395
|
- **`issue search` takes filters, and is now team-scoped by default.** Linear's `searchIssues`
|
|
@@ -434,7 +434,7 @@ list`/`mine`/`search` and `project list` with no `--team`, no configured team an
|
|
|
434
434
|
|
|
435
435
|
- **`issue list`/`mine`/`search` rows omitted `milestone` and `cycle`** (TES-652). The list
|
|
436
436
|
query selected project and labels but neither relation, so `issue list --project X --json | jq
|
|
437
|
-
'group_by(.milestone.name)'` put every issue in the null bucket while `issue view` showed the
|
|
437
|
+
'group_by(.milestone.name)'` put every issue in the null bucket while `issue view` showed the
|
|
438
438
|
milestone. Rows now carry `milestone: {id, name} | null` and `cycle: {id, number, name} | null`
|
|
439
439
|
— the detail's exact object shapes, from the one existing query (two selections, no extra
|
|
440
440
|
request; `LIST_QUERY` and `SEARCH_QUERY` share the relation block so they cannot drift again).
|
|
@@ -455,7 +455,7 @@ list`/`mine`/`search` and `project list` with no `--team`, no configured team an
|
|
|
455
455
|
Non-interactively, `comment update <id>` with no body is a usage error, not an editor.
|
|
456
456
|
- **`issue comment "<body>"` on a matching branch failed** (TES-619) — the README's headline
|
|
457
457
|
example. `issue comment [id] [body]` read a lone operand as the id, so `linear issue comment
|
|
458
|
-
'shipped'` on `tes-615-…` produced "No comment body provided" (and, in a terminal, opened
|
|
458
|
+
'shipped'` on `tes-615-…` produced "No comment body provided" (and, in a terminal, opened
|
|
459
459
|
`$EDITOR` for the body first and only then rejected `'shipped'` as an id — work lost).
|
|
460
460
|
`assign` and `state` already disambiguated a lone operand; `comment` now does the same: one
|
|
461
461
|
operand that looks like an issue id is the id (body from `--body-file` or the editor), anything
|
|
@@ -485,7 +485,7 @@ list`/`mine`/`search` and `project list` with no `--team`, no configured team an
|
|
|
485
485
|
`--help`; a bare group shows its help** (TES-633). The root has an action (bare `linear` shows the
|
|
486
486
|
branch's issue) and `issue` has a default subcommand (`view`), so commander's own "unknown
|
|
487
487
|
command" never fired for either: `linear issues list` was `too many arguments. Expected 0
|
|
488
|
-
arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a valid issue id`.
|
|
488
|
+
arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a valid issue id`.
|
|
489
489
|
Now: `Unknown command 'issues'. Did you mean 'issue'? Run 'linear --help' to see the commands.`
|
|
490
490
|
and `'lst' is not a valid issue id (…). Did you mean 'linear issue list'?` — a prefix (`proj`),
|
|
491
491
|
a small typo (`lable`, `isue`) or an alias (`docs`, `notif`) is close enough; two-letter aliases
|
|
@@ -503,7 +503,7 @@ arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a v
|
|
|
503
503
|
window-title rewrite into a title, and every teammate's `linear issue list` ran it — verified live
|
|
504
504
|
on a `clitest-esc` issue whose title carried `\e[31m` and an OSC-8 link. Every string that
|
|
505
505
|
reaches a person now passes through one function (`sanitizeForTerminal`, `src/output/sanitize.ts`)
|
|
506
|
-
that strips
|
|
506
|
+
that strips *whole* sequences — CSI, OSC (BEL- or ST-terminated), DCS/SOS/PM/APC, other `ESC`
|
|
507
507
|
sequences, and their 8-bit C1 spellings — so `\e[31m` vanishes rather than leaving `[31m`
|
|
508
508
|
behind, then whatever C0/C1/DEL and bidi-override characters remain (`\n` and `\t` stay: a
|
|
509
509
|
description is multi-line). It is applied in `cell()` (every table cell and detail value) and in
|
|
@@ -520,7 +520,7 @@ arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a v
|
|
|
520
520
|
resolves the name inside that project (names are unique per project only, the same rule
|
|
521
521
|
`issue update --milestone` applies); a name without `--project` is a usage error that says so:
|
|
522
522
|
`'…' is not a milestone id; pass --project <name|id> to look a milestone up by name.` `state view
|
|
523
|
-
<name-or-type>` resolves against `--team` or the configured default team, exactly as
|
|
523
|
+
<name-or-type>` resolves against `--team` or the configured default team, exactly as
|
|
524
524
|
`issue create --state` does, and says to pass `--team` when there is neither. UUIDs still go
|
|
525
525
|
straight through.
|
|
526
526
|
- **`milestone list`/`view` printed progress ×100 — `3846%`** (TES-648, found while fixing the
|
|
@@ -528,7 +528,7 @@ arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a v
|
|
|
528
528
|
`Project.progress`, which is a fraction; the milestone renderers multiplied it by 100 anyway. The
|
|
529
529
|
human output now reads `38%`; the JSON value stays as the API sends it.
|
|
530
530
|
- **Detail views are one request each, not six to sixteen** (TES-622). Lists have always used a
|
|
531
|
-
tailored GraphQL query, but every
|
|
531
|
+
tailored GraphQL query, but every *detail* path awaited the SDK model's lazy relation getters,
|
|
532
532
|
each its own HTTP round-trip. Measured live with a fetch counter before/after:
|
|
533
533
|
`getIssueDetail(TES-601)` **8 → 1**; `getProjectDetail(linear-sdk-cli)` **7 → 1**;
|
|
534
534
|
`getMilestoneDetail` with 13 issues **16 → 1** (it awaited `issue.state` per issue, so `-n 50`
|
|
@@ -556,12 +556,12 @@ arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a v
|
|
|
556
556
|
and can never land on the JSON stdout a script is parsing.
|
|
557
557
|
- **Mutations could report a success the API never gave** (AUDIT.md #6). Every Linear mutation
|
|
558
558
|
answers with a payload carrying `success: Boolean!`, and almost nothing here read it. The
|
|
559
|
-
create/update paths did have a guard, but it tested whether the
|
|
559
|
+
create/update paths did have a guard, but it tested whether the *entity* came back, not whether
|
|
560
560
|
the write happened — so a `{success: false}` that still carried an entity walked through it — and
|
|
561
561
|
the deletes, archives, subscribes and notification writes discarded the payload unread. Driven
|
|
562
562
|
against a client whose every write is refused, **50 of the 51 mutating service entry points
|
|
563
563
|
resolved happily**; `comment delete` was the single one that checked. The sharpest case was
|
|
564
|
-
`issue update`, which fell back to the issue it had resolved
|
|
564
|
+
`issue update`, which fell back to the issue it had resolved *before* the mutation, so a
|
|
565
565
|
`{success: false, issue: null}` printed `Updated TES-1` and exited **0** — the shell would run the
|
|
566
566
|
`&&` side of a command that changed nothing. All of it now goes through one helper
|
|
567
567
|
(`unwrapMutation` / `assertMutation`, `src/lib/mutation.ts`) that requires `success === true` and,
|
|
@@ -576,27 +576,27 @@ arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a v
|
|
|
576
576
|
to print `✓ Marked … read` and `{"read": true}` whatever the payload said.
|
|
577
577
|
|
|
578
578
|
- **`notification read-all` claimed every notification was marked read.** It hardcoded
|
|
579
|
-
`success: true` and reported the number of
|
|
579
|
+
`success: true` and reported the number of *unread* notifications it found, not the number it
|
|
580
580
|
actually marked, so a batch where some writes were refused looked identical to one where none
|
|
581
581
|
were. It now derives the aggregate: `count` is how many really went through, `attempted` how many
|
|
582
582
|
it tried, and `failed` lists the ones that did not with the API's reason. A single refusal no
|
|
583
583
|
longer aborts the rest — it is reported instead of hidden, and the human output names each one.
|
|
584
584
|
|
|
585
585
|
- **`milestone view` reported `issuesTruncated: false` while hiding issues** (AUDIT.md #5). The
|
|
586
|
-
truncation check read `pageInfo.hasNextPage` off the connection
|
|
586
|
+
truncation check read `pageInfo.hasNextPage` off the connection *after* collecting from it, and
|
|
587
587
|
`fetchNext()` mutates that connection in place — so the flag described the last page fetched
|
|
588
588
|
rather than the issues the limit hid. With 180 issues at `--limit 150` it returned 150 and said
|
|
589
589
|
nothing was hidden, suppressing the `… more (use --all)` notice at exactly the moment it was
|
|
590
590
|
needed. Truncation is now a fact rather than an inference: one extra item is requested (in the
|
|
591
591
|
same page, so it costs no round-trip) and its presence is the answer. The unit test that shipped
|
|
592
|
-
alongside the bug passed because its mock's `fetchNext()` returned a
|
|
592
|
+
alongside the bug passed because its mock's `fetchNext()` returned a *fresh object* instead of
|
|
593
593
|
mutating — every faked connection in the suite now comes from one faithful builder
|
|
594
594
|
(`test/unit/_fakes.ts`) that appends in place and returns `this`, as the SDK does.
|
|
595
595
|
|
|
596
596
|
- **Name resolution stopped at a fixed page, so a large workspace got false `not_found`s.** The
|
|
597
597
|
resolvers that match client-side asked for a fixed `first: 100`/`first: 250` and searched only
|
|
598
598
|
what came back: a team, workflow state, cycle or milestone that existed past the cap could not be
|
|
599
|
-
resolved by name, and — worse, because it is silent — the
|
|
599
|
+
resolved by name, and — worse, because it is silent — the *ambiguity* check only ever saw a prefix,
|
|
600
600
|
so a duplicate name past the cap was invisible and the CLI picked one arbitrarily. Resolution now
|
|
601
601
|
follows the connection. The page size is 250 (Linear's maximum), so the ordinary workspace still
|
|
602
602
|
costs the single request it always did and only workspaces that really have more than 250 of
|
|
@@ -610,7 +610,7 @@ arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a v
|
|
|
610
610
|
`--status` is accepted as an alias for the same thing.
|
|
611
611
|
- **`issue list --label` was case-sensitive.** The filter used an exact-case `in` comparator, so
|
|
612
612
|
`--label bug` returned an empty list when the label is stored as `Bug` — wrong results with no
|
|
613
|
-
error, while label
|
|
613
|
+
error, while label *resolution* everywhere else matches case-insensitively. (Repeating the flag
|
|
614
614
|
now narrows the match — see Changed.)
|
|
615
615
|
- **Deactivated users were invisible.** `team members` and `user list` never sent
|
|
616
616
|
`includeDisabled`, which Linear defaults to `false` — so deactivated users were never returned
|
|
@@ -643,7 +643,7 @@ arguments but got 2: issues, list.` and `linear issue lst` was `'lst' is not a v
|
|
|
643
643
|
issue id inferred from the current branch as usual. `issue describe [id]` prints the issue
|
|
644
644
|
title plus a commit-message trailer using Linear's git magic words (`Fixes <ID>`, or
|
|
645
645
|
`References <ID>` with `-r`/`--references`) — drop it into `git commit -m "$(linear issue
|
|
646
|
-
describe)"`. `issue pull-request [id]` (alias `pr`) creates a GitHub PR via the `gh` CLI:
|
|
646
|
+
describe)"`. `issue pull-request [id]` (alias `pr`) creates a GitHub PR via the `gh` CLI:
|
|
647
647
|
the title defaults to the issue title (`--title` to override) and the body is the issue
|
|
648
648
|
description followed by a `Fixes <ID>` trailer and the Linear URL, so the PR and issue
|
|
649
649
|
reference each other. Flags: `--base`, `--head`, `--draft`, `--web`. The created PR URL is the
|
|
@@ -652,8 +652,8 @@ describe)"`. `issue pull-request [id]` (alias `pr`) creates a GitHub PR via the
|
|
|
652
652
|
or when `gh` itself fails.
|
|
653
653
|
- **Multi-workspace credentials.** Store API keys for several workspaces and switch between
|
|
654
654
|
them. New `auth` subcommands: `auth list` (configured workspaces + default), `auth default
|
|
655
|
-
<slug>` (set the default), and `auth token` (print the resolved key for scripting). `auth
|
|
656
|
-
login` now accepts the global `--workspace <slug>` (derived from the key's organization when
|
|
655
|
+
<slug>` (set the default), and `auth token` (print the resolved key for scripting). `auth
|
|
656
|
+
login` now accepts the global `--workspace <slug>` (derived from the key's organization when
|
|
657
657
|
omitted) and `auth status` reports the active credential workspace. A new global
|
|
658
658
|
`--workspace <slug>` selects which stored credential to use for any command. Credentials live
|
|
659
659
|
in quoted `[workspaces."<slug>"]` tables under a top-level `default_workspace`. Credential
|
|
@@ -666,7 +666,7 @@ login` now accepts the global `--workspace <slug>` (derived from the key's organ
|
|
|
666
666
|
was fatal. First, the key: commander stores a negation under the name with `no-` stripped, so the
|
|
667
667
|
parser wrote `input: false` while `Context` read `noInput` — a field nothing ever set, so the
|
|
668
668
|
guard was reading `undefined` forever. Second, and the reason fixing the key would not have been
|
|
669
|
-
enough: a lone negation is
|
|
669
|
+
enough: a lone negation is *also* seeded with a default of `true` on every command it is
|
|
670
670
|
registered on, including the root program, and `optsWithGlobals()` lets ancestors overwrite
|
|
671
671
|
descendants. Because global options are usable in any position, the flag is parsed by the
|
|
672
672
|
subcommand — so the root's default `true` then overwrote the subcommand's `false` on the way into
|
|
@@ -685,7 +685,7 @@ login` now accepts the global `--workspace <slug>` (derived from the key's organ
|
|
|
685
685
|
Linear rejected the call outright: `Variable "$input" got invalid value false at "input.color"`.
|
|
686
686
|
`project` and `roadmap` escaped the crash only because their guards happened to be truthy-based —
|
|
687
687
|
they dropped the flag instead. Beyond the crash, there was simply no way to set an entity colour
|
|
688
|
-
|
|
688
|
+
*and* turn off terminal colour in one command, because one flag overwrote the other. The global
|
|
689
689
|
is now spelled **`--no-ansi`**, with `--no-color` kept as an alias on every command (it is the
|
|
690
690
|
conventional spelling, and both write the same key, so they cannot drift). Terminal colour and
|
|
691
691
|
entity colour are separate keys now, which makes the whole class of collision structurally
|
|
@@ -697,7 +697,7 @@ login` now accepts the global `--workspace <slug>` (derived from the key's organ
|
|
|
697
697
|
`--key` is unchanged for scripts, and nothing in the command logs or echoes the key — the receipt
|
|
698
698
|
names the user and the file it was written to.
|
|
699
699
|
- **`--json --debug` produced output that could not be parsed.** The debug detail was appended
|
|
700
|
-
|
|
700
|
+
*after* the error envelope as a second, plaintext block, so the one combination a caller reaches
|
|
701
701
|
for when a scripted call misbehaves was the one that broke the contract:
|
|
702
702
|
`linear … --json --debug 2>&1 | jq` died with "Invalid numeric literal". In JSON mode the detail
|
|
703
703
|
now lives inside the envelope as `error.detail`. Without `--debug`, or when the error carries no
|
|
@@ -718,7 +718,7 @@ login` now accepts the global `--workspace <slug>` (derived from the key's organ
|
|
|
718
718
|
a complete integer. Priority additionally validates the 0–4 range locally, matching the wording
|
|
719
719
|
`initiative` has used all along, so the two cannot read as different rules.
|
|
720
720
|
- **`project update --team` was accepted and ignored** (AUDIT.md #8). It is rejected now, rather
|
|
721
|
-
than implemented: a project belongs to
|
|
721
|
+
than implemented: a project belongs to *several* teams, and the existing `--teams` **replaces**
|
|
722
722
|
that whole set — so quietly treating `--team TES` as `--teams TES` would remove every other team
|
|
723
723
|
from the project, which is a destructive reading of a flag the user most likely meant as "also
|
|
724
724
|
this team". The error names `--teams` and says that it replaces. The flag is declared locally on
|
|
@@ -739,11 +739,11 @@ login` now accepts the global `--workspace <slug>` (derived from the key's organ
|
|
|
739
739
|
from the server, and `--operation` naming an operation the document does not define is caught here
|
|
740
740
|
too.
|
|
741
741
|
- **`api --paginate` never checked what it was re-running** (AUDIT.md #10). The loop re-executes the
|
|
742
|
-
whole document once per cursor, so a
|
|
742
|
+
whole document once per cursor, so a *mutation* whose payload happens to contain a paginatable
|
|
743
743
|
connection would have been executed once per page — creating duplicate entities, silently, in
|
|
744
744
|
proportion to how much data came back. It now refuses anything that is not a query, and refuses it
|
|
745
745
|
**before the first request**, so the check cannot itself cost a side effect. The kind is read from
|
|
746
|
-
the same parse that `--operation` uses, which means it is the
|
|
746
|
+
the same parse that `--operation` uses, which means it is the *selected* operation that is
|
|
747
747
|
checked, not merely the first one in the file.
|
|
748
748
|
- **`schema --json --output <file>` ignored the file.** The JSON branch returned before reaching the
|
|
749
749
|
write, so the introspection result went to stdout and no file was ever created — silently, since
|
|
@@ -757,7 +757,7 @@ login` now accepts the global `--workspace <slug>` (derived from the key's organ
|
|
|
757
757
|
stderr, where it lands in scrollback, CI logs and bug reports. The same channel let a project
|
|
758
758
|
`.linear.toml` — a file that is not ours and may arrive with a checkout — emit raw escape
|
|
759
759
|
sequences through our error output. We now report only the reason and the position (`line 3,
|
|
760
|
-
column 36`), never an excerpt from a credential-bearing file, and strip control characters and
|
|
760
|
+
column 36`), never an excerpt from a credential-bearing file, and strip control characters and
|
|
761
761
|
bidi overrides from what remains. The error stays actionable: it still says exactly where to look.
|
|
762
762
|
- **Credential writes could lose a config or leave a truncated one.** Writing the config in place
|
|
763
763
|
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.
|