linear-sdk-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.claude-plugin/plugin.json +9 -0
  3. package/CHANGELOG.md +807 -0
  4. package/COVERAGE.md +495 -0
  5. package/LICENSE +21 -0
  6. package/MIGRATING.md +220 -0
  7. package/README.md +524 -0
  8. package/package.json +87 -0
  9. package/skills/linear-sdk-cli/SKILL.md +534 -0
  10. package/skills/linear-sdk-cli/references/api.md +27 -0
  11. package/skills/linear-sdk-cli/references/attachment.md +82 -0
  12. package/skills/linear-sdk-cli/references/auth.md +174 -0
  13. package/skills/linear-sdk-cli/references/commands.md +37 -0
  14. package/skills/linear-sdk-cli/references/comment.md +154 -0
  15. package/skills/linear-sdk-cli/references/completion.md +17 -0
  16. package/skills/linear-sdk-cli/references/config.md +99 -0
  17. package/skills/linear-sdk-cli/references/cycle.md +131 -0
  18. package/skills/linear-sdk-cli/references/document.md +180 -0
  19. package/skills/linear-sdk-cli/references/favorite.md +78 -0
  20. package/skills/linear-sdk-cli/references/initiative-update.md +67 -0
  21. package/skills/linear-sdk-cli/references/initiative.md +256 -0
  22. package/skills/linear-sdk-cli/references/issue.md +928 -0
  23. package/skills/linear-sdk-cli/references/label.md +112 -0
  24. package/skills/linear-sdk-cli/references/milestone.md +139 -0
  25. package/skills/linear-sdk-cli/references/notification.md +123 -0
  26. package/skills/linear-sdk-cli/references/open.md +29 -0
  27. package/skills/linear-sdk-cli/references/organization.md +99 -0
  28. package/skills/linear-sdk-cli/references/project-update.md +67 -0
  29. package/skills/linear-sdk-cli/references/project.md +234 -0
  30. package/skills/linear-sdk-cli/references/roadmap.md +150 -0
  31. package/skills/linear-sdk-cli/references/schema.md +21 -0
  32. package/skills/linear-sdk-cli/references/state.md +61 -0
  33. package/skills/linear-sdk-cli/references/team.md +227 -0
  34. package/skills/linear-sdk-cli/references/user.md +103 -0
  35. package/skills/linear-sdk-cli/references/webhook.md +151 -0
  36. package/skills/linear-sdk-cli/references/whoami.md +26 -0
  37. package/skills/linear-sdk-cli/scripts/generate-docs.ts +339 -0
  38. package/src/bin/linear.ts +97 -0
  39. package/src/cli.ts +232 -0
  40. package/src/client.ts +172 -0
  41. package/src/commands/agent-session.ts +121 -0
  42. package/src/commands/api.ts +317 -0
  43. package/src/commands/attach.ts +90 -0
  44. package/src/commands/attachment.ts +74 -0
  45. package/src/commands/comment.ts +225 -0
  46. package/src/commands/completion.ts +69 -0
  47. package/src/commands/cycle.ts +125 -0
  48. package/src/commands/discover.ts +170 -0
  49. package/src/commands/document.ts +211 -0
  50. package/src/commands/favorite.ts +72 -0
  51. package/src/commands/initiative-update.ts +47 -0
  52. package/src/commands/initiative.ts +261 -0
  53. package/src/commands/issue.ts +1263 -0
  54. package/src/commands/label.ts +119 -0
  55. package/src/commands/meta.ts +534 -0
  56. package/src/commands/milestone.ts +163 -0
  57. package/src/commands/notification.ts +110 -0
  58. package/src/commands/open.ts +84 -0
  59. package/src/commands/organization.ts +79 -0
  60. package/src/commands/project-update.ts +47 -0
  61. package/src/commands/project.ts +339 -0
  62. package/src/commands/roadmap.ts +131 -0
  63. package/src/commands/state.ts +58 -0
  64. package/src/commands/team.ts +243 -0
  65. package/src/commands/user.ts +107 -0
  66. package/src/commands/webhook.ts +156 -0
  67. package/src/config.ts +1236 -0
  68. package/src/context.ts +117 -0
  69. package/src/git.ts +174 -0
  70. package/src/index.ts +10 -0
  71. package/src/lib/action.ts +26 -0
  72. package/src/lib/body.ts +172 -0
  73. package/src/lib/errors.ts +195 -0
  74. package/src/lib/introspect.ts +150 -0
  75. package/src/lib/keyring.ts +270 -0
  76. package/src/lib/mutation.ts +53 -0
  77. package/src/lib/open.ts +32 -0
  78. package/src/lib/options.ts +640 -0
  79. package/src/lib/output-shapes.ts +562 -0
  80. package/src/lib/pagination.ts +145 -0
  81. package/src/lib/prompt.ts +91 -0
  82. package/src/lib/resolve.ts +596 -0
  83. package/src/lib/shape.ts +273 -0
  84. package/src/lib/status-update.ts +91 -0
  85. package/src/lib/upload.ts +329 -0
  86. package/src/output/color.ts +52 -0
  87. package/src/output/format.ts +238 -0
  88. package/src/output/markdown.ts +133 -0
  89. package/src/output/pager.ts +131 -0
  90. package/src/output/sanitize.ts +76 -0
  91. package/src/output/table.ts +198 -0
  92. package/src/services/agent-session.ts +289 -0
  93. package/src/services/attachment.ts +198 -0
  94. package/src/services/comment.ts +262 -0
  95. package/src/services/cycle.ts +224 -0
  96. package/src/services/document.ts +357 -0
  97. package/src/services/favorite.ts +147 -0
  98. package/src/services/initiative-update.ts +59 -0
  99. package/src/services/initiative.ts +449 -0
  100. package/src/services/issue.ts +1153 -0
  101. package/src/services/label.ts +193 -0
  102. package/src/services/milestone.ts +262 -0
  103. package/src/services/notification.ts +190 -0
  104. package/src/services/organization.ts +144 -0
  105. package/src/services/project-update.ts +36 -0
  106. package/src/services/project.ts +494 -0
  107. package/src/services/roadmap.ts +177 -0
  108. package/src/services/state.ts +127 -0
  109. package/src/services/team.ts +386 -0
  110. package/src/services/user.ts +137 -0
  111. package/src/services/webhook.ts +203 -0
package/MIGRATING.md ADDED
@@ -0,0 +1,220 @@
1
+ # Migrating from `schpet/linear-cli`
2
+
3
+ _Verified against **schpet/linear-cli v2.5.0** (`5af8286`, 2026-08-11) — the Deno original, not a
4
+ fork. If you are on the `@zhendalf/linear-cli` Bun port, everything here applies too; that fork
5
+ tracks the original._
6
+
7
+ The short version: **install, run `linear auth status`, keep typing what you typed.** Your
8
+ credentials are found where schpet left them, your `.linear.toml` is read from the same places, and
9
+ schpet's spellings are accepted as aliases. Where the two CLIs genuinely differ, the difference is
10
+ loud — a usage error, never a quietly different result.
11
+
12
+ ## 1. Install (and the `linear` name)
13
+
14
+ Both CLIs install a binary called `linear`. If schpet's is on your `PATH`, ours will shadow it — or
15
+ be shadowed — depending on order; `linear --version` says which you got (`0.x` ours, `2.x` theirs)
16
+ and `which -a linear` lists both. We keep the `linear` name on purpose: it is what every doc,
17
+ script and agent skill types, and there is no second name that both projects would agree on. Two
18
+ things make it safe anyway:
19
+
20
+ - We also install **`lin`**, a shorter alias that cannot collide with anything of theirs. If you
21
+ want zero risk of running the wrong tool, use `lin` for the first week — every command in this
22
+ guide works with `lin` in place of `linear`.
23
+ - To keep theirs reachable during the transition, rename it to `linear-schpet` the way it was
24
+ installed, and let ours have `linear`:
25
+
26
+ ```bash
27
+ deno install -A --reload -f -g -n linear-schpet jsr:@schpet/linear-cli && deno uninstall -g linear # deno
28
+ brew unlink schpet/tap/linear && ln -s "$(brew --prefix)/opt/linear/bin/linear" ~/.local/bin/linear-schpet # homebrew
29
+ mv "$(command -v linear)" "$(dirname "$(command -v linear)")/linear-schpet" # npm/bun -g
30
+ # Until the first npm release: clone and run from source (see README).
31
+ linear --version # ours: 0.x
32
+ ```
33
+
34
+ A project-pinned `@schpet/linear-cli` (`bunx linear` inside that repo) never collides with a
35
+ global install of ours.
36
+
37
+ ## 2. Credentials — nothing to re-enter
38
+
39
+ schpet 2.5 stores your API key in the **system keyring** (macOS Keychain or Linux `secret-tool`;
40
+ service `linear-cli`, account = workspace slug) and keeps the workspace list in
41
+ `~/.config/linear/credentials.toml`. We use the same keyring service and account, and we read that
42
+ list file for slugs and its `default` (never for keys), so:
43
+
44
+ ```bash
45
+ linear auth status # Authenticated: true, Source: keychain, Workspace: <your slug>
46
+ linear auth list # …lists it, Storage: keychain
47
+ ```
48
+
49
+ If it does not — no keyring on this platform, or you used schpet's plaintext mode (`slug =
50
+ "lin_api_…"` inline in `credentials.toml`, which we deliberately do not read) — `linear auth login`
51
+ prompts (masked) and stores the key in the keyring; `--plaintext` keeps it in our `0600`
52
+ `config.toml` instead. `auth migrate` moves plaintext credentials from that file into the keyring,
53
+ same command name as theirs. Because the keyring entry is shared, `auth logout` here removes it for
54
+ schpet too (and drops the slug from its list file, so neither tool reports a workspace whose key is
55
+ gone). The API key is **never** read from a project `.linear.toml`.
56
+
57
+ ## 3. Config — same files, same keys
58
+
59
+ We look in every place schpet does, in its order. Walking up from the working directory, each
60
+ directory is checked for `linear.toml`, then `.linear.toml`, then `.config/linear.toml` (schpet
61
+ checks cwd and the git root; we check every directory between, which agrees with it wherever it
62
+ would find something). Below that, our `~/.config/linear/config.toml`, then schpet's global
63
+ `~/.config/linear/linear.toml` — read for `team_id`, `workspace`, `issue_sort`; **not** for
64
+ `api_key`, which schpet allows there and we never take from any file but our own. `linear config`
65
+ prints each value with the file it came from, so nothing is a mystery:
66
+
67
+ ```
68
+ Team: TES (project: /repo/.config/linear.toml)
69
+ Sort: priority (global: /Users/you/.config/linear/linear.toml)
70
+ ```
71
+
72
+ Two key differences to know: `issue_sort = "manual"` (schpet's board order) has no equivalent here
73
+ and is a usage error naming the file — change it to `priority` or drop the line. The `vcs` setting
74
+ is ignored; this CLI supports Git and does not claim partial jj support. Schpet's
75
+ `issue_create_*`, `download_images`, `hyperlink_format`, `attachment_dir` keys are ignored (no
76
+ such features yet). `linear config init` writes a `.linear.toml`; `linear config set <key>
77
+ <value>` edits one key.
78
+
79
+ ## 4. Commands — same words, or an alias
80
+
81
+ | schpet | here | notes |
82
+ | -------------------------------------------------------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
83
+ | `issue list` | **`issue mine`** | ⚠️ their `list` is an alias of `mine` (you, unstarted). Our `list` is the whole team. See §6. |
84
+ | `issue query` | `issue list` | alias, same command |
85
+ | `issue mine` | `issue mine` | same defaults: yours, unstarted; `--all-states` widens |
86
+ | `issue comment add\|list\|update\|delete` | same, or top-level `comment …` | one implementation |
87
+ | `issue comment <id> "<body>"` | same | |
88
+ | `issue attach <file>` | `issue attach <issue> <file...>` | same posture: private by default, `--public` for raster images only; ours takes several files and has `--json` |
89
+ | `issue link <url>` | `attachment create --url` | |
90
+ | `issue comment add --attach <file>` | same, or `comment add --attach` | repeatable; images render inline |
91
+ | `auth whoami` | `whoami` (also `auth whoami`) | |
92
+ | `auth migrate` | `auth migrate` | |
93
+ | `config` (writes toml) | `config init` / `config set` | |
94
+ | `team states` / `team members` / `user list` | same | `--all` there means "inactive too"; here it is pagination — use `--include-disabled` (see §6) |
95
+ | `issue start` | same | moves the issue to the first `started` state, as there; `--no-move` to only branch |
96
+ | `issue describe` | same | byte-identical output: `ID Title`, then `Linear-issue:` / `Linear-issue-url:` trailers |
97
+ | `issue pull-request` | same | title `ID Title` as there; body is the two `Linear-issue` trailers (theirs: the URL alone) — the issue description is not copied into GitHub |
98
+ | `project create -t A -t B` | same, or `--teams A,B` | collects both, as there (see §6) |
99
+ | `document create\|list\|update --project\|--issue\|--initiative\|--team\|--cycle\|--release` | same | `update` re-points, as there; `--team` is the global flag (with `--cycle` it scopes the lookup) |
100
+ | `initiative add-project` / `remove-project` / `unarchive` | same | |
101
+ | `project delete` / `team delete` | same, confirmation-gated | |
102
+ | `schema` / `api` | same | ours adds `--operation`, `--vars-file`, and refuses to `--paginate` a mutation |
103
+
104
+ Everything with no row is spelled identically.
105
+
106
+ ## 5. Flags — theirs are accepted
107
+
108
+ | schpet | here (canonical) | where |
109
+ | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ---------------------------------------------------------------------- |
110
+ | `-j, --json` | same | every command |
111
+ | `-w, --web` | same | every `view` |
112
+ | `--due-date` | `--due` | issue create/update |
113
+ | `--target-date`, `--start-date` | `--target`, `--start` | project / milestone / initiative |
114
+ | `--search` | `--query` | issue list / mine |
115
+ | `--status` | `--state` | project list |
116
+ | `--limit 0` | `--all` | every list |
117
+ | `--assignee self` | `me` / `@me` | anywhere |
118
+ | `--cycle active` | `current` | anywhere; `now`/`next`/`previous`/`+1` also work |
119
+ | `-U/--unassigned`, `--created-after`, `--updated-after`, `--project-label`, `--milestone`, `--search-comments`, repeatable `--team`/`--state` | same | issue queries |
120
+ | `--add-label`, `--remove-label`, `--unassign`, `--clear-cycle` | same | issue update |
121
+ | `--no-use-default-template` | `--no-default-template` | issue create — the team default is applied unless you say so, as there |
122
+ | `--start`, `--parent` (child joins the parent's project) | same | issue create |
123
+
124
+ Passing both spellings at once (`--due` _and_ `--due-date`) is a usage error, not a coin flip.
125
+
126
+ **Short flags are the one place we did not follow.** schpet 2.5's own tree assigns `-a` four
127
+ meanings (`--app`, `--all`, `--assignee`, `--attach`), `-f` four, `-y` three, `-t` two (`--title`
128
+ and `--team`). There is no consistent target to copy. Ours holds **one meaning per letter across
129
+ all 141 commands**, so `-t` is always `--team`, `-p` always `--project`, `-P` always `--priority`,
130
+ `-f` always `--fields`, `-n` always `--limit`. Every collision fails loudly — `-p 2` says "No
131
+ project matching '2'", not "set priority 2" — and the two that _used_ not to are loud now: `-f`
132
+ and `-n` are refused on any command that prints no table (`project create -f desc.md` says
133
+ "--fields does not apply … use --description-file <path>"; `project create -n 'My Project'` says
134
+ "--limit does not apply … the name is --name"), instead of creating the project without either.
135
+
136
+ ## 6. What would silently differ — and how we made it loud (or the same)
137
+
138
+ These are the spots where the same command line could _succeed on both and mean different things_.
139
+ Every one is now either identical, or says so on stderr, or is a usage error. Know them.
140
+
141
+ 1. **`issue list`** — theirs shows _your unstarted_ issues; ours shows the _team's_ issues.
142
+ We did not alias `list` to `mine`: a command named "list" that hides your colleagues' work and
143
+ your own in-progress work is the sharpest transition hazard there is. Type `issue mine` for the
144
+ old behavior.
145
+ 2. **Repeated `--label`** narrows (AND) in both CLIs now — same as schpet.
146
+ 3. **`--sort priority`** groups by workflow state in both — but we sort state **ascending** so active
147
+ work is on top. schpet hardcodes descending, which the API answers with Backlog _above_ In
148
+ Progress; a Low-priority backlog item outranks an Urgent in-progress one there. We diverge on
149
+ purpose.
150
+ 4. **`user list --all` / `team members --all`** — theirs: include _inactive_ members. Ours: `--all`
151
+ is the global "fetch every page" on all 141 commands, and deactivated users are
152
+ `--include-disabled`. We kept the one meaning; the command prints a warning on stderr (even
153
+ under `--quiet`) that deactivated users are still excluded and names `--include-disabled`, and
154
+ `--help` says so up front. Not adopted: making `--all` mean "and deactivated" on two commands
155
+ would give one global two meanings.
156
+ 5. **`-f <file>` on `project create/update`, `document create/update`** — theirs: `--description-file` /
157
+ `--content-file`. Ours: `-f` is `--fields`, and it used to be _dropped_ on those commands, so
158
+ `project create --name X -f desc.md` created the project with no description and exited 0.
159
+ `--fields`, `--limit` and `--all` are now usage errors on every command that prints no table or
160
+ detail block, and the message names `--description-file` / `--content-file` where they exist.
161
+ Not adopted: a local `-f = --description-file` on four commands would be one letter meaning two
162
+ things (§5), and would still be `--fields` in `--help`.
163
+ 6. **`project create --team A --team B`** — theirs collects both; ours took the last one and created
164
+ the project in B alone. `--team` on `project create` is now the same repeatable list as
165
+ `--teams`; both spellings at once is a usage error. (`project update --team` stays refused,
166
+ pointing at `--teams`, because that list _replaces_ the project's teams.)
167
+ 7. **`issue start`** — theirs always moves the issue to the first `started` state after branching;
168
+ ours only did with `--move`, so the transplanted command left the issue in Backlog without a
169
+ word. Adopted: moving is the default (that is what "start" means, and what an agent that says
170
+ "start" expects), `--no-move` opts out, `--move` is still accepted. Both the checkout and the
171
+ state change are reported.
172
+ 8. **`issue describe`** — theirs prints `ID Title`, a blank line, then `Linear-issue: Fixes ID` and
173
+ `Linear-issue-url: URL`; ours printed `Title` and a bare `Fixes ID`. Piped into `git commit -m`
174
+ that was a different commit. Adopted, byte for byte: proper trailers are what `git
175
+ interpret-trailers` and jj read back, and Linear reads the magic word right before the id
176
+ either way (linear.app/docs/github). `-r/--references` still swaps in `References`.
177
+ 9. **`issue pull-request`** — theirs titles the PR `ID Title` and sends the issue URL as the body;
178
+ ours titled it `Title` and pasted the issue _description_ into the body. Title adopted (a custom
179
+ `--title` is prefixed too). Body: the same two trailers `describe` prints — the URL as there,
180
+ plus the magic word so the link (and auto-close) does not depend on the branch name — and the
181
+ description stays in Linear rather than in a GitHub PR body.
182
+ 10. **`issue list`/`mine`/`search`, `project list` with no team configured** — theirs error
183
+ ("No default team…"); ours list the whole workspace, on purpose (`--all-teams` explicitly). So a
184
+ migrating user cannot mistake that for a team's list, the command prints one stderr note ("No
185
+ default team configured; listing every team's…"); `--quiet` silences it.
186
+ 11. **`--cycle +1`** — active cycle plus one on both (it was cycle _#1_ here once).
187
+ 12. **`document list --team X`** — filters to the team's documents on both.
188
+
189
+ Loud already, listed so you are not surprised:
190
+
191
+ | you type (schpet habit) | here | what happens |
192
+ | ------------------------------------------------------------ | ------------------------------------------ | --------------------------------------------------------------------------------------------- |
193
+ | `issue update TES-1 -t 'New title'` (`-t` = `--title` there) | `--title` | `-t` is `--team`: "No team matching 'New title'", exit 3 — unless a team is really named that |
194
+ | `project create -n 'My Project'` (`-n` = `--name` there) | `--name` | `-n` is `--limit`: refused on `create`, and the message says so |
195
+ | `issue comment delete <id>` in a script | same, `--yes` | destructive actions off-TTY need `--yes` (exit 2), there it just deletes |
196
+ | `--assignee <partial name>` (fuzzy there) | `me`, an email, an exact display/full name | "No user matching", exit 3 |
197
+ | `issue view 123` (bare number, team from config there) | `TES-123` | rejected, exit 2 |
198
+ | `linear config` (writes a toml there) | prints; `config init` / `config set` write | |
199
+ | `project update <p> -t A` (replaces the teams there) | `--teams A` | `--team` refused with the reason; `--teams` replaces, as there |
200
+ | `--all` on `user list` | see 4 above | pagination + a warning |
201
+
202
+ ## 7. Deliberate non-features
203
+
204
+ `issue commits`, `team autolinks`, and jj support are not curated commands. Bulk archive/delete
205
+ supports `--bulk`, `--bulk-file`, and `--bulk-stdin`; Markdown rendering and paging are built in.
206
+ Each is tracked; the raw `linear api` reaches all of the API surface in the meantime. If you type
207
+ one of these out of habit, the CLI says where the equivalent lives rather than just rejecting it:
208
+ `linear issue link x` answers "Use 'linear attachment create <issue> --url <url>'", and `issue
209
+ commits` points at `git log`. (`issue attach` is here as of TES-602 — see §4.)
210
+
211
+ ## 8. What you gain
212
+
213
+ - **One JSON shape everywhere.** Lists are a bare array, single results a bare object, errors
214
+ `{"error":{"message","code"}}` on stderr with distinct exit codes (0–6). schpet wraps in
215
+ `{nodes,pageInfo}` and several of its commands have no `--json` at all.
216
+ - **Discovery for agents:** `linear commands --json` (full command tree) and `linear schema`.
217
+ - **Broader surface:** notifications, webhooks, favorites, organization, cycle create/update,
218
+ comment resolve/reply, label hierarchy, `issue archive/unarchive`, `issue subscribe`.
219
+ - **Every mutation checks `success`**; every resolver scans past the first page and names the
220
+ candidates on a miss; `--json` implies non-interactive; a declined confirmation exits 6.