linear-sdk-cli 0.8.3 → 0.9.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.
@@ -0,0 +1,348 @@
1
+ # linear release
2
+
3
+ > Track releases and issue membership (Business/Enterprise)
4
+
5
+ _Generated from `linear commands --json`. `linear release --help` (or `<subcommand> --help`) is authoritative._
6
+
7
+ Every command also accepts the global flags `-j/--json`, `--no-input`, `-y/--yes`, `-q/--quiet`, `--workspace <slug>`, `--api-key <key>`, `--access-token <token>`, `-t/--team <key>`, `-n/--limit <n>`, `--all`, `-f/--fields <a,b,c>`, `--no-ansi` (alias `--no-color`), and `--debug`. Only command-specific options are listed below.
8
+
9
+ ### `linear release`
10
+
11
+ Track releases and issue membership (Business/Enterprise)
12
+
13
+ ```
14
+ linear release [options]
15
+ ```
16
+
17
+ ### `linear release add-issue`
18
+
19
+ Add an issue (KEY-123 or UUID) to an exact release
20
+
21
+ ```
22
+ linear release add-issue [options] <id> <issue>
23
+ ```
24
+
25
+ **Output (`--json`)**: a receipt object
26
+
27
+ ```text
28
+ id: string
29
+ releaseId: string
30
+ issueId: string
31
+ identifier: string
32
+ ```
33
+
34
+ ### `linear release complete`
35
+
36
+ Complete an explicit scheduled release; may roll open issues forward
37
+
38
+ ```
39
+ linear release complete [options]
40
+ ```
41
+
42
+ | Option | Description |
43
+ | ----------------------------- | -------------------------------------------------------------------- |
44
+ | `--description <text>` | release description (Markdown) |
45
+ | `--description-file <path>` | read description from file ('-' for stdin) |
46
+ | `--clear-description` | clear the description |
47
+ | `--pipeline <id\|slug>` | target pipeline UUID or slug |
48
+ | `--release-version <version>` | explicit release version within the pipeline (no latest fallback) |
49
+ | `--name <name>` | release name |
50
+ | `--link <label=url\|url>` | attach an external link (repeatable) |
51
+ | `--commit-sha <sha>` | full SHA (Linear preserves an existing SHA when targeting a version) |
52
+
53
+ **Output (`--json`)**: a receipt object
54
+
55
+ ```text
56
+ id: string
57
+ name: string
58
+ version: string | null
59
+ commitSha: string | null
60
+ url: string
61
+ pipeline: {id: string, name: string, type: string}
62
+ stage: {id: string, name: string, type: string, frozen: boolean}
63
+ startDate: string | null
64
+ targetDate: string | null
65
+ startedAt: string | null
66
+ completedAt: string | null
67
+ canceledAt: string | null
68
+ archivedAt: string | null
69
+ description: string | null
70
+ issueCount: number
71
+ ```
72
+
73
+ ### `linear release create`
74
+
75
+ Create a release (stage defaults depend on pipeline type)
76
+
77
+ Aliases: `new`
78
+
79
+ ```
80
+ linear release create [options]
81
+ ```
82
+
83
+ | Option | Description |
84
+ | ----------------------------- | -------------------------------------------- |
85
+ | `--description <text>` | release description (Markdown) |
86
+ | `--description-file <path>` | read description from file ('-' for stdin) |
87
+ | `--clear-description` | clear the description |
88
+ | `--name <name>` | release name |
89
+ | `--release-version <version>` | release version (unique within the pipeline) |
90
+ | `--commit-sha <sha>` | full Git commit SHA |
91
+ | `--stage <id>` | stage UUID from the same pipeline |
92
+ | `--start-date <date>` | estimated start date (YYYY-MM-DD) |
93
+ | `--target-date <date>` | estimated completion date (YYYY-MM-DD) |
94
+ | `--pipeline <id\|slug>` | pipeline UUID or slug |
95
+
96
+ **Output (`--json`)**: a receipt object
97
+
98
+ ```text
99
+ id: string
100
+ name: string
101
+ version: string | null
102
+ commitSha: string | null
103
+ url: string
104
+ pipeline: {id: string, name: string, type: string}
105
+ stage: {id: string, name: string, type: string, frozen: boolean}
106
+ startDate: string | null
107
+ targetDate: string | null
108
+ startedAt: string | null
109
+ completedAt: string | null
110
+ canceledAt: string | null
111
+ archivedAt: string | null
112
+ description: string | null
113
+ issueCount: number
114
+ ```
115
+
116
+ ### `linear release issues`
117
+
118
+ List issues in a release (UUID or slug)
119
+
120
+ ```
121
+ linear release issues [options] <id>
122
+ ```
123
+
124
+ **Output (`--json`)**: a bare array of objects
125
+
126
+ ```text
127
+ id: string
128
+ identifier: string
129
+ title: string
130
+ url: string
131
+ ```
132
+
133
+ ### `linear release links`
134
+
135
+ List external links on a release (UUID or slug)
136
+
137
+ ```
138
+ linear release links [options] <id>
139
+ ```
140
+
141
+ **Output (`--json`)**: a bare array of objects
142
+
143
+ ```text
144
+ id: string
145
+ label: string | null
146
+ url: string
147
+ ```
148
+
149
+ ### `linear release list`
150
+
151
+ List releases, optionally scoped to a pipeline
152
+
153
+ Aliases: `ls`
154
+
155
+ ```
156
+ linear release list [options]
157
+ ```
158
+
159
+ | Option | Description |
160
+ | ----------------------------- | ------------------------------- |
161
+ | `--pipeline <id\|slug>` | filter by pipeline UUID or slug |
162
+ | `--release-version <version>` | filter by exact version |
163
+ | `--stage-type <type>` | filter by lifecycle stage type |
164
+ | `--include-archived` | include archived releases |
165
+
166
+ **Output (`--json`)**: a bare array of objects
167
+
168
+ ```text
169
+ id: string
170
+ name: string
171
+ version: string | null
172
+ commitSha: string | null
173
+ url: string
174
+ pipeline: {id: string, name: string, type: string}
175
+ stage: {id: string, name: string, type: string, frozen: boolean}
176
+ startDate: string | null
177
+ targetDate: string | null
178
+ startedAt: string | null
179
+ completedAt: string | null
180
+ canceledAt: string | null
181
+ archivedAt: string | null
182
+ ```
183
+
184
+ ### `linear release remove-issue`
185
+
186
+ Remove an issue from an exact release
187
+
188
+ ```
189
+ linear release remove-issue [options] <id> <issue>
190
+ ```
191
+
192
+ **Output (`--json`)**: a receipt object
193
+
194
+ ```text
195
+ releaseId: string
196
+ issue: string
197
+ removed: boolean
198
+ ```
199
+
200
+ ### `linear release sync`
201
+
202
+ Sync explicit issue references; continuous pipelines complete immediately
203
+
204
+ ```
205
+ linear release sync [options]
206
+ ```
207
+
208
+ | Option | Description |
209
+ | ------------------------------ | ----------------------------------------------------------------- |
210
+ | `--description <text>` | release description (Markdown) |
211
+ | `--description-file <path>` | read description from file ('-' for stdin) |
212
+ | `--clear-description` | clear the description |
213
+ | `--pipeline <id\|slug>` | target pipeline UUID or slug |
214
+ | `--release-version <version>` | explicit release version within the pipeline (no latest fallback) |
215
+ | `--name <name>` | release name |
216
+ | `--link <label=url\|url>` | attach an external link (repeatable) |
217
+ | `--commit-sha <sha>` | full Git commit SHA (no implicit checkout scanning) |
218
+ | `--issue <key[=sha]>` | issue reference, defaulting to --commit-sha (repeatable) |
219
+ | `--reverted-issue <key[=sha]>` | reverted issue reference to remove (repeatable) |
220
+
221
+ **Output (`--json`)**: a receipt object
222
+
223
+ ```text
224
+ id: string
225
+ name: string
226
+ version: string | null
227
+ commitSha: string | null
228
+ url: string
229
+ pipeline: {id: string, name: string, type: string}
230
+ stage: {id: string, name: string, type: string, frozen: boolean}
231
+ startDate: string | null
232
+ targetDate: string | null
233
+ startedAt: string | null
234
+ completedAt: string | null
235
+ canceledAt: string | null
236
+ archivedAt: string | null
237
+ description: string | null
238
+ issueCount: number
239
+ ```
240
+
241
+ ### `linear release update`
242
+
243
+ Update exact release metadata or stage by UUID/slug
244
+
245
+ Aliases: `edit`
246
+
247
+ ```
248
+ linear release update [options] <id>
249
+ ```
250
+
251
+ | Option | Description |
252
+ | ----------------------------- | -------------------------------------------- |
253
+ | `--description <text>` | release description (Markdown) |
254
+ | `--description-file <path>` | read description from file ('-' for stdin) |
255
+ | `--clear-description` | clear the description |
256
+ | `--name <name>` | release name |
257
+ | `--release-version <version>` | release version (unique within the pipeline) |
258
+ | `--commit-sha <sha>` | full Git commit SHA |
259
+ | `--stage <id>` | stage UUID from the same pipeline |
260
+ | `--start-date <date>` | estimated start date (YYYY-MM-DD) |
261
+ | `--target-date <date>` | estimated completion date (YYYY-MM-DD) |
262
+
263
+ **Output (`--json`)**: a receipt object
264
+
265
+ ```text
266
+ id: string
267
+ name: string
268
+ version: string | null
269
+ commitSha: string | null
270
+ url: string
271
+ pipeline: {id: string, name: string, type: string}
272
+ stage: {id: string, name: string, type: string, frozen: boolean}
273
+ startDate: string | null
274
+ targetDate: string | null
275
+ startedAt: string | null
276
+ completedAt: string | null
277
+ canceledAt: string | null
278
+ archivedAt: string | null
279
+ description: string | null
280
+ issueCount: number
281
+ ```
282
+
283
+ ### `linear release update-version`
284
+
285
+ Update stage/name/links for an explicit pipeline and release version
286
+
287
+ ```
288
+ linear release update-version [options]
289
+ ```
290
+
291
+ | Option | Description |
292
+ | ----------------------------- | ----------------------------------------------------------------- |
293
+ | `--description <text>` | release description (Markdown) |
294
+ | `--description-file <path>` | read description from file ('-' for stdin) |
295
+ | `--clear-description` | clear the description |
296
+ | `--pipeline <id\|slug>` | target pipeline UUID or slug |
297
+ | `--release-version <version>` | explicit release version within the pipeline (no latest fallback) |
298
+ | `--name <name>` | release name |
299
+ | `--link <label=url\|url>` | attach an external link (repeatable) |
300
+ | `--stage <name>` | stage name (Linear matches exact name, then normalized name) |
301
+
302
+ **Output (`--json`)**: a receipt object
303
+
304
+ ```text
305
+ id: string
306
+ name: string
307
+ version: string | null
308
+ commitSha: string | null
309
+ url: string
310
+ pipeline: {id: string, name: string, type: string}
311
+ stage: {id: string, name: string, type: string, frozen: boolean}
312
+ startDate: string | null
313
+ targetDate: string | null
314
+ startedAt: string | null
315
+ completedAt: string | null
316
+ canceledAt: string | null
317
+ archivedAt: string | null
318
+ description: string | null
319
+ issueCount: number
320
+ ```
321
+
322
+ ### `linear release view`
323
+
324
+ Show a release by UUID or slug
325
+
326
+ ```
327
+ linear release view [options] <id>
328
+ ```
329
+
330
+ **Output (`--json`)**: a bare object
331
+
332
+ ```text
333
+ id: string
334
+ name: string
335
+ version: string | null
336
+ commitSha: string | null
337
+ url: string
338
+ pipeline: {id: string, name: string, type: string}
339
+ stage: {id: string, name: string, type: string, frozen: boolean}
340
+ startDate: string | null
341
+ targetDate: string | null
342
+ startedAt: string | null
343
+ completedAt: string | null
344
+ canceledAt: string | null
345
+ archivedAt: string | null
346
+ description: string | null
347
+ issueCount: number
348
+ ```
package/src/cli.ts CHANGED
@@ -19,6 +19,10 @@ import { registerProjectLabel } from "./commands/project-label.js";
19
19
  import { registerProjectUpdate } from "./commands/project-update.js";
20
20
  import { registerMilestone } from "./commands/milestone.js";
21
21
  import { registerCycle } from "./commands/cycle.js";
22
+ import { registerRelease } from "./commands/release.js";
23
+ import { registerReleasePipeline } from "./commands/release-pipeline.js";
24
+ import { registerReleaseStage } from "./commands/release-stage.js";
25
+ import { registerReleaseNote } from "./commands/release-note.js";
22
26
  import { registerUser } from "./commands/user.js";
23
27
  import { registerLabel } from "./commands/label.js";
24
28
  import { registerState } from "./commands/state.js";
@@ -94,6 +98,10 @@ export function createProgram(): Command {
94
98
  registerProjectUpdate(program);
95
99
  registerMilestone(program);
96
100
  registerCycle(program);
101
+ registerRelease(program);
102
+ registerReleasePipeline(program);
103
+ registerReleaseStage(program);
104
+ registerReleaseNote(program);
97
105
  // Phase 3: users, labels, workflow states, comments, documents, attachments, favorites.
98
106
  registerUser(program);
99
107
  registerLabel(program);
@@ -371,8 +371,12 @@ export function registerIssue(program: Command): void {
371
371
  .option("-s, --state <name>", "workflow state name or type")
372
372
  .option("-P, --priority <0-4>", "priority", parsePriority)
373
373
  .option("-l, --label <name>", "label (repeatable / comma-separated)", parseList)
374
- .option("-p, --project <name>", "project name or id")
375
- .option("--milestone <name>", "project milestone (requires --project)")
374
+ .option("-p, --project <name>", "project name or id (overrides the configured default)")
375
+ .option("--no-project", "create outside any project, ignoring configured and parent defaults")
376
+ .option(
377
+ "--milestone <name>",
378
+ "project milestone (requires an explicit, configured, or parent project)",
379
+ )
376
380
  .option(CYCLE_FLAG, CYCLE_DESC)
377
381
  .option("--estimate <n>", "estimate points", parseIntOption)
378
382
  .option(
@@ -391,6 +395,7 @@ export function registerIssue(program: Command): void {
391
395
  [
392
396
  "",
393
397
  "Examples:",
398
+ " linear config set project 'My Project' # default for this folder/repo",
394
399
  " linear issue create --title 'Fix login' --team TES --assignee me",
395
400
  " linear issue create --title 'Fix login' --assignee me --delegate Codex",
396
401
  " linear issue create --title 'Fix login' --delegate Codex --dry-run --json",
@@ -438,7 +443,8 @@ export function registerIssue(program: Command): void {
438
443
  state: opts.state,
439
444
  priority: opts.priority,
440
445
  label: opts.label,
441
- project: opts.project,
446
+ project: opts.project === false ? null : opts.project,
447
+ defaultProject: ctx.config.project,
442
448
  milestone: opts.milestone,
443
449
  cycle: opts.cycle,
444
450
  estimate: opts.estimate,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Top-level meta commands: whoami, auth (login/list/default/token/status/
3
- * logout/migrate), config.
3
+ * logout/rename/migrate), config.
4
4
  */
5
5
 
6
6
  import { Command } from "commander";
@@ -20,6 +20,8 @@ import {
20
20
  readOAuthCredential,
21
21
  writeOAuthCredential,
22
22
  migrateCredentials,
23
+ renameApiKeyProfile,
24
+ profileRenameProjectUpdates,
23
25
  redactKey,
24
26
  resolveConfig,
25
27
  defaultProjectConfigPath,
@@ -631,6 +633,80 @@ export function registerMeta(program: Command): void {
631
633
  }),
632
634
  );
633
635
 
636
+ // rename ------------------------------------------------------------------
637
+ auth
638
+ .command("rename <old-slug> <new-slug>")
639
+ .description("Rename a stored API-key profile after its Linear workspace URL slug changes")
640
+ .option(
641
+ "--project-config <paths...>",
642
+ "also inspect these project configs for old workspace selectors (read-only)",
643
+ )
644
+ .addHelpText(
645
+ "after",
646
+ `
647
+ Validates the exact stored API key against the current Linear workspace slug.
648
+ Preserves the key, profile team, user workspace/default, and shared keyring list.
649
+ Refuses configured or unlisted keyring destinations; never overwrites or revokes.
650
+ Keyring moves require atomic create-only support (macOS Keychain). Linux Secret
651
+ Service entries are refused; file-only API-key profiles are supported.
652
+ An interrupted rename can be resumed by repeating the same command.
653
+ OAuth user/app profiles are not supported; their credentials and grants stay unchanged.
654
+ Project files are never edited. Reports the discovered project config and any
655
+ --project-config paths needing updates; other repositories must be checked separately.
656
+ Example: linear auth rename old-workspace new-workspace --json`,
657
+ )
658
+ .action(
659
+ action(async (ctx: Context, opts, oldSlug: string, newSlug: string) => {
660
+ const from = explicitWorkspaceSlug(oldSlug);
661
+ const to = explicitWorkspaceSlug(newSlug);
662
+ if (ctx.options.workspace && ctx.options.workspace !== from)
663
+ throw usageError("--workspace must match <old-slug> when renaming a profile.");
664
+ if (ctx.options.apiKey || ctx.options.accessToken)
665
+ throw usageError(
666
+ "`auth rename` uses only the named stored profile; remove credential flags.",
667
+ );
668
+ const checkedProjectConfigs = [
669
+ ...new Set([
670
+ ...(ctx.config.projectConfigPath ? [ctx.config.projectConfigPath] : []),
671
+ ...(opts.projectConfig ?? []).map((path: string) => resolve(path)),
672
+ ]),
673
+ ] as string[];
674
+ const projectConfigUpdates = profileRenameProjectUpdates(from, to, checkedProjectConfigs);
675
+ const globalConfigUpdates = profileRenameProjectUpdates(
676
+ from,
677
+ to,
678
+ ctx.config.globalConfigPath ? [ctx.config.globalConfigPath] : [],
679
+ );
680
+ let organization: AuthIdentity["organization"] | undefined;
681
+ const saved = await renameApiKeyProfile(from, to, async (key) => {
682
+ organization = (await validateAuthCredential(key, to)).organization;
683
+ });
684
+ const otherProjects = `Update workspace = "${from}" to "${to}" in other project configs and LINEAR_WORKSPACE overrides. Only the listed project paths were checked.`;
685
+ ctx.output.emit(
686
+ {
687
+ success: true,
688
+ from,
689
+ workspace: to,
690
+ credentialType: "api-key",
691
+ organization: organization!,
692
+ ...saved,
693
+ checkedProjectConfigs,
694
+ projectConfigUpdates,
695
+ globalConfigUpdates,
696
+ otherProjects,
697
+ },
698
+ () => {
699
+ ctx.output.success(
700
+ `Renamed API-key profile '${from}' to '${to}'. Credential preserved; no grant revoked.`,
701
+ );
702
+ for (const update of [...projectConfigUpdates, ...globalConfigUpdates])
703
+ ctx.output.info(`Update ${update.path}: workspace = "${to}" (currently "${from}").`);
704
+ ctx.output.info(otherProjects);
705
+ },
706
+ );
707
+ }),
708
+ );
709
+
634
710
  // migrate -----------------------------------------------------------------
635
711
  auth
636
712
  .command("migrate")
@@ -753,6 +829,7 @@ export function registerMeta(program: Command): void {
753
829
  workspaceProfile: c.workspaceProfile ?? null,
754
830
  team: c.team ?? null,
755
831
  workspace: c.workspace ?? null,
832
+ project: c.project ?? null,
756
833
  sort: c.sort,
757
834
  origins: c.origins,
758
835
  userConfigPath: c.userConfigPath,
@@ -766,6 +843,7 @@ export function registerMeta(program: Command): void {
766
843
  ["Workspace profile", c.workspaceProfile ?? "(none)"],
767
844
  ["Team", show("team", c.team)],
768
845
  ["Workspace", show("workspace", c.workspace)],
846
+ ["Default issue project", show("project", c.project)],
769
847
  ["Sort", show("sort", c.sort)],
770
848
  ["User config", c.userConfigPath],
771
849
  ["Project config", c.projectConfigPath],
@@ -780,6 +858,7 @@ export function registerMeta(program: Command): void {
780
858
  .command("init")
781
859
  .description("Write a project .linear.toml (at the git root, or here outside a repository)")
782
860
  .option("--team <key>", "default team key (otherwise chosen from a list)")
861
+ .option("--project <name|id>", "default Linear project for new issues")
783
862
  .option("--sort <order>", `default issue-list sort (${ISSUE_SORTS.join(" | ")})`)
784
863
  .option("--path <file>", "write this file instead of <git root>/.linear.toml")
785
864
  .option("--force", "overwrite an existing file")
@@ -808,10 +887,11 @@ export function registerMeta(program: Command): void {
808
887
  team: validateSetting("team", team),
809
888
  };
810
889
  if (opts.sort !== undefined) settings.sort = validateSetting("sort", opts.sort);
890
+ if (opts.project !== undefined) settings.project = validateSetting("project", opts.project);
811
891
  initProjectConfig(path, settings, { force: opts.force === true });
812
892
  ctx.output.emit({ success: true, path, ...settings }, () =>
813
893
  ctx.output.success(
814
- `Wrote ${path} (team ${settings.team}${settings.sort ? `, sort ${settings.sort}` : ""}).`,
894
+ `Wrote ${path} (team ${settings.team}${settings.project ? `, project ${settings.project}` : ""}${settings.sort ? `, sort ${settings.sort}` : ""}).`,
815
895
  ),
816
896
  );
817
897
  }),
@@ -0,0 +1,90 @@
1
+ import { Command } from "commander";
2
+ import { action } from "../lib/action.js";
3
+ import { noteOptions, noteInput } from "../lib/release-input.js";
4
+ import { confirmDestructive } from "../lib/prompt.js";
5
+ import * as svc from "../services/release.js";
6
+
7
+ export function registerReleaseNote(program: Command): void {
8
+ const group = program
9
+ .command("release-note")
10
+ .description("Manage Markdown notes covering one or more releases");
11
+ group
12
+ .command("list")
13
+ .alias("ls")
14
+ .description("List release notes with optional pipeline/release filters")
15
+ .option("--pipeline <id|slug>", "filter by pipeline UUID or slug")
16
+ .option("--release <id>", "filter by covered release UUID or slug")
17
+ .option("--include-archived", "include archived notes")
18
+ .action(
19
+ action(async (ctx, opts) => {
20
+ const rows = await svc.listNotes(ctx.client, ctx.limit, opts);
21
+ ctx.output.list(
22
+ rows,
23
+ [
24
+ { key: "id", header: "ID", value: (r) => r.id },
25
+ { key: "title", header: "Title", value: (r) => r.title ?? "—" },
26
+ { key: "pipeline", header: "Pipeline", value: (r) => r.pipeline.name },
27
+ { key: "releaseCount", header: "Releases", value: (r) => r.releaseCount },
28
+ ],
29
+ rows,
30
+ );
31
+ }),
32
+ );
33
+ group
34
+ .command("view <id>")
35
+ .description("Show a release note and its Markdown body by UUID or slug")
36
+ .action(
37
+ action(async (ctx, _opts, id: string) => {
38
+ const n = await svc.getNote(ctx.client, id);
39
+ ctx.output.detail(n, [
40
+ ["ID", n.id],
41
+ ["Title", n.title],
42
+ ["Pipeline", n.pipeline.name],
43
+ ["Releases", n.releaseCount],
44
+ ["First release", n.firstRelease?.name],
45
+ ["Last release", n.lastRelease?.name],
46
+ ["Generation status", n.generationStatus],
47
+ ["Archived", n.archivedAt],
48
+ ["URL", n.url],
49
+ ["Content", n.content],
50
+ ]);
51
+ }),
52
+ );
53
+ noteOptions(
54
+ group
55
+ .command("create")
56
+ .description("Create a note for explicit releases or an inclusive createdAt range"),
57
+ )
58
+ .requiredOption("--pipeline <id|slug>", "pipeline UUID or slug")
59
+ .action(
60
+ action(async (ctx, opts) => {
61
+ const input = noteInput(opts);
62
+ svc.validateNoteScope(input, true);
63
+ const n = await svc.createNote(ctx.client, opts.pipeline, input);
64
+ ctx.output.emit(n, () => ctx.output.success(`Created release note ${n.id}`));
65
+ }),
66
+ );
67
+ noteOptions(
68
+ group
69
+ .command("update <id>")
70
+ .description("Update a note's title, Markdown, or covered releases"),
71
+ ).action(
72
+ action(async (ctx, opts, id: string) => {
73
+ const input = noteInput(opts);
74
+ svc.validateNoteScope(input, false);
75
+ const n = await svc.updateNote(ctx.client, id, input);
76
+ ctx.output.emit(n, () => ctx.output.success(`Updated release note ${n.id}`));
77
+ }),
78
+ );
79
+ group
80
+ .command("delete <id>")
81
+ .description("Delete a release note by UUID or slug")
82
+ .action(
83
+ action(async (ctx, _opts, id: string) => {
84
+ const n = await svc.getNote(ctx.client, id);
85
+ if (!(await confirmDestructive(ctx, `Delete release note ${n.title ?? n.id}?`))) return;
86
+ const receipt = await svc.deleteNote(ctx.client, n.id);
87
+ ctx.output.emit(receipt, () => ctx.output.success(`Deleted release note ${receipt.id}`));
88
+ }),
89
+ );
90
+ }