Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## [Unreleased]

### Added

- `--json` (`-j`) on `team list`, `cycle list`, `cycle view`, `milestone list`, `milestone view`, and `project view`, the last read commands without machine-readable output. List commands emit the same `{ nodes, pageInfo }` connection shape as the other list commands, after the same filtering and ordering as the table; view commands emit the GraphQL object as fetched, including every issue rather than the ten-item preview, and `milestone view --all --json` includes every page. (A 2.0.0 entry claimed `cycle list --json`; that change never actually landed.) ([#276](https://github.com/schpet/linear-cli/issues/276); thanks @lakardion)

### Fixed

- `cycle list` and `milestone list` now paginate instead of taking Linear's default page, so a team with more than 50 cycles or a project with more than 50 milestones is no longer silently truncated

## [2.6.0] - 2026-09-02

### Added
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ linear issue comment add ENG-123 -a ./screenshot.png --public # public image U

```bash
linear team list # list teams
linear team list --json # as JSON, e.g. to map a team name to its key or id in scripts
linear team id # print out the team id (e.g. for scripts)
linear team members # list team members
linear team members --all --json # include inactive members, as JSON
Expand All @@ -195,17 +196,29 @@ linear user list --json # machine-readable output
```bash
linear project list # list projects
linear project view # view project details
linear project view <projectId> --json # project details as JSON
linear project create --name "API v2" --team ENG --content-file overview.md
linear project create --name "Mobile launch" --team APP --priority high --label Launch --member jane@example.com
```

### cycle commands

```bash
linear cycle list --team ENG # list a team's cycles
linear cycle list --team ENG --json # as JSON
linear cycle view 12 --team ENG # view a cycle by number or name
linear cycle view 12 --team ENG --json # cycle details and its issues, as JSON
```

### milestone commands

```bash
linear milestone list --project <projectId> # list milestones for a project
linear m list --project <projectId> # list milestones (alias)
linear milestone list --project <projectId> --json # as JSON
linear milestone view <milestoneId> # view milestone details
linear m view <milestoneId> # view milestone (alias)
linear milestone view <milestoneId> --all --json # every attached issue, as JSON
linear milestone create --project <projectId> --name "Q1 Goals" --target-date "2026-03-31" # create a milestone
linear m create --project <projectId> # create a milestone (interactive)
linear milestone update <milestoneId> --name "New Name" # update milestone name
Expand Down
2 changes: 2 additions & 0 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ linear issue delete TEAM-123

```bash
linear team list
linear team list --json # machine-readable, e.g. to map a team name to its key
```

#### get team id
Expand Down Expand Up @@ -277,6 +278,7 @@ linear project list

```bash
linear project view PROJECT-ID
linear project view PROJECT-ID --json
```

### shell completions
Expand Down
2 changes: 1 addition & 1 deletion skills/linear-cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ Each command has detailed help output describing all available flags and options

Some commands have required flags that aren't obvious. Notable examples:

- `issue list` sorts by priority by default — override via `--sort` (valid values: `manual`, `priority`), the `issue_sort` config option, or the `LINEAR_ISSUE_SORT` env var. Requires `--team <key>` unless the team can be inferred from the directory — if unknown, run `linear team list` first.
- `issue list` sorts by priority by default — override via `--sort` (valid values: `manual`, `priority`), the `issue_sort` config option, or the `LINEAR_ISSUE_SORT` env var. Requires `--team <key>` unless the team can be inferred from the directory — if unknown, run `linear team list` first (`linear team list --json` maps team names to their `key` and `id`).
- `--no-pager` is only supported on `issue list` — passing it to other commands like `project list` will error.

## Using the Linear GraphQL API Directly
Expand Down
2 changes: 1 addition & 1 deletion skills/linear-cli/SKILL.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ Each command has detailed help output describing all available flags and options

Some commands have required flags that aren't obvious. Notable examples:

- `issue list` sorts by priority by default — override via `--sort` (valid values: `manual`, `priority`), the `issue_sort` config option, or the `LINEAR_ISSUE_SORT` env var. Requires `--team <key>` unless the team can be inferred from the directory — if unknown, run `linear team list` first.
- `issue list` sorts by priority by default — override via `--sort` (valid values: `manual`, `priority`), the `issue_sort` config option, or the `LINEAR_ISSUE_SORT` env var. Requires `--team <key>` unless the team can be inferred from the directory — if unknown, run `linear team list` first (`linear team list --json` maps team names to their `key` and `id`).
- `--no-pager` is only supported on `issue list` — passing it to other commands like `project list` will error.

## Using the Linear GraphQL API Directly
Expand Down
6 changes: 4 additions & 2 deletions skills/linear-cli/references/cycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ Options:

-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
--team <team> - Team key (defaults to current team)
--team <team> - Team key (defaults to current team)
-j, --json - Output as JSON
```

### view
Expand All @@ -57,5 +58,6 @@ Options:

-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
--team <team> - Team key (defaults to current team)
--team <team> - Team key (defaults to current team)
-j, --json - Output as JSON
```
4 changes: 3 additions & 1 deletion skills/linear-cli/references/milestone.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ Options:
-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
--project <project> - Project (UUID, slug ID, or name) (required)
-j, --json - Output as JSON
```

### update
Expand Down Expand Up @@ -123,5 +124,6 @@ Options:
-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
--all - Fetch and list every issue attached to the milestone (paginates the Linear API).
--project <project> - Project for resolving a milestone name (UUID, slug ID, or name)
--project <project> - Project for resolving a milestone name (UUID, slug ID, or name)
-j, --json - Output as JSON
```
3 changes: 2 additions & 1 deletion skills/linear-cli/references/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,6 @@ Options:
-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
-w, --web - Open in web browser
-a, --app - Open in Linear.app
-a, --app - Open in Linear.app
-j, --json - Output as JSON
```
3 changes: 2 additions & 1 deletion skills/linear-cli/references/team.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ Options:
-h, --help - Show this help.
--workspace <slug> - Target workspace (uses credentials)
-w, --web - Open in web browser
-a, --app - Open in Linear.app
-a, --app - Open in Linear.app
-j, --json - Output as JSON
```

### members
Expand Down
62 changes: 51 additions & 11 deletions src/commands/cycle/cycle-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,25 @@ import { Command } from "@cliffy/command"
import { unicodeWidth } from "@std/cli"
import { green } from "@std/fmt/colors"
import { gql } from "../../__codegen__/gql.ts"
import type { GetTeamCyclesQuery } from "../../__codegen__/graphql.ts"
import { getGraphQLClient } from "../../utils/graphql.ts"
import { padDisplay } from "../../utils/display.ts"
import { getTeamIdByKey, getTeamKey } from "../../utils/linear.ts"
import { shouldShowSpinner } from "../../utils/hyperlink.ts"
import { header, muted } from "../../utils/styling.ts"
import {
CliError,
handleError,
NotFoundError,
ValidationError,
} from "../../utils/errors.ts"

const GetTeamCycles = gql(`
query GetTeamCycles($teamId: String!) {
query GetTeamCycles($teamId: String!, $first: Int, $after: String) {
team(id: $teamId) {
id
name
cycles {
cycles(first: $first, after: $after) {
nodes {
id
number
Expand All @@ -30,6 +32,10 @@ const GetTeamCycles = gql(`
isFuture
isPast
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
Expand All @@ -56,7 +62,8 @@ export const listCommand = new Command()
.name("list")
.description("List cycles for a team")
.option("--team <team:string>", "Team key (defaults to current team)")
.action(async ({ team }) => {
.option("-j, --json", "Output as JSON")
.action(async ({ team, json }) => {
try {
const teamKey = team || getTeamKey()
if (!teamKey) {
Expand All @@ -71,25 +78,58 @@ export const listCommand = new Command()
}

const { Spinner } = await import("@std/cli/unstable-spinner")
const showSpinner = shouldShowSpinner()
const showSpinner = !json && shouldShowSpinner()
const spinner = showSpinner ? new Spinner() : null
spinner?.start()

const client = getGraphQLClient()
const result = await client.request(GetTeamCycles, { teamId })
spinner?.stop()

const cycles = result.team?.cycles?.nodes || []

if (cycles.length === 0) {
console.log("No cycles found for this team.")
return
type CyclesConnection = NonNullable<GetTeamCyclesQuery["team"]>["cycles"]
const cycles: CyclesConnection["nodes"] = []
let pageInfo: CyclesConnection["pageInfo"] = {
hasNextPage: false,
endCursor: null,
}
let after: string | null | undefined = undefined

do {
const result = await client.request(GetTeamCycles, {
teamId,
first: 100,
after,
})
if (!result.team) {
throw new NotFoundError("Team", teamKey)
}

cycles.push(...result.team.cycles.nodes)
pageInfo = result.team.cycles.pageInfo

if (pageInfo.hasNextPage && !pageInfo.endCursor) {
throw new CliError(
"Linear reported more cycles but returned no pagination cursor",
{ suggestion: "Retry the command." },
)
}
after = pageInfo.endCursor
} while (pageInfo.hasNextPage)

spinner?.stop()

const sortedCycles = [...cycles].sort((a, b) =>
b.startsAt.localeCompare(a.startsAt)
)

if (json) {
console.log(JSON.stringify({ nodes: sortedCycles, pageInfo }, null, 2))
return
}

if (sortedCycles.length === 0) {
console.log("No cycles found for this team.")
return
}

const { columns } = Deno.stdout.isTerminal()
? Deno.consoleSize()
: { columns: 120 }
Expand Down
14 changes: 12 additions & 2 deletions src/commands/cycle/cycle-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ const GetCycleDetails = gql(`
type
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
Expand All @@ -56,7 +60,8 @@ export const viewCommand = new Command()
.alias("v")
.arguments("<cycleRef:string>")
.option("--team <team:string>", "Team key (defaults to current team)")
.action(async ({ team }, cycleRef) => {
.option("-j, --json", "Output as JSON")
.action(async ({ team, json }, cycleRef) => {
try {
const teamKey = team || getTeamKey()
if (!teamKey) {
Expand All @@ -73,7 +78,7 @@ export const viewCommand = new Command()
const cycleId = await getCycleIdByNameOrNumber(cycleRef, teamId)

const { Spinner } = await import("@std/cli/unstable-spinner")
const showSpinner = shouldShowSpinner()
const showSpinner = !json && shouldShowSpinner()
const spinner = showSpinner ? new Spinner() : null
spinner?.start()

Expand All @@ -86,6 +91,11 @@ export const viewCommand = new Command()
throw new NotFoundError("Cycle", cycleRef)
}

if (json) {
console.log(JSON.stringify(cycle, null, 2))
return
}

const lines: string[] = []

const title = cycle.name || `Cycle ${cycle.number}`
Expand Down
Loading
Loading