linear-rb 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d86a47411e5f3e9ab46945cded06b74c305fee7552ac4770f6c93023dddb3dd
4
- data.tar.gz: 8b8aa4987546fc77b34b7addab90d6181aa6b13b8d5fa1f7a32ce96d5c2814ef
3
+ metadata.gz: f3e9c972a48cc50b483ca7b2b0add3aaaff8d6db08c421964e9f8cad348f8f95
4
+ data.tar.gz: dd874783401a2a38cc14d1febddaf2271f2ff75201797b5a84b9c67177244913
5
5
  SHA512:
6
- metadata.gz: 89a95415e7aa7cc0d9f605eeb37d1444bd604fcb321cb182cb13b5f60410c35b95ff39836574462c56b4682c3a117fa7fcbeef8e0cfcad709fed91ab052b5e29
7
- data.tar.gz: 7888ac607e72ae1480d9364bf0034acbe789e7bd6e7f4fa047b2e12cbce7dd626e0920d08c3d0aa1cc104aa92d0c2fb57632b46d0fb38506cb47b5bd471bc740
6
+ metadata.gz: 46ef93d456c996f850892082b45bd753e51874b451d6d528d4d5a8d8b1deebcf0fa49d956baf7a6ae0bc626beb8128b2f9219c79ff1c29e30fc78a9a5cce0e27
7
+ data.tar.gz: 003a97d70f58e93d3ccb0a2b6152e7936c9dd2abefc277d9ab20bdc646eec8e919c7b649b2875cb18b1dedf54994fc005e02fa2182845648c068b0a068559682
data/bin/linear CHANGED
@@ -13,11 +13,9 @@ def show_usage
13
13
  linear mine Show issues assigned to you
14
14
  linear teams List all teams
15
15
  linear projects List all projects
16
+ linear create [OPTIONS] Create a new issue
16
17
  linear comment ISSUE_ID COMMENT Add a comment to an issue
17
- linear update ISSUE_ID [OPTIONS] Update issue (at least one option required)
18
- --state STATE Update issue state
19
- --title TITLE Update issue title
20
- --description DESCRIPTION Update issue description
18
+ linear update ISSUE_ID STATE Update issue state
21
19
 
22
20
  Issues Filters (all optional, can be combined):
23
21
  --query TEXT Search by title text
@@ -25,6 +23,14 @@ def show_usage
25
23
  --state STATE Filter by state name (case-insensitive)
26
24
  --team TEAM_KEY Filter by team key
27
25
 
26
+ Create Issue Options:
27
+ --team TEAM_KEY Team key (required)
28
+ --title "TITLE" Issue title (required)
29
+ --description "DESC" Issue description (optional)
30
+ --priority PRIORITY Priority: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low (optional)
31
+ --state "STATE" State name (optional)
32
+ --project PROJECT_ID Project ID (optional)
33
+
28
34
  Examples:
29
35
  # View a specific issue
30
36
  linear issue ENG-123
@@ -49,16 +55,16 @@ def show_usage
49
55
  # Your assigned issues
50
56
  linear mine
51
57
 
58
+ # Create a new issue
59
+ linear create --team ENG --title "Fix login bug"
60
+ linear create --team ENG --title "Add dark mode" --description "Users want dark mode" --priority 2
61
+ linear create --team ENG --title "Refactor API" --state "In Progress" --priority 3
62
+
52
63
  # Other commands
53
64
  linear teams
54
65
  linear projects
55
- linear comment DEV-85 "This is done"
56
-
57
- # Update issue
58
- linear update DEV-85 --state "Done"
59
- linear update DEV-85 --title "New title"
60
- linear update DEV-85 --description "Updated description"
61
- linear update DEV-85 --state "In Progress" --title "Working on it" --description "Started implementation"
66
+ linear comment FAT-85 "This is done"
67
+ linear update FAT-85 "Done"
62
68
 
63
69
  Configuration:
64
70
  Set the LINEAR_API_KEY environment variable with your API key from:
@@ -143,27 +149,41 @@ when 'comment'
143
149
 
144
150
  when 'update'
145
151
  issue_id = ARGV.shift
152
+ state_name = ARGV.shift
146
153
 
147
- if issue_id.nil? || issue_id.empty?
148
- puts "Error: issue ID required"
149
- puts "Usage: linear update ISSUE_ID [--state STATE] [--title TITLE] [--description DESCRIPTION]"
154
+ if issue_id.nil? || issue_id.empty? || state_name.nil? || state_name.empty?
155
+ puts "Error: issue ID and state name required"
156
+ puts "Usage: linear update ISSUE_ID \"State Name\""
157
+ exit 1
158
+ end
159
+
160
+ begin
161
+ Linear::Commands.update_issue_state(issue_id, state_name)
162
+ rescue => e
163
+ puts "Error: #{e.message}"
150
164
  exit 1
151
165
  end
152
166
 
167
+ when 'create'
153
168
  options = {}
154
169
  OptionParser.new do |opts|
155
- opts.on("--state STATE", "Update issue state") { |v| options[:state] = v }
156
- opts.on("--title TITLE", "Update issue title") { |v| options[:title] = v }
157
- opts.on("--description DESCRIPTION", "Update issue description") { |v| options[:description] = v }
170
+ opts.on("--team TEAM", "Team key (required)") { |v| options[:team] = v }
171
+ opts.on("--title TITLE", "Issue title (required)") { |v| options[:title] = v }
172
+ opts.on("--description DESC", "Issue description") { |v| options[:description] = v }
173
+ opts.on("--priority PRIORITY", "Priority (0-4)") { |v| options[:priority] = v }
174
+ opts.on("--state STATE", "State name") { |v| options[:state] = v }
175
+ opts.on("--assignee EMAIL", "Assignee email") { |v| options[:assignee] = v }
176
+ opts.on("--project PROJECT", "Project ID") { |v| options[:project] = v }
158
177
  end.parse!
159
178
 
179
+ unless options[:team] && options[:title]
180
+ puts "Error: --team and --title are required"
181
+ puts "Usage: linear create --team TEAM --title \"Issue title\" [OPTIONS]"
182
+ exit 1
183
+ end
184
+
160
185
  begin
161
- Linear::Commands.update_issue(
162
- issue_id,
163
- state: options[:state],
164
- title: options[:title],
165
- description: options[:description]
166
- )
186
+ Linear::Commands.create_issue(options)
167
187
  rescue => e
168
188
  puts "Error: #{e.message}"
169
189
  exit 1
@@ -0,0 +1,29 @@
1
+ module Linear
2
+ module Commands
3
+ module AddComment
4
+ extend self
5
+
6
+ def add_comment(issue_id, body, client: Client.new)
7
+ # First get the issue to get its internal ID
8
+ issue_result = client.query(Queries::ISSUE, { id: issue_id })
9
+ issue = issue_result.dig("data", "issue")
10
+
11
+ unless issue
12
+ puts "Error: Issue not found: #{issue_id}"
13
+ return
14
+ end
15
+
16
+ result = client.query(Queries::CREATE_COMMENT, {
17
+ issueId: issue['id'],
18
+ body: body
19
+ })
20
+
21
+ if result.dig("data", "commentCreate", "success")
22
+ puts "Comment added to #{issue_id}"
23
+ else
24
+ puts "Error: Failed to add comment"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,67 @@
1
+ module Linear
2
+ module Commands
3
+ module CreateIssue
4
+ extend self
5
+
6
+ def create_issue(options, client: Client.new)
7
+ # Get team ID from team key
8
+ teams_result = client.query(Queries::TEAMS)
9
+ teams = teams_result.dig("data", "teams", "nodes") || []
10
+ team = teams.find { |t| t['key'].upcase == options[:team].upcase }
11
+
12
+ unless team
13
+ puts "Error: Team '#{options[:team]}' not found. Available teams:"
14
+ teams.each { |t| puts " #{t['key']} - #{t['name']}" }
15
+ return
16
+ end
17
+
18
+ variables = {
19
+ teamId: team['id'],
20
+ title: options[:title]
21
+ }
22
+
23
+ # Add optional description
24
+ variables[:description] = options[:description] if options[:description]
25
+
26
+ # Add optional project
27
+ variables[:projectId] = options[:project] if options[:project]
28
+
29
+ # Handle priority (convert string to integer if needed)
30
+ if options[:priority]
31
+ variables[:priority] = options[:priority].to_i
32
+ end
33
+
34
+ # Handle state (need to look up state ID from name)
35
+ if options[:state]
36
+ states_result = client.query(Queries::WORKFLOW_STATES, { teamId: team['id'] })
37
+ states = states_result.dig("data", "team", "states", "nodes") || []
38
+ target_state = states.find { |s| s['name'].downcase == options[:state].downcase }
39
+
40
+ if target_state
41
+ variables[:stateId] = target_state['id']
42
+ else
43
+ puts "Warning: State '#{options[:state]}' not found, using default"
44
+ end
45
+ end
46
+
47
+ # Handle assignee (need to look up user ID from email)
48
+ if options[:assignee]
49
+ # Would need a new USER_BY_EMAIL query
50
+ puts "Warning: Assignee lookup not yet implemented"
51
+ end
52
+
53
+ # Create the issue
54
+ result = client.query(Queries::CREATE_ISSUE, variables)
55
+
56
+ if result.dig("data", "issueCreate", "success")
57
+ issue = result.dig("data", "issueCreate", "issue")
58
+ puts "Created issue: #{issue['identifier']}"
59
+ puts "Title: #{issue['title']}"
60
+ puts "URL: #{issue['url']}"
61
+ else
62
+ puts "Error: Failed to create issue"
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,18 @@
1
+ module Linear
2
+ module Commands
3
+ module FetchIssue
4
+ extend self
5
+
6
+ def fetch_issue(issue_id, client: Client.new)
7
+ result = client.query(Queries::ISSUE, { id: issue_id })
8
+
9
+ issue = result.dig("data", "issue")
10
+ if issue
11
+ Formatters.display_issue(issue)
12
+ else
13
+ puts "Issue not found: #{issue_id}"
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,24 @@
1
+ module Linear
2
+ module Commands
3
+ module ListIssues
4
+ extend self
5
+
6
+ def list_issues(options = {}, client: Client.new)
7
+ filter = {}
8
+ filter[:title] = { contains: options[:query] } if options[:query]
9
+ filter[:project] = { id: { eq: options[:project] } } if options[:project]
10
+ filter[:state] = { name: { eqIgnoreCase: options[:state] } } if options[:state]
11
+ filter[:team] = { key: { eq: options[:team] } } if options[:team]
12
+
13
+ result = client.query(Queries::LIST_ISSUES, { filter: filter })
14
+
15
+ issues = result.dig("data", "issues", "nodes") || []
16
+ if issues.empty?
17
+ puts "No issues found"
18
+ else
19
+ Formatters.display_issue_list(issues)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,18 @@
1
+ module Linear
2
+ module Commands
3
+ module ListProjects
4
+ extend self
5
+
6
+ def list_projects(client: Client.new)
7
+ result = client.query(Queries::PROJECTS)
8
+
9
+ projects = result.dig("data", "projects", "nodes") || []
10
+ if projects.empty?
11
+ puts "No projects found"
12
+ else
13
+ Formatters.display_project_list(projects)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ module Linear
2
+ module Commands
3
+ module ListTeams
4
+ extend self
5
+
6
+ def list_teams(client: Client.new)
7
+ result = client.query(Queries::TEAMS)
8
+
9
+ teams = result.dig("data", "teams", "nodes") || []
10
+ teams.each do |team|
11
+ puts "#{team['key'].ljust(10)} #{team['name']}"
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ module Linear
2
+ module Commands
3
+ module MyIssues
4
+ extend self
5
+
6
+ def my_issues(client: Client.new)
7
+ result = client.query(Queries::MY_ISSUES)
8
+
9
+ issues = result.dig("data", "viewer", "assignedIssues", "nodes") || []
10
+ if issues.empty?
11
+ puts "No issues assigned to you"
12
+ else
13
+ Formatters.display_issue_list(issues)
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,30 @@
1
+ module Linear
2
+ module Commands
3
+ module UpdateIssueDescription
4
+ extend self
5
+
6
+ def update_issue_description(issue_id, description, client: Client.new)
7
+ # Get the issue to get its internal ID
8
+ issue_result = client.query(Queries::ISSUE, { id: issue_id })
9
+ issue = issue_result.dig("data", "issue")
10
+
11
+ unless issue
12
+ puts "Error: Issue not found: #{issue_id}"
13
+ return
14
+ end
15
+
16
+ # Update the issue description
17
+ result = client.query(Queries::UPDATE_ISSUE, {
18
+ issueId: issue['id'],
19
+ description: description
20
+ })
21
+
22
+ if result.dig("data", "issueUpdate", "success")
23
+ puts "Updated #{issue_id} description"
24
+ else
25
+ puts "Error: Failed to update issue description"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,56 @@
1
+ module Linear
2
+ module Commands
3
+ module UpdateIssueState
4
+ extend self
5
+
6
+ def update_issue_state(issue_id, state_name, client: Client.new)
7
+ # Get the issue details including team
8
+ issue_result = client.query(Queries::ISSUE, { id: issue_id })
9
+ issue = issue_result.dig("data", "issue")
10
+
11
+ unless issue
12
+ puts "Error: Issue not found: #{issue_id}"
13
+ return
14
+ end
15
+
16
+ # Get team states - need to find team ID first
17
+ teams_result = client.query(Queries::TEAMS)
18
+ teams = teams_result.dig("data", "teams", "nodes") || []
19
+
20
+ # Find the team from the issue identifier prefix (e.g., "FAT" from "FAT-85")
21
+ team_key = issue_id.split('-').first
22
+ team = teams.find { |t| t['key'] == team_key }
23
+
24
+ unless team
25
+ puts "Error: Team not found for issue #{issue_id}"
26
+ return
27
+ end
28
+
29
+ # Get workflow states for the team
30
+ states_result = client.query(Queries::WORKFLOW_STATES, { teamId: team['id'] })
31
+ states = states_result.dig("data", "team", "states", "nodes") || []
32
+
33
+ # Find the state by name (case-insensitive)
34
+ target_state = states.find { |s| s['name'].downcase == state_name.downcase }
35
+
36
+ unless target_state
37
+ puts "Error: State '#{state_name}' not found. Available states:"
38
+ states.each { |s| puts " - #{s['name']}" }
39
+ return
40
+ end
41
+
42
+ # Update the issue
43
+ result = client.query(Queries::UPDATE_ISSUE, {
44
+ issueId: issue['id'],
45
+ stateId: target_state['id']
46
+ })
47
+
48
+ if result.dig("data", "issueUpdate", "success")
49
+ puts "Updated #{issue_id} to '#{target_state['name']}'"
50
+ else
51
+ puts "Error: Failed to update issue state"
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,212 +1,32 @@
1
+ require_relative "commands/fetch_issue"
2
+ require_relative "commands/list_issues"
3
+ require_relative "commands/my_issues"
4
+ require_relative "commands/list_teams"
5
+ require_relative "commands/list_projects"
6
+ require_relative "commands/add_comment"
7
+ require_relative "commands/update_issue_state"
8
+ require_relative "commands/update_issue_description"
9
+ require_relative "commands/create_issue"
10
+
1
11
  module Linear
2
12
  module Commands
3
13
  extend self
4
14
 
5
- def fetch_issue(issue_id, client: Client.new)
6
- result = client.query(Queries::ISSUE, { id: issue_id })
7
-
8
- issue = result.dig("data", "issue")
9
- if issue
10
- display_issue(issue)
11
- else
12
- puts "Issue not found: #{issue_id}"
13
- end
14
- end
15
-
16
- def list_issues(options = {}, client: Client.new)
17
- filter = {}
18
- filter[:title] = { contains: options[:query] } if options[:query]
19
- filter[:project] = { id: { eq: options[:project] } } if options[:project]
20
- filter[:state] = { name: { eqIgnoreCase: options[:state] } } if options[:state]
21
- filter[:team] = { key: { eq: options[:team] } } if options[:team]
22
-
23
- result = client.query(Queries::LIST_ISSUES, { filter: filter })
24
-
25
- issues = result.dig("data", "issues", "nodes") || []
26
- if issues.empty?
27
- puts "No issues found"
28
- else
29
- display_issue_list(issues)
30
- end
31
- end
32
-
33
- def my_issues(client: Client.new)
34
- result = client.query(Queries::MY_ISSUES)
35
-
36
- issues = result.dig("data", "viewer", "assignedIssues", "nodes") || []
37
- if issues.empty?
38
- puts "No issues assigned to you"
39
- else
40
- display_issue_list(issues)
41
- end
42
- end
43
-
44
- def list_teams(client: Client.new)
45
- result = client.query(Queries::TEAMS)
46
-
47
- teams = result.dig("data", "teams", "nodes") || []
48
- teams.each do |team|
49
- puts "#{team['key'].ljust(10)} #{team['name']}"
50
- end
51
- end
52
-
53
- def list_projects(client: Client.new)
54
- result = client.query(Queries::PROJECTS)
55
-
56
- projects = result.dig("data", "projects", "nodes") || []
57
- if projects.empty?
58
- puts "No projects found"
59
- else
60
- display_project_list(projects)
61
- end
62
- end
63
-
64
- def add_comment(issue_id, body, client: Client.new)
65
- # First get the issue to get its internal ID
66
- issue_result = client.query(Queries::ISSUE, { id: issue_id })
67
- issue = issue_result.dig("data", "issue")
68
-
69
- unless issue
70
- puts "Error: Issue not found: #{issue_id}"
71
- return
72
- end
73
-
74
- result = client.query(Queries::CREATE_COMMENT, {
75
- issueId: issue['id'],
76
- body: body
77
- })
78
-
79
- if result.dig("data", "commentCreate", "success")
80
- puts "Comment added to #{issue_id}"
81
- else
82
- puts "Error: Failed to add comment"
83
- end
84
- end
85
-
86
- def update_issue(issue_id, state: nil, title: nil, description: nil, client: Client.new)
87
- # 1. Validate at least one change provided
88
- if state.nil? && title.nil? && description.nil?
89
- puts "Error: At least one of --state, --title, or --description must be provided"
90
- return
91
- end
92
-
93
- # 2. Fetch issue to get internal UUID
94
- issue_result = client.query(Queries::ISSUE, { id: issue_id })
95
- issue = issue_result.dig("data", "issue")
96
-
97
- unless issue
98
- puts "Error: Issue not found: #{issue_id}"
99
- return
100
- end
101
-
102
- # 3. If state provided, resolve state ID (existing logic)
103
- state_id = nil
104
- target_state = nil
105
- if state
106
- team_key = issue_id.split('-').first
107
- teams_result = client.query(Queries::TEAMS)
108
- teams = teams_result.dig("data", "teams", "nodes") || []
109
- team = teams.find { |t| t['key'] == team_key }
110
-
111
- unless team
112
- puts "Error: Team not found for issue #{issue_id}"
113
- return
114
- end
115
-
116
- states_result = client.query(Queries::WORKFLOW_STATES, { teamId: team['id'] })
117
- states = states_result.dig("data", "team", "states", "nodes") || []
118
- target_state = states.find { |s| s['name'].downcase == state.downcase }
119
-
120
- unless target_state
121
- puts "Error: State '#{state}' not found. Available states:"
122
- states.each { |s| puts " - #{s['name']}" }
123
- return
124
- end
125
-
126
- state_id = target_state['id']
127
- end
128
-
129
- # 4. Build mutation parameters
130
- params = { issueId: issue['id'] }
131
- params[:stateId] = state_id if state_id
132
- params[:title] = title if title
133
- params[:description] = description if description
134
-
135
- # 5. Execute mutation
136
- result = client.query(Queries::UPDATE_ISSUE, params)
137
-
138
- # 6. Display results
139
- if result.dig("data", "issueUpdate", "success")
140
- changes = []
141
- changes << "state to '#{target_state['name']}'" if state
142
- changes << "title" if title
143
- changes << "description" if description
144
- puts "Updated #{issue_id}: #{changes.join(', ')}"
145
- else
146
- puts "Error: Failed to update issue"
147
- end
148
- end
149
-
150
- private
151
-
152
- def display_issue(issue)
153
- puts "\n#{issue['identifier']}: #{issue['title']}"
154
- puts "=" * 60
155
- puts "Status: #{issue['state']['name']}"
156
- puts "Assignee: #{issue.dig('assignee', 'name') || 'Unassigned'}"
157
- puts "Priority: #{priority_label(issue['priority'])}"
158
- puts "URL: #{issue['url']}"
159
- puts "\nDescription:"
160
- puts issue['description'] || "(no description)"
161
- puts ""
162
- end
163
-
164
- def display_issue_list(issues)
165
- puts "\nFound #{issues.length} issue(s):\n\n"
166
- issues.each do |issue|
167
- state_badge = "[#{issue['state']['name']}]".ljust(15)
168
- priority_badge = priority_label(issue['priority']).ljust(8)
169
- assignee = (issue.dig('assignee', 'name') || 'Unassigned').ljust(15)
170
-
171
- puts "#{issue['identifier'].ljust(12)} #{state_badge} #{priority_badge} #{assignee} #{issue['title']}"
172
- end
173
- puts ""
174
- end
175
-
15
+ # Include all command modules
16
+ include FetchIssue
17
+ include ListIssues
18
+ include MyIssues
19
+ include ListTeams
20
+ include ListProjects
21
+ include AddComment
22
+ include UpdateIssueState
23
+ include UpdateIssueDescription
24
+ include CreateIssue
25
+
26
+ # Expose formatters for backward compatibility
176
27
  def priority_label(priority)
177
- case priority
178
- when 0 then "None"
179
- when 1 then "Urgent"
180
- when 2 then "High"
181
- when 3 then "Medium"
182
- when 4 then "Low"
183
- else "Unknown"
184
- end
185
- end
186
-
187
- def display_project_list(projects)
188
- puts "\nFound #{projects.length} project(s):\n\n"
189
- projects.each do |project|
190
- state_badge = "[#{project['state']}]".ljust(15)
191
- progress = project['progress'] ? "#{(project['progress'] * 100).round}%" : "0%"
192
- progress_badge = progress.ljust(6)
193
- lead = (project.dig('lead', 'name') || 'No lead').ljust(20)
194
-
195
- puts "#{project['name'].ljust(30)} #{state_badge} #{progress_badge} #{lead}"
196
-
197
- if project['description'] && !project['description'].empty?
198
- # Show first line of description
199
- first_line = project['description'].lines.first&.strip
200
- puts " #{first_line[0..80]}#{'...' if first_line && first_line.length > 80}" if first_line
201
- end
202
-
203
- if project['targetDate']
204
- puts " Target: #{project['targetDate']}"
205
- end
206
-
207
- puts " URL: #{project['url']}" if project['url']
208
- puts ""
209
- end
28
+ Formatters.priority_label(priority)
210
29
  end
30
+ private :priority_label
211
31
  end
212
32
  end
@@ -0,0 +1,66 @@
1
+ module Linear
2
+ module Formatters
3
+ extend self
4
+
5
+ def display_issue(issue)
6
+ puts "\n#{issue['identifier']}: #{issue['title']}"
7
+ puts "=" * 60
8
+ puts "Status: #{issue['state']['name']}"
9
+ puts "Assignee: #{issue.dig('assignee', 'name') || 'Unassigned'}"
10
+ puts "Priority: #{priority_label(issue['priority'])}"
11
+ puts "URL: #{issue['url']}"
12
+ puts "\nDescription:"
13
+ puts issue['description'] || "(no description)"
14
+ puts ""
15
+ end
16
+
17
+ def display_issue_list(issues)
18
+ puts "\nFound #{issues.length} issue(s):\n\n"
19
+ issues.each do |issue|
20
+ state_badge = "[#{issue['state']['name']}]".ljust(15)
21
+ priority_badge = priority_label(issue['priority']).ljust(8)
22
+ assignee = (issue.dig('assignee', 'name') || 'Unassigned').ljust(15)
23
+
24
+ puts "#{issue['identifier'].ljust(12)} #{state_badge} #{priority_badge} #{assignee} #{issue['title']}"
25
+ end
26
+ puts ""
27
+ end
28
+
29
+ def priority_label(priority)
30
+ case priority
31
+ when 0 then "None"
32
+ when 1 then "Urgent"
33
+ when 2 then "High"
34
+ when 3 then "Medium"
35
+ when 4 then "Low"
36
+ else "Unknown"
37
+ end
38
+ end
39
+
40
+ def display_project_list(projects)
41
+ puts "\nFound #{projects.length} project(s):\n\n"
42
+ projects.each do |project|
43
+ state_badge = "[#{project['state']}]".ljust(15)
44
+ progress = project['progress'] ? "#{(project['progress'] * 100).round}%" : "0%"
45
+ progress_badge = progress.ljust(6)
46
+ lead = (project.dig('lead', 'name') || 'No lead').ljust(20)
47
+
48
+ puts "#{project['name'].ljust(30)} #{state_badge} #{progress_badge} #{lead}"
49
+ puts " ID: #{project['id']}"
50
+
51
+ if project['description'] && !project['description'].empty?
52
+ # Show first line of description
53
+ first_line = project['description'].lines.first&.strip
54
+ puts " #{first_line[0..80]}#{'...' if first_line && first_line.length > 80}" if first_line
55
+ end
56
+
57
+ if project['targetDate']
58
+ puts " Target: #{project['targetDate']}"
59
+ end
60
+
61
+ puts " URL: #{project['url']}" if project['url']
62
+ puts ""
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,18 @@
1
+ module Linear
2
+ module Queries
3
+ CREATE_COMMENT = <<~GQL
4
+ mutation($issueId: String!, $body: String!) {
5
+ commentCreate(input: {
6
+ issueId: $issueId
7
+ body: $body
8
+ }) {
9
+ success
10
+ comment {
11
+ id
12
+ body
13
+ }
14
+ }
15
+ }
16
+ GQL
17
+ end
18
+ end
@@ -0,0 +1,25 @@
1
+ module Linear
2
+ module Queries
3
+ CREATE_ISSUE = <<~GQL
4
+ mutation($teamId: String!, $title: String!, $description: String, $priority: Int, $stateId: String, $assigneeId: String, $projectId: String) {
5
+ issueCreate(input: {
6
+ teamId: $teamId
7
+ title: $title
8
+ description: $description
9
+ priority: $priority
10
+ stateId: $stateId
11
+ assigneeId: $assigneeId
12
+ projectId: $projectId
13
+ }) {
14
+ success
15
+ issue {
16
+ id
17
+ identifier
18
+ title
19
+ url
20
+ }
21
+ }
22
+ }
23
+ GQL
24
+ end
25
+ end
@@ -0,0 +1,26 @@
1
+ module Linear
2
+ module Queries
3
+ ISSUE = <<~GQL
4
+ query($id: String!) {
5
+ issue(id: $id) {
6
+ id
7
+ identifier
8
+ title
9
+ description
10
+ state {
11
+ name
12
+ type
13
+ }
14
+ assignee {
15
+ name
16
+ email
17
+ }
18
+ priority
19
+ createdAt
20
+ updatedAt
21
+ url
22
+ }
23
+ }
24
+ GQL
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module Linear
2
+ module Queries
3
+ LIST_ISSUES = <<~GQL
4
+ query($filter: IssueFilter!) {
5
+ issues(filter: $filter) {
6
+ nodes {
7
+ id
8
+ identifier
9
+ title
10
+ state {
11
+ name
12
+ type
13
+ }
14
+ assignee {
15
+ name
16
+ }
17
+ priority
18
+ url
19
+ }
20
+ }
21
+ }
22
+ GQL
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module Linear
2
+ module Queries
3
+ MY_ISSUES = <<~GQL
4
+ query {
5
+ viewer {
6
+ assignedIssues {
7
+ nodes {
8
+ id
9
+ identifier
10
+ title
11
+ state {
12
+ name
13
+ type
14
+ }
15
+ priority
16
+ url
17
+ }
18
+ }
19
+ }
20
+ }
21
+ GQL
22
+ end
23
+ end
@@ -0,0 +1,24 @@
1
+ module Linear
2
+ module Queries
3
+ PROJECTS = <<~GQL
4
+ query {
5
+ projects {
6
+ nodes {
7
+ id
8
+ name
9
+ description
10
+ state
11
+ progress
12
+ startDate
13
+ targetDate
14
+ url
15
+ lead {
16
+ name
17
+ email
18
+ }
19
+ }
20
+ }
21
+ }
22
+ GQL
23
+ end
24
+ end
@@ -0,0 +1,15 @@
1
+ module Linear
2
+ module Queries
3
+ TEAMS = <<~GQL
4
+ query {
5
+ teams {
6
+ nodes {
7
+ id
8
+ key
9
+ name
10
+ }
11
+ }
12
+ }
13
+ GQL
14
+ end
15
+ end
@@ -0,0 +1,22 @@
1
+ module Linear
2
+ module Queries
3
+ UPDATE_ISSUE = <<~GQL
4
+ mutation($issueId: String!, $stateId: String, $description: String) {
5
+ issueUpdate(id: $issueId, input: {
6
+ stateId: $stateId
7
+ description: $description
8
+ }) {
9
+ success
10
+ issue {
11
+ id
12
+ identifier
13
+ state {
14
+ name
15
+ }
16
+ description
17
+ }
18
+ }
19
+ }
20
+ GQL
21
+ end
22
+ end
@@ -0,0 +1,17 @@
1
+ module Linear
2
+ module Queries
3
+ WORKFLOW_STATES = <<~GQL
4
+ query($teamId: String!) {
5
+ team(id: $teamId) {
6
+ states {
7
+ nodes {
8
+ id
9
+ name
10
+ type
11
+ }
12
+ }
13
+ }
14
+ }
15
+ GQL
16
+ end
17
+ end
@@ -1,150 +1,14 @@
1
+ require_relative "queries/issue"
2
+ require_relative "queries/list_issues"
3
+ require_relative "queries/my_issues"
4
+ require_relative "queries/teams"
5
+ require_relative "queries/projects"
6
+ require_relative "queries/workflow_states"
7
+ require_relative "queries/create_comment"
8
+ require_relative "queries/update_issue"
9
+ require_relative "queries/create_issue"
10
+
1
11
  module Linear
2
12
  module Queries
3
- ISSUE = <<~GQL
4
- query($id: String!) {
5
- issue(id: $id) {
6
- id
7
- identifier
8
- title
9
- description
10
- state {
11
- name
12
- type
13
- }
14
- assignee {
15
- name
16
- email
17
- }
18
- priority
19
- createdAt
20
- updatedAt
21
- url
22
- }
23
- }
24
- GQL
25
-
26
- LIST_ISSUES = <<~GQL
27
- query($filter: IssueFilter!) {
28
- issues(filter: $filter) {
29
- nodes {
30
- id
31
- identifier
32
- title
33
- state {
34
- name
35
- type
36
- }
37
- assignee {
38
- name
39
- }
40
- priority
41
- url
42
- }
43
- }
44
- }
45
- GQL
46
-
47
- MY_ISSUES = <<~GQL
48
- query {
49
- viewer {
50
- assignedIssues {
51
- nodes {
52
- id
53
- identifier
54
- title
55
- state {
56
- name
57
- type
58
- }
59
- priority
60
- url
61
- }
62
- }
63
- }
64
- }
65
- GQL
66
-
67
- TEAMS = <<~GQL
68
- query {
69
- teams {
70
- nodes {
71
- id
72
- key
73
- name
74
- }
75
- }
76
- }
77
- GQL
78
-
79
- PROJECTS = <<~GQL
80
- query {
81
- projects {
82
- nodes {
83
- id
84
- name
85
- description
86
- state
87
- progress
88
- startDate
89
- targetDate
90
- url
91
- lead {
92
- name
93
- email
94
- }
95
- }
96
- }
97
- }
98
- GQL
99
-
100
- WORKFLOW_STATES = <<~GQL
101
- query($teamId: String!) {
102
- team(id: $teamId) {
103
- states {
104
- nodes {
105
- id
106
- name
107
- type
108
- }
109
- }
110
- }
111
- }
112
- GQL
113
-
114
- CREATE_COMMENT = <<~GQL
115
- mutation($issueId: String!, $body: String!) {
116
- commentCreate(input: {
117
- issueId: $issueId
118
- body: $body
119
- }) {
120
- success
121
- comment {
122
- id
123
- body
124
- }
125
- }
126
- }
127
- GQL
128
-
129
- UPDATE_ISSUE = <<~GQL
130
- mutation($issueId: String!, $stateId: String, $description: String, $title: String) {
131
- issueUpdate(id: $issueId, input: {
132
- stateId: $stateId
133
- description: $description
134
- title: $title
135
- }) {
136
- success
137
- issue {
138
- id
139
- identifier
140
- title
141
- state {
142
- name
143
- }
144
- description
145
- }
146
- }
147
- }
148
- GQL
149
13
  end
150
14
  end
@@ -1,3 +1,3 @@
1
1
  module Linear
2
- VERSION = "0.1.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/linear.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require_relative "linear/version"
2
2
  require_relative "linear/client"
3
3
  require_relative "linear/queries"
4
+ require_relative "linear/formatters"
4
5
  require_relative "linear/commands"
5
6
 
6
7
  module Linear
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: linear-rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dave Kinkead
@@ -36,7 +36,26 @@ files:
36
36
  - lib/linear.rb
37
37
  - lib/linear/client.rb
38
38
  - lib/linear/commands.rb
39
+ - lib/linear/commands/add_comment.rb
40
+ - lib/linear/commands/create_issue.rb
41
+ - lib/linear/commands/fetch_issue.rb
42
+ - lib/linear/commands/list_issues.rb
43
+ - lib/linear/commands/list_projects.rb
44
+ - lib/linear/commands/list_teams.rb
45
+ - lib/linear/commands/my_issues.rb
46
+ - lib/linear/commands/update_issue_description.rb
47
+ - lib/linear/commands/update_issue_state.rb
48
+ - lib/linear/formatters.rb
39
49
  - lib/linear/queries.rb
50
+ - lib/linear/queries/create_comment.rb
51
+ - lib/linear/queries/create_issue.rb
52
+ - lib/linear/queries/issue.rb
53
+ - lib/linear/queries/list_issues.rb
54
+ - lib/linear/queries/my_issues.rb
55
+ - lib/linear/queries/projects.rb
56
+ - lib/linear/queries/teams.rb
57
+ - lib/linear/queries/update_issue.rb
58
+ - lib/linear/queries/workflow_states.rb
40
59
  - lib/linear/version.rb
41
60
  - readme.md
42
61
  homepage: https://github.com/davekinkead/linear-rb