diff --git a/cmd/bd/linear.go b/cmd/bd/linear.go index 73951e96..d7b102d8 100644 --- a/cmd/bd/linear.go +++ b/cmd/bd/linear.go @@ -26,6 +26,7 @@ var linearCmd = &cobra.Command{ Configuration: bd config set linear.api_key "YOUR_API_KEY" bd config set linear.team_id "TEAM_ID" + bd config set linear.project_id "PROJECT_ID" # Optional: sync only this project Environment variables (alternative to config): LINEAR_API_KEY - Linear API key @@ -586,6 +587,10 @@ func getLinearClient(ctx context.Context) (*linear.Client, error) { if endpoint, _ := store.GetConfig(ctx, "linear.api_endpoint"); endpoint != "" { client = client.WithEndpoint(endpoint) } + // Filter to specific project if configured + if projectID, _ := store.GetConfig(ctx, "linear.project_id"); projectID != "" { + client = client.WithProjectID(projectID) + } } return client, nil diff --git a/internal/linear/client.go b/internal/linear/client.go index 529cb53a..9316e498 100644 --- a/internal/linear/client.go +++ b/internal/linear/client.go @@ -92,6 +92,7 @@ func (c *Client) WithEndpoint(endpoint string) *Client { return &Client{ APIKey: c.APIKey, TeamID: c.TeamID, + ProjectID: c.ProjectID, Endpoint: endpoint, HTTPClient: c.HTTPClient, } @@ -103,11 +104,24 @@ func (c *Client) WithHTTPClient(httpClient *http.Client) *Client { return &Client{ APIKey: c.APIKey, TeamID: c.TeamID, + ProjectID: c.ProjectID, Endpoint: c.Endpoint, HTTPClient: httpClient, } } +// WithProjectID returns a new client configured to filter issues by the specified project. +// When set, FetchIssues and FetchIssuesSince will only return issues belonging to this project. +func (c *Client) WithProjectID(projectID string) *Client { + return &Client{ + APIKey: c.APIKey, + TeamID: c.TeamID, + ProjectID: projectID, + Endpoint: c.Endpoint, + HTTPClient: c.HTTPClient, + } +} + // Execute sends a GraphQL request to the Linear API. // Handles rate limiting with exponential backoff. func (c *Client) Execute(ctx context.Context, req *GraphQLRequest) (json.RawMessage, error) { @@ -178,6 +192,7 @@ func (c *Client) Execute(ctx context.Context, req *GraphQLRequest) (json.RawMess // FetchIssues retrieves issues from Linear with optional filtering by state. // state can be: "open" (unstarted/started), "closed" (completed/canceled), or "all". +// If ProjectID is set on the client, only issues from that project are returned. func (c *Client) FetchIssues(ctx context.Context, state string) ([]Issue, error) { var allIssues []Issue var cursor string @@ -189,6 +204,16 @@ func (c *Client) FetchIssues(ctx context.Context, state string) ([]Issue, error) }, }, } + + // Add project filter if configured + if c.ProjectID != "" { + filter["project"] = map[string]interface{}{ + "id": map[string]interface{}{ + "eq": c.ProjectID, + }, + } + } + switch state { case "open": filter["state"] = map[string]interface{}{ @@ -242,6 +267,7 @@ func (c *Client) FetchIssues(ctx context.Context, state string) ([]Issue, error) // FetchIssuesSince retrieves issues from Linear that have been updated since the given time. // This enables incremental sync by only fetching issues modified after the last sync. // The state parameter can be: "open", "closed", or "all". +// If ProjectID is set on the client, only issues from that project are returned. func (c *Client) FetchIssuesSince(ctx context.Context, state string, since time.Time) ([]Issue, error) { var allIssues []Issue var cursor string @@ -260,6 +286,15 @@ func (c *Client) FetchIssuesSince(ctx context.Context, state string, since time. }, } + // Add project filter if configured + if c.ProjectID != "" { + filter["project"] = map[string]interface{}{ + "id": map[string]interface{}{ + "eq": c.ProjectID, + }, + } + } + // Add state filter if specified switch state { case "open": diff --git a/internal/linear/types.go b/internal/linear/types.go index cdac28f9..abdeed7e 100644 --- a/internal/linear/types.go +++ b/internal/linear/types.go @@ -32,6 +32,7 @@ const ( type Client struct { APIKey string TeamID string + ProjectID string // Optional: filter issues to a specific project Endpoint string // GraphQL endpoint URL (defaults to DefaultAPIEndpoint) HTTPClient *http.Client }