From a18ce11a11184cfcf48d847fcd04cb5cbc14fe5d Mon Sep 17 00:00:00 2001 From: Paymahn Moghadasian Date: Thu, 15 Jan 2026 09:45:58 -0600 Subject: [PATCH] feat: add --project filter to issue list command Adds the ability to filter issues by project name when listing issues. If the project name does not match exactly, it fuzzy-searches and prompts the user to select from matching projects. Co-Authored-By: Claude Opus 4.5 --- src/commands/issue/issue-list.ts | 27 ++++++++++++++++++- src/utils/linear.ts | 5 ++++ .../__snapshots__/issue-list.test.ts.snap | 1 + 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/commands/issue/issue-list.ts b/src/commands/issue/issue-list.ts index d6b8f2d..8202885 100644 --- a/src/commands/issue/issue-list.ts +++ b/src/commands/issue/issue-list.ts @@ -8,7 +8,13 @@ import { padDisplay, truncateText, } from "../../utils/display.ts" -import { fetchIssuesForState, getTeamKey } from "../../utils/linear.ts" +import { + fetchIssuesForState, + getProjectIdByName, + getProjectOptionsByName, + getTeamKey, + selectOption, +} from "../../utils/linear.ts" import { openTeamAssigneeView } from "../../utils/actions.ts" import { pipeToUserPager, shouldUsePager } from "../../utils/pager.ts" import { header, muted } from "../../utils/styling.ts" @@ -63,6 +69,10 @@ export const listCommand = new Command() "--team ", "Team to list issues for (if not your default team)", ) + .option( + "--project ", + "Filter by project name", + ) .option( "--limit ", "Maximum number of issues to fetch (default: 50, use 0 for unlimited)", @@ -85,6 +95,7 @@ export const listCommand = new Command() app, allStates, team, + project, limit, pager, }, @@ -135,6 +146,19 @@ export const listCommand = new Command() Deno.exit(1) } + let projectId: string | undefined + if (project != null) { + projectId = await getProjectIdByName(project) + if (projectId == null) { + const projectOptions = await getProjectOptionsByName(project) + if (Object.keys(projectOptions).length === 0) { + console.error(`No projects found matching: ${project}`) + Deno.exit(1) + } + projectId = await selectOption("Project", project, projectOptions) + } + } + const { Spinner } = await import("@std/cli/unstable-spinner") const showSpinner = Deno.stdout.isTerminal() const spinner = showSpinner ? new Spinner() : null @@ -148,6 +172,7 @@ export const listCommand = new Command() unassigned, allAssignees, limit === 0 ? undefined : limit, + projectId, ) spinner?.stop() const issues = result.issues?.nodes || [] diff --git a/src/utils/linear.ts b/src/utils/linear.ts index afd9ade..2cd2648 100644 --- a/src/utils/linear.ts +++ b/src/utils/linear.ts @@ -360,6 +360,7 @@ export async function fetchIssuesForState( unassigned = false, allAssignees = false, limit?: number, + projectId?: string, ) { const sort = getOption("issue_sort") as "manual" | "priority" | undefined if (!sort) { @@ -391,6 +392,10 @@ export async function fetchIssuesForState( filter.assignee = { isMe: { eq: true } } } + if (projectId) { + filter.project = { id: { eq: projectId } } + } + const query = gql(/* GraphQL */ ` query GetIssuesForState($sort: [IssueSortInput!], $filter: IssueFilter!, $first: Int, $after: String) { issues(filter: $filter, sort: $sort, first: $first, after: $after) { diff --git a/test/commands/issue/__snapshots__/issue-list.test.ts.snap b/test/commands/issue/__snapshots__/issue-list.test.ts.snap index 429a115..ac66625 100644 --- a/test/commands/issue/__snapshots__/issue-list.test.ts.snap +++ b/test/commands/issue/__snapshots__/issue-list.test.ts.snap @@ -20,6 +20,7 @@ Options: -U, --unassigned - Show only unassigned issues --sort - Sort order (can also be set via LINEAR_ISSUE_SORT) (Values: \\x1b[32m"manual"\\x1b[39m, \\x1b[32m"priority"\\x1b[39m) --team - Team to list issues for (if not your default team) + --project - Filter by project name --limit - Maximum number of issues to fetch (default: 50, use 0 for unlimited) (Default: \\x1b[33m50\\x1b[39m) -w, --web - Open in web browser -a, --app - Open in Linear.app