import{existsSync as S}from"fs";import{fileURLToPath as v}from"url";import{dirname as E,resolve as O}from"path";var w=Object.freeze({SENTRY:"sentry",JIRA:"jira",GITHUB:"github",GITLAB:"gitlab",SLACK:"slack",LARK:"lark",OPENAI_BILLING:"openai_billing",ANTHROPIC_BILLING:"anthropic_billing",CURSOR_ADMIN:"cursor_admin",NOTION:"notion",GOOGLE:"google",PLANE:"plane",LINEAR:"linear",FIGMA:"figma",HUBSPOT:"hubspot",OPEN_DESIGN:"open_design",LINKEDIN_PERSONAL:"linkedin_personal",LINKEDIN_BUSINESS:"linkedin_business",DISCORD:"discord"}),x=Object.freeze({sentry:{id:"sentry",name:"Sentry",connectPath:"/integrations?provider=sentry"},jira:{id:"jira",name:"Jira",connectPath:"/integrations?provider=jira"},github:{id:"github",name:"GitHub",connectPath:"/integrations?provider=github"},gitlab:{id:"gitlab",name:"GitLab",connectPath:"/integrations?provider=gitlab"},slack:{id:"slack",name:"Slack",connectPath:"/integrations?provider=slack"},lark:{id:"lark",name:"Lark",connectPath:"/integrations?provider=lark"},openai_billing:{id:"openai_billing",name:"OpenAI Admin",connectPath:"/integrations?provider=openai_billing"},anthropic_billing:{id:"anthropic_billing",name:"Anthropic Admin",connectPath:"/integrations?provider=anthropic_billing"},cursor_admin:{id:"cursor_admin",name:"Cursor Admin",connectPath:"/integrations?provider=cursor_admin"},notion:{id:"notion",name:"Notion",connectPath:"/integrations?provider=notion"},google:{id:"google",name:"Google Docs",connectPath:"/integrations?provider=google"},plane:{id:"plane",name:"Plane",connectPath:"/integrations?provider=plane"},linear:{id:"linear",name:"Linear",connectPath:"/integrations?provider=linear"},figma:{id:"figma",name:"Figma",connectPath:"/integrations?provider=figma"},hubspot:{id:"hubspot",name:"HubSpot",connectPath:"/integrations?provider=hubspot"},open_design:{id:"open_design",name:"OpenDesign",connectPath:"/integrations?provider=open_design"},linkedin_personal:{id:"linkedin_personal",name:"LinkedIn (Personal)",connectPath:"/integrations?provider=linkedin_personal"},linkedin_business:{id:"linkedin_business",name:"LinkedIn (Business)",connectPath:"/integrations?provider=linkedin_business"},discord:{id:"discord",name:"Discord",connectPath:"/integrations?provider=discord"}});function T(){if(process.env.MCP_SKILL_PATH)return process.env.MCP_SKILL_PATH;let n=E(v(import.meta.url)),i=O(n,"..","bin","mcp-skill.mjs");return S(i)?i:null}var P=process.env.LINEAR_API_URL||"https://api.linear.app/graphql";function R(){if(process.env.LINEAR_OAUTH_TOKEN)return`Bearer ${process.env.LINEAR_OAUTH_TOKEN}`;let n=process.env.LINEAR_API_KEY;if(!n)throw new Error("Linear is not connected: set LINEAR_API_KEY (personal API key) or LINEAR_OAUTH_TOKEN.");return n}async function f(n,i={}){let e=await fetch(P,{method:"POST",headers:{Authorization:R(),"Content-Type":"application/json"},body:JSON.stringify({query:n,variables:i})});if(!e.ok){let s=await e.text().catch(()=>"");throw new Error(`Linear API ${e.status}: ${s.slice(0,300)}`)}let t=await e.json().catch(()=>null);if(!t)throw new Error("Linear API returned a non-JSON body");if(Array.isArray(t.errors)&&t.errors.length){let s=t.errors.map(a=>a?.message||String(a)).join("; ");throw new Error(`Linear GraphQL error: ${s.slice(0,300)}`)}return t.data}function I(n){return String(n||"").toLowerCase().replace(/\s+/g,"").replace(/[()\-_:："'`]/g,"")}function $(n,i){let e=I(n),t=I(i);if(!e||!t)return 0;if(e===t)return 1;if(e.length===1||t.length===1)return e===t?1:0;let s=d=>{let u=new Map;for(let y=0;y<d.length-1;y++){let g=d.slice(y,y+2);u.set(g,(u.get(g)||0)+1)}return u},a=s(e),r=s(t),o=0,l=0,m=0;for(let d of a.values())l+=d;for(let d of r.values())m+=d;for(let[d,u]of a.entries())o+=Math.min(u,r.get(d)||0);return 2*o/Math.max(1,l+m)}function C(n,i){let e=Array.isArray(n)?n:[];if(!e.length)return{state:null,strategy:"no-states"};let t=I(i);if(!t)return{state:null,strategy:"no-target"};let s=e.find(o=>I(o.name)===t);if(s)return{state:s,strategy:"exact"};let a={backlog:["backlog"],unstarted:["todo","unstarted","open"],started:["inprogress","started","doing","wip"],completed:["done","completed","closed","resolved","fixed"],canceled:["canceled","cancelled","wontfix","won'tfix"],triage:["triage"]};for(let[o,l]of Object.entries(a)){if(!l.some(d=>I(d)===t))continue;let m=e.find(d=>d.type===o);if(m)return{state:m,strategy:"type-alias"}}let r=e.map(o=>({s:o,score:$(i,o.name)})).sort((o,l)=>l.score-o.score);return r[0]&&r[0].score>=.5?{state:r[0].s,strategy:"fuzzy"}:{state:null,strategy:"no-match"}}var G=`
  id
  identifier
  number
  title
  description
  url
  priority
  createdAt
  updatedAt
  state { id name type color }
  assignee { id name displayName email }
  labels { nodes { id name color } }
  team { id key name }
`,_={id:"linear",serverName:"linear",allowedTools:["mcp__linear__*"],requiresIntegration:w.LINEAR,envKeys:["LINEAR_API_KEY","LINEAR_OAUTH_TOKEN"],description:"Linear \u2014 issues, comments, workflow states (GraphQL API key)",promptFragment:`## Linear (connected)
You have direct access to the user's Linear workspace (GraphQL API). Tools:

### Discovery
- linear_list_teams: List teams (id, key, name) \u2014 needed to scope states/issues
- linear_list_states: List a team's workflow states (id, name, type). Linear states are PER-TEAM; there is no global status list.
- linear_list_labels: List labels (optionally scoped to a team)

### Issues
- linear_list_issues: List/poll issues filtered by team / state / label / assignee / updatedAfter cursor (for polling candidates)
- linear_get_issue: Get one issue by identifier (e.g. ENG-123) or id \u2014 title, description, state, labels, assignee, url
- linear_get_comments: Get an issue's comments (newest first)
- linear_add_comment: Add a comment to an issue
- linear_update_state: Move an issue to a different workflow state. Pass the issue + a target state NAME; the tool resolves it to the team's matching state id (exact -> type-alias -> fuzzy). There is no "transition" in Linear \u2014 you just set the state.
- linear_link_attachment: Attach a URL (e.g. a GitHub PR) to an issue via Linear's native attachments. Use this for PR links; fall back to linear_add_comment if it fails.

### Notes
- Always resolve a team first when you need states or want to create/move issues by state name \u2014 states only make sense within their team.
- Issue identifier (ENG-123) and internal id (uuid) are both accepted by get/update tools.`,resolve(){let n={};for(let e of this.envKeys)process.env[e]&&(n[e]=process.env[e]);process.env.LINEAR_API_URL&&(n.LINEAR_API_URL=process.env.LINEAR_API_URL);let i=T();return i?{type:"stdio",command:"node",args:[i,"../dist/linear.js","linearSkill"],env:n,description:this.description,alwaysLoad:!0}:{command:null,args:[],env:n,description:this.description}},async handleToolCall(n,i){try{switch(n){case"linear_list_teams":{let t=(await f(`
            query Teams($first: Int) {
              teams(first: $first) {
                nodes { id key name description }
              }
            }
          `,{first:i?.limit||50}))?.teams?.nodes||[];return JSON.stringify({count:t.length,teams:t})}case"linear_list_states":{let{teamId:e,teamKey:t}=i||{},s=e;if(!s&&t&&(s=await N(t)),s){let l=(await f(`
              query States($teamId: String!) {
                team(id: $teamId) {
                  id key name
                  states { nodes { id name type color position } }
                }
              }
            `,{teamId:s}))?.team,m=(l?.states?.nodes||[]).slice().sort((d,u)=>(d.position||0)-(u.position||0));return JSON.stringify({team:l?{id:l.id,key:l.key,name:l.name}:null,count:m.length,states:m})}let r=(await f(`
            query AllStates($first: Int) {
              workflowStates(first: $first) {
                nodes { id name type color team { id key name } }
              }
            }
          `,{first:i?.limit||200}))?.workflowStates?.nodes||[];return JSON.stringify({scope:"workspace",count:r.length,states:r})}case"linear_list_labels":{let{teamId:e}=i||{},s=(await f(`
            query Labels($first: Int, $filter: IssueLabelFilter) {
              issueLabels(first: $first, filter: $filter) {
                nodes { id name color team { id key } }
              }
            }
          `,{first:i?.limit||100,filter:e?{team:{id:{eq:e}}}:void 0}))?.issueLabels?.nodes||[];return JSON.stringify({count:s.length,labels:s})}case"linear_list_issues":{let{teamId:e,teamKey:t,stateId:s,stateName:a,label:r,assigneeId:o,updatedAfter:l,limit:m}=i||{},d={},u=e;!u&&t&&(u=await N(t)),u&&(d.team={id:{eq:u}}),s?d.state={id:{eq:s}}:a&&(d.state={name:{eqIgnoreCase:a}}),r&&(d.labels={name:{eqIgnoreCase:r}}),o&&(d.assignee={id:{eq:o}}),l&&(d.updatedAt={gt:l});let g=((await f(`
            query Issues($first: Int, $filter: IssueFilter, $orderBy: PaginationOrderBy) {
              issues(first: $first, filter: $filter, orderBy: $orderBy) {
                nodes {
                  id identifier number title url priority createdAt updatedAt
                  state { id name type }
                  assignee { id displayName }
                  labels { nodes { name } }
                  team { id key }
                }
              }
            }
          `,{first:m||30,filter:Object.keys(d).length?d:void 0,orderBy:"updatedAt"}))?.issues?.nodes||[]).map(c=>({id:c.id,identifier:c.identifier,number:c.number,title:c.title,url:c.url,priority:c.priority,state:c.state?.name,stateType:c.state?.type,assignee:c.assignee?.displayName||null,labels:(c.labels?.nodes||[]).map(p=>p.name),team:c.team?.key,createdAt:c.createdAt,updatedAt:c.updatedAt}));return JSON.stringify({count:g.length,issues:g})}case"linear_get_issue":{let e=i?.issueId||i?.identifier||i?.issueKey;if(!e)return JSON.stringify({error:"issueId or identifier is required"});let t=await h(e);return JSON.stringify(t?{id:t.id,identifier:t.identifier,number:t.number,title:t.title,description:t.description||"",url:t.url,priority:t.priority,state:t.state?.name,stateId:t.state?.id,stateType:t.state?.type,assignee:t.assignee?.displayName||t.assignee?.name||null,assigneeId:t.assignee?.id||null,labels:(t.labels?.nodes||[]).map(s=>s.name),team:t.team?{id:t.team.id,key:t.team.key,name:t.team.name}:null,createdAt:t.createdAt,updatedAt:t.updatedAt}:{error:`Issue not found: ${e}`})}case"linear_get_comments":{let e=i?.issueId||i?.identifier||i?.issueKey;if(!e)return JSON.stringify({error:"issueId or identifier is required"});let t=await h(e,`
            id identifier
            comments(first: ${Number(i?.limit)||50}) {
              nodes { id body createdAt updatedAt user { id name displayName } }
            }
          `);if(!t)return JSON.stringify({error:`Issue not found: ${e}`});let s=(t.comments?.nodes||[]).map(a=>({id:a.id,author:a.user?.displayName||a.user?.name||"Unknown",body:a.body||"",createdAt:a.createdAt,updatedAt:a.updatedAt})).sort((a,r)=>String(r.createdAt).localeCompare(String(a.createdAt)));return JSON.stringify({count:s.length,issue:t.identifier,comments:s})}case"linear_add_comment":{let e=i?.issueId||i?.identifier||i?.issueKey,t=i?.body;if(!e||!t)return JSON.stringify({error:"issueId/identifier and body are required"});let s=await h(e,"id identifier");if(!s)return JSON.stringify({error:`Issue not found: ${e}`});let r=(await f(`
            mutation AddComment($input: CommentCreateInput!) {
              commentCreate(input: $input) {
                success
                comment { id url createdAt }
              }
            }
          `,{input:{issueId:s.id,body:t}}))?.commentCreate;return JSON.stringify({ok:!!r?.success,commentId:r?.comment?.id,url:r?.comment?.url})}case"linear_update_state":{let e=i?.issueId||i?.identifier||i?.issueKey,{stateId:t,stateName:s,toStatus:a,status:r}=i||{};if(!e)return JSON.stringify({error:"issueId or identifier is required"});let o=await h(e,`
            id identifier
            state { id name type }
            team { id key states { nodes { id name type position } } }
          `);if(!o)return JSON.stringify({error:`Issue not found: ${e}`});let l=t,m=t?{strategy:"explicit-id"}:null;if(!l){let y=String(s||a||r||"").trim(),g=(o.team?.states?.nodes||[]).slice().sort((p,L)=>(p.position||0)-(L.position||0));if(!y)return JSON.stringify({ok:!1,error:"stateId or stateName/toStatus is required",issue:o.identifier,availableStates:g.map(p=>({id:p.id,name:p.name,type:p.type}))});let c=C(g,y);if(!c.state)return JSON.stringify({ok:!1,error:`No workflow state matches "${y}" in team ${o.team?.key}`,issue:o.identifier,availableStates:g.map(p=>({id:p.id,name:p.name,type:p.type}))});l=c.state.id,m={strategy:c.strategy,matchedName:c.state.name}}let u=(await f(`
            mutation MoveIssue($id: String!, $input: IssueUpdateInput!) {
              issueUpdate(id: $id, input: $input) {
                success
                issue { id identifier state { id name type } }
              }
            }
          `,{id:o.id,input:{stateId:l}}))?.issueUpdate;return JSON.stringify({ok:!!u?.success,issue:u?.issue?.identifier||o.identifier,stateAfter:u?.issue?.state?.name||null,stateTypeAfter:u?.issue?.state?.type||null,resolution:m})}case"linear_link_attachment":{let e=i?.issueId||i?.identifier||i?.issueKey,{url:t,title:s,subtitle:a}=i||{};if(!e||!t)return JSON.stringify({error:"issueId/identifier and url are required"});let r=await h(e,"id identifier");if(!r)return JSON.stringify({error:`Issue not found: ${e}`});let l=(await f(`
            mutation LinkAttachment($input: AttachmentCreateInput!) {
              attachmentCreate(input: $input) {
                success
                attachment { id url title }
              }
            }
          `,{input:{issueId:r.id,url:t,title:s||t,subtitle:a||void 0}}))?.attachmentCreate;return JSON.stringify({ok:!!l?.success,attachmentId:l?.attachment?.id,url:l?.attachment?.url})}default:return JSON.stringify({error:`Unknown tool: ${n}`})}}catch(e){return JSON.stringify({error:e.message})}},tools:[{name:"linear_list_teams",description:"List Linear teams (id, key, name). Needed to scope workflow states and issue queries.",input_schema:{type:"object",properties:{limit:{type:"number",description:"Max teams (default: 50)"}}}},{name:"linear_list_states",description:"List a team's workflow states (id, name, type: backlog|unstarted|started|completed|canceled|triage). Linear states are PER-TEAM. Omit team to list all states across the workspace.",input_schema:{type:"object",properties:{teamId:{type:"string",description:"Team uuid"},teamKey:{type:"string",description:"Team key (e.g. ENG); resolved to an id if teamId omitted"},limit:{type:"number",description:"Max states when listing workspace-wide (default: 200)"}}}},{name:"linear_list_labels",description:"List issue labels, optionally scoped to a team.",input_schema:{type:"object",properties:{teamId:{type:"string",description:"Optional team uuid to scope labels"},limit:{type:"number",description:"Max labels (default: 100)"}}}},{name:"linear_list_issues",description:"List/poll Linear issues filtered by team, state, label, assignee, and an updatedAfter cursor. Returns newest-updated first.",input_schema:{type:"object",properties:{teamId:{type:"string",description:"Team uuid"},teamKey:{type:"string",description:"Team key (e.g. ENG); resolved if teamId omitted"},stateId:{type:"string",description:"Filter by workflow state uuid"},stateName:{type:"string",description:"Filter by state name (case-insensitive)"},label:{type:"string",description:"Filter by label name (case-insensitive)"},assigneeId:{type:"string",description:"Filter by assignee uuid"},updatedAfter:{type:"string",description:"ISO-8601 timestamp; only issues updated after this (polling cursor)"},limit:{type:"number",description:"Max issues (default: 30)"}}}},{name:"linear_get_issue",description:"Get a single Linear issue by identifier (e.g. ENG-123) or internal uuid \u2014 title, description, state, labels, assignee, url.",input_schema:{type:"object",properties:{identifier:{type:"string",description:"Issue identifier, e.g. ENG-123"},issueId:{type:"string",description:"Internal issue uuid (alternative to identifier)"}}}},{name:"linear_get_comments",description:"Get comments on a Linear issue (newest first).",input_schema:{type:"object",properties:{identifier:{type:"string",description:"Issue identifier, e.g. ENG-123"},issueId:{type:"string",description:"Internal issue uuid (alternative to identifier)"},limit:{type:"number",description:"Max comments (default: 50)"}}}},{name:"linear_add_comment",description:"Add a comment to a Linear issue (markdown supported).",input_schema:{type:"object",properties:{identifier:{type:"string",description:"Issue identifier, e.g. ENG-123"},issueId:{type:"string",description:"Internal issue uuid (alternative to identifier)"},body:{type:"string",description:"Comment body (markdown)"}},required:["body"]}},{name:"linear_update_state",description:"Move a Linear issue to a different workflow state. Pass a state NAME (toStatus/stateName) and the tool resolves it to the issue's team's matching state id (exact -> type-alias -> fuzzy), or pass stateId directly. Linear has no transitions \u2014 this sets the state.",input_schema:{type:"object",properties:{identifier:{type:"string",description:"Issue identifier, e.g. ENG-123"},issueId:{type:"string",description:"Internal issue uuid (alternative to identifier)"},stateId:{type:"string",description:"Target workflow state uuid (skips name resolution)"},stateName:{type:"string",description:'Target state name (e.g. "In Progress", "Done")'},toStatus:{type:"string",description:"Alias for stateName"}}}},{name:"linear_link_attachment",description:"Attach a URL (e.g. a GitHub PR) to a Linear issue via native attachments. Use this for PR links; fall back to linear_add_comment if it fails.",input_schema:{type:"object",properties:{identifier:{type:"string",description:"Issue identifier, e.g. ENG-123"},issueId:{type:"string",description:"Internal issue uuid (alternative to identifier)"},url:{type:"string",description:"The URL to attach (e.g. a PR link)"},title:{type:"string",description:"Attachment title (defaults to the URL)"},subtitle:{type:"string",description:"Optional attachment subtitle"}},required:["url"]}}]};async function N(n){return(await f(`
    query TeamByKey($filter: TeamFilter) {
      teams(first: 1, filter: $filter) { nodes { id key } }
    }
  `,{filter:{key:{eq:n}}}))?.teams?.nodes?.[0]?.id||null}async function h(n,i=G){let e=String(n).trim(),t=/^([A-Za-z][A-Za-z0-9]*)-(\d+)$/.exec(e);if(t){let a=t[1].toUpperCase(),r=Number(t[2]);return(await f(`
      query IssueByIdentifier($filter: IssueFilter) {
        issues(first: 1, filter: $filter) {
          nodes { ${i} }
        }
      }
    `,{filter:{number:{eq:r},team:{key:{eq:a}}}}))?.issues?.nodes?.[0]||null}return(await f(`
    query IssueById($id: String!) {
      issue(id: $id) { ${i} }
    }
  `,{id:e}))?.issue||null}var q={triage:"todo",backlog:"todo",unstarted:"todo",started:"in_progress",completed:"done",canceled:"done"},J=/\b(blocked|on[\s-]?hold|waiting|stuck)\b/i;function k(n){return n?n.name&&J.test(n.name)?"blocked":q[n.type]||"unknown":"unknown"}function b(n){let i=JSON.parse(n);if(i&&i.error)throw new Error(i.error);return i}function A(n){let i=n.state||null,e=n.stateType||null;return{id:String(n.id||n.identifier||""),key:n.identifier||String(n.id||""),title:n.title||"",body:n.description||"",state:i,stateCategory:k(i?{name:i,type:e}:null),assignee:n.assignee||null,url:n.url||null,_raw:n}}var K={id:"linear",toStateCategory:k,toNeutral:A,async listCandidates(n={}){let i=n.ctx||{},e={teamId:i.teamId,teamKey:i.teamKey,stateName:n.state,label:Array.isArray(n.labels)?n.labels[0]:n.labels,assigneeId:i.assigneeId,updatedAfter:n.updatedAfter,limit:n.limit};return(b(await _.handleToolCall("linear_list_issues",e)).issues||[]).map(A)},async getTicket(n){if(!n)throw new Error("key is required");let i=JSON.parse(await _.handleToolCall("linear_get_issue",{identifier:n}));return i.error?null:A(i)},async getComments(n){if(!n)throw new Error("key is required");return(b(await _.handleToolCall("linear_get_comments",{identifier:n})).comments||[]).map(e=>({id:String(e.id),author:e.author||"Unknown",body:e.body||"",createdAt:e.createdAt||null,updatedAt:e.updatedAt||null,_raw:e}))},async addComment(n,i){if(!n||!i)throw new Error("key and body are required");let e=b(await _.handleToolCall("linear_add_comment",{identifier:n,body:i}));return{ok:!!e.ok,id:e.commentId||null}},async transition(n,i){if(!n)throw new Error("key is required");let e=JSON.parse(await _.handleToolCall("linear_update_state",{identifier:n,toStatus:i}));if(!e.ok)return{ok:!1,error:e.error||"state update failed",_raw:e};let t=e.stateAfter||null;return{ok:!0,stateAfter:t,stateCategoryAfter:k(t?{name:t,type:e.stateTypeAfter}:null),_raw:e}},async linkPullRequest(n,i,e){if(!n||!i)throw new Error("key and prUrl are required");try{if(b(await _.handleToolCall("linear_link_attachment",{identifier:n,url:i,title:e||i})).ok)return{ok:!0,via:"attachment"};throw new Error("attachmentCreate returned ok:false")}catch(t){let s=`${e?`${e}: `:"Linked PR: "}${i}`;return{ok:!!b(await _.handleToolCall("linear_add_comment",{identifier:n,body:s})).ok,via:"comment",error:String(t?.message||t)}}}},z=K;export{z as default,K as linearAdapter};
