Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c15ed30
feat(linear): add Linear Issue Reader and Writer tools with types
sriram2k4 May 28, 2025
cf5498c
chore(tools): register Linear tools in global tool registry
sriram2k4 May 28, 2025
481d0c6
feat(icons): add LinearIcon for Linear block
sriram2k4 May 28, 2025
bd0079c
feat(blocks): register Linear block in global block registry
sriram2k4 May 28, 2025
15077eb
feat(linear): implement OAuth integration for Linear block
sriram2k4 May 28, 2025
fe058dc
feat(linear): add dynamic team and project selectors for Linear block
sriram2k4 May 28, 2025
3b83c63
feat(linear): add backend API endpoints for teams and projects
sriram2k4 May 28, 2025
e9a6194
feat(linear): update UI components for Linear selectors and modal
sriram2k4 May 28, 2025
23abcee
refactor(linear): update create/read issue tools and types
sriram2k4 May 28, 2025
40d9641
chore(linear): update block config for Linear integration
sriram2k4 May 28, 2025
84c2d6b
fix(auth): update auth and oauth logic for Linear
sriram2k4 May 28, 2025
7dd3b35
minor fix
sriram2k4 May 28, 2025
d744f7c
improvement[linear]: require teamId and projectId for all tools and t…
sriram2k4 May 28, 2025
8db78a1
style[lint]: fix code style and lint errors
sriram2k4 May 28, 2025
b370609
chore(linear): install @linear/sdk package
sriram2k4 May 28, 2025
7483381
fix[linear]: address greptile-apps feedback for type safety and error…
sriram2k4 May 29, 2025
211d5b1
fix[linear]: handle teams API response errors
sriram2k4 May 29, 2025
7c282b3
modified icon, added docs
waleedlatif1 May 30, 2025
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
104 changes: 104 additions & 0 deletions apps/docs/content/docs/tools/linear.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
title: Linear
description: Read and create issues in Linear
---

import { BlockInfoCard } from "@/components/ui/block-info-card"

<BlockInfoCard
type="linear"
color="#5E6AD2"
icon={true}
iconSvg={`<svg className="block-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 100 100"><path fill="#fff" d="M1.22541 61.5228c-.2225-.9485.90748-1.5459 1.59638-.857L39.3342 97.1782c.6889.6889.0915 1.8189-.857 1.5964C20.0515 94.4522 5.54779 79.9485 1.22541 61.5228ZM.00189135 46.8891c-.01764375.2833.08887215.5599.28957165.7606L52.3503 99.7085c.2007.2007.4773.3075.7606.2896 2.3692-.1476 4.6938-.46 6.9624-.9259.7645-.157 1.0301-1.0963.4782-1.6481L2.57595 39.4485c-.55186-.5519-1.49117-.2863-1.648174.4782-.465915 2.2686-.77832 4.5932-.92588465 6.9624ZM4.21093 29.7054c-.16649.3738-.08169.8106.20765 1.1l64.77602 64.776c.2894.2894.7262.3742 1.1.2077 1.7861-.7956 3.5171-1.6927 5.1855-2.684.5521-.328.6373-1.0867.1832-1.5407L8.43566 24.3367c-.45409-.4541-1.21271-.3689-1.54074.1832-.99132 1.6684-1.88843 3.3994-2.68399 5.1855ZM12.6587 18.074c-.3701-.3701-.393-.9637-.0443-1.3541C21.7795 6.45931 35.1114 0 49.9519 0 77.5927 0 100 22.4073 100 50.0481c0 14.8405-6.4593 28.1724-16.7199 37.3375-.3903.3487-.984.3258-1.3542-.0443L12.6587 18.074Z"/></svg>`}
/>

{/* MANUAL-CONTENT-START:intro */}
[Linear](https://linear.app) is a leading project management and issue tracking platform that helps teams plan, track, and manage their work effectively. As a modern project management tool, Linear has become increasingly popular among software development teams and project management professionals for its streamlined interface and powerful features.

Linear provides a comprehensive set of tools for managing complex projects through its flexible and customizable workflow system. With its robust API and integration capabilities, Linear enables teams to streamline their development processes and maintain clear visibility of project progress.

Key features of Linear include:

- Agile Project Management: Support for Scrum and Kanban methodologies with customizable boards and workflows
- Issue Tracking: Sophisticated tracking system for bugs, stories, epics, and tasks with detailed reporting
- Workflow Automation: Powerful automation rules to streamline repetitive tasks and processes
- Advanced Search: Complex filtering and reporting capabilities for efficient issue management

In Sim Studio, the Linear integration allows your agents to seamlessly interact with your project management workflow. This creates opportunities for automated issue creation, updates, and tracking as part of your AI workflows. The integration enables agents to read existing issues and create new ones programmatically, facilitating automated project management tasks and ensuring that important information is properly tracked and documented. By connecting Sim Studio with Linear, you can build intelligent agents that maintain project visibility while automating routine project management tasks, enhancing team productivity and ensuring consistent project tracking.
{/* MANUAL-CONTENT-END */}


## Usage Instructions

Integrate with Linear to fetch, filter, and create issues directly from your workflow.



## Tools

### `linear_read_issues`

Fetch and filter issues from Linear

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `teamId` | string | Yes | Linear team ID |
| `projectId` | string | Yes | Linear project ID |

#### Output

| Parameter | Type |
| --------- | ---- |
| `issues` | string |
Comment on lines +50 to +54

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Output type for 'issues' should be array or object, not string, since it returns multiple issues

Suggested change
#### Output
| Parameter | Type |
| --------- | ---- |
| `issues` | string |
#### Output
| Parameter | Type |
| --------- | ---- |
| `issues` | array |


### `linear_create_issue`

Create a new issue in Linear

#### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `teamId` | string | Yes | Linear team ID |
| `projectId` | string | Yes | Linear project ID |
| `title` | string | Yes | Issue title |
| `description` | string | No | Issue description |

#### Output

| Parameter | Type |
| --------- | ---- |
| `issue` | string |
| `title` | string |
| `description` | string |
| `state` | string |
| `teamId` | string |
| `projectId` | string |
Comment on lines +71 to +78

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Output parameters should be properly typed as object properties rather than separate string fields

Suggested change
| Parameter | Type |
| --------- | ---- |
| `issue` | string |
| `title` | string |
| `description` | string |
| `state` | string |
| `teamId` | string |
| `projectId` | string |
| Parameter | Type |
| --------- | ---- |
| `issue` | object |
`title` | string |
`description` | string |
`state` | string |
`teamId` | string |
`projectId` | string |




## Block Configuration

### Input

| Parameter | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `operation` | string | Yes | Operation |



### Outputs

| Output | Type | Description |
| ------ | ---- | ----------- |
| `response` | object | Output from response |
| ↳ `issues` | json | issues of the response |
| ↳ `issue` | json | issue of the response |


## Notes

- Category: `tools`
- Type: `linear`
1 change: 1 addition & 0 deletions apps/docs/content/docs/tools/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"image_generator",
"jina",
"jira",
"linear",
"linkup",
"mem0",
"memory",
Expand Down
63 changes: 63 additions & 0 deletions apps/sim/app/api/tools/linear/projects/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { Project } from '@linear/sdk'
import { LinearClient } from '@linear/sdk'
import { NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console-logger'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'

export const dynamic = 'force-dynamic'

const logger = createLogger('LinearProjects')

export async function POST(request: Request) {
try {
const session = await getSession()
const body = await request.json()
const { credential, teamId, workflowId } = body
Comment on lines +15 to +16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Add request body validation using zod or similar to ensure type safety


if (!credential || !teamId) {
logger.error('Missing credential or teamId in request')
return NextResponse.json({ error: 'Credential and teamId are required' }, { status: 400 })
}

const userId = session?.user?.id || ''
if (!userId) {
logger.error('No user ID found in session')
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}

const accessToken = await refreshAccessTokenIfNeeded(credential, userId, workflowId)
if (!accessToken) {
logger.error('Failed to get access token', { credentialId: credential, userId })
return NextResponse.json(
{
error: 'Could not retrieve access token',
authRequired: true,
},
{ status: 401 }
)
}

const linearClient = new LinearClient({ accessToken })
let projects = []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Initialize projects as const with type annotation for better type safety


const team = await linearClient.team(teamId)
const projectsResult = await team.projects()
projects = projectsResult.nodes.map((project: Project) => ({
id: project.id,
name: project.name,
}))

if (projects.length === 0) {
logger.info('No projects found for team', { teamId })
}

return NextResponse.json({ projects })
} catch (error) {
logger.error('Error processing Linear projects request:', error)
return NextResponse.json(
{ error: 'Failed to retrieve Linear projects', details: (error as Error).message },
{ status: 500 }
)
Comment on lines +56 to +61

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider handling specific Linear API errors separately from generic errors

}
}
56 changes: 56 additions & 0 deletions apps/sim/app/api/tools/linear/teams/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Team } from '@linear/sdk'
import { LinearClient } from '@linear/sdk'
import { NextResponse } from 'next/server'
import { getSession } from '@/lib/auth'
import { createLogger } from '@/lib/logs/console-logger'
import { refreshAccessTokenIfNeeded } from '@/app/api/auth/oauth/utils'

export const dynamic = 'force-dynamic'

const logger = createLogger('LinearTeams')

export async function POST(request: Request) {
try {
const session = await getSession()
const body = await request.json()
const { credential, workflowId } = body

if (!credential) {
logger.error('Missing credential in request')
return NextResponse.json({ error: 'Credential is required' }, { status: 400 })
}

const userId = session?.user?.id || ''
Comment thread
waleedlatif1 marked this conversation as resolved.
if (!userId) {
logger.error('No user ID found in session')
return NextResponse.json({ error: 'Authentication required' }, { status: 401 })
}

const accessToken = await refreshAccessTokenIfNeeded(credential, userId, workflowId)
if (!accessToken) {
logger.error('Failed to get access token', { credentialId: credential, userId })
return NextResponse.json(
{
error: 'Could not retrieve access token',
authRequired: true,
},
{ status: 401 }
)
}

const linearClient = new LinearClient({ accessToken })
const teamsResult = await linearClient.teams()
const teams = teamsResult.nodes.map((team: Team) => ({
id: team.id,
name: team.name,
}))

return NextResponse.json({ teams })
} catch (error) {
logger.error('Error processing Linear teams request:', error)
return NextResponse.json(
{ error: 'Failed to retrieve Linear teams', details: (error as Error).message },
Comment thread
waleedlatif1 marked this conversation as resolved.
{ status: 500 }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
'messages.read': 'Read your Discord messages',
guilds: 'Read your Discord guilds',
'guilds.members.read': 'Read your Discord guild members',
read: 'Read access to your Linear workspace',
write: 'Write access to your Linear workspace',
}

// Convert OAuth scope to user-friendly description
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { useEffect, useState } from 'react'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'

export interface LinearProjectInfo {
id: string
name: string
}

interface LinearProjectSelectorProps {
value: string
onChange: (projectId: string, projectInfo?: LinearProjectInfo) => void
credential: string
teamId: string
label?: string
disabled?: boolean
}

export function LinearProjectSelector({
value,
onChange,
credential,
teamId,
label = 'Select Linear project',
disabled = false,
}: LinearProjectSelectorProps) {
const [projects, setProjects] = useState<LinearProjectInfo[]>([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)

useEffect(() => {
if (!credential || !teamId) return
const controller = new AbortController()
setLoading(true)
fetch('/api/tools/linear/projects', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ credential, teamId }),
signal: controller.signal,
})
Comment thread
waleedlatif1 marked this conversation as resolved.
.then(async (res) => {
if (!res.ok) {
const errorText = await res.text()
throw new Error(`HTTP error! status: ${res.status} - ${errorText}`)
}
return res.json()
})
.then((data) => {
if (data.error) {
setError(data.error)
setProjects([])
} else {
setProjects(data.projects)
}
})
.catch((err) => {
if (err.name === 'AbortError') return
setError(err.message)
setProjects([])
})
.finally(() => setLoading(false))
return () => controller.abort()
}, [credential, teamId])

return (
<Select
value={value}
onValueChange={(projectId) => {
const projectInfo = projects.find((p) => p.id === projectId)
onChange(projectId, projectInfo)
}}
Comment thread
waleedlatif1 marked this conversation as resolved.
disabled={disabled || loading || !credential || !teamId}
>
<SelectTrigger className='w-full'>
<SelectValue placeholder={loading ? 'Loading projects...' : label} />
</SelectTrigger>
<SelectContent>
{projects.map((project) => (
<SelectItem key={project.id} value={project.id}>
{project.name}
</SelectItem>
))}
{error && <div className='px-2 py-1 text-red-500'>{error}</div>}
</SelectContent>
</Select>
)
}
Loading