linear-sdk-cli 0.6.0 → 0.7.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@ Release Please generates this file from Conventional Commits. Feature and fix pu
4
4
  not edit it directly; their squash-commit titles are the release-note source of truth. This project
5
5
  adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.7.0](https://github.com/zhendalf/linear-sdk-cli/compare/v0.6.0...v0.7.0) (2026-09-18)
8
+
9
+ ### Features
10
+
11
+ - add changelog command ([#75](https://github.com/zhendalf/linear-sdk-cli/issues/75)) ([b095e77](https://github.com/zhendalf/linear-sdk-cli/commit/b095e7782ae3fb323ce9004883c7fb047f252c63))
12
+
7
13
  ## [0.6.0](https://github.com/zhendalf/linear-sdk-cli/compare/v0.5.3...v0.6.0) (2026-09-17)
8
14
 
9
15
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linear-sdk-cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Ergonomic command-line interface for Linear, built on @linear/sdk",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -316,6 +316,8 @@ linear auth status
316
316
  linear auth token
317
317
  linear auth whoami
318
318
 
319
+ linear changelog
320
+
319
321
  linear commands
320
322
 
321
323
  linear comment
@@ -520,6 +522,7 @@ prints the same). These are supplementary — `--help` on any command is authori
520
522
  - [api](references/api.md) — Run a raw GraphQL query or mutation against the Linear API
521
523
  - [attachment](references/attachment.md) — Work with issue attachments
522
524
  - [auth](references/auth.md) — Manage authentication
525
+ - [changelog](references/changelog.md) — Show recent CLI release notes
523
526
  - [commands](references/commands.md) — Machine-readable command tree (for scripts/agents)
524
527
  - [comment](references/comment.md) — Manage comments
525
528
  - [completion](references/completion.md) — Output a shell completion script
@@ -0,0 +1,24 @@
1
+ # linear changelog
2
+
3
+ > Show recent CLI release notes
4
+
5
+ _Generated from `linear commands --json`. `linear changelog --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 changelog`
10
+
11
+ Show recent CLI release notes
12
+
13
+ ```
14
+ linear changelog [options]
15
+ ```
16
+
17
+ **Output (`--json`)**: a bare array of objects
18
+
19
+ ```text
20
+ version: string
21
+ date: string
22
+ url: string
23
+ sections: Array<{title: string, items: string[]}>
24
+ ```
package/src/cli.ts CHANGED
@@ -8,6 +8,7 @@ import { addGlobalOptions, globalOptionKeys, unknownCommand, commandPath } from
8
8
  import { registerMeta } from "./commands/meta.js";
9
9
  import { registerApi } from "./commands/api.js";
10
10
  import { registerCompletion } from "./commands/completion.js";
11
+ import { registerChangelog } from "./commands/changelog.js";
11
12
  import { registerIssue, renderIssueDetail } from "./commands/issue.js";
12
13
  import { registerAgentSession } from "./commands/agent-session.js";
13
14
  import { registerAttach } from "./commands/attach.js";
@@ -62,6 +63,7 @@ export function createProgram(): Command {
62
63
  registerMeta(program);
63
64
  registerApi(program);
64
65
  registerCompletion(program);
66
+ registerChangelog(program);
65
67
  registerOpen(program);
66
68
  // Phase 1: issues. Agent sessions live on issues, so their group is mounted
67
69
  // under `issue` (`linear issue agent-session …`); so is file upload
@@ -0,0 +1,108 @@
1
+ /** Show release notes from the CHANGELOG.md bundled with the CLI package. */
2
+
3
+ import { readFileSync } from "node:fs";
4
+ import { fileURLToPath } from "node:url";
5
+ import { Command } from "commander";
6
+ import { action } from "../lib/action.js";
7
+ import { CliError } from "../lib/errors.js";
8
+ import type { Context } from "../context.js";
9
+
10
+ export interface ChangelogSection {
11
+ title: string;
12
+ items: string[];
13
+ }
14
+
15
+ export interface ChangelogRelease {
16
+ version: string;
17
+ date: string;
18
+ url: string;
19
+ sections: ChangelogSection[];
20
+ }
21
+
22
+ const CHANGELOG_PATH = fileURLToPath(new URL("../../CHANGELOG.md", import.meta.url));
23
+ const DEFAULT_RELEASE_COUNT = 3;
24
+
25
+ /** Parse the Release Please Markdown format into a stable machine-readable shape. */
26
+ export function parseChangelog(markdown: string): ChangelogRelease[] {
27
+ const releases: ChangelogRelease[] = [];
28
+ const releasePattern = /^## \[([^\]]+)]\(([^)]+)\) \((\d{4}-\d{2}-\d{2})\)\s*$/gm;
29
+ const matches = [...markdown.matchAll(releasePattern)];
30
+
31
+ for (const [index, match] of matches.entries()) {
32
+ const bodyStart = (match.index ?? 0) + match[0].length;
33
+ const bodyEnd = matches[index + 1]?.index ?? markdown.length;
34
+ const body = markdown.slice(bodyStart, bodyEnd);
35
+ const sections: ChangelogSection[] = [];
36
+ const sectionPattern = /^### (.+)\s*$/gm;
37
+ const sectionMatches = [...body.matchAll(sectionPattern)];
38
+
39
+ for (const [sectionIndex, sectionMatch] of sectionMatches.entries()) {
40
+ const itemsStart = (sectionMatch.index ?? 0) + sectionMatch[0].length;
41
+ const itemsEnd = sectionMatches[sectionIndex + 1]?.index ?? body.length;
42
+ const itemsBody = body.slice(itemsStart, itemsEnd).trim();
43
+ const items = itemsBody
44
+ .split(/\n(?=- )/)
45
+ .map((item) => item.replace(/^- /, "").replace(/\n\s+/g, " ").trim())
46
+ .filter(Boolean);
47
+ sections.push({ title: sectionMatch[1]!.trim(), items });
48
+ }
49
+
50
+ releases.push({
51
+ version: match[1]!,
52
+ url: match[2]!,
53
+ date: match[3]!,
54
+ sections,
55
+ });
56
+ }
57
+
58
+ return releases;
59
+ }
60
+
61
+ export function registerChangelog(program: Command): void {
62
+ program
63
+ .command("changelog")
64
+ .description("Show recent CLI release notes")
65
+ .addHelpText(
66
+ "after",
67
+ [
68
+ "",
69
+ "Examples:",
70
+ " linear changelog # latest three releases",
71
+ " linear changelog --limit 1 # latest release only",
72
+ " linear changelog --all # complete bundled history",
73
+ " linear changelog --json # structured release notes",
74
+ ].join("\n"),
75
+ )
76
+ .action(action(runChangelog));
77
+ }
78
+
79
+ export function runChangelog(ctx: Context): void {
80
+ let markdown: string;
81
+ try {
82
+ markdown = readFileSync(CHANGELOG_PATH, "utf8");
83
+ } catch (error) {
84
+ throw new CliError(`Cannot read the bundled changelog: ${(error as Error).message}`, "runtime");
85
+ }
86
+
87
+ const releases = parseChangelog(markdown);
88
+ const count =
89
+ ctx.options.all || ctx.options.limit === 0
90
+ ? releases.length
91
+ : (ctx.options.limit ?? DEFAULT_RELEASE_COUNT);
92
+ const recent = releases.slice(0, count);
93
+
94
+ ctx.output.emit(recent, () => {
95
+ const rendered = recent
96
+ .map((release) => {
97
+ const sections = release.sections
98
+ .map(
99
+ (section) =>
100
+ `### ${section.title}\n\n${section.items.map((item) => `- ${item}`).join("\n")}`,
101
+ )
102
+ .join("\n\n");
103
+ return `## [${release.version}](${release.url}) (${release.date})\n\n${sections}`;
104
+ })
105
+ .join("\n\n");
106
+ ctx.output.markdown(rendered);
107
+ });
108
+ }
package/src/lib/action.ts CHANGED
@@ -27,7 +27,7 @@ export function action(handler: ActionHandler) {
27
27
  let group = command;
28
28
  while (group.parent?.parent) group = group.parent;
29
29
  const offline =
30
- ["config", "open", "commands", "completion"].includes(group.name()) ||
30
+ ["config", "open", "commands", "completion", "changelog"].includes(group.name()) ||
31
31
  (group.name() === "auth" && !["whoami", "status", "token"].includes(command.name()));
32
32
  if (!offline) await ctx.selectWorkspace();
33
33
  try {
@@ -422,8 +422,9 @@ export const FIELDS_COMMANDS: ReadonlySet<string> = new Set([
422
422
  "webhook view",
423
423
  ]);
424
424
 
425
- /** Commands that page through a query: `--limit <n>` caps them, `--all` (or `--limit 0`) exhausts them. */
425
+ /** Commands with bounded lists: `--limit <n>` caps them, `--all` (or `--limit 0`) exhausts them. */
426
426
  export const LIMIT_COMMANDS: ReadonlySet<string> = new Set([
427
+ "changelog",
427
428
  "attachment list",
428
429
  "comment list",
429
430
  "cycle list",
@@ -313,6 +313,13 @@ const RAW_API: OutputShape = {
313
313
  export const OUTPUT_SHAPES: Record<string, OutputShape | null> = {
314
314
  api: RAW_API,
315
315
 
316
+ changelog: list({
317
+ version: "string",
318
+ date: "string",
319
+ url: "string",
320
+ sections: [{ title: "string", items: ["string"] }],
321
+ }),
322
+
316
323
  attachment: null,
317
324
  "attachment create": receipt({ id: "string", title: "string", url: "string" }),
318
325
  "attachment delete": receipt({ id: "string", title: "string", deleted: "boolean" }),