Class: Agentilda::Linear::API

Inherits:
Object
  • Object
show all
Defined in:
lib/agentilda/linear/api.rb

Overview

Linear's GraphQL API, wrapped as thinly as GitHub wraps gh, and for the same reason: it is a seam. Every example in the suite injects a transport here, so nothing in the tests reaches Linear.

Only the dozen operations an import needs are here. This is not a client library, and it should not grow into one — anything Linear can do that .plans cannot express does not belong in a tool whose whole premise is that the folder is the source of truth.

Constant Summary collapse

ENDPOINT =
"https://api.linear.app/graphql"
TOKEN_VARIABLE =

The environment variable holding a personal API key.

"LINEAR_API_KEY"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(token: nil, transport: nil, endpoint: ENDPOINT) ⇒ API

Returns a new instance of API.

Parameters:

  • token (String) (defaults to: nil)

    a Linear personal API key

  • transport (#call, nil) (defaults to: nil)

    (query, variables) -> Hash, for tests

  • endpoint (String) (defaults to: ENDPOINT)

Raises:



29
30
31
32
33
34
35
36
37
# File 'lib/agentilda/linear/api.rb', line 29

def initialize(token: nil, transport: nil, endpoint: ENDPOINT)
  @token = token
  @transport = transport
  @endpoint = endpoint
  return if transport || (token && !token.empty?)

  raise Error, "no Linear token. Set #{TOKEN_VARIABLE}, or use the MCP transport:\n  " \
               "agentilda linear import <TEAM> -p <PROJECT> --format json"
end

Class Method Details

.token_from_envString?

Returns the token, from the environment.

Returns:

  • (String, nil)

    the token, from the environment



24
# File 'lib/agentilda/linear/api.rb', line 24

def self.token_from_env = ENV[TOKEN_VARIABLE].to_s.strip.then { |t| t.empty? ? nil : t }

Instance Method Details

#create_issue(input) ⇒ Hash

Returns {id:, identifier:, url:}.

Parameters:

  • input (Hash)

    {teamId:, projectId:, title:, description:, stateId:, labelIds:}

Returns:

  • (Hash)

    {id:, identifier:, url:}



67
# File 'lib/agentilda/linear/api.rb', line 67

def create_issue(input) = unwrap(query(ISSUE_CREATE, input:), "issueCreate", "issue")

#create_label(name, team_id) ⇒ Hash

Returns {id:, name:}.

Parameters:

  • name (String)
  • team_id (String)

Returns:

  • (Hash)

    {id:, name:}



77
78
79
# File 'lib/agentilda/linear/api.rb', line 77

def create_label(name, team_id)
  unwrap(query(LABEL_CREATE, input: {name:, teamId: team_id}), "issueLabelCreate", "issueLabel")
end

#create_project(input) ⇒ Hash

Returns {id:, name:, url:}.

Parameters:

  • input (Hash)

    {name:, teamIds:, content:, icon:}

Returns:

  • (Hash)

    {id:, name:, url:}



58
# File 'lib/agentilda/linear/api.rb', line 58

def create_project(input) = unwrap(query(PROJECT_CREATE, input:), "projectCreate", "project")

This method returns an undefined value.

Parameters:

  • issue_id (String)
  • url (String)
  • title (String)


85
86
87
# File 'lib/agentilda/linear/api.rb', line 85

def link(issue_id:, url:, title:)
  query(ATTACHMENT_CREATE, input: {issueId: issue_id, url:, title:})
end

#projects(team_id) ⇒ Array<Hash>

Returns {id:, name:, url:}.

Parameters:

  • team_id (String)

Returns:

  • (Array<Hash>)

    {id:, name:, url:}



54
# File 'lib/agentilda/linear/api.rb', line 54

def projects(team_id) = query(PROJECTS, teamId: team_id).dig("team", "projects", "nodes").to_a

#query(query, **variables) ⇒ Hash

Returns the data object.

Parameters:

  • query (String)

    a GraphQL document

  • variables (Hash)

Returns:

  • (Hash)

    the data object

Raises:



93
94
95
96
97
98
99
# File 'lib/agentilda/linear/api.rb', line 93

def query(query, **variables)
  body = transport.call(query, variables)
  errors = body["errors"]
  raise Error, "Linear rejected the request: #{describe(errors)}" if errors&.any?

  body.fetch("data") { raise Error, "Linear returned no data" }
end

#team(key) ⇒ Hash

The team, its workflow states and its labels, in one round trip.

Parameters:

  • key (String)

    the team key, e.g. "TAX"

Returns:

  • (Hash)

    {id:, name:, key:, states: [...], labels: [...]}

Raises:



44
45
46
47
48
49
50
# File 'lib/agentilda/linear/api.rb', line 44

def team(key)
  node = query(TEAM, key: key.to_s.upcase).dig("teams", "nodes")&.first
  raise Error, "no Linear team has the key #{key.to_s.upcase}" unless node

  {id: node["id"], name: node["name"], key: node["key"],
   states: node.dig("states", "nodes").to_a, labels: node.dig("labels", "nodes").to_a}
end

#update_issue(id, input) ⇒ Hash

Parameters:

  • id (String)

    a UUID or an identifier such as "TAX-41"

  • input (Hash)

Returns:

  • (Hash)


72
# File 'lib/agentilda/linear/api.rb', line 72

def update_issue(id, input) = unwrap(query(ISSUE_UPDATE, id:, input:), "issueUpdate", "issue")

#update_project(id, input) ⇒ Hash

Parameters:

  • id (String)
  • input (Hash)

Returns:

  • (Hash)


63
# File 'lib/agentilda/linear/api.rb', line 63

def update_project(id, input) = unwrap(query(PROJECT_UPDATE, id:, input:), "projectUpdate", "project")