diff --git a/README.md b/README.md
index dbda35c1..05e5de39 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,8 @@ linear issue url # prints the Linear.app URL for the issue
linear issue pr # creates a GitHub PR with issue details via `gh pr create`
linear issue list # list your issues in a table view (supports -s/--state and --sort)
linear issue list --project "My Project" --milestone "Phase 1" # filter by milestone
+linear issue list --created-after 2024-01-01 # issues created after a date
+linear issue list --updated-after 2024-01-01 # issues updated after a date
linear issue list -w # open issue list in web browser
linear issue list -a # open issue list in Linear.app
linear issue start # create/switch to issue branch and mark as started
diff --git a/docs/cast-issue-create.svg b/docs/cast-issue-create.svg
index 9ff98288..1ccc142e 100644
--- a/docs/cast-issue-create.svg
+++ b/docs/cast-issue-create.svg
@@ -4,15 +4,15 @@
width="1300"
height="607.88"
>
-
+
+
+
diff --git a/docs/cast-issue-start.svg b/docs/cast-issue-start.svg
index 0c9d81a5..edc08372 100644
--- a/docs/cast-issue-start.svg
+++ b/docs/cast-issue-start.svg
@@ -4,15 +4,15 @@
width="1300"
height="607.88"
>
-
+
-
+ ❯
+
+ ❯
+ ❯linearissue
+ ❯
+ linear
+ issue
+ start?Selectstart
+
+
+ ?
+ Select
+ anissuean
+ issue
+ tostart:❯---to
+ start:
+
+
+ ❯
+ ---
+ CLI-14:AllowCLI-14:
+ Allow
+ assigningpeopleassigning
+ people
+ otherthanother
+ than
+ yourselfwhenyourself
+ when
+ creatingissuescreating
+ issues
+
+
+ ---CLI-20:---
+ CLI-20:
+ AllowskippingAllow
+ skipping
+ confirmationwhendeletingconfirmation
+ when
+ deleting
+ issuesissues
+
+
+ ---CLI-16:---
+ CLI-16:
+ SupportlistingSupport
+ listing
+ projectsprojects
+
+
+ ---CLI-14:---
+ CLI-14:
+ AllowassigningAllow
+ assigning
+ peopleotherpeople
+ other
+ thanyourselfthan
+ yourself
+ whencreatingwhen
+ creating
+ issues❯---issues
+
+
+ ❯
+ ---
+ CLI-20:AllowCLI-20:
+ Allow
+ skippingconfirmationwhendeletingskipping
+ confirmation
+ when
+ deleting
+ issues❯---issues
+
+
+ ❯
+ ---
+ CLI-16:SupportCLI-16:
+ Support
+ listingprojects?Selectlisting
+ projects
+
+
+ ?
+ Select
+ anissuean
+ issue
+ tostart:to
+ start:
+ ›---›
+ ---
+ CLI-20:AllowCLI-20:
+ Allow
+ skippingconfirmationwhendeletingskipping
+ confirmation
+ when
+ deleting
+ issues⠋?Branchissues
+
+ ⠋
+
+ ?
+ Branch
+ cli-20-allow-skipping-confirmation-when-deleting-issuescli-20-allow-skipping-confirmation-when-deleting-issues
+ alreadyexists.already
+ exists.
+ WhatwouldWhat
+ would
+ youlikeyou
+ like
+ todo?Switchtoto
+ do?
+
+
+ Switch
+ to
+ existingbranch❯Createexisting
+ branch
+
+
+ ❯
+ Create
+ newbranchnew
+ branch
+ withsuffix?Branchwith
+ suffix
+
+
+ ?
+ Branch
+ cli-20-allow-skipping-confirmation-when-deleting-issuescli-20-allow-skipping-confirmation-when-deleting-issues
+ alreadyexists.already
+ exists.
+ WhatwouldWhat
+ would
+ youlikeyou
+ like
+ todo?to
+ do?
+ ›Create›
+ Create
+ newbranchnew
+ branch
+ withsuffix✓Createdwi
+
+
+ th
+ suffix
+
+
+ ✓
+ Created
+ andswitchedand
+ switched
+ tobranchto
+ branch
+ 'cli-20-allow-skipping-confirmation-when-deleting-issues-1''cli-20-allow-skipping-confirmation-when-deleting-issues-1'
+
+ ✓Issuestate
+ ✓
+ Issue
+ state
+ updatedtoupdated
+ to
+ 'InProgress''In
+ Progress'
+
+
+ ❯git❯
+ git
+ branch--show-currentbranch
+ --show-current
+
+
+ ❯git❯
+ git
+ branch--show-currentbranch
+ --show-current
+
+
+ cli-20-allow-skipping-confirmation-when-deleting-issues-1cli-20-allow-skipping-confirmation-when-deleting-issues-1
+
+
+
+
+
+
+
+
+
+
+ >
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+ ❯❯
+ l
-
-
+
+
+
+ ❯l
-
- ❯
+ l
+
+
+
+
+ ❯l❯
+ l
+ lmcmdlm
+ cmd
+ "gitshow"git
+ show
+ currentbranch"
-
- current
+ branch"
+
+
+
+
+ ❯linear
-
- ❯
+ linear
+
+
+
+
+ ❯linear
-
- ❯
+ linear
+
+
+
+
+ ❯linear❯
+ linear
+ issuelistissue
+ list
+ -a
-
-
+
+
+
+ ❯linear❯
+ linear
+ issuei
+ ssue
+ list-a
-
- list
+ -a
+
+
+
+
+ ❯linear❯
+ linear
+ issuei
+ ssue
+ list-a
-
- list
+ -a
+
+
+
+
+ ❯linear❯
+ linear
+ i
-
-
+
+
+
+ ❯linear❯
+ linear
+ issue
-
-
+
+
+
+ ❯linear❯
+ linear
+ issue
-
-
+
+
+
+ ❯linear❯
+ linear
+ issuelistissue
+ list
+ -a
-
-
+
+
+
+ ❯linear❯
+ linear
+ issuesissue
+ s
+ tart
-
-
+
+
+
+ ❯linear❯
+ linear
+ issuestissue
+ st
+ art
-
-
+
+
+
+ ❯linear❯
+ linear
+ issuestaissue
+ sta
+ rt
-
-
+
+
+
+ ❯linear❯
+ linear
+ issuestarissue
+ star
+ t
-
-
+
+
+
diff --git a/docs/usage.md b/docs/usage.md
index d98fefc6..1fe1f734 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -53,6 +53,16 @@ linear issue list --unassigned
linear issue list --all-assignees
```
+filter by date:
+
+```bash
+# List issues created after a specific date
+linear issue list --created-after 2024-01-01
+
+# List issues updated after a specific date
+linear issue list --updated-after 2024-01-01
+```
+
other options:
```bash
diff --git a/skills/linear-cli/references/issue.md b/skills/linear-cli/references/issue.md
index 16f8bc03..113a9727 100644
--- a/skills/linear-cli/references/issue.md
+++ b/skills/linear-cli/references/issue.md
@@ -82,6 +82,8 @@ Options:
--cycle - Filter by cycle name, number, or 'active'
--milestone - Filter by project milestone name (requires --project)
--limit - Maximum number of issues to fetch (default: 50, use 0 for unlimited) (Default: 50)
+ --created-after - Filter issues created after this date (ISO 8601 or YYYY-MM-DD)
+ --updated-after - Filter issues updated after this date (ISO 8601 or YYYY-MM-DD)
-w, --web - Open in web browser
-a, --app - Open in Linear.app
--no-pager - Disable automatic paging for long output
diff --git a/src/commands/issue/issue-list.ts b/src/commands/issue/issue-list.ts
index 841bfcc5..d7fe329c 100644
--- a/src/commands/issue/issue-list.ts
+++ b/src/commands/issue/issue-list.ts
@@ -97,6 +97,14 @@ export const listCommand = new Command()
default: 50,
},
)
+ .option(
+ "--created-after ",
+ "Filter issues created after this date (ISO 8601 or YYYY-MM-DD)",
+ )
+ .option(
+ "--updated-after ",
+ "Filter issues updated after this date (ISO 8601 or YYYY-MM-DD)",
+ )
.option("-w, --web", "Open in web browser")
.option("-a, --app", "Open in Linear.app")
.option("--no-pager", "Disable automatic paging for long output")
@@ -117,6 +125,8 @@ export const listCommand = new Command()
milestone,
limit,
pager,
+ createdAfter,
+ updatedAfter,
},
) => {
const usePager = pager !== false
@@ -221,6 +231,8 @@ export const listCommand = new Command()
sort,
cycleId,
milestoneId,
+ createdAfter,
+ updatedAfter,
)
spinner?.stop()
const issues = result.issues?.nodes || []
diff --git a/src/utils/linear.ts b/src/utils/linear.ts
index cd4d2c0c..86e0070c 100644
--- a/src/utils/linear.ts
+++ b/src/utils/linear.ts
@@ -13,6 +13,34 @@ import { getGraphQLClient } from "./graphql.ts"
import { getCurrentIssueFromVcs } from "./vcs.ts"
import { NotFoundError, ValidationError } from "./errors.ts"
+/**
+ * Validate and parse a date string in ISO 8601 format (YYYY-MM-DD or full ISO 8601).
+ * Rejects permissive date strings that `new Date()` would accept (e.g. "1", "March 2024").
+ */
+export function parseDateFilter(value: string, flagName: string): string {
+ const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(T[\d:.]+Z?([+-]\d{2}:?\d{2})?)?$/
+ if (!ISO_DATE_RE.test(value)) {
+ throw new ValidationError(
+ `Invalid date format for ${flagName}: "${value}"`,
+ {
+ suggestion:
+ "Use YYYY-MM-DD or ISO 8601 format (e.g. 2024-01-15 or 2024-01-15T09:00:00Z).",
+ },
+ )
+ }
+ const parsed = new Date(value)
+ if (isNaN(parsed.getTime())) {
+ throw new ValidationError(
+ `Invalid date for ${flagName}: "${value}"`,
+ {
+ suggestion:
+ "Use YYYY-MM-DD or ISO 8601 format (e.g. 2024-01-15 or 2024-01-15T09:00:00Z).",
+ },
+ )
+ }
+ return parsed.toISOString()
+}
+
function isValidLinearIdentifier(id: string): boolean {
return /^[a-zA-Z0-9]+-[1-9][0-9]*$/i.test(id)
}
@@ -422,6 +450,8 @@ export async function fetchIssuesForState(
sortParam?: "manual" | "priority",
cycleId?: string,
milestoneId?: string,
+ createdAfter?: string,
+ updatedAfter?: string,
) {
const sort = sortParam ??
getOption("issue_sort") as "manual" | "priority" | undefined
@@ -469,6 +499,14 @@ export async function fetchIssuesForState(
filter.projectMilestone = { id: { eq: milestoneId } }
}
+ if (createdAfter) {
+ filter.createdAt = { gte: parseDateFilter(createdAfter, "--created-after") }
+ }
+
+ if (updatedAfter) {
+ filter.updatedAt = { gte: parseDateFilter(updatedAfter, "--updated-after") }
+ }
+
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 68b208a7..de24447b 100644
--- a/test/commands/issue/__snapshots__/issue-list.test.ts.snap
+++ b/test/commands/issue/__snapshots__/issue-list.test.ts.snap
@@ -24,6 +24,8 @@ Options:
--cycle - Filter by cycle name, number, or 'active'
--milestone - Filter by project milestone name (requires --project)
--limit - Maximum number of issues to fetch (default: 50, use 0 for unlimited) (Default: \\x1b[33m50\\x1b[39m)
+ --created-after - Filter issues created after this date (ISO 8601 or YYYY-MM-DD)
+ --updated-after - Filter issues updated after this date (ISO 8601 or YYYY-MM-DD)
-w, --web - Open in web browser
-a, --app - Open in Linear.app
--no-pager - Disable automatic paging for long output
diff --git a/test/commands/issue/issue-list.test.ts b/test/commands/issue/issue-list.test.ts
index 277bae42..aeee7df2 100644
--- a/test/commands/issue/issue-list.test.ts
+++ b/test/commands/issue/issue-list.test.ts
@@ -1,5 +1,11 @@
import { snapshotTest } from "@cliffy/testing"
+import { assertEquals, assertRejects, assertThrows } from "@std/assert"
import { listCommand } from "../../../src/commands/issue/issue-list.ts"
+import {
+ fetchIssuesForState,
+ parseDateFilter,
+} from "../../../src/utils/linear.ts"
+import { ValidationError } from "../../../src/utils/errors.ts"
import { commonDenoArgs } from "../../utils/test-helpers.ts"
// Test help output
@@ -13,3 +19,93 @@ await snapshotTest({
await listCommand.parse()
},
})
+
+// Test invalid --created-after date
+Deno.test("fetchIssuesForState - rejects invalid createdAfter date", async () => {
+ await assertRejects(
+ () =>
+ fetchIssuesForState(
+ "TEST",
+ ["unstarted"],
+ undefined,
+ false,
+ false,
+ 50,
+ undefined,
+ "manual",
+ undefined,
+ undefined,
+ "not-a-date",
+ undefined,
+ ),
+ ValidationError,
+ 'Invalid date format for --created-after: "not-a-date"',
+ )
+})
+
+// Test invalid --updated-after date
+Deno.test("fetchIssuesForState - rejects invalid updatedAfter date", async () => {
+ await assertRejects(
+ () =>
+ fetchIssuesForState(
+ "TEST",
+ ["unstarted"],
+ undefined,
+ false,
+ false,
+ 50,
+ undefined,
+ "manual",
+ undefined,
+ undefined,
+ undefined,
+ "xyz-bad-date",
+ ),
+ ValidationError,
+ 'Invalid date format for --updated-after: "xyz-bad-date"',
+ )
+})
+
+// parseDateFilter unit tests
+
+Deno.test("parseDateFilter - accepts YYYY-MM-DD format", () => {
+ const result = parseDateFilter("2024-01-15", "--created-after")
+ assertEquals(result, new Date("2024-01-15").toISOString())
+})
+
+Deno.test("parseDateFilter - accepts full ISO 8601 with time and Z", () => {
+ const result = parseDateFilter("2024-01-15T09:00:00Z", "--created-after")
+ assertEquals(result, "2024-01-15T09:00:00.000Z")
+})
+
+Deno.test("parseDateFilter - accepts ISO 8601 with timezone offset", () => {
+ const result = parseDateFilter(
+ "2024-01-15T09:00:00+05:30",
+ "--created-after",
+ )
+ assertEquals(result, new Date("2024-01-15T09:00:00+05:30").toISOString())
+})
+
+Deno.test('parseDateFilter - rejects permissive date string "1"', () => {
+ assertThrows(
+ () => parseDateFilter("1", "--created-after"),
+ ValidationError,
+ 'Invalid date format for --created-after: "1"',
+ )
+})
+
+Deno.test('parseDateFilter - rejects permissive date string "March 2024"', () => {
+ assertThrows(
+ () => parseDateFilter("March 2024", "--updated-after"),
+ ValidationError,
+ 'Invalid date format for --updated-after: "March 2024"',
+ )
+})
+
+Deno.test('parseDateFilter - rejects permissive date string "Jan 1"', () => {
+ assertThrows(
+ () => parseDateFilter("Jan 1", "--created-after"),
+ ValidationError,
+ 'Invalid date format for --created-after: "Jan 1"',
+ )
+})