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.
Files changed (79) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/CHANGELOG.md +96 -58
  4. package/COVERAGE.md +5 -5
  5. package/MIGRATING.md +71 -69
  6. package/README.md +115 -90
  7. package/package.json +6 -7
  8. package/skills/README.md +15 -0
  9. package/skills/linear-sdk-cli/SKILL.md +27 -3
  10. package/skills/linear-sdk-cli/references/api.md +9 -9
  11. package/skills/linear-sdk-cli/references/auth.md +18 -0
  12. package/skills/linear-sdk-cli/references/commands.md +2 -2
  13. package/skills/linear-sdk-cli/references/comment.md +9 -6
  14. package/skills/linear-sdk-cli/references/config.md +9 -11
  15. package/skills/linear-sdk-cli/references/issue.md +72 -18
  16. package/skills/linear-sdk-cli/references/open.md +29 -0
  17. package/skills/linear-sdk-cli/references/team.md +16 -0
  18. package/skills/linear-sdk-cli/scripts/generate-docs.ts +6 -2
  19. package/skills/linear-sdk-cli-maintenance/SKILL.md +97 -0
  20. package/src/bin/linear.ts +16 -6
  21. package/src/cli.ts +3 -1
  22. package/src/commands/agent-session.ts +13 -5
  23. package/src/commands/api.ts +15 -9
  24. package/src/commands/attach.ts +15 -4
  25. package/src/commands/comment.ts +50 -11
  26. package/src/commands/completion.ts +2 -6
  27. package/src/commands/cycle.ts +6 -1
  28. package/src/commands/discover.ts +6 -2
  29. package/src/commands/document.ts +4 -1
  30. package/src/commands/initiative.ts +5 -1
  31. package/src/commands/issue.ts +392 -91
  32. package/src/commands/meta.ts +139 -31
  33. package/src/commands/milestone.ts +4 -1
  34. package/src/commands/notification.ts +15 -6
  35. package/src/commands/open.ts +84 -0
  36. package/src/commands/project.ts +17 -4
  37. package/src/commands/team.ts +13 -0
  38. package/src/commands/user.ts +2 -2
  39. package/src/commands/webhook.ts +12 -2
  40. package/src/config.ts +501 -184
  41. package/src/context.ts +13 -4
  42. package/src/git.ts +28 -3
  43. package/src/lib/body.ts +26 -2
  44. package/src/lib/errors.ts +33 -8
  45. package/src/lib/introspect.ts +76 -7
  46. package/src/lib/keyring.ts +51 -9
  47. package/src/lib/mentions.ts +49 -0
  48. package/src/lib/open.ts +32 -0
  49. package/src/lib/options.ts +108 -12
  50. package/src/lib/output-shapes.ts +81 -9
  51. package/src/lib/pagination.ts +50 -17
  52. package/src/lib/resolve.ts +40 -7
  53. package/src/lib/shape.ts +16 -12
  54. package/src/lib/upload.ts +3 -1
  55. package/src/output/color.ts +52 -0
  56. package/src/output/format.ts +96 -18
  57. package/src/output/markdown.ts +133 -0
  58. package/src/output/pager.ts +131 -0
  59. package/src/output/sanitize.ts +1 -1
  60. package/src/output/table.ts +7 -7
  61. package/src/services/agent-session.ts +11 -3
  62. package/src/services/attachment.ts +14 -5
  63. package/src/services/comment.ts +37 -9
  64. package/src/services/cycle.ts +11 -4
  65. package/src/services/document.ts +29 -10
  66. package/src/services/favorite.ts +15 -9
  67. package/src/services/initiative-update.ts +16 -15
  68. package/src/services/initiative.ts +15 -10
  69. package/src/services/issue.ts +263 -19
  70. package/src/services/label.ts +12 -2
  71. package/src/services/milestone.ts +8 -5
  72. package/src/services/notification.ts +16 -2
  73. package/src/services/organization.ts +23 -18
  74. package/src/services/project.ts +55 -34
  75. package/src/services/roadmap.ts +7 -6
  76. package/src/services/state.ts +9 -3
  77. package/src/services/team.ts +67 -24
  78. package/src/services/user.ts +2 -2
  79. package/src/services/webhook.ts +28 -17
@@ -8,7 +8,12 @@
8
8
  import type { LinearClient } from "@linear/sdk";
9
9
  import { withRetry } from "../client.js";
10
10
  import { shape } from "../lib/shape.js";
11
- import { collect, collectRawQuery } from "../lib/pagination.js";
11
+ import {
12
+ collect,
13
+ collectRawQuery,
14
+ inheritPaginationMetadata,
15
+ pageSize,
16
+ } from "../lib/pagination.js";
12
17
  import { usageError, notFound, ambiguous } from "../lib/errors.js";
13
18
  import { assertMutation, unwrapMutation } from "../lib/mutation.js";
14
19
  import {
@@ -206,7 +211,10 @@ query CliProjectDetail($filter: ProjectFilter!, $includeArchived: Boolean!) {
206
211
  }
207
212
  }`;
208
213
 
209
- export async function getProjectDetail(client: LinearClient, idArg: string): Promise<ProjectDetail> {
214
+ export async function getProjectDetail(
215
+ client: LinearClient,
216
+ idArg: string,
217
+ ): Promise<ProjectDetail> {
210
218
  const byId = isUuid(idArg);
211
219
  const filter = byId ? { id: { eq: idArg } } : { name: { eqIgnoreCase: idArg } };
212
220
  const data: any = await withRetry(() =>
@@ -337,7 +345,10 @@ export async function updateProject(client: LinearClient, idArg: string, opts: U
337
345
  export async function archiveProject(client: LinearClient, idArg: string) {
338
346
  const projectId = await resolveProjectId(client, idArg);
339
347
  const project = await withRetry(() => client.project(projectId));
340
- await assertMutation(withRetry(() => client.archiveProject(projectId)), "Project archive");
348
+ await assertMutation(
349
+ withRetry(() => client.archiveProject(projectId)),
350
+ "Project archive",
351
+ );
341
352
  return project;
342
353
  }
343
354
 
@@ -350,7 +361,10 @@ export async function archiveProject(client: LinearClient, idArg: string) {
350
361
  export async function deleteProject(client: LinearClient, idArg: string) {
351
362
  const projectId = await resolveProjectId(client, idArg);
352
363
  const project = await withRetry(() => client.project(projectId));
353
- await assertMutation(withRetry(() => client.deleteProject(projectId)), "Project deletion");
364
+ await assertMutation(
365
+ withRetry(() => client.deleteProject(projectId)),
366
+ "Project deletion",
367
+ );
354
368
  return project;
355
369
  }
356
370
 
@@ -376,16 +390,17 @@ export async function listMilestones(
376
390
  ): Promise<MilestoneRow[]> {
377
391
  const projectId = await resolveProjectId(client, idArg);
378
392
  const project = await withRetry(() => client.project(projectId));
379
- const conn = await withRetry(() =>
380
- project.projectMilestones({ first: Math.min(limit === Infinity ? 100 : limit, 100) }),
381
- );
393
+ const conn = await withRetry(() => project.projectMilestones({ first: pageSize(limit) }));
382
394
  const nodes = await collect(conn as any, limit);
383
- return nodes.map((m: any) => ({
384
- id: m.id,
385
- name: m.name,
386
- targetDate: m.targetDate ?? null,
387
- progress: m.progress ?? null,
388
- }));
395
+ return inheritPaginationMetadata(
396
+ nodes.map((m: any) => ({
397
+ id: m.id,
398
+ name: m.name,
399
+ targetDate: m.targetDate ?? null,
400
+ progress: m.progress ?? null,
401
+ })),
402
+ nodes,
403
+ );
389
404
  }
390
405
 
391
406
  export interface UpdateRow {
@@ -403,21 +418,22 @@ export async function listUpdates(
403
418
  ): Promise<UpdateRow[]> {
404
419
  const projectId = await resolveProjectId(client, idArg);
405
420
  const project = await withRetry(() => client.project(projectId));
406
- const conn = await withRetry(() =>
407
- project.projectUpdates({ first: Math.min(limit === Infinity ? 100 : limit, 100) }),
408
- );
421
+ const conn = await withRetry(() => project.projectUpdates({ first: pageSize(limit) }));
409
422
  const nodes = await collect(conn as any, limit);
410
- return Promise.all(
411
- nodes.map(async (u: any) => {
412
- const user = await u.user;
413
- return {
414
- id: u.id,
415
- createdAt: u.createdAt?.toISOString?.() ?? String(u.createdAt),
416
- user: user?.displayName ?? "unknown",
417
- body: u.body ?? "",
418
- health: u.health ?? null,
419
- };
420
- }),
423
+ return inheritPaginationMetadata(
424
+ await Promise.all(
425
+ nodes.map(async (u: any) => {
426
+ const user = await u.user;
427
+ return {
428
+ id: u.id,
429
+ createdAt: u.createdAt?.toISOString?.() ?? String(u.createdAt),
430
+ user: user?.displayName ?? "unknown",
431
+ body: u.body ?? "",
432
+ health: u.health ?? null,
433
+ };
434
+ }),
435
+ ),
436
+ nodes,
421
437
  );
422
438
  }
423
439
 
@@ -457,17 +473,22 @@ async function resolveTeamIds(
457
473
  /** Resolve a project status (by name or type) to a status id. */
458
474
  async function resolveStatusId(client: LinearClient, input: string): Promise<string> {
459
475
  if (isUuid(input)) return input;
460
- const statuses = await withRetry(() => client.projectStatuses({ first: 100 }));
476
+ const conn = await withRetry(() => client.projectStatuses({ first: 250 }));
477
+ const statuses = (await collect(conn as any, Infinity)) as Array<{
478
+ id: string;
479
+ name: string;
480
+ type: string;
481
+ }>;
461
482
  const lower = input.toLowerCase();
462
- const byName = statuses.nodes.filter((s) => s.name.toLowerCase() === lower);
463
- const matches = byName.length
464
- ? byName
465
- : statuses.nodes.filter((s) => s.type.toLowerCase() === lower);
483
+ const byName = statuses.filter((s) => s.name.toLowerCase() === lower);
484
+ const matches = byName.length ? byName : statuses.filter((s) => s.type.toLowerCase() === lower);
466
485
  if (matches.length === 0)
467
486
  throw notFound(
468
- `No project status '${input}'. Available: ${statuses.nodes.map((s) => s.name).join(", ")}`,
487
+ `No project status '${input}'. Available: ${statuses.map((s) => s.name).join(", ")}`,
469
488
  );
470
489
  if (matches.length > 1)
471
- throw ambiguous(`Multiple project statuses match '${input}': ${matches.map((s) => s.name).join(", ")}`);
490
+ throw ambiguous(
491
+ `Multiple project statuses match '${input}': ${matches.map((s) => s.name).join(", ")}`,
492
+ );
472
493
  return matches[0]!.id;
473
494
  }
@@ -13,7 +13,7 @@
13
13
  import type { LinearClient } from "@linear/sdk";
14
14
  import { withRetry } from "../client.js";
15
15
  import { shape } from "../lib/shape.js";
16
- import { collect } from "../lib/pagination.js";
16
+ import { collect, inheritPaginationMetadata, pageSize } from "../lib/pagination.js";
17
17
  import { usageError, notFound, ambiguous } from "../lib/errors.js";
18
18
  import { assertMutation, unwrapMutation } from "../lib/mutation.js";
19
19
  import { resolveUserId, isUuid } from "../lib/resolve.js";
@@ -44,11 +44,9 @@ export function toRow(r: any): RoadmapRow {
44
44
  }
45
45
 
46
46
  export async function listRoadmaps(client: LinearClient, limit: number): Promise<RoadmapRow[]> {
47
- const conn = await withRetry(() =>
48
- client.roadmaps({ first: limit === Infinity ? 100 : Math.min(limit, 100) }),
49
- );
47
+ const conn = await withRetry(() => client.roadmaps({ first: pageSize(limit) }));
50
48
  const nodes = await collect(conn as any, limit);
51
- return nodes.map(toRow);
49
+ return inheritPaginationMetadata(nodes.map(toRow), nodes);
52
50
  }
53
51
 
54
52
  export interface RoadmapDetail {
@@ -153,7 +151,10 @@ export async function updateRoadmap(client: LinearClient, idArg: string, opts: U
153
151
 
154
152
  export async function deleteRoadmap(client: LinearClient, idArg: string) {
155
153
  const roadmap = await resolveRoadmap(client, idArg);
156
- await assertMutation(withRetry(() => client.deleteRoadmap(roadmap.id)), "Roadmap deletion");
154
+ await assertMutation(
155
+ withRetry(() => client.deleteRoadmap(roadmap.id)),
156
+ "Roadmap deletion",
157
+ );
157
158
  return roadmap;
158
159
  }
159
160
 
@@ -10,7 +10,7 @@
10
10
  import type { LinearClient } from "@linear/sdk";
11
11
  import { withRetry } from "../client.js";
12
12
  import { shape } from "../lib/shape.js";
13
- import { collect, pageSize } from "../lib/pagination.js";
13
+ import { collect, pageSize, setPaginationMetadata } from "../lib/pagination.js";
14
14
  import { usageError } from "../lib/errors.js";
15
15
  import { resolveTeam, resolveStateId, isUuid } from "../lib/resolve.js";
16
16
 
@@ -41,8 +41,14 @@ export async function listStates(
41
41
  const team = await resolveTeam(client, teamInput, defaultTeamKey);
42
42
  const teamModel = await withRetry(() => client.team(team.id));
43
43
  const conn = await withRetry(() => teamModel.states({ first: pageSize(limit) }));
44
- const nodes = await collect(conn as any, limit);
45
- return nodes.map(toRow).sort((a, b) => a.position - b.position);
44
+ // The API's connection order is unrelated to workflow position. Sort the
45
+ // complete small state set, then apply the caller's limit.
46
+ const nodes = await collect(conn as any, Infinity);
47
+ const rows = nodes.map(toRow).sort((a, b) => a.position - b.position);
48
+ return setPaginationMetadata(
49
+ limit === Infinity ? rows : rows.slice(0, limit),
50
+ rows.length > limit,
51
+ );
46
52
  }
47
53
 
48
54
  export interface StateDetail {
@@ -9,7 +9,12 @@
9
9
  import type { LinearClient } from "@linear/sdk";
10
10
  import { withRetry } from "../client.js";
11
11
  import { shape } from "../lib/shape.js";
12
- import { collect, pageSize } from "../lib/pagination.js";
12
+ import {
13
+ collect,
14
+ inheritPaginationMetadata,
15
+ pageSize,
16
+ setPaginationMetadata,
17
+ } from "../lib/pagination.js";
13
18
  import { usageError } from "../lib/errors.js";
14
19
  import { assertMutation, unwrapMutation } from "../lib/mutation.js";
15
20
  import { resolveTeam, type ResolvedTeam } from "../lib/resolve.js";
@@ -23,11 +28,23 @@ export interface TeamRow {
23
28
  /** The row's shape as `linear commands` advertises it (TES-610); checked against the interface. */
24
29
  export const TEAM_ROW_SHAPE = shape<TeamRow>({ id: "string", key: "string", name: "string" });
25
30
 
31
+ /** Resolve the selected team to its canonical key, UUID, and name. */
32
+ export async function getTeamIdentity(
33
+ client: LinearClient,
34
+ keyArg: string | undefined,
35
+ defaultTeamKey: string | undefined,
36
+ ): Promise<ResolvedTeam> {
37
+ return resolveTeam(client, keyArg, defaultTeamKey);
38
+ }
39
+
26
40
  /** All teams (key, name, id). */
27
41
  export async function listTeams(client: LinearClient, limit: number): Promise<TeamRow[]> {
28
- const conn = await withRetry(() => client.teams({ first: limit === Infinity ? 100 : Math.min(limit, 250) }));
42
+ const conn = await withRetry(() => client.teams({ first: pageSize(limit) }));
29
43
  const nodes = await collect(conn as any, limit);
30
- return nodes.map((t: any) => ({ id: t.id, key: t.key, name: t.name }));
44
+ return inheritPaginationMetadata(
45
+ nodes.map((t: any) => ({ id: t.id, key: t.key, name: t.name })),
46
+ nodes,
47
+ );
31
48
  }
32
49
 
33
50
  export interface TeamDetail {
@@ -128,13 +145,16 @@ export async function listMembers(
128
145
  (await withRetry(() => team.members({ first: pageSize(limit), includeDisabled }))) as any,
129
146
  limit,
130
147
  );
131
- return nodes.map((u: any) => ({
132
- id: u.id,
133
- displayName: u.displayName,
134
- name: u.name,
135
- email: u.email,
136
- active: !!u.active,
137
- }));
148
+ return inheritPaginationMetadata(
149
+ nodes.map((u: any) => ({
150
+ id: u.id,
151
+ displayName: u.displayName,
152
+ name: u.name,
153
+ email: u.email,
154
+ active: !!u.active,
155
+ })),
156
+ nodes,
157
+ );
138
158
  }
139
159
 
140
160
  export interface StateRow {
@@ -161,8 +181,8 @@ export async function listStates(
161
181
  ): Promise<StateRow[]> {
162
182
  const resolved = await resolveTeam(client, keyArg, defaultTeamKey);
163
183
  const team = await withRetry(() => client.team(resolved.id));
164
- const nodes = await collect((await withRetry(() => team.states())) as any, limit);
165
- return nodes
184
+ const nodes = await collect((await withRetry(() => team.states())) as any, Infinity);
185
+ const rows = nodes
166
186
  .map((s: any) => ({
167
187
  id: s.id,
168
188
  name: s.name,
@@ -171,6 +191,10 @@ export async function listStates(
171
191
  position: s.position,
172
192
  }))
173
193
  .sort((a: StateRow, b: StateRow) => a.position - b.position);
194
+ return setPaginationMetadata(
195
+ limit === Infinity ? rows : rows.slice(0, limit),
196
+ rows.length > limit,
197
+ );
174
198
  }
175
199
 
176
200
  export interface LabelRow {
@@ -179,7 +203,11 @@ export interface LabelRow {
179
203
  color: string;
180
204
  }
181
205
 
182
- export const TEAM_LABEL_ROW_SHAPE = shape<LabelRow>({ id: "string", name: "string", color: "string" });
206
+ export const TEAM_LABEL_ROW_SHAPE = shape<LabelRow>({
207
+ id: "string",
208
+ name: "string",
209
+ color: "string",
210
+ });
183
211
 
184
212
  export async function listLabels(
185
213
  client: LinearClient,
@@ -190,7 +218,10 @@ export async function listLabels(
190
218
  const resolved = await resolveTeam(client, keyArg, defaultTeamKey);
191
219
  const team = await withRetry(() => client.team(resolved.id));
192
220
  const nodes = await collect((await withRetry(() => team.labels())) as any, limit);
193
- return nodes.map((l: any) => ({ id: l.id, name: l.name, color: l.color }));
221
+ return inheritPaginationMetadata(
222
+ nodes.map((l: any) => ({ id: l.id, name: l.name, color: l.color })),
223
+ nodes,
224
+ );
194
225
  }
195
226
 
196
227
  export interface CycleRow {
@@ -217,14 +248,20 @@ export async function listCycles(
217
248
  ): Promise<CycleRow[]> {
218
249
  const resolved = await resolveTeam(client, keyArg, defaultTeamKey);
219
250
  const team = await withRetry(() => client.team(resolved.id));
220
- const nodes = await collect((await withRetry(() => team.cycles())) as any, limit);
221
- return nodes.map((c: any) => ({
222
- id: c.id,
223
- number: c.number,
224
- name: c.name ?? null,
225
- startsAt: c.startsAt?.toISOString?.() ?? (c.startsAt ? String(c.startsAt) : null),
226
- endsAt: c.endsAt?.toISOString?.() ?? (c.endsAt ? String(c.endsAt) : null),
227
- }));
251
+ const nodes = await collect((await withRetry(() => team.cycles())) as any, Infinity);
252
+ const rows = nodes
253
+ .map((c: any) => ({
254
+ id: c.id,
255
+ number: c.number,
256
+ name: c.name ?? null,
257
+ startsAt: c.startsAt?.toISOString?.() ?? (c.startsAt ? String(c.startsAt) : null),
258
+ endsAt: c.endsAt?.toISOString?.() ?? (c.endsAt ? String(c.endsAt) : null),
259
+ }))
260
+ .sort((a, b) => b.number - a.number);
261
+ return setPaginationMetadata(
262
+ limit === Infinity ? rows : rows.slice(0, limit),
263
+ rows.length > limit,
264
+ );
228
265
  }
229
266
 
230
267
  export interface CreateTeamOptions {
@@ -321,7 +358,10 @@ export async function moveTeamIssues(
321
358
  to: ResolvedTeam,
322
359
  ): Promise<number> {
323
360
  const team = await withRetry(() => client.team(from.id));
324
- const issues = await collect((await withRetry(() => team.issues({ first: 100 }))) as any, Infinity);
361
+ const issues = await collect(
362
+ (await withRetry(() => team.issues({ first: 100 }))) as any,
363
+ Infinity,
364
+ );
325
365
  const ids = issues.map((i: any) => i.id as string);
326
366
  for (let i = 0; i < ids.length; i += MOVE_BATCH) {
327
367
  const batch = ids.slice(i, i + MOVE_BATCH);
@@ -339,5 +379,8 @@ export async function moveTeamIssues(
339
379
  * user as `feature_not_accessible` like every other plan gate.
340
380
  */
341
381
  export async function deleteTeam(client: LinearClient, team: ResolvedTeam): Promise<void> {
342
- await assertMutation(withRetry(() => client.deleteTeam(team.id)), "Team deletion");
382
+ await assertMutation(
383
+ withRetry(() => client.deleteTeam(team.id)),
384
+ "Team deletion",
385
+ );
343
386
  }
@@ -9,7 +9,7 @@
9
9
  import type { LinearClient } from "@linear/sdk";
10
10
  import { withRetry } from "../client.js";
11
11
  import { shape } from "../lib/shape.js";
12
- import { collect, pageSize } from "../lib/pagination.js";
12
+ import { collect, inheritPaginationMetadata, pageSize } from "../lib/pagination.js";
13
13
  import { resolveUserId } from "../lib/resolve.js";
14
14
 
15
15
  export interface UserRow {
@@ -45,7 +45,7 @@ export async function listUsers(
45
45
  ): Promise<UserRow[]> {
46
46
  const conn = await withRetry(() => client.users({ first: pageSize(limit), includeDisabled }));
47
47
  const nodes = await collect(conn as any, limit);
48
- return nodes.map(toRow);
48
+ return inheritPaginationMetadata(nodes.map(toRow), nodes);
49
49
  }
50
50
 
51
51
  export interface UserDetail {
@@ -9,8 +9,8 @@
9
9
  import { type LinearClient, WebhookResourceType } from "@linear/sdk";
10
10
  import { withRetry } from "../client.js";
11
11
  import { shape } from "../lib/shape.js";
12
- import { collect } from "../lib/pagination.js";
13
- import { usageError, notFound } from "../lib/errors.js";
12
+ import { collect, inheritPaginationMetadata, pageSize } from "../lib/pagination.js";
13
+ import { usageError, notFound, normalizeError } from "../lib/errors.js";
14
14
  import { assertMutation, unwrapMutation } from "../lib/mutation.js";
15
15
  import { resolveTeam } from "../lib/resolve.js";
16
16
 
@@ -56,17 +56,18 @@ export const WEBHOOK_ROW_SHAPE = shape<WebhookRow>({
56
56
 
57
57
  /** All webhooks in the workspace (relation-light, so no N+1). */
58
58
  export async function listWebhooks(client: LinearClient, limit: number): Promise<WebhookRow[]> {
59
- const conn = await withRetry(() =>
60
- client.webhooks({ first: limit === Infinity ? 100 : Math.min(limit, 100) }),
61
- );
59
+ const conn = await withRetry(() => client.webhooks({ first: pageSize(limit) }));
62
60
  const nodes = await collect(conn as any, limit);
63
- return nodes.map((w: any) => ({
64
- id: w.id,
65
- url: w.url ?? null,
66
- enabled: w.enabled,
67
- resourceTypes: w.resourceTypes ?? [],
68
- label: w.label ?? null,
69
- }));
61
+ return inheritPaginationMetadata(
62
+ nodes.map((w: any) => ({
63
+ id: w.id,
64
+ url: w.url ?? null,
65
+ enabled: w.enabled,
66
+ resourceTypes: w.resourceTypes ?? [],
67
+ label: w.label ?? null,
68
+ })),
69
+ nodes,
70
+ );
70
71
  }
71
72
 
72
73
  export interface WebhookDetail {
@@ -174,7 +175,9 @@ export async function updateWebhook(client: LinearClient, id: string, opts: Upda
174
175
  if (opts.secret !== undefined) input.secret = opts.secret;
175
176
 
176
177
  if (Object.keys(input).length === 0)
177
- throw usageError("Nothing to update; pass at least one of --url, --enabled/--disabled, --resource.");
178
+ throw usageError(
179
+ "Nothing to update; pass at least one of --url, --enabled/--disabled, --resource.",
180
+ );
178
181
 
179
182
  return unwrapMutation(
180
183
  withRetry(() => client.updateWebhook(id, input as any)),
@@ -184,9 +187,17 @@ export async function updateWebhook(client: LinearClient, id: string, opts: Upda
184
187
  }
185
188
 
186
189
  export async function deleteWebhook(client: LinearClient, id: string): Promise<WebhookDetail> {
187
- const webhook = await getWebhookDetail(client, id).catch(() => {
188
- throw notFound(`No webhook matching '${id}'.`);
189
- });
190
- await assertMutation(withRetry(() => client.deleteWebhook(id)), "Webhook deletion");
190
+ let webhook: WebhookDetail;
191
+ try {
192
+ webhook = await getWebhookDetail(client, id);
193
+ } catch (err) {
194
+ const normalized = normalizeError(err);
195
+ if (normalized.code === "not_found") throw notFound(`No webhook matching '${id}'.`);
196
+ throw normalized;
197
+ }
198
+ await assertMutation(
199
+ withRetry(() => client.deleteWebhook(id)),
200
+ "Webhook deletion",
201
+ );
191
202
  return webhook;
192
203
  }